forked from coracle/flotilla
Compare commits
5 Commits
e3e13563d5
...
57d2f61ff4
| Author | SHA1 | Date | |
|---|---|---|---|
| 57d2f61ff4 | |||
| b6b8145901 | |||
| cdc9f927b5 | |||
| 4d57e4e6ed | |||
| 1b8d6e50e2 |
@@ -12,6 +12,7 @@ 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"
|
||||
|
||||
@@ -262,12 +262,6 @@ 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
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {map, not, nthEq, reject, removeUndefined, uniqBy} from "@welshman/lib"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {makeHttpAuth, makeHttpAuthHeader, getTags} from "@welshman/util"
|
||||
import {signer} from "@welshman/app"
|
||||
import {load} from "@welshman/net"
|
||||
import {getLivekitEndpoint} from "$lib/livekit"
|
||||
import {AbortError, TimeoutError, whenAborted, whenTimeout} from "$lib/util"
|
||||
import {
|
||||
@@ -154,6 +155,12 @@ const fetchLivekitToken = async (
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export const loadVoiceParticipants = (url: string, h: string) =>
|
||||
load({
|
||||
relays: [url],
|
||||
filters: [{kinds: [LIVEKIT_PARTICIPANTS], "#d": [h]}],
|
||||
})
|
||||
|
||||
export const deriveVoiceParticipants = (url: string, h: string) =>
|
||||
// We use the livekit identity list while in a call, and fall back to the list in kind 39004.
|
||||
derived(
|
||||
@@ -173,7 +180,7 @@ export const deriveVoiceParticipants = (url: string, h: string) =>
|
||||
if (!latestEvent) return []
|
||||
const participants = removeUndefined(
|
||||
map(
|
||||
(tag: string[]) => (tag[1] ? {pubkey: tag[1], identity: tag[1]} : undefined),
|
||||
(tag: string[]) => (tag[1] ? participantFromLiveKitIdentity(tag[1]) : undefined),
|
||||
getTags("participant", latestEvent.tags),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -280,7 +280,7 @@
|
||||
</div>
|
||||
</PageBar>
|
||||
|
||||
<PageContent class="flex flex-col-reverse gap-2 py-4">
|
||||
<PageContent class="flex flex-col-reverse gap-2 py-2 !mb-0">
|
||||
{#if missingRelayLists.length > 0}
|
||||
<div class="py-12">
|
||||
<div class="card2 col-2 m-auto max-w-md items-center text-center">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import cx from "classnames"
|
||||
import {getProfile, loadProfile} from "@welshman/app"
|
||||
import {isMobile} from "@lib/html"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
@@ -6,10 +7,20 @@
|
||||
type Props = {
|
||||
pubkeys: string[]
|
||||
size?: number
|
||||
limit?: number
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {pubkeys, size = 7}: Props = $props()
|
||||
const limit = isMobile ? 7 : 10
|
||||
const {pubkeys, size = 7, limit, class: className}: Props = $props()
|
||||
const effectiveLimit = $derived(limit ?? (isMobile ? 7 : 10))
|
||||
|
||||
const dimensions = $derived(
|
||||
size <= 5
|
||||
? {box: "h-5 w-5", overlap: "-mr-2", overflow: "text-[9px]"}
|
||||
: size <= 6
|
||||
? {box: "h-6 w-6", overlap: "-mr-2.5", overflow: "text-[10px]"}
|
||||
: {box: "h-8 w-8", overlap: "-mr-3", overflow: "text-xs"},
|
||||
)
|
||||
|
||||
for (const pubkey of pubkeys) {
|
||||
loadProfile(pubkey)
|
||||
@@ -20,13 +31,31 @@
|
||||
|
||||
return filtered.length > 0 ? filtered : pubkeys.slice(0, 1)
|
||||
})
|
||||
|
||||
const displayPubkeys = $derived(visiblePubkeys.toSorted().slice(0, effectiveLimit))
|
||||
const overflowCount = $derived(Math.max(0, pubkeys.length - effectiveLimit))
|
||||
</script>
|
||||
|
||||
<div class="flex pr-3">
|
||||
{#each visiblePubkeys.toSorted().slice(0, limit) as pubkey (pubkey)}
|
||||
<div class={cx("flex", size <= 5 ? "pr-2" : "pr-3", className)}>
|
||||
{#each displayPubkeys as pubkey (pubkey)}
|
||||
<div
|
||||
class="z-feature -mr-3 inline-block flex h-8 w-8 items-center justify-center rounded-full bg-base-100">
|
||||
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {size} />
|
||||
class={cx(
|
||||
"z-feature inline-block flex items-center justify-center rounded-full bg-base-100",
|
||||
dimensions.box,
|
||||
dimensions.overlap,
|
||||
)}>
|
||||
<ProfileCircle class={cx(dimensions.box, "bg-base-300")} {pubkey} {size} />
|
||||
</div>
|
||||
{/each}
|
||||
{#if overflowCount > 0}
|
||||
<div
|
||||
class={cx(
|
||||
"z-feature inline-flex items-center justify-center rounded-full bg-neutral font-medium text-neutral-content",
|
||||
dimensions.box,
|
||||
dimensions.overlap,
|
||||
dimensions.overflow,
|
||||
)}>
|
||||
+{overflowCount}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -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(normalizeRelayUrl(customUrl))}
|
||||
{#if customUrl && isShareableRelayUrl(customUrl) && !$relays.includes(customUrl)}
|
||||
<RelayItem url={term}>
|
||||
<Button
|
||||
class="btn btn-outline btn-sm flex items-center"
|
||||
|
||||
@@ -45,20 +45,11 @@
|
||||
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,
|
||||
addSpaceBelow = false,
|
||||
canEdit,
|
||||
onEdit,
|
||||
}: Props = $props()
|
||||
const {url, event, replyTo = undefined, showPubkey = false, canEdit, onEdit}: Props = $props()
|
||||
|
||||
const path = getRoomItemPath(url, event)
|
||||
const shouldProtect = canEnforceNip70(url)
|
||||
@@ -95,7 +86,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, "mb-1.5": addSpaceBelow},
|
||||
{"mt-1.5": showPubkey},
|
||||
)}>
|
||||
<div class="flex w-full gap-3 overflow-auto">
|
||||
{#if showPubkey}
|
||||
@@ -127,7 +118,7 @@
|
||||
<div class:mt-2={showPubkey && event.kind !== MESSAGE}>
|
||||
<RoomItemContent {url} event={$innerEvent ?? event} />
|
||||
{#if thunk}
|
||||
<ThunkFailure showToastOnRetry {thunk} class="mt-2 text-sm" />
|
||||
<ThunkFailure showToastOnRetry {thunk} class="mt-1 flex justify-end" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {stopPropagation} from "svelte/legacy"
|
||||
import {noop} from "@welshman/lib"
|
||||
import type {AbstractThunk} from "@welshman/app"
|
||||
import {retryThunk, thunkIsComplete, getFailedThunkUrls} from "@welshman/app"
|
||||
import {flattenThunks, getFailedThunkUrls, publishThunk, thunkIsComplete} 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,40 +16,45 @@
|
||||
class?: string
|
||||
}
|
||||
|
||||
let {thunk, showToastOnRetry, ...restProps}: Props = $props()
|
||||
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
|
||||
|
||||
const retry = () => {
|
||||
thunk = retryThunk(thunk)
|
||||
const showFailure = $derived(thunkIsComplete($thunk) && getFailedThunkUrls($thunk).length > 0)
|
||||
|
||||
if (showToastOnRetry) {
|
||||
pushToast({
|
||||
timeout: 30_000,
|
||||
children: {
|
||||
component: ThunkToast,
|
||||
props: {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
|
||||
}
|
||||
}
|
||||
|
||||
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={{url, message, status, retry}}
|
||||
params={{interactive: true}}>
|
||||
props={{thunk, retry}}
|
||||
params={{interactive: true, maxWidth: "none"}}>
|
||||
{#snippet children()}
|
||||
<span class="flex cursor-pointer items-center gap-1 text-error">
|
||||
<Icon icon={Danger} size={3} />
|
||||
<span class="flex cursor-pointer items-center gap-1 opacity-75">
|
||||
<Icon icon={Danger} class="text-error" size={3} />
|
||||
<span>Failed to send!</span>
|
||||
</span>
|
||||
{/snippet}
|
||||
|
||||
@@ -6,17 +6,18 @@
|
||||
|
||||
interface Props {
|
||||
thunk: AbstractThunk
|
||||
showToastOnRetry?: boolean
|
||||
class?: string
|
||||
}
|
||||
|
||||
const {thunk, ...restProps}: Props = $props()
|
||||
const {thunk, showToastOnRetry, ...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} />
|
||||
<ThunkFailure class={restProps.class} {thunk} {showToastOnRetry} />
|
||||
{:else if showPending}
|
||||
<ThunkPending class={restProps.class} {thunk} />
|
||||
{/if}
|
||||
|
||||
@@ -1,32 +1,69 @@
|
||||
<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 {
|
||||
url: string
|
||||
status: string
|
||||
message: string
|
||||
retry: () => void
|
||||
thunk: AbstractThunk
|
||||
retry: (url: string) => void
|
||||
}
|
||||
|
||||
let {url, status, message = $bindable(), retry}: Props = $props()
|
||||
const {thunk, retry}: Props = $props()
|
||||
|
||||
$effect(() => {
|
||||
if (!message && status === PublishStatus.Timeout) {
|
||||
message = "request timed out"
|
||||
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
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = "no details recieved"
|
||||
if (status === PublishStatus.Timeout) {
|
||||
return "request timed out"
|
||||
}
|
||||
})
|
||||
|
||||
return "no details received"
|
||||
}
|
||||
</script>
|
||||
|
||||
<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 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>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import {loadProfile, displayProfileByPubkey} from "@welshman/app"
|
||||
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
||||
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
||||
import RoomImage from "@app/components/RoomImage.svelte"
|
||||
import RoomName from "@app/components/RoomName.svelte"
|
||||
import {makeRoomPath} from "@app/util/routes"
|
||||
@@ -20,7 +21,11 @@
|
||||
voiceState,
|
||||
type VoiceParticipant,
|
||||
} from "@app/call/stores"
|
||||
import {cancelJoinVoiceRoom, deriveVoiceParticipants} from "@app/call/voice"
|
||||
import {
|
||||
cancelJoinVoiceRoom,
|
||||
deriveVoiceParticipants,
|
||||
loadVoiceParticipants,
|
||||
} from "@app/call/voice"
|
||||
|
||||
interface Props {
|
||||
url: string
|
||||
@@ -32,6 +37,7 @@
|
||||
const {url, h, replaceState = false, notification = false}: Props = $props()
|
||||
|
||||
const participants = deriveVoiceParticipants(url, h)
|
||||
const participantPubkeys = $derived($participants.flatMap(p => (p.pubkey ? [p.pubkey] : [])))
|
||||
const isActive = $derived(
|
||||
$voiceState === VoiceState.Connected && $currentVoiceRoom?.id === makeRoomId(url, h),
|
||||
)
|
||||
@@ -53,6 +59,10 @@
|
||||
pushModal(VoiceRoomJoinDialog, {url, h})
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
void loadVoiceParticipants(url, h)
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
for (const p of $participants) {
|
||||
if (p.pubkey) loadProfile(p.pubkey)
|
||||
@@ -75,29 +85,33 @@
|
||||
{/if}
|
||||
<RoomName {url} {h} />
|
||||
</div>
|
||||
{#if $participants.length > 0}
|
||||
{#each $participants as p (participantKey(p as VoiceParticipant))}
|
||||
<div class="flex items-center gap-2 ml-6">
|
||||
<div
|
||||
class={cx(
|
||||
"inline-flex shrink-0 items-center justify-center rounded-full transition-shadow",
|
||||
isActive && $isParticipantSpeaking(p) && "ring-2 ring-success",
|
||||
)}>
|
||||
<ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" />
|
||||
</div>
|
||||
<span class="ellipsize min-w-0 flex-1 text-xs opacity-70">
|
||||
{p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"}
|
||||
</span>
|
||||
{#if isActive}
|
||||
{@const media = $mediaStateByIdentity(p.identity)}
|
||||
{#if participantPubkeys.length > 0}
|
||||
{#if isActive}
|
||||
{#each $participants as p (participantKey(p as VoiceParticipant))}
|
||||
{@const media = $mediaStateByIdentity(p.identity)}
|
||||
<div class="flex items-center gap-2 ml-6">
|
||||
<div
|
||||
class={cx(
|
||||
"inline-flex shrink-0 items-center justify-center rounded-full transition-shadow",
|
||||
$isParticipantSpeaking(p) && "ring-2 ring-success",
|
||||
)}>
|
||||
<ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" />
|
||||
</div>
|
||||
<span class="ellipsize min-w-0 flex-1 text-xs opacity-70">
|
||||
{p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"}
|
||||
</span>
|
||||
<VoiceParticipantMediaBadges
|
||||
muted={media.muted}
|
||||
cameraOn={media.cameraOn}
|
||||
size={3}
|
||||
class="shrink-0" />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="ml-6">
|
||||
<ProfileCircles pubkeys={participantPubkeys} size={5} limit={3} />
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</SecondaryNavItem>
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
||||
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
||||
import {AbortError, TimeoutError} from "$lib/util"
|
||||
import ProfileCircles from "@app/components/ProfileCircles.svelte"
|
||||
import {displayRoom} from "@app/core/state"
|
||||
import {joinVoiceRoom} from "@app/call/voice"
|
||||
import {deriveVoiceParticipants, joinVoiceRoom, loadVoiceParticipants} from "@app/call/voice"
|
||||
import {popModal} from "@app/util/modal"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
@@ -26,6 +27,8 @@
|
||||
const {url, h}: Props = $props()
|
||||
|
||||
const spaceLabel = $derived(displayRelayUrl(url))
|
||||
const participants = deriveVoiceParticipants(url, h)
|
||||
const participantPubkeys = $derived($participants.flatMap(p => (p.pubkey ? [p.pubkey] : [])))
|
||||
|
||||
let audioInputs = $state<MediaDeviceInfo[]>([])
|
||||
let selectedDeviceId = $state("")
|
||||
@@ -42,6 +45,7 @@
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
void loadVoiceParticipants(url, h)
|
||||
void loadDevices()
|
||||
})
|
||||
|
||||
@@ -81,6 +85,11 @@
|
||||
</span>
|
||||
</ModalSubtitle>
|
||||
</ModalHeader>
|
||||
{#if participantPubkeys.length > 0}
|
||||
<div class="flex justify-center py-2">
|
||||
<ProfileCircles pubkeys={participantPubkeys} size={5} limit={3} />
|
||||
</div>
|
||||
{/if}
|
||||
<p class="text-sm opacity-80">Select a microphone to join the call:</p>
|
||||
<div class="flex flex-col gap-4 pt-2">
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -755,7 +755,7 @@ export const getSpaceUrlsFromGroupList = (groupList: List | undefined) => {
|
||||
}
|
||||
}
|
||||
|
||||
return uniq(urls.map(normalizeRelayUrl))
|
||||
return uniqBy(normalizeRelayUrl, urls)
|
||||
}
|
||||
|
||||
export const getSpaceRoomsFromGroupList = (url: string, groupList: List | undefined) => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
const className = $derived(
|
||||
cx(
|
||||
props.class,
|
||||
"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",
|
||||
"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",
|
||||
),
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</PageBar>
|
||||
<PageContent class="flex flex-col gap-2 p-2 pt-4">
|
||||
<PageContent class="flex flex-col gap-2 p-2">
|
||||
<div class="flex flex-col gap-2" bind:this={element}>
|
||||
{#each PLATFORM_RELAYS as url (url)}
|
||||
<Button
|
||||
|
||||
@@ -350,16 +350,11 @@
|
||||
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
|
||||
@@ -368,9 +363,6 @@
|
||||
previousCreatedAt = event.created_at
|
||||
seen.add(event.id)
|
||||
}
|
||||
if (elements.length > 0) {
|
||||
elements[elements.length - 1].addSpaceBelow = true
|
||||
}
|
||||
}
|
||||
|
||||
elements.reverse()
|
||||
@@ -470,7 +462,7 @@
|
||||
bind:element
|
||||
onscroll={onScroll}
|
||||
class={cx(
|
||||
"flex-col-reverse pb-0! pt-4",
|
||||
"flex-col-reverse !mb-0",
|
||||
showMobileVideoPanel ? "hidden md:flex md:flex-col-reverse" : "flex",
|
||||
pageContentHiddenDesktopVideoOnly && "md:hidden",
|
||||
)}>
|
||||
@@ -501,7 +493,7 @@
|
||||
<Spinner loading={loadingForward}>Looking for messages...</Spinner>
|
||||
</p>
|
||||
{/if}
|
||||
{#each elements as { type, id, value, showPubkey, addSpaceBelow }, i (id)}
|
||||
{#each elements as { type, id, value, showPubkey }, i (id)}
|
||||
{#if type === "new-messages"}
|
||||
<div
|
||||
{id}
|
||||
@@ -523,7 +515,6 @@
|
||||
{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 pt-4">
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2">
|
||||
{#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 pt-4">
|
||||
<PageContent class="flex flex-col gap-3 p-2">
|
||||
{#if $event}
|
||||
<div class="card2 bg-alt col-3 z-feature">
|
||||
<div class="flex items-start gap-4">
|
||||
@@ -109,10 +109,12 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading comments...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load comments.</p>
|
||||
{/await}
|
||||
<div class="flex justify-center py-20">
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading event...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load event.</p>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
@@ -227,16 +227,11 @@
|
||||
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
|
||||
@@ -245,9 +240,6 @@
|
||||
previousCreatedAt = event.created_at
|
||||
seen.add(event.id)
|
||||
}
|
||||
if (elements.length > 0) {
|
||||
elements[elements.length - 1].addSpaceBelow = true
|
||||
}
|
||||
}
|
||||
|
||||
elements.reverse()
|
||||
@@ -318,13 +310,13 @@
|
||||
{/snippet}
|
||||
</SpaceBar>
|
||||
|
||||
<PageContent bind:element onscroll={onScroll} class="flex flex-col-reverse pt-4 pb-0!">
|
||||
<PageContent bind:element onscroll={onScroll} class="flex-col-reverse !mb-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, addSpaceBelow }, i (id)}
|
||||
{#each elements as { type, id, value, showPubkey }, i (id)}
|
||||
{#if type === "new-messages"}
|
||||
<div
|
||||
{id}
|
||||
@@ -347,8 +339,7 @@
|
||||
{replyTo}
|
||||
{showPubkey}
|
||||
canEdit={canEditEvent}
|
||||
onEdit={onEditEvent}
|
||||
{addSpaceBelow} />
|
||||
onEdit={onEditEvent} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
{/snippet}
|
||||
</SpaceBar>
|
||||
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2">
|
||||
{#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 p-2 pt-4">
|
||||
<PageContent class="flex flex-col gap-2 p-2">
|
||||
{#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 p-2">
|
||||
<div class="flex justify-end">
|
||||
<Button class="btn btn-primary" onclick={openReply}>
|
||||
<Icon icon={Reply} />
|
||||
Reply to listing
|
||||
@@ -98,7 +98,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="py-20 m-auto">
|
||||
<div class="flex justify-center py-20">
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading listing...</Spinner>
|
||||
{:then}
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
{/snippet}
|
||||
</SpaceBar>
|
||||
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2">
|
||||
{#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 p-2 pt-4">
|
||||
<PageContent class="flex flex-col gap-2 p-2">
|
||||
{#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 p-2">
|
||||
<div class="flex justify-end">
|
||||
<Button class="btn btn-primary" onclick={openReply}>
|
||||
<Icon icon={Reply} />
|
||||
Comment on this goal
|
||||
@@ -99,10 +99,12 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading funding goal...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load funding goal.</p>
|
||||
{/await}
|
||||
<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>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
{/snippet}
|
||||
</SpaceBar>
|
||||
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2 pt-4">
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2">
|
||||
{#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-3 p-2 pt-4">
|
||||
<PageContent class="flex flex-col gap-2 p-2">
|
||||
{#if $event}
|
||||
<div class="flex flex-col gap-3">
|
||||
<NoteCard event={$event} {url} class="card2 bg-alt z-feature w-full">
|
||||
@@ -92,15 +92,17 @@
|
||||
{#if showReply}
|
||||
<EventReply {url} event={$event} onClose={closeReply} onSubmit={closeReply} />
|
||||
{:else}
|
||||
<div class="flex justify-end p-2">
|
||||
<div class="flex justify-end">
|
||||
<Button class="btn btn-primary" onclick={openReply}>Comment on this poll</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading poll...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load poll.</p>
|
||||
{/await}
|
||||
<div class="flex justify-center py-20">
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading poll...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load poll.</p>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
{/snippet}
|
||||
</SpaceBar>
|
||||
|
||||
<PageContent class="flex flex-col gap-2 p-2 pt-4" bind:element>
|
||||
<PageContent class="flex flex-col gap-2 p-2" 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 pt-4">
|
||||
<PageContent bind:element class="flex flex-col gap-2 p-2">
|
||||
{#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 p-2 pt-4">
|
||||
<PageContent class="flex flex-col gap-2 p-2">
|
||||
{#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 p-2">
|
||||
<div class="flex justify-end">
|
||||
<Button class="btn btn-primary" onclick={openReply}>
|
||||
<Icon icon={Reply} />
|
||||
Reply to thread
|
||||
@@ -98,10 +98,12 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading thread...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load thread.</p>
|
||||
{/await}
|
||||
<div class="flex justify-center py-20">
|
||||
{#await sleep(5000)}
|
||||
<Spinner loading>Loading thread...</Spinner>
|
||||
{:then}
|
||||
<p>Failed to load thread.</p>
|
||||
{/await}
|
||||
</div>
|
||||
{/if}
|
||||
</PageContent>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</script>
|
||||
|
||||
<Page>
|
||||
<PageContent class="flex flex-col items-center gap-2 p-2 pt-4">
|
||||
<PageContent class="flex flex-col items-center gap-2 p-2">
|
||||
<PageHeader>
|
||||
{#snippet title()}
|
||||
<div>Choose your Hosting Plan</div>
|
||||
|
||||
Reference in New Issue
Block a user