@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.
- package/BEHAVIOR.md +396 -0
- package/CHANGELOG.md +22 -0
- package/README.md +25 -6
- package/dist/core/blockTree.d.ts +14 -0
- package/dist/core/blockTree.js +126 -0
- package/dist/core/blockTypes.d.ts +6 -0
- package/dist/core/blockTypes.js +5 -0
- package/dist/core/exportImport.d.ts +59 -0
- package/dist/core/exportImport.js +51 -0
- package/dist/core/features.d.ts +59 -0
- package/dist/core/features.js +57 -0
- package/dist/core/imageBlockDiagnostics.d.ts +4 -0
- package/dist/core/imageBlockDiagnostics.js +19 -0
- package/dist/core/proFeatures.d.ts +60 -0
- package/dist/core/proFeatures.js +64 -0
- package/dist/{richText.d.ts → core/richText.d.ts} +2 -0
- package/dist/core/richText.js +566 -0
- package/dist/core/types.d.ts +78 -0
- package/dist/index.d.ts +14 -8
- package/dist/index.js +8 -5
- package/dist/react/editor/RichTextBody.d.ts +28 -0
- package/dist/react/editor/RichTextBody.js +131 -0
- package/dist/react/editor/RichTextEditor.d.ts +138 -0
- package/dist/react/editor/RichTextEditor.js +2925 -0
- package/dist/react/editor/RichTextRenderedBlock.d.ts +20 -0
- package/dist/react/editor/RichTextRenderedBlock.js +162 -0
- package/dist/react/editor/RichTextRenderer.d.ts +13 -0
- package/dist/react/editor/RichTextRenderer.js +16 -0
- package/dist/react/{RichTextTitleInput.d.ts → editor/RichTextTitleInput.d.ts} +11 -1
- package/dist/react/{RichTextTitleInput.js → editor/RichTextTitleInput.js} +17 -2
- package/dist/react/editor/blockActions.d.ts +48 -0
- package/dist/react/editor/blockActions.js +495 -0
- package/dist/react/editor/editorHistory.d.ts +55 -0
- package/dist/react/editor/editorHistory.js +111 -0
- package/dist/react/{editorNavigation.d.ts → editor/editorNavigation.d.ts} +2 -0
- package/dist/react/{editorNavigation.js → editor/editorNavigation.js} +16 -0
- package/dist/react/editor/editorOperations.d.ts +10 -0
- package/dist/react/editor/editorOperations.js +3 -0
- package/dist/react/editor/editorSelection.d.ts +3 -0
- package/dist/react/editor/editorSelection.js +215 -0
- package/dist/react/{editorShortcuts.d.ts → editor/editorShortcuts.d.ts} +10 -0
- package/dist/react/{editorShortcuts.js → editor/editorShortcuts.js} +17 -1
- package/dist/react/{RichTextIcons.d.ts → icons/RichTextIcons.d.ts} +3 -0
- package/dist/react/{RichTextIcons.js → icons/RichTextIcons.js} +9 -0
- package/dist/react/index.d.ts +12 -9
- package/dist/react/index.js +7 -6
- package/dist/react/{EditorSessionProvider.d.ts → session/EditorSessionProvider.d.ts} +2 -2
- package/dist/react/{EditorSessionProvider.js → session/EditorSessionProvider.js} +3 -3
- package/dist/react/{UnsavedChangesDialog.js → session/UnsavedChangesDialog.js} +1 -1
- package/dist/react/styles/RichTextStyles.js +1362 -0
- package/dist/react/{BlockActionTool.d.ts → tools/BlockActionTool.d.ts} +1 -1
- package/dist/react/{BlockActionTool.js → tools/BlockActionTool.js} +6 -2
- package/dist/react/tools/LinkCreationInput.d.ts +9 -0
- package/dist/react/tools/LinkCreationInput.js +38 -0
- package/dist/react/{SelectionFormatToolbar.d.ts → tools/SelectionFormatToolbar.d.ts} +3 -2
- package/dist/react/{SelectionFormatToolbar.js → tools/SelectionFormatToolbar.js} +3 -3
- package/dist/react/tools/SpecialBlockOption.d.ts +9 -0
- package/dist/react/tools/SpecialBlockOption.js +8 -0
- package/dist/react/tools/SpecialBlockTool.d.ts +91 -0
- package/dist/react/tools/SpecialBlockTool.js +125 -0
- package/dist/react/{TranscriptionControl.d.ts → tools/TranscriptionControl.d.ts} +9 -0
- package/dist/react/{TranscriptionControl.js → tools/TranscriptionControl.js} +70 -9
- package/dist/react/tools/blockActionToolState.d.ts +41 -0
- package/dist/react/tools/blockActionToolState.js +177 -0
- package/dist/react/tools/imageBlockDiagnostics.d.ts +2 -0
- package/dist/react/tools/imageBlockDiagnostics.js +12 -0
- package/dist/{session.d.ts → session/session.d.ts} +1 -1
- package/dist-cjs/core/blockTree.js +137 -0
- package/dist-cjs/core/blockTypes.js +9 -0
- package/dist-cjs/core/exportImport.js +56 -0
- package/dist-cjs/core/features.js +62 -0
- package/dist-cjs/core/proFeatures.js +70 -0
- package/dist-cjs/core/richText.js +578 -0
- package/dist-cjs/index.js +22 -6
- package/dist-cjs/react/editor/RichTextBody.js +134 -0
- package/dist-cjs/react/editor/RichTextEditor.js +2956 -0
- package/dist-cjs/react/editor/RichTextRenderedBlock.js +166 -0
- package/dist-cjs/react/editor/RichTextRenderer.js +20 -0
- package/dist-cjs/react/{RichTextTitleInput.js → editor/RichTextTitleInput.js} +18 -2
- package/dist-cjs/react/editor/blockActions.js +518 -0
- package/dist-cjs/react/editor/editorHistory.js +120 -0
- package/dist-cjs/react/{editorNavigation.js → editor/editorNavigation.js} +17 -0
- package/dist-cjs/react/editor/editorOperations.js +6 -0
- package/dist-cjs/react/editor/editorSelection.js +219 -0
- package/dist-cjs/react/{editorShortcuts.js → editor/editorShortcuts.js} +17 -1
- package/dist-cjs/react/{RichTextIcons.js → icons/RichTextIcons.js} +12 -0
- package/dist-cjs/react/index.js +9 -7
- package/dist-cjs/react/{EditorSessionProvider.js → session/EditorSessionProvider.js} +3 -3
- package/dist-cjs/react/{UnsavedChangesDialog.js → session/UnsavedChangesDialog.js} +1 -1
- package/dist-cjs/react/styles/RichTextStyles.js +1365 -0
- package/dist-cjs/react/{BlockActionTool.js → tools/BlockActionTool.js} +6 -2
- package/dist-cjs/react/tools/LinkCreationInput.js +41 -0
- package/dist-cjs/react/{SelectionFormatToolbar.js → tools/SelectionFormatToolbar.js} +3 -3
- package/dist-cjs/react/tools/SpecialBlockOption.js +11 -0
- package/dist-cjs/react/tools/SpecialBlockTool.js +129 -0
- package/dist-cjs/react/{TranscriptionControl.js → tools/TranscriptionControl.js} +71 -9
- package/dist-cjs/react/tools/blockActionToolState.js +186 -0
- package/package.json +3 -2
- package/dist/react/RichTextBody.d.ts +0 -18
- package/dist/react/RichTextBody.js +0 -66
- package/dist/react/RichTextEditor.d.ts +0 -45
- package/dist/react/RichTextEditor.js +0 -1096
- package/dist/react/RichTextRenderedBlock.d.ts +0 -4
- package/dist/react/RichTextRenderedBlock.js +0 -36
- package/dist/react/RichTextRenderer.d.ts +0 -4
- package/dist/react/RichTextRenderer.js +0 -8
- package/dist/react/RichTextStyles.js +0 -719
- package/dist/react/SpecialBlockOption.d.ts +0 -7
- package/dist/react/SpecialBlockOption.js +0 -7
- package/dist/react/SpecialBlockTool.d.ts +0 -42
- package/dist/react/SpecialBlockTool.js +0 -50
- package/dist/react/blockActionToolState.d.ts +0 -18
- package/dist/react/blockActionToolState.js +0 -53
- package/dist/react/blockActions.d.ts +0 -8
- package/dist/react/blockActions.js +0 -111
- package/dist/richText.js +0 -297
- package/dist/types.d.ts +0 -34
- package/dist-cjs/react/RichTextBody.js +0 -69
- package/dist-cjs/react/RichTextEditor.js +0 -1108
- package/dist-cjs/react/RichTextRenderedBlock.js +0 -39
- package/dist-cjs/react/RichTextRenderer.js +0 -11
- package/dist-cjs/react/RichTextStyles.js +0 -722
- package/dist-cjs/react/SpecialBlockOption.js +0 -10
- package/dist-cjs/react/SpecialBlockTool.js +0 -54
- package/dist-cjs/react/blockActionToolState.js +0 -58
- package/dist-cjs/react/blockActions.js +0 -119
- package/dist-cjs/richText.js +0 -307
- /package/dist/{types.js → core/types.js} +0 -0
- /package/dist/{writingStats.d.ts → core/writingStats.d.ts} +0 -0
- /package/dist/{writingStats.js → core/writingStats.js} +0 -0
- /package/dist/react/{RichTextDocumentSurface.d.ts → editor/RichTextDocumentSurface.d.ts} +0 -0
- /package/dist/react/{RichTextDocumentSurface.js → editor/RichTextDocumentSurface.js} +0 -0
- /package/dist/react/{UnsavedChangesDialog.d.ts → session/UnsavedChangesDialog.d.ts} +0 -0
- /package/dist/react/{RichTextStyles.d.ts → styles/RichTextStyles.d.ts} +0 -0
- /package/dist/react/{richTextBlockStyles.d.ts → styles/richTextBlockStyles.d.ts} +0 -0
- /package/dist/react/{richTextBlockStyles.js → styles/richTextBlockStyles.js} +0 -0
- /package/dist/react/{specialBlockStyles.d.ts → styles/specialBlockStyles.d.ts} +0 -0
- /package/dist/react/{specialBlockStyles.js → styles/specialBlockStyles.js} +0 -0
- /package/dist/{saveControl.d.ts → session/saveControl.d.ts} +0 -0
- /package/dist/{saveControl.js → session/saveControl.js} +0 -0
- /package/dist/{session.js → session/session.js} +0 -0
- /package/dist/{sessionRegistry.d.ts → session/sessionRegistry.d.ts} +0 -0
- /package/dist/{sessionRegistry.js → session/sessionRegistry.js} +0 -0
- /package/dist-cjs/{types.js → core/types.js} +0 -0
- /package/dist-cjs/{writingStats.js → core/writingStats.js} +0 -0
- /package/dist-cjs/react/{RichTextDocumentSurface.js → editor/RichTextDocumentSurface.js} +0 -0
- /package/dist-cjs/react/{richTextBlockStyles.js → styles/richTextBlockStyles.js} +0 -0
- /package/dist-cjs/react/{specialBlockStyles.js → styles/specialBlockStyles.js} +0 -0
- /package/dist-cjs/{saveControl.js → session/saveControl.js} +0 -0
- /package/dist-cjs/{session.js → session/session.js} +0 -0
- /package/dist-cjs/{sessionRegistry.js → session/sessionRegistry.js} +0 -0
|
@@ -1,1108 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
"use client";
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.RichTextEditor = RichTextEditor;
|
|
5
|
-
exports.getDeletionFocusTarget = getDeletionFocusTarget;
|
|
6
|
-
exports.normalizeSoftBreakHtml = normalizeSoftBreakHtml;
|
|
7
|
-
exports.hasEditableTrailingWhitespace = hasEditableTrailingWhitespace;
|
|
8
|
-
exports.shouldDelayTypographyNormalization = shouldDelayTypographyNormalization;
|
|
9
|
-
exports.splitCheckboxMarkdownAtTextOffset = splitCheckboxMarkdownAtTextOffset;
|
|
10
|
-
exports.getCheckboxEnterAction = getCheckboxEnterAction;
|
|
11
|
-
exports.shouldPadTrailingLineBreak = shouldPadTrailingLineBreak;
|
|
12
|
-
exports.resolveTranscriptionConfig = resolveTranscriptionConfig;
|
|
13
|
-
exports.appendTranscriptText = appendTranscriptText;
|
|
14
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
15
|
-
const react_1 = require("react");
|
|
16
|
-
const editorShortcuts_1 = require("./editorShortcuts");
|
|
17
|
-
const BlockActionTool_1 = require("./BlockActionTool");
|
|
18
|
-
const RichTextBody_1 = require("./RichTextBody");
|
|
19
|
-
const RichTextDocumentSurface_1 = require("./RichTextDocumentSurface");
|
|
20
|
-
const RichTextTitleInput_1 = require("./RichTextTitleInput");
|
|
21
|
-
const SelectionFormatToolbar_1 = require("./SelectionFormatToolbar");
|
|
22
|
-
const blockActions_1 = require("./blockActions");
|
|
23
|
-
const blockActionToolState_1 = require("./blockActionToolState");
|
|
24
|
-
const richText_1 = require("../richText");
|
|
25
|
-
const writingStats_1 = require("../writingStats");
|
|
26
|
-
const SpecialBlockTool_1 = require("./SpecialBlockTool");
|
|
27
|
-
const RichTextStyles_1 = require("./RichTextStyles");
|
|
28
|
-
const TranscriptionControl_1 = require("./TranscriptionControl");
|
|
29
|
-
// Coordinates the title field, editable body, format toolbar, and block insertion controls.
|
|
30
|
-
function RichTextEditor({ bodyLabel, contentBlocks, disabled = false, documentBackground = "transparent", helperText, onContentBlocksChange, onTitleChange, required = false, title, titleLabel, titleValidationMessage = "", transcriptionEnabled, transcriptionLanguage, }) {
|
|
31
|
-
const bodyRef = (0, react_1.useRef)(null);
|
|
32
|
-
const blockPointerDragRef = (0, react_1.useRef)(null);
|
|
33
|
-
const specialBlockTargetRef = (0, react_1.useRef)(null);
|
|
34
|
-
const titleRef = (0, react_1.useRef)(null);
|
|
35
|
-
const [activeBlockActionMenuId, setActiveBlockActionMenuId] = (0, react_1.useState)(null);
|
|
36
|
-
const [blockActionToolHover, setBlockActionToolHover] = (0, react_1.useState)(false);
|
|
37
|
-
const [blockActionToolPlacements, setBlockActionToolPlacements] = (0, react_1.useState)([]);
|
|
38
|
-
const [blockDragDropTarget, setBlockDragDropTarget] = (0, react_1.useState)(null);
|
|
39
|
-
const [draggedBlockId, setDraggedBlockId] = (0, react_1.useState)(null);
|
|
40
|
-
const [focusedBlockId, setFocusedBlockId] = (0, react_1.useState)(null);
|
|
41
|
-
const [hoveredBlockId, setHoveredBlockId] = (0, react_1.useState)(null);
|
|
42
|
-
const [bodyActive, setBodyActive] = (0, react_1.useState)(false);
|
|
43
|
-
const [specialBlockToolHover, setSpecialBlockToolHover] = (0, react_1.useState)(false);
|
|
44
|
-
const [specialBlockToolTop, setSpecialBlockToolTop] = (0, react_1.useState)(0);
|
|
45
|
-
const [selectionActive, setSelectionActive] = (0, react_1.useState)(false);
|
|
46
|
-
const [selectionToolbarPosition, setSelectionToolbarPosition] = (0, react_1.useState)({
|
|
47
|
-
left: 0,
|
|
48
|
-
top: 0,
|
|
49
|
-
});
|
|
50
|
-
const [specialBlockToolVisible, setSpecialBlockToolVisible] = (0, react_1.useState)(false);
|
|
51
|
-
const sanitizedContentBlocks = (0, react_1.useMemo)(() => (0, richText_1.sanitizeRichTextBlocks)(contentBlocks), [contentBlocks]);
|
|
52
|
-
const editableHtml = (0, react_1.useMemo)(() => richTextBlocksToEditorHtml(sanitizedContentBlocks), [sanitizedContentBlocks]);
|
|
53
|
-
const stats = (0, react_1.useMemo)(() => (0, writingStats_1.getWritingStats)(sanitizedContentBlocks), [sanitizedContentBlocks]);
|
|
54
|
-
const transcriptionConfig = resolveTranscriptionConfig({
|
|
55
|
-
enabled: transcriptionEnabled,
|
|
56
|
-
language: transcriptionLanguage,
|
|
57
|
-
});
|
|
58
|
-
(0, react_1.useEffect)(() => {
|
|
59
|
-
const body = bodyRef.current;
|
|
60
|
-
if (body &&
|
|
61
|
-
body.innerHTML !== editableHtml &&
|
|
62
|
-
(!bodyActive || isEditorHtmlEmpty(editableHtml))) {
|
|
63
|
-
body.innerHTML = editableHtml;
|
|
64
|
-
updateBlockActionTools(body);
|
|
65
|
-
}
|
|
66
|
-
}, [bodyActive, editableHtml]);
|
|
67
|
-
(0, react_1.useEffect)(() => {
|
|
68
|
-
const handleResize = () => updateBlockActionTools();
|
|
69
|
-
window.addEventListener("resize", handleResize);
|
|
70
|
-
return () => window.removeEventListener("resize", handleResize);
|
|
71
|
-
}, []);
|
|
72
|
-
function emitBodyChange() {
|
|
73
|
-
const body = bodyRef.current;
|
|
74
|
-
if (!body) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
onContentBlocksChange(readBlocksFromEditor(body));
|
|
78
|
-
updateBlockActionTools(body);
|
|
79
|
-
}
|
|
80
|
-
function handleBodyInput() {
|
|
81
|
-
if (!applyActiveTextBlockShortcut(bodyRef.current)) {
|
|
82
|
-
normalizeActiveTextBlockTypography(bodyRef.current);
|
|
83
|
-
}
|
|
84
|
-
emitBodyChange();
|
|
85
|
-
syncSelectionState();
|
|
86
|
-
}
|
|
87
|
-
function handleBodyEnter(event) {
|
|
88
|
-
const body = bodyRef.current;
|
|
89
|
-
const selection = window.getSelection();
|
|
90
|
-
if (!body || !selection || selection.rangeCount === 0) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
const range = selection.getRangeAt(0);
|
|
94
|
-
if (!body.contains(range.commonAncestorContainer)) {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
const activeBlock = getActiveEditorBlock(range, body);
|
|
98
|
-
if (!activeBlock) {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
if (activeBlock.hasAttribute("data-rich-text-checkbox")) {
|
|
102
|
-
const label = activeBlock.querySelector("[data-checkbox-label]");
|
|
103
|
-
if (!label || !label.contains(range.startContainer)) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
if (!selection.isCollapsed) {
|
|
107
|
-
range.deleteContents();
|
|
108
|
-
}
|
|
109
|
-
if (getCheckboxEnterAction(event) === "line-break") {
|
|
110
|
-
insertLineBreakAtRange(range, {
|
|
111
|
-
padTrailingBreak: shouldPadTrailingLineBreak(getTextAfterRange(label, range)),
|
|
112
|
-
});
|
|
113
|
-
emitBodyChange();
|
|
114
|
-
syncSelectionState();
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
const splitOffset = getTextOffsetWithin(label, range);
|
|
118
|
-
const splitMarkdown = splitCheckboxMarkdownAtTextOffset((0, richText_1.editorHtmlToMarkdown)(normalizeSoftBreakHtml(label.innerHTML)), splitOffset);
|
|
119
|
-
label.innerHTML = textBlockMarkdownToEditorHtml(splitMarkdown.before);
|
|
120
|
-
activeBlock.insertAdjacentHTML("afterend", checkboxBlockToEditorHtml({
|
|
121
|
-
checked: false,
|
|
122
|
-
id: (0, richText_1.createRichTextBlockId)(),
|
|
123
|
-
markdown: splitMarkdown.after,
|
|
124
|
-
type: "checkbox",
|
|
125
|
-
}));
|
|
126
|
-
focusBlockStart(activeBlock.nextElementSibling?.querySelector("[data-checkbox-label]") ?? null);
|
|
127
|
-
emitBodyChange();
|
|
128
|
-
syncSelectionState();
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
if (event.shiftKey && isSoftBreakTextBlock(activeBlock)) {
|
|
132
|
-
if (!selection.isCollapsed) {
|
|
133
|
-
range.deleteContents();
|
|
134
|
-
}
|
|
135
|
-
insertLineBreakAtRange(range, {
|
|
136
|
-
padTrailingBreak: shouldPadTrailingLineBreak(getTextAfterRange(activeBlock, range)),
|
|
137
|
-
});
|
|
138
|
-
emitBodyChange();
|
|
139
|
-
syncSelectionState();
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
function handleBodyBackspace() {
|
|
145
|
-
const body = bodyRef.current;
|
|
146
|
-
const selection = window.getSelection();
|
|
147
|
-
if (!body || !selection || selection.rangeCount === 0) {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
const range = selection.getRangeAt(0);
|
|
151
|
-
if (!selection.isCollapsed ||
|
|
152
|
-
!body.contains(range.commonAncestorContainer)) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
const activeBlock = getActiveEditorBlock(range, body);
|
|
156
|
-
if (!activeBlock?.hasAttribute("data-rich-text-checkbox")) {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
const label = activeBlock.querySelector("[data-checkbox-label]");
|
|
160
|
-
if (!label || !label.contains(range.startContainer)) {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
if (getTextOffsetWithin(label, range) !== 0) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
const blockId = activeBlock.getAttribute("data-block-id");
|
|
167
|
-
if (!blockId) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
applyContentBlocksChange((0, blockActions_1.convertCheckboxBlockToParagraph)(readBlocksFromEditor(body), blockId), { focusBlockId: blockId });
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
function syncSelectionState() {
|
|
174
|
-
const body = bodyRef.current;
|
|
175
|
-
const selection = window.getSelection();
|
|
176
|
-
if (!body) {
|
|
177
|
-
setSelectionActive(false);
|
|
178
|
-
setBlockActionToolPlacements([]);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
updateBlockActionTools(body);
|
|
182
|
-
if (!selection || selection.rangeCount === 0) {
|
|
183
|
-
setSelectionActive(false);
|
|
184
|
-
setSpecialBlockTarget(null);
|
|
185
|
-
setFocusedBlockId(null);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
const range = selection.getRangeAt(0);
|
|
189
|
-
if (!body.contains(range.commonAncestorContainer)) {
|
|
190
|
-
setSelectionActive(false);
|
|
191
|
-
setSpecialBlockTarget(null);
|
|
192
|
-
setFocusedBlockId(null);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const bodyFocused = document.activeElement === body || body.contains(document.activeElement);
|
|
196
|
-
const rect = getVisibleRangeRect(range);
|
|
197
|
-
const bodyRect = body.getBoundingClientRect();
|
|
198
|
-
const nextTop = rect.height ? rect.top - bodyRect.top + body.scrollTop : 0;
|
|
199
|
-
const activeBlock = getActiveEditorBlock(range, body);
|
|
200
|
-
setFocusedBlockId(bodyFocused && activeBlock ? ensureEditorBlockId(activeBlock) : null);
|
|
201
|
-
if (!selection.isCollapsed && rect.width && rect.height) {
|
|
202
|
-
setSelectionActive(true);
|
|
203
|
-
setSpecialBlockTarget(null);
|
|
204
|
-
setSelectionToolbarPosition({
|
|
205
|
-
left: clamp(rect.left - bodyRect.left + rect.width / 2, 92, Math.max(92, body.clientWidth - 92)),
|
|
206
|
-
top: Math.max(0, nextTop - 58),
|
|
207
|
-
});
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
setSelectionActive(false);
|
|
211
|
-
setSpecialBlockTarget(isSpecialBlockInsertTarget(activeBlock) &&
|
|
212
|
-
!activeBlock?.hasAttribute("data-suppress-special-block-tool")
|
|
213
|
-
? activeBlock
|
|
214
|
-
: null, body);
|
|
215
|
-
}
|
|
216
|
-
function runSelectionCommand(command) {
|
|
217
|
-
bodyRef.current?.focus();
|
|
218
|
-
if (command === "link") {
|
|
219
|
-
const href = window.prompt("Link URL", "https://");
|
|
220
|
-
if (href) {
|
|
221
|
-
document.execCommand("createLink", false, href);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
else if (command === "heading") {
|
|
225
|
-
document.execCommand("formatBlock", false, "h2");
|
|
226
|
-
}
|
|
227
|
-
else if (command === "quote") {
|
|
228
|
-
document.execCommand("formatBlock", false, "blockquote");
|
|
229
|
-
}
|
|
230
|
-
else if (command === "code") {
|
|
231
|
-
wrapSelectionWith("code");
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
document.execCommand(command, false);
|
|
235
|
-
}
|
|
236
|
-
emitBodyChange();
|
|
237
|
-
syncSelectionState();
|
|
238
|
-
}
|
|
239
|
-
function insertBlock(action) {
|
|
240
|
-
const body = bodyRef.current;
|
|
241
|
-
if (!body) {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
body.focus();
|
|
245
|
-
const targetBlock = specialBlockTargetRef.current;
|
|
246
|
-
const html = action.html;
|
|
247
|
-
const nextEditableBlockHtml = `<p data-block-id="${escapeHtmlAttribute((0, richText_1.createRichTextBlockId)())}"> </p>`;
|
|
248
|
-
let insertedBlock = null;
|
|
249
|
-
let nextEditableBlock = null;
|
|
250
|
-
if (targetBlock && body.contains(targetBlock)) {
|
|
251
|
-
targetBlock.insertAdjacentHTML("afterend", `${html}${nextEditableBlockHtml}`);
|
|
252
|
-
insertedBlock = targetBlock.nextElementSibling;
|
|
253
|
-
nextEditableBlock = insertedBlock?.nextElementSibling ?? null;
|
|
254
|
-
targetBlock.remove();
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
body.insertAdjacentHTML("beforeend", `${html}${nextEditableBlockHtml}`);
|
|
258
|
-
nextEditableBlock = body.lastElementChild;
|
|
259
|
-
insertedBlock = nextEditableBlock?.previousElementSibling ?? null;
|
|
260
|
-
}
|
|
261
|
-
setSpecialBlockToolHover(false);
|
|
262
|
-
if (action.selectDefault) {
|
|
263
|
-
selectBlockContents(insertedBlock);
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
focusBlockStart(nextEditableBlock);
|
|
267
|
-
}
|
|
268
|
-
emitBodyChange();
|
|
269
|
-
syncSelectionState();
|
|
270
|
-
}
|
|
271
|
-
function insertTranscriptText(text) {
|
|
272
|
-
const body = bodyRef.current;
|
|
273
|
-
const transcript = text.trim();
|
|
274
|
-
if (!body || !transcript) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
body.focus();
|
|
278
|
-
const selection = window.getSelection();
|
|
279
|
-
if (selection &&
|
|
280
|
-
selection.rangeCount > 0 &&
|
|
281
|
-
body.contains(selection.getRangeAt(0).commonAncestorContainer)) {
|
|
282
|
-
document.execCommand("insertText", false, appendTranscriptText("", transcript));
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
const currentText = body.textContent ?? "";
|
|
286
|
-
body.insertAdjacentText("beforeend", appendTranscriptText(currentText, transcript).slice(currentText.length));
|
|
287
|
-
focusBlockEnd(body);
|
|
288
|
-
}
|
|
289
|
-
emitBodyChange();
|
|
290
|
-
syncSelectionState();
|
|
291
|
-
}
|
|
292
|
-
function focusBodyStart() {
|
|
293
|
-
window.requestAnimationFrame(() => {
|
|
294
|
-
const body = bodyRef.current;
|
|
295
|
-
if (!body) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
body.focus();
|
|
299
|
-
focusBlockStart(body.firstElementChild ?? body);
|
|
300
|
-
setBodyActive(true);
|
|
301
|
-
syncSelectionState();
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
function focusTitleEnd() {
|
|
305
|
-
window.requestAnimationFrame(() => {
|
|
306
|
-
titleRef.current?.focus();
|
|
307
|
-
titleRef.current?.setSelectionRange(title.length, title.length);
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
function setSpecialBlockTarget(block, body = bodyRef.current) {
|
|
311
|
-
specialBlockTargetRef.current = block;
|
|
312
|
-
setSpecialBlockToolVisible(!!block);
|
|
313
|
-
if (block && body) {
|
|
314
|
-
setSpecialBlockToolTop(getBlockInsertTop(block, body) ?? 0);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
async function handleBlockAction(action, blockId) {
|
|
318
|
-
const body = bodyRef.current;
|
|
319
|
-
if (!body) {
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
const blocks = readBlocksFromEditor(body);
|
|
323
|
-
const block = blocks.find((currentBlock) => currentBlock.id === blockId);
|
|
324
|
-
if (!block) {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (action === "copy" || action === "cut") {
|
|
328
|
-
const clipboardText = (0, blockActions_1.blockActionToClipboardText)(blocks, blockId) ||
|
|
329
|
-
(0, blockActions_1.blockToClipboardText)(block);
|
|
330
|
-
const copied = await writeTextToClipboard(clipboardText);
|
|
331
|
-
if (!copied || action === "copy") {
|
|
332
|
-
setActiveBlockActionMenuId(null);
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
if (action === "cut" || action === "delete") {
|
|
337
|
-
const deletionFocusTarget = getDeletionFocusTarget(blocks, blockId);
|
|
338
|
-
applyContentBlocksChange((0, blockActions_1.deleteBlockById)(blocks, blockId), {
|
|
339
|
-
focusBlockId: deletionFocusTarget?.blockId,
|
|
340
|
-
focusBlockPosition: deletionFocusTarget?.position,
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
setActiveBlockActionMenuId(null);
|
|
344
|
-
}
|
|
345
|
-
function handleBlockPointerDragStart(blockId, event) {
|
|
346
|
-
setDraggedBlockId(blockId);
|
|
347
|
-
setBlockDragDropTarget(null);
|
|
348
|
-
setActiveBlockActionMenuId(null);
|
|
349
|
-
blockPointerDragRef.current = {
|
|
350
|
-
blockId,
|
|
351
|
-
dropTarget: null,
|
|
352
|
-
started: false,
|
|
353
|
-
x: event.clientX,
|
|
354
|
-
y: event.clientY,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
function handleBlockPointerDragMove(event) {
|
|
358
|
-
const pointerDrag = blockPointerDragRef.current;
|
|
359
|
-
const body = bodyRef.current;
|
|
360
|
-
if (!pointerDrag || !body) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
const movedDistance = Math.hypot(event.clientX - pointerDrag.x, event.clientY - pointerDrag.y);
|
|
364
|
-
if (movedDistance <= 3) {
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
if (!pointerDrag.started) {
|
|
368
|
-
pointerDrag.started = true;
|
|
369
|
-
}
|
|
370
|
-
const bodyRect = body.getBoundingClientRect();
|
|
371
|
-
const nextPlacements = getBlockActionToolPlacements(body);
|
|
372
|
-
syncBlockActionToolPlacements(nextPlacements);
|
|
373
|
-
pointerDrag.dropTarget = (0, blockActionToolState_1.getPointerDropTarget)(nextPlacements, pointerDrag.blockId, event.clientY - bodyRect.top + body.scrollTop);
|
|
374
|
-
setBlockDragDropTarget(pointerDrag.dropTarget);
|
|
375
|
-
}
|
|
376
|
-
function handleBlockPointerDragEnd(event) {
|
|
377
|
-
const pointerDrag = blockPointerDragRef.current;
|
|
378
|
-
const body = bodyRef.current;
|
|
379
|
-
blockPointerDragRef.current = null;
|
|
380
|
-
setDraggedBlockId(null);
|
|
381
|
-
setBlockDragDropTarget(null);
|
|
382
|
-
if (!pointerDrag?.started || !body || !pointerDrag.dropTarget) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
event.preventDefault();
|
|
386
|
-
applyContentBlocksChange((0, blockActions_1.reorderBlock)(readBlocksFromEditor(body), pointerDrag.blockId, pointerDrag.dropTarget.targetBlockId, pointerDrag.dropTarget.placement), { focusBody: false });
|
|
387
|
-
}
|
|
388
|
-
function applyContentBlocksChange(nextBlocks, options = {}) {
|
|
389
|
-
const body = bodyRef.current;
|
|
390
|
-
if (!body) {
|
|
391
|
-
onContentBlocksChange(nextBlocks);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
body.innerHTML = richTextBlocksToEditorHtml(nextBlocks);
|
|
395
|
-
onContentBlocksChange(nextBlocks);
|
|
396
|
-
updateBlockActionTools(body);
|
|
397
|
-
if (options.focusBody ?? true) {
|
|
398
|
-
body.focus();
|
|
399
|
-
}
|
|
400
|
-
if (options.focusBlockId) {
|
|
401
|
-
const focusBlock = body.querySelector(`[data-block-id="${cssEscape(options.focusBlockId)}"]`);
|
|
402
|
-
if (focusBlock) {
|
|
403
|
-
options.focusBlockPosition === "end"
|
|
404
|
-
? focusBlockEnd(focusBlock)
|
|
405
|
-
: focusBlockStart(focusBlock);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
focusBlockStart(body.firstElementChild ?? body);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
syncSelectionState();
|
|
412
|
-
}
|
|
413
|
-
function updateBlockActionTools(body = bodyRef.current) {
|
|
414
|
-
if (!body) {
|
|
415
|
-
setBlockActionToolPlacements([]);
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
const nextPlacements = getBlockActionToolPlacements(body);
|
|
419
|
-
syncBlockActionToolPlacements(nextPlacements);
|
|
420
|
-
}
|
|
421
|
-
function syncBlockActionToolPlacements(nextPlacements) {
|
|
422
|
-
setBlockActionToolPlacements((currentPlacements) => {
|
|
423
|
-
return areBlockActionToolPlacementsEqual(currentPlacements, nextPlacements)
|
|
424
|
-
? currentPlacements
|
|
425
|
-
: nextPlacements;
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
const showSpecialBlockTool = (bodyActive || specialBlockToolHover) &&
|
|
429
|
-
!disabled &&
|
|
430
|
-
specialBlockToolVisible;
|
|
431
|
-
const visibleBlockActionToolPlacements = (0, blockActionToolState_1.getVisibleBlockActionToolPlacements)(blockActionToolPlacements, {
|
|
432
|
-
activeBlockId: activeBlockActionMenuId,
|
|
433
|
-
draggedBlockId,
|
|
434
|
-
focusedBlockId,
|
|
435
|
-
hoveredBlockId,
|
|
436
|
-
});
|
|
437
|
-
const showBlockActionTools = !disabled && visibleBlockActionToolPlacements.length > 0;
|
|
438
|
-
const uniqueBlockActionToolPlacements = (0, blockActionToolState_1.getUniqueBlockActionToolPlacements)(blockActionToolPlacements);
|
|
439
|
-
const blockDragIndicatorTop = draggedBlockId && blockDragDropTarget
|
|
440
|
-
? getBlockDragIndicatorTop(blockActionToolPlacements, blockDragDropTarget)
|
|
441
|
-
: null;
|
|
442
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "bayon-rte-stack", children: [(0, jsx_runtime_1.jsx)(RichTextStyles_1.RichTextStyleScope, {}), (0, jsx_runtime_1.jsxs)(RichTextDocumentSurface_1.RichTextDocumentSurface, { background: documentBackground, children: [(0, jsx_runtime_1.jsx)(RichTextTitleInput_1.RichTextTitleInput, { disabled: disabled, inputRef: titleRef, label: titleLabel, onArrowDown: focusBodyStart, onChange: onTitleChange, required: required, title: title, validationMessage: titleValidationMessage }), (0, jsx_runtime_1.jsxs)("div", { className: "bayon-rte-body-shell", onMouseDownCapture: (event) => {
|
|
443
|
-
if (activeBlockActionMenuId &&
|
|
444
|
-
event.target instanceof Element &&
|
|
445
|
-
!event.target.closest("[data-block-action-tool]")) {
|
|
446
|
-
setActiveBlockActionMenuId(null);
|
|
447
|
-
}
|
|
448
|
-
}, onMouseEnter: () => setBodyActive(true), onMouseLeave: () => {
|
|
449
|
-
const body = bodyRef.current;
|
|
450
|
-
const bodyStillFocused = !!body &&
|
|
451
|
-
(document.activeElement === body ||
|
|
452
|
-
body.contains(document.activeElement));
|
|
453
|
-
if (!specialBlockToolHover &&
|
|
454
|
-
!blockActionToolHover &&
|
|
455
|
-
!activeBlockActionMenuId &&
|
|
456
|
-
!draggedBlockId &&
|
|
457
|
-
!bodyStillFocused) {
|
|
458
|
-
setBodyActive(false);
|
|
459
|
-
}
|
|
460
|
-
if (!activeBlockActionMenuId && !draggedBlockId) {
|
|
461
|
-
setHoveredBlockId(null);
|
|
462
|
-
}
|
|
463
|
-
}, onMouseMove: (event) => {
|
|
464
|
-
const body = bodyRef.current;
|
|
465
|
-
updateBlockActionTools(body);
|
|
466
|
-
setHoveredBlockId(body
|
|
467
|
-
? (getHoveredEditorBlockId(event.clientX, event.clientY, body) ??
|
|
468
|
-
getHoveredBlockActionLaneId(event.clientX, event.clientY, body, blockActionToolPlacements))
|
|
469
|
-
: null);
|
|
470
|
-
}, children: [transcriptionConfig.enabled && !disabled ? ((0, jsx_runtime_1.jsx)(TranscriptionControl_1.TranscriptionControl, { language: transcriptionConfig.language, onTranscript: insertTranscriptText })) : null, selectionActive && !disabled ? ((0, jsx_runtime_1.jsx)(SelectionFormatToolbar_1.SelectionFormatToolbar, { left: selectionToolbarPosition.left, onAction: runSelectionCommand, top: selectionToolbarPosition.top })) : null, !disabled ? ((0, jsx_runtime_1.jsx)(SpecialBlockTool_1.SpecialBlockTool, { onHoverChange: setSpecialBlockToolHover, onInsert: insertBlock, top: specialBlockToolTop, toolHover: specialBlockToolHover, visible: showSpecialBlockTool })) : null, !disabled
|
|
471
|
-
? uniqueBlockActionToolPlacements.map((placement) => ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": "true", className: "bayon-rte-block-hover-zone", onMouseEnter: () => setHoveredBlockId(placement.blockId), onMouseLeave: () => {
|
|
472
|
-
if (!activeBlockActionMenuId && !draggedBlockId) {
|
|
473
|
-
setHoveredBlockId(null);
|
|
474
|
-
}
|
|
475
|
-
}, style: {
|
|
476
|
-
"--bayon-rte-block-hover-zone-height": `${Math.max(34, placement.bottom - placement.top)}px`,
|
|
477
|
-
"--bayon-rte-block-hover-zone-top": `${placement.top}px`,
|
|
478
|
-
} }, `hover-${placement.blockId}`)))
|
|
479
|
-
: null, showBlockActionTools
|
|
480
|
-
? visibleBlockActionToolPlacements.map((placement) => ((0, jsx_runtime_1.jsx)(BlockActionTool_1.BlockActionTool, { activeBlockId: activeBlockActionMenuId, draggedBlockId: draggedBlockId, onAction: (action, blockId) => {
|
|
481
|
-
void handleBlockAction(action, blockId);
|
|
482
|
-
}, onHoverChange: setBlockActionToolHover, onPointerDragEnd: handleBlockPointerDragEnd, onPointerDragMove: handleBlockPointerDragMove, onPointerDragStart: handleBlockPointerDragStart, onToggleMenu: (blockId) => {
|
|
483
|
-
setActiveBlockActionMenuId((currentBlockId) => {
|
|
484
|
-
return currentBlockId === blockId ? null : blockId;
|
|
485
|
-
});
|
|
486
|
-
}, placement: placement }, placement.blockId)))
|
|
487
|
-
: null, blockDragIndicatorTop !== null ? ((0, jsx_runtime_1.jsx)("div", { "aria-hidden": "true", className: "bayon-rte-block-drag-indicator", style: {
|
|
488
|
-
"--bayon-rte-block-drag-indicator-top": `${blockDragIndicatorTop}px`,
|
|
489
|
-
} })) : null, (0, jsx_runtime_1.jsx)(RichTextBody_1.RichTextBody, { bodyRef: bodyRef, disabled: disabled, label: bodyLabel, onArrowUpFromFirstLine: focusTitleEnd, onBackspace: handleBodyBackspace, onEnter: handleBodyEnter, onInput: handleBodyInput, onSelectionChange: syncSelectionState, onShortcutCommand: runSelectionCommand, onFocus: () => {
|
|
490
|
-
setBodyActive(true);
|
|
491
|
-
syncSelectionState();
|
|
492
|
-
} })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bayon-rte-meta", children: [helperText ? (0, jsx_runtime_1.jsx)("small", { children: helperText }) : null, (0, jsx_runtime_1.jsxs)("small", { children: [stats.wordCount, " words / ", stats.readingMinutes, " min read"] })] })] }));
|
|
493
|
-
}
|
|
494
|
-
async function writeTextToClipboard(value) {
|
|
495
|
-
if (!value.trim()) {
|
|
496
|
-
return false;
|
|
497
|
-
}
|
|
498
|
-
if (navigator.clipboard?.writeText) {
|
|
499
|
-
try {
|
|
500
|
-
await navigator.clipboard.writeText(value);
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
catch {
|
|
504
|
-
// Fall through to the textarea fallback below.
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
const textarea = document.createElement("textarea");
|
|
508
|
-
textarea.value = value;
|
|
509
|
-
textarea.setAttribute("readonly", "");
|
|
510
|
-
textarea.style.left = "-9999px";
|
|
511
|
-
textarea.style.position = "fixed";
|
|
512
|
-
textarea.style.top = "0";
|
|
513
|
-
document.body.appendChild(textarea);
|
|
514
|
-
textarea.select();
|
|
515
|
-
try {
|
|
516
|
-
return document.execCommand("copy");
|
|
517
|
-
}
|
|
518
|
-
finally {
|
|
519
|
-
textarea.remove();
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
function wrapSelectionWith(tagName) {
|
|
523
|
-
const selection = window.getSelection();
|
|
524
|
-
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
const range = selection.getRangeAt(0);
|
|
528
|
-
const wrapper = document.createElement(tagName);
|
|
529
|
-
wrapper.appendChild(range.extractContents());
|
|
530
|
-
range.insertNode(wrapper);
|
|
531
|
-
selection.removeAllRanges();
|
|
532
|
-
const nextRange = document.createRange();
|
|
533
|
-
nextRange.selectNodeContents(wrapper);
|
|
534
|
-
selection.addRange(nextRange);
|
|
535
|
-
}
|
|
536
|
-
function getVisibleRangeRect(range) {
|
|
537
|
-
const rect = range.getBoundingClientRect();
|
|
538
|
-
if (rect.width || rect.height) {
|
|
539
|
-
return rect;
|
|
540
|
-
}
|
|
541
|
-
return (Array.from(range.getClientRects()).find((clientRect) => {
|
|
542
|
-
return clientRect.width || clientRect.height;
|
|
543
|
-
}) ?? rect);
|
|
544
|
-
}
|
|
545
|
-
function getActiveEditorBlock(range, body) {
|
|
546
|
-
const node = range.startContainer.nodeType === Node.TEXT_NODE
|
|
547
|
-
? range.startContainer.parentElement
|
|
548
|
-
: range.startContainer instanceof Element
|
|
549
|
-
? range.startContainer
|
|
550
|
-
: null;
|
|
551
|
-
const block = node?.closest("p, div, h2, blockquote, pre, figure, hr, ul, ol");
|
|
552
|
-
return block && body.contains(block) ? block : null;
|
|
553
|
-
}
|
|
554
|
-
function getBlockInsertTop(block, body) {
|
|
555
|
-
if (!block) {
|
|
556
|
-
return null;
|
|
557
|
-
}
|
|
558
|
-
const bodyRect = body.getBoundingClientRect();
|
|
559
|
-
const blockRect = block.getBoundingClientRect();
|
|
560
|
-
if (!blockRect.height && !blockRect.width) {
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
return Math.max(0, blockRect.top - bodyRect.top + body.scrollTop);
|
|
564
|
-
}
|
|
565
|
-
function getBlockActionToolPlacements(body) {
|
|
566
|
-
const placements = [];
|
|
567
|
-
const children = Array.from(body.children);
|
|
568
|
-
for (let index = 0; index < children.length; index += 1) {
|
|
569
|
-
const element = children[index];
|
|
570
|
-
if (!isReadableEditorBlock(element)) {
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
const block = readBlockFromElement(element);
|
|
574
|
-
if (!element.getAttribute("data-block-id")) {
|
|
575
|
-
ensureEditorBlockId(element, block.id);
|
|
576
|
-
}
|
|
577
|
-
const position = getBlockPosition(element, body);
|
|
578
|
-
if (position && (0, blockActions_1.isBlockActionTarget)(block)) {
|
|
579
|
-
placements.push({
|
|
580
|
-
blockId: block.id,
|
|
581
|
-
bottom: position.bottom,
|
|
582
|
-
top: position.top,
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return placements;
|
|
587
|
-
}
|
|
588
|
-
function getBlockPosition(block, body) {
|
|
589
|
-
if (!block) {
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
const bodyRect = body.getBoundingClientRect();
|
|
593
|
-
const blockRect = block.getBoundingClientRect();
|
|
594
|
-
if (!blockRect.height && !blockRect.width) {
|
|
595
|
-
return null;
|
|
596
|
-
}
|
|
597
|
-
const top = Math.max(0, blockRect.top - bodyRect.top + body.scrollTop);
|
|
598
|
-
return {
|
|
599
|
-
bottom: top + blockRect.height,
|
|
600
|
-
top,
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
function getBlockDragIndicatorTop(placements, dropTarget) {
|
|
604
|
-
const targetPlacement = placements.find((placement) => {
|
|
605
|
-
return placement.blockId === dropTarget.targetBlockId;
|
|
606
|
-
});
|
|
607
|
-
if (!targetPlacement) {
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
return dropTarget.placement === "after"
|
|
611
|
-
? targetPlacement.bottom
|
|
612
|
-
: targetPlacement.top;
|
|
613
|
-
}
|
|
614
|
-
function getHoveredEditorBlockId(clientX, clientY, body) {
|
|
615
|
-
const element = document.elementFromPoint(clientX, clientY);
|
|
616
|
-
const block = element?.closest("p, div, h2, blockquote, pre, figure, hr, ul, ol");
|
|
617
|
-
if (!block || !body.contains(block) || !isReadableEditorBlock(block)) {
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
const richTextBlock = readBlockFromElement(block);
|
|
621
|
-
return (0, blockActions_1.isBlockActionTarget)(richTextBlock) ? richTextBlock.id : null;
|
|
622
|
-
}
|
|
623
|
-
function getHoveredBlockActionLaneId(clientX, clientY, body, placements) {
|
|
624
|
-
const bodyRect = body.getBoundingClientRect();
|
|
625
|
-
const laneX = clientX - bodyRect.left;
|
|
626
|
-
if (laneX < -54 || laneX > 28) {
|
|
627
|
-
return null;
|
|
628
|
-
}
|
|
629
|
-
const pointerTop = clientY - bodyRect.top + body.scrollTop;
|
|
630
|
-
const placement = placements.find((currentPlacement) => {
|
|
631
|
-
return (pointerTop >= currentPlacement.top - 10 &&
|
|
632
|
-
pointerTop <= currentPlacement.bottom + 10);
|
|
633
|
-
});
|
|
634
|
-
return placement?.blockId ?? null;
|
|
635
|
-
}
|
|
636
|
-
function isCheckboxEditorBlock(element) {
|
|
637
|
-
return !!element?.hasAttribute("data-rich-text-checkbox");
|
|
638
|
-
}
|
|
639
|
-
function isSoftBreakTextBlock(element) {
|
|
640
|
-
return ["P", "H2", "BLOCKQUOTE"].includes(element.tagName);
|
|
641
|
-
}
|
|
642
|
-
function areBlockActionToolPlacementsEqual(previousPlacements, nextPlacements) {
|
|
643
|
-
return (previousPlacements.length === nextPlacements.length &&
|
|
644
|
-
previousPlacements.every((placement, index) => {
|
|
645
|
-
const nextPlacement = nextPlacements[index];
|
|
646
|
-
return (placement.blockId === nextPlacement?.blockId &&
|
|
647
|
-
placement.top === nextPlacement.top &&
|
|
648
|
-
placement.bottom === nextPlacement.bottom);
|
|
649
|
-
}));
|
|
650
|
-
}
|
|
651
|
-
function getDeletionFocusTarget(blocks, blockId) {
|
|
652
|
-
const deletedBlockIndex = blocks.findIndex((block) => block.id === blockId);
|
|
653
|
-
if (deletedBlockIndex === -1) {
|
|
654
|
-
return undefined;
|
|
655
|
-
}
|
|
656
|
-
const previousBlockId = blocks[deletedBlockIndex - 1]?.id;
|
|
657
|
-
if (previousBlockId) {
|
|
658
|
-
return { blockId: previousBlockId, position: "end" };
|
|
659
|
-
}
|
|
660
|
-
return {
|
|
661
|
-
blockId: blocks[deletedBlockIndex + 1]?.id ?? "block-empty",
|
|
662
|
-
position: "start",
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
function focusBlockStart(block) {
|
|
666
|
-
if (!block) {
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
const range = document.createRange();
|
|
670
|
-
range.setStart(block, 0);
|
|
671
|
-
range.collapse(true);
|
|
672
|
-
const selection = window.getSelection();
|
|
673
|
-
selection?.removeAllRanges();
|
|
674
|
-
selection?.addRange(range);
|
|
675
|
-
}
|
|
676
|
-
function selectBlockContents(block) {
|
|
677
|
-
if (!block) {
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const selectionTarget = block.querySelector("p, code") ?? block;
|
|
681
|
-
const range = document.createRange();
|
|
682
|
-
range.selectNodeContents(selectionTarget);
|
|
683
|
-
const selection = window.getSelection();
|
|
684
|
-
selection?.removeAllRanges();
|
|
685
|
-
selection?.addRange(range);
|
|
686
|
-
}
|
|
687
|
-
function isSpecialBlockInsertTarget(block) {
|
|
688
|
-
if (!block || !["DIV", "P"].includes(block.tagName)) {
|
|
689
|
-
return false;
|
|
690
|
-
}
|
|
691
|
-
if (block.hasAttribute("data-rich-text-checkbox")) {
|
|
692
|
-
return false;
|
|
693
|
-
}
|
|
694
|
-
return Array.from(block.childNodes).every((child) => {
|
|
695
|
-
if (child.nodeType === Node.TEXT_NODE) {
|
|
696
|
-
return (child.textContent ?? "").trim() === "";
|
|
697
|
-
}
|
|
698
|
-
if (child.nodeType !== Node.ELEMENT_NODE) {
|
|
699
|
-
return true;
|
|
700
|
-
}
|
|
701
|
-
const element = child;
|
|
702
|
-
return (element.tagName === "BR" || (element.textContent ?? "").trim() === "");
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
function clamp(value, min, max) {
|
|
706
|
-
return Math.min(Math.max(value, min), max);
|
|
707
|
-
}
|
|
708
|
-
function richTextBlocksToEditorHtml(blocks) {
|
|
709
|
-
return (0, richText_1.sanitizeRichTextBlocks)(blocks).map(richTextBlockToEditorHtml).join("");
|
|
710
|
-
}
|
|
711
|
-
function richTextBlockToEditorHtml(block) {
|
|
712
|
-
const blockId = escapeHtmlAttribute(block.id);
|
|
713
|
-
if (block.type === "heading") {
|
|
714
|
-
return `<h2 data-block-id="${blockId}">${textBlockMarkdownToEditorHtml(block.markdown)}</h2>`;
|
|
715
|
-
}
|
|
716
|
-
if (block.type === "quote") {
|
|
717
|
-
return `<blockquote data-block-id="${blockId}">${textBlockMarkdownToEditorHtml(block.markdown)}</blockquote>`;
|
|
718
|
-
}
|
|
719
|
-
if (block.type === "code") {
|
|
720
|
-
return `<pre data-block-id="${blockId}"><code>${escapeHtmlText(block.text)}</code></pre>`;
|
|
721
|
-
}
|
|
722
|
-
if (block.type === "divider") {
|
|
723
|
-
return `<hr data-block-id="${blockId}">`;
|
|
724
|
-
}
|
|
725
|
-
if (block.type === "checkbox") {
|
|
726
|
-
return checkboxBlockToEditorHtml(block);
|
|
727
|
-
}
|
|
728
|
-
if (block.type === "image") {
|
|
729
|
-
return `<figure data-block-id="${blockId}" data-placeholder="image"><p>${block.alt ?? "Image placeholder"}</p></figure>`;
|
|
730
|
-
}
|
|
731
|
-
return `<p data-block-id="${blockId}">${textBlockMarkdownToEditorHtml(block.markdown)}</p>`;
|
|
732
|
-
}
|
|
733
|
-
function textBlockMarkdownToEditorHtml(markdown) {
|
|
734
|
-
return (0, richText_1.markdownToEditorHtml)(markdown) || " ";
|
|
735
|
-
}
|
|
736
|
-
function readBlocksFromEditor(body) {
|
|
737
|
-
const blocks = Array.from(body.childNodes).flatMap((node) => {
|
|
738
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
739
|
-
const markdown = (0, richText_1.editorHtmlToMarkdown)(node.textContent ?? "");
|
|
740
|
-
return markdown
|
|
741
|
-
? [
|
|
742
|
-
{
|
|
743
|
-
id: (0, richText_1.createRichTextBlockId)(),
|
|
744
|
-
type: "paragraph",
|
|
745
|
-
markdown,
|
|
746
|
-
},
|
|
747
|
-
]
|
|
748
|
-
: [];
|
|
749
|
-
}
|
|
750
|
-
if (!(node instanceof Element)) {
|
|
751
|
-
return [];
|
|
752
|
-
}
|
|
753
|
-
if (isReadableEditorBlock(node)) {
|
|
754
|
-
return [readBlockFromElement(node)];
|
|
755
|
-
}
|
|
756
|
-
const markdown = (0, richText_1.editorHtmlToMarkdown)(normalizeSoftBreakHtml(node.innerHTML || node.textContent || ""));
|
|
757
|
-
return markdown
|
|
758
|
-
? [
|
|
759
|
-
{
|
|
760
|
-
id: (0, richText_1.createRichTextBlockId)(),
|
|
761
|
-
type: "paragraph",
|
|
762
|
-
markdown,
|
|
763
|
-
},
|
|
764
|
-
]
|
|
765
|
-
: [];
|
|
766
|
-
});
|
|
767
|
-
if (blocks.length === 0 && (body.textContent ?? "").trim()) {
|
|
768
|
-
return (0, richText_1.sanitizeRichTextBlocks)([
|
|
769
|
-
{
|
|
770
|
-
id: (0, richText_1.createRichTextBlockId)(),
|
|
771
|
-
type: "paragraph",
|
|
772
|
-
markdown: (0, richText_1.editorHtmlToMarkdown)(normalizeSoftBreakHtml(body.innerHTML)),
|
|
773
|
-
},
|
|
774
|
-
]);
|
|
775
|
-
}
|
|
776
|
-
return (0, richText_1.sanitizeRichTextBlocks)(blocks);
|
|
777
|
-
}
|
|
778
|
-
function isReadableEditorBlock(element) {
|
|
779
|
-
return [
|
|
780
|
-
"P",
|
|
781
|
-
"DIV",
|
|
782
|
-
"H2",
|
|
783
|
-
"BLOCKQUOTE",
|
|
784
|
-
"PRE",
|
|
785
|
-
"FIGURE",
|
|
786
|
-
"HR",
|
|
787
|
-
"LABEL",
|
|
788
|
-
].includes(element.tagName);
|
|
789
|
-
}
|
|
790
|
-
function readBlockFromElement(element) {
|
|
791
|
-
const id = element.getAttribute("data-block-id") || (0, richText_1.createRichTextBlockId)();
|
|
792
|
-
const tagName = element.tagName.toLowerCase();
|
|
793
|
-
if (tagName === "h2") {
|
|
794
|
-
return {
|
|
795
|
-
id,
|
|
796
|
-
type: "heading",
|
|
797
|
-
markdown: (0, richText_1.editorHtmlToMarkdown)(normalizeSoftBreakHtml(element.innerHTML)),
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
if (tagName === "blockquote") {
|
|
801
|
-
return {
|
|
802
|
-
id,
|
|
803
|
-
type: "quote",
|
|
804
|
-
markdown: (0, richText_1.editorHtmlToMarkdown)(normalizeSoftBreakHtml(element.innerHTML)),
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
if (tagName === "pre") {
|
|
808
|
-
return { id, type: "code", text: element.textContent ?? "" };
|
|
809
|
-
}
|
|
810
|
-
if (tagName === "hr") {
|
|
811
|
-
return { id, type: "divider" };
|
|
812
|
-
}
|
|
813
|
-
if (tagName === "div" && element.hasAttribute("data-rich-text-checkbox")) {
|
|
814
|
-
const input = element.querySelector("input[type='checkbox']");
|
|
815
|
-
const label = element.querySelector("[data-checkbox-label]");
|
|
816
|
-
return {
|
|
817
|
-
id,
|
|
818
|
-
type: "checkbox",
|
|
819
|
-
checked: input instanceof HTMLInputElement ? input.checked : false,
|
|
820
|
-
markdown: (0, richText_1.editorHtmlToMarkdown)(normalizeSoftBreakHtml(label?.innerHTML ?? element.innerHTML)),
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
if (tagName === "figure") {
|
|
824
|
-
return {
|
|
825
|
-
id,
|
|
826
|
-
type: "image",
|
|
827
|
-
alt: element.textContent?.trim() || "Image placeholder",
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
return {
|
|
831
|
-
id,
|
|
832
|
-
type: "paragraph",
|
|
833
|
-
markdown: (0, richText_1.editorHtmlToMarkdown)(normalizeSoftBreakHtml(element.innerHTML)),
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
function normalizeSoftBreakHtml(html) {
|
|
837
|
-
return html
|
|
838
|
-
.replace(/<span(?:\s+data-checkbox-label(?:=(?:""|'')?)?)?[^>]*>((?:\s*<br\s*\/?>\s*)+)<\/span>/gi, (_match, breaks) => {
|
|
839
|
-
return breaks.replace(/<br\s*\/?>/gi, "<br>").replace(/\s+/g, "");
|
|
840
|
-
})
|
|
841
|
-
.replace(/<br\s*\/?>/gi, "<br>");
|
|
842
|
-
}
|
|
843
|
-
function ensureEditorBlockId(element, fallbackId = (0, richText_1.createRichTextBlockId)()) {
|
|
844
|
-
const currentId = element.getAttribute("data-block-id");
|
|
845
|
-
if (currentId) {
|
|
846
|
-
return currentId;
|
|
847
|
-
}
|
|
848
|
-
element.setAttribute("data-block-id", fallbackId);
|
|
849
|
-
return fallbackId;
|
|
850
|
-
}
|
|
851
|
-
function isEditorHtmlEmpty(html) {
|
|
852
|
-
return (html === "" || /^<p data-block-id="[^"]*">(?: )?<\/p>$/.test(html));
|
|
853
|
-
}
|
|
854
|
-
function applyActiveTextBlockShortcut(body) {
|
|
855
|
-
if (!body) {
|
|
856
|
-
return false;
|
|
857
|
-
}
|
|
858
|
-
const selection = window.getSelection();
|
|
859
|
-
if (!selection || selection.rangeCount === 0) {
|
|
860
|
-
return false;
|
|
861
|
-
}
|
|
862
|
-
const activeBlock = getActiveEditorBlock(selection.getRangeAt(0), body);
|
|
863
|
-
if (!activeBlock ||
|
|
864
|
-
activeBlock.hasAttribute("data-rich-text-checkbox") ||
|
|
865
|
-
!["DIV", "P"].includes(activeBlock.tagName)) {
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
const shortcut = (0, editorShortcuts_1.getTextBlockShortcut)((0, richText_1.editorHtmlToMarkdown)(activeBlock.innerHTML));
|
|
869
|
-
if (!shortcut) {
|
|
870
|
-
return false;
|
|
871
|
-
}
|
|
872
|
-
const id = activeBlock.getAttribute("data-block-id") || (0, richText_1.createRichTextBlockId)();
|
|
873
|
-
activeBlock.insertAdjacentHTML("afterend", richTextBlockToEditorHtml(textBlockShortcutToRichTextBlock(id, shortcut)));
|
|
874
|
-
const replacementBlock = activeBlock.nextElementSibling;
|
|
875
|
-
activeBlock.remove();
|
|
876
|
-
if (shortcut.type === "divider") {
|
|
877
|
-
replacementBlock?.insertAdjacentHTML("afterend", `<p data-block-id="${escapeHtmlAttribute((0, richText_1.createRichTextBlockId)())}" data-suppress-special-block-tool="true"> </p>`);
|
|
878
|
-
focusBlockStart(replacementBlock?.nextElementSibling ?? null);
|
|
879
|
-
return true;
|
|
880
|
-
}
|
|
881
|
-
if (shortcut.type === "checkbox") {
|
|
882
|
-
focusBlockEnd(replacementBlock?.querySelector("[data-checkbox-label]") ?? null);
|
|
883
|
-
return true;
|
|
884
|
-
}
|
|
885
|
-
focusBlockEnd(replacementBlock);
|
|
886
|
-
return true;
|
|
887
|
-
}
|
|
888
|
-
function normalizeActiveTextBlockTypography(body) {
|
|
889
|
-
if (!body) {
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
const selection = window.getSelection();
|
|
893
|
-
if (!selection || selection.rangeCount === 0) {
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
const range = selection.getRangeAt(0);
|
|
897
|
-
const activeBlock = getActiveEditorBlock(range, body);
|
|
898
|
-
if (!activeBlock ||
|
|
899
|
-
activeBlock.tagName === "PRE" ||
|
|
900
|
-
activeBlock.hasAttribute("data-rich-text-checkbox")) {
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
const currentMarkdown = (0, richText_1.editorHtmlToMarkdown)(activeBlock.innerHTML);
|
|
904
|
-
if (shouldDelayTypographyNormalization(activeBlock.textContent ?? "")) {
|
|
905
|
-
return;
|
|
906
|
-
}
|
|
907
|
-
const nextHtml = textBlockMarkdownToEditorHtml(currentMarkdown);
|
|
908
|
-
if (activeBlock.innerHTML === nextHtml) {
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const cursorOffset = getTextOffsetWithin(activeBlock, range);
|
|
912
|
-
const normalizedCursorOffset = (0, richText_1.editorHtmlToMarkdown)(activeBlock.textContent?.slice(0, cursorOffset) ?? "").length;
|
|
913
|
-
activeBlock.innerHTML = nextHtml;
|
|
914
|
-
focusBlockAtTextOffset(activeBlock, normalizedCursorOffset);
|
|
915
|
-
}
|
|
916
|
-
function focusBlockEnd(block) {
|
|
917
|
-
if (!block) {
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
const range = document.createRange();
|
|
921
|
-
range.selectNodeContents(block);
|
|
922
|
-
range.collapse(false);
|
|
923
|
-
const selection = window.getSelection();
|
|
924
|
-
selection?.removeAllRanges();
|
|
925
|
-
selection?.addRange(range);
|
|
926
|
-
}
|
|
927
|
-
function focusBlockAtTextOffset(block, offset) {
|
|
928
|
-
const walker = document.createTreeWalker(block, NodeFilter.SHOW_TEXT);
|
|
929
|
-
let remaining = offset;
|
|
930
|
-
let textNode = walker.nextNode();
|
|
931
|
-
while (textNode) {
|
|
932
|
-
const textLength = textNode.textContent?.length ?? 0;
|
|
933
|
-
if (remaining <= textLength) {
|
|
934
|
-
const range = document.createRange();
|
|
935
|
-
range.setStart(textNode, remaining);
|
|
936
|
-
range.collapse(true);
|
|
937
|
-
const selection = window.getSelection();
|
|
938
|
-
selection?.removeAllRanges();
|
|
939
|
-
selection?.addRange(range);
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
remaining -= textLength;
|
|
943
|
-
textNode = walker.nextNode();
|
|
944
|
-
}
|
|
945
|
-
focusBlockEnd(block);
|
|
946
|
-
}
|
|
947
|
-
function getTextOffsetWithin(root, range) {
|
|
948
|
-
const textRange = document.createRange();
|
|
949
|
-
textRange.selectNodeContents(root);
|
|
950
|
-
textRange.setEnd(range.startContainer, range.startOffset);
|
|
951
|
-
return textRange.toString().length;
|
|
952
|
-
}
|
|
953
|
-
function getTextAfterRange(root, range) {
|
|
954
|
-
const textRange = document.createRange();
|
|
955
|
-
textRange.selectNodeContents(root);
|
|
956
|
-
textRange.setStart(range.startContainer, range.startOffset);
|
|
957
|
-
return textRange.toString();
|
|
958
|
-
}
|
|
959
|
-
function hasEditableTrailingWhitespace(value) {
|
|
960
|
-
return /[\s\u00a0]$/.test(value);
|
|
961
|
-
}
|
|
962
|
-
function shouldDelayTypographyNormalization(value) {
|
|
963
|
-
return hasEditableTrailingWhitespace(value) && !hasDashSequence(value);
|
|
964
|
-
}
|
|
965
|
-
function splitCheckboxMarkdownAtTextOffset(markdown, offset) {
|
|
966
|
-
const visibleOffset = Math.trunc(offset);
|
|
967
|
-
const visibleTextLength = getMarkdownVisibleText(markdown).length;
|
|
968
|
-
if (visibleOffset <= 0) {
|
|
969
|
-
return {
|
|
970
|
-
after: markdown.trim(),
|
|
971
|
-
before: "",
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
if (visibleOffset >= visibleTextLength) {
|
|
975
|
-
return {
|
|
976
|
-
after: "",
|
|
977
|
-
before: markdown.trim(),
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
const splitOffset = clamp(getMarkdownIndexAtVisibleTextOffset(markdown, visibleOffset), 0, markdown.length);
|
|
981
|
-
return {
|
|
982
|
-
after: markdown.slice(splitOffset).trim(),
|
|
983
|
-
before: markdown.slice(0, splitOffset).trim(),
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
function getMarkdownIndexAtVisibleTextOffset(markdown, offset) {
|
|
987
|
-
let visibleOffset = 0;
|
|
988
|
-
for (let index = 0; index < markdown.length; index += 1) {
|
|
989
|
-
if (isMarkdownSyntaxAt(markdown, index)) {
|
|
990
|
-
continue;
|
|
991
|
-
}
|
|
992
|
-
visibleOffset += 1;
|
|
993
|
-
if (visibleOffset >= offset) {
|
|
994
|
-
return index + 1;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
return markdown.length;
|
|
998
|
-
}
|
|
999
|
-
function getMarkdownVisibleText(markdown) {
|
|
1000
|
-
return markdown
|
|
1001
|
-
.replace(/`([^`]*)`/g, "$1")
|
|
1002
|
-
.replace(/\*\*([^*]*)\*\*/g, "$1")
|
|
1003
|
-
.replace(/_([^_]*)_/g, "$1")
|
|
1004
|
-
.replace(/\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
1005
|
-
}
|
|
1006
|
-
function isMarkdownSyntaxAt(markdown, index) {
|
|
1007
|
-
return (isStrongDelimiterAt(markdown, index) || isInlineSyntaxAt(markdown, index));
|
|
1008
|
-
}
|
|
1009
|
-
function isStrongDelimiterAt(markdown, index) {
|
|
1010
|
-
return (markdown[index] === "*" &&
|
|
1011
|
-
(markdown[index - 1] === "*" || markdown[index + 1] === "*"));
|
|
1012
|
-
}
|
|
1013
|
-
function isInlineSyntaxAt(markdown, index) {
|
|
1014
|
-
const char = markdown[index];
|
|
1015
|
-
if (isInsideMarkdownLinkTarget(markdown, index)) {
|
|
1016
|
-
return true;
|
|
1017
|
-
}
|
|
1018
|
-
if (char === "`" || char === "_") {
|
|
1019
|
-
return true;
|
|
1020
|
-
}
|
|
1021
|
-
if (char === "[" || char === "]") {
|
|
1022
|
-
return true;
|
|
1023
|
-
}
|
|
1024
|
-
return false;
|
|
1025
|
-
}
|
|
1026
|
-
function isInsideMarkdownLinkTarget(markdown, index) {
|
|
1027
|
-
const linkOpenIndex = markdown.lastIndexOf("](", index);
|
|
1028
|
-
if (linkOpenIndex === -1) {
|
|
1029
|
-
return false;
|
|
1030
|
-
}
|
|
1031
|
-
const linkCloseIndex = markdown.indexOf(")", linkOpenIndex + 2);
|
|
1032
|
-
return linkCloseIndex !== -1 && index <= linkCloseIndex;
|
|
1033
|
-
}
|
|
1034
|
-
function getCheckboxEnterAction(event) {
|
|
1035
|
-
return event.shiftKey ? "line-break" : "next-checkbox";
|
|
1036
|
-
}
|
|
1037
|
-
function shouldPadTrailingLineBreak(textAfterCursor) {
|
|
1038
|
-
return textAfterCursor.length === 0;
|
|
1039
|
-
}
|
|
1040
|
-
function resolveTranscriptionConfig(config = {}) {
|
|
1041
|
-
return {
|
|
1042
|
-
enabled: config.enabled ?? true,
|
|
1043
|
-
language: config.language?.trim() || undefined,
|
|
1044
|
-
};
|
|
1045
|
-
}
|
|
1046
|
-
function appendTranscriptText(currentText, transcript) {
|
|
1047
|
-
const nextText = transcript.trim();
|
|
1048
|
-
if (!nextText) {
|
|
1049
|
-
return currentText;
|
|
1050
|
-
}
|
|
1051
|
-
if (!currentText) {
|
|
1052
|
-
return nextText;
|
|
1053
|
-
}
|
|
1054
|
-
return `${currentText}${hasEditableTrailingWhitespace(currentText) ? "" : " "}${nextText}`;
|
|
1055
|
-
}
|
|
1056
|
-
function hasDashSequence(value) {
|
|
1057
|
-
return /--|––|–-|---|—-/.test(value);
|
|
1058
|
-
}
|
|
1059
|
-
function insertLineBreakAtRange(range, options = {}) {
|
|
1060
|
-
const breakElement = document.createElement("br");
|
|
1061
|
-
range.insertNode(breakElement);
|
|
1062
|
-
if (options.padTrailingBreak) {
|
|
1063
|
-
const paddingBreakElement = document.createElement("br");
|
|
1064
|
-
breakElement.parentNode?.insertBefore(paddingBreakElement, breakElement.nextSibling);
|
|
1065
|
-
}
|
|
1066
|
-
range.setStartAfter(breakElement);
|
|
1067
|
-
range.collapse(true);
|
|
1068
|
-
const selection = window.getSelection();
|
|
1069
|
-
selection?.removeAllRanges();
|
|
1070
|
-
selection?.addRange(range);
|
|
1071
|
-
}
|
|
1072
|
-
function checkboxBlockToEditorHtml(block) {
|
|
1073
|
-
const blockId = escapeHtmlAttribute(block.id);
|
|
1074
|
-
const checked = block.checked ? " checked" : "";
|
|
1075
|
-
return `<div data-block-id="${blockId}" data-rich-text-checkbox=""><input contenteditable="false" type="checkbox"${checked}><span data-checkbox-label="">${textBlockMarkdownToEditorHtml(block.markdown)}</span></div>`;
|
|
1076
|
-
}
|
|
1077
|
-
function textBlockShortcutToRichTextBlock(id, shortcut) {
|
|
1078
|
-
if (shortcut.type === "divider") {
|
|
1079
|
-
return { id, type: "divider" };
|
|
1080
|
-
}
|
|
1081
|
-
if (shortcut.type === "code") {
|
|
1082
|
-
return { id, type: "code", text: shortcut.text };
|
|
1083
|
-
}
|
|
1084
|
-
if (shortcut.type === "checkbox") {
|
|
1085
|
-
return {
|
|
1086
|
-
id,
|
|
1087
|
-
type: "checkbox",
|
|
1088
|
-
checked: shortcut.checked,
|
|
1089
|
-
markdown: shortcut.markdown,
|
|
1090
|
-
};
|
|
1091
|
-
}
|
|
1092
|
-
return { id, type: shortcut.type, markdown: shortcut.markdown };
|
|
1093
|
-
}
|
|
1094
|
-
function escapeHtmlText(value) {
|
|
1095
|
-
return value
|
|
1096
|
-
.replace(/&/g, "&")
|
|
1097
|
-
.replace(/</g, "<")
|
|
1098
|
-
.replace(/>/g, ">");
|
|
1099
|
-
}
|
|
1100
|
-
function escapeHtmlAttribute(value) {
|
|
1101
|
-
return escapeHtmlText(value).replace(/"/g, """);
|
|
1102
|
-
}
|
|
1103
|
-
function cssEscape(value) {
|
|
1104
|
-
if (typeof CSS !== "undefined" && CSS.escape) {
|
|
1105
|
-
return CSS.escape(value);
|
|
1106
|
-
}
|
|
1107
|
-
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
1108
|
-
}
|