dsh-config-manager 0.1.37 → 0.1.39

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.
Files changed (44) hide show
  1. package/lib/client.d.ts +173 -66
  2. package/lib/client.js +1643 -1115
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +175 -1
  5. package/lib/index.js.map +1 -1
  6. package/lib/market/git-file-writer.d.ts +86 -0
  7. package/lib/market/git-file-writer.js +269 -0
  8. package/lib/market/git-file-writer.js.map +1 -0
  9. package/lib/market/github-repos.d.ts +122 -0
  10. package/lib/market/github-repos.js +407 -0
  11. package/lib/market/github-repos.js.map +1 -0
  12. package/lib/market/my-repo.d.ts +131 -0
  13. package/lib/market/my-repo.js +389 -0
  14. package/lib/market/my-repo.js.map +1 -0
  15. package/lib/ui/i18n.d.ts +9 -10
  16. package/lib/ui/i18n.js +19 -21
  17. package/lib/ui/i18n.js.map +1 -1
  18. package/package.json +1 -1
  19. package/src/client/ConfigManagerSection.tsx +2 -2
  20. package/src/client/client-types.ts +2 -0
  21. package/src/client/index.ts +4 -2
  22. package/src/client/market/MarketPanel.tsx +64 -17
  23. package/src/client/market/MyConfigsView.tsx +745 -0
  24. package/src/client/market/market-api.test.ts +29 -0
  25. package/src/client/market/market-api.ts +5 -2
  26. package/src/client/market/market-locales.ts +118 -117
  27. package/src/client/market/my-configs-api.ts +163 -0
  28. package/src/client/market/my-configs-view.test.ts +289 -0
  29. package/src/client/market/my-configs-view.ts +304 -0
  30. package/src/client/run-store.ts +13 -0
  31. package/src/index.ts +163 -1
  32. package/src/market/git-file-writer.test.ts +249 -0
  33. package/src/market/git-file-writer.ts +336 -0
  34. package/src/market/github-repos.test.ts +400 -0
  35. package/src/market/github-repos.ts +481 -0
  36. package/src/market/my-repo.test.ts +472 -0
  37. package/src/market/my-repo.ts +516 -0
  38. package/src/ui/i18n.ts +19 -21
  39. package/lib/ui/market-publish.d.ts +0 -129
  40. package/lib/ui/market-publish.js +0 -168
  41. package/lib/ui/market-publish.js.map +0 -1
  42. package/src/client/market/PublishView.tsx +0 -388
  43. package/src/ui/market-publish.test.ts +0 -219
  44. package/src/ui/market-publish.ts +0 -256
package/lib/client.js CHANGED
@@ -253,16 +253,15 @@ const uiZh = {
253
253
  "market.detail.sectionsEmpty": "未声明分区",
254
254
  "market.detail.statusValid": "校验通过",
255
255
  "market.detail.statusInvalid": "校验未通过",
256
- "marketPublish.stepSelect": "选择配置包",
257
- "marketPublish.stepValidate": "本地校验",
258
- "marketPublish.stepPrepare": "生成条目包",
259
- "marketPublish.stepPush": "推送作者仓库",
260
- "marketPublish.stepSubmit": "提交收录申请",
261
- "marketPublish.errorItemId": "条目 id 仅允许字母数字开头,字符限 . _ -(最长 128)",
262
- "marketPublish.errorName": "条目名称不能为空",
263
- "marketPublish.repoWhitespace": "仓库地址不能包含空白字符",
264
- "marketPublish.repoScheme": "仓库地址必须为 http(s) 链接",
265
- "marketPublish.repoUserinfo": "仓库地址不能包含用户名或密码(userinfo);凭据请通过 DSH credentials 提供"
256
+ "myConfigs.autoField": "系统自动",
257
+ "myConfigs.field.id": "条目 ID",
258
+ "myConfigs.field.author": "作者",
259
+ "myConfigs.field.version": "版本",
260
+ "myConfigs.field.updatedAt": "更新时间",
261
+ "myConfigs.status.notListed": "未收录",
262
+ "myConfigs.status.pendingPr": "PR 待审核",
263
+ "myConfigs.status.listed": "已收录",
264
+ "myConfigs.form.errorName": "名称不能为空"
266
265
  };
267
266
  const uiEn = {
268
267
  "commonClose": "Close",
@@ -498,16 +497,15 @@ const uiEn = {
498
497
  "market.detail.sectionsEmpty": "No sections declared",
499
498
  "market.detail.statusValid": "Verified",
500
499
  "market.detail.statusInvalid": "Failed verification",
501
- "marketPublish.stepSelect": "Select config package",
502
- "marketPublish.stepValidate": "Local validation",
503
- "marketPublish.stepPrepare": "Generate item package",
504
- "marketPublish.stepPush": "Push to author repo",
505
- "marketPublish.stepSubmit": "Submit listing request",
506
- "marketPublish.errorItemId": "Item id must start with a letter or digit; only . _ - allowed (max 128)",
507
- "marketPublish.errorName": "Item name is required",
508
- "marketPublish.repoWhitespace": "Repository URL must not contain whitespace",
509
- "marketPublish.repoScheme": "Repository URL must be an http(s) link",
510
- "marketPublish.repoUserinfo": "Repository URL must not contain username/password (userinfo); provide credentials via DSH credentials"
500
+ "myConfigs.autoField": "Auto",
501
+ "myConfigs.field.id": "Item ID",
502
+ "myConfigs.field.author": "Author",
503
+ "myConfigs.field.version": "Version",
504
+ "myConfigs.field.updatedAt": "Updated",
505
+ "myConfigs.status.notListed": "Not listed",
506
+ "myConfigs.status.pendingPr": "PR pending review",
507
+ "myConfigs.status.listed": "Listed",
508
+ "myConfigs.form.errorName": "Name is required"
511
509
  };
512
510
  /** 构造 翻译函数(zh 源 / en 镜像;缺 key 回退)。 */
513
511
  function makeUiT(lang) {
@@ -549,7 +547,7 @@ var ConfigManagerApiError = class extends Error {
549
547
  }
550
548
  };
551
549
  /** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError */
552
- async function readJson$2(response, t) {
550
+ async function readJson$3(response, t) {
553
551
  const notMountedMessage = t("error.notMounted");
554
552
  let body;
555
553
  try {
@@ -601,7 +599,7 @@ var ConfigManagerApi = class {
601
599
  }
602
600
  /** 健康/版本检查(主页横幅:插件版本 / DSH 版本 / 平台) */
603
601
  async status() {
604
- return readJson$2(await fetch(CONFIG_MANAGER_API.status), this.t);
602
+ return readJson$3(await fetch(CONFIG_MANAGER_API.status), this.t);
605
603
  }
606
604
  /** ExportPort.export:调用 Host 侧导出编排(core Exporter)。加密密码随请求体传输(仅内存)。
607
605
  * 带 AbortController 超时:宿主若卡死,客户端得到明确错误而不是永远停在进度条。 */
@@ -613,7 +611,7 @@ var ConfigManagerApi = class {
613
611
  const controller = new AbortController();
614
612
  const timer = setTimeout(() => controller.abort(), EXPORT_TIMEOUT_MS);
615
613
  try {
616
- return await readJson$2(await fetch(CONFIG_MANAGER_API.export, {
614
+ return await readJson$3(await fetch(CONFIG_MANAGER_API.export, {
617
615
  method: "POST",
618
616
  headers: { "content-type": "application/json" },
619
617
  body: JSON.stringify(body),
@@ -680,14 +678,14 @@ var ConfigManagerApi = class {
680
678
  }
681
679
  /** 上传用户选择的 ZIP 到 Host 受控临时目录,返回可引用的 zipPath(dsh-ssh 同款原始字节上传) */
682
680
  async upload(file) {
683
- return readJson$2(await fetch(CONFIG_MANAGER_API.upload + query({ name: file.name }), {
681
+ return readJson$3(await fetch(CONFIG_MANAGER_API.upload + query({ name: file.name }), {
684
682
  method: "POST",
685
683
  body: file
686
684
  }), this.t);
687
685
  }
688
686
  /** ImportPort.analyzeImport:零写入分析(校验/兼容性/差异/路径/秘密检测) */
689
687
  async analyzeImport(zipPath) {
690
- return readJson$2(await fetch(CONFIG_MANAGER_API.analyze, {
688
+ return readJson$3(await fetch(CONFIG_MANAGER_API.analyze, {
691
689
  method: "POST",
692
690
  headers: { "content-type": "application/json" },
693
691
  body: JSON.stringify({ zipPath })
@@ -695,7 +693,7 @@ var ConfigManagerApi = class {
695
693
  }
696
694
  /** ImportPort.createImportPlan:用用户决策(冲突/路径映射/策略)生成最终计划(Dry Run 零写入) */
697
695
  async createImportPlan(zipPath, decisions) {
698
- return readJson$2(await fetch(CONFIG_MANAGER_API.plan, {
696
+ return readJson$3(await fetch(CONFIG_MANAGER_API.plan, {
699
697
  method: "POST",
700
698
  headers: { "content-type": "application/json" },
701
699
  body: JSON.stringify({
@@ -706,7 +704,7 @@ var ConfigManagerApi = class {
706
704
  }
707
705
  /** ImportPort.decryptArchive:解锁整体加密备份容器 → 明文 ZIP 路径 + 解密覆盖的凭据 ref 名 */
708
706
  async decryptArchive(zipPath, password) {
709
- return readJson$2(await fetch(CONFIG_MANAGER_API.decryptArchive, {
707
+ return readJson$3(await fetch(CONFIG_MANAGER_API.decryptArchive, {
710
708
  method: "POST",
711
709
  headers: { "content-type": "application/json" },
712
710
  body: JSON.stringify({
@@ -727,7 +725,7 @@ var ConfigManagerApi = class {
727
725
  decryptPassword: opts.decryptPassword
728
726
  }
729
727
  };
730
- return readJson$2(await fetch(CONFIG_MANAGER_API.execute, {
728
+ return readJson$3(await fetch(CONFIG_MANAGER_API.execute, {
731
729
  method: "POST",
732
730
  headers: { "content-type": "application/json" },
733
731
  body: JSON.stringify(payload)
@@ -735,19 +733,19 @@ var ConfigManagerApi = class {
735
733
  }
736
734
  /** m1:查询单个 run 的实时状态(执行中轮询 / 刷新恢复用;404 = 已过保留期或不存在) */
737
735
  async progress(runId) {
738
- return readJson$2(await fetch(CONFIG_MANAGER_API.progress + query({ runId })), this.t);
736
+ return readJson$3(await fetch(CONFIG_MANAGER_API.progress + query({ runId })), this.t);
739
737
  }
740
738
  /** m1:列出当前活跃(running)的 run(刷新后重新订阅进行中任务的入口) */
741
739
  async runs() {
742
- return readJson$2(await fetch(CONFIG_MANAGER_API.runs), this.t);
740
+ return readJson$3(await fetch(CONFIG_MANAGER_API.runs), this.t);
743
741
  }
744
742
  /** 列出全部快照元信息(createdAt 倒序;含 status/条目数/宿主文件数/插件数) */
745
743
  async snapshots() {
746
- return (await readJson$2(await fetch(CONFIG_MANAGER_API.snapshots), this.t)).snapshots;
744
+ return (await readJson$3(await fetch(CONFIG_MANAGER_API.snapshots), this.t)).snapshots;
747
745
  }
748
746
  /** 快照恢复:dryRun=true 只取动作计划(零写入);false 执行并返回诚实报告 */
749
747
  async restoreSnapshot(snapshotId, dryRun) {
750
- return readJson$2(await fetch(CONFIG_MANAGER_API.restore, {
748
+ return readJson$3(await fetch(CONFIG_MANAGER_API.restore, {
751
749
  method: "POST",
752
750
  headers: { "content-type": "application/json" },
753
751
  body: JSON.stringify({
@@ -2221,6 +2219,7 @@ function defaultSyncState() {
2221
2219
  }
2222
2220
  function defaultMarketState() {
2223
2221
  return {
2222
+ subView: "browse",
2224
2223
  search: "",
2225
2224
  category: "",
2226
2225
  items: [],
@@ -2228,7 +2227,9 @@ function defaultMarketState() {
2228
2227
  approvals: {},
2229
2228
  importResult: null,
2230
2229
  error: null,
2231
- loadError: null
2230
+ loadError: null,
2231
+ myItems: null,
2232
+ myItemsError: null
2232
2233
  };
2233
2234
  }
2234
2235
  function defaultSnapshotsState() {
@@ -2290,6 +2291,7 @@ function toSyncStoreSlice(s) {
2290
2291
  /** 从市场视图状态提取切片(结构兼容:传入 MarketUiState 亦可)。 */
2291
2292
  function toMarketStoreSlice(s) {
2292
2293
  return {
2294
+ subView: s.subView,
2293
2295
  search: s.search,
2294
2296
  category: s.category,
2295
2297
  items: s.items,
@@ -2297,7 +2299,9 @@ function toMarketStoreSlice(s) {
2297
2299
  approvals: s.approvals,
2298
2300
  importResult: s.importResult,
2299
2301
  error: s.error,
2300
- loadError: s.loadError
2302
+ loadError: s.loadError,
2303
+ myItems: s.myItems,
2304
+ myItemsError: s.myItemsError
2301
2305
  };
2302
2306
  }
2303
2307
  /** 从快照面板状态提取切片(结构兼容:传入 PanelState 亦可)。 */
@@ -2906,111 +2910,111 @@ if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"
2906
2910
  document.head.appendChild(tag);
2907
2911
  }
2908
2912
  var config_manager_module_css_default = {
2909
- "dangerButton": "_367UGW_dangerButton",
2910
- "errorAction": "_367UGW_errorAction",
2913
+ "warnText": "_367UGW_warnText",
2914
+ "progressPercent": "_367UGW_progressPercent",
2911
2915
  "planScroll": "_367UGW_planScroll",
2912
- "badgeOk": "_367UGW_badgeOk",
2913
- "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
2914
- "viewBody": "_367UGW_viewBody",
2915
- "progressTrack": "_367UGW_progressTrack",
2916
+ "pullScroll": "_367UGW_pullScroll",
2917
+ "pathIssueKind": "_367UGW_pathIssueKind",
2918
+ "reportScroll": "_367UGW_reportScroll",
2919
+ "primaryButton": "_367UGW_primaryButton",
2920
+ "progressMeta": "_367UGW_progressMeta",
2921
+ "errorLine": "_367UGW_errorLine",
2922
+ "progressBadgeSection": "_367UGW_progressBadgeSection",
2916
2923
  "reportView": "_367UGW_reportView",
2917
- "severityError": "_367UGW_severityError",
2918
2924
  "progressBadge": "_367UGW_progressBadge",
2919
- "conflictHead": "_367UGW_conflictHead",
2920
- "categoryName": "_367UGW_categoryName",
2921
- "errorTitle": "_367UGW_errorTitle",
2925
+ "snapshotRow": "_367UGW_snapshotRow",
2926
+ "groupList": "_367UGW_groupList",
2927
+ "badgeInfo": "_367UGW_badgeInfo",
2928
+ "statRow": "_367UGW_statRow",
2929
+ "sectionSubtitle": "_367UGW_sectionSubtitle",
2930
+ "select": "_367UGW_select",
2931
+ "errorReason": "_367UGW_errorReason",
2932
+ "badgeError": "_367UGW_badgeError",
2933
+ "checkboxRow": "_367UGW_checkboxRow",
2934
+ "hiddenFile": "_367UGW_hiddenFile",
2935
+ "progressLabel": "_367UGW_progressLabel",
2936
+ "spinner": "_367UGW_spinner",
2937
+ "reportList": "_367UGW_reportList",
2938
+ "warnList": "_367UGW_warnList",
2939
+ "aboutLinkRow": "_367UGW_aboutLinkRow",
2922
2940
  "banner": "_367UGW_banner",
2923
- "pathNew": "_367UGW_pathNew",
2924
- "warnText": "_367UGW_warnText",
2925
- "kindTag": "_367UGW_kindTag",
2926
- "pathOld": "_367UGW_pathOld",
2927
- "section": "_367UGW_section",
2928
- "sectionTitle": "_367UGW_sectionTitle",
2929
- "modeTab": "_367UGW_modeTab",
2930
- "progressMeta": "_367UGW_progressMeta",
2931
- "categoryDesc": "_367UGW_categoryDesc",
2941
+ "badgeOk": "_367UGW_badgeOk",
2942
+ "conflictItem": "_367UGW_conflictItem",
2943
+ "errorBanner": "_367UGW_errorBanner",
2932
2944
  "conflictId": "_367UGW_conflictId",
2933
- "primaryButton": "_367UGW_primaryButton",
2934
- "pathMappingList": "_367UGW_pathMappingList",
2935
- "pathRow": "_367UGW_pathRow",
2936
- "diffScroll": "_367UGW_diffScroll",
2945
+ "secretsList": "_367UGW_secretsList",
2946
+ "sectionTitleBlock": "_367UGW_sectionTitleBlock",
2937
2947
  "input": "_367UGW_input",
2938
- "errorFooter": "_367UGW_errorFooter",
2939
- "pullScroll": "_367UGW_pullScroll",
2940
- "reportFooter": "_367UGW_reportFooter",
2948
+ "spinnerLabel": "_367UGW_spinnerLabel",
2949
+ "progressBarDone": "_367UGW_progressBarDone",
2950
+ "categoryDesc": "_367UGW_categoryDesc",
2951
+ "sectionBody": "_367UGW_sectionBody",
2941
2952
  "badge": "_367UGW_badge",
2942
- "snapshotList": "_367UGW_snapshotList",
2943
- "viewTabs": "_367UGW_viewTabs",
2944
- "sectionSubtitle": "_367UGW_sectionSubtitle",
2953
+ "groupItems": "_367UGW_groupItems",
2954
+ "progressTrack": "_367UGW_progressTrack",
2955
+ "conflictDetail": "_367UGW_conflictDetail",
2956
+ "errorActionLabel": "_367UGW_errorActionLabel",
2957
+ "kindTag": "_367UGW_kindTag",
2958
+ "progressBlock": "_367UGW_progressBlock",
2959
+ "badgeWarn": "_367UGW_badgeWarn",
2945
2960
  "groupNote": "_367UGW_groupNote",
2946
- "radioLabel": "_367UGW_radioLabel",
2947
- "pathValue": "_367UGW_pathValue",
2948
- "spinner": "_367UGW_spinner",
2949
- "reportScroll": "_367UGW_reportScroll",
2950
- "aboutLinkRow": "_367UGW_aboutLinkRow",
2951
- "aboutAuthor": "_367UGW_aboutAuthor",
2952
- "progressBarDone": "_367UGW_progressBarDone",
2953
- "progressDetail": "_367UGW_progressDetail",
2961
+ "groupHeader": "_367UGW_groupHeader",
2962
+ "actionRow": "_367UGW_actionRow",
2963
+ "optionsCard": "_367UGW_optionsCard",
2964
+ "pathOld": "_367UGW_pathOld",
2965
+ "errorItem": "_367UGW_errorItem",
2966
+ "snapshotList": "_367UGW_snapshotList",
2967
+ "pathNew": "_367UGW_pathNew",
2968
+ "confirmScroll": "_367UGW_confirmScroll",
2969
+ "conflictList": "_367UGW_conflictList",
2970
+ "categoryName": "_367UGW_categoryName",
2971
+ "groupLabel": "_367UGW_groupLabel",
2972
+ "errorList": "_367UGW_errorList",
2954
2973
  "card": "_367UGW_card",
2955
- "errorActionLabel": "_367UGW_errorActionLabel",
2956
- "conflictOptions": "_367UGW_conflictOptions",
2957
- "pathIssueKind": "_367UGW_pathIssueKind",
2958
- "empty": "_367UGW_empty",
2959
- "select": "_367UGW_select",
2960
- "secretsList": "_367UGW_secretsList",
2974
+ "sectionTitle": "_367UGW_sectionTitle",
2975
+ "optionsHeader": "_367UGW_optionsHeader",
2976
+ "aboutAuthor": "_367UGW_aboutAuthor",
2977
+ "progressBadgeCount": "_367UGW_progressBadgeCount",
2961
2978
  "fieldLabel": "_367UGW_fieldLabel",
2979
+ "reportText": "_367UGW_reportText",
2980
+ "severityError": "_367UGW_severityError",
2962
2981
  "formError": "_367UGW_formError",
2982
+ "pathMappingList": "_367UGW_pathMappingList",
2983
+ "empty": "_367UGW_empty",
2963
2984
  "spinnerWrap": "_367UGW_spinnerWrap",
2964
- "warnList": "_367UGW_warnList",
2965
- "sectionBody": "_367UGW_sectionBody",
2966
- "groupItems": "_367UGW_groupItems",
2967
- "snapshotRowHeader": "_367UGW_snapshotRowHeader",
2985
+ "field": "_367UGW_field",
2986
+ "secretFields": "_367UGW_secretFields",
2987
+ "rollbackBox": "_367UGW_rollbackBox",
2968
2988
  "rowActions": "_367UGW_rowActions",
2969
- "spinnerLabel": "_367UGW_spinnerLabel",
2970
- "optionsCard": "_367UGW_optionsCard",
2971
- "progressBlock": "_367UGW_progressBlock",
2972
- "progressPercent": "_367UGW_progressPercent",
2973
- "conflictDetail": "_367UGW_conflictDetail",
2974
- "progressLabel": "_367UGW_progressLabel",
2975
- "badgeInfo": "_367UGW_badgeInfo",
2976
- "statRow": "_367UGW_statRow",
2977
- "modeTabs": "_367UGW_modeTabs",
2989
+ "dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
2990
+ "ghostButton": "_367UGW_ghostButton",
2991
+ "diffScroll": "_367UGW_diffScroll",
2978
2992
  "viewTab": "_367UGW_viewTab",
2979
- "field": "_367UGW_field",
2993
+ "modeTab": "_367UGW_modeTab",
2994
+ "radioLabel": "_367UGW_radioLabel",
2995
+ "groupCard": "_367UGW_groupCard",
2996
+ "reportFooter": "_367UGW_reportFooter",
2980
2997
  "sectionHeader": "_367UGW_sectionHeader",
2981
- "badgeError": "_367UGW_badgeError",
2982
- "optionsHeader": "_367UGW_optionsHeader",
2983
- "groupList": "_367UGW_groupList",
2984
- "conflictList": "_367UGW_conflictList",
2985
- "groupHeader": "_367UGW_groupHeader",
2986
- "errorLine": "_367UGW_errorLine",
2987
- "conflictItem": "_367UGW_conflictItem",
2988
- "snapshotRow": "_367UGW_snapshotRow",
2989
- "errorBanner": "_367UGW_errorBanner",
2990
- "hiddenFile": "_367UGW_hiddenFile",
2998
+ "pathValue": "_367UGW_pathValue",
2991
2999
  "progressIndeterminate": "_367UGW_progressIndeterminate",
2992
- "rollbackBox": "_367UGW_rollbackBox",
2993
- "confirmScroll": "_367UGW_confirmScroll",
2994
- "actionRow": "_367UGW_actionRow",
2995
- "progressBar": "_367UGW_progressBar",
3000
+ "dangerButton": "_367UGW_dangerButton",
2996
3001
  "hint": "_367UGW_hint",
2997
- "errorList": "_367UGW_errorList",
2998
- "reportList": "_367UGW_reportList",
2999
- "ghostButton": "_367UGW_ghostButton",
3000
- "groupLabel": "_367UGW_groupLabel",
3001
- "badgeWarn": "_367UGW_badgeWarn",
3002
- "checkboxRow": "_367UGW_checkboxRow",
3003
- "progressBadgeCount": "_367UGW_progressBadgeCount",
3004
- "errorReason": "_367UGW_errorReason",
3005
- "groupCard": "_367UGW_groupCard",
3006
- "categoryItem": "_367UGW_categoryItem",
3007
3002
  "modeHint": "_367UGW_modeHint",
3008
- "reportText": "_367UGW_reportText",
3009
- "sectionTitleBlock": "_367UGW_sectionTitleBlock",
3010
- "dshCmSpin": "_367UGW_dshCmSpin",
3011
- "errorItem": "_367UGW_errorItem",
3012
- "secretFields": "_367UGW_secretFields",
3013
- "progressBadgeSection": "_367UGW_progressBadgeSection"
3003
+ "categoryItem": "_367UGW_categoryItem",
3004
+ "errorFooter": "_367UGW_errorFooter",
3005
+ "pathRow": "_367UGW_pathRow",
3006
+ "progressDetail": "_367UGW_progressDetail",
3007
+ "progressBar": "_367UGW_progressBar",
3008
+ "snapshotRowHeader": "_367UGW_snapshotRowHeader",
3009
+ "viewBody": "_367UGW_viewBody",
3010
+ "modeTabs": "_367UGW_modeTabs",
3011
+ "errorTitle": "_367UGW_errorTitle",
3012
+ "errorAction": "_367UGW_errorAction",
3013
+ "conflictHead": "_367UGW_conflictHead",
3014
+ "section": "_367UGW_section",
3015
+ "viewTabs": "_367UGW_viewTabs",
3016
+ "conflictOptions": "_367UGW_conflictOptions",
3017
+ "dshCmSpin": "_367UGW_dshCmSpin"
3014
3018
  };
3015
3019
  //#endregion
3016
3020
  //#region src/client/common/ui.tsx
@@ -5030,7 +5034,7 @@ const SYNC_API = {
5030
5034
  /** 同步请求超时(ms):与 Host 半 ROUTE_TIMEOUT_MS 对齐(git 网络操作可能较慢) */
5031
5035
  const SYNC_TIMEOUT_MS = 300 * 1e3;
5032
5036
  /** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
5033
- async function readJson$1(response, t) {
5037
+ async function readJson$2(response, t) {
5034
5038
  const notMountedMessage = t("error.notMounted");
5035
5039
  let body;
5036
5040
  try {
@@ -5043,11 +5047,11 @@ async function readJson$1(response, t) {
5043
5047
  return body;
5044
5048
  }
5045
5049
  /** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
5046
- async function postJson$1(path, body, t) {
5050
+ async function postJson$2(path, body, t) {
5047
5051
  const controller = new AbortController();
5048
5052
  const timer = setTimeout(() => controller.abort(), SYNC_TIMEOUT_MS);
5049
5053
  try {
5050
- return await readJson$1(await fetch(path, {
5054
+ return await readJson$2(await fetch(path, {
5051
5055
  method: "POST",
5052
5056
  headers: { "content-type": "application/json" },
5053
5057
  body: JSON.stringify(body),
@@ -5068,51 +5072,51 @@ var SyncApi = class {
5068
5072
  }
5069
5073
  /** 读取同步状态(配置 / 凭据 / 上次同步时间 / 分区数) */
5070
5074
  async status() {
5071
- return readJson$1(await fetch(SYNC_API.status), this.t);
5075
+ return readJson$2(await fetch(SYNC_API.status), this.t);
5072
5076
  }
5073
5077
  /** 推送:导出 portable 分区 → 提交到私有 Git 仓库 → 更新 sync-state */
5074
5078
  async push(payload) {
5075
- return postJson$1(SYNC_API.push, payload, this.t);
5079
+ return postJson$2(SYNC_API.push, payload, this.t);
5076
5080
  }
5077
5081
  /** 拉取差异预览:拉取远端最新快照 → 只读分析(绝不执行导入) */
5078
5082
  async pull(payload) {
5079
- return postJson$1(SYNC_API.pull, payload, this.t);
5083
+ return postJson$2(SYNC_API.pull, payload, this.t);
5080
5084
  }
5081
5085
  /** GitHub OAuth device flow:发起登录,返回一次性用户码 + 授权页 URL + flowId */
5082
5086
  async githubStart() {
5083
- return postJson$1(SYNC_API.githubStart, {}, this.t);
5087
+ return postJson$2(SYNC_API.githubStart, {}, this.t);
5084
5088
  }
5085
5089
  /** GitHub OAuth device flow:凭 flowId 轮询授权结果(成功时 token 已由 Host 写入 credentials) */
5086
5090
  async githubPoll(flowId) {
5087
- return postJson$1(SYNC_API.githubPoll, { flowId }, this.t);
5091
+ return postJson$2(SYNC_API.githubPoll, { flowId }, this.t);
5088
5092
  }
5089
5093
  /** GitHub OAuth device flow:取消(丢弃宿主侧登记,零副作用) */
5090
5094
  async githubCancel(flowId) {
5091
- return postJson$1(SYNC_API.githubCancel, { flowId }, this.t);
5095
+ return postJson$2(SYNC_API.githubCancel, { flowId }, this.t);
5092
5096
  }
5093
5097
  /** 同步历史:列出本地祖先快照 + 自动同步执行记录(按 createdAt 倒序合并)。 */
5094
5098
  async history() {
5095
- return readJson$1(await fetch(SYNC_API.history), this.t);
5099
+ return readJson$2(await fetch(SYNC_API.history), this.t);
5096
5100
  }
5097
5101
  /** 远端历史快照列表(供「选择历史快照」下拉)。 */
5098
5102
  async snapshotsList(payload) {
5099
- return postJson$1(SYNC_API.snapshotsList, payload, this.t);
5103
+ return postJson$2(SYNC_API.snapshotsList, payload, this.t);
5100
5104
  }
5101
5105
  /** 一键同步第一步:拉取 → 差异确认会话(items 逐项确认,暂不导入)。 */
5102
5106
  async sync(payload) {
5103
- return postJson$1(SYNC_API.sync, payload, this.t);
5107
+ return postJson$2(SYNC_API.sync, payload, this.t);
5104
5108
  }
5105
5109
  /** 一键同步第二步:按用户对差异项的逐项决策执行导入。 */
5106
5110
  async applyItems(payload) {
5107
- return postJson$1(SYNC_API.applyItems, payload, this.t);
5111
+ return postJson$2(SYNC_API.applyItems, payload, this.t);
5108
5112
  }
5109
5113
  /** 取消/清理差异确认会话(丢弃临时 ZIP,零副作用)。 */
5110
5114
  async cancel(syncSessionId) {
5111
- return postJson$1(SYNC_API.cancel, { syncSessionId }, this.t);
5115
+ return postJson$2(SYNC_API.cancel, { syncSessionId }, this.t);
5112
5116
  }
5113
5117
  /** 自动同步状态(GET /sync/autosync 返回全部通道的 { git, webdav },各自独立)。 */
5114
5118
  async autosyncStatusAll() {
5115
- return readJson$1(await fetch(SYNC_API.autosync), this.t);
5119
+ return readJson$2(await fetch(SYNC_API.autosync), this.t);
5116
5120
  }
5117
5121
  /** 自动同步状态(指定通道;从全部通道状态中取)。 */
5118
5122
  async autosyncStatus(transport = "git") {
@@ -5120,26 +5124,26 @@ var SyncApi = class {
5120
5124
  }
5121
5125
  /** 自动同步配置更新(POST /sync/autosync;payload.transport 指定目标通道)。 */
5122
5126
  async autosyncUpdate(payload) {
5123
- return postJson$1(SYNC_API.autosync, payload, this.t);
5127
+ return postJson$2(SYNC_API.autosync, payload, this.t);
5124
5128
  }
5125
5129
  /** 保存同步分区选择(POST /sync/selection):模式 + 勾选分区持久化到 Host。
5126
5130
  * 自动同步调度器与手动 push 共用此配置(刷新/重启后仍然生效)。 */
5127
5131
  async saveSelection(payload) {
5128
- return postJson$1(SYNC_API.selection, payload, this.t);
5132
+ return postJson$2(SYNC_API.selection, payload, this.t);
5129
5133
  }
5130
5134
  /** 保存同步通道配置(POST /sync/config):url/username/password(git: repoUrl/token)持久化。
5131
5135
  * password/token 经 Host 写入 DSH credentials(值永不回传);返回凭据布尔供 UI 刷新徽章。 */
5132
5136
  async saveConfig(payload) {
5133
- return postJson$1(SYNC_API.config, payload, this.t);
5137
+ return postJson$2(SYNC_API.config, payload, this.t);
5134
5138
  }
5135
5139
  /** 保存插件 UI 偏好(POST /sync/ui-prefs):当前为上次选择的同步通道(ui-prefs.json,
5136
5140
  * 随 self 分区进导出备份)。纯偏好无 secret;失败由调用方静默降级(localStorage 兜底)。 */
5137
5141
  async saveUiPrefs(payload) {
5138
- return postJson$1(SYNC_API.uiPrefs, payload, this.t);
5142
+ return postJson$2(SYNC_API.uiPrefs, payload, this.t);
5139
5143
  }
5140
5144
  /** 一键回滚:按 restoreId 调用 backup→rollback */
5141
5145
  async rollback(payload) {
5142
- return postJson$1(SYNC_API.rollback, payload, this.t);
5146
+ return postJson$2(SYNC_API.rollback, payload, this.t);
5143
5147
  }
5144
5148
  };
5145
5149
  //#endregion
@@ -7188,904 +7192,1318 @@ function isOfficialMarket(url) {
7188
7192
  return url.trim() === DEFAULT_MARKET_URL;
7189
7193
  }
7190
7194
  //#endregion
7191
- //#region src/market/types.ts
7195
+ //#region src/market/github-repos.ts
7192
7196
  /**
7193
- * itemId / 仓库 url-hash 的安全字符集(防路径穿越 + 用作目录名):
7194
- * 字母数字开头,仅 . _ -,最长 128。与 sync GitTransport 的 SAFE_ID_RE 同构。
7197
+ * 官方收录目标仓库(产品决策 2026-08-20,docs/design/2026-08-20-my-configs-design.md §2.4):
7198
+ * 收录 / PR 相关目标**固定**为 xiajiajun516/dsh-config-market,写死常量、界面不提供任何修改入口。
7199
+ * 与 src/market/builtin.ts 的内置市场为同一仓库(内置市场 URL 可经 env 覆盖仅用于浏览,
7200
+ * 收录目标不受 env 影响,恒为官方仓库)。
7195
7201
  */
7196
- const SAFE_ITEM_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
7202
+ const MARKET_UPSTREAM_OWNER = "xiajiajun516";
7203
+ /** 官方收录目标仓库名(见 MARKET_UPSTREAM_OWNER) */
7204
+ const MARKET_UPSTREAM_REPO = "dsh-config-market";
7197
7205
  //#endregion
7198
- //#region src/ui/market-publish.ts
7206
+ //#region src/client/market/my-configs-view.ts
7199
7207
  /**
7200
- * 配置市场「发布到市场」向导的框架无关渲染/校验模型(m-market-publish,node 可测)。
7208
+ * 「我的配置」区块的客户端纯渲染装配层(m-my-configs-view,node 可测)。
7201
7209
  *
7202
- * 设计纪律(docs/design/2026-08-19-market-publish-design.md §3.4):
7203
- * - 全部无副作用纯函数,React 壳(PublishView)只装配,不承载业务逻辑;
7204
- * - 5 步流程:选择配置包 → 本地校验 → 生成条目包 → 推送作者仓库 → 提交收录申请;
7205
- * - 状态组件内自持(同 MarketPanel 策略,不进 sessionStorage)。
7210
+ * 设计纪律(docs/design/2026-08-20-my-configs-design.md §4.5 / §4.6):
7211
+ * - 全部无副作用纯函数,React 壳(MyConfigsView)只装配,不承载业务逻辑;
7212
+ * - 输入数据一律由调用方(组件 / api 层)提供 —— 本文件不发起任何请求,不持有凭据;
7213
+ * - 录入最小化(§1.2):表单仅 name(预填 zip 文件名,可改)/ description(可选)/
7214
+ * categories(可选);id / author / version / updatedAt 均为系统自动生成,
7215
+ * 界面以「系统自动」徽章展示(MY_CONFIG_AUTO_FIELDS);
7216
+ * - 收录状态三态(§4.5):未收录(本地独有)| PR 待审核(带 PR 链接)| 已收录
7217
+ * (官方 dsh-config-market/index.json 含该 id);已收录 > PR 待审核 > 未收录
7218
+ * (官方已收录时即使存在 open PR 也以收录为准——PR 已合并后不应再显示待审核);
7219
+ * - open PR 匹配按固定分支 `dsh-market-sync/<itemId>`(§2.4 自动 PR 分支约定);
7220
+ * - 文案走 src/ui/i18n.ts 的 UiT('myConfigs.*' 键,zh 源 / en 镜像),不硬编码中文。
7206
7221
  *
7207
- * 安全硬约束(与安全不变量同款):
7208
- * - itemId 须过 SAFE_ITEM_ID_RE(防 items/<id>/ 路径越界,assertSafeItemId 同源正则);
7209
- * - repoUrl 可选但若填必须为 http(s) 且无 userinfo(轻量等价实现 sync validateRepoUrl 的
7210
- * 语义,不 import node 模块、浏览器/node 双端可跑;凭据永不拼入 URL);
7211
- * - 本层只生成 git 命令模板与 index.json 收录片段**文本**,不做任何 git 写操作、
7212
- * 不持有凭据(与 About 页「Star 由用户在 GitHub 完成」同款产品纪律);
7213
- * - 内容校验(zip 加固解包 / containsSecrets / SHA-256)在 Host 侧 prepare 端点,本层不管。
7222
+ * 安全硬约束:本层只做展示推导,不接触 token / 凭据;PR URL 由调用方提供,
7223
+ * 渲染前仍过 redact() 兜底(与全站一致)。
7214
7224
  */
7215
- /** 发布向导 5 步(设计文档 §3.4:选择配置包 / 本地校验 / 生成条目包 / 推送作者仓库 / 提交收录申请) */
7216
- const PUBLISH_STEP_IDS = [
7217
- "select",
7218
- "validate",
7219
- "prepare",
7220
- "push",
7221
- "submit"
7222
- ];
7223
- /** 5 步模型(顺序即流程;文案走 i18n 字典,不硬编码中文)。 */
7224
- function publishSteps(t = zhUiT) {
7225
+ /**
7226
+ * 登录状态推导(纯函数):loading 优先;authError 非空(401 等)→ token 失效;
7227
+ * 否则按 status.loggedIn 区分已登录 / 未登录。输入由调用方提供。
7228
+ */
7229
+ function deriveLoginState(input) {
7230
+ if (input.loading) return { kind: "loading" };
7231
+ if (input.authFailed) return { kind: "token-invalid" };
7232
+ const s = input.status;
7233
+ if (s !== null && s.loggedIn) return {
7234
+ kind: "logged-in",
7235
+ login: s.login ?? "",
7236
+ repoUrl: s.repoUrl ?? "",
7237
+ repoExists: s.repoExists ?? false
7238
+ };
7239
+ return { kind: "logged-out" };
7240
+ }
7241
+ /** 空表单初值(React 壳 useState 初始 + 测试用)。 */
7242
+ const EMPTY_MY_CONFIG_FORM = {
7243
+ name: "",
7244
+ description: "",
7245
+ categories: ""
7246
+ };
7247
+ /** 表单校验:名称必填(trim 后非空)。描述/类别可选。 */
7248
+ function validateMyConfigForm(form, t = zhUiT) {
7249
+ return { name: form.name.trim() === "" ? t("myConfigs.form.errorName") : null };
7250
+ }
7251
+ /** 表单是否全部合法(上传按钮可用性)。 */
7252
+ function myConfigFormValid(errors) {
7253
+ return errors.name === null;
7254
+ }
7255
+ /** 逗号分隔类别文本 → 去空白数组(空结果 = 无类别;与 PublishView 相同语义)。 */
7256
+ function parseCategories(csv) {
7257
+ return csv.split(",").map((c) => c.trim()).filter((c) => c !== "");
7258
+ }
7259
+ /** 系统自动字段徽章列表(id/作者/版本/更新时间 → 「系统自动」徽章)。 */
7260
+ function autoFieldBadges(t = zhUiT) {
7261
+ const autoText = t("myConfigs.autoField");
7225
7262
  return [
7226
7263
  {
7227
- id: "select",
7228
- title: t("marketPublish.stepSelect")
7229
- },
7230
- {
7231
- id: "validate",
7232
- title: t("marketPublish.stepValidate")
7264
+ field: "id",
7265
+ label: t("myConfigs.field.id")
7233
7266
  },
7234
7267
  {
7235
- id: "prepare",
7236
- title: t("marketPublish.stepPrepare")
7268
+ field: "author",
7269
+ label: t("myConfigs.field.author")
7237
7270
  },
7238
7271
  {
7239
- id: "push",
7240
- title: t("marketPublish.stepPush")
7272
+ field: "version",
7273
+ label: t("myConfigs.field.version")
7241
7274
  },
7242
7275
  {
7243
- id: "submit",
7244
- title: t("marketPublish.stepSubmit")
7276
+ field: "updatedAt",
7277
+ label: t("myConfigs.field.updatedAt")
7245
7278
  }
7246
- ];
7279
+ ].map(({ field, label }) => ({
7280
+ field,
7281
+ label,
7282
+ autoText
7283
+ }));
7247
7284
  }
7248
- /** 空表单初值(React 壳 useState 初始 + 测试用)。 */
7249
- const EMPTY_PUBLISH_FORM = {
7250
- itemId: "",
7251
- name: "",
7252
- version: "",
7253
- description: "",
7254
- author: "",
7255
- categories: "",
7256
- repoUrl: ""
7257
- };
7258
7285
  /**
7259
- * 发布仓库 URL 合法性校验(返回错误消息;null = 合法)。
7260
- * **可选字段**:空串/空白视为「未填」= 合法;非空须:
7261
- * - 不含空白字符(\s);
7262
- * - http(s) 开头(拒绝 git@ / ssh / ftp 等非 http(s) 形态,市场仓库只走 https clone);
7263
- * - authority 段不含 userinfo(`user[:password]@host`),凭据必须走 DSH credentials。
7264
- * 语义与 sync 的 validateRepoUrl 对齐(拒绝 userinfo / 空白),另按市场设计补强「非 http(s) 拒绝」。
7286
+ * Host 侧收录状态桥(§4.5 在 Host 判定)→ 客户端判别联合 ItemStatus:
7287
+ * my-repo.ts 的 MyItemStatus = 'not-listed' | 'pr-pending' | 'listed',
7288
+ * prUrl 在 pr-pending 时由 Host 提供;prNumber 可选(Host 未提供时缺省)。
7289
+ * 纯映射,无副作用;MyConfigsView 消费 /me/items 条目时经此桥转徽章模型。
7265
7290
  */
7266
- function validatePublishRepoUrl(repoUrl, t = zhUiT) {
7267
- if (typeof repoUrl !== "string" || repoUrl.trim() === "") return null;
7268
- const url = repoUrl.trim();
7269
- if (/\s/.test(url)) return t("marketPublish.repoWhitespace");
7270
- if (!/^https?:\/\//i.test(url)) return t("marketPublish.repoScheme");
7271
- if ((url.replace(/^https?:\/\//i, "").split(/[/?#]/)[0] ?? "").includes("@")) return t("marketPublish.repoUserinfo");
7272
- return null;
7291
+ function itemStatusFromHost(entry) {
7292
+ switch (entry.status) {
7293
+ case "listed": return { kind: "listed" };
7294
+ case "pr-pending": return {
7295
+ kind: "pending-pr",
7296
+ prUrl: entry.prUrl ?? "",
7297
+ prNumber: entry.prNumber
7298
+ };
7299
+ default: return { kind: "not-listed" };
7300
+ }
7301
+ }
7302
+ /** 收录状态 → 徽章模板(ok=已收录 / warn=PR 待审核 / info=未收录)。 */
7303
+ function itemStatusBadge(status, t = zhUiT) {
7304
+ switch (status.kind) {
7305
+ case "listed": return {
7306
+ kind: "ok",
7307
+ text: t("myConfigs.status.listed")
7308
+ };
7309
+ case "pending-pr": return {
7310
+ kind: "warn",
7311
+ text: t("myConfigs.status.pendingPr"),
7312
+ prUrl: status.prUrl,
7313
+ prNumber: status.prNumber
7314
+ };
7315
+ case "not-listed": return {
7316
+ kind: "info",
7317
+ text: t("myConfigs.status.notListed")
7318
+ };
7319
+ }
7273
7320
  }
7274
- /** 表单校验:itemId(SAFE_ITEM_ID_RE)+ name 必填 + repoUrl 可选校验。返回逐字段错误。 */
7275
- function validatePublishForm(form, t = zhUiT) {
7276
- const errors = {
7277
- itemId: null,
7278
- name: null,
7279
- repoUrl: null
7321
+ /** 条目投影:缺失字段给默认值(name 缺省回退 id;其余空串/空数组)。 */
7322
+ function toMyItemView(item, status, t = zhUiT) {
7323
+ return {
7324
+ id: item.id,
7325
+ name: item.name !== void 0 && item.name.trim() !== "" ? item.name : item.id,
7326
+ version: item.version ?? "",
7327
+ updatedAt: item.updatedAt ?? "",
7328
+ categories: item.categories ?? [],
7329
+ author: item.author ?? "",
7330
+ status,
7331
+ badge: itemStatusBadge(status, t)
7280
7332
  };
7281
- if (!SAFE_ITEM_ID_RE.test(form.itemId)) errors.itemId = t("marketPublish.errorItemId");
7282
- if (form.name.trim() === "") errors.name = t("marketPublish.errorName");
7283
- const repoErr = validatePublishRepoUrl(form.repoUrl, t);
7284
- if (repoErr !== null) errors.repoUrl = repoErr;
7285
- return errors;
7286
7333
  }
7287
- /** 表单是否全部合法(generate 按钮可用性 / canProceed 的 prepare 前置)。 */
7288
- function publishFormValid(errors) {
7289
- return errors.itemId === null && errors.name === null && errors.repoUrl === null;
7334
+ /** 列表摘要统计(按 status 分类计数)。 */
7335
+ function summarizeMyItems(views) {
7336
+ let listed = 0;
7337
+ let pendingPr = 0;
7338
+ let notListed = 0;
7339
+ for (const v of views) if (v.status.kind === "listed") listed += 1;
7340
+ else if (v.status.kind === "pending-pr") pendingPr += 1;
7341
+ else notListed += 1;
7342
+ return {
7343
+ total: views.length,
7344
+ listed,
7345
+ pendingPr,
7346
+ notListed
7347
+ };
7290
7348
  }
7291
- /**
7292
- * 生成「推送到作者仓库」的 git 命令模板(用户在**自己的公开仓库**里执行;
7293
- * 插件不做任何 git 写操作、不持有凭据)。
7294
- *
7295
- * 语义:clone 空仓库 → 进入 → 建 items/<id>/ → 从发布目录复制条目文件
7296
- * (prepare 产物解压为 `dist/`,含 dist/items/<id>/manifest.json + config.zip)→
7297
- * add → commit → push。
7298
- */
7299
- function buildGitPushCommands(input) {
7300
- const { repoUrl, itemId, dir } = input;
7301
- return [
7302
- `git clone ${repoUrl} ${dir}`,
7303
- `cd ${dir}`,
7304
- `mkdir -p items/${itemId}`,
7305
- `cp -r ../dist/items/${itemId}/. items/${itemId}/`,
7306
- `git add items/${itemId}`,
7307
- `git commit -m "publish ${itemId}"`,
7308
- `git push origin HEAD`
7309
- ];
7349
+ //#endregion
7350
+ //#region src/market/view.ts
7351
+ /** 市场状态行文案(status 区块顶部一行)。count<=0 → 未添加提示 */
7352
+ function marketStatusText(config, t) {
7353
+ const count = Array.isArray(config) ? config.length : config.count;
7354
+ if (count <= 0) return t("market.statusUnconfigured");
7355
+ return t("market.statusConfigured", { count });
7310
7356
  }
7311
- /**
7312
- * 生成「提交收录申请」(PR)时追加进官方 index.json `items[]` 的条目片段
7313
- * (pretty JSON 文本,`JSON.parse` 可直接通过;可选字段空值不写入)。
7314
- * 字段形状与 src/market/index-parser.ts 的白名单解析面一致。
7315
- */
7316
- function buildIndexEntrySnippet(item) {
7317
- const entry = {
7318
- id: item.id,
7319
- name: item.name
7357
+ /** 列表摘要:总条目数 + 缓存徽章计数(供 UI 顶部小结)。无副作用。 */
7358
+ function marketListSummary(items, _t) {
7359
+ const fresh = items.filter((i) => i.cacheState === "fresh").length;
7360
+ const cached = items.filter((i) => i.cacheState === "cached").length;
7361
+ const none = items.filter((i) => i.cacheState === "none").length;
7362
+ return {
7363
+ total: items.length,
7364
+ fresh,
7365
+ cached,
7366
+ none
7320
7367
  };
7321
- if (item.description !== void 0 && item.description.trim() !== "") entry.description = item.description.trim();
7322
- if (item.author !== void 0 && item.author.trim() !== "") entry.author = item.author.trim();
7323
- if (item.version !== void 0 && item.version.trim() !== "") entry.version = item.version.trim();
7324
- if (item.updatedAt !== void 0 && item.updatedAt.trim() !== "") entry.updatedAt = item.updatedAt.trim();
7325
- if (item.categories !== void 0 && item.categories.length > 0) entry.categories = item.categories;
7326
- entry.repo = item.repo;
7327
- return JSON.stringify(entry, null, 2);
7328
7368
  }
7329
- /**
7330
- * 当前步骤是否可前进到下一步(纯函数推导,React 壳只装配):
7331
- * - `select` :须已选择配置 zip(zipSelected);
7332
- * - `validate`:须本地校验通过(validated,dry-run 合法 + 无 secrets);
7333
- * - `prepare` :须条目包已生成(prepared,含表单校验通过——生成动作本身以 publishFormValid 为前提);
7334
- * - `push` :引导页无硬性前置(复制命令即可前进到收录申请);
7335
- * - `submit` :最后一步,不可再前进。
7336
- */
7337
- function canProceed(state) {
7338
- switch (state.step) {
7339
- case "select": return state.zipSelected;
7340
- case "validate": return state.validated;
7341
- case "prepare": return state.prepared;
7342
- case "push": return true;
7343
- case "submit": return false;
7344
- }
7369
+ /** 条目校验徽章:status 文案 + sections 清单文案。返回 { statusText, sectionsText, valid } */
7370
+ function computeItemBadge(detail, t) {
7371
+ return {
7372
+ statusText: detail.status === "valid" ? t("market.detail.statusValid") : t("market.detail.statusInvalid"),
7373
+ sectionsText: detail.sections.length > 0 ? t("market.detail.sections", { sections: detail.sections.join(", ") }) : t("market.detail.sectionsEmpty"),
7374
+ valid: detail.status === "valid"
7375
+ };
7345
7376
  }
7346
- //#endregion
7347
- //#region src/client/market/PublishView.tsx
7348
7377
  /**
7349
- * 配置市场「发布到市场」发布向导(阶段 2,docs/design/2026-08-19-market-publish-design.md §3.4)。
7350
- *
7351
- * 5 步流程(全部渲染/校验模型来自 src/ui/market-publish.ts 纯函数,node 已测;本组件只装配):
7352
- * ①选择配置包 —— 复用导出 zip(本地文件选择 → upload 受控临时区,零新增导入逻辑);
7353
- * ②本地校验 —— analyzeImport dry-run(零写入):内容合法且不含密钥(secretCount===0);
7354
- * ③生成条目包 —— 表单(name/description/author/categories/id/repoUrl 可选)+ POST /market/prepare
7355
- * → 展示 manifest 文本 / SHA-256 / sections(可复制);
7356
- * ④推送作者仓库 —— t4 控制器生成的 git 命令模板(可复制);插件不做任何 git 写操作、不持有凭据;
7357
- * ⑤提交收录申请 —— index.json 追加片段(可复制)+ fork 官方仓库提 PR 指引。
7358
- *
7359
- * 状态策略:组件内自持(useState,不进 sessionStorage —— 发布为一次性低频率流程,同 MarketPanel 的低频面板策略)。
7360
- * 安全:市场通道永不携带秘密(步骤 2 拒绝含密钥 zip,prepare 亦双保险拒绝 containsSecrets);
7361
- * 展示文本渲染前过 redact() 兜底;repoUrl 已由 validatePublishRepoUrl 拒绝 userinfo,凭据绝不拼入命令/片段。
7362
- */
7363
- /**
7364
- * 发布向导(React 壳,只装配):
7365
- * - 步骤门控用 t4 控制器 canProceed(zipSelected / validated / prepared);
7366
- * - 表单校验用 validatePublishForm / publishFormValid(行内 formError);
7367
- * - git 命令 / index 片段文本来自 buildGitPushCommands / buildIndexEntrySnippet。
7378
+ * 恒生成供应链警示行(硬不变式:任何市场下载条目确认导入前都可见)。
7379
+ * 传入 L2 manifest + 市场 URL + 下载时间;不依赖任何“来源可信”判定。
7368
7380
  */
7369
- function PublishView({ api, importApi, t, onBack }) {
7370
- const uiT = api.t;
7371
- const [stepIndex, setStepIndex] = (0, react.useState)(0);
7372
- const [fileName, setFileName] = (0, react.useState)(null);
7373
- const [uploading, setUploading] = (0, react.useState)(false);
7374
- const [zipPath, setZipPath] = (0, react.useState)(null);
7375
- const [validating, setValidating] = (0, react.useState)(false);
7376
- const [validated, setValidated] = (0, react.useState)(false);
7377
- const [validationError, setValidationError] = (0, react.useState)(null);
7378
- const [form, setForm] = (0, react.useState)(EMPTY_PUBLISH_FORM);
7379
- const [formErrors, setFormErrors] = (0, react.useState)({
7380
- itemId: null,
7381
- name: null,
7382
- repoUrl: null
7383
- });
7384
- const [generating, setGenerating] = (0, react.useState)(false);
7385
- const [prepareResult, setPrepareResult] = (0, react.useState)(null);
7386
- const [prepareError, setPrepareError] = (0, react.useState)(null);
7387
- const [error, setError] = (0, react.useState)(null);
7388
- const [copied, setCopied] = (0, react.useState)(null);
7389
- const fileInput = (0, react.useRef)(null);
7390
- const steps = publishSteps(uiT);
7391
- const currentStep = PUBLISH_STEP_IDS[stepIndex];
7392
- /** t4 状态机:zipSelected / validated / prepared 推导当前步可否前进 */
7393
- const canNext = canProceed({
7394
- step: currentStep,
7395
- zipSelected: zipPath !== null,
7396
- validated,
7397
- prepared: prepareResult !== null,
7398
- pushAcknowledged: false
7381
+ function marketItemWarnings(manifest, url, downloadedAt, t) {
7382
+ const warnings = [];
7383
+ warnings.push(t("market.supplyUnofficial"));
7384
+ if (url !== "") warnings.push(t("market.supplySource", { url }));
7385
+ if (downloadedAt !== "") warnings.push(t("market.supplyDownloadedAt", { time: downloadedAt }));
7386
+ if (manifest?.author) warnings.push(t("market.supplyAuthor", { author: manifest.author }));
7387
+ if (manifest?.provenance?.source) warnings.push(t("market.supplyProvenanceSource", { source: manifest.provenance.source }));
7388
+ if (manifest?.provenance?.note) warnings.push(t("market.supplyProvenanceNote", { note: manifest.provenance.note }));
7389
+ return warnings;
7390
+ }
7391
+ //#endregion
7392
+ //#region src/client/market/market-view.ts
7393
+ /**
7394
+ * 配置市场区块的客户端渲染装配层(m-market-ui,node 可测)。
7395
+ *
7396
+ * 设计纪律(docs/design/marketplace.md §7.2 / §9 contract 表):
7397
+ * - **共享渲染模型唯一权威 = Host 侧 `src/market/view.ts`**(marketStatusText /
7398
+ * marketListSummary / computeItemBadge / marketItemWarnings / needsReview / toMarketListItem)。
7399
+ * 本文件原样 **re-export** 这些函数(单一来源,消重,避免与后端漂移);
7400
+ * - 本文件只保留**客户端专属**的 UI 装配函数(搜索/类别过滤、详情聚合、时间格式化、
7401
+ * 供应链警示的 warn/info 着色行、条目来源徽章)——这些不属共享模型,属前端薄层。
7402
+ *
7403
+ * 安全硬约束(§1 / §7.2):供应链警示恒生成、needsReview 恒 true(re-export 自后端权威)。
7404
+ */
7405
+ /**
7406
+ * 条目来源徽章 kind(阶段 1:条目级来源仓库,docs/design/2026-08-19-market-publish-design.md §3.3):
7407
+ * - `'ok'`(官方):`item.repo` 缺省(条目与市场仓库同仓)或 `item.repo` 为官方默认地址;
7408
+ * - `'warn'`(第三方):`item.repo` 存在且非官方默认地址(条目由作者自托管仓库发布)。
7409
+ * 判定基准复用 `builtin.ts` 的 `isOfficialMarket`(固定官方地址比较):env 覆盖为预览仓库时,
7410
+ * 无 repo 条目随市场头部一并显示第三方徽章,语义自洽。纯函数、node 可测;MarketPanel 只装配。
7411
+ */
7412
+ function sourceBadgeKind(item, builtinUrl) {
7413
+ return isOfficialMarket(item.repo ?? builtinUrl) ? "ok" : "warn";
7414
+ }
7415
+ /** 类别过滤:从条目收集全部出现过的类别(用于「全部类别」下拉)。 */
7416
+ function collectCategories(items) {
7417
+ const set = /* @__PURE__ */ new Set();
7418
+ for (const it of items) for (const c of it.categories ?? []) set.add(c);
7419
+ return [...set];
7420
+ }
7421
+ /**
7422
+ * 搜索 + 类别过滤(纯函数,客户端专属)。
7423
+ * - query:对 name / author / description 做大小写不敏感子串匹配(空白 query 不过滤);
7424
+ * - category:空串表示不限类别;否则要求 categories 含该值。
7425
+ * 返回筛选后的条目(保持原始顺序)。
7426
+ */
7427
+ function filterMarketItems(items, query, category) {
7428
+ const q = query.trim().toLowerCase();
7429
+ return items.filter((it) => {
7430
+ if (category !== "" && !(it.categories ?? []).includes(category)) return false;
7431
+ if (q === "") return true;
7432
+ if (it.name.toLowerCase().includes(q)) return true;
7433
+ if ((it.author ?? "").toLowerCase().includes(q)) return true;
7434
+ if ((it.description ?? "").toLowerCase().includes(q)) return true;
7435
+ return false;
7399
7436
  });
7400
- /** 解析逗号分隔类别 → 去空白数组(空结果省略)。 */
7401
- const parseCategories = () => form.categories.split(",").map((c) => c.trim()).filter((c) => c !== "");
7402
- /** 步骤 1:选择 zip → upload 受控临时区(换选以最新文件为准,恒清空 input value)。 */
7403
- const onPickFile = async (file) => {
7404
- if (file === void 0) return;
7405
- setUploading(true);
7406
- setError(null);
7407
- setValidated(false);
7408
- setPrepareResult(null);
7437
+ }
7438
+ /**
7439
+ * 供应链警示 → warn/info 着色行(客户端专属)。**逻辑委托** Host 权威 `marketItemWarnings`
7440
+ * (恒生成非官方审核 + 来源 URL + 下载时间 + 作者/来源自述),本层只负责给「非官方审核」标 warn、
7441
+ * 其余标 info,供 UI 列表着色。硬不变式:返回恒非空(至少一条),确认导入前必经。
7442
+ */
7443
+ function marketWarningsLines(detail, url, t = zhUiT) {
7444
+ return marketItemWarnings(detail.provenance !== void 0 || detail.author !== void 0 ? {
7445
+ name: detail.name,
7446
+ author: detail.author,
7447
+ provenance: detail.provenance
7448
+ } : void 0, url, detail.downloadedAt, t).map((text, i) => ({
7449
+ kind: i === 0 ? "warn" : "info",
7450
+ text
7451
+ }));
7452
+ }
7453
+ /**
7454
+ * 聚合详情视图(客户端专属):徽章(委托共享 computeItemBadge)+ 供应链警示着色行
7455
+ * (委托共享 marketItemWarnings)+ 校验错误 + 可否导入。
7456
+ */
7457
+ function marketDetailView(detail, url, showBack, t = zhUiT) {
7458
+ const badge = computeItemBadge(detail, t);
7459
+ return {
7460
+ badge: {
7461
+ statusKind: badge.valid ? "ok" : "error",
7462
+ statusText: badge.statusText,
7463
+ sectionsText: badge.sectionsText,
7464
+ valid: badge.valid
7465
+ },
7466
+ warnings: marketWarningsLines(detail, url, t),
7467
+ errors: detail.errors ?? [],
7468
+ canImport: detail.status === "valid" && detail.sections.length > 0,
7469
+ showBack
7470
+ };
7471
+ }
7472
+ /**
7473
+ * 高风险分区(默认不导入,须逐项显式批准):
7474
+ * - pluginFiles :把远端文件写进 $DSH_HOME(可覆盖本机插件配置文件)
7475
+ * - agentInstructions :AGENTS.md 注入每个会话的全局指令(LLM 言行边界)
7476
+ * - agentPresets :agent 预设(会话 persona / 行为模板)
7477
+ * - sessions :session 文件(可带历史/敏感上下文)
7478
+ * - mcp :注册 MCP 服务器(可接入外部工具/执行能力)
7479
+ * - plugins :安装/更新插件(ExecutePlugin / Install 项,供应链最高风险)
7480
+ * 其余(settings/ui/providers/prompts/skills/workspaces/credentialsStatus…)默认勾选。
7481
+ */
7482
+ const HIGH_RISK_ADAPTERS = /* @__PURE__ */ new Set([
7483
+ "pluginFiles",
7484
+ "agentInstructions",
7485
+ "agentPresets",
7486
+ "sessions",
7487
+ "mcp",
7488
+ "plugins"
7489
+ ]);
7490
+ /** 是否为高风险分区(需逐项显式批准,默认不导入)。 */
7491
+ function isHighRiskAdapter(adapter) {
7492
+ return HIGH_RISK_ADAPTERS.has(adapter);
7493
+ }
7494
+ /** PlanItemKind 是否需要重启 DSH 生效(Install 及插件级变更)。 */
7495
+ function itemNeedsRestart(adapter, kind) {
7496
+ if (kind === "Install") return true;
7497
+ if (adapter === "plugins" || adapter === "mcp" || adapter === "agentPresets" || adapter === "agentInstructions") return true;
7498
+ return false;
7499
+ }
7500
+ /** 收集计划中出现过的分区(按 APPLY_ORDER 语义排序不重要,去重即可)。 */
7501
+ function planAdapters(plan) {
7502
+ const set = /* @__PURE__ */ new Set();
7503
+ for (const item of plan.items) set.add(item.adapter);
7504
+ return [...set];
7505
+ }
7506
+ /**
7507
+ * 默认批准表:低风险分区默认勾选(true);高风险分区默认不勾选(false,须逐项显式批准)。
7508
+ * 这是严格分层信任的默认 —— 不提供「自动信任高风险来源」的默认。
7509
+ */
7510
+ function defaultApprovals(plan) {
7511
+ const out = {};
7512
+ for (const adapter of planAdapters(plan)) out[adapter] = !isHighRiskAdapter(adapter);
7513
+ return out;
7514
+ }
7515
+ /**
7516
+ * 按批准表过滤计划 → 仅保留已批准分区的项(供 executeImportPlan 执行的子计划,subPlan)。
7517
+ * - items:仅保留 approved[adapter]===true 的项;
7518
+ * - needsRestart:按已批准项里是否有需重启者重算(不再沿用整份计划的 needsRestart);
7519
+ * - estimatedActions:仅保留已批准分区的计数;
7520
+ * - globalStrategy / missingSecrets / pathMappings:透传(导入的可选分区子集不改变这些)。
7521
+ * 返回与入参同形状的 ImportPlan,可直接交 executeImportPlan(analytic 只执行 plan.items)。
7522
+ */
7523
+ function buildApprovedPlan(plan, approvals) {
7524
+ const items = plan.items.filter((it) => approvals[it.adapter] === true);
7525
+ let needsRestart = false;
7526
+ const estimatedActions = {};
7527
+ for (const it of items) {
7528
+ if (itemNeedsRestart(it.adapter, it.kind)) needsRestart = true;
7529
+ estimatedActions[it.adapter] = (estimatedActions[it.adapter] ?? 0) + 1;
7530
+ }
7531
+ return {
7532
+ items,
7533
+ globalStrategy: plan.globalStrategy,
7534
+ pathMappings: plan.pathMappings,
7535
+ missingSecrets: plan.missingSecrets,
7536
+ needsRestart,
7537
+ estimatedActions
7538
+ };
7539
+ }
7540
+ /** 批准表摘要(确认按钮可用性 + 提示徽章数据源,纯函数)。 */
7541
+ function approvedAdapterSummary(plan, approvals) {
7542
+ const adapters = planAdapters(plan);
7543
+ let selected = 0;
7544
+ let highRiskSelected = 0;
7545
+ let highRiskTotal = 0;
7546
+ for (const a of adapters) {
7547
+ if (approvals[a] === true) selected += 1;
7548
+ if (isHighRiskAdapter(a)) {
7549
+ highRiskTotal += 1;
7550
+ if (approvals[a] === true) highRiskSelected += 1;
7551
+ }
7552
+ }
7553
+ const hasItems = plan.items.some((it) => approvals[it.adapter] === true);
7554
+ return {
7555
+ total: adapters.length,
7556
+ selected,
7557
+ canImport: hasItems,
7558
+ highRiskSelected,
7559
+ highRiskTotal
7560
+ };
7561
+ }
7562
+ /** 计划 → 分区批准列表(供详情视图逐项勾选渲染;纯函数)。 */
7563
+ function approvalRows(plan, approvals) {
7564
+ const byAdapter = /* @__PURE__ */ new Map();
7565
+ for (const item of plan.items) {
7566
+ const list = byAdapter.get(item.adapter) ?? [];
7567
+ list.push(item);
7568
+ byAdapter.set(item.adapter, list);
7569
+ }
7570
+ const rows = [];
7571
+ for (const adapter of planAdapters(plan)) {
7572
+ const items = byAdapter.get(adapter) ?? [];
7573
+ const label = items[0]?.description ?? adapter;
7574
+ rows.push({
7575
+ adapter,
7576
+ itemCount: items.length,
7577
+ highRisk: isHighRiskAdapter(adapter),
7578
+ approved: approvals[adapter] === true,
7579
+ label
7580
+ });
7581
+ }
7582
+ return rows;
7583
+ }
7584
+ //#endregion
7585
+ //#region src/client/market/MyConfigsView.tsx
7586
+ /**
7587
+ * 「我的配置」视图(设计文档 docs/design/2026-08-20-my-configs-design.md §4.6)。
7588
+ *
7589
+ * 组装「一键上传 / 查看已上传 / 一键更新 / 装回本地」:
7590
+ * - **登录卡**:未登录 → GitHub device flow 登录(复用 SyncApi.githubStart/Poll/Cancel,
7591
+ * 交互与 SyncSettingsView 一致:一次性用户码 + 授权页链接 + 轮询 + 取消,定时器卸载清理);
7592
+ * 已登录 → @login + 固定目标仓库(xiajiajun516/dsh-config-market,只读展示,无编辑入口);
7593
+ * token 失效(/me/status 401)→ 引导重新登录;
7594
+ * - **上传向导**:选 zip(复用 ConfigManagerApi.upload 受控临时区)→ analyzeImport 校验
7595
+ * (内容合法 + 无密钥,零写入)→ 精简表单(仅 name/description/categories;
7596
+ * id/author/version/updatedAt 显示「系统自动」徽章)→ meUpload 一键上传 → 结果卡
7597
+ * (PR 链接 / 仓库链接 / sha256 / 分区);
7598
+ * - **已上传列表**:条目卡片(字段 + 收录状态徽章:未收录 / PR 待审核[带 PR 链接] / 已收录,
7599
+ * 状态由 Host 侧判定经 itemStatusFromHost 桥接)+ 行操作:更新(预填信息进向导)/
7600
+ * 装回本地(复用市场下载 + 逐分区批准 + executeImportPlan 安全管道)/ 打开仓库。
7601
+ *
7602
+ * 渲染/校验模型全部来自 my-configs-view.ts + market-view.ts 纯函数(node 已测),本组件只装配;
7603
+ * 状态组件内自持(useState),非敏感切片(已上传列表 myItems + 错误)为**受控 props**:
7604
+ * 经 MarketPanel 的 commit/patch 统一镜像进模块级 runStore(market.myItems / myItemsError),
7605
+ * 切 tab 不丢;刷新后免重拉(与 MarketPanel 浏览态同单店镜像策略)。
7606
+ * 安全:token 只存宿主凭据槽;密码/表单无敏感字段;所有展示文本渲染前过 redact() 兜底;
7607
+ * 本文件不 import 任何 node 模块(纯浏览器 bundle)。
7608
+ */
7609
+ const initialGithubFlow = {
7610
+ phase: "idle",
7611
+ flowId: "",
7612
+ userCode: "",
7613
+ verificationUri: "",
7614
+ interval: 5,
7615
+ error: null
7616
+ };
7617
+ function initialWizard(mode) {
7618
+ return {
7619
+ mode,
7620
+ step: "select",
7621
+ zipPath: null,
7622
+ fileName: null,
7623
+ validating: false,
7624
+ validated: false,
7625
+ validationError: null,
7626
+ form: { ...EMPTY_MY_CONFIG_FORM },
7627
+ formErrors: { name: null },
7628
+ running: false,
7629
+ result: null,
7630
+ error: null
7631
+ };
7632
+ }
7633
+ function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsError, onMyItemsChange, onBack }) {
7634
+ const uiT = meApi.t;
7635
+ const [status, setStatus] = (0, react.useState)(null);
7636
+ const [statusLoading, setStatusLoading] = (0, react.useState)(true);
7637
+ const [statusFailed, setStatusFailed] = (0, react.useState)(false);
7638
+ const [github, setGithub] = (0, react.useState)(initialGithubFlow);
7639
+ const githubPollTimer = (0, react.useRef)(null);
7640
+ const [listLoading, setListLoading] = (0, react.useState)(false);
7641
+ const [wizard, setWizard] = (0, react.useState)(() => initialWizard("upload"));
7642
+ const [install, setInstall] = (0, react.useState)(null);
7643
+ const fileInput = (0, react.useRef)(null);
7644
+ /** 读取登录态(挂载 / 登录成功 / 状态刷新),返回 status 供后续判断 */
7645
+ const loadStatus = async () => {
7646
+ setStatusLoading(true);
7647
+ setStatusFailed(false);
7409
7648
  try {
7410
- const uploaded = await importApi.upload(file);
7411
- setZipPath(uploaded.zipPath);
7412
- setFileName(file.name);
7413
- } catch (err) {
7414
- setError(err instanceof Error ? err.message : String(err));
7649
+ const s = await meApi.meStatus();
7650
+ setStatus(s);
7651
+ return s;
7652
+ } catch {
7653
+ setStatusFailed(true);
7654
+ return null;
7415
7655
  } finally {
7416
- setUploading(false);
7656
+ setStatusLoading(false);
7417
7657
  }
7418
7658
  };
7419
- /** 步骤 2:analyzeImport dry-run(零写入)—— 无密钥 + 内容合法才放行。 */
7420
- const runValidate = async () => {
7421
- if (zipPath === null) return;
7422
- setValidating(true);
7423
- setValidationError(null);
7659
+ /** 加载已上传列表(状态上抛 MarketPanel 镜像 runStore:切 tab 不丢 / 刷新免重拉) */
7660
+ const loadItems = async (opts = {}) => {
7661
+ if (!opts.silent) setListLoading(true);
7424
7662
  try {
7425
- const analysis = await importApi.analyzeImport(zipPath);
7426
- if (analysis.secretCount > 0) {
7427
- setValidated(false);
7428
- setValidationError(t("publish.validate.secrets"));
7429
- } else if (!analysis.valid) {
7430
- setValidated(false);
7431
- setValidationError(t("publish.validate.invalid"));
7432
- } else setValidated(true);
7663
+ onMyItemsChange((await meApi.meItems()).items, null);
7433
7664
  } catch (err) {
7434
- setValidated(false);
7435
- setValidationError(err instanceof Error ? err.message : String(err));
7665
+ onMyItemsChange(myItems, err instanceof Error ? err.message : String(err));
7436
7666
  } finally {
7437
- setValidating(false);
7667
+ if (!opts.silent) setListLoading(false);
7438
7668
  }
7439
7669
  };
7440
- /** 步骤 3:表单字段更新 + 实时校验(t4 控制器)。 */
7441
- const onFormField = (field, value) => {
7442
- const next = {
7443
- ...form,
7444
- [field]: value
7670
+ (0, react.useEffect)(() => {
7671
+ let cancelled = false;
7672
+ (async () => {
7673
+ const s = await loadStatus();
7674
+ if (!cancelled && s !== null && s.loggedIn) loadItems({ silent: true });
7675
+ })();
7676
+ return () => {
7677
+ cancelled = true;
7678
+ if (githubPollTimer.current !== null) clearTimeout(githubPollTimer.current);
7445
7679
  };
7446
- setForm(next);
7447
- setFormErrors(validatePublishForm(next));
7448
- };
7449
- /** 步骤 3:POST /market/prepare 生成条目包(manifest + SHA-256 + sections)。 */
7450
- const runGenerate = async () => {
7451
- if (zipPath === null) return;
7452
- const errs = validatePublishForm(form);
7453
- setFormErrors(errs);
7454
- if (!publishFormValid(errs)) return;
7455
- const categories = parseCategories();
7456
- setGenerating(true);
7457
- setPrepareError(null);
7680
+ }, []);
7681
+ const runGithubStart = async () => {
7682
+ setGithub((g) => ({
7683
+ ...g,
7684
+ phase: "starting",
7685
+ error: null
7686
+ }));
7458
7687
  try {
7459
- const result = await api.prepare({
7460
- zipPath,
7461
- itemId: form.itemId,
7462
- name: form.name,
7463
- ...form.version !== "" ? { version: form.version } : {},
7464
- ...form.description !== "" ? { description: form.description } : {},
7465
- ...form.author !== "" ? { author: form.author } : {},
7466
- ...form.repoUrl !== "" ? { repoUrl: form.repoUrl } : {},
7467
- ...categories.length > 0 ? { categories } : {}
7688
+ const info = await syncApi.githubStart();
7689
+ setGithub({
7690
+ phase: "waiting",
7691
+ flowId: info.flowId,
7692
+ userCode: info.userCode,
7693
+ verificationUri: info.verificationUri,
7694
+ interval: info.interval,
7695
+ error: null
7468
7696
  });
7469
- setPrepareResult(result);
7697
+ scheduleGithubPoll(info.flowId, Math.max(info.interval, 1) * 1e3);
7470
7698
  } catch (err) {
7471
- setPrepareError(err instanceof Error ? err.message : String(err));
7472
- } finally {
7473
- setGenerating(false);
7699
+ setGithub((g) => ({
7700
+ ...g,
7701
+ phase: "error",
7702
+ error: err instanceof Error ? err.message : String(err)
7703
+ }));
7474
7704
  }
7475
7705
  };
7476
- /** 复制文本到剪贴板(失败降级提示手动选择)。 */
7477
- const copyText = async (label, text) => {
7706
+ const scheduleGithubPoll = (flowId, delayMs) => {
7707
+ if (githubPollTimer.current !== null) clearTimeout(githubPollTimer.current);
7708
+ githubPollTimer.current = setTimeout(() => {
7709
+ runGithubPoll(flowId);
7710
+ }, delayMs);
7711
+ };
7712
+ const runGithubPoll = async (flowId) => {
7713
+ setGithub((g) => ({
7714
+ ...g,
7715
+ phase: "polling"
7716
+ }));
7478
7717
  try {
7479
- await navigator.clipboard.writeText(text);
7480
- setCopied(label);
7481
- } catch {
7482
- setCopied(`${label}-fail`);
7718
+ const poll = await syncApi.githubPoll(flowId);
7719
+ if (poll.status === "pending") {
7720
+ setGithub((g) => ({
7721
+ ...g,
7722
+ phase: "waiting"
7723
+ }));
7724
+ scheduleGithubPoll(flowId, poll.pollDelayMs ?? Math.max(github.interval, 1) * 1e3);
7725
+ return;
7726
+ }
7727
+ const message = githubPollMessage(poll, uiT);
7728
+ if (poll.status === "success") {
7729
+ setGithub(initialGithubFlow);
7730
+ const s = await loadStatus();
7731
+ if (s !== null && s.loggedIn) loadItems({ silent: true });
7732
+ } else setGithub((g) => ({
7733
+ ...g,
7734
+ phase: "error",
7735
+ error: message
7736
+ }));
7737
+ } catch (err) {
7738
+ setGithub((g) => ({
7739
+ ...g,
7740
+ phase: "error",
7741
+ error: err instanceof Error ? err.message : String(err)
7742
+ }));
7483
7743
  }
7484
7744
  };
7485
- /** 复制反馈徽章:成功 → ok「已复制」;失败 → warn「复制失败」。 */
7486
- const copyBadge = (label) => copied === label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7487
- kind: "ok",
7488
- children: t("publish.copied")
7489
- }) : copied === `${label}-fail` ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7490
- kind: "warn",
7491
- children: t("publish.copyFailed")
7492
- }) : null;
7493
- const renderSelect = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7494
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7495
- className: config_manager_module_css_default.hint,
7496
- children: t("publish.select.hint")
7497
- }),
7498
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7499
- ref: fileInput,
7500
- type: "file",
7501
- accept: ".zip,application/zip",
7502
- className: config_manager_module_css_default.hiddenFile,
7503
- onChange: (e) => {
7504
- const picked = e.target.files?.[0];
7505
- e.target.value = "";
7506
- onPickFile(picked);
7745
+ const runGithubCancel = async () => {
7746
+ if (githubPollTimer.current !== null) {
7747
+ clearTimeout(githubPollTimer.current);
7748
+ githubPollTimer.current = null;
7749
+ }
7750
+ const flowId = github.flowId;
7751
+ setGithub(initialGithubFlow);
7752
+ if (flowId !== "") try {
7753
+ await syncApi.githubCancel(flowId);
7754
+ } catch {}
7755
+ };
7756
+ /** 步骤 1:选 zip → upload 受控临时区;upload 模式预填 name 为 zip 文件名(可改) */
7757
+ const onPickFile = async (file) => {
7758
+ if (file === void 0) return;
7759
+ setWizard((w) => ({
7760
+ ...w,
7761
+ fileName: file.name,
7762
+ error: null
7763
+ }));
7764
+ try {
7765
+ const uploaded = await importApi.upload(file);
7766
+ setWizard((w) => ({
7767
+ ...w,
7768
+ zipPath: uploaded.zipPath,
7769
+ step: "validate",
7770
+ form: w.mode === "upload" ? {
7771
+ ...w.form,
7772
+ name: file.name.replace(/\.zip$/i, "")
7773
+ } : w.form
7774
+ }));
7775
+ } catch (err) {
7776
+ setWizard((w) => ({
7777
+ ...w,
7778
+ error: err instanceof Error ? err.message : String(err)
7779
+ }));
7780
+ }
7781
+ };
7782
+ /** 步骤 2:analyzeImport dry-run(零写入)—— 无密钥 + 内容合法才放行;通过后进表单 */
7783
+ const runValidate = async () => {
7784
+ if (wizard.zipPath === null) return;
7785
+ setWizard((w) => ({
7786
+ ...w,
7787
+ validating: true,
7788
+ validationError: null
7789
+ }));
7790
+ try {
7791
+ const analysis = await importApi.analyzeImport(wizard.zipPath);
7792
+ if (analysis.secretCount > 0) setWizard((w) => ({
7793
+ ...w,
7794
+ validating: false,
7795
+ validated: false,
7796
+ validationError: t("myconfigs.upload.validateSecrets")
7797
+ }));
7798
+ else if (!analysis.valid) setWizard((w) => ({
7799
+ ...w,
7800
+ validating: false,
7801
+ validated: false,
7802
+ validationError: t("myconfigs.upload.validateInvalid")
7803
+ }));
7804
+ else setWizard((w) => ({
7805
+ ...w,
7806
+ validating: false,
7807
+ validated: true,
7808
+ step: "form"
7809
+ }));
7810
+ } catch (err) {
7811
+ setWizard((w) => ({
7812
+ ...w,
7813
+ validating: false,
7814
+ validated: false,
7815
+ validationError: err instanceof Error ? err.message : String(err)
7816
+ }));
7817
+ }
7818
+ };
7819
+ /** 表单字段更新 + 实时校验(pure 模型) */
7820
+ const onFormField = (field, value) => {
7821
+ const next = {
7822
+ ...wizard.form,
7823
+ [field]: value
7824
+ };
7825
+ setWizard((w) => ({
7826
+ ...w,
7827
+ form: next,
7828
+ formErrors: validateMyConfigForm(next, uiT)
7829
+ }));
7830
+ };
7831
+ /** 「一键上传 / 一键更新」→ meUpload / meUpdate */
7832
+ const runUpload = async () => {
7833
+ if (wizard.zipPath === null) return;
7834
+ const errs = validateMyConfigForm(wizard.form, uiT);
7835
+ setWizard((w) => ({
7836
+ ...w,
7837
+ formErrors: errs
7838
+ }));
7839
+ if (!myConfigFormValid(errs)) return;
7840
+ const categories = parseCategories(wizard.form.categories);
7841
+ const form = {
7842
+ name: wizard.form.name.trim(),
7843
+ ...wizard.form.description.trim() !== "" ? { description: wizard.form.description.trim() } : {},
7844
+ ...categories.length > 0 ? { categories } : {}
7845
+ };
7846
+ setWizard((w) => ({
7847
+ ...w,
7848
+ running: true,
7849
+ error: null,
7850
+ result: null
7851
+ }));
7852
+ try {
7853
+ const result = wizard.mode === "update" ? await meApi.meUpdate({
7854
+ zipPath: wizard.zipPath,
7855
+ form
7856
+ }) : await meApi.meUpload({
7857
+ zipPath: wizard.zipPath,
7858
+ form
7859
+ });
7860
+ setWizard((w) => ({
7861
+ ...w,
7862
+ running: false,
7863
+ result
7864
+ }));
7865
+ loadItems({ silent: true });
7866
+ } catch (err) {
7867
+ setWizard((w) => ({
7868
+ ...w,
7869
+ running: false,
7870
+ error: err instanceof Error ? err.message : String(err)
7871
+ }));
7872
+ }
7873
+ };
7874
+ /** 行操作:更新 → 打开向导,预填条目信息(name/description/categories) */
7875
+ const startUpdate = (entry) => {
7876
+ setWizard({
7877
+ ...initialWizard("update"),
7878
+ form: {
7879
+ name: entry.name,
7880
+ description: entry.description ?? "",
7881
+ categories: (entry.categories ?? []).join(", ")
7507
7882
  }
7508
- }),
7509
- fileName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7510
- className: config_manager_module_css_default.statRow,
7511
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7512
- kind: "info",
7513
- children: t("publish.select.file", { name: fileName })
7514
- })
7515
- }),
7516
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7517
- className: config_manager_module_css_default.actionRow,
7518
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7519
- variant: "primary",
7520
- disabled: uploading,
7521
- onClick: () => {
7522
- fileInput.current?.click();
7523
- },
7524
- children: uploading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("publish.select.uploading") }) : t(fileName !== null ? "publish.select.reselect" : "publish.select.browse")
7525
- })
7526
- })
7527
- ] });
7528
- const renderValidate = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7529
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7530
- className: config_manager_module_css_default.hint,
7531
- children: t("publish.validate.hint")
7532
- }),
7533
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7534
- className: config_manager_module_css_default.actionRow,
7535
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7536
- variant: "primary",
7537
- disabled: zipPath === null || validating,
7538
- onClick: () => {
7539
- runValidate();
7540
- },
7541
- children: validating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("publish.validate.running") }) : validated ? t("publish.validate.retry") : t("publish.validate.run")
7542
- })
7543
- }),
7544
- validated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7545
- kind: "ok",
7546
- children: t("publish.validate.ok")
7547
- }),
7548
- validationError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
7549
- kind: "error",
7550
- children: redact(validationError)
7551
- })
7552
- ] });
7553
- const renderForm = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7554
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7555
- className: config_manager_module_css_default.groupLabel,
7556
- children: t("publish.form.title")
7557
- }),
7558
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
7559
- label: t("publish.form.id"),
7560
- hint: t("publish.form.idHint"),
7561
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7562
- className: config_manager_module_css_default.input,
7563
- value: form.itemId,
7564
- onChange: (e) => {
7565
- onFormField("itemId", e.target.value);
7566
- }
7567
- }), formErrors.itemId !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7568
- className: config_manager_module_css_default.formError,
7569
- children: redact(formErrors.itemId)
7570
- })]
7571
- }),
7572
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
7573
- label: t("publish.form.name"),
7574
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7575
- className: config_manager_module_css_default.input,
7576
- value: form.name,
7577
- onChange: (e) => {
7578
- onFormField("name", e.target.value);
7579
- }
7580
- }), formErrors.name !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7581
- className: config_manager_module_css_default.formError,
7582
- children: redact(formErrors.name)
7583
- })]
7584
- }),
7585
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
7586
- label: t("publish.form.description"),
7587
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
7588
- className: config_manager_module_css_default.input,
7589
- value: form.description,
7590
- onChange: (e) => {
7591
- onFormField("description", e.target.value);
7592
- }
7593
- })
7594
- }),
7595
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
7596
- label: t("publish.form.author"),
7597
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7598
- className: config_manager_module_css_default.input,
7599
- value: form.author,
7600
- onChange: (e) => {
7601
- onFormField("author", e.target.value);
7602
- }
7603
- })
7604
- }),
7605
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
7606
- label: t("publish.form.version"),
7607
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7608
- className: config_manager_module_css_default.input,
7609
- value: form.version,
7610
- onChange: (e) => {
7611
- onFormField("version", e.target.value);
7612
- }
7613
- })
7614
- }),
7615
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
7616
- label: t("publish.form.categories"),
7617
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7618
- className: config_manager_module_css_default.input,
7619
- value: form.categories,
7620
- onChange: (e) => {
7621
- onFormField("categories", e.target.value);
7622
- }
7623
- })
7624
- }),
7625
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
7626
- label: t("publish.form.repoUrl"),
7627
- hint: t("publish.form.repoUrlHint"),
7628
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7629
- className: config_manager_module_css_default.input,
7630
- value: form.repoUrl,
7631
- onChange: (e) => {
7632
- onFormField("repoUrl", e.target.value);
7633
- }
7634
- }), formErrors.repoUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7635
- className: config_manager_module_css_default.formError,
7636
- children: redact(formErrors.repoUrl)
7637
- })]
7638
- }),
7639
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7640
- className: config_manager_module_css_default.actionRow,
7641
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7642
- variant: "primary",
7643
- disabled: generating || !publishFormValid(formErrors),
7644
- onClick: () => {
7645
- runGenerate();
7646
- },
7647
- children: generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("publish.form.generating") }) : t("publish.form.generate")
7648
- })
7649
- }),
7650
- prepareError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
7651
- kind: "error",
7652
- children: redact(prepareError)
7653
- }),
7654
- prepareResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
7655
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7656
- className: config_manager_module_css_default.groupLabel,
7657
- children: t("publish.prepare.title")
7658
- }),
7659
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
7660
- kind: "warn",
7661
- children: t("publish.prepare.warnings")
7662
- }),
7663
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7664
- className: config_manager_module_css_default.statRow,
7665
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7666
- kind: "info",
7667
- children: t("publish.prepare.sha256", { hash: prepareResult.sha256 })
7668
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7669
- kind: "info",
7670
- children: t("publish.prepare.sections", { sections: prepareResult.sections.join(", ") })
7671
- })]
7672
- }),
7673
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7674
- className: config_manager_module_css_default.fieldLabel,
7675
- children: t("publish.prepare.manifest")
7676
- }),
7677
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7678
- className: config_manager_module_css_default.actionRow,
7679
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7680
- onClick: () => {
7681
- copyText("manifest", prepareResult.manifestText);
7682
- },
7683
- children: t("publish.prepare.copyManifest")
7684
- }), copyBadge("manifest")]
7685
- }),
7686
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
7687
- className: config_manager_module_css_default.reportText,
7688
- children: redact(prepareResult.manifestText)
7689
- }),
7690
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7691
- className: config_manager_module_css_default.hint,
7692
- children: t("publish.prepare.dirHint", { dir: prepareResult.dir })
7693
- })
7694
- ] })
7695
- ] });
7696
- const renderPush = () => {
7697
- const repoUrl = form.repoUrl.trim();
7698
- const zipPath = prepareResult?.zipPath;
7699
- const downloadBtn = zipPath !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7700
- onClick: () => {
7701
- window.open(api.downloadPublishUrl(zipPath), "_blank");
7702
- },
7703
- children: t("publish.push.download")
7704
7883
  });
