@bayonai/rich-text-editor 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/BEHAVIOR.md +396 -0
  2. package/CHANGELOG.md +22 -0
  3. package/README.md +25 -6
  4. package/dist/core/blockTree.d.ts +14 -0
  5. package/dist/core/blockTree.js +126 -0
  6. package/dist/core/blockTypes.d.ts +6 -0
  7. package/dist/core/blockTypes.js +5 -0
  8. package/dist/core/exportImport.d.ts +59 -0
  9. package/dist/core/exportImport.js +51 -0
  10. package/dist/core/features.d.ts +59 -0
  11. package/dist/core/features.js +57 -0
  12. package/dist/core/imageBlockDiagnostics.d.ts +4 -0
  13. package/dist/core/imageBlockDiagnostics.js +19 -0
  14. package/dist/core/proFeatures.d.ts +60 -0
  15. package/dist/core/proFeatures.js +64 -0
  16. package/dist/{richText.d.ts → core/richText.d.ts} +2 -0
  17. package/dist/core/richText.js +566 -0
  18. package/dist/core/types.d.ts +78 -0
  19. package/dist/index.d.ts +14 -8
  20. package/dist/index.js +8 -5
  21. package/dist/react/editor/RichTextBody.d.ts +28 -0
  22. package/dist/react/editor/RichTextBody.js +131 -0
  23. package/dist/react/editor/RichTextEditor.d.ts +138 -0
  24. package/dist/react/editor/RichTextEditor.js +2925 -0
  25. package/dist/react/editor/RichTextRenderedBlock.d.ts +20 -0
  26. package/dist/react/editor/RichTextRenderedBlock.js +162 -0
  27. package/dist/react/editor/RichTextRenderer.d.ts +13 -0
  28. package/dist/react/editor/RichTextRenderer.js +16 -0
  29. package/dist/react/{RichTextTitleInput.d.ts → editor/RichTextTitleInput.d.ts} +11 -1
  30. package/dist/react/{RichTextTitleInput.js → editor/RichTextTitleInput.js} +17 -2
  31. package/dist/react/editor/blockActions.d.ts +48 -0
  32. package/dist/react/editor/blockActions.js +495 -0
  33. package/dist/react/editor/editorHistory.d.ts +55 -0
  34. package/dist/react/editor/editorHistory.js +111 -0
  35. package/dist/react/{editorNavigation.d.ts → editor/editorNavigation.d.ts} +2 -0
  36. package/dist/react/{editorNavigation.js → editor/editorNavigation.js} +16 -0
  37. package/dist/react/editor/editorOperations.d.ts +10 -0
  38. package/dist/react/editor/editorOperations.js +3 -0
  39. package/dist/react/editor/editorSelection.d.ts +3 -0
  40. package/dist/react/editor/editorSelection.js +215 -0
  41. package/dist/react/{editorShortcuts.d.ts → editor/editorShortcuts.d.ts} +10 -0
  42. package/dist/react/{editorShortcuts.js → editor/editorShortcuts.js} +17 -1
  43. package/dist/react/{RichTextIcons.d.ts → icons/RichTextIcons.d.ts} +3 -0
  44. package/dist/react/{RichTextIcons.js → icons/RichTextIcons.js} +9 -0
  45. package/dist/react/index.d.ts +12 -9
  46. package/dist/react/index.js +7 -6
  47. package/dist/react/{EditorSessionProvider.d.ts → session/EditorSessionProvider.d.ts} +2 -2
  48. package/dist/react/{EditorSessionProvider.js → session/EditorSessionProvider.js} +3 -3
  49. package/dist/react/{UnsavedChangesDialog.js → session/UnsavedChangesDialog.js} +1 -1
  50. package/dist/react/styles/RichTextStyles.js +1362 -0
  51. package/dist/react/{BlockActionTool.d.ts → tools/BlockActionTool.d.ts} +1 -1
  52. package/dist/react/{BlockActionTool.js → tools/BlockActionTool.js} +6 -2
  53. package/dist/react/tools/LinkCreationInput.d.ts +9 -0
  54. package/dist/react/tools/LinkCreationInput.js +38 -0
  55. package/dist/react/{SelectionFormatToolbar.d.ts → tools/SelectionFormatToolbar.d.ts} +3 -2
  56. package/dist/react/{SelectionFormatToolbar.js → tools/SelectionFormatToolbar.js} +3 -3
  57. package/dist/react/tools/SpecialBlockOption.d.ts +9 -0
  58. package/dist/react/tools/SpecialBlockOption.js +8 -0
  59. package/dist/react/tools/SpecialBlockTool.d.ts +91 -0
  60. package/dist/react/tools/SpecialBlockTool.js +125 -0
  61. package/dist/react/{TranscriptionControl.d.ts → tools/TranscriptionControl.d.ts} +9 -0
  62. package/dist/react/{TranscriptionControl.js → tools/TranscriptionControl.js} +70 -9
  63. package/dist/react/tools/blockActionToolState.d.ts +41 -0
  64. package/dist/react/tools/blockActionToolState.js +177 -0
  65. package/dist/react/tools/imageBlockDiagnostics.d.ts +2 -0
  66. package/dist/react/tools/imageBlockDiagnostics.js +12 -0
  67. package/dist/{session.d.ts → session/session.d.ts} +1 -1
  68. package/dist-cjs/core/blockTree.js +137 -0
  69. package/dist-cjs/core/blockTypes.js +9 -0
  70. package/dist-cjs/core/exportImport.js +56 -0
  71. package/dist-cjs/core/features.js +62 -0
  72. package/dist-cjs/core/proFeatures.js +70 -0
  73. package/dist-cjs/core/richText.js +578 -0
  74. package/dist-cjs/index.js +22 -6
  75. package/dist-cjs/react/editor/RichTextBody.js +134 -0
  76. package/dist-cjs/react/editor/RichTextEditor.js +2956 -0
  77. package/dist-cjs/react/editor/RichTextRenderedBlock.js +166 -0
  78. package/dist-cjs/react/editor/RichTextRenderer.js +20 -0
  79. package/dist-cjs/react/{RichTextTitleInput.js → editor/RichTextTitleInput.js} +18 -2
  80. package/dist-cjs/react/editor/blockActions.js +518 -0
  81. package/dist-cjs/react/editor/editorHistory.js +120 -0
  82. package/dist-cjs/react/{editorNavigation.js → editor/editorNavigation.js} +17 -0
  83. package/dist-cjs/react/editor/editorOperations.js +6 -0
  84. package/dist-cjs/react/editor/editorSelection.js +219 -0
  85. package/dist-cjs/react/{editorShortcuts.js → editor/editorShortcuts.js} +17 -1
  86. package/dist-cjs/react/{RichTextIcons.js → icons/RichTextIcons.js} +12 -0
  87. package/dist-cjs/react/index.js +9 -7
  88. package/dist-cjs/react/{EditorSessionProvider.js → session/EditorSessionProvider.js} +3 -3
  89. package/dist-cjs/react/{UnsavedChangesDialog.js → session/UnsavedChangesDialog.js} +1 -1
  90. package/dist-cjs/react/styles/RichTextStyles.js +1365 -0
  91. package/dist-cjs/react/{BlockActionTool.js → tools/BlockActionTool.js} +6 -2
  92. package/dist-cjs/react/tools/LinkCreationInput.js +41 -0
  93. package/dist-cjs/react/{SelectionFormatToolbar.js → tools/SelectionFormatToolbar.js} +3 -3
  94. package/dist-cjs/react/tools/SpecialBlockOption.js +11 -0
  95. package/dist-cjs/react/tools/SpecialBlockTool.js +129 -0
  96. package/dist-cjs/react/{TranscriptionControl.js → tools/TranscriptionControl.js} +71 -9
  97. package/dist-cjs/react/tools/blockActionToolState.js +186 -0
  98. package/package.json +3 -2
  99. package/dist/react/RichTextBody.d.ts +0 -18
  100. package/dist/react/RichTextBody.js +0 -66
  101. package/dist/react/RichTextEditor.d.ts +0 -45
  102. package/dist/react/RichTextEditor.js +0 -1096
  103. package/dist/react/RichTextRenderedBlock.d.ts +0 -4
  104. package/dist/react/RichTextRenderedBlock.js +0 -36
  105. package/dist/react/RichTextRenderer.d.ts +0 -4
  106. package/dist/react/RichTextRenderer.js +0 -8
  107. package/dist/react/RichTextStyles.js +0 -719
  108. package/dist/react/SpecialBlockOption.d.ts +0 -7
  109. package/dist/react/SpecialBlockOption.js +0 -7
  110. package/dist/react/SpecialBlockTool.d.ts +0 -42
  111. package/dist/react/SpecialBlockTool.js +0 -50
  112. package/dist/react/blockActionToolState.d.ts +0 -18
  113. package/dist/react/blockActionToolState.js +0 -53
  114. package/dist/react/blockActions.d.ts +0 -8
  115. package/dist/react/blockActions.js +0 -111
  116. package/dist/richText.js +0 -297
  117. package/dist/types.d.ts +0 -34
  118. package/dist-cjs/react/RichTextBody.js +0 -69
  119. package/dist-cjs/react/RichTextEditor.js +0 -1108
  120. package/dist-cjs/react/RichTextRenderedBlock.js +0 -39
  121. package/dist-cjs/react/RichTextRenderer.js +0 -11
  122. package/dist-cjs/react/RichTextStyles.js +0 -722
  123. package/dist-cjs/react/SpecialBlockOption.js +0 -10
  124. package/dist-cjs/react/SpecialBlockTool.js +0 -54
  125. package/dist-cjs/react/blockActionToolState.js +0 -58
  126. package/dist-cjs/react/blockActions.js +0 -119
  127. package/dist-cjs/richText.js +0 -307
  128. /package/dist/{types.js → core/types.js} +0 -0
  129. /package/dist/{writingStats.d.ts → core/writingStats.d.ts} +0 -0
  130. /package/dist/{writingStats.js → core/writingStats.js} +0 -0
  131. /package/dist/react/{RichTextDocumentSurface.d.ts → editor/RichTextDocumentSurface.d.ts} +0 -0
  132. /package/dist/react/{RichTextDocumentSurface.js → editor/RichTextDocumentSurface.js} +0 -0
  133. /package/dist/react/{UnsavedChangesDialog.d.ts → session/UnsavedChangesDialog.d.ts} +0 -0
  134. /package/dist/react/{RichTextStyles.d.ts → styles/RichTextStyles.d.ts} +0 -0
  135. /package/dist/react/{richTextBlockStyles.d.ts → styles/richTextBlockStyles.d.ts} +0 -0
  136. /package/dist/react/{richTextBlockStyles.js → styles/richTextBlockStyles.js} +0 -0
  137. /package/dist/react/{specialBlockStyles.d.ts → styles/specialBlockStyles.d.ts} +0 -0
  138. /package/dist/react/{specialBlockStyles.js → styles/specialBlockStyles.js} +0 -0
  139. /package/dist/{saveControl.d.ts → session/saveControl.d.ts} +0 -0
  140. /package/dist/{saveControl.js → session/saveControl.js} +0 -0
  141. /package/dist/{session.js → session/session.js} +0 -0
  142. /package/dist/{sessionRegistry.d.ts → session/sessionRegistry.d.ts} +0 -0
  143. /package/dist/{sessionRegistry.js → session/sessionRegistry.js} +0 -0
  144. /package/dist-cjs/{types.js → core/types.js} +0 -0
  145. /package/dist-cjs/{writingStats.js → core/writingStats.js} +0 -0
  146. /package/dist-cjs/react/{RichTextDocumentSurface.js → editor/RichTextDocumentSurface.js} +0 -0
  147. /package/dist-cjs/react/{richTextBlockStyles.js → styles/richTextBlockStyles.js} +0 -0
  148. /package/dist-cjs/react/{specialBlockStyles.js → styles/specialBlockStyles.js} +0 -0
  149. /package/dist-cjs/{saveControl.js → session/saveControl.js} +0 -0
  150. /package/dist-cjs/{session.js → session/session.js} +0 -0
  151. /package/dist-cjs/{sessionRegistry.js → session/sessionRegistry.js} +0 -0
