Option to show/hide boosts for a followed user (#317)
This commit is contained in:
parent
0b208d3bf7
commit
eea83214cb
|
@ -32,9 +32,9 @@ class PostInteractionStates(StateGraph):
|
|||
interaction = await instance.afetch_full()
|
||||
# Boost: send a copy to all people who follow this user
|
||||
if interaction.type == interaction.Types.boost:
|
||||
async for follow in interaction.identity.inbound_follows.select_related(
|
||||
"source", "target"
|
||||
):
|
||||
async for follow in interaction.identity.inbound_follows.filter(
|
||||
boosts=True
|
||||
).select_related("source", "target"):
|
||||
if follow.source.local or follow.target.local:
|
||||
await FanOut.objects.acreate(
|
||||
type=FanOut.Types.interaction,
|
||||
|
@ -294,7 +294,7 @@ class PostInteraction(StatorModel):
|
|||
# Boosts (announces) go to everyone who follows locally
|
||||
if interaction.type == cls.Types.boost:
|
||||
for follow in Follow.objects.filter(
|
||||
target=interaction.identity, source__local=True
|
||||
target=interaction.identity, source__local=True, boosts=True
|
||||
):
|
||||
TimelineEvent.add_post_interaction(follow.source, interaction)
|
||||
# Likes go to just the author of the post
|
||||
|
|
|
@ -175,12 +175,12 @@ def account_statuses(
|
|||
|
||||
@api_router.post("/v1/accounts/{id}/follow", response=schemas.Relationship)
|
||||
@identity_required
|
||||
def account_follow(request, id: str):
|
||||
def account_follow(request, id: str, reblogs: bool = True):
|
||||
identity = get_object_or_404(
|
||||
Identity.objects.exclude(restriction=Identity.Restriction.blocked), pk=id
|
||||
)
|
||||
service = IdentityService(identity)
|
||||
service.follow_from(request.identity)
|
||||
service.follow_from(request.identity, boosts=reblogs)
|
||||
return service.mastodon_json_relationship(request.identity)
|
||||
|
||||
|
||||
|
|
|
@ -595,6 +595,8 @@ fieldset legend {
|
|||
.right-column form,
|
||||
form.inline {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display:inline;
|
||||
}
|
||||
|
||||
div.follow-profile {
|
||||
|
@ -604,11 +606,11 @@ div.follow-profile {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
div.follow-profile.has-reverse {
|
||||
.follow-profile.has-reverse {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
div.follow-profile .reverse-follow {
|
||||
.follow-profile .reverse-follow {
|
||||
display: block;
|
||||
margin: 0 0 5px 0;
|
||||
}
|
||||
|
@ -644,6 +646,7 @@ div.follow-profile .actions menu {
|
|||
top: 43px;
|
||||
}
|
||||
|
||||
|
||||
div.follow-profile .actions menu.enabled {
|
||||
display: block;
|
||||
min-width: 160px;
|
||||
|
@ -658,7 +661,27 @@ div.follow-profile .actions menu a {
|
|||
color: var(--color-text-dull);
|
||||
}
|
||||
|
||||
div.follow-profile .actions menu a i {
|
||||
.follow-profile .actions menu button {
|
||||
background: none !important;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
padding: 4px 10px;
|
||||
color: var(--color-text-dull);
|
||||
}
|
||||
|
||||
.follow-profile .actions menu button i {
|
||||
margin-right: 4px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.follow-profile .actions button:hover {
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.follow-profile .actions menu a i {
|
||||
margin-right: 4px;
|
||||
width: 16px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
|
||||
<div class="inline follow-profile {% if reverse_follow %}has-reverse{% endif %}">
|
||||
|
||||
<div class="actions" role="menubar">
|
||||
{% if request.identity == identity %}
|
||||
<a href="{% url "settings_profile" %}" class="button" title="Edit Profile">
|
||||
|
@ -29,6 +27,18 @@
|
|||
<i class="fa-solid fa-bars"></i>
|
||||
</a>
|
||||
<menu>
|
||||
{% if follow %}
|
||||
<form action="{{ identity.urls.action }}" method="POST" class="inline">
|
||||
{% csrf_token %}
|
||||
{% if follow.boosts %}
|
||||
<input type="hidden" name="action" value="hide_boosts">
|
||||
<button role="menuitem"><i class="fa-solid fa-retweet"></i> Hide boosts</button>
|
||||
{% else %}
|
||||
<input type="hidden" name="action" value="show_boosts">
|
||||
<button role="menuitem"><i class="fa-solid fa-retweet"></i> Show boosts</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if request.user.admin %}
|
||||
<a href="{{ identity.urls.admin_edit }}" role="menuitem">
|
||||
<i class="fa-solid fa-user-gear"></i> View in Admin
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.1.4 on 2022-12-29 19:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("users", "0007_remove_invite_email_invite_expires_invite_uses"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="follow",
|
||||
name="boosts",
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
|
@ -112,6 +112,10 @@ class Follow(StatorModel):
|
|||
related_name="inbound_follows",
|
||||
)
|
||||
|
||||
boosts = models.BooleanField(
|
||||
default=True, help_text="Also follow boosts from this user"
|
||||
)
|
||||
|
||||
uri = models.CharField(blank=True, null=True, max_length=500)
|
||||
note = models.TextField(blank=True, null=True)
|
||||
|
||||
|
@ -139,7 +143,7 @@ class Follow(StatorModel):
|
|||
return None
|
||||
|
||||
@classmethod
|
||||
def create_local(cls, source, target):
|
||||
def create_local(cls, source, target, boosts=True):
|
||||
"""
|
||||
Creates a Follow from a local Identity to the target
|
||||
(which can be local or remote).
|
||||
|
@ -150,8 +154,13 @@ class Follow(StatorModel):
|
|||
raise ValueError("You cannot initiate follows from a remote Identity")
|
||||
try:
|
||||
follow = Follow.objects.get(source=source, target=target)
|
||||
if follow.boosts != boosts:
|
||||
follow.boosts = boosts
|
||||
follow.save()
|
||||
except Follow.DoesNotExist:
|
||||
follow = Follow.objects.create(source=source, target=target, uri="")
|
||||
follow = Follow.objects.create(
|
||||
source=source, target=target, boosts=boosts, uri=""
|
||||
)
|
||||
follow.uri = source.actor_uri + f"follow/{follow.pk}/"
|
||||
# TODO: Local follow approvals
|
||||
if target.local:
|
||||
|
|
|
@ -31,16 +31,20 @@ class IdentityService:
|
|||
.select_related("domain")
|
||||
)
|
||||
|
||||
def follow_from(self, from_identity: Identity) -> Follow:
|
||||
def follow_from(self, from_identity: Identity, boosts=True) -> Follow:
|
||||
"""
|
||||
Follows a user (or does nothing if already followed).
|
||||
Returns the follow.
|
||||
"""
|
||||
existing_follow = Follow.maybe_get(from_identity, self.identity)
|
||||
if not existing_follow:
|
||||
return Follow.create_local(from_identity, self.identity)
|
||||
return Follow.create_local(from_identity, self.identity, boosts=boosts)
|
||||
elif existing_follow.state not in FollowStates.group_active():
|
||||
existing_follow.transition_perform(FollowStates.unrequested)
|
||||
|
||||
if existing_follow.boosts != boosts:
|
||||
existing_follow.boosts = boosts
|
||||
existing_follow.save()
|
||||
return cast(Follow, existing_follow)
|
||||
|
||||
def unfollow_from(self, from_identity: Identity):
|
||||
|
@ -56,17 +60,20 @@ class IdentityService:
|
|||
Returns a Relationship object for the from_identity's relationship
|
||||
with this identity.
|
||||
"""
|
||||
|
||||
follow = self.identity.inbound_follows.filter(
|
||||
source=from_identity,
|
||||
state__in=FollowStates.group_active(),
|
||||
).first()
|
||||
|
||||
return {
|
||||
"id": self.identity.pk,
|
||||
"following": self.identity.inbound_follows.filter(
|
||||
source=from_identity,
|
||||
state__in=FollowStates.group_active(),
|
||||
).exists(),
|
||||
"following": follow is not None,
|
||||
"followed_by": self.identity.outbound_follows.filter(
|
||||
target=from_identity,
|
||||
state__in=FollowStates.group_active(),
|
||||
).exists(),
|
||||
"showing_reblogs": True,
|
||||
"showing_reblogs": follow.boosts,
|
||||
"notifying": False,
|
||||
"blocking": False,
|
||||
"blocked_by": False,
|
||||
|
@ -75,7 +82,7 @@ class IdentityService:
|
|||
"requested": False,
|
||||
"domain_blocking": False,
|
||||
"endorsed": False,
|
||||
"note": "",
|
||||
"note": follow.note or "",
|
||||
}
|
||||
|
||||
def set_summary(self, summary: str):
|
||||
|
|
|
@ -179,6 +179,10 @@ class ActionIdentity(View):
|
|||
IdentityService(identity).follow_from(self.request.identity)
|
||||
elif action == "unfollow":
|
||||
IdentityService(identity).unfollow_from(self.request.identity)
|
||||
elif action == "hide_boosts":
|
||||
IdentityService(identity).follow_from(self.request.identity, boosts=False)
|
||||
elif action == "show_boosts":
|
||||
IdentityService(identity).follow_from(self.request.identity, boosts=True)
|
||||
else:
|
||||
raise ValueError(f"Cannot handle identity action {action}")
|
||||
return redirect(identity.urls.view)
|
||||
|
|
Loading…
Reference in New Issue