@stolyarovmn/dsh-ui-registry-aggregator 0.4.13
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 +225 -0
- package/cordis.patch.yml +6 -0
- package/icon.svg +16 -0
- package/lib/client.js +1254 -0
- package/lib/core.js +1241 -0
- package/lib/index.js +214 -0
- package/package.json +92 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1254 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@stolyarovmn/dsh-ui-registry-aggregator",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
const react = require("react");
|
|
8
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
9
|
+
const {
|
|
10
|
+
Button, Modal, Switch, Menu, StateDot,
|
|
11
|
+
IconPlusOutlineRegular, IconRefreshOutlineRegular, IconChevronDownOutlineRegular,
|
|
12
|
+
IconCopyOutlineRegular, IconShareOutlineRegular, IconTrashOutlineRegular,
|
|
13
|
+
IconDatabaseOutlineRegular, IconCordisPluginOutlineRegular, IconLinkOutlineRegular,
|
|
14
|
+
IconWarningOutlineRegular, IconSearchOutlineRegular, IconClockOutlineRegular,
|
|
15
|
+
IconDownloadOutlineRegular, IconCheckOutlineRegular, IconChevronUpOutlineRegular,
|
|
16
|
+
IconChevronLeftOutlineRegular, IconChevronRightOutlineRegular, IconChevronsUpDownOutlineRegular,
|
|
17
|
+
IconFlatListOutlineRegular, IconShieldOutlineRegular, IconChecklistOutlineRegular, IconCodeOutlineRegular,
|
|
18
|
+
} = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
19
|
+
const h = jsxRuntime.jsx;
|
|
20
|
+
const hs = jsxRuntime.jsxs;
|
|
21
|
+
|
|
22
|
+
const NS = "registry-aggregator";
|
|
23
|
+
const CHANNEL = "/api";
|
|
24
|
+
const RPC_PREFIX = "plugin-sources";
|
|
25
|
+
const SOURCE_TYPES = ["dsh-plugin-shop", "dshplugin-app", "npm", "github", "custom-json", "corporate"];
|
|
26
|
+
let configForm;
|
|
27
|
+
let connection;
|
|
28
|
+
let remote;
|
|
29
|
+
const connectionResetListeners = new Set();
|
|
30
|
+
|
|
31
|
+
const css = `
|
|
32
|
+
.pm-root{height:100%;overflow:auto;color:var(--dsw-alias-label-primary);background:var(--dsw-specific-page,transparent);font-size:13px;--pm-line:var(--dsw-alias-border-l4);--pm-panel:var(--dsw-alias-bg-layer-1);--pm-muted:var(--dsw-alias-label-tertiary);--pm-accent:var(--dsw-alias-state-business-primary)}
|
|
33
|
+
.pm-shell{width:100%;max-width:1120px;margin:0 auto;padding:10px 0 28px;box-sizing:border-box}.pm-kicker{margin:0 0 3px;color:var(--pm-accent);font:600 10px/14px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.13em;text-transform:uppercase}.pm-title{margin:0;font-size:18px;line-height:26px;font-weight:650;letter-spacing:-.015em}.pm-lead{max-width:680px;margin:5px 0 18px;color:var(--dsw-alias-label-secondary);line-height:19px}
|
|
34
|
+
.pm-tabs{display:flex;gap:3px;width:max-content;padding:3px;border:1px solid var(--pm-line);border-radius:9px;background:var(--dsw-alias-bg-layer-2);margin-bottom:16px}.pm-tab{height:29px;padding:0 13px;border:0;border-radius:6px;background:transparent;color:var(--pm-muted);font:600 12px/1 inherit;cursor:pointer}.pm-tab[data-active=true]{background:var(--pm-panel);color:var(--dsw-alias-label-primary);box-shadow:0 1px 3px color-mix(in srgb,#000 12%,transparent)}
|
|
35
|
+
.pm-toolbar{display:flex;gap:8px;align-items:center;margin-bottom:12px}.pm-search{flex:1;position:relative}.pm-search input,.pm-input,.pm-select{box-sizing:border-box;height:35px;width:100%;border:1px solid var(--pm-line);border-radius:7px;background:var(--pm-panel);color:var(--dsw-alias-label-primary);font:inherit;outline:none;padding:0 10px}.pm-search input{padding-left:32px}.pm-search svg{position:absolute;left:10px;top:10px;color:var(--pm-muted)}.pm-search input:focus,.pm-input:focus,.pm-select:focus{border-color:var(--pm-accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--pm-accent) 14%,transparent)}
|
|
36
|
+
.pm-btn{height:33px;padding:0 11px;border:1px solid var(--pm-line);border-radius:7px;background:var(--pm-panel);color:var(--dsw-alias-label-secondary);font:600 12px/1 inherit;cursor:pointer;white-space:nowrap}.pm-btn:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}.pm-btn-primary{color:#fff;background:var(--pm-accent);border-color:var(--pm-accent)}.pm-btn-danger{color:var(--dsw-alias-state-error, #d44)}.pm-btn:disabled{opacity:.45;cursor:not-allowed}
|
|
37
|
+
.pm-panel{border:1px solid var(--pm-line);border-radius:10px;background:var(--pm-panel);overflow:hidden}.pm-panel-head{display:flex;align-items:center;justify-content:space-between;padding:11px 13px;border-bottom:1px solid var(--pm-line)}.pm-panel-title{font-weight:650}.pm-count{font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--pm-muted)}
|
|
38
|
+
.pm-form{display:grid;grid-template-columns:minmax(0,1fr) minmax(170px,.55fr);gap:10px 12px;align-items:end;padding:13px;border-bottom:1px solid var(--pm-line)}.pm-field{min-width:0}.pm-field label{display:block;margin:0 0 5px;color:var(--pm-muted);font-size:11px}.pm-field-url{grid-column:1/-1}.pm-add{grid-column:2;justify-self:end}.pm-options{display:flex;gap:14px;grid-column:1/-1;color:var(--dsw-alias-label-secondary);font-size:11px}.pm-check{display:flex;align-items:center;gap:6px}.pm-notice{margin:0;padding:10px 13px;border-bottom:1px solid var(--pm-line);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.pm-notice-error{color:var(--dsw-alias-state-error,#d44)}
|
|
39
|
+
.pm-source-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:12px 13px}.pm-source{display:flex;flex-direction:column;gap:10px;padding:13px;border:1px solid var(--pm-line);border-radius:10px;background:var(--dsw-alias-bg-layer-2)}.pm-source-main{min-width:0}.pm-source-line{display:flex;align-items:center;gap:8px}.pm-source-name{font-weight:620}.pm-type,.pm-badge,.pm-tag{display:inline-flex;align-items:center;height:18px;padding:0 6px;border:1px solid var(--pm-line);border-radius:99px;color:var(--pm-muted);font:500 10px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.pm-tag{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.pm-source-meta{margin-top:4px;color:var(--pm-muted);font:11px/16px ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pm-source-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.pm-toggle{accent-color:var(--pm-accent)}.pm-source-details{padding-top:9px;border-top:1px solid var(--pm-line)}.pm-source-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.pm-source-buttons{display:flex;align-items:center;gap:6px}.pm-share{color:var(--pm-accent)}.pm-chevron{transition:transform .15s ease}.pm-chevron[data-open=true]{transform:rotate(180deg)}
|
|
40
|
+
.pm-status{width:7px;height:7px;border-radius:50%;background:var(--dsw-alias-label-quaternary,#888);box-shadow:0 0 0 3px color-mix(in srgb,currentColor 10%,transparent)}.pm-status-ok{background:#30a46c}.pm-status-bad{background:var(--dsw-alias-state-error,#d44)}.pm-status-wait{background:#e5a000}.pm-health{color:var(--pm-muted);font-size:11px}
|
|
41
|
+
.pm-results{display:grid;gap:9px}.pm-card{position:relative;padding:13px 14px;border:1px solid var(--pm-line);border-radius:9px;background:var(--pm-panel)}.pm-card:hover{border-color:var(--dsw-alias-border-l3)}.pm-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.pm-plugin-name{font-size:14px;font-weight:650;line-height:20px}.pm-link{color:inherit;text-decoration:none}.pm-link:hover{text-decoration:underline}.pm-version{margin-left:7px;color:var(--pm-muted);font:10px ui-monospace,SFMono-Regular,Consolas,monospace}.pm-signal{display:inline-flex;align-items:center;gap:3px;margin-left:7px;color:var(--pm-muted);font:500 10px/1 ui-monospace,SFMono-Regular,Consolas,monospace}.pm-channel{display:inline-flex;align-items:center;height:18px;margin-left:7px;padding:0 6px;border:1px solid var(--pm-line);border-radius:99px;color:var(--dsw-alias-label-secondary);font:600 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace;text-transform:uppercase}.pm-channel-stable{color:#30a46c}.pm-channel-alpha,.pm-channel-beta,.pm-channel-rc,.pm-channel-prerelease{color:#e5a000}.pm-compat{border:1px solid var(--pm-line);border-radius:99px;padding:3px 6px;color:var(--dsw-alias-label-secondary);font:500 9px/1 ui-monospace,SFMono-Regular,Consolas,monospace;background:transparent}.pm-compat-button{cursor:pointer}.pm-compat-button:hover{border-color:var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary)}.pm-deprecated{color:var(--dsw-alias-state-error,#d44)}.pm-desc{margin:5px 0 3px;color:var(--dsw-alias-label-secondary);line-height:18px}.pm-desc[data-clamped=true]{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pm-more{border:0;background:transparent;padding:0;color:var(--pm-accent);font:500 11px/16px inherit;cursor:pointer}.pm-card-actions{display:flex;align-items:center;gap:7px;margin-top:10px}.pm-install-button{min-width:76px}.pm-install-status{font-size:11px;color:var(--pm-muted)}.pm-meta{display:flex;flex-wrap:wrap;gap:6px}.pm-tags{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}.pm-install{display:flex;gap:7px;align-items:center;margin-top:10px}.pm-command{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:7px 9px;border-radius:6px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font:11px ui-monospace,SFMono-Regular,Consolas,monospace}.pm-icon-btn{width:32px;min-width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--pm-line);border-radius:7px;background:var(--pm-panel);color:var(--dsw-alias-label-secondary);cursor:pointer}.pm-icon-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}.pm-icon-btn svg{display:block}
|
|
42
|
+
.pm-browse-options{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 10px;flex-wrap:wrap}.pm-filter-row{display:flex;align-items:center;gap:7px;flex-wrap:wrap}.pm-filter-select{width:auto;min-width:116px;height:30px}.pm-sort{width:auto;min-width:130px;height:30px}.pm-page-size{width:auto;min-width:76px;height:30px}.pm-pagination{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:12px}.pm-page-controls{display:flex;align-items:center;gap:7px}.pm-page-label{color:var(--pm-muted);font-size:11px}.pm-empty{padding:44px 20px;text-align:center;border:1px dashed var(--pm-line);border-radius:10px;color:var(--pm-muted)}.pm-empty strong{display:block;margin-bottom:5px;color:var(--dsw-alias-label-secondary)}.pm-spin{animation:pm-spin .8s linear infinite}@keyframes pm-spin{to{transform:rotate(360deg)}}@media(max-width:720px){.pm-shell{padding:8px 0 28px}.pm-source-list{grid-template-columns:1fr}.pm-form{grid-template-columns:1fr}.pm-field-url,.pm-add,.pm-options{grid-column:1}.pm-add{justify-self:stretch;width:100%}.pm-source{grid-template-columns:1fr}.pm-source-actions{justify-content:flex-start}.pm-card-head{display:block}.pm-card-head>.pm-btn{margin-top:8px}}
|
|
43
|
+
|
|
44
|
+
/* Registry Aggregator source layout */
|
|
45
|
+
.pm-root{--pm-blue:#5792ff;--pm-green:#2dcc74;--pm-danger:#ff5f66;background:radial-gradient(900px 420px at 50% -180px,color-mix(in srgb,var(--pm-blue) 10%,transparent),transparent 72%),var(--dsw-specific-page,transparent)}
|
|
46
|
+
.pm-shell{max-width:1200px;padding:18px 0 34px}
|
|
47
|
+
.pm-kicker{margin-bottom:5px;color:#6da6ff;font-size:10px;letter-spacing:.18em}
|
|
48
|
+
.pm-title{font-size:30px;line-height:38px;font-weight:720;letter-spacing:-.025em}
|
|
49
|
+
.pm-lead{max-width:900px;margin:5px 0 20px;font-size:14px;line-height:21px;color:var(--dsw-alias-label-secondary)}
|
|
50
|
+
.pm-tabs{gap:0;padding:3px;margin-bottom:18px;border-radius:11px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 86%,transparent)}
|
|
51
|
+
.pm-tab{height:38px;min-width:112px;padding:0 17px;border:1px solid transparent;border-radius:8px;font-size:13px}
|
|
52
|
+
.pm-tab[data-active=true]{border-color:color-mix(in srgb,var(--pm-blue) 75%,transparent);background:color-mix(in srgb,var(--pm-blue) 14%,var(--pm-panel));color:#87b3ff;box-shadow:none}
|
|
53
|
+
.pm-tab-content{display:inline-flex;align-items:center;justify-content:center;gap:9px}
|
|
54
|
+
.pm-tab-content svg{width:17px;height:17px}
|
|
55
|
+
|
|
56
|
+
.pm-sources-view{display:grid;gap:16px}
|
|
57
|
+
.pm-add-source-panel,.pm-connected-panel{overflow:hidden;border:1px solid color-mix(in srgb,var(--pm-line) 92%,#6f8ec8 8%);border-radius:13px;background:linear-gradient(180deg,color-mix(in srgb,var(--pm-panel) 96%,#14233c 4%),var(--pm-panel))}
|
|
58
|
+
.pm-section-head{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:18px 20px}
|
|
59
|
+
.pm-section-head-main{display:flex;align-items:center;gap:14px;min-width:0}
|
|
60
|
+
.pm-section-icon{width:44px;height:44px;flex:0 0 44px;display:grid;place-items:center;border:1px solid color-mix(in srgb,var(--pm-blue) 55%,transparent);border-radius:50%;color:#72a8ff;background:color-mix(in srgb,var(--pm-blue) 13%,transparent)}
|
|
61
|
+
.pm-section-icon svg{width:22px;height:22px}
|
|
62
|
+
.pm-section-title{font-size:15px;line-height:20px;font-weight:700;color:var(--dsw-alias-label-primary)}
|
|
63
|
+
.pm-section-lead{margin-top:3px;color:var(--pm-muted);font-size:12px;line-height:17px}
|
|
64
|
+
.pm-disclosure,.pm-refresh-icon{width:34px;height:34px;display:inline-grid;place-items:center;flex:0 0 34px;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
65
|
+
.pm-refresh-icon{border-color:var(--pm-line);background:color-mix(in srgb,var(--pm-panel) 92%,transparent)}
|
|
66
|
+
.pm-disclosure:hover,.pm-refresh-icon:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}
|
|
67
|
+
.pm-disclosure svg,.pm-refresh-icon svg{width:17px;height:17px}
|
|
68
|
+
|
|
69
|
+
.pm-form.pm-form-redesign{display:grid;grid-template-columns:minmax(190px,1.05fr) minmax(155px,.65fr) minmax(250px,1.15fr) auto;gap:12px 16px;align-items:end;padding:16px 20px 18px;border-top:1px solid var(--pm-line);border-bottom:0}
|
|
70
|
+
.pm-form-redesign .pm-field-url{grid-column:auto}
|
|
71
|
+
.pm-form-redesign .pm-field label{margin-bottom:6px;font-size:11px;color:var(--dsw-alias-label-secondary)}
|
|
72
|
+
.pm-required{color:var(--pm-danger);margin-left:3px}
|
|
73
|
+
.pm-form-redesign .pm-input,.pm-form-redesign .pm-select{height:38px;border-radius:8px;background:color-mix(in srgb,var(--pm-panel) 92%,#000 8%)}
|
|
74
|
+
.pm-input-wrap{position:relative}
|
|
75
|
+
.pm-input-wrap .pm-input{padding-left:34px}
|
|
76
|
+
.pm-input-leading{position:absolute;left:10px;top:50%;transform:translateY(-50%);display:grid;place-items:center;color:var(--dsw-alias-label-secondary);pointer-events:none}
|
|
77
|
+
.pm-input-leading svg{width:15px;height:15px}
|
|
78
|
+
.pm-form-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}
|
|
79
|
+
.pm-form-actions .pm-btn{height:38px;border-radius:8px}
|
|
80
|
+
.pm-form-error{grid-column:1 / span 2;justify-self:start;display:inline-flex;align-items:center;gap:8px;margin:0;padding:8px 11px;border:1px solid color-mix(in srgb,var(--pm-danger) 55%,transparent);border-radius:8px;background:color-mix(in srgb,var(--pm-danger) 9%,var(--pm-panel));color:var(--pm-danger);font-size:11px;font-weight:600;box-shadow:0 0 16px color-mix(in srgb,var(--pm-danger) 7%,transparent)}
|
|
81
|
+
.pm-form-error svg{width:14px;height:14px}
|
|
82
|
+
.pm-btn-secondary{background:transparent}
|
|
83
|
+
|
|
84
|
+
.pm-connected-head{border-bottom:1px solid var(--pm-line)}
|
|
85
|
+
.pm-source-list{grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;padding:14px 20px 18px}
|
|
86
|
+
.pm-source{gap:0;padding:0;overflow:hidden;border:1px solid color-mix(in srgb,var(--pm-line) 88%,#708bc2 12%);border-radius:12px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-2) 92%,#0b1627 8%)}
|
|
87
|
+
.pm-source-head{align-items:center;padding:14px 15px;gap:14px}
|
|
88
|
+
.pm-source-main{display:flex;align-items:center;gap:11px;min-width:0}
|
|
89
|
+
.pm-source-logo{width:42px;height:42px;flex:0 0 42px;display:grid;place-items:center;overflow:hidden;border-radius:9px;border:1px solid var(--pm-line);font-weight:800}
|
|
90
|
+
.pm-source-logo-npm{border-color:#d3414b;background:#cf3541;color:#fff;font:800 12px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
91
|
+
.pm-source-logo-github{background:#11151c;color:#fff}
|
|
92
|
+
.pm-source-logo-generic{background:color-mix(in srgb,var(--pm-blue) 9%,var(--pm-panel));color:#9fbcf4}
|
|
93
|
+
.pm-source-logo svg{width:24px;height:24px}
|
|
94
|
+
.pm-source-summary{min-width:0;flex:1}
|
|
95
|
+
.pm-source-line{gap:8px}
|
|
96
|
+
.pm-source-name{font-size:14px;font-weight:700}
|
|
97
|
+
.pm-type{height:21px;padding:0 8px;font-size:10px}
|
|
98
|
+
.pm-source-evidence{display:flex;align-items:center;gap:10px;margin-top:6px;color:var(--pm-muted);font-size:11px;white-space:nowrap}
|
|
99
|
+
.pm-source-evidence-item{display:inline-flex;align-items:center;gap:6px;min-width:0}
|
|
100
|
+
.pm-source-evidence svg{width:14px;height:14px}
|
|
101
|
+
.pm-evidence-separator{width:1px;height:16px;background:var(--pm-line)}
|
|
102
|
+
.pm-status{width:8px;height:8px;box-shadow:0 0 0 3px color-mix(in srgb,currentColor 10%,transparent)}
|
|
103
|
+
.pm-source-buttons{gap:9px;flex:0 0 auto}
|
|
104
|
+
.pm-source-switch{display:inline-flex;align-items:center;gap:8px;color:var(--dsw-alias-label-secondary);font-size:11px;cursor:pointer}
|
|
105
|
+
.pm-source-switch input{position:absolute;opacity:0;pointer-events:none}
|
|
106
|
+
.pm-switch-track{position:relative;width:38px;height:21px;border-radius:99px;background:#5d6675;transition:background .16s ease}
|
|
107
|
+
.pm-switch-track::after{content:"";position:absolute;top:3px;left:3px;width:15px;height:15px;border-radius:50%;background:#f8fbff;transition:transform .16s ease}
|
|
108
|
+
.pm-source-switch input:checked + .pm-switch-track{background:var(--pm-green)}
|
|
109
|
+
.pm-source-switch input:checked + .pm-switch-track::after{transform:translateX(17px)}
|
|
110
|
+
.pm-source-switch input:disabled + .pm-switch-track{opacity:.55}
|
|
111
|
+
.pm-source-disclosure{width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
112
|
+
.pm-source-disclosure:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--pm-panel) 80%,transparent)}
|
|
113
|
+
.pm-source-details{padding:0 15px 14px;border-top:1px solid var(--pm-line)}
|
|
114
|
+
.pm-source-url-label{margin:11px 0 6px;color:var(--dsw-alias-label-secondary);font-size:11px}
|
|
115
|
+
.pm-source-url-wrap{position:relative}
|
|
116
|
+
.pm-source-url{height:35px;width:100%;box-sizing:border-box;padding:0 37px 0 10px;border:1px solid var(--pm-line);border-radius:7px;outline:none;background:color-mix(in srgb,var(--pm-panel) 86%,#000 14%);color:var(--dsw-alias-label-secondary);font:11px ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
117
|
+
.pm-copy-url{position:absolute;right:4px;top:50%;transform:translateY(-50%);width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
118
|
+
.pm-copy-url:hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--pm-panel) 80%,transparent)}
|
|
119
|
+
.pm-source-actions{margin-top:10px;gap:8px}
|
|
120
|
+
.pm-source-action{width:34px;height:34px;display:grid;place-items:center;border:1px solid var(--pm-line);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
121
|
+
.pm-source-action:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l3)}
|
|
122
|
+
.pm-source-action-danger{color:var(--pm-danger);border-color:color-mix(in srgb,var(--pm-danger) 30%,var(--pm-line))}
|
|
123
|
+
.pm-source-action svg,.pm-copy-url svg{width:15px;height:15px}
|
|
124
|
+
.pm-source-error{margin-top:8px;color:var(--pm-danger);font-size:10px;white-space:normal}
|
|
125
|
+
.pm-connected-notice{margin:0 20px 12px;border-radius:8px;border:1px solid var(--pm-line)}
|
|
126
|
+
|
|
127
|
+
@media(max-width:900px){.pm-form.pm-form-redesign{grid-template-columns:1fr 1fr}.pm-form-redesign .pm-field-url{grid-column:1/-1}.pm-form-actions{grid-column:1/-1}.pm-form-error{grid-column:1/-1}.pm-source-list{grid-template-columns:1fr}}
|
|
128
|
+
@media(max-width:620px){.pm-shell{padding-top:10px}.pm-title{font-size:24px;line-height:30px}.pm-form.pm-form-redesign{grid-template-columns:1fr;padding:14px}.pm-form-redesign .pm-field-url,.pm-form-actions,.pm-form-error{grid-column:1}.pm-form-actions{justify-content:stretch}.pm-form-actions .pm-btn{flex:1}.pm-section-head{padding:15px}.pm-source-list{padding:12px}.pm-source-head{align-items:flex-start}.pm-source-buttons{flex-direction:column;align-items:flex-end}}
|
|
129
|
+
/* Reference UI alignment */
|
|
130
|
+
.pm-shell{max-width:1430px;padding:22px 0 34px}
|
|
131
|
+
.pm-title{font-size:31px;line-height:39px}
|
|
132
|
+
.pm-lead{max-width:1080px;font-size:14px;line-height:21px;margin-bottom:22px}
|
|
133
|
+
.pm-tabs{margin-bottom:18px;border-radius:12px}
|
|
134
|
+
.pm-tab{height:46px;min-width:168px;padding:0 22px;border-radius:9px;font-size:13px}
|
|
135
|
+
.pm-tab-content{gap:11px}
|
|
136
|
+
.pm-tab-content svg{width:19px;height:19px}
|
|
137
|
+
.pm-add-source-panel,.pm-connected-panel{border-radius:13px}
|
|
138
|
+
.pm-section-head{padding:22px 24px}
|
|
139
|
+
.pm-section-head-main{gap:16px}
|
|
140
|
+
.pm-section-icon{width:46px;height:46px;flex-basis:46px}
|
|
141
|
+
.pm-section-icon svg{width:24px;height:24px}
|
|
142
|
+
.pm-section-title{font-size:16px;line-height:22px}
|
|
143
|
+
.pm-section-lead{font-size:12px;line-height:18px}
|
|
144
|
+
.pm-disclosure{width:38px;height:38px}
|
|
145
|
+
.pm-refresh-icon{width:40px;height:40px;flex-basis:40px;border-radius:9px}
|
|
146
|
+
.pm-disclosure svg,.pm-refresh-icon svg{width:19px;height:19px}
|
|
147
|
+
.pm-form.pm-form-redesign{grid-template-columns:minmax(260px,1.25fr) minmax(190px,.75fr) minmax(330px,1.35fr);gap:12px 22px;align-items:end;padding:18px 24px 20px}
|
|
148
|
+
.pm-form-redesign .pm-field-url{grid-column:3}
|
|
149
|
+
.pm-form-redesign .pm-input,.pm-form-redesign .pm-select{height:42px;border-radius:9px}
|
|
150
|
+
.pm-form-redesign .pm-field label{margin-bottom:7px;font-size:11px}
|
|
151
|
+
.pm-form-actions{grid-column:3;grid-row:2;justify-self:end;gap:12px}
|
|
152
|
+
.pm-form-actions .pm-btn{height:40px;border-radius:9px;padding:0 18px;font-size:12px}
|
|
153
|
+
.pm-form-actions .pm-btn-primary{padding-left:20px;padding-right:20px}
|
|
154
|
+
.pm-form-error{grid-column:1;grid-row:2;padding:9px 12px;border-radius:9px}
|
|
155
|
+
.pm-source-list{gap:18px;padding:16px 22px 20px}
|
|
156
|
+
.pm-source{border-radius:12px}
|
|
157
|
+
.pm-source-head{padding:16px 18px}
|
|
158
|
+
.pm-source-main{gap:13px}
|
|
159
|
+
.pm-source-logo{width:46px;height:46px;flex-basis:46px;border-radius:10px}
|
|
160
|
+
.pm-source-logo svg{width:27px;height:27px}
|
|
161
|
+
.pm-source-name{font-size:15px}
|
|
162
|
+
.pm-source-evidence{margin-top:7px;font-size:11px}
|
|
163
|
+
.pm-source-buttons{gap:11px}
|
|
164
|
+
.pm-source-disclosure{width:34px;height:34px}
|
|
165
|
+
.pm-source-disclosure svg{width:17px;height:17px}
|
|
166
|
+
.pm-source-details{padding:0 18px 16px}
|
|
167
|
+
.pm-source-url-label{margin:13px 0 7px}
|
|
168
|
+
.pm-source-url{height:39px;border-radius:8px;padding-right:43px}
|
|
169
|
+
.pm-copy-url{width:32px;height:32px;right:4px}
|
|
170
|
+
.pm-source-actions{margin-top:13px;gap:12px}
|
|
171
|
+
.pm-source-action{width:46px;height:40px;border-radius:9px}
|
|
172
|
+
.pm-source-action svg,.pm-copy-url svg{width:18px;height:18px}
|
|
173
|
+
.pm-source-logo-npm{background:#d83243;border-color:#df4453;color:#fff}
|
|
174
|
+
.pm-source-logo-github{background:#121821;border-color:#384355}
|
|
175
|
+
.pm-source-logo-generic{background:color-mix(in srgb,var(--pm-blue) 13%,var(--pm-panel));border-color:color-mix(in srgb,var(--pm-blue) 38%,var(--pm-line))}
|
|
176
|
+
@media(max-width:1100px){.pm-form.pm-form-redesign{grid-template-columns:1fr 220px}.pm-form-redesign .pm-field-url{grid-column:1/-1}.pm-form-actions{grid-column:1/-1;grid-row:auto}.pm-form-error{grid-column:1/-1;grid-row:auto}}
|
|
177
|
+
@media(max-width:720px){.pm-shell{padding:12px 0 28px}.pm-tab{min-width:138px}.pm-form.pm-form-redesign{grid-template-columns:1fr;padding:14px}.pm-form-redesign .pm-field-url,.pm-form-actions,.pm-form-error{grid-column:1}.pm-form-actions{justify-self:stretch}.pm-form-actions .pm-btn{flex:1}}
|
|
178
|
+
|
|
179
|
+
/* Native DSH 0.1.7 geometry */
|
|
180
|
+
.pm-shell{max-width:960px;padding:12px 0 28px}
|
|
181
|
+
.pm-root{font-size:13px;background:var(--dsw-specific-page,transparent)}
|
|
182
|
+
.pm-title{font-size:20px;line-height:28px;font-weight:500;letter-spacing:0}
|
|
183
|
+
.pm-lead{max-width:760px;margin:4px 0 20px;font-size:13px;line-height:20px}
|
|
184
|
+
.pm-kicker{font-size:10px;line-height:14px;margin-bottom:4px}
|
|
185
|
+
|
|
186
|
+
.pm-tabs{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));width:300px;padding:4px;gap:0;border:0;border-radius:12px;background:var(--dsw-alias-bg-module-platform);margin-bottom:24px}
|
|
187
|
+
.pm-tab{height:34px;min-width:0;padding:0 12px;border:0;border-radius:8px;font-size:14px;line-height:20px;font-weight:400;color:var(--dsw-alias-label-secondary)}
|
|
188
|
+
.pm-tab[data-active=true]{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:600;box-shadow:none}
|
|
189
|
+
.pm-tab-content{gap:6px}
|
|
190
|
+
.pm-tab-content svg{width:16px;height:16px}
|
|
191
|
+
|
|
192
|
+
.pm-sources-view{gap:20px}
|
|
193
|
+
.pm-add-source-panel,.pm-connected-panel{border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;background:var(--dsw-alias-bg-layer-1)}
|
|
194
|
+
.pm-section-head{padding:12px 14px;gap:12px}
|
|
195
|
+
.pm-section-head-main{gap:12px}
|
|
196
|
+
.pm-section-icon{width:40px;height:40px;flex:0 0 40px;border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;background:transparent;color:var(--dsw-alias-label-secondary)}
|
|
197
|
+
.pm-section-icon svg{width:18px;height:18px}
|
|
198
|
+
.pm-section-title{font-size:14px;line-height:20px;font-weight:500}
|
|
199
|
+
.pm-section-lead{margin-top:2px;font-size:13px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
200
|
+
.pm-disclosure,.pm-refresh-icon,.pm-source-disclosure,.pm-source-action,.pm-copy-url{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border:0;border-radius:28px;background:transparent;color:var(--dsw-alias-label-caption);cursor:pointer}
|
|
201
|
+
.pm-disclosure:hover:not(:disabled),.pm-refresh-icon:hover:not(:disabled),.pm-source-disclosure:hover:not(:disabled),.pm-source-action:hover:not(:disabled),.pm-copy-url:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
|
|
202
|
+
.pm-refresh-icon{flex:0 0 28px}
|
|
203
|
+
.pm-disclosure svg,.pm-refresh-icon svg,.pm-source-disclosure svg,.pm-source-action svg,.pm-copy-url svg{width:16px;height:16px;display:block}
|
|
204
|
+
.pm-disclosure[aria-expanded=true] .pm-chevron,.pm-source-disclosure[aria-expanded=true] .pm-chevron{transform:rotate(180deg)}
|
|
205
|
+
.pm-chevron{transition:transform 160ms ease}
|
|
206
|
+
|
|
207
|
+
.pm-form.pm-form-redesign{grid-template-columns:minmax(0,1fr) 190px minmax(0,1.15fr);gap:12px 16px;padding:14px;border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
208
|
+
.pm-form-redesign .pm-field-url{grid-column:3}
|
|
209
|
+
.pm-form-redesign .pm-field label{margin-bottom:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
210
|
+
.pm-form-redesign .pm-input,.pm-form-redesign .pm-select{height:32px;padding:0 8px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:14px;line-height:22px}
|
|
211
|
+
.pm-form-redesign .pm-input:focus,.pm-form-redesign .pm-select:focus{border-color:var(--dsw-alias-brand-primary);box-shadow:none}
|
|
212
|
+
.pm-input-wrap .pm-input{padding-left:30px}
|
|
213
|
+
.pm-input-leading{left:8px;color:var(--dsw-alias-label-tertiary)}
|
|
214
|
+
.pm-input-leading svg{width:16px;height:16px}
|
|
215
|
+
.pm-form-actions{grid-column:3;grid-row:2;justify-self:end;gap:8px}
|
|
216
|
+
.pm-form-error{grid-column:1 / span 2;grid-row:2;margin:0;padding:6px 10px;border:0;border-radius:10px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);font-size:12px;line-height:18px;font-weight:400;color:var(--dsw-alias-state-error-primary);box-shadow:none}
|
|
217
|
+
|
|
218
|
+
.pm-connected-head{border-bottom:.5px solid var(--dsw-alias-border-l2)}
|
|
219
|
+
.pm-source-list{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 14px 14px}
|
|
220
|
+
.pm-source{border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;background:transparent}
|
|
221
|
+
.pm-source-head{padding:8px;gap:14px}
|
|
222
|
+
.pm-source-main{gap:14px}
|
|
223
|
+
.pm-source-logo{width:48px;height:48px;flex:0 0 48px;border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;background:transparent}
|
|
224
|
+
.pm-source-logo-npm{background:transparent;border-color:var(--dsw-alias-border-l3);color:#cb3837}
|
|
225
|
+
.pm-source-logo-npm svg{width:30px;height:30px}
|
|
226
|
+
.pm-source-logo-github{background:transparent;color:var(--dsw-alias-label-primary)}
|
|
227
|
+
.pm-source-logo-github svg{width:28px;height:28px}
|
|
228
|
+
.pm-source-logo-generic{background:transparent;color:var(--dsw-alias-label-secondary)}
|
|
229
|
+
.pm-source-name{font-size:14px;line-height:20px;font-weight:500}
|
|
230
|
+
.pm-type{height:auto;padding:1px 8px;border:.5px solid var(--dsw-alias-border-l4);font-size:11px;line-height:17px;font-weight:500;color:var(--dsw-alias-label-tertiary)}
|
|
231
|
+
.pm-source-evidence{gap:8px;margin-top:4px;font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
232
|
+
.pm-source-evidence svg{width:14px;height:14px}
|
|
233
|
+
.pm-evidence-separator{height:16px;background:var(--dsw-alias-border-l2)}
|
|
234
|
+
.pm-source-buttons{gap:8px}
|
|
235
|
+
.pm-source-switch{display:inline-flex;align-items:center;gap:8px;font-size:12.5px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
236
|
+
.pm-source-details{padding:10px 8px 12px;border-top:.5px solid var(--dsw-alias-border-l2)}
|
|
237
|
+
.pm-source-url-label{margin:0 0 6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
238
|
+
.pm-source-url{height:32px;padding:0 34px 0 8px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:12px;line-height:18px}
|
|
239
|
+
.pm-copy-url{right:2px}
|
|
240
|
+
.pm-source-actions{margin-top:8px;gap:4px}
|
|
241
|
+
.pm-source-action-danger{color:var(--dsw-alias-state-error-primary)}
|
|
242
|
+
.pm-source-error{margin-top:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}
|
|
243
|
+
|
|
244
|
+
@media(max-width:900px){.pm-form.pm-form-redesign{grid-template-columns:1fr 180px}.pm-form-redesign .pm-field-url{grid-column:1/-1}.pm-form-actions{grid-column:1/-1;grid-row:auto}.pm-form-error{grid-column:1/-1;grid-row:auto}.pm-source-list{grid-template-columns:1fr}}
|
|
245
|
+
@media(max-width:620px){.pm-shell{padding-top:8px}.pm-tabs{width:100%}.pm-form.pm-form-redesign{grid-template-columns:1fr}.pm-form-redesign .pm-field-url,.pm-form-actions,.pm-form-error{grid-column:1}.pm-form-actions{justify-self:stretch}.pm-section-head{padding:10px 8px}.pm-source-list{padding:8px}}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
.pm-section-head-main{gap:0}
|
|
249
|
+
.pm-section-icon{display:none}
|
|
250
|
+
.pm-source-line{gap:0}
|
|
251
|
+
.pm-source-evidence-item>svg{flex:0 0 14px;width:14px;height:14px}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
/* Native Browse alignment */
|
|
255
|
+
.pm-browse-view{display:grid;gap:12px}
|
|
256
|
+
.pm-browse-toolbar{display:flex;align-items:center;gap:8px;margin:0}
|
|
257
|
+
.pm-browse-toolbar .pm-search{flex:1}
|
|
258
|
+
.pm-browse-toolbar .pm-search input{height:32px;padding:0 10px 0 32px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:14px;line-height:22px}
|
|
259
|
+
.pm-browse-toolbar .pm-search svg{left:10px;top:8px;width:16px;height:16px}
|
|
260
|
+
.pm-browse-refresh{flex:0 0 28px}
|
|
261
|
+
.pm-browse-state{border:.5px solid var(--dsw-alias-border-l3);border-radius:10px;padding:8px 10px;background:transparent;font-size:12px;line-height:18px}
|
|
262
|
+
.pm-browse-content{display:grid;gap:10px}
|
|
263
|
+
.pm-browse-options{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0;flex-wrap:wrap}
|
|
264
|
+
.pm-count{margin:0;color:var(--dsw-alias-label-tertiary);font:500 11px/18px ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
265
|
+
.pm-filter-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
266
|
+
.pm-stable-filter{display:inline-flex;align-items:center;gap:7px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
267
|
+
.pm-filter-select,.pm-sort,.pm-page-size{height:32px;border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;background:var(--dsw-alias-bg-layer-1);font-size:13px;line-height:20px;padding:0 28px 0 8px}
|
|
268
|
+
.pm-filter-select{min-width:120px}
|
|
269
|
+
.pm-sort{min-width:155px}
|
|
270
|
+
.pm-page-size{min-width:72px}
|
|
271
|
+
.pm-results{display:grid;gap:8px}
|
|
272
|
+
.pm-card{position:relative;padding:12px 14px;border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;background:transparent}
|
|
273
|
+
.pm-card:hover{border-color:var(--dsw-alias-border-l4)}
|
|
274
|
+
.pm-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
|
|
275
|
+
.pm-card-main{min-width:0}
|
|
276
|
+
.pm-card-titleline{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
|
|
277
|
+
.pm-plugin-name{font-size:14px;line-height:20px;font-weight:500}
|
|
278
|
+
.pm-version{margin:0;color:var(--dsw-alias-label-tertiary);font:500 10px/18px ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
279
|
+
.pm-channel{height:18px;margin:0;padding:0 7px;border:.5px solid var(--dsw-alias-border-l4);border-radius:99px;font:600 9px/18px ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
280
|
+
.pm-card-stats{display:flex;align-items:center;gap:10px;margin-top:3px;flex-wrap:wrap}
|
|
281
|
+
.pm-signal{display:inline-flex;align-items:center;gap:4px;margin:0;color:var(--dsw-alias-label-tertiary);font:500 10px/16px ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
282
|
+
.pm-signal svg{width:13px;height:13px}
|
|
283
|
+
.pm-card-description{margin-top:7px}
|
|
284
|
+
.pm-desc{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:18px}
|
|
285
|
+
.pm-more{margin-top:1px;font:500 11px/16px inherit;color:var(--dsw-alias-brand-primary)}
|
|
286
|
+
.pm-card-meta{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-top:7px}
|
|
287
|
+
.pm-badge,.pm-tag,.pm-compat{height:18px;padding:0 6px;border:.5px solid var(--dsw-alias-border-l4);border-radius:99px;font:500 10px/17px ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
288
|
+
.pm-repo-link{display:block;max-width:100%;margin-top:6px;color:var(--dsw-alias-label-tertiary);font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
289
|
+
.pm-install-row{display:grid;grid-template-columns:minmax(0,1fr) 28px auto auto;gap:8px;align-items:center;margin-top:9px}
|
|
290
|
+
.pm-command{height:32px;box-sizing:border-box;display:flex;align-items:center;padding:0 9px;border-radius:8px;background:var(--dsw-alias-bg-layer-2);font:11px/18px ui-monospace,SFMono-Regular,Consolas,monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
291
|
+
.pm-install-row .pm-icon-btn{width:28px;min-width:28px;height:28px;border:0;border-radius:28px;background:transparent}
|
|
292
|
+
.pm-install-row .pm-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover);border:0}
|
|
293
|
+
.pm-install-status{margin-top:6px;font-size:11px;line-height:17px;color:var(--dsw-alias-state-error-primary)}
|
|
294
|
+
.pm-pagination{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:2px}
|
|
295
|
+
.pm-page-label{font-size:11px;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
296
|
+
.pm-page-controls{display:flex;align-items:center;gap:8px}
|
|
297
|
+
|
|
298
|
+
@media(max-width:820px){
|
|
299
|
+
.pm-browse-options{align-items:flex-start}
|
|
300
|
+
.pm-filter-row{width:100%}
|
|
301
|
+
.pm-filter-select,.pm-sort{flex:1 1 150px}
|
|
302
|
+
.pm-page-size{flex:0 0 72px}
|
|
303
|
+
.pm-install-row{grid-template-columns:minmax(0,1fr) 28px}
|
|
304
|
+
.pm-install-row>button:not(.pm-icon-btn){grid-column:auto}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
/* Registry Aggregator Browse v2 */
|
|
309
|
+
.pm-browse-topline,.pm-browse-bottomline{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
310
|
+
.pm-browse-bottomline{justify-content:flex-end;margin-top:2px}
|
|
311
|
+
.pm-browse-options{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
|
|
312
|
+
.pm-filter-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
313
|
+
.pm-sort-row{display:inline-flex;align-items:center;gap:4px;padding:3px;border:.5px solid var(--dsw-alias-border-l3);border-radius:9px;background:var(--dsw-alias-bg-module-platform)}
|
|
314
|
+
.pm-sort-criterion{position:relative;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:42px;height:28px;padding:0 8px;border:.5px solid transparent;border-radius:7px;background:transparent;color:var(--dsw-alias-label-tertiary);font:600 12px/1 inherit;cursor:pointer}
|
|
315
|
+
.pm-sort-criterion:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
|
|
316
|
+
.pm-sort-criterion[data-active=true]{border-color:var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary)}
|
|
317
|
+
.pm-sort-symbol{display:inline-flex;align-items:center;justify-content:center;min-width:14px;height:14px}
|
|
318
|
+
.pm-sort-symbol svg,.pm-sort-direction svg{display:block;width:13px;height:13px}
|
|
319
|
+
.pm-sort-direction{display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-label-caption)}
|
|
320
|
+
.pm-sort-criterion[data-active=true] .pm-sort-direction{color:var(--dsw-alias-brand-primary)}
|
|
321
|
+
.pm-sort-priority{position:absolute;top:-5px;right:-4px;display:flex;align-items:center;justify-content:center;width:13px;height:13px;border-radius:99px;background:var(--dsw-alias-brand-primary);color:#fff;font:700 8px/13px ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
322
|
+
|
|
323
|
+
.pm-pagination{display:flex;align-items:center;gap:8px;margin:0}
|
|
324
|
+
.pm-page-controls{display:flex;align-items:center;gap:2px}
|
|
325
|
+
.pm-page-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-secondary);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace;cursor:pointer}
|
|
326
|
+
.pm-page-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
327
|
+
.pm-page-button[data-current=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);font-weight:700}
|
|
328
|
+
.pm-page-button:disabled{opacity:.35;cursor:default}
|
|
329
|
+
.pm-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:20px;color:var(--dsw-alias-label-caption);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
330
|
+
.pm-page-size-control{display:inline-flex;align-items:center;gap:3px;height:28px;padding:0 5px 0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}
|
|
331
|
+
.pm-page-size-control select{height:24px;min-width:42px;padding:0 16px 0 2px;border:0;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
332
|
+
|
|
333
|
+
.pm-card{padding:11px 12px}
|
|
334
|
+
.pm-card-top{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
|
|
335
|
+
.pm-card-install{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:0 0 30px;width:30px;height:30px;padding:0;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
336
|
+
.pm-card-install:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-brand-primary)}
|
|
337
|
+
.pm-card-install[data-state=installed]{color:var(--dsw-alias-state-success-primary)}
|
|
338
|
+
.pm-card-install[data-state=failed]{color:var(--dsw-alias-state-error-primary)}
|
|
339
|
+
.pm-card-install:disabled{opacity:.55;cursor:default}
|
|
340
|
+
.pm-card-install svg{display:block;width:16px;height:16px}
|
|
341
|
+
|
|
342
|
+
.pm-card-description{position:relative;margin-top:7px;min-width:0}
|
|
343
|
+
.pm-desc{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:18px}
|
|
344
|
+
.pm-desc[data-clamped=true]{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;padding-right:34px}
|
|
345
|
+
.pm-desc-more{position:absolute;right:0;bottom:0;height:18px;padding:0 1px;border:0;border-radius:0;background:transparent;color:var(--dsw-alias-brand-primary);font:900 15px/18px ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;cursor:pointer;box-shadow:none;appearance:none}
|
|
346
|
+
.pm-desc-more:hover,.pm-desc-more:focus-visible{background:transparent;color:var(--dsw-alias-brand-primary);filter:brightness(1.12);outline:none;text-decoration:none}
|
|
347
|
+
.pm-desc-less{display:inline-flex;margin-top:2px;padding:0;border:0;background:transparent;color:var(--dsw-alias-brand-primary);font:600 11px/16px inherit;cursor:pointer}
|
|
348
|
+
.pm-card-description[data-expanded=true] .pm-desc{padding-right:0}
|
|
349
|
+
|
|
350
|
+
.pm-command-row{display:grid;grid-template-columns:minmax(0,1fr) 28px;gap:6px;align-items:center;margin-top:8px}
|
|
351
|
+
.pm-command-row .pm-icon-btn{width:28px;min-width:28px;height:28px;border:0;border-radius:28px;background:transparent}
|
|
352
|
+
.pm-command-row .pm-icon-btn:hover{background:var(--dsw-alias-interactive-bg-hover);border:0}
|
|
353
|
+
.pm-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
|
|
354
|
+
|
|
355
|
+
@media(max-width:960px){
|
|
356
|
+
.pm-browse-options{align-items:flex-start}
|
|
357
|
+
.pm-sort-row{order:2}
|
|
358
|
+
.pm-pagination{max-width:100%;overflow-x:auto}
|
|
359
|
+
}
|
|
360
|
+
@media(max-width:640px){
|
|
361
|
+
.pm-browse-topline{align-items:flex-start;flex-direction:column}
|
|
362
|
+
.pm-browse-topline .pm-pagination{align-self:flex-end}
|
|
363
|
+
.pm-sort-row{width:100%;justify-content:flex-end}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
/* Compact one-line Browse controls */
|
|
368
|
+
.pm-controls-row{display:flex;align-items:center;gap:6px;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;padding:1px 0 3px;scrollbar-width:none}
|
|
369
|
+
.pm-controls-row::-webkit-scrollbar{display:none}
|
|
370
|
+
.pm-filter-toggle,.pm-compact-filter,.pm-sort-row{flex:0 0 auto}
|
|
371
|
+
.pm-filter-toggle{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;padding:0;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
|
372
|
+
.pm-filter-toggle:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}
|
|
373
|
+
.pm-filter-toggle[data-active=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-border-l4)}
|
|
374
|
+
.pm-compact-filter{box-sizing:border-box;display:inline-flex;align-items:center;gap:4px;height:30px;padding:0 4px 0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}
|
|
375
|
+
.pm-compact-filter[data-active=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-brand-primary)}
|
|
376
|
+
.pm-compact-filter-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 14px}
|
|
377
|
+
.pm-compact-filter-icon svg{display:block;width:14px;height:14px}
|
|
378
|
+
.pm-compact-filter select{height:27px;max-width:128px;padding:0 18px 0 2px;border:0;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font:500 11px/1 inherit}
|
|
379
|
+
.pm-sort-row{gap:3px;padding:2px;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-module-platform)}
|
|
380
|
+
.pm-sort-criterion{min-width:36px;height:26px;padding:0 6px;border:.5px solid transparent;border-radius:6px}
|
|
381
|
+
.pm-sort-criterion[data-active=true]{border-color:var(--dsw-alias-border-l4);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,var(--dsw-alias-bg-layer-3));color:var(--dsw-alias-label-primary)}
|
|
382
|
+
.pm-sort-criterion[data-active=true] .pm-sort-direction{color:var(--dsw-alias-brand-primary)}
|
|
383
|
+
.pm-sort-priority{display:none!important}
|
|
384
|
+
|
|
385
|
+
.pm-page-size-control{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;min-width:48px;height:28px;padding:0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:7px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:500 11px/1 ui-monospace,SFMono-Regular,Consolas,monospace;cursor:pointer}
|
|
386
|
+
.pm-page-size-control:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
387
|
+
.pm-page-size-control:disabled{opacity:.5;cursor:default}
|
|
388
|
+
.pm-page-button[data-current=true]{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
389
|
+
|
|
390
|
+
@media(max-width:960px){
|
|
391
|
+
.pm-controls-row{width:100%}
|
|
392
|
+
.pm-sort-row{order:initial}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
/* Responsive request feedback, native page-size menu trigger, and source actions */
|
|
397
|
+
.pm-loading-strip{position:relative;display:grid;grid-template-columns:16px auto 1fr;align-items:center;gap:6px;min-height:20px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
398
|
+
.pm-loading-spinner{display:inline-flex;align-items:center;justify-content:center}
|
|
399
|
+
.pm-loading-track{position:relative;height:2px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2)}
|
|
400
|
+
.pm-loading-bar{position:absolute;top:0;bottom:0;width:28%;border-radius:99px;background:var(--dsw-alias-brand-primary);animation:pm-loading-slide 1s ease-in-out infinite}
|
|
401
|
+
@keyframes pm-loading-slide{0%{left:-28%}50%{left:52%}100%{left:100%}}
|
|
402
|
+
.pm-results[data-updating=true]{opacity:.68;transition:opacity .12s ease}
|
|
403
|
+
.pm-page-size-control svg:last-child{width:11px;height:11px;color:var(--dsw-alias-label-caption)}
|
|
404
|
+
.pm-source-delete{width:28px;height:28px;display:inline-grid;place-items:center;padding:0;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
|
405
|
+
.pm-source-delete:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent);color:var(--dsw-alias-state-error-primary)}
|
|
406
|
+
.pm-source-delete:disabled{opacity:.4;cursor:default}
|
|
407
|
+
.pm-source-delete svg{width:15px;height:15px}
|
|
408
|
+
.pm-source-actions{display:none!important}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
/* Native themed filter menus and install progress */
|
|
412
|
+
.pm-compact-filter{box-sizing:border-box;display:inline-flex;align-items:center;gap:5px;height:30px;max-width:180px;padding:0 7px;border:.5px solid var(--dsw-alias-border-l3);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:500 11px/1 inherit;cursor:pointer}
|
|
413
|
+
.pm-compact-filter:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
414
|
+
.pm-compact-filter[data-active=true]{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-brand-primary)}
|
|
415
|
+
.pm-compact-filter-icon{display:inline-flex;align-items:center;justify-content:center;flex:0 0 14px}
|
|
416
|
+
.pm-compact-filter-icon svg{display:block;width:14px;height:14px}
|
|
417
|
+
.pm-compact-filter-label{min-width:0;max-width:132px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
418
|
+
.pm-compact-filter>svg:last-child{flex:0 0 12px;width:12px;height:12px;color:var(--dsw-alias-label-caption)}
|
|
419
|
+
.pm-compact-filter select{display:none!important}
|
|
420
|
+
.pm-star-icon{color:var(--dsw-alias-state-warning-primary,#e5a000);font-weight:800}
|
|
421
|
+
.pm-card-install[data-state=installing]{color:var(--dsw-alias-label-secondary);cursor:progress;opacity:1}
|
|
422
|
+
.pm-card-install[data-state=installing] svg[data-state=ongoing]{width:16px;height:16px}
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
/* Verified bundle discovery and compact source state */
|
|
426
|
+
.pm-status{flex:0 0 8px;min-width:8px;max-width:8px}
|
|
427
|
+
.pm-source-evidence-item{flex:0 0 auto}
|
|
428
|
+
.pm-source-summary{overflow:hidden}
|
|
429
|
+
.pm-source-switch{gap:0}
|
|
430
|
+
.pm-bundle-state{display:inline-flex;align-items:center;height:18px;padding:0 6px;border:1px solid var(--pm-line);border-radius:99px;color:var(--pm-muted);font:600 10px/1 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
431
|
+
.pm-bundle-state-bundle{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary,#30a46c) 45%,var(--pm-line));color:var(--dsw-alias-state-success-primary,#30a46c)}
|
|
432
|
+
.pm-bundle-state-not-bundle{color:var(--dsw-alias-label-tertiary)}
|
|
433
|
+
.pm-bundle-state-unknown{color:var(--dsw-alias-state-warning-primary,#e5a000)}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
/* Source-card disclosure geometry and status glow */
|
|
437
|
+
.pm-source-list{align-items:start}
|
|
438
|
+
.pm-source{align-self:start}
|
|
439
|
+
.pm-status{margin-left:2px;box-shadow:none!important;overflow:visible}
|
|
440
|
+
.pm-status-ok{color:#30a46c;background:#30a46c;box-shadow:0 0 7px 1px color-mix(in srgb,#30a46c 58%,transparent)!important}
|
|
441
|
+
.pm-status-bad,.pm-status-wait,.pm-status-off{box-shadow:none!important}
|
|
442
|
+
.pm-source[data-open=false] .pm-source-head{border-bottom:0}
|
|
443
|
+
.pm-chevron[data-open=true]{transform:rotate(180deg)}
|
|
444
|
+
|
|
445
|
+
`;
|
|
446
|
+
|
|
447
|
+
function injectStyle() {
|
|
448
|
+
const key = "plugin-sources";
|
|
449
|
+
const document = window.document;
|
|
450
|
+
if (document.querySelector(`style[data-plugin-css="${key}"]`)) return () => {};
|
|
451
|
+
const style = document.createElement("style");
|
|
452
|
+
style.dataset.pluginCss = key;
|
|
453
|
+
style.textContent = css;
|
|
454
|
+
document.head.appendChild(style);
|
|
455
|
+
return () => style.remove?.();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const en = {
|
|
459
|
+
nav: "Registry Aggregator", kicker: "Federated plugin registry", title: "Registry Aggregator", lead: "Aggregate connected plugin registries and catalogs, search them as one index, and install through the native DSH Plugin Manager.",
|
|
460
|
+
"tab.sources": "Sources", "tab.browse": "Browse", "sources.title": "Connected sources", "sources.add": "Add source", "sources.addTitle": "Add a new source", "sources.addLead": "Connect a package registry to discover and install plugins.", "sources.manage": "Manage your connected registries and discover available plugins.", "sources.empty": "No plugin sources connected", "sources.emptyHint": "Add a catalog, npm registry, or GitHub source to begin.",
|
|
461
|
+
"field.name": "Name", "field.type": "Type", "field.url": "Catalog / API URL", "field.urlHint": "Optional for npm and GitHub", "source.remove": "Remove", "source.share": "Share", "source.shared": "Copied", "source.enabled": "Enabled", "source.disabled": "Disabled", "source.checking": "Checking", "source.online": "Status: online", "source.packages": "{count} packages", "source.repositories": "{count} repositories", "source.ok": "Healthy", "source.error": "Unavailable", "source.duplicate": "A source with this id already exists.", "source.required": "Name is required.", "source.urlRequired": "This source type requires a catalog URL.",
|
|
462
|
+
"activation.title": "Registry Aggregator ready", "activation.body": "npm and GitHub sources are connected by default. Open Registry Aggregator to search them together or add more sources.", "activation.open": "Open Registry Aggregator", "activation.later": "Later",
|
|
463
|
+
"browse.search": "Search plugins, packages, repositories…", "browse.refresh": "Refresh", "browse.pageSize": "Per page", "browse.page": "Page {page} of {pages}", "browse.prev": "Previous", "browse.next": "Next", "browse.freshness": "Freshness", "browse.freshness.any": "Any age", "browse.freshness.30": "≤ 30 days", "browse.freshness.90": "≤ 90 days", "browse.freshness.365": "≤ 1 year", "browse.category": "Category", "browse.category.any": "All categories", "browse.dshMetadata": "DSH metadata", "browse.dshMetadata.any": "Any DSH metadata", "browse.dshMetadata.declared": "DSH declared", "browse.dshMetadata.unknown": "DSH unknown", "install": "Install", "install.confirm": "Install?", "installing": "Installing…", "install.checking": "Checking package…", "install.starting": "Starting install…", "install.running": "Installing package…", "install.runningAttempt": "Installing {index}/{total}…", "install.applying": "Applying plugin…", "installed": "Installed", "install.failed": "Install failed", "install.retry": "Retry install", "install.notBundle": "Not a DSH bundle", "install.approvalRequired": "Build-script approval required in the native Plugin Manager", "install.cancel": "Cancel", "description.more": "…", "description.expand": "Expand description", "description.less": "Less", "browse.empty": "No plugins found", "browse.sort": "Sort", "browse.sort.relevance": "Relevance", "browse.sort.stars": "Stars", "browse.sort.downloads": "Downloads", "browse.sort.freshness": "Freshest release", "browse.sort.starsDownloads": "Stars + downloads", "browse.sort.downloadsFreshness": "Downloads + freshness", "browse.sort.name": "Name", "browse.sort.priority": "Sort priority {priority}", "browse.sort.off": "Not active", "browse.sort.asc": "Ascending", "browse.sort.desc": "Descending", "browse.stableOnly": "Stable only", "browse.emptyHint": "No plugins match the current search or filters.", "browse.noSources": "No enabled registry sources. Open Sources and enable or add one.", "browse.sourcesUnavailable": "All enabled registry sources are unavailable.", "browse.loading": "Loading catalogs…", "browse.updating": "Updating results…", "browse.error": "Catalog request failed", "browse.results": "{count} results", "compat.check": "Check DSH compatibility", "compat.none": "DSH not declared", "compat.loading": "DSH …", "compat.apiPeers": "DSH API peers", "released": "released {age}", "repoUpdated": "repo {age}", "deprecated": "deprecated", "copy": "Copy command", "copied": "Copied", "copy.failed": "Could not copy", "install.warning": "Review the package and source before running this third-party install command.", "readonly": "Settings are read-only.", "unavailable": "Plugin source settings are unavailable.",
|
|
464
|
+
};
|
|
465
|
+
const zh = {
|
|
466
|
+
nav: "注册源聚合器", kicker: "联合插件注册表", title: "注册源聚合器", lead: "聚合已连接的插件注册表和目录,统一搜索,并通过原生 DSH Plugin Manager 安装。",
|
|
467
|
+
"tab.sources": "来源", "tab.browse": "浏览", "sources.title": "已连接来源", "sources.add": "添加来源", "sources.addTitle": "添加新来源", "sources.addLead": "连接软件包注册表以发现和安装插件。", "sources.manage": "管理已连接的注册表并发现可用插件。", "sources.empty": "尚未连接插件源", "sources.emptyHint": "添加目录、npm 注册表或 GitHub 来源以开始。",
|
|
468
|
+
"field.name": "名称", "field.type": "类型", "field.url": "目录 / API URL", "field.urlHint": "npm 和 GitHub 可选", "source.remove": "移除", "source.share": "分享", "source.shared": "已复制", "source.enabled": "已启用", "source.disabled": "已禁用", "source.checking": "检查中", "source.online": "状态:在线", "source.packages": "{count} 个软件包", "source.repositories": "{count} 个仓库", "source.ok": "正常", "source.error": "不可用", "source.duplicate": "具有此 ID 的来源已存在。", "source.required": "名称为必填项。", "source.urlRequired": "此来源类型需要目录 URL。",
|
|
469
|
+
"activation.title": "注册源聚合器已就绪", "activation.body": "npm 和 GitHub 来源默认已连接。打开注册源聚合器即可统一搜索或添加更多来源。", "activation.open": "打开注册源聚合器", "activation.later": "稍后",
|
|
470
|
+
"browse.search": "搜索插件、包、仓库…", "browse.refresh": "刷新", "browse.pageSize": "每页", "browse.page": "第 {page} / {pages} 页", "browse.prev": "上一页", "browse.next": "下一页", "browse.freshness": "新鲜度", "browse.freshness.any": "不限时间", "browse.freshness.30": "≤ 30 天", "browse.freshness.90": "≤ 90 天", "browse.freshness.365": "≤ 1 年", "browse.category": "类别", "browse.category.any": "全部类别", "browse.dshMetadata": "DSH 元数据", "browse.dshMetadata.any": "任意 DSH 元数据", "browse.dshMetadata.declared": "已声明 DSH", "browse.dshMetadata.unknown": "DSH 未知", "install": "安装", "install.confirm": "确认安装?", "installing": "安装中…", "installed": "已安装", "install.failed": "安装失败", "install.retry": "重试安装", "install.cancel": "取消", "description.more": "…", "description.expand": "展开描述", "description.less": "收起", "browse.empty": "未找到插件", "browse.sort": "排序", "browse.sort.relevance": "相关性", "browse.sort.stars": "星标", "browse.sort.downloads": "下载量", "browse.sort.freshness": "最新发布", "browse.sort.starsDownloads": "星标 + 下载量", "browse.sort.downloadsFreshness": "下载量 + 新鲜度", "browse.sort.name": "名称", "browse.sort.priority": "排序优先级 {priority}", "browse.sort.off": "未启用", "browse.sort.asc": "升序", "browse.sort.desc": "降序", "browse.stableOnly": "仅稳定版", "browse.emptyHint": "当前搜索或筛选条件没有匹配的插件。", "browse.noSources": "没有启用的注册源。请打开“来源”并启用或添加一个来源。", "browse.sourcesUnavailable": "所有已启用的注册源当前都不可用。", "browse.loading": "正在加载目录…", "browse.updating": "正在更新结果…", "browse.error": "目录请求失败", "browse.results": "{count} 个结果", "compat.check": "检查 DSH 兼容性", "compat.none": "未声明 DSH", "compat.loading": "DSH …", "compat.apiPeers": "DSH API 依赖", "released": "发布 {age}", "repoUpdated": "仓库 {age}", "deprecated": "已弃用", "copy": "复制命令", "copied": "已复制", "copy.failed": "复制失败", "install.warning": "运行此第三方安装命令前,请检查包和来源。", "readonly": "设置为只读。", "unavailable": "插件源设置不可用。",
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
function slug(value) {
|
|
474
|
+
const input = value.trim();
|
|
475
|
+
const ascii = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 48);
|
|
476
|
+
if (ascii) return ascii;
|
|
477
|
+
let hash = 2166136261;
|
|
478
|
+
for (const char of input) { hash ^= char.codePointAt(0); hash = Math.imul(hash, 16777619); }
|
|
479
|
+
return input ? `source-${(hash >>> 0).toString(36)}` : "";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function rpc(endpoint, payload, signal) {
|
|
483
|
+
if (!connection?.rpc?.call) return Promise.reject(new Error("Host connection is unavailable"));
|
|
484
|
+
return connection.rpc.call(CHANNEL, `${RPC_PREFIX}/${endpoint}`, payload, signal).then((result) => {
|
|
485
|
+
if (!result?.ok) throw new Error(result?.error?.message || "Host request failed");
|
|
486
|
+
return result.value;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function SearchIcon() { return h(IconSearchOutlineRegular, { size: 16 }); }
|
|
491
|
+
|
|
492
|
+
function CopyIcon({ done = false }) {
|
|
493
|
+
return done
|
|
494
|
+
? h("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": true, children: h("path", { d: "m5 12 4 4L19 6" }) })
|
|
495
|
+
: hs("svg", { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, "aria-hidden": true, children: [h("rect", { x: 9, y: 9, width: 10, height: 10, rx: 2 }), h("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] });
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
function RefreshIcon() { return h(IconRefreshOutlineRegular, { size: 16 }); }
|
|
500
|
+
function PlusIcon() { return h(IconPlusOutlineRegular, { size: 16 }); }
|
|
501
|
+
function NetworkIcon() { return h(IconDatabaseOutlineRegular, { size: 18 }); }
|
|
502
|
+
function ShareIcon() { return h(IconShareOutlineRegular, { size: 16 }); }
|
|
503
|
+
function TrashIcon() { return h(IconTrashOutlineRegular, { size: 16 }); }
|
|
504
|
+
function PackageIcon() {
|
|
505
|
+
return h("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.7, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: [
|
|
506
|
+
h("path", { d: "m12 3 8 4.5v9L12 21l-8-4.5v-9L12 3Z" }),
|
|
507
|
+
h("path", { d: "m4 7.5 8 4.5 8-4.5" }),
|
|
508
|
+
h("path", { d: "M12 12v9" })
|
|
509
|
+
] });
|
|
510
|
+
}
|
|
511
|
+
function DatabaseIcon() { return h(IconDatabaseOutlineRegular, { size: 16 }); }
|
|
512
|
+
function GridIcon() { return h(IconCordisPluginOutlineRegular, { size: 16 }); }
|
|
513
|
+
function LinkIcon() { return h(IconLinkOutlineRegular, { size: 16 }); }
|
|
514
|
+
function AlertIcon() { return h(IconWarningOutlineRegular, { size: 16 }); }
|
|
515
|
+
|
|
516
|
+
function GitHubMark() {
|
|
517
|
+
return h("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children: h("path", { d: "M12 2a10 10 0 0 0-3.2 19.5c.5.1.7-.2.7-.5v-1.9c-2.8.6-3.4-1.2-3.4-1.2-.5-1.2-1.1-1.5-1.1-1.5-.9-.6.1-.6.1-.6 1 .1 1.6 1 1.6 1 .9 1.5 2.3 1.1 2.9.8.1-.6.4-1.1.6-1.3-2.2-.3-4.6-1.1-4.6-4.9 0-1.1.4-2 1-2.6-.1-.3-.4-1.3.1-2.6 0 0 .8-.3 2.7 1a9.4 9.4 0 0 1 4.9 0c1.9-1.3 2.7-1 2.7-1 .5 1.3.2 2.3.1 2.6.6.7 1 1.5 1 2.6 0 3.8-2.3 4.6-4.6 4.9.4.3.7 1 .7 1.8V21c0 .3.2.6.7.5A10 10 0 0 0 12 2Z" }) });
|
|
518
|
+
}
|
|
519
|
+
function Empty({ title, hint }) {
|
|
520
|
+
return hs("div", { className: "pm-empty", children: [h("strong", { children: title }), h("span", { children: hint })] });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function AddSourceForm({ t, sources, onSave, busy, readOnly }) {
|
|
524
|
+
const [name, setName] = react.useState("");
|
|
525
|
+
const [type, setType] = react.useState("dshplugin-app");
|
|
526
|
+
const [url, setUrl] = react.useState("");
|
|
527
|
+
const [error, setError] = react.useState("");
|
|
528
|
+
const [expanded, setExpanded] = react.useState(true);
|
|
529
|
+
const reset = () => { setName(""); setType("dshplugin-app"); setUrl(""); setError(""); };
|
|
530
|
+
const submit = async (event) => {
|
|
531
|
+
event.preventDefault();
|
|
532
|
+
const id = slug(name);
|
|
533
|
+
const needsUrl = !["npm", "github"].includes(type);
|
|
534
|
+
if (!id) return setError(t("source.required"));
|
|
535
|
+
if (needsUrl && !url.trim()) return setError(t("source.urlRequired"));
|
|
536
|
+
if (sources.some((source) => source.id === id)) return setError(t("source.duplicate"));
|
|
537
|
+
setError("");
|
|
538
|
+
try {
|
|
539
|
+
const saved = await onSave([...sources, { id, name: name.trim(), type, ...(url.trim() ? { url: url.trim() } : {}), enabled: true }]);
|
|
540
|
+
if (saved === false) return;
|
|
541
|
+
reset();
|
|
542
|
+
} catch (failure) {
|
|
543
|
+
setError(String(failure?.message ?? failure));
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
return hs("section", { className: "pm-add-source-panel", children: [
|
|
547
|
+
hs("div", { className: "pm-section-head", children: [
|
|
548
|
+
hs("div", { className: "pm-section-head-main", children: [
|
|
549
|
+
h("div", { children: [h("div", { className: "pm-section-title", children: t("sources.addTitle") }), h("div", { className: "pm-section-lead", children: t("sources.addLead") })] }),
|
|
550
|
+
] }),
|
|
551
|
+
h("button", { type: "button", className: "pm-disclosure", onClick: () => setExpanded((value) => !value), "aria-expanded": expanded, "aria-label": expanded ? "Collapse" : "Expand", children: h(ChevronIcon, { open: expanded }) }),
|
|
552
|
+
] }),
|
|
553
|
+
expanded ? hs("form", { className: "pm-form pm-form-redesign", onSubmit: submit, children: [
|
|
554
|
+
hs("div", { className: "pm-field", children: [
|
|
555
|
+
hs("label", { htmlFor: "pm-name", children: [t("field.name"), h("span", { className: "pm-required", children: "*" })] }),
|
|
556
|
+
h("input", { id: "pm-name", className: "pm-input", value: name, placeholder: "e.g. My Registry", onChange: (event) => { setName(event.target.value); if (error) setError(""); }, disabled: readOnly || busy }),
|
|
557
|
+
] }),
|
|
558
|
+
hs("div", { className: "pm-field", children: [
|
|
559
|
+
h("label", { htmlFor: "pm-type", children: t("field.type") }),
|
|
560
|
+
h("select", { id: "pm-type", className: "pm-select", value: type, onChange: (event) => { setType(event.target.value); if (error) setError(""); }, disabled: readOnly || busy, children: SOURCE_TYPES.map((item) => h("option", { value: item, children: item }, item)) }),
|
|
561
|
+
] }),
|
|
562
|
+
hs("div", { className: "pm-field pm-field-url", children: [
|
|
563
|
+
h("label", { htmlFor: "pm-url", children: t("field.url") }),
|
|
564
|
+
hs("div", { className: "pm-input-wrap", children: [h("span", { className: "pm-input-leading", children: h(LinkIcon, {}) }), h("input", { id: "pm-url", className: "pm-input", type: "url", value: url, placeholder: t("field.urlHint"), onChange: (event) => { setUrl(event.target.value); if (error) setError(""); }, disabled: readOnly || busy })] }),
|
|
565
|
+
] }),
|
|
566
|
+
hs("div", { className: "pm-form-actions", children: [
|
|
567
|
+
h(Button, { variant: "outline", size: "md", type: "button", disabled: readOnly || busy, onClick: reset, children: t("install.cancel") }),
|
|
568
|
+
h(Button, { variant: "primary", size: "md", type: "submit", disabled: readOnly || busy, children: t("sources.add") }),
|
|
569
|
+
] }),
|
|
570
|
+
error ? hs("p", { className: "pm-form-error", role: "alert", children: [h(AlertIcon, {}), h("span", { children: error })] }) : null,
|
|
571
|
+
] }) : null,
|
|
572
|
+
] });
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function ChevronIcon({ open }) {
|
|
576
|
+
return h(IconChevronDownOutlineRegular, { size: 14, className: "pm-chevron", "data-open": open });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function sourceEndpoint(source) {
|
|
580
|
+
if (source.url) return source.url;
|
|
581
|
+
if (source.type === "npm") return "https://registry.npmjs.org";
|
|
582
|
+
if (source.type === "github") return "https://api.github.com";
|
|
583
|
+
return source.id;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function NpmMark() {
|
|
587
|
+
return h("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": true, children:
|
|
588
|
+
h("path", { d: "M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z" })
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function SourceLogo({ source }) {
|
|
593
|
+
if (source.type === "npm") return h("div", { className: "pm-source-logo pm-source-logo-npm", "aria-hidden": true, children: h(NpmMark, {}) });
|
|
594
|
+
if (source.type === "github") return h("div", { className: "pm-source-logo pm-source-logo-github", "aria-hidden": true, children: h(GitHubMark, {}) });
|
|
595
|
+
return h("div", { className: "pm-source-logo pm-source-logo-generic", "aria-hidden": true, children: h(PackageIcon, {}) });
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function SourceRow({ source, health, t, busy, readOnly, onToggle, onRemove }) {
|
|
599
|
+
const [open, setOpen] = react.useState(true);
|
|
600
|
+
const [copied, setCopied] = react.useState(false);
|
|
601
|
+
const status = source.enabled === false ? "off" : health?.ok === true ? "ok" : health?.ok === false ? "bad" : "wait";
|
|
602
|
+
const endpoint = sourceEndpoint(source);
|
|
603
|
+
const count = Number.isFinite(health?.count) ? health.count : 0;
|
|
604
|
+
const statusText = status === "off" ? t("source.disabled") : status === "ok" ? t("source.online") : status === "bad" ? t("source.error") : t("source.checking");
|
|
605
|
+
const copyEndpoint = async () => {
|
|
606
|
+
try { await globalThis.navigator?.clipboard?.writeText?.(endpoint); setCopied(true); setTimeout(() => setCopied(false), 1400); } catch {}
|
|
607
|
+
};
|
|
608
|
+
return hs("article", { className: "pm-source", "data-open": open, children: [
|
|
609
|
+
hs("div", { className: "pm-source-head", children: [
|
|
610
|
+
hs("div", { className: "pm-source-main", children: [
|
|
611
|
+
h(SourceLogo, { source }),
|
|
612
|
+
h("div", { className: "pm-source-summary", children: [
|
|
613
|
+
h("div", { className: "pm-source-line", children: h("span", { className: "pm-source-name", children: source.name }) }),
|
|
614
|
+
hs("div", { className: "pm-source-evidence", children: [
|
|
615
|
+
hs("span", { className: "pm-source-evidence-item", children: [h("span", { className: `pm-status pm-status-${status}`, "aria-hidden": true }), h("span", { children: statusText })] }),
|
|
616
|
+
status === "ok" ? h("span", { className: "pm-evidence-separator", "aria-hidden": true }) : null,
|
|
617
|
+
status === "ok" ? hs("span", { className: "pm-source-evidence-item", children: [h(PackageIcon, {}), h("span", { children: t(source.type === "github" ? "source.repositories" : "source.packages", { count }) })] }) : null,
|
|
618
|
+
] }),
|
|
619
|
+
] }),
|
|
620
|
+
] }),
|
|
621
|
+
hs("div", { className: "pm-source-buttons", children: [
|
|
622
|
+
h("span", { className: "pm-source-switch", children:
|
|
623
|
+
h(Switch, { checked: source.enabled !== false, disabled: readOnly || busy, label: source.enabled !== false ? t("source.enabled") : t("source.disabled"), onChange: () => onToggle(source.id) })
|
|
624
|
+
}),
|
|
625
|
+
h("button", { type: "button", className: "pm-source-disclosure", onClick: () => setOpen((value) => !value), "aria-expanded": open, "aria-label": open ? "Collapse source" : "Expand source", children: h(ChevronIcon, { open }) }),
|
|
626
|
+
h("button", { type: "button", className: "pm-source-delete", disabled: readOnly || busy, onClick: () => onRemove(source.id), title: t("source.remove"), "aria-label": t("source.remove"), children: h(TrashIcon, {}) }),
|
|
627
|
+
] }),
|
|
628
|
+
] }),
|
|
629
|
+
open ? hs("div", { className: "pm-source-details", children: [
|
|
630
|
+
h("div", { className: "pm-source-url-label", children: t("field.url") }),
|
|
631
|
+
hs("div", { className: "pm-source-url-wrap", children: [
|
|
632
|
+
h("input", { className: "pm-source-url", value: endpoint, readOnly: true, title: endpoint }),
|
|
633
|
+
h("button", { type: "button", className: "pm-copy-url", onClick: copyEndpoint, title: copied ? t("copied") : t("copy"), "aria-label": copied ? t("copied") : t("copy"), children: copied ? h(IconCopyOutlineRegular, { size: 16 }) : h(IconCopyOutlineRegular, { size: 16 }) }),
|
|
634
|
+
] }),
|
|
635
|
+
health?.error ? h("div", { className: "pm-source-error", title: health.error, children: health.error }) : null,
|
|
636
|
+
] }) : null,
|
|
637
|
+
] });
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function SourcesView({ t, sources, writable, saveSources, busy, healthState, refreshHealth }) {
|
|
641
|
+
const [mutationError, setMutationError] = react.useState("");
|
|
642
|
+
const mutate = async (next) => {
|
|
643
|
+
setMutationError("");
|
|
644
|
+
try { await saveSources(next); return true; }
|
|
645
|
+
catch (failure) { setMutationError(String(failure?.message ?? failure)); return false; }
|
|
646
|
+
};
|
|
647
|
+
const healthMap = Object.fromEntries((healthState.data?.sources ?? []).map((row) => [row.source.id, row.health]));
|
|
648
|
+
return hs("section", { className: "pm-sources-view", children: [
|
|
649
|
+
h(AddSourceForm, { t, sources, onSave: mutate, busy, readOnly: !writable }),
|
|
650
|
+
hs("section", { className: "pm-connected-panel", children: [
|
|
651
|
+
hs("div", { className: "pm-section-head pm-connected-head", children: [
|
|
652
|
+
hs("div", { className: "pm-section-head-main", children: [
|
|
653
|
+
h("div", { children: [h("div", { className: "pm-section-title", children: t("sources.title") }), h("div", { className: "pm-section-lead", children: t("sources.manage") })] }),
|
|
654
|
+
] }),
|
|
655
|
+
h("button", { type: "button", className: "pm-refresh-icon", onClick: refreshHealth, disabled: healthState.loading, title: t("browse.refresh"), "aria-label": t("browse.refresh"), children: h(RefreshIcon, {}) }),
|
|
656
|
+
] }),
|
|
657
|
+
!writable ? h("p", { className: "pm-notice pm-connected-notice", children: t("readonly") }) : null,
|
|
658
|
+
mutationError ? h("p", { className: "pm-notice pm-notice-error pm-connected-notice", role: "alert", children: mutationError }) : null,
|
|
659
|
+
healthState.error ? h("p", { className: "pm-notice pm-notice-error pm-connected-notice", role: "alert", children: healthState.error }) : null,
|
|
660
|
+
sources.length === 0 ? h(Empty, { title: t("sources.empty"), hint: t("sources.emptyHint") }) : h("div", { className: "pm-source-list", children: sources.map((source) => h(SourceRow, { source, health: healthMap[source.id], t, busy, readOnly: !writable, onToggle: (id) => { void mutate(sources.map((row) => row.id === id ? { ...row, enabled: row.enabled === false } : row)); }, onRemove: (id) => { void mutate(sources.filter((row) => row.id !== id)); } }, source.id)) }),
|
|
661
|
+
] }),
|
|
662
|
+
] });
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function installCommand(plugin) {
|
|
666
|
+
return plugin.install?.spec ? `dsh plugin add ${plugin.install.spec}` : "";
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function compactNumber(value) {
|
|
670
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "";
|
|
671
|
+
return new Intl.NumberFormat(undefined, { notation: "compact", maximumFractionDigits: 1 }).format(value);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function repositoryEvidenceKey(value) {
|
|
675
|
+
if (typeof value !== "string" || !value.trim()) return "";
|
|
676
|
+
let raw = value.trim().replace(/^git\+/, "").replace(/^git@github\.com:/i, "https://github.com/");
|
|
677
|
+
try {
|
|
678
|
+
const url = new URL(raw);
|
|
679
|
+
url.hash = "";
|
|
680
|
+
url.search = "";
|
|
681
|
+
return url.toString().replace(/\.git$/i, "").replace(/\/$/, "").toLowerCase();
|
|
682
|
+
} catch {
|
|
683
|
+
return raw.replace(/\.git$/i, "").replace(/\/$/, "").toLowerCase();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function relativeAge(value) {
|
|
688
|
+
const timestamp = Date.parse(value || "");
|
|
689
|
+
if (!Number.isFinite(timestamp)) return "";
|
|
690
|
+
const days = Math.max(0, Math.floor((Date.now() - timestamp) / 86_400_000));
|
|
691
|
+
if (days < 1) return "<1d";
|
|
692
|
+
if (days < 30) return `${days}d`;
|
|
693
|
+
if (days < 365) return `${Math.floor(days / 30)}mo`;
|
|
694
|
+
return `${Math.floor(days / 365)}y`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function releaseChannelLabel(channel) {
|
|
698
|
+
return channel === "stable" ? "stable" : channel || "";
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function npmPackageUrl(packageName) {
|
|
702
|
+
return packageName ? `https://www.npmjs.com/package/${encodeURIComponent(packageName).replace(/%2F/gi, "/")}` : "";
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function ExternalLink({ href, className, children, title }) {
|
|
706
|
+
return h("a", { href, className, title, target: "_blank", rel: "noopener noreferrer", children });
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function ClampedDescription({ text, t, onExpand }) {
|
|
710
|
+
const [expanded, setExpanded] = react.useState(false);
|
|
711
|
+
const [overflow, setOverflow] = react.useState(false);
|
|
712
|
+
const ref = react.useRef(null);
|
|
713
|
+
react.useEffect(() => {
|
|
714
|
+
if (expanded) return undefined;
|
|
715
|
+
const node = ref.current;
|
|
716
|
+
if (!node) return undefined;
|
|
717
|
+
const measure = () => setOverflow(node.scrollHeight > node.clientHeight + 1);
|
|
718
|
+
measure();
|
|
719
|
+
if (typeof globalThis.ResizeObserver !== "function") return undefined;
|
|
720
|
+
const observer = new globalThis.ResizeObserver(measure);
|
|
721
|
+
observer.observe(node);
|
|
722
|
+
return () => observer.disconnect();
|
|
723
|
+
}, [text, expanded]);
|
|
724
|
+
const toggle = () => {
|
|
725
|
+
const next = !expanded;
|
|
726
|
+
setExpanded(next);
|
|
727
|
+
if (next) onExpand?.();
|
|
728
|
+
};
|
|
729
|
+
return hs("div", { className: "pm-card-description", "data-expanded": expanded, children: [
|
|
730
|
+
h("p", { ref, className: "pm-desc", "data-clamped": !expanded, children: text }),
|
|
731
|
+
!expanded && overflow ? h("button", { type: "button", className: "pm-desc-more", onClick: toggle, title: t("description.expand"), "aria-label": t("description.expand"), children: "..." }) : null,
|
|
732
|
+
expanded ? h("button", { type: "button", className: "pm-desc-less", onClick: toggle, children: t("description.less") }) : null,
|
|
733
|
+
] });
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function sortDirectionIcon(direction) {
|
|
737
|
+
if (direction === "asc") return h(IconChevronUpOutlineRegular, { size: 12 });
|
|
738
|
+
if (direction === "desc") return h(IconChevronDownOutlineRegular, { size: 12 });
|
|
739
|
+
return h(IconChevronsUpDownOutlineRegular, { size: 13 });
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function SortCriterion({ criterion, icon, label, sorts, onCycle, t }) {
|
|
743
|
+
const index = sorts.findIndex((item) => item.key === criterion);
|
|
744
|
+
const active = index >= 0;
|
|
745
|
+
const direction = active ? sorts[index].direction : "off";
|
|
746
|
+
const title = active
|
|
747
|
+
? `${label} · ${direction === "asc" ? t("browse.sort.asc") : t("browse.sort.desc")}`
|
|
748
|
+
: `${label} · ${t("browse.sort.off")}`;
|
|
749
|
+
return hs("button", {
|
|
750
|
+
type: "button",
|
|
751
|
+
className: "pm-sort-criterion",
|
|
752
|
+
"data-active": active,
|
|
753
|
+
"data-direction": direction,
|
|
754
|
+
title,
|
|
755
|
+
"aria-label": title,
|
|
756
|
+
onClick: () => onCycle(criterion),
|
|
757
|
+
children: [
|
|
758
|
+
h("span", { className: "pm-sort-symbol", children: icon }),
|
|
759
|
+
h("span", { className: "pm-sort-direction", children: sortDirectionIcon(direction) }),
|
|
760
|
+
],
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function pageTokens(page, pageCount) {
|
|
765
|
+
if (pageCount <= 7) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
766
|
+
const keep = new Set([1, pageCount, page - 1, page, page + 1].filter((value) => value >= 1 && value <= pageCount));
|
|
767
|
+
const sorted = [...keep].sort((a, b) => a - b);
|
|
768
|
+
const result = [];
|
|
769
|
+
for (let index = 0; index < sorted.length; index += 1) {
|
|
770
|
+
const value = sorted[index];
|
|
771
|
+
const previous = sorted[index - 1];
|
|
772
|
+
if (index > 0 && value - previous > 1) result.push("ellipsis-" + previous);
|
|
773
|
+
result.push(value);
|
|
774
|
+
}
|
|
775
|
+
return result;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function Pagination({ page, pageCount, pageSize, setPage, setPageSize, disabled, t, showPageSize = true }) {
|
|
779
|
+
const [sizeOpen, setSizeOpen] = react.useState(false);
|
|
780
|
+
const sizeItems = [20, 50, 100].map((size) => ({ id: String(size), label: String(size) }));
|
|
781
|
+
const sizePicker = showPageSize ? h(Menu, {
|
|
782
|
+
open: sizeOpen,
|
|
783
|
+
compact: true,
|
|
784
|
+
align: "end",
|
|
785
|
+
portal: true,
|
|
786
|
+
selectedId: String(pageSize),
|
|
787
|
+
items: sizeItems,
|
|
788
|
+
onClose: () => setSizeOpen(false),
|
|
789
|
+
onSelect: (id) => { setSizeOpen(false); setPageSize(Number(id)); },
|
|
790
|
+
anchor: hs("button", {
|
|
791
|
+
id: "pm-page-size",
|
|
792
|
+
type: "button",
|
|
793
|
+
className: "pm-page-size-control",
|
|
794
|
+
onClick: () => setSizeOpen((value) => !value),
|
|
795
|
+
title: `${t("browse.pageSize")}: ${pageSize}`,
|
|
796
|
+
"aria-label": `${t("browse.pageSize")}: ${pageSize}`,
|
|
797
|
+
"aria-haspopup": "menu",
|
|
798
|
+
"aria-expanded": sizeOpen,
|
|
799
|
+
children: [
|
|
800
|
+
h(IconFlatListOutlineRegular, { size: 14 }),
|
|
801
|
+
h("span", { children: String(pageSize) }),
|
|
802
|
+
h(IconChevronDownOutlineRegular, { size: 12 }),
|
|
803
|
+
],
|
|
804
|
+
}),
|
|
805
|
+
}) : null;
|
|
806
|
+
return hs("div", { className: "pm-pagination", children: [
|
|
807
|
+
hs("div", { className: "pm-page-controls", children: [
|
|
808
|
+
h("button", { type: "button", className: "pm-page-button", disabled: page <= 1, onClick: () => setPage(Math.max(1, page - 1)), title: t("browse.prev"), "aria-label": t("browse.prev"), children: h(IconChevronLeftOutlineRegular, { size: 14 }) }),
|
|
809
|
+
...pageTokens(page, pageCount).map((token) => typeof token === "number"
|
|
810
|
+
? h("button", { type: "button", className: "pm-page-button", "data-current": token === page, onClick: () => setPage(token), children: String(token) }, token)
|
|
811
|
+
: h("span", { className: "pm-page-ellipsis", children: "…" }, token)),
|
|
812
|
+
h("button", { type: "button", className: "pm-page-button", disabled: page >= pageCount, onClick: () => setPage(Math.min(pageCount, page + 1)), title: t("browse.next"), "aria-label": t("browse.next"), children: h(IconChevronRightOutlineRegular, { size: 14 }) }),
|
|
813
|
+
] }),
|
|
814
|
+
sizePicker,
|
|
815
|
+
] });
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function PluginCard({ plugin, t }) {
|
|
819
|
+
const [copyState, setCopyState] = react.useState("");
|
|
820
|
+
const [installState, setInstallState] = react.useState("idle");
|
|
821
|
+
const [installError, setInstallError] = react.useState("");
|
|
822
|
+
const [installProgress, setInstallProgress] = react.useState({ phase: "idle", activity: 0 });
|
|
823
|
+
const [detailsState, setDetailsState] = react.useState({ status: "idle" });
|
|
824
|
+
const command = installCommand(plugin);
|
|
825
|
+
const spec = plugin.install?.spec || "";
|
|
826
|
+
const declaredCompatibility = plugin.evidence?.dshCompatibility;
|
|
827
|
+
const loadDetails = async () => {
|
|
828
|
+
if (detailsState.status === "loading" || detailsState.status === "ready") return;
|
|
829
|
+
const payload = plugin.identity?.package && plugin.version
|
|
830
|
+
? { package: plugin.identity.package, version: plugin.version }
|
|
831
|
+
: plugin.identity?.repository
|
|
832
|
+
? { repository: plugin.identity.repository }
|
|
833
|
+
: null;
|
|
834
|
+
if (!payload) return setDetailsState({ status: "ready", data: {} });
|
|
835
|
+
setDetailsState({ status: "loading" });
|
|
836
|
+
try {
|
|
837
|
+
const data = await rpc("details", payload);
|
|
838
|
+
setDetailsState({ status: "ready", data: data ?? {} });
|
|
839
|
+
} catch (error) {
|
|
840
|
+
setDetailsState({ status: "error", error: String(error?.message ?? error) });
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
const resolvedCompatibility = declaredCompatibility ?? detailsState.data?.dshCompatibility;
|
|
844
|
+
const dshPeers = detailsState.data?.dshPeers ?? [];
|
|
845
|
+
const peerTitle = dshPeers.map((peer) => `${peer.dependency} ${peer.range}`).join("\n");
|
|
846
|
+
const compatibilityLabel = resolvedCompatibility ? `DSH ${resolvedCompatibility}`
|
|
847
|
+
: dshPeers.length ? t("compat.apiPeers")
|
|
848
|
+
: detailsState.status === "loading" ? t("compat.loading")
|
|
849
|
+
: detailsState.status === "ready" ? t("compat.none") : t("compat.check");
|
|
850
|
+
const releaseDate = plugin.evidence?.releasedAt;
|
|
851
|
+
const repoDate = plugin.evidence?.repositoryUpdatedAt;
|
|
852
|
+
const freshnessDate = releaseDate ?? repoDate;
|
|
853
|
+
const freshnessLabel = releaseDate ? t("released", { age: relativeAge(releaseDate) })
|
|
854
|
+
: repoDate ? t("repoUpdated", { age: relativeAge(repoDate) }) : "";
|
|
855
|
+
|
|
856
|
+
react.useEffect(() => {
|
|
857
|
+
if (!copyState) return undefined;
|
|
858
|
+
const timer = setTimeout(() => setCopyState(""), 1400);
|
|
859
|
+
return () => clearTimeout(timer);
|
|
860
|
+
}, [copyState]);
|
|
861
|
+
|
|
862
|
+
const copy = async () => {
|
|
863
|
+
try {
|
|
864
|
+
if (!command || !globalThis.navigator?.clipboard?.writeText) throw new Error(t("copy.failed"));
|
|
865
|
+
await globalThis.navigator.clipboard.writeText(command);
|
|
866
|
+
setCopyState("ok");
|
|
867
|
+
} catch { setCopyState("error"); }
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
const install = async () => {
|
|
871
|
+
if (!spec || !remote?.pluginManager || installState === "installing" || installState === "installed" || installState === "invalid") return;
|
|
872
|
+
setInstallState("installing");
|
|
873
|
+
setInstallError("");
|
|
874
|
+
setInstallProgress({ phase: "checking", activity: 0 });
|
|
875
|
+
let disposeState;
|
|
876
|
+
let disposeLog;
|
|
877
|
+
try {
|
|
878
|
+
const inspection = await remote.pluginManager.inspect(spec, { registry: null });
|
|
879
|
+
if (!inspection?.ok) throw new Error(inspection?.error?.message || t("install.failed"));
|
|
880
|
+
if (inspection.value?.status !== "accepted") {
|
|
881
|
+
const problem = inspection.value?.problem;
|
|
882
|
+
const reason = inspection.value?.reason || "Plugin spec was rejected";
|
|
883
|
+
if (problem === "not-a-bundle") {
|
|
884
|
+
setInstallState("invalid");
|
|
885
|
+
setInstallProgress({ phase: "idle", activity: 0 });
|
|
886
|
+
setInstallError(reason);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
throw new Error(reason);
|
|
890
|
+
}
|
|
891
|
+
const requestId = globalThis.crypto?.randomUUID?.();
|
|
892
|
+
if (!requestId) throw new Error("Browser cannot create an install request id");
|
|
893
|
+
setInstallProgress({ phase: "starting", requestId, activity: 0 });
|
|
894
|
+
if (typeof remote?.$on === "function") {
|
|
895
|
+
disposeState = remote.$on("plugin-manager/install-state", (progress) => {
|
|
896
|
+
if (progress?.requestId !== requestId) return;
|
|
897
|
+
setInstallProgress((current) => ({
|
|
898
|
+
...current,
|
|
899
|
+
phase: progress.phase,
|
|
900
|
+
attempt: progress.attempt,
|
|
901
|
+
activity: (current.activity ?? 0) + 1,
|
|
902
|
+
}));
|
|
903
|
+
});
|
|
904
|
+
disposeLog = remote.$on("plugin-manager/install-log", (chunk) => {
|
|
905
|
+
if (chunk?.requestId !== requestId) return;
|
|
906
|
+
setInstallProgress((current) => ({
|
|
907
|
+
...current,
|
|
908
|
+
phase: current.phase === "checking" || current.phase === "starting" ? "installing" : current.phase,
|
|
909
|
+
activity: (current.activity ?? 0) + 1,
|
|
910
|
+
}));
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
const options = {
|
|
914
|
+
enabled: true,
|
|
915
|
+
registry: inspection.value.registry,
|
|
916
|
+
requestId,
|
|
917
|
+
};
|
|
918
|
+
const result = await remote.pluginManager.installBundle(spec, options);
|
|
919
|
+
if (!result?.ok) throw new Error(result?.error?.message || t("install.failed"));
|
|
920
|
+
if (result.value?.application === "failed" || result.value?.application === "cancelled") {
|
|
921
|
+
const pending = result.value?.pendingBuilds;
|
|
922
|
+
if (Array.isArray(pending) && pending.length) throw new Error(`${t("install.approvalRequired")}: ${pending.join(", ")}`);
|
|
923
|
+
throw new Error(result.value?.error?.diagnostic || result.value?.packageResult?.output || t("install.failed"));
|
|
924
|
+
}
|
|
925
|
+
setInstallProgress((current) => ({ ...current, phase: "done", requestId }));
|
|
926
|
+
setInstallState("installed");
|
|
927
|
+
} catch (error) {
|
|
928
|
+
setInstallProgress((current) => ({ ...current, phase: "failed" }));
|
|
929
|
+
setInstallState("failed");
|
|
930
|
+
setInstallError(String(error?.message ?? error));
|
|
931
|
+
} finally {
|
|
932
|
+
if (typeof disposeState === "function") disposeState();
|
|
933
|
+
if (typeof disposeLog === "function") disposeLog();
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
const sources = plugin.sources ?? [];
|
|
938
|
+
const primarySource = sources[0];
|
|
939
|
+
const requiresVerification = sources.some((source) => source.type === "npm" || source.type === "github");
|
|
940
|
+
const installability = detailsState.data?.installability ?? plugin.evidence?.installability ?? (requiresVerification ? "unknown" : "bundle");
|
|
941
|
+
const installAllowed = Boolean(spec) && (!requiresVerification || installability === "bundle");
|
|
942
|
+
const bundleLabel = installability === "bundle" ? t("bundle.verified")
|
|
943
|
+
: installability === "not-bundle" ? t("bundle.notBundle") : t("bundle.unknown");
|
|
944
|
+
const progressTitle = installProgress.phase === "checking" ? t("install.checking")
|
|
945
|
+
: installProgress.phase === "starting" ? t("install.starting")
|
|
946
|
+
: installProgress.phase === "installing"
|
|
947
|
+
? installProgress.attempt
|
|
948
|
+
? t("install.runningAttempt", { index: installProgress.attempt.index, total: installProgress.attempt.total })
|
|
949
|
+
: t("install.running")
|
|
950
|
+
: installProgress.phase === "applying" ? t("install.applying")
|
|
951
|
+
: t("installing");
|
|
952
|
+
const installTitle = installState === "installing" ? progressTitle
|
|
953
|
+
: installState === "installed" ? t("installed")
|
|
954
|
+
: installState === "invalid" ? t("install.notBundle")
|
|
955
|
+
: installState === "failed" ? t("install.retry") : t("install");
|
|
956
|
+
const installIcon = installState === "installing" ? h(StateDot, { state: "ongoing", size: 16 })
|
|
957
|
+
: installState === "installed" ? h(IconCheckOutlineRegular, { size: 16 })
|
|
958
|
+
: installState === "failed" || installState === "invalid" ? h(IconWarningOutlineRegular, { size: 16 })
|
|
959
|
+
: h(IconDownloadOutlineRegular, { size: 16 });
|
|
960
|
+
|
|
961
|
+
return hs("article", { className: "pm-card", children: [
|
|
962
|
+
hs("div", { className: "pm-card-top", children: [
|
|
963
|
+
hs("div", { className: "pm-card-main", children: [
|
|
964
|
+
hs("div", { className: "pm-card-titleline", children: [
|
|
965
|
+
plugin.identity?.package
|
|
966
|
+
? h(ExternalLink, { href: npmPackageUrl(plugin.identity.package), className: "pm-plugin-name pm-link", title: plugin.identity.package, children: plugin.name })
|
|
967
|
+
: plugin.identity?.repository
|
|
968
|
+
? h(ExternalLink, { href: plugin.identity.repository, className: "pm-plugin-name pm-link", title: plugin.identity.repository, children: plugin.name })
|
|
969
|
+
: h("span", { className: "pm-plugin-name", children: plugin.name }),
|
|
970
|
+
plugin.version ? h("span", { className: "pm-version", children: plugin.version }) : null,
|
|
971
|
+
plugin.evidence?.releaseChannel ? h("span", { className: `pm-channel pm-channel-${plugin.evidence.releaseChannel}`, children: releaseChannelLabel(plugin.evidence.releaseChannel) }) : null,
|
|
972
|
+
] }),
|
|
973
|
+
hs("div", { className: "pm-card-stats", children: [
|
|
974
|
+
hs("span", { className: "pm-signal", title: "GitHub stars", children: [h("span", { className: "pm-star-icon", children: "★" }), h("span", { children: compactNumber(plugin.evidence?.stars ?? 0) })] }),
|
|
975
|
+
hs("span", { className: "pm-signal", title: "npm downloads in the last 30 days", children: [h(IconDownloadOutlineRegular, { size: 13 }), h("span", { children: `${compactNumber(plugin.evidence?.downloads30d ?? 0)} / 30d` })] }),
|
|
976
|
+
freshnessLabel ? hs("span", { className: "pm-signal", title: freshnessDate, children: [h(IconClockOutlineRegular, { size: 13 }), h("span", { children: freshnessLabel })] }) : null,
|
|
977
|
+
plugin.evidence?.rating !== undefined ? hs("span", { className: "pm-signal", title: plugin.evidence.ratingCount !== undefined ? `${plugin.evidence.ratingCount} ratings` : "Source rating", children: [h("span", { className: "pm-star-icon", children: "★" }), h("span", { children: `${plugin.evidence.rating.toFixed(1)}${plugin.evidence.ratingCount !== undefined ? ` (${compactNumber(plugin.evidence.ratingCount)})` : ""}` })] }) : null,
|
|
978
|
+
] }),
|
|
979
|
+
] }),
|
|
980
|
+
installAllowed ? h("button", { type: "button", className: "pm-card-install", "data-state": installState, disabled: installState === "installing" || installState === "installed" || installState === "invalid", onClick: install, title: installTitle, "aria-label": installTitle, children: installIcon }) : null,
|
|
981
|
+
] }),
|
|
982
|
+
plugin.description ? h(ClampedDescription, { text: plugin.description, t, onExpand: loadDetails }) : null,
|
|
983
|
+
hs("div", { className: "pm-card-meta", children: [
|
|
984
|
+
primarySource ? h("span", { className: "pm-badge", children: primarySource.name }, primarySource.id) : null,
|
|
985
|
+
...(plugin.tags ?? []).map((tag) => h("span", { className: "pm-tag", children: tag }, tag)),
|
|
986
|
+
requiresVerification ? h("span", { className: `pm-bundle-state pm-bundle-state-${installability}`, title: bundleLabel, children: bundleLabel }) : null,
|
|
987
|
+
h("button", { type: "button", className: "pm-compat pm-compat-button", title: resolvedCompatibility ? `DSH ${resolvedCompatibility}` : peerTitle || t("compat.check"), onClick: () => { void loadDetails(); }, children: compatibilityLabel }),
|
|
988
|
+
detailsState.data?.deprecated ? h("span", { className: "pm-compat pm-deprecated", title: detailsState.data.deprecated, children: t("deprecated") }) : null,
|
|
989
|
+
] }),
|
|
990
|
+
plugin.identity?.repository ? h(ExternalLink, { href: plugin.identity.repository, className: "pm-repo-link pm-link", title: plugin.identity.repository, children: plugin.identity.repository }) : null,
|
|
991
|
+
dshPeers.length && !resolvedCompatibility && detailsState.status === "ready" ? h("div", { className: "pm-source-meta", children: `DSH API: ${dshPeers.map((peer) => `${peer.dependency} ${peer.range}`).join("; ")}` }) : null,
|
|
992
|
+
command && installAllowed ? hs("div", { className: "pm-command-row", children: [
|
|
993
|
+
h("code", { className: "pm-command", children: command }),
|
|
994
|
+
h("button", { type: "button", className: "pm-icon-btn", onClick: copy, title: copyState === "ok" ? t("copied") : t("copy"), "aria-label": copyState === "ok" ? t("copied") : t("copy"), children: h(IconCopyOutlineRegular, { size: 16 }) }),
|
|
995
|
+
] }) : null,
|
|
996
|
+
installState === "failed" ? h("div", { className: "pm-install-status", title: installError, children: `${t("install.failed")}: ${installError}` }) : null,
|
|
997
|
+
installState === "invalid" ? h("div", { className: "pm-install-status", title: installError, children: `${t("install.notBundle")}: ${installError}` }) : null,
|
|
998
|
+
h("span", { role: "status", "aria-live": "polite", className: "pm-sr-only", children: copyState === "ok" ? t("copied") : copyState === "error" ? t("copy.failed") : installTitle }),
|
|
999
|
+
] });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function CompactFilter({ id, icon, label, value, onChange, items, active = false }) {
|
|
1003
|
+
const [open, setOpen] = react.useState(false);
|
|
1004
|
+
const selectedId = String(value);
|
|
1005
|
+
const selected = items.find((item) => String(item.id) === selectedId) ?? items[0];
|
|
1006
|
+
return h(Menu, {
|
|
1007
|
+
open,
|
|
1008
|
+
compact: true,
|
|
1009
|
+
align: "start",
|
|
1010
|
+
portal: true,
|
|
1011
|
+
selectedId,
|
|
1012
|
+
items,
|
|
1013
|
+
onClose: () => setOpen(false),
|
|
1014
|
+
onSelect: (next) => { setOpen(false); onChange(next); },
|
|
1015
|
+
anchor: hs("button", {
|
|
1016
|
+
id,
|
|
1017
|
+
type: "button",
|
|
1018
|
+
className: "pm-compact-filter",
|
|
1019
|
+
"data-active": active,
|
|
1020
|
+
title: label,
|
|
1021
|
+
"aria-label": label,
|
|
1022
|
+
"aria-haspopup": "menu",
|
|
1023
|
+
"aria-expanded": open,
|
|
1024
|
+
onClick: () => setOpen((current) => !current),
|
|
1025
|
+
children: [
|
|
1026
|
+
h("span", { className: "pm-compact-filter-icon", children: icon }),
|
|
1027
|
+
h("span", { className: "pm-compact-filter-label", children: selected?.label ?? selectedId }),
|
|
1028
|
+
h(IconChevronDownOutlineRegular, { size: 12 }),
|
|
1029
|
+
],
|
|
1030
|
+
}),
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function BrowseView({ t, state, query, setQuery, refresh, sorts, setSorts, stableOnly, setStableOnly, freshnessDays, setFreshnessDays, tag, setTag, dshMetadata, setDshMetadata, page, setPage, pageSize, setPageSize }) {
|
|
1035
|
+
const plugins = state.data?.plugins ?? [];
|
|
1036
|
+
const total = state.data?.total ?? 0;
|
|
1037
|
+
const pageCount = state.data?.pageCount ?? 1;
|
|
1038
|
+
const currentPage = state.data?.page ?? page;
|
|
1039
|
+
const [starEvidence, setStarEvidence] = react.useState({});
|
|
1040
|
+
const starSignature = plugins.map((plugin) => `${repositoryEvidenceKey(plugin.identity?.repository)}:${plugin.evidence?.stars ?? ""}`).join("|");
|
|
1041
|
+
react.useEffect(() => {
|
|
1042
|
+
if (state.loading || !plugins.length || !connection) return undefined;
|
|
1043
|
+
const repositories = [...new Set(plugins
|
|
1044
|
+
.filter((plugin) => plugin.evidence?.stars === undefined)
|
|
1045
|
+
.map((plugin) => plugin.identity?.repository)
|
|
1046
|
+
.filter((repository) => repositoryEvidenceKey(repository).startsWith("https://github.com/"))
|
|
1047
|
+
)].slice(0, 20);
|
|
1048
|
+
if (!repositories.length) return undefined;
|
|
1049
|
+
const controller = new AbortController();
|
|
1050
|
+
rpc("stars", { repositories }, controller.signal).then((rows) => {
|
|
1051
|
+
if (!Array.isArray(rows)) return;
|
|
1052
|
+
setStarEvidence((current) => {
|
|
1053
|
+
const next = { ...current };
|
|
1054
|
+
for (const row of rows) {
|
|
1055
|
+
const key = repositoryEvidenceKey(row?.repository);
|
|
1056
|
+
if (!key || typeof row?.stars !== "number") continue;
|
|
1057
|
+
next[key] = { stars: row.stars, repositoryUpdatedAt: row.repositoryUpdatedAt };
|
|
1058
|
+
}
|
|
1059
|
+
return next;
|
|
1060
|
+
});
|
|
1061
|
+
}, () => {});
|
|
1062
|
+
return () => controller.abort();
|
|
1063
|
+
}, [starSignature]);
|
|
1064
|
+
const pluginWithAsyncStars = (plugin) => {
|
|
1065
|
+
if (plugin.evidence?.stars !== undefined) return plugin;
|
|
1066
|
+
const evidence = starEvidence[repositoryEvidenceKey(plugin.identity?.repository)];
|
|
1067
|
+
return evidence ? { ...plugin, evidence: { ...(plugin.evidence ?? {}), ...evidence } } : plugin;
|
|
1068
|
+
};
|
|
1069
|
+
const sourceRows = Array.isArray(state.data?.sources) ? state.data.sources : [];
|
|
1070
|
+
const failedSources = sourceRows.filter((row) => row?.health?.ok === false);
|
|
1071
|
+
const hasEnabledSources = sourceRows.length > 0;
|
|
1072
|
+
const allSourcesFailed = hasEnabledSources && failedSources.length === sourceRows.length;
|
|
1073
|
+
const changeQuery = (value) => { setPage(1); setQuery(value); };
|
|
1074
|
+
const changeFreshnessDays = (value) => { setPage(1); setFreshnessDays(Number(value)); };
|
|
1075
|
+
const changeTag = (value) => { setPage(1); setTag(value); };
|
|
1076
|
+
const changeDshMetadata = (value) => { setPage(1); setDshMetadata(value); };
|
|
1077
|
+
const changePageSize = (value) => { setPage(1); setPageSize(Number(value)); };
|
|
1078
|
+
const cycleSort = (key) => {
|
|
1079
|
+
setPage(1);
|
|
1080
|
+
setSorts((current) => {
|
|
1081
|
+
const index = current.findIndex((item) => item.key === key);
|
|
1082
|
+
if (index < 0) return [...current, { key, direction: "desc" }];
|
|
1083
|
+
if (current[index].direction === "desc") return current.map((item, at) => at === index ? { ...item, direction: "asc" } : item);
|
|
1084
|
+
return current.filter((_, at) => at !== index);
|
|
1085
|
+
});
|
|
1086
|
+
};
|
|
1087
|
+
const topPager = h(Pagination, { page: currentPage, pageCount, pageSize, setPage, setPageSize: changePageSize, disabled: state.loading, t });
|
|
1088
|
+
const bottomPager = h(Pagination, { page: currentPage, pageCount, pageSize, setPage, setPageSize: changePageSize, disabled: state.loading, t, showPageSize: false });
|
|
1089
|
+
const controls = hs("div", { className: "pm-controls-row", children: [
|
|
1090
|
+
h("button", { type: "button", className: "pm-filter-toggle", "data-active": stableOnly, title: t("browse.stableOnly"), "aria-label": t("browse.stableOnly"), disabled: state.loading, onClick: () => { setPage(1); setStableOnly(!stableOnly); }, children: h(IconShieldOutlineRegular, { size: 14 }) }),
|
|
1091
|
+
h(CompactFilter, { id: "pm-freshness", icon: h(IconClockOutlineRegular, { size: 14 }), label: t("browse.freshness"), value: freshnessDays, active: freshnessDays > 0, onChange: changeFreshnessDays, items: [
|
|
1092
|
+
{ id: "0", label: t("browse.freshness.any") },
|
|
1093
|
+
{ id: "30", label: "30d" },
|
|
1094
|
+
{ id: "90", label: "90d" },
|
|
1095
|
+
{ id: "365", label: "1y" },
|
|
1096
|
+
] }),
|
|
1097
|
+
h(CompactFilter, { id: "pm-category", icon: h(IconChecklistOutlineRegular, { size: 14 }), label: t("browse.category"), value: tag, active: Boolean(tag), onChange: changeTag, items: [
|
|
1098
|
+
{ id: "", label: t("browse.category.any") },
|
|
1099
|
+
...["ui","theme","provider","workflow","integration","tool","automation","schedule","scheduler","skill","bundle","desktop"].map((value) => ({ id: value, label: value })),
|
|
1100
|
+
] }),
|
|
1101
|
+
h(CompactFilter, { id: "pm-dsh-metadata", icon: h(IconCodeOutlineRegular, { size: 14 }), label: t("browse.dshMetadata"), value: dshMetadata, active: dshMetadata !== "any", onChange: changeDshMetadata, items: [
|
|
1102
|
+
{ id: "any", label: "DSH: any" },
|
|
1103
|
+
{ id: "declared", label: "DSH: declared" },
|
|
1104
|
+
{ id: "unknown", label: "DSH: unknown" },
|
|
1105
|
+
] }),
|
|
1106
|
+
hs("div", { className: "pm-sort-row", role: "group", "aria-label": t("browse.sort"), children: [
|
|
1107
|
+
h(SortCriterion, { criterion: "stars", icon: "★", label: t("browse.sort.stars"), sorts, onCycle: cycleSort, t }),
|
|
1108
|
+
h(SortCriterion, { criterion: "downloads", icon: h(IconDownloadOutlineRegular, { size: 14 }), label: t("browse.sort.downloads"), sorts, onCycle: cycleSort, t }),
|
|
1109
|
+
h(SortCriterion, { criterion: "freshness", icon: h(IconClockOutlineRegular, { size: 14 }), label: t("browse.sort.freshness"), sorts, onCycle: cycleSort, t }),
|
|
1110
|
+
h(SortCriterion, { criterion: "name", icon: "A", label: t("browse.sort.name"), sorts, onCycle: cycleSort, t }),
|
|
1111
|
+
] }),
|
|
1112
|
+
] });
|
|
1113
|
+
|
|
1114
|
+
let emptyHint = t("browse.emptyHint");
|
|
1115
|
+
if (state.data && !hasEnabledSources) emptyHint = t("browse.noSources");
|
|
1116
|
+
else if (allSourcesFailed) emptyHint = t("browse.sourcesUnavailable");
|
|
1117
|
+
|
|
1118
|
+
return hs("section", { className: "pm-browse-view", children: [
|
|
1119
|
+
hs("form", { className: "pm-toolbar pm-browse-toolbar", onSubmit: (event) => { event.preventDefault(); refresh(); }, children: [
|
|
1120
|
+
hs("div", { className: "pm-search", children: [h(SearchIcon, {}), h("input", { id: "pm-search", type: "search", value: query, onChange: (event) => changeQuery(event.target.value), placeholder: t("browse.search"), "aria-label": t("browse.search") })] }),
|
|
1121
|
+
h("button", { type: "submit", className: "pm-refresh-icon pm-browse-refresh", title: t("browse.refresh"), "aria-label": t("browse.refresh"), children: h(RefreshIcon, { className: state.loading ? "pm-spin" : undefined }) }),
|
|
1122
|
+
] }),
|
|
1123
|
+
state.loading ? hs("div", { className: "pm-loading-strip", role: "status", "aria-live": "polite", children: [
|
|
1124
|
+
h("span", { className: "pm-loading-spinner", children: h(IconRefreshOutlineRegular, { size: 13, className: "pm-spin" }) }),
|
|
1125
|
+
h("span", { children: state.data ? t("browse.updating") : t("browse.loading") }),
|
|
1126
|
+
h("span", { className: "pm-loading-track", "aria-hidden": true, children: h("span", { className: "pm-loading-bar" }) }),
|
|
1127
|
+
] }) : null,
|
|
1128
|
+
state.error ? h("p", { className: "pm-notice pm-notice-error pm-browse-state", role: "alert", children: `${t("browse.error")}: ${state.error}` }) : null,
|
|
1129
|
+
state.data ? hs("div", { className: "pm-browse-content", children: [
|
|
1130
|
+
hs("div", { className: "pm-browse-topline", children: [
|
|
1131
|
+
h("p", { className: "pm-count", children: t("browse.results", { count: total }) }),
|
|
1132
|
+
topPager,
|
|
1133
|
+
] }),
|
|
1134
|
+
controls,
|
|
1135
|
+
allSourcesFailed ? failedSources.map((row) => h("p", {
|
|
1136
|
+
className: "pm-notice pm-notice-error pm-browse-state",
|
|
1137
|
+
children: `${row.name ?? row.id ?? row.source?.name ?? row.source?.id ?? "source"}: ${row.health?.error ?? t("source.error")}`,
|
|
1138
|
+
}, row.source?.id ?? row.source?.name)) : null,
|
|
1139
|
+
!state.loading && !state.error && plugins.length === 0 ? h(Empty, { title: t("browse.empty"), hint: emptyHint }) : null,
|
|
1140
|
+
plugins.length ? h("div", { className: "pm-results", "data-updating": state.loading, children: plugins.map((plugin) => h(PluginCard, { plugin: pluginWithAsyncStars(plugin), t }, plugin.identity?.package || plugin.identity?.repository || plugin.identity?.fallback)) }) : null,
|
|
1141
|
+
plugins.length ? h("div", { className: "pm-browse-bottomline", children: bottomPager }) : null,
|
|
1142
|
+
] }) : null,
|
|
1143
|
+
] });
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function PluginSourcesSection({ t }) {
|
|
1147
|
+
const bound = configForm;
|
|
1148
|
+
const [snapshot, setSnapshot] = react.useState(() => bound?.getSnapshot?.());
|
|
1149
|
+
const [tab, setTab] = react.useState("sources");
|
|
1150
|
+
const [query, setQuery] = react.useState("");
|
|
1151
|
+
const [busy, setBusy] = react.useState(false);
|
|
1152
|
+
const [healthState, setHealthState] = react.useState({ loading: false });
|
|
1153
|
+
const [browseState, setBrowseState] = react.useState({ loading: false });
|
|
1154
|
+
const [browseRevision, setBrowseRevision] = react.useState(0);
|
|
1155
|
+
const [sorts, setSorts] = react.useState([]);
|
|
1156
|
+
const [stableOnly, setStableOnly] = react.useState(false);
|
|
1157
|
+
const [freshnessDays, setFreshnessDays] = react.useState(0);
|
|
1158
|
+
const [tag, setTag] = react.useState("");
|
|
1159
|
+
const [dshMetadata, setDshMetadata] = react.useState("any");
|
|
1160
|
+
const [page, setPage] = react.useState(1);
|
|
1161
|
+
const [pageSize, setPageSize] = react.useState(20);
|
|
1162
|
+
const [defaultsMigrating, setDefaultsMigrating] = react.useState(false);
|
|
1163
|
+
const [healthRevision, setHealthRevision] = react.useState(0);
|
|
1164
|
+
const [connectionRevision, setConnectionRevision] = react.useState(0);
|
|
1165
|
+
react.useEffect(() => bound?.subscribe?.(() => setSnapshot(bound.getSnapshot())), [bound]);
|
|
1166
|
+
react.useEffect(() => {
|
|
1167
|
+
const listener = () => setConnectionRevision((value) => value + 1);
|
|
1168
|
+
connectionResetListeners.add(listener);
|
|
1169
|
+
return () => connectionResetListeners.delete(listener);
|
|
1170
|
+
}, []);
|
|
1171
|
+
const sources = Array.isArray(snapshot?.value?.sources) ? snapshot.value.sources : [];
|
|
1172
|
+
const signature = JSON.stringify(sources);
|
|
1173
|
+
const writable = snapshot?.writable === true;
|
|
1174
|
+
const sourceDefaultsVersion = Number(snapshot?.value?.sourceDefaultsVersion ?? 0);
|
|
1175
|
+
const saveSources = async (next) => { if (!bound || !writable) return; setBusy(true); try { await bound.set("sources", next); } finally { setBusy(false); } };
|
|
1176
|
+
|
|
1177
|
+
react.useEffect(() => {
|
|
1178
|
+
if (!bound || !writable || defaultsMigrating || sourceDefaultsVersion >= 2) return undefined;
|
|
1179
|
+
setDefaultsMigrating(true);
|
|
1180
|
+
const next = [...sources];
|
|
1181
|
+
if (!next.some((source) => source.type === "npm")) next.push({ id: "npm", name: "npm", type: "npm", enabled: true });
|
|
1182
|
+
if (!next.some((source) => source.type === "github")) next.push({ id: "github", name: "GitHub", type: "github", enabled: true });
|
|
1183
|
+
Promise.resolve(next.length === sources.length ? undefined : bound.set("sources", next))
|
|
1184
|
+
.then(() => bound.set("sourceDefaultsVersion", 2))
|
|
1185
|
+
.finally(() => setDefaultsMigrating(false));
|
|
1186
|
+
return undefined;
|
|
1187
|
+
}, [bound, writable, defaultsMigrating, sourceDefaultsVersion, signature]);
|
|
1188
|
+
|
|
1189
|
+
react.useEffect(() => {
|
|
1190
|
+
if (tab !== "sources" || !connection) return undefined;
|
|
1191
|
+
const controller = new AbortController();
|
|
1192
|
+
setHealthState({ loading: true });
|
|
1193
|
+
rpc("browse", { healthOnly: true }, controller.signal).then((data) => setHealthState({ loading: false, data }), (error) => { if (error?.name !== "AbortError") setHealthState({ loading: false, error: String(error?.message ?? error) }); });
|
|
1194
|
+
return () => controller.abort();
|
|
1195
|
+
}, [tab, signature, healthRevision, connectionRevision]);
|
|
1196
|
+
|
|
1197
|
+
react.useEffect(() => {
|
|
1198
|
+
if (tab !== "browse" || !connection) return undefined;
|
|
1199
|
+
const controller = new AbortController();
|
|
1200
|
+
const timer = setTimeout(() => {
|
|
1201
|
+
setBrowseState((current) => ({ ...current, loading: true, error: undefined }));
|
|
1202
|
+
rpc("browse", { query, page, pageSize, sorts, stableOnly, freshnessDays, tag, dshMetadata, refreshRevision: browseRevision }, controller.signal).then((data) => {
|
|
1203
|
+
if (data?.page && data.page !== page) setPage(data.page);
|
|
1204
|
+
setBrowseState({ loading: false, data });
|
|
1205
|
+
}, (error) => { if (error?.name !== "AbortError") setBrowseState({ loading: false, error: String(error?.message ?? error) }); });
|
|
1206
|
+
}, 220);
|
|
1207
|
+
return () => { clearTimeout(timer); controller.abort(); };
|
|
1208
|
+
}, [tab, query, page, pageSize, JSON.stringify(sorts), stableOnly, freshnessDays, tag, dshMetadata, signature, browseRevision, connectionRevision]);
|
|
1209
|
+
|
|
1210
|
+
if (!snapshot || snapshot.status === "unavailable") return h("div", { className: "pm-root", children: h("div", { className: "pm-shell", children: h(Empty, { title: t("unavailable"), hint: "registry-aggregator" }) }) });
|
|
1211
|
+
const selectTab = (next) => { setTab(next); queueMicrotask(() => window.document.getElementById?.(`pm-tab-${next}`)?.focus?.()); };
|
|
1212
|
+
const tabKey = (event) => { if (event.key === "ArrowRight" || event.key === "ArrowLeft") { event.preventDefault(); selectTab(tab === "sources" ? "browse" : "sources"); } };
|
|
1213
|
+
return h("div", { className: "pm-root", children: hs("section", { className: "pm-shell", "aria-labelledby": "pm-title", children: [
|
|
1214
|
+
h("p", { className: "pm-kicker", children: t("kicker") }), h("h2", { id: "pm-title", className: "pm-title", children: t("title") }), h("p", { className: "pm-lead", children: t("lead") }),
|
|
1215
|
+
hs("div", { className: "pm-tabs", role: "tablist", "aria-label": t("title"), children: [h("button", { id: "pm-tab-sources", type: "button", className: "pm-tab", role: "tab", tabIndex: tab === "sources" ? 0 : -1, "aria-controls": "pm-panel-sources", "aria-selected": tab === "sources", "data-active": tab === "sources", onKeyDown: tabKey, onClick: () => selectTab("sources"), children: hs("span", { className: "pm-tab-content", children: [h(DatabaseIcon, {}), h("span", { children: t("tab.sources") })] }) }), h("button", { id: "pm-tab-browse", type: "button", className: "pm-tab", role: "tab", tabIndex: tab === "browse" ? 0 : -1, "aria-controls": "pm-panel-browse", "aria-selected": tab === "browse", "data-active": tab === "browse", onKeyDown: tabKey, onClick: () => selectTab("browse"), children: hs("span", { className: "pm-tab-content", children: [h(GridIcon, {}), h("span", { children: t("tab.browse") })] }) })] }),
|
|
1216
|
+
h("div", { id: `pm-panel-${tab}`, role: "tabpanel", "aria-labelledby": `pm-tab-${tab}`, children: tab === "sources" ? h(SourcesView, { t, sources, writable, saveSources, busy, healthState, refreshHealth: () => setHealthRevision((value) => value + 1) }) : h(BrowseView, { t, state: browseState, query, setQuery, refresh: () => setBrowseRevision((value) => value + 1), sorts, setSorts, stableOnly, setStableOnly, freshnessDays, setFreshnessDays, tag, setTag, dshMetadata, setDshMetadata, page, setPage, pageSize, setPageSize }) }),
|
|
1217
|
+
] }) });
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
function RegistryAggregatorActivation({ onDismiss, onOpenDetails, t }) {
|
|
1221
|
+
return h(Modal, {
|
|
1222
|
+
open: true,
|
|
1223
|
+
title: t("activation.title"),
|
|
1224
|
+
closeLabel: t("activation.later"),
|
|
1225
|
+
onClose: onDismiss,
|
|
1226
|
+
children: hs("div", { style: { display: "grid", gap: 14 }, children: [
|
|
1227
|
+
h("p", { style: { margin: 0, color: "var(--dsw-alias-label-secondary)", lineHeight: 1.55 }, children: t("activation.body") }),
|
|
1228
|
+
hs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 8 }, children: [
|
|
1229
|
+
h(Button, { onClick: onDismiss, children: t("activation.later") }),
|
|
1230
|
+
h(Button, { onClick: onOpenDetails, children: t("activation.open") }),
|
|
1231
|
+
] }),
|
|
1232
|
+
] }),
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
const inject = ["slots", "locale", "configForms", "connection", "remote", "remote.pluginManager"];
|
|
1237
|
+
function apply(ctx) {
|
|
1238
|
+
configForm = ctx.configForms?.get?.(NS);
|
|
1239
|
+
connection = ctx.connection;
|
|
1240
|
+
remote = ctx.remote;
|
|
1241
|
+
ctx.on?.("connection/reset", () => {
|
|
1242
|
+
for (const listener of [...connectionResetListeners]) listener();
|
|
1243
|
+
});
|
|
1244
|
+
ctx.effect(injectStyle, "plugin-sources: styles");
|
|
1245
|
+
ctx.effect(() => ctx.locale.register(NS, { en, zh }), "plugin-sources: locale");
|
|
1246
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({ name: "plugins.bundle.config", key: "@stolyarovmn/dsh-ui-registry-aggregator", locale: NS }, PluginSourcesSection));
|
|
1247
|
+
ctx.slots.inject("plugins.bundle.activation", () => ctx.slots.register({ name: "plugins.bundle.activation", key: "@stolyarovmn/dsh-ui-registry-aggregator", locale: NS }, RegistryAggregatorActivation));
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
exports.apply = apply;
|
|
1251
|
+
exports.inject = inject;
|
|
1252
|
+
return module.exports;
|
|
1253
|
+
}
|
|
1254
|
+
});
|