superdoc 2.23.1-next.5 → 2.23.1-next.7

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.
@@ -302,7 +302,7 @@ var awarenessStatesToArray = (context, states) => {
302
302
  var DEFAULT_ENDPOINT = "https://ingest.superdoc.dev/v1/collect";
303
303
  function getSuperdocVersion() {
304
304
  try {
305
- return "2.23.1-next.5";
305
+ return "2.23.1-next.7";
306
306
  } catch {
307
307
  return "unknown";
308
308
  }
@@ -13159,6 +13159,114 @@ var CommentInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13159
13159
  }
13160
13160
  }, [["__scopeId", "data-v-0720a671"]]);
13161
13161
  //#endregion
13162
+ //#region src/internal/toolbar/built-in/url-safety.js
13163
+ /**
13164
+ * Minimal href sanitizer for the built-in link popover.
13165
+ *
13166
+ * The v1 toolbar used the shared `@superdoc/url-validation` package, which is not
13167
+ * a resolvable runtime dependency of the published `superdoc` ES build. This
13168
+ * keeps the same fail-closed posture (only known-safe schemes pass) without
13169
+ * pulling in that package: dangerous schemes (`javascript:`, `data:`,
13170
+ * `vbscript:`) return `null`, mirroring the v1 contract that callers treat a
13171
+ * `null` result as an invalid URL.
13172
+ */
13173
+ var SAFE_SCHEME = /^(https?|mailto|tel):/i;
13174
+ var DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
13175
+ var HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
13176
+ /**
13177
+ * Validate and normalize an href. Returns `{ href }` for safe values, or `null`.
13178
+ * @param {string} value
13179
+ * @returns {{ href: string }|null}
13180
+ */
13181
+ function sanitizeHref(value) {
13182
+ if (typeof value !== "string") return null;
13183
+ const trimmed = value.trim();
13184
+ if (!trimmed) return null;
13185
+ if (trimmed.startsWith("#")) return { href: trimmed };
13186
+ if (DANGEROUS_SCHEME.test(trimmed)) return null;
13187
+ if (HAS_SCHEME.test(trimmed)) return SAFE_SCHEME.test(trimmed) ? { href: trimmed } : null;
13188
+ return { href: trimmed };
13189
+ }
13190
+ //#endregion
13191
+ //#region src/components/CommentsLayer/comment-urls.js
13192
+ function isAbsoluteWebUrl(value) {
13193
+ if (!/^https?:\/\/[^\s/?#\\]+/i.test(value) || /[\s\\]/u.test(value)) return false;
13194
+ try {
13195
+ const url = new URL(value);
13196
+ return (url.protocol === "https:" || url.protocol === "http:") && Boolean(url.hostname);
13197
+ } catch {
13198
+ return false;
13199
+ }
13200
+ }
13201
+ function trimUrlPunctuation(value) {
13202
+ const closingDelimiter = {
13203
+ "(": ")",
13204
+ "[": "]",
13205
+ "{": "}"
13206
+ };
13207
+ const balance = {
13208
+ ")": 0,
13209
+ "]": 0,
13210
+ "}": 0
13211
+ };
13212
+ for (const character of value) {
13213
+ const close = closingDelimiter[character];
13214
+ if (close) balance[close] += 1;
13215
+ else if (Object.hasOwn(balance, character)) balance[character] -= 1;
13216
+ }
13217
+ let end = value.length;
13218
+ while (end > 0) {
13219
+ const character = value[end - 1];
13220
+ if (".,!?;:".includes(character)) end -= 1;
13221
+ else if (balance[character] < 0) {
13222
+ balance[character] += 1;
13223
+ end -= 1;
13224
+ } else break;
13225
+ }
13226
+ return value.slice(0, end);
13227
+ }
13228
+ function configureLink(link, href) {
13229
+ link.setAttribute("href", href);
13230
+ link.setAttribute("target", "_blank");
13231
+ link.setAttribute("rel", "noopener noreferrer");
13232
+ }
13233
+ /** Render links without rewriting stored comment HTML or its rich-text structure. */
13234
+ function renderCommentHtml(value) {
13235
+ if (typeof value !== "string") return "";
13236
+ if (typeof document === "undefined") return value;
13237
+ const template = document.createElement("template");
13238
+ template.innerHTML = value;
13239
+ for (const link of template.content.querySelectorAll("a")) {
13240
+ const safe = sanitizeHref(link.getAttribute("href"));
13241
+ if (safe && (isAbsoluteWebUrl(safe.href) || /^(mailto|tel):\S+$/i.test(safe.href))) configureLink(link, safe.href);
13242
+ else link.replaceWith(...link.childNodes);
13243
+ }
13244
+ const walker = document.createTreeWalker(template.content, NodeFilter.SHOW_TEXT);
13245
+ const textNodes = [];
13246
+ while (walker.nextNode()) textNodes.push(walker.currentNode);
13247
+ for (const node of textNodes) {
13248
+ if (node.parentElement?.closest("a, script, style, textarea, code, pre")) continue;
13249
+ const text = node.textContent;
13250
+ const fragment = document.createDocumentFragment();
13251
+ let end = 0;
13252
+ for (const match of text.matchAll(/(^|[\s([{])(https?:\/\/[^\s<>"']+)/gi)) {
13253
+ const href = trimUrlPunctuation(match[2]);
13254
+ if (!isAbsoluteWebUrl(href)) continue;
13255
+ const start = match.index + match[1].length;
13256
+ fragment.append(document.createTextNode(text.slice(end, start)));
13257
+ const link = document.createElement("a");
13258
+ configureLink(link, href);
13259
+ link.textContent = href;
13260
+ fragment.append(link);
13261
+ end = start + href.length;
13262
+ }
13263
+ if (end === 0) continue;
13264
+ fragment.append(document.createTextNode(text.slice(end)));
13265
+ node.replaceWith(fragment);
13266
+ }
13267
+ return template.innerHTML;
13268
+ }
13269
+ //#endregion
13162
13270
  //#region src/components/CommentsLayer/CommentDialog.vue
13163
13271
  var _hoisted_1$27 = [
13164
13272
  "aria-label",
@@ -13540,6 +13648,12 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
13540
13648
  return a.createdTime - b.createdTime;
13541
13649
  });
13542
13650
  });
13651
+ const renderedCommentTexts = computed$1(() => new Map(comments.value.map((comment) => [comment.commentId, renderCommentHtml(comment.commentText)])));
13652
+ const handleDialogClick = (event) => {
13653
+ if (event.target instanceof Element && event.target.closest(".comment a[href]")) return;
13654
+ event.preventDefault();
13655
+ return setFocus();
13656
+ };
13543
13657
  const dialogAccessibleLabel = computed$1(() => {
13544
13658
  if (props.comment.trackedChangeLabel) return props.comment.trackedChangeLabel;
13545
13659
  if (props.comment.trackedChange) return "Tracked change discussion";
@@ -14168,7 +14282,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
14168
14282
  "is-active": isDialogActive.value || isPendingNewComment.value,
14169
14283
  "is-resolved": props.comment.resolvedTime
14170
14284
  }]),
14171
- onClick: withModifiers(setFocus, ["stop", "prevent"]),
14285
+ onClick: withModifiers(handleDialogClick, ["stop"]),
14172
14286
  style: normalizeStyle(getSidebarCommentStyle.value),
14173
14287
  ref_key: "commentDialogElement",
14174
14288
  ref: commentDialogElement,
@@ -14305,7 +14419,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
14305
14419
  class: normalizeClass(["comment", index === 0 ? bodyOverflowClass.value : void 0]),
14306
14420
  ref_for: true,
14307
14421
  ref: index === 0 ? setParentBodyRef : void 0,
14308
- innerHTML: comment.commentText
14422
+ innerHTML: renderedCommentTexts.value.get(comment.commentId)
14309
14423
  }, null, 10, _hoisted_38)) : unref(isDebugging) && !isEditingThisComment.value(comment) ? (openBlock(), createElementBlock("div", _hoisted_39, toDisplayString(unref(editorCommentPositions)[comment.importedId !== void 0 ? comment.importedId : comment.commentId]?.bounds), 1)) : (openBlock(), createElementBlock("div", _hoisted_40, [createElementVNode("div", _hoisted_41, [createVNode(CommentInput_default, {
14310
14424
  ref_for: true,
14311
14425
  ref: setEditCommentInputRef(comment.commentId),
@@ -14381,7 +14495,7 @@ var CommentDialog_default = /*#__PURE__*/ _plugin_vue_export_helper_default({
14381
14495
  ], 64))], 14, _hoisted_1$27)), [[_directive_click_outside, handleClickOutside]]);
14382
14496
  };
14383
14497
  }
14384
- }, [["__scopeId", "data-v-dcbcc0d4"]]);
14498
+ }, [["__scopeId", "data-v-c189ab35"]]);
14385
14499
  //#endregion
