@stll/folio-core 0.55.0 → 0.57.0
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/dist/ai-edits/apply.js +214 -52
- package/dist/ai-edits/batch-claims.d.ts +3 -1
- package/dist/ai-edits/batch-claims.js +25 -1
- package/dist/ai-edits/headless.js +2 -2
- package/dist/ai-edits/table-row-column-mutations.js +17 -3
- package/dist/ai-edits/types.d.ts +7 -1
- package/dist/ai-suggestions/apply.js +20 -1
- package/dist/compare/compare.js +1 -0
- package/dist/compare/inline-atoms.js +40 -16
- package/dist/compare/inline-provenance.js +12 -5
- package/dist/compare/reproducible-package.d.ts +0 -17
- package/dist/compare/reproducible-package.js +12 -18
- package/dist/compat/eigenpal.d.ts +2 -1
- package/dist/compat/eigenpal.js +2 -1
- package/dist/controller/canonicalClipboard.d.ts +23 -0
- package/dist/controller/canonicalClipboard.js +482 -0
- package/dist/controller/canonicalClipboardResources.d.ts +21 -0
- package/dist/controller/canonicalClipboardResources.js +382 -0
- package/dist/controller/canonicalComments.d.ts +41 -0
- package/dist/controller/canonicalComments.js +152 -0
- package/dist/controller/canonicalComposition.d.ts +24 -3
- package/dist/controller/canonicalComposition.js +208 -48
- package/dist/controller/canonicalInlineProjection.d.ts +118 -0
- package/dist/controller/canonicalInlineProjection.js +476 -0
- package/dist/controller/canonicalInput.d.ts +13 -4
- package/dist/controller/canonicalInput.js +62 -14
- package/dist/controller/canonicalInputTimer.d.ts +16 -0
- package/dist/controller/canonicalInputTimer.js +34 -0
- package/dist/controller/canonicalPublicOperations.d.ts +48 -0
- package/dist/controller/canonicalPublicOperations.js +648 -0
- package/dist/controller/canonicalSelection.d.ts +18 -0
- package/dist/controller/canonicalSelection.js +32 -0
- package/dist/controller/canonicalSession.d.ts +27 -6
- package/dist/controller/canonicalSession.js +298 -116
- package/dist/controller/canonicalStoryEditor.d.ts +7 -3
- package/dist/controller/canonicalStoryEditor.js +27 -2
- package/dist/controller/canonicalStructure.js +57 -4
- package/dist/controller/canonicalTocSelection.d.ts +11 -0
- package/dist/controller/canonicalTocSelection.js +43 -0
- package/dist/controller/folioEditor.d.ts +2 -9
- package/dist/controller/folioEditor.js +18 -4
- package/dist/controller/headerFooterEditorManager.d.ts +2 -1
- package/dist/controller/headerFooterEditorManager.js +26 -11
- package/dist/controller/hiddenEditorApi.d.ts +24 -3
- package/dist/controller/hiddenEditorApi.js +8 -0
- package/dist/controller/hiddenEditorManager.d.ts +5 -1
- package/dist/controller/hiddenEditorManager.js +378 -43
- package/dist/controller/noteEditorManager.d.ts +2 -1
- package/dist/controller/noteEditorManager.js +29 -13
- package/dist/document-operations.d.ts +8 -1
- package/dist/document-operations.js +6 -4
- package/dist/docx/blockPlainText.d.ts +1 -1
- package/dist/docx/blockPlainText.js +11 -3
- package/dist/docx/builtInStyles.js +13 -6
- package/dist/docx/canonicalResourceSave.d.ts +16 -0
- package/dist/docx/canonicalResourceSave.js +27 -0
- package/dist/docx/canonicalSave.d.ts +29 -0
- package/dist/docx/canonicalSave.js +101 -0
- package/dist/docx/canonicalStoryRepack.d.ts +1 -1
- package/dist/docx/canonicalStoryRepack.js +38 -5
- package/dist/docx/containerChildren.gen.d.ts +3 -1
- package/dist/docx/containerChildren.gen.js +33 -1
- package/dist/docx/documentParser.d.ts +1 -13
- package/dist/docx/documentParser.js +1 -47
- package/dist/docx/ensureParaIds.js +10 -4
- package/dist/docx/fieldParser.js +14 -2
- package/dist/docx/footnoteParser.d.ts +1 -11
- package/dist/docx/footnoteParser.js +1 -16
- package/dist/docx/headerFooterParser.d.ts +1 -9
- package/dist/docx/headerFooterParser.js +1 -12
- package/dist/docx/headerFooterVerbatim.d.ts +26 -1
- package/dist/docx/headerFooterVerbatim.js +81 -3
- package/dist/docx/hyperlinkParser.d.ts +1 -1
- package/dist/docx/hyperlinkParser.js +20 -1
- package/dist/docx/imageParser.d.ts +13 -1
- package/dist/docx/imageParser.js +20 -11
- package/dist/docx/inlineWrapperContent.d.ts +4 -4
- package/dist/docx/inlineWrapperContent.js +4 -4
- package/dist/docx/linkedSdtPreservation.d.ts +10 -0
- package/dist/docx/linkedSdtPreservation.js +37 -0
- package/dist/docx/metadataPrivacy.js +6 -1
- package/dist/docx/packageParts.js +9 -3
- package/dist/docx/paragraphParser.js +88 -67
- package/dist/docx/paragraphPropertySource.d.ts +22 -1
- package/dist/docx/paragraphPropertySource.js +109 -2
- package/dist/docx/parser.js +2 -1
- package/dist/docx/preservedRunContent.js +2 -1
- package/dist/docx/relsParser.js +26 -2
- package/dist/docx/removeHeaderFooterParts.js +17 -6
- package/dist/docx/rezip.d.ts +49 -5
- package/dist/docx/rezip.js +754 -235
- package/dist/docx/saveDiagnostics.d.ts +11 -0
- package/dist/docx/selectiveSave.d.ts +2 -1
- package/dist/docx/selectiveSave.js +42 -11
- package/dist/docx/selectiveXmlPatch.js +7 -5
- package/dist/docx/serializer/commentSerializer.d.ts +4 -1
- package/dist/docx/serializer/commentSerializer.js +7 -3
- package/dist/docx/serializer/documentSerializer.d.ts +9 -2
- package/dist/docx/serializer/documentSerializer.js +29 -10
- package/dist/docx/serializer/paragraphSerializer.js +207 -94
- package/dist/docx/server/applyDocxXmlPatchProposal.js +14 -9
- package/dist/docx/server/materializeYjsDocx.js +14 -6
- package/dist/docx/server/migrateYjsAttrSchema.js +17 -5
- package/dist/docx/storyBlockReplay.d.ts +21 -1
- package/dist/docx/storyBlockReplay.js +113 -9
- package/dist/docx/storyPlainText.d.ts +20 -0
- package/dist/docx/storyPlainText.js +23 -0
- package/dist/docx/structuralXmlPatch.js +10 -22
- package/dist/docx/styleParser.d.ts +5 -1
- package/dist/docx/styleParser.js +1 -1
- package/dist/docx/tableParser.d.ts +1 -8
- package/dist/docx/tableParser.js +1 -28
- package/dist/docx/textBoxParser.d.ts +1 -5
- package/dist/docx/textBoxParser.js +1 -20
- package/dist/docx/unzip.js +6 -1
- package/dist/i18n/messages/ar.gen.js +4 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +68 -0
- package/dist/i18n/messages/catalogs.gen.js +68 -0
- package/dist/i18n/messages/cs.gen.js +4 -0
- package/dist/i18n/messages/de.gen.js +4 -0
- package/dist/i18n/messages/en.gen.js +4 -0
- package/dist/i18n/messages/es.gen.js +4 -0
- package/dist/i18n/messages/et.gen.js +4 -0
- package/dist/i18n/messages/fr.gen.js +4 -0
- package/dist/i18n/messages/he.gen.js +4 -0
- package/dist/i18n/messages/hi.gen.js +4 -0
- package/dist/i18n/messages/hu.gen.js +4 -0
- package/dist/i18n/messages/lt.gen.js +4 -0
- package/dist/i18n/messages/lv.gen.js +4 -0
- package/dist/i18n/messages/messages.gen.d.ts +4 -0
- package/dist/i18n/messages/pl.gen.js +4 -0
- package/dist/i18n/messages/pt-BR.gen.js +4 -0
- package/dist/i18n/messages/sk.gen.js +4 -0
- package/dist/i18n/messages/tr.gen.js +4 -0
- package/dist/i18n/messages/zh-CN.gen.js +4 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal/acceptedBlockProjection.d.ts +12 -0
- package/dist/internal/acceptedBlockProjection.js +45 -0
- package/dist/internal/revisionResolutionInline.js +25 -3
- package/dist/managers/DocumentLoaderManager.js +2 -1
- package/dist/markdown/escape.d.ts +3 -1
- package/dist/markdown/escape.js +9 -2
- package/dist/markdown/renderBlock.js +5 -120
- package/dist/markdown/renderParagraph.js +4 -2
- package/dist/markdown/renderRuns.d.ts +7 -1
- package/dist/markdown/renderRuns.js +33 -24
- package/dist/panel-layout.d.ts +11 -9
- package/dist/panel-layout.js +13 -9
- package/dist/prosemirror/addTrackedDeletionMark.d.ts +18 -0
- package/dist/prosemirror/addTrackedDeletionMark.js +87 -0
- package/dist/prosemirror/attrs/index.js +5 -0
- package/dist/prosemirror/canonicalCommands.d.ts +28 -4
- package/dist/prosemirror/canonicalCommands.js +11 -4
- package/dist/prosemirror/canonicalSelectionRange.d.ts +9 -0
- package/dist/prosemirror/canonicalSelectionRange.js +15 -0
- package/dist/prosemirror/clipboardIntent.d.ts +7 -0
- package/dist/prosemirror/clipboardIntent.js +9 -0
- package/dist/prosemirror/commands/comments.js +60 -35
- package/dist/prosemirror/commands/pageBreak.js +2 -2
- package/dist/prosemirror/commands/pastePlainText.js +2 -1
- package/dist/prosemirror/commands/propertyChangeScope.js +3 -6
- package/dist/prosemirror/commands/resolveParagraphProperties.js +22 -13
- package/dist/prosemirror/commentOps.js +15 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +22 -3
- package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +8 -0
- package/dist/prosemirror/conversion/hyphenTextCarriers.js +8 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -1
- package/dist/prosemirror/conversion/toProseDoc.js +124 -22
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +61 -35
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +74 -25
- package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +11 -0
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +65 -29
- package/dist/prosemirror/extensions/marks/markUtils.js +11 -8
- package/dist/prosemirror/extensions/marks/noteReferenceDeletion.js +5 -2
- package/dist/prosemirror/fieldRepresentation.d.ts +8 -0
- package/dist/prosemirror/fieldRepresentation.js +18 -0
- package/dist/prosemirror/hyperlinkRemoval.d.ts +13 -1
- package/dist/prosemirror/hyperlinkRemoval.js +28 -9
- package/dist/prosemirror/insertOperations.js +2 -1
- package/dist/prosemirror/listNumbering.js +11 -5
- package/dist/prosemirror/noteReferenceOccurrenceId.d.ts +5 -0
- package/dist/prosemirror/noteReferenceOccurrenceId.js +5 -0
- package/dist/prosemirror/noteReferenceOccurrences.d.ts +21 -0
- package/dist/prosemirror/noteReferenceOccurrences.js +185 -0
- package/dist/prosemirror/noteReferenceReview.js +14 -1
- package/dist/prosemirror/paragraphPropertyCarry.d.ts +7 -1
- package/dist/prosemirror/paragraphPropertyCarry.js +8 -3
- package/dist/prosemirror/plugins/documentStyles.d.ts +4 -1
- package/dist/prosemirror/plugins/documentStyles.js +17 -10
- package/dist/prosemirror/plugins/suggestionMode.js +48 -48
- package/dist/prosemirror/preservedXmlReview.d.ts +6 -0
- package/dist/prosemirror/preservedXmlReview.js +35 -0
- package/dist/prosemirror/runFormattingFromMarks.js +2 -2
- package/dist/prosemirror/runFormattingReconciliation.js +5 -4
- package/dist/prosemirror/runPropertyChangeResolution.d.ts +8 -1
- package/dist/prosemirror/runPropertyChangeResolution.js +24 -2
- package/dist/prosemirror/runStyleFormatting.d.ts +3 -1
- package/dist/prosemirror/runStyleFormatting.js +3 -1
- package/dist/prosemirror/schema/marks.d.ts +1 -0
- package/dist/prosemirror/schema/nodes.d.ts +2 -0
- package/dist/prosemirror/styles/paragraphStyleCascade.js +4 -1
- package/dist/prosemirror/styles/resolvedStyleAttrs.d.ts +15 -15
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +69 -28
- package/dist/prosemirror/textInput.d.ts +5 -1
- package/dist/prosemirror/textInput.js +3 -3
- package/dist/prosemirror/trackedRevisionPath.d.ts +5 -1
- package/dist/prosemirror/trackedRevisionPath.js +39 -1
- package/dist/prosemirror/validation.js +3 -5
- package/dist/prosemirror/yjsDocumentMetadata.d.ts +9 -3
- package/dist/prosemirror/yjsDocumentMetadata.js +37 -8
- package/dist/types/canonicalCapabilities.d.ts +201 -0
- package/dist/types/canonicalCapabilities.js +198 -0
- package/dist/types/canonicalComments.d.ts +51 -0
- package/dist/types/canonicalComments.js +0 -0
- package/dist/types/canonicalSave.d.ts +11 -0
- package/dist/types/canonicalSave.js +0 -0
- package/dist/types/canonicalSections.d.ts +12 -0
- package/dist/types/canonicalSections.js +0 -0
- package/dist/types/docxSerialization.d.ts +14 -0
- package/dist/types/docxSerialization.js +7 -0
- package/dist/utils/headingCollector.js +45 -4
- package/dist/utils/outlineDepth.d.ts +9 -0
- package/dist/utils/outlineDepth.js +9 -0
- package/package.json +4 -4
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { readNoteMarkerAttrs } from "../internal/noteMarkerAttrs.js";
|
|
2
|
+
import { readCommentMarkAttrs, readFootnoteRefMarkAttrs, readPreservedXmlAttrs } from "../prosemirror/attrs/index.js";
|
|
3
|
+
import { bookmarkMarkerFromAttrs, readBookmarkBoundaryAttrs } from "../prosemirror/bookmarkBoundaryAttrs.js";
|
|
4
|
+
import { HYPHEN_TEXT_CARRIERS } from "../prosemirror/conversion/hyphenTextCarriers.js";
|
|
5
|
+
import { readMoveRangeBoundaryAttrs } from "../prosemirror/moveRangeBoundaryAttrs.js";
|
|
6
|
+
import { readRangeAnchorAttrs } from "../prosemirror/rangeAnchorAttrs.js";
|
|
7
|
+
import { PRESERVED_XML_LEVELS } from "../prosemirror/schema/nodes.js";
|
|
8
|
+
import { Result, TaggedError, panic } from "better-result";
|
|
9
|
+
import { hasIllegalXmlCharacters } from "@stll/docx-core";
|
|
10
|
+
import { inlineLeafSpans, paragraphLogicalText } from "@stll/docx-core/ops";
|
|
11
|
+
//#region src/controller/canonicalInlineProjection.ts
|
|
12
|
+
const CANONICAL_SOURCE_INLINE_POLICIES = {
|
|
13
|
+
run: "container",
|
|
14
|
+
hyperlink: "container",
|
|
15
|
+
insertion: "container",
|
|
16
|
+
deletion: "container",
|
|
17
|
+
moveFrom: "container",
|
|
18
|
+
moveTo: "container",
|
|
19
|
+
inlineWrapper: "container",
|
|
20
|
+
inlineSdt: "control",
|
|
21
|
+
simpleField: "leaf",
|
|
22
|
+
complexField: "leaf",
|
|
23
|
+
mathEquation: "leaf",
|
|
24
|
+
preservedInline: "leaf",
|
|
25
|
+
bookmarkStart: "leaf",
|
|
26
|
+
bookmarkEnd: "leaf",
|
|
27
|
+
commentRangeStart: "leaf",
|
|
28
|
+
commentRangeEnd: "leaf",
|
|
29
|
+
commentReference: "leaf",
|
|
30
|
+
moveFromRangeStart: "leaf",
|
|
31
|
+
moveFromRangeEnd: "leaf",
|
|
32
|
+
moveToRangeStart: "leaf",
|
|
33
|
+
moveToRangeEnd: "leaf",
|
|
34
|
+
text: "text",
|
|
35
|
+
tab: "leaf",
|
|
36
|
+
break: "leaf",
|
|
37
|
+
symbol: "leaf",
|
|
38
|
+
footnoteRef: "leaf",
|
|
39
|
+
endnoteRef: "leaf",
|
|
40
|
+
noteMarker: "leaf",
|
|
41
|
+
fieldChar: "rawField",
|
|
42
|
+
instrText: "rawField",
|
|
43
|
+
softHyphen: "leaf",
|
|
44
|
+
noBreakHyphen: "leaf",
|
|
45
|
+
renderedPageBreak: "layout",
|
|
46
|
+
preservedXml: "leaf",
|
|
47
|
+
drawing: "leaf",
|
|
48
|
+
shape: "leaf"
|
|
49
|
+
};
|
|
50
|
+
const CANONICAL_NATIVE_INLINE_POLICIES = {
|
|
51
|
+
text: "text",
|
|
52
|
+
bookmarkBoundary: "markers",
|
|
53
|
+
moveRangeBoundary: "markers",
|
|
54
|
+
rangeAnchor: "markers",
|
|
55
|
+
noteMarker: "noteMarker",
|
|
56
|
+
preservedXml: "capture",
|
|
57
|
+
field: "unit",
|
|
58
|
+
structuredField: "unit",
|
|
59
|
+
hardBreak: "unit",
|
|
60
|
+
image: "unit",
|
|
61
|
+
math: "unit",
|
|
62
|
+
pageBreakRun: "unit",
|
|
63
|
+
shape: "unit",
|
|
64
|
+
symbol: "unit",
|
|
65
|
+
tab: "unit",
|
|
66
|
+
textBoxAnchor: "unit",
|
|
67
|
+
commentReference: "unit",
|
|
68
|
+
renderedPageBreak: "refused",
|
|
69
|
+
sdt: "refused"
|
|
70
|
+
};
|
|
71
|
+
const nativePolicies = new Map(Object.entries(CANONICAL_NATIVE_INLINE_POLICIES));
|
|
72
|
+
var InlineProjectionError = class extends TaggedError("InlineProjectionError") {};
|
|
73
|
+
const refuse = (message) => Result.err(new InlineProjectionError({ message }));
|
|
74
|
+
/** Every source variant makes a decision before conversion can discard its positions. */
|
|
75
|
+
const canonicalInlineSourceIssue = (items, context = "paragraph") => {
|
|
76
|
+
for (const item of items) switch (CANONICAL_SOURCE_INLINE_POLICIES[item.type]) {
|
|
77
|
+
case "control":
|
|
78
|
+
if (context === "fieldPayload" && item.type === "inlineSdt") {
|
|
79
|
+
const issue = canonicalInlineSourceIssue(item.content, context);
|
|
80
|
+
if (issue !== null) return issue;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
return "Canonical editing cannot preserve inline content-control boundary positions.";
|
|
84
|
+
case "rawField":
|
|
85
|
+
if (context === "fieldPayload") break;
|
|
86
|
+
return "Canonical editing cannot preserve unstructured field instructions; a structured field is required.";
|
|
87
|
+
case "layout": return "Canonical editing cannot preserve cached rendered page-break positions.";
|
|
88
|
+
case "text":
|
|
89
|
+
if (item.type === "text" && (hasIllegalXmlCharacters(item.text) || /[\t\r\n]/u.test(item.text))) return "Canonical text requires valid XML characters and explicit tab or break elements.";
|
|
90
|
+
break;
|
|
91
|
+
case "container": {
|
|
92
|
+
if ((item.type === "insertion" || item.type === "deletion") && item.resolutionJoins !== void 0 && item.content.some((child) => child.type === "hyperlink")) return "Canonical editing cannot preserve hyperlink wrapper review provenance.";
|
|
93
|
+
const children = sourceContainerChildren(item);
|
|
94
|
+
const issue = canonicalInlineSourceIssue(children, context);
|
|
95
|
+
if (issue !== null) return issue;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "leaf": {
|
|
99
|
+
let children = [];
|
|
100
|
+
if (item.type === "simpleField") children = item.content;
|
|
101
|
+
if (item.type === "complexField") children = [...item.fieldCode, ...item.fieldResult];
|
|
102
|
+
const issue = canonicalInlineSourceIssue(children, "fieldPayload");
|
|
103
|
+
if (issue !== null) return issue;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
default:
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
};
|
|
110
|
+
const sourceContainerChildren = (item) => {
|
|
111
|
+
switch (item.type) {
|
|
112
|
+
case "hyperlink": return item.children;
|
|
113
|
+
case "run":
|
|
114
|
+
case "insertion":
|
|
115
|
+
case "deletion":
|
|
116
|
+
case "moveFrom":
|
|
117
|
+
case "moveTo":
|
|
118
|
+
case "inlineWrapper": return item.content;
|
|
119
|
+
default: return panic(`Source policy incorrectly classified ${item.type} as a container.`);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/** Native positions and deletion geometry read the same inline-cell classification. */
|
|
123
|
+
const canonicalNativeCells = (paragraph) => {
|
|
124
|
+
const cells = [];
|
|
125
|
+
let issue;
|
|
126
|
+
paragraph.forEach((node, from) => {
|
|
127
|
+
if (issue !== void 0) return;
|
|
128
|
+
const to = from + node.nodeSize;
|
|
129
|
+
switch (nativePolicies.get(node.type.name)) {
|
|
130
|
+
case "text": {
|
|
131
|
+
const reference = node.marks.find(({ type }) => type.name === "footnoteRef");
|
|
132
|
+
if (!reference) {
|
|
133
|
+
cells.push({
|
|
134
|
+
type: "text",
|
|
135
|
+
text: node.text ?? "",
|
|
136
|
+
from,
|
|
137
|
+
to,
|
|
138
|
+
node
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const attrs = readFootnoteRefMarkAttrs(reference);
|
|
143
|
+
if (!attrs.ok) {
|
|
144
|
+
issue = "Canonical editing requires valid note-reference mark attributes.";
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const label = String(attrs.value.id);
|
|
148
|
+
const text = node.text ?? "";
|
|
149
|
+
if (label.length === 0 || text.length % label.length !== 0 || text !== label.repeat(text.length / label.length)) {
|
|
150
|
+
issue = "Canonical editing cannot map the rendered note-reference labels.";
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (let offset = 0; offset < text.length; offset += label.length) cells.push({
|
|
154
|
+
type: "markedUnit",
|
|
155
|
+
sourceType: attrs.value.noteType === "endnote" ? "endnoteRef" : "footnoteRef",
|
|
156
|
+
label,
|
|
157
|
+
from: from + offset,
|
|
158
|
+
to: from + offset + label.length,
|
|
159
|
+
node
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
case "unit":
|
|
164
|
+
cells.push({
|
|
165
|
+
type: "unit",
|
|
166
|
+
from,
|
|
167
|
+
to,
|
|
168
|
+
node
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
case "markers":
|
|
172
|
+
if (node.type.name === "bookmarkBoundary") {
|
|
173
|
+
const attrs = readBookmarkBoundaryAttrs(node);
|
|
174
|
+
if (!attrs.ok) {
|
|
175
|
+
issue = "Canonical editing requires valid bookmark boundary attributes.";
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
cells.push({
|
|
179
|
+
type: "zeroWidth",
|
|
180
|
+
sources: [bookmarkMarkerFromAttrs(attrs.value)],
|
|
181
|
+
from,
|
|
182
|
+
to,
|
|
183
|
+
node
|
|
184
|
+
});
|
|
185
|
+
} else if (node.type.name === "moveRangeBoundary") {
|
|
186
|
+
const attrs = readMoveRangeBoundaryAttrs(node);
|
|
187
|
+
if (!attrs.ok) {
|
|
188
|
+
issue = "Canonical editing requires valid move-range boundary attributes.";
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
cells.push({
|
|
192
|
+
type: "zeroWidth",
|
|
193
|
+
sources: [attrs.value],
|
|
194
|
+
from,
|
|
195
|
+
to,
|
|
196
|
+
node
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
const attrs = readRangeAnchorAttrs(node);
|
|
200
|
+
if (!attrs.ok) {
|
|
201
|
+
issue = "Canonical editing requires a valid collapsed range anchor.";
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
cells.push({
|
|
205
|
+
type: "zeroWidth",
|
|
206
|
+
sources: [attrs.value.start, attrs.value.end],
|
|
207
|
+
from,
|
|
208
|
+
to,
|
|
209
|
+
node
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
case "noteMarker": {
|
|
214
|
+
const attrs = readNoteMarkerAttrs(node);
|
|
215
|
+
if (!attrs.ok) {
|
|
216
|
+
issue = "Canonical editing requires a valid automatic note marker.";
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
cells.push({
|
|
220
|
+
type: "zeroWidth",
|
|
221
|
+
sources: [{
|
|
222
|
+
type: "noteMarker",
|
|
223
|
+
kind: attrs.value.kind
|
|
224
|
+
}],
|
|
225
|
+
from,
|
|
226
|
+
to,
|
|
227
|
+
node
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
case "capture": {
|
|
232
|
+
const attrs = readPreservedXmlAttrs(node);
|
|
233
|
+
if (!attrs.ok) {
|
|
234
|
+
issue = "Canonical editing requires valid preserved inline markup.";
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (attrs.value.level === PRESERVED_XML_LEVELS.inline && attrs.value.text === "") cells.push({
|
|
238
|
+
type: "zeroWidth",
|
|
239
|
+
sources: [{
|
|
240
|
+
type: "preservedInline",
|
|
241
|
+
xml: attrs.value.xml,
|
|
242
|
+
text: ""
|
|
243
|
+
}],
|
|
244
|
+
from,
|
|
245
|
+
to,
|
|
246
|
+
node
|
|
247
|
+
});
|
|
248
|
+
else cells.push({
|
|
249
|
+
type: "unit",
|
|
250
|
+
from,
|
|
251
|
+
to,
|
|
252
|
+
node
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
case "refused":
|
|
257
|
+
issue = `Canonical editing cannot preserve ${node.type.name} boundary positions.`;
|
|
258
|
+
return;
|
|
259
|
+
case void 0:
|
|
260
|
+
issue = `Canonical editing has no position mapping for inline ${node.type.name}.`;
|
|
261
|
+
return;
|
|
262
|
+
default:
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
return issue === void 0 ? Result.ok(cells) : refuse(issue);
|
|
266
|
+
};
|
|
267
|
+
const sameSourceMarker = (left, right) => {
|
|
268
|
+
switch (left.type) {
|
|
269
|
+
case "bookmarkStart":
|
|
270
|
+
case "bookmarkEnd":
|
|
271
|
+
case "commentRangeStart":
|
|
272
|
+
case "commentRangeEnd":
|
|
273
|
+
case "moveFromRangeStart":
|
|
274
|
+
case "moveFromRangeEnd":
|
|
275
|
+
case "moveToRangeStart":
|
|
276
|
+
case "moveToRangeEnd": return right.type === left.type && left.id === right.id;
|
|
277
|
+
case "noteMarker": return right.type === "noteMarker" && left.kind === right.kind;
|
|
278
|
+
case "preservedInline": return right.type === "preservedInline" && left.xml === right.xml && left.text === right.text;
|
|
279
|
+
default: return false;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
/** Marks carry nonempty ranges; explicit range anchors carry empty ranges. */
|
|
283
|
+
const commentMarkTransitions = ({ source, paragraph, commentContext }) => {
|
|
284
|
+
const spans = inlineLeafSpans(source.content);
|
|
285
|
+
const rangedIds = new Set(commentContext?.rangedIds);
|
|
286
|
+
const pointIds = new Set(commentContext?.pointIds);
|
|
287
|
+
const finalIds = new Set(commentContext?.openIds);
|
|
288
|
+
for (const { node } of spans) {
|
|
289
|
+
if (node.type === "commentRangeStart" || node.type === "commentRangeEnd") rangedIds.add(node.id);
|
|
290
|
+
if (node.type === "commentReference") pointIds.add(node.id);
|
|
291
|
+
if (node.type === "commentRangeStart") finalIds.add(node.id);
|
|
292
|
+
if (node.type === "commentRangeEnd") finalIds.delete(node.id);
|
|
293
|
+
}
|
|
294
|
+
const transitions = /* @__PURE__ */ new Map();
|
|
295
|
+
let previous = new Set(commentContext?.openIds);
|
|
296
|
+
let issue;
|
|
297
|
+
const record = (position, current) => {
|
|
298
|
+
const starts = new Set([...current].filter((id) => !previous.has(id)));
|
|
299
|
+
const ends = new Set([...previous].filter((id) => !current.has(id)));
|
|
300
|
+
if (starts.size > 0 || ends.size > 0) transitions.set(position, {
|
|
301
|
+
starts,
|
|
302
|
+
ends
|
|
303
|
+
});
|
|
304
|
+
previous = current;
|
|
305
|
+
};
|
|
306
|
+
paragraph.forEach((node, position) => {
|
|
307
|
+
if (issue !== void 0) return;
|
|
308
|
+
const current = /* @__PURE__ */ new Set();
|
|
309
|
+
for (const mark of node.marks) {
|
|
310
|
+
if (mark.type.name !== "comment") continue;
|
|
311
|
+
const attrs = readCommentMarkAttrs(mark);
|
|
312
|
+
if (!attrs.ok) {
|
|
313
|
+
issue = "Canonical editing requires valid comment mark attributes.";
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const id = attrs.value.commentId;
|
|
317
|
+
if (rangedIds.has(id)) current.add(id);
|
|
318
|
+
else if (!pointIds.has(id)) {
|
|
319
|
+
issue = "Canonical editing cannot attribute a comment mark to its source.";
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
record(position, current);
|
|
324
|
+
});
|
|
325
|
+
record(paragraph.content.size, finalIds);
|
|
326
|
+
return issue === void 0 ? Result.ok(transitions) : refuse(issue);
|
|
327
|
+
};
|
|
328
|
+
/** Bind rendered gaps to the operation owner's source leaf ordinals, including collapsed pairs. */
|
|
329
|
+
const projectCanonicalInline = ({ source, paragraph, pairedBookmarkIds, commentContext }) => {
|
|
330
|
+
const native = canonicalNativeCells(paragraph);
|
|
331
|
+
if (native.isErr()) return native;
|
|
332
|
+
const comments = commentMarkTransitions({
|
|
333
|
+
source,
|
|
334
|
+
paragraph,
|
|
335
|
+
...commentContext === void 0 ? {} : { commentContext }
|
|
336
|
+
});
|
|
337
|
+
if (comments.isErr()) return comments;
|
|
338
|
+
const spans = inlineLeafSpans(source.content);
|
|
339
|
+
const erasedBookmarks = /* @__PURE__ */ new Set();
|
|
340
|
+
const collectErasedBookmarks = (items, context) => {
|
|
341
|
+
for (const item of items) {
|
|
342
|
+
if (item.type === "bookmarkStart" || item.type === "bookmarkEnd") {
|
|
343
|
+
if (context === "paragraph" && !pairedBookmarkIds.has(item.id)) erasedBookmarks.add(item);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (CANONICAL_SOURCE_INLINE_POLICIES[item.type] !== "container") continue;
|
|
347
|
+
collectErasedBookmarks(sourceContainerChildren(item), item.type === "run" || item.type === "inlineWrapper" ? context : "nested");
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
collectErasedBookmarks(source.content, "paragraph");
|
|
351
|
+
const markedCommentIds = /* @__PURE__ */ new Set();
|
|
352
|
+
paragraph.descendants((node) => {
|
|
353
|
+
for (const mark of node.marks) {
|
|
354
|
+
if (mark.type.name !== "comment") continue;
|
|
355
|
+
const attrs = readCommentMarkAttrs(mark);
|
|
356
|
+
if (attrs.ok) markedCommentIds.add(attrs.value.commentId);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
const commentRangeOffsets = /* @__PURE__ */ new Map();
|
|
360
|
+
for (const { node, before } of spans) {
|
|
361
|
+
if (node.type !== "commentRangeStart" && node.type !== "commentRangeEnd") continue;
|
|
362
|
+
const offsets = commentRangeOffsets.get(node.id) ?? {};
|
|
363
|
+
if (node.type === "commentRangeStart") offsets.start = before.offset;
|
|
364
|
+
else offsets.end = before.offset;
|
|
365
|
+
commentRangeOffsets.set(node.id, offsets);
|
|
366
|
+
}
|
|
367
|
+
const erasedCommentMarkers = /* @__PURE__ */ new Set();
|
|
368
|
+
for (const { node } of spans) {
|
|
369
|
+
if (node.type !== "commentRangeStart" && node.type !== "commentRangeEnd") continue;
|
|
370
|
+
const offsets = commentRangeOffsets.get(node.id);
|
|
371
|
+
if (!(offsets?.start !== void 0 && offsets.end !== void 0 && offsets.start === offsets.end) && markedCommentIds.has(node.id)) erasedCommentMarkers.add(node);
|
|
372
|
+
}
|
|
373
|
+
const text = paragraphLogicalText(source);
|
|
374
|
+
const boundaries = Array.from({ length: text.length + 1 }, () => []);
|
|
375
|
+
let index = 0;
|
|
376
|
+
let consumed = 0;
|
|
377
|
+
const addGap = (position, gap) => {
|
|
378
|
+
const gaps = boundaries.at(gap.offset);
|
|
379
|
+
if (!gaps) panic(`Source leaf offset ${gap.offset} exceeds paragraph ${source.paraId}.`);
|
|
380
|
+
const existing = gaps.findIndex((entry) => entry.position === position);
|
|
381
|
+
const entry = {
|
|
382
|
+
position,
|
|
383
|
+
zeroWidthBefore: gap.zeroWidthBefore
|
|
384
|
+
};
|
|
385
|
+
if (existing < 0) gaps.push(entry);
|
|
386
|
+
else gaps[existing] = entry;
|
|
387
|
+
};
|
|
388
|
+
const consumeErasedLeaves = (position) => {
|
|
389
|
+
while (consumed === 0) {
|
|
390
|
+
const span = spans.at(index);
|
|
391
|
+
if (position !== null && span && (span.node.type === "commentRangeStart" || span.node.type === "commentRangeEnd")) {
|
|
392
|
+
const transition = comments.value.get(position);
|
|
393
|
+
if ((span.node.type === "commentRangeStart" ? transition?.starts : transition?.ends)?.delete(span.node.id)) {
|
|
394
|
+
index += 1;
|
|
395
|
+
addGap(position, span.after);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (!span || span.before.offset !== span.after.offset || !(erasedBookmarks.has(span.node) || erasedCommentMarkers.has(span.node) || CANONICAL_SOURCE_INLINE_POLICIES[span.node.type] === "container" && sourceContainerChildren(span.node).length === 0)) break;
|
|
400
|
+
index += 1;
|
|
401
|
+
if (position !== null) addGap(position, span.after);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
addGap(0, {
|
|
405
|
+
offset: 0,
|
|
406
|
+
zeroWidthBefore: 0
|
|
407
|
+
});
|
|
408
|
+
for (const cell of native.value) {
|
|
409
|
+
consumeErasedLeaves(cell.from);
|
|
410
|
+
let span = spans.at(index);
|
|
411
|
+
if (!span) return refuse(`Canonical editing cannot map native ${cell.node.type.name} to a source leaf.`);
|
|
412
|
+
addGap(cell.from, consumed === 0 ? span.before : {
|
|
413
|
+
offset: span.before.offset + consumed,
|
|
414
|
+
zeroWidthBefore: 0
|
|
415
|
+
});
|
|
416
|
+
switch (cell.type) {
|
|
417
|
+
case "zeroWidth":
|
|
418
|
+
for (const marker of cell.sources) {
|
|
419
|
+
consumeErasedLeaves(null);
|
|
420
|
+
span = spans.at(index);
|
|
421
|
+
if (!span || consumed !== 0 || span.before.offset !== span.after.offset || !sameSourceMarker(span.node, marker)) return refuse(`Canonical editing cannot preserve ${marker.type} source boundary positions.`);
|
|
422
|
+
index += 1;
|
|
423
|
+
}
|
|
424
|
+
if (!span) return refuse("Canonical editing lost a source range boundary.");
|
|
425
|
+
addGap(cell.to, span.after);
|
|
426
|
+
break;
|
|
427
|
+
case "unit":
|
|
428
|
+
case "markedUnit":
|
|
429
|
+
if (cell.type === "markedUnit" && (span.node.type !== "footnoteRef" && span.node.type !== "endnoteRef" || span.node.type !== cell.sourceType || String(span.node.id) !== cell.label)) return refuse("Canonical editing cannot preserve note-reference identity.");
|
|
430
|
+
if (consumed !== 0 || span.node.type === "text" || span.after.offset - span.before.offset !== 1) return refuse(`Canonical editing cannot map ${span.node.type} to one native inline unit.`);
|
|
431
|
+
index += 1;
|
|
432
|
+
addGap(cell.to, span.after);
|
|
433
|
+
break;
|
|
434
|
+
case "text":
|
|
435
|
+
for (let unit = 0; unit < cell.text.length; unit += 1) {
|
|
436
|
+
consumeErasedLeaves(cell.from + unit);
|
|
437
|
+
span = spans.at(index);
|
|
438
|
+
if (!span) return refuse("Canonical editing cannot map missing source text to native text positions.");
|
|
439
|
+
let value;
|
|
440
|
+
switch (span.node.type) {
|
|
441
|
+
case "text":
|
|
442
|
+
value = span.node.text;
|
|
443
|
+
break;
|
|
444
|
+
case "softHyphen":
|
|
445
|
+
case "noBreakHyphen":
|
|
446
|
+
value = HYPHEN_TEXT_CARRIERS[span.node.type];
|
|
447
|
+
break;
|
|
448
|
+
default: return refuse(`Canonical editing cannot map ${span.node.type} to native text positions.`);
|
|
449
|
+
}
|
|
450
|
+
if (value.charAt(consumed) !== cell.text.charAt(unit)) return refuse("Canonical editing cannot preserve the source text carrier.");
|
|
451
|
+
consumed += 1;
|
|
452
|
+
addGap(cell.from + unit + 1, {
|
|
453
|
+
offset: span.before.offset + consumed,
|
|
454
|
+
zeroWidthBefore: 0
|
|
455
|
+
});
|
|
456
|
+
if (consumed === value.length) {
|
|
457
|
+
index += 1;
|
|
458
|
+
consumed = 0;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
break;
|
|
462
|
+
default:
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
consumeErasedLeaves(paragraph.content.size);
|
|
466
|
+
if ([...comments.value.values()].some(({ starts, ends }) => starts.size > 0 || ends.size > 0)) return refuse("Canonical editing cannot attribute a comment mark transition to its source.");
|
|
467
|
+
const missing = spans.at(index);
|
|
468
|
+
if (missing || consumed !== 0) return refuse(`Canonical editing cannot display the source positions of ${missing?.node.type ?? "incomplete text"}.`);
|
|
469
|
+
if (boundaries.some((gaps) => gaps.length === 0)) return refuse("Canonical editing lost a source character boundary.");
|
|
470
|
+
return Result.ok({
|
|
471
|
+
text,
|
|
472
|
+
boundaries
|
|
473
|
+
});
|
|
474
|
+
};
|
|
475
|
+
//#endregion
|
|
476
|
+
export { CANONICAL_NATIVE_INLINE_POLICIES, CANONICAL_SOURCE_INLINE_POLICIES, canonicalInlineSourceIssue, canonicalNativeCells, projectCanonicalInline };
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { EditorState, Transaction } from "prosemirror-state";
|
|
2
2
|
import { EditorView } from "prosemirror-view";
|
|
3
3
|
//#region src/controller/canonicalInput.d.ts
|
|
4
|
+
declare const CANONICAL_COMPOSITION_INPUT_TYPES: readonly ["insertCompositionText", "insertFromComposition", "deleteCompositionText", "deleteByComposition"];
|
|
4
5
|
type ReplaceTextInput = {
|
|
5
6
|
from: number;
|
|
6
7
|
to: number;
|
|
7
8
|
text: string;
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
} & ({
|
|
10
|
+
semantic: "composition";
|
|
11
|
+
compositionPhase?: "correction";
|
|
12
|
+
} | {
|
|
13
|
+
semantic?: "typing" | "deleteBackward" | "deleteForward";
|
|
14
|
+
compositionPhase?: never;
|
|
15
|
+
});
|
|
10
16
|
type CanonicalInputOptions = {
|
|
11
17
|
replace: (input: ReplaceTextInput) => void;
|
|
12
18
|
structure?: (type: "split" | "joinBackward" | "joinForward" | "lineBreak" | "pageBreak" | "tab" | "indent" | "outdent") => void;
|
|
@@ -14,6 +20,8 @@ type CanonicalInputOptions = {
|
|
|
14
20
|
breakUndoGroup?: () => void;
|
|
15
21
|
beginComposition?: () => boolean;
|
|
16
22
|
endComposition?: () => void;
|
|
23
|
+
pastePlainText?: (view: EditorView) => void;
|
|
24
|
+
cut?: (view: EditorView, event: ClipboardEvent) => boolean;
|
|
17
25
|
undo: () => boolean;
|
|
18
26
|
redo: () => boolean;
|
|
19
27
|
refuse: (reason: string) => void;
|
|
@@ -26,6 +34,7 @@ declare const createCanonicalInputBoundary: (options: CanonicalInputOptions) =>
|
|
|
26
34
|
handleTextInput: (view: EditorView, from: number, to: number, text: string) => boolean;
|
|
27
35
|
handleKeyDown: (view: EditorView, event: KeyboardEvent) => boolean;
|
|
28
36
|
handleDOMEvents: {
|
|
37
|
+
keydown: (view: EditorView, event: KeyboardEvent) => boolean;
|
|
29
38
|
compositionstart: (view: EditorView) => boolean;
|
|
30
39
|
compositionend: (view: EditorView) => boolean;
|
|
31
40
|
mousedown: (view: EditorView) => boolean;
|
|
@@ -33,7 +42,7 @@ declare const createCanonicalInputBoundary: (options: CanonicalInputOptions) =>
|
|
|
33
42
|
beforeinput: (view: EditorView, event: InputEvent) => boolean;
|
|
34
43
|
input: (view: EditorView) => boolean;
|
|
35
44
|
paste: (_view: EditorView, event: Event) => boolean;
|
|
36
|
-
cut: (
|
|
45
|
+
cut: (view: EditorView, event: ClipboardEvent) => boolean;
|
|
37
46
|
drop: (_view: EditorView, event: Event) => boolean;
|
|
38
47
|
};
|
|
39
48
|
/** Returns a classified intent only; the proposed PM document is never authoritative. */
|
|
@@ -42,4 +51,4 @@ declare const createCanonicalInputBoundary: (options: CanonicalInputOptions) =>
|
|
|
42
51
|
commitNativeProposal: (view: EditorView, transaction: Transaction) => boolean;
|
|
43
52
|
};
|
|
44
53
|
//#endregion
|
|
45
|
-
export { createCanonicalInputBoundary };
|
|
54
|
+
export { CANONICAL_COMPOSITION_INPUT_TYPES, createCanonicalInputBoundary };
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { handleEditorBeforeInput } from "../prosemirror/textInput.js";
|
|
1
|
+
import { getEditorInputTargetRange, handleEditorBeforeInput } from "../prosemirror/textInput.js";
|
|
2
2
|
import { createCanonicalComposition } from "./canonicalComposition.js";
|
|
3
3
|
import { deletionRange, isCanonicalJoinBoundary } from "./canonicalSession.js";
|
|
4
4
|
import { ReplaceStep } from "prosemirror-transform";
|
|
5
5
|
import { Selection, TextSelection } from "prosemirror-state";
|
|
6
6
|
//#region src/controller/canonicalInput.ts
|
|
7
|
+
const CANONICAL_COMPOSITION_INPUT_TYPES = [
|
|
8
|
+
"insertCompositionText",
|
|
9
|
+
"insertFromComposition",
|
|
10
|
+
"deleteCompositionText",
|
|
11
|
+
"deleteByComposition"
|
|
12
|
+
];
|
|
7
13
|
/** Native changes require a preceding, classified input event at the same state. */
|
|
8
14
|
const createCanonicalInputBoundary = (options) => {
|
|
9
15
|
const composition = createCanonicalComposition({
|
|
@@ -15,7 +21,8 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
15
21
|
let proposal = { type: "idle" };
|
|
16
22
|
let authorizedInput = null;
|
|
17
23
|
let groupClosed = false;
|
|
18
|
-
const beginGesture = () => {
|
|
24
|
+
const beginGesture = (lifecycle = "new") => {
|
|
25
|
+
if (lifecycle === "new") composition.forgetCompleted();
|
|
19
26
|
proposal = { type: "idle" };
|
|
20
27
|
authorizedInput = null;
|
|
21
28
|
groupClosed = false;
|
|
@@ -37,6 +44,12 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
37
44
|
if (!composition.active) proposal = { type: "refused" };
|
|
38
45
|
return true;
|
|
39
46
|
};
|
|
47
|
+
const refuseClipboardGesture = (event, reason) => {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
closeGroup();
|
|
50
|
+
options.refuse(reason);
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
40
53
|
const refuseNativeMutation = (view) => {
|
|
41
54
|
if (!composition.active && proposal.type !== "refused") {
|
|
42
55
|
options.refuse("Unclassified native text is unavailable in this session.");
|
|
@@ -72,7 +85,7 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
72
85
|
},
|
|
73
86
|
acceptComposition: composition.accept,
|
|
74
87
|
handleTextInput: (view, from, to, text) => {
|
|
75
|
-
if (composition.active) return false;
|
|
88
|
+
if (composition.active || composition.pendingFinal) return false;
|
|
76
89
|
const pending = proposal;
|
|
77
90
|
if (authorizedInput?.view === view && authorizedInput.state === view.state) {
|
|
78
91
|
options.replace({
|
|
@@ -92,6 +105,7 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
92
105
|
return true;
|
|
93
106
|
},
|
|
94
107
|
handleKeyDown: (view, event) => {
|
|
108
|
+
if (event.isComposing || event.keyCode === 229) return false;
|
|
95
109
|
beginGesture();
|
|
96
110
|
if (composition.active) {
|
|
97
111
|
if (event.key === "Escape") {
|
|
@@ -138,6 +152,12 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
138
152
|
}
|
|
139
153
|
return true;
|
|
140
154
|
}
|
|
155
|
+
if (modifier && event.altKey && event.key.toLowerCase() === "v" && options.pastePlainText) {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
closeGroup();
|
|
158
|
+
options.pastePlainText(view);
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
141
161
|
if (event.key === "Enter") {
|
|
142
162
|
if (view.composing) return refuseEvent(event, "Paragraph structure edits are unavailable during composition.");
|
|
143
163
|
event.preventDefault();
|
|
@@ -177,6 +197,13 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
177
197
|
return true;
|
|
178
198
|
},
|
|
179
199
|
handleDOMEvents: {
|
|
200
|
+
keydown: (view, event) => {
|
|
201
|
+
if (event.isComposing || event.keyCode === 229) return true;
|
|
202
|
+
if (!composition.active || event.key !== "Escape") return false;
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
composition.cancel(view);
|
|
205
|
+
return true;
|
|
206
|
+
},
|
|
180
207
|
compositionstart: (view) => {
|
|
181
208
|
beginGesture();
|
|
182
209
|
composition.start(view);
|
|
@@ -200,19 +227,31 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
200
227
|
return false;
|
|
201
228
|
},
|
|
202
229
|
beforeinput: (view, event) => {
|
|
203
|
-
beginGesture();
|
|
204
|
-
if (event.isComposing ||
|
|
230
|
+
beginGesture(event.inputType === "insertFromComposition" ? "compositionFinal" : "new");
|
|
231
|
+
if (event.isComposing || CANONICAL_COMPOSITION_INPUT_TYPES.some((inputType) => inputType === event.inputType)) {
|
|
205
232
|
if (composition.active) {
|
|
206
233
|
composition.authorizeNative(view);
|
|
207
234
|
return false;
|
|
208
235
|
}
|
|
236
|
+
if (event.inputType === "insertFromComposition" && composition.authorizeLateFinal(view, event.data)) return false;
|
|
209
237
|
return refuseEvent(event, "Composition has no captured canonical baseline.");
|
|
210
238
|
}
|
|
211
239
|
if (composition.active) {
|
|
212
|
-
|
|
213
|
-
|
|
240
|
+
if (view.composing && (event.inputType === "deleteContentBackward" || event.inputType === "deleteContentForward")) {
|
|
241
|
+
const range = getEditorInputTargetRange(view, event);
|
|
242
|
+
if (range && composition.matchesCancellationRange(view, range)) {
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
composition.cancel(view);
|
|
245
|
+
closeGroup();
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const nativeFinal = view.composing && (event.inputType === "insertText" || event.inputType === "insertReplacementText");
|
|
250
|
+
const nativeStillComposing = view.composing;
|
|
251
|
+
const recovered = composition.recover(view);
|
|
252
|
+
composition.forgetCompleted();
|
|
214
253
|
proposal = { type: "idle" };
|
|
215
|
-
if (
|
|
254
|
+
if (nativeFinal && (recovered.type === "refused" && nativeStillComposing || (recovered.type === "committed" || recovered.type === "refused") && recovered.text !== null && recovered.text === event.data)) {
|
|
216
255
|
event.preventDefault();
|
|
217
256
|
return true;
|
|
218
257
|
}
|
|
@@ -298,7 +337,7 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
298
337
|
return refuseEvent(event, `Input ${event.inputType} is unavailable in this session.`);
|
|
299
338
|
},
|
|
300
339
|
input: (view) => {
|
|
301
|
-
if (composition.active) {
|
|
340
|
+
if (composition.active || composition.pendingFinal) {
|
|
302
341
|
composition.flushed(view);
|
|
303
342
|
return false;
|
|
304
343
|
}
|
|
@@ -312,15 +351,24 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
312
351
|
},
|
|
313
352
|
paste: (_view, event) => {
|
|
314
353
|
beginGesture();
|
|
315
|
-
|
|
354
|
+
closeGroup();
|
|
355
|
+
if (composition.active) return refuseClipboardGesture(event, "Composition must finish before pasting.");
|
|
356
|
+
return false;
|
|
316
357
|
},
|
|
317
|
-
cut: (
|
|
358
|
+
cut: (view, event) => {
|
|
318
359
|
beginGesture();
|
|
319
|
-
return
|
|
360
|
+
if (composition.active) return refuseClipboardGesture(event, "Composition must finish before cutting.");
|
|
361
|
+
if (options.cut !== void 0) {
|
|
362
|
+
closeGroup();
|
|
363
|
+
return options.cut(view, event);
|
|
364
|
+
}
|
|
365
|
+
return refuseEvent(event, "The canonical clipboard owner is unavailable.");
|
|
320
366
|
},
|
|
321
367
|
drop: (_view, event) => {
|
|
322
368
|
beginGesture();
|
|
323
|
-
|
|
369
|
+
closeGroup();
|
|
370
|
+
if (composition.active) return refuseClipboardGesture(event, "Composition must finish before dropping.");
|
|
371
|
+
return false;
|
|
324
372
|
}
|
|
325
373
|
},
|
|
326
374
|
/** Returns a classified intent only; the proposed PM document is never authoritative. */
|
|
@@ -335,4 +383,4 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
335
383
|
};
|
|
336
384
|
};
|
|
337
385
|
//#endregion
|
|
338
|
-
export { createCanonicalInputBoundary };
|
|
386
|
+
export { CANONICAL_COMPOSITION_INPUT_TYPES, createCanonicalInputBoundary };
|