@autono/pinbox-toolbar 0.13.0 → 0.15.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
package/dist/toolbar.iife.js
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
var Pinbox = (function(exports) {
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
//#region src/anchor-watch.ts
|
|
4
|
+
function watchAnchors(win, onChange) {
|
|
5
|
+
let frame = 0;
|
|
6
|
+
const schedule = () => {
|
|
7
|
+
if (frame !== 0) return;
|
|
8
|
+
frame = win.requestAnimationFrame(() => {
|
|
9
|
+
frame = 0;
|
|
10
|
+
onChange();
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
const observer = new (win.MutationObserver ?? MutationObserver)(schedule);
|
|
14
|
+
observer.observe(win.document.body, {
|
|
15
|
+
childList: true,
|
|
16
|
+
subtree: true
|
|
17
|
+
});
|
|
18
|
+
win.addEventListener("popstate", schedule);
|
|
19
|
+
return { destroy() {
|
|
20
|
+
observer.disconnect();
|
|
21
|
+
win.removeEventListener("popstate", schedule);
|
|
22
|
+
if (frame !== 0) win.cancelAnimationFrame(frame);
|
|
23
|
+
frame = 0;
|
|
24
|
+
} };
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
3
27
|
//#region src/targeting/dom.ts
|
|
4
28
|
/**
|
|
5
29
|
* Deepest element under (clientX, clientY) that the caller does not ignore, or null when there is
|
|
@@ -272,9 +296,10 @@ var Pinbox = (function(exports) {
|
|
|
272
296
|
function block(pin, thread) {
|
|
273
297
|
const { selector, url, source } = pin.target ?? {};
|
|
274
298
|
return [
|
|
275
|
-
`## Pin ${pin.id} —
|
|
299
|
+
`## Pin ${pin.n === void 0 ? pin.id : `#${pin.n} (${pin.id})`} — ${pin.status.toUpperCase()}`,
|
|
276
300
|
`- label: ${line(label(pin))}`,
|
|
277
301
|
...selector === void 0 ? [] : [`- selector: \`${line(selector)}\``],
|
|
302
|
+
...(pin.target?.targets ?? []).map((t) => t.selector ?? t.anchor ?? t.tag).filter((locus) => locus !== void 0).map((locus) => `- also: \`${line(locus)}\``),
|
|
278
303
|
...source === void 0 ? [] : [`- source: ${line(source.line === void 0 ? source.file : `${source.file}:${source.line}`)}`],
|
|
279
304
|
...url === void 0 ? [] : [`- url: ${line(url)}`],
|
|
280
305
|
"",
|
|
@@ -288,6 +313,11 @@ var Pinbox = (function(exports) {
|
|
|
288
313
|
if (open.length === 0) return "No open pins.\n";
|
|
289
314
|
return `${open.map((p) => block(p, threads.get(p.id) ?? [])).join("\n\n")}\n`;
|
|
290
315
|
}
|
|
316
|
+
/** One pin's block — the card's per-pin copy (dogfood: "I want to copy an
|
|
317
|
+
* individual pin"); any status, since you copy exactly what you're looking at. */
|
|
318
|
+
function pinToMarkdown(pin, thread) {
|
|
319
|
+
return `${block(pin, thread)}\n`;
|
|
320
|
+
}
|
|
291
321
|
//#endregion
|
|
292
322
|
//#region src/motion/spring.ts
|
|
293
323
|
/** Bar ⇄ puck morphs and the post-drag settle. */
|
|
@@ -954,7 +984,8 @@ var Pinbox = (function(exports) {
|
|
|
954
984
|
inboxOpen: false,
|
|
955
985
|
connection: "connecting",
|
|
956
986
|
queuedIds: /* @__PURE__ */ new Set(),
|
|
957
|
-
minimized: false
|
|
987
|
+
minimized: false,
|
|
988
|
+
pinsHidden: false
|
|
958
989
|
};
|
|
959
990
|
}
|
|
960
991
|
function createStore() {
|
|
@@ -981,7 +1012,8 @@ var Pinbox = (function(exports) {
|
|
|
981
1012
|
...state,
|
|
982
1013
|
draft,
|
|
983
1014
|
mode: "idle",
|
|
984
|
-
activePinId: null
|
|
1015
|
+
activePinId: null,
|
|
1016
|
+
pinsHidden: false
|
|
985
1017
|
});
|
|
986
1018
|
},
|
|
987
1019
|
discardDraft() {
|
|
@@ -1646,8 +1678,8 @@ var Pinbox = (function(exports) {
|
|
|
1646
1678
|
//#region src/ui/bar.ts
|
|
1647
1679
|
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>";
|
|
1648
1680
|
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>";
|
|
1649
|
-
const THEME_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8
|
|
1650
|
-
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>";
|
|
1681
|
+
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>";
|
|
1682
|
+
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>";
|
|
1651
1683
|
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>";
|
|
1652
1684
|
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>";
|
|
1653
1685
|
const CONNECTION_LABEL = {
|
|
@@ -1659,7 +1691,7 @@ var Pinbox = (function(exports) {
|
|
|
1659
1691
|
function createBar(doc, on) {
|
|
1660
1692
|
const root = doc.createElement("div");
|
|
1661
1693
|
root.className = "pb-bar";
|
|
1662
|
-
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>`;
|
|
1694
|
+
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>`;
|
|
1663
1695
|
const ref = (name) => root.querySelector(`[data-ref="${name}"]`);
|
|
1664
1696
|
const label = ref("label");
|
|
1665
1697
|
const pinBtn = ref("pin");
|
|
@@ -1713,6 +1745,144 @@ var Pinbox = (function(exports) {
|
|
|
1713
1745
|
return String(n).padStart(2, "0");
|
|
1714
1746
|
}
|
|
1715
1747
|
//#endregion
|
|
1748
|
+
//#region src/ui/pins.ts
|
|
1749
|
+
/** The prototype's `_h` innerHTML memo, kept off the DOM node. */
|
|
1750
|
+
const chipMemo = /* @__PURE__ */ new WeakMap();
|
|
1751
|
+
/** What the hub will number the next pin: max known `n`, else the pin count. */
|
|
1752
|
+
function nextOrdinal(pins) {
|
|
1753
|
+
return Math.max(pins.length, ...pins.map((p) => p.n ?? 0)) + 1;
|
|
1754
|
+
}
|
|
1755
|
+
/**
|
|
1756
|
+
* Does the pin's captured URL still describe the view on screen? Path + search
|
|
1757
|
+
* only — hashes are anchors, not views. An absent or unparseable URL never
|
|
1758
|
+
* gates: old pins (and CLI pins) keep rendering exactly as before.
|
|
1759
|
+
*/
|
|
1760
|
+
function sameView(win, url) {
|
|
1761
|
+
if (url === void 0) return true;
|
|
1762
|
+
try {
|
|
1763
|
+
const target = new URL(url, win.location.href);
|
|
1764
|
+
return target.pathname === win.location.pathname && target.search === win.location.search;
|
|
1765
|
+
} catch {
|
|
1766
|
+
return true;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Where the pin's anchor is NOW (dogfood #26: markers lingered over unrelated
|
|
1771
|
+
* views after SPA tab switches, because placement trusted the stored rect
|
|
1772
|
+
* forever). Re-resolve the captured selector on every render:
|
|
1773
|
+
* - it resolves with layout → snap to the LIVE rect (also fixes drift);
|
|
1774
|
+
* - it resolves without layout (test DOMs, display:none) → stored rect;
|
|
1775
|
+
* - it does not resolve → no marker; the drawer stays the see-everything list.
|
|
1776
|
+
* A pin with no selector (terminal-adjacent) keeps its stored rect, as before.
|
|
1777
|
+
*/
|
|
1778
|
+
function anchorRect(layer, pin) {
|
|
1779
|
+
const stored = pin.target?.rect;
|
|
1780
|
+
if (stored === void 0) return null;
|
|
1781
|
+
const doc = layer.ownerDocument;
|
|
1782
|
+
const win = doc.defaultView;
|
|
1783
|
+
if (win === null) return stored;
|
|
1784
|
+
if (!sameView(win, pin.target?.url)) return null;
|
|
1785
|
+
const selector = pin.target?.selector;
|
|
1786
|
+
if (selector === void 0) return stored;
|
|
1787
|
+
let el;
|
|
1788
|
+
try {
|
|
1789
|
+
el = doc.querySelector(selector);
|
|
1790
|
+
} catch {
|
|
1791
|
+
return stored;
|
|
1792
|
+
}
|
|
1793
|
+
if (el === null) return null;
|
|
1794
|
+
const r = el.getBoundingClientRect();
|
|
1795
|
+
if (r.width <= 0 && r.height <= 0) return stored;
|
|
1796
|
+
return {
|
|
1797
|
+
x: r.left + win.scrollX,
|
|
1798
|
+
y: r.top + win.scrollY,
|
|
1799
|
+
width: r.width,
|
|
1800
|
+
height: r.height
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Where the needle lands: the point inside the element that was actually clicked, when the pin
|
|
1805
|
+
* recorded one, else the centre of its box.
|
|
1806
|
+
*
|
|
1807
|
+
* `spot` is a fraction of the element, so the pin still tracks the element when it moves or
|
|
1808
|
+
* resizes — it just stops sliding to the middle of a wide block the moment you commit it.
|
|
1809
|
+
*/
|
|
1810
|
+
function pinPoint(r, spot) {
|
|
1811
|
+
const fx = spot?.x ?? .5;
|
|
1812
|
+
const fy = spot?.y ?? .5;
|
|
1813
|
+
return {
|
|
1814
|
+
x: r.x + r.width * fx,
|
|
1815
|
+
y: r.y + r.height * fy
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
/** Chip contents (prototype chipBtnInner, lines 546–550): number + linked-channel tag,
|
|
1819
|
+
* plus the queued badge while the pin waits in the outbox for the reconnect flush. */
|
|
1820
|
+
function chipInner(n, pin, queued = false) {
|
|
1821
|
+
const link = pin?.links?.[0];
|
|
1822
|
+
const badge = link ? `<span class="lk"><span>${esc(link.connector)}</span></span>` : "";
|
|
1823
|
+
const qd = queued ? "<span class=\"qd\">QUEUED</span>" : "";
|
|
1824
|
+
return `<span>${pinNumber(n)}</span>${badge}${qd}`;
|
|
1825
|
+
}
|
|
1826
|
+
function ensureNode(layer, key, fresh) {
|
|
1827
|
+
let node = layer.querySelector(`[data-pin="${key}"]`);
|
|
1828
|
+
if (!node) {
|
|
1829
|
+
node = layer.ownerDocument.createElement("div");
|
|
1830
|
+
node.className = "pb-pin";
|
|
1831
|
+
node.setAttribute("data-pin", key);
|
|
1832
|
+
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>`;
|
|
1833
|
+
layer.appendChild(node);
|
|
1834
|
+
}
|
|
1835
|
+
return node;
|
|
1836
|
+
}
|
|
1837
|
+
function patchNode(node, at, hot, inner) {
|
|
1838
|
+
node.style.left = `${at.x}px`;
|
|
1839
|
+
node.style.top = `${at.y}px`;
|
|
1840
|
+
node.style.zIndex = hot ? "40" : "20";
|
|
1841
|
+
node.classList.toggle("hot", hot);
|
|
1842
|
+
const chip = node.querySelector(".pb-chipBtn");
|
|
1843
|
+
if (chip && chipMemo.get(chip) !== inner) {
|
|
1844
|
+
chip.innerHTML = inner;
|
|
1845
|
+
chipMemo.set(chip, inner);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Render the pin layer for a state snapshot. Visible pins are open pins, the
|
|
1850
|
+
* active pin regardless of status, and the client-only draft (key "draft").
|
|
1851
|
+
*/
|
|
1852
|
+
function renderPins(layer, state) {
|
|
1853
|
+
layer.hidden = state.pinsHidden;
|
|
1854
|
+
if (state.pinsHidden) return;
|
|
1855
|
+
const visible = state.pins.filter((p) => p.status !== "resolved" || p.id === state.activePinId);
|
|
1856
|
+
const placed = [];
|
|
1857
|
+
visible.forEach((pin, i) => {
|
|
1858
|
+
const rect = anchorRect(layer, pin);
|
|
1859
|
+
if (rect === null) return;
|
|
1860
|
+
const spot = pin.target?.spot;
|
|
1861
|
+
const n = pin.n ?? i + 1;
|
|
1862
|
+
placed.push(spot === void 0 ? {
|
|
1863
|
+
pin,
|
|
1864
|
+
n,
|
|
1865
|
+
rect
|
|
1866
|
+
} : {
|
|
1867
|
+
pin,
|
|
1868
|
+
n,
|
|
1869
|
+
rect,
|
|
1870
|
+
spot
|
|
1871
|
+
});
|
|
1872
|
+
});
|
|
1873
|
+
const keys = new Set(placed.map((entry) => entry.pin.id));
|
|
1874
|
+
if (state.draft) keys.add("draft");
|
|
1875
|
+
for (const node of [...layer.children]) if (!keys.has(node.getAttribute("data-pin") ?? "")) node.remove();
|
|
1876
|
+
for (const { pin, n, rect, spot } of placed) {
|
|
1877
|
+
const node = ensureNode(layer, pin.id, false);
|
|
1878
|
+
const hot = pin.id === state.activePinId;
|
|
1879
|
+
const queued = state.queuedIds.has(pin.id);
|
|
1880
|
+
node.classList.toggle("queued", queued);
|
|
1881
|
+
patchNode(node, pinPoint(rect, spot), hot, chipInner(n, pin, queued));
|
|
1882
|
+
}
|
|
1883
|
+
if (state.draft) patchNode(ensureNode(layer, "draft", true), state.draft.placedAt, true, chipInner(nextOrdinal(state.pins), null));
|
|
1884
|
+
}
|
|
1885
|
+
//#endregion
|
|
1716
1886
|
//#region src/ui/card.ts
|
|
1717
1887
|
const STATUS_LABEL = {
|
|
1718
1888
|
open: "OPEN",
|
|
@@ -1723,6 +1893,7 @@ var Pinbox = (function(exports) {
|
|
|
1723
1893
|
};
|
|
1724
1894
|
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>";
|
|
1725
1895
|
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>";
|
|
1896
|
+
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>";
|
|
1726
1897
|
const ctxByCard = /* @__PURE__ */ new WeakMap();
|
|
1727
1898
|
/** The prototype's `_h` innerHTML memo, kept off the DOM node. */
|
|
1728
1899
|
const nodeMemo = /* @__PURE__ */ new WeakMap();
|
|
@@ -1744,8 +1915,15 @@ var Pinbox = (function(exports) {
|
|
|
1744
1915
|
if (!m.attachments?.length) return "";
|
|
1745
1916
|
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>`;
|
|
1746
1917
|
}
|
|
1918
|
+
/** "claude:lark-mac-agent" → "Claude · lark-mac-agent"; other shapes verbatim. */
|
|
1919
|
+
function agentName(origin) {
|
|
1920
|
+
const idx = origin.indexOf(":");
|
|
1921
|
+
if (idx <= 0) return origin;
|
|
1922
|
+
const agent = origin.slice(0, idx);
|
|
1923
|
+
return `${agent.charAt(0).toUpperCase()}${agent.slice(1)} · ${origin.slice(idx + 1)}`;
|
|
1924
|
+
}
|
|
1747
1925
|
function messageHtml(m) {
|
|
1748
|
-
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"
|
|
1926
|
+
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>`;
|
|
1749
1927
|
const mirror = m.role === "mirror";
|
|
1750
1928
|
const origin = mirror ? m.origin ?? "mirror" : null;
|
|
1751
1929
|
const who = origin ? origin.split(":")[1] ?? origin : "You";
|
|
@@ -1832,7 +2010,14 @@ var Pinbox = (function(exports) {
|
|
|
1832
2010
|
else if (action === "close") ctx.actions.close();
|
|
1833
2011
|
else if (ctx.pid !== "draft") {
|
|
1834
2012
|
if (action === "resolve") ctx.actions.resolve(ctx.pid);
|
|
1835
|
-
else if (action === "
|
|
2013
|
+
else if (action === "copy") {
|
|
2014
|
+
ctx.actions.copy(ctx.pid);
|
|
2015
|
+
const btn = e.target.closest?.("[data-action=\"copy\"]");
|
|
2016
|
+
if (btn) {
|
|
2017
|
+
btn.classList.add("ok");
|
|
2018
|
+
card.ownerDocument.defaultView?.setTimeout(() => btn.classList.remove("ok"), 900);
|
|
2019
|
+
}
|
|
2020
|
+
} else if (action === "verify-accept") ctx.actions.verify(ctx.pid, "accepted");
|
|
1836
2021
|
else if (action === "verify-reopen") {
|
|
1837
2022
|
ctx.actions.verify(ctx.pid, "reopened");
|
|
1838
2023
|
card.querySelector("textarea")?.focus();
|
|
@@ -1840,7 +2025,7 @@ var Pinbox = (function(exports) {
|
|
|
1840
2025
|
}
|
|
1841
2026
|
}
|
|
1842
2027
|
function buildSkeleton(card, ctx, isDraft, hasThread) {
|
|
1843
|
-
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>";
|
|
2028
|
+
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>";
|
|
1844
2029
|
const ta = card.querySelector("textarea");
|
|
1845
2030
|
ta.placeholder = hasThread ? "Ask a question or request a change…" : "What should change here?";
|
|
1846
2031
|
ta.addEventListener("keydown", (e) => {
|
|
@@ -1856,8 +2041,20 @@ var Pinbox = (function(exports) {
|
|
|
1856
2041
|
}, true);
|
|
1857
2042
|
if (isDraft) ta.focus();
|
|
1858
2043
|
}
|
|
1859
|
-
function hdHtml(n, targetLabel, status, resolvable) {
|
|
1860
|
-
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>`;
|
|
2044
|
+
function hdHtml(n, targetLabel, status, resolvable, copyable) {
|
|
2045
|
+
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>`;
|
|
2046
|
+
}
|
|
2047
|
+
/** One name per locus: selector first, else anchor, else tag. */
|
|
2048
|
+
function locusName(t) {
|
|
2049
|
+
return t.selector ?? t.anchor ?? t.tag?.toUpperCase();
|
|
2050
|
+
}
|
|
2051
|
+
/** The extra loci of a multi-target pin — the anchor leads, extras follow. */
|
|
2052
|
+
function lociHtml(pin) {
|
|
2053
|
+
const target = pin?.target;
|
|
2054
|
+
const extras = target?.targets;
|
|
2055
|
+
if (target === void 0 || extras === void 0 || extras.length === 0) return "";
|
|
2056
|
+
const names = [target, ...extras].map((t) => esc(locusName(t) ?? "?"));
|
|
2057
|
+
return `<div class="pb-loci">${names.length} targets: ${names.join(" · ")}</div>`;
|
|
1861
2058
|
}
|
|
1862
2059
|
/** Link badge: pin.links[0] read-only — no picker, no unlink yet. */
|
|
1863
2060
|
function linkHtml(pin) {
|
|
@@ -1866,8 +2063,9 @@ var Pinbox = (function(exports) {
|
|
|
1866
2063
|
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>`;
|
|
1867
2064
|
}
|
|
1868
2065
|
function verifyHtml(status) {
|
|
1869
|
-
if (status
|
|
1870
|
-
|
|
2066
|
+
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>";
|
|
2067
|
+
if (status === "resolved") return "<div class=\"pb-verify\"><button type=\"button\" class=\"pb-bt-ghost\" data-action=\"verify-reopen\">Unresolve</button></div>";
|
|
2068
|
+
return "";
|
|
1871
2069
|
}
|
|
1872
2070
|
function rowHtml(hasThread) {
|
|
1873
2071
|
return `<div class="pb-kbd">⌘ ↵</div><button type="button" class="pb-bt-solid" data-action="send">${hasThread ? "Reply" : "Comment"}</button>`;
|
|
@@ -1904,10 +2102,11 @@ var Pinbox = (function(exports) {
|
|
|
1904
2102
|
if (!state.activePinId) return null;
|
|
1905
2103
|
return state.pins.find((p) => p.id === state.activePinId) ?? null;
|
|
1906
2104
|
}
|
|
1907
|
-
/**
|
|
2105
|
+
/** The pin's hub-born number; visible-index only for pre-`n` pins, drafts next up. */
|
|
1908
2106
|
function ordinalOf(state, pin) {
|
|
1909
|
-
|
|
1910
|
-
|
|
2107
|
+
if (pin === null) return nextOrdinal(state.pins);
|
|
2108
|
+
if (pin.n !== void 0) return pin.n;
|
|
2109
|
+
return state.pins.filter((p) => p.status !== "resolved" || p.id === state.activePinId).indexOf(pin) + 1;
|
|
1911
2110
|
}
|
|
1912
2111
|
function anchorOf(pin, draft) {
|
|
1913
2112
|
const r = pin?.target?.rect;
|
|
@@ -1978,8 +2177,9 @@ var Pinbox = (function(exports) {
|
|
|
1978
2177
|
const queued = view.pin !== null && state.queuedIds.has(view.pin.id);
|
|
1979
2178
|
const statusLabel = queued ? "QUEUED" : view.status ? STATUS_LABEL[view.status] : "NEW";
|
|
1980
2179
|
const resolvable = view.pin?.status === "open" && !queued;
|
|
1981
|
-
setPart(card, ctx, "hd", hdHtml(view.n, view.label, statusLabel, resolvable));
|
|
2180
|
+
setPart(card, ctx, "hd", hdHtml(view.n, view.label, statusLabel, resolvable, view.pin !== null));
|
|
1982
2181
|
setPart(card, ctx, "link", linkHtml(view.pin));
|
|
2182
|
+
setPart(card, ctx, "loci", lociHtml(view.pin));
|
|
1983
2183
|
setPart(card, ctx, "verify", verifyHtml(view.status));
|
|
1984
2184
|
const messages = view.pin === null ? view.thread : [pinAsMessage(view.pin), ...view.thread];
|
|
1985
2185
|
setPart(card, ctx, "row", rowHtml(messages.length > 0));
|
|
@@ -2051,7 +2251,7 @@ var Pinbox = (function(exports) {
|
|
|
2051
2251
|
doneTab.classList.toggle("on", tab === "resolved");
|
|
2052
2252
|
}
|
|
2053
2253
|
const list = tab === "open" ? open : resolved;
|
|
2054
|
-
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>";
|
|
2254
|
+
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>";
|
|
2055
2255
|
if (itemsMemo !== html) {
|
|
2056
2256
|
items.innerHTML = html;
|
|
2057
2257
|
itemsMemo = html;
|
|
@@ -2084,87 +2284,23 @@ var Pinbox = (function(exports) {
|
|
|
2084
2284
|
};
|
|
2085
2285
|
}
|
|
2086
2286
|
//#endregion
|
|
2087
|
-
//#region src/ui/
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
* `spot` is a fraction of the element, so the pin still tracks the element when it moves or
|
|
2095
|
-
* resizes — it just stops sliding to the middle of a wide block the moment you commit it.
|
|
2096
|
-
*/
|
|
2097
|
-
function pinPoint(r, spot) {
|
|
2098
|
-
const fx = spot?.x ?? .5;
|
|
2099
|
-
const fy = spot?.y ?? .5;
|
|
2100
|
-
return {
|
|
2101
|
-
x: r.x + r.width * fx,
|
|
2102
|
-
y: r.y + r.height * fy
|
|
2103
|
-
};
|
|
2104
|
-
}
|
|
2105
|
-
/** Chip contents (prototype chipBtnInner, lines 546–550): number + linked-channel tag,
|
|
2106
|
-
* plus the queued badge while the pin waits in the outbox for the reconnect flush. */
|
|
2107
|
-
function chipInner(n, pin, queued = false) {
|
|
2108
|
-
const link = pin?.links?.[0];
|
|
2109
|
-
const badge = link ? `<span class="lk"><span>${esc(link.connector)}</span></span>` : "";
|
|
2110
|
-
const qd = queued ? "<span class=\"qd\">QUEUED</span>" : "";
|
|
2111
|
-
return `<span>${pinNumber(n)}</span>${badge}${qd}`;
|
|
2112
|
-
}
|
|
2113
|
-
function ensureNode(layer, key, fresh) {
|
|
2114
|
-
let node = layer.querySelector(`[data-pin="${key}"]`);
|
|
2115
|
-
if (!node) {
|
|
2116
|
-
node = layer.ownerDocument.createElement("div");
|
|
2117
|
-
node.className = "pb-pin";
|
|
2118
|
-
node.setAttribute("data-pin", key);
|
|
2119
|
-
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>`;
|
|
2120
|
-
layer.appendChild(node);
|
|
2121
|
-
}
|
|
2122
|
-
return node;
|
|
2123
|
-
}
|
|
2124
|
-
function patchNode(node, at, hot, inner) {
|
|
2125
|
-
node.style.left = `${at.x}px`;
|
|
2126
|
-
node.style.top = `${at.y}px`;
|
|
2127
|
-
node.style.zIndex = hot ? "40" : "20";
|
|
2128
|
-
node.classList.toggle("hot", hot);
|
|
2129
|
-
const chip = node.querySelector(".pb-chipBtn");
|
|
2130
|
-
if (chip && chipMemo.get(chip) !== inner) {
|
|
2131
|
-
chip.innerHTML = inner;
|
|
2132
|
-
chipMemo.set(chip, inner);
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Render the pin layer for a state snapshot. Visible pins are open pins, the
|
|
2137
|
-
* active pin regardless of status, and the client-only draft (key "draft").
|
|
2138
|
-
*/
|
|
2139
|
-
function renderPins(layer, state) {
|
|
2140
|
-
const visible = state.pins.filter((p) => p.status !== "resolved" || p.id === state.activePinId);
|
|
2141
|
-
const placed = [];
|
|
2142
|
-
visible.forEach((pin, i) => {
|
|
2143
|
-
const rect = pin.target?.rect;
|
|
2287
|
+
//#region src/ui/multimarks.ts
|
|
2288
|
+
const MARK_CLASS = "pb-multi-mark";
|
|
2289
|
+
/** Replace the mark set to mirror `targets`; entries with no rect draw nothing. */
|
|
2290
|
+
function renderMultiMarks(layer, targets) {
|
|
2291
|
+
for (const node of [...layer.querySelectorAll(`.${MARK_CLASS}`)]) node.remove();
|
|
2292
|
+
targets.forEach((target, i) => {
|
|
2293
|
+
const rect = target.rect;
|
|
2144
2294
|
if (rect === void 0) return;
|
|
2145
|
-
const
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
rect,
|
|
2154
|
-
spot
|
|
2155
|
-
});
|
|
2295
|
+
const mark = layer.ownerDocument.createElement("div");
|
|
2296
|
+
mark.className = MARK_CLASS;
|
|
2297
|
+
mark.style.left = `${rect.x}px`;
|
|
2298
|
+
mark.style.top = `${rect.y}px`;
|
|
2299
|
+
mark.style.width = `${rect.width}px`;
|
|
2300
|
+
mark.style.height = `${rect.height}px`;
|
|
2301
|
+
mark.innerHTML = `<span>${i + 1}</span>`;
|
|
2302
|
+
layer.appendChild(mark);
|
|
2156
2303
|
});
|
|
2157
|
-
const keys = new Set(placed.map((entry) => entry.pin.id));
|
|
2158
|
-
if (state.draft) keys.add("draft");
|
|
2159
|
-
for (const node of [...layer.children]) if (!keys.has(node.getAttribute("data-pin") ?? "")) node.remove();
|
|
2160
|
-
for (const { pin, n, rect, spot } of placed) {
|
|
2161
|
-
const node = ensureNode(layer, pin.id, false);
|
|
2162
|
-
const hot = pin.id === state.activePinId;
|
|
2163
|
-
const queued = state.queuedIds.has(pin.id);
|
|
2164
|
-
node.classList.toggle("queued", queued);
|
|
2165
|
-
patchNode(node, pinPoint(rect, spot), hot, chipInner(n, pin, queued));
|
|
2166
|
-
}
|
|
2167
|
-
if (state.draft) patchNode(ensureNode(layer, "draft", true), state.draft.placedAt, true, chipInner(visible.length + 1, null));
|
|
2168
2304
|
}
|
|
2169
2305
|
//#endregion
|
|
2170
2306
|
//#region src/ui/puck.ts
|
|
@@ -2172,7 +2308,9 @@ var Pinbox = (function(exports) {
|
|
|
2172
2308
|
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>";
|
|
2173
2309
|
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>";
|
|
2174
2310
|
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>";
|
|
2175
|
-
const FAN_THEME = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8
|
|
2311
|
+
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>";
|
|
2312
|
+
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>";
|
|
2313
|
+
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>";
|
|
2176
2314
|
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>";
|
|
2177
2315
|
function fanItem(act, index, icon, label, key) {
|
|
2178
2316
|
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>`;
|
|
@@ -2188,7 +2326,7 @@ var Pinbox = (function(exports) {
|
|
|
2188
2326
|
fan.className = "pb-fan";
|
|
2189
2327
|
fan.hidden = true;
|
|
2190
2328
|
fan.setAttribute("role", "menu");
|
|
2191
|
-
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("
|
|
2329
|
+
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");
|
|
2192
2330
|
const morphWrap = doc.createElement("div");
|
|
2193
2331
|
morphWrap.className = "pb-morph-wrap";
|
|
2194
2332
|
morphWrap.hidden = true;
|
|
@@ -2204,6 +2342,9 @@ var Pinbox = (function(exports) {
|
|
|
2204
2342
|
carrier.querySelector("[data-ref=\"count\"]"),
|
|
2205
2343
|
fan.querySelector("[data-ref=\"count\"]")
|
|
2206
2344
|
];
|
|
2345
|
+
const hideItem = fan.querySelector("[data-act=\"hide\"]");
|
|
2346
|
+
/** Last-rendered hide state; the item's markup is swapped only on change. */
|
|
2347
|
+
let hideShown = null;
|
|
2207
2348
|
return {
|
|
2208
2349
|
puck,
|
|
2209
2350
|
fan,
|
|
@@ -2219,6 +2360,13 @@ var Pinbox = (function(exports) {
|
|
|
2219
2360
|
const degraded = state.connection === "offline" || state.connection === "incompatible";
|
|
2220
2361
|
puck.classList.toggle("degraded", degraded);
|
|
2221
2362
|
puck.classList.toggle("armed", state.mode === "placing");
|
|
2363
|
+
if (hideShown !== state.pinsHidden) {
|
|
2364
|
+
hideShown = state.pinsHidden;
|
|
2365
|
+
const label = state.pinsHidden ? "Show pins" : "Hide pins";
|
|
2366
|
+
hideItem.innerHTML = `${state.pinsHidden ? FAN_EYE : FAN_EYE_OFF}<span class="fl">${label.toUpperCase()}<i>H</i></span>`;
|
|
2367
|
+
hideItem.setAttribute("aria-label", label);
|
|
2368
|
+
hideItem.classList.toggle("lit", state.pinsHidden);
|
|
2369
|
+
}
|
|
2222
2370
|
}
|
|
2223
2371
|
};
|
|
2224
2372
|
}
|
|
@@ -2278,6 +2426,7 @@ var Pinbox = (function(exports) {
|
|
|
2278
2426
|
["Send comment", "⌘ ↵"],
|
|
2279
2427
|
["Mark pin resolved", "R"],
|
|
2280
2428
|
["Copy open pins", "C"],
|
|
2429
|
+
["Hide / show pins", "H"],
|
|
2281
2430
|
["Minimize toolbar", "M"],
|
|
2282
2431
|
["Cancel", "ESC"]
|
|
2283
2432
|
];
|
|
@@ -2521,6 +2670,10 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2521
2670
|
.pb-fan.down { --fan-from: translateY(-16px); }
|
|
2522
2671
|
.pb-fan.on .pb-fan-item { opacity: 1; transform: none; }
|
|
2523
2672
|
.pb-fan-item:hover { color: var(--pb-amber); border-color: var(--pb-amber); }
|
|
2673
|
+
.pb-fan-item.lit { color: var(--pb-amber); border-color: var(--pb-amber); }
|
|
2674
|
+
.pb-multi-mark { position: absolute; border: 1.5px dashed var(--pb-amber); border-radius: 4px; pointer-events: none; z-index: 15; }
|
|
2675
|
+
.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; }
|
|
2676
|
+
.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; }
|
|
2524
2677
|
.pb-fan-item .badge { pointer-events: none; }
|
|
2525
2678
|
.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); }
|
|
2526
2679
|
.pb-fan.labels-right .fl { left: calc(100% + 10px); transform: translateY(-50%) translateX(-6px); }
|
|
@@ -2585,15 +2738,20 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2585
2738
|
#modal = null;
|
|
2586
2739
|
#minUi = null;
|
|
2587
2740
|
#min = null;
|
|
2741
|
+
/** SPA view watcher: DOM/history changes re-run the anchor-gated render. */
|
|
2742
|
+
#anchors = null;
|
|
2588
2743
|
#helpOpen = false;
|
|
2589
2744
|
#pageStyle = null;
|
|
2590
2745
|
#unsubscribe = null;
|
|
2591
2746
|
#hover = null;
|
|
2747
|
+
/** Shift+click accumulation while placing — extra loci for ONE pending pin. */
|
|
2748
|
+
#extraTargets = [];
|
|
2592
2749
|
/** Card → element: send/verify/resolve forward to the transport seam; close dismisses. */
|
|
2593
2750
|
#cardActions = {
|
|
2594
2751
|
send: (pinId, text) => this.actions.send?.(pinId, text),
|
|
2595
2752
|
verify: (pinId, outcome) => this.actions.verify?.(pinId, outcome),
|
|
2596
2753
|
resolve: (pinId) => this.actions.resolve?.(pinId),
|
|
2754
|
+
copy: (pinId) => this.#copyPin(pinId),
|
|
2597
2755
|
close: () => this.#dismiss()
|
|
2598
2756
|
};
|
|
2599
2757
|
/** Programmatic path (Pinbox.init). The snippet path reads hub/token attributes. */
|
|
@@ -2626,6 +2784,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2626
2784
|
this.#pageStyle = style;
|
|
2627
2785
|
if (this.#aim === null) this.#mountAim();
|
|
2628
2786
|
if (this.#min === null) this.#mountMinimize();
|
|
2787
|
+
this.#anchors = watchAnchors(window, () => this.#render(this.store.get()));
|
|
2629
2788
|
document.addEventListener("mousemove", this.#onMouseMove);
|
|
2630
2789
|
window.addEventListener("scroll", this.#onViewportChange, { passive: true });
|
|
2631
2790
|
window.addEventListener("resize", this.#onViewportChange);
|
|
@@ -2674,6 +2833,8 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2674
2833
|
this.#min?.destroy();
|
|
2675
2834
|
this.#min = null;
|
|
2676
2835
|
releaseCapture();
|
|
2836
|
+
this.#anchors?.destroy();
|
|
2837
|
+
this.#anchors = null;
|
|
2677
2838
|
this.#unsubscribe?.();
|
|
2678
2839
|
this.#unsubscribe = null;
|
|
2679
2840
|
this.#pageStyle?.remove();
|
|
@@ -2763,6 +2924,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2763
2924
|
onFanAction: (action) => {
|
|
2764
2925
|
if (action === "pin") this.#togglePlacing();
|
|
2765
2926
|
else if (action === "inbox") this.#toggleInbox();
|
|
2927
|
+
else if (action === "hide") this.#togglePinsHidden();
|
|
2766
2928
|
else this.#toggleTheme();
|
|
2767
2929
|
}
|
|
2768
2930
|
});
|
|
@@ -2890,6 +3052,18 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2890
3052
|
navigator.clipboard.writeText(pinsToMarkdown(state.pins, state.threads));
|
|
2891
3053
|
} catch {}
|
|
2892
3054
|
}
|
|
3055
|
+
/** The card's copy: exactly the pin you are looking at, thread included. */
|
|
3056
|
+
#copyPin(pinId) {
|
|
3057
|
+
const state = this.store.get();
|
|
3058
|
+
const pin = state.pins.find((p) => p.id === pinId);
|
|
3059
|
+
if (pin === void 0) return;
|
|
3060
|
+
try {
|
|
3061
|
+
navigator.clipboard.writeText(pinToMarkdown(pin, state.threads.get(pinId) ?? []));
|
|
3062
|
+
} catch {}
|
|
3063
|
+
}
|
|
3064
|
+
#togglePinsHidden() {
|
|
3065
|
+
this.store.update({ pinsHidden: !this.store.get().pinsHidden });
|
|
3066
|
+
}
|
|
2893
3067
|
#setHelp(open) {
|
|
2894
3068
|
this.#helpOpen = open;
|
|
2895
3069
|
this.#modal?.set(open);
|
|
@@ -2907,9 +3081,13 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2907
3081
|
}
|
|
2908
3082
|
#togglePlacing() {
|
|
2909
3083
|
const placing = this.store.get().mode === "placing";
|
|
2910
|
-
this.store.update({
|
|
2911
|
-
mode:
|
|
3084
|
+
this.store.update(placing ? {
|
|
3085
|
+
mode: "idle",
|
|
2912
3086
|
activePinId: null
|
|
3087
|
+
} : {
|
|
3088
|
+
mode: "placing",
|
|
3089
|
+
activePinId: null,
|
|
3090
|
+
pinsHidden: false
|
|
2913
3091
|
});
|
|
2914
3092
|
}
|
|
2915
3093
|
#toggleInbox() {
|
|
@@ -2926,6 +3104,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2926
3104
|
/** esc / click-away: leave placing, discard the draft (client-only), deactivate. */
|
|
2927
3105
|
#dismiss() {
|
|
2928
3106
|
this.#setHelp(false);
|
|
3107
|
+
this.#clearExtraTargets();
|
|
2929
3108
|
this.store.update({
|
|
2930
3109
|
mode: "idle",
|
|
2931
3110
|
activePinId: null
|
|
@@ -2991,12 +3170,14 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2991
3170
|
#placeDraft(e) {
|
|
2992
3171
|
e.preventDefault();
|
|
2993
3172
|
e.stopPropagation();
|
|
2994
|
-
const
|
|
3173
|
+
const capture = captureTarget(this.#hover ?? document.body, { at: {
|
|
3174
|
+
x: e.pageX,
|
|
3175
|
+
y: e.pageY
|
|
3176
|
+
} });
|
|
3177
|
+
if (this.#extraTargets.length > 0) capture.target.targets = this.#extraTargets;
|
|
3178
|
+
this.#clearExtraTargets();
|
|
2995
3179
|
this.store.place({
|
|
2996
|
-
target:
|
|
2997
|
-
x: e.pageX,
|
|
2998
|
-
y: e.pageY
|
|
2999
|
-
} }),
|
|
3180
|
+
target: capture,
|
|
3000
3181
|
placedAt: {
|
|
3001
3182
|
x: e.pageX,
|
|
3002
3183
|
y: e.pageY
|
|
@@ -3004,11 +3185,27 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
3004
3185
|
});
|
|
3005
3186
|
this.#reticle?.release();
|
|
3006
3187
|
}
|
|
3188
|
+
/** Shift+click while placing: capture WITHOUT committing; a numbered dashed
|
|
3189
|
+
* outline is the receipt. Plain click still commits (with these attached). */
|
|
3190
|
+
#accumulateTarget(e) {
|
|
3191
|
+
e.preventDefault();
|
|
3192
|
+
e.stopPropagation();
|
|
3193
|
+
const el = this.#hover ?? document.body;
|
|
3194
|
+
this.#extraTargets = [...this.#extraTargets, captureTarget(el).target];
|
|
3195
|
+
if (this.#pinsLayer) renderMultiMarks(this.#pinsLayer, this.#extraTargets);
|
|
3196
|
+
}
|
|
3197
|
+
#clearExtraTargets() {
|
|
3198
|
+
if (this.#extraTargets.length === 0) return;
|
|
3199
|
+
this.#extraTargets = [];
|
|
3200
|
+
if (this.#pinsLayer) renderMultiMarks(this.#pinsLayer, []);
|
|
3201
|
+
}
|
|
3007
3202
|
#onClickCapture = (e) => {
|
|
3008
3203
|
if (e.composedPath().includes(this)) return;
|
|
3009
3204
|
const state = this.store.get();
|
|
3010
3205
|
if (state.mode === "placing") {
|
|
3011
|
-
if (
|
|
3206
|
+
if (needsDragAim(window)) return;
|
|
3207
|
+
if (e.shiftKey) this.#accumulateTarget(e);
|
|
3208
|
+
else this.#placeDraft(e);
|
|
3012
3209
|
return;
|
|
3013
3210
|
}
|
|
3014
3211
|
if (state.inboxOpen) this.store.update({ inboxOpen: false });
|
|
@@ -3025,6 +3222,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
3025
3222
|
d: () => this.#toggleTheme(),
|
|
3026
3223
|
r: () => this.#resolveActive(),
|
|
3027
3224
|
c: () => this.#copyOpenPins(),
|
|
3225
|
+
h: () => this.#togglePinsHidden(),
|
|
3028
3226
|
m: () => this.#min?.minimized() === true ? this.restore(true) : this.minimize(true),
|
|
3029
3227
|
"?": () => this.#toggleHelp()
|
|
3030
3228
|
};
|
|
@@ -3057,6 +3255,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
3057
3255
|
const placing = state.mode === "placing";
|
|
3058
3256
|
this.toggleAttribute("data-placing", placing);
|
|
3059
3257
|
document.body.classList.toggle(PAGE_PLACING_CLASS, placing);
|
|
3258
|
+
if (!placing) this.#clearExtraTargets();
|
|
3060
3259
|
if (!placing) this.#reticle?.release();
|
|
3061
3260
|
this.#syncAim(placing);
|
|
3062
3261
|
if (this.#pinsLayer) renderPins(this.#pinsLayer, state);
|