Compare commits

..

1 Commits

26 changed files with 171 additions and 142 deletions
-1
View File
@@ -12,7 +12,6 @@ if (execSync('git status --porcelain', { encoding: 'utf8' }).trim() && !force) {
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'))
pkg.pnpm = pkg.pnpm || {}
pkg.pnpm.overrides = pkg.pnpm.overrides || {}
pkg.pnpm.overrides["@welshman/app"] = "link:../welshman/packages/app"
pkg.pnpm.overrides["@welshman/content"] = "link:../welshman/packages/content"
+6
View File
@@ -262,6 +262,12 @@ app.use(
// SPA fallback for routes that don't match static files
app.get("*", async context => {
const requestUrl = requestUrlFromContext(context)
// If the path has an extension, it's likely a missing static asset, not an SPA route
if (path.extname(requestUrl.pathname)) {
return context.text("Not found", 404)
}
const metadata = await getMetadataForRoute(requestUrl)
const html = metadata ? injectMeta(metadata) : TEMPLATE_HTML
+1 -1
View File
@@ -280,7 +280,7 @@
</div>
</PageBar>
<PageContent class="flex flex-col-reverse gap-2 py-2 !mb-0">
<PageContent class="flex flex-col-reverse gap-2 py-4">
{#if missingRelayLists.length > 0}
<div class="py-12">
<div class="card2 col-2 m-auto max-w-md items-center text-center">
+1 -1
View File
@@ -88,7 +88,7 @@
<input bind:value={term} class="grow" type="text" placeholder="Search for relays..." />
</label>
<div class="column -m-6 mt-0 h-[50vh] gap-2 overflow-auto p-6 pt-2" bind:this={element}>
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(customUrl)}
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(normalizeRelayUrl(customUrl))}
<RelayItem url={term}>
<Button
class="btn btn-outline btn-sm flex items-center"
+12 -3
View File
@@ -45,11 +45,20 @@
event: TrustedEvent
replyTo?: (event: TrustedEvent) => void
showPubkey?: boolean
addSpaceBelow?: boolean
canEdit: (event: TrustedEvent) => boolean
onEdit: (event: TrustedEvent) => void
}
const {url, event, replyTo = undefined, showPubkey = false, canEdit, onEdit}: Props = $props()
const {
url,
event,
replyTo = undefined,
showPubkey = false,
addSpaceBelow = false,
canEdit,
onEdit,
}: Props = $props()
const path = getRoomItemPath(url, event)
const shouldProtect = canEnforceNip70(url)
@@ -86,7 +95,7 @@
{onTap}
class={cx(
"group relative flex w-full cursor-default flex-col px-2 py-0.5 text-left hover:bg-base-100/50",
{"mt-1.5": showPubkey},
{"mt-1.5": showPubkey, "mb-1.5": addSpaceBelow},
)}>
<div class="flex w-full gap-3 overflow-auto">
{#if showPubkey}
@@ -118,7 +127,7 @@
<div class:mt-2={showPubkey && event.kind !== MESSAGE}>
<RoomItemContent {url} event={$innerEvent ?? event} />
{#if thunk}
<ThunkFailure showToastOnRetry {thunk} class="mt-1 flex justify-end" />
<ThunkFailure showToastOnRetry {thunk} class="mt-2 text-sm" />
{/if}
</div>
</div>
+21 -26
View File
@@ -2,7 +2,7 @@
import {stopPropagation} from "svelte/legacy"
import {noop} from "@welshman/lib"
import type {AbstractThunk} from "@welshman/app"
import {flattenThunks, getFailedThunkUrls, publishThunk, thunkIsComplete} from "@welshman/app"
import {retryThunk, thunkIsComplete, getFailedThunkUrls} from "@welshman/app"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte"
@@ -16,45 +16,40 @@
class?: string
}
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
let {thunk, showToastOnRetry, ...restProps}: Props = $props()
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
const retry = () => {
thunk = retryThunk(thunk)
const retry = (url: string) => {
for (const child of flattenThunks([thunk])) {
if (!child.options.relays.includes(url)) {
continue
}
const retried = publishThunk({...child.options, relays: [url]})
if (showToastOnRetry) {
pushToast({
timeout: 30_000,
children: {
component: ThunkToast,
props: {thunk: retried},
},
})
}
return
if (showToastOnRetry) {
pushToast({
timeout: 30_000,
children: {
component: ThunkToast,
props: {thunk},
},
})
}
}
const failedUrls = $derived(getFailedThunkUrls($thunk))
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
</script>
{#if showFailure}
{@const url = failedUrls[0]}
{@const {status, detail: message} = $thunk.results[url]}
<button
class="flex w-full justify-end px-1 text-xs {restProps.class}"
onclick={stopPropagation(noop)}>
<Tippy
class="flex items-center"
component={ThunkStatusDetail}
props={{thunk, retry}}
params={{interactive: true, maxWidth: "none"}}>
props={{url, message, status, retry}}
params={{interactive: true}}>
{#snippet children()}
<span class="flex cursor-pointer items-center gap-1 opacity-75">
<Icon icon={Danger} class="text-error" size={3} />
<span class="flex cursor-pointer items-center gap-1 text-error">
<Icon icon={Danger} size={3} />
<span>Failed to send!</span>
</span>
{/snippet}
+2 -3
View File
@@ -6,18 +6,17 @@
interface Props {
thunk: AbstractThunk
showToastOnRetry?: boolean
class?: string
}
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
const {thunk, ...restProps}: Props = $props()
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
const showPending = $derived(!thunkIsComplete($thunk))
</script>
{#if showFailure}
<ThunkFailure class={restProps.class} {thunk} {showToastOnRetry} />
<ThunkFailure class={restProps.class} {thunk} />
{:else if showPending}
<ThunkPending class={restProps.class} {thunk} />
{/if}
+16 -53
View File
@@ -1,69 +1,32 @@
<script lang="ts">
import {stopPropagation} from "svelte/legacy"
import type {AbstractThunk} from "@welshman/app"
import {getFailedThunkUrls, getThunkUrlsWithStatus} from "@welshman/app"
import {PublishStatus} from "@welshman/net"
import {displayRelayUrl} from "@welshman/util"
import CheckCircle from "@assets/icons/check-circle.svg?dataurl"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import {addPeriod} from "@lib/util"
interface Props {
thunk: AbstractThunk
retry: (url: string) => void
url: string
status: string
message: string
retry: () => void
}
const {thunk, retry}: Props = $props()
let {url, status, message = $bindable(), retry}: Props = $props()
const successUrls = $derived(getThunkUrlsWithStatus(PublishStatus.Success, $thunk))
const failedUrls = $derived(getFailedThunkUrls($thunk))
const total = $derived(successUrls.length + failedUrls.length)
const isPartial = $derived(successUrls.length > 0 && failedUrls.length > 0)
const title = $derived(
isPartial ? `Partial delivery ${successUrls.length}/${total} relays` : "Failed to send!",
)
const relayMessage = (status: PublishStatus | undefined, detail: string | undefined) => {
if (detail) {
return detail
$effect(() => {
if (!message && status === PublishStatus.Timeout) {
message = "request timed out"
}
if (status === PublishStatus.Timeout) {
return "request timed out"
if (!message) {
message = "no details recieved"
}
return "no details received"
}
})
</script>
<div class="card2 bg-alt flex min-w-72 max-w-sm flex-col gap-3 px-4 py-3 shadow-lg">
<span class="flex items-center gap-2 text-sm font-medium">
<Icon icon={Danger} class="text-error" size={4} />
{title}
</span>
<div class="divider my-0"></div>
<div class="flex flex-col gap-3">
{#each successUrls as url (url)}
<div class="flex items-start gap-2 text-sm">
<Icon icon={CheckCircle} class="mt-0.5 shrink-0 text-success" size={4} />
<span>{displayRelayUrl(url)}</span>
</div>
{/each}
{#each failedUrls as url (url)}
{@const {detail, status} = $thunk.results[url] || {}}
<div class="grid grid-cols-[1rem_1fr_auto] items-start gap-x-3 gap-y-1 text-sm">
<Icon icon={Danger} class="mt-0.5 text-error" size={4} />
<div class="min-w-0">
<p class="break-all">{displayRelayUrl(url)}</p>
<p class="text-xs opacity-60">{addPeriod(relayMessage(status, detail))}</p>
</div>
<Button class="link shrink-0 px-1" onclick={stopPropagation(() => retry(url))}>
Retry
</Button>
</div>
{/each}
</div>
<div class="card2 bg-alt col-2 shadow-lg">
<p>
Failed to publish to {displayRelayUrl(url)}: {addPeriod(message)}
</p>
<Button class="link" onclick={retry}>Retry</Button>
</div>
+1 -1
View File
@@ -755,7 +755,7 @@ export const getSpaceUrlsFromGroupList = (groupList: List | undefined) => {
}
}
return uniqBy(normalizeRelayUrl, urls)
return uniq(urls.map(normalizeRelayUrl))
}
export const getSpaceRoomsFromGroupList = (url: string, groupList: List | undefined) => {
+1 -1
View File
@@ -13,7 +13,7 @@
const className = $derived(
cx(
props.class,
"scroll-container z-feature flex flex-col min-h-0 w-full min-w-0 overflow-y-auto overflow-x-hidden mb-14 md:mb-0",
"scroll-container z-feature flex min-h-0 w-full min-w-0 flex-col overflow-y-auto overflow-x-hidden pb-14 md:pb-0",
),
)
</script>
+1 -1
View File
@@ -254,7 +254,7 @@
</div>
</div>
</PageBar>
<PageContent class="flex flex-col gap-2 p-2">
<PageContent class="flex flex-col gap-2 p-2 pt-4">
<div class="flex flex-col gap-2" bind:this={element}>
{#each PLATFORM_RELAYS as url (url)}
<Button
+49 -1
View File
@@ -1,15 +1,18 @@
<script lang="ts">
import type {Snippet} from "svelte"
import {get} from "svelte/store"
import {page} from "$app/stores"
import {once} from "@welshman/lib"
import {pubkey} from "@welshman/app"
import Page from "@lib/components/Page.svelte"
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
import SpaceMenu from "@app/components/SpaceMenu.svelte"
import SpaceAuthError from "@app/components/SpaceAuthError.svelte"
import SpaceTrustRelay from "@app/components/SpaceTrustRelay.svelte"
import SpaceJoin from "@app/components/SpaceJoin.svelte"
import {pushModal} from "@app/util/modal"
import {makeSpacePath} from "@app/util/routes"
import {decodeRelay, relaysPendingTrust} from "@app/core/state"
import {decodeRelay, relaysPendingTrust, userSpaceUrls, loadUserGroupList} from "@app/core/state"
import {deriveRelayAuthError} from "@app/core/commands"
type Props = {
@@ -28,6 +31,8 @@
const showPendingTrust = once(() => pushModal(SpaceTrustRelay, {url}, {noEscape: true}))
const joinPrompted = new Set<string>()
// Watch for relay errors and notify the user
$effect(() => {
if ($authError) {
@@ -36,6 +41,49 @@
showPendingTrust()
}
})
// Direct links skip Discover — prompt to join when relay is not in the user's space list.
$effect(() => {
const spaceUrl = url
const currentPubkey = get(pubkey)
if (!currentPubkey) {
return
}
if ($userSpaceUrls.includes(spaceUrl) || $authError || $relaysPendingTrust.includes(spaceUrl)) {
return
}
let cancelled = false
void loadUserGroupList().then(() => {
if (cancelled) {
return
}
if (get(pubkey) !== currentPubkey) {
return
}
if (
get(userSpaceUrls).includes(spaceUrl) ||
get(authError) ||
get(relaysPendingTrust).includes(spaceUrl) ||
joinPrompted.has(spaceUrl)
) {
return
}
joinPrompted.add(spaceUrl)
pushModal(SpaceJoin, {url: spaceUrl})
})
return () => {
cancelled = true
joinPrompted.delete(spaceUrl)
}
})
</script>
{#if $page.url.pathname === makeSpacePath(url)}
+11 -2
View File
@@ -350,11 +350,16 @@
event.created_at - previousCreatedAt > int(3, MINUTE) ||
previousKind === ROOM_ADD_MEMBER
if (showPubkey && elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
elements.push({
id: event.id,
type: "note",
value: event,
showPubkey,
addSpaceBelow: false,
})
previousDate = date
@@ -363,6 +368,9 @@
previousCreatedAt = event.created_at
seen.add(event.id)
}
if (elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
}
elements.reverse()
@@ -462,7 +470,7 @@
bind:element
onscroll={onScroll}
class={cx(
"flex-col-reverse !mb-0",
"flex-col-reverse pb-0! pt-4",
showMobileVideoPanel ? "hidden md:flex md:flex-col-reverse" : "flex",
pageContentHiddenDesktopVideoOnly && "md:hidden",
)}>
@@ -493,7 +501,7 @@
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
</p>
{/if}
{#each elements as { type, id, value, showPubkey }, i (id)}
{#each elements as { type, id, value, showPubkey, addSpaceBelow }, i (id)}
{#if type === "new-messages"}
<div
{id}
@@ -515,6 +523,7 @@
{event}
{replyTo}
{showPubkey}
{addSpaceBelow}
canEdit={canEditEvent}
onEdit={onEditEvent} />
{/if}
@@ -125,7 +125,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2">
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
{#each items as { event, dateDisplay, isFirstFutureEvent }, i (event.id)}
<div class={"calendar-event-" + event.id}>
{#if isFirstFutureEvent}
@@ -64,7 +64,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-3 p-2">
<PageContent class="flex flex-col gap-3 p-2 pt-4">
{#if $event}
<div class="card2 bg-alt col-3 z-feature">
<div class="flex items-start gap-4">
@@ -109,12 +109,10 @@
</div>
{/if}
{:else}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading event...</Spinner>
{:then}
<p>Failed to load event.</p>
{/await}
</div>
{#await sleep(5000)}
<Spinner loading>Loading comments...</Spinner>
{:then}
<p>Failed to load comments.</p>
{/await}
{/if}
</PageContent>
+12 -3
View File
@@ -227,11 +227,16 @@
event.created_at - previousCreatedAt > int(3, MINUTE) ||
previousKind === RELAY_ADD_MEMBER
if (showPubkey && elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
elements.push({
id: event.id,
type: "note",
value: event,
showPubkey,
addSpaceBelow: false,
})
previousDate = date
@@ -240,6 +245,9 @@
previousCreatedAt = event.created_at
seen.add(event.id)
}
if (elements.length > 0) {
elements[elements.length - 1].addSpaceBelow = true
}
}
elements.reverse()
@@ -310,13 +318,13 @@
{/snippet}
</SpaceBar>
<PageContent bind:element onscroll={onScroll} class="flex-col-reverse !mb-0">
<PageContent bind:element onscroll={onScroll} class="flex flex-col-reverse pt-4 pb-0!">
{#if loadingForward}
<p class="py-20 flex justify-center">
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
</p>
{/if}
{#each elements as { type, id, value, showPubkey }, i (id)}
{#each elements as { type, id, value, showPubkey, addSpaceBelow }, i (id)}
{#if type === "new-messages"}
<div
{id}
@@ -339,7 +347,8 @@
{replyTo}
{showPubkey}
canEdit={canEditEvent}
onEdit={onEditEvent} />
onEdit={onEditEvent}
{addSpaceBelow} />
{/if}
{/if}
{/each}
@@ -77,7 +77,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2">
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
{#each items as event (event.id)}
<div in:fly>
<ClassifiedItem {url} event={$state.snapshot(event)} />
@@ -61,7 +61,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-2 p-2">
<PageContent class="flex flex-col p-2 pt-4">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -90,7 +90,7 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end">
<div class="flex justify-end p-2">
<Button class="btn btn-primary" onclick={openReply}>
<Icon icon={Reply} />
Reply to listing
@@ -98,7 +98,7 @@
</div>
{/if}
{:else}
<div class="flex justify-center py-20">
<div class="py-20 m-auto">
{#await sleep(5000)}
<Spinner loading>Loading listing...</Spinner>
{:then}
+1 -1
View File
@@ -76,7 +76,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2">
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
{#each items as event (event.id)}
<div in:fly>
<GoalItem {url} event={$state.snapshot(event)} />
@@ -62,7 +62,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-2 p-2">
<PageContent class="flex flex-col p-2 pt-4">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -91,7 +91,7 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end">
<div class="flex justify-end p-2">
<Button class="btn btn-primary" onclick={openReply}>
<Icon icon={Reply} />
Comment on this goal
@@ -99,12 +99,10 @@
</div>
{/if}
{:else}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading funding goal...</Spinner>
{:then}
<p>Failed to load funding goal.</p>
{/await}
</div>
{#await sleep(5000)}
<Spinner loading>Loading funding goal...</Spinner>
{:then}
<p>Failed to load funding goal.</p>
{/await}
{/if}
</PageContent>
+1 -1
View File
@@ -76,7 +76,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2">
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
{#each items as event (event.id)}
<div in:fly>
<PollItem {url} event={$state.snapshot(event)} />
@@ -63,7 +63,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-2 p-2">
<PageContent class="flex flex-col gap-3 p-2 pt-4">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -92,17 +92,15 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end">
<div class="flex justify-end p-2">
<Button class="btn btn-primary" onclick={openReply}>Comment on this poll</Button>
</div>
{/if}
{:else}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading poll...</Spinner>
{:then}
<p>Failed to load poll.</p>
{/await}
</div>
{#await sleep(5000)}
<Spinner loading>Loading poll...</Spinner>
{:then}
<p>Failed to load poll.</p>
{/await}
{/if}
</PageContent>
@@ -292,7 +292,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-2 p-2" bind:element>
<PageContent class="flex flex-col gap-2 p-2 pt-4" bind:element>
{#if $recentActivity.length === 0}
<p class="flex flex-col items-center py-20 text-center">No recent activity found!</p>
{:else}
@@ -77,7 +77,7 @@
{/snippet}
</SpaceBar>
<PageContent bind:element class="flex flex-col gap-2 p-2">
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
{#each items as event (event.id)}
<div in:fly>
<ThreadItem {url} event={$state.snapshot(event)} />
@@ -61,7 +61,7 @@
{/snippet}
</SpaceBar>
<PageContent class="flex flex-col gap-2 p-2">
<PageContent class="flex flex-col p-2 pt-4">
{#if $event}
<div class="flex flex-col gap-3">
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
@@ -90,7 +90,7 @@
{#if showReply}
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
{:else}
<div class="flex justify-end">
<div class="flex justify-end p-2">
<Button class="btn btn-primary" onclick={openReply}>
<Icon icon={Reply} />
Reply to thread
@@ -98,12 +98,10 @@
</div>
{/if}
{:else}
<div class="flex justify-center py-20">
{#await sleep(5000)}
<Spinner loading>Loading thread...</Spinner>
{:then}
<p>Failed to load thread.</p>
{/await}
</div>
{#await sleep(5000)}
<Spinner loading>Loading thread...</Spinner>
{:then}
<p>Failed to load thread.</p>
{/await}
{/if}
</PageContent>
+1 -1
View File
@@ -10,7 +10,7 @@
</script>
<Page>
<PageContent class="flex flex-col items-center gap-2 p-2">
<PageContent class="flex flex-col items-center gap-2 p-2 pt-4">
<PageHeader>
{#snippet title()}
<div>Choose your Hosting Plan</div>