@a3s-lab/office 0.9.2 → 0.10.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.
@@ -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, documentTableRowIdentityFromElement, collectDocumentNotes, normalizeDocumentBookmarkNativeId, 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;
@@ -6868,7 +7102,8 @@ async function createDocxBlob(content, sourcePackage) {
6868
7102
  paragraphBorderPatches: new DocxParagraphBorderPatchCollector(JSON.stringify(normalizedContent)),
6869
7103
  paragraphDefaultCollapsedPatches: new DocxParagraphDefaultCollapsedPatchCollector(),
6870
7104
  paragraphIdentityPatches: new DocxParagraphIdentityPatchCollector(JSON.stringify(normalizedContent)),
6871
- equationPatches: new DocxEquationPatchCollector(JSON.stringify(normalizedContent))
7105
+ equationPatches: new DocxEquationPatchCollector(JSON.stringify(normalizedContent)),
7106
+ formattingChangePatches: new DocxRunFormattingChangePatchCollector()
6872
7107
  };
6873
7108
  const commentRecords = createDocxCommentRecords(commentThreads, docx, noteContext.commentIds);
6874
7109
  const sections = [];
@@ -6915,7 +7150,8 @@ async function createDocxBlob(content, sourcePackage) {
6915
7150
  }
6916
7151
  });
6917
7152
  const packed = await docx.Packer.toBlob(document);
6918
- const noteImageRelationshipsPatched = await patchDocxNoteImageRelationships(await packed.arrayBuffer());
7153
+ const formattingChangesPatched = await patchDocxRunFormattingChanges(await packed.arrayBuffer(), noteContext.formattingChangePatches.patches);
7154
+ const noteImageRelationshipsPatched = await patchDocxNoteImageRelationships(formattingChangesPatched);
6919
7155
  const commentMetadataPatched = await patchDocxCommentMetadata(noteImageRelationshipsPatched, commentThreads);
6920
7156
  const numberingPatched = await patchDocxNumberingRestartRules(commentMetadataPatched, noteContext.numberingRestartRules);
6921
7157
  const bibliographyPatched = await patchDocxBibliography(numberingPatched, normalizedContent.bibliography);
@@ -7157,7 +7393,9 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
7157
7393
  'footnote' === kind ? new docx.FootnoteReferenceRun(noteId) : new docx.EndnoteReferenceRun(noteId)
7158
7394
  ];
7159
7395
  }
7160
- const change = node.hasAttribute('data-document-change') ? docxTextRevision(node, 'del' === tag ? 'deletion' : 'insertion', noteContext) : revision;
7396
+ const textRevisionKind = 'del' === tag ? 'deletion' : 'ins' === tag ? 'insertion' : null;
7397
+ const change = node.hasAttribute('data-document-change') && textRevisionKind ? docxTextRevision(node, textRevisionKind, noteContext) : revision;
7398
+ const formattingChange = node.hasAttribute('data-document-change') && 'formatting' === node.dataset.changeKind ? noteContext.formattingChangePatches.register(node, docxRevisionId(node, noteContext)) : null;
7161
7399
  const commentBoundary = node.hasAttribute('data-document-comment') ? nextDocxCommentBoundary(node.dataset.commentId, noteContext) : null;
7162
7400
  const backgroundColor = cssColorToHex(node.style.backgroundColor);
7163
7401
  const direction = domDirection(node);
@@ -7202,7 +7440,7 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
7202
7440
  const children = [];
7203
7441
  for (const child of node.childNodes)children.push(...await visit(child, style, change));
7204
7442
  const href = 'a' === tag ? normalizeDocumentHref(node.getAttribute('href') ?? '') : null;
7205
- const result = href ? [
7443
+ const linked = href ? [
7206
7444
  href.startsWith('#') ? new docx.InternalHyperlink({
7207
7445
  anchor: href.slice(1),
7208
7446
  children
@@ -7211,6 +7449,11 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
7211
7449
  children
7212
7450
  })
7213
7451
  ] : children;
7452
+ const result = formattingChange ? [
7453
+ new docx.TextRun(formattingChange.start),
7454
+ ...linked,
7455
+ new docx.TextRun(formattingChange.end)
7456
+ ] : linked;
7214
7457
  if (!commentBoundary) return result;
7215
7458
  return [
7216
7459
  ...commentBoundary.start ? [
@@ -7231,13 +7474,7 @@ async function work_docx_export_inlineRuns(root, docx, noteContext) {
7231
7474
  return runs;
7232
7475
  }
7233
7476
  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
- }
7477
+ const id = docxRevisionId(element, context);
7241
7478
  const sourceDate = element.dataset.changeDate?.trim() ?? '';
7242
7479
  const time = Date.parse(sourceDate);
7243
7480
  return {
@@ -7247,9 +7484,19 @@ function docxTextRevision(element, kind, context) {
7247
7484
  date: Number.isFinite(time) ? new Date(time).toISOString() : new Date().toISOString()
7248
7485
  };
7249
7486
  }
7487
+ function docxRevisionId(element, context) {
7488
+ const key = element.dataset.changeId?.trim() || `change-${context.nextChangeId}`;
7489
+ let id = context.changeIds.get(key);
7490
+ if (!id) {
7491
+ id = context.nextChangeId;
7492
+ context.nextChangeId += 1;
7493
+ context.changeIds.set(key, id);
7494
+ }
7495
+ return id;
7496
+ }
7250
7497
  function documentHasTrackedChanges(html) {
7251
7498
  const document = new DOMParser().parseFromString(html, 'text/html');
7252
- return Boolean(document.body.querySelector('ins[data-document-change], del[data-document-change]'));
7499
+ return Boolean(document.body.querySelector('ins[data-document-change], del[data-document-change], span[data-document-change][data-change-kind="formatting"]'));
7253
7500
  }
7254
7501
  function anchoredDocumentComments(content) {
7255
7502
  const document = new DOMParser().parseFromString(content.html, 'text/html');