@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.
Files changed (31) hide show
  1. package/COLLABORATION_ROADMAP.md +29 -5
  2. package/README.md +19 -6
  3. package/dist/{0~7754.js → 0~5093.js} +1449 -3
  4. package/dist/0~document-editor.js +8 -1
  5. package/dist/0~work-docx-export.js +482 -16
  6. package/dist/0~work-docx-import.js +31 -1142
  7. package/dist/0~work-office-diagnostics.js +9 -5
  8. package/dist/4104.js +1 -1
  9. package/dist/8928.js +763 -149
  10. package/dist/internal/features/work/work-document-format-change-tracking.d.ts +9 -0
  11. package/dist/internal/features/work/work-document-format-changes.d.ts +28 -0
  12. package/dist/internal/features/work/work-document-paragraph-format-change-tracking.d.ts +7 -0
  13. package/dist/internal/features/work/work-document-paragraph-format-changes.d.ts +8 -0
  14. package/dist/internal/features/work/work-docx-format-change-export.d.ts +17 -0
  15. package/dist/internal/features/work/work-docx-import.d.ts +3 -1
  16. package/dist/internal/features/work/work-docx-paragraph-alignment-import.d.ts +1 -0
  17. package/dist/internal/features/work/work-docx-paragraph-borders-import.d.ts +1 -0
  18. package/dist/internal/features/work/work-docx-paragraph-direction-import.d.ts +1 -0
  19. package/dist/internal/features/work/work-docx-paragraph-format-change-export.d.ts +13 -0
  20. package/dist/internal/features/work/work-docx-paragraph-format-change-import.d.ts +17 -0
  21. package/dist/internal/features/work/work-docx-paragraph-indent-import.d.ts +1 -0
  22. package/dist/internal/features/work/work-docx-paragraph-pagination-import.d.ts +1 -0
  23. package/dist/internal/features/work/work-docx-paragraph-shading-import.d.ts +1 -0
  24. package/dist/internal/features/work/work-docx-paragraph-spacing-import.d.ts +1 -0
  25. package/dist/internal/features/work/work-docx-run-formatting-import.d.ts +10 -0
  26. package/dist/internal/features/work/work-docx-tab-stop-import.d.ts +1 -0
  27. package/dist/internal/features/work/work-types.d.ts +1 -1
  28. package/dist/office-kernel.wasm +0 -0
  29. package/dist/styles.css +28 -0
  30. package/docs/latest/en/browser-editor-architecture.md +18 -0
  31. 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: 'insertion' === change.kind ? '插入' : '删除'
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, normalizeTableColor, documentCaptionLabel, documentCaptionKind, applyDocumentParagraphIdentityToElement, isContourImageLayout, normalizeDocumentImageIdentity, documentTableBordersFromElement, parseDocxThemeReference, documentSections, createDocumentParagraphIdentityRegistry, documentCitationTags, docxDocumentFieldKind, wrapsBesideImage, DocxThemePatchCollector, defaultDocumentImageWrapContour, normalizeDocumentParagraphIdentity, normalizeDocumentPaperSource, documentEquationFromElement, DOCUMENT_PARAGRAPH_BORDER_EDGES, documentTableRowIdentityFromElement, patchDocxThemeReferences, parseDocumentParagraphShadingElement, documentParagraphIdentityFromElement, normalizeDocumentEquation, collectDocumentNotes, normalizeDocumentBookmarkNativeId, DOCUMENT_PAGE_MARGIN_KEYS, uniqueDocumentImageIdentity, documentImageLayoutFromElement, documentPageHorizontalMarginTwips, documentPageGeometryForLayout, normalizeDocumentPageMargins, documentNoteKind } from "./8928.js";
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 PARAGRAPH_PART_PATTERN = /^word\/(?:document|header\d*|footer\d*|footnotes|endnotes)\.xml$/i;
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 && PARAGRAPH_PART_PATTERN.test(entry.name));
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 noteImageRelationshipsPatched = await patchDocxNoteImageRelationships(await packed.arrayBuffer());
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 equationPatched = await patchDocxEquations(paragraphIdentityPatched, noteContext.equationPatches.patches);
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 change = node.hasAttribute('data-document-change') ? docxTextRevision(node, 'del' === tag ? 'deletion' : 'insertion', noteContext) : revision;
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 result = href ? [
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 key = element.dataset.changeId?.trim() || `change-${context.nextChangeId}`;
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');