@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.
@@ -110,15 +110,17 @@ Redis/NATS fan-out plus a distributed writer/lock policy.
110
110
 
111
111
  Status: browser collaboration foundation, participant roster, remote
112
112
  selection/caret projection, participant navigation, durable review-only
113
- comment and suggestion modes, immutable suggestion-decision audit, and native
114
- text/options/bounded structural paragraph/comment/suggestion mutations
115
- implemented; rich native mutation parity remains pending.
113
+ comment and suggestion modes, character-formatting revisions, immutable
114
+ decision audit, and native text/options/bounded structural
115
+ paragraph/comment/suggestion mutations implemented; rich native mutation
116
+ parity remains pending.
116
117
 
117
118
  - TipTap is bound to `document.content` through
118
119
  `@tiptap/extension-collaboration`; StarterKit undo/redo is disabled and the
119
120
  collaboration binding owns local history.
120
- - Section/page OOXML state, comment anchors, and tracked-change marks converge
121
- in the ProseMirror fragment. Document-level page color, tracking mode,
121
+ - Section/page OOXML state, comment anchors, and text or character-formatting
122
+ tracked-change marks converge in the ProseMirror fragment. Document-level
123
+ page color, tracking mode,
122
124
  comment threads/replies, and bibliography sources use conflict-local typed
123
125
  roots instead of a serialized document blob.
124
126
  - React, Vue, and Web Component adapters accept the same initialized session.
@@ -166,6 +168,12 @@ implemented; rich native mutation parity remains pending.
166
168
  converge, a second conflicting final decision fails closed, old Documents
167
169
  without the additive roots remain readable, and a final decision clears
168
170
  local history that could otherwise resurrect the decided mark.
171
+ - Character-formatting commands over existing text create one `formatting`
172
+ change mark with a bounded prior-direct-mark snapshot. Accept removes only
173
+ the revision wrapper and keeps the new formatting; reject restores the prior
174
+ bold, italic, underline, strike, sub/superscript, font, size, color,
175
+ highlight, and grid marks without changing text. Supported DOCX
176
+ `w:rPrChange` records import into the same model and export natively.
169
177
  - Rust, CLI, MCP, and A3S Code expose `document-comment-create`,
170
178
  `document-comment-reply`, `document-comment-set-resolved`,
171
179
  `document-comment-delete`, `document-suggestion-create`, and
@@ -183,6 +191,12 @@ implemented; rich native mutation parity remains pending.
183
191
  content, structure, options, non-suggestion formatting, authorship, order,
184
192
  claims, anchors, foreign deletion, or foreign suggestion changes fail before
185
193
  the durable store changes.
194
+ - Native Yrs validation admits only the bounded formatting-change schema,
195
+ preserves it across restart and duplicate/reordered delivery, exposes
196
+ `formatting` in immutable decision projections, and makes `suggest` updates
197
+ retain existing formatting revisions byte-semantically. Formatting is a
198
+ Document change kind, not a native insertion/deletion suggestion kind; the
199
+ current closed `document-suggestion-*` mutations remain text-only.
186
200
 
187
201
  Remaining:
188
202
 
package/README.md CHANGED
@@ -47,8 +47,10 @@ workflows, and a separate Rust automation plane.
47
47
  - **Collaborate across every editor** with shared Yjs/Yrs content, Awareness
48
48
  participants and remote locations, plus browser, CLI, MCP, and A3S Code peers.
49
49
  Document reviewers can submit attributed insertions, deletions, and
50
- replacements without directly changing canonical text; editors accept or
51
- reject each proposal with an immutable shared decision record.
50
+ replacements without directly changing canonical text. Editors can also
51
+ track character-formatting revisions, accept the new formatting or restore
52
+ the exact prior marks, and retain either result in the immutable shared
53
+ decision audit.
52
54
  - **Automate deterministically** through the native CLI, standard MCP server,
53
55
  or packaged Office Skill.
54
56
 
@@ -148,7 +150,8 @@ The images below are committed visual-regression baselines from the real
148
150
  Presentation scene-element and z-order, and PDF annotation, form-value, and
149
151
  review changes, including attributable Document selection comments, replies,
150
152
  text suggestions, and atomic final decisions, retain browser/native actor
151
- attribution, and checkpoint without replacing a whole Office file.
153
+ attribution, validate browser-created formatting revisions and their audit
154
+ records, and checkpoint without replacing a whole Office file.
152
155
 
153
156
  ## Real-time collaboration
154
157
 
@@ -171,9 +174,13 @@ deletions, and replacements while canonical text, structure, formatting,
171
174
  options, comments, and other actors' suggestions remain protected. An `edit`
172
175
  participant accepts or rejects a proposal and appends the final actor-attributed
173
176
  decision to the immutable `document.change-decisions` audit trail. The A3S Boot
