@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
@@ -1,54 +0,0 @@
1
- "use strict";
2
- "use client";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.specialBlockActions = void 0;
5
- exports.SpecialBlockTool = SpecialBlockTool;
6
- const jsx_runtime_1 = require("react/jsx-runtime");
7
- const RichTextIcons_1 = require("./RichTextIcons");
8
- const SpecialBlockOption_1 = require("./SpecialBlockOption");
9
- exports.specialBlockActions = [
10
- {
11
- html: '<figure data-placeholder="image"><p>Image placeholder</p></figure>',
12
- icon: RichTextIcons_1.ImageIcon,
13
- label: "Image",
14
- selectDefault: true,
15
- },
16
- {
17
- html: '<blockquote data-placeholder="quote"></blockquote>',
18
- icon: RichTextIcons_1.QuoteIcon,
19
- label: "Quote",
20
- selectDefault: true,
21
- },
22
- {
23
- html: "<h2>Title</h2>",
24
- icon: RichTextIcons_1.TitleIcon,
25
- label: "Title",
26
- selectDefault: true,
27
- },
28
- {
29
- html: "<pre><code>Code block</code></pre>",
30
- icon: RichTextIcons_1.CodeIcon,
31
- label: "Code block",
32
- selectDefault: true,
33
- },
34
- {
35
- html: "<pre><code>Embedded HTML</code></pre>",
36
- icon: RichTextIcons_1.DataObjectIcon,
37
- label: "Embedded HTML",
38
- selectDefault: true,
39
- },
40
- {
41
- html: "<hr>",
42
- icon: RichTextIcons_1.DividerIcon,
43
- label: "Divider",
44
- selectDefault: false,
45
- },
46
- ];
47
- // Renders the floating insertion control for image, quote, title, code, and divider blocks.
48
- function SpecialBlockTool({ onHoverChange, onInsert, toolHover, top, visible, }) {
49
- return ((0, jsx_runtime_1.jsxs)("div", { "aria-hidden": !visible, className: `bayon-rte-special-tool${visible ? " bayon-rte-special-tool--visible" : ""}`, onMouseEnter: () => onHoverChange(true), onMouseLeave: () => onHoverChange(false), style: { "--bayon-rte-special-tool-top": `${top}px` }, children: [(0, jsx_runtime_1.jsx)("button", { "aria-label": toolHover ? "Close special blocks" : "Add special block", className: "bayon-rte-icon-button bayon-rte-special-button bayon-rte-special-toggle", onClick: () => onHoverChange(!toolHover), style: {
50
- transform: visible
51
- ? "scale(1) rotate(0deg)"
52
- : "scale(0.84) rotate(-12deg)",
53
- }, title: toolHover ? "Close special blocks" : "Add special block", type: "button", children: toolHover ? (0, jsx_runtime_1.jsx)(RichTextIcons_1.CloseIcon, { size: 20 }) : (0, jsx_runtime_1.jsx)(RichTextIcons_1.AddIcon, { size: 20 }) }), toolHover ? ((0, jsx_runtime_1.jsx)("div", { className: "bayon-rte-special-tool__actions", children: exports.specialBlockActions.map((action) => ((0, jsx_runtime_1.jsx)(SpecialBlockOption_1.SpecialBlockOption, { action: action, onInsert: onInsert }, action.label))) })) : null] }));
54
- }
@@ -1,58 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getVisibleBlockActionToolPlacements = getVisibleBlockActionToolPlacements;
4
- exports.getPointerDropTarget = getPointerDropTarget;
5
- exports.getUniqueBlockActionToolPlacements = getUniqueBlockActionToolPlacements;
6
- function getVisibleBlockActionToolPlacements(placements, state) {
7
- const uniquePlacements = getUniqueBlockActionToolPlacements(placements);
8
- const visibleBlockId = state.activeBlockId ??
9
- state.draggedBlockId ??
10
- state.focusedBlockId ??
11
- state.hoveredBlockId;
12
- return visibleBlockId
13
- ? uniquePlacements.filter((placement) => placement.blockId === visibleBlockId)
14
- : [];
15
- }
16
- function getPointerDropTarget(placements, draggedBlockId, pointerClientY) {
17
- const targetPlacements = getUniqueBlockActionToolPlacements(placements).filter((placement) => {
18
- return placement.blockId !== draggedBlockId;
19
- });
20
- const targetPlacement = targetPlacements.find((placement) => {
21
- return (pointerClientY >= placement.top && pointerClientY <= placement.bottom);
22
- }) ??
23
- targetPlacements.reduce((closestPlacement, placement) => {
24
- if (!closestPlacement) {
25
- return placement;
26
- }
27
- return getDistanceToPlacement(placement, pointerClientY) <
28
- getDistanceToPlacement(closestPlacement, pointerClientY)
29
- ? placement
30
- : closestPlacement;
31
- }, null);
32
- if (!targetPlacement) {
33
- return null;
34
- }
35
- return {
36
- placement: pointerClientY >
37
- targetPlacement.top + (targetPlacement.bottom - targetPlacement.top) / 2
38
- ? "after"
39
- : "before",
40
- targetBlockId: targetPlacement.blockId,
41
- };
42
- }
43
- function getUniqueBlockActionToolPlacements(placements) {
44
- const seenBlockIds = new Set();
45
- return placements.filter((placement) => {
46
- if (seenBlockIds.has(placement.blockId)) {
47
- return false;
48
- }
49
- seenBlockIds.add(placement.blockId);
50
- return true;
51
- });
52
- }
53
- function getDistanceToPlacement(placement, pointerClientY) {
54
- if (pointerClientY >= placement.top && pointerClientY <= placement.bottom) {
55
- return 0;
56
- }
57
- return Math.min(Math.abs(pointerClientY - placement.top), Math.abs(pointerClientY - placement.bottom));
58
- }
@@ -1,119 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isBlockActionTarget = isBlockActionTarget;
4
- exports.reorderBlock = reorderBlock;
5
- exports.deleteBlockById = deleteBlockById;
6
- exports.convertCheckboxBlockToParagraph = convertCheckboxBlockToParagraph;
7
- exports.blockToClipboardText = blockToClipboardText;
8
- exports.blockActionToClipboardText = blockActionToClipboardText;
9
- const richText_1 = require("../richText");
10
- function isBlockActionTarget(block) {
11
- if (block.type === "paragraph") {
12
- return blockToClipboardText(block).trim().length > 0;
13
- }
14
- return true;
15
- }
16
- function reorderBlock(blocks, draggedBlockId, targetBlockId, placement = "before") {
17
- if (draggedBlockId === targetBlockId) {
18
- return (0, richText_1.sanitizeRichTextBlocks)(blocks);
19
- }
20
- const sanitizedBlocks = (0, richText_1.sanitizeRichTextBlocks)(blocks);
21
- const draggedRange = getSingleBlockRange(sanitizedBlocks, draggedBlockId);
22
- const targetRange = getSingleBlockRange(sanitizedBlocks, targetBlockId);
23
- if (!draggedRange ||
24
- !targetRange ||
25
- rangesOverlap(draggedRange, targetRange)) {
26
- return sanitizedBlocks;
27
- }
28
- const draggedBlocks = sanitizedBlocks.slice(draggedRange.start, draggedRange.end);
29
- const withoutDraggedBlocks = [
30
- ...sanitizedBlocks.slice(0, draggedRange.start),
31
- ...sanitizedBlocks.slice(draggedRange.end),
32
- ];
33
- const remainingTargetRange = getSingleBlockRange(withoutDraggedBlocks, targetBlockId);
34
- if (!remainingTargetRange) {
35
- return sanitizedBlocks;
36
- }
37
- const insertionIndex = placement === "after"
38
- ? remainingTargetRange.end
39
- : remainingTargetRange.start;
40
- return [
41
- ...withoutDraggedBlocks.slice(0, insertionIndex),
42
- ...draggedBlocks,
43
- ...withoutDraggedBlocks.slice(insertionIndex),
44
- ];
45
- }
46
- function deleteBlockById(blocks, blockId) {
47
- const sanitizedBlocks = (0, richText_1.sanitizeRichTextBlocks)(blocks);
48
- const range = getSingleBlockRange(sanitizedBlocks, blockId);
49
- if (!range) {
50
- return sanitizedBlocks;
51
- }
52
- const remainingBlocks = [
53
- ...sanitizedBlocks.slice(0, range.start),
54
- ...sanitizedBlocks.slice(range.end),
55
- ];
56
- return remainingBlocks.length > 0
57
- ? remainingBlocks
58
- : [{ id: "block-empty", markdown: "", type: "paragraph" }];
59
- }
60
- function convertCheckboxBlockToParagraph(blocks, blockId) {
61
- return (0, richText_1.sanitizeRichTextBlocks)(blocks).map((block) => {
62
- if (block.id !== blockId || block.type !== "checkbox") {
63
- return block;
64
- }
65
- return {
66
- id: block.id,
67
- markdown: block.markdown,
68
- type: "paragraph",
69
- };
70
- });
71
- }
72
- function blockToClipboardText(block) {
73
- if (block.type === "checkbox") {
74
- const text = (0, richText_1.richTextBlocksToPlainText)([block]);
75
- return text ? `[${block.checked ? "x" : " "}] ${text}` : "";
76
- }
77
- return (0, richText_1.richTextBlocksToPlainText)([block]);
78
- }
79
- function blockActionToClipboardText(blocks, blockId) {
80
- const sanitizedBlocks = (0, richText_1.sanitizeRichTextBlocks)(blocks);
81
- const range = getActionBlockRange(sanitizedBlocks, blockId);
82
- if (!range) {
83
- return "";
84
- }
85
- return sanitizedBlocks
86
- .slice(range.start, range.end)
87
- .map(blockToClipboardText)
88
- .filter((text) => text.trim())
89
- .join("\n");
90
- }
91
- function getSingleBlockRange(blocks, blockId) {
92
- const index = blocks.findIndex((block) => {
93
- return block.id === blockId;
94
- });
95
- return index === -1 ? null : { end: index + 1, start: index };
96
- }
97
- function getActionBlockRange(blocks, blockId) {
98
- const index = blocks.findIndex((block) => {
99
- return block.id === blockId;
100
- });
101
- if (index === -1) {
102
- return null;
103
- }
104
- if (blocks[index]?.type !== "checkbox") {
105
- return { end: index + 1, start: index };
106
- }
107
- let start = index;
108
- let end = index + 1;
109
- while (start > 0 && blocks[start - 1]?.type === "checkbox") {
110
- start -= 1;
111
- }
112
- while (end < blocks.length && blocks[end]?.type === "checkbox") {
113
- end += 1;
114
- }
115
- return { end, start };
116
- }
117
- function rangesOverlap(first, second) {
118
- return first.start < second.end && second.start < first.end;
119
- }
@@ -1,307 +0,0 @@
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.editorHtmlToMarkdown = editorHtmlToMarkdown;
9
- exports.markdownToEditorHtml = markdownToEditorHtml;
10
- exports.sanitizeRichTextHtml = sanitizeRichTextHtml;
11
- const allowedInlineTags = new Set(["a", "br", "code", "em", "strong"]);
12
- const textBlockTypes = new Set(["paragraph", "heading", "quote", "checkbox"]);
13
- function createEmptyRichTextBlocks() {
14
- return [{ id: createRichTextBlockId(), type: "paragraph", markdown: "" }];
15
- }
16
- function createRichTextBlockId() {
17
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
18
- return crypto.randomUUID();
19
- }
20
- return `block-${Date.now()}-${Math.random().toString(16).slice(2)}`;
21
- }
22
- /**
23
- * Sanitizes an array of RichTextBlock objects by:
24
- * - Filtering out invalid blocks
25
- * - Ensuring each block has a unique ID
26
- * - Returning an empty array if no valid blocks remain
27
- */
28
- function sanitizeRichTextBlocks(value) {
29
- const blocks = Array.isArray(value) ? value : [];
30
- const seenBlockIds = new Set();
31
- const sanitized = blocks
32
- .flatMap((block) => sanitizeRichTextBlock(block))
33
- .map((block) => {
34
- if (!seenBlockIds.has(block.id)) {
35
- seenBlockIds.add(block.id);
36
- return block;
37
- }
38
- const blockWithUniqueId = {
39
- ...block,
40
- id: createUniqueRichTextBlockId(seenBlockIds),
41
- };
42
- seenBlockIds.add(blockWithUniqueId.id);
43
- return blockWithUniqueId;
44
- });
45
- return sanitized.length > 0 ? sanitized : createEmptyRichTextBlocks();
46
- }
47
- function isRichTextBlocksEmpty(blocks) {
48
- return sanitizeRichTextBlocks(blocks).every((block) => {
49
- if (block.type === "divider") {
50
- return false;
51
- }
52
- if (block.type === "checkbox") {
53
- return markdownToPlainText(block.markdown).trim() === "";
54
- }
55
- if (block.type === "image") {
56
- return !block.assetId && !block.alt?.trim();
57
- }
58
- const text = block.type === "code" ? block.text : block.markdown;
59
- return markdownToPlainText(text).trim() === "";
60
- });
61
- }
62
- function richTextBlocksToPlainText(blocks) {
63
- return sanitizeRichTextBlocks(blocks)
64
- .map((block) => {
65
- if (block.type === "divider") {
66
- return "";
67
- }
68
- if (block.type === "checkbox") {
69
- return markdownToPlainText(block.markdown);
70
- }
71
- if (block.type === "image") {
72
- return block.alt ?? "";
73
- }
74
- return block.type === "code"
75
- ? block.text
76
- : markdownToPlainText(block.markdown);
77
- })
78
- .join(" ")
79
- .replace(/\s+/g, " ")
80
- .trim();
81
- }
82
- function editorHtmlToMarkdown(html) {
83
- const sanitized = normalizeTypography(decodeHtmlText(sanitizeRichTextHtml(html)
84
- .replace(/<br>/g, " \n")
85
- .replace(/<strong>([\s\S]*?)<\/strong>/g, (_, value) => {
86
- return `**${decodeHtmlText(stripTags(value))}**`;
87
- })
88
- .replace(/<em>([\s\S]*?)<\/em>/g, (_, value) => {
89
- return `_${decodeHtmlText(stripTags(value))}_`;
90
- })
91
- .replace(/<code>([\s\S]*?)<\/code>/g, (_, value) => {
92
- return `\`${decodeHtmlText(stripTags(value)).replace(/`/g, "\\`")}\``;
93
- })
94
- .replace(/<a href="([^"]*)">([\s\S]*?)<\/a>/g, (_, href, value) => {
95
- return `[${decodeHtmlText(stripTags(value))}](${decodeHtmlText(href)})`;
96
- })
97
- .replace(/<a>([\s\S]*?)<\/a>/g, (_, value) => {
98
- return decodeHtmlText(stripTags(value));
99
- })
100
- .replace(/<[^>]+>/g, "")))
101
- .replace(/\u00a0/g, " ")
102
- .trim();
103
- return sanitized;
104
- }
105
- function markdownToEditorHtml(markdown) {
106
- const tokens = [];
107
- let html = escapeHtmlText(normalizeTypography(markdown));
108
- html = html.replace(/`([^`\n]+)`/g, (_, value) => {
109
- const token = pushToken(tokens, `<code>${value}</code>`);
110
- return token;
111
- });
112
- html = html.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, (_, label, href) => {
113
- const safeHref = getSafeUrl(decodeHtmlText(href));
114
- return safeHref ? `<a href="${safeHref}">${label}</a>` : label;
115
- });
116
- html = html
117
- .replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>")
118
- .replace(/_([^_\n]+)_/g, "<em>$1</em>")
119
- .replace(/ {2}\n/g, "<br>")
120
- .replace(/\n/g, "<br>");
121
- tokens.forEach((value, index) => {
122
- html = html.replace(tokenPlaceholder(index), value);
123
- });
124
- return html;
125
- }
126
- function sanitizeRichTextHtml(html) {
127
- return html
128
- .replace(/<!--[\s\S]*?-->/g, "")
129
- .replace(/<script[\s\S]*?<\/script>/gi, "")
130
- .replace(/<style[\s\S]*?<\/style>/gi, "")
131
- .replace(/<b(\s[^>]*)?>/gi, "<strong>")
132
- .replace(/<\/b>/gi, "</strong>")
133
- .replace(/<i(\s[^>]*)?>/gi, "<em>")
134
- .replace(/<\/i>/gi, "</em>")
135
- .replace(/<\/?([a-z0-9]+)([^>]*)>/gi, (match, rawTag, rawAttrs) => {
136
- const tag = String(rawTag).toLowerCase();
137
- const closing = match.startsWith("</");
138
- if (!allowedInlineTags.has(tag)) {
139
- return "";
140
- }
141
- if (closing) {
142
- return tag === "br" ? "" : `</${tag}>`;
143
- }
144
- if (tag === "a") {
145
- const href = getSafeAttribute(rawAttrs, "href");
146
- return href ? `<a href="${href}">` : "<a>";
147
- }
148
- return tag === "br" ? "<br>" : `<${tag}>`;
149
- });
150
- }
151
- function sanitizeRichTextBlock(block) {
152
- if (!isRecord(block)) {
153
- return [];
154
- }
155
- const id = readBlockId(block);
156
- const type = block.type;
157
- if (type === "code") {
158
- return [{ id, type, text: String(block.text ?? "") }];
159
- }
160
- if (type === "divider") {
161
- return [{ id, type }];
162
- }
163
- if (type === "image") {
164
- return [
165
- {
166
- id,
167
- type,
168
- ...(typeof block.assetId === "string" && block.assetId.trim()
169
- ? { assetId: block.assetId.trim() }
170
- : {}),
171
- ...(typeof block.alt === "string" && block.alt.trim()
172
- ? { alt: markdownToPlainText(editorHtmlToMarkdown(block.alt)) }
173
- : {}),
174
- },
175
- ];
176
- }
177
- if (typeof type === "string" && textBlockTypes.has(type)) {
178
- const markdown = typeof block.markdown === "string"
179
- ? block.markdown
180
- : typeof block.text === "string"
181
- ? block.text
182
- : typeof block.html === "string"
183
- ? editorHtmlToMarkdown(block.html)
184
- : "";
185
- if (type === "checkbox") {
186
- return [
187
- {
188
- id,
189
- type,
190
- checked: Boolean(block.checked),
191
- markdown: sanitizeMarkdown(markdown),
192
- },
193
- ];
194
- }
195
- return [
196
- {
197
- id,
198
- type: type,
199
- markdown: sanitizeMarkdown(markdown),
200
- },
201
- ];
202
- }
203
- return [];
204
- }
205
- function readBlockId(block) {
206
- return typeof block.id === "string" && block.id.trim()
207
- ? block.id.trim()
208
- : createRichTextBlockId();
209
- }
210
- function createUniqueRichTextBlockId(existingIds) {
211
- let id = createRichTextBlockId();
212
- while (existingIds.has(id)) {
213
- id = createRichTextBlockId();
214
- }
215
- return id;
216
- }
217
- function markdownToPlainText(value) {
218
- return value
219
- .replace(/`([^`]*)`/g, "$1")
220
- .replace(/\*\*([^*]*)\*\*/g, "$1")
221
- .replace(/_([^_]*)_/g, "$1")
222
- .replace(/\[([^\]]*)\]\([^)]+\)/g, "$1")
223
- .replace(/[#>*`_[\]()]/g, " ")
224
- .replace(/\s+/g, " ")
225
- .trim();
226
- }
227
- function sanitizeMarkdown(value) {
228
- return normalizeTypography(value)
229
- .replace(/<!--[\s\S]*?-->/g, "")
230
- .trim();
231
- }
232
- function normalizeTypography(value) {
233
- const tokens = [];
234
- let normalized = value.replace(/`([^`\n]+)`/g, (match) => {
235
- return pushToken(tokens, match);
236
- });
237
- normalized = normalized.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g, (_match, label, href) => {
238
- return `[${label}](${pushToken(tokens, href)})`;
239
- });
240
- normalized = normalized
241
- .replace(/––/g, "—")
242
- .replace(/–-/g, "—")
243
- .replace(/---/g, "—")
244
- .replace(/--/g, "–");
245
- tokens.forEach((token, index) => {
246
- normalized = normalized.replace(tokenPlaceholder(index), token);
247
- });
248
- return normalized;
249
- }
250
- function stripTags(value) {
251
- return value.replace(/<[^>]+>/g, "");
252
- }
253
- function getSafeAttribute(attrs, name) {
254
- const value = getPlainAttribute(attrs, name);
255
- const safeValue = getSafeUrl(value);
256
- if (!safeValue) {
257
- return "";
258
- }
259
- return escapeAttribute(safeValue);
260
- }
261
- function getSafeUrl(value) {
262
- const trimmed = value.trim();
263
- if (!trimmed || /^javascript:/i.test(trimmed)) {
264
- return "";
265
- }
266
- return trimmed;
267
- }
268
- function getPlainAttribute(attrs, name) {
269
- const pattern = new RegExp(`${name}\\s*=\\s*("([^"]*)"|'([^']*)'|([^\\s>]+))`, "i");
270
- const match = attrs.match(pattern);
271
- return match?.[2] ?? match?.[3] ?? match?.[4] ?? "";
272
- }
273
- function escapeAttribute(value) {
274
- return value
275
- .replace(/&/g, "&amp;")
276
- .replace(/</g, "&lt;")
277
- .replace(/>/g, "&gt;")
278
- .replace(/"/g, "&quot;");
279
- }
280
- function escapeHtmlText(value) {
281
- return value
282
- .replace(/&/g, "&amp;")
283
- .replace(/</g, "&lt;")
284
- .replace(/>/g, "&gt;");
285
- }
286
- function decodeHtmlText(value) {
287
- return value
288
- .replace(/&nbsp;/g, " ")
289
- .replace(/&#160;/g, " ")
290
- .replace(/&#xA0;/gi, " ")
291
- .replace(/&amp;/g, "&")
292
- .replace(/&lt;/g, "<")
293
- .replace(/&gt;/g, ">")
294
- .replace(/&quot;/g, '"')
295
- .replace(/&apos;/g, "'")
296
- .replace(/&#39;/g, "'");
297
- }
298
- function pushToken(tokens, value) {
299
- const index = tokens.push(value) - 1;
300
- return tokenPlaceholder(index);
301
- }
302
- function tokenPlaceholder(index) {
303
- return `@@RICHTEXTTOKEN${index}@@`;
304
- }
305
- function isRecord(value) {
306
- return typeof value === "object" && value !== null;
307
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes