diff --git a/src/app/components/EventAdminDeleteConfirm.svelte b/src/app/components/EventAdminDeleteConfirm.svelte
new file mode 100644
index 00000000..0a6ab179
--- /dev/null
+++ b/src/app/components/EventAdminDeleteConfirm.svelte
@@ -0,0 +1,49 @@
+
+
+
diff --git a/src/app/components/EventMenu.svelte b/src/app/components/EventMenu.svelte
index 40ab7529..b15f4e42 100644
--- a/src/app/components/EventMenu.svelte
+++ b/src/app/components/EventMenu.svelte
@@ -9,17 +9,16 @@
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Danger from "@assets/icons/danger.svg?dataurl"
import Button from "@lib/components/Button.svelte"
- import Confirm from "@lib/components/Confirm.svelte"
import Icon from "@lib/components/Icon.svelte"
import EventInfo from "@app/components/EventInfo.svelte"
import Report from "@app/components/Report.svelte"
+ import EventAdminDeleteConfirm from "@app/components/EventAdminDeleteConfirm.svelte"
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
import PinboardSelect from "@app/components/PinboardSelect.svelte"
import {shareEvent} from "@app/share"
- import {deriveSpaceSupportedMethods} from "@app/management"
+ import {deriveUserAdminDelete} from "@app/rooms"
import {pushModal} from "@app/modal"
- import {pushToast} from "@app/toast"
- import {app, relayManagement, user} from "@app/core"
+ import {user} from "@app/core"
type Props = {
url: string
@@ -32,8 +31,7 @@
const {url, noun, event, onClick, customActions}: Props = $props()
const isRoot = event.kind !== COMMENT
- const supportedMethods = deriveSpaceSupportedMethods(url)
- const canBanEvent = $derived($supportedMethods.includes("banevent"))
+ const adminDelete = deriveUserAdminDelete(url, event)
const report = () => pushModal(Report, {url, event})
@@ -45,22 +43,7 @@
const showDelete = () => pushModal(EventDeleteConfirm, {url, event})
- const showAdminDelete = () =>
- pushModal(Confirm, {
- title: `Delete ${noun}`,
- message: `Are you sure you want to delete this ${noun.toLowerCase()} from the space?`,
- confirm: async () => {
- const {error} = await $relayManagement.forUrl(url).banEvent(event.id)
-
- if (error) {
- pushToast({theme: "error", message: error})
- } else {
- pushToast({message: "Event has successfully been deleted!"})
- $app.repository.removeEvent(event.id)
- history.back()
- }
- },
- })
+ const showAdminDelete = () => pushModal(EventAdminDeleteConfirm, {url, noun, event})
let ul: Element
@@ -105,7 +88,7 @@
Report Content
- {#if canBanEvent}
+ {#if $adminDelete}
- {#if canBanEvent}
+ {#if $adminDelete}
+ {:else if $adminDelete}
+
{/if}
{/if}
diff --git a/src/app/rooms.ts b/src/app/rooms.ts
index 7ee1d199..b2f4427b 100644
--- a/src/app/rooms.ts
+++ b/src/app/rooms.ts
@@ -2,7 +2,7 @@ import * as nip19 from "nostr-tools/nip19"
import {derived, get} from "svelte/store"
import {formatTimestampAsDate, int, sortBy, uniq, MINUTE} from "@welshman/lib"
import type {Maybe} from "@welshman/lib"
-import {outbox, relay, seen, toNostrURI} from "@welshman/util"
+import {outbox, relay, seen, tagSpec, tagValue, toNostrURI} from "@welshman/util"
import type {EventContent, TrustedEvent} from "@welshman/util"
import {Message} from "@welshman/domain"
import {MembershipStatus, RoomLists, makeRoomKey, createSearch, publish} from "@welshman/app"
@@ -19,7 +19,7 @@ import {
user,
writer,
} from "@app/core"
-import {deriveUserIsSpaceStaff} from "@app/management"
+import {deriveSpaceSupportedMethods, deriveUserIsSpaceStaff} from "@app/management"
import {makeRoomPath} from "@app/routes"
export const PROTECTED = ["-"]
@@ -173,6 +173,33 @@ export const deriveUserIsRoomAdmin = (url: string, h: string) =>
$isStaff || Boolean($room?.admins?.pubkeys().includes($user.pubkey)),
)
+// Deleting someone else's content goes one of two ways, and a space answers for either one.
+export enum AdminDelete {
+ Room = "room",
+ Space = "space",
+}
+
+// A room admin deletes over NIP-29, which is scoped to the room the content is in. NIP-86 is the
+// wider grant and the only one that reaches content belonging to no room, so it comes second.
+export const deriveUserAdminDelete = (url: string, event: TrustedEvent) => {
+ const h = tagValue(tagSpec("h"), event.tags) ?? ""
+
+ return derived(
+ [deriveUserIsRoomAdmin(url, h), deriveSpaceSupportedMethods(url)],
+ ([$isRoomAdmin, $methods]): Maybe => {
+ if (h && $isRoomAdmin) {
+ return AdminDelete.Room
+ }
+
+ if ($methods.includes("banevent")) {
+ return AdminDelete.Space
+ }
+
+ return undefined
+ },
+ )
+}
+
// Room membership is the relay's business, but a space admin outranks it.
export const deriveUserRoomMembershipStatus = (url: string, h: string) =>
derived(