Show two levels of comments under each home network note
This commit is contained in:
parent
27fa25c35c
commit
ff3461a020
4 changed files with 83 additions and 18 deletions
|
|
@ -21,18 +21,19 @@
|
||||||
node: CommentNode
|
node: CommentNode
|
||||||
root: TrustedEvent
|
root: TrustedEvent
|
||||||
replyTo?: TrustedEvent
|
replyTo?: TrustedEvent
|
||||||
setReplyTo: (comment?: TrustedEvent) => void
|
setReplyTo?: (comment?: TrustedEvent) => void
|
||||||
url?: string
|
url?: string
|
||||||
context: FeedContext
|
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 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})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey: node.comment.pubkey, url})
|
||||||
|
|
||||||
|
|
@ -70,11 +71,13 @@
|
||||||
</div>
|
</div>
|
||||||
<Content showEntire event={node.comment} {url} />
|
<Content showEntire event={node.comment} {url} />
|
||||||
{#if url}
|
{#if url}
|
||||||
<div class="mt-1 flex flex-wrap items-center justify-between gap-2">
|
<div class="mt-1 flex flex-wrap items-center justify-end gap-2">
|
||||||
<Button class="button button-neutral button-xs" onclick={reply}>
|
{#if setReplyTo}
|
||||||
<Icon icon={Reply} size={4} />
|
<Button class="button button-neutral button-xs mr-auto" onclick={reply}>
|
||||||
Reply
|
<Icon icon={Reply} size={4} />
|
||||||
</Button>
|
Reply
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
<CommentActions event={node.comment} {url} {context} />
|
<CommentActions event={node.comment} {url} {context} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -91,14 +94,21 @@
|
||||||
onSubmit={clearReplyTo} />
|
onSubmit={clearReplyTo} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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
|
<!-- 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. -->
|
this comment's text column; it brightens while the subtree is hovered. -->
|
||||||
<div
|
<div
|
||||||
data-component="CommentReplies"
|
data-component="CommentReplies"
|
||||||
class="border-line-less hover:border-line ml-4 flex flex-col border-l pl-7 transition-colors">
|
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)}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
import Masonry from "@lib/components/Masonry.svelte"
|
import Masonry from "@lib/components/Masonry.svelte"
|
||||||
import Spinner from "@lib/components/Spinner.svelte"
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import HomeSection from "@app/components/HomeSection.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 {followLists, relayLists, router, user} from "@app/core"
|
||||||
import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds"
|
import {isFeedLoading, makeFeed, makeFeedContext, makeScrollLoader} from "@app/feeds"
|
||||||
|
|
||||||
|
|
@ -20,7 +20,10 @@
|
||||||
// `syncFollowNetwork` reads their lists from.
|
// `syncFollowNetwork` reads their lists from.
|
||||||
const RELAY_LIMIT = 8
|
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 followList = $derived($followLists.one($user.pubkey))
|
||||||
const follows = $derived($followList?.pubkeys())
|
const follows = $derived($followList?.pubkeys())
|
||||||
|
|
@ -91,7 +94,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<Masonry items={notes} getKey={event => event.id} columnWidth={80} maxColumns={2} gap={3}>
|
<Masonry items={notes} getKey={event => event.id} columnWidth={80} maxColumns={2} gap={3}>
|
||||||
{#snippet child(event)}
|
{#snippet child(event)}
|
||||||
<NoteItem {event} {context} />
|
<HomeNetworkItem {event} {context} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Masonry>
|
</Masonry>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
|
|
|
||||||
41
src/app/components/HomeNetworkItem.svelte
Normal file
41
src/app/components/HomeNetworkItem.svelte
Normal 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>
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
COMMENT,
|
COMMENT,
|
||||||
DELETE,
|
DELETE,
|
||||||
EVENT_TIME,
|
EVENT_TIME,
|
||||||
|
NOTE,
|
||||||
addressTags,
|
addressTags,
|
||||||
compareEventsAsc,
|
compareEventsAsc,
|
||||||
getAddress,
|
getAddress,
|
||||||
|
|
@ -58,7 +59,17 @@ const getTargets = ({kind, tags}: TrustedEvent) =>
|
||||||
const getKeys = (event: TrustedEvent) =>
|
const getKeys = (event: TrustedEvent) =>
|
||||||
isReplaceableKind(event.kind) ? [event.id, getAddress(event)] : [event.id]
|
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 {repository} = app.get()
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const targets = new Set<string>()
|
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
|
// 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
|
// through the update listener, so file it before asking the network for the rest
|
||||||
for (const event of repository.query([
|
for (const event of repository.query([
|
||||||
...getReplyFilters(events, {kinds: EVENT_CONTEXT_KINDS}),
|
...getReplyFilters(events, {kinds: contextKinds}),
|
||||||
...getCommentFiltersForRoot(events),
|
...getCommentFiltersForRoot(events),
|
||||||
])) {
|
])) {
|
||||||
addEvent(event, touched)
|
addEvent(event, touched)
|
||||||
|
|
@ -148,7 +159,7 @@ export const makeFeedContext = ({relays}: {relays: string[] | Promise<string[]>}
|
||||||
relays: urls,
|
relays: urls,
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
filters: [
|
filters: [
|
||||||
...getReplyFilters(events, {kinds: REACTION_KINDS}),
|
...getReplyFilters(events, {kinds: requestKinds}),
|
||||||
...getCommentFiltersForRoot(events),
|
...getCommentFiltersForRoot(events),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
@ -170,7 +181,7 @@ export const makeFeedContext = ({relays}: {relays: string[] | Promise<string[]>}
|
||||||
const touched = new Set<string>()
|
const touched = new Set<string>()
|
||||||
|
|
||||||
for (const event of added) {
|
for (const event of added) {
|
||||||
if (EVENT_CONTEXT_KINDS.includes(event.kind)) {
|
if (contextKinds.includes(event.kind)) {
|
||||||
addEvent(event, touched)
|
addEvent(event, touched)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue