R8 the Mastodon API made real in decePub: featured tags, notify, languages, recommendations, notes, suggestions, the direct stream
A profile shows the hashtags an account features as chips opening their feed; under another account's header is what the persona keeps of it for itself alone: for one it follows, a bell (follow with notify), the languages of its posts that reach the home feed (follow with languages[]) and Recommend (endorsements, private here), and for any account a private note saved on blur or with Save. Settings edits the persona's featured hashtags (added by name or from those used lately, 10 at most) and its reading preferences (/clientapi/avatar/private/preferences), which CascadingState keeps per persona and the card applies to sensitive media and content warnings. A hashtag's tab features it on the profile. Explore has Who to follow (/api/v2/suggestions, dismissed for good after asking); Administration has Staff picks. The Direct tab follows the direct stream's conversation events; status notifications show the post. The follows CSV carries notify and languages. Verified: Release build, 0 errors (the 3 warnings were there before). Not run against PrivaPub or in the pasture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
1 parent
566b0e33ea
commit
d33697e3fc
28 files changed
+1784
-52
No files matched your search
+21
-5
@@ -8,9 +8,10 @@
|
||||
@* The feed page, the middle and right of three columns (NavMenu is the left): the feed with a tab per kind of feed,
|
||||
and the globe, which follows the posts in view (GlobePanel). Below lg the globe is a banner above the feed, closed by
|
||||
default on phones. Pages of older posts load as the end comes near (wwwroot/js/feed.js). Newer ones come through
|
||||
PrivaPub's streaming API (StreamingService) for the feeds it streams, and are polled every 45 seconds while the page
|
||||
is visible otherwise, or while the stream is down; either way they wait behind the "new" button. A deletion or an edit
|
||||
the stream tells is applied at once. The administrators' announcements show above the tabs (AnnouncementsBanner). *@
|
||||
PrivaPub's streaming API (StreamingService) for the feeds it streams (the direct conversations too, on the direct
|
||||
stream), and are polled every 45 seconds while the page is visible otherwise, or while the stream is down; either way
|
||||
they wait behind the "new" button. A deletion or an edit the stream tells is applied at once. The administrators'
|
||||
announcements show above the tabs (AnnouncementsBanner). *@
|
||||
<div class="flex flex-col lg:grid lg:grid-cols-[minmax(0,1fr)_minmax(320px,42%)] gap-3 md:gap-4 w-full h-full">
|
||||
|
||||
@* full screen over everything once a phone's banner is tapped; the feed underneath stays as it was, out of reach *@
|
||||
@@ -401,7 +402,7 @@
|
||||
{
|
||||
while (await pollTimer.WaitForNextTickAsync(cancellationToken))
|
||||
{
|
||||
// no stream tells of direct messages: their tab's badge is counted again on every tick
|
||||
// the direct stream is followed on its own tab only: elsewhere its badge is counted again on every tick
|
||||
if (!IsLoading && CascadingState.IsOnline && Filter.TimelineType is not TimelineType.Direct
|
||||
&& await TimelineService.UnreadConversations(cancellationToken) is var unread && unread != unreadConversations)
|
||||
{
|
||||
@@ -475,15 +476,30 @@
|
||||
TimelineType.Notifications => new("user:notification"),
|
||||
TimelineType.Tag when filter.Tag is { Length: > 0 } tag => new("hashtag", Tag: tag),
|
||||
TimelineType.Lists when filter.ListId is { Length: > 0 } list => new("list", List: list),
|
||||
TimelineType.Direct => new("direct"),
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>What the stream tells: a new post or notification waits behind the "new" button; a deletion or an edit is
|
||||
/// applied where the post is.</summary>
|
||||
/// applied where the post is. A conversation with a new message is the same item moved up with it: behind the "new"
|
||||
/// button, or at once at the top when the persona wrote that message itself.</summary>
|
||||
async Task OnStream(StreamMessage message)
|
||||
{
|
||||
switch (message.Event)
|
||||
{
|
||||
case "conversation" when message.Conversation is { LastStatus: { } last } conversation && Filter.TimelineType is TimelineType.Direct:
|
||||
var item = FeedPost.Of(conversation);
|
||||
// (the message the persona just sent shows by itself until its conversation comes)
|
||||
posts.RemoveAll(p => p.Key == item.Key || p.Conversation is null && p.Status?.Id == last.Id);
|
||||
pending.RemoveAll(p => p.Key == item.Key);
|
||||
if (item.IsOwnedBy(CascadingState.PersonaId))
|
||||
{
|
||||
posts.Insert(0, item);
|
||||
await InvokeAsync(() => Locate([item], loadCancellation?.Token ?? default));
|
||||
break;
|
||||
}
|
||||
pending.Insert(0, item);
|
||||
break;
|
||||
case "update" when message.Status is { } status && Filter.TimelineType is not TimelineType.Notifications:
|
||||
var post = FeedPost.Of(status);
|
||||
if (posts.Concat(pending).Any(p => p.Key == post.Key))
|
||||
|
||||
Reference in new issue
Block a user