174
- service validates both comment and suggestion semantics under the durable room
175
- lock before it persists or broadcasts caller-supplied Yjs bytes. `suggest` on
176
- non-Document formats remains receive-only.
177
+ service also persists `edit`-mode character-formatting revisions and validates
178
+ their bounded prior-format snapshot before broadcast. Accept keeps the new
179
+ bold, italic, underline, strike, vertical position, font, size, color, or
180
+ highlight; reject restores the prior direct marks without deleting the text.
181
+ Authenticated `suggest` updates must preserve those formatting revisions and
182
+ cannot rewrite their identity or snapshot. `suggest` on non-Document formats
183
+ remains receive-only.
177
184
  See the bilingual [real-time collaboration guide](https://a3s-lab.github.io/Office/docs/components/collaboration.html)
178
185
  for React, Vue, Web Component, reconnect, security, and native-agent setup.
179
186
  The repository also ships a runnable
@@ -196,6 +203,8 @@ bun run playground
196
203
 
197
204
  Then open the local URL printed by the development server. For a zero-install
198
205
  tour, use the [live Playground](https://a3s-lab.github.io/Office/).
206
+ Choose **体验格式修订**, open **审阅**, then **查看修订(1)** to inspect the
207
+ Formatting card and exercise accept or reject semantics.
199
208
 
200
209
  ### Embed a controlled React editor
201
210
 
@@ -1327,6 +1336,7 @@ without hard-coded return URLs.
1327
1336
 
1328
1337
  - [Live Playground](https://a3s-lab.github.io/Office/)
1329
1338
  - [Documentation center](https://a3s-lab.github.io/Office/docs/)
1339
+ - [A3S Office 0.10.0 documentation](https://a3s-lab.github.io/Office/docs/0.10.0/)
1330
1340
  - [A3S Office 0.9.2 documentation](https://a3s-lab.github.io/Office/docs/0.9.2/)
1331
1341
  - [A3S Office 0.9.1 documentation](https://a3s-lab.github.io/Office/docs/0.9.1/)
1332
1342
  - [A3S Office 0.9.0 documentation](https://a3s-lab.github.io/Office/docs/0.9.0/)
@@ -1,6 +1,6 @@
1
1
  import { descendants, directChildren as work_ooxml_package_directChildren, xmlNamespacePrefix, directChild, attribute as work_ooxml_package_attribute, childPath } from "./5184.js";
2
- import { xmlAttributeLocalName, docxThemeColor, XML_NAMESPACE as work_docx_settings_xml_XML_NAMESPACE, resolveDocxThemeResolver as work_docx_theme_resolveDocxThemeResolver, DOCX_WORDPROCESSING_NAMESPACES, XMLNS_NAMESPACE as work_docx_settings_xml_XMLNS_NAMESPACE, xmlAttributeNamespace } from "./0~2805.js";
3
- import { DOCUMENT_PARAGRAPH_BORDER_STYLES, normalizeDocumentParagraphBorders as work_document_paragraph_borders_normalizeDocumentParagraphBorders, DOCUMENT_PARAGRAPH_BORDER_EDGES, normalizeDocumentPageBorders, normalizeDocumentParagraphBorder, normalizeDocumentEquation, DOCUMENT_PAGE_BORDER_EDGES, DOCUMENT_PARAGRAPH_SHADING_PATTERNS, documentParagraphShadingDomAttributes, documentParagraphBordersDomAttributes, createDocumentEquationElement, normalizeDocumentPageGeometry, normalizeDocumentPageMargins, documentEquationText, normalizeDocumentPaperSource } from "./8928.js";
2
+ import { xmlAttributeLocalName, docxThemeFont, XML_NAMESPACE as work_docx_settings_xml_XML_NAMESPACE, docxThemeColor, resolveDocxThemeResolver as work_docx_theme_resolveDocxThemeResolver, DOCX_WORDPROCESSING_NAMESPACES, XMLNS_NAMESPACE as work_docx_settings_xml_XMLNS_NAMESPACE, xmlAttributeNamespace } from "./0~2805.js";
3
+ import { DOCUMENT_PARAGRAPH_BORDER_STYLES, normalizeDocumentParagraphBorders as work_document_paragraph_borders_normalizeDocumentParagraphBorders, DOCUMENT_PARAGRAPH_BORDER_EDGES, normalizeDocumentPageBorders, normalizeDocumentParagraphBorder, normalizeDocumentEquation, DOCUMENT_PAGE_BORDER_EDGES, DOCUMENT_PARAGRAPH_SHADING_PATTERNS, serializeDocxThemeReference, documentParagraphShadingDomAttributes, importedDocumentCharacterFormatting, documentParagraphBordersDomAttributes, createDocumentEquationElement, normalizeDocumentPageGeometry, normalizeDocumentPageMargins, documentEquationText, normalizeDocumentPaperSource, documentWordLineHeightFactor } from "./8928.js";
4
4
  function docxFieldInstructions(root) {
5
5
  return docxFieldOccurrences(root).map((field)=>field.instruction);
6
6
  }
@@ -5650,4 +5650,501 @@ function work_docx_paragraph_shading_import_textNodes(root) {
5650
5650
  while(walker.nextNode())nodes.push(walker.currentNode);
5651
5651
  return nodes;
5652
5652
  }
5653
- export { applyImportedDocxEquationMarkers, applyImportedDocxParagraphBorderMarkers, applyImportedDocxParagraphShadingMarkers, createDocxParagraphStyleResolver, createDocxTableStyleResolver, docxCaptionBookmark, docxCaptionReferenceTarget, docxCaptionSequenceKind, docxEquationHtml, docxFieldInstructions, docxFieldOccurrenceIsInlineEditable, docxFieldOccurrences, docxFieldResultText, docxParagraphPropertySources, docxRunPropertySources, docxTableCellStyleLayers, docxTableParagraphPropertySources, docxTablePropertySources, docxTableRunPropertySources, hasImportedDocxParagraphBorderMarkers, hasImportedDocxParagraphShadingMarkers, hasInvalidDocxFieldStructure, inspectDocxEquation, inspectDocxPageBorders, inspectDocxPageMarginSettings, inspectDocxPageMargins, inspectDocxPageSize, inspectDocxPaperSource, isDocxEquationLikeRoot, isSupportedDocxEquationPlacement, markDocxPackageEquations, markDocxParagraphBorders, markDocxParagraphShading, parseDirectDocxParagraphShading, parseDocxPageBorders, parseDocxPageGeometry, parseDocxPageMargins, parseDocxPaperSource, resolveDocxParagraphBordersForParagraph, resolveDocxParagraphStyleResolver, resolveDocxTableStyleResolver };
5653
+ const work_docx_run_formatting_import_WORD_NAMESPACE = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
5654
+ const work_docx_run_formatting_import_XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
5655
+ const RUN_FORMATTING_MARKER_PATTERN = /__A3S_WORK_RUN_(?:START|END)_\d+__/g;
5656
+ const SUPPORTED_RUN_PROPERTY_CHANGE_CHILDREN = new Set([
5657
+ 'b',
5658
+ 'bCs',
5659
+ 'i',
5660
+ 'iCs',
5661
+ 'u',
5662
+ 'strike',
5663
+ 'dstrike',
5664
+ 'rFonts',
5665
+ 'sz',
5666
+ 'szCs',
5667
+ 'color',
5668
+ 'highlight',
5669
+ 'shd',
5670
+ 'snapToGrid',
5671
+ 'cs',
5672
+ 'rtl',
5673
+ 'vertAlign'
5674
+ ]);
5675
+ function markDocxRunFormatting(document, styleSource, themeSource, tableStyleSource) {
5676
+ const runs = [];
5677
+ const styles = resolveDocxParagraphStyleResolver(styleSource);
5678
+ const theme = work_docx_theme_resolveDocxThemeResolver(themeSource);
5679
+ const tableStyles = resolveDocxTableStyleResolver(tableStyleSource);
5680
+ for (const run of descendants(document, 'r')){
5681
+ const runText = directRunText(run);
5682
+ if (!runText || runText.includes('__A3S_')) continue;
5683
+ const paragraph = work_docx_run_formatting_import_closestAncestor(run, 'p');
5684
+ if (!paragraph) continue;
5685
+ const paragraphProperties = directChild(paragraph, 'pPr');
5686
+ const runProperties = directChild(run, 'rPr');
5687
+ const formatting = resolvedRunFormatting(docxRunPropertySources(paragraphProperties, runProperties, styles, docxTableRunPropertySources(run, tableStyles)), theme, runText);
5688
+ const change = importedRunFormattingChange(runProperties, paragraphProperties, styles, docxTableRunPropertySources(run, tableStyles), theme, runText);
5689
+ if (!Object.keys(formatting).length && !change) continue;
5690
+ const index = runs.length + 1;
5691
+ const startMarker = `__A3S_WORK_RUN_START_${index}__`;
5692
+ const endMarker = `__A3S_WORK_RUN_END_${index}__`;
5693
+ insertRunMarkers(document, run, runProperties, startMarker, endMarker);
5694
+ runs.push({
5695
+ startMarker,
5696
+ endMarker,
5697
+ formatting,
5698
+ ...change ? {
5699
+ change
5700
+ } : {}
5701
+ });
5702
+ }
5703
+ return {
5704
+ runs
5705
+ };
5706
+ }
5707
+ function applyImportedDocxRunFormattingMarkers(document, markers) {
5708
+ const replacements = new Map();
5709
+ for (const marker of markers.runs){
5710
+ const markup = formattingMarkup(document, marker.formatting);
5711
+ const change = marker.change ? formattingChangeMarkup(document, marker.change) : {
5712
+ start: '',
5713
+ end: ''
5714
+ };
5715
+ replacements.set(marker.startMarker, `${change.start}${markup.start}`);
5716
+ replacements.set(marker.endMarker, `${markup.end}${change.end}`);
5717
+ }
5718
+ document.body.innerHTML = document.body.innerHTML.replace(RUN_FORMATTING_MARKER_PATTERN, (marker)=>replacements.get(marker) ?? '');
5719
+ }
5720
+ function hasImportedDocxRunFormattingMarkers(markers) {
5721
+ return markers.runs.length > 0;
5722
+ }
5723
+ function resolvedRunFormatting(propertySources, theme, runText) {
5724
+ const fonts = {};
5725
+ let fontSize;
5726
+ let complexFontSize;
5727
+ let color;
5728
+ let backgroundColor;
5729
+ let themeColor;
5730
+ let themeFill;
5731
+ let bold;
5732
+ let complexBold;
5733
+ let italic;
5734
+ let complexItalic;
5735
+ let underline;
5736
+ let strike;
5737
+ let subscript;
5738
+ let superscript;
5739
+ let snapToGrid;
5740
+ let complexScriptFormatting;
5741
+ let rightToLeft;
5742
+ let fontHint;
5743
+ for (const properties of propertySources){
5744
+ bold = overriddenBoolean(bold, onOffProperty(properties, 'b'));
5745
+ complexBold = overriddenBoolean(complexBold, onOffProperty(properties, 'bCs'));
5746
+ italic = overriddenBoolean(italic, onOffProperty(properties, 'i'));
5747
+ complexItalic = overriddenBoolean(complexItalic, onOffProperty(properties, 'iCs'));
5748
+ underline = overriddenBoolean(underline, underlineProperty(properties));
5749
+ strike = overriddenBoolean(strike, onOffProperty(properties, 'strike'));
5750
+ strike = overriddenBoolean(strike, onOffProperty(properties, 'dstrike'));
5751
+ const verticalAlign = directChild(properties, 'vertAlign');
5752
+ if (verticalAlign) {
5753
+ const value = work_docx_run_formatting_import_wordAttribute(verticalAlign, 'val')?.trim().toLowerCase();
5754
+ subscript = "subscript" === value;
5755
+ superscript = "superscript" === value;
5756
+ if ('baseline' === value) {
5757
+ subscript = false;
5758
+ superscript = false;
5759
+ }
5760
+ }
5761
+ snapToGrid = overriddenBoolean(snapToGrid, onOffProperty(properties, 'snapToGrid'));
5762
+ complexScriptFormatting = overriddenBoolean(complexScriptFormatting, onOffProperty(properties, 'cs'));
5763
+ rightToLeft = overriddenBoolean(rightToLeft, onOffProperty(properties, 'rtl'));
5764
+ const runFonts = directChild(properties, 'rFonts');
5765
+ if (runFonts) {
5766
+ fontHint = resolvedFontHint(runFonts) ?? fontHint;
5767
+ assignFont(fonts, 'ascii', resolvedFont(runFonts, 'ascii', 'asciiTheme', theme));
5768
+ assignFont(fonts, 'hAnsi', resolvedFont(runFonts, 'hAnsi', 'hAnsiTheme', theme));
5769
+ assignFont(fonts, 'eastAsia', resolvedFont(runFonts, 'eastAsia', 'eastAsiaTheme', theme));
5770
+ assignFont(fonts, 'complex', resolvedFont(runFonts, 'cs', 'cstheme', theme));
5771
+ }
5772
+ const size = numericAttribute(directChild(properties, 'sz'), 'val');
5773
+ if (void 0 !== size && size > 0) fontSize = Math.min(512, size / 2);
5774
+ const complexSize = numericAttribute(directChild(properties, 'szCs'), 'val');
5775
+ if (void 0 !== complexSize && complexSize > 0) complexFontSize = Math.min(512, complexSize / 2);
5776
+ const colorElement = directChild(properties, 'color');
5777
+ if (colorElement) {
5778
+ const value = work_docx_run_formatting_import_wordAttribute(colorElement, 'val')?.trim();
5779
+ const themed = docxThemeColor(theme, work_docx_run_formatting_import_wordAttribute(colorElement, 'themeColor'), work_docx_run_formatting_import_wordAttribute(colorElement, 'themeTint'), work_docx_run_formatting_import_wordAttribute(colorElement, 'themeShade'));
5780
+ if (themed) {
5781
+ color = `#${themed}`;
5782
+ themeColor = work_docx_run_formatting_import_themeReference(colorElement, 'themeColor', 'themeTint', 'themeShade', color);
5783
+ } else if (value?.toLowerCase() === 'auto') {
5784
+ color = 'inherit';
5785
+ themeColor = void 0;
5786
+ } else if (value && /^[0-9a-f]{6}$/i.test(value)) {
5787
+ color = `#${value.toLowerCase()}`;
5788
+ themeColor = void 0;
5789
+ }
5790
+ }
5791
+ const highlight = directChild(properties, 'highlight');
5792
+ if (highlight) {
5793
+ const value = work_docx_run_formatting_import_wordAttribute(highlight, 'val')?.trim().toLowerCase();
5794
+ if (value) {
5795
+ backgroundColor = wordHighlightColor(value);
5796
+ themeFill = void 0;
5797
+ }
5798
+ } else {
5799
+ const shading = directChild(properties, 'shd');
5800
+ const fill = shading ? work_docx_run_formatting_import_wordAttribute(shading, 'fill')?.trim() : void 0;
5801
+ const themed = shading ? docxThemeColor(theme, work_docx_run_formatting_import_wordAttribute(shading, 'themeFill'), work_docx_run_formatting_import_wordAttribute(shading, 'themeFillTint'), work_docx_run_formatting_import_wordAttribute(shading, 'themeFillShade')) : void 0;
5802
+ if (themed) {
5803
+ backgroundColor = `#${themed}`;
5804
+ themeFill = work_docx_run_formatting_import_themeReference(shading, 'themeFill', 'themeFillTint', 'themeFillShade', backgroundColor);
5805
+ } else if (fill?.toLowerCase() === 'auto') {
5806
+ backgroundColor = 'transparent';
5807
+ themeFill = void 0;
5808
+ } else if (fill && /^[0-9a-f]{6}$/i.test(fill)) {
5809
+ backgroundColor = `#${fill.toLowerCase()}`;
5810
+ themeFill = void 0;
5811
+ }
5812
+ }
5813
+ }
5814
+ const fontSlot = true === complexScriptFormatting || true === rightToLeft ? 'complex' : docxFontSlotForText(runText, fontHint);
5815
+ const usesComplexFormatting = 'complex' === fontSlot;
5816
+ const resolvedBold = usesComplexFormatting ? complexBold ?? bold : bold;
5817
+ const resolvedItalic = usesComplexFormatting ? complexItalic ?? italic : italic;
5818
+ const resolvedFontSize = usesComplexFormatting ? complexFontSize ?? fontSize : fontSize;
5819
+ let fontFamily = uniqueFonts(orderedFonts(fonts, fontSlot));
5820
+ if (!fontFamily) fontFamily = uniqueFonts(orderedFonts({
5821
+ ascii: docxThemeFont(theme, 'minorAscii'),
5822
+ hAnsi: docxThemeFont(theme, 'minorHAnsi'),
5823
+ eastAsia: docxThemeFont(theme, 'minorEastAsia'),
5824
+ complex: docxThemeFont(theme, 'minorBidi')
5825
+ }, fontSlot));
5826
+ const hasRunPropertySource = propertySources.length > 0;
5827
+ return {
5828
+ ...void 0 !== resolvedBold || hasRunPropertySource ? {
5829
+ bold: resolvedBold ?? false
5830
+ } : {},
5831
+ ...void 0 !== resolvedItalic || hasRunPropertySource ? {
5832
+ italic: resolvedItalic ?? false
5833
+ } : {},
5834
+ ...void 0 !== underline || hasRunPropertySource ? {
5835
+ underline: underline ?? false
5836
+ } : {},
5837
+ ...void 0 !== strike || hasRunPropertySource ? {
5838
+ strike: strike ?? false
5839
+ } : {},
5840
+ ...void 0 !== subscript ? {
5841
+ subscript
5842
+ } : {},
5843
+ ...void 0 !== superscript ? {
5844
+ superscript
5845
+ } : {},
5846
+ ...fontFamily ? {
5847
+ fontFamily,
5848
+ wordLineHeightFactor: documentWordLineHeightFactor(fontFamily)
5849
+ } : {},
5850
+ ...void 0 !== resolvedFontSize ? {
5851
+ fontSize: resolvedFontSize
5852
+ } : {},
5853
+ ...void 0 !== snapToGrid ? {
5854
+ wordSnapToGrid: snapToGrid
5855
+ } : {},
5856
+ ...color ? {
5857
+ color
5858
+ } : {},
5859
+ ...backgroundColor ? {
5860
+ backgroundColor
5861
+ } : {},
5862
+ ...themeColor ? {
5863
+ themeColor
5864
+ } : {},
5865
+ ...themeFill ? {
5866
+ themeFill
5867
+ } : {}
5868
+ };
5869
+ }
5870
+ function insertRunMarkers(document, run, properties, startMarker, endMarker) {
5871
+ const start = markerText(document, startMarker);
5872
+ const end = markerText(document, endMarker);
5873
+ run.insertBefore(start, properties?.nextSibling ?? run.firstChild);
5874
+ run.append(end);
5875
+ }
5876
+ function markerText(document, marker) {
5877
+ const text = document.createElementNS(work_docx_run_formatting_import_WORD_NAMESPACE, 'w:t');
5878
+ text.setAttributeNS(work_docx_run_formatting_import_XML_NAMESPACE, 'xml:space', 'preserve');
5879
+ text.textContent = marker;
5880
+ return text;
5881
+ }
5882
+ function formattingMarkup(document, formatting) {
5883
+ const span = document.createElement('span');
5884
+ if (false === formatting.bold) span.style.fontWeight = 'normal';
5885
+ if (false === formatting.italic) span.style.fontStyle = 'normal';
5886
+ if ((false === formatting.underline || false === formatting.strike) && !formatting.underline && !formatting.strike) span.style.textDecorationLine = 'none';
5887
+ if (formatting.fontFamily) span.style.fontFamily = formatting.fontFamily;
5888
+ if (void 0 !== formatting.wordLineHeightFactor) {
5889
+ const factor = formatLineHeightFactor(formatting.wordLineHeightFactor);
5890
+ span.dataset.officeWordLineHeightFactor = factor;
5891
+ span.style.setProperty('--work-document-word-line-height-factor', factor);
5892
+ }
5893
+ if (void 0 !== formatting.wordSnapToGrid) span.dataset.officeWordSnapToGrid = String(formatting.wordSnapToGrid);
5894
+ if (void 0 !== formatting.fontSize) span.style.fontSize = `${formatNumber(formatting.fontSize)}pt`;
5895
+ if (formatting.color) span.style.color = formatting.color;
5896
+ const themeColor = serializeDocxThemeReference(formatting.themeColor ?? null);
5897
+ if (themeColor) span.dataset.officeThemeColor = themeColor;
5898
+ if (formatting.backgroundColor) span.style.backgroundColor = formatting.backgroundColor;
5899
+ const themeFill = serializeDocxThemeReference(formatting.themeFill ?? null);
5900
+ if (themeFill) span.dataset.officeThemeFill = themeFill;
5901
+ const html = span.outerHTML;
5902
+ const tags = [
5903
+ ...formatting.bold ? [
5904
+ 'strong'
5905
+ ] : [],
5906
+ ...formatting.italic ? [
5907
+ 'em'
5908
+ ] : [],
5909
+ ...formatting.underline ? [
5910
+ 'u'
5911
+ ] : [],
5912
+ ...formatting.strike ? [
5913
+ 's'
5914
+ ] : [],
5915
+ ...formatting.subscript ? [
5916
+ 'sub'
5917
+ ] : [],
5918
+ ...formatting.superscript ? [
5919
+ 'sup'
5920
+ ] : []
5921
+ ];
5922
+ return {
5923
+ start: `${html.slice(0, html.indexOf('>') + 1)}${tags.map((tag)=>`<${tag}>`).join('')}`,
5924
+ end: `${[
5925
+ ...tags
5926
+ ].reverse().map((tag)=>`</${tag}>`).join('')}</span>`
5927
+ };
5928
+ }
5929
+ function importedRunFormattingChange(runProperties, paragraphProperties, styles, contextualProperties, theme, runText) {
5930
+ const parsed = supportedRunFormattingChange(runProperties);
5931
+ if (!parsed) return;
5932
+ const beforeFormatting = resolvedRunFormatting(docxRunPropertySources(paragraphProperties, parsed.properties, styles, contextualProperties), theme, runText);
5933
+ return {
5934
+ id: `docx-format-change-${parsed.id}`,
5935
+ author: parsed.author,
5936
+ date: normalizeRevisionDate(parsed.date),
5937
+ before: importedDocumentCharacterFormatting({
5938
+ ...beforeFormatting,
5939
+ themeColor: serializeDocxThemeReference(beforeFormatting.themeColor ?? null),
5940
+ themeFill: serializeDocxThemeReference(beforeFormatting.themeFill ?? null)
5941
+ })
5942
+ };
5943
+ }
5944
+ function isSupportedDocxRunFormattingChange(change) {
5945
+ return Boolean(supportedRunFormattingChangeElement(change));
5946
+ }
5947
+ function supportedRunFormattingChange(runProperties) {
5948
+ if (!runProperties) return null;
5949
+ const changes = Array.from(runProperties.children).filter((child)=>'rPrChange' === child.localName);
5950
+ return 1 === changes.length ? supportedRunFormattingChangeElement(changes[0]) : null;
5951
+ }
5952
+ function supportedRunFormattingChangeElement(change) {
5953
+ if ('rPrChange' !== change.localName || !DOCX_WORDPROCESSING_NAMESPACES.has(change.namespaceURI ?? '')) return null;
5954
+ const properties = Array.from(change.children).filter((child)=>'rPr' === child.localName && DOCX_WORDPROCESSING_NAMESPACES.has(child.namespaceURI ?? ''));
5955
+ if (1 !== properties.length || 1 !== change.children.length) return null;
5956
+ const source = properties[0];
5957
+ const names = new Set();
5958
+ for (const child of Array.from(source.children)){
5959
+ if (!DOCX_WORDPROCESSING_NAMESPACES.has(child.namespaceURI ?? '') || !SUPPORTED_RUN_PROPERTY_CHANGE_CHILDREN.has(child.localName) || names.has(child.localName)) return null;
5960
+ names.add(child.localName);
5961
+ }
5962
+ const id = work_ooxml_package_attribute(change, 'id')?.trim() ?? '';
5963
+ const author = work_ooxml_package_attribute(change, 'author')?.trim() ?? '';
5964
+ const date = work_ooxml_package_attribute(change, 'date');
5965
+ if (!/^\+?\d{1,10}$/.test(id) || !author || author.length > 255) return null;
5966
+ if (date && !Number.isFinite(Date.parse(date))) return null;
5967
+ return {
5968
+ id,
5969
+ author,
5970
+ date,
5971
+ properties: source
5972
+ };
5973
+ }
5974
+ function formattingChangeMarkup(document, change) {
5975
+ const span = document.createElement('span');
5976
+ span.dataset.documentChange = 'true';
5977
+ span.dataset.changeKind = 'formatting';
5978
+ span.dataset.changeId = change.id;
5979
+ span.dataset.changeAuthor = change.author;
5980
+ span.dataset.changeDate = change.date;
5981
+ span.dataset.changeBefore = change.before;
5982
+ const html = span.outerHTML;
5983
+ return {
5984
+ start: html.slice(0, html.indexOf('>') + 1),
5985
+ end: '</span>'
5986
+ };
5987
+ }
5988
+ function normalizeRevisionDate(value) {
5989
+ if (!value) return '';
5990
+ const time = Date.parse(value);
5991
+ return Number.isFinite(time) ? new Date(time).toISOString() : '';
5992
+ }
5993
+ function work_docx_run_formatting_import_themeReference(element, themeName, tintName, shadeName, resolved) {
5994
+ const theme = work_docx_run_formatting_import_wordAttribute(element, themeName)?.trim();
5995
+ if (!theme) return;
5996
+ const tint = normalizedByteHex(work_docx_run_formatting_import_wordAttribute(element, tintName));
5997
+ const shade = normalizedByteHex(work_docx_run_formatting_import_wordAttribute(element, shadeName));
5998
+ return {
5999
+ theme,
6000
+ resolved,
6001
+ ...tint ? {
6002
+ tint
6003
+ } : {},
6004
+ ...shade ? {
6005
+ shade
6006
+ } : {}
6007
+ };
6008
+ }
6009
+ function normalizedByteHex(value) {
6010
+ const normalized = value?.trim().toUpperCase();
6011
+ return normalized && /^[0-9A-F]{2}$/.test(normalized) ? normalized : void 0;
6012
+ }
6013
+ function overriddenBoolean(current, next) {
6014
+ return void 0 === next ? current : next;
6015
+ }
6016
+ function onOffProperty(properties, propertyName) {
6017
+ const element = directChild(properties, propertyName);
6018
+ if (!element) return;
6019
+ const value = work_docx_run_formatting_import_wordAttribute(element, 'val')?.trim().toLowerCase();
6020
+ return '0' !== value && 'false' !== value && 'off' !== value;
6021
+ }
6022
+ function underlineProperty(properties) {
6023
+ const element = directChild(properties, 'u');
6024
+ if (!element) return;
6025
+ const value = work_docx_run_formatting_import_wordAttribute(element, 'val')?.trim().toLowerCase();
6026
+ return '0' !== value && 'false' !== value && 'off' !== value && 'none' !== value;
6027
+ }
6028
+ function directRunText(run) {
6029
+ return Array.from(run.children).filter((child)=>'t' === child.localName || 'delText' === child.localName).map((child)=>child.textContent ?? '').join('');
6030
+ }
6031
+ function work_docx_run_formatting_import_closestAncestor(element, localName) {
6032
+ let current = element.parentElement;
6033
+ while(current){
6034
+ if (current.localName === localName) return current;
6035
+ current = current.parentElement;
6036
+ }
6037
+ return null;
6038
+ }
6039
+ function assignFont(fonts, key, value) {
6040
+ const normalized = value?.trim();
6041
+ if (normalized) fonts[key] = normalized;
6042
+ }
6043
+ function resolvedFontHint(element) {
6044
+ const hint = work_docx_run_formatting_import_wordAttribute(element, 'hint')?.trim().toLowerCase();
6045
+ if ('eastasia' === hint) return 'eastAsia';
6046
+ if ('cs' === hint) return 'complex';
6047
+ if ('default' === hint) return 'ascii';
6048
+ }
6049
+ function orderedFonts(fonts, preferred) {
6050
+ const order = {
6051
+ ascii: [
6052
+ 'ascii',
6053
+ 'hAnsi',
6054
+ 'eastAsia',
6055
+ 'complex'
6056
+ ],
6057
+ hAnsi: [
6058
+ 'hAnsi',
6059
+ 'ascii',
6060
+ 'eastAsia',
6061
+ 'complex'
6062
+ ],
6063
+ eastAsia: [
6064
+ 'eastAsia',
6065
+ 'hAnsi',
6066
+ 'ascii',
6067
+ 'complex'
6068
+ ],
6069
+ complex: [
6070
+ 'complex',
6071
+ 'hAnsi',
6072
+ 'ascii',
6073
+ 'eastAsia'
6074
+ ]
6075
+ };
6076
+ return order[preferred].map((slot)=>fonts[slot]);
6077
+ }
6078
+ function docxFontSlotForText(text, hint) {
6079
+ for (const character of text){
6080
+ const codePoint = character.codePointAt(0);
6081
+ if (!(void 0 === codePoint || isNeutralCodePoint(codePoint))) {
6082
+ if (isComplexScriptCodePoint(codePoint)) return 'complex';
6083
+ if (isEastAsianCodePoint(codePoint)) return 'eastAsia';
6084
+ return codePoint <= 0x7f ? 'ascii' : 'hAnsi';
6085
+ }
6086
+ }
6087
+ return hint ?? 'ascii';
6088
+ }
6089
+ function isNeutralCodePoint(codePoint) {
6090
+ return codePoint <= 0x20 || codePoint <= 0x7f && !(codePoint >= 0x41 && codePoint <= 0x5a || codePoint >= 0x61 && codePoint <= 0x7a) || codePoint >= 0x300 && codePoint <= 0x36f || codePoint >= 0x2000 && codePoint <= 0x206f;
6091
+ }
6092
+ function isComplexScriptCodePoint(codePoint) {
6093
+ return codePoint >= 0x0590 && codePoint <= 0x08ff || codePoint >= 0xfb1d && codePoint <= 0xfdff || codePoint >= 0xfe70 && codePoint <= 0xfeff || codePoint >= 0x1ee00 && codePoint <= 0x1eeff;
6094
+ }
6095
+ function isEastAsianCodePoint(codePoint) {
6096
+ return codePoint >= 0x1100 && codePoint <= 0x11ff || codePoint >= 0x2e80 && codePoint <= 0xa4cf || codePoint >= 0xac00 && codePoint <= 0xd7af || codePoint >= 0xf900 && codePoint <= 0xfaff || codePoint >= 0xfe10 && codePoint <= 0xfe6f || codePoint >= 0xff00 && codePoint <= 0xffef || codePoint >= 0x20000 && codePoint <= 0x323af;
6097
+ }
6098
+ function resolvedFont(element, explicitName, themeName, theme) {
6099
+ const explicit = work_docx_run_formatting_import_wordAttribute(element, explicitName);
6100
+ if (explicit?.trim()) return explicit;
6101
+ return docxThemeFont(theme, work_docx_run_formatting_import_wordAttribute(element, themeName)) ?? null;
6102
+ }
6103
+ function uniqueFonts(values) {
6104
+ const fonts = [
6105
+ ...new Set(values.filter((value)=>!!value))
6106
+ ];
6107
+ return fonts.length ? fonts.map(cssFontFamily).join(', ') : void 0;
6108
+ }
6109
+ function cssFontFamily(value) {
6110
+ return /[\s"',]/.test(value) ? `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"` : value;
6111
+ }
6112
+ function numericAttribute(element, name) {
6113
+ if (!element) return;
6114
+ const raw = work_docx_run_formatting_import_wordAttribute(element, name);
6115
+ if (null === raw || !raw.trim()) return;
6116
+ const value = Number(raw);
6117
+ return Number.isFinite(value) ? value : void 0;
6118
+ }
6119
+ function work_docx_run_formatting_import_wordAttribute(element, name) {
6120
+ return work_ooxml_package_attribute(element, name) ?? work_ooxml_package_attribute(element, `w:${name}`);
6121
+ }
6122
+ function wordHighlightColor(value) {
6123
+ const colors = {
6124
+ black: '#000000',
6125
+ blue: '#0000ff',
6126
+ cyan: '#00ffff',
6127
+ darkblue: '#000080',
6128
+ darkcyan: '#008080',
6129
+ darkgray: '#808080',
6130
+ darkgreen: '#008000',
6131
+ darkmagenta: '#800080',
6132
+ darkred: '#800000',
6133
+ darkyellow: '#808000',
6134
+ green: '#00ff00',
6135
+ lightgray: '#c0c0c0',
6136
+ magenta: '#ff00ff',
6137
+ none: 'transparent',
6138
+ red: '#ff0000',
6139
+ white: '#ffffff',
6140
+ yellow: '#ffff00'
6141
+ };
6142
+ return colors[value] ?? 'transparent';
6143
+ }
6144
+ function formatNumber(value) {
6145
+ return Number(value.toFixed(2)).toString();
6146
+ }
6147
+ function formatLineHeightFactor(value) {
6148
+ return Number(value.toFixed(4)).toString();
6149
+ }
6150
+ export { applyImportedDocxEquationMarkers, applyImportedDocxParagraphBorderMarkers, applyImportedDocxParagraphShadingMarkers, applyImportedDocxRunFormattingMarkers, createDocxParagraphStyleResolver, createDocxTableStyleResolver, docxCaptionBookmark, docxCaptionReferenceTarget, docxCaptionSequenceKind, docxEquationHtml, docxFieldInstructions, docxFieldOccurrenceIsInlineEditable, docxFieldOccurrences, docxFieldResultText, docxParagraphPropertySources, docxTableCellStyleLayers, docxTableParagraphPropertySources, docxTablePropertySources, hasImportedDocxParagraphBorderMarkers, hasImportedDocxParagraphShadingMarkers, hasImportedDocxRunFormattingMarkers, hasInvalidDocxFieldStructure, inspectDocxEquation, inspectDocxPageBorders, inspectDocxPageMarginSettings, inspectDocxPageMargins, inspectDocxPageSize, inspectDocxPaperSource, isDocxEquationLikeRoot, isSupportedDocxEquationPlacement, isSupportedDocxRunFormattingChange, markDocxPackageEquations, markDocxParagraphBorders, markDocxParagraphShading, markDocxRunFormatting, parseDirectDocxParagraphShading, parseDocxPageBorders, parseDocxPageGeometry, parseDocxPageMargins, parseDocxPaperSource, resolveDocxParagraphBordersForParagraph, resolveDocxParagraphStyleResolver, resolveDocxTableStyleResolver };
@@ -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,11 @@ 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
+ return '删除';
900
+ }
895
901
  function documentChangeWindowKey(change) {
896
902
  return `${change.kind}:${change.id}`;
897
903
  }