dsh-github-flow 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +16 -4
- package/README.md +16 -4
- package/docs/UPGRADE-NOTES.md +8 -8
- package/docs/architecture.md +33 -17
- package/docs/images/1.png +0 -0
- package/lib/api/routes-actions.js +139 -8
- package/lib/api/routes-overview.js +147 -29
- package/lib/cache.d.ts +77 -4
- package/lib/cache.js +114 -7
- package/lib/client.d.ts +7 -3
- package/lib/client.js +3309 -314
- package/lib/executor/error-classifier.d.ts +55 -0
- package/lib/executor/error-classifier.js +199 -0
- package/lib/executor/git-parser.d.ts +124 -0
- package/lib/executor/git-parser.js +397 -0
- package/lib/executor.d.ts +122 -4
- package/lib/executor.js +685 -80
- package/lib/tools/help.js +22 -7
- package/lib/tools/image-utils.d.ts +27 -0
- package/lib/tools/image-utils.js +73 -3
- package/lib/types.d.ts +331 -0
- package/lib/ui/help-modal.d.ts +6 -0
- package/lib/ui/help-modal.js +113 -15
- package/lib/ui/issue-modal.d.ts +14 -0
- package/lib/ui/issue-modal.js +366 -0
- package/lib/ui/markdown-lite.d.ts +66 -0
- package/lib/ui/markdown-lite.js +312 -0
- package/lib/ui/network-banner.d.ts +12 -0
- package/lib/ui/network-banner.js +35 -0
- package/lib/ui/pr-modal.d.ts +17 -0
- package/lib/ui/pr-modal.js +516 -0
- package/lib/ui/repo-badges.d.ts +43 -0
- package/lib/ui/repo-badges.js +82 -0
- package/lib/ui/repo-selector.d.ts +14 -0
- package/lib/ui/repo-selector.js +112 -0
- package/lib/ui/storage.d.ts +47 -0
- package/lib/ui/storage.js +139 -0
- package/lib/ui/styles.d.ts +1 -1
- package/lib/ui/styles.js +960 -0
- package/lib/ui/utils.js +13 -1
- package/lib/ui/workspace-panel.js +169 -27
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/lib/ui/help-modal.js
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* 3. 拒绝技术自嗨清单,从用户行动视角组织:3分钟快速上手、Agent实战Prompt、双界面图解与常见排错;
|
|
8
8
|
* 4. 宽屏卡片排列,配备 Prompt 一键复制到剪贴板与短暂反馈气泡;
|
|
9
9
|
* 5. 监听键盘 Esc 键与遮罩点击秒关,复制完指令直接回到聊天输入框发送,零打扰、不中断工作流。
|
|
10
|
+
*
|
|
11
|
+
* 【内容与代码同步的纪律】
|
|
12
|
+
* 本文件里出现的工具数量、action 数量、Tab 名称、按钮文案,全部以当前代码为准:
|
|
13
|
+
* 工具矩阵 = src/main.ts 的 18 处 ctx.tools.register;
|
|
14
|
+
* Tab 布局 = src/client.ts 的 TAB_DEFS 与 src/ui/workspace-panel.ts 的四个标签;
|
|
15
|
+
* 若后续增删工具或 Tab,必须同步改这里,否则用户会照着过时说明去找不存在的入口。
|
|
10
16
|
*/
|
|
11
17
|
export function GithubHelpModal(props) {
|
|
12
18
|
if (!props.isOpen)
|
|
@@ -43,6 +49,7 @@ export function GithubHelpModal(props) {
|
|
|
43
49
|
{ id: "quickstart", label: "🚀 3分钟极速上手" },
|
|
44
50
|
{ id: "prompts", label: "🤖 Agent 提示词实战" },
|
|
45
51
|
{ id: "panels", label: "🖥️ 两大界面操作图解" },
|
|
52
|
+
{ id: "tools", label: "🧰 18 大工具速查" },
|
|
46
53
|
{ id: "faq", label: "🔑 常见自愈排错" },
|
|
47
54
|
];
|
|
48
55
|
// 2. 渲染各 Tab 独立视图
|
|
@@ -59,7 +66,7 @@ export function GithubHelpModal(props) {
|
|
|
59
66
|
step: "2",
|
|
60
67
|
title: "打开你的代码仓库",
|
|
61
68
|
desc: "在 DSH 中打开任何包含 .git 的项目文件夹,插件将自动毫秒级绑定当前仓库上下文。",
|
|
62
|
-
actionText: "无需每次告诉 Agent 仓库叫什么名字,插件底层已自动完成 CWD
|
|
69
|
+
actionText: "无需每次告诉 Agent 仓库叫什么名字,插件底层已自动完成 CWD 工作区绑定,提单与审查均精准面向当前项目。\n若当前是 Fork 仓库,右侧栏顶部会显示血缘行,可一键在「我的复刻」与「上游主干」之间切换基准仓库。",
|
|
63
70
|
},
|
|
64
71
|
{
|
|
65
72
|
step: "3",
|
|
@@ -69,7 +76,9 @@ export function GithubHelpModal(props) {
|
|
|
69
76
|
},
|
|
70
77
|
].map(function (s, i) {
|
|
71
78
|
return h("div", { key: i, className: "dsh-github-help-card", style: { display: "flex", gap: "14px", alignItems: "flex-start" } }, h("div", { className: "dsh-github-help-step-badge" }, s.step), h("div", { style: { flex: 1 } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "4px" } }, s.title), h("div", { className: "dsh-github-help-desc", style: { marginBottom: "6px" } }, s.desc), h("div", { style: { fontSize: "12px", lineHeight: "1.6", whiteSpace: "pre-line", color: "inherit" } }, s.actionText)));
|
|
72
|
-
})
|
|
79
|
+
}),
|
|
80
|
+
// 能力总览:让用户三秒钟知道这插件到底能干什么,而不是去翻 README
|
|
81
|
+
h("div", { className: "dsh-github-help-card", style: { marginTop: "4px" } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, "📦 能力总览:18 大工具 · 103 个业务动作 · 2 套可视化工作台"), h("div", { style: { fontSize: "12px", lineHeight: "1.8", color: "var(--dsw-alias-label-secondary, #8b949e)" } }, "Agent 侧可直接调用 18 个 github_* 工具(PR 审查合并、Issue 任务流转、Actions 排障、免 clone 读文件、发版交付、Secrets 管理、Skills 生态、标签与规则集、构建缓存、Gist、跨仓待办大盘、Copilot 智能体、Discussions、Projects v2、Codespaces、运行时遥测与元指南)。", h("br"), "人类侧在每个会话右侧栏拥有 5 个 Tab(Git / 未提交 / PR / Issues / Actions),在最左侧 Octocat 入口拥有 4 个标签的全景驾驶舱。")));
|
|
73
82
|
}
|
|
74
83
|
if (activeTab === "prompts") {
|
|
75
84
|
var promptCards = [
|
|
@@ -98,13 +107,25 @@ export function GithubHelpModal(props) {
|
|
|
98
107
|
aiAction: "Agent 自动获取改动文件与行级差异,结合工程最佳实践逐项排查边界条件、内存泄漏、空指针与安全漏洞。",
|
|
99
108
|
},
|
|
100
109
|
{
|
|
101
|
-
title: "
|
|
110
|
+
title: "📝 场景五:从用户报障截图直接定位问题",
|
|
111
|
+
desc: "用户提的 Issue 里贴了崩溃截图,Agent 可以直接「看」图,不用你手抄报错信息。",
|
|
112
|
+
prompt: "查看 Issue #42 的正文与截图,判断是哪个模块出的问题并给出修复方案",
|
|
113
|
+
aiAction: "Agent 调用 github_issue(view_images) 把正文与评论里的图片下载到本地并转为可读图像内容块,结合文字描述一起分析。",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
title: "🏷️ 场景六:批量标签治理与跨仓库同步",
|
|
117
|
+
desc: "新仓库要配一套和主仓库一致的标签体系,手点太慢。",
|
|
118
|
+
prompt: "把 A 仓库的标签体系克隆到当前仓库,然后给所有 open 的 bug Issue 打上 triage 标签",
|
|
119
|
+
aiAction: "Agent 调用 github_label(clone) 复制标签颜色与描述,再按条件批量追加标签;删除类高危操作前会先返回影响评估要求你确认。",
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
title: "💬 场景七:团队方案提案与开源 RFC 研讨",
|
|
102
123
|
desc: "需要和团队讨论架构思路或征集想法时,直接将讨论沉淀在 GitHub Discussions 知识库中。",
|
|
103
124
|
prompt: "在当前仓库发起一个关于 '优化前端构建性能' 的 RFC 讨论方案",
|
|
104
125
|
aiAction: "Agent 自动调用 Discussions 工具在指定分类下发起主题,格式化排版技术方案正文,并返回可供团队直达探讨的链接。",
|
|
105
126
|
},
|
|
106
127
|
];
|
|
107
|
-
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "以下精选了开发者日常研发中最高频的
|
|
128
|
+
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "以下精选了开发者日常研发中最高频的 7 大真实协作场景。点击卡片右侧的「📋 一键复制」按钮,直接粘贴到聊天输入框发给 Agent 即可:"), promptCards.map(function (c, idx) {
|
|
108
129
|
var isCopied = copiedIdx === idx;
|
|
109
130
|
return h("div", { key: idx, className: "dsh-github-help-card" }, h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "8px" } }, h("div", { className: "dsh-github-help-title" }, c.title), h("button", {
|
|
110
131
|
className: "dsh-github-btn-sm",
|
|
@@ -124,29 +145,97 @@ export function GithubHelpModal(props) {
|
|
|
124
145
|
if (activeTab === "panels") {
|
|
125
146
|
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "16px" } }, "插件为人类开发者量身打造了两套互补的可视化工作台,消灭频繁在 VS Code、终端与浏览器切屏的上下文丢失痛点:"), [
|
|
126
147
|
{
|
|
127
|
-
title: "1. 全景驾驶舱 (Main Dashboard) —— 掌控全局资产",
|
|
148
|
+
title: "1. 全景驾驶舱 (Main Dashboard) —— 掌控全局资产 · 4 个标签",
|
|
128
149
|
items: [
|
|
129
150
|
"打开入口:点击 DSH 最左侧主导航栏的 Octocat 猫咪徽标,瞬间全屏展开;",
|
|
130
151
|
"SWR 0ms 瞬间秒开:采用本地持久化快照体系,打开界面零转圈、零等待;",
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
152
|
+
"📂 本地工作区矩阵:一屏透视本机所有项目与远程仓库的关联状态、分支领先/落后、未提交文件数;",
|
|
153
|
+
"☁️ 我的 GitHub 仓库:名下全部仓库、默认分支与 Star 状态,支持关键词搜索;",
|
|
154
|
+
"🔀 Pull Requests:默认展示「全部」,顶部胶囊可切 🟢 Open / 🟣 Merged / ⚪ Closed 并显示各档计数;点击任一卡片在线唤起 PR 审阅工作台;",
|
|
155
|
+
"📝 任务与待办 Issues:三维分类(📥 用户提单 / 🎯 指派待办 / ✍️ 我发起的),点击卡片在线查看 Issue 详情;",
|
|
156
|
+
"右下角长期驻留 使用指南 / ⚙️ 设置 两个入口,设置内含数据同步、网络代理、更新源与关于四个分区。",
|
|
134
157
|
],
|
|
135
158
|
},
|
|
136
159
|
{
|
|
137
|
-
title: "2. 会话右侧栏面板 (Sidebar Panel) —— 深度协同当前工作区",
|
|
160
|
+
title: "2. 会话右侧栏面板 (Sidebar Panel) —— 深度协同当前工作区 · 5 个 Tab",
|
|
138
161
|
items: [
|
|
139
162
|
"打开入口:位于每个会话工作区的右侧抽屉栏,深度锁定当前会话的物理 Git 仓库;",
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
163
|
+
"仓库身份与切换:顶部展示当前关联仓库、Fork 血缘行与权限徽章(只读/可写);若配置了多个 remote,可通过 RepoSelectorDropdown 下拉一键切换基准仓库;", "Git Tab:三段式提交时间线(待推送 / 待拉取 / 最近),默认 15 条、下滑触底自动加载;点任意提交行弹出完整 Commit 详情;",
|
|
164
|
+
"未提交 Tab:实时监听本地未提交文件与状态码(?? 新增 / M 修改 / D 删除 / R 重命名),点击文件可就地下钻查看该文件的 Unified Diff;",
|
|
165
|
+
"PR Tab:展示全部 PR 并按状态过滤;带三态 CI 门禁、冲突检测、审核判定与 Draft 标示;点击卡片在线唤起 PR 审阅工作台(描述 / 评论 / 文件 Diff 双栏比对);",
|
|
166
|
+
"Issues Tab:展示全部 Issue 并按状态过滤;点击卡片在线查看详情(Markdown 正文 / 评论时间线 / 标签色块 / 里程碑 / 关联 PR / 隐藏评论标注);",
|
|
167
|
+
"Actions Tab:最近工作流运行记录与触发方式,失败时给出可直接交给 Agent 的诊断指引;",
|
|
143
168
|
"动静解耦架构:修改本地代码只定向查询本地 .git 核心元数据(耗时仅 5ms),绝对不发起远端网络请求,杜绝网络风暴。",
|
|
144
169
|
],
|
|
145
170
|
},
|
|
171
|
+
{
|
|
172
|
+
title: "3. 两个弹窗的通用操作习惯",
|
|
173
|
+
items: [
|
|
174
|
+
"PR / Issue 弹窗都支持:按 Esc 或点击遮罩关闭、右上角「📋 复制链接」、「↗ 浏览器打开」直达 GitHub;",
|
|
175
|
+
"列表卡片上的「GitHub ↗」按钮是独立的浏览器外跳入口,点它不会触发弹窗(已做事件隔离);",
|
|
176
|
+
"PR 弹窗的文件区可拖拽分隔条调宽、双击或点 ◀ 折叠;布局偏好会记在本地,下次打开保持原样;",
|
|
177
|
+
"长评论默认折叠(显示「⌄ 展开全部」),PR/Issue 正文默认展开——避免一屏被一段超长文字占满。",
|
|
178
|
+
],
|
|
179
|
+
},
|
|
146
180
|
].map(function (sec, idx) {
|
|
147
181
|
return h("div", { key: idx, className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, sec.title), h("ul", { style: { margin: 0, paddingLeft: "18px", fontSize: "12px", lineHeight: "1.7", color: "var(--dsw-alias-label-secondary, #8b949e)" } }, sec.items.map(function (it, i) { return h("li", { key: i }, it); })));
|
|
148
182
|
}));
|
|
149
183
|
}
|
|
184
|
+
if (activeTab === "tools") {
|
|
185
|
+
var toolGroups = [
|
|
186
|
+
{
|
|
187
|
+
group: "日常研发主力",
|
|
188
|
+
items: [
|
|
189
|
+
{ name: "github_pr", desc: "PR 全生命周期:列表/详情/diff/checks/create/merge/review/ready/update_branch/revert/preview/checkout,含行级评论与附件上传" },
|
|
190
|
+
{ name: "github_issue", desc: "Issue 任务流转:list/view/create/comment/edit/close/reopen/develop/transfer/pin/lock/delete/status,含截图查看 view_images" },
|
|
191
|
+
{ name: "github_run", desc: "Actions 排障:list/view/log_failed(失败步骤日志秒级提取)/rerun/cancel/trigger/download 与 workflow 启停" },
|
|
192
|
+
{ name: "github_repo", desc: "仓库元数据、免 clone 读文件(read_file/read_dir)、跨仓检索(code/issues/prs/commits)、Fork 同步" },
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
group: "交付与配置",
|
|
197
|
+
items: [
|
|
198
|
+
{ name: "github_release", desc: "发版历史、自动生成说明、创建 Release、上传/下载构建资产" },
|
|
199
|
+
{ name: "github_secret", desc: "Actions Secrets 与 Variables:列表/设置/删除(密文经 stdin 通道,绝不进命令行)" },
|
|
200
|
+
{ name: "github_label", desc: "标签体系 CRUD 与跨仓库一键克隆(删除需 confirm)" },
|
|
201
|
+
{ name: "github_ruleset", desc: "分支保护规则集查看与「为什么这个 PR 不能合」的策略排障" },
|
|
202
|
+
{ name: "github_cache", desc: "Actions 构建缓存盘点、按 Key/ID 删除与全量清空(高危需 confirm)" },
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
group: "生态与协作",
|
|
207
|
+
items: [
|
|
208
|
+
{ name: "github_skill", desc: "Agent Skills 生态:跨仓检索/预览/安装/发布自检/批量更新" },
|
|
209
|
+
{ name: "github_status", desc: "账号维度跨仓待办大盘 + 运行时健康度与 API 降级率审计" },
|
|
210
|
+
{ name: "github_agent_task", desc: "Copilot 云端编码智能体:派活、查状态、拉会话日志" },
|
|
211
|
+
{ name: "github_discussion", desc: "社区讨论与 RFC:列表/详情/发起/编辑/评论" },
|
|
212
|
+
{ name: "github_project", desc: "Projects v2 看板:项目列表、条目增删与草稿任务" },
|
|
213
|
+
{ name: "github_codespace", desc: "云端开发机:列表/详情/停止(防计费)/销毁" },
|
|
214
|
+
{ name: "github_gist", desc: "代码片段与排障留痕:创建 Secret Gist、查看、编辑、删除" },
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
group: "兜底与元能力",
|
|
219
|
+
items: [
|
|
220
|
+
{ name: "github_api", desc: "REST / GraphQL 逃生门,支持 --jq 过滤与 raw 原文模式" },
|
|
221
|
+
{ name: "github_help", desc: "全景命令映射、官方语法速查与 gh 报错自愈指引" },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
];
|
|
225
|
+
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "18 个 Agent 工具、合计 103 个业务动作(不含元指南的 21 个查询主题与 api 逃生门的 4 个 HTTP method)。你不必记住工具名——直接用自然语言描述目标,Agent 会自己挑工具。下表用于你了解能力边界,以及排查「为什么 Agent 说做不到」:"), toolGroups.map(function (g, gi) {
|
|
226
|
+
return h("div", { key: gi, className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, g.group), g.items.map(function (t, ti) {
|
|
227
|
+
return h("div", { key: ti, style: { display: "flex", gap: "10px", alignItems: "flex-start", padding: "5px 0", borderTop: ti === 0 ? "none" : "1px solid var(--dsw-alias-border-l1, rgba(255,255,255,0.06))" } }, h("code", {
|
|
228
|
+
style: {
|
|
229
|
+
flexShrink: 0,
|
|
230
|
+
width: "132px",
|
|
231
|
+
fontSize: "11px",
|
|
232
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
233
|
+
color: "var(--dsw-alias-link, #0969da)",
|
|
234
|
+
},
|
|
235
|
+
}, t.name), h("span", { style: { fontSize: "12px", lineHeight: "1.6", color: "var(--dsw-alias-label-secondary, #8b949e)" } }, t.desc));
|
|
236
|
+
}));
|
|
237
|
+
}), h("div", { className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, "⌨️ 人类 Slash 指令"), h("div", { style: { fontSize: "12px", lineHeight: "1.9", color: "var(--dsw-alias-label-secondary, #8b949e)" } }, h("code", { className: "dsh-github-md-inline-code" }, "/gh"), " 或 ", h("code", { className: "dsh-github-md-inline-code" }, "/gh status"), " —— 账号与仓库健康度诊断(登录态、权限、网络、版本);", h("br"), h("code", { className: "dsh-github-md-inline-code" }, "/gh upgrade"), " —— 一键跨平台自动升级插件;", h("br"), h("code", { className: "dsh-github-md-inline-code" }, "/gh help"), " —— 在聊天流中打印精简版指令与范例;", h("br"), "想看点状图文说明,就是当前这个窗口:右下角「📖 使用指南」。")));
|
|
238
|
+
}
|
|
150
239
|
if (activeTab === "faq") {
|
|
151
240
|
var faqs = [
|
|
152
241
|
{
|
|
@@ -155,16 +244,24 @@ export function GithubHelpModal(props) {
|
|
|
155
244
|
},
|
|
156
245
|
{
|
|
157
246
|
q: "Q2: 提示「缺少权限 (Scope)」或「SCOPE_REQUIRED」怎么办?",
|
|
158
|
-
a: "当调用项目看板(Projects v2)或云端开发机(Codespaces)时,GitHub 要求专有的 Token Scope。在系统终端执行对应的一键自愈命令完成增量授权即可(无需重新登录):\n-
|
|
247
|
+
a: "当调用项目看板(Projects v2)或云端开发机(Codespaces)时,GitHub 要求专有的 Token Scope。在系统终端执行对应的一键自愈命令完成增量授权即可(无需重新登录):\n- 项目看板(只读):`gh auth refresh -s read:project`\n- 项目看板(读写):`gh auth refresh -s project`\n- 云端开发机:`gh auth refresh -h github.com -s codespace`",
|
|
159
248
|
},
|
|
160
249
|
{
|
|
161
|
-
q: "Q3:
|
|
250
|
+
q: "Q3: 点了一键升级提示「处于 24h 安全保护期 (防投毒策略)」怎么办?",
|
|
162
251
|
a: "这是 DSH 官方内置包管理器针对刚发布未满 24 小时新版本的供应链防投毒安全锁机制。\n- 立即体验:请先【完全退出关闭 DSH 应用程序】,然后在系统终端执行复制的手动安装命令;\n- 稍后升级:无需任何操作,新版本发版满 24 小时后,再次点击一键升级即可自动安装。",
|
|
163
252
|
},
|
|
164
253
|
{
|
|
165
254
|
q: "Q4: 使用代理节点时偶尔出现网络波动怎么办?",
|
|
166
255
|
a: "插件内置了 1s ➔ 2s ➔ 4s 的阶梯指数退避重试机制。代理节点若发生短时抖动断流,系统会自动在后台静默发起自愈重试,在节点恢复后瞬间平滑拉取最新数据,不会立刻亮出大红横幅打扰你。同时电脑联网恢复瞬间也会自动触发静默更新。",
|
|
167
256
|
},
|
|
257
|
+
{
|
|
258
|
+
q: "Q5: 右侧栏 PR / Issues 列表是空的,但 GitHub 网页上明明有记录?",
|
|
259
|
+
a: "先看顶部状态胶囊是不是停在某个具体档位(例如「Merged」)而该档确实为零——点「全部」即可看到所有记录。\n若「全部」也是空的,通常是两类原因:\n- 仓库关闭了 Issues 功能:面板会给出「该仓库未开启 Issues」的确定性提示,这不是故障;\n- 当前工作区没绑定对仓库:看右侧栏顶部显示的是哪个仓库,若不对可在仓库下拉里切换基准,或检查 `.git/config` 的 remote 配置。",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
q: "Q6: 为什么改了代码,插件界面要重启 DSH 才生效?",
|
|
263
|
+
a: "如果你是在开发这个插件本身:后端 HTTP 路由是在插件 `apply()` 时一次性注册到宿主的 WebServer 上的,`pnpm run build` 只更新磁盘产物,不会给已运行的进程热注册新路由。改完代码需重启 DSH 才能挂上新端点。\n若只是普通使用,请忽略此条——正常升级走一键升级即可。",
|
|
264
|
+
},
|
|
168
265
|
];
|
|
169
266
|
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "常见环境、权限与网络问题的排查与自愈指引:"), faqs.map(function (f, idx) {
|
|
170
267
|
return h("div", { key: idx, className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "6px", fontSize: "13px" } }, f.q), h("div", { className: "dsh-github-help-desc", style: { lineHeight: "1.6", whiteSpace: "pre-line" } }, f.a));
|
|
@@ -180,9 +277,10 @@ export function GithubHelpModal(props) {
|
|
|
180
277
|
h("div", { className: "dsh-github-help-header" }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, h("span", { style: { fontSize: "20px" } }, "📖"), h("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600, color: "inherit" } }, "DSH GitHub Flow · 使用指南与实战手册")), h("button", {
|
|
181
278
|
style: { background: "none", border: "none", cursor: "pointer", fontSize: "18px", color: "var(--dsw-alias-label-secondary, #8b949e)", padding: "0 4px" },
|
|
182
279
|
onClick: props.onClose,
|
|
280
|
+
"aria-label": "关闭 (Esc)",
|
|
183
281
|
title: "关闭 (Esc)",
|
|
184
282
|
}, "✕")),
|
|
185
|
-
//
|
|
283
|
+
// 顶部分类 Segment 导航栏
|
|
186
284
|
h("div", { className: "dsh-github-help-nav" }, tabs.map(function (t) {
|
|
187
285
|
var isActive = activeTab === t.id;
|
|
188
286
|
return h("button", {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface IssueDetailModalProps {
|
|
2
|
+
issueNumber: number;
|
|
3
|
+
repoNameWithOwner?: string;
|
|
4
|
+
cwd?: string | null;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
showToast?: (msg: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Issue 在线查看弹窗(详情 + 元数据 + 评论时间线)。
|
|
10
|
+
*
|
|
11
|
+
* 与 PrDetailModal 的差异:Issue 没有 diff 载荷,因此不做双栏文件区,
|
|
12
|
+
* 单列纵向滚动即可——多出来的宽度全部让给 Markdown 正文,可读性更好。
|
|
13
|
+
*/
|
|
14
|
+
export declare function IssueDetailModal(props: IssueDetailModalProps): any;
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub 关闭原因的状态语义映射。
|
|
3
|
+
* 实测 `stateReason` 取值域:COMPLETED / NOT_PLANNED / REOPENED / ''(空串)。
|
|
4
|
+
* 空串在关闭态下表示「未给出原因」,不渲染徽章即可。
|
|
5
|
+
*/
|
|
6
|
+
var ISSUE_STATE_REASON_LABEL = {
|
|
7
|
+
COMPLETED: '已完成',
|
|
8
|
+
NOT_PLANNED: '不计划处理',
|
|
9
|
+
REOPENED: '已重开',
|
|
10
|
+
};
|
|
11
|
+
/** `#d73a4a` 形态的标签色转 rgba;非法色值退回中性灰,绝不让颜色拼错导致整块渲染失败 */
|
|
12
|
+
function labelRgba(hex, alpha) {
|
|
13
|
+
var raw = typeof hex === 'string' ? hex.replace('#', '').trim() : '';
|
|
14
|
+
if (!/^[0-9a-fA-F]{6}$/.test(raw))
|
|
15
|
+
return 'rgba(140, 149, 159, ' + alpha + ')';
|
|
16
|
+
var r = parseInt(raw.slice(0, 2), 16);
|
|
17
|
+
var g = parseInt(raw.slice(2, 4), 16);
|
|
18
|
+
var b = parseInt(raw.slice(4, 6), 16);
|
|
19
|
+
return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 格式化里程碑截止日(未来 / 过去双向)。
|
|
23
|
+
*
|
|
24
|
+
* 为什么不能直接用 formatRelativeTime:它把**所有未来时间**都归一成「刚刚」
|
|
25
|
+
* (见 utils.ts 的 `if (diff < 0) return "刚刚"`,那是为更新时间/创建时间这类
|
|
26
|
+
* 永不为未来的字段设计的)。而里程碑 dueOn 绝大多数都在未来,
|
|
27
|
+
* 直接套用会把「30 天后到期」渲染成「刚刚截止」——语义完全反过来。
|
|
28
|
+
*/
|
|
29
|
+
function formatDueDate(iso) {
|
|
30
|
+
var t = typeof iso === 'string' ? Date.parse(iso) : NaN;
|
|
31
|
+
if (isNaN(t))
|
|
32
|
+
return '';
|
|
33
|
+
var diff = t - Date.now();
|
|
34
|
+
var DAY = 86400000;
|
|
35
|
+
var days = Math.ceil(Math.abs(diff) / DAY);
|
|
36
|
+
if (diff >= 0) {
|
|
37
|
+
if (days <= 0)
|
|
38
|
+
return '今天到期';
|
|
39
|
+
if (days === 1)
|
|
40
|
+
return '明天到期';
|
|
41
|
+
if (days < 30)
|
|
42
|
+
return days + ' 天后到期';
|
|
43
|
+
if (days < 365)
|
|
44
|
+
return Math.round(days / 30) + ' 个月后到期';
|
|
45
|
+
return Math.round(days / 365) + ' 年后到期';
|
|
46
|
+
}
|
|
47
|
+
if (days <= 0)
|
|
48
|
+
return '今天已到期';
|
|
49
|
+
if (days === 1)
|
|
50
|
+
return '昨天已到期';
|
|
51
|
+
if (days < 30)
|
|
52
|
+
return '已逾期 ' + days + ' 天';
|
|
53
|
+
if (days < 365)
|
|
54
|
+
return '已逾期 ' + Math.round(days / 30) + ' 个月';
|
|
55
|
+
return '已逾期 ' + Math.round(days / 365) + ' 年';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 图片附件清单。
|
|
59
|
+
*
|
|
60
|
+
* 为什么不在弹窗里直接 <img src>:图片可能来自任意 https 外域,直连会把
|
|
61
|
+
* 宿主页面的 Referer 与来源暴露给第三方,也绕过插件的域名白名单防线。
|
|
62
|
+
* 这里只给可点外链,由用户显式决定是否打开。
|
|
63
|
+
*/
|
|
64
|
+
function IssueImageList(props) {
|
|
65
|
+
var h = React.createElement;
|
|
66
|
+
var urls = Array.isArray(props.urls) ? props.urls : [];
|
|
67
|
+
var st = React.useState(false);
|
|
68
|
+
var open = st[0];
|
|
69
|
+
var setOpen = st[1];
|
|
70
|
+
if (urls.length === 0)
|
|
71
|
+
return null;
|
|
72
|
+
return h("span", { className: "dsh-github-issue-image-badge" }, h("button", {
|
|
73
|
+
type: "button",
|
|
74
|
+
className: "dsh-github-issue-chip dsh-github-issue-chip-btn",
|
|
75
|
+
onClick: function () { setOpen(!open); },
|
|
76
|
+
title: open ? "收起图片清单" : "展开图片链接清单(不在面板内直连渲染)",
|
|
77
|
+
"aria-expanded": open ? "true" : "false",
|
|
78
|
+
}, "📎 " + urls.length + " 张图片"), open
|
|
79
|
+
? h(
|
|
80
|
+
// 用 span 而非 div:整个元数据条本身是 span 级 content 容器,
|
|
81
|
+
// div 嵌在 span 里虽然浏览器会容错,但属于非法嵌套。
|
|
82
|
+
"span", { className: "dsh-github-issue-image-links" }, urls.map(function (u) {
|
|
83
|
+
var name = u.split("?")[0].split("/").pop() || u;
|
|
84
|
+
return h("a", {
|
|
85
|
+
key: u,
|
|
86
|
+
href: u,
|
|
87
|
+
target: "_blank",
|
|
88
|
+
rel: "noopener noreferrer",
|
|
89
|
+
className: "dsh-github-issue-image-link",
|
|
90
|
+
title: u,
|
|
91
|
+
}, "🖼 " + (name.length > 42 ? name.slice(0, 42) + "…" : name));
|
|
92
|
+
}))
|
|
93
|
+
: null);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 单条评论卡片。
|
|
97
|
+
*
|
|
98
|
+
* 为什么提到模块顶层:函数体内定义的组件每次父级渲染都会产生新引用,
|
|
99
|
+
* React 按引用比较类型会卸载重建,CollapsibleBody 的展开状态随之重置。
|
|
100
|
+
* 这是 pr-modal 上已经踩过的坑,Issue 侧不走第二遍。
|
|
101
|
+
*/
|
|
102
|
+
function IssueCommentCard(props) {
|
|
103
|
+
var h = React.createElement;
|
|
104
|
+
var c = props.comment;
|
|
105
|
+
var author = c && c.author ? c.author.login : "ghost";
|
|
106
|
+
var hidden = Boolean(c && c.isMinimized);
|
|
107
|
+
return h("div", { className: "dsh-github-issue-comment-card" }, h("div", { className: "dsh-github-issue-comment-header" }, h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap", minWidth: 0 } }, h("strong", null, "@" + author), c && c.authorAssociation && c.authorAssociation !== "NONE"
|
|
108
|
+
? h("span", { className: "dsh-github-badge dsh-badge-blue", style: { fontSize: "9px" } }, c.authorAssociation)
|
|
109
|
+
: null, c && c.includesCreatedEdit ? h("span", { style: { fontSize: "10px", opacity: 0.6 } }, "· 已编辑") : null, hidden
|
|
110
|
+
? h("span", {
|
|
111
|
+
className: "dsh-github-badge dsh-badge-red",
|
|
112
|
+
style: { fontSize: "9px" },
|
|
113
|
+
title: "GitHub 判定该评论被折叠(常见原因:垃圾信息 / 攻击性内容)",
|
|
114
|
+
}, "已隐藏" + (c.minimizedReason ? "(" + c.minimizedReason + ")" : ""))
|
|
115
|
+
: null), h("span", { style: { fontSize: "11px", opacity: 0.6, flexShrink: 0 } }, formatRelativeTime(c.createdAt))), h("div", { style: { padding: "10px 12px" } }, h(CollapsibleBody, { text: c.body, defaultExpanded: false, idPrefix: "ic-" + c.id })));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Issue 在线查看弹窗(详情 + 元数据 + 评论时间线)。
|
|
119
|
+
*
|
|
120
|
+
* 与 PrDetailModal 的差异:Issue 没有 diff 载荷,因此不做双栏文件区,
|
|
121
|
+
* 单列纵向滚动即可——多出来的宽度全部让给 Markdown 正文,可读性更好。
|
|
122
|
+
*/
|
|
123
|
+
export function IssueDetailModal(props) {
|
|
124
|
+
var issueNumber = props.issueNumber;
|
|
125
|
+
var repoNameWithOwner = props.repoNameWithOwner || "";
|
|
126
|
+
var cwd = props.cwd;
|
|
127
|
+
var onClose = props.onClose;
|
|
128
|
+
var showToast = props.showToast || (function () { });
|
|
129
|
+
// Hooks 必须先于任何 early return 无条件执行(见 pr-modal 的同类注释):
|
|
130
|
+
// 否则「issueNumber 由空变有值」时两次渲染的 Hook 数量不一致,直接崩。
|
|
131
|
+
// 注意 useState 与 useEffect 都必须在这个位置之前——early return 卡在两者
|
|
132
|
+
// 中间时,首帧(无编号)会跳过全部 effect,有编号的首帧却多出两个 effect,
|
|
133
|
+
// 同样违反「每次渲染 Hook 序列一致」。
|
|
134
|
+
var [data, setData] = React.useState(null);
|
|
135
|
+
var [loading, setLoading] = React.useState(true);
|
|
136
|
+
var [error, setError] = React.useState(null);
|
|
137
|
+
var [copied, setCopied] = React.useState(false);
|
|
138
|
+
React.useEffect(function () {
|
|
139
|
+
var onKeyDown = function (e) {
|
|
140
|
+
if (e.key === "Escape" || e.keyCode === 27)
|
|
141
|
+
onClose();
|
|
142
|
+
};
|
|
143
|
+
window.addEventListener("keydown", onKeyDown);
|
|
144
|
+
return function () { window.removeEventListener("keydown", onKeyDown); };
|
|
145
|
+
}, [onClose]);
|
|
146
|
+
React.useEffect(function () {
|
|
147
|
+
if (!issueNumber) {
|
|
148
|
+
setLoading(false);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
setLoading(true);
|
|
152
|
+
setError(null);
|
|
153
|
+
var url = "/api/github/issue-detail?number=" +
|
|
154
|
+
encodeURIComponent(String(issueNumber)) +
|
|
155
|
+
(repoNameWithOwner ? "&repo=" + encodeURIComponent(repoNameWithOwner) : "") +
|
|
156
|
+
(cwd ? "&cwd=" + encodeURIComponent(cwd) : "");
|
|
157
|
+
fetch(url)
|
|
158
|
+
.then(async function (res) {
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
var errText = await res.text().catch(function () { return ""; });
|
|
161
|
+
var j = null;
|
|
162
|
+
try {
|
|
163
|
+
j = JSON.parse(errText);
|
|
164
|
+
}
|
|
165
|
+
catch (e) { }
|
|
166
|
+
throw new Error((j && j.error) || errText || ("服务响应异常 (" + res.status + ")"));
|
|
167
|
+
}
|
|
168
|
+
return res.json();
|
|
169
|
+
})
|
|
170
|
+
.then(function (json) {
|
|
171
|
+
setLoading(false);
|
|
172
|
+
if (json && json.ok && json.data) {
|
|
173
|
+
setData(json.data);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
setError((json && json.error) || "获取 Issue 详情失败");
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
.catch(function (err) {
|
|
180
|
+
setLoading(false);
|
|
181
|
+
setError((err && err.message) || "网络请求异常");
|
|
182
|
+
});
|
|
183
|
+
}, [issueNumber, repoNameWithOwner, cwd]);
|
|
184
|
+
if (!issueNumber)
|
|
185
|
+
return null;
|
|
186
|
+
var h = React.createElement;
|
|
187
|
+
var copyLink = function (text) {
|
|
188
|
+
if (!text)
|
|
189
|
+
return;
|
|
190
|
+
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
|
191
|
+
navigator.clipboard
|
|
192
|
+
.writeText(text)
|
|
193
|
+
.then(function () {
|
|
194
|
+
setCopied(true);
|
|
195
|
+
showToast("✅ Issue 链接已复制到剪贴板");
|
|
196
|
+
setTimeout(function () { setCopied(false); }, 2000);
|
|
197
|
+
})
|
|
198
|
+
.catch(function () { });
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var stateBadge = function (d) {
|
|
202
|
+
if (!d)
|
|
203
|
+
return null;
|
|
204
|
+
var isOpen = String(d.state).toUpperCase() === "OPEN";
|
|
205
|
+
return h("span", {
|
|
206
|
+
className: "dsh-github-badge " + (isOpen ? "dsh-badge-green" : "dsh-badge-red"),
|
|
207
|
+
style: { fontSize: "10px", padding: "1px 6px", flexShrink: 0 },
|
|
208
|
+
}, isOpen ? "OPEN" : "CLOSED");
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* 元数据条:只渲染真实存在的字段。
|
|
212
|
+
* 空的「无标签 / 无指派 / 无里程碑」占位没有信息量,只会把正文推得更远。
|
|
213
|
+
*/
|
|
214
|
+
var metaChips = function (d) {
|
|
215
|
+
var chips = [];
|
|
216
|
+
var reason = d.stateReason ? ISSUE_STATE_REASON_LABEL[String(d.stateReason).toUpperCase()] : "";
|
|
217
|
+
if (reason) {
|
|
218
|
+
chips.push(h("span", {
|
|
219
|
+
key: "reason",
|
|
220
|
+
className: "dsh-github-issue-chip",
|
|
221
|
+
title: "关闭原因 (stateReason): " + d.stateReason,
|
|
222
|
+
}, "✓ " + reason));
|
|
223
|
+
}
|
|
224
|
+
if (d.isPinned) {
|
|
225
|
+
chips.push(h("span", { key: "pin", className: "dsh-github-issue-chip" }, "📌 置顶"));
|
|
226
|
+
}
|
|
227
|
+
if (d.issueType) {
|
|
228
|
+
chips.push(h("span", { key: "type", className: "dsh-github-issue-chip" }, "🏷️ " + d.issueType));
|
|
229
|
+
}
|
|
230
|
+
(Array.isArray(d.labels) ? d.labels : []).forEach(function (l, i) {
|
|
231
|
+
chips.push(h("span", {
|
|
232
|
+
key: "label-" + i + "-" + l.name,
|
|
233
|
+
className: "dsh-github-issue-chip dsh-github-issue-label-chip",
|
|
234
|
+
style: {
|
|
235
|
+
background: labelRgba(l.color, 0.14),
|
|
236
|
+
borderColor: labelRgba(l.color, 0.45),
|
|
237
|
+
},
|
|
238
|
+
title: l.description || l.name,
|
|
239
|
+
}, h("span", {
|
|
240
|
+
className: "dsh-github-issue-label-dot",
|
|
241
|
+
style: { background: labelRgba(l.color, 0.9) },
|
|
242
|
+
}), l.name));
|
|
243
|
+
});
|
|
244
|
+
(Array.isArray(d.assignees) ? d.assignees : []).forEach(function (a, i) {
|
|
245
|
+
chips.push(h("span", { key: "asg-" + i, className: "dsh-github-issue-chip" }, "👤 " + a.login));
|
|
246
|
+
});
|
|
247
|
+
if (d.milestone && d.milestone.title) {
|
|
248
|
+
var dueText = d.milestone.dueOn ? formatDueDate(d.milestone.dueOn) : '';
|
|
249
|
+
chips.push(h("span", {
|
|
250
|
+
key: "milestone",
|
|
251
|
+
className: "dsh-github-issue-chip" + (dueText.indexOf('已逾期') >= 0 ? " dsh-github-issue-chip-overdue" : ""),
|
|
252
|
+
title: d.milestone.dueOn ? "截止 " + d.milestone.dueOn : "未设置截止日期",
|
|
253
|
+
}, "🏁 " + d.milestone.title + (dueText ? " · " + dueText : "")));
|
|
254
|
+
}
|
|
255
|
+
if (d.subIssues && Number(d.subIssues.total) > 0) {
|
|
256
|
+
chips.push(h("span", { key: "sub", className: "dsh-github-issue-chip" }, "🧩 子议题 " + d.subIssues.completed + "/" + d.subIssues.total));
|
|
257
|
+
}
|
|
258
|
+
(Array.isArray(d.linkedPrs) ? d.linkedPrs : []).forEach(function (p, i) {
|
|
259
|
+
chips.push(h("a", {
|
|
260
|
+
key: "pr-" + i,
|
|
261
|
+
className: "dsh-github-issue-chip dsh-github-issue-chip-link",
|
|
262
|
+
href: p.url,
|
|
263
|
+
target: "_blank",
|
|
264
|
+
rel: "noopener noreferrer",
|
|
265
|
+
title: "该 Issue 被此 Pull Request 关联/关闭",
|
|
266
|
+
}, "🔗 PR #" + p.number));
|
|
267
|
+
});
|
|
268
|
+
chips.push(h(IssueImageList, { key: "images", urls: d.images || [] }));
|
|
269
|
+
return chips.length > 0 ? h("div", { className: "dsh-github-issue-meta" }, ...chips) : null;
|
|
270
|
+
};
|
|
271
|
+
var bodyCard = function (d) {
|
|
272
|
+
return h("div", { className: "dsh-github-issue-body-card" }, h("div", { className: "dsh-github-issue-comment-header" }, h("strong", null, "@" + d.author.login), h("span", { style: { fontSize: "11px", opacity: 0.6 } }, "提单说明 (Issue Body)")), h("div", { className: "dsh-github-issue-body-content" }, d.body
|
|
273
|
+
? h(CollapsibleBody, { text: d.body, defaultExpanded: true, idPrefix: "issue-body" })
|
|
274
|
+
: h("span", { style: { opacity: 0.5, fontStyle: "italic" } }, "(该 Issue 未填写正文)")));
|
|
275
|
+
};
|
|
276
|
+
return h("div", { className: "dsh-github-modal-overlay", onClick: onClose, style: { zIndex: 100000 } }, h("div", {
|
|
277
|
+
className: "dsh-github-modal-dialog dsh-github-issue-modal-dialog",
|
|
278
|
+
role: "dialog",
|
|
279
|
+
"aria-modal": "true",
|
|
280
|
+
"aria-label": "Issue #" + issueNumber + " 详情",
|
|
281
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
282
|
+
},
|
|
283
|
+
// 头部
|
|
284
|
+
h("div", { className: "dsh-github-modal-header dsh-github-issue-modal-header" }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", minWidth: 0, flex: 1 } }, renderOctocat(18, "#1f883d"), h("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 } }, h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" } }, h("h3", {
|
|
285
|
+
style: {
|
|
286
|
+
margin: 0,
|
|
287
|
+
fontSize: "14px",
|
|
288
|
+
fontWeight: 600,
|
|
289
|
+
overflow: "hidden",
|
|
290
|
+
textOverflow: "ellipsis",
|
|
291
|
+
whiteSpace: "nowrap",
|
|
292
|
+
},
|
|
293
|
+
title: data ? "#" + data.number + " " + data.title : "Issue #" + issueNumber,
|
|
294
|
+
}, data ? "#" + data.number + " " + data.title : "Issue #" + issueNumber), stateBadge(data)), data
|
|
295
|
+
? h("div", {
|
|
296
|
+
style: {
|
|
297
|
+
fontSize: "11px",
|
|
298
|
+
color: "var(--dsw-alias-label-secondary, #656d76)",
|
|
299
|
+
display: "flex",
|
|
300
|
+
gap: "6px",
|
|
301
|
+
alignItems: "center",
|
|
302
|
+
flexWrap: "wrap",
|
|
303
|
+
},
|
|
304
|
+
}, h("span", null, "👤 @" + data.author.login), data.createdAt ? h("span", null, "· 提单于 " + formatRelativeTime(data.createdAt)) : null, data.closedAt ? h("span", null, "· 关闭于 " + formatRelativeTime(data.closedAt)) : null, repoNameWithOwner
|
|
305
|
+
? h("span", { style: { fontFamily: "monospace", opacity: 0.85 } }, repoNameWithOwner)
|
|
306
|
+
: null)
|
|
307
|
+
: null)), h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 } }, data && data.url
|
|
308
|
+
? h("button", {
|
|
309
|
+
type: "button",
|
|
310
|
+
className: "dsh-github-btn-sm",
|
|
311
|
+
style: { padding: "4px 8px", fontSize: "11px" },
|
|
312
|
+
onClick: function () { copyLink(data.url); },
|
|
313
|
+
title: "复制该 Issue 网页链接",
|
|
314
|
+
}, copied ? "✓ 已复制" : "📋 复制链接")
|
|
315
|
+
: null, data && data.url
|
|
316
|
+
? h("a", {
|
|
317
|
+
href: data.url,
|
|
318
|
+
target: "_blank",
|
|
319
|
+
rel: "noopener noreferrer",
|
|
320
|
+
className: "dsh-github-btn-sm",
|
|
321
|
+
style: { padding: "4px 8px", fontSize: "11px", textDecoration: "none", color: "inherit" },
|
|
322
|
+
title: "在系统默认浏览器中打开该 Issue",
|
|
323
|
+
}, "↗ 浏览器打开")
|
|
324
|
+
: null, h("button", {
|
|
325
|
+
type: "button",
|
|
326
|
+
"aria-label": "关闭 (Esc)",
|
|
327
|
+
style: {
|
|
328
|
+
background: "none",
|
|
329
|
+
border: "none",
|
|
330
|
+
cursor: "pointer",
|
|
331
|
+
fontSize: "18px",
|
|
332
|
+
color: "var(--dsw-alias-label-secondary, #656d76)",
|
|
333
|
+
padding: "0 6px",
|
|
334
|
+
},
|
|
335
|
+
onClick: onClose,
|
|
336
|
+
title: "关闭 (Esc)",
|
|
337
|
+
}, "✕"))),
|
|
338
|
+
// 主体
|
|
339
|
+
loading
|
|
340
|
+
? h("div", { style: { padding: "64px 0", textAlign: "center", fontSize: "13px", opacity: 0.7 } }, "⏳ 正在加载 Issue 详情与讨论…")
|
|
341
|
+
: error
|
|
342
|
+
? h("div", {
|
|
343
|
+
style: {
|
|
344
|
+
padding: "24px",
|
|
345
|
+
margin: "16px",
|
|
346
|
+
borderRadius: "8px",
|
|
347
|
+
background: "rgba(248,81,73,0.08)",
|
|
348
|
+
border: "1px solid rgba(248,81,73,0.3)",
|
|
349
|
+
},
|
|
350
|
+
}, h("div", { style: { fontWeight: 600, color: "#f85149", marginBottom: "6px" } }, "⚠️ 读取 Issue 详情失败"), h("div", { style: { fontSize: "12px", opacity: 0.85, lineHeight: 1.6 } }, error), repoNameWithOwner
|
|
351
|
+
? h("a", {
|
|
352
|
+
href: "https://github.com/" + repoNameWithOwner + "/issues/" + issueNumber,
|
|
353
|
+
target: "_blank",
|
|
354
|
+
rel: "noopener noreferrer",
|
|
355
|
+
className: "dsh-github-btn-sm",
|
|
356
|
+
style: { display: "inline-block", marginTop: "12px", padding: "4px 10px", fontSize: "11px", textDecoration: "none" },
|
|
357
|
+
}, "↗ 在浏览器中查看")
|
|
358
|
+
: null)
|
|
359
|
+
: data
|
|
360
|
+
? h("div", { className: "dsh-github-issue-conversation-pane" }, metaChips(data), bodyCard(data), h("div", { className: "dsh-github-issue-timeline-title" }, "💬 讨论与评论时间线 (" + (data.comments ? data.comments.length : 0) + ")"), data.comments && data.comments.length > 0
|
|
361
|
+
? data.comments.map(function (c) {
|
|
362
|
+
return h(IssueCommentCard, { key: c.id || c.createdAt, comment: c });
|
|
363
|
+
})
|
|
364
|
+
: h("div", { className: "dsh-github-issue-empty-timeline" }, "💬 暂无讨论与评论——这是一个还没有人回复的议题。"))
|
|
365
|
+
: null));
|
|
366
|
+
}
|