@wangjunjian/dsh-github-trending 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js ADDED
@@ -0,0 +1,595 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@wangjunjian/dsh-github-trending",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
+ let react = require("react");
10
+ //#region lib/constants.js
11
+ /**
12
+ * Constants shared by the host half and the browser half of the plugin.
13
+ *
14
+ * The client bundle inlines this module (it is a local value import, not a
15
+ * `@deepseek-ai/*` import), so both halves always agree on these values.
16
+ *
17
+ * @module @wangjunjian/dsh-github-trending/constants
18
+ */
19
+ /** Default background refresh interval: 4 hours. Also the panel polling interval. */
20
+ const DEFAULT_REFRESH_INTERVAL_MS = 144e5;
21
+ //#endregion
22
+ //#region \0dsh-css:/Users/junjian/GitHub/wang-junjian/dsh-github-trending/src/client/GithubTrendingPanel.module.css.mjs
23
+ const css = ".xbtn-q_overlayRoot{background:var(--dsw-bg-float);border-left:1px solid var(--dsw-alias-border-l2);z-index:100;pointer-events:auto;flex-direction:column;display:flex;position:fixed;top:0;bottom:0;right:0}.xbtn-q_railRoot{background:var(--dsw-bg-float);border:1px solid var(--dsw-alias-border-l2);z-index:100;pointer-events:auto;border-right:none;border-radius:10px 0 0 10px;flex-direction:column;justify-content:center;align-items:center;gap:6px;width:44px;padding:10px 4px;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.xbtn-q_railButton{width:36px;color:var(--dsw-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:8px;flex-direction:column;justify-content:center;align-items:center;padding:8px 0;transition:background-color .15s,color .15s;display:flex}.xbtn-q_railButton:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-link)}.xbtn-q_railLabel{font:var(--dsw-font-caption);writing-mode:vertical-rl;letter-spacing:.06em}.xbtn-q_resizeHandle{cursor:col-resize;z-index:1;width:8px;position:absolute;top:0;bottom:0;left:-4px}.xbtn-q_header{border-bottom:1px solid var(--dsw-border-subtle);flex:none;justify-content:space-between;align-items:center;gap:12px;padding:12px 14px;display:flex}.xbtn-q_titleWrap{align-items:center;gap:8px;min-width:0;display:flex}.xbtn-q_titleIcon{color:var(--dsw-text-brand);flex:none}.xbtn-q_title{font:var(--dsw-font-title-2);color:var(--dsw-text-primary);margin:0;font-weight:700}.xbtn-q_cachedAt{font:var(--dsw-font-caption);color:var(--dsw-text-tertiary);white-space:nowrap;font-size:10px}.xbtn-q_footer{border-top:1px solid var(--dsw-border-subtle);flex:none;justify-content:flex-end;align-items:center;gap:12px;padding:6px 14px;display:flex}.xbtn-q_headerActions{align-items:center;gap:6px;display:flex}.xbtn-q_iconButton{width:28px;height:28px;color:var(--dsw-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;transition:background-color .15s,color .15s;display:flex}.xbtn-q_iconButton:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-primary)}.xbtn-q_iconButton:disabled{opacity:.5;cursor:not-allowed}.xbtn-q_spinning svg{animation:1s linear infinite xbtn-q_dshGithubTrendingSpin}@keyframes xbtn-q_dshGithubTrendingSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.xbtn-q_sinceTabs{border-bottom:1px solid var(--dsw-border-subtle);flex:none;align-items:center;gap:6px;padding:10px 14px;display:flex}.xbtn-q_sinceTabGroup{flex:auto;align-items:center;gap:6px;display:flex}.xbtn-q_sinceTab,.xbtn-q_sinceTabActive{border:1px solid var(--dsw-border-default);background:var(--dsw-fill-input);height:30px;color:var(--dsw-text-secondary);font:var(--dsw-font-body-2);cursor:pointer;border-radius:8px;flex:1 1 0;padding:0 8px;transition:background-color .15s,color .15s,border-color .15s}.xbtn-q_sinceTab:hover{background:var(--dsw-fill-hover);color:var(--dsw-text-primary)}.xbtn-q_sinceTabActive{background:var(--dsw-fill-brand);border-color:var(--dsw-border-brand);color:var(--dsw-text-brand);font-weight:500}.xbtn-q_list{flex-direction:column;flex:auto;gap:8px;padding:8px 10px;display:flex;overflow:auto}.xbtn-q_card{border:1px solid var(--dsw-border-subtle);background:var(--dsw-bg-base);color:inherit;border-radius:10px;flex-direction:column;gap:6px;padding:10px;text-decoration:none;transition:border-color .15s;display:flex}.xbtn-q_card:hover{border-color:var(--dsw-border-brand)}.xbtn-q_cardHeader{align-items:center;gap:8px;min-width:0;display:flex}.xbtn-q_rank,.xbtn-q_rankGold,.xbtn-q_rankSilver,.xbtn-q_rankBronze{width:22px;height:22px;font:var(--dsw-font-caption);color:var(--dsw-text-tertiary);background:var(--dsw-fill-hover);border-radius:50%;flex:none;justify-content:center;align-items:center;font-weight:600;display:flex}.xbtn-q_rankGold{color:#7c4a00;background:linear-gradient(135deg,gold,#ffb800)}.xbtn-q_rankSilver{color:#4a4a4a;background:linear-gradient(135deg,#e8e8e8,silver)}.xbtn-q_rankBronze{color:#5e3a18;background:linear-gradient(135deg,#f0a068,#cd7f32)}.xbtn-q_repoName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font:var(--dsw-font-body-1);color:var(--dsw-text-primary);flex:auto;font-weight:500;overflow:hidden}.xbtn-q_card:hover .xbtn-q_repoName{color:var(--dsw-text-link)}.xbtn-q_description{-webkit-line-clamp:2;color:var(--dsw-text-tertiary);font:var(--dsw-font-caption);-webkit-box-orient:vertical;font-size:14px;line-height:1.35;display:-webkit-box;overflow:hidden}.xbtn-q_meta{color:var(--dsw-text-tertiary);font:var(--dsw-font-caption);flex-wrap:wrap;align-items:center;gap:8px;font-size:11px;display:flex}.xbtn-q_metaPill{background:var(--dsw-fill-hover);color:var(--dsw-text-tertiary);border-radius:10px;align-items:center;gap:4px;padding:1px 6px;display:inline-flex}.xbtn-q_metaDot{border-radius:50%;width:7px;height:7px}.xbtn-q_metaItem{align-items:center;gap:3px;display:inline-flex}.xbtn-q_metaToday{color:var(--dsw-text-success);font-weight:500}.xbtn-q_momentum{flex-direction:column;gap:4px;display:flex}.xbtn-q_momentumTrack{background:var(--dsw-fill-input);border:1px solid var(--dsw-border-subtle);border-radius:2.5px;height:5px;overflow:hidden}.xbtn-q_momentumFill{height:100%;transition:width .4s var(--ds-ease-in-out,ease);border-radius:2.5px}.xbtn-q_status{text-align:center;color:var(--dsw-text-secondary);font:var(--dsw-font-body-2);padding:24px 14px}";
24
+ const tagId = "@wangjunjian/dsh-github-trending/GithubTrendingPanel.module.css";
25
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
26
+ const tag = document.createElement("style");
27
+ tag.dataset.plugin = "@wangjunjian/dsh-github-trending";
28
+ tag.dataset.pluginCss = tagId;
29
+ tag.textContent = css;
30
+ document.head.appendChild(tag);
31
+ }
32
+ var GithubTrendingPanel_module_css_default = {
33
+ "cachedAt": "xbtn-q_cachedAt",
34
+ "card": "xbtn-q_card",
35
+ "cardHeader": "xbtn-q_cardHeader",
36
+ "description": "xbtn-q_description",
37
+ "dshGithubTrendingSpin": "xbtn-q_dshGithubTrendingSpin",
38
+ "footer": "xbtn-q_footer",
39
+ "header": "xbtn-q_header",
40
+ "headerActions": "xbtn-q_headerActions",
41
+ "iconButton": "xbtn-q_iconButton",
42
+ "list": "xbtn-q_list",
43
+ "meta": "xbtn-q_meta",
44
+ "metaDot": "xbtn-q_metaDot",
45
+ "metaItem": "xbtn-q_metaItem",
46
+ "metaPill": "xbtn-q_metaPill",
47
+ "metaToday": "xbtn-q_metaToday",
48
+ "momentum": "xbtn-q_momentum",
49
+ "momentumFill": "xbtn-q_momentumFill",
50
+ "momentumTrack": "xbtn-q_momentumTrack",
51
+ "overlayRoot": "xbtn-q_overlayRoot",
52
+ "railButton": "xbtn-q_railButton",
53
+ "railLabel": "xbtn-q_railLabel",
54
+ "railRoot": "xbtn-q_railRoot",
55
+ "rank": "xbtn-q_rank",
56
+ "rankBronze": "xbtn-q_rankBronze",
57
+ "rankGold": "xbtn-q_rankGold",
58
+ "rankSilver": "xbtn-q_rankSilver",
59
+ "repoName": "xbtn-q_repoName",
60
+ "resizeHandle": "xbtn-q_resizeHandle",
61
+ "sinceTab": "xbtn-q_sinceTab",
62
+ "sinceTabActive": "xbtn-q_sinceTabActive",
63
+ "sinceTabGroup": "xbtn-q_sinceTabGroup",
64
+ "sinceTabs": "xbtn-q_sinceTabs",
65
+ "spinning": "xbtn-q_spinning",
66
+ "status": "xbtn-q_status",
67
+ "title": "xbtn-q_title",
68
+ "titleIcon": "xbtn-q_titleIcon",
69
+ "titleWrap": "xbtn-q_titleWrap"
70
+ };
71
+ //#endregion
72
+ //#region lib/client/store.js
73
+ /**
74
+ * Module-level store holding the overlay panel's UI state: open/closed,
75
+ * collapsed, selected time window, and width.
76
+ *
77
+ * @module @wangjunjian/dsh-github-trending/client/store
78
+ */
79
+ const DEFAULT_STATE = {
80
+ open: true,
81
+ collapsed: false,
82
+ since: "daily",
83
+ width: 320
84
+ };
85
+ var PanelStore = class {
86
+ state = { ...DEFAULT_STATE };
87
+ listeners = /* @__PURE__ */ new Set();
88
+ subscribe(listener) {
89
+ this.listeners.add(listener);
90
+ return () => {
91
+ this.listeners.delete(listener);
92
+ };
93
+ }
94
+ getSnapshot() {
95
+ return this.state;
96
+ }
97
+ emit() {
98
+ for (const listener of this.listeners) listener();
99
+ }
100
+ setOpen(open) {
101
+ if (this.state.open === open) return;
102
+ this.state = {
103
+ ...this.state,
104
+ open
105
+ };
106
+ this.emit();
107
+ }
108
+ setCollapsed(collapsed) {
109
+ if (this.state.collapsed === collapsed) return;
110
+ this.state = {
111
+ ...this.state,
112
+ collapsed
113
+ };
114
+ this.emit();
115
+ }
116
+ toggleCollapsed() {
117
+ this.setCollapsed(!this.state.collapsed);
118
+ }
119
+ setSince(since) {
120
+ if (this.state.since === since) return;
121
+ this.state = {
122
+ ...this.state,
123
+ since
124
+ };
125
+ this.emit();
126
+ }
127
+ setWidth(width) {
128
+ if (this.state.width === width) return;
129
+ this.state = {
130
+ ...this.state,
131
+ width
132
+ };
133
+ this.emit();
134
+ }
135
+ };
136
+ /** Singleton panel store used by the overlay panel. */
137
+ const panelStore = new PanelStore();
138
+ //#endregion
139
+ //#region lib/client/GithubTrendingPanel.js
140
+ /**
141
+ * Fixed right-hand overlay panel that displays cached GitHub Trending data.
142
+ *
143
+ * The panel is designed to feel like a layout column: it is docked to the right
144
+ * edge, has its own scrollbar, can be collapsed to a narrow rail, and its width
145
+ * is resizable. Because external plugins cannot declare new AppFrame grid
146
+ * columns in this version of DSH, the panel lives in `shell.overlay`. To avoid
147
+ * covering the conversation, it pushes the center/details columns via a runtime
148
+ * CSS variable set on the AppFrame root.
149
+ *
150
+ * @module @wangjunjian/dsh-github-trending/client/GithubTrendingPanel
151
+ */
152
+ const SINCE_OPTIONS = [
153
+ "daily",
154
+ "weekly",
155
+ "monthly"
156
+ ];
157
+ /** Polling interval while the panel is expanded: matches the default host cache refresh interval. */
158
+ const POLL_INTERVAL_MS = DEFAULT_REFRESH_INTERVAL_MS;
159
+ function formatCachedAt(iso) {
160
+ if (iso === void 0) return "";
161
+ const date = new Date(iso);
162
+ if (Number.isNaN(date.getTime())) return "";
163
+ return `${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
164
+ }
165
+ /** Deterministic but lively color for each programming language. */
166
+ function languageColor(language) {
167
+ if (language === void 0) return "var(--dsw-text-tertiary)";
168
+ let hash = 0;
169
+ for (let i = 0; i < language.length; i++) hash = language.charCodeAt(i) + ((hash << 5) - hash);
170
+ return `hsl(${Math.abs(hash) % 360} 75% 58%)`;
171
+ }
172
+ function formatNumber(value) {
173
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
174
+ if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
175
+ return value.toLocaleString();
176
+ }
177
+ const PUSH_CLASS = "dsh-github-trending-pushed";
178
+ const LAYOUT_STYLE_ID = "dsh-github-trending-layout";
179
+ /**
180
+ * Push the AppFrame center/details columns left by the panel width so the
181
+ * overlay panel does not cover the conversation. The style is injected once and
182
+ * the CSS variable is toggled on the frame element; no main-project code is
183
+ * modified.
184
+ * @param enabled - whether the panel is currently expanded.
185
+ * @param width - current panel width in pixels.
186
+ * @returns ref to attach to the panel root.
187
+ */
188
+ function useLayoutPush(enabled, width) {
189
+ const rootRef = (0, react.useRef)(null);
190
+ (0, react.useEffect)(() => {
191
+ if (typeof document === "undefined") return;
192
+ let style = document.getElementById(LAYOUT_STYLE_ID);
193
+ if (style === null) {
194
+ style = document.createElement("style");
195
+ style.id = LAYOUT_STYLE_ID;
196
+ style.textContent = `
197
+ .${PUSH_CLASS} > div:nth-child(2),
198
+ .${PUSH_CLASS} > div:nth-child(3) {
199
+ margin-right: var(--dsh-github-trending-width, 0px) !important;
200
+ transition: margin-right var(--ds-transition-duration-slow, 0.2s) var(--ds-ease-in-out, ease);
201
+ }
202
+ `;
203
+ document.head.appendChild(style);
204
+ }
205
+ return () => {
206
+ style?.remove();
207
+ };
208
+ }, []);
209
+ (0, react.useEffect)(() => {
210
+ if (typeof document === "undefined") return;
211
+ const frame = (() => {
212
+ let el = rootRef.current;
213
+ while (el !== null) {
214
+ if (el.hasAttribute("data-shell-overlay")) return el.parentElement;
215
+ el = el.parentElement;
216
+ }
217
+ return document.querySelector("[data-shell-overlay]")?.parentElement ?? null;
218
+ })();
219
+ if (!(frame instanceof HTMLElement)) return;
220
+ if (enabled) {
221
+ frame.classList.add(PUSH_CLASS);
222
+ frame.style.setProperty("--dsh-github-trending-width", `${width}px`);
223
+ } else {
224
+ frame.classList.remove(PUSH_CLASS);
225
+ frame.style.removeProperty("--dsh-github-trending-width");
226
+ }
227
+ }, [enabled, width]);
228
+ return rootRef;
229
+ }
230
+ /**
231
+ * Render the right-hand overlay panel.
232
+ * @param props - panel props.
233
+ */
234
+ function GithubTrendingPanel({ t }) {
235
+ const [state, setState] = (0, react.useState)(panelStore.getSnapshot());
236
+ const [entries, setEntries] = (0, react.useState)({
237
+ daily: void 0,
238
+ weekly: void 0,
239
+ monthly: void 0
240
+ });
241
+ const [loading, setLoading] = (0, react.useState)(false);
242
+ const [error, setError] = (0, react.useState)(false);
243
+ const resizeStart = (0, react.useRef)(void 0);
244
+ const rootRef = useLayoutPush(!state.collapsed, state.width);
245
+ (0, react.useEffect)(() => panelStore.subscribe(() => {
246
+ setState(panelStore.getSnapshot());
247
+ }), []);
248
+ const currentEntry = entries[state.since];
249
+ const fetchEntry = (0, react.useCallback)(async (since, refresh = false) => {
250
+ const params = new URLSearchParams({ since });
251
+ if (refresh) params.set("refresh", "1");
252
+ const response = await fetch(`/github-trending?${params.toString()}`, { headers: { Accept: "application/json" } });
253
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
254
+ return await response.json();
255
+ }, []);
256
+ const load = (0, react.useCallback)(async (refresh = false) => {
257
+ setLoading(true);
258
+ setError(false);
259
+ try {
260
+ if (refresh) {
261
+ const data = await fetchEntry(state.since, true);
262
+ setEntries((prev) => ({
263
+ ...prev,
264
+ [state.since]: data
265
+ }));
266
+ const otherSince = [
267
+ "daily",
268
+ "weekly",
269
+ "monthly"
270
+ ].filter((s) => s !== state.since);
271
+ const others = await Promise.all(otherSince.map(async (since) => {
272
+ return {
273
+ since,
274
+ entry: await fetchEntry(since, false)
275
+ };
276
+ }));
277
+ setEntries((prev) => {
278
+ const next = { ...prev };
279
+ for (const { since, entry } of others) next[since] = entry;
280
+ return next;
281
+ });
282
+ } else {
283
+ const data = await fetchEntry(state.since, false);
284
+ setEntries((prev) => ({
285
+ ...prev,
286
+ [state.since]: data
287
+ }));
288
+ }
289
+ } catch {
290
+ setError(true);
291
+ } finally {
292
+ setLoading(false);
293
+ }
294
+ }, [state.since, fetchEntry]);
295
+ (0, react.useEffect)(() => {
296
+ if (!state.open || state.collapsed) return;
297
+ if (entries[state.since] !== void 0) return;
298
+ load(false);
299
+ }, [
300
+ state.open,
301
+ state.collapsed,
302
+ state.since,
303
+ entries,
304
+ load
305
+ ]);
306
+ (0, react.useEffect)(() => {
307
+ if (!state.open || state.collapsed) return;
308
+ const interval = setInterval(() => {
309
+ load(false);
310
+ }, POLL_INTERVAL_MS);
311
+ return () => {
312
+ clearInterval(interval);
313
+ };
314
+ }, [
315
+ state.open,
316
+ state.collapsed,
317
+ load
318
+ ]);
319
+ const onResizeStart = (0, react.useCallback)((event) => {
320
+ resizeStart.current = {
321
+ x: event.clientX,
322
+ width: state.width
323
+ };
324
+ event.currentTarget.setPointerCapture(event.pointerId);
325
+ }, [state.width]);
326
+ const onResizeMove = (0, react.useCallback)((event) => {
327
+ if (resizeStart.current === void 0) return;
328
+ const dx = resizeStart.current.x - event.clientX;
329
+ panelStore.setWidth(Math.max(260, Math.min(720, resizeStart.current.width + dx)));
330
+ }, []);
331
+ const onResizeEnd = (0, react.useCallback)((event) => {
332
+ if (resizeStart.current === void 0) return;
333
+ event.currentTarget.releasePointerCapture(event.pointerId);
334
+ resizeStart.current = void 0;
335
+ }, []);
336
+ const sinceTabs = (0, react.useMemo)(() => SINCE_OPTIONS.map((value) => ({
337
+ value,
338
+ label: t(`panel.${value}`)
339
+ })), [t]);
340
+ const maxMomentum = (0, react.useMemo)(() => {
341
+ if (currentEntry === void 0 || currentEntry.repositories.length === 0) return 0;
342
+ return Math.max(...currentEntry.repositories.map((repo) => repo.starsToday));
343
+ }, [currentEntry]);
344
+ if (!state.open) return null;
345
+ if (state.collapsed) return (0, react_jsx_runtime.jsx)("div", {
346
+ ref: rootRef,
347
+ className: GithubTrendingPanel_module_css_default.railRoot,
348
+ children: (0, react_jsx_runtime.jsxs)("button", {
349
+ type: "button",
350
+ className: GithubTrendingPanel_module_css_default.railButton,
351
+ onClick: () => {
352
+ panelStore.setCollapsed(false);
353
+ },
354
+ "aria-label": t("action.label"),
355
+ title: t("action.label"),
356
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16, { size: 18 }), (0, react_jsx_runtime.jsx)("span", {
357
+ className: GithubTrendingPanel_module_css_default.railLabel,
358
+ children: t("action.label")
359
+ })]
360
+ })
361
+ });
362
+ return (0, react_jsx_runtime.jsxs)("div", {
363
+ ref: rootRef,
364
+ className: GithubTrendingPanel_module_css_default.overlayRoot,
365
+ style: { width: state.width },
366
+ children: [
367
+ (0, react_jsx_runtime.jsx)("div", {
368
+ className: GithubTrendingPanel_module_css_default.resizeHandle,
369
+ onPointerDown: onResizeStart,
370
+ onPointerMove: onResizeMove,
371
+ onPointerUp: onResizeEnd
372
+ }),
373
+ (0, react_jsx_runtime.jsxs)("div", {
374
+ className: GithubTrendingPanel_module_css_default.header,
375
+ children: [(0, react_jsx_runtime.jsxs)("div", {
376
+ className: GithubTrendingPanel_module_css_default.titleWrap,
377
+ children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSparkle16, {
378
+ size: 18,
379
+ className: GithubTrendingPanel_module_css_default.titleIcon
380
+ }), (0, react_jsx_runtime.jsx)("h3", {
381
+ className: GithubTrendingPanel_module_css_default.title,
382
+ children: t("panel.title")
383
+ })]
384
+ }), (0, react_jsx_runtime.jsxs)("div", {
385
+ className: GithubTrendingPanel_module_css_default.headerActions,
386
+ children: [(0, react_jsx_runtime.jsx)("button", {
387
+ type: "button",
388
+ className: `${GithubTrendingPanel_module_css_default.iconButton} ${loading ? GithubTrendingPanel_module_css_default.spinning : ""}`,
389
+ onClick: () => {
390
+ load(true);
391
+ },
392
+ disabled: loading,
393
+ "aria-label": t("panel.refresh"),
394
+ title: t("panel.refresh"),
395
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 16 })
396
+ }), (0, react_jsx_runtime.jsx)("button", {
397
+ type: "button",
398
+ className: GithubTrendingPanel_module_css_default.iconButton,
399
+ onClick: () => {
400
+ panelStore.setCollapsed(true);
401
+ },
402
+ "aria-label": t("panel.collapse"),
403
+ title: t("panel.collapse"),
404
+ children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, { size: 14 })
405
+ })]
406
+ })]
407
+ }),
408
+ (0, react_jsx_runtime.jsx)("div", {
409
+ className: GithubTrendingPanel_module_css_default.sinceTabs,
410
+ children: (0, react_jsx_runtime.jsx)("div", {
411
+ className: GithubTrendingPanel_module_css_default.sinceTabGroup,
412
+ children: sinceTabs.map((tab) => (0, react_jsx_runtime.jsx)("button", {
413
+ type: "button",
414
+ className: state.since === tab.value ? GithubTrendingPanel_module_css_default.sinceTabActive : GithubTrendingPanel_module_css_default.sinceTab,
415
+ onClick: () => {
416
+ panelStore.setSince(tab.value);
417
+ },
418
+ "aria-pressed": state.since === tab.value,
419
+ children: tab.label
420
+ }, tab.value))
421
+ })
422
+ }),
423
+ (0, react_jsx_runtime.jsxs)("div", {
424
+ className: GithubTrendingPanel_module_css_default.list,
425
+ children: [
426
+ currentEntry === void 0 && loading && (0, react_jsx_runtime.jsx)("div", {
427
+ className: GithubTrendingPanel_module_css_default.status,
428
+ children: t("panel.loading")
429
+ }),
430
+ currentEntry === void 0 && !loading && error && (0, react_jsx_runtime.jsx)("div", {
431
+ className: GithubTrendingPanel_module_css_default.status,
432
+ children: t("panel.error")
433
+ }),
434
+ currentEntry?.repositories.length === 0 && (0, react_jsx_runtime.jsx)("div", {
435
+ className: GithubTrendingPanel_module_css_default.status,
436
+ children: t("panel.empty")
437
+ }),
438
+ currentEntry?.repositories.map((repo) => {
439
+ const momentum = maxMomentum > 0 ? repo.starsToday / maxMomentum * 100 : 0;
440
+ const langColor = languageColor(repo.language);
441
+ const rankClass = repo.rank === 1 ? GithubTrendingPanel_module_css_default.rankGold : repo.rank === 2 ? GithubTrendingPanel_module_css_default.rankSilver : repo.rank === 3 ? GithubTrendingPanel_module_css_default.rankBronze : GithubTrendingPanel_module_css_default.rank;
442
+ return (0, react_jsx_runtime.jsxs)("a", {
443
+ href: repo.url,
444
+ target: "_blank",
445
+ rel: "noreferrer",
446
+ className: GithubTrendingPanel_module_css_default.card,
447
+ title: repo.fullName,
448
+ children: [
449
+ (0, react_jsx_runtime.jsxs)("div", {
450
+ className: GithubTrendingPanel_module_css_default.cardHeader,
451
+ children: [(0, react_jsx_runtime.jsx)("span", {
452
+ className: rankClass,
453
+ children: repo.rank
454
+ }), (0, react_jsx_runtime.jsx)("span", {
455
+ className: GithubTrendingPanel_module_css_default.repoName,
456
+ children: repo.fullName
457
+ })]
458
+ }),
459
+ repo.description !== void 0 && repo.description !== "" && (0, react_jsx_runtime.jsx)("div", {
460
+ className: GithubTrendingPanel_module_css_default.description,
461
+ children: repo.description
462
+ }),
463
+ (0, react_jsx_runtime.jsxs)("div", {
464
+ className: GithubTrendingPanel_module_css_default.meta,
465
+ children: [
466
+ repo.language !== void 0 && repo.language !== "" && (0, react_jsx_runtime.jsxs)("span", {
467
+ className: GithubTrendingPanel_module_css_default.metaPill,
468
+ children: [(0, react_jsx_runtime.jsx)("span", {
469
+ className: GithubTrendingPanel_module_css_default.metaDot,
470
+ style: { background: langColor }
471
+ }), repo.language]
472
+ }),
473
+ repo.stars > 0 && (0, react_jsx_runtime.jsxs)("span", {
474
+ className: GithubTrendingPanel_module_css_default.metaItem,
475
+ children: ["★ ", formatNumber(repo.stars)]
476
+ }),
477
+ repo.forks > 0 && (0, react_jsx_runtime.jsxs)("span", {
478
+ className: GithubTrendingPanel_module_css_default.metaItem,
479
+ children: ["⑂ ", formatNumber(repo.forks)]
480
+ }),
481
+ repo.starsToday > 0 && (0, react_jsx_runtime.jsxs)("span", {
482
+ className: `${GithubTrendingPanel_module_css_default.metaItem} ${GithubTrendingPanel_module_css_default.metaToday}`,
483
+ children: ["+", formatNumber(repo.starsToday)]
484
+ })
485
+ ]
486
+ }),
487
+ (0, react_jsx_runtime.jsx)("div", {
488
+ className: GithubTrendingPanel_module_css_default.momentum,
489
+ children: (0, react_jsx_runtime.jsx)("div", {
490
+ className: GithubTrendingPanel_module_css_default.momentumTrack,
491
+ children: (0, react_jsx_runtime.jsx)("div", {
492
+ className: GithubTrendingPanel_module_css_default.momentumFill,
493
+ style: {
494
+ width: `${momentum}%`,
495
+ background: langColor
496
+ }
497
+ })
498
+ })
499
+ })
500
+ ]
501
+ }, repo.fullName);
502
+ })
503
+ ]
504
+ }),
505
+ (0, react_jsx_runtime.jsx)("div", {
506
+ className: GithubTrendingPanel_module_css_default.footer,
507
+ children: (0, react_jsx_runtime.jsx)("span", {
508
+ className: GithubTrendingPanel_module_css_default.cachedAt,
509
+ children: currentEntry?.cachedAt !== void 0 ? `${t("panel.cachedAt")} ${formatCachedAt(currentEntry.cachedAt)}` : ""
510
+ })
511
+ })
512
+ ]
513
+ });
514
+ }
515
+ //#endregion
516
+ //#region lib/client/locales.js
517
+ /**
518
+ * Locale strings for the GitHub Trending sidebar action.
519
+ *
520
+ * @module @wangjunjian/dsh-github-trending/client/locales
521
+ */
522
+ const en = {
523
+ "action.label": "Trending",
524
+ "action.close": "Close",
525
+ "panel.title": "GitHub Trending",
526
+ "panel.daily": "Daily",
527
+ "panel.weekly": "Weekly",
528
+ "panel.monthly": "Monthly",
529
+ "panel.refresh": "Refresh",
530
+ "panel.loading": "Loading…",
531
+ "panel.empty": "No trending repositories found.",
532
+ "panel.error": "Failed to load trending repositories.",
533
+ "panel.cachedAt": "Updated",
534
+ "panel.autoRefresh": "Auto",
535
+ "panel.collapse": "Collapse",
536
+ "repo.stars": "stars",
537
+ "repo.forks": "forks",
538
+ "repo.today": "today"
539
+ };
540
+ const zh = {
541
+ "action.label": "趋势",
542
+ "action.close": "关闭",
543
+ "panel.title": "GitHub 趋势",
544
+ "panel.daily": "今日",
545
+ "panel.weekly": "本周",
546
+ "panel.monthly": "本月",
547
+ "panel.refresh": "刷新",
548
+ "panel.loading": "加载中…",
549
+ "panel.empty": "没有找到趋势仓库。",
550
+ "panel.error": "加载趋势仓库失败。",
551
+ "panel.cachedAt": "更新于",
552
+ "panel.autoRefresh": "自动",
553
+ "panel.collapse": "收起",
554
+ "repo.stars": "星标",
555
+ "repo.forks": "分支",
556
+ "repo.today": "今日"
557
+ };
558
+ //#endregion
559
+ //#region lib/client/index.js
560
+ /**
561
+ * Browser half of the GitHub Trending plugin.
562
+ *
563
+ * Registers a `shell.overlay` entry that renders the right-hand trending panel.
564
+ * The panel is shown by default so no sidebar trigger is needed.
565
+ *
566
+ * @module @wangjunjian/dsh-github-trending/client
567
+ */
568
+ /** Dictionary namespace owned by this plugin. */
569
+ const NS = "github-trending";
570
+ /** Required client services. */
571
+ const inject = ["slots", "locale"];
572
+ /**
573
+ * Register the GitHub Trending right-hand overlay panel.
574
+ * @param ctx - client root context.
575
+ */
576
+ function apply(ctx) {
577
+ ctx.effect(() => ctx.locale.register(NS, {
578
+ zh,
579
+ en
580
+ }), "github-trending: dictionaries");
581
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({
582
+ name: "shell.overlay",
583
+ id: "github-trending-panel",
584
+ order: 20,
585
+ locale: NS
586
+ }, GithubTrendingPanel));
587
+ }
588
+ //#endregion
589
+ exports.apply = apply;
590
+ exports.inject = inject;
591
+ return module.exports;
592
+ }
593
+ });
594
+
595
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Constants shared by the host half and the browser half of the plugin.
3
+ *
4
+ * The client bundle inlines this module (it is a local value import, not a
5
+ * `@deepseek-ai/*` import), so both halves always agree on these values.
6
+ *
7
+ * @module @wangjunjian/dsh-github-trending/constants
8
+ */
9
+ /** Default background refresh interval: 4 hours. Also the panel polling interval. */
10
+ export const DEFAULT_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;
11
+ //# sourceMappingURL=constants.js.map