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,35 @@
|
|
|
1
|
+
import type { SmartDocument, SmartOperation, SmartPos } from "../types.js";
|
|
2
|
+
import type { CommandContext } from "./types.js";
|
|
3
|
+
export interface ListSelectionTarget {
|
|
4
|
+
readonly ownerId: string;
|
|
5
|
+
readonly offset: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ListInputResult {
|
|
8
|
+
readonly operations: SmartOperation[];
|
|
9
|
+
readonly selectionTarget: ListSelectionTarget;
|
|
10
|
+
readonly intent: "split" | "indent" | "outdent" | "unwrap" | "merge-backward" | "merge-forward" | "line-break" | "check";
|
|
11
|
+
}
|
|
12
|
+
export interface ListEnterIds {
|
|
13
|
+
readonly itemId: string;
|
|
14
|
+
readonly blockId: string;
|
|
15
|
+
readonly emptyBlockId: string;
|
|
16
|
+
/**
|
|
17
|
+
* The list id used for the suffix when an empty middle item exits a list.
|
|
18
|
+
* It is caller-supplied so structural input remains deterministic.
|
|
19
|
+
*/
|
|
20
|
+
readonly splitListId: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const listItemAt: (document: SmartDocument, pos: SmartPos) => {
|
|
23
|
+
itemId: string;
|
|
24
|
+
listId: string;
|
|
25
|
+
depth: number;
|
|
26
|
+
} | null;
|
|
27
|
+
/** Backspace target: deepest last visible descendant of the preceding sibling. */
|
|
28
|
+
/** Delete target: first visible descendant when present, otherwise next sibling. */
|
|
29
|
+
export declare const resolveFollowingContentTarget: (document: SmartDocument, itemId: string, ctx: CommandContext) => {
|
|
30
|
+
itemId: string;
|
|
31
|
+
ownerId: string;
|
|
32
|
+
} | null;
|
|
33
|
+
export declare const enterInList: (document: SmartDocument, pos: SmartPos, ids: ListEnterIds, ctx: CommandContext) => ListInputResult | null;
|
|
34
|
+
export declare const backspaceAtListItemStart: (document: SmartDocument, pos: SmartPos, ctx: CommandContext) => ListInputResult | null;
|
|
35
|
+
export declare const deleteAtListItemEnd: (document: SmartDocument, pos: SmartPos, ctx: CommandContext) => ListInputResult | null;
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { cloneNode, isTextNode } from "../identity.js";
|
|
2
|
+
import { nodeAtPath } from "../positions.js";
|
|
3
|
+
import { outdentList, unwrapList } from "./commands.js";
|
|
4
|
+
import { resolvePrecedingContentTarget as resolveSharedPrecedingContentTarget } from "../structural/contentTarget.js";
|
|
5
|
+
const inlineSize = (node) => (node.children || []).reduce((size, child) => size + (isTextNode(child) ? child.text.length : 1), 0);
|
|
6
|
+
const textChildren = (node) => (node.children || []).filter(isTextNode).map((child) => child.text).join("");
|
|
7
|
+
const isInlineOwner = (node) => node.type === "paragraph" || node.type === "heading";
|
|
8
|
+
const isElementNode = (node) => !isTextNode(node);
|
|
9
|
+
const contextAt = (document, pos) => {
|
|
10
|
+
const owner = nodeAtPath(document, pos.path);
|
|
11
|
+
if (!owner || isTextNode(owner) || !isInlineOwner(owner))
|
|
12
|
+
return null;
|
|
13
|
+
for (let depth = pos.path.length - 1; depth >= 0; depth -= 1) {
|
|
14
|
+
const path = pos.path.slice(0, depth + 1);
|
|
15
|
+
const node = nodeAtPath(document, path);
|
|
16
|
+
if (!node || isTextNode(node) || node.type !== "list_item")
|
|
17
|
+
continue;
|
|
18
|
+
const listPath = path.slice(0, -1);
|
|
19
|
+
const list = nodeAtPath(document, listPath);
|
|
20
|
+
if (!list || isTextNode(list) || list.type !== "list")
|
|
21
|
+
return null;
|
|
22
|
+
const ownerIndex = (node.children || []).findIndex((child) => !isTextNode(child) && child.id === owner.id);
|
|
23
|
+
return {
|
|
24
|
+
item: node,
|
|
25
|
+
itemPath: path,
|
|
26
|
+
list,
|
|
27
|
+
listPath,
|
|
28
|
+
itemIndex: path[path.length - 1],
|
|
29
|
+
owner,
|
|
30
|
+
ownerPath: [...pos.path],
|
|
31
|
+
ownerIndex,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
};
|
|
36
|
+
export const listItemAt = (document, pos) => {
|
|
37
|
+
const context = contextAt(document, pos);
|
|
38
|
+
if (!context)
|
|
39
|
+
return null;
|
|
40
|
+
let depth = 0;
|
|
41
|
+
for (let index = 0; index < context.listPath.length; index += 1) {
|
|
42
|
+
const ancestor = nodeAtPath(document, context.listPath.slice(0, index + 1));
|
|
43
|
+
if (ancestor && !isTextNode(ancestor) && ancestor.type === "list")
|
|
44
|
+
depth += 1;
|
|
45
|
+
}
|
|
46
|
+
return { itemId: context.item.id, listId: context.list.id, depth: Math.max(0, depth - 1) };
|
|
47
|
+
};
|
|
48
|
+
const itemScope = (document, context) => ({
|
|
49
|
+
kind: "list-selection",
|
|
50
|
+
listId: context.list.id,
|
|
51
|
+
items: [{ itemId: context.item.id, depth: listItemAt(document, { path: context.ownerPath, offset: 0 })?.depth || 0, hasChildList: Boolean(nestedList(context.item)) }],
|
|
52
|
+
partialSubtree: false,
|
|
53
|
+
promotedFromPartial: false,
|
|
54
|
+
range: { from: { path: context.listPath, offset: context.itemIndex }, to: { path: context.listPath, offset: context.itemIndex + 1 } },
|
|
55
|
+
isolatingAncestorId: null,
|
|
56
|
+
clamped: false,
|
|
57
|
+
});
|
|
58
|
+
const nestedList = (item) => {
|
|
59
|
+
for (let index = (item.children?.length || 0) - 1; index >= 0; index -= 1) {
|
|
60
|
+
const child = item.children?.[index];
|
|
61
|
+
if (child && !isTextNode(child) && child.type === "list")
|
|
62
|
+
return child;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
const firstContentOwner = (item) => {
|
|
67
|
+
for (const child of item.children || [])
|
|
68
|
+
if (!isTextNode(child) && child.type !== "list" && isInlineOwner(child))
|
|
69
|
+
return child;
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
const deepestFirstItem = (item) => {
|
|
73
|
+
const nested = nestedList(item);
|
|
74
|
+
const first = nested?.children?.[0];
|
|
75
|
+
return first && !isTextNode(first) ? deepestFirstItem(first) : item;
|
|
76
|
+
};
|
|
77
|
+
/** Backspace target: deepest last visible descendant of the preceding sibling. */
|
|
78
|
+
/** Delete target: first visible descendant when present, otherwise next sibling. */
|
|
79
|
+
export const resolveFollowingContentTarget = (document, itemId, ctx) => {
|
|
80
|
+
const located = ctx.positions.positionOf(itemId);
|
|
81
|
+
if (!located || located.parent.type !== "list")
|
|
82
|
+
return null;
|
|
83
|
+
const current = located.parent.children?.[located.pos.offset];
|
|
84
|
+
if (!current || isTextNode(current))
|
|
85
|
+
return null;
|
|
86
|
+
const ownNested = nestedList(current);
|
|
87
|
+
const firstChild = ownNested?.children?.[0];
|
|
88
|
+
if (firstChild && !isTextNode(firstChild)) {
|
|
89
|
+
const target = deepestFirstItem(firstChild);
|
|
90
|
+
const owner = firstContentOwner(target);
|
|
91
|
+
return owner ? { itemId: target.id, ownerId: owner.id } : null;
|
|
92
|
+
}
|
|
93
|
+
const sibling = located.parent.children?.[located.pos.offset + 1];
|
|
94
|
+
if (!sibling || isTextNode(sibling))
|
|
95
|
+
return null;
|
|
96
|
+
const owner = firstContentOwner(sibling);
|
|
97
|
+
return owner ? { itemId: sibling.id, ownerId: owner.id } : null;
|
|
98
|
+
};
|
|
99
|
+
const splitInlineOwner = (owner, offset, newId) => {
|
|
100
|
+
const before = [];
|
|
101
|
+
const after = [];
|
|
102
|
+
let consumed = 0;
|
|
103
|
+
let split = false;
|
|
104
|
+
for (const child of owner.children || []) {
|
|
105
|
+
if (split) {
|
|
106
|
+
after.push(child);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const size = isTextNode(child) ? child.text.length : 1;
|
|
110
|
+
const local = offset - consumed;
|
|
111
|
+
if (local >= size) {
|
|
112
|
+
before.push(child);
|
|
113
|
+
consumed += size;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (isTextNode(child)) {
|
|
117
|
+
if (local > 0)
|
|
118
|
+
before.push({ ...child, text: child.text.slice(0, local) });
|
|
119
|
+
if (local < size)
|
|
120
|
+
after.push({ ...child, text: child.text.slice(local) });
|
|
121
|
+
}
|
|
122
|
+
else if (local === 0)
|
|
123
|
+
after.push(child);
|
|
124
|
+
else
|
|
125
|
+
before.push(child);
|
|
126
|
+
split = true;
|
|
127
|
+
consumed += size;
|
|
128
|
+
}
|
|
129
|
+
const left = { ...owner, children: before };
|
|
130
|
+
return [left, { ...owner, id: newId, children: after }];
|
|
131
|
+
};
|
|
132
|
+
const emptyParagraph = (id) => ({ type: "paragraph", id, children: [] });
|
|
133
|
+
export const enterInList = (document, pos, ids, ctx) => {
|
|
134
|
+
const context = contextAt(document, pos);
|
|
135
|
+
if (!context)
|
|
136
|
+
return null;
|
|
137
|
+
const scope = itemScope(document, context);
|
|
138
|
+
const depth = scope.items[0].depth;
|
|
139
|
+
const itemEmpty = (context.item.children || []).filter(isElementNode).filter((child) => child.type !== "list")
|
|
140
|
+
.every((child) => isInlineOwner(child) && inlineSize(child) === 0);
|
|
141
|
+
if (itemEmpty) {
|
|
142
|
+
if (depth > 0)
|
|
143
|
+
return {
|
|
144
|
+
operations: outdentList(document, scope, {}, ctx),
|
|
145
|
+
selectionTarget: { ownerId: context.owner.id, offset: 0 },
|
|
146
|
+
intent: "outdent",
|
|
147
|
+
};
|
|
148
|
+
const listChildren = context.list.children || [];
|
|
149
|
+
const splitsAfterItem = context.itemIndex > 0 && context.itemIndex < listChildren.length - 1;
|
|
150
|
+
return {
|
|
151
|
+
operations: unwrapList(document, scope, splitsAfterItem
|
|
152
|
+
? { splitListIds: [ids.splitListId] }
|
|
153
|
+
: {}, ctx),
|
|
154
|
+
selectionTarget: { ownerId: context.owner.id, offset: 0 },
|
|
155
|
+
intent: "unwrap",
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const ownerLength = inlineSize(context.owner);
|
|
159
|
+
const itemChildren = context.item.children || [];
|
|
160
|
+
const atItemStart = context.ownerIndex === 0 && pos.offset === 0;
|
|
161
|
+
const nonListChildren = itemChildren.filter((child) => isTextNode(child) || child.type !== "list");
|
|
162
|
+
const atItemEnd = context.ownerIndex === nonListChildren.length - 1 && pos.offset === ownerLength;
|
|
163
|
+
let first;
|
|
164
|
+
let second;
|
|
165
|
+
let target;
|
|
166
|
+
if (atItemStart) {
|
|
167
|
+
first = { type: "list_item", id: ids.itemId, children: [emptyParagraph(ids.emptyBlockId)] };
|
|
168
|
+
second = context.item;
|
|
169
|
+
target = { ownerId: context.owner.id, offset: 0 };
|
|
170
|
+
}
|
|
171
|
+
else if (atItemEnd) {
|
|
172
|
+
first = context.item;
|
|
173
|
+
second = { type: "list_item", id: ids.itemId, children: [emptyParagraph(ids.emptyBlockId)] };
|
|
174
|
+
target = { ownerId: ids.emptyBlockId, offset: 0 };
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const [leftOwner, rightOwner] = splitInlineOwner(context.owner, pos.offset, ids.blockId);
|
|
178
|
+
const beforeBlocks = itemChildren.slice(0, context.ownerIndex);
|
|
179
|
+
const afterBlocks = itemChildren.slice(context.ownerIndex + 1);
|
|
180
|
+
first = { ...context.item, children: [...beforeBlocks, leftOwner] };
|
|
181
|
+
// Nested lists and every block after the split stay with the second half.
|
|
182
|
+
second = { ...context.item, id: ids.itemId, children: [rightOwner, ...afterBlocks] };
|
|
183
|
+
target = { ownerId: rightOwner.id, offset: 0 };
|
|
184
|
+
}
|
|
185
|
+
const children = [...(context.list.children || [])];
|
|
186
|
+
children.splice(context.itemIndex, 1, first, second);
|
|
187
|
+
return {
|
|
188
|
+
operations: [{ type: "replaceNode", pos: { path: context.listPath.slice(0, -1), offset: context.listPath[context.listPath.length - 1] }, before: context.list, after: { ...context.list, children } }],
|
|
189
|
+
selectionTarget: target,
|
|
190
|
+
intent: "split",
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
const rootListForItem = (document, itemId, ctx) => {
|
|
194
|
+
let position = ctx.positions.positionOf(itemId);
|
|
195
|
+
if (!position)
|
|
196
|
+
return null;
|
|
197
|
+
let list = position.parent;
|
|
198
|
+
while (list.type === "list") {
|
|
199
|
+
const listPosition = ctx.positions.positionOf(list.id);
|
|
200
|
+
if (!listPosition || listPosition.parent.type !== "list_item")
|
|
201
|
+
return list;
|
|
202
|
+
const parentItemPosition = ctx.positions.positionOf(listPosition.parent.id);
|
|
203
|
+
if (!parentItemPosition)
|
|
204
|
+
return list;
|
|
205
|
+
list = parentItemPosition.parent;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
};
|
|
209
|
+
const transformElement = (node, transform) => {
|
|
210
|
+
const direct = transform(node);
|
|
211
|
+
if (!direct)
|
|
212
|
+
return null;
|
|
213
|
+
const children = direct.children ? [] : undefined;
|
|
214
|
+
direct.children?.forEach((child) => {
|
|
215
|
+
if (isTextNode(child))
|
|
216
|
+
children.push(child);
|
|
217
|
+
else {
|
|
218
|
+
const next = transformElement(child, transform);
|
|
219
|
+
if (next)
|
|
220
|
+
children.push(next);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return children ? { ...direct, children } : direct;
|
|
224
|
+
};
|
|
225
|
+
const mergeOwners = (target, source, backward) => {
|
|
226
|
+
if (!isInlineOwner(target) || !isInlineOwner(source) || target.type !== source.type)
|
|
227
|
+
return target;
|
|
228
|
+
const children = [];
|
|
229
|
+
[...(target.children || []), ...(source.children || []).map(cloneNode)].forEach((child) => {
|
|
230
|
+
const previous = children[children.length - 1];
|
|
231
|
+
if (previous && isTextNode(previous) && isTextNode(child)
|
|
232
|
+
&& JSON.stringify(previous.marks || []) === JSON.stringify(child.marks || [])) {
|
|
233
|
+
children[children.length - 1] = { ...previous, text: previous.text + child.text };
|
|
234
|
+
}
|
|
235
|
+
else
|
|
236
|
+
children.push(child);
|
|
237
|
+
});
|
|
238
|
+
return { ...target, children };
|
|
239
|
+
};
|
|
240
|
+
const mergeItems = (document, sourceItemId, targetItemId, sourceOwnerId, targetOwnerId, backward, ctx) => {
|
|
241
|
+
const root = rootListForItem(document, sourceItemId, ctx);
|
|
242
|
+
if (!root)
|
|
243
|
+
throw new Error("List merge root was not found.");
|
|
244
|
+
const rootLocated = ctx.positions.positionOf(root.id);
|
|
245
|
+
const sourceLocated = ctx.positions.positionOf(sourceItemId);
|
|
246
|
+
const targetLocated = ctx.positions.positionOf(targetItemId);
|
|
247
|
+
if (!rootLocated || !sourceLocated || !targetLocated)
|
|
248
|
+
throw new Error("List merge positions are stale.");
|
|
249
|
+
const sourceItem = sourceLocated.parent.children?.[sourceLocated.pos.offset];
|
|
250
|
+
const targetItem = targetLocated.parent.children?.[targetLocated.pos.offset];
|
|
251
|
+
if (!sourceItem || isTextNode(sourceItem) || !targetItem || isTextNode(targetItem))
|
|
252
|
+
throw new Error("List merge items are invalid.");
|
|
253
|
+
const sourceOwner = (sourceItem.children || []).find((child) => !isTextNode(child) && child.id === sourceOwnerId);
|
|
254
|
+
const targetOwner = (targetItem.children || []).find((child) => !isTextNode(child) && child.id === targetOwnerId);
|
|
255
|
+
if (!sourceOwner || isTextNode(sourceOwner) || !targetOwner || isTextNode(targetOwner))
|
|
256
|
+
throw new Error("List merge owners are invalid.");
|
|
257
|
+
const sourceOtherChildren = (sourceItem.children || []).filter((child) => isTextNode(child) || child.id !== sourceOwnerId);
|
|
258
|
+
const targetOffset = inlineSize(targetOwner);
|
|
259
|
+
const transformed = transformElement(root, (candidate) => {
|
|
260
|
+
if (candidate.id === sourceItemId)
|
|
261
|
+
return null;
|
|
262
|
+
if (candidate.id !== targetItemId)
|
|
263
|
+
return candidate;
|
|
264
|
+
const mergedOwner = mergeOwners(targetOwner, sourceOwner, backward);
|
|
265
|
+
const targetChildren = (candidate.children || []).map((child) => !isTextNode(child) && child.id === targetOwnerId ? mergedOwner : child);
|
|
266
|
+
return { ...candidate, children: [...targetChildren, ...sourceOtherChildren] };
|
|
267
|
+
});
|
|
268
|
+
if (!transformed)
|
|
269
|
+
throw new Error("List merge removed its root.");
|
|
270
|
+
const cleaned = transformElement(transformed, (candidate) => candidate.type === "list" && !(candidate.children?.length) ? null : candidate);
|
|
271
|
+
if (!cleaned)
|
|
272
|
+
throw new Error("List merge produced an empty root list.");
|
|
273
|
+
return {
|
|
274
|
+
// retiredInto marks this as a semantic merge, not a plain content
|
|
275
|
+
// change - an annotation anchored to the source item or its owner can
|
|
276
|
+
// snap to the corresponding target instead of orphaning. See
|
|
277
|
+
// annotations/range.ts.
|
|
278
|
+
operations: [{
|
|
279
|
+
type: "replaceNode", pos: { path: [...rootLocated.pos.path], offset: rootLocated.pos.offset }, before: root, after: cleaned,
|
|
280
|
+
retiredInto: [
|
|
281
|
+
{ retiredId: sourceItemId, survivorId: targetItemId },
|
|
282
|
+
{ retiredId: sourceOwnerId, survivorId: targetOwnerId },
|
|
283
|
+
],
|
|
284
|
+
}],
|
|
285
|
+
selectionTarget: { ownerId: targetOwnerId, offset: targetOffset },
|
|
286
|
+
intent: backward ? "merge-backward" : "merge-forward",
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
export const backspaceAtListItemStart = (document, pos, ctx) => {
|
|
290
|
+
const context = contextAt(document, pos);
|
|
291
|
+
if (!context || pos.offset !== 0 || context.ownerIndex !== 0)
|
|
292
|
+
return null;
|
|
293
|
+
const metadata = listItemAt(document, pos);
|
|
294
|
+
const scope = itemScope(document, context);
|
|
295
|
+
if (metadata.depth > 0)
|
|
296
|
+
return {
|
|
297
|
+
operations: outdentList(document, scope, {}, ctx),
|
|
298
|
+
selectionTarget: { ownerId: context.owner.id, offset: 0 },
|
|
299
|
+
intent: "outdent",
|
|
300
|
+
};
|
|
301
|
+
if (context.itemIndex === 0)
|
|
302
|
+
return {
|
|
303
|
+
operations: unwrapList(document, scope, {}, ctx),
|
|
304
|
+
selectionTarget: { ownerId: context.owner.id, offset: 0 },
|
|
305
|
+
intent: "unwrap",
|
|
306
|
+
};
|
|
307
|
+
const resolved = resolveSharedPrecedingContentTarget(document, context.item.id, ctx);
|
|
308
|
+
const targetItem = resolved && [...resolved.lineage].reverse().find((entry) => entry.type === "list_item");
|
|
309
|
+
const target = resolved && targetItem ? { itemId: targetItem.nodeId, ownerId: resolved.ownerId } : null;
|
|
310
|
+
return target ? mergeItems(document, context.item.id, target.itemId, context.owner.id, target.ownerId, true, ctx) : null;
|
|
311
|
+
};
|
|
312
|
+
export const deleteAtListItemEnd = (document, pos, ctx) => {
|
|
313
|
+
const context = contextAt(document, pos);
|
|
314
|
+
if (!context || pos.offset !== inlineSize(context.owner))
|
|
315
|
+
return null;
|
|
316
|
+
const target = resolveFollowingContentTarget(document, context.item.id, ctx);
|
|
317
|
+
if (!target)
|
|
318
|
+
return null;
|
|
319
|
+
return mergeItems(document, target.itemId, context.item.id, target.ownerId, context.owner.id, false, ctx);
|
|
320
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type KeyboardShortcutContribution, type PluginCommand } from "../plugin/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps the ten existing, already-tested list commands (list/commands.ts)
|
|
4
|
+
* with the description Phase 10's doc generation needs. list has no
|
|
5
|
+
* per-item declaration layer the way block/marks/atom do (no single
|
|
6
|
+
* "declaration" maps 1:1 to a toolbar button set) - list.setPreset's
|
|
7
|
+
* `preset` param covers what a bulleted/numbered/checklist toolbar would
|
|
8
|
+
* offer, so no separate toolbar-contribution list is built here.
|
|
9
|
+
*/
|
|
10
|
+
export declare const listPluginCommands: Record<string, PluginCommand>;
|
|
11
|
+
/**
|
|
12
|
+
* Tab/Shift+Tab inside a list-selection scope, priority 10 - lower than
|
|
13
|
+
* block's code-indent (20), so a code-block caret's Tab wins first (see
|
|
14
|
+
* block/plugin.ts's blockPluginShortcuts); indentList/outdentList
|
|
15
|
+
* themselves need a fresh id per invocation (nestedListIds/splitListIds),
|
|
16
|
+
* which a static KeyboardShortcutContribution.params can't hold, so the
|
|
17
|
+
* dispatch caller (surface/input.ts) generates it at invocation time, the
|
|
18
|
+
* same way every other id-generating command call in this codebase already
|
|
19
|
+
* does (e.g. CanonicalAuthorityEditor.tsx's table commands).
|
|
20
|
+
*/
|
|
21
|
+
export declare const listPluginShortcuts: readonly KeyboardShortcutContribution[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { definePluginCommand } from "../plugin/types.js";
|
|
2
|
+
import { continueListNumbering, createList, indentList, moveListItems, outdentList, restartListNumbering, setListChecked, setListPreset, setListStyle, unwrapList, } from "./commands.js";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps the ten existing, already-tested list commands (list/commands.ts)
|
|
5
|
+
* with the description Phase 10's doc generation needs. list has no
|
|
6
|
+
* per-item declaration layer the way block/marks/atom do (no single
|
|
7
|
+
* "declaration" maps 1:1 to a toolbar button set) - list.setPreset's
|
|
8
|
+
* `preset` param covers what a bulleted/numbered/checklist toolbar would
|
|
9
|
+
* offer, so no separate toolbar-contribution list is built here.
|
|
10
|
+
*/
|
|
11
|
+
export const listPluginCommands = {
|
|
12
|
+
"list.create": definePluginCommand({
|
|
13
|
+
id: "list.create", run: createList,
|
|
14
|
+
description: "Wraps the selected blocks in a new list (or converts an existing list's items) using the given preset.",
|
|
15
|
+
options: { preset: { description: "The list preset id, e.g. \"bulleted\" or \"numbered\"." } },
|
|
16
|
+
}),
|
|
17
|
+
"list.unwrap": definePluginCommand({
|
|
18
|
+
id: "list.unwrap", run: unwrapList,
|
|
19
|
+
description: "Removes the selected list items from their list, promoting their content back to plain blocks.",
|
|
20
|
+
}),
|
|
21
|
+
"list.indent": definePluginCommand({
|
|
22
|
+
id: "list.indent", run: indentList,
|
|
23
|
+
description: "Nests the selected list items one level deeper under the preceding sibling item.",
|
|
24
|
+
}),
|
|
25
|
+
"list.outdent": definePluginCommand({
|
|
26
|
+
id: "list.outdent", run: outdentList,
|
|
27
|
+
description: "Moves the selected list items one level shallower, or unwraps them entirely if already at the top level.",
|
|
28
|
+
}),
|
|
29
|
+
"list.setPreset": definePluginCommand({
|
|
30
|
+
id: "list.setPreset", run: setListPreset,
|
|
31
|
+
description: "Changes the selected list's preset (e.g. bulleted, numbered, checklist), restyling its markers accordingly.",
|
|
32
|
+
options: { preset: { description: "The new preset id." } },
|
|
33
|
+
}),
|
|
34
|
+
"list.setStyle": definePluginCommand({
|
|
35
|
+
id: "list.setStyle", run: setListStyle,
|
|
36
|
+
description: "Changes the selected list's marker style (e.g. disc, decimal, lower-alpha) independent of its preset.",
|
|
37
|
+
}),
|
|
38
|
+
"list.setChecked": definePluginCommand({
|
|
39
|
+
id: "list.setChecked", run: setListChecked,
|
|
40
|
+
description: "Toggles a checklist item's checked state.",
|
|
41
|
+
options: { checked: { description: "The new checked state.", required: true } },
|
|
42
|
+
}),
|
|
43
|
+
"list.move": definePluginCommand({
|
|
44
|
+
id: "list.move", run: moveListItems,
|
|
45
|
+
description: "Moves the selected list items up or down among their siblings.",
|
|
46
|
+
options: { direction: { description: "\"up\" or \"down\".", required: true } },
|
|
47
|
+
}),
|
|
48
|
+
"list.restartNumbering": definePluginCommand({
|
|
49
|
+
id: "list.restartNumbering", run: restartListNumbering,
|
|
50
|
+
description: "Restarts an ordered list's numbering at the given value from this item onward.",
|
|
51
|
+
options: { start: { description: "The number to restart counting from.", required: true } },
|
|
52
|
+
}),
|
|
53
|
+
"list.continueNumbering": definePluginCommand({
|
|
54
|
+
id: "list.continueNumbering", run: continueListNumbering,
|
|
55
|
+
description: "Removes an explicit numbering restart, letting this item continue the count from the preceding item.",
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Tab/Shift+Tab inside a list-selection scope, priority 10 - lower than
|
|
60
|
+
* block's code-indent (20), so a code-block caret's Tab wins first (see
|
|
61
|
+
* block/plugin.ts's blockPluginShortcuts); indentList/outdentList
|
|
62
|
+
* themselves need a fresh id per invocation (nestedListIds/splitListIds),
|
|
63
|
+
* which a static KeyboardShortcutContribution.params can't hold, so the
|
|
64
|
+
* dispatch caller (surface/input.ts) generates it at invocation time, the
|
|
65
|
+
* same way every other id-generating command call in this codebase already
|
|
66
|
+
* does (e.g. CanonicalAuthorityEditor.tsx's table commands).
|
|
67
|
+
*/
|
|
68
|
+
export const listPluginShortcuts = [
|
|
69
|
+
{ id: "list.indent", commandId: "list.indent", key: "Tab", scopeKinds: ["list-selection"], priority: 10 },
|
|
70
|
+
{ id: "list.outdent", commandId: "list.outdent", key: "Tab", shift: true, scopeKinds: ["list-selection"], priority: 10 },
|
|
71
|
+
];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type FoundationSmartListKind = "bullet" | "ordered";
|
|
2
|
+
export type FoundationSmartListStyle = "disc" | "circle" | "square" | "decimal" | "decimal-leading-zero" | "lower-alpha" | "upper-alpha" | "lower-roman" | "upper-roman";
|
|
3
|
+
export type FoundationSmartListPreset = "bullet-disc" | "bullet-diamond" | "bullet-square" | "bullet-arrow" | "bullet-star" | "bullet-arrow-circle" | "ordered-decimal" | "ordered-decimal-paren" | "ordered-outline" | "ordered-upper-alpha" | "ordered-upper-roman" | "ordered-leading-zero";
|
|
4
|
+
export interface FoundationSmartListPresetDefinition {
|
|
5
|
+
id: FoundationSmartListPreset;
|
|
6
|
+
kind: FoundationSmartListKind;
|
|
7
|
+
label: string;
|
|
8
|
+
styles: readonly FoundationSmartListStyle[];
|
|
9
|
+
markers?: readonly string[];
|
|
10
|
+
outline?: boolean;
|
|
11
|
+
suffix?: "." | ")";
|
|
12
|
+
}
|
|
13
|
+
/** The only list preset catalog accepted by canonical commands and schema. */
|
|
14
|
+
export declare const FOUNDATION_SMART_LIST_PRESETS: readonly FoundationSmartListPresetDefinition[];
|
|
15
|
+
export declare const isFoundationSmartListPreset: (value: unknown) => value is FoundationSmartListPreset;
|
|
16
|
+
export declare const foundationListStyleForPresetDepth: (id: FoundationSmartListPreset, depth: number) => FoundationSmartListStyle;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** The only list preset catalog accepted by canonical commands and schema. */
|
|
2
|
+
export const FOUNDATION_SMART_LIST_PRESETS = [
|
|
3
|
+
{ id: "ordered-decimal", kind: "ordered", label: "1. / a. / i.", styles: ["decimal", "lower-alpha", "lower-roman"], suffix: "." },
|
|
4
|
+
{ id: "ordered-decimal-paren", kind: "ordered", label: "1) / a) / i)", styles: ["decimal", "lower-alpha", "lower-roman"], suffix: ")" },
|
|
5
|
+
{ id: "ordered-outline", kind: "ordered", label: "1. / 1.1. / 1.1.1.", styles: ["decimal", "decimal", "decimal"], suffix: ".", outline: true },
|
|
6
|
+
{ id: "ordered-upper-alpha", kind: "ordered", label: "A. / a. / i.", styles: ["upper-alpha", "lower-alpha", "lower-roman"], suffix: "." },
|
|
7
|
+
{ id: "ordered-upper-roman", kind: "ordered", label: "I. / A. / 1.", styles: ["upper-roman", "upper-alpha", "decimal"], suffix: "." },
|
|
8
|
+
{ id: "ordered-leading-zero", kind: "ordered", label: "01. / a. / i.", styles: ["decimal-leading-zero", "lower-alpha", "lower-roman"], suffix: "." },
|
|
9
|
+
{ id: "bullet-disc", kind: "bullet", label: "● / ○ / ■", styles: ["disc", "circle", "square"] },
|
|
10
|
+
{ id: "bullet-diamond", kind: "bullet", label: "❖ / ➢ / ■", styles: ["disc", "circle", "square"], markers: ["❖", "➢", "■"] },
|
|
11
|
+
{ id: "bullet-square", kind: "bullet", label: "□ / ▣ / ▪", styles: ["square", "square", "square"], markers: ["□", "▣", "▪"] },
|
|
12
|
+
{ id: "bullet-arrow", kind: "bullet", label: "➜ / ◆ / ●", styles: ["disc", "circle", "square"], markers: ["➜", "◆", "●"] },
|
|
13
|
+
{ id: "bullet-star", kind: "bullet", label: "★ / ○ / ■", styles: ["disc", "circle", "square"], markers: ["★", "○", "■"] },
|
|
14
|
+
{ id: "bullet-arrow-circle", kind: "bullet", label: "➢ / ○ / ■", styles: ["disc", "circle", "square"], markers: ["➢", "○", "■"] },
|
|
15
|
+
];
|
|
16
|
+
const presetMap = new Map(FOUNDATION_SMART_LIST_PRESETS.map((preset) => [preset.id, preset]));
|
|
17
|
+
export const isFoundationSmartListPreset = (value) => typeof value === "string" && presetMap.has(value);
|
|
18
|
+
export const foundationListStyleForPresetDepth = (id, depth) => {
|
|
19
|
+
const styles = presetMap.get(id).styles;
|
|
20
|
+
return styles[Math.min(Math.max(0, depth), styles.length - 1)];
|
|
21
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const listNodeSpecs: readonly [{
|
|
2
|
+
readonly type: "list";
|
|
3
|
+
readonly group: "block";
|
|
4
|
+
readonly semanticRole: "list";
|
|
5
|
+
readonly content: "list_item+";
|
|
6
|
+
readonly attributes: {
|
|
7
|
+
readonly preset: {
|
|
8
|
+
validate: (value: unknown) => value is import("./presets.js").FoundationSmartListPreset;
|
|
9
|
+
};
|
|
10
|
+
readonly style: {
|
|
11
|
+
validate: (value: unknown) => value is string;
|
|
12
|
+
};
|
|
13
|
+
readonly start: {
|
|
14
|
+
validate: (value: unknown) => boolean;
|
|
15
|
+
};
|
|
16
|
+
readonly checkable: {
|
|
17
|
+
readonly validate: (value: unknown) => value is boolean;
|
|
18
|
+
readonly default: false;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
}, {
|
|
22
|
+
readonly type: "list_item";
|
|
23
|
+
readonly group: "block";
|
|
24
|
+
readonly semanticRole: "list-item";
|
|
25
|
+
readonly content: "block+";
|
|
26
|
+
readonly attributes: {
|
|
27
|
+
readonly checked: {
|
|
28
|
+
readonly validate: (value: unknown) => value is boolean;
|
|
29
|
+
readonly default: false;
|
|
30
|
+
};
|
|
31
|
+
readonly numberOverride: {
|
|
32
|
+
validate: (value: unknown) => boolean;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isFoundationSmartListPreset } from "./presets.js";
|
|
2
|
+
const optionalString = { validate: (value) => typeof value === "string" };
|
|
3
|
+
const optionalPreset = { validate: isFoundationSmartListPreset };
|
|
4
|
+
const optionalBoolean = { validate: (value) => typeof value === "boolean" };
|
|
5
|
+
const positiveInteger = { validate: (value) => Number.isInteger(value) && Number(value) >= 1 };
|
|
6
|
+
export const listNodeSpecs = [
|
|
7
|
+
{
|
|
8
|
+
type: "list",
|
|
9
|
+
group: "block",
|
|
10
|
+
semanticRole: "list",
|
|
11
|
+
content: "list_item+",
|
|
12
|
+
attributes: {
|
|
13
|
+
preset: optionalPreset,
|
|
14
|
+
style: optionalString,
|
|
15
|
+
start: positiveInteger,
|
|
16
|
+
checkable: { default: false, ...optionalBoolean },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: "list_item",
|
|
21
|
+
group: "block",
|
|
22
|
+
semanticRole: "list-item",
|
|
23
|
+
content: "block+",
|
|
24
|
+
attributes: {
|
|
25
|
+
checked: { default: false, ...optionalBoolean },
|
|
26
|
+
numberOverride: positiveInteger,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SmartDocument, SmartSchema, SmartSelection } from "../types.js";
|
|
2
|
+
export type ShadowDivergenceClassification = "expected-normalization" | "equivalent-serialization" | "selection-only" | "visual-only" | "semantic" | "data-loss" | "unknown";
|
|
3
|
+
export interface ShadowComparisonResult {
|
|
4
|
+
readonly equivalent: boolean;
|
|
5
|
+
readonly documentEquivalent: boolean;
|
|
6
|
+
readonly selectionEquivalent: boolean;
|
|
7
|
+
readonly legacyStructureHash: string;
|
|
8
|
+
readonly canonicalStructureHash: string;
|
|
9
|
+
readonly legacySelectionHash: string;
|
|
10
|
+
readonly canonicalSelectionHash: string;
|
|
11
|
+
readonly classification?: ShadowDivergenceClassification;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Comparator equivalence is normalized document structure with IDs stripped,
|
|
15
|
+
* plus semantic selection position. Operation streams are intentionally not
|
|
16
|
+
* compared: canonical editing is allowed to express the same user intent with
|
|
17
|
+
* a different operation sequence, and the legacy engine has no stable IDs.
|
|
18
|
+
*/
|
|
19
|
+
export declare const normalizedStructureWithoutIds: (document: SmartDocument, schema: SmartSchema) => unknown;
|
|
20
|
+
export declare const semanticSelectionPosition: (document: SmartDocument, selection: SmartSelection) => {
|
|
21
|
+
type: "text" | "node" | "cell" | "none";
|
|
22
|
+
anchor: {
|
|
23
|
+
path: number[];
|
|
24
|
+
offset: number;
|
|
25
|
+
ownerType: string;
|
|
26
|
+
kind: "inline" | "structural";
|
|
27
|
+
};
|
|
28
|
+
head: {
|
|
29
|
+
path: number[];
|
|
30
|
+
offset: number;
|
|
31
|
+
ownerType: string;
|
|
32
|
+
kind: "inline" | "structural";
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export declare const compareShadowDocuments: (args: {
|
|
36
|
+
legacyDocument: SmartDocument;
|
|
37
|
+
legacySelection: SmartSelection;
|
|
38
|
+
canonicalDocument: SmartDocument;
|
|
39
|
+
canonicalSelection: SmartSelection;
|
|
40
|
+
schema: SmartSchema;
|
|
41
|
+
classification?: ShadowDivergenceClassification;
|
|
42
|
+
}) => ShadowComparisonResult;
|
|
43
|
+
/** Privacy-safe logger payload: hashes and classification, never document text. */
|
|
44
|
+
export declare const shadowLogRecord: (scenarioId: string, result: ShadowComparisonResult) => {
|
|
45
|
+
classification?: ShadowDivergenceClassification | undefined;
|
|
46
|
+
scenarioId: string;
|
|
47
|
+
equivalent: boolean;
|
|
48
|
+
documentEquivalent: boolean;
|
|
49
|
+
selectionEquivalent: boolean;
|
|
50
|
+
legacyStructureHash: string;
|
|
51
|
+
canonicalStructureHash: string;
|
|
52
|
+
legacySelectionHash: string;
|
|
53
|
+
canonicalSelectionHash: string;
|
|
54
|
+
};
|