Files
decePubClient/Components/AccountListDialog.razor
T
thepraandClaude Opus 5.5 b8d7858acb
Build / Build (push) Successful in 1m8s
R4 the client catches up: follow requests, followers, downvotes, dismissing, filtering a post, quote policies, statistics in detail
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
2026-10-17 04:02:20 +02:00

137 lines
4.6 KiB
Plaintext

@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();
}