@androidsup/dsh-plugin-updates 0.1.0 → 0.1.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  创建于 2026-10-23
4
4
 
5
+ ## 0.1.1(2026-10-23)
6
+
7
+ - 新增「忽略此版本提醒」:清单逐项忽略 / 恢复,弹窗逐项或批量忽略,按 npm 版本 / GitHub 提交持久化;后续新版本仍提醒。
8
+ - 已忽略项保留在清单并可筛选,不参与全选,仍可手动更新;恢复提醒同时清除该项的旧关闭记录。
9
+ - 忽略与恢复后立即同步当前窗口的清单和弹窗;保存失败不报告成功,拒绝过期目标与正在执行操作时的请求。
10
+ - 顶部产品介绍增加中文 / English 切换,默认中文;包描述改为中文优先的双语文案,并新增英文介绍文档。
11
+ - 配置 GitHub Actions:普通提交和 PR 运行 Node.js 22 / 24 CI,稳定版本标签自动构建、测试、通过 npm OIDC 发布并创建附带安装包的 GitHub Release;支持同内容失败重跑,不保存长期 npm Token。
12
+
5
13
  ## 0.1.0
6
14
 
7
15
  - npm 包使用账号作用域 `@androidsup/dsh-plugin-updates`,避免与其他作者同名包混淆;桌面安装走 DSH 官方界面。
