@kidli1412/dsh-token-heatmap 0.1.6 → 0.4.1
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 +36 -12
- package/docs//345/215/241/347/211/207/350/256/276/347/275/256/351/235/242/346/235/277.jpg +0 -0
- package/docs//345/271/264/350/247/206/345/233/276.jpg +0 -0
- package/docs//346/234/210/350/247/206/345/233/276.jpg +0 -0
- package/docs//351/242/204/350/247/210-/346/226/260/347/211/210/344/274/232/350/257/235/351/241/265.jpg +0 -0
- package/lib/client.js +505 -370
- package/lib/config.js +17 -4
- package/lib/index.js +131 -35
- package/lib/usage.js +6 -2
- package/package.json +2 -2
- package/docs//347/203/255/345/212/233/345/233/276.jpg +0 -0
package/lib/client.js
CHANGED
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
* dsh-token-heatmap — browser half.
|
|
3
3
|
*
|
|
4
4
|
* Hand-written `__ModuleLoader__` bundle (no build step): registers into the
|
|
5
|
-
* conversation `input.dock` list slot and renders a GitHub-style
|
|
6
|
-
*
|
|
7
|
-
* all seven weekday labels on the left)
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* (
|
|
11
|
-
*
|
|
12
|
-
*
|
|
5
|
+
* conversation `input.dock` list slot and renders a GitHub-style token-usage
|
|
6
|
+
* card on the new-session (hero) screen — a calendar-year grid (Jan 1 – Dec 31;
|
|
7
|
+
* columns = weeks, rows = Mon..Sun, all seven weekday labels on the left) or a
|
|
8
|
+
* single-month calendar, switchable from the header, plus today / this-month /
|
|
9
|
+
* all-time totals and a ‹period› stepper. The card configures ITSELF through a
|
|
10
|
+
* ⚙ panel (palette + default view) persisted in the `token-heatmap` settings
|
|
11
|
+
* namespace (settings scope); it deliberately does not register into the
|
|
12
|
+
* official `settings.plugin.item` seat (设置 → 插件 → 插件配置), so all of its
|
|
13
|
+
* settings live on the card.
|
|
13
14
|
*
|
|
14
|
-
* The card is shown ONLY on the new-session (hero) screen — gated on
|
|
15
|
-
* `session.composerPhase === "blank"`
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* framework's true below-card seat
|
|
19
|
-
* screen).
|
|
15
|
+
* The card is shown ONLY on the new-session (hero) screen — gated on the
|
|
16
|
+
* boolean `session.blank`, with `composerPhase === "blank"` as the pre-0.1.2
|
|
17
|
+
* fallback — and is positioned visually BELOW the composer input card via a
|
|
18
|
+
* flex `order` on the dock wrapper (the dock's DOM site sits above the bar, but
|
|
19
|
+
* the composer stack is a flex column and the framework's true below-card seat
|
|
20
|
+
* `composer.dock` is disabled on the hero screen). There is no display switch:
|
|
21
|
+
* the hero screen always renders it.
|
|
20
22
|
*
|
|
21
23
|
* Cell colors use ABSOLUTE token thresholds (not window-relative ranks), so
|
|
22
24
|
* a huge day is always dark regardless of its neighbours:
|
|
@@ -38,18 +40,28 @@ window.__ModuleLoader__.load({
|
|
|
38
40
|
|
|
39
41
|
//#region css
|
|
40
42
|
const css = [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// Surface matched to the composer card above it (measured computed
|
|
44
|
+
// styles): same 22px radius, no hard border — the edge is the same
|
|
45
|
+
// hairline + soft drop the composer uses — and the same 16px inner
|
|
46
|
+
// inset, so the card's text lines up with the composer's placeholder.
|
|
47
|
+
".thm_dock{box-sizing:border-box;width:100%;max-width:var(--dsh-composer-card-max-width,780px);margin:0 auto;padding:0;display:flex;flex-direction:column;gap:8px}",
|
|
48
|
+
".thm_card{box-sizing:border-box;background:var(--dsw-alias-bg-base);border:none;border-radius:22px;box-shadow:0 0 0 .5px rgba(0,0,0,.1),0 4px 16px rgba(0,0,0,.03),0 0 24px rgba(0,0,0,.03);padding:14px 16px 16px;display:flex;flex-direction:column;gap:8px}",
|
|
43
49
|
".thm_head{flex-wrap:wrap;align-items:baseline;gap:4px 12px;display:flex}",
|
|
44
50
|
".thm_title{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;line-height:18px}",
|
|
45
51
|
".thm_stat{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}",
|
|
46
52
|
".thm_statLabel{margin-right:4px}",
|
|
47
53
|
".thm_statValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;line-height:18px;font-variant-numeric:tabular-nums}",
|
|
48
|
-
".
|
|
54
|
+
".thm_nav{align-items:center;gap:2px;display:inline-flex}",
|
|
49
55
|
".thm_yearBtn{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:4px;padding:0 5px;font-size:13px;line-height:18px}",
|
|
50
56
|
".thm_yearBtn:hover:not(:disabled){color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
51
57
|
".thm_yearBtn:disabled{opacity:.35;cursor:default}",
|
|
52
58
|
".thm_yearLabel{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px;min-width:36px;text-align:center;font-variant-numeric:tabular-nums}",
|
|
59
|
+
".thm_monthLabelNav{min-width:62px}",
|
|
60
|
+
".thm_viewNav{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;align-items:center;display:inline-flex;overflow:hidden}",
|
|
61
|
+
".thm_viewBtn{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;padding:1px 10px;font:inherit;font-size:11px;line-height:18px}",
|
|
62
|
+
".thm_viewBtn:hover:not(:disabled){color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
63
|
+
".thm_viewBtn[data-active=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);font-weight:600}",
|
|
64
|
+
".thm_viewBtn:disabled{opacity:.4;cursor:default}",
|
|
53
65
|
".thm_refresh{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:6px;margin-left:auto;padding:2px 6px;font:inherit;font-size:11px;line-height:16px}",
|
|
54
66
|
".thm_refresh:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
55
67
|
".thm_refresh:disabled{opacity:.5;cursor:default}",
|
|
@@ -60,49 +72,50 @@ window.__ModuleLoader__.load({
|
|
|
60
72
|
".thm_row{display:flex;gap:2px}",
|
|
61
73
|
".thm_rowLabel{color:var(--dsw-alias-label-caption);width:16px;flex:none;font-size:9px;line-height:12px;text-align:center}",
|
|
62
74
|
".thm_cell{box-sizing:border-box;width:10px;height:10px;border-radius:2px;flex:none}",
|
|
63
|
-
|
|
64
|
-
".
|
|
75
|
+
// The calendar spans the card's full content width, so the weekday
|
|
76
|
+
// rail (一) starts exactly under the "Token 用量" title.
|
|
77
|
+
".thm_monthCal{display:flex;flex-direction:column;gap:3px}",
|
|
78
|
+
".thm_weekHead{display:flex;gap:4px}",
|
|
79
|
+
".thm_weekHeadCell{color:var(--dsw-alias-label-tertiary);flex:1;min-width:0;font-size:9px;line-height:12px;text-align:center}",
|
|
80
|
+
".thm_week{display:flex;gap:4px}",
|
|
81
|
+
".thm_day{box-sizing:border-box;flex:1;min-width:0;height:38px;border-radius:5px;flex-direction:column;align-items:center;justify-content:center;gap:1px;display:flex}",
|
|
82
|
+
".thm_day[data-weekend=true]:not([data-empty=true]){background-color:var(--dsw-alias-interactive-bg-hover)}",
|
|
83
|
+
".thm_dayNum{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:13px;font-variant-numeric:tabular-nums}",
|
|
84
|
+
".thm_dayTokens{color:var(--dsw-alias-label-tertiary);font-size:8px;line-height:10px;font-variant-numeric:tabular-nums}",
|
|
85
|
+
".thm_day[data-level=\"2\"] .thm_dayNum,.thm_day[data-level=\"3\"] .thm_dayNum,.thm_day[data-level=\"4\"] .thm_dayNum{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.28)}",
|
|
86
|
+
".thm_day[data-level=\"2\"] .thm_dayTokens,.thm_day[data-level=\"3\"] .thm_dayTokens,.thm_day[data-level=\"4\"] .thm_dayTokens{color:rgba(255,255,255,.82)}",
|
|
87
|
+
".thm_legend{align-items:center;justify-content:flex-end;gap:3px;font-size:10px;line-height:14px;display:flex;color:var(--dsw-alias-label-tertiary)}", ".thm_legendCell{box-sizing:border-box;width:9px;height:9px;border-radius:2px}",
|
|
65
88
|
".thm_error{color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-interactive-bg-hover-danger);border-radius:8px;padding:7px 8px;font-size:11px;line-height:16px;display:flex;justify-content:space-between;align-items:center;gap:8px}",
|
|
66
89
|
".thm_retry{color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;flex:none;padding:0}",
|
|
67
90
|
".thm_loading{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;padding:6px 2px}",
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
".
|
|
71
|
-
".
|
|
72
|
-
".
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
".
|
|
77
|
-
".
|
|
78
|
-
".
|
|
79
|
-
".
|
|
80
|
-
".
|
|
81
|
-
".
|
|
82
|
-
".
|
|
83
|
-
".
|
|
84
|
-
".
|
|
85
|
-
".
|
|
86
|
-
".
|
|
87
|
-
".
|
|
88
|
-
".
|
|
89
|
-
".
|
|
90
|
-
".
|
|
91
|
-
".
|
|
92
|
-
".
|
|
93
|
-
".
|
|
94
|
-
".
|
|
95
|
-
".
|
|
96
|
-
".thms_swatchCells{display:flex;gap:2px}",
|
|
97
|
-
".thms_swatchCell{width:10px;height:10px;border-radius:2px}",
|
|
98
|
-
".thms_swatchLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5}",
|
|
99
|
-
".thms_footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}",
|
|
100
|
-
".thms_footerError{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}",
|
|
101
|
-
".thms_btn{appearance:none;font:inherit;cursor:pointer;border:1px solid #0000;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}",
|
|
102
|
-
".thms_btn:disabled{opacity:.4;cursor:default}",
|
|
103
|
-
".thms_discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}",
|
|
104
|
-
".thms_discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}",
|
|
105
|
-
".thms_save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}"
|
|
91
|
+
// In-card settings panel (⚙ at the row's right end) — the card
|
|
92
|
+
// configures itself; nothing registers into 设置 → 插件 → 插件配置.
|
|
93
|
+
".thm_gear{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:6px;align-items:center;padding:2px 4px;display:inline-flex}",
|
|
94
|
+
".thm_gear:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
95
|
+
".thm_gear[data-active=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
96
|
+
// The panel is an inset surface on the card: the light theme resolves
|
|
97
|
+
// every bg-layer token to the same white, so the fill is a translucent
|
|
98
|
+
// gray that reads as a nested surface in both themes.
|
|
99
|
+
".thm_panel{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:rgba(128,128,128,.08);border-radius:12px;flex-direction:column;gap:10px;margin-top:2px;padding:10px 12px 12px;display:flex}",
|
|
100
|
+
".thm_panelHead{align-items:center;gap:8px;display:flex}",
|
|
101
|
+
".thm_panelTitle{color:var(--dsw-alias-label-primary);flex:1;font-size:12px;font-weight:600;line-height:18px}",
|
|
102
|
+
".thm_panelClose{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;border-radius:4px;flex:none;padding:0 5px;font-size:16px;line-height:20px}",
|
|
103
|
+
".thm_panelClose:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
104
|
+
".thm_group{flex-direction:column;gap:5px;display:flex}",
|
|
105
|
+
".thm_groupLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}",
|
|
106
|
+
".thm_hint{color:var(--dsw-alias-label-caption);font-size:10px;line-height:14px}",
|
|
107
|
+
".thm_hintError{color:var(--dsw-alias-state-error-primary)}",
|
|
108
|
+
".thm_swatches{flex-wrap:wrap;gap:6px;display:flex}",
|
|
109
|
+
".thm_swatch{cursor:pointer;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2);background:0 0;border-radius:6px;padding:3px 8px;font:inherit;display:flex}",
|
|
110
|
+
".thm_swatch:hover{border-color:var(--dsw-alias-label-dimmed)}",
|
|
111
|
+
".thm_swatch[data-active=true]{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 1px var(--dsw-alias-state-business-primary) inset}",
|
|
112
|
+
".thm_swatchCells{display:flex;gap:2px}",
|
|
113
|
+
".thm_swatchCell{width:9px;height:9px;border-radius:2px}",
|
|
114
|
+
".thm_swatchLabel{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px}",
|
|
115
|
+
".thm_seg{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;align-items:center;display:inline-flex;overflow:hidden;width:max-content}",
|
|
116
|
+
".thm_segBtn{cursor:pointer;color:var(--dsw-alias-label-tertiary);background:0 0;border:none;padding:1px 10px;font:inherit;font-size:11px;line-height:18px}",
|
|
117
|
+
".thm_segBtn:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
118
|
+
".thm_segBtn[data-active=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);font-weight:600}"
|
|
106
119
|
].join("");
|
|
107
120
|
const tagId = "@kidli1412/dsh-token-heatmap/TokenHeatmap.module.css";
|
|
108
121
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
@@ -120,9 +133,12 @@ window.__ModuleLoader__.load({
|
|
|
120
133
|
stat: "thm_stat",
|
|
121
134
|
statLabel: "thm_statLabel",
|
|
122
135
|
statValue: "thm_statValue",
|
|
123
|
-
|
|
136
|
+
nav: "thm_nav",
|
|
124
137
|
yearBtn: "thm_yearBtn",
|
|
125
138
|
yearLabel: "thm_yearLabel",
|
|
139
|
+
monthLabelNav: "thm_monthLabelNav",
|
|
140
|
+
viewNav: "thm_viewNav",
|
|
141
|
+
viewBtn: "thm_viewBtn",
|
|
126
142
|
refresh: "thm_refresh",
|
|
127
143
|
gridWrap: "thm_gridWrap",
|
|
128
144
|
grid: "thm_grid",
|
|
@@ -131,41 +147,34 @@ window.__ModuleLoader__.load({
|
|
|
131
147
|
row: "thm_row",
|
|
132
148
|
rowLabel: "thm_rowLabel",
|
|
133
149
|
cell: "thm_cell",
|
|
150
|
+
monthCal: "thm_monthCal",
|
|
151
|
+
weekHead: "thm_weekHead",
|
|
152
|
+
weekHeadCell: "thm_weekHeadCell",
|
|
153
|
+
week: "thm_week",
|
|
154
|
+
day: "thm_day",
|
|
155
|
+
dayNum: "thm_dayNum",
|
|
156
|
+
dayTokens: "thm_dayTokens",
|
|
134
157
|
legend: "thm_legend",
|
|
135
158
|
legendCell: "thm_legendCell",
|
|
136
159
|
error: "thm_error",
|
|
137
160
|
retry: "thm_retry",
|
|
138
161
|
loading: "thm_loading",
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
settingsSwitchRow: "thms_switchRow",
|
|
156
|
-
settingsSwitch: "thms_switch",
|
|
157
|
-
settingsSwitchOn: "thms_switchOn",
|
|
158
|
-
settingsThumb: "thms_thumb",
|
|
159
|
-
settingsSwatches: "thms_swatches",
|
|
160
|
-
settingsSwatch: "thms_swatch",
|
|
161
|
-
settingsSwatchCells: "thms_swatchCells",
|
|
162
|
-
settingsSwatchCell: "thms_swatchCell",
|
|
163
|
-
settingsSwatchLabel: "thms_swatchLabel",
|
|
164
|
-
settingsFooter: "thms_footer",
|
|
165
|
-
settingsFooterError: "thms_footerError",
|
|
166
|
-
settingsBtn: "thms_btn",
|
|
167
|
-
settingsDiscard: "thms_discard",
|
|
168
|
-
settingsSave: "thms_save"
|
|
162
|
+
gear: "thm_gear",
|
|
163
|
+
settingsPanel: "thm_panel",
|
|
164
|
+
panelHead: "thm_panelHead",
|
|
165
|
+
panelTitle: "thm_panelTitle",
|
|
166
|
+
panelClose: "thm_panelClose",
|
|
167
|
+
settingsGroup: "thm_group",
|
|
168
|
+
settingsGroupLabel: "thm_groupLabel",
|
|
169
|
+
settingsHint: "thm_hint",
|
|
170
|
+
settingsHintError: "thm_hintError",
|
|
171
|
+
settingsSwatches: "thm_swatches",
|
|
172
|
+
settingsSwatch: "thm_swatch",
|
|
173
|
+
settingsSwatchCells: "thm_swatchCells",
|
|
174
|
+
settingsSwatchCell: "thm_swatchCell",
|
|
175
|
+
settingsSwatchLabel: "thm_swatchLabel",
|
|
176
|
+
seg: "thm_seg",
|
|
177
|
+
segBtn: "thm_segBtn"
|
|
169
178
|
};
|
|
170
179
|
//#endregion
|
|
171
180
|
|
|
@@ -184,31 +193,32 @@ window.__ModuleLoader__.load({
|
|
|
184
193
|
total: "累计",
|
|
185
194
|
prevYear: "上一年",
|
|
186
195
|
nextYear: "下一年",
|
|
196
|
+
prevMonth: "上一月",
|
|
197
|
+
nextMonth: "下一月",
|
|
198
|
+
viewYear: "年",
|
|
199
|
+
viewMonth: "月",
|
|
200
|
+
viewLabel: "视图",
|
|
187
201
|
less: "少",
|
|
188
202
|
more: "多",
|
|
189
203
|
cellTokens: "{date} · {tokens} tokens",
|
|
190
204
|
weekdayLabel: ["一", "二", "三", "四", "五", "六", "日"],
|
|
191
205
|
monthName: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
|
|
206
|
+
dayLabel: (monthName, day) => `${monthName}${day}日`,
|
|
207
|
+
monthYear: (year, monthName) => `${year}年${monthName}`,
|
|
192
208
|
levelLegend: ["0", "<1M", "1M–10M", "10M–100M", "≥100M"],
|
|
193
209
|
settingsTitle: "Token 用量热力图",
|
|
194
|
-
settingsDescription: "新会话页面的 Token 用量热力图显示与配色。",
|
|
195
|
-
settingsEnabled: "显示热力图",
|
|
196
|
-
settingsEnabledHint: "关闭后,新会话输入框下方的 Token 用量热力图不再显示。",
|
|
197
210
|
settingsScheme: "配色方案",
|
|
198
211
|
settingsSchemeHint: "热力图格子的颜色主题:绿色为经典 GitHub 风格,其余为新增配色。",
|
|
212
|
+
settingsDefaultView: "默认视图",
|
|
213
|
+
settingsDefaultViewHint: "新会话页面首次打开时显示的视图(标题行右端可随时切换)。",
|
|
199
214
|
settingsGreen: "绿色",
|
|
200
215
|
settingsBlue: "蓝色",
|
|
201
216
|
settingsOrange: "橙色",
|
|
202
217
|
settingsRed: "红色",
|
|
203
218
|
settingsPurple: "紫色",
|
|
204
219
|
settingsTeal: "青色",
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
settingsDiscard: "放弃修改",
|
|
208
|
-
settingsUnsaved: "未保存",
|
|
209
|
-
settingsSaveFailed: "保存失败,修改已保留,请重试。",
|
|
210
|
-
settingsLoadFailed: "配置加载失败。",
|
|
211
|
-
settingsRetry: "重试",
|
|
220
|
+
settingsPaletteHint: "颜色按每天 token 数的绝对阈值分档:{levels}。",
|
|
221
|
+
settingsSaveFailed: "保存失败,已回到服务端的值,请重试。",
|
|
212
222
|
settingsExpand: "展开设置",
|
|
213
223
|
settingsCollapse: "收起设置"
|
|
214
224
|
};
|
|
@@ -224,31 +234,32 @@ window.__ModuleLoader__.load({
|
|
|
224
234
|
total: "All time",
|
|
225
235
|
prevYear: "Previous year",
|
|
226
236
|
nextYear: "Next year",
|
|
237
|
+
prevMonth: "Previous month",
|
|
238
|
+
nextMonth: "Next month",
|
|
239
|
+
viewYear: "Year",
|
|
240
|
+
viewMonth: "Month",
|
|
241
|
+
viewLabel: "View",
|
|
227
242
|
less: "Less",
|
|
228
243
|
more: "More",
|
|
229
244
|
cellTokens: "{date} · {tokens} tokens",
|
|
230
245
|
weekdayLabel: ["M", "T", "W", "T", "F", "S", "S"],
|
|
231
246
|
monthName: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
|
247
|
+
dayLabel: (monthName, day) => `${monthName} ${day}`,
|
|
248
|
+
monthYear: (year, monthName) => `${monthName} ${year}`,
|
|
232
249
|
levelLegend: ["0", "<1M", "1M–10M", "10M–100M", "≥100M"],
|
|
233
250
|
settingsTitle: "Token usage heatmap",
|
|
234
|
-
settingsDescription: "Display and colors of the token heatmap on the new-session screen.",
|
|
235
|
-
settingsEnabled: "Show heatmap",
|
|
236
|
-
settingsEnabledHint: "When off, the token heatmap below the composer is hidden.",
|
|
237
251
|
settingsScheme: "Color scheme",
|
|
238
252
|
settingsSchemeHint: "Cell palette: green is the classic GitHub style; the rest are new themes.",
|
|
253
|
+
settingsDefaultView: "Default view",
|
|
254
|
+
settingsDefaultViewHint: "The view the new-session screen opens with (switchable anytime at the row's right end).",
|
|
239
255
|
settingsGreen: "Green",
|
|
240
256
|
settingsBlue: "Blue",
|
|
241
257
|
settingsOrange: "Orange",
|
|
242
258
|
settingsRed: "Red",
|
|
243
259
|
settingsPurple: "Purple",
|
|
244
260
|
settingsTeal: "Teal",
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
settingsDiscard: "Discard",
|
|
248
|
-
settingsUnsaved: "Unsaved",
|
|
249
|
-
settingsSaveFailed: "Save failed; your changes were kept.",
|
|
250
|
-
settingsLoadFailed: "Failed to load configuration.",
|
|
251
|
-
settingsRetry: "Retry",
|
|
261
|
+
settingsPaletteHint: "Colors follow absolute per-day token thresholds: {levels}.",
|
|
262
|
+
settingsSaveFailed: "Save failed; the server value was restored.",
|
|
252
263
|
settingsExpand: "Show settings",
|
|
253
264
|
settingsCollapse: "Hide settings"
|
|
254
265
|
};
|
|
@@ -293,6 +304,19 @@ window.__ModuleLoader__.load({
|
|
|
293
304
|
return key.slice(0, 7);
|
|
294
305
|
}
|
|
295
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Step a `YYYY-MM` key by whole months ("2026-01" -1 → "2025-12"),
|
|
309
|
+
* returning the input unchanged when it is not a month key.
|
|
310
|
+
* @param key - `YYYY-MM` month key.
|
|
311
|
+
* @param delta - signed month count.
|
|
312
|
+
*/
|
|
313
|
+
function shiftMonthKey(key, delta) {
|
|
314
|
+
const [year, month] = key.split("-").map(Number);
|
|
315
|
+
if (!Number.isFinite(year) || !Number.isFinite(month)) return key;
|
|
316
|
+
const date = new Date(year, month - 1 + delta, 1);
|
|
317
|
+
return monthKey(date.getTime());
|
|
318
|
+
}
|
|
319
|
+
|
|
296
320
|
/**
|
|
297
321
|
* Pick the display dictionary for locale-shaped helpers (month names,
|
|
298
322
|
* weekday labels, date formatting). The locale plugin resolves the UI
|
|
@@ -305,13 +329,31 @@ window.__ModuleLoader__.load({
|
|
|
305
329
|
return candidates.some((tag) => tag.toLowerCase().startsWith("zh")) ? zh : en;
|
|
306
330
|
}
|
|
307
331
|
|
|
308
|
-
/**
|
|
332
|
+
/**
|
|
333
|
+
* Display date "8月13日" / "Aug 13" — the whole shape comes from the
|
|
334
|
+
* dictionary, so an English UI never renders a Chinese 日 and the
|
|
335
|
+
* Chinese one never grows a space before it.
|
|
336
|
+
*/
|
|
309
337
|
function dateLabel(key) {
|
|
310
338
|
const [year, month, day] = key.split("-").map(Number);
|
|
311
|
-
const
|
|
312
|
-
return
|
|
339
|
+
const dict = uiDict();
|
|
340
|
+
return dict.dayLabel(dict.monthName[month - 1], day);
|
|
313
341
|
}
|
|
314
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Month-key navigation label, ordered per locale: "2026年8月" in Chinese
|
|
345
|
+
* (year first), "Aug 2026" in English (month first) — each dictionary
|
|
346
|
+
* owns its ordering rather than both sharing a Chinese-shaped template.
|
|
347
|
+
*/
|
|
348
|
+
function monthLabelFull(key) {
|
|
349
|
+
const [year, month] = key.split("-").map(Number);
|
|
350
|
+
const dict = uiDict();
|
|
351
|
+
return dict.monthYear(year, dict.monthName[month - 1]);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Heatmap view modes: the calendar-year grid or a single-month calendar. */
|
|
355
|
+
const VIEW_MODES = ["year", "month"];
|
|
356
|
+
|
|
315
357
|
/** Per-request staleness guard: only the most recent start may `isCurrent()`. */
|
|
316
358
|
function createLoader() {
|
|
317
359
|
let current = 0;
|
|
@@ -332,25 +374,32 @@ window.__ModuleLoader__.load({
|
|
|
332
374
|
|
|
333
375
|
//#region config store (settings-scope backed)
|
|
334
376
|
/**
|
|
335
|
-
* Display preferences (
|
|
336
|
-
* Host `token-heatmap` settings namespace (settings.yaml) via the
|
|
337
|
-
* settings scope — NOT the legacy loopback config endpoint. The
|
|
377
|
+
* Display preferences (colorScheme + defaultView), persisted through
|
|
378
|
+
* the Host `token-heatmap` settings namespace (settings.yaml) via the
|
|
379
|
+
* bound settings scope — NOT the legacy loopback config endpoint. The
|
|
338
380
|
* heatmap and the settings card read the store through
|
|
339
381
|
* useSyncExternalStore; while the scope is loading or unavailable the
|
|
340
|
-
* store falls back to the defaults (
|
|
341
|
-
* blocks on the settings transport.
|
|
382
|
+
* store falls back to the defaults (green / year view), so the UI never
|
|
383
|
+
* blocks on the settings transport. The 0.1.x display switch is gone:
|
|
384
|
+
* its `enabled` key is simply no longer read (see the settings card).
|
|
342
385
|
*/
|
|
343
|
-
const CONFIG_DEFAULTS = {
|
|
386
|
+
const CONFIG_DEFAULTS = { colorScheme: "green", defaultView: "year" };
|
|
344
387
|
/** Coerce a scheme to a non-blank string, else the default. */
|
|
345
388
|
function sanitizeScheme(value) {
|
|
346
389
|
if (typeof value !== "string") return CONFIG_DEFAULTS.colorScheme;
|
|
347
390
|
const scheme = value.trim();
|
|
348
391
|
return scheme.length > 0 && scheme.length <= 32 ? scheme : CONFIG_DEFAULTS.colorScheme;
|
|
349
392
|
}
|
|
393
|
+
/** Coerce a view mode to a known mode, else the default (year). */
|
|
394
|
+
function sanitizeView(value) {
|
|
395
|
+
if (typeof value !== "string") return CONFIG_DEFAULTS.defaultView;
|
|
396
|
+
const view = value.trim();
|
|
397
|
+
return VIEW_MODES.includes(view) ? view : CONFIG_DEFAULTS.defaultView;
|
|
398
|
+
}
|
|
350
399
|
/**
|
|
351
400
|
* Reactive adapter over one bound settings scope: a
|
|
352
401
|
* useSyncExternalStore-compatible store whose snapshot is
|
|
353
|
-
* `{ status,
|
|
402
|
+
* `{ status, colorScheme, defaultView }` derived from the scope.
|
|
354
403
|
* `set(patch)` writes the touched fields through scope.set (async; the
|
|
355
404
|
* scope fences revisions and reloads on failure), and the snapshot
|
|
356
405
|
* only changes after the Host confirms. `reload()` re-reads from the
|
|
@@ -367,8 +416,8 @@ window.__ModuleLoader__.load({
|
|
|
367
416
|
const v = s.value;
|
|
368
417
|
return {
|
|
369
418
|
status: s.status,
|
|
370
|
-
|
|
371
|
-
|
|
419
|
+
colorScheme: sanitizeScheme(v.colorScheme),
|
|
420
|
+
defaultView: sanitizeView(v.defaultView)
|
|
372
421
|
};
|
|
373
422
|
};
|
|
374
423
|
const notify = () => {
|
|
@@ -389,12 +438,12 @@ window.__ModuleLoader__.load({
|
|
|
389
438
|
};
|
|
390
439
|
},
|
|
391
440
|
set: async (patch) => {
|
|
392
|
-
if (Object.hasOwn(patch, "enabled")) {
|
|
393
|
-
await scope.set("enabled", patch.enabled === true);
|
|
394
|
-
}
|
|
395
441
|
if (Object.hasOwn(patch, "colorScheme")) {
|
|
396
442
|
await scope.set("colorScheme", sanitizeScheme(patch.colorScheme));
|
|
397
443
|
}
|
|
444
|
+
if (Object.hasOwn(patch, "defaultView")) {
|
|
445
|
+
await scope.set("defaultView", sanitizeView(patch.defaultView));
|
|
446
|
+
}
|
|
398
447
|
},
|
|
399
448
|
reload: () => scope.load(),
|
|
400
449
|
dispose: () => unsubscribe()
|
|
@@ -490,13 +539,56 @@ window.__ModuleLoader__.load({
|
|
|
490
539
|
}
|
|
491
540
|
return { columns, monthStarts };
|
|
492
541
|
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Build the calendar grid for ONE MONTH (the 年/月 view switch's month
|
|
545
|
+
* view): Monday-first weeks, exactly as many week rows as the month
|
|
546
|
+
* spans (5 or 6), cells outside the month null. Days after today are
|
|
547
|
+
* empty level-0 cells (today itself is always rendered with its real
|
|
548
|
+
* value), and every cell carries `day` (day of month) so the calendar
|
|
549
|
+
* can print numbers on top of the same absolute-threshold heat colors
|
|
550
|
+
* the year view uses.
|
|
551
|
+
* @param dayMap - date key → tokens.
|
|
552
|
+
* @param now - reference timestamp.
|
|
553
|
+
* @param month - `YYYY-MM` to render; defaults to the month of `now`.
|
|
554
|
+
* @returns `{ month, weeks, weekdays }` where each week is
|
|
555
|
+
* `Array<{key, tokens, level, day} | null>` of 7 cells.
|
|
556
|
+
*/
|
|
557
|
+
function buildMonthGrid(dayMap, now, month) {
|
|
558
|
+
const nowDate = new Date(now);
|
|
559
|
+
const todayKey = dayKey(nowDate.getTime());
|
|
560
|
+
const target = month ?? monthKey(nowDate.getTime());
|
|
561
|
+
const [year, monthNumber] = target.split("-").map(Number);
|
|
562
|
+
const monthStart = new Date(year, monthNumber - 1, 1);
|
|
563
|
+
const monthEnd = new Date(year, monthNumber, 0);
|
|
564
|
+
const startKey = dayKey(monthStart.getTime());
|
|
565
|
+
const endKey = dayKey(monthEnd.getTime());
|
|
566
|
+
const cursor = new Date(year, monthNumber - 1, 1 - ((monthStart.getDay() + 6) % 7));
|
|
567
|
+
const weeks = [];
|
|
568
|
+
while (cursor.getTime() <= monthEnd.getTime()) {
|
|
569
|
+
const cells = [];
|
|
570
|
+
for (let d = 0; d < 7; d += 1) {
|
|
571
|
+
const key = dayKey(cursor.getTime());
|
|
572
|
+
if (key < startKey || key > endKey) {
|
|
573
|
+
cells.push(null);
|
|
574
|
+
} else {
|
|
575
|
+
const future = key > todayKey;
|
|
576
|
+
const tokens = future ? 0 : (dayMap.get(key) ?? 0);
|
|
577
|
+
cells.push({ key, tokens, level: levelOf(tokens), day: cursor.getDate() });
|
|
578
|
+
}
|
|
579
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
580
|
+
}
|
|
581
|
+
weeks.push(cells);
|
|
582
|
+
}
|
|
583
|
+
return { month: target, weeks, weekdays: 7 };
|
|
584
|
+
}
|
|
493
585
|
//#endregion
|
|
494
586
|
|
|
495
587
|
//#region TokenHeatmap
|
|
496
588
|
/**
|
|
497
589
|
* The heatmap card. Shown only on the new-session hero screen
|
|
498
|
-
* (`composerPhase === "blank"`
|
|
499
|
-
* it below the composer input card.
|
|
590
|
+
* (`session.blank`, or `composerPhase === "blank"` on older hosts); a
|
|
591
|
+
* flex `order` on the wrapper places it below the composer input card.
|
|
500
592
|
* @param props - `session`/`input` from the InputZone owner share, `t`
|
|
501
593
|
* bound by the slot runtime locale seat.
|
|
502
594
|
*/
|
|
@@ -507,6 +599,24 @@ window.__ModuleLoader__.load({
|
|
|
507
599
|
const [error, setError] = react.useState(null);
|
|
508
600
|
const [loading, setLoading] = react.useState(false);
|
|
509
601
|
const [year, setYear] = react.useState(() => new Date().getFullYear());
|
|
602
|
+
// Active view mode, seeded from the persisted default (the 年/月
|
|
603
|
+
// switch at the row's right end; a manual switch wins for this
|
|
604
|
+
// mount).
|
|
605
|
+
const [view, setView] = react.useState(() => sanitizeView(configStore?.getSnapshot().defaultView));
|
|
606
|
+
// Month-view cursor (`YYYY-MM`); initialised from the current month
|
|
607
|
+
// and re-anchored when it would fall outside the navigable bounds.
|
|
608
|
+
const [monthCursor, setMonthCursor] = react.useState(() => monthKey(Date.now()));
|
|
609
|
+
// In-card settings panel (⚙ at the row's right end); the card
|
|
610
|
+
// configures itself here instead of living in 设置 → 插件 → 插件配置.
|
|
611
|
+
const [settingsOpen, setSettingsOpen] = react.useState(false);
|
|
612
|
+
react.useEffect(() => {
|
|
613
|
+
if (!settingsOpen) return;
|
|
614
|
+
const onKeyDown = (event) => {
|
|
615
|
+
if (event.key === "Escape") setSettingsOpen(false);
|
|
616
|
+
};
|
|
617
|
+
document.addEventListener("keydown", onKeyDown);
|
|
618
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
619
|
+
}, [settingsOpen]);
|
|
510
620
|
const loaderRef = react.useRef(null);
|
|
511
621
|
if (loaderRef.current === null) loaderRef.current = createLoader();
|
|
512
622
|
const load = react.useCallback(async () => {
|
|
@@ -545,17 +655,18 @@ window.__ModuleLoader__.load({
|
|
|
545
655
|
// New-session hero only. DSH 0.1.2+ (rc.1) exposes the state as
|
|
546
656
|
// the boolean `session.blank` (the `composerPhase` string is
|
|
547
657
|
// gone); older harnesses keep `composerPhase === "blank"`.
|
|
548
|
-
// Conversed sessions hide the card.
|
|
658
|
+
// Conversed sessions hide the card. There is no display switch any
|
|
659
|
+
// more: the card is always shown on the hero screen, and the
|
|
660
|
+
// settings card only carries presentation choices.
|
|
549
661
|
if (session === void 0 || session === null || input === void 0 || input === null) return null;
|
|
550
662
|
const heroBlank = typeof session.blank === "boolean" ? session.blank === true : session.composerPhase === "blank";
|
|
551
663
|
if (!heroBlank) return null;
|
|
552
|
-
// Master switch from the settings card (设置 → 插件 → 插件配置).
|
|
553
|
-
if (config.enabled === false) return null;
|
|
554
664
|
|
|
555
665
|
const now = Date.now();
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
//
|
|
666
|
+
// Navigation bounds, derived from the data itself: at most the
|
|
667
|
+
// current year/month (future usage cannot exist) and at least the
|
|
668
|
+
// earliest one present, so the ‹ › selectors never wander into
|
|
669
|
+
// guaranteed-empty territory.
|
|
559
670
|
const nowYear = new Date(now).getFullYear();
|
|
560
671
|
let minYear = nowYear;
|
|
561
672
|
for (const day of data?.days ?? []) {
|
|
@@ -563,21 +674,47 @@ window.__ModuleLoader__.load({
|
|
|
563
674
|
if (y < minYear) minYear = y;
|
|
564
675
|
}
|
|
565
676
|
const maxYear = nowYear;
|
|
677
|
+
const currentMonthKey = monthKey(now);
|
|
678
|
+
let minMonth = currentMonthKey;
|
|
679
|
+
for (const day of data?.days ?? []) {
|
|
680
|
+
const key = monthOfDateKey(day.date);
|
|
681
|
+
if (key < minMonth) minMonth = key;
|
|
682
|
+
}
|
|
683
|
+
const maxMonth = currentMonthKey;
|
|
566
684
|
const todayTokens = data?.days?.find((day) => day.date === dayKey(now))?.tokens ?? 0;
|
|
567
|
-
const monthPrefix =
|
|
685
|
+
const monthPrefix = currentMonthKey;
|
|
568
686
|
let monthTokens = 0;
|
|
569
687
|
for (const day of data?.days ?? []) {
|
|
570
688
|
if (day.date.startsWith(monthPrefix)) monthTokens += day.tokens;
|
|
571
689
|
}
|
|
572
690
|
const totalTokens = data?.total ?? 0;
|
|
691
|
+
// Keep the last known cursor, but never outside the bounds (a
|
|
692
|
+
// brand-new store of only old months must not open on next month).
|
|
693
|
+
const activeMonth = monthCursor < minMonth
|
|
694
|
+
? minMonth
|
|
695
|
+
: monthCursor > maxMonth ? maxMonth : monthCursor;
|
|
573
696
|
|
|
574
697
|
const dayMap = new Map();
|
|
575
698
|
for (const day of data?.days ?? []) dayMap.set(day.date, day.tokens);
|
|
576
699
|
const grid = buildGrid(dayMap, now, year);
|
|
700
|
+
const monthGrid = buildMonthGrid(dayMap, now, activeMonth);
|
|
577
701
|
const levelLegend = uiDict().levelLegend;
|
|
578
|
-
// Palette from the settings
|
|
702
|
+
// Palette from the in-card settings panel; unknown schemes fall back
|
|
703
|
+
// to green.
|
|
579
704
|
const palette = COLOR_SCHEMES[config.colorScheme] ?? COLOR_SCHEMES.green;
|
|
580
705
|
|
|
706
|
+
// ‹ › step the active view: years in year view, months in month view.
|
|
707
|
+
const stepBack = () => {
|
|
708
|
+
if (view === "month") setMonthCursor(shiftMonthKey(activeMonth, -1));
|
|
709
|
+
else setYear(year - 1);
|
|
710
|
+
};
|
|
711
|
+
const stepForward = () => {
|
|
712
|
+
if (view === "month") setMonthCursor(shiftMonthKey(activeMonth, 1));
|
|
713
|
+
else setYear(year + 1);
|
|
714
|
+
};
|
|
715
|
+
const backDisabled = view === "month" ? activeMonth <= minMonth : year <= minYear;
|
|
716
|
+
const forwardDisabled = view === "month" ? activeMonth >= maxMonth : year >= maxYear;
|
|
717
|
+
|
|
581
718
|
const stats = [
|
|
582
719
|
{ label: dict("today"), value: todayTokens },
|
|
583
720
|
{ label: dict("month"), value: monthTokens },
|
|
@@ -622,37 +759,120 @@ window.__ModuleLoader__.load({
|
|
|
622
759
|
]
|
|
623
760
|
}, stat.label)),
|
|
624
761
|
react_jsx_runtime.jsxs("span", {
|
|
625
|
-
className: S.
|
|
762
|
+
className: S.nav,
|
|
626
763
|
children: [
|
|
627
764
|
react_jsx_runtime.jsx("button", {
|
|
628
765
|
type: "button",
|
|
629
766
|
className: S.yearBtn,
|
|
630
|
-
disabled:
|
|
631
|
-
onClick:
|
|
632
|
-
"aria-label": dict("prevYear"),
|
|
767
|
+
disabled: backDisabled,
|
|
768
|
+
onClick: stepBack,
|
|
769
|
+
"aria-label": dict(view === "month" ? "prevMonth" : "prevYear"),
|
|
633
770
|
children: "\u2039"
|
|
634
771
|
}),
|
|
635
|
-
react_jsx_runtime.jsx("span", {
|
|
772
|
+
view === "month" ? react_jsx_runtime.jsx("span", {
|
|
773
|
+
className: `${S.yearLabel} ${S.monthLabelNav}`,
|
|
774
|
+
children: monthLabelFull(activeMonth)
|
|
775
|
+
}) : react_jsx_runtime.jsx("span", { className: S.yearLabel, children: String(year) }),
|
|
636
776
|
react_jsx_runtime.jsx("button", {
|
|
637
777
|
type: "button",
|
|
638
778
|
className: S.yearBtn,
|
|
639
|
-
disabled:
|
|
640
|
-
onClick:
|
|
641
|
-
"aria-label": dict("nextYear"),
|
|
779
|
+
disabled: forwardDisabled,
|
|
780
|
+
onClick: stepForward,
|
|
781
|
+
"aria-label": dict(view === "month" ? "nextMonth" : "nextYear"),
|
|
642
782
|
children: "\u203a"
|
|
643
783
|
})
|
|
644
784
|
]
|
|
645
785
|
}),
|
|
786
|
+
// The view switch sits next to the ‹ › stepper it pairs
|
|
787
|
+
// with; the ⚙ closes the row on the right.
|
|
788
|
+
react_jsx_runtime.jsx("span", {
|
|
789
|
+
className: S.viewNav,
|
|
790
|
+
role: "group",
|
|
791
|
+
"aria-label": dict("viewLabel"),
|
|
792
|
+
children: VIEW_MODES.map((mode) => react_jsx_runtime.jsx("button", {
|
|
793
|
+
type: "button",
|
|
794
|
+
className: S.viewBtn,
|
|
795
|
+
"data-active": view === mode,
|
|
796
|
+
"aria-pressed": view === mode,
|
|
797
|
+
onClick: () => setView(mode),
|
|
798
|
+
children: dict(mode === "month" ? "viewMonth" : "viewYear")
|
|
799
|
+
}, mode))
|
|
800
|
+
}),
|
|
646
801
|
react_jsx_runtime.jsx("button", {
|
|
647
802
|
type: "button",
|
|
648
803
|
className: S.refresh,
|
|
649
804
|
disabled: loading,
|
|
650
805
|
onClick: load,
|
|
651
806
|
children: dict("refresh")
|
|
807
|
+
}),
|
|
808
|
+
// The setting button owns the row's right end; the ‹ ›
|
|
809
|
+
// stepper and the view switch sit left of it, the
|
|
810
|
+
// stepper next to the stats it moves through.
|
|
811
|
+
react_jsx_runtime.jsx("button", {
|
|
812
|
+
type: "button",
|
|
813
|
+
className: S.gear,
|
|
814
|
+
"data-active": settingsOpen,
|
|
815
|
+
"aria-expanded": settingsOpen,
|
|
816
|
+
"aria-label": dict("settingsTitle"),
|
|
817
|
+
title: dict("settingsTitle"),
|
|
818
|
+
onClick: () => setSettingsOpen(!settingsOpen),
|
|
819
|
+
children: react_jsx_runtime.jsx("svg", {
|
|
820
|
+
width: 14,
|
|
821
|
+
height: 14,
|
|
822
|
+
viewBox: "0 0 24 24",
|
|
823
|
+
fill: "none",
|
|
824
|
+
stroke: "currentColor",
|
|
825
|
+
"aria-hidden": "true",
|
|
826
|
+
children: react_jsx_runtime.jsx("path", {
|
|
827
|
+
d: "M10.9 2.6h2.2l.4 2.5 1.9.8 2-1.5 1.6 1.6-1.5 2 .8 1.9 2.5.4v2.2l-2.5.4-.8 1.9 1.5 2-1.6 1.6-2-1.5-1.9.8-.4 2.5h-2.2l-.4-2.5-1.9-.8-2 1.5-1.6-1.6 1.5-2-.8-1.9-2.5-.4v-2.2l2.5-.4.8-1.9-1.5-2 1.6-1.6 2 1.5 1.9-.8.4-2.5Z",
|
|
828
|
+
"stroke-width": 1.5,
|
|
829
|
+
"stroke-linejoin": "round",
|
|
830
|
+
"stroke-linecap": "round"
|
|
831
|
+
})
|
|
832
|
+
})
|
|
652
833
|
})
|
|
653
834
|
]
|
|
654
835
|
}),
|
|
655
|
-
react_jsx_runtime.jsx("div", {
|
|
836
|
+
view === "month" ? react_jsx_runtime.jsx("div", {
|
|
837
|
+
className: S.gridWrap,
|
|
838
|
+
children: react_jsx_runtime.jsxs("div", {
|
|
839
|
+
className: S.monthCal,
|
|
840
|
+
children: [
|
|
841
|
+
react_jsx_runtime.jsx("div", {
|
|
842
|
+
className: S.weekHead,
|
|
843
|
+
children: uiDict().weekdayLabel.map((label, index) => react_jsx_runtime.jsx("span", {
|
|
844
|
+
className: S.weekHeadCell,
|
|
845
|
+
children: label
|
|
846
|
+
}, `weekhead-${index}`))
|
|
847
|
+
}),
|
|
848
|
+
...monthGrid.weeks.map((week, weekIndex) => react_jsx_runtime.jsx("div", {
|
|
849
|
+
className: S.week,
|
|
850
|
+
children: week.map((cell, dayIndex) => {
|
|
851
|
+
if (cell === null) {
|
|
852
|
+
return react_jsx_runtime.jsx("span", {
|
|
853
|
+
className: S.day,
|
|
854
|
+
"data-empty": "true"
|
|
855
|
+
}, `empty-${weekIndex}-${dayIndex}`);
|
|
856
|
+
}
|
|
857
|
+
return react_jsx_runtime.jsxs("span", {
|
|
858
|
+
className: S.day,
|
|
859
|
+
"data-level": String(cell.level),
|
|
860
|
+
"data-weekend": dayIndex >= 5 ? "true" : "false",
|
|
861
|
+
style: { background: palette[cell.level] },
|
|
862
|
+
title: translate("cellTokens", { date: dateLabel(cell.key), tokens: fmt(cell.tokens) }),
|
|
863
|
+
children: [
|
|
864
|
+
react_jsx_runtime.jsx("span", { className: S.dayNum, children: String(cell.day) }),
|
|
865
|
+
cell.tokens > 0 ? react_jsx_runtime.jsx("span", {
|
|
866
|
+
className: S.dayTokens,
|
|
867
|
+
children: fmtCompact(cell.tokens)
|
|
868
|
+
}) : null
|
|
869
|
+
]
|
|
870
|
+
}, cell.key);
|
|
871
|
+
})
|
|
872
|
+
}, `week-${weekIndex}`))
|
|
873
|
+
]
|
|
874
|
+
})
|
|
875
|
+
}) : react_jsx_runtime.jsx("div", {
|
|
656
876
|
className: S.gridWrap,
|
|
657
877
|
children: react_jsx_runtime.jsxs("div", {
|
|
658
878
|
className: S.grid,
|
|
@@ -691,28 +911,30 @@ window.__ModuleLoader__.load({
|
|
|
691
911
|
})
|
|
692
912
|
]
|
|
693
913
|
}, `row-${row}`);
|
|
694
|
-
}),
|
|
695
|
-
react_jsx_runtime.jsx("div", {
|
|
696
|
-
className: S.row,
|
|
697
|
-
children: [
|
|
698
|
-
react_jsx_runtime.jsx("span", { className: S.rowLabel, children: "" }),
|
|
699
|
-
react_jsx_runtime.jsx("span", {
|
|
700
|
-
className: S.legend,
|
|
701
|
-
children: [
|
|
702
|
-
dict("less"),
|
|
703
|
-
...palette.map((color, level) => react_jsx_runtime.jsx("span", {
|
|
704
|
-
className: S.legendCell,
|
|
705
|
-
style: { background: color },
|
|
706
|
-
title: levelLegend[level] ?? ""
|
|
707
|
-
}, `lvl-${level}`)),
|
|
708
|
-
dict("more")
|
|
709
|
-
]
|
|
710
|
-
})
|
|
711
|
-
]
|
|
712
914
|
})
|
|
713
915
|
]
|
|
714
916
|
})
|
|
715
|
-
})
|
|
917
|
+
}),
|
|
918
|
+
react_jsx_runtime.jsx("div", {
|
|
919
|
+
className: S.legend,
|
|
920
|
+
children: [
|
|
921
|
+
dict("less"),
|
|
922
|
+
...palette.map((color, level) => react_jsx_runtime.jsx("span", {
|
|
923
|
+
className: S.legendCell,
|
|
924
|
+
style: { background: color },
|
|
925
|
+
title: levelLegend[level] ?? ""
|
|
926
|
+
}, `lvl-${level}`)),
|
|
927
|
+
dict("more")
|
|
928
|
+
]
|
|
929
|
+
}),
|
|
930
|
+
settingsOpen ? react_jsx_runtime.jsx(TokenHeatmapInlineSettings, {
|
|
931
|
+
dict,
|
|
932
|
+
config,
|
|
933
|
+
store: configStore,
|
|
934
|
+
palette,
|
|
935
|
+
translate,
|
|
936
|
+
onClose: () => setSettingsOpen(false)
|
|
937
|
+
}) : null
|
|
716
938
|
]
|
|
717
939
|
});
|
|
718
940
|
}
|
|
@@ -725,213 +947,130 @@ window.__ModuleLoader__.load({
|
|
|
725
947
|
}
|
|
726
948
|
//#endregion
|
|
727
949
|
|
|
728
|
-
//#region
|
|
950
|
+
//#region TokenHeatmapInlineSettings
|
|
729
951
|
/**
|
|
730
|
-
* The
|
|
731
|
-
* 设置 → 插件 → 插件配置
|
|
952
|
+
* The card's own settings panel: 配色方案 and 默认视图, edited where the
|
|
953
|
+
* card lives instead of inside 设置 → 插件 → 插件配置 (this plugin no
|
|
954
|
+
* longer registers into the `settings.plugin.item` seat).
|
|
732
955
|
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
* @param props -
|
|
956
|
+
* Every control applies immediately through the bound settings scope —
|
|
957
|
+
* there is no draft/save step, so the component owns only a local save
|
|
958
|
+
* error. The 0.1.x 显示热力图 switch is gone (the card is always shown on
|
|
959
|
+
* the hero screen), so its stored `enabled` key is neither read nor
|
|
960
|
+
* written. The legacy loopback config endpoint remains as a back-compat
|
|
961
|
+
* API, but nothing in the UI uses it.
|
|
962
|
+
* @param props.dict - bound translator.
|
|
963
|
+
* @param props.config - current config store snapshot.
|
|
964
|
+
* @param props.store - config store (set writes through the scope).
|
|
965
|
+
* @param props.palette - active palette (level 0..4 → css color).
|
|
966
|
+
* @param props.translate - interpolation helper for translated labels.
|
|
967
|
+
* @param props.onClose - collapse the panel (the × button).
|
|
740
968
|
*/
|
|
741
|
-
function
|
|
742
|
-
const dict = t !== void 0 ? t : (key) => zh[key] ?? key;
|
|
743
|
-
const [open, setOpen] = react.useState(false);
|
|
744
|
-
const [loaded, setLoaded] = react.useState(null);
|
|
745
|
-
const [draft, setDraft] = react.useState(null);
|
|
746
|
-
const [saving, setSaving] = react.useState(false);
|
|
969
|
+
function TokenHeatmapInlineSettings({ dict, config, store, palette, translate, onClose }) {
|
|
747
970
|
const [saveFailed, setSaveFailed] = react.useState(false);
|
|
748
|
-
const
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
}, [config, loaded]);
|
|
757
|
-
|
|
758
|
-
const dirty = loaded !== null && draft !== null && (draft.enabled !== loaded.enabled || draft.colorScheme !== loaded.colorScheme);
|
|
759
|
-
const save = react.useCallback(async () => {
|
|
760
|
-
if (draft === null || saving) return;
|
|
761
|
-
setSaving(true);
|
|
971
|
+
const schemes = Object.keys(COLOR_SCHEMES);
|
|
972
|
+
// Optimistic local echo: the settings scope only reports the new value
|
|
973
|
+
// after the Host confirms, and a control must not lag behind the click.
|
|
974
|
+
const [pending, setPending] = react.useState({});
|
|
975
|
+
const scheme = pending.colorScheme ?? config.colorScheme;
|
|
976
|
+
const defaultView = pending.defaultView ?? config.defaultView;
|
|
977
|
+
const commit = async (patch) => {
|
|
978
|
+
setPending((current) => ({ ...current, ...patch }));
|
|
762
979
|
setSaveFailed(false);
|
|
763
980
|
try {
|
|
764
|
-
await
|
|
765
|
-
// The scope confirms the write and refreshes its snapshot
|
|
766
|
-
// synchronously before settling; sync the staged form.
|
|
767
|
-
const confirmed = configStore.getSnapshot();
|
|
768
|
-
if (confirmed.status === "ready") {
|
|
769
|
-
setLoaded({ enabled: confirmed.enabled, colorScheme: confirmed.colorScheme });
|
|
770
|
-
setDraft({ enabled: confirmed.enabled, colorScheme: confirmed.colorScheme });
|
|
771
|
-
}
|
|
981
|
+
await store.set(patch);
|
|
772
982
|
} catch {
|
|
773
983
|
setSaveFailed(true);
|
|
774
|
-
} finally {
|
|
775
|
-
setSaving(false);
|
|
776
984
|
}
|
|
777
|
-
}, [draft, saving]);
|
|
778
|
-
const discard = () => {
|
|
779
|
-
if (loaded !== null) setDraft(loaded);
|
|
780
|
-
setSaveFailed(false);
|
|
781
985
|
};
|
|
782
|
-
const
|
|
783
|
-
if (
|
|
986
|
+
const chooseScheme = (value) => {
|
|
987
|
+
if (value !== scheme) commit({ colorScheme: value });
|
|
784
988
|
};
|
|
785
|
-
const
|
|
786
|
-
if (
|
|
989
|
+
const chooseView = (value) => {
|
|
990
|
+
if (value !== defaultView) commit({ defaultView: value });
|
|
787
991
|
};
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
children: dict("loading")
|
|
809
|
-
});
|
|
810
|
-
} else {
|
|
811
|
-
bodyContent = react_jsx_runtime.jsxs(react_jsx_runtime.Fragment, {
|
|
812
|
-
children: [
|
|
813
|
-
react_jsx_runtime.jsx("div", {
|
|
814
|
-
className: S.settingsField,
|
|
815
|
-
children: react_jsx_runtime.jsxs("div", {
|
|
816
|
-
className: S.settingsSwitchRow,
|
|
817
|
-
children: [
|
|
818
|
-
react_jsx_runtime.jsx("button", {
|
|
819
|
-
type: "button",
|
|
820
|
-
role: "switch",
|
|
821
|
-
"aria-checked": draft.enabled,
|
|
822
|
-
"aria-label": dict("settingsEnabled"),
|
|
823
|
-
disabled: saving,
|
|
824
|
-
className: draft.enabled ? `${S.settingsSwitch} ${S.settingsSwitchOn}` : S.settingsSwitch,
|
|
825
|
-
onClick: toggleEnabled,
|
|
826
|
-
children: react_jsx_runtime.jsx("span", { className: S.settingsThumb })
|
|
827
|
-
}),
|
|
828
|
-
react_jsx_runtime.jsx("span", { className: S.settingsFieldLabel, children: dict("settingsEnabled") })
|
|
829
|
-
]
|
|
992
|
+
const labelOf = (name) => dict(`settings${name[0].toUpperCase()}${name.slice(1)}`);
|
|
993
|
+
return react_jsx_runtime.jsxs("div", {
|
|
994
|
+
className: S.settingsPanel,
|
|
995
|
+
// Escape and "focus left the panel" both collapse it, so the
|
|
996
|
+
// panel never lingers behind an unrelated interaction.
|
|
997
|
+
onBlur: (event) => {
|
|
998
|
+
if (!event.currentTarget.contains(event.relatedTarget)) onClose();
|
|
999
|
+
},
|
|
1000
|
+
children: [
|
|
1001
|
+
react_jsx_runtime.jsxs("div", {
|
|
1002
|
+
className: S.panelHead,
|
|
1003
|
+
children: [
|
|
1004
|
+
react_jsx_runtime.jsx("span", { className: S.panelTitle, children: dict("settingsTitle") }),
|
|
1005
|
+
react_jsx_runtime.jsx("button", {
|
|
1006
|
+
type: "button",
|
|
1007
|
+
className: S.panelClose,
|
|
1008
|
+
"aria-label": dict("settingsCollapse"),
|
|
1009
|
+
title: dict("settingsCollapse"),
|
|
1010
|
+
onClick: onClose,
|
|
1011
|
+
children: "\u00d7"
|
|
830
1012
|
})
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
react_jsx_runtime.jsx("div", {
|
|
841
|
-
className: S.settingsSwatches,
|
|
842
|
-
children: Object.keys(COLOR_SCHEMES).map((scheme) => react_jsx_runtime.jsx("button", {
|
|
843
|
-
type: "button",
|
|
844
|
-
disabled: saving,
|
|
845
|
-
className: S.settingsSwatch,
|
|
846
|
-
"data-active": draft.colorScheme === scheme,
|
|
847
|
-
onClick: () => chooseScheme(scheme),
|
|
848
|
-
children: [
|
|
849
|
-
react_jsx_runtime.jsx("span", {
|
|
850
|
-
className: S.settingsSwatchCells,
|
|
851
|
-
children: COLOR_SCHEMES[scheme].slice(1).map((color, index) => react_jsx_runtime.jsx("span", {
|
|
852
|
-
className: S.settingsSwatchCell,
|
|
853
|
-
style: { background: color }
|
|
854
|
-
}, `cell-${index}`))
|
|
855
|
-
}),
|
|
856
|
-
react_jsx_runtime.jsx("span", {
|
|
857
|
-
className: S.settingsSwatchLabel,
|
|
858
|
-
children: dict(`settings${scheme[0].toUpperCase()}${scheme.slice(1)}`)
|
|
859
|
-
})
|
|
860
|
-
]
|
|
861
|
-
}, scheme))
|
|
862
|
-
})
|
|
863
|
-
]
|
|
864
|
-
}),
|
|
865
|
-
react_jsx_runtime.jsx("p", {
|
|
866
|
-
className: S.settingsFieldHint,
|
|
867
|
-
children: dict("settingsSchemeHint")
|
|
868
|
-
}),
|
|
869
|
-
react_jsx_runtime.jsxs("div", {
|
|
870
|
-
className: S.settingsFooter,
|
|
871
|
-
children: [
|
|
872
|
-
saveFailed ? react_jsx_runtime.jsx("p", {
|
|
873
|
-
className: S.settingsFooterError,
|
|
874
|
-
children: dict("settingsSaveFailed")
|
|
875
|
-
}) : null,
|
|
876
|
-
react_jsx_runtime.jsx("button", {
|
|
877
|
-
type: "button",
|
|
878
|
-
className: `${S.settingsBtn} ${S.settingsDiscard}`,
|
|
879
|
-
disabled: !dirty || saving,
|
|
880
|
-
onClick: discard,
|
|
881
|
-
children: dict("settingsDiscard")
|
|
882
|
-
}),
|
|
883
|
-
react_jsx_runtime.jsx("button", {
|
|
1013
|
+
]
|
|
1014
|
+
}),
|
|
1015
|
+
react_jsx_runtime.jsxs("div", {
|
|
1016
|
+
className: S.settingsGroup,
|
|
1017
|
+
children: [
|
|
1018
|
+
react_jsx_runtime.jsx("span", { className: S.settingsGroupLabel, children: dict("settingsScheme") }),
|
|
1019
|
+
react_jsx_runtime.jsx("div", {
|
|
1020
|
+
className: S.settingsSwatches,
|
|
1021
|
+
children: schemes.map((name) => react_jsx_runtime.jsxs("button", {
|
|
884
1022
|
type: "button",
|
|
885
|
-
className:
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1023
|
+
className: S.settingsSwatch,
|
|
1024
|
+
"data-active": scheme === name,
|
|
1025
|
+
"aria-pressed": scheme === name,
|
|
1026
|
+
title: labelOf(name),
|
|
1027
|
+
onClick: () => chooseScheme(name),
|
|
1028
|
+
children: [
|
|
1029
|
+
react_jsx_runtime.jsx("span", {
|
|
1030
|
+
className: S.settingsSwatchCells,
|
|
1031
|
+
children: COLOR_SCHEMES[name].slice(1).map((color, index) => react_jsx_runtime.jsx("span", {
|
|
1032
|
+
className: S.settingsSwatchCell,
|
|
1033
|
+
style: { background: color }
|
|
1034
|
+
}, `cell-${index}`))
|
|
1035
|
+
}),
|
|
1036
|
+
react_jsx_runtime.jsx("span", {
|
|
1037
|
+
className: S.settingsSwatchLabel,
|
|
1038
|
+
children: labelOf(name)
|
|
1039
|
+
})
|
|
1040
|
+
]
|
|
1041
|
+
}, name))
|
|
1042
|
+
})
|
|
1043
|
+
]
|
|
1044
|
+
}),
|
|
1045
|
+
react_jsx_runtime.jsxs("div", {
|
|
1046
|
+
className: S.settingsGroup,
|
|
905
1047
|
children: [
|
|
906
|
-
react_jsx_runtime.
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1048
|
+
react_jsx_runtime.jsx("span", { className: S.settingsGroupLabel, children: dict("settingsDefaultView") }),
|
|
1049
|
+
react_jsx_runtime.jsx("div", {
|
|
1050
|
+
className: S.seg,
|
|
1051
|
+
role: "group",
|
|
1052
|
+
"aria-label": dict("settingsDefaultView"),
|
|
1053
|
+
children: VIEW_MODES.map((mode) => react_jsx_runtime.jsx("button", {
|
|
1054
|
+
type: "button",
|
|
1055
|
+
className: S.segBtn,
|
|
1056
|
+
"data-active": defaultView === mode,
|
|
1057
|
+
"aria-pressed": defaultView === mode,
|
|
1058
|
+
onClick: () => chooseView(mode),
|
|
1059
|
+
children: dict(mode === "month" ? "viewMonth" : "viewYear")
|
|
1060
|
+
}, mode))
|
|
912
1061
|
}),
|
|
913
|
-
|
|
914
|
-
className: S.
|
|
915
|
-
children: dict("
|
|
916
|
-
}) : null,
|
|
917
|
-
react_jsx_runtime.jsx("svg", {
|
|
918
|
-
width: 14,
|
|
919
|
-
height: 14,
|
|
920
|
-
className: open ? `${S.settingsChevron} ${S.settingsChevronOpen}` : S.settingsChevron,
|
|
921
|
-
viewBox: "0 0 14 14",
|
|
922
|
-
fill: "none",
|
|
923
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
924
|
-
children: react_jsx_runtime.jsx("path", {
|
|
925
|
-
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
926
|
-
fill: "currentColor"
|
|
927
|
-
})
|
|
1062
|
+
react_jsx_runtime.jsx("span", {
|
|
1063
|
+
className: S.settingsHint,
|
|
1064
|
+
children: dict("settingsDefaultViewHint")
|
|
928
1065
|
})
|
|
929
1066
|
]
|
|
930
1067
|
}),
|
|
931
|
-
|
|
932
|
-
className: S.
|
|
933
|
-
children:
|
|
934
|
-
|
|
1068
|
+
react_jsx_runtime.jsx("span", {
|
|
1069
|
+
className: saveFailed ? `${S.settingsHint} ${S.settingsHintError}` : S.settingsHint,
|
|
1070
|
+
children: saveFailed
|
|
1071
|
+
? dict("settingsSaveFailed")
|
|
1072
|
+
: translate("settingsPaletteHint", { levels: uiDict().levelLegend.join(" / ") })
|
|
1073
|
+
})
|
|
935
1074
|
]
|
|
936
1075
|
});
|
|
937
1076
|
}
|
|
@@ -939,28 +1078,26 @@ window.__ModuleLoader__.load({
|
|
|
939
1078
|
|
|
940
1079
|
//#region plugin body
|
|
941
1080
|
/**
|
|
942
|
-
* Settings namespace
|
|
943
|
-
*
|
|
944
|
-
*
|
|
1081
|
+
* Settings namespace the card reads/writes — must match the namespace
|
|
1082
|
+
* the Host registers (lib/index.js). Spelled here rather than imported:
|
|
1083
|
+
* a client package must not depend on a Host package.
|
|
945
1084
|
*/
|
|
946
1085
|
const SETTINGS_NS = "token-heatmap";
|
|
947
1086
|
/** Services required by the client plugin body. */
|
|
948
1087
|
const inject = ["slots", "locale", "connection", "remote", "settingsScope"];
|
|
949
1088
|
|
|
950
1089
|
/**
|
|
951
|
-
* Client plugin body: register the dictionaries
|
|
952
|
-
* (heatmap below the composer card on the new-session screen)
|
|
953
|
-
*
|
|
954
|
-
*
|
|
1090
|
+
* Client plugin body: register the dictionaries and the input-dock entry
|
|
1091
|
+
* (the heatmap below the composer card on the new-session screen), backed
|
|
1092
|
+
* by the `token-heatmap` settings namespace through a bound settings
|
|
1093
|
+
* scope. The card configures itself through its own ⚙ panel, so this
|
|
1094
|
+
* plugin does NOT register into `settings.plugin.item` (the official
|
|
1095
|
+
* 设置 → 插件 → 插件配置 card seat) any more — the Host still serves the
|
|
1096
|
+
* namespace, it just has no configuration card to dispatch.
|
|
955
1097
|
*
|
|
956
1098
|
* Slot-kind contract: `conversation.input.dock` is a LIST slot, so its
|
|
957
|
-
* registration is keyed by `id
|
|
958
|
-
*
|
|
959
|
-
* edits) — registering with `id` instead throws
|
|
960
|
-
* `keyed slot "settings.plugin.item" requires options.key` and fails
|
|
961
|
-
* the whole plugin load. The card is only dispatched by the official
|
|
962
|
-
* 插件配置 tab when the Host actually SERVES that namespace, which is
|
|
963
|
-
* why this plugin registers it on the Host side too.
|
|
1099
|
+
* registration is keyed by `id` (a `settings.plugin.item` registration
|
|
1100
|
+
* would instead need `key`, the settings namespace the card edits).
|
|
964
1101
|
* @param ctx - client root context.
|
|
965
1102
|
*/
|
|
966
1103
|
function apply(ctx) {
|
|
@@ -977,21 +1114,19 @@ window.__ModuleLoader__.load({
|
|
|
977
1114
|
order: 10,
|
|
978
1115
|
locale: NS
|
|
979
1116
|
}, TokenHeatmap));
|
|
980
|
-
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
981
|
-
name: "settings.plugin.item",
|
|
982
|
-
key: SETTINGS_NS,
|
|
983
|
-
order: 30,
|
|
984
|
-
locale: NS
|
|
985
|
-
}, TokenHeatmapSettingsCard));
|
|
986
1117
|
}
|
|
987
1118
|
//#endregion
|
|
988
1119
|
|
|
989
1120
|
exports.apply = apply;
|
|
990
1121
|
exports.inject = inject;
|
|
991
1122
|
exports.TokenHeatmap = TokenHeatmap;
|
|
992
|
-
exports.
|
|
1123
|
+
exports.TokenHeatmapInlineSettings = TokenHeatmapInlineSettings;
|
|
993
1124
|
exports.buildGrid = buildGrid;
|
|
1125
|
+
exports.buildMonthGrid = buildMonthGrid;
|
|
994
1126
|
exports.levelOf = levelOf;
|
|
1127
|
+
exports.monthLabelFull = monthLabelFull;
|
|
1128
|
+
exports.dateLabel = dateLabel;
|
|
1129
|
+
exports.shiftMonthKey = shiftMonthKey;
|
|
995
1130
|
exports.CELL_COLORS = CELL_COLORS;
|
|
996
1131
|
exports.COLOR_SCHEMES = COLOR_SCHEMES;
|
|
997
1132
|
exports.createConfigStore = createConfigStore;
|