forked from coracle/flotilla
Compare commits
9 Commits
dev
..
dcf332fbe4
| Author | SHA1 | Date | |
|---|---|---|---|
| dcf332fbe4 | |||
| 9fa1375435 | |||
| 132c7f031b | |||
| 964ef441ec | |||
| cdeb7afcae | |||
| 796f37d320 | |||
| b46fd94578 | |||
| bdc8e75640 | |||
| ef08821796 |
@@ -55,6 +55,7 @@
|
|||||||
"@capacitor/keyboard": "^8.0.0",
|
"@capacitor/keyboard": "^8.0.0",
|
||||||
"@capacitor/preferences": "^8.0.0",
|
"@capacitor/preferences": "^8.0.0",
|
||||||
"@capacitor/push-notifications": "^8.0.0",
|
"@capacitor/push-notifications": "^8.0.0",
|
||||||
|
"@capacitor/share": "^8.0.1",
|
||||||
"@capawesome/capacitor-android-dark-mode-support": "^8.0.0",
|
"@capawesome/capacitor-android-dark-mode-support": "^8.0.0",
|
||||||
"@capawesome/capacitor-badge": "^8.0.0",
|
"@capawesome/capacitor-badge": "^8.0.0",
|
||||||
"@getalby/lightning-tools": "^6.1.0",
|
"@getalby/lightning-tools": "^6.1.0",
|
||||||
|
|||||||
Generated
+12
@@ -47,6 +47,9 @@ importers:
|
|||||||
'@capacitor/push-notifications':
|
'@capacitor/push-notifications':
|
||||||
specifier: ^8.0.0
|
specifier: ^8.0.0
|
||||||
version: 8.0.0(@capacitor/core@8.0.1)
|
version: 8.0.0(@capacitor/core@8.0.1)
|
||||||
|
'@capacitor/share':
|
||||||
|
specifier: ^8.0.1
|
||||||
|
version: 8.0.1(@capacitor/core@8.0.1)
|
||||||
'@capawesome/capacitor-android-dark-mode-support':
|
'@capawesome/capacitor-android-dark-mode-support':
|
||||||
specifier: ^8.0.0
|
specifier: ^8.0.0
|
||||||
version: 8.0.0(@capacitor/core@8.0.1)
|
version: 8.0.0(@capacitor/core@8.0.1)
|
||||||
@@ -838,6 +841,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@capacitor/core': '>=8.0.0'
|
'@capacitor/core': '>=8.0.0'
|
||||||
|
|
||||||
|
'@capacitor/share@8.0.1':
|
||||||
|
resolution: {integrity: sha512-3cSBKBCJVon54rKDROP2rqGyeGks4pBh9TbaEk9S375Kbek/ZHe72N50zIa0Vn9Eac/SuhwgehO/mmA4CsUOiw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@capacitor/core': '>=8.0.0'
|
||||||
|
|
||||||
'@capacitor/synapse@1.0.4':
|
'@capacitor/synapse@1.0.4':
|
||||||
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
||||||
|
|
||||||
@@ -6021,6 +6029,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@capacitor/core': 8.0.1
|
'@capacitor/core': 8.0.1
|
||||||
|
|
||||||
|
'@capacitor/share@8.0.1(@capacitor/core@8.0.1)':
|
||||||
|
dependencies:
|
||||||
|
'@capacitor/core': 8.0.1
|
||||||
|
|
||||||
'@capacitor/synapse@1.0.4': {}
|
'@capacitor/synapse@1.0.4': {}
|
||||||
|
|
||||||
'@capawesome/capacitor-android-dark-mode-support@8.0.0(@capacitor/core@8.0.1)':
|
'@capawesome/capacitor-android-dark-mode-support@8.0.0(@capacitor/core@8.0.1)':
|
||||||
|
|||||||
@@ -279,7 +279,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</PageBar>
|
</PageBar>
|
||||||
|
|
||||||
<PageContent class="flex flex-col-reverse gap-2 pt-4">
|
<PageContent class="flex flex-col-reverse gap-2 py-4">
|
||||||
{#if missingRelayLists.length > 0}
|
{#if missingRelayLists.length > 0}
|
||||||
<div class="py-12">
|
<div class="py-12">
|
||||||
<div class="card2 col-2 m-auto max-w-md items-center text-center">
|
<div class="card2 col-2 m-auto max-w-md items-center text-center">
|
||||||
|
|||||||
@@ -243,7 +243,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $members.length > 0}
|
{#if $members !== undefined && $members.length > 0}
|
||||||
<div class="card2 card2-sm bg-alt flex items-center justify-between gap-4">
|
<div class="card2 card2-sm bg-alt flex items-center justify-between gap-4">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span>Members:</span>
|
<span>Members:</span>
|
||||||
@@ -251,6 +251,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<Button class="btn btn-neutral btn-sm" onclick={showMembers}>View All</Button>
|
<Button class="btn btn-neutral btn-sm" onclick={showMembers}>View All</Button>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if $members === undefined}
|
||||||
|
<div class="card2 card2-sm bg-base-200 flex items-center gap-4">
|
||||||
|
<span class="text-error">Member list not available from this relay</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="card2 card2-sm bg-alt col-4">
|
<div class="card2 card2-sm bg-alt col-4">
|
||||||
<strong class="text-lg">Room Settings</strong>
|
<strong class="text-lg">Room Settings</strong>
|
||||||
|
|||||||
@@ -73,6 +73,15 @@
|
|||||||
</ModalSubtitle>
|
</ModalSubtitle>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
{#if $members === undefined}
|
||||||
|
<div class="card2 bg-base-200 p-4">
|
||||||
|
<span class="text-error">Member list not available from this relay</span>
|
||||||
|
</div>
|
||||||
|
{:else if $members.length === 0}
|
||||||
|
<div class="card2 bg-base-200 p-4">
|
||||||
|
<span class="text-base-content/70">No members yet</span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
{#each $members as pubkey (pubkey)}
|
{#each $members as pubkey (pubkey)}
|
||||||
<div class="card2 bg-alt relative">
|
<div class="card2 bg-alt relative">
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
@@ -101,6 +110,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -56,6 +56,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
|
if (!$spaceMembers) {
|
||||||
|
addMembers()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const pubkeysSnapshot = $state.snapshot(pubkeys)
|
const pubkeysSnapshot = $state.snapshot(pubkeys)
|
||||||
const nonSpaceMembers = pubkeysSnapshot.filter(pubkey => !$spaceMembers.includes(pubkey))
|
const nonSpaceMembers = pubkeysSnapshot.filter(pubkey => !$spaceMembers.includes(pubkey))
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
import {sleep} from "@welshman/lib"
|
import {sleep} from "@welshman/lib"
|
||||||
import {request} from "@welshman/net"
|
import {request} from "@welshman/net"
|
||||||
import {displayRelayUrl, getTagValue, RELAY_INVITE} from "@welshman/util"
|
import {displayRelayUrl, getTagValue, RELAY_INVITE} from "@welshman/util"
|
||||||
|
import {Share} from "@capacitor/share"
|
||||||
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
import LinkRound from "@assets/icons/link-round.svg?dataurl"
|
||||||
|
import NativeShare from "@assets/icons/native-share.svg?dataurl"
|
||||||
import Copy from "@assets/icons/copy.svg?dataurl"
|
import Copy from "@assets/icons/copy.svg?dataurl"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import Field from "@lib/components/Field.svelte"
|
import Field from "@lib/components/Field.svelte"
|
||||||
@@ -28,6 +30,17 @@
|
|||||||
|
|
||||||
const copyInvite = () => clip(invite)
|
const copyInvite = () => clip(invite)
|
||||||
|
|
||||||
|
const shareInvite = async () => {
|
||||||
|
if (!canShare) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Share.share({url: invite})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let canShare = $state(false)
|
||||||
let claim = $state("")
|
let claim = $state("")
|
||||||
let loading = $state(true)
|
let loading = $state(true)
|
||||||
|
|
||||||
@@ -41,6 +54,13 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
const {value} = await Share.canShare()
|
||||||
|
canShare = value
|
||||||
|
} catch {
|
||||||
|
canShare = false
|
||||||
|
}
|
||||||
|
|
||||||
const [[event]] = await Promise.all([
|
const [[event]] = await Promise.all([
|
||||||
request({
|
request({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
@@ -74,17 +94,37 @@
|
|||||||
<p class="center">Oops! It looks like you're not a member of this relay.</p>
|
<p class="center">Oops! It looks like you're not a member of this relay.</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-col items-center gap-6">
|
<div class="flex flex-col items-center gap-6">
|
||||||
|
<div class="w-48">
|
||||||
<QRCode code={invite} />
|
<QRCode code={invite} />
|
||||||
|
</div>
|
||||||
<Field>
|
<Field>
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="input input-bordered flex w-full items-center gap-2">
|
<div class="flex w-full gap-2">
|
||||||
<Icon icon={LinkRound} />
|
{#if canShare}
|
||||||
<input bind:value={invite} class="grow" type="text" />
|
<Button
|
||||||
<Button onclick={copyInvite}>
|
class="input input-bordered flex shrink-0 w-12 items-center justify-center p-0"
|
||||||
|
onclick={shareInvite}
|
||||||
|
>
|
||||||
|
<Icon icon={NativeShare} />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<label class="input input-bordered flex min-w-0 flex-1 items-center gap-2">
|
||||||
|
<Icon icon={LinkRound} class="shrink-0" />
|
||||||
|
<input
|
||||||
|
bind:value={invite}
|
||||||
|
class="min-w-0 flex-1 truncate"
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button class="shrink-0" onclick={copyInvite}>
|
||||||
<Icon icon={Copy} />
|
<Icon icon={Copy} />
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#snippet info()}
|
{#snippet info()}
|
||||||
<p>
|
<p>
|
||||||
This invite link can be used by clicking "Add Space" and pasting it there.
|
This invite link can be used by clicking "Add Space" and pasting it there.
|
||||||
|
|||||||
@@ -112,6 +112,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
{#if $members === undefined}
|
||||||
|
<div class="card2 bg-base-200 p-4">
|
||||||
|
<span class="text-error">Member list not available from this space</span>
|
||||||
|
</div>
|
||||||
|
{:else if $members.length === 0}
|
||||||
|
<div class="card2 bg-base-200 p-4">
|
||||||
|
<span class="text-base-content/70">No members yet</span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
{#each $members as pubkey (pubkey)}
|
{#each $members as pubkey (pubkey)}
|
||||||
<div class="card2 card2-sm bg-alt relative">
|
<div class="card2 card2-sm bg-alt relative">
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
@@ -120,7 +129,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{#if canBan || canUnallow}
|
{#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} />
|
||||||
</Button>
|
</Button>
|
||||||
{#if menuPubkey === pubkey}
|
{#if menuPubkey === pubkey}
|
||||||
@@ -152,6 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|||||||
@@ -181,7 +181,11 @@
|
|||||||
<li>
|
<li>
|
||||||
<Button onclick={showMembers}>
|
<Button onclick={showMembers}>
|
||||||
<Icon icon={UserRounded} />
|
<Icon icon={UserRounded} />
|
||||||
|
{#if $members === undefined}
|
||||||
|
View Members
|
||||||
|
{:else}
|
||||||
View Members ({$members.length})
|
View Members ({$members.length})
|
||||||
|
{/if}
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
{#if $userIsAdmin}
|
{#if $userIsAdmin}
|
||||||
|
|||||||
@@ -766,9 +766,10 @@ export const addSpaceMembers = async (
|
|||||||
pubkeys: string[],
|
pubkeys: string[],
|
||||||
): Promise<string | undefined> => {
|
): Promise<string | undefined> => {
|
||||||
const spaceMembers = get(deriveSpaceMembers(url))
|
const spaceMembers = get(deriveSpaceMembers(url))
|
||||||
|
|
||||||
const results = await Promise.all(
|
const results = await Promise.all(
|
||||||
pubkeys
|
pubkeys
|
||||||
.filter(pubkey => !spaceMembers.includes(pubkey))
|
.filter(pubkey => !spaceMembers || !spaceMembers.includes(pubkey))
|
||||||
.map(pubkey =>
|
.map(pubkey =>
|
||||||
manageRelay(url, {
|
manageRelay(url, {
|
||||||
method: ManagementMethod.AllowPubkey,
|
method: ManagementMethod.AllowPubkey,
|
||||||
|
|||||||
+115
-86
@@ -615,7 +615,12 @@ export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByU
|
|||||||
|
|
||||||
for (const event of deleteEvents) {
|
for (const event of deleteEvents) {
|
||||||
for (const h of getTagValues("h", event.tags)) {
|
for (const h of getTagValues("h", event.tags)) {
|
||||||
deletedByH.set(h, max([deletedByH.get(h), event.created_at]))
|
const deletedAt = deletedByH.get(h)
|
||||||
|
|
||||||
|
deletedByH.set(
|
||||||
|
h,
|
||||||
|
deletedAt === undefined ? event.created_at : max([deletedAt, event.created_at]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,8 +628,9 @@ export const roomsByUrl = derived(roomMetaEventsByIdByUrl, roomMetaEventsByIdByU
|
|||||||
|
|
||||||
for (const event of metaEvents) {
|
for (const event of metaEvents) {
|
||||||
const meta = tryCatch(() => readRoomMeta(event))
|
const meta = tryCatch(() => readRoomMeta(event))
|
||||||
|
const deletedAt = meta ? deletedByH.get(meta.h) : undefined
|
||||||
|
|
||||||
if (!meta || gt(deletedByH.get(meta.h), meta.event.created_at)) {
|
if (!meta || (deletedAt !== undefined && !gt(meta.event.created_at, deletedAt))) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -808,36 +814,49 @@ export const deriveOtherRooms = (url: string) =>
|
|||||||
|
|
||||||
// Space/room memberships
|
// Space/room memberships
|
||||||
|
|
||||||
const getSpaceMembers = (_url: string, events: TrustedEvent[]) => {
|
export const deriveSpaceMembers = (url: string) =>
|
||||||
const members = new Set<string>()
|
derived(deriveRelaySignedEvents(url, [{kinds: [RELAY_MEMBERS]}]), ([event]) =>
|
||||||
|
uniq(getTagValues("member", event?.tags ?? [])),
|
||||||
|
)
|
||||||
|
|
||||||
for (const event of sortEventsAsc(events)) {
|
export const deriveRoomMembers = (url: string, h: string) => {
|
||||||
if (event.kind === RELAY_MEMBERS) {
|
const filters: Filter[] = [{kinds: [ROOM_MEMBERS], "#d": [h]}]
|
||||||
members.clear()
|
|
||||||
|
|
||||||
for (const pubkey of uniq(getTagValues("member", event.tags))) {
|
return derived(deriveEventsForUrl(url, filters), ([event]) =>
|
||||||
members.add(pubkey)
|
uniq(getPubkeyTagValues(event?.tags ?? [])),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BannedPubkeyItem = {
|
||||||
|
pubkey: string
|
||||||
|
reason: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spaceBannedPubkeyItems = new Map<string, BannedPubkeyItem[]>()
|
||||||
|
|
||||||
|
export const deriveSpaceBannedPubkeyItems = (url: string) => {
|
||||||
|
const store = writable(spaceBannedPubkeyItems.get(url) || [])
|
||||||
|
|
||||||
|
manageRelay(url, {method: ManagementMethod.ListBannedPubkeys, params: []}).then(res => {
|
||||||
|
spaceBannedPubkeyItems.set(url, res.result)
|
||||||
|
store.set(res.result)
|
||||||
|
})
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deriveRoomAdmins = (url: string, h: string) => {
|
||||||
|
const filters: Filter[] = [{kinds: [ROOM_ADMINS], "#d": [h]}]
|
||||||
|
|
||||||
|
return derived(deriveEventsForUrl(url, filters), $events => {
|
||||||
|
const adminsEvent = first($events)
|
||||||
|
|
||||||
|
if (adminsEvent) {
|
||||||
|
return getPubkeyTagValues(adminsEvent.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
return []
|
||||||
}
|
})
|
||||||
|
|
||||||
const pubkeys = getPubkeyTagValues(event.tags)
|
|
||||||
|
|
||||||
if (event.kind === RELAY_ADD_MEMBER) {
|
|
||||||
for (const pubkey of pubkeys) {
|
|
||||||
members.add(pubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.kind === RELAY_REMOVE_MEMBER) {
|
|
||||||
for (const pubkey of pubkeys) {
|
|
||||||
members.delete(pubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(members)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRoomMembers = (_url: string, h: string, events: TrustedEvent[]) => {
|
const getRoomMembers = (_url: string, h: string, events: TrustedEvent[]) => {
|
||||||
@@ -876,53 +895,6 @@ const getRoomMembers = (_url: string, h: string, events: TrustedEvent[]) => {
|
|||||||
return Array.from(members)
|
return Array.from(members)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveSpaceMembers = (url: string) =>
|
|
||||||
derived(
|
|
||||||
deriveRelaySignedEvents(url, [{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER, RELAY_MEMBERS]}]),
|
|
||||||
$events => getSpaceMembers(url, $events),
|
|
||||||
)
|
|
||||||
|
|
||||||
export type BannedPubkeyItem = {
|
|
||||||
pubkey: string
|
|
||||||
reason: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const spaceBannedPubkeyItems = new Map<string, BannedPubkeyItem[]>()
|
|
||||||
|
|
||||||
export const deriveSpaceBannedPubkeyItems = (url: string) => {
|
|
||||||
const store = writable(spaceBannedPubkeyItems.get(url) || [])
|
|
||||||
|
|
||||||
manageRelay(url, {method: ManagementMethod.ListBannedPubkeys, params: []}).then(res => {
|
|
||||||
spaceBannedPubkeyItems.set(url, res.result)
|
|
||||||
store.set(res.result)
|
|
||||||
})
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deriveRoomMembers = (url: string, h: string) => {
|
|
||||||
const filters: Filter[] = [
|
|
||||||
{kinds: [ROOM_MEMBERS], "#d": [h]},
|
|
||||||
{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER], "#h": [h]},
|
|
||||||
]
|
|
||||||
|
|
||||||
return derived(deriveEventsForUrl(url, filters), $events => getRoomMembers(url, h, $events))
|
|
||||||
}
|
|
||||||
|
|
||||||
export const deriveRoomAdmins = (url: string, h: string) => {
|
|
||||||
const filters: Filter[] = [{kinds: [ROOM_ADMINS], "#d": [h]}]
|
|
||||||
|
|
||||||
return derived(deriveEventsForUrl(url, filters), $events => {
|
|
||||||
const adminsEvent = first($events)
|
|
||||||
|
|
||||||
if (adminsEvent) {
|
|
||||||
return getPubkeyTagValues(adminsEvent.tags)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action items (admin review queue)
|
// Action items (admin review queue)
|
||||||
// const pendingJoins: TrustedEvent[] = []
|
// const pendingJoins: TrustedEvent[] = []
|
||||||
|
|
||||||
@@ -1019,19 +991,49 @@ export const deriveUserIsSpaceAdmin = memoize((url?: string) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const deriveUserSpaceMembershipStatus = (url: string) => {
|
export const deriveUserSpaceMembershipStatus = (url: string) => {
|
||||||
const filters: Filter[] = [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]
|
// Fetch member list and user add/remove events directly in this derivation.
|
||||||
|
const memberListFilters: Filter[] = [{kinds: [RELAY_MEMBERS]}]
|
||||||
|
const userEventFilters: Filter[] = [{kinds: [RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER]}]
|
||||||
|
|
||||||
return derived(
|
return derived(
|
||||||
[
|
[
|
||||||
pubkey,
|
pubkey,
|
||||||
deriveSpaceMembers(url),
|
deriveRelaySignedEvents(url, memberListFilters),
|
||||||
deriveEventsForUrl(url, filters),
|
deriveRelaySignedEvents(url, userEventFilters),
|
||||||
|
deriveEventsForUrl(url, [{kinds: [RELAY_JOIN, RELAY_LEAVE]}]),
|
||||||
deriveUserIsSpaceAdmin(url),
|
deriveUserIsSpaceAdmin(url),
|
||||||
],
|
],
|
||||||
([$pubkey, $members, $events, $isAdmin]) => {
|
([$pubkey, $memberListEvents, $userAddRemoveEvents, $joinLeaveEvents, $isAdmin]) => {
|
||||||
const isMember = $members.includes($pubkey!) || $isAdmin
|
// If admin, always granted.
|
||||||
|
if ($isAdmin) {
|
||||||
|
return MembershipStatus.Granted
|
||||||
|
}
|
||||||
|
|
||||||
for (const event of $events) {
|
const membersEvent = $memberListEvents.find(spec({kind: RELAY_MEMBERS}))
|
||||||
|
const memberList = membersEvent ? uniq(getTagValues("member", membersEvent.tags)) : undefined
|
||||||
|
|
||||||
|
let isMember = false
|
||||||
|
|
||||||
|
if (memberList) {
|
||||||
|
// Member list exists - check if user is in it.
|
||||||
|
isMember = memberList.includes($pubkey!)
|
||||||
|
} else {
|
||||||
|
// No member list available - replay the user's add/remove history.
|
||||||
|
for (const event of sortBy(e => e.created_at, $userAddRemoveEvents)) {
|
||||||
|
if (event.pubkey !== $pubkey) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.kind === RELAY_ADD_MEMBER) {
|
||||||
|
isMember = true
|
||||||
|
} else if (event.kind === RELAY_REMOVE_MEMBER) {
|
||||||
|
isMember = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const event of $joinLeaveEvents) {
|
||||||
|
// Join events indicate pending or granted status, leave resets to initial.
|
||||||
if (event.pubkey !== $pubkey) {
|
if (event.pubkey !== $pubkey) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1057,19 +1059,46 @@ export const deriveUserIsRoomAdmin = (url: string, h: string) =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const deriveUserRoomMembershipStatus = (url: string, h: string) => {
|
export const deriveUserRoomMembershipStatus = (url: string, h: string) => {
|
||||||
const filters: Filter[] = [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]
|
// Fetch the room member list and the current user's add/remove events.
|
||||||
|
const userEventFilters: Filter[] = [{kinds: [ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER], "#h": [h]}]
|
||||||
|
const joinLeaveFilters: Filter[] = [{kinds: [ROOM_JOIN, ROOM_LEAVE], "#h": [h]}]
|
||||||
|
|
||||||
return derived(
|
return derived(
|
||||||
[
|
[
|
||||||
pubkey,
|
pubkey,
|
||||||
deriveRoomMembers(url, h),
|
deriveRoomMembers(url, h),
|
||||||
deriveEventsForUrl(url, filters),
|
deriveEventsForUrl(url, userEventFilters),
|
||||||
|
deriveEventsForUrl(url, joinLeaveFilters),
|
||||||
deriveUserIsRoomAdmin(url, h),
|
deriveUserIsRoomAdmin(url, h),
|
||||||
],
|
],
|
||||||
([$pubkey, $members, $events, $isAdmin]) => {
|
([$pubkey, $memberList, $userAddRemoveEvents, $joinLeaveEvents, $isAdmin]) => {
|
||||||
const isMember = $members.includes($pubkey!) || $isAdmin
|
// If admin of this room's space, always granted.
|
||||||
|
if ($isAdmin) {
|
||||||
|
return MembershipStatus.Granted
|
||||||
|
}
|
||||||
|
|
||||||
for (const event of $events) {
|
let isMember = false
|
||||||
|
|
||||||
|
if ($memberList) {
|
||||||
|
// Member list exists - check if user is in it.
|
||||||
|
isMember = $memberList.includes($pubkey!)
|
||||||
|
} else {
|
||||||
|
// No member list available - replay the user's add/remove history.
|
||||||
|
for (const event of sortEventsAsc($userAddRemoveEvents)) {
|
||||||
|
if (event.pubkey !== $pubkey) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.kind === ROOM_ADD_MEMBER) {
|
||||||
|
isMember = true
|
||||||
|
} else if (event.kind === ROOM_REMOVE_MEMBER) {
|
||||||
|
isMember = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const event of $joinLeaveEvents) {
|
||||||
|
// Join events indicate pending or granted status, leave resets to initial.
|
||||||
if (event.pubkey !== $pubkey) {
|
if (event.pubkey !== $pubkey) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ const syncSpace = (url: string) => {
|
|||||||
{kinds: [MESSAGE, ...CONTENT_KINDS], since, "#h": [room]},
|
{kinds: [MESSAGE, ...CONTENT_KINDS], since, "#h": [room]},
|
||||||
makeCommentFilter(CONTENT_KINDS, {since, "#h": [room]}),
|
makeCommentFilter(CONTENT_KINDS, {since, "#h": [room]}),
|
||||||
{
|
{
|
||||||
kinds: [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER],
|
kinds: [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE],
|
||||||
"#h": [room],
|
"#h": [room],
|
||||||
},
|
},
|
||||||
{kinds: [PollResponse], since},
|
{kinds: [PollResponse], since},
|
||||||
@@ -293,15 +293,15 @@ const syncSpace = (url: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const relayKinds = [RELAY_MEMBERS, RELAY_ADD_MEMBER, RELAY_REMOVE_MEMBER]
|
const relayKinds = [RELAY_MEMBERS]
|
||||||
const roomMetaKinds = [ROOM_META, ROOM_ADMINS, ROOM_MEMBERS, LIVEKIT_PARTICIPANTS]
|
const roomMetaKinds = [ROOM_META, ROOM_ADMINS, ROOM_MEMBERS, LIVEKIT_PARTICIPANTS]
|
||||||
const roomMemberKinds = [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE, ROOM_ADD_MEMBER, ROOM_REMOVE_MEMBER]
|
const roomDeleteKinds = [ROOM_DELETE, ROOM_JOIN, ROOM_LEAVE]
|
||||||
|
|
||||||
pullAndListen({
|
pullAndListen({
|
||||||
url,
|
url,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
filters: [
|
filters: [
|
||||||
{kinds: [...relayKinds, ...roomMetaKinds, ...roomMemberKinds, ...CONTENT_KINDS, MESSAGE]},
|
{kinds: [...relayKinds, ...roomMetaKinds, ...roomDeleteKinds, ...CONTENT_KINDS, MESSAGE]},
|
||||||
makeCommentFilter(CONTENT_KINDS, {since}),
|
makeCommentFilter(CONTENT_KINDS, {since}),
|
||||||
{kinds: [PollResponse], since},
|
{kinds: [PollResponse], since},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export const makeEditor = async ({
|
|||||||
getValue: (profile: PublishedProfile) => profile.event.pubkey,
|
getValue: (profile: PublishedProfile) => profile.event.pubkey,
|
||||||
sortFn: ({score = 1, item}) => {
|
sortFn: ({score = 1, item}) => {
|
||||||
const wotScore = getWotGraph().get(item.event.pubkey) || 0
|
const wotScore = getWotGraph().get(item.event.pubkey) || 0
|
||||||
const membershipScale = $spaceMembers.includes(item.event.pubkey) ? 2 : 1
|
const membershipScale = $spaceMembers?.includes(item.event.pubkey) ? 2 : 1
|
||||||
|
|
||||||
return dec(score) * inc(wotScore / getMaxWot()) * membershipScale
|
return dec(score) * inc(wotScore / getMaxWot()) * membershipScale
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="9" cy="6" r="1.5" fill="#000000"/>
|
||||||
|
<circle cx="15" cy="6" r="1.5" fill="#000000"/>
|
||||||
|
<circle cx="9" cy="12" r="1.5" fill="#000000"/>
|
||||||
|
<circle cx="15" cy="12" r="1.5" fill="#000000"/>
|
||||||
|
<circle cx="9" cy="18" r="1.5" fill="#000000"/>
|
||||||
|
<circle cx="15" cy="18" r="1.5" fill="#000000"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 400 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="16" height="15" viewBox="0 0 16 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.17933 3.72734L7.41899 1.39855V9.76536C7.41899 10.0954 7.67913 10.3425 7.97911 10.3425C8.29941 10.3425 8.53924 10.0745 8.53924 9.76536V1.41957L10.7789 3.72734C10.8992 3.8513 11.039 3.89235 11.1789 3.89235C11.3187 3.89235 11.4789 3.83037 11.5788 3.72734C11.7992 3.50033 11.7992 3.13007 11.5788 2.88213L9.35938 0.574361C8.69927 -0.187929 7.37983 -0.187929 6.63916 0.553433L4.3791 2.90303C4.15879 3.13003 4.15879 3.5003 4.3791 3.74824C4.5994 3.97444 4.95952 3.97444 5.17905 3.72731L5.17933 3.72734Z" fill="#A6ADBB"/>
|
||||||
|
<path d="M13.8789 4.61365H11.2197C10.8994 4.61365 10.6596 4.8817 10.6596 5.19081C10.6596 5.49991 10.9197 5.76796 11.2197 5.76796H13.8797C14.4195 5.76796 14.8593 6.22115 14.8593 6.77737V12.7944C14.8593 13.3506 14.4195 13.8038 13.8797 13.8038L2.12025 13.8046C1.58044 13.8046 1.14063 13.3514 1.14063 12.7952L1.13985 6.79816C1.13985 6.24194 1.56014 5.78876 2.11947 5.78876H4.7795C5.0998 5.78876 5.33963 5.5207 5.33963 5.2116C5.33963 4.90169 5.0998 4.61353 4.7795 4.61353H2.12027C0.939881 4.61353 0 5.56177 0 6.79827V12.8153C0 14.0307 0.939781 15 2.12027 15H13.8797C15.0593 15 16 14.0317 16 12.8153L15.9992 6.79827C15.9992 5.56185 15.0594 4.61365 13.8789 4.61365Z" fill="#A6ADBB"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,53 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type {Snippet} from "svelte"
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
children: Snippet
|
|
||||||
root?: HTMLElement
|
|
||||||
initiallyVisible?: boolean
|
|
||||||
estimatedHeight?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const {children, root, initiallyVisible = false, estimatedHeight = 48}: Props = $props()
|
|
||||||
|
|
||||||
let visible = $state(initiallyVisible)
|
|
||||||
let height = $state(estimatedHeight)
|
|
||||||
let el: HTMLElement | undefined = $state()
|
|
||||||
let hasMeasured = false
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (!el) return
|
|
||||||
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
([entry]) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
visible = true
|
|
||||||
} else {
|
|
||||||
// Measure actual height before hiding content
|
|
||||||
if (el) {
|
|
||||||
const h = el.offsetHeight
|
|
||||||
if (h > 0) {
|
|
||||||
height = h
|
|
||||||
hasMeasured = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (hasMeasured) {
|
|
||||||
visible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{root: root || null, rootMargin: "1000px 0px"},
|
|
||||||
)
|
|
||||||
|
|
||||||
observer.observe(el)
|
|
||||||
return () => observer.disconnect()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div bind:this={el}>
|
|
||||||
{#if visible}
|
|
||||||
{@render children()}
|
|
||||||
{:else}
|
|
||||||
<div style:height="{height}px"></div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<Page>
|
<Page>
|
||||||
<ContentSearch>
|
<ContentSearch>
|
||||||
{#snippet input()}
|
{#snippet input()}
|
||||||
<label class="row-2 input input-bordered">
|
<label class="row-2 input input-bordered w-full">
|
||||||
<Icon icon={Magnifier} />
|
<Icon icon={Magnifier} />
|
||||||
<!-- svelte-ignore a11y_autofocus -->
|
<!-- svelte-ignore a11y_autofocus -->
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -58,14 +58,14 @@
|
|||||||
<RelaySettingsItem
|
<RelaySettingsItem
|
||||||
icon={Inbox}
|
icon={Inbox}
|
||||||
title="Inbox Relays"
|
title="Inbox Relays"
|
||||||
subtitle="Where you send your public notes. Be sure to select relays that will accept your notes, and which will let people who follow you read them."
|
subtitle="Where other people should send notes intended for you. Be sure to select relays that will accept notes that tag you."
|
||||||
relays={readRelayUrls}
|
relays={readRelayUrls}
|
||||||
addRelay={addReadRelay}
|
addRelay={addReadRelay}
|
||||||
removeRelay={removeReadRelay} />
|
removeRelay={removeReadRelay} />
|
||||||
<RelaySettingsItem
|
<RelaySettingsItem
|
||||||
icon={Plane}
|
icon={Plane}
|
||||||
title="Outbox Relays"
|
title="Outbox Relays"
|
||||||
subtitle="Where other people should send notes intended for you. Be sure to select relays that will accept notes that tag you."
|
subtitle="Where you send your public notes. Be sure to select relays that will accept your notes, and which will let people who follow you read them."
|
||||||
relays={writeRelayUrls}
|
relays={writeRelayUrls}
|
||||||
addRelay={addWriteRelay}
|
addRelay={addWriteRelay}
|
||||||
removeRelay={removeWriteRelay} />
|
removeRelay={removeWriteRelay} />
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount, tick} from "svelte"
|
import {onMount, tick} from "svelte"
|
||||||
|
import {flip} from "svelte/animate"
|
||||||
|
import {cubicOut} from "svelte/easing"
|
||||||
import {derived as _derived} from "svelte/store"
|
import {derived as _derived} from "svelte/store"
|
||||||
import {dec, insertAt, removeAt, sleep} from "@welshman/lib"
|
import {dec, insertAt, removeAt, sleep} from "@welshman/lib"
|
||||||
import type {RelayProfile} from "@welshman/util"
|
import type {RelayProfile} from "@welshman/util"
|
||||||
@@ -7,6 +9,7 @@
|
|||||||
import {relays, createSearch} from "@welshman/app"
|
import {relays, createSearch} from "@welshman/app"
|
||||||
import {createScroller} from "@lib/html"
|
import {createScroller} from "@lib/html"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
|
import DragHandle from "@assets/icons/drag-handle.svg?dataurl"
|
||||||
import Widget from "@assets/icons/widget-4.svg?dataurl"
|
import Widget from "@assets/icons/widget-4.svg?dataurl"
|
||||||
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
|
||||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||||
@@ -98,6 +101,8 @@
|
|||||||
const onDragStart = (e: DragEvent, url: string) => {
|
const onDragStart = (e: DragEvent, url: string) => {
|
||||||
draggedUrl = url
|
draggedUrl = url
|
||||||
dragStartOrder = [...orderedSpaceUrls]
|
dragStartOrder = [...orderedSpaceUrls]
|
||||||
|
lastDragTarget = undefined
|
||||||
|
didDrop = false
|
||||||
|
|
||||||
if (e.dataTransfer) {
|
if (e.dataTransfer) {
|
||||||
e.dataTransfer.effectAllowed = "move"
|
e.dataTransfer.effectAllowed = "move"
|
||||||
@@ -105,15 +110,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDragOver = (e: DragEvent, targetUrl: string) => {
|
const onDragOver = (e: DragEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDragEnter = (e: DragEvent, targetUrl: string) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (lastDragTarget === targetUrl) return
|
||||||
|
|
||||||
|
lastDragTarget = targetUrl
|
||||||
reorderSpaceUrls(targetUrl)
|
reorderSpaceUrls(targetUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDrop = (e: DragEvent, targetUrl: string) => {
|
const onDrop = (e: DragEvent, targetUrl: string) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
reorderSpaceUrls(targetUrl)
|
reorderSpaceUrls(targetUrl)
|
||||||
|
didDrop = true
|
||||||
draggedUrl = undefined
|
draggedUrl = undefined
|
||||||
|
lastDragTarget = undefined
|
||||||
|
|
||||||
if (dragStartOrder && !isSameOrder(dragStartOrder, orderedSpaceUrls)) {
|
if (dragStartOrder && !isSameOrder(dragStartOrder, orderedSpaceUrls)) {
|
||||||
void setSpaceMembershipOrder(orderedSpaceUrls).catch(console.error)
|
void setSpaceMembershipOrder(orderedSpaceUrls).catch(console.error)
|
||||||
@@ -123,8 +138,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onDragEnd = () => {
|
const onDragEnd = () => {
|
||||||
|
if (!didDrop && dragStartOrder && !isSameOrder(dragStartOrder, orderedSpaceUrls)) {
|
||||||
|
orderedSpaceUrls = dragStartOrder
|
||||||
|
}
|
||||||
|
|
||||||
draggedUrl = undefined
|
draggedUrl = undefined
|
||||||
dragStartOrder = undefined
|
dragStartOrder = undefined
|
||||||
|
lastDragTarget = undefined
|
||||||
|
didDrop = false
|
||||||
}
|
}
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -143,6 +164,8 @@
|
|||||||
let orderedSpaceUrls = $state<string[]>([])
|
let orderedSpaceUrls = $state<string[]>([])
|
||||||
let draggedUrl = $state<string | undefined>()
|
let draggedUrl = $state<string | undefined>()
|
||||||
let dragStartOrder = $state<string[] | undefined>()
|
let dragStartOrder = $state<string[] | undefined>()
|
||||||
|
let lastDragTarget = $state<string | undefined>()
|
||||||
|
let didDrop = $state(false)
|
||||||
|
|
||||||
const openSearch = () => {
|
const openSearch = () => {
|
||||||
showSearch = true
|
showSearch = true
|
||||||
@@ -247,17 +270,25 @@
|
|||||||
<Divider>Your spaces</Divider>
|
<Divider>Your spaces</Divider>
|
||||||
{#each filteredUserUrls as url (url)}
|
{#each filteredUserUrls as url (url)}
|
||||||
<div
|
<div
|
||||||
class:opacity-60={draggedUrl === url}
|
animate:flip={{duration: 300, easing: cubicOut}}
|
||||||
|
class="transition-opacity duration-200 {draggedUrl === url ? 'opacity-50' : ''}"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
ondragstart={e => onDragStart(e, url)}
|
ondragstart={e => onDragStart(e, url)}
|
||||||
ondragover={e => onDragOver(e, url)}
|
ondragover={onDragOver}
|
||||||
|
ondragenter={e => onDragEnter(e, url)}
|
||||||
ondrop={e => onDrop(e, url)}
|
ondrop={e => onDrop(e, url)}
|
||||||
ondragend={onDragEnd}>
|
ondragend={onDragEnd}>
|
||||||
<Button
|
<Button
|
||||||
class="card2 bg-alt shadow-md transition-all hover:shadow-lg hover:dark:brightness-[1.1] w-full relative"
|
class="group card2 bg-alt shadow-md transition-all hover:shadow-lg hover:dark:brightness-[1.1] w-full relative min-w-0"
|
||||||
onclick={() => openSpace(url)}>
|
onclick={() => openSpace(url)}>
|
||||||
|
<div class="flex w-full items-start gap-2">
|
||||||
|
<div
|
||||||
|
class="mt-4 flex cursor-grab p-1 text-base-content/30 transition-colors group-hover:text-base-content/60">
|
||||||
|
<Icon icon={DragHandle} />
|
||||||
|
</div>
|
||||||
<RelaySummary hideFavorites {url} />
|
<RelaySummary hideFavorites {url} />
|
||||||
|
</div>
|
||||||
{#if $notifications.has(makeSpacePath(url))}
|
{#if $notifications.has(makeSpacePath(url))}
|
||||||
<div class="absolute right-3 top-3 h-2 w-2 rounded-full bg-primary"></div>
|
<div class="absolute right-3 top-3 h-2 w-2 rounded-full bg-primary"></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
||||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
||||||
import VirtualItem from "@lib/components/VirtualItem.svelte"
|
|
||||||
import RoomComposeEdit from "@src/app/components/RoomComposeEdit.svelte"
|
import RoomComposeEdit from "@src/app/components/RoomComposeEdit.svelte"
|
||||||
import {canEnforceNip70, prependParent, publishDelete} from "@app/core/commands"
|
import {canEnforceNip70, prependParent, publishDelete} from "@app/core/commands"
|
||||||
import {
|
import {
|
||||||
@@ -486,7 +485,6 @@
|
|||||||
{:else if type === "date"}
|
{:else if type === "date"}
|
||||||
<Divider>{value}</Divider>
|
<Divider>{value}</Divider>
|
||||||
{:else if shouldVirtualize}
|
{:else if shouldVirtualize}
|
||||||
<VirtualItem root={element} initiallyVisible={i < 25}>
|
|
||||||
{@const event = value as TrustedEvent}
|
{@const event = value as TrustedEvent}
|
||||||
{#if event.kind === ROOM_ADD_MEMBER}
|
{#if event.kind === ROOM_ADD_MEMBER}
|
||||||
<RoomItemAddMember {url} {event} />
|
<RoomItemAddMember {url} {event} />
|
||||||
@@ -502,7 +500,6 @@
|
|||||||
onEdit={onEditEvent} />
|
onEdit={onEditEvent} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</VirtualItem>
|
|
||||||
{:else}
|
{:else}
|
||||||
{@const event = value as TrustedEvent}
|
{@const event = value as TrustedEvent}
|
||||||
{#if event.kind === ROOM_ADD_MEMBER}
|
{#if event.kind === ROOM_ADD_MEMBER}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
||||||
import RoomItem from "@app/components/RoomItem.svelte"
|
import RoomItem from "@app/components/RoomItem.svelte"
|
||||||
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
import RoomItemAddMember from "@src/app/components/RoomItemAddMember.svelte"
|
||||||
import VirtualItem from "@lib/components/VirtualItem.svelte"
|
|
||||||
|
|
||||||
import RoomCompose from "@app/components/RoomCompose.svelte"
|
import RoomCompose from "@app/components/RoomCompose.svelte"
|
||||||
import RoomComposeEdit from "@src/app/components/RoomComposeEdit.svelte"
|
import RoomComposeEdit from "@src/app/components/RoomComposeEdit.svelte"
|
||||||
@@ -324,7 +323,6 @@
|
|||||||
{:else if type === "date"}
|
{:else if type === "date"}
|
||||||
<Divider>{value}</Divider>
|
<Divider>{value}</Divider>
|
||||||
{:else if shouldVirtualize}
|
{:else if shouldVirtualize}
|
||||||
<VirtualItem root={element} initiallyVisible={i < 25}>
|
|
||||||
{@const event = value as TrustedEvent}
|
{@const event = value as TrustedEvent}
|
||||||
{#if event.kind === RELAY_ADD_MEMBER}
|
{#if event.kind === RELAY_ADD_MEMBER}
|
||||||
<RoomItemAddMember {url} {event} />
|
<RoomItemAddMember {url} {event} />
|
||||||
@@ -340,7 +338,6 @@
|
|||||||
{addSpaceBelow} />
|
{addSpaceBelow} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</VirtualItem>
|
|
||||||
{:else}
|
{:else}
|
||||||
{@const event = value as TrustedEvent}
|
{@const event = value as TrustedEvent}
|
||||||
{#if event.kind === RELAY_ADD_MEMBER}
|
{#if event.kind === RELAY_ADD_MEMBER}
|
||||||
|
|||||||
Reference in New Issue
Block a user