R5 the server's own settings in decePub: instance, rules, announcements, relays, media limits, the staff's log, About
Build / Build (push) Successful in 1m5s

Administration's Pleroma placeholders give way to real sections: Instance (title, descriptions, contact, thumbnail,
privacy policy, terms, the weekly activity), Rules, Announcements, Relays, Media limits and the Moderation log, next to
Reports, blocked servers (now published or obfuscated), statistics and backups; moderators see Reports and the log. Every
save sends the whole settings form built from the latest read, since PrivaPub replaces what it carries. /about shows the
server's documents to anyone. The report dialog picks the rules a violation breaks, and reports show them. Announcements
show above the feed tabs, dismissed and reacted to, live from the user stream. Login names the server.

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 04:32:57 +02:00
1 parent b8d7858acb
commit 4c88980374
33 files changed
+3743 -239

No files matched your search

+60 -4
View File
@@ -1,7 +1,9 @@
@inherits LocalizableComponentBase
@* Reports an account, and the post it was opened from, to the persona's server's moderators (/api/v1/reports).
Forwarded, a remote account's server gets the report too, from PrivaPub's own actor: it never names the persona. *@
Forwarded, a remote account's server gets the report too, from PrivaPub's own actor: it never names the persona. A
violation names the server's rules it breaks (/api/v1/instance/rules, sent as rule_ids); it is offered only while the
server has rules. *@
<DModal Placement="DModalPlacement.ResponsiveBottom" IsOpen="true" IsOpenChanged="Closed" CloseText="@Localizer["Close"]" data-report-dialog>
<Heading>@Localizer["Report {0}", Account.Name]</Heading>
<ChildContent>
@@ -11,8 +13,31 @@
<DSelect TValue="ReportCategory" @bind-Value="form.Category" data-report-category>
<option value="@ReportCategory.Spam">@Localizer[nameof(ReportCategory.Spam)]</option>
<option value="@ReportCategory.Legal">@Localizer[nameof(ReportCategory.Legal)]</option>
@if (choices.Count > 0)
{
<option value="@ReportCategory.Violation">@Localizer[nameof(ReportCategory.Violation)]</option>
}
<option value="@ReportCategory.Other">@Localizer[nameof(ReportCategory.Other)]</option>
</DSelect>
@if (form.Category is ReportCategory.Violation)
{
<fieldset class="flex flex-col gap-1 pointer-coarse:gap-2" data-report-rules>
<legend class="text-sm mb-1">@Localizer["Which of the server's rules does it break?"]</legend>
@foreach (var choice in choices)
{
<label class="flex items-start gap-2 cursor-pointer pointer-coarse:py-2">
<DCheckbox @bind-Value="choice.Picked" class="mt-0.5 flex-none" data-report-rule="@choice.Rule.Id" />
<span class="flex flex-col min-w-0">
<span class="text-sm break-words">@choice.Rule.Text</span>
@if (choice.Rule.Hint is { Length: > 0 } hint)
{
<span class="text-xs opacity-70 break-words">@hint</span>
}
</span>
</label>
}
</fieldset>
}
<DTextarea TValue="string" @bind-Value="form.Comment" Immediate="true" rows="3" autocomplete="off" maxlength="@ReportForm.CommentLength"
placeholder="@Localizer["What is wrong with it?"]" data-report-comment />
@if (Host is { } host)
@@ -30,6 +55,7 @@
@code {
[Inject] AccountService AccountService { get; set; }
[Inject] AboutService AboutService { get; set; }
[Parameter, EditorRequired] public Account Account { get; set; }
/// <summary>The post it was opened from, sent with the report.</summary>
@@ -39,17 +65,41 @@
ReportForm form = new();
bool isSending;
string error;
// the server's rules, each ticked or not, for a violation
List<RuleChoice> choices = [];
// the account's own server, for a remote account
string Host => Account.Acct?.Split('@') is [_, { Length: > 0 } host] ? host : null;
protected override void OnInitialized() => IsLoading = false;
protected override async Task OnInitializedAsync()
{
if (CascadingState.IsOffline)
{
WhenOnline(LoadData);
StartOnlinePolling();
IsLoading = false;
return;
}
await LoadData();
IsLoading = false;
}
async Task LoadData() => choices = [.. (await AboutService.Rules()).Select(rule => new RuleChoice { Rule = rule })];
async Task Send()
{
isSending = true;
error = null;
var result = await AccountService.Report(Account.Id, Post is null ? [] : [Post.Id], form.Comment, form.Category, Host is not null && form.Forward);
var ruleIds = form.Category is ReportCategory.Violation ? choices.Where(c => c.Picked).Select(c => c.Rule.Id).ToList() : [];
if (form.Category is ReportCategory.Violation && ruleIds.Count == 0)
{
error = Localizer["Tick the rules it breaks."];
return;
}
isSending = true;
var result = await AccountService.Report(Account.Id, Post is null ? [] : [Post.Id], form.Comment, form.Category, ruleIds,
Host is not null && form.Forward);
isSending = false;
if (result.IsInvalid)
{
@@ -62,4 +112,10 @@
}
Task Closed(bool open) => open ? Task.CompletedTask : OnClosed.InvokeAsync();
sealed class RuleChoice
{
public InstanceRule Rule { get; init; }
public bool Picked { get; set; }
}
}