Show two levels of comments under each home network note

This commit is contained in:
Coracle-Bot 2026-09-09 01:13:19 +00:00 committed by hodlbod
parent 27fa25c35c
commit ff3461a020
4 changed files with 83 additions and 18 deletions

View file

@ -21,18 +21,19 @@
node: CommentNode
root: TrustedEvent
replyTo?: TrustedEvent
setReplyTo: (comment?: TrustedEvent) => void
setReplyTo?: (comment?: TrustedEvent) => void
url?: string
context: FeedContext
maxDepth?: number
}
const {node, root, replyTo, setReplyTo, url, context}: Props = $props()
const {node, root, replyTo, setReplyTo, url, context, maxDepth = Infinity}: Props = $props()
const composing = $derived(replyTo?.id === node.comment.id)
const reply = () => setReplyTo(node.comment)
const reply = () => setReplyTo?.(node.comment)
const clearReplyTo = () => setReplyTo(undefined)
const clearReplyTo = () => setReplyTo?.(undefined)
const openProfile = () => pushModal(ProfileDetail, {pubkey: node.comment.pubkey, url})
@ -70,11 +71,13 @@
</div>
<Content showEntire event={node.comment} {url} />
{#if url}
<div class="mt-1 flex flex-wrap items-center justify-between gap-2">
<Button class="button button-neutral button-xs" onclick={reply}>
<Icon icon={Reply} size={4} />
Reply
</Button>
<div class="mt-1 flex flex-wrap items-center justify-end gap-2">
{#if setReplyTo}
<Button class="button button-neutral button-xs mr-auto" onclick={reply}>
<Icon icon={Reply} size={4} />
Reply
</Button>
{/if}
<CommentActions event={node.comment} {url} {context} />
</div>
{/if}
@ -91,14 +94,21 @@
onSubmit={clearReplyTo} />
</div>
{/if}
{#if node.children.length > 0}
{#if node.children.length > 0 && maxDepth > 1}
<!-- The thread line runs under the avatar's center and indents replies to line up with
this comment's text column; it brightens while the subtree is hovered. -->
<div
data-component="CommentReplies"
class="border-line-less hover:border-line ml-4 flex flex-col border-l pl-7 transition-colors">
{#each node.children as child (child.comment.id)}
<CommentTree node={child} {root} {replyTo} {setReplyTo} {url} {context} />
<CommentTree
node={child}
{root}
{replyTo}
{setReplyTo}
{url}
{context}
maxDepth={maxDepth - 1} />
{/each}
</div>
{/if}

View file

@ -12,7 +12,7 @@
import Masonry from "@lib/components/Masonry.svelte"
import Spinner from "@lib/components/Spinner.svelte"
import HomeSection from "@app/components/HomeSection.svelte"
import NoteItem from "@app/components/NoteItem.svelte"
import HomeNetworkItem from "@app/components/HomeNetworkItem.svelte"
import {followLists, relayLists, router, user} from "@app/core"
import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds"
@ -20,7 +20,10 @@
// `syncFollowNetwork` reads their lists from.
const RELAY_LIMIT = 8
const context = makeFeedContext({relays: $relayLists.readUrls($user.pubkey).get()})
const context = makeFeedContext({
relays: $relayLists.readUrls($user.pubkey).get(),
withReplies: true,
})
const followList = $derived($followLists.one($user.pubkey))
const follows = $derived($followList?.pubkeys())
@ -91,7 +94,7 @@
{:else}
<Masonry items={notes} getKey={event => event.id} columnWidth={80} maxColumns={2} gap={3}>
{#snippet child(event)}
<NoteItem {event} {context} />
<HomeNetworkItem {event} {context} />
{/snippet}
</Masonry>
{#if loading}

View file

@ -0,0 +1,41 @@
<script lang="ts">
import {sortBy} from "@welshman/lib"
import {COMMENT, NOTE} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import NoteItem from "@app/components/NoteItem.svelte"
import CommentTree from "@app/components/CommentTree.svelte"
import type {FeedContext} from "@app/feeds"
import {buildCommentTree} from "@app/social"
type Props = {
event: TrustedEvent
context: FeedContext
}
const {event, context}: Props = $props()
const related = context.related(event)
// buildCommentTree adopts a comment whose parent never loaded, and reads oldest first so
// that it adopts a parent before its own children.
const nodes = $derived(
buildCommentTree(
event,
sortBy(
e => e.created_at,
$related.filter(e => e.kind === COMMENT || e.kind === NOTE),
),
),
)
</script>
<div class="cv card card-interactive flex flex-col gap-3">
<NoteItem {event} {context} class="" />
{#if nodes.length > 0}
<div class="border-line-less flex flex-col border-t">
{#each nodes as node (node.comment.id)}
<CommentTree {node} root={event} {context} maxDepth={2} />
{/each}
</div>
{/if}
</div>

View file

@ -5,6 +5,7 @@ import {
COMMENT,
DELETE,
EVENT_TIME,
NOTE,
addressTags,
compareEventsAsc,
getAddress,
@ -58,7 +59,17 @@ const getTargets = ({kind, tags}: TrustedEvent) =>
const getKeys = (event: TrustedEvent) =>
isReplaceableKind(event.kind) ? [event.id, getAddress(event)] : [event.id]
export const makeFeedContext = ({relays}: {relays: string[] | Promise<string[]>}) => {
export const makeFeedContext = ({
relays,
withReplies = false,
}: {
relays: string[] | Promise<string[]>
withReplies?: boolean
}) => {
// Every feed loads NIP-22 comments. Kind 1 notes reply to each other with `e` tags instead,
// so a feed that renders those replies has to ask for them as well.
const contextKinds = withReplies ? [...EVENT_CONTEXT_KINDS, NOTE] : EVENT_CONTEXT_KINDS
const requestKinds = withReplies ? [...REACTION_KINDS, NOTE] : REACTION_KINDS
const {repository} = app.get()
const controller = new AbortController()
const targets = new Set<string>()
@ -134,7 +145,7 @@ export const makeFeedContext = ({relays}: {relays: string[] | Promise<string[]>}
// What's already local — an earlier page, our own optimistic reactions — never comes
// through the update listener, so file it before asking the network for the rest
for (const event of repository.query([
...getReplyFilters(events, {kinds: EVENT_CONTEXT_KINDS}),
...getReplyFilters(events, {kinds: contextKinds}),
...getCommentFiltersForRoot(events),
])) {
addEvent(event, touched)
@ -148,7 +159,7 @@ export const makeFeedContext = ({relays}: {relays: string[] | Promise<string[]>}
relays: urls,
signal: controller.signal,
filters: [
...getReplyFilters(events, {kinds: REACTION_KINDS}),
...getReplyFilters(events, {kinds: requestKinds}),
...getCommentFiltersForRoot(events),
],
})
@ -170,7 +181,7 @@ export const makeFeedContext = ({relays}: {relays: string[] | Promise<string[]>}
const touched = new Set<string>()
for (const event of added) {
if (EVENT_CONTEXT_KINDS.includes(event.kind)) {
if (contextKinds.includes(event.kind)) {
addEvent(event, touched)
}
}