R1 cleanup: collAnon and dead code gone, no IndexedDB, honest PWA files
Build / Build (push) Successful in 51s
Build / Build (push) Successful in 51s
Starts the completion program (SocialPub docs/ROADMAP.md, "Owner decisions on finishing everything", 2026-10-17). - The localizer's namespace is decePubClient.Services; collAnon is gone from the repo. - Dead code: PagesBase, VConstants, SettingsOptions, ViewLanguage, unused APIs routes, InstanceService.Self, RequestStats.LastOf/AverageMs, PostAnon/DeleteTotallyAnon, unused AppStatusService methods and their main.js helpers (the app badge stays for Web Push), ExtensionMethods/SUtility leftovers (Markdig and BlazorZXingJs packages go), unused PublicCacheData/PageSettings/AppConfiguration fields, commented-out code. - The client log in IndexedDB, which nothing ever read, goes with the whole IndexedDB layer (DnetIndexedDb, IStorage): logs go to the browser console; main.js deletes the old "data" database once. - The solution names the real ClientModels project. Unused vendor assets (fontawesome, solid, open-iconic, rxjs) go. - robots.txt disallows everything; manifest.json and index.html say decePub; the service worker's cache name follows each publish's assets version, so old caches are purged. - MessageType is Visibility (member names kept: they are resx keys); ExpandMessage.razor is Thread.razor (same route). - i18n: dead keys removed, Italian "camera" is "punto di vista", DInputBase/DCalendar validation messages come from ErrorMessages, DataTransfer's spinner has its label. - CLAUDE.md follows: client storage, JS interop, PWA, the D fields' messages. Verified: dotnet build decePubClient.csproj and decePubClient.sln -c Release (0 errors, no new warnings); a Pasture publish served by the pasture's decepub peer. 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
e4414f1ac9
commit
fd2f2e7c65
79 files changed
+155
-6673
No files matched your search
@@ -0,0 +1,185 @@
|
||||
@page "/expand/{statusId}"
|
||||
@attribute [Authorize]
|
||||
@inherits LocalizableComponentBase
|
||||
<Title>@Localizer["Thread"]</Title>
|
||||
|
||||
@* A post with its thread (/api/v1/statuses/{id}/context): the posts it answers above, the post, the answers below.
|
||||
The cards do their own actions; a reply or a deletion updates the thread here. The persona's thread filters apply
|
||||
to the posts around the one it opened, never to that one. Opening a remote thread makes PrivaPub read its replies
|
||||
from their servers, so the thread is read twice more, a few seconds later, and what was found joins it. *@
|
||||
<section class="block relative w-full h-full neo-inset neo-xs rounded-xl">
|
||||
<div class="flex flex-col gap-4 p-4 md:p-5 w-full h-full absolute overflow-y-auto">
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<DButton Size="DSize.Sm" OnClick="Back" title="@Localizer["Back"]">
|
||||
<i class="ion-md-arrow-back"></i>
|
||||
</DButton>
|
||||
<h1 class="font-bold">@Localizer["Thread"]</h1>
|
||||
</div>
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
<p class="flex w-full items-center justify-center gap-2">
|
||||
<DLoading Variant="DLoadingVariant.Dots" LabelText="@Localizer["loading"]" />
|
||||
@Localizer["loading"]
|
||||
</p>
|
||||
}
|
||||
else if (current is null)
|
||||
{
|
||||
<p class="w-full text-center text-lg">
|
||||
<i class="ion-ios-remove-circle-outline"></i> @Localizer["This message doesn't exist."]
|
||||
</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var post in ancestors)
|
||||
{
|
||||
<Content @key="post.Key" Post="post" CssContainer="ml-6 max-w-[calc(100%-1.5rem)]" FilterIn="FilterContext.Thread" OnDeleted="Remove" OnReplied="Replied" OnTag="OpenTag" />
|
||||
}
|
||||
|
||||
<Content @key="current.Key" Post="current" Highlighted="true" IncludeExpand="false" OnDeleted="Remove" OnReplied="Replied" OnTag="OpenTag" />
|
||||
|
||||
@foreach (var post in descendants)
|
||||
{
|
||||
<Content @key="post.Key" Post="post" CssContainer="ml-6 max-w-[calc(100%-1.5rem)]" FilterIn="FilterContext.Thread" OnDeleted="Remove" OnReplied="Replied" OnTag="OpenTag" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[Inject] StatusService StatusService { get; set; }
|
||||
[Inject] InstanceService InstanceService { get; set; }
|
||||
[Inject] ILocalStorageService LocalStorage { get; set; }
|
||||
[Inject] IJSRuntime JS { get; set; }
|
||||
|
||||
[Parameter] public string StatusId { get; set; }
|
||||
|
||||
// when the thread is read again, after PrivaPub has had time to fetch the replies held on other servers
|
||||
static readonly TimeSpan[] Rereads = [TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(15)];
|
||||
|
||||
FeedPost current;
|
||||
List<FeedPost> ancestors = [];
|
||||
List<FeedPost> descendants = [];
|
||||
string loadedId;
|
||||
CancellationTokenSource rereading;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (StatusId == loadedId)
|
||||
return;
|
||||
loadedId = StatusId;
|
||||
|
||||
if (CascadingState.IsOffline)
|
||||
{
|
||||
WhenOnline(LoadData);
|
||||
StartOnlinePolling();
|
||||
IsLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await LoadData();
|
||||
}
|
||||
|
||||
async Task LoadData()
|
||||
{
|
||||
rereading?.Cancel();
|
||||
rereading?.Dispose();
|
||||
rereading = null;
|
||||
IsLoading = true;
|
||||
var status = await StatusService.Get(StatusId);
|
||||
if (status is null)
|
||||
{
|
||||
current = null;
|
||||
IsLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var context = await StatusService.Context(status.Id);
|
||||
current = FeedPost.Of(status);
|
||||
ancestors = [.. (context?.Ancestors ?? []).Select(FeedPost.Of)];
|
||||
descendants = [.. (context?.Descendants ?? []).Select(FeedPost.Of)];
|
||||
IsLoading = false;
|
||||
|
||||
var all = ancestors.Append(current).Concat(descendants).ToList();
|
||||
await InstanceService.Resolve(all.Select(p => p.Host).Append(InstanceService.SelfHost));
|
||||
InstanceService.Locate(all);
|
||||
|
||||
if (new[] { current, ancestors.FirstOrDefault() }.Any(p => p is not null && p.Host != InstanceService.SelfHost))
|
||||
{
|
||||
rereading = new();
|
||||
_ = Reread(status.Id, rereading.Token);
|
||||
}
|
||||
}
|
||||
|
||||
// the answers PrivaPub found since, in the thread's order; the cards already shown stay as they are, and one the
|
||||
// thread no longer lists (a reply just made, not yet in it) stays at the end
|
||||
async Task Reread(string statusId, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var pause in Rereads)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(pause, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var context = await StatusService.Context(statusId, cancellationToken);
|
||||
if (cancellationToken.IsCancellationRequested || context is null)
|
||||
return;
|
||||
var shown = descendants.ToDictionary(p => p.Key);
|
||||
var found = context.Descendants.Where(s => !shown.ContainsKey(s.Id)).Select(FeedPost.Of).ToList();
|
||||
if (found.Count == 0)
|
||||
continue;
|
||||
var listed = context.Descendants.Select(s => s.Id).ToHashSet();
|
||||
descendants = [.. context.Descendants.Select(s => shown.GetValueOrDefault(s.Id) ?? found.First(p => p.Key == s.Id)),
|
||||
.. descendants.Where(p => !listed.Contains(p.Key))];
|
||||
await InstanceService.Resolve(found.Select(p => p.Host));
|
||||
InstanceService.Locate(found);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void Remove(FeedPost post)
|
||||
{
|
||||
if (post.Key == current?.Key)
|
||||
{
|
||||
current = null;
|
||||
return;
|
||||
}
|
||||
ancestors.RemoveAll(p => p.Key == post.Key);
|
||||
descendants.RemoveAll(p => p.Key == post.Key);
|
||||
}
|
||||
|
||||
async Task Replied(FeedPost reply)
|
||||
{
|
||||
if (reply is null)
|
||||
return;
|
||||
descendants.Add(reply);
|
||||
await InstanceService.Resolve([reply.Host]);
|
||||
InstanceService.Locate([reply]);
|
||||
}
|
||||
|
||||
async Task OpenTag(string tag)
|
||||
{
|
||||
var filter = await LocalStorage.GetItemAsync<FeedFilter>(nameof(FeedFilter)) ?? new();
|
||||
filter.Tag = tag;
|
||||
filter.TimelineType = TimelineType.Tag;
|
||||
await LocalStorage.SetItemAsync(nameof(FeedFilter), filter);
|
||||
Navigation.NavigateTo("");
|
||||
}
|
||||
|
||||
async Task Back() => await JS.InvokeVoidAsync("history.back");
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
if (rereading is not null)
|
||||
{
|
||||
await rereading.CancelAsync();
|
||||
rereading.Dispose();
|
||||
}
|
||||
await base.DisposeAsync();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user