dshmarket 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/client.js CHANGED
@@ -90,6 +90,31 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
90
90
  marketUpdate: "市场有新版本,升级",
91
91
  updateAll: "全部更新",
92
92
  tabThemes: "主题",
93
+ tabBackup: "备份与恢复",
94
+ backupLocal: "本地文件",
95
+ backupDownload: "导出备份",
96
+ backupImport: "导入并预览",
97
+ backupHint: "仅包含插件清单和 profile 配置,不包含 node_modules;恢复会按清单重新安装插件。",
98
+ webdav: "WebDAV",
99
+ webdavPreset: "服务商预设",
100
+ webdavUrl: "备份文件 URL",
101
+ webdavUser: "用户名(可选)",
102
+ webdavPassword: "密码(可选)",
103
+ webdavUpload: "上传备份",
104
+ webdavRestore: "从 WebDAV 恢复",
105
+ autoBackup: "每天自动备份(打开市场时)",
106
+ webdavNote: "WebDAV 地址与用户名仅保存在当前浏览器;密码只存于服务端,每次会话需重新输入。",
107
+ localOnly: "WebDAV 地址和凭证仅保存在当前浏览器。",
108
+ credsWarning: "注意:备份包含配置与可能含密钥的文件(config.toml、.env 等)。下载件会原样导出,上传 WebDAV 前请确认目标可信。",
109
+ backupWorking: "处理中…",
110
+ backupDone: "备份已上传",
111
+ restoreDone: "恢复完成,请重启 DeepSeek Harness",
112
+ restorePartial: "恢复已继续完成,但以下插件安装失败:",
113
+ restoreConfirm: "恢复将覆盖当前 profile 配置并重新安装插件,确定继续吗?",
114
+ restorePreviewDone: "备份已导入,请在“已安装”中确认后开始恢复",
115
+ restoreMissing: "备份中有 {0} 个插件尚未安装",
116
+ restoreStart: "开始恢复",
117
+ notInstalled: "未安装",
93
118
  themeApply: "使用",
94
119
  themeActive: "使用中",
95
120
  themeEmpty: "目录里暂时还没有主题,敬请期待",
@@ -192,6 +217,31 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
192
217
  marketUpdate: "Market update available — upgrade",
193
218
  updateAll: "Update all",
194
219
  tabThemes: "Themes",
220
+ tabBackup: "Backup & Restore",
221
+ backupLocal: "Local file",
222
+ backupDownload: "Export backup",
223
+ backupImport: "Import and preview",
224
+ backupHint: "Includes the plugin list and profile configuration, never node_modules. Restore reinstalls plugins from the list.",
225
+ webdav: "WebDAV",
226
+ webdavPreset: "Provider preset",
227
+ webdavUrl: "Backup file URL",
228
+ webdavUser: "Username (optional)",
229
+ webdavPassword: "Password (optional)",
230
+ webdavUpload: "Upload backup",
231
+ webdavRestore: "Restore from WebDAV",
232
+ autoBackup: "Back up daily (when the market opens)",
233
+ webdavNote: "The WebDAV URL and username stay in this browser only; the password is kept server-side and must be re-entered each session.",
234
+ localOnly: "The WebDAV URL and credentials stay in this browser only.",
235
+ credsWarning: "Heads up: backups include profile configuration and files that may hold secrets (config.toml, .env, …). Local exports are unmodified, so only upload to a WebDAV target you trust.",
236
+ backupWorking: "Working…",
237
+ backupDone: "Backup uploaded",
238
+ restoreDone: "Restore complete — restart DeepSeek Harness",
239
+ restorePartial: "Restore continued, but these plugins failed to install:",
240
+ restoreConfirm: "Restore will overwrite this profile configuration and reinstall plugins. Continue?",
241
+ restorePreviewDone: "Backup imported. Review Installed, then start restore.",
242
+ restoreMissing: "{0} plugins from this backup are not installed",
243
+ restoreStart: "Start restore",
244
+ notInstalled: "Not installed",
195
245
  themeApply: "Use",
196
246
  themeActive: "Active",
197
247
  themeEmpty: "No more theme plugins in the catalog yet — stay tuned",
@@ -404,6 +454,97 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
404
454
  pick(["--dsw-alias-label-primary"]) || (dark ? "#e5e7eb" : "#1f2328")
405
455
  ];
406
456
  }
