Skip to content
File

Blob: src/shared/editor/yjs-text.ts

typescript66 lines
1import * as Y from "yjs";
2import { DEFAULT_PAGE_TITLE, YJS_CANVAS_ELEMENTS, YJS_DOCUMENT_STORE, YJS_PAGE_TITLE } from "@/shared/constants";
3 
4interface ExcalidrawElementLike {
5 type?: string;
6 text?: string;
7 name?: string;
8 isDeleted?: boolean;
9}
10 
11export function extractPlaintext(ydoc: Y.Doc): { title: string; bodyText: string } {
12 const title = ydoc.getText(YJS_PAGE_TITLE).toString();
13 
14 const fragment = ydoc.getXmlFragment(YJS_DOCUMENT_STORE);
15 const parts: string[] = [];
16 
17 function walk(node: unknown) {
18 if (!node || typeof node !== "object") return;
19 
20 if (node instanceof Y.XmlText) {
21 const text = node.toString();
22 if (text.trim()) parts.push(text.trim());
23 return;
24 }
25 
26 if (node instanceof Y.XmlElement) {
27 const blockType = node.getAttribute("blockType");
28 if (blockType === "embed") return;
29 
30 for (let i = 0; i < node.length; i++) {
31 walk(node.get(i));
32 }
33 return;
34 }
35 
36 if (node instanceof Y.XmlFragment) {
37 const frag = node;
38 for (let i = 0; i < frag.length; i++) {
39 walk(frag.get(i));
40 }
41 }
42 }
43 
44 walk(fragment);
45 
46 return { title: title.trim() || DEFAULT_PAGE_TITLE, bodyText: parts.join(" ") };
47}
48 
49export function extractCanvasPlaintext(ydoc: Y.Doc): { title: string; bodyText: string } {
50 const title = ydoc.getText(YJS_PAGE_TITLE).toString();
51 const elements = ydoc.getMap<Y.Map<unknown>>(YJS_CANVAS_ELEMENTS);
52 const parts: string[] = [];
53 
54 elements.forEach((entry) => {
55 const el = entry.get("element") as ExcalidrawElementLike | undefined;
56 if (!el || el.isDeleted) return;
57 if (el.type === "text" && typeof el.text === "string" && el.text.trim()) {
58 parts.push(el.text.trim());
59 } else if (el.type === "frame" && typeof el.name === "string" && el.name.trim()) {
60 parts.push(el.name.trim());
61 }
62 });
63 
64 return { title: title.trim() || DEFAULT_PAGE_TITLE, bodyText: parts.join(" ") };
65}