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,197 @@
|
|
|
1
|
+
import { cloneNode, isTextNode } from "../identity.js";
|
|
2
|
+
import { moveContiguousSiblings } from "../structural/move.js";
|
|
3
|
+
const cleanAttrs = (attrs) => Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== undefined));
|
|
4
|
+
const blockScopes = (scope) => {
|
|
5
|
+
if (scope.kind === "block-range")
|
|
6
|
+
return [scope];
|
|
7
|
+
if (scope.kind !== "mixed")
|
|
8
|
+
return [];
|
|
9
|
+
const collect = (mixed) => mixed.parts.flatMap((part) => part.kind === "block-range" ? [part] : part.kind === "mixed" ? collect(part) : []);
|
|
10
|
+
return collect(scope);
|
|
11
|
+
};
|
|
12
|
+
const selectedBlockIds = (scope) => [...new Set(blockScopes(scope).flatMap((entry) => entry.blockIds))];
|
|
13
|
+
const locate = (nodeId, ctx) => {
|
|
14
|
+
const resolved = ctx.positions.positionOf(nodeId);
|
|
15
|
+
const node = resolved?.parent.children?.[resolved.pos.offset];
|
|
16
|
+
if (!resolved || !node || isTextNode(node) || node.id !== nodeId)
|
|
17
|
+
throw new Error(`Unknown or stale block ID "${nodeId}".`);
|
|
18
|
+
return { node, pos: { path: [...resolved.pos.path], offset: resolved.pos.offset } };
|
|
19
|
+
};
|
|
20
|
+
const semanticRole = (node, ctx) => ctx.schema.nodes[node.type]?.semanticRole || node.type;
|
|
21
|
+
const ancestorWithRole = (nodeId, role, ctx) => {
|
|
22
|
+
const resolved = ctx.positions.positionOf(nodeId);
|
|
23
|
+
if (!resolved)
|
|
24
|
+
return null;
|
|
25
|
+
for (let index = resolved.ancestors.length - 1; index >= 0; index -= 1) {
|
|
26
|
+
const candidate = resolved.ancestors[index];
|
|
27
|
+
if (semanticRole(candidate, ctx) === role)
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
const ancestorTargets = (scope, role, ctx) => {
|
|
33
|
+
const targets = new Map();
|
|
34
|
+
selectedBlockIds(scope).forEach((nodeId) => {
|
|
35
|
+
const ancestor = ancestorWithRole(nodeId, role, ctx);
|
|
36
|
+
if (!ancestor || targets.has(ancestor.id))
|
|
37
|
+
return;
|
|
38
|
+
targets.set(ancestor.id, locate(ancestor.id, ctx));
|
|
39
|
+
});
|
|
40
|
+
return [...targets.values()].sort((left, right) => right.pos.path.length - left.pos.path.length || right.pos.offset - left.pos.offset);
|
|
41
|
+
};
|
|
42
|
+
const typeAttrs = (node, type, attrs) => {
|
|
43
|
+
const next = { ...(node.attrs || {}) };
|
|
44
|
+
delete next.level;
|
|
45
|
+
delete next.language;
|
|
46
|
+
Object.assign(next, attrs || {});
|
|
47
|
+
if (type === "heading" && next.level === undefined)
|
|
48
|
+
next.level = 1;
|
|
49
|
+
return cleanAttrs(next);
|
|
50
|
+
};
|
|
51
|
+
const uniqueMarks = (node) => {
|
|
52
|
+
const marks = new Map();
|
|
53
|
+
node.children?.forEach((child) => {
|
|
54
|
+
if (!isTextNode(child))
|
|
55
|
+
return;
|
|
56
|
+
child.marks?.forEach((mark) => marks.set(JSON.stringify(mark), mark));
|
|
57
|
+
});
|
|
58
|
+
return [...marks.values()];
|
|
59
|
+
};
|
|
60
|
+
export const setBlockTypeCommand = (_document, scope, params, ctx) => selectedBlockIds(scope).flatMap((nodeId) => {
|
|
61
|
+
const { node, pos } = locate(nodeId, ctx);
|
|
62
|
+
if (!["paragraph", "heading", "code_block"].includes(node.type))
|
|
63
|
+
return [];
|
|
64
|
+
const afterAttrs = typeAttrs(node, params.type, params.attrs);
|
|
65
|
+
if (node.type === params.type) {
|
|
66
|
+
return JSON.stringify(node.attrs || {}) === JSON.stringify(afterAttrs) ? [] : [{
|
|
67
|
+
type: "setNodeAttributes",
|
|
68
|
+
pos: { path: [...pos.path, pos.offset], offset: 0 },
|
|
69
|
+
before: node.attrs || {},
|
|
70
|
+
after: afterAttrs,
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
const operations = [];
|
|
74
|
+
if (params.type === "code_block") {
|
|
75
|
+
const range = ctx.positions.contentRangeOf(node.id);
|
|
76
|
+
if (!range)
|
|
77
|
+
return [];
|
|
78
|
+
uniqueMarks(node).forEach((mark) => operations.push({ type: "removeMark", range, mark }));
|
|
79
|
+
}
|
|
80
|
+
operations.push({
|
|
81
|
+
type: "setNodeType",
|
|
82
|
+
pos: { path: [...pos.path, pos.offset], offset: 0 },
|
|
83
|
+
before: node.type,
|
|
84
|
+
after: params.type,
|
|
85
|
+
beforeAttrs: node.attrs || {},
|
|
86
|
+
afterAttrs,
|
|
87
|
+
});
|
|
88
|
+
return operations;
|
|
89
|
+
});
|
|
90
|
+
const groupedBlocks = (scope, ctx) => {
|
|
91
|
+
const entries = selectedBlockIds(scope).map((nodeId) => ({ nodeId, ...locate(nodeId, ctx) }));
|
|
92
|
+
const byParent = new Map();
|
|
93
|
+
entries.forEach((entry) => {
|
|
94
|
+
const key = JSON.stringify(entry.pos.path);
|
|
95
|
+
byParent.set(key, [...(byParent.get(key) || []), entry]);
|
|
96
|
+
});
|
|
97
|
+
return [...byParent.values()].map((group) => group.sort((a, b) => a.pos.offset - b.pos.offset))
|
|
98
|
+
.filter((group) => group.every((entry, index) => index === 0 || entry.pos.offset === group[index - 1].pos.offset + 1))
|
|
99
|
+
.sort((left, right) => right[0].pos.offset - left[0].pos.offset);
|
|
100
|
+
};
|
|
101
|
+
export const wrapBlocks = (_document, scope, params, ctx) => {
|
|
102
|
+
const groups = groupedBlocks(scope, ctx);
|
|
103
|
+
const listTargets = ancestorTargets(scope, "list", ctx);
|
|
104
|
+
const nonListGroups = groups.filter((group) => !ancestorWithRole(group[0].node.id, "list", ctx));
|
|
105
|
+
const targetCount = listTargets.length + nonListGroups.length;
|
|
106
|
+
if (params.wrapperIds.length < targetCount)
|
|
107
|
+
throw new Error(`block.wrap requires ${targetCount} caller-provided wrapper ID(s).`);
|
|
108
|
+
let wrapperCursor = 0;
|
|
109
|
+
const listOperations = listTargets.map(({ node, pos }) => {
|
|
110
|
+
const wrapper = {
|
|
111
|
+
type: params.type,
|
|
112
|
+
id: params.wrapperIds[wrapperCursor++],
|
|
113
|
+
...(params.attrs && Object.keys(params.attrs).length ? { attrs: cleanAttrs(params.attrs) } : {}),
|
|
114
|
+
children: [cloneNode(node)],
|
|
115
|
+
};
|
|
116
|
+
return { type: "replaceNode", pos, before: node, after: wrapper };
|
|
117
|
+
});
|
|
118
|
+
const blockOperations = nonListGroups.flatMap((group) => {
|
|
119
|
+
const first = group[0];
|
|
120
|
+
const wrapper = {
|
|
121
|
+
type: params.type,
|
|
122
|
+
id: params.wrapperIds[wrapperCursor++],
|
|
123
|
+
...(params.attrs && Object.keys(params.attrs).length ? { attrs: cleanAttrs(params.attrs) } : {}),
|
|
124
|
+
children: group.map((entry) => cloneNode(entry.node)),
|
|
125
|
+
};
|
|
126
|
+
const operations = [{ type: "replaceNode", pos: first.pos, before: first.node, after: wrapper }];
|
|
127
|
+
group.slice(1).forEach((entry) => operations.push({
|
|
128
|
+
type: "removeNode",
|
|
129
|
+
pos: { path: [...first.pos.path], offset: first.pos.offset + 1 },
|
|
130
|
+
node: entry.node,
|
|
131
|
+
}));
|
|
132
|
+
return operations;
|
|
133
|
+
});
|
|
134
|
+
return [...listOperations, ...blockOperations];
|
|
135
|
+
};
|
|
136
|
+
export const unwrapBlocks = (_document, scope, params, ctx) => {
|
|
137
|
+
const wrapperType = params.type || "blockquote";
|
|
138
|
+
const wrappers = new Map();
|
|
139
|
+
selectedBlockIds(scope).forEach((nodeId) => {
|
|
140
|
+
const selected = locate(nodeId, ctx);
|
|
141
|
+
const target = selected.node.type === wrapperType
|
|
142
|
+
? selected.node
|
|
143
|
+
: ancestorWithRole(nodeId, wrapperType === "blockquote" ? "blockquote" : wrapperType, ctx);
|
|
144
|
+
if (!target || wrappers.has(target.id))
|
|
145
|
+
return;
|
|
146
|
+
wrappers.set(target.id, locate(target.id, ctx));
|
|
147
|
+
});
|
|
148
|
+
return [...wrappers.values()].sort((left, right) => right.pos.path.length - left.pos.path.length || right.pos.offset - left.pos.offset).flatMap(({ node, pos }) => {
|
|
149
|
+
if (node.type !== wrapperType)
|
|
150
|
+
return [];
|
|
151
|
+
const children = (node.children || []).filter((child) => !isTextNode(child));
|
|
152
|
+
if (!children.length)
|
|
153
|
+
return [];
|
|
154
|
+
const operations = [{ type: "replaceNode", pos, before: node, after: cloneNode(children[0]) }];
|
|
155
|
+
children.slice(1).forEach((child, index) => operations.push({
|
|
156
|
+
type: "insertNode",
|
|
157
|
+
pos: { path: [...pos.path], offset: pos.offset + index + 1 },
|
|
158
|
+
node: cloneNode(child),
|
|
159
|
+
}));
|
|
160
|
+
return operations;
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
export const setBlockAttributes = (_document, scope, params, ctx) => selectedBlockIds(scope).flatMap((nodeId) => {
|
|
164
|
+
const { node, pos } = locate(nodeId, ctx);
|
|
165
|
+
const after = cleanAttrs({ ...(node.attrs || {}), ...params.attrs });
|
|
166
|
+
return JSON.stringify(node.attrs || {}) === JSON.stringify(after) ? [] : [{
|
|
167
|
+
type: "setNodeAttributes",
|
|
168
|
+
pos: { path: [...pos.path, pos.offset], offset: 0 },
|
|
169
|
+
before: node.attrs || {},
|
|
170
|
+
after,
|
|
171
|
+
}];
|
|
172
|
+
});
|
|
173
|
+
export const moveBlockCommand = (_document, scope, params, ctx) => moveContiguousSiblings(selectedBlockIds(scope), params.direction, ctx);
|
|
174
|
+
const changeIndent = (scope, amount, ctx) => selectedBlockIds(scope).flatMap((nodeId) => {
|
|
175
|
+
const { node, pos } = locate(nodeId, ctx);
|
|
176
|
+
const current = Number(node.attrs?.indentLevel) || 0;
|
|
177
|
+
const indentLevel = Math.max(0, Math.min(10, current + amount));
|
|
178
|
+
if (indentLevel === current)
|
|
179
|
+
return [];
|
|
180
|
+
return [{
|
|
181
|
+
type: "setNodeAttributes",
|
|
182
|
+
pos: { path: [...pos.path, pos.offset], offset: 0 },
|
|
183
|
+
before: node.attrs || {},
|
|
184
|
+
after: cleanAttrs({ ...(node.attrs || {}), indentLevel: indentLevel || undefined }),
|
|
185
|
+
}];
|
|
186
|
+
});
|
|
187
|
+
export const indentBlockCommand = (_document, scope, params, ctx) => changeIndent(scope, Math.max(1, params.amount || 1), ctx);
|
|
188
|
+
export const outdentBlockCommand = (_document, scope, params, ctx) => changeIndent(scope, -Math.max(1, params.amount || 1), ctx);
|
|
189
|
+
export const blockCommands = {
|
|
190
|
+
"block.setType": setBlockTypeCommand,
|
|
191
|
+
"block.wrap": wrapBlocks,
|
|
192
|
+
"block.unwrap": unwrapBlocks,
|
|
193
|
+
"block.setAttributes": setBlockAttributes,
|
|
194
|
+
"block.move": moveBlockCommand,
|
|
195
|
+
"block.indent": indentBlockCommand,
|
|
196
|
+
"block.outdent": outdentBlockCommand,
|
|
197
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseCanonicalListHtml, parseCanonicalListMarkdown, serializeCanonicalListHtml, serializeCanonicalListMarkdown } from "../list/formats.js";
|
|
2
|
+
import type { SmartDocument, SmartElementNode } from "../types.js";
|
|
3
|
+
export { parseCanonicalListHtml as parseCanonicalBlockHtml, parseCanonicalListMarkdown as parseCanonicalBlockMarkdown, serializeCanonicalListHtml as serializeCanonicalBlockHtml, serializeCanonicalListMarkdown as serializeCanonicalBlockMarkdown, };
|
|
4
|
+
export interface CanonicalDocxBlock {
|
|
5
|
+
readonly nodeId: string;
|
|
6
|
+
readonly kind: "paragraph" | "heading" | "blockquote" | "code";
|
|
7
|
+
readonly text: string;
|
|
8
|
+
readonly style: string;
|
|
9
|
+
readonly outlineLevel?: number;
|
|
10
|
+
readonly alignment?: "left" | "center" | "right" | "justify";
|
|
11
|
+
/** DOCX left indentation in twips (720 twips per canonical indent level). */
|
|
12
|
+
readonly indentTwips?: number;
|
|
13
|
+
readonly language?: string;
|
|
14
|
+
readonly quoteDepth?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The per-node half of canonicalBlocksToDocx's visit() body, matching
|
|
18
|
+
* FeatureFormatCodec.serialize's (node, ctx) => unknown shape
|
|
19
|
+
* (formats/codec.ts) - Phase 11 Tier 2's block codec slice. `quoteDepth`
|
|
20
|
+
* defaults to 0 for a direct single-node call (a codec invocation has no
|
|
21
|
+
* ancestor-walk context); canonicalBlocksToDocx below still owns tracking
|
|
22
|
+
* the real depth across a blockquote's descendants and passes it through.
|
|
23
|
+
* Returns null for a block type this mapping doesn't cover (e.g. `table`,
|
|
24
|
+
* `list`), matching FeatureFormatCodec.parse's null-for-inapplicable
|
|
25
|
+
* convention.
|
|
26
|
+
*/
|
|
27
|
+
export declare const blockToDocxEntry: (node: SmartElementNode, quoteDepth?: number) => CanonicalDocxBlock | null;
|
|
28
|
+
/** Semantic DOCX mapping. Heading styles and paragraph properties are retained;
|
|
29
|
+
* unsupported custom visual CSS is deliberately not transported. */
|
|
30
|
+
export declare const canonicalBlocksToDocx: (document: SmartDocument) => CanonicalDocxBlock[];
|
|
31
|
+
/** PDF fidelity is visual-only; this deterministic text projection is used by
|
|
32
|
+
* fixture tests and by render adapters as the non-destructive fallback. */
|
|
33
|
+
export declare const canonicalBlocksPdfText: (document: SmartDocument) => string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isTextNode } from "../identity.js";
|
|
2
|
+
import { parseCanonicalListHtml, parseCanonicalListMarkdown, serializeCanonicalListHtml, serializeCanonicalListMarkdown, } from "../list/formats.js";
|
|
3
|
+
export { parseCanonicalListHtml as parseCanonicalBlockHtml, parseCanonicalListMarkdown as parseCanonicalBlockMarkdown, serializeCanonicalListHtml as serializeCanonicalBlockHtml, serializeCanonicalListMarkdown as serializeCanonicalBlockMarkdown, };
|
|
4
|
+
const textOf = (node) => (node.children || []).map((child) => isTextNode(child) ? child.text : child.type === "hard_break" ? "\n" : "").join("");
|
|
5
|
+
/**
|
|
6
|
+
* The per-node half of canonicalBlocksToDocx's visit() body, matching
|
|
7
|
+
* FeatureFormatCodec.serialize's (node, ctx) => unknown shape
|
|
8
|
+
* (formats/codec.ts) - Phase 11 Tier 2's block codec slice. `quoteDepth`
|
|
9
|
+
* defaults to 0 for a direct single-node call (a codec invocation has no
|
|
10
|
+
* ancestor-walk context); canonicalBlocksToDocx below still owns tracking
|
|
11
|
+
* the real depth across a blockquote's descendants and passes it through.
|
|
12
|
+
* Returns null for a block type this mapping doesn't cover (e.g. `table`,
|
|
13
|
+
* `list`), matching FeatureFormatCodec.parse's null-for-inapplicable
|
|
14
|
+
* convention.
|
|
15
|
+
*/
|
|
16
|
+
export const blockToDocxEntry = (node, quoteDepth = 0) => {
|
|
17
|
+
if (!["paragraph", "heading", "code_block"].includes(node.type))
|
|
18
|
+
return null;
|
|
19
|
+
const level = node.type === "heading" ? Math.max(1, Math.min(6, Number(node.attrs?.level) || 1)) : undefined;
|
|
20
|
+
const alignment = typeof node.attrs?.align === "string" && ["left", "center", "right", "justify"].includes(node.attrs.align)
|
|
21
|
+
? node.attrs.align : undefined;
|
|
22
|
+
const indentLevel = Math.max(0, Number(node.attrs?.indentLevel) || 0);
|
|
23
|
+
return {
|
|
24
|
+
nodeId: node.id,
|
|
25
|
+
kind: node.type === "code_block" ? "code" : node.type === "heading" ? "heading" : "paragraph",
|
|
26
|
+
text: textOf(node),
|
|
27
|
+
style: node.type === "heading" ? `Heading${level}` : node.type === "code_block" ? "Code" : quoteDepth ? "Quote" : "Normal",
|
|
28
|
+
...(level ? { outlineLevel: level - 1 } : {}),
|
|
29
|
+
...(alignment ? { alignment } : {}),
|
|
30
|
+
...(indentLevel ? { indentTwips: indentLevel * 720 } : {}),
|
|
31
|
+
...(node.type === "code_block" && typeof node.attrs?.language === "string" ? { language: node.attrs.language } : {}),
|
|
32
|
+
...(quoteDepth ? { quoteDepth } : {}),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/** Semantic DOCX mapping. Heading styles and paragraph properties are retained;
|
|
36
|
+
* unsupported custom visual CSS is deliberately not transported. */
|
|
37
|
+
export const canonicalBlocksToDocx = (document) => {
|
|
38
|
+
const output = [];
|
|
39
|
+
const visit = (node, quoteDepth = 0) => {
|
|
40
|
+
if (node.type === "blockquote") {
|
|
41
|
+
(node.children || []).forEach((child) => { if (!isTextNode(child))
|
|
42
|
+
visit(child, quoteDepth + 1); });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const entry = blockToDocxEntry(node, quoteDepth);
|
|
46
|
+
if (entry)
|
|
47
|
+
output.push(entry);
|
|
48
|
+
};
|
|
49
|
+
document.children.forEach((node) => { if (!isTextNode(node))
|
|
50
|
+
visit(node); });
|
|
51
|
+
return output;
|
|
52
|
+
};
|
|
53
|
+
/** PDF fidelity is visual-only; this deterministic text projection is used by
|
|
54
|
+
* fixture tests and by render adapters as the non-destructive fallback. */
|
|
55
|
+
export const canonicalBlocksPdfText = (document) => canonicalBlocksToDocx(document)
|
|
56
|
+
.map((block) => `${block.kind === "heading" ? `${"#".repeat((block.outlineLevel || 0) + 1)} ` : ""}${block.quoteDepth ? `${"> ".repeat(block.quoteDepth)}` : ""}${block.text}`)
|
|
57
|
+
.join("\n");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SmartDocument, SmartOperation, SmartPos } from "../types.js";
|
|
2
|
+
import type { BlockCommandContext } from "./types.js";
|
|
3
|
+
export interface CodeBlockInputResult {
|
|
4
|
+
readonly operations: SmartOperation[];
|
|
5
|
+
readonly selectionTarget: {
|
|
6
|
+
readonly ownerId: string;
|
|
7
|
+
readonly offset: number;
|
|
8
|
+
};
|
|
9
|
+
readonly intent: "newline" | "indent" | "exit-before" | "exit-after" | "fragment";
|
|
10
|
+
}
|
|
11
|
+
export declare const insertCodeBlockNewline: (document: SmartDocument, pos: SmartPos, options?: {
|
|
12
|
+
readonly exitOnTrailingEmptyLine?: boolean;
|
|
13
|
+
readonly paragraphId?: string;
|
|
14
|
+
}) => CodeBlockInputResult | null;
|
|
15
|
+
export declare const indentInsideCodeBlock: (document: SmartDocument, pos: SmartPos) => CodeBlockInputResult | null;
|
|
16
|
+
/** Ctrl/Cmd+Enter exits before only at offset zero; otherwise it exits after. */
|
|
17
|
+
export declare const exitCodeBlock: (document: SmartDocument, pos: SmartPos, paragraphId?: string) => CodeBlockInputResult | null;
|
|
18
|
+
/** Canonical fragment insertion only; Phase 8 owns clipboard parsing. */
|
|
19
|
+
export declare const insertPlainCodeFragment: (document: SmartDocument, pos: SmartPos, fragment: SmartDocument, _ctx: BlockCommandContext) => CodeBlockInputResult | null;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createNodeId, isTextNode } from "../identity.js";
|
|
2
|
+
import { nodeAtPath } from "../positions.js";
|
|
3
|
+
const codeAt = (document, pos) => {
|
|
4
|
+
const node = nodeAtPath(document, pos.path);
|
|
5
|
+
return node && !isTextNode(node) && node.type === "code_block" ? node : null;
|
|
6
|
+
};
|
|
7
|
+
const textOf = (node) => {
|
|
8
|
+
if (isTextNode(node))
|
|
9
|
+
return node.text;
|
|
10
|
+
if (node.type === "hard_break")
|
|
11
|
+
return "\n";
|
|
12
|
+
return (node.children || []).map(textOf).join(node.type === "doc" || node.type === "blockquote" ? "\n" : "");
|
|
13
|
+
};
|
|
14
|
+
export const insertCodeBlockNewline = (document, pos, options = {}) => {
|
|
15
|
+
const code = codeAt(document, pos);
|
|
16
|
+
if (!code)
|
|
17
|
+
return null;
|
|
18
|
+
const text = textOf(code);
|
|
19
|
+
if (options.exitOnTrailingEmptyLine && pos.offset === text.length && text.endsWith("\n")) {
|
|
20
|
+
return exitCodeBlock(document, pos, options.paragraphId || createNodeId());
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
operations: [{ type: "insertText", pos, text: "\n" }],
|
|
24
|
+
selectionTarget: { ownerId: code.id, offset: pos.offset + 1 },
|
|
25
|
+
intent: "newline",
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export const indentInsideCodeBlock = (document, pos) => {
|
|
29
|
+
const code = codeAt(document, pos);
|
|
30
|
+
if (!code)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
operations: [{ type: "insertText", pos, text: "\t" }],
|
|
34
|
+
selectionTarget: { ownerId: code.id, offset: pos.offset + 1 },
|
|
35
|
+
intent: "indent",
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/** Ctrl/Cmd+Enter exits before only at offset zero; otherwise it exits after. */
|
|
39
|
+
export const exitCodeBlock = (document, pos, paragraphId = createNodeId()) => {
|
|
40
|
+
const code = codeAt(document, pos);
|
|
41
|
+
if (!code || !pos.path.length)
|
|
42
|
+
return null;
|
|
43
|
+
const parentPath = pos.path.slice(0, -1);
|
|
44
|
+
const codeIndex = pos.path[pos.path.length - 1];
|
|
45
|
+
const before = pos.offset === 0;
|
|
46
|
+
return {
|
|
47
|
+
operations: [{
|
|
48
|
+
type: "insertNode",
|
|
49
|
+
pos: { path: parentPath, offset: codeIndex + (before ? 0 : 1) },
|
|
50
|
+
node: { type: "paragraph", id: paragraphId, children: [] },
|
|
51
|
+
}],
|
|
52
|
+
selectionTarget: { ownerId: paragraphId, offset: 0 },
|
|
53
|
+
intent: before ? "exit-before" : "exit-after",
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
/** Canonical fragment insertion only; Phase 8 owns clipboard parsing. */
|
|
57
|
+
export const insertPlainCodeFragment = (document, pos, fragment, _ctx) => {
|
|
58
|
+
const code = codeAt(document, pos);
|
|
59
|
+
if (!code)
|
|
60
|
+
return null;
|
|
61
|
+
const text = fragment.children.map(textOf).join("\n");
|
|
62
|
+
return {
|
|
63
|
+
operations: text ? [{ type: "insertText", pos, text }] : [],
|
|
64
|
+
selectionTarget: { ownerId: code.id, offset: pos.offset + text.length },
|
|
65
|
+
intent: "fragment",
|
|
66
|
+
};
|
|
67
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type KeyboardShortcutContribution, type PluginCommand, type ToolbarContribution } from "../plugin/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps the seven existing, already-tested block commands (block/commands.ts)
|
|
4
|
+
* with the description Phase 10's doc generation needs.
|
|
5
|
+
*/
|
|
6
|
+
export declare const blockPluginCommands: Record<string, PluginCommand>;
|
|
7
|
+
/**
|
|
8
|
+
* Tab inside a code block always wins over list indent/outdent (priority
|
|
9
|
+
* 20 > 10); Tab inside a list wins unless the caret is also in a table (the
|
|
10
|
+
* list command itself has nothing to do outside a list-selection scope, so
|
|
11
|
+
* it naturally yields); nothing is declared for table-grid, so Tab inside a
|
|
12
|
+
* table falls through to native browser/table-cell navigation, unchanged.
|
|
13
|
+
* This reproduces surface/input.ts's pre-Phase-11-Tier-0 hardcoded Tab
|
|
14
|
+
* precedence, now data-driven - see plugin/dispatch.ts's resolveShortcut.
|
|
15
|
+
*/
|
|
16
|
+
export declare const blockPluginShortcuts: readonly KeyboardShortcutContribution[];
|
|
17
|
+
/**
|
|
18
|
+
* One toolbar contribution per blockToolDeclarations entry that needs no
|
|
19
|
+
* caller-generated ids at invocation time (every "setType" and
|
|
20
|
+
* "setAttributes" declaration - headings, paragraph, code block, and the
|
|
21
|
+
* four alignments). The "wrapToggle" (blockquote) declaration needs fresh
|
|
22
|
+
* wrapperIds per invocation, which a static toolbar contribution's params
|
|
23
|
+
* cannot supply, so it is intentionally not represented here - a UI layer
|
|
24
|
+
* invokes block.wrap directly once it has generated ids.
|
|
25
|
+
*/
|
|
26
|
+
export declare const blockToolbarContributions: readonly ToolbarContribution[];
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { definePluginCommand } from "../plugin/types.js";
|
|
2
|
+
import { indentBlockCommand, moveBlockCommand, outdentBlockCommand, setBlockAttributes, setBlockTypeCommand, unwrapBlocks, wrapBlocks, } from "./commands.js";
|
|
3
|
+
import { indentInsideCodeBlock } from "./input.js";
|
|
4
|
+
import { blockToolDeclarations } from "./schema.js";
|
|
5
|
+
/**
|
|
6
|
+
* Wraps the seven existing, already-tested block commands (block/commands.ts)
|
|
7
|
+
* with the description Phase 10's doc generation needs.
|
|
8
|
+
*/
|
|
9
|
+
export const blockPluginCommands = {
|
|
10
|
+
"block.setType": definePluginCommand({
|
|
11
|
+
id: "block.setType", run: setBlockTypeCommand,
|
|
12
|
+
description: "Converts the selected blocks to a different type (paragraph, heading, or code block), preserving IDs and inline content where the target type allows it.",
|
|
13
|
+
options: { type: { description: "The target block type.", required: true }, attrs: { description: "Attributes for the new type, e.g. heading level." } },
|
|
14
|
+
examples: [{ description: "Convert the selection to a level-2 heading", params: { type: "heading", attrs: { level: 2 } } }],
|
|
15
|
+
}),
|
|
16
|
+
"block.wrap": definePluginCommand({
|
|
17
|
+
id: "block.wrap", run: wrapBlocks,
|
|
18
|
+
description: "Wraps the selected blocks in a blockquote, one wrapper per contiguous group.",
|
|
19
|
+
options: { type: { description: "Currently only \"blockquote\".", required: true }, wrapperIds: { description: "Caller-provided ids, one per wrapper node created.", required: true } },
|
|
20
|
+
}),
|
|
21
|
+
"block.unwrap": definePluginCommand({
|
|
22
|
+
id: "block.unwrap", run: unwrapBlocks,
|
|
23
|
+
description: "Removes the enclosing blockquote (or other wrapper) from the selected blocks, promoting their content back out.",
|
|
24
|
+
}),
|
|
25
|
+
"block.setAttributes": definePluginCommand({
|
|
26
|
+
id: "block.setAttributes", run: setBlockAttributes,
|
|
27
|
+
description: "Sets attributes (e.g. alignment, indent level) on the selected blocks.",
|
|
28
|
+
options: { attrs: { description: "The attributes to set.", required: true } },
|
|
29
|
+
examples: [{ description: "Center-align the selected blocks", params: { attrs: { align: "center" } } }],
|
|
30
|
+
}),
|
|
31
|
+
"block.move": definePluginCommand({
|
|
32
|
+
id: "block.move", run: moveBlockCommand,
|
|
33
|
+
description: "Moves the selected contiguous blocks up or down among their siblings.",
|
|
34
|
+
options: { direction: { description: "\"up\" or \"down\".", required: true } },
|
|
35
|
+
}),
|
|
36
|
+
"block.indent": definePluginCommand({
|
|
37
|
+
id: "block.indent", run: indentBlockCommand,
|
|
38
|
+
description: "Increases the selected blocks' indent level.",
|
|
39
|
+
options: { amount: { description: "How many levels to indent by (default 1)." } },
|
|
40
|
+
}),
|
|
41
|
+
"block.outdent": definePluginCommand({
|
|
42
|
+
id: "block.outdent", run: outdentBlockCommand,
|
|
43
|
+
description: "Decreases the selected blocks' indent level.",
|
|
44
|
+
options: { amount: { description: "How many levels to outdent by (default 1)." } },
|
|
45
|
+
}),
|
|
46
|
+
"block.code.indentTab": definePluginCommand({
|
|
47
|
+
id: "block.code.indentTab", run: (document, scope) => scope.kind === "block-range" ? indentInsideCodeBlock(document, scope.range.to)?.operations ?? [] : [],
|
|
48
|
+
description: "Inserts a literal tab character at the caret when it is inside a code block; produces no operations otherwise.",
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Tab inside a code block always wins over list indent/outdent (priority
|
|
53
|
+
* 20 > 10); Tab inside a list wins unless the caret is also in a table (the
|
|
54
|
+
* list command itself has nothing to do outside a list-selection scope, so
|
|
55
|
+
* it naturally yields); nothing is declared for table-grid, so Tab inside a
|
|
56
|
+
* table falls through to native browser/table-cell navigation, unchanged.
|
|
57
|
+
* This reproduces surface/input.ts's pre-Phase-11-Tier-0 hardcoded Tab
|
|
58
|
+
* precedence, now data-driven - see plugin/dispatch.ts's resolveShortcut.
|
|
59
|
+
*/
|
|
60
|
+
export const blockPluginShortcuts = [
|
|
61
|
+
{ id: "block.code.indentTab", commandId: "block.code.indentTab", key: "Tab", scopeKinds: ["block-range"], priority: 20 },
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* One toolbar contribution per blockToolDeclarations entry that needs no
|
|
65
|
+
* caller-generated ids at invocation time (every "setType" and
|
|
66
|
+
* "setAttributes" declaration - headings, paragraph, code block, and the
|
|
67
|
+
* four alignments). The "wrapToggle" (blockquote) declaration needs fresh
|
|
68
|
+
* wrapperIds per invocation, which a static toolbar contribution's params
|
|
69
|
+
* cannot supply, so it is intentionally not represented here - a UI layer
|
|
70
|
+
* invokes block.wrap directly once it has generated ids.
|
|
71
|
+
*/
|
|
72
|
+
export const blockToolbarContributions = blockToolDeclarations
|
|
73
|
+
.filter((declaration) => declaration.kind === "setType" || declaration.kind === "setAttributes")
|
|
74
|
+
.map((declaration) => ({
|
|
75
|
+
id: `block.toolbar.${declaration.id}`,
|
|
76
|
+
commandId: declaration.kind === "setType" ? "block.setType" : "block.setAttributes",
|
|
77
|
+
params: declaration.kind === "setType" ? { type: declaration.type, attrs: "attrs" in declaration ? declaration.attrs : undefined } : { attrs: "attrs" in declaration ? declaration.attrs : undefined },
|
|
78
|
+
label: declaration.id,
|
|
79
|
+
}));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Attrs, NodeSpec } from "../types.js";
|
|
2
|
+
/** Node specs owned by the block family - extracted out of foundation/schema.ts's previously hardcoded foundationSchema literal (Phase 10). */
|
|
3
|
+
export declare const blockNodeSpecs: readonly NodeSpec[];
|
|
4
|
+
export declare const blockToolDeclarations: readonly [...{
|
|
5
|
+
id: string;
|
|
6
|
+
kind: "setType";
|
|
7
|
+
type: "heading";
|
|
8
|
+
attrs: {
|
|
9
|
+
level: number;
|
|
10
|
+
};
|
|
11
|
+
validate: (attrs: Attrs | undefined) => boolean;
|
|
12
|
+
}[], {
|
|
13
|
+
readonly id: "paragraph";
|
|
14
|
+
readonly kind: "setType";
|
|
15
|
+
readonly type: "paragraph";
|
|
16
|
+
}, {
|
|
17
|
+
readonly id: "codeBlock";
|
|
18
|
+
readonly kind: "setType";
|
|
19
|
+
readonly type: "code_block";
|
|
20
|
+
}, {
|
|
21
|
+
readonly id: "blockquote";
|
|
22
|
+
readonly kind: "wrapToggle";
|
|
23
|
+
readonly type: "blockquote";
|
|
24
|
+
readonly nestable: true;
|
|
25
|
+
}, {
|
|
26
|
+
readonly id: "alignLeft";
|
|
27
|
+
readonly kind: "setAttributes";
|
|
28
|
+
readonly attrs: {
|
|
29
|
+
readonly align: "left";
|
|
30
|
+
};
|
|
31
|
+
}, {
|
|
32
|
+
readonly id: "alignCenter";
|
|
33
|
+
readonly kind: "setAttributes";
|
|
34
|
+
readonly attrs: {
|
|
35
|
+
readonly align: "center";
|
|
36
|
+
};
|
|
37
|
+
}, {
|
|
38
|
+
readonly id: "alignRight";
|
|
39
|
+
readonly kind: "setAttributes";
|
|
40
|
+
readonly attrs: {
|
|
41
|
+
readonly align: "right";
|
|
42
|
+
};
|
|
43
|
+
}, {
|
|
44
|
+
readonly id: "alignJustify";
|
|
45
|
+
readonly kind: "setAttributes";
|
|
46
|
+
readonly attrs: {
|
|
47
|
+
readonly align: "justify";
|
|
48
|
+
};
|
|
49
|
+
}];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const stringAttr = { validate: (value) => typeof value === "string" };
|
|
2
|
+
const alignmentAttr = { validate: (value) => ["left", "center", "right", "justify"].includes(String(value)) };
|
|
3
|
+
const indentLevelAttr = { validate: (value) => Number.isInteger(value) && Number(value) >= 0 && Number(value) <= 10 };
|
|
4
|
+
const blockAttrs = { align: alignmentAttr, indentLevel: indentLevelAttr };
|
|
5
|
+
/** Node specs owned by the block family - extracted out of foundation/schema.ts's previously hardcoded foundationSchema literal (Phase 10). */
|
|
6
|
+
export const blockNodeSpecs = [
|
|
7
|
+
{ type: "paragraph", group: "block", content: "inline*", attributes: blockAttrs },
|
|
8
|
+
{ type: "heading", group: "block", content: "inline*", attributes: { ...blockAttrs, level: { required: true, default: 1, validate: (v) => Number.isInteger(v) && Number(v) >= 1 && Number(v) <= 6 } } },
|
|
9
|
+
{ type: "blockquote", group: "block", content: "block+", attributes: blockAttrs, defining: true },
|
|
10
|
+
{ type: "code_block", group: "block", content: "text*", marks: "", attributes: { ...blockAttrs, language: stringAttr }, defining: true },
|
|
11
|
+
];
|
|
12
|
+
export const blockToolDeclarations = [
|
|
13
|
+
...Array.from({ length: 6 }, (_, index) => ({
|
|
14
|
+
id: `heading${index + 1}`,
|
|
15
|
+
kind: "setType",
|
|
16
|
+
type: "heading",
|
|
17
|
+
attrs: { level: index + 1 },
|
|
18
|
+
validate: (attrs) => Number.isInteger(attrs?.level) && Number(attrs?.level) >= 1 && Number(attrs?.level) <= 6,
|
|
19
|
+
})),
|
|
20
|
+
{ id: "paragraph", kind: "setType", type: "paragraph" },
|
|
21
|
+
{ id: "codeBlock", kind: "setType", type: "code_block" },
|
|
22
|
+
{ id: "blockquote", kind: "wrapToggle", type: "blockquote", nestable: true },
|
|
23
|
+
{ id: "alignLeft", kind: "setAttributes", attrs: { align: "left" } },
|
|
24
|
+
{ id: "alignCenter", kind: "setAttributes", attrs: { align: "center" } },
|
|
25
|
+
{ id: "alignRight", kind: "setAttributes", attrs: { align: "right" } },
|
|
26
|
+
{ id: "alignJustify", kind: "setAttributes", attrs: { align: "justify" } },
|
|
27
|
+
];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ResolvedScope } from "../scope/types.js";
|
|
2
|
+
import type { Attrs, SmartDocument, SmartOperation, SmartSchema } from "../types.js";
|
|
3
|
+
import type { PositionLookup } from "../scope/types.js";
|
|
4
|
+
export interface BlockCommandContext {
|
|
5
|
+
readonly schema: SmartSchema;
|
|
6
|
+
readonly positions: PositionLookup;
|
|
7
|
+
}
|
|
8
|
+
export type BlockCommand<P> = (document: SmartDocument, scope: ResolvedScope, params: P, ctx: BlockCommandContext) => SmartOperation[];
|
|
9
|
+
export interface SetBlockTypeParams {
|
|
10
|
+
readonly type: "paragraph" | "heading" | "code_block";
|
|
11
|
+
readonly attrs?: Attrs;
|
|
12
|
+
}
|
|
13
|
+
export interface WrapBlocksParams {
|
|
14
|
+
readonly type: "blockquote";
|
|
15
|
+
readonly attrs?: Attrs;
|
|
16
|
+
readonly wrapperIds: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export interface UnwrapBlocksParams {
|
|
19
|
+
readonly type?: "blockquote";
|
|
20
|
+
}
|
|
21
|
+
export interface SetBlockAttributesParams {
|
|
22
|
+
readonly attrs: Attrs;
|
|
23
|
+
}
|
|
24
|
+
export interface MoveBlocksParams {
|
|
25
|
+
readonly direction: "up" | "down";
|
|
26
|
+
}
|
|
27
|
+
export interface IndentBlocksParams {
|
|
28
|
+
readonly amount?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface OutdentBlocksParams {
|
|
31
|
+
readonly amount?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface BlockToolDeclaration {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly kind: "setType" | "wrapToggle" | "setAttributes";
|
|
36
|
+
readonly type?: "paragraph" | "heading" | "code_block" | "blockquote";
|
|
37
|
+
readonly attrs?: Attrs;
|
|
38
|
+
readonly nestable?: boolean;
|
|
39
|
+
readonly validate?: (attrs: Attrs | undefined) => boolean;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NormalizerRegistration, SmartDocument, SmartOperation, SmartSchema } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Returns deterministic insert operations for missing outside-edge caret
|
|
4
|
+
* blocks. The traversal is scoped by the supplied root path, allowing the
|
|
5
|
+
* normalizer to remain local for ordinary typing.
|
|
6
|
+
*/
|
|
7
|
+
export declare const editableBoundaryOperations: (document: SmartDocument, schema: SmartSchema, rootPath?: readonly number[]) => SmartOperation[];
|
|
8
|
+
export interface EditableBoundaryNormalizerOptions {
|
|
9
|
+
readonly priority?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const createEditableBoundaryNormalizer: (options?: EditableBoundaryNormalizerOptions) => NormalizerRegistration;
|