diff --git a/CLAUDE.md b/CLAUDE.md index d245d48..c4b6d93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -285,7 +285,7 @@ supplies: `/clientapi/post/nearby` and have no Mastodon actions), `StatusService` (publish, favourite, boost, bookmark, delete, threads, mute, block, poll votes, emoji reactions through Pleroma's `/api/v1/pleroma/statuses/:id/reactions`, joining and leaving remote events through `/api/privapub/v1/statuses/:id/join|leave`; located and group posts go through - `/clientapi/post/insert`), `AccountService` (profiles, relationships, follow, + `/clientapi/post/insert`, and a located post's author deletes it through `/clientapi/post/delete`, its card's one action), `AccountService` (profiles, relationships, follow, block, mute, follow requests, `/api/v2/search`, whose handles and URLs PrivaPub resolves), `ListService` (the persona's lists and their members; members are only accounts it follows, found with `accounts/search?following=true`), `FilterService` (the persona's v2 filters, their keywords and filtered posts), `DiscoveryService` (hashtags and diff --git a/ClientJsonContext.cs b/ClientJsonContext.cs index d4b14d2..9f978ed 100644 --- a/ClientJsonContext.cs +++ b/ClientJsonContext.cs @@ -49,6 +49,7 @@ namespace decePubClient; [JsonSerializable(typeof(ViewPost))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(InsertPostForm))] +[JsonSerializable(typeof(DeletePostForm))] [JsonSerializable(typeof(ViewReport))] [JsonSerializable(typeof(List))] [JsonSerializable(typeof(DomainBlockForm))] diff --git a/Components/Content.razor b/Components/Content.razor index 9fc8522..87d5b37 100644 --- a/Components/Content.razor +++ b/Components/Content.razor @@ -7,7 +7,7 @@ quoted post (Compact) has none, since it is not an item of the feed. A CDN-fronted server's post has no point, only its CDN's group (data-cdn); data-post-at is when it was written, for the globe's Sun; data-uri is its ActivityPub id, which the e2e tests (tests/e2e) find it by. The persona's filters apply where the page says the card is (FilterIn): a - hide filter leaves it out, a warn filter folds it behind the filter's title, a blur filter blurs its media. Located posts have no actions: PrivaPub keeps them off the Mastodon API. Nerd stats are on + hide filter leaves it out, a warn filter folds it behind the filter's title, a blur filter blurs its media. Located posts have no Mastodon actions (PrivaPub keeps them off the Mastodon API); their author deletes them through /clientapi. Nerd stats are on the time, the author and its server's history, the place or the CDN, the media and the counts. *@ @if (!IsFilteredOut) { @@ -211,10 +211,20 @@ @if (Post.Located) { -

- - @Localizer["{0} km from you, shown within {1} km", Number(Post.DistanceKm), Number(Post.RangeKm)] -

+
+

+ + @Localizer["{0} km from you, shown within {1} km", Number(Post.DistanceKm), Number(Post.RangeKm)] +

+ @* its author takes it back here: it has no other action *@ + @if (IsOwn && !Compact) + { + + + @Localizer["Delete"] + + } +
} @if (status.Privapub?.Approval is "pending" or "rejected") @@ -949,11 +959,12 @@ async Task Delete() { - if (!await Confirm.Ask(Localizer["Delete this post?"], Localizer["It goes from here, and the servers that have it are asked to delete it too."], + // a located post never left PrivaPub: no server is asked + if (!await Confirm.Ask(Localizer["Delete this post?"], Post.Located ? null : Localizer["It goes from here, and the servers that have it are asked to delete it too."], Localizer["Delete"], true)) return; isActing = true; - var result = await StatusService.Delete(Status.Id); + var result = Post.Located ? await StatusService.DeleteLocated(Status.Id, CascadingState.PersonaId) : await StatusService.Delete(Status.Id); isActing = false; if (result.IsInvalid) { diff --git a/Models/APIs.cs b/Models/APIs.cs index 6882a2c..29d3f41 100644 --- a/Models/APIs.cs +++ b/Models/APIs.cs @@ -33,6 +33,7 @@ public static class APIs public const string GET_Posts = "clientapi/post/list"; public const string GET_Nearby = "clientapi/post/nearby"; public const string POST_InsertPost = "clientapi/post/insert"; + public const string POST_DeletePost = "clientapi/post/delete"; public const string GET_ModeratorReports = "clientapi/moderator/reports"; public static string POST_M_ResolveReport(string reportId) => $"clientapi/moderator/reports/{Uri.EscapeDataString(reportId)}/resolve"; public const string GET_AdminDomainBlocks = "clientapi/admin/domainblocks/list"; diff --git a/Services/StatusService.cs b/Services/StatusService.cs index 8b53241..4485d82 100644 --- a/Services/StatusService.cs +++ b/Services/StatusService.cs @@ -228,6 +228,17 @@ namespace decePubClient.Services return await StatusResult(response, cancellationToken); } + /// Deletes the persona's own located post: through /clientapi, since the Mastodon API looks past located posts. + public async Task DeleteLocated(string postId, string personaId, CancellationToken cancellationToken = default) + { + using var response = await httpService.Post(APIs.ClientApi.POST_DeletePost, new DeletePostForm { AvatarId = personaId, PostId = postId }, + cancellationToken: cancellationToken); + if (!response.IsSuccessStatusCode) + return await response.Content.ReadWebResult(response.StatusCode, localizer, cancellationToken); + + return new WebResult(); + } + public async Task Get(string statusId, CancellationToken cancellationToken = default) { using var response = await httpService.Get(APIs.Mastodon.GET_M_Status(statusId), cancellationToken: cancellationToken);