7705
- if (repoUrl === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
7706
- kind: "warn",
7707
- children: t("publish.push.repoMissing", { id: form.itemId })
7708
- }), downloadBtn !== false && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7709
- className: config_manager_module_css_default.actionRow,
7710
- children: downloadBtn
7711
- })] });
7712
- const commands = buildGitPushCommands({
7713
- repoUrl,
7714
- itemId: form.itemId,
7715
- dir: "market-items"
7884
+ };
7885
+ /** 装回本地(复用市场下载 + 逐分区批准链路;条目内容在用户自己的公开仓库,必须带 repo 来源) */
7886
+ const runDownload = async (entry) => {
7887
+ setInstall({
7888
+ itemId: entry.id,
7889
+ detail: null,
7890
+ approvals: {},
7891
+ importing: false,
7892
+ importResult: null,
7893
+ error: null
7716
7894
  });
7717
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7718
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7719
- className: config_manager_module_css_default.hint,
7720
- children: t("publish.push.hint")
7721
- }),
7895
+ try {
7896
+ const detail = await api.download(entry.id, entry.repoUrl);
7897
+ setInstall((i) => i !== null ? {
7898
+ ...i,
7899
+ detail,
7900
+ approvals: defaultApprovals(detail.plan)
7901
+ } : i);
7902
+ } catch (err) {
7903
+ setInstall((i) => i !== null ? {
7904
+ ...i,
7905
+ error: err instanceof Error ? err.message : String(err)
7906
+ } : i);
7907
+ }
7908
+ };
7909
+ const runImport = async () => {
7910
+ if (install === null || install.detail === null) return;
7911
+ const approvedPlan = buildApprovedPlan(install.detail.plan, install.approvals);
7912
+ if (approvedPlan.items.length === 0) {
7913
+ setInstall((i) => i !== null ? {
7914
+ ...i,
7915
+ error: t("detail.noApproval")
7916
+ } : i);
7917
+ return;
7918
+ }
7919
+ setInstall((i) => i !== null ? {
7920
+ ...i,
7921
+ importing: true,
7922
+ error: null
7923
+ } : i);
7924
+ try {
7925
+ const executed = await importApi.executeImportPlan(install.detail.zipPath, approvedPlan, {
7926
+ confirm: true,
7927
+ rollbackOnError: true
7928
+ });
7929
+ setInstall((i) => i !== null ? {
7930
+ ...i,
7931
+ importing: false,
7932
+ importResult: executed
7933
+ } : i);
7934
+ } catch (err) {
7935
+ setInstall((i) => i !== null ? {
7936
+ ...i,
7937
+ importing: false,
7938
+ error: err instanceof Error ? err.message : String(err)
7939
+ } : i);
7940
+ }
7941
+ };
7942
+ /** 登录视图(loading / logged-out / logged-in / token-invalid) */
7943
+ const loginView = deriveLoginState({
7944
+ loading: statusLoading,
7945
+ status,
7946
+ authFailed: statusFailed
7947
+ });
7948
+ /** GitHub 登录卡渲染模型(复用 sync-view 纯函数:状态行 / 按钮态 / 展示设备码) */
7949
+ const githubView = computeGithubLoginView(github.phase, github.userCode, github.verificationUri, github.error, uiT);
7950
+ /** 已上传条目投影(Host 状态 → 徽章模型) */
7951
+ const itemViews = (myItems ?? []).map((entry) => {
7952
+ const view = toMyItemView(entry, itemStatusFromHost(entry), uiT);
7953
+ return {
7954
+ entry,
7955
+ view,
7956
+ badge: view.badge
7957
+ };
7958
+ });
7959
+ const summary = summarizeMyItems(itemViews.map((v) => v.view));
7960
+ const autoBadges = autoFieldBadges(uiT);
7961
+ const targetRepo = `${MARKET_UPSTREAM_OWNER}/${MARKET_UPSTREAM_REPO}`;
7962
+ /** 设备码 + 授权页链接展示(waiting/polling 时) */
7963
+ const renderDeviceCode = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7964
+ className: config_manager_module_css_default.statRow,
7965
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7966
+ kind: "info",
7967
+ children: t("myconfigs.login.userCode", { code: githubView.userCode })
7968
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
7969
+ className: config_manager_module_css_default.ghostButton,
7970
+ href: githubView.verificationUri,
7971
+ target: "_blank",
7972
+ rel: "noreferrer",
7973
+ style: { textDecoration: "none" },
7974
+ children: t("myconfigs.login.openAuth")
7975
+ })]
7976
+ });
7977
+ /** 登录卡(未登录 / token 失效 → device flow;已登录 → @login + 固定目标仓库只读展示) */
7978
+ const renderLoginCard = () => {
7979
+ if (loginView.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7980
+ className: config_manager_module_css_default.statRow,
7981
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7982
+ className: config_manager_module_css_default.groupLabel,
7983
+ children: t("myconfigs.login.title")
7984
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.login.checking") })]
7985
+ }) });
7986
+ if (loginView.kind === "logged-out" || loginView.kind === "token-invalid") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7722
7987
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7723
7988
  className: config_manager_module_css_default.actionRow,
