@stll/folio-core 0.12.0 → 0.13.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.d.ts +2 -1
- package/dist/ai-edits/apply.js +118 -40
- package/dist/ai-edits/blockRange.js +1 -1
- package/dist/ai-edits/clean-text.js +2 -1
- package/dist/ai-edits/headless.js +3 -3
- package/dist/ai-edits/snapshot.js +21 -3
- package/dist/ai-edits/table-cell-mutations.js +2 -1
- package/dist/ai-edits/table-row-column-mutations.d.ts +9 -1
- package/dist/ai-edits/table-row-column-mutations.js +1 -1
- package/dist/ai-edits/types.d.ts +28 -1
- package/dist/ai-edits/word-diff.js +21 -0
- package/dist/compat/eigenpal.d.ts +3 -3
- package/dist/compat/eigenpal.js +3 -3
- package/dist/controller/headerFooterEditorManager.js +1 -1
- package/dist/controller/hiddenEditorManager.js +12 -1
- package/dist/controller/layoutPipeline.js +1 -1
- package/dist/controller/noteEditorManager.js +1 -1
- package/dist/document-operations.d.ts +13 -13
- package/dist/document-operations.js +31 -21
- package/dist/docx/blockContentParser.js +19 -5
- package/dist/docx/commentRangeIntegrity.d.ts +6 -0
- package/dist/docx/commentRangeIntegrity.js +186 -0
- package/dist/docx/conformance.js +57 -3
- package/dist/docx/encryption/agileDecryption.js +12 -2
- package/dist/docx/encryption/compoundFile.js +6 -1
- package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
- package/dist/docx/encryption/encryptionInfo.js +18 -2
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/ensureParaIds.js +7 -6
- package/dist/docx/groupDrawingParser.js +10 -5
- package/dist/docx/hyperlinkParser.js +1 -1
- package/dist/docx/imageParser.js +10 -6
- package/dist/docx/metadataPrivacy.js +17 -3
- package/dist/docx/paragraphParser.js +7 -3
- package/dist/docx/rezip.js +19 -8
- package/dist/docx/runParser.js +13 -4
- package/dist/docx/sectionParser.js +17 -4
- package/dist/docx/selectiveSave.js +4 -0
- package/dist/docx/selectiveXmlPatch.d.ts +22 -1
- package/dist/docx/selectiveXmlPatch.js +61 -1
- package/dist/docx/serializer/blockSdtSerializer.js +3 -2
- package/dist/docx/serializer/commentSerializer.js +4 -4
- package/dist/docx/serializer/paragraphSerializer.js +16 -14
- package/dist/docx/serializer/runSerializer.js +21 -19
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
- package/dist/docx/serializer/tableSerializer.js +9 -7
- package/dist/docx/serializer/xmlUtils.d.ts +13 -1
- package/dist/docx/serializer/xmlUtils.js +27 -1
- package/dist/docx/server/extractDocxText.js +55 -8
- package/dist/docx/settingsParser.js +15 -2
- package/dist/docx/tableParser.d.ts +8 -1
- package/dist/docx/tableParser.js +13 -3
- package/dist/docx/unzip.js +1 -0
- package/dist/docx/vmlImageParser.js +11 -3
- package/dist/docx/xmlParser.js +11 -1
- package/dist/fonts/embeddedFonts.d.ts +33 -5
- package/dist/fonts/embeddedFonts.js +37 -8
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/layout-bridge/convert/toFlowBlocks.js +15 -0
- package/dist/layout-engine/index.js +16 -3
- package/dist/layout-engine/measure/cache.js +2 -0
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +3 -3
- package/dist/layout-engine/measure/measureBlocks.js +9 -1
- package/dist/layout-engine/measure/measureHelpers.d.ts +10 -1
- package/dist/layout-engine/measure/measureHelpers.js +12 -1
- package/dist/layout-engine/measure/measureParagraph.js +16 -0
- package/dist/layout-engine/types.d.ts +22 -2
- package/dist/layout-painter/renderImage.d.ts +12 -1
- package/dist/layout-painter/renderImage.js +21 -4
- package/dist/layout-painter/renderPage.js +3 -2
- package/dist/layout-painter/renderParagraph.js +33 -9
- package/dist/layout-painter/renderTable.js +2 -1
- package/dist/layout-painter/renderWatermark.d.ts +1 -1
- package/dist/layout-painter/renderWatermark.js +2 -1
- package/dist/markdown/fromMarkdown.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +1 -1
- package/dist/prosemirror/attrs/index.js +21 -0
- package/dist/prosemirror/commands/comments.d.ts +76 -1
- package/dist/prosemirror/commands/comments.js +401 -28
- package/dist/prosemirror/commands/hyperlink.js +15 -1
- package/dist/prosemirror/commands/image.js +7 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.js +1 -1
- package/dist/prosemirror/commentIdAllocator.js +13 -3
- package/dist/prosemirror/conversion/fromProseDoc.js +122 -5
- package/dist/prosemirror/conversion/index.js +1 -1
- package/dist/prosemirror/conversion/toProseDoc.js +20 -8
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
- package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +49 -13
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -3
- package/dist/prosemirror/index.d.ts +1 -1
- package/dist/prosemirror/index.js +1 -1
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +2 -2
- package/dist/prosemirror/plugins/contentControlWidgets.js +4 -4
- package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
- package/dist/prosemirror/plugins/revisionIds.js +57 -0
- package/dist/prosemirror/plugins/suggestionMode.js +4 -3
- package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
- package/dist/prosemirror/runFormattingMarkNames.js +40 -0
- package/dist/prosemirror/schema/index.d.ts +1 -1
- package/dist/prosemirror/schema/marks.d.ts +27 -4
- package/dist/prosemirror/schema/marks.js +15 -0
- package/dist/prosemirror/schema/nodes.d.ts +50 -2
- package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +3 -3
- package/dist/utils/clipboard.d.ts +10 -1
- package/dist/utils/clipboard.js +33 -6
- package/dist/utils/colorResolver.d.ts +8 -1
- package/dist/utils/colorResolver.js +35 -5
- package/dist/utils/fontResolver.d.ts +2 -1
- package/dist/utils/fontResolver.js +9 -3
- package/dist/utils/hexId.d.ts +11 -1
- package/dist/utils/hexId.js +11 -1
- package/dist/utils/sanitizeImageSrc.d.ts +21 -0
- package/dist/utils/sanitizeImageSrc.js +30 -0
- package/dist/utils/stripXmlDeclarations.d.ts +11 -0
- package/dist/utils/stripXmlDeclarations.js +35 -0
- package/dist/version-comparison.d.ts +22 -2
- package/dist/version-comparison.js +41 -12
- package/dist/watermark/index.d.ts +11 -1
- package/dist/watermark/index.js +22 -1
- package/package.json +3 -2
|
@@ -5,10 +5,13 @@ import { ShapeOutlineStyleSchema, narrowEnum } from "../../docx/parserEnums.js";
|
|
|
5
5
|
import { numPrEqual } from "../../docx/numberingParser.js";
|
|
6
6
|
import { pixelsToEmu } from "../../utils/units.js";
|
|
7
7
|
import { autospacingMatchesBase, hasAutospacingBaseSide } from "../autospacingBase.js";
|
|
8
|
+
import { RUN_FORMATTING_MARK_NAMES } from "../runFormattingMarkNames.js";
|
|
8
9
|
import { expectTextBoxAnchorAttrs } from "../textBoxAnchorAttrs.js";
|
|
9
10
|
import { assertValidProseMirrorDocument } from "../validation.js";
|
|
10
11
|
import { runShadingAttrsToShading } from "./runShadingMark.js";
|
|
11
12
|
import { sdtPropertiesFromAttrs, sdtPropertiesMatchAttrs } from "./sdtAttrs.js";
|
|
13
|
+
import { textFormattingToMarks } from "./toProseDoc.js";
|
|
14
|
+
import { Fragment } from "prosemirror-model";
|
|
12
15
|
//#region src/prosemirror/conversion/fromProseDoc.ts
|
|
13
16
|
function normalizeShapeOutlineStyle(style) {
|
|
14
17
|
if (!style) return;
|
|
@@ -71,11 +74,120 @@ function fromProseDoc(pmDoc, baseDocument) {
|
|
|
71
74
|
};
|
|
72
75
|
return { package: { document: documentBody } };
|
|
73
76
|
}
|
|
77
|
+
const isSuggestedMark = (mark) => mark.attrs["provenance"] === "suggested";
|
|
78
|
+
const hasSuggestedInsertion = (marks) => marks.some((mark) => mark.type.name === "insertion" && isSuggestedMark(mark));
|
|
79
|
+
/**
|
|
80
|
+
* Compute the mark set an inline node should keep once its suggested tracked
|
|
81
|
+
* changes are stripped. Suggested deletions are dropped so their text survives
|
|
82
|
+
* as plain content; a suggested `runPropertyChange` is dropped and the run's
|
|
83
|
+
* live formatting is reverted to the recorded `previousFormatting`.
|
|
84
|
+
*
|
|
85
|
+
* Returns the input array unchanged when there is nothing to strip so callers
|
|
86
|
+
* can skip rebuilding the node.
|
|
87
|
+
*/
|
|
88
|
+
function stripSuggestedInlineMarks(marks) {
|
|
89
|
+
const hasSuggestedDeletion = marks.some((mark) => mark.type.name === "deletion" && isSuggestedMark(mark));
|
|
90
|
+
const suggestedRunPropertyChange = marks.find((mark) => mark.type.name === "runPropertyChange" && isSuggestedMark(mark));
|
|
91
|
+
if (!hasSuggestedDeletion && !suggestedRunPropertyChange) return marks;
|
|
92
|
+
let next = marks.filter((mark) => !((mark.type.name === "deletion" || mark.type.name === "runPropertyChange") && isSuggestedMark(mark)));
|
|
93
|
+
if (suggestedRunPropertyChange) {
|
|
94
|
+
const previousFormatting = expectRunPropertyChangeMarkAttrs(suggestedRunPropertyChange).changes.at(0)?.previousFormatting;
|
|
95
|
+
next = next.filter((mark) => !RUN_FORMATTING_MARK_NAMES.has(mark.type.name));
|
|
96
|
+
for (const restored of textFormattingToMarks(previousFormatting)) next = restored.addToSet(next);
|
|
97
|
+
}
|
|
98
|
+
return next;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Compute the node attrs a block/structural node keeps once its suggested
|
|
102
|
+
* revision markers are neutralized. Returns `null` when nothing changes.
|
|
103
|
+
*
|
|
104
|
+
* - a suggested `trDel` / `cellMarker` (insertion or deletion) is cleared so
|
|
105
|
+
* the row/cell serializes as though the proposed change never happened;
|
|
106
|
+
* merge markers never carry suggestion provenance (structurally excluded);
|
|
107
|
+
* - suggested INSERT markers are handled by the caller, which drops the whole
|
|
108
|
+
* node instead of clearing an attr.
|
|
109
|
+
*
|
|
110
|
+
* Reads go through the typed attr readers (adapter-boundary convention).
|
|
111
|
+
*/
|
|
112
|
+
function stripSuggestedNodeAttrs(node) {
|
|
113
|
+
const name = node.type.name;
|
|
114
|
+
if (name === "tableRow") {
|
|
115
|
+
const rowAttrs = expectTableRowAttrs(node);
|
|
116
|
+
if (rowAttrs.trDel?.provenance === "suggested") return {
|
|
117
|
+
...rowAttrs,
|
|
118
|
+
trDel: null
|
|
119
|
+
};
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (name === "tableCell" || name === "tableHeader") {
|
|
123
|
+
const cellAttrs = expectTableCellAttrs(node);
|
|
124
|
+
const marker = cellAttrs.cellMarker;
|
|
125
|
+
if (marker && marker.kind !== "merge" && marker.info.provenance === "suggested") return {
|
|
126
|
+
...cellAttrs,
|
|
127
|
+
cellMarker: null
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Whether a block/structural node is a suggested whole-node INSERT and must be
|
|
134
|
+
* dropped from serialization entirely (paragraph, table, row, or column cell).
|
|
135
|
+
*/
|
|
136
|
+
function isSuggestedInsertedNode(node) {
|
|
137
|
+
const name = node.type.name;
|
|
138
|
+
if (name === "paragraph") return expectParagraphAttrs(node)._suggestedInsert != null;
|
|
139
|
+
if (name === "table") return expectTableAttrs(node)._suggestedInsert != null;
|
|
140
|
+
if (name === "tableRow") return expectTableRowAttrs(node).trIns?.provenance === "suggested";
|
|
141
|
+
if (name === "tableCell" || name === "tableHeader") {
|
|
142
|
+
const marker = expectTableCellAttrs(node).cellMarker;
|
|
143
|
+
return marker?.kind === "ins" && marker.info.provenance === "suggested";
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Recursively rewrite a ProseMirror node tree, removing every suggested
|
|
149
|
+
* tracked change. Returns `null` when the node itself must be dropped: a
|
|
150
|
+
* suggested-inserted inline node, or a suggested-inserted block/row/cell.
|
|
151
|
+
* Surviving block nodes are rebuilt with their suggested delete/merge markers
|
|
152
|
+
* cleared and their children stripped.
|
|
153
|
+
*/
|
|
154
|
+
function mapSuggestionStrippedNode(node) {
|
|
155
|
+
if (node.isInline) {
|
|
156
|
+
if (hasSuggestedInsertion(node.marks)) return null;
|
|
157
|
+
const marks = stripSuggestedInlineMarks(node.marks);
|
|
158
|
+
return marks === node.marks ? node : node.mark(marks);
|
|
159
|
+
}
|
|
160
|
+
if (isSuggestedInsertedNode(node)) return null;
|
|
161
|
+
const nextAttrs = stripSuggestedNodeAttrs(node);
|
|
162
|
+
const children = [];
|
|
163
|
+
let changed = nextAttrs !== null;
|
|
164
|
+
node.forEach((child) => {
|
|
165
|
+
const mapped = mapSuggestionStrippedNode(child);
|
|
166
|
+
if (mapped === null) {
|
|
167
|
+
changed = true;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (mapped !== child) changed = true;
|
|
171
|
+
children.push(mapped);
|
|
172
|
+
});
|
|
173
|
+
if (!changed) return node;
|
|
174
|
+
const content = Fragment.fromArray(children);
|
|
175
|
+
return nextAttrs === null ? node.copy(content) : node.type.create(nextAttrs, content, node.marks);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Strip suggested (AI-proposed) tracked changes from a document so it
|
|
179
|
+
* serializes as though the suggestion never happened. See the call site in
|
|
180
|
+
* {@link extractBlocks} for why this is the sole serialization boundary.
|
|
181
|
+
*/
|
|
182
|
+
function stripSuggestedProvenance(doc) {
|
|
183
|
+
return mapSuggestionStrippedNode(doc) ?? doc;
|
|
184
|
+
}
|
|
74
185
|
/**
|
|
75
186
|
* Extract block content (paragraphs, tables, block SDTs) from a ProseMirror
|
|
76
187
|
* document.
|
|
77
188
|
*/
|
|
78
|
-
function extractBlocks(
|
|
189
|
+
function extractBlocks(inputDoc) {
|
|
190
|
+
const pmDoc = stripSuggestedProvenance(inputDoc);
|
|
79
191
|
const blocks = [];
|
|
80
192
|
const textBoxAnchorMarkers = /* @__PURE__ */ new Map();
|
|
81
193
|
const documentCounts = buildDocumentTrackedChangeCounts(pmDoc);
|
|
@@ -605,7 +717,8 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
605
717
|
const info = {
|
|
606
718
|
id: changeAttrs.revisionId,
|
|
607
719
|
author: changeAttrs.author || "Unknown",
|
|
608
|
-
...changeAttrs.date ? { date: changeAttrs.date } : {}
|
|
720
|
+
...changeAttrs.date ? { date: changeAttrs.date } : {},
|
|
721
|
+
...changeAttrs.initials ? { initials: changeAttrs.initials } : {}
|
|
609
722
|
};
|
|
610
723
|
if (insertionMark) content.push({
|
|
611
724
|
type: changeAttrs.moveKind === "moveTo" ? "moveTo" : "insertion",
|
|
@@ -635,6 +748,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
|
|
|
635
748
|
author: changeAttrs.author || "Unknown"
|
|
636
749
|
};
|
|
637
750
|
if (changeAttrs.date) info.date = changeAttrs.date;
|
|
751
|
+
if (changeAttrs.initials) info.initials = changeAttrs.initials;
|
|
638
752
|
if (insertionMark) if (changeAttrs.moveKind === "moveTo") content.push({
|
|
639
753
|
type: "moveTo",
|
|
640
754
|
info,
|
|
@@ -1563,7 +1677,8 @@ function convertPMTableRow(node, documentCounts, activeVerticalMerges) {
|
|
|
1563
1677
|
info: {
|
|
1564
1678
|
id: attrs.trIns.revisionId,
|
|
1565
1679
|
author: attrs.trIns.author,
|
|
1566
|
-
...attrs.trIns.date != null && { date: attrs.trIns.date }
|
|
1680
|
+
...attrs.trIns.date != null && { date: attrs.trIns.date },
|
|
1681
|
+
...attrs.trIns.initials != null && { initials: attrs.trIns.initials }
|
|
1567
1682
|
}
|
|
1568
1683
|
};
|
|
1569
1684
|
else if (attrs.trDel) row.structuralChange = {
|
|
@@ -1571,7 +1686,8 @@ function convertPMTableRow(node, documentCounts, activeVerticalMerges) {
|
|
|
1571
1686
|
info: {
|
|
1572
1687
|
id: attrs.trDel.revisionId,
|
|
1573
1688
|
author: attrs.trDel.author,
|
|
1574
|
-
...attrs.trDel.date != null && { date: attrs.trDel.date }
|
|
1689
|
+
...attrs.trDel.date != null && { date: attrs.trDel.date },
|
|
1690
|
+
...attrs.trDel.initials != null && { initials: attrs.trDel.initials }
|
|
1575
1691
|
}
|
|
1576
1692
|
};
|
|
1577
1693
|
return row;
|
|
@@ -1652,7 +1768,8 @@ function convertPMTableCell(node, documentCounts) {
|
|
|
1652
1768
|
const info = {
|
|
1653
1769
|
id: attrs.cellMarker.info.revisionId,
|
|
1654
1770
|
author: attrs.cellMarker.info.author,
|
|
1655
|
-
...attrs.cellMarker.info.date != null && { date: attrs.cellMarker.info.date }
|
|
1771
|
+
...attrs.cellMarker.info.date != null && { date: attrs.cellMarker.info.date },
|
|
1772
|
+
...attrs.cellMarker.info.initials != null && { initials: attrs.cellMarker.info.initials }
|
|
1656
1773
|
};
|
|
1657
1774
|
if (attrs.cellMarker.kind === "merge") cell.structuralChange = {
|
|
1658
1775
|
type: "tableCellMerge",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { assertValidProseMirrorDocument, formatProseMirrorDocumentIssues, validateProseMirrorDocument } from "../validation.js";
|
|
2
|
-
import { fromProseDoc, proseDocToBlocks, updateDocumentContent } from "./fromProseDoc.js";
|
|
3
2
|
import { createEmptyDoc, headerFooterToProseDoc, toProseDoc } from "./toProseDoc.js";
|
|
3
|
+
import { fromProseDoc, proseDocToBlocks, updateDocumentContent } from "./fromProseDoc.js";
|
|
4
4
|
export { assertValidProseMirrorDocument, createEmptyDoc, formatProseMirrorDocumentIssues, fromProseDoc, headerFooterToProseDoc, proseDocToBlocks, toProseDoc, updateDocumentContent, validateProseMirrorDocument };
|
|
@@ -4,17 +4,32 @@ import { setAutospacingBaseValue } from "../autospacingBase.js";
|
|
|
4
4
|
import { assertValidProseMirrorDocument } from "../validation.js";
|
|
5
5
|
import { shadingToRunShadingAttrs } from "./runShadingMark.js";
|
|
6
6
|
import { sdtAttrsFromProperties } from "./sdtAttrs.js";
|
|
7
|
-
import { marksToTextFormatting } from "./fromProseDoc.js";
|
|
8
7
|
import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
|
|
9
8
|
import { mergeParagraphFormatting, mergeParagraphTabStops } from "../../utils/paragraphFormattingMerge.js";
|
|
10
9
|
import { createStyleEngine } from "../../style-engine/styleEngine.js";
|
|
11
10
|
import { buildRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
|
|
12
11
|
import { schema } from "../schema/index.js";
|
|
13
12
|
import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatting.js";
|
|
13
|
+
import { marksToTextFormatting } from "./fromProseDoc.js";
|
|
14
14
|
//#region src/prosemirror/conversion/toProseDoc.ts
|
|
15
15
|
const TOC_STYLE_ID = /^TOC\d*$/iu;
|
|
16
16
|
const isTocStyleId = (styleId) => styleId !== void 0 && TOC_STYLE_ID.test(styleId);
|
|
17
17
|
/**
|
|
18
|
+
* Build a `nextTextBoxGroupId()` generator salted with a random per-load
|
|
19
|
+
* nonce, so minted text-box anchor ids (`<salt>:<group>:<index>`) are unique
|
|
20
|
+
* across separate conversions instead of purely sequential ("0:0", "0:1",
|
|
21
|
+
* ...) starting fresh every load. `TextBoxAnchorExtension` parses
|
|
22
|
+
* `data-docx-textbox-anchor` straight off pasted DOM and `fromProseDoc`
|
|
23
|
+
* registers anchors first-seen-wins — without the salt, a pasted external
|
|
24
|
+
* span carrying a guessed/copied id could collide with and hijack a real
|
|
25
|
+
* text box anchored in a completely different document.
|
|
26
|
+
*/
|
|
27
|
+
const createTextBoxGroupIdFactory = () => {
|
|
28
|
+
const salt = Math.random().toString(36).slice(2, 10);
|
|
29
|
+
let index = 0;
|
|
30
|
+
return () => `${salt}:${index++}`;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
18
33
|
* Convert a Document to a ProseMirror document
|
|
19
34
|
*
|
|
20
35
|
* @param document - The Document to convert
|
|
@@ -25,8 +40,7 @@ function toProseDoc(document, options) {
|
|
|
25
40
|
const nodes = [];
|
|
26
41
|
const styleResolver = createStyleEngine(options?.styles ?? document.package.styles);
|
|
27
42
|
const theme = options?.theme ?? document.package.theme ?? null;
|
|
28
|
-
|
|
29
|
-
const nextTextBoxGroupId = () => String(textBoxGroupIndex++);
|
|
43
|
+
const nextTextBoxGroupId = createTextBoxGroupIdFactory();
|
|
30
44
|
const convertBodyBlocks = (blocks) => {
|
|
31
45
|
const out = [];
|
|
32
46
|
for (const block of blocks) if (block.type === "paragraph") {
|
|
@@ -244,6 +258,7 @@ function convertTrackedChange(change, markType, getInheritedRunFormatting, style
|
|
|
244
258
|
revisionId: change.info.id,
|
|
245
259
|
author: change.info.author,
|
|
246
260
|
date: change.info.date ?? null,
|
|
261
|
+
initials: change.info.initials ?? null,
|
|
247
262
|
moveKind
|
|
248
263
|
});
|
|
249
264
|
return nodes.map((node) => {
|
|
@@ -896,14 +911,12 @@ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPer
|
|
|
896
911
|
return schema.node(nodeType, attrs, contentNodes);
|
|
897
912
|
}
|
|
898
913
|
function standaloneTableCellToProseMirror(cell, nodeType) {
|
|
899
|
-
let textBoxGroupIndex = 0;
|
|
900
|
-
const nextTextBoxGroupId = () => String(textBoxGroupIndex++);
|
|
901
914
|
return convertTableCell({
|
|
902
915
|
cell,
|
|
903
916
|
styleResolver: null,
|
|
904
917
|
context: {
|
|
905
918
|
theme: null,
|
|
906
|
-
nextTextBoxGroupId
|
|
919
|
+
nextTextBoxGroupId: createTextBoxGroupIdFactory()
|
|
907
920
|
},
|
|
908
921
|
isHeader: nodeType === "tableHeader",
|
|
909
922
|
gridWidthPercent: void 0,
|
|
@@ -1602,8 +1615,7 @@ function headerFooterToProseDoc(content, options) {
|
|
|
1602
1615
|
const nodes = [];
|
|
1603
1616
|
const styleResolver = options?.styles ? createStyleEngine(options.styles) : null;
|
|
1604
1617
|
const theme = options?.theme ?? null;
|
|
1605
|
-
|
|
1606
|
-
const nextTextBoxGroupId = () => String(textBoxGroupIndex++);
|
|
1618
|
+
const nextTextBoxGroupId = createTextBoxGroupIdFactory();
|
|
1607
1619
|
const convertBlocks = (blocks) => {
|
|
1608
1620
|
const out = [];
|
|
1609
1621
|
for (const block of blocks) if (block.type === "paragraph") out.push(...convertParagraphWithTextBoxes(block, styleResolver, {
|
|
@@ -237,7 +237,8 @@ const paragraphNodeSpec = {
|
|
|
237
237
|
_autospacingBase: { default: null },
|
|
238
238
|
_sectionProperties: { default: null },
|
|
239
239
|
_propertyChanges: { default: null },
|
|
240
|
-
pPrMark: { default: null }
|
|
240
|
+
pPrMark: { default: null },
|
|
241
|
+
_suggestedInsert: { default: null }
|
|
241
242
|
},
|
|
242
243
|
parseDOM: [{
|
|
243
244
|
tag: "p",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createExtension } from "../create.js";
|
|
2
|
+
import { sanitizeImageSrc } from "../../../utils/sanitizeImageSrc.js";
|
|
2
3
|
import { getClipboardImageFiles } from "../../../utils/clipboard.js";
|
|
3
4
|
import { isSafeImageFile } from "../../../utils/imageValidation.js";
|
|
4
5
|
import { Plugin, TextSelection } from "prosemirror-state";
|
|
@@ -20,13 +21,18 @@ async function readFileAsDataUrl(file) {
|
|
|
20
21
|
}
|
|
21
22
|
async function loadImageSize(src) {
|
|
22
23
|
return await new Promise((resolve, reject) => {
|
|
24
|
+
const safeSrc = sanitizeImageSrc(src);
|
|
25
|
+
if (!safeSrc) {
|
|
26
|
+
reject(/* @__PURE__ */ new Error("Rejected unsafe image source"));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
23
29
|
const img = new Image();
|
|
24
30
|
img.addEventListener("load", () => resolve({
|
|
25
31
|
width: img.naturalWidth || 1,
|
|
26
32
|
height: img.naturalHeight || 1
|
|
27
33
|
}));
|
|
28
34
|
img.addEventListener("error", () => reject(/* @__PURE__ */ new Error("Failed to load pasted image")));
|
|
29
|
-
img.src =
|
|
35
|
+
img.src = safeSrc;
|
|
30
36
|
});
|
|
31
37
|
}
|
|
32
38
|
async function insertImageFiles(view, files) {
|
|
@@ -37,7 +43,9 @@ async function insertImageFiles(view, files) {
|
|
|
37
43
|
if (!await isSafeImageFile(file)) continue;
|
|
38
44
|
let dataUrl;
|
|
39
45
|
try {
|
|
40
|
-
|
|
46
|
+
const safeSrc = sanitizeImageSrc(await readFileAsDataUrl(file));
|
|
47
|
+
if (!safeSrc) continue;
|
|
48
|
+
dataUrl = safeSrc;
|
|
41
49
|
} catch {
|
|
42
50
|
continue;
|
|
43
51
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripXmlDeclarations } from "../../../utils/stripXmlDeclarations.js";
|
|
1
2
|
//#region src/prosemirror/extensions/features/pasteCleanup.ts
|
|
2
3
|
/**
|
|
3
4
|
* Office / web paste cleanup
|
|
@@ -112,11 +113,22 @@ function splitStyleDeclarations(value) {
|
|
|
112
113
|
}
|
|
113
114
|
const TAG_TAIL = `(?:"[^"]*"|'[^']*'|[^>"'])*`;
|
|
114
115
|
const NAMESPACED_TAG = new RegExp(`<\\/?(?:[owvm]|st\\d+):${TAG_TAIL}>`, "gi");
|
|
115
|
-
const XML_PROCESSING_INSTRUCTION = new RegExp(`<\\?xml${TAG_TAIL}>`, "gi");
|
|
116
116
|
const STRAY_XML_TAG = new RegExp(`<\\/?xml${TAG_TAIL}>`, "gi");
|
|
117
117
|
const NOISE_TAG = new RegExp(`<\\/?(?:font|meta|link)${TAG_TAIL}>`, "gi");
|
|
118
118
|
const EMPTY_SPAN = new RegExp(`<span(?:\\s${TAG_TAIL})?><\\/span>`, "gi");
|
|
119
119
|
const MAX_EMPTY_SPAN_PASSES = 5;
|
|
120
|
+
const PM_SLICE_MARKER = /\bdata-pm-slice\s*=/i;
|
|
121
|
+
const TEXTBOX_ANCHOR_ATTR = /\s+data-docx-textbox-anchor(?:="[^"]*"|='[^']*')?/gi;
|
|
122
|
+
/**
|
|
123
|
+
* Remove the `data-docx-textbox-anchor` marker from HTML that did not come
|
|
124
|
+
* from a ProseMirror clipboard slice (see {@link PM_SLICE_MARKER}). Internal
|
|
125
|
+
* copy/paste of a real text box carries the marker HTML unmodified so it
|
|
126
|
+
* keeps working; anything else has the marker stripped defensively.
|
|
127
|
+
*/
|
|
128
|
+
function stripForeignTextBoxAnchors(html, originalHtml) {
|
|
129
|
+
if (PM_SLICE_MARKER.test(originalHtml)) return html;
|
|
130
|
+
return html.replace(TEXTBOX_ANCHOR_ATTR, "");
|
|
131
|
+
}
|
|
120
132
|
/**
|
|
121
133
|
* Remove empty spans left behind after stripping `mso-*` styles. Only truly
|
|
122
134
|
* empty spans are removed (whitespace-only spans are kept so word-separating
|
|
@@ -144,12 +156,13 @@ function cleanPastedHtml(html) {
|
|
|
144
156
|
if (!html) return html;
|
|
145
157
|
try {
|
|
146
158
|
let cleaned = stripHtmlComments(html);
|
|
147
|
-
cleaned = cleaned
|
|
159
|
+
cleaned = stripXmlDeclarations(cleaned);
|
|
148
160
|
cleaned = cleaned.replace(NAMESPACED_TAG, "");
|
|
149
161
|
cleaned = cleaned.replace(STRAY_XML_TAG, "");
|
|
150
162
|
cleaned = cleaned.replace(NOISE_TAG, "");
|
|
151
163
|
cleaned = stripMsoStyles(cleaned);
|
|
152
164
|
cleaned = stripEmptySpans(cleaned);
|
|
165
|
+
cleaned = stripForeignTextBoxAnchors(cleaned, html);
|
|
153
166
|
return cleaned.trim();
|
|
154
167
|
} catch {
|
|
155
168
|
return html;
|
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
import { expectHyperlinkMarkAttrs } from "../../attrs/index.js";
|
|
2
2
|
import { createMarkExtension } from "../create.js";
|
|
3
3
|
import { isMarkActive } from "./markUtils.js";
|
|
4
|
+
import { normalizeUserUrl, sanitizeExternalUrl } from "../../../utils/urlSecurity.js";
|
|
4
5
|
import { panic } from "better-result";
|
|
5
6
|
//#region src/prosemirror/extensions/marks/HyperlinkExtension.ts
|
|
6
7
|
/**
|
|
7
8
|
* Hyperlink Mark Extension
|
|
8
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* Sanitize an href arriving from (or being emitted to) the DOM: pasted HTML,
|
|
12
|
+
* clipboard content, or a mark attr already stored on the document. Only
|
|
13
|
+
* allow-listed absolute URLs (http/https/mailto/tel) and internal bookmark
|
|
14
|
+
* anchors survive; everything else (javascript:, data:, file:, ...) becomes
|
|
15
|
+
* an empty href, matching how the codebase already signals "no link" for an
|
|
16
|
+
* unresolved hyperlink (see `toProseDoc.ts`'s `hyperlink.href || ""`).
|
|
17
|
+
*/
|
|
18
|
+
function sanitizeStoredHref(rawHref) {
|
|
19
|
+
if (!rawHref) return "";
|
|
20
|
+
const trimmed = rawHref.trim();
|
|
21
|
+
if (trimmed.startsWith("#")) return trimmed;
|
|
22
|
+
return sanitizeExternalUrl(trimmed) ?? "";
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Normalize/sanitize a URL typed by a user (hyperlink dialog/popup) before it
|
|
26
|
+
* is written into a mark. Protocol-less input (e.g. "example.com") is
|
|
27
|
+
* accepted and normalized to https; anything resolving to a disallowed
|
|
28
|
+
* scheme is dropped.
|
|
29
|
+
*/
|
|
30
|
+
function normalizeHyperlinkInput(rawHref) {
|
|
31
|
+
const trimmed = rawHref.trim();
|
|
32
|
+
if (trimmed.startsWith("#")) return trimmed;
|
|
33
|
+
return normalizeUserUrl(trimmed);
|
|
34
|
+
}
|
|
9
35
|
function isHyperlinkActive(state) {
|
|
10
36
|
const hlType = state.schema.marks["hyperlink"];
|
|
11
37
|
if (!hlType) return false;
|
|
@@ -62,14 +88,14 @@ const HyperlinkExtension = createMarkExtension({
|
|
|
62
88
|
parseDOM: [{
|
|
63
89
|
tag: "a[href]",
|
|
64
90
|
getAttrs: (dom) => ({
|
|
65
|
-
href: dom.getAttribute("href") ??
|
|
91
|
+
href: sanitizeStoredHref(dom.getAttribute("href") ?? void 0),
|
|
66
92
|
tooltip: dom.getAttribute("title") ?? void 0
|
|
67
93
|
})
|
|
68
94
|
}],
|
|
69
95
|
toDOM(mark) {
|
|
70
96
|
const { href, tooltip } = expectHyperlinkMarkAttrs(mark);
|
|
71
97
|
const domAttrs = {
|
|
72
|
-
href,
|
|
98
|
+
href: sanitizeStoredHref(href),
|
|
73
99
|
target: "_blank",
|
|
74
100
|
rel: "noopener noreferrer"
|
|
75
101
|
};
|
|
@@ -89,7 +115,7 @@ const HyperlinkExtension = createMarkExtension({
|
|
|
89
115
|
if (empty) return false;
|
|
90
116
|
if (dispatch) {
|
|
91
117
|
const mark = hlType.create({
|
|
92
|
-
href,
|
|
118
|
+
href: normalizeHyperlinkInput(href),
|
|
93
119
|
tooltip: tooltip || null
|
|
94
120
|
});
|
|
95
121
|
let tr = state.tr.addMark(from, to, mark);
|
|
@@ -127,7 +153,7 @@ const HyperlinkExtension = createMarkExtension({
|
|
|
127
153
|
const insertHyperlink = (text, href, tooltip) => (state, dispatch) => {
|
|
128
154
|
if (dispatch) {
|
|
129
155
|
const mark = hlType.create({
|
|
130
|
-
href,
|
|
156
|
+
href: normalizeHyperlinkInput(href),
|
|
131
157
|
tooltip: tooltip || null
|
|
132
158
|
});
|
|
133
159
|
const textNode = state.schema.text(text, [mark]);
|
|
@@ -18,6 +18,20 @@ import { AUTHOR_COLORS, getAuthorColorIdx } from "../../../utils/authorColors.js
|
|
|
18
18
|
const insertionStyle = (color) => `color: ${color}; text-decoration: underline; text-decoration-color: ${color};`;
|
|
19
19
|
const deletionStyle = (color) => `color: ${color}; text-decoration: line-through; text-decoration-color: ${color};`;
|
|
20
20
|
/**
|
|
21
|
+
* Suggested (AI-proposed) tracked changes reuse the insertion/deletion visual
|
|
22
|
+
* grammar but with a DOTTED stroke and a dedicated suggestion hue, kept
|
|
23
|
+
* distinct from the per-author redline colors. The hue and tint are driven by
|
|
24
|
+
* the `--suggestion-color` / `--suggestion-bg` CSS custom properties (see
|
|
25
|
+
* editor.css); the inline fallbacks below keep the paged-canvas projection —
|
|
26
|
+
* which reuses this same `toDOM` style string — legible when the stylesheet
|
|
27
|
+
* variables are not in scope.
|
|
28
|
+
*/
|
|
29
|
+
const SUGGESTION_COLOR = "var(--suggestion-color, #6d3bd6)";
|
|
30
|
+
const SUGGESTION_TINT = "var(--suggestion-bg, color-mix(in oklch, #6d3bd6 12%, transparent))";
|
|
31
|
+
const SUGGESTION_TINT_LAYER = `linear-gradient(${SUGGESTION_TINT}, ${SUGGESTION_TINT})`;
|
|
32
|
+
const suggestedInsertionStyle = () => `color: ${SUGGESTION_COLOR}; text-decoration: underline; text-decoration-style: dotted; text-decoration-color: ${SUGGESTION_COLOR}; background-image: ${SUGGESTION_TINT_LAYER};`;
|
|
33
|
+
const suggestedDeletionStyle = () => `color: ${SUGGESTION_COLOR}; text-decoration: line-through; text-decoration-style: dotted; text-decoration-color: ${SUGGESTION_COLOR}; background-image: ${SUGGESTION_TINT_LAYER};`;
|
|
34
|
+
/**
|
|
21
35
|
* Insertion mark — text added in tracked changes
|
|
22
36
|
* Renders with per-author colored underline.
|
|
23
37
|
*/
|
|
@@ -29,7 +43,10 @@ const InsertionExtension = createMarkExtension({
|
|
|
29
43
|
revisionId: { default: 0 },
|
|
30
44
|
author: { default: "" },
|
|
31
45
|
date: { default: null },
|
|
32
|
-
moveKind: { default: null }
|
|
46
|
+
moveKind: { default: null },
|
|
47
|
+
initials: { default: null },
|
|
48
|
+
provenance: { default: "user" },
|
|
49
|
+
suggestionId: { default: null }
|
|
33
50
|
},
|
|
34
51
|
inclusive: false,
|
|
35
52
|
parseDOM: [{
|
|
@@ -43,20 +60,23 @@ const InsertionExtension = createMarkExtension({
|
|
|
43
60
|
}
|
|
44
61
|
}],
|
|
45
62
|
toDOM(mark) {
|
|
46
|
-
const { revisionId, author, date } = expectTrackedChangeMarkAttrs(mark);
|
|
63
|
+
const { revisionId, author, date, provenance, suggestionId } = expectTrackedChangeMarkAttrs(mark);
|
|
47
64
|
const idx = getAuthorColorIdx(author);
|
|
48
65
|
const color = AUTHOR_COLORS[idx] ?? "#000000";
|
|
49
66
|
const titleParts = [author, date ? new Date(date).toLocaleDateString() : ""].filter(Boolean);
|
|
67
|
+
const suggested = provenance === "suggested";
|
|
50
68
|
return [
|
|
51
69
|
"span",
|
|
52
70
|
{
|
|
53
|
-
class: "docx-insertion",
|
|
71
|
+
class: suggested ? "docx-insertion docx-insertion--suggested" : "docx-insertion",
|
|
54
72
|
"data-revision-id": String(revisionId),
|
|
55
73
|
"data-author": author,
|
|
56
74
|
"data-tc-author-idx": String(idx),
|
|
75
|
+
...suggested ? { "data-provenance": "suggested" } : {},
|
|
76
|
+
...suggested && suggestionId ? { "data-suggestion-id": suggestionId } : {},
|
|
57
77
|
...date ? { "data-date": date } : {},
|
|
58
|
-
...titleParts.length > 0 ? { title:
|
|
59
|
-
style: insertionStyle(color)
|
|
78
|
+
...titleParts.length > 0 ? { title: `${suggested ? "Suggested" : "Inserted"}: ${titleParts.join(", ")}` } : {},
|
|
79
|
+
style: suggested ? suggestedInsertionStyle() : insertionStyle(color)
|
|
60
80
|
},
|
|
61
81
|
0
|
|
62
82
|
];
|
|
@@ -75,7 +95,10 @@ const DeletionExtension = createMarkExtension({
|
|
|
75
95
|
revisionId: { default: 0 },
|
|
76
96
|
author: { default: "" },
|
|
77
97
|
date: { default: null },
|
|
78
|
-
moveKind: { default: null }
|
|
98
|
+
moveKind: { default: null },
|
|
99
|
+
initials: { default: null },
|
|
100
|
+
provenance: { default: "user" },
|
|
101
|
+
suggestionId: { default: null }
|
|
79
102
|
},
|
|
80
103
|
inclusive: false,
|
|
81
104
|
parseDOM: [{
|
|
@@ -89,20 +112,23 @@ const DeletionExtension = createMarkExtension({
|
|
|
89
112
|
}
|
|
90
113
|
}],
|
|
91
114
|
toDOM(mark) {
|
|
92
|
-
const { revisionId, author, date } = expectTrackedChangeMarkAttrs(mark);
|
|
115
|
+
const { revisionId, author, date, provenance, suggestionId } = expectTrackedChangeMarkAttrs(mark);
|
|
93
116
|
const idx = getAuthorColorIdx(author);
|
|
94
117
|
const color = AUTHOR_COLORS[idx] ?? "#000000";
|
|
95
118
|
const titleParts = [author, date ? new Date(date).toLocaleDateString() : ""].filter(Boolean);
|
|
119
|
+
const suggested = provenance === "suggested";
|
|
96
120
|
return [
|
|
97
121
|
"span",
|
|
98
122
|
{
|
|
99
|
-
class: "docx-deletion",
|
|
123
|
+
class: suggested ? "docx-deletion docx-deletion--suggested" : "docx-deletion",
|
|
100
124
|
"data-revision-id": String(revisionId),
|
|
101
125
|
"data-author": author,
|
|
102
126
|
"data-tc-author-idx": String(idx),
|
|
127
|
+
...suggested ? { "data-provenance": "suggested" } : {},
|
|
128
|
+
...suggested && suggestionId ? { "data-suggestion-id": suggestionId } : {},
|
|
103
129
|
...date ? { "data-date": date } : {},
|
|
104
|
-
...titleParts.length > 0 ? { title:
|
|
105
|
-
style: deletionStyle(color)
|
|
130
|
+
...titleParts.length > 0 ? { title: `${suggested ? "Suggested deletion" : "Deleted"}: ${titleParts.join(", ")}` } : {},
|
|
131
|
+
style: suggested ? suggestedDeletionStyle() : deletionStyle(color)
|
|
106
132
|
},
|
|
107
133
|
0
|
|
108
134
|
];
|
|
@@ -120,13 +146,23 @@ const RunPropertyChangeExtension = createMarkExtension({
|
|
|
120
146
|
name: "runPropertyChange",
|
|
121
147
|
schemaMarkName: "runPropertyChange",
|
|
122
148
|
markSpec: {
|
|
123
|
-
attrs: {
|
|
149
|
+
attrs: {
|
|
150
|
+
changes: { default: [] },
|
|
151
|
+
provenance: { default: "user" },
|
|
152
|
+
suggestionId: { default: null }
|
|
153
|
+
},
|
|
124
154
|
inclusive: false,
|
|
125
155
|
parseDOM: [{ tag: "span.docx-run-property-change" }],
|
|
126
|
-
toDOM() {
|
|
156
|
+
toDOM(mark) {
|
|
157
|
+
const suggested = mark.attrs["provenance"] === "suggested";
|
|
158
|
+
const suggestionId = mark.attrs["suggestionId"];
|
|
127
159
|
return [
|
|
128
160
|
"span",
|
|
129
|
-
{
|
|
161
|
+
{
|
|
162
|
+
class: suggested ? "docx-run-property-change docx-run-property-change--suggested" : "docx-run-property-change",
|
|
163
|
+
...suggested ? { "data-provenance": "suggested" } : {},
|
|
164
|
+
...suggested && typeof suggestionId === "string" ? { "data-suggestion-id": suggestionId } : {}
|
|
165
|
+
},
|
|
130
166
|
0
|
|
131
167
|
];
|
|
132
168
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AnyExtension, Extension, NodeExtension } from "../types.js";
|
|
2
1
|
import { ColorValue } from "../../../types/colors.js";
|
|
2
|
+
import { AnyExtension, Extension, NodeExtension } from "../types.js";
|
|
3
3
|
import { Command, EditorState } from "prosemirror-state";
|
|
4
4
|
import { Node } from "prosemirror-model";
|
|
5
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_WIDTH_TYPE_VALUES } from "../../../types/documentEnumValues.js";
|
|
2
2
|
import { expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs } from "../../attrs/index.js";
|
|
3
3
|
import { createExtension, createNodeExtension } from "../create.js";
|
|
4
|
-
import { resolveColor } from "../../../utils/colorResolver.js";
|
|
4
|
+
import { isValidHexColor, resolveColor } from "../../../utils/colorResolver.js";
|
|
5
5
|
import { panic } from "better-result";
|
|
6
6
|
import { CellSelection, TableMap, columnResizing, mergeCells, removeRow, selectedRect, splitCell, tableEditing } from "prosemirror-tables";
|
|
7
7
|
import { Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state";
|
|
@@ -127,7 +127,8 @@ function parseCellAttrsFromDOM(element) {
|
|
|
127
127
|
const margins = extractCellMarginsFromCSS(style);
|
|
128
128
|
const rawValign = element.dataset["valign"];
|
|
129
129
|
const verticalAlign = (rawValign === "top" || rawValign === "center" || rawValign === "bottom" ? rawValign : void 0) ?? mapCssVerticalAlign(style.verticalAlign) ?? void 0;
|
|
130
|
-
const
|
|
130
|
+
const rawBgColor = element.dataset["bgcolor"];
|
|
131
|
+
const backgroundColor = (isValidHexColor(rawBgColor) ? rawBgColor : void 0) || parseCssColorToHex(style.backgroundColor) || void 0;
|
|
131
132
|
return {
|
|
132
133
|
colspan: Number(element.getAttribute("colspan") ?? "1") || 1,
|
|
133
134
|
rowspan: Number(element.getAttribute("rowspan") ?? "1") || 1,
|
|
@@ -154,7 +155,8 @@ const tableSpec = {
|
|
|
154
155
|
borders: { default: null },
|
|
155
156
|
_resolvedIndent: { default: null },
|
|
156
157
|
_originalFormatting: { default: null },
|
|
157
|
-
tblPrChange: { default: null }
|
|
158
|
+
tblPrChange: { default: null },
|
|
159
|
+
_suggestedInsert: { default: null }
|
|
158
160
|
},
|
|
159
161
|
parseDOM: [{
|
|
160
162
|
tag: "table",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ImageAttrs, ParagraphAttrs } from "./schema/nodes.js";
|
|
2
1
|
import { FontFamilyAttrs, FontSizeAttrs, HyperlinkAttrs, TextColorAttrs, UnderlineAttrs } from "./schema/marks.js";
|
|
2
|
+
import { ImageAttrs, ParagraphAttrs } from "./schema/nodes.js";
|
|
3
3
|
import { schema, singletonManager } from "./schema/index.js";
|
|
4
4
|
import { PAINTABLE_MARK_NAMES, applyFormatMarks, captureFormatMarks } from "./commands/formatPainter.js";
|
|
5
5
|
import { clearFormatting, getMarkAttr, isMarkActive } from "./extensions/marks/markUtils.js";
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { assertValidProseMirrorDocument, formatProseMirrorDocumentIssues, validateProseMirrorDocument } from "./validation.js";
|
|
2
|
-
import { fromProseDoc, updateDocumentContent } from "./conversion/fromProseDoc.js";
|
|
3
2
|
import { StyleResolver, createStyleResolver } from "./styles/styleResolver.js";
|
|
4
3
|
import { getParagraphAlignment, getParagraphBidi, getStyleId } from "./extensions/core/ParagraphExtension.js";
|
|
5
4
|
import { createDocumentStylesPlugin, documentStylesKey, getDocumentStyleResolver } from "./plugins/documentStyles.js";
|
|
@@ -11,6 +10,7 @@ import { createSelectionTrackerPlugin, extractSelectionContext, getSelectionCont
|
|
|
11
10
|
import { getHyperlinkAttrs, getSelectedText, isHyperlinkActive } from "./extensions/marks/HyperlinkExtension.js";
|
|
12
11
|
import { schema, singletonManager } from "./schema/index.js";
|
|
13
12
|
import { createEmptyDoc, toProseDoc } from "./conversion/toProseDoc.js";
|
|
13
|
+
import { fromProseDoc, updateDocumentContent } from "./conversion/fromProseDoc.js";
|
|
14
14
|
import { addColumnLeft, addColumnRight, addRowAbove, addRowBelow, applyTableStyle, autoFitContents, deleteColumn, deleteRow, deleteTable, distributeColumns, insertTable, mergeCells, removeTableBorders, selectColumn, selectRow, selectTable, setAllTableBorders, setCellBorder, setCellFillColor, setCellMargins, setCellTextDirection, setCellVerticalAlign, setInsideTableBorders, setOutsideTableBorders, setRowHeight, setTableBorderColor, setTableBorderPreset, setTableBorderWidth, setTableBorders, setTableProperties, splitCell, toggleHeaderRow, toggleNoWrap } from "./commands/table.js";
|
|
15
15
|
import { extractSelectionState } from "./selectionState.js";
|
|
16
16
|
import { clearFontFamily, clearFontSize, clearHighlight, clearTextColor, insertHyperlink, removeHyperlink, setFontFamily, setFontSize, setHighlight, setHyperlink, setTextColor, toggleBold, toggleItalic, toggleStrike, toggleSubscript, toggleSuperscript, toggleUnderline } from "./commands/formatting.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContentControlLockedError, ContentControlTypeError } from "../../content-controls/errors.js";
|
|
1
|
+
import { ContentControlBoundError, ContentControlLockedError, ContentControlTypeError } from "../../content-controls/errors.js";
|
|
2
2
|
import { Plugin, PluginKey } from "prosemirror-state";
|
|
3
3
|
import { EditorView } from "prosemirror-view";
|
|
4
4
|
|
|
@@ -33,7 +33,7 @@ type ContentControlWidgetEvent = {
|
|
|
33
33
|
pmPos: number;
|
|
34
34
|
sdtType: string;
|
|
35
35
|
anchor: ContentControlWidgetAnchor;
|
|
36
|
-
error: ContentControlLockedError | ContentControlTypeError;
|
|
36
|
+
error: ContentControlLockedError | ContentControlTypeError | ContentControlBoundError;
|
|
37
37
|
};
|
|
38
38
|
type ContentControlWidgetCallback = (event: ContentControlWidgetEvent) => void;
|
|
39
39
|
type ContentControlWidgetClickEvent = {
|