From 8430a55bebf839b849f7ea1de2647999dab68f92 Mon Sep 17 00:00:00 2001 From: en Date: Thu, 30 Jan 2025 09:34:34 +0100 Subject: [PATCH 1/7] fix(comments): cache first comments fetch --- src/loaders/comment.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/loaders/comment.ts b/src/loaders/comment.ts index 829ae36..ce12d7c 100644 --- a/src/loaders/comment.ts +++ b/src/loaders/comment.ts @@ -1,4 +1,8 @@ -import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk' +import { + NDKEvent, + NDKKind, + NDKSubscriptionCacheUsage +} from '@nostr-dev-kit/ndk' import { NDKContextType } from 'contexts/NDKContext' import { LoaderFunctionArgs, redirect } from 'react-router-dom' import { CommentsLoaderResult } from 'types/comments' @@ -24,10 +28,15 @@ export const commentsLoader = const replies: NDKEvent[] = [] let eTag: string | undefined = replyEvent.tagValue('e') while (eTag) { - const prev = await ndkContext.ndk.fetchEvent({ - kinds: [NDKKind.Text, NDKKind.GenericReply], - ids: [eTag] - }) + const prev = await ndkContext.ndk.fetchEvent( + { + kinds: [NDKKind.Text, NDKKind.GenericReply], + ids: [eTag] + }, + { + cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST + } + ) if (prev) { replies.push(prev) eTag = prev.tagValue('e') @@ -39,6 +48,7 @@ export const commentsLoader = event: replyEvent, parents: replies } + console.log(replyEvent.content) return result } catch (error) { let message = 'An error occurred in fetching comment from relays' From b03fa6e55d5ac9bec0e8b75b450a795a324858e5 Mon Sep 17 00:00:00 2001 From: en Date: Thu, 30 Jan 2025 09:40:12 +0100 Subject: [PATCH 2/7] fix(comments): show new line in content p --- src/components/comment/CommentContent.tsx | 2 +- src/styles/comments.css | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/comment/CommentContent.tsx b/src/components/comment/CommentContent.tsx index 292a93f..4b6db38 100644 --- a/src/components/comment/CommentContent.tsx +++ b/src/components/comment/CommentContent.tsx @@ -7,7 +7,7 @@ export const CommentContent = ({ content }: CommentContentProps) => { return ( <> -

{text}

+

{text}

