Name the group-space, platform-flag and active-space rules once, and keep makeEventPath to in-app paths
This commit is contained in:
parent
0e01055855
commit
602ac09293
18 changed files with 134 additions and 135 deletions
|
|
@ -185,7 +185,7 @@ One web build runs in several shells:
|
||||||
Platform checks call Capacitor directly. There is no wrapper module:
|
Platform checks call Capacitor directly. There is no wrapper module:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export const ENABLE_ZAPS = Capacitor.getPlatform() != "ios" // src/app/env.ts
|
export const ENABLE_ZAPS = Capacitor.getPlatform() !== "ios" // src/app/env.ts
|
||||||
export const HOSTING_ENABLED = Capacitor.getPlatform() !== "ios" // src/app/hosting.ts
|
export const HOSTING_ENABLED = Capacitor.getPlatform() !== "ios" // src/app/hosting.ts
|
||||||
|
|
||||||
if (!Capacitor.isPluginAvailable("Keyboard")) return noop // src/app/keyboard.ts
|
if (!Capacitor.isPluginAvailable("Keyboard")) return noop // src/app/keyboard.ts
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ carries `["h", h]`; content with no `h` belongs to the whole space. `/spaces/[re
|
||||||
`deriveUserRooms(url)` in `src/app/rooms.ts` read it. "Joined" in the UI means "in this list",
|
`deriveUserRooms(url)` in `src/app/rooms.ts` read it. "Joined" in the UI means "in this list",
|
||||||
which is separate from NIP-43 membership on the relay: `src/routes/spaces/[relay]/+layout.svelte`
|
which is separate from NIP-43 membership on the relay: `src/routes/spaces/[relay]/+layout.svelte`
|
||||||
prompts `SpaceJoin` for any URL not in `userSpaceUrls`. A deployment with `VITE_PLATFORM_RELAYS`
|
prompts `SpaceJoin` for any URL not in `userSpaceUrls`. A deployment with `VITE_PLATFORM_RELAYS`
|
||||||
uses `PLATFORM_RELAYS` in place of the list for sync and navigation.
|
uses `PLATFORM_RELAYS` in place of the list for sync and navigation: `activeSpaceUrls` in
|
||||||
|
`src/app/rooms.ts` is that choice, read by background sync, notifications and editor suggestions.
|
||||||
|
|
||||||
### NIP-11 relay info
|
### NIP-11 relay info
|
||||||
|
|
||||||
|
|
@ -40,7 +41,7 @@ domain `Relay`. These fields drive protocol decisions:
|
||||||
|
|
||||||
| Read | Decides |
|
| Read | Decides |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `hasNip(29)` | whether the space has rooms. Without it everything lives in the space chat: `makeSpaceEntryPath` (`src/app/routes.ts`), `shareEvent` (`src/app/share.ts`), room search, notification grouping, `SpaceMenuRooms` |
|
| `hasNip(29)` | whether the space has rooms, read through `isGroupSpace(url)` / `deriveIsGroupSpace(url)` in `src/app/relays.ts`. Without it everything lives in the space chat (`makeSpaceChatPath`): `shareEvent` (`src/app/share.ts`), room search, notification grouping, `SpaceMenuNavItems`, `SpaceMenuRooms` |
|
||||||
| `hasNip(70)` | whether space content is marked protected (below) |
|
| `hasNip(70)` | whether space content is marked protected (below) |
|
||||||
| `self` | the relay's own pubkey, the trust anchor for relay-signed state |
|
| `self` | the relay's own pubkey, the trust anchor for relay-signed state |
|
||||||
| `pubkey` | the space's owner, who writes space-wide content that has no other author (`deriveUserIsSpaceOwner`) |
|
| `pubkey` | the space's owner, who writes space-wide content that has no other author (`deriveUserIsSpaceOwner`) |
|
||||||
|
|
@ -104,9 +105,9 @@ user holds a method for — reports under `banevent`, join requests under `allow
|
||||||
- `deriveUserRoomMembershipStatus`: an admin is always `Granted`.
|
- `deriveUserRoomMembershipStatus`: an admin is always `Granted`.
|
||||||
- `addRoomMembers`: allows each non-member at the relay (NIP-86 `allowPubkey`) before publishing
|
- `addRoomMembers`: allows each non-member at the relay (NIP-86 `allowPubkey`) before publishing
|
||||||
9000, because a room member the relay won't serve can't read the room.
|
9000, because a room member the relay won't serve can't read the room.
|
||||||
- `deriveUserRooms`, `deriveOtherRooms`, `deriveOtherVoiceRooms`: rooms from the user's 10009
|
- `deriveUserRooms`, `deriveOtherRooms(url, type)`: rooms from the user's 10009 list and the rest
|
||||||
list and the rest of the space, limited to rooms the relay still advertises. `meta.hasLivekit()`
|
of the space by `RoomType`, limited to rooms the relay still advertises. `meta.hasLivekit()`
|
||||||
marks a voice room.
|
marks a voice room (`getRoomType`).
|
||||||
|
|
||||||
### Room flows
|
### Room flows
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,9 @@ mobile. Pages outside a space use `Page`, `PageBar` and `PageContent` themselves
|
||||||
`makeCalendarPath`, `makeGoalPath`, `makePollPath`, `makeLibraryPath`, `makeArticleCreatePath`).
|
`makeCalendarPath`, `makeGoalPath`, `makePollPath`, `makeLibraryPath`, `makeArticleCreatePath`).
|
||||||
- `makeContentPath(url, kind, idOrAddress)` maps a kind to its page. `makeEventPath` builds on it
|
- `makeContentPath(url, kind, idOrAddress)` maps a kind to its page. `makeEventPath` builds on it
|
||||||
for any event, covering DMs, room messages (`?at=`) and comments (through the parent's `K`,
|
for any event, covering DMs, room messages (`?at=`) and comments (through the parent's `K`,
|
||||||
`A` and `E` tags), and falls back to `entityLink` from `src/app/env.ts`, an external link.
|
`A` and `E` tags), and returns `undefined` for an event with no page in the app. A caller that
|
||||||
|
wants an external link then calls `entityLink` from `src/app/env.ts` itself, as
|
||||||
|
`makeEventPermalink` does.
|
||||||
- `goToEvent(event)` scrolls to the event if it is already rendered with a `data-event` attribute
|
- `goToEvent(event)` scrolls to the event if it is already rendered with a `data-event` attribute
|
||||||
and navigates otherwise; `makeEventPermalink` is the shareable form.
|
and navigates otherwise; `makeEventPermalink` is the shareable form.
|
||||||
- `goToSpace(url)` goes to `makeSpaceEntryPath(url)`: the last page visited in that space, which
|
- `goToSpace(url)` goes to `makeSpaceEntryPath(url)`: the last page visited in that space, which
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Capacitor} from "@capacitor/core"
|
|
||||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||||
import ServerPath from "@assets/icons/server-path.svg?dataurl"
|
import ServerPath from "@assets/icons/server-path.svg?dataurl"
|
||||||
import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl"
|
import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl"
|
||||||
|
|
@ -14,6 +13,8 @@
|
||||||
import Profile from "@app/components/Profile.svelte"
|
import Profile from "@app/components/Profile.svelte"
|
||||||
import LogOut from "@app/components/LogOut.svelte"
|
import LogOut from "@app/components/LogOut.svelte"
|
||||||
import {user} from "@app/core"
|
import {user} from "@app/core"
|
||||||
|
import {ENABLE_ZAPS} from "@app/env"
|
||||||
|
import {HOSTING_ENABLED} from "@app/hosting"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {makeProfilePath} from "@app/routes"
|
import {makeProfilePath} from "@app/routes"
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@
|
||||||
<Icon icon={Bell} size={5} />
|
<Icon icon={Bell} size={5} />
|
||||||
Alerts
|
Alerts
|
||||||
</Link>
|
</Link>
|
||||||
{#if Capacitor.getPlatform() !== "ios"}
|
{#if ENABLE_ZAPS}
|
||||||
<Link
|
<Link
|
||||||
href="/settings/wallet"
|
href="/settings/wallet"
|
||||||
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
||||||
|
|
@ -47,12 +48,14 @@
|
||||||
Wallet
|
Wallet
|
||||||
</Link>
|
</Link>
|
||||||
{/if}
|
{/if}
|
||||||
<Link
|
{#if HOSTING_ENABLED}
|
||||||
href="/settings/hosting"
|
<Link
|
||||||
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
href="/settings/hosting"
|
||||||
<Icon icon={ServerPath} size={5} />
|
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
||||||
Hosting
|
<Icon icon={ServerPath} size={5} />
|
||||||
</Link>
|
Hosting
|
||||||
|
</Link>
|
||||||
|
{/if}
|
||||||
<Link
|
<Link
|
||||||
href="/settings/content"
|
href="/settings/content"
|
||||||
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import {remove} from "@welshman/lib"
|
import {remove} from "@welshman/lib"
|
||||||
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
||||||
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
||||||
|
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
|
||||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||||
import {preventDefault} from "@lib/html"
|
import {preventDefault} from "@lib/html"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
|
@ -21,7 +22,7 @@
|
||||||
import {chatSearch} from "@app/chats"
|
import {chatSearch} from "@app/chats"
|
||||||
import {user} from "@app/core"
|
import {user} from "@app/core"
|
||||||
import {deriveUserRoomSearch} from "@app/rooms"
|
import {deriveUserRoomSearch} from "@app/rooms"
|
||||||
import {makeChatPath, makeRoomPath} from "@app/routes"
|
import {makeChatPath} from "@app/routes"
|
||||||
import {shareTo, type Share} from "@app/share"
|
import {shareTo, type Share} from "@app/share"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -65,8 +66,7 @@
|
||||||
{#if roomResults.length > 0}
|
{#if roomResults.length > 0}
|
||||||
<p class="text-xs uppercase tracking-wide opacity-60">Rooms</p>
|
<p class="text-xs uppercase tracking-wide opacity-60">Rooms</p>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each roomResults as { url, h } (makeRoomPath(url, h))}
|
{#each roomResults as { url, h, path } (path)}
|
||||||
{@const path = makeRoomPath(url, h)}
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
class={cx(
|
class={cx(
|
||||||
|
|
@ -74,7 +74,14 @@
|
||||||
selection === path ? "button-primary" : "button-neutral",
|
selection === path ? "button-primary" : "button-neutral",
|
||||||
)}
|
)}
|
||||||
onclick={() => toggle(path)}>
|
onclick={() => toggle(path)}>
|
||||||
<RoomNameWithImage {url} {h} />
|
{#if h}
|
||||||
|
<RoomNameWithImage {url} {h} />
|
||||||
|
{:else}
|
||||||
|
<div class="inline-flex min-w-0 items-center gap-2">
|
||||||
|
<Icon icon={Hashtag} size={5} />
|
||||||
|
<span class="truncate min-w-0">chat</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<RelayName {url} class="min-w-0 truncate text-xs opacity-60" />
|
<RelayName {url} class="min-w-0 truncate text-xs opacity-60" />
|
||||||
</Button>
|
</Button>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,11 @@
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
||||||
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
import SpaceSearch from "@app/components/SpaceSearch.svelte"
|
||||||
import {events, relays} from "@app/core"
|
import {events} from "@app/core"
|
||||||
import {ENABLE_ZAPS} from "@app/env"
|
import {ENABLE_ZAPS} from "@app/env"
|
||||||
import {CONTENT_KINDS} from "@app/content"
|
import {CONTENT_KINDS} from "@app/content"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {deriveIsGroupSpace} from "@app/relays"
|
||||||
|
import {makeSpaceChatPath, makeSpacePath} from "@app/routes"
|
||||||
import {allNotifications, notifications} from "@app/notifications"
|
import {allNotifications, notifications} from "@app/notifications"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
|
|
@ -30,8 +31,8 @@
|
||||||
|
|
||||||
const {url}: Props = $props()
|
const {url}: Props = $props()
|
||||||
|
|
||||||
const relay = $relays.one(url)
|
const isGroupSpace = deriveIsGroupSpace(url)
|
||||||
const chatPath = makeSpacePath(url, "chat")
|
const chatPath = makeSpaceChatPath(url)
|
||||||
const goalsPath = makeSpacePath(url, "goals")
|
const goalsPath = makeSpacePath(url, "goals")
|
||||||
const threadsPath = makeSpacePath(url, "threads")
|
const threadsPath = makeSpacePath(url, "threads")
|
||||||
const classifiedsPath = makeSpacePath(url, "classifieds")
|
const classifiedsPath = makeSpacePath(url, "classifieds")
|
||||||
|
|
@ -53,8 +54,6 @@
|
||||||
const showSection = (kind: number, path: string) =>
|
const showSection = (kind: number, path: string) =>
|
||||||
$spaceKinds.has(kind) || $allNotifications.has(path) || $page.url.pathname.startsWith(path)
|
$spaceKinds.has(kind) || $allNotifications.has(path) || $page.url.pathname.startsWith(path)
|
||||||
|
|
||||||
const hasNip29 = $derived($relay?.hasNip(29) ?? false)
|
|
||||||
|
|
||||||
const openSearch = () => pushModal(SpaceSearch, {url})
|
const openSearch = () => pushModal(SpaceSearch, {url})
|
||||||
|
|
||||||
$effect(() => setJson(spaceKindsKey, [...$spaceKinds]))
|
$effect(() => setJson(spaceKindsKey, [...$spaceKinds]))
|
||||||
|
|
@ -63,7 +62,7 @@
|
||||||
<SecondaryNavItem href={makeSpacePath(url, "about")}>
|
<SecondaryNavItem href={makeSpacePath(url, "about")}>
|
||||||
<Icon icon={Home} /> Space Details
|
<Icon icon={Home} /> Space Details
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{#if !hasNip29}
|
{#if !$isGroupSpace}
|
||||||
<SecondaryNavItem href={chatPath} notification={$notifications.has(chatPath)}>
|
<SecondaryNavItem href={chatPath} notification={$notifications.has(chatPath)}>
|
||||||
<Icon icon={ChatRound} /> Chat
|
<Icon icon={ChatRound} /> Chat
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
|
|
@ -106,7 +105,7 @@
|
||||||
<Icon icon={Revote} /> Polls
|
<Icon icon={Revote} /> Polls
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{/if}
|
{/if}
|
||||||
{#if hasNip29}
|
{#if $isGroupSpace}
|
||||||
<SecondaryNavItem onclick={openSearch}>
|
<SecondaryNavItem onclick={openSearch}>
|
||||||
<Icon icon={Magnifier} /> Search
|
<Icon icon={Magnifier} /> Search
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
import SecondaryNavHeader from "@lib/components/SecondaryNavHeader.svelte"
|
import SecondaryNavHeader from "@lib/components/SecondaryNavHeader.svelte"
|
||||||
import SpaceMenuRoomItem from "@app/components/SpaceMenuRoomItem.svelte"
|
import SpaceMenuRoomItem from "@app/components/SpaceMenuRoomItem.svelte"
|
||||||
import RoomCreate from "@app/components/RoomCreate.svelte"
|
import RoomCreate from "@app/components/RoomCreate.svelte"
|
||||||
import {relays} from "@app/core"
|
|
||||||
import {deriveUserCanCreateRoom} from "@app/management"
|
import {deriveUserCanCreateRoom} from "@app/management"
|
||||||
import {deriveUserRooms, deriveOtherRooms, deriveOtherVoiceRooms, displayRoom} from "@app/rooms"
|
import {deriveIsGroupSpace} from "@app/relays"
|
||||||
|
import {RoomType, deriveUserRooms, deriveOtherRooms, displayRoom} from "@app/rooms"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -20,10 +20,10 @@
|
||||||
|
|
||||||
const {url, mobile = false}: Props = $props()
|
const {url, mobile = false}: Props = $props()
|
||||||
|
|
||||||
const relay = $relays.one(url)
|
const isGroupSpace = deriveIsGroupSpace(url)
|
||||||
const userRooms = deriveUserRooms(url)
|
const userRooms = deriveUserRooms(url)
|
||||||
const otherRooms = deriveOtherRooms(url)
|
const otherRooms = deriveOtherRooms(url, RoomType.Text)
|
||||||
const otherVoiceRooms = deriveOtherVoiceRooms(url)
|
const otherVoiceRooms = deriveOtherRooms(url, RoomType.Voice)
|
||||||
const canCreateRoom = deriveUserCanCreateRoom(url)
|
const canCreateRoom = deriveUserCanCreateRoom(url)
|
||||||
|
|
||||||
const roomSearch = derived(otherRooms, $otherRooms =>
|
const roomSearch = derived(otherRooms, $otherRooms =>
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#if ($relay?.hasNip(29) ?? false) && ($userRooms.length > 0 || $otherRooms.length > 0 || $otherVoiceRooms.length > 0 || $canCreateRoom)}
|
{#if $isGroupSpace && ($userRooms.length > 0 || $otherRooms.length > 0 || $otherVoiceRooms.length > 0 || $canCreateRoom)}
|
||||||
{#if mobile}
|
{#if mobile}
|
||||||
<div class="card space-menu__card flex flex-col gap-1">
|
<div class="card space-menu__card flex flex-col gap-1">
|
||||||
{@render content()}
|
{@render content()}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,7 @@ import {RoomReferenceExtension} from "@app/editor/RoomReferenceExtension"
|
||||||
import RoomSuggestion from "@app/editor/RoomSuggestion.svelte"
|
import RoomSuggestion from "@app/editor/RoomSuggestion.svelte"
|
||||||
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
|
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
|
||||||
import {compressFileForUpload, makeImetaTag, uploadFile} from "@app/uploads"
|
import {compressFileForUpload, makeImetaTag, uploadFile} from "@app/uploads"
|
||||||
import {userSpaceUrls} from "@app/rooms"
|
import {activeSpaceUrls} from "@app/rooms"
|
||||||
import {PLATFORM_RELAYS} from "@app/env"
|
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
const isEmpty = (editor: Editor) => editor.getText({blockSeparator: "\n"}).trim() === ""
|
const isEmpty = (editor: Editor) => editor.getText({blockSeparator: "\n"}).trim() === ""
|
||||||
|
|
@ -79,13 +78,9 @@ export const makeEditor = async ({
|
||||||
)
|
)
|
||||||
|
|
||||||
const roomReferenceSearch = derived(
|
const roomReferenceSearch = derived(
|
||||||
[throttled(800, userSpaceUrls), throttled(800, rooms.get().byUrl.$)],
|
[throttled(800, activeSpaceUrls), throttled(800, rooms.get().byUrl.$)],
|
||||||
([$userSpaceUrls, $roomsByUrl]) => {
|
([$activeSpaceUrls, $roomsByUrl]) => {
|
||||||
// Platform relays, where configured, are the only spaces suggestions come from.
|
const spaceUrls = uniq(url ? [url, ...$activeSpaceUrls] : $activeSpaceUrls)
|
||||||
const spaceUrls =
|
|
||||||
PLATFORM_RELAYS.length > 0
|
|
||||||
? PLATFORM_RELAYS
|
|
||||||
: uniq(url ? [url, ...$userSpaceUrls] : $userSpaceUrls)
|
|
||||||
|
|
||||||
const options = spaceUrls.flatMap(spaceUrl =>
|
const options = spaceUrls.flatMap(spaceUrl =>
|
||||||
($roomsByUrl.get(spaceUrl) ?? []).map((room: Room) => ({
|
($roomsByUrl.get(spaceUrl) ?? []).map((room: Room) => ({
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const PUSH_SERVER = env("VITE_PUSH_SERVER")
|
||||||
|
|
||||||
export const PUSH_BRIDGE = normalizeRelayUrl(env("VITE_PUSH_BRIDGE"))
|
export const PUSH_BRIDGE = normalizeRelayUrl(env("VITE_PUSH_BRIDGE"))
|
||||||
|
|
||||||
export const ENABLE_ZAPS = Capacitor.getPlatform() != "ios"
|
export const ENABLE_ZAPS = Capacitor.getPlatform() !== "ios"
|
||||||
|
|
||||||
export const SIGNER_RELAYS = fromCsv(env("VITE_SIGNER_RELAYS")).map(normalizeRelayUrl)
|
export const SIGNER_RELAYS = fromCsv(env("VITE_SIGNER_RELAYS")).map(normalizeRelayUrl)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,15 @@ import {
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {Comment} from "@welshman/domain"
|
import {Comment} from "@welshman/domain"
|
||||||
import {synced, throttled, withGetter} from "@welshman/store"
|
import {synced, throttled, withGetter} from "@welshman/store"
|
||||||
import {Events, Relays, RoomLists} from "@welshman/app"
|
import {Events, Relays} from "@welshman/app"
|
||||||
import {app, fromApp, reader} from "@app/core"
|
import {app, fromApp, reader} from "@app/core"
|
||||||
import {makeRoomPath, makeSpaceChatPath, makeChatPath, makeContentPath} from "@app/routes"
|
import {makeRoomPath, makeSpaceChatPath, makeChatPath, makeContentPath} from "@app/routes"
|
||||||
import {CONTENT_KINDS, makeCommentFilter} from "@app/content"
|
import {CONTENT_KINDS, makeCommentFilter} from "@app/content"
|
||||||
import {getIsMuted, notificationSettings, userSettingsValues} from "@app/settings"
|
import {getIsMuted, notificationSettings, userSettingsValues} from "@app/settings"
|
||||||
import {Chats} from "@app/chats"
|
import {Chats} from "@app/chats"
|
||||||
import {dufflepud, DUFFLEPUD_URL, PLATFORM_RELAYS} from "@app/env"
|
import {dufflepud, DUFFLEPUD_URL} from "@app/env"
|
||||||
|
import {isGroupSpace} from "@app/relays"
|
||||||
|
import {activeSpaceUrls} from "@app/rooms"
|
||||||
import {kv} from "@app/storage"
|
import {kv} from "@app/storage"
|
||||||
|
|
||||||
// Checked state
|
// Checked state
|
||||||
|
|
@ -231,10 +233,10 @@ export const latestActivityByPath = derived(
|
||||||
1000,
|
1000,
|
||||||
derived(
|
derived(
|
||||||
[
|
[
|
||||||
app,
|
activeSpaceUrls,
|
||||||
fromApp($app => $app.use(Chats).index.$),
|
fromApp($app => $app.use(Chats).index.$),
|
||||||
|
// Only here to recompute once a relay's NIP-11 says whether it's a group space
|
||||||
fromApp($app => $app.use(Relays).index.$),
|
fromApp($app => $app.use(Relays).index.$),
|
||||||
fromApp($app => $app.use(RoomLists).index.$),
|
|
||||||
fromApp(
|
fromApp(
|
||||||
$app =>
|
$app =>
|
||||||
$app
|
$app
|
||||||
|
|
@ -247,7 +249,7 @@ export const latestActivityByPath = derived(
|
||||||
identity,
|
identity,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
([$app, $chatsById, $relays, $roomLists, eventsByIdByUrl, $settings]) => {
|
([$activeSpaceUrls, $chatsById, , eventsByIdByUrl, $settings]) => {
|
||||||
const activity = new Map<string, Activity>()
|
const activity = new Map<string, Activity>()
|
||||||
|
|
||||||
for (const {pubkeys, messages} of $chatsById.values()) {
|
for (const {pubkeys, messages} of $chatsById.values()) {
|
||||||
|
|
@ -256,13 +258,10 @@ export const latestActivityByPath = derived(
|
||||||
activity.set(path, {path, pubkeys, event: messages[0]})
|
activity.set(path, {path, pubkeys, event: messages[0]})
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomList = $app.user?.pubkey ? $roomLists.get($app.user.pubkey) : undefined
|
for (const url of $activeSpaceUrls) {
|
||||||
const urls = PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : (roomList?.urls() ?? [])
|
|
||||||
|
|
||||||
for (const url of urls) {
|
|
||||||
const events = sortEventsDesc((eventsByIdByUrl.get(url) || new Map()).values())
|
const events = sortEventsDesc((eventsByIdByUrl.get(url) || new Map()).values())
|
||||||
|
|
||||||
if ($relays.get(url)?.hasNip(29)) {
|
if (isGroupSpace(url)) {
|
||||||
for (const [h, [event]] of groupBy(e => tagValue(tagSpec("h"), e.tags), events)) {
|
for (const [h, [event]] of groupBy(e => tagValue(tagSpec("h"), e.tags), events)) {
|
||||||
// A muted room is left out entirely, so it can't light up its own badge or the space's
|
// A muted room is left out entirely, so it can't light up its own badge or the space's
|
||||||
if (h && !getIsMuted($settings, url, h)) {
|
if (h && !getIsMuted($settings, url, h)) {
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,7 @@ export const onPushNotificationAction = async (action: {
|
||||||
const target = event?.kind === WRAP ? await loadNotificationRumor(event) : event
|
const target = event?.kind === WRAP ? await loadNotificationRumor(event) : event
|
||||||
const path = target && makeEventPath(target, [relay])
|
const path = target && makeEventPath(target, [relay])
|
||||||
|
|
||||||
// Kinds we have no route for get a coracle.social permalink, which navigate refuses
|
if (path) {
|
||||||
if (path && !path.includes("://")) {
|
|
||||||
navigate(path)
|
navigate(path)
|
||||||
} else if (event?.kind === WRAP) {
|
} else if (event?.kind === WRAP) {
|
||||||
goToChat()
|
goToChat()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {call, on, simpleCache} from "@welshman/lib"
|
||||||
import {normalizeRelayUrl} from "@welshman/util"
|
import {normalizeRelayUrl} from "@welshman/util"
|
||||||
import {AuthStateEvent, AuthStatus, SocketEvent, SocketStatus} from "@welshman/net"
|
import {AuthStateEvent, AuthStatus, SocketEvent, SocketStatus} from "@welshman/net"
|
||||||
import {throttled} from "@welshman/store"
|
import {throttled} from "@welshman/store"
|
||||||
import {app} from "@app/core"
|
import {app, relays} from "@app/core"
|
||||||
import {relaysMostlyRestricted} from "@app/policies"
|
import {relaysMostlyRestricted} from "@app/policies"
|
||||||
import {colorFor} from "@app/theme"
|
import {colorFor} from "@app/theme"
|
||||||
|
|
||||||
|
|
@ -95,6 +95,12 @@ export const deriveSocketStatus = (url: string) =>
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A space that isn't a NIP-29 relay has no rooms, only one space-wide chat.
|
||||||
|
export const isGroupSpace = (url: string) => Boolean(relays.get().get(url)?.hasNip(29))
|
||||||
|
|
||||||
|
export const deriveIsGroupSpace = (url: string) =>
|
||||||
|
derived(relays.get().one(url), $relay => Boolean($relay?.hasNip(29)))
|
||||||
|
|
||||||
const livekitUrl = (url: string) =>
|
const livekitUrl = (url: string) =>
|
||||||
url.replace(/^ws/, "http").replace(/\/$/, "") + "/.well-known/nip29/livekit"
|
url.replace(/^ws/, "http").replace(/\/$/, "") + "/.well-known/nip29/livekit"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,10 @@ import {
|
||||||
user,
|
user,
|
||||||
writer,
|
writer,
|
||||||
} from "@app/core"
|
} from "@app/core"
|
||||||
|
import {PLATFORM_RELAYS} from "@app/env"
|
||||||
import {allowPubkeys, deriveSpacePermissions, deriveUserIsSpaceStaff} from "@app/management"
|
import {allowPubkeys, deriveSpacePermissions, deriveUserIsSpaceStaff} from "@app/management"
|
||||||
import {makeRoomPath} from "@app/routes"
|
import {isGroupSpace} from "@app/relays"
|
||||||
|
import {makeRoomPath, makeSpaceChatPath} from "@app/routes"
|
||||||
import {loadSendDelay} from "@app/settings"
|
import {loadSendDelay} from "@app/settings"
|
||||||
|
|
||||||
export const PROTECTED = ["-"]
|
export const PROTECTED = ["-"]
|
||||||
|
|
@ -180,6 +182,11 @@ export const userRoomList = deriveUserItem(RoomLists)
|
||||||
|
|
||||||
export const userSpaceUrls = derived(userRoomList, $userRoomList => $userRoomList?.urls() ?? [])
|
export const userSpaceUrls = derived(userRoomList, $userRoomList => $userRoomList?.urls() ?? [])
|
||||||
|
|
||||||
|
// Platform relays, where configured, stand in for the spaces the user has joined.
|
||||||
|
export const activeSpaceUrls = derived(userSpaceUrls, $userSpaceUrls =>
|
||||||
|
PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : $userSpaceUrls,
|
||||||
|
)
|
||||||
|
|
||||||
// Spaces get reordered from lists showing only some of them, so urls go back in the slots they replace.
|
// Spaces get reordered from lists showing only some of them, so urls go back in the slots they replace.
|
||||||
export const reorderSpaceUrls = (urls: string[]) => {
|
export const reorderSpaceUrls = (urls: string[]) => {
|
||||||
let index = 0
|
let index = 0
|
||||||
|
|
@ -200,19 +207,10 @@ export const deriveUserRooms = (url: string) =>
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export const deriveOtherRooms = (url: string) =>
|
export const deriveOtherRooms = (url: string, type: RoomType) =>
|
||||||
derived([deriveUserRooms(url), rooms.get().forUrl(url).$], ([$userRooms, $spaceRooms]) => {
|
derived([deriveUserRooms(url), rooms.get().forUrl(url).$], ([$userRooms, $spaceRooms]) => {
|
||||||
const result = $spaceRooms
|
const result = $spaceRooms
|
||||||
.filter(room => !room.meta?.hasLivekit() && !$userRooms.includes(room.h))
|
.filter(room => getRoomType(room) === type && !$userRooms.includes(room.h))
|
||||||
.map(room => room.h)
|
|
||||||
|
|
||||||
return sortBy(roomComparator(url), uniq(result))
|
|
||||||
})
|
|
||||||
|
|
||||||
export const deriveOtherVoiceRooms = (url: string) =>
|
|
||||||
derived([deriveUserRooms(url), rooms.get().forUrl(url).$], ([$userRooms, $spaceRooms]) => {
|
|
||||||
const result = $spaceRooms
|
|
||||||
.filter(room => room.meta?.hasLivekit() && !$userRooms.includes(room.h))
|
|
||||||
.map(room => room.h)
|
.map(room => room.h)
|
||||||
|
|
||||||
return sortBy(roomComparator(url), uniq(result))
|
return sortBy(roomComparator(url), uniq(result))
|
||||||
|
|
@ -310,23 +308,26 @@ export const deriveUserRoomSearch = () =>
|
||||||
derived(
|
derived(
|
||||||
[userSpaceUrls, userRoomList, rooms.get().byUrl.$],
|
[userSpaceUrls, userRoomList, rooms.get().byUrl.$],
|
||||||
([$userSpaceUrls, $userRoomList, $roomsByUrl]) => {
|
([$userSpaceUrls, $userRoomList, $roomsByUrl]) => {
|
||||||
const options = $userSpaceUrls.flatMap(url => {
|
type Option = {url: string; h?: string; path: string; name: string; isFavorite: boolean}
|
||||||
const favorites = new Set($userRoomList?.roomsForUrl(url) ?? [])
|
|
||||||
// Spaces that aren't NIP-29 relays keep all their messages in the space-wide chat
|
|
||||||
const roomIds = relays.get().get(url)?.hasNip(29)
|
|
||||||
? ($roomsByUrl.get(url) ?? []).map(room => room.h)
|
|
||||||
: ["chat"]
|
|
||||||
|
|
||||||
return roomIds.map(h => ({
|
const options = $userSpaceUrls.flatMap((url): Option[] => {
|
||||||
url,
|
if (isGroupSpace(url)) {
|
||||||
h,
|
const favorites = new Set($userRoomList?.roomsForUrl(url) ?? [])
|
||||||
name: displayRoom(url, h),
|
|
||||||
isFavorite: favorites.has(h),
|
return ($roomsByUrl.get(url) ?? []).map(({h}) => ({
|
||||||
}))
|
url,
|
||||||
|
h,
|
||||||
|
path: makeRoomPath(url, h),
|
||||||
|
name: displayRoom(url, h),
|
||||||
|
isFavorite: favorites.has(h),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
return [{url, path: makeSpaceChatPath(url), name: "chat", isFavorite: false}]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return createSearch(options, {
|
return createSearch(options, {
|
||||||
getValue: option => makeRoomPath(option.url, option.h),
|
getValue: option => option.path,
|
||||||
fuseOptions: {keys: ["name", "url"]},
|
fuseOptions: {keys: ["name", "url"]},
|
||||||
sortFn: ({item}) => (item.isFavorite ? 0 : 1),
|
sortFn: ({item}) => (item.isFavorite ? 0 : 1),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import {get, writable} from "svelte/store"
|
||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {identity, tryCatch} from "@welshman/lib"
|
import {identity, tryCatch} from "@welshman/lib"
|
||||||
|
import type {Maybe} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
CLASSIFIED,
|
CLASSIFIED,
|
||||||
|
|
@ -203,7 +204,7 @@ export const makeContentPath = (url: string, kind: number, idOrAddress?: string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeEventPath = (event: TrustedEvent, urls: string[]) => {
|
export const makeEventPath = (event: TrustedEvent, urls: string[]): Maybe<string> => {
|
||||||
if (DM_KINDS.includes(event.kind)) {
|
if (DM_KINDS.includes(event.kind)) {
|
||||||
return makeChatPath([event.pubkey, ...tagValues(hexTags("p"), event.tags)])
|
return makeChatPath([event.pubkey, ...tagValues(hexTags("p"), event.tags)])
|
||||||
}
|
}
|
||||||
|
|
@ -245,21 +246,18 @@ export const makeEventPath = (event: TrustedEvent, urls: string[]) => {
|
||||||
|
|
||||||
return `/notes/${nip19.neventEncode({id, author, kind, relays: urls.slice(0, 3)})}`
|
return `/notes/${nip19.neventEncode({id, author, kind, relays: urls.slice(0, 3)})}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return entityLink(nip19.neventEncode({id: event.id, relays: urls}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const makeEventPermalink = (event: TrustedEvent, url?: string) => {
|
export const makeEventPermalink = (event: TrustedEvent, url?: string) => {
|
||||||
const urls = url ? [url] : Array.from(app.get().tracker.getRelays(event.id))
|
const urls = url ? [url] : Array.from(app.get().tracker.getRelays(event.id))
|
||||||
const path = makeEventPath(event, urls)
|
const path = makeEventPath(event, urls)
|
||||||
|
|
||||||
if (path.includes("://")) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
const pointer = nip19.neventEncode({id: event.id, relays: urls})
|
const pointer = nip19.neventEncode({id: event.id, relays: urls})
|
||||||
|
|
||||||
return `${PLATFORM_URL}${path}${path.includes("?") ? "&" : "?"}event=${pointer}`
|
if (path) {
|
||||||
|
return `${PLATFORM_URL}${path}${path.includes("?") ? "&" : "?"}event=${pointer}`
|
||||||
|
} else {
|
||||||
|
return entityLink(pointer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPermalinkTarget = (url: URL) => {
|
export const getPermalinkTarget = (url: URL) => {
|
||||||
|
|
@ -299,11 +297,13 @@ export const goToEvent = (event: TrustedEvent, options: Record<string, any> = {}
|
||||||
|
|
||||||
const path = makeEventPath(event, urls)
|
const path = makeEventPath(event, urls)
|
||||||
|
|
||||||
if (path.includes("://")) {
|
if (path) {
|
||||||
window.open(path)
|
if (!scrollToEvent(event.id)) {
|
||||||
} else if (!scrollToEvent(event.id)) {
|
const replaceState = path.replace(/\?.*$/, "") === get(page).url.pathname
|
||||||
const replaceState = path.replace(/\?.*$/, "") === get(page).url.pathname
|
|
||||||
|
|
||||||
navigate(path, {replaceState, ...options})
|
navigate(path, {replaceState, ...options})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.open(entityLink(nip19.neventEncode({id: event.id, relays: urls})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ import type {PluginListenerHandle} from "@capacitor/core"
|
||||||
import {noop} from "@welshman/lib"
|
import {noop} from "@welshman/lib"
|
||||||
import type {Maybe} from "@welshman/lib"
|
import type {Maybe} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {app, relays} from "@app/core"
|
import {app} from "@app/core"
|
||||||
import {navigate, pushModal} from "@app/modal"
|
import {navigate, pushModal} from "@app/modal"
|
||||||
|
import {isGroupSpace} from "@app/relays"
|
||||||
import {makeSpaceChatPath} from "@app/routes"
|
import {makeSpaceChatPath} from "@app/routes"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import ShareDialog from "@app/components/Share.svelte"
|
import ShareDialog from "@app/components/Share.svelte"
|
||||||
|
|
@ -30,7 +31,7 @@ export const shareTo = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
export const shareEvent = (url: string, noun: string, event: TrustedEvent) => {
|
export const shareEvent = (url: string, noun: string, event: TrustedEvent) => {
|
||||||
if (relays.get().get(url)?.hasNip(29)) {
|
if (isGroupSpace(url)) {
|
||||||
pushModal(ShareEvent, {url, noun, event})
|
pushModal(ShareEvent, {url, noun, event})
|
||||||
} else {
|
} else {
|
||||||
shareTo(makeSpaceChatPath(url), {type: "event", value: event})
|
shareTo(makeSpaceChatPath(url), {type: "event", value: event})
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,9 @@ import {
|
||||||
unionFilters,
|
unionFilters,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {Filter} from "@welshman/util"
|
import type {Filter} from "@welshman/util"
|
||||||
import type {FollowListReader, RoomListReader} from "@welshman/domain"
|
import type {FollowListReader} from "@welshman/domain"
|
||||||
import {merged, synced, throttled, withGetter} from "@welshman/store"
|
import {merged, synced, throttled, withGetter} from "@welshman/store"
|
||||||
import {
|
import {FollowLists, MessagingRelayLists, RelayLists, SearchRelayLists, Sync} from "@welshman/app"
|
||||||
FollowLists,
|
|
||||||
MessagingRelayLists,
|
|
||||||
RelayLists,
|
|
||||||
RoomLists,
|
|
||||||
SearchRelayLists,
|
|
||||||
Sync,
|
|
||||||
} from "@welshman/app"
|
|
||||||
import {
|
import {
|
||||||
app,
|
app,
|
||||||
blockedRelayLists,
|
blockedRelayLists,
|
||||||
|
|
@ -59,9 +52,10 @@ import {
|
||||||
} from "@app/core"
|
} from "@app/core"
|
||||||
import {LIVEKIT_PARTICIPANTS} from "@app/call"
|
import {LIVEKIT_PARTICIPANTS} from "@app/call"
|
||||||
import {CONTENT_KINDS, REACTION_KINDS, makeCommentFilter} from "@app/content"
|
import {CONTENT_KINDS, REACTION_KINDS, makeCommentFilter} from "@app/content"
|
||||||
import {INDEXER_RELAYS, PLATFORM_RELAYS} from "@app/env"
|
import {INDEXER_RELAYS} from "@app/env"
|
||||||
import {FEATURED_CONTENT_D} from "@app/featured"
|
import {FEATURED_CONTENT_D} from "@app/featured"
|
||||||
import {decodeRelay} from "@app/relays"
|
import {decodeRelay} from "@app/relays"
|
||||||
|
import {activeSpaceUrls, userSpaceUrls} from "@app/rooms"
|
||||||
import {Settings} from "@app/settings"
|
import {Settings} from "@app/settings"
|
||||||
import {kv} from "@app/storage"
|
import {kv} from "@app/storage"
|
||||||
import {hasBlossomSupport} from "@app/uploads"
|
import {hasBlossomSupport} from "@app/uploads"
|
||||||
|
|
@ -95,8 +89,6 @@ const pullAndListen = ({url, signal, filters}: SyncOpts) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const userRoomList = deriveUserItem(RoomLists)
|
|
||||||
|
|
||||||
const userRelayList = deriveUserItem(RelayLists)
|
const userRelayList = deriveUserItem(RelayLists)
|
||||||
|
|
||||||
const userFollowList = deriveUserItem(FollowLists)
|
const userFollowList = deriveUserItem(FollowLists)
|
||||||
|
|
@ -105,9 +97,6 @@ const userMessagingRelayList = deriveUserItem(MessagingRelayLists)
|
||||||
|
|
||||||
const userSearchRelayList = deriveUserItem(SearchRelayLists)
|
const userSearchRelayList = deriveUserItem(SearchRelayLists)
|
||||||
|
|
||||||
const getSpaceUrls = ($roomList: Maybe<RoomListReader>) =>
|
|
||||||
PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : ($roomList?.urls() ?? [])
|
|
||||||
|
|
||||||
// Relays
|
// Relays
|
||||||
|
|
||||||
const syncRelays = () => {
|
const syncRelays = () => {
|
||||||
|
|
@ -124,8 +113,8 @@ const syncRelays = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const unsubscribeSpaceUrls = userRoomList.subscribe($roomList => {
|
const unsubscribeSpaceUrls = userSpaceUrls.subscribe($userSpaceUrls => {
|
||||||
for (const url of $roomList?.urls() ?? []) {
|
for (const url of $userSpaceUrls) {
|
||||||
relays.get().load(url)
|
relays.get().load(url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -169,21 +158,19 @@ const syncUserData = () => {
|
||||||
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
||||||
const $pubkey = app.get().user?.pubkey
|
const $pubkey = app.get().user?.pubkey
|
||||||
|
|
||||||
const syncRoomList = ($roomList: Maybe<RoomListReader>) => {
|
const syncSpaceMembership = ($activeSpaceUrls: string[]) => {
|
||||||
if ($roomList) {
|
const urls = new Set($activeSpaceUrls)
|
||||||
const urls = new Set(getSpaceUrls($roomList))
|
|
||||||
|
|
||||||
for (const url of urls) {
|
for (const url of urls) {
|
||||||
if (!unsubscribersByUrl.has(url)) {
|
if (!unsubscribersByUrl.has(url)) {
|
||||||
unsubscribersByUrl.set(url, syncUserSpaceMembership(url))
|
unsubscribersByUrl.set(url, syncUserSpaceMembership(url))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
|
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
|
||||||
if (!urls.has(url)) {
|
if (!urls.has(url)) {
|
||||||
unsubscribersByUrl.delete(url)
|
unsubscribersByUrl.delete(url)
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -218,13 +205,13 @@ const syncUserData = () => {
|
||||||
relayLists.get().load($pubkey)
|
relayLists.get().load($pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsubscribeRoomList = userRoomList.subscribe(syncRoomList)
|
const unsubscribeSpaceMembership = activeSpaceUrls.subscribe(syncSpaceMembership)
|
||||||
const unsubscribeRelayList = userRelayList.subscribe(syncUserLists)
|
const unsubscribeRelayList = userRelayList.subscribe(syncUserLists)
|
||||||
const unsubscribeFollowList = userFollowList.subscribe(syncFollowNetwork)
|
const unsubscribeFollowList = userFollowList.subscribe(syncFollowNetwork)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribersByUrl.forEach(call)
|
unsubscribersByUrl.forEach(call)
|
||||||
unsubscribeRoomList()
|
unsubscribeSpaceMembership()
|
||||||
unsubscribeRelayList()
|
unsubscribeRelayList()
|
||||||
unsubscribeFollowList()
|
unsubscribeFollowList()
|
||||||
}
|
}
|
||||||
|
|
@ -304,8 +291,8 @@ const syncSpaces = () => {
|
||||||
let detailUrl: Maybe<string>
|
let detailUrl: Maybe<string>
|
||||||
let unsubscribeDetail: Unsubscriber = noop
|
let unsubscribeDetail: Unsubscriber = noop
|
||||||
|
|
||||||
const unsubscribe = merged([userRoomList, page]).subscribe(([$roomList, $page]) => {
|
const unsubscribe = merged([activeSpaceUrls, page]).subscribe(([$activeSpaceUrls, $page]) => {
|
||||||
const urls = new Set(getSpaceUrls($roomList))
|
const urls = new Set($activeSpaceUrls)
|
||||||
const currentUrl = $page.params.relay ? decodeRelay($page.params.relay) : undefined
|
const currentUrl = $page.params.relay ? decodeRelay($page.params.relay) : undefined
|
||||||
|
|
||||||
if (currentUrl) {
|
if (currentUrl) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
import {Capacitor} from "@capacitor/core"
|
|
||||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||||
import Wallet from "@assets/icons/wallet.svg?dataurl"
|
import Wallet from "@assets/icons/wallet.svg?dataurl"
|
||||||
import ServerPath from "@assets/icons/server-path.svg?dataurl"
|
import ServerPath from "@assets/icons/server-path.svg?dataurl"
|
||||||
|
|
@ -15,6 +14,7 @@
|
||||||
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
||||||
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
|
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
|
||||||
import LogOut from "@app/components/LogOut.svelte"
|
import LogOut from "@app/components/LogOut.svelte"
|
||||||
|
import {ENABLE_ZAPS} from "@app/env"
|
||||||
import {HOSTING_ENABLED} from "@app/hosting"
|
import {HOSTING_ENABLED} from "@app/hosting"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
<SecondaryNavItem href="/settings/alerts">
|
<SecondaryNavItem href="/settings/alerts">
|
||||||
<Icon icon={Bell} /> Alerts
|
<Icon icon={Bell} /> Alerts
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
{#if Capacitor.getPlatform() !== "ios"}
|
{#if ENABLE_ZAPS}
|
||||||
<SecondaryNavItem href="/settings/wallet">
|
<SecondaryNavItem href="/settings/wallet">
|
||||||
<Icon icon={Wallet} /> Wallet
|
<Icon icon={Wallet} /> Wallet
|
||||||
</SecondaryNavItem>
|
</SecondaryNavItem>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {Capacitor} from "@capacitor/core"
|
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
|
@ -10,7 +9,7 @@
|
||||||
import HeadphonesRound from "@assets/icons/headphones-round.svg?dataurl"
|
import HeadphonesRound from "@assets/icons/headphones-round.svg?dataurl"
|
||||||
import Zap from "@app/components/Zap.svelte"
|
import Zap from "@app/components/Zap.svelte"
|
||||||
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
import ProfileDetail from "@app/components/ProfileDetail.svelte"
|
||||||
import {PLATFORM_NAME, PLATFORM_ABOUT} from "@app/env"
|
import {ENABLE_ZAPS, PLATFORM_NAME, PLATFORM_ABOUT} from "@app/env"
|
||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {makeSpacePath} from "@app/routes"
|
import {makeSpacePath} from "@app/routes"
|
||||||
|
|
||||||
|
|
@ -27,7 +26,7 @@
|
||||||
<p class="text-center text-2xl">Thanks for using</p>
|
<p class="text-center text-2xl">Thanks for using</p>
|
||||||
<h1 class="mb-4 text-center text-5xl font-bold uppercase">{PLATFORM_NAME}</h1>
|
<h1 class="mb-4 text-center text-5xl font-bold uppercase">{PLATFORM_NAME}</h1>
|
||||||
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
<div class="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||||
{#if Capacitor.getPlatform() !== "ios"}
|
{#if ENABLE_ZAPS}
|
||||||
<Card class="flex flex-col gap-2 text-center">
|
<Card class="flex flex-col gap-2 text-center">
|
||||||
<h3 class="text-2xl sm:h-12">Donate</h3>
|
<h3 class="text-2xl sm:h-12">Donate</h3>
|
||||||
<p class="sm:h-16">Funds will be used to support development.</p>
|
<p class="sm:h-16">Funds will be used to support development.</p>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue