@a3s-lab/office 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/8928.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Extension, Mark, Node as core_Node, ResizableNodeView, generateHTML, getSchema, mergeAttributes } from "@tiptap/core";
2
2
  import { Collaboration, isChangeOrigin } from "@tiptap/extension-collaboration";
3
3
  import { NodeSelection, Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
4
- import { AddMarkStep, Mapping, RemoveMarkStep } from "@tiptap/pm/transform";
4
+ import { AddMarkStep, Mapping, RemoveMarkStep, ReplaceStep } from "@tiptap/pm/transform";
5
5
  import { defaultDeleteFilter, defaultProtectedNodes, prosemirrorJSONToYXmlFragment, ySyncPluginKey, yXmlFragmentToProsemirrorJSON } from "@tiptap/y-tiptap";
6
6
  import extension_color from "@tiptap/extension-color";
7
7
  import { Table, TableCell, TableHeader, TableKit, TableRow, TableView, createTable } from "@tiptap/extension-table";
@@ -6697,6 +6697,7 @@ const DocumentParagraphIdentity = Extension.create({
6697
6697
  name: 'documentParagraphIdentity',
6698
6698
  addOptions () {
6699
6699
  return {
6700
+ rotateTextId: ()=>true,
6700
6701
  types: [
6701
6702
  'paragraph',
6702
6703
  'heading',
@@ -6735,7 +6736,7 @@ const DocumentParagraphIdentity = Extension.create({
6735
6736
  },
6736
6737
  addProseMirrorPlugins () {
6737
6738
  return [
6738
- createDocumentParagraphIdentityPlugin(this.options.types)
6739
+ createDocumentParagraphIdentityPlugin(this.options.types, this.options.rotateTextId)
6739
6740
  ];
6740
6741
  }
6741
6742
  });
@@ -6776,11 +6777,11 @@ function applyDocumentParagraphIdentityToElement(element, source) {
6776
6777
  element.setAttribute(DOCUMENT_PARAGRAPH_TEXT_ID_ATTRIBUTE, identity.textId);
6777
6778
  return identity;
6778
6779
  }
6779
- function createDocumentParagraphIdentityPlugin(types) {
6780
+ function createDocumentParagraphIdentityPlugin(types, rotateTextId) {
6780
6781
  const trackedTypes = new Set(types);
6781
6782
  return new Plugin({
6782
6783
  view (view) {
6783
- const transaction = normalizeDocumentParagraphIdentities(view.state, trackedTypes);
6784
+ const transaction = normalizeDocumentParagraphIdentities(view.state, trackedTypes, rotateTextId);
6784
6785
  if (transaction) {
6785
6786
  transaction.setMeta('addToHistory', false);
6786
6787
  view.dispatch(transaction);
@@ -6789,16 +6790,16 @@ function createDocumentParagraphIdentityPlugin(types) {
6789
6790
  },
6790
6791
  appendTransaction (transactions, oldState, newState) {
6791
6792
  if (!transactions.some((transaction)=>transaction.docChanged)) return null;
6792
- return normalizeDocumentParagraphIdentities(newState, trackedTypes, oldState, transactions);
6793
+ return normalizeDocumentParagraphIdentities(newState, trackedTypes, rotateTextId, oldState, transactions);
6793
6794
  }
6794
6795
  });
6795
6796
  }
6796
- function normalizeDocumentParagraphIdentities(state, trackedTypes, oldState, transactions = []) {
6797
+ function normalizeDocumentParagraphIdentities(state, trackedTypes, rotateTextId, oldState, transactions = []) {
6797
6798
  const paragraphs = documentParagraphs(state.doc, trackedTypes);
6798
6799
  const identifiedParagraphs = paragraphs.filter(({ node })=>hasDocumentParagraphIdentityComponent(node));
6799
6800
  if (!identifiedParagraphs.length) return null;
6800
6801
  const retainedPositions = oldState ? retainedDocumentParagraphPositions(oldState, state, transactions, trackedTypes) : new Set();
6801
- const editedPositions = oldState && !transactions.some(isHistoryTransaction) ? editedDocumentParagraphPositions(oldState, state, transactions, trackedTypes) : new Set();
6802
+ const editedPositions = oldState && rotateTextId() && !transactions.some(isHistoryTransaction) ? editedDocumentParagraphPositions(oldState, state, transactions, trackedTypes) : new Set();
6802
6803
  const ordered = [
6803
6804
  ...identifiedParagraphs.filter((item)=>retainedPositions.has(item.position)),
6804
6805
  ...identifiedParagraphs.filter((item)=>!retainedPositions.has(item.position))
@@ -6971,6 +6972,11 @@ const DOCUMENT_TABLE_ROW_ID_ATTRIBUTE = 'data-office-row-id';
6971
6972
  const DOCUMENT_TABLE_ROW_TEXT_ID_ATTRIBUTE = 'data-office-row-text-id';
6972
6973
  const DocumentTableRowIdentity = Extension.create({
6973
6974
  name: 'documentTableRowIdentity',
6975
+ addOptions () {
6976
+ return {
6977
+ rotateTextId: ()=>true
6978
+ };
6979
+ },
6974
6980
  addGlobalAttributes () {
6975
6981
  return [
6976
6982
  {
@@ -6986,7 +6992,7 @@ const DocumentTableRowIdentity = Extension.create({
6986
6992
  },
6987
6993
  addProseMirrorPlugins () {
6988
6994
  return [
6989
- createDocumentTableRowIdentityPlugin()
6995
+ createDocumentTableRowIdentityPlugin(this.options.rotateTextId)
6990
6996
  ];
6991
6997
  }
6992
6998
  });
@@ -7028,10 +7034,10 @@ function identityAttribute(htmlName) {
7028
7034
  }
7029
7035
  };
7030
7036
  }
7031
- function createDocumentTableRowIdentityPlugin() {
7037
+ function createDocumentTableRowIdentityPlugin(rotateTextId) {
7032
7038
  return new Plugin({
7033
7039
  view (view) {
7034
- const transaction = normalizeDocumentTableRowIdentities(view.state);
7040
+ const transaction = normalizeDocumentTableRowIdentities(view.state, rotateTextId);
7035
7041
  if (transaction) {
7036
7042
  transaction.setMeta('addToHistory', false);
7037
7043
  view.dispatch(transaction);
@@ -7040,16 +7046,16 @@ function createDocumentTableRowIdentityPlugin() {
7040
7046
  },
7041
7047
  appendTransaction (transactions, oldState, newState) {
7042
7048
  if (!transactions.some((transaction)=>transaction.docChanged)) return null;
7043
- return normalizeDocumentTableRowIdentities(newState, oldState, transactions);
7049
+ return normalizeDocumentTableRowIdentities(newState, rotateTextId, oldState, transactions);
7044
7050
  }
7045
7051
  });
7046
7052
  }
7047
- function normalizeDocumentTableRowIdentities(state, oldState, transactions = []) {
7053
+ function normalizeDocumentTableRowIdentities(state, rotateTextId, oldState, transactions = []) {
7048
7054
  const rows = documentTableRows(state.doc);
7049
7055
  const identifiedRows = rows.filter(({ node })=>hasDocumentTableRowIdentityComponent(node));
7050
7056
  if (!identifiedRows.length) return null;
7051
7057
  const retainedPositions = oldState ? retainedDocumentTableRowPositions(oldState, state, transactions) : new Set();
7052
- const editedPositions = oldState && !transactions.some(isHistoryTransaction) ? editedDocumentTableRowPositions(oldState, state, transactions) : new Set();
7058
+ const editedPositions = oldState && rotateTextId() && !transactions.some(isHistoryTransaction) ? editedDocumentTableRowPositions(oldState, state, transactions) : new Set();
7053
7059
  const ordered = [
7054
7060
  ...identifiedRows.filter((item)=>retainedPositions.has(item.position)),
7055
7061
  ...identifiedRows.filter((item)=>!retainedPositions.has(item.position))
@@ -8410,6 +8416,13 @@ const DocumentChange = Mark.create({
8410
8416
  'data-change-id': attributes.id
8411
8417
  })
8412
8418
  },
8419
+ actorId: {
8420
+ default: '',
8421
+ parseHTML: (element)=>element.getAttribute('data-change-actor-id') ?? '',
8422
+ renderHTML: (attributes)=>attributes.actorId ? {
8423
+ 'data-change-actor-id': attributes.actorId
8424
+ } : {}
8425
+ },
8413
8426
  author: {
8414
8427
  default: '',
8415
8428
  parseHTML: (element)=>element.getAttribute('data-change-author') ?? '',
@@ -8451,10 +8464,12 @@ const DocumentChange = Mark.create({
8451
8464
  acceptDocumentChange: (id)=>(props)=>resolveDocumentChangesCommand(props, this.type, 'accept', new Set([
8452
8465
  id
8453
8466
  ])) > 0,
8467
+ acceptDocumentChanges: (ids)=>(props)=>resolveDocumentChangesCommand(props, this.type, 'accept', new Set(ids)) > 0,
8454
8468
  rejectAllDocumentChanges: ()=>(props)=>resolveDocumentChangesCommand(props, this.type, 'reject') > 0,
8455
8469
  rejectDocumentChange: (id)=>(props)=>resolveDocumentChangesCommand(props, this.type, 'reject', new Set([
8456
8470
  id
8457
8471
  ])) > 0,
8472
+ rejectDocumentChanges: (ids)=>(props)=>resolveDocumentChangesCommand(props, this.type, 'reject', new Set(ids)) > 0,
8458
8473
  replaceDocumentTextWithTrackedChange: (from, to, text)=>({ state, tr })=>{
8459
8474
  if (from < 0 || to < from || to > state.doc.content.size) return false;
8460
8475
  trackedReplacement(tr, state.doc, this.type, from, to, text, this.options.createChange);
@@ -8537,6 +8552,9 @@ function collectDocumentChanges(document1) {
8537
8552
  changes.set(key, {
8538
8553
  id,
8539
8554
  kind,
8555
+ ...work_document_changes_stringAttribute(mark.attrs.actorId) ? {
8556
+ actorId: work_document_changes_stringAttribute(mark.attrs.actorId)
8557
+ } : {},
8540
8558
  author: work_document_changes_stringAttribute(mark.attrs.author) || '未知审阅者',
8541
8559
  date: work_document_changes_stringAttribute(mark.attrs.date),
8542
8560
  from: position,
@@ -8640,6 +8658,7 @@ function changeMark(type, kind, createChange) {
8640
8658
  return type.create({
8641
8659
  kind,
8642
8660
  id: identity.id || createDocumentChangeId(),
8661
+ actorId: identity.actorId ?? '',
8643
8662
  author: identity.author || 'A3S Work',
8644
8663
  date: identity.date || new Date().toISOString()
8645
8664
  });
@@ -15597,7 +15616,9 @@ function createWorkDocumentExtensions(options = {}) {
15597
15616
  DocumentTableCell,
15598
15617
  DocumentTableHeader,
15599
15618
  DocumentTableRow,
15600
- DocumentTableRowIdentity,
15619
+ DocumentTableRowIdentity.configure({
15620
+ rotateTextId: options.rotateTrackedTextIdentities ?? (()=>true)
15621
+ }),
15601
15622
  DocumentTableCommands,
15602
15623
  DocumentTableFormatting,
15603
15624
  DocumentTableSizing,
@@ -15614,7 +15635,9 @@ function createWorkDocumentExtensions(options = {}) {
15614
15635
  'paragraph'
15615
15636
  ]
15616
15637
  }),
15617
- DocumentParagraphIdentity,
15638
+ DocumentParagraphIdentity.configure({
15639
+ rotateTextId: options.rotateTrackedTextIdentities ?? (()=>true)
15640
+ }),
15618
15641
  DocumentParagraphFormatting,
15619
15642
  DocumentParagraphTabStops,
15620
15643
  DocumentTab,
@@ -15864,6 +15887,142 @@ function deniedImmutableRecord(label) {
15864
15887
  function deniedReviewMutation(session) {
15865
15888
  throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `The '${session.mode}' collaboration mode cannot modify Document review records.`);
15866
15889
  }
15890
+ function validatedWorkOfficeDocumentChangeDecisions(value) {
15891
+ if (!Array.isArray(value)) invalidInputSidecars('an array of tracked-change decisions');
15892
+ const ids = new Set();
15893
+ const changes = new Set();
15894
+ return value.map((candidate)=>{
15895
+ const decision = validatedDecision(candidate);
15896
+ if (ids.has(decision.id)) invalidInputSidecars(`a unique tracked-change decision ID; '${decision.id}' is repeated`);
15897
+ const change = changeIdentity(decision);
15898
+ if (changes.has(change)) invalidInputSidecars(`one final decision for tracked change '${decision.changeId}'`);
15899
+ ids.add(decision.id);
15900
+ changes.add(change);
15901
+ return decision;
15902
+ });
15903
+ }
15904
+ function initializeWorkOfficeDocumentChangeDecisions(records, order, decisions) {
15905
+ for (const decision of decisions){
15906
+ createDecisionRecord(records, decision);
15907
+ order.push([
15908
+ decision.id
15909
+ ]);
15910
+ }
15911
+ }
15912
+ function readWorkOfficeDocumentChangeDecisions(records, order) {
15913
+ const decisions = validatedOrder(order, records, 'tracked-change decision').map((id)=>readDecisionRecord(requiredSharedMap(records, id, 'tracked-change decision'), id));
15914
+ const changes = new Set();
15915
+ for (const decision of decisions){
15916
+ const identity = changeIdentity(decision);
15917
+ if (changes.has(identity)) invalidSharedSidecars(`multiple final decisions for tracked change '${decision.changeId}'`);
15918
+ changes.add(identity);
15919
+ }
15920
+ return decisions;
15921
+ }
15922
+ function patchWorkOfficeDocumentChangeDecisions(records, order, previous, next) {
15923
+ const beforeById = new Map(previous.map((decision)=>[
15924
+ decision.id,
15925
+ decision
15926
+ ]));
15927
+ for (const decision of previous){
15928
+ const candidate = next.find(({ id })=>id === decision.id);
15929
+ if (!candidate || !jsonEqual(candidate, decision)) immutableDecision(decision.id);
15930
+ }
15931
+ for (const decision of next)if (!beforeById.has(decision.id)) {
15932
+ if (!records.has(decision.id)) createDecisionRecord(records, decision);
15933
+ insertIntoOrder(order, next.map(({ id })=>id), decision.id);
15934
+ }
15935
+ }
15936
+ function assertWorkOfficeDocumentChangeDecisionConflicts(previous, next, shared) {
15937
+ assertNoAddedCollision(previous, next, shared, 'tracked-change decision');
15938
+ const nextById = new Map(next.map((decision)=>[
15939
+ decision.id,
15940
+ decision
15941
+ ]));
15942
+ for (const decision of previous)if (!jsonEqual(nextById.get(decision.id), decision)) immutableDecision(decision.id);
15943
+ const sharedByChange = new Map(shared.map((decision)=>[
15944
+ changeIdentity(decision),
15945
+ decision
15946
+ ]));
15947
+ const previousIds = new Set(previous.map(({ id })=>id));
15948
+ for (const decision of next){
15949
+ if (previousIds.has(decision.id)) continue;
15950
+ const current = sharedByChange.get(changeIdentity(decision));
15951
+ if (current && !jsonEqual(current, decision)) throw new WorkOfficeCollaborationError('office.collaboration.content_invalid', `Tracked change '${decision.changeId}' already has a different final decision.`);
15952
+ }
15953
+ }
15954
+ function validatedDecision(value) {
15955
+ if (!office_document_collaboration_sidecar_utils_isRecord(value)) invalidInputSidecars('valid tracked-change decision records');
15956
+ const decision = {
15957
+ id: requiredIdentifier(value.id, 'tracked-change decision'),
15958
+ changeId: requiredIdentifier(value.changeId, 'tracked change'),
15959
+ changeKind: office_document_collaboration_change_decisions_changeKind(value.changeKind, false),
15960
+ suggestedBy: requiredString(value.suggestedBy, 'suggestion author'),
15961
+ suggestedAt: requiredString(value.suggestedAt, 'suggestion date'),
15962
+ text: requiredString(value.text, 'suggestion text'),
15963
+ decision: decisionAction(value.decision, false),
15964
+ decidedBy: requiredString(value.decidedBy, 'decision author'),
15965
+ decidedAt: requiredString(value.decidedAt, 'decision date')
15966
+ };
15967
+ if (void 0 !== value.suggestedByActorId) decision.suggestedByActorId = requiredIdentifier(value.suggestedByActorId, 'suggestion actor');
15968
+ if (void 0 !== value.decidedByActorId) decision.decidedByActorId = requiredIdentifier(value.decidedByActorId, 'decision actor');
15969
+ return decision;
15970
+ }
15971
+ function createDecisionRecord(records, decision) {
15972
+ const record = new __rspack_external_yjs.Map();
15973
+ records.set(decision.id, record);
15974
+ for (const [key, value] of Object.entries(decision))record.set(key, value);
15975
+ }
15976
+ function readDecisionRecord(record, expectedId) {
15977
+ const id = requiredSharedIdentifier(record.get('id'), 'tracked-change decision');
15978
+ if (id !== expectedId) invalidSharedSidecars('tracked-change decision identity');
15979
+ const allowed = new Set([
15980
+ 'id',
15981
+ 'changeId',
15982
+ 'changeKind',
15983
+ 'suggestedByActorId',
15984
+ 'suggestedBy',
15985
+ 'suggestedAt',
15986
+ 'text',
15987
+ 'decision',
15988
+ 'decidedByActorId',
15989
+ 'decidedBy',
15990
+ 'decidedAt'
15991
+ ]);
15992
+ if (Array.from(record.keys()).some((key)=>!allowed.has(key))) invalidSharedSidecars('tracked-change decision fields');
15993
+ const decision = {
15994
+ id,
15995
+ changeId: requiredSharedIdentifier(record.get('changeId'), 'tracked change'),
15996
+ changeKind: office_document_collaboration_change_decisions_changeKind(record.get('changeKind'), true),
15997
+ suggestedBy: requiredSharedString(record.get('suggestedBy'), 'suggestion author'),
15998
+ suggestedAt: requiredSharedString(record.get('suggestedAt'), 'suggestion date'),
15999
+ text: requiredSharedString(record.get('text'), 'suggestion text'),
16000
+ decision: decisionAction(record.get('decision'), true),
16001
+ decidedBy: requiredSharedString(record.get('decidedBy'), 'decision author'),
16002
+ decidedAt: requiredSharedString(record.get('decidedAt'), 'decision date')
16003
+ };
16004
+ const suggestedByActorId = record.get('suggestedByActorId');
16005
+ if (void 0 !== suggestedByActorId) decision.suggestedByActorId = requiredSharedIdentifier(suggestedByActorId, 'suggestion actor');
16006
+ const decidedByActorId = record.get('decidedByActorId');
16007
+ if (void 0 !== decidedByActorId) decision.decidedByActorId = requiredSharedIdentifier(decidedByActorId, 'decision actor');
16008
+ return decision;
16009
+ }
16010
+ function changeIdentity(decision) {
16011
+ return `${decision.changeKind}:${decision.changeId}`;
16012
+ }
16013
+ function office_document_collaboration_change_decisions_changeKind(value, shared) {
16014
+ if ('insertion' === value || 'deletion' === value) return value;
16015
+ if (shared) invalidSharedSidecars('tracked-change decision kind');
16016
+ invalidInputSidecars('an insertion or deletion tracked-change kind');
16017
+ }
16018
+ function decisionAction(value, shared) {
16019
+ if ('accept' === value || 'reject' === value) return value;
16020
+ if (shared) invalidSharedSidecars('tracked-change decision action');
16021
+ invalidInputSidecars('an accept or reject tracked-change decision');
16022
+ }
16023
+ function immutableDecision(id) {
16024
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `Tracked-change decision '${id}' is immutable and cannot be rewritten or removed.`);
16025
+ }
15867
16026
  function validatedWorkOfficeDocumentBibliography(value) {
15868
16027
  if (!office_document_collaboration_sidecar_utils_isRecord(value) || !isCitationStyle(value.style)) invalidInputSidecars('a supported bibliography style');
15869
16028
  if (!Array.isArray(value.sources)) invalidInputSidecars('an array of bibliography sources');
@@ -16216,6 +16375,8 @@ function optionalSharedIdentifier(value, label) {
16216
16375
  return void 0 === value ? void 0 : requiredSharedIdentifier(value, label);
16217
16376
  }
16218
16377
  function appendWorkOfficeDocumentRecordClaims(claims, previous, next) {
16378
+ const previousDecisions = new Set((previous.changeDecisions ?? []).map((decision)=>decision.id));
16379
+ for (const decision of next.changeDecisions ?? [])if (!previousDecisions.has(decision.id)) appendClaim(claims, 'change-decision', decision.id, decision);
16219
16380
  const previousComments = new Map((previous.comments ?? []).map((comment)=>[
16220
16381
  comment.id,
16221
16382
  comment
@@ -16238,6 +16399,7 @@ function assertWorkOfficeDocumentRecordClaims(claims, sidecars) {
16238
16399
  if (void 0 !== existing && existing !== claim.fingerprint) throw new WorkOfficeCollaborationError('office.collaboration.content_invalid', `The ${claimLabel(claim)} ID '${claim.id}' was concurrently assigned to different records.`);
16239
16400
  fingerprints.set(identity, claim.fingerprint);
16240
16401
  }
16402
+ for (const decision of sidecars.changeDecisions ?? [])assertClaimExists(fingerprints, 'change-decision', decision.id);
16241
16403
  for (const comment of sidecars.comments ?? []){
16242
16404
  assertClaimExists(fingerprints, 'comment', comment.id);
16243
16405
  for (const reply of comment.replies ?? [])assertClaimExists(fingerprints, 'comment-reply', reply.id, comment.id);
@@ -16269,7 +16431,7 @@ function parsedClaim(rawClaim) {
16269
16431
  const keys = Object.keys(value);
16270
16432
  if (keys.some((key)=>'fingerprint' !== key && 'id' !== key && 'kind' !== key && 'parentId' !== key)) invalidSharedSidecars('record claim');
16271
16433
  const kind = value.kind;
16272
- if ('bibliography-source' !== kind && 'comment' !== kind && 'comment-reply' !== kind) invalidSharedSidecars('record claim kind');
16434
+ if ('bibliography-source' !== kind && 'change-decision' !== kind && 'comment' !== kind && 'comment-reply' !== kind) invalidSharedSidecars('record claim kind');
16273
16435
  const id = requiredSharedIdentifier(value.id, 'record claim');
16274
16436
  const fingerprint = requiredSharedString(value.fingerprint, 'record claim fingerprint');
16275
16437
  const parentId = void 0 === value.parentId ? void 0 : requiredSharedIdentifier(value.parentId, 'record claim parent');
@@ -16302,6 +16464,7 @@ function claimIdentity(kind, id, parentId) {
16302
16464
  ]);
16303
16465
  }
16304
16466
  function claimLabel(claim) {
16467
+ if ('change-decision' === claim.kind) return 'tracked-change decision';
16305
16468
  if ('comment' === claim.kind) return 'comment';
16306
16469
  if ('bibliography-source' === claim.kind) return 'bibliography source';
16307
16470
  return `reply in comment '${claim.parentId}'`;
@@ -16309,13 +16472,15 @@ function claimLabel(claim) {
16309
16472
  const DOCUMENT_OPTIONS_ROOT = 'document.options';
16310
16473
  const DOCUMENT_COMMENTS_ROOT = 'document.comments';
16311
16474
  const DOCUMENT_COMMENT_ORDER_ROOT = 'document.comment-order';
16475
+ const DOCUMENT_CHANGE_DECISIONS_ROOT = 'document.change-decisions';
16476
+ const DOCUMENT_CHANGE_DECISION_ORDER_ROOT = 'document.change-decision-order';
16312
16477
  const DOCUMENT_BIBLIOGRAPHY_ROOT = 'document.bibliography';
16313
16478
  const DOCUMENT_BIBLIOGRAPHY_SOURCES_ROOT = 'document.bibliography.sources';
16314
16479
  const DOCUMENT_BIBLIOGRAPHY_SOURCE_ORDER_ROOT = 'document.bibliography.source-order';
16315
16480
  const DOCUMENT_RECORD_CLAIMS_ROOT = 'document.record-claims';
16316
16481
  function assertWorkOfficeDocumentSidecarsEmpty(session) {
16317
16482
  const roots = documentSidecarRoots(session);
16318
- if (roots.options.size > 0 || roots.comments.size > 0 || roots.commentOrder.length > 0 || roots.bibliography.size > 0 || roots.bibliographySources.size > 0 || roots.bibliographySourceOrder.length > 0 || roots.recordClaims.length > 0) throw new WorkOfficeCollaborationError('office.collaboration.bootstrap_ambiguous', 'The Document collaboration sidecars contain data without initialized metadata.');
16483
+ if (roots.options.size > 0 || roots.comments.size > 0 || roots.commentOrder.length > 0 || roots.changeDecisions.size > 0 || roots.changeDecisionOrder.length > 0 || roots.bibliography.size > 0 || roots.bibliographySources.size > 0 || roots.bibliographySourceOrder.length > 0 || roots.recordClaims.length > 0) throw new WorkOfficeCollaborationError('office.collaboration.bootstrap_ambiguous', 'The Document collaboration sidecars contain data without initialized metadata.');
16319
16484
  }
16320
16485
  function initializeWorkOfficeDocumentSidecars(session, content) {
16321
16486
  const roots = documentSidecarRoots(session);
@@ -16323,6 +16488,7 @@ function initializeWorkOfficeDocumentSidecars(session, content) {
16323
16488
  appendWorkOfficeDocumentRecordClaims(roots.recordClaims, {}, sidecars);
16324
16489
  if (void 0 !== sidecars.pageColor) roots.options.set('pageColor', sidecars.pageColor);
16325
16490
  if (void 0 !== sidecars.trackChanges) roots.options.set('trackChanges', sidecars.trackChanges);
16491
+ if (void 0 !== sidecars.changeDecisions) initializeWorkOfficeDocumentChangeDecisions(roots.changeDecisions, roots.changeDecisionOrder, sidecars.changeDecisions);
16326
16492
  if (void 0 !== sidecars.comments) {
16327
16493
  roots.options.set('commentsPresent', true);
16328
16494
  initializeWorkOfficeDocumentComments(roots.comments, roots.commentOrder, sidecars.comments);
@@ -16345,6 +16511,8 @@ function readWorkOfficeDocumentSidecars(session) {
16345
16511
  if ('boolean' != typeof trackChanges) invalidSharedSidecars('track-changes setting');
16346
16512
  result.trackChanges = trackChanges;
16347
16513
  }
16514
+ if (roots.changeDecisionOrder.length > 0) result.changeDecisions = readWorkOfficeDocumentChangeDecisions(roots.changeDecisions, roots.changeDecisionOrder);
16515
+ else if (roots.changeDecisions.size > 0) invalidSharedSidecars('tracked-change decision order and record set');
16348
16516
  const commentsPresent = optionalPresence(roots.options.get('commentsPresent'), 'comment');
16349
16517
  if (true === commentsPresent || roots.commentOrder.length > 0) result.comments = readWorkOfficeDocumentComments(roots.comments, roots.commentOrder);
16350
16518
  else if (roots.comments.size > 0) invalidSharedSidecars('comment order and record set');
@@ -16362,11 +16530,13 @@ function updateWorkOfficeDocumentSidecars(session, previous, next, origin) {
16362
16530
  const roots = documentSidecarRoots(session);
16363
16531
  const shared = readWorkOfficeDocumentSidecars(session);
16364
16532
  assertWorkOfficeDocumentCommentConflicts(before.comments ?? [], after.comments ?? [], shared.comments ?? []);
16533
+ assertWorkOfficeDocumentChangeDecisionConflicts(before.changeDecisions ?? [], after.changeDecisions ?? [], shared.changeDecisions ?? []);
16365
16534
  assertWorkOfficeDocumentBibliographyConflicts(before.bibliography, after.bibliography, shared.bibliography);
16366
16535
  session.transact(()=>{
16367
16536
  appendWorkOfficeDocumentRecordClaims(roots.recordClaims, before, after);
16368
16537
  patchOptionalScalar(roots.options, 'pageColor', before.pageColor, after.pageColor);
16369
16538
  patchOptionalScalar(roots.options, 'trackChanges', before.trackChanges, after.trackChanges);
16539
+ if (!jsonEqual(before.changeDecisions, after.changeDecisions)) patchWorkOfficeDocumentChangeDecisions(roots.changeDecisions, roots.changeDecisionOrder, before.changeDecisions ?? [], after.changeDecisions ?? []);
16370
16540
  if (!jsonEqual(before.comments, after.comments)) {
16371
16541
  patchPresence(roots.options, 'commentsPresent', before.comments, after.comments);
16372
16542
  patchWorkOfficeDocumentComments(roots.comments, roots.commentOrder, before.comments ?? [], after.comments ?? []);
@@ -16390,9 +16560,19 @@ function workOfficeDocumentSidecarUndoScope(session) {
16390
16560
  roots.recordClaims
16391
16561
  ];
16392
16562
  }
16563
+ function workOfficeDocumentDecisionRootsChanged(session, transaction) {
16564
+ const roots = documentSidecarRoots(session);
16565
+ const changed = new Set(transaction.changedParentTypes.keys());
16566
+ return changed.has(roots.changeDecisions) || changed.has(roots.changeDecisionOrder);
16567
+ }
16393
16568
  function workOfficeDocumentSidecarsChanged(session, transaction) {
16394
16569
  const changed = new Set(transaction.changedParentTypes.keys());
16395
- return workOfficeDocumentSidecarUndoScope(session).some((root)=>changed.has(root));
16570
+ const roots = documentSidecarRoots(session);
16571
+ return [
16572
+ ...workOfficeDocumentSidecarUndoScope(session),
16573
+ roots.changeDecisions,
16574
+ roots.changeDecisionOrder
16575
+ ].some((root)=>changed.has(root));
16396
16576
  }
16397
16577
  function validatedWorkOfficeDocumentSidecars(content) {
16398
16578
  if (!content || 'document' !== content.type) invalidInputSidecars('a Document content value');
@@ -16405,6 +16585,7 @@ function validatedWorkOfficeDocumentSidecars(content) {
16405
16585
  if ('boolean' != typeof content.trackChanges) invalidInputSidecars('a boolean track-changes setting');
16406
16586
  result.trackChanges = content.trackChanges;
16407
16587
  }
16588
+ if (void 0 !== content.changeDecisions) result.changeDecisions = validatedWorkOfficeDocumentChangeDecisions(content.changeDecisions);
16408
16589
  if (void 0 !== content.comments) result.comments = validatedWorkOfficeDocumentComments(content.comments);
16409
16590
  if (void 0 !== content.bibliography) result.bibliography = validatedWorkOfficeDocumentBibliography(content.bibliography);
16410
16591
  return result;
@@ -16412,7 +16593,7 @@ function validatedWorkOfficeDocumentSidecars(content) {
16412
16593
  function assertDocumentSidecarMutationAllowed(session, previous, next) {
16413
16594
  if ('edit' === session.mode) return;
16414
16595
  if ('comment' !== session.mode) return void assertWorkOfficeCollaborationEditable(session);
16415
- if (!jsonEqual(previous.pageColor, next.pageColor) || !jsonEqual(previous.trackChanges, next.trackChanges) || !jsonEqual(previous.bibliography, next.bibliography)) throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', 'The comment collaboration mode can modify only Document review records.');
16596
+ if (!jsonEqual(previous.pageColor, next.pageColor) || !jsonEqual(previous.trackChanges, next.trackChanges) || !jsonEqual(previous.changeDecisions, next.changeDecisions) || !jsonEqual(previous.bibliography, next.bibliography)) throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', 'The comment collaboration mode can modify only Document review records.');
16416
16597
  assertWorkOfficeDocumentCommentMutationAllowed(session, previous.comments ?? [], next.comments ?? []);
16417
16598
  }
16418
16599
  function documentSidecarRoots(session) {
@@ -16420,6 +16601,8 @@ function documentSidecarRoots(session) {
16420
16601
  options: session.document.getMap(session.rootName(DOCUMENT_OPTIONS_ROOT)),
16421
16602
  comments: session.document.getMap(session.rootName(DOCUMENT_COMMENTS_ROOT)),
16422
16603
  commentOrder: session.document.getArray(session.rootName(DOCUMENT_COMMENT_ORDER_ROOT)),
16604
+ changeDecisions: session.document.getMap(session.rootName(DOCUMENT_CHANGE_DECISIONS_ROOT)),
16605
+ changeDecisionOrder: session.document.getArray(session.rootName(DOCUMENT_CHANGE_DECISION_ORDER_ROOT)),
16423
16606
  bibliography: session.document.getMap(session.rootName(DOCUMENT_BIBLIOGRAPHY_ROOT)),
16424
16607
  bibliographySources: session.document.getMap(session.rootName(DOCUMENT_BIBLIOGRAPHY_SOURCES_ROOT)),
16425
16608
  bibliographySourceOrder: session.document.getArray(session.rootName(DOCUMENT_BIBLIOGRAPHY_SOURCE_ORDER_ROOT)),
@@ -16436,6 +16619,152 @@ function patchPresence(target, key, previous, next, absentValue) {
16436
16619
  else if (void 0 !== previous) if (false === absentValue) target.set(key, false);
16437
16620
  else target.delete(key);
16438
16621
  }
16622
+ function workOfficeDocumentSuggestionTransactionAllowed(session, transaction) {
16623
+ const actor = session.actor;
16624
+ if (!actor || !transaction.docChanged || 0 === transaction.steps.length) return false;
16625
+ if (!transaction.steps.every((step)=>step instanceof ReplaceStep || (step instanceof AddMarkStep || step instanceof RemoveMarkStep) && 'documentChange' === step.mark.type.name)) return false;
16626
+ const before = strictDocumentChanges(transaction.before);
16627
+ const after = strictDocumentChanges(transaction.doc);
16628
+ if (!before || !after) return false;
16629
+ if (JSON.stringify(documentSuggestionBaseline(transaction.before)) !== JSON.stringify(documentSuggestionBaseline(transaction.doc))) return false;
16630
+ const beforeById = new Map(before.map((change)=>[
16631
+ change.id,
16632
+ change
16633
+ ]));
16634
+ const afterById = new Map(after.map((change)=>[
16635
+ change.id,
16636
+ change
16637
+ ]));
16638
+ for (const previous of before){
16639
+ const current = afterById.get(previous.id);
16640
+ if (!current) {
16641
+ if (previous.actorId !== actor.id) return false;
16642
+ continue;
16643
+ }
16644
+ if (!sameChangeIdentity(previous, current)) return false;
16645
+ if (previous.actorId !== actor.id && previous.text !== current.text) return false;
16646
+ if (previous.actorId === actor.id && 'deletion' === previous.kind && previous.text !== current.text) return false;
16647
+ }
16648
+ for (const current of after)if (!beforeById.has(current.id)) {
16649
+ if (current.actorId !== actor.id || current.author !== actor.name || !current.date || !current.text) return false;
16650
+ }
16651
+ return true;
16652
+ }
16653
+ function createWorkOfficeDocumentChangeDecisions(session, changes, decision, decidedAt = new Date().toISOString()) {
16654
+ const decidedBy = session.actor?.name ?? 'A3S Work 用户';
16655
+ return changes.map((change)=>({
16656
+ id: `${change.kind}:${change.id}`,
16657
+ changeId: change.id,
16658
+ changeKind: change.kind,
16659
+ ...change.actorId ? {
16660
+ suggestedByActorId: change.actorId
16661
+ } : {},
16662
+ suggestedBy: change.author,
16663
+ suggestedAt: change.date,
16664
+ text: change.text,
16665
+ decision,
16666
+ ...session.actor ? {
16667
+ decidedByActorId: session.actor.id
16668
+ } : {},
16669
+ decidedBy,
16670
+ decidedAt
16671
+ }));
16672
+ }
16673
+ function strictDocumentChanges(document1) {
16674
+ const changes = new Map();
16675
+ let valid = true;
16676
+ document1.descendants((node)=>{
16677
+ if (!valid || !node.isText || !node.text) return;
16678
+ const marks = node.marks.filter((mark)=>'documentChange' === mark.type.name);
16679
+ if (0 === marks.length) return;
16680
+ if (1 !== marks.length) {
16681
+ valid = false;
16682
+ return false;
16683
+ }
16684
+ const mark = marks[0];
16685
+ const id = strictString(mark.attrs.id);
16686
+ const kind = strictChangeKind(mark.attrs.kind);
16687
+ const author = strictString(mark.attrs.author);
16688
+ const date = strictString(mark.attrs.date);
16689
+ const actorId = optionalStrictString(mark.attrs.actorId);
16690
+ if (!id || !kind || null === author || null === date || null === actorId) {
16691
+ valid = false;
16692
+ return false;
16693
+ }
16694
+ const current = changes.get(id);
16695
+ const candidate = {
16696
+ id,
16697
+ kind,
16698
+ ...actorId ? {
16699
+ actorId
16700
+ } : {},
16701
+ author,
16702
+ date,
16703
+ text: node.text
16704
+ };
16705
+ if (!current) return void changes.set(id, candidate);
16706
+ if (!sameChangeIdentity(current, candidate)) {
16707
+ valid = false;
16708
+ return false;
16709
+ }
16710
+ current.text += node.text;
16711
+ });
16712
+ return valid ? Array.from(changes.values()) : null;
16713
+ }
16714
+ function documentSuggestionBaseline(node) {
16715
+ const json = node.toJSON();
16716
+ if (node.isText) {
16717
+ const change = node.marks.find((mark)=>'documentChange' === mark.type.name);
16718
+ if (change?.attrs.kind === 'insertion') return null;
16719
+ const marks = node.marks.filter((mark)=>'documentChange' !== mark.type.name).map((mark)=>mark.toJSON());
16720
+ if (marks.length > 0) return {
16721
+ ...json,
16722
+ marks
16723
+ };
16724
+ const { marks: _marks, ...withoutMarks } = json;
16725
+ return withoutMarks;
16726
+ }
16727
+ const content = [];
16728
+ node.forEach((child)=>{
16729
+ const projected = documentSuggestionBaseline(child);
16730
+ if (null !== projected) appendSuggestionBaselineNode(content, projected);
16731
+ });
16732
+ if (content.length > 0) return {
16733
+ ...json,
16734
+ content
16735
+ };
16736
+ const { content: _content, ...withoutContent } = json;
16737
+ return withoutContent;
16738
+ }
16739
+ function appendSuggestionBaselineNode(content, projected) {
16740
+ const previous = content.at(-1);
16741
+ if (isSuggestionBaselineText(previous) && isSuggestionBaselineText(projected) && JSON.stringify(previous.marks ?? []) === JSON.stringify(projected.marks ?? [])) {
16742
+ content[content.length - 1] = {
16743
+ ...previous,
16744
+ text: previous.text + projected.text
16745
+ };
16746
+ return;
16747
+ }
16748
+ content.push(projected);
16749
+ }
16750
+ function isSuggestionBaselineText(value) {
16751
+ if (!value || 'object' != typeof value) return false;
16752
+ const candidate = value;
16753
+ return 'text' === candidate.type && 'string' == typeof candidate.text;
16754
+ }
16755
+ function sameChangeIdentity(left, right) {
16756
+ return left.id === right.id && left.kind === right.kind && left.actorId === right.actorId && left.author === right.author && left.date === right.date;
16757
+ }
16758
+ function strictString(value) {
16759
+ return 'string' == typeof value && value.trim() === value && value ? value : null;
16760
+ }
16761
+ function optionalStrictString(value) {
16762
+ if (void 0 === value || '' === value) return;
16763
+ return strictString(value);
16764
+ }
16765
+ function strictChangeKind(value) {
16766
+ return 'insertion' === value || 'deletion' === value ? value : null;
16767
+ }
16439
16768
  const DOCUMENT_CONTENT_ROOT = 'document.content';
16440
16769
  const MAX_DOCUMENT_COMMENT_HISTORY = 100;
16441
16770
  const mountedDocumentBindings = new WeakMap();
@@ -16518,7 +16847,8 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16518
16847
  assertWorkOfficeCollaborationOrigin(this.origin);
16519
16848
  const builtIns = createWorkDocumentExtensions({
16520
16849
  ...options.workExtensions,
16521
- collaborative: true
16850
+ collaborative: true,
16851
+ rotateTrackedTextIdentities: ()=>'suggest' !== session.mode
16522
16852
  });
16523
16853
  const additional = options.additionalExtensions ?? [];
16524
16854
  assertBehaviorOnlyExtensions(additional);
@@ -16541,7 +16871,7 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16541
16871
  ]));
16542
16872
  const existing = mountedDocumentBindings.get(session.document);
16543
16873
  if (existing) throw new WorkOfficeCollaborationError('office.collaboration.content_invalid', 'Only one Document collaboration binding may use a local Y.Doc at a time. Give each editor client its own synchronized Y.Doc.');
16544
- const undoScope = 'comment' === session.mode ? [
16874
+ const undoScope = 'comment' === session.mode || 'suggest' === session.mode ? [
16545
16875
  this.fragment
16546
16876
  ] : [
16547
16877
  this.fragment,
@@ -16559,7 +16889,7 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16559
16889
  const restore = restorable.restore?.bind(this.#undoManager);
16560
16890
  if (restore) restorable.restore = ()=>{
16561
16891
  restore();
16562
- this.#undoManager.addToScope('comment' === this.#session.mode ? this.fragment : [
16892
+ this.#undoManager.addToScope('comment' === this.#session.mode || 'suggest' === this.#session.mode ? this.fragment : [
16563
16893
  this.fragment,
16564
16894
  ...workOfficeDocumentSidecarUndoScope(this.#session)
16565
16895
  ]);
@@ -16602,6 +16932,36 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16602
16932
  }
16603
16933
  return changed;
16604
16934
  }
16935
+ decideChanges(editor, changeIds, decision, options = {}) {
16936
+ this.ensureActive();
16937
+ assertWorkOfficeCollaborationEditable(this.#session);
16938
+ if (editor.isDestroyed || 0 === changeIds.length) return false;
16939
+ const requested = new Set(changeIds);
16940
+ const changes = collectDocumentChanges(editor.state.doc).filter((change)=>requested.has(change.id));
16941
+ if (changes.length !== requested.size) return false;
16942
+ const before = this.content();
16943
+ const decisions = createWorkOfficeDocumentChangeDecisions(this.#session, changes, decision, options.decidedAt);
16944
+ const existing = new Set((before.changeDecisions ?? []).map(({ id })=>id));
16945
+ if (decisions.some(({ id })=>existing.has(id))) throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', 'A tracked change with a final decision cannot be decided again.');
16946
+ const origin = this.#session.createOrigin(this.#session.localOrigin.kind);
16947
+ let handled = false;
16948
+ this.#session.transact(()=>{
16949
+ handled = 'accept' === decision ? editor.commands.acceptDocumentChanges(changeIds) : editor.commands.rejectDocumentChanges(changeIds);
16950
+ if (!handled) return;
16951
+ updateWorkOfficeDocumentSidecars(this.#session, before, {
16952
+ ...before,
16953
+ changeDecisions: [
16954
+ ...before.changeDecisions ?? [],
16955
+ ...decisions
16956
+ ]
16957
+ }, origin);
16958
+ }, origin);
16959
+ if (handled) {
16960
+ this.#undoManager.clear();
16961
+ this.#onHistoryChange();
16962
+ }
16963
+ return handled;
16964
+ }
16605
16965
  canUndo() {
16606
16966
  this.ensureActive();
16607
16967
  if ('comment' === this.#session.mode) return this.#commentUndoStack.length > 0 || this.#undoManager.canUndo();
@@ -16618,6 +16978,10 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16618
16978
  assertWorkOfficeCollaborationWritable(this.#session, 'document-comment');
16619
16979
  return this.applyCommentHistory('undo');
16620
16980
  }
16981
+ if ('suggest' === this.#session.mode) {
16982
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-suggestion');
16983
+ return null !== this.#undoManager.undo();
16984
+ }
16621
16985
  assertWorkOfficeCollaborationEditable(this.#session);
16622
16986
  return null !== this.#undoManager.undo();
16623
16987
  }
@@ -16627,6 +16991,10 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16627
16991
  assertWorkOfficeCollaborationWritable(this.#session, 'document-comment');
16628
16992
  return this.applyCommentHistory('redo');
16629
16993
  }
16994
+ if ('suggest' === this.#session.mode) {
16995
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-suggestion');
16996
+ return null !== this.#undoManager.redo();
16997
+ }
16630
16998
  assertWorkOfficeCollaborationEditable(this.#session);
16631
16999
  return null !== this.#undoManager.redo();
16632
17000
  }
@@ -16669,6 +17037,10 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16669
17037
  if (transaction.origin === ySyncPluginKey) transaction.origin = this.origin;
16670
17038
  };
16671
17039
  #onTransaction = (transaction)=>{
17040
+ if ('suggest' === this.#session.mode && workOfficeDocumentDecisionRootsChanged(this.#session, transaction)) {
17041
+ this.#undoManager.clear();
17042
+ this.#onHistoryChange();
17043
+ }
16672
17044
  if (this.#destroyed || this.#destroyRequested || !transactionTouchesRoot(transaction, this.fragment) && !workOfficeDocumentSidecarsChanged(this.#session, transaction)) return;
16673
17045
  const local = transaction.origin === ySyncPluginKey || transaction.origin === this.origin || transaction.origin === this.#undoManager;
16674
17046
  this.#pendingLocal = this.#pendingChange ? this.#pendingLocal && local : local;
@@ -16944,7 +17316,7 @@ function documentCollaborationPermissionExtension(session) {
16944
17316
  return [
16945
17317
  new Plugin({
16946
17318
  filterTransaction (transaction) {
16947
- return 'edit' === session.mode || !transaction.docChanged || isChangeOrigin(transaction) || 'comment' === session.mode && transaction.steps.length > 0 && transaction.steps.every((step)=>(step instanceof AddMarkStep || step instanceof RemoveMarkStep) && 'documentComment' === step.mark.type.name);
17319
+ return 'edit' === session.mode || !transaction.docChanged || isChangeOrigin(transaction) || 'comment' === session.mode && transaction.steps.length > 0 && transaction.steps.every((step)=>(step instanceof AddMarkStep || step instanceof RemoveMarkStep) && 'documentComment' === step.mark.type.name) || 'suggest' === session.mode && workOfficeDocumentSuggestionTransactionAllowed(session, transaction);
16948
17320
  }
16949
17321
  })
16950
17322
  ];