457
+ /**
458
+ * Image hosts screenshots may load from (#61) — GitHub's own hosting only.
459
+ * Any other host is dropped BEFORE an <img> is created: a screenshot URL is
460
+ * a request carrying the user's IP, so registry data and README content are
461
+ * both treated as untrusted here, matching the upstream build gate.
462
+ */
463
+ const SCREENSHOT_HOSTS = /* @__PURE__ */ new Set([
464
+ "raw.githubusercontent.com",
465
+ "user-images.githubusercontent.com",
466
+ "camo.githubusercontent.com",
467
+ "github.com"
468
+ ]);
469
+ const MAX_SCREENSHOTS = 6;
470
+ /** Keep only https URLs on allowlisted image hosts; SVG dropped (logos/badges). */
471
+ function safeScreenshots(urls) {
472
+ if (!Array.isArray(urls)) return [];
473
+ const safe = [];
474
+ for (const value of urls) {
475
+ if (typeof value !== "string") continue;
476
+ let parsed = null;
477
+ try {
478
+ parsed = new URL(value);
479
+ } catch {
480
+ continue;
481
+ }
482
+ if (parsed.protocol !== "https:" || !SCREENSHOT_HOSTS.has(parsed.hostname)) continue;
483
+ if (/\.svg$/i.test(parsed.pathname)) continue;
484
+ if (!safe.includes(value)) safe.push(value);
485
+ if (safe.length >= MAX_SCREENSHOTS) break;
486
+ }
487
+ return safe;
488
+ }
489
+ /**
490
+ * Image URLs extracted from a repo README, in document order — the fallback
491
+ * when an entry has no curated screenshots (#61). Markdown and <img> forms;
492
+ * relative paths resolve against the README's directory on
493
+ * raw.githubusercontent.com; badges fall out naturally (shields.io etc. are
494
+ * not allowlisted) and SVG is skipped as logo/badge noise.
495
+ */
496
+ function extractReadmeImages(markdown, owner, repo, subpath) {
497
+ const base = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${subpath === null ? "" : subpath + "/"}`;
498
+ const found = [];
499
+ const push = (raw) => {
500
+ const src = raw.trim().replace(/^<|>$/g, "");
501
+ if (src === "" || src.startsWith("data:")) return;
502
+ let absolute;
503
+ if (/^https?:\/\//i.test(src)) absolute = src;
504
+ else if (src.startsWith("/")) absolute = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD${src}`;
505
+ else try {
506
+ absolute = new URL(src, base).href;
507
+ } catch {
508
+ return;
509
+ }
510
+ found.push(absolute);
511
+ };
512
+ for (const m of markdown.matchAll(/!\[[^\]]*\]\(\s*([^)\s]+)(?:\s+"[^"]*")?\s*\)|<img[^>]*\ssrc=["']([^"']+)["']/gi)) push(m[1] ?? m[2]);
513
+ return safeScreenshots(found);
514
+ }
515
+ const readmeShotsCache = /* @__PURE__ */ new Map();
516
+ /**
517
+ * Screenshots for a plugin: the registry's curated list when present,
518
+ * otherwise lazily extracted from the repo README. Only ever called AFTER
519
+ * the user opens the detail dialog — browsing the list must make zero
520
+ * external requests. Failures resolve to [] (silent degradation).
521
+ */
522
+ function pluginScreenshots(plugin) {
523
+ const curated = safeScreenshots(plugin.screenshots);
524
+ if (curated.length > 0) return Promise.resolve(curated);
525
+ const m = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\/tree\/[^/]+\/(.+?))?\/?$/.exec(plugin.url);
526
+ if (m === null) return Promise.resolve([]);
527
+ const [, owner, repo, subpath = null] = m;
528
+ const cacheKey = plugin.url;
529
+ const cached = readmeShotsCache.get(cacheKey);
530
+ if (cached !== void 0) return cached;
531
+ const fetchReadme = async (path) => {
532
+ try {
533
+ const res = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/HEAD/${path === null ? "" : path + "/"}README.md`);
534
+ return res.ok ? await res.text() : null;
535
+ } catch {
536
+ return null;
537
+ }
538
+ };
539
+ const task = (async () => {
540
+ const sub = subpath === null ? null : await fetchReadme(subpath);
541
+ if (sub !== null) return extractReadmeImages(sub, owner, repo, subpath);
542
+ const root = await fetchReadme(null);
543
+ return root === null ? [] : extractReadmeImages(root, owner, repo, null);
544
+ })().catch(() => []);
545
+ readmeShotsCache.set(cacheKey, task);
546
+ return task;
547
+ }
407
548
  //#endregion
408
549
  //#region src/client/InstallToast.tsx
409
550
  /**
@@ -432,7 +573,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
432
573
  }
433
574
  //#endregion
434
575
  //#region \0dsh-css:/home/runner/work/dsh-market/dsh-market/src/client/Market.module.css.mjs
435
- const css = ".eGUBIq_root{min-width:0;height:100%;color:var(--dsw-alias-label-primary,#1f2328);flex-direction:column;display:flex;position:relative}.eGUBIq_head{flex-direction:column;gap:12px;padding:4px 4px 12px;display:flex}.eGUBIq_title{margin:0;font-size:16px;font-weight:500;line-height:24px}.eGUBIq_sub{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:0;font-size:14px;line-height:22px}.eGUBIq_searchInline{flex-shrink:0;width:200px;margin-bottom:6px}.eGUBIq_tabs{border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);align-items:flex-end;gap:2px;display:flex}.eGUBIq_tab{font:inherit;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;padding:7px 12px;font-size:13px}.eGUBIq_tab.eGUBIq_on{color:var(--dsw-alias-brand-primary,#4f6ef7);border-bottom-color:var(--dsw-alias-brand-primary,#4f6ef7);font-weight:600}.eGUBIq_restart{background:var(--dsw-alias-bg-layer-2,#fdf3e3);border:1px solid var(--dsw-alias-border-l2,#f3e3c3);border-radius:8px;align-items:center;gap:8px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_body{flex:1;padding:12px 4px 24px;overflow-x:hidden;overflow-y:auto}.eGUBIq_cats{z-index:5;background:var(--dsw-alias-bg-layer-2,#f7f8fa);margin:-12px -4px 2px;padding:12px 4px 4px;position:sticky;top:-13px}.eGUBIq_catsRow{align-items:flex-start;gap:8px;display:flex;position:relative}.eGUBIq_filterWrap{flex-shrink:0;position:relative}.eGUBIq_filterBtn{border:1px solid var(--dsw-alias-border-l1,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);font:inherit;cursor:pointer;white-space:nowrap;border-radius:8px;align-items:center;gap:5px;padding:5px 11px;font-size:12px;line-height:18px;display:flex}.eGUBIq_filterBtn:hover,.eGUBIq_filterBtnOn{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_filterPanel{z-index:30;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;flex-direction:column;gap:10px;width:250px;padding:8px;display:flex;position:absolute;top:calc(100% + 6px);right:0;box-shadow:0 8px 28px #00000024}.eGUBIq_filterGroup{flex-direction:column;gap:2px;display:flex}.eGUBIq_filterTitle{color:var(--dsw-alias-label-secondary,#6b7280);padding:4px 8px 2px;font-size:11px;font-weight:600}.eGUBIq_filterOption{cursor:pointer;color:var(--dsw-alias-label-primary,#1f2328);border-radius:6px;align-items:center;gap:8px;padding:5px 8px;font-size:12px;line-height:18px;display:flex}.eGUBIq_filterOption:hover{background:var(--dsw-alias-bg-layer-2,#f3f4f6)}.eGUBIq_filterOption input{margin:0}.eGUBIq_star{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_top{z-index:20;border:1px solid var(--dsw-alias-border-l1,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);width:38px;height:38px;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;border-radius:99px;font-size:16px;position:absolute;bottom:18px;right:18px;box-shadow:0 4px 14px #0000001f}.eGUBIq_top:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_tag{border:1px solid var(--dsw-alias-border-l3,#d9dde3);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:4px;flex-shrink:0;padding:1px 6px;font-size:11px;line-height:16px}.eGUBIq_okState{color:var(--dsw-alias-state-success-primary,#16a34a);white-space:nowrap;font-size:12px;font-weight:600}.eGUBIq_dangerBtn.eGUBIq_dangerBtn{border-color:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-state-error-primary,#dc2626)}.eGUBIq_dangerArmed.eGUBIq_dangerArmed{background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff}.eGUBIq_warnBtn.eGUBIq_warnBtn{background:var(--dsw-alias-state-warn-primary,#ea580c);color:#fff}.eGUBIq_catsWrap{flex-wrap:wrap;flex:1;align-items:center;gap:6px;min-width:0;display:flex}.eGUBIq_catsCollapsed{max-height:62px;overflow:hidden}.eGUBIq_catsToggle.eGUBIq_catsToggle{height:26px;min-height:26px;color:var(--dsw-alias-label-secondary,#6b7280);padding:0 6px}.eGUBIq_cmdDetails{margin:0}.eGUBIq_cmdSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary,#6b7280);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}.eGUBIq_cmdSummary::-webkit-details-marker{display:none}.eGUBIq_cmdSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.eGUBIq_cmdDetails[open]>.eGUBIq_cmdSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.eGUBIq_cmdSummary:hover{color:var(--dsw-alias-label-primary,#1f2328)}.eGUBIq_cmd{background:var(--dsw-alias-bg-layer-2,#f3f4f6);word-break:break-all;border-radius:6px;margin:8px 0 0;padding:8px 10px;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:18px}.eGUBIq_warnLine{color:var(--dsw-alias-state-warn-primary,#b45309);margin:0;font-size:12px;font-weight:600;line-height:18px}.eGUBIq_modalNote{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:12px 0 0;font-size:12px;line-height:18px}.eGUBIq_grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;display:grid}.eGUBIq_sect{color:var(--dsw-alias-label-secondary,#6b7280);margin:14px 2px 8px;font-size:12px;font-weight:600}.eGUBIq_sect:first-child{margin-top:2px}.eGUBIq_swatches{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;gap:0;height:34px;display:flex;overflow:hidden}.eGUBIq_themesGrid{margin-bottom:12px}.eGUBIq_swatches i{flex:1}.eGUBIq_card{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.eGUBIq_row1{align-items:center;gap:10px;min-width:0;display:flex}.eGUBIq_av{color:#fff;object-fit:cover;background:var(--dsw-alias-bg-layer-2,#f3f4f6);border-radius:8px;flex-shrink:0;place-items:center;width:32px;height:32px;font-size:14px;font-weight:700;display:grid}.eGUBIq_nm{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:22px;overflow:hidden}.eGUBIq_owner{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_desc{color:var(--dsw-alias-label-tertiary,#8b93a1);min-height:36px;margin:0;font-size:12px;line-height:18px}.eGUBIq_foot{align-items:center;gap:8px;margin-top:auto;display:flex}.eGUBIq_grow{flex:1}.eGUBIq_titleRow{align-items:center;gap:10px;display:flex}.eGUBIq_descTight{min-height:0}.eGUBIq_src{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px;text-decoration:none}.eGUBIq_src:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_dot{vertical-align:2px;margin-left:5px}.eGUBIq_act{flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px;font-size:11px;display:flex}.eGUBIq_actLive{color:var(--dsw-alias-state-success-primary,#16a34a);align-items:center;gap:4px;font-weight:600;display:inline-flex}.eGUBIq_actWarn{color:var(--dsw-alias-state-warn-primary,#b45309);align-items:center;gap:4px;font-weight:600;display:inline-flex}.eGUBIq_actBroken{color:var(--dsw-alias-state-error-primary,#dc2626);align-items:center;gap:4px;font-weight:600;display:inline-flex}.eGUBIq_actWhy{margin:0}.eGUBIq_actWhy summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px}.eGUBIq_loading{color:var(--dsw-alias-label-secondary,#9ca3af);flex-direction:column;align-items:center;gap:12px;padding:48px;font-size:13px;display:flex}.eGUBIq_spin{border:3px solid var(--dsw-alias-border-l1,#e5e7eb);border-top-color:var(--dsw-alias-brand-primary,#4f6ef7);border-radius:99px;width:22px;height:22px;animation:.8s linear infinite eGUBIq_sp}@keyframes eGUBIq_sp{to{transform:rotate(360deg)}}.eGUBIq_progress{background:var(--dsw-alias-bg-layer-2,#f3f4f6);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:8px;flex-wrap:wrap;align-items:center;gap:9px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_bar{background:var(--dsw-alias-border-l1,#e5e7eb);border-radius:99px;width:100%;height:4px;overflow:hidden}.eGUBIq_barFill{background:var(--dsw-alias-brand-primary,#4f6ef7);border-radius:99px;height:100%;transition:width .6s}.eGUBIq_barWave{width:30%;animation:1.2s ease-in-out infinite eGUBIq_dshmSlide}@keyframes eGUBIq_dshmSlide{0%{margin-left:-30%}to{margin-left:100%}}.eGUBIq_irow .eGUBIq_progress{margin-top:8px}.eGUBIq_progress .eGUBIq_spin{border-width:2px;flex-shrink:0;width:14px;height:14px}.eGUBIq_progress code{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,monospace;font-size:11px;overflow:hidden}.eGUBIq_empty{color:var(--dsw-alias-label-secondary,#9ca3af);text-align:center;padding:32px;font-size:13px}.eGUBIq_err{color:var(--dsw-alias-state-error-primary,#dc2626);white-space:pre-wrap;word-break:break-all;margin:8px 0;font-size:12px}.eGUBIq_irow{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;align-items:center;gap:10px;margin-bottom:8px;padding:12px 14px;display:flex}.eGUBIq_irow>.eGUBIq_src,.eGUBIq_irow>.eGUBIq_owner,.eGUBIq_dangerBtn.eGUBIq_dangerBtn,.eGUBIq_warnBtn.eGUBIq_warnBtn,.eGUBIq_dangerArmed.eGUBIq_dangerArmed{white-space:nowrap;flex-shrink:0}.eGUBIq_spec{color:var(--dsw-alias-label-secondary,#9ca3af);font-family:ui-monospace,Menlo,monospace;font-size:11px}.eGUBIq_staleAction{margin-top:8px}.eGUBIq_pct{color:var(--dsw-alias-label-secondary,#6b7280);flex-shrink:0;font-size:11px;font-weight:600}.eGUBIq_cancelBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#6b7280);font:inherit;cursor:pointer;white-space:nowrap;border-radius:6px;flex-shrink:0;padding:2px 10px;font-size:11px;line-height:16px}.eGUBIq_cancelBtn:hover{color:var(--dsw-alias-state-error-primary,#dc2626);border-color:var(--dsw-alias-state-error-primary,#dc2626)}.eGUBIq_pager{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;margin:16px 0 4px;display:flex}.eGUBIq_pagerPages{flex-wrap:wrap;flex:1;justify-content:center;align-items:center;gap:6px;min-width:0;display:flex}.eGUBIq_pagerSize{flex-shrink:0;align-items:center;gap:4px;display:flex}.eGUBIq_sizeLabel{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px}.eGUBIq_sizeBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#6b7280);font:inherit;cursor:pointer;border-radius:6px;padding:3px 8px;font-size:12px;line-height:18px}.eGUBIq_sizeBtn:hover{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_sizeOn{background:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7);color:#fff;font-weight:600}.eGUBIq_pageBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#6b7280);font:inherit;cursor:pointer;border-radius:6px;min-width:28px;padding:4px 10px;font-size:12px;line-height:18px}.eGUBIq_pageBtn:hover:not(:disabled){color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_pageBtn:disabled{opacity:.45;cursor:default}.eGUBIq_pageOn{background:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7);color:#fff;font-weight:600}.eGUBIq_pageEllipsis{color:var(--dsw-alias-label-secondary,#9ca3af);padding:0 2px;font-size:12px}.eGUBIq_pageInfo{color:var(--dsw-alias-label-secondary,#6b7280);white-space:nowrap;font-size:12px}";
576
+ const css = ".eGUBIq_root{min-width:0;height:100%;color:var(--dsw-alias-label-primary,#1f2328);flex-direction:column;display:flex;position:relative}.eGUBIq_head{flex-direction:column;gap:12px;padding:4px 4px 12px;display:flex}.eGUBIq_title{margin:0;font-size:16px;font-weight:500;line-height:24px}.eGUBIq_sub{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:0;font-size:14px;line-height:22px}.eGUBIq_searchInline{flex-shrink:0;width:200px;margin-bottom:6px}.eGUBIq_tabs{border-bottom:1px solid var(--dsw-alias-border-l2,#e5e7eb);align-items:flex-end;gap:2px;display:flex}.eGUBIq_tab{font:inherit;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;white-space:nowrap;background:0 0;border:none;border-bottom:2px solid #0000;padding:7px 12px;font-size:13px}.eGUBIq_tab.eGUBIq_on{color:var(--dsw-alias-brand-primary,#4f6ef7);border-bottom-color:var(--dsw-alias-brand-primary,#4f6ef7);font-weight:600}.eGUBIq_restart{background:var(--dsw-alias-bg-layer-2,#fdf3e3);border:1px solid var(--dsw-alias-border-l2,#f3e3c3);border-radius:8px;align-items:center;gap:8px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_body{flex:1;padding:12px 4px 24px;overflow-x:hidden;overflow-y:auto}.eGUBIq_cats{z-index:5;background:var(--dsw-alias-bg-layer-2,#f7f8fa);margin:-12px -4px 2px;padding:12px 4px 4px;position:sticky;top:-13px}.eGUBIq_catsRow{align-items:flex-start;gap:8px;display:flex;position:relative}.eGUBIq_filterWrap{flex-shrink:0;position:relative}.eGUBIq_filterBtn{border:1px solid var(--dsw-alias-border-l1,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);font:inherit;cursor:pointer;white-space:nowrap;border-radius:8px;align-items:center;gap:5px;padding:5px 11px;font-size:12px;line-height:18px;display:flex}.eGUBIq_filterBtn:hover,.eGUBIq_filterBtnOn{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_filterPanel{z-index:30;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:10px;flex-direction:column;gap:10px;width:250px;padding:8px;display:flex;position:absolute;top:calc(100% + 6px);right:0;box-shadow:0 8px 28px #00000024}.eGUBIq_filterGroup{flex-direction:column;gap:2px;display:flex}.eGUBIq_filterTitle{color:var(--dsw-alias-label-secondary,#6b7280);padding:4px 8px 2px;font-size:11px;font-weight:600}.eGUBIq_filterOption{cursor:pointer;color:var(--dsw-alias-label-primary,#1f2328);border-radius:6px;align-items:center;gap:8px;padding:5px 8px;font-size:12px;line-height:18px;display:flex}.eGUBIq_filterOption:hover{background:var(--dsw-alias-bg-layer-2,#f3f4f6)}.eGUBIq_filterOption input{margin:0}.eGUBIq_star{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_top{z-index:20;border:1px solid var(--dsw-alias-border-l1,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);width:38px;height:38px;color:var(--dsw-alias-label-secondary,#6b7280);cursor:pointer;border-radius:99px;font-size:16px;position:absolute;bottom:18px;right:18px;box-shadow:0 4px 14px #0000001f}.eGUBIq_top:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_tag{border:1px solid var(--dsw-alias-border-l3,#d9dde3);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:4px;flex-shrink:0;padding:1px 6px;font-size:11px;line-height:16px}.eGUBIq_okState{color:var(--dsw-alias-state-success-primary,#16a34a);white-space:nowrap;font-size:12px;font-weight:600}.eGUBIq_dangerBtn.eGUBIq_dangerBtn{border-color:var(--dsw-alias-state-error-primary,#dc2626);color:var(--dsw-alias-state-error-primary,#dc2626)}.eGUBIq_dangerArmed.eGUBIq_dangerArmed{background:var(--dsw-alias-state-error-primary,#dc2626);color:#fff}.eGUBIq_warnBtn.eGUBIq_warnBtn{background:var(--dsw-alias-state-warn-primary,#ea580c);color:#fff}.eGUBIq_catsWrap{flex-wrap:wrap;flex:1;align-items:center;gap:6px;min-width:0;display:flex}.eGUBIq_catsCollapsed{max-height:62px;overflow:hidden}.eGUBIq_catsToggle.eGUBIq_catsToggle{height:26px;min-height:26px;color:var(--dsw-alias-label-secondary,#6b7280);padding:0 6px}.eGUBIq_shots{-webkit-overflow-scrolling:touch;scrollbar-width:thin;gap:8px;margin:0 0 8px;padding:2px 0 6px;display:flex;overflow-x:auto}.eGUBIq_shot{object-fit:cover;border:1px solid var(--dsw-alias-border-default,#e5e7eb);background:var(--dsw-alias-bg-layer-2,#f3f4f6);border-radius:8px;flex:none;max-width:260px;height:150px}.eGUBIq_cmdDetails{margin:0}.eGUBIq_cmdSummary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary,#6b7280);border-radius:6px;align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}.eGUBIq_cmdSummary::-webkit-details-marker{display:none}.eGUBIq_cmdSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.eGUBIq_cmdDetails[open]>.eGUBIq_cmdSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.eGUBIq_cmdSummary:hover{color:var(--dsw-alias-label-primary,#1f2328)}.eGUBIq_cmd{background:var(--dsw-alias-bg-layer-2,#f3f4f6);word-break:break-all;border-radius:6px;margin:8px 0 0;padding:8px 10px;font-family:ui-monospace,Menlo,monospace;font-size:11px;line-height:18px}.eGUBIq_warnLine{color:var(--dsw-alias-state-warn-primary,#b45309);margin:0;font-size:12px;font-weight:600;line-height:18px}.eGUBIq_modalNote{color:var(--dsw-alias-label-tertiary,#8b93a1);margin:12px 0 0;font-size:12px;line-height:18px}.eGUBIq_grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;display:grid}.eGUBIq_sect{color:var(--dsw-alias-label-secondary,#6b7280);margin:14px 2px 8px;font-size:12px;font-weight:600}.eGUBIq_sect:first-child{margin-top:2px}.eGUBIq_swatches{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:8px;gap:0;height:34px;display:flex;overflow:hidden}.eGUBIq_themesGrid{margin-bottom:12px}.eGUBIq_swatches i{flex:1}.eGUBIq_card{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.eGUBIq_row1{align-items:center;gap:10px;min-width:0;display:flex}.eGUBIq_av{color:#fff;object-fit:cover;background:var(--dsw-alias-bg-layer-2,#f3f4f6);border-radius:8px;flex-shrink:0;place-items:center;width:32px;height:32px;font-size:14px;font-weight:700;display:grid}.eGUBIq_nm{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:22px;overflow:hidden}.eGUBIq_owner{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px}.eGUBIq_desc{color:var(--dsw-alias-label-tertiary,#8b93a1);min-height:36px;margin:0;font-size:12px;line-height:18px}.eGUBIq_foot{align-items:center;gap:8px;margin-top:auto;display:flex}.eGUBIq_grow{flex:1}.eGUBIq_titleRow{align-items:center;gap:10px;display:flex}.eGUBIq_descTight{min-height:0}.eGUBIq_src{color:var(--dsw-alias-label-secondary,#9ca3af);font-size:11px;text-decoration:none}.eGUBIq_src:hover{color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_dot{vertical-align:2px;margin-left:5px}.eGUBIq_act{flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px;font-size:11px;display:flex}.eGUBIq_actLive{color:var(--dsw-alias-state-success-primary,#16a34a);align-items:center;gap:4px;font-weight:600;display:inline-flex}.eGUBIq_actWarn{color:var(--dsw-alias-state-warn-primary,#b45309);align-items:center;gap:4px;font-weight:600;display:inline-flex}.eGUBIq_actBroken{color:var(--dsw-alias-state-error-primary,#dc2626);align-items:center;gap:4px;font-weight:600;display:inline-flex}.eGUBIq_actWhy{margin:0}.eGUBIq_actWhy summary{cursor:pointer;color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px}.eGUBIq_loading{color:var(--dsw-alias-label-secondary,#9ca3af);flex-direction:column;align-items:center;gap:12px;padding:48px;font-size:13px;display:flex}.eGUBIq_spin{border:3px solid var(--dsw-alias-border-l1,#e5e7eb);border-top-color:var(--dsw-alias-brand-primary,#4f6ef7);border-radius:99px;width:22px;height:22px;animation:.8s linear infinite eGUBIq_sp}@keyframes eGUBIq_sp{to{transform:rotate(360deg)}}.eGUBIq_progress{background:var(--dsw-alias-bg-layer-2,#f3f4f6);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);color:var(--dsw-alias-label-secondary,#6b7280);border-radius:8px;flex-wrap:wrap;align-items:center;gap:9px;margin:0;padding:8px 12px;font-size:12px;display:flex}.eGUBIq_bar{background:var(--dsw-alias-border-l1,#e5e7eb);border-radius:99px;width:100%;height:4px;overflow:hidden}.eGUBIq_barFill{background:var(--dsw-alias-brand-primary,#4f6ef7);border-radius:99px;height:100%;transition:width .6s}.eGUBIq_barWave{width:30%;animation:1.2s ease-in-out infinite eGUBIq_dshmSlide}@keyframes eGUBIq_dshmSlide{0%{margin-left:-30%}to{margin-left:100%}}.eGUBIq_irow .eGUBIq_progress{margin-top:8px}.eGUBIq_progress .eGUBIq_spin{border-width:2px;flex-shrink:0;width:14px;height:14px}.eGUBIq_progress code{text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,monospace;font-size:11px;overflow:hidden}.eGUBIq_empty{color:var(--dsw-alias-label-secondary,#9ca3af);text-align:center;padding:32px;font-size:13px}.eGUBIq_err{color:var(--dsw-alias-state-error-primary,#dc2626);white-space:pre-wrap;word-break:break-all;margin:8px 0;font-size:12px}.eGUBIq_irow{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;align-items:center;gap:10px;margin-bottom:8px;padding:12px 14px;display:flex}.eGUBIq_irowMissing{filter:grayscale();opacity:.5}.eGUBIq_irow>.eGUBIq_src,.eGUBIq_irow>.eGUBIq_owner,.eGUBIq_dangerBtn.eGUBIq_dangerBtn,.eGUBIq_warnBtn.eGUBIq_warnBtn,.eGUBIq_dangerArmed.eGUBIq_dangerArmed{white-space:nowrap;flex-shrink:0}.eGUBIq_spec{color:var(--dsw-alias-label-secondary,#9ca3af);font-family:ui-monospace,Menlo,monospace;font-size:11px}.eGUBIq_staleAction{margin-top:8px}.eGUBIq_pct{color:var(--dsw-alias-label-secondary,#6b7280);flex-shrink:0;font-size:11px;font-weight:600}.eGUBIq_cancelBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#6b7280);font:inherit;cursor:pointer;white-space:nowrap;border-radius:6px;flex-shrink:0;padding:2px 10px;font-size:11px;line-height:16px}.eGUBIq_cancelBtn:hover{color:var(--dsw-alias-state-error-primary,#dc2626);border-color:var(--dsw-alias-state-error-primary,#dc2626)}.eGUBIq_pager{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;margin:16px 0 4px;display:flex}.eGUBIq_pagerPages{flex-wrap:wrap;flex:1;justify-content:center;align-items:center;gap:6px;min-width:0;display:flex}.eGUBIq_pagerSize{flex-shrink:0;align-items:center;gap:4px;display:flex}.eGUBIq_sizeLabel{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px}.eGUBIq_sizeBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#6b7280);font:inherit;cursor:pointer;border-radius:6px;padding:3px 8px;font-size:12px;line-height:18px}.eGUBIq_sizeBtn:hover{color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_sizeOn{background:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7);color:#fff;font-weight:600}.eGUBIq_pageBtn{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#6b7280);font:inherit;cursor:pointer;border-radius:6px;min-width:28px;padding:4px 10px;font-size:12px;line-height:18px}.eGUBIq_pageBtn:hover:not(:disabled){color:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7)}.eGUBIq_pageBtn:disabled{opacity:.45;cursor:default}.eGUBIq_pageOn{background:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7);color:#fff;font-weight:600}.eGUBIq_pageEllipsis{color:var(--dsw-alias-label-secondary,#9ca3af);padding:0 2px;font-size:12px}.eGUBIq_pageInfo{color:var(--dsw-alias-label-secondary,#6b7280);white-space:nowrap;font-size:12px}.eGUBIq_backupGrid{grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px;display:grid}.eGUBIq_backupCard{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;flex-direction:column;gap:10px;padding:16px;display:flex}.eGUBIq_backupCard h3{margin:0;font-size:14px}.eGUBIq_backupCard p{color:var(--dsw-alias-label-secondary,#6b7280);margin:0;font-size:12px;line-height:18px}.eGUBIq_backupActions{flex-wrap:wrap;gap:8px;display:flex}.eGUBIq_backupButton{border:1px solid var(--dsw-alias-border-l2,#e5e7eb);color:var(--dsw-alias-label-primary,#1f2328);cursor:pointer;border-radius:6px;align-items:center;padding:4px 10px;font-size:12px;line-height:18px;text-decoration:none;display:inline-flex;position:relative}.eGUBIq_backupButton[aria-disabled=true]{opacity:.5;pointer-events:none}.eGUBIq_backupButton input{opacity:0;width:1px;height:1px;position:absolute}.eGUBIq_backupPrimary{background:var(--dsw-alias-brand-primary,#4f6ef7);border-color:var(--dsw-alias-brand-primary,#4f6ef7);color:#fff}.eGUBIq_backupInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#fff);width:100%;color:var(--dsw-alias-label-primary,#1f2328);font:inherit;border-radius:8px;padding:7px 9px;font-size:12px}.eGUBIq_backupCheck{cursor:pointer;align-items:center;gap:6px;font-size:12px;display:flex}.eGUBIq_backupWarn{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-warn-primary,#b45309)!important}.eGUBIq_backupMessage{color:var(--dsw-alias-label-secondary,#6b7280);grid-column:1/-1;font-size:12px}";
436
577
  const tagId = "dshmarket/Market.module.css";
437
578
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
438
579
  const tag = document.createElement("style");
@@ -442,86 +583,98 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
442
583
  document.head.appendChild(tag);
443
584
  }
444
585
  var Market_module_css_default = {
445
- "swatches": "eGUBIq_swatches",
446
- "body": "eGUBIq_body",
447
- "okState": "eGUBIq_okState",
448
- "empty": "eGUBIq_empty",
449
- "desc": "eGUBIq_desc",
450
- "modalNote": "eGUBIq_modalNote",
451
- "dangerArmed": "eGUBIq_dangerArmed",
452
- "sizeOn": "eGUBIq_sizeOn",
453
- "filterTitle": "eGUBIq_filterTitle",
454
- "warnLine": "eGUBIq_warnLine",
455
586
  "actLive": "eGUBIq_actLive",
456
- "cmdSummary": "eGUBIq_cmdSummary",
587
+ "okState": "eGUBIq_okState",
588
+ "filterWrap": "eGUBIq_filterWrap",
457
589
  "foot": "eGUBIq_foot",
458
- "filterBtnOn": "eGUBIq_filterBtnOn",
459
- "grow": "eGUBIq_grow",
460
- "actBroken": "eGUBIq_actBroken",
461
- "act": "eGUBIq_act",
462
- "pagerSize": "eGUBIq_pagerSize",
463
- "pageOn": "eGUBIq_pageOn",
464
- "barWave": "eGUBIq_barWave",
590
+ "tab": "eGUBIq_tab",
591
+ "tabs": "eGUBIq_tabs",
592
+ "loading": "eGUBIq_loading",
593
+ "barFill": "eGUBIq_barFill",
594
+ "searchInline": "eGUBIq_searchInline",
595
+ "pageBtn": "eGUBIq_pageBtn",
596
+ "sub": "eGUBIq_sub",
465
597
  "tag": "eGUBIq_tag",
598
+ "backupGrid": "eGUBIq_backupGrid",
599
+ "dangerBtn": "eGUBIq_dangerBtn",
600
+ "barWave": "eGUBIq_barWave",
601
+ "pct": "eGUBIq_pct",
602
+ "filterPanel": "eGUBIq_filterPanel",
603
+ "filterTitle": "eGUBIq_filterTitle",
604
+ "desc": "eGUBIq_desc",
605
+ "actWarn": "eGUBIq_actWarn",
606
+ "sizeLabel": "eGUBIq_sizeLabel",
607
+ "sp": "eGUBIq_sp",
608
+ "sizeBtn": "eGUBIq_sizeBtn",
609
+ "backupCard": "eGUBIq_backupCard",
466
610
  "head": "eGUBIq_head",
467
- "searchInline": "eGUBIq_searchInline",
468
- "tab": "eGUBIq_tab",
611
+ "src": "eGUBIq_src",
612
+ "empty": "eGUBIq_empty",
613
+ "on": "eGUBIq_on",
614
+ "av": "eGUBIq_av",
615
+ "pager": "eGUBIq_pager",
616
+ "backupButton": "eGUBIq_backupButton",
617
+ "backupInput": "eGUBIq_backupInput",
618
+ "swatches": "eGUBIq_swatches",
619
+ "sect": "eGUBIq_sect",
469
620
  "filterBtn": "eGUBIq_filterBtn",
470
621
  "catsWrap": "eGUBIq_catsWrap",
471
- "catsToggle": "eGUBIq_catsToggle",
622
+ "root": "eGUBIq_root",
623
+ "top": "eGUBIq_top",
472
624
  "title": "eGUBIq_title",
473
- "themesGrid": "eGUBIq_themesGrid",
474
- "nm": "eGUBIq_nm",
475
- "pager": "eGUBIq_pager",
625
+ "backupCheck": "eGUBIq_backupCheck",
476
626
  "irow": "eGUBIq_irow",
477
- "pageBtn": "eGUBIq_pageBtn",
478
- "staleAction": "eGUBIq_staleAction",
479
- "pageInfo": "eGUBIq_pageInfo",
480
- "err": "eGUBIq_err",
627
+ "filterOption": "eGUBIq_filterOption",
628
+ "grow": "eGUBIq_grow",
629
+ "cancelBtn": "eGUBIq_cancelBtn",
630
+ "pagerSize": "eGUBIq_pagerSize",
631
+ "modalNote": "eGUBIq_modalNote",
632
+ "dshmSlide": "eGUBIq_dshmSlide",
633
+ "filterGroup": "eGUBIq_filterGroup",
634
+ "card": "eGUBIq_card",
481
635
  "catsCollapsed": "eGUBIq_catsCollapsed",
482
- "actWarn": "eGUBIq_actWarn",
636
+ "body": "eGUBIq_body",
637
+ "cmdSummary": "eGUBIq_cmdSummary",
483
638
  "actWhy": "eGUBIq_actWhy",
484
- "src": "eGUBIq_src",
485
- "card": "eGUBIq_card",
486
639
  "star": "eGUBIq_star",
487
- "dangerBtn": "eGUBIq_dangerBtn",
488
- "pct": "eGUBIq_pct",
489
- "filterGroup": "eGUBIq_filterGroup",
490
- "pageEllipsis": "eGUBIq_pageEllipsis",
491
- "grid": "eGUBIq_grid",
492
- "dshmSlide": "eGUBIq_dshmSlide",
493
- "av": "eGUBIq_av",
494
- "sizeBtn": "eGUBIq_sizeBtn",
495
- "root": "eGUBIq_root",
496
- "filterWrap": "eGUBIq_filterWrap",
497
- "pagerPages": "eGUBIq_pagerPages",
498
- "row1": "eGUBIq_row1",
499
- "owner": "eGUBIq_owner",
640
+ "cats": "eGUBIq_cats",
641
+ "bar": "eGUBIq_bar",
642
+ "irowMissing": "eGUBIq_irowMissing",
643
+ "sizeOn": "eGUBIq_sizeOn",
644
+ "backupPrimary": "eGUBIq_backupPrimary",
645
+ "backupActions": "eGUBIq_backupActions",
646
+ "nm": "eGUBIq_nm",
500
647
  "warnBtn": "eGUBIq_warnBtn",
501
- "tabs": "eGUBIq_tabs",
502
- "titleRow": "eGUBIq_titleRow",
503
- "barFill": "eGUBIq_barFill",
648
+ "pageEllipsis": "eGUBIq_pageEllipsis",
649
+ "warnLine": "eGUBIq_warnLine",
504
650
  "spec": "eGUBIq_spec",
505
- "cmdDetails": "eGUBIq_cmdDetails",
506
- "top": "eGUBIq_top",
507
- "sub": "eGUBIq_sub",
508
- "restart": "eGUBIq_restart",
651
+ "staleAction": "eGUBIq_staleAction",
652
+ "grid": "eGUBIq_grid",
509
653
  "descTight": "eGUBIq_descTight",
510
- "progress": "eGUBIq_progress",
511
- "cmd": "eGUBIq_cmd",
512
- "bar": "eGUBIq_bar",
513
- "cancelBtn": "eGUBIq_cancelBtn",
514
- "sect": "eGUBIq_sect",
515
- "spin": "eGUBIq_spin",
516
- "filterPanel": "eGUBIq_filterPanel",
517
654
  "dot": "eGUBIq_dot",
655
+ "shot": "eGUBIq_shot",
656
+ "backupMessage": "eGUBIq_backupMessage",
657
+ "filterBtnOn": "eGUBIq_filterBtnOn",
658
+ "cmdDetails": "eGUBIq_cmdDetails",
659
+ "pageOn": "eGUBIq_pageOn",
660
+ "restart": "eGUBIq_restart",
661
+ "backupWarn": "eGUBIq_backupWarn",
662
+ "themesGrid": "eGUBIq_themesGrid",
663
+ "owner": "eGUBIq_owner",
664
+ "pageInfo": "eGUBIq_pageInfo",
518
665
  "catsRow": "eGUBIq_catsRow",
519
- "sp": "eGUBIq_sp",
520
- "loading": "eGUBIq_loading",
521
- "on": "eGUBIq_on",
522
- "cats": "eGUBIq_cats",
523
- "filterOption": "eGUBIq_filterOption",
524
- "sizeLabel": "eGUBIq_sizeLabel"
666
+ "act": "eGUBIq_act",
667
+ "catsToggle": "eGUBIq_catsToggle",
668
+ "spin": "eGUBIq_spin",
669
+ "progress": "eGUBIq_progress",
670
+ "err": "eGUBIq_err",
671
+ "pagerPages": "eGUBIq_pagerPages",
672
+ "dangerArmed": "eGUBIq_dangerArmed",
673
+ "shots": "eGUBIq_shots",
674
+ "row1": "eGUBIq_row1",
675
+ "titleRow": "eGUBIq_titleRow",
676
+ "actBroken": "eGUBIq_actBroken",
677
+ "cmd": "eGUBIq_cmd"
525
678
  };
526
679
  //#endregion
527
680
  //#region src/client/MarketSection.tsx
@@ -579,6 +732,41 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
579
732
  });
580
733
  }
581
734
  /**
735
+ * AppStore-style screenshot strip in the install detail dialog (#61).
736
+ * Curated registry screenshots win; otherwise images are extracted from the
737
+ * repo README. Requests start only once the dialog opens; failures — no
738
+ * README, no images, broken links — degrade to rendering nothing at all.
739
+ */
740
+ function ScreenshotStrip({ plugin }) {
741
+ const [shots, setShots] = (0, react.useState)([]);
742
+ const [broken, setBroken] = (0, react.useState)([]);
743
+ (0, react.useEffect)(() => {
744
+ let live = true;
745
+ setShots([]);
746
+ setBroken([]);
747
+ pluginScreenshots(plugin).then((list) => {
748
+ if (live) setShots(list);
749
+ });
750
+ return () => {
751
+ live = false;
752
+ };
753
+ }, [plugin]);
754
+ const visible = shots.filter((src) => !broken.includes(src));
755
+ if (visible.length === 0) return null;
756
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
757
+ className: Market_module_css_default.shots,
758
+ children: visible.map((src) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
759
+ className: Market_module_css_default.shot,
760
+ src,
761
+ alt: "",
762
+ loading: "lazy",
763
+ decoding: "async",
764
+ referrerPolicy: "no-referrer",
765
+ onError: () => setBroken((prev) => prev.includes(src) ? prev : prev.concat(src))
766
+ }, src))
767
+ });
768
+ }
769
+ /**
582
770
  * Official-style market glyph: the shared block-grid brand mark converted to
583
771
  * the official monochrome icon form (16×16, fill="currentColor") so it
584
772
  * follows the active theme. Mirrors the settings-nav glyph used for the
@@ -617,6 +805,38 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
617
805
  96
618
806
  ];
619
807
  const DEFAULT_PAGE_SIZE = 24;
808
+ const WEBDAV_STORAGE_KEY = "dshm-webdav";
809
+ function savedWebdav() {
810
+ try {
811
+ const value = JSON.parse(localStorage.getItem(WEBDAV_STORAGE_KEY) ?? "{}");
812
+ return {
813
+ url: typeof value.url === "string" ? value.url : "",
814
+ username: typeof value.username === "string" ? value.username : "",
815
+ password: "",
816
+ auto: value.auto === true
817
+ };
818
+ } catch {
819
+ return {
820
+ url: "",
821
+ username: "",
822
+ password: "",
823
+ auto: false
824
+ };
825
+ }
826
+ }
827
+ function backupDependencies(value) {
828
+ if (value === null || typeof value !== "object") throw new Error("invalid backup");
829
+ const backup = value;
830
+ if (backup.format !== "dsh-profile-backup" || backup.version !== .2) throw new Error("unsupported backup format");
831
+ const files = backup.files;
832
+ if (!Array.isArray(files)) throw new Error("unsupported backup format");
833
+ const manifest = files.find((file) => file !== null && typeof file === "object" && file.path === "package.json");
834
+ if (manifest?.json === null || typeof manifest?.json !== "object" || Array.isArray(manifest.json)) throw new Error("backup package.json is invalid");
835
+ const dependencies = manifest.json.dependencies;
836
+ if (dependencies === null || typeof dependencies !== "object" || Array.isArray(dependencies)) return {};
837
+ if (!Object.values(dependencies).every((spec) => typeof spec === "string")) throw new Error("backup dependencies are invalid");
838
+ return dependencies;
839
+ }
620
840
  /** Sort field choices in the filter panel. */
621
841
  const SORT_FIELD_OPTIONS = [{
622
842
  key: "stars",
@@ -656,6 +876,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
656
876
  ];
657
877
  function MarketSection(props) {
658
878
  const t = props.t;
879
+ const initialWebdav = (0, react.useMemo)(savedWebdav, []);
659
880
  const localeSnap = (0, react.useSyncExternalStore)((cb) => props.locale.subscribe(cb), () => props.locale.getSnapshot());
660
881
  const lang = String(localeSnap.active).toLowerCase().startsWith("zh") ? "zh" : "en";
661
882
  const themeSnap = (0, react.useSyncExternalStore)(props.themeStore.subscribe, props.themeStore.getSnapshot);
@@ -666,6 +887,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
666
887
  cachedInstalled = value;
667
888
  setInstalledState(value);
668
889
  }, []);
890
+ const [installedFiles, setInstalledFiles] = (0, react.useState)([]);
669
891
  const [skins, setSkins] = (0, react.useState)([]);
670
892
  const [tab, setTab] = (0, react.useState)(() => {
671
893
  const saved = sessionStorage.getItem("dshm-tab");
@@ -720,6 +942,15 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
720
942
  const [restartEnabled, setRestartEnabled] = (0, react.useState)(false);
721
943
  const [restarting, setRestarting] = (0, react.useState)(false);
722
944
  const [showTop, setShowTop] = (0, react.useState)(false);
945
+ const [backupBusy, setBackupBusy] = (0, react.useState)(false);
946
+ const [backupMessage, setBackupMessage] = (0, react.useState)(null);
947
+ const [backupRestored, setBackupRestored] = (0, react.useState)(false);
948
+ const [pendingBackup, setPendingBackup] = (0, react.useState)(null);
949
+ const [pendingDependencies, setPendingDependencies] = (0, react.useState)({});
950
+ const [webdavUrl, setWebdavUrl] = (0, react.useState)(initialWebdav.url);
951
+ const [webdavUser, setWebdavUser] = (0, react.useState)(initialWebdav.username);
952
+ const [webdavPassword, setWebdavPassword] = (0, react.useState)(initialWebdav.password);
953
+ const [autoBackup, setAutoBackup] = (0, react.useState)(initialWebdav.auto);
723
954
  const bodyRef = (0, react.useRef)(null);
724
955
  const [sortField, setSortField] = (0, react.useState)("stars");
725
956
  const [sortDir, setSortDir] = (0, react.useState)("desc");
@@ -734,6 +965,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
734
965
  const refreshInstalled = (0, react.useCallback)((force) => {
735
966
  fetch("/dsh-market/installed", { cache: "no-store" }).then((res) => res.json()).then((body) => {
736
967
  setInstalled(body.installed || {});
968
+ setInstalledFiles(Array.isArray(body.present) ? body.present : Object.keys(body.installed || {}));
737
969
  setSkins(body.live || []);
738
970
  if (body.activation && typeof body.activation === "object") setActivations(body.activation);
739
971
  }).catch(() => {});
@@ -1144,7 +1376,100 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1144
1376
  };
1145
1377
  next();
1146
1378
  }, [updatableNames, doUpdate]);
1147
- const pendingRestart = doneUrls.length + updatedNames.length + removedCount;
1379
+ const finishRestore = (0, react.useCallback)((body) => {
1380
+ const errors = Array.isArray(body.errors) ? body.errors : [];
1381
+ if (errors.length > 0) window.alert(`${t("restorePartial")}\n\n${errors.map((item) => `${String(item.name)}: ${String(item.error)}`).join("\n")}`);
1382
+ setBackupRestored(true);
1383
+ setBackupMessage(t("restoreDone"));
1384
+ if (errors.length === 0) {
1385
+ setPendingBackup(null);
1386
+ setPendingDependencies({});
1387
+ }
1388
+ refreshInstalled(true);
1389
+ }, [refreshInstalled, t]);
1390
+ const previewBackup = (0, react.useCallback)((backup) => {
1391
+ const dependencies = backupDependencies(backup);
1392
+ setPendingBackup(backup);
1393
+ setPendingDependencies(dependencies);
1394
+ setBackupMessage(t("restorePreviewDone"));
1395
+ setTab("installed");
1396
+ }, [t]);
1397
+ const restoreBackup = (0, react.useCallback)(() => {
1398
+ if (pendingBackup === null) return Promise.resolve();
1399
+ if (!window.confirm(t("restoreConfirm"))) return Promise.resolve();
1400
+ setBackupBusy(true);
1401
+ setBackupMessage(null);
1402
+ return fetch("/dsh-market/restore", {
1403
+ method: "POST",
1404
+ headers: { "content-type": "application/json" },
1405
+ body: JSON.stringify({ backup: pendingBackup })
1406
+ }).then(async (response) => {
1407
+ const body = await response.json();
1408
+ if (!response.ok) throw new Error(String(body.error || "restore failed"));
1409
+ finishRestore(body);
1410
+ }).catch((error) => setBackupMessage(String(error))).finally(() => setBackupBusy(false));
1411
+ }, [
1412
+ finishRestore,
1413
+ pendingBackup,
1414
+ t
1415
+ ]);
1416
+ const runWebdav = (0, react.useCallback)((action) => {
1417
+ if (webdavUrl.trim() === "") return;
1418
+ setBackupBusy(true);
1419
+ setBackupMessage(null);
1420
+ fetch("/dsh-market/webdav", {
1421
+ method: "POST",
1422
+ headers: { "content-type": "application/json" },
1423
+ body: JSON.stringify({
1424
+ action,
1425
+ url: webdavUrl.trim(),
1426
+ username: webdavUser,
1427
+ password: webdavPassword
1428
+ })
1429
+ }).then(async (response) => {
1430
+ const body = await response.json();
1431
+ if (!response.ok) throw new Error(String(body.error || "WebDAV failed"));
1432
+ if (action === "restore") previewBackup(body.backup);
1433
+ if (action === "backup") {
1434
+ try {
1435
+ localStorage.setItem("dshm-webdav-last", String(Date.now()));
1436
+ } catch {}
1437
+ setBackupMessage(t("backupDone"));
1438
+ }
1439
+ }).catch((error) => setBackupMessage(String(error))).finally(() => setBackupBusy(false));
1440
+ }, [
1441
+ previewBackup,
1442
+ t,
1443
+ webdavPassword,
1444
+ webdavUrl,
1445
+ webdavUser
1446
+ ]);
1447
+ (0, react.useEffect)(() => {
1448
+ try {
1449
+ localStorage.setItem(WEBDAV_STORAGE_KEY, JSON.stringify({
1450
+ url: webdavUrl,
1451
+ username: webdavUser,
1452
+ auto: autoBackup
1453
+ }));
1454
+ } catch {}
1455
+ if (!autoBackup || webdavUrl.trim() === "") return;
1456
+ let last = 0;
1457
+ try {
1458
+ last = Number(localStorage.getItem("dshm-webdav-last")) || 0;
1459
+ } catch {}
1460
+ if (Date.now() - last >= 864e5) runWebdav("backup");
1461
+ }, [
1462
+ autoBackup,
1463
+ runWebdav,
1464
+ webdavUrl,
1465
+ webdavUser
1466
+ ]);
1467
+ const pendingRestart = doneUrls.length + updatedNames.length + removedCount + (backupRestored ? 1 : 0);
1468
+ const displayedInstalled = pendingBackup === null ? installed : {
1469
+ ...pendingDependencies,
1470
+ ...installed
1471
+ };
1472
+ const missingRestoreCount = Object.keys(pendingDependencies).filter((name) => !installedFiles.includes(name)).length;
1148
1473
  const hasUpdates = Object.keys(installed).some((name) => !updatedNames.includes(name) && updates[name] && updates[name].updateAvailable);
1149
1474
  /** Live status line: structured phase, or the human-line fallback. */
1150
1475
  const phasePart = progressPhase != null ? phaseLabel(progressPhase, t) + (progressCurrent !== null ? " · " + progressCurrent : "") + (progressDone > 0 ? " · " + t("packagesDone").replace("{0}", String(progressDone)) : "") : progressLine || t("progressHint");
@@ -1491,8 +1816,13 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1491
1816
  className: Market_module_css_default.dot
1492
1817
  })]
1493
1818
  }),
1819
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1820
+ className: tab === "backup" ? `${Market_module_css_default.tab} ${Market_module_css_default.on}` : Market_module_css_default.tab,
1821
+ onClick: () => setTab("backup"),
1822
+ children: t("tabBackup")
1823
+ }),
1494
1824
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: Market_module_css_default.grow }),
1495
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1825
+ tab !== "backup" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1496
1826
  className: Market_module_css_default.searchInline,
1497
1827
  icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
1498
1828
  placeholder: t("searchPh"),
@@ -1518,6 +1848,23 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1518
1848
  })
1519
1849
  ]
1520
1850
  }),
1851
+ tab === "installed" && pendingBackup !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1852
+ className: Market_module_css_default.restart,
1853
+ children: [
1854
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "↺" }),
1855
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1856
+ className: Market_module_css_default.grow,
1857
+ children: t("restoreMissing").replace("{0}", String(missingRestoreCount))
1858
+ }),
1859
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1860
+ variant: "primary",
1861
+ size: "sm",
1862
+ disabled: backupBusy,
1863
+ onClick: restoreBackup,
1864
+ children: backupBusy ? t("backupWorking") : t("restoreStart")
1865
+ })
1866
+ ]
1867
+ }),
1521
1868
  hotUrls.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1522
1869
  className: Market_module_css_default.restart,
1523
1870
  children: [
@@ -1631,7 +1978,137 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1631
1978
  className: Market_module_css_default.body,
1632
1979
  ref: bodyRef,
1633
1980
  onScroll: (e) => setShowTop(e.currentTarget.scrollTop > 400),
1634
- children: tab === "discover" ? loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1981
+ children: tab === "backup" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1982
+ className: Market_module_css_default.backupGrid,
1983
+ children: [
1984
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1985
+ className: Market_module_css_default.backupCard,
1986
+ children: [
1987
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("backupLocal") }),
1988
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("backupHint") }),
1989
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1990
+ className: Market_module_css_default.backupWarn,
1991
+ children: t("credsWarning")
1992
+ }),
1993
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1994
+ className: Market_module_css_default.backupActions,
1995
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1996
+ className: `${Market_module_css_default.backupButton} ${Market_module_css_default.backupPrimary}`,
1997
+ href: "/dsh-market/backup",
1998
+ download: true,
1999
+ "aria-disabled": backupBusy,
2000
+ children: t("backupDownload")
2001
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2002
+ className: Market_module_css_default.backupButton,
2003
+ "aria-disabled": backupBusy,
2004
+ children: [backupBusy ? t("backupWorking") : t("backupImport"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2005
+ type: "file",
2006
+ accept: "application/json,.json",
2007
+ disabled: backupBusy,
2008
+ onChange: (event) => {
2009
+ const file = event.currentTarget.files?.[0];
2010
+ event.currentTarget.value = "";
2011
+ if (file !== void 0) file.text().then((text) => previewBackup(JSON.parse(text))).catch((error) => setBackupMessage(String(error)));
2012
+ }
2013
+ })]
2014
+ })]
2015
+ })
2016
+ ]
2017
+ }),
2018
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2019
+ className: Market_module_css_default.backupCard,
2020
+ children: [
2021
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("webdav") }),
2022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2023
+ className: Market_module_css_default.backupInput,
2024
+ "aria-label": t("webdavPreset"),
2025
+ defaultValue: "",
2026
+ onChange: (event) => {
2027
+ const urls = {
2028
+ jianguoyun: "https://dav.jianguoyun.com/dav/dsh-profile-backup.json",
2029
+ koofr: "https://app.koofr.net/dav/Koofr/dsh-profile-backup.json",
2030
+ nextcloud: "https://nextcloud.example/remote.php/dav/files/USERNAME/dsh-profile-backup.json"
2031
+ };
2032
+ if (urls[event.target.value] !== void 0) setWebdavUrl(urls[event.target.value]);
2033
+ },
2034
+ children: [
2035
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2036
+ value: "",
2037
+ children: t("webdavPreset")
2038
+ }),
2039
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2040
+ value: "jianguoyun",
2041
+ children: "坚果云 / Nutstore"
2042
+ }),
2043
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2044
+ value: "koofr",
2045
+ children: "Koofr"
2046
+ }),
2047
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2048
+ value: "nextcloud",
2049
+ children: "Nextcloud"
2050
+ })
2051
+ ]
2052
+ }),
2053
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2054
+ className: Market_module_css_default.backupInput,
2055
+ type: "url",
2056
+ value: webdavUrl,
2057
+ placeholder: t("webdavUrl"),
2058
+ onChange: (e) => setWebdavUrl(e.target.value)
2059
+ }),
2060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2061
+ className: Market_module_css_default.backupInput,
2062
+ autoComplete: "username",
2063
+ value: webdavUser,
2064
+ placeholder: t("webdavUser"),
2065
+ onChange: (e) => setWebdavUser(e.target.value)
2066
+ }),
2067
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2068
+ className: Market_module_css_default.backupInput,
2069
+ type: "password",
2070
+ autoComplete: "current-password",
2071
+ value: webdavPassword,
2072
+ placeholder: t("webdavPassword"),
2073
+ onChange: (e) => setWebdavPassword(e.target.value)
2074
+ }),
2075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2076
+ className: Market_module_css_default.backupActions,
2077
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2078
+ variant: "primary",
2079
+ size: "sm",
2080
+ disabled: backupBusy || webdavUrl.trim() === "",
2081
+ onClick: () => runWebdav("backup"),
2082
+ children: backupBusy ? t("backupWorking") : t("webdavUpload")
2083
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2084
+ variant: "outline",
2085
+ size: "sm",
2086
+ disabled: backupBusy || webdavUrl.trim() === "",
2087
+ onClick: () => runWebdav("restore"),
2088
+ children: t("webdavRestore")
2089
+ })]
2090
+ }),
2091
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2092
+ className: Market_module_css_default.backupCheck,
2093
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2094
+ type: "checkbox",
2095
+ checked: autoBackup,
2096
+ onChange: (e) => setAutoBackup(e.target.checked)
2097
+ }), t("autoBackup")]
2098
+ }),
2099
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("webdavNote") }),
2100
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2101
+ className: Market_module_css_default.backupWarn,
2102
+ children: t("credsWarning")
2103
+ })
2104
+ ]
2105
+ }),
2106
+ backupMessage !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2107
+ className: Market_module_css_default.backupMessage,
2108
+ children: backupMessage
2109
+ })
2110
+ ]
2111
+ }) : tab === "discover" ? loadError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1635
2112
  className: Market_module_css_default.empty,
1636
2113
  children: t("loadFail")
1637
2114
  }) : data === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -1812,10 +2289,11 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1812
2289
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1813
2290
  className: Market_module_css_default.grid,
1814
2291
  children: themePlugins$1.map(themePluginCard)
1815
- })] }) : Object.keys(installed).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2292
+ })] }) : Object.keys(displayedInstalled).length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1816
2293
  className: Market_module_css_default.empty,
1817
2294
  children: t("installedEmpty")
1818
- }) : Object.entries(installed).map(([name, spec]) => {
2295
+ }) : Object.entries(displayedInstalled).map(([name, spec]) => {
2296
+ const missing = pendingBackup !== null && !installedFiles.includes(name);
1819
2297
  const entry = data === null ? void 0 : entryForDep(data.plugins, name, String(spec));
1820
2298
  const status = updates[name];
1821
2299
  const act = activations[name];
@@ -1825,7 +2303,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1825
2303
  const ghSpec = /^github:([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:#|$)/.exec(specText);
1826
2304
  const repoUrl = entry !== void 0 ? entry.url : ghSpec !== null ? "https://github.com/" + ghSpec[1] : null;
1827
2305
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1828
- className: Market_module_css_default.irow,
2306
+ className: missing ? `${Market_module_css_default.irow} ${Market_module_css_default.irowMissing}` : Market_module_css_default.irow,
1829
2307
  children: [
1830
2308
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1831
2309
  style: { minWidth: 0 },
@@ -1906,7 +2384,10 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1906
2384
  rel: "noreferrer",
1907
2385
  children: t("readme")
1908
2386
  }),
1909
- updatedNames.includes(name) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2387
+ missing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2388
+ className: Market_module_css_default.owner,
2389
+ children: t("notInstalled")
2390
+ }) : updatedNames.includes(name) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1910
2391
  className: Market_module_css_default.okState,
1911
2392
  children: act?.state === "live" ? t("updatedLive") : t("updated")
1912
2393
  }) : updatingName === name ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
@@ -1929,7 +2410,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1929
2410
  className: Market_module_css_default.owner,
1930
2411
  children: t("upToDate")
1931
2412
  }),
1932
- name !== "dsh-market" && name !== "dshmarket" && (removingName === name ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2413
+ !missing && name !== "dsh-market" && name !== "dshmarket" && (removingName === name ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1933
2414
  variant: "outline",
1934
2415
  size: "sm",
1935
2416
  className: Market_module_css_default.dangerBtn,
@@ -1981,6 +2462,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
1981
2462
  children: t("confirm")
1982
2463
  })] }),
1983
2464
  children: [
2465
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScreenshotStrip, { plugin: confirming }),
1984
2466
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
1985
2467
  className: Market_module_css_default.cmdDetails,
1986
2468
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {