Listen for new threads, add reply/quote button to channels and chats, better quote handling
This commit is contained in:
@@ -1,19 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import type {Readable} from "svelte/store"
|
import {createEditor, EditorContent} from "svelte-tiptap"
|
||||||
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
|
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import {getEditorOptions, getEditorTags} from "@lib/editor"
|
import {getEditorOptions, getEditorTags} from "@lib/editor"
|
||||||
import {getPubkeyHints} from "@app/commands"
|
import {getPubkeyHints} from "@app/commands"
|
||||||
|
|
||||||
export let onSubmit
|
export let onSubmit: any
|
||||||
export let content = ""
|
export let content = ""
|
||||||
|
export let editor = createEditor(
|
||||||
|
getEditorOptions({
|
||||||
|
submit,
|
||||||
|
getPubkeyHints,
|
||||||
|
submitOnEnter: true,
|
||||||
|
autofocus: !isMobile,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
let editor: Readable<Editor>
|
function submit() {
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
if ($loading) return
|
if ($loading) return
|
||||||
|
|
||||||
onSubmit({
|
onSubmit({
|
||||||
@@ -27,15 +32,6 @@
|
|||||||
$: loading = $editor?.storage.fileUpload.loading
|
$: loading = $editor?.storage.fileUpload.loading
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor = createEditor(
|
|
||||||
getEditorOptions({
|
|
||||||
submit,
|
|
||||||
getPubkeyHints,
|
|
||||||
submitOnEnter: true,
|
|
||||||
autofocus: !isMobile,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
$editor.commands.setContent(content)
|
$editor.commands.setContent(content)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
import LongPress from "@lib/components/LongPress.svelte"
|
import LongPress from "@lib/components/LongPress.svelte"
|
||||||
import Avatar from "@lib/components/Avatar.svelte"
|
import Avatar from "@lib/components/Avatar.svelte"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Link from "@lib/components/Link.svelte"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Content from "@app/components/Content.svelte"
|
import Content from "@app/components/Content.svelte"
|
||||||
import ThunkStatus from "@app/components/ThunkStatus.svelte"
|
import ThunkStatus from "@app/components/ThunkStatus.svelte"
|
||||||
import ReplySummary from "@app/components/ReplySummary.svelte"
|
import ReplySummary from "@app/components/ReplySummary.svelte"
|
||||||
@@ -27,6 +29,7 @@
|
|||||||
|
|
||||||
export let url, room
|
export let url, room
|
||||||
export let event: TrustedEvent
|
export let event: TrustedEvent
|
||||||
|
export let replyTo: any = undefined
|
||||||
export let showPubkey = false
|
export let showPubkey = false
|
||||||
export let isHead = false
|
export let isHead = false
|
||||||
export let inert = false
|
export let inert = false
|
||||||
@@ -40,6 +43,8 @@
|
|||||||
const rootEvent = rootId ? deriveEvent(rootId, rootHints) : readable(null)
|
const rootEvent = rootId ? deriveEvent(rootId, rootHints) : readable(null)
|
||||||
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
|
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
|
||||||
|
|
||||||
|
const reply = () => replyTo(event)
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
const root = $rootEvent || event
|
const root = $rootEvent || event
|
||||||
|
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LongPress
|
<LongPress
|
||||||
|
data-event={event.id}
|
||||||
on:click={isMobile || inert ? null : onClick}
|
on:click={isMobile || inert ? null : onClick}
|
||||||
onLongPress={inert ? null : onLongPress}
|
onLongPress={inert ? null : onLongPress}
|
||||||
class="group relative flex w-full flex-col gap-1 p-2 text-left transition-colors {inert
|
class="group relative flex w-full flex-col gap-1 p-2 text-left transition-colors {inert
|
||||||
@@ -110,6 +116,11 @@
|
|||||||
class:group-hover:opacity-100={!isMobile}
|
class:group-hover:opacity-100={!isMobile}
|
||||||
on:click|stopPropagation>
|
on:click|stopPropagation>
|
||||||
<ChannelMessageEmojiButton {url} {room} {event} />
|
<ChannelMessageEmojiButton {url} {room} {event} />
|
||||||
|
{#if replyTo}
|
||||||
|
<Button class="btn join-item btn-xs" on:click={reply}>
|
||||||
|
<Icon icon="reply" size={4} />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
<ChannelMessageMenuButton {url} {event} />
|
<ChannelMessageMenuButton {url} {event} />
|
||||||
</button>
|
</button>
|
||||||
</LongPress>
|
</LongPress>
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {int, nthNe, MINUTE, sortBy, remove} from "@welshman/lib"
|
import type {Readable} from "svelte/store"
|
||||||
|
import type {Editor} from "svelte-tiptap"
|
||||||
|
import {nip19} from "nostr-tools"
|
||||||
|
import {int, nthNe, MINUTE, sortBy, remove, ctx} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {createEvent, DIRECT_MESSAGE, INBOX_RELAYS} from "@welshman/util"
|
import {createEvent, DIRECT_MESSAGE, INBOX_RELAYS} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
@@ -52,6 +55,15 @@
|
|||||||
const showMembers = () =>
|
const showMembers = () =>
|
||||||
pushModal(ProfileList, {pubkeys: others, title: `People in this conversation`})
|
pushModal(ProfileList, {pubkeys: others, title: `People in this conversation`})
|
||||||
|
|
||||||
|
const replyTo = (event: TrustedEvent) => {
|
||||||
|
const relays = ctx.app.router.Event(event).getUrls()
|
||||||
|
const nevent = nip19.neventEncode({...event, relays})
|
||||||
|
|
||||||
|
$editor.commands.insertNEvent({nevent})
|
||||||
|
$editor.commands.insertContent("\n")
|
||||||
|
$editor.commands.focus()
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = async ({content, ...params}: EventContent) => {
|
const onSubmit = async ({content, ...params}: EventContent) => {
|
||||||
// Remove p tags since they result in forking the conversation
|
// Remove p tags since they result in forking the conversation
|
||||||
const tags = [...params.tags.filter(nthNe(0, "p")), ...remove($pubkey!, pubkeys).map(tagPubkey)]
|
const tags = [...params.tags.filter(nthNe(0, "p")), ...remove($pubkey!, pubkeys).map(tagPubkey)]
|
||||||
@@ -64,6 +76,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let loading = true
|
let loading = true
|
||||||
|
let editor: Readable<Editor>
|
||||||
let elements: Element[] = []
|
let elements: Element[] = []
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
@@ -170,7 +183,7 @@
|
|||||||
{#if type === "date"}
|
{#if type === "date"}
|
||||||
<Divider>{value}</Divider>
|
<Divider>{value}</Divider>
|
||||||
{:else}
|
{:else}
|
||||||
<ChatMessage event={assertEvent(value)} {pubkeys} {showPubkey} />
|
<ChatMessage event={assertEvent(value)} {pubkeys} {showPubkey} {replyTo} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<p
|
<p
|
||||||
@@ -185,5 +198,5 @@
|
|||||||
<slot name="info" />
|
<slot name="info" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ChatCompose {onSubmit} />
|
<ChatCompose bind:editor {onSubmit} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
export let event: TrustedEvent
|
export let event: TrustedEvent
|
||||||
|
export let replyTo: any = undefined
|
||||||
export let pubkeys: string[]
|
export let pubkeys: string[]
|
||||||
export let showPubkey = false
|
export let showPubkey = false
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
<ThunkStatus {thunk} class="mt-1" />
|
<ThunkStatus {thunk} class="mt-1" />
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div
|
||||||
|
data-event={event.id}
|
||||||
class="group chat flex items-center justify-end gap-1 px-2"
|
class="group chat flex items-center justify-end gap-1 px-2"
|
||||||
class:chat-start={event.pubkey !== $pubkey}
|
class:chat-start={event.pubkey !== $pubkey}
|
||||||
class:flex-row-reverse={event.pubkey !== $pubkey}
|
class:flex-row-reverse={event.pubkey !== $pubkey}
|
||||||
@@ -66,7 +68,7 @@
|
|||||||
<Tippy
|
<Tippy
|
||||||
bind:popover
|
bind:popover
|
||||||
component={ChatMessageMenu}
|
component={ChatMessageMenu}
|
||||||
props={{event, pubkeys, popover}}
|
props={{event, pubkeys, popover, replyTo}}
|
||||||
params={{
|
params={{
|
||||||
interactive: true,
|
interactive: true,
|
||||||
trigger: "manual",
|
trigger: "manual",
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
export let event
|
export let event
|
||||||
export let pubkeys
|
export let pubkeys
|
||||||
export let popover
|
export let popover
|
||||||
|
export let replyTo
|
||||||
|
|
||||||
|
const reply = () => replyTo(event)
|
||||||
|
|
||||||
const showInfo = () => {
|
const showInfo = () => {
|
||||||
popover.hide()
|
popover.hide()
|
||||||
@@ -17,6 +20,11 @@
|
|||||||
|
|
||||||
<div class="join border border-solid border-neutral text-xs">
|
<div class="join border border-solid border-neutral text-xs">
|
||||||
<ChatMessageEmojiButton {event} {pubkeys} />
|
<ChatMessageEmojiButton {event} {pubkeys} />
|
||||||
|
{#if replyTo}
|
||||||
|
<Button class="btn join-item btn-xs" on:click={reply}>
|
||||||
|
<Icon size={4} icon="reply" />
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
<Button class="btn join-item btn-xs" on:click={showInfo}>
|
<Button class="btn join-item btn-xs" on:click={showInfo}>
|
||||||
<Icon size={4} icon="code-2" />
|
<Icon size={4} icon="code-2" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<code
|
<code
|
||||||
class="w-full overflow-auto whitespace-pre rounded bg-neutral p-2 text-neutral-content"
|
class="w-full overflow-auto whitespace-pre rounded bg-neutral px-1 text-neutral-content"
|
||||||
class:block={isBlock}>
|
class:block={isBlock}>
|
||||||
{value.trim()}
|
{value.trim()}
|
||||||
</code>
|
</code>
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
|
import {goto} from "$app/navigation"
|
||||||
import {ctx, nthEq} from "@welshman/lib"
|
import {ctx, nthEq} from "@welshman/lib"
|
||||||
import {Address} from "@welshman/util"
|
import {Address, DIRECT_MESSAGE} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import {repository} from "@welshman/app"
|
||||||
import Link from "@lib/components/Link.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import NoteCard from "@app/components/NoteCard.svelte"
|
import NoteCard from "@app/components/NoteCard.svelte"
|
||||||
|
import ChannelConversation from "@app/components/ChannelConversation.svelte"
|
||||||
import {deriveEvent, entityLink, MESSAGE, THREAD} from "@app/state"
|
import {deriveEvent, entityLink, MESSAGE, THREAD} from "@app/state"
|
||||||
import {makeThreadPath} from "@app/routes"
|
import {makeThreadPath} from "@app/routes"
|
||||||
|
import {pushDrawer} from "@app/modal"
|
||||||
|
|
||||||
export let value
|
export let value
|
||||||
export let event
|
export let event
|
||||||
@@ -20,25 +23,63 @@
|
|||||||
const quote = deriveEvent(idOrAddress, relays)
|
const quote = deriveEvent(idOrAddress, relays)
|
||||||
const entity = id ? nip19.neventEncode({id, relays}) : addr.toNaddr()
|
const entity = id ? nip19.neventEncode({id, relays}) : addr.toNaddr()
|
||||||
|
|
||||||
const getLocalHref = (e: TrustedEvent) => {
|
const scrollToEvent = (id: string) => {
|
||||||
const url = e.tags.find(nthEq(0, "~"))?.[2]
|
const element = document.querySelector(`[data-event="${id}"]`)
|
||||||
|
|
||||||
if (!url) return
|
if (element) {
|
||||||
if ([MESSAGE, THREAD].includes(e.kind)) return makeThreadPath(url, e.id)
|
element.scrollIntoView({behavior: "smooth"})
|
||||||
|
}
|
||||||
|
|
||||||
const kind = e.tags.find(nthEq(0, "K"))?.[1]
|
return Boolean(element)
|
||||||
const id = e.tags.find(nthEq(0, "E"))?.[1]
|
|
||||||
|
|
||||||
if (!id || !kind) return
|
|
||||||
if ([MESSAGE, THREAD].includes(parseInt(kind))) return makeThreadPath(url, id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found this event on a relay that the user is a member of, redirect internally
|
const openMessage = (url: string, room: string, id: string) => {
|
||||||
$: localHref = $quote ? getLocalHref($quote) : null
|
const event = repository.getEvent(id)
|
||||||
$: href = localHref || entityLink(entity)
|
|
||||||
|
if (event) {
|
||||||
|
return pushDrawer(ChannelConversation, {url, room, event})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClick = (e: Event) => {
|
||||||
|
if ($quote) {
|
||||||
|
if ($quote.kind === DIRECT_MESSAGE) {
|
||||||
|
return scrollToEvent($quote.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [room, url] = $quote.tags.find(nthEq(0, "~"))?.slice(1) || []
|
||||||
|
|
||||||
|
if (url && room) {
|
||||||
|
if ($quote.kind === THREAD) {
|
||||||
|
return goto(makeThreadPath(url, $quote.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($quote.kind === MESSAGE) {
|
||||||
|
return scrollToEvent($quote.id) || openMessage(url, room, $quote.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const kind = $quote.tags.find(nthEq(0, "K"))?.[1]
|
||||||
|
const id = $quote.tags.find(nthEq(0, "E"))?.[1]
|
||||||
|
|
||||||
|
if (id && kind) {
|
||||||
|
if (parseInt(kind) === THREAD) {
|
||||||
|
return goto(makeThreadPath(url, id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parseInt(kind) === MESSAGE) {
|
||||||
|
return scrollToEvent(id) || openMessage(url, room, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(entityLink(entity))
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link external={!localHref} {href} class="my-2 block max-w-full text-left">
|
<Button class="my-2 block max-w-full text-left" on:click={onClick}>
|
||||||
{#if $quote}
|
{#if $quote}
|
||||||
<NoteCard event={$quote} class="bg-alt rounded-box p-4">
|
<NoteCard event={$quote} class="bg-alt rounded-box p-4">
|
||||||
<slot name="note-content" event={$quote} {depth} />
|
<slot name="note-content" event={$quote} {depth} />
|
||||||
@@ -48,4 +89,4 @@
|
|||||||
<Spinner loading>Loading event...</Spinner>
|
<Spinner loading>Loading event...</Spinner>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Link>
|
</Button>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
|
import {ctx} from "@welshman/lib"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import FieldInline from "@lib/components/FieldInline.svelte"
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
@@ -8,10 +9,11 @@
|
|||||||
|
|
||||||
export let event
|
export let event
|
||||||
|
|
||||||
const note1 = nip19.noteEncode(event.id)
|
const relays = ctx.app.router.Event(event).getUrls()
|
||||||
|
const nevent1 = nip19.neventEncode({...event, relays})
|
||||||
const npub1 = nip19.npubEncode(event.pubkey)
|
const npub1 = nip19.npubEncode(event.pubkey)
|
||||||
const json = JSON.stringify(event, null, 2)
|
const json = JSON.stringify(event, null, 2)
|
||||||
const copyId = () => clip(note1)
|
const copyLink = () => clip(nevent1)
|
||||||
const copyPubkey = () => clip(npub1)
|
const copyPubkey = () => clip(npub1)
|
||||||
const copyJson = () => clip(json)
|
const copyJson = () => clip(json)
|
||||||
</script>
|
</script>
|
||||||
@@ -22,11 +24,11 @@
|
|||||||
<div slot="info">The full details of this event are shown below.</div>
|
<div slot="info">The full details of this event are shown below.</div>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
<p slot="label">Event ID</p>
|
<p slot="label">Event Link</p>
|
||||||
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
|
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
|
||||||
<Icon icon="file" />
|
<Icon icon="file" />
|
||||||
<input type="text" class="ellipsize min-w-0 grow" value={note1} />
|
<input type="text" class="ellipsize min-w-0 grow" value={nevent1} />
|
||||||
<Button on:click={copyId} class="flex items-center">
|
<Button on:click={copyLink} class="flex items-center">
|
||||||
<Icon icon="copy" />
|
<Icon icon="copy" />
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {createFeedController} from "@welshman/app"
|
import {createFeedController} from "@welshman/app"
|
||||||
import {createScroller} from "@lib/html"
|
import {createScroller} from "@lib/html"
|
||||||
|
import {fly} from "@lib/transition"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import NoteItem from "@app/components/NoteItem.svelte"
|
import NoteItem from "@app/components/NoteItem.svelte"
|
||||||
|
|
||||||
@@ -54,7 +55,9 @@
|
|||||||
<div class="col-4" bind:this={element}>
|
<div class="col-4" bind:this={element}>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{#each events as event (event.id)}
|
{#each events as event (event.id)}
|
||||||
<NoteItem {url} {event} />
|
<div in:fly>
|
||||||
|
<NoteItem {url} {event} />
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<p class="center my-12 flex">
|
<p class="center my-12 flex">
|
||||||
<Spinner loading />
|
<Spinner loading />
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
export let selected: NodeViewProps["selected"]
|
export let selected: NodeViewProps["selected"]
|
||||||
|
|
||||||
const displayEvent = (e: TrustedEvent) => {
|
const displayEvent = (e: TrustedEvent) => {
|
||||||
const content = e?.tags.find(nthEq(0, "alt"))?.[1] || e?.content
|
const content = e?.tags.find(nthEq(0, "alt"))?.[1] || e?.content || ""
|
||||||
|
|
||||||
return content.length > 1
|
return content.length > 1
|
||||||
? ellipsize(content, 30)
|
? ellipsize(content, 30)
|
||||||
|
|||||||
@@ -8,9 +8,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {nip19} from "nostr-tools"
|
||||||
import {onMount, onDestroy} from "svelte"
|
import {onMount, onDestroy} from "svelte"
|
||||||
|
import type {Readable} from "svelte/store"
|
||||||
|
import type {Editor} from "svelte-tiptap"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {sortBy, append, now} from "@welshman/lib"
|
import {sortBy, append, now, ctx} from "@welshman/lib"
|
||||||
import type {TrustedEvent, EventContent} from "@welshman/util"
|
import type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {createEvent, DELETE} from "@welshman/util"
|
import {createEvent, DELETE} from "@welshman/util"
|
||||||
import {formatTimestampAsDate, publishThunk} from "@welshman/app"
|
import {formatTimestampAsDate, publishThunk} from "@welshman/app"
|
||||||
@@ -47,6 +50,15 @@
|
|||||||
|
|
||||||
const assertEvent = (e: any) => e as TrustedEvent
|
const assertEvent = (e: any) => e as TrustedEvent
|
||||||
|
|
||||||
|
const replyTo = (event: TrustedEvent) => {
|
||||||
|
const relays = ctx.app.router.Event(event).getUrls()
|
||||||
|
const nevent = nip19.neventEncode({...event, relays})
|
||||||
|
|
||||||
|
$editor.commands.insertNEvent({nevent})
|
||||||
|
$editor.commands.insertContent("\n")
|
||||||
|
$editor.commands.focus()
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = ({content, tags}: EventContent) =>
|
const onSubmit = ({content, tags}: EventContent) =>
|
||||||
publishThunk({
|
publishThunk({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
@@ -55,6 +67,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
let loading = true
|
let loading = true
|
||||||
|
let editor: Readable<Editor>
|
||||||
let elements: Element[] = []
|
let elements: Element[] = []
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
@@ -142,8 +155,8 @@
|
|||||||
{#if type === "date"}
|
{#if type === "date"}
|
||||||
<Divider>{value}</Divider>
|
<Divider>{value}</Divider>
|
||||||
{:else}
|
{:else}
|
||||||
<div in:slide>
|
<div in:slide class:-mt-4={!showPubkey}>
|
||||||
<ChannelMessage {url} {room} event={assertEvent(value)} {showPubkey} />
|
<ChannelMessage {url} {room} {replyTo} event={assertEvent(value)} {showPubkey} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -157,5 +170,5 @@
|
|||||||
</Spinner>
|
</Spinner>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ChannelCompose {content} {onSubmit} />
|
<ChannelCompose bind:editor {content} {onSubmit} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import {sortBy, sleep, uniqBy, now} from "@welshman/lib"
|
import {sortBy, sleep, uniqBy, now} from "@welshman/lib"
|
||||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
import {getListTags, getPubkeyTagValues, LOCAL_RELAY_URL} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
||||||
import {nthEq} from "@welshman/lib"
|
import {nthEq} from "@welshman/lib"
|
||||||
@@ -72,8 +72,21 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
const unsub = subscribePersistent({
|
const unsub = subscribePersistent({
|
||||||
relays: [url],
|
relays: [url, LOCAL_RELAY_URL],
|
||||||
filters: [{kinds: [COMMENT], "#K": [String(THREAD)], since: now()}],
|
filters: [
|
||||||
|
{kinds: [THREAD], since: now()},
|
||||||
|
{kinds: [COMMENT], "#K": [String(THREAD)], since: now()},
|
||||||
|
],
|
||||||
|
onEvent: (event: TrustedEvent) => {
|
||||||
|
if (event.kind === THREAD) {
|
||||||
|
const index = Math.max(
|
||||||
|
0,
|
||||||
|
events.findIndex(e => e.created_at < event.created_at),
|
||||||
|
)
|
||||||
|
|
||||||
|
events = [...events.slice(0, index), event, ...events.slice(index)]
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user