@a3s-lab/office 0.48.1 → 0.50.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.
@@ -1172,6 +1172,7 @@ function documentChangeKindLabel(kind) {
1172
1172
  if ('formatting' === kind) return '格式';
1173
1173
  if ('paragraph-formatting' === kind) return '段落格式';
1174
1174
  if ('numbering' === kind) return '编号格式';
1175
+ if ('move' === kind) return '移动';
1175
1176
  return '删除';
1176
1177
  }
1177
1178
  function documentChangeWindowKey(change) {
@@ -17433,6 +17434,222 @@ function sortJsonValue(value) {
17433
17434
  sortJsonValue(child)
17434
17435
  ]));
17435
17436
  }
17437
+ function createComparisonIdentityFactory(currentSignature, revisedSignature, options) {
17438
+ const seed = stableHash(`${options.mode}\u0000${currentSignature}\u0000${revisedSignature}`);
17439
+ const author = boundedMetadata(options.author, 256) || 'A3S Work user';
17440
+ const date = normalizedDate(options.date);
17441
+ const summary = emptyComparisonSummary();
17442
+ let sequence = 0;
17443
+ return {
17444
+ summary,
17445
+ create (kind) {
17446
+ sequence += 1;
17447
+ if ('insertion' === kind) summary.insertions += 1;
17448
+ else if ('deletion' === kind) summary.deletions += 1;
17449
+ else if ('formatting' === kind) summary.formatting += 1;
17450
+ else if ('move' === kind) summary.moves = (summary.moves ?? 0) + 1;
17451
+ else summary.paragraphFormatting += 1;
17452
+ return {
17453
+ id: `compare-${seed}-${kind}-${sequence.toString(36)}`,
17454
+ author,
17455
+ date
17456
+ };
17457
+ },
17458
+ paragraphIdentity (channel) {
17459
+ return {
17460
+ paragraphId: wordParagraphId(`${seed}:${channel}:paragraph`),
17461
+ textId: wordParagraphId(`${seed}:${channel}:text`)
17462
+ };
17463
+ }
17464
+ };
17465
+ }
17466
+ function summarizeComparisonChanges(changes) {
17467
+ const summary = emptyComparisonSummary();
17468
+ for (const change of changes)if ('insertion' === change.kind) summary.insertions += 1;
17469
+ else if ('deletion' === change.kind) summary.deletions += 1;
17470
+ else if ('formatting' === change.kind) summary.formatting += 1;
17471
+ else if ('move' === change.kind) summary.moves = (summary.moves ?? 0) + 1;
17472
+ else summary.paragraphFormatting += 1;
17473
+ return summary;
17474
+ }
17475
+ function emptyComparisonSummary() {
17476
+ return {
17477
+ deletions: 0,
17478
+ formatting: 0,
17479
+ insertions: 0,
17480
+ paragraphFormatting: 0
17481
+ };
17482
+ }
17483
+ const MAX_INFERRED_MOVE_TEXT = 65536;
17484
+ const MAX_INFERRED_MOVES = 256;
17485
+ function work_document_compare_moves_appendRevisionUnits(target, units, kind, identity, schema, stripReviewMarks) {
17486
+ for (const unit of units)target.push(schema.text(unit.text, [
17487
+ ...stripReviewMarks(unit.marks),
17488
+ schema.marks.documentChange.create({
17489
+ kind,
17490
+ id: identity.id,
17491
+ actorId: identity.actorId ?? '',
17492
+ author: identity.author,
17493
+ date: identity.date,
17494
+ before: ''
17495
+ })
17496
+ ]));
17497
+ }
17498
+ function inferInlineMovePairs(changes, marksEqual) {
17499
+ const fullCandidates = [];
17500
+ for (const [stepIndex, change] of changes.entries()){
17501
+ if ('equal' === change.kind) continue;
17502
+ const units = 'delete' === change.kind ? change.left : change.right;
17503
+ const candidate = inlineMoveCandidate(stepIndex, change.kind, units);
17504
+ if (candidate) fullCandidates.push(candidate);
17505
+ }
17506
+ const fullPairs = pairInlineMoveCandidates(fullCandidates, marksEqual);
17507
+ const consumedSteps = new Set();
17508
+ for (const pair of fullPairs){
17509
+ consumedSteps.add(pair.deletion.stepIndex);
17510
+ consumedSteps.add(pair.insertion.stepIndex);
17511
+ }
17512
+ const tokenCandidates = [];
17513
+ for (const [stepIndex, change] of changes.entries()){
17514
+ if ('equal' === change.kind || consumedSteps.has(stepIndex)) continue;
17515
+ const units = 'delete' === change.kind ? change.left : change.right;
17516
+ tokenCandidates.push(...inlineMoveTokenCandidates(stepIndex, change.kind, units));
17517
+ }
17518
+ return [
17519
+ ...fullPairs,
17520
+ ...pairInlineMoveCandidates(tokenCandidates, marksEqual)
17521
+ ].sort((left, right)=>left.deletion.stepIndex - right.deletion.stepIndex || left.insertion.stepIndex - right.insertion.stepIndex || left.deletion.start - right.deletion.start).slice(0, MAX_INFERRED_MOVES);
17522
+ }
17523
+ function appendComparisonRevisionUnits(target, units, kind, schema, factory, stripReviewMarks, appendRevisionUnits, moves) {
17524
+ if (!moves?.length) return void appendRevisionUnits(target, units, kind, factory.create(kind), schema, stripReviewMarks);
17525
+ const byUnit = new Map();
17526
+ for (const move of [
17527
+ ...moves
17528
+ ].sort((left, right)=>left.candidate.start - right.candidate.start))for(let index = move.candidate.start; index < move.candidate.end; index += 1){
17529
+ if (byUnit.has(index)) return void appendRevisionUnits(target, units, kind, factory.create(kind), schema, stripReviewMarks);
17530
+ byUnit.set(index, move);
17531
+ }
17532
+ let ordinary = [];
17533
+ const flushOrdinary = ()=>{
17534
+ if (!ordinary.length) return;
17535
+ appendRevisionUnits(target, ordinary, kind, factory.create(kind), schema, stripReviewMarks);
17536
+ ordinary = [];
17537
+ };
17538
+ for (const [index, unit] of units.entries()){
17539
+ const move = byUnit.get(index);
17540
+ if (!move) {
17541
+ ordinary.push(unit);
17542
+ continue;
17543
+ }
17544
+ const start = index === move.candidate.start ? move.candidate.startOffset : 0;
17545
+ const end = index === move.candidate.end - 1 ? move.candidate.endOffset : unit.text.length;
17546
+ const prefix = unit.text.slice(0, start);
17547
+ const marked = unit.text.slice(start, end);
17548
+ const suffix = unit.text.slice(end);
17549
+ if (prefix) ordinary.push({
17550
+ text: prefix,
17551
+ marks: unit.marks
17552
+ });
17553
+ flushOrdinary();
17554
+ if (marked) appendMoveRevisionUnits(target, [
17555
+ {
17556
+ text: marked,
17557
+ marks: unit.marks
17558
+ }
17559
+ ], move.role, move.identity, schema, stripReviewMarks);
17560
+ ordinary = suffix ? [
17561
+ {
17562
+ text: suffix,
17563
+ marks: unit.marks
17564
+ }
17565
+ ] : [];
17566
+ }
17567
+ flushOrdinary();
17568
+ }
17569
+ function pairInlineMoveCandidates(candidates, marksEqual) {
17570
+ const grouped = new Map();
17571
+ for (const candidate of candidates){
17572
+ const group = grouped.get(candidate.text) ?? {
17573
+ deletion: [],
17574
+ insertion: []
17575
+ };
17576
+ ('delete' === candidate.kind ? group.deletion : group.insertion).push(candidate);
17577
+ grouped.set(candidate.text, group);
17578
+ }
17579
+ return Array.from(grouped.values()).filter(({ deletion, insertion })=>1 === deletion.length && 1 === insertion.length && inlineMoveCandidatesEqual(deletion[0], insertion[0], marksEqual)).map(({ deletion, insertion })=>({
17580
+ deletion: deletion[0],
17581
+ insertion: insertion[0]
17582
+ })).sort((left, right)=>left.deletion.stepIndex - right.deletion.stepIndex || left.insertion.stepIndex - right.insertion.stepIndex).slice(0, MAX_INFERRED_MOVES);
17583
+ }
17584
+ function inlineMoveCandidate(stepIndex, kind, units) {
17585
+ let start = 0;
17586
+ while(start < units.length && isWhitespaceUnit(units[start]))start += 1;
17587
+ let end = units.length;
17588
+ while(end > start && isWhitespaceUnit(units[end - 1]))end -= 1;
17589
+ if (start === end) return null;
17590
+ const first = units[start];
17591
+ const last = units[end - 1];
17592
+ const startOffset = first.leadingWhitespaceAttached ? 0 : first.text.length - first.text.trimStart().length;
17593
+ const endOffset = last.leadingWhitespaceAttached ? last.text.length : last.text.trimEnd().length;
17594
+ const text = units.slice(start, end).map((unit, index, selected)=>unit.text.slice(0 === index ? startOffset : 0, index === selected.length - 1 ? endOffset : unit.text.length)).join('');
17595
+ if (!text || text.length > MAX_INFERRED_MOVE_TEXT || !/[\p{L}\p{N}]/u.test(text)) return null;
17596
+ return {
17597
+ stepIndex,
17598
+ kind,
17599
+ units,
17600
+ start,
17601
+ end,
17602
+ startOffset,
17603
+ endOffset,
17604
+ text
17605
+ };
17606
+ }
17607
+ function inlineMoveTokenCandidates(stepIndex, kind, units) {
17608
+ const candidates = [];
17609
+ for (const [index, unit] of units.entries()){
17610
+ const leadingWhitespace = unit.text.length - unit.text.trimStart().length;
17611
+ const text = unit.text.slice(leadingWhitespace);
17612
+ if (text && !(text.length > MAX_INFERRED_MOVE_TEXT) && /[\p{L}\p{N}]/u.test(text)) candidates.push({
17613
+ stepIndex,
17614
+ kind,
17615
+ units,
17616
+ start: index,
17617
+ end: index + 1,
17618
+ startOffset: leadingWhitespace,
17619
+ endOffset: unit.text.length,
17620
+ text
17621
+ });
17622
+ }
17623
+ return candidates;
17624
+ }
17625
+ function inlineMoveCandidatesEqual(left, right, marksEqual) {
17626
+ const leftUnits = selectedMoveUnits(left);
17627
+ const rightUnits = selectedMoveUnits(right);
17628
+ return leftUnits.length === rightUnits.length && leftUnits.every((unit, index)=>unit.text === rightUnits[index]?.text && marksEqual(unit.marks, rightUnits[index]?.marks ?? []));
17629
+ }
17630
+ function selectedMoveUnits(candidate) {
17631
+ return candidate.units.slice(candidate.start, candidate.end).map((unit, index, selected)=>({
17632
+ text: unit.text.slice(0 === index ? candidate.startOffset : 0, index === selected.length - 1 ? candidate.endOffset : unit.text.length),
17633
+ marks: unit.marks
17634
+ })).filter((unit)=>unit.text.length > 0);
17635
+ }
17636
+ function isWhitespaceUnit(unit) {
17637
+ return '' === unit.text.trim();
17638
+ }
17639
+ function appendMoveRevisionUnits(target, units, role, identity, schema, stripReviewMarks) {
17640
+ for (const unit of units)target.push(schema.text(unit.text, [
17641
+ ...stripReviewMarks(unit.marks),
17642
+ schema.marks.documentChange.create({
17643
+ kind: 'move',
17644
+ moveRole: role,
17645
+ id: identity.id,
17646
+ actorId: identity.actorId ?? '',
17647
+ author: identity.author,
17648
+ date: identity.date,
17649
+ before: ''
17650
+ })
17651
+ ]));
17652
+ }
17436
17653
  const MAX_COMPARISON_BLOCKS = 1024;
