@morehao/dsh-github-trending 0.6.1 → 0.7.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/client.js CHANGED
@@ -294,6 +294,10 @@ var DICT = {
294
294
  "overview.live.note": "核心一次完成抓取、总结、渲染与提交,结束前没有分段进度;这里只显示已用时间和粗阶段。",
295
295
  "overview.live.started": "开始于",
296
296
  "run.dryRunShort": "试跑",
297
+ "run.force": "重新生成",
298
+ "run.force.hint": "忽略摘要缓存,强制重新抓取与总结",
299
+ "run.force.noChange": "内容无变化,未新增提交",
300
+ "run.forced.tag": "强制重跑",
297
301
  "run.usage.commit": "提交",
298
302
  "runs.count": "最近 {count} 次,超出即裁剪",
299
303
  "runs.files": "{count} 个文件",
@@ -352,6 +356,7 @@ var DICT = {
352
356
  "digest.select": "选择日报日期",
353
357
  "digest.render": "渲染",
354
358
  "digest.raw": "原文",
359
+ "digest.regenerate": "重新生成这一期",
355
360
  "digest.mode": "预览方式",
356
361
  "digest.copyPath": "复制路径",
357
362
  "digest.copied": "已复制",
@@ -449,7 +454,7 @@ var DICT = {
449
454
  "releases.budget.auto": "自动",
450
455
  "releases.budget.fixed": "指定",
451
456
  "subs.title": "订阅",
452
- "subs.desc": "输入 owner/repo 立刻知道订没订;每行带最近版本、最近总结与状态,点「变更」看每周记录。",
457
+ "subs.desc": "管理发版订阅仓库,查看每周变更记录。",
453
458
  "subs.select": "选择",
454
459
  "subs.select.done": "完成",
455
460
  "subs.cmd.placeholder": "搜索 owner/repo,或输入完整仓库名直接添加",
@@ -476,8 +481,18 @@ var DICT = {
476
481
  "subs.removed.many": "已移除 {count} 个仓库,下次运行随产物提交。",
477
482
  "subs.remove": "移除",
478
483
  "subs.removing": "移除中…",
479
- "subs.row.changes": "变更",
484
+ "subs.row.changes": "查看变更",
480
485
  "subs.row.changesRepo": "查看 {repo} 的变更",
486
+ "subs.row.trigger": "触发分析",
487
+ "subs.row.triggerRepo": "只对 {repo} 生成变更分析",
488
+ "subs.search.placeholder": "搜索仓库",
489
+ "subs.search.label": "搜索仓库",
490
+ "subs.toolbar.add": "+ 添加仓库",
491
+ "subs.toolbar.analyze": "生成变更分析",
492
+ "subs.analyzing": "分析中…",
493
+ "subs.analyze.done": "变更分析已生成 · {count} 个仓库有更新",
494
+ "subs.analyze.doneOne": "{repo} 已更新",
495
+ "subs.analyze.failed": "分析失败:{message}",
481
496
  "subs.row.removeRepo": "移除 {repo}",
482
497
  "subs.col.repo": "仓库",
483
498
  "subs.col.tag": "最近版本",
@@ -801,6 +816,10 @@ var DICT = {
801
816
  "overview.live.note": "The core fetches, summarizes, renders and commits in one run; there is no per-step progress before it finishes. Only elapsed time and a coarse phase are shown here.",
802
817
  "overview.live.started": "Started",
803
818
  "run.dryRunShort": "Dry run",
819
+ "run.force": "Regenerate",
820
+ "run.force.hint": "Ignore the summary cache and re-fetch and re-summarize",
821
+ "run.force.noChange": "No content change; no new commit",
822
+ "run.forced.tag": "Forced",
804
823
  "run.usage.commit": "Commit",
805
824
  "runs.count": "Last {count} runs; older ones are trimmed",
806
825
  "runs.files": "{count} files",
@@ -859,6 +878,7 @@ var DICT = {
859
878
  "digest.select": "Choose a digest date",
860
879
  "digest.render": "Rendered",
861
880
  "digest.raw": "Raw",
881
+ "digest.regenerate": "Regenerate this period",
862
882
  "digest.mode": "Preview mode",
863
883
  "digest.copyPath": "Copy path",
864
884
  "digest.copied": "Copied",
@@ -956,7 +976,7 @@ var DICT = {
956
976
  "releases.budget.auto": "Auto",
957
977
  "releases.budget.fixed": "Fixed",
958
978
  "subs.title": "Subscriptions",
959
- "subs.desc": "Type owner/repo to see instantly whether it is subscribed; each row carries its latest version, latest weekly summary and status, and “Changes” opens the weekly record.",
979
+ "subs.desc": "Manage release-subscription repos and review weekly change records.",
960
980
  "subs.select": "Select",
961
981
  "subs.select.done": "Done",
962
982
  "subs.cmd.placeholder": "Search owner/repo, or type a full repo name to add it",
@@ -983,8 +1003,18 @@ var DICT = {
983
1003
  "subs.removed.many": "Removed {count} repos; committed with artifacts on the next run.",
984
1004
  "subs.remove": "Remove",
985
1005
  "subs.removing": "Removing…",
986
- "subs.row.changes": "Changes",
1006
+ "subs.row.changes": "View changes",
987
1007
  "subs.row.changesRepo": "Show changes for {repo}",
1008
+ "subs.row.trigger": "Analyze",
1009
+ "subs.row.triggerRepo": "Generate change analysis for {repo} only",
1010
+ "subs.search.placeholder": "Search repositories",
1011
+ "subs.search.label": "Search repositories",
1012
+ "subs.toolbar.add": "+ Add repository",
1013
+ "subs.toolbar.analyze": "Generate change analysis",
1014
+ "subs.analyzing": "Analyzing…",
1015
+ "subs.analyze.done": "Change analysis ready · {count} repos updated",
1016
+ "subs.analyze.doneOne": "{repo} updated",
1017
+ "subs.analyze.failed": "Analysis failed: {message}",
988
1018
  "subs.row.removeRepo": "Remove {repo}",
989
1019
  "subs.col.repo": "Repo",
990
1020
  "subs.col.tag": "Latest version",
@@ -1424,6 +1454,24 @@ var CSS = `
1424
1454
 
1425
1455
  /* ——— 订阅:命令栏 + 四列表格 + 选择条 + 发版记录抽屉 ——— */
1426
1456
  .dshgt-subs{gap:12px;padding-bottom:10px;}
1457
+ /* 工具条:搜索 + 添加 + 选择 + 生成变更分析;列表在上方操作区之下 */
1458
+ .dshgt-subs-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding-bottom:12px;border-bottom:1px solid color-mix(in srgb,var(--gt-border) 70%,transparent);}
1459
+ .dshgt-subs-search{position:relative;flex:1;min-width:180px;max-width:340px;display:flex;align-items:center;}
1460
+ .dshgt-subs-search svg{position:absolute;left:9px;color:var(--gt-muted);pointer-events:none;}
1461
+ .dshgt-subs-search input{width:100%;height:32px;box-sizing:border-box;padding:0 10px 0 30px;border:1px solid var(--gt-border);border-radius:8px;background:var(--gt-bg);color:var(--gt-fg);font:inherit;font-size:13px;}
1462
+ .dshgt-subs-search input:focus{outline:2px solid color-mix(in srgb,var(--gt-accent) 30%,transparent);outline-offset:1px;border-color:color-mix(in srgb,var(--gt-accent) 45%,var(--gt-border));}
1463
+ .dshgt-subs-search input::placeholder{color:var(--gt-muted);}
1464
+ .dshgt-subs-toolbar-spacer{flex:1 1 auto;min-width:0;}
1465
+ .dshgt-subs-addform{display:flex;align-items:center;gap:6px;}
1466
+ .dshgt-subs-addform input{height:32px;box-sizing:border-box;width:200px;padding:0 9px;border:1px solid var(--gt-border);border-radius:8px;background:var(--gt-bg);color:var(--gt-fg);font:inherit;font-size:12.5px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;}
1467
+ .dshgt-subs-addform input:focus{outline:2px solid color-mix(in srgb,var(--gt-accent) 30%,transparent);outline-offset:1px;}
1468
+ .dshgt-subs-act.gen{color:var(--gt-muted);}
1469
+ .dshgt-subs-act.gen:hover{color:var(--gt-accent);background:color-mix(in srgb,var(--gt-accent) 10%,transparent);}
1470
+ .dshgt-subs-act.gen:disabled{opacity:.5;cursor:not-allowed;}
1471
+ .dshgt-subs-rowbusy{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--gt-accent);padding:3px 4px;white-space:nowrap;}
1472
+ .dshgt-spin{width:12px;height:12px;border-radius:50%;border:2px solid color-mix(in srgb,var(--gt-accent) 30%,transparent);border-top-color:var(--gt-accent);animation:dshgt-spin .7s linear infinite;display:inline-block;flex:none;}
1473
+ @keyframes dshgt-spin{to{transform:rotate(360deg);}}
1474
+ .dshgt-subs-row.is-analyzing{background:color-mix(in srgb,var(--gt-accent) 6%,var(--gt-bg));}
1427
1475
  .dshgt-subs-head{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;}
1428
1476
  .dshgt-subs-head-main{min-width:0;}
1429
1477
  .dshgt-subs-head h2{margin:0;font-size:17px;font-weight:650;letter-spacing:-.01em;}
@@ -1771,11 +1819,6 @@ function releaseRunsOf(state) {
1771
1819
  const nested = Array.isArray(state.status?.releaseRuns) ? state.status.releaseRuns : void 0;
1772
1820
  return root ?? nested ?? [];
1773
1821
  }
1774
- function lastReleaseAtOf(state) {
1775
- if (state === null || state === void 0) return void 0;
1776
- const value = state.lastReleaseAt ?? state.status?.lastReleaseAt;
1777
- return typeof value === "string" && value !== "" ? value : void 0;
1778
- }
1779
1822
  function getPreview(runId, signal) {
1780
1823
  return apiCall("GET", `/preview?id=${encodeURIComponent(runId)}`, void 0, signal === void 0 ? {} : { signal });
1781
1824
  }
@@ -1823,6 +1866,45 @@ async function postSubscriptions(action, repos) {
1823
1866
  const payload = await apiCall("POST", "/subscriptions", { action, repos });
1824
1867
  return normalizeSubscriptionList(payload);
1825
1868
  }
1869
+ async function postReleasesRun(repo) {
1870
+ const payload = await apiCall("POST", "/releases/run", repo === void 0 ? {} : { repo });
1871
+ return normalizeReleaseRun(payload.release);
1872
+ }
1873
+ function normalizeReleaseRun(raw) {
1874
+ const r = typeof raw === "object" && raw !== null ? raw : {};
1875
+ const str = (v, fallback = "") => typeof v === "string" ? v : fallback;
1876
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
1877
+ const opt = (v) => typeof v === "string" && v !== "" ? v : void 0;
1878
+ const status = r.status === "ok" || r.status === "failed" || r.status === "skipped" || r.status === "cancelled" ? r.status : "failed";
1879
+ const record = {
1880
+ id: str(r.id),
1881
+ startedAt: str(r.startedAt),
1882
+ finishedAt: str(r.finishedAt),
1883
+ status,
1884
+ checked: num(r.checked),
1885
+ changed: num(r.changed),
1886
+ newTags: num(r.newTags),
1887
+ summarized: num(r.summarized),
1888
+ degraded: num(r.degraded),
1889
+ failed: num(r.failed),
1890
+ rateLimited: num(r.rateLimited),
1891
+ durationMs: num(r.durationMs)
1892
+ };
1893
+ const maybe = {
1894
+ tokens: typeof r.tokens === "number" ? r.tokens : void 0,
1895
+ commit: opt(r.commit),
1896
+ prUrl: opt(r.prUrl),
1897
+ branch: opt(r.branch),
1898
+ errorCode: opt(r.errorCode),
1899
+ errorMessage: opt(r.errorMessage),
1900
+ coreVersion: opt(r.coreVersion),
1901
+ scopeRepo: opt(r.scopeRepo)
1902
+ };
1903
+ for (const [k, v] of Object.entries(maybe)) {
1904
+ if (v !== void 0) record[k] = v;
1905
+ }
1906
+ return record;
1907
+ }
1826
1908
  async function getSubscriptionDoc(repo, volume, signal) {
1827
1909
  const query = `?repo=${encodeURIComponent(repo)}${volume === void 0 ? "" : `&volume=${encodeURIComponent(String(volume))}`}`;
1828
1910
  const payload = await apiCall(
@@ -2740,14 +2822,18 @@ function useRunController(store) {
2740
2822
  const [dryRun, setDryRun] = import_react.default.useState(false);
2741
2823
  const [localStartedAt, setLocalStartedAt] = import_react.default.useState(null);
2742
2824
  const run = import_react.default.useCallback(
2743
- async (overrideDry) => {
2825
+ async (overrideDry, options) => {
2744
2826
  const dry = overrideDry ?? dryRun;
2745
2827
  setBusy(true);
2746
2828
  setError(null);
2747
2829
  setSummary(null);
2748
2830
  setLocalStartedAt((/* @__PURE__ */ new Date()).toISOString());
2749
2831
  try {
2750
- const result = await apiCall("POST", "/run", { dryRun: dry });
2832
+ const body = { dryRun: dry };
2833
+ if (options?.force === true) body.force = true;
2834
+ if (options?.date !== void 0 && options.date !== "") body.date = options.date;
2835
+ if (options?.period !== void 0) body.period = options.period;
2836
+ const result = await apiCall("POST", "/run", body);
2751
2837
  setSummary(result.summary);
2752
2838
  await refresh({ quiet: true });
2753
2839
  } catch (failure) {
@@ -2936,6 +3022,9 @@ function digestTarget(record, timeZone) {
2936
3022
  const period = recordPeriod(record);
2937
3023
  return { period, bucket: periodBucket(period, info.date) };
2938
3024
  }
3025
+ function regenTarget(day) {
3026
+ return { period: day.period, date: day.date };
3027
+ }
2939
3028
  function buildPeriodGroups(records, options) {
2940
3029
  const now = options.now ?? /* @__PURE__ */ new Date();
2941
3030
  const today = zonedDateString(now, options.timeZone);
@@ -3325,7 +3414,10 @@ function recordSummary(t, record) {
3325
3414
  if (!record.ok) {
3326
3415
  return [record.errorCode, record.errorMessage].filter((item) => item !== void 0 && item !== "").join(" · ") || t("state.failed");
3327
3416
  }
3328
- if (record.skipped) return skipHint(t, record.skipReason, t("runs.noChange"));
3417
+ if (record.skipped) {
3418
+ if (record.forced === true && record.skipReason === "unchanged") return t("run.force.noChange");
3419
+ return skipHint(t, record.skipReason, t("runs.noChange"));
3420
+ }
3329
3421
  if (record.kind === "dry-run") return t("runs.dryHint");
3330
3422
  if (record.branch !== void 0) return record.branch;
3331
3423
  if (record.commit !== void 0) return record.commit;
@@ -3459,7 +3551,8 @@ function RunsView(props) {
3459
3551
  { className: "dshgt-run-what" },
3460
3552
  record.kind === "dry-run" ? t("state.dry") : record.skipped ? t("state.skipped") : record.ok ? t("state.ok") : t("state.failed"),
3461
3553
  h("span", { className: "dshgt-run-muted" }, recordSummary(t, record)),
3462
- record.legacy === true ? h("span", { className: "dshgt-tag dshgt-inline-tag" }, t("runs.legacy")) : null
3554
+ record.legacy === true ? h("span", { className: "dshgt-tag dshgt-inline-tag" }, t("runs.legacy")) : null,
3555
+ record.forced === true ? h("span", { className: "dshgt-tag dshgt-inline-tag" }, t("run.forced.tag")) : null
3463
3556
  ),
3464
3557
  h("span", { className: "dshgt-run-files" }, t("runs.files", { count: record.files })),
3465
3558
  record.tokens === void 0 ? null : h("span", { className: "dshgt-run-tokens dshgt-mono", title: t("run.usage.tokens") }, `${formatTokens(record.tokens)} tok`),
@@ -4074,6 +4167,7 @@ function EmptyDigest(props) {
4074
4167
  "div",
4075
4168
  { className: "dshgt-actions" },
4076
4169
  h(Btn, { small: true, onClick: props.onOpenRuns }, t("digest.empty.viewRuns")),
4170
+ props.onRegenerate === void 0 ? null : h(Btn, { small: true, onClick: props.onRegenerate, disabled: props.regenDisabled === true }, t("digest.regenerate")),
4077
4171
  nearest === void 0 ? null : h(
4078
4172
  Btn,
4079
4173
  { small: true, onClick: () => props.onSelect({ period: nearest.period, bucket: nearest.bucket }) },
@@ -4083,7 +4177,7 @@ function EmptyDigest(props) {
4083
4177
  );
4084
4178
  }
4085
4179
  function DigestView(props) {
4086
- const { t, lang, store, selected } = props;
4180
+ const { t, lang, store, selected, controller } = props;
4087
4181
  const [mode, setMode] = import_react.default.useState("render");
4088
4182
  const [payload, setPayload] = import_react.default.useState(null);
4089
4183
  const [loading, setLoading] = import_react.default.useState(false);
@@ -4119,20 +4213,20 @@ function DigestView(props) {
4119
4213
  setLoading(false);
4120
4214
  return () => void 0;
4121
4215
  }
4122
- const controller = new AbortController();
4216
+ const controller2 = new AbortController();
4123
4217
  setLoading(true);
4124
4218
  setError(null);
4125
4219
  setPayload(null);
4126
- getPreview(previewRunId, controller.signal).then((result) => {
4127
- if (controller.signal.aborted) return;
4220
+ getPreview(previewRunId, controller2.signal).then((result) => {
4221
+ if (controller2.signal.aborted) return;
4128
4222
  setPayload(result);
4129
4223
  setLoading(false);
4130
4224
  }).catch((failure) => {
4131
- if (controller.signal.aborted) return;
4225
+ if (controller2.signal.aborted) return;
4132
4226
  setError(failure instanceof Error ? failure.message : String(failure));
4133
4227
  setLoading(false);
4134
4228
  });
4135
- return () => controller.abort();
4229
+ return () => controller2.abort();
4136
4230
  }, [previewRunId]);
4137
4231
  if (state === null) {
4138
4232
  return h("div", { className: "dshgt-empty" }, store.error ?? t("quickfill.loading"));
@@ -4253,6 +4347,12 @@ function DigestView(props) {
4253
4347
  }
4254
4348
  const showEmpty = day.status !== "preview" || error !== null;
4255
4349
  const record = day.record;
4350
+ const regenerate = () => {
4351
+ const target = regenTarget(day);
4352
+ props.onSelect({ period: target.period, bucket: day.bucket });
4353
+ void controller.run(false, { force: true, date: target.date, period: target.period });
4354
+ };
4355
+ const regenerateDisabled = controller.running || controller.busy;
4256
4356
  return h(
4257
4357
  "div",
4258
4358
  { className: "dshgt-view dshgt-digest" },
@@ -4268,7 +4368,9 @@ function DigestView(props) {
4268
4368
  ...nearest === void 0 ? {} : { nearest },
4269
4369
  ...error === null ? {} : { error },
4270
4370
  onOpenRuns: props.onOpenRuns,
4271
- onSelect: props.onSelect
4371
+ onSelect: props.onSelect,
4372
+ onRegenerate: regenerate,
4373
+ regenDisabled: regenerateDisabled
4272
4374
  }) : h(
4273
4375
  "div",
4274
4376
  { className: "dshgt-digest-body" },
@@ -4284,6 +4386,7 @@ function DigestView(props) {
4284
4386
  h("span", { className: "dshgt-dim" }, record.preview === void 0 ? "—" : formatBytes(record.preview.bytes)),
4285
4387
  payload?.preview.truncated === true ? h("span", { className: "dshgt-tag" }, t("digest.truncated")) : null,
4286
4388
  h("span", { className: "dshgt-grow" }),
4389
+ h(Btn, { small: true, onClick: regenerate, disabled: regenerateDisabled }, t("digest.regenerate")),
4287
4390
  h(
4288
4391
  "div",
4289
4392
  { className: "dshgt-seg", role: "group", "aria-label": t("digest.mode") },
@@ -4372,12 +4475,6 @@ function monthDay(value) {
4372
4475
  if (value === void 0 || value === "") return "—";
4373
4476
  return DATE_RE2.test(value) ? value.slice(5) : value;
4374
4477
  }
4375
- function clock(value) {
4376
- return /^\d{2}:\d{2}/.test(value) ? value.slice(0, 5) : value;
4377
- }
4378
- function weekdayLabel(t, weekday) {
4379
- return weekday >= 1 && weekday <= 7 ? t(`weekday.${weekday}`) : String(weekday);
4380
- }
4381
4478
  function toneOf(status) {
4382
4479
  if (status === "ok") return "ok";
4383
4480
  if (status === "rate_limited") return "warn";
@@ -4435,47 +4532,6 @@ function highlight(text, needle) {
4435
4532
  if (index < text.length) parts.push(text.slice(index));
4436
4533
  return parts;
4437
4534
  }
4438
- function weekStart(date) {
4439
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
4440
- if (match === null) return date;
4441
- const value = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
4442
- const dayIndex = (value.getUTCDay() + 6) % 7;
4443
- value.setUTCDate(value.getUTCDate() - dayIndex);
4444
- return value.toISOString().slice(0, 10);
4445
- }
4446
- function metaOf(t, store) {
4447
- const state = store?.state ?? null;
4448
- const schedule = state?.schedule;
4449
- const releaseSchedule = state?.settings?.releases?.schedule;
4450
- const weekday = typeof schedule?.releaseWeekday === "number" ? schedule.releaseWeekday : releaseSchedule?.weekday;
4451
- const time = typeof schedule?.releaseTime === "string" && schedule.releaseTime !== "" ? schedule.releaseTime : releaseSchedule?.time;
4452
- const timezone = (typeof schedule?.releaseTimezone === "string" && schedule.releaseTimezone !== "" ? schedule.releaseTimezone : releaseSchedule?.timezone) ?? schedule?.timezone ?? "Asia/Shanghai";
4453
- const cadence = weekday === void 0 || time === void 0 || time === "" ? t("subs.meta.cadence") : t("subs.meta.cadence.full", { weekday: weekdayLabel(t, weekday), time: clock(time) });
4454
- const nextAt = typeof schedule?.nextReleaseAt === "string" && schedule.nextReleaseAt !== "" ? schedule.nextReleaseAt : void 0;
4455
- const next = nextAt === void 0 ? void 0 : shortTime(nextAt);
4456
- const runs = releaseRunsOf(state);
4457
- const newest = runs.length === 0 ? void 0 : runs.reduce((a, b) => Date.parse(b.finishedAt || b.startedAt) > Date.parse(a.finishedAt || a.startedAt) ? b : a);
4458
- const lastCheckAt = newest?.finishedAt ?? lastReleaseAtOf(state);
4459
- const lastCheck = typeof lastCheckAt === "string" && lastCheckAt !== "" ? shortTime(lastCheckAt) : void 0;
4460
- let lastWeek;
4461
- if (newest !== void 0) {
4462
- const today = zonedDateString(/* @__PURE__ */ new Date(), timezone);
4463
- const currentFrom = weekStart(today);
4464
- const lastFrom = addDays(currentFrom, -7);
4465
- const at = newest.finishedAt !== "" ? newest.finishedAt : newest.startedAt;
4466
- const runDate = zonedDateString(new Date(at), timezone);
4467
- if (runDate >= lastFrom && runDate <= addDays(lastFrom, 6)) {
4468
- lastWeek = t("subs.meta.lastWeek", { from: monthDay(lastFrom), to: monthDay(addDays(lastFrom, 6)), changed: newest.changed });
4469
- } else if (runDate >= currentFrom && runDate <= addDays(currentFrom, 6)) {
4470
- lastWeek = t("subs.meta.thisWeek", { from: monthDay(currentFrom), to: monthDay(addDays(currentFrom, 6)), changed: newest.changed });
4471
- }
4472
- }
4473
- const info = { cadence };
4474
- if (next !== void 0) info.next = next;
4475
- if (lastWeek !== void 0) info.lastWeek = lastWeek;
4476
- if (lastCheck !== void 0) info.lastCheck = lastCheck;
4477
- return info;
4478
- }
4479
4535
  function isMissingDoc(code, message) {
4480
4536
  if (code !== void 0 && /NOT_?FOUND|MISSING|NO_?DOC/i.test(code)) return true;
4481
4537
  return /not\s*found|no such file|enoent|404|不存在|no release document|has no release|无发版记录/i.test(message);
@@ -4630,7 +4686,10 @@ function SubscriptionsView(props) {
4630
4686
  const [selected, setSelected] = import_react.default.useState([]);
4631
4687
  const [confirming, setConfirming] = import_react.default.useState(null);
4632
4688
  const [openRepo, setOpenRepo] = import_react.default.useState(null);
4633
- const [focus, setFocus] = import_react.default.useState(-1);
4689
+ const [addOpen, setAddOpen] = import_react.default.useState(false);
4690
+ const [addValue, setAddValue] = import_react.default.useState("");
4691
+ const [analyzing, setAnalyzing] = import_react.default.useState(null);
4692
+ const [result, setResult] = import_react.default.useState(null);
4634
4693
  const load = import_react.default.useCallback((signal) => {
4635
4694
  getSubscriptions(signal).then((next) => {
4636
4695
  if (signal?.aborted === true) return;
@@ -4647,23 +4706,10 @@ function SubscriptionsView(props) {
4647
4706
  load(controller.signal);
4648
4707
  return () => controller.abort();
4649
4708
  }, [load]);
4650
- const inputRef = import_react.default.useRef(null);
4651
- import_react.default.useEffect(() => {
4652
- const onKey = (event) => {
4653
- if (event.key.toLowerCase() !== "k" || !(event.metaKey || event.ctrlKey)) return;
4654
- event.preventDefault();
4655
- inputRef.current?.focus?.();
4656
- };
4657
- document.addEventListener("keydown", onKey);
4658
- return () => document.removeEventListener("keydown", onKey);
4659
- }, []);
4660
4709
  const rows = mergeRows(list);
4661
4710
  const needle = query.trim().toLowerCase();
4662
4711
  const matched = needle === "" ? rows : rows.filter((row) => row.name.toLowerCase().includes(needle));
4663
4712
  const sorted = sortRows(matched, sort);
4664
- const candidate = needle !== "" && REPO_PATTERN.test(query.trim()) ? query.trim() : void 0;
4665
- const subscribed = candidate !== void 0 && rows.some((row) => row.name.toLowerCase() === candidate.toLowerCase());
4666
- const activeFocus = focus >= 0 && focus < sorted.length ? focus : -1;
4667
4713
  const toggleSelect = (repo) => {
4668
4714
  setSelected((current) => current.includes(repo) ? current.filter((item) => item !== repo) : [...current, repo]);
4669
4715
  };
@@ -4673,8 +4719,8 @@ function SubscriptionsView(props) {
4673
4719
  setNote(null);
4674
4720
  postSubscriptions("add", [repo]).then((next) => {
4675
4721
  setList(next);
4676
- setQuery("");
4677
- setFocus(-1);
4722
+ setAddValue("");
4723
+ setAddOpen(false);
4678
4724
  setNote(t("subs.add.queued", { repo }));
4679
4725
  }).catch((failure) => setError(errorText(failure))).finally(() => setBusy(null));
4680
4726
  };
@@ -4703,6 +4749,22 @@ function SubscriptionsView(props) {
4703
4749
  setConfirming(null);
4704
4750
  }).finally(() => setBusy(null));
4705
4751
  };
4752
+ const runAnalyze = (repo) => {
4753
+ if (analyzing !== null) return;
4754
+ setAnalyzing(repo === void 0 ? { scope: "all" } : { scope: "one", repo });
4755
+ setError(null);
4756
+ setResult(null);
4757
+ postReleasesRun(repo).then((record) => {
4758
+ setResult({ scope: repo === void 0 ? "all" : "one", ...repo === void 0 ? {} : { repo }, ok: record.status !== "failed", changed: record.changed });
4759
+ if (record.status === "failed" && record.errorMessage !== void 0) {
4760
+ setError(t("subs.analyze.failed", { message: record.errorMessage }));
4761
+ }
4762
+ load();
4763
+ }).catch((failure) => {
4764
+ setError(errorText(failure));
4765
+ setResult({ scope: repo === void 0 ? "all" : "one", ...repo === void 0 ? {} : { repo }, ok: false, changed: 0 });
4766
+ }).finally(() => setAnalyzing(null));
4767
+ };
4706
4768
  const onSort = (key) => {
4707
4769
  setSort((current) => current.key === key ? { key, dir: current.dir === 1 ? -1 : 1 } : { key, dir: 1 });
4708
4770
  };
@@ -4723,48 +4785,19 @@ function SubscriptionsView(props) {
4723
4785
  active ? h("span", { className: "dshgt-subs-ord" }, sort.dir === 1 ? "▲" : "▼") : null
4724
4786
  );
4725
4787
  };
4726
- const onCommandKey = (event) => {
4727
- if (event.isComposing === true) return;
4728
- if (event.key === "Escape") {
4729
- setQuery("");
4730
- setFocus(-1);
4731
- if (selecting) {
4732
- setSelecting(false);
4733
- setSelected([]);
4734
- }
4735
- return;
4736
- }
4737
- if (event.key === "Enter") {
4738
- if (candidate !== void 0 && !subscribed) {
4739
- event.preventDefault();
4740
- addRepo(candidate);
4741
- return;
4742
- }
4743
- if (activeFocus >= 0) {
4744
- event.preventDefault();
4745
- setOpenRepo(sorted[activeFocus]?.name ?? null);
4746
- }
4747
- return;
4748
- }
4749
- if (event.key === "ArrowDown") {
4750
- event.preventDefault();
4751
- setFocus(Math.min(sorted.length - 1, focus + 1));
4752
- return;
4753
- }
4754
- if (event.key === "ArrowUp") {
4755
- event.preventDefault();
4756
- setFocus(Math.max(-1, focus - 1));
4757
- }
4758
- };
4759
- const meta = metaOf(t, props.store);
4760
- const metaRight = needle !== "" ? [t("subs.meta.match", { matched: sorted.length, total: rows.length })] : [
4761
- meta.lastCheck === void 0 ? void 0 : t("subs.meta.lastCheck", { time: meta.lastCheck }),
4762
- meta.lastWeek
4763
- ].filter((item) => item !== void 0);
4788
+ const canAdd = REPO_PATTERN.test(addValue.trim());
4789
+ const alreadySubscribed = canAdd && rows.some((row) => row.name.toLowerCase() === addValue.trim().toLowerCase());
4764
4790
  const drawerRow = openRepo === null ? void 0 : rows.find((row) => row.name === openRepo);
4765
- const rowNodes = sorted.map((row, index) => {
4791
+ const globalBusy = analyzing !== null;
4792
+ const banner = result === null ? null : result.ok ? h(
4793
+ Alert,
4794
+ { kind: "ok" },
4795
+ result.scope === "all" ? t("subs.analyze.done", { count: result.changed }) : t("subs.analyze.doneOne", { repo: result.repo ?? "" })
4796
+ ) : null;
4797
+ const rowNodes = sorted.map((row) => {
4766
4798
  const item = row.item;
4767
4799
  const tone = toneOf(item?.status);
4800
+ const rowAnalyzing = analyzing !== null && analyzing.scope === "one" && analyzing.repo === row.name;
4768
4801
  const checkbox = selecting ? h(
4769
4802
  "span",
4770
4803
  { key: "cb", role: "cell", className: "dshgt-subs-cbwrap" },
@@ -4782,7 +4815,7 @@ function SubscriptionsView(props) {
4782
4815
  {
4783
4816
  key: row.name,
4784
4817
  role: "row",
4785
- className: `dshgt-subs-row${index === activeFocus ? " is-focus" : ""}`,
4818
+ className: `dshgt-subs-row${rowAnalyzing ? " is-analyzing" : ""}`,
4786
4819
  onClick: () => {
4787
4820
  if (selecting) toggleSelect(row.name);
4788
4821
  else setOpenRepo(row.name);
@@ -4805,6 +4838,21 @@ function SubscriptionsView(props) {
4805
4838
  h(
4806
4839
  "span",
4807
4840
  { key: "act", role: "cell", className: "dshgt-subs-actions" },
4841
+ rowAnalyzing ? h("span", { className: "dshgt-subs-rowbusy" }, h("span", { className: "dshgt-spin" }), t("subs.analyzing")) : h(
4842
+ "button",
4843
+ {
4844
+ type: "button",
4845
+ className: "dshgt-subs-act gen",
4846
+ "aria-label": t("subs.row.triggerRepo", { repo: row.name }),
4847
+ title: t("subs.row.triggerRepo", { repo: row.name }),
4848
+ disabled: globalBusy || busy !== null,
4849
+ onClick: (event) => {
4850
+ event.stopPropagation();
4851
+ runAnalyze(row.name);
4852
+ }
4853
+ },
4854
+ t("subs.row.trigger")
4855
+ ),
4808
4856
  h(
4809
4857
  "button",
4810
4858
  {
@@ -4840,10 +4888,7 @@ function SubscriptionsView(props) {
4840
4888
  { className: "dshgt-subs-empty" },
4841
4889
  h("b", null, t("subs.empty")),
4842
4890
  h("span", null, t("subs.empty.hint"))
4843
- ) : sorted.length === 0 ? candidate !== void 0 && !subscribed ? (
4844
- // 输入完整 owner/repo 时上方已给出「+ 添加」建议,这里不再叠一句「没有匹配」。
4845
- null
4846
- ) : h(
4891
+ ) : sorted.length === 0 ? h(
4847
4892
  "div",
4848
4893
  { className: "dshgt-subs-empty" },
4849
4894
  h("b", null, t("subs.empty.match")),
@@ -4867,101 +4912,94 @@ function SubscriptionsView(props) {
4867
4912
  return h(
4868
4913
  "div",
4869
4914
  { className: "dshgt-view dshgt-subs" },
4870
- h(
4871
- "section",
4872
- { className: "dshgt-subs-head" },
4873
- h(
4874
- "div",
4875
- { className: "dshgt-subs-head-main" },
4876
- h("h2", null, t("subs.title")),
4877
- h("p", { className: "dshgt-subs-dek" }, t("subs.desc"))
4878
- ),
4879
- h(
4880
- "div",
4881
- { className: "dshgt-subs-head-actions" },
4882
- h(
4883
- Btn,
4884
- {
4885
- small: true,
4886
- onClick: () => {
4887
- if (selecting) setSelected([]);
4888
- setSelecting(!selecting);
4889
- }
4890
- },
4891
- selecting ? t("subs.select.done") : t("subs.select")
4892
- )
4893
- )
4894
- ),
4915
+ // 工具条:搜索 + 添加 + 选择 + 生成变更分析(主操作)
4895
4916
  h(
4896
4917
  "div",
4897
- { className: "dshgt-subs-cmdwrap" },
4918
+ { className: "dshgt-subs-toolbar" },
4898
4919
  h(
4899
4920
  "div",
4900
- { className: "dshgt-subs-cmd" },
4921
+ { className: "dshgt-subs-search" },
4922
+ h(
4923
+ "svg",
4924
+ { width: 15, height: 15, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "aria-hidden": "true" },
4925
+ h("circle", { cx: 11, cy: 11, r: 7 }),
4926
+ h("path", { d: "m20 20-3.5-3.5" })
4927
+ ),
4901
4928
  h("input", {
4902
4929
  type: "text",
4903
- ref: inputRef,
4904
4930
  value: query,
4905
- placeholder: t("subs.cmd.placeholder"),
4906
- "aria-label": t("subs.cmd.label"),
4907
- "aria-keyshortcuts": "Meta+K Control+K",
4931
+ placeholder: t("subs.search.placeholder"),
4932
+ "aria-label": t("subs.search.label"),
4908
4933
  autoComplete: "off",
4909
4934
  spellCheck: false,
4910
- onInput: (event) => {
4911
- setQuery(event.target.value);
4912
- setFocus(-1);
4913
- },
4914
- onKeyDown: onCommandKey
4915
- }),
4916
- h("kbd", { className: "dshgt-subs-kbd" }, "⌘K")
4935
+ onInput: (event) => setQuery(event.target.value)
4936
+ })
4917
4937
  ),
4918
- h(
4938
+ h("div", { className: "dshgt-subs-toolbar-spacer" }),
4939
+ addOpen ? h(
4919
4940
  "div",
4920
- { className: "dshgt-subs-meta" },
4941
+ { className: "dshgt-subs-addform" },
4942
+ h("input", {
4943
+ type: "text",
4944
+ value: addValue,
4945
+ placeholder: t("subs.add.placeholder"),
4946
+ "aria-label": t("subs.add.placeholder"),
4947
+ autoComplete: "off",
4948
+ spellCheck: false,
4949
+ autoFocus: true,
4950
+ onInput: (event) => setAddValue(event.target.value),
4951
+ onKeyDown: (event) => {
4952
+ if (event.isComposing === true) return;
4953
+ if (event.key === "Enter" && canAdd && !alreadySubscribed) addRepo(addValue.trim());
4954
+ if (event.key === "Escape") {
4955
+ setAddOpen(false);
4956
+ setAddValue("");
4957
+ }
4958
+ }
4959
+ }),
4921
4960
  h(
4922
- "span",
4923
- null,
4924
- t("subs.meta.count", { count: rows.length }),
4925
- " · ",
4926
- h(
4927
- "button",
4928
- { type: "button", className: "dshgt-subs-linkbtn", title: t("subs.param.link"), onClick: () => props.onOpenSettings() },
4929
- meta.cadence
4930
- ),
4931
- meta.next === void 0 ? null : ` · ${t("subs.meta.next", { time: meta.next })}`
4932
- ),
4933
- metaRight.length === 0 ? null : h(
4934
- "span",
4935
- { className: "dshgt-subs-meta-right" },
4936
- metaRight.flatMap((part, index) => index === 0 ? [part] : [h("span", { key: `sep${index}`, className: "dshgt-subs-meta-sep" }, "·"), part])
4961
+ Btn,
4962
+ { small: true, variant: "primary", disabled: busy !== null || !canAdd || alreadySubscribed, onClick: () => canAdd && !alreadySubscribed && addRepo(addValue.trim()) },
4963
+ t("subs.add.confirm")
4937
4964
  )
4965
+ ) : h(
4966
+ Btn,
4967
+ {
4968
+ small: true,
4969
+ disabled: busy !== null,
4970
+ onClick: () => {
4971
+ setAddOpen(true);
4972
+ setAddValue("");
4973
+ }
4974
+ },
4975
+ t("subs.toolbar.add")
4976
+ ),
4977
+ h(
4978
+ Btn,
4979
+ {
4980
+ small: true,
4981
+ onClick: () => {
4982
+ if (selecting) setSelected([]);
4983
+ setSelecting(!selecting);
4984
+ }
4985
+ },
4986
+ selecting ? t("subs.select.done") : t("subs.select")
4987
+ ),
4988
+ h(
4989
+ Btn,
4990
+ {
4991
+ small: true,
4992
+ variant: "primary",
4993
+ disabled: globalBusy || busy !== null,
4994
+ onClick: () => runAnalyze()
4995
+ },
4996
+ analyzing !== null && analyzing.scope === "all" ? t("subs.analyzing") : t("subs.toolbar.analyze")
4938
4997
  )
4939
4998
  ),
4940
- candidate === void 0 ? null : h(
4941
- "div",
4942
- { className: "dshgt-subs-suggest" },
4943
- subscribed ? [
4944
- h("span", { key: "pill", className: "dshgt-tag" }, t("subs.cmd.subscribed")),
4945
- h("span", { key: "repo", className: "repo" }, candidate),
4946
- h("span", { key: "hint", className: "hint" }, t("subs.cmd.subscribed.hint"))
4947
- ] : [
4948
- h("span", { key: "plus", className: "plus" }, "+"),
4949
- h("span", { key: "add" }, t("subs.cmd.add")),
4950
- h("span", { key: "repo", className: "repo" }, candidate),
4951
- h("span", { key: "hint", className: "hint" }, t("subs.cmd.add.hint"))
4952
- ]
4953
- ),
4999
+ banner,
4954
5000
  error === null ? null : h(Alert, { kind: "err" }, error),
4955
5001
  note === null ? null : h(Alert, { kind: "ok" }, note),
4956
5002
  tableBody,
4957
- h(
4958
- "div",
4959
- { className: "dshgt-subs-keys" },
4960
- h("span", null, t("subs.keys.move")),
4961
- h("span", null, t("subs.keys.enter")),
4962
- h("span", null, t("subs.keys.esc")),
4963
- h("span", null, t("subs.keys.sort"))
4964
- ),
4965
5003
  selecting && rows.length > 0 ? h(
4966
5004
  "div",
4967
5005
  { className: "dshgt-subs-selbar" },
@@ -5550,7 +5588,7 @@ function apply(ctx) {
5550
5588
  state.github.configured
5551
5589
  ].filter(Boolean).length;
5552
5590
  const digestTail = state?.status.lastResult?.date ?? state?.status.history[0]?.date;
5553
- const showActions = view === "overview" || view === "runs";
5591
+ const showActions = view === "overview" || view === "runs" || view === "digest";
5554
5592
  const nextRun = schedule === void 0 || !schedule.enabled ? t("shell.scheduleOff") : schedule.nextRunAt === void 0 ? `${schedule.time} · ${schedule.timezone}` : nextRunText(schedule.nextRunAt);
5555
5593
  const relNext = schedule !== void 0 && schedule.enabled && schedule.nextRunAt !== void 0 ? relativeFuture(schedule.nextRunAt, t) : void 0;
5556
5594
  const viewNode = view === "overview" ? h(OverviewView, {
@@ -5565,6 +5603,7 @@ function apply(ctx) {
5565
5603
  t,
5566
5604
  lang,
5567
5605
  store: value,
5606
+ controller,
5568
5607
  ...digestTarget2 === void 0 ? {} : { selected: digestTarget2 },
5569
5608
  onSelect: setDigestTarget,
5570
5609
  onOpenRuns: openRuns
@@ -5641,6 +5680,15 @@ function apply(ctx) {
5641
5680
  },
5642
5681
  t("run.dryRunShort")
5643
5682
  ),
5683
+ h(
5684
+ Btn,
5685
+ {
5686
+ key: "force",
5687
+ onClick: () => void controller.run(false, { force: true }),
5688
+ disabled: controller.running || controller.busy
5689
+ },
5690
+ t("run.force")
5691
+ ),
5644
5692
  h(
5645
5693
  Btn,
5646
5694
  {