14386
14500
  //#region src/components/CommentsLayer/commentsList/ReviewDirectoryListItem.vue
14387
14501
  var _hoisted_1$26 = [
@@ -18607,35 +18721,6 @@ function shouldHandleFindShortcut(event, { focusInside, owner }) {
18607
18721
  return (!active || active === document.body || active === document.documentElement) && ownsAmbientFindShortcut(owner);
18608
18722
  }
18609
18723
  //#endregion
18610
- //#region src/internal/toolbar/built-in/url-safety.js
18611
- /**
18612
- * Minimal href sanitizer for the built-in link popover.
18613
- *
18614
- * The v1 toolbar used the shared `@superdoc/url-validation` package, which is not
18615
- * a resolvable runtime dependency of the published `superdoc` ES build. This
18616
- * keeps the same fail-closed posture (only known-safe schemes pass) without
18617
- * pulling in that package: dangerous schemes (`javascript:`, `data:`,
18618
- * `vbscript:`) return `null`, mirroring the v1 contract that callers treat a
18619
- * `null` result as an invalid URL.
18620
- */
18621
- var SAFE_SCHEME = /^(https?|mailto|tel):/i;
18622
- var DANGEROUS_SCHEME = /^(javascript|data|vbscript):/i;
18623
- var HAS_SCHEME = /^[a-z][a-z0-9+.-]*:/i;
18624
- /**
18625
- * Validate and normalize an href. Returns `{ href }` for safe values, or `null`.
18626
- * @param {string} value
18627
- * @returns {{ href: string }|null}
18628
- */
18629
- function sanitizeHref(value) {
18630
- if (typeof value !== "string") return null;
18631
- const trimmed = value.trim();
18632
- if (!trimmed) return null;
18633
- if (trimmed.startsWith("#")) return { href: trimmed };
18634
- if (DANGEROUS_SCHEME.test(trimmed)) return null;
18635
- if (HAS_SCHEME.test(trimmed)) return SAFE_SCHEME.test(trimmed) ? { href: trimmed } : null;
18636
- return { href: trimmed };
18637
- }
18638
- //#endregion
18639
18724
  //#region ../../shared/common/icons/bold-solid.svg?raw
18640
18725
  var bold_solid_default = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 384 512\"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d=\"M0 64C0 46.3 14.3 32 32 32l48 0 16 0 128 0c70.7 0 128 57.3 128 128c0 31.3-11.3 60.1-30 82.3c37.1 22.4 62 63.1 62 109.7c0 70.7-57.3 128-128 128L96 480l-16 0-48 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l16 0 0-160L48 96 32 96C14.3 96 0 81.7 0 64zM224 224c35.3 0 64-28.7 64-64s-28.7-64-64-64L112 96l0 128 112 0zM112 288l0 128 144 0c35.3 0 64-28.7 64-64s-28.7-64-64-64l-32 0-112 0z\"/></svg>";
18641
18726
  //#endregion
@@ -46401,7 +46486,7 @@ var SuperDoc = class extends import_eventemitter3.default {
46401
46486
  }
46402
46487
  this.userColorMap = /* @__PURE__ */ new Map();
46403
46488
  this.colorIndex = 0;
46404
- this.version = "2.23.1-next.5";
46489
+ this.version = "2.23.1-next.7";
46405
46490
  this.#log("🦋 [superdoc] Using SuperDoc version:", this.version);
46406
46491
  this.superdocId = config.superdocId || v4();
46407
46492
  this.colors = this.config.colors ?? [];