Support unban/unallow

This commit is contained in:
Jon Staab 2026-02-26 13:35:13 -08:00
parent 606a9343d9
commit 65483a6ef0
4 changed files with 117 additions and 40 deletions

1
.ackrc
View file

@ -1,5 +1,6 @@
--ignore-dir=.svelte-kit --ignore-dir=.svelte-kit
--ignore-dir=android --ignore-dir=android
--ignore-dir=target
--ignore-dir=build --ignore-dir=build
--ignore-dir=ios/DerivedData --ignore-dir=ios/DerivedData
--ignore-dir=ios/App/App/public --ignore-dir=ios/App/App/public

View file

@ -2,6 +2,7 @@
import {ManagementMethod} from "@welshman/util" import {ManagementMethod} from "@welshman/util"
import {manageRelay, displayProfileByPubkey} 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 UserMinus from "@assets/icons/user-minus.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"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
@ -24,6 +25,7 @@
deriveSpaceMembers, deriveSpaceMembers,
deriveSpaceBannedPubkeyItems, deriveSpaceBannedPubkeyItems,
deriveUserIsSpaceAdmin, deriveUserIsSpaceAdmin,
deriveSupportedMethods,
} from "@app/core/state" } from "@app/core/state"
import {pushModal} from "@app/util/modal" import {pushModal} from "@app/util/modal"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
@ -37,6 +39,9 @@
const members = deriveSpaceMembers(url) const members = deriveSpaceMembers(url)
const bans = deriveSpaceBannedPubkeyItems(url) const bans = deriveSpaceBannedPubkeyItems(url)
const userIsAdmin = deriveUserIsSpaceAdmin(url) const userIsAdmin = deriveUserIsSpaceAdmin(url)
const supportedMethods = deriveSupportedMethods(url)
const canBan = $derived($supportedMethods.includes(ManagementMethod.BanPubkey))
const canUnallow = $derived($supportedMethods.includes(ManagementMethod.UnallowPubkey))
const back = () => history.back() const back = () => history.back()
@ -52,6 +57,25 @@
const addMember = () => pushModal(SpaceMembersAdd, {url}) const addMember = () => pushModal(SpaceMembersAdd, {url})
const unallowMember = (pubkey: string) =>
pushModal(Confirm, {
title: "Remove User",
message: `Are you sure you want to remove @${displayProfileByPubkey(pubkey)} from the space?`,
confirm: async () => {
const {error} = await manageRelay(url, {
method: ManagementMethod.UnallowPubkey,
params: [pubkey],
})
if (error) {
pushToast({theme: "error", message: error})
} else {
pushToast({message: "User has successfully been removed!"})
back()
}
},
})
const banMember = (pubkey: string) => const banMember = (pubkey: string) =>
pushModal(Confirm, { pushModal(Confirm, {
title: "Ban User", title: "Ban User",
@ -94,6 +118,7 @@
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<Profile {pubkey} {url} /> <Profile {pubkey} {url} />
</div> </div>
{#if canBan || canUnallow}
<div class="relative"> <div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}> <Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} /> <Icon icon={MenuDots} />
@ -103,16 +128,27 @@
<ul <ul
transition:fly transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md"> class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
{#if canUnallow}
<li>
<Button onclick={() => unallowMember(pubkey)}>
<Icon icon={UserMinus} />
Remove User
</Button>
</li>
{/if}
{#if canBan}
<li> <li>
<Button class="text-error" onclick={() => banMember(pubkey)}> <Button class="text-error" onclick={() => banMember(pubkey)}>
<Icon icon={MinusCircle} /> <Icon icon={MinusCircle} />
Ban User Ban User
</Button> </Button>
</li> </li>
{/if}
</ul> </ul>
</Popover> </Popover>
{/if} {/if}
</div> </div>
{/if}
</div> </div>
</div> </div>
{/each} {/each}

View file

@ -3,6 +3,7 @@
import {manageRelay} from "@welshman/app" import {manageRelay} from "@welshman/app"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Restart from "@assets/icons/restart.svg?dataurl" import Restart from "@assets/icons/restart.svg?dataurl"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@ -15,7 +16,7 @@
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
import ModalFooter from "@lib/components/ModalFooter.svelte" import ModalFooter from "@lib/components/ModalFooter.svelte"
import Profile from "@app/components/Profile.svelte" import Profile from "@app/components/Profile.svelte"
import {deriveSpaceBannedPubkeyItems} from "@app/core/state" import {deriveSpaceBannedPubkeyItems, deriveSupportedMethods} from "@app/core/state"
import {pushToast} from "@app/util/toast" import {pushToast} from "@app/util/toast"
interface Props { interface Props {
@ -25,6 +26,9 @@
const {url}: Props = $props() const {url}: Props = $props()
const bans = deriveSpaceBannedPubkeyItems(url) const bans = deriveSpaceBannedPubkeyItems(url)
const supportedMethods = deriveSupportedMethods(url)
const canUnban = $derived($supportedMethods.includes(ManagementMethod.UnbanPubkey))
const canRestore = $derived($supportedMethods.includes(ManagementMethod.AllowPubkey))
const back = () => history.back() const back = () => history.back()
@ -36,6 +40,20 @@
menuPubkey = undefined menuPubkey = undefined
} }
const unbanMember = async (pubkey: string) => {
const {error} = await manageRelay(url, {
method: ManagementMethod.UnbanPubkey,
params: [pubkey],
})
if (error) {
pushToast({theme: "error", message: error})
} else {
pushToast({message: "User has successfully been removed from the ban list!"})
back()
}
}
const restoreMember = async (pubkey: string) => { const restoreMember = async (pubkey: string) => {
const {error} = await manageRelay(url, { const {error} = await manageRelay(url, {
method: ManagementMethod.AllowPubkey, method: ManagementMethod.AllowPubkey,
@ -45,7 +63,7 @@
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
} else { } else {
pushToast({message: "User has successfully been restored!"}) pushToast({message: "User has successfully been restored to membership!"})
back() back()
} }
} }
@ -66,6 +84,7 @@
<div class="min-w-0 flex-1"> <div class="min-w-0 flex-1">
<Profile {pubkey} {url} /> <Profile {pubkey} {url} />
</div> </div>
{#if canUnban || canRestore}
<div class="relative"> <div class="relative">
<Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}> <Button class="btn btn-circle btn-ghost btn-sm" onclick={() => toggleMenu(pubkey)}>
<Icon icon={MenuDots} /> <Icon icon={MenuDots} />
@ -75,16 +94,27 @@
<ul <ul
transition:fly transition:fly
class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md"> class="menu absolute right-0 z-popover mt-2 w-48 gap-1 rounded-box bg-base-100 p-2 shadow-md">
{#if canUnban}
<li>
<Button onclick={() => unbanMember(pubkey)}>
<Icon icon={CloseCircle} />
Unban User
</Button>
</li>
{/if}
{#if canRestore}
<li> <li>
<Button onclick={() => restoreMember(pubkey)}> <Button onclick={() => restoreMember(pubkey)}>
<Icon icon={Restart} /> <Icon icon={Restart} />
Restore User Restore User
</Button> </Button>
</li> </li>
{/if}
</ul> </ul>
</Popover> </Popover>
{/if} {/if}
</div> </div>
{/if}
</div> </div>
</div> </div>
{/each} {/each}

View file

@ -29,6 +29,7 @@ import {
fromPairs, fromPairs,
groupBy, groupBy,
remove, remove,
simpleCache,
} from "@welshman/lib" } from "@welshman/lib"
import type {Override} from "@welshman/lib" import type {Override} from "@welshman/lib"
import type {RepositoryUpdate} from "@welshman/net" import type {RepositoryUpdate} from "@welshman/net"
@ -1124,6 +1125,15 @@ export const deriveSocketStatus = (url: string) =>
}), }),
) )
export const deriveSupportedMethods = simpleCache(([url]: [string]) => {
return readable<ManagementMethod[]>([], set => {
manageRelay(url, {
method: ManagementMethod.SupportedMethods,
params: [],
}).then(({result = []}) => set(result))
})
})
export const deriveTimeout = (timeout: number) => { export const deriveTimeout = (timeout: number) => {
const store = writable<boolean>(false) const store = writable<boolean>(false)