package/README.en.md ADDED
@@ -0,0 +1,67 @@
1
+ # DSH Plugin Update Manager
2
+
3
+ Created on 2026-10-23
4
+
5
+ [中文(默认)](README.md) · [English](README.en.md)
6
+
7
+ [Join the QQ group](https://qm.qq.com/q/eQC7OQXQe4) · QQ **1124103038** · [GitHub repository](https://github.com/AndroidSix/dsh-plugin-updates). Find it useful? Give us a Star!
8
+
9
+ **Only installed plugins. Every update checked and accounted for.**
10
+
11
+ `@androidsup/dsh-plugin-updates` is an independent DeepSeek Harness plugin with its own repository, npm package, directory, and lockfile. It does not require `dsh-multi-router` or any account-routing service.
12
+
13
+ ## Not a Marketplace
14
+
15
+ A marketplace helps you choose what to install. This plugin helps you decide whether to update what is already installed, verify the result, and understand failures.
16
+
17
+ - **Keep the source.** Compare npm versions on their original release channel and GitHub commits on their original branch or tag. Keep pinned Git commits pinned.
18
+ - **You decide.** Check daily while DSH is running; install only after explicit confirmation. Preserve enabled and disabled states.
19
+ - **Ignore a specific release.** Since **0.1.1**, ignore one npm version or Git commit from the reminder or installed-plugin list. Ignores survive restarts; newer versions and commits still notify you. Restore notifications or update manually at any time. Bulk selection skips ignored targets.
20
+ - **Verify the result.** Check the installed npm version or locked Git commit instead of trusting a successful command exit. Distinguish installation from activation or a required restart.
21
+ - **Handle failures.** Show per-plugin results and redacted diagnostics. Respect release-age policies and build-script approval; never disable global safeguards.
22
+
23
+ There is no discovery catalog, recommendation feed, ranking, new-plugin installation, uninstall, or enable/disable control. Use the native DSH plugin page for those actions.
24
+
25
+ ## Installation
26
+
27
+ Current stable version: **0.1.1**.
28
+
29
+ In the DSH desktop app, open **Plugins → Add plugin**, enter `@androidsup/dsh-plugin-updates@0.1.1`, choose the official npm registry, install, and enable it. The Electron app exclusively manages the `desktop` profile; do not modify it using the CLI.
30
+
31
+ For non-desktop profiles:
32
+
33
+ ```bash
34
+ dsh plugin --profile web add @androidsup/dsh-plugin-updates@0.1.1
35
+ ```
36
+
37
+ Use the full scoped package name. The unscoped `dsh-plugin-updates` package belongs to another author and is not this project.
38
+
39
+ ## Language and Privacy
40
+
41
+ In 0.1.1, the introduction has a Chinese / English selector, with Chinese as the default. The selector affects the introduction only; management controls remain in Chinese. The npm package description includes Chinese first, followed by English.
42
+
43
+ Check results and notification preferences are stored in the current DSH profile's `.dsh-plugin-updates/state.json`, not synced between profiles. Checks access only the npm registry or GitHub metadata for already installed plugins. There is no marketplace backend or analytics service; router configuration, credentials, and conversations are not uploaded.
44
+
45
+ The GitHub repository is currently private. Viewing it or leaving a Star requires repository access; the npm package is public.
46
+
47
+ ## Development
48
+
49
+ Requires Node.js `^22.19.0 || >=24.0.0`.
50
+
51
+ ```bash
52
+ npm ci
53
+ npm run check
54
+ npm run pack:release
55
+ ```
56
+
57
+ See the [Chinese documentation](README.md) for installation policies, safety boundaries, migration, and limitations. License: MIT.
58
+
59
+ ## Automated Releases
60
+
61
+ Pushes to `main` and pull requests run CI on Node.js 22.19.0 and 24. Only stable `vX.Y.Z` tags trigger `.github/workflows/publish.yml`; ordinary commits do not publish or update users' installations.
62
+
63
+ The release workflow validates the scoped package name and matching tag / package / lockfile versions, builds and tests, packs once, publishes through npm OIDC trusted publishing, verifies the registry integrity, and attaches the same archive to a GitHub Release. It needs no long-lived `NPM_TOKEN`. Official actions are pinned by commit, and release jobs do not use dependency caches.
64
+
65
+ The npm trusted publisher is bound to `AndroidSix/dsh-plugin-updates` and `publish.yml`. The source repository remains private; npm does not generate provenance for private repositories, even for a public npm package. OIDC authentication still applies. See the [official npm documentation](https://docs.npmjs.com/trusted-publishers/).
66
+
67
+ For the next release, update the version with `npm version X.Y.Z --no-git-tag-version`, update the changelog and both READMEs, run `npm run check`, commit all changed sources and build outputs, push `main`, then push `git tag vX.Y.Z`. See the Chinese guide for the full command sequence. Never move an already published tag. Failed workflows can be rerun: an existing npm version is skipped only when its integrity matches exactly; different contents require a new version.
package/README.md CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  创建于 2026-10-23
4
4
 
5
+ [中文(默认)](README.md) · [English introduction](README.en.md)
6
+
5
7
  [加入 QQ 交流群](https://qm.qq.com/q/eQC7OQXQe4) · 群号 **1124103038** · [GitHub 仓库](https://github.com/AndroidSix/dsh-plugin-updates)。觉得好用?点个 **Star** 鼓励一下。插件页面顶部也有入口。
6
8
 
7
9
  **只管已安装插件,让每一次更新有据可查。**
8
10
 
9
- `@androidsup/dsh-plugin-updates` 是独立的 DeepSeek Harness 插件,初始版本 **0.1.0**。它从 `dsh-multi-router` 拆出,不依赖路由、账号池、模型供应商、签到或 API 服务。拥有独立目录、Git 仓库、依赖锁文件和 npm 包,不需要 `dsh-multi-router` 源码、依赖或构建脚本。
11
+ `@androidsup/dsh-plugin-updates` 是独立的 DeepSeek Harness 插件,当前版本 **0.1.1**。它从 `dsh-multi-router` 拆出,不依赖路由、账号池、模型供应商、签到或 API 服务。拥有独立目录、Git 仓库、依赖锁文件和 npm 包,不需要 `dsh-multi-router` 源码、依赖或构建脚本。
10
12
 
11
13
  ## 不是插件市场
12
14
 
@@ -16,15 +18,36 @@
16
18
  | --- | --- |
17
19
  | 来源感知,而非只看包版本 | npm 比较 SemVer、沿用 `latest` / `next` 等频道;GitHub 比较原分支或标签的提交,包版本没变也能发现更新。固定 Git 提交不解除。 |
18
20
  | 用户决定,而非后台代装 | DSH 运行期间每 24 小时检查;同一目标处理后不重复提醒,更高版本或新提交再次提醒。逐个或批量查看目标、明确确认后才更新。 |
21
+ | 按版本忽略,而非关闭所有提醒 | 清单或弹窗可忽略指定 npm 版本 / GitHub 提交,重启后保留;更高版本 / 新提交仍提醒,可随时恢复或手动更新。 |
19
22
  | 核验结果,而非相信成功提示 | 更新后复查实际 npm 版本或锁文件中的 Git 提交。命令退出码为零但版本不符仍判为失败;区分已安装与重启后生效。 |
20
23
  | 不扰动现有配置 | 只操作当前 DSH 配置中用户安装的插件,包含停用项;排除内置与受保护组件,保留原启用状态,不自动换源。 |
21
24
  | 失败能继续处理 | 逐项显示失败,单项失败不阻断其它项;区分等待期、脚本授权、文件占用、兼容、网络及加载问题,可查看和复制脱敏诊断。 |
22
25
 
23
26
  **明确不做:**插件搜索发现、远端目录、推荐、排行、评分、新插件安装、卸载、启停管理、任意版本降级。页面中的搜索仅筛选已安装清单。首次安装及依赖脚本授权仍使用 DSH 原生插件页。
24
27
 
28
+ ## 忽略指定版本提醒
29
+
30
+ 以下功能自 **0.1.1** 起提供:
31
+
32
+ - 更新提醒弹窗:单项「忽略此版本」,或「忽略以上版本」批量忽略弹窗中的确切目标。
33
+ - 更新清单:每个可更新项提供「忽略此版本提醒」;已忽略项仍保留,显示标记,可点击「恢复此版本提醒」。
34
+ - 忽略只影响提醒和「选择全部未忽略」,不影响后台检查,也不禁止手动勾选和更新。可通过「已忽略提醒」筛选查看。
35
+ - npm 按确切版本、GitHub 按提交记录;忽略当前版本不会屏蔽后续新版本。记录仅保存在当前 DSH 配置,重启后仍有效,不跨配置同步。
36
+ - 页面顶部的产品介绍提供「中文 / English」切换,首次打开默认中文;切换仅影响介绍区,管理操作界面仍使用中文。npm 包描述为中文在前的双语文案。
37
+
25
38
  ## 安装与使用
26
39
 
27
- 独立 npm 包名为 `@androidsup/dsh-plugin-updates`。当前提供源码与本地安装包,尚未发布到 npm;完成 npm 登录后可按下文发布。开发环境要求 Node.js `^22.19.0 || >=24.0.0`。
40
+ 独立 npm 包 [`@androidsup/dsh-plugin-updates`](https://www.npmjs.com/package/@androidsup/dsh-plugin-updates) 已发布,当前版本为 **0.1.1**。
41
+
42
+ 桌面版:打开 DSH「插件」→「添加插件」,输入 `@androidsup/dsh-plugin-updates@0.1.1`,选择 npm 官方源,安装后点击「立即启用」。`desktop` 配置由 Electron 独占管理,CLI 不可修改。
43
+
44
+ 非桌面配置可使用官方 CLI,例如:
45
+
46
+ ```bash
47
+ dsh plugin --profile web add @androidsup/dsh-plugin-updates@0.1.1
48
+ ```
49
+
50
+ 如需从源码开发或生成本地安装包,要求 Node.js `^22.19.0 || >=24.0.0`:
28
51
 
29
52
  ```bash
30
53
  # 克隆独立仓库;私有仓库需要访问权限
@@ -38,7 +61,7 @@ npm run pack:release
38
61
  # 桌面版:在 DSH 插件页「添加插件」输入安装包绝对路径。
39
62
  # desktop 配置由 Electron 独占管理,CLI 不可修改。
40
63
  # 以下命令仅适用于非桌面配置,例如 web:
41
- dsh plugin --profile web add /absolute/path/androidsup-dsh-plugin-updates-0.1.0.tgz
64
+ dsh plugin --profile web add /absolute/path/androidsup-dsh-plugin-updates-0.1.1.tgz
42
65
  ```
43
66
 
44
67
  在 DSH 插件页打开本插件配置即可进入「插件更新管理」。支持按名称、来源、可更新状态筛选、单项或批量更新及取消。所有操作作用于当前配置,不跨配置、跨设备同步。
@@ -52,24 +75,38 @@ dsh plugin --profile web add /absolute/path/androidsup-dsh-plugin-updates-0.1.0.
52
75
 
53
76
  注意:npm 上不带作用域的 `dsh-plugin-updates` 属于其他作者,不是本项目。请使用完整的 `@androidsup/dsh-plugin-updates` 包名。
54
77
 
55
- ## 独立发布
78
+ ## 独立发布与 GitHub Actions
56
79
 
57
80
  源码仓库:<https://github.com/AndroidSix/dsh-plugin-updates>。当前 GitHub 仓库保持私有;面板顶部仓库链接也指向此独立仓库,未获访问权限的用户无法查看或 Star。是否公开仓库与是否发布 npm 是两个独立决定。
58
81
 
82
+ 普通 `main` 提交和 Pull Request 仅触发 CI,分别在 Node.js 22.19.0 与 24 上构建、测试并检查打包。**只有推送稳定版本标签 `vX.Y.Z` 才自动发布**,不会每次提交都发包,也不会后台更新用户的插件。
83
+
84
+ 发布流程在 `.github/workflows/publish.yml`:校验标签、包名与锁文件版本 → 独立构建和测试 → 打包 → npm OIDC 可信发布 → 核验 registry 完整性 → 创建 GitHub Release,附上同一份 `.tgz`。不需要 `NPM_TOKEN` Secret;Actions 固定到提交 SHA,发布任务不使用依赖缓存。GitHub Release 的写权限与 npm 发布任务隔离。
85
+
86
+ npm 的可信发布绑定 `AndroidSix/dsh-plugin-updates` 仓库和 `publish.yml` 工作流。换仓库或文件名后,需要包维护者重新绑定。当前源码仓库私有,npm 不生成 provenance 声明,但仍通过短期 OIDC 凭证发布;不会因此自动公开仓库。[npm 官方说明](https://docs.npmjs.com/trusted-publishers/)。
87
+
88
+ 后续发布示例(将版本替换为实际的新版本):
89
+
59
90
  ```bash
91
+ npm version 0.1.2 --no-git-tag-version
92
+ # 同步 CHANGELOG 和中英文说明
60
93
  npm run check
61
- npm pack --dry-run
62
- npm login --registry=https://registry.npmjs.org/
63
- npm publish
94
+ git add package.json package-lock.json lib cordis.patch.yml CHANGELOG.md README.md README.en.md
95
+ git commit -m "Release 0.1.2"
96
+ git push origin main
97
+ git tag v0.1.2
98
+ git push origin v0.1.2
64
99
  ```
65
100
 
66
- `prepublishOnly` 会在发布前重新构建并运行测试;npm 包配置为公开发布。首次发布需要账号拥有 `@androidsup/dsh-plugin-updates` 包名权限,并按 npm 要求完成二次验证。未成功发布前,请使用本地 `.tgz`,不要把包名安装说明当作已上线。
101
+ 标签与 `package.json`、`package-lock.json` 不一致时拒绝发布。版本不可覆盖;若工作流中途失败,可在 GitHub Actions 重跑:只有 npm 中已发布版本的完整性与当前包完全一致才跳过发包,并补齐 Release。内容不同则必须升新版本。不要删除或移动已经发布的标签。
102
+
103
+ 本机仍可运行 `npm publish`,`prepublishOnly` 会先构建并测试,且需自行完成 npm 登录和二次验证;正式发布优先使用上述标签流程。
67
104
 
68
105
  GitHub 仓库单独维护提交和版本,路由项目不再调用本项目的构建或测试。`node_modules/`、本地安装包、认证配置和运行时数据不纳入 Git。
69
106
 
70
107
  ## 数据与安全边界
71
108
 
72
- 检查记录和提醒去重保存在当前 DSH 配置目录的 `.dsh-plugin-updates/state.json`,目录 0700、文件 0600;不同配置相互独立。安装与实际文件变更交给 DSH 的插件管理接口及官方执行器,不自建安装器。
109
+ 检查记录、提醒去重和忽略版本保存在当前 DSH 配置目录的 `.dsh-plugin-updates/state.json`,目录 0700、文件 0600;不同配置相互独立。安装与实际文件变更交给 DSH 的插件管理接口及官方执行器,不自建安装器。
73
110
 
74
111
  本地 HTTP 接口为 `/.well-known/dsh-plugin-updates`:仅允许回环地址和同源请求,写操作要求会话随机密钥,只接受检查、清单刷新、更新、提醒处理及取消操作。后台没有新插件安装接口。浏览器诊断不返回原始安装输出、账号令牌或用户路径。
75
112
 
package/cordis.patch.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  - insert:
2
- - id: dsh-plugin-updates-0.1.0
3
- name: ./lib/host-v0.1.0.js
2
+ - id: dsh-plugin-updates-0.1.1
3
+ name: ./lib/host-v0.1.1.js
package/lib/client.js CHANGED
@@ -28,11 +28,11 @@ __export(client_exports, {
28
28
  name: () => name2
29
29
  });
30
30
  module.exports = __toCommonJS(client_exports);
31
- var import_react2 = require("react");
31
+ var import_react3 = require("react");
32
32
 
33
33
  // package.json
34
34
  var name = "@androidsup/dsh-plugin-updates";
35
- var version = "0.1.0";
35
+ var version = "0.1.1";
36
36
  var repository = {
37
37
  type: "git",
38
38
  url: "https://github.com/AndroidSix/dsh-plugin-updates.git"
@@ -63,15 +63,23 @@ function Modal({ title, close, children, busy }) {
63
63
  var updateNames = { unchecked: "\u5F85\u68C0\u67E5", available: "\u53EF\u66F4\u65B0", current: "\u5DF2\u540C\u6B65", pinned: "\u56FA\u5B9A\u63D0\u4EA4", unsupported: "\u672C\u5730\u6765\u6E90", failed: "\u68C0\u67E5 / \u5B89\u88C5\u5931\u8D25", installed: "\u5DF2\u5B89\u88C5" };
64
64
  function PluginUpdatesPanel({ data, busy, act }) {
65
65
  const [selected, setSelected] = (0, import_react.useState)([]), [plan, setPlan] = (0, import_react.useState)(null), [registry, setRegistry] = (0, import_react.useState)(""), [filter, setFilter] = (0, import_react.useState)("all"), [pluginSearch, setPluginSearch] = (0, import_react.useState)("");
66
- const [diagnostic, setDiagnostic] = (0, import_react.useState)(null), [copied, setCopied] = (0, import_react.useState)(false);
66
+ const [diagnostic, setDiagnostic] = (0, import_react.useState)(null), [copied, setCopied] = (0, import_react.useState)(false), [reminderNotice, setReminderNotice] = (0, import_react.useState)(null);
67
67
  const running = data?.job?.state === "running", blocked = busy || running;
68
68
  const rows = (data?.rows ?? []).filter((r) => r.source.kind !== "builtin"), available = rows.filter((r) => r.status === "available" || r.status === "failed" && r.target);
69
69
  (0, import_react.useEffect)(() => {
70
70
  if (data?.available && !running) void act({ action: !data.checkedAt || Date.now() - data.checkedAt > 864e5 ? "plugins.check" : "plugins.list" });
71
71
  }, []);
72
+ const notIgnored = available.filter((r) => !r.ignored), ignored = available.filter((r) => r.ignored);
72
73
  const chosen = available.filter((r) => selected.includes(r.name));
73
74
  const freshVersions = plan ? [...new Set(plan.rows.filter((r) => r.source.kind === "npm" && r.latestVersion).map((r) => `${r.source.package}@${r.latestVersion}`))] : [];
74
- const visibleRows = rows.filter((r) => r.name.toLowerCase().includes(pluginSearch.toLowerCase()) && (filter === "all" || (filter === "updates" ? available.includes(r) : r.source.kind === filter)));
75
+ const visibleRows = rows.filter((r) => r.name.toLowerCase().includes(pluginSearch.toLowerCase()) && (filter === "all" || (filter === "updates" ? notIgnored.includes(r) : filter === "ignored" ? ignored.includes(r) : r.source.kind === filter)));
76
+ async function toggleReminder(row) {
77
+ setReminderNotice(null);
78
+ if (await act({ action: row.ignored ? "plugins.unignore" : "plugins.ignore", keys: [row.targetKey] })) {
79
+ setReminderNotice({ name: row.name, targetKey: row.targetKey, ignored: !row.ignored, message: `${row.name}\uFF1A${row.ignored ? "\u5DF2\u6062\u590D\u6B64\u7248\u672C\u63D0\u9192" : "\u5DF2\u5FFD\u7565\u6B64\u7248\u672C\u63D0\u9192\uFF1B\u65B0\u7248\u672C\u4ECD\u4F1A\u63D0\u9192\uFF0C\u53EF\u968F\u65F6\u6062\u590D\u6216\u624B\u52A8\u66F4\u65B0"}` });
80
+ if (!row.ignored) setSelected((names) => names.filter((name3) => name3 !== row.name));
81
+ }
82
+ }
75
83
  async function submitPlan() {
76
84
  if (await act({ action: "plugins.update", names: plan.rows.map((r) => r.name), registry: registry || void 0, allowFreshVersions: plan.allowFresh === true ? freshVersions : [] })) {
77
85
  setPlan(null);
@@ -91,7 +99,9 @@ function PluginUpdatesPanel({ data, busy, act }) {
91
99
  rows.length,
92
100
  " \u4E2A\u7528\u6237\u5B89\u88C5\u63D2\u4EF6 \xB7 ",
93
101
  available.length,
94
- " \u4E2A\u53EF\u66F4\u65B0"
102
+ " \u4E2A\u53EF\u66F4\u65B0 \xB7 ",
103
+ ignored.length,
104
+ " \u4E2A\u5DF2\u5FFD\u7565\u63D0\u9192"
95
105
  ] })
96
106
  ] }),
97
107
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: blocked, onClick: () => {
@@ -103,6 +113,8 @@ function PluginUpdatesPanel({ data, busy, act }) {
103
113
  when(data.checkedAt),
104
114
  " \xB7 \u6BCF\u5929\u81EA\u52A8\u68C0\u67E5\u5E76\u63D0\u9192"
105
115
  ] }),
116
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "notice", children: "\u5FFD\u7565\u4EC5\u5C4F\u853D\u6307\u5B9A\u7248\u672C / \u63D0\u4EA4\u7684\u63D0\u9192\uFF0C\u91CD\u542F\u540E\u4FDD\u7559\uFF1B\u65B0\u7248\u672C\u4ECD\u63D0\u9192\u3002\u5DF2\u5FFD\u7565\u9879\u4E0D\u53C2\u4E0E\u5168\u9009\uFF0C\u4ECD\u53EF\u624B\u52A8\u66F4\u65B0\u6216\u6062\u590D\u63D0\u9192\u3002" }),
117
+ reminderNotice && rows.some((row) => row.name === reminderNotice.name && row.targetKey === reminderNotice.targetKey && row.ignored === reminderNotice.ignored) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: reminderNotice.message }),
106
118
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "help", children: [
107
119
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: "\u5B89\u88C5\u6E90\u4E0E\u68C0\u67E5\u89C4\u5219" }),
108
120
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack", children: [
@@ -152,7 +164,7 @@ function PluginUpdatesPanel({ data, busy, act }) {
152
164
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box table-shell", children: [
153
165
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "bar section-pad", children: [
154
166
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
155
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked || !available.length, onClick: () => setSelected(available.map((r) => r.name)), children: "\u9009\u62E9\u5168\u90E8\u53EF\u66F4\u65B0" }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked || !notIgnored.length, onClick: () => setSelected(notIgnored.map((r) => r.name)), children: "\u9009\u62E9\u5168\u90E8\u672A\u5FFD\u7565" }),
156
168
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked || !selected.length, onClick: () => setSelected([]), children: "\u6E05\u7A7A\u9009\u62E9" })
157
169
  ] }),
158
170
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "primary", disabled: blocked || !chosen.length, onClick: () => setPlan({ kind: "update", rows: chosen }), children: [
@@ -170,7 +182,8 @@ function PluginUpdatesPanel({ data, busy, act }) {
170
182
  "\u663E\u793A\u8303\u56F4",
171
183
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: filter, onChange: (e) => setFilter(e.target.value), children: [
172
184
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: "\u5168\u90E8\u7528\u6237\u5B89\u88C5\u63D2\u4EF6" }),
173
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "updates", children: "\u53EF\u66F4\u65B0 / \u91CD\u8BD5" }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "updates", children: "\u53EF\u66F4\u65B0 / \u91CD\u8BD5\uFF08\u672A\u5FFD\u7565\uFF09" }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "ignored", children: "\u5DF2\u5FFD\u7565\u63D0\u9192" }),
174
187
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "npm", children: "npm \u6765\u6E90" }),
175
188
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "github", children: "GitHub \u6765\u6E90" })
176
189
  ] })
@@ -198,9 +211,15 @@ function PluginUpdatesPanel({ data, busy, act }) {
198
211
  ] }),
199
212
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: [
200
213
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: r.status === "failed" ? "error" : r.status === "available" ? "good" : "muted", children: updateNames[r.status] }),
214
+ r.ignored && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: "muted", children: "\u5DF2\u5FFD\u7565\u6B64\u7248\u672C\u63D0\u9192" }),
201
215
  r.message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { className: r.status === "failed" ? "error" : "muted", children: r.message })
