superdoc 2.0.0-next.28 → 2.0.0-next.29

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/superdoc.cjs CHANGED
@@ -6,7 +6,7 @@ const require_uuid = require("./chunks/uuid-CFp0WGVU.cjs");
6
6
  const require_jszip = require("./chunks/jszip-BdUBlUeG.cjs");
7
7
  const require__plugin_vue_export_helper = require("./chunks/_plugin-vue_export-helper-BTwbGDKw.cjs");
8
8
  const require_constants = require("./chunks/constants-sbCZ2O_A.cjs");
9
- const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-CZBUMdjl.cjs");
9
+ const require_create_super_doc_ui = require("./chunks/create-super-doc-ui-BXehf3i3.cjs");
10
10
  let vue = require("vue");
11
11
  vue = require_rolldown_runtime.__toESM(vue);
12
12
  require("y-websocket");
@@ -145,19 +145,50 @@ function compareVersions(version1, version2) {
145
145
  }
146
146
  return 0;
147
147
  }
148
+ var registries = /* @__PURE__ */ new WeakMap();
149
+ function ownerDocumentFor(el) {
150
+ return el.ownerDocument ?? document;
151
+ }
152
+ function unregister(el, ownerDocument) {
153
+ const registry = registries.get(ownerDocument);
154
+ registry?.bindings.delete(el);
155
+ if (registry && registry.bindings.size === 0) {
156
+ ownerDocument.removeEventListener("click", registry.listener);
157
+ registries.delete(ownerDocument);
158
+ }
159
+ }
160
+ function register(el, binding) {
161
+ const ownerDocument = ownerDocumentFor(el);
162
+ if (el.__clickOutsideDocument && el.__clickOutsideDocument !== ownerDocument) unregister(el, el.__clickOutsideDocument);
163
+ let registry = registries.get(ownerDocument);
164
+ if (!registry) {
165
+ const bindings = /* @__PURE__ */ new Map();
166
+ const listener = (event) => {
167
+ const path = typeof event.composedPath === "function" ? event.composedPath() : null;
168
+ for (const [element, currentBinding] of bindings) if (!(path?.includes(element) ?? element.contains(event.target))) currentBinding.value?.(event);
169
+ };
170
+ registry = {
171
+ bindings,
172
+ listener
173
+ };
174
+ registries.set(ownerDocument, registry);
175
+ ownerDocument.addEventListener("click", listener);
176
+ }
177
+ registry.bindings.set(el, binding);
178
+ el.__clickOutsideHandler = registry.listener;
179
+ el.__clickOutsideDocument = ownerDocument;
180
+ }
148
181
  var v_click_outside_default = {
149
182
  mounted(el, binding) {
150
- const clickOutsideHandler = (event) => {
151
- if (!el.contains(event.target)) binding.value?.(event);
152
- };
153
- document.addEventListener("click", clickOutsideHandler);
154
- el.__clickOutsideHandler = clickOutsideHandler;
183
+ register(el, binding);
184
+ },
185
+ updated(el, binding) {
186
+ register(el, binding);
155
187
  },
156
188
  unmounted(el) {
157
- if (el.__clickOutsideHandler) {
158
- document.removeEventListener("click", el.__clickOutsideHandler);
159
- delete el.__clickOutsideHandler;
160
- }
189
+ unregister(el, el.__clickOutsideDocument ?? ownerDocumentFor(el));
190
+ delete el.__clickOutsideHandler;
191
+ delete el.__clickOutsideDocument;
161
192
  }
162
193
  };
