smartrte-react 0.3.4 → 1.0.0-beta.1
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/CHANGELOG.md +19 -0
- package/README.md +23 -0
- package/dist/adapters/domChecklistCommandBridge.d.ts +6 -0
- package/dist/adapters/domChecklistCommandBridge.js +33 -0
- package/dist/adapters/domCommandBridge.d.ts +19 -0
- package/dist/adapters/domCommandBridge.js +33 -0
- package/dist/adapters/domInlineAtomCommandBridge.d.ts +15 -0
- package/dist/adapters/domInlineAtomCommandBridge.js +205 -0
- package/dist/adapters/domInlineImageCommandBridge.d.ts +14 -0
- package/dist/adapters/domInlineImageCommandBridge.js +6 -0
- package/dist/adapters/domMoveCommandBridge.d.ts +7 -0
- package/dist/adapters/domMoveCommandBridge.js +54 -0
- package/dist/adapters/domSelectionBridge.d.ts +5 -3
- package/dist/adapters/domSelectionBridge.js +96 -44
- package/dist/adapters/domSmartDocument.d.ts +4 -4
- package/dist/adapters/domSmartDocument.js +159 -17
- package/dist/adapters/domTableCommandBridge.d.ts +61 -0
- package/dist/adapters/domTableCommandBridge.js +231 -0
- package/dist/adapters/internalFlags.d.ts +0 -8
- package/dist/adapters/internalFlags.js +0 -24
- package/dist/adapters/legacyListShadowComparator.d.ts +26 -0
- package/dist/adapters/legacyListShadowComparator.js +316 -0
- package/dist/adapters/pdfPrint.d.ts +8 -0
- package/dist/adapters/pdfPrint.js +22 -0
- package/dist/adapters/shadowMode.d.ts +3 -3
- package/dist/adapters/shadowMode.js +1 -1
- package/dist/canonicalEditorRuntime.d.ts +129 -0
- package/dist/canonicalEditorRuntime.js +331 -0
- package/dist/capabilityPresets.d.ts +27 -0
- package/dist/capabilityPresets.js +21 -0
- package/dist/collabTransport.d.ts +66 -0
- package/dist/collabTransport.js +1 -0
- package/dist/commentProvider.d.ts +24 -0
- package/dist/commentProvider.js +1 -0
- package/dist/components/CanonicalAuthorityEditor.d.ts +59 -0
- package/dist/components/CanonicalAuthorityEditor.js +1492 -0
- package/dist/components/ClassicEditorAuthority.d.ts +109 -0
- package/dist/components/ClassicEditorAuthority.js +17 -0
- package/dist/components/ColorPickerPopover.d.ts +39 -0
- package/dist/components/ColorPickerPopover.js +285 -0
- package/dist/components/CommentMarkers.d.ts +25 -0
- package/dist/components/CommentMarkers.js +105 -0
- package/dist/components/CommentThreadPanel.d.ts +26 -0
- package/dist/components/CommentThreadPanel.js +47 -0
- package/dist/components/ContextMenu.d.ts +23 -0
- package/dist/components/ContextMenu.js +141 -0
- package/dist/components/FormulaLibraryPopover.d.ts +16 -0
- package/dist/components/FormulaLibraryPopover.js +107 -0
- package/dist/components/LinkEditorPopover.d.ts +3 -1
- package/dist/components/LinkEditorPopover.js +22 -6
- package/dist/components/MediaManager.d.ts +4 -20
- package/dist/components/MediaManager.js +35 -6
- package/dist/components/MediaOverlay.d.ts +36 -0
- package/dist/components/MediaOverlay.js +163 -0
- package/dist/components/MediaPicker.d.ts +10 -0
- package/dist/components/MediaPicker.js +16 -0
- package/dist/components/SpecialCharacterPopover.d.ts +18 -0
- package/dist/components/SpecialCharacterPopover.js +99 -0
- package/dist/components/StructuralSuggestionMarkers.d.ts +20 -0
- package/dist/components/StructuralSuggestionMarkers.js +56 -0
- package/dist/components/SuggestionPanel.d.ts +26 -0
- package/dist/components/SuggestionPanel.js +33 -0
- package/dist/components/TableBorderPopover.d.ts +59 -0
- package/dist/components/TableBorderPopover.js +147 -0
- package/dist/components/TableResizeHandles.d.ts +25 -0
- package/dist/components/TableResizeHandles.js +301 -0
- package/dist/components/TableSizePickerPopover.d.ts +16 -0
- package/dist/components/TableSizePickerPopover.js +126 -0
- package/dist/components/ToolbarPrimitives.d.ts +129 -0
- package/dist/components/ToolbarPrimitives.js +146 -0
- package/dist/components/VersionHistoryPanel.d.ts +9 -0
- package/dist/components/VersionHistoryPanel.js +157 -0
- package/dist/formulaLibrary.d.ts +31 -0
- package/dist/formulaLibrary.js +90 -0
- package/dist/index.d.ts +23 -1
- package/dist/index.js +14 -1
- package/dist/mediaManagerAdapter.d.ts +12 -0
- package/dist/mediaManagerAdapter.js +34 -0
- package/dist/mediaProvider.d.ts +59 -0
- package/dist/mediaProvider.js +1 -0
- package/dist/specialCharacters.d.ts +22 -0
- package/dist/specialCharacters.js +86 -0
- package/dist/standalone/classic-editor-embed.js +19 -12
- package/dist/suggestionProvider.d.ts +17 -0
- package/dist/suggestionProvider.js +1 -0
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +161 -1
- package/dist/versionProvider.d.ts +29 -0
- package/dist/versionProvider.js +1 -0
- package/package.json +28 -14
- package/dist/adapters/coreBold.d.ts +0 -5
- package/dist/adapters/coreBold.js +0 -3
- package/dist/adapters/inlineMarkCoreExecution.d.ts +0 -9
- package/dist/adapters/inlineMarkCoreExecution.js +0 -25
- package/dist/components/ClassicEditor.d.ts +0 -66
- package/dist/components/ClassicEditor.js +0 -6617
- package/dist/standalone/editor.js +0 -841
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const LEAF_TAGS = new Set(["p", "h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
2
2
|
const CONTAINER_TAGS = new Set(["blockquote", "ul", "ol", "li", "table", "tr", "td", "th"]);
|
|
3
3
|
export const isEditorOnlyElement = (node) => node.getAttribute("data-table-wrapper") === "true" ||
|
|
4
|
-
|
|
4
|
+
node.hasAttribute("data-srte-selection-marker") ||
|
|
5
|
+
node.hasAttribute("data-srte-resize-overlay") ||
|
|
6
|
+
node.hasAttribute("data-srte-drag-handle") ||
|
|
7
|
+
node.hasAttribute("data-srte-check") ||
|
|
5
8
|
node.matches(".srte-table-resize-handle, .srte-table-resize-overlay, .srte-drag-handle");
|
|
6
9
|
const isSemanticElement = (node) => LEAF_TAGS.has(node.tagName.toLowerCase()) || CONTAINER_TAGS.has(node.tagName.toLowerCase());
|
|
7
10
|
const hasSemanticChild = (node) => Array.from(node.children).some((child) => !isEditorOnlyElement(child) && isSemanticElement(child));
|
|
@@ -59,38 +62,51 @@ const pathForElement = (element, editor) => {
|
|
|
59
62
|
};
|
|
60
63
|
return findPath(editor, []);
|
|
61
64
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
/** Returns the semantic model path for a DOM element at the adapter boundary. */
|
|
66
|
+
export const pathForDomElement = (element, editor) => pathForElement(element, editor);
|
|
67
|
+
const isInlineAtom = (element) => element.tagName.toLowerCase() === "img" || element.hasAttribute("data-formula");
|
|
68
|
+
const inlineUnitsIn = (root) => {
|
|
69
|
+
const units = [];
|
|
70
|
+
const visit = (node) => {
|
|
71
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
72
|
+
if (node.textContent)
|
|
73
|
+
units.push({ type: "text", node: node });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!(node instanceof Element) || isEditorOnlyElement(node))
|
|
77
|
+
return;
|
|
78
|
+
if (isInlineAtom(node)) {
|
|
79
|
+
units.push({ type: "atom", node });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
Array.from(node.childNodes).forEach(visit);
|
|
83
|
+
};
|
|
84
|
+
Array.from(root.childNodes).forEach(visit);
|
|
85
|
+
return units;
|
|
81
86
|
};
|
|
82
|
-
const
|
|
83
|
-
|
|
87
|
+
const pointRelativeTo = (container, offset, unit) => {
|
|
88
|
+
const point = container.ownerDocument?.createRange();
|
|
89
|
+
const unitRange = container.ownerDocument?.createRange();
|
|
90
|
+
if (!point || !unitRange)
|
|
84
91
|
return null;
|
|
85
|
-
const range = leaf.ownerDocument.createRange();
|
|
86
|
-
range.selectNodeContents(leaf);
|
|
87
92
|
try {
|
|
88
|
-
|
|
93
|
+
point.setStart(container, offset);
|
|
94
|
+
point.collapse(true);
|
|
95
|
+
if (unit.type === "text")
|
|
96
|
+
unitRange.selectNodeContents(unit.node);
|
|
97
|
+
else
|
|
98
|
+
unitRange.selectNode(unit.node);
|
|
99
|
+
const fromStart = point.compareBoundaryPoints(Range.START_TO_START, unitRange);
|
|
100
|
+
const fromEnd = point.compareBoundaryPoints(Range.START_TO_END, unitRange);
|
|
101
|
+
if (fromStart <= 0)
|
|
102
|
+
return "before";
|
|
103
|
+
if (fromEnd >= 0)
|
|
104
|
+
return "after";
|
|
105
|
+
return "inside";
|
|
89
106
|
}
|
|
90
107
|
catch {
|
|
91
108
|
return null;
|
|
92
109
|
}
|
|
93
|
-
return range.toString().length;
|
|
94
110
|
};
|
|
95
111
|
const pointForDomPoint = (editor, container, offset) => {
|
|
96
112
|
const leaf = closestLeaf(container, editor);
|
|
@@ -99,25 +115,47 @@ const pointForDomPoint = (editor, container, offset) => {
|
|
|
99
115
|
const leafPath = pathForElement(leaf, editor);
|
|
100
116
|
if (!leafPath)
|
|
101
117
|
return null;
|
|
102
|
-
const
|
|
103
|
-
if (
|
|
118
|
+
const units = inlineUnitsIn(leaf);
|
|
119
|
+
if (units.length === 0)
|
|
104
120
|
return { path: [...leafPath, 0], offset: 0 };
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
const directTextIndex = units.findIndex((unit) => unit.type === "text" && unit.node === container);
|
|
122
|
+
if (directTextIndex >= 0) {
|
|
123
|
+
const text = units[directTextIndex];
|
|
124
|
+
return {
|
|
125
|
+
path: [...leafPath, directTextIndex],
|
|
126
|
+
offset: Math.max(0, Math.min(offset, text.node.data.length)),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
let previousText = null;
|
|
130
|
+
for (let index = 0; index < units.length; index += 1) {
|
|
131
|
+
const unit = units[index];
|
|
132
|
+
const relative = pointRelativeTo(container, offset, unit);
|
|
133
|
+
if (relative === "before") {
|
|
134
|
+
if (unit.type === "text")
|
|
135
|
+
return { path: [...leafPath, index], offset: 0 };
|
|
136
|
+
const nextTextIndex = units.slice(index + 1).findIndex((candidate) => candidate.type === "text");
|
|
137
|
+
if (nextTextIndex >= 0)
|
|
138
|
+
return { path: [...leafPath, index + 1 + nextTextIndex], offset: 0 };
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
if (unit.type === "text")
|
|
142
|
+
previousText = { index, node: unit.node };
|
|
143
|
+
}
|
|
144
|
+
if (previousText) {
|
|
145
|
+
return { path: [...leafPath, previousText.index], offset: previousText.node.data.length };
|
|
114
146
|
}
|
|
115
|
-
|
|
116
|
-
return { path: [...leafPath, last], offset: textNodes[last].data.length };
|
|
147
|
+
return null;
|
|
117
148
|
};
|
|
118
149
|
const nodeSelectionFromRange = (editor, range) => {
|
|
119
|
-
const nodes = Array.from(editor.querySelectorAll("img, [data-srte-node-selection='true']"));
|
|
120
|
-
const selected = nodes.find((node) =>
|
|
150
|
+
const nodes = Array.from(editor.querySelectorAll("img, [data-formula], [data-srte-node-selection='true']"));
|
|
151
|
+
const selected = nodes.find((node) => {
|
|
152
|
+
if (range.collapsed)
|
|
153
|
+
return node === range.startContainer || node.contains(range.startContainer);
|
|
154
|
+
const nodeRange = editor.ownerDocument.createRange();
|
|
155
|
+
nodeRange.selectNode(node);
|
|
156
|
+
return range.compareBoundaryPoints(Range.START_TO_START, nodeRange) === 0 &&
|
|
157
|
+
range.compareBoundaryPoints(Range.END_TO_END, nodeRange) === 0;
|
|
158
|
+
});
|
|
121
159
|
if (!selected)
|
|
122
160
|
return null;
|
|
123
161
|
const path = pathForElement(selected, editor);
|
|
@@ -155,13 +193,27 @@ const domPointForSmartPoint = (editor, point) => {
|
|
|
155
193
|
const leaf = elementAtPath(editor, point.path.slice(0, -1));
|
|
156
194
|
if (!leaf)
|
|
157
195
|
return null;
|
|
158
|
-
const
|
|
159
|
-
if (
|
|
196
|
+
const unit = inlineUnitsIn(leaf)[point.path[point.path.length - 1]];
|
|
197
|
+
if (unit?.type !== "text" || point.offset < 0 || point.offset > unit.node.data.length)
|
|
160
198
|
return null;
|
|
161
|
-
return { node:
|
|
199
|
+
return { node: unit.node, offset: point.offset };
|
|
162
200
|
};
|
|
163
201
|
/** Restores a text selection from core paths after the editor DOM is rebuilt. */
|
|
164
202
|
export const restoreSelectionToDom = (editor, smartSelection) => {
|
|
203
|
+
if (smartSelection.type === "node") {
|
|
204
|
+
const element = elementAtPath(editor, smartSelection.path);
|
|
205
|
+
if (!element)
|
|
206
|
+
return false;
|
|
207
|
+
const range = editor.ownerDocument.createRange();
|
|
208
|
+
range.selectNodeContents(element);
|
|
209
|
+
range.collapse(false);
|
|
210
|
+
const selection = editor.ownerDocument.defaultView?.getSelection();
|
|
211
|
+
if (!selection)
|
|
212
|
+
return false;
|
|
213
|
+
selection.removeAllRanges();
|
|
214
|
+
selection.addRange(range);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
165
217
|
if (smartSelection.type !== "text")
|
|
166
218
|
return false;
|
|
167
219
|
const anchor = domPointForSmartPoint(editor, smartSelection.anchor);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type LegacySmartDocument } from "smartrte-core/legacy";
|
|
2
2
|
/** Removes editor UI before handing the document to the core parser boundary. */
|
|
3
3
|
export declare const cleanEditorHtml: (root: HTMLElement) => string;
|
|
4
|
-
export declare const smartDocumentFromHtml: (html: string, ownerDocument: Document) =>
|
|
4
|
+
export declare const smartDocumentFromHtml: (html: string, ownerDocument: Document) => LegacySmartDocument;
|
|
5
5
|
export declare const smartDocumentFromEditorRoot: (root: HTMLElement) => {
|
|
6
|
-
document:
|
|
6
|
+
document: LegacySmartDocument;
|
|
7
7
|
html: string;
|
|
8
8
|
};
|
|
9
9
|
/** Serializes the shadow-only model. It is not used for persisted editor HTML. */
|
|
10
|
-
export declare const serializeSmartDocument: (document:
|
|
10
|
+
export declare const serializeSmartDocument: (document: LegacySmartDocument) => string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { normalizeCompatibilityHtml, sanitizeLinkAttrs, } from "smartrte-core";
|
|
1
|
+
import { normalizeCompatibilityHtml, sanitizeLinkAttrs, } from "smartrte-core/legacy";
|
|
2
|
+
import { isSmartListPreset } from "smartrte-core/legacy";
|
|
2
3
|
import { isEditorOnlyElement } from "./domSelectionBridge.js";
|
|
3
|
-
const blockTags = new Set(["p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "blockquote", "pre", "table"]);
|
|
4
|
+
const blockTags = new Set(["p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "blockquote", "pre", "table", "img", "video", "audio"]);
|
|
4
5
|
const unwrap = (element) => {
|
|
5
6
|
const parent = element.parentNode;
|
|
6
7
|
if (!parent)
|
|
@@ -40,6 +41,9 @@ const marksFor = (element, inherited) => {
|
|
|
40
41
|
marks = addMark(marks, { type: "subscript" });
|
|
41
42
|
if (tag === "code")
|
|
42
43
|
marks = addMark(marks, { type: "code" });
|
|
44
|
+
if (element.hasAttribute("data-formula")) {
|
|
45
|
+
marks = addMark(marks, { type: "formula", value: element.getAttribute("data-formula") || "" });
|
|
46
|
+
}
|
|
43
47
|
if (tag === "a") {
|
|
44
48
|
const safeLink = sanitizeLinkAttrs({
|
|
45
49
|
href: element.getAttribute("href") || "",
|
|
@@ -51,6 +55,7 @@ const marksFor = (element, inherited) => {
|
|
|
51
55
|
const color = element.getAttribute("color") || element.style.color;
|
|
52
56
|
const backgroundColor = element.style.backgroundColor;
|
|
53
57
|
const fontSize = element.style.fontSize;
|
|
58
|
+
const fontFamily = element.style.fontFamily;
|
|
54
59
|
if (color)
|
|
55
60
|
marks = addMark(marks, { type: "textColor", value: color });
|
|
56
61
|
if (backgroundColor)
|
|
@@ -64,6 +69,8 @@ const marksFor = (element, inherited) => {
|
|
|
64
69
|
marks = addMark(marks, { type: "fontSize", valuePx });
|
|
65
70
|
}
|
|
66
71
|
}
|
|
72
|
+
if (fontFamily)
|
|
73
|
+
marks = addMark(marks, { type: "fontFamily", value: fontFamily });
|
|
67
74
|
return marks;
|
|
68
75
|
};
|
|
69
76
|
const inlineNodes = (nodes, inherited = []) => {
|
|
@@ -76,6 +83,27 @@ const inlineNodes = (nodes, inherited = []) => {
|
|
|
76
83
|
}
|
|
77
84
|
if (!(node instanceof Element))
|
|
78
85
|
return;
|
|
86
|
+
if (node.hasAttribute("data-formula")) {
|
|
87
|
+
result.push({
|
|
88
|
+
type: "formula",
|
|
89
|
+
value: node.getAttribute("data-formula") || "",
|
|
90
|
+
...(node.textContent && node.textContent !== node.getAttribute("data-formula")
|
|
91
|
+
? { displayText: node.textContent }
|
|
92
|
+
: {}),
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (node.tagName.toLowerCase() === "img") {
|
|
97
|
+
result.push({
|
|
98
|
+
type: "inlineImage",
|
|
99
|
+
src: node.getAttribute("src") || "",
|
|
100
|
+
alt: node.getAttribute("alt") || undefined,
|
|
101
|
+
title: node.getAttribute("title") || undefined,
|
|
102
|
+
width: Number(node.getAttribute("width")) || undefined,
|
|
103
|
+
height: Number(node.getAttribute("height")) || undefined,
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
79
107
|
if (node.tagName.toLowerCase() === "br") {
|
|
80
108
|
result.push({ type: "text", text: "\n", marks: inherited.length ? inherited : undefined });
|
|
81
109
|
return;
|
|
@@ -103,9 +131,19 @@ const alignmentFor = (element) => {
|
|
|
103
131
|
const value = (element.style.textAlign || element.getAttribute("align") || "").toLowerCase();
|
|
104
132
|
return value === "center" || value === "right" || value === "justify" ? value : undefined;
|
|
105
133
|
};
|
|
134
|
+
const indentFor = (element) => {
|
|
135
|
+
const margin = Number.parseFloat(element.style.marginLeft || "0");
|
|
136
|
+
return Number.isFinite(margin) && margin > 0 ? Math.min(Math.round(margin / 24), 10) : undefined;
|
|
137
|
+
};
|
|
106
138
|
const parseBlocks = (parent) => directBlockChildren(parent).flatMap(parseBlock);
|
|
107
139
|
const parseList = (element) => {
|
|
108
|
-
const
|
|
140
|
+
const requestedStyle = element.style.listStyleType;
|
|
141
|
+
const supportedStyles = new Set([
|
|
142
|
+
"disc", "circle", "square", "decimal", "decimal-leading-zero", "lower-alpha", "upper-alpha",
|
|
143
|
+
"lower-roman", "upper-roman",
|
|
144
|
+
]);
|
|
145
|
+
const fallbackStyle = element.tagName.toLowerCase() === "ol" ? "decimal" : "disc";
|
|
146
|
+
const style = (supportedStyles.has(requestedStyle) ? requestedStyle : fallbackStyle);
|
|
109
147
|
const items = Array.from(element.children)
|
|
110
148
|
.filter((child) => child.tagName.toLowerCase() === "li")
|
|
111
149
|
.map((item) => {
|
|
@@ -113,9 +151,28 @@ const parseList = (element) => {
|
|
|
113
151
|
const nested = Array.from(item.children)
|
|
114
152
|
.filter((child) => ["ul", "ol", "blockquote", "pre", "table", "p", "h1", "h2", "h3", "h4", "h5", "h6"].includes(child.tagName.toLowerCase()))
|
|
115
153
|
.flatMap(parseBlock);
|
|
116
|
-
return {
|
|
154
|
+
return {
|
|
155
|
+
type: "listItem",
|
|
156
|
+
alignment: alignmentFor(item),
|
|
157
|
+
...(item.hasAttribute("data-checked") || item.hasAttribute("data-srte-checked")
|
|
158
|
+
? { checked: (item.getAttribute("data-checked") || item.getAttribute("data-srte-checked")) === "true" }
|
|
159
|
+
: {}),
|
|
160
|
+
children: [...(content ? [content] : []), ...nested],
|
|
161
|
+
};
|
|
117
162
|
});
|
|
118
|
-
|
|
163
|
+
const checklist = element.getAttribute("data-srte-checklist") === "true";
|
|
164
|
+
const requestedPreset = element.getAttribute("data-srte-list-preset");
|
|
165
|
+
return {
|
|
166
|
+
type: "list",
|
|
167
|
+
indent: indentFor(element),
|
|
168
|
+
style,
|
|
169
|
+
...(isSmartListPreset(requestedPreset) ? { preset: requestedPreset } : {}),
|
|
170
|
+
...(checklist ? { checklist: true } : {}),
|
|
171
|
+
...(checklist && element.getAttribute("data-srte-checklist-strike") === "true"
|
|
172
|
+
? { strikeCompleted: true }
|
|
173
|
+
: {}),
|
|
174
|
+
children: items,
|
|
175
|
+
};
|
|
119
176
|
};
|
|
120
177
|
const tableRows = (table) => {
|
|
121
178
|
const rows = [];
|
|
@@ -128,16 +185,38 @@ const tableRows = (table) => {
|
|
|
128
185
|
});
|
|
129
186
|
return rows;
|
|
130
187
|
};
|
|
188
|
+
const borderFor = (element) => {
|
|
189
|
+
const style = element.style;
|
|
190
|
+
if (style.border)
|
|
191
|
+
return style.border;
|
|
192
|
+
const declared = element.getAttribute("style")?.match(/(?:^|;)\s*border\s*:\s*([^;]+)/i)?.[1]?.trim();
|
|
193
|
+
if (declared)
|
|
194
|
+
return declared;
|
|
195
|
+
if (style.borderTopStyle === "none" &&
|
|
196
|
+
style.borderRightStyle === "none" &&
|
|
197
|
+
style.borderBottomStyle === "none" &&
|
|
198
|
+
style.borderLeftStyle === "none")
|
|
199
|
+
return "none";
|
|
200
|
+
return undefined;
|
|
201
|
+
};
|
|
131
202
|
const parseTable = (element) => ({
|
|
132
203
|
type: "table",
|
|
204
|
+
indent: indentFor(element),
|
|
205
|
+
columnWidths: Array.from(element.querySelectorAll(":scope > colgroup > col"))
|
|
206
|
+
.map((column) => Number.parseFloat(column.style.width || column.getAttribute("width") || ""))
|
|
207
|
+
.filter((width) => Number.isFinite(width) && width > 0),
|
|
133
208
|
children: tableRows(element).map((row) => ({
|
|
134
209
|
type: "tableRow",
|
|
210
|
+
heightPx: Number.parseFloat(row.style.height || "") || undefined,
|
|
135
211
|
children: Array.from(row.children)
|
|
136
212
|
.filter((cell) => ["td", "th"].includes(cell.tagName.toLowerCase()))
|
|
137
213
|
.map((cell) => ({
|
|
138
214
|
type: cell.tagName.toLowerCase() === "th" ? "tableHeaderCell" : "tableCell",
|
|
139
215
|
colspan: Number(cell.getAttribute("colspan")) || undefined,
|
|
140
216
|
rowspan: Number(cell.getAttribute("rowspan")) || undefined,
|
|
217
|
+
backgroundColor: cell.style.backgroundColor || undefined,
|
|
218
|
+
textColor: cell.style.color || undefined,
|
|
219
|
+
border: borderFor(cell),
|
|
141
220
|
children: parseBlocks(cell).length ? parseBlocks(cell) : [{ type: "paragraph", alignment: alignmentFor(cell), children: inlineNodes(cell.childNodes) }],
|
|
142
221
|
})),
|
|
143
222
|
})),
|
|
@@ -145,15 +224,34 @@ const parseTable = (element) => ({
|
|
|
145
224
|
const parseBlock = (element) => {
|
|
146
225
|
const tag = element.tagName.toLowerCase();
|
|
147
226
|
if (tag === "p")
|
|
148
|
-
return [{ type: "paragraph", alignment: alignmentFor(element), children: inlineNodes(element.childNodes) }];
|
|
227
|
+
return [{ type: "paragraph", alignment: alignmentFor(element), indent: indentFor(element), children: inlineNodes(element.childNodes) }];
|
|
149
228
|
if (/^h[1-6]$/.test(tag))
|
|
150
|
-
return [{ type: "heading", level: Number(tag.slice(1)), alignment: alignmentFor(element), children: inlineNodes(element.childNodes) }];
|
|
229
|
+
return [{ type: "heading", level: Number(tag.slice(1)), alignment: alignmentFor(element), indent: indentFor(element), children: inlineNodes(element.childNodes) }];
|
|
151
230
|
if (tag === "ul" || tag === "ol")
|
|
152
231
|
return [parseList(element)];
|
|
153
232
|
if (tag === "blockquote")
|
|
154
|
-
return [{ type: "blockquote", alignment: alignmentFor(element), children: parseBlocks(element) }];
|
|
233
|
+
return [{ type: "blockquote", alignment: alignmentFor(element), indent: indentFor(element), children: parseBlocks(element) }];
|
|
155
234
|
if (tag === "pre")
|
|
156
|
-
return [{ type: "codeBlock", alignment: alignmentFor(element), text: element.textContent || "", language: element.querySelector("code")?.className.replace(/^language-/, "") || undefined }];
|
|
235
|
+
return [{ type: "codeBlock", alignment: alignmentFor(element), indent: indentFor(element), text: element.textContent || "", language: element.querySelector("code")?.className.replace(/^language-/, "") || undefined }];
|
|
236
|
+
if (tag === "img")
|
|
237
|
+
return [{
|
|
238
|
+
type: "image",
|
|
239
|
+
indent: indentFor(element),
|
|
240
|
+
src: element.getAttribute("src") || "",
|
|
241
|
+
alt: element.getAttribute("alt") || undefined,
|
|
242
|
+
title: element.getAttribute("title") || undefined,
|
|
243
|
+
width: Number(element.getAttribute("width")) || undefined,
|
|
244
|
+
height: Number(element.getAttribute("height")) || undefined,
|
|
245
|
+
}];
|
|
246
|
+
if (tag === "video" || tag === "audio")
|
|
247
|
+
return [{
|
|
248
|
+
type: "media",
|
|
249
|
+
indent: indentFor(element),
|
|
250
|
+
src: element.getAttribute("src") || element.querySelector("source")?.getAttribute("src") || "",
|
|
251
|
+
mediaType: tag,
|
|
252
|
+
title: element.getAttribute("title") || undefined,
|
|
253
|
+
mimeType: element.getAttribute("type") || element.querySelector("source")?.getAttribute("type") || undefined,
|
|
254
|
+
}];
|
|
157
255
|
if (tag === "table")
|
|
158
256
|
return [parseTable(element)];
|
|
159
257
|
return [];
|
|
@@ -191,26 +289,70 @@ const serializeText = (node) => {
|
|
|
191
289
|
html = `<span style="background-color:${escapeHtml(mark.value)}">${html}</span>`;
|
|
192
290
|
if (mark.type === "fontSize")
|
|
193
291
|
html = `<span style="font-size:${mark.valuePx}px">${html}</span>`;
|
|
292
|
+
if (mark.type === "fontFamily")
|
|
293
|
+
html = `<span style="font-family:${escapeHtml(mark.value)}">${html}</span>`;
|
|
294
|
+
if (mark.type === "formula")
|
|
295
|
+
html = `<span data-formula="${escapeHtml(mark.value)}">${html}</span>`;
|
|
194
296
|
if (mark.type === "link")
|
|
195
297
|
html = `<a href="${escapeHtml(mark.href)}"${mark.target ? ` target="${escapeHtml(mark.target)}"` : ""}>${html}</a>`;
|
|
196
298
|
});
|
|
197
299
|
return html;
|
|
198
300
|
};
|
|
199
|
-
const
|
|
301
|
+
const serializeInline = (node) => {
|
|
302
|
+
if (node.type === "text")
|
|
303
|
+
return serializeText(node);
|
|
304
|
+
if (node.type === "formula") {
|
|
305
|
+
return `<span data-formula="${escapeHtml(node.value)}">${escapeHtml(node.displayText ?? node.value)}</span>`;
|
|
306
|
+
}
|
|
307
|
+
return `<img data-srte-inline="true" src="${escapeHtml(node.src)}"${node.alt ? ` alt="${escapeHtml(node.alt)}"` : ""}${node.title ? ` title="${escapeHtml(node.title)}"` : ""}${node.width ? ` width="${node.width}"` : ""}${node.height ? ` height="${node.height}"` : ""}>`;
|
|
308
|
+
};
|
|
309
|
+
const blockStyleAttribute = (block, extra) => {
|
|
310
|
+
const declarations = [
|
|
311
|
+
block.alignment ? `text-align:${block.alignment}` : "",
|
|
312
|
+
block.indent ? `margin-left:${block.indent * 24}px` : "",
|
|
313
|
+
extra || "",
|
|
314
|
+
].filter(Boolean);
|
|
315
|
+
return declarations.length ? ` style="${declarations.join(";")}"` : "";
|
|
316
|
+
};
|
|
317
|
+
const alignmentAttribute = (alignment) => blockStyleAttribute({ alignment });
|
|
200
318
|
const serializeBlock = (block) => {
|
|
201
319
|
if (block.type === "paragraph")
|
|
202
|
-
return `<p${
|
|
320
|
+
return `<p${blockStyleAttribute(block)}>${block.children.map(serializeInline).join("")}</p>`;
|
|
203
321
|
if (block.type === "heading")
|
|
204
|
-
return `<h${block.level}${
|
|
322
|
+
return `<h${block.level}${blockStyleAttribute(block)}>${block.children.map(serializeInline).join("")}</h${block.level}>`;
|
|
205
323
|
if (block.type === "blockquote")
|
|
206
|
-
return `<blockquote${
|
|
324
|
+
return `<blockquote${blockStyleAttribute(block)}>${block.children.map(serializeBlock).join("")}</blockquote>`;
|
|
207
325
|
if (block.type === "codeBlock")
|
|
208
|
-
return `<pre${
|
|
326
|
+
return `<pre${blockStyleAttribute(block)}><code${block.language ? ` class="language-${escapeHtml(block.language)}"` : ""}>${escapeHtml(block.text)}</code></pre>`;
|
|
327
|
+
if (block.type === "image")
|
|
328
|
+
return `<img${blockStyleAttribute(block)} src="${escapeHtml(block.src)}"${block.alt ? ` alt="${escapeHtml(block.alt)}"` : ""}${block.title ? ` title="${escapeHtml(block.title)}"` : ""}${block.width ? ` width="${block.width}"` : ""}${block.height ? ` height="${block.height}"` : ""}>`;
|
|
329
|
+
if (block.type === "media") {
|
|
330
|
+
const tag = block.mediaType === "audio" ? "audio" : "video";
|
|
331
|
+
return `<${tag}${blockStyleAttribute(block)} controls src="${escapeHtml(block.src)}"${block.title ? ` title="${escapeHtml(block.title)}"` : ""}${block.mimeType ? ` type="${escapeHtml(block.mimeType)}"` : ""}></${tag}>`;
|
|
332
|
+
}
|
|
209
333
|
if (block.type === "list") {
|
|
210
|
-
const
|
|
211
|
-
|
|
334
|
+
const ordered = ["decimal", "decimal-leading-zero", "lower-alpha", "upper-alpha", "lower-roman", "upper-roman"].includes(block.style);
|
|
335
|
+
const tag = ordered ? "ol" : "ul";
|
|
336
|
+
const style = blockStyleAttribute(block, `list-style-type:${block.style}`);
|
|
337
|
+
const checklist = block.checklist
|
|
338
|
+
? ` data-srte-checklist="true" data-srte-checklist-strike="${block.strikeCompleted ? "true" : "false"}"`
|
|
339
|
+
: "";
|
|
340
|
+
const preset = block.preset ? ` data-srte-list-preset="${block.preset}"` : "";
|
|
341
|
+
return `<${tag}${style}${preset}${checklist}>${block.children.map((item) => `<li${item.checked !== undefined ? ` data-srte-checked="${item.checked ? "true" : "false"}"` : ""}${alignmentAttribute(item.alignment)}>${item.children.map(serializeBlock).join("")}</li>`).join("")}</${tag}>`;
|
|
212
342
|
}
|
|
213
|
-
|
|
343
|
+
const columns = block.columnWidths?.length
|
|
344
|
+
? `<colgroup>${block.columnWidths.map((width) => `<col style="width:${width}px">`).join("")}</colgroup>`
|
|
345
|
+
: "";
|
|
346
|
+
return `<table${blockStyleAttribute(block)}>${columns}<tbody>${block.children.map((row) => `<tr${row.heightPx ? ` style="height:${row.heightPx}px"` : ""}>${row.children.map((cell) => {
|
|
347
|
+
const tag = cell.type === "tableHeaderCell" ? "th" : "td";
|
|
348
|
+
const spans = `${cell.colspan && cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}${cell.rowspan && cell.rowspan > 1 ? ` rowspan="${cell.rowspan}"` : ""}`;
|
|
349
|
+
const style = [
|
|
350
|
+
cell.backgroundColor ? `background-color:${escapeHtml(cell.backgroundColor)}` : "",
|
|
351
|
+
cell.textColor ? `color:${escapeHtml(cell.textColor)}` : "",
|
|
352
|
+
cell.border ? `border:${escapeHtml(cell.border)}` : "",
|
|
353
|
+
].filter(Boolean);
|
|
354
|
+
return `<${tag}${spans}${style.length ? ` style="${style.join(";")}"` : ""}>${cell.children.map(serializeBlock).join("")}</${tag}>`;
|
|
355
|
+
}).join("")}</tr>`).join("")}</tbody></table>`;
|
|
214
356
|
};
|
|
215
357
|
/** Serializes the shadow-only model. It is not used for persisted editor HTML. */
|
|
216
358
|
export const serializeSmartDocument = (document) => document.children.map(serializeBlock).join("");
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type Point = {
|
|
2
|
+
row: number;
|
|
3
|
+
column: number;
|
|
4
|
+
};
|
|
5
|
+
export type DomTableCommand = {
|
|
6
|
+
id: "table.remove";
|
|
7
|
+
input?: Record<string, never>;
|
|
8
|
+
} | {
|
|
9
|
+
id: "table.row.add" | "table.row.remove";
|
|
10
|
+
input: {
|
|
11
|
+
index: number;
|
|
12
|
+
};
|
|
13
|
+
} | {
|
|
14
|
+
id: "table.column.add" | "table.column.remove";
|
|
15
|
+
input: {
|
|
16
|
+
index: number;
|
|
17
|
+
};
|
|
18
|
+
} | {
|
|
19
|
+
id: "table.cell.merge";
|
|
20
|
+
input: {
|
|
21
|
+
start: Point;
|
|
22
|
+
end: Point;
|
|
23
|
+
};
|
|
24
|
+
} | {
|
|
25
|
+
id: "table.cell.split";
|
|
26
|
+
input: Point;
|
|
27
|
+
} | {
|
|
28
|
+
id: "table.header.cell.toggle" | "table.header.row.toggle" | "table.header.column.toggle";
|
|
29
|
+
input: Point;
|
|
30
|
+
} | {
|
|
31
|
+
id: "table.cell.style.set";
|
|
32
|
+
input: {
|
|
33
|
+
start: Point;
|
|
34
|
+
end?: Point;
|
|
35
|
+
backgroundColor?: string | null;
|
|
36
|
+
textColor?: string | null;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
id: "table.column.width.set";
|
|
40
|
+
input: {
|
|
41
|
+
index: number;
|
|
42
|
+
widthPx: number;
|
|
43
|
+
};
|
|
44
|
+
} | {
|
|
45
|
+
id: "table.row.height.set";
|
|
46
|
+
input: {
|
|
47
|
+
index: number;
|
|
48
|
+
heightPx: number;
|
|
49
|
+
};
|
|
50
|
+
} | {
|
|
51
|
+
id: "table.cell.border.toggle";
|
|
52
|
+
input: {
|
|
53
|
+
start: Point;
|
|
54
|
+
end?: Point;
|
|
55
|
+
visibleBorder?: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export declare const executeDomTableCommand: (element: HTMLTableElement, command: DomTableCommand) => HTMLTableElement | null;
|
|
59
|
+
export declare const executeDomTableRemoval: (element: HTMLTableElement) => boolean;
|
|
60
|
+
export declare const executeDomTableInsert: (root: HTMLElement, afterBlockIndex: number, rows: number, columns: number, withHeader?: boolean) => HTMLTableElement | null;
|
|
61
|
+
export {};
|