Clean up report item design, bad/restore user actions, space description input, add feed to home page
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import type {Snippet} from "svelte"
|
||||
import type {NativeEmoji} from "emoji-picker-element/shared"
|
||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||
import {Router} from "@welshman/router"
|
||||
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import EmojiButton from "@lib/components/EmojiButton.svelte"
|
||||
@@ -11,26 +12,28 @@
|
||||
import {publishDelete, publishReaction, canEnforceNip70} from "@app/core/commands"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
event: TrustedEvent
|
||||
children?: Snippet
|
||||
url?: string
|
||||
}
|
||||
|
||||
const {url, event, children}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const relays = url ? [url] : Router.get().Event(event).getUrls()
|
||||
|
||||
const shouldProtect = url ? canEnforceNip70(url) : Promise.resolve(false)
|
||||
|
||||
const deleteReaction = async (event: TrustedEvent) =>
|
||||
publishDelete({relays: [url], event, protect: await shouldProtect})
|
||||
publishDelete({relays, event, protect: await shouldProtect})
|
||||
|
||||
const createReaction = async (template: EventContent) =>
|
||||
publishReaction({...template, event, relays: [url], protect: await shouldProtect})
|
||||
publishReaction({...template, event, relays, protect: await shouldProtect})
|
||||
|
||||
const onEmoji = async (emoji: NativeEmoji) =>
|
||||
publishReaction({
|
||||
event,
|
||||
relays,
|
||||
content: emoji.unicode,
|
||||
relays: [url],
|
||||
protect: await shouldProtect,
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
|
||||
import Restart from "@assets/icons/restart.svg?dataurl"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||
@@ -30,7 +31,7 @@
|
||||
import EventInfo from "@app/components/EventInfo.svelte"
|
||||
import ProfileBadges from "@app/components/ProfileBadges.svelte"
|
||||
import ChatEnable from "@app/components/ChatEnable.svelte"
|
||||
import {pubkeyLink, deriveUserIsSpaceAdmin} from "@app/core/state"
|
||||
import {pubkeyLink, deriveUserIsSpaceAdmin, deriveSpaceBannedPubkeyItems} from "@app/core/state"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {makeChatPath} from "@app/util/routes"
|
||||
@@ -46,6 +47,10 @@
|
||||
|
||||
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||
|
||||
const bannedPubkeys = url ? deriveSpaceBannedPubkeyItems(url) : undefined
|
||||
|
||||
const isBanned = $derived($bannedPubkeys?.some(item => item.pubkey === pubkey) ?? false)
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const chatPath = makeChatPath([pubkey])
|
||||
@@ -81,6 +86,20 @@
|
||||
},
|
||||
})
|
||||
|
||||
const restoreMember = async () => {
|
||||
const {error} = await manageRelay(url!, {
|
||||
method: ManagementMethod.AllowPubkey,
|
||||
params: [pubkey],
|
||||
})
|
||||
|
||||
if (error) {
|
||||
pushToast({theme: "error", message: error})
|
||||
} else {
|
||||
pushToast({message: "User has successfully been restored!"})
|
||||
back()
|
||||
}
|
||||
}
|
||||
|
||||
let showMenu = $state(false)
|
||||
|
||||
onMount(() => {
|
||||
@@ -112,12 +131,21 @@
|
||||
</li>
|
||||
{/if}
|
||||
{#if $userIsAdmin}
|
||||
<li>
|
||||
<Button class="text-error" onclick={banMember}>
|
||||
<Icon icon={MinusCircle} />
|
||||
Ban User
|
||||
</Button>
|
||||
</li>
|
||||
{#if isBanned}
|
||||
<li>
|
||||
<Button onclick={restoreMember}>
|
||||
<Icon icon={Restart} />
|
||||
Restore User
|
||||
</Button>
|
||||
</li>
|
||||
{:else}
|
||||
<li>
|
||||
<Button class="text-error" onclick={banMember}>
|
||||
<Icon icon={MinusCircle} />
|
||||
Ban User
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
</ul>
|
||||
</Popover>
|
||||
|
||||
@@ -40,9 +40,7 @@
|
||||
<ModalSubtitle>All reports for this event are shown below.</ModalSubtitle>
|
||||
</ModalHeader>
|
||||
{#each $reports.values() as report (report.id)}
|
||||
<div class="card2 card2-sm bg-alt">
|
||||
<ReportItem {url} event={report} {onDelete} />
|
||||
</div>
|
||||
<ReportItem {url} event={report} {onDelete} />
|
||||
{/each}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
import {getTag, getIdFilters} from "@welshman/util"
|
||||
import {load, LOCAL_RELAY_URL} from "@welshman/net"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {pubkey} from "@welshman/app"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Profile from "@app/components/Profile.svelte"
|
||||
import ProfileName from "@app/components/ProfileName.svelte"
|
||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||
import NoteContent from "@app/components/NoteContent.svelte"
|
||||
import ReportMenu from "@app/components/ReportMenu.svelte"
|
||||
import {publishDelete, canEnforceNip70} from "@app/core/commands"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
import {goToEvent} from "@app/util/routes"
|
||||
|
||||
@@ -25,7 +23,6 @@
|
||||
const etag = getTag("e", event.tags)
|
||||
const ptag = getTag("p", event.tags)
|
||||
const reason = etag?.[2] || ptag?.[2]
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
|
||||
const onClick = (e: Event, event: TrustedEvent) => {
|
||||
// @ts-ignore
|
||||
@@ -35,17 +32,12 @@
|
||||
goToEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteReport = async () => {
|
||||
publishDelete({event, relays: [url], protect: await shouldProtect})
|
||||
onDelete?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="column gap-4">
|
||||
<div class="column gap-4 card2 card2-sm bg-alt">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<Profile pubkey={event.pubkey} {url} avatarSize={5} />
|
||||
<ProfileName pubkey={event.pubkey} {url} />
|
||||
<span>
|
||||
Reported this event
|
||||
{#if reason}
|
||||
@@ -53,11 +45,7 @@
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{#if event.pubkey === $pubkey}
|
||||
<Button class="btn-default btn" onclick={deleteReport}>Delete Report</Button>
|
||||
{:else}
|
||||
<ReportMenu {url} {event} />
|
||||
{/if}
|
||||
<ReportMenu {url} {event} {onDelete} />
|
||||
</div>
|
||||
{#if event.content}
|
||||
<div class="border-l-2 border-primary pl-3">
|
||||
|
||||
@@ -1,26 +1,32 @@
|
||||
<script lang="ts">
|
||||
import {getTag, ManagementMethod} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {manageRelay, repository, displayProfileByPubkey} from "@welshman/app"
|
||||
import {pubkey, manageRelay, repository, displayProfileByPubkey} from "@welshman/app"
|
||||
import InboxOut from "@assets/icons/inbox-out.svg?dataurl"
|
||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
|
||||
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
|
||||
import Close from "@assets/icons/close.svg?dataurl"
|
||||
import {fly} from "@lib/transition"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import Popover from "@lib/components/Popover.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Confirm from "@lib/components/Confirm.svelte"
|
||||
import {deriveUserIsSpaceAdmin} from "@app/core/state"
|
||||
import {publishDelete, canEnforceNip70} from "@app/core/commands"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
import {pushModal} from "@app/util/modal"
|
||||
|
||||
type Props = {
|
||||
url: string
|
||||
event: TrustedEvent
|
||||
onDelete?: () => void
|
||||
}
|
||||
|
||||
const {url, event}: Props = $props()
|
||||
const {url, event, onDelete}: Props = $props()
|
||||
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||
const etag = getTag("e", event.tags)
|
||||
const ptag = getTag("p", event.tags)
|
||||
|
||||
@@ -32,6 +38,11 @@
|
||||
isOpen = false
|
||||
}
|
||||
|
||||
const deleteReport = async () => {
|
||||
publishDelete({event, relays: [url], protect: await shouldProtect})
|
||||
onDelete?.()
|
||||
}
|
||||
|
||||
const dismissReport = async () => {
|
||||
const {error} = await manageRelay(url, {
|
||||
method: ManagementMethod.BanEvent,
|
||||
@@ -43,7 +54,7 @@
|
||||
} else {
|
||||
pushToast({message: "Content has successfully been deleted!"})
|
||||
repository.removeEvent(event.id)
|
||||
history.back()
|
||||
onDelete?.()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +62,7 @@
|
||||
const [_, id, reason = ""] = etag!
|
||||
|
||||
pushModal(Confirm, {
|
||||
title: `Delete Content`,
|
||||
title: `Remove Content`,
|
||||
message: `Are you sure you want to delete this content from the space?`,
|
||||
confirm: async () => {
|
||||
const {error} = await manageRelay(url, {
|
||||
@@ -63,15 +74,17 @@
|
||||
pushToast({theme: "error", message: error})
|
||||
} else {
|
||||
pushToast({message: "Content has successfully been deleted!"})
|
||||
repository.removeEvent(event.id)
|
||||
repository.removeEvent(id)
|
||||
history.back()
|
||||
setTimeout(() => onDelete?.())
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const banMember = () => {
|
||||
const [pubkey, reason = ""] = ptag!
|
||||
const [_, pubkey, reason = ""] = ptag!
|
||||
|
||||
pushModal(Confirm, {
|
||||
title: "Ban User",
|
||||
@@ -86,7 +99,9 @@
|
||||
pushToast({theme: "error", message: error})
|
||||
} else {
|
||||
pushToast({message: "User has successfully been banned!"})
|
||||
repository.removeEvent(event.id)
|
||||
history.back()
|
||||
setTimeout(() => onDelete?.())
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -104,27 +119,37 @@
|
||||
<ul
|
||||
transition:fly
|
||||
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
|
||||
<li>
|
||||
<Button onclick={dismissReport}>
|
||||
<Icon icon={InboxOut} />
|
||||
Dismiss Report
|
||||
</Button>
|
||||
</li>
|
||||
{#if etag}
|
||||
{#if event.pubkey === $pubkey}
|
||||
<li>
|
||||
<Button class="text-error" onclick={banContent}>
|
||||
<Icon icon={TrashBin2} />
|
||||
Remove Content
|
||||
<Button onclick={deleteReport}>
|
||||
<Icon icon={Close} />
|
||||
Delete Report
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
{#if ptag}
|
||||
{#if $userIsAdmin}
|
||||
<li>
|
||||
<Button class="text-error" onclick={banMember}>
|
||||
<Icon icon={MinusCircle} />
|
||||
Ban User
|
||||
<Button onclick={dismissReport}>
|
||||
<Icon icon={InboxOut} />
|
||||
Dismiss Report
|
||||
</Button>
|
||||
</li>
|
||||
{#if etag}
|
||||
<li>
|
||||
<Button class="text-error" onclick={banContent}>
|
||||
<Icon icon={TrashBin2} />
|
||||
Remove Content
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
{#if ptag}
|
||||
<li>
|
||||
<Button class="text-error" onclick={banMember}>
|
||||
<Icon icon={MinusCircle} />
|
||||
Ban User
|
||||
</Button>
|
||||
</li>
|
||||
{/if}
|
||||
{/if}
|
||||
</ul>
|
||||
</Popover>
|
||||
|
||||
@@ -172,12 +172,12 @@
|
||||
</FieldInline>
|
||||
<FieldInline>
|
||||
{#snippet label()}
|
||||
<p>Description</p>
|
||||
<p class="flex flex-col items-start h-full">Description</p>
|
||||
{/snippet}
|
||||
{#snippet input()}
|
||||
<label class="input input-bordered flex w-full items-center gap-2">
|
||||
<input bind:value={values.description} class="grow" type="text" />
|
||||
</label>
|
||||
<textarea
|
||||
bind:value={values.description}
|
||||
class="min-h-24 textarea textarea-bordered flex w-full"></textarea>
|
||||
{/snippet}
|
||||
</FieldInline>
|
||||
</ModalBody>
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
const reports = deriveEventsForUrl(url, [{kinds: [REPORT]}])
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const onDelete = () => {
|
||||
if ($reports.length === 0) {
|
||||
back()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal>
|
||||
@@ -32,7 +38,7 @@
|
||||
</ModalHeader>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each $reports as event (event.id)}
|
||||
<ReportItem {url} {event} />
|
||||
<ReportItem {url} {event} {onDelete} />
|
||||
{:else}
|
||||
<p class="py-12 text-center">No reports found.</p>
|
||||
{/each}
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
makeDeriveItem,
|
||||
deriveItemsByKey,
|
||||
deriveDeduplicated,
|
||||
deriveEventsById,
|
||||
deriveEventsByIdByUrl,
|
||||
deriveEventsByIdForUrl,
|
||||
getEventsByIdForUrl,
|
||||
@@ -231,6 +232,9 @@ export const deriveEvent = makeDeriveEvent({
|
||||
onDerive: (filters: Filter[], relays: string[]) => load({filters, relays}),
|
||||
})
|
||||
|
||||
export const deriveEvents = (filters: Filter[] = [{}]) =>
|
||||
deriveEventsDesc(deriveEventsById({repository, filters}))
|
||||
|
||||
export const getEventsForUrl = (url: string, filters: Filter[] = [{}]) =>
|
||||
getEventsByIdForUrl({url, tracker, repository, filters}).values()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user