{isTextOverflowing && (

{isExpanded ? 'Hide' : 'View'} full post

diff --git a/src/styles/comments.css b/src/styles/comments.css index e728c0f..e3c1dcc 100644 --- a/src/styles/comments.css +++ b/src/styles/comments.css @@ -59,6 +59,7 @@ } .IBMSMSMBSSCL_CBText { + white-space: pre-line; } .IBMSMSMBSSCL_CBTextStatus { From 9b9e97b40edbc875e4d7fc7b87591b2c263af350 Mon Sep 17 00:00:00 2001 From: en Date: Thu, 30 Jan 2025 09:44:17 +0100 Subject: [PATCH 3/7] fix(comments): comment out repost button --- src/components/comment/Comment.tsx | 4 ++-- src/components/comment/CommentsPopup.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/comment/Comment.tsx b/src/components/comment/Comment.tsx index 46a2787..25cf56d 100644 --- a/src/components/comment/Comment.tsx +++ b/src/components/comment/Comment.tsx @@ -99,7 +99,7 @@ export const Comment = ({ comment }: CommentProps) => {
-
@@ -117,7 +117,7 @@ export const Comment = ({ comment }: CommentProps) => {
-
+
*/} {typeof profile?.lud16 !== 'undefined' && profile.lud16 !== '' && ( )} diff --git a/src/components/comment/CommentsPopup.tsx b/src/components/comment/CommentsPopup.tsx index 8cb7d6d..516505f 100644 --- a/src/components/comment/CommentsPopup.tsx +++ b/src/components/comment/CommentsPopup.tsx @@ -185,7 +185,7 @@ export const CommentsPopup = () => {
-
@@ -203,7 +203,7 @@ export const CommentsPopup = () => {
-
+
*/} {typeof profile?.lud16 !== 'undefined' && profile.lud16 !== '' && } From a92d1da7ad84e3f60675e927942ad53618703da0 Mon Sep 17 00:00:00 2001 From: en Date: Thu, 30 Jan 2025 11:12:03 +0100 Subject: [PATCH 4/7] fix(comments): move depth calc, parent and root fetch to hook, main post leads to root, add small spinners --- src/components/comment/CommentsPopup.tsx | 27 ++++++++---- src/hooks/index.ts | 1 + src/hooks/useReplies.tsx | 53 ++++++++++++++++++++++++ src/loaders/comment.ts | 28 +------------ src/types/comments.ts | 1 - 5 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 src/hooks/useReplies.tsx diff --git a/src/components/comment/CommentsPopup.tsx b/src/components/comment/CommentsPopup.tsx index 516505f..6cbc6ac 100644 --- a/src/components/comment/CommentsPopup.tsx +++ b/src/components/comment/CommentsPopup.tsx @@ -1,5 +1,5 @@ import { formatDate } from 'date-fns' -import { useBodyScrollDisable, useNDKContext } from 'hooks' +import { useBodyScrollDisable, useNDKContext, useReplies } from 'hooks' import { nip19 } from 'nostr-tools' import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' import { @@ -19,6 +19,7 @@ import { NDKKind } from '@nostr-dev-kit/ndk' import { Comment } from './Comment' import { useComments } from 'hooks/useComments' import { CommentContent } from './CommentContent' +import { Dots } from 'components/Spinner' export const CommentsPopup = () => { const { naddr } = useParams() @@ -35,7 +36,13 @@ export const CommentsPopup = () => { : undefined : undefined - const { event, parents } = useLoaderData() as CommentsLoaderResult + const { event } = useLoaderData() as CommentsLoaderResult + const { + size, + parent: replyEvent, + isComplete, + root: rootEvent + } = useReplies(event.tagValue('e')) const isRoot = event.tagValue('a') === event.tagValue('A') const [profile, setProfile] = useState() const { commentEvents, setCommentEvents } = useComments( @@ -56,7 +63,6 @@ export const CommentsPopup = () => { [event.pubkey] ) - const replyEvent = parents.length > 0 ? parents[0] : undefined const navigate = useNavigate() const [isSubmitting, setIsSubmitting] = useState(false) @@ -110,6 +116,9 @@ export const CommentsPopup = () => {
{replyEvent && ( @@ -126,16 +135,20 @@ export const CommentsPopup = () => { )}

- Reply Depth: {parents.length} + Reply Depth: {size} + {!isComplete && }

- {!isRoot && ( + {!isRoot && rootEvent && ( - Main Post + Main Post {!isComplete && } )}
diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 7746976..b397cf6 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -11,3 +11,4 @@ export * from './useScrollDisable' export * from './useLocalStorage' export * from './useSessionStorage' export * from './useLocalCache' +export * from './useReplies' diff --git a/src/hooks/useReplies.tsx b/src/hooks/useReplies.tsx new file mode 100644 index 0000000..5513d32 --- /dev/null +++ b/src/hooks/useReplies.tsx @@ -0,0 +1,53 @@ +import { + NDKEvent, + NDKKind, + NDKSubscriptionCacheUsage +} from '@nostr-dev-kit/ndk' +import { useState } from 'react' +import { useNDKContext } from './useNDKContext' +import { useDidMount } from './useDidMount' + +export const useReplies = (eTag: string | undefined) => { + const { ndk } = useNDKContext() + const [replies, setReplies] = useState([]) + const [isComplete, setIsComplete] = useState(false) + + useDidMount(async () => { + if (!eTag) { + setIsComplete(true) + return + } + + let eDepth: string | undefined = eTag + while (eDepth) { + const previousReply = await ndk.fetchEvent( + { + kinds: [NDKKind.Text, NDKKind.GenericReply], + ids: [eDepth] + }, + { + cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST + } + ) + if (previousReply) { + setReplies((p) => { + if (p.findIndex((p) => p.id === previousReply.id) === -1) { + p.push(previousReply) + } + return p + }) + eDepth = previousReply.tagValue('e') + } else { + eDepth = undefined + } + } + setIsComplete(true) + }) + + return { + size: replies.length, + isComplete, + parent: replies.length > 0 ? replies[0] : undefined, + root: isComplete ? replies[replies.length - 1] : undefined + } +} diff --git a/src/loaders/comment.ts b/src/loaders/comment.ts index ce12d7c..06fa85c 100644 --- a/src/loaders/comment.ts +++ b/src/loaders/comment.ts @@ -1,8 +1,3 @@ -import { - NDKEvent, - NDKKind, - NDKSubscriptionCacheUsage -} from '@nostr-dev-kit/ndk' import { NDKContextType } from 'contexts/NDKContext' import { LoaderFunctionArgs, redirect } from 'react-router-dom' import { CommentsLoaderResult } from 'types/comments' @@ -25,30 +20,9 @@ export const commentsLoader = throw new Error('We are unable to find the comment on the relays') } - const replies: NDKEvent[] = [] - let eTag: string | undefined = replyEvent.tagValue('e') - while (eTag) { - const prev = await ndkContext.ndk.fetchEvent( - { - kinds: [NDKKind.Text, NDKKind.GenericReply], - ids: [eTag] - }, - { - cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST - } - ) - if (prev) { - replies.push(prev) - eTag = prev.tagValue('e') - } else { - eTag = undefined - } - } const result: Partial = { - event: replyEvent, - parents: replies + event: replyEvent } - console.log(replyEvent.content) return result } catch (error) { let message = 'An error occurred in fetching comment from relays' diff --git a/src/types/comments.ts b/src/types/comments.ts index bdde1e4..a14a7f0 100644 --- a/src/types/comments.ts +++ b/src/types/comments.ts @@ -2,7 +2,6 @@ import { NDKEvent } from '@nostr-dev-kit/ndk' export interface CommentsLoaderResult { event: NDKEvent - parents: NDKEvent[] } export enum SortByEnum { From bf18d61f1f998435539a61ad09fbbc00ff5d3207 Mon Sep 17 00:00:00 2001 From: en Date: Thu, 30 Jan 2025 14:20:44 +0100 Subject: [PATCH 5/7] fix(comments): publish and discovery interaction, add discovery to popup --- src/components/comment/CommentsPopup.tsx | 62 ++++++++++++++++++++++-- src/components/comment/index.tsx | 39 +++++++++------ src/utils/comments.ts | 52 ++++++++++++-------- 3 files changed, 117 insertions(+), 36 deletions(-) diff --git a/src/components/comment/CommentsPopup.tsx b/src/components/comment/CommentsPopup.tsx index 6cbc6ac..ec1cdb8 100644 --- a/src/components/comment/CommentsPopup.tsx +++ b/src/components/comment/CommentsPopup.tsx @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom' import { getBlogPageRoute, getModPageRoute, getProfilePageRoute } from 'routes' -import { UserProfile } from 'types' +import { CommentEvent, UserProfile } from 'types' import { CommentsLoaderResult } from 'types/comments' import { adjustTextareaHeight, handleCommentSubmit, hexToNpub } from 'utils' import { Reactions } from './Reactions' @@ -74,7 +74,30 @@ export const CommentsPopup = () => { adjustTextareaHeight(e.currentTarget) }, []) - const handleSubmit = handleCommentSubmit(event, setCommentEvents, ndk) + const [visible, setVisible] = useState([]) + const discoveredCount = commentEvents.length - visible.length + const [isLoading, setIsLoading] = useState(true) + useEffect(() => { + // Initial loading to indicate comments fetching (stop after 5 seconds) + const t = window.setTimeout(() => setIsLoading(false), 5000) + return () => { + window.clearTimeout(t) + } + }, []) + useEffect(() => { + if (isLoading) { + setVisible(commentEvents) + } + }, [commentEvents, isLoading]) + const handleDiscoveredClick = () => { + setVisible(commentEvents) + } + const handleSubmit = handleCommentSubmit( + event, + setCommentEvents, + setVisible, + ndk + ) const handleComment = async () => { setIsSubmitting(true) @@ -273,8 +296,41 @@ export const CommentsPopup = () => {
{commentEvents.length > 0 && ( <> -

+

Replies +
+ +

{commentEvents.map((reply) => ( diff --git a/src/components/comment/index.tsx b/src/components/comment/index.tsx index 6edcdaf..7700e27 100644 --- a/src/components/comment/index.tsx +++ b/src/components/comment/index.tsx @@ -1,4 +1,4 @@ -import { Spinner } from 'components/Spinner' +import { Dots } from 'components/Spinner' import { useNDKContext } from 'hooks' import { useComments } from 'hooks/useComments' import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react' @@ -48,12 +48,16 @@ export const Comments = ({ addressable, setCommentCount }: Props) => { setCommentCount(commentEvents.length) }, [commentEvents, setCommentCount]) - const handleSubmit = handleCommentSubmit(event, setCommentEvents, ndk) - const handleDiscoveredClick = () => { setVisible(commentEvents) } const [visible, setVisible] = useState([]) + const handleSubmit = handleCommentSubmit( + event, + setCommentEvents, + setVisible, + ndk + ) useEffect(() => { if (isLoading) { setVisible(commentEvents) @@ -93,17 +97,24 @@ export const Comments = ({ addressable, setCommentCount }: Props) => { {/* Hide comment form if aTag is missing */} {!!addressable.aTag && }
- {isLoading ? ( - - ) : ( - - )} +
>, + setVisible: React.Dispatch>, ndk: NDK ) { return async (content: string): Promise => { @@ -19,13 +20,18 @@ export function handleCommentSubmit( const reply = event.reply() reply.content = content.trim() - setCommentEvents((prev) => [ - { - event: reply, - status: CommentEventStatus.Publishing - }, - ...prev - ]) + setCommentEvents((prev) => { + const newCommentEvents = [ + { + event: reply, + status: CommentEventStatus.Publishing + }, + ...prev + ] + setVisible(newCommentEvents) + return newCommentEvents + }) + if (!ndk.signer) { ndk.signer = new NDKNip07Signer() } @@ -33,8 +39,8 @@ export function handleCommentSubmit( id = reply.id const relaySet = await reply.publish() if (relaySet.size) { - setCommentEvents((prev) => - prev.map((ce) => { + setCommentEvents((prev) => { + const newCommentEvents = prev.map((ce) => { if (ce.event.id === reply.id) { return { event: ce.event, @@ -43,23 +49,27 @@ export function handleCommentSubmit( } return ce }) - ) + setVisible(newCommentEvents) + return newCommentEvents + }) // when an event is successfully published remove the status from it after 15 seconds setTimeout(() => { - setCommentEvents((prev) => - prev.map((ce) => { + setCommentEvents((prev) => { + const newCommentEvents = prev.map((ce) => { if (ce.event.id === reply.id) { delete ce.status } return ce }) - ) + setVisible(newCommentEvents) + return newCommentEvents + }) }, 15000) } else { log(true, LogType.Error, 'Publishing reply failed.') - setCommentEvents((prev) => - prev.map((ce) => { + setCommentEvents((prev) => { + const newCommentEvents = prev.map((ce) => { if (ce.event.id === reply.id) { return { event: ce.event, @@ -68,14 +78,16 @@ export function handleCommentSubmit( } return ce }) - ) + setVisible(newCommentEvents) + return newCommentEvents + }) } return false } catch (error) { toast.error('An error occurred in publishing reply.') log(true, LogType.Error, 'An error occurred in publishing reply.', error) - setCommentEvents((prev) => - prev.map((ce) => { + setCommentEvents((prev) => { + const newCommentEvents = prev.map((ce) => { if (ce.event.id === id) { return { event: ce.event, @@ -84,7 +96,9 @@ export function handleCommentSubmit( } return ce }) - ) + setVisible(newCommentEvents) + return newCommentEvents + }) return false } } From 94eb88bdd3054ffcf96c1471c76d7ebf53f569ca Mon Sep 17 00:00:00 2001 From: en Date: Thu, 30 Jan 2025 14:28:42 +0100 Subject: [PATCH 6/7] fix(comments): clear input on publish --- src/utils/comments.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/comments.ts b/src/utils/comments.ts index 3a7a8d0..74c4438 100644 --- a/src/utils/comments.ts +++ b/src/utils/comments.ts @@ -66,6 +66,7 @@ export function handleCommentSubmit( return newCommentEvents }) }, 15000) + return true } else { log(true, LogType.Error, 'Publishing reply failed.') setCommentEvents((prev) => { @@ -81,8 +82,8 @@ export function handleCommentSubmit( setVisible(newCommentEvents) return newCommentEvents }) + return false } - return false } catch (error) { toast.error('An error occurred in publishing reply.') log(true, LogType.Error, 'An error occurred in publishing reply.', error) From 1226c6091782f6564b4e732cd90a3d928b7855b7 Mon Sep 17 00:00:00 2001 From: en Date: Thu, 30 Jan 2025 14:42:12 +0100 Subject: [PATCH 7/7] refactor(comments): move style from react to css --- src/components/comment/CommentsPopup.tsx | 53 +++++++++--------------- src/styles/comments.css | 7 ++++ 2 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/components/comment/CommentsPopup.tsx b/src/components/comment/CommentsPopup.tsx index ec1cdb8..527261d 100644 --- a/src/components/comment/CommentsPopup.tsx +++ b/src/components/comment/CommentsPopup.tsx @@ -296,41 +296,28 @@ export const CommentsPopup = () => {
{commentEvents.length > 0 && ( <> -

+

Replies -
- -
+ + {isLoading ? ( + <> + Discovering replies + + + ) : discoveredCount ? ( + <>Load {discoveredCount} discovered replies + ) : ( + <>No new replies + )} + +

{commentEvents.map((reply) => ( diff --git a/src/styles/comments.css b/src/styles/comments.css index e3c1dcc..d0dc7ea 100644 --- a/src/styles/comments.css +++ b/src/styles/comments.css @@ -479,6 +479,13 @@ hover { width: 100%; margin: 0 0 15px 0; color: rgba(255, 255, 255, 0.5); + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.IBMSMSMBSSCL_CommentNoteRepliesTitleBtn { + font-size: 16px; } .IBMSMSMBSSCL_CAElementLoadWrapper {