@autono/pinbox-toolbar 0.20.0 → 0.21.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-xNXeJ2mR.d.ts → index-CMSOhoVs.d.ts} +4 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/model.d.ts +1 -1
- package/dist/{options-CNqXqkp9.d.ts → options-DPsmmgra.d.ts} +4 -0
- package/dist/plugins/next.d.ts +1 -1
- package/dist/plugins/vite.d.ts +1 -1
- package/dist/plugins/vite.js +61 -5
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{src-DnAkx29T.js → src-B134WXZ8.js} +322 -29
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +2 -2
- package/dist/toolbar.iife.js +143 -28
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +1 -1
- package/package.json +2 -2
|
@@ -150,6 +150,9 @@ declare class PinboxToolbarElement extends BaseElement {
|
|
|
150
150
|
restore(keyboard?: boolean): void;
|
|
151
151
|
}
|
|
152
152
|
//#endregion
|
|
153
|
+
//#region src/previews.d.ts
|
|
154
|
+
declare function mountPreviewSwitcher(host: HTMLElement, endpoint: string): void;
|
|
155
|
+
//#endregion
|
|
153
156
|
//#region src/transport/mirror.d.ts
|
|
154
157
|
interface StorageLike {
|
|
155
158
|
getItem(key: string): string | null;
|
|
@@ -283,4 +286,4 @@ declare const Pinbox: {
|
|
|
283
286
|
init(config: PinboxConfig): PinboxToolbarElement;
|
|
284
287
|
};
|
|
285
288
|
//#endregion
|
|
286
|
-
export {
|
|
289
|
+
export { CaptureResult as S, appendThreadMessage as _, HubEvent as a, deriveUiStatus as b, WebSocketLike as c, mountPreviewSwitcher as d, PinboxToolbarElement as f, UiStatus as g, ToolbarState as h, ConnectionState as i, HubError as l, Store as m, PinboxConfig as n, HubTransport as o, Draft as p, defineToolbarElement as r, TransportOptions as s, Pinbox as t, StorageLike as u, applyHubEvent as v, upsertPin as x, createStore as y };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { type CaptureResult, type ConnectionState, type Draft, HubError, type HubEvent, HubTransport, Pinbox, PinboxConfig, PinboxToolbarElement, type StorageLike, type Store, type ToolbarState, type TransportOptions, type UiStatus, type WebSocketLike, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, upsertPin };
|
|
1
|
+
import { S as CaptureResult, _ as appendThreadMessage, a as HubEvent, b as deriveUiStatus, c as WebSocketLike, d as mountPreviewSwitcher, f as PinboxToolbarElement, g as UiStatus, h as ToolbarState, i as ConnectionState, l as HubError, m as Store, n as PinboxConfig, o as HubTransport, p as Draft, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as applyHubEvent, x as upsertPin, y as createStore } from "./index-CMSOhoVs.js";
|
|
2
|
+
export { type CaptureResult, type ConnectionState, type Draft, HubError, type HubEvent, HubTransport, Pinbox, PinboxConfig, PinboxToolbarElement, type StorageLike, type Store, type ToolbarState, type TransportOptions, type UiStatus, type WebSocketLike, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, mountPreviewSwitcher, upsertPin };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { HubError, HubTransport, Pinbox, PinboxToolbarElement, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, upsertPin };
|
|
1
|
+
import { a as HubTransport, c as applyHubEvent, d as upsertPin, i as PinboxToolbarElement, l as createStore, n as defineToolbarElement, o as HubError, r as mountPreviewSwitcher, s as appendThreadMessage, t as Pinbox, u as deriveUiStatus } from "./src-B134WXZ8.js";
|
|
2
|
+
export { HubError, HubTransport, Pinbox, PinboxToolbarElement, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, mountPreviewSwitcher, upsertPin };
|
package/dist/model.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as PinboxToolbarElement } from "./index-CMSOhoVs.js";
|
|
2
2
|
import { ModelAnchor, ModelAnchor as ModelAnchor$1, Pin } from "@autono/pinbox-core/schema";
|
|
3
3
|
//#region src/model-target.d.ts
|
|
4
4
|
type ModelProjection = (anchor: ModelAnchor$1) => {
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
interface PinboxPluginOptions {
|
|
4
4
|
/** Explicit hub base URL. When set, `.pinbox/server.json` discovery is skipped entirely. */
|
|
5
5
|
hub?: string;
|
|
6
|
+
/** Vite only: show registered local worktree previews. Opt-in, dev-only. */
|
|
7
|
+
previews?: boolean;
|
|
8
|
+
/** Local CLI executable used for preview discovery. Defaults to pinbox on PATH. */
|
|
9
|
+
previewExecutable?: string;
|
|
6
10
|
/** Directory containing `.pinbox/server.json`. Defaults to `process.cwd()`. */
|
|
7
11
|
projectRoot?: string;
|
|
8
12
|
/** Spawn `pinbox serve` when no healthy hub is found. Defaults to true. */
|
package/dist/plugins/next.d.ts
CHANGED
package/dist/plugins/vite.d.ts
CHANGED
package/dist/plugins/vite.js
CHANGED
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
import { a as readHubToken, i as ensureHub, n as VIRTUAL_ID, r as resolveOptions, t as RESOLVED_VIRTUAL_ID } from "../options-DSrJgbJh.js";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { realpathSync } from "node:fs";
|
|
5
|
+
//#region src/plugins/previews.ts
|
|
6
|
+
const PREVIEWS_PATH = "/__pinbox_previews";
|
|
7
|
+
function servePreviews(server, cwd, executable) {
|
|
8
|
+
server.middlewares.use((req, res, next) => {
|
|
9
|
+
const path = req.url?.split("?")[0];
|
|
10
|
+
if (path !== "/__pinbox_previews" && path !== `/__pinbox_previews/identity`) return next();
|
|
11
|
+
res.setHeader("Content-Type", "application/json");
|
|
12
|
+
res.setHeader("Cache-Control", "no-store");
|
|
13
|
+
if (req.method !== "GET" || req.headers["sec-fetch-site"] === "cross-site") {
|
|
14
|
+
res.statusCode = 403;
|
|
15
|
+
res.end(JSON.stringify({
|
|
16
|
+
ok: false,
|
|
17
|
+
error: { message: "same-origin GET required" }
|
|
18
|
+
}));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (path === `/__pinbox_previews/identity`) {
|
|
22
|
+
res.end(JSON.stringify({ id: createHash("sha256").update(realpathSync(cwd)).digest("hex").slice(0, 12) }));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
execFile(executable, [
|
|
26
|
+
"preview",
|
|
27
|
+
"list",
|
|
28
|
+
"--json"
|
|
29
|
+
], {
|
|
30
|
+
cwd,
|
|
31
|
+
timeout: 6e3,
|
|
32
|
+
maxBuffer: 1048576
|
|
33
|
+
}, (error, stdout) => {
|
|
34
|
+
if (error) {
|
|
35
|
+
res.statusCode = 503;
|
|
36
|
+
res.end(JSON.stringify({
|
|
37
|
+
ok: false,
|
|
38
|
+
error: { message: "Preview discovery unavailable. Update the Pinbox CLI." }
|
|
39
|
+
}));
|
|
40
|
+
} else try {
|
|
41
|
+
JSON.parse(stdout);
|
|
42
|
+
res.end(stdout);
|
|
43
|
+
} catch {
|
|
44
|
+
res.statusCode = 503;
|
|
45
|
+
res.end("{\"ok\":false}");
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
2
51
|
//#region src/plugins/snippet.ts
|
|
3
52
|
/**
|
|
4
53
|
* Build the module source injected into the dev page.
|
|
@@ -24,14 +73,17 @@ import { a as readHubToken, i as ensureHub, n as VIRTUAL_ID, r as resolveOptions
|
|
|
24
73
|
* this import was dropped for real. `src/build.test.ts` bundles this exact import with Vite and
|
|
25
74
|
* asserts `customElements.define` survives, because statting dist/ cannot see that class of break.
|
|
26
75
|
*/
|
|
27
|
-
function buildBootstrap(hubUrl, token) {
|
|
76
|
+
function buildBootstrap(hubUrl, token, previews) {
|
|
28
77
|
const hub = JSON.stringify(hubUrl);
|
|
78
|
+
const tag = JSON.stringify("pinbox-toolbar");
|
|
79
|
+
const tokenLiteral = token === void 0 ? "null" : JSON.stringify(token);
|
|
29
80
|
return `// pinbox dev toolbar — injected by @autono/pinbox-toolbar (dev only)
|
|
30
81
|
import "@autono/pinbox-toolbar";
|
|
82
|
+
${previews ? "import { mountPreviewSwitcher } from \"@autono/pinbox-toolbar\";" : ""}
|
|
31
83
|
|
|
32
|
-
const TAG = ${
|
|
84
|
+
const TAG = ${tag};
|
|
33
85
|
const HUB = ${hub};
|
|
34
|
-
const TOKEN = ${
|
|
86
|
+
const TOKEN = ${tokenLiteral};
|
|
35
87
|
|
|
36
88
|
function mount() {
|
|
37
89
|
let el = document.querySelector(TAG);
|
|
@@ -44,6 +96,7 @@ function mount() {
|
|
|
44
96
|
if (TOKEN === null) el.removeAttribute("token");
|
|
45
97
|
else el.setAttribute("token", TOKEN);
|
|
46
98
|
if (created) document.body.appendChild(el);
|
|
99
|
+
${previews ? `mountPreviewSwitcher(el, ${JSON.stringify(previews)});` : ""}
|
|
47
100
|
}
|
|
48
101
|
|
|
49
102
|
if (document.readyState === "loading") {
|
|
@@ -63,6 +116,8 @@ const PLUGIN_NAME = "pinbox";
|
|
|
63
116
|
*/
|
|
64
117
|
function pinbox(options) {
|
|
65
118
|
const resolved = resolveOptions(options);
|
|
119
|
+
const previews = options?.previews ?? process.env["PINBOX_PREVIEWS"] === "1";
|
|
120
|
+
const previewExecutable = options?.previewExecutable ?? process.env["PINBOX_PREVIEW_EXECUTABLE"] ?? "pinbox";
|
|
66
121
|
if (resolved.disabled) return { name: PLUGIN_NAME };
|
|
67
122
|
let hubPromise;
|
|
68
123
|
function hub() {
|
|
@@ -72,7 +127,8 @@ function pinbox(options) {
|
|
|
72
127
|
return {
|
|
73
128
|
name: PLUGIN_NAME,
|
|
74
129
|
apply: "serve",
|
|
75
|
-
async configureServer() {
|
|
130
|
+
async configureServer(server) {
|
|
131
|
+
if (previews) servePreviews(server, resolved.projectRoot, previewExecutable);
|
|
76
132
|
await hub();
|
|
77
133
|
},
|
|
78
134
|
resolveId(id) {
|
|
@@ -83,7 +139,7 @@ function pinbox(options) {
|
|
|
83
139
|
if (id !== "\0virtual:pinbox-toolbar") return null;
|
|
84
140
|
const status = await hub();
|
|
85
141
|
if (status === void 0) return "// pinbox: hub unavailable, toolbar not mounted.\nexport {};\n";
|
|
86
|
-
return buildBootstrap(status.url, readHubToken(resolved.projectRoot));
|
|
142
|
+
return buildBootstrap(status.url, readHubToken(resolved.projectRoot), previews ? PREVIEWS_PATH : void 0);
|
|
87
143
|
},
|
|
88
144
|
transformIndexHtml: {
|
|
89
145
|
order: "pre",
|
package/dist/react.d.ts
CHANGED
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as PinboxToolbarElement, n as defineToolbarElement } from "./src-B134WXZ8.js";
|
|
2
2
|
import { createElement, useEffect, useRef } from "react";
|
|
3
3
|
//#region src/react.ts
|
|
4
4
|
/**
|
|
@@ -793,6 +793,125 @@ function createMinimize(host) {
|
|
|
793
793
|
};
|
|
794
794
|
}
|
|
795
795
|
//#endregion
|
|
796
|
+
//#region src/reveal-settle.ts
|
|
797
|
+
const pending = /* @__PURE__ */ new WeakMap();
|
|
798
|
+
function cancelRevealSettle(doc) {
|
|
799
|
+
pending.get(doc)?.();
|
|
800
|
+
}
|
|
801
|
+
function settlePinReveal(doc, reveal) {
|
|
802
|
+
cancelRevealSettle(doc);
|
|
803
|
+
const win = doc.defaultView;
|
|
804
|
+
if (!win) return;
|
|
805
|
+
let attempts = 0;
|
|
806
|
+
let timer = 0;
|
|
807
|
+
const events = [
|
|
808
|
+
"wheel",
|
|
809
|
+
"touchstart",
|
|
810
|
+
"pointerdown",
|
|
811
|
+
"keydown"
|
|
812
|
+
];
|
|
813
|
+
const stop = () => {
|
|
814
|
+
win.clearTimeout(timer);
|
|
815
|
+
for (const event of events) doc.removeEventListener(event, stop, true);
|
|
816
|
+
pending.delete(doc);
|
|
817
|
+
};
|
|
818
|
+
for (const event of events) doc.addEventListener(event, stop, {
|
|
819
|
+
capture: true,
|
|
820
|
+
passive: true
|
|
821
|
+
});
|
|
822
|
+
const tick = () => {
|
|
823
|
+
reveal();
|
|
824
|
+
if (++attempts >= 10) stop();
|
|
825
|
+
else timer = win.setTimeout(tick, 150);
|
|
826
|
+
};
|
|
827
|
+
pending.set(doc, stop);
|
|
828
|
+
timer = win.setTimeout(tick, 150);
|
|
829
|
+
}
|
|
830
|
+
//#endregion
|
|
831
|
+
//#region src/pin-reveal.ts
|
|
832
|
+
/** Query ordering is not a view change; repeated parameter ordering remains intact. */
|
|
833
|
+
function samePinView(win, value) {
|
|
834
|
+
if (!value) return true;
|
|
835
|
+
try {
|
|
836
|
+
const target = new URL(value, win.location.href);
|
|
837
|
+
const current = new URL(win.location.href);
|
|
838
|
+
target.searchParams.sort();
|
|
839
|
+
current.searchParams.sort();
|
|
840
|
+
return target.pathname === current.pathname && target.search === current.search;
|
|
841
|
+
} catch {
|
|
842
|
+
return true;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function pagePin(pin) {
|
|
846
|
+
return pin.target?.selector === "html" || pin.target?.selector === "body";
|
|
847
|
+
}
|
|
848
|
+
/** Resolve before clipping: an offscreen row is precisely the target we need to scroll. */
|
|
849
|
+
function scrollToPin(doc, pin) {
|
|
850
|
+
if (pin.target?.model || pagePin(pin)) return false;
|
|
851
|
+
const win = doc.defaultView;
|
|
852
|
+
if (!win || !samePinView(win, pin.target?.url) || !pin.target?.selector) return false;
|
|
853
|
+
try {
|
|
854
|
+
const element = doc.querySelector(pin.target.selector);
|
|
855
|
+
if (!element?.getClientRects().length) return false;
|
|
856
|
+
element.scrollIntoView({
|
|
857
|
+
behavior: "instant",
|
|
858
|
+
block: "center",
|
|
859
|
+
inline: "center"
|
|
860
|
+
});
|
|
861
|
+
return true;
|
|
862
|
+
} catch {
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const key = (endpoint) => `pinbox:${endpoint}:reveal`;
|
|
867
|
+
/** Returns true when leaving this document. Never navigates to a different origin. */
|
|
868
|
+
function revealPin(doc, pin, endpoint) {
|
|
869
|
+
cancelRevealSettle(doc);
|
|
870
|
+
const win = doc.defaultView;
|
|
871
|
+
if (!win) return false;
|
|
872
|
+
try {
|
|
873
|
+
win.sessionStorage.removeItem(key(endpoint));
|
|
874
|
+
} catch {}
|
|
875
|
+
if (pagePin(pin) || pin.target?.model) return false;
|
|
876
|
+
if (pin.target?.url && !samePinView(win, pin.target.url)) try {
|
|
877
|
+
const target = new URL(pin.target.url, win.location.href);
|
|
878
|
+
if (target.origin !== win.location.origin || !["http:", "https:"].includes(target.protocol)) return false;
|
|
879
|
+
win.sessionStorage.setItem(key(endpoint), JSON.stringify({
|
|
880
|
+
id: pin.id,
|
|
881
|
+
expires: Date.now() + 3e4
|
|
882
|
+
}));
|
|
883
|
+
win.location.assign(target.href);
|
|
884
|
+
return true;
|
|
885
|
+
} catch {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
scrollToPin(doc, pin);
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
/** Called on pin updates and DOM changes: SPA content may arrive after the toolbar. */
|
|
892
|
+
function pendingPinReveal(doc, pins, endpoint) {
|
|
893
|
+
const win = doc.defaultView;
|
|
894
|
+
if (!win) return null;
|
|
895
|
+
try {
|
|
896
|
+
const raw = win.sessionStorage.getItem(key(endpoint));
|
|
897
|
+
if (!raw) return null;
|
|
898
|
+
const pending = JSON.parse(raw);
|
|
899
|
+
if (typeof pending.id !== "string" || !Number.isFinite(pending.expires) || pending.expires < Date.now()) {
|
|
900
|
+
win.sessionStorage.removeItem(key(endpoint));
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
const pin = pins.find((p) => p.id === pending.id);
|
|
904
|
+
if (!pin || !scrollToPin(doc, pin)) return null;
|
|
905
|
+
win.sessionStorage.removeItem(key(endpoint));
|
|
906
|
+
settlePinReveal(doc, () => {
|
|
907
|
+
scrollToPin(doc, pin);
|
|
908
|
+
});
|
|
909
|
+
return pin.id;
|
|
910
|
+
} catch {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
//#endregion
|
|
796
915
|
//#region src/ui/aim.ts
|
|
797
916
|
/**
|
|
798
917
|
* True when aiming has to be done by dragging rather than by pointing.
|
|
@@ -2385,20 +2504,6 @@ const chipMemo = /* @__PURE__ */ new WeakMap();
|
|
|
2385
2504
|
function nextOrdinal(pins) {
|
|
2386
2505
|
return Math.max(pins.length, ...pins.map((p) => p.n ?? 0)) + 1;
|
|
2387
2506
|
}
|
|
2388
|
-
/**
|
|
2389
|
-
* Does the pin's captured URL still describe the view on screen? Path + search
|
|
2390
|
-
* only — hashes are anchors, not views. An absent or unparseable URL never
|
|
2391
|
-
* gates: old pins (and CLI pins) keep rendering exactly as before.
|
|
2392
|
-
*/
|
|
2393
|
-
function sameView(win, url) {
|
|
2394
|
-
if (url === void 0) return true;
|
|
2395
|
-
try {
|
|
2396
|
-
const target = new URL(url, win.location.href);
|
|
2397
|
-
return target.pathname === win.location.pathname && target.search === win.location.search;
|
|
2398
|
-
} catch {
|
|
2399
|
-
return true;
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
2507
|
/** A stored (document-space) rect or point, in today's viewport. */
|
|
2403
2508
|
function toViewport(win, p) {
|
|
2404
2509
|
return {
|
|
@@ -2425,21 +2530,21 @@ function anchorRect(doc, pin) {
|
|
|
2425
2530
|
function targetRect(doc, target) {
|
|
2426
2531
|
if (target?.model) return projectModelTarget(doc, target.model);
|
|
2427
2532
|
const stored = target?.rect;
|
|
2428
|
-
if (stored === void 0) return null;
|
|
2429
2533
|
const win = doc.defaultView;
|
|
2430
|
-
if (win === null) return stored;
|
|
2431
|
-
if (!
|
|
2534
|
+
if (win === null) return stored ?? null;
|
|
2535
|
+
if (!samePinView(win, target?.url)) return null;
|
|
2432
2536
|
const selector = target?.selector;
|
|
2433
|
-
if (selector === void 0) return toViewport(win, stored);
|
|
2537
|
+
if (selector === void 0) return stored ? toViewport(win, stored) : null;
|
|
2538
|
+
if (!stored && (selector === "html" || selector === "body")) return null;
|
|
2434
2539
|
let el;
|
|
2435
2540
|
try {
|
|
2436
2541
|
el = doc.querySelector(selector);
|
|
2437
2542
|
} catch {
|
|
2438
|
-
return toViewport(win, stored);
|
|
2543
|
+
return stored ? toViewport(win, stored) : null;
|
|
2439
2544
|
}
|
|
2440
2545
|
if (el === null) return null;
|
|
2441
2546
|
const r = el.getBoundingClientRect();
|
|
2442
|
-
if (r.width <= 0 && r.height <= 0) return toViewport(win, stored);
|
|
2547
|
+
if (r.width <= 0 && r.height <= 0) return stored ? toViewport(win, stored) : null;
|
|
2443
2548
|
return clipToScrollAncestors(win, el, {
|
|
2444
2549
|
x: r.left,
|
|
2445
2550
|
y: r.top,
|
|
@@ -2753,6 +2858,7 @@ function anchorOf(root, pin, draft) {
|
|
|
2753
2858
|
* labels the card without claiming an element that was never captured.
|
|
2754
2859
|
*/
|
|
2755
2860
|
function labelOf(target) {
|
|
2861
|
+
if (target?.selector === "html" || target?.selector === "body") return "PAGE";
|
|
2756
2862
|
if (target?.model) return `3D · ${target.model.partId}`;
|
|
2757
2863
|
return target?.anchor ?? target?.tag?.toUpperCase() ?? "PIN";
|
|
2758
2864
|
}
|
|
@@ -3762,16 +3868,15 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
3762
3868
|
/** Inbox item click: activate the pin and scroll it into view (prototype line 700). */
|
|
3763
3869
|
#activateFromInbox(pinId) {
|
|
3764
3870
|
const pin = this.store.get().pins.find((p) => p.id === pinId);
|
|
3871
|
+
if (!pin) return;
|
|
3872
|
+
this.store.update({
|
|
3873
|
+
inboxOpen: false,
|
|
3874
|
+
activePinId: null,
|
|
3875
|
+
pinsHidden: false
|
|
3876
|
+
});
|
|
3877
|
+
if (revealPin(document, pin, this.config?.endpoint ?? "")) return;
|
|
3765
3878
|
this.#ensureThread(pinId);
|
|
3766
3879
|
this.store.update({ activePinId: pinId });
|
|
3767
|
-
const rect = pin === void 0 ? null : anchorRect(document, pin);
|
|
3768
|
-
if (rect) {
|
|
3769
|
-
const y = window.scrollY + rect.y + rect.height / 2;
|
|
3770
|
-
window.scrollTo({
|
|
3771
|
-
top: Math.max(0, y - window.innerHeight / 2),
|
|
3772
|
-
behavior: "smooth"
|
|
3773
|
-
});
|
|
3774
|
-
}
|
|
3775
3880
|
}
|
|
3776
3881
|
/**
|
|
3777
3882
|
* Nudge a stale pin: re-post the last human message as a new thread message. A watcher that
|
|
@@ -3920,6 +4025,16 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
3920
4025
|
});
|
|
3921
4026
|
};
|
|
3922
4027
|
#render(state) {
|
|
4028
|
+
const restored = pendingPinReveal(document, state.pins, this.config?.endpoint ?? "");
|
|
4029
|
+
if (restored) {
|
|
4030
|
+
this.#ensureThread(restored);
|
|
4031
|
+
this.store.update({
|
|
4032
|
+
activePinId: restored,
|
|
4033
|
+
inboxOpen: false,
|
|
4034
|
+
pinsHidden: false
|
|
4035
|
+
});
|
|
4036
|
+
return;
|
|
4037
|
+
}
|
|
3923
4038
|
const placing = state.mode === "placing";
|
|
3924
4039
|
this.toggleAttribute("data-placing", placing);
|
|
3925
4040
|
document.body.classList.toggle(PAGE_PLACING_CLASS, placing);
|
|
@@ -3932,6 +4047,184 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
3932
4047
|
}
|
|
3933
4048
|
};
|
|
3934
4049
|
//#endregion
|
|
4050
|
+
//#region src/ui/preview-styles.ts
|
|
4051
|
+
const PREVIEW_STYLES = `
|
|
4052
|
+
.pb-preview-wrap { display:flex; align-items:center; border-left:1px solid var(--pb-line); margin-left:4px; padding-left:4px; }
|
|
4053
|
+
.pb-preview-trigger { max-width:160px; gap:7px; }
|
|
4054
|
+
.pb-preview-trigger .name { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:110px; }
|
|
4055
|
+
.pb-preview-trigger svg { flex-shrink:0; }
|
|
4056
|
+
.pb-preview-menu { position:fixed; margin:0; padding:0; width:304px; max-width:calc(100vw - 24px); max-height:70vh; overflow:auto; background:var(--pb-surface); color:var(--pb-fg1); border:1px solid var(--pb-line-2); border-radius:4px; box-shadow:var(--pb-shadow); font-family:var(--pb-font-body); }
|
|
4057
|
+
.pb-preview-menu::backdrop { background:transparent; }
|
|
4058
|
+
.pb-preview-head { display:flex; align-items:center; justify-content:space-between; padding:10px 12px 8px 16px; border-bottom:1px solid var(--pb-line); }
|
|
4059
|
+
.pb-preview-head span { font:10px var(--pb-font-mono); letter-spacing:.18em; color:var(--pb-fg3); }
|
|
4060
|
+
.pb-preview-list { padding:5px; }
|
|
4061
|
+
.pb-preview-option { display:flex; align-items:center; gap:10px; width:100%; text-align:left; padding:11px 10px; border-radius:2px; color:var(--pb-fg2); }
|
|
4062
|
+
.pb-preview-option:hover, .pb-preview-option:focus-visible { background:var(--pb-hover); outline:1px solid var(--pb-line-2); }
|
|
4063
|
+
.pb-preview-option[aria-checked="true"] { background:var(--pb-amber-soft); color:var(--pb-fg1); }
|
|
4064
|
+
.pb-preview-option:disabled { opacity:.45; cursor:not-allowed; }
|
|
4065
|
+
.pb-preview-option .check { width:14px; flex-shrink:0; color:var(--pb-amber); }
|
|
4066
|
+
.pb-preview-option .copy { display:flex; flex-direction:column; gap:4px; min-width:0; }
|
|
4067
|
+
.pb-preview-option .title { font-size:12px; font-weight:500; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
4068
|
+
.pb-preview-option .branch { font:10px var(--pb-font-mono); color:var(--pb-fg3); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
4069
|
+
.pb-preview-foot { border-top:1px solid var(--pb-line); padding:11px 16px; display:flex; flex-direction:column; gap:8px; }
|
|
4070
|
+
.pb-preview-status { font-size:11px; line-height:1.5; color:var(--pb-fg3); }
|
|
4071
|
+
.pb-preview-foot a { font:10px var(--pb-font-mono); letter-spacing:.08em; color:var(--pb-amber); text-decoration:none; }
|
|
4072
|
+
`;
|
|
4073
|
+
//#endregion
|
|
4074
|
+
//#region src/ui/preview-switcher.ts
|
|
4075
|
+
const CHEVRON = icon("<path d=\"m4 6 4 4 4-4\"/>", 14);
|
|
4076
|
+
const REFRESH = icon("<path d=\"M2 8a6 6 0 0 1 10.5-4L14 6M14 2v4h-4M14 8a6 6 0 0 1-10.5 4L2 10M2 14v-4h4\"/>", 14);
|
|
4077
|
+
const BRANCH = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\"><circle cx=\"4\" cy=\"3\" r=\"1.5\"/><circle cx=\"4\" cy=\"13\" r=\"1.5\"/><circle cx=\"12\" cy=\"4\" r=\"1.5\"/><path d=\"M4 4.5v7M12 5.5v1a3 3 0 0 1-3 3H7a3 3 0 0 0-3 3\"/></svg>";
|
|
4078
|
+
function createPreviewView(doc, root, bar) {
|
|
4079
|
+
const style = doc.createElement("style");
|
|
4080
|
+
style.textContent = PREVIEW_STYLES;
|
|
4081
|
+
root.append(style);
|
|
4082
|
+
const wrap = doc.createElement("div");
|
|
4083
|
+
wrap.dataset["previewSwitcher"] = "";
|
|
4084
|
+
wrap.className = "pb-preview-wrap";
|
|
4085
|
+
wrap.innerHTML = `<button type="button" class="pb-tb pb-preview-trigger" aria-label="Switch preview" aria-haspopup="menu" aria-controls="pb-preview-menu" aria-expanded="false">${BRANCH}<span class="name">PREVIEW</span>${CHEVRON}</button>
|
|
4086
|
+
<div id="pb-preview-menu" class="pb-preview-menu" popover="auto" aria-label="Local previews">
|
|
4087
|
+
<div class="pb-preview-head"><span>LOCAL PREVIEWS</span><button type="button" class="pb-tb sq" aria-label="Refresh previews">${REFRESH}</button></div>
|
|
4088
|
+
<div class="pb-preview-list" role="menu" aria-label="Preview branch or worktree"></div>
|
|
4089
|
+
<div class="pb-preview-foot"><span class="pb-preview-status" role="status">Loading previews…</span><a target="_blank" rel="noopener noreferrer" hidden>OPEN PR ↗</a></div>
|
|
4090
|
+
</div>`;
|
|
4091
|
+
bar.append(wrap);
|
|
4092
|
+
const trigger = wrap.querySelector(".pb-preview-trigger");
|
|
4093
|
+
const panel = wrap.querySelector(".pb-preview-menu");
|
|
4094
|
+
const list = wrap.querySelector(".pb-preview-list");
|
|
4095
|
+
const reload = wrap.querySelector("[aria-label=\"Refresh previews\"]");
|
|
4096
|
+
const status = wrap.querySelector("[role=\"status\"]");
|
|
4097
|
+
const pr = wrap.querySelector("a");
|
|
4098
|
+
wirePopover(trigger, panel, list, doc);
|
|
4099
|
+
return {
|
|
4100
|
+
trigger,
|
|
4101
|
+
list,
|
|
4102
|
+
status,
|
|
4103
|
+
reload,
|
|
4104
|
+
render(choices) {
|
|
4105
|
+
renderOptions(doc, list, choices);
|
|
4106
|
+
const current = choices.find((p) => p.current);
|
|
4107
|
+
trigger.querySelector(".name").textContent = compactLabel(current);
|
|
4108
|
+
trigger.setAttribute("aria-label", `Switch preview: ${current?.label ?? "local previews"}`);
|
|
4109
|
+
trigger.title = current ? `${current.branch} · ${current.commit.slice(0, 8)}` : "Choose a preview";
|
|
4110
|
+
status.textContent = current?.backend || "Local frontend preview";
|
|
4111
|
+
pr.hidden = !current?.pr;
|
|
4112
|
+
if (current?.pr) pr.href = current.pr;
|
|
4113
|
+
}
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
function compactLabel(current) {
|
|
4117
|
+
if (!current) return "PREVIEW";
|
|
4118
|
+
return current.label.match(/^PR #\d+/)?.[0] ?? (current.branch === "main" ? "MAIN" : current.branch.replace(/^[^/]+\//, ""));
|
|
4119
|
+
}
|
|
4120
|
+
function renderOptions(doc, list, choices) {
|
|
4121
|
+
list.replaceChildren();
|
|
4122
|
+
for (const item of choices) {
|
|
4123
|
+
const button = doc.createElement("button");
|
|
4124
|
+
button.type = "button";
|
|
4125
|
+
button.className = "pb-preview-option";
|
|
4126
|
+
button.dataset["previewId"] = item.id;
|
|
4127
|
+
button.setAttribute("role", "menuitemradio");
|
|
4128
|
+
button.setAttribute("aria-checked", String(item.current));
|
|
4129
|
+
button.disabled = !item.ready;
|
|
4130
|
+
button.innerHTML = "<span class=\"check\" aria-hidden=\"true\"></span><span class=\"copy\"><span class=\"title\"></span><span class=\"branch\"></span></span>";
|
|
4131
|
+
button.querySelector(".check").textContent = item.current ? "✓" : "";
|
|
4132
|
+
button.querySelector(".title").textContent = item.label;
|
|
4133
|
+
button.querySelector(".branch").textContent = `${item.branch}${item.ready ? "" : " · unavailable"}`;
|
|
4134
|
+
list.append(button);
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
function wirePopover(trigger, panel, list, doc) {
|
|
4138
|
+
trigger.addEventListener("click", () => {
|
|
4139
|
+
panel.togglePopover();
|
|
4140
|
+
const rect = trigger.getBoundingClientRect();
|
|
4141
|
+
const width = doc.documentElement.clientWidth;
|
|
4142
|
+
panel.style.left = `${Math.max(12, Math.min(rect.right - 304, width - 316))}px`;
|
|
4143
|
+
panel.style.top = `${rect.top > panel.offsetHeight + 20 ? rect.top - panel.offsetHeight - 10 : rect.bottom + 10}px`;
|
|
4144
|
+
(list.querySelector("button[aria-checked=\"true\"]:not(:disabled)") ?? list.querySelector("button:not(:disabled)"))?.focus();
|
|
4145
|
+
});
|
|
4146
|
+
panel.addEventListener("toggle", () => trigger.setAttribute("aria-expanded", String(panel.matches(":popover-open"))));
|
|
4147
|
+
panel.addEventListener("keydown", (e) => {
|
|
4148
|
+
const buttons = [...list.querySelectorAll("button:not(:disabled)")];
|
|
4149
|
+
const index = buttons.indexOf(panel.getRootNode().activeElement);
|
|
4150
|
+
if ([
|
|
4151
|
+
"ArrowDown",
|
|
4152
|
+
"ArrowUp",
|
|
4153
|
+
"Home",
|
|
4154
|
+
"End"
|
|
4155
|
+
].includes(e.key)) {
|
|
4156
|
+
e.preventDefault();
|
|
4157
|
+
buttons[e.key === "Home" ? 0 : e.key === "End" ? buttons.length - 1 : (index + (e.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length]?.focus();
|
|
4158
|
+
}
|
|
4159
|
+
if (e.key === "Escape") {
|
|
4160
|
+
panel.hidePopover();
|
|
4161
|
+
trigger.focus();
|
|
4162
|
+
}
|
|
4163
|
+
});
|
|
4164
|
+
}
|
|
4165
|
+
//#endregion
|
|
4166
|
+
//#region src/previews.ts
|
|
4167
|
+
function previewDestination(origin, current) {
|
|
4168
|
+
const target = new URL(origin);
|
|
4169
|
+
const source = new URL(current);
|
|
4170
|
+
if (!["http:", "https:"].includes(target.protocol) || target.username || target.password || target.pathname !== "/" || target.search || target.hash) throw new Error("Invalid preview origin");
|
|
4171
|
+
target.pathname = source.pathname;
|
|
4172
|
+
target.search = source.search;
|
|
4173
|
+
target.hash = source.hash;
|
|
4174
|
+
return target.href;
|
|
4175
|
+
}
|
|
4176
|
+
function mountPreviewSwitcher(host, endpoint) {
|
|
4177
|
+
const root = host.shadowRoot;
|
|
4178
|
+
const bar = root?.querySelector(".pb-bar");
|
|
4179
|
+
if (!root || !bar || root.querySelector("[data-preview-switcher]")) return;
|
|
4180
|
+
const doc = host.ownerDocument;
|
|
4181
|
+
const { trigger, list, status, reload, render } = createPreviewView(doc, root, bar);
|
|
4182
|
+
let choices = [];
|
|
4183
|
+
let busy = false;
|
|
4184
|
+
const load = async () => {
|
|
4185
|
+
const response = await fetch(endpoint, {
|
|
4186
|
+
credentials: "same-origin",
|
|
4187
|
+
cache: "no-store"
|
|
4188
|
+
});
|
|
4189
|
+
const envelope = await response.json();
|
|
4190
|
+
if (!response.ok || !envelope.ok || !Array.isArray(envelope.data)) throw new Error("Preview discovery unavailable");
|
|
4191
|
+
return envelope.data;
|
|
4192
|
+
};
|
|
4193
|
+
async function refresh() {
|
|
4194
|
+
if (busy) return;
|
|
4195
|
+
busy = true;
|
|
4196
|
+
try {
|
|
4197
|
+
choices = await load();
|
|
4198
|
+
render(choices);
|
|
4199
|
+
trigger.disabled = false;
|
|
4200
|
+
} catch {
|
|
4201
|
+
status.textContent = "Preview discovery unavailable";
|
|
4202
|
+
trigger.disabled = false;
|
|
4203
|
+
} finally {
|
|
4204
|
+
busy = false;
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
reload.addEventListener("click", () => {
|
|
4208
|
+
refresh();
|
|
4209
|
+
});
|
|
4210
|
+
list.addEventListener("click", async (event) => {
|
|
4211
|
+
const target = event.target.closest("[data-preview-id]");
|
|
4212
|
+
const id = target?.dataset["previewId"];
|
|
4213
|
+
if (!id || target?.disabled) return;
|
|
4214
|
+
trigger.disabled = true;
|
|
4215
|
+
status.textContent = "Checking preview…";
|
|
4216
|
+
try {
|
|
4217
|
+
const destination = (await load()).find((p) => p.id === id);
|
|
4218
|
+
if (!destination?.ready || !destination.url) throw new Error("unavailable");
|
|
4219
|
+
doc.defaultView?.location.assign(previewDestination(destination.url, doc.location.href));
|
|
4220
|
+
} catch {
|
|
4221
|
+
status.textContent = "Preview unavailable. Refresh to try again.";
|
|
4222
|
+
trigger.disabled = false;
|
|
4223
|
+
}
|
|
4224
|
+
});
|
|
4225
|
+
refresh();
|
|
4226
|
+
}
|
|
4227
|
+
//#endregion
|
|
3935
4228
|
//#region src/index.ts
|
|
3936
4229
|
/** Register <pinbox-toolbar>; no-op outside a browser or when already defined. */
|
|
3937
4230
|
function defineToolbarElement() {
|
|
@@ -3947,4 +4240,4 @@ const Pinbox = { init(config) {
|
|
|
3947
4240
|
} };
|
|
3948
4241
|
defineToolbarElement();
|
|
3949
4242
|
//#endregion
|
|
3950
|
-
export {
|
|
4243
|
+
export { HubTransport as a, applyHubEvent as c, upsertPin as d, PinboxToolbarElement as i, createStore as l, defineToolbarElement as n, HubError as o, mountPreviewSwitcher as r, appendThreadMessage as s, Pinbox as t, deriveUiStatus as u };
|
package/dist/svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { S as CaptureResult, _ as appendThreadMessage, a as HubEvent, b as deriveUiStatus, c as WebSocketLike, d as mountPreviewSwitcher, f as PinboxToolbarElement, g as UiStatus, h as ToolbarState, i as ConnectionState, l as HubError, m as Store, n as PinboxConfig, o as HubTransport, p as Draft, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as applyHubEvent, x as upsertPin, y as createStore } from "./index-CMSOhoVs.js";
|
|
2
2
|
import { Action } from "svelte/action";
|
|
3
3
|
//#region src/svelte.d.ts
|
|
4
4
|
/**
|
|
@@ -8,4 +8,4 @@ import { Action } from "svelte/action";
|
|
|
8
8
|
*/
|
|
9
9
|
declare const pinbox: Action<HTMLElement, PinboxConfig>;
|
|
10
10
|
//#endregion
|
|
11
|
-
export { type CaptureResult, type ConnectionState, type Draft, HubError, type HubEvent, HubTransport, Pinbox, PinboxConfig, PinboxToolbarElement, type StorageLike, type Store, type ToolbarState, type TransportOptions, type UiStatus, type WebSocketLike, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, pinbox, upsertPin };
|
|
11
|
+
export { type CaptureResult, type ConnectionState, type Draft, HubError, type HubEvent, HubTransport, Pinbox, PinboxConfig, PinboxToolbarElement, type StorageLike, type Store, type ToolbarState, type TransportOptions, type UiStatus, type WebSocketLike, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, mountPreviewSwitcher, pinbox, upsertPin };
|
package/dist/svelte.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as HubTransport, c as applyHubEvent, d as upsertPin, i as PinboxToolbarElement, l as createStore, n as defineToolbarElement, o as HubError, r as mountPreviewSwitcher, s as appendThreadMessage, t as Pinbox, u as deriveUiStatus } from "./src-B134WXZ8.js";
|
|
2
2
|
//#region src/svelte.ts
|
|
3
3
|
/**
|
|
4
4
|
* `<div use:pinbox={{ endpoint }} />` — creates + configures the element BEFORE insertion
|
|
@@ -15,4 +15,4 @@ const pinbox = (_node, config) => {
|
|
|
15
15
|
} };
|
|
16
16
|
};
|
|
17
17
|
//#endregion
|
|
18
|
-
export { HubError, HubTransport, Pinbox, PinboxToolbarElement, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, pinbox, upsertPin };
|
|
18
|
+
export { HubError, HubTransport, Pinbox, PinboxToolbarElement, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, mountPreviewSwitcher, pinbox, upsertPin };
|
package/dist/toolbar.iife.js
CHANGED
|
@@ -839,6 +839,125 @@ var Pinbox = (function(exports) {
|
|
|
839
839
|
return true;
|
|
840
840
|
}
|
|
841
841
|
//#endregion
|
|
842
|
+
//#region src/reveal-settle.ts
|
|
843
|
+
const pending = /* @__PURE__ */ new WeakMap();
|
|
844
|
+
function cancelRevealSettle(doc) {
|
|
845
|
+
pending.get(doc)?.();
|
|
846
|
+
}
|
|
847
|
+
function settlePinReveal(doc, reveal) {
|
|
848
|
+
cancelRevealSettle(doc);
|
|
849
|
+
const win = doc.defaultView;
|
|
850
|
+
if (!win) return;
|
|
851
|
+
let attempts = 0;
|
|
852
|
+
let timer = 0;
|
|
853
|
+
const events = [
|
|
854
|
+
"wheel",
|
|
855
|
+
"touchstart",
|
|
856
|
+
"pointerdown",
|
|
857
|
+
"keydown"
|
|
858
|
+
];
|
|
859
|
+
const stop = () => {
|
|
860
|
+
win.clearTimeout(timer);
|
|
861
|
+
for (const event of events) doc.removeEventListener(event, stop, true);
|
|
862
|
+
pending.delete(doc);
|
|
863
|
+
};
|
|
864
|
+
for (const event of events) doc.addEventListener(event, stop, {
|
|
865
|
+
capture: true,
|
|
866
|
+
passive: true
|
|
867
|
+
});
|
|
868
|
+
const tick = () => {
|
|
869
|
+
reveal();
|
|
870
|
+
if (++attempts >= 10) stop();
|
|
871
|
+
else timer = win.setTimeout(tick, 150);
|
|
872
|
+
};
|
|
873
|
+
pending.set(doc, stop);
|
|
874
|
+
timer = win.setTimeout(tick, 150);
|
|
875
|
+
}
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/pin-reveal.ts
|
|
878
|
+
/** Query ordering is not a view change; repeated parameter ordering remains intact. */
|
|
879
|
+
function samePinView(win, value) {
|
|
880
|
+
if (!value) return true;
|
|
881
|
+
try {
|
|
882
|
+
const target = new URL(value, win.location.href);
|
|
883
|
+
const current = new URL(win.location.href);
|
|
884
|
+
target.searchParams.sort();
|
|
885
|
+
current.searchParams.sort();
|
|
886
|
+
return target.pathname === current.pathname && target.search === current.search;
|
|
887
|
+
} catch {
|
|
888
|
+
return true;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
function pagePin(pin) {
|
|
892
|
+
return pin.target?.selector === "html" || pin.target?.selector === "body";
|
|
893
|
+
}
|
|
894
|
+
/** Resolve before clipping: an offscreen row is precisely the target we need to scroll. */
|
|
895
|
+
function scrollToPin(doc, pin) {
|
|
896
|
+
if (pin.target?.model || pagePin(pin)) return false;
|
|
897
|
+
const win = doc.defaultView;
|
|
898
|
+
if (!win || !samePinView(win, pin.target?.url) || !pin.target?.selector) return false;
|
|
899
|
+
try {
|
|
900
|
+
const element = doc.querySelector(pin.target.selector);
|
|
901
|
+
if (!element?.getClientRects().length) return false;
|
|
902
|
+
element.scrollIntoView({
|
|
903
|
+
behavior: "instant",
|
|
904
|
+
block: "center",
|
|
905
|
+
inline: "center"
|
|
906
|
+
});
|
|
907
|
+
return true;
|
|
908
|
+
} catch {
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
const key = (endpoint) => `pinbox:${endpoint}:reveal`;
|
|
913
|
+
/** Returns true when leaving this document. Never navigates to a different origin. */
|
|
914
|
+
function revealPin(doc, pin, endpoint) {
|
|
915
|
+
cancelRevealSettle(doc);
|
|
916
|
+
const win = doc.defaultView;
|
|
917
|
+
if (!win) return false;
|
|
918
|
+
try {
|
|
919
|
+
win.sessionStorage.removeItem(key(endpoint));
|
|
920
|
+
} catch {}
|
|
921
|
+
if (pagePin(pin) || pin.target?.model) return false;
|
|
922
|
+
if (pin.target?.url && !samePinView(win, pin.target.url)) try {
|
|
923
|
+
const target = new URL(pin.target.url, win.location.href);
|
|
924
|
+
if (target.origin !== win.location.origin || !["http:", "https:"].includes(target.protocol)) return false;
|
|
925
|
+
win.sessionStorage.setItem(key(endpoint), JSON.stringify({
|
|
926
|
+
id: pin.id,
|
|
927
|
+
expires: Date.now() + 3e4
|
|
928
|
+
}));
|
|
929
|
+
win.location.assign(target.href);
|
|
930
|
+
return true;
|
|
931
|
+
} catch {
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
scrollToPin(doc, pin);
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
/** Called on pin updates and DOM changes: SPA content may arrive after the toolbar. */
|
|
938
|
+
function pendingPinReveal(doc, pins, endpoint) {
|
|
939
|
+
const win = doc.defaultView;
|
|
940
|
+
if (!win) return null;
|
|
941
|
+
try {
|
|
942
|
+
const raw = win.sessionStorage.getItem(key(endpoint));
|
|
943
|
+
if (!raw) return null;
|
|
944
|
+
const pending = JSON.parse(raw);
|
|
945
|
+
if (typeof pending.id !== "string" || !Number.isFinite(pending.expires) || pending.expires < Date.now()) {
|
|
946
|
+
win.sessionStorage.removeItem(key(endpoint));
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
const pin = pins.find((p) => p.id === pending.id);
|
|
950
|
+
if (!pin || !scrollToPin(doc, pin)) return null;
|
|
951
|
+
win.sessionStorage.removeItem(key(endpoint));
|
|
952
|
+
settlePinReveal(doc, () => {
|
|
953
|
+
scrollToPin(doc, pin);
|
|
954
|
+
});
|
|
955
|
+
return pin.id;
|
|
956
|
+
} catch {
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
//#endregion
|
|
842
961
|
//#region src/targeting/dom.ts
|
|
843
962
|
/**
|
|
844
963
|
* Deepest element under (clientX, clientY) that the caller does not ignore, or null when there is
|
|
@@ -2695,20 +2814,6 @@ var Pinbox = (function(exports) {
|
|
|
2695
2814
|
function nextOrdinal(pins) {
|
|
2696
2815
|
return Math.max(pins.length, ...pins.map((p) => p.n ?? 0)) + 1;
|
|
2697
2816
|
}
|
|
2698
|
-
/**
|
|
2699
|
-
* Does the pin's captured URL still describe the view on screen? Path + search
|
|
2700
|
-
* only — hashes are anchors, not views. An absent or unparseable URL never
|
|
2701
|
-
* gates: old pins (and CLI pins) keep rendering exactly as before.
|
|
2702
|
-
*/
|
|
2703
|
-
function sameView(win, url) {
|
|
2704
|
-
if (url === void 0) return true;
|
|
2705
|
-
try {
|
|
2706
|
-
const target = new URL(url, win.location.href);
|
|
2707
|
-
return target.pathname === win.location.pathname && target.search === win.location.search;
|
|
2708
|
-
} catch {
|
|
2709
|
-
return true;
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
2817
|
/** A stored (document-space) rect or point, in today's viewport. */
|
|
2713
2818
|
function toViewport(win, p) {
|
|
2714
2819
|
return {
|
|
@@ -2735,21 +2840,21 @@ var Pinbox = (function(exports) {
|
|
|
2735
2840
|
function targetRect(doc, target) {
|
|
2736
2841
|
if (target?.model) return projectModelTarget(doc, target.model);
|
|
2737
2842
|
const stored = target?.rect;
|
|
2738
|
-
if (stored === void 0) return null;
|
|
2739
2843
|
const win = doc.defaultView;
|
|
2740
|
-
if (win === null) return stored;
|
|
2741
|
-
if (!
|
|
2844
|
+
if (win === null) return stored ?? null;
|
|
2845
|
+
if (!samePinView(win, target?.url)) return null;
|
|
2742
2846
|
const selector = target?.selector;
|
|
2743
|
-
if (selector === void 0) return toViewport(win, stored);
|
|
2847
|
+
if (selector === void 0) return stored ? toViewport(win, stored) : null;
|
|
2848
|
+
if (!stored && (selector === "html" || selector === "body")) return null;
|
|
2744
2849
|
let el;
|
|
2745
2850
|
try {
|
|
2746
2851
|
el = doc.querySelector(selector);
|
|
2747
2852
|
} catch {
|
|
2748
|
-
return toViewport(win, stored);
|
|
2853
|
+
return stored ? toViewport(win, stored) : null;
|
|
2749
2854
|
}
|
|
2750
2855
|
if (el === null) return null;
|
|
2751
2856
|
const r = el.getBoundingClientRect();
|
|
2752
|
-
if (r.width <= 0 && r.height <= 0) return toViewport(win, stored);
|
|
2857
|
+
if (r.width <= 0 && r.height <= 0) return stored ? toViewport(win, stored) : null;
|
|
2753
2858
|
return clipToScrollAncestors(win, el, {
|
|
2754
2859
|
x: r.left,
|
|
2755
2860
|
y: r.top,
|
|
@@ -3063,6 +3168,7 @@ var Pinbox = (function(exports) {
|
|
|
3063
3168
|
* labels the card without claiming an element that was never captured.
|
|
3064
3169
|
*/
|
|
3065
3170
|
function labelOf(target) {
|
|
3171
|
+
if (target?.selector === "html" || target?.selector === "body") return "PAGE";
|
|
3066
3172
|
if (target?.model) return `3D · ${target.model.partId}`;
|
|
3067
3173
|
return target?.anchor ?? target?.tag?.toUpperCase() ?? "PIN";
|
|
3068
3174
|
}
|
|
@@ -4072,16 +4178,15 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
4072
4178
|
/** Inbox item click: activate the pin and scroll it into view (prototype line 700). */
|
|
4073
4179
|
#activateFromInbox(pinId) {
|
|
4074
4180
|
const pin = this.store.get().pins.find((p) => p.id === pinId);
|
|
4181
|
+
if (!pin) return;
|
|
4182
|
+
this.store.update({
|
|
4183
|
+
inboxOpen: false,
|
|
4184
|
+
activePinId: null,
|
|
4185
|
+
pinsHidden: false
|
|
4186
|
+
});
|
|
4187
|
+
if (revealPin(document, pin, this.config?.endpoint ?? "")) return;
|
|
4075
4188
|
this.#ensureThread(pinId);
|
|
4076
4189
|
this.store.update({ activePinId: pinId });
|
|
4077
|
-
const rect = pin === void 0 ? null : anchorRect(document, pin);
|
|
4078
|
-
if (rect) {
|
|
4079
|
-
const y = window.scrollY + rect.y + rect.height / 2;
|
|
4080
|
-
window.scrollTo({
|
|
4081
|
-
top: Math.max(0, y - window.innerHeight / 2),
|
|
4082
|
-
behavior: "smooth"
|
|
4083
|
-
});
|
|
4084
|
-
}
|
|
4085
4190
|
}
|
|
4086
4191
|
/**
|
|
4087
4192
|
* Nudge a stale pin: re-post the last human message as a new thread message. A watcher that
|
|
@@ -4230,6 +4335,16 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
4230
4335
|
});
|
|
4231
4336
|
};
|
|
4232
4337
|
#render(state) {
|
|
4338
|
+
const restored = pendingPinReveal(document, state.pins, this.config?.endpoint ?? "");
|
|
4339
|
+
if (restored) {
|
|
4340
|
+
this.#ensureThread(restored);
|
|
4341
|
+
this.store.update({
|
|
4342
|
+
activePinId: restored,
|
|
4343
|
+
inboxOpen: false,
|
|
4344
|
+
pinsHidden: false
|
|
4345
|
+
});
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4233
4348
|
const placing = state.mode === "placing";
|
|
4234
4349
|
this.toggleAttribute("data-placing", placing);
|
|
4235
4350
|
document.body.classList.toggle(PAGE_PLACING_CLASS, placing);
|
package/dist/vue.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as PinboxToolbarElement, n as PinboxConfig } from "./index-CMSOhoVs.js";
|
|
2
2
|
import { VNode } from "vue";
|
|
3
3
|
//#region src/vue.d.ts
|
|
4
4
|
/** The slice of the Vue component instance this wrapper touches. */
|
package/dist/vue.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autono/pinbox-toolbar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
}
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
|
-
"@autono/pinbox-core": "0.
|
|
78
|
+
"@autono/pinbox-core": "0.21.0",
|
|
79
79
|
"@types/react": "^19.2.0",
|
|
80
80
|
"typescript": "^7.0.0",
|
|
81
81
|
"tsdown": "^0.22.0",
|