smartrte-react 0.3.3 → 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.
Files changed (87) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +14 -83
  3. package/dist/adapters/canonicalInlineCommandBridge.d.ts +15 -0
  4. package/dist/adapters/canonicalInlineCommandBridge.js +269 -0
  5. package/dist/adapters/canonicalListCommandBridge.d.ts +26 -0
  6. package/dist/adapters/canonicalListCommandBridge.js +430 -0
  7. package/dist/adapters/documentFormats.d.ts +26 -0
  8. package/dist/adapters/documentFormats.js +49 -0
  9. package/dist/adapters/docxFormat.d.ts +5 -0
  10. package/dist/adapters/docxFormat.js +272 -0
  11. package/dist/adapters/domBlockCommandBridge.d.ts +19 -0
  12. package/dist/adapters/domBlockCommandBridge.js +79 -0
  13. package/dist/adapters/domChecklistCommandBridge.d.ts +6 -0
  14. package/dist/adapters/domChecklistCommandBridge.js +33 -0
  15. package/dist/adapters/domCommandBridge.d.ts +19 -0
  16. package/dist/adapters/domCommandBridge.js +33 -0
  17. package/dist/adapters/domInlineAtomCommandBridge.d.ts +14 -0
  18. package/dist/adapters/domInlineAtomCommandBridge.js +101 -0
  19. package/dist/adapters/domInlineImageCommandBridge.d.ts +14 -0
  20. package/dist/adapters/domInlineImageCommandBridge.js +72 -0
  21. package/dist/adapters/domMoveCommandBridge.d.ts +7 -0
  22. package/dist/adapters/domMoveCommandBridge.js +54 -0
  23. package/dist/adapters/domSelectionBridge.d.ts +3 -1
  24. package/dist/adapters/domSelectionBridge.js +96 -44
  25. package/dist/adapters/domSmartDocument.js +158 -16
  26. package/dist/adapters/domTableCommandBridge.d.ts +35 -0
  27. package/dist/adapters/domTableCommandBridge.js +201 -0
  28. package/dist/adapters/legacyListShadowComparator.d.ts +26 -0
  29. package/dist/adapters/legacyListShadowComparator.js +316 -0
  30. package/dist/adapters/pdfFormat.d.ts +23 -0
  31. package/dist/adapters/pdfFormat.js +204 -0
  32. package/dist/adapters/portableDocxAtoms.d.ts +4 -0
  33. package/dist/adapters/portableDocxAtoms.js +58 -0
  34. package/dist/adapters/styledDocxFormat.d.ts +8 -0
  35. package/dist/adapters/styledDocxFormat.js +190 -0
  36. package/dist/builtInFormatDefinitions.d.ts +5 -0
  37. package/dist/builtInFormatDefinitions.js +82 -0
  38. package/dist/canonicalAuthorityFlag.d.ts +32 -0
  39. package/dist/canonicalAuthorityFlag.js +49 -0
  40. package/dist/canonicalClipboardRuntime.d.ts +15 -0
  41. package/dist/canonicalClipboardRuntime.js +76 -0
  42. package/dist/canonicalEditorRuntime.d.ts +77 -0
  43. package/dist/canonicalEditorRuntime.js +274 -0
  44. package/dist/components/CanonicalAuthorityEditor.d.ts +25 -0
  45. package/dist/components/CanonicalAuthorityEditor.js +518 -0
  46. package/dist/components/ClassicEditor.d.ts +13 -3
  47. package/dist/components/ClassicEditor.js +1309 -1159
  48. package/dist/components/ClassicEditorAuthority.d.ts +38 -0
  49. package/dist/components/ClassicEditorAuthority.js +49 -0
  50. package/dist/components/MediaPicker.d.ts +10 -0
  51. package/dist/components/MediaPicker.js +16 -0
  52. package/dist/editorController.d.ts +78 -0
  53. package/dist/editorController.js +298 -0
  54. package/dist/formatFidelity.d.ts +14 -0
  55. package/dist/formatFidelity.js +107 -0
  56. package/dist/formatRuntime.d.ts +50 -0
  57. package/dist/formatRuntime.js +20 -0
  58. package/dist/index.d.ts +24 -0
  59. package/dist/index.js +10 -0
  60. package/dist/mediaProvider.d.ts +39 -0
  61. package/dist/mediaProvider.js +1 -0
  62. package/dist/pluginRuntime.d.ts +68 -0
  63. package/dist/pluginRuntime.js +78 -0
  64. package/dist/standalone/editor.js +722 -495
  65. package/dist/test-harness/atomShadowComparator.d.ts +14 -0
  66. package/dist/test-harness/atomShadowComparator.js +63 -0
  67. package/dist/test-harness/blockShadowComparator.d.ts +30 -0
  68. package/dist/test-harness/blockShadowComparator.js +140 -0
  69. package/dist/test-harness/clipboardShadowComparator.d.ts +12 -0
  70. package/dist/test-harness/clipboardShadowComparator.js +46 -0
  71. package/dist/test-harness/inlineShadowComparator.d.ts +32 -0
  72. package/dist/test-harness/inlineShadowComparator.js +152 -0
  73. package/dist/test-harness/legacyAtomEngine.d.ts +10 -0
  74. package/dist/test-harness/legacyAtomEngine.js +15 -0
  75. package/dist/test-harness/legacyBlockEngine.d.ts +31 -0
  76. package/dist/test-harness/legacyBlockEngine.js +31 -0
  77. package/dist/test-harness/legacyClipboardEngine.d.ts +14 -0
  78. package/dist/test-harness/legacyClipboardEngine.js +67 -0
  79. package/dist/test-harness/legacyInlineEngine.d.ts +19 -0
  80. package/dist/test-harness/legacyInlineEngine.js +49 -0
  81. package/dist/test-harness/legacyTableEngine.d.ts +12 -0
  82. package/dist/test-harness/legacyTableEngine.js +25 -0
  83. package/dist/test-harness/tableShadowComparator.d.ts +29 -0
  84. package/dist/test-harness/tableShadowComparator.js +101 -0
  85. package/dist/theme.d.ts +1 -1
  86. package/dist/theme.js +28 -6
  87. package/package.json +1 -1
