superdoc 2.5.1 → 2.6.0-next.10

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
@@ -5694,7 +5694,7 @@ function useComment(params) {
5694
5694
  const fileType = params.fileType;
5695
5695
  const createdAtVersionNumber = params.createdAtVersionNumber;
5696
5696
  const isInternal = (0, vue.ref)(params.isInternal !== void 0 ? params.isInternal : true);
5697
- const mentions = (0, vue.ref)([]);
5697
+ const mentions = (0, vue.ref)(Array.isArray(params.mentions) ? params.mentions : []);
5698
5698
  const commentElement = (0, vue.ref)(null);
5699
5699
  const isFocused = (0, vue.ref)(params.isFocused || false);
5700
5700
  const creatorId = (0, vue.ref)(params.creatorId ?? null);
@@ -7254,6 +7254,7 @@ var useCommentsStore = defineStore("comments", () => {
7254
7254
  const overlappedIds = /* @__PURE__ */ new Set([]);
7255
7255
  const suppressInternalExternal = (0, vue.ref)(true);
7256
7256
  const currentCommentText = (0, vue.ref)("");
7257
+ const currentCommentMentions = (0, vue.ref)([]);
7257
7258
  const commentsList = (0, vue.ref)([]);
7258
7259
  const reviewDirectoryList = (0, vue.shallowRef)([]);
7259
7260
  let priorTrackedChangeThreadIndex = /* @__PURE__ */ new Map();
@@ -7443,9 +7444,10 @@ var useCommentsStore = defineStore("comments", () => {
7443
7444
  */
7444
7445
  const getComment = (id) => {
7445
7446
  if (id === void 0 || id === null) return null;
7446
- const directMatch = commentsList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id);
7447
- if (directMatch) return directMatch;
7448
- return reviewDirectoryList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id) || getTrackedChangeCommentByPositionAlias(id);
7447
+ const byPrimaryId = (c) => c.commentId == id;
7448
+ const byAliasId = (c) => c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id;
7449
+ const findIn = (list) => list.find(byPrimaryId) ?? list.find(byAliasId);
7450
+ return findIn(commentsList.value) ?? findIn(reviewDirectoryList.value) ?? getTrackedChangeCommentByPositionAlias(id);
7449
7451
  };
7450
7452
  const getThreadParent = (comment) => {
7451
7453
  if (!comment?.parentCommentId) return comment;
@@ -8335,6 +8337,7 @@ var useCommentsStore = defineStore("comments", () => {
8335
8337
  const removePendingComment = (superdoc) => {
8336
8338
  const hadPending = !!pendingComment.value;
8337
8339
  currentCommentText.value = "";
8340
+ currentCommentMentions.value = [];
8338
8341
  pendingComment.value = null;
8339
8342
  pendingV2CommentTarget.value = null;
8340
8343
  superdocStore.selectionPosition = null;
@@ -8354,16 +8357,19 @@ var useCommentsStore = defineStore("comments", () => {
8354
8357
  const v2Adapter = !skipEditorUpdate && !isHydration ? getV2CommentsAdapter(superdoc) : null;
8355
8358
  if (v2Adapter && !comment.trackedChange) {
8356
8359
  const text = normalizeV2CommentDraftText(pendingComment.value ? currentCommentText.value : comment.commentText);
8360
+ const mentions = pendingComment.value ? currentCommentMentions.value : comment.mentions ?? [];
8357
8361
  const target = pendingComment.value ? pendingV2CommentTarget.value : null;
8358
8362
  const parentCommentId = comment.parentCommentId ? String(comment.parentCommentId) : pendingComment.value ? null : null;
8359
8363
  return (async () => {
8360
8364
  try {
8361
8365
  return await (parentCommentId ? v2Adapter.reply({
8362
8366
  parentCommentId,
8363
- text
8367
+ text,
8368
+ ...mentions.length ? { mentions } : {}
8364
8369
  }) : v2Adapter.commitPendingComment({
8365
8370
  text,
8366
- target
8371
+ target,
8372
+ ...mentions.length ? { mentions } : {}
8367
8373
  }));
8368
8374
  } catch (err) {
8369
8375
  return {
@@ -8824,7 +8830,7 @@ var useCommentsStore = defineStore("comments", () => {
8824
8830
  * - rejection preserves rows and emits a rejected `comments-update` event
8825
8831
  * - committed-but-refresh-failed surfaces honestly (no reconcile with `[]`)
8826
8832
  */
8827
- const replyCommentV2 = async ({ superdoc, parentCommentId, text } = {}) => {
8833
+ const replyCommentV2 = async ({ superdoc, parentCommentId, text, mentions = [] } = {}) => {
8828
8834
  if (commentsAreReadOnly()) return readOnlyMutationOutcome();
8829
8835
  const v2Adapter = getV2CommentsAdapter(superdoc);
8830
8836
  if (!v2Adapter) return {
@@ -8849,7 +8855,8 @@ var useCommentsStore = defineStore("comments", () => {
8849
8855
  fileId,
8850
8856
  operation: () => v2Adapter.reply({
8851
8857
  parentCommentId: normalizedParent,
8852
- text: plainText
8858
+ text: plainText,
8859
+ ...mentions.length ? { mentions } : {}
8853
8860
  }),
8854
8861
  eventType: COMMENT_EVENTS.ADD,
8855
8862
  rejectionFallbackReason: "v2-reply-failed",
@@ -9305,6 +9312,7 @@ var useCommentsStore = defineStore("comments", () => {
9305
9312
  };
9306
9313
  const applyUpdate = (existing, input) => {
9307
9314
  existing.commentText = input.commentText ?? existing.commentText;
9315
+ existing.mentions = Array.isArray(input.mentions) ? input.mentions : [];
9308
9316
  existing.isInternal = typeof input.isInternal === "boolean" ? input.isInternal : existing.isInternal;
9309
9317
  if (typeof input.resolvedTime === "number") {
9310
9318
  existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime;
@@ -9354,6 +9362,58 @@ var useCommentsStore = defineStore("comments", () => {
9354
9362
  return { added: addedComments[addedComments.length - 1] ?? null };
9355
9363
  });
9356
9364
  /**
9365
+ * Reconcile and publish a root comment created by the private V2 context menu.
9366
+ * That path owns selection capture, while this store remains the sole owner
9367
+ * of shared comment rows and public comments-update events.
9368
+ */
9369
+ const announceV2CommentCreated = async ({ superdoc, commentId } = {}) => {
9370
+ const id = normalizeCommentId(commentId);
9371
+ const adapter = getV2CommentsAdapter(superdoc);
9372
+ const commentsApi = superdoc?.activeEditor?.doc?.comments;
9373
+ if (!id) return {
9374
+ ok: false,
9375
+ reason: "comment-id-missing"
9376
+ };
9377
+ if (!adapter || !isCurrentV2CommentsAdapter(adapter)) return {
9378
+ ok: false,
9379
+ reason: "comments-adapter-stale"
9380
+ };
9381
+ if (typeof commentsApi?.get !== "function") return {
9382
+ ok: false,
9383
+ reason: "document-api-unavailable"
9384
+ };
9385
+ let item;
9386
+ try {
9387
+ item = await commentsApi.get({ commentId: id });
9388
+ } catch (err) {
9389
+ return {
9390
+ ok: false,
9391
+ reason: "comment-read-failed",
9392
+ detail: err?.message ?? String(err)
9393
+ };
9394
+ }
9395
+ if (!isCurrentV2CommentsAdapter(adapter)) return {
9396
+ ok: false,
9397
+ reason: "comments-adapter-stale"
9398
+ };
9399
+ const comment = reconcileCommentsFromV2({
9400
+ superdoc,
9401
+ adapter,
9402
+ documentId: adapter.documentId,
9403
+ items: [item],
9404
+ pruneStale: false
9405
+ })?.added ?? commentsList.value.find((candidate) => String(candidate?.commentId ?? "") === id || String(candidate?.importedId ?? "") === id) ?? null;
9406
+ const event = {
9407
+ type: COMMENT_EVENTS.ADD,
9408
+ comment: comment?.getValues?.() ?? null
9409
+ };
9410
+ superdoc?.emit?.("comments-update", event);
9411
+ return {
9412
+ ok: true,
9413
+ comment
9414
+ };
9415
+ };
9416
+ /**
9357
9417
  * Cancel the pending comment
9358
9418
  *
9359
9419
  * @returns {void}
@@ -9554,7 +9614,7 @@ var useCommentsStore = defineStore("comments", () => {
9554
9614
  });
9555
9615
  commentsList.value = commentsList.value.filter((comment) => !removedComments.includes(comment));
9556
9616
  if (removedAliasIds.size) {
9557
- const nextPositions = { ...editorCommentPositions.value || {} };
9617
+ const nextPositions = { ...editorCommentPositions.value };
9558
9618
  removedAliasIds.forEach((id) => {
9559
9619
  delete nextPositions[id];
9560
9620
  });
@@ -11075,6 +11135,7 @@ var useCommentsStore = defineStore("comments", () => {
11075
11135
  suppressInternalExternal,
11076
11136
  pendingComment,
11077
11137
  currentCommentText,
11138
+ currentCommentMentions,
11078
11139
  commentsList,
11079
11140
  reviewDirectoryList,
11080
11141
  isCommentsListVisible,
@@ -11150,6 +11211,7 @@ var useCommentsStore = defineStore("comments", () => {
11150
11211
  getV2CommentsAdapter,
11151
11212
  applyReviewWindowFromV2,
11152
11213
  reconcileCommentsFromV2,
11214
+ announceV2CommentCreated,
11153
11215
  isV2EditorActive,
11154
11216
  replyCommentV2,
11155
11217
  editCommentV2,
@@ -11298,13 +11360,13 @@ function useUiFontFamily() {
11298
11360
  //#endregion
11299
11361
  //#region src/components/CommentsLayer/CommentsDropdown.vue
11300
11362
  var _hoisted_1$31 = { class: "comments-dropdown" };
11301
- var _hoisted_2$24 = ["onClick"];
11302
- var _hoisted_3$20 = {
11363
+ var _hoisted_2$25 = ["onClick"];
11364
+ var _hoisted_3$21 = {
11303
11365
  key: 0,
11304
11366
  class: "comments-dropdown__option-icon"
11305
11367
  };
11306
- var _hoisted_4$12 = ["innerHTML"];
11307
- var _hoisted_5$8 = { class: "comments-dropdown__option-label" };
11368
+ var _hoisted_4$13 = ["innerHTML"];
11369
+ var _hoisted_5$9 = { class: "comments-dropdown__option-label" };
11308
11370
  var _sfc_main$38 = {
11309
11371
  __name: "CommentsDropdown",
11310
11372
  props: {
@@ -11444,13 +11506,13 @@ var _sfc_main$38 = {
11444
11506
  key: option.key,
11445
11507
  class: (0, vue.normalizeClass)(["comments-dropdown__option", { "sd-disabled": option.disabled }]),
11446
11508
  onClick: ($event) => onOptionClick(option)
11447
- }, [hasIcon(option) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$20, [option.iconString ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
11509
+ }, [hasIcon(option) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$21, [option.iconString ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", {
11448
11510
  key: 0,
11449
11511
  innerHTML: option.iconString
11450
- }, null, 8, _hoisted_4$12)) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(OptionIcon), {
11512
+ }, null, 8, _hoisted_4$13)) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(OptionIcon), {
11451
11513
  key: 1,
11452
11514
  option
11453
- }, null, 8, ["option"]))])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", _hoisted_5$8, (0, vue.toDisplayString)(option.label), 1)], 10, _hoisted_2$24);
11515
+ }, null, 8, ["option"]))])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("span", _hoisted_5$9, (0, vue.toDisplayString)(option.label), 1)], 10, _hoisted_2$25);
11454
11516
  }), 128))], 4)) : (0, vue.createCommentVNode)("", true)]))]);
11455
11517
  };
11456
11518
  }
@@ -11459,9 +11521,9 @@ var CommentsDropdown_default = /*#__PURE__*/ require__plugin_vue_export_helper._
11459
11521
  //#endregion
11460
11522
  //#region src/components/CommentsLayer/InternalDropdown.vue
11461
11523
  var _hoisted_1$30 = { class: "sd-comment-option" };
11462
- var _hoisted_2$23 = ["innerHTML"];
11463
- var _hoisted_3$19 = { class: "sd-option-state" };
11464
- var _hoisted_4$11 = ["innerHTML"];
11524
+ var _hoisted_2$24 = ["innerHTML"];
11525
+ var _hoisted_3$20 = { class: "sd-option-state" };
11526
+ var _hoisted_4$12 = ["innerHTML"];
11465
11527
  var _sfc_main$37 = {
11466
11528
  __name: "InternalDropdown",
11467
11529
  props: {
@@ -11533,12 +11595,12 @@ var _sfc_main$37 = {
11533
11595
  (0, vue.createElementVNode)("div", {
11534
11596
  class: "sd-active-icon",
11535
11597
  innerHTML: activeIcon.value
11536
- }, null, 8, _hoisted_2$23),
11537
- (0, vue.createElementVNode)("div", _hoisted_3$19, (0, vue.toDisplayString)(getState.value), 1),
11598
+ }, null, 8, _hoisted_2$24),
11599
+ (0, vue.createElementVNode)("div", _hoisted_3$20, (0, vue.toDisplayString)(getState.value), 1),
11538
11600
  (0, vue.createElementVNode)("div", {
11539
11601
  class: "sd-dropdown-caret",
11540
11602
  innerHTML: (0, vue.unref)(superdocIcons).caretDown
11541
- }, null, 8, _hoisted_4$11)
11603
+ }, null, 8, _hoisted_4$12)
11542
11604
  ])]),
11543
11605
  _: 1
11544
11606
  }, 8, ["disabled", "content-style"])], 4);
@@ -11689,8 +11751,8 @@ var isAllowed = (permission, role, isInternal, context = {}) => {
11689
11751
  //#endregion
11690
11752
  //#region src/components/general/Avatar.vue
11691
11753
  var _hoisted_1$29 = { class: "user-container" };
11692
- var _hoisted_2$22 = ["src"];
11693
- var _hoisted_3$18 = {
11754
+ var _hoisted_2$23 = ["src"];
11755
+ var _hoisted_3$19 = {
11694
11756
  key: 1,
11695
11757
  class: "user-bg"
11696
11758
  };
@@ -11715,7 +11777,7 @@ var _sfc_main$36 = {
11715
11777
  key: 0,
11716
11778
  class: "user-bg",
11717
11779
  src: __props.user.image.startsWith("http") ? __props.user.image : `data:image/png;base64,${__props.user.image}`
11718
- }, null, 8, _hoisted_2$22)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$18, (0, vue.toDisplayString)(getInitials(__props.user.name, __props.user.email)), 1))]);
11780
+ }, null, 8, _hoisted_2$23)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_3$19, (0, vue.toDisplayString)(getInitials(__props.user.name, __props.user.email)), 1))]);
11719
11781
  };
11720
11782
  }
11721
11783
  };
@@ -11723,14 +11785,14 @@ var Avatar_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue
11723
11785
  //#endregion
11724
11786
  //#region src/components/CommentsLayer/CommentHeader.vue
11725
11787
  var _hoisted_1$28 = { class: "card-section comment-header" };
11726
- var _hoisted_2$21 = { class: "comment-header-left" };
11727
- var _hoisted_3$17 = { class: "user-info" };
11728
- var _hoisted_4$10 = { class: "user-name" };
11729
- var _hoisted_5$7 = {
11788
+ var _hoisted_2$22 = { class: "comment-header-left" };
11789
+ var _hoisted_3$18 = { class: "user-info" };
11790
+ var _hoisted_4$11 = { class: "user-name" };
11791
+ var _hoisted_5$8 = {
11730
11792
  key: 0,
11731
11793
  class: "imported-tag"
11732
11794
  };
11733
- var _hoisted_6$6 = {
11795
+ var _hoisted_6$7 = {
11734
11796
  key: 0,
11735
11797
  class: "user-timestamp"
11736
11798
  };
@@ -11955,10 +12017,10 @@ var _sfc_main$35 = {
11955
12017
  return user;
11956
12018
  });
11957
12019
  return (_ctx, _cache) => {
11958
- return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$28, [(0, vue.createElementVNode)("div", _hoisted_2$21, [(0, vue.createVNode)(Avatar_default, {
12020
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$28, [(0, vue.createElementVNode)("div", _hoisted_2$22, [(0, vue.createVNode)(Avatar_default, {
11959
12021
  user: getCurrentUser.value,
11960
12022
  class: "avatar"
11961
- }, null, 8, ["user"]), (0, vue.createElementVNode)("div", _hoisted_3$17, [(0, vue.createElementVNode)("div", _hoisted_4$10, [(0, vue.createTextVNode)((0, vue.toDisplayString)(getCurrentUser.value.name), 1), isImported.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$7, "IMPORTED")) : (0, vue.createCommentVNode)("", true)]), props.comment.createdTime ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$6, (0, vue.toDisplayString)((0, vue.unref)(formatDate)(props.comment.createdTime)), 1)) : (0, vue.createCommentVNode)("", true)])]), (0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["overflow-menu", { "is-visible": props.isActive }]) }, [
12023
+ }, null, 8, ["user"]), (0, vue.createElementVNode)("div", _hoisted_3$18, [(0, vue.createElementVNode)("div", _hoisted_4$11, [(0, vue.createTextVNode)((0, vue.toDisplayString)(getCurrentUser.value.name), 1), isImported.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$8, "IMPORTED")) : (0, vue.createCommentVNode)("", true)]), props.comment.createdTime ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$7, (0, vue.toDisplayString)((0, vue.unref)(formatDate)(props.comment.createdTime)), 1)) : (0, vue.createCommentVNode)("", true)])]), (0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["overflow-menu", { "is-visible": props.isActive }]) }, [
11962
12024
  allowResolve.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
11963
12025
  key: 0,
11964
12026
  class: (0, vue.normalizeClass)(["overflow-menu__icon", { "sd-is-disabled": Boolean(__props.resolveDisabledReason) }]),
@@ -12008,8 +12070,24 @@ var CommentHeader_default = /*#__PURE__*/ require__plugin_vue_export_helper._plu
12008
12070
  //#endregion
12009
12071
  //#region src/components/CommentsLayer/CommentInput.vue
12010
12072
  var _hoisted_1$27 = { class: "input-section" };
12011
- var TEXTAREA_MIN_HEIGHT = 28;
12012
- var TEXTAREA_MAX_HEIGHT = 132;
12073
+ var _hoisted_2$21 = { class: "comment-composer" };
12074
+ var _hoisted_3$17 = [
12075
+ "aria-expanded",
12076
+ "aria-controls",
12077
+ "aria-activedescendant"
12078
+ ];
12079
+ var _hoisted_4$10 = [
12080
+ "id",
12081
+ "aria-selected",
12082
+ "data-sd-comment-mention-option",
12083
+ "onMousedown"
12084
+ ];
12085
+ var _hoisted_5$7 = { class: "comment-mention-option__name" };
12086
+ var _hoisted_6$6 = {
12087
+ key: 0,
12088
+ class: "comment-mention-option__email"
12089
+ };
12090
+ var commentInputSequence = 0;
12013
12091
  var _sfc_main$34 = {
12014
12092
  __name: "CommentInput",
12015
12093
  props: {
@@ -12037,15 +12115,23 @@ var _sfc_main$34 = {
12037
12115
  },
12038
12116
  emits: ["focus"],
12039
12117
  setup(__props, { expose: __expose, emit: __emit }) {
12118
+ const TEXTAREA_MIN_HEIGHT = 28;
12119
+ const TEXTAREA_MAX_HEIGHT = 132;
12040
12120
  const emit = __emit;
12041
- const { currentCommentText } = storeToRefs(useCommentsStore());
12121
+ const props = __props;
12122
+ const { currentCommentText, currentCommentMentions } = storeToRefs(useCommentsStore());
12042
12123
  const inputRef = (0, vue.ref)(null);
12124
+ const mentionStart = (0, vue.ref)(null);
12125
+ const mentionQuery = (0, vue.ref)("");
12126
+ const highlightedMentionIndex = (0, vue.ref)(0);
12127
+ const mentionListId = `sd-comment-mention-list-${++commentInputSequence}`;
12043
12128
  const handleFocusChange = (focused) => emit("focus", focused);
12044
- const focus = () => {
12045
- inputRef.value?.focus?.();
12129
+ const getInputElement = () => inputRef.value;
12130
+ const focus = (options) => {
12131
+ getInputElement()?.focus?.(options);
12046
12132
  };
12047
12133
  const syncInputHeight = () => {
12048
- const input = inputRef.value;
12134
+ const input = getInputElement();
12049
12135
  if (!input) return;
12050
12136
  input.style.height = `${TEXTAREA_MIN_HEIGHT}px`;
12051
12137
  const scrollHeight = input.scrollHeight || TEXTAREA_MIN_HEIGHT;
@@ -12076,6 +12162,79 @@ var _sfc_main$34 = {
12076
12162
  currentCommentText.value = textToHtml(value);
12077
12163
  }
12078
12164
  });
12165
+ const displayName = (user) => user?.name?.trim() || user?.email?.trim() || "";
12166
+ const userKey = (user) => String(user?.id ?? user?.email ?? user?.name ?? "");
12167
+ const isViewer = (user) => user?.role === "viewer" || user?.access === "viewer" || typeof user?.access === "object" && user.access?.role === "viewer";
12168
+ const mentionIdentity = (user) => ({
12169
+ ...user?.id != null ? { id: user.id } : {},
12170
+ ...user?.name != null ? { name: user.name } : {},
12171
+ ...user?.email != null ? { email: user.email } : {}
12172
+ });
12173
+ const mentionSuggestions = (0, vue.computed)(() => {
12174
+ if (mentionStart.value == null) return [];
12175
+ const needle = mentionQuery.value.toLocaleLowerCase();
12176
+ return props.users.filter((user) => !isViewer(user) && displayName(user)).filter((user) => {
12177
+ const name = displayName(user).toLocaleLowerCase();
12178
+ const email = user?.email?.toLocaleLowerCase() ?? "";
12179
+ return !needle || name.startsWith(needle) || email.startsWith(needle);
12180
+ }).slice(0, 8);
12181
+ });
12182
+ const mentionListOpen = (0, vue.computed)(() => mentionSuggestions.value.length > 0);
12183
+ const activeMentionOptionId = (0, vue.computed)(() => {
12184
+ const user = mentionSuggestions.value[highlightedMentionIndex.value];
12185
+ return user ? `${mentionListId}-${userKey(user)}` : void 0;
12186
+ });
12187
+ const updateMentionQuery = (caret) => {
12188
+ const beforeCaret = commentDraft.value.slice(0, caret);
12189
+ const match = /(?:^|\s)@([^\s@]{0,40})$/.exec(beforeCaret);
12190
+ mentionStart.value = match ? caret - match[1].length - 1 : null;
12191
+ mentionQuery.value = match?.[1] ?? "";
12192
+ highlightedMentionIndex.value = 0;
12193
+ };
12194
+ const onInput = (event) => {
12195
+ currentCommentMentions.value = currentCommentMentions.value.filter((user) => commentDraft.value.includes(`@${displayName(user)}`));
12196
+ updateMentionQuery(event.target.selectionStart ?? commentDraft.value.length);
12197
+ syncInputHeight();
12198
+ };
12199
+ const selectMention = (user) => {
12200
+ if (mentionStart.value == null) return;
12201
+ const start = mentionStart.value;
12202
+ const caret = inputRef.value?.selectionStart ?? commentDraft.value.length;
12203
+ const token = `@${displayName(user)}`;
12204
+ const identity = mentionIdentity(user);
12205
+ commentDraft.value = `${commentDraft.value.slice(0, start)}${token}${commentDraft.value.slice(caret)}`;
12206
+ if (!currentCommentMentions.value.some((selected) => userKey(selected) === userKey(identity))) currentCommentMentions.value = [...currentCommentMentions.value, identity];
12207
+ mentionStart.value = null;
12208
+ mentionQuery.value = "";
12209
+ (0, vue.nextTick)(() => {
12210
+ const nextCaret = start + token.length;
12211
+ inputRef.value?.focus();
12212
+ inputRef.value?.setSelectionRange(nextCaret, nextCaret);
12213
+ syncInputHeight();
12214
+ });
12215
+ };
12216
+ const onKeydown = (event) => {
12217
+ if (!mentionListOpen.value) return;
12218
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
12219
+ event.preventDefault();
12220
+ event.stopPropagation();
12221
+ const direction = event.key === "ArrowDown" ? 1 : -1;
12222
+ highlightedMentionIndex.value = (highlightedMentionIndex.value + direction + mentionSuggestions.value.length) % mentionSuggestions.value.length;
12223
+ return;
12224
+ }
12225
+ if (event.key === "Enter") {
12226
+ event.preventDefault();
12227
+ event.stopPropagation();
12228
+ const user = mentionSuggestions.value[highlightedMentionIndex.value];
12229
+ if (user) selectMention(user);
12230
+ return;
12231
+ }
12232
+ if (event.key === "Escape") {
12233
+ event.preventDefault();
12234
+ event.stopPropagation();
12235
+ mentionStart.value = null;
12236
+ }
12237
+ };
12079
12238
  (0, vue.onMounted)(scheduleInputHeightSync);
12080
12239
  (0, vue.watch)(currentCommentText, scheduleInputHeightSync);
12081
12240
  __expose({ focus });
@@ -12085,21 +12244,46 @@ var _sfc_main$34 = {
12085
12244
  config: __props.config,
12086
12245
  comment: __props.comment,
12087
12246
  "is-pending-input": true
12088
- }, null, 8, ["config", "comment"])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["comment-entry", { "sd-input-active": __props.isFocused }]) }, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
12247
+ }, null, 8, ["config", "comment"])) : (0, vue.createCommentVNode)("", true), (0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["comment-entry", { "sd-input-active": __props.isFocused }]) }, [(0, vue.createElementVNode)("div", _hoisted_2$21, [(0, vue.withDirectives)((0, vue.createElementVNode)("textarea", {
12089
12248
  ref_key: "inputRef",
12090
12249
  ref: inputRef,
12250
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => commentDraft.value = $event),
12091
12251
  class: "superdoc-field",
12252
+ role: "combobox",
12253
+ "aria-autocomplete": "list",
12254
+ "aria-expanded": mentionListOpen.value,
12255
+ "aria-controls": mentionListOpen.value ? mentionListId : void 0,
12256
+ "aria-activedescendant": activeMentionOptionId.value,
12257
+ "data-sd-comment-mention-input": "",
12258
+ "data-sd-comment-text": "",
12092
12259
  placeholder: "Add a comment",
12093
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => commentDraft.value = $event),
12094
12260
  rows: "1",
12095
- onInput: syncInputHeight,
12261
+ onInput,
12262
+ onKeydown,
12096
12263
  onFocus: _cache[1] || (_cache[1] = ($event) => handleFocusChange(true)),
12097
12264
  onBlur: _cache[2] || (_cache[2] = ($event) => handleFocusChange(false))
12098
- }, null, 544), [[vue.vModelText, commentDraft.value]])], 2)]);
12265
+ }, null, 40, _hoisted_3$17), [[vue.vModelText, commentDraft.value]]), mentionListOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
12266
+ key: 0,
12267
+ id: mentionListId,
12268
+ class: "comment-mention-list",
12269
+ role: "listbox",
12270
+ "data-sd-comment-mention-list": ""
12271
+ }, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(mentionSuggestions.value, (user, index) => {
12272
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
12273
+ id: `${mentionListId}-${userKey(user)}`,
12274
+ key: userKey(user),
12275
+ type: "button",
12276
+ class: (0, vue.normalizeClass)(["comment-mention-option", { "comment-mention-option--active": index === highlightedMentionIndex.value }]),
12277
+ role: "option",
12278
+ "aria-selected": index === highlightedMentionIndex.value,
12279
+ "data-sd-comment-mention-option": userKey(user),
12280
+ onMousedown: (0, vue.withModifiers)(($event) => selectMention(user), ["prevent"])
12281
+ }, [(0, vue.createElementVNode)("span", _hoisted_5$7, (0, vue.toDisplayString)(displayName(user)), 1), user.email ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_6$6, (0, vue.toDisplayString)(user.email), 1)) : (0, vue.createCommentVNode)("", true)], 42, _hoisted_4$10);
12282
+ }), 128))])) : (0, vue.createCommentVNode)("", true)])], 2)]);
12099
12283
  };
