@autono/pinbox-toolbar 0.13.0 → 0.14.0
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-DK_kVd2Z.d.ts → index-BVd-ZD4Y.d.ts} +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{src-C3zvwQrr.js → src-GX3kPe0X.js} +306 -107
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +1 -1
- package/dist/toolbar.iife.js +306 -107
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +1 -1
- package/package.json +2 -2
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
//#region src/anchor-watch.ts
|
|
2
|
+
function watchAnchors(win, onChange) {
|
|
3
|
+
let frame = 0;
|
|
4
|
+
const schedule = () => {
|
|
5
|
+
if (frame !== 0) return;
|
|
6
|
+
frame = win.requestAnimationFrame(() => {
|
|
7
|
+
frame = 0;
|
|
8
|
+
onChange();
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
const observer = new (win.MutationObserver ?? MutationObserver)(schedule);
|
|
12
|
+
observer.observe(win.document.body, {
|
|
13
|
+
childList: true,
|
|
14
|
+
subtree: true
|
|
15
|
+
});
|
|
16
|
+
win.addEventListener("popstate", schedule);
|
|
17
|
+
return { destroy() {
|
|
18
|
+
observer.disconnect();
|
|
19
|
+
win.removeEventListener("popstate", schedule);
|
|
20
|
+
if (frame !== 0) win.cancelAnimationFrame(frame);
|
|
21
|
+
frame = 0;
|
|
22
|
+
} };
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
1
25
|
//#region src/targeting/dom.ts
|
|
2
26
|
/**
|
|
3
27
|
* Deepest element under (clientX, clientY) that the caller does not ignore, or null when there is
|
|
@@ -270,9 +294,10 @@ function threadTail(thread) {
|
|
|
270
294
|
function block(pin, thread) {
|
|
271
295
|
const { selector, url, source } = pin.target ?? {};
|
|
272
296
|
return [
|
|
273
|
-
`## Pin ${pin.id} —
|
|
297
|
+
`## Pin ${pin.n === void 0 ? pin.id : `#${pin.n} (${pin.id})`} — ${pin.status.toUpperCase()}`,
|
|
274
298
|
`- label: ${line(label(pin))}`,
|
|
275
299
|
...selector === void 0 ? [] : [`- selector: \`${line(selector)}\``],
|
|
300
|
+
...(pin.target?.targets ?? []).map((t) => t.selector ?? t.anchor ?? t.tag).filter((locus) => locus !== void 0).map((locus) => `- also: \`${line(locus)}\``),
|
|
276
301
|
...source === void 0 ? [] : [`- source: ${line(source.line === void 0 ? source.file : `${source.file}:${source.line}`)}`],
|
|
277
302
|
...url === void 0 ? [] : [`- url: ${line(url)}`],
|
|
278
303
|
"",
|
|
@@ -286,6 +311,11 @@ function pinsToMarkdown(pins, threads) {
|
|
|
286
311
|
if (open.length === 0) return "No open pins.\n";
|
|
287
312
|
return `${open.map((p) => block(p, threads.get(p.id) ?? [])).join("\n\n")}\n`;
|
|
288
313
|
}
|
|
314
|
+
/** One pin's block — the card's per-pin copy (dogfood: "I want to copy an
|
|
315
|
+
* individual pin"); any status, since you copy exactly what you're looking at. */
|
|
316
|
+
function pinToMarkdown(pin, thread) {
|
|
317
|
+
return `${block(pin, thread)}\n`;
|
|
318
|
+
}
|
|
289
319
|
//#endregion
|
|
290
320
|
//#region src/motion/spring.ts
|
|
291
321
|
/** Bar ⇄ puck morphs and the post-drag settle. */
|
|
@@ -952,7 +982,8 @@ function initialState() {
|
|
|
952
982
|
inboxOpen: false,
|
|
953
983
|
connection: "connecting",
|
|
954
984
|
queuedIds: /* @__PURE__ */ new Set(),
|
|
955
|
-
minimized: false
|
|
985
|
+
minimized: false,
|
|
986
|
+
pinsHidden: false
|
|
956
987
|
};
|
|
957
988
|
}
|
|
958
989
|
function createStore() {
|
|
@@ -979,7 +1010,8 @@ function createStore() {
|
|
|
979
1010
|
...state,
|
|
980
1011
|
draft,
|
|
981
1012
|
mode: "idle",
|
|
982
|
-
activePinId: null
|
|
1013
|
+
activePinId: null,
|
|
1014
|
+
pinsHidden: false
|
|
983
1015
|
});
|
|
984
1016
|
},
|
|
985
1017
|
discardDraft() {
|
|
@@ -1644,8 +1676,8 @@ function createAim(doc, handlers) {
|
|
|
1644
1676
|
//#region src/ui/bar.ts
|
|
1645
1677
|
const PIN_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"3\" y=\"1.5\" width=\"10\" height=\"6.5\" rx=\"1\"/><path d=\"M8 8v6.5\"/></svg>";
|
|
1646
1678
|
const INBOX_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M1.8 8.5h3.4l1 2h3.6l1-2h3.4\"/><path d=\"M2.6 3.2h10.8l1.2 5.3v4a1 1 0 01-1 1H2.4a1 1 0 01-1-1v-4z\"/></svg>";
|
|
1647
|
-
const THEME_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8
|
|
1648
|
-
const COPY_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1\"/><path d=\"M10.5 3.5v-1a1 1 0 00-1-1h-6a1 1 0 00-1 1v6a1 1 0 001 1h1\"/></svg>";
|
|
1679
|
+
const THEME_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8 2a4 4 0 0 0 6 6 6 6 0 1 1-6-6z\"/></svg>";
|
|
1680
|
+
const COPY_ICON$1 = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1\"/><path d=\"M10.5 3.5v-1a1 1 0 00-1-1h-6a1 1 0 00-1 1v6a1 1 0 001 1h1\"/></svg>";
|
|
1649
1681
|
const IDENT_ICON = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"var(--pb-amber)\" stroke-width=\"1.4\"><rect x=\"2.5\" y=\"1.5\" width=\"11\" height=\"7\" rx=\"1\"/><path d=\"M8 8.5v6\"/><circle cx=\"8\" cy=\"14.6\" r=\".9\" fill=\"var(--pb-amber)\" stroke=\"none\"/></svg>";
|
|
1650
1682
|
const MIN_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M6.5 2.5v4h-4\"/><path d=\"M9.5 13.5v-4h4\"/></svg>";
|
|
1651
1683
|
const CONNECTION_LABEL = {
|
|
@@ -1657,7 +1689,7 @@ const CONNECTION_LABEL = {
|
|
|
1657
1689
|
function createBar(doc, on) {
|
|
1658
1690
|
const root = doc.createElement("div");
|
|
1659
1691
|
root.className = "pb-bar";
|
|
1660
|
-
root.innerHTML = `<div class="armed-ring"></div><div class="ident">${IDENT_ICON}<span class="bl" data-ref="label">PINBOX</span></div><div class="div"></div><button type="button" class="pb-tb" data-ref="pin" title="Pin (P)">${PIN_ICON}PIN</button><button type="button" class="pb-tb" data-ref="inbox" title="Inbox (I)">${INBOX_ICON}<span data-ref="count">0</span></button><div class="div" style="margin:0 3px"></div><button type="button" class="pb-tb sq" data-ref="copy" title="Copy open pins (C)">${COPY_ICON}</button><button type="button" class="pb-tb sq" data-ref="theme" title="Theme (D)">${THEME_ICON}</button><button type="button" class="pb-tb sq" data-ref="help" title="Shortcuts (?)">?</button><button type="button" class="pb-tb sq" data-ref="min" title="Minimize (M)" aria-label="Minimize toolbar">${MIN_ICON}</button>`;
|
|
1692
|
+
root.innerHTML = `<div class="armed-ring"></div><div class="ident">${IDENT_ICON}<span class="bl" data-ref="label">PINBOX</span></div><div class="div"></div><button type="button" class="pb-tb" data-ref="pin" title="Pin (P)">${PIN_ICON}PIN</button><button type="button" class="pb-tb" data-ref="inbox" title="Inbox (I)">${INBOX_ICON}<span data-ref="count">0</span></button><div class="div" style="margin:0 3px"></div><button type="button" class="pb-tb sq" data-ref="copy" title="Copy open pins (C)">${COPY_ICON$1}</button><button type="button" class="pb-tb sq" data-ref="theme" title="Theme (D)">${THEME_ICON}</button><button type="button" class="pb-tb sq" data-ref="help" title="Shortcuts (?)">?</button><button type="button" class="pb-tb sq" data-ref="min" title="Minimize (M)" aria-label="Minimize toolbar">${MIN_ICON}</button>`;
|
|
1661
1693
|
const ref = (name) => root.querySelector(`[data-ref="${name}"]`);
|
|
1662
1694
|
const label = ref("label");
|
|
1663
1695
|
const pinBtn = ref("pin");
|
|
@@ -1711,6 +1743,144 @@ function pinNumber(n) {
|
|
|
1711
1743
|
return String(n).padStart(2, "0");
|
|
1712
1744
|
}
|
|
1713
1745
|
//#endregion
|
|
1746
|
+
//#region src/ui/pins.ts
|
|
1747
|
+
/** The prototype's `_h` innerHTML memo, kept off the DOM node. */
|
|
1748
|
+
const chipMemo = /* @__PURE__ */ new WeakMap();
|
|
1749
|
+
/** What the hub will number the next pin: max known `n`, else the pin count. */
|
|
1750
|
+
function nextOrdinal(pins) {
|
|
1751
|
+
return Math.max(pins.length, ...pins.map((p) => p.n ?? 0)) + 1;
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Does the pin's captured URL still describe the view on screen? Path + search
|
|
1755
|
+
* only — hashes are anchors, not views. An absent or unparseable URL never
|
|
1756
|
+
* gates: old pins (and CLI pins) keep rendering exactly as before.
|
|
1757
|
+
*/
|
|
1758
|
+
function sameView(win, url) {
|
|
1759
|
+
if (url === void 0) return true;
|
|
1760
|
+
try {
|
|
1761
|
+
const target = new URL(url, win.location.href);
|
|
1762
|
+
return target.pathname === win.location.pathname && target.search === win.location.search;
|
|
1763
|
+
} catch {
|
|
1764
|
+
return true;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Where the pin's anchor is NOW (dogfood #26: markers lingered over unrelated
|
|
1769
|
+
* views after SPA tab switches, because placement trusted the stored rect
|
|
1770
|
+
* forever). Re-resolve the captured selector on every render:
|
|
1771
|
+
* - it resolves with layout → snap to the LIVE rect (also fixes drift);
|
|
1772
|
+
* - it resolves without layout (test DOMs, display:none) → stored rect;
|
|
1773
|
+
* - it does not resolve → no marker; the drawer stays the see-everything list.
|
|
1774
|
+
* A pin with no selector (terminal-adjacent) keeps its stored rect, as before.
|
|
1775
|
+
*/
|
|
1776
|
+
function anchorRect(layer, pin) {
|
|
1777
|
+
const stored = pin.target?.rect;
|
|
1778
|
+
if (stored === void 0) return null;
|
|
1779
|
+
const doc = layer.ownerDocument;
|
|
1780
|
+
const win = doc.defaultView;
|
|
1781
|
+
if (win === null) return stored;
|
|
1782
|
+
if (!sameView(win, pin.target?.url)) return null;
|
|
1783
|
+
const selector = pin.target?.selector;
|
|
1784
|
+
if (selector === void 0) return stored;
|
|
1785
|
+
let el;
|
|
1786
|
+
try {
|
|
1787
|
+
el = doc.querySelector(selector);
|
|
1788
|
+
} catch {
|
|
1789
|
+
return stored;
|
|
1790
|
+
}
|
|
1791
|
+
if (el === null) return null;
|
|
1792
|
+
const r = el.getBoundingClientRect();
|
|
1793
|
+
if (r.width <= 0 && r.height <= 0) return stored;
|
|
1794
|
+
return {
|
|
1795
|
+
x: r.left + win.scrollX,
|
|
1796
|
+
y: r.top + win.scrollY,
|
|
1797
|
+
width: r.width,
|
|
1798
|
+
height: r.height
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
/**
|
|
1802
|
+
* Where the needle lands: the point inside the element that was actually clicked, when the pin
|
|
1803
|
+
* recorded one, else the centre of its box.
|
|
1804
|
+
*
|
|
1805
|
+
* `spot` is a fraction of the element, so the pin still tracks the element when it moves or
|
|
1806
|
+
* resizes — it just stops sliding to the middle of a wide block the moment you commit it.
|
|
1807
|
+
*/
|
|
1808
|
+
function pinPoint(r, spot) {
|
|
1809
|
+
const fx = spot?.x ?? .5;
|
|
1810
|
+
const fy = spot?.y ?? .5;
|
|
1811
|
+
return {
|
|
1812
|
+
x: r.x + r.width * fx,
|
|
1813
|
+
y: r.y + r.height * fy
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
/** Chip contents (prototype chipBtnInner, lines 546–550): number + linked-channel tag,
|
|
1817
|
+
* plus the queued badge while the pin waits in the outbox for the reconnect flush. */
|
|
1818
|
+
function chipInner(n, pin, queued = false) {
|
|
1819
|
+
const link = pin?.links?.[0];
|
|
1820
|
+
const badge = link ? `<span class="lk"><span>${esc(link.connector)}</span></span>` : "";
|
|
1821
|
+
const qd = queued ? "<span class=\"qd\">QUEUED</span>" : "";
|
|
1822
|
+
return `<span>${pinNumber(n)}</span>${badge}${qd}`;
|
|
1823
|
+
}
|
|
1824
|
+
function ensureNode(layer, key, fresh) {
|
|
1825
|
+
let node = layer.querySelector(`[data-pin="${key}"]`);
|
|
1826
|
+
if (!node) {
|
|
1827
|
+
node = layer.ownerDocument.createElement("div");
|
|
1828
|
+
node.className = "pb-pin";
|
|
1829
|
+
node.setAttribute("data-pin", key);
|
|
1830
|
+
node.innerHTML = `${fresh ? "<div class=\"ring\"></div>" : ""}<div class="dot"></div><div class="needle"></div><button type="button" class="pb-chipBtn" data-open="${esc(key)}"></button>`;
|
|
1831
|
+
layer.appendChild(node);
|
|
1832
|
+
}
|
|
1833
|
+
return node;
|
|
1834
|
+
}
|
|
1835
|
+
function patchNode(node, at, hot, inner) {
|
|
1836
|
+
node.style.left = `${at.x}px`;
|
|
1837
|
+
node.style.top = `${at.y}px`;
|
|
1838
|
+
node.style.zIndex = hot ? "40" : "20";
|
|
1839
|
+
node.classList.toggle("hot", hot);
|
|
1840
|
+
const chip = node.querySelector(".pb-chipBtn");
|
|
1841
|
+
if (chip && chipMemo.get(chip) !== inner) {
|
|
1842
|
+
chip.innerHTML = inner;
|
|
1843
|
+
chipMemo.set(chip, inner);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Render the pin layer for a state snapshot. Visible pins are open pins, the
|
|
1848
|
+
* active pin regardless of status, and the client-only draft (key "draft").
|
|
1849
|
+
*/
|
|
1850
|
+
function renderPins(layer, state) {
|
|
1851
|
+
layer.hidden = state.pinsHidden;
|
|
1852
|
+
if (state.pinsHidden) return;
|
|
1853
|
+
const visible = state.pins.filter((p) => p.status !== "resolved" || p.id === state.activePinId);
|
|
1854
|
+
const placed = [];
|
|
1855
|
+
visible.forEach((pin, i) => {
|
|
1856
|
+
const rect = anchorRect(layer, pin);
|
|
1857
|
+
if (rect === null) return;
|
|
1858
|
+
const spot = pin.target?.spot;
|
|
1859
|
+
const n = pin.n ?? i + 1;
|
|
1860
|
+
placed.push(spot === void 0 ? {
|
|
1861
|
+
pin,
|
|
1862
|
+
n,
|
|
1863
|
+
rect
|
|
1864
|
+
} : {
|
|
1865
|
+
pin,
|
|
1866
|
+
n,
|
|
1867
|
+
rect,
|
|
1868
|
+
spot
|
|
1869
|
+
});
|
|
1870
|
+
});
|
|
1871
|
+
const keys = new Set(placed.map((entry) => entry.pin.id));
|
|
1872
|
+
if (state.draft) keys.add("draft");
|
|
1873
|
+
for (const node of [...layer.children]) if (!keys.has(node.getAttribute("data-pin") ?? "")) node.remove();
|
|
1874
|
+
for (const { pin, n, rect, spot } of placed) {
|
|
1875
|
+
const node = ensureNode(layer, pin.id, false);
|
|
1876
|
+
const hot = pin.id === state.activePinId;
|
|
1877
|
+
const queued = state.queuedIds.has(pin.id);
|
|
1878
|
+
node.classList.toggle("queued", queued);
|
|
1879
|
+
patchNode(node, pinPoint(rect, spot), hot, chipInner(n, pin, queued));
|
|
1880
|
+
}
|
|
1881
|
+
if (state.draft) patchNode(ensureNode(layer, "draft", true), state.draft.placedAt, true, chipInner(nextOrdinal(state.pins), null));
|
|
1882
|
+
}
|
|
1883
|
+
//#endregion
|
|
1714
1884
|
//#region src/ui/card.ts
|
|
1715
1885
|
const STATUS_LABEL = {
|
|
1716
1886
|
open: "OPEN",
|
|
@@ -1721,6 +1891,7 @@ const STATUS_LABEL = {
|
|
|
1721
1891
|
};
|
|
1722
1892
|
const CHECK_ICON = "<svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M3 8.5l3.2 3.2L13 4.8\"/></svg>";
|
|
1723
1893
|
const X_ICON$1 = "<svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M4 4l8 8M12 4l-8 8\"/></svg>";
|
|
1894
|
+
const COPY_ICON = "<svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1\"/><path d=\"M10.5 3.5v-1a1 1 0 00-1-1h-6a1 1 0 00-1 1v6a1 1 0 001 1h1\"/></svg>";
|
|
1724
1895
|
const ctxByCard = /* @__PURE__ */ new WeakMap();
|
|
1725
1896
|
/** The prototype's `_h` innerHTML memo, kept off the DOM node. */
|
|
1726
1897
|
const nodeMemo = /* @__PURE__ */ new WeakMap();
|
|
@@ -1742,8 +1913,15 @@ function attachmentsHtml(m) {
|
|
|
1742
1913
|
if (!m.attachments?.length) return "";
|
|
1743
1914
|
return `<div class="atts">${m.attachments.map((att) => isImage(att) ? `<span class="pb-att"><img src="${esc(safeUrl(att.url ?? att.path ?? ""))}" alt="${esc(fileName(att))}" loading="lazy"></span>` : `<span class="pb-att-chip">${esc(fileName(att))}</span>`).join("")}</div>`;
|
|
1744
1915
|
}
|
|
1916
|
+
/** "claude:lark-mac-agent" → "Claude · lark-mac-agent"; other shapes verbatim. */
|
|
1917
|
+
function agentName(origin) {
|
|
1918
|
+
const idx = origin.indexOf(":");
|
|
1919
|
+
if (idx <= 0) return origin;
|
|
1920
|
+
const agent = origin.slice(0, idx);
|
|
1921
|
+
return `${agent.charAt(0).toUpperCase()}${agent.slice(1)} · ${origin.slice(idx + 1)}`;
|
|
1922
|
+
}
|
|
1745
1923
|
function messageHtml(m) {
|
|
1746
|
-
if (m.role === "agent") return `<div class="pb-msg"><div class="pb-av agent">AI</div><div class="col"><div class="line"><span class="who"
|
|
1924
|
+
if (m.role === "agent") return `<div class="pb-msg"><div class="pb-av agent">AI</div><div class="col"><div class="line"><span class="who">${esc(m.origin === void 0 ? "Agent" : agentName(m.origin))}</span><span class="tm">${esc(timeOf(m.at))}</span></div><div class="txt">${esc(m.text)}</div>${attachmentsHtml(m)}</div></div>`;
|
|
1747
1925
|
const mirror = m.role === "mirror";
|
|
1748
1926
|
const origin = mirror ? m.origin ?? "mirror" : null;
|
|
1749
1927
|
const who = origin ? origin.split(":")[1] ?? origin : "You";
|
|
@@ -1830,7 +2008,14 @@ function onCardClick(card, ctx, e) {
|
|
|
1830
2008
|
else if (action === "close") ctx.actions.close();
|
|
1831
2009
|
else if (ctx.pid !== "draft") {
|
|
1832
2010
|
if (action === "resolve") ctx.actions.resolve(ctx.pid);
|
|
1833
|
-
else if (action === "
|
|
2011
|
+
else if (action === "copy") {
|
|
2012
|
+
ctx.actions.copy(ctx.pid);
|
|
2013
|
+
const btn = e.target.closest?.("[data-action=\"copy\"]");
|
|
2014
|
+
if (btn) {
|
|
2015
|
+
btn.classList.add("ok");
|
|
2016
|
+
card.ownerDocument.defaultView?.setTimeout(() => btn.classList.remove("ok"), 900);
|
|
2017
|
+
}
|
|
2018
|
+
} else if (action === "verify-accept") ctx.actions.verify(ctx.pid, "accepted");
|
|
1834
2019
|
else if (action === "verify-reopen") {
|
|
1835
2020
|
ctx.actions.verify(ctx.pid, "reopened");
|
|
1836
2021
|
card.querySelector("textarea")?.focus();
|
|
@@ -1838,7 +2023,7 @@ function onCardClick(card, ctx, e) {
|
|
|
1838
2023
|
}
|
|
1839
2024
|
}
|
|
1840
2025
|
function buildSkeleton(card, ctx, isDraft, hasThread) {
|
|
1841
|
-
card.innerHTML = "<div class=\"in\"><div class=\"pb-hd\" data-ref=\"hd\"></div><div data-ref=\"link\"></div><div class=\"pb-thread\" data-ref=\"thread\"></div><div data-ref=\"verify\"></div><div class=\"pb-composer\"><textarea rows=\"2\"></textarea><div class=\"row\" data-ref=\"row\"></div></div></div>";
|
|
2026
|
+
card.innerHTML = "<div class=\"in\"><div class=\"pb-hd\" data-ref=\"hd\"></div><div data-ref=\"link\"></div><div data-ref=\"loci\"></div><div class=\"pb-thread\" data-ref=\"thread\"></div><div data-ref=\"verify\"></div><div class=\"pb-composer\"><textarea rows=\"2\"></textarea><div class=\"row\" data-ref=\"row\"></div></div></div>";
|
|
1842
2027
|
const ta = card.querySelector("textarea");
|
|
1843
2028
|
ta.placeholder = hasThread ? "Ask a question or request a change…" : "What should change here?";
|
|
1844
2029
|
ta.addEventListener("keydown", (e) => {
|
|
@@ -1854,8 +2039,20 @@ function buildSkeleton(card, ctx, isDraft, hasThread) {
|
|
|
1854
2039
|
}, true);
|
|
1855
2040
|
if (isDraft) ta.focus();
|
|
1856
2041
|
}
|
|
1857
|
-
function hdHtml(n, targetLabel, status, resolvable) {
|
|
1858
|
-
return `<div class="meta"><span class="num">${pinNumber(n)}</span><span>${esc(targetLabel)}</span><span class="st">${esc(status)}</span></div><div style="display:flex;gap:2px">` + (resolvable ? `<button type="button" class="pb-ico ok" data-action="resolve" title="Resolve (R)">${CHECK_ICON}</button>` : "") + `<button type="button" class="pb-ico" data-action="close" title="Close (Esc)">${X_ICON$1}</button></div>`;
|
|
2042
|
+
function hdHtml(n, targetLabel, status, resolvable, copyable) {
|
|
2043
|
+
return `<div class="meta"><span class="num">${pinNumber(n)}</span><span>${esc(targetLabel)}</span><span class="st">${esc(status)}</span></div><div style="display:flex;gap:2px">` + (copyable ? `<button type="button" class="pb-ico" data-action="copy" title="Copy this pin">${COPY_ICON}</button>` : "") + (resolvable ? `<button type="button" class="pb-ico ok" data-action="resolve" title="Resolve (R)">${CHECK_ICON}</button>` : "") + `<button type="button" class="pb-ico" data-action="close" title="Close (Esc)">${X_ICON$1}</button></div>`;
|
|
2044
|
+
}
|
|
2045
|
+
/** One name per locus: selector first, else anchor, else tag. */
|
|
2046
|
+
function locusName(t) {
|
|
2047
|
+
return t.selector ?? t.anchor ?? t.tag?.toUpperCase();
|
|
2048
|
+
}
|
|
2049
|
+
/** The extra loci of a multi-target pin — the anchor leads, extras follow. */
|
|
2050
|
+
function lociHtml(pin) {
|
|
2051
|
+
const target = pin?.target;
|
|
2052
|
+
const extras = target?.targets;
|
|
2053
|
+
if (target === void 0 || extras === void 0 || extras.length === 0) return "";
|
|
2054
|
+
const names = [target, ...extras].map((t) => esc(locusName(t) ?? "?"));
|
|
2055
|
+
return `<div class="pb-loci">${names.length} targets: ${names.join(" · ")}</div>`;
|
|
1859
2056
|
}
|
|
1860
2057
|
/** Link badge: pin.links[0] read-only — no picker, no unlink yet. */
|
|
1861
2058
|
function linkHtml(pin) {
|
|
@@ -1864,8 +2061,9 @@ function linkHtml(pin) {
|
|
|
1864
2061
|
return `<div class="pb-linkbar"><span class="ch">${esc(link.connector)}</span><span class="mt">${esc(link.ref)}</span><span class="sp"></span><a class="pb-open" href="${esc(safeUrl(link.url))}" target="_blank" rel="noreferrer">OPEN</a></div>`;
|
|
1865
2062
|
}
|
|
1866
2063
|
function verifyHtml(status) {
|
|
1867
|
-
if (status
|
|
1868
|
-
|
|
2064
|
+
if (status === "verify") return "<div class=\"pb-verify\"><button type=\"button\" class=\"pb-bt-ok\" data-action=\"verify-accept\">Looks good</button><button type=\"button\" class=\"pb-bt-ghost\" data-action=\"verify-reopen\">Reopen</button></div>";
|
|
2065
|
+
if (status === "resolved") return "<div class=\"pb-verify\"><button type=\"button\" class=\"pb-bt-ghost\" data-action=\"verify-reopen\">Unresolve</button></div>";
|
|
2066
|
+
return "";
|
|
1869
2067
|
}
|
|
1870
2068
|
function rowHtml(hasThread) {
|
|
1871
2069
|
return `<div class="pb-kbd">⌘ ↵</div><button type="button" class="pb-bt-solid" data-action="send">${hasThread ? "Reply" : "Comment"}</button>`;
|
|
@@ -1902,10 +2100,11 @@ function activePin(state) {
|
|
|
1902
2100
|
if (!state.activePinId) return null;
|
|
1903
2101
|
return state.pins.find((p) => p.id === state.activePinId) ?? null;
|
|
1904
2102
|
}
|
|
1905
|
-
/**
|
|
2103
|
+
/** The pin's hub-born number; visible-index only for pre-`n` pins, drafts next up. */
|
|
1906
2104
|
function ordinalOf(state, pin) {
|
|
1907
|
-
|
|
1908
|
-
|
|
2105
|
+
if (pin === null) return nextOrdinal(state.pins);
|
|
2106
|
+
if (pin.n !== void 0) return pin.n;
|
|
2107
|
+
return state.pins.filter((p) => p.status !== "resolved" || p.id === state.activePinId).indexOf(pin) + 1;
|
|
1909
2108
|
}
|
|
1910
2109
|
function anchorOf(pin, draft) {
|
|
1911
2110
|
const r = pin?.target?.rect;
|
|
@@ -1976,8 +2175,9 @@ function renderCard(root, state, actions) {
|
|
|
1976
2175
|
const queued = view.pin !== null && state.queuedIds.has(view.pin.id);
|
|
1977
2176
|
const statusLabel = queued ? "QUEUED" : view.status ? STATUS_LABEL[view.status] : "NEW";
|
|
1978
2177
|
const resolvable = view.pin?.status === "open" && !queued;
|
|
1979
|
-
setPart(card, ctx, "hd", hdHtml(view.n, view.label, statusLabel, resolvable));
|
|
2178
|
+
setPart(card, ctx, "hd", hdHtml(view.n, view.label, statusLabel, resolvable, view.pin !== null));
|
|
1980
2179
|
setPart(card, ctx, "link", linkHtml(view.pin));
|
|
2180
|
+
setPart(card, ctx, "loci", lociHtml(view.pin));
|
|
1981
2181
|
setPart(card, ctx, "verify", verifyHtml(view.status));
|
|
1982
2182
|
const messages = view.pin === null ? view.thread : [pinAsMessage(view.pin), ...view.thread];
|
|
1983
2183
|
setPart(card, ctx, "row", rowHtml(messages.length > 0));
|
|
@@ -2049,7 +2249,7 @@ function createDrawer(doc, on) {
|
|
|
2049
2249
|
doneTab.classList.toggle("on", tab === "resolved");
|
|
2050
2250
|
}
|
|
2051
2251
|
const list = tab === "open" ? open : resolved;
|
|
2052
|
-
const html = list.length ? list.map((p) => itemHtml(p, state.pins.indexOf(p) + 1, p.id === state.activePinId, state.threads.get(p.id) ?? [], state.queuedIds.has(p.id))).join("") : "<div class=\"pb-empty\">Nothing here yet.</div>";
|
|
2252
|
+
const html = list.length ? list.map((p) => itemHtml(p, p.n ?? state.pins.indexOf(p) + 1, p.id === state.activePinId, state.threads.get(p.id) ?? [], state.queuedIds.has(p.id))).join("") : "<div class=\"pb-empty\">Nothing here yet.</div>";
|
|
2053
2253
|
if (itemsMemo !== html) {
|
|
2054
2254
|
items.innerHTML = html;
|
|
2055
2255
|
itemsMemo = html;
|
|
@@ -2082,87 +2282,23 @@ function createDrawer(doc, on) {
|
|
|
2082
2282
|
};
|
|
2083
2283
|
}
|
|
2084
2284
|
//#endregion
|
|
2085
|
-
//#region src/ui/
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
* `spot` is a fraction of the element, so the pin still tracks the element when it moves or
|
|
2093
|
-
* resizes — it just stops sliding to the middle of a wide block the moment you commit it.
|
|
2094
|
-
*/
|
|
2095
|
-
function pinPoint(r, spot) {
|
|
2096
|
-
const fx = spot?.x ?? .5;
|
|
2097
|
-
const fy = spot?.y ?? .5;
|
|
2098
|
-
return {
|
|
2099
|
-
x: r.x + r.width * fx,
|
|
2100
|
-
y: r.y + r.height * fy
|
|
2101
|
-
};
|
|
2102
|
-
}
|
|
2103
|
-
/** Chip contents (prototype chipBtnInner, lines 546–550): number + linked-channel tag,
|
|
2104
|
-
* plus the queued badge while the pin waits in the outbox for the reconnect flush. */
|
|
2105
|
-
function chipInner(n, pin, queued = false) {
|
|
2106
|
-
const link = pin?.links?.[0];
|
|
2107
|
-
const badge = link ? `<span class="lk"><span>${esc(link.connector)}</span></span>` : "";
|
|
2108
|
-
const qd = queued ? "<span class=\"qd\">QUEUED</span>" : "";
|
|
2109
|
-
return `<span>${pinNumber(n)}</span>${badge}${qd}`;
|
|
2110
|
-
}
|
|
2111
|
-
function ensureNode(layer, key, fresh) {
|
|
2112
|
-
let node = layer.querySelector(`[data-pin="${key}"]`);
|
|
2113
|
-
if (!node) {
|
|
2114
|
-
node = layer.ownerDocument.createElement("div");
|
|
2115
|
-
node.className = "pb-pin";
|
|
2116
|
-
node.setAttribute("data-pin", key);
|
|
2117
|
-
node.innerHTML = `${fresh ? "<div class=\"ring\"></div>" : ""}<div class="dot"></div><div class="needle"></div><button type="button" class="pb-chipBtn" data-open="${esc(key)}"></button>`;
|
|
2118
|
-
layer.appendChild(node);
|
|
2119
|
-
}
|
|
2120
|
-
return node;
|
|
2121
|
-
}
|
|
2122
|
-
function patchNode(node, at, hot, inner) {
|
|
2123
|
-
node.style.left = `${at.x}px`;
|
|
2124
|
-
node.style.top = `${at.y}px`;
|
|
2125
|
-
node.style.zIndex = hot ? "40" : "20";
|
|
2126
|
-
node.classList.toggle("hot", hot);
|
|
2127
|
-
const chip = node.querySelector(".pb-chipBtn");
|
|
2128
|
-
if (chip && chipMemo.get(chip) !== inner) {
|
|
2129
|
-
chip.innerHTML = inner;
|
|
2130
|
-
chipMemo.set(chip, inner);
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
/**
|
|
2134
|
-
* Render the pin layer for a state snapshot. Visible pins are open pins, the
|
|
2135
|
-
* active pin regardless of status, and the client-only draft (key "draft").
|
|
2136
|
-
*/
|
|
2137
|
-
function renderPins(layer, state) {
|
|
2138
|
-
const visible = state.pins.filter((p) => p.status !== "resolved" || p.id === state.activePinId);
|
|
2139
|
-
const placed = [];
|
|
2140
|
-
visible.forEach((pin, i) => {
|
|
2141
|
-
const rect = pin.target?.rect;
|
|
2285
|
+
//#region src/ui/multimarks.ts
|
|
2286
|
+
const MARK_CLASS = "pb-multi-mark";
|
|
2287
|
+
/** Replace the mark set to mirror `targets`; entries with no rect draw nothing. */
|
|
2288
|
+
function renderMultiMarks(layer, targets) {
|
|
2289
|
+
for (const node of [...layer.querySelectorAll(`.${MARK_CLASS}`)]) node.remove();
|
|
2290
|
+
targets.forEach((target, i) => {
|
|
2291
|
+
const rect = target.rect;
|
|
2142
2292
|
if (rect === void 0) return;
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
rect,
|
|
2152
|
-
spot
|
|
2153
|
-
});
|
|
2293
|
+
const mark = layer.ownerDocument.createElement("div");
|
|
2294
|
+
mark.className = MARK_CLASS;
|
|
2295
|
+
mark.style.left = `${rect.x}px`;
|
|
2296
|
+
mark.style.top = `${rect.y}px`;
|
|
2297
|
+
mark.style.width = `${rect.width}px`;
|
|
2298
|
+
mark.style.height = `${rect.height}px`;
|
|
2299
|
+
mark.innerHTML = `<span>${i + 1}</span>`;
|
|
2300
|
+
layer.appendChild(mark);
|
|
2154
2301
|
});
|
|
2155
|
-
const keys = new Set(placed.map((entry) => entry.pin.id));
|
|
2156
|
-
if (state.draft) keys.add("draft");
|
|
2157
|
-
for (const node of [...layer.children]) if (!keys.has(node.getAttribute("data-pin") ?? "")) node.remove();
|
|
2158
|
-
for (const { pin, n, rect, spot } of placed) {
|
|
2159
|
-
const node = ensureNode(layer, pin.id, false);
|
|
2160
|
-
const hot = pin.id === state.activePinId;
|
|
2161
|
-
const queued = state.queuedIds.has(pin.id);
|
|
2162
|
-
node.classList.toggle("queued", queued);
|
|
2163
|
-
patchNode(node, pinPoint(rect, spot), hot, chipInner(n, pin, queued));
|
|
2164
|
-
}
|
|
2165
|
-
if (state.draft) patchNode(ensureNode(layer, "draft", true), state.draft.placedAt, true, chipInner(visible.length + 1, null));
|
|
2166
2302
|
}
|
|
2167
2303
|
//#endregion
|
|
2168
2304
|
//#region src/ui/puck.ts
|
|
@@ -2170,7 +2306,9 @@ function renderPins(layer, state) {
|
|
|
2170
2306
|
const PUCK_ICON = "<svg width=\"17\" height=\"17\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"2.5\" y=\"1.5\" width=\"11\" height=\"7\" rx=\"1\"/><path d=\"M8 8.5v6\"/><circle cx=\"8\" cy=\"14.6\" r=\".9\" fill=\"currentColor\" stroke=\"none\"/></svg>";
|
|
2171
2307
|
const FAN_PIN = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"3\" y=\"1.5\" width=\"10\" height=\"6.5\" rx=\"1\"/><path d=\"M8 8v6.5\"/></svg>";
|
|
2172
2308
|
const FAN_INBOX = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M1.8 8.5h3.4l1 2h3.6l1-2h3.4\"/><path d=\"M2.6 3.2h10.8l1.2 5.3v4a1 1 0 01-1 1H2.4a1 1 0 01-1-1v-4z\"/></svg>";
|
|
2173
|
-
const FAN_THEME = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8
|
|
2309
|
+
const FAN_THEME = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8 2a4 4 0 0 0 6 6 6 6 0 1 1-6-6z\"/></svg>";
|
|
2310
|
+
const FAN_EYE = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M1.6 8S4 3.8 8 3.8 14.4 8 14.4 8 12 12.2 8 12.2 1.6 8 1.6 8z\"/><circle cx=\"8\" cy=\"8\" r=\"1.8\"/></svg>";
|
|
2311
|
+
const FAN_EYE_OFF = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M2.3 2.3l11.4 11.4\"/><path d=\"M4.9 4.9C2.7 6.2 1.6 8 1.6 8s2.4 4.2 6.4 4.2c1.2 0 2.3-.3 3.1-.8M6.7 4c.4-.1.9-.2 1.3-.2 4 0 6.4 4.2 6.4 4.2s-.8 1.4-2.2 2.5\"/></svg>";
|
|
2174
2312
|
const FAN_EXPAND = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M9.5 6.5v-4h4\"/><path d=\"M6.5 9.5v4h-4\"/></svg>";
|
|
2175
2313
|
function fanItem(act, index, icon, label, key) {
|
|
2176
2314
|
return `<button type="button" class="pb-fan-item" data-act="${act}" style="--i:${index}" aria-label="${label}">${icon}${act === "inbox" ? "<span class=\"badge\" data-ref=\"count\" hidden>0</span>" : ""}<span class="fl">${label.toUpperCase()}<i>${key}</i></span></button>`;
|
|
@@ -2186,7 +2324,7 @@ function createMinimizeUi(doc) {
|
|
|
2186
2324
|
fan.className = "pb-fan";
|
|
2187
2325
|
fan.hidden = true;
|
|
2188
2326
|
fan.setAttribute("role", "menu");
|
|
2189
|
-
fan.innerHTML = fanItem("pin", 0, FAN_PIN, "Drop a pin", "P") + fanItem("inbox", 1, FAN_INBOX, "Inbox", "I") + fanItem("theme", 2, FAN_THEME, "Theme", "D") + fanItem("
|
|
2327
|
+
fan.innerHTML = fanItem("pin", 0, FAN_PIN, "Drop a pin", "P") + fanItem("inbox", 1, FAN_INBOX, "Inbox", "I") + fanItem("theme", 2, FAN_THEME, "Theme", "D") + fanItem("hide", 3, FAN_EYE_OFF, "Hide pins", "H") + fanItem("expand", 4, FAN_EXPAND, "Expand", "M");
|
|
2190
2328
|
const morphWrap = doc.createElement("div");
|
|
2191
2329
|
morphWrap.className = "pb-morph-wrap";
|
|
2192
2330
|
morphWrap.hidden = true;
|
|
@@ -2202,6 +2340,9 @@ function createMinimizeUi(doc) {
|
|
|
2202
2340
|
carrier.querySelector("[data-ref=\"count\"]"),
|
|
2203
2341
|
fan.querySelector("[data-ref=\"count\"]")
|
|
2204
2342
|
];
|
|
2343
|
+
const hideItem = fan.querySelector("[data-act=\"hide\"]");
|
|
2344
|
+
/** Last-rendered hide state; the item's markup is swapped only on change. */
|
|
2345
|
+
let hideShown = null;
|
|
2205
2346
|
return {
|
|
2206
2347
|
puck,
|
|
2207
2348
|
fan,
|
|
@@ -2217,6 +2358,13 @@ function createMinimizeUi(doc) {
|
|
|
2217
2358
|
const degraded = state.connection === "offline" || state.connection === "incompatible";
|
|
2218
2359
|
puck.classList.toggle("degraded", degraded);
|
|
2219
2360
|
puck.classList.toggle("armed", state.mode === "placing");
|
|
2361
|
+
if (hideShown !== state.pinsHidden) {
|
|
2362
|
+
hideShown = state.pinsHidden;
|
|
2363
|
+
const label = state.pinsHidden ? "Show pins" : "Hide pins";
|
|
2364
|
+
hideItem.innerHTML = `${state.pinsHidden ? FAN_EYE : FAN_EYE_OFF}<span class="fl">${label.toUpperCase()}<i>H</i></span>`;
|
|
2365
|
+
hideItem.setAttribute("aria-label", label);
|
|
2366
|
+
hideItem.classList.toggle("lit", state.pinsHidden);
|
|
2367
|
+
}
|
|
2220
2368
|
}
|
|
2221
2369
|
};
|
|
2222
2370
|
}
|
|
@@ -2276,6 +2424,7 @@ const ROWS = [
|
|
|
2276
2424
|
["Send comment", "⌘ ↵"],
|
|
2277
2425
|
["Mark pin resolved", "R"],
|
|
2278
2426
|
["Copy open pins", "C"],
|
|
2427
|
+
["Hide / show pins", "H"],
|
|
2279
2428
|
["Minimize toolbar", "M"],
|
|
2280
2429
|
["Cancel", "ESC"]
|
|
2281
2430
|
];
|
|
@@ -2519,6 +2668,10 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2519
2668
|
.pb-fan.down { --fan-from: translateY(-16px); }
|
|
2520
2669
|
.pb-fan.on .pb-fan-item { opacity: 1; transform: none; }
|
|
2521
2670
|
.pb-fan-item:hover { color: var(--pb-amber); border-color: var(--pb-amber); }
|
|
2671
|
+
.pb-fan-item.lit { color: var(--pb-amber); border-color: var(--pb-amber); }
|
|
2672
|
+
.pb-multi-mark { position: absolute; border: 1.5px dashed var(--pb-amber); border-radius: 4px; pointer-events: none; z-index: 15; }
|
|
2673
|
+
.pb-multi-mark span { position: absolute; top: -9px; left: -9px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--pb-amber); color: var(--pb-amber-ink); font-family: var(--pb-font-mono); font-size: 9px; font-weight: 500; display: flex; align-items: center; justify-content: center; }
|
|
2674
|
+
.pb-loci { padding: 6px 14px 0; font-family: var(--pb-font-mono); font-size: 9.5px; letter-spacing: .04em; color: var(--pb-fg3); overflow-wrap: anywhere; }
|
|
2522
2675
|
.pb-fan-item .badge { pointer-events: none; }
|
|
2523
2676
|
.pb-fan-item .fl { position: absolute; top: 50%; display: flex; align-items: center; gap: 6px; padding: 4px 8px; background: var(--pb-elev); border: 1px solid var(--pb-line-2); border-radius: 2px; box-shadow: var(--pb-shadow); font-family: var(--pb-font-mono); font-size: 9px; letter-spacing: .14em; color: var(--pb-fg1); white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 140ms linear, transform 200ms var(--pb-ease); }
|
|
2524
2677
|
.pb-fan.labels-right .fl { left: calc(100% + 10px); transform: translateY(-50%) translateX(-6px); }
|
|
@@ -2583,15 +2736,20 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2583
2736
|
#modal = null;
|
|
2584
2737
|
#minUi = null;
|
|
2585
2738
|
#min = null;
|
|
2739
|
+
/** SPA view watcher: DOM/history changes re-run the anchor-gated render. */
|
|
2740
|
+
#anchors = null;
|
|
2586
2741
|
#helpOpen = false;
|
|
2587
2742
|
#pageStyle = null;
|
|
2588
2743
|
#unsubscribe = null;
|
|
2589
2744
|
#hover = null;
|
|
2745
|
+
/** Shift+click accumulation while placing — extra loci for ONE pending pin. */
|
|
2746
|
+
#extraTargets = [];
|
|
2590
2747
|
/** Card → element: send/verify/resolve forward to the transport seam; close dismisses. */
|
|
2591
2748
|
#cardActions = {
|
|
2592
2749
|
send: (pinId, text) => this.actions.send?.(pinId, text),
|
|
2593
2750
|
verify: (pinId, outcome) => this.actions.verify?.(pinId, outcome),
|
|
2594
2751
|
resolve: (pinId) => this.actions.resolve?.(pinId),
|
|
2752
|
+
copy: (pinId) => this.#copyPin(pinId),
|
|
2595
2753
|
close: () => this.#dismiss()
|
|
2596
2754
|
};
|
|
2597
2755
|
/** Programmatic path (Pinbox.init). The snippet path reads hub/token attributes. */
|
|
@@ -2624,6 +2782,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2624
2782
|
this.#pageStyle = style;
|
|
2625
2783
|
if (this.#aim === null) this.#mountAim();
|
|
2626
2784
|
if (this.#min === null) this.#mountMinimize();
|
|
2785
|
+
this.#anchors = watchAnchors(window, () => this.#render(this.store.get()));
|
|
2627
2786
|
document.addEventListener("mousemove", this.#onMouseMove);
|
|
2628
2787
|
window.addEventListener("scroll", this.#onViewportChange, { passive: true });
|
|
2629
2788
|
window.addEventListener("resize", this.#onViewportChange);
|
|
@@ -2672,6 +2831,8 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2672
2831
|
this.#min?.destroy();
|
|
2673
2832
|
this.#min = null;
|
|
2674
2833
|
releaseCapture();
|
|
2834
|
+
this.#anchors?.destroy();
|
|
2835
|
+
this.#anchors = null;
|
|
2675
2836
|
this.#unsubscribe?.();
|
|
2676
2837
|
this.#unsubscribe = null;
|
|
2677
2838
|
this.#pageStyle?.remove();
|
|
@@ -2761,6 +2922,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2761
2922
|
onFanAction: (action) => {
|
|
2762
2923
|
if (action === "pin") this.#togglePlacing();
|
|
2763
2924
|
else if (action === "inbox") this.#toggleInbox();
|
|
2925
|
+
else if (action === "hide") this.#togglePinsHidden();
|
|
2764
2926
|
else this.#toggleTheme();
|
|
2765
2927
|
}
|
|
2766
2928
|
});
|
|
@@ -2888,6 +3050,18 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2888
3050
|
navigator.clipboard.writeText(pinsToMarkdown(state.pins, state.threads));
|
|
2889
3051
|
} catch {}
|
|
2890
3052
|
}
|
|
3053
|
+
/** The card's copy: exactly the pin you are looking at, thread included. */
|
|
3054
|
+
#copyPin(pinId) {
|
|
3055
|
+
const state = this.store.get();
|
|
3056
|
+
const pin = state.pins.find((p) => p.id === pinId);
|
|
3057
|
+
if (pin === void 0) return;
|
|
3058
|
+
try {
|
|
3059
|
+
navigator.clipboard.writeText(pinToMarkdown(pin, state.threads.get(pinId) ?? []));
|
|
3060
|
+
} catch {}
|
|
3061
|
+
}
|
|
3062
|
+
#togglePinsHidden() {
|
|
3063
|
+
this.store.update({ pinsHidden: !this.store.get().pinsHidden });
|
|
3064
|
+
}
|
|
2891
3065
|
#setHelp(open) {
|
|
2892
3066
|
this.#helpOpen = open;
|
|
2893
3067
|
this.#modal?.set(open);
|
|
@@ -2905,9 +3079,13 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2905
3079
|
}
|
|
2906
3080
|
#togglePlacing() {
|
|
2907
3081
|
const placing = this.store.get().mode === "placing";
|
|
2908
|
-
this.store.update({
|
|
2909
|
-
mode:
|
|
3082
|
+
this.store.update(placing ? {
|
|
3083
|
+
mode: "idle",
|
|
2910
3084
|
activePinId: null
|
|
3085
|
+
} : {
|
|
3086
|
+
mode: "placing",
|
|
3087
|
+
activePinId: null,
|
|
3088
|
+
pinsHidden: false
|
|
2911
3089
|
});
|
|
2912
3090
|
}
|
|
2913
3091
|
#toggleInbox() {
|
|
@@ -2924,6 +3102,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2924
3102
|
/** esc / click-away: leave placing, discard the draft (client-only), deactivate. */
|
|
2925
3103
|
#dismiss() {
|
|
2926
3104
|
this.#setHelp(false);
|
|
3105
|
+
this.#clearExtraTargets();
|
|
2927
3106
|
this.store.update({
|
|
2928
3107
|
mode: "idle",
|
|
2929
3108
|
activePinId: null
|
|
@@ -2989,12 +3168,14 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2989
3168
|
#placeDraft(e) {
|
|
2990
3169
|
e.preventDefault();
|
|
2991
3170
|
e.stopPropagation();
|
|
2992
|
-
const
|
|
3171
|
+
const capture = captureTarget(this.#hover ?? document.body, { at: {
|
|
3172
|
+
x: e.pageX,
|
|
3173
|
+
y: e.pageY
|
|
3174
|
+
} });
|
|
3175
|
+
if (this.#extraTargets.length > 0) capture.target.targets = this.#extraTargets;
|
|
3176
|
+
this.#clearExtraTargets();
|
|
2993
3177
|
this.store.place({
|
|
2994
|
-
target:
|
|
2995
|
-
x: e.pageX,
|
|
2996
|
-
y: e.pageY
|
|
2997
|
-
} }),
|
|
3178
|
+
target: capture,
|
|
2998
3179
|
placedAt: {
|
|
2999
3180
|
x: e.pageX,
|
|
3000
3181
|
y: e.pageY
|
|
@@ -3002,11 +3183,27 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
3002
3183
|
});
|
|
3003
3184
|
this.#reticle?.release();
|
|
3004
3185
|
}
|
|
3186
|
+
/** Shift+click while placing: capture WITHOUT committing; a numbered dashed
|
|
3187
|
+
* outline is the receipt. Plain click still commits (with these attached). */
|
|
3188
|
+
#accumulateTarget(e) {
|
|
3189
|
+
e.preventDefault();
|
|
3190
|
+
e.stopPropagation();
|
|
3191
|
+
const el = this.#hover ?? document.body;
|
|
3192
|
+
this.#extraTargets = [...this.#extraTargets, captureTarget(el).target];
|
|
3193
|
+
if (this.#pinsLayer) renderMultiMarks(this.#pinsLayer, this.#extraTargets);
|
|
3194
|
+
}
|
|
3195
|
+
#clearExtraTargets() {
|
|
3196
|
+
if (this.#extraTargets.length === 0) return;
|
|
3197
|
+
this.#extraTargets = [];
|
|
3198
|
+
if (this.#pinsLayer) renderMultiMarks(this.#pinsLayer, []);
|
|
3199
|
+
}
|
|
3005
3200
|
#onClickCapture = (e) => {
|
|
3006
3201
|
if (e.composedPath().includes(this)) return;
|
|
3007
3202
|
const state = this.store.get();
|
|
3008
3203
|
if (state.mode === "placing") {
|
|
3009
|
-
if (
|
|
3204
|
+
if (needsDragAim(window)) return;
|
|
3205
|
+
if (e.shiftKey) this.#accumulateTarget(e);
|
|
3206
|
+
else this.#placeDraft(e);
|
|
3010
3207
|
return;
|
|
3011
3208
|
}
|
|
3012
3209
|
if (state.inboxOpen) this.store.update({ inboxOpen: false });
|
|
@@ -3023,6 +3220,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
3023
3220
|
d: () => this.#toggleTheme(),
|
|
3024
3221
|
r: () => this.#resolveActive(),
|
|
3025
3222
|
c: () => this.#copyOpenPins(),
|
|
3223
|
+
h: () => this.#togglePinsHidden(),
|
|
3026
3224
|
m: () => this.#min?.minimized() === true ? this.restore(true) : this.minimize(true),
|
|
3027
3225
|
"?": () => this.#toggleHelp()
|
|
3028
3226
|
};
|
|
@@ -3055,6 +3253,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
3055
3253
|
const placing = state.mode === "placing";
|
|
3056
3254
|
this.toggleAttribute("data-placing", placing);
|
|
3057
3255
|
document.body.classList.toggle(PAGE_PLACING_CLASS, placing);
|
|
3256
|
+
if (!placing) this.#clearExtraTargets();
|
|
3058
3257
|
if (!placing) this.#reticle?.release();
|
|
3059
3258
|
this.#syncAim(placing);
|
|
3060
3259
|
if (this.#pinsLayer) renderPins(this.#pinsLayer, state);
|