@unhingged/vizu-core 0.1.9 → 0.1.11

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/auto.js CHANGED
@@ -12,7 +12,8 @@ function isInside(target, selector) {
12
12
  return !!target.closest(selector);
13
13
  }
14
14
  function escapeHtml(s) {
15
- return s.replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]);
15
+ if (s == null) return "";
16
+ return String(s).replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch]);
16
17
  }
17
18
  function formatTime(ts) {
18
19
  const d = new Date(ts);
@@ -24,14 +25,16 @@ function formatTime(ts) {
24
25
  return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
25
26
  }
26
27
  function initials(name) {
27
- return name.split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0].toUpperCase()).join("");
28
+ if (!name) return "";
29
+ return String(name).split(/\s+/).filter(Boolean).slice(0, 2).map((p) => p[0].toUpperCase()).join("");
28
30
  }
29
31
  function avatarHtml(user, className = "vz-comment-author-avatar") {
30
32
  if (!user) return `<span class="${className}">\xB7</span>`;
33
+ const displayName = user.name || user.id || "User";
31
34
  if (user.avatarUrl) {
32
- return `<span class="${className}"><img src="${escapeHtml(user.avatarUrl)}" alt="${escapeHtml(user.name)}" /></span>`;
35
+ return `<span class="${className}"><img src="${escapeHtml(user.avatarUrl)}" alt="${escapeHtml(displayName)}" /></span>`;
33
36
  }
34
- return `<span class="${className}">${escapeHtml(initials(user.name) || "?")}</span>`;
37
+ return `<span class="${className}">${escapeHtml(initials(displayName) || "?")}</span>`;
35
38
  }
36
39
  function refId() {
37
40
  return Math.random().toString(36).slice(2, 10);
@@ -434,6 +437,26 @@ var CloudStorageAdapter = class {
434
437
  accessDenialReason() {
435
438
  return this.accessDeniedReason;
436
439
  }
440
+ /**
441
+ * Fetch the list of users who can be @-mentioned on this workspace.
442
+ * Used by the popover's mention dropdown — returns owner + joined
443
+ * editors/viewers, sorted owner-first. Silent on no auth (returns [])
444
+ * so popover doesn't open a popup just to render an empty dropdown.
445
+ */
446
+ async searchMentionable() {
447
+ if (!this.cachedToken || this.isExpired(this.cachedToken)) return [];
448
+ try {
449
+ const res = await this.fetchAuthed(
450
+ this.apiUrl + "/api/workspaces/" + encodeURIComponent(this.workspace) + "/mentionable",
451
+ { method: "GET" }
452
+ );
453
+ if (!res.ok) return [];
454
+ const json = await this.parseJson(res);
455
+ return Array.isArray(json?.users) ? json.users : [];
456
+ } catch {
457
+ return [];
458
+ }
459
+ }
437
460
  /* ─── StorageAdapter v2 ───────────────────────────────────────────── */
438
461
  async load(_namespace) {
439
462
  if (!this.cachedToken || this.isExpired(this.cachedToken)) {
@@ -1183,7 +1206,7 @@ function renderRepliesHtml(c) {
1183
1206
  const items = replies.map(
1184
1207
  (r) => `
1185
1208
  <div class="vz-reply" data-reply-id="${escapeHtml(r.id)}">
1186
- ${r.author ? `<div class="vz-comment-author">${avatarHtml(r.author)} <span class="vz-comment-author-name">${escapeHtml(r.author.name)}</span></div>` : ""}
1209
+ ${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>` : ""}
1187
1210
  <div class="vz-reply-text">${escapeHtml(r.text)}</div>
1188
1211
  <div class="vz-comment-meta">
1189
1212
  <span>${escapeHtml(formatTime(typeof r.createdAt === "number" ? r.createdAt : new Date(r.createdAt).getTime()))}</span>
@@ -1460,7 +1483,7 @@ var Popover = class {
1460
1483
  ${comments.length === 0 ? '<div class="vz-empty">No comments yet.</div>' : comments.map(
1461
1484
  (c) => `
1462
1485
  <div class="vz-comment" data-comment-id="${escapeHtml(c.id)}">
1463
- ${c.author ? `<div class="vz-comment-author">${avatarHtml(c.author)} <span class="vz-comment-author-name">${escapeHtml(c.author.name)}</span></div>` : ""}
1486
+ ${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>` : ""}
1464
1487
  <div>${renderCommentText(c.text, c.id)}</div>
1465
1488
  ${renderAttachmentsHtml(c.attachments)}
1466
1489
  <div class="vz-comment-meta">
@@ -3271,11 +3294,25 @@ var Vizu = class {
3271
3294
  this.user = user;
3272
3295
  this.popover?.setUser(user);
3273
3296
  this.pill?.setUser(user);
3297
+ if (this.pill) {
3298
+ if (this.shouldShowPill()) this.pill.show();
3299
+ else this.pill.hide();
3300
+ }
3274
3301
  this.bus.emit("user:changed", { user });
3275
3302
  }
3276
3303
  getUser() {
3277
3304
  return this.user;
3278
3305
  }
3306
+ /**
3307
+ * Whether the pill belongs on-screen right now. Cloud-mode workspaces
3308
+ * keep it hidden until the user is signed in; non-cloud usage (local /
3309
+ * memory storage) has no auth and always shows it. Recomputed on every
3310
+ * setUser and on mount.
3311
+ */
3312
+ shouldShowPill() {
3313
+ if (!(this.storage instanceof CloudStorageAdapter)) return true;
3314
+ return this.user !== null;
3315
+ }
3279
3316
  /**
3280
3317
  * Cloud-mode write gate. Every entry point that creates or modifies a
3281
3318
  * comment first checks that we have a known user. Without one, the
@@ -3639,7 +3676,7 @@ var Vizu = class {
3639
3676
  this.pill.setUser(this.user);
3640
3677
  this.pill.setComments(this.comments);
3641
3678
  this.pill.setMultiSelectState(this.multiSelectMode, this.pendingFingerprints.length);
3642
- this.pill.show();
3679
+ if (this.shouldShowPill()) this.pill.show();
3643
3680
  this.highlighter = new Highlighter(
3644
3681
  this.root,
3645
3682
  this.opts.ignoreSelectors ?? [],