mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-11 03:17:02 +00:00
Migrate more stuff
This commit is contained in:
@@ -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 />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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]})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let value
|
||||
export let isBlock
|
||||
let {value, isBlock} = $props()
|
||||
</script>
|
||||
|
||||
<code
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let value
|
||||
let {value} = $props()
|
||||
</script>
|
||||
|
||||
{#each value as _}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
export let value
|
||||
let {value} = $props()
|
||||
</script>
|
||||
|
||||
<span class="link-content">
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import {makeSpacePath} from "@app/routes"
|
||||
import {pushDrawer} from "@app/modal"
|
||||
|
||||
export let url
|
||||
let {url} = $props()
|
||||
|
||||
const path = makeSpacePath(url)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {deriveProfileDisplay} from "@welshman/app"
|
||||
|
||||
export let pubkey
|
||||
let {pubkey} = $props()
|
||||
|
||||
const profileDisplay = deriveProfileDisplay(pubkey)
|
||||
</script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {deriveRelayDisplay} from "@welshman/app"
|
||||
|
||||
export let url
|
||||
let {url} = $props()
|
||||
|
||||
const display = deriveRelayDisplay(url)
|
||||
</script>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -77,7 +77,7 @@ export const makeEditor = ({
|
||||
},
|
||||
nprofile: {
|
||||
extend: {
|
||||
addNodeView: () => SvelteNodeViewRenderer(EditMention),
|
||||
addNodeView: () => SvelteNodeViewRenderer(asClassComponent(EditMention)),
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
MentionSuggestion({
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user