@stll/folio-core 0.25.1 → 0.25.3
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/headless.d.ts +6 -0
- package/dist/ai-edits/headless.js +60 -7
- package/dist/ai-edits/read.d.ts +26 -1
- package/dist/ai-edits/read.js +26 -1
- package/dist/docx/rezip.d.ts +2 -0
- package/dist/docx/rezip.js +61 -58
- package/dist/docx/selectiveXmlPatch.d.ts +24 -1
- package/dist/docx/selectiveXmlPatch.js +186 -1
- package/dist/docx/xmlParser.d.ts +7 -1
- package/dist/docx/xmlParser.js +11 -4
- package/dist/layout-bridge/convert/toFlowBlocks.js +3 -4
- package/dist/layout-bridge/engine/clickToPosition.js +13 -28
- package/dist/layout-bridge/engine/hitTest.d.ts +2 -0
- package/dist/layout-bridge/engine/hitTest.js +28 -13
- package/dist/layout-bridge/engine/selectionRects.js +26 -15
- package/dist/layout-engine/index.js +6 -9
- package/dist/layout-engine/measure/hyperlinkInstance.d.ts +6 -0
- package/dist/layout-engine/measure/hyperlinkInstance.js +8 -0
- package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -1
- package/dist/layout-engine/measure/tableCellGrid.js +29 -6
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.js +22 -0
- package/dist/layout-engine/types.d.ts +1 -1
- package/dist/layout-painter/renderParagraph.d.ts +3 -0
- package/dist/layout-painter/renderParagraph.js +13 -19
- package/dist/layout-painter/renderTable.js +36 -74
- package/dist/prosemirror/conversion/toProseDoc.js +42 -44
- package/dist/prosemirror/extensions/marks/CommentExtension.js +1 -0
- package/dist/utils/paragraphInlineLayout.d.ts +14 -0
- package/dist/utils/paragraphInlineLayout.js +25 -0
- package/package.json +1 -1
|
@@ -30,6 +30,11 @@ const createTextBoxGroupIdFactory = () => {
|
|
|
30
30
|
let index = 0;
|
|
31
31
|
return () => `${salt}:${index++}`;
|
|
32
32
|
};
|
|
33
|
+
/** Keep imported hyperlink identity unique across every nested conversion scope. */
|
|
34
|
+
const createHyperlinkInstanceIndexAllocator = () => {
|
|
35
|
+
let index = 0;
|
|
36
|
+
return () => index++;
|
|
37
|
+
};
|
|
33
38
|
/**
|
|
34
39
|
* Convert a Document to a ProseMirror document
|
|
35
40
|
*
|
|
@@ -42,6 +47,11 @@ function toProseDoc(document, options) {
|
|
|
42
47
|
const styleResolver = createStyleEngine(options?.styles ?? document.package.styles);
|
|
43
48
|
const theme = options?.theme ?? document.package.theme ?? null;
|
|
44
49
|
const nextTextBoxGroupId = createTextBoxGroupIdFactory();
|
|
50
|
+
const conversionContext = {
|
|
51
|
+
theme,
|
|
52
|
+
nextTextBoxGroupId,
|
|
53
|
+
nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
|
|
54
|
+
};
|
|
45
55
|
const convertBodyBlocks = (blocks) => {
|
|
46
56
|
const out = [];
|
|
47
57
|
for (const block of blocks) if (block.type === "paragraph") {
|
|
@@ -49,10 +59,7 @@ function toProseDoc(document, options) {
|
|
|
49
59
|
if (pbPos === "before") out.push(schema.node("pageBreak"));
|
|
50
60
|
const converted = convertParagraphWithTextBoxes(block, styleResolver, {
|
|
51
61
|
textBoxGroupId: nextTextBoxGroupId(),
|
|
52
|
-
context:
|
|
53
|
-
theme,
|
|
54
|
-
nextTextBoxGroupId
|
|
55
|
-
}
|
|
62
|
+
context: conversionContext
|
|
56
63
|
});
|
|
57
64
|
const firstConverted = converted.at(0);
|
|
58
65
|
if (pbPos === "before" && converted.length === 1 && firstConverted?.type.name === "paragraph" && firstConverted.content.size === 0 && document.package.settings?.splitPageBreakAndParagraphMark !== true) {
|
|
@@ -72,10 +79,7 @@ function toProseDoc(document, options) {
|
|
|
72
79
|
}
|
|
73
80
|
out.push(...converted);
|
|
74
81
|
if (pbPos === "after") out.push(schema.node("pageBreak"));
|
|
75
|
-
} else if (block.type === "table") out.push(convertTable(block, styleResolver,
|
|
76
|
-
theme,
|
|
77
|
-
nextTextBoxGroupId
|
|
78
|
-
}));
|
|
82
|
+
} else if (block.type === "table") out.push(convertTable(block, styleResolver, conversionContext));
|
|
79
83
|
else out.push(convertBlockSdt(block, convertBodyBlocks));
|
|
80
84
|
return out;
|
|
81
85
|
};
|
|
@@ -122,14 +126,13 @@ function convertBlockSdt(blockSdt, convertBlocks) {
|
|
|
122
126
|
* Resolves style-based text formatting and passes it to runs so that
|
|
123
127
|
* paragraph styles (like Heading1) apply their font size, color, etc.
|
|
124
128
|
*/
|
|
125
|
-
function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
|
|
129
|
+
function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex, activeCommentIds, extraRunFormatting, tableParagraphOverlay, textBoxAnchors) {
|
|
126
130
|
const attrs = paragraphFormattingToAttrs(paragraph, styleResolver, tableParagraphOverlay);
|
|
127
131
|
const isTocParagraph = isTocStyleId(paragraph.formatting?.styleId);
|
|
128
132
|
const inlineNodes = [];
|
|
129
133
|
let inlineOffset = 0;
|
|
130
134
|
let bookmarksArr;
|
|
131
135
|
let emptyHyperlinks;
|
|
132
|
-
let hyperlinkIndex = 0;
|
|
133
136
|
const commentIds = activeCommentIds ?? /* @__PURE__ */ new Set();
|
|
134
137
|
const emitInlineNodes = (nodes) => {
|
|
135
138
|
if (nodes.length === 0) return;
|
|
@@ -160,7 +163,7 @@ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFo
|
|
|
160
163
|
return suppressParagraphMarkFormatting(baseRunFormatting, inheritableParagraphRunFormatting, formatting, paragraphMarkPrecedesStyle);
|
|
161
164
|
};
|
|
162
165
|
const emitTrackedChange = (change, markType, moveKind) => {
|
|
163
|
-
emitInlineNodes(convertTrackedChange(change, markType, getInheritedRunFormatting, styleResolver, moveKind, textBoxAnchors));
|
|
166
|
+
emitInlineNodes(convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, moveKind, textBoxAnchors));
|
|
164
167
|
};
|
|
165
168
|
for (const content of paragraph.content) {
|
|
166
169
|
if (content.type === "commentRangeStart") commentIds.add(content.id);
|
|
@@ -168,12 +171,10 @@ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFo
|
|
|
168
171
|
else if (content.type === "commentReference") anchorPointComment(inlineNodes, content.id);
|
|
169
172
|
else if (content.type === "run") emitInlineNodes(convertRun(content, getInheritedRunFormatting(content.formatting), styleResolver, textBoxAnchors));
|
|
170
173
|
else if (content.type === "hyperlink") {
|
|
171
|
-
const currentHyperlinkIndex = hyperlinkIndex;
|
|
172
|
-
hyperlinkIndex += 1;
|
|
173
174
|
const linkNodes = convertHyperlink(content, {
|
|
174
175
|
getInheritedRunFormatting,
|
|
175
176
|
styleResolver,
|
|
176
|
-
hyperlinkIndex:
|
|
177
|
+
hyperlinkIndex: nextHyperlinkInstanceIndex(),
|
|
177
178
|
textBoxAnchors
|
|
178
179
|
});
|
|
179
180
|
if (linkNodes.length === 0) {
|
|
@@ -189,7 +190,7 @@ function convertParagraph(paragraph, styleResolver, activeCommentIds, extraRunFo
|
|
|
189
190
|
}
|
|
190
191
|
emitInlineNodes(linkNodes);
|
|
191
192
|
} else if (content.type === "simpleField" || content.type === "complexField") emitInlineNode(convertField(content, getInheritedRunFormatting, styleResolver));
|
|
192
|
-
else if (content.type === "inlineSdt") emitInlineNode(convertInlineSdt(content, getInheritedRunFormatting, styleResolver, textBoxAnchors));
|
|
193
|
+
else if (content.type === "inlineSdt") emitInlineNode(convertInlineSdt(content, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors));
|
|
193
194
|
else if (content.type === "insertion" || content.type === "moveTo") emitTrackedChange(content, "insertion", content.type === "moveTo" ? "moveTo" : null);
|
|
194
195
|
else if (content.type === "deletion" || content.type === "moveFrom") emitTrackedChange(content, "deletion", content.type === "moveFrom" ? "moveFrom" : null);
|
|
195
196
|
else if (content.type === "mathEquation") emitInlineNode(convertMathEquation(content));
|
|
@@ -216,15 +217,17 @@ const resolveParagraphStyleFontFamily = (styleId, styleResolver) => {
|
|
|
216
217
|
};
|
|
217
218
|
/**
|
|
218
219
|
* Apply comment marks to PM nodes within a comment range.
|
|
219
|
-
* Only the first active comment ID is used (comments don't overlap visually).
|
|
220
220
|
*/
|
|
221
221
|
function applyCommentMarks(nodes, commentIds) {
|
|
222
222
|
if (commentIds.size === 0) return nodes;
|
|
223
|
-
const
|
|
224
|
-
|
|
223
|
+
const commentMarkType = schema.marks["comment"];
|
|
224
|
+
if (!commentMarkType) return nodes;
|
|
225
|
+
const commentMarks = [...commentIds].toSorted((left, right) => left - right).map((commentId) => commentMarkType.create({ commentId }));
|
|
225
226
|
return nodes.map((node) => {
|
|
226
|
-
if (node.isText
|
|
227
|
-
|
|
227
|
+
if (!node.isText && (!node.isInline || !node.type.allowsMarkType(commentMarkType))) return node;
|
|
228
|
+
let marks = node.marks;
|
|
229
|
+
for (const commentMark of commentMarks) marks = commentMark.addToSet(marks);
|
|
230
|
+
return node.mark(marks);
|
|
228
231
|
});
|
|
229
232
|
}
|
|
230
233
|
function anchorPointComment(nodes, commentId) {
|
|
@@ -241,13 +244,11 @@ function anchorPointComment(nodes, commentId) {
|
|
|
241
244
|
* Convert tracked change (insertion or deletion) content to PM nodes with
|
|
242
245
|
* an insertion/deletion mark applied.
|
|
243
246
|
*/
|
|
244
|
-
function convertTrackedChange(change, markType, getInheritedRunFormatting, styleResolver, moveKind = null, textBoxAnchors) {
|
|
247
|
+
function convertTrackedChange(change, markType, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, moveKind = null, textBoxAnchors) {
|
|
245
248
|
const nodes = [];
|
|
246
|
-
let hyperlinkIndex = 0;
|
|
247
249
|
for (const item of change.content) if (item.type === "run") nodes.push(...convertRun(item, getInheritedRunFormatting(item.formatting), styleResolver, textBoxAnchors));
|
|
248
250
|
else {
|
|
249
|
-
const currentHyperlinkIndex =
|
|
250
|
-
hyperlinkIndex += 1;
|
|
251
|
+
const currentHyperlinkIndex = nextHyperlinkInstanceIndex();
|
|
251
252
|
nodes.push(...convertHyperlink(item, {
|
|
252
253
|
getInheritedRunFormatting,
|
|
253
254
|
styleResolver,
|
|
@@ -919,7 +920,8 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
|
|
|
919
920
|
styleResolver: null,
|
|
920
921
|
context: {
|
|
921
922
|
theme: null,
|
|
922
|
-
nextTextBoxGroupId: createTextBoxGroupIdFactory()
|
|
923
|
+
nextTextBoxGroupId: createTextBoxGroupIdFactory(),
|
|
924
|
+
nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
|
|
923
925
|
},
|
|
924
926
|
isHeader: nodeType === "tableHeader",
|
|
925
927
|
gridWidthPercent: void 0,
|
|
@@ -971,20 +973,17 @@ function convertMathEquation(math) {
|
|
|
971
973
|
/**
|
|
972
974
|
* Convert an InlineSdt to a ProseMirror sdt node with inline content.
|
|
973
975
|
*/
|
|
974
|
-
function convertInlineSdt(sdt, getInheritedRunFormatting, styleResolver, textBoxAnchors) {
|
|
976
|
+
function convertInlineSdt(sdt, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors) {
|
|
975
977
|
const props = sdt.properties;
|
|
976
978
|
const inlineNodes = [];
|
|
977
|
-
let hyperlinkIndex = 0;
|
|
978
979
|
for (const content of sdt.content) if (content.type === "run") {
|
|
979
980
|
const runNodes = convertRun(content, getInheritedRunFormatting(content.formatting), styleResolver, textBoxAnchors);
|
|
980
981
|
inlineNodes.push(...runNodes);
|
|
981
982
|
} else if (content.type === "hyperlink") {
|
|
982
|
-
const currentHyperlinkIndex = hyperlinkIndex;
|
|
983
|
-
hyperlinkIndex += 1;
|
|
984
983
|
const linkNodes = convertHyperlink(content, {
|
|
985
984
|
getInheritedRunFormatting,
|
|
986
985
|
styleResolver,
|
|
987
|
-
hyperlinkIndex:
|
|
986
|
+
hyperlinkIndex: nextHyperlinkInstanceIndex(),
|
|
988
987
|
textBoxAnchors
|
|
989
988
|
});
|
|
990
989
|
inlineNodes.push(...linkNodes);
|
|
@@ -992,12 +991,12 @@ function convertInlineSdt(sdt, getInheritedRunFormatting, styleResolver, textBox
|
|
|
992
991
|
const fieldNode = convertField(content, getInheritedRunFormatting, styleResolver);
|
|
993
992
|
if (fieldNode) inlineNodes.push(fieldNode);
|
|
994
993
|
} else if (content.type === "inlineSdt") {
|
|
995
|
-
const nestedSdt = convertInlineSdt(content, getInheritedRunFormatting, styleResolver, textBoxAnchors);
|
|
994
|
+
const nestedSdt = convertInlineSdt(content, nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, textBoxAnchors);
|
|
996
995
|
if (nestedSdt) inlineNodes.push(nestedSdt);
|
|
997
|
-
} else if (content.type === "insertion") inlineNodes.push(...convertTrackedChange(content, "insertion", getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
998
|
-
else if (content.type === "deletion") inlineNodes.push(...convertTrackedChange(content, "deletion", getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
999
|
-
else if (content.type === "moveTo") inlineNodes.push(...convertTrackedChange(content, "insertion", getInheritedRunFormatting, styleResolver, "moveTo", textBoxAnchors));
|
|
1000
|
-
else if (content.type === "moveFrom") inlineNodes.push(...convertTrackedChange(content, "deletion", getInheritedRunFormatting, styleResolver, "moveFrom", textBoxAnchors));
|
|
996
|
+
} else if (content.type === "insertion") inlineNodes.push(...convertTrackedChange(content, "insertion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
997
|
+
else if (content.type === "deletion") inlineNodes.push(...convertTrackedChange(content, "deletion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, null, textBoxAnchors));
|
|
998
|
+
else if (content.type === "moveTo") inlineNodes.push(...convertTrackedChange(content, "insertion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, "moveTo", textBoxAnchors));
|
|
999
|
+
else if (content.type === "moveFrom") inlineNodes.push(...convertTrackedChange(content, "deletion", nextHyperlinkInstanceIndex, getInheritedRunFormatting, styleResolver, "moveFrom", textBoxAnchors));
|
|
1001
1000
|
else {
|
|
1002
1001
|
const mathNode = convertMathEquation(content);
|
|
1003
1002
|
if (mathNode) inlineNodes.push(mathNode);
|
|
@@ -1378,7 +1377,7 @@ function convertShape(shape) {
|
|
|
1378
1377
|
}
|
|
1379
1378
|
function convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId, context, extraRunFormatting, tableParagraphOverlay }) {
|
|
1380
1379
|
const { textBoxes, textBoxAnchors } = extractTextBoxesFromParagraph(block, textBoxGroupId);
|
|
1381
|
-
const pmParagraph = convertParagraph(block, styleResolver, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
|
|
1380
|
+
const pmParagraph = convertParagraph(block, styleResolver, context.nextHyperlinkInstanceIndex, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
|
|
1382
1381
|
const nodes = [];
|
|
1383
1382
|
const isEmptyAfterExtraction = textBoxes.length > 0 && !hasContentBesidesTextBoxAnchors(pmParagraph);
|
|
1384
1383
|
const keepWrapperParagraph = isEmptyAfterExtraction && hasParagraphBoundaryPayload(block, pmParagraph);
|
|
@@ -1637,19 +1636,18 @@ function headerFooterToProseDoc(content, options) {
|
|
|
1637
1636
|
const styleResolver = options?.styles ? createStyleEngine(options.styles) : null;
|
|
1638
1637
|
const theme = options?.theme ?? null;
|
|
1639
1638
|
const nextTextBoxGroupId = createTextBoxGroupIdFactory();
|
|
1639
|
+
const conversionContext = {
|
|
1640
|
+
theme,
|
|
1641
|
+
nextTextBoxGroupId,
|
|
1642
|
+
nextHyperlinkInstanceIndex: createHyperlinkInstanceIndexAllocator()
|
|
1643
|
+
};
|
|
1640
1644
|
const convertBlocks = (blocks) => {
|
|
1641
1645
|
const out = [];
|
|
1642
1646
|
for (const block of blocks) if (block.type === "paragraph") out.push(...convertParagraphWithTextBoxes(block, styleResolver, {
|
|
1643
1647
|
textBoxGroupId: nextTextBoxGroupId(),
|
|
1644
|
-
context:
|
|
1645
|
-
theme,
|
|
1646
|
-
nextTextBoxGroupId
|
|
1647
|
-
}
|
|
1648
|
-
}));
|
|
1649
|
-
else if (block.type === "table") out.push(convertTable(block, styleResolver, {
|
|
1650
|
-
theme,
|
|
1651
|
-
nextTextBoxGroupId
|
|
1648
|
+
context: conversionContext
|
|
1652
1649
|
}));
|
|
1650
|
+
else if (block.type === "table") out.push(convertTable(block, styleResolver, conversionContext));
|
|
1653
1651
|
else out.push(convertBlockSdt(block, convertBlocks));
|
|
1654
1652
|
return out;
|
|
1655
1653
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ParagraphAttrs, ParagraphBlock } from "../layout-engine/types.js";
|
|
2
|
+
//#region src/utils/paragraphInlineLayout.d.ts
|
|
3
|
+
type ParagraphAlignment = NonNullable<ParagraphAttrs["alignment"]>;
|
|
4
|
+
type PhysicalParagraphInlineLayout = {
|
|
5
|
+
alignment: ParagraphAlignment;
|
|
6
|
+
explicitAlignment?: ParagraphAlignment;
|
|
7
|
+
indentLeft: number;
|
|
8
|
+
indentRight: number;
|
|
9
|
+
isRtl: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** Resolve logical OOXML paragraph sides into physical inline geometry. */
|
|
12
|
+
declare const resolvePhysicalParagraphInlineLayout: (block: ParagraphBlock) => PhysicalParagraphInlineLayout;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { PhysicalParagraphInlineLayout, resolvePhysicalParagraphInlineLayout };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isRtlParagraph } from "./paragraphBaseDirection.js";
|
|
2
|
+
//#region src/utils/paragraphInlineLayout.ts
|
|
3
|
+
const mirrorHorizontalAlignment = (alignment) => {
|
|
4
|
+
if (alignment === "left") return "right";
|
|
5
|
+
if (alignment === "right") return "left";
|
|
6
|
+
return alignment;
|
|
7
|
+
};
|
|
8
|
+
/** Resolve logical OOXML paragraph sides into physical inline geometry. */
|
|
9
|
+
const resolvePhysicalParagraphInlineLayout = (block) => {
|
|
10
|
+
const attrs = block.attrs;
|
|
11
|
+
const isRtl = isRtlParagraph(block);
|
|
12
|
+
const mirrorsHorizontalSides = attrs?.bidi === true;
|
|
13
|
+
const explicitAlignment = mirrorsHorizontalSides ? mirrorHorizontalAlignment(attrs?.alignment) : attrs?.alignment;
|
|
14
|
+
const indentLeft = attrs?.indent?.left ?? 0;
|
|
15
|
+
const indentRight = attrs?.indent?.right ?? 0;
|
|
16
|
+
return {
|
|
17
|
+
alignment: explicitAlignment ?? (isRtl ? "right" : "left"),
|
|
18
|
+
...explicitAlignment === void 0 ? {} : { explicitAlignment },
|
|
19
|
+
indentLeft: mirrorsHorizontalSides ? indentRight : indentLeft,
|
|
20
|
+
indentRight: mirrorsHorizontalSides ? indentLeft : indentRight,
|
|
21
|
+
isRtl
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { resolvePhysicalParagraphInlineLayout };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.3",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|