Skip to content

Commit c7eebf0

Browse files
feat: Update team collaboration features with role enhancements, optimized API endpoints, and improved sharing functionalities.
1 parent cbb8def commit c7eebf0

2 files changed

Lines changed: 210 additions & 279 deletions

File tree

apps/api/src/collaboration/collaboration.service.ts

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@ import {
1616
import { Database } from '@hocuspocus/extension-database';
1717
import { PrismaService } from '../prisma/prisma.service';
1818
import * as Y from 'yjs';
19-
import { generateHTML } from '@tiptap/html';
2019
import Document from '@tiptap/extension-document';
2120
import Paragraph from '@tiptap/extension-paragraph';
2221
import Text from '@tiptap/extension-text';
2322
import { 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.
2925
const 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

Comments
 (0)