202
216
  ] }),
203
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "cell-actions", children: available.includes(r) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked, onClick: () => setPlan({ kind: "update", rows: [r] }), children: "\u67E5\u770B\u5E76\u66F4\u65B0" }) : "\u2014" })
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "cell-actions", children: available.includes(r) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "stack row-actions", children: [
218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked, onClick: () => setPlan({ kind: "update", rows: [r] }), children: "\u67E5\u770B\u5E76\u66F4\u65B0" }),
219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: blocked, "aria-label": `${r.ignored ? "\u6062\u590D" : "\u5FFD\u7565"} ${r.name} \u6B64\u7248\u672C\u63D0\u9192`, onClick: () => {
220
+ void toggleReminder(r);
221
+ }, children: r.ignored ? "\u6062\u590D\u6B64\u7248\u672C\u63D0\u9192" : "\u5FFD\u7565\u6B64\u7248\u672C\u63D0\u9192" })
222
+ ] }) : "\u2014" })
204
223
  ] }, r.name)) })
205
224
  ] }),
206
225
  !visibleRows.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "empty", children: running ? "\u6B63\u5728\u8BFB\u53D6\u63D2\u4EF6\u6E05\u5355\u2026" : rows.length ? "\u6CA1\u6709\u5339\u914D\u7684\u5DF2\u5B89\u88C5\u63D2\u4EF6\uFF0C\u8BF7\u8C03\u6574\u7B5B\u9009\u3002" : "\u5F53\u524D\u914D\u7F6E\u5C1A\u672A\u8BFB\u53D6\u63D2\u4EF6\uFF0C\u8BF7\u68C0\u67E5\u66F4\u65B0\u3002" })
