@unhingged/vizu-core 0.1.12 → 0.1.13
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.cjs +183 -6
- package/dist/auto.cjs.map +1 -1
- package/dist/auto.js +183 -6
- package/dist/auto.js.map +1 -1
- package/dist/index.cjs +183 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +183 -6
- package/dist/index.js.map +1 -1
- package/dist/vizu.min.js +119 -54
- package/dist/vizu.min.js.map +1 -1
- package/package.json +1 -1
package/dist/auto.cjs
CHANGED
|
@@ -1261,6 +1261,10 @@ var Popover = class {
|
|
|
1261
1261
|
this.pendingRefs = {};
|
|
1262
1262
|
/** Attachments uploaded during this session, persisted into `comment.attachments` on save. */
|
|
1263
1263
|
this.pendingAttachments = [];
|
|
1264
|
+
/** Open mention picker element, if any. Held so the next openMentionPicker / close calls can tear it down. */
|
|
1265
|
+
this.mentionPicker = null;
|
|
1266
|
+
/** Dismiss handlers wired to document for the open picker; cleared in closeMentionPicker. */
|
|
1267
|
+
this.mentionPickerDismiss = null;
|
|
1264
1268
|
this.handleEditorKey = (e) => {
|
|
1265
1269
|
const editor = e.currentTarget;
|
|
1266
1270
|
if (e.key === "#") {
|
|
@@ -1367,7 +1371,7 @@ var Popover = class {
|
|
|
1367
1371
|
else if (action === "save") this.save();
|
|
1368
1372
|
else if (action === "mention") {
|
|
1369
1373
|
e.preventDefault();
|
|
1370
|
-
this.
|
|
1374
|
+
this.openMentionPicker(target);
|
|
1371
1375
|
} else if (action === "upload") {
|
|
1372
1376
|
e.preventDefault();
|
|
1373
1377
|
const input = this.el.querySelector('[data-vz="file-input"]');
|
|
@@ -1476,6 +1480,7 @@ var Popover = class {
|
|
|
1476
1480
|
this.position();
|
|
1477
1481
|
}
|
|
1478
1482
|
close() {
|
|
1483
|
+
this.closeMentionPicker();
|
|
1479
1484
|
this.el.style.display = "none";
|
|
1480
1485
|
this.anchorTargets = [];
|
|
1481
1486
|
this.anchorFingerprints = [];
|
|
@@ -1533,7 +1538,7 @@ var Popover = class {
|
|
|
1533
1538
|
<div class="vz-popover-actions">
|
|
1534
1539
|
<button class="vz-btn vz-btn-ghost" data-vz="close">Close</button>
|
|
1535
1540
|
${this.callbacks.canUploadAttachments() ? `<button class="vz-btn vz-btn-ghost vz-btn-upload" data-vz="upload" title="Attach an image">+ image</button>` : ""}
|
|
1536
|
-
${this.currentUser ? `<button class="vz-btn vz-btn-ghost vz-btn-mention" data-vz="mention" title="Mention
|
|
1541
|
+
${this.currentUser ? `<button class="vz-btn vz-btn-ghost vz-btn-mention" data-vz="mention" title="Mention a teammate">@ mention</button>` : ""}
|
|
1537
1542
|
<button class="vz-btn vz-btn-primary" data-vz="save">Save</button>
|
|
1538
1543
|
</div>
|
|
1539
1544
|
<input type="file" class="vz-file-input" data-vz="file-input" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml" hidden />
|
|
@@ -1702,11 +1707,104 @@ var Popover = class {
|
|
|
1702
1707
|
for (const child of editor.childNodes) walk(child);
|
|
1703
1708
|
return parts.join("").trim();
|
|
1704
1709
|
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Open a dropdown listing the workspace's mentionable users. The user
|
|
1712
|
+
* clicks one to insert a mention chip for them; Esc or an outside
|
|
1713
|
+
* click closes the dropdown.
|
|
1714
|
+
*
|
|
1715
|
+
* The dropdown is fetched fresh on every open so member adds/removes
|
|
1716
|
+
* in another tab are picked up without a popover reopen. Falls back
|
|
1717
|
+
* to silent no-op if `onMentionSearch` isn't wired (non-cloud
|
|
1718
|
+
* adapters) or returns an empty list.
|
|
1719
|
+
*/
|
|
1720
|
+
async openMentionPicker(anchor) {
|
|
1721
|
+
this.closeMentionPicker();
|
|
1722
|
+
if (!this.callbacks.onMentionSearch) return;
|
|
1723
|
+
const picker = document.createElement("div");
|
|
1724
|
+
picker.className = "vz-mention-picker";
|
|
1725
|
+
picker.setAttribute("data-vz-ignore", "");
|
|
1726
|
+
picker.innerHTML = `<div class="vz-mention-loading">Loading\u2026</div>`;
|
|
1727
|
+
this.el.appendChild(picker);
|
|
1728
|
+
this.mentionPicker = picker;
|
|
1729
|
+
this.positionMentionPicker(picker, anchor);
|
|
1730
|
+
let users = [];
|
|
1731
|
+
try {
|
|
1732
|
+
users = await this.callbacks.onMentionSearch();
|
|
1733
|
+
} catch {
|
|
1734
|
+
}
|
|
1735
|
+
if (this.mentionPicker !== picker) return;
|
|
1736
|
+
if (users.length === 0) {
|
|
1737
|
+
picker.innerHTML = `<div class="vz-mention-empty">No teammates to mention yet.</div>`;
|
|
1738
|
+
this.positionMentionPicker(picker, anchor);
|
|
1739
|
+
this.wireMentionPickerDismiss();
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
const itemsHtml = users.map((u) => {
|
|
1743
|
+
const avatar = u.avatarUrl ? `<img class="vz-mention-avatar" src="${escapeHtml(u.avatarUrl)}" alt="" />` : `<span class="vz-mention-avatar vz-mention-avatar-initials">${escapeHtml(initials(u.name) || "?")}</span>`;
|
|
1744
|
+
return `<button class="vz-mention-item" type="button" data-mention-id="${escapeHtml(u.id)}" data-mention-name="${escapeHtml(u.name)}">${avatar}<span class="vz-mention-item-name">${escapeHtml(u.name)}</span></button>`;
|
|
1745
|
+
}).join("");
|
|
1746
|
+
picker.innerHTML = itemsHtml;
|
|
1747
|
+
this.positionMentionPicker(picker, anchor);
|
|
1748
|
+
picker.addEventListener("click", (e) => {
|
|
1749
|
+
const btn = e.target?.closest(".vz-mention-item");
|
|
1750
|
+
if (!btn) return;
|
|
1751
|
+
e.preventDefault();
|
|
1752
|
+
e.stopPropagation();
|
|
1753
|
+
const id = btn.getAttribute("data-mention-id") || "";
|
|
1754
|
+
const name = btn.getAttribute("data-mention-name") || "";
|
|
1755
|
+
if (id && name) this.insertMentionChip({ id, name });
|
|
1756
|
+
this.closeMentionPicker();
|
|
1757
|
+
});
|
|
1758
|
+
this.wireMentionPickerDismiss();
|
|
1759
|
+
}
|
|
1760
|
+
/** Wires Esc + outside-click handlers to close the open picker. */
|
|
1761
|
+
wireMentionPickerDismiss() {
|
|
1762
|
+
if (this.mentionPickerDismiss) return;
|
|
1763
|
+
const onKey = (e) => {
|
|
1764
|
+
if (e.key === "Escape") {
|
|
1765
|
+
e.preventDefault();
|
|
1766
|
+
this.closeMentionPicker();
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
const onOutside = (e) => {
|
|
1770
|
+
if (!this.mentionPicker) return;
|
|
1771
|
+
if (this.mentionPicker.contains(e.target)) return;
|
|
1772
|
+
this.closeMentionPicker();
|
|
1773
|
+
};
|
|
1774
|
+
document.addEventListener("keydown", onKey, true);
|
|
1775
|
+
document.addEventListener("mousedown", onOutside, true);
|
|
1776
|
+
this.mentionPickerDismiss = () => {
|
|
1777
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1778
|
+
document.removeEventListener("mousedown", onOutside, true);
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
closeMentionPicker() {
|
|
1782
|
+
if (this.mentionPickerDismiss) {
|
|
1783
|
+
this.mentionPickerDismiss();
|
|
1784
|
+
this.mentionPickerDismiss = null;
|
|
1785
|
+
}
|
|
1786
|
+
if (this.mentionPicker) {
|
|
1787
|
+
this.mentionPicker.remove();
|
|
1788
|
+
this.mentionPicker = null;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Place the picker below the anchor button. Stays within the
|
|
1793
|
+
* popover's right edge so we don't overflow the popover frame.
|
|
1794
|
+
*/
|
|
1795
|
+
positionMentionPicker(picker, anchor) {
|
|
1796
|
+
const popRect = this.el.getBoundingClientRect();
|
|
1797
|
+
const btnRect = anchor.getBoundingClientRect();
|
|
1798
|
+
const left = btnRect.left - popRect.left;
|
|
1799
|
+
const top = btnRect.bottom - popRect.top + 4;
|
|
1800
|
+
picker.style.left = `${left}px`;
|
|
1801
|
+
picker.style.top = `${top}px`;
|
|
1802
|
+
}
|
|
1705
1803
|
/**
|
|
1706
1804
|
* Insert a mention chip for the given user into the editor at the
|
|
1707
|
-
* current caret position.
|
|
1708
|
-
*
|
|
1709
|
-
*
|
|
1805
|
+
* current caret position. Accepts any user-like shape with a required
|
|
1806
|
+
* `id` + `name` — covers both the current VizuUser (for self-mention)
|
|
1807
|
+
* and MentionableUser (from the workspace member picker).
|
|
1710
1808
|
*/
|
|
1711
1809
|
insertMentionChip(user) {
|
|
1712
1810
|
if (!user || !user.id) return;
|
|
@@ -1783,6 +1881,7 @@ var Popover = class {
|
|
|
1783
1881
|
this.el.style.left = left + "px";
|
|
1784
1882
|
}
|
|
1785
1883
|
destroy() {
|
|
1884
|
+
this.closeMentionPicker();
|
|
1786
1885
|
this.el.removeEventListener("click", this.handleClick);
|
|
1787
1886
|
this.el.remove();
|
|
1788
1887
|
}
|
|
@@ -2561,6 +2660,71 @@ var STYLES = `
|
|
|
2561
2660
|
}
|
|
2562
2661
|
.vz-btn-primary:hover { background: color-mix(in srgb, var(--vz-accent) 88%, white); }
|
|
2563
2662
|
|
|
2663
|
+
/* Mention picker \u2014 floating dropdown anchored under the @ mention button */
|
|
2664
|
+
.vz-mention-picker {
|
|
2665
|
+
position: absolute;
|
|
2666
|
+
z-index: 10;
|
|
2667
|
+
min-width: 200px;
|
|
2668
|
+
max-width: 280px;
|
|
2669
|
+
max-height: 240px;
|
|
2670
|
+
overflow-y: auto;
|
|
2671
|
+
padding: 4px;
|
|
2672
|
+
background: var(--vz-bg-popover, #1c1c1c);
|
|
2673
|
+
border: 1px solid var(--vz-border, rgba(255,255,255,0.08));
|
|
2674
|
+
border-radius: 8px;
|
|
2675
|
+
box-shadow: 0 12px 28px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.3);
|
|
2676
|
+
display: flex;
|
|
2677
|
+
flex-direction: column;
|
|
2678
|
+
gap: 1px;
|
|
2679
|
+
}
|
|
2680
|
+
.vz-mention-loading,
|
|
2681
|
+
.vz-mention-empty {
|
|
2682
|
+
padding: 10px 12px;
|
|
2683
|
+
font-size: 12px;
|
|
2684
|
+
color: var(--vz-text-muted);
|
|
2685
|
+
text-align: center;
|
|
2686
|
+
}
|
|
2687
|
+
.vz-mention-item {
|
|
2688
|
+
display: flex;
|
|
2689
|
+
align-items: center;
|
|
2690
|
+
gap: 8px;
|
|
2691
|
+
padding: 7px 10px;
|
|
2692
|
+
border-radius: 6px;
|
|
2693
|
+
background: transparent;
|
|
2694
|
+
border: none;
|
|
2695
|
+
color: #fafafa;
|
|
2696
|
+
font-size: 13px;
|
|
2697
|
+
text-align: left;
|
|
2698
|
+
cursor: pointer;
|
|
2699
|
+
transition: background 80ms;
|
|
2700
|
+
}
|
|
2701
|
+
.vz-mention-item:hover,
|
|
2702
|
+
.vz-mention-item:focus-visible {
|
|
2703
|
+
background: var(--vz-bg-hover, rgba(255,255,255,0.06));
|
|
2704
|
+
outline: none;
|
|
2705
|
+
}
|
|
2706
|
+
.vz-mention-avatar {
|
|
2707
|
+
display: inline-flex;
|
|
2708
|
+
align-items: center;
|
|
2709
|
+
justify-content: center;
|
|
2710
|
+
width: 22px; height: 22px;
|
|
2711
|
+
border-radius: 50%;
|
|
2712
|
+
font-size: 10px; font-weight: 600;
|
|
2713
|
+
color: var(--vz-marker-fg, #0a0a0a);
|
|
2714
|
+
background: var(--vz-accent, #ff8b6e);
|
|
2715
|
+
flex-shrink: 0;
|
|
2716
|
+
overflow: hidden;
|
|
2717
|
+
}
|
|
2718
|
+
.vz-mention-avatar img {
|
|
2719
|
+
width: 100%; height: 100%; object-fit: cover;
|
|
2720
|
+
}
|
|
2721
|
+
.vz-mention-item-name {
|
|
2722
|
+
flex: 1;
|
|
2723
|
+
white-space: nowrap;
|
|
2724
|
+
overflow: hidden;
|
|
2725
|
+
text-overflow: ellipsis;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2564
2728
|
/* Sidebar */
|
|
2565
2729
|
.vz-sidebar {
|
|
2566
2730
|
position: fixed;
|
|
@@ -3328,6 +3492,18 @@ var Vizu = class {
|
|
|
3328
3492
|
getUser() {
|
|
3329
3493
|
return this.user;
|
|
3330
3494
|
}
|
|
3495
|
+
/**
|
|
3496
|
+
* Fetch the list of users who can be @-mentioned on the current
|
|
3497
|
+
* workspace. Used by the popover's mention dropdown; hosts can also
|
|
3498
|
+
* call this directly to build their own picker. Returns [] for
|
|
3499
|
+
* non-cloud adapters (no workspace, no member list to fetch from).
|
|
3500
|
+
*/
|
|
3501
|
+
async searchMentionable() {
|
|
3502
|
+
if (this.storage instanceof CloudStorageAdapter) {
|
|
3503
|
+
return this.storage.searchMentionable();
|
|
3504
|
+
}
|
|
3505
|
+
return [];
|
|
3506
|
+
}
|
|
3331
3507
|
/**
|
|
3332
3508
|
* Whether the full Vizu UI (pill, highlighter, markers, popover) can
|
|
3333
3509
|
* be mounted right now. Cloud-mode workspaces hold off until the
|
|
@@ -3683,7 +3859,8 @@ var Vizu = class {
|
|
|
3683
3859
|
onClose: () => this.closePopover(),
|
|
3684
3860
|
onStartReferencePick: (onPicked, onCancel) => this.startPicking(onPicked, onCancel),
|
|
3685
3861
|
onJumpToReference: (commentId, refId2) => this.jumpToReference(commentId, refId2),
|
|
3686
|
-
onAnchorsChanged: (fps) => this.syncActiveAnchorOutlines(fps)
|
|
3862
|
+
onAnchorsChanged: (fps) => this.syncActiveAnchorOutlines(fps),
|
|
3863
|
+
onMentionSearch: () => this.searchMentionable()
|
|
3687
3864
|
});
|
|
3688
3865
|
this.popover.setUser(this.user);
|
|
3689
3866
|
this.sidebar = new Sidebar(this.root, {
|