7724
7989
  children: [
7725
- downloadBtn !== false && downloadBtn,
7990
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7991
+ className: config_manager_module_css_default.groupLabel,
7992
+ children: t("myconfigs.login.title")
7993
+ }),
7726
7994
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7995
+ variant: "primary",
7996
+ disabled: !githubView.canStart,
7727
7997
  onClick: () => {
7728
- copyText("commands", commands.join("\n"));
7998
+ runGithubStart();
7729
7999
  },
7730
- children: t("publish.push.copy")
8000
+ children: githubView.startLabel
7731
8001
  }),
7732
- copyBadge("commands")
8002
+ githubView.canCancel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8003
+ disabled: github.phase === "starting",
8004
+ onClick: () => {
8005
+ runGithubCancel();
8006
+ },
8007
+ children: t("myconfigs.login.cancel")
8008
+ })
7733
8009
  ]
7734
8010
  }),
7735
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
7736
- className: config_manager_module_css_default.reportText,
7737
- children: redact(commands.join("\n"))
7738
- })
7739
- ] });
7740
- };
7741
- const renderSubmit = () => {
7742
- const repoUrl = form.repoUrl.trim();
7743
- if (repoUrl === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
7744
- kind: "warn",
7745
- children: t("publish.submit.repoMissing")
7746
- }) });
7747
- const categories = parseCategories();
7748
- const snippet = buildIndexEntrySnippet({
7749
- id: form.itemId,
7750
- name: form.name,
7751
- ...form.description !== "" ? { description: form.description } : {},
7752
- ...form.author !== "" ? { author: form.author } : {},
7753
- ...form.version !== "" ? { version: form.version } : {},
7754
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
7755
- ...categories.length > 0 ? { categories } : {},
7756
- repo: repoUrl
7757
- });
7758
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7759
8011
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7760
8012
  className: config_manager_module_css_default.hint,
7761
- children: t("publish.submit.hint")
8013
+ children: t("myconfigs.login.hint")
8014
+ }),
8015
+ loginView.kind === "token-invalid" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8016
+ kind: "warn",
8017
+ children: t("myconfigs.error.loadStatus")
7762
8018
  }),
8019
+ githubView.showCode && renderDeviceCode(),
8020
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8021
+ className: config_manager_module_css_default.statRow,
8022
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8023
+ kind: githubView.phase === "error" ? "error" : "warn",
8024
+ children: githubView.statusText
8025
+ })
8026
+ }),
8027
+ github.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8028
+ kind: "error",
8029
+ children: redact(github.error)
8030
+ })
8031
+ ] });
8032
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7763
8033
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7764
8034
  className: config_manager_module_css_default.actionRow,
7765
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7766
- onClick: () => {
7767
- copyText("snippet", snippet);
7768
- },
7769
- children: t("publish.submit.copy")
7770
- }), copyBadge("snippet")]
7771
- }),
7772
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7773
- className: config_manager_module_css_default.fieldLabel,
7774
- children: t("publish.submit.snippet")
7775
- }),
7776
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
7777
- className: config_manager_module_css_default.reportText,
7778
- children: redact(snippet)
7779
- }),
7780
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7781
- className: config_manager_module_css_default.hint,
7782
- children: t("publish.submit.fork", { url: "https://github.com/xiajiajun516/dsh-config-market" })
7783
- }),
7784
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7785
- className: config_manager_module_css_default.hint,
7786
- children: t("publish.submit.manual")
7787
- })
7788
- ] });
7789
- };
7790
- const renderStep = () => {
7791
- switch (currentStep) {
7792
- case "select": return renderSelect();
7793
- case "validate": return renderValidate();
7794
- case "prepare": return renderForm();
7795
- case "push": return renderPush();
7796
- case "submit": return renderSubmit();
7797
- }
7798
- };
7799
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7800
- className: config_manager_module_css_default.viewBody,
7801
- children: [
7802
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
7803
- title: t("publish.title"),
7804
- subtitle: t("publish.subtitle")
7805
- }),
7806
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7807
- className: config_manager_module_css_default.actionRow,
7808
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7809
- onClick: onBack,
7810
- children: t("publish.back")
7811
- })
8035
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8036
+ className: config_manager_module_css_default.groupLabel,
8037
+ children: t("myconfigs.login.title")
8038
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8039
+ kind: "ok",
8040
+ children: t("myconfigs.login.loggedInAs", { login: loginView.login })
8041
+ })]
7812
8042
  }),
7813
8043
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7814
8044
  className: config_manager_module_css_default.statRow,
7815
- children: steps.map((s, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7816
- kind: i === stepIndex ? "ok" : "info",
7817
- children: `${i + 1}. ${s.title}`
7818
- }, s.id))
8045
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8046
+ kind: "info",
8047
+ children: t("myconfigs.login.targetRepo", { repo: targetRepo })
8048
+ })
7819
8049
  }),
7820
8050
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7821
8051
  className: config_manager_module_css_default.statRow,
7822
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8052
+ children: loginView.repoExists ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8053
+ kind: "ok",
8054
+ children: t("myconfigs.login.repoReady", { repo: loginView.repoUrl })
8055
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8056
+ kind: "warn",
8057
+ children: t("myconfigs.login.repoMissing")
8058
+ })
8059
+ })
8060
+ ] });
8061
+ };
8062
+ /** 上传 / 更新向导卡片(选 zip → 校验 → 表单 → 结果) */
8063
+ const renderWizard = () => {
8064
+ const reset = () => setWizard(initialWizard(wizard.mode));
8065
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
8066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8067
+ className: config_manager_module_css_default.actionRow,
8068
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8069
+ className: config_manager_module_css_default.groupLabel,
8070
+ children: wizard.mode === "update" ? t("myconfigs.update.title") : t("myconfigs.upload.title")
8071
+ }), wizard.mode === "update" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
7823
8072
  kind: "info",
7824
- children: t("publish.stepIndicator", {
7825
- current: String(stepIndex + 1),
7826
- total: String(PUBLISH_STEP_IDS.length),
7827
- label: steps[stepIndex]?.title ?? ""
8073
+ children: t("myconfigs.update.hint")
8074
+ })]
8075
+ }),
8076
+ wizard.step === "select" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8078
+ className: config_manager_module_css_default.hint,
8079
+ children: t("myconfigs.upload.selectHint")
8080
+ }),
8081
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8082
+ ref: fileInput,
8083
+ type: "file",
8084
+ accept: ".zip,application/zip",
8085
+ className: config_manager_module_css_default.hiddenFile,
8086
+ onChange: (e) => {
8087
+ const picked = e.target.files?.[0];
8088
+ e.target.value = "";
8089
+ onPickFile(picked);
8090
+ }
8091
+ }),
8092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8093
+ className: config_manager_module_css_default.actionRow,
8094
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8095
+ variant: "primary",
8096
+ disabled: wizard.running,
8097
+ onClick: () => {
8098
+ fileInput.current?.click();
8099
+ },
8100
+ children: t("myconfigs.upload.select")
7828
8101
  })
7829
8102
  })