@@ -267,17 +286,95 @@ var styles = `
267
286
  .pu .community{padding:10px 14px;border:1px solid var(--pu-border);border-radius:8px;background:var(--pu-card);font-size:13px;column-gap:16px;row-gap:8px}.pu .community a{display:inline-flex;align-items:center;min-height:44px;color:var(--pu-accent);font-weight:500;text-decoration:underline;text-underline-offset:3px}.pu .community a:hover{opacity:.8}.pu .community a:focus-visible{outline:2px solid var(--pu-accent);outline-offset:4px;border-radius:2px}.pu .community span{min-width:0;overflow-wrap:anywhere}
268
287
  .pu button,.pu input,.pu select,.pu textarea{font:inherit;color:inherit;border:1px solid var(--pu-border);border-radius:8px;background:var(--pu-bg);min-height:44px;padding:9px 12px}.pu button{cursor:pointer;transition:background .15s}.pu button:hover{background:var(--pu-soft)}.pu button:disabled{opacity:.45;cursor:default}.pu button.primary{background:var(--pu-text);color:var(--pu-card);border-color:var(--pu-text)}.pu :is(button,input,select,textarea,summary):focus-visible{outline:2px solid var(--pu-accent);outline-offset:3px}.pu input:not([type=checkbox]),.pu select,.pu textarea{width:100%;min-width:0}.pu input[type=checkbox]{width:18px;height:18px;min-height:0;accent-color:var(--pu-accent)}.pu label{display:flex;flex-direction:column;gap:6px;font-size:13px}.pu label.check{flex-direction:row;align-items:center;gap:8px;min-height:44px}.pu .filter{min-width:170px;max-width:240px}
269
288
  .pu .error{color:var(--dsw-alias-state-error-primary,#ef9393)}.pu .good{color:var(--dsw-alias-state-success-primary,#77ca9d)}.pu summary{cursor:pointer;color:var(--pu-muted);min-height:44px;display:flex;align-items:center}.pu details>div{padding-top:12px}.pu progress{width:100%;height:8px;accent-color:var(--pu-accent)}.pu .item{padding:16px 0;border-top:1px solid var(--pu-border)}.pu .empty{padding:24px;text-align:center;color:var(--pu-muted)}.pu .steps{padding:14px 16px;border:1px solid var(--pu-border);background:var(--pu-soft);border-radius:10px}
289
+ .pu .row-actions{gap:8px;align-items:stretch}.pu .reminder-list{list-style:none;padding:0;margin:0}.pu .reminder-list li{padding:12px 0;border-bottom:1px solid var(--pu-border)}.pu .reminder-list .mono{flex:1;min-width:180px}.pu .reminder-list button{flex-shrink:0}
270
290
  .pu .table-shell{padding:0;overflow:hidden}.pu .section-pad{padding:16px 20px}.pu .scroll{overflow:auto;max-width:100%}.pu .scroll:focus-visible{outline:2px solid var(--pu-accent);outline-offset:-3px}.pu .table{width:100%;min-width:760px;border-collapse:collapse;font-size:14px}.pu th,.pu td{text-align:left;border-bottom:1px solid var(--pu-border);padding:16px 20px;vertical-align:middle}.pu th{background:var(--pu-soft);font-size:12px;color:var(--pu-muted)}.pu .table td small{display:block;font-size:12px;margin-top:3px}.pu .table tr:last-child td{border-bottom:0}.pu .table tr.selected,.pu .table tbody tr:hover{background:var(--pu-soft)}.pu .cell-actions{text-align:right;white-space:nowrap}
271
291
  .pu dialog{color:var(--pu-text);background:var(--pu-bg);border:1px solid var(--pu-border);border-radius:16px;width:min(900px,calc(100vw - 32px));max-height:calc(100dvh - 40px);padding:0;box-shadow:0 24px 80px #0008}.pu dialog::backdrop{background:#000a}.pu .modal-head,.pu .modal-foot{padding:18px 24px;background:var(--pu-bg);position:sticky;z-index:1}.pu .modal-head{top:0;border-bottom:1px solid var(--pu-border)}.pu .modal-foot{bottom:0;border-top:1px solid var(--pu-border)}.pu .modal-body{padding:24px}
272
292
  @media(max-width:600px){.pu .principles{grid-template-columns:1fr;gap:12px}.pu .box,.pu .section-pad,.pu .modal-body,.pu .modal-head,.pu .modal-foot{padding:16px}.pu .box.table-shell{padding:0}.pu .filter{max-width:none;flex:1}.pu h2{font-size:22px}}
273
293
  @media(prefers-reduced-motion:reduce){.pu *{transition:none!important}}
274
294
  `;
