Compare commits

..

1 Commits

Author SHA1 Message Date
hodlbod 9df8cee501 Migrate to new @welshman/domain + instance-based @welshman/app API
Adopts the rewritten welshman API: the removed @welshman/util helpers
(Profile/List/Room/Handler/Encryptable) are now Reader/Builder classes in
@welshman/domain, and @welshman/app dropped its global singletons for an App
instance + app.use(Plugin) registry.

- src/app/welshman.ts is now the app bootstrap + session-state module (one shared
  App instance, multi-account sessions/login, app-wide reactive views) rather than
  a compat shim re-exporting the old globals.
- Rewrote ~100 callers to use app.use(Plugin) directly (thunks, profiles, relays,
  rooms, zaps, tags, wot, feeds, sync); thunk helpers are now thunk methods.
- Added @welshman/domain dependency.
- Resolved residual gaps (storage hydration via plugin.onItem/wrapManager/Plaintext,
  relay-list mutators, search-relay list, outbox #d filter).

Best-effort: no toolchain/linking available, so this is not build- or
type-checked. Remaining judgment calls are flagged with TODO(welshman-migration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsMjvv7krpZeHK1Njeneru
2026-06-20 14:55:06 +00:00
149 changed files with 1445 additions and 707 deletions
+1
View File
@@ -74,6 +74,7 @@
"@vite-pwa/sveltekit": "^1.1.0", "@vite-pwa/sveltekit": "^1.1.0",
"@welshman/app": "^0.8.16", "@welshman/app": "^0.8.16",
"@welshman/content": "^0.8.16", "@welshman/content": "^0.8.16",
"@welshman/domain": "^0.8.16",
"@welshman/editor": "^0.8.16", "@welshman/editor": "^0.8.16",
"@welshman/feeds": "^0.8.16", "@welshman/feeds": "^0.8.16",
"@welshman/lib": "^0.8.16", "@welshman/lib": "^0.8.16",
+1 -1
View File
@@ -18,7 +18,7 @@ import {derived, get} from "svelte/store"
import {map, not, nthEq, reject, removeUndefined, uniqBy} from "@welshman/lib" import {map, not, nthEq, reject, removeUndefined, uniqBy} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util" import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util"
import {signer} from "@welshman/app" import {signer} from "@app/welshman"
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {getLivekitEndpoint} from "$lib/livekit" import {getLivekitEndpoint} from "$lib/livekit"
import {AbortError, TimeoutError, whenAborted, whenTimeout} from "$lib/util" import {AbortError, TimeoutError, whenAborted, whenTimeout} from "$lib/util"
+4 -1
View File
@@ -2,7 +2,8 @@ import {DELETE, PROFILE, getPubkeyTagValues} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {append, call, on, reject, remove, sort, sortBy, spec, uniq, uniqBy} from "@welshman/lib" import {append, call, on, reject, remove, sort, sortBy, spec, uniq, uniqBy} from "@welshman/lib"
import type {Override} from "@welshman/lib" import type {Override} from "@welshman/lib"
import {createSearch, displayProfileByPubkey, pubkey, repository} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app, createSearch, pubkey, repository} from "@app/welshman"
import {derived, readable} from "svelte/store" import {derived, readable} from "svelte/store"
import {DM_KINDS} from "@app/content" import {DM_KINDS} from "@app/content"
import type {RepositoryUpdate} from "@welshman/net" import type {RepositoryUpdate} from "@welshman/net"
@@ -35,6 +36,8 @@ export const chatsById = call(() => {
const chatsByPubkey = new Map<string, string[]>() const chatsByPubkey = new Map<string, string[]>()
const addSearchText = (chat: Override<Chat, {search_text?: string}>) => { const addSearchText = (chat: Override<Chat, {search_text?: string}>) => {
const displayProfileByPubkey = (pk: string) => app.use(Profiles).display(pk).get()
chat.search_text = chat.search_text =
chat.pubkeys.length === 1 chat.pubkeys.length === 1
? displayProfileByPubkey(chat.pubkeys[0]) + " note to self" ? displayProfileByPubkey(chat.pubkeys[0]) + " note to self"
+4 -3
View File
@@ -1,6 +1,7 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {COMMENT, makeEvent} from "@welshman/util" import {COMMENT, makeEvent} from "@welshman/util"
import {publishThunk, tagEventForComment} from "@welshman/app" import {Thunks, Tags} from "@welshman/app"
import {app} from "@app/welshman"
export type CommentParams = { export type CommentParams = {
event: TrustedEvent event: TrustedEvent
@@ -10,7 +11,7 @@ export type CommentParams = {
} }
export const makeComment = ({url, event, content, tags = []}: CommentParams) => export const makeComment = ({url, event, content, tags = []}: CommentParams) =>
makeEvent(COMMENT, {content, tags: [...tags, ...tagEventForComment(event, url)]}) makeEvent(COMMENT, {content, tags: [...tags, ...app.use(Tags).tagEventForComment(event, url)]})
export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) => export const publishComment = ({relays, ...params}: CommentParams & {relays: string[]}) =>
publishThunk({event: makeComment({url: relays[0], ...params}), relays}) app.use(Thunks).publish({event: makeComment({url: relays[0], ...params}), relays})
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Dialog from "@lib/components/Dialog.svelte" import Dialog from "@lib/components/Dialog.svelte"
import Landing from "@app/components/Landing.svelte" import Landing from "@app/components/Landing.svelte"
import Toast from "@app/components/Toast.svelte" import Toast from "@app/components/Toast.svelte"
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {getTagValue, getAddress} from "@welshman/util" import {getTagValue, getAddress} from "@welshman/util"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Pen2 from "@assets/icons/pen-2.svg?dataurl" import Pen2 from "@assets/icons/pen-2.svg?dataurl"
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"
+4 -3
View File
@@ -3,7 +3,8 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {randomId, HOUR} from "@welshman/lib" import {randomId, HOUR} from "@welshman/lib"
import {makeEvent, EVENT_TIME} from "@welshman/util" import {makeEvent, EVENT_TIME} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {app} from "@app/welshman"
import {Thunks} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {daysBetween} from "@lib/util" import {daysBetween} from "@lib/util"
import GallerySend from "@assets/icons/gallery-send.svg?dataurl" import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
@@ -107,8 +108,8 @@
} }
const event = makeEvent(EVENT_TIME, {content, tags}) const event = makeEvent(EVENT_TIME, {content, tags})
const calendarThunk = publishThunk({event, relays: [url]}) const calendarThunk = app.use(Thunks).publish({event, relays: [url]})
const error = await waitForThunkError(calendarThunk) const error = await calendarThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+9 -13
View File
@@ -26,14 +26,10 @@
DIRECT_MESSAGE, DIRECT_MESSAGE,
DIRECT_MESSAGE_FILE, DIRECT_MESSAGE_FILE,
} from "@welshman/util" } from "@welshman/util"
import { import {app, pubkey} from "@app/welshman"
pubkey, import {Tags, Wraps, Thunks, MessagingRelayLists} from "@welshman/app"
tagPubkey,
sendWrapped, const messagingRelayListsByPubkey = app.use(MessagingRelayLists).index.$
mergeThunks,
loadMessagingRelayList,
messagingRelayListsByPubkey,
} from "@welshman/app"
import Danger from "@assets/icons/danger-triangle.svg?dataurl" import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import ArrowLeft from "@assets/icons/arrow-left.svg?dataurl" import ArrowLeft from "@assets/icons/arrow-left.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -96,7 +92,7 @@
const onSubmit = async (params: EventContent) => { const onSubmit = async (params: EventContent) => {
try { try {
const ptags = remove($pubkey!, pubkeys).map(tagPubkey) const ptags = remove($pubkey!, pubkeys).map(pk => app.use(Tags).tagPubkey(pk))
// Remove p tags since they result in forking the conversation // Remove p tags since they result in forking the conversation
params.tags = params.tags.filter(nthNe(0, "p")) params.tags = params.tags.filter(nthNe(0, "p"))
@@ -109,7 +105,7 @@
return return
} }
await sendWrapped({ await app.use(Wraps).publish({
event: makeDelete({event: eventToEdit, protect: false}), event: makeDelete({event: eventToEdit, protect: false}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
@@ -158,7 +154,7 @@
// Sleep 1 second between each one to make sure timestamps are distinct // Sleep 1 second between each one to make sure timestamps are distinct
const thunks = await Promise.all( const thunks = await Promise.all(
Array.from(enumerate(templates)).map(([i, event]) => Array.from(enumerate(templates)).map(([i, event]) =>
sendWrapped({ app.use(Wraps).publish({
event, event,
recipients: pubkeys, recipients: pubkeys,
delay: $userSettingsValues.send_delay + ms(i), delay: $userSettingsValues.send_delay + ms(i),
@@ -171,7 +167,7 @@
timeout: 30_000, timeout: 30_000,
children: { children: {
component: ThunkToast, component: ThunkToast,
props: {thunk: mergeThunks(thunks)}, props: {thunk: app.use(Thunks).merge(thunks)},
}, },
}) })
} finally { } finally {
@@ -234,7 +230,7 @@
onMount(() => { onMount(() => {
for (const pubkey of others) { for (const pubkey of others) {
loadMessagingRelayList(pubkey) app.use(MessagingRelayLists).load(pubkey)
} }
}) })
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "@welshman/app" import {app} from "@app/welshman"
import {Profiles} from "@welshman/app"
import {slide} from "@lib/transition" import {slide} from "@lib/transition"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -21,7 +22,7 @@
<div <div
class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8" class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8"
transition:slide> transition:slide>
<p class="text-xs text-primary">{verb} @{displayProfileByPubkey(event.pubkey)}</p> <p class="text-xs text-primary">{verb} @{app.use(Profiles).display(event.pubkey).get()}</p>
{#key event.id} {#key event.id}
<NoteContentMinimal trimParent {event} /> <NoteContentMinimal trimParent {event} />
{/key} {/key}
+9 -5
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {getRelaysFromList} from "@welshman/util" import {app, userRelayList} from "@app/welshman"
import {waitForThunkError, setMessagingRelays, userRelayList, setRelays} from "@welshman/app" import {RelayLists, MessagingRelayLists} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
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"
@@ -29,8 +29,10 @@
loading = true loading = true
try { try {
if (getRelaysFromList($userRelayList).length === 0) { if (($userRelayList?.urls() ?? []).length === 0) {
const error = await waitForThunkError(await setRelays(DEFAULT_RELAYS.map(r => ["r", r]))) const error = await (
await app.use(RelayLists).setRelays(DEFAULT_RELAYS.map(r => ["r", r]))
).waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
@@ -38,7 +40,9 @@
} }
} }
const error = await waitForThunkError(await setMessagingRelays(DEFAULT_MESSAGING_RELAYS)) const error = await (
await app.use(MessagingRelayLists).setUrls(DEFAULT_MESSAGING_RELAYS)
).waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
+3 -2
View File
@@ -3,7 +3,8 @@
import {page} from "$app/stores" import {page} from "$app/stores"
import {remove, uniq, formatTimestamp} from "@welshman/lib" import {remove, uniq, formatTimestamp} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {pubkey, loadMessagingRelayList} from "@welshman/app" import {app, pubkey} from "@app/welshman"
import {MessagingRelayLists} from "@welshman/app"
import {fade} from "@lib/transition" import {fade} from "@lib/transition"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileName from "@app/components/ProfileName.svelte" import ProfileName from "@app/components/ProfileName.svelte"
@@ -28,7 +29,7 @@
onMount(() => { onMount(() => {
for (const pk of others) { for (const pk of others) {
loadMessagingRelayList(pk) app.use(MessagingRelayLists).load(pk)
} }
}) })
</script> </script>
+6 -5
View File
@@ -2,7 +2,8 @@
import {type Instance} from "tippy.js" import {type Instance} from "tippy.js"
import {hash, formatTimestampAsTime} from "@welshman/lib" import {hash, formatTimestampAsTime} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {thunks, mergeThunks, pubkey, deriveProfileDisplay, sendWrapped} from "@welshman/app" import {app, thunks, pubkey} from "@app/welshman"
import {Thunks, Profiles, Wraps} from "@welshman/app"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -33,18 +34,18 @@
const {event, replyTo, canEdit, onEdit, pubkeys, showPubkey = false}: Props = $props() const {event, replyTo, canEdit, onEdit, pubkeys, showPubkey = false}: Props = $props()
const isOwn = event.pubkey === $pubkey const isOwn = event.pubkey === $pubkey
const profileDisplay = deriveProfileDisplay(event.pubkey) const profileDisplay = app.use(Profiles).display(event.pubkey).$
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id)) const thunk = app.use(Thunks).merge($thunks.filter(t => t.event.id === event.id))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length] const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
const reply = () => replyTo(event) const reply = () => replyTo(event)
const edit = canEdit?.(event) ? () => onEdit?.(event) : undefined const edit = canEdit?.(event) ? () => onEdit?.(event) : undefined
const deleteReaction = (event: TrustedEvent) => const deleteReaction = (event: TrustedEvent) =>
sendWrapped({event: makeDelete({event, protect: false}), recipients: pubkeys, pow: 16}) app.use(Wraps).publish({event: makeDelete({event, protect: false}), recipients: pubkeys, pow: 16})
const createReaction = (template: EventContent) => const createReaction = (template: EventContent) =>
sendWrapped({ app.use(Wraps).publish({
event: makeReaction({event, protect: false, ...template}), event: makeReaction({event, protect: false, ...template}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app" import {app} from "@app/welshman"
import {Wraps} from "@welshman/app"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl" import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import EmojiButton from "@lib/components/EmojiButton.svelte" import EmojiButton from "@lib/components/EmojiButton.svelte"
@@ -15,7 +16,7 @@
const {event, pubkeys}: Props = $props() const {event, pubkeys}: Props = $props()
const onEmoji = (emoji: NativeEmoji) => const onEmoji = (emoji: NativeEmoji) =>
sendWrapped({ app.use(Wraps).publish({
event: makeReaction({event, content: emoji.unicode, protect: false}), event: makeReaction({event, content: emoji.unicode, protect: false}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {sendWrapped} from "@welshman/app" import {app} from "@app/welshman"
import {Wraps} from "@welshman/app"
import SmileCircle from "@assets/icons/smile-circle.svg?dataurl" import SmileCircle from "@assets/icons/smile-circle.svg?dataurl"
import Pen from "@assets/icons/pen.svg?dataurl" import Pen from "@assets/icons/pen.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
@@ -28,7 +29,7 @@
const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => { const onEmoji = ((event: TrustedEvent, pubkeys: string[], emoji: NativeEmoji) => {
history.back() history.back()
sendWrapped({ app.use(Wraps).publish({
event: makeReaction({event, content: emoji.unicode, protect: false}), event: makeReaction({event, content: emoji.unicode, protect: false}),
recipients: pubkeys, recipients: pubkeys,
pow: 16, pow: 16,
+3 -2
View File
@@ -4,7 +4,8 @@
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {tryCatch, uniq} from "@welshman/lib" import {tryCatch, uniq} from "@welshman/lib"
import {fromNostrURI} from "@welshman/util" import {fromNostrURI} from "@welshman/util"
import {loadMessagingRelayList} from "@welshman/app" import {app} from "@app/welshman"
import {MessagingRelayLists} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Field from "@lib/components/Field.svelte" import Field from "@lib/components/Field.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -34,7 +35,7 @@
let pubkeys: string[] = $state([]) let pubkeys: string[] = $state([])
$effect(() => { $effect(() => {
pubkeys.forEach(pubkey => loadMessagingRelayList(pubkey)) pubkeys.forEach(pubkey => app.use(MessagingRelayLists).load(pubkey))
}) })
onMount(() => { onMount(() => {
+1 -1
View File
@@ -2,7 +2,7 @@
import {uniq} from "@welshman/lib" import {uniq} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {getTagValue, getTagValues, getAddress} from "@welshman/util" import {getTagValue, getTagValues, getAddress} from "@welshman/util"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Pen2 from "@assets/icons/pen-2.svg?dataurl" import Pen2 from "@assets/icons/pen-2.svg?dataurl"
import {normalizeTopic} from "@lib/util" import {normalizeTopic} from "@lib/util"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
+4 -3
View File
@@ -2,7 +2,8 @@
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import {removeUndefined, randomId, uniq} from "@welshman/lib" import {removeUndefined, randomId, uniq} from "@welshman/lib"
import {makeEvent, CLASSIFIED} from "@welshman/util" import {makeEvent, CLASSIFIED} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {app} from "@app/welshman"
import {Thunks} from "@welshman/app"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import {normalizeTopic} from "@lib/util" import {normalizeTopic} from "@lib/util"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
@@ -118,12 +119,12 @@
} }
} }
const classifiedThunk = publishThunk({ const classifiedThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makeEvent(CLASSIFIED, {content, tags}), event: makeEvent(CLASSIFIED, {content, tags}),
}) })
const error = await waitForThunkError(classifiedThunk) const error = await classifiedThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
@@ -9,7 +9,7 @@
tagsFromIMeta, tagsFromIMeta,
makeBlossomAuthEvent, makeBlossomAuthEvent,
} from "@welshman/util" } from "@welshman/util"
import {signer} from "@welshman/app" import {signer} from "@app/welshman"
import LinkRound from "@assets/icons/link-round.svg?dataurl" import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
+3 -2
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import type {ProfilePointer} from "@welshman/content" import type {ProfilePointer} from "@welshman/content"
import {deriveProfileDisplay} from "@welshman/app" import {app} from "@app/welshman"
import {Profiles} from "@welshman/app"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte" import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
@@ -13,7 +14,7 @@
const {value, url}: Props = $props() const {value, url}: Props = $props()
const display = deriveProfileDisplay(value.pubkey, removeUndefined([url])) const display = app.use(Profiles).display(value.pubkey, removeUndefined([url])).$
const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url}) const openProfile = () => pushModal(ProfileDetail, {pubkey: value.pubkey, url})
</script> </script>
+1 -1
View File
@@ -4,7 +4,7 @@
import type {Writable} from "svelte/store" import type {Writable} from "svelte/store"
import type {Instance} from "tippy.js" import type {Instance} from "tippy.js"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {createSearch} from "@welshman/app" import {createSearch} from "@app/welshman"
import {currencyOptions, displayCurrency} from "@lib/currency" import {currencyOptions, displayCurrency} from "@lib/currency"
import Suggestions from "@lib/components/Suggestions.svelte" import Suggestions from "@lib/components/Suggestions.svelte"
import CurrencySuggestion from "@app/components/CurrencySuggestion.svelte" import CurrencySuggestion from "@app/components/CurrencySuggestion.svelte"
+1 -1
View File
@@ -5,7 +5,7 @@
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {deriveArray, deriveEventsById} from "@welshman/store" import {deriveArray, deriveEventsById} from "@welshman/store"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {repository} from "@welshman/app" import {repository} from "@app/welshman"
import {deriveChecked} from "@app/notifications" import {deriveChecked} from "@app/notifications"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
+1 -1
View File
@@ -4,7 +4,7 @@
import {LOCALE, secondsToDate} from "@welshman/lib" import {LOCALE, secondsToDate} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {tracker} from "@welshman/app" import {tracker} from "@app/welshman"
import FileText from "@assets/icons/file-text.svg?dataurl" import FileText from "@assets/icons/file-text.svg?dataurl"
import Copy from "@assets/icons/copy.svg?dataurl" import Copy from "@assets/icons/copy.svg?dataurl"
import UserCircle from "@assets/icons/user-circle.svg?dataurl" import UserCircle from "@assets/icons/user-circle.svg?dataurl"
+3 -2
View File
@@ -4,7 +4,8 @@
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {COMMENT, ManagementMethod} from "@welshman/util" import {COMMENT, ManagementMethod} from "@welshman/util"
import {pubkey, repository, relaysByUrl, manageRelay} from "@welshman/app" import {app, pubkey, repository, relaysByUrl} from "@app/welshman"
import {RelayManagement} from "@welshman/app"
import ShareCircle from "@assets/icons/share-circle.svg?dataurl" import ShareCircle from "@assets/icons/share-circle.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl" import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
@@ -56,7 +57,7 @@
title: `Delete ${noun}`, title: `Delete ${noun}`,
message: `Are you sure you want to delete this ${noun.toLowerCase()} from the space?`, message: `Are you sure you want to delete this ${noun.toLowerCase()} from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id], params: [event.id],
}) })
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {makeEvent, ZAP_GOAL} from "@welshman/util" import {makeEvent, ZAP_GOAL} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {app} from "@app/welshman"
import {Thunks} from "@welshman/app"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl" import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
import Bolt from "@assets/icons/bolt.svg?dataurl" import Bolt from "@assets/icons/bolt.svg?dataurl"
@@ -93,12 +94,12 @@
tags.push(["h", h]) tags.push(["h", h])
} }
const goalThunk = publishThunk({ const goalThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makeEvent(ZAP_GOAL, {content: title, tags}), event: makeEvent(ZAP_GOAL, {content: title, tags}),
}) })
const error = await waitForThunkError(goalThunk) const error = await goalThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+13 -9
View File
@@ -2,8 +2,10 @@
import {now, DAY, uniq, sum} from "@welshman/lib" import {now, DAY, uniq, sum} from "@welshman/lib"
import type {Zap, TrustedEvent} from "@welshman/util" import type {Zap, TrustedEvent} from "@welshman/util"
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util" import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
import {deriveItemsByKey, deriveArray} from "@welshman/store" import {derived} from "svelte/store"
import {repository, getValidZap} from "@welshman/app" import {deriveEvents} from "@welshman/store"
import {app, repository} from "@app/welshman"
import {Zappers} from "@welshman/app"
import Bolt from "@assets/icons/bolt.svg?dataurl" import Bolt from "@assets/icons/bolt.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ZapButton from "@app/components/ZapButton.svelte" import ZapButton from "@app/components/ZapButton.svelte"
@@ -16,13 +18,15 @@
const {url, event, ...props}: Props = $props() const {url, event, ...props}: Props = $props()
const zaps = deriveArray( // Validated zaps for this goal. `validZapReceipts` is a reactive Projection
deriveItemsByKey<Zap>({ // (resolves recipient zappers from loaded profiles); we re-derive it whenever
repository, // the set of ZAP_RESPONSE events in the repository changes.
getKey: zap => zap.response.id, const zapReceipts = deriveEvents({repository, filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}]})
filters: [{kinds: [ZAP_RESPONSE], "#e": [event.id]}], const zaps = derived(
eventToItem: (response: TrustedEvent) => getValidZap(response, event), zapReceipts,
}), ($receipts: TrustedEvent[], set) =>
app.use(Zappers).validZapReceipts($receipts, event).$.subscribe(set),
[] as Zap[],
) )
const goalAmount = parseInt(getTagValue("amount", event.tags) || "0") const goalAmount = parseInt(getTagValue("amount", event.tags) || "0")
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {createSearch} from "@welshman/app" import {createSearch} from "@app/welshman"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {session} from "@welshman/app" import {session} from "@app/welshman"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl" import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveZapperForPubkey} from "@welshman/app" import {app} from "@app/welshman"
import {Zappers} from "@welshman/app"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
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"
@@ -12,7 +13,7 @@
const {pubkey} = $props() const {pubkey} = $props()
const zapper = deriveZapperForPubkey(pubkey) const zapper = app.use(Zappers).forPubkey(pubkey).$
const back = () => history.back() const back = () => history.back()
</script> </script>
+8 -2
View File
@@ -1,8 +1,14 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import {getPubkey} from "@welshman/util" import {getPubkey} from "@welshman/util"
import type {SessionPomade} from "@welshman/app" import {session} from "@app/welshman"
import {session} from "@welshman/app" // TODO(welshman-migration): `SessionPomade` was removed from @welshman/app; the
// new generic Session nests its descriptor under `.data` ({clientOptions, email}),
// whereas this code destructures `email`/`clientOptions` from the top level.
// Confirm whether flotilla's FlotillaSession still surfaces these at the top
// level (loginWithPomade stores them under `.data` via toSession) and adjust the
// destructuring accordingly. Local type kept loose to avoid a broken import.
type SessionPomade = {email: string; clientOptions: {secret: string; peers: any}}
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
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"
+7 -2
View File
@@ -1,7 +1,12 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import type {SessionPomade} from "@welshman/app" import {session} from "@app/welshman"
import {session} from "@welshman/app" // TODO(welshman-migration): `SessionPomade` was removed from @welshman/app; the
// new generic Session nests its descriptor under `.data` ({clientOptions, email}),
// whereas this code destructures from the top level. Confirm whether flotilla's
// FlotillaSession still surfaces these at the top level and adjust accordingly.
// Local type kept loose to avoid a broken import.
type SessionPomade = {email: string; clientOptions: {peers: any}}
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
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"
+5 -5
View File
@@ -2,7 +2,7 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {Capacitor} from "@capacitor/core" import {Capacitor} from "@capacitor/core"
import {getNip07, getNip55, Nip55Signer} from "@welshman/signer" import {getNip07, getNip55, Nip55Signer} from "@welshman/signer"
import {addSession, type Session, makeNip07Session, makeNip55Session} from "@welshman/app" import {addSession, type FlotillaSession, makeNip07Session, makeNip55Session} from "@app/welshman"
import Widget from "@assets/icons/widget-4.svg?dataurl" import Widget from "@assets/icons/widget-4.svg?dataurl"
import Letter from "@assets/icons/letter.svg?dataurl" import Letter from "@assets/icons/letter.svg?dataurl"
import Cpu from "@assets/icons/cpu-bolt.svg?dataurl" import Cpu from "@assets/icons/cpu-bolt.svg?dataurl"
@@ -32,8 +32,8 @@
const signUp = () => pushModal(SignUp) const signUp = () => pushModal(SignUp)
const onSuccess = async (session: Session) => { const onSuccess = async (session: FlotillaSession, pubkey: string) => {
addSession(session) addSession({...session, pubkey})
setChecked("*") setChecked("*")
clearModals() clearModals()
} }
@@ -45,7 +45,7 @@
const pubkey = await getNip07()?.getPublicKey() const pubkey = await getNip07()?.getPublicKey()
if (pubkey) { if (pubkey) {
await onSuccess(makeNip07Session(pubkey)) await onSuccess(makeNip07Session(pubkey), pubkey)
} else { } else {
pushToast({ pushToast({
theme: "error", theme: "error",
@@ -65,7 +65,7 @@
const pubkey = await signer.getPubkey() const pubkey = await signer.getPubkey()
if (pubkey) { if (pubkey) {
await onSuccess(makeNip55Session(pubkey, app.packageName)) await onSuccess(makeNip55Session(pubkey, app.packageName), pubkey)
} else { } else {
pushToast({ pushToast({
theme: "error", theme: "error",
+1 -1
View File
@@ -4,7 +4,7 @@
import type {Nip46ResponseWithResult} from "@welshman/signer" import type {Nip46ResponseWithResult} from "@welshman/signer"
import {Nip46Broker} from "@welshman/signer" import {Nip46Broker} from "@welshman/signer"
import {makeSecret} from "@welshman/util" import {makeSecret} from "@welshman/util"
import {loginWithNip01, loginWithNip46} from "@welshman/app" import {loginWithNip01, loginWithNip46} from "@app/welshman"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
+2 -2
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {bytesToHex} from "@welshman/lib" import {bytesToHex} from "@welshman/lib"
import {loginWithNip01} from "@welshman/app" import {loginWithNip01} from "@app/welshman"
import {decrypt} from "nostr-tools/nip49" import {decrypt} from "nostr-tools/nip49"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import {nsecDecode} from "@lib/util" import {nsecDecode} from "@lib/util"
@@ -56,7 +56,7 @@
}) })
} }
loginWithNip01(secret) await loginWithNip01(secret)
setChecked("*") setChecked("*")
clearModals() clearModals()
} catch (e) { } catch (e) {
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {Capacitor} from "@capacitor/core" import {Capacitor} from "@capacitor/core"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Server from "@assets/icons/server.svg?dataurl" import Server from "@assets/icons/server.svg?dataurl"
import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl" import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl"
import Shield from "@assets/icons/shield-minimalistic.svg?dataurl" import Shield from "@assets/icons/shield-minimalistic.svg?dataurl"
@@ -3,8 +3,10 @@
import {sum} from "@welshman/lib" import {sum} from "@welshman/lib"
import type {Zap, TrustedEvent} from "@welshman/util" import type {Zap, TrustedEvent} from "@welshman/util"
import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util" import {getTagValue, fromMsats, ZAP_RESPONSE} from "@welshman/util"
import {deriveItemsByKey, deriveArray} from "@welshman/store" import {derived} from "svelte/store"
import {repository, getValidZap} from "@welshman/app" import {deriveEvents} from "@welshman/store"
import {app, repository} from "@app/welshman"
import {Zappers} from "@welshman/app"
import Bolt from "@assets/icons/bolt.svg?dataurl" import Bolt from "@assets/icons/bolt.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ContentMinimal from "@app/components/ContentMinimal.svelte" import ContentMinimal from "@app/components/ContentMinimal.svelte"
@@ -14,13 +16,14 @@
const content = getTagValue("summary", props.event.tags) const content = getTagValue("summary", props.event.tags)
const fakeEvent = {content, tags: props.event.tags} const fakeEvent = {content, tags: props.event.tags}
const zaps = deriveArray( // Validated zaps for this goal (reactive Projection, re-derived as the set of
deriveItemsByKey<Zap>({ // ZAP_RESPONSE events in the repository changes).
repository, const zapReceipts = deriveEvents({repository, filters: [{kinds: [ZAP_RESPONSE], "#e": [props.event.id]}]})
getKey: zap => zap.response.id, const zaps = derived(
filters: [{kinds: [ZAP_RESPONSE], "#e": [props.event.id]}], zapReceipts,
eventToItem: (response: TrustedEvent) => getValidZap(response, props.event), ($receipts: TrustedEvent[], set) =>
}), app.use(Zappers).validZapReceipts($receipts, props.event).$.subscribe(set),
[] as Zap[],
) )
const goalAmount = parseInt(getTagValue("amount", props.event.tags) || "0") const goalAmount = parseInt(getTagValue("amount", props.event.tags) || "0")
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import type {SessionPomade} from "@welshman/app" import type {SessionPomade} from "@welshman/app"
import {session} from "@welshman/app" import {session} from "@app/welshman"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
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"
@@ -1,7 +1,12 @@
<script lang="ts"> <script lang="ts">
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import {session} from "@welshman/app" import {session} from "@app/welshman"
import type {SessionPomade} from "@welshman/app" // TODO(welshman-migration): `SessionPomade` was removed from @welshman/app; the
// new generic Session nests its descriptor under `.data` ({clientOptions, email}),
// whereas this code destructures `email` from the top level. Confirm whether
// flotilla's FlotillaSession still surfaces `email` at the top level and adjust.
// Local type kept loose to avoid a broken import.
type SessionPomade = {email: string}
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
import Key from "@assets/icons/key.svg?dataurl" import Key from "@assets/icons/key.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {insertAt, now, randomId, removeAt, removeUndefined} from "@welshman/lib" import {insertAt, now, randomId, removeAt, removeUndefined} from "@welshman/lib"
import {makeEvent, POLL} from "@welshman/util" import {makeEvent, POLL} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {Thunks} from "@welshman/app"
import {app} from "@app/welshman"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import HamburgerMenu from "@assets/icons/hamburger-menu.svg?dataurl" import HamburgerMenu from "@assets/icons/hamburger-menu.svg?dataurl"
@@ -142,12 +143,12 @@
tags.push(PROTECTED) tags.push(PROTECTED)
} }
const pollThunk = publishThunk({ const pollThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makeEvent(POLL, {content: title.trim(), tags}), event: makeEvent(POLL, {content: title.trim(), tags}),
}) })
const error = await waitForThunkError(pollThunk) const error = await pollThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+7 -5
View File
@@ -2,7 +2,9 @@
import {onDestroy} from "svelte" import {onDestroy} from "svelte"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {POLL_RESPONSE} from "@welshman/util" import {POLL_RESPONSE} from "@welshman/util"
import {pubkey, publishThunk, abortThunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import {Thunks} from "@welshman/app"
import {pubkey, app} from "@app/welshman"
import {formatTimestampRelative} from "@welshman/lib" import {formatTimestampRelative} from "@welshman/lib"
import {deriveEvents} from "@app/repository" import {deriveEvents} from "@app/repository"
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
@@ -50,7 +52,7 @@
const publishSelection = (selection: string[]) => { const publishSelection = (selection: string[]) => {
if (activeThunk) { if (activeThunk) {
abortThunk(activeThunk) activeThunk.abort()
} }
if (selection.length === 0) { if (selection.length === 0) {
@@ -58,7 +60,7 @@
return return
} }
activeThunk = publishThunk({ activeThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makePollResponse({event, selectedIds: selection}), event: makePollResponse({event, selectedIds: selection}),
delay: publishDelay, delay: publishDelay,
@@ -92,7 +94,7 @@
} }
let selectedIds = $state<string[]>([]) let selectedIds = $state<string[]>([])
let activeThunk: ReturnType<typeof publishThunk> | undefined let activeThunk: Thunk | undefined
$effect(() => { $effect(() => {
if (ownResponse) { if (ownResponse) {
@@ -102,7 +104,7 @@
onDestroy(() => { onDestroy(() => {
if (activeThunk) { if (activeThunk) {
abortThunk(activeThunk) activeThunk.abort()
} }
}) })
</script> </script>
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {Client} from "@pomade/core" import {Client} from "@pomade/core"
import {session, isPomadeSession} from "@welshman/app" import {session, isPomadeSession} from "@app/welshman"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
+5 -5
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import {userProfile} from "@welshman/app" import {userProfile} from "@app/welshman"
import Letter from "@assets/icons/letter.svg?dataurl" import Letter from "@assets/icons/letter.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import Widget from "@assets/icons/widget-4.svg?dataurl" import Widget from "@assets/icons/widget-4.svg?dataurl"
@@ -41,8 +41,8 @@
{/if} {/if}
<div class="flex flex-col"> <div class="flex flex-col">
<PrimaryNavItem title="Settings" href="/settings/profile" prefix="/settings"> <PrimaryNavItem title="Settings" href="/settings/profile" prefix="/settings">
{#if $userProfile?.picture} {#if $userProfile?.picture()}
<ImageIcon alt="Settings" src={$userProfile?.picture} class="rounded-full" size={10} /> <ImageIcon alt="Settings" src={$userProfile?.picture()} class="rounded-full" size={10} />
{:else} {:else}
<ImageIcon alt="Settings" src={UserRounded} class="rounded-full" size={8} /> <ImageIcon alt="Settings" src={UserRounded} class="rounded-full" size={8} />
{/if} {/if}
@@ -86,8 +86,8 @@
{/if} {/if}
</div> </div>
<PrimaryNavItem title="Settings" onclick={showSettingsMenu}> <PrimaryNavItem title="Settings" onclick={showSettingsMenu}>
{#if $userProfile?.picture} {#if $userProfile?.picture()}
<ImageIcon alt="Settings" src={$userProfile?.picture} size={10} class="rounded-full" /> <ImageIcon alt="Settings" src={$userProfile?.picture()} size={10} class="rounded-full" />
{:else} {:else}
<ImageIcon alt="Settings" src={Settings} size={8} class="rounded-full" /> <ImageIcon alt="Settings" src={Settings} size={8} class="rounded-full" />
{/if} {/if}
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveRelayDisplay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte" import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
import RelayIcon from "@app/components/RelayIcon.svelte" import RelayIcon from "@app/components/RelayIcon.svelte"
import {makeSpacePath, goToSpace} from "@app/routes" import {makeSpacePath, goToSpace} from "@app/routes"
@@ -15,7 +16,7 @@
const path = makeSpacePath(url) const path = makeSpacePath(url)
const display = $derived(deriveRelayDisplay(url)) const display = $derived(app.use(Relays).display(url).$)
</script> </script>
<PrimaryNavItem <PrimaryNavItem
+6 -5
View File
@@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import * as nip19 from "nostr-tools/nip19" import * as nip19 from "nostr-tools/nip19"
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {displayPubkey} from "@welshman/util" import {displayPubkey, displayNip05} from "@welshman/util"
import {deriveHandleForPubkey, displayHandle, deriveProfileDisplay} from "@welshman/app" import {Handles, Profiles} from "@welshman/app"
import {app} from "@app/welshman"
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 ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte"
@@ -23,8 +24,8 @@
const {pubkey, url, showPubkey, inert, avatarSize = 10}: Props = $props() const {pubkey, url, showPubkey, inert, avatarSize = 10}: Props = $props()
const relays = removeUndefined([url]) const relays = removeUndefined([url])
const profileDisplay = deriveProfileDisplay(pubkey, relays) const profileDisplay = app.use(Profiles).display(pubkey, relays).$
const handle = deriveHandleForPubkey(pubkey) const handle = app.use(Handles).forPubkey(pubkey).$
const openProfile = () => { const openProfile = () => {
pushModal(ProfileDetail, {pubkey, url}) pushModal(ProfileDetail, {pubkey, url})
@@ -58,7 +59,7 @@
</div> </div>
{#if $handle} {#if $handle}
<div class="overflow-hidden text-ellipsis text-sm opacity-75"> <div class="overflow-hidden text-ellipsis text-sm opacity-75">
{displayHandle($handle)} {displayNip05($handle?.nip05)}
</div> </div>
{/if} {/if}
{#if showPubkey} {#if showPubkey}
+3 -2
View File
@@ -6,7 +6,8 @@
import {deriveEventsDesc, deriveEventsById} from "@welshman/store" import {deriveEventsDesc, deriveEventsById} from "@welshman/store"
import {formatTimestampRelative} from "@welshman/lib" import {formatTimestampRelative} from "@welshman/lib"
import {NOTE, ROOMS, COMMENT, MESSAGE} from "@welshman/util" import {NOTE, ROOMS, COMMENT, MESSAGE} from "@welshman/util"
import {repository, loadRelayList} from "@welshman/app" import {RelayLists} from "@welshman/app"
import {repository, app} from "@app/welshman"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileSpaces from "@app/components/ProfileSpaces.svelte" import ProfileSpaces from "@app/components/ProfileSpaces.svelte"
import {deriveGroupList, getSpaceUrlsFromGroupList} from "@app/groups" import {deriveGroupList, getSpaceUrlsFromGroupList} from "@app/groups"
@@ -30,7 +31,7 @@
onMount(async () => { onMount(async () => {
// Make sure we have their relay selections before we load their posts // Make sure we have their relay selections before we load their posts
await loadRelayList(pubkey) await app.use(RelayLists).load(pubkey)
// Load groups and at least one note, regardless of time frame // Load groups and at least one note, regardless of time frame
load({ load({
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {deriveProfile} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl" import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import ImageIcon from "@lib/components/ImageIcon.svelte" import ImageIcon from "@lib/components/ImageIcon.svelte"
@@ -14,11 +15,11 @@
const {pubkey, url, size = 7, ...props}: Props = $props() const {pubkey, url, size = 7, ...props}: Props = $props()
const profile = deriveProfile(pubkey, removeUndefined([url])) const profile = app.use(Profiles).one(pubkey, removeUndefined([url]))
</script> </script>
<ImageIcon <ImageIcon
{size} {size}
alt="" alt=""
class={cx(props.class, "rounded-full")} class={cx(props.class, "rounded-full")}
src={$profile?.picture || UserRounded} /> src={$profile?.picture() || UserRounded} />
+4 -3
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {getProfile, loadProfile} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte"
@@ -23,11 +24,11 @@
) )
for (const pubkey of pubkeys) { for (const pubkey of pubkeys) {
loadProfile(pubkey) app.use(Profiles).load(pubkey)
} }
const visiblePubkeys = $derived.by(() => { const visiblePubkeys = $derived.by(() => {
const filtered = pubkeys.filter(pubkey => getProfile(pubkey)?.picture) const filtered = pubkeys.filter(pubkey => app.use(Profiles).get(pubkey)?.picture())
return filtered.length > 0 ? filtered : pubkeys.slice(0, 1) return filtered.length > 0 ? filtered : pubkeys.slice(0, 1)
}) })
+9 -15
View File
@@ -1,15 +1,9 @@
<script lang="ts"> <script lang="ts">
import {chunk, sleep, uniq} from "@welshman/lib" import {chunk, sleep, uniq} from "@welshman/lib"
import { import {makeEvent, DELETE, isReplaceable, getAddress} from "@welshman/util"
makeEvent, import {ProfileBuilder} from "@welshman/domain"
createProfile, import {Thunks, RelayLists} from "@welshman/app"
PROFILE, import {pubkey, repository, app} from "@app/welshman"
DELETE,
isReplaceable,
getAddress,
RelayMode,
} from "@welshman/util"
import {pubkey, publishThunk, repository, derivePubkeyRelays} from "@welshman/app"
import {preventDefault} from "@lib/html" import {preventDefault} from "@lib/html"
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"
@@ -31,7 +25,7 @@
let confirmText = $state("") let confirmText = $state("")
const CONFIRM_TEXT = "permanently delete my nostr account" const CONFIRM_TEXT = "permanently delete my nostr account"
const userWriteRelays = derivePubkeyRelays($pubkey!, RelayMode.Write) const userWriteRelays = app.use(RelayLists).writeUrls($pubkey!).$
const confirmOk = $derived(confirmText.toLowerCase().trim() === CONFIRM_TEXT) const confirmOk = $derived(confirmText.toLowerCase().trim() === CONFIRM_TEXT)
const showProgress = $derived(progress !== undefined) const showProgress = $derived(progress !== undefined)
@@ -44,7 +38,7 @@
} }
const chunks = chunk(500, repository.query([{authors: [$pubkey!]}])) const chunks = chunk(500, repository.query([{authors: [$pubkey!]}]))
const profileEvent = makeEvent(PROFILE, createProfile({name: "[deleted]"})) const profileEvent = await new ProfileBuilder().update({name: "[deleted]"}).toTemplate()
const vanishEvent = makeEvent(62, {tags: [["relay", "ALL_RELAYS"]]}) const vanishEvent = makeEvent(62, {tags: [["relay", "ALL_RELAYS"]]})
const denominator = chunks.length + 2 const denominator = chunks.length + 2
const relays = uniq([...INDEXER_RELAYS, ...$userWriteRelays, ...$userSpaceUrls]) const relays = uniq([...INDEXER_RELAYS, ...$userWriteRelays, ...$userSpaceUrls])
@@ -58,12 +52,12 @@
} }
// First, blank out their profile in case relays don't support deletion by address // First, blank out their profile in case relays don't support deletion by address
await publishThunk({relays, event: profileEvent}) await app.use(Thunks).publish({relays, event: profileEvent})
await incrementProgress() await incrementProgress()
// Next, send a "right to vanish" event to all relays // Next, send a "right to vanish" event to all relays
await publishThunk({relays, event: vanishEvent}) await app.use(Thunks).publish({relays, event: vanishEvent})
await incrementProgress() await incrementProgress()
@@ -79,7 +73,7 @@
} }
} }
await publishThunk({relays, event: makeEvent(DELETE, {tags})}) await app.use(Thunks).publish({relays, event: makeEvent(DELETE, {tags})})
await incrementProgress() await incrementProgress()
} }
+6 -10
View File
@@ -2,12 +2,8 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {ManagementMethod} from "@welshman/util" import {ManagementMethod} from "@welshman/util"
import { import {RelayManagement, Profiles, MessagingRelayLists} from "@welshman/app"
manageRelay, import {app} from "@app/welshman"
deriveProfile,
displayProfileByPubkey,
loadMessagingRelayList,
} from "@welshman/app"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
import Letter from "@assets/icons/letter-opened.svg?dataurl" import Letter from "@assets/icons/letter-opened.svg?dataurl"
@@ -41,7 +37,7 @@
const {pubkey, url}: Props = $props() const {pubkey, url}: Props = $props()
const profile = deriveProfile(pubkey, removeUndefined([url])) const profile = app.use(Profiles).one(pubkey, removeUndefined([url]))
const userIsAdmin = deriveUserIsSpaceAdmin(url) const userIsAdmin = deriveUserIsSpaceAdmin(url)
@@ -66,9 +62,9 @@
const banMember = () => const banMember = () =>
pushModal(Confirm, { pushModal(Confirm, {
title: "Ban User", title: "Ban User",
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`, message: `Are you sure you want to ban @${app.use(Profiles).display(pubkey).get()} from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url!, { const {error} = await app.use(RelayManagement).post(url!, {
method: ManagementMethod.BanPubkey, method: ManagementMethod.BanPubkey,
params: [pubkey], params: [pubkey],
}) })
@@ -96,7 +92,7 @@
let showMenu = $state(false) let showMenu = $state(false)
onMount(() => { onMount(() => {
loadMessagingRelayList(pubkey) app.use(MessagingRelayLists).load(pubkey)
}) })
</script> </script>
+16 -5
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Profile} from "@welshman/util" import {Profile, ProfileBuilder} from "@welshman/domain"
import {makeProfile} from "@welshman/util" import {pubkey, profilesByPubkey} from "@app/welshman"
import {pubkey, profilesByPubkey, waitForThunkError} from "@welshman/app"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import {errorMessage} from "@lib/util" import {errorMessage} from "@lib/util"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -12,16 +11,28 @@
import {pushToast} from "@app/toast" import {pushToast} from "@app/toast"
import {updateProfile} from "@app/profiles" import {updateProfile} from "@app/profiles"
const profile = $profilesByPubkey.get($pubkey!) || makeProfile() // The edit form binds to plain mutable fields (name/about/nip05/picture), so we
// hand it a plain values object rather than a Profile Reader. A Reader exposes its
// raw parsed content as `.values`; a fresh profile starts from an empty builder.
const reader = $profilesByPubkey.get($pubkey!)
const profile = reader instanceof Profile ? {...reader.values} : new ProfileBuilder().update({}).values
const initialValues = {profile} const initialValues = {profile}
const back = () => history.back() const back = () => history.back()
// TODO(welshman-migration): `profile` here is a plain values object (the form binds
// mutable fields), not a Profile Reader. It is typed Profile to match ProfileEditForm's
// Values type and updateProfile's signature, both of which still say Profile; updateProfile
// routes a non-Reader through `new ProfileBuilder().update(profile)`. Confirm the intended
// values vs Reader contract once the shared types settle.
const onsubmit = async ({profile}: {profile: Profile}) => { const onsubmit = async ({profile}: {profile: Profile}) => {
loading = true loading = true
try { try {
const error = await waitForThunkError(updateProfile({profile})) // TODO(welshman-migration): updateProfile is async (returns Promise<Thunk>); the old
// waitForThunkError shim did not await its arg. Awaiting the thunk first before
// .waitForError() — confirm this matches intended behavior.
const error = await (await updateProfile({profile})).waitForError()
if (error) { if (error) {
pushToast({ pushToast({
+3 -2
View File
@@ -4,7 +4,8 @@
import {feedFromFilter, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds" import {feedFromFilter, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
import {NOTE, getReplyTags} from "@welshman/util" import {NOTE, getReplyTags} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {makeFeedController} from "@welshman/app" import {Feeds} from "@welshman/app"
import {app} from "@app/welshman"
import {createScroller} from "@lib/html" import {createScroller} from "@lib/html"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -19,7 +20,7 @@
let {url, pubkey, events = $bindable([]), hideLoading = false}: Props = $props() let {url, pubkey, events = $bindable([]), hideLoading = false}: Props = $props()
const ctrl = makeFeedController({ const ctrl = app.use(Feeds).makeFeedController({
useWindowing: true, useWindowing: true,
feed: makeIntersectionFeed( feed: makeIntersectionFeed(
makeRelayFeed(url), makeRelayFeed(url),
+4 -3
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {deriveProfile} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import ContentMinimal from "@app/components/ContentMinimal.svelte" import ContentMinimal from "@app/components/ContentMinimal.svelte"
export type Props = { export type Props = {
@@ -10,9 +11,9 @@
const {pubkey, url}: Props = $props() const {pubkey, url}: Props = $props()
const profile = deriveProfile(pubkey, removeUndefined([url])) const profile = app.use(Profiles).one(pubkey, removeUndefined([url]))
</script> </script>
{#if $profile} {#if $profile}
<ContentMinimal event={{content: $profile.about || "", tags: []}} /> <ContentMinimal event={{content: $profile.about() || "", tags: []}} />
{/if} {/if}
+1 -1
View File
@@ -4,7 +4,7 @@
import type {Writable} from "svelte/store" import type {Writable} from "svelte/store"
import {type Instance} from "tippy.js" import {type Instance} from "tippy.js"
import {append, remove, uniq} from "@welshman/lib" import {append, remove, uniq} from "@welshman/lib"
import {profileSearch} from "@welshman/app" import {profileSearch} from "@app/welshman"
import Suggestions from "@lib/components/Suggestions.svelte" import Suggestions from "@lib/components/Suggestions.svelte"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {removeUndefined} from "@welshman/lib" import {removeUndefined} from "@welshman/lib"
import {deriveProfileDisplay} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
type Props = { type Props = {
pubkey: string pubkey: string
@@ -9,7 +10,7 @@
const {pubkey, url}: Props = $props() const {pubkey, url}: Props = $props()
const profileDisplay = deriveProfileDisplay(pubkey, removeUndefined([url])) const profileDisplay = app.use(Profiles).display(pubkey, removeUndefined([url])).$
</script> </script>
{$profileDisplay} {$profileDisplay}
+7 -6
View File
@@ -17,7 +17,8 @@
import type {TrustedEvent, EventContent, Zap} from "@welshman/util" import type {TrustedEvent, EventContent, Zap} from "@welshman/util"
import {deriveArray, deriveEventsById, deriveItemsByKey} from "@welshman/store" import {deriveArray, deriveEventsById, deriveItemsByKey} from "@welshman/store"
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {pubkey, repository, getValidZap, displayProfileByPubkey} from "@welshman/app" import {Zappers, Profiles} from "@welshman/app"
import {pubkey, repository, app} from "@app/welshman"
import {isMobile, preventDefault, stopPropagation} from "@lib/html" import {isMobile, preventDefault, stopPropagation} from "@lib/html"
import Danger from "@assets/icons/danger-triangle.svg?dataurl" import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -64,15 +65,15 @@
repository, repository,
getKey: zap => zap.response.id, getKey: zap => zap.response.id,
filters: [{kinds: [ZAP_RESPONSE], "#e": eventIds}], filters: [{kinds: [ZAP_RESPONSE], "#e": eventIds}],
eventToItem: (response: TrustedEvent) => { eventToItem: async (response: TrustedEvent) => {
const zap = getValidZap(response, event) const zap = await app.use(Zappers).validateZapReceipt(response, event)
if (zap) { if (zap) {
return zap return zap
} }
if (innerEvent) { if (innerEvent) {
return getValidZap(response, innerEvent) return await app.use(Zappers).validateZapReceipt(response, innerEvent)
} }
}, },
}), }),
@@ -150,7 +151,7 @@
{@const amount = fromMsats(sum(zaps.map(zap => zap.invoiceAmount)))} {@const amount = fromMsats(sum(zaps.map(zap => zap.invoiceAmount)))}
{@const pubkeys = uniq(zaps.map(zap => zap.request.pubkey))} {@const pubkeys = uniq(zaps.map(zap => zap.request.pubkey))}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)} {@const isOwn = $pubkey && pubkeys.includes($pubkey)}
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))} {@const info = displayList(pubkeys.map(pubkey => app.use(Profiles).display(pubkey).get()))}
{@const tooltip = `${info} zapped`} {@const tooltip = `${info} zapped`}
<button <button
type="button" type="button"
@@ -171,7 +172,7 @@
{#each groupedReactions.entries() as [key, events]} {#each groupedReactions.entries() as [key, events]}
{@const pubkeys = events.map(e => e.pubkey)} {@const pubkeys = events.map(e => e.pubkey)}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)} {@const isOwn = $pubkey && pubkeys.includes($pubkey)}
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))} {@const info = displayList(pubkeys.map(pubkey => app.use(Profiles).display(pubkey).get()))}
{@const tooltip = `${info} reacted`} {@const tooltip = `${info} reacted`}
{@const onClick = () => onReactionClick(events)} {@const onClick = () => onReactionClick(events)}
<button <button
+2 -2
View File
@@ -5,7 +5,7 @@
import {tryCatch} from "@welshman/lib" import {tryCatch} from "@welshman/lib"
import {isShareableRelayUrl, isIPAddress, normalizeRelayUrl} from "@welshman/util" import {isShareableRelayUrl, isIPAddress, normalizeRelayUrl} from "@welshman/util"
import type {Thunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import {waitForThunkError, relaySearch} from "@welshman/app" import {relaySearch} from "@app/welshman"
import {createScroller} from "@lib/html" import {createScroller} from "@lib/html"
import {errorMessage} from "@lib/util" import {errorMessage} from "@lib/util"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
@@ -34,7 +34,7 @@
loading.add(url) loading.add(url)
try { try {
const error = await waitForThunkError(await addRelay(url)) const error = await (await addRelay(url)).waitForError()
if (error) { if (error) {
pushToast({ pushToast({
+3 -2
View File
@@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import {deriveRelay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
const {...props} = $props() const {...props} = $props()
const relay = deriveRelay(props.url) const relay = app.use(Relays).one(props.url)
</script> </script>
{#if $relay?.description} {#if $relay?.description}
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveRelay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl" import RemoteControllerMinimalistic from "@assets/icons/remote-controller-minimalistic.svg?dataurl"
import ImageIcon from "@lib/components/ImageIcon.svelte" import ImageIcon from "@lib/components/ImageIcon.svelte"
@@ -11,7 +12,7 @@
const {url, size = 7, ...props}: Props = $props() const {url, size = 7, ...props}: Props = $props()
const relay = deriveRelay(url) const relay = app.use(Relays).one(url)
</script> </script>
{#if $relay?.icon} {#if $relay?.icon}
+4 -3
View File
@@ -4,12 +4,13 @@
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import {displayUrl} from "@welshman/lib" import {displayUrl} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {deriveRelay, deriveRelayStats} from "@welshman/app" import {Relays, RelayStats} from "@welshman/app"
import {app} from "@app/welshman"
const {url, children} = $props() const {url, children} = $props()
const relay = deriveRelay(url) const relay = app.use(Relays).one(url)
const relayStats = deriveRelayStats(url) const relayStats = app.use(RelayStats).one(url)
const connections = $derived($relayStats?.open_count || 0) const connections = $derived($relayStats?.open_count || 0)
</script> </script>
+1 -2
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type {Readable} from "svelte/store" import type {Readable} from "svelte/store"
import {SvelteSet} from "svelte/reactivity" import {SvelteSet} from "svelte/reactivity"
import {waitForThunkError} from "@welshman/app"
import type {Thunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
@@ -37,7 +36,7 @@
loading.add(url) loading.add(url)
try { try {
const error = await waitForThunkError(await removeRelay(url)) const error = await (await removeRelay(url)).waitForError()
if (error) { if (error) {
pushToast({ pushToast({
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveRelayDisplay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
type Props = { type Props = {
url: string url: string
@@ -8,7 +9,7 @@
const {url, ...props}: Props = $props() const {url, ...props}: Props = $props()
const display = $derived(deriveRelayDisplay(url)) const display = $derived(app.use(Relays).display(url).$)
</script> </script>
<span class={props.class}> <span class={props.class}>
+1 -1
View File
@@ -2,7 +2,7 @@
import {REPORT} from "@welshman/util" import {REPORT} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {deriveEventsById} from "@welshman/store" import {deriveEventsById} from "@welshman/store"
import {repository} from "@welshman/app" import {repository} from "@app/welshman"
import ModalHeader from "@lib/components/ModalHeader.svelte" import ModalHeader from "@lib/components/ModalHeader.svelte"
import ModalTitle from "@lib/components/ModalTitle.svelte" import ModalTitle from "@lib/components/ModalTitle.svelte"
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte" import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
+6 -5
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {getTag, ManagementMethod} from "@welshman/util" import {getTag, ManagementMethod} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {pubkey, manageRelay, repository, displayProfileByPubkey} from "@welshman/app" import {RelayManagement, Profiles} from "@welshman/app"
import {pubkey, repository, app} from "@app/welshman"
import InboxOut from "@assets/icons/inbox-out.svg?dataurl" import InboxOut from "@assets/icons/inbox-out.svg?dataurl"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl" import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
@@ -45,7 +46,7 @@
} }
const dismissReport = async () => { const dismissReport = async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id, "Dismissed by admin"], params: [event.id, "Dismissed by admin"],
}) })
@@ -66,7 +67,7 @@
title: `Remove Content`, title: `Remove Content`,
message: `Are you sure you want to delete this content from the space?`, message: `Are you sure you want to delete this content from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [id, reason], params: [id, reason],
}) })
@@ -89,9 +90,9 @@
pushModal(Confirm, { pushModal(Confirm, {
title: "Ban User", title: "Ban User",
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`, message: `Are you sure you want to ban @${app.use(Profiles).display(pubkey).get()} from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanPubkey, method: ManagementMethod.BanPubkey,
params: [pubkey, reason], params: [pubkey, reason],
}) })
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {displayProfileByPubkey} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import {slide} from "@lib/transition" import {slide} from "@lib/transition"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -21,7 +22,7 @@
<div <div
class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8" class="relative border-l-2 border-solid border-primary bg-base-300 px-2 py-1 pr-8"
transition:slide> transition:slide>
<p class="text-xs text-primary">{verb} @{displayProfileByPubkey(event.pubkey)}</p> <p class="text-xs text-primary">{verb} @{app.use(Profiles).display(event.pubkey).get()}</p>
{#key event.id} {#key event.id}
<NoteContentMinimal trimParent {event} /> <NoteContentMinimal trimParent {event} />
{/key} {/key}
+11 -9
View File
@@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import type {RoomMeta} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {displayRelayUrl, makeRoomMeta} from "@welshman/util"
import type {Thunk} from "@welshman/app" import type {Thunk} from "@welshman/app"
import {deleteRoom, waitForThunkError, repository, joinRoom, leaveRoom} from "@welshman/app" import {Rooms} from "@welshman/app"
import {repository, app} from "@app/welshman"
import Pen from "@assets/icons/pen.svg?dataurl" import Pen from "@assets/icons/pen.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl" import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Login3 from "@assets/icons/login-3.svg?dataurl" import Login3 from "@assets/icons/login-3.svg?dataurl"
@@ -71,11 +71,13 @@
const startEdit = () => pushModal(RoomEdit, {url, h}) const startEdit = () => pushModal(RoomEdit, {url, h})
const handleLoading = async (f: (url: string, room: RoomMeta) => Thunk) => { const handleLoading = async (
f: (url: string, room: {h: string}) => Promise<Thunk>,
) => {
loading = true loading = true
try { try {
const message = await waitForThunkError(f(url, makeRoomMeta({h}))) const message = await (await f(url, {h})).waitForError()
if (message && !message.startsWith("duplicate:")) { if (message && !message.startsWith("duplicate:")) {
pushToast({theme: "error", message}) pushToast({theme: "error", message})
@@ -85,9 +87,9 @@
} }
} }
const join = () => handleLoading(joinRoom) const join = () => handleLoading((url, room) => app.use(Rooms).join(url, room))
const leave = () => handleLoading(leaveRoom) const leave = () => handleLoading((url, room) => app.use(Rooms).leave(url, room))
const showMembers = () => pushModal(RoomMembers, {url, h}) const showMembers = () => pushModal(RoomMembers, {url, h})
@@ -109,8 +111,8 @@
message: message:
"This room will no longer be accessible to space members, and all messages posted to it will be deleted.", "This room will no longer be accessible to space members, and all messages posted to it will be deleted.",
confirm: async () => { confirm: async () => {
const thunk = deleteRoom(url, $room) const thunk = await app.use(Rooms).delete(url, $room)
const message = await waitForThunkError(thunk) const message = await thunk.waitForError()
if (message) { if (message) {
repository.removeEvent(thunk.event.id) repository.removeEvent(thunk.event.id)
+58 -6
View File
@@ -1,8 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import type {RoomMeta} from "@welshman/util" import {Rooms} from "@welshman/app"
import {makeRoomMeta} from "@welshman/util" import {app} from "@app/welshman"
import {waitForThunkError, createRoom, editRoom, joinRoom} from "@welshman/app"
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl" import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
import Hashtag from "@assets/icons/hashtag.svg?dataurl" import Hashtag from "@assets/icons/hashtag.svg?dataurl"
import Volume from "@assets/icons/volume.svg?dataurl" import Volume from "@assets/icons/volume.svg?dataurl"
@@ -19,6 +18,56 @@
import {deriveHasLivekit} from "@app/relays" import {deriveHasLivekit} from "@app/relays"
import {getRoomType, RoomType} from "@app/groups" import {getRoomType, RoomType} from "@app/groups"
// Plain mutable form object (the old @welshman/util `RoomMeta` plain-object
// type, removed in the migration). The new domain `RoomMeta` is an async
// method-accessor Reader, which doesn't fit a `$state` object bound to inputs,
// so we keep a plain object here and let `app.use(Rooms).create/edit/join`
// (which accept a plain object) build the events at submit time.
type RoomMeta = {
h: string
name?: string
about?: string
picture?: string
pictureMeta?: string[]
isClosed?: boolean
isHidden?: boolean
isPrivate?: boolean
isRestricted?: boolean
livekit?: boolean
}
// TODO(welshman-migration): reimplemented inline from the removed
// @welshman/util `generateH`/`makeRoomMeta` (room-id generator). Verify the
// generated id still matches the expected `^[a-z]+[1-9]$` shape and that no
// shared generator should be used instead.
const vowels = "a,e,i,o,u,ay,ey,oy,ou,ia,ea,ough,oo,ee,argh".split(",")
const consonants =
"p,b,t,d,k,g,ch,sh,th,f,v,s,z,l,r,m,n,pl,bl,cl,gl,pr,br,tr,dr,kr,gr,fl,sl,fr,thr,str,sk,sp,st".split(
",",
)
const generateH = () => {
const n = (6 + Math.random() * 2) | 0
const s = [consonants.slice(), vowels.slice()]
if (Math.random() < 0.5) {
s.reverse()
}
return (
Array.from({length: n}, (_, i) =>
s[i % 2].splice((Math.random() * s[i % 2].length) | 0, 1),
).join("") +
(1 + Math.floor(Math.random() * 9))
)
}
const makeRoomMeta = (room: Partial<RoomMeta> = {}): RoomMeta => ({
h: room.h ?? generateH(),
...room,
})
type Props = { type Props = {
url: string url: string
header: Snippet header: Snippet
@@ -58,19 +107,22 @@
room.pictureMeta = result.tags room.pictureMeta = result.tags
} }
const createMessage = await waitForThunkError(createRoom(url, room)) // TODO(welshman-migration): app.use(Rooms).create/edit/join are async
// (return Promise<Thunk>); the old code passed the un-awaited result to
// waitForThunkError. Awaiting the thunk first before .waitForError().
const createMessage = await (await app.use(Rooms).create(url, room)).waitForError()
if (createMessage && !createMessage.includes("already")) { if (createMessage && !createMessage.includes("already")) {
return pushToast({theme: "error", message: createMessage}) return pushToast({theme: "error", message: createMessage})
} }
const editMessage = await waitForThunkError(editRoom(url, room)) const editMessage = await (await app.use(Rooms).edit(url, room)).waitForError()
if (editMessage) { if (editMessage) {
return pushToast({theme: "error", message: editMessage}) return pushToast({theme: "error", message: editMessage})
} }
const joinMessage = await waitForThunkError(joinRoom(url, room)) const joinMessage = await (await app.use(Rooms).join(url, room)).waitForError()
if (joinMessage && !joinMessage.includes("already")) { if (joinMessage && !joinMessage.includes("already")) {
return pushToast({theme: "error", message: joinMessage}) return pushToast({theme: "error", message: joinMessage})
+5 -10
View File
@@ -11,13 +11,8 @@
} from "@welshman/lib" } from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {MESSAGE, COMMENT, getTag} from "@welshman/util" import {MESSAGE, COMMENT, getTag} from "@welshman/util"
import { import {Thunks, Profiles} from "@welshman/app"
thunks, import {thunks, pubkey, app} from "@app/welshman"
pubkey,
mergeThunks,
deriveProfileDisplay,
displayProfileByPubkey,
} from "@welshman/app"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import Pen from "@assets/icons/pen.svg?dataurl" import Pen from "@assets/icons/pen.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
@@ -58,8 +53,8 @@
const path = getRoomItemPath(url, event) const path = getRoomItemPath(url, event)
const shouldProtect = canEnforceNip70(url) const shouldProtect = canEnforceNip70(url)
const today = formatTimestampAsDate(now()) const today = formatTimestampAsDate(now())
const profileDisplay = deriveProfileDisplay(event.pubkey, [url]) const profileDisplay = app.use(Profiles).display(event.pubkey, [url]).$
const thunk = mergeThunks($thunks.filter(t => t.event.id === event.id)) const thunk = app.use(Thunks).merge($thunks.filter(t => t.event.id === event.id))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length] const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
const qTag = getTag("q", event.tags) const qTag = getTag("q", event.tags)
@@ -138,7 +133,7 @@
{#if path && $innerComments.length > 0} {#if path && $innerComments.length > 0}
{@const pubkeys = $innerComments.map(e => e.pubkey)} {@const pubkeys = $innerComments.map(e => e.pubkey)}
{@const isOwn = $pubkey && pubkeys.includes($pubkey)} {@const isOwn = $pubkey && pubkeys.includes($pubkey)}
{@const info = displayList(pubkeys.map(pubkey => displayProfileByPubkey(pubkey)))} {@const info = displayList(pubkeys.map(pubkey => app.use(Profiles).display(pubkey).get()))}
{@const tooltip = `${info} commented`} {@const tooltip = `${info} commented`}
<div data-tip={tooltip} class="tooltip tooltip-right flex"> <div data-tip={tooltip} class="tooltip tooltip-right flex">
<Link <Link
+3 -2
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {ManagementMethod} from "@welshman/util" import {ManagementMethod} from "@welshman/util"
import {pubkey, manageRelay, repository} from "@welshman/app" import {RelayManagement} from "@welshman/app"
import {pubkey, repository, app} from "@app/welshman"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl" import TrashBin2 from "@assets/icons/trash-bin-2.svg?dataurl"
import Danger from "@assets/icons/danger.svg?dataurl" import Danger from "@assets/icons/danger.svg?dataurl"
@@ -45,7 +46,7 @@
title: `Delete Message`, title: `Delete Message`,
message: `Are you sure you want to delete this message from the space?`, message: `Are you sure you want to delete this message from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id], params: [event.id],
}) })
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {pubkey} from "@welshman/app" import {pubkey} from "@app/welshman"
import Bolt from "@assets/icons/bolt.svg?dataurl" import Bolt from "@assets/icons/bolt.svg?dataurl"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl" import Code2 from "@assets/icons/code-2.svg?dataurl"
+9 -4
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {getTagValue, ManagementMethod} from "@welshman/util" import {getTagValue, ManagementMethod} from "@welshman/util"
import type {TrustedEvent, PublishedRoomMeta} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {repository, manageRelay} from "@welshman/app" import {RelayManagement} from "@welshman/app"
import {app, repository} from "@app/welshman"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import ProfileName from "@app/components/ProfileName.svelte" import ProfileName from "@app/components/ProfileName.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte" import ProfileDetail from "@app/components/ProfileDetail.svelte"
@@ -28,7 +29,7 @@
loading = true loading = true
try { try {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanEvent, method: ManagementMethod.BanEvent,
params: [event.id, "Join request dismissed"], params: [event.id, "Join request dismissed"],
}) })
@@ -49,7 +50,11 @@
loading = true loading = true
try { try {
const error = await addRoomMembers(url, $room as PublishedRoomMeta, [event.pubkey]) // TODO(welshman-migration): addRoomMembers now expects a RoomMeta domain
// Reader, but deriveRoom yields the plain `Room` object. Passing it through
// as `any` preserves prior runtime behavior; verify Rooms.addMember accepts
// the plain Room shape (url/h/...) or resolve a real RoomMeta reader here.
const error = await addRoomMembers(url, $room as any, [event.pubkey])
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
+4 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {waitForThunkError, removeRoomMember} from "@welshman/app" import {Rooms} from "@welshman/app"
import {app} from "@app/welshman"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl" import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl" import AddCircle from "@assets/icons/add-circle.svg?dataurl"
@@ -51,7 +52,8 @@
title: "Remove Member", title: "Remove Member",
message: "Are you sure you want to remove this user from the room?", message: "Are you sure you want to remove this user from the room?",
confirm: async () => { confirm: async () => {
const error = await waitForThunkError(removeRoomMember(url, $room, pubkey)) const thunk = await app.use(Rooms).removeMember(url, $room, pubkey)
const error = await thunk.waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
+8 -4
View File
@@ -2,8 +2,8 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {setKey, popKey} from "@lib/implicit" import {setKey, popKey} from "@lib/implicit"
import {sleep} from "@welshman/lib" import {sleep} from "@welshman/lib"
import {displayProfileByPubkey} from "@welshman/app" import {Profiles} from "@welshman/app"
import type {PublishedRoomMeta} from "@welshman/util" import {app} from "@app/welshman"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
@@ -42,7 +42,11 @@
// Show loading for auto submit callback // Show loading for auto submit callback
await sleep(500) await sleep(500)
const error = await addRoomMembers(url, $room as PublishedRoomMeta, pubkeys) // TODO(welshman-migration): addRoomMembers now expects a RoomMeta domain
// Reader, but deriveRoom yields the plain `Room` object. Passing it through
// as `any` preserves prior runtime behavior; verify Rooms.addMember accepts
// the plain Room shape (url/h/...) or resolve a real RoomMeta reader here.
const error = await addRoomMembers(url, $room as any, pubkeys)
if (error) { if (error) {
pushToast({theme: "error", message: error}) pushToast({theme: "error", message: error})
@@ -72,7 +76,7 @@
subtitle: "Automatically add members to space", subtitle: "Automatically add members to space",
message: message:
nonSpaceMembers.length === 1 nonSpaceMembers.length === 1
? `${displayProfileByPubkey(nonSpaceMembers[0])} is not a member of this space. Add them?` ? `${app.use(Profiles).display(nonSpaceMembers[0]).get()} is not a member of this space. Add them?`
: `${nonSpaceMembers.length} people are not members of this space. Add them?`, : `${nonSpaceMembers.length} people are not members of this space. Add them?`,
confirm: async () => { confirm: async () => {
setKey("RoomMembersAdd.confirm", true) setKey("RoomMembersAdd.confirm", true)
+10 -8
View File
@@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import type {ClientOptions} from "@pomade/core" import type {ClientOptions} from "@pomade/core"
import type {Profile} from "@welshman/util" import type {Profile} from "@welshman/domain"
import {makeProfile, makeSecret, RELAYS, MESSAGING_RELAYS, makeEvent} from "@welshman/util" import {makeSecret, RELAYS, MESSAGING_RELAYS, makeEvent} from "@welshman/util"
import {loginWithNip01, publishThunk} from "@welshman/app" import {ProfileBuilder} from "@welshman/domain"
import {Thunks} from "@welshman/app"
import {app, loginWithNip01} from "@app/welshman"
import Key from "@assets/icons/key-minimalistic.svg?dataurl" import Key from "@assets/icons/key-minimalistic.svg?dataurl"
import Letter from "@assets/icons/letter.svg?dataurl" import Letter from "@assets/icons/letter.svg?dataurl"
import {getKey, setKey} from "@lib/implicit" import {getKey, setKey} from "@lib/implicit"
@@ -31,7 +33,7 @@
setKey("signup.email", "") setKey("signup.email", "")
setKey("signup.secret", makeSecret()) setKey("signup.secret", makeSecret())
setKey("signup.profile", makeProfile()) setKey("signup.profile", new ProfileBuilder().values)
setKey("signup.clientOptions", undefined) setKey("signup.clientOptions", undefined)
const hasPomade = POMADE_SIGNERS.length >= 3 const hasPomade = POMADE_SIGNERS.length >= 3
@@ -40,13 +42,13 @@
const completeSignup = () => { const completeSignup = () => {
// Add default outbox/inbox relays // Add default outbox/inbox relays
publishThunk({ app.use(Thunks).publish({
event: makeEvent(RELAYS, {tags: DEFAULT_RELAYS.map(url => ["r", url])}), event: makeEvent(RELAYS, {tags: DEFAULT_RELAYS.map(url => ["r", url])}),
relays: [...INDEXER_RELAYS, ...DEFAULT_RELAYS], relays: [...INDEXER_RELAYS, ...DEFAULT_RELAYS],
}) })
// Add default messaging relays // Add default messaging relays
publishThunk({ app.use(Thunks).publish({
event: makeEvent(MESSAGING_RELAYS, {tags: DEFAULT_MESSAGING_RELAYS.map(url => ["r", url])}), event: makeEvent(MESSAGING_RELAYS, {tags: DEFAULT_MESSAGING_RELAYS.map(url => ["r", url])}),
relays: DEFAULT_RELAYS, relays: DEFAULT_RELAYS,
}) })
@@ -83,10 +85,10 @@
key: () => pushModal(SignUpKey, {next: flows.nostr.complete, step: 2, totalSteps: 3}), key: () => pushModal(SignUpKey, {next: flows.nostr.complete, step: 2, totalSteps: 3}),
complete: () => complete: () =>
pushModal(SignUpComplete, {next: flows.nostr.finalize, step: 3, totalSteps: 3}), pushModal(SignUpComplete, {next: flows.nostr.finalize, step: 3, totalSteps: 3}),
finalize: () => { finalize: async () => {
const secret = getKey<string>("signup.secret")! const secret = getKey<string>("signup.secret")!
loginWithNip01(secret) await loginWithNip01(secret)
completeSignup() completeSignup()
}, },
}, },
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {spec, avg} from "@welshman/lib" import {spec, avg} from "@welshman/lib"
import {session, SessionMethod, signerLog} from "@welshman/app" import {session, SessionMethod, signerLog} from "@app/welshman"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import Danger from "@assets/icons/danger-triangle.svg?dataurl" import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import ClockCircle from "@assets/icons/clock-circle.svg?dataurl" import ClockCircle from "@assets/icons/clock-circle.svg?dataurl"
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import {deriveRelay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
import UserRounded from "@assets/icons/user-rounded.svg?dataurl" import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
import Pen from "@assets/icons/pen.svg?dataurl" import Pen from "@assets/icons/pen.svg?dataurl"
@@ -26,7 +27,7 @@
} }
const {url}: Props = $props() const {url}: Props = $props()
const relay = deriveRelay(url) const relay = app.use(Relays).one(url)
const owner = $derived($relay?.pubkey) const owner = $derived($relay?.pubkey)
const userIsAdmin = deriveUserIsSpaceAdmin(url) const userIsAdmin = deriveUserIsSpaceAdmin(url)
+6 -5
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import type {RelayProfile} from "@welshman/util" import type {RelayProfile} from "@welshman/util"
import {displayRelayUrl, ManagementMethod} from "@welshman/util" import {displayRelayUrl, ManagementMethod} from "@welshman/util"
import {manageRelay, forceLoadRelay} from "@welshman/app" import {RelayManagement, Relays} from "@welshman/app"
import {app} from "@app/welshman"
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl" import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
import Widget from "@assets/icons/widget-4.svg?dataurl" import Widget from "@assets/icons/widget-4.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
@@ -36,7 +37,7 @@
const submit = async () => { const submit = async () => {
if (values.name != initialValues.name) { if (values.name != initialValues.name) {
const res = await manageRelay(url, { const res = await app.use(RelayManagement).post(url, {
method: ManagementMethod.ChangeRelayName, method: ManagementMethod.ChangeRelayName,
params: [values.name || ""], params: [values.name || ""],
}) })
@@ -47,7 +48,7 @@
} }
if (values.description != initialValues.description) { if (values.description != initialValues.description) {
const res = await manageRelay(url, { const res = await app.use(RelayManagement).post(url, {
method: ManagementMethod.ChangeRelayDescription, method: ManagementMethod.ChangeRelayDescription,
params: [values.description || ""], params: [values.description || ""],
}) })
@@ -64,7 +65,7 @@
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
} }
const res = await manageRelay(url, { const res = await app.use(RelayManagement).post(url, {
method: ManagementMethod.ChangeRelayIcon, method: ManagementMethod.ChangeRelayIcon,
params: [result.url], params: [result.url],
}) })
@@ -75,7 +76,7 @@
} }
pushToast({message: "Your changes have been saved!"}) pushToast({message: "Your changes have been saved!"})
forceLoadRelay(url) app.use(Relays).forceLoad(url)
clearModals() clearModals()
} }
+6 -5
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {ManagementMethod} from "@welshman/util" import {ManagementMethod} from "@welshman/util"
import {manageRelay, displayProfileByPubkey} from "@welshman/app" import {RelayManagement, Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import UserMinus from "@assets/icons/user-minus.svg?dataurl" import UserMinus from "@assets/icons/user-minus.svg?dataurl"
import MinusCircle from "@assets/icons/minus-circle.svg?dataurl" import MinusCircle from "@assets/icons/minus-circle.svg?dataurl"
@@ -60,9 +61,9 @@
const unallowMember = (pubkey: string) => const unallowMember = (pubkey: string) =>
pushModal(Confirm, { pushModal(Confirm, {
title: "Remove User", title: "Remove User",
message: `Are you sure you want to remove @${displayProfileByPubkey(pubkey)} from the space?`, message: `Are you sure you want to remove @${app.use(Profiles).display(pubkey).get()} from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.UnallowPubkey, method: ManagementMethod.UnallowPubkey,
params: [pubkey], params: [pubkey],
}) })
@@ -79,9 +80,9 @@
const banMember = (pubkey: string) => const banMember = (pubkey: string) =>
pushModal(Confirm, { pushModal(Confirm, {
title: "Ban User", title: "Ban User",
message: `Are you sure you want to ban @${displayProfileByPubkey(pubkey)} from the space?`, message: `Are you sure you want to ban @${app.use(Profiles).display(pubkey).get()} from the space?`,
confirm: async () => { confirm: async () => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.BanPubkey, method: ManagementMethod.BanPubkey,
params: [pubkey], params: [pubkey],
}) })
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import {displayRelayUrl, ManagementMethod} from "@welshman/util" import {displayRelayUrl, ManagementMethod} from "@welshman/util"
import {manageRelay} from "@welshman/app" import {RelayManagement} from "@welshman/app"
import {app} from "@app/welshman"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Restart from "@assets/icons/restart.svg?dataurl" import Restart from "@assets/icons/restart.svg?dataurl"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
@@ -42,7 +43,7 @@
} }
const unbanMember = async (pubkey: string) => { const unbanMember = async (pubkey: string) => {
const {error} = await manageRelay(url, { const {error} = await app.use(RelayManagement).post(url, {
method: ManagementMethod.UnbanPubkey, method: ManagementMethod.UnbanPubkey,
params: [pubkey], params: [pubkey],
}) })
+3 -2
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {derived} from "svelte/store" import {derived} from "svelte/store"
import {displayRelayUrl, EVENT_TIME, ZAP_GOAL, THREAD, CLASSIFIED, POLL} from "@welshman/util" import {displayRelayUrl, EVENT_TIME, ZAP_GOAL, THREAD, CLASSIFIED, POLL} from "@welshman/util"
import {deriveRelay, createSearch, pubkey} from "@welshman/app" import {Relays} from "@welshman/app"
import {app, createSearch, pubkey} from "@app/welshman"
import {fly} from "@lib/transition" import {fly} from "@lib/transition"
import Magnifier from "@assets/icons/magnifier.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl"
import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl" import AltArrowDown from "@assets/icons/alt-arrow-down.svg?dataurl"
@@ -60,7 +61,7 @@
const {url} = $props() const {url} = $props()
const relay = deriveRelay(url) const relay = app.use(Relays).one(url)
const chatPath = makeSpacePath(url, "chat") const chatPath = makeSpacePath(url, "chat")
const goalsPath = makeSpacePath(url, "goals") const goalsPath = makeSpacePath(url, "goals")
const threadsPath = makeSpacePath(url, "threads") const threadsPath = makeSpacePath(url, "threads")
+3 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import {deriveRelay} from "@welshman/app" import {Relays} from "@welshman/app"
import {app} from "@app/welshman"
import Server from "@assets/icons/server.svg?dataurl" import Server from "@assets/icons/server.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import ProfileLink from "@app/components/ProfileLink.svelte" import ProfileLink from "@app/components/ProfileLink.svelte"
@@ -11,7 +12,7 @@
const {url}: Props = $props() const {url}: Props = $props()
const relay = deriveRelay(url) const relay = app.use(Relays).one(url)
</script> </script>
<div class="card2 bg-alt flex flex-col gap-4"> <div class="card2 bg-alt flex flex-col gap-4">
+1 -1
View File
@@ -2,7 +2,7 @@
import {tick} from "svelte" import {tick} from "svelte"
import {debounce} from "throttle-debounce" import {debounce} from "throttle-debounce"
import {request} from "@welshman/net" import {request} from "@welshman/net"
import {repository, tracker} from "@welshman/app" import {repository, tracker} from "@app/welshman"
import {formatTimestampAsDate, groupBy, uniqBy, now, MINUTE, HOUR, DAY, WEEK} from "@welshman/lib" import {formatTimestampAsDate, groupBy, uniqBy, now, MINUTE, HOUR, DAY, WEEK} from "@welshman/lib"
import type {TrustedEvent, Filter} from "@welshman/util" import type {TrustedEvent, Filter} from "@welshman/util"
import {MESSAGE, sortEventsDesc} from "@welshman/util" import {MESSAGE, sortEventsDesc} from "@welshman/util"
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import {writable} from "svelte/store" import {writable} from "svelte/store"
import {makeEvent, THREAD} from "@welshman/util" import {makeEvent, THREAD} from "@welshman/util"
import {publishThunk, waitForThunkError} from "@welshman/app" import {Thunks} from "@welshman/app"
import {app} from "@app/welshman"
import {isMobile, preventDefault} from "@lib/html" import {isMobile, preventDefault} from "@lib/html"
import Paperclip from "@assets/icons/paperclip-2.svg?dataurl" import Paperclip from "@assets/icons/paperclip-2.svg?dataurl"
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl" import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
@@ -79,12 +80,12 @@
tags.push(["h", h]) tags.push(["h", h])
} }
const threadThunk = publishThunk({ const threadThunk = app.use(Thunks).publish({
relays: [url], relays: [url],
event: makeEvent(THREAD, {content, tags}), event: makeEvent(THREAD, {content, tags}),
}) })
const error = await waitForThunkError(threadThunk) const error = await threadThunk.waitForError()
if (error) { if (error) {
return pushToast({theme: "error", message: error}) return pushToast({theme: "error", message: error})
+6 -5
View File
@@ -1,8 +1,9 @@
<script lang="ts"> <script lang="ts">
import {formatTimestamp} from "@welshman/lib" import {formatTimestamp} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {COMMENT} from "@welshman/util" import {COMMENT, displayNip05} from "@welshman/util"
import {deriveHandleForPubkey, deriveProfileDisplay, displayHandle} from "@welshman/app" import {Handles, Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import Reply from "@assets/icons/reply-2.svg?dataurl" import Reply from "@assets/icons/reply-2.svg?dataurl"
import LinkRound from "@assets/icons/link-round.svg?dataurl" import LinkRound from "@assets/icons/link-round.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@@ -26,8 +27,8 @@
const {url, event, threadPubkey, onReply}: Props = $props() const {url, event, threadPubkey, onReply}: Props = $props()
const profileDisplay = deriveProfileDisplay(event.pubkey, [url]) const profileDisplay = app.use(Profiles).display(event.pubkey, [url]).$
const handle = deriveHandleForPubkey(event.pubkey) const handle = app.use(Handles).forPubkey(event.pubkey).$
const isOp = event.pubkey === threadPubkey const isOp = event.pubkey === threadPubkey
const isComment = event.kind === COMMENT const isComment = event.kind === COMMENT
@@ -53,7 +54,7 @@
{$profileDisplay} {$profileDisplay}
</Button> </Button>
{#if $handle} {#if $handle}
<span class="ellipsize text-xs opacity-75">{displayHandle($handle)}</span> <span class="ellipsize text-xs opacity-75">{displayNip05($handle?.nip05)}</span>
{/if} {/if}
{#if isOp} {#if isOp}
<span class="badge badge-primary badge-sm">OP</span> <span class="badge badge-primary badge-sm">OP</span>
+5 -4
View File
@@ -2,7 +2,8 @@
import {stopPropagation} from "svelte/legacy" import {stopPropagation} from "svelte/legacy"
import {noop} from "@welshman/lib" import {noop} from "@welshman/lib"
import type {AbstractThunk} from "@welshman/app" import type {AbstractThunk} from "@welshman/app"
import {flattenThunks, getFailedThunkUrls, publishThunk, thunkIsComplete} from "@welshman/app" import {Thunks} from "@welshman/app"
import {app} from "@app/welshman"
import Danger from "@assets/icons/danger-triangle.svg?dataurl" import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte" import Tippy from "@lib/components/Tippy.svelte"
@@ -18,15 +19,15 @@
const {thunk, showToastOnRetry, ...restProps}: Props = $props() const {thunk, showToastOnRetry, ...restProps}: Props = $props()
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0) const showFailure = $derived($thunk.isComplete() && $thunk.getFailedUrls().length > 0)
const retry = (url: string) => { const retry = (url: string) => {
for (const child of flattenThunks([thunk])) { for (const child of app.use(Thunks).flatten([thunk])) {
if (!child.options.relays.includes(url)) { if (!child.options.relays.includes(url)) {
continue continue
} }
const retried = publishThunk({...child.options, relays: [url]}) const retried = app.use(Thunks).publish({...child.options, relays: [url]})
if (showToastOnRetry) { if (showToastOnRetry) {
pushToast({ pushToast({
+2 -3
View File
@@ -2,7 +2,6 @@
import {stopPropagation} from "svelte/legacy" import {stopPropagation} from "svelte/legacy"
import {PublishStatus} from "@welshman/net" import {PublishStatus} from "@welshman/net"
import type {AbstractThunk} from "@welshman/app" import type {AbstractThunk} from "@welshman/app"
import {abortThunk, thunkHasStatus} from "@welshman/app"
interface Props { interface Props {
thunk: AbstractThunk thunk: AbstractThunk
@@ -11,9 +10,9 @@
const {thunk, ...restProps}: Props = $props() const {thunk, ...restProps}: Props = $props()
const abort = () => abortThunk(thunk) const abort = () => thunk.abort()
const isSending = $derived(thunkHasStatus(PublishStatus.Sending, $thunk)) const isSending = $derived($thunk.hasStatus(PublishStatus.Sending))
</script> </script>
<div class="flex w-full justify-end px-1 text-xs {restProps.class}"> <div class="flex w-full justify-end px-1 text-xs {restProps.class}">
+2 -3
View File
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import type {AbstractThunk} from "@welshman/app" import type {AbstractThunk} from "@welshman/app"
import {thunkIsComplete, getFailedThunkUrls} from "@welshman/app"
import ThunkFailure from "@app/components/ThunkFailure.svelte" import ThunkFailure from "@app/components/ThunkFailure.svelte"
import ThunkPending from "@app/components/ThunkPending.svelte" import ThunkPending from "@app/components/ThunkPending.svelte"
@@ -12,8 +11,8 @@
const {thunk, showToastOnRetry, ...restProps}: Props = $props() const {thunk, showToastOnRetry, ...restProps}: Props = $props()
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0) const showFailure = $derived($thunk.isComplete() && $thunk.getFailedUrls().length > 0)
const showPending = $derived(!thunkIsComplete($thunk)) const showPending = $derived(!$thunk.isComplete())
</script> </script>
{#if showFailure} {#if showFailure}
+2 -3
View File
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import {stopPropagation} from "svelte/legacy" import {stopPropagation} from "svelte/legacy"
import type {AbstractThunk} from "@welshman/app" import type {AbstractThunk} from "@welshman/app"
import {getFailedThunkUrls, getThunkUrlsWithStatus} from "@welshman/app"
import {PublishStatus} from "@welshman/net" import {PublishStatus} from "@welshman/net"
import {displayRelayUrl} from "@welshman/util" import {displayRelayUrl} from "@welshman/util"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl" import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
@@ -17,8 +16,8 @@
const {thunk, retry}: Props = $props() const {thunk, retry}: Props = $props()
const successUrls = $derived(getThunkUrlsWithStatus(PublishStatus.Success, $thunk)) const successUrls = $derived($thunk.getUrlsWithStatus(PublishStatus.Success))
const failedUrls = $derived(getFailedThunkUrls($thunk)) const failedUrls = $derived($thunk.getFailedUrls())
const total = $derived(successUrls.length + failedUrls.length) const total = $derived(successUrls.length + failedUrls.length)
const isPartial = $derived(successUrls.length > 0 && failedUrls.length > 0) const isPartial = $derived(successUrls.length > 0 && failedUrls.length > 0)
@@ -3,7 +3,8 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {PublishStatus} from "@welshman/net" import {PublishStatus} from "@welshman/net"
import {deriveIsDeleted} from "@welshman/store" import {deriveIsDeleted} from "@welshman/store"
import {thunks, mergeThunks, thunkHasStatus, repository} from "@welshman/app" import {Thunks} from "@welshman/app"
import {app, thunks, repository} from "@app/welshman"
import ThunkStatus from "@app/components/ThunkStatus.svelte" import ThunkStatus from "@app/components/ThunkStatus.svelte"
type Props = { type Props = {
@@ -14,12 +15,12 @@
const {event, children}: Props = $props() const {event, children}: Props = $props()
const deleted = deriveIsDeleted(repository, event) const deleted = deriveIsDeleted(repository, event)
const thunk = $derived(mergeThunks($thunks.filter(t => t.event.id === event.id))) const thunk = $derived(app.use(Thunks).merge($thunks.filter(t => t.event.id === event.id)))
</script> </script>
{#if $deleted} {#if $deleted}
<div class="btn btn-error btn-xs rounded-full">Deleted</div> <div class="btn btn-error btn-xs rounded-full">Deleted</div>
{:else if thunk.thunks.length > 0 && !thunkHasStatus(PublishStatus.Success, thunk)} {:else if thunk.thunks.length > 0 && !thunk.hasStatus(PublishStatus.Success)}
<ThunkStatus {thunk} /> <ThunkStatus {thunk} />
{:else if children} {:else if children}
{@render children?.()} {@render children?.()}
+2 -3
View File
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import type {AbstractThunk} from "@welshman/app" import type {AbstractThunk} from "@welshman/app"
import {thunkHasStatus, thunkIsComplete} from "@welshman/app"
import {PublishStatus} from "@welshman/net" import {PublishStatus} from "@welshman/net"
import ThunkPending from "@app/components/ThunkPending.svelte" import ThunkPending from "@app/components/ThunkPending.svelte"
import type {Toast} from "@app/toast" import type {Toast} from "@app/toast"
@@ -16,8 +15,8 @@
const id = toast.id const id = toast.id
const thunk = props.thunk const thunk = props.thunk
const {Aborted, Timeout, Failure} = PublishStatus const {Aborted, Timeout, Failure} = PublishStatus
const isFailure = $derived(thunkHasStatus([Aborted, Timeout, Failure], $thunk)) const isFailure = $derived($thunk.hasStatus([Aborted, Timeout, Failure]))
const isComplete = $derived(thunkIsComplete($thunk)) const isComplete = $derived($thunk.isComplete())
$effect(() => { $effect(() => {
if (isFailure) { if (isFailure) {
+1 -1
View File
@@ -3,7 +3,7 @@
import type {Writable} from "svelte/store" import type {Writable} from "svelte/store"
import type {Instance} from "tippy.js" import type {Instance} from "tippy.js"
import {remove, reject, spec, uniq} from "@welshman/lib" import {remove, reject, spec, uniq} from "@welshman/lib"
import {createSearch, topics} from "@welshman/app" import {createSearch, topics} from "@app/welshman"
import {normalizeTopic} from "@lib/util" import {normalizeTopic} from "@lib/util"
import Suggestions from "@lib/components/Suggestions.svelte" import Suggestions from "@lib/components/Suggestions.svelte"
import CloseCircle from "@assets/icons/close-circle.svg?dataurl" import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {Track} from "livekit-client" import {Track} from "livekit-client"
import {displayProfileByPubkey, loadProfile} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import Pin from "@assets/icons/pin.svg?dataurl" import Pin from "@assets/icons/pin.svg?dataurl"
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"
@@ -184,13 +185,13 @@
$effect(() => { $effect(() => {
for (const t of videoTiles) { for (const t of videoTiles) {
const pk = pubkeyFromLiveKitIdentity(t.identity) const pk = pubkeyFromLiveKitIdentity(t.identity)
if (pk) loadProfile(pk) if (pk) app.use(Profiles).load(pk)
} }
}) })
const labelFor = (identity: string, source: VideoTileData["source"]) => { const labelFor = (identity: string, source: VideoTileData["source"]) => {
const pk = pubkeyFromLiveKitIdentity(identity) const pk = pubkeyFromLiveKitIdentity(identity)
const name = pk ? displayProfileByPubkey(pk) : "Unknown" const name = pk ? app.use(Profiles).display(pk).get() : "Unknown"
return source === Track.Source.ScreenShare ? `${name} · screen` : name return source === Track.Source.ScreenShare ? `${name} · screen` : name
} }
+4 -3
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {goto} from "$app/navigation" import {goto} from "$app/navigation"
import {loadProfile, displayProfileByPubkey} from "@welshman/app" import {Profiles} from "@welshman/app"
import {app} from "@app/welshman"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte" import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte"
import ProfileCircles from "@app/components/ProfileCircles.svelte" import ProfileCircles from "@app/components/ProfileCircles.svelte"
@@ -65,7 +66,7 @@
$effect(() => { $effect(() => {
for (const p of $participants) { for (const p of $participants) {
if (p.pubkey) loadProfile(p.pubkey) if (p.pubkey) app.use(Profiles).load(p.pubkey)
} }
}) })
</script> </script>
@@ -98,7 +99,7 @@
<ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" /> <ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" />
</div> </div>
<span class="ellipsize min-w-0 flex-1 text-xs opacity-70"> <span class="ellipsize min-w-0 flex-1 text-xs opacity-70">
{p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"} {p.pubkey ? app.use(Profiles).display(p.pubkey).get() : "Unknown"}
</span> </span>
<VoiceParticipantMediaBadges <VoiceParticipantMediaBadges
muted={media.muted} muted={media.muted}
@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import {makeProfile} from "@welshman/util"
import {getWalletAddress} from "@welshman/util" import {getWalletAddress} from "@welshman/util"
import {userProfile, waitForThunkError, session} from "@welshman/app" import {userProfile, session} from "@app/welshman"
import {errorMessage} from "@lib/util" import {errorMessage} from "@lib/util"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte" import Spinner from "@lib/components/Spinner.svelte"
@@ -17,12 +16,14 @@
const lud16 = getWalletAddress($session!.wallet!) const lud16 = getWalletAddress($session!.wallet!)
const confirm = async () => { const confirm = async () => {
const profile = $userProfile || makeProfile() // $userProfile is now a Profile Reader; its raw metadata lives on `.values`.
const profile = {...($userProfile?.values ?? {}), lud16}
loading = true loading = true
try { try {
const error = await waitForThunkError(updateProfile({profile: {...profile, lud16}})) const thunk = await updateProfile({profile})
const error = await thunk.waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: `Failed to update profile: ${errorMessage(error)}`}) pushToast({theme: "error", message: `Failed to update profile: ${errorMessage(error)}`})
@@ -46,7 +47,7 @@
<ModalHeader> <ModalHeader>
<ModalTitle>Set as Receiving Address?</ModalTitle> <ModalTitle>Set as Receiving Address?</ModalTitle>
</ModalHeader> </ModalHeader>
{#if $userProfile?.lud16} {#if $userProfile?.values?.lud16}
<p> <p>
Your current receiving address is different from the one provided by your connected wallet. Your current receiving address is different from the one provided by your connected wallet.
</p> </p>
+2 -2
View File
@@ -3,7 +3,7 @@
import {nwc} from "@getalby/sdk" import {nwc} from "@getalby/sdk"
import {sleep, assoc} from "@welshman/lib" import {sleep, assoc} from "@welshman/lib"
import type {NWCInfo} from "@welshman/util" import type {NWCInfo} from "@welshman/util"
import {pubkey, userProfile, updateSession} from "@welshman/app" import {pubkey, userProfile, updateSession} from "@app/welshman"
import Link from "@lib/components/Link.svelte" import Link from "@lib/components/Link.svelte"
import Cpu from "@assets/icons/cpu-bolt.svg?dataurl" import Cpu from "@assets/icons/cpu-bolt.svg?dataurl"
import Lock from "@assets/icons/lock-keyhole.svg?dataurl" import Lock from "@assets/icons/lock-keyhole.svg?dataurl"
@@ -83,7 +83,7 @@
back() back()
if (info.lud16 && info.lud16 !== $userProfile?.lud16) { if (info.lud16 && info.lud16 !== $userProfile?.values?.lud16) {
pushModal(WalletAsReceivingAddress) pushModal(WalletAsReceivingAddress)
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {dissoc} from "@welshman/lib" import {dissoc} from "@welshman/lib"
import {pubkey, updateSession} from "@welshman/app" import {pubkey, updateSession} from "@app/welshman"
import Confirm from "@lib/components/Confirm.svelte" import Confirm from "@lib/components/Confirm.svelte"
import {clearModals} from "@app/modal" import {clearModals} from "@app/modal"
@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {getWalletAddress} from "@welshman/util" import {getWalletAddress} from "@welshman/util"
import {session, waitForThunkError, userProfile} from "@welshman/app" import {session, userProfile} from "@app/welshman"
import {errorMessage} from "@lib/util" import {errorMessage} from "@lib/util"
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"
@@ -17,6 +17,8 @@
const back = () => history.back() const back = () => history.back()
// TODO(welshman-migration): `$userProfile` is now a Profile Reader; `.lud16` is
// a raw-metadata field. Read via `$userProfile?.values?.lud16` once confirmed.
let address = $state($userProfile?.lud16 || "") let address = $state($userProfile?.lud16 || "")
let loading = $state(false) let loading = $state(false)
@@ -32,15 +34,17 @@
loading = true loading = true
try { try {
const error = await waitForThunkError( // TODO(welshman-migration): `$userProfile` is now a Profile Reader; spreading
updateProfile({ // it copies Reader methods rather than the raw metadata. Use
// `$userProfile?.values` (cf. WalletAsReceivingAddress) once confirmed.
const thunk = await updateProfile({
profile: { profile: {
...$userProfile, ...$userProfile,
lud06: undefined, lud06: undefined,
lud16: address.trim() || undefined, lud16: address.trim() || undefined,
}, },
}), })
) const error = await thunk.waitForError()
if (error) { if (error) {
pushToast({theme: "error", message: `Failed to update profile: ${errorMessage(error)}`}) pushToast({theme: "error", message: `Failed to update profile: ${errorMessage(error)}`})
+5 -3
View File
@@ -14,8 +14,10 @@
</style> </style>
<script lang="ts"> <script lang="ts">
import {get} from "svelte/store"
import {clamp} from "@welshman/lib" import {clamp} from "@welshman/lib"
import {pubkey, getFollows, deriveUserWotScore} from "@welshman/app" import {Wot} from "@welshman/app"
import {app, pubkey} from "@app/welshman"
interface Props { interface Props {
pubkey: string pubkey: string
@@ -27,8 +29,8 @@
const radius = 6 const radius = 6
const center = radius + 1 const center = radius + 1
const score = deriveUserWotScore(target) const score = app.use(Wot).wotScore(get(pubkey)!, target).$
const active = $derived(getFollows($pubkey!).includes(target)) const active = $derived(app.use(Wot).follows($pubkey!).get().includes(target))
const normalizedScore = $derived(clamp([0, max], $score) / max) const normalizedScore = $derived(clamp([0, max], $score) / max)
const dashOffset = $derived(100 - 44 * normalizedScore) const dashOffset = $derived(100 - 44 * normalizedScore)
const style = $derived(`transform: rotate(${135 - normalizedScore * 180}deg)`) const style = $derived(`transform: rotate(${135 - normalizedScore * 180}deg)`)
+3 -2
View File
@@ -2,7 +2,8 @@
import cx from "classnames" import cx from "classnames"
import {first} from "@welshman/lib" import {first} from "@welshman/lib"
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import {signer, deriveZapperForPubkey} from "@welshman/app" import {Zappers} from "@welshman/app"
import {app, signer} from "@app/welshman"
import {load} from "@welshman/net" import {load} from "@welshman/net"
import {Router} from "@welshman/router" import {Router} from "@welshman/router"
import {requestZap, makeZapRequest, getZapResponseFilter} from "@welshman/util" import {requestZap, makeZapRequest, getZapResponseFilter} from "@welshman/util"
@@ -33,7 +34,7 @@
const {url, pubkey, eventId}: Props = $props() const {url, pubkey, eventId}: Props = $props()
const zapperStore = deriveZapperForPubkey(pubkey) const zapperStore = app.use(Zappers).forPubkey(pubkey).$
const back = () => history.back() const back = () => history.back()
+3 -2
View File
@@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
import type {Snippet} from "svelte" import type {Snippet} from "svelte"
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {session, loadZapperForPubkey} from "@welshman/app" import {Zappers} from "@welshman/app"
import {app, session} from "@app/welshman"
import Button from "@lib/components/Button.svelte" import Button from "@lib/components/Button.svelte"
import Zap from "@app/components/Zap.svelte" import Zap from "@app/components/Zap.svelte"
import ZapInvoice from "@app/components/ZapInvoice.svelte" import ZapInvoice from "@app/components/ZapInvoice.svelte"
@@ -18,7 +19,7 @@
const {url, event, children, replaceState, ...props}: Props = $props() const {url, event, children, replaceState, ...props}: Props = $props()
const zapperPromise = loadZapperForPubkey(event.pubkey) const zapperPromise = app.use(Zappers).loadForPubkey(event.pubkey)
const onClick = async () => { const onClick = async () => {
loading = true loading = true
+3 -2
View File
@@ -3,7 +3,8 @@
import cx from "classnames" import cx from "classnames"
import {first} from "@welshman/lib" import {first} from "@welshman/lib"
import type {NativeEmoji} from "emoji-picker-element/shared" import type {NativeEmoji} from "emoji-picker-element/shared"
import {signer, deriveZapperForPubkey} from "@welshman/app" import {Zappers} from "@welshman/app"
import {app, signer} from "@app/welshman"
import {request} from "@welshman/net" import {request} from "@welshman/net"
import {Router} from "@welshman/router" import {Router} from "@welshman/router"
import {requestZap, makeZapRequest, getZapResponseFilter} from "@welshman/util" import {requestZap, makeZapRequest, getZapResponseFilter} from "@welshman/util"
@@ -37,7 +38,7 @@
const {url, pubkey, eventId}: Props = $props() const {url, pubkey, eventId}: Props = $props()
const zapperStore = deriveZapperForPubkey(pubkey) const zapperStore = app.use(Zappers).forPubkey(pubkey).$
const back = () => history.back() const back = () => history.back()
+4 -3
View File
@@ -1,6 +1,7 @@
import type {TrustedEvent} from "@welshman/util" import type {TrustedEvent} from "@welshman/util"
import {DELETE, getTag, makeEvent} from "@welshman/util" import {DELETE, getTag, makeEvent} from "@welshman/util"
import {publishThunk, tagEvent} from "@welshman/app" import {Thunks, Tags} from "@welshman/app"
import {app} from "@app/welshman"
import {PROTECTED} from "@app/groups" import {PROTECTED} from "@app/groups"
export type DeleteParams = { export type DeleteParams = {
@@ -10,7 +11,7 @@ export type DeleteParams = {
} }
export const makeDelete = ({protect, event, tags = []}: DeleteParams) => { export const makeDelete = ({protect, event, tags = []}: DeleteParams) => {
const thisTags = [["k", String(event.kind)], ...tagEvent(event), ...tags] const thisTags = [["k", String(event.kind)], ...app.use(Tags).tagEvent(event), ...tags]
const groupTag = getTag("h", event.tags) const groupTag = getTag("h", event.tags)
if (groupTag) { if (groupTag) {
@@ -25,4 +26,4 @@ export const makeDelete = ({protect, event, tags = []}: DeleteParams) => {
} }
export const publishDelete = ({relays, ...params}: DeleteParams & {relays: string[]}) => export const publishDelete = ({relays, ...params}: DeleteParams & {relays: string[]}) =>
publishThunk({event: makeDelete(params), relays}) app.use(Thunks).publish({event: makeDelete(params), relays})

Some files were not shown because too many files have changed in this diff Show More