Migrate more stuff

This commit is contained in:
Jon Staab
2025-02-03 16:37:14 -08:00
parent 0f705c459a
commit 8d3433b167
150 changed files with 2001 additions and 1205 deletions

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import type {Snippet} from "svelte"
import {page} from "$app/stores"
import {pubkey} from "@welshman/app"
import Landing from "@app/components/Landing.svelte"
@@ -9,6 +10,12 @@
import {BURROW_URL} from "@app/state"
import {modals, pushModal} from "@app/modal"
interface Props {
children: Snippet
}
let {children}: Props = $props()
if (BURROW_URL && !$pubkey) {
if ($page.url.pathname === "/confirm-email") {
pushModal(EmailConfirm, {
@@ -29,7 +36,7 @@
<div class="flex h-screen overflow-hidden">
{#if $pubkey}
<PrimaryNav>
<slot />
{@render children?.()}
</PrimaryNav>
{:else if !$modals[$page.url.hash.slice(1)]}
<Landing />

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {onMount} from "svelte"
import {writable} from "svelte/store"
import {EditorContent} from "svelte-tiptap"
@@ -7,8 +9,12 @@
import Button from "@lib/components/Button.svelte"
import {makeEditor} from "@app/editor"
export let onSubmit: any
export let content = ""
interface Props {
onSubmit: any
content?: string
}
let {onSubmit, content = ""}: Props = $props()
export const focus = () => $editor.chain().focus().run()
@@ -36,9 +42,7 @@
})
</script>
<form
class="relative z-feature flex gap-2 p-2"
on:submit|preventDefault={$uploading ? undefined : submit}>
<form class="relative z-feature flex gap-2 p-2" onsubmit={preventDefault(submit)}>
<Button
data-tip="Add an image"
class="center tooltip tooltip-right h-10 w-10 min-w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200"

View File

@@ -6,8 +6,12 @@
import Button from "@lib/components/Button.svelte"
import Content from "@app/components/Content.svelte"
export let event: TrustedEvent
export let clear: () => void
interface Props {
event: TrustedEvent
clear: () => void
}
let {event, clear}: Props = $props()
</script>
<div

View File

@@ -1,4 +1,7 @@
<script lang="ts">
import {createBubbler, stopPropagation} from "svelte/legacy"
const bubble = createBubbler()
import {hash} from "@welshman/lib"
import {now} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
@@ -26,11 +29,16 @@
import {publishDelete, publishReaction} from "@app/commands"
import {pushModal} from "@app/modal"
export let url, room
export let event: TrustedEvent
export let replyTo: any = undefined
export let showPubkey = false
export let inert = false
interface Props {
url: any
room: any
event: TrustedEvent
replyTo?: any
showPubkey?: boolean
inert?: boolean
}
let {url, room, event, replyTo = undefined, showPubkey = false, inert = false}: Props = $props()
const thunk = $thunks[event.id]
const today = formatTimestampAsDate(now())
@@ -97,7 +105,7 @@
<button
class="join absolute right-1 top-1 border border-solid border-neutral text-xs opacity-0 transition-all"
class:group-hover:opacity-100={!isMobile}
on:click|stopPropagation>
onclick={stopPropagation(bubble("click"))}>
<ChannelMessageEmojiButton {url} {room} {event} />
{#if replyTo}
<Button class="btn join-item btn-xs" on:click={reply}>

View File

@@ -5,7 +5,7 @@
import Icon from "@lib/components/Icon.svelte"
import {publishReaction} from "@app/commands"
export let url, room, event
let {url, room, event} = $props()
// Tell svelte-check to shut up
noop(room)

View File

@@ -7,9 +7,7 @@
import ConfirmDelete from "@app/components/ConfirmDelete.svelte"
import {pushModal} from "@app/modal"
export let url
export let event
export let onClick
let {url, event, onClick} = $props()
const report = () => {
onClick()

View File

@@ -6,24 +6,26 @@
import Tippy from "@lib/components/Tippy.svelte"
import ChannelMessageMenu from "@app/components/ChannelMessageMenu.svelte"
export let url, event
let {url, event} = $props()
const open = () => popover.show()
const open = () => popover?.show()
const onClick = () => popover.hide()
const onClick = () => popover?.hide()
const onMouseMove = ({clientX, clientY}: any) => {
const {x, y, width, height} = popover.popper.getBoundingClientRect()
if (popover) {
const {x, y, width, height} = popover.popper.getBoundingClientRect()
if (!between([x, x + width], clientX) || !between([y, y + height + 30], clientY)) {
popover.hide()
if (!between([x, x + width], clientX) || !between([y, y + height + 30], clientY)) {
popover.hide()
}
}
}
let popover: Instance
let popover: Instance | undefined = $state()
</script>
<svelte:document on:mousemove={onMouseMove} />
<svelte:document onmousemove={onMouseMove} />
<div class="flex">
<Button class="btn join-item btn-xs" on:click={open}>

View File

@@ -9,9 +9,7 @@
import {publishReaction} from "@app/commands"
import {pushModal} from "@app/modal"
export let url
export let event
export let reply
let {url, event, reply} = $props()
const onEmoji = (emoji: NativeEmoji) => {
history.back()

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import {GENERAL, channelsById, makeChannelId} from "@app/state"
export let url
export let room
let {url, room} = $props()
</script>
{#if room === GENERAL}

View File

@@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
type Element = {
id: string
type: "date" | "note"
@@ -9,7 +9,6 @@
<script lang="ts">
import {onMount} from "svelte"
import {derived} from "svelte/store"
import {int, nthNe, MINUTE, sortBy, remove} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {createEvent, DIRECT_MESSAGE, INBOX_RELAYS} from "@welshman/util"
@@ -38,14 +37,12 @@
import {pushModal} from "@app/modal"
import {sendWrapped, prependParent} from "@app/commands"
export let id
let {id, info = undefined} = $props()
const chat = deriveChat(id)
const pubkeys = splitChatId(id)
const others = remove($pubkey!, pubkeys)
const missingInboxes = derived(inboxRelaySelectionsByPubkey, $m =>
pubkeys.filter(pk => !$m.has(pk)),
)
const missingInboxes = $derived(pubkeys.filter(pk => !$inboxRelaySelectionsByPubkey.has(pk)))
const assertEvent = (e: any) => e as TrustedEvent
@@ -56,7 +53,7 @@
const replyTo = (event: TrustedEvent) => {
parent = event
compose.focus()
compose?.focus()
}
const clearParent = () => {
@@ -76,13 +73,12 @@
clearParent()
}
let loading = true
let parent: TrustedEvent | undefined
let elements: Element[] = []
let compose: ChatCompose
let loading = $state(true)
let compose: ChatCompose | undefined = $state()
let parent: TrustedEvent | undefined = $state()
$: {
elements = []
const elements = $derived.by(() => {
const elements = []
let previousDate
let previousPubkey
@@ -108,8 +104,8 @@
previousCreatedAt = created_at
}
elements.reverse()
}
return elements.reverse()
})
onMount(() => {
// Don't use loadInboxRelaySelection because we want to force reload
@@ -124,50 +120,54 @@
<div class="relative flex h-full w-full flex-col">
{#if others.length > 0}
<PageBar>
<div slot="title" class="flex flex-col gap-1 sm:flex-row sm:gap-2">
{#if others.length === 1}
{@const pubkey = others[0]}
{@const onClick = () => pushModal(ProfileDetail, {pubkey})}
<Button on:click={onClick} class="row-2">
<ProfileCircle {pubkey} size={5} />
<ProfileName {pubkey} />
</Button>
{:else}
<div class="flex items-center gap-2">
<ProfileCircles pubkeys={others} size={5} />
<p class="overflow-hidden text-ellipsis whitespace-nowrap">
<ProfileName pubkey={others[0]} />
and
{#if others.length === 2}
<ProfileName pubkey={others[1]} />
{:else}
{others.length - 1}
{others.length > 2 ? "others" : "other"}
{/if}
</p>
</div>
{#if others.length > 2}
<Button on:click={showMembers} class="btn btn-link hidden sm:block"
>Show all members</Button>
{#snippet title()}
<div class="flex flex-col gap-1 sm:flex-row sm:gap-2">
{#if others.length === 1}
{@const pubkey = others[0]}
{@const onClick = () => pushModal(ProfileDetail, {pubkey})}
<Button on:click={onClick} class="row-2">
<ProfileCircle {pubkey} size={5} />
<ProfileName {pubkey} />
</Button>
{:else}
<div class="flex items-center gap-2">
<ProfileCircles pubkeys={others} size={5} />
<p class="overflow-hidden text-ellipsis whitespace-nowrap">
<ProfileName pubkey={others[0]} />
and
{#if others.length === 2}
<ProfileName pubkey={others[1]} />
{:else}
{others.length - 1}
{others.length > 2 ? "others" : "other"}
{/if}
</p>
</div>
{#if others.length > 2}
<Button on:click={showMembers} class="btn btn-link hidden sm:block"
>Show all members</Button>
{/if}
{/if}
{/if}
</div>
<div slot="action">
{#if remove($pubkey, $missingInboxes).length > 0}
{@const count = remove($pubkey, $missingInboxes).length}
{@const label = count > 1 ? "inboxes are" : "inbox is"}
<div
class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer"
data-tip="{count} {label} not configured.">
<Icon icon="danger" />
{count}
</div>
{/if}
</div>
</div>
{/snippet}
{#snippet action()}
<div>
{#if remove($pubkey, missingInboxes).length > 0}
{@const count = remove($pubkey, missingInboxes).length}
{@const label = count > 1 ? "inboxes are" : "inbox is"}
<div
class="row-2 badge badge-error badge-lg tooltip tooltip-left cursor-pointer"
data-tip="{count} {label} not configured.">
<Icon icon="danger" />
{count}
</div>
{/if}
</div>
{/snippet}
</PageBar>
{/if}
<div class="-mt-2 flex flex-grow flex-col-reverse overflow-auto py-2">
{#if $missingInboxes.includes(assertNotNil($pubkey))}
{#if missingInboxes.includes(assertNotNil($pubkey))}
<div class="py-12">
<div class="card2 col-2 m-auto max-w-md items-center text-center">
<p class="row-2 text-lg text-error">
@@ -198,7 +198,7 @@
End of message history
{/if}
</Spinner>
<slot name="info" />
{@render info?.()}
</p>
</div>
{#if parent}

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {goto} from "$app/navigation"
import {WRAP} from "@welshman/util"
import {repository} from "@welshman/app"
@@ -10,9 +12,9 @@
import {canDecrypt, PLATFORM_NAME, ensureUnwrapped} from "@app/state"
import {clearModals} from "@app/modal"
export let next
let {next} = $props()
let loading = false
let loading = $state(false)
const enableChat = async () => {
canDecrypt.set(true)
@@ -38,10 +40,14 @@
const back = () => history.back()
</script>
<form class="column gap-4" on:submit|preventDefault={submit}>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
<div slot="title">Enable Messages</div>
<div slot="info">Do you want to enable direct messages?</div>
{#snippet title()}
<div>Enable Messages</div>
{/snippet}
{#snippet info()}
<div>Do you want to enable direct messages?</div>
{/snippet}
</ModalHeader>
<p>
By default, direct messages are disabled, since loading them requires

View File

@@ -12,13 +12,18 @@
import {makeChatPath} from "@app/routes"
import {notifications} from "@app/notifications"
export let id: string
export let pubkeys: string[]
export let messages: TrustedEvent[]
interface Props {
id: string
pubkeys: string[]
messages: TrustedEvent[]
[key: string]: any
}
const others = remove($pubkey!, pubkeys)
const active = $page.params.chat === id
const path = makeChatPath(pubkeys)
let {...props}: Props = $props()
const others = remove($pubkey!, props.pubkeys)
const active = $page.params.chat === props.id
const path = makeChatPath(props.pubkeys)
onMount(() => {
for (const pk of others) {
@@ -27,9 +32,9 @@
})
</script>
<Link class="flex flex-col justify-start gap-1" href={makeChatPath(pubkeys)}>
<Link class="flex flex-col justify-start gap-1" href={makeChatPath(props.pubkeys)}>
<div
class="cursor-pointer border-t border-solid border-base-100 px-6 py-2 transition-colors hover:bg-base-100 {$$props.class}"
class="cursor-pointer border-t border-solid border-base-100 px-6 py-2 transition-colors hover:bg-base-100 {props.class}"
class:bg-base-100={active}>
<div class="flex flex-col justify-start gap-1">
<div class="flex items-center justify-between gap-2">
@@ -54,7 +59,7 @@
{/if}
</div>
<p class="overflow-hidden text-ellipsis whitespace-nowrap text-sm">
{messages[0].content}
{props.messages[0].content}
</p>
</div>
</div>

View File

@@ -25,10 +25,14 @@
import {makeDelete, makeReaction, sendWrapped} from "@app/commands"
import {pushModal} from "@app/modal"
export let event: TrustedEvent
export let replyTo: any = undefined
export let pubkeys: string[]
export let showPubkey = false
interface Props {
event: TrustedEvent
replyTo?: any
pubkeys: string[]
showPubkey?: boolean
}
let {event, replyTo = undefined, pubkeys, showPubkey = false}: Props = $props()
const thunk = $thunks[event.id]
const isOwn = event.pubkey === $pubkey
@@ -49,14 +53,14 @@
const togglePopover = () => {
if (popoverIsVisible) {
popover.hide()
popover?.hide()
} else {
popover.show()
popover?.show()
}
}
let popover: Instance
let popoverIsVisible = false
let popover: Instance | undefined = $state()
let popoverIsVisible = $state(false)
</script>
{#if thunk}
@@ -86,7 +90,7 @@
type="button"
class="opacity-0 transition-all"
class:group-hover:opacity-100={!isMobile}
on:click={togglePopover}>
onclick={togglePopover}>
<Icon icon="menu-dots" size={4} />
</button>
</Tippy>

View File

@@ -5,8 +5,12 @@
import EmojiButton from "@lib/components/EmojiButton.svelte"
import {makeReaction, sendWrapped} from "@app/commands"
export let event: TrustedEvent
export let pubkeys: string[]
interface Props {
event: TrustedEvent
pubkeys: string[]
}
let {event, pubkeys}: Props = $props()
const onEmoji = (emoji: NativeEmoji) =>
sendWrapped({template: makeReaction({event, content: emoji.unicode}), pubkeys})

View File

@@ -5,10 +5,7 @@
import EventInfo from "@app/components/EventInfo.svelte"
import {pushModal} from "@app/modal"
export let event
export let pubkeys
export let popover
export let replyTo
let {event, pubkeys, popover, replyTo} = $props()
const reply = () => replyTo(event)

View File

@@ -8,8 +8,7 @@
import {pushModal} from "@app/modal"
import {clip} from "@app/toast"
export let event
export let pubkeys
let {event, pubkeys} = $props()
const onEmoji = (emoji: NativeEmoji) => {
history.back()

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {goto} from "$app/navigation"
import {pubkey} from "@welshman/app"
import Field from "@lib/components/Field.svelte"
@@ -13,18 +15,24 @@
const onSubmit = () => goto(makeChatPath([...pubkeys, $pubkey!]))
let pubkeys: string[] = []
let pubkeys: string[] = $state([])
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
<div slot="title">Start a Chat</div>
<div slot="info">Create an encrypted chat room for private conversations.</div>
{#snippet title()}
<div>Start a Chat</div>
{/snippet}
{#snippet info()}
<div>Create an encrypted chat room for private conversations.</div>
{/snippet}
</ModalHeader>
<Field>
<div slot="input">
<ProfileMultiSelect autofocus bind:value={pubkeys} />
</div>
{#snippet input()}
<div>
<ProfileMultiSelect autofocus bind:value={pubkeys} />
</div>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -3,8 +3,7 @@
import {publishDelete} from "@app/commands"
import {clearModals} from "@app/modal"
export let url
export let event
let {url, event} = $props()
const confirm = async () => {
await publishDelete({event, relays: [url]})

View File

@@ -1,4 +1,7 @@
<script lang="ts">
import Content from "./Content.svelte"
import {preventDefault, stopPropagation} from "svelte/legacy"
import {fromNostrURI} from "@welshman/util"
import {nthEq} from "@welshman/lib"
import {
@@ -30,14 +33,27 @@
import ContentMention from "@app/components/ContentMention.svelte"
import {entityLink, userSettingValues} from "@app/state"
export let event
export let minLength = 500
export let maxLength = 700
export let showEntire = false
export let hideMedia = false
export let expandMode = "block"
export let quoteProps: Record<string, any> = {}
export let depth = 0
interface Props {
event: any
minLength?: number
maxLength?: number
showEntire?: boolean
hideMedia?: boolean
expandMode?: string
quoteProps?: Record<string, any>
depth?: number
}
let {
event,
minLength = 500,
maxLength = 700,
showEntire = $bindable(false),
hideMedia = false,
expandMode = "block",
quoteProps = {},
depth = 0,
}: Props = $props()
const fullContent = parse(event)
@@ -82,20 +98,23 @@
warning = null
}
let warning =
$userSettingValues.hide_sensitive && event.tags.find(nthEq(0, "content-warning"))?.[1]
let warning = $state(
$userSettingValues.hide_sensitive && event.tags.find(nthEq(0, "content-warning"))?.[1],
)
$: shortContent = showEntire
? fullContent
: truncate(fullContent, {
minLength,
maxLength,
mediaLength: hideMedia ? 20 : 200,
})
let shortContent = $derived(
showEntire
? fullContent
: truncate(fullContent, {
minLength,
maxLength,
mediaLength: hideMedia ? 20 : 200,
}),
)
$: hasEllipsis = shortContent.some(isEllipsis)
$: expandInline = hasEllipsis && expandMode === "inline"
$: expandBlock = hasEllipsis && expandMode === "block"
let hasEllipsis = $derived(shortContent.some(isEllipsis))
let expandInline = $derived(hasEllipsis && expandMode === "inline")
let expandBlock = $derived(hasEllipsis && expandMode === "block")
</script>
<div class="relative">
@@ -133,9 +152,9 @@
{:else if isEvent(parsed) || isAddress(parsed)}
{#if isBlock(i)}
<ContentQuote {...quoteProps} value={parsed.value} {depth} {event}>
<div slot="note-content" let:event>
<svelte:self {quoteProps} {hideMedia} {event} depth={depth + 1} />
</div>
{#snippet noteContent({event})}
<Content {quoteProps} {hideMedia} {event} depth={depth + 1} />
{/snippet}
</ContentQuote>
{:else}
<Link
@@ -158,7 +177,7 @@
<button
type="button"
class="btn btn-neutral"
on:click|stopPropagation|preventDefault={expand}>
onclick={stopPropagation(preventDefault(expand))}>
See more
</button>
</div>

View File

@@ -1,6 +1,5 @@
<script lang="ts">
export let value
export let isBlock
let {value, isBlock} = $props()
</script>
<code

View File

@@ -1,11 +1,13 @@
<script lang="ts">
import {preventDefault, stopPropagation} from "svelte/legacy"
import {ellipsize, postJson} from "@welshman/lib"
import {dufflepud, imgproxy} from "@app/state"
import Link from "@lib/components/Link.svelte"
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
import {pushModal} from "@app/modal"
export let value
let {value} = $props()
const url = value.url.toString()
@@ -29,7 +31,7 @@
<track kind="captions" />
</video>
{:else if url.match(/\.(jpe?g|png|gif|webp)$/)}
<button type="button" on:click|stopPropagation|preventDefault={expand}>
<button type="button" onclick={stopPropagation(preventDefault(expand))}>
<img alt="Link preview" src={imgproxy(url)} class="m-auto max-h-96 rounded-box" />
</button>
{:else}

View File

@@ -2,7 +2,7 @@
import Button from "@lib/components/Button.svelte"
import {imgproxy} from "@app/state"
export let url
let {url} = $props()
const back = () => history.back()
</script>

View File

@@ -1,11 +1,13 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {displayUrl} from "@welshman/lib"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import ContentLinkDetail from "@app/components/ContentLinkDetail.svelte"
import {pushModal} from "@app/modal"
export let value
let {value} = $props()
const url = value.url.toString()
@@ -14,7 +16,7 @@
{#if url.match(/\.(jpe?g|png|gif|webp)$/)}
<!-- Use a real link so people can copy the href -->
<a href={url} class="link-content whitespace-nowrap" on:click|preventDefault={expand}>
<a href={url} class="link-content whitespace-nowrap" onclick={preventDefault(expand)}>
<Icon icon="link-round" size={3} class="inline-block" />
{displayUrl(url)}
</a>

View File

@@ -5,7 +5,7 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal"
export let value
let {value} = $props()
const profile = deriveProfile(value.pubkey)

View File

@@ -1,5 +1,5 @@
<script lang="ts">
export let value
let {value} = $props()
</script>
{#each value as _}

View File

@@ -10,11 +10,7 @@
import {deriveEvent, entityLink, ROOM} from "@app/state"
import {makeThreadPath, makeRoomPath} from "@app/routes"
export let value
export let event
export let depth = 0
export let relays: string[] = []
export let minimal = false
let props = ({value, event, depth = 0, relays = [], minimal = false, ...restProps} = $props())
const {id, identifier, kind, pubkey, relays: relayHints = []} = value
const idOrAddress = id || new Address(kind, pubkey, identifier).toString()
@@ -100,7 +96,7 @@
<Button class="my-2 block max-w-full text-left" on:click={onClick}>
{#if $quote}
<NoteCard {minimal} event={$quote} class="bg-alt rounded-box p-4">
<slot name="note-content" event={$quote} {depth} />
{@render noteContent({event: $quote, depth})}
</NoteCard>
{:else}
<div class="rounded-box p-4">

View File

@@ -3,7 +3,7 @@
import Button from "@lib/components/Button.svelte"
import {clip} from "@app/toast"
export let value
let {value} = $props()
const copy = () => clip(value)
</script>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
export let value
let {value} = $props()
</script>
<span class="link-content">

View File

@@ -7,15 +7,14 @@
import {pushModal} from "@app/modal"
import {BURROW_URL} from "@app/state"
export let email
export let confirm_token
let {email, confirm_token} = $props()
const login = () => {
pushModal(LogInPassword, {email}, {path: "/"})
}
let error: string
let loading = true
let error = $state("")
let loading = $state(true)
onMount(async () => {
const [res] = await Promise.all([

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {EditorContent} from "svelte-tiptap"
import {writable} from "svelte/store"
import {randomId} from "@welshman/lib"
@@ -14,7 +16,7 @@
import {makeEditor} from "@app/editor"
import {pushToast} from "@app/toast"
export let url
let {url} = $props()
const uploading = writable(false)
@@ -56,61 +58,77 @@
const editor = makeEditor({submit, uploading})
let title = ""
let location = ""
let start: Date
let end: Date
let title = $state("")
let location = $state("")
let start: Date | undefined = $state()
let end: Date | undefined = $state()
</script>
<form class="column gap-4" on:submit|preventDefault={submit}>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
<div slot="title">Create an Event</div>
<div slot="info">Invite other group members to events online or in real life.</div>
{#snippet title()}
<div>Create an Event</div>
{/snippet}
{#snippet info()}
<div>Invite other group members to events online or in real life.</div>
{/snippet}
</ModalHeader>
<Field>
<p slot="label">Title*</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<input bind:value={title} class="grow" type="text" />
</label>
{#snippet label()}
<p>Title*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<input bind:value={title} class="grow" type="text" />
</label>
{/snippet}
</Field>
<Field>
<p slot="label">Summary</p>
<div
slot="input"
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
<div class="input-editor flex-grow overflow-hidden">
<EditorContent editor={$editor} />
{#snippet label()}
<p>Summary</p>
{/snippet}
{#snippet input()}
<div class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
<div class="input-editor flex-grow overflow-hidden">
<EditorContent editor={$editor} />
</div>
<Button
data-tip="Add an image"
class="center btn tooltip"
on:click={() => $editor.chain().selectFiles().run()}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="gallery-send" />
{/if}
</Button>
</div>
<Button
data-tip="Add an image"
class="center btn tooltip"
on:click={() => $editor.chain().selectFiles().run()}>
{#if $uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="gallery-send" />
{/if}
</Button>
</div>
{/snippet}
</Field>
<Field>
<div slot="input" class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1">
<strong>Start</strong>
<DateTimeInput bind:value={start} />
{#snippet input()}
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1">
<strong>Start</strong>
<DateTimeInput bind:value={start} />
</div>
<div class="flex flex-col gap-1">
<strong>End</strong>
<DateTimeInput bind:value={end} />
</div>
</div>
<div class="flex flex-col gap-1">
<strong>End</strong>
<DateTimeInput bind:value={end} />
</div>
</div>
{/snippet}
</Field>
<Field>
<p slot="label">Location (optional)</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="map-point" />
<input bind:value={location} class="grow" type="text" />
</label>
{#snippet label()}
<p>Location (optional)</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="map-point" />
<input bind:value={location} class="grow" type="text" />
</label>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -7,7 +7,7 @@
import ModalHeader from "@lib/components/ModalHeader.svelte"
import {clip} from "@app/toast"
export let event
let {event} = $props()
const relays = ctx.app.router.Event(event).getUrls()
const nevent1 = nip19.neventEncode({...event, relays})
@@ -20,28 +20,40 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">Event Details</div>
<div slot="info">The full details of this event are shown below.</div>
{#snippet title()}
<div>Event Details</div>
{/snippet}
{#snippet info()}
<div>The full details of this event are shown below.</div>
{/snippet}
</ModalHeader>
<FieldInline>
<p slot="label">Event Link</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="file" />
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
<Button on:click={copyLink} class="flex items-center">
<Icon icon="copy" />
</Button>
</label>
{#snippet label()}
<p>Event Link</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="file" />
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
<Button on:click={copyLink} class="flex items-center">
<Icon icon="copy" />
</Button>
</label>
{/snippet}
</FieldInline>
<FieldInline>
<p slot="label">Author Pubkey</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-circle" />
<input type="text" class="ellipsize min-w-0 grow" value={npub1} />
<Button on:click={copyPubkey} class="flex items-center">
<Icon icon="copy" />
</Button>
</label>
{#snippet label()}
<p>Author Pubkey</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-circle" />
<input type="text" class="ellipsize min-w-0 grow" value={npub1} />
<Button on:click={copyPubkey} class="flex items-center">
<Icon icon="copy" />
</Button>
</label>
{/snippet}
</FieldInline>
<div class="relative">
<pre class="card2 card2-sm bg-alt overflow-auto text-xs"><code>{json}</code></pre>

View File

@@ -3,14 +3,14 @@
import {formatTimestamp, formatTimestampAsDate, formatTimestampAsTime} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
export let event
let {event} = $props()
$: meta = fromPairs(event.tags) as Record<string, string>
$: end = parseInt(meta.end)
$: start = parseInt(meta.start)
$: startDateDisplay = formatTimestampAsDate(start)
$: endDateDisplay = formatTimestampAsDate(end)
$: isSingleDay = startDateDisplay === endDateDisplay
let meta = $derived(fromPairs(event.tags) as Record<string, string>)
let end = $derived(parseInt(meta.end))
let start = $derived(parseInt(meta.start))
let startDateDisplay = $derived(formatTimestampAsDate(start))
let endDateDisplay = $derived(formatTimestampAsDate(end))
let isSingleDay = $derived(startDateDisplay === endDateDisplay)
</script>
<div class="card2 flex items-center justify-between gap-2">

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
@@ -8,8 +10,7 @@
import {pushToast} from "@app/toast"
import {publishReport} from "@app/commands"
export let url
export let event
let {url, event} = $props()
const back = () => history.back()
@@ -31,34 +32,50 @@
return pushToast({message: "Your report has been sent!"})
}
let reason = ""
let content = ""
let loading = false
let reason = $state("")
let content = $state("")
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={confirm}>
<form class="column gap-4" onsubmit={preventDefault(confirm)}>
<ModalHeader>
<div slot="title">Report Content</div>
<div slot="info">Flag inappropriate content.</div>
{#snippet title()}
<div>Report Content</div>
{/snippet}
{#snippet info()}
<div>Flag inappropriate content.</div>
{/snippet}
</ModalHeader>
<Field>
<p slot="label">Reason*</p>
<select slot="input" class="select select-bordered" bind:value={reason}>
<option disabled selected>Choose a reason</option>
<option>Nudity</option>
<option>Malware</option>
<option>Profanity</option>
<option>Illegal</option>
<option>Spam</option>
<option>Impersonation</option>
<option>Other</option>
</select>
<p slot="info">Please select a reason for your report.</p>
{#snippet label()}
<p>Reason*</p>
{/snippet}
{#snippet input()}
<select class="select select-bordered" bind:value={reason}>
<option disabled selected>Choose a reason</option>
<option>Nudity</option>
<option>Malware</option>
<option>Profanity</option>
<option>Illegal</option>
<option>Spam</option>
<option>Impersonation</option>
<option>Other</option>
</select>
{/snippet}
{#snippet info()}
<p>Please select a reason for your report.</p>
{/snippet}
</Field>
<Field>
<p slot="label">Details</p>
<textarea slot="input" class="textarea textarea-bordered" bind:value={content}></textarea>
<p slot="info">Please provide any additional details relevant to your report.</p>
{#snippet label()}
<p>Details</p>
{/snippet}
{#snippet input()}
<textarea class="textarea textarea-bordered" bind:value={content}></textarea>
{/snippet}
{#snippet info()}
<p>Please provide any additional details relevant to your report.</p>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -8,8 +8,7 @@
import Profile from "@app/components/Profile.svelte"
import {publishDelete} from "@app/commands"
export let url
export let event
let {url, event} = $props()
const reports = deriveEvents(repository, {
filters: [{kinds: [REPORT], "#e": [event.id]}],
@@ -30,8 +29,12 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">Report Details</div>
<div slot="info">All reports for this event are shown below.</div>
{#snippet title()}
<div>Report Details</div>
{/snippet}
{#snippet info()}
<div>All reports for this event are shown below.</div>
{/snippet}
</ModalHeader>
{#each $reports as report (report.id)}
{@const reason = getReason(report.tags)}

View File

@@ -7,7 +7,9 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">What is a bunker link?</div>
{#snippet title()}
<div>What is a bunker link?</div>
{/snippet}
</ModalHeader>
<p>
<Link external class="link" href="https://nostr.com/">Nostr</Link> uses "keys" instead of passwords

View File

@@ -7,7 +7,9 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">What is a nostr address?</div>
{#snippet title()}
<div>What is a nostr address?</div>
{/snippet}
</ModalHeader>
<p>
{PLATFORM_NAME} hosts spaces on the <Link external href="https://nostr.com/" class="underline"

View File

@@ -16,7 +16,9 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">What is a private key?</div>
{#snippet title()}
<div>What is a private key?</div>
{/snippet}
</ModalHeader>
<p>
Most online services keep track of users by giving them a username and password. This gives the

View File

@@ -6,7 +6,9 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">What is nostr?</div>
{#snippet title()}
<div>What is nostr?</div>
{/snippet}
</ModalHeader>
<p>
<Link external href="https://nostr.com/" class="link">Nostr</Link> is way to build social apps that

View File

@@ -7,7 +7,9 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">What is a relay?</div>
{#snippet title()}
<div>What is a relay?</div>
{/snippet}
</ModalHeader>
<p>
{PLATFORM_NAME} hosts spaces on the <Link external href="https://nostr.com/" class="underline"

View File

@@ -22,16 +22,28 @@
</div>
<Button on:click={logIn}>
<CardButton class="!btn-primary">
<div slot="icon"><Icon icon="login-2" size={7} /></div>
<div slot="title">Log in</div>
<div slot="info">If you've been here before, you know the drill.</div>
{#snippet icon()}
<div><Icon icon="login-2" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Log in</div>
{/snippet}
{#snippet info()}
<div>If you've been here before, you know the drill.</div>
{/snippet}
</CardButton>
</Button>
<Button on:click={signUp}>
<CardButton>
<div slot="icon"><Icon icon="add-circle" size={7} /></div>
<div slot="title">Create an account</div>
<div slot="info">Just a few questions and you'll be on your way.</div>
{#snippet icon()}
<div><Icon icon="add-circle" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Create an account</div>
{/snippet}
{#snippet info()}
<div>Just a few questions and you'll be on your way.</div>
{/snippet}
</CardButton>
</Button>
<p class="text-center text-xs opacity-75">

View File

@@ -70,10 +70,10 @@
const loginWithBunker = () => pushModal(LogInBunker)
let signers: any[] = []
let loading: string | undefined
let signers: any[] = $state([])
let loading: string | undefined = $state()
$: hasSigner = getNip07() || signers.length > 0
let hasSigner = $derived(getNip07() || signers.length > 0)
onMount(async () => {
if (Capacitor.isNativePlatform()) {

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {run, preventDefault} from "svelte/legacy"
import {onMount, onDestroy} from "svelte"
import {Nip46Broker, getPubkey, makeSecret} from "@welshman/signer"
import {addSession} from "@welshman/app"
@@ -59,18 +61,18 @@
clearModals()
}
let url = ""
let input = ""
let loading = false
let url = $state("")
let input = $state("")
let loading = $state(false)
$: {
run(() => {
// For testing and for play store reviewers
if (input === "reviewkey") {
const secret = makeSecret()
addSession({method: "nip01", secret, pubkey: getPubkey(secret)})
}
}
})
onMount(async () => {
url = await broker.makeNostrconnectUrl({
@@ -121,12 +123,14 @@
})
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
<div slot="title">Log In</div>
<div slot="info">
Connect your signer by scanning the QR code below or pasting a bunker link.
</div>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Connect your signer by scanning the QR code below or pasting a bunker link.</div>
{/snippet}
</ModalHeader>
{#if !loading && url}
<div class="flex justify-center" out:slideAndFade>
@@ -134,15 +138,21 @@
</div>
{/if}
<Field>
<p slot="label">Bunker Link*</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="cpu" />
<input disabled={loading} bind:value={input} class="grow" placeholder="bunker://" />
</label>
<p slot="info">
A login link provided by a nostr signing app.
<Button class="link" on:click={() => pushModal(InfoBunker)}>What is a bunker link?</Button>
</p>
{#snippet label()}
<p>Bunker Link*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="cpu" />
<input disabled={loading} bind:value={input} class="grow" placeholder="bunker://" />
</label>
{/snippet}
{#snippet info()}
<p>
A login link provided by a nostr signing app.
<Button class="link" on:click={() => pushModal(InfoBunker)}>What is a bunker link?</Button>
</p>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back} disabled={loading}>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {onMount, onDestroy} from "svelte"
import {postJson, stripProtocol} from "@welshman/lib"
import {Nip46Broker, makeSecret} from "@welshman/signer"
@@ -17,7 +19,11 @@
import {pushToast} from "@app/toast"
import {NIP46_PERMS, BURROW_URL, PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO} from "@app/state"
export let email = ""
interface Props {
email?: string
}
let {email = $bindable("")}: Props = $props()
const clientSecret = makeSecret()
@@ -50,8 +56,8 @@
}
let url = ""
let password = ""
let loading = false
let password = $state("")
let loading = $state(false)
onMount(async () => {
url = await broker.makeNostrconnectUrl({
@@ -100,24 +106,36 @@
})
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
<div slot="title">Log In</div>
<div slot="info">Log in using your email and password</div>
{#snippet title()}
<div>Log In</div>
{/snippet}
{#snippet info()}
<div>Log in using your email and password</div>
{/snippet}
</ModalHeader>
<FieldInline>
<p slot="label">Email</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-rounded" />
<input bind:value={email} />
</label>
{#snippet label()}
<p>Email</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
<FieldInline>
<p slot="label">Password</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="key" />
<input bind:value={password} type="password" />
</label>
{#snippet label()}
<p>Password</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" />
<input bind:value={password} type="password" />
</label>
{/snippet}
</FieldInline>
<p class="text-sm">
Your email and password only work to log in to {PLATFORM_NAME}. To use your key on other nostr

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
@@ -19,12 +21,14 @@
}
}
let loading = false
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={doLogout}>
<form class="column gap-4" onsubmit={preventDefault(doLogout)}>
<ModalHeader>
<div slot="title">Are you sure you want<br />to log out?</div>
{#snippet title()}
<div>Are you sure you want<br />to log out?</div>
{/snippet}
</ModalHeader>
<p class="text-center">Your local database will be cleared.</p>
<ModalFooter>

View File

@@ -13,30 +13,54 @@
<div class="column menu gap-2">
<Link replaceState href="/settings/profile">
<CardButton>
<div slot="icon"><Icon icon="user-rounded" size={7} /></div>
<div slot="title">Profile</div>
<div slot="info">Customize your user profile</div>
{#snippet icon()}
<div><Icon icon="user-rounded" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Profile</div>
{/snippet}
{#snippet info()}
<div>Customize your user profile</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/relays">
<CardButton>
<div slot="icon"><Icon icon="server" size={7} /></div>
<div slot="title">Relays</div>
<div slot="info">Control how {PLATFORM_NAME} talks to the network</div>
{#snippet icon()}
<div><Icon icon="server" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Relays</div>
{/snippet}
{#snippet info()}
<div>Control how {PLATFORM_NAME} talks to the network</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings">
<CardButton>
<div slot="icon"><Icon icon="settings" size={7} /></div>
<div slot="title">Settings</div>
<div slot="info">Get into the details about how {PLATFORM_NAME} works</div>
{#snippet icon()}
<div><Icon icon="settings" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Settings</div>
{/snippet}
{#snippet info()}
<div>Get into the details about how {PLATFORM_NAME} works</div>
{/snippet}
</CardButton>
</Link>
<Link replaceState href="/settings/about">
<CardButton>
<div slot="icon"><Icon icon="code-2" size={7} /></div>
<div slot="title">About</div>
<div slot="info">Learn about {PLATFORM_NAME} and support the developer</div>
{#snippet icon()}
<div><Icon icon="code-2" size={7} /></div>
{/snippet}
{#snippet title()}
<div>About</div>
{/snippet}
{#snippet info()}
<div>Learn about {PLATFORM_NAME} and support the developer</div>
{/snippet}
</CardButton>
</Link>
<Button on:click={logout} class="btn btn-neutral">

View File

@@ -26,7 +26,7 @@
import {pushModal} from "@app/modal"
import {makeSpacePath} from "@app/routes"
export let url
let {url} = $props()
const threadsPath = makeSpacePath(url, "threads")
const userRooms = deriveUserRooms(url)
@@ -55,14 +55,16 @@
const addRoom = () => pushModal(RoomCreate, {url}, {replaceState})
let showMenu = false
let showMenu = $state(false)
let replaceState = false
let element: Element
let element: Element | undefined = $state()
$: members = $memberships.filter(l => hasMembershipUrl(l, url)).map(l => l.event.pubkey)
let members = $derived(
$memberships.filter(l => hasMembershipUrl(l, url)).map(l => l.event.pubkey),
)
onMount(async () => {
replaceState = Boolean(element.closest(".drawer"))
replaceState = Boolean(element?.closest(".drawer"))
pullConservatively({relays: [url], filters: [{kinds: [GROUP_META]}]})
})
</script>

View File

@@ -6,7 +6,7 @@
import {makeSpacePath} from "@app/routes"
import {pushDrawer} from "@app/modal"
export let url
let {url} = $props()
const path = makeSpacePath(url)

View File

@@ -6,9 +6,13 @@
import {deriveChannel, channelIsLocked} from "@app/state"
import {notifications} from "@app/notifications"
export let url
export let room
export let notify = false
interface Props {
url: any
room: any
notify?: boolean
}
let {url, room, notify = false}: Props = $props()
const path = makeRoomPath(url, room)
const channel = deriveChannel(url, room)

View File

@@ -24,9 +24,15 @@
{#if !PLATFORM_RELAY}
<Button on:click={addSpace}>
<CardButton>
<div slot="icon"><Icon icon="login-2" size={7} /></div>
<div slot="title">Add a space</div>
<div slot="info">Join or create a new space</div>
{#snippet icon()}
<div><Icon icon="login-2" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Add a space</div>
{/snippet}
{#snippet info()}
<div>Join or create a new space</div>
{/snippet}
</CardButton>
</Button>
{/if}

View File

@@ -7,20 +7,26 @@
import {makeSpacePath} from "@app/routes"
import {notifications} from "@app/notifications"
export let url
let {url} = $props()
const path = makeSpacePath(url)
</script>
<Link replaceState href={path}>
<CardButton>
<div slot="icon"><SpaceAvatar {url} /></div>
<div slot="title" class="flex gap-1">
<RelayName {url} />
{#if $notifications.has(path)}
<div class="relative top-1 h-2 w-2 rounded-full bg-primary"></div>
{/if}
</div>
<div slot="info"><RelayDescription {url} /></div>
{#snippet icon()}
<div><SpaceAvatar {url} /></div>
{/snippet}
{#snippet title()}
<div class="flex gap-1">
<RelayName {url} />
{#if $notifications.has(path)}
<div class="relative top-1 h-2 w-2 rounded-full bg-primary"></div>
{/if}
</div>
{/snippet}
{#snippet info()}
<div><RelayDescription {url} /></div>
{/snippet}
</CardButton>
</Link>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {run} from "svelte/legacy"
import {page} from "$app/stores"
import Drawer from "@lib/components/Drawer.svelte"
import Dialog from "@lib/components/Dialog.svelte"
@@ -10,25 +12,29 @@
}
}
let modal: any
let modal: any = $state()
let hash = $derived($page.url.hash.slice(1))
let hashIsValid = $derived(Boolean($modals[hash]))
$: hash = $page.url.hash.slice(1)
$: hashIsValid = Boolean($modals[hash])
$: modal = $modals[hash] || modal
$effect(() => {
if ($modals[hash]) {
modal = $modals[hash]
}
})
</script>
<svelte:window on:keydown={onKeyDown} />
<svelte:window onkeydown={onKeyDown} />
{#if hashIsValid && modal?.options?.drawer}
<Drawer onClose={clearModals} {...modal.options}>
{#key modal.id}
<svelte:component this={modal.component} {...modal.props} />
<modal.component {...modal.props} />
{/key}
</Drawer>
{:else if hashIsValid && modal}
<Dialog onClose={clearModals} {...modal.options}>
{#key modal.id}
<svelte:component this={modal.component} {...modal.props} />
<modal.component {...modal.props} />
{/key}
</Dialog>
{/if}

View File

@@ -11,9 +11,13 @@
import ProfileName from "@app/components/ProfileName.svelte"
import {entityLink} from "@app/state"
export let event
export let minimal = false
export let hideProfile = false
interface Props {
event: TrustedEvent
minimal: boolean
hideProfile: boolean
}
let {event, children, minimal = false, hideProfile = false, ...restProps} = $props()
const relays = ctx.app.router.Event(event).getUrls()
const nevent = nip19.neventEncode({id: event.id, relays})
@@ -25,7 +29,7 @@
let muted = getPubkeyTagValues(getListTags($userMutes)).includes(event.pubkey)
</script>
<div class="flex flex-col gap-2 {$$props.class}">
<div class="flex flex-col gap-2 {restProps.class}">
{#if muted}
<div class="flex items-center justify-between">
<div class="row-2 relative">
@@ -50,6 +54,6 @@
{formatTimestamp(event.created_at)}
</Link>
</div>
<slot />
{@render children()}
{/if}
</div>

View File

@@ -9,8 +9,7 @@
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import {publishDelete, publishReaction} from "@app/commands"
export let url
export let event
let {url, event} = $props()
const onReactionClick = (content: string, events: TrustedEvent[]) => {
const reaction = events.find(e => e.pubkey === $pubkey)

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {postJson, sleep} from "@welshman/lib"
import Button from "@lib/components/Button.svelte"
import Spinner from "@lib/components/Spinner.svelte"
@@ -10,8 +12,7 @@
import {pushToast} from "@app/toast"
import {BURROW_URL} from "@app/state"
export let email
export let reset_token
let {email, reset_token} = $props()
const onSubmit = async () => {
loading = true
@@ -33,27 +34,37 @@
}
}
let loading = false
let password = ""
let loading = $state(false)
let password = $state("")
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
<div slot="title">Reset your password</div>
{#snippet title()}
<div>Reset your password</div>
{/snippet}
</ModalHeader>
<FieldInline disabled={loading}>
<p slot="label">Email Address</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-rounded" />
<input readonly value={email} class="grow" />
</label>
{#snippet label()}
<p>Email Address</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" />
<input readonly value={email} class="grow" />
</label>
{/snippet}
</FieldInline>
<FieldInline disabled={loading}>
<p slot="label">New Password</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="key" />
<input bind:value={password} class="grow" type="password" />
</label>
{#snippet label()}
<p>New Password</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" />
<input bind:value={password} class="grow" type="password" />
</label>
{/snippet}
</FieldInline>
<Button type="submit" class="btn btn-primary" disabled={loading}>
<Spinner {loading}>Reset password</Spinner>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {postJson, sleep} from "@welshman/lib"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
@@ -11,7 +13,11 @@
import {pushToast} from "@app/toast"
import {BURROW_URL} from "@app/state"
export let email: string
interface Props {
email: string
}
let {email = $bindable()}: Props = $props()
const back = () => history.back()
@@ -35,20 +41,28 @@
}
}
let loading = false
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
<div slot="title">Reset your password</div>
{#snippet title()}
<div>Reset your password</div>
{/snippet}
</ModalHeader>
<FieldInline disabled={loading}>
<p slot="label">Email Address</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-rounded" />
<input bind:value={email} class="grow" />
</label>
<p slot="info">You'll be sent an email with a password reset link.</p>
{#snippet label()}
<p>Email Address</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" />
<input bind:value={email} class="grow" />
</label>
{/snippet}
{#snippet info()}
<p>You'll be sent an email with a password reset link.</p>
{/snippet}
</FieldInline>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -10,7 +10,7 @@
import ProfileInfo from "@app/components/ProfileInfo.svelte"
import {makeChatPath} from "@app/routes"
export let pubkey
let {pubkey} = $props()
const filters: Filter[] = [{authors: [pubkey], limit: 1}]
const events = deriveEvents(repository, {filters})

View File

@@ -14,6 +14,11 @@
import {pushModal} from "@app/modal"
import {makeSpacePath} from "@app/routes"
import {notifications} from "@app/notifications"
interface Props {
children?: import("svelte").Snippet
}
let {children}: Props = $props()
const addSpace = () => pushModal(SpaceAdd)
@@ -23,10 +28,10 @@
const openChat = () => ($canDecrypt ? goto("/chat") : pushModal(ChatEnable, {next: "/chat"}))
$: spaceUrls = Array.from($userRoomsByUrl.keys())
$: spacePaths = spaceUrls.map(url => makeSpacePath(url))
$: anySpaceNotifications = spacePaths.some(
path => !$page.url.pathname.startsWith(path) && $notifications.has(path),
let spaceUrls = $derived(Array.from($userRoomsByUrl.keys()))
let spacePaths = $derived(spaceUrls.map(url => makeSpacePath(url)))
let anySpaceNotifications = $derived(
spacePaths.some(path => !$page.url.pathname.startsWith(path) && $notifications.has(path)),
)
</script>
@@ -70,7 +75,7 @@
</div>
</div>
<slot />
{@render children?.()}
<!-- a little extra something for ios -->
<div class="fixed bottom-0 left-0 right-0 z-nav h-14 bg-base-100 md:hidden"></div>

View File

@@ -5,7 +5,7 @@
import {makeSpacePath} from "@app/routes"
import {notifications} from "@app/notifications"
export let url
let {url} = $props()
const path = makeSpacePath(url)
</script>

View File

@@ -15,7 +15,7 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal"
export let pubkey
let {pubkey} = $props()
const profile = deriveProfile(pubkey)
const profileDisplay = deriveProfileDisplay(pubkey)
@@ -24,8 +24,9 @@
const openProfile = () => pushModal(ProfileDetail, {pubkey})
$: following =
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey)
let following = $derived(
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey),
)
</script>
<div class="flex max-w-full gap-3">

View File

@@ -2,9 +2,9 @@
import {deriveProfile} from "@welshman/app"
import Avatar from "@lib/components/Avatar.svelte"
export let pubkey
let {...props} = $props()
const profile = deriveProfile(pubkey)
const profile = deriveProfile(props.pubkey)
</script>
<Avatar src={$profile?.picture} icon="user-circle" {...$$props} />
<Avatar src={$profile?.picture} icon="user-circle" {...props} />

View File

@@ -1,13 +1,13 @@
<script lang="ts">
import ProfileCircle from "@app/components/ProfileCircle.svelte"
export let pubkeys
let {...props} = $props()
</script>
<div class="flex pr-3">
{#each pubkeys.slice(0, 15) as pubkey (pubkey)}
{#each props.pubkeys.slice(0, 15) as pubkey (pubkey)}
<div class="z-feature -mr-3 inline-block">
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {...$$props} />
<ProfileCircle class="h-8 w-8 bg-base-300" {pubkey} {...props} />
</div>
{/each}
</div>

View File

@@ -22,7 +22,7 @@
import {pushModal} from "@app/modal"
import {makeChatPath} from "@app/routes"
export let pubkey
let {pubkey} = $props()
const profile = deriveProfile(pubkey)
const profileDisplay = deriveProfileDisplay(pubkey)
@@ -35,8 +35,9 @@
const openChat = () => ($canDecrypt ? goto(chatPath) : pushModal(ChatEnable, {next: chatPath}))
$: following =
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey)
let following = $derived(
pubkey === $session!.pubkey || getPubkeyTagValues(getListTags($userFollows)).includes(pubkey),
)
</script>
<div class="column gap-4">

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {ctx} from "@welshman/lib"
import {
createEvent,
@@ -16,7 +18,7 @@
import {pushModal, clearModals} from "@app/modal"
import {pushToast} from "@app/toast"
const values = {...($profilesByPubkey.get($pubkey!) || makeProfile())}
const values = $state({...($profilesByPubkey.get($pubkey!) || makeProfile())})
const back = () => history.back()
@@ -30,38 +32,49 @@
clearModals()
}
let file: File
let file: File | undefined = $state()
</script>
<form class="col-4" on:submit|preventDefault={saveEdit}>
<form class="col-4" onsubmit={preventDefault(saveEdit)}>
<div class="flex justify-center py-2">
<InputProfilePicture bind:file bind:url={values.picture} />
</div>
<Field>
<p slot="label">Username</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-circle" />
<input bind:value={values.name} class="grow" type="text" />
</label>
{#snippet label()}
<p>Username</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-circle" />
<input bind:value={values.name} class="grow" type="text" />
</label>
{/snippet}
</Field>
<Field>
<p slot="label">About You</p>
<textarea
class="textarea textarea-bordered leading-4"
rows="3"
bind:value={values.about}
slot="input">
</textarea>
{#snippet label()}
<p>About You</p>
{/snippet}
{#snippet input()}
<textarea class="textarea textarea-bordered leading-4" rows="3" bind:value={values.about}>
</textarea>
{/snippet}
</Field>
<Field>
<p slot="label">Nostr Address</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="map-point" />
<input bind:value={values.nip05} class="grow" type="text" />
</label>
<p slot="info">
<Button class="link" on:click={() => pushModal(InfoHandle)}>What is a nostr address?</Button>
</p>
{#snippet label()}
<p>Nostr Address</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="map-point" />
<input bind:value={values.nip05} class="grow" type="text" />
</label>
{/snippet}
{#snippet info()}
<p>
<Button class="link" on:click={() => pushModal(InfoHandle)}
>What is a nostr address?</Button>
</p>
{/snippet}
</Field>
<div class="mt-4 flex flex-row items-center justify-between gap-4">
<Button class="btn btn-neutral" on:click={back}>Discard Changes</Button>

View File

@@ -42,14 +42,16 @@
window.location.href = "/"
}
let password = ""
let success = false
let loading = false
let password = $state("")
let success = $state(false)
let loading = $state(false)
</script>
<div class="column gap-4">
<ModalHeader>
<div slot="title">Export your keys</div>
{#snippet title()}
<div>Export your keys</div>
{/snippet}
</ModalHeader>
<p>Here's what the process looks like:</p>
<ul class="flex list-inside list-decimal flex-col gap-4">
@@ -74,11 +76,15 @@
{#if !success}
<div out:slideAndFade>
<Field>
<p slot="label">To confirm, please enter your password below:</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="key" />
<input type="password" disabled={loading} bind:value={password} class="grow" />
</label>
{#snippet label()}
<p>To confirm, please enter your password below:</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" />
<input type="password" disabled={loading} bind:value={password} class="grow" />
</label>
{/snippet}
</Field>
</div>
{/if}

View File

@@ -10,10 +10,14 @@
import Spinner from "@lib/components/Spinner.svelte"
import NoteItem from "@app/components/NoteItem.svelte"
export let url
export let pubkey
export let events: TrustedEvent[] = []
export let hideLoading = false
interface Props {
url: any
pubkey: any
events?: TrustedEvent[]
hideLoading?: boolean
}
let {url, pubkey, events = $bindable([]), hideLoading = false}: Props = $props()
const ctrl = createFeedController({
useWindowing: true,
@@ -28,12 +32,12 @@
},
})
let element: Element
let element: Element | undefined = $state()
let buffer: TrustedEvent[] = []
onMount(() => {
const scroller = createScroller({
element,
element: element!,
delay: 300,
threshold: 3000,
onScroll: () => {

View File

@@ -2,7 +2,7 @@
import {deriveProfile} from "@welshman/app"
import Content from "@app/components/Content.svelte"
export let pubkey
let {pubkey} = $props()
const profile = deriveProfile(pubkey)
</script>

View File

@@ -3,15 +3,23 @@
import ModalHeader from "@lib/components/ModalHeader.svelte"
import Profile from "@app/components/Profile.svelte"
export let title
export let subtitle = ""
export let pubkeys
interface Props {
title: any
subtitle?: string
pubkeys: any
}
let {title, subtitle = "", pubkeys}: Props = $props()
</script>
<div class="column gap-4">
<ModalHeader>
<div slot="title">{title}</div>
<div slot="info">{subtitle}</div>
{#snippet title()}
<div>{title}</div>
{/snippet}
{#snippet info()}
<div>{subtitle}</div>
{/snippet}
</ModalHeader>
{#each pubkeys as pubkey (pubkey)}
<div class="card2 bg-alt">

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type {SvelteComponent} from "svelte"
import type {Component} from "svelte"
import {derived} from "svelte/store"
import {type Instance} from "tippy.js"
import {append, remove, uniq} from "@welshman/lib"
@@ -13,19 +13,23 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import {pushModal} from "@app/modal"
export let value: string[]
export let autofocus = false
interface Props {
value: string[]
autofocus?: boolean
}
let term = ""
let input: Element
let popover: Instance
let instance: SvelteComponent
let {value = $bindable(), autofocus = false}: Props = $props()
let term = $state("")
let input: Element | undefined = $state()
let popover: Instance | undefined = $state()
let instance: any = $state()
const search = derived(profileSearch, $profileSearch => $profileSearch.searchValues)
const selectPubkey = (pubkey: string) => {
term = ""
popover.hide()
popover?.hide()
value = uniq(append(pubkey, value))
}
@@ -39,13 +43,13 @@
}
}
$: {
$effect(() => {
if (term) {
popover?.show()
} else {
popover?.hide()
}
}
})
</script>
<div class="flex flex-col gap-2">
@@ -64,14 +68,14 @@
</div>
<label class="input input-bordered flex w-full items-center gap-2" bind:this={input}>
<Icon icon="magnifer" />
<!-- svelte-ignore a11y-autofocus -->
<!-- svelte-ignore a11y_autofocus -->
<input
{autofocus}
class="grow"
type="text"
placeholder="Search for profiles..."
bind:value={term}
on:keydown={onKeyDown} />
onkeydown={onKeyDown} />
</label>
<Tippy
bind:popover
@@ -89,6 +93,6 @@
trigger: "manual",
interactive: true,
maxWidth: "none",
getReferenceClientRect: () => input.getBoundingClientRect(),
getReferenceClientRect: () => input!.getBoundingClientRect(),
}} />
</div>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import {deriveProfileDisplay} from "@welshman/app"
export let pubkey
let {pubkey} = $props()
const profileDisplay = deriveProfileDisplay(pubkey)
</script>

View File

@@ -4,23 +4,25 @@
import Button from "@lib/components/Button.svelte"
import {clip} from "@app/toast"
export let code
let {code} = $props()
let canvas: Element
let wrapper: Element
let scale = 0.1
let height: number
let canvas: Element | undefined = $state()
let wrapper: Element | undefined = $state()
let scale = $state(0.1)
let height = $state(0)
const copy = () => clip(code)
onMount(() => {
QRCode.toCanvas(canvas, code)
if (canvas && wrapper) {
QRCode.toCanvas(canvas, code)
const wrapperRect = wrapper.getBoundingClientRect()
const canvasRect = canvas.getBoundingClientRect()
const wrapperRect = wrapper.getBoundingClientRect()
const canvasRect = canvas.getBoundingClientRect()
scale = wrapperRect.width / (canvasRect.width * 10)
height = canvasRect.width * 10 * scale
scale = wrapperRect.width / (canvasRect.width * 10)
height = canvasRect.width * 10 * scale
}
})
</script>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault, stopPropagation} from "svelte/legacy"
import {onMount} from "svelte"
import {groupBy, uniq, uniqBy, batch} from "@welshman/lib"
import {REACTION, getTag, REPORT, DELETE} from "@welshman/util"
@@ -12,11 +14,23 @@
import {displayReaction} from "@app/state"
import {pushModal} from "@app/modal"
export let event
export let onReactionClick
export let url = ""
export let reactionClass = ""
export let noTooltip = false
interface Props {
event: any
onReactionClick: any
url?: string
reactionClass?: string
noTooltip?: boolean
children?: import("svelte").Snippet
}
let {
event,
onReactionClick,
url = "",
reactionClass = "",
noTooltip = false,
children,
}: Props = $props()
const reports = deriveEvents(repository, {
filters: [{kinds: [REPORT], "#e": [event.id]}],
@@ -28,11 +42,13 @@
const onReportClick = () => pushModal(EventReportDetails, {url, event})
$: reportReasons = uniq($reports.map(e => getTag("e", e.tags)?.[2]))
let reportReasons = $derived(uniq($reports.map(e => getTag("e", e.tags)?.[2])))
$: groupedReactions = groupBy(
e => e.content,
uniqBy(e => e.pubkey + e.content, $reactions),
let groupedReactions = $derived(
groupBy(
e => e.content,
uniqBy(e => e.pubkey + e.content, $reactions),
),
)
onMount(() => {
@@ -59,7 +75,7 @@
data-tip="{`This content has been reported as "${displayList(reportReasons)}".`}}"
class="btn btn-error btn-xs tooltip-right flex items-center gap-1 rounded-full"
class:tooltip={!noTooltip && !isMobile}
on:click|preventDefault|stopPropagation={onReportClick}>
onclick={stopPropagation(preventDefault(onReportClick))}>
<Icon icon="danger" />
<span>{$reports.length}</span>
</button>
@@ -78,13 +94,13 @@
class:border={isOwn}
class:border-solid={isOwn}
class:border-primary={isOwn}
on:click|preventDefault|stopPropagation={onClick}>
onclick={stopPropagation(preventDefault(onClick))}>
<span>{displayReaction(content)}</span>
{#if events.length > 1}
<span>{events.length}</span>
{/if}
</button>
{/each}
<slot />
{@render children?.()}
</div>
{/if}

View File

@@ -9,18 +9,22 @@
import Button from "@lib/components/Button.svelte"
import RelayItem from "@app/components/RelayItem.svelte"
export let relays: Readable<string[]>
export let addRelay: (url: string) => void
interface Props {
relays: Readable<string[]>
addRelay: (url: string) => void
}
let term = ""
let limit = 20
let element: Element
let {relays, addRelay}: Props = $props()
$: customUrl = tryCatch(() => normalizeRelayUrl(term))
let term = $state("")
let limit = $state(20)
let element: Element | undefined = $state()
let customUrl = $derived(tryCatch(() => normalizeRelayUrl(term)))
onMount(() => {
const scroller = createScroller({
element,
element: element!,
delay: 300,
onScroll: () => {
limit += 20

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import {deriveRelay} from "@welshman/app"
export let url
let {...props} = $props()
const relay = deriveRelay(url)
const relay = deriveRelay(props.url)
</script>
{#if $relay?.profile?.description}
<p class={$$props.class}>{$relay?.profile.description}</p>
<p class={props.class}>{$relay?.profile.description}</p>
{/if}

View File

@@ -5,11 +5,11 @@
import {displayRelayUrl} from "@welshman/util"
import {deriveRelay} from "@welshman/app"
export let url
let {url, children} = $props()
const relay = deriveRelay(url)
$: connections = $relay?.stats?.open_count || 0
let connections = $derived($relay?.stats?.open_count || 0)
</script>
<div class="card2 card2-sm bg-alt column gap-2">
@@ -18,7 +18,7 @@
<Icon icon="server" />
<p class="ellipsize">{displayRelayUrl(url)}</p>
</div>
<slot />
{@render children?.()}
</div>
{#if $relay?.profile?.description}
<p class="ellipsize">{$relay?.profile.description}</p>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import {deriveRelayDisplay} from "@welshman/app"
export let url
let {url} = $props()
const display = deriveRelayDisplay(url)
</script>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {goto} from "$app/navigation"
import {randomId} from "@welshman/lib"
import {displayRelayUrl} from "@welshman/util"
@@ -14,7 +16,7 @@
import {makeSpacePath} from "@app/routes"
import {pushToast} from "@app/toast"
export let url
let {url} = $props()
const room = randomId()
const relay = deriveRelay(url)
@@ -56,23 +58,31 @@
}
}
let name = ""
let loading = false
let name = $state("")
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={create}>
<form class="column gap-4" onsubmit={preventDefault(create)}>
<ModalHeader>
<div slot="title">Create a Room</div>
<div slot="info">
On <span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{#snippet title()}
<div>Create a Room</div>
{/snippet}
{#snippet info()}
<div>
On <span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<Field>
<p slot="label">Room Name</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="hashtag" />
<input bind:value={name} class="grow" type="text" />
</label>
{#snippet label()}
<p>Room Name</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="hashtag" />
<input bind:value={name} class="grow" type="text" />
</label>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {postJson} from "@welshman/lib"
import {isMobile} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
@@ -43,12 +45,12 @@
}
}
let email = ""
let password = ""
let loading = false
let email = $state("")
let password = $state("")
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={signup}>
<form class="column gap-4" onsubmit={preventDefault(signup)}>
<h1 class="heading">Sign up with Nostr</h1>
<p class="m-auto max-w-sm text-center">
{PLATFORM_NAME} is built using the
@@ -57,18 +59,26 @@
</p>
{#if BURROW_URL}
<FieldInline>
<p slot="label">Email</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="user-rounded" />
<input bind:value={email} />
</label>
{#snippet label()}
<p>Email</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="user-rounded" />
<input bind:value={email} />
</label>
{/snippet}
</FieldInline>
<FieldInline>
<p slot="label">Password</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="key" />
<input bind:value={password} type="password" />
</label>
{#snippet label()}
<p>Password</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="key" />
<input bind:value={password} type="password" />
</label>
{/snippet}
</FieldInline>
<Button type="submit" class="btn btn-primary" disabled={loading || !email || !password}>
<Spinner {loading}>Sign Up</Spinner>

View File

@@ -3,7 +3,7 @@
import LogInPassword from "@app/components/LogInPassword.svelte"
import {pushModal} from "@app/modal"
export let email
let {email} = $props()
const login = () => pushModal(LogInPassword)
</script>

View File

@@ -15,30 +15,50 @@
<div class="column gap-4">
<ModalHeader>
<div slot="title">Add a Space</div>
<div slot="info">
Spaces are places where communities come together to work, play, and hang out.
</div>
{#snippet title()}
<div>Add a Space</div>
{/snippet}
{#snippet info()}
<div>Spaces are places where communities come together to work, play, and hang out.</div>
{/snippet}
</ModalHeader>
<Link href="/discover">
<CardButton class="!btn-primary">
<div slot="icon"><Icon icon="compass" size={7} /></div>
<div slot="title">Discover spaces</div>
<div slot="info">Browse spaces on the discover page.</div>
{#snippet icon()}
<div><Icon icon="compass" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Discover spaces</div>
{/snippet}
{#snippet info()}
<div>Browse spaces on the discover page.</div>
{/snippet}
</CardButton>
</Link>
<Button on:click={startJoin}>
<CardButton>
<div slot="icon"><Icon icon="login-2" size={7} /></div>
<div slot="title">Join a space</div>
<div slot="info">Enter an invite code or url to join an existing space.</div>
{#snippet icon()}
<div><Icon icon="login-2" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Join a space</div>
{/snippet}
{#snippet info()}
<div>Enter an invite code or url to join an existing space.</div>
{/snippet}
</CardButton>
</Button>
<Button on:click={startCreate}>
<CardButton>
<div slot="icon"><Icon icon="add-circle" size={7} /></div>
<div slot="title">Create a space</div>
<div slot="info">Just a few questions and you'll be on your way.</div>
{#snippet icon()}
<div><Icon icon="add-circle" size={7} /></div>
{/snippet}
{#snippet title()}
<div>Create a space</div>
{/snippet}
{#snippet info()}
<div>Just a few questions and you'll be on your way.</div>
{/snippet}
</CardButton>
</Button>
</div>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {displayRelayUrl} from "@welshman/util"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
@@ -10,8 +12,7 @@
import {clearModals} from "@app/modal"
import {attemptRelayAccess} from "@app/commands"
export let url
export let error
let {url, error} = $props()
const back = () => history.back()
@@ -39,14 +40,18 @@
}
}
let claim = ""
let loading = false
let claim = $state("")
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={join}>
<form class="column gap-4" onsubmit={preventDefault(join)}>
<ModalHeader>
<div slot="title">Access Error</div>
<div slot="info">We couldn't connect you to this space.</div>
{#snippet title()}
<div>Access Error</div>
{/snippet}
{#snippet info()}
<div>We couldn't connect you to this space.</div>
{/snippet}
</ModalHeader>
<p>
We received an error from the relay indicating you don't have access to {displayRelayUrl(url)}.
@@ -56,12 +61,18 @@
</p>
<p>If you have one, you can try entering an invite code below to request access.</p>
<Field>
<p slot="label">Invite code</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="link-round" />
<input bind:value={claim} class="grow" type="text" />
</label>
<p slot="info">Enter an invite code provided to you by the admin of the relay.</p>
{#snippet label()}
<p>Invite code</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="link-round" />
<input bind:value={claim} class="grow" type="text" />
</label>
{/snippet}
{#snippet info()}
<p>Enter an invite code provided to you by the admin of the relay.</p>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -3,7 +3,11 @@
import Avatar from "@lib/components/Avatar.svelte"
import {deriveRelay} from "@welshman/app"
export let url = ""
interface Props {
url?: string
}
let {url = ""}: Props = $props()
const relay = deriveRelay(url)
</script>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {onMount} from "svelte"
import {goto} from "$app/navigation"
import {ctx, sleep} from "@welshman/lib"
@@ -13,7 +15,7 @@
import {makeSpacePath} from "@app/routes"
import {pushModal} from "@app/modal"
export let url
let {url} = $props()
const back = () => history.back()
@@ -31,8 +33,8 @@
}
}
let error: string | undefined
let loading = true
let error: string | undefined = $state()
let loading = $state(true)
onMount(async () => {
;[error] = await Promise.all([attemptRelayAccess(url), sleep(3000)])
@@ -40,12 +42,16 @@
})
</script>
<form class="column gap-4" on:submit|preventDefault={next}>
<form class="column gap-4" onsubmit={preventDefault(next)}>
<ModalHeader>
<div slot="title">Checking Space...</div>
<div slot="info">
Connecting you to to <span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{#snippet title()}
<div>Checking Space...</div>
{/snippet}
{#snippet info()}
<div>
Connecting you to to <span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<div class="m-auto flex flex-col gap-4">
{#if loading}

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import InputProfilePicture from "@lib/components/InputProfilePicture.svelte"
import Button from "@lib/components/Button.svelte"
import Field from "@lib/components/Field.svelte"
@@ -13,36 +15,50 @@
const next = () => pushModal(SpaceCreateFinish)
let file: File
let name = ""
let relay = ""
let file: File | undefined = $state()
let name = $state("")
let relay = $state("")
</script>
<form class="column gap-4" on:submit|preventDefault={next}>
<form class="column gap-4" onsubmit={preventDefault(next)}>
<ModalHeader>
<div slot="title">Customize your Space</div>
<div slot="info">Give people a few details to go on. You can always change this later.</div>
{#snippet title()}
<div>Customize your Space</div>
{/snippet}
{#snippet info()}
<div>Give people a few details to go on. You can always change this later.</div>
{/snippet}
</ModalHeader>
<div class="flex justify-center py-2">
<InputProfilePicture bind:file />
</div>
<Field>
<p slot="label">Space Name</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="fire-minimalistic" />
<input bind:value={name} class="grow" type="text" />
</label>
{#snippet label()}
<p>Space Name</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="fire-minimalistic" />
<input bind:value={name} class="grow" type="text" />
</label>
{/snippet}
</Field>
<Field>
<p slot="label">Relay</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="server" />
<input bind:value={relay} class="grow" type="text" />
</label>
<p slot="info">
This can be any nostr relay where you'd like to host your space.
<Button class="link" on:click={() => pushModal(InfoRelay)}>What is a relay?</Button>
</p>
{#snippet label()}
<p>Relay</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="server" />
<input bind:value={relay} class="grow" type="text" />
</label>
{/snippet}
{#snippet info()}
<p>
This can be any nostr relay where you'd like to host your space.
<Button class="link" on:click={() => pushModal(InfoRelay)}>What is a relay?</Button>
</p>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import Button from "@lib/components/Button.svelte"
import Link from "@lib/components/Link.svelte"
import Icon from "@lib/components/Icon.svelte"
@@ -17,10 +19,14 @@
}
</script>
<form class="column gap-4" on:submit|preventDefault={next}>
<form class="column gap-4" onsubmit={preventDefault(next)}>
<ModalHeader>
<div slot="title">Create a Space</div>
<div slot="info">Host your own space, for your community.</div>
{#snippet title()}
<div>Create a Space</div>
{/snippet}
{#snippet info()}
<div>Host your own space, for your community.</div>
{/snippet}
</ModalHeader>
<p>
<Link class="text-primary" external href="https://relay.tools">relay.tools</Link> is a third-party

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {goto} from "$app/navigation"
import {displayRelayUrl} from "@welshman/util"
import Spinner from "@lib/components/Spinner.svelte"
@@ -8,7 +10,7 @@
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {removeSpaceMembership} from "@app/commands"
export let url
let {url} = $props()
const back = () => history.back()
@@ -24,14 +26,16 @@
goto("/home")
}
let loading = false
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={exit}>
<form class="column gap-4" onsubmit={preventDefault(exit)}>
<ModalHeader>
<div slot="title">
You are leaving<br /><span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{#snippet title()}
<div>
You are leaving<br /><span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<p class="text-center">Are you sure you want to leave?</p>
<ModalFooter>

View File

@@ -12,16 +12,20 @@
import ModalFooter from "@lib/components/ModalFooter.svelte"
import {clip} from "@app/toast"
export let url
let {url} = $props()
const back = () => history.back()
const copyInvite = () => clip(invite)
let claim = ""
let loading = true
let claim = $state("")
let loading = $state(true)
$: invite = [displayRelayUrl(url), claim].filter(identity).join("|")
let invite = $state("")
$effect(() => {
invite = [displayRelayUrl(url), claim].filter(identity).join("|")
})
onMount(async () => {
const [[event]] = await Promise.all([
@@ -36,11 +40,15 @@
<div class="col-4">
<ModalHeader>
<div slot="title">Create an Invite</div>
<div slot="info">
Get a link that you can use to invite people to
<span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{#snippet title()}
<div>Create an Invite</div>
{/snippet}
{#snippet info()}
<div>
Get a link that you can use to invite people to
<span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
</ModalHeader>
<div>
{#if loading}
@@ -50,20 +58,24 @@
{:else}
<div in:slide>
<Field>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="link-round" />
<input bind:value={invite} class="grow" type="text" />
<Button on:click={copyInvite}>
<Icon icon="copy" />
</Button>
</label>
<p slot="info">
This invite link can be used by clicking "Add Space" and pasting it there.
{#if !claim}
This space did not issue a claim for this link, so additional steps might be required
for people using this invite link.
{/if}
</p>
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="link-round" />
<input bind:value={invite} class="grow" type="text" />
<Button on:click={copyInvite}>
<Icon icon="copy" />
</Button>
</label>
{/snippet}
{#snippet info()}
<p>
This invite link can be used by clicking "Add Space" and pasting it there.
{#if !claim}
This space did not issue a claim for this link, so additional steps might be
required for people using this invite link.
{/if}
</p>
{/snippet}
</Field>
</div>
{/if}

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {goto} from "$app/navigation"
import {ctx, tryCatch} from "@welshman/lib"
import {isRelayUrl, normalizeRelayUrl} from "@welshman/util"
@@ -59,27 +61,39 @@
}
}
let url = ""
let loading = false
let url = $state("")
let loading = $state(false)
$: linkIsValid = Boolean(tryCatch(() => isRelayUrl(normalizeRelayUrl(url.split("|")[0]))))
let linkIsValid = $derived(
Boolean(tryCatch(() => isRelayUrl(normalizeRelayUrl(url.split("|")[0])))),
)
</script>
<form class="column gap-4" on:submit|preventDefault={join}>
<form class="column gap-4" onsubmit={preventDefault(join)}>
<ModalHeader>
<div slot="title">Join a Space</div>
<div slot="info">Enter an invite code below to join an existing space.</div>
{#snippet title()}
<div>Join a Space</div>
{/snippet}
{#snippet info()}
<div>Enter an invite code below to join an existing space.</div>
{/snippet}
</ModalHeader>
<Field>
<p slot="label">Invite code*</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<Icon icon="link-round" />
<input bind:value={url} class="grow" type="text" />
</label>
<p slot="info">
You can also directly join any relay by entering its URL here.
<Button class="link" on:click={() => pushModal(InfoRelay)}>What is a relay?</Button>
</p>
{#snippet label()}
<p>Invite code*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<Icon icon="link-round" />
<input bind:value={url} class="grow" type="text" />
</label>
{/snippet}
{#snippet info()}
<p>
You can also directly join any relay by entering its URL here.
<Button class="link" on:click={() => pushModal(InfoRelay)}>What is a relay?</Button>
</p>
{/snippet}
</Field>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {displayRelayUrl} from "@welshman/util"
import Spinner from "@lib/components/Spinner.svelte"
import Button from "@lib/components/Button.svelte"
@@ -8,7 +10,7 @@
import {clearModals} from "@app/modal"
import {addSpaceMembership} from "@app/commands"
export let url
let {url} = $props()
const back = () => history.back()
@@ -28,15 +30,19 @@
}
}
let loading = false
let loading = $state(false)
</script>
<form class="column gap-4" on:submit|preventDefault={join}>
<form class="column gap-4" onsubmit={preventDefault(join)}>
<ModalHeader>
<div slot="title">
Joining <span class="text-primary">{displayRelayUrl(url)}</span>
</div>
<div slot="info">Are you sure you'd like to join this space?</div>
{#snippet title()}
<div>
Joining <span class="text-primary">{displayRelayUrl(url)}</span>
</div>
{/snippet}
{#snippet info()}
<div>Are you sure you'd like to join this space?</div>
{/snippet}
</ModalHeader>
<ModalFooter>
<Button class="btn btn-link" on:click={back}>

View File

@@ -18,9 +18,13 @@
import {notifications} from "@app/notifications"
import {makeSpacePath} from "@app/routes"
export let url
export let event
export let showActivity = false
interface Props {
url: any
event: any
showActivity?: boolean
}
let {url, event, showActivity = false}: Props = $props()
const thunk = $thunks[event.id]
const deleted = deriveIsDeleted(repository, event)
@@ -28,9 +32,9 @@
const filters = [{kinds: [COMMENT], "#E": [event.id]}]
const replies = deriveEvents(repository, {filters})
const showPopover = () => popover.show()
const showPopover = () => popover?.show()
const hidePopover = () => popover.hide()
const hidePopover = () => popover?.hide()
const onReactionClick = (content: string, events: TrustedEvent[]) => {
const reaction = events.find(e => e.pubkey === $pubkey)
@@ -45,9 +49,9 @@
const onEmoji = (emoji: NativeEmoji) =>
publishReaction({event, content: emoji.unicode, relays: [url]})
let popover: Instance
let popover: Instance | undefined = $state()
$: lastActive = max([...$replies, event].map(e => e.created_at))
let lastActive = $derived(max([...$replies, event].map(e => e.created_at)))
onMount(() => {
load({relays: [url], filters})

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {writable} from "svelte/store"
import {EditorContent} from "svelte-tiptap"
import {createEvent, THREAD} from "@welshman/util"
@@ -13,7 +15,7 @@
import {GENERAL, tagRoom, PROTECTED} from "@app/state"
import {makeEditor} from "@app/editor"
export let url
let {url} = $props()
const uploading = writable(false)
@@ -55,32 +57,44 @@
const editor = makeEditor({submit, uploading, placeholder: "What's on your mind?"})
let title: string
let title: string = $state("")
</script>
<form class="column gap-4" on:submit|preventDefault={submit}>
<form class="column gap-4" onsubmit={preventDefault(submit)}>
<ModalHeader>
<div slot="title">Create a Thread</div>
<div slot="info">Share a link, or start a discussion.</div>
{#snippet title()}
<div>Create a Thread</div>
{/snippet}
{#snippet info()}
<div>Share a link, or start a discussion.</div>
{/snippet}
</ModalHeader>
<div class="col-8 relative">
<Field>
<p slot="label">Title*</p>
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
<!-- svelte-ignore a11y-autofocus -->
<input
autofocus={!isMobile}
bind:value={title}
class="grow"
type="text"
placeholder="What is this thread about?" />
</label>
{#snippet label()}
<p>Title*</p>
{/snippet}
{#snippet input()}
<label class="input input-bordered flex w-full items-center gap-2">
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus={!isMobile}
bind:value={title}
class="grow"
type="text"
placeholder="What is this thread about?" />
</label>
{/snippet}
</Field>
<Field>
<p slot="label">Message*</p>
<div slot="input" class="note-editor flex-grow overflow-hidden">
<EditorContent editor={$editor} />
</div>
{#snippet label()}
<p>Message*</p>
{/snippet}
{#snippet input()}
<div class="note-editor flex-grow overflow-hidden">
<EditorContent editor={$editor} />
</div>
{/snippet}
</Field>
<Button
data-tip="Add an image"

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {nthEq} from "@welshman/lib"
import {formatTimestamp} from "@welshman/app"
import Link from "@lib/components/Link.svelte"
@@ -9,9 +11,13 @@
import {makeThreadPath} from "@app/routes"
import {pushModal} from "@app/modal"
export let url
export let event
export let hideActions = false
interface Props {
url: any
event: any
hideActions?: boolean
}
let {url, event, hideActions = false}: Props = $props()
const title = event.tags.find(nthEq(0, "title"))?.[1]
@@ -35,7 +41,7 @@
<div class="flex w-full flex-col items-end justify-between gap-2 sm:flex-row">
<span class="whitespace-nowrap py-1 text-sm opacity-75">
Posted by
<button type="button" on:click|preventDefault={openProfile} class="link-content">
<button type="button" onclick={preventDefault(openProfile)} class="link-content">
@<ProfileName pubkey={event.pubkey} />
</button>
</span>

View File

@@ -9,9 +9,7 @@
import ConfirmDelete from "@app/components/ConfirmDelete.svelte"
import {pushModal} from "@app/modal"
export let url
export let event
export let onClick
let {url, event, onClick} = $props()
const isRoot = event.kind !== COMMENT

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {writable} from "svelte/store"
import {EditorContent} from "svelte-tiptap"
import {isMobile} from "@lib/html"
@@ -11,10 +13,7 @@
import {makeEditor} from "@app/editor"
import {pushToast} from "@app/toast"
export let url
export let event
export let onClose
export let onSubmit
let {url, event, onClose, onSubmit} = $props()
const uploading = writable(false)
@@ -40,7 +39,7 @@
<form
in:fly
out:slideAndFade
on:submit|preventDefault={submit}
onsubmit={preventDefault(submit)}
class="card2 sticky bottom-2 z-feature mx-2 mt-4 bg-neutral">
<div class="relative">
<div class="note-editor flex-grow overflow-hidden">

View File

@@ -1,4 +1,6 @@
<script lang="ts">
import {preventDefault} from "svelte/legacy"
import {nip19} from "nostr-tools"
import {goto} from "$app/navigation"
import {ctx} from "@welshman/lib"
@@ -12,8 +14,7 @@
import {makeRoomPath} from "@app/routes"
import {setKey} from "@app/implicit"
export let url
export let event
let {url, event} = $props()
const relays = ctx.app.router.Event(event).getUrls()
const nevent = nip19.neventEncode({id: event.id, relays})
@@ -29,13 +30,17 @@
selection = room === selection ? "" : room
}
let selection = ""
let selection = $state("")
</script>
<form class="column gap-4" on:submit|preventDefault={onSubmit}>
<form class="column gap-4" onsubmit={preventDefault(onSubmit)}>
<ModalHeader>
<div slot="title">Share Thread</div>
<div slot="info">Which room would you like to share this thread to?</div>
{#snippet title()}
<div>Share Thread</div>
{/snippet}
{#snippet info()}
<div>Which room would you like to share this thread to?</div>
{/snippet}
</ModalHeader>
<div class="grid grid-cols-3 gap-2">
{#each $channelsByUrl.get(url) || [] as channel (channel.room)}
@@ -44,7 +49,7 @@
class="btn"
class:btn-neutral={selection !== channel.room}
class:btn-primary={selection === channel.room}
on:click={() => toggleRoom(channel.room)}>
onclick={() => toggleRoom(channel.room)}>
#<ChannelName {...channel} />
</button>
{/each}

View File

@@ -10,7 +10,11 @@
import ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
import {userSettingValues} from "@app/state"
export let thunk: Thunk | MergedThunk
interface Props {
thunk: Thunk | MergedThunk
}
let {thunk} = $props()
const {Pending, Failure, Timeout} = PublishStatus

View File

@@ -1,14 +1,20 @@
<script lang="ts">
import {run} from "svelte/legacy"
import {PublishStatus} from "@welshman/net"
import {displayRelayUrl} from "@welshman/util"
import Button from "@lib/components/Button.svelte"
export let url: string
export let status: string
export let message: string
export let retry: () => void
interface Props {
url: string
status: string
message: string
retry: () => void
}
$: {
let {url, status, message = $bindable(), retry}: Props = $props()
run(() => {
if (!message && status === PublishStatus.Timeout) {
message = "request timed out"
}
@@ -16,7 +22,7 @@
if (!message) {
message = "no details recieved"
}
}
})
</script>
<div class="card2 bg-alt col-2 shadow-2xl">

View File

@@ -3,8 +3,12 @@
import {NodeViewWrapper} from "svelte-tiptap"
import {deriveProfileDisplay} from "@welshman/app"
export let node: NodeViewProps["node"]
export let selected: NodeViewProps["selected"]
interface Props {
node: NodeViewProps["node"]
selected: NodeViewProps["selected"]
}
let {node, selected}: Props = $props()
const display = deriveProfileDisplay(node.attrs.pubkey)
</script>

View File

@@ -10,14 +10,14 @@
import WotScore from "@lib/components/WotScore.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
export let value
let {value} = $props()
const pubkey = value
const profileDisplay = deriveProfileDisplay(pubkey)
const handle = deriveHandleForPubkey(pubkey)
const score = deriveUserWotScore(pubkey)
$: following = getPubkeyTagValues(getListTags($userFollows)).includes(pubkey)
let following = $derived(getPubkeyTagValues(getListTags($userFollows)).includes(pubkey))
</script>
<div class="flex max-w-full gap-3">

View File

@@ -77,7 +77,7 @@ export const makeEditor = ({
},
nprofile: {
extend: {
addNodeView: () => SvelteNodeViewRenderer(EditMention),
addNodeView: () => SvelteNodeViewRenderer(asClassComponent(EditMention)),
addProseMirrorPlugins() {
return [
MentionSuggestion({

View File

@@ -2,13 +2,11 @@
import {onMount} from "svelte"
import Icon from "@lib/components/Icon.svelte"
export let src = ""
export let size = 7
export let icon = "user-rounded"
let {src = "", size = 7, icon = "user-rounded", style = "", ...restProps} = $props()
let element: HTMLElement
$: rem = size * 4
const rem = $derived(size * 4)
onMount(() => {
if (src) {
@@ -25,8 +23,7 @@
<div
bind:this={element}
class="{$$props.class} relative !flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-cover bg-center"
style="width: {rem}px; height: {rem}px; min-width: {rem}px; background-image: url({src}); {$$props.style ||
''}">
class="{restProps.class} relative !flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-cover bg-center"
style="width: {rem}px; height: {rem}px; min-width: {rem}px; background-image: url({src}); {style}">
<Icon {icon} class={src ? "hidden" : ""} size={Math.round(size * 0.8)} />
</div>

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