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,34 @@
|
|
|
1
|
+
const matchesEvent = (event, shortcut) => event.key.toLowerCase() === shortcut.key.toLowerCase() &&
|
|
2
|
+
(shortcut.primary ? Boolean(event.metaKey || event.ctrlKey) : !event.metaKey && !event.ctrlKey) &&
|
|
3
|
+
Boolean(event.altKey) === Boolean(shortcut.alt) &&
|
|
4
|
+
Boolean(event.shiftKey) === Boolean(shortcut.shift);
|
|
5
|
+
/**
|
|
6
|
+
* Tries every key-matching shortcut contribution in priority order (ties
|
|
7
|
+
* broken by registration order, which is what PluginRegistry.keyboardShortcuts
|
|
8
|
+
* already provides), invoking `tryShortcut` for each and returning the
|
|
9
|
+
* first that actually produces operations.
|
|
10
|
+
*
|
|
11
|
+
* This is a fallback chain, not a static "one scope-kind owns this key"
|
|
12
|
+
* lookup, because ScopeKind alone cannot distinguish every case a real
|
|
13
|
+
* shortcut cares about (e.g. "inside a code block" and "inside a plain
|
|
14
|
+
* paragraph" are both "block-range" - see scope/resolveScope.ts). It
|
|
15
|
+
* reproduces surface/input.ts's actual current Tab precedence (code-block
|
|
16
|
+
* wins if applicable, then list unless inTable, then table/native
|
|
17
|
+
* fallthrough if nothing applies) by asking each candidate, in order,
|
|
18
|
+
* "does this genuinely apply here" via `tryShortcut` - which resolves the
|
|
19
|
+
* scope the contribution declares and runs its command - rather than by
|
|
20
|
+
* pre-filtering on a single precomputed scope-kind label. `scopeKinds` on
|
|
21
|
+
* a contribution remains meaningful as declared metadata for callers
|
|
22
|
+
* (which `resolveScope({want})` to attempt) and for doc generation, it's
|
|
23
|
+
* just no longer what this function filters by directly.
|
|
24
|
+
*/
|
|
25
|
+
export const resolveShortcut = (shortcuts, event, tryShortcut) => {
|
|
26
|
+
for (const shortcut of shortcuts) {
|
|
27
|
+
if (!matchesEvent(event, shortcut))
|
|
28
|
+
continue;
|
|
29
|
+
const operations = tryShortcut(shortcut);
|
|
30
|
+
if (operations?.length)
|
|
31
|
+
return { shortcut, operations };
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type SchemaContribution } from "../schemaBuilder.js";
|
|
2
|
+
import type { NormalizerRegistration, SmartSchema } from "../types.js";
|
|
3
|
+
import type { SourceNormalizer } from "../clipboard/types.js";
|
|
4
|
+
import type { ContextMenuContribution, FoundationPlugin, KeyboardShortcutContribution, PluginCommand, RendererContribution, ToolbarContribution } from "./types.js";
|
|
5
|
+
export interface PluginRegistry {
|
|
6
|
+
readonly plugins: readonly FoundationPlugin[];
|
|
7
|
+
readonly pluginIds: ReadonlySet<string>;
|
|
8
|
+
readonly schema: SmartSchema;
|
|
9
|
+
readonly commands: ReadonlyMap<string, PluginCommand>;
|
|
10
|
+
readonly normalizers: readonly NormalizerRegistration[];
|
|
11
|
+
readonly clipboardNormalizers: readonly SourceNormalizer[];
|
|
12
|
+
readonly keyboardShortcuts: readonly KeyboardShortcutContribution[];
|
|
13
|
+
readonly toolbar: readonly ToolbarContribution[];
|
|
14
|
+
readonly contextMenu: readonly ContextMenuContribution[];
|
|
15
|
+
readonly renderer: readonly RendererContribution[];
|
|
16
|
+
hasPlugin(id: string): boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Builds one authoritative registry from a plugin list: dependency-ordered,
|
|
20
|
+
* schema merged via the existing createSchema (same duplicate-type/duplicate-
|
|
21
|
+
* mark checks it has always enforced), commands/contributions merged with
|
|
22
|
+
* hard duplicate-id errors - mirroring the validation shape
|
|
23
|
+
* packages/react/src/pluginRuntime.ts already proved out for the now-retired
|
|
24
|
+
* legacy system, rebuilt here for the canonical architecture.
|
|
25
|
+
*
|
|
26
|
+
* `baseSchema` covers node/mark types no feature family owns and that must
|
|
27
|
+
* exist regardless of which plugins are registered - `doc` (the document
|
|
28
|
+
* root), `text`, `hard_break`, and `unknown` (the disable-safety passthrough
|
|
29
|
+
* node type itself, which would be a chicken-and-egg problem if any plugin
|
|
30
|
+
* had to own it).
|
|
31
|
+
*/
|
|
32
|
+
export declare const createPluginRegistry: (plugins: readonly FoundationPlugin[], options: {
|
|
33
|
+
baseSchema: SchemaContribution;
|
|
34
|
+
topNode?: string;
|
|
35
|
+
schemaVersion: number;
|
|
36
|
+
}) => PluginRegistry;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createSchema } from "../schemaBuilder.js";
|
|
2
|
+
/** Dependency-ordered so a plugin's `requires` are always registered before it. */
|
|
3
|
+
const orderPlugins = (plugins) => {
|
|
4
|
+
const byId = new Map(plugins.map((plugin) => [plugin.id, plugin]));
|
|
5
|
+
if (byId.size !== plugins.length) {
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const duplicate = plugins.find((plugin) => (seen.has(plugin.id) ? true : (seen.add(plugin.id), false)));
|
|
8
|
+
throw new Error(`Duplicate plugin id "${duplicate?.id}".`);
|
|
9
|
+
}
|
|
10
|
+
const ordered = [];
|
|
11
|
+
const visited = new Set();
|
|
12
|
+
const visiting = new Set();
|
|
13
|
+
const visit = (plugin) => {
|
|
14
|
+
if (visited.has(plugin.id))
|
|
15
|
+
return;
|
|
16
|
+
if (visiting.has(plugin.id))
|
|
17
|
+
throw new Error(`Circular plugin dependency involving "${plugin.id}".`);
|
|
18
|
+
visiting.add(plugin.id);
|
|
19
|
+
(plugin.requires || []).forEach((requiredId) => {
|
|
20
|
+
const required = byId.get(requiredId);
|
|
21
|
+
if (!required)
|
|
22
|
+
throw new Error(`Plugin "${plugin.id}" requires missing plugin "${requiredId}".`);
|
|
23
|
+
visit(required);
|
|
24
|
+
});
|
|
25
|
+
(plugin.optional || []).forEach((optionalId) => {
|
|
26
|
+
const optional = byId.get(optionalId);
|
|
27
|
+
if (optional)
|
|
28
|
+
visit(optional);
|
|
29
|
+
});
|
|
30
|
+
visiting.delete(plugin.id);
|
|
31
|
+
visited.add(plugin.id);
|
|
32
|
+
ordered.push(plugin);
|
|
33
|
+
};
|
|
34
|
+
plugins.forEach(visit);
|
|
35
|
+
return ordered;
|
|
36
|
+
};
|
|
37
|
+
const byPriorityThenOrder = (items) => [...items].sort((left, right) => (right.item.priority ?? 0) - (left.item.priority ?? 0) || left.index - right.index)
|
|
38
|
+
.map(({ item }) => item);
|
|
39
|
+
/**
|
|
40
|
+
* Builds one authoritative registry from a plugin list: dependency-ordered,
|
|
41
|
+
* schema merged via the existing createSchema (same duplicate-type/duplicate-
|
|
42
|
+
* mark checks it has always enforced), commands/contributions merged with
|
|
43
|
+
* hard duplicate-id errors - mirroring the validation shape
|
|
44
|
+
* packages/react/src/pluginRuntime.ts already proved out for the now-retired
|
|
45
|
+
* legacy system, rebuilt here for the canonical architecture.
|
|
46
|
+
*
|
|
47
|
+
* `baseSchema` covers node/mark types no feature family owns and that must
|
|
48
|
+
* exist regardless of which plugins are registered - `doc` (the document
|
|
49
|
+
* root), `text`, `hard_break`, and `unknown` (the disable-safety passthrough
|
|
50
|
+
* node type itself, which would be a chicken-and-egg problem if any plugin
|
|
51
|
+
* had to own it).
|
|
52
|
+
*/
|
|
53
|
+
export const createPluginRegistry = (plugins, options) => {
|
|
54
|
+
const ordered = orderPlugins(plugins);
|
|
55
|
+
const pluginIds = new Set(ordered.map((plugin) => plugin.id));
|
|
56
|
+
const schema = createSchema({
|
|
57
|
+
version: options.schemaVersion,
|
|
58
|
+
topNode: options.topNode,
|
|
59
|
+
nodes: [...(options.baseSchema.nodes || [])],
|
|
60
|
+
marks: [...(options.baseSchema.marks || [])],
|
|
61
|
+
extensions: ordered.map((plugin) => ({ nodes: plugin.schema?.nodes, marks: plugin.schema?.marks })),
|
|
62
|
+
});
|
|
63
|
+
const commands = new Map();
|
|
64
|
+
ordered.forEach((plugin) => {
|
|
65
|
+
Object.values(plugin.commands).forEach((command) => {
|
|
66
|
+
if (commands.has(command.id))
|
|
67
|
+
throw new Error(`Duplicate command id "${command.id}" (from plugin "${plugin.id}").`);
|
|
68
|
+
if (!command.description.trim())
|
|
69
|
+
throw new Error(`Command "${command.id}" (plugin "${plugin.id}") requires a non-empty description.`);
|
|
70
|
+
commands.set(command.id, command);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
const normalizers = ordered.flatMap((plugin) => plugin.normalizers || []);
|
|
74
|
+
const clipboardNormalizers = ordered
|
|
75
|
+
.map((plugin, index) => ({ item: plugin.clipboard, index, priority: plugin.clipboard?.priority }))
|
|
76
|
+
.filter((entry) => Boolean(entry.item?.normalizers?.length))
|
|
77
|
+
.sort((left, right) => (right.priority ?? 0) - (left.priority ?? 0) || left.index - right.index)
|
|
78
|
+
.flatMap((entry) => entry.item.normalizers || []);
|
|
79
|
+
const contributionIds = new Set();
|
|
80
|
+
const validateContribution = (pluginId, kind, contribution) => {
|
|
81
|
+
if (!contribution.id.trim())
|
|
82
|
+
throw new Error(`Plugin "${pluginId}" has a ${kind} contribution with an empty id.`);
|
|
83
|
+
if (contributionIds.has(contribution.id))
|
|
84
|
+
throw new Error(`Duplicate ${kind} contribution id "${contribution.id}".`);
|
|
85
|
+
if (!commands.has(contribution.commandId)) {
|
|
86
|
+
throw new Error(`${kind} contribution "${contribution.id}" (plugin "${pluginId}") references missing command "${contribution.commandId}".`);
|
|
87
|
+
}
|
|
88
|
+
contributionIds.add(contribution.id);
|
|
89
|
+
};
|
|
90
|
+
const collect = (kind, pick) => byPriorityThenOrder(ordered.flatMap((plugin, pluginIndex) => (pick(plugin) || []).map((contribution, index) => {
|
|
91
|
+
validateContribution(plugin.id, kind, contribution);
|
|
92
|
+
return { item: contribution, index: pluginIndex * 100000 + index };
|
|
93
|
+
})));
|
|
94
|
+
const keyboardShortcuts = collect("Shortcut", (plugin) => plugin.keyboardShortcuts);
|
|
95
|
+
const toolbar = collect("Toolbar", (plugin) => plugin.toolbar);
|
|
96
|
+
const contextMenu = collect("Context menu", (plugin) => plugin.contextMenu);
|
|
97
|
+
const renderer = ordered
|
|
98
|
+
.map((plugin, index) => ({ item: plugin.renderer, index, priority: plugin.renderer?.priority }))
|
|
99
|
+
.filter((entry) => Boolean(entry.item))
|
|
100
|
+
.sort((left, right) => (right.priority ?? 0) - (left.priority ?? 0) || left.index - right.index)
|
|
101
|
+
.map((entry) => entry.item);
|
|
102
|
+
return {
|
|
103
|
+
plugins: ordered,
|
|
104
|
+
pluginIds,
|
|
105
|
+
schema,
|
|
106
|
+
commands,
|
|
107
|
+
normalizers,
|
|
108
|
+
clipboardNormalizers,
|
|
109
|
+
keyboardShortcuts,
|
|
110
|
+
toolbar,
|
|
111
|
+
contextMenu,
|
|
112
|
+
renderer,
|
|
113
|
+
hasPlugin: (id) => pluginIds.has(id),
|
|
114
|
+
};
|
|
115
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { SmartDocument, SmartOperation, SmartSchema } from "../types.js";
|
|
2
|
+
import type { PositionLookup, ResolvedScope, ScopeKind } from "../scope/types.js";
|
|
3
|
+
import type { NormalizerRegistration } from "../types.js";
|
|
4
|
+
import type { SchemaContribution } from "../schemaBuilder.js";
|
|
5
|
+
import type { SourceNormalizer } from "../clipboard/types.js";
|
|
6
|
+
import type { FeatureFormatCodec } from "../formats/codec.js";
|
|
7
|
+
/**
|
|
8
|
+
* Shared by every existing per-family command context
|
|
9
|
+
* (TableCommandContext, BlockCommandContext, MarkCommandContext,
|
|
10
|
+
* ListCommand's CommandContext, AtomCommandContext) - all five are this
|
|
11
|
+
* exact shape today, just independently named. A PluginCommand's `run`
|
|
12
|
+
* accepts this directly; no existing command function needs to change.
|
|
13
|
+
*/
|
|
14
|
+
export interface PluginCommandContext {
|
|
15
|
+
readonly schema: SmartSchema;
|
|
16
|
+
readonly positions: PositionLookup;
|
|
17
|
+
}
|
|
18
|
+
export interface CommandExample {
|
|
19
|
+
readonly description: string;
|
|
20
|
+
readonly params: unknown;
|
|
21
|
+
readonly expectedOutcome?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface OptionSpec {
|
|
24
|
+
readonly description: string;
|
|
25
|
+
readonly required?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Wraps the existing per-family command function shape
|
|
29
|
+
* `(document, scope, params, ctx) => SmartOperation[]` with the metadata
|
|
30
|
+
* Phase 10's doc generation needs. `run` is exactly what every existing
|
|
31
|
+
* TableCommand/BlockCommand/MarkCommand/ListCommand/AtomCommand already is -
|
|
32
|
+
* converting a family to a plugin wraps its existing commands, it does not
|
|
33
|
+
* rewrite them.
|
|
34
|
+
*/
|
|
35
|
+
export interface PluginCommand<P = unknown> {
|
|
36
|
+
readonly id: string;
|
|
37
|
+
readonly run: (document: SmartDocument, scope: ResolvedScope, params: P, ctx: PluginCommandContext) => SmartOperation[];
|
|
38
|
+
/** Required, not optional - enforced at the type level and by a non-empty-string lint check. */
|
|
39
|
+
readonly description: string;
|
|
40
|
+
readonly examples?: readonly CommandExample[];
|
|
41
|
+
readonly options?: Record<string, OptionSpec>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A command registry is necessarily heterogeneous - each entry's own
|
|
45
|
+
* params type (MarkApplyParams, TableRowParams, ...) is narrower than the
|
|
46
|
+
* `unknown` a dynamic-dispatch-by-id lookup can statically guarantee.
|
|
47
|
+
* definePluginCommand narrows the unsafe cast this requires to one place:
|
|
48
|
+
* wrap an existing, already-typed command function (TableCommand<P>,
|
|
49
|
+
* MarkCommand<P>, ...) as-is - the underlying function is never rewritten,
|
|
50
|
+
* only its params type is erased at the registry boundary, the same way
|
|
51
|
+
* every extensible command-by-id system needs to (callers are responsible
|
|
52
|
+
* for passing the right params shape for a given command id; `options`
|
|
53
|
+
* documents that shape for humans/doc-gen, it is not statically enforced).
|
|
54
|
+
*/
|
|
55
|
+
export declare const definePluginCommand: <P>(command: {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly run: (document: SmartDocument, scope: ResolvedScope, params: P, ctx: PluginCommandContext) => SmartOperation[];
|
|
58
|
+
readonly description: string;
|
|
59
|
+
readonly examples?: readonly CommandExample[];
|
|
60
|
+
readonly options?: Record<string, OptionSpec>;
|
|
61
|
+
}) => PluginCommand;
|
|
62
|
+
/**
|
|
63
|
+
* A keyboard shortcut declares the ScopeKind(s) (scope/types.ts's existing,
|
|
64
|
+
* already-tested taxonomy - inline-range/block-range/list-selection/
|
|
65
|
+
* table-grid/atomic-node/container-tree/mixed/empty) it applies to, so the
|
|
66
|
+
* dispatcher can pick the handler whose declared scope matches the resolved
|
|
67
|
+
* scope at the cursor instead of hardcoding precedence per key the way
|
|
68
|
+
* surface/input.ts's Tab handling does today. `priority` breaks a genuine
|
|
69
|
+
* tie between two handlers declaring the same scope kind for the same key;
|
|
70
|
+
* registration order breaks a tie in `priority`.
|
|
71
|
+
*/
|
|
72
|
+
export interface KeyboardShortcutContribution {
|
|
73
|
+
readonly id: string;
|
|
74
|
+
readonly commandId: string;
|
|
75
|
+
/** Fixed params for commands parameterized at the call site (e.g. mark.toggle's markType) - one command, many bindings. */
|
|
76
|
+
readonly params?: unknown;
|
|
77
|
+
readonly key: string;
|
|
78
|
+
readonly primary?: boolean;
|
|
79
|
+
readonly alt?: boolean;
|
|
80
|
+
readonly shift?: boolean;
|
|
81
|
+
readonly scopeKinds: readonly ScopeKind[];
|
|
82
|
+
readonly priority?: number;
|
|
83
|
+
}
|
|
84
|
+
export interface ToolbarContribution {
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly commandId: string;
|
|
87
|
+
readonly params?: unknown;
|
|
88
|
+
readonly label: string;
|
|
89
|
+
readonly order?: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Unlike ToolbarContribution, a context menu is scope-gated: right-clicking
|
|
93
|
+
* a table cell shouldn't offer "Clear formatting", and selecting text
|
|
94
|
+
* shouldn't offer "Delete row". `scopeKinds` mirrors
|
|
95
|
+
* KeyboardShortcutContribution's field - the same declared-metadata
|
|
96
|
+
* contract, checked by resolving `scopeKinds[0]` and requiring the actual
|
|
97
|
+
* resolved scope to match it exactly (see resolveContextMenuItems in
|
|
98
|
+
* packages/react's CanonicalAuthorityEditor, which plays the same role
|
|
99
|
+
* surface/input.ts's resolveShortcut plays for keyboard shortcuts).
|
|
100
|
+
*/
|
|
101
|
+
export interface ContextMenuContribution {
|
|
102
|
+
readonly id: string;
|
|
103
|
+
readonly commandId: string;
|
|
104
|
+
readonly params?: unknown;
|
|
105
|
+
readonly label: string;
|
|
106
|
+
readonly order?: number;
|
|
107
|
+
readonly scopeKinds: readonly ScopeKind[];
|
|
108
|
+
}
|
|
109
|
+
export interface ClipboardContribution {
|
|
110
|
+
readonly normalizers?: readonly SourceNormalizer[];
|
|
111
|
+
readonly priority?: number;
|
|
112
|
+
}
|
|
113
|
+
export interface RendererContribution {
|
|
114
|
+
/** Node type -> a project-defined render descriptor; the DOM renderer layer resolves the actual function. */
|
|
115
|
+
readonly nodeRenderers?: Record<string, unknown>;
|
|
116
|
+
readonly priority?: number;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The unit of ownership for one editor feature. Registering a list of these
|
|
120
|
+
* (see registry.ts's createPluginRegistry) produces the SmartSchema,
|
|
121
|
+
* command lookup, and normalizer/clipboard/renderer/toolbar/contextMenu
|
|
122
|
+
* registrations a FoundationEditor is built from - replacing the static,
|
|
123
|
+
* module-load-time spread `foundationSchema` used before Phase 10.
|
|
124
|
+
*/
|
|
125
|
+
export interface FoundationPlugin {
|
|
126
|
+
readonly id: string;
|
|
127
|
+
readonly version: string;
|
|
128
|
+
readonly requires?: readonly string[];
|
|
129
|
+
readonly optional?: readonly string[];
|
|
130
|
+
/** Higher wins on a genuine tie; registration order breaks a tie in this. */
|
|
131
|
+
readonly priority?: number;
|
|
132
|
+
readonly schema?: SchemaContribution;
|
|
133
|
+
readonly commands: Record<string, PluginCommand>;
|
|
134
|
+
readonly normalizers?: readonly NormalizerRegistration[];
|
|
135
|
+
readonly keyboardShortcuts?: readonly KeyboardShortcutContribution[];
|
|
136
|
+
readonly clipboard?: ClipboardContribution;
|
|
137
|
+
readonly renderer?: RendererContribution;
|
|
138
|
+
readonly toolbar?: readonly ToolbarContribution[];
|
|
139
|
+
readonly contextMenu?: readonly ContextMenuContribution[];
|
|
140
|
+
readonly formats?: Record<string, FeatureFormatCodec>;
|
|
141
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A command registry is necessarily heterogeneous - each entry's own
|
|
3
|
+
* params type (MarkApplyParams, TableRowParams, ...) is narrower than the
|
|
4
|
+
* `unknown` a dynamic-dispatch-by-id lookup can statically guarantee.
|
|
5
|
+
* definePluginCommand narrows the unsafe cast this requires to one place:
|
|
6
|
+
* wrap an existing, already-typed command function (TableCommand<P>,
|
|
7
|
+
* MarkCommand<P>, ...) as-is - the underlying function is never rewritten,
|
|
8
|
+
* only its params type is erased at the registry boundary, the same way
|
|
9
|
+
* every extensible command-by-id system needs to (callers are responsible
|
|
10
|
+
* for passing the right params shape for a given command id; `options`
|
|
11
|
+
* documents that shape for humans/doc-gen, it is not statically enforced).
|
|
12
|
+
*/
|
|
13
|
+
export const definePluginCommand = (command) => command;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ResolvedPos, SmartDocument, SmartNode, SmartPos, SmartRange, SmartSelection } from "./types.js";
|
|
2
|
+
export declare const samePath: (left: readonly number[], right: readonly number[]) => boolean;
|
|
3
|
+
export declare const nodeAtPath: (document: SmartDocument, path: readonly number[]) => SmartNode | undefined;
|
|
4
|
+
/** Inline atomic nodes are one indivisible offset unit (the model equivalent of U+FFFC). */
|
|
5
|
+
export declare const inlineSize: (node: SmartNode) => number;
|
|
6
|
+
export declare const positionLimit: (node: SmartNode) => number;
|
|
7
|
+
export declare const resolvePos: (document: SmartDocument, pos: SmartPos, affinity?: "forward" | "backward") => ResolvedPos;
|
|
8
|
+
export declare const comparePos: (left: SmartPos, right: SmartPos) => number;
|
|
9
|
+
export declare const selectionRange: (selection: SmartSelection) => SmartRange;
|
|
10
|
+
export declare const graphemeBoundaries: (text: string) => number[];
|
|
11
|
+
/**
|
|
12
|
+
* Grapheme-safe inline offsets. Each text child is segmented independently and
|
|
13
|
+
* each inline atom contributes one indivisible unit, so a cluster can never
|
|
14
|
+
* bridge across an atom boundary.
|
|
15
|
+
*/
|
|
16
|
+
export declare const inlineGraphemeBoundaries: (children: readonly SmartNode[]) => number[];
|
|
17
|
+
export declare const previousGraphemeBoundary: (text: string, offset: number) => number;
|
|
18
|
+
export declare const nextGraphemeBoundary: (text: string, offset: number) => number;
|
|
19
|
+
export declare const moveGrapheme: (text: string, offset: number, direction: -1 | 1) => number;
|
|
20
|
+
export declare const graphemeBackspaceRange: (position: ResolvedPos, text: string) => SmartRange;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { isTextNode } from "./identity.js";
|
|
2
|
+
export const samePath = (left, right) => left.length === right.length && left.every((part, index) => part === right[index]);
|
|
3
|
+
export const nodeAtPath = (document, path) => {
|
|
4
|
+
let node = document;
|
|
5
|
+
for (const index of path) {
|
|
6
|
+
if (isTextNode(node) || !node.children || index < 0 || index >= node.children.length)
|
|
7
|
+
return undefined;
|
|
8
|
+
node = node.children[index];
|
|
9
|
+
}
|
|
10
|
+
return node;
|
|
11
|
+
};
|
|
12
|
+
/** Inline atomic nodes are one indivisible offset unit (the model equivalent of U+FFFC). */
|
|
13
|
+
export const inlineSize = (node) => isTextNode(node) ? node.text.length : 1;
|
|
14
|
+
export const positionLimit = (node) => {
|
|
15
|
+
if (isTextNode(node))
|
|
16
|
+
return node.text.length;
|
|
17
|
+
if (node.children && (node.type === "paragraph" || node.type === "heading" || node.type === "code_block")) {
|
|
18
|
+
return node.children.reduce((total, child) => total + inlineSize(child), 0);
|
|
19
|
+
}
|
|
20
|
+
return node.children?.length || (node.type === "unknown" ? 1 : 0);
|
|
21
|
+
};
|
|
22
|
+
export const resolvePos = (document, pos, affinity = "backward") => {
|
|
23
|
+
const node = nodeAtPath(document, pos.path);
|
|
24
|
+
if (!node || isTextNode(node))
|
|
25
|
+
throw new Error("A SmartPos path must resolve to an owning non-text node.");
|
|
26
|
+
const limit = positionLimit(node);
|
|
27
|
+
if (!Number.isInteger(pos.offset) || pos.offset < 0 || pos.offset > limit) {
|
|
28
|
+
throw new Error(`Position offset ${pos.offset} is outside 0..${limit}.`);
|
|
29
|
+
}
|
|
30
|
+
const ancestors = [];
|
|
31
|
+
let current = document;
|
|
32
|
+
ancestors.push(document);
|
|
33
|
+
for (const index of pos.path) {
|
|
34
|
+
if (isTextNode(current) || !current.children)
|
|
35
|
+
throw new Error("Invalid position path.");
|
|
36
|
+
current = current.children[index];
|
|
37
|
+
if (!isTextNode(current))
|
|
38
|
+
ancestors.push(current);
|
|
39
|
+
}
|
|
40
|
+
const structural = node.type !== "paragraph" && node.type !== "heading" && node.type !== "code_block";
|
|
41
|
+
const before = structural && pos.offset > 0 ? node.children?.[pos.offset - 1] || null : null;
|
|
42
|
+
const after = structural && pos.offset < limit ? node.children?.[pos.offset] || null : null;
|
|
43
|
+
return {
|
|
44
|
+
pos: { path: [...pos.path], offset: pos.offset },
|
|
45
|
+
kind: structural ? "structural" : "inline",
|
|
46
|
+
nodeId: node.id,
|
|
47
|
+
parent: node,
|
|
48
|
+
depth: pos.path.length,
|
|
49
|
+
affinity,
|
|
50
|
+
ancestors: Object.freeze(ancestors),
|
|
51
|
+
nodeBefore: before,
|
|
52
|
+
nodeAfter: after,
|
|
53
|
+
atStart: pos.offset === 0,
|
|
54
|
+
atEnd: pos.offset === limit,
|
|
55
|
+
indexAt(depth) {
|
|
56
|
+
if (!Number.isInteger(depth) || depth < 0 || depth >= pos.path.length)
|
|
57
|
+
throw new Error("Position depth is out of range.");
|
|
58
|
+
return pos.path[depth];
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export const comparePos = (left, right) => {
|
|
63
|
+
const length = Math.min(left.path.length, right.path.length);
|
|
64
|
+
for (let index = 0; index < length; index += 1) {
|
|
65
|
+
if (left.path[index] !== right.path[index])
|
|
66
|
+
return left.path[index] - right.path[index];
|
|
67
|
+
}
|
|
68
|
+
if (left.path.length !== right.path.length)
|
|
69
|
+
return left.path.length - right.path.length;
|
|
70
|
+
return left.offset - right.offset;
|
|
71
|
+
};
|
|
72
|
+
export const selectionRange = (selection) => comparePos(selection.anchor, selection.head) <= 0
|
|
73
|
+
? { from: selection.anchor, to: selection.head }
|
|
74
|
+
: { from: selection.head, to: selection.anchor };
|
|
75
|
+
const Segmenter = Intl.Segmenter;
|
|
76
|
+
const segmenter = new Segmenter(undefined, { granularity: "grapheme" });
|
|
77
|
+
export const graphemeBoundaries = (text) => {
|
|
78
|
+
const boundaries = [0];
|
|
79
|
+
for (const segment of segmenter.segment(text))
|
|
80
|
+
boundaries.push(segment.index + segment.segment.length);
|
|
81
|
+
return [...new Set(boundaries)].sort((a, b) => a - b);
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Grapheme-safe inline offsets. Each text child is segmented independently and
|
|
85
|
+
* each inline atom contributes one indivisible unit, so a cluster can never
|
|
86
|
+
* bridge across an atom boundary.
|
|
87
|
+
*/
|
|
88
|
+
export const inlineGraphemeBoundaries = (children) => {
|
|
89
|
+
const boundaries = [0];
|
|
90
|
+
let offset = 0;
|
|
91
|
+
children.forEach((child) => {
|
|
92
|
+
if (isTextNode(child)) {
|
|
93
|
+
graphemeBoundaries(child.text).slice(1).forEach((boundary) => boundaries.push(offset + boundary));
|
|
94
|
+
offset += child.text.length;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
offset += 1;
|
|
98
|
+
boundaries.push(offset);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return [...new Set(boundaries)].sort((left, right) => left - right);
|
|
102
|
+
};
|
|
103
|
+
export const previousGraphemeBoundary = (text, offset) => {
|
|
104
|
+
if (!Number.isInteger(offset) || offset < 0 || offset > text.length)
|
|
105
|
+
throw new Error("Text offset is out of bounds.");
|
|
106
|
+
const boundaries = graphemeBoundaries(text);
|
|
107
|
+
for (let index = boundaries.length - 1; index >= 0; index -= 1)
|
|
108
|
+
if (boundaries[index] < offset)
|
|
109
|
+
return boundaries[index];
|
|
110
|
+
return 0;
|
|
111
|
+
};
|
|
112
|
+
export const nextGraphemeBoundary = (text, offset) => {
|
|
113
|
+
if (!Number.isInteger(offset) || offset < 0 || offset > text.length)
|
|
114
|
+
throw new Error("Text offset is out of bounds.");
|
|
115
|
+
return graphemeBoundaries(text).find((boundary) => boundary > offset) ?? text.length;
|
|
116
|
+
};
|
|
117
|
+
export const moveGrapheme = (text, offset, direction) => direction < 0 ? previousGraphemeBoundary(text, offset) : nextGraphemeBoundary(text, offset);
|
|
118
|
+
export const graphemeBackspaceRange = (position, text) => ({
|
|
119
|
+
from: { path: [...position.pos.path], offset: previousGraphemeBoundary(text, position.pos.offset) },
|
|
120
|
+
to: { path: [...position.pos.path], offset: position.pos.offset },
|
|
121
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Repair, SmartDocument, SmartNode, SmartSchema, ValidationError } from "./types.js";
|
|
2
|
+
import { type PluginRegistry } from "./plugin/registry.js";
|
|
3
|
+
import { type SchemaContribution } from "./schemaBuilder.js";
|
|
4
|
+
export { createSchema, parseContentExpression, type SchemaContribution } from "./schemaBuilder.js";
|
|
5
|
+
export declare const contentMatches: (expression: string, children: readonly SmartNode[], schema: SmartSchema) => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Node/mark types no feature-family plugin owns and that must exist
|
|
8
|
+
* regardless of which plugins are registered: the document root, plain
|
|
9
|
+
* text, hard breaks, and `unknown` itself - the disable-safety passthrough
|
|
10
|
+
* node type (see repair() below), which would be a chicken-and-egg problem
|
|
11
|
+
* if any plugin had to own it.
|
|
12
|
+
*/
|
|
13
|
+
export declare const baseSchema: SchemaContribution;
|
|
14
|
+
/**
|
|
15
|
+
* The "all built-ins" preset, now derived from the plugin registry
|
|
16
|
+
* (plugin/registry.ts, plugin/builtins.ts) rather than a hand-maintained
|
|
17
|
+
* literal - Phase 10's runtime plugin registration applies to the default
|
|
18
|
+
* editor construction too, not just custom plugin lists. Computed once and
|
|
19
|
+
* exported alongside `foundationSchema` so editor.ts can default
|
|
20
|
+
* FoundationEditorOptions.commands/keyboardShortcuts to the same registry
|
|
21
|
+
* instance instead of recomputing it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const foundationRegistry: PluginRegistry;
|
|
24
|
+
export declare const foundationSchema: SmartSchema;
|
|
25
|
+
export declare const validate: (document: SmartDocument, schema?: SmartSchema) => ValidationError[];
|
|
26
|
+
export declare const repair: (input: SmartDocument, schema?: SmartSchema) => {
|
|
27
|
+
doc: SmartDocument;
|
|
28
|
+
repairs: Repair[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The reverse of repair()'s "preserve-unknown" case: a node whose type is
|
|
32
|
+
* now recognized by `schema` (e.g. its owning plugin was re-enabled) is
|
|
33
|
+
* restored verbatim from the `unknown` wrapper's `raw` attribute, recursing
|
|
34
|
+
* into the restored subtree in case it too contains a now-restorable
|
|
35
|
+
* `unknown` node. This is the disable-safety round trip's other half -
|
|
36
|
+
* repair() already existed and demotes unrecognized content to `unknown`
|
|
37
|
+
* without any Phase 10 changes; this direction did not exist before.
|
|
38
|
+
*/
|
|
39
|
+
export declare const restoreUnknownNodes: (document: SmartDocument, schema: SmartSchema) => SmartDocument;
|
|
40
|
+
/**
|
|
41
|
+
* The mark-side counterpart of restoreUnknownNodes(): a text node's
|
|
42
|
+
* "unknown-mark" sentinels (see repair()'s mark-filtering loop above) are
|
|
43
|
+
* restored to their real mark type once `schema` recognizes it again,
|
|
44
|
+
* re-sorted into canonical order since the restored type string can sort
|
|
45
|
+
* differently than "unknown-mark" did.
|
|
46
|
+
*/
|
|
47
|
+
export declare const restoreUnknownMarks: (document: SmartDocument, schema: SmartSchema) => SmartDocument;
|
|
48
|
+
export declare const serializePersistedDocument: (value: {
|
|
49
|
+
schemaVersion: number;
|
|
50
|
+
revision: number;
|
|
51
|
+
document: SmartDocument;
|
|
52
|
+
}) => string;
|
|
53
|
+
export declare const parsePersistedDocument: (json: string) => {
|
|
54
|
+
schemaVersion: number;
|
|
55
|
+
revision: number;
|
|
56
|
+
document: SmartDocument;
|
|
57
|
+
};
|