@@ -0,0 +1,578 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEmptyRichTextBlocks = createEmptyRichTextBlocks;
4
+ exports.createRichTextBlockId = createRichTextBlockId;
5
+ exports.sanitizeRichTextBlocks = sanitizeRichTextBlocks;
6
+ exports.isRichTextBlocksEmpty = isRichTextBlocksEmpty;
7
+ exports.richTextBlocksToPlainText = richTextBlocksToPlainText;
8
+ exports.encodeRichTextBlocksForClipboard = encodeRichTextBlocksForClipboard;
9
+ exports.decodeRichTextBlocksFromClipboardText = decodeRichTextBlocksFromClipboardText;
10
+ exports.editorHtmlToMarkdown = editorHtmlToMarkdown;
11
+ exports.markdownToEditorHtml = markdownToEditorHtml;
12
+ exports.sanitizeRichTextHtml = sanitizeRichTextHtml;
13
+ const allowedInlineTags = new Set(["a", "br", "code", "em", "strong"]);
14
+ const textBlockTypes = new Set([
15
+ "paragraph",
16
+ "message",
17
+ "heading",
18
+ "quote",
19
+ "bullet",
20
+ "ordered",
21
+ "checkbox",
22
+ "toggle",
23
+ ]);
24
+ const richTextClipboardPrefix = "bounded-rich-text-blocks-v1:";
25
+ const toolBlockStatuses = new Set([
26
+ "pending",
27
+ "success",
28
+ "error",
29
+ ]);
30
+ const invalidToolData = Symbol("invalidToolData");
31
+ const maxToolDataDepth = 8;
32
+ function createEmptyRichTextBlocks() {
33
+ return [{ id: createRichTextBlockId(), type: "paragraph", markdown: "" }];
34
+ }
35
+ function createRichTextBlockId() {
36
+ if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
37
+ return crypto.randomUUID();
38
+ }
39
+ return `block-${Date.now()}-${Math.random().toString(16).slice(2)}`;
40
+ }
41
+ /**
42
+ * Sanitizes an array of RichTextBlock objects by:
43
+ * - Filtering out invalid blocks
44
+ * - Ensuring each block has a unique ID
45
+ * - Returning an empty array if no valid blocks remain
46
+ */
47
+ function sanitizeRichTextBlocks(value) {
48
+ return sanitizeRichTextBlocksWithSeen(value, new Set());
49
+ }
50
+ function sanitizeRichTextBlocksWithSeen(value, seenBlockIds) {
51
+ const blocks = Array.isArray(value) ? value : [];
52
+ const sanitized = blocks
53
+ .flatMap((block) => sanitizeRichTextBlock(block, seenBlockIds))
54
+ .map((block) => {
55
+ if (!seenBlockIds.has(block.id)) {
56
+ seenBlockIds.add(block.id);
57
+ return block;
58
+ }
59
+ const blockWithUniqueId = {
60
+ ...block,
61
+ id: createUniqueRichTextBlockId(seenBlockIds),
62
+ };
63
+ seenBlockIds.add(blockWithUniqueId.id);
64
+ return blockWithUniqueId;
65
+ });
66
+ return sanitized.length > 0 ? sanitized : createEmptyRichTextBlocks();
67
+ }
68
+ function isRichTextBlocksEmpty(blocks) {
69
+ return sanitizeRichTextBlocks(blocks).every((block) => {
70
+ if (block.type === "divider") {
71
+ return false;
72
+ }
73
+ if (block.type === "checkbox") {
74
+ return markdownToPlainText(block.markdown).trim() === "";
75
+ }
76
+ if (block.type === "image") {
77
+ return !block.url && typeof block.uploadProgress !== "number";
78
+ }
79
+ if (block.type === "tool") {
80
+ return false;
81
+ }
82
+ const text = block.type === "code" ? block.text : block.markdown;
83
+ return markdownToPlainText(text).trim() === "";
84
+ });
85
+ }
86
+ function richTextBlocksToPlainText(blocks) {
87
+ return sanitizeRichTextBlocks(blocks)
88
+ .map((block) => {
89
+ if (block.type === "divider") {
90
+ return "";
91
+ }
92
+ if (block.type === "checkbox") {
93
+ return [
94
+ markdownToPlainText(block.markdown),
95
+ ...richTextBlocksToPlainText(block.children ?? [])
96
+ .split(" ")
97
+ .filter(Boolean),
98
+ ]
99
+ .filter(Boolean)
100
+ .join(" ");
101
+ }
102
+ if (block.type === "image") {
103
+ return block.alt ?? "";
104
+ }
105
+ if (block.type === "tool") {
106
+ return [
107
+ `[Tool: ${block.title || block.toolType} ${block.status}]`,
108
+ block.markdown ? markdownToPlainText(block.markdown) : "",
109
+ ]
110
+ .filter(Boolean)
111
+ .join(" ");
112
+ }
113
+ if (block.type === "bullet" ||
114
+ block.type === "ordered" ||
115
+ block.type === "toggle") {
116
+ return [
117
+ markdownToPlainText(block.markdown),
118
+ richTextBlocksToPlainText(block.children ?? []),
119
+ ]
120
+ .filter(Boolean)
121
+ .join(" ");
122
+ }
123
+ return block.type === "code"
124
+ ? block.text
125
+ : markdownToPlainText(block.markdown);
126
+ })
127
+ .join(" ")
128
+ .replace(/\s+/g, " ")
129
+ .trim();
130
+ }
131
+ function encodeRichTextBlocksForClipboard(blocks) {
132
+ const sanitizedBlocks = sanitizeRichTextBlocks(blocks);
133
+ const encoded = encodeURIComponent(JSON.stringify(sanitizedBlocks));
134
+ const plainText = richTextBlocksToPlainText(sanitizedBlocks);
135
+ return `${richTextClipboardPrefix}${encoded}${plainText ? `\n${plainText}` : ""}`;
136
+ }
137
+ function decodeRichTextBlocksFromClipboardText(value) {
138
+ const [firstLine] = value.split(/\r?\n/, 1);
139
+ if (!firstLine?.startsWith(richTextClipboardPrefix)) {
140
+ return null;
141
+ }
142
+ try {
143
+ const decoded = decodeURIComponent(firstLine.slice(richTextClipboardPrefix.length));
144
+ const parsed = JSON.parse(decoded);
145
+ return Array.isArray(parsed) ? sanitizeRichTextBlocks(parsed) : null;
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ function editorHtmlToMarkdown(html) {
152
+ const sanitized = normalizeTypography(decodeHtmlText(sanitizeRichTextHtml(html)
153
+ .replace(/<br>/g, " \n")
154
+ .replace(/<strong>([\s\S]*?)<\/strong>/g, (_, value) => {
155
+ return `**${decodeHtmlText(stripTags(value))}**`;
156
+ })
157
+ .replace(/<em>([\s\S]*?)<\/em>/g, (_, value) => {
158
+ return `_${decodeHtmlText(stripTags(value))}_`;
159
+ })
160
+ .replace(/<code>([\s\S]*?)<\/code>/g, (_, value) => {
161
+ return `\`${decodeHtmlText(stripTags(value)).replace(/`/g, "\\`")}\``;
162
+ })
163
+ .replace(/<a href="([^"]*)">([\s\S]*?)<\/a>/g, (_, href, value) => {
164
+ return `[${decodeHtmlText(stripTags(value))}](${decodeHtmlText(href)})`;
165
+ })
166
+ .replace(/<a>([\s\S]*?)<\/a>/g, (_, value) => {
167
+ return decodeHtmlText(stripTags(value));
168
+ })
169
+ .replace(/<[^>]+>/g, ""))).replace(/\u00a0/g, " ");
170
+ return stripWhitespaceOnlyMarkdown(sanitized);
171
+ }
172
+ function markdownToEditorHtml(markdown) {
173
+ const tokens = [];
174
+ let html = escapeHtmlText(normalizeTypography(markdown));
175
+ html = html.replace(/`([^`\n]+)`/g, (_, value) => {
176
+ const token = pushToken(tokens, `<code>${value}</code>`);
177
+ return token;
178
+ });
179
+ html = html.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, (_, label, href) => {
180
+ const safeHref = getSafeUrl(decodeHtmlText(href));
181
+ return safeHref ? `<a href="${safeHref}">${label}</a>` : label;
182
+ });
183
+ html = html
184
+ .replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>")
185
+ .replace(/_([^_\n]+)_/g, "<em>$1</em>")
186
+ .replace(/ {2}\n/g, "<br>")
187
+ .replace(/\n/g, "<br>");
188
+ tokens.forEach((value, index) => {
189
+ html = html.replace(tokenPlaceholder(index), value);
190
+ });
191
+ return html;
192
+ }
193
+ function sanitizeRichTextHtml(html) {
194
+ return html
195
+ .replace(/<!--[\s\S]*?-->/g, "")
196
+ .replace(/<script[\s\S]*?<\/script>/gi, "")
197
+ .replace(/<style[\s\S]*?<\/style>/gi, "")
198
+ .replace(/<b(\s[^>]*)?>/gi, "<strong>")
199
+ .replace(/<\/b>/gi, "</strong>")
200
+ .replace(/<i(\s[^>]*)?>/gi, "<em>")
201
+ .replace(/<\/i>/gi, "</em>")
202
+ .replace(/<\/?([a-z0-9]+)([^>]*)>/gi, (match, rawTag, rawAttrs) => {
203
+ const tag = String(rawTag).toLowerCase();
204
+ const closing = match.startsWith("</");
205
+ if (!allowedInlineTags.has(tag)) {
206
+ return "";
207
+ }
208
+ if (closing) {
209
+ return tag === "br" ? "" : `</${tag}>`;
210
+ }
211
+ if (tag === "a") {
212
+ const href = getSafeAttribute(rawAttrs, "href");
213
+ return href ? `<a href="${href}">` : "<a>";
214
+ }
215
+ return tag === "br" ? "<br>" : `<${tag}>`;
216
+ });
217
+ }
218
+ function sanitizeRichTextBlock(block, seenBlockIds) {
219
+ if (!isRecord(block)) {
220
+ return [];
221
+ }
222
+ const id = readBlockId(block);
223
+ const type = block.type;
224
+ if (type === "code") {
225
+ return [{ id, type, text: String(block.text ?? "") }];
226
+ }
227
+ if (type === "divider") {
228
+ return [{ id, type }];
229
+ }
230
+ if (type === "image") {
231
+ const safeUrl = typeof block.url === "string" && getSafeUrl(block.url)
232
+ ? getSafeUrl(block.url)
233
+ : "";
234
+ const uploadProgress = typeof block.uploadProgress === "number" &&
235
+ Number.isFinite(block.uploadProgress)
236
+ ? clamp(Math.round(block.uploadProgress), 0, 100)
237
+ : null;
238
+ const displaySize = readImageDisplaySize(block.displaySize);
239
+ const customWidth = readImageCustomWidth(block.customWidth);
240
+ if (!safeUrl && uploadProgress === null) {
241
+ return [];
242
+ }
243
+ return [
244
+ {
245
+ id,
246
+ type,
247
+ alignment: readImageAlignment(block.alignment),
248
+ ...(displaySize ? { displaySize } : {}),
249
+ ...(displaySize === "custom" && customWidth
250
+ ? { customWidth }
251
+ : {}),
252
+ ...(safeUrl ? { url: safeUrl } : {}),
253
+ ...(typeof block.alt === "string" && block.alt.trim()
254
+ ? { alt: markdownToPlainText(editorHtmlToMarkdown(block.alt)) }
255
+ : {}),
256
+ ...(uploadProgress !== null ? { uploadProgress } : {}),
257
+ },
258
+ ];
259
+ }
260
+ if (type === "tool") {
261
+ return [sanitizeToolBlock(block, id)];
262
+ }
263
+ if (typeof type === "string" && textBlockTypes.has(type)) {
264
+ const markdown = typeof block.markdown === "string"
265
+ ? block.markdown
266
+ : typeof block.text === "string"
267
+ ? block.text
268
+ : typeof block.html === "string"
269
+ ? editorHtmlToMarkdown(block.html)
270
+ : "";
271
+ if (type === "bullet" || type === "ordered") {
272
+ return [
273
+ withSanitizedChildren({
274
+ id,
275
+ type: type,
276
+ markdown: sanitizeMarkdown(markdown),
277
+ }, block, seenBlockIds),
278
+ ];
279
+ }
280
+ if (type === "checkbox") {
281
+ return [
282
+ withSanitizedChildren({
283
+ id,
284
+ type,
285
+ checked: Boolean(block.checked),
286
+ markdown: sanitizeMarkdown(markdown),
287
+ }, block, seenBlockIds),
288
+ ];
289
+ }
290
+ if (type === "toggle") {
291
+ const children = sanitizeRichTextChildBlocksWithSeen(block.children, seenBlockIds);
292
+ return [
293
+ {
294
+ id,
295
+ type,
296
+ collapsed: Boolean(block.collapsed),
297
+ markdown: sanitizeMarkdown(markdown),
298
+ children,
299
+ },
300
+ ];
301
+ }
302
+ return [
303
+ {
304
+ id,
305
+ type: type,
306
+ markdown: sanitizeMarkdown(markdown),
307
+ },
308
+ ];
309
+ }
310
+ return [];
311
+ }
312
+ function sanitizeToolBlock(block, id) {
313
+ const toolType = readToolType(block.toolType, id);
314
+ const status = readToolStatus(block.status, id);
315
+ const title = typeof block.title === "string" && block.title.trim()
316
+ ? markdownToPlainText(editorHtmlToMarkdown(block.title))
317
+ : "";
318
+ const markdown = typeof block.markdown === "string"
319
+ ? sanitizeMarkdown(block.markdown)
320
+ : typeof block.text === "string"
321
+ ? sanitizeMarkdown(block.text)
322
+ : typeof block.html === "string"
323
+ ? sanitizeMarkdown(editorHtmlToMarkdown(block.html))
324
+ : "";
325
+ const dataResult = "data" in block ? sanitizeToolData(block.data, id) : null;
326
+ return {
327
+ id,
328
+ type: "tool",
329
+ toolType,
330
+ status,
331
+ ...(title ? { title } : {}),
332
+ ...(markdown ? { markdown } : {}),
333
+ ...(dataResult?.value !== undefined ? { data: dataResult.value } : {}),
334
+ };
335
+ }
336
+ function readToolType(value, blockId) {
337
+ if (typeof value === "string" && value.trim()) {
338
+ return value.trim();
339
+ }
340
+ console.warn("[RichText] Tool block has an invalid toolType.", { blockId });
341
+ return "unknown";
342
+ }
343
+ function readToolStatus(value, blockId) {
344
+ if (typeof value === "string" &&
345
+ toolBlockStatuses.has(value)) {
346
+ return value;
347
+ }
348
+ console.warn("[RichText] Tool block has an invalid status.", {
349
+ blockId,
350
+ status: value,
351
+ });
352
+ return "success";
353
+ }
354
+ function sanitizeToolData(value, blockId) {
355
+ const result = sanitizeToolDataValue(value, {
356
+ depth: 0,
357
+ seen: new WeakSet(),
358
+ });
359
+ if (result.changed) {
360
+ console.warn("[RichText] Tool block data contained unsupported values.", {
361
+ blockId,
362
+ });
363
+ }
364
+ return result.value === invalidToolData
365
+ ? null
366
+ : { changed: result.changed, value: result.value };
367
+ }
368
+ function sanitizeToolDataValue(value, context) {
369
+ if (value === null ||
370
+ typeof value === "boolean" ||
371
+ typeof value === "string") {
372
+ return { changed: false, value };
373
+ }
374
+ if (typeof value === "number") {
375
+ return Number.isFinite(value)
376
+ ? { changed: false, value }
377
+ : { changed: true, value: invalidToolData };
378
+ }
379
+ if (typeof value !== "object" || value === undefined) {
380
+ return { changed: true, value: invalidToolData };
381
+ }
382
+ if (context.depth >= maxToolDataDepth) {
383
+ return { changed: true, value: invalidToolData };
384
+ }
385
+ if (context.seen.has(value)) {
386
+ return { changed: true, value: invalidToolData };
387
+ }
388
+ context.seen.add(value);
389
+ if (Array.isArray(value)) {
390
+ let changed = false;
391
+ const sanitizedArray = [];
392
+ value.forEach((item) => {
393
+ const result = sanitizeToolDataValue(item, {
394
+ depth: context.depth + 1,
395
+ seen: context.seen,
396
+ });
397
+ if (result.value === invalidToolData) {
398
+ changed = true;
399
+ return;
400
+ }
401
+ changed = changed || result.changed;
402
+ sanitizedArray.push(result.value);
403
+ });
404
+ context.seen.delete(value);
405
+ return { changed, value: sanitizedArray };
406
+ }
407
+ const prototype = Object.getPrototypeOf(value);
408
+ if (prototype !== Object.prototype && prototype !== null) {
409
+ context.seen.delete(value);
410
+ return { changed: true, value: invalidToolData };
411
+ }
412
+ let changed = false;
413
+ const sanitizedRecord = {};
414
+ Object.entries(value).forEach(([key, item]) => {
415
+ const result = sanitizeToolDataValue(item, {
416
+ depth: context.depth + 1,
417
+ seen: context.seen,
418
+ });
419
+ if (result.value === invalidToolData) {
420
+ changed = true;
421
+ return;
422
+ }
423
+ changed = changed || result.changed;
424
+ sanitizedRecord[key] = result.value;
425
+ });
426
+ context.seen.delete(value);
427
+ return { changed, value: sanitizedRecord };
428
+ }
429
+ function withSanitizedChildren(sanitizedBlock, rawBlock, seenBlockIds) {
430
+ const children = sanitizeRichTextChildBlocksWithSeen(rawBlock.children, seenBlockIds);
431
+ if (children.length === 0) {
432
+ return sanitizedBlock;
433
+ }
434
+ return { ...sanitizedBlock, children };
435
+ }
436
+ function sanitizeRichTextChildBlocksWithSeen(value, seenBlockIds) {
437
+ if (!Array.isArray(value)) {
438
+ return [];
439
+ }
440
+ return value
441
+ .flatMap((block) => sanitizeRichTextBlock(block, seenBlockIds))
442
+ .map((block) => {
443
+ if (!seenBlockIds.has(block.id)) {
444
+ seenBlockIds.add(block.id);
445
+ return block;
446
+ }
447
+ const blockWithUniqueId = {
448
+ ...block,
449
+ id: createUniqueRichTextBlockId(seenBlockIds),
450
+ };
451
+ seenBlockIds.add(blockWithUniqueId.id);
452
+ return blockWithUniqueId;
453
+ });
454
+ }
455
+ function readBlockId(block) {
456
+ return typeof block.id === "string" && block.id.trim()
457
+ ? block.id.trim()
458
+ : createRichTextBlockId();
459
+ }
460
+ function createUniqueRichTextBlockId(existingIds) {
461
+ let id = createRichTextBlockId();
462
+ while (existingIds.has(id)) {
463
+ id = createRichTextBlockId();
464
+ }
465
+ return id;
466
+ }
467
+ function markdownToPlainText(value) {
468
+ return value
469
+ .replace(/`([^`]*)`/g, "$1")
470
+ .replace(/\*\*([^*]*)\*\*/g, "$1")
471
+ .replace(/_([^_]*)_/g, "$1")
472
+ .replace(/\[([^\]]*)\]\([^)]+\)/g, "$1")
473
+ .replace(/[#>*`_[\]()]/g, " ")
474
+ .replace(/\s+/g, " ")
475
+ .trim();
476
+ }
477
+ function sanitizeMarkdown(value) {
478
+ return stripWhitespaceOnlyMarkdown(normalizeTypography(value).replace(/<!--[\s\S]*?-->/g, ""));
479
+ }
480
+ function stripWhitespaceOnlyMarkdown(value) {
481
+ return value.trim().length > 0 ? value : "";
482
+ }
483
+ function normalizeTypography(value) {
484
+ const tokens = [];
485
+ let normalized = value.replace(/`([^`\n]+)`/g, (match) => {
486
+ return pushToken(tokens, match);
487
+ });
488
+ normalized = normalized.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, (_match, label, href) => {
489
+ return `[${label}](${pushToken(tokens, href)})`;
490
+ });
491
+ normalized = normalized
492
+ .replace(/––/g, "—")
493
+ .replace(/–-/g, "—")
494
+ .replace(/---/g, "—")
495
+ .replace(/--/g, "–");
496
+ tokens.forEach((token, index) => {
497
+ normalized = normalized.replace(tokenPlaceholder(index), token);
498
+ });
499
+ return normalized;
500
+ }
501
+ function stripTags(value) {
502
+ return value.replace(/<[^>]+>/g, "");
503
+ }
504
+ function getSafeAttribute(attrs, name) {
505
+ const value = getPlainAttribute(attrs, name);
506
+ const safeValue = getSafeUrl(value);
507
+ if (!safeValue) {
508
+ return "";
509
+ }
510
+ return escapeAttribute(safeValue);
511
+ }
512
+ function getSafeUrl(value) {
513
+ const trimmed = value.trim();
514
+ if (!trimmed || /^javascript:/i.test(trimmed)) {
515
+ return "";
516
+ }
517
+ return trimmed;
518
+ }
519
+ function getPlainAttribute(attrs, name) {
520
+ const pattern = new RegExp(`${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i");
521
+ const match = attrs.match(pattern);
522
+ return match?.[2] ?? match?.[3] ?? match?.[4] ?? "";
523
+ }
524
+ function escapeAttribute(value) {
525
+ return value
526
+ .replace(/&/g, "&amp;")
527
+ .replace(/</g, "&lt;")
528
+ .replace(/>/g, "&gt;")
529
+ .replace(/"/g, "&quot;");
530
+ }
531
+ function escapeHtmlText(value) {
532
+ return value
533
+ .replace(/&/g, "&amp;")
534
+ .replace(/</g, "&lt;")
535
+ .replace(/>/g, "&gt;");
536
+ }
537
+ function decodeHtmlText(value) {
538
+ return value
539
+ .replace(/&nbsp;/g, " ")
540
+ .replace(/&#160;/g, " ")
541
+ .replace(/&#xA0;/gi, " ")
542
+ .replace(/&amp;/g, "&")
543
+ .replace(/&lt;/g, "<")
544
+ .replace(/&gt;/g, ">")
545
+ .replace(/&quot;/g, '"')
546
+ .replace(/&apos;/g, "'")
547
+ .replace(/&#39;/g, "'");
548
+ }
549
+ function pushToken(tokens, value) {
550
+ const index = tokens.push(value) - 1;
551
+ return tokenPlaceholder(index);
552
+ }
553
+ function tokenPlaceholder(index) {
554
+ return `@@RICHTEXTTOKEN${index}@@`;
555
+ }
556
+ function clamp(value, min, max) {
557
+ return Math.min(Math.max(value, min), max);
558
+ }
559
+ function readImageAlignment(value) {
560
+ return value === "left" || value === "right" ? value : "center";
561
+ }
562
+ function readImageDisplaySize(value) {
563
+ return value === "small" ||
564
+ value === "medium" ||
565
+ value === "full" ||
566
+ value === "custom"
567
+ ? value
568
+ : null;
569
+ }
570
+ function readImageCustomWidth(value) {
571
+ if (typeof value !== "number" || !Number.isFinite(value)) {
572
+ return null;
573
+ }
574
+ return clamp(Math.round(value), 120, 1600);
575
+ }
576
+ function isRecord(value) {
577
+ return typeof value === "object" && value !== null;
578
+ }
package/dist-cjs/index.js CHANGED
@@ -1,22 +1,38 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getWritingStats = exports.sanitizeRichTextHtml = exports.sanitizeRichTextBlocks = exports.richTextBlocksToPlainText = exports.markdownToEditorHtml = exports.isRichTextBlocksEmpty = exports.editorHtmlToMarkdown = exports.createRichTextBlockId = exports.createEmptyRichTextBlocks = exports.createEditorSessionRegistry = exports.getEditorSaveControlState = exports.defaultEditorSessionEquals = exports.createEditorSession = exports.canonicalSerialize = void 0;
4
- var session_1 = require("./session");
3
+ exports.getWritingStats = exports.runRichTextSchemaMigrations = exports.instantiateRichTextTemplate = exports.createRichTextAnnotations = exports.connectRichTextCollaboration = exports.richTextJsonImportAdapter = exports.richTextJsonExportAdapter = exports.importRichTextBlocks = exports.exportRichTextBlocks = exports.richTextEditorFeatureCatalog = exports.isRichTextFeatureEnabled = exports.getRichTextFeatureAccess = exports.sanitizeRichTextHtml = exports.sanitizeRichTextBlocks = exports.richTextBlocksToPlainText = exports.markdownToEditorHtml = exports.isRichTextBlocksEmpty = exports.editorHtmlToMarkdown = exports.encodeRichTextBlocksForClipboard = exports.decodeRichTextBlocksFromClipboardText = exports.createRichTextBlockId = exports.createEmptyRichTextBlocks = exports.createEditorSessionRegistry = exports.getEditorSaveControlState = exports.defaultEditorSessionEquals = exports.createEditorSession = exports.canonicalSerialize = void 0;
4
+ var session_1 = require("./session/session");
5
5
  Object.defineProperty(exports, "canonicalSerialize", { enumerable: true, get: function () { return session_1.canonicalSerialize; } });
6
6
  Object.defineProperty(exports, "createEditorSession", { enumerable: true, get: function () { return session_1.createEditorSession; } });
7
7
  Object.defineProperty(exports, "defaultEditorSessionEquals", { enumerable: true, get: function () { return session_1.defaultEditorSessionEquals; } });
8
- var saveControl_1 = require("./saveControl");
8
+ var saveControl_1 = require("./session/saveControl");
9
9
  Object.defineProperty(exports, "getEditorSaveControlState", { enumerable: true, get: function () { return saveControl_1.getEditorSaveControlState; } });
10
- var sessionRegistry_1 = require("./sessionRegistry");
10
+ var sessionRegistry_1 = require("./session/sessionRegistry");
11
11
  Object.defineProperty(exports, "createEditorSessionRegistry", { enumerable: true, get: function () { return sessionRegistry_1.createEditorSessionRegistry; } });
12
- var richText_1 = require("./richText");
12
+ var richText_1 = require("./core/richText");
13
13
  Object.defineProperty(exports, "createEmptyRichTextBlocks", { enumerable: true, get: function () { return richText_1.createEmptyRichTextBlocks; } });
14
14
  Object.defineProperty(exports, "createRichTextBlockId", { enumerable: true, get: function () { return richText_1.createRichTextBlockId; } });
15
+ Object.defineProperty(exports, "decodeRichTextBlocksFromClipboardText", { enumerable: true, get: function () { return richText_1.decodeRichTextBlocksFromClipboardText; } });
16
+ Object.defineProperty(exports, "encodeRichTextBlocksForClipboard", { enumerable: true, get: function () { return richText_1.encodeRichTextBlocksForClipboard; } });
15
17
  Object.defineProperty(exports, "editorHtmlToMarkdown", { enumerable: true, get: function () { return richText_1.editorHtmlToMarkdown; } });
16
18
  Object.defineProperty(exports, "isRichTextBlocksEmpty", { enumerable: true, get: function () { return richText_1.isRichTextBlocksEmpty; } });
17
19
  Object.defineProperty(exports, "markdownToEditorHtml", { enumerable: true, get: function () { return richText_1.markdownToEditorHtml; } });
18
20
  Object.defineProperty(exports, "richTextBlocksToPlainText", { enumerable: true, get: function () { return richText_1.richTextBlocksToPlainText; } });
19
21
  Object.defineProperty(exports, "sanitizeRichTextBlocks", { enumerable: true, get: function () { return richText_1.sanitizeRichTextBlocks; } });
20
22
  Object.defineProperty(exports, "sanitizeRichTextHtml", { enumerable: true, get: function () { return richText_1.sanitizeRichTextHtml; } });
21
- var writingStats_1 = require("./writingStats");
23
+ var features_1 = require("./core/features");
24
+ Object.defineProperty(exports, "getRichTextFeatureAccess", { enumerable: true, get: function () { return features_1.getRichTextFeatureAccess; } });
25
+ Object.defineProperty(exports, "isRichTextFeatureEnabled", { enumerable: true, get: function () { return features_1.isRichTextFeatureEnabled; } });
26
+ Object.defineProperty(exports, "richTextEditorFeatureCatalog", { enumerable: true, get: function () { return features_1.richTextEditorFeatureCatalog; } });
27
+ var exportImport_1 = require("./core/exportImport");
28
+ Object.defineProperty(exports, "exportRichTextBlocks", { enumerable: true, get: function () { return exportImport_1.exportRichTextBlocks; } });
29
+ Object.defineProperty(exports, "importRichTextBlocks", { enumerable: true, get: function () { return exportImport_1.importRichTextBlocks; } });
30
+ Object.defineProperty(exports, "richTextJsonExportAdapter", { enumerable: true, get: function () { return exportImport_1.richTextJsonExportAdapter; } });
31
+ Object.defineProperty(exports, "richTextJsonImportAdapter", { enumerable: true, get: function () { return exportImport_1.richTextJsonImportAdapter; } });
32
+ var proFeatures_1 = require("./core/proFeatures");
33
+ Object.defineProperty(exports, "connectRichTextCollaboration", { enumerable: true, get: function () { return proFeatures_1.connectRichTextCollaboration; } });
34
+ Object.defineProperty(exports, "createRichTextAnnotations", { enumerable: true, get: function () { return proFeatures_1.createRichTextAnnotations; } });
35
+ Object.defineProperty(exports, "instantiateRichTextTemplate", { enumerable: true, get: function () { return proFeatures_1.instantiateRichTextTemplate; } });
36
+ Object.defineProperty(exports, "runRichTextSchemaMigrations", { enumerable: true, get: function () { return proFeatures_1.runRichTextSchemaMigrations; } });
37
+ var writingStats_1 = require("./core/writingStats");
22
38
  Object.defineProperty(exports, "getWritingStats", { enumerable: true, get: function () { return writingStats_1.getWritingStats; } });