@@ -0,0 +1,430 @@
1
+ import { applyOperations, createList, createNodeId, createScopeIndex, foundationSchema, enterInList, backspaceAtListItemStart, deleteAtListItemEnd, indentList, moveListItems, outdentList, parseCanonicalListHtml, serializeCanonicalListHtml, setListChecked, setListPreset, setListStyle, unwrapList, isTextNode, } from "smartrte-core";
2
+ // ROLLBACK_ADAPTER: canonical-list-dom-roundtrip; unreachable from canonical authority.
3
+ // Temporary parse -> pure command -> render scaffolding. This is not the final
4
+ // editor architecture and must disappear when ClassicEditor becomes canonical-authoritative.
5
+ const semanticNodeSelector = "p,h1,h2,h3,h4,h5,h6,ul,ol,li";
6
+ const nodeId = (element) => {
7
+ var _a;
8
+ (_a = element.dataset).smartId || (_a.smartId = createNodeId());
9
+ return element.dataset.smartId;
10
+ };
11
+ const ensureIds = (root) => {
12
+ if (root.matches(semanticNodeSelector))
13
+ nodeId(root);
14
+ root.querySelectorAll(semanticNodeSelector).forEach(nodeId);
15
+ };
16
+ const pointInOwner = (root, node, offset) => {
17
+ const element = node instanceof HTMLElement ? node : node?.parentElement;
18
+ const owner = element?.closest("p,h1,h2,h3,h4,h5,h6");
19
+ if (!owner || !root.contains(owner))
20
+ return null;
21
+ const range = root.ownerDocument.createRange();
22
+ range.selectNodeContents(owner);
23
+ try {
24
+ range.setEnd(node, offset);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ return { ownerId: nodeId(owner), offset: range.toString().length };
30
+ };
31
+ const captureSelection = (root) => {
32
+ const selection = root.ownerDocument.defaultView?.getSelection();
33
+ if (!selection?.anchorNode || !selection.focusNode)
34
+ return null;
35
+ const anchor = pointInOwner(root, selection.anchorNode, selection.anchorOffset);
36
+ const head = pointInOwner(root, selection.focusNode, selection.focusOffset);
37
+ return anchor && head ? { anchor, head } : null;
38
+ };
39
+ const textPoint = (owner, requested) => {
40
+ const walker = owner.ownerDocument.createTreeWalker(owner, NodeFilter.SHOW_TEXT);
41
+ let remaining = Math.max(0, requested);
42
+ let node = walker.nextNode();
43
+ let last = null;
44
+ while (node) {
45
+ const text = node;
46
+ if (remaining <= text.data.length)
47
+ return { node: text, offset: remaining };
48
+ remaining -= text.data.length;
49
+ last = text;
50
+ node = walker.nextNode();
51
+ }
52
+ return last ? { node: last, offset: last.data.length } : { node: owner, offset: 0 };
53
+ };
54
+ const restoreSelection = (root, snapshot) => {
55
+ if (!snapshot)
56
+ return;
57
+ const byId = (id) => Array.from(root.querySelectorAll("[data-smart-id]"))
58
+ .find((element) => element.dataset.smartId === id) || null;
59
+ const anchorOwner = byId(snapshot.anchor.ownerId);
60
+ const headOwner = byId(snapshot.head.ownerId);
61
+ const selection = root.ownerDocument.defaultView?.getSelection();
62
+ if (!anchorOwner || !headOwner || !selection)
63
+ return;
64
+ const anchor = textPoint(anchorOwner, snapshot.anchor.offset);
65
+ const head = textPoint(headOwner, snapshot.head.offset);
66
+ selection.setBaseAndExtent(anchor.node, anchor.offset, head.node, head.offset);
67
+ };
68
+ const contextFor = (document) => ({
69
+ schema: foundationSchema,
70
+ positions: createScopeIndex().positions(document, foundationSchema),
71
+ });
72
+ const listDepth = (item) => {
73
+ let depth = 0;
74
+ let list = item.parentElement?.closest("ul,ol") || null;
75
+ while (list) {
76
+ depth += 1;
77
+ list = list.parentElement?.closest("li")?.parentElement?.closest("ul,ol") || null;
78
+ }
79
+ return Math.max(0, depth - 1);
80
+ };
81
+ const scopeForItems = (list, items) => ({
82
+ kind: "list-selection",
83
+ listId: nodeId(list),
84
+ items: items.map((item) => ({
85
+ itemId: nodeId(item), depth: listDepth(item), hasChildList: Boolean(item.querySelector(":scope > ul,:scope > ol")),
86
+ })),
87
+ partialSubtree: false,
88
+ promotedFromPartial: false,
89
+ range: { from: { path: [], offset: 0 }, to: { path: [], offset: 0 } },
90
+ isolatingAncestorId: null,
91
+ clamped: false,
92
+ });
93
+ const outerList = (item) => {
94
+ let list = item.parentElement;
95
+ while (list.parentElement?.closest("li")?.parentElement?.matches("ul,ol")) {
96
+ list = list.parentElement.closest("li").parentElement;
97
+ }
98
+ return list;
99
+ };
100
+ const replaceOuterList = (root, outer, document, selection) => {
101
+ const template = root.ownerDocument.createElement("template");
102
+ template.innerHTML = serializeCanonicalListHtml(document, { fragment: true });
103
+ outer.replaceWith(template.content);
104
+ restoreSelection(root, selection);
105
+ };
106
+ const runByOuterList = (root, selected, execute) => {
107
+ if (!selected.length)
108
+ return false;
109
+ selected.forEach((item) => {
110
+ const list = item.parentElement;
111
+ if (!list?.matches("ul,ol"))
112
+ return;
113
+ Array.from(list.children).forEach((child) => {
114
+ if (child instanceof HTMLElement && child.matches("ul,ol")) {
115
+ const previous = child.previousElementSibling;
116
+ if (previous instanceof HTMLElement && previous.tagName === "LI")
117
+ previous.appendChild(child);
118
+ }
119
+ });
120
+ });
121
+ ensureIds(root);
122
+ const selection = captureSelection(root);
123
+ const groups = new Map();
124
+ selected.forEach((item) => {
125
+ const outer = outerList(item);
126
+ groups.set(outer, [...(groups.get(outer) || []), item]);
127
+ });
128
+ let changed = false;
129
+ groups.forEach((items, outer) => {
130
+ const source = parseCanonicalListHtml(outer.outerHTML);
131
+ let document = source;
132
+ const byList = new Map();
133
+ items.forEach((item) => {
134
+ const list = item.parentElement;
135
+ byList.set(list, [...(byList.get(list) || []), item]);
136
+ });
137
+ let groupChanged = false;
138
+ const listGroups = [...byList.entries()].sort(([listA], [listB]) => listDepth(listB) - listDepth(listA));
139
+ listGroups.forEach(([list, directItems]) => {
140
+ const hierarchyRootId = byList.size > 1 ? nodeId(outer) : nodeId(list);
141
+ const result = execute(document, scopeForItems(list, directItems), contextFor(document), hierarchyRootId);
142
+ if (result && result !== document) {
143
+ document = result;
144
+ changed = true;
145
+ groupChanged = true;
146
+ }
147
+ });
148
+ if (groupChanged)
149
+ replaceOuterList(root, outer, document, selection);
150
+ });
151
+ return changed;
152
+ };
153
+ const mergeCompatibleDomLists = (root) => {
154
+ const lists = Array.from(root.querySelectorAll("ul,ol"));
155
+ lists.forEach((list) => {
156
+ let next = list.nextElementSibling;
157
+ while (next?.tagName === list.tagName) {
158
+ const following = next.nextElementSibling;
159
+ while (next.firstChild)
160
+ list.appendChild(next.firstChild);
161
+ next.remove();
162
+ next = following;
163
+ }
164
+ });
165
+ };
166
+ const scopesForAllLists = (document) => {
167
+ const output = [];
168
+ const visit = (node, depth) => {
169
+ if (node.type === "list") {
170
+ output.push({
171
+ depth,
172
+ scope: {
173
+ kind: "list-selection", listId: node.id,
174
+ items: (node.children || []).filter((child) => !isTextNode(child))
175
+ .map((item) => ({ itemId: item.id, depth, hasChildList: (item.children || []).some((child) => !isTextNode(child) && child.type === "list") })),
176
+ partialSubtree: false, promotedFromPartial: false,
177
+ range: { from: { path: [], offset: 0 }, to: { path: [], offset: 0 } },
178
+ isolatingAncestorId: null, clamped: false,
179
+ },
180
+ });
181
+ }
182
+ (node.children || []).forEach((child) => { if (!isTextNode(child))
183
+ visit(child, node.type === "list_item" && child.type === "list" ? depth + 1 : depth); });
184
+ };
185
+ document.children.forEach((child) => { if (!isTextNode(child))
186
+ visit(child, 0); });
187
+ return output;
188
+ };
189
+ const scopesFromList = (document, rootListId) => {
190
+ const all = scopesForAllLists(document);
191
+ const root = all.find((entry) => entry.scope.listId === rootListId);
192
+ if (!root)
193
+ return [];
194
+ const index = createScopeIndex().positions(document, foundationSchema);
195
+ return all.filter((entry) => {
196
+ const resolved = index.positionOf(entry.scope.listId);
197
+ return entry.scope.listId === rootListId || Boolean(resolved?.ancestors.some((ancestor) => ancestor.id === rootListId));
198
+ }).map((entry) => ({ ...entry, depth: entry.depth - root.depth }));
199
+ };
200
+ export const executeCanonicalListDepth = (root, items, direction) => runByOuterList(root, items, (document, scope, ctx) => {
201
+ const operations = direction === "indent"
202
+ ? indentList(document, scope, { nestedListIds: [createNodeId()] }, ctx)
203
+ : outdentList(document, scope, { splitListIds: [createNodeId()] }, ctx);
204
+ return operations.length ? applyOperations(document, operations) : null;
205
+ });
206
+ export const executeCanonicalListMove = (root, items, direction) => runByOuterList(root, items, (document, scope, ctx) => {
207
+ const operations = moveListItems(document, scope, { direction }, ctx);
208
+ return operations.length ? applyOperations(document, operations) : null;
209
+ });
210
+ export const executeCanonicalListStyle = (args) => runByOuterList(args.root, args.items, (document, selectedScope, _ctx, hierarchyRootId) => {
211
+ const stylesForDepth = (rootStyle, depth) => {
212
+ const sequence = rootStyle === "upper-alpha" ? ["upper-alpha", "lower-alpha", "lower-roman"]
213
+ : rootStyle === "upper-roman" ? ["upper-roman", "upper-alpha", "decimal"]
214
+ : rootStyle === "lower-alpha" ? ["lower-alpha", "lower-alpha", "lower-roman"]
215
+ : rootStyle === "lower-roman" ? ["lower-roman", "lower-roman", "lower-alpha"]
216
+ : rootStyle === "decimal-leading-zero" ? ["decimal-leading-zero", "lower-alpha", "lower-roman"]
217
+ : rootStyle === "decimal" ? ["decimal", "lower-alpha", "lower-roman"]
218
+ : rootStyle === "circle" ? ["circle", "square", "disc"]
219
+ : rootStyle === "square" ? ["square", "circle", "disc"]
220
+ : rootStyle === "disc" ? ["disc", "circle", "square"]
221
+ : [rootStyle];
222
+ return sequence[Math.min(depth, sequence.length - 1)];
223
+ };
224
+ let next = document;
225
+ for (const { scope, depth } of scopesFromList(document, hierarchyRootId || selectedScope.listId)) {
226
+ const style = stylesForDepth(args.style, depth);
227
+ const operations = args.preset !== undefined
228
+ ? setListPreset(next, scope, { preset: args.preset }, contextFor(next))
229
+ : setListStyle(next, scope, { style, checkable: args.checkable }, contextFor(next));
230
+ if (operations.length)
231
+ next = applyOperations(next, operations);
232
+ }
233
+ return next === document ? null : next;
234
+ });
235
+ export const executeCanonicalListCheck = (root, item, checked) => {
236
+ ensureIds(root);
237
+ const outer = outerList(item);
238
+ const document = parseCanonicalListHtml(outer.outerHTML);
239
+ const scope = scopeForItems(item.parentElement, [item]);
240
+ const ctx = contextFor(document);
241
+ const operations = setListChecked(document, scope, { checked }, ctx);
242
+ if (!operations.length)
243
+ return false;
244
+ applyOperations(document, operations);
245
+ item.dataset.smartChecked = String(checked);
246
+ item.dataset.checked = String(checked);
247
+ return true;
248
+ };
249
+ export const executeCanonicalListToggle = (args) => {
250
+ const style = args.style || (args.listTag === "ol" ? "decimal" : "disc");
251
+ let changed = false;
252
+ if (args.items.length) {
253
+ const hasPlainBlocks = args.blocks.some((block) => !block.closest("ul,ol"));
254
+ const togglingOff = !hasPlainBlocks && args.items.every((item) => item.parentElement?.tagName.toLowerCase() === args.listTag);
255
+ changed = runByOuterList(args.root, args.items, (document, scope, ctx) => {
256
+ if (togglingOff) {
257
+ const operations = unwrapList(document, scope, { splitListIds: [createNodeId()] }, ctx);
258
+ return operations.length ? applyOperations(document, operations) : null;
259
+ }
260
+ let next = document;
261
+ const rootScope = args.blocks.length ? scopesForAllLists(document)[0]?.scope : scope;
262
+ if (!rootScope)
263
+ return null;
264
+ for (const { scope: descendant, depth } of scopesFromList(document, rootScope.listId)) {
265
+ const descendantStyle = style === "decimal" ? ["decimal", "lower-alpha", "lower-roman"][Math.min(depth, 2)]
266
+ : style === "disc" ? ["disc", "circle", "square"][Math.min(depth, 2)]
267
+ : style;
268
+ const operations = args.preset !== undefined
269
+ ? setListPreset(next, descendant, { preset: args.preset }, contextFor(next))
270
+ : setListStyle(next, descendant, { style: descendantStyle, checkable: args.checkable ?? false }, contextFor(next));
271
+ if (operations.length)
272
+ next = applyOperations(next, operations);
273
+ }
274
+ return next === document ? null : next;
275
+ }) || changed;
276
+ }
277
+ const plain = args.blocks.filter((block) => !block.closest("ul,ol") && block.matches("p,h1,h2,h3,h4,h5,h6"));
278
+ if (plain.length) {
279
+ ensureIds(args.root);
280
+ const selection = captureSelection(args.root);
281
+ const groups = new Map();
282
+ plain.forEach((block) => {
283
+ const parent = block.parentElement;
284
+ if (parent)
285
+ groups.set(parent, [...(groups.get(parent) || []), block]);
286
+ });
287
+ groups.forEach((blocks) => {
288
+ const source = parseCanonicalListHtml(blocks.map((block) => block.outerHTML).join(""));
289
+ const blockIds = source.children.filter((node) => node.type !== "text").map((node) => node.id);
290
+ const scope = {
291
+ kind: "block-range", blockIds, promotedFromPartial: true, commonParentId: source.id,
292
+ range: { from: { path: [], offset: 0 }, to: { path: [], offset: blockIds.length } }, isolatingAncestorId: null, clamped: false,
293
+ };
294
+ const operations = createList(source, scope, {
295
+ // Nesting reconstruction (see foundation createList) can need up to
296
+ // one extra list per block, on top of the one flat list this loop
297
+ // used to always produce — over-provision rather than assume flat.
298
+ listIds: blockIds.map(() => createNodeId()).concat(blockIds.map(() => createNodeId())),
299
+ itemIds: blockIds.map(() => createNodeId()),
300
+ ...(args.preset !== undefined ? { preset: args.preset } : { style }),
301
+ ...(args.checkable !== undefined ? { checkable: args.checkable } : {}),
302
+ }, contextFor(source));
303
+ if (!operations.length)
304
+ return;
305
+ const output = applyOperations(source, operations);
306
+ const template = args.root.ownerDocument.createElement("template");
307
+ template.innerHTML = serializeCanonicalListHtml(output, { fragment: true });
308
+ blocks[0].before(template.content);
309
+ blocks.forEach((block) => block.remove());
310
+ changed = true;
311
+ });
312
+ restoreSelection(args.root, selection);
313
+ }
314
+ if (!plain.length && args.range && !args.range.collapsed) {
315
+ const startElement = args.range.startContainer instanceof HTMLElement ? args.range.startContainer : args.range.startContainer.parentElement;
316
+ const endElement = args.range.endContainer instanceof HTMLElement ? args.range.endContainer : args.range.endContainer.parentElement;
317
+ const startCell = startElement?.closest("td,th") || null;
318
+ const endCell = endElement?.closest("td,th") || null;
319
+ const container = startCell && startCell === endCell ? startCell : args.root;
320
+ const directChild = (node) => {
321
+ let current = node;
322
+ while (current?.parentNode && current.parentNode !== container)
323
+ current = current.parentNode;
324
+ return current?.parentNode === container ? current : null;
325
+ };
326
+ const start = directChild(args.range.startContainer);
327
+ const end = directChild(args.range.endContainer);
328
+ if (start && end) {
329
+ const nodes = Array.from(container.childNodes);
330
+ let first = nodes.indexOf(start);
331
+ let last = nodes.indexOf(end);
332
+ if (start instanceof HTMLElement && start.matches("ul,ol"))
333
+ first += 1;
334
+ while (first <= last && nodes[first] instanceof HTMLBRElement)
335
+ first += 1;
336
+ while (first > 0 && !(nodes[first - 1] instanceof HTMLBRElement))
337
+ first -= 1;
338
+ while (last + 1 < nodes.length && !(nodes[last + 1] instanceof HTMLBRElement))
339
+ last += 1;
340
+ if (first > last || nodes.slice(first, last + 1).some((node) => node instanceof HTMLElement && node.matches("p,h1,h2,h3,h4,h5,h6,ul,ol,table"))) {
341
+ if (changed)
342
+ mergeCompatibleDomLists(args.root);
343
+ return changed;
344
+ }
345
+ const expanded = container.ownerDocument.createRange();
346
+ expanded.setStartBefore(nodes[first]);
347
+ expanded.setEndAfter(nodes[last]);
348
+ const fragment = expanded.extractContents();
349
+ const parts = [[]];
350
+ Array.from(fragment.childNodes).forEach((node) => {
351
+ if (node instanceof HTMLBRElement)
352
+ parts.push([]);
353
+ else
354
+ parts[parts.length - 1].push(node);
355
+ });
356
+ const wrapper = container.ownerDocument.createElement("div");
357
+ parts.filter((part) => part.some((node) => node.textContent?.length || node instanceof HTMLElement)).forEach((part) => {
358
+ const paragraph = container.ownerDocument.createElement("p");
359
+ paragraph.append(...part);
360
+ wrapper.appendChild(paragraph);
361
+ });
362
+ const source = parseCanonicalListHtml(wrapper.innerHTML);
363
+ const blockIds = source.children.filter((node) => !isTextNode(node)).map((node) => node.id);
364
+ if (blockIds.length) {
365
+ const scope = {
366
+ kind: "block-range", blockIds, promotedFromPartial: true, commonParentId: source.id,
367
+ range: { from: { path: [], offset: 0 }, to: { path: [], offset: blockIds.length } }, isolatingAncestorId: null, clamped: false,
368
+ };
369
+ const operations = createList(source, scope, {
370
+ listIds: blockIds.map(() => createNodeId()).concat(blockIds.map(() => createNodeId())),
371
+ itemIds: blockIds.map(() => createNodeId()),
372
+ ...(args.preset !== undefined ? { preset: args.preset } : { style }),
373
+ ...(args.checkable !== undefined ? { checkable: args.checkable } : {}),
374
+ }, contextFor(source));
375
+ const output = applyOperations(source, operations);
376
+ const template = container.ownerDocument.createElement("template");
377
+ template.innerHTML = serializeCanonicalListHtml(output, { fragment: true });
378
+ expanded.insertNode(template.content);
379
+ const before = nodes[first - 1];
380
+ if (before instanceof HTMLBRElement && before.previousElementSibling?.matches("ul,ol"))
381
+ before.remove();
382
+ changed = true;
383
+ }
384
+ }
385
+ }
386
+ if (changed)
387
+ mergeCompatibleDomLists(args.root);
388
+ return changed;
389
+ };
390
+ export const executeCanonicalListStructuralInput = (root, input) => {
391
+ ensureIds(root);
392
+ const native = root.ownerDocument.defaultView?.getSelection();
393
+ if (!native?.isCollapsed || !native.anchorNode)
394
+ return false;
395
+ const element = native.anchorNode instanceof HTMLElement ? native.anchorNode : native.anchorNode.parentElement;
396
+ const owner = element?.closest("p,h1,h2,h3,h4,h5,h6");
397
+ const item = owner?.closest("li");
398
+ if (!owner || !item || !root.contains(item))
399
+ return false;
400
+ const snapshot = pointInOwner(root, native.anchorNode, native.anchorOffset);
401
+ if (!snapshot)
402
+ return false;
403
+ const outer = outerList(item);
404
+ const document = parseCanonicalListHtml(outer.outerHTML);
405
+ const ctx = contextFor(document);
406
+ const ownerId = nodeId(owner);
407
+ const content = ctx.positions.contentRangeOf(ownerId);
408
+ if (!content)
409
+ return false;
410
+ const pos = { path: [...content.from.path], offset: snapshot.offset };
411
+ const result = input === "enter"
412
+ ? enterInList(document, pos, {
413
+ itemId: createNodeId(), blockId: createNodeId(), emptyBlockId: createNodeId(), splitListId: createNodeId(),
414
+ }, ctx)
415
+ : input === "backspace"
416
+ ? backspaceAtListItemStart(document, pos, ctx)
417
+ : deleteAtListItemEnd(document, pos, ctx);
418
+ if (!result?.operations.length)
419
+ return false;
420
+ const output = applyOperations(document, result.operations);
421
+ const targetContent = contextFor(output).positions.contentRangeOf(result.selectionTarget.ownerId);
422
+ const targetOffset = targetContent
423
+ ? Math.min(result.selectionTarget.offset, targetContent.to.offset)
424
+ : 0;
425
+ replaceOuterList(root, outer, output, {
426
+ anchor: { ownerId: result.selectionTarget.ownerId, offset: targetOffset },
427
+ head: { ownerId: result.selectionTarget.ownerId, offset: targetOffset },
428
+ });
429
+ return true;
430
+ };
@@ -0,0 +1,26 @@
1
+ import { type SmartDocument } from "smartrte-core";
2
+ export type TextDocumentFormat = "html" | "markdown";
3
+ export interface DocumentImportContext {
4
+ ownerDocument: Document;
5
+ }
6
+ export interface DocumentFormatAdapter {
7
+ id: string;
8
+ mediaType: string;
9
+ extension: string;
10
+ importDocument: (source: string, context: DocumentImportContext) => SmartDocument;
11
+ exportDocument: (document: SmartDocument) => string;
12
+ }
13
+ export declare class DocumentFormatRegistry {
14
+ private readonly adapters;
15
+ constructor(adapters?: readonly DocumentFormatAdapter[]);
16
+ register(adapter: DocumentFormatAdapter): this;
17
+ has(format: string): boolean;
18
+ get(format: string): DocumentFormatAdapter;
19
+ list(): readonly DocumentFormatAdapter[];
20
+ }
21
+ export declare const createDocumentFormatRegistry: (adapters?: readonly DocumentFormatAdapter[]) => DocumentFormatRegistry;
22
+ export declare const defaultDocumentFormatRegistry: DocumentFormatRegistry;
23
+ export declare const getDocumentFormatAdapter: (format: TextDocumentFormat) => DocumentFormatAdapter;
24
+ export declare const importTextDocument: (format: TextDocumentFormat, source: string, context: DocumentImportContext) => SmartDocument;
25
+ export declare const exportTextDocument: (format: TextDocumentFormat, document: SmartDocument) => string;
26
+ export declare const roundTripTextDocument: (format: TextDocumentFormat, document: SmartDocument, context: DocumentImportContext) => SmartDocument;
@@ -0,0 +1,49 @@
1
+ import { compatibilityHtmlToMarkdown, markdownToCompatibilityHtml, normalizeSmartDocument, } from "smartrte-core";
2
+ import { serializeSmartDocument, smartDocumentFromHtml } from "./domSmartDocument.js";
3
+ const htmlAdapter = {
4
+ id: "html",
5
+ mediaType: "text/html;charset=utf-8",
6
+ extension: "html",
7
+ importDocument: (source, context) => normalizeSmartDocument(smartDocumentFromHtml(source, context.ownerDocument)),
8
+ exportDocument: (document) => serializeSmartDocument(normalizeSmartDocument(document)),
9
+ };
10
+ const markdownAdapter = {
11
+ id: "markdown",
12
+ mediaType: "text/markdown;charset=utf-8",
13
+ extension: "md",
14
+ importDocument: (source, context) => normalizeSmartDocument(smartDocumentFromHtml(markdownToCompatibilityHtml(source), context.ownerDocument)),
15
+ exportDocument: (document) => compatibilityHtmlToMarkdown(serializeSmartDocument(normalizeSmartDocument(document))),
16
+ };
17
+ export class DocumentFormatRegistry {
18
+ constructor(adapters = []) {
19
+ this.adapters = new Map();
20
+ adapters.forEach((adapter) => this.register(adapter));
21
+ }
22
+ register(adapter) {
23
+ if (!adapter.id.trim())
24
+ throw new Error("Document format adapter id cannot be empty.");
25
+ if (this.adapters.has(adapter.id)) {
26
+ throw new Error(`Duplicate document format adapter "${adapter.id}".`);
27
+ }
28
+ this.adapters.set(adapter.id, adapter);
29
+ return this;
30
+ }
31
+ has(format) {
32
+ return this.adapters.has(format);
33
+ }
34
+ get(format) {
35
+ const adapter = this.adapters.get(format);
36
+ if (!adapter)
37
+ throw new Error(`Unknown document format "${format}".`);
38
+ return adapter;
39
+ }
40
+ list() {
41
+ return [...this.adapters.values()];
42
+ }
43
+ }
44
+ export const createDocumentFormatRegistry = (adapters = [htmlAdapter, markdownAdapter]) => new DocumentFormatRegistry(adapters);
45
+ export const defaultDocumentFormatRegistry = createDocumentFormatRegistry();
46
+ export const getDocumentFormatAdapter = (format) => defaultDocumentFormatRegistry.get(format);
47
+ export const importTextDocument = (format, source, context) => getDocumentFormatAdapter(format).importDocument(source, context);
48
+ export const exportTextDocument = (format, document) => getDocumentFormatAdapter(format).exportDocument(document);
49
+ export const roundTripTextDocument = (format, document, context) => importTextDocument(format, exportTextDocument(format, document), context);
@@ -0,0 +1,5 @@
1
+ import { type SmartDocument } from "smartrte-core";
2
+ export declare const DOCX_MEDIA_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
3
+ export declare const smartDocumentToDocxXml: (document: SmartDocument) => string;
4
+ export declare const exportDocxDocument: (document: SmartDocument) => Promise<Blob>;
5
+ export declare const importDocxDocumentWithMammoth: (arrayBuffer: ArrayBuffer, ownerDocument: Document) => Promise<SmartDocument>;