@stll/folio-core 0.57.0 → 0.58.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 +77 -8
- package/dist/controller/canonicalClipboard.d.ts +3 -1
- package/dist/controller/canonicalClipboard.js +24 -12
- package/dist/controller/canonicalComposition.d.ts +13 -4
- package/dist/controller/canonicalComposition.js +82 -9
- package/dist/controller/canonicalInput.d.ts +1 -1
- package/dist/controller/canonicalInput.js +4 -3
- package/dist/controller/canonicalSelection.js +10 -1
- package/dist/controller/canonicalSession.d.ts +1 -1
- package/dist/controller/canonicalSession.js +59 -23
- package/dist/controller/canonicalStoryEditor.d.ts +1 -1
- package/dist/controller/canonicalStoryEditor.js +1 -1
- package/dist/controller/canonicalStructure.js +8 -3
- package/dist/controller/hiddenEditorApi.js +1 -1
- package/dist/controller/hiddenEditorManager.js +1 -1
- package/dist/docx/listRendering.d.ts +18 -0
- package/dist/{prosemirror/conversion/listRenderingDefinition.js → docx/listRendering.js} +17 -3
- package/dist/docx/server/materializeYjsDocx.js +1 -1
- package/dist/markdown/internals.js +7 -0
- package/dist/markdown/renderParagraph.js +11 -12
- package/dist/markdown/renderRuns.js +2 -2
- package/dist/markdown/types.d.ts +3 -0
- package/dist/prosemirror/attrs/index.js +4 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +128 -27
- package/dist/prosemirror/conversion/toProseDoc.js +10 -23
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +8 -1
- package/dist/prosemirror/imagePictureNames.d.ts +12 -0
- package/dist/prosemirror/imagePictureNames.js +20 -0
- package/dist/prosemirror/paragraphFormattingProvenance.js +1 -0
- package/dist/prosemirror/runStyleFormatting.d.ts +72 -2
- package/dist/prosemirror/runStyleFormatting.js +18 -1
- package/dist/prosemirror/schema/nodes.d.ts +1 -0
- package/dist/prosemirror/styles/styleResolver.d.ts +4 -1
- package/dist/prosemirror/styles/styleResolver.js +32 -11
- package/dist/types/canonicalCapabilities.d.ts +15 -1
- package/dist/types/canonicalCapabilities.js +15 -1
- package/package.json +2 -2
- package/dist/prosemirror/conversion/listRenderingDefinition.d.ts +0 -6
package/dist/ai-edits/apply.js
CHANGED
|
@@ -3,7 +3,7 @@ import { formattingEquals } from "../docx/runConsolidator.js";
|
|
|
3
3
|
import { isStyleSourcedParagraphNumbering } from "../internal/paragraphFormattingSerialization.js";
|
|
4
4
|
import { addTrackedDeletionMark } from "../prosemirror/addTrackedDeletionMark.js";
|
|
5
5
|
import { deleteTextBoxesAnchoredAt, droppedTextBoxAnchorIds, endOfParagraphWithItsTextBoxes, gatherTextBoxesAfterParagraph, textBoxAnchorIdOf, textBoxAnchorIdsBetween } from "../prosemirror/anchoredTextBoxes.js";
|
|
6
|
-
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs, expectTableRowAttrs } from "../prosemirror/attrs/index.js";
|
|
6
|
+
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs, expectTableRowAttrs, mergeParagraphAttrs } from "../prosemirror/attrs/index.js";
|
|
7
7
|
import { hasSerializableParagraphPropertyChange, paragraphPropertiesSnapshot } from "../prosemirror/commands/propertyChangeScope.js";
|
|
8
8
|
import { resolveParagraphChangeAttrs } from "../prosemirror/commands/resolveParagraphProperties.js";
|
|
9
9
|
import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../prosemirror/containerFinalParagraph.js";
|
|
@@ -30,7 +30,7 @@ import { RUN_FORMATTING_MARK_NAMES } from "../prosemirror/runFormattingMarkNames
|
|
|
30
30
|
import { readAuthoredRunFormatting, reconcileRunFormattingMarks } from "../prosemirror/runFormattingReconciliation.js";
|
|
31
31
|
import { paragraphRunStyleContext, paragraphRunStyleContextAt, resolveParagraphBodyRunFormatting } from "../prosemirror/runStyleFormatting.js";
|
|
32
32
|
import { sectionPropertiesOf } from "../prosemirror/sectionCarrier.js";
|
|
33
|
-
import { listAttrsFromNumbering, listLevelAttrPatch } from "../prosemirror/styles/resolvedStyleAttrs.js";
|
|
33
|
+
import { listAttrsFromNumbering, listLevelAttrPatch, listLevelIndentRemovalPatch } from "../prosemirror/styles/resolvedStyleAttrs.js";
|
|
34
34
|
import { removeRowsWithoutCellsAfterBatch } from "../prosemirror/tableGridMutation.js";
|
|
35
35
|
import { isZeroWidthAnchor } from "../prosemirror/zeroWidthAnchors.js";
|
|
36
36
|
import { getFolioParaIdFromBlockId } from "../types/block-id.js";
|
|
@@ -187,7 +187,7 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
187
187
|
const resolvedIndentationFromStyle = paragraphIndentationFromFormatting(resolvedFormattingFromStyle);
|
|
188
188
|
const resolvedSpacingFromStyle = paragraphSpacingFromFormatting(resolvedFormattingFromStyle);
|
|
189
189
|
const patch = {};
|
|
190
|
-
let originalFormatting = attrs._originalFormatting
|
|
190
|
+
let originalFormatting = withDirectParagraphIndentation(attrs._originalFormatting, currentDirectIndentation);
|
|
191
191
|
let originalFormattingChanged = false;
|
|
192
192
|
const nextStyleId = properties.styleId;
|
|
193
193
|
const styleChanged = nextStyleId !== void 0 && (attrs.styleId ?? null) !== nextStyleId;
|
|
@@ -227,11 +227,18 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
227
227
|
}
|
|
228
228
|
const numPrFromStyle = styleChanged ? readParagraphNumberingAttr(patch["numPrFromStyle"]) : attrs.numPrFromStyle;
|
|
229
229
|
if (properties.numbering !== void 0) if (properties.numbering === null) {
|
|
230
|
+
Object.assign(patch, listLevelIndentRemovalPatch(mergeParagraphAttrs(node, {
|
|
231
|
+
...attrs,
|
|
232
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(resolvedFormattingFromStyle)
|
|
233
|
+
}), numbering));
|
|
230
234
|
patch["numPr"] = removedNumberingAttr(numPrFromStyle);
|
|
231
235
|
Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
|
|
232
236
|
} else {
|
|
233
237
|
const listReference = concreteListReference(properties.numbering);
|
|
234
|
-
Object.assign(patch, listLevelAttrPatch(
|
|
238
|
+
Object.assign(patch, listLevelAttrPatch(mergeParagraphAttrs(node, {
|
|
239
|
+
...attrs,
|
|
240
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(resolvedFormattingFromStyle)
|
|
241
|
+
}), {
|
|
235
242
|
numId: listReference.numId,
|
|
236
243
|
ilvl: listReference.level
|
|
237
244
|
}, numbering));
|
|
@@ -241,6 +248,10 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
241
248
|
}
|
|
242
249
|
}
|
|
243
250
|
else if (properties.listLevel !== void 0) if (properties.listLevel === null) {
|
|
251
|
+
Object.assign(patch, listLevelIndentRemovalPatch(mergeParagraphAttrs(node, {
|
|
252
|
+
...attrs,
|
|
253
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(resolvedFormattingFromStyle)
|
|
254
|
+
}), numbering));
|
|
244
255
|
patch["numPr"] = removedNumberingAttr(numPrFromStyle);
|
|
245
256
|
Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
|
|
246
257
|
} else {
|
|
@@ -251,7 +262,10 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
251
262
|
ilvl: properties.listLevel
|
|
252
263
|
});
|
|
253
264
|
Object.assign(patch, CLEARED_LIST_RENDERING_ATTRS);
|
|
254
|
-
} else Object.assign(patch, listLevelAttrPatch(
|
|
265
|
+
} else Object.assign(patch, listLevelAttrPatch(mergeParagraphAttrs(node, {
|
|
266
|
+
...attrs,
|
|
267
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(resolvedFormattingFromStyle)
|
|
268
|
+
}), {
|
|
255
269
|
numId,
|
|
256
270
|
ilvl: properties.listLevel
|
|
257
271
|
}, numbering));
|
|
@@ -283,6 +297,28 @@ const paragraphPropertiesPatch = ({ node, properties, resolvedFormattingFromStyl
|
|
|
283
297
|
originalFormattingChanged = true;
|
|
284
298
|
}
|
|
285
299
|
if (originalFormattingChanged) patch["_originalFormatting"] = originalFormatting && Object.keys(originalFormatting).length > 0 ? originalFormatting : null;
|
|
300
|
+
if (styleChanged || properties.numbering !== void 0 || properties.listLevel !== void 0 || properties.indentation !== void 0) {
|
|
301
|
+
const nextNumbering = patch["numPr"] === void 0 ? attrs.numPr : readParagraphNumberingAttr(patch["numPr"]);
|
|
302
|
+
if (nextNumbering?.kind === "reference") {
|
|
303
|
+
const direct = properties.indentation === void 0 ? currentDirectIndentation : properties.indentation ?? void 0;
|
|
304
|
+
Object.assign(patch, listLevelAttrPatch(mergeParagraphAttrs(node, {
|
|
305
|
+
numPr: nextNumbering,
|
|
306
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(resolvedFormattingFromStyle),
|
|
307
|
+
numPrFromStyle: numPrFromStyle ?? void 0,
|
|
308
|
+
listImplicitChildLevelAdvances: attrs.listImplicitChildLevelAdvances,
|
|
309
|
+
_originalFormatting: withDirectParagraphIndentation(originalFormatting, direct),
|
|
310
|
+
_resolvedFormatting: withDirectParagraphIndentation(styleChanged ? styleResolvedParagraphFormatting(resolvedFormattingFromStyle) : attrs._resolvedFormatting, resolvedIndentationFromStyle),
|
|
311
|
+
indentLeft: direct?.indentLeft ?? resolvedFormattingFromStyle?.indentLeft ?? void 0,
|
|
312
|
+
indentRight: direct?.indentRight ?? resolvedFormattingFromStyle?.indentRight ?? void 0,
|
|
313
|
+
indentFirstLine: direct?.indentFirstLine ?? resolvedFormattingFromStyle?.indentFirstLine ?? void 0,
|
|
314
|
+
hangingIndent: direct?.hangingIndent ?? resolvedFormattingFromStyle?.hangingIndent ?? false
|
|
315
|
+
}), {
|
|
316
|
+
numId: nextNumbering.numId,
|
|
317
|
+
ilvl: nextNumbering.ilvl ?? 0
|
|
318
|
+
}, numbering));
|
|
319
|
+
patch["numPr"] = nextNumbering;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
286
322
|
return Object.keys(patch).length > 0 ? patch : null;
|
|
287
323
|
};
|
|
288
324
|
const REVISION_MARK_NAMES = /* @__PURE__ */ new Set(["insertion", "deletion"]);
|
|
@@ -2009,10 +2045,17 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
2009
2045
|
const listLevel = operation.listLevel;
|
|
2010
2046
|
if (formatsParagraph) {
|
|
2011
2047
|
if (explicitNumbering === null) {
|
|
2048
|
+
Object.assign(attrs, listLevelIndentRemovalPatch(mergeParagraphAttrs(schema.node("paragraph"), {
|
|
2049
|
+
...operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode),
|
|
2050
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(formattingFromStyle)
|
|
2051
|
+
}), numbering));
|
|
2012
2052
|
attrs["numPr"] = removedNumberingAttr(readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
2013
2053
|
Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
|
|
2014
2054
|
} else if (explicitNumbering !== void 0) {
|
|
2015
|
-
Object.assign(attrs, listLevelAttrPatch(
|
|
2055
|
+
Object.assign(attrs, listLevelAttrPatch(mergeParagraphAttrs(schema.node("paragraph"), {
|
|
2056
|
+
...operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode),
|
|
2057
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(formattingFromStyle)
|
|
2058
|
+
}), {
|
|
2016
2059
|
numId: explicitNumbering.numId,
|
|
2017
2060
|
ilvl: explicitNumbering.level
|
|
2018
2061
|
}, numbering));
|
|
@@ -2021,6 +2064,10 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
2021
2064
|
attrs["numPrFromStyle"] = null;
|
|
2022
2065
|
}
|
|
2023
2066
|
} else if (listLevel === null) {
|
|
2067
|
+
Object.assign(attrs, listLevelIndentRemovalPatch(mergeParagraphAttrs(schema.node("paragraph"), {
|
|
2068
|
+
...operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode),
|
|
2069
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(formattingFromStyle)
|
|
2070
|
+
}), numbering));
|
|
2024
2071
|
attrs["numPr"] = removedNumberingAttr(readParagraphNumberingAttr(attrs["numPrFromStyle"]));
|
|
2025
2072
|
Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
|
|
2026
2073
|
} else if (typeof listLevel === "number") {
|
|
@@ -2031,7 +2078,10 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
2031
2078
|
ilvl: listLevel
|
|
2032
2079
|
});
|
|
2033
2080
|
Object.assign(attrs, CLEARED_LIST_RENDERING_ATTRS);
|
|
2034
|
-
} else Object.assign(attrs, listLevelAttrPatch(
|
|
2081
|
+
} else Object.assign(attrs, listLevelAttrPatch(mergeParagraphAttrs(schema.node("paragraph"), {
|
|
2082
|
+
...operation.inheritFormatting === false ? {} : expectParagraphAttrs(item.blockNode),
|
|
2083
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(formattingFromStyle)
|
|
2084
|
+
}), {
|
|
2035
2085
|
numId,
|
|
2036
2086
|
ilvl: listLevel
|
|
2037
2087
|
}, numbering));
|
|
@@ -2083,7 +2133,7 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
2083
2133
|
else originalFormatting.alignment = directAlignment;
|
|
2084
2134
|
}
|
|
2085
2135
|
if (operation.spacing !== void 0) originalFormatting = withDirectParagraphSpacing(originalFormatting, directSpacing);
|
|
2086
|
-
|
|
2136
|
+
originalFormatting = withDirectParagraphIndentation(originalFormatting, directIndentation);
|
|
2087
2137
|
if (operation.styleId !== void 0 || operation.spacing !== void 0) Object.assign(attrs, paragraphSpacingAttrPatch({
|
|
2088
2138
|
direct: directSpacing,
|
|
2089
2139
|
inherited: paragraphSpacingFromFormatting(formattingFromStyle)
|
|
@@ -2093,6 +2143,25 @@ const buildInsertedParagraphs = ({ item, schema, formattingFromStyle, mode, auth
|
|
|
2093
2143
|
inherited: paragraphIndentationFromFormatting(formattingFromStyle)
|
|
2094
2144
|
}));
|
|
2095
2145
|
attrs["_originalFormatting"] = originalFormatting && Object.keys(originalFormatting).length > 0 ? originalFormatting : null;
|
|
2146
|
+
const finalNumbering = readParagraphNumberingAttr(attrs["numPr"]);
|
|
2147
|
+
if (finalNumbering?.kind === "reference") {
|
|
2148
|
+
Object.assign(attrs, listLevelAttrPatch(mergeParagraphAttrs(schema.node("paragraph"), {
|
|
2149
|
+
numPr: finalNumbering,
|
|
2150
|
+
_styleResolvedFormatting: styleResolvedParagraphFormatting(formattingFromStyle),
|
|
2151
|
+
numPrFromStyle: readParagraphNumberingAttr(attrs["numPrFromStyle"]) ?? void 0,
|
|
2152
|
+
listImplicitChildLevelAdvances: operation.inheritFormatting === false ? void 0 : expectParagraphAttrs(item.blockNode).listImplicitChildLevelAdvances,
|
|
2153
|
+
_originalFormatting: originalFormatting,
|
|
2154
|
+
_resolvedFormatting: styleResolvedParagraphFormatting(formattingFromStyle),
|
|
2155
|
+
indentLeft: directIndentation?.indentLeft ?? formattingFromStyle?.indentLeft ?? void 0,
|
|
2156
|
+
indentRight: directIndentation?.indentRight ?? formattingFromStyle?.indentRight ?? void 0,
|
|
2157
|
+
indentFirstLine: directIndentation?.indentFirstLine ?? formattingFromStyle?.indentFirstLine ?? void 0,
|
|
2158
|
+
hangingIndent: directIndentation?.hangingIndent ?? formattingFromStyle?.hangingIndent ?? false
|
|
2159
|
+
}), {
|
|
2160
|
+
numId: finalNumbering.numId,
|
|
2161
|
+
ilvl: finalNumbering.ilvl ?? 0
|
|
2162
|
+
}, numbering));
|
|
2163
|
+
attrs["numPr"] = finalNumbering;
|
|
2164
|
+
}
|
|
2096
2165
|
}
|
|
2097
2166
|
if (formatsParagraph && operation.styleId !== void 0 && formattingFromStyle?.numPr?.kind === "reference" && operation.numbering === void 0 && operation.listLevel === void 0) {
|
|
2098
2167
|
const { numId, ilvl = 0 } = formattingFromStyle.numPr;
|
|
@@ -4,6 +4,8 @@ import { Slice } from "prosemirror-model";
|
|
|
4
4
|
import { EditorState } from "prosemirror-state";
|
|
5
5
|
import { Result } from "better-result";
|
|
6
6
|
//#region src/controller/canonicalClipboard.d.ts
|
|
7
|
+
/** Imported comments and notes require copying their source story parts. */
|
|
8
|
+
declare const canonicalClipboardStoryPartRefusal: (paragraph: document_d_exports.Paragraph) => CanonicalSessionError | undefined;
|
|
7
9
|
/** Clipboard slices are input payloads; their paragraph identities and private captures are not owners. */
|
|
8
10
|
type PrepareCanonicalPasteOptions = {
|
|
9
11
|
session: CanonicalSession;
|
|
@@ -20,4 +22,4 @@ type PrepareCanonicalPasteOptions = {
|
|
|
20
22
|
};
|
|
21
23
|
declare const prepareCanonicalPaste: ({ session, state, slice, plain, moveTarget, pasteTarget, sourceDocument, moveSource }: PrepareCanonicalPasteOptions) => Result<CanonicalCommit, CanonicalSessionError>;
|
|
22
24
|
//#endregion
|
|
23
|
-
export { prepareCanonicalPaste };
|
|
25
|
+
export { canonicalClipboardStoryPartRefusal, prepareCanonicalPaste };
|
|
@@ -3,6 +3,7 @@ import { copyParagraphIndentationProjection } from "../docx/paragraphPropertySou
|
|
|
3
3
|
import { visitInlineContentSlots, visitParagraphRuns } from "../docx/paragraphTraversal.js";
|
|
4
4
|
import { captureVerbatimXml } from "../docx/verbatimCapture.js";
|
|
5
5
|
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, OOXML_NAMESPACE_SCOPE, findAttributeByNamespaceUri, getLocalName, parseXml } from "../docx/xmlParser.js";
|
|
6
|
+
import { canonicalSelectionRange } from "../prosemirror/canonicalSelectionRange.js";
|
|
6
7
|
import { proseDocToBlocks } from "../prosemirror/conversion/fromProseDoc.js";
|
|
7
8
|
import { completeNumberingForDoc } from "../prosemirror/listInstanceReferences.js";
|
|
8
9
|
import { marksToTextFormatting } from "../prosemirror/runFormattingFromMarks.js";
|
|
@@ -174,6 +175,22 @@ const importNumbering = ({ destination, source, paragraphs, mode, sourceKind, ex
|
|
|
174
175
|
numIds: nums
|
|
175
176
|
});
|
|
176
177
|
};
|
|
178
|
+
/** Imported comments and notes require copying their source story parts. */
|
|
179
|
+
const canonicalClipboardStoryPartRefusal = (paragraph) => {
|
|
180
|
+
let requiresStory = false;
|
|
181
|
+
visitInlineContentSlots(paragraph, ({ item }) => {
|
|
182
|
+
if (item.type === "commentRangeStart" || item.type === "commentRangeEnd" || item.type === "commentReference") requiresStory = true;
|
|
183
|
+
});
|
|
184
|
+
visitParagraphRuns(paragraph, (run) => {
|
|
185
|
+
if (run.content.some((item) => item.type === "footnoteRef" || item.type === "endnoteRef")) requiresStory = true;
|
|
186
|
+
});
|
|
187
|
+
if (!requiresStory) return void 0;
|
|
188
|
+
return new CanonicalSessionError({
|
|
189
|
+
gap: CANONICAL_GAP.dispatch,
|
|
190
|
+
reason: "refused",
|
|
191
|
+
message: "Clipboard comments and note references require importing their source story parts."
|
|
192
|
+
});
|
|
193
|
+
};
|
|
177
194
|
const prepareCanonicalPaste = ({ session, state, slice, plain = false, moveTarget, pasteTarget, sourceDocument, moveSource }) => {
|
|
178
195
|
if (session.isComposing) return refuse("Composition must finish before using the clipboard.");
|
|
179
196
|
if (slice.content.size === 0) return Result.err(new CanonicalSessionError({
|
|
@@ -212,9 +229,10 @@ const prepareCanonicalPaste = ({ session, state, slice, plain = false, moveTarge
|
|
|
212
229
|
paragraphs.push(paragraph);
|
|
213
230
|
}
|
|
214
231
|
if (moveTarget === void 0 && (plain || sourceDocument === void 0)) flattenClipboardStyleReferences(paragraphs);
|
|
215
|
-
const
|
|
232
|
+
const selectionRange = canonicalSelectionRange(state);
|
|
233
|
+
const from = session.projection.addressAt(pasteTarget ?? moveSource?.from ?? selectionRange.from);
|
|
216
234
|
if (from.isErr()) return from;
|
|
217
|
-
const to = session.projection.addressAt(pasteTarget ?? moveSource?.to ??
|
|
235
|
+
const to = session.projection.addressAt(pasteTarget ?? moveSource?.to ?? selectionRange.to);
|
|
218
236
|
if (to.isErr()) return to;
|
|
219
237
|
let styles = session.document.package.styles;
|
|
220
238
|
let styleIds;
|
|
@@ -315,14 +333,8 @@ const prepareCanonicalPaste = ({ session, state, slice, plain = false, moveTarge
|
|
|
315
333
|
};
|
|
316
334
|
for (const paragraph of paragraphs) {
|
|
317
335
|
if (moveTarget === void 0) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (item.type === "commentRangeStart" || item.type === "commentRangeEnd" || item.type === "commentReference") requiresStory = true;
|
|
321
|
-
});
|
|
322
|
-
visitParagraphRuns(paragraph, (run) => {
|
|
323
|
-
if (run.content.some((item) => item.type === "footnoteRef" || item.type === "endnoteRef")) requiresStory = true;
|
|
324
|
-
});
|
|
325
|
-
if (requiresStory) return refuse("Clipboard comments and note references require importing their source story parts.");
|
|
336
|
+
const storyPartRefusal = canonicalClipboardStoryPartRefusal(paragraph);
|
|
337
|
+
if (storyPartRefusal !== void 0) return Result.err(storyPartRefusal);
|
|
326
338
|
const links = [];
|
|
327
339
|
visitInlineContentSlots(paragraph, ({ item }) => {
|
|
328
340
|
if (item.type === "hyperlink") links.push(item);
|
|
@@ -341,7 +353,7 @@ const prepareCanonicalPaste = ({ session, state, slice, plain = false, moveTarge
|
|
|
341
353
|
const runs = [];
|
|
342
354
|
visitParagraphRuns(paragraph, (run) => runs.push(run));
|
|
343
355
|
for (const item of runs) {
|
|
344
|
-
if (plain) item.formatting = marksToTextFormatting(state.storedMarks ?? state.doc.resolve(pasteTarget ?? moveSource?.from ??
|
|
356
|
+
if (plain) item.formatting = marksToTextFormatting(state.storedMarks ?? state.doc.resolve(pasteTarget ?? moveSource?.from ?? selectionRange.from).marks());
|
|
345
357
|
for (const child of item.content) {
|
|
346
358
|
if (child.type !== "drawing") continue;
|
|
347
359
|
if (child.rawXmlMode !== void 0) return refuse("Opaque drawings and embedded objects require their source package parts.");
|
|
@@ -479,4 +491,4 @@ const prepareCanonicalPaste = ({ session, state, slice, plain = false, moveTarge
|
|
|
479
491
|
});
|
|
480
492
|
};
|
|
481
493
|
//#endregion
|
|
482
|
-
export { prepareCanonicalPaste };
|
|
494
|
+
export { canonicalClipboardStoryPartRefusal, prepareCanonicalPaste };
|
|
@@ -13,18 +13,27 @@ type CompositionOptions = {
|
|
|
13
13
|
}) => void;
|
|
14
14
|
refuse: (reason: string) => void;
|
|
15
15
|
};
|
|
16
|
+
type CompositionCompletionEvidence = "provisional" | "explicitFinal";
|
|
17
|
+
type CompositionPhase = {
|
|
18
|
+
type: "active";
|
|
19
|
+
completion: CompositionCompletionEvidence;
|
|
20
|
+
} | {
|
|
21
|
+
type: "ended";
|
|
22
|
+
data: string | null;
|
|
23
|
+
completion: CompositionCompletionEvidence;
|
|
24
|
+
};
|
|
16
25
|
type CompositionState = {
|
|
17
26
|
type: "committed";
|
|
18
27
|
} | {
|
|
19
28
|
type: "provisional";
|
|
20
29
|
baseline: EditorState;
|
|
21
30
|
view: EditorView;
|
|
22
|
-
phase:
|
|
31
|
+
phase: CompositionPhase;
|
|
23
32
|
} | {
|
|
24
33
|
type: "refused";
|
|
25
34
|
baseline: EditorState;
|
|
26
35
|
view: EditorView;
|
|
27
|
-
phase:
|
|
36
|
+
phase: CompositionPhase;
|
|
28
37
|
text: string | null;
|
|
29
38
|
};
|
|
30
39
|
type CompositionFinish = {
|
|
@@ -42,11 +51,11 @@ declare const createCanonicalComposition: (options: CompositionOptions) => {
|
|
|
42
51
|
to: number;
|
|
43
52
|
}) => boolean;
|
|
44
53
|
start: (view: EditorView) => void;
|
|
45
|
-
ended: (view: EditorView) => void;
|
|
54
|
+
ended: (view: EditorView, data: string | null) => void;
|
|
46
55
|
forgetCompleted: () => void;
|
|
47
56
|
readonly pendingFinal: boolean;
|
|
48
57
|
authorizeLateFinal: (view: EditorView, text: string | null) => boolean;
|
|
49
|
-
authorizeNative: (view: EditorView) => void;
|
|
58
|
+
authorizeNative: (view: EditorView, completion: CompositionCompletionEvidence) => void;
|
|
50
59
|
flushed: (view: EditorView) => void;
|
|
51
60
|
recover: (view: EditorView) => CompositionFinish;
|
|
52
61
|
cancel: (view: EditorView) => CompositionFinish;
|
|
@@ -1,10 +1,52 @@
|
|
|
1
1
|
import { splitsSurrogatePair } from "../ai-edits/character-boundaries.js";
|
|
2
|
+
import { RUN_IDENTITY_MARK_NAME } from "../prosemirror/runIdentity.js";
|
|
2
3
|
import { CANONICAL_GAP } from "../types/canonicalCapabilities.js";
|
|
3
4
|
import { setCanonicalInputTimer } from "./canonicalInputTimer.js";
|
|
4
5
|
import { CanonicalSessionError } from "./canonicalSession.js";
|
|
5
6
|
import { Result } from "better-result";
|
|
6
7
|
import { EditorState, TextSelection } from "prosemirror-state";
|
|
7
8
|
//#region src/controller/canonicalComposition.ts
|
|
9
|
+
const matchesNativeReplacement = ({ baseline, proposed, from, to, text }) => {
|
|
10
|
+
const expected = baseline.tr.insertText(text, from, to);
|
|
11
|
+
if (expected.doc.eq(proposed.doc)) return true;
|
|
12
|
+
if (!baseline.doc.resolve(from).sameParent(baseline.doc.resolve(to))) return false;
|
|
13
|
+
if (expected.doc.content.size !== proposed.doc.content.size) return false;
|
|
14
|
+
const markType = baseline.schema.marks[RUN_IDENTITY_MARK_NAME];
|
|
15
|
+
if (markType === void 0) return false;
|
|
16
|
+
const position = expected.doc.resolve(from);
|
|
17
|
+
const paragraph = position.parent;
|
|
18
|
+
const start = position.start();
|
|
19
|
+
const native = proposed.doc.nodeAt(start - 1);
|
|
20
|
+
if (native === null || native.type !== paragraph.type) return false;
|
|
21
|
+
let index = 0;
|
|
22
|
+
let offset = 0;
|
|
23
|
+
const missing = [];
|
|
24
|
+
native.forEach((node, nodeOffset) => {
|
|
25
|
+
const end = nodeOffset + node.nodeSize;
|
|
26
|
+
let cursor = nodeOffset;
|
|
27
|
+
while (cursor < end && index < paragraph.childCount) {
|
|
28
|
+
const authored = paragraph.child(index);
|
|
29
|
+
const authoredEnd = offset + authored.nodeSize;
|
|
30
|
+
const overlapEnd = Math.min(end, authoredEnd);
|
|
31
|
+
if (node.isText && authored.isText && markType.isInSet(authored.marks) !== void 0 && markType.isInSet(node.marks) === void 0) {
|
|
32
|
+
const previous = missing.at(-1);
|
|
33
|
+
if (previous?.to === start + cursor) previous.to = start + overlapEnd;
|
|
34
|
+
else missing.push({
|
|
35
|
+
type: "omittedRunIdentity",
|
|
36
|
+
from: start + cursor,
|
|
37
|
+
to: start + overlapEnd
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
cursor = overlapEnd;
|
|
41
|
+
if (cursor === authoredEnd) {
|
|
42
|
+
offset = authoredEnd;
|
|
43
|
+
index += 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
for (const range of missing) expected.removeMark(range.from, range.to, markType);
|
|
48
|
+
return expected.doc.eq(proposed.doc);
|
|
49
|
+
};
|
|
8
50
|
/** Native IME owns the provisional view; the captured canonical projection stays unchanged. */
|
|
9
51
|
const createCanonicalComposition = (options) => {
|
|
10
52
|
let state = { type: "committed" };
|
|
@@ -25,7 +67,13 @@ const createCanonicalComposition = (options) => {
|
|
|
25
67
|
const selectedReplacement = (baseline, proposed) => {
|
|
26
68
|
const { from, to } = baseline.selection;
|
|
27
69
|
const text = selectedText(baseline, proposed);
|
|
28
|
-
if (text === null || !
|
|
70
|
+
if (text === null || !matchesNativeReplacement({
|
|
71
|
+
baseline,
|
|
72
|
+
proposed,
|
|
73
|
+
from,
|
|
74
|
+
to,
|
|
75
|
+
text
|
|
76
|
+
})) return null;
|
|
29
77
|
return {
|
|
30
78
|
from,
|
|
31
79
|
to,
|
|
@@ -87,7 +135,13 @@ const createCanonicalComposition = (options) => {
|
|
|
87
135
|
const inserted = next.content.cut(from, newEnd);
|
|
88
136
|
const text = inserted.textBetween(0, inserted.size, "", "");
|
|
89
137
|
if (removed.textBetween(0, removed.size, "", "").length !== removed.size || text.length !== inserted.size) return null;
|
|
90
|
-
if (!
|
|
138
|
+
if (!matchesNativeReplacement({
|
|
139
|
+
baseline,
|
|
140
|
+
proposed,
|
|
141
|
+
from: start + from,
|
|
142
|
+
to: start + oldEnd,
|
|
143
|
+
text
|
|
144
|
+
})) return null;
|
|
91
145
|
return {
|
|
92
146
|
from: start + from,
|
|
93
147
|
to: start + oldEnd,
|
|
@@ -119,7 +173,7 @@ const createCanonicalComposition = (options) => {
|
|
|
119
173
|
view.updateState(pending.baseline);
|
|
120
174
|
}
|
|
121
175
|
options.end();
|
|
122
|
-
if (!commit || view.isDestroyed) return { type: "cancelled" };
|
|
176
|
+
if (!commit || view.isDestroyed || pending.phase.type === "ended" && pending.phase.data === "" && pending.phase.completion === "provisional" && committedText === "") return { type: "cancelled" };
|
|
123
177
|
if (pending.type === "refused") return {
|
|
124
178
|
type: "refused",
|
|
125
179
|
text: pending.text
|
|
@@ -178,10 +232,13 @@ const createCanonicalComposition = (options) => {
|
|
|
178
232
|
type: "provisional",
|
|
179
233
|
baseline: view.state,
|
|
180
234
|
view,
|
|
181
|
-
phase:
|
|
235
|
+
phase: {
|
|
236
|
+
type: "active",
|
|
237
|
+
completion: "provisional"
|
|
238
|
+
}
|
|
182
239
|
};
|
|
183
240
|
},
|
|
184
|
-
ended: (view) => {
|
|
241
|
+
ended: (view, data) => {
|
|
185
242
|
if (state.type === "committed") return;
|
|
186
243
|
switch (state.type) {
|
|
187
244
|
case "provisional":
|
|
@@ -189,7 +246,11 @@ const createCanonicalComposition = (options) => {
|
|
|
189
246
|
type: "provisional",
|
|
190
247
|
baseline: state.baseline,
|
|
191
248
|
view: state.view,
|
|
192
|
-
phase:
|
|
249
|
+
phase: {
|
|
250
|
+
type: "ended",
|
|
251
|
+
data,
|
|
252
|
+
completion: state.phase.completion
|
|
253
|
+
}
|
|
193
254
|
};
|
|
194
255
|
break;
|
|
195
256
|
case "refused":
|
|
@@ -197,7 +258,11 @@ const createCanonicalComposition = (options) => {
|
|
|
197
258
|
type: "refused",
|
|
198
259
|
baseline: state.baseline,
|
|
199
260
|
view: state.view,
|
|
200
|
-
phase:
|
|
261
|
+
phase: {
|
|
262
|
+
type: "ended",
|
|
263
|
+
data,
|
|
264
|
+
completion: state.phase.completion
|
|
265
|
+
},
|
|
201
266
|
text: state.text
|
|
202
267
|
};
|
|
203
268
|
break;
|
|
@@ -225,15 +290,23 @@ const createCanonicalComposition = (options) => {
|
|
|
225
290
|
};
|
|
226
291
|
return true;
|
|
227
292
|
},
|
|
228
|
-
authorizeNative: (view) => {
|
|
293
|
+
authorizeNative: (view, completion) => {
|
|
229
294
|
authorizedNativeState = view.state;
|
|
295
|
+
if (state.type !== "committed" && completion === "explicitFinal") state.phase = state.phase.type === "active" ? {
|
|
296
|
+
type: "active",
|
|
297
|
+
completion
|
|
298
|
+
} : {
|
|
299
|
+
type: "ended",
|
|
300
|
+
data: state.phase.data,
|
|
301
|
+
completion
|
|
302
|
+
};
|
|
230
303
|
},
|
|
231
304
|
flushed: (view) => {
|
|
232
305
|
const authorized = authorizedNativeState;
|
|
233
306
|
queueMicrotask(() => {
|
|
234
307
|
if (authorizedNativeState === authorized) authorizedNativeState = null;
|
|
235
308
|
});
|
|
236
|
-
if (state.type !== "committed" && state.phase === "ended") schedule(view);
|
|
309
|
+
if (state.type !== "committed" && state.phase.type === "ended") schedule(view);
|
|
237
310
|
},
|
|
238
311
|
recover: (view) => finish(view, true),
|
|
239
312
|
cancel: (view) => finish(view, false),
|
|
@@ -36,7 +36,7 @@ declare const createCanonicalInputBoundary: (options: CanonicalInputOptions) =>
|
|
|
36
36
|
handleDOMEvents: {
|
|
37
37
|
keydown: (view: EditorView, event: KeyboardEvent) => boolean;
|
|
38
38
|
compositionstart: (view: EditorView) => boolean;
|
|
39
|
-
compositionend: (view: EditorView) => boolean;
|
|
39
|
+
compositionend: (view: EditorView, event: Event) => boolean;
|
|
40
40
|
mousedown: (view: EditorView) => boolean;
|
|
41
41
|
blur: (view: EditorView) => boolean;
|
|
42
42
|
beforeinput: (view: EditorView, event: InputEvent) => boolean;
|
|
@@ -209,8 +209,8 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
209
209
|
composition.start(view);
|
|
210
210
|
return false;
|
|
211
211
|
},
|
|
212
|
-
compositionend: (view) => {
|
|
213
|
-
composition.ended(view);
|
|
212
|
+
compositionend: (view, event) => {
|
|
213
|
+
composition.ended(view, event instanceof CompositionEvent && typeof event.data === "string" ? event.data : null);
|
|
214
214
|
return false;
|
|
215
215
|
},
|
|
216
216
|
mousedown: (view) => {
|
|
@@ -230,7 +230,7 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
230
230
|
beginGesture(event.inputType === "insertFromComposition" ? "compositionFinal" : "new");
|
|
231
231
|
if (event.isComposing || CANONICAL_COMPOSITION_INPUT_TYPES.some((inputType) => inputType === event.inputType)) {
|
|
232
232
|
if (composition.active) {
|
|
233
|
-
composition.authorizeNative(view);
|
|
233
|
+
composition.authorizeNative(view, event.inputType === "insertFromComposition" ? "explicitFinal" : "provisional");
|
|
234
234
|
return false;
|
|
235
235
|
}
|
|
236
236
|
if (event.inputType === "insertFromComposition" && composition.authorizeLateFinal(view, event.data)) return false;
|
|
@@ -248,6 +248,7 @@ const createCanonicalInputBoundary = (options) => {
|
|
|
248
248
|
}
|
|
249
249
|
const nativeFinal = view.composing && (event.inputType === "insertText" || event.inputType === "insertReplacementText");
|
|
250
250
|
const nativeStillComposing = view.composing;
|
|
251
|
+
if (nativeFinal) composition.authorizeNative(view, "explicitFinal");
|
|
251
252
|
const recovered = composition.recover(view);
|
|
252
253
|
composition.forgetCompleted();
|
|
253
254
|
proposal = { type: "idle" };
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { panic } from "better-result";
|
|
2
|
-
import { AllSelection, TextSelection } from "prosemirror-state";
|
|
2
|
+
import { AllSelection, NodeSelection, TextSelection } from "prosemirror-state";
|
|
3
3
|
//#region src/controller/canonicalSelection.ts
|
|
4
4
|
/** Restore the admitted selection kind after a canonical projection changes. */
|
|
5
5
|
const restoreCanonicalSelection = ({ transaction, projection, selection, unavailable }) => {
|
|
6
6
|
switch (selection.type) {
|
|
7
|
+
case "inlineNode": {
|
|
8
|
+
const anchor = projection.positionAt(selection.anchor);
|
|
9
|
+
const head = projection.positionAt(selection.head);
|
|
10
|
+
if (anchor.isErr() || head.isErr()) return false;
|
|
11
|
+
const node = transaction.doc.nodeAt(anchor.value);
|
|
12
|
+
if (!node?.isInline || !node.isAtom || !NodeSelection.isSelectable(node) || anchor.value + node.nodeSize !== head.value) return false;
|
|
13
|
+
transaction.setSelection(NodeSelection.create(transaction.doc, anchor.value));
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
7
16
|
case "all":
|
|
8
17
|
transaction.setSelection(new AllSelection(transaction.doc));
|
|
9
18
|
return true;
|
|
@@ -14,7 +14,7 @@ declare class CanonicalSessionError extends CanonicalSessionError_base<{
|
|
|
14
14
|
reason: "refused" | "noChange";
|
|
15
15
|
}> {}
|
|
16
16
|
type CanonicalSelection = {
|
|
17
|
-
type: "text" | "all";
|
|
17
|
+
type: "text" | "inlineNode" | "all";
|
|
18
18
|
anchor: TextPosition;
|
|
19
19
|
head: TextPosition;
|
|
20
20
|
};
|