dsh-plugin-panel 0.1.1 → 0.1.3
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 +6 -0
- package/client.js +820 -143
- package/docs/screenshot.png +0 -0
- package/index.js +99 -1
- package/package.json +5 -2
- package/trend.js +294 -0
- package/docs/promo-3x4.png +0 -0
- package/docs/promo-wechat.png +0 -0
package/client.js
CHANGED
|
@@ -6,7 +6,7 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
const PANEL_ID = 'dsh-plugin-panel'
|
|
7
7
|
const TAB_KIND = 'plugin-canvas'
|
|
8
8
|
const NS = 'dsh-plugin-panel'
|
|
9
|
-
const VERSION = 'v0.1.
|
|
9
|
+
const VERSION = 'v0.1.3'
|
|
10
10
|
const PREF_KEY = 'dsh.plugin-list.pref.v2'
|
|
11
11
|
|
|
12
12
|
const COPY = {
|
|
@@ -16,8 +16,45 @@ window.__ModuleLoader__.load({
|
|
|
16
16
|
'guide.description': '查看并打开右侧栏插件。',
|
|
17
17
|
'list.installed': '用户安装',
|
|
18
18
|
'list.builtin': '系统内置',
|
|
19
|
-
'list.all': '全部',
|
|
20
19
|
'list.refresh': '刷新',
|
|
20
|
+
'list.market': '市场',
|
|
21
|
+
'list.back': '返回',
|
|
22
|
+
'list.marketTitle': '插件市场趋势',
|
|
23
|
+
'list.marketHint': '来自 dsh-plugin-shop 目录的每日快照,不是 npm 全站统计。',
|
|
24
|
+
'list.marketLoading': '正在取历史数据,第一次大约十几秒…',
|
|
25
|
+
'list.marketError': '取不到目录数据。如果刚更新过插件,重启一次 DeepSeek Harness 再试。',
|
|
26
|
+
'list.countNow': '当前收录',
|
|
27
|
+
'list.countFiltered': '已过滤',
|
|
28
|
+
'list.perDay': '日均新增',
|
|
29
|
+
'list.spanDays': '覆盖天数',
|
|
30
|
+
'list.marketFirstNote': '首日已含此前录入的存量,不是从 0 开始',
|
|
31
|
+
'list.install': '安装',
|
|
32
|
+
'list.uninstall': '卸载',
|
|
33
|
+
'list.uninstallConfirm': '确认卸载',
|
|
34
|
+
'list.installing': '安装中…',
|
|
35
|
+
'list.removing': '卸载中…',
|
|
36
|
+
'list.hotDelta': '7 天增长',
|
|
37
|
+
'list.hotStars': '仓库 star',
|
|
38
|
+
'list.hotCategory': '分类',
|
|
39
|
+
'list.hotRepo': '仓库',
|
|
40
|
+
'list.installedState': '已安装',
|
|
41
|
+
'list.notInstalled': '未安装',
|
|
42
|
+
'list.chartTitle': '插件市场的增长趋势',
|
|
43
|
+
'list.chartCumulative': '累计',
|
|
44
|
+
'list.chartDaily': '每日增量',
|
|
45
|
+
'list.chartDailyHint': '每天新增多少,比累计更能看出快慢',
|
|
46
|
+
'list.marketSearch': '搜索整个市场的插件…',
|
|
47
|
+
'list.marketSearching': '搜索中…',
|
|
48
|
+
'list.searchError': '搜索暂时不可用。如果刚更新过插件,在插件管理里停用再启用一次即可。',
|
|
49
|
+
'list.marketNoMatch': '没有找到匹配的插件',
|
|
50
|
+
'list.marketHits': '找到 {n} 个',
|
|
51
|
+
'list.hotSearch': '搜索插件名、说明或分类…',
|
|
52
|
+
'list.hotNoMatch': '没有匹配的插件',
|
|
53
|
+
'list.hotTitle': '最近 7 天增长最快',
|
|
54
|
+
'list.hotHint': '按 GitHub 仓库 star 的 7 天增量排序,同一仓库只算一次。',
|
|
55
|
+
'list.hotLoading': '正在算增长…',
|
|
56
|
+
'list.hotError': '取不到增长数据。如果刚更新插件,需要重启一次 DeepSeek Harness 才会启用这个榜单。',
|
|
57
|
+
'list.hotEmpty': '这 7 天没有增长记录。',
|
|
21
58
|
'list.viewList': '列表',
|
|
22
59
|
'list.viewCard': '卡片',
|
|
23
60
|
'list.open': '打开',
|
|
@@ -79,8 +116,45 @@ window.__ModuleLoader__.load({
|
|
|
79
116
|
'guide.description': 'Browse and open right-sidebar plugins.',
|
|
80
117
|
'list.installed': 'Installed',
|
|
81
118
|
'list.builtin': 'Built-in',
|
|
82
|
-
'list.all': 'All',
|
|
83
119
|
'list.refresh': 'Refresh',
|
|
120
|
+
'list.market': 'Market',
|
|
121
|
+
'list.back': 'Back',
|
|
122
|
+
'list.marketTitle': 'Plugin market trend',
|
|
123
|
+
'list.marketHint': 'Daily snapshots of the dsh-plugin-shop catalog, not a site-wide npm count.',
|
|
124
|
+
'list.marketLoading': 'Fetching history, about ten seconds the first time…',
|
|
125
|
+
'list.marketError': 'Could not read the catalog. Restart DeepSeek Harness once if you just updated.',
|
|
126
|
+
'list.countNow': 'Listed',
|
|
127
|
+
'list.countFiltered': 'Filtered',
|
|
128
|
+
'list.perDay': 'Per day',
|
|
129
|
+
'list.spanDays': 'Days',
|
|
130
|
+
'list.marketFirstNote': 'First point already includes earlier stock; not growth from zero',
|
|
131
|
+
'list.install': 'Install',
|
|
132
|
+
'list.uninstall': 'Remove',
|
|
133
|
+
'list.uninstallConfirm': 'Confirm',
|
|
134
|
+
'list.installing': 'Installing…',
|
|
135
|
+
'list.removing': 'Removing…',
|
|
136
|
+
'list.hotDelta': '7-day growth',
|
|
137
|
+
'list.hotStars': 'Repo stars',
|
|
138
|
+
'list.hotCategory': 'Category',
|
|
139
|
+
'list.hotRepo': 'Repository',
|
|
140
|
+
'list.installedState': 'Installed',
|
|
141
|
+
'list.notInstalled': 'Not installed',
|
|
142
|
+
'list.chartTitle': 'Plugin market growth',
|
|
143
|
+
'list.chartCumulative': 'Total',
|
|
144
|
+
'list.chartDaily': 'Per day',
|
|
145
|
+
'list.chartDailyHint': 'New plugins per day, which shows pace better than a total',
|
|
146
|
+
'list.marketSearch': 'Search the whole market…',
|
|
147
|
+
'list.marketSearching': 'Searching…',
|
|
148
|
+
'list.searchError': 'Search is unavailable. If you just updated the plugin, disable and re-enable it once.',
|
|
149
|
+
'list.marketNoMatch': 'No matching plugin',
|
|
150
|
+
'list.marketHits': '{n} found',
|
|
151
|
+
'list.hotSearch': 'Search by name, description or category…',
|
|
152
|
+
'list.hotNoMatch': 'No matching plugin',
|
|
153
|
+
'list.hotTitle': 'Fastest growing, last 7 days',
|
|
154
|
+
'list.hotHint': 'Ranked by 7-day GitHub repository star delta; one row per repository.',
|
|
155
|
+
'list.hotLoading': 'Crunching growth…',
|
|
156
|
+
'list.hotError': 'Could not load growth data. Restart DeepSeek Harness once to enable this ranking.',
|
|
157
|
+
'list.hotEmpty': 'No growth recorded in the last 7 days.',
|
|
84
158
|
'list.viewList': 'List',
|
|
85
159
|
'list.viewCard': 'Cards',
|
|
86
160
|
'list.open': 'Open',
|
|
@@ -151,7 +225,7 @@ window.__ModuleLoader__.load({
|
|
|
151
225
|
try {
|
|
152
226
|
const parsed = JSON.parse(window.localStorage.getItem(PREF_KEY) || '{}')
|
|
153
227
|
return {
|
|
154
|
-
filter:
|
|
228
|
+
filter: parsed.filter === 'builtin' ? 'builtin' : 'installed',
|
|
155
229
|
pins: Array.isArray(parsed.pins) ? parsed.pins.filter((id) => typeof id === 'string') : [],
|
|
156
230
|
layout: parsed.layout === 'card' ? 'card' : 'list',
|
|
157
231
|
}
|
|
@@ -253,6 +327,169 @@ window.__ModuleLoader__.load({
|
|
|
253
327
|
return ''
|
|
254
328
|
}
|
|
255
329
|
|
|
330
|
+
/** Bars for the day-over-day change; the first day has no previous snapshot. */
|
|
331
|
+
function barChart(days, hoverIndex, onHover) {
|
|
332
|
+
const W = 1000
|
|
333
|
+
const H = 250
|
|
334
|
+
const L = 68
|
|
335
|
+
const R = 24
|
|
336
|
+
const T = 18
|
|
337
|
+
const B = 42
|
|
338
|
+
const deltas = days.map((day, index) => (index === 0 ? null : day.count - days[index - 1].count))
|
|
339
|
+
const usable = deltas.filter((value) => typeof value === 'number')
|
|
340
|
+
const peak = Math.max(...usable, 1) * 1.15
|
|
341
|
+
const n = days.length
|
|
342
|
+
const slot = (W - L - R) / Math.max(n, 1)
|
|
343
|
+
const barW = Math.max(2, Math.min(slot * 0.62, 14))
|
|
344
|
+
const x = (i) => L + slot * i + slot / 2
|
|
345
|
+
const zeroY = T + (H - T - B)
|
|
346
|
+
const y = (v) => zeroY - ((H - T - B) * v) / Math.max(peak, 1)
|
|
347
|
+
const ticks = [0, 0.5, 1].map((r) => Math.round((peak * r) / 500) * 500)
|
|
348
|
+
const labelIndexes = n <= 1 ? [0] : [0, Math.floor((n - 1) / 2), n - 1]
|
|
349
|
+
|
|
350
|
+
const pick = (event) => {
|
|
351
|
+
const rect = event.currentTarget.getBoundingClientRect()
|
|
352
|
+
if (!rect.width) return
|
|
353
|
+
const px = ((event.clientX - rect.left) / rect.width) * W
|
|
354
|
+
const index = Math.round((px - L - slot / 2) / Math.max(slot, 1))
|
|
355
|
+
onHover(Math.max(0, Math.min(n - 1, index)))
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const hovered = hoverIndex >= 0 && hoverIndex < n ? hoverIndex : -1
|
|
359
|
+
const hx = hovered >= 0 ? x(hovered) : 0
|
|
360
|
+
const value = hovered >= 0 ? deltas[hovered] : null
|
|
361
|
+
const boxW = 200
|
|
362
|
+
const boxH = 64
|
|
363
|
+
const boxX = hovered >= 0 ? Math.min(Math.max(hx - boxW / 2, L), W - R - boxW) : 0
|
|
364
|
+
const boxY = hovered >= 0 ? Math.max(T + 8, y(typeof value === 'number' ? value : 0) - boxH - 14) : 0
|
|
365
|
+
const shown = typeof value === 'number' ? `${value >= 0 ? '+' : ''}${value.toLocaleString()}` : '—'
|
|
366
|
+
|
|
367
|
+
return h('svg', {
|
|
368
|
+
className: 'dsh-pl-chart',
|
|
369
|
+
viewBox: `0 0 ${W} ${H}`,
|
|
370
|
+
role: 'img',
|
|
371
|
+
onMouseMove: pick,
|
|
372
|
+
onMouseLeave: () => onHover(-1),
|
|
373
|
+
},
|
|
374
|
+
ticks.map((tick, i) => h('line', {
|
|
375
|
+
key: `g${i}`, x1: L, x2: W - R, y1: y(tick), y2: y(tick),
|
|
376
|
+
stroke: 'var(--dsw-alias-border-l1)', strokeWidth: 1,
|
|
377
|
+
})),
|
|
378
|
+
ticks.map((tick, i) => h('text', {
|
|
379
|
+
key: `t${i}`, x: L - 12, y: y(tick) + 6, textAnchor: 'end',
|
|
380
|
+
fill: 'var(--dsw-alias-label-secondary)', fontSize: 16,
|
|
381
|
+
}, `+${Math.round(tick / 1000)}k`)),
|
|
382
|
+
deltas.map((delta, i) => {
|
|
383
|
+
if (typeof delta !== 'number') return null
|
|
384
|
+
const top = Math.min(y(delta), zeroY)
|
|
385
|
+
const height = Math.max(1, Math.abs(zeroY - y(delta)))
|
|
386
|
+
return h('rect', {
|
|
387
|
+
key: `b${i}`, x: x(i) - barW / 2, y: top, width: barW, height,
|
|
388
|
+
rx: Math.min(2, barW / 2),
|
|
389
|
+
fill: 'var(--dsw-alias-brand-primary)',
|
|
390
|
+
opacity: hovered === i ? 0.95 : 0.55,
|
|
391
|
+
})
|
|
392
|
+
}),
|
|
393
|
+
labelIndexes.map((i) => h('text', {
|
|
394
|
+
key: `d${i}`, x: x(i), y: H - B + 34, textAnchor: i === 0 ? 'start' : i === n - 1 ? 'end' : 'middle',
|
|
395
|
+
fill: 'var(--dsw-alias-label-secondary)', fontSize: 16,
|
|
396
|
+
}, days[i].day.slice(5))),
|
|
397
|
+
hovered >= 0 ? h('g', null,
|
|
398
|
+
h('line', {
|
|
399
|
+
x1: hx, x2: hx, y1: T, y2: H - B,
|
|
400
|
+
stroke: 'var(--dsw-alias-label-secondary)', strokeWidth: 1, strokeDasharray: '4 4', opacity: 0.5,
|
|
401
|
+
}),
|
|
402
|
+
h('rect', {
|
|
403
|
+
x: boxX, y: boxY, width: boxW, height: boxH, rx: 10,
|
|
404
|
+
fill: 'var(--dsw-alias-bg-overlay)', stroke: 'var(--dsw-alias-border-l2)', strokeWidth: 1,
|
|
405
|
+
}),
|
|
406
|
+
h('text', { x: boxX + 14, y: boxY + 24, fill: 'var(--dsw-alias-label-secondary)', fontSize: 16 }, days[hovered].day),
|
|
407
|
+
h('text', { x: boxX + 14, y: boxY + 50, fill: 'var(--dsw-alias-label-primary)', fontSize: 22, fontWeight: 600 }, shown),
|
|
408
|
+
) : null,
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function trendChart(days, hoverIndex, onHover, firstNote) {
|
|
413
|
+
const W = 1000
|
|
414
|
+
const H = 250
|
|
415
|
+
const L = 68
|
|
416
|
+
const R = 24
|
|
417
|
+
const T = 18
|
|
418
|
+
const B = 42
|
|
419
|
+
const values = days.map((d) => d.count)
|
|
420
|
+
const max = Math.max(...values, 1) * 1.2
|
|
421
|
+
const n = days.length
|
|
422
|
+
const x = (i) => L + (W - L - R) * (n <= 1 ? 0.5 : i / (n - 1))
|
|
423
|
+
const y = (v) => T + (H - T - B) * (1 - v / max)
|
|
424
|
+
const line = days.map((d, i) => `${i === 0 ? 'M' : 'L'} ${x(i).toFixed(1)} ${y(d.count).toFixed(1)}`).join(' ')
|
|
425
|
+
const area = `${line} L ${x(n - 1).toFixed(1)} ${H - B} L ${x(0).toFixed(1)} ${H - B} Z`
|
|
426
|
+
const ticks = [0, 0.25, 0.5, 0.75, 1].map((r) => Math.round((max * r) / 1000) * 1000)
|
|
427
|
+
const labelIndexes = n <= 1 ? [0] : [0, Math.floor((n - 1) / 2), n - 1]
|
|
428
|
+
|
|
429
|
+
const pick = (event) => {
|
|
430
|
+
const rect = event.currentTarget.getBoundingClientRect()
|
|
431
|
+
if (!rect.width) return
|
|
432
|
+
const px = ((event.clientX - rect.left) / rect.width) * W
|
|
433
|
+
const ratio = (px - L) / Math.max(W - L - R, 1)
|
|
434
|
+
const index = Math.round(ratio * Math.max(n - 1, 1))
|
|
435
|
+
onHover(Math.max(0, Math.min(n - 1, index)))
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const hovered = hoverIndex >= 0 && hoverIndex < n ? hoverIndex : -1
|
|
439
|
+
const hx = hovered >= 0 ? x(hovered) : 0
|
|
440
|
+
const hy = hovered >= 0 ? y(values[hovered]) : 0
|
|
441
|
+
const isFirst = hovered === 0
|
|
442
|
+
const boxW = isFirst ? 246 : 196
|
|
443
|
+
const boxH = isFirst ? 96 : 64
|
|
444
|
+
const boxX = hovered >= 0 ? Math.min(Math.max(hx - boxW / 2, L), W - R - boxW) : 0
|
|
445
|
+
const boxY = hovered >= 0 ? Math.max(hy - boxH - 16, T) : 0
|
|
446
|
+
|
|
447
|
+
return h('svg', {
|
|
448
|
+
className: 'dsh-pl-chart',
|
|
449
|
+
viewBox: `0 0 ${W} ${H}`,
|
|
450
|
+
role: 'img',
|
|
451
|
+
onMouseMove: pick,
|
|
452
|
+
onMouseLeave: () => onHover(-1),
|
|
453
|
+
},
|
|
454
|
+
ticks.map((tick, i) => h('line', {
|
|
455
|
+
key: `g${i}`, x1: L, x2: W - R, y1: y(tick), y2: y(tick),
|
|
456
|
+
stroke: 'var(--dsw-alias-border-l1)', strokeWidth: 1,
|
|
457
|
+
})),
|
|
458
|
+
ticks.map((tick, i) => h('text', {
|
|
459
|
+
key: `t${i}`, x: L - 12, y: y(tick) + 6, textAnchor: 'end',
|
|
460
|
+
fill: 'var(--dsw-alias-label-secondary)', fontSize: 16,
|
|
461
|
+
}, `${Math.round(tick / 1000)}k`)),
|
|
462
|
+
h('path', { d: area, fill: 'var(--dsw-alias-brand-primary)', opacity: 0.08 }),
|
|
463
|
+
h('path', { d: line, fill: 'none', stroke: 'var(--dsw-alias-brand-primary)', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' }),
|
|
464
|
+
labelIndexes.map((i) => h('text', {
|
|
465
|
+
key: `d${i}`, x: x(i), y: H - B + 34, textAnchor: i === 0 ? 'start' : i === n - 1 ? 'end' : 'middle',
|
|
466
|
+
fill: 'var(--dsw-alias-label-secondary)', fontSize: 16,
|
|
467
|
+
}, days[i].day.slice(5))),
|
|
468
|
+
h('text', {
|
|
469
|
+
x: x(n - 1), y: y(values[n - 1]) - 16, textAnchor: 'end',
|
|
470
|
+
fill: 'var(--dsw-alias-label-primary)', fontSize: 22, fontWeight: 600,
|
|
471
|
+
}, String(values[n - 1])),
|
|
472
|
+
hovered >= 0 ? h('g', null,
|
|
473
|
+
h('line', {
|
|
474
|
+
x1: hx, x2: hx, y1: T, y2: H - B,
|
|
475
|
+
stroke: 'var(--dsw-alias-label-secondary)', strokeWidth: 1, strokeDasharray: '4 4', opacity: 0.5,
|
|
476
|
+
}),
|
|
477
|
+
h('circle', { cx: hx, cy: hy, r: 4.5, fill: 'var(--dsw-alias-brand-primary)', stroke: '#fff', strokeWidth: 2 }),
|
|
478
|
+
h('rect', {
|
|
479
|
+
x: boxX, y: boxY, width: boxW, height: boxH, rx: 10,
|
|
480
|
+
fill: 'var(--dsw-alias-bg-overlay)', stroke: 'var(--dsw-alias-border-l2)', strokeWidth: 1,
|
|
481
|
+
}),
|
|
482
|
+
h('text', { x: boxX + 14, y: boxY + 24, fill: 'var(--dsw-alias-label-secondary)', fontSize: 16 },
|
|
483
|
+
days[hovered].day),
|
|
484
|
+
h('text', { x: boxX + 14, y: boxY + 50, fill: 'var(--dsw-alias-label-primary)', fontSize: 22, fontWeight: 600 },
|
|
485
|
+
`${values[hovered].toLocaleString()} 个`),
|
|
486
|
+
isFirst && firstNote
|
|
487
|
+
? h('text', { x: boxX + 14, y: boxY + 78, fill: 'var(--dsw-alias-label-secondary)', fontSize: 14 }, firstNote)
|
|
488
|
+
: null,
|
|
489
|
+
) : null,
|
|
490
|
+
)
|
|
491
|
+
}
|
|
492
|
+
|
|
256
493
|
function detailNames(plugin, bundle) {
|
|
257
494
|
const names = []
|
|
258
495
|
const moduleName = String((plugin && plugin.moduleName) || '')
|
|
@@ -410,6 +647,49 @@ window.__ModuleLoader__.load({
|
|
|
410
647
|
}) || null
|
|
411
648
|
}
|
|
412
649
|
|
|
650
|
+
/** One card shape, shared by the plugin list and the market ranking. */
|
|
651
|
+
function PluginCard(options) {
|
|
652
|
+
const { icon, title, subtitle, actions, className, onClick, badge } = options
|
|
653
|
+
const classes = ['dsh-pl-item']
|
|
654
|
+
if (className) classes.push(className)
|
|
655
|
+
return h('div', {
|
|
656
|
+
className: classes.join(' '),
|
|
657
|
+
role: 'button',
|
|
658
|
+
tabIndex: 0,
|
|
659
|
+
onClick,
|
|
660
|
+
onKeyDown: (event) => {
|
|
661
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
662
|
+
event.preventDefault()
|
|
663
|
+
if (onClick) onClick()
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
icon || h(DefaultPluginIcon),
|
|
668
|
+
h('span', { className: 'dsh-pl-copy' },
|
|
669
|
+
h('strong', null, title),
|
|
670
|
+
subtitle ? h('span', { className: 'dsh-pl-where' }, subtitle) : null),
|
|
671
|
+
badge || null,
|
|
672
|
+
actions && actions.length ? h('div', { className: 'dsh-pl-acts' }, actions) : null)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** One detail shape, shared by the plugin detail and the market detail. */
|
|
676
|
+
function DetailPanel(options) {
|
|
677
|
+
const { icon, title, facts, onClose, closeLabel, actions, note, children } = options
|
|
678
|
+
const rows = (facts || []).filter((entry) => entry && entry.value)
|
|
679
|
+
return h('aside', { className: 'dsh-pl-detail' },
|
|
680
|
+
h('div', { className: 'dsh-pl-detail-head' },
|
|
681
|
+
icon || h(DefaultPluginIcon),
|
|
682
|
+
h('strong', null, title),
|
|
683
|
+
h('button', { type: 'button', onClick: onClose }, closeLabel)),
|
|
684
|
+
h('div', { className: 'dsh-pl-detail-body' },
|
|
685
|
+
rows.map((entry, index) => h('div', { className: 'dsh-pl-fact', key: index },
|
|
686
|
+
h('span', { className: 'dsh-pl-fact-label' }, entry.label),
|
|
687
|
+
h('span', { className: 'dsh-pl-fact-value' }, entry.value))),
|
|
688
|
+
note ? h('p', { className: 'dsh-pl-note' }, note) : null,
|
|
689
|
+
children || null,
|
|
690
|
+
actions && actions.length ? h('div', { className: 'dsh-pl-actions' }, actions) : null))
|
|
691
|
+
}
|
|
692
|
+
|
|
413
693
|
function DefaultPluginIcon() {
|
|
414
694
|
return h('svg', {
|
|
415
695
|
className: 'dsh-pl-fallback',
|
|
@@ -449,6 +729,22 @@ window.__ModuleLoader__.load({
|
|
|
449
729
|
const [filter, setFilter] = React.useState(pref.current.filter)
|
|
450
730
|
const [pins, setPins] = React.useState(pref.current.pins || [])
|
|
451
731
|
const [view, setView] = React.useState(pref.current.layout === 'card' ? 'card' : 'list')
|
|
732
|
+
const [surface, setSurface] = React.useState('plugins')
|
|
733
|
+
const [trend, setTrend] = React.useState(null)
|
|
734
|
+
const [trendStatus, setTrendStatus] = React.useState('')
|
|
735
|
+
const [hoverIndex, setHoverIndex] = React.useState(-1)
|
|
736
|
+
const [hot, setHot] = React.useState(null)
|
|
737
|
+
const [hotError, setHotError] = React.useState('')
|
|
738
|
+
const [busyKey, setBusyKey] = React.useState('')
|
|
739
|
+
const [confirmKey, setConfirmKey] = React.useState('')
|
|
740
|
+
const [selectedMarket, setSelectedMarket] = React.useState(null)
|
|
741
|
+
const [chartMode, setChartMode] = React.useState('cumulative')
|
|
742
|
+
const [hotQuery, setHotQuery] = React.useState('')
|
|
743
|
+
const [marketQuery, setMarketQuery] = React.useState('')
|
|
744
|
+
const [searchResult, setSearchResult] = React.useState(null)
|
|
745
|
+
const [searchStatus, setSearchStatus] = React.useState('')
|
|
746
|
+
const [trendError, setTrendError] = React.useState('')
|
|
747
|
+
const [hotStatus, setHotStatus] = React.useState('')
|
|
452
748
|
const [plugins, setPlugins] = React.useState([])
|
|
453
749
|
const [bundles, setBundles] = React.useState([])
|
|
454
750
|
const [status, setStatus] = React.useState(listPlugins ? 'loading' : 'unavailable')
|
|
@@ -574,12 +870,100 @@ window.__ModuleLoader__.load({
|
|
|
574
870
|
return () => { cancelled = true }
|
|
575
871
|
}, [selectedId, plugins, bundles, loadFacts])
|
|
576
872
|
|
|
873
|
+
const MARKET_CACHE_KEY = 'dsh.plugin-list.market.v1'
|
|
874
|
+
|
|
875
|
+
function todayKey() {
|
|
876
|
+
return new Date().toISOString().slice(0, 10)
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function readMarketCache() {
|
|
880
|
+
try {
|
|
881
|
+
const raw = JSON.parse(window.localStorage.getItem(MARKET_CACHE_KEY) || '{}')
|
|
882
|
+
return raw && raw.day === todayKey() ? raw : null
|
|
883
|
+
} catch {
|
|
884
|
+
return null
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function writeMarketCache(patch) {
|
|
889
|
+
try {
|
|
890
|
+
const raw = readMarketCache() || { day: todayKey() }
|
|
891
|
+
window.localStorage.setItem(MARKET_CACHE_KEY, JSON.stringify({ ...raw, ...patch }))
|
|
892
|
+
} catch { /* caching is best effort */ }
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const loadTrend = React.useCallback(async () => {
|
|
896
|
+
const cached = readMarketCache()
|
|
897
|
+
if (cached && cached.trend) {
|
|
898
|
+
setTrend(cached.trend)
|
|
899
|
+
setTrendStatus('ready')
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
const call = props.loadMarketTrend
|
|
903
|
+
if (typeof call !== 'function') return
|
|
904
|
+
setTrendStatus('loading')
|
|
905
|
+
try {
|
|
906
|
+
const result = await call()
|
|
907
|
+
if (result && result.ok === false) {
|
|
908
|
+
setTrendError((result.error && result.error.message) || '')
|
|
909
|
+
setTrendStatus('error')
|
|
910
|
+
return
|
|
911
|
+
}
|
|
912
|
+
const value = result && result.ok === true ? result.value : result
|
|
913
|
+
if (value && Array.isArray(value.days) && value.days.length) {
|
|
914
|
+
setTrend(value)
|
|
915
|
+
writeMarketCache({ trend: value })
|
|
916
|
+
setTrendStatus('ready')
|
|
917
|
+
} else {
|
|
918
|
+
setTrendStatus('error')
|
|
919
|
+
}
|
|
920
|
+
} catch (failure) {
|
|
921
|
+
setTrendError(failure instanceof Error ? failure.message : '')
|
|
922
|
+
setTrendStatus('error')
|
|
923
|
+
}
|
|
924
|
+
}, [props.loadMarketTrend])
|
|
925
|
+
|
|
926
|
+
const loadHot = React.useCallback(async () => {
|
|
927
|
+
const cached = readMarketCache()
|
|
928
|
+
if (cached && cached.hot) {
|
|
929
|
+
setHot(cached.hot)
|
|
930
|
+
setHotStatus('ready')
|
|
931
|
+
return
|
|
932
|
+
}
|
|
933
|
+
const call = props.loadMarketHot
|
|
934
|
+
if (typeof call !== 'function') return
|
|
935
|
+
setHotStatus('loading')
|
|
936
|
+
try {
|
|
937
|
+
const result = await call()
|
|
938
|
+
if (result && result.ok === false) {
|
|
939
|
+
setHotError((result.error && result.error.message) || '')
|
|
940
|
+
setHotStatus('error')
|
|
941
|
+
return
|
|
942
|
+
}
|
|
943
|
+
const value = result && result.ok === true ? result.value : result
|
|
944
|
+
if (value && Array.isArray(value.top) && value.top.length) {
|
|
945
|
+
setHot(value)
|
|
946
|
+
writeMarketCache({ hot: value })
|
|
947
|
+
setHotStatus('ready')
|
|
948
|
+
} else {
|
|
949
|
+
setHotStatus('empty')
|
|
950
|
+
}
|
|
951
|
+
} catch (failure) {
|
|
952
|
+
setHotError(failure instanceof Error ? failure.message : '')
|
|
953
|
+
setHotStatus('error')
|
|
954
|
+
}
|
|
955
|
+
}, [props.loadMarketHot])
|
|
956
|
+
|
|
957
|
+
React.useEffect(() => {
|
|
958
|
+
if (surface !== 'market') return
|
|
959
|
+
if (trendStatus === '') void loadTrend()
|
|
960
|
+
if (hotStatus === '') void loadHot()
|
|
961
|
+
}, [surface, trendStatus, hotStatus, loadTrend, loadHot])
|
|
962
|
+
|
|
577
963
|
const visible = React.useMemo(() => {
|
|
578
964
|
const rows = plugins.filter((plugin) => {
|
|
579
965
|
const origin = originOf(plugin)
|
|
580
|
-
|
|
581
|
-
if (filter === 'builtin') return origin === 'builtin'
|
|
582
|
-
return true
|
|
966
|
+
return filter === 'builtin' ? origin === 'builtin' : origin === 'installed'
|
|
583
967
|
})
|
|
584
968
|
const rank = (plugin) => {
|
|
585
969
|
const places = placesFor(plugin, guide, seats, localeId)
|
|
@@ -599,13 +983,6 @@ window.__ModuleLoader__.load({
|
|
|
599
983
|
const selected = plugins.find((item) => item.entryId === selectedId) || null
|
|
600
984
|
const selectedBundle = selected ? matchBundle(selected, bundles) : null
|
|
601
985
|
|
|
602
|
-
function fact(label, value) {
|
|
603
|
-
if (!value) return null
|
|
604
|
-
return h('div', { className: 'dsh-pl-fact' },
|
|
605
|
-
h('span', { className: 'dsh-pl-fact-label' }, label),
|
|
606
|
-
h('span', { className: 'dsh-pl-fact-value' }, value))
|
|
607
|
-
}
|
|
608
|
-
|
|
609
986
|
function revealHint(text) {
|
|
610
987
|
const raw = String(text || '')
|
|
611
988
|
if (/Unknown detail method|invalid server-response|unaddressable|not found/i.test(raw)) return textOf(t, 'list.revealRestart')
|
|
@@ -640,64 +1017,73 @@ window.__ModuleLoader__.load({
|
|
|
640
1017
|
const componentText = rows.length
|
|
641
1018
|
? `${rows.length}${siblings.length ? ` · ${siblings.join('、')}${rows.length > 7 ? '…' : ''}` : ''}`
|
|
642
1019
|
: ''
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1020
|
+
const detailFacts = [
|
|
1021
|
+
{ label: textOf(t, 'list.intro'), value: intro || textOf(t, 'list.unknown') },
|
|
1022
|
+
{ label: textOf(t, 'list.module'), value: shortModule(plugin.moduleName) },
|
|
1023
|
+
{ label: textOf(t, 'list.entry'), value: plugin.entryId },
|
|
1024
|
+
{ label: textOf(t, 'list.status'), value: textOf(t, phaseKey(plugin)) },
|
|
1025
|
+
{ label: textOf(t, 'list.package'), value: (selectedBundle && selectedBundle.name) || (manifest && manifest.name) || shortModule(plugin.moduleName) },
|
|
1026
|
+
{ label: textOf(t, 'list.version'), value: (selectedBundle && selectedBundle.version) || (manifest && manifest.version) },
|
|
1027
|
+
{ label: textOf(t, 'list.place'), value: place },
|
|
1028
|
+
{ label: textOf(t, 'list.source'), value: source },
|
|
1029
|
+
{ label: textOf(t, 'list.flags'), value: flags },
|
|
1030
|
+
{ label: textOf(t, 'list.components'), value: componentText },
|
|
1031
|
+
{ label: textOf(t, 'list.license'), value: manifest && manifest.license },
|
|
1032
|
+
{ label: textOf(t, 'list.repo'), value: manifest && manifest.repository },
|
|
1033
|
+
{ label: textOf(t, 'list.engines'), value: manifest && manifest.engines },
|
|
1034
|
+
{ label: textOf(t, 'list.keywords'), value: manifest && Array.isArray(manifest.keywords) ? manifest.keywords.join('、') : '' },
|
|
1035
|
+
{ label: textOf(t, 'list.inject'), value: manifest && Array.isArray(manifest.clientInject) ? manifest.clientInject.join('、') : '' },
|
|
1036
|
+
]
|
|
1037
|
+
|
|
1038
|
+
const detailActions = []
|
|
1039
|
+
const openKind = tabKindFor(plugin, guide, localeId)
|
|
1040
|
+
if (openKind && typeof openPanel === 'function') {
|
|
1041
|
+
detailActions.push(h('button', {
|
|
1042
|
+
key: 'open',
|
|
1043
|
+
type: 'button',
|
|
1044
|
+
onClick: () => openPanel(openKind),
|
|
1045
|
+
}, textOf(t, 'list.open')))
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof props.revealPackage === 'function') {
|
|
1048
|
+
detailActions.push(h('button', {
|
|
1049
|
+
key: 'reveal',
|
|
1050
|
+
type: 'button',
|
|
1051
|
+
onClick: () => {
|
|
1052
|
+
setRevealNote('')
|
|
1053
|
+
const source = selectedBundle && selectedBundle.source
|
|
1054
|
+
Promise.resolve(props.revealPackage({ moduleName: plugin.moduleName || '', source: source || '' })).then((result) => {
|
|
1055
|
+
if (result && result.ok === false) setRevealNote(revealHint(result.error && result.error.message))
|
|
1056
|
+
}).catch((failure) => {
|
|
1057
|
+
setRevealNote(revealHint(failure instanceof Error ? failure.message : ''))
|
|
1058
|
+
})
|
|
1059
|
+
},
|
|
1060
|
+
}, textOf(t, 'list.reveal')))
|
|
1061
|
+
}
|
|
1062
|
+
if (plugin.patchId && plugin.readOnlyReason == null && setPluginEnabled) {
|
|
1063
|
+
detailActions.push(h('button', {
|
|
1064
|
+
key: 'toggle',
|
|
1065
|
+
type: 'button',
|
|
1066
|
+
onClick: () => { void setPluginEnabled(plugin.entryId, plugin.enabled !== true).then(() => load()) },
|
|
1067
|
+
}, plugin.enabled ? textOf(t, 'list.disable') : textOf(t, 'list.enable')))
|
|
1068
|
+
}
|
|
1069
|
+
if (revealNote) detailActions.push(h('span', { key: 'note', className: 'dsh-pl-note' }, revealNote))
|
|
1070
|
+
if (plugin.readOnlyReason) detailActions.push(h('span', { key: 'locked', className: 'dsh-pl-note' }, String(plugin.readOnlyReason)))
|
|
1071
|
+
|
|
1072
|
+
return h(DetailPanel, {
|
|
1073
|
+
icon: icon ? h('img', { src: icon, alt: '' }) : null,
|
|
1074
|
+
title,
|
|
1075
|
+
closeLabel: textOf(t, 'list.close'),
|
|
1076
|
+
onClose: () => setSelectedId(''),
|
|
1077
|
+
facts: detailFacts,
|
|
1078
|
+
actions: detailActions,
|
|
1079
|
+
children: h('section', { className: 'dsh-pl-doc-block' },
|
|
1080
|
+
h('div', { className: 'dsh-pl-fact-label' }, textOf(t, 'list.doc')),
|
|
1081
|
+
readmeStatus === 'loading'
|
|
1082
|
+
? h('p', null, textOf(t, 'list.docLoading'))
|
|
1083
|
+
: (readme || (facts && facts.document))
|
|
1084
|
+
? markdownView(readme || facts.document)
|
|
1085
|
+
: h('p', null, textOf(t, 'list.docEmpty'))),
|
|
1086
|
+
})
|
|
701
1087
|
}
|
|
702
1088
|
|
|
703
1089
|
function togglePin(entryId) {
|
|
@@ -719,20 +1105,271 @@ window.__ModuleLoader__.load({
|
|
|
719
1105
|
}, label)
|
|
720
1106
|
}
|
|
721
1107
|
|
|
1108
|
+
function isInstalled(name) {
|
|
1109
|
+
const target = String(name || '')
|
|
1110
|
+
return plugins.some((plugin) => {
|
|
1111
|
+
const moduleName = String(plugin.moduleName || '')
|
|
1112
|
+
return moduleName === target || plugin.patchId === target || plugin.entryId === `include:${target}`
|
|
1113
|
+
})
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
async function installFromMarket(name) {
|
|
1117
|
+
if (typeof props.installBundle !== 'function' || busyKey) return
|
|
1118
|
+
setBusyKey(name)
|
|
1119
|
+
try {
|
|
1120
|
+
const result = await props.installBundle(String(name))
|
|
1121
|
+
const value = result && result.ok === true ? result.value : result
|
|
1122
|
+
if (value && value.application === 'failed') {
|
|
1123
|
+
setHotError((value.error && (value.error.diagnostic || value.error.code)) || '')
|
|
1124
|
+
} else if (value && Array.isArray(value.pendingBuilds) && value.pendingBuilds.length) {
|
|
1125
|
+
setHotError(`需要批准构建脚本:${value.pendingBuilds.join(', ')}`)
|
|
1126
|
+
}
|
|
1127
|
+
await load()
|
|
1128
|
+
} catch (failure) {
|
|
1129
|
+
setHotError(failure instanceof Error ? failure.message : '')
|
|
1130
|
+
} finally {
|
|
1131
|
+
setBusyKey('')
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
async function removeFromMarket(name) {
|
|
1136
|
+
if (typeof props.removeBundle !== 'function' || busyKey) return
|
|
1137
|
+
setBusyKey(name)
|
|
1138
|
+
setConfirmKey('')
|
|
1139
|
+
try {
|
|
1140
|
+
const result = await props.removeBundle(String(name))
|
|
1141
|
+
const value = result && result.ok === true ? result.value : result
|
|
1142
|
+
if (value && value.application === 'failed') {
|
|
1143
|
+
setHotError((value.error && (value.error.diagnostic || value.error.code)) || '')
|
|
1144
|
+
}
|
|
1145
|
+
await load()
|
|
1146
|
+
} catch (failure) {
|
|
1147
|
+
setHotError(failure instanceof Error ? failure.message : '')
|
|
1148
|
+
} finally {
|
|
1149
|
+
setBusyKey('')
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function hotAction(row) {
|
|
1154
|
+
const name = row.name
|
|
1155
|
+
if (busyKey === name) {
|
|
1156
|
+
return h('span', { className: 'dsh-pl-busy' }, isInstalled(name) ? textOf(t, 'list.removing') : textOf(t, 'list.installing'))
|
|
1157
|
+
}
|
|
1158
|
+
if (isInstalled(name)) {
|
|
1159
|
+
return confirmKey === name
|
|
1160
|
+
? h('button', {
|
|
1161
|
+
type: 'button',
|
|
1162
|
+
className: 'dsh-pl-card-btn',
|
|
1163
|
+
onClick: (event) => { event.stopPropagation(); void removeFromMarket(name) },
|
|
1164
|
+
}, textOf(t, 'list.uninstallConfirm'))
|
|
1165
|
+
: h('button', {
|
|
1166
|
+
type: 'button',
|
|
1167
|
+
className: 'dsh-pl-card-btn',
|
|
1168
|
+
onClick: (event) => { event.stopPropagation(); setConfirmKey(name) },
|
|
1169
|
+
}, textOf(t, 'list.uninstall'))
|
|
1170
|
+
}
|
|
1171
|
+
return h('button', {
|
|
1172
|
+
type: 'button',
|
|
1173
|
+
className: 'dsh-pl-card-btn',
|
|
1174
|
+
onClick: (event) => { event.stopPropagation(); void installFromMarket(name) },
|
|
1175
|
+
}, textOf(t, 'list.install'))
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
function hotView() {
|
|
1179
|
+
const rows = hot && Array.isArray(hot.top) ? hot.top : []
|
|
1180
|
+
const query = hotQuery.trim().toLowerCase()
|
|
1181
|
+
const shown = query
|
|
1182
|
+
? rows.filter((row) => [row.name, row.summary, row.category]
|
|
1183
|
+
.filter(Boolean)
|
|
1184
|
+
.some((field) => String(field).toLowerCase().includes(query)))
|
|
1185
|
+
: rows
|
|
1186
|
+
return h('section', { className: 'dsh-pl-hot' },
|
|
1187
|
+
h('div', { className: 'dsh-pl-block-head' },
|
|
1188
|
+
h('strong', null, textOf(t, 'list.hotTitle')),
|
|
1189
|
+
hot && hot.fromDay
|
|
1190
|
+
? h('span', null, rows.length === shown.length ? `${hot.fromDay} → ${hot.toDay}` : `${shown.length} / ${rows.length}`)
|
|
1191
|
+
: null),
|
|
1192
|
+
h('p', { className: 'dsh-pl-note' }, textOf(t, 'list.hotHint')),
|
|
1193
|
+
hotStatus === 'loading'
|
|
1194
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.hotLoading'))
|
|
1195
|
+
: hotStatus === 'error'
|
|
1196
|
+
? h('p', { className: 'dsh-pl-state' }, hotError ? `${textOf(t, 'list.hotError')}(${hotError})` : textOf(t, 'list.hotError'))
|
|
1197
|
+
: rows.length === 0
|
|
1198
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.hotEmpty'))
|
|
1199
|
+
: shown.length === 0
|
|
1200
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.hotNoMatch'))
|
|
1201
|
+
: h('div', { className: `dsh-pl-grid${view === 'card' ? ' is-card' : ''}` },
|
|
1202
|
+
shown.map((row, index) => h(PluginCard, {
|
|
1203
|
+
key: row.repo || row.name || index,
|
|
1204
|
+
icon: row.icon ? h('img', { src: row.icon, alt: '', draggable: false }) : null,
|
|
1205
|
+
title: row.name,
|
|
1206
|
+
subtitle: row.summary || row.category || '',
|
|
1207
|
+
className: 'is-market',
|
|
1208
|
+
badge: h('span', { className: 'dsh-pl-delta' }, `+${row.delta.toLocaleString()}`),
|
|
1209
|
+
onClick: () => setSelectedMarket(row),
|
|
1210
|
+
actions: [hotAction(row)],
|
|
1211
|
+
}))))
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const searchTimer = React.useRef(null)
|
|
1215
|
+
|
|
1216
|
+
const runMarketSearch = React.useCallback((query) => {
|
|
1217
|
+
const call = props.searchCatalog
|
|
1218
|
+
if (typeof call !== 'function') return
|
|
1219
|
+
if (!query.trim()) {
|
|
1220
|
+
setSearchResult(null)
|
|
1221
|
+
setSearchStatus('')
|
|
1222
|
+
return
|
|
1223
|
+
}
|
|
1224
|
+
setSearchStatus('loading')
|
|
1225
|
+
Promise.resolve(call({ query })).then((result) => {
|
|
1226
|
+
if (result && result.ok === false) {
|
|
1227
|
+
setSearchResult(null)
|
|
1228
|
+
setSearchStatus('error')
|
|
1229
|
+
return
|
|
1230
|
+
}
|
|
1231
|
+
const value = result && result.ok === true ? result.value : result
|
|
1232
|
+
setSearchResult(value && Array.isArray(value.items) ? value.items : [])
|
|
1233
|
+
setSearchStatus('ready')
|
|
1234
|
+
}).catch(() => {
|
|
1235
|
+
setSearchResult(null)
|
|
1236
|
+
setSearchStatus('error')
|
|
1237
|
+
})
|
|
1238
|
+
}, [props.searchCatalog])
|
|
1239
|
+
|
|
1240
|
+
function onMarketQuery(value) {
|
|
1241
|
+
setMarketQuery(value)
|
|
1242
|
+
if (searchTimer.current) clearTimeout(searchTimer.current)
|
|
1243
|
+
searchTimer.current = setTimeout(() => runMarketSearch(value), 300)
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
function marketDetail(row) {
|
|
1247
|
+
const installed = isInstalled(row.name)
|
|
1248
|
+
const facts = [
|
|
1249
|
+
typeof row.delta === 'number'
|
|
1250
|
+
? { label: textOf(t, 'list.hotDelta'), value: `+${row.delta.toLocaleString()}` }
|
|
1251
|
+
: null,
|
|
1252
|
+
typeof row.stars === 'number'
|
|
1253
|
+
? { label: textOf(t, 'list.hotStars'), value: row.stars.toLocaleString() }
|
|
1254
|
+
: null,
|
|
1255
|
+
row.version ? { label: textOf(t, 'list.version'), value: row.version } : null,
|
|
1256
|
+
row.category ? { label: textOf(t, 'list.hotCategory'), value: row.category } : null,
|
|
1257
|
+
row.repo ? { label: textOf(t, 'list.hotRepo'), value: row.repo } : null,
|
|
1258
|
+
{ label: textOf(t, 'list.status'), value: installed ? textOf(t, 'list.installedState') : textOf(t, 'list.notInstalled') },
|
|
1259
|
+
].filter(Boolean)
|
|
1260
|
+
return h(DetailPanel, {
|
|
1261
|
+
title: row.name,
|
|
1262
|
+
closeLabel: textOf(t, 'list.close'),
|
|
1263
|
+
onClose: () => setSelectedMarket(null),
|
|
1264
|
+
facts,
|
|
1265
|
+
note: row.summary || '',
|
|
1266
|
+
actions: [hotAction(row)],
|
|
1267
|
+
})
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
function marketView() {
|
|
1271
|
+
const days = trend && Array.isArray(trend.days) ? trend.days : []
|
|
1272
|
+
const first = days[0]
|
|
1273
|
+
const last = days[days.length - 1]
|
|
1274
|
+
const span = first && last
|
|
1275
|
+
? Math.max(1, Math.round((Date.parse(last.day) - Date.parse(first.day)) / 86400000))
|
|
1276
|
+
: 0
|
|
1277
|
+
const perDay = first && last ? Math.round((last.count - first.count) / Math.max(span, 1)) : 0
|
|
1278
|
+
const query = marketQuery.trim()
|
|
1279
|
+
const searchBox = h('input', {
|
|
1280
|
+
type: 'search',
|
|
1281
|
+
className: 'dsh-pl-search',
|
|
1282
|
+
placeholder: textOf(t, 'list.marketSearch'),
|
|
1283
|
+
value: marketQuery,
|
|
1284
|
+
onChange: (event) => onMarketQuery(event.target.value),
|
|
1285
|
+
})
|
|
1286
|
+
if (query) {
|
|
1287
|
+
const items = Array.isArray(searchResult) ? searchResult : []
|
|
1288
|
+
return h('div', { className: 'dsh-pl-market' },
|
|
1289
|
+
searchBox,
|
|
1290
|
+
searchStatus === 'loading'
|
|
1291
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.marketSearching'))
|
|
1292
|
+
: searchStatus === 'error'
|
|
1293
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.searchError'))
|
|
1294
|
+
: items.length === 0
|
|
1295
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.marketNoMatch'))
|
|
1296
|
+
: h('div', null,
|
|
1297
|
+
h('div', { className: 'dsh-pl-block-head' },
|
|
1298
|
+
h('strong', null, textOf(t, 'list.marketHits').replace('{n}', String(items.length)))),
|
|
1299
|
+
h('div', { className: `dsh-pl-grid${view === 'card' ? ' is-card' : ''}` },
|
|
1300
|
+
items.map((row, index) => h(PluginCard, {
|
|
1301
|
+
key: row.name || index,
|
|
1302
|
+
icon: null,
|
|
1303
|
+
title: row.name,
|
|
1304
|
+
subtitle: row.summary || row.category || '',
|
|
1305
|
+
className: 'is-market',
|
|
1306
|
+
onClick: () => setSelectedMarket(row),
|
|
1307
|
+
actions: [hotAction(row)],
|
|
1308
|
+
})))))
|
|
1309
|
+
}
|
|
1310
|
+
return h('div', { className: 'dsh-pl-market' },
|
|
1311
|
+
searchBox,
|
|
1312
|
+
trendStatus === 'loading'
|
|
1313
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.marketLoading'))
|
|
1314
|
+
: trendStatus === 'error'
|
|
1315
|
+
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.marketError'))
|
|
1316
|
+
: days.length
|
|
1317
|
+
? [
|
|
1318
|
+
h('section', { className: 'dsh-pl-block', key: 'trend' },
|
|
1319
|
+
h('div', { className: 'dsh-pl-block-head' },
|
|
1320
|
+
h('strong', null, textOf(t, 'list.chartTitle')),
|
|
1321
|
+
h('div', { className: 'dsh-pl-seg', role: 'group', 'aria-label': 'chart' },
|
|
1322
|
+
chip('cumulative', textOf(t, 'list.chartCumulative'), chartMode, setChartMode),
|
|
1323
|
+
chip('daily', textOf(t, 'list.chartDaily'), chartMode, setChartMode))),
|
|
1324
|
+
h('div', { className: 'dsh-pl-market-stats' },
|
|
1325
|
+
stat(textOf(t, 'list.countNow'), last.count.toLocaleString()),
|
|
1326
|
+
stat(textOf(t, 'list.countFiltered'), (last.rejected || 0).toLocaleString()),
|
|
1327
|
+
stat(textOf(t, 'list.perDay'), `+${perDay.toLocaleString()}`),
|
|
1328
|
+
stat(textOf(t, 'list.spanDays'), String(days.length))),
|
|
1329
|
+
h('div', { className: 'dsh-pl-chart-wrap' },
|
|
1330
|
+
chartMode === 'daily'
|
|
1331
|
+
? barChart(days, hoverIndex, setHoverIndex)
|
|
1332
|
+
: trendChart(days, hoverIndex, setHoverIndex, textOf(t, 'list.marketFirstNote'))),
|
|
1333
|
+
h('p', { className: 'dsh-pl-note' }, chartMode === 'daily' ? textOf(t, 'list.chartDailyHint') : textOf(t, 'list.marketHint'))),
|
|
1334
|
+
hotView(),
|
|
1335
|
+
]
|
|
1336
|
+
: null)
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function stat(label, value) {
|
|
1340
|
+
return h('div', { className: 'dsh-pl-stat' },
|
|
1341
|
+
h('span', null, label),
|
|
1342
|
+
h('strong', null, value))
|
|
1343
|
+
}
|
|
1344
|
+
|
|
722
1345
|
return h('div', { className: 'dsh-pl' },
|
|
723
1346
|
h('style', null, CSS),
|
|
724
1347
|
h('header', { className: 'dsh-pl-bar' },
|
|
725
1348
|
h('div', { className: 'dsh-pl-tools' },
|
|
726
1349
|
h('div', { className: 'dsh-pl-seg', role: 'group', 'aria-label': 'filter' },
|
|
727
|
-
chip('installed', textOf(t, 'list.installed'), filter, (id) => { setFilter(id); remember({ filter: id }) }),
|
|
728
|
-
chip('builtin', textOf(t, 'list.builtin'), filter, (id) => { setFilter(id); remember({ filter: id }) }),
|
|
729
|
-
chip('
|
|
730
|
-
h('button', {
|
|
1350
|
+
chip('installed', textOf(t, 'list.installed'), surface === 'market' ? '' : filter, (id) => { setSurface('plugins'); setFilter(id); remember({ filter: id }) }),
|
|
1351
|
+
chip('builtin', textOf(t, 'list.builtin'), surface === 'market' ? '' : filter, (id) => { setSurface('plugins'); setFilter(id); remember({ filter: id }) }),
|
|
1352
|
+
chip('market', textOf(t, 'list.market'), surface === 'market' ? 'market' : '', () => setSurface('market'))),
|
|
1353
|
+
h('button', {
|
|
1354
|
+
type: 'button',
|
|
1355
|
+
className: 'dsh-pl-refresh',
|
|
1356
|
+
onClick: () => {
|
|
1357
|
+
if (surface === 'market') {
|
|
1358
|
+
try { window.localStorage.removeItem('dsh.plugin-list.market.v1') } catch { /* ignore */ }
|
|
1359
|
+
setTrendStatus('')
|
|
1360
|
+
setHotStatus('')
|
|
1361
|
+
} else {
|
|
1362
|
+
void load()
|
|
1363
|
+
}
|
|
1364
|
+
},
|
|
1365
|
+
}, textOf(t, 'list.refresh')),
|
|
731
1366
|
h('div', { className: 'dsh-pl-view', role: 'group', 'aria-label': 'view' },
|
|
732
1367
|
chip('list', textOf(t, 'list.viewList'), view, (id) => { setView(id); remember({ layout: id }) }),
|
|
733
1368
|
chip('card', textOf(t, 'list.viewCard'), view, (id) => { setView(id); remember({ layout: id }) })),
|
|
734
1369
|
h('span', { className: 'dsh-pl-version' }, VERSION))),
|
|
735
|
-
|
|
1370
|
+
surface === 'market'
|
|
1371
|
+
? selectedMarket ? marketDetail(selectedMarket) : marketView()
|
|
1372
|
+
: status === 'loading' && plugins.length === 0
|
|
736
1373
|
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.loading'))
|
|
737
1374
|
: status === 'unavailable'
|
|
738
1375
|
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.unavailable'))
|
|
@@ -748,66 +1385,80 @@ window.__ModuleLoader__.load({
|
|
|
748
1385
|
? h('p', { className: 'dsh-pl-state' }, textOf(t, 'list.empty'))
|
|
749
1386
|
: visible.map((plugin) => {
|
|
750
1387
|
const title = pluginTitle(plugin, localeId)
|
|
751
|
-
const
|
|
1388
|
+
const iconUrl = safeIcon(plugin.meta && plugin.meta.icon)
|
|
752
1389
|
const kind = tabKindFor(plugin, guide, localeId)
|
|
753
1390
|
const pinned = pins.includes(plugin.entryId)
|
|
754
|
-
|
|
1391
|
+
const places = placesFor(plugin, guide, seats, localeId)
|
|
1392
|
+
.map((place) => textOf(t, place === 'right' ? 'list.placeRight' : place === 'settings' ? 'list.placeSettings' : 'list.placeLeft'))
|
|
1393
|
+
.join(' · ') || textOf(t, 'list.placeNone')
|
|
1394
|
+
const classes = [
|
|
1395
|
+
selectedId === plugin.entryId ? 'is-on' : '',
|
|
1396
|
+
pinned ? 'is-pinned' : '',
|
|
1397
|
+
plugin.fiberPhase === 'failed' ? 'is-failed' : '',
|
|
1398
|
+
].filter(Boolean).join(' ')
|
|
1399
|
+
return h(PluginCard, {
|
|
755
1400
|
key: plugin.entryId,
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1401
|
+
icon: iconUrl ? h('img', { src: iconUrl, alt: '', draggable: false }) : null,
|
|
1402
|
+
title,
|
|
1403
|
+
subtitle: places,
|
|
1404
|
+
className: classes,
|
|
759
1405
|
onClick: () => setSelectedId(plugin.entryId),
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
'
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
togglePin(plugin.entryId)
|
|
783
|
-
},
|
|
784
|
-
}, pinned ? textOf(t, 'list.unpin') : textOf(t, 'list.pin')),
|
|
785
|
-
kind ? h('button', {
|
|
786
|
-
type: 'button',
|
|
787
|
-
className: 'dsh-pl-open',
|
|
788
|
-
onClick: (event) => {
|
|
789
|
-
event.stopPropagation()
|
|
790
|
-
if (typeof openPanel === 'function') openPanel(kind)
|
|
791
|
-
},
|
|
792
|
-
}, textOf(t, 'list.open')) : null))
|
|
1406
|
+
actions: [
|
|
1407
|
+
h('button', {
|
|
1408
|
+
key: 'pin',
|
|
1409
|
+
type: 'button',
|
|
1410
|
+
className: 'dsh-pl-card-btn',
|
|
1411
|
+
'aria-pressed': pinned,
|
|
1412
|
+
onClick: (event) => {
|
|
1413
|
+
event.stopPropagation()
|
|
1414
|
+
togglePin(plugin.entryId)
|
|
1415
|
+
},
|
|
1416
|
+
}, pinned ? textOf(t, 'list.unpin') : textOf(t, 'list.pin')),
|
|
1417
|
+
kind ? h('button', {
|
|
1418
|
+
key: 'open',
|
|
1419
|
+
type: 'button',
|
|
1420
|
+
className: 'dsh-pl-card-btn',
|
|
1421
|
+
onClick: (event) => {
|
|
1422
|
+
event.stopPropagation()
|
|
1423
|
+
if (typeof openPanel === 'function') openPanel(kind)
|
|
1424
|
+
},
|
|
1425
|
+
}, textOf(t, 'list.open')) : null,
|
|
1426
|
+
],
|
|
1427
|
+
})
|
|
793
1428
|
}))))
|
|
794
1429
|
}
|
|
795
1430
|
|
|
796
1431
|
const CSS = `
|
|
797
1432
|
.dsh-pl, .dsh-pl * { box-sizing: border-box; }
|
|
798
|
-
.dsh-pl {
|
|
1433
|
+
.dsh-pl {
|
|
1434
|
+
--pl-fs-xs: 11px;
|
|
1435
|
+
--pl-fs-sm: 12px;
|
|
1436
|
+
--pl-fs-md: 13px;
|
|
1437
|
+
--pl-fs-lg: 14px;
|
|
1438
|
+
--pl-fs-title: 16px;
|
|
1439
|
+
--pl-fs-num: 18px;
|
|
1440
|
+
--pl-gap-1: 4px;
|
|
1441
|
+
--pl-gap-2: 8px;
|
|
1442
|
+
--pl-gap-3: 12px;
|
|
1443
|
+
--pl-radius-card: 16px;
|
|
1444
|
+
--pl-radius-inner: 12px;
|
|
1445
|
+
--pl-radius-chip: 10px;
|
|
1446
|
+
--pl-radius-pill: 7px;
|
|
1447
|
+
}
|
|
1448
|
+
.dsh-pl { display: flex; flex-direction: column; flex: 1 1 auto; height: 100%; min-height: 0; min-width: 0; padding-top: 8px; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); font-family: inherit; font-size: var(--pl-fs-md); line-height: 1.4; }
|
|
799
1449
|
.dsh-pl-bar { display: flex; flex: none; flex-direction: column; gap: 8px; margin: 0; padding: 12px 12px 10px; border-bottom: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-specific-sidebar-fill, var(--dsw-alias-bg-layer-1)); position: relative; z-index: 1; }
|
|
800
1450
|
.dsh-pl-top { display: flex; align-items: center; justify-content: space-between; }
|
|
801
|
-
.dsh-pl-version, .dsh-pl-hint, .dsh-pl-item span { color: var(--dsw-alias-label-secondary); font-size:
|
|
1451
|
+
.dsh-pl-version, .dsh-pl-hint, .dsh-pl-item span { color: var(--dsw-alias-label-secondary); font-size: var(--pl-fs-xs); }
|
|
802
1452
|
.dsh-pl-hint { margin: 0; }
|
|
803
1453
|
.dsh-pl-tools { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; width: 100%; }
|
|
804
|
-
.dsh-pl-
|
|
805
|
-
.dsh-pl-
|
|
806
|
-
.dsh-pl-view
|
|
1454
|
+
.dsh-pl-refresh { margin-left: auto; }
|
|
1455
|
+
.dsh-pl-version { margin-left: 10px; }
|
|
1456
|
+
.dsh-pl-view { display: flex; gap: 2px; padding: 2px; border-radius: var(--pl-radius-inner); background: var(--dsw-alias-bg-layer-2); }
|
|
1457
|
+
.dsh-pl-view button { border: 0; border-radius: var(--pl-radius-pill); padding: 3px 8px; font-size: var(--pl-fs-xs); background: transparent; color: var(--dsw-alias-label-secondary); }
|
|
807
1458
|
.dsh-pl-view button.is-on { background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font-weight: 600; }
|
|
808
|
-
.dsh-pl-seg { display: flex; gap: 2px; padding: 3px; border: 0; border-radius:
|
|
809
|
-
.dsh-pl button { font: inherit; font-size:
|
|
810
|
-
.dsh-pl-seg button { border: 0; border-radius:
|
|
1459
|
+
.dsh-pl-seg { display: flex; gap: 2px; padding: 3px; border: 0; border-radius: var(--pl-radius-inner); background: var(--dsw-alias-bg-layer-2); }
|
|
1460
|
+
.dsh-pl button { font: inherit; font-size: var(--pl-fs-sm); line-height: 1.2; color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-base); border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--pl-radius-chip); padding: 6px 12px; cursor: pointer; }
|
|
1461
|
+
.dsh-pl-seg button { border: 0; border-radius: var(--pl-radius-pill); background: transparent; color: var(--dsw-alias-label-secondary); padding: 5px 12px; }
|
|
811
1462
|
.dsh-pl-seg button.is-on { background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font-weight: 600; box-shadow: none; }
|
|
812
1463
|
.dsh-pl-item.is-on { border-color: var(--dsw-alias-brand-primary); background: var(--dsw-alias-bg-layer-2); box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary); }
|
|
813
1464
|
.dsh-pl-body { display: flex; flex: 1 1 auto; min-height: 0; min-width: 0; overflow: hidden; }
|
|
@@ -819,46 +1470,67 @@ window.__ModuleLoader__.load({
|
|
|
819
1470
|
.dsh-pl-acts { display: flex; gap: 6px; align-items: center; margin-left: auto; flex: none; }
|
|
820
1471
|
.dsh-pl-item { position: relative; }
|
|
821
1472
|
.dsh-pl-grid.is-card .dsh-pl-acts { position: absolute; top: 8px; right: 8px; margin: 0; padding: 0; background: transparent; opacity: 0; pointer-events: none; transition: opacity .12s ease; }
|
|
822
|
-
.dsh-pl-grid.is-card .dsh-pl-acts .dsh-pl-
|
|
1473
|
+
.dsh-pl-grid.is-card .dsh-pl-acts .dsh-pl-card-btn { padding: 4px 8px; font-size: var(--pl-fs-xs); border: 0; background: var(--dsw-alias-bg-layer-2); }
|
|
823
1474
|
.dsh-pl-grid.is-card .dsh-pl-item:hover .dsh-pl-acts, .dsh-pl-grid.is-card .dsh-pl-item:focus-within .dsh-pl-acts { opacity: 1; pointer-events: auto; }
|
|
824
|
-
.dsh-pl-item { display: flex; gap: 10px; align-items: center; width: 100%; min-width: 0; min-height: 64px; padding: 12px; text-align: left; border: 1px solid var(--dsw-alias-border-l2); border-radius:
|
|
825
|
-
.dsh-pl-
|
|
1475
|
+
.dsh-pl-item { display: flex; gap: 10px; align-items: center; width: 100%; min-width: 0; min-height: 64px; padding: 12px; text-align: left; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--pl-radius-card); background: var(--dsw-alias-bg-layer-1); overflow: hidden; }
|
|
1476
|
+
.dsh-pl-card-btn { margin-left: 0; flex: none; position: static; }
|
|
826
1477
|
.dsh-pl-item.is-pinned { background: color-mix(in srgb, var(--dsw-alias-label-primary) 7%, var(--dsw-alias-bg-layer-1)); }
|
|
827
|
-
.dsh-pl-item img, .dsh-pl-fallback { width: 22px; height: 22px; border-radius:
|
|
1478
|
+
.dsh-pl-item img, .dsh-pl-fallback { width: 22px; height: 22px; border-radius: var(--pl-radius-pill); object-fit: cover; flex: none; }
|
|
828
1479
|
.dsh-pl-copy { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 2px; }
|
|
829
|
-
.dsh-pl-copy strong { font-size:
|
|
830
|
-
.dsh-pl-where { color: var(--dsw-alias-label-secondary); font-size:
|
|
1480
|
+
.dsh-pl-copy strong { font-size: var(--pl-fs-lg); font-weight: 560; color: var(--dsw-alias-label-primary); white-space: normal; overflow-wrap: anywhere; }
|
|
1481
|
+
.dsh-pl-where { color: var(--dsw-alias-label-secondary); font-size: var(--pl-fs-xs); }
|
|
1482
|
+
.dsh-pl-item.is-market .dsh-pl-where { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1483
|
+
.dsh-pl-item.is-market .dsh-pl-copy strong { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
831
1484
|
.dsh-pl-item.is-failed { border-color: var(--dsw-alias-state-error-primary); }
|
|
832
1485
|
.dsh-pl-state { margin: 16px; color: var(--dsw-alias-label-secondary); }
|
|
833
1486
|
.dsh-pl-detail { flex: 1 1 auto; width: auto; max-width: none; min-width: 0; min-height: 0; overflow: auto; background: var(--dsw-alias-bg-base); padding: 12px 16px 20px; }
|
|
834
1487
|
.dsh-pl-detail-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
|
|
835
|
-
.dsh-pl-detail-head strong { flex: 1 1 auto; min-width: 0; font-size:
|
|
1488
|
+
.dsh-pl-detail-head strong { flex: 1 1 auto; min-width: 0; font-size: var(--pl-fs-title); line-height: 1.35; overflow-wrap: anywhere; }
|
|
836
1489
|
.dsh-pl-detail-head button { flex: none; white-space: nowrap; align-self: flex-start; }
|
|
837
1490
|
.dsh-pl-detail-head img, .dsh-pl-detail-head .dsh-pl-fallback { width: 32px; height: 32px; }
|
|
838
1491
|
.dsh-pl-fact { display: grid; grid-template-columns: 4.5em minmax(0, 1fr); column-gap: 12px; align-items: start; margin: 0 0 10px; }
|
|
839
|
-
.dsh-pl-fact-label { color: var(--dsw-alias-label-secondary); font-size:
|
|
840
|
-
.dsh-pl-fact-value { color: var(--dsw-alias-label-primary); font-size:
|
|
841
|
-
.dsh-pl-detail h3 { margin: 14px 0 6px; font-size:
|
|
842
|
-
.dsh-pl-detail p, .dsh-pl-doc { margin: 0 0 8px; font-size:
|
|
843
|
-
.dsh-pl-note { color: var(--dsw-alias-label-secondary); }
|
|
1492
|
+
.dsh-pl-fact-label { color: var(--dsw-alias-label-secondary); font-size: var(--pl-fs-sm); line-height: 1.45; }
|
|
1493
|
+
.dsh-pl-fact-value { color: var(--dsw-alias-label-primary); font-size: var(--pl-fs-md); line-height: 1.45; overflow-wrap: anywhere; }
|
|
1494
|
+
.dsh-pl-detail h3 { margin: 14px 0 6px; font-size: var(--pl-fs-sm); color: var(--dsw-alias-label-secondary); }
|
|
1495
|
+
.dsh-pl-detail p, .dsh-pl-doc { margin: 0 0 8px; font-size: var(--pl-fs-md); line-height: 1.45; overflow-wrap: anywhere; }
|
|
1496
|
+
.dsh-pl-note { color: var(--dsw-alias-label-secondary); font-size: var(--pl-fs-sm); line-height: 1.5; }
|
|
844
1497
|
.dsh-pl-doc-block { margin-top: 12px; }
|
|
845
|
-
.dsh-pl-md h3, .dsh-pl-md-h { margin: 14px 0 6px; font-size:
|
|
846
|
-
.dsh-pl-md h4 { margin: 10px 0 4px; font-size:
|
|
847
|
-
.dsh-pl-md p, .dsh-pl-md li { margin: 0 0 8px; font-size:
|
|
1498
|
+
.dsh-pl-md h3, .dsh-pl-md-h { margin: 14px 0 6px; font-size: var(--pl-fs-lg); line-height: 1.4; font-weight: 650; color: var(--dsw-alias-label-primary); }
|
|
1499
|
+
.dsh-pl-md h4 { margin: 10px 0 4px; font-size: var(--pl-fs-md); font-weight: 650; }
|
|
1500
|
+
.dsh-pl-md p, .dsh-pl-md li { margin: 0 0 8px; font-size: var(--pl-fs-md); line-height: 1.55; }
|
|
848
1501
|
.dsh-pl-md ul { margin: 0 0 10px; padding-left: 18px; }
|
|
849
|
-
.dsh-pl-md code { font-size:
|
|
1502
|
+
.dsh-pl-md code { font-size: var(--pl-fs-sm); padding: 0 4px; border-radius: 4px; background: var(--dsw-alias-bg-layer-2); }
|
|
850
1503
|
.dsh-pl-md a { color: var(--dsw-alias-brand-primary); }
|
|
851
|
-
.dsh-pl-table-wrap { margin: 8px 0 14px; overflow-x: auto; border: 1px solid var(--dsw-alias-border-l1); border-radius:
|
|
852
|
-
.dsh-pl-md table { width: 100%; border-collapse: collapse; font-size:
|
|
1504
|
+
.dsh-pl-table-wrap { margin: 8px 0 14px; overflow-x: auto; border: 1px solid var(--dsw-alias-border-l1); border-radius: var(--pl-radius-chip); }
|
|
1505
|
+
.dsh-pl-md table { width: 100%; border-collapse: collapse; font-size: var(--pl-fs-md); }
|
|
853
1506
|
.dsh-pl-md th, .dsh-pl-md td { padding: 8px 10px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--dsw-alias-border-l1); }
|
|
854
1507
|
.dsh-pl-md th { color: var(--dsw-alias-label-secondary); font-weight: 650; background: var(--dsw-alias-bg-layer-1); white-space: nowrap; }
|
|
855
1508
|
.dsh-pl-md td:first-child { white-space: nowrap; }
|
|
856
1509
|
.dsh-pl-md tr:last-child td { border-bottom: 0; }
|
|
1510
|
+
.dsh-pl-market { flex: 1 1 auto; min-height: 0; overflow: auto; padding: 12px; }
|
|
1511
|
+
.dsh-pl-market-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-bottom: 12px; }
|
|
1512
|
+
.dsh-pl-stat { display: flex; flex-direction: column; gap: 6px; padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--pl-radius-card); background: var(--dsw-alias-bg-layer-1); }
|
|
1513
|
+
.dsh-pl-stat span { font-size: var(--pl-fs-xs); color: var(--dsw-alias-label-secondary); }
|
|
1514
|
+
.dsh-pl-stat strong { font-size: var(--pl-fs-num); font-weight: 600; line-height: 1.2; }
|
|
1515
|
+
.dsh-pl-chart-wrap { padding: 10px 12px 4px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--pl-radius-card); background: var(--dsw-alias-bg-layer-1); }
|
|
1516
|
+
.dsh-pl-chart { display: block; width: 100%; height: auto; }
|
|
1517
|
+
.dsh-pl-block-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
|
|
1518
|
+
.dsh-pl-block-head strong { font-size: var(--pl-fs-lg); }
|
|
1519
|
+
.dsh-pl-block-head span { font-size: var(--pl-fs-xs); color: var(--dsw-alias-label-secondary); }
|
|
1520
|
+
.dsh-pl-block { margin-bottom: 22px; }
|
|
1521
|
+
.dsh-pl-market .dsh-pl-note { margin: 10px 2px 0; }
|
|
1522
|
+
.dsh-pl-hot { margin-top: 4px; }
|
|
1523
|
+
.dsh-pl-search { width: 100%; margin: 2px 0 8px; padding: 8px 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: var(--pl-radius-inner); background: var(--dsw-alias-bg-layer-1); color: var(--dsw-alias-label-primary); font-family: inherit; font-size: var(--pl-fs-sm); }
|
|
1524
|
+
.dsh-pl-search::placeholder { color: var(--dsw-alias-label-secondary); }
|
|
1525
|
+
.dsh-pl-search:focus { outline: none; border-color: var(--dsw-alias-brand-primary); }
|
|
1526
|
+
.dsh-pl-delta { flex: none; margin-left: auto; font-size: var(--pl-fs-sm); font-weight: 600; color: var(--dsw-alias-brand-primary); }
|
|
1527
|
+
.dsh-pl-busy { flex: none; font-size: var(--pl-fs-xs); color: var(--dsw-alias-label-secondary); }
|
|
1528
|
+
.dsh-pl-hot-busy { flex: none; font-size: var(--pl-fs-xs); color: var(--dsw-alias-label-secondary); }
|
|
857
1529
|
.dsh-pl-actions { display: flex; gap: 8px; align-items: center; margin-top: 16px; }
|
|
858
1530
|
`
|
|
859
1531
|
|
|
860
1532
|
return {
|
|
861
|
-
name: 'dsh-
|
|
1533
|
+
name: 'dsh-plugin-panel',
|
|
862
1534
|
inject: ['slots', 'sidebarRightTabs', 'sidebarRight', 'layout', 'locale', 'remote', 'remote.pluginManager', 'remote.session', 'connection'],
|
|
863
1535
|
apply(ctx) {
|
|
864
1536
|
ctx.effect(() => ctx.locale.register(NS, COPY), 'plugin-list: dictionaries')
|
|
@@ -870,6 +1542,11 @@ window.__ModuleLoader__.load({
|
|
|
870
1542
|
const setPluginEnabled = pluginManager.setPluginEnabled.bind(pluginManager)
|
|
871
1543
|
const rpcCall = ctx.connection.rpc.call.bind(ctx.connection.rpc)
|
|
872
1544
|
const loadFacts = (payload) => rpcCall('/dsh-plugins-ui-rpc', 'facts', payload || {})
|
|
1545
|
+
const loadMarketTrend = (payload) => rpcCall('/dsh-plugins-ui-rpc', 'trend', payload || {})
|
|
1546
|
+
const loadMarketHot = (payload) => rpcCall('/dsh-plugins-ui-rpc', 'hot', payload || {})
|
|
1547
|
+
const searchCatalog = (payload) => rpcCall('/dsh-plugins-ui-rpc', 'search', payload || {})
|
|
1548
|
+
const installBundle = pluginManager.installBundle.bind(pluginManager)
|
|
1549
|
+
const removeBundle = pluginManager.removeBundle.bind(pluginManager)
|
|
873
1550
|
const revealPackage = async (payload) => {
|
|
874
1551
|
const input = payload && typeof payload === 'object' ? payload : {}
|
|
875
1552
|
const dir = directoryFromSource(input.source)
|
|
@@ -953,7 +1630,7 @@ window.__ModuleLoader__.load({
|
|
|
953
1630
|
name: 'sidebar.right.pane.tab',
|
|
954
1631
|
key: PANEL_ID,
|
|
955
1632
|
locale: NS,
|
|
956
|
-
inject: () => ({ listPlugins, listBundles, readBundleDetail, setPluginEnabled, loadFacts, revealPackage, listGuide, subscribeGuide, listSeats, subscribeSeats, openPanel, t, readLocale, subscribeLocale }),
|
|
1633
|
+
inject: () => ({ listPlugins, listBundles, readBundleDetail, setPluginEnabled, installBundle, removeBundle, loadFacts, revealPackage, loadMarketTrend, loadMarketHot, searchCatalog, listGuide, subscribeGuide, listSeats, subscribeSeats, openPanel, t, readLocale, subscribeLocale }),
|
|
957
1634
|
}, ListView)), 'plugin-list: tab body')
|
|
958
1635
|
},
|
|
959
1636
|
}
|