275
295
 
276
- // src/client.jsx
296
+ // src/intro.jsx
297
+ var import_react2 = require("react");
277
298
  var import_jsx_runtime2 = require("react/jsx-runtime");
299
+ var descriptions = {
300
+ zh: {
301
+ title: "\u63D2\u4EF6\u66F4\u65B0\u7BA1\u7406",
302
+ language: "\u4ECB\u7ECD\u8BED\u8A00 / Introduction language",
303
+ community: "\u9879\u76EE\u4EA4\u6D41\u4E0E\u652F\u6301",
304
+ qq: "\u52A0\u5165 QQ \u4EA4\u6D41\u7FA4 \u2197",
305
+ group: "AndroidSix \u7684\u5F00\u6E90\u9879\u76EE\u4EA4\u6D41\u7FA4 \xB7 \u7FA4\u53F7",
306
+ repository: "GitHub \u4ED3\u5E93 \u2197",
307
+ star: "\u89C9\u5F97\u597D\u7528\uFF1F\u70B9\u4E2A Star \u9F13\u52B1\u4E00\u4E0B",
308
+ tagline: "\u53EA\u7BA1\u5DF2\u5B89\u88C5\u63D2\u4EF6\uFF0C\u8BA9\u6BCF\u4E00\u6B21\u66F4\u65B0\u6709\u636E\u53EF\u67E5\u3002",
309
+ principles: [
310
+ ["\u6765\u6E90\u4E0D\u4E71\u6362", "npm \u9891\u9053\u4E0E GitHub \u63D0\u4EA4\u5206\u522B\u68C0\u67E5\uFF0C\u4FDD\u7559\u542F\u7528\u72B6\u6001\u3002"],
311
+ ["\u66F4\u65B0\u5148\u786E\u8BA4", "\u6BCF\u5929\u68C0\u67E5\u3001\u786E\u8BA4\u540E\u5B89\u88C5\uFF1B\u53EF\u5FFD\u7565\u6307\u5B9A\u7248\u672C\u63D0\u9192\uFF0C\u65B0\u7248\u672C\u4ECD\u4F1A\u63D0\u9192\u3002"],
312
+ ["\u7ED3\u679C\u8981\u6838\u9A8C", "\u6838\u5BF9\u5B9E\u9645\u7248\u672C\u6216\u9501\u6587\u4EF6\u63D0\u4EA4\uFF0C\u5B89\u88C5\u4E0E\u751F\u6548\u5206\u5F00\u8BF4\u660E\u3002"]
313
+ ],
314
+ boundary: "\u4E0D\u662F\u63D2\u4EF6\u5E02\u573A\uFF1A\u4E0D\u63D0\u4F9B\u53D1\u73B0\u3001\u63A8\u8350\u3001\u6392\u884C\u6216\u65B0\u63D2\u4EF6\u5B89\u88C5\u3002\u5B89\u88C5\u3001\u5378\u8F7D\u548C\u811A\u672C\u6388\u6743\u4ECD\u5728 DSH \u539F\u751F\u63D2\u4EF6\u9875\u5904\u7406\u3002"
315
+ },
316
+ en: {
317
+ title: "Plugin Update Manager",
318
+ language: "\u4ECB\u7ECD\u8BED\u8A00 / Introduction language",
319
+ community: "Community and support",
320
+ qq: "Join the QQ group \u2197",
321
+ group: "AndroidSix community \xB7 QQ group",
322
+ repository: "GitHub repository \u2197",
323
+ star: "Find it useful? Give us a Star!",
324
+ tagline: "Only installed plugins. Every update checked and accounted for.",
325
+ principles: [
326
+ ["Keep the source", "Check npm channels and GitHub commits separately. Preserve enabled and disabled states."],
327
+ ["You decide", "Check daily, install only after confirmation. Ignore a specific release; newer releases still notify you."],
328
+ ["Verify the result", "Verify the installed version or locked commit. Distinguish installation from activation."]
329
+ ],
330
+ boundary: "Not a plugin marketplace: no discovery, recommendations, rankings, or new plugin installs. Use the native DSH plugin page for installation, removal, and build-script approval."
331
+ }
332
+ };
333
+ function UpdatesIntro({ initialLanguage = "zh" }) {
334
+ const [language, setLanguage] = (0, import_react2.useState)(initialLanguage === "en" ? "en" : "zh");
335
+ const text = descriptions[language];
336
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "box stack", lang: language === "zh" ? "zh-CN" : "en", children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bar", children: [
338
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "row", children: [
339
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: text.title }),
340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "notice", children: [
341
+ "v",
342
+ version
343
+ ] })
344
+ ] }),
345
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "filter", children: [
346
+ text.language,
347
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { value: language, onChange: (e) => setLanguage(e.target.value), children: [
348
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "zh", children: "\u4E2D\u6587" }),
349
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "en", children: "English" })
350
+ ] })
351
+ ] })
352
+ ] }),
353
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "row community", "aria-label": text.community, children: [
354
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: "https://qm.qq.com/q/eQC7OQXQe4", target: "_blank", rel: "noopener noreferrer", children: text.qq }),
355
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "muted", children: [
356
+ text.group,
357
+ " ",
358
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "1124103038" })
359
+ ] }),
360
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: repository.url.replace(/^git\+/, "").replace(/\.git$/, ""), target: "_blank", rel: "noopener noreferrer", children: text.repository }),
361
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "muted", children: text.star })
362
+ ] }),
363
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: text.tagline }),
364
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "principles", children: text.principles.map(([title, detail]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
365
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: title }),
366
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "notice", children: detail })
367
+ ] }, title)) }),
368
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "notice", children: text.boundary })
369
+ ] });
370
+ }
371
+
372
+ // src/client.jsx
373
+ var import_jsx_runtime3 = require("react/jsx-runtime");
278
374
  var name2 = "dsh-plugin-updates-client";
