dsh-github-flow 0.4.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 +91 -12
- package/README.md +91 -12
- package/docs/UPGRADE-NOTES.md +9 -12
- package/docs/architecture.md +63 -29
- package/docs/images/1.png +0 -0
- package/lib/api/routes-actions.js +268 -5
- 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 +9 -4
- package/lib/client.js +5293 -942
- package/lib/commands/gh.d.ts +6 -1
- package/lib/commands/gh.js +144 -71
- package/lib/executor/error-classifier.d.ts +55 -0
- package/lib/executor/error-classifier.js +199 -0
- package/lib/executor/git-parser.d.ts +157 -1
- package/lib/executor/git-parser.js +513 -4
- package/lib/executor.d.ts +147 -4
- package/lib/executor.js +866 -77
- package/lib/main.js +28 -4
- package/lib/renderers/agent-task.d.ts +16 -0
- package/lib/renderers/agent-task.js +107 -0
- package/lib/renderers/codespace.d.ts +16 -0
- package/lib/renderers/codespace.js +103 -0
- package/lib/renderers/discussion.d.ts +16 -0
- package/lib/renderers/discussion.js +101 -0
- package/lib/renderers/markdown.d.ts +4 -0
- package/lib/renderers/markdown.js +4 -0
- package/lib/renderers/project.d.ts +20 -0
- package/lib/renderers/project.js +128 -0
- package/lib/renderers/skill.d.ts +30 -0
- package/lib/renderers/skill.js +180 -0
- package/lib/renderers/status.d.ts +5 -0
- package/lib/renderers/status.js +52 -0
- package/lib/telemetry.d.ts +70 -0
- package/lib/telemetry.js +161 -0
- package/lib/tools/agent-task.d.ts +54 -0
- package/lib/tools/agent-task.js +135 -0
- package/lib/tools/codespace.d.ts +51 -0
- package/lib/tools/codespace.js +154 -0
- package/lib/tools/discussion.d.ts +70 -0
- package/lib/tools/discussion.js +195 -0
- package/lib/tools/help.js +129 -14
- package/lib/tools/image-utils.d.ts +27 -0
- package/lib/tools/image-utils.js +73 -3
- package/lib/tools/project.d.ts +80 -0
- package/lib/tools/project.js +241 -0
- package/lib/tools/repo.js +13 -1
- package/lib/tools/skill.d.ts +24 -1
- package/lib/tools/skill.js +115 -5
- package/lib/tools/status.d.ts +3 -2
- package/lib/tools/status.js +9 -4
- package/lib/types.d.ts +336 -0
- package/lib/ui/commit-modal.js +144 -14
- package/lib/ui/help-modal.d.ts +21 -0
- package/lib/ui/help-modal.js +300 -0
- 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 +56 -0
- package/lib/ui/storage.js +163 -0
- package/lib/ui/styles.d.ts +1 -1
- package/lib/ui/styles.js +1219 -0
- package/lib/ui/sync-settings-modal.js +1 -1
- package/lib/ui/upgrade-card.js +39 -14
- package/lib/ui/utils.d.ts +35 -0
- package/lib/ui/utils.js +172 -0
- package/lib/ui/workspace-panel.js +198 -31
- package/lib/upgrade-manager.d.ts +5 -1
- package/lib/upgrade-manager.js +24 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 轻量 Markdown 解析与 React 渲染(零依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须自己写:本插件没有前端 dependencies,bundle 机制只内联
|
|
5
|
+
* lib/ui/*.js 且不做 import 解析,因此 marked / markdown-it 都无法引入。
|
|
6
|
+
* 服务端的 src/renderers/* 只产出给模型看的 Markdown 文本,不能复用为 DOM。
|
|
7
|
+
*
|
|
8
|
+
* 设计取舍:
|
|
9
|
+
* - 只支持 GitHub PR/Issue 实际会用到的语法子集(标题/粗斜体/行内码/
|
|
10
|
+
* 围栏代码块/列表/引用/分隔线/链接)。表格、图片、内嵌 HTML 明确不做。
|
|
11
|
+
* - 无法识别的语法**降级为纯文本**,绝不抛错——弹窗是只读展示,
|
|
12
|
+
* 因为一段奇怪文本崩掉整块内容是不可接受的。
|
|
13
|
+
* - 全程 React.createElement,**从不使用 dangerouslySetInnerHTML**:
|
|
14
|
+
* 用户写的 <script> 会被当作纯文本渲染,天然免疫 XSS。
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* 链接协议白名单。
|
|
18
|
+
*
|
|
19
|
+
* 为什么必须白名单:Markdown 的链接目标是**用户可控文本**。
|
|
20
|
+
* `[点我](javascript:alert(1))` 若直接渲染成 href,点击即执行脚本。
|
|
21
|
+
* 只放行 http/https/mailto,其余降级为纯文本(保留文字,不生成 <a>)。
|
|
22
|
+
*/
|
|
23
|
+
function isSafeHref(href) {
|
|
24
|
+
if (typeof href !== 'string')
|
|
25
|
+
return false;
|
|
26
|
+
const trimmed = href.trim();
|
|
27
|
+
if (!trimmed)
|
|
28
|
+
return false;
|
|
29
|
+
if (/^(https?:\/\/|mailto:)/i.test(trimmed))
|
|
30
|
+
return true;
|
|
31
|
+
// 站内相对路径(如 /owner/repo/issues/1)视为安全,但必须以单个 / 开头
|
|
32
|
+
if (/^\/(?!\/)/.test(trimmed))
|
|
33
|
+
return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 解析行内语法:`code` / **strong** / *em* / [text](url)。
|
|
38
|
+
*
|
|
39
|
+
* 用单遍扫描而非多次 replace 的原因:
|
|
40
|
+
* 行内代码里的内容**不应再被解析**(`**a**` 是字面量),
|
|
41
|
+
* 分段替换很容易把代码块内的星号也吃掉。手写扫描能保证优先级正确:
|
|
42
|
+
* 行内代码 > 链接 > 粗体 > 斜体。
|
|
43
|
+
*/
|
|
44
|
+
export function parseInline(src) {
|
|
45
|
+
const out = [];
|
|
46
|
+
const text = String(src ?? '');
|
|
47
|
+
let buf = '';
|
|
48
|
+
let i = 0;
|
|
49
|
+
const flush = () => {
|
|
50
|
+
if (buf) {
|
|
51
|
+
out.push({ type: 'text', text: buf });
|
|
52
|
+
buf = '';
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
while (i < text.length) {
|
|
56
|
+
const ch = text[i];
|
|
57
|
+
// ---- 行内代码:最高优先级,内部不再解析任何语法 ----
|
|
58
|
+
if (ch === '`') {
|
|
59
|
+
const close = text.indexOf('`', i + 1);
|
|
60
|
+
if (close > i) {
|
|
61
|
+
flush();
|
|
62
|
+
out.push({ type: 'code', text: text.slice(i + 1, close) });
|
|
63
|
+
i = close + 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ---- 链接:[text](url) ----
|
|
68
|
+
if (ch === '[') {
|
|
69
|
+
const closeBracket = text.indexOf(']', i + 1);
|
|
70
|
+
if (closeBracket > i && text[closeBracket + 1] === '(') {
|
|
71
|
+
const closeParen = text.indexOf(')', closeBracket + 2);
|
|
72
|
+
if (closeParen > closeBracket) {
|
|
73
|
+
const label = text.slice(i + 1, closeBracket);
|
|
74
|
+
const href = text.slice(closeBracket + 2, closeParen);
|
|
75
|
+
flush();
|
|
76
|
+
if (isSafeHref(href)) {
|
|
77
|
+
out.push({ type: 'link', href: href.trim(), children: [{ type: 'text', text: label }] });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// 不安全协议:保留可见文字,但不生成可点链接
|
|
81
|
+
out.push({ type: 'text', text: label });
|
|
82
|
+
}
|
|
83
|
+
i = closeParen + 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ---- 粗体:**text** 或 __text__ ----
|
|
89
|
+
if ((ch === '*' && text[i + 1] === '*') || (ch === '_' && text[i + 1] === '_')) {
|
|
90
|
+
const mark = ch + ch;
|
|
91
|
+
const close = text.indexOf(mark, i + 2);
|
|
92
|
+
if (close > i + 2) {
|
|
93
|
+
flush();
|
|
94
|
+
out.push({ type: 'strong', children: parseInline(text.slice(i + 2, close)) });
|
|
95
|
+
i = close + 2;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ---- 斜体:*text* 或 _text_(单字符,且不能被上面粗体规则截获) ----
|
|
100
|
+
if (ch === '*' || ch === '_') {
|
|
101
|
+
// 避免把列表标记或孤立的 * 当斜体:要求闭合符前后都不是同一字符
|
|
102
|
+
const close = text.indexOf(ch, i + 1);
|
|
103
|
+
if (close > i + 1 && text[close + 1] !== ch && text[close - 1] !== ch) {
|
|
104
|
+
flush();
|
|
105
|
+
out.push({ type: 'em', children: parseInline(text.slice(i + 1, close)) });
|
|
106
|
+
i = close + 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
buf += ch;
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
flush();
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/** 判断一行是否是围栏代码块标记,返回 { fence, lang } 或 null */
|
|
117
|
+
function matchFence(line) {
|
|
118
|
+
const m = /^\s*(```+|~~~+)\s*([A-Za-z0-9_+-]*)\s*$/.exec(line);
|
|
119
|
+
if (!m)
|
|
120
|
+
return null;
|
|
121
|
+
return { fence: m[1], lang: m[2] || '' };
|
|
122
|
+
}
|
|
123
|
+
/** 判断一行是否是列表项,返回 { marker, content, ordered } 或 null */
|
|
124
|
+
function matchListItem(line) {
|
|
125
|
+
const m = /^(\s*)([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
126
|
+
if (!m)
|
|
127
|
+
return null;
|
|
128
|
+
return { marker: m[2], content: m[3], ordered: /\d+\./.test(m[2]) };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 把 Markdown 源文本解析为块级 AST。
|
|
132
|
+
*
|
|
133
|
+
* 纯函数、无 I/O,可被单测直接覆盖——这是把「渲染正确性」从浏览器里
|
|
134
|
+
* 剥离出来断言的关键。任何异常输入都返回空数组,绝不抛错。
|
|
135
|
+
*/
|
|
136
|
+
export function parseMarkdownBlocks(src) {
|
|
137
|
+
const blocks = [];
|
|
138
|
+
if (typeof src !== 'string' || !src.trim())
|
|
139
|
+
return blocks;
|
|
140
|
+
const lines = src.replace(/\r\n/g, '\n').split('\n');
|
|
141
|
+
let i = 0;
|
|
142
|
+
while (i < lines.length) {
|
|
143
|
+
const line = lines[i];
|
|
144
|
+
// ---- 空行 ----
|
|
145
|
+
if (!line.trim()) {
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
// ---- 围栏代码块 ----
|
|
150
|
+
const fence = matchFence(line);
|
|
151
|
+
if (fence) {
|
|
152
|
+
const codeLines = [];
|
|
153
|
+
i++;
|
|
154
|
+
// 一直读到同种围栏闭合;未闭合时读到末尾(容错,不报错)
|
|
155
|
+
while (i < lines.length) {
|
|
156
|
+
const maybeClose = matchFence(lines[i]);
|
|
157
|
+
if (maybeClose && maybeClose.fence[0] === fence.fence[0] && maybeClose.fence.length >= fence.fence.length) {
|
|
158
|
+
i++;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
codeLines.push(lines[i]);
|
|
162
|
+
i++;
|
|
163
|
+
}
|
|
164
|
+
blocks.push({ type: 'code', lang: fence.lang, text: codeLines.join('\n') });
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
// ---- 标题 ----
|
|
168
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
169
|
+
if (heading) {
|
|
170
|
+
blocks.push({ type: 'heading', level: heading[1].length, children: parseInline(heading[2].trim()) });
|
|
171
|
+
i++;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
// ---- 分隔线 ----
|
|
175
|
+
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
|
|
176
|
+
blocks.push({ type: 'hr' });
|
|
177
|
+
i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// ---- 引用:连续 > 行合并为一个 blockquote ----
|
|
181
|
+
if (/^\s*>\s?/.test(line)) {
|
|
182
|
+
const quoteLines = [];
|
|
183
|
+
while (i < lines.length && /^\s*>\s?/.test(lines[i])) {
|
|
184
|
+
quoteLines.push(lines[i].replace(/^\s*>\s?/, ''));
|
|
185
|
+
i++;
|
|
186
|
+
}
|
|
187
|
+
blocks.push({ type: 'quote', children: parseInline(quoteLines.join('\n').trim()) });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
// ---- 列表:连续同类型(有序/无序)合并为一个 list ----
|
|
191
|
+
const firstItem = matchListItem(line);
|
|
192
|
+
if (firstItem) {
|
|
193
|
+
const ordered = firstItem.ordered;
|
|
194
|
+
const items = [];
|
|
195
|
+
while (i < lines.length) {
|
|
196
|
+
const item = matchListItem(lines[i]);
|
|
197
|
+
if (!item || item.ordered !== ordered)
|
|
198
|
+
break;
|
|
199
|
+
items.push({ marker: item.marker, children: parseInline(item.content) });
|
|
200
|
+
i++;
|
|
201
|
+
}
|
|
202
|
+
blocks.push({ type: 'list', ordered, items });
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// ---- 段落:连续非空行合并 ----
|
|
206
|
+
const paraLines = [];
|
|
207
|
+
while (i < lines.length) {
|
|
208
|
+
const cur = lines[i];
|
|
209
|
+
if (!cur.trim())
|
|
210
|
+
break;
|
|
211
|
+
// 遇到块级语法起始就停下,交给下一轮解析
|
|
212
|
+
if (matchFence(cur) || /^(#{1,6})\s+/.test(cur) || /^\s*>\s?/.test(cur) || matchListItem(cur) || /^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(cur)) {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
paraLines.push(cur);
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
// 段落内的硬换行保留为 text 中的 \n,渲染时用 white-space: pre-wrap 呈现
|
|
219
|
+
if (paraLines.length > 0) {
|
|
220
|
+
blocks.push({ type: 'paragraph', children: parseInline(paraLines.join('\n')) });
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// 兜底:当前行以某种方式无法消费时跳过,防止死循环
|
|
224
|
+
i++;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return blocks;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 把 AST 渲染为 React 元素数组。
|
|
231
|
+
*
|
|
232
|
+
* @param h React.createElement
|
|
233
|
+
* @param blocks parseMarkdownBlocks 的产物
|
|
234
|
+
*/
|
|
235
|
+
export function renderMarkdownBlocks(h, blocks) {
|
|
236
|
+
if (!Array.isArray(blocks))
|
|
237
|
+
return [];
|
|
238
|
+
const renderInline = (nodes, keyPrefix) => {
|
|
239
|
+
if (!Array.isArray(nodes))
|
|
240
|
+
return [];
|
|
241
|
+
return nodes.map((n, idx) => {
|
|
242
|
+
const key = `${keyPrefix}-${idx}`;
|
|
243
|
+
switch (n.type) {
|
|
244
|
+
case 'code':
|
|
245
|
+
return h('code', { key, className: 'dsh-github-md-inline-code' }, n.text ?? '');
|
|
246
|
+
case 'strong':
|
|
247
|
+
return h('strong', { key }, renderInline(n.children, key));
|
|
248
|
+
case 'em':
|
|
249
|
+
return h('em', { key }, renderInline(n.children, key));
|
|
250
|
+
case 'link':
|
|
251
|
+
return h('a', {
|
|
252
|
+
key,
|
|
253
|
+
href: n.href,
|
|
254
|
+
target: '_blank',
|
|
255
|
+
rel: 'noopener noreferrer',
|
|
256
|
+
className: 'dsh-github-md-link',
|
|
257
|
+
}, renderInline(n.children, key));
|
|
258
|
+
default:
|
|
259
|
+
return h('span', { key }, n.text ?? '');
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
};
|
|
263
|
+
return blocks.map((b, idx) => {
|
|
264
|
+
const key = `md-${idx}`;
|
|
265
|
+
switch (b.type) {
|
|
266
|
+
case 'heading': {
|
|
267
|
+
const tag = `h${Math.min(Math.max(b.level || 1, 1), 6)}`;
|
|
268
|
+
return h(tag, { key, className: 'dsh-github-md-heading' }, renderInline(b.children, key));
|
|
269
|
+
}
|
|
270
|
+
case 'code':
|
|
271
|
+
return h('pre', { key, className: 'dsh-github-md-pre' }, h('code', { className: b.lang ? `language-${b.lang}` : undefined }, b.text ?? ''));
|
|
272
|
+
case 'list': {
|
|
273
|
+
const tag = b.ordered ? 'ol' : 'ul';
|
|
274
|
+
return h(tag, { key, className: 'dsh-github-md-list' }, (b.items || []).map((item, ii) => {
|
|
275
|
+
const itemKey = `${key}-${ii}`;
|
|
276
|
+
// 任务列表:- [ ] / - [x]
|
|
277
|
+
const raw = (item.children || []).map((c) => c.text || '').join('');
|
|
278
|
+
const task = /^\[( |x|X)\]\s*/.exec(raw);
|
|
279
|
+
if (task) {
|
|
280
|
+
const checked = task[1].toLowerCase() === 'x';
|
|
281
|
+
const rest = raw.replace(/^\[( |x|X)\]\s*/, '');
|
|
282
|
+
return h('li', { key: itemKey, className: 'dsh-github-md-task' }, h('span', { className: 'dsh-github-md-checkbox' }, checked ? '☑' : '☐'), h('span', null, rest));
|
|
283
|
+
}
|
|
284
|
+
return h('li', { key: itemKey }, renderInline(item.children, itemKey));
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
case 'quote':
|
|
288
|
+
return h('blockquote', { key, className: 'dsh-github-md-quote' }, renderInline(b.children, key));
|
|
289
|
+
case 'hr':
|
|
290
|
+
return h('hr', { key, className: 'dsh-github-md-hr' });
|
|
291
|
+
default:
|
|
292
|
+
return h('p', { key, className: 'dsh-github-md-p' }, renderInline(b.children, key));
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 估算一段 Markdown 渲染后的行数,用于决定是否折叠。
|
|
298
|
+
*
|
|
299
|
+
* 为什么用估算而不是测量 DOM:getBoundingClientRect 需要 ref + 二次渲染,
|
|
300
|
+
* 在弹窗内会造成可见的布局抖动。按换行数 + 长行折行数估算已足够准确,
|
|
301
|
+
* 且成本为零。阈值取保守值——宁可多折一次,也不要漏折导致内容淹没页面。
|
|
302
|
+
*/
|
|
303
|
+
export function estimateContentLines(src, charsPerLine = 80) {
|
|
304
|
+
if (typeof src !== 'string' || !src)
|
|
305
|
+
return 0;
|
|
306
|
+
let lines = 0;
|
|
307
|
+
for (const raw of src.replace(/\r\n/g, '\n').split('\n')) {
|
|
308
|
+
const len = raw.length;
|
|
309
|
+
lines += len === 0 ? 1 : Math.ceil(len / charsPerLine);
|
|
310
|
+
}
|
|
311
|
+
return lines;
|
|
312
|
+
}
|
|
@@ -8,3 +8,15 @@ export declare function NetworkAlertBanner(props: {
|
|
|
8
8
|
onRefresh?: () => void;
|
|
9
9
|
onOpenProxy?: () => void;
|
|
10
10
|
}): any;
|
|
11
|
+
/**
|
|
12
|
+
* 确定性降级说明条(非错误)。
|
|
13
|
+
*
|
|
14
|
+
* 为什么要与 NetworkAlertBanner 分开:仓库关闭了 Issues、未启用 Discussions
|
|
15
|
+
* 属于仓库的**稳定配置事实**,不是故障。把它塞进「网络连接失败」横幅会产生
|
|
16
|
+
* 三重误导——用户去查网线、客户端启动阶梯重试、自动同步每 5 分钟空转一轮。
|
|
17
|
+
* 这里只做安静陈述:一句话说明为什么列表是空的,不提供重试按钮。
|
|
18
|
+
*/
|
|
19
|
+
export declare function RepoNoticeBar(props: {
|
|
20
|
+
isRightbar?: boolean;
|
|
21
|
+
notices?: string[] | null;
|
|
22
|
+
}): any;
|
package/lib/ui/network-banner.js
CHANGED
|
@@ -52,3 +52,38 @@ export function NetworkAlertBanner(props) {
|
|
|
52
52
|
style: { padding: "3px 8px", fontSize: "11px", cursor: "pointer" },
|
|
53
53
|
}, "⚙️ 配置代理"))), h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", lineHeight: 1.4 } }, reason + (lastSyncedAt ? `(当前数据快照同步于 ${formatTime(lastSyncedAt)})` : "")));
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* 确定性降级说明条(非错误)。
|
|
57
|
+
*
|
|
58
|
+
* 为什么要与 NetworkAlertBanner 分开:仓库关闭了 Issues、未启用 Discussions
|
|
59
|
+
* 属于仓库的**稳定配置事实**,不是故障。把它塞进「网络连接失败」横幅会产生
|
|
60
|
+
* 三重误导——用户去查网线、客户端启动阶梯重试、自动同步每 5 分钟空转一轮。
|
|
61
|
+
* 这里只做安静陈述:一句话说明为什么列表是空的,不提供重试按钮。
|
|
62
|
+
*/
|
|
63
|
+
export function RepoNoticeBar(props) {
|
|
64
|
+
var notices = Array.isArray(props.notices) ? props.notices.filter(Boolean) : [];
|
|
65
|
+
if (notices.length === 0)
|
|
66
|
+
return null;
|
|
67
|
+
var isRightbar = Boolean(props.isRightbar);
|
|
68
|
+
var h = React.createElement;
|
|
69
|
+
return h("div", {
|
|
70
|
+
className: "dsh-github-repo-notice",
|
|
71
|
+
style: {
|
|
72
|
+
display: "flex",
|
|
73
|
+
flexDirection: "column",
|
|
74
|
+
gap: "3px",
|
|
75
|
+
padding: isRightbar ? "8px 10px" : "10px 14px",
|
|
76
|
+
marginBottom: "10px",
|
|
77
|
+
borderRadius: "8px",
|
|
78
|
+
background: "rgba(84, 174, 255, 0.10)",
|
|
79
|
+
border: "1px solid rgba(84, 174, 255, 0.35)",
|
|
80
|
+
fontSize: "12px",
|
|
81
|
+
color: "var(--dsw-alias-label-primary, #1f2328)",
|
|
82
|
+
},
|
|
83
|
+
}, h("div", { style: { display: "flex", alignItems: "center", gap: "6px", fontWeight: 600 } }, h("span", null, "ℹ️"), h("span", null, "仓库配置提示")), ...notices.map(function (n, i) {
|
|
84
|
+
return h("div", {
|
|
85
|
+
key: "notice-" + i,
|
|
86
|
+
style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", lineHeight: 1.5 },
|
|
87
|
+
}, n);
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface PrDetailModalProps {
|
|
2
|
+
prNumber: number;
|
|
3
|
+
repoNameWithOwner?: string;
|
|
4
|
+
cwd?: string | null;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
showToast?: (msg: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* PR 在线完整审阅与代码比对工作台模态弹窗组件。
|
|
10
|
+
*
|
|
11
|
+
* 架构设计:
|
|
12
|
+
* 1. 单次网络往返拉取元数据与 Diff 切片,按文件切换比对 0ms 瞬间响应;
|
|
13
|
+
* 2. 覆盖作者卡片、状态徽章、分支流向、正文描述、CI 与审查决定;
|
|
14
|
+
* 3. 评论回复流 Timeline(Issue Comments + Review 评注);
|
|
15
|
+
* 4. 变动文件清单与行级高保真 Unified Diff 比对(绿色新增、红色删除、灰底 Hunk)。
|
|
16
|
+
*/
|
|
17
|
+
export declare function PrDetailModal(props: PrDetailModalProps): any;
|