smartrte-react 0.3.4 → 0.3.5
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 +9 -0
- package/README.md +14 -83
- package/dist/adapters/canonicalInlineCommandBridge.d.ts +15 -0
- package/dist/adapters/canonicalInlineCommandBridge.js +269 -0
- package/dist/adapters/canonicalListCommandBridge.d.ts +26 -0
- package/dist/adapters/canonicalListCommandBridge.js +430 -0
- package/dist/adapters/documentFormats.d.ts +26 -0
- package/dist/adapters/documentFormats.js +49 -0
- package/dist/adapters/docxFormat.d.ts +5 -0
- package/dist/adapters/docxFormat.js +272 -0
- package/dist/adapters/domBlockCommandBridge.d.ts +19 -0
- package/dist/adapters/domBlockCommandBridge.js +79 -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 +14 -0
- package/dist/adapters/domInlineAtomCommandBridge.js +101 -0
- package/dist/adapters/domInlineImageCommandBridge.d.ts +14 -0
- package/dist/adapters/domInlineImageCommandBridge.js +72 -0
- package/dist/adapters/domMoveCommandBridge.d.ts +7 -0
- package/dist/adapters/domMoveCommandBridge.js +54 -0
- package/dist/adapters/domSelectionBridge.d.ts +3 -1
- package/dist/adapters/domSelectionBridge.js +96 -44
- package/dist/adapters/domSmartDocument.js +158 -16
- package/dist/adapters/domTableCommandBridge.d.ts +35 -0
- package/dist/adapters/domTableCommandBridge.js +201 -0
- package/dist/adapters/legacyListShadowComparator.d.ts +26 -0
- package/dist/adapters/legacyListShadowComparator.js +316 -0
- package/dist/adapters/pdfFormat.d.ts +23 -0
- package/dist/adapters/pdfFormat.js +204 -0
- package/dist/adapters/portableDocxAtoms.d.ts +4 -0
- package/dist/adapters/portableDocxAtoms.js +58 -0
- package/dist/adapters/styledDocxFormat.d.ts +8 -0
- package/dist/adapters/styledDocxFormat.js +190 -0
- package/dist/builtInFormatDefinitions.d.ts +5 -0
- package/dist/builtInFormatDefinitions.js +82 -0
- package/dist/canonicalAuthorityFlag.d.ts +32 -0
- package/dist/canonicalAuthorityFlag.js +49 -0
- package/dist/canonicalClipboardRuntime.d.ts +15 -0
- package/dist/canonicalClipboardRuntime.js +76 -0
- package/dist/canonicalEditorRuntime.d.ts +77 -0
- package/dist/canonicalEditorRuntime.js +274 -0
- package/dist/components/CanonicalAuthorityEditor.d.ts +25 -0
- package/dist/components/CanonicalAuthorityEditor.js +518 -0
- package/dist/components/ClassicEditor.d.ts +13 -3
- package/dist/components/ClassicEditor.js +1226 -1114
- package/dist/components/ClassicEditorAuthority.d.ts +38 -0
- package/dist/components/ClassicEditorAuthority.js +49 -0
- package/dist/components/MediaPicker.d.ts +10 -0
- package/dist/components/MediaPicker.js +16 -0
- package/dist/editorController.d.ts +78 -0
- package/dist/editorController.js +298 -0
- package/dist/formatFidelity.d.ts +14 -0
- package/dist/formatFidelity.js +107 -0
- package/dist/formatRuntime.d.ts +50 -0
- package/dist/formatRuntime.js +20 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +10 -0
- package/dist/mediaProvider.d.ts +39 -0
- package/dist/mediaProvider.js +1 -0
- package/dist/pluginRuntime.d.ts +68 -0
- package/dist/pluginRuntime.js +78 -0
- package/dist/standalone/editor.js +722 -495
- package/dist/test-harness/atomShadowComparator.d.ts +14 -0
- package/dist/test-harness/atomShadowComparator.js +63 -0
- package/dist/test-harness/blockShadowComparator.d.ts +30 -0
- package/dist/test-harness/blockShadowComparator.js +140 -0
- package/dist/test-harness/clipboardShadowComparator.d.ts +12 -0
- package/dist/test-harness/clipboardShadowComparator.js +46 -0
- package/dist/test-harness/inlineShadowComparator.d.ts +32 -0
- package/dist/test-harness/inlineShadowComparator.js +152 -0
- package/dist/test-harness/legacyAtomEngine.d.ts +10 -0
- package/dist/test-harness/legacyAtomEngine.js +15 -0
- package/dist/test-harness/legacyBlockEngine.d.ts +31 -0
- package/dist/test-harness/legacyBlockEngine.js +31 -0
- package/dist/test-harness/legacyClipboardEngine.d.ts +14 -0
- package/dist/test-harness/legacyClipboardEngine.js +67 -0
- package/dist/test-harness/legacyInlineEngine.d.ts +19 -0
- package/dist/test-harness/legacyInlineEngine.js +49 -0
- package/dist/test-harness/legacyTableEngine.d.ts +12 -0
- package/dist/test-harness/legacyTableEngine.js +25 -0
- package/dist/test-harness/tableShadowComparator.d.ts +29 -0
- package/dist/test-harness/tableShadowComparator.js +101 -0
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +28 -0
- package/package.json +1 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import JSZip from "jszip";
|
|
2
|
+
import mammoth from "mammoth";
|
|
3
|
+
import { normalizeSmartDocument } from "smartrte-core";
|
|
4
|
+
import { portableFormulaMarker, portableImageMarker, restorePortableDocxAtoms } from "./portableDocxAtoms.js";
|
|
5
|
+
import { smartDocumentFromHtml } from "./domSmartDocument.js";
|
|
6
|
+
export const DOCX_MEDIA_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
7
|
+
const xmlEscape = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
8
|
+
const colorValue = (value) => {
|
|
9
|
+
const hex = /^#([0-9a-f]{6})$/i.exec(value.trim());
|
|
10
|
+
if (hex)
|
|
11
|
+
return hex[1].toUpperCase();
|
|
12
|
+
const rgb = /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i.exec(value.trim());
|
|
13
|
+
return rgb
|
|
14
|
+
? rgb.slice(1, 4).map((part) => Math.max(0, Math.min(255, Number(part))).toString(16).padStart(2, "0")).join("").toUpperCase()
|
|
15
|
+
: "";
|
|
16
|
+
};
|
|
17
|
+
const runProperties = (marks = []) => marks.map((mark) => {
|
|
18
|
+
if (mark.type === "bold")
|
|
19
|
+
return "<w:b/>";
|
|
20
|
+
if (mark.type === "italic")
|
|
21
|
+
return "<w:i/>";
|
|
22
|
+
if (mark.type === "underline")
|
|
23
|
+
return '<w:u w:val="single"/>';
|
|
24
|
+
if (mark.type === "strike")
|
|
25
|
+
return "<w:strike/>";
|
|
26
|
+
if (mark.type === "superscript")
|
|
27
|
+
return '<w:vertAlign w:val="superscript"/>';
|
|
28
|
+
if (mark.type === "subscript")
|
|
29
|
+
return '<w:vertAlign w:val="subscript"/>';
|
|
30
|
+
if (mark.type === "textColor") {
|
|
31
|
+
const color = colorValue(mark.value);
|
|
32
|
+
return color ? `<w:color w:val="${color}"/>` : "";
|
|
33
|
+
}
|
|
34
|
+
if (mark.type === "backgroundColor") {
|
|
35
|
+
const color = colorValue(mark.value);
|
|
36
|
+
return color ? `<w:shd w:val="clear" w:color="auto" w:fill="${color}"/>` : "";
|
|
37
|
+
}
|
|
38
|
+
if (mark.type === "fontSize")
|
|
39
|
+
return `<w:sz w:val="${Math.max(2, Math.round(mark.valuePx * 1.5))}"/>`;
|
|
40
|
+
return "";
|
|
41
|
+
}).join("");
|
|
42
|
+
const textRun = (text, properties = "") => `<w:r>${properties ? `<w:rPr>${properties}</w:rPr>` : ""}<w:t xml:space="preserve">${xmlEscape(text)}</w:t></w:r>`;
|
|
43
|
+
const createSerializationContext = () => ({
|
|
44
|
+
relationships: [],
|
|
45
|
+
hyperlinkIds: new Map(),
|
|
46
|
+
media: [],
|
|
47
|
+
drawingId: 1,
|
|
48
|
+
});
|
|
49
|
+
const hyperlinkRelationshipId = (context, href) => {
|
|
50
|
+
const existing = context.hyperlinkIds.get(href);
|
|
51
|
+
if (existing)
|
|
52
|
+
return existing;
|
|
53
|
+
const id = `rId${context.relationships.length + 2}`;
|
|
54
|
+
context.hyperlinkIds.set(href, id);
|
|
55
|
+
context.relationships.push({ id, target: href, kind: "hyperlink" });
|
|
56
|
+
return id;
|
|
57
|
+
};
|
|
58
|
+
const embeddableImage = (src) => {
|
|
59
|
+
const match = /^data:(image\/(?:png|jpeg|gif));base64,([a-z0-9+/=\s]+)$/i.exec(src);
|
|
60
|
+
if (!match)
|
|
61
|
+
return;
|
|
62
|
+
return {
|
|
63
|
+
mimeType: match[1].toLowerCase(),
|
|
64
|
+
base64: match[2].replace(/\s/g, ""),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
const imageRun = (node, context) => {
|
|
68
|
+
const image = embeddableImage(node.src);
|
|
69
|
+
if (!image)
|
|
70
|
+
return textRun(portableImageMarker(node.src, node.alt, node.title));
|
|
71
|
+
const extension = image.mimeType === "image/jpeg" ? "jpg" : image.mimeType.split("/")[1];
|
|
72
|
+
const filename = `image${context.media.length + 1}.${extension}`;
|
|
73
|
+
const relationshipId = `rId${context.relationships.length + 2}`;
|
|
74
|
+
const drawingId = context.drawingId++;
|
|
75
|
+
context.media.push({ filename, ...image });
|
|
76
|
+
context.relationships.push({
|
|
77
|
+
id: relationshipId,
|
|
78
|
+
target: `media/${filename}`,
|
|
79
|
+
kind: "image",
|
|
80
|
+
});
|
|
81
|
+
const widthEmu = Math.max(1, Math.round((node.width || 96) * 9525));
|
|
82
|
+
const heightEmu = Math.max(1, Math.round((node.height || 96) * 9525));
|
|
83
|
+
const name = xmlEscape(node.title || node.alt || filename);
|
|
84
|
+
const description = xmlEscape(node.alt || "");
|
|
85
|
+
return `<w:r><w:drawing><wp:inline xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${widthEmu}" cy="${heightEmu}"/><wp:docPr id="${drawingId}" name="${name}" descr="${description}"/><a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:nvPicPr><pic:cNvPr id="0" name="${name}" descr="${description}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${relationshipId}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${widthEmu}" cy="${heightEmu}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
86
|
+
};
|
|
87
|
+
const formulaRun = (node) => {
|
|
88
|
+
const source = xmlEscape(node.value);
|
|
89
|
+
const marker = xmlEscape(portableFormulaMarker(node.value, node.displayText));
|
|
90
|
+
return `<m:oMath><m:r><m:t>${source}</m:t></m:r></m:oMath><w:r><w:rPr><w:vanish/></w:rPr><w:t>${marker}</w:t></w:r>`;
|
|
91
|
+
};
|
|
92
|
+
const inlineRun = (node, context) => {
|
|
93
|
+
if (node.type === "formula")
|
|
94
|
+
return formulaRun(node);
|
|
95
|
+
if (node.type === "inlineImage")
|
|
96
|
+
return imageRun(node, context);
|
|
97
|
+
const properties = runProperties(node.marks);
|
|
98
|
+
const runs = node.text.split("\n").map((part, index) => `${index ? "<w:r><w:br/></w:r>" : ""}${part ? textRun(part, properties) : ""}`).join("");
|
|
99
|
+
const link = node.marks?.find((mark) => mark.type === "link");
|
|
100
|
+
if (!link || !runs)
|
|
101
|
+
return runs;
|
|
102
|
+
return `<w:hyperlink r:id="${hyperlinkRelationshipId(context, link.href)}"${link.target === "_blank" ? ' w:history="1"' : ""}>${runs}</w:hyperlink>`;
|
|
103
|
+
};
|
|
104
|
+
const paragraphProperties = (block, extra = "") => [
|
|
105
|
+
block.alignment ? `<w:jc w:val="${xmlEscape(block.alignment)}"/>` : "",
|
|
106
|
+
block.indent ? `<w:ind w:left="${block.indent * 480}"/>` : "",
|
|
107
|
+
extra,
|
|
108
|
+
].join("");
|
|
109
|
+
const paragraphXml = (children, block, context, extraProperties = "") => {
|
|
110
|
+
const properties = paragraphProperties(block, extraProperties);
|
|
111
|
+
const content = children.map((node) => inlineRun(node, context)).join("") || textRun("");
|
|
112
|
+
return `<w:p>${properties ? `<w:pPr>${properties}</w:pPr>` : ""}${content}</w:p>`;
|
|
113
|
+
};
|
|
114
|
+
const tableXml = (block, context) => {
|
|
115
|
+
const grid = [];
|
|
116
|
+
const placements = [];
|
|
117
|
+
block.children.forEach((row, rowIndex) => {
|
|
118
|
+
grid[rowIndex] || (grid[rowIndex] = []);
|
|
119
|
+
let column = 0;
|
|
120
|
+
row.children.forEach((cell) => {
|
|
121
|
+
var _a;
|
|
122
|
+
while (grid[rowIndex][column])
|
|
123
|
+
column += 1;
|
|
124
|
+
const placement = {
|
|
125
|
+
row: rowIndex,
|
|
126
|
+
column,
|
|
127
|
+
rowspan: cell.rowspan || 1,
|
|
128
|
+
colspan: cell.colspan || 1,
|
|
129
|
+
cell,
|
|
130
|
+
};
|
|
131
|
+
placements.push(placement);
|
|
132
|
+
for (let rowOffset = 0; rowOffset < placement.rowspan; rowOffset += 1) {
|
|
133
|
+
grid[_a = rowIndex + rowOffset] || (grid[_a] = []);
|
|
134
|
+
for (let columnOffset = 0; columnOffset < placement.colspan; columnOffset += 1) {
|
|
135
|
+
grid[rowIndex + rowOffset][column + columnOffset] = placement;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
column += placement.colspan;
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
const width = Math.max(1, ...grid.map((row) => row.length));
|
|
142
|
+
const widths = Array.from({ length: width }, (_, index) => block.columnWidths?.[index] || 96);
|
|
143
|
+
const gridXml = `<w:tblGrid>${widths.map((widthPx) => `<w:gridCol w:w="${Math.max(1, Math.round(widthPx * 15))}"/>`).join("")}</w:tblGrid>`;
|
|
144
|
+
const borderNone = '<w:tcBorders><w:top w:val="nil"/><w:left w:val="nil"/><w:bottom w:val="nil"/><w:right w:val="nil"/></w:tcBorders>';
|
|
145
|
+
const rows = block.children.map((row, rowIndex) => {
|
|
146
|
+
const header = grid[rowIndex]?.every((placement) => placement?.cell.type === "tableHeaderCell");
|
|
147
|
+
const rowProperties = [
|
|
148
|
+
header ? "<w:tblHeader/>" : "",
|
|
149
|
+
row.heightPx ? `<w:trHeight w:val="${Math.round(row.heightPx * 15)}" w:hRule="atLeast"/>` : "",
|
|
150
|
+
].join("");
|
|
151
|
+
let cells = "";
|
|
152
|
+
for (let column = 0; column < width;) {
|
|
153
|
+
const placement = grid[rowIndex]?.[column];
|
|
154
|
+
if (!placement || placement.column !== column) {
|
|
155
|
+
column += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const continuation = placement.row < rowIndex;
|
|
159
|
+
const shade = colorValue(placement.cell.backgroundColor || "");
|
|
160
|
+
const properties = [
|
|
161
|
+
placement.colspan > 1 ? `<w:gridSpan w:val="${placement.colspan}"/>` : "",
|
|
162
|
+
placement.rowspan > 1
|
|
163
|
+
? continuation ? "<w:vMerge/>" : '<w:vMerge w:val="restart"/>'
|
|
164
|
+
: "",
|
|
165
|
+
shade ? `<w:shd w:val="clear" w:color="auto" w:fill="${shade}"/>` : "",
|
|
166
|
+
placement.cell.border === "none" ? borderNone : "",
|
|
167
|
+
].join("");
|
|
168
|
+
const content = continuation
|
|
169
|
+
? "<w:p/>"
|
|
170
|
+
: placement.cell.children.map((child) => blockXml(child, context)).join("") || "<w:p/>";
|
|
171
|
+
cells += `<w:tc><w:tcPr>${properties}</w:tcPr>${content}</w:tc>`;
|
|
172
|
+
column += placement.colspan;
|
|
173
|
+
}
|
|
174
|
+
return `<w:tr>${rowProperties ? `<w:trPr>${rowProperties}</w:trPr>` : ""}${cells}</w:tr>`;
|
|
175
|
+
}).join("");
|
|
176
|
+
return `<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:color="D1D5DB"/><w:left w:val="single" w:sz="4" w:color="D1D5DB"/><w:bottom w:val="single" w:sz="4" w:color="D1D5DB"/><w:right w:val="single" w:sz="4" w:color="D1D5DB"/><w:insideH w:val="single" w:sz="4" w:color="D1D5DB"/><w:insideV w:val="single" w:sz="4" w:color="D1D5DB"/></w:tblBorders></w:tblPr>${gridXml}${rows}</w:tbl>`;
|
|
177
|
+
};
|
|
178
|
+
const listStyleNumId = {
|
|
179
|
+
disc: 1, circle: 2, square: 3, decimal: 4,
|
|
180
|
+
"decimal-leading-zero": 4,
|
|
181
|
+
"lower-alpha": 5, "upper-alpha": 6, "lower-roman": 7, "upper-roman": 8,
|
|
182
|
+
};
|
|
183
|
+
const blockXml = (block, context, listLevel = 0) => {
|
|
184
|
+
if (block.type === "paragraph")
|
|
185
|
+
return paragraphXml(block.children, block, context);
|
|
186
|
+
if (block.type === "heading") {
|
|
187
|
+
return paragraphXml(block.children, block, context, `<w:pStyle w:val="Heading${block.level}"/>`);
|
|
188
|
+
}
|
|
189
|
+
if (block.type === "codeBlock")
|
|
190
|
+
return paragraphXml([{ type: "text", text: block.text, marks: [{ type: "code" }] }], block, context);
|
|
191
|
+
if (block.type === "blockquote") {
|
|
192
|
+
return block.children.map((child) => {
|
|
193
|
+
if (child.type === "paragraph")
|
|
194
|
+
return paragraphXml(child.children, child, context, '<w:ind w:left="720"/>');
|
|
195
|
+
return blockXml(child, context, listLevel);
|
|
196
|
+
}).join("");
|
|
197
|
+
}
|
|
198
|
+
if (block.type === "list") {
|
|
199
|
+
return block.children.map((item) => item.children.map((child) => {
|
|
200
|
+
if (child.type === "paragraph") {
|
|
201
|
+
return paragraphXml(child.children, child, context, `<w:numPr><w:ilvl w:val="${Math.min(listLevel, 8)}"/><w:numId w:val="${listStyleNumId[block.style]}"/></w:numPr>`);
|
|
202
|
+
}
|
|
203
|
+
return blockXml(child, context, listLevel + 1);
|
|
204
|
+
}).join("")).join("");
|
|
205
|
+
}
|
|
206
|
+
if (block.type === "table")
|
|
207
|
+
return tableXml(block, context);
|
|
208
|
+
if (block.type === "image")
|
|
209
|
+
return paragraphXml([{
|
|
210
|
+
type: "inlineImage",
|
|
211
|
+
src: block.src,
|
|
212
|
+
alt: block.alt,
|
|
213
|
+
title: block.title,
|
|
214
|
+
width: block.width,
|
|
215
|
+
height: block.height,
|
|
216
|
+
}], block, context);
|
|
217
|
+
return paragraphXml([{ type: "text", text: block.title || block.src }], block, context);
|
|
218
|
+
};
|
|
219
|
+
const numberingLevelXml = (level, format, text, font) => `<w:lvl w:ilvl="${level}"><w:start w:val="1"/><w:numFmt w:val="${format}"/><w:lvlText w:val="${text}"/><w:lvlJc w:val="left"/><w:pPr><w:tabs><w:tab w:val="num" w:pos="${720 + level * 360}"/></w:tabs><w:ind w:left="${720 + level * 360}" w:hanging="360"/></w:pPr>${font ? `<w:rPr><w:rFonts w:ascii="${font}" w:hAnsi="${font}"/></w:rPr>` : ""}</w:lvl>`;
|
|
220
|
+
const numberingXml = () => {
|
|
221
|
+
const definitions = [
|
|
222
|
+
{ id: 1, format: "bullet", text: "•", font: "Symbol" },
|
|
223
|
+
{ id: 2, format: "bullet", text: "○", font: "Arial" },
|
|
224
|
+
{ id: 3, format: "bullet", text: "▪", font: "Arial" },
|
|
225
|
+
{ id: 4, format: "decimal", text: "%1." },
|
|
226
|
+
{ id: 5, format: "lowerLetter", text: "%1." },
|
|
227
|
+
{ id: 6, format: "upperLetter", text: "%1." },
|
|
228
|
+
{ id: 7, format: "lowerRoman", text: "%1." },
|
|
229
|
+
{ id: 8, format: "upperRoman", text: "%1." },
|
|
230
|
+
];
|
|
231
|
+
const abstracts = definitions.map(({ id, format, text, font }) => `<w:abstractNum w:abstractNumId="${id}"><w:multiLevelType w:val="multilevel"/>${Array.from({ length: 9 }, (_, level) => numberingLevelXml(level, format, text.replace("%1", `%${level + 1}`), font)).join("")}</w:abstractNum>`).join("");
|
|
232
|
+
const instances = definitions.map(({ id }) => `<w:num w:numId="${id}"><w:abstractNumId w:val="${id}"/></w:num>`).join("");
|
|
233
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:numbering xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">${abstracts}${instances}</w:numbering>`;
|
|
234
|
+
};
|
|
235
|
+
const serializeDocxDocument = (document) => {
|
|
236
|
+
const normalized = normalizeSmartDocument(document);
|
|
237
|
+
const context = createSerializationContext();
|
|
238
|
+
const body = normalized.children.map((block) => blockXml(block, context)).join("");
|
|
239
|
+
return {
|
|
240
|
+
documentXml: `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"><w:body>${body}<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720"/></w:sectPr></w:body></w:document>`,
|
|
241
|
+
relationships: context.relationships,
|
|
242
|
+
media: context.media,
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
export const smartDocumentToDocxXml = (document) => serializeDocxDocument(document).documentXml;
|
|
246
|
+
export const exportDocxDocument = async (document) => {
|
|
247
|
+
const zip = new JSZip();
|
|
248
|
+
const serialized = serializeDocxDocument(document);
|
|
249
|
+
const imageTypes = [...new Set(serialized.media.map(({ filename, mimeType }) => ({
|
|
250
|
+
extension: filename.split(".").pop(),
|
|
251
|
+
mimeType,
|
|
252
|
+
})).map((entry) => JSON.stringify(entry)))].map((entry) => JSON.parse(entry));
|
|
253
|
+
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/>${imageTypes.map(({ extension, mimeType }) => `<Default Extension="${extension}" ContentType="${mimeType}"/>`).join("")}<Override PartName="/word/document.xml" ContentType="${DOCX_MEDIA_TYPE}.main+xml"/><Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/></Types>`);
|
|
254
|
+
zip.folder("_rels")?.file(".rels", `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>`);
|
|
255
|
+
zip.folder("word")?.file("document.xml", serialized.documentXml);
|
|
256
|
+
zip.folder("word")?.file("numbering.xml", numberingXml());
|
|
257
|
+
serialized.media.forEach(({ filename, base64 }) => {
|
|
258
|
+
zip.folder("word")?.folder("media")?.file(filename, base64, { base64: true });
|
|
259
|
+
});
|
|
260
|
+
const relationships = serialized.relationships.map((relationship) => relationship.kind === "hyperlink"
|
|
261
|
+
? `<Relationship Id="${relationship.id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="${xmlEscape(relationship.target)}" TargetMode="External"/>`
|
|
262
|
+
: `<Relationship Id="${relationship.id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="${relationship.target}"/>`).join("");
|
|
263
|
+
zip.folder("word")?.folder("_rels")?.file("document.xml.rels", `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/>${relationships}</Relationships>`);
|
|
264
|
+
return zip.generateAsync({ type: "blob", mimeType: DOCX_MEDIA_TYPE });
|
|
265
|
+
};
|
|
266
|
+
export const importDocxDocumentWithMammoth = async (arrayBuffer, ownerDocument) => {
|
|
267
|
+
// Mammoth's browser entry reads `arrayBuffer`, while its Node entry reads
|
|
268
|
+
// `buffer`. Supplying both keeps the adapter portable across bundlers/tests.
|
|
269
|
+
const result = await mammoth.convertToHtml({ arrayBuffer, buffer: arrayBuffer });
|
|
270
|
+
const html = restorePortableDocxAtoms(result.value, ownerDocument);
|
|
271
|
+
return normalizeSmartDocument(smartDocumentFromHtml(html, ownerDocument));
|
|
272
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SetBlockTypeInput, type TextAlignment } from "smartrte-core";
|
|
2
|
+
export type DomBlockCommand = {
|
|
3
|
+
id: "block-type.set";
|
|
4
|
+
input: Pick<SetBlockTypeInput, "type" | "level">;
|
|
5
|
+
} | {
|
|
6
|
+
id: "alignment.set";
|
|
7
|
+
input: {
|
|
8
|
+
alignment: TextAlignment | null;
|
|
9
|
+
};
|
|
10
|
+
} | {
|
|
11
|
+
id: "blockquote.toggle";
|
|
12
|
+
} | {
|
|
13
|
+
id: "code-block.toggle";
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Runs a core block command against a contiguous sibling run and replaces only
|
|
17
|
+
* that run. Returns null when the DOM shape needs a more specialized adapter.
|
|
18
|
+
*/
|
|
19
|
+
export declare const executeDomBlockCommand: (blocks: readonly HTMLElement[], command: DomBlockCommand) => HTMLElement[] | null;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { alignmentPlugin, blockquotePlugin, blockTypePlugin, codeBlockPlugin, createSmartEditor, } from "smartrte-core";
|
|
2
|
+
import { serializeSmartDocument, smartDocumentFromHtml } from "./domSmartDocument.js";
|
|
3
|
+
const pluginFor = (id) => {
|
|
4
|
+
if (id === "block-type.set")
|
|
5
|
+
return blockTypePlugin;
|
|
6
|
+
if (id === "alignment.set")
|
|
7
|
+
return alignmentPlugin;
|
|
8
|
+
if (id === "blockquote.toggle")
|
|
9
|
+
return blockquotePlugin;
|
|
10
|
+
return codeBlockPlugin;
|
|
11
|
+
};
|
|
12
|
+
const isContiguousSiblingRun = (blocks) => {
|
|
13
|
+
if (!blocks.length || !blocks[0].parentElement)
|
|
14
|
+
return false;
|
|
15
|
+
const parent = blocks[0].parentElement;
|
|
16
|
+
if (!blocks.every((block) => block.parentElement === parent))
|
|
17
|
+
return false;
|
|
18
|
+
const siblings = Array.from(parent.children);
|
|
19
|
+
const indexes = blocks.map((block) => siblings.indexOf(block));
|
|
20
|
+
return indexes.every((index, offset) => index >= 0 && (offset === 0 || index === indexes[offset - 1] + 1));
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Runs a core block command against a contiguous sibling run and replaces only
|
|
24
|
+
* that run. Returns null when the DOM shape needs a more specialized adapter.
|
|
25
|
+
*/
|
|
26
|
+
export const executeDomBlockCommand = (blocks, command) => {
|
|
27
|
+
if (!isContiguousSiblingRun(blocks))
|
|
28
|
+
return null;
|
|
29
|
+
const ownerDocument = blocks[0].ownerDocument;
|
|
30
|
+
const document = smartDocumentFromHtml(blocks.map((block) => block.outerHTML).join(""), ownerDocument);
|
|
31
|
+
if (document.children.length !== blocks.length)
|
|
32
|
+
return null;
|
|
33
|
+
const indexes = document.children.map((_, index) => index);
|
|
34
|
+
const editor = createSmartEditor({
|
|
35
|
+
state: { document, selection: { type: "all" } },
|
|
36
|
+
plugins: [pluginFor(command.id)],
|
|
37
|
+
});
|
|
38
|
+
const input = command.id === "alignment.set"
|
|
39
|
+
? { paths: indexes.map((index) => [index]), alignment: command.input.alignment }
|
|
40
|
+
: command.id === "block-type.set"
|
|
41
|
+
? { parentPath: [], blockIndexes: indexes, ...command.input }
|
|
42
|
+
: { parentPath: [], blockIndexes: indexes };
|
|
43
|
+
if (!editor.execute(command.id, input))
|
|
44
|
+
return null;
|
|
45
|
+
const container = ownerDocument.createElement("div");
|
|
46
|
+
container.innerHTML = serializeSmartDocument(editor.state.document);
|
|
47
|
+
const replacements = Array.from(container.children);
|
|
48
|
+
if (!replacements.length)
|
|
49
|
+
return null;
|
|
50
|
+
if (command.id === "alignment.set" && replacements.length === blocks.length) {
|
|
51
|
+
replacements.forEach((replacement, index) => {
|
|
52
|
+
blocks[index].style.textAlign = replacement.style.textAlign;
|
|
53
|
+
if (!blocks[index].getAttribute("style"))
|
|
54
|
+
blocks[index].removeAttribute("style");
|
|
55
|
+
});
|
|
56
|
+
return [...blocks];
|
|
57
|
+
}
|
|
58
|
+
const parent = blocks[0].parentElement;
|
|
59
|
+
const reference = blocks[0];
|
|
60
|
+
replacements.forEach((replacement, index) => {
|
|
61
|
+
if (replacements.length === blocks.length) {
|
|
62
|
+
const previous = blocks[index];
|
|
63
|
+
const generatedAlignment = replacement.style.textAlign;
|
|
64
|
+
const previousStyle = previous.getAttribute("style");
|
|
65
|
+
const previousClass = previous.getAttribute("class");
|
|
66
|
+
if (previousStyle)
|
|
67
|
+
replacement.setAttribute("style", previousStyle);
|
|
68
|
+
if (generatedAlignment)
|
|
69
|
+
replacement.style.textAlign = generatedAlignment;
|
|
70
|
+
if (!replacement.getAttribute("style"))
|
|
71
|
+
replacement.removeAttribute("style");
|
|
72
|
+
if (previousClass)
|
|
73
|
+
replacement.setAttribute("class", previousClass);
|
|
74
|
+
}
|
|
75
|
+
parent.insertBefore(replacement, reference);
|
|
76
|
+
});
|
|
77
|
+
blocks.forEach((block) => block.remove());
|
|
78
|
+
return replacements;
|
|
79
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates and executes a checklist mutation through core, then projects the
|
|
3
|
+
* changed attribute onto the existing DOM item. Keeping the subtree in place
|
|
4
|
+
* preserves focus, selection, event targets, and framework-owned DOM identity.
|
|
5
|
+
*/
|
|
6
|
+
export declare const executeDomChecklistItemCommand: (list: HTMLElement, item: HTMLElement, checked?: boolean) => HTMLElement | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { checklistPlugin, createSmartEditor, listPlugin } from "smartrte-core";
|
|
2
|
+
import { smartDocumentFromHtml } from "./domSmartDocument.js";
|
|
3
|
+
/**
|
|
4
|
+
* Validates and executes a checklist mutation through core, then projects the
|
|
5
|
+
* changed attribute onto the existing DOM item. Keeping the subtree in place
|
|
6
|
+
* preserves focus, selection, event targets, and framework-owned DOM identity.
|
|
7
|
+
*/
|
|
8
|
+
export const executeDomChecklistItemCommand = (list, item, checked) => {
|
|
9
|
+
if (list.dataset.srteChecklist !== "true" || item.parentElement !== list)
|
|
10
|
+
return null;
|
|
11
|
+
const items = Array.from(list.children).filter((child) => child instanceof HTMLElement && child.tagName === "LI");
|
|
12
|
+
const index = items.indexOf(item);
|
|
13
|
+
if (index < 0)
|
|
14
|
+
return null;
|
|
15
|
+
const document = smartDocumentFromHtml(list.outerHTML, list.ownerDocument);
|
|
16
|
+
if (document.children.length !== 1 || document.children[0].type !== "list")
|
|
17
|
+
return null;
|
|
18
|
+
const editor = createSmartEditor({
|
|
19
|
+
state: { document, selection: { type: "node", path: [0, index] } },
|
|
20
|
+
plugins: [listPlugin, checklistPlugin],
|
|
21
|
+
});
|
|
22
|
+
if (!editor.execute("checklist.set-checked", { path: [0, index], checked }))
|
|
23
|
+
return null;
|
|
24
|
+
const nextList = editor.state.document.children[0];
|
|
25
|
+
if (nextList.type !== "list")
|
|
26
|
+
return null;
|
|
27
|
+
const nextItem = nextList.children[index];
|
|
28
|
+
if (!nextItem || nextItem.type !== "listItem")
|
|
29
|
+
return null;
|
|
30
|
+
item.dataset.checked = nextItem.checked ? "true" : "false";
|
|
31
|
+
item.removeAttribute("data-srte-checked");
|
|
32
|
+
return list;
|
|
33
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SmartRtePlugin, type SmartSelection } from "smartrte-core";
|
|
2
|
+
export interface DomCommandResult {
|
|
3
|
+
html: string;
|
|
4
|
+
selection: SmartSelection;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Executes one model command against the live editor boundary.
|
|
8
|
+
*
|
|
9
|
+
* The DOM is parsed once, the command runs entirely in core, and the resulting
|
|
10
|
+
* document replaces the DOM once. History and change emission remain owned by
|
|
11
|
+
* the React host until the complete editor runtime is migrated.
|
|
12
|
+
*/
|
|
13
|
+
export declare const executeDomCommand: <Input>(args: {
|
|
14
|
+
root: HTMLElement;
|
|
15
|
+
plugins: SmartRtePlugin[];
|
|
16
|
+
commandId: string;
|
|
17
|
+
input?: Input;
|
|
18
|
+
selection?: Selection | null;
|
|
19
|
+
}) => DomCommandResult | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createSmartEditor, } from "smartrte-core";
|
|
2
|
+
import { restoreSelectionToDom, selectionFromDom } from "./domSelectionBridge.js";
|
|
3
|
+
import { serializeSmartDocument, smartDocumentFromEditorRoot, } from "./domSmartDocument.js";
|
|
4
|
+
/**
|
|
5
|
+
* Executes one model command against the live editor boundary.
|
|
6
|
+
*
|
|
7
|
+
* The DOM is parsed once, the command runs entirely in core, and the resulting
|
|
8
|
+
* document replaces the DOM once. History and change emission remain owned by
|
|
9
|
+
* the React host until the complete editor runtime is migrated.
|
|
10
|
+
*/
|
|
11
|
+
export const executeDomCommand = (args) => {
|
|
12
|
+
// These nodes are not represented by SmartDocument yet. Falling back is
|
|
13
|
+
// mandatory so a supported command can never erase unsupported content.
|
|
14
|
+
if (args.root.querySelector("img,video,audio,iframe")) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const selection = selectionFromDom(args.root, args.selection === undefined
|
|
18
|
+
? args.root.ownerDocument.defaultView?.getSelection() || null
|
|
19
|
+
: args.selection);
|
|
20
|
+
if (!selection)
|
|
21
|
+
return null;
|
|
22
|
+
const { document } = smartDocumentFromEditorRoot(args.root);
|
|
23
|
+
const editor = createSmartEditor({
|
|
24
|
+
state: { document, selection },
|
|
25
|
+
plugins: args.plugins,
|
|
26
|
+
});
|
|
27
|
+
if (!editor.execute(args.commandId, args.input))
|
|
28
|
+
return null;
|
|
29
|
+
const html = serializeSmartDocument(editor.state.document);
|
|
30
|
+
args.root.innerHTML = html;
|
|
31
|
+
restoreSelectionToDom(args.root, editor.state.selection);
|
|
32
|
+
return { html, selection: editor.state.selection };
|
|
33
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Path } from "smartrte-core";
|
|
2
|
+
export declare const inlineAtomPathFromDom: (root: HTMLElement, atom: HTMLElement) => Path | null;
|
|
3
|
+
/**
|
|
4
|
+
* Runs deletion through the owning feature plugin and removes only the
|
|
5
|
+
* validated live atom. Caret placement remains a DOM-host responsibility.
|
|
6
|
+
*/
|
|
7
|
+
export declare const executeDomInlineAtomDelete: (root: HTMLElement, atom: HTMLElement) => boolean;
|
|
8
|
+
export interface DomFormulaInput {
|
|
9
|
+
value: string;
|
|
10
|
+
displayText?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Validates formula insertion through core, then projects only the new atom. */
|
|
13
|
+
export declare const executeDomFormulaInsert: (root: HTMLElement, input: DomFormulaInput, domSelection?: Selection | null) => HTMLElement | null;
|
|
14
|
+
export declare const adjacentInlineAtom: (range: Range, direction: "backward" | "forward") => HTMLElement | null;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createSmartEditor, formulaPlugin, getNodeAtPath, mediaPlugin, } from "smartrte-core";
|
|
2
|
+
import { isEditorOnlyElement, pathForDomElement, selectionFromDom } from "./domSelectionBridge.js";
|
|
3
|
+
import { smartDocumentFromEditorRoot } from "./domSmartDocument.js";
|
|
4
|
+
const atomType = (element) => {
|
|
5
|
+
if (element.tagName.toLowerCase() === "img")
|
|
6
|
+
return "inlineImage";
|
|
7
|
+
if (element.hasAttribute("data-formula"))
|
|
8
|
+
return "formula";
|
|
9
|
+
return null;
|
|
10
|
+
};
|
|
11
|
+
const inlineUnits = (root) => {
|
|
12
|
+
const units = [];
|
|
13
|
+
const visit = (node) => {
|
|
14
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
15
|
+
if (node.textContent)
|
|
16
|
+
units.push(node);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!(node instanceof Element) || isEditorOnlyElement(node))
|
|
20
|
+
return;
|
|
21
|
+
if (atomType(node)) {
|
|
22
|
+
units.push(node);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
Array.from(node.childNodes).forEach(visit);
|
|
26
|
+
};
|
|
27
|
+
Array.from(root.childNodes).forEach(visit);
|
|
28
|
+
return units;
|
|
29
|
+
};
|
|
30
|
+
export const inlineAtomPathFromDom = (root, atom) => {
|
|
31
|
+
const leaf = atom.closest("p,h1,h2,h3,h4,h5,h6");
|
|
32
|
+
if (!leaf || !root.contains(leaf))
|
|
33
|
+
return null;
|
|
34
|
+
const leafPath = pathForDomElement(leaf, root);
|
|
35
|
+
const index = inlineUnits(leaf).indexOf(atom);
|
|
36
|
+
return leafPath && index >= 0 ? [...leafPath, index] : null;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Runs deletion through the owning feature plugin and removes only the
|
|
40
|
+
* validated live atom. Caret placement remains a DOM-host responsibility.
|
|
41
|
+
*/
|
|
42
|
+
export const executeDomInlineAtomDelete = (root, atom) => {
|
|
43
|
+
const type = atomType(atom);
|
|
44
|
+
const path = type && inlineAtomPathFromDom(root, atom);
|
|
45
|
+
if (!type || !path)
|
|
46
|
+
return false;
|
|
47
|
+
const selection = selectionFromDom(root, root.ownerDocument.defaultView?.getSelection() || null) || { type: "node", path };
|
|
48
|
+
const { document } = smartDocumentFromEditorRoot(root);
|
|
49
|
+
const editor = createSmartEditor({
|
|
50
|
+
state: { document, selection },
|
|
51
|
+
plugins: type === "formula" ? [formulaPlugin] : [mediaPlugin],
|
|
52
|
+
});
|
|
53
|
+
const command = type === "formula" ? "formula.delete" : "image.delete-inline";
|
|
54
|
+
if (!editor.execute(command, { path }))
|
|
55
|
+
return false;
|
|
56
|
+
atom.remove();
|
|
57
|
+
return true;
|
|
58
|
+
};
|
|
59
|
+
/** Validates formula insertion through core, then projects only the new atom. */
|
|
60
|
+
export const executeDomFormulaInsert = (root, input, domSelection = root.ownerDocument.defaultView?.getSelection() || null) => {
|
|
61
|
+
if (!domSelection?.rangeCount || !domSelection.isCollapsed)
|
|
62
|
+
return null;
|
|
63
|
+
const modelSelection = selectionFromDom(root, domSelection);
|
|
64
|
+
if (!modelSelection)
|
|
65
|
+
return null;
|
|
66
|
+
const { document } = smartDocumentFromEditorRoot(root);
|
|
67
|
+
const editor = createSmartEditor({
|
|
68
|
+
state: { document, selection: modelSelection },
|
|
69
|
+
plugins: [formulaPlugin],
|
|
70
|
+
});
|
|
71
|
+
if (!editor.execute("formula.insert", input))
|
|
72
|
+
return null;
|
|
73
|
+
const selectionAfter = editor.state.selection;
|
|
74
|
+
if (selectionAfter.type !== "text")
|
|
75
|
+
return null;
|
|
76
|
+
const formulaPath = [
|
|
77
|
+
...selectionAfter.anchor.path.slice(0, -1),
|
|
78
|
+
selectionAfter.anchor.path[selectionAfter.anchor.path.length - 1] - 1,
|
|
79
|
+
];
|
|
80
|
+
const atom = getNodeAtPath(editor.state.document, formulaPath);
|
|
81
|
+
if (atom?.type !== "formula")
|
|
82
|
+
return null;
|
|
83
|
+
const range = domSelection.getRangeAt(0);
|
|
84
|
+
const span = root.ownerDocument.createElement("span");
|
|
85
|
+
span.dataset.formula = atom.value;
|
|
86
|
+
span.textContent = atom.displayText ?? atom.value;
|
|
87
|
+
range.insertNode(span);
|
|
88
|
+
range.setStartAfter(span);
|
|
89
|
+
range.collapse(true);
|
|
90
|
+
domSelection.removeAllRanges();
|
|
91
|
+
domSelection.addRange(range);
|
|
92
|
+
return span;
|
|
93
|
+
};
|
|
94
|
+
export const adjacentInlineAtom = (range, direction) => {
|
|
95
|
+
if (!range.collapsed || range.startContainer.nodeType !== Node.ELEMENT_NODE)
|
|
96
|
+
return null;
|
|
97
|
+
const container = range.startContainer;
|
|
98
|
+
const index = direction === "backward" ? range.startOffset - 1 : range.startOffset;
|
|
99
|
+
const candidate = container.childNodes[index];
|
|
100
|
+
return candidate instanceof HTMLElement && atomType(candidate) ? candidate : null;
|
|
101
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DomInlineImageInput {
|
|
2
|
+
src: string;
|
|
3
|
+
alt?: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Executes inline image insertion through core and projects only the resulting
|
|
10
|
+
* atom into the existing DOM range. Unrelated DOM and metadata stay untouched.
|
|
11
|
+
*/
|
|
12
|
+
export declare const executeDomInlineImageCommand: (root: HTMLElement, input: DomInlineImageInput, domSelection?: Selection | null) => HTMLImageElement | null;
|
|
13
|
+
/** Validates an inline image attribute update through core, then mutates it in place. */
|
|
14
|
+
export declare const executeDomInlineImageUpdate: (root: HTMLElement, image: HTMLImageElement, input: Omit<DomInlineImageInput, "src">) => boolean;
|