279
375
  var inject = ["slots"];
280
376
  var ENDPOINT = "/.well-known/dsh-plugin-updates";
377
+ var updateListeners = /* @__PURE__ */ new Set();
281
378
  var openUpdates;
282
379
  function apply(ctx) {
283
380
  openUpdates = () => ctx.get("pluginNavigation")?.openBundle(name);
@@ -296,9 +393,9 @@ async function readResponse(response) {
296
393
  return value;
297
394
  }
298
395
  function useUpdates() {
299
- const [data, setData] = (0, import_react2.useState)(null), [error, setError] = (0, import_react2.useState)(""), [busy, setBusy] = (0, import_react2.useState)(false);
396
+ const [data, setData] = (0, import_react3.useState)(null), [error, setError] = (0, import_react3.useState)(""), [busy, setBusy] = (0, import_react3.useState)(false);
300
397
  const running = data?.pluginUpdates.job?.state === "running";
301
- (0, import_react2.useEffect)(() => {
398
+ (0, import_react3.useEffect)(() => {
302
399
  let live = true, pending = false;
303
400
  const abort = new AbortController();
304
401
  async function poll() {
@@ -318,11 +415,19 @@ function useUpdates() {
318
415
  }
319
416
  void poll();
320
417
  const timer = setInterval(poll, running ? 2e3 : 6e4);
418
+ const sync = (value) => {
419
+ if (live) {
420
+ setData(value);
421
+ setError("");
422
+ }
423
+ };
424
+ updateListeners.add(sync);
321
425
  document.addEventListener("visibilitychange", poll);
322
426
  return () => {
323
427
  live = false;
324
428
  abort.abort();
325
429
  clearInterval(timer);
430
+ updateListeners.delete(sync);
326
431
  document.removeEventListener("visibilitychange", poll);
327
432
  };
328
433
  }, [running]);
@@ -338,6 +443,7 @@ function useUpdates() {
338
443
  signal: AbortSignal.timeout(15e3)
339
444
  }));
340
445
  setData(value);
446
+ for (const listener of updateListeners) listener(value);
341
447
  return value;
342
448
  } catch (e) {
343
449
  setError(e.message);
@@ -348,59 +454,39 @@ function useUpdates() {
348
454
  }
349
455
  return { data, error, busy, act };
350
456
  }
351
- function UpdatesIntro() {
352
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("header", { className: "box stack", children: [
353
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bar", children: [
354
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { children: "\u63D2\u4EF6\u66F4\u65B0\u7BA1\u7406" }),
355
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "notice", children: [
356
- "v",
357
- version
358
- ] })
359
- ] }),
360
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "row community", "aria-label": "\u9879\u76EE\u4EA4\u6D41\u4E0E\u652F\u6301", children: [
361
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: "https://qm.qq.com/q/eQC7OQXQe4", target: "_blank", rel: "noopener noreferrer", children: "\u52A0\u5165 QQ \u4EA4\u6D41\u7FA4 \u2197" }),
362
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "muted", children: [
363
- "AndroidSix \u7684\u5F00\u6E90\u9879\u76EE\u4EA4\u6D41\u7FA4 \xB7 \u7FA4\u53F7 ",
364
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "1124103038" })
365
- ] }),
366
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("a", { href: repository.url.replace(/\.git$/, ""), target: "_blank", rel: "noopener noreferrer", children: "GitHub \u4ED3\u5E93 \u2197" }),
367
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "muted", children: "\u89C9\u5F97\u597D\u7528\uFF1F\u70B9\u4E2A Star \u9F13\u52B1\u4E00\u4E0B" })
368
- ] }),
369
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "\u53EA\u7BA1\u5DF2\u5B89\u88C5\u63D2\u4EF6\uFF0C\u8BA9\u6BCF\u4E00\u6B21\u66F4\u65B0\u6709\u636E\u53EF\u67E5\u3002" }),
370
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "principles", children: [
371
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
372
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u6765\u6E90\u4E0D\u4E71\u6362" }),
373
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "notice", children: "npm \u9891\u9053\u4E0E GitHub \u63D0\u4EA4\u5206\u522B\u68C0\u67E5\uFF0C\u4FDD\u7559\u542F\u7528\u72B6\u6001\u3002" })
374
- ] }),
375
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
376
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u66F4\u65B0\u5148\u786E\u8BA4" }),
377
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "notice", children: "\u6BCF\u5929\u68C0\u67E5\u3001\u540C\u7248\u63D0\u9192\u4E00\u6B21\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u5B89\u88C5\u3002" })
378
- ] }),
379
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { children: [
380
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: "\u7ED3\u679C\u8981\u6838\u9A8C" }),
381
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "notice", children: "\u6838\u5BF9\u5B9E\u9645\u7248\u672C\u6216\u9501\u6587\u4EF6\u63D0\u4EA4\uFF0C\u5B89\u88C5\u4E0E\u751F\u6548\u5206\u5F00\u8BF4\u660E\u3002" })
382
- ] })
383
- ] }),
384
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "notice", children: "\u4E0D\u662F\u63D2\u4EF6\u5E02\u573A\uFF1A\u4E0D\u63D0\u4F9B\u53D1\u73B0\u3001\u63A8\u8350\u3001\u6392\u884C\u6216\u65B0\u63D2\u4EF6\u5B89\u88C5\u3002\u5B89\u88C5\u3001\u5378\u8F7D\u548C\u811A\u672C\u6388\u6743\u4ECD\u5728 DSH \u539F\u751F\u63D2\u4EF6\u9875\u5904\u7406\u3002" })
385
- ] });
386
- }
387
457
  function UpdatesPage() {
388
458
  const { data, error, busy, act } = useUpdates();
389
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("main", { className: "pu stack", children: [
390
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: styles }),
391
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(UpdatesIntro, {}),
392
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "error", role: "alert", children: error }),
393
- data ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PluginUpdatesPanel, { data: data.pluginUpdates, busy, act }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "status", children: "\u6B63\u5728\u8FDE\u63A5\u66F4\u65B0\u7BA1\u7406\u540E\u53F0\u2026" })
459
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("main", { className: "pu stack", children: [
460
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: styles }),
461
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(UpdatesIntro, {}),
462
+ error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "error", role: "alert", children: error }),
463
+ data ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PluginUpdatesPanel, { data: data.pluginUpdates, busy, act }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { role: "status", children: "\u6B63\u5728\u8FDE\u63A5\u66F4\u65B0\u7BA1\u7406\u540E\u53F0\u2026" })
394
464
  ] });
