@bayonai/rich-text-editor 0.1.2

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 (91) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +37 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +5 -0
  5. package/dist/react/BlockActionTool.d.ts +15 -0
  6. package/dist/react/BlockActionTool.js +37 -0
  7. package/dist/react/EditorSessionProvider.d.ts +28 -0
  8. package/dist/react/EditorSessionProvider.js +74 -0
  9. package/dist/react/RichTextBody.d.ts +18 -0
  10. package/dist/react/RichTextBody.js +66 -0
  11. package/dist/react/RichTextDocumentSurface.d.ts +6 -0
  12. package/dist/react/RichTextDocumentSurface.js +5 -0
  13. package/dist/react/RichTextEditor.d.ts +45 -0
  14. package/dist/react/RichTextEditor.js +1096 -0
  15. package/dist/react/RichTextIcons.d.ts +21 -0
  16. package/dist/react/RichTextIcons.js +55 -0
  17. package/dist/react/RichTextRenderedBlock.d.ts +4 -0
  18. package/dist/react/RichTextRenderedBlock.js +36 -0
  19. package/dist/react/RichTextRenderer.d.ts +4 -0
  20. package/dist/react/RichTextRenderer.js +8 -0
  21. package/dist/react/RichTextStyles.d.ts +1 -0
  22. package/dist/react/RichTextStyles.js +719 -0
  23. package/dist/react/RichTextTitleInput.d.ts +20 -0
  24. package/dist/react/RichTextTitleInput.js +39 -0
  25. package/dist/react/SelectionFormatToolbar.d.ts +34 -0
  26. package/dist/react/SelectionFormatToolbar.js +18 -0
  27. package/dist/react/SpecialBlockOption.d.ts +7 -0
  28. package/dist/react/SpecialBlockOption.js +7 -0
  29. package/dist/react/SpecialBlockTool.d.ts +42 -0
  30. package/dist/react/SpecialBlockTool.js +50 -0
  31. package/dist/react/TranscriptionControl.d.ts +19 -0
  32. package/dist/react/TranscriptionControl.js +129 -0
  33. package/dist/react/UnsavedChangesDialog.d.ts +9 -0
  34. package/dist/react/UnsavedChangesDialog.js +13 -0
  35. package/dist/react/blockActionToolState.d.ts +18 -0
  36. package/dist/react/blockActionToolState.js +53 -0
  37. package/dist/react/blockActions.d.ts +8 -0
  38. package/dist/react/blockActions.js +111 -0
  39. package/dist/react/editorNavigation.d.ts +19 -0
  40. package/dist/react/editorNavigation.js +39 -0
  41. package/dist/react/editorShortcuts.d.ts +20 -0
  42. package/dist/react/editorShortcuts.js +25 -0
  43. package/dist/react/index.d.ts +9 -0
  44. package/dist/react/index.js +6 -0
  45. package/dist/react/richTextBlockStyles.d.ts +7 -0
  46. package/dist/react/richTextBlockStyles.js +7 -0
  47. package/dist/react/specialBlockStyles.d.ts +15 -0
  48. package/dist/react/specialBlockStyles.js +9 -0
  49. package/dist/richText.d.ts +15 -0
  50. package/dist/richText.js +297 -0
  51. package/dist/saveControl.d.ts +8 -0
  52. package/dist/saveControl.js +9 -0
  53. package/dist/session.d.ts +27 -0
  54. package/dist/session.js +78 -0
  55. package/dist/sessionRegistry.d.ts +24 -0
  56. package/dist/sessionRegistry.js +87 -0
  57. package/dist/types.d.ts +34 -0
  58. package/dist/types.js +1 -0
  59. package/dist/writingStats.d.ts +5 -0
  60. package/dist/writingStats.js +9 -0
  61. package/dist-cjs/index.js +22 -0
  62. package/dist-cjs/package.json +3 -0
  63. package/dist-cjs/react/BlockActionTool.js +40 -0
  64. package/dist-cjs/react/EditorSessionProvider.js +79 -0
  65. package/dist-cjs/react/RichTextBody.js +69 -0
  66. package/dist-cjs/react/RichTextDocumentSurface.js +8 -0
  67. package/dist-cjs/react/RichTextEditor.js +1108 -0
  68. package/dist-cjs/react/RichTextIcons.js +74 -0
  69. package/dist-cjs/react/RichTextRenderedBlock.js +39 -0
  70. package/dist-cjs/react/RichTextRenderer.js +11 -0
  71. package/dist-cjs/react/RichTextStyles.js +722 -0
  72. package/dist-cjs/react/RichTextTitleInput.js +44 -0
  73. package/dist-cjs/react/SelectionFormatToolbar.js +22 -0
  74. package/dist-cjs/react/SpecialBlockOption.js +10 -0
  75. package/dist-cjs/react/SpecialBlockTool.js +54 -0
  76. package/dist-cjs/react/TranscriptionControl.js +133 -0
  77. package/dist-cjs/react/UnsavedChangesDialog.js +16 -0
  78. package/dist-cjs/react/blockActionToolState.js +58 -0
  79. package/dist-cjs/react/blockActions.js +119 -0
  80. package/dist-cjs/react/editorNavigation.js +45 -0
  81. package/dist-cjs/react/editorShortcuts.js +28 -0
  82. package/dist-cjs/react/index.js +17 -0
  83. package/dist-cjs/react/richTextBlockStyles.js +10 -0
  84. package/dist-cjs/react/specialBlockStyles.js +12 -0
  85. package/dist-cjs/richText.js +307 -0
  86. package/dist-cjs/saveControl.js +12 -0
  87. package/dist-cjs/session.js +83 -0
  88. package/dist-cjs/sessionRegistry.js +90 -0
  89. package/dist-cjs/types.js +2 -0
  90. package/dist-cjs/writingStats.js +12 -0
  91. package/package.json +45 -0
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RichTextTitleInput = RichTextTitleInput;
5
+ exports.getTitleKeyCommand = getTitleKeyCommand;
6
+ exports.RichTextReadTitle = RichTextReadTitle;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const RichTextStyles_1 = require("./RichTextStyles");
10
+ // Renders the title field and handles keyboard movement into the editor body.
11
+ function RichTextTitleInput({ disabled, inputRef, label, onArrowDown, onChange, required, title, validationMessage = "", }) {
12
+ const validationMessageId = (0, react_1.useId)();
13
+ (0, react_1.useEffect)(() => {
14
+ resizeTitleTextArea(inputRef.current);
15
+ }, [inputRef, title]);
16
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("textarea", { "aria-describedby": validationMessage ? validationMessageId : undefined, "aria-invalid": validationMessage ? true : undefined, "aria-label": label, "aria-required": required ? true : undefined, className: "bayon-rte-title", dir: "auto", disabled: disabled, onChange: (event) => {
17
+ resizeTitleTextArea(event.currentTarget);
18
+ onChange(event.target.value);
19
+ }, onInput: (event) => resizeTitleTextArea(event.currentTarget), onKeyDown: (event) => {
20
+ if (getTitleKeyCommand(event) === "focus-body-start") {
21
+ event.preventDefault();
22
+ onArrowDown();
23
+ }
24
+ }, placeholder: "Title", ref: inputRef, rows: 1, value: title }), validationMessage ? ((0, jsx_runtime_1.jsx)("div", { className: "bayon-rte-title-error", id: validationMessageId, role: "alert", children: validationMessage })) : null] }));
25
+ }
26
+ function getTitleKeyCommand(event) {
27
+ return event.key === "ArrowDown" || event.key === "Enter"
28
+ ? "focus-body-start"
29
+ : null;
30
+ }
31
+ function RichTextReadTitle({ title }) {
32
+ const titleRef = (0, react_1.useRef)(null);
33
+ (0, react_1.useEffect)(() => {
34
+ resizeTitleTextArea(titleRef.current);
35
+ }, [title]);
36
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(RichTextStyles_1.RichTextStyleScope, {}), (0, jsx_runtime_1.jsx)("textarea", { "aria-label": "Entry title", className: "bayon-rte-title", dir: "auto", placeholder: "Title", readOnly: true, ref: titleRef, rows: 1, value: title })] }));
37
+ }
38
+ function resizeTitleTextArea(textarea) {
39
+ if (!textarea) {
40
+ return;
41
+ }
42
+ textarea.style.height = "auto";
43
+ textarea.style.height = `${textarea.scrollHeight}px`;
44
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.selectionActions = void 0;
5
+ exports.SelectionFormatToolbar = SelectionFormatToolbar;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const RichTextIcons_1 = require("./RichTextIcons");
8
+ exports.selectionActions = [
9
+ { command: "bold", icon: RichTextIcons_1.BoldIcon, label: "Bold" },
10
+ { command: "italic", icon: RichTextIcons_1.ItalicIcon, label: "Italic" },
11
+ { command: "link", icon: RichTextIcons_1.LinkIcon, label: "Link" },
12
+ { command: "heading", icon: RichTextIcons_1.TitleIcon, label: "Title" },
13
+ { command: "quote", icon: RichTextIcons_1.QuoteIcon, label: "Quote" },
14
+ { command: "code", icon: RichTextIcons_1.CodeIcon, label: "Code" },
15
+ ];
16
+ // Renders the floating toolbar used to format the current text selection.
17
+ function SelectionFormatToolbar({ left, onAction, top, }) {
18
+ return ((0, jsx_runtime_1.jsx)("div", { className: "bayon-rte-toolbar", style: {
19
+ "--bayon-rte-toolbar-left": `${left}px`,
20
+ "--bayon-rte-toolbar-top": `${top}px`,
21
+ }, children: exports.selectionActions.map(({ command, icon: Icon, label }, index) => ((0, jsx_runtime_1.jsx)("button", { "aria-label": label, className: `bayon-rte-icon-button bayon-rte-toolbar__button${index === 3 ? " bayon-rte-toolbar__button--divider" : ""}`, onClick: () => onAction(command), onMouseDown: (event) => event.preventDefault(), title: label, type: "button", children: (0, jsx_runtime_1.jsx)(Icon, { size: 18 }) }, command))) }));
22
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.SpecialBlockOption = SpecialBlockOption;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ // Renders one selectable special-block action inside the insertion tool.
7
+ function SpecialBlockOption({ action, onInsert, }) {
8
+ const Icon = action.icon;
9
+ return ((0, jsx_runtime_1.jsx)("button", { "aria-label": action.label, className: "bayon-rte-icon-button bayon-rte-special-button", onClick: () => onInsert(action), onMouseDown: (event) => event.preventDefault(), title: action.label, type: "button", children: (0, jsx_runtime_1.jsx)(Icon, { size: 18 }) }));
10
+ }
@@ -0,0 +1,54 @@
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
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.TranscriptionControl = TranscriptionControl;
5
+ exports.getTranscriptionControlState = getTranscriptionControlState;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const react_1 = require("react");
8
+ const RichTextIcons_1 = require("./RichTextIcons");
9
+ function TranscriptionControl({ disabled = false, language, onTranscript, }) {
10
+ const recognitionRef = (0, react_1.useRef)(null);
11
+ const [errorMessage, setErrorMessage] = (0, react_1.useState)(null);
12
+ const [lastInserted, setLastInserted] = (0, react_1.useState)(false);
13
+ const [recording, setRecording] = (0, react_1.useState)(false);
14
+ const recognitionSupported = typeof window === "undefined" || Boolean(getSpeechRecognitionConstructor());
15
+ const controlState = getTranscriptionControlState({
16
+ disabled,
17
+ errorMessage,
18
+ lastInserted,
19
+ recording,
20
+ supported: recognitionSupported,
21
+ });
22
+ (0, react_1.useEffect)(() => {
23
+ return () => {
24
+ recognitionRef.current?.stop();
25
+ recognitionRef.current = null;
26
+ };
27
+ }, []);
28
+ function stopRecording() {
29
+ recognitionRef.current?.stop();
30
+ recognitionRef.current = null;
31
+ setRecording(false);
32
+ setLastInserted(false);
33
+ }
34
+ function startRecording() {
35
+ const Recognition = getSpeechRecognitionConstructor();
36
+ if (!Recognition || disabled) {
37
+ setErrorMessage("Browser transcription is unavailable.");
38
+ setLastInserted(false);
39
+ return;
40
+ }
41
+ const recognition = new Recognition();
42
+ recognition.continuous = true;
43
+ recognition.interimResults = false;
44
+ if (language?.trim()) {
45
+ recognition.lang = language.trim();
46
+ }
47
+ recognition.onresult = (event) => {
48
+ const transcript = readFinalTranscript(event);
49
+ if (transcript) {
50
+ onTranscript(transcript);
51
+ setLastInserted(true);
52
+ }
53
+ };
54
+ recognition.onerror = (event) => {
55
+ setErrorMessage(readRecognitionErrorMessage(event));
56
+ setLastInserted(false);
57
+ setRecording(false);
58
+ };
59
+ recognition.onend = () => {
60
+ setRecording(false);
61
+ recognitionRef.current = null;
62
+ };
63
+ recognitionRef.current = recognition;
64
+ setErrorMessage(null);
65
+ setLastInserted(false);
66
+ setRecording(true);
67
+ recognition.start();
68
+ }
69
+ return ((0, jsx_runtime_1.jsxs)("div", { "aria-label": "Browser transcription", className: `bayon-rte-transcription bayon-rte-transcription--${controlState.tone}`, children: [(0, jsx_runtime_1.jsx)("button", { "aria-label": controlState.buttonLabel, "aria-pressed": recording, className: `bayon-rte-icon-button bayon-rte-transcription__button${recording ? " bayon-rte-transcription__button--recording" : ""}`, disabled: controlState.disabled, onClick: () => (recording ? stopRecording() : startRecording()), title: controlState.buttonLabel, type: "button", children: recording ? (0, jsx_runtime_1.jsx)(RichTextIcons_1.StopIcon, { size: 18 }) : (0, jsx_runtime_1.jsx)(RichTextIcons_1.MicIcon, { size: 18 }) }), controlState.statusLabel ? ((0, jsx_runtime_1.jsxs)("small", { className: "bayon-rte-transcription__status", role: "status", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", className: "bayon-rte-transcription__dot" }), controlState.statusLabel] })) : null] }));
70
+ }
71
+ function getTranscriptionControlState({ disabled, errorMessage, lastInserted, recording, supported, }) {
72
+ if (!supported || disabled) {
73
+ return {
74
+ buttonLabel: "Transcription unavailable",
75
+ disabled: true,
76
+ statusLabel: "Unavailable",
77
+ tone: "unavailable",
78
+ };
79
+ }
80
+ if (errorMessage) {
81
+ return {
82
+ buttonLabel: "Transcribe",
83
+ disabled: false,
84
+ statusLabel: errorMessage,
85
+ tone: "error",
86
+ };
87
+ }
88
+ if (recording) {
89
+ return {
90
+ buttonLabel: "Stop transcription",
91
+ disabled: false,
92
+ statusLabel: "Listening",
93
+ tone: "recording",
94
+ };
95
+ }
96
+ if (lastInserted) {
97
+ return {
98
+ buttonLabel: "Transcribe",
99
+ disabled: false,
100
+ statusLabel: "Added",
101
+ tone: "success",
102
+ };
103
+ }
104
+ return {
105
+ buttonLabel: "Transcribe",
106
+ disabled: false,
107
+ statusLabel: null,
108
+ tone: "ready",
109
+ };
110
+ }
111
+ function getSpeechRecognitionConstructor() {
112
+ if (typeof window === "undefined") {
113
+ return null;
114
+ }
115
+ const speechWindow = window;
116
+ return speechWindow.SpeechRecognition ?? speechWindow.webkitSpeechRecognition ?? null;
117
+ }
118
+ function readFinalTranscript(event) {
119
+ let text = "";
120
+ for (let index = event.resultIndex; index < event.results.length; index += 1) {
121
+ const result = event.results[index];
122
+ if (result?.isFinal) {
123
+ text += result[0]?.transcript ?? "";
124
+ }
125
+ }
126
+ return text.trim();
127
+ }
128
+ function readRecognitionErrorMessage(event) {
129
+ if (event.error === "not-allowed" || event.error === "service-not-allowed") {
130
+ return "Microphone access was blocked.";
131
+ }
132
+ return "Transcription stopped.";
133
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.UnsavedChangesDialog = UnsavedChangesDialog;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const RichTextStyles_1 = require("./RichTextStyles");
7
+ function UnsavedChangesDialog({ error, onDiscardAndLeave, onSaveAndLeave, onStay, open, saving, }) {
8
+ if (!open) {
9
+ return null;
10
+ }
11
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "bayon-rte-unsaved-backdrop", onMouseDown: () => {
12
+ if (!saving) {
13
+ onStay();
14
+ }
15
+ }, children: [(0, jsx_runtime_1.jsx)(RichTextStyles_1.RichTextStyleScope, {}), (0, jsx_runtime_1.jsxs)("section", { "aria-describedby": "unsaved-changes-description", "aria-labelledby": "unsaved-changes-title", className: "bayon-rte-unsaved-dialog", onMouseDown: (event) => event.stopPropagation(), role: "dialog", children: [(0, jsx_runtime_1.jsx)("h2", { id: "unsaved-changes-title", children: "Unsaved changes" }), (0, jsx_runtime_1.jsx)("p", { id: "unsaved-changes-description", children: "Save your changes before leaving, discard them, or stay on this page." }), error ? ((0, jsx_runtime_1.jsx)("div", { className: "bayon-rte-unsaved-error", role: "alert", children: error })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "bayon-rte-unsaved-actions", children: [(0, jsx_runtime_1.jsx)("button", { className: "bayon-rte-button", disabled: saving, onClick: onStay, type: "button", children: "Stay" }), (0, jsx_runtime_1.jsx)("button", { className: "bayon-rte-button bayon-rte-button--danger", disabled: saving, onClick: onDiscardAndLeave, type: "button", children: "Discard and leave" }), (0, jsx_runtime_1.jsx)("button", { className: "bayon-rte-button bayon-rte-button--primary", disabled: saving, onClick: onSaveAndLeave, type: "button", children: saving ? "Saving..." : "Save and leave" })] })] })] }));
16
+ }
@@ -0,0 +1,58 @@
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
+ }
@@ -0,0 +1,119 @@
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
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isCursorOnFirstLine = isCursorOnFirstLine;
4
+ exports.isSelectAllShortcut = isSelectAllShortcut;
5
+ exports.getEditorKeyboardShortcut = getEditorKeyboardShortcut;
6
+ exports.getCodeBlockSelectionTarget = getCodeBlockSelectionTarget;
7
+ function isCursorOnFirstLine(value, selectionStart) {
8
+ return !value.slice(0, selectionStart).includes("\n");
9
+ }
10
+ function isSelectAllShortcut(event) {
11
+ return (event.key.toLowerCase() === "a" &&
12
+ (event.ctrlKey || event.metaKey) &&
13
+ !event.altKey &&
14
+ !event.shiftKey);
15
+ }
16
+ function getEditorKeyboardShortcut(event) {
17
+ if (!(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey) {
18
+ return null;
19
+ }
20
+ const key = event.key.toLowerCase();
21
+ if (key === "b") {
22
+ return "bold";
23
+ }
24
+ if (key === "i") {
25
+ return "italic";
26
+ }
27
+ if (key === "k") {
28
+ return "link";
29
+ }
30
+ return null;
31
+ }
32
+ function getCodeBlockSelectionTarget(root, selection) {
33
+ if (!selection || selection.rangeCount === 0) {
34
+ return null;
35
+ }
36
+ const { startContainer } = selection.getRangeAt(0);
37
+ const activeElement = startContainer.nodeType === 3
38
+ ? startContainer.parentElement
39
+ : startContainer.nodeType === 1
40
+ ? startContainer
41
+ : null;
42
+ const code = activeElement?.closest("pre code");
43
+ const pre = code?.closest("pre");
44
+ return code && pre && root.contains(pre) ? code : null;
45
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTextBlockShortcut = getTextBlockShortcut;
4
+ const dividerMarkers = new Set(["---", "—-", "***", "___"]);
5
+ function getTextBlockShortcut(markdown) {
6
+ const trimmed = markdown.trim();
7
+ if (dividerMarkers.has(trimmed)) {
8
+ return { type: "divider" };
9
+ }
10
+ const checkbox = trimmed.match(/^\[( |x|X)?\]\s*(.*)$/);
11
+ if (checkbox) {
12
+ return {
13
+ type: "checkbox",
14
+ checked: checkbox[1]?.toLowerCase() === "x",
15
+ markdown: checkbox[2]?.trim() ?? "",
16
+ };
17
+ }
18
+ if (trimmed.startsWith("# ")) {
19
+ return { type: "heading", markdown: trimmed.slice(2).trim() };
20
+ }
21
+ if (trimmed.startsWith("> ")) {
22
+ return { type: "quote", markdown: trimmed.slice(2).trim() };
23
+ }
24
+ if (trimmed.startsWith("```")) {
25
+ return { type: "code", text: trimmed.slice(3).trimStart() };
26
+ }
27
+ return null;
28
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsavedChangesDialog = exports.RichTextRenderer = exports.RichTextReadTitle = exports.RichTextEditor = exports.RichTextDocumentSurface = exports.useEditorSession = exports.useEditorExitGuard = exports.EditorSessionProvider = void 0;
4
+ var EditorSessionProvider_1 = require("./EditorSessionProvider");
5
+ Object.defineProperty(exports, "EditorSessionProvider", { enumerable: true, get: function () { return EditorSessionProvider_1.EditorSessionProvider; } });
6
+ Object.defineProperty(exports, "useEditorExitGuard", { enumerable: true, get: function () { return EditorSessionProvider_1.useEditorExitGuard; } });
7
+ Object.defineProperty(exports, "useEditorSession", { enumerable: true, get: function () { return EditorSessionProvider_1.useEditorSession; } });
8
+ var RichTextDocumentSurface_1 = require("./RichTextDocumentSurface");
9
+ Object.defineProperty(exports, "RichTextDocumentSurface", { enumerable: true, get: function () { return RichTextDocumentSurface_1.RichTextDocumentSurface; } });
10
+ var RichTextEditor_1 = require("./RichTextEditor");
11
+ Object.defineProperty(exports, "RichTextEditor", { enumerable: true, get: function () { return RichTextEditor_1.RichTextEditor; } });
12
+ var RichTextTitleInput_1 = require("./RichTextTitleInput");
13
+ Object.defineProperty(exports, "RichTextReadTitle", { enumerable: true, get: function () { return RichTextTitleInput_1.RichTextReadTitle; } });
14
+ var RichTextRenderer_1 = require("./RichTextRenderer");
15
+ Object.defineProperty(exports, "RichTextRenderer", { enumerable: true, get: function () { return RichTextRenderer_1.RichTextRenderer; } });
16
+ var UnsavedChangesDialog_1 = require("./UnsavedChangesDialog");
17
+ Object.defineProperty(exports, "UnsavedChangesDialog", { enumerable: true, get: function () { return UnsavedChangesDialog_1.UnsavedChangesDialog; } });
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dividerBlockSx = void 0;
4
+ exports.dividerBlockSx = {
5
+ border: "0",
6
+ borderTop: "1px solid currentColor",
7
+ height: 0,
8
+ my: "6px",
9
+ py: 0,
10
+ };
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toolButtonSx = void 0;
4
+ exports.toolButtonSx = {
5
+ backgroundColor: "#ffffff",
6
+ color: "#7f38d8",
7
+ height: { xs: 28, sm: 39 },
8
+ width: { xs: 28, sm: 39 },
9
+ "&:hover": {
10
+ backgroundColor: "#fbf6ff",
11
+ },
12
+ };