Posts for later, hashtags followed, and Explore

The composer's clock button gives a post a time (scheduled_at, at least five
minutes ahead; located and group posts have none), and /scheduled moves or
drops what waits. A hashtag's feed tab shows its week and follows it, so its
public posts come home. Explore (/explore) shows what PrivaPub says is
trending, hashtags, posts and links from public posts it holds, the
directory by activity or by joining, and the hashtags the persona follows.
DiscoveryService does the calls; the e2e tests check each against PrivaPub's
own answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 03:53:51 +02:00
1 parent 9a752d9288
commit 6d5cf17723
17 files changed
+1001 -7

No files matched your search

+43 -2
View File
@@ -2,7 +2,7 @@
@* The feed page's tabs, one per kind of feed, scrolling sideways on narrow screens. Groups adds a choice of the
persona's communities and circles, Lists a choice of its lists (made and filled on /lists), Tag the hashtag being
followed. The stats icon shows how the current feed was
read, with its week and a button to follow it (its public posts then come home). The stats icon shows how the current feed was
read; the buttons on the right open the composer and, on narrow screens, the globe. *@
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2 min-w-0">
@@ -74,7 +74,17 @@
}
else if (Filter.TimelineType is TimelineType.Tag)
{
<p class="text-sm inline-flex items-center gap-2"><i class="ion-md-pricetag"></i><b>#@Filter.Tag</b></p>
<div class="flex flex-wrap items-center gap-2 text-sm">
<i class="ion-md-pricetag"></i><b>#@Filter.Tag</b>
@if (tag is not null)
{
<span class="text-xs opacity-70 tabular-nums">@Localizer["{0} posts this week, {1} people today", tag.WeekUses, tag.TodayAccounts]</span>
<DButton Size="DSize.Xs" Color="@(tag.Following == true ? DColor.Neutral : DColor.Primary)" Disabled="@(IsDefaultDisabled || isFollowing)"
OnClick="ToggleTagFollow" data-tag-follow>
<span>@(tag.Following == true ? Localizer["Stop following #{0}", tag.Name] : Localizer["Follow #{0}", tag.Name])</span>
</DButton>
}
</div>
}
</div>
@@ -100,8 +110,39 @@
ViewGroup SelectedGroup => Groups.FirstOrDefault(g => g.Id == Filter.GroupId);
[Inject] DiscoveryService DiscoveryService { get; set; }
Tag tag;
string loadedTag;
bool isFollowing;
protected override void OnInitialized() => IsLoading = false;
// the hashtag being read: its week and whether the persona follows it, read again when it changes
protected override async Task OnParametersSetAsync()
{
var wanted = Filter.TimelineType is TimelineType.Tag ? Filter.Tag : null;
if (wanted == loadedTag)
return;
loadedTag = wanted;
tag = wanted is { Length: > 0 } ? await DiscoveryService.Tag(wanted) : null;
}
async Task ToggleTagFollow()
{
isFollowing = true;
var result = await DiscoveryService.FollowTag(tag.Name, tag.Following != true);
isFollowing = false;
if (result.IsInvalid)
{
Toast.AddResponseError(result);
return;
}
tag = (Tag)result.Data;
Toast.AddResponseSuccess(tag.Following == true ? Localizer["Following #{0}: its public posts come home.", tag.Name] : Localizer["No longer following #{0}.", tag.Name]);
}
string TabName(TimelineType type) => type switch
{
TimelineType.Federation => Localizer["Federated"],