@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/index.cjs
CHANGED
|
@@ -1271,6 +1271,10 @@ var Popover = class {
|
|
|
1271
1271
|
this.pendingRefs = {};
|
|
1272
1272
|
/** Attachments uploaded during this session, persisted into `comment.attachments` on save. */
|
|
1273
1273
|
this.pendingAttachments = [];
|
|
1274
|
+
/** Open mention picker element, if any. Held so the next openMentionPicker / close calls can tear it down. */
|
|
1275
|
+
this.mentionPicker = null;
|
|
1276
|
+
/** Dismiss handlers wired to document for the open picker; cleared in closeMentionPicker. */
|
|
1277
|
+
this.mentionPickerDismiss = null;
|
|
1274
1278
|
this.handleEditorKey = (e) => {
|
|
1275
1279
|
const editor = e.currentTarget;
|
|
1276
1280
|
if (e.key === "#") {
|
|
@@ -1377,7 +1381,7 @@ var Popover = class {
|
|
|
1377
1381
|
else if (action === "save") this.save();
|
|
1378
1382
|
else if (action === "mention") {
|
|
1379
1383
|
e.preventDefault();
|
|
1380
|
-
this.
|
|
1384
|
+
this.openMentionPicker(target);
|
|
1381
1385
|
} else if (action === "upload") {
|
|
1382
1386
|
e.preventDefault();
|
|
1383
1387
|
const input = this.el.querySelector('[data-vz="file-input"]');
|
|
@@ -1486,6 +1490,7 @@ var Popover = class {
|
|
|
1486
1490
|
this.position();
|
|
1487
1491
|
}
|
|
1488
1492
|
close() {
|
|
1493
|
+
this.closeMentionPicker();
|
|
1489
1494
|
this.el.style.display = "none";
|
|
1490
1495
|
this.anchorTargets = [];
|
|
1491
1496
|
this.anchorFingerprints = [];
|
|
@@ -1543,7 +1548,7 @@ var Popover = class {
|
|
|
1543
1548
|
<div class="vz-popover-actions">
|
|
1544
1549
|
<button class="vz-btn vz-btn-ghost" data-vz="close">Close</button>
|
|
1545
1550
|
${this.callbacks.canUploadAttachments() ? `<button class="vz-btn vz-btn-ghost vz-btn-upload" data-vz="upload" title="Attach an image">+ image</button>` : ""}
|
|
1546
|
-
${this.currentUser ? `<button class="vz-btn vz-btn-ghost vz-btn-mention" data-vz="mention" title="Mention
|
|
1551
|
+
${this.currentUser ? `<button class="vz-btn vz-btn-ghost vz-btn-mention" data-vz="mention" title="Mention a teammate">@ mention</button>` : ""}
|
|
1547
1552
|
<button class="vz-btn vz-btn-primary" data-vz="save">Save</button>
|
|
1548
1553
|
</div>
|
|
1549
1554
|
<input type="file" class="vz-file-input" data-vz="file-input" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml" hidden />
|
|
@@ -1712,11 +1717,104 @@ var Popover = class {
|
|
|
1712
1717
|
for (const child of editor.childNodes) walk(child);
|
|
1713
1718
|
return parts.join("").trim();
|
|
1714
1719
|
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Open a dropdown listing the workspace's mentionable users. The user
|
|
1722
|
+
* clicks one to insert a mention chip for them; Esc or an outside
|
|
1723
|
+
* click closes the dropdown.
|
|
1724
|
+
*
|
|
1725
|
+
* The dropdown is fetched fresh on every open so member adds/removes
|
|
1726
|
+
* in another tab are picked up without a popover reopen. Falls back
|
|
1727
|
+
* to silent no-op if `onMentionSearch` isn't wired (non-cloud
|
|
1728
|
+
* adapters) or returns an empty list.
|
|
1729
|
+
*/
|
|
1730
|
+
async openMentionPicker(anchor) {
|
|
1731
|
+
this.closeMentionPicker();
|
|
1732
|
+
if (!this.callbacks.onMentionSearch) return;
|
|
1733
|
+
const picker = document.createElement("div");
|
|
1734
|
+
picker.className = "vz-mention-picker";
|
|
1735
|
+
picker.setAttribute("data-vz-ignore", "");
|
|
1736
|
+
picker.innerHTML = `<div class="vz-mention-loading">Loading\u2026</div>`;
|
|
1737
|
+
this.el.appendChild(picker);
|
|
1738
|
+
this.mentionPicker = picker;
|
|
1739
|
+
this.positionMentionPicker(picker, anchor);
|
|
1740
|
+
let users = [];
|
|
1741
|
+
try {
|
|
1742
|
+
users = await this.callbacks.onMentionSearch();
|
|
1743
|
+
} catch {
|
|
1744
|
+
}
|
|
1745
|
+
if (this.mentionPicker !== picker) return;
|
|
1746
|
+
if (users.length === 0) {
|
|
1747
|
+
picker.innerHTML = `<div class="vz-mention-empty">No teammates to mention yet.</div>`;
|
|
1748
|
+
this.positionMentionPicker(picker, anchor);
|
|
1749
|
+
this.wireMentionPickerDismiss();
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1752
|
+
const itemsHtml = users.map((u) => {
|
|
1753
|
+
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>`;
|
|
1754
|
+
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>`;
|
|
1755
|
+
}).join("");
|
|
1756
|
+
picker.innerHTML = itemsHtml;
|
|
1757
|
+
this.positionMentionPicker(picker, anchor);
|
|
1758
|
+
picker.addEventListener("click", (e) => {
|
|
1759
|
+
const btn = e.target?.closest(".vz-mention-item");
|
|
1760
|
+
if (!btn) return;
|
|
1761
|
+
e.preventDefault();
|
|
1762
|
+
e.stopPropagation();
|
|
1763
|
+
const id = btn.getAttribute("data-mention-id") || "";
|
|
1764
|
+
const name = btn.getAttribute("data-mention-name") || "";
|
|
1765
|
+
if (id && name) this.insertMentionChip({ id, name });
|
|
1766
|
+
this.closeMentionPicker();
|
|
1767
|
+
});
|
|
1768
|
+
this.wireMentionPickerDismiss();
|
|
1769
|
+
}
|
|
1770
|
+
/** Wires Esc + outside-click handlers to close the open picker. */
|
|
1771
|
+
wireMentionPickerDismiss() {
|
|
1772
|
+
if (this.mentionPickerDismiss) return;
|
|
1773
|
+
const onKey = (e) => {
|
|
1774
|
+
if (e.key === "Escape") {
|
|
1775
|
+
e.preventDefault();
|
|
1776
|
+
this.closeMentionPicker();
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
const onOutside = (e) => {
|
|
1780
|
+
if (!this.mentionPicker) return;
|
|
1781
|
+
if (this.mentionPicker.contains(e.target)) return;
|
|
1782
|
+
this.closeMentionPicker();
|
|
1783
|
+
};
|
|
1784
|
+
document.addEventListener("keydown", onKey, true);
|
|
1785
|
+
document.addEventListener("mousedown", onOutside, true);
|
|
1786
|
+
this.mentionPickerDismiss = () => {
|
|
1787
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1788
|
+
document.removeEventListener("mousedown", onOutside, true);
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
closeMentionPicker() {
|
|
1792
|
+
if (this.mentionPickerDismiss) {
|
|
1793
|
+
this.mentionPickerDismiss();
|
|
1794
|
+
this.mentionPickerDismiss = null;
|
|
1795
|
+
}
|
|
1796
|
+
if (this.mentionPicker) {
|
|
1797
|
+
this.mentionPicker.remove();
|
|
1798
|
+
this.mentionPicker = null;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Place the picker below the anchor button. Stays within the
|
|
1803
|
+
* popover's right edge so we don't overflow the popover frame.
|
|
1804
|
+
*/
|
|
1805
|
+
positionMentionPicker(picker, anchor) {
|
|
1806
|
+
const popRect = this.el.getBoundingClientRect();
|
|
1807
|
+
const btnRect = anchor.getBoundingClientRect();
|
|
1808
|
+
const left = btnRect.left - popRect.left;
|
|
1809
|
+
const top = btnRect.bottom - popRect.top + 4;
|
|
1810
|
+
picker.style.left = `${left}px`;
|
|
1811
|
+
picker.style.top = `${top}px`;
|
|
1812
|
+
}
|
|
1715
1813
|
/**
|
|
1716
1814
|
* Insert a mention chip for the given user into the editor at the
|
|
1717
|
-
* current caret position.
|
|
1718
|
-
*
|
|
1719
|
-
*
|
|
1815
|
+
* current caret position. Accepts any user-like shape with a required
|
|
1816
|
+
* `id` + `name` — covers both the current VizuUser (for self-mention)
|
|
1817
|
+
* and MentionableUser (from the workspace member picker).
|
|
1720
1818
|
*/
|
|
1721
1819
|
insertMentionChip(user) {
|
|
1722
1820
|
if (!user || !user.id) return;
|
|
@@ -1793,6 +1891,7 @@ var Popover = class {
|
|
|
1793
1891
|
this.el.style.left = left + "px";
|
|
1794
1892
|
}
|
|
1795
1893
|
destroy() {
|
|
1894
|
+
this.closeMentionPicker();
|
|
1796
1895
|
this.el.removeEventListener("click", this.handleClick);
|
|
1797
1896
|
this.el.remove();
|
|
1798
1897
|
}
|
|
@@ -2571,6 +2670,71 @@ var STYLES = `
|
|
|
2571
2670
|
}
|
|
2572
2671
|
.vz-btn-primary:hover { background: color-mix(in srgb, var(--vz-accent) 88%, white); }
|
|
2573
2672
|
|
|
2673
|
+
/* Mention picker \u2014 floating dropdown anchored under the @ mention button */
|
|
2674
|
+
.vz-mention-picker {
|
|
2675
|
+
position: absolute;
|
|
2676
|
+
z-index: 10;
|
|
2677
|
+
min-width: 200px;
|
|
2678
|
+
max-width: 280px;
|
|
2679
|
+
max-height: 240px;
|
|
2680
|
+
overflow-y: auto;
|
|
2681
|
+
padding: 4px;
|
|
2682
|
+
background: var(--vz-bg-popover, #1c1c1c);
|
|
2683
|
+
border: 1px solid var(--vz-border, rgba(255,255,255,0.08));
|
|
2684
|
+
border-radius: 8px;
|
|
2685
|
+
box-shadow: 0 12px 28px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.3);
|
|
2686
|
+
display: flex;
|
|
2687
|
+
flex-direction: column;
|
|
2688
|
+
gap: 1px;
|
|
2689
|
+
}
|
|
2690
|
+
.vz-mention-loading,
|
|
2691
|
+
.vz-mention-empty {
|
|
2692
|
+
padding: 10px 12px;
|
|
2693
|
+
font-size: 12px;
|
|
2694
|
+
color: var(--vz-text-muted);
|
|
2695
|
+
text-align: center;
|
|
2696
|
+
}
|
|
2697
|
+
.vz-mention-item {
|
|
2698
|
+
display: flex;
|
|
2699
|
+
align-items: center;
|
|
2700
|
+
gap: 8px;
|
|
2701
|
+
padding: 7px 10px;
|
|
2702
|
+
border-radius: 6px;
|
|
2703
|
+
background: transparent;
|
|
2704
|
+
border: none;
|
|
2705
|
+
color: #fafafa;
|
|
2706
|
+
font-size: 13px;
|
|
2707
|
+
text-align: left;
|
|
2708
|
+
cursor: pointer;
|
|
2709
|
+
transition: background 80ms;
|
|
2710
|
+
}
|
|
2711
|
+
.vz-mention-item:hover,
|
|
2712
|
+
.vz-mention-item:focus-visible {
|
|
2713
|
+
background: var(--vz-bg-hover, rgba(255,255,255,0.06));
|
|
2714
|
+
outline: none;
|
|
2715
|
+
}
|
|
2716
|
+
.vz-mention-avatar {
|
|
2717
|
+
display: inline-flex;
|
|
2718
|
+
align-items: center;
|
|
2719
|
+
justify-content: center;
|
|
2720
|
+
width: 22px; height: 22px;
|
|
2721
|
+
border-radius: 50%;
|
|
2722
|
+
font-size: 10px; font-weight: 600;
|
|
2723
|
+
color: var(--vz-marker-fg, #0a0a0a);
|
|
2724
|
+
background: var(--vz-accent, #ff8b6e);
|
|
2725
|
+
flex-shrink: 0;
|
|
2726
|
+
overflow: hidden;
|
|
2727
|
+
}
|
|
2728
|
+
.vz-mention-avatar img {
|
|
2729
|
+
width: 100%; height: 100%; object-fit: cover;
|
|
2730
|
+
}
|
|
2731
|
+
.vz-mention-item-name {
|
|
2732
|
+
flex: 1;
|
|
2733
|
+
white-space: nowrap;
|
|
2734
|
+
overflow: hidden;
|
|
2735
|
+
text-overflow: ellipsis;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2574
2738
|
/* Sidebar */
|
|
2575
2739
|
.vz-sidebar {
|
|
2576
2740
|
position: fixed;
|
|
@@ -3338,6 +3502,18 @@ var Vizu = class {
|
|
|
3338
3502
|
getUser() {
|
|
3339
3503
|
return this.user;
|
|
3340
3504
|
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Fetch the list of users who can be @-mentioned on the current
|
|
3507
|
+
* workspace. Used by the popover's mention dropdown; hosts can also
|
|
3508
|
+
* call this directly to build their own picker. Returns [] for
|
|
3509
|
+
* non-cloud adapters (no workspace, no member list to fetch from).
|
|
3510
|
+
*/
|
|
3511
|
+
async searchMentionable() {
|
|
3512
|
+
if (this.storage instanceof CloudStorageAdapter) {
|
|
3513
|
+
return this.storage.searchMentionable();
|
|
3514
|
+
}
|
|
3515
|
+
return [];
|
|
3516
|
+
}
|
|
3341
3517
|
/**
|
|
3342
3518
|
* Whether the full Vizu UI (pill, highlighter, markers, popover) can
|
|
3343
3519
|
* be mounted right now. Cloud-mode workspaces hold off until the
|
|
@@ -3693,7 +3869,8 @@ var Vizu = class {
|
|
|
3693
3869
|
onClose: () => this.closePopover(),
|
|
3694
3870
|
onStartReferencePick: (onPicked, onCancel) => this.startPicking(onPicked, onCancel),
|
|
3695
3871
|
onJumpToReference: (commentId, refId2) => this.jumpToReference(commentId, refId2),
|
|
3696
|
-
onAnchorsChanged: (fps) => this.syncActiveAnchorOutlines(fps)
|
|
3872
|
+
onAnchorsChanged: (fps) => this.syncActiveAnchorOutlines(fps),
|
|
3873
|
+
onMentionSearch: () => this.searchMentionable()
|
|
3697
3874
|
});
|
|
3698
3875
|
this.popover.setUser(this.user);
|
|
3699
3876
|
this.sidebar = new Sidebar(this.root, {
|