2026-08-17 15:42:14 +00:00
|
|
|
<script lang="ts">
|
|
|
|
|
import {onMount} from "svelte"
|
|
|
|
|
import {removeUndefined} from "@welshman/lib"
|
|
|
|
|
import type {TrustedEvent} from "@welshman/util"
|
|
|
|
|
import {getCommentFiltersForRoot} from "@welshman/util"
|
|
|
|
|
import {deriveEventsAsc} from "@welshman/store"
|
|
|
|
|
import Reply from "@assets/icons/reply-2.svg?dataurl"
|
|
|
|
|
import Icon from "@lib/components/Icon.svelte"
|
|
|
|
|
import Button from "@lib/components/Button.svelte"
|
|
|
|
|
import CommentTree from "@app/components/CommentTree.svelte"
|
2026-08-29 16:19:01 +00:00
|
|
|
import type {FeedContext} from "@app/feeds"
|
2026-08-17 15:42:14 +00:00
|
|
|
import CommentCompose from "@app/components/CommentCompose.svelte"
|
|
|
|
|
import {network} from "@app/core"
|
|
|
|
|
import {deriveEventsById} from "@app/repository"
|
|
|
|
|
import {buildCommentTree} from "@app/social"
|
|
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
|
event: TrustedEvent
|
|
|
|
|
url?: string
|
2026-08-29 16:19:01 +00:00
|
|
|
context: FeedContext
|
2026-08-17 15:42:14 +00:00
|
|
|
}
|
|
|
|
|
|
2026-08-29 16:19:01 +00:00
|
|
|
const {event, url, context}: Props = $props()
|
2026-08-17 15:42:14 +00:00
|
|
|
|
|
|
|
|
const relays = removeUndefined([url])
|
|
|
|
|
const filters = getCommentFiltersForRoot([event])
|
|
|
|
|
const comments = deriveEventsAsc(deriveEventsById(filters))
|
|
|
|
|
|
|
|
|
|
const nodes = $derived(buildCommentTree(event, $comments))
|
|
|
|
|
|
|
|
|
|
const setReplyTo = (comment?: TrustedEvent) => {
|
|
|
|
|
replyTo = comment
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const commentOnRoot = () => setReplyTo(event)
|
|
|
|
|
|
|
|
|
|
const clearReplyTo = () => setReplyTo(undefined)
|
|
|
|
|
|
|
|
|
|
let replyTo: TrustedEvent | undefined = $state()
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
if (relays.length > 0) {
|
|
|
|
|
const controller = new AbortController()
|
|
|
|
|
|
|
|
|
|
$network.request({relays, filters, signal: controller.signal})
|
|
|
|
|
|
|
|
|
|
return () => controller.abort()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
2026-08-18 21:18:10 +00:00
|
|
|
<section class="flex flex-col">
|
|
|
|
|
<!-- The heading rule is a page-level divider, so it runs full width; the comments below it
|
|
|
|
|
stay in the article's reading column. -->
|
|
|
|
|
<div class="border-b px-5 sm:px-8" style="border-color: var(--line)">
|
|
|
|
|
<div class="mx-auto flex w-full max-w-[68ch] flex-wrap items-center justify-between gap-2 py-3">
|
|
|
|
|
<h2 class="text-lg font-bold">
|
|
|
|
|
{nodes.length === 0
|
|
|
|
|
? "Discussion about this post"
|
|
|
|
|
: `${$comments.length} ${$comments.length === 1 ? "comment" : "comments"}`}
|
|
|
|
|
</h2>
|
|
|
|
|
{#if url && replyTo?.id !== event.id}
|
|
|
|
|
<Button class="button button-primary button-sm" onclick={commentOnRoot}>
|
|
|
|
|
<Icon icon={Reply} size={4} />
|
|
|
|
|
Add a comment
|
|
|
|
|
</Button>
|
|
|
|
|
{/if}
|
2026-08-17 15:42:14 +00:00
|
|
|
</div>
|
2026-08-18 21:18:10 +00:00
|
|
|
</div>
|
|
|
|
|
<div class="px-5 sm:px-8">
|
|
|
|
|
<div class="mx-auto flex w-full max-w-[68ch] flex-col pb-10">
|
|
|
|
|
{#if url && replyTo?.id === event.id}
|
|
|
|
|
<div class="py-4">
|
|
|
|
|
<CommentCompose {url} {event} onCancel={clearReplyTo} onSubmit={clearReplyTo} />
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
{#each nodes as node (node.comment.id)}
|
|
|
|
|
<CommentTree {node} root={event} {replyTo} {setReplyTo} {url} {context} />
|
|
|
|
|
{/each}
|
|
|
|
|
{#if nodes.length === 0 && replyTo?.id !== event.id}
|
|
|
|
|
<p class="py-6 text-sm opacity-60">No comments yet.</p>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|