@stll/folio-core 0.54.0 → 0.54.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/dist/ai-edits/apply.d.ts +1 -1
- package/dist/ai-edits/apply.js +43 -31
- package/dist/ai-edits/headless.d.ts +1 -2
- package/dist/ai-edits/headless.js +12 -27
- package/dist/ai-edits/read.d.ts +1 -0
- package/dist/ai-edits/read.js +7 -2
- package/dist/ai-edits/table-mutation-plan.d.ts +2 -1
- package/dist/ai-edits/table-mutation-plan.js +39 -1
- package/dist/ai-suggestions/apply.d.ts +1 -1
- package/dist/ai-suggestions/apply.js +3 -2
- package/dist/compare/compare.js +2 -1
- package/dist/docx/bookmarkIds.d.ts +11 -0
- package/dist/docx/bookmarkIds.js +33 -0
- package/dist/docx/documentParser.d.ts +14 -1
- package/dist/docx/documentParser.js +17 -4
- package/dist/docx/drawingIdNormalization.js +23 -8
- package/dist/docx/ensureParaIds.d.ts +5 -0
- package/dist/docx/ensureParaIds.js +10 -2
- package/dist/docx/listNumberingInstances.js +11 -9
- package/dist/docx/noteIds.d.ts +4 -0
- package/dist/docx/noteIds.js +13 -0
- package/dist/docx/numberingIds.d.ts +14 -0
- package/dist/docx/numberingIds.js +16 -0
- package/dist/docx/numericIdAllocator.d.ts +12 -0
- package/dist/docx/numericIdAllocator.js +33 -0
- package/dist/docx/numericIdNormalization.d.ts +16 -0
- package/dist/docx/numericIdNormalization.js +174 -0
- package/dist/docx/paragraphPropertySource.js +1 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
- package/dist/docx/parser.js +17 -5
- package/dist/docx/rasterMime.d.ts +64 -0
- package/dist/docx/rasterMime.js +116 -0
- package/dist/docx/replyToComment.js +13 -6
- package/dist/docx/rezip.js +14 -7
- package/dist/docx/selectiveSave.js +10 -0
- package/dist/docx/serializer/partNamespaces.js +4 -2
- package/dist/docx/serializer/runSerializer.js +5 -4
- package/dist/docx/serializer/trackedChangeAttributes.js +7 -1
- package/dist/docx/server/build.js +14 -3
- package/dist/docx/server/createBilingualDocument.js +10 -9
- package/dist/docx/streamingXmlParser.d.ts +10 -1
- package/dist/docx/streamingXmlParser.js +88 -19
- package/dist/docx/unzip.d.ts +3 -1
- package/dist/docx/unzip.js +36 -16
- package/dist/docx/verticalMergeProjection.d.ts +17 -0
- package/dist/docx/verticalMergeProjection.js +120 -0
- package/dist/layout-bridge/convert/fixedTableColumnWidths.d.ts +6 -0
- package/dist/layout-bridge/convert/fixedTableColumnWidths.js +23 -0
- package/dist/layout-bridge/convert/tableConversion.js +4 -1
- package/dist/layout-painter/index.js +2 -5
- package/dist/paged-layout/editorScrollRoot.d.ts +15 -0
- package/dist/paged-layout/editorScrollRoot.js +72 -0
- package/dist/paged-layout/scrollToPmPosition.js +8 -22
- package/dist/prosemirror/commands/clearParagraphIndent.d.ts +6 -0
- package/dist/prosemirror/commands/clearParagraphIndent.js +25 -0
- package/dist/prosemirror/commands/comments.js +6 -1
- package/dist/prosemirror/commands/propertyChangeScope.js +4 -1
- package/dist/prosemirror/commands/resolveAllTableChanges.js +18 -24
- package/dist/prosemirror/commands/resolveParagraphProperties.js +13 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +8 -9
- package/dist/prosemirror/commands/tableCellMergeResolution.js +28 -25
- package/dist/prosemirror/commands/tableMergeFoldDecisions.d.ts +61 -0
- package/dist/prosemirror/commands/tableMergeFoldDecisions.js +79 -0
- package/dist/prosemirror/commentIdAllocator.d.ts +15 -22
- package/dist/prosemirror/commentIdAllocator.js +50 -25
- package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -6
- package/dist/prosemirror/conversion/toProseDoc.js +2 -117
- package/dist/prosemirror/extensions/core/HistoryExtension.js +97 -5
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +3 -1
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +22 -28
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -6
- package/dist/prosemirror/listNumbering.js +14 -14
- package/dist/prosemirror/listRendering.d.ts +4 -2
- package/dist/prosemirror/listRendering.js +3 -4
- package/dist/prosemirror/paragraphPropertyCarry.js +2 -5
- package/dist/prosemirror/plugins/revisionIds.d.ts +23 -19
- package/dist/prosemirror/plugins/revisionIds.js +110 -29
- package/dist/prosemirror/plugins/suggestionMode.d.ts +2 -2
- package/dist/prosemirror/plugins/suggestionMode.js +197 -45
- package/dist/prosemirror/plugins/templateDirectives.d.ts +6 -0
- package/dist/prosemirror/plugins/templateDirectives.js +21 -7
- package/dist/prosemirror/plugins/templatePreviewValues.js +14 -21
- package/dist/prosemirror/storyListNumbering.js +11 -9
- package/dist/prosemirror/textInput.d.ts +6 -1
- package/dist/prosemirror/textInput.js +9 -2
- package/dist/prosemirror/utils/visualLineNavigation.js +6 -8
- package/dist/utils/mergeDocumentContent.d.ts +2 -2
- package/dist/utils/mergeDocumentContent.js +11 -8
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expectParagraphAttrs, expectTrackedChangeMarkAttrs } from "../attrs/index.js";
|
|
2
|
+
import { clearIndentOnBackspace } from "../commands/clearParagraphIndent.js";
|
|
2
3
|
import { paragraphPropertiesSnapshot } from "../commands/propertyChangeScope.js";
|
|
3
4
|
import { paragraphEndsItsContainer } from "../containerFinalParagraph.js";
|
|
4
5
|
import { encloseWholeControls } from "../contentControlRevisions.js";
|
|
@@ -10,14 +11,16 @@ import { rebaseParagraphRuns } from "../rebaseParagraphRunFormatting.js";
|
|
|
10
11
|
import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
|
|
11
12
|
import { paragraphRunStyleContext } from "../runStyleFormatting.js";
|
|
12
13
|
import { cellPasteRange, pasteTableCells } from "../tableCellPaste.js";
|
|
13
|
-
import { handleEditorBeforeInput } from "../textInput.js";
|
|
14
|
+
import { afterNativeCompositionFlush, handleEditorBeforeInput } from "../textInput.js";
|
|
14
15
|
import { canCarryTrackedRunMark } from "../trackedRunInlineAtoms.js";
|
|
15
16
|
import { getDocumentNumbering } from "./documentNumbering.js";
|
|
16
|
-
import { getDocumentStyleResolver } from "./documentStyleState.js";
|
|
17
|
+
import { documentStylesKey, getDocumentStyleResolver } from "./documentStyleState.js";
|
|
17
18
|
import { mintRevisionId, seedRevisionIdsFromDoc } from "./revisionIds.js";
|
|
18
19
|
import { Slice } from "prosemirror-model";
|
|
20
|
+
import { CellSelection } from "prosemirror-tables";
|
|
19
21
|
import { Mapping, ReplaceStep } from "prosemirror-transform";
|
|
20
|
-
import { AllSelection, Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state";
|
|
22
|
+
import { AllSelection, EditorState, Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state";
|
|
23
|
+
import { joinBackward, joinForward, selectNodeBackward, selectNodeForward } from "prosemirror-commands";
|
|
21
24
|
import { isHistoryTransaction } from "prosemirror-history";
|
|
22
25
|
import { undoInputRule } from "prosemirror-inputrules";
|
|
23
26
|
//#region src/prosemirror/plugins/suggestionMode.ts
|
|
@@ -241,10 +244,44 @@ function markRangeAsInserted(tr, doc, from, to, insertionType, deletionType, att
|
|
|
241
244
|
function handleSuggestionPaste(view, slice, pluginState) {
|
|
242
245
|
const { selection } = view.state;
|
|
243
246
|
const selectsAll = selection instanceof AllSelection;
|
|
244
|
-
if (!(selection instanceof TextSelection || selectsAll)
|
|
247
|
+
if (!(selection instanceof TextSelection || selectsAll)) return false;
|
|
245
248
|
const insertionType = view.state.schema.marks["insertion"];
|
|
246
249
|
const deletionType = view.state.schema.marks["deletion"];
|
|
247
250
|
if (!insertionType || !deletionType) return false;
|
|
251
|
+
if (selection.empty) {
|
|
252
|
+
const { $from } = selection;
|
|
253
|
+
const closedTable = slice.openStart === 0 && slice.openEnd === 0 && slice.content.firstChild?.type.name === "table" && slice.content.lastChild?.type.name === "table";
|
|
254
|
+
const prefix = $from.parent.content.cut(0, $from.parentOffset);
|
|
255
|
+
let deletedPrefix = prefix.size > 0;
|
|
256
|
+
prefix.forEach((node) => {
|
|
257
|
+
if (!node.marks.some((mark) => mark.type === deletionType)) deletedPrefix = false;
|
|
258
|
+
});
|
|
259
|
+
if (!closedTable || $from.parent.type.name !== "paragraph" || !deletedPrefix) return false;
|
|
260
|
+
const suffix = $from.parent.content.cut($from.parentOffset);
|
|
261
|
+
let deletedSuffix = true;
|
|
262
|
+
suffix.forEach((node) => {
|
|
263
|
+
if (!node.marks.some((mark) => mark.type === deletionType)) deletedSuffix = false;
|
|
264
|
+
});
|
|
265
|
+
const tr = view.state.tr.setMeta(SUGGESTION_META, true).setMeta("paste", true);
|
|
266
|
+
const at = $from.before();
|
|
267
|
+
tr.replaceRange(at, at, slice);
|
|
268
|
+
const revision = makeMarkAttrs(pluginState);
|
|
269
|
+
const end = tr.mapping.map(at, 1);
|
|
270
|
+
enclosePastedRunRevisions({
|
|
271
|
+
tr,
|
|
272
|
+
from: at,
|
|
273
|
+
to: end,
|
|
274
|
+
revision
|
|
275
|
+
});
|
|
276
|
+
markRangeAsInserted(tr, tr.doc, at, end, insertionType, deletionType, revision);
|
|
277
|
+
if (deletedSuffix && $from.parent.attrs["pPrMark"] == null && !paragraphEndsItsContainer($from.doc.resolve($from.before()), "paragraph")) tr.setNodeAttribute(tr.mapping.map($from.before()), "pPrMark", {
|
|
278
|
+
kind: "del",
|
|
279
|
+
info: makeParagraphMarkInfo(pluginState)
|
|
280
|
+
});
|
|
281
|
+
tr.setSelection(TextSelection.create(tr.doc, tr.mapping.map(selection.from)));
|
|
282
|
+
view.dispatch(tr.scrollIntoView());
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
248
285
|
if (selectsAll && (slice.openStart > 0 || slice.openEnd > 0)) slice = new Slice(view.state.tr.replaceSelection(slice).doc.content, 0, 0);
|
|
249
286
|
const { from, to } = selectsAll ? {
|
|
250
287
|
from: Selection.atStart(view.state.doc).from,
|
|
@@ -372,16 +409,54 @@ function handleSuggestionTableCellPaste(view, slice, pluginState) {
|
|
|
372
409
|
const insertFrom = tr.mapping.slice(mapFrom).map(to);
|
|
373
410
|
const sizeBefore = tr.doc.content.size;
|
|
374
411
|
tr.replaceRange(insertFrom, insertFrom, content);
|
|
375
|
-
|
|
412
|
+
let insertTo = insertFrom + (tr.doc.content.size - sizeBefore);
|
|
413
|
+
let pastedFrom = insertFrom;
|
|
414
|
+
const finalParagraph = cell.lastChild;
|
|
415
|
+
const $pasteFrom = tr.doc.resolve(pastedFrom);
|
|
416
|
+
const $pasteTo = tr.doc.resolve(insertTo);
|
|
417
|
+
if (content.openStart > 0 && content.openEnd > 0 && finalParagraph?.type.name === "paragraph" && $pasteFrom.parent.type.name === "paragraph" && $pasteTo.parent.type.name === "paragraph" && $pasteFrom.before() !== $pasteTo.before()) {
|
|
418
|
+
tr.setNodeAttribute($pasteFrom.before(), "pPrMark", {
|
|
419
|
+
kind: "ins",
|
|
420
|
+
info: {
|
|
421
|
+
id: revision.revisionId,
|
|
422
|
+
author: revision.author,
|
|
423
|
+
date: revision.date
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
tr.setNodeAttribute($pasteTo.before(), "pPrMark", finalParagraph.attrs["pPrMark"]);
|
|
427
|
+
recordReplacedParagraphProperties({
|
|
428
|
+
tr,
|
|
429
|
+
position: $pasteTo.before(),
|
|
430
|
+
replaced: finalParagraph,
|
|
431
|
+
revision: {
|
|
432
|
+
id: revision.revisionId,
|
|
433
|
+
author: revision.author,
|
|
434
|
+
date: revision.date
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
const emptyInsertions = [];
|
|
439
|
+
tr.doc.nodeAt(cellPos)?.forEach((node, offset) => {
|
|
440
|
+
const pos = cellPos + 1 + offset;
|
|
441
|
+
if (pos < pastedFrom && node.type.name === "paragraph" && node.content.size === 0 && isCurrentAuthorParagraphInsertion(node.attrs["pPrMark"], pluginState.author)) emptyInsertions.push({
|
|
442
|
+
from: pos,
|
|
443
|
+
to: pos + node.nodeSize
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
const retraction = tr.mapping.maps.length;
|
|
447
|
+
for (const range of emptyInsertions.toReversed()) tr.delete(range.from, range.to);
|
|
448
|
+
const retracted = tr.mapping.slice(retraction);
|
|
449
|
+
pastedFrom = retracted.map(pastedFrom);
|
|
450
|
+
insertTo = retracted.map(insertTo);
|
|
376
451
|
enclosePastedRunRevisions({
|
|
377
452
|
tr,
|
|
378
|
-
from:
|
|
453
|
+
from: pastedFrom,
|
|
379
454
|
to: insertTo,
|
|
380
455
|
revision
|
|
381
456
|
});
|
|
382
|
-
markRangeAsInserted(tr, tr.doc,
|
|
383
|
-
tr.doc.nodesBetween(
|
|
384
|
-
if (node.type.name === "paragraph" && pos >=
|
|
457
|
+
markRangeAsInserted(tr, tr.doc, pastedFrom, insertTo, insertionType, deletionType, revision);
|
|
458
|
+
tr.doc.nodesBetween(pastedFrom, insertTo, (node, pos) => {
|
|
459
|
+
if (node.type.name === "paragraph" && pos >= pastedFrom && pos + node.nodeSize <= insertTo && node.attrs["pPrMark"] == null) tr.setNodeAttribute(pos, "pPrMark", {
|
|
385
460
|
kind: "ins",
|
|
386
461
|
info: {
|
|
387
462
|
id: revision.revisionId,
|
|
@@ -407,11 +482,7 @@ function rotateIntoFinalParagraph(state, tr, replacedPos, attrs) {
|
|
|
407
482
|
const replaced = tr.doc.nodeAt(replacedPos);
|
|
408
483
|
const first = replaced ? tr.doc.nodeAt(replacedPos + replaced.nodeSize) : null;
|
|
409
484
|
if (!replaced || !first) return replacedPos;
|
|
410
|
-
const
|
|
411
|
-
const previousFormatting = {
|
|
412
|
-
...paragraphPropertiesSnapshot(replaced),
|
|
413
|
-
numPr: replacedAttrs.numPr ?? null
|
|
414
|
-
};
|
|
485
|
+
const previousFormatting = paragraphPropertiesSnapshot(replaced);
|
|
415
486
|
const { _sectionProperties: sectionProperties } = replaced.attrs;
|
|
416
487
|
tr.join(replacedPos + replaced.nodeSize);
|
|
417
488
|
tr.setNodeMarkup(replacedPos, void 0, first.attrs);
|
|
@@ -537,15 +608,38 @@ function suggestRangeDeletion(state, tr, from, to) {
|
|
|
537
608
|
* prior author's revision must not be silently overwritten.
|
|
538
609
|
*/
|
|
539
610
|
function handleSuggestionEnter(view, pluginState) {
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
611
|
+
const state = view.state;
|
|
612
|
+
if (state.selection.$from.parent.type.name !== "paragraph") return false;
|
|
613
|
+
const replacement = { tr: null };
|
|
614
|
+
let splitState = state;
|
|
615
|
+
if (state.selection instanceof TextSelection && !state.selection.empty) {
|
|
616
|
+
if (!handleSuggestionDelete(state, (tr) => {
|
|
617
|
+
replacement.tr = tr;
|
|
618
|
+
const styles = documentStylesKey.get(state);
|
|
619
|
+
const plugins = [];
|
|
620
|
+
if (styles) {
|
|
621
|
+
const spec = { ...styles.spec };
|
|
622
|
+
delete spec.appendTransaction;
|
|
623
|
+
delete spec.filterTransaction;
|
|
624
|
+
plugins.push(new Plugin(spec));
|
|
625
|
+
}
|
|
626
|
+
splitState = state.reconfigure({ plugins }).apply(tr);
|
|
627
|
+
}, "forward")) return false;
|
|
628
|
+
}
|
|
629
|
+
const { $from } = splitState.selection;
|
|
542
630
|
const sourcePos = $from.before();
|
|
543
631
|
const sourceAttrs = $from.parent.attrs;
|
|
544
632
|
const captured = { tr: null };
|
|
545
|
-
if (!splitBlockClearBorders(
|
|
633
|
+
if (!splitBlockClearBorders(splitState, (tr) => {
|
|
546
634
|
captured.tr = tr;
|
|
547
635
|
}, view) || !captured.tr) return false;
|
|
548
|
-
const tr = captured.tr;
|
|
636
|
+
const tr = replacement.tr ?? captured.tr;
|
|
637
|
+
if (replacement.tr) {
|
|
638
|
+
for (const step of captured.tr.steps) tr.step(step);
|
|
639
|
+
tr.setSelection(captured.tr.selection.getBookmark().resolve(tr.doc));
|
|
640
|
+
tr.setStoredMarks(captured.tr.storedMarks);
|
|
641
|
+
}
|
|
642
|
+
tr.setMeta(SUGGESTION_META, true);
|
|
549
643
|
if (sourceAttrs["pPrMark"] == null) {
|
|
550
644
|
if (tr.doc.nodeAt(sourcePos)?.type.name === "paragraph") {
|
|
551
645
|
const markInfo = {
|
|
@@ -565,6 +659,24 @@ function handleSuggestionEnter(view, pluginState) {
|
|
|
565
659
|
view.dispatch(tr.scrollIntoView());
|
|
566
660
|
return true;
|
|
567
661
|
}
|
|
662
|
+
/** Find the adjacent visible unit without consuming a bookmark range marker. */
|
|
663
|
+
function caretDeleteTarget(state, direction) {
|
|
664
|
+
const { $from } = state.selection;
|
|
665
|
+
const backward = direction === "backward";
|
|
666
|
+
let from = backward ? $from.pos - 1 : $from.pos;
|
|
667
|
+
let to = backward ? $from.pos : $from.pos + 1;
|
|
668
|
+
while (from >= $from.start() && to <= $from.end()) {
|
|
669
|
+
const adjacent = state.doc.resolve(from).nodeAfter;
|
|
670
|
+
if (adjacent?.type.name !== "bookmarkBoundary") return {
|
|
671
|
+
type: "inlineUnit",
|
|
672
|
+
from,
|
|
673
|
+
to
|
|
674
|
+
};
|
|
675
|
+
from += backward ? -adjacent.nodeSize : adjacent.nodeSize;
|
|
676
|
+
to += backward ? -adjacent.nodeSize : adjacent.nodeSize;
|
|
677
|
+
}
|
|
678
|
+
return { type: "paragraphEdge" };
|
|
679
|
+
}
|
|
568
680
|
/**
|
|
569
681
|
* Detect a caret-at-paragraph-boundary scenario where Backspace/Delete should
|
|
570
682
|
* record a tracked paragraph-mark deletion instead of joining paragraphs.
|
|
@@ -576,8 +688,8 @@ function handleSuggestionEnter(view, pluginState) {
|
|
|
576
688
|
* - Delete-at-paragraph-end → current paragraph.
|
|
577
689
|
*
|
|
578
690
|
* Returns `null` when there is no adjacent sibling paragraph (the join would
|
|
579
|
-
* not produce paragraph-mark merging
|
|
580
|
-
*
|
|
691
|
+
* not produce paragraph-mark merging, e.g. doc start, doc end, or a table).
|
|
692
|
+
* Bookmark markers at an edge do not hide the adjacent paragraph break.
|
|
581
693
|
*/
|
|
582
694
|
function paragraphBoundaryTarget(state, direction) {
|
|
583
695
|
const { $from, empty } = state.selection;
|
|
@@ -585,14 +697,13 @@ function paragraphBoundaryTarget(state, direction) {
|
|
|
585
697
|
if ($from.parent.type.name !== "paragraph") return null;
|
|
586
698
|
const paragraphStart = $from.before();
|
|
587
699
|
const paragraphEnd = $from.after();
|
|
700
|
+
if (caretDeleteTarget(state, direction).type !== "paragraphEdge") return null;
|
|
588
701
|
if (direction === "backward") {
|
|
589
|
-
if ($from.parentOffset !== 0) return null;
|
|
590
702
|
if (paragraphStart === 0) return null;
|
|
591
703
|
const prev = state.doc.resolve(paragraphStart).nodeBefore;
|
|
592
704
|
if (!prev || prev.type.name !== "paragraph") return null;
|
|
593
705
|
return paragraphStart - prev.nodeSize;
|
|
594
706
|
}
|
|
595
|
-
if ($from.parentOffset !== $from.parent.content.size) return null;
|
|
596
707
|
if (paragraphEnd >= state.doc.content.size) return null;
|
|
597
708
|
const next = state.doc.resolve(paragraphEnd).nodeAfter;
|
|
598
709
|
if (!next || next.type.name !== "paragraph") return null;
|
|
@@ -616,7 +727,13 @@ function applyPPrDel(view, targetParagraphPos, pluginState) {
|
|
|
616
727
|
const joined = view.state.doc.nodeAt(joinPos);
|
|
617
728
|
try {
|
|
618
729
|
tr.join(joinPos);
|
|
619
|
-
tr.setNodeAttribute(targetParagraphPos, "pPrMark", null);
|
|
730
|
+
tr.setNodeAttribute(targetParagraphPos, "pPrMark", joined?.type === targetNode.type ? expectParagraphAttrs(joined).pPrMark : null);
|
|
731
|
+
if (joined?.type === targetNode.type) recordReplacedParagraphProperties({
|
|
732
|
+
tr,
|
|
733
|
+
position: targetParagraphPos,
|
|
734
|
+
replaced: joined,
|
|
735
|
+
revision: existingMark.info
|
|
736
|
+
});
|
|
620
737
|
const styleResolver = getDocumentStyleResolver(view.state);
|
|
621
738
|
if (styleResolver && joined?.type === targetNode.type && joined.content.size > 0) rebaseParagraphRuns({
|
|
622
739
|
previousContext: paragraphRunStyleContext(joined, styleResolver),
|
|
@@ -675,15 +792,28 @@ function handleSuggestionDelete(state, dispatch, direction) {
|
|
|
675
792
|
to: rangeTo.pos
|
|
676
793
|
}).sort((a, b) => b.from - a.from);
|
|
677
794
|
for (const { from, to } of ranges) markRangeAsDeleted(tr, state.doc, from, to, insertionType, deletionType, pluginState, state);
|
|
678
|
-
|
|
679
|
-
|
|
795
|
+
if (state.selection instanceof CellSelection) tr.setSelection(state.selection.map(tr.doc, tr.mapping));
|
|
796
|
+
else {
|
|
797
|
+
const cursorPos = tr.mapping.map($to.pos);
|
|
798
|
+
tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
|
|
799
|
+
}
|
|
680
800
|
dispatch(tr.scrollIntoView());
|
|
681
801
|
return true;
|
|
682
802
|
}
|
|
683
803
|
const isBackward = direction === "backward";
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
804
|
+
const target = caretDeleteTarget(state, direction);
|
|
805
|
+
if (target.type === "paragraphEdge") {
|
|
806
|
+
const edge = isBackward ? $from.start() : $from.end();
|
|
807
|
+
if ($from.pos === edge) return false;
|
|
808
|
+
const virtual = EditorState.create({
|
|
809
|
+
doc: state.doc,
|
|
810
|
+
selection: TextSelection.create(state.doc, edge)
|
|
811
|
+
});
|
|
812
|
+
if ((isBackward ? joinBackward : joinForward)(virtual, dispatch)) return true;
|
|
813
|
+
return (isBackward ? selectNodeBackward : selectNodeForward)(virtual, dispatch);
|
|
814
|
+
}
|
|
815
|
+
const deletePos = target.from;
|
|
816
|
+
const deleteEnd = target.to;
|
|
687
817
|
const noteRange = expandNoteReferenceDeletionRange(state.doc, deletePos, deleteEnd);
|
|
688
818
|
const rangeFrom = noteRange?.from ?? deletePos;
|
|
689
819
|
const rangeTo = noteRange?.to ?? deleteEnd;
|
|
@@ -705,19 +835,7 @@ function handleSuggestionDelete(state, dispatch, direction) {
|
|
|
705
835
|
}
|
|
706
836
|
/** Apply the editor's selected-content deletion path for a host cut action. */
|
|
707
837
|
const deleteSelectionAsSuggestion = (state, dispatch) => state.selection.empty ? false : handleSuggestionDelete(state, dispatch, "forward");
|
|
708
|
-
|
|
709
|
-
* Mark the text committed by an IME composition as a tracked insertion.
|
|
710
|
-
*
|
|
711
|
-
* Invoked from `compositionend` (deferred to a microtask) rather than from the
|
|
712
|
-
* `appendTransaction` catch-all. ProseMirror commits composed text as part of
|
|
713
|
-
* its own composition-end flush; adding the insertion mark *synchronously*
|
|
714
|
-
* during that flush re-wraps the very text node the IME just finalized, which
|
|
715
|
-
* corrupts CJK (Japanese / Chinese / Korean) input — characters duplicate or
|
|
716
|
-
* garble. Running one tick later, after the composition has settled, wraps the
|
|
717
|
-
* committed range safely. `markRangeAsInserted` skips nodes that already carry a
|
|
718
|
-
* tracked-change mark, so this is idempotent. eigenpal/docx-editor#938.
|
|
719
|
-
*/
|
|
720
|
-
function markComposedAsInsertion(view, from, pluginState, replaced) {
|
|
838
|
+
function markComposedAsInsertion({ view, from, pluginState, replaced, compositionId }) {
|
|
721
839
|
const insertionType = view.state.schema.marks["insertion"];
|
|
722
840
|
const deletionType = view.state.schema.marks["deletion"];
|
|
723
841
|
if (!insertionType || !deletionType) return;
|
|
@@ -725,9 +843,16 @@ function markComposedAsInsertion(view, from, pluginState, replaced) {
|
|
|
725
843
|
if (to <= from && !replaced) return;
|
|
726
844
|
const tr = view.state.tr;
|
|
727
845
|
tr.setMeta(SUGGESTION_META, true);
|
|
846
|
+
if (compositionId !== null) tr.setMeta("composition", compositionId);
|
|
728
847
|
if (replaced) {
|
|
729
|
-
tr.
|
|
730
|
-
markRangeAsDeleted(tr, tr.doc, from,
|
|
848
|
+
tr.replaceRange(from, from, replaced);
|
|
849
|
+
markRangeAsDeleted(tr, tr.doc, from, tr.mapping.map(from, 1), insertionType, deletionType, pluginState);
|
|
850
|
+
const restoredEnd = tr.doc.resolve(tr.mapping.map(from, 1));
|
|
851
|
+
const restoredStart = tr.doc.resolve(from);
|
|
852
|
+
if (restoredStart.parent.type.name === "paragraph" && restoredEnd.parent.type.name === "paragraph" && restoredStart.parent !== restoredEnd.parent) {
|
|
853
|
+
const mark = expectParagraphAttrs(restoredStart.parent).pPrMark;
|
|
854
|
+
if (mark?.kind === "del") carryIntoParagraphLeft(view.state, tr, view.state.doc.resolve(from).parent, restoredEnd.before(), mark.info);
|
|
855
|
+
}
|
|
731
856
|
}
|
|
732
857
|
const insertionFrom = tr.mapping.map(from, 1);
|
|
733
858
|
const insertionTo = tr.mapping.map(to, 1);
|
|
@@ -777,6 +902,7 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
|
|
|
777
902
|
let composing = false;
|
|
778
903
|
let compositionFrom = null;
|
|
779
904
|
let compositionReplaced = null;
|
|
905
|
+
let compositionId = null;
|
|
780
906
|
return new Plugin({
|
|
781
907
|
key: suggestionModeKey,
|
|
782
908
|
state: {
|
|
@@ -788,6 +914,8 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
|
|
|
788
914
|
};
|
|
789
915
|
},
|
|
790
916
|
apply(tr, state) {
|
|
917
|
+
const nativeCompositionId = tr.getMeta("composition");
|
|
918
|
+
if (composing && tr.docChanged && typeof nativeCompositionId === "number") compositionId = nativeCompositionId;
|
|
791
919
|
const meta = tr.getMeta(suggestionModeKey);
|
|
792
920
|
if (meta) return {
|
|
793
921
|
...state,
|
|
@@ -813,6 +941,7 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
|
|
|
813
941
|
if (!suggestionModeKey.getState(view.state)?.active) return false;
|
|
814
942
|
if (composing) return false;
|
|
815
943
|
composing = true;
|
|
944
|
+
compositionId = null;
|
|
816
945
|
const { from, to } = view.state.selection;
|
|
817
946
|
compositionFrom = from;
|
|
818
947
|
compositionReplaced = from === to ? null : view.state.doc.slice(from, to);
|
|
@@ -826,14 +955,23 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
|
|
|
826
955
|
compositionReplaced = null;
|
|
827
956
|
if (!pluginState?.active || from == null) {
|
|
828
957
|
composing = false;
|
|
958
|
+
compositionId = null;
|
|
829
959
|
return false;
|
|
830
960
|
}
|
|
831
|
-
|
|
961
|
+
afterNativeCompositionFlush(() => {
|
|
832
962
|
try {
|
|
833
963
|
const current = suggestionModeKey.getState(view.state);
|
|
834
|
-
|
|
964
|
+
const nativeCompositionId = compositionId;
|
|
965
|
+
if (current?.active) markComposedAsInsertion({
|
|
966
|
+
view,
|
|
967
|
+
from,
|
|
968
|
+
pluginState: current,
|
|
969
|
+
replaced,
|
|
970
|
+
compositionId: nativeCompositionId
|
|
971
|
+
});
|
|
835
972
|
} finally {
|
|
836
973
|
composing = false;
|
|
974
|
+
compositionId = null;
|
|
837
975
|
}
|
|
838
976
|
});
|
|
839
977
|
return false;
|
|
@@ -855,6 +993,20 @@ function createSuggestionModePlugin(initialActive = false, author = "User") {
|
|
|
855
993
|
if (event.key === "Backspace" || event.key === "Delete") {
|
|
856
994
|
const { $from, empty } = view.state.selection;
|
|
857
995
|
if (event.key === "Backspace" && empty && $from.parentOffset === 0 && $from.parent.type.name === "paragraph" && expectParagraphAttrs($from.parent).numPr?.kind === "reference") return false;
|
|
996
|
+
if (event.key === "Backspace" && clearIndentOnBackspace(view.state, (tr) => {
|
|
997
|
+
const revision = makeMarkAttrs(pluginState);
|
|
998
|
+
recordReplacedParagraphProperties({
|
|
999
|
+
tr,
|
|
1000
|
+
position: $from.before(),
|
|
1001
|
+
replaced: $from.parent,
|
|
1002
|
+
revision: {
|
|
1003
|
+
id: revision.revisionId,
|
|
1004
|
+
author: revision.author,
|
|
1005
|
+
date: revision.date
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
view.dispatch(tr.setMeta("suggestionModeApplied", true));
|
|
1009
|
+
})) return true;
|
|
858
1010
|
const boundaryTarget = paragraphBoundaryTarget(view.state, event.key === "Backspace" ? "backward" : "forward");
|
|
859
1011
|
if (boundaryTarget !== null) return applyPPrDel(view, boundaryTarget, pluginState);
|
|
860
1012
|
const direction = event.key === "Backspace" ? "backward" : "forward";
|
|
@@ -14,6 +14,12 @@ type DirectiveRange = {
|
|
|
14
14
|
* array path the loop iterates (`{% for row in items %}` ⇒ `items`).
|
|
15
15
|
*/
|
|
16
16
|
expr: string;
|
|
17
|
+
/**
|
|
18
|
+
* For an `if` / `elif` whose body is one field path plus a filter chain
|
|
19
|
+
* (`{% if consented | checkbox | label("…") %}`), that path; `expr` keeps
|
|
20
|
+
* the body as authored. Unset for every other tag.
|
|
21
|
+
*/
|
|
22
|
+
conditionPath?: string;
|
|
17
23
|
/** Clause-slot version selector, e.g. "v3" or "latest". */
|
|
18
24
|
clauseVersion?: string;
|
|
19
25
|
/** Loop alias of a `for` marker (`{% for row in items %}` ⇒ `row`); unset
|
|
@@ -3,9 +3,17 @@ import { collectBlockChunks, joinChunks, offsetToDocPos } from "./pmTextScan.js"
|
|
|
3
3
|
import { PluginKey } from "prosemirror-state";
|
|
4
4
|
import { assertNever, isBlockDirectiveKind, scanMarkers } from "@stll/template-conditions";
|
|
5
5
|
//#region src/prosemirror/plugins/templateDirectives.ts
|
|
6
|
+
/**
|
|
7
|
+
* The body of an `if` / `elif` tag as authored: its inner text after the
|
|
8
|
+
* keyword. The grammar reads a body that is one path plus a filter chain as
|
|
9
|
+
* that path, with the chain in `filters`; the document still holds the whole
|
|
10
|
+
* body, and that is what a host keys a condition by.
|
|
11
|
+
*/
|
|
12
|
+
const conditionBody = ({ inner, meta }) => inner.slice(meta.kind.length).trim();
|
|
6
13
|
/** The display expression for a marker (field path, clause name, key,
|
|
7
|
-
* condition, loop array path, loop property). */
|
|
8
|
-
const directiveExpr = (
|
|
14
|
+
* condition as authored, loop array path, loop property). */
|
|
15
|
+
const directiveExpr = (marker) => {
|
|
16
|
+
const { meta } = marker;
|
|
9
17
|
switch (meta.kind) {
|
|
10
18
|
case "placeholder": return meta.expr;
|
|
11
19
|
case "clause": return meta.name;
|
|
@@ -13,7 +21,7 @@ const directiveExpr = (meta) => {
|
|
|
13
21
|
case "ref": return meta.key;
|
|
14
22
|
case "loop": return meta.property;
|
|
15
23
|
case "if":
|
|
16
|
-
case "elif": return
|
|
24
|
+
case "elif": return conditionBody(marker);
|
|
17
25
|
case "for": return meta.path;
|
|
18
26
|
case "else":
|
|
19
27
|
case "endif":
|
|
@@ -63,6 +71,8 @@ const computeBlockDepths = (ranges) => {
|
|
|
63
71
|
};
|
|
64
72
|
/** The loop alias a `for` marker binds, or undefined for every other kind. */
|
|
65
73
|
const directiveAlias = (meta) => meta.kind === "for" ? meta.alias : void 0;
|
|
74
|
+
/** The path a condition's filter chain configures, or undefined without a chain. */
|
|
75
|
+
const directiveConditionPath = (meta) => (meta.kind === "if" || meta.kind === "elif") && meta.filters.length > 0 ? meta.expr : void 0;
|
|
66
76
|
const scanDirectives = (doc) => {
|
|
67
77
|
const ranges = [];
|
|
68
78
|
for (const chunks of collectBlockChunks(doc)) {
|
|
@@ -73,27 +83,31 @@ const scanDirectives = (doc) => {
|
|
|
73
83
|
if (sole && sole.raw === trimmed && isBlockDirectiveKind(sole.meta.kind)) {
|
|
74
84
|
const last = chunks.at(-1);
|
|
75
85
|
const alias = directiveAlias(sole.meta);
|
|
86
|
+
const conditionPath = directiveConditionPath(sole.meta);
|
|
76
87
|
ranges.push({
|
|
77
88
|
from: chunks[0]?.start ?? 0,
|
|
78
89
|
to: last ? last.end ?? last.start + last.text.length : 0,
|
|
79
90
|
kind: sole.meta.kind,
|
|
80
|
-
expr: directiveExpr(sole
|
|
91
|
+
expr: directiveExpr(sole),
|
|
81
92
|
block: true,
|
|
82
|
-
...alias !== void 0 ? { alias } : {}
|
|
93
|
+
...alias !== void 0 ? { alias } : {},
|
|
94
|
+
...conditionPath !== void 0 ? { conditionPath } : {}
|
|
83
95
|
});
|
|
84
96
|
continue;
|
|
85
97
|
}
|
|
86
98
|
for (const marker of scanMarkers(joined)) {
|
|
87
99
|
const clauseVersion = marker.meta.kind === "clause" ? marker.meta.version : void 0;
|
|
88
100
|
const alias = directiveAlias(marker.meta);
|
|
101
|
+
const conditionPath = directiveConditionPath(marker.meta);
|
|
89
102
|
ranges.push({
|
|
90
103
|
from: offsetToDocPos(chunks, marker.start),
|
|
91
104
|
to: offsetToDocPos(chunks, marker.end, "end"),
|
|
92
105
|
kind: marker.meta.kind,
|
|
93
|
-
expr: directiveExpr(marker
|
|
106
|
+
expr: directiveExpr(marker),
|
|
94
107
|
block: false,
|
|
95
108
|
...clauseVersion !== void 0 ? { clauseVersion } : {},
|
|
96
|
-
...alias !== void 0 ? { alias } : {}
|
|
109
|
+
...alias !== void 0 ? { alias } : {},
|
|
110
|
+
...conditionPath !== void 0 ? { conditionPath } : {}
|
|
97
111
|
});
|
|
98
112
|
}
|
|
99
113
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { scanDirectives } from "./templateDirectives.js";
|
|
2
2
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
3
|
import { Decoration, DecorationSet } from "prosemirror-view";
|
|
4
|
-
import {
|
|
4
|
+
import { isFieldPath } from "@stll/template-conditions";
|
|
5
5
|
//#region src/prosemirror/plugins/templatePreviewValues.ts
|
|
6
6
|
/** Concatenated plain text of a preview value. */
|
|
7
7
|
const templatePreviewValueText = (value) => typeof value === "string" ? value : value.runs.map((run) => run.text).join("");
|
|
@@ -64,28 +64,21 @@ function buildValueWidget(value, mode) {
|
|
|
64
64
|
return span;
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
/** Loop alias the bare-path probe binds. Any identifier does; it is discarded. */
|
|
68
|
-
const CHAIN_PROBE_ALIAS = "__folioCondition";
|
|
69
67
|
/** A field path opens with a letter or underscore, so `9x` and `-x` are not one. */
|
|
70
68
|
const FIELD_PATH_HEAD_RE = /^[\p{L}_]/u;
|
|
71
69
|
/**
|
|
72
70
|
* The bare field path a condition's filter chain hangs off, or undefined when
|
|
73
|
-
* the
|
|
71
|
+
* the tag is not a path plus a chain.
|
|
74
72
|
*
|
|
75
|
-
* A host may write the tag the
|
|
76
|
-
*
|
|
77
|
-
* keying `conditions` by the bare path.
|
|
78
|
-
*
|
|
79
|
-
* that
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* which is what keeps a real expression on exact matching.
|
|
73
|
+
* A host may write the tag with the chain of the boolean it names —
|
|
74
|
+
* `{% if buyer_is_a_consumer | checkbox | ai("Is the buyer …") %}` — while
|
|
75
|
+
* keying `conditions` by the bare path. The grammar's argument-aware scan
|
|
76
|
+
* splits the chain (`label("a | b")` stays one argument) and reports the path
|
|
77
|
+
* as `conditionPath`; an expression that is not a path plus a chain of known
|
|
78
|
+
* filters (`a and b`, `items|length > 0`, `ai("a|b")`) has none, which is what
|
|
79
|
+
* keeps a real expression on exact matching.
|
|
83
80
|
*/
|
|
84
|
-
const filterChainPath = (
|
|
85
|
-
const meta = classifyMarker(`for ${CHAIN_PROBE_ALIAS} in ${expr}`, "statement");
|
|
86
|
-
if (meta?.kind !== "for" || !isFieldPath(meta.path) || !FIELD_PATH_HEAD_RE.test(meta.path)) return;
|
|
87
|
-
return meta.path;
|
|
88
|
-
};
|
|
81
|
+
const filterChainPath = ({ conditionPath }) => conditionPath !== void 0 && isFieldPath(conditionPath) && FIELD_PATH_HEAD_RE.test(conditionPath) ? conditionPath : void 0;
|
|
89
82
|
/**
|
|
90
83
|
* The verdict stored under one key. Only a key the host set itself, holding a
|
|
91
84
|
* real boolean, counts: an inherited property (`constructor`, or a boolean the
|
|
@@ -102,10 +95,10 @@ const ownVerdict = (conditions, key) => {
|
|
|
102
95
|
* by the bare path its filter chain hangs off. `undefined` means the host said
|
|
103
96
|
* nothing about this block, which leaves it exactly as authored.
|
|
104
97
|
*/
|
|
105
|
-
const conditionVerdict = (conditions,
|
|
106
|
-
const exact = ownVerdict(conditions, expr);
|
|
98
|
+
const conditionVerdict = (conditions, opener) => {
|
|
99
|
+
const exact = ownVerdict(conditions, opener.expr);
|
|
107
100
|
if (exact !== void 0) return exact;
|
|
108
|
-
const path = filterChainPath(
|
|
101
|
+
const path = filterChainPath(opener);
|
|
109
102
|
return path === void 0 ? void 0 : ownVerdict(conditions, path);
|
|
110
103
|
};
|
|
111
104
|
/**
|
|
@@ -157,7 +150,7 @@ function collectHiddenRanges(ranges, conditions, mode) {
|
|
|
157
150
|
}
|
|
158
151
|
if (block === void 0 || block.opener.kind !== "if") continue;
|
|
159
152
|
const { opener, branches } = block;
|
|
160
|
-
const verdict = conditionVerdict(conditions, opener
|
|
153
|
+
const verdict = conditionVerdict(conditions, opener);
|
|
161
154
|
if (verdict === void 0) continue;
|
|
162
155
|
if (!verdict) {
|
|
163
156
|
paired.push({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createNumberingIdAllocator } from "../docx/numberingIds.js";
|
|
1
2
|
import { getCachedNumberingMap, isBulletLevel } from "../docx/numberingParser.js";
|
|
2
3
|
import { paragraphNumberingLevel, paragraphNumberingReference, paragraphNumberingReferenceId } from "../docx/numberingReference.js";
|
|
3
4
|
import { expectParagraphAttrs } from "./attrs/index.js";
|
|
@@ -20,16 +21,11 @@ const remappedAbstractNumId = (abstractNumId, remap) => {
|
|
|
20
21
|
if (abstractNumId === null || abstractNumId === void 0 || remap.baseAbstractNumIds.has(abstractNumId)) return abstractNumId;
|
|
21
22
|
let next = remap.abstractNumIds.get(abstractNumId);
|
|
22
23
|
if (next === void 0) {
|
|
23
|
-
next = remap.
|
|
24
|
+
next = remap.abstractIds.next();
|
|
24
25
|
remap.abstractNumIds.set(abstractNumId, next);
|
|
25
26
|
}
|
|
26
27
|
return next;
|
|
27
28
|
};
|
|
28
|
-
const maxOf = (ids, floor) => {
|
|
29
|
-
let max = floor;
|
|
30
|
-
for (const id of ids) max = Math.max(max, id);
|
|
31
|
-
return max;
|
|
32
|
-
};
|
|
33
29
|
/**
|
|
34
30
|
* The story in `state` as it should be written into a package whose numbering
|
|
35
31
|
* is now `numbering`, and that numbering completed with the story's lists.
|
|
@@ -41,10 +37,16 @@ const storyListNumbering = (state, numbering) => {
|
|
|
41
37
|
const definedMap = numbering ? getCachedNumberingMap(numbering) : null;
|
|
42
38
|
const referenced = /* @__PURE__ */ new Set();
|
|
43
39
|
const colliding = /* @__PURE__ */ new Set();
|
|
40
|
+
const existingAbstractNumIds = /* @__PURE__ */ new Set([...(numbering?.abstractNums ?? []).map(({ abstractNumId }) => abstractNumId), ...(packageDefinitions?.abstractNums ?? []).map(({ abstractNumId }) => abstractNumId)]);
|
|
44
41
|
state.doc.descendants((node) => {
|
|
45
42
|
if (node.type.name !== "paragraph") return true;
|
|
46
43
|
for (const numId of paragraphListReferences(node)) referenced.add(numId);
|
|
47
44
|
const attrs = expectParagraphAttrs(node);
|
|
45
|
+
if (typeof attrs.listAbstractNumId === "number") existingAbstractNumIds.add(attrs.listAbstractNumId);
|
|
46
|
+
for (const change of attrs._propertyChanges ?? []) {
|
|
47
|
+
const abstractId = change.previousFormatting?.listAbstractNumId;
|
|
48
|
+
if (typeof abstractId === "number") existingAbstractNumIds.add(abstractId);
|
|
49
|
+
}
|
|
48
50
|
const numId = paragraphNumberingReferenceId(attrs.numPr);
|
|
49
51
|
if (numId !== void 0 && !storyBase.has(numId) && definedMap?.hasNumbering(numId)) {
|
|
50
52
|
const level = definedMap.getLevel(numId, paragraphNumberingLevel(attrs.numPr) ?? 0);
|
|
@@ -56,12 +58,12 @@ const storyListNumbering = (state, numbering) => {
|
|
|
56
58
|
doc: state.doc,
|
|
57
59
|
numbering: completeNumberingForDoc(numbering, state.doc)
|
|
58
60
|
};
|
|
59
|
-
|
|
61
|
+
const numIds = createNumberingIdAllocator("num", [...defined, ...referenced]);
|
|
60
62
|
const remap = {
|
|
61
|
-
numIds: new Map([...colliding].map((numId) => [numId,
|
|
63
|
+
numIds: new Map([...colliding].map((numId) => [numId, numIds.next()])),
|
|
62
64
|
baseAbstractNumIds: new Set((packageDefinitions?.abstractNums ?? []).map(({ abstractNumId }) => abstractNumId)),
|
|
63
65
|
abstractNumIds: /* @__PURE__ */ new Map(),
|
|
64
|
-
|
|
66
|
+
abstractIds: createNumberingIdAllocator("abstract", existingAbstractNumIds)
|
|
65
67
|
};
|
|
66
68
|
const tr = new Transform(state.doc);
|
|
67
69
|
state.doc.descendants((node, pos) => {
|
|
@@ -15,7 +15,12 @@ type TextInput = {
|
|
|
15
15
|
declare const dispatchEditorTextInput: <TView extends TextInputDispatchTarget<TView>>(view: TView, input: string | TextInput) => void;
|
|
16
16
|
/** Shared DOM boundary for editor runtimes and standalone suggestion views. */
|
|
17
17
|
declare const handleEditorBeforeInput: (view: EditorView, event: InputEvent) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Plugin compositionend handlers run before ProseMirror queues its final DOM
|
|
20
|
+
* flush. A second microtask runs after that flush and sees the committed state.
|
|
21
|
+
*/
|
|
22
|
+
declare const afterNativeCompositionFlush: (callback: () => void) => void;
|
|
18
23
|
/** Route committed text through the model before the browser mutates mark spans. */
|
|
19
24
|
declare const createTextInputPlugin: () => Plugin<any>;
|
|
20
25
|
//#endregion
|
|
21
|
-
export { createTextInputPlugin, dispatchEditorTextInput, handleEditorBeforeInput };
|
|
26
|
+
export { afterNativeCompositionFlush, createTextInputPlugin, dispatchEditorTextInput, handleEditorBeforeInput };
|
|
@@ -45,6 +45,13 @@ const handleEditorBeforeInput = (view, event) => {
|
|
|
45
45
|
});
|
|
46
46
|
return true;
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Plugin compositionend handlers run before ProseMirror queues its final DOM
|
|
50
|
+
* flush. A second microtask runs after that flush and sees the committed state.
|
|
51
|
+
*/
|
|
52
|
+
const afterNativeCompositionFlush = (callback) => {
|
|
53
|
+
queueMicrotask(() => queueMicrotask(callback));
|
|
54
|
+
};
|
|
48
55
|
/** Route committed text through the model before the browser mutates mark spans. */
|
|
49
56
|
const createTextInputPlugin = () => {
|
|
50
57
|
const compositions = /* @__PURE__ */ new WeakMap();
|
|
@@ -55,7 +62,7 @@ const createTextInputPlugin = () => {
|
|
|
55
62
|
},
|
|
56
63
|
compositionend(view, event) {
|
|
57
64
|
compositions.set(view, event);
|
|
58
|
-
|
|
65
|
+
afterNativeCompositionFlush(() => {
|
|
59
66
|
if (compositions.get(view) === event) compositions.delete(view);
|
|
60
67
|
});
|
|
61
68
|
return false;
|
|
@@ -67,4 +74,4 @@ const createTextInputPlugin = () => {
|
|
|
67
74
|
} } });
|
|
68
75
|
};
|
|
69
76
|
//#endregion
|
|
70
|
-
export { createTextInputPlugin, dispatchEditorTextInput, handleEditorBeforeInput };
|
|
77
|
+
export { afterNativeCompositionFlush, createTextInputPlugin, dispatchEditorTextInput, handleEditorBeforeInput };
|