smartrte-core 0.2.1 → 1.0.0-beta.2
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 +24 -0
- package/dist/foundation/atom/formats.js +105 -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 +21 -0
- package/dist/foundation/list/formats.js +813 -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 +27 -0
- package/dist/foundation/table/formats.js +40 -0
- package/dist/foundation/table/grid.d.ts +8 -0
- package/dist/foundation/table/grid.js +178 -0
- package/dist/foundation/table/index.d.ts +6 -0
- package/dist/foundation/table/index.js +6 -0
- package/dist/foundation/table/plugin.d.ts +23 -0
- package/dist/foundation/table/plugin.js +119 -0
- package/dist/foundation/table/schema.d.ts +2 -0
- package/dist/foundation/table/schema.js +38 -0
- package/dist/foundation/table/selection.d.ts +7 -0
- package/dist/foundation/table/selection.js +35 -0
- package/dist/foundation/table/types.d.ts +109 -0
- package/dist/foundation/table/types.js +1 -0
- package/dist/foundation/transactions.d.ts +41 -0
- package/dist/foundation/transactions.js +90 -0
- package/dist/foundation/types.d.ts +261 -0
- package/dist/foundation/types.js +1 -0
- package/dist/foundation/versioning/diffVersions.d.ts +5 -0
- package/dist/foundation/versioning/diffVersions.js +3 -0
- package/dist/foundation/versioning/index.d.ts +2 -0
- package/dist/foundation/versioning/index.js +2 -0
- package/dist/foundation/versioning/types.d.ts +17 -0
- package/dist/foundation/versioning/types.js +1 -0
- package/dist/history.d.ts +20 -0
- package/dist/history.js +165 -0
- package/dist/html/compatibility.js +7 -1
- package/dist/index.d.ts +1 -14
- package/dist/index.js +9 -14
- package/dist/legacy/index.d.ts +45 -0
- package/dist/legacy/index.js +45 -0
- package/dist/legacyCommands/alignment.d.ts +20 -0
- package/dist/{commands → legacyCommands}/alignment.js +17 -0
- package/dist/legacyCommands/blocks.d.ts +14 -0
- package/dist/legacyCommands/blocks.js +105 -0
- package/dist/legacyCommands/checklist.d.ts +11 -0
- package/dist/legacyCommands/checklist.js +45 -0
- package/dist/legacyCommands/code.d.ts +15 -0
- package/dist/{commands → legacyCommands}/code.js +25 -1
- package/dist/legacyCommands/formula.d.ts +7 -0
- package/dist/legacyCommands/formula.js +67 -0
- package/dist/legacyCommands/inlineAtoms.d.ts +7 -0
- package/dist/legacyCommands/inlineAtoms.js +58 -0
- package/dist/legacyCommands/list.d.ts +38 -0
- package/dist/legacyCommands/list.js +477 -0
- package/dist/{commands → legacyCommands}/marks.d.ts +3 -0
- package/dist/{commands → legacyCommands}/marks.js +28 -11
- package/dist/legacyCommands/media.d.ts +31 -0
- package/dist/legacyCommands/media.js +205 -0
- package/dist/legacyCommands/move.d.ts +14 -0
- package/dist/legacyCommands/move.js +91 -0
- package/dist/legacyCommands/table.d.ts +91 -0
- package/dist/legacyCommands/table.js +547 -0
- package/dist/listPresets.d.ts +12 -0
- package/dist/listPresets.js +6 -0
- package/dist/listScope.d.ts +27 -0
- package/dist/listScope.js +104 -0
- package/dist/markdown/compatibility.d.ts +2 -0
- package/dist/markdown/compatibility.js +94 -2
- package/dist/marks.d.ts +5 -5
- package/dist/model.d.ts +65 -9
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.js +31 -0
- package/dist/plugins/alignment.d.ts +3 -0
- package/dist/plugins/alignment.js +6 -0
- package/dist/plugins/basicFormatting.d.ts +9 -0
- package/dist/plugins/basicFormatting.js +24 -0
- package/dist/plugins/blockType.d.ts +3 -0
- package/dist/plugins/blockType.js +6 -0
- package/dist/plugins/blockquote.d.ts +3 -0
- package/dist/plugins/blockquote.js +6 -0
- package/dist/plugins/checklist.d.ts +3 -0
- package/dist/plugins/checklist.js +10 -0
- package/dist/plugins/codeBlock.d.ts +3 -0
- package/dist/plugins/codeBlock.js +6 -0
- package/dist/plugins/formula.d.ts +3 -0
- package/dist/plugins/formula.js +9 -0
- package/dist/plugins/list.d.ts +3 -0
- package/dist/plugins/list.js +10 -0
- package/dist/plugins/media.d.ts +3 -0
- package/dist/plugins/media.js +12 -0
- package/dist/plugins/move.d.ts +3 -0
- package/dist/plugins/move.js +9 -0
- package/dist/plugins/table.d.ts +3 -0
- package/dist/plugins/table.js +22 -0
- package/dist/preset.d.ts +12 -0
- package/dist/preset.js +40 -0
- package/dist/schema.d.ts +22 -0
- package/dist/schema.js +333 -0
- package/dist/security/urlPolicy.d.ts +1 -17
- package/dist/security/urlPolicy.js +3 -69
- package/dist/selection.d.ts +1 -1
- package/dist/selectionMapping.d.ts +4 -0
- package/dist/selectionMapping.js +222 -0
- package/dist/table.d.ts +3 -3
- package/dist/transaction.d.ts +35 -12
- package/dist/transaction.js +174 -28
- package/dist/tree.d.ts +8 -2
- package/dist/tree.js +44 -0
- package/package.json +27 -8
- package/dist/commands/alignment.d.ts +0 -18
- package/dist/commands/code.d.ts +0 -13
- package/dist/commands/list.d.ts +0 -20
- package/dist/commands/list.js +0 -52
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isTextNode } from "../identity.js";
|
|
2
|
+
import { canonicalMarkOrder } from "./canonical.js";
|
|
3
|
+
export const MARKDOWN_SUPPORTED_MARKS = Object.freeze(["bold", "italic", "code", "strike", "link"]);
|
|
4
|
+
export const MARKDOWN_UNSUPPORTED_MARKS = Object.freeze([
|
|
5
|
+
"underline", "textColor", "backgroundColor", "fontSize", "fontFamily", "superscript", "subscript",
|
|
6
|
+
]);
|
|
7
|
+
export const docxProperties = (marks) => {
|
|
8
|
+
const properties = {};
|
|
9
|
+
canonicalMarkOrder(marks).forEach((mark) => {
|
|
10
|
+
if (mark.type === "bold" || mark.type === "italic" || mark.type === "underline" || mark.type === "strike"
|
|
11
|
+
|| mark.type === "code" || mark.type === "superscript" || mark.type === "subscript")
|
|
12
|
+
properties[mark.type] = true;
|
|
13
|
+
else if (mark.type === "textColor")
|
|
14
|
+
properties.color = mark.attrs?.value;
|
|
15
|
+
else if (mark.type === "backgroundColor")
|
|
16
|
+
properties.backgroundColor = mark.attrs?.value;
|
|
17
|
+
else if (mark.type === "fontSize")
|
|
18
|
+
properties.fontSizeHalfPoints = Math.round(Number(mark.attrs?.valuePx || 0) * 1.5);
|
|
19
|
+
else if (mark.type === "fontFamily")
|
|
20
|
+
properties.fontFamily = mark.attrs?.value;
|
|
21
|
+
else if (mark.type === "link")
|
|
22
|
+
properties.link = { href: String(mark.attrs?.href || ""), ...(mark.attrs?.target ? { target: String(mark.attrs.target) } : {}) };
|
|
23
|
+
});
|
|
24
|
+
return properties;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The per-node half of docxProperties, matching FeatureFormatCodec.serialize's
|
|
28
|
+
* (node, ctx) => unknown shape (formats/codec.ts) - Phase 11 Tier 2's marks
|
|
29
|
+
* codec slice. Real single-node function already existed (docxProperties
|
|
30
|
+
* operates on one text node's marks array already); this only adapts its
|
|
31
|
+
* signature to accept a SmartNode directly, it does not add new logic.
|
|
32
|
+
*/
|
|
33
|
+
export const markRunDocxProperties = (node) => isTextNode(node) ? docxProperties(node.marks || []) : {};
|
|
34
|
+
/** Semantic DOCX run model. Conversion to OOXML lives in the product adapter. */
|
|
35
|
+
export const canonicalMarksToDocxRuns = (document) => {
|
|
36
|
+
const runs = [];
|
|
37
|
+
const visit = (node) => {
|
|
38
|
+
if (isTextNode(node))
|
|
39
|
+
runs.push({ text: node.text, properties: docxProperties(node.marks || []) });
|
|
40
|
+
else if (node.type === "hard_break")
|
|
41
|
+
runs.push({ text: "\n", properties: {} });
|
|
42
|
+
else
|
|
43
|
+
node.children?.forEach(visit);
|
|
44
|
+
};
|
|
45
|
+
visit(document);
|
|
46
|
+
return runs;
|
|
47
|
+
};
|
|
48
|
+
/** PDF fidelity is visual/lossy; this payload deliberately contains no mark identity. */
|
|
49
|
+
export const canonicalMarksPdfText = (document) => canonicalMarksToDocxRuns(document).map((run) => run.text).join("");
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SmartDocument, SmartOperation, SmartPos } from "../types.js";
|
|
2
|
+
export interface HardBreakMigrationResult {
|
|
3
|
+
readonly document: SmartDocument;
|
|
4
|
+
readonly migratedBreaks: number;
|
|
5
|
+
}
|
|
6
|
+
/** Migrates legacy canonical newline text to atomic, unmarked hard_break nodes. */
|
|
7
|
+
export declare const migrateNewlineTextToHardBreaks: (document: SmartDocument) => HardBreakMigrationResult;
|
|
8
|
+
export declare const insertHardBreak: (document: SmartDocument, pos: SmartPos, id?: string) => SmartOperation[];
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { cloneNode, createNodeId, isTextNode } from "../identity.js";
|
|
2
|
+
import { nodeAtPath } from "../positions.js";
|
|
3
|
+
/** Migrates legacy canonical newline text to atomic, unmarked hard_break nodes. */
|
|
4
|
+
export const migrateNewlineTextToHardBreaks = (document) => {
|
|
5
|
+
let migratedBreaks = 0;
|
|
6
|
+
const visit = (node) => {
|
|
7
|
+
if (isTextNode(node)) {
|
|
8
|
+
if (!node.text.includes("\n"))
|
|
9
|
+
return node;
|
|
10
|
+
throw new Error("Text-node migration must be performed by its inline owner.");
|
|
11
|
+
}
|
|
12
|
+
const children = [];
|
|
13
|
+
let changed = false;
|
|
14
|
+
(node.children || []).forEach((child) => {
|
|
15
|
+
if (!isTextNode(child) || !child.text.includes("\n")) {
|
|
16
|
+
const visited = visit(child);
|
|
17
|
+
if (visited !== child)
|
|
18
|
+
changed = true;
|
|
19
|
+
children.push(visited);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
changed = true;
|
|
23
|
+
const pieces = child.text.split("\n");
|
|
24
|
+
pieces.forEach((piece, index) => {
|
|
25
|
+
if (piece)
|
|
26
|
+
children.push({ type: "text", text: piece, ...(child.marks?.length ? { marks: [...child.marks] } : {}) });
|
|
27
|
+
if (index < pieces.length - 1) {
|
|
28
|
+
children.push({ type: "hard_break", id: createNodeId() });
|
|
29
|
+
migratedBreaks += 1;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
if (!changed)
|
|
34
|
+
return node;
|
|
35
|
+
return { ...node, ...(node.children ? { children } : {}) };
|
|
36
|
+
};
|
|
37
|
+
return { document: visit(document), migratedBreaks };
|
|
38
|
+
};
|
|
39
|
+
const splitChildrenAt = (children, offset) => {
|
|
40
|
+
const before = [];
|
|
41
|
+
const after = [];
|
|
42
|
+
let cursor = 0;
|
|
43
|
+
let split = false;
|
|
44
|
+
children.forEach((child) => {
|
|
45
|
+
if (split)
|
|
46
|
+
return after.push(child);
|
|
47
|
+
const width = isTextNode(child) ? child.text.length : 1;
|
|
48
|
+
if (offset > cursor + width) {
|
|
49
|
+
before.push(child);
|
|
50
|
+
cursor += width;
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const local = offset - cursor;
|
|
54
|
+
if (isTextNode(child)) {
|
|
55
|
+
if (local > 0)
|
|
56
|
+
before.push({ ...child, text: child.text.slice(0, local) });
|
|
57
|
+
if (local < width)
|
|
58
|
+
after.push({ ...child, text: child.text.slice(local) });
|
|
59
|
+
}
|
|
60
|
+
else if (local === 0)
|
|
61
|
+
after.push(child);
|
|
62
|
+
else if (local === 1)
|
|
63
|
+
before.push(child);
|
|
64
|
+
else
|
|
65
|
+
throw new Error("Hard-break insertion position is inside an atom.");
|
|
66
|
+
split = true;
|
|
67
|
+
cursor += width;
|
|
68
|
+
});
|
|
69
|
+
if (!split && cursor !== offset)
|
|
70
|
+
throw new Error("Hard-break insertion position is out of bounds.");
|
|
71
|
+
return [before, after];
|
|
72
|
+
};
|
|
73
|
+
export const insertHardBreak = (document, pos, id = createNodeId()) => {
|
|
74
|
+
const owner = nodeAtPath(document, pos.path);
|
|
75
|
+
if (!owner || isTextNode(owner) || !owner.children)
|
|
76
|
+
throw new Error("hard_break requires an inline owner position.");
|
|
77
|
+
const [before, after] = splitChildrenAt(owner.children, pos.offset);
|
|
78
|
+
const replacement = { ...owner, children: [...before, { type: "hard_break", id }, ...after] };
|
|
79
|
+
return [{ type: "replaceNode", pos: { path: pos.path.slice(0, -1), offset: pos.path[pos.path.length - 1] }, before: cloneNode(owner), after: replacement }];
|
|
80
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./canonical.js";
|
|
3
|
+
export * from "./schema.js";
|
|
4
|
+
export * from "./commands.js";
|
|
5
|
+
export * from "./hardBreak.js";
|
|
6
|
+
export * from "./resolveMarkRun.js";
|
|
7
|
+
export * from "./normalization.js";
|
|
8
|
+
export * from "./dom.js";
|
|
9
|
+
export * from "./stored.js";
|
|
10
|
+
export * from "./editor.js";
|
|
11
|
+
export * from "./formats.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./canonical.js";
|
|
3
|
+
export * from "./schema.js";
|
|
4
|
+
export * from "./commands.js";
|
|
5
|
+
export * from "./hardBreak.js";
|
|
6
|
+
export * from "./resolveMarkRun.js";
|
|
7
|
+
export * from "./normalization.js";
|
|
8
|
+
export * from "./dom.js";
|
|
9
|
+
export * from "./stored.js";
|
|
10
|
+
export * from "./editor.js";
|
|
11
|
+
export * from "./formats.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isTextNode } from "../identity.js";
|
|
2
|
+
import { nodeAtPath } from "../positions.js";
|
|
3
|
+
import { canonicalMarkOrder, stableValue } from "./canonical.js";
|
|
4
|
+
const normalizedInlineChildren = (children) => {
|
|
5
|
+
const output = [];
|
|
6
|
+
children.forEach((child) => {
|
|
7
|
+
if (isTextNode(child) && !child.text)
|
|
8
|
+
return;
|
|
9
|
+
const normalized = isTextNode(child) && child.marks?.length
|
|
10
|
+
? { ...child, marks: canonicalMarkOrder(child.marks) }
|
|
11
|
+
: child;
|
|
12
|
+
const previous = output[output.length - 1];
|
|
13
|
+
if (isTextNode(normalized) && previous && isTextNode(previous)
|
|
14
|
+
&& stableValue(previous.marks || []) === stableValue(normalized.marks || [])) {
|
|
15
|
+
output[output.length - 1] = { ...previous, text: previous.text + normalized.text };
|
|
16
|
+
}
|
|
17
|
+
else
|
|
18
|
+
output.push(normalized);
|
|
19
|
+
});
|
|
20
|
+
return output;
|
|
21
|
+
};
|
|
22
|
+
const inlineOwner = (node, schema) => schema.nodes[node.type]?.content?.includes("inline") === true;
|
|
23
|
+
export const createMarkNormalizer = () => ({
|
|
24
|
+
id: "foundation.mark-normalization",
|
|
25
|
+
priority: 100,
|
|
26
|
+
normalize(document, context) {
|
|
27
|
+
const root = nodeAtPath(document, context.affectedPath);
|
|
28
|
+
if (!root || isTextNode(root))
|
|
29
|
+
return { operations: [] };
|
|
30
|
+
const operations = [];
|
|
31
|
+
const visit = (node, path) => {
|
|
32
|
+
if (inlineOwner(node, context.schema)) {
|
|
33
|
+
const children = normalizedInlineChildren(node.children || []);
|
|
34
|
+
if (stableValue(children) !== stableValue(node.children || []))
|
|
35
|
+
operations.push({
|
|
36
|
+
type: "replaceNode",
|
|
37
|
+
pos: { path: path.slice(0, -1), offset: path[path.length - 1] },
|
|
38
|
+
before: node,
|
|
39
|
+
after: { ...node, children },
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
node.children?.forEach((child, index) => { if (!isTextNode(child))
|
|
44
|
+
visit(child, [...path, index]); });
|
|
45
|
+
};
|
|
46
|
+
visit(root, [...context.affectedPath]);
|
|
47
|
+
return { operations };
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type PluginCommand, type ToolbarContribution } from "../plugin/types.js";
|
|
2
|
+
import { inlineMarkSpecs } from "./schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps the seven existing, already-tested mark commands (marks/commands.ts)
|
|
5
|
+
* with the description Phase 10's doc generation needs. The commands
|
|
6
|
+
* themselves are unchanged - all are generic (apply/remove/toggle/setAttrs
|
|
7
|
+
* to any mark type via a `markType` param), so individual marks like bold
|
|
8
|
+
* or italic are not separate commands; they are toolbar/shortcut
|
|
9
|
+
* contributions that invoke the same command with a different `params`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const markPluginCommands: Record<string, PluginCommand>;
|
|
12
|
+
/** One toolbar contribution per inlineToolDeclarations entry (schema.ts), each invoking mark.toggle with that mark's type as params. */
|
|
13
|
+
export declare const markToolbarContributions: readonly ToolbarContribution[];
|
|
14
|
+
export { inlineMarkSpecs };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { definePluginCommand } from "../plugin/types.js";
|
|
2
|
+
import { clearAllMarksCommand, editLinkCommand, removeLinkCommand, removeMarkCommand, applyMarkCommand, setMarkAttrsCommand, toggleMarkCommand, } from "./commands.js";
|
|
3
|
+
import { inlineMarkSpecs, inlineToolDeclarations } from "./schema.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wraps the seven existing, already-tested mark commands (marks/commands.ts)
|
|
6
|
+
* with the description Phase 10's doc generation needs. The commands
|
|
7
|
+
* themselves are unchanged - all are generic (apply/remove/toggle/setAttrs
|
|
8
|
+
* to any mark type via a `markType` param), so individual marks like bold
|
|
9
|
+
* or italic are not separate commands; they are toolbar/shortcut
|
|
10
|
+
* contributions that invoke the same command with a different `params`.
|
|
11
|
+
*/
|
|
12
|
+
export const markPluginCommands = {
|
|
13
|
+
"mark.apply": definePluginCommand({
|
|
14
|
+
id: "mark.apply", run: applyMarkCommand,
|
|
15
|
+
description: "Applies a mark (e.g. bold, link) to the current inline selection, removing any mutually-excluded mark first.",
|
|
16
|
+
options: { markType: { description: "The mark type to apply, e.g. \"bold\" or \"link\".", required: true }, attrs: { description: "Mark attributes, if the mark type requires any (e.g. link's href)." } },
|
|
17
|
+
}),
|
|
18
|
+
"mark.remove": definePluginCommand({
|
|
19
|
+
id: "mark.remove", run: removeMarkCommand,
|
|
20
|
+
description: "Removes a mark type from the current inline selection.",
|
|
21
|
+
options: { markType: { description: "The mark type to remove.", required: true } },
|
|
22
|
+
}),
|
|
23
|
+
"mark.toggle": definePluginCommand({
|
|
24
|
+
id: "mark.toggle", run: toggleMarkCommand,
|
|
25
|
+
description: "Applies a mark, or removes it if the selection is already fully covered by it (params.coverage === \"all\").",
|
|
26
|
+
options: {
|
|
27
|
+
markType: { description: "The mark type to toggle.", required: true },
|
|
28
|
+
attrs: { description: "Mark attributes, if the mark type requires any." },
|
|
29
|
+
coverage: { description: "\"all\" when the selection is already fully covered by this mark - toggles it off instead of re-applying it." },
|
|
30
|
+
},
|
|
31
|
+
examples: [
|
|
32
|
+
{ description: "Toggle bold on the current selection", params: { markType: "bold" } },
|
|
33
|
+
{ description: "Toggle bold off, given the selection is already fully bold", params: { markType: "bold", coverage: "all" } },
|
|
34
|
+
],
|
|
35
|
+
}),
|
|
36
|
+
"mark.setAttrs": definePluginCommand({
|
|
37
|
+
id: "mark.setAttrs", run: setMarkAttrsCommand,
|
|
38
|
+
description: "Sets a mark's attributes on the current selection (an alias of mark.apply - re-applying overwrites existing attributes).",
|
|
39
|
+
options: { markType: { description: "The mark type to update.", required: true }, attrs: { description: "The new attributes." } },
|
|
40
|
+
}),
|
|
41
|
+
"mark.clearAll": definePluginCommand({
|
|
42
|
+
id: "mark.clearAll", run: clearAllMarksCommand,
|
|
43
|
+
description: "Removes every mark present on the current inline selection, regardless of type.",
|
|
44
|
+
}),
|
|
45
|
+
"link.remove": definePluginCommand({
|
|
46
|
+
id: "link.remove", run: removeLinkCommand,
|
|
47
|
+
description: "Removes the link mark from the current selection, or from the link run at a collapsed caret inside one.",
|
|
48
|
+
}),
|
|
49
|
+
"link.edit": definePluginCommand({
|
|
50
|
+
id: "link.edit", run: editLinkCommand,
|
|
51
|
+
description: "Sets or replaces the link mark's href/target on the current selection, or on the link run at a collapsed caret inside one.",
|
|
52
|
+
options: { href: { description: "The link URL.", required: true }, target: { description: "The link's target attribute, e.g. \"_blank\"." } },
|
|
53
|
+
examples: [{ description: "Turn the selection into a link", params: { href: "https://example.com" } }],
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
/** One toolbar contribution per inlineToolDeclarations entry (schema.ts), each invoking mark.toggle with that mark's type as params. */
|
|
57
|
+
export const markToolbarContributions = inlineToolDeclarations.map((declaration) => ({
|
|
58
|
+
id: `marks.toolbar.${declaration.id}`,
|
|
59
|
+
commandId: "mark.toggle",
|
|
60
|
+
params: { markType: declaration.markType, coverage: "partial" },
|
|
61
|
+
label: declaration.id,
|
|
62
|
+
}));
|
|
63
|
+
export { inlineMarkSpecs };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SmartDocument, SmartMark, SmartPos, SmartRange } from "../types.js";
|
|
2
|
+
export interface ResolvedMarkRun {
|
|
3
|
+
readonly mark: SmartMark;
|
|
4
|
+
readonly range: SmartRange;
|
|
5
|
+
readonly ownerNodeId: string;
|
|
6
|
+
}
|
|
7
|
+
/** Resolves the complete contiguous run for one exact mark at a collapsed position. */
|
|
8
|
+
export declare const resolveMarkRun: (document: SmartDocument, pos: SmartPos, markType: string) => ResolvedMarkRun | null;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isTextNode } from "../identity.js";
|
|
2
|
+
import { nodeAtPath } from "../positions.js";
|
|
3
|
+
import { markKey } from "./canonical.js";
|
|
4
|
+
/** Resolves the complete contiguous run for one exact mark at a collapsed position. */
|
|
5
|
+
export const resolveMarkRun = (document, pos, markType) => {
|
|
6
|
+
const owner = nodeAtPath(document, pos.path);
|
|
7
|
+
if (!owner || isTextNode(owner))
|
|
8
|
+
return null;
|
|
9
|
+
let offset = 0;
|
|
10
|
+
const segments = (owner.children || []).map((node) => {
|
|
11
|
+
const from = offset;
|
|
12
|
+
offset += isTextNode(node) ? node.text.length : 1;
|
|
13
|
+
return { node, from, to: offset };
|
|
14
|
+
});
|
|
15
|
+
const active = [...segments].reverse().find((segment) => isTextNode(segment.node)
|
|
16
|
+
&& segment.from < pos.offset && pos.offset <= segment.to
|
|
17
|
+
&& segment.node.marks?.some((mark) => mark.type === markType))
|
|
18
|
+
?? segments.find((segment) => isTextNode(segment.node)
|
|
19
|
+
&& segment.from <= pos.offset && pos.offset < segment.to
|
|
20
|
+
&& segment.node.marks?.some((mark) => mark.type === markType));
|
|
21
|
+
if (!active || !isTextNode(active.node))
|
|
22
|
+
return null;
|
|
23
|
+
const mark = active.node.marks?.find((candidate) => candidate.type === markType);
|
|
24
|
+
if (!mark)
|
|
25
|
+
return null;
|
|
26
|
+
const key = markKey(mark);
|
|
27
|
+
let from = active.from;
|
|
28
|
+
let to = active.to;
|
|
29
|
+
const index = segments.indexOf(active);
|
|
30
|
+
for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
|
|
31
|
+
const segment = segments[cursor];
|
|
32
|
+
if (!isTextNode(segment.node) || !segment.node.marks?.some((candidate) => markKey(candidate) === key))
|
|
33
|
+
break;
|
|
34
|
+
from = segment.from;
|
|
35
|
+
}
|
|
36
|
+
for (let cursor = index + 1; cursor < segments.length; cursor += 1) {
|
|
37
|
+
const segment = segments[cursor];
|
|
38
|
+
if (!isTextNode(segment.node) || !segment.node.marks?.some((candidate) => markKey(candidate) === key))
|
|
39
|
+
break;
|
|
40
|
+
to = segment.to;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
mark,
|
|
44
|
+
ownerNodeId: owner.id,
|
|
45
|
+
range: { from: { path: [...pos.path], offset: from }, to: { path: [...pos.path], offset: to } },
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export declare const inlineMarkSpecs: readonly [{
|
|
2
|
+
readonly type: "bold";
|
|
3
|
+
readonly inclusive: true;
|
|
4
|
+
}, {
|
|
5
|
+
readonly type: "italic";
|
|
6
|
+
readonly inclusive: true;
|
|
7
|
+
}, {
|
|
8
|
+
readonly type: "underline";
|
|
9
|
+
readonly inclusive: true;
|
|
10
|
+
}, {
|
|
11
|
+
readonly type: "strike";
|
|
12
|
+
readonly inclusive: true;
|
|
13
|
+
}, {
|
|
14
|
+
readonly type: "code";
|
|
15
|
+
readonly inclusive: true;
|
|
16
|
+
}, {
|
|
17
|
+
readonly type: "superscript";
|
|
18
|
+
readonly inclusive: true;
|
|
19
|
+
readonly excludes: ["subscript"];
|
|
20
|
+
}, {
|
|
21
|
+
readonly type: "subscript";
|
|
22
|
+
readonly inclusive: true;
|
|
23
|
+
readonly excludes: ["superscript"];
|
|
24
|
+
}, {
|
|
25
|
+
readonly type: "textColor";
|
|
26
|
+
readonly inclusive: true;
|
|
27
|
+
readonly excludes: ["textColor"];
|
|
28
|
+
readonly attributes: {
|
|
29
|
+
readonly value: {
|
|
30
|
+
required: boolean;
|
|
31
|
+
validate: (value: unknown) => boolean;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
}, {
|
|
35
|
+
readonly type: "backgroundColor";
|
|
36
|
+
readonly inclusive: true;
|
|
37
|
+
readonly excludes: ["backgroundColor"];
|
|
38
|
+
readonly attributes: {
|
|
39
|
+
readonly value: {
|
|
40
|
+
required: boolean;
|
|
41
|
+
validate: (value: unknown) => boolean;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
}, {
|
|
45
|
+
readonly type: "fontSize";
|
|
46
|
+
readonly inclusive: true;
|
|
47
|
+
readonly excludes: ["fontSize"];
|
|
48
|
+
readonly attributes: {
|
|
49
|
+
readonly valuePx: {
|
|
50
|
+
required: boolean;
|
|
51
|
+
validate: (value: unknown) => boolean;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}, {
|
|
55
|
+
readonly type: "fontFamily";
|
|
56
|
+
readonly inclusive: true;
|
|
57
|
+
readonly excludes: ["fontFamily"];
|
|
58
|
+
readonly attributes: {
|
|
59
|
+
readonly value: {
|
|
60
|
+
required: boolean;
|
|
61
|
+
validate: (value: unknown) => boolean;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}, {
|
|
65
|
+
readonly type: "link";
|
|
66
|
+
readonly inclusive: false;
|
|
67
|
+
readonly excludes: ["link"];
|
|
68
|
+
readonly attributes: {
|
|
69
|
+
readonly href: {
|
|
70
|
+
required: boolean;
|
|
71
|
+
validate: (value: unknown) => boolean;
|
|
72
|
+
};
|
|
73
|
+
readonly target: {
|
|
74
|
+
readonly validate: (value: unknown) => boolean;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
}, {
|
|
78
|
+
readonly type: "suggestion";
|
|
79
|
+
readonly inclusive: false;
|
|
80
|
+
readonly attributes: {
|
|
81
|
+
readonly id: {
|
|
82
|
+
required: boolean;
|
|
83
|
+
validate: (value: unknown) => boolean;
|
|
84
|
+
};
|
|
85
|
+
readonly authorId: {
|
|
86
|
+
required: boolean;
|
|
87
|
+
validate: (value: unknown) => boolean;
|
|
88
|
+
};
|
|
89
|
+
readonly kind: {
|
|
90
|
+
readonly required: true;
|
|
91
|
+
readonly validate: (value: unknown) => value is "insert" | "delete";
|
|
92
|
+
};
|
|
93
|
+
readonly createdAt: {
|
|
94
|
+
required: boolean;
|
|
95
|
+
validate: (value: unknown) => boolean;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
}];
|
|
99
|
+
/** A hard break is one inline cursor unit and never carries marks itself. */
|
|
100
|
+
export declare const hardBreakNodeSpec: {
|
|
101
|
+
readonly type: "hard_break";
|
|
102
|
+
readonly group: "inline";
|
|
103
|
+
readonly atomic: true;
|
|
104
|
+
readonly selectable: false;
|
|
105
|
+
readonly marks: "";
|
|
106
|
+
};
|
|
107
|
+
export declare const inlineToolDeclarations: readonly [{
|
|
108
|
+
readonly id: "bold";
|
|
109
|
+
readonly markType: "bold";
|
|
110
|
+
readonly inclusive: true;
|
|
111
|
+
}, {
|
|
112
|
+
readonly id: "italic";
|
|
113
|
+
readonly markType: "italic";
|
|
114
|
+
readonly inclusive: true;
|
|
115
|
+
}, {
|
|
116
|
+
readonly id: "underline";
|
|
117
|
+
readonly markType: "underline";
|
|
118
|
+
readonly inclusive: true;
|
|
119
|
+
}, {
|
|
120
|
+
readonly id: "strikethrough";
|
|
121
|
+
readonly markType: "strike";
|
|
122
|
+
readonly inclusive: true;
|
|
123
|
+
}, {
|
|
124
|
+
readonly id: "inlineCode";
|
|
125
|
+
readonly markType: "code";
|
|
126
|
+
readonly inclusive: true;
|
|
127
|
+
}, {
|
|
128
|
+
readonly id: "superscript";
|
|
129
|
+
readonly markType: "superscript";
|
|
130
|
+
readonly inclusive: true;
|
|
131
|
+
readonly excludes: readonly ["subscript"];
|
|
132
|
+
}, {
|
|
133
|
+
readonly id: "subscript";
|
|
134
|
+
readonly markType: "subscript";
|
|
135
|
+
readonly inclusive: true;
|
|
136
|
+
readonly excludes: readonly ["superscript"];
|
|
137
|
+
}, {
|
|
138
|
+
readonly id: "textColor";
|
|
139
|
+
readonly markType: "textColor";
|
|
140
|
+
readonly inclusive: true;
|
|
141
|
+
readonly excludes: readonly ["textColor"];
|
|
142
|
+
readonly validate: (attrs: Readonly<Record<string, unknown>> | undefined) => boolean;
|
|
143
|
+
}, {
|
|
144
|
+
readonly id: "backgroundColor";
|
|
145
|
+
readonly markType: "backgroundColor";
|
|
146
|
+
readonly inclusive: true;
|
|
147
|
+
readonly excludes: readonly ["backgroundColor"];
|
|
148
|
+
readonly validate: (attrs: Readonly<Record<string, unknown>> | undefined) => boolean;
|
|
149
|
+
}, {
|
|
150
|
+
readonly id: "fontSize";
|
|
151
|
+
readonly markType: "fontSize";
|
|
152
|
+
readonly inclusive: true;
|
|
153
|
+
readonly excludes: readonly ["fontSize"];
|
|
154
|
+
readonly validate: (attrs: Readonly<Record<string, unknown>> | undefined) => boolean;
|
|
155
|
+
}, {
|
|
156
|
+
readonly id: "fontFamily";
|
|
157
|
+
readonly markType: "fontFamily";
|
|
158
|
+
readonly inclusive: true;
|
|
159
|
+
readonly excludes: readonly ["fontFamily"];
|
|
160
|
+
readonly validate: (attrs: Readonly<Record<string, unknown>> | undefined) => boolean;
|
|
161
|
+
}, {
|
|
162
|
+
readonly id: "link";
|
|
163
|
+
readonly markType: "link";
|
|
164
|
+
readonly inclusive: false;
|
|
165
|
+
readonly excludes: readonly ["link"];
|
|
166
|
+
readonly validate: (attrs: Readonly<Record<string, unknown>> | undefined) => boolean;
|
|
167
|
+
}];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { normalizeLinkInput } from "../security/urlPolicy.js";
|
|
2
|
+
import { canonicalColor, canonicalFontFamily, canonicalFontSize } from "./canonical.js";
|
|
3
|
+
const stringValue = { required: true, validate: (value) => typeof value === "string" && Boolean(value) };
|
|
4
|
+
const numberValue = { required: true, validate: (value) => typeof value === "number" && Number.isFinite(value) };
|
|
5
|
+
export const inlineMarkSpecs = [
|
|
6
|
+
{ type: "bold", inclusive: true },
|
|
7
|
+
{ type: "italic", inclusive: true },
|
|
8
|
+
{ type: "underline", inclusive: true },
|
|
9
|
+
{ type: "strike", inclusive: true },
|
|
10
|
+
{ type: "code", inclusive: true },
|
|
11
|
+
{ type: "superscript", inclusive: true, excludes: ["subscript"] },
|
|
12
|
+
{ type: "subscript", inclusive: true, excludes: ["superscript"] },
|
|
13
|
+
{ type: "textColor", inclusive: true, excludes: ["textColor"], attributes: { value: stringValue } },
|
|
14
|
+
{ type: "backgroundColor", inclusive: true, excludes: ["backgroundColor"], attributes: { value: stringValue } },
|
|
15
|
+
{ type: "fontSize", inclusive: true, excludes: ["fontSize"], attributes: { valuePx: numberValue } },
|
|
16
|
+
{ type: "fontFamily", inclusive: true, excludes: ["fontFamily"], attributes: { value: stringValue } },
|
|
17
|
+
{ type: "link", inclusive: false, excludes: ["link"], attributes: {
|
|
18
|
+
href: stringValue,
|
|
19
|
+
target: { validate: (value) => typeof value === "string" && Boolean(value) },
|
|
20
|
+
} },
|
|
21
|
+
// Phase 12a §2.3: a proposed inline insert/delete, applied to real
|
|
22
|
+
// document text like any other mark rather than a separate not-yet-
|
|
23
|
+
// applied record. Not inclusive - typing at a suggestion's boundary
|
|
24
|
+
// should not silently extend someone else's (or an already-decided)
|
|
25
|
+
// proposal.
|
|
26
|
+
{ type: "suggestion", inclusive: false, attributes: {
|
|
27
|
+
id: stringValue,
|
|
28
|
+
authorId: stringValue,
|
|
29
|
+
kind: { required: true, validate: (value) => value === "insert" || value === "delete" },
|
|
30
|
+
createdAt: numberValue,
|
|
31
|
+
} },
|
|
32
|
+
];
|
|
33
|
+
/** A hard break is one inline cursor unit and never carries marks itself. */
|
|
34
|
+
export const hardBreakNodeSpec = {
|
|
35
|
+
type: "hard_break", group: "inline", atomic: true, selectable: false, marks: "",
|
|
36
|
+
};
|
|
37
|
+
export const inlineToolDeclarations = [
|
|
38
|
+
{ id: "bold", markType: "bold", inclusive: true },
|
|
39
|
+
{ id: "italic", markType: "italic", inclusive: true },
|
|
40
|
+
{ id: "underline", markType: "underline", inclusive: true },
|
|
41
|
+
{ id: "strikethrough", markType: "strike", inclusive: true },
|
|
42
|
+
{ id: "inlineCode", markType: "code", inclusive: true },
|
|
43
|
+
{ id: "superscript", markType: "superscript", inclusive: true, excludes: ["subscript"] },
|
|
44
|
+
{ id: "subscript", markType: "subscript", inclusive: true, excludes: ["superscript"] },
|
|
45
|
+
{ id: "textColor", markType: "textColor", inclusive: true, excludes: ["textColor"], validate: (attrs) => canonicalColor(attrs?.value) !== null },
|
|
46
|
+
{ id: "backgroundColor", markType: "backgroundColor", inclusive: true, excludes: ["backgroundColor"], validate: (attrs) => canonicalColor(attrs?.value) !== null },
|
|
47
|
+
{ id: "fontSize", markType: "fontSize", inclusive: true, excludes: ["fontSize"], validate: (attrs) => canonicalFontSize(attrs?.valuePx) !== null },
|
|
48
|
+
{ id: "fontFamily", markType: "fontFamily", inclusive: true, excludes: ["fontFamily"], validate: (attrs) => canonicalFontFamily(attrs?.value) !== null },
|
|
49
|
+
{ id: "link", markType: "link", inclusive: false, excludes: ["link"], validate: (attrs) => normalizeLinkInput(String(attrs?.href || "")).href !== null },
|
|
50
|
+
];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isTextNode } from "../identity.js";
|
|
2
|
+
import { nodeAtPath } from "../positions.js";
|
|
3
|
+
import { canonicalMarkOrder } from "./canonical.js";
|
|
4
|
+
export const marksAtInsertion = (document, pos, schema) => {
|
|
5
|
+
const owner = nodeAtPath(document, pos.path);
|
|
6
|
+
if (!owner || isTextNode(owner))
|
|
7
|
+
return [];
|
|
8
|
+
let offset = 0;
|
|
9
|
+
const segments = (owner.children || []).map((node) => {
|
|
10
|
+
const from = offset;
|
|
11
|
+
offset += isTextNode(node) ? node.text.length : 1;
|
|
12
|
+
return { node, from, to: offset };
|
|
13
|
+
});
|
|
14
|
+
const inside = segments.find((segment) => isTextNode(segment.node) && segment.from < pos.offset && pos.offset < segment.to);
|
|
15
|
+
if (inside && isTextNode(inside.node))
|
|
16
|
+
return canonicalMarkOrder(inside.node.marks);
|
|
17
|
+
const candidates = [
|
|
18
|
+
[...segments].reverse().find((segment) => isTextNode(segment.node) && segment.to === pos.offset),
|
|
19
|
+
segments.find((segment) => isTextNode(segment.node) && segment.from === pos.offset),
|
|
20
|
+
];
|
|
21
|
+
const marks = new Map();
|
|
22
|
+
candidates.forEach((segment) => {
|
|
23
|
+
if (!segment || !isTextNode(segment.node))
|
|
24
|
+
return;
|
|
25
|
+
segment.node.marks?.forEach((mark) => {
|
|
26
|
+
if (schema.marks[mark.type]?.inclusive !== false)
|
|
27
|
+
marks.set(mark.type, mark);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
return canonicalMarkOrder([...marks.values()]);
|
|
31
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ResolvedScope } from "../scope/types.js";
|
|
2
|
+
import type { Attrs, SmartDocument, SmartOperation, SmartSchema } from "../types.js";
|
|
3
|
+
import type { PositionLookup } from "../scope/types.js";
|
|
4
|
+
export interface MarkCommandContext {
|
|
5
|
+
readonly schema: SmartSchema;
|
|
6
|
+
readonly positions: PositionLookup;
|
|
7
|
+
}
|
|
8
|
+
export type MarkCommand<P> = (document: SmartDocument, scope: ResolvedScope, params: P, ctx: MarkCommandContext) => SmartOperation[];
|
|
9
|
+
export interface MarkApplyParams {
|
|
10
|
+
readonly markType: string;
|
|
11
|
+
readonly attrs?: Attrs;
|
|
12
|
+
}
|
|
13
|
+
export interface MarkRemoveParams {
|
|
14
|
+
readonly markType: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MarkToggleParams extends MarkApplyParams {
|
|
17
|
+
/** Supplied from Phase 2 describe(); commands do not recompute coverage. */
|
|
18
|
+
readonly coverage: "all" | "partial" | "none";
|
|
19
|
+
}
|
|
20
|
+
export interface MarkSetAttrsParams extends MarkApplyParams {
|
|
21
|
+
}
|
|
22
|
+
export interface MarkClearAllParams {
|
|
23
|
+
}
|
|
24
|
+
export interface LinkEditParams {
|
|
25
|
+
readonly href: string;
|
|
26
|
+
readonly target?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface InlineToolDeclaration {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly markType: string;
|
|
31
|
+
readonly inclusive: boolean;
|
|
32
|
+
readonly excludes?: readonly string[];
|
|
33
|
+
readonly validate?: (attrs: Attrs | undefined) => boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface MarkApplicationReport {
|
|
36
|
+
readonly ownerCount: number;
|
|
37
|
+
readonly ownerIdsSkipped: readonly string[];
|
|
38
|
+
readonly atomOwnersSkipped: readonly string[];
|
|
39
|
+
readonly partial: boolean;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|