395
465
  }
396
466
  function UpdateReminder() {
397
467
  const { data, busy, act } = useUpdates();
398
- const [opened, setOpened] = (0, import_react2.useState)(null), [error, setError] = (0, import_react2.useState)("");
468
+ const [opened, setOpened] = (0, import_react3.useState)(null), [error, setError] = (0, import_react3.useState)("");
399
469
  const reminders = data?.pluginUpdates.reminders;
400
- (0, import_react2.useEffect)(() => {
401
- if (reminders?.length && !document.querySelector("dialog[open]")) setOpened((current) => current ?? reminders);
402
- if (!reminders?.length) setOpened(null);
470
+ (0, import_react3.useEffect)(() => {
471
+ setOpened((current) => {
472
+ if (!reminders?.length) return null;
473
+ if (!current?.length) return document.querySelector("dialog[open]") ? null : reminders;
474
+ const remaining = current.filter((row) => reminders.some((next) => next.key === row.key));
475
+ return remaining.length ? remaining : null;
476
+ });
403
477
  }, [reminders]);
478
+ async function ignore(rows) {
479
+ const result = await act({ action: "plugins.ignore", keys: rows.map((row) => row.targetKey) });
480
+ if (!result) {
481
+ setError("\u5FFD\u7565\u7248\u672C\u672A\u4FDD\u5B58\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
482
+ return;
483
+ }
484
+ setError("");
485
+ setOpened((current) => {
486
+ const remaining = current?.filter((row) => !rows.some((ignored) => ignored.key === row.key));
487
+ return remaining?.length ? remaining : null;
488
+ });
489
+ }
404
490
  async function dismiss(open = false) {
405
491
  const result = await act({ action: "plugins.dismiss", keys: opened.map((row) => row.key) });
406
492
  if (!result) {
@@ -410,30 +496,38 @@ function UpdateReminder() {
410
496
  setOpened(null);
411
497
  if (open) openUpdates?.();
412
498
  }
413
- if (!opened || !data) return null;
414
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "pu", children: [
415
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: styles }),
416
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Modal, { title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\u6709\u66F4\u65B0", busy, close: () => {
499
+ if (!opened?.length || !data) return null;
500
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "pu", children: [
501
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: styles }),
502
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Modal, { title: "\u5DF2\u5B89\u88C5\u63D2\u4EF6\u6709\u66F4\u65B0", busy, close: () => {
417
503
  void dismiss();
418
- }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "modal-body stack", children: [
419
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
504
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "modal-body stack", children: [
505
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
420
506
  opened.length,
421
507
  " \u4E2A\u63D2\u4EF6\u53EF\u66F4\u65B0\u3002\u4E0D\u4F1A\u81EA\u52A8\u5B89\u88C5\uFF0C\u8BF7\u5148\u67E5\u770B\u76EE\u6807\u7248\u672C\u3002"
422
508
  ] }),
423
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "error", children: error }),
424
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { children: opened.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { children: [
425
- row.name,
426
- " \xB7 ",
427
- row.version || "\u672A\u77E5\u7248\u672C",
428
- " \u2192 ",
429
- row.latestVersion || row.latestCommit?.slice(0, 8)
430
- ] }, row.key)) }),
431
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "notice", children: "\u540C\u4E00\u66F4\u65B0\u5904\u7406\u540E\u4E0D\u91CD\u590D\u63D0\u9192\uFF1B\u66F4\u9AD8\u7248\u672C\u6216\u65B0\u63D0\u4EA4\u4F1A\u518D\u6B21\u63D0\u9192\u3002" }),
432
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "row", children: [
433
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "primary", disabled: busy, onClick: () => {
509
+ error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { role: "alert", className: "error", children: error }),
510
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "reminder-list", children: opened.map((row) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bar", children: [
511
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mono", children: [
512
+ row.name,
513
+ " \xB7 ",
514
+ row.version || "\u672A\u77E5\u7248\u672C",
515
+ " \u2192 ",
516
+ row.latestCommit?.slice(0, 8) || row.latestVersion
517
+ ] }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { disabled: busy, "aria-label": `\u5FFD\u7565 ${row.name} \u6B64\u7248\u672C\u63D0\u9192`, onClick: () => {
519
+ void ignore([row]);
520
+ }, children: "\u5FFD\u7565\u6B64\u7248\u672C" })
521
+ ] }) }, row.key)) }),
522
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "notice", children: "\u5FFD\u7565\u4EC5\u5C4F\u853D\u5BF9\u5E94\u7248\u672C / \u63D0\u4EA4\u7684\u63D0\u9192\uFF0C\u91CD\u542F\u540E\u4FDD\u7559\uFF1B\u65B0\u7248\u672C\u4ECD\u63D0\u9192\u3002\u5728\u66F4\u65B0\u6E05\u5355\u53EF\u6062\u590D\u63D0\u9192\u6216\u624B\u52A8\u66F4\u65B0\u3002" }),
523
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "row", children: [
524
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { className: "primary", disabled: busy, onClick: () => {
434
525
  void dismiss(true);
435
526
  }, children: "\u67E5\u770B\u66F4\u65B0\u7BA1\u7406" }),
436
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { disabled: busy, onClick: () => {
527
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { disabled: busy, onClick: () => {
528
+ void ignore(opened);
529
+ }, children: "\u5FFD\u7565\u4EE5\u4E0A\u7248\u672C" }),
530
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { disabled: busy, onClick: () => {
437
531
  void dismiss();
438
532
  }, children: "\u7A0D\u540E\u5904\u7406" })
439
533
  ] })