@a3s-lab/office 0.7.2 → 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.
Files changed (28) hide show
  1. package/COLLABORATION_ROADMAP.md +81 -26
  2. package/README.md +48 -4
  3. package/dist/0~document-editor.js +414 -247
  4. package/dist/8928.js +689 -49
  5. package/dist/9787.js +14 -4
  6. package/dist/core.d.ts +2 -2
  7. package/dist/internal/collaboration/office-collaboration.d.ts +4 -2
  8. package/dist/internal/collaboration/office-document-collaboration-change-decisions.d.ts +7 -0
  9. package/dist/internal/collaboration/office-document-collaboration-claims.d.ts +2 -1
  10. package/dist/internal/collaboration/office-document-collaboration-comment-permissions.d.ts +3 -0
  11. package/dist/internal/collaboration/office-document-collaboration-sidecars.d.ts +3 -1
  12. package/dist/internal/collaboration/office-document-collaboration-suggestions.d.ts +6 -0
  13. package/dist/internal/collaboration/office-document-collaboration.d.ts +6 -2
  14. package/dist/internal/features/work/editors/document-changes-panel.d.ts +7 -1
  15. package/dist/internal/features/work/editors/document-comment-composer.d.ts +1 -0
  16. package/dist/internal/features/work/editors/document-comments-panel.d.ts +3 -1
  17. package/dist/internal/features/work/editors/document-selection-toolbar.d.ts +2 -1
  18. package/dist/internal/features/work/editors/document-toolbar.d.ts +5 -1
  19. package/dist/internal/features/work/editors/use-document-comments.d.ts +6 -1
  20. package/dist/internal/features/work/work-document-changes.d.ts +5 -2
  21. package/dist/internal/features/work/work-document-comments.d.ts +5 -1
  22. package/dist/internal/features/work/work-document-extensions.d.ts +1 -0
  23. package/dist/internal/features/work/work-document-paragraph-identity.d.ts +1 -0
  24. package/dist/internal/features/work/work-document-table-row-identity.d.ts +5 -1
  25. package/dist/internal/features/work/work-types.d.ts +22 -0
  26. package/dist/office-kernel.wasm +0 -0
  27. package/dist/styles.css +76 -0
  28. package/package.json +12 -3
package/dist/8928.js CHANGED
@@ -1,6 +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, ReplaceStep } from "@tiptap/pm/transform";
4
5
  import { defaultDeleteFilter, defaultProtectedNodes, prosemirrorJSONToYXmlFragment, ySyncPluginKey, yXmlFragmentToProsemirrorJSON } from "@tiptap/y-tiptap";
5
6
  import extension_color from "@tiptap/extension-color";
6
7
  import { Table, TableCell, TableHeader, TableKit, TableRow, TableView, createTable } from "@tiptap/extension-table";
@@ -8,7 +9,6 @@ import extension_text_align from "@tiptap/extension-text-align";
8
9
  import font_size from "@tiptap/extension-text-style/font-size";
9
10
  import extension_underline from "@tiptap/extension-underline";
10
11
  import starter_kit from "@tiptap/starter-kit";
11
- import { Mapping } from "@tiptap/pm/transform";
12
12
  import { DOMParser as model_DOMParser, Fragment, Slice } from "@tiptap/pm/model";
13
13
  import jszip from "jszip";
14
14
  import { closeHistory, isHistoryTransaction } from "@tiptap/pm/history";
@@ -23,7 +23,7 @@ import { TextStyle } from "@tiptap/extension-text-style";
23
23
  import extension_highlight from "@tiptap/extension-highlight";
24
24
  import { createOfficeId as createWorkId, parseXml, xmlNamespacePrefix } from "./5184.js";
25
25
  import { workOfficeCollaborationJsonEqual, canonicalWorkOfficeCollaborationJson, isWorkOfficeCollaborationRecord, cloneWorkOfficeCollaborationJson } from "./4650.js";
26
- import { initializeWorkOfficeCollaborationMetadata, assertWorkOfficeCollaborationEditable, OfficeCollaborationError as WorkOfficeCollaborationError, readOfficeCollaborationMetadata as readWorkOfficeCollaborationMetadata, registerWorkOfficeCollaborationInitializer, markWorkOfficeCollaborationInitialized, assertWorkOfficeCollaborationOrigin } from "./9787.js";
26
+ import { assertWorkOfficeCollaborationEditable, markWorkOfficeCollaborationInitialized, assertWorkOfficeCollaborationOrigin, initializeWorkOfficeCollaborationMetadata, assertWorkOfficeCollaborationWritable, OfficeCollaborationError as WorkOfficeCollaborationError, readOfficeCollaborationMetadata as readWorkOfficeCollaborationMetadata, registerWorkOfficeCollaborationInitializer } from "./9787.js";
27
27
  import * as __rspack_external_yjs from "yjs";
28
28
  const BOOKMARK_REFERENCE_SELECTOR = 'span[data-document-cross-reference][data-reference-target-type="bookmark"]';
29
29
  const BLOCK_TEXT_CONTAINER_NAMES = new Set([
@@ -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
  });
@@ -9169,20 +9188,35 @@ function collectDocumentCommentAnchors(document1) {
9169
9188
  return Array.from(anchors.values()).sort((left, right)=>left.from - right.from);
9170
9189
  }