12100
12284
  }
12101
12285
  };
12102
- var CommentInput_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$34, [["__scopeId", "data-v-cc54b489"]]);
12286
+ var CommentInput_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$34, [["__scopeId", "data-v-87953125"]]);
12103
12287
  //#endregion
12104
12288
  //#region src/components/CommentsLayer/CommentDialog.vue
12105
12289
  var _hoisted_1$26 = [
@@ -12254,7 +12438,7 @@ var _sfc_main$33 = {
12254
12438
  const superdocStore = useSuperdocStore();
12255
12439
  const commentsStore = useCommentsStore();
12256
12440
  const { addComment, cancelComment, deleteComment, getCommentAliasIds, removePendingComment, getCommentDocumentId, requestInstantSidebarAlignment, resolveCommentPositionEntry, clearInstantSidebarAlignment, setActiveFloatingCommentInstance } = commentsStore;
12257
- const { suppressInternalExternal, getConfig, activeComment, activeFloatingCommentInstanceId, floatingCommentsOffset, pendingComment, currentCommentText, isDebugging, editingCommentId, editorCommentPositions, isCommentHighlighted } = storeToRefs(commentsStore);
12441
+ const { suppressInternalExternal, getConfig, activeComment, activeFloatingCommentInstanceId, floatingCommentsOffset, pendingComment, currentCommentText, currentCommentMentions, isDebugging, editingCommentId, editorCommentPositions, isCommentHighlighted } = storeToRefs(commentsStore);
12258
12442
  const isInternal = (0, vue.ref)(true);
12259
12443
  const commentInput = (0, vue.ref)(null);
12260
12444
  const editCommentInputs = (0, vue.ref)(/* @__PURE__ */ new Map());
@@ -12498,6 +12682,7 @@ var _sfc_main$33 = {
12498
12682
  if (!active) {
12499
12683
  if (isReplying.value || isEditingCommentInThisThread()) {
12500
12684
  currentCommentText.value = "";
12685
+ currentCommentMentions.value = [];
12501
12686
  editingCommentId.value = null;
12502
12687
  }
12503
12688
  textExpanded.value = false;
@@ -12678,7 +12863,8 @@ var _sfc_main$33 = {
12678
12863
  const outcome = await commentsStore.replyCommentV2({
12679
12864
  superdoc: proxy.$superdoc,
12680
12865
  parentCommentId,
12681
- text: currentCommentText.value
12866
+ text: currentCommentText.value,
12867
+ ...currentCommentMentions.value.length ? { mentions: currentCommentMentions.value } : {}
12682
12868
  });
12683
12869
  if (!outcome?.ok) {
12684
12870
  (0, vue.nextTick)(() => emit("resize"));
@@ -12686,6 +12872,7 @@ var _sfc_main$33 = {
12686
12872
  }
12687
12873
  isReplying.value = false;
12688
12874
  currentCommentText.value = "";
12875
+ currentCommentMentions.value = [];
12689
12876
  (0, vue.nextTick)(() => emit("resize"));
12690
12877
  return outcome;
12691
12878
  } finally {
@@ -12957,6 +13144,7 @@ var _sfc_main$33 = {
12957
13144
  switch (value) {
12958
13145
  case "edit":
12959
13146
  currentCommentText.value = comment?.commentText?.value ?? comment?.commentText ?? "";
13147
+ currentCommentMentions.value = Array.isArray(comment?.mentions) ? [...comment.mentions] : [];
12960
13148
  activeComment.value = props.comment.commentId;
12961
13149
  if (props.floatingInstanceId) setActiveFloatingCommentInstance(props.floatingInstanceId);
12962
13150
  editingCommentId.value = comment.commentId;
@@ -13016,13 +13204,13 @@ var _sfc_main$33 = {
13016
13204
  };
13017
13205
  const usersFiltered = (0, vue.computed)(() => {
13018
13206
  const users = proxy.$superdoc.users;
13019
- if (props.comment.isInternal === true) return users.filter((user) => user.access?.role === "internal");
13207
+ if (props.comment.isInternal === true) return users.filter((user) => user.access === "internal" || user.access?.role === "internal");
13020
13208
  return users;
13021
13209
  });
13022
13210
  (0, vue.onMounted)(() => {
13023
13211
  if (props.autoFocus) (0, vue.nextTick)(() => setFocus());
13024
13212
  if (isPendingNewComment.value) (0, vue.nextTick)(() => {
13025
- commentInput.value?.focus?.();
13213
+ commentInput.value?.focus?.({ preventScroll: true });
13026
13214
  });
13027
13215
  (0, vue.nextTick)(() => {
13028
13216
  const commentId = props.floatingInstanceId ?? (props.comment.importedId !== void 0 ? props.comment.importedId : props.comment.commentId);
@@ -13036,7 +13224,7 @@ var _sfc_main$33 = {
13036
13224
  (0, vue.watch)(showInputSection, (isVisible) => {
13037
13225
  if (!isVisible) return;
13038
13226
  (0, vue.nextTick)(() => {
13039
- commentInput.value?.focus?.();
13227
+ commentInput.value?.focus?.(isPendingNewComment.value ? { preventScroll: true } : void 0);
13040
13228
  });
13041
13229
  }, { immediate: true });
13042
13230
  (0, vue.watch)(editingCommentId, (commentId) => {
@@ -13207,15 +13395,11 @@ var _sfc_main$33 = {
13207
13395
  }, null, 10, _hoisted_36)) : (0, vue.unref)(isDebugging) && !isEditingThisComment.value(comment) ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_37, (0, vue.toDisplayString)((0, vue.unref)(editorCommentPositions)[comment.importedId !== void 0 ? comment.importedId : comment.commentId]?.bounds), 1)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_38, [(0, vue.createElementVNode)("div", _hoisted_39, [(0, vue.createVNode)(CommentInput_default, {
13208
13396
  ref_for: true,
13209
13397
  ref: setEditCommentInputRef(comment.commentId),
13210
- users: usersFiltered.value,
13398
+ users: [],
13211
13399
  config: (0, vue.unref)(getConfig),
13212
13400
  "include-header": false,
13213
13401
  comment
13214
- }, null, 8, [
13215
- "users",
13216
- "config",
13217
- "comment"
13218
- ])]), (0, vue.createElementVNode)("div", _hoisted_40, [(0, vue.createElementVNode)("button", {
13402
+ }, null, 8, ["config", "comment"])]), (0, vue.createElementVNode)("div", _hoisted_40, [(0, vue.createElementVNode)("button", {
13219
13403
  class: "sd-button reply-btn-cancel",
13220
13404
  onClick: (0, vue.withModifiers)(($event) => handleCancel(comment), ["stop", "prevent"])
13221
13405
  }, "Cancel", 8, _hoisted_41), (0, vue.createElementVNode)("button", {
@@ -13284,7 +13468,7 @@ var _sfc_main$33 = {
13284
13468
  };
13285
13469
  }
13286
13470
  };
13287
- var CommentDialog_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$33, [["__scopeId", "data-v-fd214e1f"]]);
13471
+ var CommentDialog_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$33, [["__scopeId", "data-v-f4c743f2"]]);
13288
13472
  //#endregion
13289
13473
  //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue
13290
13474
  var _hoisted_1$25 = [
@@ -21629,6 +21813,16 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
21629
21813
  const onV2LinkClick = (payload) => {
21630
21814
  linkPopover.handleLinkClick(payload);
21631
21815
  };
21816
+ const onV2CommentCreated = async (payload) => {
21817
+ try {
21818
+ await commentsStore.announceV2CommentCreated?.({
21819
+ superdoc: proxy.$superdoc,
21820
+ commentId: payload?.commentId
21821
+ });
21822
+ } catch (err) {
21823
+ console.warn("[SuperDoc][v2] context-menu comment reconciliation failed", err);
21824
+ }
21825
+ };
21632
21826
  const recollectV2GeometryIfActive = (options = void 0) => {
21633
21827
  if (!isV2Mode.value) return;
21634
21828
  if (!v2GeometryPublisher.getLastPayload()) return;
@@ -22731,6 +22925,7 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
22731
22925
  onV2SelectionChanged,
22732
22926
  onV2HostEvent: (event) => onV2HostEvent(doc, event),
22733
22927
  onV2LinkClick,
22928
+ onV2CommentCreated,
22734
22929
  onV2PageMetrics
22735
22930
  }, null, 8, [
22736
22931
  "file-source",
@@ -22795,7 +22990,7 @@ var SuperDoc_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_v
22795
22990
  ], 38);
22796
22991
  };
22797
22992
  }
22798
- }, [["__scopeId", "data-v-af3574db"]]);
22993
+ }, [["__scopeId", "data-v-85324013"]]);
22799
22994
  //#endregion
22800
22995
  //#region src/core/create-app.js
22801
22996
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -37087,7 +37282,11 @@ var _sfc_main$19 = {
37087
37282
  var ToolbarButtonIcon_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$19, [["__scopeId", "data-v-d7d79424"]]);
37088
37283
  //#endregion
37089
37284
  //#region src/internal/toolbar/built-in/ToolbarButton.vue
37090
- var _hoisted_1$14 = ["role", "aria-label"];
37285
+ var _hoisted_1$14 = [
37286
+ "role",
37287
+ "aria-label",
37288
+ "aria-disabled"
37289
+ ];
37091
37290
  var _hoisted_2$11 = ["data-item"];
37092
37291
  var _hoisted_3$9 = ["data-item"];
37093
37292
  var _hoisted_4$6 = {
@@ -37278,6 +37477,7 @@ var _sfc_main$18 = {
37278
37477
  style: (0, vue.normalizeStyle)(getStyle.value),
37279
37478
  role: __props.isOverflowItem ? "menuitem" : "button",
37280
37479
  "aria-label": (0, vue.unref)(attributes).ariaLabel,
37480
+ "aria-disabled": (0, vue.unref)(disabled) ? "true" : void 0,
37281
37481
  "data-sd-part": "toolbar-item",
37282
37482
  onClick: handleOuterClick,
37283
37483
  onKeydown: _cache[9] || (_cache[9] = (0, vue.withKeys)(($event) => onEnterKeydown($event), ["enter"])),
@@ -37379,7 +37579,7 @@ var _sfc_main$18 = {
37379
37579
  };
37380
37580
  }
37381
37581
  };
37382
- var ToolbarButton_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$18, [["__scopeId", "data-v-dbdea151"]]);
37582
+ var ToolbarButton_default = /*#__PURE__*/ require__plugin_vue_export_helper._plugin_vue_export_helper_default(_sfc_main$18, [["__scopeId", "data-v-22817fa7"]]);
37383
37583
  //#endregion
37384
37584
  //#region src/internal/toolbar/built-in/ToolbarSeparator.vue
37385
37585
  var _hoisted_1$13 = {
@@ -42535,7 +42735,7 @@ var BuiltInToolbar = class extends require_eventemitter3.import_eventemitter3.de
42535
42735
  return true;
42536
42736
  }
42537
42737
  if (name === "search") {
42538
- item.setDisabled(false);
42738
+ item.setDisabled(this.superdoc?.uiConfig?.search?.enabled !== true);
42539
42739
  return true;
42540
42740
  }
42541
42741
  if (getBuiltInToolbarItem(name)?.disposition === "host-routed") {
@@ -43277,7 +43477,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
43277
43477
  this.config.colors = shuffleArray(this.config.colors);
43278
43478
  this.userColorMap = /* @__PURE__ */ new Map();
43279
43479
  this.colorIndex = 0;
43280
- this.version = "2.5.1";
43480
+ this.version = "2.6.0-next.10";
43281
43481
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43282
43482
  this.superdocId = config.superdocId || require_uuid.v4();
43283
43483
  this.colors = this.config.colors ?? [];
@@ -44690,6 +44890,7 @@ var SuperDoc = class extends require_eventemitter3.import_eventemitter3.default
44690
44890
  * Story-aware navigation is currently supported for bookmark and tracked
44691
44891
  * change targets. Block and comment targets are body-only.
44692
44892
  *
44893
+ * @deprecated Use the target-specific navigation APIs on `superdoc.ui`. This method will be removed in v3.
44693
44894
  * @returns Whether the target was found and navigated to.
44694
44895
  */
44695
44896
  async navigateTo(target) {