flotilla/src/app/routes.ts

222 lines
6 KiB
TypeScript
Raw Normal View History

import theme from "tailwindcss/defaultTheme"
import {get} from "svelte/store"
2025-06-05 20:37:50 +00:00
import * as nip19 from "nostr-tools/nip19"
2025-06-05 17:55:16 +00:00
import {goto} from "$app/navigation"
2026-02-16 23:28:25 +00:00
import {page} from "$app/stores"
2026-02-16 22:45:54 +00:00
import {nthEq} from "@welshman/lib"
2025-06-05 17:55:16 +00:00
import type {TrustedEvent} from "@welshman/util"
2026-02-04 01:09:30 +00:00
import {getAddress} from "@welshman/util"
2026-03-23 21:21:04 +00:00
import {tracker, userMessagingRelayList} from "@welshman/app"
2025-02-06 17:29:30 +00:00
import {identity} from "@welshman/lib"
2025-06-05 17:55:16 +00:00
import {
getTagValue,
MESSAGE,
THREAD,
2026-02-03 19:00:13 +00:00
CLASSIFIED,
2025-07-07 22:28:36 +00:00
ZAP_GOAL,
2025-06-05 17:55:16 +00:00
EVENT_TIME,
2026-04-16 18:30:59 +00:00
POLL,
getPubkeyTagValues,
2026-03-23 21:21:04 +00:00
getRelaysFromList,
2025-06-05 17:55:16 +00:00
} from "@welshman/util"
2026-03-23 21:21:04 +00:00
import {makeChatId, entityLink, encodeRelay, DM_KINDS, ROOM} from "@app/core/state"
2026-06-08 21:14:40 +00:00
import {pushModal} from "@app/modal"
2026-03-23 21:21:04 +00:00
import ChatEnable from "@app/components/ChatEnable.svelte"
2026-06-08 21:14:40 +00:00
// State
export let lastChatUrl: string | undefined = undefined
export const lastPageBySpaceUrl = new Map<string, string>()
export const setupHistory = () =>
page.subscribe($page => {
if ($page.params.relay) {
lastPageBySpaceUrl.set($page.params.relay, $page.url.pathname)
}
if ($page.params.chat) {
lastChatUrl = $page.url.pathname
}
})
2026-03-23 21:21:04 +00:00
// Chat
export const makeChatPath = (pubkeys: string[]) => `/chat/${makeChatId(pubkeys)}`
export const makeRoomPath = (url: string, h: string) => `/spaces/${encodeRelay(url)}/${h}`
export const makeSpaceChatPath = (url: string) => makeRoomPath(url, "chat")
export const goToChat = (pubkeys: string[] = []) => {
if (getRelaysFromList(get(userMessagingRelayList)).length === 0) {
pushModal(ChatEnable, {next: () => goToChat(pubkeys)})
} else if (pubkeys.length === 0) {
goto(lastChatUrl ?? "/chat")
} else {
goto(makeChatPath(pubkeys))
}
}
// Spaces
2025-02-06 17:29:30 +00:00
export const makeSpacePath = (url: string, ...extra: (string | undefined)[]) => {
2024-10-21 17:30:12 +00:00
let path = `/spaces/${encodeRelay(url)}`
2024-10-31 19:24:40 +00:00
if (extra.length > 0) {
2025-02-06 17:29:30 +00:00
path +=
"/" +
extra
.filter(identity)
.map(s => encodeURIComponent(s as string))
.join("/")
}
return path
}
2024-08-15 19:08:07 +00:00
export const goToSpace = async (url: string) => {
const prevPath = lastPageBySpaceUrl.get(encodeRelay(url))
if (prevPath && prevPath !== makeSpacePath(url)) {
goto(prevPath, {replaceState: true})
} else if (window.matchMedia(`(min-width: ${theme.screens.md})`).matches) {
goto(makeSpacePath(url, "recent"), {replaceState: true})
} else {
goto(makeSpacePath(url), {replaceState: true})
}
}
2026-03-23 21:21:04 +00:00
// Content types, events
2025-06-25 00:36:14 +00:00
2026-02-16 22:45:54 +00:00
export const makeMessagePath = (url: string, event: TrustedEvent) => {
const h = getTagValue(ROOM, event.tags)
const path = h ? makeRoomPath(url, h) : makeSpaceChatPath(url)
const qp = new URLSearchParams({at: String(event.created_at)})
return path + "?" + qp.toString()
}
export const makeGoalPath = (url: string, id?: string) => makeSpacePath(url, "goals", id)
2025-07-07 22:28:36 +00:00
export const makeThreadPath = (url: string, id?: string) => makeSpacePath(url, "threads", id)
2024-12-16 18:30:42 +00:00
2026-02-04 01:09:30 +00:00
export const makeClassifiedPath = (url: string, address?: string) =>
makeSpacePath(url, "classifieds", address)
2026-02-03 19:00:13 +00:00
2026-02-04 01:09:30 +00:00
export const makeCalendarPath = (url: string, address?: string) =>
makeSpacePath(url, "calendar", address)
2024-10-23 21:20:24 +00:00
2026-04-03 17:54:50 +00:00
export const makePollPath = (url: string, id?: string) => makeSpacePath(url, "polls", id)
2026-02-16 23:28:25 +00:00
export const scrollToEvent = (id: string) => {
const element = document.querySelector(`[data-event="${id}"]`) as any
if (element) {
element.scrollIntoView({behavior: "smooth", block: "center"})
element.style = "filter: brightness(1.5); transition-property: all; transition-duration: 400ms;"
setTimeout(() => {
element.style = "transition-property: all; transition-duration: 300ms;"
}, 800)
setTimeout(() => {
element.style = ""
}, 800 + 400)
}
return Boolean(element)
}
2026-02-16 22:45:54 +00:00
export const goToEvent = (event: TrustedEvent, options: Record<string, any> = {}) => {
const urls = Array.from(tracker.getRelays(event.id))
2026-02-16 22:45:54 +00:00
const path = getEventPath(event, urls)
2025-06-05 17:55:16 +00:00
if (path.includes("://")) {
window.open(path)
2026-02-16 23:28:25 +00:00
} else if (!scrollToEvent(event.id)) {
const replaceState = path.replace(/\?.*$/, "") === get(page).url.pathname
goto(path, {replaceState, ...options})
2025-06-19 17:22:17 +00:00
}
}
2026-02-16 22:45:54 +00:00
export const getEventPath = (event: TrustedEvent, urls: string[]) => {
2026-02-13 23:37:17 +00:00
if (DM_KINDS.includes(event.kind)) {
return makeChatPath([event.pubkey, ...getPubkeyTagValues(event.tags)])
}
2025-06-05 20:37:50 +00:00
if (urls.length > 0) {
const url = urls[0]
2025-07-07 22:28:36 +00:00
if (event.kind === ZAP_GOAL) {
return makeGoalPath(url, event.id)
}
2025-06-05 17:55:16 +00:00
if (event.kind === THREAD) {
2025-06-19 17:22:17 +00:00
return makeThreadPath(url, event.id)
2025-06-05 17:55:16 +00:00
}
2026-02-03 19:00:13 +00:00
if (event.kind === CLASSIFIED) {
return makeClassifiedPath(url, getAddress(event))
2026-02-03 19:00:13 +00:00
}
2025-06-05 17:55:16 +00:00
if (event.kind === EVENT_TIME) {
return makeCalendarPath(url, getAddress(event))
2025-06-05 17:55:16 +00:00
}
2026-04-16 18:30:59 +00:00
if (event.kind === POLL) {
2026-04-03 17:54:50 +00:00
return makePollPath(url, event.id)
}
2025-06-05 17:55:16 +00:00
if (event.kind === MESSAGE) {
2026-02-16 22:45:54 +00:00
return makeMessagePath(url, event)
2025-06-05 17:55:16 +00:00
}
const address = event.tags.find(nthEq(0, "A"))?.[1]
2025-06-05 17:55:16 +00:00
const kind = event.tags.find(nthEq(0, "K"))?.[1]
const id = event.tags.find(nthEq(0, "E"))?.[1]
if (id && kind) {
2025-07-07 22:28:36 +00:00
if (parseInt(kind) === ZAP_GOAL) {
return makeGoalPath(url, id)
}
2025-06-05 17:55:16 +00:00
if (parseInt(kind) === THREAD) {
2025-06-19 17:22:17 +00:00
return makeThreadPath(url, id)
2025-06-05 17:55:16 +00:00
}
if (parseInt(kind) === MESSAGE) {
2026-02-16 22:45:54 +00:00
return makeMessagePath(url, event)
}
}
if (address && kind) {
2026-02-03 19:00:13 +00:00
if (parseInt(kind) === CLASSIFIED) {
return makeClassifiedPath(url, address)
2026-02-03 19:00:13 +00:00
}
2025-06-05 17:55:16 +00:00
if (parseInt(kind) === EVENT_TIME) {
return makeCalendarPath(url, address)
2025-06-05 17:55:16 +00:00
}
}
}
2025-06-05 20:37:50 +00:00
2025-06-19 17:22:17 +00:00
return entityLink(nip19.neventEncode({id: event.id, relays: urls}))
2025-06-05 17:55:16 +00:00
}
2025-10-06 18:23:19 +00:00
2025-10-30 22:36:14 +00:00
export const getRoomItemPath = (url: string, event: TrustedEvent) => {
2025-10-06 18:23:19 +00:00
switch (event.kind) {
case THREAD:
return makeThreadPath(url, event.id)
2026-02-03 19:00:13 +00:00
case CLASSIFIED:
return makeClassifiedPath(url, getAddress(event))
2025-10-06 18:23:19 +00:00
case ZAP_GOAL:
return makeGoalPath(url, event.id)
case EVENT_TIME:
return makeCalendarPath(url, getAddress(event))
2026-04-16 18:30:59 +00:00
case POLL:
2026-04-03 17:54:50 +00:00
return makePollPath(url, event.id)
2025-10-06 18:23:19 +00:00
}
}