@stll/folio-core 0.35.0 → 0.36.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 +370 -118
- package/dist/ai-edits/headless.js +16 -10
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/snapshot.js +6 -1
- package/dist/ai-edits/types.d.ts +19 -6
- package/dist/ai-edits/word-diff.d.ts +18 -3
- package/dist/ai-edits/word-diff.js +556 -67
- package/dist/compare/compare.js +7 -16
- package/dist/compare/plan.js +8 -5
- package/dist/compare/verification.d.ts +8 -9
- package/dist/compare/verification.js +51 -46
- package/dist/compat/eigenpal.d.ts +2 -2
- package/dist/compat/eigenpal.js +2 -2
- package/dist/controller/headerFooterEditorManager.js +5 -5
- package/dist/controller/hiddenEditorApi.js +1 -1
- package/dist/controller/noteEditorManager.js +2 -2
- package/dist/document-operations.d.ts +5 -3
- package/dist/document-operations.js +32 -5
- package/dist/docx/commentParser.js +2 -4
- package/dist/docx/commentRangeIntegrity.js +2 -4
- package/dist/docx/documentClone.d.ts +2 -0
- package/dist/docx/documentClone.js +2 -0
- package/dist/docx/headerFooterParser.js +2 -4
- package/dist/docx/normalizeBaseDirection.js +5 -7
- package/dist/docx/paragraphParser.js +27 -29
- package/dist/docx/paragraphPropertySource.d.ts +49 -0
- package/dist/docx/paragraphPropertySource.js +113 -0
- package/dist/docx/runConsolidator.js +3 -4
- package/dist/docx/runParser.js +2 -15
- package/dist/docx/sectionParser.js +1 -15
- package/dist/docx/serializer/paragraphSerializer.js +287 -30
- package/dist/docx/serializer/runSerializer.js +2 -10
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -10
- package/dist/docx/serializer/tableSerializer.js +4 -23
- package/dist/docx/serializer/trackedChangeAttributes.d.ts +9 -0
- package/dist/docx/serializer/trackedChangeAttributes.js +21 -0
- package/dist/docx/server/createBilingualDocument.js +8 -10
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +22 -13
- package/dist/docx/server/validateDocxConformance.js +17 -13
- package/dist/docx/tableParser.js +1 -37
- package/dist/docx/trackedChangeInfo.d.ts +11 -0
- package/dist/docx/trackedChangeInfo.js +38 -0
- package/dist/docx/verbatimCapture.d.ts +20 -9
- package/dist/docx/verbatimCapture.js +208 -8
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +6 -1
- package/dist/docx/xmlSafety.d.ts +1 -1
- package/dist/docx/xmlSafety.js +157 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/layout-bridge/convert/headerFooterLayout.js +4 -4
- package/dist/layout-bridge/convert/toFlowBlocks.js +12 -7
- package/dist/markdown/renderBlock.js +7 -10
- package/dist/prosemirror/attrs/index.js +15 -0
- package/dist/prosemirror/commands/comments.js +365 -32
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +26 -4
- package/dist/prosemirror/commands/propertyChangeScope.js +65 -3
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -4
- package/dist/prosemirror/conversion/fromProseDoc.js +130 -18
- package/dist/prosemirror/conversion/toProseDoc.js +27 -12
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +48 -4
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +20 -7
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +2 -0
- package/dist/prosemirror/paragraphAlignment.d.ts +12 -0
- package/dist/prosemirror/paragraphAlignment.js +13 -0
- package/dist/prosemirror/plugins/suggestionMode.js +1 -1
- package/dist/prosemirror/schema/marks.d.ts +2 -0
- package/dist/prosemirror/schema/nodes.d.ts +12 -1
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/redline.js +4 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/utils/mergeDocumentContent.js +3 -3
- package/dist/utils/replaceText.js +2 -1
- package/dist/version-comparison.js +6 -4
- package/dist/watermark/index.js +2 -4
- package/package.json +7 -2
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../attrs/index.js";
|
|
2
|
-
import { paragraphEndsItsContainer } from "../containerFinalParagraph.js";
|
|
2
|
+
import { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer } from "../containerFinalParagraph.js";
|
|
3
3
|
import { textFormattingToMarks } from "../conversion/toProseDoc.js";
|
|
4
4
|
import { markStructuralChange } from "../extensions/features/ParagraphChangeTrackerExtension.js";
|
|
5
|
+
import { getDocumentStyleResolver } from "../plugins/documentStyles.js";
|
|
5
6
|
import { getFolioNodeRevisionCarriers } from "../revisionCarriers.js";
|
|
6
7
|
import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
|
|
7
8
|
import { getTableCellMergeChange } from "../tableCellMergeRevision.js";
|
|
8
9
|
import { holdsNoContent } from "../zeroWidthAnchors.js";
|
|
9
|
-
import { paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
|
|
10
|
+
import { paragraphPropertiesSnapshot, paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, removeParagraphPropertyChanges, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch } from "./propertyChangeScope.js";
|
|
10
11
|
import { hasMatchingCollapsedTableCellMerge, resolveCollapsedTableCellMerge, resolveVisibleTableCellMerge } from "./tableCellMergeResolution.js";
|
|
11
12
|
import { TableMap, removeRow } from "prosemirror-tables";
|
|
12
13
|
import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
|
|
@@ -59,6 +60,7 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
59
60
|
return (state, dispatch) => {
|
|
60
61
|
const insertionType = state.schema.marks["insertion"];
|
|
61
62
|
const deletionType = state.schema.marks["deletion"];
|
|
63
|
+
const styleResolver = getDocumentStyleResolver(state);
|
|
62
64
|
const keepType = mode === "accept" ? insertionType : deletionType;
|
|
63
65
|
const removeType = mode === "accept" ? deletionType : insertionType;
|
|
64
66
|
const revisionSet = revisionIds === void 0 ? null : new Set(revisionIds);
|
|
@@ -78,16 +80,21 @@ function resolveChange(from, to, mode, revisionIds) {
|
|
|
78
80
|
let nextAttrs = null;
|
|
79
81
|
const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
|
|
80
82
|
if (Array.isArray(propertyChanges) && propertyChanges.length > 0 && boundaryCovered) {
|
|
81
|
-
const
|
|
82
|
-
if (
|
|
83
|
-
const
|
|
83
|
+
const matchesPropertyChange = (change) => revisionSet === null || revisionSet.has(change.info.id);
|
|
84
|
+
if (propertyChanges.some(matchesPropertyChange)) {
|
|
85
|
+
const rejection = mode === "reject" ? removeParagraphPropertyChanges(propertyChanges, matchesPropertyChange) : null;
|
|
86
|
+
let remaining;
|
|
87
|
+
if (rejection === null) remaining = propertyChanges.filter((change) => !matchesPropertyChange(change));
|
|
88
|
+
else if (rejection.type === "unchanged") remaining = propertyChanges;
|
|
89
|
+
else remaining = rejection.remaining;
|
|
84
90
|
nextAttrs = {
|
|
85
91
|
...node.attrs,
|
|
86
92
|
_propertyChanges: remaining.length > 0 ? remaining : null
|
|
87
93
|
};
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
nextAttrs
|
|
94
|
+
if (rejection?.type === "restore-previous") {
|
|
95
|
+
const previousAlignmentFromStyle = styleResolver ? styleResolver.resolveParagraphStyle(rejection.previousFormatting?.styleId).paragraphFormatting?.alignment : expectParagraphAttrs(node).alignmentFromStyle;
|
|
96
|
+
Object.assign(nextAttrs, paragraphRejectAttrPatch(rejection.previousFormatting, previousAlignmentFromStyle));
|
|
97
|
+
nextAttrs["_originalFormatting"] = paragraphRejectOriginalFormatting(rejection.previousFormatting, node.attrs["_originalFormatting"]);
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
100
|
}
|
|
@@ -480,6 +487,7 @@ function isPPrMarkAttr(value) {
|
|
|
480
487
|
* the two ends belong together, not because they resolve differently.
|
|
481
488
|
*/
|
|
482
489
|
const paragraphMarkWasAdded = (kind) => kind === "ins" || kind === "moveTo";
|
|
490
|
+
const isAddedPPrMarkAttr = (value) => isPPrMarkAttr(value) && (value.kind === "ins" || value.kind === "moveTo");
|
|
483
491
|
/**
|
|
484
492
|
* Whether the paragraph's mark carries a section's properties.
|
|
485
493
|
*
|
|
@@ -682,6 +690,13 @@ const readSuggestedMarker = (marker) => {
|
|
|
682
690
|
revisionId
|
|
683
691
|
};
|
|
684
692
|
};
|
|
693
|
+
const readSuggestedParagraphPropertyChange = (change) => {
|
|
694
|
+
if (change.info.provenance !== "suggested" || typeof change.info.suggestionId !== "string") return null;
|
|
695
|
+
return {
|
|
696
|
+
suggestionId: change.info.suggestionId,
|
|
697
|
+
revisionId: change.info.id
|
|
698
|
+
};
|
|
699
|
+
};
|
|
685
700
|
const readStructuralSuggestion = (node) => {
|
|
686
701
|
const attrs = node.attrs;
|
|
687
702
|
const name = node.type.name;
|
|
@@ -754,6 +769,20 @@ const collectSuggestions = (state) => {
|
|
|
754
769
|
entry.kinds.add(structural.kind);
|
|
755
770
|
entry.revisionIds.add(structural.revisionId);
|
|
756
771
|
}
|
|
772
|
+
if (node.type.name === "paragraph") {
|
|
773
|
+
const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
|
|
774
|
+
if (Array.isArray(propertyChanges)) for (const change of propertyChanges) {
|
|
775
|
+
const suggested = readSuggestedParagraphPropertyChange(change);
|
|
776
|
+
if (!suggested) continue;
|
|
777
|
+
const entry = entryFor(suggested.suggestionId);
|
|
778
|
+
entry.segments.push({
|
|
779
|
+
from: pos,
|
|
780
|
+
to: pos + node.nodeSize
|
|
781
|
+
});
|
|
782
|
+
entry.kinds.add("formatting");
|
|
783
|
+
entry.revisionIds.add(suggested.revisionId);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
757
786
|
if (!node.isInline) return;
|
|
758
787
|
for (const mark of node.marks) {
|
|
759
788
|
if (!isSuggestionMark(mark)) continue;
|
|
@@ -848,6 +877,22 @@ const normalizeAcceptDate = (date) => {
|
|
|
848
877
|
const parsed = new Date(date);
|
|
849
878
|
return Number.isNaN(parsed.getTime()) ? (/* @__PURE__ */ new Date()).toISOString() : parsed.toISOString();
|
|
850
879
|
};
|
|
880
|
+
const acceptedParagraphMark = (node, author, date) => {
|
|
881
|
+
if (node.type.name !== "paragraph") return null;
|
|
882
|
+
const marker = node.attrs["_suggestedInsert"];
|
|
883
|
+
if (typeof marker !== "object" || marker === null || !("revisionId" in marker)) return null;
|
|
884
|
+
if (typeof marker.revisionId !== "number") return null;
|
|
885
|
+
const initials = "initials" in marker ? marker.initials : void 0;
|
|
886
|
+
return {
|
|
887
|
+
kind: "ins",
|
|
888
|
+
info: {
|
|
889
|
+
id: marker.revisionId,
|
|
890
|
+
author,
|
|
891
|
+
date,
|
|
892
|
+
...typeof initials === "string" ? { initials } : {}
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
};
|
|
851
896
|
/**
|
|
852
897
|
* The node-attr patch that converts a suggested block/table revision into a
|
|
853
898
|
* normal (`"user"`) one authored by the accepting user. Returns `null` when the
|
|
@@ -868,23 +913,15 @@ const convertStructuralSuggestionAttrs = (node, author, date) => {
|
|
|
868
913
|
const name = node.type.name;
|
|
869
914
|
const insertMarker = attrs["_suggestedInsert"];
|
|
870
915
|
if ((name === "paragraph" || name === "table") && typeof insertMarker === "object" && insertMarker !== null) {
|
|
871
|
-
const marker = insertMarker;
|
|
872
916
|
if (name === "table") return {
|
|
873
917
|
...attrs,
|
|
874
918
|
_suggestedInsert: null
|
|
875
919
|
};
|
|
876
|
-
|
|
920
|
+
const paragraphMark = acceptedParagraphMark(node, author, date);
|
|
921
|
+
if (paragraphMark) return {
|
|
877
922
|
...attrs,
|
|
878
923
|
_suggestedInsert: null,
|
|
879
|
-
pPrMark:
|
|
880
|
-
kind: "ins",
|
|
881
|
-
info: {
|
|
882
|
-
id: marker.revisionId,
|
|
883
|
-
author,
|
|
884
|
-
date,
|
|
885
|
-
...typeof marker.initials === "string" ? { initials: marker.initials } : {}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
924
|
+
pPrMark: attrs["pPrMark"] ?? paragraphMark
|
|
888
925
|
};
|
|
889
926
|
}
|
|
890
927
|
if (node.type.name === "tableRow") {
|
|
@@ -909,6 +946,247 @@ const convertStructuralSuggestionAttrs = (node, author, date) => {
|
|
|
909
946
|
}
|
|
910
947
|
return null;
|
|
911
948
|
};
|
|
949
|
+
const reauthorParagraphPropertySuggestions = ({ node, matchesSuggestion, author, date }) => {
|
|
950
|
+
if (node.type.name !== "paragraph") return null;
|
|
951
|
+
const propertyChanges = expectParagraphAttrs(node)._propertyChanges;
|
|
952
|
+
if (!Array.isArray(propertyChanges)) return null;
|
|
953
|
+
let changed = false;
|
|
954
|
+
const next = propertyChanges.map((change) => {
|
|
955
|
+
const suggested = readSuggestedParagraphPropertyChange(change);
|
|
956
|
+
if (!suggested || !matchesSuggestion(suggested.suggestionId)) return change;
|
|
957
|
+
changed = true;
|
|
958
|
+
return Object.assign({}, change, { info: {
|
|
959
|
+
...change.info,
|
|
960
|
+
author,
|
|
961
|
+
date,
|
|
962
|
+
provenance: "user",
|
|
963
|
+
suggestionId: null
|
|
964
|
+
} });
|
|
965
|
+
});
|
|
966
|
+
return changed ? next : null;
|
|
967
|
+
};
|
|
968
|
+
const appendRotatedParagraphPropertyChange = ({ tr, currentPosition, previous, mark, fallbackAuthor }) => {
|
|
969
|
+
const current = tr.doc.nodeAt(currentPosition);
|
|
970
|
+
if (!current || current.type.name !== "paragraph") return false;
|
|
971
|
+
const author = typeof mark.info.author === "string" ? mark.info.author : fallbackAuthor;
|
|
972
|
+
if (author === void 0) return false;
|
|
973
|
+
const existing = expectParagraphAttrs(current)._propertyChanges;
|
|
974
|
+
tr.setNodeMarkup(currentPosition, void 0, {
|
|
975
|
+
...current.attrs,
|
|
976
|
+
_propertyChanges: [...Array.isArray(existing) ? existing : [], {
|
|
977
|
+
type: "paragraphPropertyChange",
|
|
978
|
+
info: {
|
|
979
|
+
id: mark.info.id,
|
|
980
|
+
author,
|
|
981
|
+
...typeof mark.info.date === "string" ? { date: mark.info.date } : {},
|
|
982
|
+
...typeof mark.info.initials === "string" ? { initials: mark.info.initials } : {}
|
|
983
|
+
},
|
|
984
|
+
previousFormatting: paragraphPropertiesSnapshot(previous)
|
|
985
|
+
}]
|
|
986
|
+
});
|
|
987
|
+
return true;
|
|
988
|
+
};
|
|
989
|
+
/**
|
|
990
|
+
* Shift a final run of inserted paragraph marks one paragraph to the left.
|
|
991
|
+
* The container-final paragraph stays markless, while every inserted
|
|
992
|
+
* paragraph keeps a same-revision property snapshot that makes targeted
|
|
993
|
+
* rejection restore the paragraph whose mark now carries its break.
|
|
994
|
+
*/
|
|
995
|
+
const rotateAddedFinalParagraphBreaks = ({ tr, shouldRotate, fallbackAuthor }) => {
|
|
996
|
+
const paragraphTypeName = tr.doc.type.schema.nodes["paragraph"]?.name ?? "paragraph";
|
|
997
|
+
const finals = finalParagraphsOf(tr.doc, paragraphTypeName).filter(({ node, position }) => shouldRotate(node, position));
|
|
998
|
+
for (const { position: finalPosition } of finals) {
|
|
999
|
+
const final = tr.doc.nodeAt(finalPosition);
|
|
1000
|
+
const finalMark = final?.attrs["pPrMark"];
|
|
1001
|
+
if (!final || !isAddedPPrMarkAttr(finalMark)) continue;
|
|
1002
|
+
tr.setNodeAttribute(finalPosition, "pPrMark", null);
|
|
1003
|
+
if (!shiftAddedParagraphBreakBefore({
|
|
1004
|
+
tr,
|
|
1005
|
+
paragraphPosition: finalPosition,
|
|
1006
|
+
mark: finalMark,
|
|
1007
|
+
...fallbackAuthor !== void 0 && { fallbackAuthor }
|
|
1008
|
+
})) return false;
|
|
1009
|
+
}
|
|
1010
|
+
return true;
|
|
1011
|
+
};
|
|
1012
|
+
const rebaseRotatedParagraphPropertyChange = ({ tr, currentPosition, previous, revisionId }) => {
|
|
1013
|
+
const current = tr.doc.nodeAt(currentPosition);
|
|
1014
|
+
if (!current || current.type.name !== "paragraph") return false;
|
|
1015
|
+
const existing = expectParagraphAttrs(current)._propertyChanges;
|
|
1016
|
+
if (!Array.isArray(existing)) return false;
|
|
1017
|
+
if (existing.filter(({ info }) => info.id === revisionId).length !== 1) return false;
|
|
1018
|
+
tr.setNodeMarkup(currentPosition, void 0, {
|
|
1019
|
+
...current.attrs,
|
|
1020
|
+
_propertyChanges: existing.map((change) => change.info.id === revisionId ? Object.assign({}, change, { previousFormatting: paragraphPropertiesSnapshot(previous) }) : change)
|
|
1021
|
+
});
|
|
1022
|
+
return true;
|
|
1023
|
+
};
|
|
1024
|
+
/**
|
|
1025
|
+
* Rebase every property change in one rotated paragraph-break chain to the
|
|
1026
|
+
* formatting of its original, leftmost carrier. A later targeted suggestion
|
|
1027
|
+
* may extend an already-rotated chain, so normalizing only the newly shifted
|
|
1028
|
+
* prefix would leave the serialized history dependent on acceptance order.
|
|
1029
|
+
*/
|
|
1030
|
+
const rebaseAddedParagraphBreakChain = ({ tr, carrierPosition, previous }) => {
|
|
1031
|
+
const carrier = tr.doc.nodeAt(carrierPosition);
|
|
1032
|
+
if (!carrier || carrier.type.name !== "paragraph") return false;
|
|
1033
|
+
const resolved = tr.doc.resolve(carrierPosition);
|
|
1034
|
+
let current = carrier;
|
|
1035
|
+
let nextPosition = carrierPosition + carrier.nodeSize;
|
|
1036
|
+
for (let index = resolved.index() + 1; index < resolved.parent.childCount; index++) {
|
|
1037
|
+
const currentMark = current.attrs["pPrMark"];
|
|
1038
|
+
if (!isAddedPPrMarkAttr(currentMark)) return true;
|
|
1039
|
+
const sibling = resolved.parent.child(index);
|
|
1040
|
+
if (sibling.type.name !== carrier.type.name) {
|
|
1041
|
+
if (sibling.type.spec["tableRole"] === "table") return false;
|
|
1042
|
+
nextPosition += sibling.nodeSize;
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
if (!rebaseRotatedParagraphPropertyChange({
|
|
1046
|
+
tr,
|
|
1047
|
+
currentPosition: nextPosition,
|
|
1048
|
+
previous,
|
|
1049
|
+
revisionId: currentMark.info.id
|
|
1050
|
+
})) return false;
|
|
1051
|
+
const next = tr.doc.nodeAt(nextPosition);
|
|
1052
|
+
if (!next || next.type.name !== carrier.type.name) return false;
|
|
1053
|
+
current = next;
|
|
1054
|
+
nextPosition += sibling.nodeSize;
|
|
1055
|
+
}
|
|
1056
|
+
return !isAddedPPrMarkAttr(current.attrs["pPrMark"]);
|
|
1057
|
+
};
|
|
1058
|
+
/**
|
|
1059
|
+
* Insert an added paragraph break immediately before `paragraphPosition`.
|
|
1060
|
+
* Existing added marks shift left as one chain until the first free carrier;
|
|
1061
|
+
* each shifted mark keeps a property snapshot on the paragraph to its right.
|
|
1062
|
+
*/
|
|
1063
|
+
const shiftAddedParagraphBreakBefore = ({ tr, paragraphPosition, mark, fallbackAuthor, firstPropertyChange = { type: "append" } }) => {
|
|
1064
|
+
const paragraph = tr.doc.nodeAt(paragraphPosition);
|
|
1065
|
+
if (!paragraph || paragraph.type.name !== "paragraph") return false;
|
|
1066
|
+
const resolved = tr.doc.resolve(paragraphPosition);
|
|
1067
|
+
const carrier = addedBreakCarrierBefore(resolved, paragraph.type.name);
|
|
1068
|
+
if (!carrier) return false;
|
|
1069
|
+
const path = [{
|
|
1070
|
+
node: paragraph,
|
|
1071
|
+
position: paragraphPosition
|
|
1072
|
+
}];
|
|
1073
|
+
let position = paragraphPosition;
|
|
1074
|
+
for (let index = resolved.index() - 1; index >= 0; index--) {
|
|
1075
|
+
const sibling = resolved.parent.child(index);
|
|
1076
|
+
position -= sibling.nodeSize;
|
|
1077
|
+
if (sibling.type.name !== paragraph.type.name) {
|
|
1078
|
+
if (sibling.type.spec["tableRole"] === "table") break;
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
path.push({
|
|
1082
|
+
node: sibling,
|
|
1083
|
+
position
|
|
1084
|
+
});
|
|
1085
|
+
if (position === carrier.position) break;
|
|
1086
|
+
}
|
|
1087
|
+
if (path.at(-1)?.position !== carrier.position) return false;
|
|
1088
|
+
const propertyChangeBaseline = carrier.node;
|
|
1089
|
+
let carriedMark = mark;
|
|
1090
|
+
for (let index = 1; index < path.length; index++) {
|
|
1091
|
+
const current = path[index - 1];
|
|
1092
|
+
const previous = path[index];
|
|
1093
|
+
if (!current || !previous) return false;
|
|
1094
|
+
const displacedMark = previous.node.attrs["pPrMark"];
|
|
1095
|
+
tr.setNodeAttribute(previous.position, "pPrMark", carriedMark);
|
|
1096
|
+
if (!(index === 1 && firstPropertyChange.type === "rebase" ? rebaseRotatedParagraphPropertyChange({
|
|
1097
|
+
tr,
|
|
1098
|
+
currentPosition: current.position,
|
|
1099
|
+
previous: propertyChangeBaseline,
|
|
1100
|
+
revisionId: firstPropertyChange.revisionId
|
|
1101
|
+
}) : appendRotatedParagraphPropertyChange({
|
|
1102
|
+
tr,
|
|
1103
|
+
currentPosition: current.position,
|
|
1104
|
+
previous: propertyChangeBaseline,
|
|
1105
|
+
mark: carriedMark,
|
|
1106
|
+
...fallbackAuthor !== void 0 && { fallbackAuthor }
|
|
1107
|
+
}))) return false;
|
|
1108
|
+
if (displacedMark == null) return rebaseAddedParagraphBreakChain({
|
|
1109
|
+
tr,
|
|
1110
|
+
carrierPosition: carrier.position,
|
|
1111
|
+
previous: propertyChangeBaseline
|
|
1112
|
+
});
|
|
1113
|
+
if (!isAddedPPrMarkAttr(displacedMark)) return false;
|
|
1114
|
+
carriedMark = displacedMark;
|
|
1115
|
+
}
|
|
1116
|
+
return false;
|
|
1117
|
+
};
|
|
1118
|
+
/**
|
|
1119
|
+
* Place an accepted paragraph's break when a following accepted insertion has
|
|
1120
|
+
* already rotated its own break onto that paragraph.
|
|
1121
|
+
*/
|
|
1122
|
+
const placeAcceptedParagraphBreakBefore = ({ tr, paragraphPosition, mark, fallbackAuthor }) => {
|
|
1123
|
+
const paragraph = tr.doc.nodeAt(paragraphPosition);
|
|
1124
|
+
const displaced = paragraph?.attrs["pPrMark"];
|
|
1125
|
+
if (!paragraph || paragraph.type.name !== "paragraph" || !isPPrMarkAttr(displaced) || !paragraphMarkWasAdded(displaced.kind)) return false;
|
|
1126
|
+
return shiftAddedParagraphBreakBefore({
|
|
1127
|
+
tr,
|
|
1128
|
+
paragraphPosition,
|
|
1129
|
+
mark,
|
|
1130
|
+
fallbackAuthor
|
|
1131
|
+
});
|
|
1132
|
+
};
|
|
1133
|
+
/**
|
|
1134
|
+
* Remember non-final added breaks before suggested nodes are removed. If one
|
|
1135
|
+
* becomes final because of those removals, it must rotate with its own
|
|
1136
|
+
* revision rather than remain as an unresolvable final mark.
|
|
1137
|
+
*/
|
|
1138
|
+
const collectAddedBreakRotationCandidates = (doc) => {
|
|
1139
|
+
const paragraphTypeName = doc.type.schema.nodes["paragraph"]?.name ?? "paragraph";
|
|
1140
|
+
const finalPositions = new Set(finalParagraphsOf(doc, paragraphTypeName).map(({ position }) => position));
|
|
1141
|
+
const candidates = [];
|
|
1142
|
+
doc.descendants((node, position) => {
|
|
1143
|
+
if (node.type.name !== paragraphTypeName || finalPositions.has(position)) return;
|
|
1144
|
+
const mark = node.attrs["pPrMark"];
|
|
1145
|
+
if (isPPrMarkAttr(mark) && paragraphMarkWasAdded(mark.kind)) candidates.push({
|
|
1146
|
+
position,
|
|
1147
|
+
revisionId: mark.info.id
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
return candidates;
|
|
1151
|
+
};
|
|
1152
|
+
const mappedRotationCandidatePredicate = ({ candidates, tr }) => {
|
|
1153
|
+
const revisionIdsByPosition = /* @__PURE__ */ new Map();
|
|
1154
|
+
for (const candidate of candidates) {
|
|
1155
|
+
const mapped = tr.mapping.mapResult(candidate.position, 1);
|
|
1156
|
+
if (mapped.deleted) continue;
|
|
1157
|
+
const mappedPosition = mapped.pos;
|
|
1158
|
+
const revisionIds = revisionIdsByPosition.get(mappedPosition) ?? /* @__PURE__ */ new Set();
|
|
1159
|
+
revisionIds.add(candidate.revisionId);
|
|
1160
|
+
revisionIdsByPosition.set(mappedPosition, revisionIds);
|
|
1161
|
+
}
|
|
1162
|
+
return (node, position) => {
|
|
1163
|
+
const mark = node.attrs["pPrMark"];
|
|
1164
|
+
return isPPrMarkAttr(mark) && paragraphMarkWasAdded(mark.kind) && revisionIdsByPosition.get(position)?.has(mark.info.id) === true;
|
|
1165
|
+
};
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* Remove suggested nodes without deleting a following accepted insertion's
|
|
1169
|
+
* paragraph break that has rotated onto one of them.
|
|
1170
|
+
*/
|
|
1171
|
+
const removeSuggestedInsertNodes = ({ tr, positions, resolvedRevisionIds }) => {
|
|
1172
|
+
for (const position of positions.toSorted((left, right) => right - left)) {
|
|
1173
|
+
const node = tr.doc.nodeAt(position);
|
|
1174
|
+
if (!node) continue;
|
|
1175
|
+
const mark = node.attrs["pPrMark"];
|
|
1176
|
+
const followingBreak = node.type.name === "paragraph" && isAddedPPrMarkAttr(mark) && !resolvedRevisionIds.has(mark.info.id) ? mark : null;
|
|
1177
|
+
tr.delete(position, position + node.nodeSize);
|
|
1178
|
+
if (followingBreak && !shiftAddedParagraphBreakBefore({
|
|
1179
|
+
tr,
|
|
1180
|
+
paragraphPosition: position,
|
|
1181
|
+
mark: followingBreak,
|
|
1182
|
+
firstPropertyChange: {
|
|
1183
|
+
type: "rebase",
|
|
1184
|
+
revisionId: followingBreak.info.id
|
|
1185
|
+
}
|
|
1186
|
+
})) return false;
|
|
1187
|
+
}
|
|
1188
|
+
return true;
|
|
1189
|
+
};
|
|
912
1190
|
/**
|
|
913
1191
|
* Convert suggested marks AND block/table node revisions to normal (`"user"`)
|
|
914
1192
|
* tracked changes. `matchesSuggestion(id)` selects which suggestion to convert
|
|
@@ -921,16 +1199,37 @@ const acceptSuggestions = (matchesSuggestion, options) => {
|
|
|
921
1199
|
const runPropertyChangeType = state.schema.marks["runPropertyChange"];
|
|
922
1200
|
const date = normalizeAcceptDate(options.date);
|
|
923
1201
|
const tr = state.tr;
|
|
1202
|
+
const acceptedParagraphBreaks = [];
|
|
924
1203
|
let changed = false;
|
|
925
1204
|
state.doc.descendants((node, pos) => {
|
|
926
1205
|
const structural = readStructuralSuggestion(node);
|
|
1206
|
+
let nextAttrs = null;
|
|
927
1207
|
if (structural && matchesSuggestion(structural.suggestionId)) {
|
|
928
|
-
|
|
929
|
-
if (
|
|
930
|
-
|
|
931
|
-
|
|
1208
|
+
nextAttrs = convertStructuralSuggestionAttrs(node, options.author, date);
|
|
1209
|
+
if (structural.kind === "insertBlock") {
|
|
1210
|
+
const mark = acceptedParagraphMark(node, options.author, date);
|
|
1211
|
+
if (!mark) return;
|
|
1212
|
+
acceptedParagraphBreaks.push({
|
|
1213
|
+
position: pos,
|
|
1214
|
+
mark,
|
|
1215
|
+
hadBreak: node.attrs["pPrMark"] != null
|
|
1216
|
+
});
|
|
932
1217
|
}
|
|
933
1218
|
}
|
|
1219
|
+
const propertyChanges = reauthorParagraphPropertySuggestions({
|
|
1220
|
+
node,
|
|
1221
|
+
matchesSuggestion,
|
|
1222
|
+
author: options.author,
|
|
1223
|
+
date
|
|
1224
|
+
});
|
|
1225
|
+
if (propertyChanges) nextAttrs = {
|
|
1226
|
+
...nextAttrs ?? node.attrs,
|
|
1227
|
+
_propertyChanges: propertyChanges
|
|
1228
|
+
};
|
|
1229
|
+
if (nextAttrs) {
|
|
1230
|
+
tr.setNodeMarkup(pos, void 0, nextAttrs);
|
|
1231
|
+
changed = true;
|
|
1232
|
+
}
|
|
934
1233
|
if (!node.isInline) return;
|
|
935
1234
|
const from = pos;
|
|
936
1235
|
const to = pos + node.nodeSize;
|
|
@@ -964,6 +1263,18 @@ const acceptSuggestions = (matchesSuggestion, options) => {
|
|
|
964
1263
|
}));
|
|
965
1264
|
}
|
|
966
1265
|
});
|
|
1266
|
+
for (const accepted of acceptedParagraphBreaks) if (accepted.hadBreak && !placeAcceptedParagraphBreakBefore({
|
|
1267
|
+
tr,
|
|
1268
|
+
paragraphPosition: accepted.position,
|
|
1269
|
+
mark: accepted.mark,
|
|
1270
|
+
fallbackAuthor: options.author
|
|
1271
|
+
})) return false;
|
|
1272
|
+
const directlyMarkedParagraphPositions = new Set(acceptedParagraphBreaks.filter(({ hadBreak }) => !hadBreak).map(({ position }) => position));
|
|
1273
|
+
if (directlyMarkedParagraphPositions.size > 0 && !rotateAddedFinalParagraphBreaks({
|
|
1274
|
+
tr,
|
|
1275
|
+
shouldRotate: (_node, position) => directlyMarkedParagraphPositions.has(position),
|
|
1276
|
+
fallbackAuthor: options.author
|
|
1277
|
+
})) return false;
|
|
967
1278
|
if (!changed) return false;
|
|
968
1279
|
if (dispatch) dispatch(tr);
|
|
969
1280
|
return true;
|
|
@@ -1001,6 +1312,7 @@ function rejectSuggestion(suggestionId) {
|
|
|
1001
1312
|
return (state, dispatch) => {
|
|
1002
1313
|
const entry = collectSuggestions(state).get(suggestionId);
|
|
1003
1314
|
if (!entry) return false;
|
|
1315
|
+
const rotationCandidates = entry.kinds.has("insertBlock") || entry.kinds.has("insertTable") ? collectAddedBreakRotationCandidates(state.doc) : [];
|
|
1004
1316
|
let tr = null;
|
|
1005
1317
|
if (entry.revisionIds.size > 0) {
|
|
1006
1318
|
const ranges = mergeSegments(entry.segments);
|
|
@@ -1015,10 +1327,18 @@ function rejectSuggestion(suggestionId) {
|
|
|
1015
1327
|
const structural = readStructuralSuggestion(node);
|
|
1016
1328
|
if (structural?.isNodeInsert && structural.suggestionId === suggestionId) positions.push(pos);
|
|
1017
1329
|
});
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1330
|
+
if (!removeSuggestedInsertNodes({
|
|
1331
|
+
tr: workingTr,
|
|
1332
|
+
positions,
|
|
1333
|
+
resolvedRevisionIds: entry.revisionIds
|
|
1334
|
+
})) return false;
|
|
1335
|
+
if (positions.length > 0 && rotationCandidates.length > 0 && !rotateAddedFinalParagraphBreaks({
|
|
1336
|
+
tr: workingTr,
|
|
1337
|
+
shouldRotate: mappedRotationCandidatePredicate({
|
|
1338
|
+
candidates: rotationCandidates,
|
|
1339
|
+
tr: workingTr
|
|
1340
|
+
})
|
|
1341
|
+
})) return false;
|
|
1022
1342
|
if (workingTr.steps.length === 0) return false;
|
|
1023
1343
|
if (dispatch) dispatch(workingTr);
|
|
1024
1344
|
return true;
|
|
@@ -1030,7 +1350,12 @@ function rejectAllSuggestions() {
|
|
|
1030
1350
|
const bySuggestion = collectSuggestions(state);
|
|
1031
1351
|
if (bySuggestion.size === 0) return false;
|
|
1032
1352
|
const revisionIds = /* @__PURE__ */ new Set();
|
|
1033
|
-
|
|
1353
|
+
let removesWholeNode = false;
|
|
1354
|
+
for (const entry of bySuggestion.values()) {
|
|
1355
|
+
removesWholeNode ||= entry.kinds.has("insertBlock") || entry.kinds.has("insertTable");
|
|
1356
|
+
for (const id of entry.revisionIds) revisionIds.add(id);
|
|
1357
|
+
}
|
|
1358
|
+
const rotationCandidates = removesWholeNode ? collectAddedBreakRotationCandidates(state.doc) : [];
|
|
1034
1359
|
let tr = null;
|
|
1035
1360
|
if (revisionIds.size > 0) resolveChange(0, state.doc.content.size, "reject", [...revisionIds])(state, (resolved) => {
|
|
1036
1361
|
tr = resolved;
|
|
@@ -1040,10 +1365,18 @@ function rejectAllSuggestions() {
|
|
|
1040
1365
|
workingTr.doc.descendants((node, pos) => {
|
|
1041
1366
|
if (readStructuralSuggestion(node)?.isNodeInsert) positions.push(pos);
|
|
1042
1367
|
});
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1368
|
+
if (!removeSuggestedInsertNodes({
|
|
1369
|
+
tr: workingTr,
|
|
1370
|
+
positions,
|
|
1371
|
+
resolvedRevisionIds: revisionIds
|
|
1372
|
+
})) return false;
|
|
1373
|
+
if (positions.length > 0 && rotationCandidates.length > 0 && !rotateAddedFinalParagraphBreaks({
|
|
1374
|
+
tr: workingTr,
|
|
1375
|
+
shouldRotate: mappedRotationCandidatePredicate({
|
|
1376
|
+
candidates: rotationCandidates,
|
|
1377
|
+
tr: workingTr
|
|
1378
|
+
})
|
|
1379
|
+
})) return false;
|
|
1047
1380
|
if (workingTr.steps.length === 0) return false;
|
|
1048
1381
|
if (dispatch) dispatch(workingTr);
|
|
1049
1382
|
return true;
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
|
-
import { ParagraphPropertyChangeAttrs } from "../schema/nodes.js";
|
|
2
|
+
import { ParagraphAttrs, ParagraphPropertyChangeAttrs } from "../schema/nodes.js";
|
|
3
|
+
import { Node } from "prosemirror-model";
|
|
3
4
|
//#region src/prosemirror/commands/propertyChangeScope.d.ts
|
|
4
5
|
type AttrPatch = Record<string, unknown>;
|
|
5
6
|
type ParagraphPropertySnapshot = NonNullable<ParagraphPropertyChangeAttrs["previousFormatting"]>;
|
|
7
|
+
type ParagraphPropertyChangeRemoval = {
|
|
8
|
+
type: "unchanged";
|
|
9
|
+
} | {
|
|
10
|
+
type: "keep-live";
|
|
11
|
+
remaining: ParagraphPropertyChangeAttrs[];
|
|
12
|
+
} | {
|
|
13
|
+
type: "restore-previous";
|
|
14
|
+
remaining: ParagraphPropertyChangeAttrs[];
|
|
15
|
+
previousFormatting: ParagraphPropertyChangeAttrs["previousFormatting"];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Remove selected entries from an oldest-to-newest paragraph-property chain.
|
|
19
|
+
* A retained entry immediately after a removed run inherits that run's
|
|
20
|
+
* earliest previous snapshot. Only a removed trailing run restores the live
|
|
21
|
+
* paragraph properties. This makes repeated scoped rejection independent of
|
|
22
|
+
* the order in which revisions are resolved.
|
|
23
|
+
*/
|
|
24
|
+
declare const removeParagraphPropertyChanges: (changes: readonly ParagraphPropertyChangeAttrs[], shouldRemove: (change: ParagraphPropertyChangeAttrs) => boolean) => ParagraphPropertyChangeRemoval;
|
|
6
25
|
/**
|
|
7
26
|
* Paragraph attrs governed by a `w:pPrChange` — the attrs
|
|
8
27
|
* `paragraphFormattingToAttrs` (conversion/toProseDoc.ts) can produce from a
|
|
@@ -19,9 +38,12 @@ type ParagraphPropertySnapshot = NonNullable<ParagraphPropertyChangeAttrs["previ
|
|
|
19
38
|
* `runInWithNext` (`w:specVanish` lives in that rPr)
|
|
20
39
|
* - load-time style/numbering bookkeeping the command layer cannot recompute
|
|
21
40
|
* without a style resolver: `numPrFromStyle`, the `list*` rendering attrs,
|
|
22
|
-
* `
|
|
41
|
+
* `alignmentFromStyle`, `spacingFromDocDefaults`,
|
|
42
|
+
* `spacingFromImplicitDefaultStyle`
|
|
23
43
|
*/
|
|
24
44
|
declare const PPR_CHANGE_SCOPED_ATTR_KEYS: readonly ["styleId", "numPr", "alignment", "spaceBefore", "spaceAfter", "lineSpacing", "lineSpacingRule", "lineSpacingExplicit", "snapToGrid", "spacingExplicit", "indentLeft", "indentRight", "indentFirstLine", "hangingIndent", "borders", "shading", "tabs", "pageBreakBefore", "keepNext", "keepLines", "widowControl", "contextualSpacing", "outlineLevel", "direction", "_autospacingBase"];
|
|
45
|
+
/** The in-scope paragraph properties as they stand, for a `w:pPrChange` record. */
|
|
46
|
+
declare const paragraphPropertiesSnapshot: (node: Node) => ParagraphPropertySnapshot;
|
|
25
47
|
/**
|
|
26
48
|
* Build the attr patch that rejecting one pPrChange applies to a paragraph:
|
|
27
49
|
* every in-scope key set to the stored previous value, or reset to `null`
|
|
@@ -29,7 +51,7 @@ declare const PPR_CHANGE_SCOPED_ATTR_KEYS: readonly ["styleId", "numPr", "alignm
|
|
|
29
51
|
* the scoped set (editor-created list suggestions snapshot list-rendering
|
|
30
52
|
* bookkeeping attrs) merge through 1:1 so their pre-change values restore too.
|
|
31
53
|
*/
|
|
32
|
-
declare function paragraphRejectAttrPatch(previousFormatting: ParagraphPropertySnapshot | null | undefined): AttrPatch;
|
|
54
|
+
declare function paragraphRejectAttrPatch(previousFormatting: ParagraphPropertySnapshot | null | undefined, alignmentFromStyle?: ParagraphAttrs["alignmentFromStyle"]): AttrPatch;
|
|
33
55
|
/**
|
|
34
56
|
* Rebuild the paragraph's `_originalFormatting` (the serializer's pPr source)
|
|
35
57
|
* after a reject: the stored old pPr wholesale within CT_PPrBase scope, with
|
|
@@ -66,4 +88,4 @@ declare function tableRowRejectAttrPatch(previousFormatting: document_d_exports.
|
|
|
66
88
|
*/
|
|
67
89
|
declare function tableCellRejectAttrPatch(previousFormatting: document_d_exports.TableCellFormatting | undefined, liveOriginalFormatting: document_d_exports.TableCellFormatting | null | undefined): AttrPatch;
|
|
68
90
|
//#endregion
|
|
69
|
-
export { PPR_CHANGE_SCOPED_ATTR_KEYS, paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch };
|
|
91
|
+
export { PPR_CHANGE_SCOPED_ATTR_KEYS, ParagraphPropertyChangeRemoval, paragraphPropertiesSnapshot, paragraphRejectAttrPatch, paragraphRejectOriginalFormatting, removeParagraphPropertyChanges, sectionRejectProperties, tableCellRejectAttrPatch, tableRejectAttrPatch, tableRowRejectAttrPatch };
|