R4 the client catches up: follow requests, followers, downvotes, dismissing, filtering a post, quote policies, statistics in detail
Build / Build (push) Successful in 1m8s
Build / Build (push) Successful in 1m8s
Follow requests answered on the persona's own profile and from the Notifications tab; its followers and the accounts it follows listed there, a follower removed (asked first). Downvotes on threadiverse posts, after the public-vote notice. A notification dismissed from its line. "Filter this post" from a card's menu, and the Filters page showing the posts a filter holds. Who may quote the persona's own posts, and revoking a quote of one. Statistics: a server in detail with "describe again", the ledger's events, the server's own days. The composer deletes an upload taken out before posting. MediaType.Documents goes. 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
b731d8c8c0
commit
b8d7858acb
25 files changed
+1543
-61
No files matched your search
@@ -0,0 +1,136 @@
|
||||
@inherits LocalizableComponentBase
|
||||
|
||||
@* The persona's own followers or the accounts it follows (/api/v1/accounts/:id/followers, /following), 40 a page by the
|
||||
Link header, each opening its profile. PrivaPub lists them to the persona alone (hide_collections), so only the
|
||||
persona's own profile opens this. A follower can be removed (remove_from_followers), asked first. *@
|
||||
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]"
|
||||
data-account-list="@(Followers ? "followers" : "following")">
|
||||
<Heading><span class="capitalize">@(Followers ? Localizer["followers"] : Localizer["following"])</span></Heading>
|
||||
<ChildContent>
|
||||
@if (IsLoading)
|
||||
{
|
||||
<p class="flex items-center gap-2 mt-3"><DLoading Variant="DLoadingVariant.Dots" LabelText="@Localizer["loading"]" />@Localizer["loading"]</p>
|
||||
}
|
||||
else if (accounts.Count == 0)
|
||||
{
|
||||
<p class="text-sm opacity-70 mt-3" data-account-list-empty>@Localizer["Nobody yet."]</p>
|
||||
}
|
||||
<div class="flex flex-col gap-2 mt-3">
|
||||
@foreach (var account in accounts)
|
||||
{
|
||||
<div class="flex flex-wrap items-center gap-2 p-2 rounded-xl neo-raised neo-2xs min-w-0" data-account="@account.Acct">
|
||||
<a class="flex flex-1 items-center gap-3 min-w-0 pointer-coarse:min-h-11" href="@($"account/{Uri.EscapeDataString(account.Id)}")">
|
||||
@* alt="": the name is next to it. *@
|
||||
<img alt="" class="h-9 w-9 flex-none object-cover rounded-full" src="@(account.Avatar ?? "imgs/icon-192.png")" />
|
||||
<span class="flex flex-col min-w-0">
|
||||
<b class="truncate">@CustomEmojis.Text(account.Name, account.Emojis)</b>
|
||||
<span class="text-xs opacity-70 truncate">@("@" + account.Acct)</span>
|
||||
</span>
|
||||
</a>
|
||||
@if (Followers)
|
||||
{
|
||||
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || busy.Contains(account.Id))" OnClick="() => RemoveFollower(account)"
|
||||
data-follower-remove>
|
||||
<i class="ion-md-person text-error" aria-hidden="true"></i><span>@Localizer["Remove from followers"]</span>
|
||||
</DButton>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (maxId is not null)
|
||||
{
|
||||
<div class="flex justify-center mt-3">
|
||||
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || isLoadingMore)" OnClick="LoadMore" data-account-list-more>
|
||||
@if (isLoadingMore)
|
||||
{
|
||||
<DLoading Size="DSize.Xs" LabelText="@Localizer["loading"]" />
|
||||
}
|
||||
<span>@Localizer["Load more"]</span>
|
||||
</DButton>
|
||||
</div>
|
||||
}
|
||||
</ChildContent>
|
||||
</DModal>
|
||||
|
||||
@code {
|
||||
[Inject] AccountService AccountService { get; set; }
|
||||
[Inject] ConfirmService Confirm { get; set; }
|
||||
|
||||
/// <summary>The persona's own account id.</summary>
|
||||
[Parameter, EditorRequired] public string AccountId { get; set; }
|
||||
/// <summary>Its followers, rather than the accounts it follows.</summary>
|
||||
[Parameter] public bool Followers { get; set; }
|
||||
[Parameter] public EventCallback OnClosed { get; set; }
|
||||
/// <summary>A follower was removed.</summary>
|
||||
[Parameter] public EventCallback<Account> OnRemoved { get; set; }
|
||||
|
||||
List<Account> accounts = [];
|
||||
HashSet<string> busy = [];
|
||||
string maxId;
|
||||
bool isLoadingMore;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (CascadingState.IsOffline)
|
||||
{
|
||||
WhenOnline(LoadData);
|
||||
StartOnlinePolling();
|
||||
IsLoading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await LoadData();
|
||||
IsLoading = false;
|
||||
}
|
||||
|
||||
async Task LoadData()
|
||||
{
|
||||
accounts = [];
|
||||
maxId = null;
|
||||
await LoadPage();
|
||||
}
|
||||
|
||||
async Task LoadPage()
|
||||
{
|
||||
var result = Followers ? await AccountService.Followers(AccountId, maxId) : await AccountService.Following(AccountId, maxId);
|
||||
if (result.IsInvalid)
|
||||
{
|
||||
Toast.AddResponseError(result);
|
||||
return;
|
||||
}
|
||||
|
||||
var page = (AccountPage)result.Data;
|
||||
var known = accounts.Select(a => a.Id).ToHashSet();
|
||||
accounts.AddRange(page.Accounts.Where(a => known.Add(a.Id)));
|
||||
maxId = page.MaxId;
|
||||
}
|
||||
|
||||
async Task LoadMore()
|
||||
{
|
||||
isLoadingMore = true;
|
||||
await LoadPage();
|
||||
isLoadingMore = false;
|
||||
}
|
||||
|
||||
async Task RemoveFollower(Account account)
|
||||
{
|
||||
var name = account.Name ?? account.Acct;
|
||||
if (!await Confirm.Ask(Localizer["Remove {0} from your followers?", name], Localizer["They no longer see your followers-only posts, and may ask to follow you again."],
|
||||
Localizer["Remove from followers"], true))
|
||||
return;
|
||||
busy.Add(account.Id);
|
||||
var result = await AccountService.RemoveFromFollowers(account.Id);
|
||||
busy.Remove(account.Id);
|
||||
if (result.IsInvalid)
|
||||
{
|
||||
Toast.AddResponseError(result);
|
||||
return;
|
||||
}
|
||||
|
||||
accounts.Remove(account);
|
||||
Toast.AddResponseSuccess(Localizer["{0} removed from your followers.", name]);
|
||||
await OnRemoved.InvokeAsync(account);
|
||||
}
|
||||
|
||||
Task Closed(bool open) => open ? Task.CompletedTask : OnClosed.InvokeAsync();
|
||||
}
|
||||
Reference in new issue
Block a user