@a3s-lab/office 0.9.2 → 0.11.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/COLLABORATION_ROADMAP.md +29 -5
- package/README.md +19 -6
- package/dist/{0~7754.js → 0~5093.js} +1449 -3
- package/dist/0~document-editor.js +8 -1
- package/dist/0~work-docx-export.js +482 -16
- package/dist/0~work-docx-import.js +31 -1142
- package/dist/0~work-office-diagnostics.js +9 -5
- package/dist/4104.js +1 -1
- package/dist/8928.js +763 -149
- package/dist/internal/features/work/work-document-format-change-tracking.d.ts +9 -0
- package/dist/internal/features/work/work-document-format-changes.d.ts +28 -0
- package/dist/internal/features/work/work-document-paragraph-format-change-tracking.d.ts +7 -0
- package/dist/internal/features/work/work-document-paragraph-format-changes.d.ts +8 -0
- package/dist/internal/features/work/work-docx-format-change-export.d.ts +17 -0
- package/dist/internal/features/work/work-docx-import.d.ts +3 -1
- package/dist/internal/features/work/work-docx-paragraph-alignment-import.d.ts +1 -0
- package/dist/internal/features/work/work-docx-paragraph-borders-import.d.ts +1 -0
- package/dist/internal/features/work/work-docx-paragraph-direction-import.d.ts +1 -0
- package/dist/internal/features/work/work-docx-paragraph-format-change-export.d.ts +13 -0
- package/dist/internal/features/work/work-docx-paragraph-format-change-import.d.ts +17 -0
- package/dist/internal/features/work/work-docx-paragraph-indent-import.d.ts +1 -0
- package/dist/internal/features/work/work-docx-paragraph-pagination-import.d.ts +1 -0
- package/dist/internal/features/work/work-docx-paragraph-shading-import.d.ts +1 -0
- package/dist/internal/features/work/work-docx-paragraph-spacing-import.d.ts +1 -0
- package/dist/internal/features/work/work-docx-run-formatting-import.d.ts +10 -0
- package/dist/internal/features/work/work-docx-tab-stop-import.d.ts +1 -0
- package/dist/internal/features/work/work-types.d.ts +1 -1
- package/dist/office-kernel.wasm +0 -0
- package/dist/styles.css +28 -0
- package/docs/latest/en/browser-editor-architecture.md +18 -0
- package/package.json +7 -3
|
@@ -769,7 +769,7 @@ function DocumentChangesPanel({ editor, changes, decisions = [], trackChanges, s
|
|
|
769
769
|
}).run(),
|
|
770
770
|
children: [
|
|
771
771
|
/*#__PURE__*/ jsx("span", {
|
|
772
|
-
children:
|
|
772
|
+
children: documentChangeKindLabel(change.kind)
|
|
773
773
|
}),
|
|
774
774
|
/*#__PURE__*/ jsx("strong", {
|
|
775
775
|
children: change.text.trim() || '(空白字符)'
|
|
@@ -868,6 +868,7 @@ function DocumentChangeDecisionHistory({ decisions }) {
|
|
|
868
868
|
/*#__PURE__*/ jsx("ol", {
|
|
869
869
|
children: visible.map((decision)=>/*#__PURE__*/ jsxs("li", {
|
|
870
870
|
"data-document-change-decision": decision.decision,
|
|
871
|
+
"data-document-change-kind": decision.changeKind,
|
|
871
872
|
children: [
|
|
872
873
|
/*#__PURE__*/ jsx("span", {
|
|
873
874
|
children: 'accept' === decision.decision ? '已接受' : '已拒绝'
|
|
@@ -892,6 +893,12 @@ function DocumentChangeDecisionHistory({ decisions }) {
|
|
|
892
893
|
function documentChangeDecisionKey(changeKey, decision) {
|
|
893
894
|
return `${changeKey}:${decision}`;
|
|
894
895
|
}
|
|
896
|
+
function documentChangeKindLabel(kind) {
|
|
897
|
+
if ('insertion' === kind) return '插入';
|
|
898
|
+
if ('formatting' === kind) return '格式';
|
|
899
|
+
if ('paragraph-formatting' === kind) return '段落格式';
|
|
900
|
+
return '删除';
|
|
901
|
+
}
|
|
895
902
|
function documentChangeWindowKey(change) {
|
|
896
903
|
return `${change.kind}:${change.id}`;
|
|
897
904
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import jszip from "jszip";
|
|
2
|
-
import { normalizeDocumentBookmarkName, applyDocumentImageIdentityToElement, DOCUMENT_PAGE_BORDER_EDGES, createDocumentImageIdentityRegistry, normalizeDocumentColumns, normalizeDocumentTableVerticalAlign, uniqueDocumentParagraphIdentity, documentTableColumnPercentagesFromElement, documentTableCellMarginOverridesFromElement, applyDocumentTableRowIdentityToElement, normalizeDocumentBookmarksHtml, documentImageIdentityFromElement, documentTableGeometryFromElement, normalizeDocumentTableRowHeightRule, documentNoteKey, normalizeDocumentParagraphBorders, normalizeDocumentTableBorderWidth, normalizeDocumentParagraphId, normalizeDocumentTableBorderStyle, normalizeDocumentBookmarkReferencesHtml, documentCitationInstruction, parseDocumentParagraphBordersElement, documentImageWrapContourFromElement, documentImagePositionFromElement, normalizeDocumentPageChrome, documentPageMarginsForLayout, normalizeDocumentImageLayer, documentBookmarkReferenceInstruction, documentImageCropFromElement, normalizeDocumentPageBorders, documentImageLayerFromElement,
|
|
2
|
+
import { normalizeDocumentBookmarkName, applyDocumentImageIdentityToElement, DOCUMENT_PAGE_BORDER_EDGES, createDocumentImageIdentityRegistry, normalizeDocumentColumns, normalizeDocumentTableVerticalAlign, uniqueDocumentParagraphIdentity, documentTableColumnPercentagesFromElement, documentTableCellMarginOverridesFromElement, applyDocumentTableRowIdentityToElement, normalizeDocumentBookmarksHtml, documentImageIdentityFromElement, documentTableGeometryFromElement, normalizeDocumentTableRowHeightRule, documentNoteKey, normalizeDocumentParagraphBorders, normalizeDocumentTableBorderWidth, normalizeDocumentParagraphId, normalizeDocumentTableBorderStyle, normalizeDocumentBookmarkReferencesHtml, documentCitationInstruction, parseDocumentParagraphBordersElement, documentImageWrapContourFromElement, documentImagePositionFromElement, normalizeDocumentPageChrome, documentPageMarginsForLayout, normalizeDocumentImageLayer, documentBookmarkReferenceInstruction, documentImageCropFromElement, normalizeDocumentPageBorders, documentImageLayerFromElement, parseDocxThemeReference, documentCaptionLabel, documentCaptionKind, applyDocumentParagraphIdentityToElement, isContourImageLayout, normalizeDocumentImageIdentity, normalizeTableColor, documentTableBordersFromElement, documentSections, createDocumentParagraphIdentityRegistry, documentCitationTags, docxDocumentFieldKind, wrapsBesideImage, DocxThemePatchCollector, defaultDocumentImageWrapContour, normalizeDocumentParagraphIdentity, normalizeDocumentPaperSource, documentEquationFromElement, DOCUMENT_PARAGRAPH_BORDER_EDGES, parseDocumentCharacterFormatting, patchDocxThemeReferences, parseDocumentParagraphShadingElement, documentParagraphIdentityFromElement, normalizeDocumentEquation, parseDocumentParagraphFormatting, documentTableRowIdentityFromElement, normalizeDocumentBookmarkNativeId, collectDocumentNotes, DOCUMENT_PAGE_MARGIN_KEYS, uniqueDocumentImageIdentity, documentImageLayoutFromElement, documentPageHorizontalMarginTwips, documentPageGeometryForLayout, normalizeDocumentPageMargins, documentNoteKind } from "./8928.js";
|
|
3
3
|
import { serializeUtf8Xml, directChildren, xmlNamespacePrefix, parseXml, decodeXmlBytes, descendants, resolvePartTarget, directChild, attribute as work_ooxml_package_attribute } from "./5184.js";
|
|
4
4
|
import { assignDocxNoteIds, patchDocxPageColor, normalizeDocxCommentId, assignDocxCommentThreads } from "./0~4980.js";
|
|
5
5
|
import { paragraphTabStops, mergeDocxIgnorableExtensionsAtPairs, patchDocxBibliography, XMLNS_NAMESPACE as work_docx_settings_xml_XMLNS_NAMESPACE, paragraphAlignment, paragraphSpacingOptions, xmlNamespaceUri, hasNonWhitespaceXmlText, cloneXmlElement, cssColorToHex, DocxParagraphDefaultCollapsedPatchCollector, assertXmlRoot, xmlDeclaredPrefix, paragraphIndent, domDirection, DOCX_WORDPROCESSING_NAMESPACES, paragraphBidirectional, XML_NAMESPACE as work_docx_settings_xml_XML_NAMESPACE, patchDocxParagraphDefaultCollapsed, xmlAttributeNamespace, dataBoolean, mergeDocxIgnorableExtensions, parseDocxColorSchemeMappingElement, paragraphPaginationOptions, DOCX_COLOR_SCHEME_MAPPING_ATTRIBUTES, xmlAttributeLocalName, cssFontSize, paragraphDirectionOptions, cssFontFamily } from "./0~2805.js";
|
|
@@ -2095,6 +2095,240 @@ function docxDocumentFieldRun(element, docx) {
|
|
|
2095
2095
|
if (!docxDocumentFieldKind(instruction)) return new docx.TextRun(display);
|
|
2096
2096
|
return new docx.SimpleField(instruction, display);
|
|
2097
2097
|
}
|
|
2098
|
+
const MAX_RUN_FORMATTING_CHANGE_PATCHES = 65536;
|
|
2099
|
+
const RUN_FORMATTING_PART_PATTERN = /^word\/(?:document|header\d*|footer\d*|footnotes|endnotes)\.xml$/i;
|
|
2100
|
+
class DocxRunFormattingChangePatchCollector {
|
|
2101
|
+
patches = [];
|
|
2102
|
+
register(element, id) {
|
|
2103
|
+
if ('formatting' !== element.dataset.changeKind || !element.hasAttribute('data-document-change')) return null;
|
|
2104
|
+
const key = element.dataset.changeId?.trim() ?? '';
|
|
2105
|
+
const author = element.dataset.changeAuthor?.trim() ?? '';
|
|
2106
|
+
const date = normalizedRevisionDate(element.dataset.changeDate);
|
|
2107
|
+
const before = element.dataset.changeBefore ?? '';
|
|
2108
|
+
if (!key || !author || author.length > 255 || !parseDocumentCharacterFormatting(before)) throw new Error('Document contains an invalid character-formatting revision.');
|
|
2109
|
+
if (this.patches.length >= MAX_RUN_FORMATTING_CHANGE_PATCHES) throw new Error('Document exceeds the character-formatting revision limit.');
|
|
2110
|
+
const sequence = this.patches.length + 1;
|
|
2111
|
+
const start = `__A3S_WORK_FORMAT_CHANGE_START_${sequence}__`;
|
|
2112
|
+
const end = `__A3S_WORK_FORMAT_CHANGE_END_${sequence}__`;
|
|
2113
|
+
this.patches.push({
|
|
2114
|
+
start,
|
|
2115
|
+
end,
|
|
2116
|
+
id,
|
|
2117
|
+
author,
|
|
2118
|
+
date,
|
|
2119
|
+
before
|
|
2120
|
+
});
|
|
2121
|
+
return {
|
|
2122
|
+
start,
|
|
2123
|
+
end
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
async function patchDocxRunFormattingChanges(buffer, patches) {
|
|
2128
|
+
if (!patches.length) return buffer;
|
|
2129
|
+
if (patches.length > MAX_RUN_FORMATTING_CHANGE_PATCHES) throw new Error('Document exceeds the character-formatting revision limit.');
|
|
2130
|
+
const archive = await jszip.loadAsync(buffer);
|
|
2131
|
+
const byMarker = new Map();
|
|
2132
|
+
for (const patch of patches){
|
|
2133
|
+
byMarker.set(patch.start, patch);
|
|
2134
|
+
byMarker.set(patch.end, patch);
|
|
2135
|
+
}
|
|
2136
|
+
const applied = new Set();
|
|
2137
|
+
for (const entry of Object.values(archive.files)){
|
|
2138
|
+
if (entry.dir || !RUN_FORMATTING_PART_PATTERN.test(entry.name)) continue;
|
|
2139
|
+
const document = parseXml(decodeXmlBytes(await entry.async('uint8array'), `generated DOCX ${entry.name}`), `generated DOCX ${entry.name}`);
|
|
2140
|
+
const runs = descendants(document, 'r').filter((run)=>DOCX_WORDPROCESSING_NAMESPACES.has(run.namespaceURI ?? ''));
|
|
2141
|
+
const markerRuns = new Map();
|
|
2142
|
+
for (const run of runs){
|
|
2143
|
+
const marker = runMarker(run, byMarker);
|
|
2144
|
+
if (!marker) continue;
|
|
2145
|
+
const current = markerRuns.get(marker.patch) ?? {};
|
|
2146
|
+
if ('start' === marker.kind) {
|
|
2147
|
+
if (current.start) throw duplicateMarker(marker.patch.start);
|
|
2148
|
+
current.start = run;
|
|
2149
|
+
} else {
|
|
2150
|
+
if (current.end) throw duplicateMarker(marker.patch.end);
|
|
2151
|
+
current.end = run;
|
|
2152
|
+
}
|
|
2153
|
+
markerRuns.set(marker.patch, current);
|
|
2154
|
+
}
|
|
2155
|
+
let changed = false;
|
|
2156
|
+
for (const [patch, markers] of markerRuns){
|
|
2157
|
+
if (!markers.start || !markers.end) continue;
|
|
2158
|
+
const start = runs.indexOf(markers.start);
|
|
2159
|
+
const end = runs.indexOf(markers.end);
|
|
2160
|
+
if (start < 0 || end <= start + 1) throw new Error('Generated DOCX character-formatting markers are invalid.');
|
|
2161
|
+
const targets = runs.slice(start + 1, end).filter((run)=>directChildren(run, 't').length > 0);
|
|
2162
|
+
if (!targets.length) throw new Error('A character-formatting revision must mark text.');
|
|
2163
|
+
for (const run of targets)setRunFormattingChange(document, run, patch);
|
|
2164
|
+
markers.start.remove();
|
|
2165
|
+
markers.end.remove();
|
|
2166
|
+
applied.add(patch);
|
|
2167
|
+
changed = true;
|
|
2168
|
+
}
|
|
2169
|
+
if (changed) archive.file(entry.name, serializeUtf8Xml(document));
|
|
2170
|
+
}
|
|
2171
|
+
const missing = patches.filter((patch)=>!applied.has(patch));
|
|
2172
|
+
if (missing.length) throw new Error(`DOCX character-formatting revision markers were not emitted: ${missing.map((patch)=>patch.start).join(', ')}.`);
|
|
2173
|
+
return archive.generateAsync({
|
|
2174
|
+
type: 'arraybuffer'
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
function runMarker(run, patches) {
|
|
2178
|
+
const texts = directChildren(run, 't').filter((text)=>DOCX_WORDPROCESSING_NAMESPACES.has(text.namespaceURI ?? ''));
|
|
2179
|
+
if (1 !== texts.length || !work_docx_format_change_export_runHasOnlyMarkerText(run, texts[0])) return null;
|
|
2180
|
+
const value = texts[0].textContent ?? '';
|
|
2181
|
+
const patch = patches.get(value);
|
|
2182
|
+
if (!patch) return null;
|
|
2183
|
+
return {
|
|
2184
|
+
patch,
|
|
2185
|
+
kind: value === patch.start ? 'start' : 'end'
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
function work_docx_format_change_export_runHasOnlyMarkerText(run, text) {
|
|
2189
|
+
return directChildren(run).every((child)=>child === text || 'rPr' === child.localName && DOCX_WORDPROCESSING_NAMESPACES.has(child.namespaceURI ?? ''));
|
|
2190
|
+
}
|
|
2191
|
+
function setRunFormattingChange(document, run, patch) {
|
|
2192
|
+
const wordNamespace = run.namespaceURI ?? '';
|
|
2193
|
+
const prefix = run.prefix || 'w';
|
|
2194
|
+
const existing = directChildren(run, 'rPr').filter((element)=>element.namespaceURI === wordNamespace);
|
|
2195
|
+
if (existing.length > 1) throw new Error('Generated DOCX run contains duplicate properties.');
|
|
2196
|
+
const properties = existing[0] ?? insertRunProperties(document, run, wordNamespace, prefix);
|
|
2197
|
+
if (directChildren(properties, 'rPrChange').some((element)=>element.namespaceURI === wordNamespace)) throw new Error('Generated DOCX run already contains a formatting revision.');
|
|
2198
|
+
const change = wordElement(document, wordNamespace, prefix, 'rPrChange');
|
|
2199
|
+
work_docx_format_change_export_setWordAttribute(change, wordNamespace, prefix, 'id', String(patch.id));
|
|
2200
|
+
work_docx_format_change_export_setWordAttribute(change, wordNamespace, prefix, 'author', patch.author);
|
|
2201
|
+
work_docx_format_change_export_setWordAttribute(change, wordNamespace, prefix, 'date', patch.date);
|
|
2202
|
+
const before = wordElement(document, wordNamespace, prefix, 'rPr');
|
|
2203
|
+
appendFormattingProperties(document, before, wordNamespace, prefix, patch.before);
|
|
2204
|
+
change.append(before);
|
|
2205
|
+
properties.append(change);
|
|
2206
|
+
}
|
|
2207
|
+
function insertRunProperties(document, run, namespace, prefix) {
|
|
2208
|
+
const properties = wordElement(document, namespace, prefix, 'rPr');
|
|
2209
|
+
run.insertBefore(properties, run.firstChild);
|
|
2210
|
+
return properties;
|
|
2211
|
+
}
|
|
2212
|
+
function appendFormattingProperties(document, properties, namespace, prefix, serialized) {
|
|
2213
|
+
const formatting = parseDocumentCharacterFormatting(serialized);
|
|
2214
|
+
if (!formatting) throw new Error('Document contains an invalid character-formatting revision.');
|
|
2215
|
+
const byType = new Map(formatting.map((mark)=>[
|
|
2216
|
+
mark.type,
|
|
2217
|
+
mark
|
|
2218
|
+
]));
|
|
2219
|
+
appendFontProperties(document, properties, namespace, prefix, byType.get('textStyle'));
|
|
2220
|
+
appendBooleanProperty(document, properties, namespace, prefix, byType, 'bold', 'b');
|
|
2221
|
+
appendBooleanProperty(document, properties, namespace, prefix, byType, 'bold', 'bCs');
|
|
2222
|
+
appendBooleanProperty(document, properties, namespace, prefix, byType, 'italic', 'i');
|
|
2223
|
+
appendBooleanProperty(document, properties, namespace, prefix, byType, 'italic', 'iCs');
|
|
2224
|
+
if (byType.has('underline')) appendValuedProperty(document, properties, namespace, prefix, 'u', 'single');
|
|
2225
|
+
appendBooleanProperty(document, properties, namespace, prefix, byType, 'strike', 'strike');
|
|
2226
|
+
appendColorProperty(document, properties, namespace, prefix, byType.get('textStyle'));
|
|
2227
|
+
appendFontSizeProperties(document, properties, namespace, prefix, byType.get('textStyle'));
|
|
2228
|
+
appendHighlightProperty(document, properties, namespace, prefix, byType.get('highlight'));
|
|
2229
|
+
appendSnapToGridProperty(document, properties, namespace, prefix, byType.get('textStyle'));
|
|
2230
|
+
appendVerticalAlignProperty(document, properties, namespace, prefix, byType);
|
|
2231
|
+
}
|
|
2232
|
+
function appendFontProperties(document, properties, namespace, prefix, mark) {
|
|
2233
|
+
const family = firstFontFamily(mark?.attrs?.fontFamily);
|
|
2234
|
+
if (!family) return;
|
|
2235
|
+
const fonts = wordElement(document, namespace, prefix, 'rFonts');
|
|
2236
|
+
for (const name of [
|
|
2237
|
+
'ascii',
|
|
2238
|
+
'hAnsi',
|
|
2239
|
+
'eastAsia',
|
|
2240
|
+
'cs'
|
|
2241
|
+
])work_docx_format_change_export_setWordAttribute(fonts, namespace, prefix, name, family);
|
|
2242
|
+
properties.append(fonts);
|
|
2243
|
+
}
|
|
2244
|
+
function appendBooleanProperty(document, properties, namespace, prefix, marks, mark, property) {
|
|
2245
|
+
if (marks.has(mark)) properties.append(wordElement(document, namespace, prefix, property));
|
|
2246
|
+
}
|
|
2247
|
+
function appendValuedProperty(document, properties, namespace, prefix, property, value) {
|
|
2248
|
+
const element = wordElement(document, namespace, prefix, property);
|
|
2249
|
+
work_docx_format_change_export_setWordAttribute(element, namespace, prefix, 'val', value);
|
|
2250
|
+
properties.append(element);
|
|
2251
|
+
}
|
|
2252
|
+
function appendColorProperty(document, properties, namespace, prefix, mark) {
|
|
2253
|
+
const color = normalizedHex(mark?.attrs?.color);
|
|
2254
|
+
const theme = themeReference(mark?.attrs?.themeColor);
|
|
2255
|
+
if (!color && !theme) return;
|
|
2256
|
+
const element = wordElement(document, namespace, prefix, 'color');
|
|
2257
|
+
work_docx_format_change_export_setWordAttribute(element, namespace, prefix, 'val', color ?? normalizedHex(theme?.resolved) ?? '000000');
|
|
2258
|
+
if (theme) setThemeAttributes(element, namespace, prefix, theme, 'themeColor');
|
|
2259
|
+
properties.append(element);
|
|
2260
|
+
}
|
|
2261
|
+
function appendFontSizeProperties(document, properties, namespace, prefix, mark) {
|
|
2262
|
+
const halfPoints = fontSizeHalfPoints(mark?.attrs?.fontSize);
|
|
2263
|
+
if (null === halfPoints) return;
|
|
2264
|
+
appendValuedProperty(document, properties, namespace, prefix, 'sz', String(halfPoints));
|
|
2265
|
+
appendValuedProperty(document, properties, namespace, prefix, 'szCs', String(halfPoints));
|
|
2266
|
+
}
|
|
2267
|
+
function appendHighlightProperty(document, properties, namespace, prefix, mark) {
|
|
2268
|
+
const fill = normalizedHex(mark?.attrs?.color);
|
|
2269
|
+
const theme = themeReference(mark?.attrs?.themeFill);
|
|
2270
|
+
if (!fill && !theme) return;
|
|
2271
|
+
const shading = wordElement(document, namespace, prefix, 'shd');
|
|
2272
|
+
work_docx_format_change_export_setWordAttribute(shading, namespace, prefix, 'val', 'clear');
|
|
2273
|
+
work_docx_format_change_export_setWordAttribute(shading, namespace, prefix, 'color', 'auto');
|
|
2274
|
+
work_docx_format_change_export_setWordAttribute(shading, namespace, prefix, 'fill', fill ?? normalizedHex(theme?.resolved) ?? 'FFFFFF');
|
|
2275
|
+
if (theme) setThemeAttributes(shading, namespace, prefix, theme, 'themeFill');
|
|
2276
|
+
properties.append(shading);
|
|
2277
|
+
}
|
|
2278
|
+
function appendSnapToGridProperty(document, properties, namespace, prefix, mark) {
|
|
2279
|
+
const value = mark?.attrs?.wordSnapToGrid;
|
|
2280
|
+
if ('boolean' != typeof value) return;
|
|
2281
|
+
appendValuedProperty(document, properties, namespace, prefix, 'snapToGrid', value ? '1' : '0');
|
|
2282
|
+
}
|
|
2283
|
+
function appendVerticalAlignProperty(document, properties, namespace, prefix, marks) {
|
|
2284
|
+
if (marks.has("subscript")) appendValuedProperty(document, properties, namespace, prefix, 'vertAlign', "subscript");
|
|
2285
|
+
else if (marks.has("superscript")) appendValuedProperty(document, properties, namespace, prefix, 'vertAlign', "superscript");
|
|
2286
|
+
}
|
|
2287
|
+
function setThemeAttributes(element, namespace, prefix, theme, name) {
|
|
2288
|
+
work_docx_format_change_export_setWordAttribute(element, namespace, prefix, name, theme.theme);
|
|
2289
|
+
if (theme.tint) work_docx_format_change_export_setWordAttribute(element, namespace, prefix, `${name}Tint`, theme.tint);
|
|
2290
|
+
if (theme.shade) work_docx_format_change_export_setWordAttribute(element, namespace, prefix, `${name}Shade`, theme.shade);
|
|
2291
|
+
}
|
|
2292
|
+
function themeReference(value) {
|
|
2293
|
+
return 'string' == typeof value ? parseDocxThemeReference(value) : null;
|
|
2294
|
+
}
|
|
2295
|
+
function firstFontFamily(value) {
|
|
2296
|
+
if ('string' != typeof value) return null;
|
|
2297
|
+
const source = value.trim();
|
|
2298
|
+
if (!source || source.length > 255) return null;
|
|
2299
|
+
const quote = source[0];
|
|
2300
|
+
if ('"' === quote || "'" === quote) {
|
|
2301
|
+
const end = source.indexOf(quote, 1);
|
|
2302
|
+
if (end > 1) return source.slice(1, end).trim() || null;
|
|
2303
|
+
}
|
|
2304
|
+
return source.split(',')[0]?.trim() || null;
|
|
2305
|
+
}
|
|
2306
|
+
function fontSizeHalfPoints(value) {
|
|
2307
|
+
if ('string' != typeof value) return null;
|
|
2308
|
+
const match = /^(\d+(?:\.\d+)?)pt$/i.exec(value.trim());
|
|
2309
|
+
if (!match) return null;
|
|
2310
|
+
const points = Number(match[1]);
|
|
2311
|
+
if (!Number.isFinite(points) || points <= 0 || points > 512) return null;
|
|
2312
|
+
return Math.round(2 * points);
|
|
2313
|
+
}
|
|
2314
|
+
function normalizedHex(value) {
|
|
2315
|
+
if ('string' != typeof value) return null;
|
|
2316
|
+
const match = /^#?([0-9a-f]{6})$/i.exec(value.trim());
|
|
2317
|
+
return match?.[1]?.toUpperCase() ?? null;
|
|
2318
|
+
}
|
|
2319
|
+
function wordElement(document, namespace, prefix, name) {
|
|
2320
|
+
return document.createElementNS(namespace, `${prefix}:${name}`);
|
|
2321
|
+
}
|
|
2322
|
+
function work_docx_format_change_export_setWordAttribute(element, namespace, prefix, name, value) {
|
|
2323
|
+
element.setAttributeNS(namespace, `${prefix}:${name}`, value);
|
|
2324
|
+
}
|
|
2325
|
+
function normalizedRevisionDate(value) {
|
|
2326
|
+
const time = Date.parse(value ?? '');
|
|
2327
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : new Date().toISOString();
|
|
2328
|
+
}
|
|
2329
|
+
function duplicateMarker(marker) {
|
|
2330
|
+
return new Error(`Generated DOCX marker '${marker}' is duplicated.`);
|
|
2331
|
+
}
|
|
2098
2332
|
class DocxImageCropPatchCollector {
|
|
2099
2333
|
patches = [];
|
|
2100
2334
|
nextMarker = 1;
|
|
@@ -2870,10 +3104,223 @@ async function patchDocxNumberingRestartRules(buffer, rules) {
|
|
|
2870
3104
|
function wordQualifiedName(root, localName) {
|
|
2871
3105
|
return `${xmlNamespacePrefix(root, work_docx_numbering_WORD_NAMESPACE) ?? root.prefix ?? 'w'}:${localName}`;
|
|
2872
3106
|
}
|
|
3107
|
+
const work_docx_paragraph_format_change_export_WORD_NAMESPACE = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
|
|
3108
|
+
const work_docx_paragraph_format_change_export_WORD_2012_NAMESPACE = 'http://schemas.microsoft.com/office/word/2012/wordml';
|
|
3109
|
+
const work_docx_paragraph_format_change_export_XMLNS_NAMESPACE = 'http://www.w3.org/2000/xmlns/';
|
|
3110
|
+
const MAX_PARAGRAPH_FORMATTING_CHANGE_PATCHES = 65536;
|
|
3111
|
+
const PARAGRAPH_PART_PATTERN = /^word\/(?:document|header\d*|footer\d*|footnotes|endnotes)\.xml$/i;
|
|
3112
|
+
class DocxParagraphFormattingChangePatchCollector {
|
|
3113
|
+
patches = [];
|
|
3114
|
+
register(element, id) {
|
|
3115
|
+
if ('paragraph-formatting' !== element.dataset.changeKind || !element.hasAttribute('data-document-change')) return null;
|
|
3116
|
+
const key = element.dataset.changeId?.trim() ?? '';
|
|
3117
|
+
const author = element.dataset.changeAuthor?.trim() ?? '';
|
|
3118
|
+
const date = work_docx_paragraph_format_change_export_normalizedRevisionDate(element.dataset.changeDate);
|
|
3119
|
+
const before = element.dataset.changeBefore ?? '';
|
|
3120
|
+
if (!key || !author || author.length > 255 || !parseDocumentParagraphFormatting(before)) throw new Error('Document contains an invalid paragraph-formatting revision.');
|
|
3121
|
+
if (this.patches.length >= MAX_PARAGRAPH_FORMATTING_CHANGE_PATCHES) throw new Error('Document exceeds the paragraph-formatting revision limit.');
|
|
3122
|
+
const marker = `__A3S_WORK_PARAGRAPH_FORMAT_CHANGE_EXPORT_${this.patches.length + 1}__`;
|
|
3123
|
+
this.patches.push({
|
|
3124
|
+
marker,
|
|
3125
|
+
id,
|
|
3126
|
+
author,
|
|
3127
|
+
date,
|
|
3128
|
+
before
|
|
3129
|
+
});
|
|
3130
|
+
return marker;
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
async function patchDocxParagraphFormattingChanges(buffer, patches) {
|
|
3134
|
+
if (!patches.length) return buffer;
|
|
3135
|
+
if (patches.length > MAX_PARAGRAPH_FORMATTING_CHANGE_PATCHES) throw new Error('Document exceeds the paragraph-formatting revision limit.');
|
|
3136
|
+
const archive = await jszip.loadAsync(buffer);
|
|
3137
|
+
const byMarker = new Map(patches.map((patch)=>[
|
|
3138
|
+
patch.marker,
|
|
3139
|
+
patch
|
|
3140
|
+
]));
|
|
3141
|
+
const applied = new Set();
|
|
3142
|
+
for (const entry of Object.values(archive.files)){
|
|
3143
|
+
if (entry.dir || !PARAGRAPH_PART_PATTERN.test(entry.name)) continue;
|
|
3144
|
+
const document = parseXml(decodeXmlBytes(await entry.async('uint8array'), `generated DOCX ${entry.name}`), `generated DOCX ${entry.name}`);
|
|
3145
|
+
let changed = false;
|
|
3146
|
+
for (const paragraph of descendants(document, 'p').filter((element)=>element.namespaceURI === work_docx_paragraph_format_change_export_WORD_NAMESPACE)){
|
|
3147
|
+
const marker = paragraphFormattingMarker(paragraph, byMarker);
|
|
3148
|
+
if (marker) {
|
|
3149
|
+
setParagraphFormattingChange(document, paragraph, marker.patch);
|
|
3150
|
+
marker.run.remove();
|
|
3151
|
+
applied.add(marker.patch.marker);
|
|
3152
|
+
changed = true;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
if (changed) archive.file(entry.name, serializeUtf8Xml(document));
|
|
3156
|
+
}
|
|
3157
|
+
const missing = patches.filter((patch)=>!applied.has(patch.marker));
|
|
3158
|
+
if (missing.length) throw new Error(`DOCX paragraph-formatting revision markers were not emitted: ${missing.map((patch)=>patch.marker).join(', ')}.`);
|
|
3159
|
+
return archive.generateAsync({
|
|
3160
|
+
type: 'arraybuffer'
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
function paragraphFormattingMarker(paragraph, patches) {
|
|
3164
|
+
const matches = directChildren(paragraph, 'r').flatMap((run)=>{
|
|
3165
|
+
if (run.namespaceURI !== work_docx_paragraph_format_change_export_WORD_NAMESPACE) return [];
|
|
3166
|
+
const texts = directChildren(run, 't').filter((text)=>text.namespaceURI === work_docx_paragraph_format_change_export_WORD_NAMESPACE);
|
|
3167
|
+
if (1 !== texts.length || !work_docx_paragraph_format_change_export_runHasOnlyMarkerText(run, texts[0])) return [];
|
|
3168
|
+
const patch = patches.get(texts[0]?.textContent ?? '');
|
|
3169
|
+
return patch ? [
|
|
3170
|
+
{
|
|
3171
|
+
patch,
|
|
3172
|
+
run
|
|
3173
|
+
}
|
|
3174
|
+
] : [];
|
|
3175
|
+
});
|
|
3176
|
+
if (matches.length > 1) throw new Error('Generated DOCX paragraph has duplicate revision markers.');
|
|
3177
|
+
return matches[0] ?? null;
|
|
3178
|
+
}
|
|
3179
|
+
function work_docx_paragraph_format_change_export_runHasOnlyMarkerText(run, markerText) {
|
|
3180
|
+
return Boolean(markerText && directChildren(run).every((child)=>child === markerText || 'rPr' === child.localName && child.namespaceURI === work_docx_paragraph_format_change_export_WORD_NAMESPACE));
|
|
3181
|
+
}
|
|
3182
|
+
function setParagraphFormattingChange(document, paragraph, patch) {
|
|
3183
|
+
const propertyNodes = directChildren(paragraph, 'pPr').filter((element)=>element.namespaceURI === work_docx_paragraph_format_change_export_WORD_NAMESPACE);
|
|
3184
|
+
if (propertyNodes.length > 1) throw new Error('Generated DOCX paragraph contains duplicate properties.');
|
|
3185
|
+
const prefix = xmlNamespacePrefix(document.documentElement, work_docx_paragraph_format_change_export_WORD_NAMESPACE) ?? 'w';
|
|
3186
|
+
const properties = propertyNodes[0] ?? insertParagraphProperties(document, paragraph, prefix);
|
|
3187
|
+
if (directChildren(properties, 'pPrChange').some((element)=>element.namespaceURI === work_docx_paragraph_format_change_export_WORD_NAMESPACE)) throw new Error('Generated DOCX paragraph already contains a formatting revision.');
|
|
3188
|
+
const change = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'pPrChange');
|
|
3189
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, change, 'id', String(patch.id));
|
|
3190
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, change, 'author', patch.author);
|
|
3191
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, change, 'date', patch.date);
|
|
3192
|
+
const before = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'pPr');
|
|
3193
|
+
appendParagraphFormattingProperties(document, before, patch.before, prefix);
|
|
3194
|
+
change.append(before);
|
|
3195
|
+
properties.append(change);
|
|
3196
|
+
}
|
|
3197
|
+
function appendParagraphFormattingProperties(document, properties, serialized, prefix) {
|
|
3198
|
+
const parsed = parseDocumentParagraphFormatting(serialized);
|
|
3199
|
+
if (!parsed) throw new Error('Document contains an invalid paragraph-formatting revision.');
|
|
3200
|
+
const formatting = parsed;
|
|
3201
|
+
appendOnOff(document, properties, prefix, 'keepNext', formatting.keepWithNext);
|
|
3202
|
+
appendOnOff(document, properties, prefix, 'keepLines', formatting.keepLines);
|
|
3203
|
+
appendOnOff(document, properties, prefix, 'pageBreakBefore', formatting.pageBreakBefore);
|
|
3204
|
+
appendOnOff(document, properties, prefix, 'widowControl', formatting.widowControl);
|
|
3205
|
+
appendBorders(document, properties, prefix, formatting.paragraphBorders);
|
|
3206
|
+
appendShading(document, properties, prefix, formatting.paragraphShading);
|
|
3207
|
+
appendTabStops(document, properties, prefix, formatting.tabStops);
|
|
3208
|
+
if (formatting.paragraphDirection) appendOnOff(document, properties, prefix, 'bidi', 'rtl' === formatting.paragraphDirection);
|
|
3209
|
+
appendSpacing(document, properties, prefix, formatting);
|
|
3210
|
+
appendIndent(document, properties, prefix, formatting);
|
|
3211
|
+
appendOnOff(document, properties, prefix, 'contextualSpacing', formatting.contextualSpacing);
|
|
3212
|
+
if (formatting.textAlign) work_docx_paragraph_format_change_export_appendValuedProperty(document, properties, prefix, 'jc', 'justify' === formatting.textAlign ? 'both' : formatting.textAlign);
|
|
3213
|
+
if (null !== formatting.outlineLevel) work_docx_paragraph_format_change_export_appendValuedProperty(document, properties, prefix, 'outlineLvl', String(formatting.outlineLevel));
|
|
3214
|
+
appendDefaultCollapsed(document, properties, prefix, formatting.defaultCollapsed);
|
|
3215
|
+
}
|
|
3216
|
+
function appendSpacing(document, properties, prefix, formatting) {
|
|
3217
|
+
const spacing = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'spacing');
|
|
3218
|
+
if (null !== formatting.spaceBefore) work_docx_paragraph_format_change_export_setWordAttribute(document, spacing, 'before', String(Math.round(20 * formatting.spaceBefore)));
|
|
3219
|
+
if (null !== formatting.spaceAfter) work_docx_paragraph_format_change_export_setWordAttribute(document, spacing, 'after', String(Math.round(20 * formatting.spaceAfter)));
|
|
3220
|
+
if (formatting.lineHeight && formatting.lineRule) {
|
|
3221
|
+
const line = docxLineHeight(formatting.lineHeight, formatting.lineRule);
|
|
3222
|
+
if (null !== line) {
|
|
3223
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, spacing, 'line', String(line));
|
|
3224
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, spacing, 'lineRule', formatting.lineRule);
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
if (spacing.attributes.length) properties.append(spacing);
|
|
3228
|
+
}
|
|
3229
|
+
function docxLineHeight(value, rule) {
|
|
3230
|
+
if ('auto' === rule) {
|
|
3231
|
+
const multiple = Number(value.replace(/%$/, ''));
|
|
3232
|
+
if (!Number.isFinite(multiple)) return null;
|
|
3233
|
+
return Math.round((value.endsWith('%') ? multiple / 100 : multiple) * 240);
|
|
3234
|
+
}
|
|
3235
|
+
const length = /^(\d+(?:\.\d+)?)(px|pt)?$/i.exec(value);
|
|
3236
|
+
if (!length?.[1]) return null;
|
|
3237
|
+
const points = length[2]?.toLowerCase() === 'px' ? 0.75 * Number(length[1]) : Number(length[1]);
|
|
3238
|
+
return Math.round(20 * points);
|
|
3239
|
+
}
|
|
3240
|
+
function appendIndent(document, properties, prefix, formatting) {
|
|
3241
|
+
const indent = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'ind');
|
|
3242
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, indent, 'left', String(Math.round(24 * formatting.indentLevel * 15)));
|
|
3243
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, indent, 'right', String(Math.round(15 * formatting.rightIndent)));
|
|
3244
|
+
formatting.firstLineIndent < 0 ? work_docx_paragraph_format_change_export_setWordAttribute(document, indent, 'hanging', String(Math.round(15 * Math.abs(formatting.firstLineIndent)))) : work_docx_paragraph_format_change_export_setWordAttribute(document, indent, 'firstLine', String(Math.round(15 * formatting.firstLineIndent)));
|
|
3245
|
+
properties.append(indent);
|
|
3246
|
+
}
|
|
3247
|
+
function appendTabStops(document, properties, prefix, tabStops) {
|
|
3248
|
+
if (!tabStops?.length) return;
|
|
3249
|
+
const tabs = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'tabs');
|
|
3250
|
+
for (const stop of tabStops){
|
|
3251
|
+
const tab = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'tab');
|
|
3252
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, tab, 'val', stop.alignment);
|
|
3253
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, tab, 'pos', String(Math.round(15 * stop.position)));
|
|
3254
|
+
if ('none' !== stop.leader) work_docx_paragraph_format_change_export_setWordAttribute(document, tab, 'leader', stop.leader);
|
|
3255
|
+
tabs.append(tab);
|
|
3256
|
+
}
|
|
3257
|
+
properties.append(tabs);
|
|
3258
|
+
}
|
|
3259
|
+
function appendBorders(document, properties, prefix, borders) {
|
|
3260
|
+
if (!borders) return;
|
|
3261
|
+
const container = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'pBdr');
|
|
3262
|
+
for (const edge of DOCUMENT_PARAGRAPH_BORDER_EDGES){
|
|
3263
|
+
const border = borders[edge];
|
|
3264
|
+
if (!border) continue;
|
|
3265
|
+
const element = work_docx_paragraph_format_change_export_wordElement(document, prefix, edge);
|
|
3266
|
+
setDocxBorderAttributes(document, element, border);
|
|
3267
|
+
container.append(element);
|
|
3268
|
+
}
|
|
3269
|
+
if (container.children.length) properties.append(container);
|
|
3270
|
+
}
|
|
3271
|
+
function appendShading(document, properties, prefix, shading) {
|
|
3272
|
+
if (!shading) return;
|
|
3273
|
+
const element = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'shd');
|
|
3274
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, element, 'val', shading.pattern);
|
|
3275
|
+
appendShadingColor(document, element, 'color', shading.color);
|
|
3276
|
+
appendShadingColor(document, element, 'fill', shading.fill);
|
|
3277
|
+
properties.append(element);
|
|
3278
|
+
}
|
|
3279
|
+
function appendShadingColor(document, element, kind, color) {
|
|
3280
|
+
if (!color) return;
|
|
3281
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, element, kind, 'auto' === color.value ? 'auto' : color.value.slice(1).toUpperCase());
|
|
3282
|
+
if (!color.theme) return;
|
|
3283
|
+
const themeName = 'color' === kind ? 'themeColor' : 'themeFill';
|
|
3284
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, element, themeName, color.theme.theme);
|
|
3285
|
+
if (color.theme.tint) work_docx_paragraph_format_change_export_setWordAttribute(document, element, `${themeName}Tint`, color.theme.tint);
|
|
3286
|
+
if (color.theme.shade) work_docx_paragraph_format_change_export_setWordAttribute(document, element, `${themeName}Shade`, color.theme.shade);
|
|
3287
|
+
}
|
|
3288
|
+
function appendDefaultCollapsed(document, properties, wordPrefix, value) {
|
|
3289
|
+
if (null === value) return;
|
|
3290
|
+
const element = document.createElementNS(work_docx_paragraph_format_change_export_WORD_2012_NAMESPACE, 'w15:collapsed');
|
|
3291
|
+
element.setAttributeNS(work_docx_paragraph_format_change_export_XMLNS_NAMESPACE, 'xmlns:w15', work_docx_paragraph_format_change_export_WORD_2012_NAMESPACE);
|
|
3292
|
+
element.setAttributeNS(work_docx_paragraph_format_change_export_WORD_NAMESPACE, `${wordPrefix}:val`, value ? '1' : '0');
|
|
3293
|
+
properties.append(element);
|
|
3294
|
+
}
|
|
3295
|
+
function appendOnOff(document, properties, prefix, name, value) {
|
|
3296
|
+
if (null === value) return;
|
|
3297
|
+
work_docx_paragraph_format_change_export_appendValuedProperty(document, properties, prefix, name, value ? '1' : '0');
|
|
3298
|
+
}
|
|
3299
|
+
function work_docx_paragraph_format_change_export_appendValuedProperty(document, properties, prefix, name, value) {
|
|
3300
|
+
const element = work_docx_paragraph_format_change_export_wordElement(document, prefix, name);
|
|
3301
|
+
work_docx_paragraph_format_change_export_setWordAttribute(document, element, 'val', value);
|
|
3302
|
+
properties.append(element);
|
|
3303
|
+
}
|
|
3304
|
+
function insertParagraphProperties(document, paragraph, prefix) {
|
|
3305
|
+
const properties = work_docx_paragraph_format_change_export_wordElement(document, prefix, 'pPr');
|
|
3306
|
+
paragraph.insertBefore(properties, paragraph.firstChild);
|
|
3307
|
+
return properties;
|
|
3308
|
+
}
|
|
3309
|
+
function work_docx_paragraph_format_change_export_wordElement(document, prefix, name) {
|
|
3310
|
+
return document.createElementNS(work_docx_paragraph_format_change_export_WORD_NAMESPACE, `${prefix}:${name}`);
|
|
3311
|
+
}
|
|
3312
|
+
function work_docx_paragraph_format_change_export_setWordAttribute(document, element, name, value) {
|
|
3313
|
+
const prefix = xmlNamespacePrefix(document.documentElement, work_docx_paragraph_format_change_export_WORD_NAMESPACE) ?? 'w';
|
|
3314
|
+
element.setAttributeNS(work_docx_paragraph_format_change_export_WORD_NAMESPACE, `${prefix}:${name}`, value);
|
|
3315
|
+
}
|
|
3316
|
+
function work_docx_paragraph_format_change_export_normalizedRevisionDate(value) {
|
|
3317
|
+
const time = Date.parse(value?.trim() ?? '');
|
|
3318
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : new Date().toISOString();
|
|
3319
|
+
}
|
|
2873
3320
|
const work_docx_paragraph_identity_WORD_2010_NAMESPACE = 'http://schemas.microsoft.com/office/word/2010/wordml';
|
|
2874
3321
|
const work_docx_paragraph_identity_MARKUP_COMPATIBILITY_NAMESPACE = 'http://schemas.openxmlformats.org/markup-compatibility/2006';
|
|
2875
3322
|
const MAX_PARAGRAPH_IDENTITY_PATCHES = 65536;
|
|
2876
|
-
const
|
|
3323
|
+
const work_docx_paragraph_identity_PARAGRAPH_PART_PATTERN = /^word\/(?:document|header\d*|footer\d*|footnotes|endnotes)\.xml$/i;
|
|
2877
3324
|
class DocxParagraphIdentityPatchCollector {
|
|
2878
3325
|
source;
|
|
2879
3326
|
patches = [];
|
|
@@ -2923,7 +3370,7 @@ async function patchDocxParagraphIdentities(buffer, patches) {
|
|
|
2923
3370
|
patch
|
|
2924
3371
|
]));
|
|
2925
3372
|
const applied = new Set();
|
|
2926
|
-
const entries = Object.values(archive.files).filter((entry)=>!entry.dir &&
|
|
3373
|
+
const entries = Object.values(archive.files).filter((entry)=>!entry.dir && work_docx_paragraph_identity_PARAGRAPH_PART_PATTERN.test(entry.name));
|
|
2927
3374
|
for (const entry of entries){
|
|
2928
3375
|
const document = parseXml(decodeXmlBytes(await entry.async('uint8array'), `generated DOCX ${entry.name}`), `generated DOCX ${entry.name}`);
|
|
2929
3376
|
if (!isParagraphPartRoot(document.documentElement, entry.name)) continue;
|
|
@@ -6868,7 +7315,9 @@ async function createDocxBlob(content, sourcePackage) {
|
|
|
6868
7315
|
paragraphBorderPatches: new DocxParagraphBorderPatchCollector(JSON.stringify(normalizedContent)),
|
|
6869
7316
|
paragraphDefaultCollapsedPatches: new DocxParagraphDefaultCollapsedPatchCollector(),
|
|
6870
7317
|
paragraphIdentityPatches: new DocxParagraphIdentityPatchCollector(JSON.stringify(normalizedContent)),
|
|
6871
|
-
equationPatches: new DocxEquationPatchCollector(JSON.stringify(normalizedContent))
|
|
7318
|
+
equationPatches: new DocxEquationPatchCollector(JSON.stringify(normalizedContent)),
|
|
7319
|
+
formattingChangePatches: new DocxRunFormattingChangePatchCollector(),
|
|
7320
|
+
paragraphFormattingChangePatches: new DocxParagraphFormattingChangePatchCollector()
|
|
6872
7321
|
};
|
|
6873
7322
|
const commentRecords = createDocxCommentRecords(commentThreads, docx, noteContext.commentIds);
|
|
6874
7323
|
const sections = [];
|
|
@@ -6915,7 +7364,8 @@ async function createDocxBlob(content, sourcePackage) {
|
|
|
6915
7364
|
}
|
|
6916
7365
|
});
|
|
6917
7366
|
const packed = await docx.Packer.toBlob(document);
|
|
6918
|
-
const
|
|
7367
|
+
const formattingChangesPatched = await patchDocxRunFormattingChanges(await packed.arrayBuffer(), noteContext.formattingChangePatches.patches);
|
|
7368
|
+
const noteImageRelationshipsPatched = await patchDocxNoteImageRelationships(formattingChangesPatched);
|
|
6919
7369
|
const commentMetadataPatched = await patchDocxCommentMetadata(noteImageRelationshipsPatched, commentThreads);
|
|
6920
7370
|
const numberingPatched = await patchDocxNumberingRestartRules(commentMetadataPatched, noteContext.numberingRestartRules);
|
|
6921
7371
|
const bibliographyPatched = await patchDocxBibliography(numberingPatched, normalizedContent.bibliography);
|
|
@@ -6929,7 +7379,8 @@ async function createDocxBlob(content, sourcePackage) {
|
|
|
6929
7379
|
const bookmarkPatched = await patchDocxBookmarks(imageIdentityPatched, noteContext.bookmarkPatches.patches);
|
|
6930
7380
|
const paragraphDefaultCollapsedPatched = await patchDocxParagraphDefaultCollapsed(bookmarkPatched, noteContext.paragraphDefaultCollapsedPatches.patches);
|
|
6931
7381
|
const paragraphIdentityPatched = await patchDocxParagraphIdentities(paragraphDefaultCollapsedPatched, noteContext.paragraphIdentityPatches.patches);
|
|
6932
|
-
const
|
|
7382
|
+
const paragraphFormattingChangesPatched = await patchDocxParagraphFormattingChanges(paragraphIdentityPatched, noteContext.paragraphFormattingChangePatches.patches);
|
|
7383
|
+
const equationPatched = await patchDocxEquations(paragraphFormattingChangesPatched, noteContext.equationPatches.patches);
|
|
6933
7384
|
const patched = await patchDocxPageColor(equationPatched, normalizedContent.pageColor);
|
|
6934
7385
|
const preserved = sourcePackage ? await preserveDocxSourcePackage(patched, sourcePackage, {
|
|
6935
7386
|
numberingIdentities: noteContext.numberingSourceIdentities
|
|
@@ -7095,8 +7546,12 @@ async function pageChromeBlocks(html, docx, noteContext) {
|
|
|
7095
7546
|
async function paragraphRuns(element, docx, noteContext) {
|
|
7096
7547
|
const identityMarker = noteContext.paragraphIdentityPatches.marker(element);
|
|
7097
7548
|
noteContext.paragraphDefaultCollapsedPatches.register(identityMarker, element);
|
|
7549
|
+
const paragraphFormattingChangeMarker = element.hasAttribute('data-document-change') && 'paragraph-formatting' === element.dataset.changeKind ? noteContext.paragraphFormattingChangePatches.register(element, docxRevisionId(element, noteContext)) : null;
|
|
7098
7550
|
return [
|
|
7099
7551
|
new docx.TextRun(identityMarker),
|
|
7552
|
+
...paragraphFormattingChangeMarker ? [
|
|
7553
|
+
new docx.TextRun(paragraphFormattingChangeMarker)
|
|
7554
|
+
] : [],
|
|
7100
7555
|
...await work_docx_export_inlineRuns(element, docx, noteContext)
|
|
7101
7556
|
];
|
|
7102
7557
|
}
|
|
@@ -7157,7 +7612,9 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
|
|
|
7157
7612
|
'footnote' === kind ? new docx.FootnoteReferenceRun(noteId) : new docx.EndnoteReferenceRun(noteId)
|
|
7158
7613
|
];
|
|
7159
7614
|
}
|
|
7160
|
-
const
|
|
7615
|
+
const textRevisionKind = 'del' === tag ? 'deletion' : 'ins' === tag ? 'insertion' : null;
|
|
7616
|
+
const change = node.hasAttribute('data-document-change') && textRevisionKind ? docxTextRevision(node, textRevisionKind, noteContext) : revision;
|
|
7617
|
+
const formattingChange = node.hasAttribute('data-document-change') && 'formatting' === node.dataset.changeKind ? noteContext.formattingChangePatches.register(node, docxRevisionId(node, noteContext)) : null;
|
|
7161
7618
|
const commentBoundary = node.hasAttribute('data-document-comment') ? nextDocxCommentBoundary(node.dataset.commentId, noteContext) : null;
|
|
7162
7619
|
const backgroundColor = cssColorToHex(node.style.backgroundColor);
|
|
7163
7620
|
const direction = domDirection(node);
|
|
@@ -7202,7 +7659,7 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
|
|
|
7202
7659
|
const children = [];
|
|
7203
7660
|
for (const child of node.childNodes)children.push(...await visit(child, style, change));
|
|
7204
7661
|
const href = 'a' === tag ? normalizeDocumentHref(node.getAttribute('href') ?? '') : null;
|
|
7205
|
-
const
|
|
7662
|
+
const linked = href ? [
|
|
7206
7663
|
href.startsWith('#') ? new docx.InternalHyperlink({
|
|
7207
7664
|
anchor: href.slice(1),
|
|
7208
7665
|
children
|
|
@@ -7211,6 +7668,11 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
|
|
|
7211
7668
|
children
|
|
7212
7669
|
})
|
|
7213
7670
|
] : children;
|
|
7671
|
+
const result = formattingChange ? [
|
|
7672
|
+
new docx.TextRun(formattingChange.start),
|
|
7673
|
+
...linked,
|
|
7674
|
+
new docx.TextRun(formattingChange.end)
|
|
7675
|
+
] : linked;
|
|
7214
7676
|
if (!commentBoundary) return result;
|
|
7215
7677
|
return [
|
|
7216
7678
|
...commentBoundary.start ? [
|
|
@@ -7231,13 +7693,7 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
|
|
|
7231
7693
|
return runs;
|
|
7232
7694
|
}
|
|
7233
7695
|
function docxTextRevision(element, kind, context) {
|
|
7234
|
-
const
|
|
7235
|
-
let id = context.changeIds.get(key);
|
|
7236
|
-
if (!id) {
|
|
7237
|
-
id = context.nextChangeId;
|
|
7238
|
-
context.nextChangeId += 1;
|
|
7239
|
-
context.changeIds.set(key, id);
|
|
7240
|
-
}
|
|
7696
|
+
const id = docxRevisionId(element, context);
|
|
7241
7697
|
const sourceDate = element.dataset.changeDate?.trim() ?? '';
|
|
7242
7698
|
const time = Date.parse(sourceDate);
|
|
7243
7699
|
return {
|
|
@@ -7247,9 +7703,19 @@ function docxTextRevision(element, kind, context) {
|
|
|
7247
7703
|
date: Number.isFinite(time) ? new Date(time).toISOString() : new Date().toISOString()
|
|
7248
7704
|
};
|
|
7249
7705
|
}
|
|
7706
|
+
function docxRevisionId(element, context) {
|
|
7707
|
+
const key = element.dataset.changeId?.trim() || `change-${context.nextChangeId}`;
|
|
7708
|
+
let id = context.changeIds.get(key);
|
|
7709
|
+
if (!id) {
|
|
7710
|
+
id = context.nextChangeId;
|
|
7711
|
+
context.nextChangeId += 1;
|
|
7712
|
+
context.changeIds.set(key, id);
|
|
7713
|
+
}
|
|
7714
|
+
return id;
|
|
7715
|
+
}
|
|
7250
7716
|
function documentHasTrackedChanges(html) {
|
|
7251
7717
|
const document = new DOMParser().parseFromString(html, 'text/html');
|
|
7252
|
-
return Boolean(document.body.querySelector('ins[data-document-change], del[data-document-change]'));
|
|
7718
|
+
return Boolean(document.body.querySelector('ins[data-document-change], del[data-document-change], span[data-document-change][data-change-kind="formatting"], [data-document-change][data-change-kind="paragraph-formatting"]'));
|
|
7253
7719
|
}
|
|
7254
7720
|
function anchoredDocumentComments(content) {
|
|
7255
7721
|
const document = new DOMParser().parseFromString(content.html, 'text/html');
|