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:
|
||||
|
||||
```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
|
||||
|
||||
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",
|
||||
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`
|
||||
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
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ domain `Relay`. These fields drive protocol decisions:
|
|||
|
||||
| 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) |
|
||||
| `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`) |
|
||||
|
|
@ -104,9 +105,9 @@ user holds a method for — reports under `banevent`, join requests under `allow
|
|||
- `deriveUserRoomMembershipStatus`: an admin is always `Granted`.
|
||||
- `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.
|
||||
- `deriveUserRooms`, `deriveOtherRooms`, `deriveOtherVoiceRooms`: rooms from the user's 10009
|
||||
list and the rest of the space, limited to rooms the relay still advertises. `meta.hasLivekit()`
|
||||
marks a voice room.
|
||||
- `deriveUserRooms`, `deriveOtherRooms(url, type)`: rooms from the user's 10009 list and the rest
|
||||
of the space by `RoomType`, limited to rooms the relay still advertises. `meta.hasLivekit()`
|
||||
marks a voice room (`getRoomType`).
|
||||
|
||||
### Room flows
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ mobile. Pages outside a space use `Page`, `PageBar` and `PageContent` themselves
|
|||
`makeCalendarPath`, `makeGoalPath`, `makePollPath`, `makeLibraryPath`, `makeArticleCreatePath`).
|
||||
- `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`,
|
||||
`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
|
||||
and navigates otherwise; `makeEventPermalink` is the shareable form.
|
||||
- `goToSpace(url)` goes to `makeSpaceEntryPath(url)`: the last page visited in that space, which
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||
import ServerPath from "@assets/icons/server-path.svg?dataurl"
|
||||
import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl"
|
||||
|
|
@ -14,6 +13,8 @@
|
|||
import Profile from "@app/components/Profile.svelte"
|
||||
import LogOut from "@app/components/LogOut.svelte"
|
||||
import {user} from "@app/core"
|
||||
import {ENABLE_ZAPS} from "@app/env"
|
||||
import {HOSTING_ENABLED} from "@app/hosting"
|
||||
import {pushModal} from "@app/modal"
|
||||
import {makeProfilePath} from "@app/routes"
|
||||
|
||||
|
|
@ -39,7 +40,7 @@
|
|||
<Icon icon={Bell} size={5} />
|
||||
Alerts
|
||||
</Link>
|
||||
{#if Capacitor.getPlatform() !== "ios"}
|
||||
{#if ENABLE_ZAPS}
|
||||
<Link
|
||||
href="/settings/wallet"
|
||||
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
||||
|
|
@ -47,12 +48,14 @@
|
|||
Wallet
|
||||
</Link>
|
||||
{/if}
|
||||
<Link
|
||||
href="/settings/hosting"
|
||||
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
||||
<Icon icon={ServerPath} size={5} />
|
||||
Hosting
|
||||
</Link>
|
||||
{#if HOSTING_ENABLED}
|
||||
<Link
|
||||
href="/settings/hosting"
|
||||
class="aspect-square button button-neutral h-[unset] flex flex-col gap-2 text-center">
|
||||
<Icon icon={ServerPath} size={5} />
|
||||
Hosting
|
||||
</Link>
|
||||
{/if}
|
||||
<Link
|
||||
href="/settings/content"
|
||||
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 AltArrowLeft from "@assets/icons/alt-arrow-left.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 {preventDefault} from "@lib/html"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
import {chatSearch} from "@app/chats"
|
||||
import {user} from "@app/core"
|
||||
import {deriveUserRoomSearch} from "@app/rooms"
|
||||
import {makeChatPath, makeRoomPath} from "@app/routes"
|
||||
import {makeChatPath} from "@app/routes"
|
||||
import {shareTo, type Share} from "@app/share"
|
||||
|
||||
type Props = {
|
||||
|
|
@ -65,8 +66,7 @@
|
|||
{#if roomResults.length > 0}
|
||||
<p class="text-xs uppercase tracking-wide opacity-60">Rooms</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each roomResults as { url, h } (makeRoomPath(url, h))}
|
||||
{@const path = makeRoomPath(url, h)}
|
||||
{#each roomResults as { url, h, path } (path)}
|
||||
<Button
|
||||
type="button"
|
||||
class={cx(
|
||||
|
|
@ -74,7 +74,14 @@
|
|||
selection === path ? "button-primary" : "button-neutral",
|
||||
)}
|
||||
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" />
|
||||
</Button>
|
||||
{/each}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,11 @@
|
|||
import Icon from "@lib/components/Icon.svelte"
|
||||
import SecondaryNavItem from "@lib/components/SecondaryNavItem.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 {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 {pushModal} from "@app/modal"
|
||||
|
||||
|
|
@ -30,8 +31,8 @@
|
|||
|
||||
const {url}: Props = $props()
|
||||
|
||||
const relay = $relays.one(url)
|
||||
const chatPath = makeSpacePath(url, "chat")
|
||||
const isGroupSpace = deriveIsGroupSpace(url)
|
||||
const chatPath = makeSpaceChatPath(url)
|
||||
const goalsPath = makeSpacePath(url, "goals")
|
||||
const threadsPath = makeSpacePath(url, "threads")
|
||||
const classifiedsPath = makeSpacePath(url, "classifieds")
|
||||
|
|
@ -53,8 +54,6 @@
|
|||
const showSection = (kind: number, path: string) =>
|
||||
$spaceKinds.has(kind) || $allNotifications.has(path) || $page.url.pathname.startsWith(path)
|
||||
|
||||
const hasNip29 = $derived($relay?.hasNip(29) ?? false)
|
||||
|
||||
const openSearch = () => pushModal(SpaceSearch, {url})
|
||||
|
||||
$effect(() => setJson(spaceKindsKey, [...$spaceKinds]))
|
||||
|
|
@ -63,7 +62,7 @@
|
|||
<SecondaryNavItem href={makeSpacePath(url, "about")}>
|
||||
<Icon icon={Home} /> Space Details
|
||||
</SecondaryNavItem>
|
||||
{#if !hasNip29}
|
||||
{#if !$isGroupSpace}
|
||||
<SecondaryNavItem href={chatPath} notification={$notifications.has(chatPath)}>
|
||||
<Icon icon={ChatRound} /> Chat
|
||||
</SecondaryNavItem>
|
||||
|
|
@ -106,7 +105,7 @@
|
|||
<Icon icon={Revote} /> Polls
|
||||
</SecondaryNavItem>
|
||||
{/if}
|
||||
{#if hasNip29}
|
||||
{#if $isGroupSpace}
|
||||
<SecondaryNavItem onclick={openSearch}>
|
||||
<Icon icon={Magnifier} /> Search
|
||||
</SecondaryNavItem>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
import SecondaryNavHeader from "@lib/components/SecondaryNavHeader.svelte"
|
||||
import SpaceMenuRoomItem from "@app/components/SpaceMenuRoomItem.svelte"
|
||||
import RoomCreate from "@app/components/RoomCreate.svelte"
|
||||
import {relays} from "@app/core"
|
||||
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"
|
||||
|
||||
type Props = {
|
||||
|
|
@ -20,10 +20,10 @@
|
|||
|
||||
const {url, mobile = false}: Props = $props()
|
||||
|
||||
const relay = $relays.one(url)
|
||||
const isGroupSpace = deriveIsGroupSpace(url)
|
||||
const userRooms = deriveUserRooms(url)
|
||||
const otherRooms = deriveOtherRooms(url)
|
||||
const otherVoiceRooms = deriveOtherVoiceRooms(url)
|
||||
const otherRooms = deriveOtherRooms(url, RoomType.Text)
|
||||
const otherVoiceRooms = deriveOtherRooms(url, RoomType.Voice)
|
||||
const canCreateRoom = deriveUserCanCreateRoom(url)
|
||||
|
||||
const roomSearch = derived(otherRooms, $otherRooms =>
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
{/if}
|
||||
{/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}
|
||||
<div class="card space-menu__card flex flex-col gap-1">
|
||||
{@render content()}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ import {RoomReferenceExtension} from "@app/editor/RoomReferenceExtension"
|
|||
import RoomSuggestion from "@app/editor/RoomSuggestion.svelte"
|
||||
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
|
||||
import {compressFileForUpload, makeImetaTag, uploadFile} from "@app/uploads"
|
||||
import {userSpaceUrls} from "@app/rooms"
|
||||
import {PLATFORM_RELAYS} from "@app/env"
|
||||
import {activeSpaceUrls} from "@app/rooms"
|
||||
import {pushToast} from "@app/toast"
|
||||
|
||||
const isEmpty = (editor: Editor) => editor.getText({blockSeparator: "\n"}).trim() === ""
|
||||
|
|
@ -79,13 +78,9 @@ export const makeEditor = async ({
|
|||
)
|
||||
|
||||
const roomReferenceSearch = derived(
|
||||
[throttled(800, userSpaceUrls), throttled(800, rooms.get().byUrl.$)],
|
||||
([$userSpaceUrls, $roomsByUrl]) => {
|
||||
// Platform relays, where configured, are the only spaces suggestions come from.
|
||||
const spaceUrls =
|
||||
PLATFORM_RELAYS.length > 0
|
||||
? PLATFORM_RELAYS
|
||||
: uniq(url ? [url, ...$userSpaceUrls] : $userSpaceUrls)
|
||||
[throttled(800, activeSpaceUrls), throttled(800, rooms.get().byUrl.$)],
|
||||
([$activeSpaceUrls, $roomsByUrl]) => {
|
||||
const spaceUrls = uniq(url ? [url, ...$activeSpaceUrls] : $activeSpaceUrls)
|
||||
|
||||
const options = spaceUrls.flatMap(spaceUrl =>
|
||||
($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 ENABLE_ZAPS = Capacitor.getPlatform() != "ios"
|
||||
export const ENABLE_ZAPS = Capacitor.getPlatform() !== "ios"
|
||||
|
||||
export const SIGNER_RELAYS = fromCsv(env("VITE_SIGNER_RELAYS")).map(normalizeRelayUrl)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,13 +15,15 @@ import {
|
|||
} from "@welshman/util"
|
||||
import {Comment} from "@welshman/domain"
|
||||
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 {makeRoomPath, makeSpaceChatPath, makeChatPath, makeContentPath} from "@app/routes"
|
||||
import {CONTENT_KINDS, makeCommentFilter} from "@app/content"
|
||||
import {getIsMuted, notificationSettings, userSettingsValues} from "@app/settings"
|
||||
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"
|
||||
|
||||
// Checked state
|
||||
|
|
@ -231,10 +233,10 @@ export const latestActivityByPath = derived(
|
|||
1000,
|
||||
derived(
|
||||
[
|
||||
app,
|
||||
activeSpaceUrls,
|
||||
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(RoomLists).index.$),
|
||||
fromApp(
|
||||
$app =>
|
||||
$app
|
||||
|
|
@ -247,7 +249,7 @@ export const latestActivityByPath = derived(
|
|||
identity,
|
||||
),
|
||||
),
|
||||
([$app, $chatsById, $relays, $roomLists, eventsByIdByUrl, $settings]) => {
|
||||
([$activeSpaceUrls, $chatsById, , eventsByIdByUrl, $settings]) => {
|
||||
const activity = new Map<string, Activity>()
|
||||
|
||||
for (const {pubkeys, messages} of $chatsById.values()) {
|
||||
|
|
@ -256,13 +258,10 @@ export const latestActivityByPath = derived(
|
|||
activity.set(path, {path, pubkeys, event: messages[0]})
|
||||
}
|
||||
|
||||
const roomList = $app.user?.pubkey ? $roomLists.get($app.user.pubkey) : undefined
|
||||
const urls = PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : (roomList?.urls() ?? [])
|
||||
|
||||
for (const url of urls) {
|
||||
for (const url of $activeSpaceUrls) {
|
||||
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)) {
|
||||
// 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)) {
|
||||
|
|
|
|||
|
|
@ -137,8 +137,7 @@ export const onPushNotificationAction = async (action: {
|
|||
const target = event?.kind === WRAP ? await loadNotificationRumor(event) : event
|
||||
const path = target && makeEventPath(target, [relay])
|
||||
|
||||
// Kinds we have no route for get a coracle.social permalink, which navigate refuses
|
||||
if (path && !path.includes("://")) {
|
||||
if (path) {
|
||||
navigate(path)
|
||||
} else if (event?.kind === WRAP) {
|
||||
goToChat()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {call, on, simpleCache} from "@welshman/lib"
|
|||
import {normalizeRelayUrl} from "@welshman/util"
|
||||
import {AuthStateEvent, AuthStatus, SocketEvent, SocketStatus} from "@welshman/net"
|
||||
import {throttled} from "@welshman/store"
|
||||
import {app} from "@app/core"
|
||||
import {app, relays} from "@app/core"
|
||||
import {relaysMostlyRestricted} from "@app/policies"
|
||||
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) =>
|
||||
url.replace(/^ws/, "http").replace(/\/$/, "") + "/.well-known/nip29/livekit"
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,10 @@ import {
|
|||
user,
|
||||
writer,
|
||||
} from "@app/core"
|
||||
import {PLATFORM_RELAYS} from "@app/env"
|
||||
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"
|
||||
|
||||
export const PROTECTED = ["-"]
|
||||
|
|
@ -180,6 +182,11 @@ export const userRoomList = deriveUserItem(RoomLists)
|
|||
|
||||
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.
|
||||
export const reorderSpaceUrls = (urls: string[]) => {
|
||||
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]) => {
|
||||
const result = $spaceRooms
|
||||
.filter(room => !room.meta?.hasLivekit() && !$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))
|
||||
.filter(room => getRoomType(room) === type && !$userRooms.includes(room.h))
|
||||
.map(room => room.h)
|
||||
|
||||
return sortBy(roomComparator(url), uniq(result))
|
||||
|
|
@ -310,23 +308,26 @@ export const deriveUserRoomSearch = () =>
|
|||
derived(
|
||||
[userSpaceUrls, userRoomList, rooms.get().byUrl.$],
|
||||
([$userSpaceUrls, $userRoomList, $roomsByUrl]) => {
|
||||
const options = $userSpaceUrls.flatMap(url => {
|
||||
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"]
|
||||
type Option = {url: string; h?: string; path: string; name: string; isFavorite: boolean}
|
||||
|
||||
return roomIds.map(h => ({
|
||||
url,
|
||||
h,
|
||||
name: displayRoom(url, h),
|
||||
isFavorite: favorites.has(h),
|
||||
}))
|
||||
const options = $userSpaceUrls.flatMap((url): Option[] => {
|
||||
if (isGroupSpace(url)) {
|
||||
const favorites = new Set($userRoomList?.roomsForUrl(url) ?? [])
|
||||
|
||||
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, {
|
||||
getValue: option => makeRoomPath(option.url, option.h),
|
||||
getValue: option => option.path,
|
||||
fuseOptions: {keys: ["name", "url"]},
|
||||
sortFn: ({item}) => (item.isFavorite ? 0 : 1),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {get, writable} from "svelte/store"
|
|||
import * as nip19 from "nostr-tools/nip19"
|
||||
import {page} from "$app/stores"
|
||||
import {identity, tryCatch} from "@welshman/lib"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {
|
||||
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)) {
|
||||
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 entityLink(nip19.neventEncode({id: event.id, relays: urls}))
|
||||
}
|
||||
|
||||
export const makeEventPermalink = (event: TrustedEvent, url?: string) => {
|
||||
const urls = url ? [url] : Array.from(app.get().tracker.getRelays(event.id))
|
||||
const path = makeEventPath(event, urls)
|
||||
|
||||
if (path.includes("://")) {
|
||||
return path
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
|
@ -299,11 +297,13 @@ export const goToEvent = (event: TrustedEvent, options: Record<string, any> = {}
|
|||
|
||||
const path = makeEventPath(event, urls)
|
||||
|
||||
if (path.includes("://")) {
|
||||
window.open(path)
|
||||
} else if (!scrollToEvent(event.id)) {
|
||||
const replaceState = path.replace(/\?.*$/, "") === get(page).url.pathname
|
||||
if (path) {
|
||||
if (!scrollToEvent(event.id)) {
|
||||
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 type {Maybe} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {app, relays} from "@app/core"
|
||||
import {app} from "@app/core"
|
||||
import {navigate, pushModal} from "@app/modal"
|
||||
import {isGroupSpace} from "@app/relays"
|
||||
import {makeSpaceChatPath} from "@app/routes"
|
||||
import {pushToast} from "@app/toast"
|
||||
import ShareDialog from "@app/components/Share.svelte"
|
||||
|
|
@ -30,7 +31,7 @@ export const shareTo = async (
|
|||
}
|
||||
|
||||
export const shareEvent = (url: string, noun: string, event: TrustedEvent) => {
|
||||
if (relays.get().get(url)?.hasNip(29)) {
|
||||
if (isGroupSpace(url)) {
|
||||
pushModal(ShareEvent, {url, noun, event})
|
||||
} else {
|
||||
shareTo(makeSpaceChatPath(url), {type: "event", value: event})
|
||||
|
|
|
|||
|
|
@ -30,16 +30,9 @@ import {
|
|||
unionFilters,
|
||||
} 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 {
|
||||
FollowLists,
|
||||
MessagingRelayLists,
|
||||
RelayLists,
|
||||
RoomLists,
|
||||
SearchRelayLists,
|
||||
Sync,
|
||||
} from "@welshman/app"
|
||||
import {FollowLists, MessagingRelayLists, RelayLists, SearchRelayLists, Sync} from "@welshman/app"
|
||||
import {
|
||||
app,
|
||||
blockedRelayLists,
|
||||
|
|
@ -59,9 +52,10 @@ import {
|
|||
} from "@app/core"
|
||||
import {LIVEKIT_PARTICIPANTS} from "@app/call"
|
||||
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 {decodeRelay} from "@app/relays"
|
||||
import {activeSpaceUrls, userSpaceUrls} from "@app/rooms"
|
||||
import {Settings} from "@app/settings"
|
||||
import {kv} from "@app/storage"
|
||||
import {hasBlossomSupport} from "@app/uploads"
|
||||
|
|
@ -95,8 +89,6 @@ const pullAndListen = ({url, signal, filters}: SyncOpts) => {
|
|||
})
|
||||
}
|
||||
|
||||
const userRoomList = deriveUserItem(RoomLists)
|
||||
|
||||
const userRelayList = deriveUserItem(RelayLists)
|
||||
|
||||
const userFollowList = deriveUserItem(FollowLists)
|
||||
|
|
@ -105,9 +97,6 @@ const userMessagingRelayList = deriveUserItem(MessagingRelayLists)
|
|||
|
||||
const userSearchRelayList = deriveUserItem(SearchRelayLists)
|
||||
|
||||
const getSpaceUrls = ($roomList: Maybe<RoomListReader>) =>
|
||||
PLATFORM_RELAYS.length > 0 ? PLATFORM_RELAYS : ($roomList?.urls() ?? [])
|
||||
|
||||
// Relays
|
||||
|
||||
const syncRelays = () => {
|
||||
|
|
@ -124,8 +113,8 @@ const syncRelays = () => {
|
|||
}
|
||||
})
|
||||
|
||||
const unsubscribeSpaceUrls = userRoomList.subscribe($roomList => {
|
||||
for (const url of $roomList?.urls() ?? []) {
|
||||
const unsubscribeSpaceUrls = userSpaceUrls.subscribe($userSpaceUrls => {
|
||||
for (const url of $userSpaceUrls) {
|
||||
relays.get().load(url)
|
||||
}
|
||||
})
|
||||
|
|
@ -169,21 +158,19 @@ const syncUserData = () => {
|
|||
const unsubscribersByUrl = new Map<string, Unsubscriber>()
|
||||
const $pubkey = app.get().user?.pubkey
|
||||
|
||||
const syncRoomList = ($roomList: Maybe<RoomListReader>) => {
|
||||
if ($roomList) {
|
||||
const urls = new Set(getSpaceUrls($roomList))
|
||||
const syncSpaceMembership = ($activeSpaceUrls: string[]) => {
|
||||
const urls = new Set($activeSpaceUrls)
|
||||
|
||||
for (const url of urls) {
|
||||
if (!unsubscribersByUrl.has(url)) {
|
||||
unsubscribersByUrl.set(url, syncUserSpaceMembership(url))
|
||||
}
|
||||
for (const url of urls) {
|
||||
if (!unsubscribersByUrl.has(url)) {
|
||||
unsubscribersByUrl.set(url, syncUserSpaceMembership(url))
|
||||
}
|
||||
}
|
||||
|
||||
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
|
||||
if (!urls.has(url)) {
|
||||
unsubscribersByUrl.delete(url)
|
||||
unsubscribe()
|
||||
}
|
||||
for (const [url, unsubscribe] of unsubscribersByUrl.entries()) {
|
||||
if (!urls.has(url)) {
|
||||
unsubscribersByUrl.delete(url)
|
||||
unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -218,13 +205,13 @@ const syncUserData = () => {
|
|||
relayLists.get().load($pubkey)
|
||||
}
|
||||
|
||||
const unsubscribeRoomList = userRoomList.subscribe(syncRoomList)
|
||||
const unsubscribeSpaceMembership = activeSpaceUrls.subscribe(syncSpaceMembership)
|
||||
const unsubscribeRelayList = userRelayList.subscribe(syncUserLists)
|
||||
const unsubscribeFollowList = userFollowList.subscribe(syncFollowNetwork)
|
||||
|
||||
return () => {
|
||||
unsubscribersByUrl.forEach(call)
|
||||
unsubscribeRoomList()
|
||||
unsubscribeSpaceMembership()
|
||||
unsubscribeRelayList()
|
||||
unsubscribeFollowList()
|
||||
}
|
||||
|
|
@ -304,8 +291,8 @@ const syncSpaces = () => {
|
|||
let detailUrl: Maybe<string>
|
||||
let unsubscribeDetail: Unsubscriber = noop
|
||||
|
||||
const unsubscribe = merged([userRoomList, page]).subscribe(([$roomList, $page]) => {
|
||||
const urls = new Set(getSpaceUrls($roomList))
|
||||
const unsubscribe = merged([activeSpaceUrls, page]).subscribe(([$activeSpaceUrls, $page]) => {
|
||||
const urls = new Set($activeSpaceUrls)
|
||||
const currentUrl = $page.params.relay ? decodeRelay($page.params.relay) : undefined
|
||||
|
||||
if (currentUrl) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type {Snippet} from "svelte"
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
|
||||
import Wallet from "@assets/icons/wallet.svg?dataurl"
|
||||
import ServerPath from "@assets/icons/server-path.svg?dataurl"
|
||||
|
|
@ -15,6 +14,7 @@
|
|||
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
||||
import SecondaryNavSection from "@lib/components/SecondaryNavSection.svelte"
|
||||
import LogOut from "@app/components/LogOut.svelte"
|
||||
import {ENABLE_ZAPS} from "@app/env"
|
||||
import {HOSTING_ENABLED} from "@app/hosting"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
|
|
@ -38,7 +38,7 @@
|
|||
<SecondaryNavItem href="/settings/alerts">
|
||||
<Icon icon={Bell} /> Alerts
|
||||
</SecondaryNavItem>
|
||||
{#if Capacitor.getPlatform() !== "ios"}
|
||||
{#if ENABLE_ZAPS}
|
||||
<SecondaryNavItem href="/settings/wallet">
|
||||
<Icon icon={Wallet} /> Wallet
|
||||
</SecondaryNavItem>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
import {Capacitor} from "@capacitor/core"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
|
|
@ -10,7 +9,7 @@
|
|||
import HeadphonesRound from "@assets/icons/headphones-round.svg?dataurl"
|
||||
import Zap from "@app/components/Zap.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 {makeSpacePath} from "@app/routes"
|
||||
|
||||
|
|
@ -27,7 +26,7 @@
|
|||
<p class="text-center text-2xl">Thanks for using</p>
|
||||
<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">
|
||||
{#if Capacitor.getPlatform() !== "ios"}
|
||||
{#if ENABLE_ZAPS}
|
||||
<Card class="flex flex-col gap-2 text-center">
|
||||
<h3 class="text-2xl sm:h-12">Donate</h3>
|
||||
<p class="sm:h-16">Funds will be used to support development.</p>
|
||||
|
|
|
|||
Loading…
Reference in a new issue