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.
@@ -5693,7 +5693,7 @@ function useComment(params) {
5693
5693
  const fileType = params.fileType;
5694
5694
  const createdAtVersionNumber = params.createdAtVersionNumber;
5695
5695
  const isInternal = ref(params.isInternal !== void 0 ? params.isInternal : true);
5696
- const mentions = ref([]);
5696
+ const mentions = ref(Array.isArray(params.mentions) ? params.mentions : []);
5697
5697
  const commentElement = ref(null);
5698
5698
  const isFocused = ref(params.isFocused || false);
5699
5699
  const creatorId = ref(params.creatorId ?? null);
@@ -7253,6 +7253,7 @@ var useCommentsStore = defineStore("comments", () => {
7253
7253
  const overlappedIds = /* @__PURE__ */ new Set([]);
7254
7254
  const suppressInternalExternal = ref(true);
7255
7255
  const currentCommentText = ref("");
7256
+ const currentCommentMentions = ref([]);
7256
7257
  const commentsList = ref([]);
7257
7258
  const reviewDirectoryList = shallowRef([]);
7258
7259
  let priorTrackedChangeThreadIndex = /* @__PURE__ */ new Map();
@@ -7442,9 +7443,10 @@ var useCommentsStore = defineStore("comments", () => {
7442
7443
  */
7443
7444
  const getComment = (id) => {
7444
7445
  if (id === void 0 || id === null) return null;
7445
- const directMatch = commentsList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id);
7446
- if (directMatch) return directMatch;
7447
- return reviewDirectoryList.value.find((c) => c.commentId == id || c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id) || getTrackedChangeCommentByPositionAlias(id);
7446
+ const byPrimaryId = (c) => c.commentId == id;
7447
+ const byAliasId = (c) => c.importedId == id || c.trackedChangeAnchorKey == id || c?.trackedChange && buildTrackedChangeImportedPositionId(c.importedId) == id;
7448
+ const findIn = (list) => list.find(byPrimaryId) ?? list.find(byAliasId);
7449
+ return findIn(commentsList.value) ?? findIn(reviewDirectoryList.value) ?? getTrackedChangeCommentByPositionAlias(id);
7448
7450
  };
7449
7451
  const getThreadParent = (comment) => {
7450
7452
  if (!comment?.parentCommentId) return comment;
@@ -8334,6 +8336,7 @@ var useCommentsStore = defineStore("comments", () => {
8334
8336
  const removePendingComment = (superdoc) => {
8335
8337
  const hadPending = !!pendingComment.value;
8336
8338
  currentCommentText.value = "";
8339
+ currentCommentMentions.value = [];
8337
8340
  pendingComment.value = null;
8338
8341
  pendingV2CommentTarget.value = null;
8339
8342
  superdocStore.selectionPosition = null;
@@ -8353,16 +8356,19 @@ var useCommentsStore = defineStore("comments", () => {
8353
8356
  const v2Adapter = !skipEditorUpdate && !isHydration ? getV2CommentsAdapter(superdoc) : null;
8354
8357
  if (v2Adapter && !comment.trackedChange) {
8355
8358
  const text = normalizeV2CommentDraftText(pendingComment.value ? currentCommentText.value : comment.commentText);
8359
+ const mentions = pendingComment.value ? currentCommentMentions.value : comment.mentions ?? [];
8356
8360
  const target = pendingComment.value ? pendingV2CommentTarget.value : null;
8357
8361
  const parentCommentId = comment.parentCommentId ? String(comment.parentCommentId) : pendingComment.value ? null : null;
8358
8362
  return (async () => {
8359
8363
  try {
8360
8364
  return await (parentCommentId ? v2Adapter.reply({
8361
8365
  parentCommentId,
8362
- text
8366
+ text,
8367
+ ...mentions.length ? { mentions } : {}
8363
8368
  }) : v2Adapter.commitPendingComment({
8364
8369
  text,
8365
- target
8370
+ target,
8371
+ ...mentions.length ? { mentions } : {}
8366
8372
  }));
8367
8373
  } catch (err) {
8368
8374
  return {
@@ -8823,7 +8829,7 @@ var useCommentsStore = defineStore("comments", () => {
8823
8829
  * - rejection preserves rows and emits a rejected `comments-update` event
8824
8830
  * - committed-but-refresh-failed surfaces honestly (no reconcile with `[]`)
8825
8831
  */
8826
- const replyCommentV2 = async ({ superdoc, parentCommentId, text } = {}) => {
8832
+ const replyCommentV2 = async ({ superdoc, parentCommentId, text, mentions = [] } = {}) => {
8827
8833
  if (commentsAreReadOnly()) return readOnlyMutationOutcome();
8828
8834
  const v2Adapter = getV2CommentsAdapter(superdoc);
8829
8835
  if (!v2Adapter) return {
@@ -8848,7 +8854,8 @@ var useCommentsStore = defineStore("comments", () => {
8848
8854
  fileId,
8849
8855
  operation: () => v2Adapter.reply({
8850
8856
  parentCommentId: normalizedParent,
8851
- text: plainText
8857
+ text: plainText,
8858
+ ...mentions.length ? { mentions } : {}
8852
8859
  }),
8853
8860
  eventType: COMMENT_EVENTS.ADD,
8854
8861
  rejectionFallbackReason: "v2-reply-failed",
@@ -9304,6 +9311,7 @@ var useCommentsStore = defineStore("comments", () => {
9304
9311
  };
9305
9312
  const applyUpdate = (existing, input) => {
9306
9313
  existing.commentText = input.commentText ?? existing.commentText;
9314
+ existing.mentions = Array.isArray(input.mentions) ? input.mentions : [];
9307
9315
  existing.isInternal = typeof input.isInternal === "boolean" ? input.isInternal : existing.isInternal;
9308
9316
  if (typeof input.resolvedTime === "number") {
9309
9317
  existing.resolvedTime = input.resolvedTimeWasSynthesized === true && typeof existing.resolvedTime === "number" ? existing.resolvedTime : input.resolvedTime;
@@ -9353,6 +9361,58 @@ var useCommentsStore = defineStore("comments", () => {
9353
9361
  return { added: addedComments[addedComments.length - 1] ?? null };
9354
9362
  });
9355
9363
  /**
9364
+ * Reconcile and publish a root comment created by the private V2 context menu.
9365
+ * That path owns selection capture, while this store remains the sole owner
9366
+ * of shared comment rows and public comments-update events.
9367
+ */
9368
+ const announceV2CommentCreated = async ({ superdoc, commentId } = {}) => {
9369
+ const id = normalizeCommentId(commentId);
9370
+ const adapter = getV2CommentsAdapter(superdoc);
9371
+ const commentsApi = superdoc?.activeEditor?.doc?.comments;
9372
+ if (!id) return {
9373
+ ok: false,
9374
+ reason: "comment-id-missing"
9375
+ };
9376
+ if (!adapter || !isCurrentV2CommentsAdapter(adapter)) return {
9377
+ ok: false,
9378
+ reason: "comments-adapter-stale"
9379
+ };
9380
+ if (typeof commentsApi?.get !== "function") return {
9381
+ ok: false,
9382
+ reason: "document-api-unavailable"
9383
+ };
9384
+ let item;
9385
+ try {
9386
+ item = await commentsApi.get({ commentId: id });
9387
+ } catch (err) {
9388
+ return {
9389
+ ok: false,
9390
+ reason: "comment-read-failed",
9391
+ detail: err?.message ?? String(err)
9392
+ };
9393
+ }
9394
+ if (!isCurrentV2CommentsAdapter(adapter)) return {
9395
+ ok: false,
9396
+ reason: "comments-adapter-stale"
9397
+ };
9398
+ const comment = reconcileCommentsFromV2({
9399
+ superdoc,
9400
+ adapter,
9401
+ documentId: adapter.documentId,
9402
+ items: [item],
9403
+ pruneStale: false
9404
+ })?.added ?? commentsList.value.find((candidate) => String(candidate?.commentId ?? "") === id || String(candidate?.importedId ?? "") === id) ?? null;
9405
+ const event = {
9406
+ type: COMMENT_EVENTS.ADD,
9407
+ comment: comment?.getValues?.() ?? null
9408
+ };
9409
+ superdoc?.emit?.("comments-update", event);
9410
+ return {
9411
+ ok: true,
9412
+ comment
9413
+ };
9414
+ };
9415
+ /**
9356
9416
  * Cancel the pending comment
9357
9417
  *
9358
9418
  * @returns {void}
@@ -9553,7 +9613,7 @@ var useCommentsStore = defineStore("comments", () => {
9553
9613
  });
9554
9614
  commentsList.value = commentsList.value.filter((comment) => !removedComments.includes(comment));
9555
9615
  if (removedAliasIds.size) {
9556
- const nextPositions = { ...editorCommentPositions.value || {} };
9616
+ const nextPositions = { ...editorCommentPositions.value };
9557
9617
  removedAliasIds.forEach((id) => {
9558
9618
  delete nextPositions[id];
9559
9619
  });
@@ -11074,6 +11134,7 @@ var useCommentsStore = defineStore("comments", () => {
11074
11134
  suppressInternalExternal,
11075
11135
  pendingComment,
11076
11136
  currentCommentText,
11137
+ currentCommentMentions,
11077
11138
  commentsList,
11078
11139
  reviewDirectoryList,
11079
11140
  isCommentsListVisible,
@@ -11149,6 +11210,7 @@ var useCommentsStore = defineStore("comments", () => {
11149
11210
  getV2CommentsAdapter,
11150
11211
  applyReviewWindowFromV2,
11151
11212
  reconcileCommentsFromV2,
11213
+ announceV2CommentCreated,
11152
11214
  isV2EditorActive,
11153
11215
  replyCommentV2,
11154
11216
  editCommentV2,
@@ -11297,13 +11359,13 @@ function useUiFontFamily() {
11297
11359
  //#endregion
11298
11360
  //#region src/components/CommentsLayer/CommentsDropdown.vue
11299
11361
  var _hoisted_1$31 = { class: "comments-dropdown" };
11300
- var _hoisted_2$24 = ["onClick"];
11301
- var _hoisted_3$20 = {
11362
+ var _hoisted_2$25 = ["onClick"];
11363
+ var _hoisted_3$21 = {
11302
11364
  key: 0,
11303
11365
  class: "comments-dropdown__option-icon"
11304
11366
  };
11305
- var _hoisted_4$12 = ["innerHTML"];
11306
- var _hoisted_5$8 = { class: "comments-dropdown__option-label" };
11367
+ var _hoisted_4$13 = ["innerHTML"];
11368
+ var _hoisted_5$9 = { class: "comments-dropdown__option-label" };
11307
11369
  var CommentsDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11308
11370
  __name: "CommentsDropdown",
11309
11371
  props: {
@@ -11443,13 +11505,13 @@ var CommentsDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11443
11505
  key: option.key,
11444
11506
  class: normalizeClass(["comments-dropdown__option", { "sd-disabled": option.disabled }]),
11445
11507
  onClick: ($event) => onOptionClick(option)
11446
- }, [hasIcon(option) ? (openBlock(), createElementBlock("span", _hoisted_3$20, [option.iconString ? (openBlock(), createElementBlock("span", {
11508
+ }, [hasIcon(option) ? (openBlock(), createElementBlock("span", _hoisted_3$21, [option.iconString ? (openBlock(), createElementBlock("span", {
11447
11509
  key: 0,
11448
11510
  innerHTML: option.iconString
11449
- }, null, 8, _hoisted_4$12)) : (openBlock(), createBlock(unref(OptionIcon), {
11511
+ }, null, 8, _hoisted_4$13)) : (openBlock(), createBlock(unref(OptionIcon), {
11450
11512
  key: 1,
11451
11513
  option
11452
- }, null, 8, ["option"]))])) : createCommentVNode("", true), createElementVNode("span", _hoisted_5$8, toDisplayString(option.label), 1)], 10, _hoisted_2$24);
11514
+ }, null, 8, ["option"]))])) : createCommentVNode("", true), createElementVNode("span", _hoisted_5$9, toDisplayString(option.label), 1)], 10, _hoisted_2$25);
11453
11515
  }), 128))], 4)) : createCommentVNode("", true)]))]);
11454
11516
  };
11455
11517
  }
@@ -11457,9 +11519,9 @@ var CommentsDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11457
11519
  //#endregion
11458
11520
  //#region src/components/CommentsLayer/InternalDropdown.vue
11459
11521
  var _hoisted_1$30 = { class: "sd-comment-option" };
11460
- var _hoisted_2$23 = ["innerHTML"];
11461
- var _hoisted_3$19 = { class: "sd-option-state" };
11462
- var _hoisted_4$11 = ["innerHTML"];
11522
+ var _hoisted_2$24 = ["innerHTML"];
11523
+ var _hoisted_3$20 = { class: "sd-option-state" };
11524
+ var _hoisted_4$12 = ["innerHTML"];
11463
11525
  var InternalDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11464
11526
  __name: "InternalDropdown",
11465
11527
  props: {
@@ -11531,12 +11593,12 @@ var InternalDropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11531
11593
  createElementVNode("div", {
11532
11594
  class: "sd-active-icon",
11533
11595
  innerHTML: activeIcon.value
11534
- }, null, 8, _hoisted_2$23),
11535
- createElementVNode("div", _hoisted_3$19, toDisplayString(getState.value), 1),
11596
+ }, null, 8, _hoisted_2$24),
11597
+ createElementVNode("div", _hoisted_3$20, toDisplayString(getState.value), 1),
11536
11598
  createElementVNode("div", {
11537
11599
  class: "sd-dropdown-caret",
11538
11600
  innerHTML: unref(superdocIcons).caretDown
11539
- }, null, 8, _hoisted_4$11)
11601
+ }, null, 8, _hoisted_4$12)
11540
11602
  ])]),
11541
11603
  _: 1
11542
11604
  }, 8, ["disabled", "content-style"])], 4);
@@ -11686,8 +11748,8 @@ var isAllowed = (permission, role, isInternal, context = {}) => {
11686
11748
  //#endregion
11687
11749
  //#region src/components/general/Avatar.vue
11688
11750
  var _hoisted_1$29 = { class: "user-container" };
11689
- var _hoisted_2$22 = ["src"];
11690
- var _hoisted_3$18 = {
11751
+ var _hoisted_2$23 = ["src"];
11752
+ var _hoisted_3$19 = {
11691
11753
  key: 1,
11692
11754
  class: "user-bg"
11693
11755
  };
@@ -11712,21 +11774,21 @@ var Avatar_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11712
11774
  key: 0,
11713
11775
  class: "user-bg",
11714
11776
  src: __props.user.image.startsWith("http") ? __props.user.image : `data:image/png;base64,${__props.user.image}`
11715
- }, null, 8, _hoisted_2$22)) : (openBlock(), createElementBlock("span", _hoisted_3$18, toDisplayString(getInitials(__props.user.name, __props.user.email)), 1))]);
11777
+ }, null, 8, _hoisted_2$23)) : (openBlock(), createElementBlock("span", _hoisted_3$19, toDisplayString(getInitials(__props.user.name, __props.user.email)), 1))]);
11716
11778
  };
11717
11779
  }
11718
11780
  }, [["__scopeId", "data-v-c95b2073"]]);
11719
11781
  //#endregion
11720
11782
  //#region src/components/CommentsLayer/CommentHeader.vue
11721
11783
  var _hoisted_1$28 = { class: "card-section comment-header" };
11722
- var _hoisted_2$21 = { class: "comment-header-left" };
11723
- var _hoisted_3$17 = { class: "user-info" };
11724
- var _hoisted_4$10 = { class: "user-name" };
11725
- var _hoisted_5$7 = {
11784
+ var _hoisted_2$22 = { class: "comment-header-left" };
11785
+ var _hoisted_3$18 = { class: "user-info" };
11786
+ var _hoisted_4$11 = { class: "user-name" };
11787
+ var _hoisted_5$8 = {
11726
11788
  key: 0,
11727
11789
  class: "imported-tag"
11728
11790
  };
11729
- var _hoisted_6$6 = {
11791
+ var _hoisted_6$7 = {
11730
11792
  key: 0,
11731
11793
  class: "user-timestamp"
11732
11794
  };
@@ -11951,10 +12013,10 @@ var CommentHeader_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
11951
12013
  return user;
11952
12014
  });
11953
12015
  return (_ctx, _cache) => {
11954
- return openBlock(), createElementBlock("div", _hoisted_1$28, [createElementVNode("div", _hoisted_2$21, [createVNode(Avatar_default, {
12016
+ return openBlock(), createElementBlock("div", _hoisted_1$28, [createElementVNode("div", _hoisted_2$22, [createVNode(Avatar_default, {
11955
12017
  user: getCurrentUser.value,
11956
12018
  class: "avatar"
11957
- }, null, 8, ["user"]), createElementVNode("div", _hoisted_3$17, [createElementVNode("div", _hoisted_4$10, [createTextVNode(toDisplayString(getCurrentUser.value.name), 1), isImported.value ? (openBlock(), createElementBlock("span", _hoisted_5$7, "IMPORTED")) : createCommentVNode("", true)]), props.comment.createdTime ? (openBlock(), createElementBlock("div", _hoisted_6$6, toDisplayString(unref(formatDate)(props.comment.createdTime)), 1)) : createCommentVNode("", true)])]), createElementVNode("div", { class: normalizeClass(["overflow-menu", { "is-visible": props.isActive }]) }, [
12019
+ }, null, 8, ["user"]), createElementVNode("div", _hoisted_3$18, [createElementVNode("div", _hoisted_4$11, [createTextVNode(toDisplayString(getCurrentUser.value.name), 1), isImported.value ? (openBlock(), createElementBlock("span", _hoisted_5$8, "IMPORTED")) : createCommentVNode("", true)]), props.comment.createdTime ? (openBlock(), createElementBlock("div", _hoisted_6$7, toDisplayString(unref(formatDate)(props.comment.createdTime)), 1)) : createCommentVNode("", true)])]), createElementVNode("div", { class: normalizeClass(["overflow-menu", { "is-visible": props.isActive }]) }, [
11958
12020
  allowResolve.value ? (openBlock(), createElementBlock("div", {
11959
12021
  key: 0,
11960
12022
  class: normalizeClass(["overflow-menu__icon", { "sd-is-disabled": Boolean(__props.resolveDisabledReason) }]),
@@ -12003,8 +12065,24 @@ var CommentHeader_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12003
12065
  //#endregion
12004
12066
  //#region src/components/CommentsLayer/CommentInput.vue
12005
12067
  var _hoisted_1$27 = { class: "input-section" };
12006
- var TEXTAREA_MIN_HEIGHT = 28;
12007
- var TEXTAREA_MAX_HEIGHT = 132;
12068
+ var _hoisted_2$21 = { class: "comment-composer" };
12069
+ var _hoisted_3$17 = [
12070
+ "aria-expanded",
12071
+ "aria-controls",
12072
+ "aria-activedescendant"
12073
+ ];
12074
+ var _hoisted_4$10 = [
12075
+ "id",
12076
+ "aria-selected",
12077
+ "data-sd-comment-mention-option",
12078
+ "onMousedown"
12079
+ ];
12080
+ var _hoisted_5$7 = { class: "comment-mention-option__name" };
12081
+ var _hoisted_6$6 = {
12082
+ key: 0,
12083
+ class: "comment-mention-option__email"
12084
+ };
12085
+ var commentInputSequence = 0;
12008
12086
  var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12009
12087
  __name: "CommentInput",
12010
12088
  props: {
@@ -12032,15 +12110,23 @@ var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12032
12110
  },
12033
12111
  emits: ["focus"],
12034
12112
  setup(__props, { expose: __expose, emit: __emit }) {
12113
+ const TEXTAREA_MIN_HEIGHT = 28;
12114
+ const TEXTAREA_MAX_HEIGHT = 132;
12035
12115
  const emit = __emit;
12036
- const { currentCommentText } = storeToRefs(useCommentsStore());
12116
+ const props = __props;
12117
+ const { currentCommentText, currentCommentMentions } = storeToRefs(useCommentsStore());
12037
12118
  const inputRef = ref(null);
12119
+ const mentionStart = ref(null);
12120
+ const mentionQuery = ref("");
12121
+ const highlightedMentionIndex = ref(0);
12122
+ const mentionListId = `sd-comment-mention-list-${++commentInputSequence}`;
12038
12123
  const handleFocusChange = (focused) => emit("focus", focused);
12039
- const focus = () => {
12040
- inputRef.value?.focus?.();
12124
+ const getInputElement = () => inputRef.value;
12125
+ const focus = (options) => {
12126
+ getInputElement()?.focus?.(options);
12041
12127
  };
12042
12128
  const syncInputHeight = () => {
12043
- const input = inputRef.value;
12129
+ const input = getInputElement();
12044
12130
  if (!input) return;
12045
12131
  input.style.height = `${TEXTAREA_MIN_HEIGHT}px`;
12046
12132
  const scrollHeight = input.scrollHeight || TEXTAREA_MIN_HEIGHT;
@@ -12071,6 +12157,79 @@ var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12071
12157
  currentCommentText.value = textToHtml(value);
12072
12158
  }
12073
12159
  });
12160
+ const displayName = (user) => user?.name?.trim() || user?.email?.trim() || "";
12161
+ const userKey = (user) => String(user?.id ?? user?.email ?? user?.name ?? "");
12162
+ const isViewer = (user) => user?.role === "viewer" || user?.access === "viewer" || typeof user?.access === "object" && user.access?.role === "viewer";
12163
+ const mentionIdentity = (user) => ({
12164
+ ...user?.id != null ? { id: user.id } : {},
12165
+ ...user?.name != null ? { name: user.name } : {},
12166
+ ...user?.email != null ? { email: user.email } : {}
12167
+ });
12168
+ const mentionSuggestions = computed(() => {
12169
+ if (mentionStart.value == null) return [];
12170
+ const needle = mentionQuery.value.toLocaleLowerCase();
12171
+ return props.users.filter((user) => !isViewer(user) && displayName(user)).filter((user) => {
12172
+ const name = displayName(user).toLocaleLowerCase();
12173
+ const email = user?.email?.toLocaleLowerCase() ?? "";
12174
+ return !needle || name.startsWith(needle) || email.startsWith(needle);
12175
+ }).slice(0, 8);
12176
+ });
12177
+ const mentionListOpen = computed(() => mentionSuggestions.value.length > 0);
12178
+ const activeMentionOptionId = computed(() => {
12179
+ const user = mentionSuggestions.value[highlightedMentionIndex.value];
12180
+ return user ? `${mentionListId}-${userKey(user)}` : void 0;
12181
+ });
12182
+ const updateMentionQuery = (caret) => {
12183
+ const beforeCaret = commentDraft.value.slice(0, caret);
12184
+ const match = /(?:^|\s)@([^\s@]{0,40})$/.exec(beforeCaret);
12185
+ mentionStart.value = match ? caret - match[1].length - 1 : null;
12186
+ mentionQuery.value = match?.[1] ?? "";
12187
+ highlightedMentionIndex.value = 0;
12188
+ };
12189
+ const onInput = (event) => {
12190
+ currentCommentMentions.value = currentCommentMentions.value.filter((user) => commentDraft.value.includes(`@${displayName(user)}`));
12191
+ updateMentionQuery(event.target.selectionStart ?? commentDraft.value.length);
12192
+ syncInputHeight();
12193
+ };
12194
+ const selectMention = (user) => {
12195
+ if (mentionStart.value == null) return;
12196
+ const start = mentionStart.value;
12197
+ const caret = inputRef.value?.selectionStart ?? commentDraft.value.length;
12198
+ const token = `@${displayName(user)}`;
12199
+ const identity = mentionIdentity(user);
12200
+ commentDraft.value = `${commentDraft.value.slice(0, start)}${token}${commentDraft.value.slice(caret)}`;
12201
+ if (!currentCommentMentions.value.some((selected) => userKey(selected) === userKey(identity))) currentCommentMentions.value = [...currentCommentMentions.value, identity];
12202
+ mentionStart.value = null;
12203
+ mentionQuery.value = "";
12204
+ nextTick(() => {
12205
+ const nextCaret = start + token.length;
12206
+ inputRef.value?.focus();
12207
+ inputRef.value?.setSelectionRange(nextCaret, nextCaret);
12208
+ syncInputHeight();
12209
+ });
12210
+ };
12211
+ const onKeydown = (event) => {
12212
+ if (!mentionListOpen.value) return;
12213
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
12214
+ event.preventDefault();
12215
+ event.stopPropagation();
12216
+ const direction = event.key === "ArrowDown" ? 1 : -1;
12217
+ highlightedMentionIndex.value = (highlightedMentionIndex.value + direction + mentionSuggestions.value.length) % mentionSuggestions.value.length;
12218
+ return;
12219
+ }
12220
+ if (event.key === "Enter") {
12221
+ event.preventDefault();
12222
+ event.stopPropagation();
12223
+ const user = mentionSuggestions.value[highlightedMentionIndex.value];
12224
+ if (user) selectMention(user);
12225
+ return;
12226
+ }
12227
+ if (event.key === "Escape") {
12228
+ event.preventDefault();
12229
+ event.stopPropagation();
12230
+ mentionStart.value = null;
12231
+ }
12232
+ };
12074
12233
  onMounted(scheduleInputHeightSync);
12075
12234
  watch(currentCommentText, scheduleInputHeightSync);
12076
12235
  __expose({ focus });
@@ -12080,20 +12239,45 @@ var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12080
12239
  config: __props.config,
12081
12240
  comment: __props.comment,
12082
12241
  "is-pending-input": true
12083
- }, null, 8, ["config", "comment"])) : createCommentVNode("", true), createElementVNode("div", { class: normalizeClass(["comment-entry", { "sd-input-active": __props.isFocused }]) }, [withDirectives(createElementVNode("textarea", {
12242
+ }, null, 8, ["config", "comment"])) : createCommentVNode("", true), createElementVNode("div", { class: normalizeClass(["comment-entry", { "sd-input-active": __props.isFocused }]) }, [createElementVNode("div", _hoisted_2$21, [withDirectives(createElementVNode("textarea", {
12084
12243
  ref_key: "inputRef",
12085
12244
  ref: inputRef,
12245
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => commentDraft.value = $event),
12086
12246
  class: "superdoc-field",
12247
+ role: "combobox",
12248
+ "aria-autocomplete": "list",
12249
+ "aria-expanded": mentionListOpen.value,
12250
+ "aria-controls": mentionListOpen.value ? mentionListId : void 0,
12251
+ "aria-activedescendant": activeMentionOptionId.value,
12252
+ "data-sd-comment-mention-input": "",
12253
+ "data-sd-comment-text": "",
12087
12254
  placeholder: "Add a comment",
12088
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => commentDraft.value = $event),
12089
12255
  rows: "1",
12090
- onInput: syncInputHeight,
12256
+ onInput,
12257
+ onKeydown,
12091
12258
  onFocus: _cache[1] || (_cache[1] = ($event) => handleFocusChange(true)),
12092
12259
  onBlur: _cache[2] || (_cache[2] = ($event) => handleFocusChange(false))
12093
- }, null, 544), [[vModelText, commentDraft.value]])], 2)]);
12260
+ }, null, 40, _hoisted_3$17), [[vModelText, commentDraft.value]]), mentionListOpen.value ? (openBlock(), createElementBlock("div", {
12261
+ key: 0,
12262
+ id: mentionListId,
12263
+ class: "comment-mention-list",
12264
+ role: "listbox",
12265
+ "data-sd-comment-mention-list": ""
12266
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(mentionSuggestions.value, (user, index) => {
12267
+ return openBlock(), createElementBlock("button", {
12268
+ id: `${mentionListId}-${userKey(user)}`,
12269
+ key: userKey(user),
12270
+ type: "button",
12271
+ class: normalizeClass(["comment-mention-option", { "comment-mention-option--active": index === highlightedMentionIndex.value }]),
12272
+ role: "option",
12273
+ "aria-selected": index === highlightedMentionIndex.value,
12274
+ "data-sd-comment-mention-option": userKey(user),
12275
+ onMousedown: withModifiers(($event) => selectMention(user), ["prevent"])
12276
+ }, [createElementVNode("span", _hoisted_5$7, toDisplayString(displayName(user)), 1), user.email ? (openBlock(), createElementBlock("span", _hoisted_6$6, toDisplayString(user.email), 1)) : createCommentVNode("", true)], 42, _hoisted_4$10);
12277
+ }), 128))])) : createCommentVNode("", true)])], 2)]);
12094
12278
  };
12095
12279
  }
12096
- }, [["__scopeId", "data-v-cc54b489"]]);
12280
+ }, [["__scopeId", "data-v-87953125"]]);
12097
12281
  //#endregion
12098
12282
  //#region src/components/CommentsLayer/CommentDialog.vue
12099
12283
  var _hoisted_1$26 = [
@@ -12248,7 +12432,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12248
12432
  const superdocStore = useSuperdocStore();
12249
12433
  const commentsStore = useCommentsStore();
12250
12434
  const { addComment, cancelComment, deleteComment, getCommentAliasIds, removePendingComment, getCommentDocumentId, requestInstantSidebarAlignment, resolveCommentPositionEntry, clearInstantSidebarAlignment, setActiveFloatingCommentInstance } = commentsStore;
12251
- const { suppressInternalExternal, getConfig, activeComment, activeFloatingCommentInstanceId, floatingCommentsOffset, pendingComment, currentCommentText, isDebugging, editingCommentId, editorCommentPositions, isCommentHighlighted } = storeToRefs(commentsStore);
12435
+ const { suppressInternalExternal, getConfig, activeComment, activeFloatingCommentInstanceId, floatingCommentsOffset, pendingComment, currentCommentText, currentCommentMentions, isDebugging, editingCommentId, editorCommentPositions, isCommentHighlighted } = storeToRefs(commentsStore);
12252
12436
  const isInternal = ref(true);
12253
12437
  const commentInput = ref(null);
12254
12438
  const editCommentInputs = ref(/* @__PURE__ */ new Map());
@@ -12492,6 +12676,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12492
12676
  if (!active) {
12493
12677
  if (isReplying.value || isEditingCommentInThisThread()) {
12494
12678
  currentCommentText.value = "";
12679
+ currentCommentMentions.value = [];
12495
12680
  editingCommentId.value = null;
12496
12681
  }
12497
12682
  textExpanded.value = false;
@@ -12672,7 +12857,8 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12672
12857
  const outcome = await commentsStore.replyCommentV2({
12673
12858
  superdoc: proxy.$superdoc,
12674
12859
  parentCommentId,
12675
- text: currentCommentText.value
12860
+ text: currentCommentText.value,
12861
+ ...currentCommentMentions.value.length ? { mentions: currentCommentMentions.value } : {}
12676
12862
  });
12677
12863
  if (!outcome?.ok) {
12678
12864
  nextTick(() => emit("resize"));
@@ -12680,6 +12866,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12680
12866
  }
12681
12867
  isReplying.value = false;
12682
12868
  currentCommentText.value = "";
12869
+ currentCommentMentions.value = [];
12683
12870
  nextTick(() => emit("resize"));
12684
12871
  return outcome;
12685
12872
  } finally {
@@ -12951,6 +13138,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
12951
13138
  switch (value) {
12952
13139
  case "edit":
12953
13140
  currentCommentText.value = comment?.commentText?.value ?? comment?.commentText ?? "";
13141
+ currentCommentMentions.value = Array.isArray(comment?.mentions) ? [...comment.mentions] : [];
12954
13142
  activeComment.value = props.comment.commentId;
12955
13143
  if (props.floatingInstanceId) setActiveFloatingCommentInstance(props.floatingInstanceId);
12956
13144
  editingCommentId.value = comment.commentId;
@@ -13010,13 +13198,13 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13010
13198
  };
13011
13199
  const usersFiltered = computed(() => {
13012
13200
  const users = proxy.$superdoc.users;
13013
- if (props.comment.isInternal === true) return users.filter((user) => user.access?.role === "internal");
13201
+ if (props.comment.isInternal === true) return users.filter((user) => user.access === "internal" || user.access?.role === "internal");
13014
13202
  return users;
13015
13203
  });
13016
13204
  onMounted(() => {
13017
13205
  if (props.autoFocus) nextTick(() => setFocus());
13018
13206
  if (isPendingNewComment.value) nextTick(() => {
13019
- commentInput.value?.focus?.();
13207
+ commentInput.value?.focus?.({ preventScroll: true });
13020
13208
  });
13021
13209
  nextTick(() => {
13022
13210
  const commentId = props.floatingInstanceId ?? (props.comment.importedId !== void 0 ? props.comment.importedId : props.comment.commentId);
@@ -13030,7 +13218,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13030
13218
  watch(showInputSection, (isVisible) => {
13031
13219
  if (!isVisible) return;
13032
13220
  nextTick(() => {
13033
- commentInput.value?.focus?.();
13221
+ commentInput.value?.focus?.(isPendingNewComment.value ? { preventScroll: true } : void 0);
13034
13222
  });
13035
13223
  }, { immediate: true });
13036
13224
  watch(editingCommentId, (commentId) => {
@@ -13201,15 +13389,11 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13201
13389
  }, null, 10, _hoisted_36)) : unref(isDebugging) && !isEditingThisComment.value(comment) ? (openBlock(), createElementBlock("div", _hoisted_37, toDisplayString(unref(editorCommentPositions)[comment.importedId !== void 0 ? comment.importedId : comment.commentId]?.bounds), 1)) : (openBlock(), createElementBlock("div", _hoisted_38, [createElementVNode("div", _hoisted_39, [createVNode(CommentInput_default, {
13202
13390
  ref_for: true,
13203
13391
  ref: setEditCommentInputRef(comment.commentId),
13204
- users: usersFiltered.value,
13392
+ users: [],
13205
13393
  config: unref(getConfig),
13206
13394
  "include-header": false,
13207
13395
  comment
13208
- }, null, 8, [
13209
- "users",
13210
- "config",
13211
- "comment"
13212
- ])]), createElementVNode("div", _hoisted_40, [createElementVNode("button", {
13396
+ }, null, 8, ["config", "comment"])]), createElementVNode("div", _hoisted_40, [createElementVNode("button", {
13213
13397
  class: "sd-button reply-btn-cancel",
13214
13398
  onClick: withModifiers(($event) => handleCancel(comment), ["stop", "prevent"])
13215
13399
  }, "Cancel", 8, _hoisted_41), createElementVNode("button", {
@@ -13277,7 +13461,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13277
13461
  ], 64))], 14, _hoisted_1$26)), [[_directive_click_outside, handleClickOutside]]);
13278
13462
  };
13279
13463
  }
13280
- }, [["__scopeId", "data-v-fd214e1f"]]);
13464
+ }, [["__scopeId", "data-v-f4c743f2"]]);
13281
13465
  //#endregion
13282
13466
  //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue
13283
13467
  var _hoisted_1$25 = [
@@ -21589,6 +21773,16 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
21589
21773
  const onV2LinkClick = (payload) => {
21590
21774
  linkPopover.handleLinkClick(payload);
21591
21775
  };
21776
+ const onV2CommentCreated = async (payload) => {
21777
+ try {
21778
+ await commentsStore.announceV2CommentCreated?.({
21779
+ superdoc: proxy.$superdoc,
21780
+ commentId: payload?.commentId
21781
+ });
21782
+ } catch (err) {
21783
+ console.warn("[SuperDoc][v2] context-menu comment reconciliation failed", err);
21784
+ }
21785
+ };
21592
21786
  const recollectV2GeometryIfActive = (options = void 0) => {
21593
21787
  if (!isV2Mode.value) return;
21594
21788
  if (!v2GeometryPublisher.getLastPayload()) return;
@@ -22691,6 +22885,7 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
22691
22885
  onV2SelectionChanged,
22692
22886
  onV2HostEvent: (event) => onV2HostEvent(doc, event),
22693
22887
  onV2LinkClick,
22888
+ onV2CommentCreated,
22694
22889
  onV2PageMetrics
22695
22890
  }, null, 8, [
22696
22891
  "file-source",
@@ -22755,7 +22950,7 @@ var SuperDoc_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
22755
22950
  ], 38);
22756
22951
  };
22757
22952
  }
22758
- }, [["__scopeId", "data-v-af3574db"]]);
22953
+ }, [["__scopeId", "data-v-85324013"]]);
22759
22954
  //#endregion
22760
22955
  //#region src/core/create-app.js
22761
22956
  var PINIA_DEVTOOLS_SETUP_EVENT = "devtools-plugin:setup";
@@ -37046,7 +37241,11 @@ var ToolbarButtonIcon_default = /*#__PURE__*/ _plugin_vue_export_helper_default(
37046
37241
  }, [["__scopeId", "data-v-d7d79424"]]);
37047
37242
  //#endregion
37048
37243
  //#region src/internal/toolbar/built-in/ToolbarButton.vue
37049
- var _hoisted_1$14 = ["role", "aria-label"];
37244
+ var _hoisted_1$14 = [
37245
+ "role",
37246
+ "aria-label",
37247
+ "aria-disabled"
37248
+ ];
37050
37249
  var _hoisted_2$11 = ["data-item"];
37051
37250
  var _hoisted_3$9 = ["data-item"];
37052
37251
  var _hoisted_4$6 = {
@@ -37237,6 +37436,7 @@ var ToolbarButton_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
37237
37436
  style: normalizeStyle(getStyle.value),
37238
37437
  role: __props.isOverflowItem ? "menuitem" : "button",
37239
37438
  "aria-label": unref(attributes).ariaLabel,
37439
+ "aria-disabled": unref(disabled) ? "true" : void 0,
37240
37440
  "data-sd-part": "toolbar-item",
37241
37441
  onClick: handleOuterClick,
37242
37442
  onKeydown: _cache[9] || (_cache[9] = withKeys(($event) => onEnterKeydown($event), ["enter"])),
@@ -37337,7 +37537,7 @@ var ToolbarButton_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
37337
37537
  ], 10, _hoisted_2$11)], 46, _hoisted_1$14);
37338
37538
  };
37339
37539
  }
37340
- }, [["__scopeId", "data-v-dbdea151"]]);
37540
+ }, [["__scopeId", "data-v-22817fa7"]]);
37341
37541
  //#endregion
37342
37542
  //#region src/internal/toolbar/built-in/ToolbarSeparator.vue
37343
37543
  var _hoisted_1$13 = {
@@ -42468,7 +42668,7 @@ var BuiltInToolbar = class extends import_eventemitter3.default {
42468
42668
  return true;
42469
42669
  }
42470
42670
  if (name === "search") {
42471
- item.setDisabled(false);
42671
+ item.setDisabled(this.superdoc?.uiConfig?.search?.enabled !== true);
42472
42672
  return true;
42473
42673
  }
42474
42674
  if (getBuiltInToolbarItem(name)?.disposition === "host-routed") {
@@ -43210,7 +43410,7 @@ var SuperDoc = class extends import_eventemitter3.default {
43210
43410
  this.config.colors = shuffleArray(this.config.colors);
43211
43411
  this.userColorMap = /* @__PURE__ */ new Map();
43212
43412
  this.colorIndex = 0;
43213
- this.version = "2.5.1";
43413
+ this.version = "2.6.0-next.10";
43214
43414
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
43215
43415
  this.superdocId = config.superdocId || v4();
43216
43416
  this.colors = this.config.colors ?? [];
@@ -44623,6 +44823,7 @@ var SuperDoc = class extends import_eventemitter3.default {
44623
44823
  * Story-aware navigation is currently supported for bookmark and tracked
44624
44824
  * change targets. Block and comment targets are body-only.
44625
44825
  *
44826
+ * @deprecated Use the target-specific navigation APIs on `superdoc.ui`. This method will be removed in v3.
44626
44827
  * @returns Whether the target was found and navigated to.
44627
44828
  */
44628
44829
  async navigateTo(target) {