Follows page

This commit is contained in:
Andrew Godwin 2022-11-17 20:04:01 -07:00
parent adf2449d37
commit b3072c81ba
8 changed files with 125 additions and 16 deletions

View File

@ -335,6 +335,18 @@ nav a i {
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 {
vertical-align: middle;
display: inline-block;
@ -375,6 +387,20 @@ form.follow {
float: right;
margin: 20px 0 0 0;
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 {

View File

@ -6,7 +6,7 @@ from django.views.static import serve
from activities.views import posts, search, timelines
from core import views as core
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 = [
path("", core.homepage),
@ -31,6 +31,11 @@ urlpatterns = [
settings.ProfilePage.as_view(),
name="settings_profile",
),
path(
"settings/follows/",
follows.FollowsPage.as_view(),
name="settings_follows",
),
path(
"settings/interface/",
settings.InterfacePage.as_view(),

View File

@ -13,16 +13,25 @@
<img src="{{ identity.local_icon_url }}" class="icon">
{% if request.identity %}
<form action="{{ identity.urls.action }}" method="POST" class="inline follow">
{% csrf_token %}
{% 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>
{% if identity == request.identity %}
<form class="inline follow">
<a class="button" href="{% url "settings_profile" %}">Edit Profile</a>
</form>
{% else %}
<form action="{{ identity.urls.action }}" method="POST" class="inline follow {% if reverse_follow %}has-reverse{% endif %}">
{% csrf_token %}
{% if reverse_follow %}
<span class="reverse-follow">Follows You</span>
{% 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 %}
{{ identity.name_or_handle }} <small>@{{ identity.handle }}</small>

View File

@ -6,6 +6,9 @@
<a href="{% url "settings_interface" %}" {% if section == "interface" %}class="selected"{% endif %}>
<i class="fa-solid fa-display"></i> Interface
</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 %}
<h3>Account</h3>
<a href="{% url "settings_security" %}" {% if section == "security" %}class="selected"{% endif %}>

View File

@ -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 %}

View File

@ -23,6 +23,10 @@ class FollowStates(StateGraph):
accepted.transitions_to(undone)
undone.transitions_to(undone_remotely)
@classmethod
def group_active(cls):
return [cls.unrequested, cls.local_requested, cls.accepted]
@classmethod
async def handle_unrequested(cls, instance: "Follow"):
"""

33
users/views/follows.py Normal file
View File

@ -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),
}

View File

@ -28,18 +28,22 @@ class ViewIdentity(TemplateView):
if identity.data_age > Config.system.identity_max_age:
identity.transition_perform(IdentityStates.outdated)
follow = None
reverse_follow = None
if self.request.identity:
follow = Follow.maybe_get(self.request.identity, identity)
if follow and follow.state not in [
FollowStates.unrequested,
FollowStates.local_requested,
FollowStates.accepted,
]:
if follow and follow.state not in FollowStates.group_active():
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 {
"identity": identity,
"posts": posts,
"follow": follow,
"reverse_follow": reverse_follow,
}