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
122 lines
4.6 KiB
Plaintext
122 lines
4.6 KiB
Plaintext
@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. 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>
|
|
<EditForm Model="form" OnValidSubmit="Send" class="flex flex-col gap-3 mt-3">
|
|
<DataAnnotationsValidator />
|
|
<ErrorViewer AdditionalError="@error" />
|
|
<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)
|
|
{
|
|
<DCheckbox @bind-Value="form.Forward" LabelText="@Localizer["Send a copy to {0}", host]" data-report-forward />
|
|
<p class="text-xs opacity-70">@Localizer["The copy comes from the server, never from you."]</p>
|
|
}
|
|
<div class="flex justify-end gap-2">
|
|
<DButton OnClick="() => Closed(false)" data-report-cancel>@Localizer["Cancel"]</DButton>
|
|
<DButton Type="submit" Color="DColor.Error" Disabled="@(IsDefaultDisabled || isSending)" data-report-send>@Localizer["Report"]</DButton>
|
|
</div>
|
|
</EditForm>
|
|
</ChildContent>
|
|
</DModal>
|
|
|
|
@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>
|
|
[Parameter] public Status Post { get; set; }
|
|
[Parameter] public EventCallback OnClosed { get; set; }
|
|
|
|
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 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()
|
|
{
|
|
error = null;
|
|
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)
|
|
{
|
|
Toast.AddResponseError(result);
|
|
return;
|
|
}
|
|
|
|
Toast.AddResponseSuccess(Localizer["Report sent to the moderators."]);
|
|
await OnClosed.InvokeAsync();
|
|
}
|
|
|
|
Task Closed(bool open) => open ? Task.CompletedTask : OnClosed.InvokeAsync();
|
|
|
|
sealed class RuleChoice
|
|
{
|
|
public InstanceRule Rule { get; init; }
|
|
public bool Picked { get; set; }
|
|
}
|
|
}
|