@loupekit/sdk 0.10.28 → 0.11.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/README.md +11 -4
- package/dist/index.global.js +327 -33
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +327 -33
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.global.js
CHANGED
|
@@ -24,12 +24,15 @@ var Loupe = (() => {
|
|
|
24
24
|
clearActivity: () => clearActivity,
|
|
25
25
|
connectTab: () => connectTab,
|
|
26
26
|
destroy: () => destroy,
|
|
27
|
+
hideLauncher: () => hideLauncher,
|
|
27
28
|
init: () => init,
|
|
28
29
|
openTool: () => openTool,
|
|
29
30
|
requestNavigation: () => requestNavigation2,
|
|
30
31
|
setActivityStatus: () => setActivityStatus,
|
|
31
32
|
setLocalAi: () => setLocalAi,
|
|
32
|
-
|
|
33
|
+
showLauncher: () => showLauncher,
|
|
34
|
+
trackActivity: () => trackActivity,
|
|
35
|
+
version: () => SDK_VERSION
|
|
33
36
|
});
|
|
34
37
|
|
|
35
38
|
// src/styles.ts
|
|
@@ -121,6 +124,14 @@ var Loupe = (() => {
|
|
|
121
124
|
display: none; flex-direction: column; align-items: flex-end; gap: 10px;
|
|
122
125
|
}
|
|
123
126
|
.fab-cluster.show { display: flex; }
|
|
127
|
+
/* A dragged launcher is anchored to its nearest edges (JS sets left/right/top/bottom),
|
|
128
|
+
and the quick actions grow INTO the page: downward from the upper half, labels to the
|
|
129
|
+
right from the left half. */
|
|
130
|
+
.fab-cluster.at-top { flex-direction: column-reverse; }
|
|
131
|
+
.fab-cluster.at-left, .fab-cluster.at-left .fab-minis { align-items: flex-start; }
|
|
132
|
+
.fab-cluster.at-left .fab-mini .fab-tip { right: auto; left: calc(100% + 8px); }
|
|
133
|
+
.fab-cluster.dragging .launcher { cursor: grabbing; border-color: var(--accent); }
|
|
134
|
+
.fab-cluster.dragging .fab-mini .fab-tip { display: none; }
|
|
124
135
|
|
|
125
136
|
.fab-minis { display: none; flex-direction: column; align-items: flex-end; gap: 10px; }
|
|
126
137
|
.fab-cluster.expanded .fab-minis { display: flex; }
|
|
@@ -129,7 +140,10 @@ var Loupe = (() => {
|
|
|
129
140
|
.fab-cluster.expanded .fab-minis .fab-mini:nth-child(2) { animation-delay: 30ms; }
|
|
130
141
|
.fab-cluster.expanded .fab-minis .fab-mini:nth-child(3) { animation-delay: 60ms; }
|
|
131
142
|
.fab-cluster.expanded .fab-minis .fab-mini:nth-child(4) { animation-delay: 90ms; }
|
|
143
|
+
.fab-cluster.expanded .fab-minis .fab-mini:nth-child(5) { animation-delay: 120ms; }
|
|
132
144
|
@keyframes loupe-fab-in { from { opacity: 0; transform: translateY(8px) scale(.9); } to { opacity: 1; transform: none; } }
|
|
145
|
+
.fab-cluster.at-top.expanded .fab-minis .fab-mini { animation-name: loupe-fab-in-down; }
|
|
146
|
+
@keyframes loupe-fab-in-down { from { opacity: 0; transform: translateY(-8px) scale(.9); } to { opacity: 1; transform: none; } }
|
|
133
147
|
|
|
134
148
|
.fab-mini {
|
|
135
149
|
position: relative; width: 40px; height: 40px; border-radius: 50%; padding: 0;
|
|
@@ -150,22 +164,32 @@ var Loupe = (() => {
|
|
|
150
164
|
}
|
|
151
165
|
.fab-mini:hover .fab-tip { opacity: 1; }
|
|
152
166
|
|
|
167
|
+
/* The launcher and its chevron share a box so the chevron can sit on the launcher's
|
|
168
|
+
corner while being its own button (tap = quick actions; the launcher = open). */
|
|
169
|
+
.fab-main { position: relative; display: inline-flex; }
|
|
153
170
|
.launcher {
|
|
154
171
|
position: relative; width: 46px; height: 46px; border-radius: 50%; padding: 0;
|
|
155
172
|
border: 1px solid var(--line); background: var(--bg-2); color: var(--ink);
|
|
156
|
-
cursor:
|
|
173
|
+
cursor: grab; display: inline-flex; align-items: center; justify-content: center;
|
|
157
174
|
box-shadow: var(--shadow);
|
|
175
|
+
touch-action: none; user-select: none; -webkit-user-select: none; /* drag with a finger too */
|
|
158
176
|
}
|
|
159
177
|
.launcher:hover { border-color: var(--accent); }
|
|
178
|
+
.launcher:active { cursor: grabbing; }
|
|
160
179
|
.launcher .logo { font-size: 24px; line-height: 1; color: var(--accent); }
|
|
161
180
|
/* Chevron pinned to the corner: up = actions are tucked away, down = they are out. */
|
|
162
|
-
.
|
|
163
|
-
position: absolute; right: -3px; bottom: -3px; width:
|
|
181
|
+
.fab-more {
|
|
182
|
+
position: absolute; right: -3px; bottom: -3px; width: 20px; height: 20px; border-radius: 50%;
|
|
183
|
+
padding: 0; cursor: pointer; z-index: 1;
|
|
164
184
|
background: var(--bg); border: 1px solid var(--line); color: var(--muted);
|
|
165
185
|
display: grid; place-items: center; transition: transform 160ms cubic-bezier(.16, 1, .3, 1);
|
|
166
186
|
}
|
|
167
|
-
.
|
|
168
|
-
.fab-
|
|
187
|
+
.fab-more:hover { border-color: var(--accent); color: var(--accent); }
|
|
188
|
+
.fab-more .lchev { display: grid; place-items: center; }
|
|
189
|
+
.fab-more svg { width: 11px; height: 11px; display: block; }
|
|
190
|
+
.fab-cluster.expanded .fab-more { transform: rotate(180deg); }
|
|
191
|
+
.fab-cluster.at-top .fab-more { bottom: auto; top: -3px; transform: rotate(180deg); }
|
|
192
|
+
.fab-cluster.at-top.expanded .fab-more { transform: none; }
|
|
169
193
|
.launcher .lcount {
|
|
170
194
|
position: absolute; top: -5px; right: -5px; background: var(--pin); color: #fff;
|
|
171
195
|
font-size: 10px; font-weight: 700; line-height: 1; border-radius: 999px; padding: 3px 6px;
|
|
@@ -250,7 +274,10 @@ var Loupe = (() => {
|
|
|
250
274
|
.item .num { background: var(--pin); color: #fff; width: 20px; height: 20px; border-radius: 50%; font-size: 11px; font-weight: 700; display: grid; place-items: center; flex: none; }
|
|
251
275
|
.item .num.detached { background: #9aa0af; }
|
|
252
276
|
.item .num.done { background: #10935a; }
|
|
253
|
-
|
|
277
|
+
/* Author + absolute time, visible collapsed too. */
|
|
278
|
+
.item .who { display: flex; flex-wrap: wrap; gap: 0 4px; font-size: 11.5px; color: var(--muted); margin-top: 3px; }
|
|
279
|
+
.item .who b { color: var(--ink); font-weight: 600; }
|
|
280
|
+
.item .who time { font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
254
281
|
.item .device { font-size: 10px; color: var(--muted); background: var(--bg-3); border-radius: 999px; padding: 1px 7px; white-space: nowrap; }
|
|
255
282
|
.item .body { font-size: 13px; line-height: 1.4; }
|
|
256
283
|
.item .meta { font-size: 11px; color: var(--muted); margin-top: 6px; font-family: ui-monospace, Menlo, monospace; word-break: break-all; }
|
|
@@ -445,6 +472,23 @@ var Loupe = (() => {
|
|
|
445
472
|
@keyframes loupe-recpulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }
|
|
446
473
|
@media (prefers-reduced-motion: reduce) { .recbar .recdot { animation: none; } }
|
|
447
474
|
|
|
475
|
+
/* ---------------------------------------------------------------- toast */
|
|
476
|
+
/* A short notice (e.g. how to bring a hidden launcher back). Click to dismiss. */
|
|
477
|
+
.toast {
|
|
478
|
+
position: fixed; z-index: 2147483006; bottom: 24px; left: 50%;
|
|
479
|
+
transform: translateX(-50%) translateY(8px); opacity: 0; pointer-events: none;
|
|
480
|
+
max-width: calc(100vw - 32px); padding: 9px 14px; border-radius: 999px; text-align: center;
|
|
481
|
+
background: var(--bg); color: var(--ink); border: 1px solid var(--line); box-shadow: var(--shadow);
|
|
482
|
+
font-size: 12px; font-weight: 600; line-height: 1.4;
|
|
483
|
+
transition: opacity 160ms ease, transform 160ms ease;
|
|
484
|
+
}
|
|
485
|
+
.toast.show { opacity: 1; transform: translateX(-50%); pointer-events: auto; cursor: pointer; }
|
|
486
|
+
.toast kbd {
|
|
487
|
+
font-family: ui-monospace, Menlo, monospace; font-size: 11px; padding: 1px 5px;
|
|
488
|
+
border: 1px solid var(--line); border-radius: 4px; background: var(--bg-3);
|
|
489
|
+
}
|
|
490
|
+
@media (prefers-reduced-motion: reduce) { .toast { transition: none; } }
|
|
491
|
+
|
|
448
492
|
/* ---------------------------------------------- header popovers (pos + settings) */
|
|
449
493
|
.menu-wrap { position: relative; }
|
|
450
494
|
.menu {
|
|
@@ -619,6 +663,8 @@ var Loupe = (() => {
|
|
|
619
663
|
}
|
|
620
664
|
.menu-ver b { color: var(--ink); font-family: ui-monospace, Menlo, monospace; font-weight: 600; }
|
|
621
665
|
.menu-mode { text-transform: uppercase; letter-spacing: .06em; font-size: 9.5px; }
|
|
666
|
+
/* The host package's version, shown only when it differs from this bundle's (a stale publish). */
|
|
667
|
+
.ver-stale { color: var(--pin); font-family: ui-monospace, Menlo, monospace; font-weight: 600; cursor: help; }
|
|
622
668
|
|
|
623
669
|
/* ------------------------------------------------ lifecycle chips + review flow */
|
|
624
670
|
.lifechip {
|
|
@@ -3881,7 +3927,10 @@ var Loupe = (() => {
|
|
|
3881
3927
|
};
|
|
3882
3928
|
|
|
3883
3929
|
// src/app.ts
|
|
3884
|
-
var SDK_VERSION = true ? "0.
|
|
3930
|
+
var SDK_VERSION = true ? "0.11.0" : "dev";
|
|
3931
|
+
var FAB_SIZE = 46;
|
|
3932
|
+
var FAB_DRAG_THRESHOLD = 6;
|
|
3933
|
+
var LAUNCHER_SHORTCUT = "Alt+Shift+L";
|
|
3885
3934
|
var ACCENTS = [
|
|
3886
3935
|
{ id: "indigo", dark: "#6b73e6", light: "#4a55d6", soft: "rgba(107,115,230,0.12)" },
|
|
3887
3936
|
{ id: "violet", dark: "#a06be6", light: "#7c3fd4", soft: "rgba(160,107,230,0.14)" },
|
|
@@ -3988,6 +4037,21 @@ var Loupe = (() => {
|
|
|
3988
4037
|
this.fabExpanded = false;
|
|
3989
4038
|
/** Whether pin markers are hidden on the page (a quick-action toggle, persisted). */
|
|
3990
4039
|
this.markersHidden = false;
|
|
4040
|
+
/**
|
|
4041
|
+
* Whether the collapsed launcher is hidden altogether (persisted). The way back is
|
|
4042
|
+
* Alt+Shift+L, Settings → Launcher, or the host calling `showLauncher()`.
|
|
4043
|
+
*/
|
|
4044
|
+
this.launcherHidden = false;
|
|
4045
|
+
/**
|
|
4046
|
+
* Where the user dragged the launcher to — the launcher's top-left in viewport px —
|
|
4047
|
+
* or null for the default corner on the dock's side. Persisted; clamped on apply so
|
|
4048
|
+
* a position saved on a wide screen still lands inside a narrow one.
|
|
4049
|
+
*/
|
|
4050
|
+
this.fabPos = null;
|
|
4051
|
+
/** The in-flight launcher drag: pointer start, launcher origin, and whether it has moved past the tap threshold. */
|
|
4052
|
+
this.fabDrag = null;
|
|
4053
|
+
/** Set for one tick after a drag so the click the browser fires afterwards does not open the panel. */
|
|
4054
|
+
this.fabSuppressClick = false;
|
|
3991
4055
|
this.comments = [];
|
|
3992
4056
|
/** Free-text filter over the list (title / body / author). */
|
|
3993
4057
|
this.search = "";
|
|
@@ -4088,6 +4152,56 @@ var Loupe = (() => {
|
|
|
4088
4152
|
this.lastReplyId = "";
|
|
4089
4153
|
/** In-app notifications: mentions of you, newest first. */
|
|
4090
4154
|
this.notifications = [];
|
|
4155
|
+
// ---- launcher: drag anywhere, hide, bring back ------------------------------
|
|
4156
|
+
this.onFabPointerDown = (e) => {
|
|
4157
|
+
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
4158
|
+
const target = e.currentTarget;
|
|
4159
|
+
const r = target.getBoundingClientRect();
|
|
4160
|
+
this.fabDrag = { px: e.clientX, py: e.clientY, ox: r.left, oy: r.top, moved: false };
|
|
4161
|
+
let captured = false;
|
|
4162
|
+
try {
|
|
4163
|
+
if (typeof target.setPointerCapture === "function") {
|
|
4164
|
+
target.setPointerCapture(e.pointerId);
|
|
4165
|
+
captured = true;
|
|
4166
|
+
}
|
|
4167
|
+
} catch {
|
|
4168
|
+
captured = false;
|
|
4169
|
+
}
|
|
4170
|
+
if (!captured) {
|
|
4171
|
+
window.addEventListener("pointermove", this.onFabPointerMove);
|
|
4172
|
+
window.addEventListener("pointerup", this.onFabPointerUp);
|
|
4173
|
+
window.addEventListener("pointercancel", this.onFabPointerUp);
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
4176
|
+
this.onFabPointerMove = (e) => {
|
|
4177
|
+
const d = this.fabDrag;
|
|
4178
|
+
if (!d) return;
|
|
4179
|
+
const dx = e.clientX - d.px, dy = e.clientY - d.py;
|
|
4180
|
+
if (!d.moved && Math.hypot(dx, dy) < FAB_DRAG_THRESHOLD) return;
|
|
4181
|
+
if (!d.moved) {
|
|
4182
|
+
d.moved = true;
|
|
4183
|
+
this.fabCluster.classList.add("dragging");
|
|
4184
|
+
this.collapseFab();
|
|
4185
|
+
}
|
|
4186
|
+
e.preventDefault();
|
|
4187
|
+
this.fabPos = this.clampFabPos(d.ox + dx, d.oy + dy);
|
|
4188
|
+
this.applyFabPosition();
|
|
4189
|
+
};
|
|
4190
|
+
this.onFabPointerUp = () => {
|
|
4191
|
+
window.removeEventListener("pointermove", this.onFabPointerMove);
|
|
4192
|
+
window.removeEventListener("pointerup", this.onFabPointerUp);
|
|
4193
|
+
window.removeEventListener("pointercancel", this.onFabPointerUp);
|
|
4194
|
+
const d = this.fabDrag;
|
|
4195
|
+
this.fabDrag = null;
|
|
4196
|
+
if (!d?.moved) return;
|
|
4197
|
+
this.fabCluster.classList.remove("dragging");
|
|
4198
|
+
this.saveState();
|
|
4199
|
+
this.renderSettings();
|
|
4200
|
+
this.fabSuppressClick = true;
|
|
4201
|
+
window.setTimeout(() => {
|
|
4202
|
+
this.fabSuppressClick = false;
|
|
4203
|
+
}, 0);
|
|
4204
|
+
};
|
|
4091
4205
|
// ---- inspector ------------------------------------------------------------
|
|
4092
4206
|
// Pointer Events, not mouse events: a touch drag on a phone never fires
|
|
4093
4207
|
// mousemove/mouseup, so Region/Record selection was impossible on touch devices.
|
|
@@ -4130,6 +4244,18 @@ var Loupe = (() => {
|
|
|
4130
4244
|
this.closeComposer();
|
|
4131
4245
|
}
|
|
4132
4246
|
};
|
|
4247
|
+
/**
|
|
4248
|
+
* Alt+Shift+L toggles the launcher — the way back once it has been hidden, so it is
|
|
4249
|
+
* listened for the whole time the widget is mounted (unlike Escape, which only
|
|
4250
|
+
* matters while a tool is armed). `code` is checked too: on some keyboard layouts
|
|
4251
|
+
* Alt+Shift+L yields a different `key`.
|
|
4252
|
+
*/
|
|
4253
|
+
this.onLauncherKey = (e) => {
|
|
4254
|
+
if (!e.altKey || !e.shiftKey || e.ctrlKey || e.metaKey) return;
|
|
4255
|
+
if (e.code !== "KeyL" && (e.key ?? "").toLowerCase() !== "l") return;
|
|
4256
|
+
e.preventDefault();
|
|
4257
|
+
this.setLauncherHidden(!this.launcherHidden);
|
|
4258
|
+
};
|
|
4133
4259
|
// ---- region ("free-size screenshot") selection ----------------------------
|
|
4134
4260
|
this.onRegionDown = (e) => {
|
|
4135
4261
|
if (this.mode !== "region" && this.mode !== "record") return;
|
|
@@ -4331,6 +4457,7 @@ var Loupe = (() => {
|
|
|
4331
4457
|
this.loadState();
|
|
4332
4458
|
this.loadProject();
|
|
4333
4459
|
this.buildDom();
|
|
4460
|
+
document.addEventListener("keydown", this.onLauncherKey);
|
|
4334
4461
|
if (this.cfg.autoOpen) this.open = true;
|
|
4335
4462
|
this.applyDockLayout();
|
|
4336
4463
|
this.lastUrl = this.url;
|
|
@@ -4404,11 +4531,15 @@ var Loupe = (() => {
|
|
|
4404
4531
|
this.dock = this.buildDock();
|
|
4405
4532
|
this.fabCluster = this.buildFabCluster();
|
|
4406
4533
|
this.recBar = this.buildRecBar();
|
|
4534
|
+
this.toastEl = el("div", "toast");
|
|
4535
|
+
this.toastEl.setAttribute("role", "status");
|
|
4536
|
+
this.toastEl.setAttribute("aria-live", "polite");
|
|
4537
|
+
this.toastEl.onclick = () => this.toastEl.classList.remove("show");
|
|
4407
4538
|
this.tourEl = el("div", "tour");
|
|
4408
4539
|
this.tourSpot = el("div", "tour-spot");
|
|
4409
4540
|
this.tourCard = el("div", "tour-card");
|
|
4410
4541
|
this.tourEl.append(this.tourSpot, this.tourCard);
|
|
4411
|
-
this.shadow.append(this.dock, this.fabCluster, this.recBar, this.tourEl);
|
|
4542
|
+
this.shadow.append(this.dock, this.fabCluster, this.recBar, this.toastEl, this.tourEl);
|
|
4412
4543
|
this.shadow.addEventListener("click", (e) => {
|
|
4413
4544
|
const t = e.target;
|
|
4414
4545
|
if (t?.closest && t.closest(".menu-wrap")) return;
|
|
@@ -4461,7 +4592,7 @@ var Loupe = (() => {
|
|
|
4461
4592
|
setBtn.innerHTML = I_GEAR;
|
|
4462
4593
|
setBtn.onclick = () => this.toggleMenu(this.settingsMenu);
|
|
4463
4594
|
this.settingsMenu = el("div", "menu");
|
|
4464
|
-
this.settingsMenu.innerHTML = `<div class="menu-label">Appearance</div><div class="acc-dots">` + ACCENTS.map((a) => `<button class="acc-dot" data-accent="${a.id}" style="background:${a.dark}" title="${a.id}" aria-label="${a.id} accent"></button>`).join("") + `</div><div class="menu-sep"></div><div class="menu-label">Show</div><button class="menu-row" data-set="hoverHints" aria-pressed="true"><span>Hover hints</span><span class="sw"></span></button><button class="menu-row" data-set="markersHidden" aria-pressed="true"><span>Markers</span><span class="sw"></span></button><button class="menu-row" data-set="showPaths" aria-pressed="false"><span>Page paths</span><span class="sw"></span></button><div class="menu-sep"></div><button class="menu-row" data-set="tour"><span>Restart tour</span></button><div class="menu-ver">Loupe <b>v${escapeHtml(SDK_VERSION)}</b><span class="menu-mode">${this.cfg.apiBase ? "server" : "offline"}</span></div>`;
|
|
4595
|
+
this.settingsMenu.innerHTML = `<div class="menu-label">Appearance</div><div class="acc-dots">` + ACCENTS.map((a) => `<button class="acc-dot" data-accent="${a.id}" style="background:${a.dark}" title="${a.id}" aria-label="${a.id} accent"></button>`).join("") + `</div><div class="menu-sep"></div><div class="menu-label">Show</div><button class="menu-row" data-set="hoverHints" aria-pressed="true"><span>Hover hints</span><span class="sw"></span></button><button class="menu-row" data-set="markersHidden" aria-pressed="true"><span>Markers</span><span class="sw"></span></button><button class="menu-row" data-set="showPaths" aria-pressed="false"><span>Page paths</span><span class="sw"></span></button><button class="menu-row" data-set="launcherHidden" aria-pressed="true" title="The \u25CE button shown while the panel is closed. ${LAUNCHER_SHORTCUT} toggles it too."><span>Launcher</span><span class="sw"></span></button><div class="menu-sep"></div><button class="menu-row" data-set="fabReset" hidden><span>Reset launcher position</span></button><button class="menu-row" data-set="tour"><span>Restart tour</span></button><div class="menu-ver"><span>Loupe <b>v${escapeHtml(SDK_VERSION)}</b>${this.packageVersionNote()}</span><span class="menu-mode">${this.cfg.apiBase ? "server" : "offline"}</span></div>`;
|
|
4465
4596
|
this.settingsMenu.querySelectorAll("[data-accent]").forEach((b) => {
|
|
4466
4597
|
b.onclick = () => this.setAccent(b.dataset.accent);
|
|
4467
4598
|
});
|
|
@@ -4473,6 +4604,11 @@ var Loupe = (() => {
|
|
|
4473
4604
|
this.startTour();
|
|
4474
4605
|
return;
|
|
4475
4606
|
}
|
|
4607
|
+
if (key === "fabReset") {
|
|
4608
|
+
this.closeMenus();
|
|
4609
|
+
this.resetLauncherPosition();
|
|
4610
|
+
return;
|
|
4611
|
+
}
|
|
4476
4612
|
this.toggleSetting(key);
|
|
4477
4613
|
};
|
|
4478
4614
|
});
|
|
@@ -5071,7 +5207,7 @@ var Loupe = (() => {
|
|
|
5071
5207
|
*/
|
|
5072
5208
|
buildHomePanel() {
|
|
5073
5209
|
const title = this.cfg.label ?? "Loupe";
|
|
5074
|
-
this.homeEl.innerHTML = `<div class="hint-slot" id="loupe-hhint"></div><div class="hstat" id="loupe-hstats"></div><div class="hscope"><button class="hscope-b" data-scope="page">This page <b class="hscope-n"></b></button><button class="hscope-b" data-scope="all">All <b class="hscope-n"></b></button><button class="hrefresh" title="Refresh" aria-label="Refresh">\u27F3</button></div><button class="hpin" data-role="home-pin">\u271B Pin feedback on this page</button><div class="projbar"><span class="proj-label">Project</span><button class="proj-chip" data-role="proj-open" aria-haspopup="dialog" aria-expanded="false"><span class="proj-repo"></span><span class="proj-caret">\u25BE</span></button><div class="proj-pop" id="loupe-proj" role="dialog" aria-label="Project settings"></div></div><div class="hnotif" id="loupe-hnotif"></div><div class="hlabel">Recent</div><div class="hfeed" id="loupe-hfeed"></div><div class="hfoot">${escapeHtml(title)} \xB7 <span class="hver">v${escapeHtml(SDK_VERSION)}</span
|
|
5210
|
+
this.homeEl.innerHTML = `<div class="hint-slot" id="loupe-hhint"></div><div class="hstat" id="loupe-hstats"></div><div class="hscope"><button class="hscope-b" data-scope="page">This page <b class="hscope-n"></b></button><button class="hscope-b" data-scope="all">All <b class="hscope-n"></b></button><button class="hrefresh" title="Refresh" aria-label="Refresh">\u27F3</button></div><button class="hpin" data-role="home-pin">\u271B Pin feedback on this page</button><div class="projbar"><span class="proj-label">Project</span><button class="proj-chip" data-role="proj-open" aria-haspopup="dialog" aria-expanded="false"><span class="proj-repo"></span><span class="proj-caret">\u25BE</span></button><div class="proj-pop" id="loupe-proj" role="dialog" aria-label="Project settings"></div></div><div class="hnotif" id="loupe-hnotif"></div><div class="hlabel">Recent</div><div class="hfeed" id="loupe-hfeed"></div><div class="hfoot">${escapeHtml(title)} \xB7 <span class="hver">v${escapeHtml(SDK_VERSION)}</span>${this.packageVersionNote()}</div>`;
|
|
5075
5211
|
this.homeEl.querySelector('[data-role="proj-open"]').onclick = (e) => {
|
|
5076
5212
|
e.stopPropagation();
|
|
5077
5213
|
this.setProjectOpen(!this.projOpen);
|
|
@@ -5164,7 +5300,7 @@ var Loupe = (() => {
|
|
|
5164
5300
|
feed.innerHTML = recent.length ? recent.map((c) => {
|
|
5165
5301
|
const stage = STAGE_LABELS[normalizeStatus(c.status)];
|
|
5166
5302
|
const prio = normalizePriority(c.priority);
|
|
5167
|
-
return `<button class="hfeed-i" data-id="${escapeAttr(c.id)}"><span class="hfeed-t">${escapeHtml(c.title || (c.body.split("\n")[0] ?? "").slice(0, 60))}</span><span class="hfeed-m">${escapeHtml(c.author?.name ?? "")} \xB7 ${fmtAgo(c.createdAt)} \xB7 <span class="hfeed-s hfeed-s-${normalizeStatus(c.status)}">${stage}</span><span class="hfeed-p hfeed-p-${prio}">${PRIORITY_LABELS[prio]}</span></span></button>`;
|
|
5303
|
+
return `<button class="hfeed-i" data-id="${escapeAttr(c.id)}"><span class="hfeed-t">${escapeHtml(c.title || (c.body.split("\n")[0] ?? "").slice(0, 60))}</span><span class="hfeed-m">${escapeHtml(c.author?.name ?? "")} \xB7 <span title="${escapeAttr(this.fmtWhen(c.createdAt))}">${fmtAgo(c.createdAt)}</span> \xB7 <span class="hfeed-s hfeed-s-${normalizeStatus(c.status)}">${stage}</span><span class="hfeed-p hfeed-p-${prio}">${PRIORITY_LABELS[prio]}</span></span></button>`;
|
|
5168
5304
|
}).join("") : `<div class="hempty">Nothing here yet.</div>`;
|
|
5169
5305
|
feed.querySelectorAll(".hfeed-i").forEach((b) => {
|
|
5170
5306
|
b.onclick = () => {
|
|
@@ -5431,20 +5567,21 @@ var Loupe = (() => {
|
|
|
5431
5567
|
}
|
|
5432
5568
|
/**
|
|
5433
5569
|
* The collapsed-state FAB cluster. The primary brand button carries the comment
|
|
5434
|
-
* count and
|
|
5435
|
-
*
|
|
5436
|
-
*
|
|
5437
|
-
*
|
|
5570
|
+
* count and OPENS the panel — one tap, the thing most people came for. A drag on
|
|
5571
|
+
* it moves the whole cluster anywhere on screen (persisted). The chevron beside it
|
|
5572
|
+
* is its own button and toggles the quick actions: pin a comment, drop a note,
|
|
5573
|
+
* hide/show the markers already on the page, hide the launcher itself, and — when
|
|
5574
|
+
* the host registered one — jump straight to the Connect tab.
|
|
5438
5575
|
*/
|
|
5439
5576
|
buildFabCluster() {
|
|
5440
5577
|
const cluster = el("div", "fab-cluster");
|
|
5441
5578
|
const minis = el("div", "fab-minis");
|
|
5442
|
-
const mini = (role, icon,
|
|
5579
|
+
const mini = (role, icon, label2, title) => {
|
|
5443
5580
|
const b = el("button", "fab-mini");
|
|
5444
5581
|
b.dataset.fab = role;
|
|
5445
5582
|
b.title = title;
|
|
5446
5583
|
b.setAttribute("aria-label", title);
|
|
5447
|
-
b.innerHTML = `${icon}<span class="fab-tip">${
|
|
5584
|
+
b.innerHTML = `${icon}<span class="fab-tip">${label2}</span>`;
|
|
5448
5585
|
return b;
|
|
5449
5586
|
};
|
|
5450
5587
|
const comment = mini("comment", I_COMMENT, "Pin comment", "Pin feedback on any element");
|
|
@@ -5461,7 +5598,9 @@ var Loupe = (() => {
|
|
|
5461
5598
|
};
|
|
5462
5599
|
const markers = mini("markers", I_EYE, "Markers", "Show or hide the markers on this page");
|
|
5463
5600
|
markers.onclick = () => this.toggleMarkers();
|
|
5464
|
-
|
|
5601
|
+
const hide = mini("hide", I_EYE_OFF, "Hide launcher", `Hide this launcher \u2014 ${LAUNCHER_SHORTCUT} brings it back`);
|
|
5602
|
+
hide.onclick = () => this.setLauncherHidden(true);
|
|
5603
|
+
minis.append(comment, note, markers, hide);
|
|
5465
5604
|
if (this.tabList.some((t) => t.id === "connect")) {
|
|
5466
5605
|
const connect = mini("connect", I_PLUG, "Connect Claude", "Set up the Claude/MCP connection");
|
|
5467
5606
|
connect.onclick = () => {
|
|
@@ -5471,14 +5610,33 @@ var Loupe = (() => {
|
|
|
5471
5610
|
};
|
|
5472
5611
|
minis.appendChild(connect);
|
|
5473
5612
|
}
|
|
5613
|
+
const label = this.cfg.label ?? "Loupe";
|
|
5474
5614
|
const primary = el("button", "launcher");
|
|
5475
|
-
primary.title = `Open ${
|
|
5476
|
-
primary.setAttribute("aria-label",
|
|
5477
|
-
primary.
|
|
5478
|
-
primary.innerHTML = `<span class="logo">\u25CE</span>${I_FAB_CHEVRON}<span class="lcount"></span>`;
|
|
5615
|
+
primary.title = `Open ${label} \u2014 drag to move`;
|
|
5616
|
+
primary.setAttribute("aria-label", `Open ${label}`);
|
|
5617
|
+
primary.innerHTML = `<span class="logo">\u25CE</span><span class="lcount"></span>`;
|
|
5479
5618
|
this.fabBadge = primary.querySelector(".lcount");
|
|
5480
|
-
primary.onclick = () =>
|
|
5481
|
-
|
|
5619
|
+
primary.onclick = () => {
|
|
5620
|
+
if (this.fabSuppressClick) return;
|
|
5621
|
+
this.openDock();
|
|
5622
|
+
};
|
|
5623
|
+
primary.addEventListener("pointerdown", this.onFabPointerDown);
|
|
5624
|
+
primary.addEventListener("pointermove", this.onFabPointerMove);
|
|
5625
|
+
primary.addEventListener("pointerup", this.onFabPointerUp);
|
|
5626
|
+
primary.addEventListener("pointercancel", this.onFabPointerUp);
|
|
5627
|
+
const more = el("button", "fab-more");
|
|
5628
|
+
more.dataset.fab = "more";
|
|
5629
|
+
more.title = "Quick actions";
|
|
5630
|
+
more.setAttribute("aria-label", "Quick actions");
|
|
5631
|
+
more.setAttribute("aria-expanded", "false");
|
|
5632
|
+
more.innerHTML = I_FAB_CHEVRON;
|
|
5633
|
+
more.onclick = (e) => {
|
|
5634
|
+
e.stopPropagation();
|
|
5635
|
+
this.toggleFab();
|
|
5636
|
+
};
|
|
5637
|
+
const main = el("div", "fab-main");
|
|
5638
|
+
main.append(primary, more);
|
|
5639
|
+
cluster.append(minis, main);
|
|
5482
5640
|
this.fabMinis = minis;
|
|
5483
5641
|
return cluster;
|
|
5484
5642
|
}
|
|
@@ -5487,6 +5645,79 @@ var Loupe = (() => {
|
|
|
5487
5645
|
this.fabExpanded = !this.fabExpanded;
|
|
5488
5646
|
this.applyFab();
|
|
5489
5647
|
}
|
|
5648
|
+
/** Keep the launcher fully on screen, with a small margin so it never kisses the edge. */
|
|
5649
|
+
clampFabPos(x, y) {
|
|
5650
|
+
const m = 8;
|
|
5651
|
+
return {
|
|
5652
|
+
x: clampPx(Math.round(x), m, Math.max(m, window.innerWidth - FAB_SIZE - m)),
|
|
5653
|
+
y: clampPx(Math.round(y), m, Math.max(m, window.innerHeight - FAB_SIZE - m))
|
|
5654
|
+
};
|
|
5655
|
+
}
|
|
5656
|
+
/**
|
|
5657
|
+
* Place the cluster. A dragged launcher is anchored to the viewport edge it is
|
|
5658
|
+
* nearest, so the quick actions always grow INTO the page: minis open upward from
|
|
5659
|
+
* the lower half and downward from the upper half, and their labels point away
|
|
5660
|
+
* from the nearest side. With no saved position the cluster sits in the default
|
|
5661
|
+
* corner on the dock's side, so reopening feels like the panel sliding back in.
|
|
5662
|
+
*/
|
|
5663
|
+
applyFabPosition() {
|
|
5664
|
+
const s = this.fabCluster.style;
|
|
5665
|
+
if (this.fabPos) {
|
|
5666
|
+
const { x, y } = this.clampFabPos(this.fabPos.x, this.fabPos.y);
|
|
5667
|
+
const atTop = y + FAB_SIZE / 2 < window.innerHeight / 2;
|
|
5668
|
+
const atLeft = x + FAB_SIZE / 2 < window.innerWidth / 2;
|
|
5669
|
+
s.left = atLeft ? `${x}px` : "auto";
|
|
5670
|
+
s.right = atLeft ? "auto" : `${Math.max(0, window.innerWidth - x - FAB_SIZE)}px`;
|
|
5671
|
+
s.top = atTop ? `${y}px` : "auto";
|
|
5672
|
+
s.bottom = atTop ? "auto" : `${Math.max(0, window.innerHeight - y - FAB_SIZE)}px`;
|
|
5673
|
+
this.fabCluster.classList.toggle("at-top", atTop);
|
|
5674
|
+
this.fabCluster.classList.toggle("at-left", atLeft);
|
|
5675
|
+
return;
|
|
5676
|
+
}
|
|
5677
|
+
const leftSide = this.dockMode === "left";
|
|
5678
|
+
s.left = leftSide ? "20px" : "auto";
|
|
5679
|
+
s.right = leftSide ? "auto" : "20px";
|
|
5680
|
+
s.top = "auto";
|
|
5681
|
+
s.bottom = "";
|
|
5682
|
+
this.fabCluster.classList.remove("at-top");
|
|
5683
|
+
this.fabCluster.classList.toggle("at-left", leftSide);
|
|
5684
|
+
}
|
|
5685
|
+
resetLauncherPosition() {
|
|
5686
|
+
this.fabPos = null;
|
|
5687
|
+
this.saveState();
|
|
5688
|
+
this.applyDockLayout();
|
|
5689
|
+
this.renderSettings();
|
|
5690
|
+
}
|
|
5691
|
+
/**
|
|
5692
|
+
* Hide or show the collapsed launcher. Hidden is persisted, and the way back is
|
|
5693
|
+
* announced at the moment of hiding — a control that vanishes with no stated way
|
|
5694
|
+
* to return is a support ticket, not a feature.
|
|
5695
|
+
*/
|
|
5696
|
+
setLauncherHidden(hidden) {
|
|
5697
|
+
if (this.launcherHidden === hidden) return;
|
|
5698
|
+
this.launcherHidden = hidden;
|
|
5699
|
+
this.fabExpanded = false;
|
|
5700
|
+
this.saveState();
|
|
5701
|
+
this.applyDockLayout();
|
|
5702
|
+
this.renderSettings();
|
|
5703
|
+
const label = this.cfg.label ?? "Loupe";
|
|
5704
|
+
this.toast(hidden ? `${escapeHtml(label)} launcher hidden \u2014 press <kbd>${LAUNCHER_SHORTCUT}</kbd> to bring it back` : `${escapeHtml(label)} launcher is back`);
|
|
5705
|
+
}
|
|
5706
|
+
/** Public seam for the host (`Loupe.showLauncher()`). */
|
|
5707
|
+
showLauncher() {
|
|
5708
|
+
this.setLauncherHidden(false);
|
|
5709
|
+
}
|
|
5710
|
+
/** Public seam for the host (`Loupe.hideLauncher()`). */
|
|
5711
|
+
hideLauncher() {
|
|
5712
|
+
this.setLauncherHidden(true);
|
|
5713
|
+
}
|
|
5714
|
+
/** A short notice at the bottom of the viewport. `html` is trusted markup built by the caller. */
|
|
5715
|
+
toast(html, ms = 6e3) {
|
|
5716
|
+
this.toastEl.innerHTML = html;
|
|
5717
|
+
this.toastEl.classList.add("show");
|
|
5718
|
+
window.clearTimeout(this.toastTimer);
|
|
5719
|
+
this.toastTimer = window.setTimeout(() => this.toastEl.classList.remove("show"), ms);
|
|
5720
|
+
}
|
|
5490
5721
|
collapseFab() {
|
|
5491
5722
|
if (!this.fabExpanded) return;
|
|
5492
5723
|
this.fabExpanded = false;
|
|
@@ -5501,9 +5732,9 @@ var Loupe = (() => {
|
|
|
5501
5732
|
/** Reflect cluster expansion + marker visibility into the DOM. */
|
|
5502
5733
|
applyFab() {
|
|
5503
5734
|
this.fabCluster.classList.toggle("expanded", this.fabExpanded);
|
|
5504
|
-
const
|
|
5505
|
-
|
|
5506
|
-
|
|
5735
|
+
const more = this.fabCluster.querySelector('[data-fab="more"]');
|
|
5736
|
+
more?.setAttribute("aria-expanded", String(this.fabExpanded));
|
|
5737
|
+
more?.setAttribute("aria-label", this.fabExpanded ? "Close quick actions" : "Quick actions");
|
|
5507
5738
|
const markers = this.fabCluster.querySelector('[data-fab="markers"]');
|
|
5508
5739
|
markers?.classList.toggle("on", this.markersHidden);
|
|
5509
5740
|
markers?.setAttribute("aria-pressed", String(this.markersHidden));
|
|
@@ -6067,6 +6298,10 @@ var Loupe = (() => {
|
|
|
6067
6298
|
}
|
|
6068
6299
|
/** Flip one of the "Show …" settings and repaint whatever it governs. */
|
|
6069
6300
|
toggleSetting(key) {
|
|
6301
|
+
if (key === "launcherHidden") {
|
|
6302
|
+
this.setLauncherHidden(!this.launcherHidden);
|
|
6303
|
+
return;
|
|
6304
|
+
}
|
|
6070
6305
|
if (key === "markersHidden") this.markersHidden = !this.markersHidden;
|
|
6071
6306
|
else if (key === "hoverHints") this.hoverHints = !this.hoverHints;
|
|
6072
6307
|
else this.showPaths = !this.showPaths;
|
|
@@ -6085,11 +6320,13 @@ var Loupe = (() => {
|
|
|
6085
6320
|
const on = {
|
|
6086
6321
|
hoverHints: this.hoverHints,
|
|
6087
6322
|
markersHidden: !this.markersHidden,
|
|
6088
|
-
showPaths: this.showPaths
|
|
6323
|
+
showPaths: this.showPaths,
|
|
6324
|
+
launcherHidden: !this.launcherHidden
|
|
6089
6325
|
};
|
|
6090
6326
|
this.settingsMenu.querySelectorAll("[data-set]").forEach((b) => {
|
|
6091
6327
|
const key = b.dataset.set;
|
|
6092
6328
|
if (key in on) b.setAttribute("aria-pressed", String(on[key]));
|
|
6329
|
+
if (key === "fabReset") b.hidden = !this.fabPos;
|
|
6093
6330
|
});
|
|
6094
6331
|
this.settingsMenu.querySelectorAll("[data-accent]").forEach((b) => b.classList.toggle("on", b.dataset.accent === this.accent));
|
|
6095
6332
|
}
|
|
@@ -6223,6 +6460,8 @@ var Loupe = (() => {
|
|
|
6223
6460
|
if (typeof p?.repoFilter === "string") this.repoFilter = p.repoFilter;
|
|
6224
6461
|
if (p?.float && typeof p.float.w === "number") this.floatRect = { ...this.floatRect, ...p.float };
|
|
6225
6462
|
if (typeof p?.markersHidden === "boolean") this.markersHidden = p.markersHidden;
|
|
6463
|
+
if (typeof p?.launcherHidden === "boolean") this.launcherHidden = p.launcherHidden;
|
|
6464
|
+
if (p?.fab && typeof p.fab.x === "number" && typeof p.fab.y === "number") this.fabPos = { x: p.fab.x, y: p.fab.y };
|
|
6226
6465
|
if (p?.accent && ACCENT_IDS.includes(p.accent)) this.accent = p.accent;
|
|
6227
6466
|
if (typeof p?.minimized === "boolean") this.minimized = p.minimized;
|
|
6228
6467
|
if (typeof p?.hoverHints === "boolean") this.hoverHints = p.hoverHints;
|
|
@@ -6241,6 +6480,8 @@ var Loupe = (() => {
|
|
|
6241
6480
|
tab: this.tab,
|
|
6242
6481
|
float: this.floatRect,
|
|
6243
6482
|
markersHidden: this.markersHidden,
|
|
6483
|
+
launcherHidden: this.launcherHidden,
|
|
6484
|
+
fab: this.fabPos,
|
|
6244
6485
|
scope: this.scope,
|
|
6245
6486
|
statFilter: this.statFilter,
|
|
6246
6487
|
repoFilter: this.repoFilter,
|
|
@@ -6293,10 +6534,8 @@ var Loupe = (() => {
|
|
|
6293
6534
|
for (const p of ["left", "top", "right", "bottom", "width", "height"]) d.style[p] = "";
|
|
6294
6535
|
}
|
|
6295
6536
|
d.querySelectorAll(".dctl [data-dock]").forEach((b) => b.classList.toggle("on", b.dataset.dock === this.dockMode));
|
|
6296
|
-
this.fabCluster.classList.toggle("show", !this.open);
|
|
6297
|
-
|
|
6298
|
-
this.fabCluster.style.left = leftSide ? "20px" : "auto";
|
|
6299
|
-
this.fabCluster.style.right = leftSide ? "auto" : "20px";
|
|
6537
|
+
this.fabCluster.classList.toggle("show", !this.open && !this.launcherHidden);
|
|
6538
|
+
this.applyFabPosition();
|
|
6300
6539
|
this.applyFab();
|
|
6301
6540
|
this.pushPage();
|
|
6302
6541
|
}
|
|
@@ -6425,6 +6664,10 @@ var Loupe = (() => {
|
|
|
6425
6664
|
item.appendChild(top);
|
|
6426
6665
|
const summary = c.title || (c.body.split("\n")[0] ?? "").slice(0, 140) || "(no description)";
|
|
6427
6666
|
item.appendChild(el("div", "summary", summary));
|
|
6667
|
+
const who = el("div", "who");
|
|
6668
|
+
const when = this.fmtWhen(c.createdAt);
|
|
6669
|
+
who.innerHTML = `<b>${escapeHtml(c.author?.name?.trim() || "Unknown")}</b>` + (when ? ` \xB7 <time datetime="${escapeAttr(c.createdAt)}" title="${escapeAttr(fmtAgo(c.createdAt))}">${escapeHtml(when)}</time>` : "");
|
|
6670
|
+
item.appendChild(who);
|
|
6428
6671
|
const detail = el("div", "detail");
|
|
6429
6672
|
const repliesForState = this.messages.get(c.id) ?? [];
|
|
6430
6673
|
const lastMsg = repliesForState.length ? repliesForState[repliesForState.length - 1] : void 0;
|
|
@@ -6997,6 +7240,7 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
6997
7240
|
el("b", "msg-name", m.author.name),
|
|
6998
7241
|
el("span", "msg-when", fmtAgo(m.createdAt))
|
|
6999
7242
|
);
|
|
7243
|
+
head.lastElementChild.title = this.fmtWhen(m.createdAt);
|
|
7000
7244
|
if (fromAgent) head.appendChild(el("span", "msg-tag", "agent"));
|
|
7001
7245
|
const body = el("div", "msg-body");
|
|
7002
7246
|
for (const seg of mentionSegments(m.body, parseMentions(m.body))) {
|
|
@@ -7525,6 +7769,21 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
7525
7769
|
const elx = this.resolved.get(id);
|
|
7526
7770
|
if (elx) elx.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
7527
7771
|
}
|
|
7772
|
+
/** Absolute timestamp in the host's configured time zone and locale (see `LoupeConfig.timeZone`). */
|
|
7773
|
+
fmtWhen(iso) {
|
|
7774
|
+
return fmtAbsolute(iso, this.cfg.locale, this.cfg.timeZone);
|
|
7775
|
+
}
|
|
7776
|
+
/**
|
|
7777
|
+
* " · package v0.11.0", flagged, when the host's package version differs from this
|
|
7778
|
+
* bundle's. The two agree on a healthy install; they differ exactly when the
|
|
7779
|
+
* package was upgraded but its published JS was not — the trap this makes visible.
|
|
7780
|
+
*/
|
|
7781
|
+
packageVersionNote() {
|
|
7782
|
+
const pkg = (this.cfg.packageVersion ?? "").trim().replace(/^v/i, "");
|
|
7783
|
+
if (!pkg || pkg === SDK_VERSION) return "";
|
|
7784
|
+
const why = `This widget bundle is v${SDK_VERSION} but the installed package is v${pkg}. Re-publish the package assets so the served bundle matches.`;
|
|
7785
|
+
return ` \xB7 <span class="ver-stale" title="${escapeAttr(why)}">package v${escapeHtml(pkg)}</span>`;
|
|
7786
|
+
}
|
|
7528
7787
|
destroy() {
|
|
7529
7788
|
this.liveSource?.close();
|
|
7530
7789
|
this.liveSource = void 0;
|
|
@@ -7543,6 +7802,11 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
7543
7802
|
window.removeEventListener("pointerup", this.onHeadPointerUp);
|
|
7544
7803
|
window.removeEventListener("pointermove", this.onResizeMove);
|
|
7545
7804
|
window.removeEventListener("pointerup", this.onResizeUp);
|
|
7805
|
+
window.removeEventListener("pointermove", this.onFabPointerMove);
|
|
7806
|
+
window.removeEventListener("pointerup", this.onFabPointerUp);
|
|
7807
|
+
window.removeEventListener("pointercancel", this.onFabPointerUp);
|
|
7808
|
+
window.clearTimeout(this.toastTimer);
|
|
7809
|
+
document.removeEventListener("keydown", this.onLauncherKey);
|
|
7546
7810
|
document.removeEventListener("keydown", this.onKey, true);
|
|
7547
7811
|
const de = document.documentElement;
|
|
7548
7812
|
de.style.marginLeft = de.style.marginRight = de.style.marginBottom = "";
|
|
@@ -7569,6 +7833,27 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
7569
7833
|
if (h < 24) return `${h}h ago`;
|
|
7570
7834
|
return `${Math.round(h / 24)}d ago`;
|
|
7571
7835
|
}
|
|
7836
|
+
var warnedBadTimeZone = false;
|
|
7837
|
+
function fmtAbsolute(iso, locale, timeZone) {
|
|
7838
|
+
const t = Date.parse(iso);
|
|
7839
|
+
if (Number.isNaN(t)) return "";
|
|
7840
|
+
const opts = {
|
|
7841
|
+
day: "numeric",
|
|
7842
|
+
month: "short",
|
|
7843
|
+
year: "numeric",
|
|
7844
|
+
hour: "numeric",
|
|
7845
|
+
minute: "2-digit"
|
|
7846
|
+
};
|
|
7847
|
+
try {
|
|
7848
|
+
return new Intl.DateTimeFormat(locale || void 0, { ...opts, timeZone: timeZone || void 0 }).format(t);
|
|
7849
|
+
} catch (err) {
|
|
7850
|
+
if (!warnedBadTimeZone) {
|
|
7851
|
+
warnedBadTimeZone = true;
|
|
7852
|
+
console.warn(`[loupe] unusable timeZone/locale (${timeZone ?? "-"} / ${locale ?? "-"}); using the browser's`, err);
|
|
7853
|
+
}
|
|
7854
|
+
return new Intl.DateTimeFormat(void 0, opts).format(t);
|
|
7855
|
+
}
|
|
7856
|
+
}
|
|
7572
7857
|
function shortPath(url) {
|
|
7573
7858
|
try {
|
|
7574
7859
|
return new URL(url, location.origin).pathname || "/";
|
|
@@ -7651,6 +7936,9 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
7651
7936
|
var I_PLUG = svg(
|
|
7652
7937
|
`<path d="M6 1.8v3.1M10 1.8v3.1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><path d="M4.4 4.9h7.2v2.3a3.6 3.6 0 0 1-7.2 0z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M8 10.8v3.4" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>`
|
|
7653
7938
|
);
|
|
7939
|
+
var I_EYE_OFF = svg(
|
|
7940
|
+
`<path d="M2 2l12 12M6.6 6.7A2 2 0 0 0 9.3 9.4M4.3 4.5C2.9 5.4 1.9 6.7 1.5 8c1.1 3 3.7 5 6.5 5 1.3 0 2.5-.4 3.6-1.1M6.9 3.2C7.3 3.1 7.6 3 8 3c2.8 0 5.4 2 6.5 5-.3.8-.8 1.6-1.4 2.3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>`
|
|
7941
|
+
);
|
|
7654
7942
|
var I_FAB_CHEVRON = `<span class="lchev">${svg(`<path d="M4.4 9.6 8 6l3.6 3.6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>`)}</span>`;
|
|
7655
7943
|
var REGION_ICON = `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><rect x="1.5" y="2.5" width="12" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4" stroke-dasharray="2.4 1.8"/></svg>`;
|
|
7656
7944
|
var NOTE_ICON = `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" aria-hidden="true"><path d="M2 2.5h11v7.5H6l-3 2.5v-2.5H2z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>`;
|
|
@@ -7779,6 +8067,12 @@ ${c.body}` : c.body)}</div>` + (c.context?.html ? `<pre class="or-code">${escape
|
|
|
7779
8067
|
function openTool(tool) {
|
|
7780
8068
|
app?.openTool(tool);
|
|
7781
8069
|
}
|
|
8070
|
+
function showLauncher() {
|
|
8071
|
+
app?.showLauncher();
|
|
8072
|
+
}
|
|
8073
|
+
function hideLauncher() {
|
|
8074
|
+
app?.hideLauncher();
|
|
8075
|
+
}
|
|
7782
8076
|
return __toCommonJS(src_exports);
|
|
7783
8077
|
})();
|
|
7784
8078
|
//# sourceMappingURL=index.global.js.map
|