Follows page
This commit is contained in:
parent
adf2449d37
commit
b3072c81ba
|
@ -335,6 +335,18 @@ nav a i {
|
||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-menu .option .handle {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-menu .option .pill {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 8px;
|
||||||
|
background: var(--color-highlight);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 0 5px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.handle {
|
.handle {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -375,6 +387,20 @@ form.follow {
|
||||||
float: right;
|
float: right;
|
||||||
margin: 20px 0 0 0;
|
margin: 20px 0 0 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.follow.has-reverse {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.follow .reverse-follow {
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.follow button {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form h1 {
|
form h1 {
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.views.static import serve
|
||||||
from activities.views import posts, search, timelines
|
from activities.views import posts, search, timelines
|
||||||
from core import views as core
|
from core import views as core
|
||||||
from stator import views as stator
|
from stator import views as stator
|
||||||
from users.views import activitypub, admin, auth, identity, settings
|
from users.views import activitypub, admin, auth, follows, identity, settings
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", core.homepage),
|
path("", core.homepage),
|
||||||
|
@ -31,6 +31,11 @@ urlpatterns = [
|
||||||
settings.ProfilePage.as_view(),
|
settings.ProfilePage.as_view(),
|
||||||
name="settings_profile",
|
name="settings_profile",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"settings/follows/",
|
||||||
|
follows.FollowsPage.as_view(),
|
||||||
|
name="settings_follows",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"settings/interface/",
|
"settings/interface/",
|
||||||
settings.InterfacePage.as_view(),
|
settings.InterfacePage.as_view(),
|
||||||
|
|
|
@ -13,16 +13,25 @@
|
||||||
<img src="{{ identity.local_icon_url }}" class="icon">
|
<img src="{{ identity.local_icon_url }}" class="icon">
|
||||||
|
|
||||||
{% if request.identity %}
|
{% if request.identity %}
|
||||||
<form action="{{ identity.urls.action }}" method="POST" class="inline follow">
|
{% if identity == request.identity %}
|
||||||
{% csrf_token %}
|
<form class="inline follow">
|
||||||
{% if follow %}
|
<a class="button" href="{% url "settings_profile" %}">Edit Profile</a>
|
||||||
<input type="hidden" name="action" value="unfollow">
|
</form>
|
||||||
<button>Unfollow</button>
|
{% else %}
|
||||||
{% else %}
|
<form action="{{ identity.urls.action }}" method="POST" class="inline follow {% if reverse_follow %}has-reverse{% endif %}">
|
||||||
<input type="hidden" name="action" value="follow">
|
{% csrf_token %}
|
||||||
<button>Follow</button>
|
{% if reverse_follow %}
|
||||||
{% endif %}
|
<span class="reverse-follow">Follows You</span>
|
||||||
</form>
|
{% endif %}
|
||||||
|
{% if follow %}
|
||||||
|
<input type="hidden" name="action" value="unfollow">
|
||||||
|
<button>Unfollow</button>
|
||||||
|
{% else %}
|
||||||
|
<input type="hidden" name="action" value="follow">
|
||||||
|
<button>Follow</button>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ identity.name_or_handle }} <small>@{{ identity.handle }}</small>
|
{{ identity.name_or_handle }} <small>@{{ identity.handle }}</small>
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
<a href="{% url "settings_interface" %}" {% if section == "interface" %}class="selected"{% endif %}>
|
<a href="{% url "settings_interface" %}" {% if section == "interface" %}class="selected"{% endif %}>
|
||||||
<i class="fa-solid fa-display"></i> Interface
|
<i class="fa-solid fa-display"></i> Interface
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url "settings_follows" %}" {% if section == "follows" %}class="selected"{% endif %}>
|
||||||
|
<i class="fa-solid fa-arrow-right-arrow-left"></i> Follows
|
||||||
|
</a>
|
||||||
{% if request.user.admin %}
|
{% if request.user.admin %}
|
||||||
<h3>Account</h3>
|
<h3>Account</h3>
|
||||||
<a href="{% url "settings_security" %}" {% if section == "security" %}class="selected"{% endif %}>
|
<a href="{% url "settings_security" %}" {% if section == "security" %}class="selected"{% endif %}>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "settings/base.html" %}
|
||||||
|
|
||||||
|
{% block subtitle %}Follows{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<section class="icon-menu">
|
||||||
|
{% for identity, details in identities %}
|
||||||
|
<a class="option" href="{{ identity.urls.view }}">
|
||||||
|
<img src="{{ identity.local_icon_url }}">
|
||||||
|
<span class="handle">
|
||||||
|
{{ identity.name_or_handle }}
|
||||||
|
<small>@{{ identity.handle }}</small>
|
||||||
|
</span>
|
||||||
|
{% if details.outbound %}
|
||||||
|
<span class="pill">Following</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if details.inbound %}
|
||||||
|
<span class="pill">Follows You</span>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% empty %}
|
||||||
|
<p class="option empty">You have no follows.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
{% endblock %}
|
|
@ -23,6 +23,10 @@ class FollowStates(StateGraph):
|
||||||
accepted.transitions_to(undone)
|
accepted.transitions_to(undone)
|
||||||
undone.transitions_to(undone_remotely)
|
undone.transitions_to(undone_remotely)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def group_active(cls):
|
||||||
|
return [cls.unrequested, cls.local_requested, cls.accepted]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def handle_unrequested(cls, instance: "Follow"):
|
async def handle_unrequested(cls, instance: "Follow"):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
from users.decorators import identity_required
|
||||||
|
from users.models import FollowStates
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(identity_required, name="dispatch")
|
||||||
|
class FollowsPage(TemplateView):
|
||||||
|
"""
|
||||||
|
Shows followers/follows.
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_name = "settings/follows.html"
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
# Gather all identities with a following relationship with us
|
||||||
|
identities = {}
|
||||||
|
for outbound_follow in self.request.identity.outbound_follows.filter(
|
||||||
|
state__in=FollowStates.group_active()
|
||||||
|
):
|
||||||
|
identities.setdefault(outbound_follow.target, {})[
|
||||||
|
"outbound"
|
||||||
|
] = outbound_follow
|
||||||
|
for inbound_follow in self.request.identity.inbound_follows.filter(
|
||||||
|
state__in=FollowStates.group_active()
|
||||||
|
):
|
||||||
|
identities.setdefault(inbound_follow.source, {})["inbound"] = inbound_follow
|
||||||
|
|
||||||
|
return {
|
||||||
|
"section": "follows",
|
||||||
|
"identities": sorted(identities.items(), key=lambda i: i[0].username),
|
||||||
|
}
|
|
@ -28,18 +28,22 @@ class ViewIdentity(TemplateView):
|
||||||
if identity.data_age > Config.system.identity_max_age:
|
if identity.data_age > Config.system.identity_max_age:
|
||||||
identity.transition_perform(IdentityStates.outdated)
|
identity.transition_perform(IdentityStates.outdated)
|
||||||
follow = None
|
follow = None
|
||||||
|
reverse_follow = None
|
||||||
if self.request.identity:
|
if self.request.identity:
|
||||||
follow = Follow.maybe_get(self.request.identity, identity)
|
follow = Follow.maybe_get(self.request.identity, identity)
|
||||||
if follow and follow.state not in [
|
if follow and follow.state not in FollowStates.group_active():
|
||||||
FollowStates.unrequested,
|
|
||||||
FollowStates.local_requested,
|
|
||||||
FollowStates.accepted,
|
|
||||||
]:
|
|
||||||
follow = None
|
follow = None
|
||||||
|
reverse_follow = Follow.maybe_get(identity, self.request.identity)
|
||||||
|
if (
|
||||||
|
reverse_follow
|
||||||
|
and reverse_follow.state not in FollowStates.group_active()
|
||||||
|
):
|
||||||
|
reverse_follow = None
|
||||||
return {
|
return {
|
||||||
"identity": identity,
|
"identity": identity,
|
||||||
"posts": posts,
|
"posts": posts,
|
||||||
"follow": follow,
|
"follow": follow,
|
||||||
|
"reverse_follow": reverse_follow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue