forked from coracle/flotilla
Merge report detail components
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
import Confirm from "@lib/components/Confirm.svelte"
|
import Confirm from "@lib/components/Confirm.svelte"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import EventInfo from "@app/components/EventInfo.svelte"
|
import EventInfo from "@app/components/EventInfo.svelte"
|
||||||
import EventReport from "@app/components/EventReport.svelte"
|
import Report from "@app/components/Report.svelte"
|
||||||
import EventShare from "@app/components/EventShare.svelte"
|
import EventShare from "@app/components/EventShare.svelte"
|
||||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||||
import {hasNip29, deriveUserIsSpaceAdmin} from "@app/core/state"
|
import {hasNip29, deriveUserIsSpaceAdmin} from "@app/core/state"
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
const isRoot = event.kind !== COMMENT
|
const isRoot = event.kind !== COMMENT
|
||||||
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||||
|
|
||||||
const report = () => pushModal(EventReport, {url, event})
|
const report = () => pushModal(Report, {url, event})
|
||||||
|
|
||||||
const showInfo = () => pushModal(EventInfo, {url, event})
|
const showInfo = () => pushModal(EventInfo, {url, event})
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {getTag, REPORT} from "@welshman/util"
|
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
|
||||||
import {deriveEvents} from "@welshman/store"
|
|
||||||
import {pubkey, repository} from "@welshman/app"
|
|
||||||
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
|
||||||
import Button from "@lib/components/Button.svelte"
|
|
||||||
import Profile from "@app/components/Profile.svelte"
|
|
||||||
import {publishDelete, canEnforceNip70} from "@app/core/commands"
|
|
||||||
|
|
||||||
const {url, event} = $props()
|
|
||||||
|
|
||||||
const shouldProtect = canEnforceNip70(url)
|
|
||||||
|
|
||||||
const reports = deriveEvents(repository, {
|
|
||||||
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
|
||||||
})
|
|
||||||
|
|
||||||
const back = () => history.back()
|
|
||||||
|
|
||||||
const deleteReport = async (report: TrustedEvent) => {
|
|
||||||
publishDelete({event: report, relays: [url], protect: await shouldProtect})
|
|
||||||
|
|
||||||
if ($reports.length === 0) {
|
|
||||||
history.back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getReason = (tags: string[][]) => getTag("e", tags)?.[2] || "other"
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="column gap-4">
|
|
||||||
<ModalHeader>
|
|
||||||
{#snippet title()}
|
|
||||||
<div>Report Details</div>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet info()}
|
|
||||||
<div>All reports for this event are shown below.</div>
|
|
||||||
{/snippet}
|
|
||||||
</ModalHeader>
|
|
||||||
{#each $reports as report (report.id)}
|
|
||||||
{@const reason = getReason(report.tags)}
|
|
||||||
{@const remove = () => deleteReport(report)}
|
|
||||||
<div class="column gap-2">
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<div>
|
|
||||||
<Profile pubkey={report.pubkey} {url} />
|
|
||||||
<span>Reported this event as "{reason}"</span>
|
|
||||||
</div>
|
|
||||||
{#if report.pubkey === $pubkey}
|
|
||||||
<Button class="btn-default btn" onclick={remove}>Delete Report</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{#if report.content}
|
|
||||||
<p>"{report.content}"</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<Button class="btn btn-primary" onclick={back}>Got it</Button>
|
|
||||||
</div>
|
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
import * as nip19 from "nostr-tools/nip19"
|
|
||||||
import {formatTimestamp} from "@welshman/lib"
|
import {formatTimestamp} from "@welshman/lib"
|
||||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {Router} from "@welshman/router"
|
|
||||||
import {userMutes} from "@welshman/app"
|
import {userMutes} from "@welshman/app"
|
||||||
import Link from "@lib/components/Link.svelte"
|
|
||||||
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import ProfileName from "@app/components/ProfileName.svelte"
|
import ProfileName from "@app/components/ProfileName.svelte"
|
||||||
import {entityLink} from "@app/core/state"
|
import {goToEvent} from "@app/util/routes"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
event,
|
event,
|
||||||
@@ -31,9 +28,6 @@
|
|||||||
class?: string
|
class?: string
|
||||||
} = $props()
|
} = $props()
|
||||||
|
|
||||||
const relays = Router.get().Event(event).getUrls()
|
|
||||||
const nevent = nip19.neventEncode({id: event.id, relays})
|
|
||||||
|
|
||||||
const ignoreMute = () => {
|
const ignoreMute = () => {
|
||||||
muted = false
|
muted = false
|
||||||
}
|
}
|
||||||
@@ -59,12 +53,11 @@
|
|||||||
<Profile pubkey={event.pubkey} {url} />
|
<Profile pubkey={event.pubkey} {url} />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<Link
|
<Button
|
||||||
external
|
class={cx("text-sm opacity-75", {"text-xs": minimal})}
|
||||||
href={entityLink(nevent)}
|
onclick={() => goToEvent(event)}>
|
||||||
class={cx("text-sm opacity-75", {"text-xs": minimal})}>
|
|
||||||
{formatTimestamp(event.created_at)}
|
{formatTimestamp(event.created_at)}
|
||||||
</Link>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {removeUndefined} from "@welshman/lib"
|
import {removeUndefined} from "@welshman/lib"
|
||||||
import {ManagementMethod} from "@welshman/util"
|
import {ManagementMethod} from "@welshman/util"
|
||||||
import {shouldUnwrap, manageRelay, deriveProfile} from "@welshman/app"
|
import {shouldUnwrap, manageRelay, deriveProfile, displayProfileByPubkey} from "@welshman/app"
|
||||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
import Code2 from "@assets/icons/code-2.svg?dataurl"
|
||||||
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
import Letter from "@assets/icons/letter-opened.svg?dataurl"
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
const banMember = () =>
|
const banMember = () =>
|
||||||
pushModal(Confirm, {
|
pushModal(Confirm, {
|
||||||
title: "Ban User",
|
title: "Ban User",
|
||||||
message: "Are you sure you want to ban this user from the space?",
|
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`,
|
||||||
confirm: async () => {
|
confirm: async () => {
|
||||||
const {error} = await manageRelay(url!, {
|
const {error} = await manageRelay(url!, {
|
||||||
method: ManagementMethod.BanPubkey,
|
method: ManagementMethod.BanPubkey,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Reaction from "@app/components/Reaction.svelte"
|
import Reaction from "@app/components/Reaction.svelte"
|
||||||
import EventReportDetails from "@app/components/EventReportDetails.svelte"
|
import ReportDetails from "@app/components/ReportDetails.svelte"
|
||||||
import {REACTION_KINDS} from "@app/core/state"
|
import {REACTION_KINDS} from "@app/core/state"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReportClick = () => pushModal(EventReportDetails, {url, event})
|
const onReportClick = () => pushModal(ReportDetails, {url, event})
|
||||||
|
|
||||||
const reportReasons = $derived(uniq($reports.map(e => getTag("e", e.tags)?.[2])))
|
const reportReasons = $derived(uniq($reports.map(e => getTag("e", e.tags)?.[2])))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {REPORT} from "@welshman/util"
|
||||||
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import {deriveEvents} from "@welshman/store"
|
||||||
|
import {repository} from "@welshman/app"
|
||||||
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import ReportItem from "@app/components/ReportItem.svelte"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string
|
||||||
|
event: TrustedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, event}: Props = $props()
|
||||||
|
|
||||||
|
const reports = deriveEvents(repository, {
|
||||||
|
filters: [{kinds: [REPORT], "#e": [event.id]}],
|
||||||
|
})
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
|
||||||
|
const onDelete = () => {
|
||||||
|
if ($reports.length === 0) {
|
||||||
|
back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="column gap-4">
|
||||||
|
<ModalHeader>
|
||||||
|
{#snippet title()}
|
||||||
|
<div>Report Details</div>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet info()}
|
||||||
|
<div>All reports for this event are shown below.</div>
|
||||||
|
{/snippet}
|
||||||
|
</ModalHeader>
|
||||||
|
{#each $reports as report (report.id)}
|
||||||
|
<div class="card2 card2-sm bg-alt">
|
||||||
|
<ReportItem {url} event={report} {onDelete} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<Button class="btn btn-primary" onclick={back}>Got it</Button>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {formatTimestamp} from "@welshman/lib"
|
||||||
|
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"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string
|
||||||
|
event: TrustedEvent
|
||||||
|
onDelete?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, event, onDelete}: Props = $props()
|
||||||
|
|
||||||
|
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
|
||||||
|
if (e.target?.classList.contains("profile-name")) {
|
||||||
|
pushModal(ProfileDetail, {pubkey: event.pubkey, url})
|
||||||
|
} else {
|
||||||
|
goToEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteReport = async () => {
|
||||||
|
publishDelete({event, relays: [url], protect: await shouldProtect})
|
||||||
|
onDelete?.()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="column gap-4">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div>
|
||||||
|
<Profile pubkey={event.pubkey} {url} avatarSize={5} />
|
||||||
|
<span>
|
||||||
|
Reported this event
|
||||||
|
{#if reason}
|
||||||
|
as "{reason}"
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#if event.pubkey === $pubkey}
|
||||||
|
<Button class="btn-default btn" onclick={deleteReport}>Delete Report</Button>
|
||||||
|
{:else}
|
||||||
|
<ReportMenu {url} {event} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if event.content}
|
||||||
|
<div class="border-l-2 border-primary pl-3">
|
||||||
|
<NoteContent {event} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="card2 card2-sm bg-alt">
|
||||||
|
{#if etag}
|
||||||
|
{#await load({relays: [url, LOCAL_RELAY_URL], filters: getIdFilters([etag[1]])})}
|
||||||
|
<p>Loading</p>
|
||||||
|
{:then reportedEvents}
|
||||||
|
{#if reportedEvents.length === 0}
|
||||||
|
<p>Unable to find reported note.</p>
|
||||||
|
{:else}
|
||||||
|
{@const event = reportedEvents[0]}
|
||||||
|
<Button class="col-2 w-full" onclick={(e: Event) => onClick(e, event)}>
|
||||||
|
<div class="flex items-center justify-between gap-2">
|
||||||
|
<span class="profile-name">
|
||||||
|
@<ProfileName pubkey={event.pubkey} {url} />
|
||||||
|
</span>
|
||||||
|
<span class="text-xs opacity-75">
|
||||||
|
{formatTimestamp(event.created_at)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<NoteContent {event} />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
{:else if ptag}
|
||||||
|
<Profile pubkey={ptag[1]} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {getTag, ManagementMethod} from "@welshman/util"
|
||||||
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
|
import {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 {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 {pushToast} from "@app/util/toast"
|
||||||
|
import {pushModal} from "@app/util/modal"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
url: string
|
||||||
|
event: TrustedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, event}: Props = $props()
|
||||||
|
|
||||||
|
const etag = getTag("e", event.tags)
|
||||||
|
const ptag = getTag("p", event.tags)
|
||||||
|
|
||||||
|
const toggleMenu = () => {
|
||||||
|
isOpen = !isOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeMenu = () => {
|
||||||
|
isOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const dismissReport = async () => {
|
||||||
|
const {error} = await manageRelay(url, {
|
||||||
|
method: ManagementMethod.BanEvent,
|
||||||
|
params: [event.id, "Dismissed by admin"],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
pushToast({theme: "error", message: error})
|
||||||
|
} else {
|
||||||
|
pushToast({message: "Content has successfully been deleted!"})
|
||||||
|
repository.removeEvent(event.id)
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const banContent = () => {
|
||||||
|
const [_, id, reason = ""] = etag!
|
||||||
|
|
||||||
|
pushModal(Confirm, {
|
||||||
|
title: `Delete Content`,
|
||||||
|
message: `Are you sure you want to delete this content from the space?`,
|
||||||
|
confirm: async () => {
|
||||||
|
const {error} = await manageRelay(url, {
|
||||||
|
method: ManagementMethod.BanEvent,
|
||||||
|
params: [id, reason],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
pushToast({theme: "error", message: error})
|
||||||
|
} else {
|
||||||
|
pushToast({message: "Content has successfully been deleted!"})
|
||||||
|
repository.removeEvent(id)
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const banMember = () => {
|
||||||
|
const [pubkey, reason = ""] = ptag!
|
||||||
|
|
||||||
|
pushModal(Confirm, {
|
||||||
|
title: "Ban User",
|
||||||
|
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`,
|
||||||
|
confirm: async () => {
|
||||||
|
const {error} = await manageRelay(url, {
|
||||||
|
method: ManagementMethod.BanPubkey,
|
||||||
|
params: [pubkey, reason],
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
pushToast({theme: "error", message: error})
|
||||||
|
} else {
|
||||||
|
pushToast({message: "User has successfully been banned!"})
|
||||||
|
history.back()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let isOpen = $state(false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
|
<Button class="btn btn-circle btn-ghost btn-sm" onclick={toggleMenu}>
|
||||||
|
<Icon icon={MenuDots} />
|
||||||
|
</Button>
|
||||||
|
{#if isOpen}
|
||||||
|
<Popover hideOnClick onClose={closeMenu}>
|
||||||
|
<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}
|
||||||
|
<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}
|
||||||
|
</ul>
|
||||||
|
</Popover>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Confirm from "@lib/components/Confirm.svelte"
|
import Confirm from "@lib/components/Confirm.svelte"
|
||||||
import EventInfo from "@app/components/EventInfo.svelte"
|
import EventInfo from "@app/components/EventInfo.svelte"
|
||||||
import EventReport from "@app/components/EventReport.svelte"
|
import Report from "@app/components/Report.svelte"
|
||||||
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
import EventDeleteConfirm from "@app/components/EventDeleteConfirm.svelte"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
const report = () => {
|
const report = () => {
|
||||||
onClick()
|
onClick()
|
||||||
pushModal(EventReport, {url, event})
|
pushModal(Report, {url, event})
|
||||||
}
|
}
|
||||||
|
|
||||||
const showInfo = () => {
|
const showInfo = () => {
|
||||||
|
|||||||
@@ -31,11 +31,11 @@
|
|||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const toggleMenu = (pubkey: string) => {
|
const toggleMenu = (pubkey: string) => {
|
||||||
menuPubkey = menuPubkey === pubkey ? null : pubkey
|
menuPubkey = menuPubkey === pubkey ? undefined : pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeMenu = () => {
|
const closeMenu = () => {
|
||||||
menuPubkey = null
|
menuPubkey = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const addMember = () => pushModal(RoomMembersAdd, {url, h})
|
const addMember = () => pushModal(RoomMembersAdd, {url, h})
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
let menuPubkey = $state<string | null>(null)
|
let menuPubkey = $state<string | undefined>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {displayRelayUrl, ManagementMethod} from "@welshman/util"
|
import {displayRelayUrl, ManagementMethod} from "@welshman/util"
|
||||||
import {manageRelay} from "@welshman/app"
|
import {manageRelay, displayProfileByPubkey} from "@welshman/app"
|
||||||
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
|
||||||
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
|
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
|
||||||
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
@@ -35,11 +35,11 @@
|
|||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const toggleMenu = (pubkey: string) => {
|
const toggleMenu = (pubkey: string) => {
|
||||||
menuPubkey = menuPubkey === pubkey ? null : pubkey
|
menuPubkey = menuPubkey === pubkey ? undefined : pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeMenu = () => {
|
const closeMenu = () => {
|
||||||
menuPubkey = null
|
menuPubkey = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const showBannedPubkeyItems = () => pushModal(SpaceMembersBanned, {url})
|
const showBannedPubkeyItems = () => pushModal(SpaceMembersBanned, {url})
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
const banMember = (pubkey: string) =>
|
const banMember = (pubkey: string) =>
|
||||||
pushModal(Confirm, {
|
pushModal(Confirm, {
|
||||||
title: "Ban User",
|
title: "Ban User",
|
||||||
message: "Are you sure you want to ban this user from the space?",
|
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`,
|
||||||
confirm: async () => {
|
confirm: async () => {
|
||||||
const {error} = await manageRelay(url, {
|
const {error} = await manageRelay(url, {
|
||||||
method: ManagementMethod.BanPubkey,
|
method: ManagementMethod.BanPubkey,
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
let menuPubkey = $state<string | null>(null)
|
let menuPubkey = $state<string | undefined>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const toggleMenu = (pubkey: string) => {
|
const toggleMenu = (pubkey: string) => {
|
||||||
menuPubkey = menuPubkey === pubkey ? null : pubkey
|
menuPubkey = menuPubkey === pubkey ? undefined : pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeMenu = () => {
|
const closeMenu = () => {
|
||||||
menuPubkey = null
|
menuPubkey = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const restoreMember = async (pubkey: string) => {
|
const restoreMember = async (pubkey: string) => {
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let menuPubkey = $state<string | null>(null)
|
let menuPubkey = $state<string | undefined>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="column gap-4">
|
<div class="column gap-4">
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {displayRelayUrl, getTagValue, EVENT_TIME, ZAP_GOAL, THREAD} from "@welshman/util"
|
import {displayRelayUrl, getTagValue, EVENT_TIME, ZAP_GOAL, THREAD, REPORT} from "@welshman/util"
|
||||||
import {deriveRelay, pubkey} from "@welshman/app"
|
import {deriveRelay, pubkey} from "@welshman/app"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
|
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
|
||||||
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
|
||||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
import Danger from "@assets/icons/danger.svg?dataurl"
|
||||||
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||||
import Exit from "@assets/icons/logout-3.svg?dataurl"
|
import Exit from "@assets/icons/logout-3.svg?dataurl"
|
||||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
import SpaceJoin from "@app/components/SpaceJoin.svelte"
|
import SpaceJoin from "@app/components/SpaceJoin.svelte"
|
||||||
import RelayName from "@app/components/RelayName.svelte"
|
import RelayName from "@app/components/RelayName.svelte"
|
||||||
import SpaceMembers from "@app/components/SpaceMembers.svelte"
|
import SpaceMembers from "@app/components/SpaceMembers.svelte"
|
||||||
|
import SpaceReports from "@app/components/SpaceReports.svelte"
|
||||||
import AlertAdd from "@app/components/AlertAdd.svelte"
|
import AlertAdd from "@app/components/AlertAdd.svelte"
|
||||||
import Alerts from "@app/components/Alerts.svelte"
|
import Alerts from "@app/components/Alerts.svelte"
|
||||||
import RoomCreate from "@app/components/RoomCreate.svelte"
|
import RoomCreate from "@app/components/RoomCreate.svelte"
|
||||||
@@ -47,6 +49,7 @@
|
|||||||
hasNip29,
|
hasNip29,
|
||||||
alerts,
|
alerts,
|
||||||
deriveUserCanCreateRoom,
|
deriveUserCanCreateRoom,
|
||||||
|
deriveUserIsSpaceAdmin,
|
||||||
} from "@app/core/state"
|
} from "@app/core/state"
|
||||||
import {notifications} from "@app/util/notifications"
|
import {notifications} from "@app/util/notifications"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
@@ -62,6 +65,8 @@
|
|||||||
const userRooms = deriveUserRooms(url)
|
const userRooms = deriveUserRooms(url)
|
||||||
const otherRooms = deriveOtherRooms(url)
|
const otherRooms = deriveOtherRooms(url)
|
||||||
const members = deriveSpaceMembers(url)
|
const members = deriveSpaceMembers(url)
|
||||||
|
const userIsAdmin = deriveUserIsSpaceAdmin(url)
|
||||||
|
const reports = deriveEventsForUrl(url, [{kinds: [REPORT]}])
|
||||||
const hasAlerts = $derived($alerts.some(a => getTagValue("feed", a.tags)?.includes(url)))
|
const hasAlerts = $derived($alerts.some(a => getTagValue("feed", a.tags)?.includes(url)))
|
||||||
|
|
||||||
const spaceKinds = derived(
|
const spaceKinds = derived(
|
||||||
@@ -81,6 +86,8 @@
|
|||||||
|
|
||||||
const showMembers = () => pushModal(SpaceMembers, {url}, {replaceState})
|
const showMembers = () => pushModal(SpaceMembers, {url}, {replaceState})
|
||||||
|
|
||||||
|
const showReports = () => pushModal(SpaceReports, {url}, {replaceState})
|
||||||
|
|
||||||
const canCreateRoom = deriveUserCanCreateRoom(url)
|
const canCreateRoom = deriveUserCanCreateRoom(url)
|
||||||
|
|
||||||
const createInvite = () => pushModal(SpaceInvite, {url}, {replaceState})
|
const createInvite = () => pushModal(SpaceInvite, {url}, {replaceState})
|
||||||
@@ -144,6 +151,14 @@
|
|||||||
View Members ({$members.length})
|
View Members ({$members.length})
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
|
{#if $userIsAdmin}
|
||||||
|
<li>
|
||||||
|
<Button onclick={showReports}>
|
||||||
|
<Icon icon={Danger} />
|
||||||
|
View Reports ({$reports.length})
|
||||||
|
</Button>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
{#if $relay?.pubkey && $relay.pubkey !== $pubkey}
|
{#if $relay?.pubkey && $relay.pubkey !== $pubkey}
|
||||||
<li>
|
<li>
|
||||||
<Link href={makeChatPath([$relay.pubkey])}>
|
<Link href={makeChatPath([$relay.pubkey])}>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {REPORT, displayRelayUrl} from "@welshman/util"
|
||||||
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
||||||
|
import ReportItem from "@app/components/ReportItem.svelte"
|
||||||
|
import {deriveEventsForUrl} from "@app/core/state"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url}: Props = $props()
|
||||||
|
|
||||||
|
const reports = deriveEventsForUrl(url, [{kinds: [REPORT]}])
|
||||||
|
|
||||||
|
const back = () => history.back()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="column gap-4">
|
||||||
|
<div class="flex min-w-0 flex-col gap-1">
|
||||||
|
<h1 class="ellipsize whitespace-nowrap text-2xl font-bold">Reports</h1>
|
||||||
|
<p class="ellipsize text-sm opacity-75">on {displayRelayUrl(url)}</p>
|
||||||
|
</div>
|
||||||
|
{#each $reports as event (event.id)}
|
||||||
|
<ReportItem {url} {event} />
|
||||||
|
{/each}
|
||||||
|
<ModalFooter>
|
||||||
|
<Button class="btn btn-link" onclick={back}>
|
||||||
|
<Icon icon={AltArrowLeft} />
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</div>
|
||||||
@@ -34,7 +34,7 @@ export class IDB {
|
|||||||
|
|
||||||
async init(adapters: IDBAdapters) {
|
async init(adapters: IDBAdapters) {
|
||||||
if (this.idbp) {
|
if (this.idbp) {
|
||||||
await this.close()
|
throw new Error("Unable to initialize a database that isn't yet closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
this.status = IDBStatus.Opening
|
this.status = IDBStatus.Opening
|
||||||
|
|||||||
@@ -111,7 +111,9 @@
|
|||||||
])
|
])
|
||||||
|
|
||||||
// Wait until data storage is initialized before syncing other stuff
|
// Wait until data storage is initialized before syncing other stuff
|
||||||
await db.init(storage.adapters)
|
if (!db.idbp) {
|
||||||
|
await db.init(storage.adapters)
|
||||||
|
}
|
||||||
|
|
||||||
// Add our extra policies now that we're set up
|
// Add our extra policies now that we're set up
|
||||||
defaultSocketPolicies.push(...policies)
|
defaultSocketPolicies.push(...policies)
|
||||||
|
|||||||
Reference in New Issue
Block a user