@noob-stupid/dsh-plugin-console 0.5.87 → 0.5.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -4
- package/lib/client.js +1109 -78
- package/lib/server/domain/framework.js +7 -0
- package/lib/server/domain/plugin-annotations.js +569 -0
- package/lib/server/domain/update-batch.js +181 -0
- package/lib/server/routes/annotations.js +82 -0
- package/lib/server/routes/framework.js +137 -120
- package/lib/server/routes/index.js +12 -0
- package/lib/server/routes/update-batch.js +97 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
2
|
id: "@noob-stupid/dsh-plugin-console",
|
|
3
3
|
factory: (require) => {
|
|
4
4
|
var module = { exports: {} };
|
|
@@ -6,7 +6,7 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
//#region styles
|
|
9
|
-
const css = ".pc_section{width:100%;max-width:760px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:14px;display:flex}.pc_section h3{margin:0;font-size:13px;font-weight:600;line-height:20px}.pc_message{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px;margin:0}.pc_messageRow{display:flex;align-items:center;gap:10px}.pc_spinner{width:14px;height:14px;border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-state-business-primary);border-radius:50%;animation:pcspin 1s linear infinite;flex:none}@keyframes pcspin{to{transform:rotate(360deg)}}.pc_message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.pc_message[data-warn=true]{color:var(--dsw-alias-state-warning-primary)}.pc_list{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none;display:grid}.pc_row{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;min-width:0;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_rowTop{align-items:center;gap:8px;display:flex}.pc_name{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.pc_tag{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:16px;flex:none}.pc_tag[data-enabled=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_tag[data-user=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_tag[data-pending=true]{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_status[data-pending=true]{color:var(--dsw-alias-state-warning-primary)}.pc_toggle[data-pending=true]{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_tag[data-skill=true]{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_tag[data-system=true]{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-2)}.pc_tag[data-disabled=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_tag[data-suite=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_tag[data-error=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_tabGroup{display:inline-flex;gap:4px;align-items:center}.pc_meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;gap:6px;display:flex;align-items:center}.pc_phase[data-phase=failed]{color:var(--dsw-alias-state-error-primary)}.pc_phase[data-phase=active]{color:var(--dsw-alias-state-success-primary)}.pc_toggle{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:4px 12px;align-self:flex-start}.pc_toggle:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.pc_toggle:disabled{opacity:.5;cursor:default}.pc_search{gap:8px;display:flex}.pc_search input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;flex:1;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;outline:none;padding:0 12px;font-size:13px}.pc_search input:focus-visible{border-color:var(--dsw-alias-state-business-primary)}.pc_search button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:0 14px;font-size:13px}.pc_search button:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_market{flex-direction:column;gap:10px;margin:0;padding:0;list-style:none;display:flex}.pc_item{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;min-width:0;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_itemTop{align-items:center;gap:8px;display:flex}.pc_itemTop a{color:var(--dsw-alias-state-business-primary);font-size:13px;font-weight:600;text-decoration:none;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pc_stars{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none}.pc_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}.pc_item button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:3px 10px;align-self:flex-start;font-size:12px}.pc_item button:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.pc_item button:disabled{opacity:.5;cursor:default}.pc_detail{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_status{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}.pc_note{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0}.pc_note[data-truncated=true]{color:var(--dsw-alias-state-warning-primary)}.pc_marketHead{display:flex;align-items:center;justify-content:space-between;gap:10px}.pc_descWrap{flex-direction:column;display:flex}.pc_installedHead{display:flex;align-items:center;gap:10px}.pc_backTopWrap{position:fixed;top:56px;right:20px;z-index:100;display:flex;flex-direction:column;gap:8px}.pc_backTop{width:34px;height:34px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:rgba(22,27,34,.72);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;backdrop-filter:blur(4px);opacity:.7}.pc_backTop:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary)}.pc_descTopbar{display:flex;justify-content:flex-end;margin:2px 0}.pc_ghpill{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 10px;font-size:11px;line-height:18px}.pc_ghpill[data-on=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_extraBtn{opacity:.75;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:4px 10px;font-size:12px;line-height:16px;flex:none;transition:opacity .15s ease,border-color 1.5s ease,color 1.5s ease}.pc_extraBtn:hover{opacity:1}.pc_extraBtn[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_extraBtn[data-flash=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_view{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:2px 10px;font-size:12px;line-height:18px;flex:none}.pc_floatPanel{position:fixed;left:20px;top:50%;transform:translateY(-50%);width:340px;max-height:75vh;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:12px;box-shadow:0 8px 24px rgba(0,0,0,.18);z-index:50;flex-direction:column;gap:8px;display:flex}.pc_refresh{position:sticky;bottom:14px;align-self:flex-end;margin-top:8px;z-index:60;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_refresh:hover{opacity:.9}.pc_date{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;flex:none}.pc_searchInner{position:relative;flex:1;display:flex;align-items:center;min-width:0}.pc_searchInner input{padding-right:32px;width:100%}.pc_starBtn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:13px;line-height:1;padding:4px 8px;border-radius:6px;opacity:.75;transition:opacity .15s ease;flex:none}.pc_starBtn:hover{opacity:1}.pc_starBtn[data-active=true]{opacity:1;color:var(--dsw-alias-state-business-primary)}.pc_restartBtn{position:fixed;right:20px;top:50%;transform:translateY(-50%);z-index:60;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_restartBtn:hover{opacity:.9}.pc_relaunchFloat{position:fixed;left:20px;top:50%;transform:translateY(-50%);z-index:70;opacity:.85;border:1px solid var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_relaunchFloat:hover{opacity:1}.pc_aiToggle{position:fixed;right:20px;top:20px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_aiToggle:hover{opacity:1}.pc_aiToggle[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_sourcesFloat{position:fixed;right:20px;top:104px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_sourcesFloat:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_modeFloat{position:fixed;right:20px;top:204px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_modeFloat:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_modeFloat[data-mode=true]{opacity:1;border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_trashBtn{border:0;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:12px;line-height:16px;padding:2px 6px;flex:none;display:flex;align-items:center;border-radius:6px;transition:color .15s ease}.pc_trashBtn:hover{color:var(--dsw-alias-state-error-primary)}.pc_consentRemember{display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);cursor:pointer}.pc_modalBackdrop{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center}.pc_modalCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;padding:16px 18px;width:min(760px,calc(100vw - 64px));max-height:calc(100vh - 96px);overflow-y:auto;overscroll-behavior:contain;flex-direction:column;gap:10px;display:flex;box-shadow:0 12px 32px rgba(0,0,0,.25)}.pc_modalCard::-webkit-scrollbar{width:8px}.pc_modalCard::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2);border-radius:4px}.pc_srcUrl{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%}.pc_accWrap{display:flex;flex-direction:column;gap:8px}.pc_acc{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-2)}.pc_accHead{width:100%;text-align:left;background:transparent;border:0;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;font-weight:600;line-height:20px;padding:8px 12px;cursor:pointer;display:flex;align-items:center;gap:6px}.pc_accHead:hover{background:var(--dsw-alias-bg-layer-3)}.pc_accHead[data-open=true]{border-bottom:1px solid var(--dsw-alias-border-l2)}.pc_accBody{padding:10px 12px;display:flex;flex-direction:column;gap:8px}.pc_ghpill{background:0 0;cursor:pointer}.pc_ghwrap{position:relative;display:inline-flex}.pc_sourceMenu{position:absolute;right:0;top:calc(100% + 6px);z-index:90;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:4px;flex-direction:column;display:flex;min-width:120px;box-shadow:0 8px 24px rgba(0,0,0,.18)}.pc_sourceOpt{border:0;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:6px;padding:6px 10px;text-align:left;font-size:12px;line-height:16px}.pc_sourceOpt[data-active=true]{color:var(--dsw-alias-state-business-primary);font-weight:600}.pc_sourceOpt:hover{background:var(--dsw-alias-bg-layer-3)}.pc_aiEmpower{position:fixed;right:20px;top:48px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_aiEmpower:hover{opacity:.9}.pc_aiEmpower[data-busy=true]{opacity:1;border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_aiEmpower[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_compCard{position:fixed;left:8px;top:20px;width:320px;max-height:70vh;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:12px;box-shadow:0 8px 24px rgba(0,0,0,.18);z-index:45;flex-direction:column;gap:8px;display:flex}.pc_compCard .pc_rowTop{flex-wrap:wrap}.pc_compCard .pc_toggle{padding:3px 8px;font-size:11px}.pc_compCard[data-hidden=true]{display:none}.pc_compDrop{background:rgba(24,28,34,.92);backdrop-filter:blur(6px);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px;box-shadow:0 8px 24px rgba(0,0,0,.35);flex-direction:column;display:flex;gap:2px}.pc_compDropRow{border:0;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:6px;padding:6px 8px;text-align:left;font-size:12px;line-height:16px;flex-direction:column;display:flex;gap:2px}.pc_compDropRow:hover{background:var(--dsw-alias-bg-layer-2)}.pc_compDropUrl{color:var(--dsw-alias-label-tertiary);font-size:11px;word-break:break-all}.pc_compToggle{position:fixed;left:336px;top:20px;z-index:46;opacity:.55;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:rgba(22,27,34,.72);font:inherit;cursor:pointer;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;line-height:1;backdrop-filter:blur(4px);transition:opacity .15s ease}.pc_compToggle:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary)}.pc_toolbarMain{position:fixed;right:70px;top:max(88px,21vh);z-index:71;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:border-color .15s ease}.pc_toolbarMain:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_toolbarMain[data-active=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_toolbarGroup{position:fixed;right:70px;top:calc(max(88px,21vh) + 36px);z-index:70;flex-direction:column;gap:8px;display:flex;transform-origin:top right;animation:pcDrawerIn .3s cubic-bezier(.34,1.56,.64,1)}@media (max-width:1160px){.pc_toolbarMain{top:max(150px,30vh)}.pc_toolbarGroup{top:calc(max(150px,30vh) + 36px)}.pc_aiToggle{top:104px}.pc_aiEmpower{top:132px}.pc_sourcesFloat{top:188px}.pc_modeFloat{top:288px}}@keyframes pcDrawerIn{from{opacity:0;transform:translateX(28px) scale(.94)}to{opacity:1;transform:translateX(0) scale(1)}}.pc_toolItem{opacity:.85;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_toolItem:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary)}.pc_toolItem[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_toolItem[data-mode=true]{opacity:1;border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_switch{width:34px;height:18px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);position:relative;cursor:pointer;flex:none;transition:background .15s ease,border-color .15s ease}.pc_switch:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_switch[data-on=true]{background:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.pc_switchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:left .15s ease}.pc_switch[data-on=true] .pc_switchKnob{left:18px}.pc_aiSpinner{width:12px;height:12px;border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-state-business-primary);border-radius:50%;animation:pcspin 1s linear infinite;flex:none}.pc_aiSpinBtn{position:fixed;right:152px;top:20px;z-index:71;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px}.pc_aiSpinBtn:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_tag[data-bad=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_progress{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;margin:0;font-variant-numeric:tabular-nums}.pc_consentCard{border:2px solid var(--dsw-alias-state-warning-primary);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:12px 14px;flex-direction:column;gap:8px;display:flex;box-shadow:0 0 0 3px rgba(255,176,32,.10)}.pc_consentCountdown{margin-left:auto;flex:none;font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dsw-alias-state-warning-primary)}.pc_consentApprove{border:1px solid var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:0 0;font:inherit;font-weight:600;cursor:pointer;border-radius:6px;padding:4px 12px;align-self:flex-start}.pc_consentApprove:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_starFoot{margin-top:auto;position:sticky;bottom:0;justify-content:flex-end;display:flex;padding:6px 0 2px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 16px 0 var(--dsw-alias-bg-layer-1)}.pc_starLink{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;line-height:16px;text-decoration:none;display:inline-flex;flex:none;border-radius:4px}.pc_starLink svg{flex:none;display:block}.pc_starLink:hover{color:var(--dsw-alias-state-business-primary);text-decoration:underline;text-underline-offset:3px}.pc_starLink:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}";
|
|
9
|
+
const css = ".pc_section{width:100%;max-width:760px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:14px;display:flex}.pc_section h3{margin:0;font-size:13px;font-weight:600;line-height:20px}.pc_message{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px;margin:0}.pc_messageRow{display:flex;align-items:center;gap:10px}.pc_spinner{width:14px;height:14px;border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-state-business-primary);border-radius:50%;animation:pcspin 1s linear infinite;flex:none}@keyframes pcspin{to{transform:rotate(360deg)}}.pc_message[data-error=true]{color:var(--dsw-alias-state-error-primary)}.pc_message[data-warn=true]{color:var(--dsw-alias-state-warning-primary)}.pc_list{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none;display:grid}.pc_row{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;min-width:0;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_rowTop{align-items:center;gap:8px;display:flex}.pc_name{font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.pc_tag{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:16px;flex:none}.pc_tag[data-enabled=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_tag[data-user=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_tag[data-pending=true]{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_status[data-pending=true]{color:var(--dsw-alias-state-warning-primary)}.pc_toggle[data-pending=true]{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_tag[data-skill=true]{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_tag[data-system=true]{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-layer-2)}.pc_tag[data-disabled=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_tag[data-suite=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_tag[data-error=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_tabGroup{display:inline-flex;gap:4px;align-items:center}.pc_meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;gap:6px;display:flex;align-items:center}.pc_phase[data-phase=failed]{color:var(--dsw-alias-state-error-primary)}.pc_phase[data-phase=active]{color:var(--dsw-alias-state-success-primary)}.pc_toggle{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:4px 12px;align-self:flex-start}.pc_toggle:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.pc_toggle:disabled{opacity:.5;cursor:default}.pc_search{gap:8px;display:flex}.pc_search input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;flex:1;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;outline:none;padding:0 12px;font-size:13px}.pc_search input:focus-visible{border-color:var(--dsw-alias-state-business-primary)}.pc_search button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:0 14px;font-size:13px}.pc_search button:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_market{flex-direction:column;gap:10px;margin:0;padding:0;list-style:none;display:flex}.pc_item{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;min-width:0;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_itemTop{align-items:center;gap:8px;display:flex}.pc_itemTop a{color:var(--dsw-alias-state-business-primary);font-size:13px;font-weight:600;text-decoration:none;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pc_stars{color:var(--dsw-alias-label-tertiary);font-size:11px;flex:none}.pc_desc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}.pc_item button{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:3px 10px;align-self:flex-start;font-size:12px}.pc_item button:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.pc_item button:disabled{opacity:.5;cursor:default}.pc_detail{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:10px 12px;flex-direction:column;gap:6px;display:flex}.pc_status{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;margin:0}.pc_note{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;margin:0}.pc_note[data-truncated=true]{color:var(--dsw-alias-state-warning-primary)}.pc_marketHead{display:flex;align-items:center;justify-content:space-between;gap:10px}.pc_descWrap{flex-direction:column;display:flex}.pc_installedHead{display:flex;align-items:center;gap:10px}.pc_iconBtn{width:26px;height:26px;padding:0;display:flex;align-items:center;justify-content:center;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:0 0;color:var(--dsw-alias-label-primary);cursor:pointer;flex:none}.pc_iconBtn:hover:not(:disabled){border-color:var(--dsw-alias-state-business-primary)}.pc_iconBtn:disabled{opacity:.5;cursor:default}.pc_iconBtn[data-state=done]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_iconBtn[data-state=active]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-layer-2)}.pc_badgeRow{display:flex;align-items:center;gap:4px;min-width:0;overflow:hidden;flex-wrap:nowrap;flex:0 1 auto;max-width:60%;height:20px;white-space:nowrap}.pc_badgeAll{display:flex;flex-wrap:wrap;gap:4px;margin-top:2px}.pc_tag[data-user-badge=true]{border-style:dashed;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-layer-2)}.pc_tag[data-more=true]{border-style:dotted;cursor:pointer;font:inherit;background:0 0}.pc_userNote{border-left:2px solid var(--dsw-alias-state-business-primary);padding-left:8px;display:flex;flex-direction:column;gap:4px;margin-top:2px}.pc_noteInput{width:100%;font:inherit;padding:4px 6px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);resize:vertical}.pc_annotRow{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.pc_backTopWrap{position:fixed;top:56px;right:20px;z-index:100;display:flex;flex-direction:column;gap:8px}.pc_backTop{width:34px;height:34px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:rgba(22,27,34,.72);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);cursor:pointer;backdrop-filter:blur(4px);opacity:.7}.pc_backTop:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary)}.pc_descTopbar{display:flex;justify-content:flex-end;margin:2px 0}.pc_ghpill{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 10px;font-size:11px;line-height:18px}.pc_ghpill[data-on=true]{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary)}.pc_extraBtn{opacity:.75;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:4px 10px;font-size:12px;line-height:16px;flex:none;transition:opacity .15s ease,border-color 1.5s ease,color 1.5s ease}.pc_extraBtn:hover{opacity:1}.pc_extraBtn[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_extraBtn[data-flash=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_view{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:0 0;font:inherit;cursor:pointer;border-radius:6px;padding:2px 10px;font-size:12px;line-height:18px;flex:none}.pc_floatPanel{position:fixed;left:20px;top:50%;transform:translateY(-50%);width:340px;max-height:75vh;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:12px;box-shadow:0 8px 24px rgba(0,0,0,.18);z-index:50;flex-direction:column;gap:8px;display:flex}.pc_refresh{position:sticky;bottom:14px;align-self:flex-end;margin-top:8px;z-index:60;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_refresh:hover{opacity:.9}.pc_date{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px;flex:none}.pc_searchInner{position:relative;flex:1;display:flex;align-items:center;min-width:0}.pc_searchInner input{padding-right:32px;width:100%}.pc_starBtn{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:13px;line-height:1;padding:4px 8px;border-radius:6px;opacity:.75;transition:opacity .15s ease;flex:none}.pc_starBtn:hover{opacity:1}.pc_starBtn[data-active=true]{opacity:1;color:var(--dsw-alias-state-business-primary)}.pc_restartBtn{position:fixed;right:20px;top:50%;transform:translateY(-50%);z-index:60;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_restartBtn:hover{opacity:.9}.pc_relaunchFloat{position:fixed;left:20px;top:50%;transform:translateY(-50%);z-index:70;opacity:.85;border:1px solid var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_relaunchFloat:hover{opacity:1}.pc_aiToggle{position:fixed;right:20px;top:20px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_aiToggle:hover{opacity:1}.pc_aiToggle[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_sourcesFloat{position:fixed;right:20px;top:104px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_sourcesFloat:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_modeFloat{position:fixed;right:20px;top:204px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_modeFloat:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_modeFloat[data-mode=true]{opacity:1;border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_trashBtn{border:0;background:0 0;color:var(--dsw-alias-label-tertiary);cursor:pointer;font-size:12px;line-height:16px;padding:2px 6px;flex:none;display:flex;align-items:center;border-radius:6px;transition:color .15s ease}.pc_trashBtn:hover{color:var(--dsw-alias-state-error-primary)}.pc_consentRemember{display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;color:var(--dsw-alias-label-secondary);cursor:pointer}.pc_modalBackdrop{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center}.pc_modalCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;padding:16px 18px;width:min(760px,calc(100vw - 64px));max-height:calc(100vh - 96px);overflow-y:auto;overscroll-behavior:contain;flex-direction:column;gap:10px;display:flex;box-shadow:0 12px 32px rgba(0,0,0,.25)}.pc_modalCard::-webkit-scrollbar{width:8px}.pc_modalCard::-webkit-scrollbar-thumb{background:var(--dsw-alias-border-l2);border-radius:4px}.pc_srcUrl{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;color:var(--dsw-alias-label-tertiary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%}.pc_accWrap{display:flex;flex-direction:column;gap:8px}.pc_acc{border:1px solid var(--dsw-alias-border-l2);border-radius:10px;overflow:hidden;background:var(--dsw-alias-bg-layer-2)}.pc_accHead{width:100%;text-align:left;background:transparent;border:0;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;font-weight:600;line-height:20px;padding:8px 12px;cursor:pointer;display:flex;align-items:center;gap:6px}.pc_accHead:hover{background:var(--dsw-alias-bg-layer-3)}.pc_accHead[data-open=true]{border-bottom:1px solid var(--dsw-alias-border-l2)}.pc_accBody{padding:10px 12px;display:flex;flex-direction:column;gap:8px}.pc_ghpill{background:0 0;cursor:pointer}.pc_ghwrap{position:relative;display:inline-flex}.pc_sourceMenu{position:absolute;right:0;top:calc(100% + 6px);z-index:90;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:4px;flex-direction:column;display:flex;min-width:120px;box-shadow:0 8px 24px rgba(0,0,0,.18)}.pc_sourceOpt{border:0;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:6px;padding:6px 10px;text-align:left;font-size:12px;line-height:16px}.pc_sourceOpt[data-active=true]{color:var(--dsw-alias-state-business-primary);font-weight:600}.pc_sourceOpt:hover{background:var(--dsw-alias-bg-layer-3)}.pc_aiEmpower{position:fixed;right:20px;top:48px;z-index:70;opacity:1;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_aiEmpower:hover{opacity:.9}.pc_aiEmpower[data-busy=true]{opacity:1;border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_aiEmpower[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_compCard{position:fixed;left:8px;top:20px;width:320px;max-height:70vh;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:10px;padding:12px;box-shadow:0 8px 24px rgba(0,0,0,.18);z-index:45;flex-direction:column;gap:8px;display:flex}.pc_compCard .pc_rowTop{flex-wrap:wrap}.pc_compCard .pc_toggle{padding:3px 8px;font-size:11px}.pc_compCard[data-hidden=true]{display:none}.pc_compDrop{background:rgba(24,28,34,.92);backdrop-filter:blur(6px);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:4px;box-shadow:0 8px 24px rgba(0,0,0,.35);flex-direction:column;display:flex;gap:2px}.pc_compDropRow{border:0;background:0 0;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;border-radius:6px;padding:6px 8px;text-align:left;font-size:12px;line-height:16px;flex-direction:column;display:flex;gap:2px}.pc_compDropRow:hover{background:var(--dsw-alias-bg-layer-2)}.pc_compDropUrl{color:var(--dsw-alias-label-tertiary);font-size:11px;word-break:break-all}.pc_compToggle{position:fixed;left:336px;top:20px;z-index:46;opacity:.55;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:rgba(22,27,34,.72);font:inherit;cursor:pointer;width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;line-height:1;backdrop-filter:blur(4px);transition:opacity .15s ease}.pc_compToggle:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary)}.pc_toolbarMain{position:fixed;right:70px;top:max(88px,21vh);z-index:71;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:border-color .15s ease}.pc_toolbarMain:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_toolbarMain[data-active=true]{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_toolbarGroup{position:fixed;right:70px;top:calc(max(88px,21vh) + 36px);z-index:70;flex-direction:column;gap:8px;display:flex;transform-origin:top right;animation:pcDrawerIn .3s cubic-bezier(.34,1.56,.64,1)}@media (max-width:1160px){.pc_toolbarMain{top:max(150px,30vh)}.pc_toolbarGroup{top:calc(max(150px,30vh) + 36px)}.pc_aiToggle{top:104px}.pc_aiEmpower{top:132px}.pc_sourcesFloat{top:188px}.pc_modeFloat{top:288px}}@keyframes pcDrawerIn{from{opacity:0;transform:translateX(28px) scale(.94)}to{opacity:1;transform:translateX(0) scale(1)}}.pc_toolItem{opacity:.85;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);font:inherit;cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px;transition:opacity .15s ease}.pc_toolItem:hover{opacity:1;border-color:var(--dsw-alias-state-business-primary)}.pc_toolItem[data-active=true]{opacity:1;border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_toolItem[data-mode=true]{opacity:1;border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary)}.pc_switch{width:34px;height:18px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);position:relative;cursor:pointer;flex:none;transition:background .15s ease,border-color .15s ease}.pc_switch:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_switch[data-on=true]{background:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.pc_switchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;transition:left .15s ease}.pc_switch[data-on=true] .pc_switchKnob{left:18px}.pc_aiSpinner{width:12px;height:12px;border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-state-business-primary);border-radius:50%;animation:pcspin 1s linear infinite;flex:none}.pc_aiSpinBtn{position:fixed;right:152px;top:20px;z-index:71;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);cursor:pointer;border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:6px;font-size:12px;line-height:16px}.pc_aiSpinBtn:hover{border-color:var(--dsw-alias-state-business-primary)}.pc_tag[data-bad=true]{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.pc_progress{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;margin:0;font-variant-numeric:tabular-nums}.pc_consentCard{border:2px solid var(--dsw-alias-state-warning-primary);background:var(--dsw-alias-bg-layer-2);border-radius:10px;padding:12px 14px;flex-direction:column;gap:8px;display:flex;box-shadow:0 0 0 3px rgba(255,176,32,.10)}.pc_consentCountdown{margin-left:auto;flex:none;font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dsw-alias-state-warning-primary)}.pc_consentApprove{border:1px solid var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:0 0;font:inherit;font-weight:600;cursor:pointer;border-radius:6px;padding:4px 12px;align-self:flex-start}.pc_consentApprove:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.pc_starFoot{margin-top:auto;position:sticky;bottom:0;justify-content:flex-end;display:flex;padding:6px 0 2px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 16px 0 var(--dsw-alias-bg-layer-1)}.pc_starLink{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:12px;line-height:16px;text-decoration:none;display:inline-flex;flex:none;border-radius:4px}.pc_starLink svg{flex:none;display:block}.pc_starLink:hover{color:var(--dsw-alias-state-business-primary);text-decoration:underline;text-underline-offset:3px}.pc_starLink:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.pc_groupNew{animation:pcGroupIn 260ms cubic-bezier(.2,.9,.3,1.25) both}@keyframes pcGroupIn{0%{opacity:0;transform:translateY(-10px) scale(.94)}55%{opacity:1;transform:translateY(4px) scale(1.03)}76%{transform:translateY(-2px) scale(.995)}100%{opacity:1;transform:none}}@media (prefers-reduced-motion:reduce){.pc_groupNew{animation:none}}";
|
|
10
10
|
const tagId = "@noob-stupid/dsh-plugin-console/PluginConsoleTab.css";
|
|
11
11
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
12
12
|
const tag = document.createElement("style");
|
|
@@ -87,6 +87,13 @@ window.__ModuleLoader__.load({
|
|
|
87
87
|
detail: "pc_detail",
|
|
88
88
|
status: "pc_status",
|
|
89
89
|
note: "pc_note",
|
|
90
|
+
iconBtn: "pc_iconBtn",
|
|
91
|
+
badgeRow: "pc_badgeRow",
|
|
92
|
+
badgeAll: "pc_badgeAll",
|
|
93
|
+
userNote: "pc_userNote",
|
|
94
|
+
noteInput: "pc_noteInput",
|
|
95
|
+
annotRow: "pc_annotRow",
|
|
96
|
+
groupNew: "pc_groupNew",
|
|
90
97
|
};
|
|
91
98
|
//#endregion
|
|
92
99
|
//#region locales
|
|
@@ -268,7 +275,87 @@ window.__ModuleLoader__.load({
|
|
|
268
275
|
familyCheckUpdate: "批量检测更新",
|
|
269
276
|
familyUpdateBtn: "更新",
|
|
270
277
|
familyCheckUpdateTip: "检测到新版本后此处出现「更新」按钮,点击开始整包更新(含子包版本对齐与适配校验)",
|
|
278
|
+
// issue #22 ①:已安装列表的「一键检测全部更新」(只有图标 ↑↑↑ 的圆按钮 + 结果/筛选态)
|
|
279
|
+
checkAllUpdates: "检测全部更新",
|
|
280
|
+
checkAllUpdatesHint: "一键检测全部已装插件是否有新版(本次要查 {n} 个)",
|
|
281
|
+
checkingAllUpdates: "正在检测全部更新({n} 个,并发受限,可能要几秒)",
|
|
282
|
+
checkAllUpdatesDone: "{n} 个可更新",
|
|
283
|
+
checkAllUpdateNone: "没有可更新的插件",
|
|
284
|
+
checkAllUpdateUnknownNote: "其中 {n} 个未检测到(网络或 registry 没答上来,可单独点卡片上的「检测更新」重试)",
|
|
285
|
+
checkAllUpdateFailed: "批量检测失败",
|
|
286
|
+
checkAllUpdateEmptyList: "已安装列表里没有可检测的插件",
|
|
287
|
+
batchUpdateOnly: "仅看可更新",
|
|
288
|
+
batchUpdateOnlyTip: "只显示本次检测到有更新的插件(再点一次取消)",
|
|
289
|
+
batchUpdateFilterEmpty: "本次检测没有可更新的插件(取消「仅看可更新」可看全部)",
|
|
290
|
+
batchUpdateAvailable: "可更新 → v{v}",
|
|
291
|
+
batchUpdateAvailableTip: "本次批量检测到新版本;点「更新」按这个版本更新(与单卡「检测更新」同一判据)",
|
|
292
|
+
batchUpToDate: "已最新",
|
|
293
|
+
batchUpdateUnknown: "未检测到",
|
|
294
|
+
batchUpdateUnknownTip: "registry 与 GitHub 都没答上来 ⇒ 不知道有没有新版(不是\"已是最新\");可单独点「检测更新」重试",
|
|
295
|
+
batchUpdateResultTip: "本次检测的结论:{n} 个可更新、{m} 个已最新、{k} 个未检测到({at})",
|
|
271
296
|
adaptUnlockBtn: "已适配,立即解锁",
|
|
297
|
+
// issue #22 ②③:用户注解(备注 / 用户徽章 / 用户自建分组)—— 都是**用户自己的数据**
|
|
298
|
+
annotationsBtn: "备注插件",
|
|
299
|
+
annotationsPanelTitle: "备注 / 用户徽章 / 用户分组:这些都是你自己的数据,存在 <DSH_HOME>/plugin-console/plugin-annotations.json,不写进插件、不改 profile;文件坏了也不影响上面的插件列表",
|
|
300
|
+
annotationsFailed: "注解操作失败",
|
|
301
|
+
annotationsCorrupt: "注解文件读不出来({err})—— 插件列表不受影响;修好或删掉该文件后再试写",
|
|
302
|
+
annotationsRepaired: "注解文件被手工改过,已按规则忽略 {n} 处不合规内容",
|
|
303
|
+
annotationsEmpty: "还没有任何备注 / 徽章 / 分组:在下面任一插件行里写第一条",
|
|
304
|
+
annotationsStats: "{p} 个插件有注解 · {b} 个用户徽章 · {g} 个分组",
|
|
305
|
+
annotationsListShow: "插件列表({n})· 点此展开",
|
|
306
|
+
annotationsListHide: "插件列表({n})· 点此收起",
|
|
307
|
+
annotationsSearchPlaceholder: "搜索:包名 / rowId / 备注内容(边打边筛)",
|
|
308
|
+
annotationsNoMatch: "没有匹配的插件",
|
|
309
|
+
noteLabel: "备注",
|
|
310
|
+
noteEdit: "改备注",
|
|
311
|
+
noteEmpty: "+ 加备注",
|
|
312
|
+
notePlaceholder: "这是干什么的、为什么装它",
|
|
313
|
+
noteSaveHint: "回车保存 · Shift+回车换行 · Esc 取消(存空 = 清空备注)",
|
|
314
|
+
noteSaved: "备注已保存",
|
|
315
|
+
noteCleared: "备注已清空",
|
|
316
|
+
badgeLabel: "用户徽章",
|
|
317
|
+
badgeAddPlaceholder: "徽章文字(回车添加)",
|
|
318
|
+
badgeAdded: "已加徽章「{b}」",
|
|
319
|
+
badgeRemove: "移除这个徽章",
|
|
320
|
+
badgeRemoved: "已移除徽章「{b}」",
|
|
321
|
+
badgeUndo: "撤销",
|
|
322
|
+
badgeUndoDone: "已恢复徽章",
|
|
323
|
+
badgeMax: "用户徽章最多 {n} 个",
|
|
324
|
+
badgesAll: "全部徽章({n}):系统徽章 + 用户徽章都在这",
|
|
325
|
+
groupLabel: "分组",
|
|
326
|
+
groupNone: "(不分组)",
|
|
327
|
+
groupCreate: "新建分组",
|
|
328
|
+
groupCreatePlaceholder: "分组名,如:记忆类(回车创建)",
|
|
329
|
+
groupCreated: "已创建分组「{name}」",
|
|
330
|
+
groupNewInlineHint: "在详情页直接建一个分组,并把当前插件放进去",
|
|
331
|
+
groupNewInlinePlaceholder: "分组名(回车创建并拉入)",
|
|
332
|
+
groupNewInline: "新建分组",
|
|
333
|
+
groupRename: "重命名",
|
|
334
|
+
groupRenamed: "分组已重命名",
|
|
335
|
+
groupRemove: "删除这个分组(只解除归属,不删插件)",
|
|
336
|
+
groupDeleteConfirm: "确认删除分组「{name}」?只解除归属,插件本身不会删",
|
|
337
|
+
groupDeleted: "已删除分组「{name}」(解除 {n} 个归属,插件本身没删)",
|
|
338
|
+
groupRestore: "撤销删除",
|
|
339
|
+
groupRestored: "分组「{name}」已恢复",
|
|
340
|
+
groupAssigned: "已加入分组「{name}」",
|
|
341
|
+
groupUnassigned: "已从分组移除",
|
|
342
|
+
groupMembers: "{n} 个插件",
|
|
343
|
+
groupNoMembers: "这个分组里还没有插件:从右边下拉里选一个加进来",
|
|
344
|
+
groupMemberPick: "+ 选一个插件加进这个分组",
|
|
345
|
+
groupUpdateAll: "一键更新本组",
|
|
346
|
+
groupUpdateAllTip: "按组内每个成员各自的目标版本逐个更新(串行,不并发;不适配的仍会被门控挡住)",
|
|
347
|
+
groupUpdateAllProgress: "正在更新第 {i}/{n} 个:",
|
|
348
|
+
groupUpdateAllDone: "本组更新完成:{n} 个成员,{hit} 个有新版并已发起更新",
|
|
349
|
+
groupLoadMore: "加载更多(还有 {n} 个)",
|
|
350
|
+
groupOpenHint: "点分组名可以在这里展开,搜索并添加插件",
|
|
351
|
+
groupAddSearchPlaceholder: "搜索插件名,点「加入」放进这个分组",
|
|
352
|
+
groupAddOne: "加入",
|
|
353
|
+
groupAddNoMatch: "没有匹配的插件(或都已在分组里)",
|
|
354
|
+
groupInFamilyNote: "成员都在官方全家桶区块里(同一插件不会重复显示)",
|
|
355
|
+
groupMemberNone: "插件都已经在这个分组里了",
|
|
356
|
+
groupMemberRemove: "从这个分组移出(只解除归属,不删插件)",
|
|
357
|
+
groupEmptyHint: "还没有分组:先在上面建一个(如「记忆类」),再到插件行里选它",
|
|
358
|
+
userGroupTag: "我的分组",
|
|
272
359
|
adaptUnlockHint: "已安装版本经源码扫描确认适配当前框架:立即移除禁用块并解锁启用",
|
|
273
360
|
adaptUnlockDone: "已适配解锁:刷新后该行可正常启用",
|
|
274
361
|
riskyEnableTitle: "强行启用不适配插件?",
|
|
@@ -441,7 +528,7 @@ window.__ModuleLoader__.load({
|
|
|
441
528
|
cfDone: "已改名 {renamed} / 重建链接 {linked} / 删备份 {removed} 条(释放约 {mb} MB)",
|
|
442
529
|
cfFailed: "{n} 项失败(详见 fw/console 日志)",
|
|
443
530
|
compatNoticeDismiss: "我知道了(此版本不再提示)",
|
|
444
|
-
searchInstalledPlaceholder: "
|
|
531
|
+
searchInstalledPlaceholder: "搜索已安装插件(名称 / id / 备注)",
|
|
445
532
|
extraFilter: "已装",
|
|
446
533
|
extraFilterAll: "全部",
|
|
447
534
|
extraTag: "第三方",
|
|
@@ -464,9 +551,9 @@ window.__ModuleLoader__.load({
|
|
|
464
551
|
stageInstalling: "下载安装中(可离开本页,后台继续)",
|
|
465
552
|
stageConfiguring: "写入启用配置",
|
|
466
553
|
stageRepairing: "本地 AI 接管安装中…",
|
|
467
|
-
restartService: "
|
|
554
|
+
restartService: "重启服务",
|
|
468
555
|
restartHostedNotice: "本实例由桌面端外壳托管 —— 请在桌面端内重启:完全退出 DeepSeek Harness 再打开(控制台不自行 kill / spawn,以免与宿主争端口)。",
|
|
469
|
-
restarting: "
|
|
556
|
+
restarting: "正在处理重启请求…",
|
|
470
557
|
autoReload: "页面即将自动刷新…",
|
|
471
558
|
aiRepaired: "本地 AI 已接管并完成修复,请刷新页面查看",
|
|
472
559
|
deletePlugin: "删除插件(移除配置并卸载包)",
|
|
@@ -927,7 +1014,83 @@ window.__ModuleLoader__.load({
|
|
|
927
1014
|
familyCheckUpdate: "Check all",
|
|
928
1015
|
familyUpdateBtn: "Update",
|
|
929
1016
|
familyCheckUpdateTip: "When a newer version is detected this Update button appears; click to start the full-family update (subpackage alignment + adapt check)",
|
|
1017
|
+
// issue #22 ①: bulk update check for the installed list (icon-only ↑↑↑ button + result/filter states)
|
|
1018
|
+
checkAllUpdates: "Check all updates",
|
|
1019
|
+
checkAllUpdatesHint: "Check every installed plugin for a newer version ({n} to check)",
|
|
1020
|
+
checkingAllUpdates: "Checking all updates ({n} plugins, concurrency capped - may take a few seconds)",
|
|
1021
|
+
checkAllUpdatesDone: "{n} updatable",
|
|
1022
|
+
checkAllUpdateNone: "No updatable plugin",
|
|
1023
|
+
checkAllUpdateUnknownNote: "{n} could not be checked (network or registry did not answer; use the per-card \"Check update\" to retry)",
|
|
1024
|
+
checkAllUpdateFailed: "Bulk update check failed",
|
|
1025
|
+
checkAllUpdateEmptyList: "No checkable plugin in the installed list",
|
|
1026
|
+
batchUpdateOnly: "Updatable only",
|
|
1027
|
+
batchUpdateOnlyTip: "Show only plugins with an update detected in this check (click again to clear)",
|
|
1028
|
+
batchUpdateFilterEmpty: "No updatable plugin in this check (clear \"Updatable only\" to see all)",
|
|
1029
|
+
batchUpdateAvailable: "Update → v{v}",
|
|
1030
|
+
batchUpdateAvailableTip: "A newer version was detected by the bulk check; Update installs exactly this version (same criterion as the per-card Check update)",
|
|
1031
|
+
batchUpToDate: "Up to date",
|
|
1032
|
+
batchUpdateUnknown: "Not detected",
|
|
1033
|
+
batchUpdateUnknownTip: "Neither the registry nor GitHub answered, so \"no update\" is unknown - not \"up to date\". Retry per card with Check update",
|
|
1034
|
+
batchUpdateResultTip: "This check: {n} updatable, {m} up to date, {k} not detected ({at})",
|
|
930
1035
|
adaptUnlockBtn: "Adapted - unlock now",
|
|
1036
|
+
// issue #22 ②③: user annotations (notes / user badges / user groups) - the user's own data
|
|
1037
|
+
annotationsBtn: "Annotate",
|
|
1038
|
+
annotationsPanelTitle: "Notes / user badges / user groups: your own data, stored in <DSH_HOME>/plugin-console/plugin-annotations.json. Nothing is written into plugins or the profile, and a broken file never affects the plugin list above.",
|
|
1039
|
+
annotationsFailed: "Annotation action failed",
|
|
1040
|
+
annotationsCorrupt: "The annotation file cannot be read ({err}) - the plugin list is unaffected; fix or delete that file, then retry",
|
|
1041
|
+
annotationsRepaired: "The annotation file was edited by hand; {n} invalid entries were ignored",
|
|
1042
|
+
annotationsEmpty: "No notes / badges / groups yet: write the first one in any plugin row below",
|
|
1043
|
+
annotationsStats: "{p} annotated plugins · {b} user badges · {g} groups",
|
|
1044
|
+
annotationsListShow: "Plugin list ({n}) · click to expand",
|
|
1045
|
+
annotationsListHide: "Plugin list ({n}) · click to collapse",
|
|
1046
|
+
annotationsSearchPlaceholder: "Search: package name / rowId / note text (filters as you type)",
|
|
1047
|
+
annotationsNoMatch: "No matching plugins",
|
|
1048
|
+
noteLabel: "Note",
|
|
1049
|
+
noteEdit: "Edit note",
|
|
1050
|
+
noteEmpty: "+ Add note",
|
|
1051
|
+
notePlaceholder: "What is this plugin, and why did you install it",
|
|
1052
|
+
noteSaveHint: "Enter saves · Shift+Enter newline · Esc cancels (empty = clear the note)",
|
|
1053
|
+
noteSaved: "Note saved",
|
|
1054
|
+
noteCleared: "Note cleared",
|
|
1055
|
+
badgeLabel: "User badge",
|
|
1056
|
+
badgeAddPlaceholder: "Badge text (Enter to add)",
|
|
1057
|
+
badgeAdded: "Badge \"{b}\" added",
|
|
1058
|
+
badgeRemove: "Remove this badge",
|
|
1059
|
+
badgeRemoved: "Badge \"{b}\" removed",
|
|
1060
|
+
badgeUndo: "Undo",
|
|
1061
|
+
badgeUndoDone: "Badges restored",
|
|
1062
|
+
badgeMax: "At most {n} user badges",
|
|
1063
|
+
badgesAll: "All badges ({n}): system badges and user badges",
|
|
1064
|
+
groupLabel: "Group",
|
|
1065
|
+
groupNone: "(no group)",
|
|
1066
|
+
groupCreate: "New group",
|
|
1067
|
+
groupCreatePlaceholder: "Group name, e.g. Memory (Enter to create)",
|
|
1068
|
+
groupCreated: "Group \"{name}\" created",
|
|
1069
|
+
groupNewInlineHint: "Create a group here and put this plugin in it",
|
|
1070
|
+
groupNewInlinePlaceholder: "Group name (Enter creates and adds)",
|
|
1071
|
+
groupNewInline: "New group",
|
|
1072
|
+
groupRename: "Rename",
|
|
1073
|
+
groupRenamed: "Group renamed",
|
|
1074
|
+
groupRemove: "Delete this group (membership only, plugins are kept)",
|
|
1075
|
+
groupDeleteConfirm: "Delete group \"{name}\"? Only the membership is removed - plugins stay",
|
|
1076
|
+
groupDeleted: "Group \"{name}\" deleted ({n} memberships removed, plugins kept)",
|
|
1077
|
+
groupRestore: "Undo delete",
|
|
1078
|
+
groupRestored: "Group \"{name}\" restored",
|
|
1079
|
+
groupAssigned: "Added to group \"{name}\"",
|
|
1080
|
+
groupUnassigned: "Removed from the group",
|
|
1081
|
+
groupMembers: "{n} plugins",
|
|
1082
|
+
groupNoMembers: "No plugins in this group yet: pick one from the dropdown on the right",
|
|
1083
|
+
groupMemberPick: "+ Pick a plugin to add to this group",
|
|
1084
|
+
groupUpdateAll: "Update this group",
|
|
1085
|
+
groupUpdateAllTip: "Updates each member sequentially, one at a time, at its own target version; the compatibility gate still applies",
|
|
1086
|
+
groupUpdateAllProgress: "Updating {i}/{n}:",
|
|
1087
|
+
groupUpdateAllDone: "Group update finished: {n} members, {hit} had a newer version and were started",
|
|
1088
|
+
groupLoadMore: "Load more ({n} left)",
|
|
1089
|
+
groupInFamilyNote: "Members live inside the official family block, not duplicated here",
|
|
1090
|
+
groupMemberNone: "Every plugin is already in this group",
|
|
1091
|
+
groupMemberRemove: "Remove from this group (membership only, the plugin is kept)",
|
|
1092
|
+
groupEmptyHint: "No groups yet: create one above (e.g. Memory), then pick it in a plugin row",
|
|
1093
|
+
userGroupTag: "My group",
|
|
931
1094
|
adaptUnlockHint: "Installed version passed the source scan for the current framework: unlock immediately",
|
|
932
1095
|
adaptUnlockDone: "Unlocked: refresh and the row is enabled",
|
|
933
1096
|
riskyEnableTitle: "Force-enable an incompatible plugin?",
|
|
@@ -1088,7 +1251,7 @@ window.__ModuleLoader__.load({
|
|
|
1088
1251
|
githubLoginBtn: "Sign in",
|
|
1089
1252
|
githubLoginOk: "Signed in to GitHub: {login}",
|
|
1090
1253
|
githubLoginFail: "Sign-in failed",
|
|
1091
|
-
searchInstalledPlaceholder: "Search installed plugins (name
|
|
1254
|
+
searchInstalledPlaceholder: "Search installed plugins (name / id / note)",
|
|
1092
1255
|
extraFilter: "Installed",
|
|
1093
1256
|
extraFilterAll: "All",
|
|
1094
1257
|
extraTag: "Third-party",
|
|
@@ -1111,9 +1274,9 @@ window.__ModuleLoader__.load({
|
|
|
1111
1274
|
stageInstalling: "Downloading & installing (safe to leave this page)",
|
|
1112
1275
|
stageConfiguring: "Writing enable config",
|
|
1113
1276
|
stageRepairing: "Local AI is taking over the install…",
|
|
1114
|
-
restartService: "Restart service
|
|
1277
|
+
restartService: "Restart service",
|
|
1115
1278
|
restartHostedNotice: "This instance is hosted by the desktop shell - restart it there by fully quitting and reopening DeepSeek Harness.",
|
|
1116
|
-
restarting: "Handling the restart request
|
|
1279
|
+
restarting: "Handling the restart request…",
|
|
1117
1280
|
autoReload: "Page will auto-reload…",
|
|
1118
1281
|
aiRepaired: "Local AI took over and finished the repair; refresh the page",
|
|
1119
1282
|
deletePlugin: "Delete plugin (remove config and uninstall package)",
|
|
@@ -1673,6 +1836,10 @@ window.__ModuleLoader__.load({
|
|
|
1673
1836
|
}
|
|
1674
1837
|
// <<<update-pick:end>>>
|
|
1675
1838
|
const el = react.createElement;
|
|
1839
|
+
// ★ 0.5.93(issue #22 ② 收口):徽章"放得下几个"的估算函数(BADGE_AREA_FALLBACK_PX / badgeChipWidthOf /
|
|
1840
|
+
// fitBadgeChunks / fitBadgeList)**整段删除**。它们配合 ResizeObserver 撑起了"卡片上收成 +N"那套机制,
|
|
1841
|
+
// 而用户定稿的行为是:卡片上放不下就**不显示**(纯 CSS 截断、无任何提示),全部徽章去详情页看。
|
|
1842
|
+
// 留着这套估算只会有人再把它接回去(抖动正是这么来的)。
|
|
1676
1843
|
// ★ 0.5.60:被跳过 bundle 的签名(包名+版本)。关掉提示时记下它;清单变了就重新出现。
|
|
1677
1844
|
// 模块级 —— 上一版把辅助函数放子组件里、主面板误用,导致整块面板空白。
|
|
1678
1845
|
function skippedSignature(skipped) {
|
|
@@ -1771,12 +1938,24 @@ function healthSignature(health) {
|
|
|
1771
1938
|
}
|
|
1772
1939
|
/** 重启服务(按钮 / 工具项 / 自更新后共用):服务端在「本实例由桌面端外壳托管」等场景会**拒绝**
|
|
1773
1940
|
* 并回结构化短句 —— 必须把它显示出来,否则按钮看起来是「点了没反应」(旧实现把错误吞掉了)。 */
|
|
1941
|
+
// ★ 0.5.92:本函数是模块级的,**不能用组件的 t()** —— 用了就抛 ReferenceError,
|
|
1942
|
+
// 点击直接异常、请求都发不出去(这正是"点了没反应"的真因)。文案用常量,永不抛。
|
|
1943
|
+
const RESTART_IN_PROGRESS_NOTE = "正在处理重启请求…";
|
|
1944
|
+
const RESTART_FAILED_NOTE = "重启请求失败";
|
|
1774
1945
|
function restartServiceViaServer(setMessage, options) {
|
|
1775
1946
|
const reload = (options ?? {}).reload !== false;
|
|
1776
|
-
if (reload) setMessage(
|
|
1947
|
+
if (reload) setMessage(RESTART_IN_PROGRESS_NOTE);
|
|
1777
1948
|
call("/plugin-console/restart", {}).then(
|
|
1778
|
-
() => {
|
|
1779
|
-
|
|
1949
|
+
(data) => {
|
|
1950
|
+
// ★ 0.5.89:显示服务端那段说明(发生了什么、要不要等、等多久)
|
|
1951
|
+
const note = data !== null && typeof data === "object" && typeof data.message === "string" && data.message !== ""
|
|
1952
|
+
? data.message
|
|
1953
|
+
: RESTART_IN_PROGRESS_NOTE;
|
|
1954
|
+
setMessage(note);
|
|
1955
|
+
if (reload) window.setTimeout(() => window.location.reload(), 12000);
|
|
1956
|
+
else if (typeof options?.onDone === "function") options.onDone(data);
|
|
1957
|
+
},
|
|
1958
|
+
(error) => { setMessage(error !== null && typeof error.message === "string" && error.message !== "" ? error.message : RESTART_FAILED_NOTE); },
|
|
1780
1959
|
);
|
|
1781
1960
|
}
|
|
1782
1961
|
/** 超时类错误附上可操作提示(网络黑洞期的最常见表现)。 */
|
|
@@ -2378,6 +2557,42 @@ function healthSignature(health) {
|
|
|
2378
2557
|
}, []);
|
|
2379
2558
|
// 自动版本比对结果:{ fullName: latest }(已安装且有新版)
|
|
2380
2559
|
const [updateMap, setUpdateMap] = react.useState({});
|
|
2560
|
+
// ★ issue #22 ①:已安装列表的「一键检测全部更新」(批量)。
|
|
2561
|
+
// 状态机:null(还没查过)→ checking(查中,带本次要查的包数)→ ready(有结论)/ error。
|
|
2562
|
+
// 界面只**展示**结论,不当判据:哪个版本更高由服务端唯一判据(`<<<update-pick>>>` 那块)给出,
|
|
2563
|
+
// 这里把 target 原样交给 startJob —— 与单卡「检测更新」同一个参数形状。
|
|
2564
|
+
const [batchCheck, setBatchCheck] = react.useState(null);
|
|
2565
|
+
// 「仅看可更新」筛选:只在 ready 且真的有可更新项时才渲染成按钮(没有可点的空状态)
|
|
2566
|
+
const [onlyUpdatable, setOnlyUpdatable] = react.useState(false);
|
|
2567
|
+
// ★ issue #22 ②③:用户注解(备注 / 用户徽章 / 用户自建分组)—— 独立文件、独立通道;
|
|
2568
|
+
// 读不到或写失败只影响这块,插件列表照常(服务端边界见 domain/plugin-annotations.js 顶部)。
|
|
2569
|
+
const [annotations, setAnnotations] = react.useState(null);
|
|
2570
|
+
const [annotationsNote, setAnnotationsNote] = react.useState(null);
|
|
2571
|
+
const [annotationBusy, setAnnotationBusy] = react.useState(null);
|
|
2572
|
+
const [annotationsPanelOpen, setAnnotationsPanelOpen] = react.useState(false);
|
|
2573
|
+
// ② 收口(0.5.93,用户定稿):面板一打开列表**收起**(只留一行摘要「插件列表(N)· 点此展开」);
|
|
2574
|
+
// 展开后可按 包名 / rowId / 已有备注内容 **边打边筛**(不需要点按钮)。
|
|
2575
|
+
// 查询词是受控 state —— 过滤过程绝不回写、更不清空用户正在输入的内容。
|
|
2576
|
+
const [annotationsListOpen, setAnnotationsListOpen] = react.useState(false);
|
|
2577
|
+
const [annotationsQuery, setAnnotationsQuery] = react.useState("");
|
|
2578
|
+
const [noteEdit, setNoteEdit] = react.useState(null);
|
|
2579
|
+
const [badgeDraft, setBadgeDraft] = react.useState({});
|
|
2580
|
+
const [badgeUndo, setBadgeUndo] = react.useState({});
|
|
2581
|
+
const [groupDraftName, setGroupDraftName] = react.useState("");
|
|
2582
|
+
// ③ 收口(0.5.93):**只**记住"刚刚建出来的那个分组 id"用来播一次入场动画;
|
|
2583
|
+
// 分组本身的真相永远来自服务端返回的全量 groups(annotationWrite 直接覆盖),
|
|
2584
|
+
// 这里不维护第二份客户端状态。
|
|
2585
|
+
const [groupJustCreated, setGroupJustCreated] = react.useState(null);
|
|
2586
|
+
// #5:详情页里就地新建分组 —— **按卡片 key 区分**({ key, draft } | null)。
|
|
2587
|
+
// 用一个全局值会让所有卡片同时展开输入框(用户实测到的问题)。
|
|
2588
|
+
const [detailGroupDraft, setDetailGroupDraft] = react.useState(null);
|
|
2589
|
+
// 面板里"点开某个分组"(只允许一个展开)+ 该分组内的搜索词
|
|
2590
|
+
const [panelGroupOpen, setPanelGroupOpen] = react.useState(null);
|
|
2591
|
+
const [panelGroupQuery, setPanelGroupQuery] = react.useState("");
|
|
2592
|
+
const [panelGroupShow, setPanelGroupShow] = react.useState(8); // 候选默认 8 个,可加载更多
|
|
2593
|
+
const [groupRenameEdit, setGroupRenameEdit] = react.useState(null);
|
|
2594
|
+
const [groupDeleteArm, setGroupDeleteArm] = react.useState(null);
|
|
2595
|
+
const [groupUndo, setGroupUndo] = react.useState(null);
|
|
2381
2596
|
const [expandedDescs, setExpandedDescs] = react.useState({});
|
|
2382
2597
|
const [installedQuery, setInstalledQuery] = react.useState(() => {
|
|
2383
2598
|
try { return localStorage.getItem("pc-market-installed-q") ?? ""; } catch { return ""; }
|
|
@@ -3204,6 +3419,68 @@ function healthSignature(health) {
|
|
|
3204
3419
|
(error) => setUpdateCheck({ status: "ready", latest: null, source: "npm", error: friendlyGithubError(error).message }),
|
|
3205
3420
|
);
|
|
3206
3421
|
};
|
|
3422
|
+
// ★ issue #22 ①:一键检测全部更新 —— **一次**请求查完已装列表(服务端并发上限 4,见 domain/update-batch.js)。
|
|
3423
|
+
// 为什么不是逐个包各发一个请求:那样并发由客户端决定,几十个包同时出去就是 0.5.84
|
|
3424
|
+
// 市场富化把网络打满、安装作业被拖死那次的形状(教训写在 domain/enrich-throttle.js 顶部)。
|
|
3425
|
+
// 为什么没有 x/y 进度:单个请求拿不到逐包进度;编一个假百分比比转圈更糟。要真进度只能分批请求
|
|
3426
|
+
// (每批一次请求、累加 x/y)——已写进 UI 方案等用户定,本版先给转圈 + 待查包数。
|
|
3427
|
+
const checkAllUpdates = () => {
|
|
3428
|
+
if (batchCheck !== null && batchCheck.status === "checking") return;
|
|
3429
|
+
const names = [];
|
|
3430
|
+
const seen = new Set();
|
|
3431
|
+
for (const entry of (state.status === "ready" ? state.data.entries : [])) {
|
|
3432
|
+
const mn = String(entry.moduleName ?? "");
|
|
3433
|
+
if (mn === "" || mn.startsWith("cordis:") || seen.has(mn)) continue;
|
|
3434
|
+
seen.add(mn);
|
|
3435
|
+
names.push(mn);
|
|
3436
|
+
}
|
|
3437
|
+
if (names.length === 0) {
|
|
3438
|
+
// 没有可查的东西也必须**有反馈**(空列表不是"点了没反应")
|
|
3439
|
+
setMessage(t("checkAllUpdateEmptyList"));
|
|
3440
|
+
return;
|
|
3441
|
+
}
|
|
3442
|
+
setBatchCheck({ status: "checking", total: names.length });
|
|
3443
|
+
setMessage(t("checkingAllUpdates").replace("{n}", String(names.length)));
|
|
3444
|
+
// ★ 2026-10-27:**分批**跑(每批 40,逐批回报进度)。整批一次性丢给服务端时,
|
|
3445
|
+
// 界面在几分钟里只有一个转圈,用户以为"卡在限制上"。分批后可看到"已查 40/238"逐步推进。
|
|
3446
|
+
const CHUNK = 40;
|
|
3447
|
+
const merged = { items: [], checked: 0, updatable: 0, unknown: 0 };
|
|
3448
|
+
const runChunk = (offset) => {
|
|
3449
|
+
const slice = names.slice(offset, offset + CHUNK);
|
|
3450
|
+
if (slice.length === 0) return Promise.resolve(merged);
|
|
3451
|
+
setMessage(t("checkingAllUpdates").replace("{n}", String(names.length)) + "(" + offset + "/" + names.length + ")");
|
|
3452
|
+
return call("/plugin-console/check-updates", { packageNames: slice }).then((data) => {
|
|
3453
|
+
if (data !== null && Array.isArray(data.items)) merged.items.push(...data.items);
|
|
3454
|
+
merged.checked += typeof data?.checked === "number" ? data.checked : slice.length;
|
|
3455
|
+
merged.updatable += typeof data?.updatable === "number" ? data.updatable : 0;
|
|
3456
|
+
merged.unknown += typeof data?.unknown === "number" ? data.unknown : 0;
|
|
3457
|
+
setBatchCheck({ status: "checking", total: names.length, done: merged.checked });
|
|
3458
|
+
return runChunk(offset + CHUNK);
|
|
3459
|
+
}, () => runChunk(offset + CHUNK));
|
|
3460
|
+
};
|
|
3461
|
+
runChunk(0).then(
|
|
3462
|
+
(data) => {
|
|
3463
|
+
const items = {};
|
|
3464
|
+
for (const item of (data !== null && Array.isArray(data.items) ? data.items : [])) {
|
|
3465
|
+
if (item !== null && typeof item === "object" && typeof item.packageName === "string") items[item.packageName] = item;
|
|
3466
|
+
}
|
|
3467
|
+
const updatable = typeof data?.updatable === "number" ? data.updatable : 0;
|
|
3468
|
+
const unknown = typeof data?.unknown === "number" ? data.unknown : 0;
|
|
3469
|
+
const checked = typeof data?.checked === "number" ? data.checked : names.length;
|
|
3470
|
+
setBatchCheck({ status: "ready", at: Date.now(), checked, updatable, unknown, items });
|
|
3471
|
+
// 结论必须看得见:没有可更新项时**不动**"仅看可更新"筛选(否则列表突然变空)
|
|
3472
|
+
if (updatable === 0) setOnlyUpdatable(false);
|
|
3473
|
+
setMessage(
|
|
3474
|
+
(updatable > 0 ? t("checkAllUpdatesDone").replace("{n}", String(updatable)) : t("checkAllUpdateNone"))
|
|
3475
|
+
+ (unknown > 0 ? ";" + t("checkAllUpdateUnknownNote").replace("{n}", String(unknown)) : ""),
|
|
3476
|
+
);
|
|
3477
|
+
},
|
|
3478
|
+
(error) => {
|
|
3479
|
+
setBatchCheck({ status: "error", error: friendlyGithubError(error).message });
|
|
3480
|
+
setMessage(t("checkAllUpdateFailed") + ":" + friendlyGithubError(error).message);
|
|
3481
|
+
},
|
|
3482
|
+
);
|
|
3483
|
+
};
|
|
3207
3484
|
const toggle = (entry, enabled, confirmRisky = false) => {
|
|
3208
3485
|
setBusy(entry.entryId);
|
|
3209
3486
|
setMessage(null);
|
|
@@ -4263,14 +4540,288 @@ function healthSignature(health) {
|
|
|
4263
4540
|
const label = primary?.name ?? t("repoLandSourceUnknown");
|
|
4264
4541
|
return t("repoLandTitle").replace("{source}", label);
|
|
4265
4542
|
};
|
|
4543
|
+
// issue #22 ①:批量检测结论查询(entry → 该项结论)。没查过 / 查中 / 出错一律 null =「这次没有结论」,
|
|
4544
|
+
// 于是卡片既不显示"可更新"也不显示"已最新"——不拿"没查过"冒充"已是最新"。
|
|
4545
|
+
const batchItemOf = (entry) => (batchCheck !== null && batchCheck.status === "ready"
|
|
4546
|
+
? batchCheck.items[String(entry.moduleName ?? "")] ?? null
|
|
4547
|
+
: null);
|
|
4548
|
+
|
|
4549
|
+
// ★ 0.5.93 改错(issue #22 ② 收口):徽章区**不再做任何宽度测量**。
|
|
4550
|
+
// 上一版用 ResizeObserver 读第一张卡片的徽章行宽度、再按估算宽度决定"显示几个 / 收成 +N"——
|
|
4551
|
+
// 测量值随卡片宽度抖动 ⇒ 徽章时不时多一个少一个(用户原话「徽章有挤压变化」)。
|
|
4552
|
+
// 现在:卡片上把徽章**全渲染**,靠 `.pc_badgeRow` 的 overflow:hidden + white-space:nowrap
|
|
4553
|
+
// 在纯 CSS 层截断(单行、固定高度、放不下的直接看不见,不给"还有 N 个"的提示);
|
|
4554
|
+
// 全部徽章在**详情页**一次看全。于是没有任何"测量 → 重算 → 重渲染"的反馈环。
|
|
4555
|
+
// ── issue #22 ②③:用户注解的读写与编辑块(备注 / 用户徽章 / 用户自建分组)───────────
|
|
4556
|
+
// 数据在**独立文件**里、走独立接口;这块出问题绝不影响插件列表(那是另一条通道)。
|
|
4557
|
+
const annotationKeyOf = (entry) => {
|
|
4558
|
+
const name = String(entry?.moduleName ?? "");
|
|
4559
|
+
return name !== "" ? name : String(entry?.rowId ?? "");
|
|
4560
|
+
};
|
|
4561
|
+
const annotationOf = (entry) => {
|
|
4562
|
+
const key = annotationKeyOf(entry);
|
|
4563
|
+
const record = key === "" ? null : annotations?.plugins?.[key];
|
|
4564
|
+
return record !== null && typeof record === "object" ? record : null;
|
|
4565
|
+
};
|
|
4566
|
+
const annotationsLoad = () => {
|
|
4567
|
+
call("/plugin-console/annotations").then(
|
|
4568
|
+
(data) => {
|
|
4569
|
+
setAnnotations(data !== null && typeof data === "object" ? data : null);
|
|
4570
|
+
// 手工改过文件 → 服务端规范化时丢弃了不合规内容,如实说(不静默)
|
|
4571
|
+
if (data !== null && Array.isArray(data.repaired) && data.repaired.length > 0) {
|
|
4572
|
+
setAnnotationsNote(t("annotationsRepaired").replace("{n}", String(data.repaired.length)));
|
|
4573
|
+
}
|
|
4574
|
+
},
|
|
4575
|
+
(error) => {
|
|
4576
|
+
setAnnotations(null);
|
|
4577
|
+
setAnnotationsNote(t("annotationsCorrupt").replace("{err}", friendlyGithubError(error).message));
|
|
4578
|
+
},
|
|
4579
|
+
);
|
|
4580
|
+
};
|
|
4581
|
+
/** 一次写动作:成功就用响应里的**整份最新状态**刷新(服务端保证回了全量),失败如实提示。 */
|
|
4582
|
+
const annotationWrite = (body, okText) => {
|
|
4583
|
+
const busyKey = String(body?.packageName ?? body?.id ?? body?.action ?? "");
|
|
4584
|
+
setAnnotationBusy(busyKey);
|
|
4585
|
+
return call("/plugin-console/annotations", body).then(
|
|
4586
|
+
(data) => {
|
|
4587
|
+
if (data !== null && data.plugins !== null && typeof data.plugins === "object") {
|
|
4588
|
+
setAnnotations({
|
|
4589
|
+
ok: true, file: data.file ?? null, exists: true, emptyFile: false, version: 1, updatedAt: 0,
|
|
4590
|
+
plugins: data.plugins, groups: Array.isArray(data.groups) ? data.groups : [],
|
|
4591
|
+
stats: data.stats ?? null, repaired: [], error: null,
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4594
|
+
setAnnotationsNote(okText === undefined || okText === null ? null : okText);
|
|
4595
|
+
return data;
|
|
4596
|
+
},
|
|
4597
|
+
(error) => { setAnnotationsNote(t("annotationsFailed") + ":" + friendlyGithubError(error).message); return null; },
|
|
4598
|
+
).finally(() => setAnnotationBusy(null));
|
|
4599
|
+
};
|
|
4600
|
+
const userGroups = Array.isArray(annotations?.groups) ? annotations.groups : [];
|
|
4601
|
+
/** 某个包属于哪个用户分组(一个包最多一个分组;服务端保证 assign 会自动移出旧的)。 */
|
|
4602
|
+
const groupOfPackage = (moduleName) => {
|
|
4603
|
+
const name = String(moduleName ?? "");
|
|
4604
|
+
if (name === "") return null;
|
|
4605
|
+
for (const group of userGroups) {
|
|
4606
|
+
if (Array.isArray(group.packages) && group.packages.includes(name)) return group;
|
|
4607
|
+
}
|
|
4608
|
+
return null;
|
|
4609
|
+
};
|
|
4610
|
+
const createUserGroup = () => {
|
|
4611
|
+
const name = groupDraftName.trim();
|
|
4612
|
+
if (name === "") return;
|
|
4613
|
+
// ③:新建只走**回车/点按钮**这一条明确动作(绝不边打边建);成功后用服务端返回的
|
|
4614
|
+
// group.id 标一下"刚建好"(只为了在主列表的横框上播一次入场动画,数据仍是服务端那份)。
|
|
4615
|
+
setGroupDraftName("");
|
|
4616
|
+
annotationWrite({ action: "group-create", name }, t("groupCreated").replace("{name}", name)).then((data) => {
|
|
4617
|
+
const id = data !== null && data !== undefined && data.group !== null && data.group !== undefined
|
|
4618
|
+
? String(data.group.id ?? "")
|
|
4619
|
+
: "";
|
|
4620
|
+
if (id === "") return;
|
|
4621
|
+
setGroupJustCreated(id);
|
|
4622
|
+
try {
|
|
4623
|
+
// 动画只有 220ms;1.2s 后把这个标记撤掉,避免以后重渲染时又播一次
|
|
4624
|
+
window.setTimeout(() => setGroupJustCreated((prev) => (prev === id ? null : prev)), 1200);
|
|
4625
|
+
} catch {}
|
|
4626
|
+
});
|
|
4627
|
+
};
|
|
4628
|
+
/**
|
|
4629
|
+
* 打开「备注插件」面板(用户定稿:面板打开时插件列表**收起**,只留一行摘要)。
|
|
4630
|
+
* 每次打开都回到收起 + 空查询 —— 不给"上次展开到一半"的残留状态。
|
|
4631
|
+
*/
|
|
4632
|
+
const openAnnotationsPanel = () => {
|
|
4633
|
+
setAnnotationsListOpen(false);
|
|
4634
|
+
setAnnotationsQuery("");
|
|
4635
|
+
setAnnotationsPanelOpen(true);
|
|
4636
|
+
annotationsLoad();
|
|
4637
|
+
};
|
|
4638
|
+
/** 工具栏那个按钮是**开关**:开着再点就关掉("点了没反应"算缺陷)。 */
|
|
4639
|
+
const toggleAnnotationsPanel = () => {
|
|
4640
|
+
if (annotationsPanelOpen) { setAnnotationsPanelOpen(false); return; }
|
|
4641
|
+
openAnnotationsPanel();
|
|
4642
|
+
};
|
|
4643
|
+
// issue #22 ②:注解面板里的插件行 —— 默认收起;展开后按 包名 / rowId / 已有备注内容 即时过滤。
|
|
4644
|
+
// 查询词只读不写 ⇒ 边打边筛的过程中用户正在输入的内容**不会被清掉**。
|
|
4645
|
+
const annotationListEntries = state.status === "ready" ? state.data.entries : [];
|
|
4646
|
+
const annotationsQueryNorm = annotationsQuery.trim().toLowerCase();
|
|
4647
|
+
const annotationListFiltered = annotationsQueryNorm === ""
|
|
4648
|
+
? annotationListEntries
|
|
4649
|
+
: annotationListEntries.filter((entry) => [entry.moduleName, entry.rowId, annotationOf(entry)?.note]
|
|
4650
|
+
.some((value) => String(value ?? "").toLowerCase().includes(annotationsQueryNorm)));
|
|
4651
|
+
/**
|
|
4652
|
+
* 用户注解编辑块 —— 详情页与「备注插件」面板**共用同一份**(两处交互必须一模一样)。
|
|
4653
|
+
* · 备注:点一下就地编辑,回车保存、Esc 取消;存空 = 清空(清空后**不占位**)。
|
|
4654
|
+
* · 徽章:∪ 追加(系统徽章在卡片上是另一组,**绝不替换、绝不合并**);× 删除后可撤销一次。
|
|
4655
|
+
* · 没有备注也没有徽章时:只显示一个「+ 加备注」入口(用户定稿的那一种,不显示空壳)。
|
|
4656
|
+
*/
|
|
4657
|
+
const annotationBlock = (entry) => {
|
|
4658
|
+
const key = annotationKeyOf(entry);
|
|
4659
|
+
const packageName = String(entry?.moduleName ?? key);
|
|
4660
|
+
const record = annotationOf(entry);
|
|
4661
|
+
const note = typeof record?.note === "string" ? record.note : "";
|
|
4662
|
+
const badges = Array.isArray(record?.badges) ? record.badges : [];
|
|
4663
|
+
const group = groupOfPackage(packageName);
|
|
4664
|
+
const editing = noteEdit !== null && noteEdit.key === key;
|
|
4665
|
+
const busy = annotationBusy === key || annotationBusy === packageName;
|
|
4666
|
+
const draft = badgeDraft[key] ?? "";
|
|
4667
|
+
const undo = badgeUndo[key];
|
|
4668
|
+
const writeNote = (text) => annotationWrite({ action: "note", packageName, rowId: entry?.rowId ?? null, note: text }, text.trim() === "" ? t("noteCleared") : t("noteSaved"));
|
|
4669
|
+
const writeBadges = (list, okText) => annotationWrite({ action: "badges", packageName, rowId: entry?.rowId ?? null, badges: list }, okText);
|
|
4670
|
+
const addBadge = () => {
|
|
4671
|
+
const text = draft.trim();
|
|
4672
|
+
if (text === "") return;
|
|
4673
|
+
setBadgeDraft((prev) => ({ ...prev, [key]: "" }));
|
|
4674
|
+
if (badges.includes(text)) return;
|
|
4675
|
+
if (badges.length >= 12) { setAnnotationsNote(t("badgeMax").replace("{n}", "12")); return; }
|
|
4676
|
+
writeBadges([...badges, text], t("badgeAdded").replace("{b}", text));
|
|
4677
|
+
};
|
|
4678
|
+
return el("div", { className: styles.userNote, "data-annot": "true" },
|
|
4679
|
+
editing
|
|
4680
|
+
? el("div", null,
|
|
4681
|
+
el("textarea", {
|
|
4682
|
+
className: styles.noteInput,
|
|
4683
|
+
rows: 2,
|
|
4684
|
+
value: noteEdit.draft,
|
|
4685
|
+
placeholder: t("notePlaceholder"),
|
|
4686
|
+
"aria-label": t("noteLabel"),
|
|
4687
|
+
onChange: (event) => setNoteEdit({ key, draft: String(event.target.value ?? "") }),
|
|
4688
|
+
onKeyDown: (event) => {
|
|
4689
|
+
if (event.key === "Escape") { event.preventDefault(); setNoteEdit(null); return; }
|
|
4690
|
+
if (event.key === "Enter" && event.shiftKey !== true) {
|
|
4691
|
+
event.preventDefault();
|
|
4692
|
+
const text = noteEdit.draft;
|
|
4693
|
+
setNoteEdit(null);
|
|
4694
|
+
writeNote(text);
|
|
4695
|
+
}
|
|
4696
|
+
},
|
|
4697
|
+
}),
|
|
4698
|
+
el("p", { className: styles.note, style: { margin: 0 } }, t("noteSaveHint")))
|
|
4699
|
+
: el("div", { className: styles.annotRow },
|
|
4700
|
+
note !== "" ? el("p", { className: styles.desc, style: { margin: 0, whiteSpace: "pre-wrap", flex: "1 1 200px" } }, note) : null,
|
|
4701
|
+
el("button", {
|
|
4702
|
+
type: "button",
|
|
4703
|
+
className: styles.toggle,
|
|
4704
|
+
disabled: busy,
|
|
4705
|
+
title: t("notePlaceholder"),
|
|
4706
|
+
onClick: () => setNoteEdit({ key, draft: note }),
|
|
4707
|
+
}, note !== "" ? t("noteEdit") : t("noteEmpty"))),
|
|
4708
|
+
el("div", { className: styles.annotRow },
|
|
4709
|
+
el("span", { className: styles.status }, t("badgeLabel") + ":"),
|
|
4710
|
+
badges.map((badge) => el("span", { key: "ub-" + badge, className: styles.tag, "data-user-badge": "true" },
|
|
4711
|
+
badge,
|
|
4712
|
+
el("button", {
|
|
4713
|
+
type: "button",
|
|
4714
|
+
className: styles.tag,
|
|
4715
|
+
"data-more": "true",
|
|
4716
|
+
title: t("badgeRemove"),
|
|
4717
|
+
"aria-label": t("badgeRemove"),
|
|
4718
|
+
disabled: busy,
|
|
4719
|
+
onClick: () => {
|
|
4720
|
+
setBadgeUndo((prev) => ({ ...prev, [key]: badges }));
|
|
4721
|
+
writeBadges(badges.filter((item) => item !== badge), t("badgeRemoved").replace("{b}", badge));
|
|
4722
|
+
},
|
|
4723
|
+
}, "×"))),
|
|
4724
|
+
el("input", {
|
|
4725
|
+
type: "text",
|
|
4726
|
+
value: draft,
|
|
4727
|
+
placeholder: t("badgeAddPlaceholder"),
|
|
4728
|
+
"aria-label": t("badgeAddPlaceholder"),
|
|
4729
|
+
disabled: busy,
|
|
4730
|
+
style: { width: 130, font: "inherit", padding: "2px 6px" },
|
|
4731
|
+
onChange: (event) => setBadgeDraft((prev) => ({ ...prev, [key]: String(event.target.value ?? "") })),
|
|
4732
|
+
onKeyDown: (event) => { if (event.key === "Enter") { event.preventDefault(); addBadge(); } },
|
|
4733
|
+
}),
|
|
4734
|
+
undo !== undefined
|
|
4735
|
+
? el("button", {
|
|
4736
|
+
type: "button",
|
|
4737
|
+
className: styles.toggle,
|
|
4738
|
+
disabled: busy,
|
|
4739
|
+
onClick: () => {
|
|
4740
|
+
writeBadges(undo, t("badgeUndoDone"));
|
|
4741
|
+
setBadgeUndo((prev) => { const next = { ...prev }; delete next[key]; return next; });
|
|
4742
|
+
},
|
|
4743
|
+
}, t("badgeUndo"))
|
|
4744
|
+
: null),
|
|
4745
|
+
el("div", { className: styles.annotRow },
|
|
4746
|
+
el("span", { className: styles.status }, t("groupLabel") + ":"),
|
|
4747
|
+
// #5:就在这里新建分组,并把当前插件直接拉进去(回车创建)
|
|
4748
|
+
detailGroupDraft === null || detailGroupDraft.key !== key
|
|
4749
|
+
? el("button", {
|
|
4750
|
+
type: "button",
|
|
4751
|
+
className: styles.toggle,
|
|
4752
|
+
"data-newgroup": "true",
|
|
4753
|
+
title: t("groupNewInlineHint"),
|
|
4754
|
+
disabled: busy,
|
|
4755
|
+
onClick: () => setDetailGroupDraft({ key, draft: "" }),
|
|
4756
|
+
}, "+ " + t("groupNewInline"))
|
|
4757
|
+
: el("input", {
|
|
4758
|
+
type: "text",
|
|
4759
|
+
autoFocus: true,
|
|
4760
|
+
value: detailGroupDraft.draft,
|
|
4761
|
+
placeholder: t("groupNewInlinePlaceholder"),
|
|
4762
|
+
"aria-label": t("groupNewInline"),
|
|
4763
|
+
style: { font: "inherit", padding: "2px 6px", width: 160 },
|
|
4764
|
+
onChange: (event) => setDetailGroupDraft({ key, draft: String(event.target.value ?? "") }),
|
|
4765
|
+
onKeyDown: (event) => {
|
|
4766
|
+
if (event.key === "Escape") { event.preventDefault(); setDetailGroupDraft(null); return; }
|
|
4767
|
+
if (event.key !== "Enter") return;
|
|
4768
|
+
event.preventDefault();
|
|
4769
|
+
const name = String(detailGroupDraft.draft ?? "").trim();
|
|
4770
|
+
if (name === "") { setDetailGroupDraft(null); return; }
|
|
4771
|
+
setDetailGroupDraft(null);
|
|
4772
|
+
// 建组 → 用返回的全量 groups 找到它 → 把当前插件拉进去
|
|
4773
|
+
annotationWrite({ action: "group-create", name }, t("groupCreated").replace("{name}", name))
|
|
4774
|
+
.then((data) => {
|
|
4775
|
+
if (data === null) return;
|
|
4776
|
+
const created = (Array.isArray(data.groups) ? data.groups : [])
|
|
4777
|
+
.find((item) => String(item.name) === name);
|
|
4778
|
+
if (created !== undefined && created !== null) {
|
|
4779
|
+
annotationWrite({ action: "group-assign", id: created.id, packageName }, t("groupAssigned").replace("{name}", name));
|
|
4780
|
+
}
|
|
4781
|
+
});
|
|
4782
|
+
},
|
|
4783
|
+
}),
|
|
4784
|
+
el("select", {
|
|
4785
|
+
value: group?.id ?? "",
|
|
4786
|
+
"aria-label": t("groupLabel"),
|
|
4787
|
+
disabled: busy,
|
|
4788
|
+
style: { font: "inherit", padding: "2px 6px" },
|
|
4789
|
+
onChange: (event) => {
|
|
4790
|
+
const picked = String(event.target.value ?? "");
|
|
4791
|
+
if (picked === "") {
|
|
4792
|
+
if (group === null) return;
|
|
4793
|
+
annotationWrite({ action: "group-assign", id: group.id, packageName, member: false }, t("groupUnassigned"));
|
|
4794
|
+
return;
|
|
4795
|
+
}
|
|
4796
|
+
const pickedName = String(userGroups.find((item) => item.id === picked)?.name ?? "");
|
|
4797
|
+
annotationWrite({ action: "group-assign", id: picked, packageName }, t("groupAssigned").replace("{name}", pickedName));
|
|
4798
|
+
},
|
|
4799
|
+
},
|
|
4800
|
+
el("option", { value: "" }, t("groupNone")),
|
|
4801
|
+
userGroups.map((item) => el("option", { key: "gs-" + item.id, value: item.id }, String(item.name))))));
|
|
4802
|
+
};
|
|
4266
4803
|
const extraIds = new Set((state.status === "ready" ? state.data.patch?.inserts ?? [] : []));
|
|
4267
4804
|
const extraCount = state.status === "ready" ? state.data.entries.filter((entry) => entry.extra === true || extraIds.has(entry.rowId)).length : 0;
|
|
4805
|
+
// issue #22 ②③:打开面板即读一次注解(读失败只提示,插件列表照常)
|
|
4806
|
+
react.useEffect(() => { annotationsLoad(); }, []);
|
|
4268
4807
|
const pkgRootOf = (m) => { const mm = String(m ?? ""); const x = mm.match(/^(@[^/]+\/[^/]+)(?:\/.*)?$/u); return x ? x[1] : mm; };
|
|
4269
4808
|
const filteredEntries = state.status === "ready"
|
|
4270
4809
|
? state.data.entries.filter((entry) => {
|
|
4271
4810
|
if (extraOnly && !(entry.extra === true || extraIds.has(entry.rowId))) return false;
|
|
4811
|
+
// issue #22 ①:「仅看可更新」只筛**本次批量检测确实查到新版**的行。
|
|
4812
|
+
// 没查到结论的行(未检测到 / 没查过)**不算**可更新 —— 与"没查到 ≠ 已最新"同一条纪律。
|
|
4813
|
+
if (onlyUpdatable && batchItemOf(entry)?.status !== "update") return false;
|
|
4272
4814
|
if (installedQueryNorm === "") return true;
|
|
4273
|
-
|
|
4815
|
+
// ★ 用户 2026-10-27:搜索也要命中**备注**(备注是可用来命名的,只按包名搜会漏)
|
|
4816
|
+
const noteText = (() => {
|
|
4817
|
+
const key = annotationKeyOf(entry);
|
|
4818
|
+
if (key === "") return "";
|
|
4819
|
+
const rec = annotations !== null && annotations.plugins !== null && annotations.plugins !== undefined
|
|
4820
|
+
? annotations.plugins[key] ?? null
|
|
4821
|
+
: null;
|
|
4822
|
+
return typeof rec?.note === "string" ? rec.note : "";
|
|
4823
|
+
})();
|
|
4824
|
+
return [entry.moduleName, entry.entryId, entry.rowId, noteText]
|
|
4274
4825
|
.some((value) => String(value ?? "").toLowerCase().includes(installedQueryNorm));
|
|
4275
4826
|
})
|
|
4276
4827
|
: [];
|
|
@@ -4290,10 +4841,33 @@ function healthSignature(health) {
|
|
|
4290
4841
|
familyCounts.set(root, set);
|
|
4291
4842
|
}
|
|
4292
4843
|
const familySize = (root) => (familyCounts.get(root)?.size ?? 0);
|
|
4293
|
-
const familyCards = filteredEntries.map((entry) => {
|
|
4844
|
+
const familyCards = filteredEntries.map((entry, cardIndex) => {
|
|
4294
4845
|
const familyRoot = String(entry.moduleName ?? "").startsWith("@deepseek-ai/") ? null : pkgRootOf(entry.moduleName);
|
|
4295
4846
|
void familyRoot;
|
|
4296
4847
|
const open = details !== null && details.entryId === entry.entryId;
|
|
4848
|
+
// issue #22 ①:这张卡本次的批量检测结论(null = 没查过/查中/查失败 ⇒ 不加任何"已最新"标签)
|
|
4849
|
+
const batchItem = batchItemOf(entry);
|
|
4850
|
+
// issue #22 ②:卡片徽章 = **系统徽章(现有顺序,一条不少)** + 用户徽章(追加在最后)。
|
|
4851
|
+
// 系统徽章用现有实心/描边样式(props 原样搬过来),用户徽章带 data-user-badge(虚线+浅底)。
|
|
4852
|
+
const systemBadges = [
|
|
4853
|
+
{ key: "enabled", label: t(entry.enabled ? "enabledTag" : "disabledTag"), props: { "data-enabled": entry.enabled ? "true" : "false" } },
|
|
4854
|
+
entry.extra === true ? { key: "extra", label: t("extraTag"), props: { "data-user": "true" } } : null,
|
|
4855
|
+
entry.protected ? { key: "protected", label: t("protectedTag"), props: { "data-user": "true" } } : null,
|
|
4856
|
+
entry.userDisabled ? { key: "userDisabled", label: t("userDisabledTag"), props: { "data-user": "true" } } : null,
|
|
4857
|
+
entry.userForced ? { key: "userForced", label: t("userForcedTag"), props: { "data-user": "true" } } : null,
|
|
4858
|
+
batchItem?.status === "update"
|
|
4859
|
+
? { key: "batch-update", label: t("batchUpdateAvailable").replace("{v}", String(batchItem.target ?? "?")), props: { "data-pending": "true", title: t("batchUpdateAvailableTip") } }
|
|
4860
|
+
: batchItem?.status === "current"
|
|
4861
|
+
? { key: "batch-current", label: t("batchUpToDate"), props: { "data-enabled": "true" } }
|
|
4862
|
+
: batchItem?.status === "unknown"
|
|
4863
|
+
? { key: "batch-unknown", label: t("batchUpdateUnknown"), props: { title: t("batchUpdateUnknownTip") + (batchItem.error ? "(" + String(batchItem.error) + ")" : "") } }
|
|
4864
|
+
: null,
|
|
4865
|
+
entry.disableSource === "bundle" ? { key: "bundle", label: t("disabledByBundleTag"), props: { "data-user": "true", "data-source": "bundle" } } : null,
|
|
4866
|
+
entry.forcedNotLoaded === true ? { key: "ineffective", label: t("forcedNotLoadedTag"), props: { "data-user": "true", "data-source": "ineffective" } } : null,
|
|
4867
|
+
].filter((badge) => badge !== null);
|
|
4868
|
+
const userBadges = (Array.isArray(annotationOf(entry)?.badges) ? annotationOf(entry).badges : [])
|
|
4869
|
+
.map((label) => ({ key: "user-" + label, label: String(label), props: { "data-user-badge": "true", title: t("badgeLabel") } }));
|
|
4870
|
+
const allBadges = [...systemBadges, ...userBadges];
|
|
4297
4871
|
let detailPanel = null;
|
|
4298
4872
|
if (open) {
|
|
4299
4873
|
if (details.status === "loading") {
|
|
@@ -4308,20 +4882,10 @@ function healthSignature(health) {
|
|
|
4308
4882
|
const compatInfo = state.status === "ready" && state.data.compatPending
|
|
4309
4883
|
? (state.data.compatPending.pending ?? []).find((p) => p.rowId === entry.rowId) ?? null
|
|
4310
4884
|
: null;
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
entry.adoptable
|
|
4316
|
-
? (entry.adoptable.basis === "verdict-rescinded"
|
|
4317
|
-
// 0.5.34:旧判据的误报已被当前判据撤回(真机 dshmarket 现场)——如实说是"判据更新",
|
|
4318
|
-
// 不能说成"插件已适配"(那会让人以为作者发过新版)
|
|
4319
|
-
? t("adoptableRescinded").replace("{version}", String(entry.adoptable.version ?? "?"))
|
|
4320
|
-
: t("adoptableDetected").replace("{version}", String(entry.adoptable.version ?? "?")).replace("{prev}", String(compatInfo?.version ?? "?")))
|
|
4321
|
-
: t("pendingCompatHint") + (compatInfo && compatInfo.checkNote ? ";已知校验:" + compatInfo.checkNote : ""))
|
|
4322
|
-
: null,
|
|
4323
|
-
meta !== null && meta.version ? el("div", { className: styles.rowTop },
|
|
4324
|
-
el("p", { className: styles.status }, t("versionLabel") + ":" + meta.version),
|
|
4885
|
+
// ★ issue #22 ②(用户定稿):动作按钮排在详情页**最后**(原来挂在"版本"那一行,夹在中间)。
|
|
4886
|
+
// 判据/禁用条件/文案逐字搬过来,一个字都没改,只换了位置。
|
|
4887
|
+
const detailActionRow = meta !== null && meta.version
|
|
4888
|
+
? el("div", { className: styles.rowTop },
|
|
4325
4889
|
el("button", {
|
|
4326
4890
|
type: "button",
|
|
4327
4891
|
className: styles.toggle,
|
|
@@ -4353,7 +4917,29 @@ function healthSignature(health) {
|
|
|
4353
4917
|
);
|
|
4354
4918
|
},
|
|
4355
4919
|
}, adaptUnlocking === entry.entryId ? t("loadingPhase") : t("adaptUnlockBtn"))
|
|
4356
|
-
: null)
|
|
4920
|
+
: null)
|
|
4921
|
+
: null;
|
|
4922
|
+
detailPanel = el("div", { className: styles.detail },
|
|
4923
|
+
// ★ 0.5.93(issue #22 ② 收口):卡片上放不下的徽章**不提示、不展开**,
|
|
4924
|
+
// 全部徽章(系统徽章 + 用户徽章,顺序与卡片一致)在详情页里一次看全 ——
|
|
4925
|
+
// 这也是"卡片不再出现 +N"之后"看不到的徽章去哪了"的唯一答案。
|
|
4926
|
+
el("div", { className: styles.badgeAll, "data-allbadges": "true", title: t("badgesAll").replace("{n}", String(allBadges.length)) },
|
|
4927
|
+
allBadges.map((badge) => el("span", { key: "detail-" + badge.key, className: styles.tag, ...badge.props }, badge.label))),
|
|
4928
|
+
// ★ issue #22 ②(用户定稿的详情页顺序):用户备注/用户徽章放**最前面**,
|
|
4929
|
+
// 系统诊断 → 版本/仓库/说明摘要 → 动作按钮依次在其后。
|
|
4930
|
+
annotationBlock(entry),
|
|
4931
|
+
meta !== null ? el("p", { className: styles.desc }, meta.description ?? t("noDetail")) : null,
|
|
4932
|
+
entry.pendingCompat === true
|
|
4933
|
+
? el("p", { className: styles.status, "data-pending": entry.adoptable ? "false" : "true" },
|
|
4934
|
+
entry.adoptable
|
|
4935
|
+
? (entry.adoptable.basis === "verdict-rescinded"
|
|
4936
|
+
// 0.5.34:旧判据的误报已被当前判据撤回(真机 dshmarket 现场)——如实说是"判据更新",
|
|
4937
|
+
// 不能说成"插件已适配"(那会让人以为作者发过新版)
|
|
4938
|
+
? t("adoptableRescinded").replace("{version}", String(entry.adoptable.version ?? "?"))
|
|
4939
|
+
: t("adoptableDetected").replace("{version}", String(entry.adoptable.version ?? "?")).replace("{prev}", String(compatInfo?.version ?? "?")))
|
|
4940
|
+
: t("pendingCompatHint") + (compatInfo && compatInfo.checkNote ? ";已知校验:" + compatInfo.checkNote : ""))
|
|
4941
|
+
: null,
|
|
4942
|
+
meta !== null && meta.version ? el("p", { className: styles.status }, t("versionLabel") + ":" + meta.version) : null,
|
|
4357
4943
|
updateCheck !== null && updateCheck.status === "ready" && updateCheck.migrate && updateCheck.migrate.compatible
|
|
4358
4944
|
? el("div", null,
|
|
4359
4945
|
el("div", { className: styles.rowTop },
|
|
@@ -4406,28 +4992,34 @@ function healthSignature(health) {
|
|
|
4406
4992
|
readme.summary ? el("p", { className: styles.desc }, readme.summary) : null,
|
|
4407
4993
|
)
|
|
4408
4994
|
: meta === null ? el("p", { className: styles.status }, t("noDetail")) : null,
|
|
4995
|
+
detailActionRow,
|
|
4409
4996
|
);
|
|
4410
4997
|
}
|
|
4411
4998
|
}
|
|
4412
4999
|
return el("li", { key: entry.entryId, className: styles.row },
|
|
4413
|
-
el("div", { className: styles.rowTop },
|
|
4414
|
-
el("strong", { className: styles.name, title: entry.moduleName }, (() => {
|
|
5000
|
+
el("div", { className: styles.rowTop, style: { minWidth: 0 } },
|
|
5001
|
+
el("strong", { className: styles.name, title: entry.moduleName, style: { minWidth: 0 } }, (() => {
|
|
4415
5002
|
const famRoot = famRootOf(entry);
|
|
4416
5003
|
const mn = String(entry.moduleName ?? "");
|
|
4417
|
-
|
|
5004
|
+
const shortName = famRoot !== null && familySize(famRoot) > 1 && mn.startsWith(famRoot + "/")
|
|
4418
5005
|
? mn.slice(famRoot.length + 1)
|
|
4419
5006
|
: moduleShortName(entry.moduleName);
|
|
5007
|
+
// ★ 用户 2026-10-26:备注可以**覆盖显示名**。有备注时标题用备注(强调色 + ✎ 标记),
|
|
5008
|
+
// 原短名降到旁边的小字 —— 一眼能看出"这个插件被我备注过",与没备注的区分开。
|
|
5009
|
+
const rec = annotationOf(entry);
|
|
5010
|
+
const custom = typeof rec?.note === "string" ? rec.note.trim() : "";
|
|
5011
|
+
if (custom === "") return shortName;
|
|
5012
|
+
return el("span", { "data-note-name": "true", style: { display: "inline-flex", alignItems: "baseline", gap: 6, minWidth: 0 } },
|
|
5013
|
+
el("span", { className: styles.noteName ?? undefined, style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, custom),
|
|
5014
|
+
el("span", { style: { opacity: 0.55, fontWeight: 400, fontSize: ".85em", whiteSpace: "nowrap" } }, shortName));
|
|
4420
5015
|
})()),
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
entry.disableSource === "bundle" ? el("span", { className: styles.tag, "data-user": "true", "data-source": "bundle" }, t("disabledByBundleTag")) : null,
|
|
4429
|
-
// ★ 0.5.76:补丁写了强制开启、运行时却仍没起来 ⇒ 这条覆盖是**无效的**,必须说出来
|
|
4430
|
-
entry.forcedNotLoaded === true ? el("span", { className: styles.tag, "data-user": "true", "data-source": "ineffective" }, t("forcedNotLoadedTag")) : null,
|
|
5016
|
+
// ★ 0.5.93(issue #22 ② 收口):徽章区**单行不换行**,放不下的直接看不见
|
|
5017
|
+
// (纯 CSS:`.pc_badgeRow` 固定高度 + overflow:hidden + white-space:nowrap),
|
|
5018
|
+
// 卡片上**不给** "+N"、不给"还有几个"的任何提示,也不做宽度测量 ——
|
|
5019
|
+
// 行为与这个功能上线前一致;全部徽章去详情页看。
|
|
5020
|
+
// 顺序(用户定稿):**先系统徽章(现有顺序一个字没改)→ 再用户徽章(描边样式)**。
|
|
5021
|
+
el("div", { className: styles.badgeRow },
|
|
5022
|
+
allBadges.map((badge) => el("span", { key: badge.key, className: styles.tag, ...badge.props }, badge.label))),
|
|
4431
5023
|
),
|
|
4432
5024
|
el("div", { className: styles.meta },
|
|
4433
5025
|
el("span", { className: styles.phase, "data-phase": entry.fiberPhase ?? "unobserved" },
|
|
@@ -4463,6 +5055,20 @@ function healthSignature(health) {
|
|
|
4463
5055
|
title: entry.pendingCompat === true ? (entry.compatRescinded === true ? t("compatRescindedHint") : t("pendingCompatHint")) : undefined,
|
|
4464
5056
|
onClick: () => toggle(entry, !entry.enabled),
|
|
4465
5057
|
}, busy === entry.entryId ? t("loadingPhase") : (entry.pendingCompat === true ? (entry.compatRescinded === true ? t("compatRescindedTag") : t("pendingCompatTag")) : t(entry.enabled ? "off" : "on"))),
|
|
5058
|
+
// ★ issue #22 ①:批量检测到新版的卡片直接给「更新」按钮 —— 与详情卡片里那个按钮
|
|
5059
|
+
// **完全同一条路**(startJob(-, moduleName, github, plugin, update=true, -, target)),
|
|
5060
|
+
// 目标是服务端唯一判据算出的 target;点下去走的是既有安装/更新作业,不新增任何写盘路径。
|
|
5061
|
+
batchItem !== null && batchItem.status === "update"
|
|
5062
|
+
? el("button", {
|
|
5063
|
+
type: "button",
|
|
5064
|
+
className: styles.toggle,
|
|
5065
|
+
title: t("batchUpdateAvailableTip"),
|
|
5066
|
+
disabled: installing === entry.moduleName || inFlight.has(entry.moduleName),
|
|
5067
|
+
onClick: () => startJob("", entry.moduleName, "github", "plugin", true, null, batchItem.target ?? null),
|
|
5068
|
+
}, installing === entry.moduleName
|
|
5069
|
+
? t("installing")
|
|
5070
|
+
: (entry.pendingCompat === true ? t("updateAndAdapt") : t("update") + " → v" + String(batchItem.target ?? "?")))
|
|
5071
|
+
: null,
|
|
4466
5072
|
// ★ 0.5.76:补丁写了强制开启但运行时没起来 ⇒ 给一个撤销覆盖的入口
|
|
4467
5073
|
entry.userForced === true ? el("button", {
|
|
4468
5074
|
type: "button",
|
|
@@ -4590,11 +5196,26 @@ function healthSignature(health) {
|
|
|
4590
5196
|
// 组头放到最底部:普通条目在前,全家桶(组头+子包卡片)整体在后
|
|
4591
5197
|
const nonFamilyCards = [];
|
|
4592
5198
|
const familySections = new Map();
|
|
5199
|
+
// issue #22 ③:用户自建分组 → 折叠区块。**官方 family 优先**(用户拍定的规则):
|
|
5200
|
+
// 属于官方 family 的插件即使被归进用户分组,也**留在官方 family 区块里**,不进用户分组区块 ——
|
|
5201
|
+
// 官方 family 的渲染因此一个字都没变,两者共存且不会出现同一插件两处重复。
|
|
5202
|
+
const userGroupBuckets = new Map();
|
|
4593
5203
|
familyCards.forEach((card, i) => {
|
|
4594
5204
|
const e = filteredEntries[i];
|
|
4595
5205
|
if (e === undefined) { nonFamilyCards.push(card); return; }
|
|
4596
5206
|
const root = famRootOf(e);
|
|
4597
|
-
|
|
5207
|
+
// ★ 用户 2026-10-26:**用户分组优先** —— 被归进自己分组的插件,卡片直接住进那个分组区块
|
|
5208
|
+
// (像全家桶一样能展开看到卡片),官方 family 区块里不再重复渲染它。
|
|
5209
|
+
const userGroup = groupOfPackage(e.moduleName);
|
|
5210
|
+
if (userGroup !== null) {
|
|
5211
|
+
if (!userGroupBuckets.has(userGroup.id)) userGroupBuckets.set(userGroup.id, []);
|
|
5212
|
+
userGroupBuckets.get(userGroup.id).push(card);
|
|
5213
|
+
return;
|
|
5214
|
+
}
|
|
5215
|
+
if (root === null || familySize(root) <= 1) {
|
|
5216
|
+
nonFamilyCards.push(card);
|
|
5217
|
+
return;
|
|
5218
|
+
}
|
|
4598
5219
|
if (!familySections.has(root)) familySections.set(root, { head: familyHeaders.find((h) => h && h.key === "family-" + root) ?? null, cards: [] });
|
|
4599
5220
|
familySections.get(root).cards.push(card);
|
|
4600
5221
|
});
|
|
@@ -4659,7 +5280,131 @@ function healthSignature(health) {
|
|
|
4659
5280
|
),
|
|
4660
5281
|
),
|
|
4661
5282
|
));
|
|
4662
|
-
|
|
5283
|
+
// issue #22 ③(0.5.93 收口,用户定稿):用户自建分组在主列表里是**横框**,外观与官方 family
|
|
5284
|
+
// 区块**同一套视觉语言**(标题行 + 折叠箭头 + 容器 + 成员数徽章)。三条行为:
|
|
5285
|
+
// · 空分组(刚建出来、还没放插件)**也渲染** —— 否则"新建分组"在列表里根本看不到,
|
|
5286
|
+
// 等于点了没反应;
|
|
5287
|
+
// · 框里可以直接**加入 / 移出**插件(走 group-assign;服务端保证一个包只属于一个分组,
|
|
5288
|
+
// 移出只解除归属,不删插件、不动备注与徽章);
|
|
5289
|
+
// · **只含官方 family 成员**的分组仍不画(官方 family 优先:那些成员留在 family 区块里,
|
|
5290
|
+
// 绝不出现同一插件两处重复);
|
|
5291
|
+
// · 刚建出来的那个横框多挂一个 pc_groupNew(220ms 入场动画;prefers-reduced-motion 下不播)。
|
|
5292
|
+
// 数据来源只有服务端那一份 groups(annotationWrite 用响应里的全量状态覆盖 annotations),
|
|
5293
|
+
// 客户端额外只记一个"刚建好的 id"用于播动画,不维护第二份状态。
|
|
5294
|
+
const userGroupFrame = (group, bucket) => {
|
|
5295
|
+
const members = (Array.isArray(group.packages) ? group.packages : []).map((item) => String(item));
|
|
5296
|
+
const inGroup = new Set(members);
|
|
5297
|
+
const candidates = [];
|
|
5298
|
+
for (const entry of (state.status === "ready" ? state.data.entries : [])) {
|
|
5299
|
+
const name = String(entry.moduleName ?? "");
|
|
5300
|
+
if (name === "" || inGroup.has(name) || candidates.includes(name)) continue;
|
|
5301
|
+
candidates.push(name);
|
|
5302
|
+
}
|
|
5303
|
+
const fresh = groupJustCreated !== null && String(group.id) === String(groupJustCreated);
|
|
5304
|
+
const header = el("div", { className: styles.rowTop, style: { alignItems: "center" } },
|
|
5305
|
+
el("strong", { className: styles.name, style: { flex: "0 0 auto", whiteSpace: "nowrap" }, title: String(group.name) }, String(group.name)),
|
|
5306
|
+
el("span", { className: styles.tag, "data-user-badge": "true" }, t("userGroupTag") + " · " + members.length + " " + t("familySubs")),
|
|
5307
|
+
el("button", {
|
|
5308
|
+
type: "button",
|
|
5309
|
+
className: styles.toggle,
|
|
5310
|
+
style: { whiteSpace: "nowrap", flexShrink: 0 },
|
|
5311
|
+
disabled: annotationBusy === group.id,
|
|
5312
|
+
onClick: () => annotationWrite({ action: "group-collapse", id: group.id, collapsed: group.collapsed !== true }, null),
|
|
5313
|
+
}, group.collapsed === true ? "▸ " + t("familyExpand") : "▾ " + t("familyCollapse")),
|
|
5314
|
+
// ★ 用户 2026-10-27:分组框也要有「批量检测更新 / 一键启用已适配 / 删除分组」
|
|
5315
|
+
el("button", {
|
|
5316
|
+
type: "button",
|
|
5317
|
+
className: styles.toggle,
|
|
5318
|
+
style: { whiteSpace: "nowrap", flexShrink: 0 },
|
|
5319
|
+
disabled: annotationBusy === group.id,
|
|
5320
|
+
onClick: () => {
|
|
5321
|
+
// 批检:组内每个成员各查一次(单包接口复用,判据不分叉)
|
|
5322
|
+
setMessage(t("checkingUpdate"));
|
|
5323
|
+
const names = members.slice();
|
|
5324
|
+
Promise.all(names.map((nm) => call("/plugin-console/check-update", { packageName: nm }).catch(() => null)))
|
|
5325
|
+
.then((list) => {
|
|
5326
|
+
const hits = list.filter((d) => d !== null && typeof d?.target === "string" && d.target !== "")
|
|
5327
|
+
setMessage(hits.length > 0 ? t("familyUpdate") + ":" + hits.length + " / " + names.length : t("upToDate"));
|
|
5328
|
+
});
|
|
5329
|
+
},
|
|
5330
|
+
}, t("familyCheckUpdate")),
|
|
5331
|
+
el("button", {
|
|
5332
|
+
type: "button",
|
|
5333
|
+
className: styles.toggle,
|
|
5334
|
+
style: { whiteSpace: "nowrap", flexShrink: 0 },
|
|
5335
|
+
disabled: annotationBusy === group.id,
|
|
5336
|
+
onClick: () => {
|
|
5337
|
+
// 一键启用已适配:只对组内有 adoptable 的成员解锁
|
|
5338
|
+
const ready = bucket
|
|
5339
|
+
.map((card) => card?.key ?? null)
|
|
5340
|
+
.filter((x) => x !== null);
|
|
5341
|
+
const targets = (state.status === "ready" ? state.data.entries : [])
|
|
5342
|
+
.filter((e) => members.includes(String(e.moduleName ?? "")) && e.adoptable !== null && e.adoptable !== undefined);
|
|
5343
|
+
if (targets.length === 0) { setMessage(t("gateNoAdoptable")); return; }
|
|
5344
|
+
Promise.all(targets.map((e) => call("/plugin-console/adapt-unlock", { entryId: e.entryId }).catch(() => null)))
|
|
5345
|
+
.then(() => { setMessage(t("familyUnlockAll") + ":" + targets.length); refresh(); });
|
|
5346
|
+
},
|
|
5347
|
+
}, t("familyUnlockAll")),
|
|
5348
|
+
el("button", {
|
|
5349
|
+
type: "button",
|
|
5350
|
+
className: styles.toggle,
|
|
5351
|
+
style: { display: "none" },
|
|
5352
|
+
title: t("annotationsPanelTitle"),
|
|
5353
|
+
onClick: () => { openAnnotationsPanel(); },
|
|
5354
|
+
}, t("annotationsBtn")));
|
|
5355
|
+
const memberChips = members.length === 0
|
|
5356
|
+
? el("span", { className: styles.status }, t("groupNoMembers"))
|
|
5357
|
+
: members.map((name) => el("span", { key: "ugm-" + name, className: styles.tag, "data-usergroup-member": "true", title: name }, name,
|
|
5358
|
+
el("button", {
|
|
5359
|
+
type: "button",
|
|
5360
|
+
className: styles.tag,
|
|
5361
|
+
"data-more": "true",
|
|
5362
|
+
title: t("groupMemberRemove"),
|
|
5363
|
+
"aria-label": t("groupMemberRemove"),
|
|
5364
|
+
disabled: annotationBusy === group.id,
|
|
5365
|
+
onClick: () => annotationWrite({ action: "group-assign", id: group.id, packageName: name, member: false }, t("groupUnassigned")),
|
|
5366
|
+
}, "×")));
|
|
5367
|
+
const addSelect = el("select", {
|
|
5368
|
+
value: "",
|
|
5369
|
+
"aria-label": "groupMemberAdd",
|
|
5370
|
+
disabled: annotationBusy === group.id || candidates.length === 0,
|
|
5371
|
+
style: { font: "inherit", padding: "2px 6px", maxWidth: 320 },
|
|
5372
|
+
onChange: (event) => {
|
|
5373
|
+
const picked = String(event.target.value ?? "");
|
|
5374
|
+
if (picked === "") return;
|
|
5375
|
+
annotationWrite({ action: "group-assign", id: group.id, packageName: picked }, t("groupAssigned").replace("{name}", String(group.name)));
|
|
5376
|
+
},
|
|
5377
|
+
},
|
|
5378
|
+
el("option", { value: "" }, candidates.length === 0 ? t("groupMemberNone") : t("groupMemberPick")),
|
|
5379
|
+
candidates.map((name) => el("option", { key: "ugc-" + name, value: name }, name)));
|
|
5380
|
+
const body = el("div", { style: { flexDirection: "column", gap: 6, display: "flex", marginTop: 2 } },
|
|
5381
|
+
el("div", { className: styles.annotRow }, memberChips, addSelect),
|
|
5382
|
+
bucket.length > 0
|
|
5383
|
+
? el("ul", { className: styles.list }, bucket)
|
|
5384
|
+
: members.length > 0
|
|
5385
|
+
? el("p", { className: styles.status, "data-in-family": "true" }, t("groupInFamilyNote"))
|
|
5386
|
+
: null);
|
|
5387
|
+
return el("div", {
|
|
5388
|
+
key: "usergroup-" + group.id,
|
|
5389
|
+
className: styles.detail + (fresh ? " " + styles.groupNew : ""),
|
|
5390
|
+
"data-usergroup": "true",
|
|
5391
|
+
"data-group-id": String(group.id),
|
|
5392
|
+
"data-new-group": fresh ? "true" : undefined,
|
|
5393
|
+
style: { border: "1px solid var(--dsw-alias-state-business-primary)", borderRadius: 10, padding: "8px 12px", gridColumn: "1 / -1" },
|
|
5394
|
+
},
|
|
5395
|
+
header,
|
|
5396
|
+
group.collapsed === true ? null : body);
|
|
5397
|
+
};
|
|
5398
|
+
const userGroupSectionEls = [];
|
|
5399
|
+
for (const group of userGroups) {
|
|
5400
|
+
const bucket = userGroupBuckets.get(group.id) ?? [];
|
|
5401
|
+
const members = Array.isArray(group.packages) ? group.packages : [];
|
|
5402
|
+
// ★ 用户 2026-10-26:分组框**永远画出来**。此前"成员全在官方 family 里就跳过"会让用户
|
|
5403
|
+
// 建完分组后在主列表什么都看不到(既没框也没入场动画),观感等于"点了没反应"。
|
|
5404
|
+
// 同一插件仍不重复渲染:那些成员留在官方 family 区块里,这里只给一句如实说明。
|
|
5405
|
+
userGroupSectionEls.push(userGroupFrame(group, bucket));
|
|
5406
|
+
}
|
|
5407
|
+
const rows = state.status === "ready" ? [...nonFamilyCards, ...familySectionEls, ...userGroupSectionEls, ...pendingCards] : [];
|
|
4663
5408
|
const marketShown = market === null || market.status !== "ready"
|
|
4664
5409
|
? []
|
|
4665
5410
|
: officialOnly
|
|
@@ -5213,7 +5958,7 @@ function healthSignature(health) {
|
|
|
5213
5958
|
"aria-label": t("linksDevRootLabel"),
|
|
5214
5959
|
value: "",
|
|
5215
5960
|
disabled: linksPinBusy,
|
|
5216
|
-
onChange: (event) => { const picked = String(event.
|
|
5961
|
+
onChange: (event) => { const picked = String(event.target.value ?? ""); if (picked !== "") setLinksDir(devRootDraft(picked)); },
|
|
5217
5962
|
style: { flex: "0 1 300px", minWidth: 180, font: "inherit", padding: "4px 6px" },
|
|
5218
5963
|
},
|
|
5219
5964
|
el("option", { value: "" }, t("linksDevRootPick")),
|
|
@@ -5225,7 +5970,7 @@ function healthSignature(health) {
|
|
|
5225
5970
|
value: linksDir,
|
|
5226
5971
|
disabled: linksPinBusy,
|
|
5227
5972
|
"aria-label": t("linksDirPlaceholder"),
|
|
5228
|
-
onChange: (event) => setLinksDir(String(event.
|
|
5973
|
+
onChange: (event) => setLinksDir(String(event.target.value ?? "")),
|
|
5229
5974
|
style: { flex: 1, minWidth: 220, font: "inherit", padding: "4px 8px" },
|
|
5230
5975
|
}),
|
|
5231
5976
|
// ★ 只在宿主**确实**有系统目录对话框时才渲染(web 端没有 ⇒ 不显示,不是灰掉)
|
|
@@ -5306,6 +6051,231 @@ function healthSignature(health) {
|
|
|
5306
6051
|
el("div", { className: styles.rowTop, style: { justifyContent: "flex-end", gap: 8, marginTop: 10 } },
|
|
5307
6052
|
el("button", { type: "button", className: styles.toolItem, "data-busy": linksPanelBusy ? "true" : "false", onClick: () => { linksPanelLoad(); linksDevRootsLoad(); } }, linksPanelBusy ? t("linksPanelLoading") : t("linksPanelReload")))))
|
|
5308
6053
|
: null,
|
|
6054
|
+
annotationsPanelOpen
|
|
6055
|
+
? el("div", { className: styles.modalBackdrop, onClick: (event) => { if (event.target === event.currentTarget) setAnnotationsPanelOpen(false); } },
|
|
6056
|
+
el("div", { className: styles.modalCard, style: { maxWidth: 760, maxHeight: "82vh", overflow: "auto" } },
|
|
6057
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
6058
|
+
el("strong", { className: styles.name }, t("annotationsBtn")),
|
|
6059
|
+
el("button", { type: "button", className: styles.trashBtn, style: { fontSize: 14, padding: "4px 8px" }, title: t("closeModal"), "aria-label": t("closeModal"), onClick: () => setAnnotationsPanelOpen(false) }, "✕")),
|
|
6060
|
+
el("p", { className: styles.message }, t("annotationsPanelTitle")),
|
|
6061
|
+
annotations !== null && annotations.file !== null && annotations.file !== undefined
|
|
6062
|
+
? el("p", { className: styles.status }, String(annotations.file))
|
|
6063
|
+
: null,
|
|
6064
|
+
// 失败/降级一律有文字(绝不"点了一下没反应")
|
|
6065
|
+
annotationsNote !== null
|
|
6066
|
+
? el("p", { className: styles.message, "data-error": "true" }, annotationsNote)
|
|
6067
|
+
: null,
|
|
6068
|
+
// ── 分组管理:新建 / 重命名 / 删除(两段式)/ 撤销删除 ─────────────────
|
|
6069
|
+
el("div", { style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 6, paddingTop: 8, borderTop: "1px solid var(--dsw-alias-border-secondary, rgba(127,127,127,.18))" } },
|
|
6070
|
+
el("div", { className: styles.annotRow },
|
|
6071
|
+
el("input", {
|
|
6072
|
+
type: "text",
|
|
6073
|
+
value: groupDraftName,
|
|
6074
|
+
placeholder: t("groupCreatePlaceholder"),
|
|
6075
|
+
"aria-label": t("groupCreatePlaceholder"),
|
|
6076
|
+
disabled: annotationBusy !== null,
|
|
6077
|
+
style: { flex: 1, minWidth: 200, font: "inherit", padding: "4px 8px" },
|
|
6078
|
+
onChange: (event) => setGroupDraftName(String(event.target.value ?? "")),
|
|
6079
|
+
onKeyDown: (event) => { if (event.key === "Enter") { event.preventDefault(); createUserGroup(); } },
|
|
6080
|
+
}),
|
|
6081
|
+
el("button", { type: "button", className: styles.toggle, disabled: groupDraftName.trim() === "" || annotationBusy !== null, onClick: createUserGroup }, t("groupCreate"))),
|
|
6082
|
+
groupUndo !== null
|
|
6083
|
+
? el("div", { className: styles.annotRow },
|
|
6084
|
+
el("span", { className: styles.status, "data-pending": "true" }, t("groupDeleted").replace("{name}", String(groupUndo.group?.name ?? "")).replace("{n}", String(Array.isArray(groupUndo.group?.packages) ? groupUndo.group.packages.length : 0))),
|
|
6085
|
+
el("button", {
|
|
6086
|
+
type: "button",
|
|
6087
|
+
className: styles.toggle,
|
|
6088
|
+
disabled: annotationBusy !== null,
|
|
6089
|
+
onClick: () => {
|
|
6090
|
+
const record = groupUndo;
|
|
6091
|
+
setGroupUndo(null);
|
|
6092
|
+
annotationWrite({ action: "group-restore", group: record.group, index: record.index }, t("groupRestored").replace("{name}", String(record.group?.name ?? "")));
|
|
6093
|
+
},
|
|
6094
|
+
}, t("groupRestore")))
|
|
6095
|
+
: null,
|
|
6096
|
+
userGroups.length === 0
|
|
6097
|
+
? el("p", { className: styles.status }, t("groupEmptyHint"))
|
|
6098
|
+
: el("ul", { className: styles.list }, userGroups.map((group) => el("li", { key: "ug-" + group.id, className: styles.row },
|
|
6099
|
+
el("div", { className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
6100
|
+
el("strong", { className: styles.name, "data-group-name": String(group.id), style: { cursor: "pointer", borderBottom: "1px dotted currentColor" }, title: t("groupOpenHint"), onClick: () => { setPanelGroupOpen((prev) => (prev === group.id ? null : group.id)); setPanelGroupQuery(""); } }, String(group.name)),
|
|
6101
|
+
el("span", { className: styles.tag, "data-user-badge": "true" }, t("groupMembers").replace("{n}", String(Array.isArray(group.packages) ? group.packages.length : 0)))),
|
|
6102
|
+
el("div", { className: styles.annotRow },
|
|
6103
|
+
groupRenameEdit !== null && groupRenameEdit.id === group.id
|
|
6104
|
+
? el("div", { className: styles.annotRow },
|
|
6105
|
+
el("input", {
|
|
6106
|
+
type: "text",
|
|
6107
|
+
value: groupRenameEdit.draft,
|
|
6108
|
+
"aria-label": t("groupRename"),
|
|
6109
|
+
style: { font: "inherit", padding: "2px 6px" },
|
|
6110
|
+
onChange: (event) => setGroupRenameEdit({ id: group.id, draft: String(event.target.value ?? "") }),
|
|
6111
|
+
onKeyDown: (event) => {
|
|
6112
|
+
if (event.key === "Escape") { event.preventDefault(); setGroupRenameEdit(null); return; }
|
|
6113
|
+
if (event.key === "Enter") {
|
|
6114
|
+
event.preventDefault();
|
|
6115
|
+
const draft = groupRenameEdit.draft;
|
|
6116
|
+
setGroupRenameEdit(null);
|
|
6117
|
+
annotationWrite({ action: "group-rename", id: group.id, name: draft }, t("groupRenamed"));
|
|
6118
|
+
}
|
|
6119
|
+
},
|
|
6120
|
+
}),
|
|
6121
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setGroupRenameEdit(null) }, t("linksCancel")))
|
|
6122
|
+
: el("button", { type: "button", className: styles.toggle, disabled: annotationBusy === group.id, onClick: () => setGroupRenameEdit({ id: group.id, draft: String(group.name) }) }, t("groupRename")),
|
|
6123
|
+
groupDeleteArm === group.id
|
|
6124
|
+
? el("div", { className: styles.annotRow },
|
|
6125
|
+
el("span", { className: styles.status, "data-pending": "true" }, t("groupDeleteConfirm").replace("{name}", String(group.name))),
|
|
6126
|
+
el("button", { type: "button", className: styles.toggle, onClick: () => setGroupDeleteArm(null) }, t("linksCancel")),
|
|
6127
|
+
el("button", {
|
|
6128
|
+
type: "button",
|
|
6129
|
+
className: styles.toggle,
|
|
6130
|
+
disabled: annotationBusy === group.id,
|
|
6131
|
+
onClick: () => {
|
|
6132
|
+
setGroupDeleteArm(null);
|
|
6133
|
+
const memberCount = Array.isArray(group.packages) ? group.packages.length : 0;
|
|
6134
|
+
// 两段式:第一次点只是"上膛",这里才是真删(且**只解除归属**)
|
|
6135
|
+
annotationWrite({ action: "group-delete", id: group.id }, t("groupDeleted").replace("{name}", String(group.name)).replace("{n}", String(memberCount))).then((data) => {
|
|
6136
|
+
const removed = data?.deleted;
|
|
6137
|
+
if (removed !== null && removed !== undefined && removed.group !== null && removed.group !== undefined) setGroupUndo({ group: removed.group, index: Number.isFinite(removed.index) ? removed.index : null });
|
|
6138
|
+
});
|
|
6139
|
+
},
|
|
6140
|
+
}, t("confirmDelete")))
|
|
6141
|
+
: el("button", { type: "button", className: styles.trashBtn, title: t("groupRemove"), "aria-label": t("groupRemove"), disabled: annotationBusy === group.id, onClick: () => setGroupDeleteArm(group.id) }, "✕")))))),
|
|
6142
|
+
// ★ 2026-10-27:点分组名 → 展开「成员 + 一键更新 + 搜索添加(含备注)」
|
|
6143
|
+
(() => {
|
|
6144
|
+
const open = userGroups.find((g) => g.id === panelGroupOpen) ?? null;
|
|
6145
|
+
if (open === null) {
|
|
6146
|
+
return el("p", { className: styles.status, "data-panel-groupadd-hint": "true" }, t("groupOpenHint"));
|
|
6147
|
+
}
|
|
6148
|
+
const inGroup = new Set((Array.isArray(open.packages) ? open.packages : []).map((x) => String(x)));
|
|
6149
|
+
const noteOf = (nm) => {
|
|
6150
|
+
const rec = annotations !== null && annotations.plugins !== null && annotations.plugins !== undefined
|
|
6151
|
+
? annotations.plugins[nm] ?? null
|
|
6152
|
+
: null;
|
|
6153
|
+
return typeof rec?.note === "string" ? rec.note : "";
|
|
6154
|
+
};
|
|
6155
|
+
const q = panelGroupQuery.trim().toLowerCase();
|
|
6156
|
+
const pool = (state.status === "ready" ? state.data.entries : [])
|
|
6157
|
+
.map((e) => String(e.moduleName ?? ""))
|
|
6158
|
+
.filter((nm) => nm !== "" && !nm.startsWith("cordis:") && !inGroup.has(nm))
|
|
6159
|
+
// ★ 用户 2026-10-27:搜索要能命中**备注**(不只是包名)
|
|
6160
|
+
.filter((nm) => q === "" || nm.toLowerCase().includes(q) || noteOf(nm).toLowerCase().includes(q));
|
|
6161
|
+
const shown = pool.slice(0, panelGroupShow);
|
|
6162
|
+
const hidden = Math.max(0, pool.length - shown.length);
|
|
6163
|
+
const chips = [...inGroup].map((nm) => el("span", { key: "pgm-" + nm, className: styles.tag, "data-usergroup-member": "true" },
|
|
6164
|
+
nm,
|
|
6165
|
+
el("button", {
|
|
6166
|
+
type: "button",
|
|
6167
|
+
className: styles.tag,
|
|
6168
|
+
"data-more": "true",
|
|
6169
|
+
title: t("groupMemberRemove"),
|
|
6170
|
+
onClick: () => annotationWrite({ action: "group-assign", id: open.id, packageName: nm, member: false }, t("groupUnassigned")),
|
|
6171
|
+
}, "×")))
|
|
6172
|
+
const headerRow = el("div", { className: styles.annotRow },
|
|
6173
|
+
el("strong", { className: styles.name }, String(open.name)),
|
|
6174
|
+
el("span", { className: styles.status }, t("groupMembers").replace("{n}", String(inGroup.size))),
|
|
6175
|
+
el("button", {
|
|
6176
|
+
type: "button",
|
|
6177
|
+
className: styles.toggle,
|
|
6178
|
+
"data-group-update-all": String(open.id),
|
|
6179
|
+
title: t("groupUpdateAllTip"),
|
|
6180
|
+
onClick: () => {
|
|
6181
|
+
const members2 = [...inGroup];
|
|
6182
|
+
if (members2.length === 0) { setMessage(t("groupNoMembers")); return; }
|
|
6183
|
+
// 逐个串行:先查目标版本,再按那个版本更新(与单卡同一判据),门控照旧生效
|
|
6184
|
+
const step = (i, hit) => {
|
|
6185
|
+
if (i >= members2.length) {
|
|
6186
|
+
setMessage(t("groupUpdateAllDone").replace("{hit}", String(hit)).replace("{n}", String(members2.length)));
|
|
6187
|
+
return;
|
|
6188
|
+
}
|
|
6189
|
+
const nm = members2[i];
|
|
6190
|
+
setMessage(t("groupUpdateAllProgress").replace("{i}", String(i + 1)).replace("{n}", String(members2.length)) + " — " + nm);
|
|
6191
|
+
call("/plugin-console/check-update", { packageName: nm }).then((d) => {
|
|
6192
|
+
const target = typeof d?.target === "string" && d.target !== "" ? d.target : (typeof d?.latest === "string" ? d.latest : null);
|
|
6193
|
+
if (target !== null) startJob("", nm, "github", "plugin", true, null, target);
|
|
6194
|
+
step(i + 1, target !== null ? hit + 1 : hit);
|
|
6195
|
+
}, () => step(i + 1, hit));
|
|
6196
|
+
};
|
|
6197
|
+
step(0, 0);
|
|
6198
|
+
},
|
|
6199
|
+
}, t("groupUpdateAll")))
|
|
6200
|
+
const searchInput = el("input", {
|
|
6201
|
+
type: "search",
|
|
6202
|
+
value: panelGroupQuery,
|
|
6203
|
+
placeholder: t("groupAddSearchPlaceholder"),
|
|
6204
|
+
"aria-label": t("groupAddSearchPlaceholder"),
|
|
6205
|
+
style: { font: "inherit", padding: "4px 8px", width: "100%" },
|
|
6206
|
+
onChange: (event) => setPanelGroupQuery(String(event.target.value ?? "")),
|
|
6207
|
+
})
|
|
6208
|
+
const candRows = shown.map((nm) => {
|
|
6209
|
+
const note = noteOf(nm);
|
|
6210
|
+
return el("li", { key: "pgc-" + nm, className: styles.row },
|
|
6211
|
+
el("div", { className: styles.rowTop },
|
|
6212
|
+
el("strong", { className: styles.name, title: nm, style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, nm),
|
|
6213
|
+
note !== ""
|
|
6214
|
+
? el("span", { style: { color: "var(--dsw-alias-state-business-primary)", fontWeight: 500, fontSize: ".9em", whiteSpace: "nowrap", maxWidth: "45%", overflow: "hidden", textOverflow: "ellipsis" } }, note)
|
|
6215
|
+
: null,
|
|
6216
|
+
el("button", {
|
|
6217
|
+
type: "button",
|
|
6218
|
+
className: styles.toggle,
|
|
6219
|
+
"data-group-add": nm,
|
|
6220
|
+
disabled: annotationBusy !== null,
|
|
6221
|
+
onClick: () => annotationWrite({ action: "group-assign", id: open.id, packageName: nm }, t("groupAssigned").replace("{name}", String(open.name))),
|
|
6222
|
+
}, "+ " + t("groupAddOne"))));
|
|
6223
|
+
})
|
|
6224
|
+
const candList = el("ul", { className: styles.list, "data-group-candidates": "true" },
|
|
6225
|
+
pool.length === 0
|
|
6226
|
+
? el("li", { className: styles.row }, el("span", { className: styles.status }, t("groupAddNoMatch")))
|
|
6227
|
+
: candRows)
|
|
6228
|
+
const moreBtn = hidden > 0
|
|
6229
|
+
? el("button", { type: "button", className: styles.toggle, "data-group-loadmore": "true", onClick: () => setPanelGroupShow((prev) => prev + 20) }, t("groupLoadMore").replace("{n}", String(hidden)))
|
|
6230
|
+
: null
|
|
6231
|
+
return el("div", { "data-panel-groupadd": String(open.id), style: { display: "flex", flexDirection: "column", gap: 6, marginTop: 6 } },
|
|
6232
|
+
headerRow,
|
|
6233
|
+
el("div", { className: styles.annotRow }, chips.length > 0 ? chips : el("span", { className: styles.status }, t("groupNoMembers"))),
|
|
6234
|
+
searchInput,
|
|
6235
|
+
candList,
|
|
6236
|
+
moreBtn)
|
|
6237
|
+
})(),
|
|
6238
|
+
// ── 插件行:备注 / 徽章 / 分组(每行一套,与详情页同一个编辑块)───────────
|
|
6239
|
+
el("p", { className: styles.message, style: { marginTop: 8 } },
|
|
6240
|
+
annotations === null
|
|
6241
|
+
? t("annotationsEmpty")
|
|
6242
|
+
: t("annotationsStats")
|
|
6243
|
+
.replace("{p}", String(annotations.stats?.packages ?? 0))
|
|
6244
|
+
.replace("{b}", String(annotations.stats?.badges ?? 0))
|
|
6245
|
+
.replace("{g}", String(annotations.stats?.groups ?? 0))),
|
|
6246
|
+
// ★ 0.5.93(issue #22 ②,用户定稿):列表**默认收起**,只留一行摘要「插件列表(N)· 点此展开」;
|
|
6247
|
+
// 点开才渲染搜索框与插件行。查询词是受控 state ⇒ 边打边筛,且过滤不会清掉输入框里的字。
|
|
6248
|
+
el("button", {
|
|
6249
|
+
type: "button",
|
|
6250
|
+
className: styles.toggle,
|
|
6251
|
+
"aria-label": "annotationsListToggle",
|
|
6252
|
+
"aria-expanded": annotationsListOpen ? "true" : "false",
|
|
6253
|
+
"data-open": annotationsListOpen ? "true" : "false",
|
|
6254
|
+
onClick: () => setAnnotationsListOpen((prev) => !prev),
|
|
6255
|
+
}, (annotationsListOpen ? t("annotationsListHide") : t("annotationsListShow"))
|
|
6256
|
+
.replace("{n}", String(annotationListEntries.length))),
|
|
6257
|
+
annotationsListOpen === false
|
|
6258
|
+
? null
|
|
6259
|
+
: el("div", { style: { flexDirection: "column", gap: 8, display: "flex", marginTop: 6 } },
|
|
6260
|
+
el("input", {
|
|
6261
|
+
type: "text",
|
|
6262
|
+
value: annotationsQuery,
|
|
6263
|
+
placeholder: t("annotationsSearchPlaceholder"),
|
|
6264
|
+
"aria-label": "annotationsSearch",
|
|
6265
|
+
style: { font: "inherit", padding: "4px 8px", borderRadius: 6 },
|
|
6266
|
+
onChange: (event) => setAnnotationsQuery(String(event.target.value ?? "")),
|
|
6267
|
+
}),
|
|
6268
|
+
annotationListEntries.length === 0
|
|
6269
|
+
? el("p", { className: styles.status }, t("empty"))
|
|
6270
|
+
: annotationListFiltered.length === 0
|
|
6271
|
+
? el("p", { className: styles.status, "data-nomatch": "true" }, t("annotationsNoMatch"))
|
|
6272
|
+
: el("ul", { className: styles.list, "data-annotlist": "true" }, annotationListFiltered.map((entry) => el("li", { key: "an-" + entry.entryId, className: styles.row },
|
|
6273
|
+
el("div", { className: styles.rowTop },
|
|
6274
|
+
el("strong", { className: styles.name, title: entry.moduleName }, moduleShortName(entry.moduleName)),
|
|
6275
|
+
entry.extra === true ? el("span", { className: styles.tag, "data-user": "true" }, t("extraTag")) : null),
|
|
6276
|
+
el("div", { className: styles.meta }, el("code", null, String(entry.moduleName ?? ""))),
|
|
6277
|
+
annotationBlock(entry)))))))
|
|
6278
|
+
: null,
|
|
5309
6279
|
gateOpen
|
|
5310
6280
|
? el("div", { className: styles.modalBackdrop, onClick: (event) => { if (event.target === event.currentTarget) setGateOpen(false); } },
|
|
5311
6281
|
el("div", { className: styles.modalCard, style: { maxWidth: 520 } },
|
|
@@ -5412,7 +6382,7 @@ function healthSignature(health) {
|
|
|
5412
6382
|
aiJob === null || (aiJob.status === "running" && aiJob.stage === "planning")
|
|
5413
6383
|
? el("div", { style: { flexDirection: "column", gap: "10px", display: "flex" } },
|
|
5414
6384
|
el("section", null,
|
|
5415
|
-
el("input", { type: "text", placeholder: t("aiEmpowerSrcTitle"), value: aiSource, disabled: aiRunning, onChange: (event) => setAiSource(event.
|
|
6385
|
+
el("input", { type: "text", placeholder: t("aiEmpowerSrcTitle"), value: aiSource, disabled: aiRunning, onChange: (event) => setAiSource(event.target.value), onKeyDown: (event) => { if (event.key === "Enter" && !aiRunning) aiStartPlan(); }, style: { width: "100%", boxSizing: "border-box", font: "inherit", padding: "6px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-1)", color: "var(--dsw-alias-label-primary)" } }),
|
|
5416
6386
|
el("div", { className: styles.rowTop },
|
|
5417
6387
|
el("button", { type: "button", className: styles.toggle, disabled: aiRunning || aiSource.trim() === "", onClick: aiStartPlan }, t("aiEmpowerLabel")),
|
|
5418
6388
|
aiRunning ? el("span", { className: styles.message }, t("aiEmpowerWaiting") + (aiJob?.source ? "(" + aiJob.source + ")" : "")) : null,
|
|
@@ -5435,7 +6405,7 @@ function healthSignature(health) {
|
|
|
5435
6405
|
el("div", { style: { flexDirection: "column", gap: "6px", display: "flex", maxHeight: 220, overflowY: "auto" } },
|
|
5436
6406
|
(aiJob.steps ?? []).map((s) =>
|
|
5437
6407
|
el("label", { key: s.index, className: styles.consentRemember, style: { alignItems: "flex-start" } },
|
|
5438
|
-
el("input", { type: "checkbox", checked: aiSelected[s.index] === true, onChange: (event) => setAiSelected((prev) => ({ ...prev, [s.index]: event.
|
|
6408
|
+
el("input", { type: "checkbox", checked: aiSelected[s.index] === true, onChange: (event) => setAiSelected((prev) => ({ ...prev, [s.index]: event.target.checked })) }),
|
|
5439
6409
|
el("span", null, (s.index + 1) + ". [" + s.action + "] " + (s.description ?? "") + (s.path ? " → " + s.path : "")))),
|
|
5440
6410
|
el("div", { className: styles.rowTop },
|
|
5441
6411
|
el("button", { type: "button", className: styles.toggle, onClick: aiRun }, t("aiEmpowerRun")),
|
|
@@ -5456,10 +6426,10 @@ function healthSignature(health) {
|
|
|
5456
6426
|
el("p", { className: styles.message }, repoLandTitleText()),
|
|
5457
6427
|
el("div", { className: styles.rowTop, style: { flexWrap: "wrap" } },
|
|
5458
6428
|
el("span", { className: styles.message }, t("repoLandDirLabel") + ":" + (repoLandDir || "—")),
|
|
5459
|
-
el("input", { type: "text", value: repoLandDirInput, onChange: (event) => setRepoLandDirInput(event.
|
|
6429
|
+
el("input", { type: "text", value: repoLandDirInput, onChange: (event) => setRepoLandDirInput(event.target.value), style: { flex: 1, minWidth: 120, font: "inherit", padding: "4px 8px", borderRadius: 6, border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-1)", color: "var(--dsw-alias-label-primary)" } }),
|
|
5460
6430
|
el("button", { type: "button", className: styles.toggle, onClick: repoLandSaveDir }, t("repoLandSaveDir"))),
|
|
5461
6431
|
el("section", null,
|
|
5462
|
-
el("input", { type: "text", placeholder: t("repoLandPlaceholder"), value: repoLandInput, disabled: repoLandBusy, onChange: (event) => setRepoLandInput(event.
|
|
6432
|
+
el("input", { type: "text", placeholder: t("repoLandPlaceholder"), value: repoLandInput, disabled: repoLandBusy, onChange: (event) => setRepoLandInput(event.target.value), onKeyDown: (event) => { if (event.key === "Enter" && !repoLandBusy) repoLandStart(); }, style: { width: "100%", boxSizing: "border-box", font: "inherit", padding: "6px 10px", borderRadius: 8, border: "1px solid var(--dsw-alias-border-l2)", background: "var(--dsw-alias-bg-layer-1)", color: "var(--dsw-alias-label-primary)" } }),
|
|
5463
6433
|
el("div", { className: styles.rowTop },
|
|
5464
6434
|
el("button", { type: "button", className: styles.toggle, disabled: repoLandBusy || repoLandInput.trim() === "", onClick: repoLandStart }, repoLandBusy ? t("repoLandBusy") : t("repoLandLabel"))),
|
|
5465
6435
|
repoLandResult !== null
|
|
@@ -5716,7 +6686,7 @@ function healthSignature(health) {
|
|
|
5716
6686
|
type: "password",
|
|
5717
6687
|
placeholder: t("githubTokenPlaceholder"),
|
|
5718
6688
|
value: ghToken,
|
|
5719
|
-
onChange: (event) => setGhToken(event.
|
|
6689
|
+
onChange: (event) => setGhToken(event.target.value),
|
|
5720
6690
|
style: { width: "100%", boxSizing: "border-box" },
|
|
5721
6691
|
}),
|
|
5722
6692
|
el("button", { type: "button", className: styles.toggle, disabled: ghLoginBusy || ghToken.trim() === "", onClick: githubLogin }, t("githubLoginBtn")))
|
|
@@ -5748,9 +6718,12 @@ function healthSignature(health) {
|
|
|
5748
6718
|
dragRef.current.base = cur;
|
|
5749
6719
|
dragRef.current.active = false;
|
|
5750
6720
|
dragRef.current.justDragged = false;
|
|
5751
|
-
|
|
6721
|
+
// ★ 0.5.91:不要在这里捕获指针(会把内部按钮的 click 吞到容器上)。先记下,长按确认拖动后再捕获。
|
|
6722
|
+
dragRef.current.captureEl = e.currentTarget;
|
|
6723
|
+
dragRef.current.captureId = e.pointerId;
|
|
5752
6724
|
dragRef.current.long = window.setTimeout(() => {
|
|
5753
6725
|
dragRef.current.active = true;
|
|
6726
|
+
try { dragRef.current.captureEl?.setPointerCapture?.(dragRef.current.captureId); } catch {}
|
|
5754
6727
|
setDragPos(cur);
|
|
5755
6728
|
}, 450);
|
|
5756
6729
|
},
|
|
@@ -5766,6 +6739,7 @@ function healthSignature(health) {
|
|
|
5766
6739
|
},
|
|
5767
6740
|
onPointerUp: () => {
|
|
5768
6741
|
if (dragRef.current.long !== null) { window.clearTimeout(dragRef.current.long); dragRef.current.long = null; }
|
|
6742
|
+
try { dragRef.current.captureEl?.releasePointerCapture?.(dragRef.current.captureId); } catch {}
|
|
5769
6743
|
if (dragRef.current.active) {
|
|
5770
6744
|
const p = dragPos;
|
|
5771
6745
|
if (p !== null) {
|
|
@@ -5795,6 +6769,16 @@ function healthSignature(health) {
|
|
|
5795
6769
|
toolbarOpen
|
|
5796
6770
|
? el("div", { className: styles.toolbarGroup, style: (dragPos ?? toolbarPos) !== null ? { left: (dragPos ?? toolbarPos).left, top: (dragPos ?? toolbarPos).top + 36, right: "auto" } : undefined },
|
|
5797
6771
|
|
|
6772
|
+
el("button", {
|
|
6773
|
+
type: "button",
|
|
6774
|
+
className: styles.toolItem,
|
|
6775
|
+
"data-active": annotationsPanelOpen ? "true" : "false",
|
|
6776
|
+
title: t("annotationsPanelTitle"),
|
|
6777
|
+
"aria-label": t("annotationsBtn"),
|
|
6778
|
+
// issue #22 ②③:打开即读一次(注解是独立文件,别处改了也能看到最新的);
|
|
6779
|
+
// ★ 0.5.93:打开时列表回到**收起**状态(见 openAnnotationsPanel)
|
|
6780
|
+
onClick: () => toggleAnnotationsPanel(),
|
|
6781
|
+
}, t("annotationsBtn") + (annotations !== null && annotations.stats !== null && annotations.stats !== undefined && annotations.stats.packages > 0 ? " " + annotations.stats.packages : "")),
|
|
5798
6782
|
hostedByShell ? null : el("button", { // ★ 0.5.84:桌面端(shell 托管)不显示;web 端保留且可用
|
|
5799
6783
|
type: "button",
|
|
5800
6784
|
className: styles.toolItem,
|
|
@@ -5949,7 +6933,7 @@ function healthSignature(health) {
|
|
|
5949
6933
|
: t("searchPlaceholderOther").replace("{source}", sourceDisplayName(searchSource))),
|
|
5950
6934
|
"aria-label": t("search"),
|
|
5951
6935
|
onChange: (event) => {
|
|
5952
|
-
const v = event.
|
|
6936
|
+
const v = event.target.value;
|
|
5953
6937
|
setQuery(v);
|
|
5954
6938
|
try { localStorage.setItem("pc-market-query", v); } catch {}
|
|
5955
6939
|
},
|
|
@@ -6167,6 +7151,49 @@ function healthSignature(health) {
|
|
|
6167
7151
|
el("h3", null, mode === "skills" ? t("installedSkillsTitle") : t("installedTitle")),
|
|
6168
7152
|
mode === "plugins"
|
|
6169
7153
|
? el("button", { type: "button", className: styles.toggle, onClick: () => setInstalledSearchOpen((v) => !v) }, t("search"))
|
|
7154
|
+
: null,
|
|
7155
|
+
// ★ issue #22 ①:一键检测全部更新(**只有图标**的圆按钮 ↑↑↑,与「搜索」同高)。
|
|
7156
|
+
// 三态都在按钮上:idle(可点)/ checking(转圈 + 禁用)/ done(描边高亮,再点是重查)。
|
|
7157
|
+
// 判据全在服务端(唯一判据 + 并发上限),这里只负责"问一句、如实显示"。
|
|
7158
|
+
mode === "plugins"
|
|
7159
|
+
? el("button", {
|
|
7160
|
+
type: "button",
|
|
7161
|
+
className: styles.iconBtn,
|
|
7162
|
+
title: batchCheck !== null && batchCheck.status === "checking"
|
|
7163
|
+
? t("checkingAllUpdates").replace("{n}", String(batchCheck.total ?? 0))
|
|
7164
|
+
: t("checkAllUpdates") + ":" + t("checkAllUpdatesHint").replace("{n}", String(state.status === "ready" ? state.data.entries.length : 0)),
|
|
7165
|
+
"aria-label": t("checkAllUpdates"),
|
|
7166
|
+
"data-state": batchCheck !== null && batchCheck.status === "checking" ? "checking" : (batchCheck !== null && batchCheck.status === "ready" ? "done" : "idle"),
|
|
7167
|
+
disabled: (batchCheck !== null && batchCheck.status === "checking") || state.status !== "ready",
|
|
7168
|
+
onClick: checkAllUpdates,
|
|
7169
|
+
}, batchCheck !== null && batchCheck.status === "checking"
|
|
7170
|
+
? el("span", { className: styles.spinner })
|
|
7171
|
+
: el("svg", { width: 13, height: 13, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.2, strokeLinecap: "round", strokeLinejoin: "round" },
|
|
7172
|
+
el("path", { d: "M5 20V7" }), el("polyline", { points: "3 9 5 7 7 9" }),
|
|
7173
|
+
el("path", { d: "M12 20V7" }), el("polyline", { points: "10 9 12 7 14 9" }),
|
|
7174
|
+
el("path", { d: "M19 20V7" }), el("polyline", { points: "17 9 19 7 21 9" })))
|
|
7175
|
+
: null,
|
|
7176
|
+
// 结果态:可更新 > 0 ⇒ 可点的筛选按钮(同一个筛选的开关);可更新 = 0 ⇒ **只读**小结,
|
|
7177
|
+
// 不做成按钮(否则就是"点了一下列表变空"的死按钮)。
|
|
7178
|
+
mode === "plugins" && batchCheck !== null && batchCheck.status === "ready" && batchCheck.updatable > 0
|
|
7179
|
+
? el("button", {
|
|
7180
|
+
type: "button",
|
|
7181
|
+
className: styles.extraBtn,
|
|
7182
|
+
title: t("batchUpdateResultTip")
|
|
7183
|
+
.replace("{n}", String(batchCheck.updatable))
|
|
7184
|
+
.replace("{m}", String(Math.max(0, batchCheck.checked - batchCheck.updatable - batchCheck.unknown)))
|
|
7185
|
+
.replace("{k}", String(batchCheck.unknown))
|
|
7186
|
+
.replace("{at}", new Date(batchCheck.at).toLocaleTimeString()),
|
|
7187
|
+
"aria-pressed": onlyUpdatable ? "true" : "false",
|
|
7188
|
+
onClick: () => setOnlyUpdatable((v) => !v),
|
|
7189
|
+
}, t("batchUpdateOnly") + " · " + batchCheck.updatable + (onlyUpdatable ? " ✓" : ""))
|
|
7190
|
+
: null,
|
|
7191
|
+
mode === "plugins" && batchCheck !== null && batchCheck.status === "ready" && batchCheck.updatable === 0
|
|
7192
|
+
? el("span", { className: styles.tag, "data-enabled": "true", title: t("batchUpdateResultTip").replace("{n}", "0").replace("{m}", String(Math.max(0, batchCheck.checked - batchCheck.unknown))).replace("{k}", String(batchCheck.unknown)).replace("{at}", new Date(batchCheck.at).toLocaleTimeString()) },
|
|
7193
|
+
t("checkAllUpdateNone") + (batchCheck.unknown > 0 ? "(" + t("batchUpdateUnknown") + " " + batchCheck.unknown + ")" : ""))
|
|
7194
|
+
: null,
|
|
7195
|
+
mode === "plugins" && batchCheck !== null && batchCheck.status === "error"
|
|
7196
|
+
? el("span", { className: styles.tag, "data-pending": "true", title: String(batchCheck.error ?? "") }, t("checkAllUpdateFailed"))
|
|
6170
7197
|
: null),
|
|
6171
7198
|
mode === "plugins" && installedSearchOpen
|
|
6172
7199
|
? el("div", { className: styles.search },
|
|
@@ -6177,7 +7204,7 @@ function healthSignature(health) {
|
|
|
6177
7204
|
placeholder: t("searchInstalledPlaceholder"),
|
|
6178
7205
|
"aria-label": t("searchInstalledPlaceholder"),
|
|
6179
7206
|
onChange: (event) => {
|
|
6180
|
-
const v = event.
|
|
7207
|
+
const v = event.target.value;
|
|
6181
7208
|
setInstalledQuery(v);
|
|
6182
7209
|
try { localStorage.setItem("pc-market-installed-q", v); } catch {}
|
|
6183
7210
|
},
|
|
@@ -6257,9 +7284,13 @@ function healthSignature(health) {
|
|
|
6257
7284
|
? el("p", { className: styles.status }, t("empty"))
|
|
6258
7285
|
: installedQueryNorm !== "" && rows.length === 0
|
|
6259
7286
|
? el("p", { className: styles.status }, t("noMatch"))
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
7287
|
+
// issue #22 ①:把「仅看可更新」筛空的话必须说清楚**是筛空的**,
|
|
7288
|
+
// 否则用户看到空列表会以为插件没了(并给出取消筛选的办法)。
|
|
7289
|
+
: onlyUpdatable && rows.length === 0
|
|
7290
|
+
? el("p", { className: styles.status }, t("batchUpdateFilterEmpty"))
|
|
7291
|
+
: extraOnly && rows.length === 0
|
|
7292
|
+
? el("p", { className: styles.status }, t("extraEmpty"))
|
|
7293
|
+
: el("ul", { className: styles.list }, rows)),
|
|
6263
7294
|
showBackTop
|
|
6264
7295
|
? el("button", {
|
|
6265
7296
|
type: "button",
|
|
@@ -6307,7 +7338,7 @@ function healthSignature(health) {
|
|
|
6307
7338
|
type: "checkbox",
|
|
6308
7339
|
checked: aiRemember,
|
|
6309
7340
|
onChange: (event) => {
|
|
6310
|
-
const checked = event.
|
|
7341
|
+
const checked = event.target.checked;
|
|
6311
7342
|
setAiRemember(checked);
|
|
6312
7343
|
try { localStorage.setItem("pc-ai-remember", checked ? "1" : "0"); } catch {}
|
|
6313
7344
|
},
|
|
@@ -6335,8 +7366,8 @@ function healthSignature(health) {
|
|
|
6335
7366
|
...sourcesData.registries.map((src) =>
|
|
6336
7367
|
editReg !== null && editReg.id === src.id
|
|
6337
7368
|
? el("div", { key: src.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
6338
|
-
el("input", { type: "text", value: editReg.name, onChange: (event) => setEditReg({ ...editReg, name: event.
|
|
6339
|
-
el("input", { type: "text", value: editReg.url, onChange: (event) => setEditReg({ ...editReg, url: event.
|
|
7369
|
+
el("input", { type: "text", value: editReg.name, onChange: (event) => setEditReg({ ...editReg, name: event.target.value }), style: { flex: 1, minWidth: 80 } }),
|
|
7370
|
+
el("input", { type: "text", value: editReg.url, onChange: (event) => setEditReg({ ...editReg, url: event.target.value }), style: { flex: 2, minWidth: 120 } }),
|
|
6340
7371
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: saveEditReg }, t("saveSource")),
|
|
6341
7372
|
el("button", { type: "button", className: styles.toggle, onClick: () => setEditReg(null) }, t("cancelEdit")))
|
|
6342
7373
|
: el("div", { key: src.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
@@ -6362,8 +7393,8 @@ function healthSignature(health) {
|
|
|
6362
7393
|
src.primary ? null : el("button", { type: "button", className: styles.toggle, title: t("setPrimaryHint"), disabled: sourcesBusy, onClick: () => sourcesAction({ action: "set-primary", id: src.id }) }, t("setPrimary")),
|
|
6363
7394
|
el("button", { type: "button", className: styles.toggle, title: t("editSourceHint"), disabled: sourcesBusy, onClick: () => setEditReg({ id: src.id, name: src.name, url: src.url }) }, t("editSource"))))),
|
|
6364
7395
|
el("div", { className: styles.rowTop },
|
|
6365
|
-
el("input", { type: "text", placeholder: t("sourceName"), value: sourceName, onChange: (event) => setSourceName(event.
|
|
6366
|
-
el("input", { type: "text", placeholder: t("sourceUrl"), value: sourceUrl, onChange: (event) => setSourceUrl(event.
|
|
7396
|
+
el("input", { type: "text", placeholder: t("sourceName"), value: sourceName, onChange: (event) => setSourceName(event.target.value), style: { flex: 1, minWidth: 0 } }),
|
|
7397
|
+
el("input", { type: "text", placeholder: t("sourceUrl"), value: sourceUrl, onChange: (event) => setSourceUrl(event.target.value), style: { flex: 2, minWidth: 0 } }),
|
|
6367
7398
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: addSource }, t("addSource"))),
|
|
6368
7399
|
el("div", { className: styles.rowTop },
|
|
6369
7400
|
el("button", {
|
|
@@ -6385,8 +7416,8 @@ function healthSignature(health) {
|
|
|
6385
7416
|
(sourcesData.indexSources ?? []).map((s) =>
|
|
6386
7417
|
editIndex !== null && editIndex.id === s.id
|
|
6387
7418
|
? el("div", { key: s.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
6388
|
-
el("input", { type: "text", value: editIndex.name, onChange: (event) => setEditIndex({ ...editIndex, name: event.
|
|
6389
|
-
el("input", { type: "text", value: editIndex.url, onChange: (event) => setEditIndex({ ...editIndex, url: event.
|
|
7419
|
+
el("input", { type: "text", value: editIndex.name, onChange: (event) => setEditIndex({ ...editIndex, name: event.target.value }), style: { flex: 1, minWidth: 80 } }),
|
|
7420
|
+
el("input", { type: "text", value: editIndex.url, onChange: (event) => setEditIndex({ ...editIndex, url: event.target.value }), style: { flex: 2, minWidth: 120 } }),
|
|
6390
7421
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: saveEditIndex }, t("saveSource")),
|
|
6391
7422
|
el("button", { type: "button", className: styles.toggle, onClick: () => setEditIndex(null) }, t("cancelEdit")))
|
|
6392
7423
|
: el("div", { key: s.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
@@ -6398,8 +7429,8 @@ function healthSignature(health) {
|
|
|
6398
7429
|
el("button", { type: "button", className: styles.toggle, title: t("editSourceHint"), disabled: sourcesBusy, onClick: () => setEditIndex({ id: s.id, name: s.name, url: s.url }) }, t("editSource")),
|
|
6399
7430
|
el("button", { type: "button", className: styles.toggle, title: t("removeSourceHint"), disabled: sourcesBusy, onClick: () => sourcesAction({ action: "remove-index", id: s.id }) }, t("removeSource"))))),
|
|
6400
7431
|
el("div", { className: styles.rowTop },
|
|
6401
|
-
el("input", { type: "text", placeholder: t("sourceName"), value: indexSourceName, onChange: (event) => setIndexSourceName(event.
|
|
6402
|
-
el("input", { type: "text", placeholder: t("indexUrlPlaceholder"), value: indexSourceUrl, onChange: (event) => setIndexSourceUrl(event.
|
|
7432
|
+
el("input", { type: "text", placeholder: t("sourceName"), value: indexSourceName, onChange: (event) => setIndexSourceName(event.target.value), style: { flex: 1, minWidth: 0 } }),
|
|
7433
|
+
el("input", { type: "text", placeholder: t("indexUrlPlaceholder"), value: indexSourceUrl, onChange: (event) => setIndexSourceUrl(event.target.value), style: { flex: 2, minWidth: 0 } }),
|
|
6403
7434
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: addIndexSource }, t("addIndexSource"))),
|
|
6404
7435
|
el("div", { className: styles.rowTop },
|
|
6405
7436
|
el("button", {
|
|
@@ -6417,8 +7448,8 @@ function healthSignature(health) {
|
|
|
6417
7448
|
(sourcesData.gitSources ?? []).map((s) =>
|
|
6418
7449
|
editGit !== null && editGit.id === s.id
|
|
6419
7450
|
? el("div", { key: s.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
6420
|
-
el("input", { type: "text", value: editGit.name, onChange: (event) => setEditGit({ ...editGit, name: event.
|
|
6421
|
-
el("input", { type: "text", value: editGit.urlTemplate, onChange: (event) => setEditGit({ ...editGit, urlTemplate: event.
|
|
7451
|
+
el("input", { type: "text", value: editGit.name, onChange: (event) => setEditGit({ ...editGit, name: event.target.value }), style: { flex: 1, minWidth: 80 } }),
|
|
7452
|
+
el("input", { type: "text", value: editGit.urlTemplate, onChange: (event) => setEditGit({ ...editGit, urlTemplate: event.target.value }), style: { flex: 2, minWidth: 120 } }),
|
|
6422
7453
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: saveEditGit }, t("saveSource")),
|
|
6423
7454
|
el("button", { type: "button", className: styles.toggle, onClick: () => setEditGit(null) }, t("cancelEdit")))
|
|
6424
7455
|
: el("div", { key: s.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
@@ -6430,8 +7461,8 @@ function healthSignature(health) {
|
|
|
6430
7461
|
el("button", { type: "button", className: styles.toggle, title: t("editSourceHint"), disabled: sourcesBusy, onClick: () => setEditGit({ id: s.id, name: s.name, urlTemplate: s.urlTemplate }) }, t("editSource")),
|
|
6431
7462
|
el("button", { type: "button", className: styles.toggle, title: t("removeSourceHint"), disabled: sourcesBusy, onClick: () => sourcesAction({ action: "remove-git", id: s.id }) }, t("removeSource"))))),
|
|
6432
7463
|
el("div", { className: styles.rowTop },
|
|
6433
|
-
el("input", { type: "text", placeholder: t("sourceName"), value: gitSourceName, onChange: (event) => setGitSourceName(event.
|
|
6434
|
-
el("input", { type: "text", placeholder: t("gitUrlPlaceholder"), value: gitSourceUrl, onChange: (event) => setGitSourceUrl(event.
|
|
7464
|
+
el("input", { type: "text", placeholder: t("sourceName"), value: gitSourceName, onChange: (event) => setGitSourceName(event.target.value), style: { flex: 1, minWidth: 0 } }),
|
|
7465
|
+
el("input", { type: "text", placeholder: t("gitUrlPlaceholder"), value: gitSourceUrl, onChange: (event) => setGitSourceUrl(event.target.value), style: { flex: 2, minWidth: 0 } }),
|
|
6435
7466
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: addGitSource }, t("addGitSource"))),
|
|
6436
7467
|
]),
|
|
6437
7468
|
accSection("archive", t("archiveSourcesTitle"), [
|
|
@@ -6439,8 +7470,8 @@ function healthSignature(health) {
|
|
|
6439
7470
|
(sourcesData.archiveSources ?? []).map((s) =>
|
|
6440
7471
|
editArchive !== null && editArchive.id === s.id
|
|
6441
7472
|
? el("div", { key: s.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
6442
|
-
el("input", { type: "text", value: editArchive.name, onChange: (event) => setEditArchive({ ...editArchive, name: event.
|
|
6443
|
-
el("input", { type: "text", value: editArchive.urlTemplate, onChange: (event) => setEditArchive({ ...editArchive, urlTemplate: event.
|
|
7473
|
+
el("input", { type: "text", value: editArchive.name, onChange: (event) => setEditArchive({ ...editArchive, name: event.target.value }), style: { flex: 1, minWidth: 80 } }),
|
|
7474
|
+
el("input", { type: "text", value: editArchive.urlTemplate, onChange: (event) => setEditArchive({ ...editArchive, urlTemplate: event.target.value }), style: { flex: 2, minWidth: 120 } }),
|
|
6444
7475
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: saveEditArchive }, t("saveSource")),
|
|
6445
7476
|
el("button", { type: "button", className: styles.toggle, onClick: () => setEditArchive(null) }, t("cancelEdit")))
|
|
6446
7477
|
: el("div", { key: s.id, className: styles.rowTop, style: { justifyContent: "space-between" } },
|
|
@@ -6452,8 +7483,8 @@ function healthSignature(health) {
|
|
|
6452
7483
|
el("button", { type: "button", className: styles.toggle, title: t("editSourceHint"), disabled: sourcesBusy, onClick: () => setEditArchive({ id: s.id, name: s.name, urlTemplate: s.urlTemplate }) }, t("editSource")),
|
|
6453
7484
|
el("button", { type: "button", className: styles.toggle, title: t("removeSourceHint"), disabled: sourcesBusy, onClick: () => sourcesAction({ action: "remove-archive", id: s.id }) }, t("removeSource"))))),
|
|
6454
7485
|
el("div", { className: styles.rowTop },
|
|
6455
|
-
el("input", { type: "text", placeholder: t("sourceName"), value: archiveSourceName, onChange: (event) => setArchiveSourceName(event.
|
|
6456
|
-
el("input", { type: "text", placeholder: t("archiveUrlPlaceholder"), value: archiveSourceUrl, onChange: (event) => setArchiveSourceUrl(event.
|
|
7486
|
+
el("input", { type: "text", placeholder: t("sourceName"), value: archiveSourceName, onChange: (event) => setArchiveSourceName(event.target.value), style: { flex: 1, minWidth: 0 } }),
|
|
7487
|
+
el("input", { type: "text", placeholder: t("archiveUrlPlaceholder"), value: archiveSourceUrl, onChange: (event) => setArchiveSourceUrl(event.target.value), style: { flex: 2, minWidth: 0 } }),
|
|
6457
7488
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: addArchiveSource }, t("addArchiveSource"))),
|
|
6458
7489
|
]),
|
|
6459
7490
|
accSection("search", t("searchSourcesTitle"), [
|
|
@@ -6470,14 +7501,14 @@ function healthSignature(health) {
|
|
|
6470
7501
|
? el("button", { type: "button", className: styles.toggle, title: t("removeSourceHint"), disabled: sourcesBusy, onClick: () => sourcesAction({ action: "remove-search", id: s.id }) }, t("removeSource"))
|
|
6471
7502
|
: null)),
|
|
6472
7503
|
el("div", { className: styles.rowTop },
|
|
6473
|
-
el("input", { type: "text", placeholder: t("sourceName"), value: searchSourceName, onChange: (event) => setSearchSourceName(event.
|
|
6474
|
-
el("input", { type: "text", placeholder: t("searchUrlPlaceholder"), value: searchSourceUrl, onChange: (event) => setSearchSourceUrl(event.
|
|
7504
|
+
el("input", { type: "text", placeholder: t("sourceName"), value: searchSourceName, onChange: (event) => setSearchSourceName(event.target.value), style: { flex: 1, minWidth: 0 } }),
|
|
7505
|
+
el("input", { type: "text", placeholder: t("searchUrlPlaceholder"), value: searchSourceUrl, onChange: (event) => setSearchSourceUrl(event.target.value), style: { flex: 2, minWidth: 0 } }),
|
|
6475
7506
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy, onClick: addSearchSource }, t("addSearchSource"))),
|
|
6476
7507
|
el("textarea", {
|
|
6477
7508
|
rows: 2,
|
|
6478
7509
|
placeholder: t("headersPlaceholder"),
|
|
6479
7510
|
value: searchSourceHeaders,
|
|
6480
|
-
onChange: (event) => setSearchSourceHeaders(event.
|
|
7511
|
+
onChange: (event) => setSearchSourceHeaders(event.target.value),
|
|
6481
7512
|
style: { width: "100%", boxSizing: "border-box", font: "inherit", resize: "vertical" },
|
|
6482
7513
|
}),
|
|
6483
7514
|
]),
|
|
@@ -6489,8 +7520,8 @@ function healthSignature(health) {
|
|
|
6489
7520
|
? t("giteeLoggedIn") + (giteeStatus.login || "—")
|
|
6490
7521
|
: (giteeStatus !== null && giteeStatus.clientConfigured ? t("giteeSetupHint") : t("giteeLoginPrompt")))),
|
|
6491
7522
|
el("div", { className: styles.rowTop },
|
|
6492
|
-
el("input", { type: "text", placeholder: t("giteeClientId"), value: giteeClientId, onChange: (event) => setGiteeClientId(event.
|
|
6493
|
-
el("input", { type: "password", placeholder: t("giteeClientSecret"), value: giteeClientSecret, onChange: (event) => setGiteeClientSecret(event.
|
|
7523
|
+
el("input", { type: "text", placeholder: t("giteeClientId"), value: giteeClientId, onChange: (event) => setGiteeClientId(event.target.value), style: { flex: 1, minWidth: 0 } }),
|
|
7524
|
+
el("input", { type: "password", placeholder: t("giteeClientSecret"), value: giteeClientSecret, onChange: (event) => setGiteeClientSecret(event.target.value), style: { flex: 2, minWidth: 0 } }),
|
|
6494
7525
|
el("button", { type: "button", className: styles.toggle, disabled: sourcesBusy || !giteeClientId.trim() || !giteeClientSecret.trim(), onClick: saveGiteeSetup }, t("giteeSave"))),
|
|
6495
7526
|
el("div", { className: styles.rowTop },
|
|
6496
7527
|
el("button", { type: "button", className: styles.toggle, disabled: giteeStatus !== null && !giteeStatus.clientConfigured, onClick: startGiteeOauth }, t("giteeLoginBtn")),
|