7830
- }),
7831
- error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8103
+ ] }),
8104
+ wizard.step === "validate" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8106
+ className: config_manager_module_css_default.hint,
8107
+ children: t("myconfigs.upload.selectHint")
8108
+ }),
8109
+ wizard.fileName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8110
+ className: config_manager_module_css_default.statRow,
8111
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8112
+ kind: "info",
8113
+ children: t("myconfigs.upload.selected", { name: wizard.fileName })
8114
+ })
8115
+ }),
8116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8117
+ className: config_manager_module_css_default.actionRow,
8118
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8119
+ variant: "primary",
8120
+ disabled: wizard.zipPath === null || wizard.validating,
8121
+ onClick: () => {
8122
+ runValidate();
8123
+ },
8124
+ children: wizard.validating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.upload.validating") }) : t("myconfigs.upload.validate")
8125
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8126
+ disabled: wizard.validating,
8127
+ onClick: reset,
8128
+ children: t("myconfigs.upload.reselect")
8129
+ })]
8130
+ }),
8131
+ wizard.validationError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8132
+ kind: "error",
8133
+ children: redact(wizard.validationError)
8134
+ })
8135
+ ] }),
8136
+ wizard.step === "form" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8137
+ wizard.validated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8138
+ className: config_manager_module_css_default.statRow,
8139
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8140
+ kind: "ok",
8141
+ children: t("myconfigs.upload.validateOk")
8142
+ })
8143
+ }),
8144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
8145
+ label: t("myconfigs.upload.form.name"),
8146
+ hint: t("myconfigs.upload.form.nameHint"),
8147
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8148
+ className: config_manager_module_css_default.input,
8149
+ value: wizard.form.name,
8150
+ onChange: (e) => {
8151
+ onFormField("name", e.target.value);
8152
+ }
8153
+ }), wizard.formErrors.name !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8154
+ className: config_manager_module_css_default.formError,
8155
+ children: redact(wizard.formErrors.name)
8156
+ })]
8157
+ }),
8158
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
8159
+ label: t("myconfigs.upload.form.description"),
8160
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
8161
+ className: config_manager_module_css_default.input,
8162
+ value: wizard.form.description,
8163
+ onChange: (e) => {
8164
+ onFormField("description", e.target.value);
8165
+ }
8166
+ })
8167
+ }),
8168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
8169
+ label: t("myconfigs.upload.form.categories"),
8170
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8171
+ className: config_manager_module_css_default.input,
8172
+ value: wizard.form.categories,
8173
+ onChange: (e) => {
8174
+ onFormField("categories", e.target.value);
8175
+ }
8176
+ })
8177
+ }),
8178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8179
+ className: config_manager_module_css_default.hint,
8180
+ children: t("myconfigs.upload.form.autoHint")
8181
+ }),
8182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8183
+ className: config_manager_module_css_default.statRow,
8184
+ children: autoBadges.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
8185
+ kind: "info",
8186
+ children: [
8187
+ b.label,
8188
+ ":",
8189
+ b.autoText
8190
+ ]
8191
+ }, b.field))
8192
+ }),
8193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8194
+ className: config_manager_module_css_default.actionRow,
8195
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8196
+ variant: "primary",
8197
+ disabled: wizard.validated !== true || wizard.running || !myConfigFormValid(wizard.formErrors),
8198
+ onClick: () => {
8199
+ runUpload();
8200
+ },
8201
+ children: wizard.running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: wizard.mode === "update" ? t("myconfigs.update.running") : t("myconfigs.upload.running") }) : wizard.mode === "update" ? t("myconfigs.update.run") : t("myconfigs.upload.run")
8202
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8203
+ disabled: wizard.running,
8204
+ onClick: reset,
8205
+ children: t("myconfigs.upload.reselect")
8206
+ })]
8207
+ })
8208
+ ] }),
8209
+ wizard.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
7832
8210
  kind: "error",
7833
- children: redact(error)
8211
+ children: redact(wizard.error)
7834
8212
  }),
7835
- renderStep(),
8213
+ wizard.result !== null && (wizard.result.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8214
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8215
+ className: config_manager_module_css_default.groupLabel,
8216
+ children: t("myconfigs.result.title")
8217
+ }),
8218
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8219
+ className: config_manager_module_css_default.statRow,
8220
+ children: [
8221
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8222
+ kind: "ok",
8223
+ children: t("myconfigs.result.version", { version: wizard.result.version })
8224
+ }),
8225
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8226
+ kind: "info",
8227
+ children: t("myconfigs.result.sha256", { hash: wizard.result.sha256 })
8228
+ }),
8229
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8230
+ kind: "info",
8231
+ children: t("myconfigs.result.sections", { sections: wizard.result.sections.join(", ") })
8232
+ })
8233
+ ]
8234
+ }),
8235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8236
+ className: config_manager_module_css_default.actionRow,
8237
+ children: [
8238
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8239
+ kind: "info",
8240
+ children: t("myconfigs.result.repo")
8241
+ }),
8242
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8243
+ href: wizard.result.repoUrl,
8244
+ children: t("myconfigs.result.openRepo")
8245
+ }),
8246
+ wizard.result.prUrl !== null && wizard.result.prUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8247
+ href: wizard.result.prUrl,
8248
+ children: wizard.result.prNumber !== null ? t("myconfigs.result.pr", { number: String(wizard.result.prNumber) }) : t("myconfigs.result.openPr")
8249
+ })
8250
+ ]
8251
+ })
8252
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8253
+ kind: "error",
8254
+ children: redact(wizard.result.error ?? ((wizard.result.warnings ?? []).join(" · ") || t("common.unknownError")))
8255
+ }))
8256
+ ] });
8257
+ };
8258
+ /** 已上传列表(条目卡片 + 状态徽章 + 行操作) */
8259
+ const renderList = () => {
8260
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
7836
8261
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7837
8262
  className: config_manager_module_css_default.actionRow,
7838
- children: [stepIndex > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7839
- onClick: () => {
7840
- setStepIndex((i) => i - 1);
7841
- },
7842
- children: t("publish.prev")
7843
- }), stepIndex < PUBLISH_STEP_IDS.length - 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7844
- variant: "primary",
7845
- disabled: !canNext,
7846
- onClick: () => {
7847
- setStepIndex((i) => i + 1);
7848
- },
7849
- children: t("publish.next")
7850
- })]
7851
- })
7852
- ]
7853
- });
7854
- }
7855
- //#endregion
7856
- //#region src/market/view.ts
7857
- /** 市场状态行文案(status 区块顶部一行)。count<=0 → 未添加提示 */
7858
- function marketStatusText(config, t) {
7859
- const count = Array.isArray(config) ? config.length : config.count;
7860
- if (count <= 0) return t("market.statusUnconfigured");
7861
- return t("market.statusConfigured", { count });
7862
- }
7863
- /** 列表摘要:总条目数 + 缓存徽章计数(供 UI 顶部小结)。无副作用。 */
7864
- function marketListSummary(items, _t) {
7865
- const fresh = items.filter((i) => i.cacheState === "fresh").length;
7866
- const cached = items.filter((i) => i.cacheState === "cached").length;
7867
- const none = items.filter((i) => i.cacheState === "none").length;
7868
- return {
7869
- total: items.length,
7870
- fresh,
7871
- cached,
7872
- none
7873
- };
7874
- }
7875
- /** 条目校验徽章:status 文案 + sections 清单文案。返回 { statusText, sectionsText, valid } */
7876
- function computeItemBadge(detail, t) {
7877
- return {
7878
- statusText: detail.status === "valid" ? t("market.detail.statusValid") : t("market.detail.statusInvalid"),
7879
- sectionsText: detail.sections.length > 0 ? t("market.detail.sections", { sections: detail.sections.join(", ") }) : t("market.detail.sectionsEmpty"),
7880
- valid: detail.status === "valid"
7881
- };
7882
- }
7883
- /**
7884
- * 恒生成供应链警示行(硬不变式:任何市场下载条目确认导入前都可见)。
7885
- * 传入 L2 manifest + 市场 URL + 下载时间;不依赖任何“来源可信”判定。
7886
- */
7887
- function marketItemWarnings(manifest, url, downloadedAt, t) {
7888
- const warnings = [];
7889
- warnings.push(t("market.supplyUnofficial"));
7890
- if (url !== "") warnings.push(t("market.supplySource", { url }));
7891
- if (downloadedAt !== "") warnings.push(t("market.supplyDownloadedAt", { time: downloadedAt }));
7892
- if (manifest?.author) warnings.push(t("market.supplyAuthor", { author: manifest.author }));
7893
- if (manifest?.provenance?.source) warnings.push(t("market.supplyProvenanceSource", { source: manifest.provenance.source }));
7894
- if (manifest?.provenance?.note) warnings.push(t("market.supplyProvenanceNote", { note: manifest.provenance.note }));
7895
- return warnings;
7896
- }
7897
- //#endregion
7898
- //#region src/client/market/market-view.ts
7899
- /**
7900
- * 配置市场区块的客户端渲染装配层(m-market-ui,node 可测)。
7901
- *
7902
- * 设计纪律(docs/design/marketplace.md §7.2 / §9 contract 表):
7903
- * - **共享渲染模型唯一权威 = Host 侧 `src/market/view.ts`**(marketStatusText /
7904
- * marketListSummary / computeItemBadge / marketItemWarnings / needsReview / toMarketListItem)。
7905
- * 本文件原样 **re-export** 这些函数(单一来源,消重,避免与后端漂移);
7906
- * - 本文件只保留**客户端专属**的 UI 装配函数(搜索/类别过滤、详情聚合、时间格式化、
7907
- * 供应链警示的 warn/info 着色行、条目来源徽章)——这些不属共享模型,属前端薄层。
7908
- *
7909
- * 安全硬约束(§1 / §7.2):供应链警示恒生成、needsReview 恒 true(re-export 自后端权威)。
7910
- */
7911
- /**
7912
- * 条目来源徽章 kind(阶段 1:条目级来源仓库,docs/design/2026-08-19-market-publish-design.md §3.3):
7913
- * - `'ok'`(官方):`item.repo` 缺省(条目与市场仓库同仓)或 `item.repo` 为官方默认地址;
7914
- * - `'warn'`(第三方):`item.repo` 存在且非官方默认地址(条目由作者自托管仓库发布)。
7915
- * 判定基准复用 `builtin.ts` 的 `isOfficialMarket`(固定官方地址比较):env 覆盖为预览仓库时,
7916
- * 无 repo 条目随市场头部一并显示第三方徽章,语义自洽。纯函数、node 可测;MarketPanel 只装配。
7917
- */
7918
- function sourceBadgeKind(item, builtinUrl) {
7919
- return isOfficialMarket(item.repo ?? builtinUrl) ? "ok" : "warn";
7920
- }
7921
- /** 类别过滤:从条目收集全部出现过的类别(用于「全部类别」下拉)。 */
7922
- function collectCategories(items) {
7923
- const set = /* @__PURE__ */ new Set();
7924
- for (const it of items) for (const c of it.categories ?? []) set.add(c);
7925
- return [...set];
7926
- }
7927
- /**
7928
- * 搜索 + 类别过滤(纯函数,客户端专属)。
7929
- * - query:对 name / author / description 做大小写不敏感子串匹配(空白 query 不过滤);
7930
- * - category:空串表示不限类别;否则要求 categories 含该值。
7931
- * 返回筛选后的条目(保持原始顺序)。
7932
- */
7933
- function filterMarketItems(items, query, category) {
7934
- const q = query.trim().toLowerCase();
7935
- return items.filter((it) => {
7936
- if (category !== "" && !(it.categories ?? []).includes(category)) return false;
7937
- if (q === "") return true;
7938
- if (it.name.toLowerCase().includes(q)) return true;
7939
- if ((it.author ?? "").toLowerCase().includes(q)) return true;
7940
- if ((it.description ?? "").toLowerCase().includes(q)) return true;
7941
- return false;
7942
- });
7943
- }
7944
- /**
7945
- * 供应链警示 → warn/info 着色行(客户端专属)。**逻辑委托** Host 权威 `marketItemWarnings`
7946
- * (恒生成非官方审核 + 来源 URL + 下载时间 + 作者/来源自述),本层只负责给「非官方审核」标 warn、
7947
- * 其余标 info,供 UI 列表着色。硬不变式:返回恒非空(至少一条),确认导入前必经。
7948
- */
7949
- function marketWarningsLines(detail, url, t = zhUiT) {
7950
- return marketItemWarnings(detail.provenance !== void 0 || detail.author !== void 0 ? {
7951
- name: detail.name,
7952
- author: detail.author,
7953
- provenance: detail.provenance
7954
- } : void 0, url, detail.downloadedAt, t).map((text, i) => ({
7955
- kind: i === 0 ? "warn" : "info",
7956
- text
7957
- }));
7958
- }
7959
- /**
7960
- * 聚合详情视图(客户端专属):徽章(委托共享 computeItemBadge)+ 供应链警示着色行
7961
- * (委托共享 marketItemWarnings)+ 校验错误 + 可否导入。
7962
- */
7963
- function marketDetailView(detail, url, showBack, t = zhUiT) {
7964
- const badge = computeItemBadge(detail, t);
7965
- return {
7966
- badge: {
7967
- statusKind: badge.valid ? "ok" : "error",
7968
- statusText: badge.statusText,
7969
- sectionsText: badge.sectionsText,
7970
- valid: badge.valid
7971
- },
7972
- warnings: marketWarningsLines(detail, url, t),
7973
- errors: detail.errors ?? [],
7974
- canImport: detail.status === "valid" && detail.sections.length > 0,
7975
- showBack
7976
- };
7977
- }
7978
- /**
7979
- * 高风险分区(默认不导入,须逐项显式批准):
7980
- * - pluginFiles :把远端文件写进 $DSH_HOME(可覆盖本机插件配置文件)
7981
- * - agentInstructions :AGENTS.md 注入每个会话的全局指令(LLM 言行边界)
7982
- * - agentPresets :agent 预设(会话 persona / 行为模板)
7983
- * - sessions :session 文件(可带历史/敏感上下文)
7984
- * - mcp :注册 MCP 服务器(可接入外部工具/执行能力)
7985
- * - plugins :安装/更新插件(ExecutePlugin / Install 项,供应链最高风险)
7986
- * 其余(settings/ui/providers/prompts/skills/workspaces/credentialsStatus…)默认勾选。
7987
- */
7988
- const HIGH_RISK_ADAPTERS = /* @__PURE__ */ new Set([
7989
- "pluginFiles",
7990
- "agentInstructions",
7991
- "agentPresets",
7992
- "sessions",
7993
- "mcp",
7994
- "plugins"
7995
- ]);
7996
- /** 是否为高风险分区(需逐项显式批准,默认不导入)。 */
7997
- function isHighRiskAdapter(adapter) {
7998
- return HIGH_RISK_ADAPTERS.has(adapter);
7999
- }
8000
- /** PlanItemKind 是否需要重启 DSH 生效(Install 及插件级变更)。 */
8001
- function itemNeedsRestart(adapter, kind) {
8002
- if (kind === "Install") return true;
8003
- if (adapter === "plugins" || adapter === "mcp" || adapter === "agentPresets" || adapter === "agentInstructions") return true;
8004
- return false;
8005
- }
8006
- /** 收集计划中出现过的分区(按 APPLY_ORDER 语义排序不重要,去重即可)。 */
8007
- function planAdapters(plan) {
8008
- const set = /* @__PURE__ */ new Set();
8009
- for (const item of plan.items) set.add(item.adapter);
8010
- return [...set];
8011
- }
8012
- /**
8013
- * 默认批准表:低风险分区默认勾选(true);高风险分区默认不勾选(false,须逐项显式批准)。
8014
- * 这是严格分层信任的默认 —— 不提供「自动信任高风险来源」的默认。
8015
- */
8016
- function defaultApprovals(plan) {
8017
- const out = {};
8018
- for (const adapter of planAdapters(plan)) out[adapter] = !isHighRiskAdapter(adapter);
8019
- return out;
8020
- }
8021
- /**
8022
- * 按批准表过滤计划 → 仅保留已批准分区的项(供 executeImportPlan 执行的子计划,subPlan)。
8023
- * - items:仅保留 approved[adapter]===true 的项;
8024
- * - needsRestart:按已批准项里是否有需重启者重算(不再沿用整份计划的 needsRestart);
8025
- * - estimatedActions:仅保留已批准分区的计数;
8026
- * - globalStrategy / missingSecrets / pathMappings:透传(导入的可选分区子集不改变这些)。
8027
- * 返回与入参同形状的 ImportPlan,可直接交 executeImportPlan(analytic 只执行 plan.items)。
8028
- */
8029
- function buildApprovedPlan(plan, approvals) {
8030
- const items = plan.items.filter((it) => approvals[it.adapter] === true);
8031
- let needsRestart = false;
8032
- const estimatedActions = {};
8033
- for (const it of items) {
8034
- if (itemNeedsRestart(it.adapter, it.kind)) needsRestart = true;
8035
- estimatedActions[it.adapter] = (estimatedActions[it.adapter] ?? 0) + 1;
8036
- }
8037
- return {
8038
- items,
8039
- globalStrategy: plan.globalStrategy,
8040
- pathMappings: plan.pathMappings,
8041
- missingSecrets: plan.missingSecrets,
8042
- needsRestart,
8043
- estimatedActions
8263
+ children: [
8264
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8265
+ className: config_manager_module_css_default.groupLabel,
8266
+ children: t("myconfigs.list.title")
8267
+ }),
8268
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8269
+ disabled: listLoading,
8270
+ onClick: () => {
8271
+ loadItems();
8272
+ },
8273
+ children: listLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.list.loading") }) : t("myconfigs.list.refresh")
8274
+ }),
8275
+ myItems !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8276
+ kind: "info",
8277
+ children: t("myconfigs.list.summary", {
8278
+ total: String(summary.total),
8279
+ listed: String(summary.listed),
8280
+ pending: String(summary.pendingPr),
8281
+ none: String(summary.notListed)
8282
+ })
8283
+ })
8284
+ ]
8285
+ }),
8286
+ myItemsError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8287
+ kind: "error",
8288
+ children: redact(myItemsError)
8289
+ }),
8290
+ listLoading && myItems === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8291
+ className: config_manager_module_css_default.statRow,
8292
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.list.loading") })
8293
+ }),
8294
+ !listLoading && myItems !== null && myItems.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, { children: t("myconfigs.list.empty") }),
8295
+ !listLoading && itemViews.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8296
+ className: config_manager_module_css_default.snapshotList,
8297
+ children: itemViews.map(({ entry, view, badge }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8298
+ className: config_manager_module_css_default.statRow,
8299
+ style: { paddingTop: 4 },
8300
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8301
+ style: {
8302
+ flex: 1,
8303
+ minWidth: 0
8304
+ },
8305
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8306
+ className: config_manager_module_css_default.conflictHead,
8307
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8308
+ className: config_manager_module_css_default.conflictId,
8309
+ children: view.name
8310
+ }), view.version !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8311
+ kind: "info",
8312
+ children: view.version
8313
+ })]
8314
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8315
+ className: config_manager_module_css_default.statRow,
8316
+ children: [
8317
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8318
+ kind: badge.kind,
8319
+ children: badge.text
8320
+ }),
8321
+ view.author !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8322
+ kind: "info",
8323
+ children: view.author
8324
+ }),
8325
+ view.updatedAt !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8326
+ kind: "info",
8327
+ children: view.updatedAt
8328
+ }),
8329
+ view.categories.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8330
+ kind: "info",
8331
+ children: c
8332
+ }, c))
8333
+ ]
8334
+ })]
8335
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8336
+ className: config_manager_module_css_default.rowActions,
8337
+ children: [
8338
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8339
+ onClick: () => {
8340
+ startUpdate(entry);
8341
+ },
8342
+ children: t("myconfigs.item.update")
8343
+ }),
8344
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8345
+ disabled: install !== null && install.itemId === view.id && install.detail === null,
8346
+ onClick: () => {
8347
+ runDownload(entry);
8348
+ },
8349
+ children: t("myconfigs.item.install")
8350
+ }),
8351
+ entry.repoUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8352
+ href: entry.repoUrl,
8353
+ children: t("myconfigs.list.openRepo")
8354
+ }),
8355
+ badge.kind === "warn" && badge.prUrl !== void 0 && badge.prUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8356
+ href: badge.prUrl,
8357
+ children: t("myconfigs.item.openPr")
8358
+ })
8359
+ ]
8360
+ })]
8361
+ }, view.id))
8362
+ })
8363
+ ] });
8044
8364
  };
8045
- }
8046
- /** 批准表摘要(确认按钮可用性 + 提示徽章数据源,纯函数)。 */
8047
- function approvedAdapterSummary(plan, approvals) {
8048
- const adapters = planAdapters(plan);
8049
- let selected = 0;
8050
- let highRiskSelected = 0;
8051
- let highRiskTotal = 0;
8052
- for (const a of adapters) {
8053
- if (approvals[a] === true) selected += 1;
8054
- if (isHighRiskAdapter(a)) {
8055
- highRiskTotal += 1;
8056
- if (approvals[a] === true) highRiskSelected += 1;
8057
- }
8058
- }
8059
- const hasItems = plan.items.some((it) => approvals[it.adapter] === true);
8060
- return {
8061
- total: adapters.length,
8062
- selected,
8063
- canImport: hasItems,
8064
- highRiskSelected,
8065
- highRiskTotal
8365
+ /** 装回本地:下载 + 逐分区批准 + 执行导入(复用市场安全管道 + market-view 纯模型) */
8366
+ const renderInstall = () => {
8367
+ if (install === null) return null;
8368
+ const { detail } = install;
8369
+ const approvalList = detail !== null ? approvalRows(detail.plan, install.approvals) : [];
8370
+ const approvalSummary = detail !== null ? approvedAdapterSummary(detail.plan, install.approvals) : null;
8371
+ const detailView = detail !== null ? marketDetailView(detail, detail.repo ?? entryRepoUrl(install.itemId), true, uiT) : null;
8372
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
8373
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8374
+ className: config_manager_module_css_default.actionRow,
8375
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8376
+ className: config_manager_module_css_default.groupLabel,
8377
+ children: [
8378
+ t("detail.title"),
8379
+ ":",
8380
+ install.itemId
8381
+ ]
8382
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8383
+ onClick: () => {
8384
+ setInstall(null);
8385
+ },
8386
+ children: t("detail.back")
8387
+ })]
8388
+ }),
8389
+ install.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8390
+ kind: "error",
8391
+ children: redact(install.error)
8392
+ }),
8393
+ detail === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8394
+ className: config_manager_module_css_default.statRow,
8395
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") })
8396
+ }),
8397
+ detail !== null && detailView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8398
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8399
+ kind: "warn",
8400
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.needReview") })
8401
+ }),
8402
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8403
+ className: config_manager_module_css_default.statRow,
8404
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8405
+ kind: detailView.badge.valid ? "ok" : "error",
8406
+ children: detailView.badge.statusText
8407
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8408
+ kind: "info",
8409
+ children: detailView.badge.sectionsText
8410
+ })]
8411
+ }),
8412
+ approvalList.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
8413
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8414
+ className: config_manager_module_css_default.groupLabel,
8415
+ children: t("detail.approval.title")
8416
+ }),
8417
+ approvalSummary !== null && approvalSummary.highRiskTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
8418
+ kind: "warn",
8419
+ children: t("detail.approval.highRiskHint")
8420
+ }),
8421
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8422
+ className: config_manager_module_css_default.conflictList,
8423
+ children: approvalList.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
8424
+ checked: row.approved,
8425
+ onChange: (checked) => {
8426
+ setInstall((i) => i !== null ? {
8427
+ ...i,
8428
+ approvals: {
8429
+ ...i.approvals,
8430
+ [row.adapter]: checked
8431
+ }
8432
+ } : i);
8433
+ },
8434
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
8435
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8436
+ className: config_manager_module_css_default.conflictId,
8437
+ children: row.adapter
8438
+ }),
8439
+ " ",
8440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8441
+ kind: row.highRisk ? "warn" : "info",
8442
+ children: row.highRisk ? t("detail.approval.requiresApproval") : t("detail.approval.safe")
8443
+ }),
8444
+ " ",
8445
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8446
+ kind: "info",
8447
+ children: row.label
8448
+ })
8449
+ ] })
8450
+ }, row.adapter))
8451
+ }),
8452
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8453
+ className: config_manager_module_css_default.statRow,
8454
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
8455
+ kind: approvalSummary !== null && approvalSummary.canImport ? "ok" : "warn",
8456
+ children: approvalSummary !== null ? t("detail.approval.count", {
8457
+ selected: String(approvalSummary.selected),
8458
+ total: String(approvalSummary.total)
8459
+ }) : ""
8460
+ })
8461
+ })
8462
+ ] }),
8463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8464
+ className: config_manager_module_css_default.actionRow,
8465
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8466
+ variant: "primary",
8467
+ disabled: install.importing || approvalSummary !== null && !approvalSummary.canImport,
8468
+ onClick: () => {
8469
+ runImport();
8470
+ },
8471
+ children: install.importing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("detail.import")
8472
+ })
8473
+ })
8474
+ ] }),
8475
+ install.importResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Banner, {
8476
+ kind: install.importResult.ok ? "ok" : "error",
8477
+ children: [install.importResult.ok ? `导入完成:${install.importResult.executed.filter((e) => e.status === "ok").length} 项写入` : `导入失败(${install.importResult.executed.filter((e) => e.status === "failed").length} 项失败)`, install.importResult.needsRestart && " · 部分改动需重启 DSH 后生效"]
8478
+ })
8479
+ ] });
8066
8480
  };
8067
- }
8068
- /** 计划 → 分区批准列表(供详情视图逐项勾选渲染;纯函数)。 */
8069
- function approvalRows(plan, approvals) {
8070
- const byAdapter = /* @__PURE__ */ new Map();
8071
- for (const item of plan.items) {
8072
- const list = byAdapter.get(item.adapter) ?? [];
8073
- list.push(item);
8074
- byAdapter.set(item.adapter, list);
8075
- }
8076
- const rows = [];
8077
- for (const adapter of planAdapters(plan)) {
8078
- const items = byAdapter.get(adapter) ?? [];
8079
- const label = items[0]?.description ?? adapter;
8080
- rows.push({
8081
- adapter,
8082
- itemCount: items.length,
8083
- highRisk: isHighRiskAdapter(adapter),
8084
- approved: approvals[adapter] === true,
8085
- label
8086
- });
8481
+ /** 装回本地详情展示用的仓库 URL(供应链警示来源行;取条目 repoUrl 兜底固定目标仓库) */
8482
+ function entryRepoUrl(itemId) {
8483
+ return (myItems ?? []).find((e) => e.id === itemId)?.repoUrl ?? `https://github.com/xiajiajun516/dsh-config-market`;
8087
8484
  }
8088
- return rows;
8485
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8486
+ className: config_manager_module_css_default.viewBody,
8487
+ children: [
8488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8489
+ className: config_manager_module_css_default.actionRow,
8490
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8491
+ onClick: onBack,
8492
+ children: t("myconfigs.back")
8493
+ })
8494
+ }),
8495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
8496
+ title: t("myconfigs.tab.myconfigs"),
8497
+ subtitle: t("myconfigs.login.hint")
8498
+ }),
8499
+ renderLoginCard(),
8500
+ loginView.kind === "logged-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8501
+ renderWizard(),
8502
+ renderList(),
8503
+ renderInstall()
8504
+ ] })
8505
+ ]
8506
+ });
8089
8507
  }
8090
8508
  //#endregion
8091
8509
  //#region src/client/market/MarketPanel.tsx
@@ -8110,6 +8528,9 @@ function approvalRows(plan, approvals) {
8110
8528
  * 内容一律视为不可信,确认导入前 supply-chain 警示可见 & needsReview 恒 true(不允许默认信任)。
8111
8529
  */
8112
8530
  const initial$1 = {
8531
+ subView: "browse",
8532
+ myItems: null,
8533
+ myItemsError: null,
8113
8534
  loading: true,
8114
8535
  loadError: null,
8115
8536
  market: null,
@@ -8134,6 +8555,9 @@ function initFromStore() {
8134
8555
  const s = runStore.getSnapshot().market;
8135
8556
  return {
8136
8557
  ...initial$1,
8558
+ subView: s.subView,
8559
+ myItems: s.myItems,
8560
+ myItemsError: s.myItemsError,
8137
8561
  search: s.search,
8138
8562
  category: s.category,
8139
8563
  items: s.items,
@@ -8144,10 +8568,8 @@ function initFromStore() {
8144
8568
  loadError: s.loadError
8145
8569
  };
8146
8570
  }
8147
- function MarketPanel({ api, importApi, t }) {
8571
+ function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
8148
8572
  const uiT = api.t;
8149
- /** 发布向导开关(组件内状态,不进 sessionStorage —— 发布为一次性低频率流程) */
8150
- const [publishOpen, setPublishOpen] = (0, react.useState)(false);
8151
8573
  const [state, setState] = (0, react.useState)(initFromStore);
8152
8574
  /** 最新 state 镜像(commit/卸载 flush 读取,避免闭包过期值) */
8153
8575
  const stateRef = (0, react.useRef)(state);
@@ -8243,14 +8665,14 @@ function MarketPanel({ api, importApi, t }) {
8243
8665
  });
8244
8666
  }
8245
8667
  };
8246
- /** 下载 + 校验单条目 → dry-run 详情预览(零写入) */
8247
- const runDownload = async (itemId) => {
8668
+ /** 下载 + 校验单条目 → dry-run 详情预览(零写入)。自托管条目(带 repo)必须携带来源仓库。 */
8669
+ const runDownload = async (item) => {
8248
8670
  patch({
8249
- downloadingId: itemId,
8671
+ downloadingId: item.id,
8250
8672
  error: null
8251
8673
  });
8252
8674
  try {
8253
- const detail = await api.download(itemId);
8675
+ const detail = await api.download(item.id, item.repo);
8254
8676
  patch({
8255
8677
  downloadingId: null,
8256
8678
  detail,
@@ -8296,7 +8718,7 @@ function MarketPanel({ api, importApi, t }) {
8296
8718
  const filtered = filterMarketItems(state.items, state.search, state.category);
8297
8719
  const summary = marketListSummary(state.items, uiT);
8298
8720
  const categories = collectCategories(state.items);
8299
- const detailView = state.detail !== null ? marketDetailView(state.detail, marketUrl, state.items.length > 0 || state.detail.status !== "valid", uiT) : null;
8721
+ const detailView = state.detail !== null ? marketDetailView(state.detail, state.detail.repo ?? marketUrl, state.items.length > 0 || state.detail.status !== "valid", uiT) : null;
8300
8722
  const approvalList = state.detail !== null ? approvalRows(state.detail.plan, state.approvals) : [];
8301
8723
  const approvalSummary = state.detail !== null ? approvedAdapterSummary(state.detail.plan, state.approvals) : null;
8302
8724
  const cacheLabel = (cacheState) => {
@@ -8304,14 +8726,48 @@ function MarketPanel({ api, importApi, t }) {
8304
8726
  if (cacheState === "fresh") return t("list.cacheFresh");
8305
8727
  return t("list.cacheNone");
8306
8728
  };
8307
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8729
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8308
8730
  className: config_manager_module_css_default.viewBody,
8309
- children: publishOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PublishView, {
8731
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8732
+ className: config_manager_module_css_default.modeTabs,
8733
+ role: "tablist",
8734
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8735
+ type: "button",
8736
+ role: "tab",
8737
+ "aria-selected": state.subView === "browse",
8738
+ "data-active": state.subView === "browse" ? "" : void 0,
8739
+ className: config_manager_module_css_default.modeTab,
8740
+ onClick: () => {
8741
+ patch({ subView: "browse" });
8742
+ },
8743
+ children: t("myconfigs.tab.browse")
8744
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8745
+ type: "button",
8746
+ role: "tab",
8747
+ "aria-selected": state.subView === "myconfigs",
8748
+ "data-active": state.subView === "myconfigs" ? "" : void 0,
8749
+ className: config_manager_module_css_default.modeTab,
8750
+ onClick: () => {
8751
+ patch({ subView: "myconfigs" });
8752
+ },
8753
+ children: t("myconfigs.tab.myconfigs")
8754
+ })]
8755
+ }), state.subView === "myconfigs" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MyConfigsView, {
8756
+ meApi: myConfigsApi,
8310
8757
  api,
8311
8758
  importApi,
8759
+ syncApi,
8312
8760
  t,
8761
+ myItems: state.myItems,
8762
+ myItemsError: state.myItemsError,
8763
+ onMyItemsChange: (items, error) => {
8764
+ patch({
8765
+ myItems: items,
8766
+ myItemsError: error
8767
+ });
8768
+ },
8313
8769
  onBack: () => {
8314
- setPublishOpen(false);
8770
+ patch({ subView: "browse" });
8315
8771
  }
8316
8772
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8317
8773
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
@@ -8357,29 +8813,20 @@ function MarketPanel({ api, importApi, t }) {
8357
8813
  }),
8358
8814
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8359
8815
  className: config_manager_module_css_default.actionRow,
8360
- children: [
8361
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8362
- variant: "primary",
8363
- disabled: state.refreshing || state.importing,
8364
- onClick: () => {
8365
- runRefresh();
8366
- },
8367
- children: state.refreshing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.refreshing") }) : t("config.refresh")
8368
- }),
8369
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8370
- disabled: state.browsing,
8371
- onClick: () => {
8372
- runBrowse();
8373
- },
8374
- children: state.browsing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") }) : t("list.browse")
8375
- }),
8376
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8377
- onClick: () => {
8378
- setPublishOpen(true);
8379
- },
8380
- children: t("publish.title")
8381
- })
8382
- ]
8816
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8817
+ variant: "primary",
8818
+ disabled: state.refreshing || state.importing,
8819
+ onClick: () => {
8820
+ runRefresh();
8821
+ },
8822
+ children: state.refreshing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.refreshing") }) : t("config.refresh")
8823
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8824
+ disabled: state.browsing,
8825
+ onClick: () => {
8826
+ runBrowse();
8827
+ },
8828
+ children: state.browsing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") }) : t("list.browse")
8829
+ })]
8383
8830
  }),
8384
8831
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8385
8832
  className: config_manager_module_css_default.statRow,
@@ -8613,7 +9060,7 @@ function MarketPanel({ api, importApi, t }) {
8613
9060
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8614
9061
  disabled: state.downloadingId === it.id,
8615
9062
  onClick: () => {
8616
- runDownload(it.id);
9063
+ runDownload(it);
8617
9064
  },
8618
9065
  children: state.downloadingId === it.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("list.download")
8619
9066
  })]
@@ -8621,7 +9068,7 @@ function MarketPanel({ api, importApi, t }) {
8621
9068
  })
8622
9069
  })
8623
9070
  ] })
8624
- ] })
9071
+ ] })]
8625
9072
  });
8626
9073
  }
8627
9074
  //#endregion
