@stll/folio-core 0.40.0 → 0.41.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/clean-text.d.ts +28 -6
- package/dist/ai-edits/clean-text.js +28 -13
- package/dist/ai-edits/headless.d.ts +12 -0
- package/dist/ai-edits/headless.js +26 -3
- package/dist/ai-edits/snapshot.d.ts +6 -1
- package/dist/ai-edits/snapshot.js +15 -10
- package/dist/ai-suggestions/text-positions.js +15 -3
- package/dist/compare/inline-atoms.js +2 -2
- package/dist/compat/eigenpal.d.ts +3 -2
- package/dist/compat/eigenpal.js +2 -1
- package/dist/display-list/primitives.d.ts +2 -1
- package/dist/document-operations.d.ts +3 -2
- package/dist/docx/blockPlainText.d.ts +8 -0
- package/dist/docx/blockPlainText.js +40 -0
- package/dist/docx/compatibility.d.ts +16 -2
- package/dist/docx/compatibility.js +30 -9
- package/dist/docx/footnoteParser.js +4 -23
- package/dist/docx/graphicFrameLocks.d.ts +22 -0
- package/dist/docx/graphicFrameLocks.js +55 -0
- package/dist/docx/groupDrawingParser.d.ts +7 -1
- package/dist/docx/groupDrawingParser.js +11 -2
- package/dist/docx/headerFooterParser.d.ts +5 -1
- package/dist/docx/headerFooterParser.js +7 -21
- package/dist/docx/imageParser.js +5 -0
- package/dist/docx/imageRawXml.d.ts +33 -1
- package/dist/docx/imageRawXml.js +56 -1
- package/dist/docx/runParser.js +43 -21
- package/dist/docx/serializer/runSerializer.js +4 -2
- package/dist/docx/server/createBilingualDocument.js +31 -5
- package/dist/docx/shapeParser.js +32 -6
- package/dist/docx/vmlImageParser.d.ts +11 -1
- package/dist/docx/vmlImageParser.js +29 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/internal/compare/inline-presentation.d.ts +11 -3
- package/dist/internal/compare/inline-presentation.js +8 -1
- package/dist/prosemirror/attrs/index.js +33 -3
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +13 -2
- package/dist/prosemirror/conversion/fromProseDoc.js +80 -27
- package/dist/prosemirror/conversion/index.d.ts +2 -2
- package/dist/prosemirror/conversion/toProseDoc.js +10 -2
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -0
- package/dist/prosemirror/imageCommit.js +7 -3
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +16 -1
- package/dist/prosemirror/runFormattingInlineCarriers.js +20 -1
- package/dist/prosemirror/schema/nodes.d.ts +20 -0
- package/package.json +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { blockPlainText } from "./blockPlainText.js";
|
|
1
2
|
import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
|
|
2
|
-
import {
|
|
3
|
+
import { parseParagraph } from "./paragraphParser.js";
|
|
3
4
|
import { parseSdtProperties } from "./sdtProperties.js";
|
|
4
5
|
import { parseTable } from "./tableParser.js";
|
|
5
6
|
import { findChild, findChildren, getAttributes, getChildElements, getLocalName, parseXml } from "./xmlParser.js";
|
|
6
|
-
import { panic } from "better-result";
|
|
7
7
|
//#region src/docx/footnoteParser.ts
|
|
8
8
|
/**
|
|
9
9
|
* Parse note type attribute
|
|
@@ -173,32 +173,13 @@ function createEndnoteMap(byId, endnotes) {
|
|
|
173
173
|
* Uses the accepted tracked-change view and recurses through every note block.
|
|
174
174
|
*/
|
|
175
175
|
function getFootnoteText(footnote) {
|
|
176
|
-
return
|
|
176
|
+
return blockPlainText(footnote.content);
|
|
177
177
|
}
|
|
178
178
|
/**
|
|
179
179
|
* Get plain text content of an endnote.
|
|
180
180
|
*/
|
|
181
181
|
function getEndnoteText(endnote) {
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
function collectNoteBlockTexts(blocks) {
|
|
185
|
-
const texts = [];
|
|
186
|
-
for (const block of blocks) switch (block.type) {
|
|
187
|
-
case "paragraph":
|
|
188
|
-
texts.push(getParagraphText(block));
|
|
189
|
-
break;
|
|
190
|
-
case "table":
|
|
191
|
-
for (const row of block.rows) {
|
|
192
|
-
if (row.formatting?.hidden === true) continue;
|
|
193
|
-
texts.push(row.cells.map((cell) => collectNoteBlockTexts(cell.content).join("\n")).join(" "));
|
|
194
|
-
}
|
|
195
|
-
break;
|
|
196
|
-
case "blockSdt":
|
|
197
|
-
texts.push(...collectNoteBlockTexts(block.content));
|
|
198
|
-
break;
|
|
199
|
-
default: panic(`Unsupported note block in plain-text extraction: ${JSON.stringify(block)}`);
|
|
200
|
-
}
|
|
201
|
-
return texts;
|
|
182
|
+
return blockPlainText(endnote.content);
|
|
202
183
|
}
|
|
203
184
|
/**
|
|
204
185
|
* Check if a footnote is a separator (not regular content)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
//#region src/docx/graphicFrameLocks.d.ts
|
|
4
|
+
/** Every modeled lock, in schema order: the total map above keeps it exhaustive. */
|
|
5
|
+
declare const GRAPHIC_FRAME_LOCK_KEYS: ("noGrp" | "noDrilldown" | "noSelect" | "noChangeAspect" | "noMove" | "noResize")[];
|
|
6
|
+
/**
|
|
7
|
+
* Read the locks off a `wp:inline` or `wp:anchor` element.
|
|
8
|
+
*
|
|
9
|
+
* Returns undefined when the element is absent or carries no recognized
|
|
10
|
+
* attribute: an authored `<a:graphicFrameLocks/>` with nothing on it means the
|
|
11
|
+
* same as no element at all, so both stay "spec defaults" on the model.
|
|
12
|
+
*/
|
|
13
|
+
declare const parseGraphicFrameLocks: (parent: XmlElement) => document_d_exports.ImageFrameLocks | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Emit the whole `wp:cNvGraphicFramePr` element for regenerated DrawingML.
|
|
16
|
+
*
|
|
17
|
+
* Absent locks mean no authored frame was ever parsed (a Folio-created
|
|
18
|
+
* picture), which keeps the historical `noChangeAspect="1"`.
|
|
19
|
+
*/
|
|
20
|
+
declare const serializeGraphicFrameLocks: (locks: document_d_exports.ImageFrameLocks | undefined) => string;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { GRAPHIC_FRAME_LOCK_KEYS, parseGraphicFrameLocks, serializeGraphicFrameLocks };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { findChild, getAttribute, parseOnOffValue } from "./xmlParser.js";
|
|
2
|
+
//#region src/docx/graphicFrameLocks.ts
|
|
3
|
+
/**
|
|
4
|
+
* Model key → OOXML attribute. Insertion order is the schema's attribute
|
|
5
|
+
* order, which is also the order we emit.
|
|
6
|
+
*/
|
|
7
|
+
const GRAPHIC_FRAME_LOCK_ATTRIBUTES = {
|
|
8
|
+
noGrp: "noGrp",
|
|
9
|
+
noDrilldown: "noDrilldown",
|
|
10
|
+
noSelect: "noSelect",
|
|
11
|
+
noChangeAspect: "noChangeAspect",
|
|
12
|
+
noMove: "noMove",
|
|
13
|
+
noResize: "noResize"
|
|
14
|
+
};
|
|
15
|
+
const isGraphicFrameLockKey = (key) => key in GRAPHIC_FRAME_LOCK_ATTRIBUTES;
|
|
16
|
+
/** Every modeled lock, in schema order: the total map above keeps it exhaustive. */
|
|
17
|
+
const GRAPHIC_FRAME_LOCK_KEYS = Object.keys(GRAPHIC_FRAME_LOCK_ATTRIBUTES).filter(isGraphicFrameLockKey);
|
|
18
|
+
const DRAWINGML_NAMESPACE = "xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"";
|
|
19
|
+
/**
|
|
20
|
+
* Read the locks off a `wp:inline` or `wp:anchor` element.
|
|
21
|
+
*
|
|
22
|
+
* Returns undefined when the element is absent or carries no recognized
|
|
23
|
+
* attribute: an authored `<a:graphicFrameLocks/>` with nothing on it means the
|
|
24
|
+
* same as no element at all, so both stay "spec defaults" on the model.
|
|
25
|
+
*/
|
|
26
|
+
const parseGraphicFrameLocks = (parent) => {
|
|
27
|
+
const locksEl = findChild(findChild(parent, "wp", "cNvGraphicFramePr"), "a", "graphicFrameLocks");
|
|
28
|
+
if (!locksEl) return;
|
|
29
|
+
const locks = {};
|
|
30
|
+
let present = false;
|
|
31
|
+
for (const key of GRAPHIC_FRAME_LOCK_KEYS) {
|
|
32
|
+
const value = parseOnOffValue(getAttribute(locksEl, null, GRAPHIC_FRAME_LOCK_ATTRIBUTES[key]));
|
|
33
|
+
if (value !== void 0) {
|
|
34
|
+
locks[key] = value;
|
|
35
|
+
present = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return present ? locks : void 0;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Emit the whole `wp:cNvGraphicFramePr` element for regenerated DrawingML.
|
|
42
|
+
*
|
|
43
|
+
* Absent locks mean no authored frame was ever parsed (a Folio-created
|
|
44
|
+
* picture), which keeps the historical `noChangeAspect="1"`.
|
|
45
|
+
*/
|
|
46
|
+
const serializeGraphicFrameLocks = (locks) => {
|
|
47
|
+
const attrs = locks ? GRAPHIC_FRAME_LOCK_KEYS.flatMap((key) => {
|
|
48
|
+
const value = locks[key];
|
|
49
|
+
return value === void 0 ? [] : [`${GRAPHIC_FRAME_LOCK_ATTRIBUTES[key]}="${value ? "1" : "0"}"`];
|
|
50
|
+
}) : ["noChangeAspect=\"1\""];
|
|
51
|
+
const attrList = attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
52
|
+
return `<wp:cNvGraphicFramePr><a:graphicFrameLocks ${DRAWINGML_NAMESPACE}${attrList}/></wp:cNvGraphicFramePr>`;
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { GRAPHIC_FRAME_LOCK_KEYS, parseGraphicFrameLocks, serializeGraphicFrameLocks };
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
import { XmlElement } from "./xmlParser.js";
|
|
3
3
|
//#region src/docx/groupDrawingParser.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Whether a `w:drawing` carries a WordprocessingGroup payload. A group this
|
|
6
|
+
* module declines to rasterize has no editable projection either — the shape
|
|
7
|
+
* model holds one shape, not a group — so the caller must preserve it raw.
|
|
8
|
+
*/
|
|
9
|
+
declare const isGroupDrawing: (drawing: XmlElement) => boolean;
|
|
4
10
|
/** Parse a WordprocessingGroup drawing into a safe SVG-backed image preview. */
|
|
5
11
|
declare const parseGroupDrawing: (drawing: XmlElement, rels?: document_d_exports.RelationshipMap, media?: Map<string, document_d_exports.MediaFile>) => document_d_exports.Image | null;
|
|
6
12
|
//#endregion
|
|
7
|
-
export { parseGroupDrawing };
|
|
13
|
+
export { isGroupDrawing, parseGroupDrawing };
|
|
@@ -165,9 +165,18 @@ const createSvg = (group, width, height, rels, media) => {
|
|
|
165
165
|
const viewBox = groupViewBox(group, width, height);
|
|
166
166
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}" width="${emuToPixels(width)}" height="${emuToPixels(height)}">${content}</svg>`;
|
|
167
167
|
};
|
|
168
|
+
const groupElement = (drawing) => {
|
|
169
|
+
return findChildByLocalName(findAllDeep(drawing, "a", "graphicData").at(0) ?? null, "wgp");
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Whether a `w:drawing` carries a WordprocessingGroup payload. A group this
|
|
173
|
+
* module declines to rasterize has no editable projection either — the shape
|
|
174
|
+
* model holds one shape, not a group — so the caller must preserve it raw.
|
|
175
|
+
*/
|
|
176
|
+
const isGroupDrawing = (drawing) => groupElement(drawing) !== null;
|
|
168
177
|
/** Parse a WordprocessingGroup drawing into a safe SVG-backed image preview. */
|
|
169
178
|
const parseGroupDrawing = (drawing, rels, media) => {
|
|
170
|
-
const group =
|
|
179
|
+
const group = groupElement(drawing);
|
|
171
180
|
if (!group) return null;
|
|
172
181
|
const image = parseImage(drawing, void 0, void 0);
|
|
173
182
|
if (!image || image.size.width <= 0 || image.size.height <= 0) return null;
|
|
@@ -179,4 +188,4 @@ const parseGroupDrawing = (drawing, rels, media) => {
|
|
|
179
188
|
return image;
|
|
180
189
|
};
|
|
181
190
|
//#endregion
|
|
182
|
-
export { parseGroupDrawing };
|
|
191
|
+
export { isGroupDrawing, parseGroupDrawing };
|
|
@@ -77,7 +77,11 @@ declare function createEmptyHeaderFooterMap(): HeaderFooterMap;
|
|
|
77
77
|
*/
|
|
78
78
|
declare function buildHeaderFooterMap(references: (document_d_exports.HeaderReference | document_d_exports.FooterReference)[], xmlContents: Map<string, string>, isHeader: boolean, styles?: StyleMap | null, theme?: document_d_exports.Theme | null, numbering?: NumberingMap | null, rels?: document_d_exports.RelationshipMap | null, media?: Map<string, document_d_exports.MediaFile> | null): HeaderFooterMap;
|
|
79
79
|
/**
|
|
80
|
-
* Get plain text content of a header/footer
|
|
80
|
+
* Get plain text content of a header/footer.
|
|
81
|
+
*
|
|
82
|
+
* Shares one walk with the note stories: this used to read only text runs, so a
|
|
83
|
+
* header's fields, hyperlinks, tabs and breaks were silently absent from its
|
|
84
|
+
* text while the same paragraph in a footnote read in full.
|
|
81
85
|
*/
|
|
82
86
|
declare function getHeaderFooterText(hf: document_d_exports.HeaderFooter): string;
|
|
83
87
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseBlockContent } from "./blockContentParser.js";
|
|
2
|
+
import { blockPlainText } from "./blockPlainText.js";
|
|
2
3
|
import { parseFooterReference, parseFooterReferences, parseHeaderReference, parseHeaderReferences } from "./headerFooterRefParser.js";
|
|
3
4
|
import { assignHeaderFooterVerbatimXml } from "./headerFooterVerbatim.js";
|
|
4
5
|
import { cloneParagraphWithPropertySource } from "./paragraphPropertySource.js";
|
|
@@ -139,29 +140,14 @@ function buildHeaderFooterMap(references, xmlContents, isHeader, styles = null,
|
|
|
139
140
|
return createHeaderFooterMap(byId);
|
|
140
141
|
}
|
|
141
142
|
/**
|
|
142
|
-
* Get plain text content of a header/footer
|
|
143
|
+
* Get plain text content of a header/footer.
|
|
144
|
+
*
|
|
145
|
+
* Shares one walk with the note stories: this used to read only text runs, so a
|
|
146
|
+
* header's fields, hyperlinks, tabs and breaks were silently absent from its
|
|
147
|
+
* text while the same paragraph in a footnote read in full.
|
|
143
148
|
*/
|
|
144
149
|
function getHeaderFooterText(hf) {
|
|
145
|
-
|
|
146
|
-
for (const item of hf.content) if (item.type === "paragraph") {
|
|
147
|
-
const paraTexts = [];
|
|
148
|
-
for (const content of item.content) if (content.type === "run") {
|
|
149
|
-
for (const runContent of content.content) if (runContent.type === "text") paraTexts.push(runContent.text);
|
|
150
|
-
}
|
|
151
|
-
texts.push(paraTexts.join(""));
|
|
152
|
-
} else if (item.type === "blockSdt") texts.push(getHeaderFooterText({
|
|
153
|
-
type: hf.type,
|
|
154
|
-
hdrFtrType: hf.hdrFtrType,
|
|
155
|
-
content: item.content
|
|
156
|
-
}));
|
|
157
|
-
else for (const row of item.rows) for (const cell of row.cells) for (const cellContent of cell.content) if (cellContent.type === "paragraph") {
|
|
158
|
-
const paraTexts = [];
|
|
159
|
-
for (const content of cellContent.content) if (content.type === "run") {
|
|
160
|
-
for (const runContent of content.content) if (runContent.type === "text") paraTexts.push(runContent.text);
|
|
161
|
-
}
|
|
162
|
-
texts.push(paraTexts.join(""));
|
|
163
|
-
}
|
|
164
|
-
return texts.join("\n");
|
|
150
|
+
return blockPlainText(hf.content);
|
|
165
151
|
}
|
|
166
152
|
/**
|
|
167
153
|
* Check if header/footer is empty (no content)
|
package/dist/docx/imageParser.js
CHANGED
|
@@ -2,6 +2,7 @@ import { sanitizeImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
|
2
2
|
import { emuToPixels } from "../utils/units.js";
|
|
3
3
|
import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
|
|
4
4
|
import { WRAP_ELEMENT_NAMES, parsePositionH, parsePositionV, parseWrapElement } from "./drawingUtils.js";
|
|
5
|
+
import { parseGraphicFrameLocks } from "./graphicFrameLocks.js";
|
|
5
6
|
import { resolveTarget } from "./relsParser.js";
|
|
6
7
|
import { isTextBoxDrawing } from "./textBoxParser.js";
|
|
7
8
|
import { findByFullName, findChild, getAttribute, getChildElements, parseNumericAttribute, parseOnOffValue } from "./xmlParser.js";
|
|
@@ -299,6 +300,7 @@ function parseInline(inlineEl, rels, media) {
|
|
|
299
300
|
const size = parseExtent(findByFullName(inlineEl, "wp:extent"));
|
|
300
301
|
const padding = parseEffectExtent(findByFullName(inlineEl, "wp:effectExtent"));
|
|
301
302
|
const props = parseDocProps(findByFullName(inlineEl, "wp:docPr"));
|
|
303
|
+
const frameLocks = parseGraphicFrameLocks(inlineEl);
|
|
302
304
|
const blipFill = findBlipFillElement(inlineEl);
|
|
303
305
|
const blip = blipFill ? findByFullName(blipFill, "a:blip") : null;
|
|
304
306
|
const rId = extractBlipRId(blip);
|
|
@@ -334,6 +336,7 @@ function parseInline(inlineEl, rels, media) {
|
|
|
334
336
|
if (transform) image.transform = transform;
|
|
335
337
|
if (crop) image.crop = crop;
|
|
336
338
|
if (opacity !== void 0) image.opacity = opacity;
|
|
339
|
+
if (frameLocks) image.frameLocks = frameLocks;
|
|
337
340
|
if (props.hlinkRId && rels) {
|
|
338
341
|
const safeHref = sanitizeExternalUrl(resolveTarget(rels, props.hlinkRId));
|
|
339
342
|
if (safeHref) {
|
|
@@ -355,6 +358,7 @@ function parseAnchor(anchorEl, rels, media) {
|
|
|
355
358
|
const size = parseExtent(findByFullName(anchorEl, "wp:extent"));
|
|
356
359
|
const padding = parseEffectExtent(findByFullName(anchorEl, "wp:effectExtent"));
|
|
357
360
|
const props = parseDocProps(findByFullName(anchorEl, "wp:docPr"));
|
|
361
|
+
const frameLocks = parseGraphicFrameLocks(anchorEl);
|
|
358
362
|
const behindDoc = parseOnOffValue(getAttribute(anchorEl, null, "behindDoc")) === true;
|
|
359
363
|
const layoutInCell = parseOnOffAttr(anchorEl, "layoutInCell");
|
|
360
364
|
const allowOverlap = parseOnOffAttr(anchorEl, "allowOverlap");
|
|
@@ -405,6 +409,7 @@ function parseAnchor(anchorEl, rels, media) {
|
|
|
405
409
|
if (transform) image.transform = transform;
|
|
406
410
|
if (crop) image.crop = crop;
|
|
407
411
|
if (opacity !== void 0) image.opacity = opacity;
|
|
412
|
+
if (frameLocks) image.frameLocks = frameLocks;
|
|
408
413
|
if (layoutInCell !== void 0) image.layoutInCell = layoutInCell;
|
|
409
414
|
if (allowOverlap !== void 0) image.allowOverlap = allowOverlap;
|
|
410
415
|
if (props.hlinkRId && rels) {
|
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
//#region src/docx/imageRawXml.d.ts
|
|
3
|
+
/** An unclassified drawing is an ordinary editable projection; a classified one is not. */
|
|
4
|
+
declare const allowsDirectDrawingEdit: (mode: document_d_exports.DrawingRawXmlMode | undefined) => boolean;
|
|
5
|
+
/** Narrow an unvalidated value (a ProseMirror attr) to a raw-XML mode. */
|
|
6
|
+
declare const isDrawingRawXmlMode: (value: unknown) => value is document_d_exports.DrawingRawXmlMode;
|
|
7
|
+
/**
|
|
8
|
+
* Stands in for a preview's fingerprint once the editor has changed the image
|
|
9
|
+
* it renders. `canonicalJson` always produces an object literal, so this can
|
|
10
|
+
* never collide with a real fingerprint and the drawing can never replay.
|
|
11
|
+
*/
|
|
12
|
+
declare const EDITED_PREVIEW_FINGERPRINT = "editedPreview";
|
|
3
13
|
/** Fingerprints modeled image fields which make raw DrawingML stale when edited. */
|
|
4
14
|
declare const imageRawXmlFingerprint: (image: document_d_exports.Image) => string;
|
|
5
15
|
/** Editable raw drawing XML can replay only while its modeled projection is unchanged. */
|
|
6
16
|
declare const canReplayEditableImageRawXml: (drawing: document_d_exports.DrawingContent) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* What a drawing costs the document when Folio writes it back.
|
|
19
|
+
*
|
|
20
|
+
* `native` and `replayable` both survive a save intact, so neither restricts
|
|
21
|
+
* editing; only `opaque` loses content.
|
|
22
|
+
*/
|
|
23
|
+
declare const DRAWING_SAFETY_CLASSES: {
|
|
24
|
+
/** No raw XML: Folio owns the whole drawing and regenerates it from the model. */
|
|
25
|
+
readonly NATIVE: "native";
|
|
26
|
+
/** Raw XML the serializer replays verbatim, so every unmodeled attribute survives. */
|
|
27
|
+
readonly REPLAYABLE: "replayable";
|
|
28
|
+
/** Raw XML the serializer can neither replay nor regenerate without losing the media. */
|
|
29
|
+
readonly OPAQUE: "opaque";
|
|
30
|
+
};
|
|
31
|
+
type DrawingSafetyClass = (typeof DRAWING_SAFETY_CLASSES)[keyof typeof DRAWING_SAFETY_CLASSES];
|
|
32
|
+
/**
|
|
33
|
+
* Classify a drawing by what the run serializer will do with it.
|
|
34
|
+
*
|
|
35
|
+
* Shares {@link canReplayEditableImageRawXml} with the serializer so a
|
|
36
|
+
* compatibility probe and a save can never disagree about the same drawing.
|
|
37
|
+
*/
|
|
38
|
+
declare const classifyDrawingSafety: (drawing: document_d_exports.DrawingContent) => DrawingSafetyClass;
|
|
7
39
|
//#endregion
|
|
8
|
-
export { canReplayEditableImageRawXml, imageRawXmlFingerprint };
|
|
40
|
+
export { DRAWING_SAFETY_CLASSES, DrawingSafetyClass, EDITED_PREVIEW_FINGERPRINT, allowsDirectDrawingEdit, canReplayEditableImageRawXml, classifyDrawingSafety, imageRawXmlFingerprint, isDrawingRawXmlMode };
|
package/dist/docx/imageRawXml.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
2
|
import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
|
|
3
3
|
//#region src/docx/imageRawXml.ts
|
|
4
|
+
/**
|
|
5
|
+
* Whether the editor may manipulate the modeled image of a classified drawing.
|
|
6
|
+
*
|
|
7
|
+
* A mode exists precisely because `rawXml` says more than the model does, so
|
|
8
|
+
* both current modes refuse: a resize would either serialize a placeholder
|
|
9
|
+
* (preserve-only) or one child picture in place of a group (preview-only).
|
|
10
|
+
* Totality is the point — a third mode cannot be added without deciding here.
|
|
11
|
+
*/
|
|
12
|
+
const DRAWING_RAW_XML_MODE_ALLOWS_DIRECT_EDIT = {
|
|
13
|
+
[DRAWING_RAW_XML_MODES.PRESERVE_ONLY]: false,
|
|
14
|
+
[DRAWING_RAW_XML_MODES.PREVIEW_ONLY]: false
|
|
15
|
+
};
|
|
16
|
+
/** An unclassified drawing is an ordinary editable projection; a classified one is not. */
|
|
17
|
+
const allowsDirectDrawingEdit = (mode) => mode === void 0 || DRAWING_RAW_XML_MODE_ALLOWS_DIRECT_EDIT[mode];
|
|
18
|
+
/** Narrow an unvalidated value (a ProseMirror attr) to a raw-XML mode. */
|
|
19
|
+
const isDrawingRawXmlMode = (value) => typeof value === "string" && value in DRAWING_RAW_XML_MODE_ALLOWS_DIRECT_EDIT;
|
|
20
|
+
/**
|
|
21
|
+
* Stands in for a preview's fingerprint once the editor has changed the image
|
|
22
|
+
* it renders. `canonicalJson` always produces an object literal, so this can
|
|
23
|
+
* never collide with a real fingerprint and the drawing can never replay.
|
|
24
|
+
*/
|
|
25
|
+
const EDITED_PREVIEW_FINGERPRINT = "editedPreview";
|
|
4
26
|
const editableImageProjection = ({ id: _id, rId: _rId, src: _src, mimeType: _mimeType, filename: _filename, ...image }) => image;
|
|
5
27
|
/** Fingerprints modeled image fields which make raw DrawingML stale when edited. */
|
|
6
28
|
const imageRawXmlFingerprint = (image) => canonicalJson(editableImageProjection(image));
|
|
@@ -9,5 +31,38 @@ const canReplayEditableImageRawXml = (drawing) => {
|
|
|
9
31
|
if (drawing.rawXmlMode === DRAWING_RAW_XML_MODES.PRESERVE_ONLY) return true;
|
|
10
32
|
return drawing.rawImageFingerprint === void 0 || drawing.rawImageFingerprint === imageRawXmlFingerprint(drawing.image);
|
|
11
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* What a drawing costs the document when Folio writes it back.
|
|
36
|
+
*
|
|
37
|
+
* `native` and `replayable` both survive a save intact, so neither restricts
|
|
38
|
+
* editing; only `opaque` loses content.
|
|
39
|
+
*/
|
|
40
|
+
const DRAWING_SAFETY_CLASSES = {
|
|
41
|
+
/** No raw XML: Folio owns the whole drawing and regenerates it from the model. */
|
|
42
|
+
NATIVE: "native",
|
|
43
|
+
/** Raw XML the serializer replays verbatim, so every unmodeled attribute survives. */
|
|
44
|
+
REPLAYABLE: "replayable",
|
|
45
|
+
/** Raw XML the serializer can neither replay nor regenerate without losing the media. */
|
|
46
|
+
OPAQUE: "opaque"
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Regenerated DrawingML points at `image.rId`, and `serializePicGraphic` falls
|
|
50
|
+
* back to `"rId1"` when that id is empty, which rebinds the picture to whichever
|
|
51
|
+
* relationship happens to be first. A drawing with no relationship id therefore
|
|
52
|
+
* has no faithful regeneration.
|
|
53
|
+
*/
|
|
54
|
+
const canRegenerateDrawing = (drawing) => drawing.image.rId !== "";
|
|
55
|
+
/**
|
|
56
|
+
* Classify a drawing by what the run serializer will do with it.
|
|
57
|
+
*
|
|
58
|
+
* Shares {@link canReplayEditableImageRawXml} with the serializer so a
|
|
59
|
+
* compatibility probe and a save can never disagree about the same drawing.
|
|
60
|
+
*/
|
|
61
|
+
const classifyDrawingSafety = (drawing) => {
|
|
62
|
+
if (drawing.rawXml === void 0) return DRAWING_SAFETY_CLASSES.NATIVE;
|
|
63
|
+
if (canReplayEditableImageRawXml(drawing)) return DRAWING_SAFETY_CLASSES.REPLAYABLE;
|
|
64
|
+
if (drawing.rawXmlMode === DRAWING_RAW_XML_MODES.PREVIEW_ONLY) return DRAWING_SAFETY_CLASSES.OPAQUE;
|
|
65
|
+
return canRegenerateDrawing(drawing) ? DRAWING_SAFETY_CLASSES.NATIVE : DRAWING_SAFETY_CLASSES.OPAQUE;
|
|
66
|
+
};
|
|
12
67
|
//#endregion
|
|
13
|
-
export { canReplayEditableImageRawXml, imageRawXmlFingerprint };
|
|
68
|
+
export { DRAWING_SAFETY_CLASSES, EDITED_PREVIEW_FINGERPRINT, allowsDirectDrawingEdit, canReplayEditableImageRawXml, classifyDrawingSafety, imageRawXmlFingerprint, isDrawingRawXmlMode };
|
package/dist/docx/runParser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isValidHexColor } from "../utils/colorResolver.js";
|
|
2
2
|
import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
|
|
3
3
|
import { parseDiagramPreview } from "./diagramPreview.js";
|
|
4
|
-
import { parseGroupDrawing } from "./groupDrawingParser.js";
|
|
4
|
+
import { isGroupDrawing, parseGroupDrawing } from "./groupDrawingParser.js";
|
|
5
5
|
import { parseImage } from "./imageParser.js";
|
|
6
6
|
import { imageRawXmlFingerprint } from "./imageRawXml.js";
|
|
7
7
|
import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema, ShadingPatternSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
@@ -11,7 +11,7 @@ import { requiresXmlSpacePreserve } from "./textWhitespace.js";
|
|
|
11
11
|
import { resolveThemeFontRef } from "./themeParser.js";
|
|
12
12
|
import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
|
|
13
13
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
14
|
-
import { parseVmlImageContent } from "./vmlImageParser.js";
|
|
14
|
+
import { parseVmlImageContent, shouldPreserveRawVmlPict } from "./vmlImageParser.js";
|
|
15
15
|
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
|
|
16
16
|
import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
|
|
17
17
|
//#region src/docx/runParser.ts
|
|
@@ -470,6 +470,28 @@ function parseInstrText(element) {
|
|
|
470
470
|
};
|
|
471
471
|
}
|
|
472
472
|
/**
|
|
473
|
+
* Wrap raw XML the model cannot project at all.
|
|
474
|
+
*
|
|
475
|
+
* `DrawingContent` always carries an `Image`, so preservation-only content
|
|
476
|
+
* gets a placeholder one: the empty `rId` marks it as backed by no
|
|
477
|
+
* relationship, which keeps `classifyDrawingSafety` and the serializer on the
|
|
478
|
+
* replay path instead of regenerating DrawingML from the placeholder.
|
|
479
|
+
*/
|
|
480
|
+
const preserveOnlyDrawing = (rawXml) => ({
|
|
481
|
+
type: "drawing",
|
|
482
|
+
image: {
|
|
483
|
+
type: "image",
|
|
484
|
+
rId: "",
|
|
485
|
+
size: {
|
|
486
|
+
width: 0,
|
|
487
|
+
height: 0
|
|
488
|
+
},
|
|
489
|
+
wrap: { type: "inline" }
|
|
490
|
+
},
|
|
491
|
+
rawXml,
|
|
492
|
+
rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
|
|
493
|
+
});
|
|
494
|
+
/**
|
|
473
495
|
* Parse drawing content (w:drawing).
|
|
474
496
|
*
|
|
475
497
|
* Dispatches by graphicData payload:
|
|
@@ -480,6 +502,8 @@ function parseInstrText(element) {
|
|
|
480
502
|
* context that is only available at the block parser level).
|
|
481
503
|
* - `wps:wsp` without text body → generic shape; parsed via
|
|
482
504
|
* `shapeParser.parseShapeFromDrawing` into a `ShapeContent`.
|
|
505
|
+
* - anything the model cannot project (a group the rasterizer declines,
|
|
506
|
+
* a diagram, a shape with unmodeled properties) → preservation-only raw XML.
|
|
483
507
|
*/
|
|
484
508
|
function parseDrawingContent(element, rels, media) {
|
|
485
509
|
const groupImage = parseGroupDrawing(element, rels ?? void 0, media ?? void 0);
|
|
@@ -487,8 +511,10 @@ function parseDrawingContent(element, rels, media) {
|
|
|
487
511
|
type: "drawing",
|
|
488
512
|
image: groupImage,
|
|
489
513
|
rawXml: captureVerbatimXml(element),
|
|
490
|
-
rawImageFingerprint: imageRawXmlFingerprint(groupImage)
|
|
514
|
+
rawImageFingerprint: imageRawXmlFingerprint(groupImage),
|
|
515
|
+
rawXmlMode: DRAWING_RAW_XML_MODES.PREVIEW_ONLY
|
|
491
516
|
};
|
|
517
|
+
if (isGroupDrawing(element)) return preserveOnlyDrawing(captureVerbatimXml(element));
|
|
492
518
|
const diagramImage = parseDiagramPreview(element, rels ?? void 0, media ?? void 0);
|
|
493
519
|
if (diagramImage) return {
|
|
494
520
|
type: "drawing",
|
|
@@ -496,20 +522,7 @@ function parseDrawingContent(element, rels, media) {
|
|
|
496
522
|
rawXml: captureVerbatimXml(element),
|
|
497
523
|
rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
|
|
498
524
|
};
|
|
499
|
-
if (shouldPreserveRawShapeDrawing(element)) return
|
|
500
|
-
type: "drawing",
|
|
501
|
-
image: {
|
|
502
|
-
type: "image",
|
|
503
|
-
rId: "",
|
|
504
|
-
size: {
|
|
505
|
-
width: 0,
|
|
506
|
-
height: 0
|
|
507
|
-
},
|
|
508
|
-
wrap: { type: "inline" }
|
|
509
|
-
},
|
|
510
|
-
rawXml: captureVerbatimXml(element),
|
|
511
|
-
rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
|
|
512
|
-
};
|
|
525
|
+
if (shouldPreserveRawShapeDrawing(element)) return preserveOnlyDrawing(captureVerbatimXml(element));
|
|
513
526
|
const shape = parseShapeFromDrawing(element);
|
|
514
527
|
if (shape) return {
|
|
515
528
|
type: "shape",
|
|
@@ -572,7 +585,11 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
572
585
|
}
|
|
573
586
|
case "pict": {
|
|
574
587
|
const vmlDrawing = parseVmlImageContent(child, rels, media, rootXmlns);
|
|
575
|
-
if (vmlDrawing)
|
|
588
|
+
if (vmlDrawing) {
|
|
589
|
+
contents.push(vmlDrawing);
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
if (shouldPreserveRawVmlPict(child)) contents.push(preserveOnlyDrawing(captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns))));
|
|
576
593
|
break;
|
|
577
594
|
}
|
|
578
595
|
case "object": {
|
|
@@ -594,13 +611,16 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
594
611
|
const alternateChildren = getChildElements(child);
|
|
595
612
|
const choiceEl = alternateChildren.find((el) => getLocalName(el.name) === "Choice");
|
|
596
613
|
const fallbackEl = alternateChildren.find((el) => getLocalName(el.name) === "Fallback");
|
|
614
|
+
const contentsBeforeAlternate = contents.length;
|
|
597
615
|
const choiceTextBoxDrawing = choiceEl ? getChildElements(choiceEl).find((element) => getLocalName(element.name) === "drawing" && isTextBoxDrawing(element)) : void 0;
|
|
598
616
|
const groupedChoiceDrawing = choiceEl ? getChildElements(choiceEl).find((element) => getLocalName(element.name) === "drawing" && findAllDeep(element, "wpg", "wgp").length > 0) : void 0;
|
|
599
617
|
if (groupedChoiceDrawing) {
|
|
600
618
|
const groupedDrawing = parseDrawingContent(groupedChoiceDrawing, rels, media);
|
|
601
|
-
if (groupedDrawing?.type === "drawing" && groupedDrawing.image.src) {
|
|
602
|
-
|
|
603
|
-
|
|
619
|
+
if (groupedDrawing?.type === "drawing" && groupedDrawing.rawXmlMode === DRAWING_RAW_XML_MODES.PREVIEW_ONLY && groupedDrawing.image.src) {
|
|
620
|
+
contents.push({
|
|
621
|
+
...groupedDrawing,
|
|
622
|
+
rawXml: captureVerbatimXml(child)
|
|
623
|
+
});
|
|
604
624
|
break;
|
|
605
625
|
}
|
|
606
626
|
}
|
|
@@ -631,6 +651,8 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
631
651
|
elements: [innerChild]
|
|
632
652
|
}, rels, media, rootXmlns));
|
|
633
653
|
}
|
|
654
|
+
const hasTextBoxBranch = [choiceEl, fallbackEl].some((branch) => getChildElements(branch).some((element) => getLocalName(element.name) === "drawing" && isTextBoxDrawing(element)));
|
|
655
|
+
if (contents.length === contentsBeforeAlternate && !hasTextBoxBranch) contents.push(preserveOnlyDrawing(captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns))));
|
|
634
656
|
break;
|
|
635
657
|
}
|
|
636
658
|
case "footnoteRef":
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isValidHexColor } from "../../utils/colorResolver.js";
|
|
2
2
|
import { THEME_COLOR_TO_DRAWING_SCHEME } from "../drawingUtils.js";
|
|
3
|
+
import { serializeGraphicFrameLocks } from "../graphicFrameLocks.js";
|
|
3
4
|
import { canReplayEditableImageRawXml } from "../imageRawXml.js";
|
|
4
5
|
import { requiresXmlSpacePreserve } from "../textWhitespace.js";
|
|
5
6
|
import { serializeParagraph } from "./paragraphSerializer.js";
|
|
@@ -285,6 +286,7 @@ function serializeDrawingContent(content) {
|
|
|
285
286
|
const inlineDocPr = hlinkClick ? `<wp:docPr ${inlineDocPrAttrs}>${hlinkClick}</wp:docPr>` : `<wp:docPr ${inlineDocPrAttrs}/>`;
|
|
286
287
|
const anchorDocPrAttrs = `id="${docPrId}" name="${escapeXml(docPrName)}"${docPrDescription}${docPrTitle}`;
|
|
287
288
|
const anchorDocPr = hlinkClick ? `<wp:docPr ${anchorDocPrAttrs}>${hlinkClick}</wp:docPr>` : `<wp:docPr ${anchorDocPrAttrs}/>`;
|
|
289
|
+
const graphicFramePr = serializeGraphicFrameLocks(image.frameLocks);
|
|
288
290
|
const graphic = serializePicGraphic(image, docPrId);
|
|
289
291
|
if (!isFloating) return [
|
|
290
292
|
"<w:drawing>",
|
|
@@ -292,7 +294,7 @@ function serializeDrawingContent(content) {
|
|
|
292
294
|
`<wp:extent cx="${intAttr(cx)}" cy="${intAttr(cy)}"/>`,
|
|
293
295
|
effectExtentEl,
|
|
294
296
|
inlineDocPr,
|
|
295
|
-
|
|
297
|
+
graphicFramePr,
|
|
296
298
|
graphic,
|
|
297
299
|
"</wp:inline>",
|
|
298
300
|
"</w:drawing>"
|
|
@@ -309,7 +311,7 @@ function serializeDrawingContent(content) {
|
|
|
309
311
|
effectExtentEl,
|
|
310
312
|
wrap,
|
|
311
313
|
anchorDocPr,
|
|
312
|
-
|
|
314
|
+
graphicFramePr,
|
|
313
315
|
graphic,
|
|
314
316
|
"</wp:anchor>",
|
|
315
317
|
"</w:drawing>"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { deterministicHexId } from "../../utils/hexId.js";
|
|
2
2
|
import { getParagraphText } from "../paragraphParser.js";
|
|
3
3
|
import { cloneParagraphWithoutPropertySource } from "../paragraphPropertySource.js";
|
|
4
|
+
import { getRunText } from "../runParser.js";
|
|
4
5
|
import { TaggedError } from "better-result";
|
|
5
6
|
//#region src/docx/server/createBilingualDocument.ts
|
|
6
7
|
/**
|
|
@@ -115,7 +116,7 @@ function createBilingualDocument(source, options) {
|
|
|
115
116
|
}
|
|
116
117
|
if (block.type === "paragraph") {
|
|
117
118
|
if (isEmptyParagraph(block)) continue;
|
|
118
|
-
if (block
|
|
119
|
+
if (!isTranslatableParagraph(block, options.editableParagraphIds)) {
|
|
119
120
|
flushSection();
|
|
120
121
|
content.push(block);
|
|
121
122
|
continue;
|
|
@@ -129,7 +130,7 @@ function createBilingualDocument(source, options) {
|
|
|
129
130
|
sectionRows.push(buildRow(block, copy, styleById, textWidth));
|
|
130
131
|
continue;
|
|
131
132
|
}
|
|
132
|
-
const paragraphs = collectTableParagraphs(block).filter((paragraph) => paragraph
|
|
133
|
+
const paragraphs = collectTableParagraphs(block).filter((paragraph) => isTranslatableParagraph(paragraph, options.editableParagraphIds)).map((paragraph) => ({
|
|
133
134
|
paraId: paragraph.paraId,
|
|
134
135
|
sourceText: getParagraphText(paragraph)
|
|
135
136
|
}));
|
|
@@ -208,6 +209,31 @@ const isEmptyParagraph = (paragraph) => {
|
|
|
208
209
|
if (getParagraphText(paragraph).trim().length > 0) return false;
|
|
209
210
|
return paragraph.content.every((item) => item.type === "run" && item.formatting?.hidden !== true && item.content.every((part) => part.type === "text"));
|
|
210
211
|
};
|
|
212
|
+
/**
|
|
213
|
+
* A paragraph whose only text is a field result (a table of contents, a page
|
|
214
|
+
* reference). Word recomputes that text, so translating it would be discarded;
|
|
215
|
+
* the paragraph is copied through full width instead of becoming a row.
|
|
216
|
+
*/
|
|
217
|
+
const isFieldOnlyParagraph = (paragraph) => {
|
|
218
|
+
let hasField = false;
|
|
219
|
+
for (const item of paragraph.content) {
|
|
220
|
+
if (item.type === "simpleField" || item.type === "complexField") {
|
|
221
|
+
hasField = true;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (item.type === "run" && getRunText(item).trim().length === 0) continue;
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return hasField;
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* A paragraph offered as a translation row.
|
|
231
|
+
*
|
|
232
|
+
* Every site that builds or reads a row reads this one predicate: creation and
|
|
233
|
+
* reading derive the manifest independently, so a rule added to only one of
|
|
234
|
+
* them would surface as a missing handle rather than as the rule it is.
|
|
235
|
+
*/
|
|
236
|
+
const isTranslatableParagraph = (paragraph, editableParagraphIds) => paragraph.paraId !== void 0 && editableParagraphIds.has(paragraph.paraId) && !isFieldOnlyParagraph(paragraph);
|
|
211
237
|
/** Heading style families across Word UI languages (en, cs/sk, de, fr, pl). */
|
|
212
238
|
const HEADING_STYLE_PATTERN = /heading|nadpis|berschrift|titre|nag[łl]/iu;
|
|
213
239
|
const classifyParagraph = (paragraph, styleById) => {
|
|
@@ -459,7 +485,7 @@ const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner
|
|
|
459
485
|
if (item.type === "table") return cloneTable(item);
|
|
460
486
|
const targetParaId = paraIds.mint(item.paraId);
|
|
461
487
|
const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
|
|
462
|
-
if (item
|
|
488
|
+
if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
|
|
463
489
|
sourceParaId: item.paraId,
|
|
464
490
|
targetParaId,
|
|
465
491
|
sourceText: getParagraphText(item)
|
|
@@ -701,7 +727,7 @@ function readBilingualDocument(document, editableParagraphIds) {
|
|
|
701
727
|
}
|
|
702
728
|
const paragraphs = [];
|
|
703
729
|
for (const [index, source] of sourceParagraphs.entries()) {
|
|
704
|
-
if (source
|
|
730
|
+
if (!isTranslatableParagraph(source, editableParagraphIds)) continue;
|
|
705
731
|
const target = targetParagraphs.at(index);
|
|
706
732
|
if (target?.paraId === void 0 || target.paraId === source.paraId || !editableParagraphIds.has(target.paraId)) {
|
|
707
733
|
missingHandleCount += 1;
|
|
@@ -725,7 +751,7 @@ function readBilingualDocument(document, editableParagraphIds) {
|
|
|
725
751
|
missingHandleCount += 1;
|
|
726
752
|
continue;
|
|
727
753
|
}
|
|
728
|
-
const paragraphs = collectTableParagraphs(sourceTable).filter((paragraph) => paragraph
|
|
754
|
+
const paragraphs = collectTableParagraphs(sourceTable).filter((paragraph) => isTranslatableParagraph(paragraph, editableParagraphIds)).map((paragraph) => ({
|
|
729
755
|
paraId: paragraph.paraId,
|
|
730
756
|
sourceText: getParagraphText(paragraph)
|
|
731
757
|
}));
|