@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/LICENSE +21 -0
- package/README.md +106 -0
- package/README.zh.md +102 -0
- package/cordis.patch.yml +6 -0
- package/lib/cache.js +129 -0
- package/lib/client.js +595 -0
- package/lib/constants.js +11 -0
- package/lib/index.js +147 -0
- package/lib/parser.js +121 -0
- package/lib/tool.js +236 -0
- package/lib/types/cache.d.ts +83 -0
- package/lib/types/client/GithubTrendingPanel.d.ts +23 -0
- package/lib/types/client/index.d.ts +24 -0
- package/lib/types/client/locales.d.ts +9 -0
- package/lib/types/client/store.d.ts +34 -0
- package/lib/types/constants.d.ts +11 -0
- package/lib/types/index.d.ts +41 -0
- package/lib/types/parser.d.ts +41 -0
- package/lib/types/tool.d.ts +118 -0
- package/package.json +121 -0
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
|
package/lib/constants.js
ADDED
|
@@ -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
|