@iasiv5/dsh-quota-watch 0.1.1 → 0.1.2
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 +2 -2
- package/lib/client.js +70 -83
- package/package.json +1 -1
- package/src/client/prefs.mjs +4 -3
- package/src/client.mjs +88 -51
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
- **悬浮胶囊**(0.1.1 起为唯一浮动形态,圆球已退役):常驻屏幕右下角(距边 16px 起步),可整体拖拽到任意位置,位置跨重启记忆,越界自动钳回视口。胶囊一行显示「GLM 13% · Copilot 24%」,已用超过 80% 转橙、超过 95% 转红,任一 provider 超 95% 时胶囊轻微脉动(尊重系统「减少动态效果」设置,开启时不脉动);颜色使用 DSH 主题语义变量,自动适配浅色 / 深色主题。悬停提示与 aria-label 实时给出摘要。
|
|
10
10
|
- **面板**:点击球或胶囊展开浮动详情面板(半透明毛玻璃底,宽 `min(320px, 100vw−24px)`,靠边自动翻侧,滚动 / 缩放跟随),头部带标题与 ✕,Esc 或点击面板外关闭。概览列两个 provider 行,点击进入详情:GLM 为今日 Tokens / 调用次数双大数字 + 五列对齐窗口网格(MCP(月)→ 5 小时 → 每月 → 每周固定排序,周窗口未下发时显示「每周额度 · ♾️(无限)」占位)+ 分模型用量行;Copilot 为可用额度、本期已用(credits 消耗 + 弱化百分比)、重置时间三行。接口未下发有效 `remaining` / `percent_remaining` 时该行不渲染,`credits_used` 绝不用于推算余额。
|
|
11
|
-
-
|
|
12
|
-
- **右键菜单**:显示 / 隐藏侧边栏卡片 ·
|
|
11
|
+
- **侧边栏卡片(可选,默认隐藏)**:胶囊是默认界面;需要侧栏卡片时在胶囊右键菜单开启「显示侧边栏卡片」(Web profile 侧栏底部区块存在时才可选)。卡片显示时提供摘要行 + 行点击开面板 + 行首 provider 名称点击立即探测。侧栏收起(Web 窄栏)或容器过窄(<160px,跨 profile 通用判据)时卡片自动隐藏,侧栏整体不存在(Desktop 收起态)同样自然缺席、无报错——入口始终由悬浮胶囊承载。
|
|
12
|
+
- **右键菜单**:显示 / 隐藏侧边栏卡片 · 立即刷新。菜单项随上下文增减(无侧栏卡片挂载点时只剩刷新项)。
|
|
13
13
|
- 凭据缺失的 provider 不渲染摘要行与胶囊分段(两者皆缺时悬浮胶囊与卡片一并隐藏);探测失败时保留最后一次成功数据并在行上加 ⚠ 过期标记(⚠ 只出现在面板与卡片行内,不上球)。
|
|
14
14
|
- 宿主默认每 60 秒探测(窗口 + 用量 + Copilot);profile patch 可将 `pollIntervalSec` 设置在 30–3600 秒。浏览器页签可见时每 30 秒刷新视图,隐藏时暂停。
|
|
15
15
|
|
package/lib/client.js
CHANGED
|
@@ -117,14 +117,14 @@ function saveFloatGeometry(storage, point) {
|
|
|
117
117
|
function loadSurfaceFlags(storage) {
|
|
118
118
|
try {
|
|
119
119
|
const raw = storage.getItem(SURFACE_FLAGS_KEY);
|
|
120
|
-
if (typeof raw !== "string" || raw === "") return { cardHidden:
|
|
120
|
+
if (typeof raw !== "string" || raw === "") return { cardHidden: true };
|
|
121
121
|
const parsed = JSON.parse(raw);
|
|
122
122
|
if (typeof parsed !== "object" || parsed === null || typeof parsed.cardHidden !== "boolean") {
|
|
123
|
-
return { cardHidden:
|
|
123
|
+
return { cardHidden: true };
|
|
124
124
|
}
|
|
125
125
|
return { cardHidden: parsed.cardHidden };
|
|
126
126
|
} catch {
|
|
127
|
-
return { cardHidden:
|
|
127
|
+
return { cardHidden: true };
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
function saveSurfaceFlags(storage, flags) {
|
|
@@ -138,9 +138,11 @@ function saveSurfaceFlags(storage, flags) {
|
|
|
138
138
|
var name = "quota-watch-client";
|
|
139
139
|
var inject = [];
|
|
140
140
|
var CARD_SELECTOR = "[data-dsh-quota-watch-card]";
|
|
141
|
+
var FLOAT_SELECTOR = "[data-dsh-quota-watch-float]";
|
|
141
142
|
var FETCH_TIMEOUT_MS = 15e3;
|
|
142
143
|
var SURFACE_SIZE = 38;
|
|
143
144
|
var SURFACE_MARGIN = 16;
|
|
145
|
+
var CARD_MIN_WIDTH = 160;
|
|
144
146
|
var COPY = {
|
|
145
147
|
zh: {
|
|
146
148
|
title: "Token\u989D\u5EA6",
|
|
@@ -149,9 +151,7 @@ var COPY = {
|
|
|
149
151
|
menu: {
|
|
150
152
|
showCard: "\u663E\u793A\u4FA7\u8FB9\u680F\u5361\u7247",
|
|
151
153
|
hideCard: "\u9690\u85CF\u4FA7\u8FB9\u680F\u5361\u7247",
|
|
152
|
-
refreshNow: "\u7ACB\u5373\u5237\u65B0"
|
|
153
|
-
hideBall: "\u9690\u85CF\u60AC\u6D6E\u80F6\u56CA",
|
|
154
|
-
showBall: "\u663E\u793A\u60AC\u6D6E\u80F6\u56CA"
|
|
154
|
+
refreshNow: "\u7ACB\u5373\u5237\u65B0"
|
|
155
155
|
},
|
|
156
156
|
backToOverview: "\u8FD4\u56DE\u989D\u5EA6\u6982\u89C8",
|
|
157
157
|
providerNames: { glm: "GLM", copilot: "Copilot" },
|
|
@@ -184,9 +184,7 @@ var COPY = {
|
|
|
184
184
|
menu: {
|
|
185
185
|
showCard: "Show sidebar card",
|
|
186
186
|
hideCard: "Hide sidebar card",
|
|
187
|
-
refreshNow: "Refresh now"
|
|
188
|
-
hideBall: "Hide floating capsule",
|
|
189
|
-
showBall: "Show floating capsule"
|
|
187
|
+
refreshNow: "Refresh now"
|
|
190
188
|
},
|
|
191
189
|
backToOverview: "Back to quota overview",
|
|
192
190
|
providerNames: { glm: "GLM", copilot: "Copilot" },
|
|
@@ -236,11 +234,7 @@ var STYLE_TEXT = `
|
|
|
236
234
|
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
237
235
|
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
238
236
|
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
239
|
-
|
|
240
|
-
.dqw-card:hover .dqw-ball-toggle, .dqw-ball-toggle:focus-visible, .dqw-ball-toggle[data-ball-hidden="true"] { opacity: 1; }
|
|
241
|
-
.dqw-ball-toggle[data-ball-hidden="true"] { color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
242
|
-
.dqw-ball-toggle:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
243
|
-
.dqw-ball-toggle svg { display: block; width: 14px; height: 14px; }
|
|
237
|
+
@media print { :host { display: none !important; } }
|
|
244
238
|
`;
|
|
245
239
|
var FLOAT_STYLE_TEXT = `
|
|
246
240
|
:host { position: fixed; z-index: 2147483000; }
|
|
@@ -251,15 +245,17 @@ var FLOAT_STYLE_TEXT = `
|
|
|
251
245
|
.dqw-capsule-glm, .dqw-capsule-copilot { font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
252
246
|
.dqw-capsule-glm.warn, .dqw-capsule-copilot.warn { color: var(--dsw-alias-label-warning, #d29922); }
|
|
253
247
|
.dqw-capsule-glm.danger, .dqw-capsule-copilot.danger { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
248
|
+
.dqw-capsule[hidden] { display: none; }
|
|
254
249
|
@media (prefers-reduced-motion: no-preference) {
|
|
255
250
|
.dqw-capsule[data-alert] { animation: dqw-pulse 1.6s ease-in-out infinite; }
|
|
256
251
|
}
|
|
252
|
+
@media print { :host { display: none !important; } }
|
|
257
253
|
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
258
254
|
.dqw-menu { position: fixed; min-width: 150px; padding: 4px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 88%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); font: inherit; display: flex; flex-direction: column; }
|
|
259
255
|
.dqw-menu[hidden] { display: none; }
|
|
260
256
|
.dqw-menu-item { display: flex; align-items: center; gap: 6px; margin: 0; padding: 6px 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-size: 11px; line-height: 16px; text-align: left; cursor: pointer; }
|
|
261
257
|
.dqw-menu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
262
|
-
.dqw-panel { position: fixed; width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
258
|
+
.dqw-panel { position: fixed; z-index: 2147483000; min-width: 166px; max-width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
263
259
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
264
260
|
.dqw-panel[hidden] { display: none; }
|
|
265
261
|
.dqw-panel *, .dqw-panel *::before, .dqw-panel *::after { box-sizing: border-box; }
|
|
@@ -270,7 +266,7 @@ var FLOAT_STYLE_TEXT = `
|
|
|
270
266
|
.dqw-panel-back:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
271
267
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
272
268
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
273
|
-
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
269
|
+
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; max-width: 240px; }
|
|
274
270
|
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
275
271
|
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
276
272
|
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
@@ -349,36 +345,6 @@ function text(doc, tag, className, value) {
|
|
|
349
345
|
node.textContent = String(value ?? "");
|
|
350
346
|
return node;
|
|
351
347
|
}
|
|
352
|
-
function quotaIcon(doc) {
|
|
353
|
-
const namespace = "http://www.w3.org/2000/svg";
|
|
354
|
-
const svg = doc.createElementNS(namespace, "svg");
|
|
355
|
-
svg.setAttribute("viewBox", "0 0 24 24");
|
|
356
|
-
svg.setAttribute("width", "16");
|
|
357
|
-
svg.setAttribute("height", "16");
|
|
358
|
-
svg.setAttribute("fill", "none");
|
|
359
|
-
svg.setAttribute("stroke", "currentColor");
|
|
360
|
-
svg.setAttribute("stroke-width", "2");
|
|
361
|
-
svg.setAttribute("stroke-linecap", "round");
|
|
362
|
-
svg.setAttribute("stroke-linejoin", "round");
|
|
363
|
-
svg.setAttribute("aria-hidden", "true");
|
|
364
|
-
svg.setAttribute("focusable", "false");
|
|
365
|
-
const dial = doc.createElementNS(namespace, "circle");
|
|
366
|
-
dial.setAttribute("cx", "12");
|
|
367
|
-
dial.setAttribute("cy", "12");
|
|
368
|
-
dial.setAttribute("r", "10");
|
|
369
|
-
const ticks = doc.createElementNS(namespace, "path");
|
|
370
|
-
ticks.setAttribute("d", "M12 2.4v1.6M12 20v1.6M2.4 12h1.6M20 12h1.6M5.2 5.2l1.1 1.1M18.8 18.8l-1.1-1.1M18.8 5.2l-1.1 1.1M5.2 18.8l1.1-1.1");
|
|
371
|
-
const needle = doc.createElementNS(namespace, "path");
|
|
372
|
-
needle.setAttribute("d", "M12 12l4.5-4");
|
|
373
|
-
const hub = doc.createElementNS(namespace, "circle");
|
|
374
|
-
hub.setAttribute("cx", "12");
|
|
375
|
-
hub.setAttribute("cy", "12");
|
|
376
|
-
hub.setAttribute("r", "1.4");
|
|
377
|
-
hub.setAttribute("fill", "currentColor");
|
|
378
|
-
hub.setAttribute("stroke", "none");
|
|
379
|
-
svg.append(dial, ticks, needle, hub);
|
|
380
|
-
return svg;
|
|
381
|
-
}
|
|
382
348
|
function menuItem(doc, action, label) {
|
|
383
349
|
const item = doc.createElement("button");
|
|
384
350
|
item.type = "button";
|
|
@@ -465,7 +431,7 @@ function mountQuotaCard({
|
|
|
465
431
|
} = {}) {
|
|
466
432
|
if (!doc?.body || typeof fetchImpl !== "function") return () => {
|
|
467
433
|
};
|
|
468
|
-
if (doc.querySelector(CARD_SELECTOR)) return () => {
|
|
434
|
+
if (doc.querySelector(CARD_SELECTOR) || doc.querySelector(FLOAT_SELECTOR)) return () => {
|
|
469
435
|
};
|
|
470
436
|
const locale = localeFor(win);
|
|
471
437
|
const copy = COPY[locale];
|
|
@@ -485,15 +451,7 @@ function mountQuotaCard({
|
|
|
485
451
|
const lastUpdated = text(doc, "p", "dqw-meta", copy.loading);
|
|
486
452
|
lastUpdated.dataset.role = "updated";
|
|
487
453
|
body.append(lastUpdated);
|
|
488
|
-
|
|
489
|
-
ballToggle.type = "button";
|
|
490
|
-
ballToggle.className = "dqw-ball-toggle";
|
|
491
|
-
ballToggle.dataset.action = "toggle-ball";
|
|
492
|
-
ballToggle.append(quotaIcon(doc));
|
|
493
|
-
ballToggle.addEventListener("click", () => {
|
|
494
|
-
setBallSessionHidden(!ballSessionHidden);
|
|
495
|
-
});
|
|
496
|
-
card.append(body, ballToggle);
|
|
454
|
+
card.append(body);
|
|
497
455
|
cardRoot.append(style, card);
|
|
498
456
|
const panel = doc.createElement("div");
|
|
499
457
|
panel.className = "dqw-panel";
|
|
@@ -523,12 +481,8 @@ function mountQuotaCard({
|
|
|
523
481
|
let surface = buildCapsule();
|
|
524
482
|
floatRoot.append(surface);
|
|
525
483
|
const syncFloatVisibility = () => {
|
|
526
|
-
surface.hidden =
|
|
484
|
+
surface.hidden = dataHidden;
|
|
527
485
|
floatHost.hidden = dataHidden;
|
|
528
|
-
ballToggle.setAttribute("data-ball-hidden", String(ballSessionHidden));
|
|
529
|
-
const toggleLabel = ballSessionHidden ? copy.menu.showBall : copy.menu.hideBall;
|
|
530
|
-
ballToggle.setAttribute("aria-label", toggleLabel);
|
|
531
|
-
ballToggle.title = toggleLabel;
|
|
532
486
|
};
|
|
533
487
|
doc.documentElement.append(floatHost);
|
|
534
488
|
const applyFloatGeometry = () => {
|
|
@@ -542,7 +496,6 @@ function mountQuotaCard({
|
|
|
542
496
|
);
|
|
543
497
|
floatHost.style.left = `${Math.round(point.x)}px`;
|
|
544
498
|
floatHost.style.top = `${Math.round(point.y)}px`;
|
|
545
|
-
saveFloatGeometry(win.localStorage, point);
|
|
546
499
|
};
|
|
547
500
|
applyFloatGeometry();
|
|
548
501
|
floatRoot.append(panel);
|
|
@@ -607,18 +560,8 @@ function mountQuotaCard({
|
|
|
607
560
|
let queuedPlace = false;
|
|
608
561
|
let openKey;
|
|
609
562
|
let panelNav = false;
|
|
610
|
-
let ballSessionHidden = false;
|
|
611
563
|
let dataHidden = false;
|
|
612
564
|
let surfaceFlags = loadSurfaceFlags(win.localStorage);
|
|
613
|
-
const setBallSessionHidden = (hidden) => {
|
|
614
|
-
const wasHidden = ballSessionHidden;
|
|
615
|
-
ballSessionHidden = hidden;
|
|
616
|
-
if (hidden && !wasHidden && openKey !== void 0) {
|
|
617
|
-
openKey = void 0;
|
|
618
|
-
syncPop();
|
|
619
|
-
}
|
|
620
|
-
syncFloatVisibility();
|
|
621
|
-
};
|
|
622
565
|
const setCardHidden = (hidden) => {
|
|
623
566
|
surfaceFlags = { cardHidden: hidden };
|
|
624
567
|
saveSurfaceFlags(win.localStorage, surfaceFlags);
|
|
@@ -813,9 +756,13 @@ function mountQuotaCard({
|
|
|
813
756
|
}
|
|
814
757
|
}
|
|
815
758
|
let lastAnchor = null;
|
|
759
|
+
const anchorFromRow = (key) => () => body.querySelector(`[data-dsh-quota-watch-row="${key}"]`);
|
|
760
|
+
const anchorFromEl = (el) => () => el;
|
|
816
761
|
const placePanel = () => {
|
|
817
|
-
if (panel.hidden
|
|
818
|
-
const
|
|
762
|
+
if (panel.hidden) return;
|
|
763
|
+
const anchor = lastAnchor?.();
|
|
764
|
+
if (!anchor || !anchor.isConnected) return;
|
|
765
|
+
const rect = anchor.getBoundingClientRect();
|
|
819
766
|
const viewWidth = win.innerWidth ?? 1024;
|
|
820
767
|
const viewHeight = win.innerHeight ?? 768;
|
|
821
768
|
const width = panel.offsetWidth || 240;
|
|
@@ -917,7 +864,6 @@ function mountQuotaCard({
|
|
|
917
864
|
items.push(menuItem(doc, "toggle-card", surfaceFlags.cardHidden ? copy.menu.showCard : copy.menu.hideCard));
|
|
918
865
|
}
|
|
919
866
|
items.push(menuItem(doc, "refresh", copy.menu.refreshNow));
|
|
920
|
-
items.push(menuItem(doc, "hide-ball", copy.menu.hideBall));
|
|
921
867
|
menu.replaceChildren(...items);
|
|
922
868
|
menu.hidden = false;
|
|
923
869
|
const rect = surface.getBoundingClientRect();
|
|
@@ -943,12 +889,23 @@ function mountQuotaCard({
|
|
|
943
889
|
setCardHidden(!surfaceFlags.cardHidden);
|
|
944
890
|
} else if (action === "refresh") {
|
|
945
891
|
void poll(true);
|
|
946
|
-
} else if (action === "hide-ball") {
|
|
947
|
-
setBallSessionHidden(true);
|
|
948
892
|
}
|
|
949
893
|
menu.hidden = true;
|
|
950
894
|
};
|
|
951
895
|
menu.addEventListener("click", onMenuClick);
|
|
896
|
+
let currentFoot = null;
|
|
897
|
+
let footTooNarrow = false;
|
|
898
|
+
let noDataHidden = false;
|
|
899
|
+
const syncFootNarrowness = () => {
|
|
900
|
+
footTooNarrow = currentFoot !== null && currentFoot.clientWidth > 0 && currentFoot.clientWidth < CARD_MIN_WIDTH;
|
|
901
|
+
};
|
|
902
|
+
const syncCardVisibility = () => {
|
|
903
|
+
syncFootNarrowness();
|
|
904
|
+
container.hidden = surfaceFlags.cardHidden || sidebarCollapsed || footTooNarrow || noDataHidden;
|
|
905
|
+
};
|
|
906
|
+
const footResizeObserver = typeof win.ResizeObserver === "function" ? new win.ResizeObserver(() => {
|
|
907
|
+
syncCardVisibility();
|
|
908
|
+
}) : null;
|
|
952
909
|
const renderAll = () => {
|
|
953
910
|
const providers = snapshot?.providers ?? [];
|
|
954
911
|
const glm = providers.find((provider) => provider?.key === "glm");
|
|
@@ -960,7 +917,8 @@ function mountQuotaCard({
|
|
|
960
917
|
if (glmParts) fragment.append(...glmParts);
|
|
961
918
|
if (copilotParts) fragment.append(...copilotParts);
|
|
962
919
|
body.replaceChildren(fragment);
|
|
963
|
-
|
|
920
|
+
noDataHidden = glmParts === null && copilotParts === null;
|
|
921
|
+
syncCardVisibility();
|
|
964
922
|
const updated = formatTime(snapshot?.updatedAt ? new Date(snapshot.updatedAt).toISOString() : "", locale);
|
|
965
923
|
if (updated) {
|
|
966
924
|
lastUpdated.hidden = true;
|
|
@@ -995,6 +953,11 @@ function mountQuotaCard({
|
|
|
995
953
|
if (container.parentElement !== foot || foot.firstElementChild !== container) {
|
|
996
954
|
foot.insertBefore(container, foot.firstChild);
|
|
997
955
|
}
|
|
956
|
+
if (currentFoot !== foot) {
|
|
957
|
+
currentFoot = foot;
|
|
958
|
+
footResizeObserver?.disconnect();
|
|
959
|
+
footResizeObserver?.observe(foot);
|
|
960
|
+
}
|
|
998
961
|
};
|
|
999
962
|
const schedulePlace = () => {
|
|
1000
963
|
if (queuedPlace || disposed) return;
|
|
@@ -1004,10 +967,24 @@ function mountQuotaCard({
|
|
|
1004
967
|
if (!disposed) place();
|
|
1005
968
|
});
|
|
1006
969
|
};
|
|
970
|
+
let sidebarCollapsed = Boolean(doc.querySelector("[data-sidebar-collapsed]"));
|
|
971
|
+
const syncSidebarCollapsed = () => {
|
|
972
|
+
const next = Boolean(doc.querySelector("[data-sidebar-collapsed]"));
|
|
973
|
+
if (next !== sidebarCollapsed) {
|
|
974
|
+
sidebarCollapsed = next;
|
|
975
|
+
renderAll();
|
|
976
|
+
}
|
|
977
|
+
};
|
|
1007
978
|
const observer = new win.MutationObserver(() => {
|
|
979
|
+
syncSidebarCollapsed();
|
|
1008
980
|
schedulePlace();
|
|
1009
981
|
});
|
|
1010
|
-
observer.observe(doc.body, {
|
|
982
|
+
observer.observe(doc.body, {
|
|
983
|
+
childList: true,
|
|
984
|
+
subtree: true,
|
|
985
|
+
attributes: true,
|
|
986
|
+
attributeFilter: ["data-sidebar-collapsed"]
|
|
987
|
+
});
|
|
1011
988
|
place();
|
|
1012
989
|
const startPolling = () => {
|
|
1013
990
|
if (timer !== void 0 || doc.visibilityState === "hidden" || disposed) return;
|
|
@@ -1024,15 +1001,15 @@ function mountQuotaCard({
|
|
|
1024
1001
|
if (doc.visibilityState === "hidden") stopPolling();
|
|
1025
1002
|
else startPolling();
|
|
1026
1003
|
};
|
|
1027
|
-
const toggleRow = (key
|
|
1004
|
+
const toggleRow = (key) => {
|
|
1028
1005
|
panelNav = false;
|
|
1029
|
-
lastAnchor =
|
|
1006
|
+
lastAnchor = anchorFromRow(key);
|
|
1030
1007
|
openKey = openKey === key ? void 0 : key;
|
|
1031
1008
|
renderAll();
|
|
1032
1009
|
};
|
|
1033
1010
|
const openOverview = (anchorEl) => {
|
|
1034
1011
|
panelNav = false;
|
|
1035
|
-
lastAnchor = anchorEl
|
|
1012
|
+
lastAnchor = anchorFromEl(anchorEl);
|
|
1036
1013
|
openKey = "overview";
|
|
1037
1014
|
syncPop();
|
|
1038
1015
|
};
|
|
@@ -1060,7 +1037,7 @@ function mountQuotaCard({
|
|
|
1060
1037
|
return;
|
|
1061
1038
|
}
|
|
1062
1039
|
const row = event.target?.closest?.("[data-dsh-quota-watch-row][data-expandable]");
|
|
1063
|
-
if (row) toggleRow(row.dataset.dshQuotaWatchRow
|
|
1040
|
+
if (row) toggleRow(row.dataset.dshQuotaWatchRow);
|
|
1064
1041
|
};
|
|
1065
1042
|
const onBodyKeydown = (event) => {
|
|
1066
1043
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
@@ -1085,6 +1062,16 @@ function mountQuotaCard({
|
|
|
1085
1062
|
};
|
|
1086
1063
|
const onWinResize = () => {
|
|
1087
1064
|
placePanel();
|
|
1065
|
+
syncCardVisibility();
|
|
1066
|
+
const viewWidth = win.innerWidth ?? 1024;
|
|
1067
|
+
const viewHeight = win.innerHeight ?? 768;
|
|
1068
|
+
const x = parseFloat(floatHost.style.left);
|
|
1069
|
+
const y = parseFloat(floatHost.style.top);
|
|
1070
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
1071
|
+
const point = clampPoint({ x, y }, { width: viewWidth, height: viewHeight }, SURFACE_SIZE);
|
|
1072
|
+
floatHost.style.left = `${Math.round(point.x)}px`;
|
|
1073
|
+
floatHost.style.top = `${Math.round(point.y)}px`;
|
|
1074
|
+
}
|
|
1088
1075
|
};
|
|
1089
1076
|
const onDocKeydown = (event) => {
|
|
1090
1077
|
if (event.key !== "Escape") return;
|
package/package.json
CHANGED
package/src/client/prefs.mjs
CHANGED
|
@@ -47,14 +47,15 @@ export function saveFloatGeometry(storage, point) {
|
|
|
47
47
|
export function loadSurfaceFlags(storage) {
|
|
48
48
|
try {
|
|
49
49
|
const raw = storage.getItem(SURFACE_FLAGS_KEY)
|
|
50
|
-
|
|
50
|
+
// The sidebar card is opt-in: the floating capsule is the default surface.
|
|
51
|
+
if (typeof raw !== 'string' || raw === '') return { cardHidden: true }
|
|
51
52
|
const parsed = JSON.parse(raw)
|
|
52
53
|
if (typeof parsed !== 'object' || parsed === null || typeof parsed.cardHidden !== 'boolean') {
|
|
53
|
-
return { cardHidden:
|
|
54
|
+
return { cardHidden: true }
|
|
54
55
|
}
|
|
55
56
|
return { cardHidden: parsed.cardHidden }
|
|
56
57
|
} catch {
|
|
57
|
-
return { cardHidden:
|
|
58
|
+
return { cardHidden: true }
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
package/src/client.mjs
CHANGED
|
@@ -6,9 +6,14 @@ export const name = 'quota-watch-client'
|
|
|
6
6
|
export const inject = []
|
|
7
7
|
|
|
8
8
|
const CARD_SELECTOR = '[data-dsh-quota-watch-card]'
|
|
9
|
+
const FLOAT_SELECTOR = '[data-dsh-quota-watch-float]'
|
|
9
10
|
const FETCH_TIMEOUT_MS = 15_000
|
|
10
11
|
const SURFACE_SIZE = 38
|
|
11
12
|
const SURFACE_MARGIN = 16
|
|
13
|
+
// Below this a mounted card would clip into unreadable text (collapsed web
|
|
14
|
+
// rails, squeezed desktop layouts) — hide it and let the capsule carry the
|
|
15
|
+
// entry. clientWidth 0 (no layout yet, jsdom) counts as "unknown", not narrow.
|
|
16
|
+
const CARD_MIN_WIDTH = 160
|
|
12
17
|
|
|
13
18
|
const COPY = {
|
|
14
19
|
zh: {
|
|
@@ -19,8 +24,6 @@ const COPY = {
|
|
|
19
24
|
showCard: '显示侧边栏卡片',
|
|
20
25
|
hideCard: '隐藏侧边栏卡片',
|
|
21
26
|
refreshNow: '立即刷新',
|
|
22
|
-
hideBall: '隐藏悬浮胶囊',
|
|
23
|
-
showBall: '显示悬浮胶囊',
|
|
24
27
|
},
|
|
25
28
|
backToOverview: '返回额度概览',
|
|
26
29
|
providerNames: { glm: 'GLM', copilot: 'Copilot' },
|
|
@@ -54,8 +57,6 @@ const COPY = {
|
|
|
54
57
|
showCard: 'Show sidebar card',
|
|
55
58
|
hideCard: 'Hide sidebar card',
|
|
56
59
|
refreshNow: 'Refresh now',
|
|
57
|
-
hideBall: 'Hide floating capsule',
|
|
58
|
-
showBall: 'Show floating capsule',
|
|
59
60
|
},
|
|
60
61
|
backToOverview: 'Back to quota overview',
|
|
61
62
|
providerNames: { glm: 'GLM', copilot: 'Copilot' },
|
|
@@ -106,11 +107,7 @@ const STYLE_TEXT = `
|
|
|
106
107
|
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
107
108
|
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
108
109
|
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
109
|
-
|
|
110
|
-
.dqw-card:hover .dqw-ball-toggle, .dqw-ball-toggle:focus-visible, .dqw-ball-toggle[data-ball-hidden="true"] { opacity: 1; }
|
|
111
|
-
.dqw-ball-toggle[data-ball-hidden="true"] { color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
112
|
-
.dqw-ball-toggle:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
113
|
-
.dqw-ball-toggle svg { display: block; width: 14px; height: 14px; }
|
|
110
|
+
@media print { :host { display: none !important; } }
|
|
114
111
|
`
|
|
115
112
|
|
|
116
113
|
// Float shell (capsule) and the panel live on their own shadow host at the
|
|
@@ -127,15 +124,17 @@ const FLOAT_STYLE_TEXT = `
|
|
|
127
124
|
.dqw-capsule-glm, .dqw-capsule-copilot { font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
128
125
|
.dqw-capsule-glm.warn, .dqw-capsule-copilot.warn { color: var(--dsw-alias-label-warning, #d29922); }
|
|
129
126
|
.dqw-capsule-glm.danger, .dqw-capsule-copilot.danger { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
127
|
+
.dqw-capsule[hidden] { display: none; }
|
|
130
128
|
@media (prefers-reduced-motion: no-preference) {
|
|
131
129
|
.dqw-capsule[data-alert] { animation: dqw-pulse 1.6s ease-in-out infinite; }
|
|
132
130
|
}
|
|
131
|
+
@media print { :host { display: none !important; } }
|
|
133
132
|
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
134
133
|
.dqw-menu { position: fixed; min-width: 150px; padding: 4px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 88%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); font: inherit; display: flex; flex-direction: column; }
|
|
135
134
|
.dqw-menu[hidden] { display: none; }
|
|
136
135
|
.dqw-menu-item { display: flex; align-items: center; gap: 6px; margin: 0; padding: 6px 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-size: 11px; line-height: 16px; text-align: left; cursor: pointer; }
|
|
137
136
|
.dqw-menu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
138
|
-
.dqw-panel { position: fixed; width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
137
|
+
.dqw-panel { position: fixed; z-index: 2147483000; min-width: 166px; max-width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
139
138
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
140
139
|
.dqw-panel[hidden] { display: none; }
|
|
141
140
|
.dqw-panel *, .dqw-panel *::before, .dqw-panel *::after { box-sizing: border-box; }
|
|
@@ -146,7 +145,7 @@ const FLOAT_STYLE_TEXT = `
|
|
|
146
145
|
.dqw-panel-back:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
147
146
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
148
147
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
149
|
-
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
148
|
+
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; max-width: 240px; }
|
|
150
149
|
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
151
150
|
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
152
151
|
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
@@ -363,7 +362,7 @@ export function mountQuotaCard({
|
|
|
363
362
|
pollIntervalMs = CLIENT_POLL_INTERVAL_MS,
|
|
364
363
|
} = {}) {
|
|
365
364
|
if (!doc?.body || typeof fetchImpl !== 'function') return () => {}
|
|
366
|
-
if (doc.querySelector(CARD_SELECTOR)) return () => {}
|
|
365
|
+
if (doc.querySelector(CARD_SELECTOR) || doc.querySelector(FLOAT_SELECTOR)) return () => {}
|
|
367
366
|
|
|
368
367
|
const locale = localeFor(win)
|
|
369
368
|
const copy = COPY[locale]
|
|
@@ -385,13 +384,7 @@ export function mountQuotaCard({
|
|
|
385
384
|
const lastUpdated = text(doc, 'p', 'dqw-meta', copy.loading)
|
|
386
385
|
lastUpdated.dataset.role = 'updated'
|
|
387
386
|
body.append(lastUpdated)
|
|
388
|
-
|
|
389
|
-
ballToggle.type = 'button'
|
|
390
|
-
ballToggle.className = 'dqw-ball-toggle'
|
|
391
|
-
ballToggle.dataset.action = 'toggle-ball'
|
|
392
|
-
ballToggle.append(quotaIcon(doc))
|
|
393
|
-
ballToggle.addEventListener('click', () => { setBallSessionHidden(!ballSessionHidden) })
|
|
394
|
-
card.append(body, ballToggle)
|
|
387
|
+
card.append(body)
|
|
395
388
|
cardRoot.append(style, card)
|
|
396
389
|
// Details open in a viewport-level floating panel — the only detail surface.
|
|
397
390
|
const panel = doc.createElement('div')
|
|
@@ -423,14 +416,10 @@ export function mountQuotaCard({
|
|
|
423
416
|
// Capsule is the only floating surface (0.1.1: the round ball was retired).
|
|
424
417
|
let surface = buildCapsule()
|
|
425
418
|
floatRoot.append(surface)
|
|
426
|
-
/**
|
|
419
|
+
/** The capsule is always present unless no provider reports anything. */
|
|
427
420
|
const syncFloatVisibility = () => {
|
|
428
|
-
surface.hidden =
|
|
421
|
+
surface.hidden = dataHidden
|
|
429
422
|
floatHost.hidden = dataHidden
|
|
430
|
-
ballToggle.setAttribute('data-ball-hidden', String(ballSessionHidden))
|
|
431
|
-
const toggleLabel = ballSessionHidden ? copy.menu.showBall : copy.menu.hideBall
|
|
432
|
-
ballToggle.setAttribute('aria-label', toggleLabel)
|
|
433
|
-
ballToggle.title = toggleLabel
|
|
434
423
|
}
|
|
435
424
|
doc.documentElement.append(floatHost)
|
|
436
425
|
const applyFloatGeometry = () => {
|
|
@@ -444,8 +433,9 @@ export function mountQuotaCard({
|
|
|
444
433
|
)
|
|
445
434
|
floatHost.style.left = `${Math.round(point.x)}px`
|
|
446
435
|
floatHost.style.top = `${Math.round(point.y)}px`
|
|
447
|
-
//
|
|
448
|
-
|
|
436
|
+
// Deliberately NOT persisted here: a window that starts small (Electron
|
|
437
|
+
// restore, split-screen) must not clobber the user's preferred coordinates.
|
|
438
|
+
// The visual clamp above applies every mount; storage updates on drag only.
|
|
449
439
|
}
|
|
450
440
|
applyFloatGeometry()
|
|
451
441
|
// Panel lives inside the float shadow root from now on (契约总表「归属时序」).
|
|
@@ -515,20 +505,8 @@ export function mountQuotaCard({
|
|
|
515
505
|
let queuedPlace = false
|
|
516
506
|
let openKey
|
|
517
507
|
let panelNav = false
|
|
518
|
-
let ballSessionHidden = false
|
|
519
508
|
let dataHidden = false
|
|
520
509
|
let surfaceFlags = loadSurfaceFlags(win.localStorage)
|
|
521
|
-
const setBallSessionHidden = (hidden) => {
|
|
522
|
-
const wasHidden = ballSessionHidden
|
|
523
|
-
ballSessionHidden = hidden
|
|
524
|
-
// Closing the panel is a one-time consequence of hiding, not a steady state:
|
|
525
|
-
// card rows must keep opening the panel while the ball stays hidden.
|
|
526
|
-
if (hidden && !wasHidden && openKey !== undefined) {
|
|
527
|
-
openKey = undefined
|
|
528
|
-
syncPop()
|
|
529
|
-
}
|
|
530
|
-
syncFloatVisibility()
|
|
531
|
-
}
|
|
532
510
|
const setCardHidden = (hidden) => {
|
|
533
511
|
surfaceFlags = { cardHidden: hidden }
|
|
534
512
|
saveSurfaceFlags(win.localStorage, surfaceFlags)
|
|
@@ -741,10 +719,17 @@ export function mountQuotaCard({
|
|
|
741
719
|
}
|
|
742
720
|
|
|
743
721
|
/** Anchor the panel beside its opener (a card row today, the float surface later). */
|
|
722
|
+
// Anchor resolver: card rows are re-created on every render pass, so keep a
|
|
723
|
+
// "how to find the anchor" closure instead of a detached element reference
|
|
724
|
+
// (a detached node's getBoundingClientRect is all zeros → panel teleports).
|
|
744
725
|
let lastAnchor = null
|
|
726
|
+
const anchorFromRow = (key) => () => body.querySelector(`[data-dsh-quota-watch-row="${key}"]`)
|
|
727
|
+
const anchorFromEl = (el) => () => el
|
|
745
728
|
const placePanel = () => {
|
|
746
|
-
if (panel.hidden
|
|
747
|
-
const
|
|
729
|
+
if (panel.hidden) return
|
|
730
|
+
const anchor = lastAnchor?.()
|
|
731
|
+
if (!anchor || !anchor.isConnected) return
|
|
732
|
+
const rect = anchor.getBoundingClientRect()
|
|
748
733
|
const viewWidth = win.innerWidth ?? 1024
|
|
749
734
|
const viewHeight = win.innerHeight ?? 768
|
|
750
735
|
const width = panel.offsetWidth || 240
|
|
@@ -853,7 +838,6 @@ export function mountQuotaCard({
|
|
|
853
838
|
items.push(menuItem(doc, 'toggle-card', surfaceFlags.cardHidden ? copy.menu.showCard : copy.menu.hideCard))
|
|
854
839
|
}
|
|
855
840
|
items.push(menuItem(doc, 'refresh', copy.menu.refreshNow))
|
|
856
|
-
items.push(menuItem(doc, 'hide-ball', copy.menu.hideBall))
|
|
857
841
|
menu.replaceChildren(...items)
|
|
858
842
|
menu.hidden = false
|
|
859
843
|
const rect = surface.getBoundingClientRect()
|
|
@@ -879,13 +863,28 @@ export function mountQuotaCard({
|
|
|
879
863
|
setCardHidden(!surfaceFlags.cardHidden)
|
|
880
864
|
} else if (action === 'refresh') {
|
|
881
865
|
void poll(true)
|
|
882
|
-
} else if (action === 'hide-ball') {
|
|
883
|
-
setBallSessionHidden(true)
|
|
884
866
|
}
|
|
885
867
|
menu.hidden = true
|
|
886
868
|
}
|
|
887
869
|
menu.addEventListener('click', onMenuClick)
|
|
888
870
|
|
|
871
|
+
// Cross-profile narrow-container detection: a measurably narrow foot would
|
|
872
|
+
// clip the card into unreadable text regardless of HOW the host signals its
|
|
873
|
+
// collapsed state — hide the card and let the capsule carry the entry.
|
|
874
|
+
// clientWidth 0 (no layout yet, jsdom) counts as "unknown", not narrow.
|
|
875
|
+
let currentFoot = null
|
|
876
|
+
let footTooNarrow = false
|
|
877
|
+
let noDataHidden = false
|
|
878
|
+
const syncFootNarrowness = () => {
|
|
879
|
+
footTooNarrow = currentFoot !== null && currentFoot.clientWidth > 0 && currentFoot.clientWidth < CARD_MIN_WIDTH
|
|
880
|
+
}
|
|
881
|
+
const syncCardVisibility = () => {
|
|
882
|
+
syncFootNarrowness()
|
|
883
|
+
container.hidden = surfaceFlags.cardHidden || sidebarCollapsed || footTooNarrow || noDataHidden
|
|
884
|
+
}
|
|
885
|
+
const footResizeObserver = typeof win.ResizeObserver === 'function'
|
|
886
|
+
? new win.ResizeObserver(() => { syncCardVisibility() })
|
|
887
|
+
: null
|
|
889
888
|
const renderAll = () => {
|
|
890
889
|
const providers = snapshot?.providers ?? []
|
|
891
890
|
const glm = providers.find((provider) => provider?.key === 'glm')
|
|
@@ -897,7 +896,8 @@ export function mountQuotaCard({
|
|
|
897
896
|
if (glmParts) fragment.append(...glmParts)
|
|
898
897
|
if (copilotParts) fragment.append(...copilotParts)
|
|
899
898
|
body.replaceChildren(fragment)
|
|
900
|
-
|
|
899
|
+
noDataHidden = glmParts === null && copilotParts === null
|
|
900
|
+
syncCardVisibility()
|
|
901
901
|
const updated = formatTime(snapshot?.updatedAt ? new Date(snapshot.updatedAt).toISOString() : '', locale)
|
|
902
902
|
if (updated) {
|
|
903
903
|
lastUpdated.hidden = true
|
|
@@ -939,6 +939,11 @@ export function mountQuotaCard({
|
|
|
939
939
|
if (container.parentElement !== foot || foot.firstElementChild !== container) {
|
|
940
940
|
foot.insertBefore(container, foot.firstChild)
|
|
941
941
|
}
|
|
942
|
+
if (currentFoot !== foot) {
|
|
943
|
+
currentFoot = foot
|
|
944
|
+
footResizeObserver?.disconnect()
|
|
945
|
+
footResizeObserver?.observe(foot)
|
|
946
|
+
}
|
|
942
947
|
}
|
|
943
948
|
|
|
944
949
|
const schedulePlace = () => {
|
|
@@ -950,10 +955,28 @@ export function mountQuotaCard({
|
|
|
950
955
|
})
|
|
951
956
|
}
|
|
952
957
|
|
|
958
|
+
// Web narrow-rail collapse: the host keeps the footArea but squeezes it, which
|
|
959
|
+
// clips the card into garbled text — hide the card and let the capsule carry
|
|
960
|
+
// the entry (0.0.18 shipped dedicated rail CSS; 0.1.0 retired it, and 0.1.2
|
|
961
|
+
// reintroduces only this visibility signal — zero layout coupling).
|
|
962
|
+
let sidebarCollapsed = Boolean(doc.querySelector('[data-sidebar-collapsed]'))
|
|
963
|
+
const syncSidebarCollapsed = () => {
|
|
964
|
+
const next = Boolean(doc.querySelector('[data-sidebar-collapsed]'))
|
|
965
|
+
if (next !== sidebarCollapsed) {
|
|
966
|
+
sidebarCollapsed = next
|
|
967
|
+
renderAll()
|
|
968
|
+
}
|
|
969
|
+
}
|
|
953
970
|
const observer = new win.MutationObserver(() => {
|
|
971
|
+
syncSidebarCollapsed()
|
|
954
972
|
schedulePlace()
|
|
955
973
|
})
|
|
956
|
-
observer.observe(doc.body, {
|
|
974
|
+
observer.observe(doc.body, {
|
|
975
|
+
childList: true,
|
|
976
|
+
subtree: true,
|
|
977
|
+
attributes: true,
|
|
978
|
+
attributeFilter: ['data-sidebar-collapsed'],
|
|
979
|
+
})
|
|
957
980
|
place()
|
|
958
981
|
|
|
959
982
|
const startPolling = () => {
|
|
@@ -969,15 +992,15 @@ export function mountQuotaCard({
|
|
|
969
992
|
if (doc.visibilityState === 'hidden') stopPolling()
|
|
970
993
|
else startPolling()
|
|
971
994
|
}
|
|
972
|
-
const toggleRow = (key
|
|
995
|
+
const toggleRow = (key) => {
|
|
973
996
|
panelNav = false
|
|
974
|
-
lastAnchor =
|
|
997
|
+
lastAnchor = anchorFromRow(key)
|
|
975
998
|
openKey = openKey === key ? undefined : key
|
|
976
999
|
renderAll()
|
|
977
1000
|
}
|
|
978
1001
|
const openOverview = (anchorEl) => {
|
|
979
1002
|
panelNav = false
|
|
980
|
-
lastAnchor = anchorEl
|
|
1003
|
+
lastAnchor = anchorFromEl(anchorEl)
|
|
981
1004
|
openKey = 'overview'
|
|
982
1005
|
syncPop()
|
|
983
1006
|
}
|
|
@@ -1005,7 +1028,7 @@ export function mountQuotaCard({
|
|
|
1005
1028
|
return
|
|
1006
1029
|
}
|
|
1007
1030
|
const row = event.target?.closest?.('[data-dsh-quota-watch-row][data-expandable]')
|
|
1008
|
-
if (row) toggleRow(row.dataset.dshQuotaWatchRow
|
|
1031
|
+
if (row) toggleRow(row.dataset.dshQuotaWatchRow)
|
|
1009
1032
|
}
|
|
1010
1033
|
const onBodyKeydown = (event) => {
|
|
1011
1034
|
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
@@ -1026,7 +1049,21 @@ export function mountQuotaCard({
|
|
|
1026
1049
|
renderAll()
|
|
1027
1050
|
}
|
|
1028
1051
|
const onDocScroll = () => { placePanel() }
|
|
1029
|
-
const onWinResize = () => {
|
|
1052
|
+
const onWinResize = () => {
|
|
1053
|
+
placePanel()
|
|
1054
|
+
syncCardVisibility()
|
|
1055
|
+
// Keep the capsule inside a shrunk viewport; storage keeps the user's
|
|
1056
|
+
// preferred coordinates — only the visual position is clamped here.
|
|
1057
|
+
const viewWidth = win.innerWidth ?? 1024
|
|
1058
|
+
const viewHeight = win.innerHeight ?? 768
|
|
1059
|
+
const x = parseFloat(floatHost.style.left)
|
|
1060
|
+
const y = parseFloat(floatHost.style.top)
|
|
1061
|
+
if (Number.isFinite(x) && Number.isFinite(y)) {
|
|
1062
|
+
const point = clampPoint({ x, y }, { width: viewWidth, height: viewHeight }, SURFACE_SIZE)
|
|
1063
|
+
floatHost.style.left = `${Math.round(point.x)}px`
|
|
1064
|
+
floatHost.style.top = `${Math.round(point.y)}px`
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1030
1067
|
const onDocKeydown = (event) => {
|
|
1031
1068
|
if (event.key !== 'Escape') return
|
|
1032
1069
|
if (!menu.hidden) {
|