@a3s-lab/office 0.7.2 → 0.7.3

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,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 } 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([
@@ -9169,20 +9169,35 @@ function collectDocumentCommentAnchors(document1) {
9169
9169
  return Array.from(anchors.values()).sort((left, right)=>left.from - right.from);
9170
9170
  }
9171
9171
  function documentCommentViews(comments, anchors) {
9172
- const byId = new Map(comments.map((comment)=>[
9173
- comment.id,
9174
- normalizeDocumentComment(comment)
9172
+ const anchorsById = new Map(anchors.map((anchor)=>[
9173
+ anchor.id,
9174
+ anchor
9175
9175
  ]));
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
- }));
9176
+ const commentIds = new Set(comments.map((comment)=>comment.id));
9177
+ const views = comments.map((comment)=>{
9178
+ const normalized = normalizeDocumentComment(comment);
9179
+ const anchor = anchorsById.get(comment.id);
9180
+ return anchor ? {
9181
+ ...normalized,
9182
+ ...anchor,
9183
+ detached: false
9184
+ } : {
9185
+ ...normalized,
9186
+ from: null,
9187
+ to: null,
9188
+ anchorText: '',
9189
+ detached: true
9190
+ };
9191
+ });
9192
+ for (const anchor of anchors)if (!commentIds.has(anchor.id)) views.push({
9193
+ author: '未知审阅者',
9194
+ date: '',
9195
+ text: '此批注的内容不可用。',
9196
+ resolved: false,
9197
+ ...anchor,
9198
+ detached: false
9199
+ });
9200
+ return views;
9186
9201
  }
9187
9202
  function retainAnchoredDocumentComments(comments, anchors, retainedIds = new Set()) {
9188
9203
  const ids = new Set(anchors.map((anchor)=>anchor.id));
@@ -9259,19 +9274,25 @@ function documentCommentMark(marks) {
9259
9274
  return marks.find((mark)=>'documentComment' === mark.type.name);
9260
9275
  }
9261
9276
  function normalizeDocumentComment(comment) {
9262
- return {
9277
+ const normalized = {
9263
9278
  id: comment.id,
9264
9279
  author: comment.author || '未知审阅者',
9265
9280
  date: comment.date || '',
9266
9281
  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
- }))
9282
+ resolved: Boolean(comment.resolved)
9274
9283
  };
9284
+ if (void 0 !== comment.actorId) normalized.actorId = comment.actorId;
9285
+ if (void 0 !== comment.replies) normalized.replies = comment.replies.map((reply)=>{
9286
+ const normalizedReply = {
9287
+ id: reply.id,
9288
+ author: reply.author || '未知审阅者',
9289
+ date: reply.date || '',
9290
+ text: reply.text || '(空回复)'
9291
+ };
9292
+ if (void 0 !== reply.actorId) normalizedReply.actorId = reply.actorId;
9293
+ return normalizedReply;
9294
+ });
9295
+ return normalized;
9275
9296
  }
9276
9297
  function work_document_comments_stringAttribute(value) {
9277
9298
  return 'string' == typeof value ? value : '';
@@ -15767,6 +15788,82 @@ function invalidSharedSidecars(label) {
15767
15788
  function office_document_collaboration_sidecar_utils_isRecord(value) {
15768
15789
  return isWorkOfficeCollaborationRecord(value);
15769
15790
  }
15791
+ function assertWorkOfficeDocumentCommentMutationAllowed(session, previous, next) {
15792
+ if ('edit' === session.mode) return;
15793
+ if ('comment' !== session.mode) deniedReviewMutation(session);
15794
+ const actor = requiredReviewActor(session);
15795
+ assertRetainedOrder(previous, next, 'comments');
15796
+ const nextById = new Map(next.map((comment)=>[
15797
+ comment.id,
15798
+ comment
15799
+ ]));
15800
+ const previousIds = new Set(previous.map((comment)=>comment.id));
15801
+ for (const comment of previous){
15802
+ const updated = nextById.get(comment.id);
15803
+ if (!updated) {
15804
+ assertOwnedRecord(comment.actorId, actor, 'own comment');
15805
+ continue;
15806
+ }
15807
+ assertExistingCommentMutation(actor, comment, updated);
15808
+ }
15809
+ for (const comment of next)if (!previousIds.has(comment.id)) assertNewComment(actor, comment);
15810
+ }
15811
+ function assertExistingCommentMutation(actor, previous, next) {
15812
+ if (previous.id !== next.id || previous.actorId !== next.actorId || previous.author !== next.author || previous.date !== next.date || previous.text !== next.text) deniedImmutableRecord('comment');
15813
+ assertReplyMutations(actor, previous.id, previous.replies ?? [], next.replies ?? []);
15814
+ }
15815
+ function assertReplyMutations(actor, commentId, previous, next) {
15816
+ assertRetainedOrder(previous, next, `replies in comment '${commentId}'`);
15817
+ const nextById = new Map(next.map((reply)=>[
15818
+ reply.id,
15819
+ reply
15820
+ ]));
15821
+ const previousIds = new Set(previous.map((reply)=>reply.id));
15822
+ for (const reply of previous){
15823
+ const updated = nextById.get(reply.id);
15824
+ if (!updated) {
15825
+ assertOwnedRecord(reply.actorId, actor, 'own comment reply');
15826
+ continue;
15827
+ }
15828
+ if (!jsonEqual(reply, updated)) deniedImmutableRecord('comment reply');
15829
+ }
15830
+ for (const reply of next)if (!previousIds.has(reply.id)) assertActorRecord(actor, reply, 'reply');
15831
+ }
15832
+ function assertNewComment(actor, comment) {
15833
+ assertActorRecord(actor, comment, 'comment');
15834
+ for (const reply of comment.replies ?? [])assertActorRecord(actor, reply, 'reply');
15835
+ }
15836
+ function assertActorRecord(actor, record, label) {
15837
+ if (record.actorId === actor.id && record.author === actor.name) return;
15838
+ 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.`);
15839
+ }
15840
+ function assertOwnedRecord(actorId, actor, label) {
15841
+ if (actorId === actor.id) return;
15842
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `Comment mode can delete only the collaboration actor's ${label}.`);
15843
+ }
15844
+ function assertRetainedOrder(previous, next, label) {
15845
+ const previousIds = new Set(previous.map((record)=>record.id));
15846
+ const nextIds = new Set(next.map((record)=>record.id));
15847
+ const retainedBefore = previous.filter((record)=>nextIds.has(record.id)).map((record)=>record.id);
15848
+ const retainedAfter = next.filter((record)=>previousIds.has(record.id)).map((record)=>record.id);
15849
+ let encounteredNewRecord = false;
15850
+ let existingAfterNewRecord = false;
15851
+ for (const record of next)if (previousIds.has(record.id)) {
15852
+ if (encounteredNewRecord) existingAfterNewRecord = true;
15853
+ } else encounteredNewRecord = true;
15854
+ if (jsonEqual(retainedBefore, retainedAfter) && !existingAfterNewRecord) return;
15855
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `Comment mode must preserve existing ${label} order and append new review records.`);
15856
+ }
15857
+ function requiredReviewActor(session) {
15858
+ if (session.actor) return session.actor;
15859
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', 'Comment mode requires a collaboration actor before it can write review records.');
15860
+ }
15861
+ function deniedImmutableRecord(label) {
15862
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `Comment mode cannot rewrite an existing ${label}; it may append replies or change resolution state.`);
15863
+ }
15864
+ function deniedReviewMutation(session) {
15865
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `The '${session.mode}' collaboration mode cannot modify Document review records.`);
15866
+ }
15770
15867
  function validatedWorkOfficeDocumentBibliography(value) {
15771
15868
  if (!office_document_collaboration_sidecar_utils_isRecord(value) || !isCitationStyle(value.style)) invalidInputSidecars('a supported bibliography style');
15772
15869
  if (!Array.isArray(value.sources)) invalidInputSidecars('an array of bibliography sources');
@@ -15995,6 +16092,7 @@ function validatedComment(value) {
15995
16092
  text: requiredString(value.text, 'comment text'),
15996
16093
  resolved: requiredBoolean(value.resolved, 'comment resolution')
15997
16094
  };
16095
+ if (void 0 !== value.actorId) comment.actorId = requiredIdentifier(value.actorId, 'comment actor');
15998
16096
  if (void 0 !== value.replies) {
15999
16097
  if (!Array.isArray(value.replies)) invalidInputSidecars('an array of comment replies');
16000
16098
  const ids = new Set();
@@ -16009,17 +16107,20 @@ function validatedComment(value) {
16009
16107
  }
16010
16108
  function validatedReply(value) {
16011
16109
  if (!office_document_collaboration_sidecar_utils_isRecord(value)) invalidInputSidecars('valid comment reply records');
16012
- return {
16110
+ const reply = {
16013
16111
  id: requiredIdentifier(value.id, 'comment reply'),
16014
16112
  author: requiredString(value.author, 'comment reply author'),
16015
16113
  date: requiredString(value.date, 'comment reply date'),
16016
16114
  text: requiredString(value.text, 'comment reply text')
16017
16115
  };
16116
+ if (void 0 !== value.actorId) reply.actorId = requiredIdentifier(value.actorId, 'comment reply actor');
16117
+ return reply;
16018
16118
  }
16019
16119
  function createCommentRecord(records, comment) {
16020
16120
  const record = new __rspack_external_yjs.Map();
16021
16121
  records.set(comment.id, record);
16022
16122
  record.set('id', comment.id);
16123
+ if (void 0 !== comment.actorId) record.set('actorId', comment.actorId);
16023
16124
  record.set('author', comment.author);
16024
16125
  record.set('date', comment.date);
16025
16126
  record.set('text', comment.text);
@@ -16039,6 +16140,7 @@ function createReplyRecord(records, reply) {
16039
16140
  const record = new __rspack_external_yjs.Map();
16040
16141
  records.set(reply.id, record);
16041
16142
  record.set('id', reply.id);
16143
+ if (void 0 !== reply.actorId) record.set('actorId', reply.actorId);
16042
16144
  record.set('author', reply.author);
16043
16145
  record.set('date', reply.date);
16044
16146
  record.set('text', reply.text);
@@ -16056,20 +16158,26 @@ function readCommentRecord(record, expectedId) {
16056
16158
  text: requiredSharedString(record.get('text'), 'comment text'),
16057
16159
  resolved: requiredSharedBoolean(record.get('resolved'), 'comment resolution')
16058
16160
  };
16161
+ const actorId = optionalSharedIdentifier(record.get('actorId'), 'comment actor');
16162
+ if (void 0 !== actorId) comment.actorId = actorId;
16059
16163
  if (orderedReplies.length > 0) comment.replies = orderedReplies;
16060
16164
  return comment;
16061
16165
  }
16062
16166
  function readReplyRecord(record, expectedId) {
16063
16167
  const id = requiredSharedIdentifier(record.get('id'), 'comment reply');
16064
16168
  if (id !== expectedId) invalidSharedSidecars('comment reply identity');
16065
- return {
16169
+ const reply = {
16066
16170
  id,
16067
16171
  author: requiredSharedString(record.get('author'), 'comment reply author'),
16068
16172
  date: requiredSharedString(record.get('date'), 'comment reply date'),
16069
16173
  text: requiredSharedString(record.get('text'), 'comment reply text')
16070
16174
  };
16175
+ const actorId = optionalSharedIdentifier(record.get('actorId'), 'comment reply actor');
16176
+ if (void 0 !== actorId) reply.actorId = actorId;
16177
+ return reply;
16071
16178
  }
16072
16179
  function patchCommentRecord(record, previous, next) {
16180
+ patchOptionalScalar(record, 'actorId', previous.actorId, next.actorId);
16073
16181
  patchRequiredScalar(record, 'author', previous.author, next.author);
16074
16182
  patchRequiredScalar(record, 'date', previous.date, next.date);
16075
16183
  patchRequiredScalar(record, 'text', previous.text, next.text);
@@ -16098,11 +16206,15 @@ function patchReplies(records, order, previous, next) {
16098
16206
  }
16099
16207
  if (jsonEqual(before, reply)) continue;
16100
16208
  const record = requiredSharedMap(records, reply.id, 'comment reply');
16209
+ patchOptionalScalar(record, 'actorId', before.actorId, reply.actorId);
16101
16210
  patchRequiredScalar(record, 'author', before.author, reply.author);
16102
16211
  patchRequiredScalar(record, 'date', before.date, reply.date);
16103
16212
  patchRequiredScalar(record, 'text', before.text, reply.text);
16104
16213
  }
16105
16214
  }
16215
+ function optionalSharedIdentifier(value, label) {
16216
+ return void 0 === value ? void 0 : requiredSharedIdentifier(value, label);
16217
+ }
16106
16218
  function appendWorkOfficeDocumentRecordClaims(claims, previous, next) {
16107
16219
  const previousComments = new Map((previous.comments ?? []).map((comment)=>[
16108
16220
  comment.id,
@@ -16246,6 +16358,7 @@ function updateWorkOfficeDocumentSidecars(session, previous, next, origin) {
16246
16358
  const before = validatedWorkOfficeDocumentSidecars(previous);
16247
16359
  const after = validatedWorkOfficeDocumentSidecars(next);
16248
16360
  if (jsonEqual(before, after)) return false;
16361
+ assertDocumentSidecarMutationAllowed(session, before, after);
16249
16362
  const roots = documentSidecarRoots(session);
16250
16363
  const shared = readWorkOfficeDocumentSidecars(session);
16251
16364
  assertWorkOfficeDocumentCommentConflicts(before.comments ?? [], after.comments ?? [], shared.comments ?? []);
@@ -16262,7 +16375,7 @@ function updateWorkOfficeDocumentSidecars(session, previous, next, origin) {
16262
16375
  patchPresence(roots.options, 'bibliographyPresent', before.bibliography, after.bibliography, false);
16263
16376
  patchWorkOfficeDocumentBibliography(roots.bibliography, roots.bibliographySources, roots.bibliographySourceOrder, before.bibliography, after.bibliography);
16264
16377
  }
16265
- }, origin);
16378
+ }, origin, 'comment' === session.mode ? 'document-comment' : 'content');
16266
16379
  return true;
16267
16380
  }
16268
16381
  function workOfficeDocumentSidecarUndoScope(session) {
@@ -16296,6 +16409,12 @@ function validatedWorkOfficeDocumentSidecars(content) {
16296
16409
  if (void 0 !== content.bibliography) result.bibliography = validatedWorkOfficeDocumentBibliography(content.bibliography);
16297
16410
  return result;
16298
16411
  }
16412
+ function assertDocumentSidecarMutationAllowed(session, previous, next) {
16413
+ if ('edit' === session.mode) return;
16414
+ 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.');
16416
+ assertWorkOfficeDocumentCommentMutationAllowed(session, previous.comments ?? [], next.comments ?? []);
16417
+ }
16299
16418
  function documentSidecarRoots(session) {
16300
16419
  return {
16301
16420
  options: session.document.getMap(session.rootName(DOCUMENT_OPTIONS_ROOT)),
@@ -16318,6 +16437,7 @@ function patchPresence(target, key, previous, next, absentValue) {
16318
16437
  else target.delete(key);
16319
16438
  }
16320
16439
  const DOCUMENT_CONTENT_ROOT = 'document.content';
16440
+ const MAX_DOCUMENT_COMMENT_HISTORY = 100;
16321
16441
  const mountedDocumentBindings = new WeakMap();
16322
16442
  function initializeWorkOfficeDocumentCollaboration(session, content) {
16323
16443
  assertDocumentSession(session);
@@ -16381,6 +16501,9 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16381
16501
  #listeners = new Set();
16382
16502
  #errorListeners = new Set();
16383
16503
  #historyListeners = new Set();
16504
+ #commentUndoStack = [];
16505
+ #commentRedoStack = [];
16506
+ #applyingCommentHistory = false;
16384
16507
  #destroyed = false;
16385
16508
  #destroyRequested = false;
16386
16509
  #observersAttached = false;
@@ -16418,10 +16541,13 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16418
16541
  ]));
16419
16542
  const existing = mountedDocumentBindings.get(session.document);
16420
16543
  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([
16544
+ const undoScope = 'comment' === session.mode ? [
16545
+ this.fragment
16546
+ ] : [
16422
16547
  this.fragment,
16423
16548
  ...workOfficeDocumentSidecarUndoScope(session)
16424
- ], {
16549
+ ];
16550
+ this.#undoManager = new __rspack_external_yjs.UndoManager(undoScope, {
16425
16551
  captureTimeout: options.captureTimeoutMs ?? 500,
16426
16552
  captureTransaction: (transaction)=>false !== transaction.meta.get('addToHistory'),
16427
16553
  deleteFilter: (item)=>defaultDeleteFilter(item, defaultProtectedNodes),
@@ -16433,7 +16559,7 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16433
16559
  const restore = restorable.restore?.bind(this.#undoManager);
16434
16560
  if (restore) restorable.restore = ()=>{
16435
16561
  restore();
16436
- this.#undoManager.addToScope([
16562
+ this.#undoManager.addToScope('comment' === this.#session.mode ? this.fragment : [
16437
16563
  this.fragment,
16438
16564
  ...workOfficeDocumentSidecarUndoScope(this.#session)
16439
16565
  ]);
@@ -16466,23 +16592,41 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16466
16592
  }
16467
16593
  updateSidecars(previous, next) {
16468
16594
  this.ensureActive();
16469
- return updateWorkOfficeDocumentSidecars(this.#session, previous, next, this.origin);
16595
+ const historyEntry = 'comment' !== this.#session.mode || this.#applyingCommentHistory ? null : createDocumentCommentHistoryEntry(previous, next);
16596
+ if (historyEntry?.commentMembershipChanged) this.#undoManager.stopCapturing();
16597
+ const changed = updateWorkOfficeDocumentSidecars(this.#session, previous, next, this.origin);
16598
+ if (changed && historyEntry) {
16599
+ appendBoundedCommentHistory(this.#commentUndoStack, historyEntry);
16600
+ this.#commentRedoStack.length = 0;
16601
+ this.#onHistoryChange();
16602
+ }
16603
+ return changed;
16470
16604
  }
16471
16605
  canUndo() {
16472
16606
  this.ensureActive();
16607
+ if ('comment' === this.#session.mode) return this.#commentUndoStack.length > 0 || this.#undoManager.canUndo();
16473
16608
  return this.#undoManager.canUndo();
16474
16609
  }
16475
16610
  canRedo() {
16476
16611
  this.ensureActive();
16612
+ if ('comment' === this.#session.mode) return this.#commentRedoStack.length > 0 || this.#undoManager.canRedo();
16477
16613
  return this.#undoManager.canRedo();
16478
16614
  }
16479
16615
  undo() {
16480
16616
  this.ensureActive();
16617
+ if ('comment' === this.#session.mode) {
16618
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-comment');
16619
+ return this.applyCommentHistory('undo');
16620
+ }
16481
16621
  assertWorkOfficeCollaborationEditable(this.#session);
16482
16622
  return null !== this.#undoManager.undo();
16483
16623
  }
16484
16624
  redo() {
16485
16625
  this.ensureActive();
16626
+ if ('comment' === this.#session.mode) {
16627
+ assertWorkOfficeCollaborationWritable(this.#session, 'document-comment');
16628
+ return this.applyCommentHistory('redo');
16629
+ }
16486
16630
  assertWorkOfficeCollaborationEditable(this.#session);
16487
16631
  return null !== this.#undoManager.redo();
16488
16632
  }
@@ -16559,6 +16703,30 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16559
16703
  #onHistoryChange = ()=>{
16560
16704
  for (const listener of this.#historyListeners)listener();
16561
16705
  };
16706
+ applyCommentHistory(direction) {
16707
+ const sourceStack = 'undo' === direction ? this.#commentUndoStack : this.#commentRedoStack;
16708
+ const targetStack = 'undo' === direction ? this.#commentRedoStack : this.#commentUndoStack;
16709
+ const entry = sourceStack.pop();
16710
+ if (!entry) return 'undo' === direction ? null !== this.#undoManager.undo() : null !== this.#undoManager.redo();
16711
+ const from = 'undo' === direction ? entry.after : entry.before;
16712
+ const to = 'undo' === direction ? entry.before : entry.after;
16713
+ const current = this.content();
16714
+ const comments = reconcileDocumentCommentHistory(current.comments, from, to);
16715
+ let sidecarsChanged = false;
16716
+ this.#applyingCommentHistory = true;
16717
+ try {
16718
+ if (!jsonEqual(current.comments, comments)) sidecarsChanged = this.updateSidecars(current, {
16719
+ ...current,
16720
+ comments
16721
+ });
16722
+ } finally{
16723
+ this.#applyingCommentHistory = false;
16724
+ }
16725
+ const anchorChanged = entry.commentMembershipChanged ? 'undo' === direction ? null !== this.#undoManager.undo() : null !== this.#undoManager.redo() : false;
16726
+ appendBoundedCommentHistory(targetStack, entry);
16727
+ this.#onHistoryChange();
16728
+ return sidecarsChanged || anchorChanged;
16729
+ }
16562
16730
  finishDestroy(destroyUndoManager) {
16563
16731
  if (this.#destroyed) return;
16564
16732
  this.#destroyed = true;
@@ -16572,6 +16740,8 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16572
16740
  this.#listeners.clear();
16573
16741
  this.#errorListeners.clear();
16574
16742
  this.#historyListeners.clear();
16743
+ this.#commentUndoStack.length = 0;
16744
+ this.#commentRedoStack.length = 0;
16575
16745
  }
16576
16746
  detachObservers() {
16577
16747
  if (!this.#observersAttached) return;
@@ -16587,6 +16757,104 @@ class WorkOfficeDocumentCollaborationBindingImpl {
16587
16757
  throw new WorkOfficeCollaborationError('office.collaboration.binding_destroyed', 'The Document collaboration binding has been destroyed.');
16588
16758
  }
16589
16759
  }
16760
+ function createDocumentCommentHistoryEntry(previous, next) {
16761
+ if (jsonEqual(previous.comments, next.comments)) return null;
16762
+ const before = cloneDocumentComments(previous.comments);
16763
+ const after = cloneDocumentComments(next.comments);
16764
+ return {
16765
+ before,
16766
+ after,
16767
+ commentMembershipChanged: !sameDocumentCommentMembership(before, after)
16768
+ };
16769
+ }
16770
+ function appendBoundedCommentHistory(stack, entry) {
16771
+ stack.push(entry);
16772
+ if (stack.length > MAX_DOCUMENT_COMMENT_HISTORY) stack.shift();
16773
+ }
16774
+ function sameDocumentCommentMembership(left, right) {
16775
+ return jsonEqual((left ?? []).map(({ id })=>id), (right ?? []).map(({ id })=>id));
16776
+ }
16777
+ function reconcileDocumentCommentHistory(current, from, to) {
16778
+ const fromById = new Map((from ?? []).map((comment)=>[
16779
+ comment.id,
16780
+ comment
16781
+ ]));
16782
+ const toById = new Map((to ?? []).map((comment)=>[
16783
+ comment.id,
16784
+ comment
16785
+ ]));
16786
+ const result = cloneDocumentComments(current) ?? [];
16787
+ const resultById = ()=>new Map(result.map((comment)=>[
16788
+ comment.id,
16789
+ comment
16790
+ ]));
16791
+ const orderedIds = [
16792
+ ...(from ?? []).map(({ id })=>id),
16793
+ ...(to ?? []).map(({ id })=>id)
16794
+ ].filter((id, index, ids)=>ids.indexOf(id) === index);
16795
+ for (const id of orderedIds){
16796
+ const source = fromById.get(id);
16797
+ const target = toById.get(id);
16798
+ const currentById = resultById();
16799
+ const existing = currentById.get(id);
16800
+ if (!source && target) {
16801
+ if (!existing) result.push(cloneDocumentComment(target));
16802
+ continue;
16803
+ }
16804
+ if (source && !target) {
16805
+ if (existing && jsonEqual(existing, source)) result.splice(result.findIndex((comment)=>comment.id === id), 1);
16806
+ continue;
16807
+ }
16808
+ if (!source || !target || !existing) continue;
16809
+ const next = reconcileExistingDocumentComment(existing, source, target);
16810
+ const index = result.findIndex((comment)=>comment.id === id);
16811
+ if (index >= 0) result[index] = next;
16812
+ }
16813
+ if (result.length > 0) return result;
16814
+ return void 0 === to ? void 0 : [];
16815
+ }
16816
+ function reconcileExistingDocumentComment(current, from, to) {
16817
+ const next = cloneDocumentComment(current);
16818
+ if (from.resolved !== to.resolved && current.resolved === from.resolved) next.resolved = to.resolved;
16819
+ const fromReplies = new Map((from.replies ?? []).map((reply)=>[
16820
+ reply.id,
16821
+ reply
16822
+ ]));
16823
+ const toReplies = new Map((to.replies ?? []).map((reply)=>[
16824
+ reply.id,
16825
+ reply
16826
+ ]));
16827
+ const replies = (next.replies ?? []).map((reply)=>({
16828
+ ...reply
16829
+ }));
16830
+ const replyIds = [
16831
+ ...(from.replies ?? []).map(({ id })=>id),
16832
+ ...(to.replies ?? []).map(({ id })=>id)
16833
+ ].filter((id, index, ids)=>ids.indexOf(id) === index);
16834
+ for (const id of replyIds){
16835
+ const source = fromReplies.get(id);
16836
+ const target = toReplies.get(id);
16837
+ const index = replies.findIndex((reply)=>reply.id === id);
16838
+ if (!source && target && index < 0) replies.push({
16839
+ ...target
16840
+ });
16841
+ else if (source && !target && index >= 0 && jsonEqual(replies[index], source)) replies.splice(index, 1);
16842
+ }
16843
+ if (replies.length > 0 || void 0 !== to.replies) next.replies = replies;
16844
+ else delete next.replies;
16845
+ return next;
16846
+ }
16847
+ function cloneDocumentComments(comments) {
16848
+ return comments?.map(cloneDocumentComment);
16849
+ }
16850
+ function cloneDocumentComment(comment) {
16851
+ return {
16852
+ ...comment,
16853
+ replies: comment.replies?.map((reply)=>({
16854
+ ...reply
16855
+ }))
16856
+ };
16857
+ }
16590
16858
  function workOfficeDocumentCollaborationFragment(session) {
16591
16859
  assertDocumentSession(session);
16592
16860
  return session.document.getXmlFragment(session.rootName(DOCUMENT_CONTENT_ROOT));
@@ -16676,7 +16944,7 @@ function documentCollaborationPermissionExtension(session) {
16676
16944
  return [
16677
16945
  new Plugin({
16678
16946
  filterTransaction (transaction) {
16679
- return 'edit' === session.mode || !transaction.docChanged || isChangeOrigin(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);
16680
16948
  }
16681
16949
  })
16682
16950
  ];
package/dist/9787.js CHANGED
@@ -52,9 +52,9 @@ class WorkOfficeCollaborationSessionImpl {
52
52
  operationId: void 0 === operationId ? void 0 : normalizedIdentifier(operationId, 'operation ID')
53
53
  });
54
54
  }
55
- transact(operation, origin = this.localOrigin) {
55
+ transact(operation, origin = this.localOrigin, scope = 'content') {
56
56
  this.ensureActive();
57
- assertWorkOfficeCollaborationEditable(this);
57
+ assertWorkOfficeCollaborationWritable(this, scope);
58
58
  assertWorkOfficeCollaborationOrigin(origin);
59
59
  let result;
60
60
  this.document.transact((transaction)=>{
@@ -132,8 +132,17 @@ function registerWorkOfficeCollaborationInitializer(session) {
132
132
  ]);
133
133
  }
134
134
  function assertWorkOfficeCollaborationEditable(session) {
135
+ assertWorkOfficeCollaborationWritable(session, 'content');
136
+ }
137
+ function assertWorkOfficeCollaborationWritable(session, scope) {
138
+ const normalizedScope = normalizedMutationScope(scope);
135
139
  if ('edit' === session.mode) return;
136
- throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', `The '${session.mode}' collaboration mode cannot modify canonical content.`);
140
+ if ('comment' === session.mode && 'document' === session.kind && 'document-comment' === normalizedScope) return;
141
+ throw new WorkOfficeCollaborationError('office.collaboration.permission_denied', 'document-comment' === normalizedScope ? `The '${session.mode}' collaboration mode cannot modify Document review records.` : `The '${session.mode}' collaboration mode cannot modify canonical content.`);
142
+ }
143
+ function normalizedMutationScope(value) {
144
+ if ('content' === value || 'document-comment' === value) return value;
145
+ throw new WorkOfficeCollaborationError('office.collaboration.mutation_scope_invalid', `The collaboration mutation scope '${String(value)}' is invalid.`);
137
146
  }
138
147
  function assertSessionMetadata(session) {
139
148
  readWorkOfficeCollaborationMetadata(session);
@@ -206,4 +215,4 @@ function normalizedRootSuffix(value) {
206
215
  function isWorkArtifactKind(value) {
207
216
  return 'document' === value || 'markdown' === value || 'spreadsheet' === value || 'presentation' === value || 'pdf' === value;
208
217
  }
209
- export { WORK_OFFICE_COLLABORATION_NAMESPACE as OFFICE_COLLABORATION_NAMESPACE, WORK_OFFICE_COLLABORATION_PROTOCOL as OFFICE_COLLABORATION_PROTOCOL, WorkOfficeCollaborationError as OfficeCollaborationError, assertWorkOfficeCollaborationEditable, assertWorkOfficeCollaborationOrigin, createWorkOfficeCollaborationSession as createOfficeCollaborationSession, initializeWorkOfficeCollaborationMetadata, markWorkOfficeCollaborationInitialized, readWorkOfficeCollaborationMetadata as readOfficeCollaborationMetadata, registerWorkOfficeCollaborationInitializer };
218
+ export { WORK_OFFICE_COLLABORATION_NAMESPACE as OFFICE_COLLABORATION_NAMESPACE, WORK_OFFICE_COLLABORATION_PROTOCOL as OFFICE_COLLABORATION_PROTOCOL, WorkOfficeCollaborationError as OfficeCollaborationError, assertWorkOfficeCollaborationEditable, assertWorkOfficeCollaborationOrigin, assertWorkOfficeCollaborationWritable, createWorkOfficeCollaborationSession as createOfficeCollaborationSession, initializeWorkOfficeCollaborationMetadata, markWorkOfficeCollaborationInitialized, readWorkOfficeCollaborationMetadata as readOfficeCollaborationMetadata, registerWorkOfficeCollaborationInitializer };
package/dist/core.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { WorkOfficeCollaborationActor as OfficeCollaborationActor, WorkOfficeCollaborationActorKind as OfficeCollaborationActorKind, WorkOfficeCollaborationAwareness as OfficeCollaborationAwareness, WorkOfficeCollaborationErrorCode as OfficeCollaborationErrorCode, WorkOfficeCollaborationMetadata as OfficeCollaborationMetadata, WorkOfficeCollaborationMode as OfficeCollaborationMode, WorkOfficeCollaborationOrigin as OfficeCollaborationOrigin, WorkOfficeCollaborationOriginKind as OfficeCollaborationOriginKind, WorkOfficeCollaborationSession as OfficeCollaborationSession, WorkOfficeCollaborationSessionOptions as OfficeCollaborationSessionOptions, } from './internal/collaboration/office-collaboration';
1
+ export type { WorkOfficeCollaborationActor as OfficeCollaborationActor, WorkOfficeCollaborationActorKind as OfficeCollaborationActorKind, WorkOfficeCollaborationAwareness as OfficeCollaborationAwareness, WorkOfficeCollaborationErrorCode as OfficeCollaborationErrorCode, WorkOfficeCollaborationMetadata as OfficeCollaborationMetadata, WorkOfficeCollaborationMode as OfficeCollaborationMode, WorkOfficeCollaborationMutationScope as OfficeCollaborationMutationScope, WorkOfficeCollaborationOrigin as OfficeCollaborationOrigin, WorkOfficeCollaborationOriginKind as OfficeCollaborationOriginKind, WorkOfficeCollaborationSession as OfficeCollaborationSession, WorkOfficeCollaborationSessionOptions as OfficeCollaborationSessionOptions, } from './internal/collaboration/office-collaboration';
2
2
  export { createWorkOfficeCollaborationSession as createOfficeCollaborationSession, readWorkOfficeCollaborationMetadata as readOfficeCollaborationMetadata, WORK_OFFICE_COLLABORATION_NAMESPACE as OFFICE_COLLABORATION_NAMESPACE, WORK_OFFICE_COLLABORATION_PROTOCOL as OFFICE_COLLABORATION_PROTOCOL, WORK_OFFICE_COLLABORATION_VERSION as OFFICE_COLLABORATION_VERSION, WorkOfficeCollaborationError as OfficeCollaborationError, } from './internal/collaboration/office-collaboration';
3
3
  export type { WorkOfficeCollaborationParticipant as OfficeCollaborationParticipant, WorkOfficeCollaborationPresence as OfficeCollaborationPresence, WorkOfficeCollaborationPresenceActivity as OfficeCollaborationPresenceActivity, WorkOfficeCollaborationPresenceActor as OfficeCollaborationPresenceActor, WorkOfficeCollaborationPresenceLocation as OfficeCollaborationPresenceLocation, WorkOfficeCollaborationPresenceSnapshot as OfficeCollaborationPresenceSnapshot, WorkOfficeCollaborationPresenceState as OfficeCollaborationPresenceState, WorkOfficeCollaborationPresenceUpdate as OfficeCollaborationPresenceUpdate, WorkOfficeDocumentPresenceLocation as OfficeDocumentPresenceLocation, WorkOfficeMarkdownPresenceLocation as OfficeMarkdownPresenceLocation, WorkOfficePdfPresenceLocation as OfficePdfPresenceLocation, WorkOfficePresentationPresenceLocation as OfficePresentationPresenceLocation, WorkOfficeSpreadsheetPresenceCell as OfficeSpreadsheetPresenceCell, WorkOfficeSpreadsheetPresenceLocation as OfficeSpreadsheetPresenceLocation, WorkOfficeSpreadsheetPresenceRange as OfficeSpreadsheetPresenceRange, WorkOfficeTextPresenceLocation as OfficeTextPresenceLocation, } from './internal/collaboration/office-collaboration-presence';
4
4
  export { createWorkOfficeCollaborationPresence as createOfficeCollaborationPresence, WORK_OFFICE_COLLABORATION_PRESENCE_FIELD as OFFICE_COLLABORATION_PRESENCE_FIELD, } from './internal/collaboration/office-collaboration-presence';
@@ -13,6 +13,7 @@ export interface WorkOfficeCollaborationActor {
13
13
  kind?: WorkOfficeCollaborationActorKind;
14
14
  }
15
15
  export type WorkOfficeCollaborationOriginKind = 'bootstrap' | 'editor' | 'agent' | 'import' | 'system';
16
+ export type WorkOfficeCollaborationMutationScope = 'content' | 'document-comment';
16
17
  export interface WorkOfficeCollaborationOrigin {
17
18
  readonly protocol: typeof WORK_OFFICE_COLLABORATION_PROTOCOL;
18
19
  readonly kind: WorkOfficeCollaborationOriginKind;
@@ -68,10 +69,10 @@ export interface WorkOfficeCollaborationSession {
68
69
  metadata(): WorkOfficeCollaborationMetadata | null;
69
70
  rootName(suffix: string): string;
70
71
  createOrigin(kind: WorkOfficeCollaborationOriginKind, operationId?: string): WorkOfficeCollaborationOrigin;
71
- transact<T>(operation: (transaction: Y.Transaction) => T, origin?: WorkOfficeCollaborationOrigin): T;
72
+ transact<T>(operation: (transaction: Y.Transaction) => T, origin?: WorkOfficeCollaborationOrigin, scope?: WorkOfficeCollaborationMutationScope): T;
72
73
  destroy(): void;
73
74
  }
74
- export type WorkOfficeCollaborationErrorCode = 'office.collaboration.actor_invalid' | 'office.collaboration.artifact_mismatch' | 'office.collaboration.binding_destroyed' | 'office.collaboration.bootstrap_ambiguous' | 'office.collaboration.bootstrap_invalid' | 'office.collaboration.content_invalid' | 'office.collaboration.identifier_invalid' | 'office.collaboration.kind_invalid' | 'office.collaboration.kind_mismatch' | 'office.collaboration.metadata_invalid' | 'office.collaboration.metadata_missing' | 'office.collaboration.mode_invalid' | 'office.collaboration.namespace_invalid' | 'office.collaboration.not_initialized' | 'office.collaboration.origin_invalid' | 'office.collaboration.permission_denied' | 'office.collaboration.presence_conflict' | 'office.collaboration.presence_invalid' | 'office.collaboration.presence_unavailable' | 'office.collaboration.root_invalid' | 'office.collaboration.session_destroyed' | 'office.collaboration.transport_identity_mismatch' | 'office.collaboration.transport_invalid' | 'office.collaboration.transport_message_invalid' | 'office.collaboration.transport_message_too_large';
75
+ export type WorkOfficeCollaborationErrorCode = 'office.collaboration.actor_invalid' | 'office.collaboration.artifact_mismatch' | 'office.collaboration.binding_destroyed' | 'office.collaboration.bootstrap_ambiguous' | 'office.collaboration.bootstrap_invalid' | 'office.collaboration.content_invalid' | 'office.collaboration.identifier_invalid' | 'office.collaboration.kind_invalid' | 'office.collaboration.kind_mismatch' | 'office.collaboration.metadata_invalid' | 'office.collaboration.metadata_missing' | 'office.collaboration.mode_invalid' | 'office.collaboration.mutation_scope_invalid' | 'office.collaboration.namespace_invalid' | 'office.collaboration.not_initialized' | 'office.collaboration.origin_invalid' | 'office.collaboration.permission_denied' | 'office.collaboration.presence_conflict' | 'office.collaboration.presence_invalid' | 'office.collaboration.presence_unavailable' | 'office.collaboration.root_invalid' | 'office.collaboration.session_destroyed' | 'office.collaboration.transport_identity_mismatch' | 'office.collaboration.transport_invalid' | 'office.collaboration.transport_message_invalid' | 'office.collaboration.transport_message_too_large';
75
76
  export declare class WorkOfficeCollaborationError extends Error {
76
77
  readonly code: WorkOfficeCollaborationErrorCode;
77
78
  constructor(code: WorkOfficeCollaborationErrorCode, message: string);
@@ -82,4 +83,5 @@ export declare function initializeWorkOfficeCollaborationMetadata(session: WorkO
82
83
  export declare function markWorkOfficeCollaborationInitialized(session: WorkOfficeCollaborationSession): void;
83
84
  export declare function registerWorkOfficeCollaborationInitializer(session: WorkOfficeCollaborationSession): void;
84
85
  export declare function assertWorkOfficeCollaborationEditable(session: WorkOfficeCollaborationSession): void;
86
+ export declare function assertWorkOfficeCollaborationWritable(session: WorkOfficeCollaborationSession, scope: WorkOfficeCollaborationMutationScope): void;
85
87
  export declare function assertWorkOfficeCollaborationOrigin(origin: WorkOfficeCollaborationOrigin): void;
@@ -0,0 +1,3 @@
1
+ import type { WorkDocumentComment } from '../features/work/work-types';
2
+ import { type WorkOfficeCollaborationSession } from './office-collaboration';
3
+ export declare function assertWorkOfficeDocumentCommentMutationAllowed(session: WorkOfficeCollaborationSession, previous: readonly WorkDocumentComment[], next: readonly WorkDocumentComment[]): void;
@@ -5,6 +5,7 @@ export interface DocumentCommentDraft {
5
5
  anchorText: string;
6
6
  }
7
7
  export declare const DocumentCommentComposer: import("react").ForwardRefExoticComponent<{
8
+ author?: string;
8
9
  draft: DocumentCommentDraft;
9
10
  top: number;
10
11
  onCancel: () => void;
@@ -2,10 +2,12 @@ import type { Editor } from '@tiptap/core';
2
2
  import { type RefObject } from 'react';
3
3
  import { type WorkDocumentCommentView } from '../work-document-comments';
4
4
  import { type DocumentCommentDraft } from './document-comment-composer';
5
- export declare function DocumentCommentsPanel({ editor, comments, draft, surfaceRef, onReply, onToggleResolved, onDelete, onCancelDraft, onSubmitDraft, onClose, onDirtyChange, }: {
5
+ export declare function DocumentCommentsPanel({ editor, comments, canDelete, draft, draftAuthor, surfaceRef, onReply, onToggleResolved, onDelete, onCancelDraft, onSubmitDraft, onClose, onDirtyChange, }: {
6
6
  editor: Editor;
7
7
  comments: WorkDocumentCommentView[];
8
+ canDelete?: (id: string) => boolean;
8
9
  draft: DocumentCommentDraft | null;
10
+ draftAuthor?: string;
9
11
  surfaceRef: RefObject<HTMLDivElement | null>;
10
12
  onReply: (id: string, text: string) => void;
11
13
  onToggleResolved: (id: string) => void;