17437
17654
  const MAX_COMPARISON_TEXT = 1000000;
17438
17655
  const MAX_BLOCK_ALIGNMENT_CELLS = 1100000;
@@ -17496,7 +17713,7 @@ function planDocumentCompare(current, revised, options) {
17496
17713
  const limits = comparisonLimits(currentSections, revisedSections);
17497
17714
  if (limits) return unsupportedResult(limits);
17498
17715
  const diagnostics = [];
17499
- const factory = comparisonIdentityFactory(current, revised, options);
17716
+ const factory = createComparisonIdentityFactory(comparisonSemanticSignature(current), comparisonSemanticSignature(revised), options);
17500
17717
  const comparedSections = [];
17501
17718
  for(let sectionIndex = 0; sectionIndex < currentSections.length; sectionIndex += 1){
17502
17719
  const currentSection = currentSections[sectionIndex];
@@ -17565,7 +17782,7 @@ function planDocumentCombine(current, reviewed) {
17565
17782
  return {
17566
17783
  status: 'applied',
17567
17784
  document: document1,
17568
- summary: summarizeChanges(reviewedChanges),
17785
+ summary: summarizeComparisonChanges(reviewedChanges),
17569
17786
  diagnostics: []
17570
17787
  };
17571
17788
  }
@@ -17637,11 +17854,11 @@ function comparePairedBlocks(current, revised, factory) {
17637
17854
  return current.type.create(attributes, inline);
17638
17855
  }
17639
17856
  function compareInlineContent(current, revised, factory) {
17640
- const currentUnits = inlineUnits(current);
17641
- const revisedUnits = inlineUnits(revised);
17857
+ let currentUnits = inlineUnits(current);
17858
+ let revisedUnits = inlineUnits(revised);
17642
17859
  if (!currentUnits || !revisedUnits) return null;
17643
17860
  const diff = boundedDocumentSequenceDiff(currentUnits, revisedUnits, (left, right)=>left.text === right.text, MAX_INLINE_DIFF_CELLS);
17644
- const changes = diff ?? [
17861
+ let changes = diff ?? [
17645
17862
  {
17646
17863
  kind: 'delete',
17647
17864
  left: currentUnits,
@@ -17653,14 +17870,48 @@ function compareInlineContent(current, revised, factory) {
17653
17870
  right: revisedUnits
17654
17871
  }
17655
17872
  ];
17873
+ const baseMovePairs = inferInlineMovePairs(changes, work_document_compare_marksEqual);
17874
+ const moveCurrentUnits = inlineUnits(current, true);
17875
+ const moveRevisedUnits = inlineUnits(revised, true);
17876
+ if (moveCurrentUnits && moveRevisedUnits && current.textContent.length + revised.textContent.length <= 131072) {
17877
+ const moveDiff = boundedDocumentSequenceDiff(moveCurrentUnits, moveRevisedUnits, (left, right)=>left.text === right.text, MAX_INLINE_DIFF_CELLS);
17878
+ if (moveDiff) {
17879
+ const moveChanges = moveDiff;
17880
+ const movePairs = inferInlineMovePairs(moveChanges, work_document_compare_marksEqual);
17881
+ if (movePairs.length >= baseMovePairs.length && movePairs.length > 0) {
17882
+ currentUnits = moveCurrentUnits;
17883
+ revisedUnits = moveRevisedUnits;
17884
+ changes = moveChanges;
17885
+ }
17886
+ }
17887
+ }
17888
+ const movePairs = inferInlineMovePairs(changes, work_document_compare_marksEqual);
17889
+ const movesByStep = new Map();
17890
+ for (const pair of movePairs){
17891
+ const identity = factory.create('move');
17892
+ const deletion = movesByStep.get(pair.deletion.stepIndex) ?? [];
17893
+ deletion.push({
17894
+ role: 'from',
17895
+ candidate: pair.deletion,
17896
+ identity
17897
+ });
17898
+ movesByStep.set(pair.deletion.stepIndex, deletion);
17899
+ const insertion = movesByStep.get(pair.insertion.stepIndex) ?? [];
17900
+ insertion.push({
17901
+ role: 'to',
17902
+ candidate: pair.insertion,
17903
+ identity
17904
+ });
17905
+ movesByStep.set(pair.insertion.stepIndex, insertion);
17906
+ }
17656
17907
  const nodes = [];
17657
- for (const change of changes){
17908
+ for (const [stepIndex, change] of changes.entries()){
17658
17909
  if ('delete' === change.kind) {
17659
- appendRevisionUnits(nodes, change.left, 'deletion', factory.create('deletion'), current.type.schema);
17910
+ appendComparisonRevisionUnits(nodes, change.left, 'deletion', current.type.schema, factory, withoutReviewMarks, work_document_compare_moves_appendRevisionUnits, movesByStep.get(stepIndex));
17660
17911
  continue;
17661
17912
  }
17662
17913
  if ('insert' === change.kind) {
17663
- appendRevisionUnits(nodes, change.right, 'insertion', factory.create('insertion'), current.type.schema);
17914
+ appendComparisonRevisionUnits(nodes, change.right, 'insertion', current.type.schema, factory, withoutReviewMarks, work_document_compare_moves_appendRevisionUnits, movesByStep.get(stepIndex));
17664
17915
  continue;
17665
17916
  }
17666
17917
  let formattingIdentity = null;
@@ -17668,7 +17919,7 @@ function compareInlineContent(current, revised, factory) {
17668
17919
  for(let index = 0; index < change.left.length; index += 1){
17669
17920
  const left = change.left[index];
17670
17921
  const right = change.right[index];
17671
- if (marksEqual(left.marks, right.marks)) {
17922
+ if (work_document_compare_marksEqual(left.marks, right.marks)) {
17672
17923
  nodes.push(current.type.schema.text(left.text, [
17673
17924
  ...left.marks
17674
17925
  ]));
@@ -17692,12 +17943,12 @@ function compareInlineContent(current, revised, factory) {
17692
17943
  }
17693
17944
  const deletion = factory.create('deletion');
17694
17945
  const insertion = factory.create('insertion');
17695
- appendRevisionUnits(nodes, [
17946
+ work_document_compare_moves_appendRevisionUnits(nodes, [
17696
17947
  left
17697
- ], 'deletion', deletion, current.type.schema);
17698
- appendRevisionUnits(nodes, [
17948
+ ], 'deletion', deletion, current.type.schema, withoutReviewMarks);
17949
+ work_document_compare_moves_appendRevisionUnits(nodes, [
17699
17950
  right
17700
- ], 'insertion', insertion, current.type.schema);
17951
+ ], 'insertion', insertion, current.type.schema, withoutReviewMarks);
17701
17952
  formattingIdentity = null;
17702
17953
  formattingSignature = '';
17703
17954
  }
@@ -17712,9 +17963,9 @@ function structuralChangeBlock(block, kind, factory, identityChannel) {
17712
17963
  textId: block.attrs.textId
17713
17964
  };
17714
17965
  const nodes = [];
17715
- for (const unit of inlineUnits(block) ?? [])appendRevisionUnits(nodes, [
17966
+ for (const unit of inlineUnits(block) ?? [])work_document_compare_moves_appendRevisionUnits(nodes, [
17716
17967
  unit
17717
- ], kind, identity, block.type.schema);
17968
+ ], kind, identity, block.type.schema, withoutReviewMarks);
17718
17969
  return block.type.create({
17719
17970
  ...block.attrs,
17720
17971
  ...paragraphIdentity,
@@ -17725,19 +17976,6 @@ function structuralChangeBlock(block, kind, factory, identityChannel) {
17725
17976
  blockChangeDate: identity.date
17726
17977
  }, model_Fragment.fromArray(nodes));
17727
17978
  }
17728
- function appendRevisionUnits(target, units, kind, identity, schema) {
17729
- for (const unit of units)target.push(schema.text(unit.text, [
17730
- ...withoutReviewMarks(unit.marks),
17731
- schema.marks.documentChange.create({
17732
- kind,
17733
- id: identity.id,
17734
- actorId: identity.actorId ?? '',
17735
- author: identity.author,
17736
- date: identity.date,
17737
- before: ''
17738
- })
17739
- ]));
17740
- }
17741
17979
  function comparisonMark(node, kind, identity, before) {
17742
17980
  return node.type.schema.marks.documentChange.create({
17743
17981
  kind,
@@ -17821,19 +18059,35 @@ function hasStructuralBlockRevisions(document1) {
17821
18059
  });
17822
18060
  return found;
17823
18061
  }
17824
- function inlineUnits(node) {
18062
+ function inlineUnits(node, attachLeadingWhitespace = false) {
17825
18063
  if (!isSimpleTextBlock(node)) return null;
17826
18064
  const units = [];
17827
18065
  node.forEach((child)=>{
17828
18066
  const text = child.text ?? '';
17829
- for (const part of text.match(/\s+|[\p{L}\p{N}_]+|[^\s\p{L}\p{N}_]/gu) ?? [])units.push({
17830
- text: part,
17831
- marks: child.marks
18067
+ let pendingWhitespace = '';
18068
+ for (const part of text.match(/\s+|[\p{L}\p{N}_]+|[^\s\p{L}\p{N}_]/gu) ?? []){
18069
+ if (attachLeadingWhitespace && '' === part.trim()) {
18070
+ pendingWhitespace += part;
18071
+ continue;
18072
+ }
18073
+ units.push({
18074
+ text: attachLeadingWhitespace ? `${pendingWhitespace}${part}` : part,
18075
+ marks: child.marks,
18076
+ ...attachLeadingWhitespace ? {
18077
+ leadingWhitespaceAttached: true
18078
+ } : {}
18079
+ });
18080
+ pendingWhitespace = '';
18081
+ }
18082
+ if (attachLeadingWhitespace && pendingWhitespace) units.push({
18083
+ text: pendingWhitespace,
18084
+ marks: child.marks,
18085
+ leadingWhitespaceAttached: true
17832
18086
  });
17833
18087
  });
17834
18088
  return units;
17835
18089
  }
17836
- function marksEqual(left, right) {
18090
+ function work_document_compare_marksEqual(left, right) {
17837
18091
  const normalizedLeft = withoutReviewMarks(left);
17838
18092
  const normalizedRight = withoutReviewMarks(right);
17839
18093
  return normalizedLeft.length === normalizedRight.length && normalizedLeft.every((mark, index)=>mark.eq(normalizedRight[index]));
@@ -17919,42 +18173,6 @@ function transferCombineIdentities(current, reviewed) {
17919
18173
  if ('documentSection' === reviewed.type.name) attributes.id = current.attrs.id;
17920
18174
  return reviewed.isText ? reviewed : reviewed.type.create(attributes, model_Fragment.fromArray(content), reviewed.marks);
17921
18175
  }
17922
- function comparisonIdentityFactory(current, revised, options) {
17923
- const seed = stableHash(`${options.mode}\u0000${comparisonSemanticSignature(current)}\u0000${comparisonSemanticSignature(revised)}`);
17924
- const author = boundedMetadata(options.author, 256) || 'A3S Work user';
17925
- const date = normalizedDate(options.date);
17926
- const summary = emptyComparisonSummary();
17927
- let sequence = 0;
17928
- return {
17929
- summary,
17930
- create (kind) {
17931
- sequence += 1;
17932
- if ('insertion' === kind) summary.insertions += 1;
17933
- else if ('deletion' === kind) summary.deletions += 1;
17934
- else if ('formatting' === kind) summary.formatting += 1;
17935
- else summary.paragraphFormatting += 1;
17936
- return {
17937
- id: `compare-${seed}-${kind}-${sequence.toString(36)}`,
17938
- author,
17939
- date
17940
- };
17941
- },
17942
- paragraphIdentity (channel) {
17943
- return {
17944
- paragraphId: wordParagraphId(`${seed}:${channel}:paragraph`),
17945
- textId: wordParagraphId(`${seed}:${channel}:text`)
17946
- };
17947
- }
17948
- };
17949
- }
17950
- function summarizeChanges(changes) {
17951
- const summary = emptyComparisonSummary();
17952
- for (const change of changes)if ('insertion' === change.kind) summary.insertions += 1;
17953
- else if ('deletion' === change.kind) summary.deletions += 1;
17954
- else if ('formatting' === change.kind) summary.formatting += 1;
17955
- else summary.paragraphFormatting += 1;
17956
- return summary;
17957
- }
17958
18176
  function structuralBlockDiagnostic(block, section, blockIndex) {
17959
18177
  return 'paragraph' === block.type.name || 'heading' === block.type.name ? {
17960
18178
  code: 'empty-structural-change',
@@ -17977,14 +18195,6 @@ function unsupportedResult(diagnostic) {
17977
18195
  ]
17978
18196
  };
17979
18197
  }
17980
- function emptyComparisonSummary() {
17981
- return {
17982
- deletions: 0,
17983
- formatting: 0,
17984
- insertions: 0,
17985
- paragraphFormatting: 0
17986
- };
17987
- }
17988
18198
  function childNodes(node) {
17989
18199
  const children = [];
17990
18200
  node.forEach((child)=>{
@@ -18184,7 +18394,7 @@ function DocumentCompareDialog({ initialMode, restoreFocusTarget, onApplied, onC
18184
18394
  }
18185
18395
  }),
18186
18396
  /*#__PURE__*/ jsx("small", {
18187
- children: "该名称会显示在生成的插入、删除与格式修订中。"
18397
+ children: "该名称会显示在生成的插入、删除、移动与格式修订中。"
18188
18398
  })
18189
18399
  ]
18190
18400
  }),
@@ -18222,7 +18432,7 @@ function ComparisonBoundary({ mode }) {
18222
18432
  children: 'compare' === mode ? '确定性比较边界' : '安全合并边界'
18223
18433
  }),
18224
18434
  /*#__PURE__*/ jsx("p", {
18225
- children: 'compare' === mode ? '支持同一分节布局中的段落、标题、文字和格式差异;复杂对象或节布局变化会明确停止。' : '审阅副本必须包含修订,且拒绝全部修订后与当前文档一致;现有修订须先处理。'
18435
+ children: 'compare' === mode ? '支持同一分节布局中的段落、标题、文字、格式差异,以及同一段内可安全识别的文本移动;复杂对象或节布局变化会明确停止。' : '审阅副本必须包含修订,且拒绝全部修订后与当前文档一致;现有修订须先处理。'
18226
18436
  })
18227
18437
  ]
18228
18438
  });
@@ -18293,7 +18503,7 @@ function useDocumentComparison({ editor, onApplied }) {
18293
18503
  }
18294
18504
  };
18295
18505
  const applied = (result)=>{
18296
- const count = result.summary.insertions + result.summary.deletions + result.summary.formatting + result.summary.paragraphFormatting;
18506
+ const count = result.summary.insertions + result.summary.deletions + result.summary.formatting + result.summary.paragraphFormatting + (result.summary.moves ?? 0);
18297
18507
  close();
18298
18508
  onApplied();
18299
18509
  showToast(`已生成 ${count} 项可审阅修订`, 'success');
@@ -19872,7 +20082,10 @@ function createDocumentPaginationRunCoordinator({ cancelFrame, onAbort, onCoales
19872
20082
  };
19873
20083
  }
19874
20084
  const MAX_DOCUMENT_BLOCK_RESIZE_OBSERVATIONS = 4096;
19875
- function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, page, selectionVersion, wasmUrl, layoutFonts, loadedLayoutFontIds }) {
20085
+ function neverComposing() {
20086
+ return false;
20087
+ }
20088
+ function useDocumentPagination({ compositionRevision = 0, editor, documentRevision, enabled, isComposing = neverComposing, layoutKey, page, selectionVersion, wasmUrl, layoutFonts, loadedLayoutFontIds }) {
19876
20089
  const client = useOfficeKernelClient(wasmUrl, layoutFonts);
19877
20090
  const editorMounted = useEditorMounted(editor);
19878
20091
  const revision = useRef(0);
@@ -20011,6 +20224,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20011
20224
  for (const element of observedElements)observer?.observe(element);
20012
20225
  };
20013
20226
  const run = async (signal)=>{
20227
+ if (isComposing()) return void measurementRange.ensureDirty();
20014
20228
  stopObservingBlocks();
20015
20229
  const nextRevision = ++revision.current;
20016
20230
  editor.commands.clearDocumentPagination(nextRevision);
@@ -20040,7 +20254,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20040
20254
  documentRevision,
20041
20255
  paragraphs
20042
20256
  }, signal);
20043
- if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed) return;
20257
+ if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed || isComposing()) return;
20044
20258
  textLayoutEngine ||= textLayout.engine;
20045
20259
  unsupportedTextLayoutCount += textLayout.unsupportedParagraphIds.length + textLayout.layouts.filter((layout)=>layout.missingGlyphCount > 0).length;
20046
20260
  for (const layout of textLayout.layouts)if (0 === layout.missingGlyphCount) {
@@ -20051,7 +20265,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20051
20265
  editorDom.dataset.paginationTextEngine = textLayoutEngine;
20052
20266
  editorDom.dataset.paginationUnsupportedText = String(unsupportedTextLayoutCount);
20053
20267
  } catch (error) {
20054
- if (signal.aborted || error instanceof DOMException && 'AbortError' === error.name) return;
20268
+ if (signal.aborted || isComposing() || error instanceof DOMException && 'AbortError' === error.name) return;
20055
20269
  textLayouts.clear();
20056
20270
  fallbackGlyphCount = 0;
20057
20271
  editorDom.dataset.paginationTextEngine = 'dom';
@@ -20069,6 +20283,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20069
20283
  const snapshot = await measureDocumentLayoutBlocksIncrementally(editor, measurementCache.current, measurementStart, textLayouts, 1000000, {
20070
20284
  signal
20071
20285
  });
20286
+ if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed || isComposing()) return;
20072
20287
  measurementCache.current = snapshot;
20073
20288
  editorDom.dataset.paginationBlocks = String(snapshot.blocks.length);
20074
20289
  editorDom.dataset.paginationFlows = String(new Set(snapshot.blocks.flatMap((block)=>block.block.flowId ? [
@@ -20098,7 +20313,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20098
20313
  pageStyles: snapshot.pageStyles,
20099
20314
  blocks: layoutPlan.blocks
20100
20315
  }, signal);
20101
- if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed) return;
20316
+ if (disposed || signal.aborted || nextRevision !== revision.current || editor.isDestroyed || isComposing()) return;
20102
20317
  const layout = previousPagination && layoutPlan.startPageIndex > 0 ? mergeIncrementalDocumentLayout(previousPagination.layout, partialLayout) : partialLayout;
20103
20318
  const blockById = new Map(snapshot.blocks.map((block)=>[
20104
20319
  block.block.id,
@@ -20147,7 +20362,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20147
20362
  measurementRange.commit(measurementPass);
20148
20363
  setPagination(nextPagination);
20149
20364
  } catch (error) {
20150
- if (disposed || signal.aborted || error instanceof DOMException && 'AbortError' === error.name) return;
20365
+ if (disposed || signal.aborted || isComposing() || error instanceof DOMException && 'AbortError' === error.name) return;
20151
20366
  editorDom.dataset.paginationState = 'error';
20152
20367
  editorDom.dataset.paginationError = error instanceof Error ? `${error.name}: ${error.message}` : 'UnknownError';
20153
20368
  editor.commands.clearDocumentPagination(nextRevision);
@@ -20167,7 +20382,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20167
20382
  onCoalescedRequest: ()=>updateDiagnostic('coalescedRequests', 'paginationCoalescedRequests'),
20168
20383
  onError: (error)=>{
20169
20384
  measurementRange.restoreActive();
20170
- if (disposed || editor.isDestroyed) return;
20385
+ if (disposed || editor.isDestroyed || isComposing()) return;
20171
20386
  editorDom.dataset.paginationState = 'error';
20172
20387
  editorDom.dataset.paginationError = error instanceof Error ? `${error.name}: ${error.message}` : 'UnknownError';
20173
20388
  paginationCache.current = null;
@@ -20184,6 +20399,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20184
20399
  run
20185
20400
  });
20186
20401
  const schedule = (invalidateActive = false)=>{
20402
+ if (isComposing()) return;
20187
20403
  coordinator.request({
20188
20404
  invalidateActive
20189
20405
  });
@@ -20200,6 +20416,15 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20200
20416
  markDirty(earliestChangedPosition(transaction));
20201
20417
  schedule(true);
20202
20418
  };
20419
+ const handleCompositionStart = ()=>{
20420
+ measurementRange.ensureDirty();
20421
+ coordinator.request({
20422
+ invalidateActive: true
20423
+ });
20424
+ };
20425
+ const handleCompositionEnd = ()=>{
20426
+ schedule(true);
20427
+ };
20203
20428
  const handleLoadedAsset = (event)=>{
20204
20429
  updateDiagnostic('assetTriggers', 'paginationAssetTriggers');
20205
20430
  const target = event.target;
@@ -20219,6 +20444,8 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20219
20444
  };
20220
20445
  const fonts = document.fonts;
20221
20446
  editor.on('update', handleDocumentUpdate);
20447
+ editorDom.addEventListener('compositionstart', handleCompositionStart);
20448
+ editorDom.addEventListener('compositionend', handleCompositionEnd);
20222
20449
  editorDom.addEventListener('load', handleLoadedAsset, true);
20223
20450
  fonts?.addEventListener('loadingdone', handleFontLoading);
20224
20451
  window.addEventListener('resize', handleWindowResize);
@@ -20227,6 +20454,8 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20227
20454
  disposed = true;
20228
20455
  stopObservingBlocks();
20229
20456
  editor.off('update', handleDocumentUpdate);
20457
+ editorDom.removeEventListener('compositionstart', handleCompositionStart);
20458
+ editorDom.removeEventListener('compositionend', handleCompositionEnd);
20230
20459
  editorDom.removeEventListener('load', handleLoadedAsset, true);
20231
20460
  fonts?.removeEventListener('loadingdone', handleFontLoading);
20232
20461
  window.removeEventListener('resize', handleWindowResize);
@@ -20234,6 +20463,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20234
20463
  };
20235
20464
  }, [
20236
20465
  client,
20466
+ compositionRevision,
20237
20467
  documentRevision,
20238
20468
  editor,
20239
20469
  editorMounted,
@@ -20243,6 +20473,7 @@ function useDocumentPagination({ editor, documentRevision, enabled, layoutKey, p
20243
20473
  layoutFonts,
20244
20474
  loadedLayoutFontKey,
20245
20475
  loadedLayoutFontIds,
20476
+ isComposing,
20246
20477
  pageKey
20247
20478
  ]);
20248
20479
  const resolveFieldContext = useMemo(()=>pagination ? createDocumentFieldPaginationContextResolver(pagination) : null, [
@@ -20929,12 +21160,24 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
20929
21160
  },
20930
21161
  handleDOMEvents: {
20931
21162
  compositionstart: ()=>{
20932
- composition.start();
21163
+ composition.start(editorRef.current);
20933
21164
  return false;
20934
21165
  },
20935
- compositionend: ()=>{
21166
+ compositionupdate: ()=>{
21167
+ composition.start(editorRef.current);
21168
+ return false;
21169
+ },
21170
+ beforeinput: (_view, event)=>{
21171
+ composition.noteInput(event);
21172
+ return false;
21173
+ },
21174
+ input: (_view, event)=>{
21175
+ composition.noteInput(event);
21176
+ return false;
21177
+ },
21178
+ compositionend: (_view, event)=>{
20936
21179
  const current = editorRef.current;
20937
- if (current) composition.end(current, (settled)=>settleCompositionRef.current(settled));
21180
+ if (current) composition.end(current, event.data ?? null, (settled, snapshot)=>settleCompositionRef.current(settled, snapshot));
20938
21181
  return false;
20939
21182
  }
20940
21183
  }
@@ -21010,7 +21253,20 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
21010
21253
  publishDocumentUpdate,
21011
21254
  queueLazyDocumentPublication
21012
21255
  ]);
21013
- settleCompositionRef.current = (current)=>{
21256
+ settleCompositionRef.current = (current, snapshot)=>{
21257
+ if (null !== snapshot.text && snapshot.range && !current.isDestroyed) {
21258
+ const maximum = current.state.doc.content.size;
21259
+ const from = Math.max(0, Math.min(maximum, snapshot.range.from));
21260
+ const to = Math.max(0, Math.min(maximum, snapshot.range.to));
21261
+ const start = Math.min(from, to);
21262
+ const end = Math.max(from, to);
21263
+ const currentText = current.state.doc.textBetween(start, end, '\n', '\n');
21264
+ if (currentText !== snapshot.text) {
21265
+ if (trackChangesRef.current) current.commands.replaceDocumentTextWithTrackedChange(start, end, snapshot.text);
21266
+ else current.view.dispatch(current.state.tr.insertText(snapshot.text, start, end).setMeta('composition', snapshot.id).setMeta('uiEvent', 'input'));
21267
+ current.view.dom.dataset.documentCompositionNormalized = 'true';
21268
+ }
21269
+ }
21014
21270
  const pending = pendingLazyPublicationRef.current;
21015
21271
  if (pending) {
21016
21272
  pending.cancel();
@@ -21065,6 +21321,8 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
21065
21321
  composition.destroy();
21066
21322
  },
21067
21323
  onTransaction: ({ appendedTransactions, editor: current, transaction })=>{
21324
+ composition.recordTransaction(transaction);
21325
+ for (const appended of appendedTransactions ?? [])composition.recordTransaction(appended);
21068
21326
  if (documentTransactionsOnlyHydrateChunks([
21069
21327
  transaction,
21070
21328
  ...appendedTransactions
@@ -21429,10 +21687,16 @@ function DocumentEditorSurface({ artifactId, autoFocus = true, collaboration, co
21429
21687
  JSON.stringify(layout.pageMargins),
21430
21688
  layout.pageSize
21431
21689
  ]);
21690
+ const documentCompositionIsActive = useCallback(()=>composition.isBlocking(editor), [
21691
+ composition,
21692
+ editor
21693
+ ]);
21432
21694
  const pagination = useDocumentPagination({
21695
+ compositionRevision,
21433
21696
  editor,
21434
21697
  documentRevision: editorInput.revision,
21435
21698
  enabled: Boolean(editor && 'page' === viewMode),
21699
+ isComposing: documentCompositionIsActive,
21436
21700
  layoutKey: [
21437
21701
  layout.breakAfter,
21438
21702
  layout.columns.count,