@unhingged/vizu-core 0.1.10 → 0.1.12

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/index.cjs CHANGED
@@ -48,7 +48,8 @@ function isInside(target, selector) {
48
48
  return !!target.closest(selector);
49
49
  }
50
50
  function escapeHtml(s) {
51
- return s.replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]);
51
+ if (s == null) return "";
52
+ return String(s).replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]);
52
53
  }
53
54
  function formatTime(ts) {
54
55
  const d = new Date(ts);
@@ -60,14 +61,16 @@ function formatTime(ts) {
60
61
  return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
61
62
  }
62
63
  function initials(name) {
63
- return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0].toUpperCase()).join("");
64
+ if (!name) return "";
65
+ return String(name).split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0].toUpperCase()).join("");
64
66
  }
65
67
  function avatarHtml(user, className = "vz-comment-author-avatar") {
66
68
  if (!user) return `<span class="${className}">\xB7</span>`;
69
+ const displayName = user.name || user.id || "User";
67
70
  if (user.avatarUrl) {
68
- return `<span class="${className}"><img src="${escapeHtml(user.avatarUrl)}" alt="${escapeHtml(user.name)}" /></span>`;
71
+ return `<span class="${className}"><img src="${escapeHtml(user.avatarUrl)}" alt="${escapeHtml(displayName)}" /></span>`;
69
72
  }
70
- return `<span class="${className}">${escapeHtml(initials(user.name) || "?")}</span>`;
73
+ return `<span class="${className}">${escapeHtml(initials(displayName) || "?")}</span>`;
71
74
  }
72
75
  function refId() {
73
76
  return Math.random().toString(36).slice(2, 10);
@@ -470,6 +473,26 @@ var CloudStorageAdapter = class {
470
473
  accessDenialReason() {
471
474
  return this.accessDeniedReason;
472
475
  }
476
+ /**
477
+ * Fetch the list of users who can be @-mentioned on this workspace.
478
+ * Used by the popover's mention dropdown — returns owner + joined
479
+ * editors/viewers, sorted owner-first. Silent on no auth (returns [])
480
+ * so popover doesn't open a popup just to render an empty dropdown.
481
+ */
482
+ async searchMentionable() {
483
+ if (!this.cachedToken || this.isExpired(this.cachedToken)) return [];
484
+ try {
485
+ const res = await this.fetchAuthed(
486
+ this.apiUrl + "/api/workspaces/" + encodeURIComponent(this.workspace) + "/mentionable",
487
+ { method: "GET" }
488
+ );
489
+ if (!res.ok) return [];
490
+ const json = await this.parseJson(res);
491
+ return Array.isArray(json?.users) ? json.users : [];
492
+ } catch {
493
+ return [];
494
+ }
495
+ }
473
496
  /* ─── StorageAdapter v2 ───────────────────────────────────────────── */
474
497
  async load(_namespace) {
475
498
  if (!this.cachedToken || this.isExpired(this.cachedToken)) {
@@ -1219,7 +1242,7 @@ function renderRepliesHtml(c) {
1219
1242
  const items = replies.map(
1220
1243
  (r) => `
1221
1244
  <div class="vz-reply" data-reply-id="${escapeHtml(r.id)}">
1222
- ${r.author ? `<div class="vz-comment-author">${avatarHtml(r.author)} <span class="vz-comment-author-name">${escapeHtml(r.author.name)}</span></div>` : ""}
1245
+ ${r.author ? `<div class="vz-comment-author">${avatarHtml(r.author)} <span class="vz-comment-author-name">${escapeHtml(r.author.name || r.author.id || "User")}</span></div>` : ""}
1223
1246
  <div class="vz-reply-text">${escapeHtml(r.text)}</div>
1224
1247
  <div class="vz-comment-meta">
1225
1248
  <span>${escapeHtml(formatTime(typeof r.createdAt === "number" ? r.createdAt : new Date(r.createdAt).getTime()))}</span>
@@ -1496,7 +1519,7 @@ var Popover = class {
1496
1519
  ${comments.length === 0 ? '<div class="vz-empty">No comments yet.</div>' : comments.map(
1497
1520
  (c) => `
1498
1521
  <div class="vz-comment" data-comment-id="${escapeHtml(c.id)}">
1499
- ${c.author ? `<div class="vz-comment-author">${avatarHtml(c.author)} <span class="vz-comment-author-name">${escapeHtml(c.author.name)}</span></div>` : ""}
1522
+ ${c.author ? `<div class="vz-comment-author">${avatarHtml(c.author)} <span class="vz-comment-author-name">${escapeHtml(c.author.name || c.author.id || "User")}</span></div>` : ""}
1500
1523
  <div>${renderCommentText(c.text, c.id)}</div>
1501
1524
  ${renderAttachmentsHtml(c.attachments)}
1502
1525
  <div class="vz-comment-meta">
@@ -3307,22 +3330,23 @@ var Vizu = class {
3307
3330
  this.user = user;
3308
3331
  this.popover?.setUser(user);
3309
3332
  this.pill?.setUser(user);
3310
- if (this.pill) {
3311
- if (this.shouldShowPill()) this.pill.show();
3312
- else this.pill.hide();
3313
- }
3314
3333
  this.bus.emit("user:changed", { user });
3334
+ if (this.enabled && !this.root && this.shouldMount()) {
3335
+ this.deferred(() => this.mount());
3336
+ }
3315
3337
  }
3316
3338
  getUser() {
3317
3339
  return this.user;
3318
3340
  }
3319
3341
  /**
3320
- * Whether the pill belongs on-screen right now. Cloud-mode workspaces
3321
- * keep it hidden until the user is signed in; non-cloud usage (local /
3322
- * memory storage) has no auth and always shows it. Recomputed on every
3323
- * setUser and on mount.
3342
+ * Whether the full Vizu UI (pill, highlighter, markers, popover) can
3343
+ * be mounted right now. Cloud-mode workspaces hold off until the
3344
+ * user is signed in; non-cloud usage (local / memory storage) has no
3345
+ * auth concept and mounts immediately. Re-checked from `enable()`
3346
+ * and from `setUser()` so that the moment auth resolves, the UI
3347
+ * appears without the user needing to press the shortcut again.
3324
3348
  */
3325
- shouldShowPill() {
3349
+ shouldMount() {
3326
3350
  if (!(this.storage instanceof CloudStorageAdapter)) return true;
3327
3351
  return this.user !== null;
3328
3352
  }
@@ -3349,7 +3373,9 @@ var Vizu = class {
3349
3373
  if (this.enabled) return;
3350
3374
  this.enabled = true;
3351
3375
  this.bus.emit("enabled", {});
3352
- this.deferred(() => this.mount());
3376
+ if (this.shouldMount()) {
3377
+ this.deferred(() => this.mount());
3378
+ }
3353
3379
  }
3354
3380
  disable() {
3355
3381
  if (!this.enabled) return;
@@ -3689,7 +3715,7 @@ var Vizu = class {
3689
3715
  this.pill.setUser(this.user);
3690
3716
  this.pill.setComments(this.comments);
3691
3717
  this.pill.setMultiSelectState(this.multiSelectMode, this.pendingFingerprints.length);
3692
- if (this.shouldShowPill()) this.pill.show();
3718
+ this.pill.show();
3693
3719
  this.highlighter = new Highlighter(
3694
3720
  this.root,
3695
3721
  this.opts.ignoreSelectors ?? [],