smartrte-core 0.2.0 → 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 +16 -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 +45 -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,90 @@
|
|
|
1
|
+
import { applyOperations, invertOperations } from "./operations.js";
|
|
2
|
+
import { isTextNode } from "./identity.js";
|
|
3
|
+
import { nodeAtPath, resolvePos } from "./positions.js";
|
|
4
|
+
import { validate } from "./schema.js";
|
|
5
|
+
export class StaleTransactionError extends Error {
|
|
6
|
+
constructor(expected, actual) {
|
|
7
|
+
super(`Stale transaction: base revision ${actual}, current revision ${expected}.`);
|
|
8
|
+
this.name = "StaleTransactionError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A transaction was behind the current revision, but not too far behind to
|
|
13
|
+
* rebase - and at least one of its operations could not be safely
|
|
14
|
+
* transformed against what was committed in the meantime (see
|
|
15
|
+
* collab/transform.ts's doc comment for exactly which shapes this covers:
|
|
16
|
+
* concurrent moves sharing an array, or a concurrent edit to content a
|
|
17
|
+
* merge absorbed). The transaction is rejected outright rather than
|
|
18
|
+
* partially or incorrectly applied - the caller (a real transport
|
|
19
|
+
* implementation, in 12b-server/transport) decides what happens to the
|
|
20
|
+
* user's edit from here (e.g. surface it for manual resolution, retry as a
|
|
21
|
+
* fresh transaction against current state, or convert it to a suggestion).
|
|
22
|
+
*/
|
|
23
|
+
export class RebaseConflictError extends Error {
|
|
24
|
+
constructor(reasons) {
|
|
25
|
+
super(`Transaction could not be rebased: ${reasons.map((entry) => entry.reason).join("; ")}`);
|
|
26
|
+
this.reasons = reasons;
|
|
27
|
+
this.name = "RebaseConflictError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A transaction's baseRevision is older than anything this editor instance
|
|
32
|
+
* has retained (see FoundationEditor's revisionLogLimit) - there is not
|
|
33
|
+
* enough history left to rebase through. The caller must resync (reload
|
|
34
|
+
* the full document at the current revision, e.g. via replaceState) rather
|
|
35
|
+
* than attempt a partial rebase against an incomplete operation log.
|
|
36
|
+
*/
|
|
37
|
+
export class ResyncRequiredError extends Error {
|
|
38
|
+
constructor(atRevision) {
|
|
39
|
+
super(`Transaction is too far behind to rebase (current revision ${atRevision}); a full resync is required.`);
|
|
40
|
+
this.atRevision = atRevision;
|
|
41
|
+
this.name = "ResyncRequiredError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export const invertTransaction = (transaction) => ({
|
|
45
|
+
id: `inverse:${transaction.id}`,
|
|
46
|
+
baseRevision: transaction.baseRevision + 1,
|
|
47
|
+
operations: invertOperations(transaction.operations),
|
|
48
|
+
selectionBefore: structuredClone(transaction.selectionAfter),
|
|
49
|
+
selectionAfter: structuredClone(transaction.selectionBefore),
|
|
50
|
+
...(transaction.storedMarksAfter ? { storedMarksBefore: structuredClone(transaction.storedMarksAfter) } : {}),
|
|
51
|
+
...(transaction.storedMarksBefore ? { storedMarksAfter: structuredClone(transaction.storedMarksBefore) } : {}),
|
|
52
|
+
metadata: {
|
|
53
|
+
...structuredClone(transaction.metadata),
|
|
54
|
+
source: "api",
|
|
55
|
+
addToHistory: false,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
export const assertTransactionSerializable = (transaction) => {
|
|
59
|
+
const json = JSON.stringify(transaction);
|
|
60
|
+
if (json === undefined)
|
|
61
|
+
throw new Error("Transaction is not JSON-serializable.");
|
|
62
|
+
const parsed = JSON.parse(json);
|
|
63
|
+
if (JSON.stringify(parsed) !== json)
|
|
64
|
+
throw new Error("Transaction is not stably JSON-serializable.");
|
|
65
|
+
};
|
|
66
|
+
export const applyTransactionAtomic = (state, transaction, schema) => {
|
|
67
|
+
if (transaction.baseRevision !== state.revision)
|
|
68
|
+
throw new StaleTransactionError(state.revision, transaction.baseRevision);
|
|
69
|
+
assertTransactionSerializable(transaction);
|
|
70
|
+
const document = applyOperations(state.document, transaction.operations);
|
|
71
|
+
const textOnly = transaction.operations.every((operation) => operation.type === "insertText" || operation.type === "deleteText" || operation.type === "addMark" || operation.type === "removeMark");
|
|
72
|
+
const affectedPaths = [...new Set(transaction.operations.flatMap((operation) => operation.type === "addMark" || operation.type === "removeMark"
|
|
73
|
+
? [JSON.stringify(operation.range.from.path), JSON.stringify(operation.range.to.path)]
|
|
74
|
+
: "pos" in operation ? [JSON.stringify(operation.pos.path)] : []))];
|
|
75
|
+
const errors = textOnly
|
|
76
|
+
? affectedPaths.flatMap((pathJson) => {
|
|
77
|
+
const node = nodeAtPath(document, JSON.parse(pathJson));
|
|
78
|
+
if (!node || isTextNode(node))
|
|
79
|
+
return [{ path: JSON.parse(pathJson), code: "invalid-text-owner", message: "Text operation owner no longer exists." }];
|
|
80
|
+
return validate({ type: "doc", id: "local-validation-root", children: [node] }, schema)
|
|
81
|
+
.filter((error) => error.path.length > 0);
|
|
82
|
+
})
|
|
83
|
+
: validate(document, schema);
|
|
84
|
+
if (errors.length)
|
|
85
|
+
throw new Error(`Transaction produced an invalid document: ${errors[0].message}`);
|
|
86
|
+
resolvePos(document, transaction.selectionAfter.anchor);
|
|
87
|
+
resolvePos(document, transaction.selectionAfter.head);
|
|
88
|
+
return { schemaVersion: schema.version, revision: state.revision + 1, document };
|
|
89
|
+
};
|
|
90
|
+
export const documentsEqual = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
export type NodeGroup = "document" | "block" | "inline";
|
|
2
|
+
export type Attrs = Readonly<Record<string, unknown>>;
|
|
3
|
+
export interface AttributeSpec {
|
|
4
|
+
default?: unknown;
|
|
5
|
+
required?: boolean;
|
|
6
|
+
validate?: (value: unknown) => boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface NodeSpec {
|
|
9
|
+
type: string;
|
|
10
|
+
group: NodeGroup;
|
|
11
|
+
content?: string;
|
|
12
|
+
marks?: string[] | "_all" | "";
|
|
13
|
+
attributes?: Record<string, AttributeSpec>;
|
|
14
|
+
atomic?: boolean;
|
|
15
|
+
isolating?: boolean;
|
|
16
|
+
selectable?: boolean;
|
|
17
|
+
defining?: boolean;
|
|
18
|
+
/** Structural role for scope resolution. Falls back to `type` when absent. */
|
|
19
|
+
semanticRole?: "list" | "list-item" | "table" | "table-row" | "table-cell";
|
|
20
|
+
}
|
|
21
|
+
export interface MarkSpec {
|
|
22
|
+
type: string;
|
|
23
|
+
attributes?: Record<string, AttributeSpec>;
|
|
24
|
+
inclusive?: boolean;
|
|
25
|
+
excludes?: string[];
|
|
26
|
+
spanning?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface SmartSchema {
|
|
29
|
+
readonly nodes: Readonly<Record<string, Readonly<NodeSpec>>>;
|
|
30
|
+
readonly marks: Readonly<Record<string, Readonly<MarkSpec>>>;
|
|
31
|
+
readonly topNode: string;
|
|
32
|
+
readonly version: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SmartMark {
|
|
35
|
+
readonly type: string;
|
|
36
|
+
readonly attrs?: Attrs;
|
|
37
|
+
}
|
|
38
|
+
export interface SmartTextNode {
|
|
39
|
+
readonly type: "text";
|
|
40
|
+
readonly text: string;
|
|
41
|
+
readonly marks?: readonly SmartMark[];
|
|
42
|
+
}
|
|
43
|
+
export interface SmartElementNode {
|
|
44
|
+
readonly type: string;
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly attrs?: Attrs;
|
|
47
|
+
readonly children?: readonly SmartNode[];
|
|
48
|
+
}
|
|
49
|
+
export type SmartNode = SmartTextNode | SmartElementNode;
|
|
50
|
+
export interface SmartDocument extends SmartElementNode {
|
|
51
|
+
readonly type: "doc";
|
|
52
|
+
readonly children: readonly SmartNode[];
|
|
53
|
+
}
|
|
54
|
+
export interface PersistedEditorDocument {
|
|
55
|
+
schemaVersion: number;
|
|
56
|
+
revision: number;
|
|
57
|
+
document: SmartDocument;
|
|
58
|
+
}
|
|
59
|
+
export interface SmartPos {
|
|
60
|
+
/**
|
|
61
|
+
* Path of the owning non-text node. Offset meaning is resolved from that
|
|
62
|
+
* node's schema role: UTF-16 units for an inline-content owner, child
|
|
63
|
+
* boundaries for a structural owner. Inline atomic children occupy exactly
|
|
64
|
+
* one UTF-16-offset unit and have no internal cursor positions.
|
|
65
|
+
*
|
|
66
|
+
* This is intentionally not a stored discriminated union: a stored kind
|
|
67
|
+
* could disagree with the schema after version skew. `ResolvedPos.kind` is
|
|
68
|
+
* the authoritative, schema-derived discriminant consumed by commands.
|
|
69
|
+
*/
|
|
70
|
+
path: number[];
|
|
71
|
+
offset: number;
|
|
72
|
+
}
|
|
73
|
+
export interface ResolvedPos {
|
|
74
|
+
pos: SmartPos;
|
|
75
|
+
kind: "inline" | "structural";
|
|
76
|
+
nodeId: string;
|
|
77
|
+
parent: SmartElementNode;
|
|
78
|
+
depth: number;
|
|
79
|
+
affinity: "forward" | "backward";
|
|
80
|
+
ancestors: readonly SmartElementNode[];
|
|
81
|
+
nodeBefore: SmartNode | null;
|
|
82
|
+
nodeAfter: SmartNode | null;
|
|
83
|
+
atStart: boolean;
|
|
84
|
+
atEnd: boolean;
|
|
85
|
+
indexAt(depth: number): number;
|
|
86
|
+
}
|
|
87
|
+
export interface SmartRange {
|
|
88
|
+
from: SmartPos;
|
|
89
|
+
to: SmartPos;
|
|
90
|
+
}
|
|
91
|
+
export interface SmartSelection {
|
|
92
|
+
anchor: SmartPos;
|
|
93
|
+
head: SmartPos;
|
|
94
|
+
type: "text" | "node" | "cell" | "none";
|
|
95
|
+
}
|
|
96
|
+
export type SmartOperation = {
|
|
97
|
+
type: "insertNode";
|
|
98
|
+
pos: SmartPos;
|
|
99
|
+
node: SmartNode;
|
|
100
|
+
} | {
|
|
101
|
+
type: "removeNode";
|
|
102
|
+
pos: SmartPos;
|
|
103
|
+
node: SmartNode;
|
|
104
|
+
/**
|
|
105
|
+
* Set when this removal is semantically a merge - the node's content
|
|
106
|
+
* was absorbed into the node at this id, not simply deleted. Purely
|
|
107
|
+
* informational: every existing consumer of `removeNode` (apply,
|
|
108
|
+
* invert, map, normalization, history rebasing) ignores it, since
|
|
109
|
+
* removal itself is unaffected either way. Populated by
|
|
110
|
+
* `mergeTableCellsCommand` for an absorbed cell; consumed by
|
|
111
|
+
* `rebaseAnnotationRange` to decide whether an annotation anchored to
|
|
112
|
+
* this id should snap to the surviving node instead of orphaning.
|
|
113
|
+
*/
|
|
114
|
+
mergedInto?: string;
|
|
115
|
+
} | {
|
|
116
|
+
type: "replaceNode";
|
|
117
|
+
pos: SmartPos;
|
|
118
|
+
before: SmartNode;
|
|
119
|
+
after: SmartNode;
|
|
120
|
+
/**
|
|
121
|
+
* Ids present in `before`'s subtree that have no counterpart in
|
|
122
|
+
* `after` because their content was folded into a node that does
|
|
123
|
+
* survive, paired with which surviving id each one merged into (a
|
|
124
|
+
* single `replaceNode` can retire more than one id into more than
|
|
125
|
+
* one survivor - e.g. list item merge retires both the outer
|
|
126
|
+
* `list_item` wrapper and its inner paragraph, into the target
|
|
127
|
+
* item's wrapper and paragraph respectively, not one shared
|
|
128
|
+
* survivor). Every other id difference between before/after is an
|
|
129
|
+
* ordinary content change, not a merge. Same "purely informational,
|
|
130
|
+
* ignored by every existing consumer" contract as
|
|
131
|
+
* `removeNode.mergedInto` above. Populated by list item merge
|
|
132
|
+
* (`mergeItems`); consumed by `rebaseAnnotationRange`.
|
|
133
|
+
*/
|
|
134
|
+
retiredInto?: readonly {
|
|
135
|
+
readonly retiredId: string;
|
|
136
|
+
readonly survivorId: string;
|
|
137
|
+
}[];
|
|
138
|
+
} | {
|
|
139
|
+
type: "moveNode";
|
|
140
|
+
from: SmartPos;
|
|
141
|
+
to: SmartPos;
|
|
142
|
+
nodeId: string;
|
|
143
|
+
} | {
|
|
144
|
+
type: "splitNode";
|
|
145
|
+
pos: SmartPos;
|
|
146
|
+
depth: number;
|
|
147
|
+
newId: string;
|
|
148
|
+
} | {
|
|
149
|
+
type: "mergeNode";
|
|
150
|
+
pos: SmartPos;
|
|
151
|
+
depth: number;
|
|
152
|
+
retiredId: string;
|
|
153
|
+
splitOffset: number;
|
|
154
|
+
} | {
|
|
155
|
+
type: "setNodeAttributes";
|
|
156
|
+
pos: SmartPos;
|
|
157
|
+
before: Attrs;
|
|
158
|
+
after: Attrs;
|
|
159
|
+
} | {
|
|
160
|
+
type: "setNodeType";
|
|
161
|
+
pos: SmartPos;
|
|
162
|
+
before: string;
|
|
163
|
+
after: string;
|
|
164
|
+
beforeAttrs: Attrs;
|
|
165
|
+
afterAttrs: Attrs;
|
|
166
|
+
} | {
|
|
167
|
+
type: "insertText";
|
|
168
|
+
pos: SmartPos;
|
|
169
|
+
text: string;
|
|
170
|
+
marks?: SmartMark[];
|
|
171
|
+
} | {
|
|
172
|
+
type: "deleteText";
|
|
173
|
+
pos: SmartPos;
|
|
174
|
+
text: string;
|
|
175
|
+
marks?: SmartMark[];
|
|
176
|
+
} | {
|
|
177
|
+
type: "addMark";
|
|
178
|
+
range: SmartRange;
|
|
179
|
+
mark: SmartMark;
|
|
180
|
+
} | {
|
|
181
|
+
type: "removeMark";
|
|
182
|
+
range: SmartRange;
|
|
183
|
+
mark: SmartMark;
|
|
184
|
+
};
|
|
185
|
+
export interface SmartTransaction {
|
|
186
|
+
id: string;
|
|
187
|
+
baseRevision: number;
|
|
188
|
+
operations: SmartOperation[];
|
|
189
|
+
selectionBefore: SmartSelection;
|
|
190
|
+
selectionAfter: SmartSelection;
|
|
191
|
+
storedMarksBefore?: SmartMark[];
|
|
192
|
+
storedMarksAfter?: SmartMark[];
|
|
193
|
+
metadata: {
|
|
194
|
+
source: "input" | "keyboard" | "toolbar" | "paste" | "cut" | "drop" | "api";
|
|
195
|
+
timestamp: number;
|
|
196
|
+
historyGroup?: string;
|
|
197
|
+
addToHistory: boolean;
|
|
198
|
+
compositionId?: string;
|
|
199
|
+
authorId?: string;
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
export interface TransactionMap {
|
|
203
|
+
map(pos: SmartPos, bias?: -1 | 1): SmartPos;
|
|
204
|
+
mapRange(range: SmartRange): SmartRange;
|
|
205
|
+
mapSelection(selection: SmartSelection): SmartSelection;
|
|
206
|
+
deleted(pos: SmartPos): boolean;
|
|
207
|
+
}
|
|
208
|
+
export interface NormalizerContext {
|
|
209
|
+
readonly schema: SmartSchema;
|
|
210
|
+
readonly affectedPath: readonly number[];
|
|
211
|
+
readonly pass: number;
|
|
212
|
+
readonly documentWide: boolean;
|
|
213
|
+
}
|
|
214
|
+
export interface NormalizerResult {
|
|
215
|
+
operations: SmartOperation[];
|
|
216
|
+
}
|
|
217
|
+
export interface NormalizerRegistration {
|
|
218
|
+
id: string;
|
|
219
|
+
priority: number;
|
|
220
|
+
documentWide?: boolean;
|
|
221
|
+
normalize(document: SmartDocument, context: NormalizerContext): NormalizerResult;
|
|
222
|
+
}
|
|
223
|
+
export interface HistoryEntry {
|
|
224
|
+
forward: SmartTransaction;
|
|
225
|
+
inverse: SmartTransaction;
|
|
226
|
+
estimatedBytes: number;
|
|
227
|
+
}
|
|
228
|
+
export interface SmartHistory {
|
|
229
|
+
readonly undo: readonly HistoryEntry[];
|
|
230
|
+
readonly redo: readonly HistoryEntry[];
|
|
231
|
+
readonly limit: number;
|
|
232
|
+
/** Maximum estimated serialized bytes retained in either history stack. */
|
|
233
|
+
readonly byteLimit: number;
|
|
234
|
+
readonly coalescenceWindowMs: number;
|
|
235
|
+
}
|
|
236
|
+
export interface ModelDomMapping {
|
|
237
|
+
nodeToDom(nodeId: string): HTMLElement | null;
|
|
238
|
+
domToNode(el: Node): {
|
|
239
|
+
nodeId: string;
|
|
240
|
+
node: SmartNode;
|
|
241
|
+
} | null;
|
|
242
|
+
posToDom(pos: SmartPos): {
|
|
243
|
+
node: Node;
|
|
244
|
+
offset: number;
|
|
245
|
+
} | null;
|
|
246
|
+
domToPos(node: Node, offset: number): SmartPos | null;
|
|
247
|
+
isEditorUiNode(el: Node): boolean;
|
|
248
|
+
rebuild(root: HTMLElement, document: SmartDocument): void;
|
|
249
|
+
}
|
|
250
|
+
export interface ValidationError {
|
|
251
|
+
path: number[];
|
|
252
|
+
code: string;
|
|
253
|
+
message: string;
|
|
254
|
+
}
|
|
255
|
+
export interface Repair {
|
|
256
|
+
path: number[];
|
|
257
|
+
code: string;
|
|
258
|
+
message: string;
|
|
259
|
+
before?: unknown;
|
|
260
|
+
after?: unknown;
|
|
261
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DocumentDiff } from "../diff/types.js";
|
|
2
|
+
import type { SmartSchema } from "../types.js";
|
|
3
|
+
import type { DocumentVersion } from "./types.js";
|
|
4
|
+
/** Thin convenience wrapper over `diffDocuments` for the common "diff two saved versions" call shape. */
|
|
5
|
+
export declare const diffVersions: (a: DocumentVersion, b: DocumentVersion, schema: SmartSchema) => DocumentDiff;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { diffDocuments } from "../diff/documentDiff.js";
|
|
2
|
+
/** Thin convenience wrapper over `diffDocuments` for the common "diff two saved versions" call shape. */
|
|
3
|
+
export const diffVersions = (a, b, schema) => diffDocuments(a.envelope.document, b.envelope.document, schema);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PersistedEditorDocument } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A named, saved snapshot of the document a user can return to or compare
|
|
4
|
+
* against - distinct from the undo/redo history stack (`history.ts`), which
|
|
5
|
+
* is moment-to-moment edit state, not something a user browses or restores
|
|
6
|
+
* from days later. Reuses the existing `PersistedEditorDocument` envelope
|
|
7
|
+
* verbatim as its payload rather than a new document shape - a version
|
|
8
|
+
* *is* a full document snapshot with a revision, which is exactly what
|
|
9
|
+
* that envelope already represents.
|
|
10
|
+
*/
|
|
11
|
+
export interface DocumentVersion {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly label?: string;
|
|
14
|
+
readonly createdAt: number;
|
|
15
|
+
readonly authorId?: string;
|
|
16
|
+
readonly envelope: PersistedEditorDocument;
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SmartEditorState, type LegacySmartTransaction } from "./transaction.js";
|
|
2
|
+
export interface LegacyHistoryEntry {
|
|
3
|
+
forward: LegacySmartTransaction;
|
|
4
|
+
inverse: LegacySmartTransaction;
|
|
5
|
+
}
|
|
6
|
+
export interface SmartHistoryState {
|
|
7
|
+
undo: LegacyHistoryEntry[];
|
|
8
|
+
redo: LegacyHistoryEntry[];
|
|
9
|
+
limit: number;
|
|
10
|
+
}
|
|
11
|
+
export interface HistoryResult {
|
|
12
|
+
state: SmartEditorState;
|
|
13
|
+
history: SmartHistoryState;
|
|
14
|
+
applied: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare const createHistoryState: (limit?: number) => SmartHistoryState;
|
|
17
|
+
export declare const invertLegacyTransaction: (state: SmartEditorState, transaction: LegacySmartTransaction) => LegacySmartTransaction;
|
|
18
|
+
export declare const applyTransactionWithHistory: (state: SmartEditorState, history: SmartHistoryState, transaction: LegacySmartTransaction) => HistoryResult;
|
|
19
|
+
export declare const undoHistory: (state: SmartEditorState, history: SmartHistoryState) => HistoryResult;
|
|
20
|
+
export declare const redoHistory: (state: SmartEditorState, history: SmartHistoryState) => HistoryResult;
|
package/dist/history.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { mapSelectionThroughOperation } from "./selectionMapping.js";
|
|
2
|
+
import { normalizeSmartDocument } from "./schema.js";
|
|
3
|
+
import { applyOperationToState, applyTransaction, getMoveDestinationAfterRemoval, } from "./transaction.js";
|
|
4
|
+
import { getNodeAtTreePath, isSmartContainer } from "./tree.js";
|
|
5
|
+
export const createHistoryState = (limit = 100) => ({
|
|
6
|
+
undo: [],
|
|
7
|
+
redo: [],
|
|
8
|
+
limit: Math.max(1, Math.floor(limit)),
|
|
9
|
+
});
|
|
10
|
+
const parentPath = (path) => path.slice(0, -1);
|
|
11
|
+
const inverseForOperation = (document, selection, operation) => {
|
|
12
|
+
if (operation.type === "insertNode")
|
|
13
|
+
return [{ type: "removeNode", path: operation.path }];
|
|
14
|
+
if (operation.type === "removeNode") {
|
|
15
|
+
return [{ type: "insertNode", path: operation.path, node: getNodeAtTreePath(document, operation.path) }];
|
|
16
|
+
}
|
|
17
|
+
if (operation.type === "replaceNode") {
|
|
18
|
+
return [{ type: "replaceNode", path: operation.path, node: getNodeAtTreePath(document, operation.path) }];
|
|
19
|
+
}
|
|
20
|
+
if (operation.type === "moveNode") {
|
|
21
|
+
const movedNode = getNodeAtTreePath(document, operation.from);
|
|
22
|
+
const destination = getMoveDestinationAfterRemoval(operation.from, operation.to);
|
|
23
|
+
return [
|
|
24
|
+
{ type: "removeNode", path: destination },
|
|
25
|
+
{ type: "insertNode", path: operation.from, node: movedNode },
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
if (operation.type === "splitNode") {
|
|
29
|
+
const rightPath = [
|
|
30
|
+
...parentPath(operation.path),
|
|
31
|
+
operation.path[operation.path.length - 1] + 1,
|
|
32
|
+
];
|
|
33
|
+
return [{ type: "mergeNode", path: rightPath }];
|
|
34
|
+
}
|
|
35
|
+
if (operation.type === "mergeNode") {
|
|
36
|
+
const leftPath = [
|
|
37
|
+
...parentPath(operation.path),
|
|
38
|
+
operation.path[operation.path.length - 1] - 1,
|
|
39
|
+
];
|
|
40
|
+
const left = getNodeAtTreePath(document, leftPath);
|
|
41
|
+
const position = left?.type === "text"
|
|
42
|
+
? left.text.length
|
|
43
|
+
: isSmartContainer(left)
|
|
44
|
+
? left.children.length
|
|
45
|
+
: 0;
|
|
46
|
+
return [{ type: "splitNode", path: leftPath, position }];
|
|
47
|
+
}
|
|
48
|
+
if (operation.type === "setNodeAttrs") {
|
|
49
|
+
const node = getNodeAtTreePath(document, operation.path);
|
|
50
|
+
const attrs = Object.fromEntries(Object.keys(operation.attrs).map((name) => [name, Object.prototype.hasOwnProperty.call(node, name) ? node[name] : undefined]));
|
|
51
|
+
return [{ type: "setNodeAttrs", path: operation.path, attrs }];
|
|
52
|
+
}
|
|
53
|
+
if (operation.type === "replaceText") {
|
|
54
|
+
const node = getNodeAtTreePath(document, operation.path);
|
|
55
|
+
return [{
|
|
56
|
+
type: "replaceText",
|
|
57
|
+
path: operation.path,
|
|
58
|
+
start: operation.start,
|
|
59
|
+
end: operation.start + operation.text.length,
|
|
60
|
+
text: node.text.slice(operation.start, operation.end),
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
if (operation.type === "addMark" || operation.type === "removeMark") {
|
|
64
|
+
const path = parentPath(operation.path);
|
|
65
|
+
return [{ type: "replaceNode", path, node: getNodeAtTreePath(document, path) }];
|
|
66
|
+
}
|
|
67
|
+
return [{ type: "setSelection", selection }];
|
|
68
|
+
};
|
|
69
|
+
export const invertLegacyTransaction = (state, transaction) => {
|
|
70
|
+
let document = state.document;
|
|
71
|
+
let selection = state.selection;
|
|
72
|
+
const inverseGroups = [];
|
|
73
|
+
transaction.operations.forEach((operation) => {
|
|
74
|
+
inverseGroups.unshift(inverseForOperation(document, selection, operation));
|
|
75
|
+
const before = document;
|
|
76
|
+
const next = applyOperationToState(document, selection, operation);
|
|
77
|
+
document = next.document;
|
|
78
|
+
selection = operation.type === "setSelection"
|
|
79
|
+
? next.selection
|
|
80
|
+
: mapSelectionThroughOperation(selection, operation, before);
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
id: `undo:${transaction.id}`,
|
|
84
|
+
source: "history",
|
|
85
|
+
operations: inverseGroups.flat(),
|
|
86
|
+
selectionBefore: transaction.selectionAfter ?? selection,
|
|
87
|
+
selectionAfter: state.selection,
|
|
88
|
+
addToHistory: false,
|
|
89
|
+
timestamp: transaction.timestamp,
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
export const applyTransactionWithHistory = (state, history, transaction) => {
|
|
93
|
+
let inverse = invertLegacyTransaction(state, transaction);
|
|
94
|
+
const nextState = applyTransaction(state, transaction);
|
|
95
|
+
const restored = applyTransaction(nextState, inverse);
|
|
96
|
+
const expectedDocument = normalizeSmartDocument(state.document);
|
|
97
|
+
if (JSON.stringify(restored.document) !== JSON.stringify(expectedDocument) ||
|
|
98
|
+
JSON.stringify(restored.selection) !== JSON.stringify(state.selection)) {
|
|
99
|
+
inverse = {
|
|
100
|
+
...inverse,
|
|
101
|
+
operations: [{ type: "replaceNode", path: [], node: expectedDocument }],
|
|
102
|
+
selectionAfter: state.selection,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (!transaction.addToHistory) {
|
|
106
|
+
return { state: nextState, history, applied: true };
|
|
107
|
+
}
|
|
108
|
+
const previous = history.undo[history.undo.length - 1];
|
|
109
|
+
const canGroup = Boolean(transaction.historyGroup &&
|
|
110
|
+
previous?.forward.historyGroup === transaction.historyGroup);
|
|
111
|
+
const entry = canGroup
|
|
112
|
+
? {
|
|
113
|
+
forward: {
|
|
114
|
+
...previous.forward,
|
|
115
|
+
operations: [...previous.forward.operations, ...transaction.operations],
|
|
116
|
+
selectionAfter: transaction.selectionAfter ?? nextState.selection,
|
|
117
|
+
timestamp: transaction.timestamp,
|
|
118
|
+
},
|
|
119
|
+
inverse: {
|
|
120
|
+
...inverse,
|
|
121
|
+
operations: [...inverse.operations, ...previous.inverse.operations],
|
|
122
|
+
selectionBefore: transaction.selectionAfter ?? nextState.selection,
|
|
123
|
+
selectionAfter: previous.inverse.selectionAfter,
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
: { forward: transaction, inverse };
|
|
127
|
+
const undo = canGroup
|
|
128
|
+
? [...history.undo.slice(0, -1), entry]
|
|
129
|
+
: [...history.undo, entry];
|
|
130
|
+
if (undo.length > history.limit)
|
|
131
|
+
undo.splice(0, undo.length - history.limit);
|
|
132
|
+
return {
|
|
133
|
+
state: nextState,
|
|
134
|
+
history: { ...history, undo, redo: [] },
|
|
135
|
+
applied: true,
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
export const undoHistory = (state, history) => {
|
|
139
|
+
const entry = history.undo[history.undo.length - 1];
|
|
140
|
+
if (!entry)
|
|
141
|
+
return { state, history, applied: false };
|
|
142
|
+
return {
|
|
143
|
+
state: applyTransaction(state, entry.inverse),
|
|
144
|
+
history: {
|
|
145
|
+
...history,
|
|
146
|
+
undo: history.undo.slice(0, -1),
|
|
147
|
+
redo: [...history.redo, entry],
|
|
148
|
+
},
|
|
149
|
+
applied: true,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
export const redoHistory = (state, history) => {
|
|
153
|
+
const entry = history.redo[history.redo.length - 1];
|
|
154
|
+
if (!entry)
|
|
155
|
+
return { state, history, applied: false };
|
|
156
|
+
return {
|
|
157
|
+
state: applyTransaction(state, entry.forward),
|
|
158
|
+
history: {
|
|
159
|
+
...history,
|
|
160
|
+
undo: [...history.undo, entry],
|
|
161
|
+
redo: history.redo.slice(0, -1),
|
|
162
|
+
},
|
|
163
|
+
applied: true,
|
|
164
|
+
};
|
|
165
|
+
};
|
|
@@ -3,6 +3,12 @@ const BLOCK_TAGS = new Set([
|
|
|
3
3
|
"p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li",
|
|
4
4
|
"blockquote", "pre", "table", "div",
|
|
5
5
|
]);
|
|
6
|
+
const SEMANTIC_SRTE_ATTRIBUTES = new Set([
|
|
7
|
+
"data-srte-checklist",
|
|
8
|
+
"data-srte-checklist-strike",
|
|
9
|
+
"data-srte-checked",
|
|
10
|
+
"data-srte-list-preset",
|
|
11
|
+
]);
|
|
6
12
|
const attrsOf = (node) => node.attrs || [];
|
|
7
13
|
const hasAttr = (node, name, value) => attrsOf(node).some((attr) => attr.name === name && (value == null || attr.value === value));
|
|
8
14
|
const isTag = (node, tagName) => node.tagName === tagName;
|
|
@@ -38,7 +44,7 @@ const normalizeNode = (node) => {
|
|
|
38
44
|
return false;
|
|
39
45
|
if (attr.name === "data-row-index" || attr.name === "data-col-index")
|
|
40
46
|
return false;
|
|
41
|
-
return !attr.name.startsWith("data-srte-");
|
|
47
|
+
return !attr.name.startsWith("data-srte-") || SEMANTIC_SRTE_ATTRIBUTES.has(attr.name);
|
|
42
48
|
});
|
|
43
49
|
if (!node.childNodes)
|
|
44
50
|
return;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./command.js";
|
|
3
|
-
export * from "./marks.js";
|
|
4
|
-
export * from "./selection.js";
|
|
5
|
-
export * from "./table.js";
|
|
6
|
-
export * from "./transaction.js";
|
|
7
|
-
export * from "./tree.js";
|
|
8
|
-
export * from "./commands/list.js";
|
|
9
|
-
export * from "./commands/code.js";
|
|
10
|
-
export * from "./commands/marks.js";
|
|
11
|
-
export * from "./commands/alignment.js";
|
|
12
|
-
export * from "./html/compatibility.js";
|
|
13
|
-
export * from "./markdown/compatibility.js";
|
|
14
|
-
export * from "./security/urlPolicy.js";
|
|
1
|
+
export * from "./foundation/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./commands/marks.js";
|
|
11
|
-
export * from "./commands/alignment.js";
|
|
12
|
-
export * from "./html/compatibility.js";
|
|
13
|
-
export * from "./markdown/compatibility.js";
|
|
14
|
-
export * from "./security/urlPolicy.js";
|
|
1
|
+
// The package root is the canonical, framework-agnostic public API: the
|
|
2
|
+
// foundation document model and editing engine, and nothing else. Everything
|
|
3
|
+
// built on the pre-canonical model (legacyCommands/*, plugins/*, the
|
|
4
|
+
// html/markdown compatibility layers, and the discriminated-union model
|
|
5
|
+
// types in model.ts et al.) lives exclusively behind "smartrte-core/legacy"
|
|
6
|
+
// - see legacy/index.ts. Do not re-add those exports here; that flat
|
|
7
|
+
// re-export used to defeat the whole point of the /legacy subpath by making
|
|
8
|
+
// the same symbols reachable from the root import too.
|
|
9
|
+
export * from "./foundation/index.js";
|