forked from coracle/flotilla
Move from topic to room using tilde
This commit is contained in:
@@ -1,3 +1,10 @@
|
|||||||
# Flotilla
|
# Flotilla
|
||||||
|
|
||||||
A discord-like nostr client. WIP.
|
A discord-like nostr client. WIP.
|
||||||
|
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
- Privacy, migrations, and content replication
|
||||||
|
- Allow relays to strip signatures based on auth'd user
|
||||||
|
- Federated relays/admins can get signatures
|
||||||
|
- Other users have to opt-in to using the relay in trusted mode
|
||||||
|
|||||||
+6
-5
@@ -14,7 +14,7 @@ import {
|
|||||||
loadMutes,
|
loadMutes,
|
||||||
followsByPubkey,
|
followsByPubkey,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import {MEMBERSHIPS, INDEXER_RELAYS} from "@app/state"
|
import {ROOM, MEMBERSHIPS, INDEXER_RELAYS} from "@app/state"
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
@@ -95,9 +95,9 @@ export const updateList = async (kind: number, modifyTags: ModifyTags) => {
|
|||||||
export const addSpaceMembership = (url: string) =>
|
export const addSpaceMembership = (url: string) =>
|
||||||
updateList(MEMBERSHIPS, (tags: string[][]) => uniqBy(t => t.join(""), [...tags, ["r", url]]))
|
updateList(MEMBERSHIPS, (tags: string[][]) => uniqBy(t => t.join(""), [...tags, ["r", url]]))
|
||||||
|
|
||||||
export const addRoomMembership = (url: string, topic: string) =>
|
export const addRoomMembership = (url: string, room: string) =>
|
||||||
updateList(MEMBERSHIPS, (tags: string[][]) =>
|
updateList(MEMBERSHIPS, (tags: string[][]) =>
|
||||||
uniqBy(t => t.join(""), [...tags, ["t", topic, url]]),
|
uniqBy(t => t.join(""), [...tags, [ROOM, room, url]]),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const removeSpaceMembership = (url: string) =>
|
export const removeSpaceMembership = (url: string) =>
|
||||||
@@ -105,5 +105,6 @@ export const removeSpaceMembership = (url: string) =>
|
|||||||
tags.filter(t => !equals(["r", url], t) && t[2] !== url),
|
tags.filter(t => !equals(["r", url], t) && t[2] !== url),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const removeRoomMembership = (url: string, topic: string) =>
|
export const removeRoomMembership = (url: string, room: string) =>
|
||||||
updateList(MEMBERSHIPS, (tags: string[][]) => tags.filter(t => !equals(["t", topic, url], t)))
|
updateList(MEMBERSHIPS, (tags: string[][]) => tags.filter(t => !equals([ROOM, room, url], t)))
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {makeMention, makeIMeta} from "@app/commands"
|
import {makeMention, makeIMeta} from "@app/commands"
|
||||||
import {getChatEditorOptions, addFile} from "@app/editor"
|
import {getChatEditorOptions, addFile} from "@app/editor"
|
||||||
import {MESSAGE} from "@app/state"
|
import {ROOM, MESSAGE, GENERAL} from "@app/state"
|
||||||
|
|
||||||
export let url
|
export let url
|
||||||
export let topic = ""
|
export let room = GENERAL
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@
|
|||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
const json = $editor.getJSON()
|
const json = $editor.getJSON()
|
||||||
const topicTags = topic ? [["t", topic]] : []
|
|
||||||
const mentionTags = findNodes(NProfileExtension.name, json).map(m =>
|
const mentionTags = findNodes(NProfileExtension.name, json).map(m =>
|
||||||
makeMention(m.attrs!.pubkey, m.attrs!.relays),
|
makeMention(m.attrs!.pubkey, m.attrs!.relays),
|
||||||
)
|
)
|
||||||
@@ -32,7 +31,7 @@
|
|||||||
|
|
||||||
const event = createEvent(MESSAGE, {
|
const event = createEvent(MESSAGE, {
|
||||||
content: $editor.getText(),
|
content: $editor.getText(),
|
||||||
tags: [["-"], ...topicTags, ...mentionTags, ...imetaTags],
|
tags: [[ROOM, room], ...mentionTags, ...imetaTags],
|
||||||
})
|
})
|
||||||
|
|
||||||
publishThunk(makeThunk({event, relays: [url]}))
|
publishThunk(makeThunk({event, relays: [url]}))
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import type {PublishStatusData} from "@welshman/app"
|
import type {PublishStatusData} from "@welshman/app"
|
||||||
import {
|
import {
|
||||||
GROUP_REPLY,
|
|
||||||
REACTION,
|
REACTION,
|
||||||
ZAP_RESPONSE,
|
ZAP_RESPONSE,
|
||||||
displayRelayUrl,
|
displayRelayUrl,
|
||||||
@@ -27,7 +26,7 @@
|
|||||||
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 Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import {deriveEvent, displayReaction} from "@app/state"
|
import {REPLY, deriveEvent, displayReaction} from "@app/state"
|
||||||
import {getChatViewOptions} from "@app/editor"
|
import {getChatViewOptions} from "@app/editor"
|
||||||
|
|
||||||
export let event: TrustedEvent
|
export let event: TrustedEvent
|
||||||
@@ -85,7 +84,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div in:fly class="group relative flex flex-col gap-1 p-2 transition-colors hover:bg-base-300">
|
<div in:fly class="group relative flex flex-col gap-1 p-2 transition-colors hover:bg-base-300">
|
||||||
{#if event.kind === GROUP_REPLY}
|
{#if event.kind === REPLY}
|
||||||
<div class="flex items-center gap-1 pl-12 text-xs">
|
<div class="flex items-center gap-1 pl-12 text-xs">
|
||||||
<Icon icon="arrow-right" />
|
<Icon icon="arrow-right" />
|
||||||
<Avatar src={$parentProfile?.picture} size={4} />
|
<Avatar src={$parentProfile?.picture} size={4} />
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
const event = createEvent(kind, {
|
const event = createEvent(kind, {
|
||||||
content: $editor.getText(),
|
content: $editor.getText(),
|
||||||
tags: [
|
tags: [
|
||||||
["-"],
|
|
||||||
["d", randomId()],
|
["d", randomId()],
|
||||||
["title", title],
|
["title", title],
|
||||||
["location", location],
|
["location", location],
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
class="!h-10 !w-10 border border-solid border-base-300"
|
class="!h-10 !w-10 border border-solid border-base-300"
|
||||||
size={7} />
|
size={7} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
{#each $userMembership?.topicsByUrl.keys() || [] as url (url)}
|
{#each $userMembership?.roomsByUrl.keys() || [] as url (url)}
|
||||||
<PrimaryNavItem title={displayRelayUrl(url)} href={makeSpacePath(url)}>
|
<PrimaryNavItem title={displayRelayUrl(url)} href={makeSpacePath(url)}>
|
||||||
<SpaceAvatar {url} />
|
<SpaceAvatar {url} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
const tryCreate = async () => {
|
const tryCreate = async () => {
|
||||||
await addRoomMembership(url, topic)
|
await addRoomMembership(url, room)
|
||||||
|
|
||||||
goto(makeSpacePath(url, topic))
|
goto(makeSpacePath(url, room))
|
||||||
}
|
}
|
||||||
|
|
||||||
const create = async () => {
|
const create = async () => {
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let topic = ""
|
let room = ""
|
||||||
let loading = false
|
let loading = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<p slot="label">Room Name</p>
|
<p slot="label">Room Name</p>
|
||||||
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
|
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
|
||||||
<Icon icon="hashtag" />
|
<Icon icon="hashtag" />
|
||||||
<input bind:value={topic} class="grow" type="text" />
|
<input bind:value={room} class="grow" type="text" />
|
||||||
</label>
|
</label>
|
||||||
</Field>
|
</Field>
|
||||||
<div class="flex flex-row items-center justify-between gap-4">
|
<div class="flex flex-row items-center justify-between gap-4">
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<Icon icon="alt-arrow-left" />
|
<Icon icon="alt-arrow-left" />
|
||||||
Go back
|
Go back
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" class="btn btn-primary" disabled={!topic || loading}>
|
<Button type="submit" class="btn btn-primary" disabled={!room || loading}>
|
||||||
<Spinner {loading}>Create Room</Spinner>
|
<Spinner {loading}>Create Room</Spinner>
|
||||||
<Icon icon="alt-arrow-right" />
|
<Icon icon="alt-arrow-right" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ export const makeSpacePath = (url: string, extra = "") => {
|
|||||||
export const getPrimaryNavItem = ($page: Page) => $page.route?.id?.split("/")[1]
|
export const getPrimaryNavItem = ($page: Page) => $page.route?.id?.split("/")[1]
|
||||||
|
|
||||||
export const getPrimaryNavItemIndex = ($page: Page) => {
|
export const getPrimaryNavItemIndex = ($page: Page) => {
|
||||||
const urls = Array.from(userMembership.get()?.topicsByUrl.keys() || [])
|
const urls = Array.from(userMembership.get()?.roomsByUrl.keys() || [])
|
||||||
|
|
||||||
switch (getPrimaryNavItem($page)) {
|
switch (getPrimaryNavItem($page)) {
|
||||||
case "discover":
|
case "discover":
|
||||||
|
|||||||
+28
-25
@@ -1,7 +1,7 @@
|
|||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
import {get, derived} from "svelte/store"
|
import {get, derived} from "svelte/store"
|
||||||
import type {Maybe} from "@welshman/lib"
|
import type {Maybe} from "@welshman/lib"
|
||||||
import {setContext, max, pushToMapKey, nthEq} from "@welshman/lib"
|
import {setContext, nth, max, pushToMapKey, nthEq} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
getIdFilters,
|
getIdFilters,
|
||||||
RELAYS,
|
RELAYS,
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
EVENT_DATE,
|
EVENT_DATE,
|
||||||
EVENT_TIME,
|
EVENT_TIME,
|
||||||
getRelayTagValues,
|
getRelayTagValues,
|
||||||
getTopicTagValues,
|
|
||||||
isShareableRelayUrl,
|
isShareableRelayUrl,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
@@ -31,9 +30,13 @@ import {
|
|||||||
import type {SubscribeRequestWithHandlers} from "@welshman/net"
|
import type {SubscribeRequestWithHandlers} from "@welshman/net"
|
||||||
import {deriveEvents, deriveEventsMapped, withGetter} from "@welshman/store"
|
import {deriveEvents, deriveEventsMapped, withGetter} from "@welshman/store"
|
||||||
|
|
||||||
|
export const ROOM = "~"
|
||||||
|
|
||||||
|
export const GENERAL = "general"
|
||||||
|
|
||||||
export const MESSAGE = 209
|
export const MESSAGE = 209
|
||||||
|
|
||||||
export const REPLY = 210
|
export const REPLY = 1111
|
||||||
|
|
||||||
export const MEMBERSHIPS = 10209
|
export const MEMBERSHIPS = 10209
|
||||||
|
|
||||||
@@ -75,7 +78,7 @@ export const deriveEvent = (idOrAddress: string, hints: string[] = []) => {
|
|||||||
// Membership
|
// Membership
|
||||||
|
|
||||||
export type Membership = {
|
export type Membership = {
|
||||||
topicsByUrl: Map<string, string[]>
|
roomsByUrl: Map<string, string[]>
|
||||||
event?: TrustedEvent
|
event?: TrustedEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,17 +87,17 @@ export type PublishedMembership = Omit<Membership, "event"> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const readMembership = (event: TrustedEvent): PublishedMembership => {
|
export const readMembership = (event: TrustedEvent): PublishedMembership => {
|
||||||
const topicsByUrl = new Map<string, string[]>()
|
const roomsByUrl = new Map<string, string[]>()
|
||||||
|
|
||||||
for (const tag of event.tags.filter(nthEq(0, "r"))) {
|
for (const tag of event.tags.filter(nthEq(0, "r"))) {
|
||||||
topicsByUrl.set(tag[1], [])
|
roomsByUrl.set(tag[1], [])
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tag of event.tags.filter(nthEq(0, "t"))) {
|
for (const tag of event.tags.filter(nthEq(0, "t"))) {
|
||||||
pushToMapKey(topicsByUrl, tag[2], tag[1])
|
pushToMapKey(roomsByUrl, tag[2], tag[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
return {event, topicsByUrl}
|
return {event, roomsByUrl}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const memberships = deriveEventsMapped<PublishedMembership>(repository, {
|
export const memberships = deriveEventsMapped<PublishedMembership>(repository, {
|
||||||
@@ -121,16 +124,16 @@ export const {
|
|||||||
// Messages
|
// Messages
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
topic: string
|
room: string
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
export const readMessage = (event: TrustedEvent): Maybe<Message> => {
|
export const readMessage = (event: TrustedEvent): Maybe<Message> => {
|
||||||
const topics = getTopicTagValues(event.tags)
|
const rooms = event.tags.filter(nthEq(0, ROOM)).map(nth(1))
|
||||||
|
|
||||||
if (topics.length > 1) return undefined
|
if (rooms.length > 1) return undefined
|
||||||
|
|
||||||
return {topic: topics[0] || "", event}
|
return {room: rooms[0] || "", event}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messages = deriveEventsMapped<Message>(repository, {
|
export const messages = deriveEventsMapped<Message>(repository, {
|
||||||
@@ -144,11 +147,11 @@ export const messages = deriveEventsMapped<Message>(repository, {
|
|||||||
export type Chat = {
|
export type Chat = {
|
||||||
id: string
|
id: string
|
||||||
url: string
|
url: string
|
||||||
topic: string
|
room: string
|
||||||
messages: Message[]
|
messages: Message[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeChatId = (url: string, topic: string) => `${url}'${topic}`
|
export const makeChatId = (url: string, room: string) => `${url}'${room}`
|
||||||
|
|
||||||
export const splitChatId = (id: string) => id.split("'")
|
export const splitChatId = (id: string) => id.split("'")
|
||||||
|
|
||||||
@@ -157,16 +160,16 @@ export const chats = derived([trackerStore, messages], ([$tracker, $messages]) =
|
|||||||
|
|
||||||
for (const message of $messages) {
|
for (const message of $messages) {
|
||||||
for (const url of $tracker.getRelays(message.event.id)) {
|
for (const url of $tracker.getRelays(message.event.id)) {
|
||||||
const chatId = makeChatId(url, message.topic)
|
const chatId = makeChatId(url, message.room)
|
||||||
|
|
||||||
pushToMapKey(messagesByChatId, chatId, message)
|
pushToMapKey(messagesByChatId, chatId, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(messagesByChatId.entries()).map(([id, messages]) => {
|
return Array.from(messagesByChatId.entries()).map(([id, messages]) => {
|
||||||
const [url, topic] = splitChatId(id)
|
const [url, room] = splitChatId(id)
|
||||||
|
|
||||||
return {id, url, topic, messages}
|
return {id, url, room, messages}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -179,12 +182,12 @@ export const {
|
|||||||
store: chats,
|
store: chats,
|
||||||
getKey: chat => chat.id,
|
getKey: chat => chat.id,
|
||||||
load: (id: string, request: Partial<SubscribeRequestWithHandlers> = {}) => {
|
load: (id: string, request: Partial<SubscribeRequestWithHandlers> = {}) => {
|
||||||
const [url, topic] = splitChatId(id)
|
const [url, room] = splitChatId(id)
|
||||||
const chat = get(chatsById).get(id)
|
const chat = get(chatsById).get(id)
|
||||||
const timestamps = chat?.messages.map(m => m.event.created_at) || []
|
const timestamps = chat?.messages.map(m => m.event.created_at) || []
|
||||||
const since = Math.max(0, max(timestamps) - 3600)
|
const since = Math.max(0, max(timestamps) - 3600)
|
||||||
|
|
||||||
return load({...request, relays: [url], filters: [{"#t": [topic], since}]})
|
return load({...request, relays: [url], filters: [{'#~': [room], since}]})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -204,18 +207,18 @@ export const eventsByUrl = derived([trackerStore, events], ([$tracker, $events])
|
|||||||
return eventsByUrl
|
return eventsByUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
// Topics
|
// Rooms
|
||||||
|
|
||||||
export const topicsByUrl = derived(chats, $chats => {
|
export const roomsByUrl = derived(chats, $chats => {
|
||||||
const $topicsByUrl = new Map<string, string[]>()
|
const $roomsByUrl = new Map<string, string[]>()
|
||||||
|
|
||||||
for (const chat of $chats) {
|
for (const chat of $chats) {
|
||||||
if (chat.topic) {
|
if (chat.room) {
|
||||||
pushToMapKey($topicsByUrl, chat.url, chat.topic)
|
pushToMapKey($roomsByUrl, chat.url, chat.room)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $topicsByUrl
|
return $roomsByUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
// User stuff
|
// User stuff
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if $userMembership?.topicsByUrl.has(relay.url)}
|
{#if $userMembership?.roomsByUrl.has(relay.url)}
|
||||||
<div class="center absolute flex w-full">
|
<div class="center absolute flex w-full">
|
||||||
<div
|
<div
|
||||||
class="tooltip relative left-8 top-[38px] h-5 w-5 rounded-full bg-primary"
|
class="tooltip relative left-8 top-[38px] h-5 w-5 rounded-full bg-primary"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
import SpaceExit from "@app/components/SpaceExit.svelte"
|
import SpaceExit from "@app/components/SpaceExit.svelte"
|
||||||
import SpaceJoin from "@app/components/SpaceJoin.svelte"
|
import SpaceJoin from "@app/components/SpaceJoin.svelte"
|
||||||
import RoomCreate from "@app/components/RoomCreate.svelte"
|
import RoomCreate from "@app/components/RoomCreate.svelte"
|
||||||
import {userMembership, topicsByUrl, decodeNRelay, MESSAGE, REPLY} from "@app/state"
|
import {userMembership, roomsByUrl, decodeNRelay, GENERAL, MESSAGE, REPLY} from "@app/state"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
let showMenu = false
|
let showMenu = false
|
||||||
|
|
||||||
$: url = decodeNRelay($page.params.nrelay)
|
$: url = decodeNRelay($page.params.nrelay)
|
||||||
$: rooms = sort($userMembership?.topicsByUrl?.get(url) || [])
|
$: rooms = sort($userMembership?.roomsByUrl?.get(url) || [])
|
||||||
$: otherRooms = ($topicsByUrl.get(url) || []).filter(t => !rooms.includes(t))
|
$: otherRooms = ($roomsByUrl.get(url) || []).filter(room => !rooms.concat(GENERAL).includes(room))
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const kinds = [MESSAGE, REPLY, EVENT_DATE, EVENT_TIME, CLASSIFIED]
|
const kinds = [MESSAGE, REPLY, EVENT_DATE, EVENT_TIME, CLASSIFIED]
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
<ul
|
<ul
|
||||||
transition:fly
|
transition:fly
|
||||||
class="menu absolute z-popover mt-2 w-full rounded-box bg-base-100 p-2 shadow-xl">
|
class="menu absolute z-popover mt-2 w-full rounded-box bg-base-100 p-2 shadow-xl">
|
||||||
{#if $userMembership?.topicsByUrl.has(url)}
|
{#if $userMembership?.roomsByUrl.has(url)}
|
||||||
<li class="text-error">
|
<li class="text-error">
|
||||||
<Button on:click={leaveSpace}>
|
<Button on:click={leaveSpace}>
|
||||||
<Icon icon="exit" />
|
<Icon icon="exit" />
|
||||||
@@ -113,11 +113,11 @@
|
|||||||
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
|
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each rooms as topic, i (topic)}
|
{#each rooms as room, i (room)}
|
||||||
<div transition:slide={{delay: getDelay()}}>
|
<div transition:slide={{delay: getDelay()}}>
|
||||||
<SecondaryNavItem href={makeSpacePath(url, topic)}>
|
<SecondaryNavItem href={makeSpacePath(url, room)}>
|
||||||
<Icon icon="hashtag" />
|
<Icon icon="hashtag" />
|
||||||
{topic}
|
{room}
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -133,11 +133,11 @@
|
|||||||
</SecondaryNavHeader>
|
</SecondaryNavHeader>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each otherRooms as topic, i (topic)}
|
{#each otherRooms as room, i (room)}
|
||||||
<div transition:slide={{delay: getDelay()}}>
|
<div transition:slide={{delay: getDelay()}}>
|
||||||
<SecondaryNavItem href={makeSpacePath(url, topic)}>
|
<SecondaryNavItem href={makeSpacePath(url, room)}>
|
||||||
<Icon icon="hashtag" />
|
<Icon icon="hashtag" />
|
||||||
{topic}
|
{room}
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
</SecondaryNavSection>
|
</SecondaryNavSection>
|
||||||
</SecondaryNav>
|
</SecondaryNav>
|
||||||
<Page>
|
<Page>
|
||||||
{#key $page.params.topic}
|
{#key $page.params.room}
|
||||||
<slot />
|
<slot />
|
||||||
{/key}
|
{/key}
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
+10
-10
@@ -18,19 +18,19 @@
|
|||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
import ChatMessage from "@app/components/ChatMessage.svelte"
|
import ChatMessage from "@app/components/ChatMessage.svelte"
|
||||||
import ChatCompose from "@app/components/ChatCompose.svelte"
|
import ChatCompose from "@app/components/ChatCompose.svelte"
|
||||||
import {userMembership, decodeNRelay, makeChatId, deriveChat} from "@app/state"
|
import {userMembership, decodeNRelay, makeChatId, deriveChat, GENERAL} from "@app/state"
|
||||||
import {addRoomMembership, removeRoomMembership} from "@app/commands"
|
import {addRoomMembership, removeRoomMembership} from "@app/commands"
|
||||||
|
|
||||||
const {nrelay, topic = ""} = $page.params
|
const {nrelay, room = GENERAL} = $page.params
|
||||||
const url = decodeNRelay(nrelay)
|
const url = decodeNRelay(nrelay)
|
||||||
const chat = deriveChat(makeChatId(url, topic))
|
const chat = deriveChat(makeChatId(url, room))
|
||||||
|
|
||||||
const assertEvent = (e: any) => e as TrustedEvent
|
const assertEvent = (e: any) => e as TrustedEvent
|
||||||
|
|
||||||
let loading = true
|
let loading = true
|
||||||
let elements: Element[] = []
|
let elements: Element[] = []
|
||||||
|
|
||||||
$: membership = $userMembership?.topicsByUrl.get(url) || []
|
$: membership = $userMembership?.roomsByUrl.get(url) || []
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
elements = []
|
elements = []
|
||||||
@@ -71,16 +71,16 @@
|
|||||||
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
|
class="flex min-h-12 items-center justify-between gap-4 rounded-xl bg-base-100 px-4 shadow-xl">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Icon icon="hashtag" />
|
<Icon icon="hashtag" />
|
||||||
<strong>{topic || "General"}</strong>
|
<strong>{room || "General"}</strong>
|
||||||
</div>
|
</div>
|
||||||
{#if topic}
|
{#if room}
|
||||||
{#if membership.includes(topic)}
|
{#if membership.includes(room)}
|
||||||
<Button class="btn btn-neutral btn-sm" on:click={() => removeRoomMembership(url, topic)}>
|
<Button class="btn btn-neutral btn-sm" on:click={() => removeRoomMembership(url, room)}>
|
||||||
<Icon icon="arrows-a-logout-2" />
|
<Icon icon="arrows-a-logout-2" />
|
||||||
Leave Room
|
Leave Room
|
||||||
</Button>
|
</Button>
|
||||||
{:else}
|
{:else}
|
||||||
<Button class="btn btn-neutral btn-sm" on:click={() => addRoomMembership(url, topic)}>
|
<Button class="btn btn-neutral btn-sm" on:click={() => addRoomMembership(url, room)}>
|
||||||
<Icon icon="login-2" />
|
<Icon icon="login-2" />
|
||||||
Join Room
|
Join Room
|
||||||
</Button>
|
</Button>
|
||||||
@@ -106,5 +106,5 @@
|
|||||||
</Spinner>
|
</Spinner>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ChatCompose {url} {topic} />
|
<ChatCompose {url} {room} />
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user