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