9171
9190
  function documentCommentViews(comments, anchors) {
9172
- const byId = new Map(comments.map((comment)=>[
9173
- comment.id,
9174
- normalizeDocumentComment(comment)
9191
+ const anchorsById = new Map(anchors.map((anchor)=>[
9192
+ anchor.id,
9193
+ anchor
9175
9194
  ]));
9176
- return anchors.map((anchor)=>({
9177
- ...byId.get(anchor.id) ?? {
9178
- id: anchor.id,
9179
- author: '未知审阅者',
9180
- date: '',
9181
- text: '此批注的内容不可用。',
9182
- resolved: false
9183
- },
9184
- ...anchor
9185
- }));
9195
+ const commentIds = new Set(comments.map((comment)=>comment.id));
9196
+ const views = comments.map((comment)=>{
9197
+ const normalized = normalizeDocumentComment(comment);
9198
+ const anchor = anchorsById.get(comment.id);
9199
+ return anchor ? {
9200
+ ...normalized,
9201
+ ...anchor,
9202
+ detached: false
9203
+ } : {
9204
+ ...normalized,
9205
+ from: null,
9206
+ to: null,
9207
+ anchorText: '',
9208
+ detached: true
9209
+ };
9210
+ });
9211
+ for (const anchor of anchors)if (!commentIds.has(anchor.id)) views.push({
9212
+ author: '未知审阅者',
9213
+ date: '',
9214
+ text: '此批注的内容不可用。',
9215
+ resolved: false,
9216
+ ...anchor,
9217
+ detached: false
9218
+ });
9219
+ return views;
9186
9220
  }
9187
9221
  function retainAnchoredDocumentComments(comments, anchors, retainedIds = new Set()) {
9188
9222
  const ids = new Set(anchors.map((anchor)=>anchor.id));
@@ -9259,19 +9293,25 @@ function documentCommentMark(marks) {
9259
9293
  return marks.find((mark)=>'documentComment' === mark.type.name);
9260
9294
  }
9261
9295
  function normalizeDocumentComment(comment) {
9262
- return {
9296
+ const normalized = {
9263
9297
  id: comment.id,
9264
9298
  author: comment.author || '未知审阅者',
9265
9299
  date: comment.date || '',
9266
9300
  text: comment.text || '(空批注)',
9267
- resolved: Boolean(comment.resolved),
9268
- replies: comment.replies?.map((reply)=>({
9269
- id: reply.id,
9270
- author: reply.author || '未知审阅者',
9271
- date: reply.date || '',
9272
- text: reply.text || '(空回复)'
9273
- }))
9301
+ resolved: Boolean(comment.resolved)
9274
9302
  };
9303
+ if (void 0 !== comment.actorId) normalized.actorId = comment.actorId;
9304
+ if (void 0 !== comment.replies) normalized.replies = comment.replies.map((reply)=>{
9305
+ const normalizedReply = {
9306
+ id: reply.id,
9307
+ author: reply.author || '未知审阅者',
9308
+ date: reply.date || '',
9309
+ text: reply.text || '(空回复)'
9310
+ };
9311
+ if (void 0 !== reply.actorId) normalizedReply.actorId = reply.actorId;
9312
+ return normalizedReply;
9313
+ });
9314
+ return normalized;
9275
9315
  }
9276
9316
  function work_document_comments_stringAttribute(value) {
9277
9317
  return 'string' == typeof value ? value : '';
@@ -15576,7 +15616,9 @@ function createWorkDocumentExtensions(options = {}) {
15576
15616
  DocumentTableCell,
15577
15617
  DocumentTableHeader,
15578
15618
  DocumentTableRow,
15579
- DocumentTableRowIdentity,
15619
+ DocumentTableRowIdentity.configure({
15620
+ rotateTextId: options.rotateTrackedTextIdentities ?? (()=>true)
15621
+ }),
15580
15622
  DocumentTableCommands,
15581
15623
  DocumentTableFormatting,
15582
15624
  DocumentTableSizing,
@@ -15593,7 +15635,9 @@ function createWorkDocumentExtensions(options = {}) {
15593
15635
  'paragraph'
15594
15636
  ]
15595
15637
  }),
15596
- DocumentParagraphIdentity,
15638
+ DocumentParagraphIdentity.configure({
15639
+ rotateTextId: options.rotateTrackedTextIdentities ?? (()=>true)
15640
+ }),
15597
15641
  DocumentParagraphFormatting,
15598
15642
  DocumentParagraphTabStops,
15599
15643
  DocumentTab,
@@ -15767,6 +15811,218 @@ function invalidSharedSidecars(label) {
15767
15811
  function office_document_collaboration_sidecar_utils_isRecord(value) {
15768
15812
  return isWorkOfficeCollaborationRecord(value);
15769
15813
  }
15814
+ function assertWorkOfficeDocumentCommentMutationAllowed(session, previous, next) {
15815
+ if ('edit' === session.mode) return;
15816
+ if ('comment' !== session.mode) deniedReviewMutation(session);
15817
+ const actor = requiredReviewActor(session);
15818
+ assertRetainedOrder(previous, next, 'comments');
15819
+ const nextById = new Map(next.map((comment)=>[
15820
+ comment.id,
15821
+ comment
15822
+ ]));
15823
+ const previousIds = new Set(previous.map((comment)=>comment.id));
15824
+ for (const comment of previous){
15825
+ const updated = nextById.get(comment.id);
15826
+ if (!updated) {
15827
+ assertOwnedRecord(comment.actorId, actor, 'own comment');
15828
+ continue;
15829
+ }
15830
+ assertExistingCommentMutation(actor, comment, updated);
15831
+ }
15832
+ for (const comment of next)if (!previousIds.has(comment.id)) assertNewComment(actor, comment);
15833
+ }
15834
+ function assertExistingCommentMutation(actor, previous, next) {
15835
+ if (previous.id !== next.id || previous.actorId !== next.actorId || previous.author !== next.author || previous.date !== next.date || previous.text !== next.text) deniedImmutableRecord('comment');
15836
+ assertReplyMutations(actor, previous.id, previous.replies ?? [], next.replies ?? []);
15837
+ }
15838
+ function assertReplyMutations(actor, commentId, previous, next) {
15839
+ assertRetainedOrder(previous, next, `replies in comment '${commentId}'`);
15840
+ const nextById = new Map(next.map((reply)=>[
15841
+ reply.id,
15842
+ reply
15843
+ ]));
15844
+ const previousIds = new Set(previous.map((reply)=>reply.id));
15845
+ for (const reply of previous){
15846
+ const updated = nextById.get(reply.id);
15847
+ if (!updated) {
15848
+ assertOwnedRecord(reply.actorId, actor, 'own comment reply');
15849
+ continue;
15850
+ }
15851
+ if (!jsonEqual(reply, updated)) deniedImmutableRecord('comment reply');
15852
+ }
15853
+ for (const reply of next)if (!previousIds.has(reply.id)) assertActorRecord(actor, reply, 'reply');
15854
+ }
15855
+ function assertNewComment(actor, comment) {
15856
+ assertActorRecord(actor, comment, 'comment');
15857
+ for (const reply of comment.replies ?? [])assertActorRecord(actor, reply, 'reply');
15858
+ }
15859
+ function assertActorRecord(actor, record, label) {
15860
+ if (record.actorId === actor.id && record.author === actor.name) return;
15861
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `A Document ${label} created in comment mode must use collaboration actor '${actor.id}' and its current display name.`);
15862
+ }
15863
+ function assertOwnedRecord(actorId, actor, label) {
15864
+ if (actorId === actor.id) return;
15865
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `Comment mode can delete only the collaboration actor's ${label}.`);
15866
+ }
15867
+ function assertRetainedOrder(previous, next, label) {
15868
+ const previousIds = new Set(previous.map((record)=>record.id));
15869
+ const nextIds = new Set(next.map((record)=>record.id));
15870
+ const retainedBefore = previous.filter((record)=>nextIds.has(record.id)).map((record)=>record.id);
15871
+ const retainedAfter = next.filter((record)=>previousIds.has(record.id)).map((record)=>record.id);
15872
+ let encounteredNewRecord = false;
15873
+ let existingAfterNewRecord = false;
15874
+ for (const record of next)if (previousIds.has(record.id)) {
15875
+ if (encounteredNewRecord) existingAfterNewRecord = true;
15876
+ } else encounteredNewRecord = true;
15877
+ if (jsonEqual(retainedBefore, retainedAfter) && !existingAfterNewRecord) return;
15878
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `Comment mode must preserve existing ${label} order and append new review records.`);
15879
+ }
15880
+ function requiredReviewActor(session) {
15881
+ if (session.actor) return session.actor;
15882
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', 'Comment mode requires a collaboration actor before it can write review records.');
15883
+ }
15884
+ function deniedImmutableRecord(label) {
15885
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `Comment mode cannot rewrite an existing ${label}; it may append replies or change resolution state.`);
15886
+ }
15887
+ function deniedReviewMutation(session) {
15888
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `The '${session.mode}' collaboration mode cannot modify Document review records.`);
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
+ }
15770
16026
  function validatedWorkOfficeDocumentBibliography(value) {
15771
16027
  if (!office_document_collaboration_sidecar_utils_isRecord(value) || !isCitationStyle(value.style)) invalidInputSidecars('a supported bibliography style');
15772
16028
  if (!Array.isArray(value.sources)) invalidInputSidecars('an array of bibliography sources');
@@ -15995,6 +16251,7 @@ function validatedComment(value) {
15995
16251
  text: requiredString(value.text, 'comment text'),
15996
16252
  resolved: requiredBoolean(value.resolved, 'comment resolution')
15997
16253
  };
16254
+ if (void 0 !== value.actorId) comment.actorId = requiredIdentifier(value.actorId, 'comment actor');
15998
16255
  if (void 0 !== value.replies) {
15999
16256
  if (!Array.isArray(value.replies)) invalidInputSidecars('an array of comment replies');
16000
16257
  const ids = new Set();
@@ -16009,17 +16266,20 @@ function validatedComment(value) {
16009
16266
  }
16010
16267
  function validatedReply(value) {
16011
16268
  if (!office_document_collaboration_sidecar_utils_isRecord(value)) invalidInputSidecars('valid comment reply records');
16012
- return {
16269
+ const reply = {
16013
16270
  id: requiredIdentifier(value.id, 'comment reply'),
16014
16271
  author: requiredString(value.author, 'comment reply author'),
16015
16272
  date: requiredString(value.date, 'comment reply date'),
16016
16273
  text: requiredString(value.text, 'comment reply text')
16017
16274
  };
16275
+ if (void 0 !== value.actorId) reply.actorId = requiredIdentifier(value.actorId, 'comment reply actor');
16276
+ return reply;
16018
16277
  }
16019
16278
  function createCommentRecord(records, comment) {
16020
16279
  const record = new __rspack_external_yjs.Map();
16021
16280
  records.set(comment.id, record);
16022
16281
  record.set('id', comment.id);
16282
+ if (void 0 !== comment.actorId) record.set('actorId', comment.actorId);
16023
16283
  record.set('author', comment.author);
16024
16284
  record.set('date', comment.date);
16025
16285
  record.set('text', comment.text);
@@ -16039,6 +16299,7 @@ function createReplyRecord(records, reply) {
16039
16299
  const record = new __rspack_external_yjs.Map();
16040
16300
  records.set(reply.id, record);
16041
16301
  record.set('id', reply.id);
16302
+ if (void 0 !== reply.actorId) record.set('actorId', reply.actorId);
16042
16303
  record.set('author', reply.author);
16043
16304
  record.set('date', reply.date);
16044
16305
  record.set('text', reply.text);
@@ -16056,20 +16317,26 @@ function readCommentRecord(record, expectedId) {
16056
16317
  text: requiredSharedString(record.get('text'), 'comment text'),
16057
16318
  resolved: requiredSharedBoolean(record.get('resolved'), 'comment resolution')
16058
16319
  };
16320
+ const actorId = optionalSharedIdentifier(record.get('actorId'), 'comment actor');
16321
+ if (void 0 !== actorId) comment.actorId = actorId;
16059
16322
  if (orderedReplies.length > 0) comment.replies = orderedReplies;
16060
16323
  return comment;
16061
16324
  }
16062
16325
  function readReplyRecord(record, expectedId) {
16063
16326
  const id = requiredSharedIdentifier(record.get('id'), 'comment reply');
16064
16327
  if (id !== expectedId) invalidSharedSidecars('comment reply identity');
16065
- return {
16328
+ const reply = {
16066
16329
  id,
16067
16330
  author: requiredSharedString(record.get('author'), 'comment reply author'),
16068
16331
  date: requiredSharedString(record.get('date'), 'comment reply date'),
16069
16332
  text: requiredSharedString(record.get('text'), 'comment reply text')
16070
16333
  };
16334
+ const actorId = optionalSharedIdentifier(record.get('actorId'), 'comment reply actor');
16335
+ if (void 0 !== actorId) reply.actorId = actorId;
16336
+ return reply;
16071
16337
  }
16072
16338
  function patchCommentRecord(record, previous, next) {
16339
+ patchOptionalScalar(record, 'actorId', previous.actorId, next.actorId);
16073
16340
  patchRequiredScalar(record, 'author', previous.author, next.author);
16074
16341
  patchRequiredScalar(record, 'date', previous.date, next.date);
16075
16342
  patchRequiredScalar(record, 'text', previous.text, next.text);
@@ -16098,12 +16365,18 @@ function patchReplies(records, order, previous, next) {
16098
16365
  }
16099
16366
  if (jsonEqual(before, reply)) continue;
16100
16367
  const record = requiredSharedMap(records, reply.id, 'comment reply');
16368
+ patchOptionalScalar(record, 'actorId', before.actorId, reply.actorId);
16101
16369
  patchRequiredScalar(record, 'author', before.author, reply.author);
16102
16370
  patchRequiredScalar(record, 'date', before.date, reply.date);
16103
16371
  patchRequiredScalar(record, 'text', before.text, reply.text);
16104
16372
  }
16105
16373
  }
16374
+ function optionalSharedIdentifier(value, label) {
16375
+ return void 0 === value ? void 0 : requiredSharedIdentifier(value, label);
16376
+ }
16106
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);
16107
16380
  const previousComments = new Map((previous.comments ?? []).map((comment)=>[
16108
16381
  comment.id,
16109
16382
  comment
@@ -16126,6 +16399,7 @@ function assertWorkOfficeDocumentRecordClaims(claims, sidecars) {
16126
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.`);
16127
16400
  fingerprints.set(identity, claim.fingerprint);
16128
16401
  }
16402
+ for (const decision of sidecars.changeDecisions ?? [])assertClaimExists(fingerprints, 'change-decision', decision.id);
16129
16403
  for (const comment of sidecars.comments ?? []){
16130
16404
  assertClaimExists(fingerprints, 'comment', comment.id);
16131
16405
  for (const reply of comment.replies ?? [])assertClaimExists(fingerprints, 'comment-reply', reply.id, comment.id);
@@ -16157,7 +16431,7 @@ function parsedClaim(rawClaim) {
16157
16431
  const keys = Object.keys(value);
16158
16432
  if (keys.some((key)=>'fingerprint' !== key && 'id' !== key && 'kind' !== key && 'parentId' !== key)) invalidSharedSidecars('record claim');
16159
16433
  const kind = value.kind;
16160
- 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');
16161
16435
  const id = requiredSharedIdentifier(value.id, 'record claim');
16162
16436
  const fingerprint = requiredSharedString(value.fingerprint, 'record claim fingerprint');
16163
16437
  const parentId = void 0 === value.parentId ? void 0 : requiredSharedIdentifier(value.parentId, 'record claim parent');
@@ -16190,6 +16464,7 @@ function claimIdentity(kind, id, parentId) {
16190
16464
  ]);
16191
16465
  }
16192
16466
  function claimLabel(claim) {
16467
+ if ('change-decision' === claim.kind) return 'tracked-change decision';
16193
16468
  if ('comment' === claim.kind) return 'comment';
16194
16469
  if ('bibliography-source' === claim.kind) return 'bibliography source';
16195
16470
  return `reply in comment '${claim.parentId}'`;
@@ -16197,13 +16472,15 @@ function claimLabel(claim) {
16197
16472
  const DOCUMENT_OPTIONS_ROOT = 'document.options';
16198
16473
  const DOCUMENT_COMMENTS_ROOT = 'document.comments';
16199
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';
16200
16477
  const DOCUMENT_BIBLIOGRAPHY_ROOT = 'document.bibliography';
16201
16478
  const DOCUMENT_BIBLIOGRAPHY_SOURCES_ROOT = 'document.bibliography.sources';
16202
16479
  const DOCUMENT_BIBLIOGRAPHY_SOURCE_ORDER_ROOT = 'document.bibliography.source-order';
16203
16480
  const DOCUMENT_RECORD_CLAIMS_ROOT = 'document.record-claims';
16204
16481
  function assertWorkOfficeDocumentSidecarsEmpty(session) {
16205
16482
  const roots = documentSidecarRoots(session);
16206
- 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.');
16207
16484
  }
16208
16485
  function initializeWorkOfficeDocumentSidecars(session, content) {
16209
16486
  const roots = documentSidecarRoots(session);
@@ -16211,6 +16488,7 @@ function initializeWorkOfficeDocumentSidecars(session, content) {
16211
16488
  appendWorkOfficeDocumentRecordClaims(roots.recordClaims, {}, sidecars);
16212
16489
  if (void 0 !== sidecars.pageColor) roots.options.set('pageColor', sidecars.pageColor);
16213
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);
16214
16492
  if (void 0 !== sidecars.comments) {
16215
16493
  roots.options.set('commentsPresent', true);
16216
16494
  initializeWorkOfficeDocumentComments(roots.comments, roots.commentOrder, sidecars.comments);
@@ -16233,6 +16511,8 @@ function readWorkOfficeDocumentSidecars(session) {
16233
16511
  if ('boolean' != typeof trackChanges) invalidSharedSidecars('track-changes setting');
16234
16512
  result.trackChanges = trackChanges;
16235
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');
16236
16516
  const commentsPresent = optionalPresence(roots.options.get('commentsPresent'), 'comment');
16237
16517
  if (true === commentsPresent || roots.commentOrder.length > 0) result.comments = readWorkOfficeDocumentComments(roots.comments, roots.commentOrder);
16238
16518
  else if (roots.comments.size > 0) invalidSharedSidecars('comment order and record set');
@@ -16246,14 +16526,17 @@ function updateWorkOfficeDocumentSidecars(session, previous, next, origin) {
16246
16526
  const before = validatedWorkOfficeDocumentSidecars(previous);
16247
16527
  const after = validatedWorkOfficeDocumentSidecars(next);
16248
16528
  if (jsonEqual(before, after)) return false;
16529
+ assertDocumentSidecarMutationAllowed(session, before, after);
16249
16530
  const roots = documentSidecarRoots(session);
16250
16531
  const shared = readWorkOfficeDocumentSidecars(session);
16251
16532
  assertWorkOfficeDocumentCommentConflicts(before.comments ?? [], after.comments ?? [], shared.comments ?? []);
16533
+ assertWorkOfficeDocumentChangeDecisionConflicts(before.changeDecisions ?? [], after.changeDecisions ?? [], shared.changeDecisions ?? []);
16252
16534
  assertWorkOfficeDocumentBibliographyConflicts(before.bibliography, after.bibliography, shared.bibliography);
16253
16535
  session.transact(()=>{
16254
16536
  appendWorkOfficeDocumentRecordClaims(roots.recordClaims, before, after);
16255
16537
  patchOptionalScalar(roots.options, 'pageColor', before.pageColor, after.pageColor);
16256
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 ?? []);
16257
16540
  if (!jsonEqual(before.comments, after.comments)) {
16258
16541
  patchPresence(roots.options, 'commentsPresent', before.comments, after.comments);
16259
16542
  patchWorkOfficeDocumentComments(roots.comments, roots.commentOrder, before.comments ?? [], after.comments ?? []);
@@ -16262,7 +16545,7 @@ function updateWorkOfficeDocumentSidecars(session, previous, next, origin) {
16262
16545
  patchPresence(roots.options, 'bibliographyPresent', before.bibliography, after.bibliography, false);
16263
16546
  patchWorkOfficeDocumentBibliography(roots.bibliography, roots.bibliographySources, roots.bibliographySourceOrder, before.bibliography, after.bibliography);
16264
16547
  }
16265
- }, origin);
16548
+ }, origin, 'comment' === session.mode ? 'document-comment' : 'content');
16266
16549
  return true;
16267
16550
  }
16268
16551
  function workOfficeDocumentSidecarUndoScope(session) {
@@ -16277,9 +16560,19 @@ function workOfficeDocumentSidecarUndoScope(session) {
16277
16560
  roots.recordClaims
16278
16561
  ];
16279
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
+ }
16280
16568
  function workOfficeDocumentSidecarsChanged(session, transaction) {
16281
16569
  const changed = new Set(transaction.changedParentTypes.keys());
16282
- 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));
16283
16576
  }
16284
16577
  function validatedWorkOfficeDocumentSidecars(content) {
16285
16578
  if (!content || 'document' !== content.type) invalidInputSidecars('a Document content value');
@@ -16292,15 +16585,24 @@ function validatedWorkOfficeDocumentSidecars(content) {
16292
16585
  if ('boolean' != typeof content.trackChanges) invalidInputSidecars('a boolean track-changes setting');
16293
16586
  result.trackChanges = content.trackChanges;
16294
16587
  }
16588
+ if (void 0 !== content.changeDecisions) result.changeDecisions = validatedWorkOfficeDocumentChangeDecisions(content.changeDecisions);
16295
16589
  if (void 0 !== content.comments) result.comments = validatedWorkOfficeDocumentComments(content.comments);
16296
16590
  if (void 0 !== content.bibliography) result.bibliography = validatedWorkOfficeDocumentBibliography(content.bibliography);
16297
16591
  return result;
16298
16592
  }
16593
+ function assertDocumentSidecarMutationAllowed(session, previous, next) {
16594
+ if ('edit' === session.mode) return;
16595
+ if ('comment' !== session.mode) return void assertWorkOfficeCollaborationEditable(session);
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.');
16597
+ assertWorkOfficeDocumentCommentMutationAllowed(session, previous.comments ?? [], next.comments ?? []);
16598
+ }
16299
16599
  function documentSidecarRoots(session) {
16300
16600
  return {
16301
16601
  options: session.document.getMap(session.rootName(DOCUMENT_OPTIONS_ROOT)),
16302
16602
  comments: session.document.getMap(session.rootName(DOCUMENT_COMMENTS_ROOT)),
16303
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)),
16304
16606
  bibliography: session.document.getMap(session.rootName(DOCUMENT_BIBLIOGRAPHY_ROOT)),
16305
16607
  bibliographySources: session.document.getMap(session.rootName(DOCUMENT_BIBLIOGRAPHY_SOURCES_ROOT)),
16306
16608
  bibliographySourceOrder: session.document.getArray(session.rootName(DOCUMENT_BIBLIOGRAPHY_SOURCE_ORDER_ROOT)),
@@ -16317,7 +16619,154 @@ function patchPresence(target, key, previous, next, absentValue) {
16317
16619
  else if (void 0 !== previous) if (false === absentValue) target.set(key, false);
16318
16620
  else target.delete(key);
16319
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
+ }
16320
16768
  const DOCUMENT_CONTENT_ROOT = 'document.content';
16769
+ const MAX_DOCUMENT_COMMENT_HISTORY = 100;
16321
16770
  const mountedDocumentBindings = new WeakMap();
16322
16771
  function initializeWorkOfficeDocumentCollaboration(session, content) {
16323
16772
  assertDocumentSession(session);
@@ -16381,6 +16830,9 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16381
16830
  #listeners = new Set();
16382
16831
  #errorListeners = new Set();
16383
16832
  #historyListeners = new Set();
16833
+ #commentUndoStack = [];
16834
+ #commentRedoStack = [];
16835
+ #applyingCommentHistory = false;
16384
16836
  #destroyed = false;
16385
16837
  #destroyRequested = false;
16386
16838
  #observersAttached = false;
@@ -16395,7 +16847,8 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16395
16847
  assertWorkOfficeCollaborationOrigin(this.origin);
16396
16848
  const builtIns = createWorkDocumentExtensions({
16397
16849
  ...options.workExtensions,
16398
- collaborative: true
16850
+ collaborative: true,
16851
+ rotateTrackedTextIdentities: ()=>'suggest' !== session.mode
16399
16852
  });
16400
16853
  const additional = options.additionalExtensions ?? [];
16401
16854
  assertBehaviorOnlyExtensions(additional);
@@ -16418,10 +16871,13 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16418
16871
  ]));
16419
16872
  const existing = mountedDocumentBindings.get(session.document);
16420
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.');
16421
- this.#undoManager = new __rspack_external_yjs.UndoManager([
16874
+ const undoScope = 'comment' === session.mode || 'suggest' === session.mode ? [
16875
+ this.fragment
16876
+ ] : [
16422
16877
  this.fragment,
16423
16878
  ...workOfficeDocumentSidecarUndoScope(session)
16424
- ], {
16879
+ ];
16880
+ this.#undoManager = new __rspack_external_yjs.UndoManager(undoScope, {
16425
16881
  captureTimeout: options.captureTimeoutMs ?? 500,
16426
16882
  captureTransaction: (transaction)=>false !== transaction.meta.get('addToHistory'),
16427
16883
  deleteFilter: (item)=>defaultDeleteFilter(item, defaultProtectedNodes),
@@ -16433,7 +16889,7 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16433
16889
  const restore = restorable.restore?.bind(this.#undoManager);
16434
16890
  if (restore) restorable.restore = ()=>{
16435
16891
  restore();
16436
- this.#undoManager.addToScope([
16892
+ this.#undoManager.addToScope('comment' === this.#session.mode || 'suggest' === this.#session.mode ? this.fragment : [
16437
16893
  this.fragment,
16438
16894
  ...workOfficeDocumentSidecarUndoScope(this.#session)
16439
16895
  ]);
@@ -16466,23 +16922,79 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16466
16922
  }
16467
16923
  updateSidecars(previous, next) {
16468
16924
  this.ensureActive();
16469
- return updateWorkOfficeDocumentSidecars(this.#session, previous, next, this.origin);
16925
+ const historyEntry = 'comment' !== this.#session.mode || this.#applyingCommentHistory ? null : createDocumentCommentHistoryEntry(previous, next);
16926
+ if (historyEntry?.commentMembershipChanged) this.#undoManager.stopCapturing();
16927
+ const changed = updateWorkOfficeDocumentSidecars(this.#session, previous, next, this.origin);
16928
+ if (changed && historyEntry) {
16929
+ appendBoundedCommentHistory(this.#commentUndoStack, historyEntry);
16930
+ this.#commentRedoStack.length = 0;
16931
+ this.#onHistoryChange();
16932
+ }
16933
+ return changed;
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;
16470
16964
  }
16471
16965
  canUndo() {
16472
16966
  this.ensureActive();
16967
+ if ('comment' === this.#session.mode) return this.#commentUndoStack.length > 0 || this.#undoManager.canUndo();
16473
16968
  return this.#undoManager.canUndo();
16474
16969
  }
16475
16970
  canRedo() {
16476
16971
  this.ensureActive();
16972
+ if ('comment' === this.#session.mode) return this.#commentRedoStack.length > 0 || this.#undoManager.canRedo();
16477
16973
  return this.#undoManager.canRedo();
16478
16974
  }
16479
16975
  undo() {
16480
16976
  this.ensureActive();
16977
+ if ('comment' === this.#session.mode) {
16978
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-comment');
16979
+ return this.applyCommentHistory('undo');
16980
+ }
16981
+ if ('suggest' === this.#session.mode) {
16982
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-suggestion');
16983
+ return null !== this.#undoManager.undo();
16984
+ }
16481
16985
  assertWorkOfficeCollaborationEditable(this.#session);
16482
16986
  return null !== this.#undoManager.undo();
16483
16987
  }
16484
16988
  redo() {
16485
16989
  this.ensureActive();
16990
+ if ('comment' === this.#session.mode) {
16991
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-comment');
16992
+ return this.applyCommentHistory('redo');
16993
+ }
16994
+ if ('suggest' === this.#session.mode) {
16995
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-suggestion');
16996
+ return null !== this.#undoManager.redo();
16997
+ }
16486
16998
  assertWorkOfficeCollaborationEditable(this.#session);
16487
16999
  return null !== this.#undoManager.redo();
16488
17000
  }
@@ -16525,6 +17037,10 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16525
17037
  if (transaction.origin === ySyncPluginKey) transaction.origin = this.origin;
16526
17038
  };
16527
17039
  #onTransaction = (transaction)=>{
17040
+ if ('suggest' === this.#session.mode && workOfficeDocumentDecisionRootsChanged(this.#session, transaction)) {
17041
+ this.#undoManager.clear();
17042
+ this.#onHistoryChange();
17043
+ }
16528
17044
  if (this.#destroyed || this.#destroyRequested || !transactionTouchesRoot(transaction, this.fragment) && !workOfficeDocumentSidecarsChanged(this.#session, transaction)) return;
16529
17045
  const local = transaction.origin === ySyncPluginKey || transaction.origin === this.origin || transaction.origin === this.#undoManager;
16530
17046
  this.#pendingLocal = this.#pendingChange ? this.#pendingLocal && local : local;
@@ -16559,6 +17075,30 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16559
17075
  #onHistoryChange = ()=>{
16560
17076
  for (const listener of this.#historyListeners)listener();
16561
17077
  };
17078
+ applyCommentHistory(direction) {
17079
+ const sourceStack = 'undo' === direction ? this.#commentUndoStack : this.#commentRedoStack;
17080
+ const targetStack = 'undo' === direction ? this.#commentRedoStack : this.#commentUndoStack;
17081
+ const entry = sourceStack.pop();
17082
+ if (!entry) return 'undo' === direction ? null !== this.#undoManager.undo() : null !== this.#undoManager.redo();
17083
+ const from = 'undo' === direction ? entry.after : entry.before;
17084
+ const to = 'undo' === direction ? entry.before : entry.after;
17085
+ const current = this.content();
17086
+ const comments = reconcileDocumentCommentHistory(current.comments, from, to);
17087
+ let sidecarsChanged = false;
17088
+ this.#applyingCommentHistory = true;
17089
+ try {
17090
+ if (!jsonEqual(current.comments, comments)) sidecarsChanged = this.updateSidecars(current, {
17091
+ ...current,
17092
+ comments
17093
+ });
17094
+ } finally{
17095
+ this.#applyingCommentHistory = false;
17096
+ }
17097
+ const anchorChanged = entry.commentMembershipChanged ? 'undo' === direction ? null !== this.#undoManager.undo() : null !== this.#undoManager.redo() : false;
17098
+ appendBoundedCommentHistory(targetStack, entry);
17099
+ this.#onHistoryChange();
17100
+ return sidecarsChanged || anchorChanged;
17101
+ }
16562
17102
  finishDestroy(destroyUndoManager) {
16563
17103
  if (this.#destroyed) return;
16564
17104
  this.#destroyed = true;
@@ -16572,6 +17112,8 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16572
17112
  this.#listeners.clear();
16573
17113
  this.#errorListeners.clear();
16574
17114
  this.#historyListeners.clear();
17115
+ this.#commentUndoStack.length = 0;
17116
+ this.#commentRedoStack.length = 0;
16575
17117
  }
16576
17118
  detachObservers() {
16577
17119
  if (!this.#observersAttached) return;
@@ -16587,6 +17129,104 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16587
17129
  throw new WorkOfficeCollaborationError('office.collaboration.binding_destroyed', 'The Document collaboration binding has been destroyed.');
16588
17130
  }
16589
17131
  }
17132
+ function createDocumentCommentHistoryEntry(previous, next) {
17133
+ if (jsonEqual(previous.comments, next.comments)) return null;
17134
+ const before = cloneDocumentComments(previous.comments);
17135
+ const after = cloneDocumentComments(next.comments);
17136
+ return {
17137
+ before,
17138
+ after,
17139
+ commentMembershipChanged: !sameDocumentCommentMembership(before, after)
17140
+ };
17141
+ }
17142
+ function appendBoundedCommentHistory(stack, entry) {
17143
+ stack.push(entry);
17144
+ if (stack.length > MAX_DOCUMENT_COMMENT_HISTORY) stack.shift();
17145
+ }
17146
+ function sameDocumentCommentMembership(left, right) {
17147
+ return jsonEqual((left ?? []).map(({ id })=>id), (right ?? []).map(({ id })=>id));
17148
+ }
17149
+ function reconcileDocumentCommentHistory(current, from, to) {
17150
+ const fromById = new Map((from ?? []).map((comment)=>[
17151
+ comment.id,
17152
+ comment
17153
+ ]));
17154
+ const toById = new Map((to ?? []).map((comment)=>[
17155
+ comment.id,
17156
+ comment
17157
+ ]));
17158
+ const result = cloneDocumentComments(current) ?? [];
17159
+ const resultById = ()=>new Map(result.map((comment)=>[
17160
+ comment.id,
17161
+ comment
17162
+ ]));
17163
+ const orderedIds = [
17164
+ ...(from ?? []).map(({ id })=>id),
17165
+ ...(to ?? []).map(({ id })=>id)
17166
+ ].filter((id, index, ids)=>ids.indexOf(id) === index);
17167
+ for (const id of orderedIds){
17168
+ const source = fromById.get(id);
17169
+ const target = toById.get(id);
17170
+ const currentById = resultById();
17171
+ const existing = currentById.get(id);
17172
+ if (!source && target) {
17173
+ if (!existing) result.push(cloneDocumentComment(target));
17174
+ continue;
17175
+ }
17176
+ if (source && !target) {
17177
+ if (existing && jsonEqual(existing, source)) result.splice(result.findIndex((comment)=>comment.id === id), 1);
17178
+ continue;
17179
+ }
17180
+ if (!source || !target || !existing) continue;
17181
+ const next = reconcileExistingDocumentComment(existing, source, target);
17182
+ const index = result.findIndex((comment)=>comment.id === id);
17183
+ if (index >= 0) result[index] = next;
17184
+ }
17185
+ if (result.length > 0) return result;
17186
+ return void 0 === to ? void 0 : [];
17187
+ }
17188
+ function reconcileExistingDocumentComment(current, from, to) {
17189
+ const next = cloneDocumentComment(current);
17190
+ if (from.resolved !== to.resolved && current.resolved === from.resolved) next.resolved = to.resolved;
17191
+ const fromReplies = new Map((from.replies ?? []).map((reply)=>[
17192
+ reply.id,
17193
+ reply
17194
+ ]));
17195
+ const toReplies = new Map((to.replies ?? []).map((reply)=>[
17196
+ reply.id,
17197
+ reply
17198
+ ]));
17199
+ const replies = (next.replies ?? []).map((reply)=>({
17200
+ ...reply
17201
+ }));
17202
+ const replyIds = [
17203
+ ...(from.replies ?? []).map(({ id })=>id),
17204
+ ...(to.replies ?? []).map(({ id })=>id)
17205
+ ].filter((id, index, ids)=>ids.indexOf(id) === index);
17206
+ for (const id of replyIds){
17207
+ const source = fromReplies.get(id);
17208
+ const target = toReplies.get(id);
17209
+ const index = replies.findIndex((reply)=>reply.id === id);
17210
+ if (!source && target && index < 0) replies.push({
17211
+ ...target
17212
+ });
17213
+ else if (source && !target && index >= 0 && jsonEqual(replies[index], source)) replies.splice(index, 1);
17214
+ }
17215
+ if (replies.length > 0 || void 0 !== to.replies) next.replies = replies;
17216
+ else delete next.replies;
17217
+ return next;
17218
+ }
17219
+ function cloneDocumentComments(comments) {
17220
+ return comments?.map(cloneDocumentComment);
17221
+ }
17222
+ function cloneDocumentComment(comment) {
17223
+ return {
17224
+ ...comment,
17225
+ replies: comment.replies?.map((reply)=>({
17226
+ ...reply
17227
+ }))
17228
+ };
17229
+ }
16590
17230
  function workOfficeDocumentCollaborationFragment(session) {
16591
17231
  assertDocumentSession(session);
16592
17232
  return session.document.getXmlFragment(session.rootName(DOCUMENT_CONTENT_ROOT));
@@ -16676,7 +17316,7 @@ function documentCollaborationPermissionExtension(session) {
16676
17316
  return [
16677
17317
  new Plugin({
16678
17318
  filterTransaction (transaction) {
16679
- return 'edit' === session.mode || !transaction.docChanged || isChangeOrigin(transaction);
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);
16680
17320
  }
16681
17321
  })
16682
17322
  ];