@autono/pinbox-toolbar 0.12.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-C-gslo8y.js → src-GX3kPe0X.js} +342 -116
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +1 -1
- package/dist/toolbar.iife.js +342 -116
- 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. */
|
|
@@ -839,6 +869,39 @@ var Pinbox = (function(exports) {
|
|
|
839
869
|
});
|
|
840
870
|
return video;
|
|
841
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* The one live capture stream, reused across pins. getDisplayMedia MUST
|
|
874
|
+
* prompt on every call (spec — no persistent grant exists), so the only way
|
|
875
|
+
* to stop asking per pin is to never re-call it: prompt once, keep the track,
|
|
876
|
+
* and read a fresh frame from the still-playing video for every capture.
|
|
877
|
+
* The browser's "sharing this tab" indicator stays on while the track lives —
|
|
878
|
+
* honest, and the user ending it from there simply re-prompts on the next pin.
|
|
879
|
+
*/
|
|
880
|
+
let liveCapture = null;
|
|
881
|
+
async function captureVideo(win, media) {
|
|
882
|
+
const track = liveCapture?.stream.getVideoTracks()[0];
|
|
883
|
+
if (liveCapture && track?.readyState === "live") return liveCapture.video;
|
|
884
|
+
releaseCapture();
|
|
885
|
+
const stream = await media.getDisplayMedia({
|
|
886
|
+
video: true,
|
|
887
|
+
audio: false,
|
|
888
|
+
preferCurrentTab: true
|
|
889
|
+
});
|
|
890
|
+
const video = await firstFrame(win, stream);
|
|
891
|
+
stream.getVideoTracks()[0]?.addEventListener("ended", releaseCapture, { once: true });
|
|
892
|
+
liveCapture = {
|
|
893
|
+
stream,
|
|
894
|
+
video
|
|
895
|
+
};
|
|
896
|
+
return video;
|
|
897
|
+
}
|
|
898
|
+
/** Stop the cached stream (toolbar disconnect; also the track-ended handler). */
|
|
899
|
+
function releaseCapture() {
|
|
900
|
+
if (liveCapture === null) return;
|
|
901
|
+
for (const t of liveCapture.stream.getTracks()) t.stop();
|
|
902
|
+
liveCapture.video.srcObject = null;
|
|
903
|
+
liveCapture = null;
|
|
904
|
+
}
|
|
842
905
|
/** webp-encode a bitmap; also emit the ≤32px placeholder data URL. */
|
|
843
906
|
async function encode(bmp) {
|
|
844
907
|
const canvas = new OffscreenCanvas(bmp.width, bmp.height);
|
|
@@ -878,21 +941,14 @@ var Pinbox = (function(exports) {
|
|
|
878
941
|
const crop = visibleCropRect(el);
|
|
879
942
|
if (source === null || crop === null) return null;
|
|
880
943
|
const { win, media } = source;
|
|
881
|
-
let stream = null;
|
|
882
944
|
try {
|
|
883
|
-
|
|
884
|
-
video: true,
|
|
885
|
-
audio: false,
|
|
886
|
-
preferCurrentTab: true
|
|
887
|
-
});
|
|
888
|
-
const video = await firstFrame(win, stream);
|
|
945
|
+
const video = await captureVideo(win, media);
|
|
889
946
|
const sx = video.videoWidth / win.innerWidth;
|
|
890
947
|
const sy = video.videoHeight / win.innerHeight;
|
|
891
948
|
return await encode(await createImageBitmap(video, Math.round(crop.x * sx), Math.round(crop.y * sy), Math.max(1, Math.round(crop.width * sx)), Math.max(1, Math.round(crop.height * sy))));
|
|
892
949
|
} catch {
|
|
950
|
+
releaseCapture();
|
|
893
951
|
return null;
|
|
894
|
-
} finally {
|
|
895
|
-
if (stream) for (const track of stream.getTracks()) track.stop();
|
|
896
952
|
}
|
|
897
953
|
}
|
|
898
954
|
/**
|
|
@@ -928,7 +984,8 @@ var Pinbox = (function(exports) {
|
|
|
928
984
|
inboxOpen: false,
|
|
929
985
|
connection: "connecting",
|
|
930
986
|
queuedIds: /* @__PURE__ */ new Set(),
|
|
931
|
-
minimized: false
|
|
987
|
+
minimized: false,
|
|
988
|
+
pinsHidden: false
|
|
932
989
|
};
|
|
933
990
|
}
|
|
934
991
|
function createStore() {
|
|
@@ -955,7 +1012,8 @@ var Pinbox = (function(exports) {
|
|
|
955
1012
|
...state,
|
|
956
1013
|
draft,
|
|
957
1014
|
mode: "idle",
|
|
958
|
-
activePinId: null
|
|
1015
|
+
activePinId: null,
|
|
1016
|
+
pinsHidden: false
|
|
959
1017
|
});
|
|
960
1018
|
},
|
|
961
1019
|
discardDraft() {
|
|
@@ -1620,8 +1678,8 @@ var Pinbox = (function(exports) {
|
|
|
1620
1678
|
//#region src/ui/bar.ts
|
|
1621
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>";
|
|
1622
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>";
|
|
1623
|
-
const THEME_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8
|
|
1624
|
-
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>";
|
|
1625
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>";
|
|
1626
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>";
|
|
1627
1685
|
const CONNECTION_LABEL = {
|
|
@@ -1633,7 +1691,7 @@ var Pinbox = (function(exports) {
|
|
|
1633
1691
|
function createBar(doc, on) {
|
|
1634
1692
|
const root = doc.createElement("div");
|
|
1635
1693
|
root.className = "pb-bar";
|
|
1636
|
-
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>`;
|
|
1637
1695
|
const ref = (name) => root.querySelector(`[data-ref="${name}"]`);
|
|
1638
1696
|
const label = ref("label");
|
|
1639
1697
|
const pinBtn = ref("pin");
|
|
@@ -1687,6 +1745,144 @@ var Pinbox = (function(exports) {
|
|
|
1687
1745
|
return String(n).padStart(2, "0");
|
|
1688
1746
|
}
|
|
1689
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
|
|
1690
1886
|
//#region src/ui/card.ts
|
|
1691
1887
|
const STATUS_LABEL = {
|
|
1692
1888
|
open: "OPEN",
|
|
@@ -1697,6 +1893,7 @@ var Pinbox = (function(exports) {
|
|
|
1697
1893
|
};
|
|
1698
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>";
|
|
1699
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>";
|
|
1700
1897
|
const ctxByCard = /* @__PURE__ */ new WeakMap();
|
|
1701
1898
|
/** The prototype's `_h` innerHTML memo, kept off the DOM node. */
|
|
1702
1899
|
const nodeMemo = /* @__PURE__ */ new WeakMap();
|
|
@@ -1718,8 +1915,15 @@ var Pinbox = (function(exports) {
|
|
|
1718
1915
|
if (!m.attachments?.length) return "";
|
|
1719
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>`;
|
|
1720
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
|
+
}
|
|
1721
1925
|
function messageHtml(m) {
|
|
1722
|
-
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>`;
|
|
1723
1927
|
const mirror = m.role === "mirror";
|
|
1724
1928
|
const origin = mirror ? m.origin ?? "mirror" : null;
|
|
1725
1929
|
const who = origin ? origin.split(":")[1] ?? origin : "You";
|
|
@@ -1806,7 +2010,14 @@ var Pinbox = (function(exports) {
|
|
|
1806
2010
|
else if (action === "close") ctx.actions.close();
|
|
1807
2011
|
else if (ctx.pid !== "draft") {
|
|
1808
2012
|
if (action === "resolve") ctx.actions.resolve(ctx.pid);
|
|
1809
|
-
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");
|
|
1810
2021
|
else if (action === "verify-reopen") {
|
|
1811
2022
|
ctx.actions.verify(ctx.pid, "reopened");
|
|
1812
2023
|
card.querySelector("textarea")?.focus();
|
|
@@ -1814,7 +2025,7 @@ var Pinbox = (function(exports) {
|
|
|
1814
2025
|
}
|
|
1815
2026
|
}
|
|
1816
2027
|
function buildSkeleton(card, ctx, isDraft, hasThread) {
|
|
1817
|
-
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>";
|
|
1818
2029
|
const ta = card.querySelector("textarea");
|
|
1819
2030
|
ta.placeholder = hasThread ? "Ask a question or request a change…" : "What should change here?";
|
|
1820
2031
|
ta.addEventListener("keydown", (e) => {
|
|
@@ -1830,8 +2041,20 @@ var Pinbox = (function(exports) {
|
|
|
1830
2041
|
}, true);
|
|
1831
2042
|
if (isDraft) ta.focus();
|
|
1832
2043
|
}
|
|
1833
|
-
function hdHtml(n, targetLabel, status, resolvable) {
|
|
1834
|
-
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>`;
|
|
1835
2058
|
}
|
|
1836
2059
|
/** Link badge: pin.links[0] read-only — no picker, no unlink yet. */
|
|
1837
2060
|
function linkHtml(pin) {
|
|
@@ -1840,8 +2063,9 @@ var Pinbox = (function(exports) {
|
|
|
1840
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>`;
|
|
1841
2064
|
}
|
|
1842
2065
|
function verifyHtml(status) {
|
|
1843
|
-
if (status
|
|
1844
|
-
|
|
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 "";
|
|
1845
2069
|
}
|
|
1846
2070
|
function rowHtml(hasThread) {
|
|
1847
2071
|
return `<div class="pb-kbd">⌘ ↵</div><button type="button" class="pb-bt-solid" data-action="send">${hasThread ? "Reply" : "Comment"}</button>`;
|
|
@@ -1878,10 +2102,11 @@ var Pinbox = (function(exports) {
|
|
|
1878
2102
|
if (!state.activePinId) return null;
|
|
1879
2103
|
return state.pins.find((p) => p.id === state.activePinId) ?? null;
|
|
1880
2104
|
}
|
|
1881
|
-
/**
|
|
2105
|
+
/** The pin's hub-born number; visible-index only for pre-`n` pins, drafts next up. */
|
|
1882
2106
|
function ordinalOf(state, pin) {
|
|
1883
|
-
|
|
1884
|
-
|
|
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;
|
|
1885
2110
|
}
|
|
1886
2111
|
function anchorOf(pin, draft) {
|
|
1887
2112
|
const r = pin?.target?.rect;
|
|
@@ -1952,8 +2177,9 @@ var Pinbox = (function(exports) {
|
|
|
1952
2177
|
const queued = view.pin !== null && state.queuedIds.has(view.pin.id);
|
|
1953
2178
|
const statusLabel = queued ? "QUEUED" : view.status ? STATUS_LABEL[view.status] : "NEW";
|
|
1954
2179
|
const resolvable = view.pin?.status === "open" && !queued;
|
|
1955
|
-
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));
|
|
1956
2181
|
setPart(card, ctx, "link", linkHtml(view.pin));
|
|
2182
|
+
setPart(card, ctx, "loci", lociHtml(view.pin));
|
|
1957
2183
|
setPart(card, ctx, "verify", verifyHtml(view.status));
|
|
1958
2184
|
const messages = view.pin === null ? view.thread : [pinAsMessage(view.pin), ...view.thread];
|
|
1959
2185
|
setPart(card, ctx, "row", rowHtml(messages.length > 0));
|
|
@@ -2025,7 +2251,7 @@ var Pinbox = (function(exports) {
|
|
|
2025
2251
|
doneTab.classList.toggle("on", tab === "resolved");
|
|
2026
2252
|
}
|
|
2027
2253
|
const list = tab === "open" ? open : resolved;
|
|
2028
|
-
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>";
|
|
2029
2255
|
if (itemsMemo !== html) {
|
|
2030
2256
|
items.innerHTML = html;
|
|
2031
2257
|
itemsMemo = html;
|
|
@@ -2058,87 +2284,23 @@ var Pinbox = (function(exports) {
|
|
|
2058
2284
|
};
|
|
2059
2285
|
}
|
|
2060
2286
|
//#endregion
|
|
2061
|
-
//#region src/ui/
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
* `spot` is a fraction of the element, so the pin still tracks the element when it moves or
|
|
2069
|
-
* resizes — it just stops sliding to the middle of a wide block the moment you commit it.
|
|
2070
|
-
*/
|
|
2071
|
-
function pinPoint(r, spot) {
|
|
2072
|
-
const fx = spot?.x ?? .5;
|
|
2073
|
-
const fy = spot?.y ?? .5;
|
|
2074
|
-
return {
|
|
2075
|
-
x: r.x + r.width * fx,
|
|
2076
|
-
y: r.y + r.height * fy
|
|
2077
|
-
};
|
|
2078
|
-
}
|
|
2079
|
-
/** Chip contents (prototype chipBtnInner, lines 546–550): number + linked-channel tag,
|
|
2080
|
-
* plus the queued badge while the pin waits in the outbox for the reconnect flush. */
|
|
2081
|
-
function chipInner(n, pin, queued = false) {
|
|
2082
|
-
const link = pin?.links?.[0];
|
|
2083
|
-
const badge = link ? `<span class="lk"><span>${esc(link.connector)}</span></span>` : "";
|
|
2084
|
-
const qd = queued ? "<span class=\"qd\">QUEUED</span>" : "";
|
|
2085
|
-
return `<span>${pinNumber(n)}</span>${badge}${qd}`;
|
|
2086
|
-
}
|
|
2087
|
-
function ensureNode(layer, key, fresh) {
|
|
2088
|
-
let node = layer.querySelector(`[data-pin="${key}"]`);
|
|
2089
|
-
if (!node) {
|
|
2090
|
-
node = layer.ownerDocument.createElement("div");
|
|
2091
|
-
node.className = "pb-pin";
|
|
2092
|
-
node.setAttribute("data-pin", key);
|
|
2093
|
-
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>`;
|
|
2094
|
-
layer.appendChild(node);
|
|
2095
|
-
}
|
|
2096
|
-
return node;
|
|
2097
|
-
}
|
|
2098
|
-
function patchNode(node, at, hot, inner) {
|
|
2099
|
-
node.style.left = `${at.x}px`;
|
|
2100
|
-
node.style.top = `${at.y}px`;
|
|
2101
|
-
node.style.zIndex = hot ? "40" : "20";
|
|
2102
|
-
node.classList.toggle("hot", hot);
|
|
2103
|
-
const chip = node.querySelector(".pb-chipBtn");
|
|
2104
|
-
if (chip && chipMemo.get(chip) !== inner) {
|
|
2105
|
-
chip.innerHTML = inner;
|
|
2106
|
-
chipMemo.set(chip, inner);
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
/**
|
|
2110
|
-
* Render the pin layer for a state snapshot. Visible pins are open pins, the
|
|
2111
|
-
* active pin regardless of status, and the client-only draft (key "draft").
|
|
2112
|
-
*/
|
|
2113
|
-
function renderPins(layer, state) {
|
|
2114
|
-
const visible = state.pins.filter((p) => p.status !== "resolved" || p.id === state.activePinId);
|
|
2115
|
-
const placed = [];
|
|
2116
|
-
visible.forEach((pin, i) => {
|
|
2117
|
-
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;
|
|
2118
2294
|
if (rect === void 0) return;
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
rect,
|
|
2128
|
-
spot
|
|
2129
|
-
});
|
|
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);
|
|
2130
2303
|
});
|
|
2131
|
-
const keys = new Set(placed.map((entry) => entry.pin.id));
|
|
2132
|
-
if (state.draft) keys.add("draft");
|
|
2133
|
-
for (const node of [...layer.children]) if (!keys.has(node.getAttribute("data-pin") ?? "")) node.remove();
|
|
2134
|
-
for (const { pin, n, rect, spot } of placed) {
|
|
2135
|
-
const node = ensureNode(layer, pin.id, false);
|
|
2136
|
-
const hot = pin.id === state.activePinId;
|
|
2137
|
-
const queued = state.queuedIds.has(pin.id);
|
|
2138
|
-
node.classList.toggle("queued", queued);
|
|
2139
|
-
patchNode(node, pinPoint(rect, spot), hot, chipInner(n, pin, queued));
|
|
2140
|
-
}
|
|
2141
|
-
if (state.draft) patchNode(ensureNode(layer, "draft", true), state.draft.placedAt, true, chipInner(visible.length + 1, null));
|
|
2142
2304
|
}
|
|
2143
2305
|
//#endregion
|
|
2144
2306
|
//#region src/ui/puck.ts
|
|
@@ -2146,7 +2308,9 @@ var Pinbox = (function(exports) {
|
|
|
2146
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>";
|
|
2147
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>";
|
|
2148
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>";
|
|
2149
|
-
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>";
|
|
2150
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>";
|
|
2151
2315
|
function fanItem(act, index, icon, label, key) {
|
|
2152
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>`;
|
|
@@ -2162,7 +2326,7 @@ var Pinbox = (function(exports) {
|
|
|
2162
2326
|
fan.className = "pb-fan";
|
|
2163
2327
|
fan.hidden = true;
|
|
2164
2328
|
fan.setAttribute("role", "menu");
|
|
2165
|
-
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");
|
|
2166
2330
|
const morphWrap = doc.createElement("div");
|
|
2167
2331
|
morphWrap.className = "pb-morph-wrap";
|
|
2168
2332
|
morphWrap.hidden = true;
|
|
@@ -2178,6 +2342,9 @@ var Pinbox = (function(exports) {
|
|
|
2178
2342
|
carrier.querySelector("[data-ref=\"count\"]"),
|
|
2179
2343
|
fan.querySelector("[data-ref=\"count\"]")
|
|
2180
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;
|
|
2181
2348
|
return {
|
|
2182
2349
|
puck,
|
|
2183
2350
|
fan,
|
|
@@ -2193,6 +2360,13 @@ var Pinbox = (function(exports) {
|
|
|
2193
2360
|
const degraded = state.connection === "offline" || state.connection === "incompatible";
|
|
2194
2361
|
puck.classList.toggle("degraded", degraded);
|
|
2195
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
|
+
}
|
|
2196
2370
|
}
|
|
2197
2371
|
};
|
|
2198
2372
|
}
|
|
@@ -2252,6 +2426,7 @@ var Pinbox = (function(exports) {
|
|
|
2252
2426
|
["Send comment", "⌘ ↵"],
|
|
2253
2427
|
["Mark pin resolved", "R"],
|
|
2254
2428
|
["Copy open pins", "C"],
|
|
2429
|
+
["Hide / show pins", "H"],
|
|
2255
2430
|
["Minimize toolbar", "M"],
|
|
2256
2431
|
["Cancel", "ESC"]
|
|
2257
2432
|
];
|
|
@@ -2495,6 +2670,10 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2495
2670
|
.pb-fan.down { --fan-from: translateY(-16px); }
|
|
2496
2671
|
.pb-fan.on .pb-fan-item { opacity: 1; transform: none; }
|
|
2497
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; }
|
|
2498
2677
|
.pb-fan-item .badge { pointer-events: none; }
|
|
2499
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); }
|
|
2500
2679
|
.pb-fan.labels-right .fl { left: calc(100% + 10px); transform: translateY(-50%) translateX(-6px); }
|
|
@@ -2559,15 +2738,20 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2559
2738
|
#modal = null;
|
|
2560
2739
|
#minUi = null;
|
|
2561
2740
|
#min = null;
|
|
2741
|
+
/** SPA view watcher: DOM/history changes re-run the anchor-gated render. */
|
|
2742
|
+
#anchors = null;
|
|
2562
2743
|
#helpOpen = false;
|
|
2563
2744
|
#pageStyle = null;
|
|
2564
2745
|
#unsubscribe = null;
|
|
2565
2746
|
#hover = null;
|
|
2747
|
+
/** Shift+click accumulation while placing — extra loci for ONE pending pin. */
|
|
2748
|
+
#extraTargets = [];
|
|
2566
2749
|
/** Card → element: send/verify/resolve forward to the transport seam; close dismisses. */
|
|
2567
2750
|
#cardActions = {
|
|
2568
2751
|
send: (pinId, text) => this.actions.send?.(pinId, text),
|
|
2569
2752
|
verify: (pinId, outcome) => this.actions.verify?.(pinId, outcome),
|
|
2570
2753
|
resolve: (pinId) => this.actions.resolve?.(pinId),
|
|
2754
|
+
copy: (pinId) => this.#copyPin(pinId),
|
|
2571
2755
|
close: () => this.#dismiss()
|
|
2572
2756
|
};
|
|
2573
2757
|
/** Programmatic path (Pinbox.init). The snippet path reads hub/token attributes. */
|
|
@@ -2600,6 +2784,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2600
2784
|
this.#pageStyle = style;
|
|
2601
2785
|
if (this.#aim === null) this.#mountAim();
|
|
2602
2786
|
if (this.#min === null) this.#mountMinimize();
|
|
2787
|
+
this.#anchors = watchAnchors(window, () => this.#render(this.store.get()));
|
|
2603
2788
|
document.addEventListener("mousemove", this.#onMouseMove);
|
|
2604
2789
|
window.addEventListener("scroll", this.#onViewportChange, { passive: true });
|
|
2605
2790
|
window.addEventListener("resize", this.#onViewportChange);
|
|
@@ -2647,6 +2832,9 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2647
2832
|
this.#aim = null;
|
|
2648
2833
|
this.#min?.destroy();
|
|
2649
2834
|
this.#min = null;
|
|
2835
|
+
releaseCapture();
|
|
2836
|
+
this.#anchors?.destroy();
|
|
2837
|
+
this.#anchors = null;
|
|
2650
2838
|
this.#unsubscribe?.();
|
|
2651
2839
|
this.#unsubscribe = null;
|
|
2652
2840
|
this.#pageStyle?.remove();
|
|
@@ -2736,6 +2924,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2736
2924
|
onFanAction: (action) => {
|
|
2737
2925
|
if (action === "pin") this.#togglePlacing();
|
|
2738
2926
|
else if (action === "inbox") this.#toggleInbox();
|
|
2927
|
+
else if (action === "hide") this.#togglePinsHidden();
|
|
2739
2928
|
else this.#toggleTheme();
|
|
2740
2929
|
}
|
|
2741
2930
|
});
|
|
@@ -2863,6 +3052,18 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2863
3052
|
navigator.clipboard.writeText(pinsToMarkdown(state.pins, state.threads));
|
|
2864
3053
|
} catch {}
|
|
2865
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
|
+
}
|
|
2866
3067
|
#setHelp(open) {
|
|
2867
3068
|
this.#helpOpen = open;
|
|
2868
3069
|
this.#modal?.set(open);
|
|
@@ -2880,9 +3081,13 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2880
3081
|
}
|
|
2881
3082
|
#togglePlacing() {
|
|
2882
3083
|
const placing = this.store.get().mode === "placing";
|
|
2883
|
-
this.store.update({
|
|
2884
|
-
mode:
|
|
3084
|
+
this.store.update(placing ? {
|
|
3085
|
+
mode: "idle",
|
|
2885
3086
|
activePinId: null
|
|
3087
|
+
} : {
|
|
3088
|
+
mode: "placing",
|
|
3089
|
+
activePinId: null,
|
|
3090
|
+
pinsHidden: false
|
|
2886
3091
|
});
|
|
2887
3092
|
}
|
|
2888
3093
|
#toggleInbox() {
|
|
@@ -2899,6 +3104,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2899
3104
|
/** esc / click-away: leave placing, discard the draft (client-only), deactivate. */
|
|
2900
3105
|
#dismiss() {
|
|
2901
3106
|
this.#setHelp(false);
|
|
3107
|
+
this.#clearExtraTargets();
|
|
2902
3108
|
this.store.update({
|
|
2903
3109
|
mode: "idle",
|
|
2904
3110
|
activePinId: null
|
|
@@ -2964,12 +3170,14 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2964
3170
|
#placeDraft(e) {
|
|
2965
3171
|
e.preventDefault();
|
|
2966
3172
|
e.stopPropagation();
|
|
2967
|
-
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();
|
|
2968
3179
|
this.store.place({
|
|
2969
|
-
target:
|
|
2970
|
-
x: e.pageX,
|
|
2971
|
-
y: e.pageY
|
|
2972
|
-
} }),
|
|
3180
|
+
target: capture,
|
|
2973
3181
|
placedAt: {
|
|
2974
3182
|
x: e.pageX,
|
|
2975
3183
|
y: e.pageY
|
|
@@ -2977,11 +3185,27 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2977
3185
|
});
|
|
2978
3186
|
this.#reticle?.release();
|
|
2979
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
|
+
}
|
|
2980
3202
|
#onClickCapture = (e) => {
|
|
2981
3203
|
if (e.composedPath().includes(this)) return;
|
|
2982
3204
|
const state = this.store.get();
|
|
2983
3205
|
if (state.mode === "placing") {
|
|
2984
|
-
if (
|
|
3206
|
+
if (needsDragAim(window)) return;
|
|
3207
|
+
if (e.shiftKey) this.#accumulateTarget(e);
|
|
3208
|
+
else this.#placeDraft(e);
|
|
2985
3209
|
return;
|
|
2986
3210
|
}
|
|
2987
3211
|
if (state.inboxOpen) this.store.update({ inboxOpen: false });
|
|
@@ -2998,6 +3222,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2998
3222
|
d: () => this.#toggleTheme(),
|
|
2999
3223
|
r: () => this.#resolveActive(),
|
|
3000
3224
|
c: () => this.#copyOpenPins(),
|
|
3225
|
+
h: () => this.#togglePinsHidden(),
|
|
3001
3226
|
m: () => this.#min?.minimized() === true ? this.restore(true) : this.minimize(true),
|
|
3002
3227
|
"?": () => this.#toggleHelp()
|
|
3003
3228
|
};
|
|
@@ -3030,6 +3255,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
3030
3255
|
const placing = state.mode === "placing";
|
|
3031
3256
|
this.toggleAttribute("data-placing", placing);
|
|
3032
3257
|
document.body.classList.toggle(PAGE_PLACING_CLASS, placing);
|
|
3258
|
+
if (!placing) this.#clearExtraTargets();
|
|
3033
3259
|
if (!placing) this.#reticle?.release();
|
|
3034
3260
|
this.#syncAim(placing);
|
|
3035
3261
|
if (this.#pinsLayer) renderPins(this.#pinsLayer, state);
|