@@ -8836,7 +9283,7 @@ function AboutPanel({ api, t }) {
8836
9283
  * 所有 tab(主视图 view + 低频面板 panel)状态都在模块级 store(切 tab/刷新不丢);
8837
9284
  * 面板内部状态由各视图镜像进 store(Sync/Market/Snapshots),敏感字段白名单剔除。
8838
9285
  */
8839
- function ConfigManagerSection({ api, syncApi, syncT, marketApi, marketT, t }) {
9286
+ function ConfigManagerSection({ api, syncApi, syncT, marketApi, myConfigsApi, marketT, t }) {
8840
9287
  const state = (0, react.useSyncExternalStore)(runStore.subscribe, runStore.getSnapshot);
8841
9288
  const view = state.view;
8842
9289
  const panel = state.panel;
@@ -8941,6 +9388,8 @@ function ConfigManagerSection({ api, syncApi, syncT, marketApi, marketT, t }) {
8941
9388
  t
8942
9389
  }) : panel === "market" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketPanel, {
8943
9390
  api: marketApi,
9391
+ myConfigsApi,
9392
+ syncApi,
8944
9393
  importApi: api,
8945
9394
  t: marketT
8946
9395
  }) : panel === "sync" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SyncSettingsView, {
@@ -9595,7 +10044,7 @@ const MARKET_API = {
9595
10044
  /** 市场请求超时(ms):git 拉取可能较慢,与 Host 半 ROUTE_TIMEOUT_MS 对齐量级 */
9596
10045
  const MARKET_TIMEOUT_MS = 300 * 1e3;
9597
10046
  /** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
9598
- async function readJson(response, t) {
10047
+ async function readJson$1(response, t) {
9599
10048
  const notMountedMessage = t("error.notMounted");
9600
10049
  let body;
9601
10050
  try {
@@ -9608,11 +10057,11 @@ async function readJson(response, t) {
9608
10057
  return body;
9609
10058
  }
9610
10059
  /** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
9611
- async function postJson(path, body, t) {
10060
+ async function postJson$1(path, body, t) {
9612
10061
  const controller = new AbortController();
9613
10062
  const timer = setTimeout(() => controller.abort(), MARKET_TIMEOUT_MS);
9614
10063
  try {
9615
- return await readJson(await fetch(path, {
10064
+ return await readJson$1(await fetch(path, {
9616
10065
  method: "POST",
9617
10066
  headers: { "content-type": "application/json" },
9618
10067
  body: JSON.stringify(body),
@@ -9633,25 +10082,31 @@ var MarketApi = class {
9633
10082
  }
9634
10083
  /** 读取内置市场摘要(条目数 / 最近拉取时间;无任何凭据) */
9635
10084
  async status() {
9636
- return readJson(await fetch(MARKET_API.status), this.t);
10085
+ return readJson$1(await fetch(MARKET_API.status), this.t);
9637
10086
  }
9638
10087
  /** 拉取市场最新 index.json(内置单市场;返回目录条目 + 市场缓存摘要) */
9639
10088
  async refresh() {
9640
- return postJson(MARKET_API.refresh, {}, this.t);
10089
+ return postJson$1(MARKET_API.refresh, {}, this.t);
9641
10090
  }
9642
10091
  /** 浏览内置市场(合并 index + 本地缓存状态 → 条目列表带 cacheState) */
9643
10092
  async browse() {
9644
- return postJson(MARKET_API.browse, {}, this.t);
10093
+ return postJson$1(MARKET_API.browse, {}, this.t);
9645
10094
  }
9646
10095
  /** 下载 + 校验单条目(dry-run 预览:拉取 → §6 校验 → analyzeImport → createImportPlan)。
10096
+ * repo 可选:条目来源仓库(作者自托管)。自托管条目(官方 index 带 repo 引用、或「我的配置」
10097
+ * 未收录条目)内容文件在作者自己的公开仓库,必须显式传 repo 才能从正确来源拉取;
10098
+ * 缺省 = 市场仓库(官方同仓条目)。
9647
10099
  * 真正落盘由用户对预览确认后走现有 executeImportPlan(confirm:true 安全阀 + 回滚)。 */
9648
- async download(itemId) {
9649
- return postJson(MARKET_API.download, { itemId }, this.t);
10100
+ async download(itemId, repo) {
10101
+ return postJson$1(MARKET_API.download, {
10102
+ itemId,
10103
+ ...repo !== void 0 && repo !== "" ? { repo } : {}
10104
+ }, this.t);
9650
10105
  }
9651
10106
  /** 发布向导:由「上传 zip + 用户填写元数据」生成市场条目包(L2 manifest + SHA-256 + sections),
9652
10107
  * 供 UI 展示/复制与引导推送。零写入配置(发布目录在受控临时区);不含任何凭据字段。 */
9653
10108
  async prepare(payload) {
9654
- return postJson(MARKET_API.prepare, payload, this.t);
10109
+ return postJson$1(MARKET_API.prepare, payload, this.t);
9655
10110
  }
9656
10111
  /** 发布包下载 URL(受控临时区 zip;经宿主 /download 端点,无凭据、无写操作) */
9657
10112
  downloadPublishUrl(zipPath) {
@@ -9659,6 +10114,75 @@ var MarketApi = class {
9659
10114
  }
9660
10115
  };
9661
10116
  //#endregion
10117
+ //#region src/client/market/my-configs-api.ts
10118
+ /** 「我的配置」端点常量(与 Host 半 API 常量保持一致) */
10119
+ const MY_CONFIGS_API = {
10120
+ base: "/api/dsh-config-manager/me",
10121
+ status: "/api/dsh-config-manager/me/status",
10122
+ upload: "/api/dsh-config-manager/me/upload",
10123
+ items: "/api/dsh-config-manager/me/items",
10124
+ update: "/api/dsh-config-manager/me/update"
10125
+ };
10126
+ /** 「我的配置」请求超时(ms):上传/更新含 git clone/push/fork/PR,与 market/sync 对齐量级 */
10127
+ const MY_CONFIGS_TIMEOUT_MS = 300 * 1e3;
10128
+ /** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
10129
+ async function readJson(response, t) {
10130
+ const notMountedMessage = t("error.notMounted");
10131
+ let body;
10132
+ try {
10133
+ body = await response.json();
10134
+ } catch {
10135
+ if (response.status === 404) throw new ConfigManagerApiError(notMountedMessage);
10136
+ throw new ConfigManagerApiError(t("error.httpInvalidJson", { status: String(response.status) }));
10137
+ }
10138
+ if (!response.ok) throw new ConfigManagerApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : response.status === 404 ? notMountedMessage : `HTTP ${response.status}`);
10139
+ return body;
10140
+ }
10141
+ /** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
10142
+ async function postJson(path, body, t) {
10143
+ const controller = new AbortController();
10144
+ const timer = setTimeout(() => controller.abort(), MY_CONFIGS_TIMEOUT_MS);
10145
+ try {
10146
+ return await readJson(await fetch(path, {
10147
+ method: "POST",
10148
+ headers: { "content-type": "application/json" },
10149
+ body: JSON.stringify(body),
10150
+ signal: controller.signal
10151
+ }), t);
10152
+ } catch (err) {
10153
+ if (controller.signal.aborted) throw new ConfigManagerApiError(t("error.syncTimeout", { minutes: String(Math.round(MY_CONFIGS_TIMEOUT_MS / 6e4)) }));
10154
+ throw err;
10155
+ } finally {
10156
+ clearTimeout(timer);
10157
+ }
10158
+ }
10159
+ /**
10160
+ * 「我的配置」浏览器半数据入口(MarketPanel 的「我的配置」子视图注入业务面)。
10161
+ * 登录(GitHub device flow)不在此类 —— 复用 SyncApi.githubStart/githubPoll/githubCancel。
10162
+ */
10163
+ var MyConfigsApi = class {
10164
+ t;
10165
+ constructor(t = zhUiT) {
10166
+ this.t = t;
10167
+ }
10168
+ /** 读取登录态 + 用户配置仓库状态(token 失效 → Host 返回 401/未登录,UI 引导重新登录) */
10169
+ async meStatus() {
10170
+ return postJson(MY_CONFIGS_API.status, {}, this.t);
10171
+ }
10172
+ /** 一键上传:校验 → 建/复用仓库 → 写入用户仓库 → fork → 改官方 index → 提收录 PR */
10173
+ async meUpload(payload) {
10174
+ return postJson(MY_CONFIGS_API.upload, payload, this.t);
10175
+ }
10176
+ /** 读取已上传条目列表(用户仓库 index.json;每条目含 Host 侧判定的收录状态) */
10177
+ async meItems() {
10178
+ return postJson(MY_CONFIGS_API.items, {}, this.t);
10179
+ }
10180
+ /** 一键更新:version 自动 +1,复用/重开收录 PR */
10181
+ async meUpdate(payload) {
10182
+ return postJson(MY_CONFIGS_API.update, payload, this.t);
10183
+ }
10184
+ };
10185
+ //#endregion
9662
10186
  //#region src/client/market/market-locales.ts
9663
10187
  /**
9664
10188
  * 配置市场区块(config-manager-market)表面文案:zh 为源语言,en 镜像每个键。
@@ -9712,61 +10236,62 @@ const zh = {
9712
10236
  "detail.approval.safe": "可导入",
9713
10237
  "detail.approval.count": "已批准 {selected}/{total} 分区",
9714
10238
  "detail.noApproval": "未批准任何分区,无法导入",
9715
- "publish.title": "发布到市场",
9716
- "publish.subtitle": "把本地导出配置分享到社区:作者自托管公开仓库 + 官方收录引用(方案 B),官方市场只读、零凭据",
9717
- "publish.back": "返回市场",
9718
- "publish.stepIndicator": "步骤 {current}/{total}:{label}",
9719
- "publish.next": "下一步",
9720
- "publish.prev": "上一步",
9721
- "publish.step.select": "选择配置包",
9722
- "publish.step.validate": "本地校验",
9723
- "publish.step.prepare": "生成条目包",
9724
- "publish.step.push": "推送作者仓库",
9725
- "publish.step.submit": "提交收录申请",
9726
- "publish.select.hint": "发布内容来自你的导出 zip:先在「导出」tab 生成备份文件,再回到这里选择。市场通道永不携带秘密——包含密钥的备份将被拒绝。",
9727
- "publish.select.browse": "选择导出 ZIP…",
9728
- "publish.select.reselect": "重新选择",
9729
- "publish.select.file": "已选择:{name}",
9730
- "publish.select.uploading": "上传中…",
9731
- "publish.validate.hint": "本地 dry-run 校验(零写入):确认内容合法且不含密钥,之后才能生成条目包。",
9732
- "publish.validate.run": "开始校验",
9733
- "publish.validate.running": "校验中…",
9734
- "publish.validate.ok": "校验通过:内容合法、不含密钥",
9735
- "publish.validate.secrets": "包含密钥:市场通道永不携带秘密,拒绝发布",
9736
- "publish.validate.invalid": "内容校验未通过,无法发布",
9737
- "publish.validate.retry": "重新校验",
9738
- "publish.form.title": "条目元数据",
9739
- "publish.form.id": "条目 ID(必需)",
9740
- "publish.form.idHint": "字母数字开头,仅 . _ -,最长 128;将作为 items/<id>/ 目录名",
9741
- "publish.form.name": "条目名称(必需)",
9742
- "publish.form.description": "描述",
9743
- "publish.form.author": "作者",
9744
- "publish.form.version": "版本",
9745
- "publish.form.categories": "类别(逗号分隔)",
9746
- "publish.form.repoUrl": "托管仓库 URL(可选)",
9747
- "publish.form.repoUrlHint": "作者公开 Git 仓库地址(http(s),不含用户名/密码)。留空表示暂不关联仓库;推送指引与收录引用需要它。",
9748
- "publish.form.generate": "生成条目包",
9749
- "publish.form.generating": "生成中…",
9750
- "publish.prepare.title": "条目包已生成",
9751
- "publish.prepare.manifest": "items/<id>/manifest.json",
9752
- "publish.prepare.sha256": "SHA-256:{hash}",
9753
- "publish.prepare.sections": "包含分区:{sections}",
9754
- "publish.prepare.warnings": "发布即公开、未经官方审核;下载方导入前会逐项核对",
9755
- "publish.prepare.dirHint": "发布目录已在宿主受控临时区生成({dir}):items/<id>/manifest.json + config.zip",
9756
- "publish.prepare.copyManifest": "复制 manifest",
9757
- "publish.push.hint": "先下载「发布包」并解压为 dist/,然后在你自己的公开仓库中按序执行以下命令(插件不做任何 git 写操作、不持有凭据):",
9758
- "publish.push.commands": "git 命令",
9759
- "publish.push.download": "下载发布包",
9760
- "publish.push.copy": "复制命令",
9761
- "publish.push.repoMissing": "尚未填写托管仓库 URL——请在上一步「生成条目包」中填写,或手动把 items/{id}/ 目录推送到你的公开仓库。",
9762
- "publish.submit.hint": "把下面的片段追加到官方市场仓库 index.json 的 items[],然后提 PR。收录由创建者人工审核。",
9763
- "publish.submit.snippet": "index.json 追加片段(items[] 内新增一条)",
9764
- "publish.submit.copy": "复制片段",
9765
- "publish.submit.fork": "提交流程:fork 官方仓库({url})→ 在 index.json 的 items[] 追加上述片段 → 提交 PR。",
9766
- "publish.submit.manual": "也可把「条目 ID + 仓库地址」提交到项目 issue,由创建者人工收录。",
9767
- "publish.submit.repoMissing": "收录引用需要托管仓库 URL——请先在上一步「生成条目包」中填写。",
9768
- "publish.copied": "已复制",
9769
- "publish.copyFailed": "复制失败,请手动选择文本复制",
10239
+ "myconfigs.tab.browse": "浏览市场",
10240
+ "myconfigs.tab.myconfigs": "我的配置",
10241
+ "myconfigs.back": "返回市场",
10242
+ "myconfigs.login.title": "GitHub 登录",
10243
+ "myconfigs.login.hint": "登录后即可一键上传配置到你的公开仓库(自动创建 <login>/dsh-configs)并提交官方市场收录(目标仓库固定,不可修改)。",
10244
+ "myconfigs.login.checking": "正在检查登录状态…",
10245
+ "myconfigs.login.loggedInAs": "已登录:{login}",
10246
+ "myconfigs.login.targetRepo": "收录目标(固定):{repo}",
10247
+ "myconfigs.login.repoMissing": "配置仓库尚未创建,首次上传将自动创建(公开)",
10248
+ "myconfigs.login.repoReady": "配置仓库:{repo}",
10249
+ "myconfigs.login.start": "使用 GitHub 登录",
10250
+ "myconfigs.login.relogin": "重新登录",
10251
+ "myconfigs.login.cancel": "取消登录",
10252
+ "myconfigs.login.userCode": "一次性代码:{code}",
10253
+ "myconfigs.login.openAuth": "打开授权页",
10254
+ "myconfigs.upload.title": "一键上传",
10255
+ "myconfigs.upload.selectHint": "选择导出的配置 zip(先在「导出」tab 生成备份文件,再回到这里选择)。市场通道永不携带秘密——包含密钥的备份将被拒绝。",
10256
+ "myconfigs.upload.select": "选择 ZIP…",
10257
+ "myconfigs.upload.reselect": "重新选择",
10258
+ "myconfigs.upload.selected": "已选择:{name}",
10259
+ "myconfigs.upload.validate": "开始校验",
10260
+ "myconfigs.upload.validating": "校验中…",
10261
+ "myconfigs.upload.validateOk": "校验通过:内容合法、不含密钥",
10262
+ "myconfigs.upload.validateSecrets": "包含密钥:市场通道永不携带秘密,拒绝上传",
10263
+ "myconfigs.upload.validateInvalid": "内容校验未通过,无法上传",
10264
+ "myconfigs.upload.form.title": "条目信息",
10265
+ "myconfigs.upload.form.name": "名称",
10266
+ "myconfigs.upload.form.nameHint": "预填 zip 文件名,可修改",
10267
+ "myconfigs.upload.form.description": "描述(可选)",
10268
+ "myconfigs.upload.form.categories": "类别(可选,逗号分隔)",
10269
+ "myconfigs.upload.form.autoHint": "以下字段由系统自动生成:",
10270
+ "myconfigs.upload.run": "一键上传",
10271
+ "myconfigs.upload.running": "上传中…",
10272
+ "myconfigs.update.title": "更新配置",
10273
+ "myconfigs.update.hint": "版本将自动 +1,已预填原条目信息(名称可改)。",
10274
+ "myconfigs.update.run": "一键更新",
10275
+ "myconfigs.update.running": "更新中…",
10276
+ "myconfigs.result.title": "上传/更新完成",
10277
+ "myconfigs.result.version": "版本 {version}",
10278
+ "myconfigs.result.sha256": "SHA-256:{hash}",
10279
+ "myconfigs.result.sections": "分区:{sections}",
10280
+ "myconfigs.result.repo": "配置仓库",
10281
+ "myconfigs.result.openRepo": "打开仓库",
10282
+ "myconfigs.result.pr": "收录 PR #{number}",
10283
+ "myconfigs.result.openPr": "查看 PR",
10284
+ "myconfigs.list.title": "已上传",
10285
+ "myconfigs.list.empty": "尚未上传任何配置",
10286
+ "myconfigs.list.loading": "正在读取…",
10287
+ "myconfigs.list.refresh": "刷新",
10288
+ "myconfigs.list.summary": "共 {total} 条 · {listed} 已收录 · {pending} 待审核 · {none} 未收录",
10289
+ "myconfigs.list.openRepo": "打开仓库",
10290
+ "myconfigs.item.update": "更新",
10291
+ "myconfigs.item.install": "装回本地",
10292
+ "myconfigs.item.openPr": "查看收录 PR",
10293
+ "myconfigs.error.loadStatus": "登录状态读取失败",
10294
+ "myconfigs.error.loadItems": "已上传列表读取失败",
9770
10295
  "common.cancel": "取消",
9771
10296
  "common.close": "关闭",
9772
10297
  "common.retry": "重试",
@@ -9814,61 +10339,62 @@ const en = {
9814
10339
  "detail.approval.safe": "Importable",
9815
10340
  "detail.approval.count": "{selected}/{total} section(s) approved",
9816
10341
  "detail.noApproval": "No sections approved — cannot import",
9817
- "publish.title": "Publish to Market",
9818
- "publish.subtitle": "Share your local config with the community: author self-hosted public repo + official index reference (Option B); the official market stays read-only with zero credentials",
9819
- "publish.back": "Back to market",
9820
- "publish.stepIndicator": "Step {current}/{total}: {label}",
9821
- "publish.next": "Next",
9822
- "publish.prev": "Back",
9823
- "publish.step.select": "Select config package",
9824
- "publish.step.validate": "Local validation",
9825
- "publish.step.prepare": "Generate item package",
9826
- "publish.step.push": "Push to author repo",
9827
- "publish.step.submit": "Submit listing request",
9828
- "publish.select.hint": "Publish content comes from your exported zip: create a backup on the \"Export\" tab first, then pick it here. The market channel never carries secrets — backups containing secrets are rejected.",
9829
- "publish.select.browse": "Choose exported ZIP…",
9830
- "publish.select.reselect": "Choose again",
9831
- "publish.select.file": "Selected: {name}",
9832
- "publish.select.uploading": "Uploading…",
9833
- "publish.validate.hint": "Local dry-run validation (zero writes): confirms the content is valid and secret-free before generating the item package.",
9834
- "publish.validate.run": "Run validation",
9835
- "publish.validate.running": "Validating…",
9836
- "publish.validate.ok": "Validation passed: valid content, no secrets",
9837
- "publish.validate.secrets": "Contains secrets: the market channel never carries secrets — publishing rejected",
9838
- "publish.validate.invalid": "Content validation failed — cannot publish",
9839
- "publish.validate.retry": "Validate again",
9840
- "publish.form.title": "Item Metadata",
9841
- "publish.form.id": "Item ID (required)",
9842
- "publish.form.idHint": "Starts with a letter or digit; only . _ - allowed, max 128; used as the items/<id>/ directory name",
9843
- "publish.form.name": "Item name (required)",
9844
- "publish.form.description": "Description",
9845
- "publish.form.author": "Author",
9846
- "publish.form.version": "Version",
9847
- "publish.form.categories": "Categories (comma-separated)",
9848
- "publish.form.repoUrl": "Host repo URL (optional)",
9849
- "publish.form.repoUrlHint": "Your public Git repository URL (http(s), no username/password). Leave empty to skip the repo link; the push guide and listing reference need it.",
9850
- "publish.form.generate": "Generate item package",
9851
- "publish.form.generating": "Generating…",
9852
- "publish.prepare.title": "Item package generated",
9853
- "publish.prepare.manifest": "items/<id>/manifest.json",
9854
- "publish.prepare.sha256": "SHA-256: {hash}",
9855
- "publish.prepare.sections": "Sections: {sections}",
9856
- "publish.prepare.warnings": "Publishing makes it public and not officially reviewed; downloaders re-check items before importing",
9857
- "publish.prepare.dirHint": "Publish directory created in the host controlled temp area ({dir}): items/<id>/manifest.json + config.zip",
9858
- "publish.prepare.copyManifest": "Copy manifest",
9859
- "publish.push.hint": "First download the \"publish package\" and extract it as dist/, then run the commands below in your own public repository (the plugin performs no git writes and holds no credentials):",
9860
- "publish.push.commands": "git commands",
9861
- "publish.push.download": "Download publish package",
9862
- "publish.push.copy": "Copy commands",
9863
- "publish.push.repoMissing": "Host repo URL is not set — fill it in the previous \"Generate item package\" step, or push the items/{id}/ directory to your public repo manually.",
9864
- "publish.submit.hint": "Append the snippet below to the items[] array in the official market repo index.json, then open a PR. Listings are manually reviewed by the maintainer.",
9865
- "publish.submit.snippet": "index.json append snippet (a new entry in items[])",
9866
- "publish.submit.copy": "Copy snippet",
9867
- "publish.submit.fork": "Flow: fork the official repo ({url}) → append the snippet to items[] in index.json → open a PR.",
9868
- "publish.submit.manual": "Alternatively, file an issue with the \"item ID + repo URL\" and the maintainer will add it manually.",
9869
- "publish.submit.repoMissing": "The listing reference needs a host repo URL — fill it in the previous \"Generate item package\" step first.",
9870
- "publish.copied": "Copied",
9871
- "publish.copyFailed": "Copy failed — select the text manually",
10342
+ "myconfigs.tab.browse": "Browse Market",
10343
+ "myconfigs.tab.myconfigs": "My Configs",
10344
+ "myconfigs.back": "Back to market",
10345
+ "myconfigs.login.title": "GitHub sign-in",
10346
+ "myconfigs.login.hint": "After signing in you can upload configs to your public repo (auto-created as <login>/dsh-configs) and submit a listing PR to the official market (target repo is fixed and not editable).",
10347
+ "myconfigs.login.checking": "Checking sign-in status…",
10348
+ "myconfigs.login.loggedInAs": "Signed in as {login}",
10349
+ "myconfigs.login.targetRepo": "Listing target (fixed): {repo}",
10350
+ "myconfigs.login.repoMissing": "Config repo not created yet — it will be auto-created (public) on first upload",
10351
+ "myconfigs.login.repoReady": "Config repo: {repo}",
10352
+ "myconfigs.login.start": "Sign in with GitHub",
10353
+ "myconfigs.login.relogin": "Sign in again",
10354
+ "myconfigs.login.cancel": "Cancel sign-in",
10355
+ "myconfigs.login.userCode": "One-time code: {code}",
10356
+ "myconfigs.login.openAuth": "Open authorization page",
10357
+ "myconfigs.upload.title": "Upload",
10358
+ "myconfigs.upload.selectHint": "Pick an exported config zip (create a backup on the \"Export\" tab first, then pick it here). The market channel never carries secrets — backups containing secrets are rejected.",
10359
+ "myconfigs.upload.select": "Choose ZIP…",
10360
+ "myconfigs.upload.reselect": "Choose again",
10361
+ "myconfigs.upload.selected": "Selected: {name}",
10362
+ "myconfigs.upload.validate": "Run validation",
10363
+ "myconfigs.upload.validating": "Validating…",
10364
+ "myconfigs.upload.validateOk": "Validation passed: valid content, no secrets",
10365
+ "myconfigs.upload.validateSecrets": "Contains secrets: the market channel never carries secrets — upload rejected",
10366
+ "myconfigs.upload.validateInvalid": "Content validation failed — cannot upload",
10367
+ "myconfigs.upload.form.title": "Item info",
10368
+ "myconfigs.upload.form.name": "Name",
10369
+ "myconfigs.upload.form.nameHint": "Prefilled from the zip file name; editable",
10370
+ "myconfigs.upload.form.description": "Description (optional)",
10371
+ "myconfigs.upload.form.categories": "Categories (optional, comma-separated)",
10372
+ "myconfigs.upload.form.autoHint": "These fields are generated automatically:",
10373
+ "myconfigs.upload.run": "Upload",
10374
+ "myconfigs.upload.running": "Uploading…",
10375
+ "myconfigs.update.title": "Update config",
10376
+ "myconfigs.update.hint": "The version will be bumped automatically (+1); item info is prefilled (name editable).",
10377
+ "myconfigs.update.run": "Update",
10378
+ "myconfigs.update.running": "Updating…",
10379
+ "myconfigs.result.title": "Upload/update complete",
10380
+ "myconfigs.result.version": "version {version}",
10381
+ "myconfigs.result.sha256": "SHA-256: {hash}",
10382
+ "myconfigs.result.sections": "Sections: {sections}",
10383
+ "myconfigs.result.repo": "Config repo",
10384
+ "myconfigs.result.openRepo": "Open repo",
10385
+ "myconfigs.result.pr": "Listing PR #{number}",
10386
+ "myconfigs.result.openPr": "View PR",
10387
+ "myconfigs.list.title": "Uploaded",
10388
+ "myconfigs.list.empty": "No configs uploaded yet",
10389
+ "myconfigs.list.loading": "Loading…",
10390
+ "myconfigs.list.refresh": "Refresh",
10391
+ "myconfigs.list.summary": "{total} item(s) · {listed} listed · {pending} pending · {none} not listed",
10392
+ "myconfigs.list.openRepo": "Open repo",
10393
+ "myconfigs.item.update": "Update",
10394
+ "myconfigs.item.install": "Install locally",
10395
+ "myconfigs.item.openPr": "View listing PR",
10396
+ "myconfigs.error.loadStatus": "Failed to read sign-in status",
10397
+ "myconfigs.error.loadItems": "Failed to read uploaded items",
9872
10398
  "common.cancel": "Cancel",
9873
10399
  "common.close": "Close",
9874
10400
  "common.retry": "Retry",
@@ -9909,6 +10435,7 @@ function apply(ctx) {
9909
10435
  const syncApi = new SyncApi(uiT);
9910
10436
  const marketT = ctx.locale.bind(MARKET_NS);
9911
10437
  const marketApi = new MarketApi(uiT);
10438
+ const myConfigsApi = new MyConfigsApi(uiT);
9912
10439
  ctx.slots.inject("settings.section", () => ctx.slots.register({
9913
10440
  name: "settings.section",
9914
10441
  id: "config-manager",
@@ -9920,7 +10447,8 @@ function apply(ctx) {
9920
10447
  syncApi,
9921
10448
  syncT,
9922
10449
  marketApi,
9923
- marketT
10450
+ marketT,
10451
+ myConfigsApi
9924
10452
  })
9925
10453
  }, ConfigManagerSection));
9926
10454
  }