R6 moderation in decePub: users and personas, accounts, report actions, deletions, suspended personas

Administration's Users & personas (administrators): logins searched and opened into their personas and groups; roles,
bans, two-factor reset and deletion of a login; silence, suspension and deletion of a persona; a group deleted. Accounts
(moderators): local personas and remote accounts searched or listed as moderated, silenced and suspended with a staff
note and a warning. Reports resolve with an action. ActDialog asks for the note, the warning or the password and is the
act's confirmation. A login deletes its own persona and the groups it owns with its password. Suspended personas can't
be chosen; one refused a token mid-session moves the session to another, or to none, once. Warnings show in
notifications, limited accounts on their profile, and moderators remove a post from its card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-17 05:41:10 +02:00
1 parent 4c88980374
commit c572471b88
30 files changed
+2604 -67

No files matched your search

+36 -1
View File
@@ -4,7 +4,8 @@
<Title>@Localizer["Groups"]</Title>
@* The current persona's communities and circles (/clientapi/group): made, joined by invitation, and left; for the ones
it runs, edited, their invitation shown, the requests to join answered and members taken out. A circle's members are
it runs, edited, their invitation shown, the requests to join answered and members taken out; for the ones it owns,
deleted with the login's password, asked first (its members are told, its name stays taken). A circle's members are
never shown to anyone but its owner and moderators, here as on every server. *@
<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">
@@ -179,6 +180,13 @@
</div>
}
</div>
<div class="flex flex-wrap items-center justify-end gap-2 pt-2 border-t border-base-300">
<span class="text-xs opacity-70 flex-1 min-w-48">@Localizer["Deleting the group tells its members, and its name stays taken."]</span>
<DButton Size="DSize.Sm" Disabled="@(IsDefaultDisabled || isSaving)" OnClick="() => deleting = group" data-group-delete-own>
<i class="ion-md-trash text-error" aria-hidden="true"></i><span>@Localizer["Delete the group"]</span>
</DButton>
</div>
}
</article>
}
@@ -186,6 +194,14 @@
</div>
</section>
@if (deleting is not null)
{
<ActDialog Question="@Localizer["Delete the group {0}?", deleting.Name ?? deleting.UserName]"
Text="@Localizer["Its members and followers are told, and its name stays taken. It cannot be undone."]"
ConfirmText="@Localizer["Delete for good"]" Danger="true" Act="group-delete" AskPassword="true" OnConfirm="Delete"
OnClosed="() => deleting = null" />
}
@code {
static readonly string[] PostingPolicies = ["followers", "anyone", "moderators"];
@@ -200,6 +216,8 @@
bool editDiscoverable;
bool editApproves;
ViewGroup open;
// the group whose deletion the dialog asks about
ViewGroup deleting;
string loadedPersonaId;
bool isSaving;
@@ -300,6 +318,23 @@
Toast.AddResponseSuccess(Localizer["You left {0}.", group.Name ?? group.UserName]);
}
async Task<WebResult> Delete(ActDialog.Answer answer)
{
var group = deleting;
var result = await GroupService.Delete(CascadingState.PersonaId, group.Id, answer.Password);
if (result.IsInvalid)
return result;
groups = [.. groups.Where(g => g.Id != group.Id)];
if (open?.Id == group.Id)
{
open = null;
members = [];
}
Toast.AddResponseSuccess(Localizer["{0} is deleted.", group.Name ?? group.UserName]);
return result;
}
async Task Toggle(ViewGroup group)
{
if (open?.Id == group.Id)