163
194
  function hasUser(entry) {
@@ -2773,6 +2804,79 @@ const trackedChangeThreadParentIdForComment = (comment) => {
2773
2804
  if (comment?.trackedChangeParentId != null && hasLegacyTrackedChangeThreadSignal(comment)) return comment.trackedChangeParentId;
2774
2805
  return null;
2775
2806
  };
2807
+ const buildTrackedChangeThreadIndex = (allComments, previous = /* @__PURE__ */ new Map()) => {
2808
+ const childrenByParentId = /* @__PURE__ */ new Map();
2809
+ const anchoredRootsByTrackedChangeId = /* @__PURE__ */ new Map();
2810
+ const commentsById = /* @__PURE__ */ new Map();
2811
+ const sourceIndex = /* @__PURE__ */ new Map();
2812
+ const append = (map, key, value) => {
2813
+ const existing = map.get(key);
2814
+ if (existing) existing.push(value);
2815
+ else map.set(key, [value]);
2816
+ };
2817
+ allComments.forEach((comment, index) => {
2818
+ sourceIndex.set(comment, index);
2819
+ append(commentsById, comment.commentId, comment);
2820
+ new Set([comment.parentCommentId, comment.threadingParentCommentId].filter((id) => id != null).map((id) => String(id))).forEach((parentId) => append(childrenByParentId, parentId, comment));
2821
+ const trackedChangeThreadParentId = trackedChangeThreadParentIdForComment(comment);
2822
+ if (trackedChangeThreadParentId != null && !comment.parentCommentId) append(anchoredRootsByTrackedChangeId, String(trackedChangeThreadParentId), comment);
2823
+ });
2824
+ const next = /* @__PURE__ */ new Map();
2825
+ for (const parentComment of allComments) {
2826
+ if (!parentComment?.trackedChange || parentComment.commentId == null) continue;
2827
+ const trackedChangeId = parentComment.commentId;
2828
+ const threadIds = new Set([trackedChangeId]);
2829
+ const queue = [];
2830
+ const seed = typeof trackedChangeId === "string" ? [...childrenByParentId.get(trackedChangeId) ?? [], ...anchoredRootsByTrackedChangeId.get(trackedChangeId) ?? []] : [];
2831
+ for (const comment of seed) {
2832
+ const id = comment.commentId;
2833
+ if (id === trackedChangeId || threadIds.has(id)) continue;
2834
+ threadIds.add(id);
2835
+ queue.push(id);
2836
+ }
2837
+ for (let index = 0; index < queue.length; index += 1) {
2838
+ const parentId = queue[index];
2839
+ for (const comment of typeof parentId === "string" ? childrenByParentId.get(parentId) ?? [] : []) {
2840
+ const id = comment.commentId;
2841
+ if (threadIds.has(id)) continue;
2842
+ threadIds.add(id);
2843
+ queue.push(id);
2844
+ }
2845
+ }
2846
+ const members = [...threadIds].flatMap((id) => commentsById.get(id) ?? []).sort((left, right) => (sourceIndex.get(left) ?? 0) - (sourceIndex.get(right) ?? 0));
2847
+ members.sort((left, right) => {
2848
+ if (left === parentComment) return -1;
2849
+ if (right === parentComment) return 1;
2850
+ return Number(left.createdTime) - Number(right.createdTime);
2851
+ });
2852
+ const prior = previous.get(trackedChangeId);
2853
+ const stable = prior?.length === members.length && members.every((comment, index) => prior[index] === comment) ? prior : members;
2854
+ next.set(trackedChangeId, stable);
2855
+ }
2856
+ return next;
2857
+ };
2858
+ const buildTrackedChangeDecisionLinkIndex = (allComments, previous = /* @__PURE__ */ new Map()) => {
2859
+ const grouped = /* @__PURE__ */ new Map();
2860
+ for (const comment of allComments) {
2861
+ const ids = new Set([
2862
+ comment.parentCommentId,
2863
+ comment.threadingParentCommentId,
2864
+ comment.trackedChangeParentId,
2865
+ comment.trackedChangeThreadParentId
2866
+ ].filter((id) => id != null).map((id) => String(id)));
2867
+ for (const id of ids) {
2868
+ const existing = grouped.get(id);
2869
+ if (existing) existing.push(comment);
2870
+ else grouped.set(id, [comment]);
2871
+ }
2872
+ }
2873
+ const next = /* @__PURE__ */ new Map();
2874
+ for (const [id, comments] of grouped) {
2875
+ const prior = previous.get(id);
2876
+ next.set(id, prior?.length === comments.length && comments.every((comment, index) => prior[index] === comment) ? prior : comments);
2877
+ }
2878
+ return next;
2879
+ };
2776
2880
  var Editor = class {
2777
2881
  constructor() {
2778
2882
  throw new Error("SuperDoc v2 beta: v1 editor-backed comment import paths are not available.");
@@ -2933,6 +3037,22 @@ const useCommentsStore = defineStore("comments", () => {
2933
3037
  const suppressInternalExternal = (0, vue.ref)(true);
2934
3038
  const currentCommentText = (0, vue.ref)("");
2935
3039
  const commentsList = (0, vue.ref)([]);
3040
+ let priorTrackedChangeThreadIndex = /* @__PURE__ */ new Map();
3041
+ const trackedChangeThreadIndex = (0, vue.computed)(() => {
3042
+ const next = buildTrackedChangeThreadIndex(commentsList.value, priorTrackedChangeThreadIndex);
3043
+ priorTrackedChangeThreadIndex = next;
3044
+ return next;
3045
+ });
3046
+ const getTrackedChangeThread = (parentComment) => {
3047
+ const id = parentComment?.commentId ?? null;
3048
+ return (id != null ? trackedChangeThreadIndex.value.get(id) : null) ?? [parentComment].filter(Boolean);
3049
+ };
3050
+ let priorTrackedChangeDecisionLinkIndex = /* @__PURE__ */ new Map();
3051
+ const trackedChangeDecisionLinkIndex = (0, vue.computed)(() => {
3052
+ const next = buildTrackedChangeDecisionLinkIndex(commentsList.value, priorTrackedChangeDecisionLinkIndex);
3053
+ priorTrackedChangeDecisionLinkIndex = next;
3054
+ return next;
3055
+ });
2936
3056
  const isCommentsListVisible = (0, vue.ref)(false);
2937
3057
  const editorCommentIds = (0, vue.ref)([]);
2938
3058
  const editorCommentPositions = (0, vue.ref)({});
@@ -3719,6 +3839,7 @@ const useCommentsStore = defineStore("comments", () => {
3719
3839
  };
3720
3840
  };
3721
3841
  const getGroupedComments = (0, vue.computed)(() => buildGroupedComments(compareByCreatedTime));
3842
+ const hasOpenTrackedChanges = (0, vue.computed)(() => getGroupedComments.value?.parentComments.some((comment) => comment.trackedChange && !comment.resolvedTime) ?? false);
3722
3843
  const getCommentsByPosition = (0, vue.computed)(() => buildGroupedComments(compareByPosition));
3723
3844
  const hasOverlapId = (id) => overlappedIds.includes(id);
3724
3845
  const documentsWithConverations = (0, vue.computed)(() => {
@@ -4904,7 +5025,7 @@ const useCommentsStore = defineStore("comments", () => {
4904
5025
  superdoc
4905
5026
  };
4906
5027
  let resolvedCount = 0;
4907
- commentsList.value.forEach((linkedComment) => {
5028
+ getTrackedChangeDecisionLinks(comment).forEach((linkedComment) => {
4908
5029
  if (!linkedComment || linkedComment === comment) return;
4909
5030
  if (linkedComment.resolvedTime) return;
4910
5031
  if (![
@@ -4932,6 +5053,16 @@ const useCommentsStore = defineStore("comments", () => {
4932
5053
  }
4933
5054
  return trackedChangeIds;
4934
5055
  };
5056
+ const getTrackedChangeDecisionLinks = (comment) => {
5057
+ const linked = [];
5058
+ const seen = /* @__PURE__ */ new Set();
5059
+ for (const id of collectTrackedChangeDecisionIds(comment)) for (const candidate of trackedChangeDecisionLinkIndex.value.get(String(id)) ?? []) {
5060
+ if (seen.has(candidate)) continue;
5061
+ seen.add(candidate);
5062
+ linked.push(candidate);
5063
+ }
5064
+ return linked;
5065
+ };
4935
5066
  const collectTrackedChangeEntityIdsFromReceipt = (receipt) => {
4936
5067
  const record = receipt && typeof receipt === "object" ? receipt : null;
4937
5068
  if (!record?.success) return /* @__PURE__ */ new Set();
@@ -4963,7 +5094,7 @@ const useCommentsStore = defineStore("comments", () => {
4963
5094
  const trackedChangeIds = collectTrackedChangeDecisionIds(comment);
4964
5095
  if (!trackedChangeIds.size) return 0;
4965
5096
  let detachedCount = 0;
4966
- commentsList.value.forEach((linkedComment) => {
5097
+ getTrackedChangeDecisionLinks(comment).forEach((linkedComment) => {
4967
5098
  if (!linkedComment || linkedComment === comment) return;
4968
5099
  const trackedChangeParentId = normalizeCommentId(linkedComment.trackedChangeParentId);
4969
5100
  const trackedChangeThreadParentId = normalizeCommentId(linkedComment.trackedChangeThreadParentId);
@@ -4979,7 +5110,7 @@ const useCommentsStore = defineStore("comments", () => {
4979
5110
  });
4980
5111
  return detachedCount;
4981
5112
  };
4982
- const pruneDecidedTrackedChangeRow = ({ superdoc, decidedId, decidedIds, documentId } = {}) => {
5113
+ const pruneDecidedTrackedChangeRow = ({ superdoc, comment: targetComment, decidedId, decidedIds, documentId } = {}) => {
4983
5114
  const normalizedDecidedIds = new Set((Array.isArray(decidedIds) || decidedIds instanceof Set ? Array.from(decidedIds) : [decidedId]).map((id) => normalizeCommentId(id)).filter(Boolean));
4984
5115
  const normalizedDocumentId = normalizeCommentId(documentId);
4985
5116
  if (!normalizedDecidedIds.size || !normalizedDocumentId) return {
@@ -4988,7 +5119,13 @@ const useCommentsStore = defineStore("comments", () => {
4988
5119
  };
4989
5120
  const removedIds = /* @__PURE__ */ new Set();
4990
5121
  let removed = false;
4991
- commentsList.value = commentsList.value.filter((comment) => {
5122
+ const targetIndex = targetComment ? commentsList.value.indexOf(targetComment) : -1;
5123
+ const targetIds = targetComment?.trackedChange ? collectTrackedChangeDecisionIds(targetComment) : /* @__PURE__ */ new Set();
5124
+ if (targetIndex >= 0 && belongsToTrackedChangeSyncDocument(targetComment, normalizedDocumentId) && Array.from(normalizedDecidedIds).some((id) => targetIds.has(id))) {
5125
+ targetIds.forEach((id) => removedIds.add(id));
5126
+ commentsList.value.splice(targetIndex, 1);
5127
+ removed = true;
5128
+ } else commentsList.value = commentsList.value.filter((comment) => {
4992
5129
  if (!comment?.trackedChange) return true;
4993
5130
  if (!belongsToTrackedChangeSyncDocument(comment, normalizedDocumentId)) return true;
4994
5131
  const ids = collectTrackedChangeDecisionIds(comment);
@@ -5024,6 +5161,25 @@ const useCommentsStore = defineStore("comments", () => {
5024
5161
  adapter.clearActiveTrackedChangeTargetIfMatches(decidedId);
5025
5162
  } catch {}
5026
5163
  };
5164
+ const waitForTrackedDecisionCanonicalPaint = async ({ superdoc, receipt } = {}) => {
5165
+ const readiness = superdoc?.activeEditor?.documentMutationReadiness;
5166
+ if (receipt?.success !== true || typeof receipt?.txId !== "string" || !receipt.txId || typeof readiness?.whenPainted !== "function") return;
5167
+ try {
5168
+ await readiness.whenPainted(receipt);
5169
+ const requestFrame = globalThis.requestAnimationFrame;
5170
+ if (typeof requestFrame === "function") await new Promise((resolve) => {
5171
+ let settled = false;
5172
+ const finish = () => {
5173
+ if (settled) return;
5174
+ settled = true;
5175
+ clearTimeout(fallback);
5176
+ resolve();
5177
+ };
5178
+ const fallback = setTimeout(finish, 250);
5179
+ requestFrame(() => requestFrame(() => setTimeout(finish, 0)));
5180
+ });
5181
+ } catch {}
5182
+ };
5027
5183
  const tracePostDecisionBackgroundReconcile = (superdoc, event, payload = {}) => {
5028
5184
  const record = {
5029
5185
  event,
@@ -5175,7 +5331,7 @@ const useCommentsStore = defineStore("comments", () => {
5175
5331
  adapter: v2Adapter,
5176
5332
  documentId: effectiveDocumentId,
5177
5333
  items: listResult.items ?? [],
5178
- pruneStale: listResult.complete !== false && listResult.sourceCoverageComplete !== false
5334
+ pruneStale: listResult.complete === true && listResult.sourceCoverageComplete === true
5179
5335
  });
5180
5336
  const liveIds = new Set((listResult.items ?? []).map((item) => item && item.id != null ? String(item.id) : null).filter(Boolean));
5181
5337
  if (!(decidedId != null && liveIds.has(decidedId))) {
@@ -5219,6 +5375,17 @@ const useCommentsStore = defineStore("comments", () => {
5219
5375
  };
5220
5376
  };
5221
5377
  if (!reliableDecidedIds.size || !effectiveDocumentId) return runBlockingPostDecisionReconcile();
5378
+ reliableDecidedIds.forEach((id$1) => clearV2ActiveTrackedChangeTarget(v2Adapter, id$1));
5379
+ schedulePostDecisionBackgroundReconcile({
5380
+ superdoc,
5381
+ trackedChangesAdapter: v2Adapter,
5382
+ commentsAdapter: getV2CommentsAdapter(superdoc),
5383
+ documentId: effectiveDocumentId
5384
+ });
5385
+ await waitForTrackedDecisionCanonicalPaint({
5386
+ superdoc,
5387
+ receipt: outcome.receipt
5388
+ });
5222
5389
  resolveLinkedCommentsForTrackedChangeDecision({
5223
5390
  superdoc,
5224
5391
  comment,
@@ -5230,17 +5397,11 @@ const useCommentsStore = defineStore("comments", () => {
5230
5397
  });
5231
5398
  pruneDecidedTrackedChangeRow({
5232
5399
  superdoc,
5400
+ comment,
5233
5401
  decidedIds: reliableDecidedIds,
5234
5402
  documentId: effectiveDocumentId
5235
5403
  });
5236
5404
  if (wasActiveBeforeDecide) clearActiveCommentSelection();
5237
- reliableDecidedIds.forEach((id$1) => clearV2ActiveTrackedChangeTarget(v2Adapter, id$1));
5238
- schedulePostDecisionBackgroundReconcile({
5239
- superdoc,
5240
- trackedChangesAdapter: v2Adapter,
5241
- commentsAdapter: getV2CommentsAdapter(superdoc),
5242
- documentId: effectiveDocumentId
5243
- });
5244
5405
  return {
5245
5406
  ok: true,
5246
5407
  success: true
@@ -5309,7 +5470,7 @@ const useCommentsStore = defineStore("comments", () => {
5309
5470
  reason: "adapter-stale"
5310
5471
  };
5311
5472
  if (!result?.ok) return result;
5312
- const pruneStale = !(trackedChangesListMode === "startup-page" || trackedChangesListMode === "interaction-prime" || trackedChangesListMode === "visible-window") && result.visibleWindowSource == null && result.complete !== false && result.sourceCoverageComplete !== false;
5473
+ const pruneStale = !(trackedChangesListMode === "startup-page" || trackedChangesListMode === "interaction-prime" || trackedChangesListMode === "visible-window") && result.visibleWindowSource == null && result.complete === true && result.sourceCoverageComplete === true;
5313
5474
  reconcileTrackedChangesFromV2({
5314
5475
  superdoc,
5315
5476
  adapter: effectiveAdapter,
@@ -5346,12 +5507,12 @@ const useCommentsStore = defineStore("comments", () => {
5346
5507
  };
5347
5508
  const effectiveDocumentId = documentId ?? effectiveAdapter.documentId ?? superdoc?.activeEditor?.documentId ?? null;
5348
5509
  const normalizedRemovedIds = new Set(Array.from(removedIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean));
5349
- if (normalizedRemovedIds.size > 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
5510
+ const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean))];
5511
+ if (normalizedRemovedIds.size > 0 && normalizedUpsertIds.length === 0 && effectiveDocumentId != null) pruneDecidedTrackedChangeRow({
5350
5512
  superdoc,
5351
5513
  decidedIds: normalizedRemovedIds,
5352
5514
  documentId: effectiveDocumentId
5353
5515
  });
5354
- const normalizedUpsertIds = [...new Set(Array.from(upsertIds ?? []).map((id) => normalizeCommentId(id)).filter(Boolean))];
5355
5516
  if (normalizedUpsertIds.length === 0) return {
5356
5517
  ok: true,
5357
5518
  items: [],
@@ -5955,10 +6116,12 @@ const useCommentsStore = defineStore("comments", () => {
5955
6116
  getConfig,
5956
6117
  documentsWithConverations,
5957
6118
  getGroupedComments,
6119
+ hasOpenTrackedChanges,
5958
6120
  getCommentsByPosition,
5959
6121
  getFloatingComments,
5960
6122
  getFloatingCommentInstances,
5961
6123
  getCommentAliasIds,
6124
+ getTrackedChangeThread,
5962
6125
  getCommentPositionKey,
5963
6126
  getCommentPosition,
5964
6127
  getCommentAnchoredText,
@@ -6871,30 +7034,6 @@ var _sfc_main$33 = {
6871
7034
  }
6872
7035
  };
6873
7036
  var CommentInput_default = /* @__PURE__ */ require__plugin_vue_export_helper.__plugin_vue_export_helper_default(_sfc_main$33, [["__scopeId", "data-v-cc54b489"]]);
6874
- const collectTrackedChangeThread = (parentComment, allComments) => {
6875
- const trackedChangeId = parentComment.commentId;
6876
- const threadIds = new Set([trackedChangeId]);
6877
- const queue = [];
6878
- allComments.forEach((comment) => {
6879
- if (comment.commentId === trackedChangeId) return;
6880
- const isDirectChild = [comment.parentCommentId, comment.threadingParentCommentId].filter((id) => id != null).map((id) => String(id)).includes(trackedChangeId);
6881
- const isTrackedChangeThreadRoot = String(trackedChangeThreadParentIdForComment(comment) ?? "") === trackedChangeId && !comment.parentCommentId;
6882
- if (isDirectChild || isTrackedChangeThreadRoot) {
6883
- threadIds.add(comment.commentId);
6884
- queue.push(comment.commentId);
6885
- }
6886
- });
6887
- for (let i = 0; i < queue.length; i += 1) {
6888
- const parentId = queue[i];
6889
- allComments.forEach((comment) => {
6890
- if ([comment.parentCommentId, comment.threadingParentCommentId].filter((id) => id != null).map((id) => String(id)).includes(parentId) && !threadIds.has(comment.commentId)) {
6891
- threadIds.add(comment.commentId);
6892
- queue.push(comment.commentId);
6893
- }
6894
- });
6895
- }
6896
- return allComments.filter((comment) => threadIds.has(comment.commentId));
6897
- };
6898
7037
  var _hoisted_1$25 = [
6899
7038
  "data-comment-instance-id",
6900
7039
  "data-comment-thread-id",
@@ -6987,13 +7126,14 @@ var _hoisted_45 = {
6987
7126
  role: "alert"
6988
7127
  };
6989
7128
  var _hoisted_46 = { class: "tracked-change-decision-alert__message" };
6990
- var _hoisted_47 = {
7129
+ var _hoisted_47 = ["disabled", "data-disabled-reason"];
7130
+ var _hoisted_48 = {
6991
7131
  key: 1,
6992
7132
  class: "reply-expanded"
6993
7133
  };
6994
- var _hoisted_48 = { class: "reply-input-wrapper" };
6995
- var _hoisted_49 = { class: "reply-actions" };
6996
- var _hoisted_50 = ["disabled", "data-disabled-reason"];
7134
+ var _hoisted_49 = { class: "reply-input-wrapper" };
7135
+ var _hoisted_50 = { class: "reply-actions" };
7136
+ var _hoisted_51 = ["disabled", "data-disabled-reason"];
6997
7137
  var CLICK_OUTSIDE_HIT_TOLERANCE_PX = 3;
6998
7138
  var TRACKED_CHANGE_PREVIEW_MAX_PX = 90;
6999
7139
  var _sfc_main$32 = {
@@ -7213,8 +7353,13 @@ var _sfc_main$32 = {
7213
7353
  });
7214
7354
  const commentsAreReadOnly$1 = () => getConfig.value?.readOnly === true;
7215
7355
  const isReplying = (0, vue.ref)(false);
7356
+ const isSubmittingReply = (0, vue.ref)(false);
7216
7357
  const startReply = () => {
7217
7358
  if (commentsAreReadOnly$1()) return readOnlyMutationOutcome$1();
7359
+ if (isV2WriteDisabled.value) return {
7360
+ ok: false,
7361
+ reason: v2WriteCapability.value?.reason ?? "v2-write-unavailable"
7362
+ };
7218
7363
  isReplying.value = true;
7219
7364
  (0, vue.nextTick)(() => {
7220
7365
  commentInput.value?.focus?.();
@@ -7223,12 +7368,12 @@ var _sfc_main$32 = {
7223
7368
  };
7224
7369
  const comments = (0, vue.computed)(() => {
7225
7370
  const parentComment = props.comment;
7226
- const allComments = commentsStore.commentsList;
7227
- return (parentComment.trackedChange ? collectTrackedChangeThread(parentComment, allComments) : allComments.filter((comment) => {
7371
+ if (parentComment.trackedChange) return commentsStore.getTrackedChangeThread(parentComment);
7372
+ return commentsStore.commentsList.filter((comment) => {
7228
7373
  const isThreadedComment = comment.parentCommentId === parentComment.commentId;
7229
7374
  const isThisComment = comment.commentId === parentComment.commentId;
7230
7375
  return isThreadedComment || isThisComment;
7231
- })).sort((a, b) => {
7376
+ }).sort((a, b) => {
7232
7377
  if (a.commentId === parentComment.commentId) return -1;
7233
7378
  if (b.commentId === parentComment.commentId) return 1;
7234
7379
  return a.createdTime - b.createdTime;
@@ -7406,20 +7551,33 @@ var _sfc_main$32 = {
7406
7551
  };
7407
7552
  const handleAddComment = async () => {
7408
7553
  if (commentsAreReadOnly$1()) return readOnlyMutationOutcome$1();
7554
+ if (isV2WriteDisabled.value) return {
7555
+ ok: false,
7556
+ reason: v2WriteCapability.value?.reason ?? "v2-write-unavailable"
7557
+ };
7409
7558
  if (isV2Mode.value && v2CommentsAdapter.value && !isPendingNewComment.value) {
7410
- const outcome = await commentsStore.replyCommentV2({
7411
- superdoc: proxy.$superdoc,
7412
- parentCommentId: getV2ExistingReplyParentId(props.comment),
7413
- text: currentCommentText.value
7414
- });
7415
- if (!outcome?.ok) {
7559
+ if (isSubmittingReply.value) return {
7560
+ ok: false,
7561
+ reason: "reply-submit-in-flight"
7562
+ };
7563
+ isSubmittingReply.value = true;
7564
+ try {
7565
+ const outcome = await commentsStore.replyCommentV2({
7566
+ superdoc: proxy.$superdoc,
7567
+ parentCommentId: getV2ExistingReplyParentId(props.comment),
7568
+ text: currentCommentText.value
7569
+ });
7570
+ if (!outcome?.ok) {
7571
+ (0, vue.nextTick)(() => emit("resize"));
7572
+ return outcome;
7573
+ }
7574
+ isReplying.value = false;
7575
+ currentCommentText.value = "";
7416
7576
  (0, vue.nextTick)(() => emit("resize"));
7417
7577
  return outcome;
7578
+ } finally {
7579
+ isSubmittingReply.value = false;
7418
7580
  }
7419
- isReplying.value = false;
7420
- currentCommentText.value = "";
7421
- (0, vue.nextTick)(() => emit("resize"));
7422
- return outcome;
7423
7581
  }
7424
7582
  const options = {
7425
7583
  documentId: props.comment.fileId,
@@ -7445,7 +7603,14 @@ var _sfc_main$32 = {
7445
7603
  const v2TrackedChangesAdapter = (0, vue.computed)(() => isV2Mode.value ? proxy.$superdoc?.activeEditor?.v2TrackedChanges ?? null : null);
7446
7604
  const v2WriteCapability = (0, vue.computed)(() => {
7447
7605
  if (!v2CommentsAdapter.value) return null;
7448
- return v2CommentsAdapter.value.getCapabilityState?.() ?? null;
7606
+ const documentMode = commentsStore.viewingVisibility.documentMode;
7607
+ const capability = v2CommentsAdapter.value.getCapabilityState?.() ?? null;
7608
+ if (documentMode === "viewing") return {
7609
+ ...capability ?? {},
7610
+ canWrite: false,
7611
+ reason: "review-surface-read-only"
7612
+ };
7613
+ return capability;
7449
7614
  });
7450
7615
  const isV2WriteDisabled = (0, vue.computed)(() => {
7451
7616
  if (!isV2Mode.value) return false;
@@ -7453,6 +7618,11 @@ var _sfc_main$32 = {
7453
7618
  if (!cap) return false;
7454
7619
  return cap.canWrite === false;
7455
7620
  });
7621
+ const v2ReplyDisabledReason = (0, vue.computed)(() => {
7622
+ if (isSubmittingReply.value) return "reply-submit-in-flight";
7623
+ if (!isV2WriteDisabled.value) return null;
7624
+ return v2WriteCapability.value?.reason ?? "v2-write-unavailable";
7625
+ });
7456
7626
  const v2TrackedChangeCapability = (0, vue.computed)(() => {
7457
7627
  if (!v2TrackedChangesAdapter.value) return null;
7458
7628
  const documentMode = commentsStore.viewingVisibility.documentMode;
@@ -7762,6 +7932,11 @@ var _sfc_main$32 = {
7762
7932
  if (isPendingNewComment.value) cancelComment(proxy.$superdoc);
7763
7933
  (0, vue.nextTick)(() => emit("resize"));
7764
7934
  }, { immediate: true });
7935
+ (0, vue.watch)(isV2WriteDisabled, (isDisabled) => {
7936
+ if (!isDisabled) return;
7937
+ isReplying.value = false;
7938
+ (0, vue.nextTick)(() => emit("resize"));
7939
+ });
7765
7940
  return (_ctx, _cache) => {
7766
7941
  const _directive_click_outside = (0, vue.resolveDirective)("click-outside");
7767
7942
  return (0, vue.withDirectives)(((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
@@ -7955,11 +8130,14 @@ var _sfc_main$32 = {
7955
8130
  class: "sd-button tracked-change-decision-alert__retry",
7956
8131
  onClick: (0, vue.withModifiers)(retryTrackedChangeDecision, ["stop", "prevent"])
7957
8132
  }, " Retry ")])) : (0, vue.createCommentVNode)("", true),
7958
- showInputSection.value && !(0, vue.unref)(getConfig).readOnly ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 3 }, [!isReplying.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
8133
+ showInputSection.value && !(0, vue.unref)(getConfig).readOnly ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 3 }, [!isReplying.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
7959
8134
  key: 0,
7960
- class: "reply-pill",
7961
- onClick: (0, vue.withModifiers)(startReply, ["stop", "prevent"])
7962
- }, "Reply or add others with @")) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_47, [(0, vue.createElementVNode)("div", _hoisted_48, [(0, vue.createVNode)(CommentInput_default, {
8135
+ type: "button",
8136
+ class: (0, vue.normalizeClass)(["reply-pill", { "sd-is-disabled": isV2WriteDisabled.value }]),
8137
+ onClick: (0, vue.withModifiers)(startReply, ["stop", "prevent"]),
8138
+ disabled: isV2WriteDisabled.value,
8139
+ "data-disabled-reason": isV2WriteDisabled.value ? v2WriteCapability.value?.reason ?? null : null
8140
+ }, " Reply or add others with @ ", 10, _hoisted_47)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_48, [(0, vue.createElementVNode)("div", _hoisted_49, [(0, vue.createVNode)(CommentInput_default, {
7963
8141
  ref_key: "commentInput",
7964
8142
  ref: commentInput,
7965
8143
  users: usersFiltered.value,
@@ -7970,20 +8148,20 @@ var _sfc_main$32 = {
7970
8148
  "users",
7971
8149
  "config",
7972
8150
  "comment"
7973
- ])]), (0, vue.createElementVNode)("div", _hoisted_49, [(0, vue.createElementVNode)("button", {
8151
+ ])]), (0, vue.createElementVNode)("div", _hoisted_50, [(0, vue.createElementVNode)("button", {
7974
8152
  class: "sd-button reply-btn-cancel",
7975
8153
  onClick: (0, vue.withModifiers)(handleCancel, ["stop", "prevent"])
7976
8154
  }, "Cancel"), (0, vue.createElementVNode)("button", {
7977
- class: (0, vue.normalizeClass)(["sd-button primary reply-btn-primary", { "sd-is-disabled": !hasTextContent.value || isV2WriteDisabled.value }]),
8155
+ class: (0, vue.normalizeClass)(["sd-button primary reply-btn-primary", { "sd-is-disabled": !hasTextContent.value || isV2WriteDisabled.value || isSubmittingReply.value }]),
7978
8156
  onClick: (0, vue.withModifiers)(handleAddComment, ["stop", "prevent"]),
7979
- disabled: !hasTextContent.value || isV2WriteDisabled.value,
7980
- "data-disabled-reason": isV2WriteDisabled.value ? v2WriteCapability.value?.reason ?? null : null
7981
- }, " Reply ", 10, _hoisted_50)])]))], 64)) : (0, vue.createCommentVNode)("", true)
8157
+ disabled: !hasTextContent.value || isV2WriteDisabled.value || isSubmittingReply.value,
8158
+ "data-disabled-reason": v2ReplyDisabledReason.value
8159
+ }, " Reply ", 10, _hoisted_51)])]))], 64)) : (0, vue.createCommentVNode)("", true)
7982
8160
  ], 64))], 14, _hoisted_1$25)), [[_directive_click_outside, handleClickOutside]]);
7983
8161
  };
7984
8162
  }
7985
8163
  };
7986
- var CommentDialog_default = /* @__PURE__ */ require__plugin_vue_export_helper.__plugin_vue_export_helper_default(_sfc_main$32, [["__scopeId", "data-v-35236c9c"]]);
8164
+ var CommentDialog_default = /* @__PURE__ */ require__plugin_vue_export_helper.__plugin_vue_export_helper_default(_sfc_main$32, [["__scopeId", "data-v-74340036"]]);
7987
8165
  var _hoisted_1$24 = { class: "comments-list" };
7988
8166
  var _hoisted_2$19 = { key: 0 };
7989
8167
  var _hoisted_3$15 = [
@@ -8327,12 +8505,12 @@ const resolvePersistentReviewCardTop = ({ comment, anchorTop, anchorBottom, card
8327
8505
  if ((Number.isFinite(anchorBottom) ? anchorBottom : anchorTop) < viewportTop) return Math.min(anchorTop, viewportTop - cardHeight - 1);
8328
8506
  return anchorTop;
8329
8507
  };
8330
- const shouldMountFloatingCommentDialog = ({ id, visibleIds, activeCommentInstanceId, comment }) => {
8508
+ const shouldMountFloatingCommentDialog = ({ id, visibleIds, activeCommentInstanceId }) => {
8331
8509
  if (!id) return false;
8332
8510
  if (id === "pending") return true;
8333
8511
  if (activeCommentInstanceId != null && String(id) === String(activeCommentInstanceId)) return true;
8334
8512
  if (visibleIds?.has?.(id)) return true;
8335
- return isPersistentReviewSidebarItem(comment);
8513
+ return false;
8336
8514
  };
8337
8515
  const resolveRemovedReviewCardContinuityTarget = ({ previousIds, currentIds, removedId }) => {
8338
8516
  if (!Array.isArray(previousIds) || !(currentIds instanceof Set) || !removedId) return null;
@@ -8371,7 +8549,6 @@ var _sfc_main$30 = {
8371
8549
  setup(__props) {
8372
8550
  const ESTIMATED_HEIGHT = 110;
8373
8551
  const OBSERVER_MARGIN = 600;
8374
- const FALLBACK_ANCHOR_STEP = .001;
8375
8552
  const resolveCollisions = (positions, activeIndex, gap) => {
8376
8553
  if (activeIndex >= 0) {
8377
8554
  positions[activeIndex].top = positions[activeIndex].anchorTop;
@@ -8414,6 +8591,7 @@ var _sfc_main$30 = {
8414
8591
  const commentsRenderKey = (0, vue.ref)(0);
8415
8592
  const sidebarOffsetY = (0, vue.ref)(0);
8416
8593
  const disableInstantLayoutTransitions = (0, vue.ref)(false);
8594
+ const viewportRevision = (0, vue.ref)(0);
8417
8595
  const isPendingThread = (commentOrId) => {
8418
8596
  const pendingId = pendingComment.value?.commentId;
8419
8597
  if (!pendingId) return false;
@@ -8423,10 +8601,27 @@ var _sfc_main$30 = {
8423
8601
  const getThreadId = (comment) => {
8424
8602
  return comment?.commentId ?? comment?.importedId ?? null;
8425
8603
  };
8604
+ const floatingInstanceIndex = (0, vue.computed)(() => {
8605
+ const byId = /* @__PURE__ */ new Map();
8606
+ const byThreadId = /* @__PURE__ */ new Map();
8607
+ for (const instance of floatingCommentInstances.value) {
8608
+ if (instance?.id != null) byId.set(String(instance.id), instance);
8609
+ const threadId = getThreadId(instance?.comment);
8610
+ if (threadId == null) continue;
8611
+ const key = String(threadId);
8612
+ const threadInstances = byThreadId.get(key) ?? [];
8613
+ threadInstances.push(instance);
8614
+ byThreadId.set(key, threadInstances);
8615
+ }
8616
+ return {
8617
+ byId,
8618
+ byThreadId
8619
+ };
8620
+ });
8426
8621
  const resolveFloatingInstanceId = (threadId, preferredInstanceId = null) => {
8427
8622
  if (threadId == null) return null;
8428
8623
  const normalizedThreadId = String(threadId);
8429
- const matchingInstances = floatingCommentInstances.value.filter((instance) => String(getThreadId(instance.comment)) === normalizedThreadId);
8624
+ const matchingInstances = floatingInstanceIndex.value.byThreadId.get(normalizedThreadId) ?? [];
8430
8625
  if (!matchingInstances.length) return null;
8431
8626
  if (preferredInstanceId != null) {
8432
8627
  const preferredInstance = matchingInstances.find((instance) => String(instance.id) === String(preferredInstanceId));
@@ -8449,6 +8644,14 @@ var _sfc_main$30 = {
8449
8644
  const placeholderRefs = (0, vue.ref)({});
8450
8645
  let observer = null;
8451
8646
  const observedElements = /* @__PURE__ */ new Set();
8647
+ let viewportFrame = null;
8648
+ const refreshViewportWindow = () => {
8649
+ if (viewportFrame != null) return;
8650
+ viewportFrame = requestAnimationFrame(() => {
8651
+ viewportFrame = null;
8652
+ viewportRevision.value += 1;
8653
+ });
8654
+ };
8452
8655
  const getAnchorTop = (instance) => {
8453
8656
  if (props.currentDocument.type === "application/pdf") {
8454
8657
  const zoom = (activeZoom.value ?? 100) / 100;
@@ -8466,6 +8669,24 @@ var _sfc_main$30 = {
8466
8669
  const bottom = instance?.positionEntry?.bounds?.bottom;
8467
8670
  return Number.isFinite(bottom) ? bottom : anchorTop;
8468
8671
  };
8672
+ const geometryBearingInstances = (0, vue.computed)(() => {
8673
+ const positioned = [];
8674
+ for (const instance of floatingCommentInstances.value) {
8675
+ const id = instance?.id;
8676
+ const threadId = getThreadId(instance?.comment);
8677
+ const anchorTop = getAnchorTop(instance);
8678
+ if (id == null || threadId == null || !Number.isFinite(anchorTop)) continue;
8679
+ const top = normalizeFloatingAnchorTop(anchorTop, instance.comment);
8680
+ positioned.push({
8681
+ id,
8682
+ threadId,
8683
+ instance,
8684
+ top,
8685
+ anchorBottom: getAnchorBottom(instance, top)
8686
+ });
8687
+ }
8688
+ return positioned;
8689
+ });
8469
8690
  const getPendingAnchorTop = () => {
8470
8691
  const positionEntry = editorCommentPositions.value["pending"];
8471
8692
  if (typeof positionEntry?.bounds?.top === "number" && !isNaN(positionEntry.bounds.top)) return positionEntry.bounds.top;
@@ -8499,40 +8720,72 @@ var _sfc_main$30 = {
8499
8720
  return resolveFloatingInstanceId(instantSidebarAlignmentThreadId.value, instantSidebarAlignmentInstanceId.value);
8500
8721
  });
8501
8722
  const allPositions = (0, vue.computed)(() => {
8502
- const instances = floatingCommentInstances.value;
8723
+ viewportRevision.value;
8724
+ const positionedInstances = geometryBearingInstances.value;
8503
8725
  const hasPending = pendingComment.value && pendingComment.value.fileId === props.currentDocument.id;
8504
- if (!instances.length && !hasPending) return [];
8726
+ if (!positionedInstances.length && !floatingCommentInstances.value.length && !hasPending) return [];
8505
8727
  const positions = [];
8506
- let lastAnchoredTop = 0;
8507
- let fallbackAnchorCount = 0;
8508
- for (const instance of instances) {
8509
- const key = instance?.id;
8510
- const anchorTop = getAnchorTop(instance);
8511
- const threadId = getThreadId(instance?.comment);
8512
- if (!key || !threadId) continue;
8513
- const hasAnchorGeometry = typeof anchorTop === "number" && !isNaN(anchorTop);
8514
- if (!hasAnchorGeometry && !isPersistentReviewSidebarItem(instance?.comment)) continue;
8515
- let top;
8516
- if (hasAnchorGeometry) {
8517
- top = normalizeFloatingAnchorTop(anchorTop, instance.comment);
8518
- lastAnchoredTop = top;
8519
- fallbackAnchorCount = 0;
8520
- } else {
8521
- fallbackAnchorCount += 1;
8522
- top = lastAnchoredTop + fallbackAnchorCount * FALLBACK_ANCHOR_STEP;
8523
- }
8728
+ const viewportRange = getFloatingViewportRange() ?? {
8729
+ top: 0,
8730
+ bottom: Math.max(0, props.parent?.clientHeight || window.innerHeight || 0)
8731
+ };
8732
+ const renderTop = viewportRange.top - OBSERVER_MARGIN;
8733
+ const renderBottom = viewportRange.bottom + OBSERVER_MARGIN;
8734
+ const activeKey = hasPending ? "pending" : activeCommentInstanceId.value;
8735
+ const positionIds = /* @__PURE__ */ new Set();
8736
+ const pushPosition = ({ id, threadId, instance, top, anchorBottom, hasAnchorGeometry }) => {
8737
+ const key = String(id);
8738
+ if (positionIds.has(key)) return;
8739
+ positionIds.add(key);
8524
8740
  positions.push({
8525
- id: key,
8741
+ id,
8526
8742
  threadId,
8527
8743
  pageIndex: instance?.pageIndex ?? null,
8528
8744
  anchorTop: top,
8529
- anchorBottom: hasAnchorGeometry ? getAnchorBottom(instance, top) : top,
8745
+ anchorBottom,
8530
8746
  hasAnchorGeometry,
8531
8747
  top,
8532
- height: measuredHeights.value[key] || ESTIMATED_HEIGHT,
8748
+ height: measuredHeights.value[id] || ESTIMATED_HEIGHT,
8533
8749
  commentRef: instance.comment,
8534
8750
  instanceRef: instance
8535
8751
  });
8752
+ };
8753
+ for (const positioned of positionedInstances) {
8754
+ const { id, threadId, instance, top, anchorBottom } = positioned;
8755
+ const isActive = activeKey != null && String(id) === String(activeKey);
8756
+ const isEditing = editingCommentId.value != null && String(threadId) === String(editingCommentId.value);
8757
+ if (!isActive && !isEditing && (anchorBottom < renderTop || top > renderBottom)) continue;
8758
+ pushPosition({
8759
+ id,
8760
+ threadId,
8761
+ instance,
8762
+ top,
8763
+ anchorBottom,
8764
+ hasAnchorGeometry: true
8765
+ });
8766
+ }
8767
+ const transitionalInstances = [];
8768
+ if (activeKey && activeKey !== "pending") {
8769
+ const activeInstance = floatingInstanceIndex.value.byId.get(String(activeKey));
8770
+ if (activeInstance) transitionalInstances.push(activeInstance);
8771
+ }
8772
+ if (editingCommentId.value != null) {
8773
+ const editingInstances = floatingInstanceIndex.value.byThreadId.get(String(editingCommentId.value)) ?? [];
8774
+ const editingInstance = editingInstances.find((instance) => instance.isPrimary) ?? editingInstances[0];
8775
+ if (editingInstance) transitionalInstances.push(editingInstance);
8776
+ }
8777
+ for (const instance of transitionalInstances) {
8778
+ const id = instance.id;
8779
+ const threadId = getThreadId(instance.comment);
8780
+ if (id == null || threadId == null || positionIds.has(String(id))) continue;
8781
+ pushPosition({
8782
+ id,
8783
+ threadId,
8784
+ instance,
8785
+ top: viewportRange.top,
8786
+ anchorBottom: viewportRange.top,
8787
+ hasAnchorGeometry: false
8788
+ });
8536
8789
  }
8537
8790
  if (hasPending) {
8538
8791
  const pendingTop = getPendingAnchorTop();
@@ -8547,12 +8800,10 @@ var _sfc_main$30 = {
8547
8800
  });
8548
8801
  }
8549
8802
  positions.sort((a, b) => a.anchorTop - b.anchorTop);
8550
- const activeKey = hasPending ? "pending" : activeCommentInstanceId.value;
8551
8803
  resolveCollisions(positions, activeKey ? positions.findIndex((p) => p.id === activeKey) : -1, 15);
8552
- const viewportRange = getFloatingViewportRange();
8553
- if (viewportRange) for (const position of positions) {
8804
+ for (const position of positions) {
8554
8805
  if (!position.hasAnchorGeometry) continue;
8555
- const persistentReviewTop = resolvePersistentReviewCardTop({
8806
+ const offscreenTop = resolvePersistentReviewCardTop({
8556
8807
  comment: position.commentRef,
8557
8808
  anchorTop: position.anchorTop,
8558
8809
  anchorBottom: position.anchorBottom,
@@ -8560,21 +8811,13 @@ var _sfc_main$30 = {
8560
8811
  viewportTop: viewportRange.top,
8561
8812
  viewportBottom: viewportRange.bottom
8562
8813
  });
8563
- if (persistentReviewTop != null) position.top = persistentReviewTop;
8814
+ if (offscreenTop != null) position.top = offscreenTop;
8564
8815
  }
8565
8816
  return positions;
8566
8817
  });
8567
- const totalHeight = (0, vue.computed)(() => {
8568
- if (!allPositions.value.length) return 0;
8569
- let max = 0;
8570
- for (const p of allPositions.value) {
8571
- const bottom = p.top + p.height;
8572
- if (bottom > max) max = bottom;
8573
- }
8574
- return max + 50;
8575
- });
8576
8818
  const wrapperMinHeight = (0, vue.computed)(() => {
8577
- return totalHeight.value + Math.max(0, sidebarOffsetY.value);
8819
+ viewportRevision.value;
8820
+ return Math.max(0, props.parent?.clientHeight || 0, props.parent?.scrollHeight || 0);
8578
8821
  });
8579
8822
  const setupObserver = () => {
8580
8823
  if (observer) observer.disconnect();
@@ -8932,6 +9175,8 @@ var _sfc_main$30 = {
8932
9175
  });
8933
9176
  (0, vue.onMounted)(() => {
8934
9177
  setupObserver();
9178
+ props.parent?.addEventListener?.("scroll", refreshViewportWindow, { passive: true });
9179
+ window.addEventListener("resize", refreshViewportWindow, { passive: true });
8935
9180
  (0, vue.nextTick)(observePlaceholders);
8936
9181
  });
8937
9182
  (0, vue.onBeforeUnmount)(() => {
@@ -8942,6 +9187,12 @@ var _sfc_main$30 = {
8942
9187
  cancelAnimationFrame(activeLayoutContinuityFrame);
8943
9188
  activeLayoutContinuityFrame = null;
8944
9189
  }
9190
+ props.parent?.removeEventListener?.("scroll", refreshViewportWindow);
9191
+ window.removeEventListener("resize", refreshViewportWindow);
9192
+ if (viewportFrame != null) {
9193
+ cancelAnimationFrame(viewportFrame);
9194
+ viewportFrame = null;
9195
+ }
8945
9196
  if (observer) {
8946
9197
  observer.disconnect();
8947
9198
  observer = null;
@@ -9003,7 +9254,7 @@ var _sfc_main$30 = {
9003
9254
  };
9004
9255
  }
9005
9256
  };
9006
- var FloatingComments_default = /* @__PURE__ */ require__plugin_vue_export_helper.__plugin_vue_export_helper_default(_sfc_main$30, [["__scopeId", "data-v-3672b976"]]);
9257
+ var FloatingComments_default = /* @__PURE__ */ require__plugin_vue_export_helper.__plugin_vue_export_helper_default(_sfc_main$30, [["__scopeId", "data-v-e699ceb8"]]);
9007
9258
  var _hoisted_1$22 = {
9008
9259
  class: "superdoc__pdf-comments-layer pdf-comments-layer",
9009
9260
  "aria-hidden": "false"
@@ -11866,7 +12117,34 @@ function useLinkPopover({ getSurfaceManager, getActiveEditor, getUi, getResolver
11866
12117
  tick();
11867
12118
  });
11868
12119
  }
11869
- async function navigateToAnchor(anchorUrl) {
12120
+ async function placeCaretAtHeadingStart(blockId) {
12121
+ const setSelectionTarget = getActiveEditor()?.authoring?.setSelectionTarget;
12122
+ if (typeof setSelectionTarget !== "function") return;
12123
+ const story = {
12124
+ kind: "story",
12125
+ storyType: "body"
12126
+ };
12127
+ const anchor = {
12128
+ kind: "text",
12129
+ blockId,
12130
+ offset: 0,
12131
+ story
12132
+ };
12133
+ const target = {
12134
+ kind: "selection",
12135
+ start: anchor,
12136
+ end: anchor,
12137
+ story
12138
+ };
12139
+ try {
12140
+ await Promise.resolve(setSelectionTarget({
12141
+ target,
12142
+ collapse: "start",
12143
+ focus: true
12144
+ }));
12145
+ } catch {}
12146
+ }
12147
+ async function navigateToAnchor(anchorUrl, { placeCaret = false } = {}) {
11870
12148
  if (typeof anchorUrl !== "string" || !anchorUrl.startsWith("#")) return;
11871
12149
  const anchorName = anchorUrl.slice(1);
11872
12150
  const escapedAnchorName = cssEscape(anchorName);
@@ -11891,6 +12169,7 @@ function useLinkPopover({ getSurfaceManager, getActiveEditor, getUi, getResolver
11891
12169
  if (story && story.storyType !== "body") return;
11892
12170
  const blockId = bookmark?.range?.from?.blockId;
11893
12171
  if (typeof blockId !== "string") return;
12172
+ if (placeCaret) await placeCaretAtHeadingStart(blockId);
11894
12173
  const mountedParagraph = findPaintedParagraphByParaId(getUi()?.viewport?.getHost?.() ?? null, blockId);
11895
12174
  if (mountedParagraph) {
11896
12175
  scrollToElement(mountedParagraph);
@@ -12000,7 +12279,7 @@ function useLinkPopover({ getSurfaceManager, getActiveEditor, getUi, getResolver
12000
12279
  function openDefaultPopover(ctx) {
12001
12280
  closeCurrentPopover("replace");
12002
12281
  if (ctx.isAnchorLink && isTocEntryLinkElement(ctx.element)) {
12003
- navigateToAnchor(ctx.href);
12282
+ navigateToAnchor(ctx.href, { placeCaret: ctx.documentMode !== "viewing" });
12004
12283
  return;
12005
12284
  }
12006
12285
  if (ctx.documentMode === "viewing") {
@@ -12016,7 +12295,7 @@ function useLinkPopover({ getSurfaceManager, getActiveEditor, getUi, getResolver
12016
12295
  const closePopover = () => closeCurrentPopover("closed");
12017
12296
  const goToAnchor = (anchorUrl) => {
12018
12297
  closePopover();
12019
- navigateToAnchor(anchorUrl);
12298
+ navigateToAnchor(anchorUrl, { placeCaret: true });
12020
12299
  };
12021
12300
  const generation = openGeneration;
12022
12301
  const openWithHyperlinkTarget = (hyperlinkTarget) => {
@@ -13779,7 +14058,7 @@ var _sfc_main$20 = {
13779
14058
  __name: "SuperDoc",
13780
14059
  emits: ["selection-update"],
13781
14060
  setup(__props, { emit: __emit }) {
13782
- const PdfViewer = (0, vue.defineAsyncComponent)(() => Promise.resolve().then(() => require("./chunks/PdfViewer-BtuYf2yM.cjs")));
14061
+ const PdfViewer = (0, vue.defineAsyncComponent)(() => Promise.resolve().then(() => require("./chunks/PdfViewer-CoFiGAhm.cjs")));
13783
14062
  const getDocumentLoadPassword = (doc) => doc.password ?? proxy.$superdoc.config.password;
13784
14063
  const resolveDocumentV2Collaboration = (doc) => {
13785
14064
  const rawConfig = doc?.v2Collaboration;
@@ -13824,7 +14103,7 @@ var _sfc_main$20 = {
13824
14103
  });
13825
14104
  }
13826
14105
  });
13827
- const { getConfig, documentsWithConverations, commentsList, pendingComment, activeComment, skipSelectionUpdate, commentsByDocument, isCommentsListVisible, isFloatingCommentsReady, generalCommentIds, hasSyncedCollaborationComments, editorCommentPositions, hasInitializedLocations, isCommentHighlighted } = storeToRefs(commentsStore);
14106
+ const { getConfig, documentsWithConverations, commentsList, pendingComment, activeComment, skipSelectionUpdate, commentsByDocument, isCommentsListVisible, isFloatingCommentsReady, generalCommentIds, hasSyncedCollaborationComments, editorCommentPositions, hasInitializedLocations, isCommentHighlighted, hasOpenTrackedChanges } = storeToRefs(commentsStore);
13828
14107
  const { showAddComment, handleEditorLocationsUpdate, handleTrackedChangeUpdate, refreshTrackedChangeCommentsByIds, syncTrackedChangePositionsWithDocument, syncTrackedChangeComments, addComment, addHydratedComment, getComment, resolveCommentPositionEntry, belongsToDocument, COMMENT_EVENTS, requestInstantSidebarAlignment, peekInstantSidebarAlignment, clearInstantSidebarAlignment } = commentsStore;
13829
14108
  const { proxy } = (0, vue.getCurrentInstance)();
13830
14109
  commentsStore.proxy = proxy;
@@ -14004,18 +14283,19 @@ var _sfc_main$20 = {
14004
14283
  if (!documentId) return;
14005
14284
  clearV2CommandShortcutBinding(documentId);
14006
14285
  if (typeof bindEditShortcuts !== "function") return;
14007
- const ui = require_create_super_doc_ui.createSuperDocUI({ superdoc: proxy.$superdoc });
14286
+ const fallbackUi = require_create_super_doc_ui.createSuperDocUI({ superdoc: proxy.$superdoc });
14287
+ const resolveUi = () => proxy.$superdoc?.toolbar?.ui ?? fallbackUi;
14008
14288
  const openLinkWorkflow = resolveV2LinkWorkflowOpener();
14009
14289
  const unbind = bindEditShortcuts({
14010
14290
  commandRoute: {
14011
- executeAsync: (commandId, payload) => ui.commands.executeAsync(commandId, payload),
14012
- getState: (commandId) => ui.commands.get(commandId).getState()
14291
+ executeAsync: (commandId, payload) => resolveUi().commands.executeAsync(commandId, payload),
14292
+ getState: (commandId) => resolveUi().commands.get(commandId).getState()
14013
14293
  },
14014
14294
  ...openLinkWorkflow ? { openLinkWorkflow } : {}
14015
14295
  });
14016
14296
  v2CommandShortcutBindings.set(documentId, {
14017
14297
  unbind,
14018
- ui
14298
+ ui: fallbackUi
14019
14299
  });
14020
14300
  };
14021
14301
  const clearV2SessionShortcutBinding = (documentId) => {
@@ -14839,13 +15119,17 @@ var _sfc_main$20 = {
14839
15119
  v2RulerReady.value = false;
14840
15120
  };
14841
15121
  let v2TypingReviewHydrationTimer = 0;
14842
- const hydrateV2ReviewRowsFromHost = (options = {}) => {
15122
+ const hydrateV2CommentRowsFromHost = () => {
14843
15123
  const commentsAdapter = proxy.$superdoc?.activeEditor?.v2Comments ?? null;
14844
- if (commentsAdapter) commentsStore.hydrateCommentsFromV2?.({
15124
+ if (!commentsAdapter) return;
15125
+ commentsStore.hydrateCommentsFromV2?.({
14845
15126
  superdoc: proxy.$superdoc,
14846
15127
  adapter: commentsAdapter,
14847
15128
  documentId: proxy.$superdoc?.activeEditor?.documentId ?? null
14848
15129
  });
15130
+ };
15131
+ const hydrateV2ReviewRowsFromHost = (options = {}) => {
15132
+ hydrateV2CommentRowsFromHost();
14849
15133
  const trackedChangesAdapter = proxy.$superdoc?.activeEditor?.v2TrackedChanges ?? null;
14850
15134
  if (!trackedChangesAdapter) return;
14851
15135
  commentsStore.hydrateTrackedChangesFromV2?.({
@@ -14948,6 +15232,10 @@ var _sfc_main$20 = {
14948
15232
  const receipt = event.origin === "history" ? null : event.receipt;
14949
15233
  const canWaitForExactPaint = reviewImpact && typeof receipt?.txId === "string" && receipt.txId.length > 0 && typeof readiness?.whenPainted === "function";
14950
15234
  v2ReviewMutationReconciler.enqueueAfterPaint(reviewImpact, canWaitForExactPaint ? () => readiness.whenPainted.call(readiness, receipt) : null);
15235
+ if (reviewImpact?.reconcileMode === "authoritative" && reviewImpact.upsertIds.size > 0) {
15236
+ hydrateV2CommentRowsFromHost();
15237
+ return;
15238
+ }
14951
15239
  if (require_create_super_doc_ui.isV2EditableTextMutationEvent(event)) {
14952
15240
  scheduleV2TypingReviewHydration();
14953
15241
  return;
@@ -15409,7 +15697,7 @@ var _sfc_main$20 = {
15409
15697
  if (isV2Mode.value && !v2GeometryAvailable.value && !hasPdfFloatingComments.value) return false;
15410
15698
  if (!shouldRenderCommentsInViewing.value) return false;
15411
15699
  if (!shouldUseSidebarComments.value) return false;
15412
- return pendingComment.value || floatingComments.value.length > 0 && isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value;
15700
+ return pendingComment.value || (floatingComments.value.length > 0 || hasOpenTrackedChanges.value) && isReady.value && layers.value && isCommentsEnabled.value && !isCommentsListVisible.value;
15413
15701
  });
15414
15702
  const activeCompactComment = (0, vue.computed)(() => {
15415
15703
  if (showCommentsSidebar.value) return null;
@@ -16078,7 +16366,7 @@ var _sfc_main$20 = {
16078
16366
  };
16079
16367
  }
16080
16368
  };
16081
- var SuperDoc_default = /* @__PURE__ */ require__plugin_vue_export_helper.__plugin_vue_export_helper_default(_sfc_main$20, [["__scopeId", "data-v-c4ab995e"]]);
16369
+ var SuperDoc_default = /* @__PURE__ */ require__plugin_vue_export_helper.__plugin_vue_export_helper_default(_sfc_main$20, [["__scopeId", "data-v-4084bf1b"]]);
16082
16370
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
16083
16371
  var PINIA_DEVTOOLS_PLUGIN_ID = "dev.esm.pinia";
16084
16372
  var piniaDevtoolsSuppressionState = {
@@ -34647,7 +34935,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
34647
34935
  this.config.colors = shuffleArray(this.config.colors);
34648
34936
  this.userColorMap = /* @__PURE__ */ new Map();
34649
34937
  this.colorIndex = 0;
34650
- this.version = "2.0.0-next.28";
34938
+ this.version = "2.0.0-next.29";
34651
34939
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
34652
34940
  this.superdocId = config.superdocId || require_uuid.v4_default();
34653
34941
  this.colors = this.config.colors ?? [];