1- import { trpc } from '@chirpy-dev/trpc/src/client' ;
2- import { CommonWidgetProps } from '@chirpy-dev/types' ;
1+ import { trpc , type RouterOutputs } from '@chirpy-dev/trpc/src/client' ;
2+ import { CommonWidgetProps , type RTEValue } from '@chirpy-dev/types' ;
3+ import { getTextFromRteValue } from '@chirpy-dev/utils' ;
34import * as React from 'react' ;
5+ import type {
6+ Comment ,
7+ InteractionCounter ,
8+ LikeAction ,
9+ Person ,
10+ } from 'schema-dts' ;
411
512import {
6- CommentTimeline ,
13+ CommentTimeline as CommentTimelineComponent ,
714 PoweredBy ,
815 UserMenu ,
916 WidgetLayout ,
1017} from '../../blocks' ;
1118import { Heading , IconArrowLeft , IconButton , Link } from '../../components' ;
19+ import { JSONLD } from '../../components/json-ld/json-ld' ;
1220import { CommentContextProvider } from '../../contexts' ;
1321import { useRefetchInterval } from './use-refetch-interval' ;
1422
@@ -21,6 +29,56 @@ export type CommentTimelineWidgetProps = CommonWidgetProps & {
2129 } ;
2230} ;
2331
32+ type TimelineComment = NonNullable < RouterOutputs [ 'comment' ] [ 'timeline' ] > ;
33+ /**
34+ * Creates a JSON-LD comment structure recursively for a comment and its replies
35+ */
36+ function createCommentJsonLd (
37+ comment : TimelineComment ,
38+ pageUrl : string ,
39+ ) : Comment | null {
40+ const author : Person = {
41+ '@type' : 'Person' ,
42+ name : comment . user . name || '' ,
43+ image : comment . user . image || undefined ,
44+ } ;
45+
46+ const commentJsonLd : Comment = {
47+ '@type' : 'Comment' ,
48+ text : getTextFromRteValue ( comment . content as RTEValue ) ,
49+ dateCreated : comment . createdAt . toISOString ( ) ,
50+ author : author ,
51+ url : `${ pageUrl } #comment-${ comment . id } ` ,
52+ mainEntityOfPage : pageUrl ,
53+ } ;
54+
55+ // Add likes information if available
56+ if ( comment . likes && comment . likes . length > 0 ) {
57+ commentJsonLd . interactionStatistic = {
58+ '@type' : 'InteractionCounter' ,
59+ interactionType : 'https://schema.org/LikeAction' as unknown as LikeAction ,
60+ userInteractionCount : comment . likes . length ,
61+ } as InteractionCounter ;
62+ }
63+
64+ // Add information about parent comment if it exists
65+ if ( comment . parentId ) {
66+ commentJsonLd . parentItem = {
67+ '@type' : 'Comment' ,
68+ url : `${ pageUrl } #comment-${ comment . parentId } ` ,
69+ } as Comment ;
70+ }
71+
72+ // Recursively process replies (if they exist in the timeline data)
73+ if ( comment . replies && comment . replies . length > 0 ) {
74+ commentJsonLd . comment = comment . replies
75+ . map ( ( reply : any ) => createCommentJsonLd ( reply , pageUrl ) )
76+ . filter ( Boolean ) as Comment [ ] ;
77+ }
78+
79+ return commentJsonLd ;
80+ }
81+
2482export function CommentTimelineWidget (
2583 props : CommentTimelineWidgetProps ,
2684) : JSX . Element {
@@ -34,8 +92,18 @@ export function CommentTimelineWidget(
3492 } ,
3593 ) ;
3694
95+ // Generate JSON-LD data for the comment timeline
96+ const jsonLdData = comment
97+ ? createCommentJsonLd ( comment , props . page . url )
98+ : null ;
99+
37100 return (
38101 < WidgetLayout widgetTheme = { props . theme } title = "Comment timeline" >
102+ { jsonLdData && (
103+ < JSONLD < Comment >
104+ data = { { '@context' : 'https://schema.org' , ...jsonLdData } }
105+ />
106+ ) }
39107 < CommentContextProvider projectId = { props . projectId } page = { props . page } >
40108 < div className = "mb-4 flex flex-row items-center justify-between" >
41109 { /* Can't use history.back() here in case user open this page individual */ }
@@ -54,7 +122,9 @@ export function CommentTimelineWidget(
54122 < UserMenu variant = "Widget" />
55123 </ div >
56124
57- { comment ?. id && < CommentTimeline key = { comment . id } comment = { comment } /> }
125+ { comment ?. id && (
126+ < CommentTimelineComponent key = { comment . id } comment = { comment } />
127+ ) }
58128 { props . plan === 'HOBBY' && < PoweredBy /> }
59129 </ CommentContextProvider >
60130 </ WidgetLayout >
0 commit comments