smartrte-core 0.2.1 → 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 +12 -0
- package/README.md +23 -0
- package/dist/command.d.ts +8 -8
- package/dist/editor.d.ts +32 -0
- package/dist/editor.js +121 -0
- package/dist/foundation/annotations/index.d.ts +2 -0
- package/dist/foundation/annotations/index.js +2 -0
- package/dist/foundation/annotations/range.d.ts +54 -0
- package/dist/foundation/annotations/range.js +158 -0
- package/dist/foundation/annotations/types.d.ts +33 -0
- package/dist/foundation/annotations/types.js +1 -0
- package/dist/foundation/atom/commands.d.ts +11 -0
- package/dist/foundation/atom/commands.js +118 -0
- package/dist/foundation/atom/composition.d.ts +8 -0
- package/dist/foundation/atom/composition.js +22 -0
- package/dist/foundation/atom/declarations.d.ts +2 -0
- package/dist/foundation/atom/declarations.js +17 -0
- package/dist/foundation/atom/formats.d.ts +21 -0
- package/dist/foundation/atom/formats.js +77 -0
- package/dist/foundation/atom/index.d.ts +10 -0
- package/dist/foundation/atom/index.js +10 -0
- package/dist/foundation/atom/lifecycle.d.ts +12 -0
- package/dist/foundation/atom/lifecycle.js +41 -0
- package/dist/foundation/atom/persistence.d.ts +3 -0
- package/dist/foundation/atom/persistence.js +20 -0
- package/dist/foundation/atom/plugin.d.ts +20 -0
- package/dist/foundation/atom/plugin.js +55 -0
- package/dist/foundation/atom/schema.d.ts +3 -0
- package/dist/foundation/atom/schema.js +29 -0
- package/dist/foundation/atom/security.d.ts +10 -0
- package/dist/foundation/atom/security.js +20 -0
- package/dist/foundation/atom/selection.d.ts +3 -0
- package/dist/foundation/atom/selection.js +4 -0
- package/dist/foundation/atom/types.d.ts +47 -0
- package/dist/foundation/atom/types.js +1 -0
- package/dist/foundation/block/commands.d.ts +17 -0
- package/dist/foundation/block/commands.js +197 -0
- package/dist/foundation/block/formats.d.ts +33 -0
- package/dist/foundation/block/formats.js +57 -0
- package/dist/foundation/block/index.d.ts +5 -0
- package/dist/foundation/block/index.js +5 -0
- package/dist/foundation/block/input.d.ts +19 -0
- package/dist/foundation/block/input.js +67 -0
- package/dist/foundation/block/plugin.d.ts +26 -0
- package/dist/foundation/block/plugin.js +79 -0
- package/dist/foundation/block/schema.d.ts +49 -0
- package/dist/foundation/block/schema.js +27 -0
- package/dist/foundation/block/types.d.ts +40 -0
- package/dist/foundation/block/types.js +1 -0
- package/dist/foundation/boundaries.d.ts +11 -0
- package/dist/foundation/boundaries.js +117 -0
- package/dist/foundation/clipboard/detection.d.ts +3 -0
- package/dist/foundation/clipboard/detection.js +29 -0
- package/dist/foundation/clipboard/diagnostics.d.ts +24 -0
- package/dist/foundation/clipboard/diagnostics.js +59 -0
- package/dist/foundation/clipboard/index.d.ts +8 -0
- package/dist/foundation/clipboard/index.js +8 -0
- package/dist/foundation/clipboard/insertion.d.ts +22 -0
- package/dist/foundation/clipboard/insertion.js +210 -0
- package/dist/foundation/clipboard/normalizers.d.ts +8 -0
- package/dist/foundation/clipboard/normalizers.js +229 -0
- package/dist/foundation/clipboard/pipeline.d.ts +13 -0
- package/dist/foundation/clipboard/pipeline.js +58 -0
- package/dist/foundation/clipboard/sanitizer.d.ts +3 -0
- package/dist/foundation/clipboard/sanitizer.js +58 -0
- package/dist/foundation/clipboard/serialization.d.ts +5 -0
- package/dist/foundation/clipboard/serialization.js +33 -0
- package/dist/foundation/clipboard/transfer.d.ts +11 -0
- package/dist/foundation/clipboard/transfer.js +108 -0
- package/dist/foundation/clipboard/types.d.ts +65 -0
- package/dist/foundation/clipboard/types.js +1 -0
- package/dist/foundation/collab/index.d.ts +3 -0
- package/dist/foundation/collab/index.js +3 -0
- package/dist/foundation/collab/rebase.d.ts +28 -0
- package/dist/foundation/collab/rebase.js +82 -0
- package/dist/foundation/collab/transform.d.ts +73 -0
- package/dist/foundation/collab/transform.js +251 -0
- package/dist/foundation/collab/types.d.ts +52 -0
- package/dist/foundation/collab/types.js +1 -0
- package/dist/foundation/comments/fromSelection.d.ts +16 -0
- package/dist/foundation/comments/fromSelection.js +24 -0
- package/dist/foundation/comments/index.d.ts +3 -0
- package/dist/foundation/comments/index.js +3 -0
- package/dist/foundation/comments/rebase.d.ts +31 -0
- package/dist/foundation/comments/rebase.js +43 -0
- package/dist/foundation/comments/types.d.ts +37 -0
- package/dist/foundation/comments/types.js +1 -0
- package/dist/foundation/diff/documentDiff.d.ts +12 -0
- package/dist/foundation/diff/documentDiff.js +148 -0
- package/dist/foundation/diff/index.d.ts +3 -0
- package/dist/foundation/diff/index.js +3 -0
- package/dist/foundation/diff/textDiff.d.ts +12 -0
- package/dist/foundation/diff/textDiff.js +114 -0
- package/dist/foundation/diff/types.d.ts +74 -0
- package/dist/foundation/diff/types.js +1 -0
- package/dist/foundation/editor.d.ts +138 -0
- package/dist/foundation/editor.js +398 -0
- package/dist/foundation/formats/codec.d.ts +45 -0
- package/dist/foundation/formats/codec.js +1 -0
- package/dist/foundation/formats/docx/export.d.ts +4 -0
- package/dist/foundation/formats/docx/export.js +285 -0
- package/dist/foundation/formats/docx/import.d.ts +9 -0
- package/dist/foundation/formats/docx/import.js +37 -0
- package/dist/foundation/formats/docx/nestingGuard.d.ts +40 -0
- package/dist/foundation/formats/docx/nestingGuard.js +66 -0
- package/dist/foundation/formats/docx/portableAtoms.d.ts +4 -0
- package/dist/foundation/formats/docx/portableAtoms.js +66 -0
- package/dist/foundation/formats/docx/styledImport.d.ts +10 -0
- package/dist/foundation/formats/docx/styledImport.js +205 -0
- package/dist/foundation/formats/featureCodecs.d.ts +37 -0
- package/dist/foundation/formats/featureCodecs.js +69 -0
- package/dist/foundation/formats/fidelity.d.ts +31 -0
- package/dist/foundation/formats/fidelity.js +123 -0
- package/dist/foundation/formats/index.d.ts +7 -0
- package/dist/foundation/formats/index.js +7 -0
- package/dist/foundation/formats/pdf/format.d.ts +29 -0
- package/dist/foundation/formats/pdf/format.js +190 -0
- package/dist/foundation/history.d.ts +20 -0
- package/dist/foundation/history.js +121 -0
- package/dist/foundation/identity.d.ts +8 -0
- package/dist/foundation/identity.js +30 -0
- package/dist/foundation/index.d.ts +30 -0
- package/dist/foundation/index.js +30 -0
- package/dist/foundation/list/commands.d.ts +28 -0
- package/dist/foundation/list/commands.js +422 -0
- package/dist/foundation/list/formats.d.ts +20 -0
- package/dist/foundation/list/formats.js +795 -0
- package/dist/foundation/list/index.d.ts +8 -0
- package/dist/foundation/list/index.js +8 -0
- package/dist/foundation/list/input.d.ts +35 -0
- package/dist/foundation/list/input.js +320 -0
- package/dist/foundation/list/plugin.d.ts +21 -0
- package/dist/foundation/list/plugin.js +71 -0
- package/dist/foundation/list/presets.d.ts +16 -0
- package/dist/foundation/list/presets.js +21 -0
- package/dist/foundation/list/schema.d.ts +35 -0
- package/dist/foundation/list/schema.js +29 -0
- package/dist/foundation/list/shadow.d.ts +54 -0
- package/dist/foundation/list/shadow.js +75 -0
- package/dist/foundation/list/shadowCorpus.d.ts +10 -0
- package/dist/foundation/list/shadowCorpus.js +41 -0
- package/dist/foundation/list/types.d.ts +50 -0
- package/dist/foundation/list/types.js +1 -0
- package/dist/foundation/mapping.d.ts +11 -0
- package/dist/foundation/mapping.js +40 -0
- package/dist/foundation/marks/canonical.d.ts +9 -0
- package/dist/foundation/marks/canonical.js +72 -0
- package/dist/foundation/marks/commands.d.ts +21 -0
- package/dist/foundation/marks/commands.js +136 -0
- package/dist/foundation/marks/dom.d.ts +2 -0
- package/dist/foundation/marks/dom.js +46 -0
- package/dist/foundation/marks/editor.d.ts +8 -0
- package/dist/foundation/marks/editor.js +50 -0
- package/dist/foundation/marks/formats.d.ts +37 -0
- package/dist/foundation/marks/formats.js +49 -0
- package/dist/foundation/marks/hardBreak.d.ts +8 -0
- package/dist/foundation/marks/hardBreak.js +80 -0
- package/dist/foundation/marks/index.d.ts +11 -0
- package/dist/foundation/marks/index.js +11 -0
- package/dist/foundation/marks/normalization.d.ts +2 -0
- package/dist/foundation/marks/normalization.js +49 -0
- package/dist/foundation/marks/plugin.d.ts +14 -0
- package/dist/foundation/marks/plugin.js +63 -0
- package/dist/foundation/marks/resolveMarkRun.d.ts +8 -0
- package/dist/foundation/marks/resolveMarkRun.js +47 -0
- package/dist/foundation/marks/schema.d.ts +167 -0
- package/dist/foundation/marks/schema.js +50 -0
- package/dist/foundation/marks/stored.d.ts +2 -0
- package/dist/foundation/marks/stored.js +31 -0
- package/dist/foundation/marks/types.d.ts +40 -0
- package/dist/foundation/marks/types.js +1 -0
- package/dist/foundation/modelDom.d.ts +38 -0
- package/dist/foundation/modelDom.js +327 -0
- package/dist/foundation/normalization.d.ts +22 -0
- package/dist/foundation/normalization.js +132 -0
- package/dist/foundation/operations.d.ts +28 -0
- package/dist/foundation/operations.js +568 -0
- package/dist/foundation/plugin/builtins.d.ts +10 -0
- package/dist/foundation/plugin/builtins.js +27 -0
- package/dist/foundation/plugin/dispatch.d.ts +33 -0
- package/dist/foundation/plugin/dispatch.js +34 -0
- package/dist/foundation/plugin/index.d.ts +4 -0
- package/dist/foundation/plugin/index.js +4 -0
- package/dist/foundation/plugin/registry.d.ts +36 -0
- package/dist/foundation/plugin/registry.js +115 -0
- package/dist/foundation/plugin/types.d.ts +141 -0
- package/dist/foundation/plugin/types.js +13 -0
- package/dist/foundation/positions.d.ts +20 -0
- package/dist/foundation/positions.js +121 -0
- package/dist/foundation/schema.d.ts +57 -0
- package/dist/foundation/schema.js +371 -0
- package/dist/foundation/schemaBuilder.d.ts +41 -0
- package/dist/foundation/schemaBuilder.js +96 -0
- package/dist/foundation/scope/index.d.ts +2 -0
- package/dist/foundation/scope/index.js +2 -0
- package/dist/foundation/scope/resolveScope.d.ts +13 -0
- package/dist/foundation/scope/resolveScope.js +860 -0
- package/dist/foundation/scope/types.d.ts +122 -0
- package/dist/foundation/scope/types.js +1 -0
- package/dist/foundation/security/urlPolicy.d.ts +21 -0
- package/dist/foundation/security/urlPolicy.js +75 -0
- package/dist/foundation/structural/contentTarget.d.ts +21 -0
- package/dist/foundation/structural/contentTarget.js +51 -0
- package/dist/foundation/structural/index.d.ts +2 -0
- package/dist/foundation/structural/index.js +2 -0
- package/dist/foundation/structural/move.d.ts +7 -0
- package/dist/foundation/structural/move.js +35 -0
- package/dist/foundation/suggestions/index.d.ts +3 -0
- package/dist/foundation/suggestions/index.js +3 -0
- package/dist/foundation/suggestions/inline.d.ts +64 -0
- package/dist/foundation/suggestions/inline.js +116 -0
- package/dist/foundation/suggestions/structural.d.ts +40 -0
- package/dist/foundation/suggestions/structural.js +47 -0
- package/dist/foundation/suggestions/types.d.ts +19 -0
- package/dist/foundation/suggestions/types.js +1 -0
- package/dist/foundation/surface/index.d.ts +3 -0
- package/dist/foundation/surface/index.js +3 -0
- package/dist/foundation/surface/input.d.ts +127 -0
- package/dist/foundation/surface/input.js +1761 -0
- package/dist/foundation/surface/renderer.d.ts +84 -0
- package/dist/foundation/surface/renderer.js +974 -0
- package/dist/foundation/surface/types.d.ts +48 -0
- package/dist/foundation/surface/types.js +1 -0
- package/dist/foundation/table/commands.d.ts +31 -0
- package/dist/foundation/table/commands.js +470 -0
- package/dist/foundation/table/formats.d.ts +26 -0
- package/dist/foundation/table/formats.js +40 -0
- package/dist/foundation/table/grid.d.ts +8 -0
- package/dist/foundation/table/grid.js +178 -0
- package/dist/foundation/table/index.d.ts +6 -0
- package/dist/foundation/table/index.js +6 -0
- package/dist/foundation/table/plugin.d.ts +23 -0
- package/dist/foundation/table/plugin.js +119 -0
- package/dist/foundation/table/schema.d.ts +2 -0
- package/dist/foundation/table/schema.js +38 -0
- package/dist/foundation/table/selection.d.ts +7 -0
- package/dist/foundation/table/selection.js +35 -0
- package/dist/foundation/table/types.d.ts +109 -0
- package/dist/foundation/table/types.js +1 -0
- package/dist/foundation/transactions.d.ts +41 -0
- package/dist/foundation/transactions.js +90 -0
- package/dist/foundation/types.d.ts +261 -0
- package/dist/foundation/types.js +1 -0
- package/dist/foundation/versioning/diffVersions.d.ts +5 -0
- package/dist/foundation/versioning/diffVersions.js +3 -0
- package/dist/foundation/versioning/index.d.ts +2 -0
- package/dist/foundation/versioning/index.js +2 -0
- package/dist/foundation/versioning/types.d.ts +17 -0
- package/dist/foundation/versioning/types.js +1 -0
- package/dist/history.d.ts +20 -0
- package/dist/history.js +165 -0
- package/dist/html/compatibility.js +7 -1
- package/dist/index.d.ts +1 -14
- package/dist/index.js +9 -14
- package/dist/legacy/index.d.ts +45 -0
- package/dist/legacy/index.js +45 -0
- package/dist/legacyCommands/alignment.d.ts +20 -0
- package/dist/{commands → legacyCommands}/alignment.js +17 -0
- package/dist/legacyCommands/blocks.d.ts +14 -0
- package/dist/legacyCommands/blocks.js +105 -0
- package/dist/legacyCommands/checklist.d.ts +11 -0
- package/dist/legacyCommands/checklist.js +45 -0
- package/dist/legacyCommands/code.d.ts +15 -0
- package/dist/{commands → legacyCommands}/code.js +25 -1
- package/dist/legacyCommands/formula.d.ts +7 -0
- package/dist/legacyCommands/formula.js +67 -0
- package/dist/legacyCommands/inlineAtoms.d.ts +7 -0
- package/dist/legacyCommands/inlineAtoms.js +58 -0
- package/dist/legacyCommands/list.d.ts +38 -0
- package/dist/legacyCommands/list.js +477 -0
- package/dist/{commands → legacyCommands}/marks.d.ts +3 -0
- package/dist/{commands → legacyCommands}/marks.js +28 -11
- package/dist/legacyCommands/media.d.ts +31 -0
- package/dist/legacyCommands/media.js +205 -0
- package/dist/legacyCommands/move.d.ts +14 -0
- package/dist/legacyCommands/move.js +91 -0
- package/dist/legacyCommands/table.d.ts +91 -0
- package/dist/legacyCommands/table.js +547 -0
- package/dist/listPresets.d.ts +12 -0
- package/dist/listPresets.js +6 -0
- package/dist/listScope.d.ts +27 -0
- package/dist/listScope.js +104 -0
- package/dist/markdown/compatibility.d.ts +2 -0
- package/dist/markdown/compatibility.js +94 -2
- package/dist/marks.d.ts +5 -5
- package/dist/model.d.ts +65 -9
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.js +31 -0
- package/dist/plugins/alignment.d.ts +3 -0
- package/dist/plugins/alignment.js +6 -0
- package/dist/plugins/basicFormatting.d.ts +9 -0
- package/dist/plugins/basicFormatting.js +24 -0
- package/dist/plugins/blockType.d.ts +3 -0
- package/dist/plugins/blockType.js +6 -0
- package/dist/plugins/blockquote.d.ts +3 -0
- package/dist/plugins/blockquote.js +6 -0
- package/dist/plugins/checklist.d.ts +3 -0
- package/dist/plugins/checklist.js +10 -0
- package/dist/plugins/codeBlock.d.ts +3 -0
- package/dist/plugins/codeBlock.js +6 -0
- package/dist/plugins/formula.d.ts +3 -0
- package/dist/plugins/formula.js +9 -0
- package/dist/plugins/list.d.ts +3 -0
- package/dist/plugins/list.js +10 -0
- package/dist/plugins/media.d.ts +3 -0
- package/dist/plugins/media.js +12 -0
- package/dist/plugins/move.d.ts +3 -0
- package/dist/plugins/move.js +9 -0
- package/dist/plugins/table.d.ts +3 -0
- package/dist/plugins/table.js +22 -0
- package/dist/preset.d.ts +12 -0
- package/dist/preset.js +40 -0
- package/dist/schema.d.ts +22 -0
- package/dist/schema.js +333 -0
- package/dist/security/urlPolicy.d.ts +1 -17
- package/dist/security/urlPolicy.js +3 -69
- package/dist/selection.d.ts +1 -1
- package/dist/selectionMapping.d.ts +4 -0
- package/dist/selectionMapping.js +222 -0
- package/dist/table.d.ts +3 -3
- package/dist/transaction.d.ts +35 -12
- package/dist/transaction.js +174 -28
- package/dist/tree.d.ts +8 -2
- package/dist/tree.js +44 -0
- package/package.json +27 -8
- package/dist/commands/alignment.d.ts +0 -18
- package/dist/commands/code.d.ts +0 -13
- package/dist/commands/list.d.ts +0 -20
- package/dist/commands/list.js +0 -52
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { getNodeAtPath, paragraph, } from "../model.js";
|
|
2
|
+
import { listFromBlocks } from "../table.js";
|
|
3
|
+
import { replaceNodeAtPath } from "../tree.js";
|
|
4
|
+
import { resolveListSelectionScope } from "../listScope.js";
|
|
5
|
+
import { listStyleForPresetDepth } from "../listPresets.js";
|
|
6
|
+
const samePath = (left, right) => left.length === right.length && left.every((part, index) => part === right[index]);
|
|
7
|
+
const isBlock = (node) => Boolean(node) && typeof node === "object" &&
|
|
8
|
+
["paragraph", "heading", "list", "blockquote", "codeBlock", "table"]
|
|
9
|
+
.includes(node.type || "");
|
|
10
|
+
const selectionPaths = (selection) => {
|
|
11
|
+
if (selection.type === "text")
|
|
12
|
+
return [selection.anchor.path, selection.focus.path];
|
|
13
|
+
if (selection.type === "node")
|
|
14
|
+
return [selection.path, selection.path];
|
|
15
|
+
return null;
|
|
16
|
+
};
|
|
17
|
+
const structuralUnit = (document, path) => {
|
|
18
|
+
let firstBlockPath = null;
|
|
19
|
+
for (let depth = 1; depth <= path.length; depth += 1) {
|
|
20
|
+
const candidatePath = path.slice(0, depth);
|
|
21
|
+
const node = getNodeAtPath(document, candidatePath);
|
|
22
|
+
if (!isBlock(node))
|
|
23
|
+
continue;
|
|
24
|
+
if (!firstBlockPath)
|
|
25
|
+
firstBlockPath = candidatePath;
|
|
26
|
+
if (node.type === "list") {
|
|
27
|
+
const itemIndex = path[depth];
|
|
28
|
+
return {
|
|
29
|
+
path: candidatePath,
|
|
30
|
+
...(Number.isInteger(itemIndex) ? { listItemIndex: itemIndex } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// A table is a container boundary, not a selectable text block.
|
|
34
|
+
if (node.type === "table")
|
|
35
|
+
firstBlockPath = null;
|
|
36
|
+
}
|
|
37
|
+
return firstBlockPath ? { path: firstBlockPath } : null;
|
|
38
|
+
};
|
|
39
|
+
const resolveListTarget = (context) => {
|
|
40
|
+
const scope = resolveListSelectionScope(context.document, context.selection);
|
|
41
|
+
if (scope.kind === "none")
|
|
42
|
+
return null;
|
|
43
|
+
if (scope.kind === "all")
|
|
44
|
+
return { kind: "range", containerPath: [], start: 0, end: context.document.children.length - 1 };
|
|
45
|
+
if (scope.kind === "blocks")
|
|
46
|
+
return { kind: "range", containerPath: scope.containerPath, start: scope.start, end: scope.end };
|
|
47
|
+
if (scope.kind === "list")
|
|
48
|
+
return { kind: "items", listPath: scope.listPath, start: scope.start, end: scope.end };
|
|
49
|
+
if (scope.kind === "mixed")
|
|
50
|
+
return { kind: "mixed", containerPath: scope.containerPath, blockIndex: scope.blockIndex, listPath: scope.listPath, itemStart: scope.itemStart, itemEnd: scope.itemEnd };
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
const itemFromBlock = (block) => ({
|
|
54
|
+
type: "listItem",
|
|
55
|
+
children: [block],
|
|
56
|
+
});
|
|
57
|
+
const blocksFromItem = (item) => item.children.length ? item.children : [paragraph()];
|
|
58
|
+
const replaceContainerChildren = (document, containerPath, children) => {
|
|
59
|
+
const container = getNodeAtPath(document, containerPath);
|
|
60
|
+
if (!container?.children)
|
|
61
|
+
throw new Error("List selection does not resolve to a block container.");
|
|
62
|
+
return replaceNodeAtPath(document, containerPath, { ...container, children });
|
|
63
|
+
};
|
|
64
|
+
const transformBlockRange = (document, target, style, forceList = false) => {
|
|
65
|
+
const container = getNodeAtPath(document, target.containerPath);
|
|
66
|
+
if (!container?.children || target.start < 0 || target.end >= container.children.length) {
|
|
67
|
+
throw new Error("Selected block range is out of bounds.");
|
|
68
|
+
}
|
|
69
|
+
const selected = container.children.slice(target.start, target.end + 1);
|
|
70
|
+
const allSameStyleLists = selected.every((block) => block.type === "list" && block.style === style);
|
|
71
|
+
const replacement = allSameStyleLists && !forceList
|
|
72
|
+
? selected.flatMap((block) => block.type === "list" ? block.children.flatMap(blocksFromItem) : [block])
|
|
73
|
+
: [{
|
|
74
|
+
type: "list",
|
|
75
|
+
style,
|
|
76
|
+
children: selected.flatMap((block) => block.type === "list" ? block.children : [itemFromBlock(block)]),
|
|
77
|
+
}];
|
|
78
|
+
const children = [
|
|
79
|
+
...container.children.slice(0, target.start),
|
|
80
|
+
...replacement,
|
|
81
|
+
...container.children.slice(target.end + 1),
|
|
82
|
+
];
|
|
83
|
+
return {
|
|
84
|
+
document: replaceContainerChildren(document, target.containerPath, children),
|
|
85
|
+
path: [...target.containerPath, target.start],
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
const transformListItems = (document, target, style) => {
|
|
89
|
+
const list = getNodeAtPath(document, target.listPath);
|
|
90
|
+
if (!list || list.type !== "list" || target.start < 0 || target.end >= list.children.length) {
|
|
91
|
+
throw new Error("Selected list-item range is out of bounds.");
|
|
92
|
+
}
|
|
93
|
+
const before = list.children.slice(0, target.start);
|
|
94
|
+
const selected = list.children.slice(target.start, target.end + 1);
|
|
95
|
+
const after = list.children.slice(target.end + 1);
|
|
96
|
+
const parentPath = target.listPath.slice(0, -1);
|
|
97
|
+
const listIndex = target.listPath[target.listPath.length - 1];
|
|
98
|
+
const parent = getNodeAtPath(document, parentPath);
|
|
99
|
+
if (!parent?.children)
|
|
100
|
+
throw new Error("List parent is not a block container.");
|
|
101
|
+
const replacement = [];
|
|
102
|
+
if (before.length)
|
|
103
|
+
replacement.push({ ...list, children: before });
|
|
104
|
+
if (list.style === style) {
|
|
105
|
+
replacement.push(...selected.flatMap(blocksFromItem));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
replacement.push({ ...list, style, children: selected });
|
|
109
|
+
}
|
|
110
|
+
if (after.length)
|
|
111
|
+
replacement.push({ ...list, children: after });
|
|
112
|
+
const children = [
|
|
113
|
+
...parent.children.slice(0, listIndex),
|
|
114
|
+
...replacement,
|
|
115
|
+
...parent.children.slice(listIndex + 1),
|
|
116
|
+
];
|
|
117
|
+
return {
|
|
118
|
+
document: replaceContainerChildren(document, parentPath, children),
|
|
119
|
+
path: [...parentPath, listIndex + (before.length ? 1 : 0)],
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const transformMixedRange = (document, target, style) => {
|
|
123
|
+
const container = getNodeAtPath(document, target.containerPath);
|
|
124
|
+
const source = getNodeAtPath(document, target.listPath);
|
|
125
|
+
if (!container?.children || source?.type !== "list")
|
|
126
|
+
throw new Error("Mixed list selection is invalid.");
|
|
127
|
+
const selectedItems = source.children.slice(target.itemStart, target.itemEnd + 1);
|
|
128
|
+
const mixedList = {
|
|
129
|
+
type: "list",
|
|
130
|
+
style,
|
|
131
|
+
children: [
|
|
132
|
+
itemFromBlock(container.children[target.blockIndex]),
|
|
133
|
+
...selectedItems,
|
|
134
|
+
],
|
|
135
|
+
};
|
|
136
|
+
const remaining = source.children.filter((_, index) => index < target.itemStart || index > target.itemEnd);
|
|
137
|
+
const replacement = remaining.length > 0
|
|
138
|
+
? [mixedList, { ...source, children: remaining }]
|
|
139
|
+
: [mixedList];
|
|
140
|
+
const children = [...container.children];
|
|
141
|
+
children.splice(Math.min(target.blockIndex, target.listPath[target.listPath.length - 1]), 2, ...replacement);
|
|
142
|
+
return {
|
|
143
|
+
document: replaceContainerChildren(document, target.containerPath, children),
|
|
144
|
+
path: [...target.containerPath, Math.min(target.blockIndex, target.listPath[target.listPath.length - 1])],
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
const cascadeListStyles = (list, depth = 0) => {
|
|
148
|
+
if (list.preset) {
|
|
149
|
+
return {
|
|
150
|
+
...list,
|
|
151
|
+
style: listStyleForPresetDepth(list.preset, depth),
|
|
152
|
+
children: list.children.map((item) => ({
|
|
153
|
+
...item,
|
|
154
|
+
children: item.children.map((child) => child.type === "list"
|
|
155
|
+
? cascadeListStyles({ ...child, preset: list.preset }, depth + 1)
|
|
156
|
+
: child),
|
|
157
|
+
})),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const ordered = ["decimal", "lower-alpha", "lower-roman", "upper-alpha", "upper-roman"];
|
|
161
|
+
const unordered = ["disc", "circle", "square"];
|
|
162
|
+
const orderedStyle = list.style === "decimal-leading-zero" || ordered.includes(list.style);
|
|
163
|
+
const family = orderedStyle ? ordered : unordered;
|
|
164
|
+
const style = depth === 0 ? list.style : family[Math.min(depth, family.length - 1)];
|
|
165
|
+
return {
|
|
166
|
+
...list,
|
|
167
|
+
style,
|
|
168
|
+
children: list.children.map((item) => ({
|
|
169
|
+
...item,
|
|
170
|
+
children: item.children.map((child) => child.type === "list" ? cascadeListStyles({ ...child, style }, depth + 1) : child),
|
|
171
|
+
})),
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
export const toggleList = {
|
|
175
|
+
id: "list.toggle",
|
|
176
|
+
isEnabled: (context, input) => Boolean(input && resolveListTarget(context)),
|
|
177
|
+
execute: (context, input) => {
|
|
178
|
+
if (!input)
|
|
179
|
+
throw new Error("list.toggle requires a list style.");
|
|
180
|
+
const target = resolveListTarget(context);
|
|
181
|
+
if (!target)
|
|
182
|
+
throw new Error("list.toggle requires a compatible block selection.");
|
|
183
|
+
const effectiveTarget = target.kind === "items" && input.scope !== "selected-items"
|
|
184
|
+
? {
|
|
185
|
+
kind: "range",
|
|
186
|
+
containerPath: target.listPath.slice(0, -1),
|
|
187
|
+
start: target.listPath[target.listPath.length - 1],
|
|
188
|
+
end: target.listPath[target.listPath.length - 1],
|
|
189
|
+
}
|
|
190
|
+
: target;
|
|
191
|
+
const selectedNode = effectiveTarget.kind === "range" && effectiveTarget.start === effectiveTarget.end
|
|
192
|
+
? getNodeAtPath(context.document, [...effectiveTarget.containerPath, effectiveTarget.start])
|
|
193
|
+
: null;
|
|
194
|
+
if (input.checklist &&
|
|
195
|
+
effectiveTarget.kind === "range" &&
|
|
196
|
+
selectedNode?.type === "list" &&
|
|
197
|
+
selectedNode.checklist) {
|
|
198
|
+
const list = selectedNode;
|
|
199
|
+
const document = replaceNodeAtPath(context.document, [...effectiveTarget.containerPath, effectiveTarget.start], {
|
|
200
|
+
...list,
|
|
201
|
+
checklist: undefined,
|
|
202
|
+
strikeCompleted: undefined,
|
|
203
|
+
children: list.children.map((item) => ({ ...item, checked: undefined })),
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
id: "list.toggle",
|
|
207
|
+
source: "user",
|
|
208
|
+
operations: [{ type: "replaceNode", path: [], node: document }],
|
|
209
|
+
selectionBefore: context.selection,
|
|
210
|
+
selectionAfter: context.selection,
|
|
211
|
+
addToHistory: true,
|
|
212
|
+
timestamp: context.now?.() ?? Date.now(),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
let result = effectiveTarget.kind === "range"
|
|
216
|
+
? transformBlockRange(context.document, effectiveTarget, input.style, input.checklist || Boolean(input.preset))
|
|
217
|
+
: effectiveTarget.kind === "items"
|
|
218
|
+
? transformListItems(context.document, effectiveTarget, input.style)
|
|
219
|
+
: transformMixedRange(context.document, effectiveTarget, input.style);
|
|
220
|
+
if (input.preset) {
|
|
221
|
+
const transformed = getNodeAtPath(result.document, result.path);
|
|
222
|
+
if (transformed?.type === "list") {
|
|
223
|
+
const list = transformed;
|
|
224
|
+
result = {
|
|
225
|
+
...result,
|
|
226
|
+
document: replaceNodeAtPath(result.document, result.path, {
|
|
227
|
+
...list,
|
|
228
|
+
preset: input.preset,
|
|
229
|
+
style: listStyleForPresetDepth(input.preset, 0),
|
|
230
|
+
}),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (input.checklist) {
|
|
235
|
+
const transformed = getNodeAtPath(result.document, result.path);
|
|
236
|
+
if (transformed?.type === "list") {
|
|
237
|
+
const list = transformed;
|
|
238
|
+
result = {
|
|
239
|
+
...result,
|
|
240
|
+
document: replaceNodeAtPath(result.document, result.path, {
|
|
241
|
+
...list,
|
|
242
|
+
checklist: true,
|
|
243
|
+
...(input.strikeCompleted ? { strikeCompleted: true } : {}),
|
|
244
|
+
children: list.children.map((item) => ({
|
|
245
|
+
...item,
|
|
246
|
+
checked: item.checked ?? false,
|
|
247
|
+
})),
|
|
248
|
+
}),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (input.cascadeStyles) {
|
|
253
|
+
const transformed = getNodeAtPath(result.document, result.path);
|
|
254
|
+
if (transformed?.type === "list") {
|
|
255
|
+
result = {
|
|
256
|
+
...result,
|
|
257
|
+
document: replaceNodeAtPath(result.document, result.path, cascadeListStyles(transformed)),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
id: "list.toggle",
|
|
263
|
+
source: "user",
|
|
264
|
+
operations: [{ type: "replaceNode", path: [], node: result.document }],
|
|
265
|
+
selectionBefore: context.selection,
|
|
266
|
+
selectionAfter: { type: "node", path: result.path },
|
|
267
|
+
addToHistory: true,
|
|
268
|
+
timestamp: context.now?.() ?? Date.now(),
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
const deepestListUnit = (document, path) => {
|
|
273
|
+
let result = null;
|
|
274
|
+
for (let depth = 1; depth < path.length; depth += 1) {
|
|
275
|
+
const listPath = path.slice(0, depth);
|
|
276
|
+
const node = getNodeAtPath(document, listPath);
|
|
277
|
+
if (node?.type === "list" && Number.isInteger(path[depth])) {
|
|
278
|
+
result = { listPath, itemIndex: path[depth] };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
};
|
|
283
|
+
const listItemTarget = (context) => {
|
|
284
|
+
const paths = selectionPaths(context.selection);
|
|
285
|
+
if (!paths)
|
|
286
|
+
return null;
|
|
287
|
+
const anchor = deepestListUnit(context.document, paths[0]);
|
|
288
|
+
const focus = deepestListUnit(context.document, paths[1]);
|
|
289
|
+
if (!anchor || !focus || !samePath(anchor.listPath, focus.listPath))
|
|
290
|
+
return null;
|
|
291
|
+
return {
|
|
292
|
+
kind: "items",
|
|
293
|
+
listPath: anchor.listPath,
|
|
294
|
+
start: Math.min(anchor.itemIndex, focus.itemIndex),
|
|
295
|
+
end: Math.max(anchor.itemIndex, focus.itemIndex),
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
const listTransaction = (id, context, document, selectionAfter) => ({
|
|
299
|
+
id,
|
|
300
|
+
source: "user",
|
|
301
|
+
operations: [{ type: "replaceNode", path: [], node: document }],
|
|
302
|
+
selectionBefore: context.selection,
|
|
303
|
+
selectionAfter,
|
|
304
|
+
addToHistory: true,
|
|
305
|
+
timestamp: context.now?.() ?? Date.now(),
|
|
306
|
+
});
|
|
307
|
+
const pathStartsWith = (path, prefix) => prefix.length <= path.length && prefix.every((part, index) => path[index] === part);
|
|
308
|
+
const mapSelectedTextItems = (selection, listPath, start, end, mapPath) => {
|
|
309
|
+
if (selection.type !== "text")
|
|
310
|
+
return null;
|
|
311
|
+
const mapPoint = (point) => {
|
|
312
|
+
if (!pathStartsWith(point.path, listPath) || point.path.length <= listPath.length)
|
|
313
|
+
return null;
|
|
314
|
+
const itemIndex = point.path[listPath.length];
|
|
315
|
+
if (itemIndex < start || itemIndex > end)
|
|
316
|
+
return null;
|
|
317
|
+
return {
|
|
318
|
+
path: mapPath(itemIndex, point.path.slice(listPath.length + 1)),
|
|
319
|
+
offset: point.offset,
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
const anchor = mapPoint(selection.anchor);
|
|
323
|
+
const focus = mapPoint(selection.focus);
|
|
324
|
+
return anchor && focus ? { type: "text", anchor, focus } : null;
|
|
325
|
+
};
|
|
326
|
+
export const indentListItems = {
|
|
327
|
+
id: "list.indent",
|
|
328
|
+
isEnabled: (context) => {
|
|
329
|
+
const target = listItemTarget(context);
|
|
330
|
+
return Boolean(target && target.start > 0);
|
|
331
|
+
},
|
|
332
|
+
execute: (context) => {
|
|
333
|
+
const target = listItemTarget(context);
|
|
334
|
+
if (!target || target.start === 0) {
|
|
335
|
+
throw new Error("list.indent requires list items with a previous sibling.");
|
|
336
|
+
}
|
|
337
|
+
const list = getNodeAtPath(context.document, target.listPath);
|
|
338
|
+
const previous = list.children[target.start - 1];
|
|
339
|
+
const selected = list.children.slice(target.start, target.end + 1);
|
|
340
|
+
const lastChild = previous.children[previous.children.length - 1];
|
|
341
|
+
const existingNestedCount = lastChild?.type === "list" && lastChild.style === list.style
|
|
342
|
+
? lastChild.children.length
|
|
343
|
+
: 0;
|
|
344
|
+
const nested = lastChild?.type === "list" && lastChild.style === list.style
|
|
345
|
+
? { ...lastChild, children: [...lastChild.children, ...selected] }
|
|
346
|
+
: { type: "list", style: list.style, children: selected };
|
|
347
|
+
const previousChildren = lastChild?.type === "list" && lastChild.style === list.style
|
|
348
|
+
? [...previous.children.slice(0, -1), nested]
|
|
349
|
+
: [...previous.children, nested];
|
|
350
|
+
const nextList = {
|
|
351
|
+
...list,
|
|
352
|
+
children: [
|
|
353
|
+
...list.children.slice(0, target.start - 1),
|
|
354
|
+
{ ...previous, children: previousChildren },
|
|
355
|
+
...list.children.slice(target.end + 1),
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
const document = replaceNodeAtPath(context.document, target.listPath, nextList);
|
|
359
|
+
const selectionAfter = mapSelectedTextItems(context.selection, target.listPath, target.start, target.end, (itemIndex, suffix) => [
|
|
360
|
+
...target.listPath,
|
|
361
|
+
target.start - 1,
|
|
362
|
+
previousChildren.length - 1,
|
|
363
|
+
existingNestedCount + itemIndex - target.start,
|
|
364
|
+
...suffix,
|
|
365
|
+
]) || { type: "node", path: [...target.listPath, target.start - 1, previousChildren.length - 1] };
|
|
366
|
+
return listTransaction("list.indent", context, document, selectionAfter);
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
export const outdentListItems = {
|
|
370
|
+
id: "list.outdent",
|
|
371
|
+
isEnabled: (context) => {
|
|
372
|
+
const target = listItemTarget(context);
|
|
373
|
+
if (!target || target.listPath.length < 3)
|
|
374
|
+
return false;
|
|
375
|
+
const parentItemPath = target.listPath.slice(0, -1);
|
|
376
|
+
const parentItem = getNodeAtPath(context.document, parentItemPath);
|
|
377
|
+
const ancestorList = getNodeAtPath(context.document, parentItemPath.slice(0, -1));
|
|
378
|
+
return parentItem?.type === "listItem" &&
|
|
379
|
+
ancestorList?.type === "list";
|
|
380
|
+
},
|
|
381
|
+
execute: (context) => {
|
|
382
|
+
const target = listItemTarget(context);
|
|
383
|
+
if (!target || !outdentListItems.isEnabled(context)) {
|
|
384
|
+
throw new Error("list.outdent requires items in a nested list.");
|
|
385
|
+
}
|
|
386
|
+
const nestedList = getNodeAtPath(context.document, target.listPath);
|
|
387
|
+
const parentItemPath = target.listPath.slice(0, -1);
|
|
388
|
+
const parentItem = getNodeAtPath(context.document, parentItemPath);
|
|
389
|
+
const ancestorListPath = parentItemPath.slice(0, -1);
|
|
390
|
+
const ancestorList = getNodeAtPath(context.document, ancestorListPath);
|
|
391
|
+
const parentIndex = parentItemPath[parentItemPath.length - 1];
|
|
392
|
+
const nestedIndex = target.listPath[target.listPath.length - 1];
|
|
393
|
+
const selected = nestedList.children.slice(target.start, target.end + 1);
|
|
394
|
+
const remaining = [
|
|
395
|
+
...nestedList.children.slice(0, target.start),
|
|
396
|
+
...nestedList.children.slice(target.end + 1),
|
|
397
|
+
];
|
|
398
|
+
const nextParent = {
|
|
399
|
+
...parentItem,
|
|
400
|
+
children: remaining.length
|
|
401
|
+
? [
|
|
402
|
+
...parentItem.children.slice(0, nestedIndex),
|
|
403
|
+
{ ...nestedList, children: remaining },
|
|
404
|
+
...parentItem.children.slice(nestedIndex + 1),
|
|
405
|
+
]
|
|
406
|
+
: [
|
|
407
|
+
...parentItem.children.slice(0, nestedIndex),
|
|
408
|
+
...parentItem.children.slice(nestedIndex + 1),
|
|
409
|
+
],
|
|
410
|
+
};
|
|
411
|
+
const nextAncestor = {
|
|
412
|
+
...ancestorList,
|
|
413
|
+
children: [
|
|
414
|
+
...ancestorList.children.slice(0, parentIndex),
|
|
415
|
+
nextParent,
|
|
416
|
+
...selected,
|
|
417
|
+
...ancestorList.children.slice(parentIndex + 1),
|
|
418
|
+
],
|
|
419
|
+
};
|
|
420
|
+
const document = replaceNodeAtPath(context.document, ancestorListPath, nextAncestor);
|
|
421
|
+
const selectionAfter = mapSelectedTextItems(context.selection, target.listPath, target.start, target.end, (itemIndex, suffix) => [
|
|
422
|
+
...ancestorListPath,
|
|
423
|
+
parentIndex + 1 + itemIndex - target.start,
|
|
424
|
+
...suffix,
|
|
425
|
+
]) || { type: "node", path: [...ancestorListPath, parentIndex + 1] };
|
|
426
|
+
return listTransaction("list.outdent", context, document, selectionAfter);
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
/**
|
|
430
|
+
* Converts explicitly selected block nodes in one table cell into a single
|
|
431
|
+
* list. No DOM ranges, wrappers, or React state participate in this command.
|
|
432
|
+
*/
|
|
433
|
+
export const toggleTableCellList = (document, selection, input) => {
|
|
434
|
+
const table = getNodeAtPath(document, input.tablePath);
|
|
435
|
+
if (!table || table.type !== "table")
|
|
436
|
+
throw new Error("Expected a table at tablePath.");
|
|
437
|
+
const cell = table.children[input.row]?.children[input.column];
|
|
438
|
+
if (!cell || (cell.type !== "tableCell" && cell.type !== "tableHeaderCell")) {
|
|
439
|
+
throw new Error("Expected a table cell at the requested coordinates.");
|
|
440
|
+
}
|
|
441
|
+
const indexes = [...new Set(input.blockIndexes)].sort((left, right) => left - right);
|
|
442
|
+
if (indexes.length === 0)
|
|
443
|
+
throw new Error("At least one table-cell block must be selected.");
|
|
444
|
+
if (indexes.some((index) => index < 0 || index >= cell.children.length)) {
|
|
445
|
+
throw new Error("Selected table-cell block index is out of bounds.");
|
|
446
|
+
}
|
|
447
|
+
const selected = indexes.map((index) => cell.children[index]);
|
|
448
|
+
const selectedIndexes = new Set(indexes);
|
|
449
|
+
const firstIndex = indexes[0];
|
|
450
|
+
const list = listFromBlocks(selected, input.style);
|
|
451
|
+
const nextCell = {
|
|
452
|
+
...cell,
|
|
453
|
+
children: cell.children.reduce((children, block, index) => {
|
|
454
|
+
if (index === firstIndex)
|
|
455
|
+
children.push(list);
|
|
456
|
+
if (!selectedIndexes.has(index))
|
|
457
|
+
children.push(block);
|
|
458
|
+
return children;
|
|
459
|
+
}, []),
|
|
460
|
+
};
|
|
461
|
+
const cellPath = [...input.tablePath, input.row, input.column];
|
|
462
|
+
const nextDocument = replaceNodeAtPath(document, cellPath, nextCell);
|
|
463
|
+
const listPath = [...cellPath, firstIndex];
|
|
464
|
+
const selectionAfter = { type: "node", path: listPath };
|
|
465
|
+
return {
|
|
466
|
+
document: nextDocument,
|
|
467
|
+
transaction: {
|
|
468
|
+
id: "toggle-table-cell-list",
|
|
469
|
+
source: "user",
|
|
470
|
+
operations: [{ type: "replaceNode", path: cellPath, node: nextCell }],
|
|
471
|
+
selectionBefore: selection,
|
|
472
|
+
selectionAfter,
|
|
473
|
+
addToHistory: true,
|
|
474
|
+
timestamp: Date.now(),
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
};
|
|
@@ -2,11 +2,14 @@ import type { SmartCommand } from "../command.js";
|
|
|
2
2
|
export declare const toggleBold: SmartCommand<void>;
|
|
3
3
|
export declare const toggleItalic: SmartCommand<void>;
|
|
4
4
|
export declare const toggleUnderline: SmartCommand<void>;
|
|
5
|
+
export declare const toggleStrike: SmartCommand<void>;
|
|
6
|
+
export declare const toggleInlineCode: SmartCommand<void>;
|
|
5
7
|
export declare const toggleSuperscript: SmartCommand<void>;
|
|
6
8
|
export declare const toggleSubscript: SmartCommand<void>;
|
|
7
9
|
export declare const applyTextColor: SmartCommand<string>;
|
|
8
10
|
export declare const applyBackgroundColor: SmartCommand<string>;
|
|
9
11
|
export declare const applyFontSize: SmartCommand<number>;
|
|
12
|
+
export declare const applyFontFamily: SmartCommand<string>;
|
|
10
13
|
type LinkInput = {
|
|
11
14
|
href: string;
|
|
12
15
|
target?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { addMark, hasMark, removeMark } from "../marks.js";
|
|
2
|
-
import { getNodeAtPath } from "../model.js";
|
|
2
|
+
import { getNodeAtPath, } from "../model.js";
|
|
3
3
|
import { sanitizeLinkAttrs } from "../security/urlPolicy.js";
|
|
4
4
|
const samePath = (left, right) => left.length === right.length && left.every((part, index) => part === right[index]);
|
|
5
5
|
const getTextRange = (context) => {
|
|
@@ -20,11 +20,15 @@ const getTextRange = (context) => {
|
|
|
20
20
|
const nodes = parent?.children;
|
|
21
21
|
if (!nodes || startIndex < 0 || endIndex >= nodes.length || startOffset < 0 || endOffset < 0)
|
|
22
22
|
return null;
|
|
23
|
-
|
|
23
|
+
const startNode = nodes[startIndex];
|
|
24
|
+
const endNode = nodes[endIndex];
|
|
25
|
+
if (startNode?.type !== "text" || endNode?.type !== "text")
|
|
24
26
|
return null;
|
|
25
|
-
if (startIndex === endIndex && (startOffset === endOffset || endOffset >
|
|
27
|
+
if (startIndex === endIndex && (startOffset === endOffset || endOffset > startNode.text.length))
|
|
26
28
|
return null;
|
|
27
|
-
if (startOffset >
|
|
29
|
+
if (startOffset > startNode.text.length || endOffset > endNode.text.length)
|
|
30
|
+
return null;
|
|
31
|
+
if (!nodes.slice(startIndex, endIndex + 1).some((node) => node.type === "text"))
|
|
28
32
|
return null;
|
|
29
33
|
return { selection: context.selection, parentPath, startIndex, endIndex, startOffset, endOffset, nodes };
|
|
30
34
|
};
|
|
@@ -37,6 +41,10 @@ const replaceRange = (range, mutate) => {
|
|
|
37
41
|
children.push(node);
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
44
|
+
if (node.type !== "text") {
|
|
45
|
+
children.push(node);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
40
48
|
const from = index === range.startIndex ? range.startOffset : 0;
|
|
41
49
|
const to = index === range.endIndex ? range.endOffset : node.text.length;
|
|
42
50
|
if (from > 0)
|
|
@@ -54,16 +62,23 @@ const replaceRange = (range, mutate) => {
|
|
|
54
62
|
return { children, start: selectionStart, end: selectionEnd };
|
|
55
63
|
};
|
|
56
64
|
const hasLink = (marks) => marks?.some((mark) => mark.type === "link") || false;
|
|
65
|
+
const selectedTextNodes = (range) => range.nodes
|
|
66
|
+
.slice(range.startIndex, range.endIndex + 1)
|
|
67
|
+
.filter((node) => node.type === "text");
|
|
57
68
|
const createMarkTransaction = (id, context, input, mutate) => {
|
|
58
69
|
const range = getTextRange(context);
|
|
59
70
|
if (!range)
|
|
60
71
|
throw new Error(`${id} requires text in one inline container.`);
|
|
61
72
|
const result = replaceRange(range, (marks) => mutate(marks, range));
|
|
62
73
|
const parent = getNodeAtPath(context.document, range.parentPath);
|
|
74
|
+
const selectionEnd = result.children[result.end];
|
|
63
75
|
const selectionAfter = {
|
|
64
76
|
type: "text",
|
|
65
77
|
anchor: { path: [...range.parentPath, result.start], offset: 0 },
|
|
66
|
-
focus: {
|
|
78
|
+
focus: {
|
|
79
|
+
path: [...range.parentPath, result.end],
|
|
80
|
+
offset: selectionEnd?.type === "text" ? selectionEnd.text.length : 0,
|
|
81
|
+
},
|
|
67
82
|
};
|
|
68
83
|
return {
|
|
69
84
|
id,
|
|
@@ -83,7 +98,7 @@ const createMarkCommand = (id, nextMark, toggle) => ({
|
|
|
83
98
|
if (!range)
|
|
84
99
|
throw new Error(`${id} requires text in one inline container.`);
|
|
85
100
|
const mark = nextMark(input);
|
|
86
|
-
const selected =
|
|
101
|
+
const selected = selectedTextNodes(range);
|
|
87
102
|
const remove = toggle && selected.every((node) => hasMark(node.marks, mark.type));
|
|
88
103
|
return createMarkTransaction(id, context, input, (marks) => remove ? removeMark(marks, mark.type) : addMark(marks, mark));
|
|
89
104
|
},
|
|
@@ -96,8 +111,7 @@ const toggleScript = (id, type) => ({
|
|
|
96
111
|
const range = getTextRange(context);
|
|
97
112
|
if (!range)
|
|
98
113
|
throw new Error(`${id} requires text in one inline container.`);
|
|
99
|
-
const remove = range
|
|
100
|
-
.slice(range.startIndex, range.endIndex + 1)
|
|
114
|
+
const remove = selectedTextNodes(range)
|
|
101
115
|
.every((node) => hasMark(node.marks, type));
|
|
102
116
|
const opposite = type === "superscript" ? "subscript" : "superscript";
|
|
103
117
|
return createMarkTransaction(id, context, undefined, (marks) => {
|
|
@@ -109,11 +123,14 @@ const toggleScript = (id, type) => ({
|
|
|
109
123
|
export const toggleBold = toggle("toggle-bold", "bold");
|
|
110
124
|
export const toggleItalic = toggle("toggle-italic", "italic");
|
|
111
125
|
export const toggleUnderline = toggle("toggle-underline", "underline");
|
|
126
|
+
export const toggleStrike = toggle("toggle-strike", "strike");
|
|
127
|
+
export const toggleInlineCode = toggle("toggle-inline-code", "code");
|
|
112
128
|
export const toggleSuperscript = toggleScript("toggle-superscript", "superscript");
|
|
113
129
|
export const toggleSubscript = toggleScript("toggle-subscript", "subscript");
|
|
114
130
|
export const applyTextColor = createMarkCommand("apply-text-color", (value) => ({ type: "textColor", value }), false);
|
|
115
131
|
export const applyBackgroundColor = createMarkCommand("apply-background-color", (value) => ({ type: "backgroundColor", value }), false);
|
|
116
132
|
export const applyFontSize = createMarkCommand("apply-font-size", (valuePx) => ({ type: "fontSize", valuePx }), false);
|
|
133
|
+
export const applyFontFamily = createMarkCommand("apply-font-family", (value) => ({ type: "fontFamily", value }), false);
|
|
117
134
|
export const applyLink = {
|
|
118
135
|
id: "apply-link",
|
|
119
136
|
isEnabled: (context, input) => Boolean(getTextRange(context) && input && sanitizeLinkAttrs(input)),
|
|
@@ -128,12 +145,12 @@ export const updateLink = {
|
|
|
128
145
|
id: "update-link",
|
|
129
146
|
isEnabled: (context, input) => {
|
|
130
147
|
const range = getTextRange(context);
|
|
131
|
-
return Boolean(range && input && sanitizeLinkAttrs(input) &&
|
|
148
|
+
return Boolean(range && input && sanitizeLinkAttrs(input) && selectedTextNodes(range).some((node) => hasLink(node.marks)));
|
|
132
149
|
},
|
|
133
150
|
execute: (context, input) => {
|
|
134
151
|
const range = getTextRange(context);
|
|
135
152
|
const safe = sanitizeLinkAttrs(input || {});
|
|
136
|
-
if (!range || !safe || !
|
|
153
|
+
if (!range || !safe || !selectedTextNodes(range).some((node) => hasLink(node.marks))) {
|
|
137
154
|
throw new Error("update-link requires selected linked text and a safe href.");
|
|
138
155
|
}
|
|
139
156
|
return createMarkTransaction("update-link", context, input, (marks) => addMark(marks, { type: "link", ...safe }));
|
|
@@ -143,7 +160,7 @@ export const removeLink = {
|
|
|
143
160
|
id: "remove-link",
|
|
144
161
|
isEnabled: (context) => {
|
|
145
162
|
const range = getTextRange(context);
|
|
146
|
-
return Boolean(range &&
|
|
163
|
+
return Boolean(range && selectedTextNodes(range).some((node) => hasLink(node.marks)));
|
|
147
164
|
},
|
|
148
165
|
execute: (context) => {
|
|
149
166
|
if (!removeLink.isEnabled(context))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SmartCommand } from "../command.js";
|
|
2
|
+
import { type Path } from "../model.js";
|
|
3
|
+
export interface InsertImageInput {
|
|
4
|
+
src: string;
|
|
5
|
+
alt?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
width?: number;
|
|
8
|
+
height?: number;
|
|
9
|
+
path?: Path;
|
|
10
|
+
}
|
|
11
|
+
export interface InsertInlineImageInput extends InsertImageInput {
|
|
12
|
+
}
|
|
13
|
+
export declare const insertImage: SmartCommand<InsertImageInput>;
|
|
14
|
+
export declare const insertInlineImage: SmartCommand<InsertInlineImageInput>;
|
|
15
|
+
export declare const deleteInlineImage: SmartCommand<import("./inlineAtoms.js").InlineAtomPathInput>;
|
|
16
|
+
export interface UpdateInlineImageInput {
|
|
17
|
+
path: Path;
|
|
18
|
+
alt?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
width?: number;
|
|
21
|
+
height?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const updateInlineImage: SmartCommand<UpdateInlineImageInput>;
|
|
24
|
+
export interface InsertMediaInput {
|
|
25
|
+
src: string;
|
|
26
|
+
mediaType: "audio" | "video" | "file";
|
|
27
|
+
title?: string;
|
|
28
|
+
mimeType?: string;
|
|
29
|
+
path?: Path;
|
|
30
|
+
}
|
|
31
|
+
export declare const insertMedia: SmartCommand<InsertMediaInput>;
|