@@ -16,16 +16,12 @@ import {
1616import { Database } from '@hocuspocus/extension-database' ;
1717import { PrismaService } from '../prisma/prisma.service' ;
1818import * as Y from 'yjs' ;
19- import { generateHTML } from '@tiptap/html' ;
2019import Document from '@tiptap/extension-document' ;
2120import Paragraph from '@tiptap/extension-paragraph' ;
2221import Text from '@tiptap/extension-text' ;
2322import { SnapshotsService } from '../snapshots/snapshots.service' ;
2423
25- // A minimal set of extensions to allow parsing the YDoc to HTML on the server.
26- // For perfect HTML representation of customized nodes (like imageUpload),
27- // we would import all custom extensions here, but for search/text extraction
28- // and fallback rendering, the core blocks are sufficient.
24+ // A minimal set of extensions to allow parsing the YDoc on the server.
2925const serverExtensions = [ Document , Paragraph , Text ] ;
3026
3127@Injectable ( )
@@ -103,43 +99,44 @@ export class CollaborationService implements OnModuleInit, OnModuleDestroy {
10399 ) ;
104100 }
105101
106- // 2. Also parse the Yjs state into HTML to save to the main content field
102+ // 2. Extract plain text for search/preview purposes
107103 try {
108104 const ydoc = new Y . Doc ( ) ;
109105 Y . applyUpdate ( ydoc , state ) ;
110- // "content" is the field name Tiptap collaboration extension uses
111106 const xmlFragment = ydoc . getXmlFragment ( 'content' ) ;
112107
113- // xmlFragment.toJSON() returns an XML *string* like "<heading ...>",
114- // NOT a JSON string — so we must NOT call JSON.parse() on it.
115- // Instead, build the Tiptap ProseMirror JSON manually from the fragment's children.
116- // eslint-disable-next-line @typescript-eslint/no-explicit-any
117- const contentNodes : any [ ] = [ ] ;
108+ // Extract plain text by recursively traversing the fragment
109+ const extractText = ( node : any ) : string => {
110+ if ( node . type === 'text' ) {
111+ return node . content || '' ;
112+ }
113+ let text = '' ;
114+ if ( node . children ) {
115+ node . children . forEach ( ( child : any ) => {
116+ text += extractText ( child ) ;
117+ } ) ;
118+ }
119+ return text ;
120+ } ;
121+
122+ let plainText = '' ;
118123 xmlFragment . forEach ( ( child ) => {
119- // eslint-disable-next-line @typescript-eslint/no-explicit-any
120124 const c = child as any ;
121- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
122125 if ( typeof c . toJSON === 'function' ) {
123- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
124- contentNodes . push ( c . toJSON ( ) ) ;
126+ const json = c . toJSON ( ) ;
127+ plainText += extractText ( json ) + '\n' ;
125128 }
126129 } ) ;
127130
128- if ( contentNodes . length > 0 ) {
129- const html = generateHTML (
130- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
131- { type : 'doc' , content : contentNodes as any } ,
132- serverExtensions ,
133- ) ;
134-
131+ if ( plainText . trim ( ) ) {
135132 await prisma . document . update ( {
136133 where : { ydocKey : documentName } ,
137- data : { content : html } ,
134+ data : { content : plainText . trim ( ) } ,
138135 } ) ;
139136 }
140137 } catch ( parseErr ) {
141- logger . error (
142- `Failed to parse YDoc to HTML for ${ documentName } ` ,
138+ logger . warn (
139+ `Failed to extract text from YDoc for ${ documentName } ` ,
143140 parseErr ,
144141 ) ;
145142 }
0 commit comments