@yangzhe1991/dsh-code-review 0.1.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/lib/client.js ADDED
@@ -0,0 +1,1416 @@
1
+ window.__ModuleLoader__.load({ id: "@yangzhe1991/dsh-code-review", factory: (require) => {
2
+ var module = { exports: {} }; var exports = module.exports;
3
+ "use strict";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
23
+
24
+ // src/client/index.tsx
25
+ var index_exports = {};
26
+ __export(index_exports, {
27
+ apply: () => apply,
28
+ inject: () => inject
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+ var import_react = require("react");
32
+
33
+ // src/client/diff-parse.ts
34
+ function displayPath(path) {
35
+ if (path === "/dev/null") return path;
36
+ return path.replace(/^[ab]\//, "");
37
+ }
38
+ function isDiffText(text) {
39
+ const lines = text.split("\n");
40
+ if (lines.some((line) => line.startsWith("diff --git "))) return true;
41
+ const hasHunk = lines.some((line) => /^@@ -\d+/.test(line));
42
+ const hasFileMark = lines.some((line) => /^(---|\+\+\+) \S/.test(line));
43
+ return hasHunk && hasFileMark;
44
+ }
45
+ function parseGitDiff(text) {
46
+ const lines = text.split(/\r?\n/);
47
+ const files = [];
48
+ let inHunk = false;
49
+ let oldNo = 0;
50
+ let newNo = 0;
51
+ let pendingDels = [];
52
+ let lastKind = null;
53
+ let lastNewRow = null;
54
+ function current() {
55
+ return files.length > 0 ? files[files.length - 1] : null;
56
+ }
57
+ function flushPending() {
58
+ const file = current();
59
+ if (file === null || pendingDels.length === 0) return;
60
+ for (const del of pendingDels) {
61
+ file.rows.push({
62
+ kind: "del",
63
+ oldNo: del.no,
64
+ oldText: del.text,
65
+ oldNoEol: del.noEol
66
+ });
67
+ lastNewRow = null;
68
+ }
69
+ pendingDels = [];
70
+ }
71
+ function startFile(oldPath, newPath) {
72
+ flushPending();
73
+ inHunk = false;
74
+ lastKind = null;
75
+ lastNewRow = null;
76
+ files.push({
77
+ oldPath,
78
+ newPath,
79
+ status: "modified",
80
+ meta: [],
81
+ rows: []
82
+ });
83
+ }
84
+ function openHunk(line) {
85
+ const match = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/.exec(line);
86
+ if (match === null) return false;
87
+ flushPending();
88
+ oldNo = Number(match[1]);
89
+ newNo = Number(match[3]);
90
+ inHunk = true;
91
+ lastKind = null;
92
+ lastNewRow = null;
93
+ current()?.rows.push({ kind: "gap", gapText: line });
94
+ return true;
95
+ }
96
+ function emitContext(text2) {
97
+ const file = current();
98
+ if (file === null) return;
99
+ const row = { kind: "ctx", oldNo, newNo, oldText: text2, newText: text2 };
100
+ file.rows.push(row);
101
+ oldNo += 1;
102
+ newNo += 1;
103
+ lastKind = "ctx";
104
+ lastNewRow = row;
105
+ }
106
+ function emitChange(del, addText) {
107
+ const file = current();
108
+ if (file === null) return;
109
+ const row = {
110
+ kind: "change",
111
+ oldNo: del.no,
112
+ newNo,
113
+ oldText: del.text,
114
+ newText: addText,
115
+ oldNoEol: del.noEol
116
+ };
117
+ file.rows.push(row);
118
+ newNo += 1;
119
+ lastKind = "add";
120
+ lastNewRow = row;
121
+ }
122
+ function emitAdd(text2) {
123
+ const file = current();
124
+ if (file === null) return;
125
+ const row = { kind: "add", newNo, newText: text2 };
126
+ file.rows.push(row);
127
+ newNo += 1;
128
+ lastKind = "add";
129
+ lastNewRow = row;
130
+ }
131
+ function markNoEol() {
132
+ if (lastKind === "del") {
133
+ const last = pendingDels[pendingDels.length - 1];
134
+ if (last !== void 0) {
135
+ last.noEol = true;
136
+ } else {
137
+ const file = current();
138
+ if (file !== null && file.rows.length > 0) {
139
+ const row = file.rows[file.rows.length - 1];
140
+ if (row.kind === "del") row.oldNoEol = true;
141
+ }
142
+ }
143
+ } else if (lastKind === "add") {
144
+ if (lastNewRow !== null) lastNewRow.newNoEol = true;
145
+ } else if (lastKind === "ctx") {
146
+ const file = current();
147
+ if (file !== null && file.rows.length > 0) {
148
+ const row = file.rows[file.rows.length - 1];
149
+ if (row.kind === "ctx") {
150
+ row.oldNoEol = true;
151
+ row.newNoEol = true;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ for (const line of lines) {
157
+ if (line === "") continue;
158
+ if (line.startsWith("diff --git ")) {
159
+ const match = /^diff --git (?:a\/)?(\S+) (?:b\/)?(\S+)$/.exec(line);
160
+ startFile(match?.[1] ?? "", match?.[2] ?? "");
161
+ continue;
162
+ }
163
+ if (line.startsWith("@@ ")) {
164
+ openHunk(line);
165
+ continue;
166
+ }
167
+ const cur = current();
168
+ if (cur === null) {
169
+ if (line.startsWith("--- ") && line.length > 4) startFile(line.slice(4), "");
170
+ continue;
171
+ }
172
+ if (!inHunk) {
173
+ if (line.startsWith("--- ") && line.length > 4) {
174
+ cur.oldPath = line.slice(4);
175
+ } else if (line.startsWith("+++ ") && line.length > 4) {
176
+ cur.newPath = line.slice(4);
177
+ } else if (line.startsWith("new file mode ")) {
178
+ cur.status = "added";
179
+ cur.meta.push(line);
180
+ } else if (line.startsWith("deleted file mode ")) {
181
+ cur.status = "deleted";
182
+ cur.meta.push(line);
183
+ } else if (line.startsWith("rename from ") || line.startsWith("rename to ")) {
184
+ cur.status = "renamed";
185
+ cur.meta.push(line);
186
+ } else if (line.startsWith("old mode ") || line.startsWith("new mode ") || line.startsWith("similarity index ") || line.startsWith("dissimilarity index ") || line.startsWith("copy from ") || line.startsWith("copy to ")) {
187
+ cur.meta.push(line);
188
+ } else if (line.startsWith("Binary files ") || line.startsWith("GIT binary patch")) {
189
+ cur.status = "binary";
190
+ cur.meta.push(line);
191
+ } else if (line.startsWith("index ")) {
192
+ cur.meta.push(line);
193
+ }
194
+ continue;
195
+ }
196
+ const kind = line[0];
197
+ const body = line.slice(1);
198
+ if (kind === " ") {
199
+ flushPending();
200
+ emitContext(body);
201
+ } else if (kind === "-") {
202
+ pendingDels.push({ text: body, no: oldNo, noEol: false });
203
+ oldNo += 1;
204
+ lastKind = "del";
205
+ } else if (kind === "+") {
206
+ const del = pendingDels.shift();
207
+ if (del !== void 0) {
208
+ emitChange(del, body);
209
+ } else {
210
+ emitAdd(body);
211
+ }
212
+ } else if (kind === "\\" && line.startsWith("\")) {
213
+ markNoEol();
214
+ } else {
215
+ flushPending();
216
+ emitContext(line);
217
+ }
218
+ }
219
+ flushPending();
220
+ return files;
221
+ }
222
+ function countStats(files) {
223
+ let added = 0;
224
+ let removed = 0;
225
+ for (const file of files) {
226
+ for (const row of file.rows) {
227
+ if (row.kind === "add" || row.kind === "change") added += 1;
228
+ if (row.kind === "del" || row.kind === "change") removed += 1;
229
+ }
230
+ }
231
+ return { added, removed, files: files.length };
232
+ }
233
+
234
+ // src/client/diff-highlight.ts
235
+ var KEYWORDS = /* @__PURE__ */ new Set([
236
+ // JS/TS
237
+ "const",
238
+ "let",
239
+ "var",
240
+ "function",
241
+ "class",
242
+ "extends",
243
+ "super",
244
+ "new",
245
+ "delete",
246
+ "typeof",
247
+ "instanceof",
248
+ "in",
249
+ "of",
250
+ "this",
251
+ "static",
252
+ "get",
253
+ "set",
254
+ "async",
255
+ "await",
256
+ "yield",
257
+ "return",
258
+ "if",
259
+ "else",
260
+ "switch",
261
+ "case",
262
+ "default",
263
+ "break",
264
+ "continue",
265
+ "try",
266
+ "catch",
267
+ "finally",
268
+ "throw",
269
+ "for",
270
+ "while",
271
+ "do",
272
+ "import",
273
+ "export",
274
+ "from",
275
+ "as",
276
+ "interface",
277
+ "type",
278
+ "enum",
279
+ "namespace",
280
+ "module",
281
+ "implements",
282
+ "public",
283
+ "private",
284
+ "protected",
285
+ "readonly",
286
+ "abstract",
287
+ "declare",
288
+ "keyof",
289
+ "infer",
290
+ "never",
291
+ "unknown",
292
+ "any",
293
+ "void",
294
+ "null",
295
+ "undefined",
296
+ "true",
297
+ "false",
298
+ "NaN",
299
+ "Infinity",
300
+ "require",
301
+ // Python
302
+ "def",
303
+ "lambda",
304
+ "pass",
305
+ "raise",
306
+ "elif",
307
+ "not",
308
+ "and",
309
+ "or",
310
+ "is",
311
+ "None",
312
+ "True",
313
+ "False",
314
+ "print",
315
+ "with",
316
+ "global",
317
+ "nonlocal",
318
+ "assert",
319
+ "del",
320
+ // Go
321
+ "go",
322
+ "func",
323
+ "package",
324
+ "struct",
325
+ "map",
326
+ "chan",
327
+ "select",
328
+ "defer",
329
+ "range",
330
+ "nil",
331
+ "goto",
332
+ "fallthrough",
333
+ // Rust
334
+ "fn",
335
+ "let",
336
+ "mut",
337
+ "pub",
338
+ "impl",
339
+ "trait",
340
+ "match",
341
+ "use",
342
+ "mod",
343
+ "self",
344
+ "crate",
345
+ "unsafe",
346
+ "where",
347
+ "loop",
348
+ "move",
349
+ "ref",
350
+ "dyn",
351
+ // Java / C 系
352
+ "int",
353
+ "float",
354
+ "double",
355
+ "char",
356
+ "boolean",
357
+ "byte",
358
+ "short",
359
+ "long",
360
+ "signed",
361
+ "unsigned",
362
+ "auto",
363
+ "register",
364
+ "extern",
365
+ "volatile",
366
+ "constexpr",
367
+ "sizeof",
368
+ "template",
369
+ "typename"
370
+ ]);
371
+ var HIGHLIGHT_EXTS = /* @__PURE__ */ new Set([
372
+ "ts",
373
+ "tsx",
374
+ "js",
375
+ "jsx",
376
+ "mjs",
377
+ "cjs",
378
+ "json",
379
+ "py",
380
+ "rb",
381
+ "go",
382
+ "rs",
383
+ "java",
384
+ "c",
385
+ "h",
386
+ "cpp",
387
+ "cc",
388
+ "cs",
389
+ "css",
390
+ "scss",
391
+ "less",
392
+ "html",
393
+ "htm",
394
+ "vue",
395
+ "xml",
396
+ "sql",
397
+ "md",
398
+ "yml",
399
+ "yaml",
400
+ "toml",
401
+ "sh",
402
+ "bash",
403
+ "zsh",
404
+ "ini"
405
+ ]);
406
+ var HASH_COMMENT_EXTS = /* @__PURE__ */ new Set(["py", "rb", "sh", "bash", "zsh", "yml", "yaml", "toml", "ini"]);
407
+ function langFor(path) {
408
+ const dot = path.lastIndexOf(".");
409
+ const ext = dot >= 0 ? path.slice(dot + 1).toLowerCase() : "";
410
+ if (ext === "" || !HIGHLIGHT_EXTS.has(ext)) return null;
411
+ return { hashComment: HASH_COMMENT_EXTS.has(ext) };
412
+ }
413
+ function isIdentStart(ch) {
414
+ return /[A-Za-z_]/.test(ch);
415
+ }
416
+ function isIdentPart(ch) {
417
+ return /[A-Za-z0-9_]/.test(ch);
418
+ }
419
+ function isDigitStart(ch) {
420
+ return /[0-9]/.test(ch);
421
+ }
422
+ function isDigitPart(ch) {
423
+ return /[0-9a-fA-FxXbBoO._]/.test(ch);
424
+ }
425
+ function tokenizeLine(text, lang) {
426
+ const tokens = [];
427
+ let plain = "";
428
+ const flushPlain = () => {
429
+ if (plain !== "") {
430
+ tokens.push({ text: plain, cls: null });
431
+ plain = "";
432
+ }
433
+ };
434
+ let i = 0;
435
+ while (i < text.length) {
436
+ const ch = text[i];
437
+ if (ch === '"' || ch === "'" || ch === "`") {
438
+ let j = i + 1;
439
+ while (j < text.length) {
440
+ if (text[j] === "\\") {
441
+ j += 2;
442
+ continue;
443
+ }
444
+ if (text[j] === ch) break;
445
+ j += 1;
446
+ }
447
+ flushPlain();
448
+ tokens.push({ text: text.slice(i, Math.min(j + 1, text.length)), cls: "tok-str" });
449
+ i = Math.min(j + 1, text.length);
450
+ continue;
451
+ }
452
+ if (ch === "/" && text[i + 1] === "/") {
453
+ flushPlain();
454
+ tokens.push({ text: text.slice(i), cls: "tok-com" });
455
+ break;
456
+ }
457
+ if (ch === "/" && text[i + 1] === "*") {
458
+ const close = text.indexOf("*/", i + 2);
459
+ const end = close === -1 ? text.length : close + 2;
460
+ flushPlain();
461
+ tokens.push({ text: text.slice(i, end), cls: "tok-com" });
462
+ i = end;
463
+ continue;
464
+ }
465
+ if (lang.hashComment && ch === "#") {
466
+ flushPlain();
467
+ tokens.push({ text: text.slice(i), cls: "tok-com" });
468
+ break;
469
+ }
470
+ if (isDigitStart(ch)) {
471
+ let j = i + 1;
472
+ while (j < text.length && isDigitPart(text[j])) j += 1;
473
+ flushPlain();
474
+ tokens.push({ text: text.slice(i, j), cls: "tok-num" });
475
+ i = j;
476
+ continue;
477
+ }
478
+ if (isIdentStart(ch)) {
479
+ let j = i + 1;
480
+ while (j < text.length && isIdentPart(text[j])) j += 1;
481
+ const word = text.slice(i, j);
482
+ flushPlain();
483
+ tokens.push({ text: word, cls: KEYWORDS.has(word) ? "tok-kw" : null });
484
+ i = j;
485
+ continue;
486
+ }
487
+ plain += ch;
488
+ i += 1;
489
+ }
490
+ flushPlain();
491
+ return tokens;
492
+ }
493
+ function diffMidRange(oldText, newText) {
494
+ const a = Array.from(oldText);
495
+ const b = Array.from(newText);
496
+ let pre = 0;
497
+ const minLen = Math.min(a.length, b.length);
498
+ while (pre < minLen && a[pre] === b[pre]) pre += 1;
499
+ let oldEnd = a.length;
500
+ let newEnd = b.length;
501
+ while (oldEnd > pre && newEnd > pre && a[oldEnd - 1] === b[newEnd - 1]) {
502
+ oldEnd -= 1;
503
+ newEnd -= 1;
504
+ }
505
+ if (pre === a.length && pre === b.length) return null;
506
+ return { oldStart: pre, oldEnd, newStart: pre, newEnd };
507
+ }
508
+
509
+ // src/client/diff-view.ts
510
+ function escapeHtml(text) {
511
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
512
+ }
513
+ var STANDALONE_CSS = `
514
+ * { box-sizing: border-box; }
515
+ body {
516
+ margin: 0;
517
+ font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);
518
+ font-size: 13px;
519
+ line-height: 22px;
520
+ color: var(--dsw-alias-label-primary);
521
+ background: var(--dsw-alias-bg-base);
522
+ }
523
+ .topbar {
524
+ position: sticky;
525
+ top: 0;
526
+ z-index: 10;
527
+ height: 36px;
528
+ display: flex;
529
+ align-items: center;
530
+ justify-content: space-between;
531
+ gap: 12px;
532
+ padding: 0 16px;
533
+ background: var(--dsw-alias-markdown-code-block);
534
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
535
+ }
536
+ .stats { font-size: 13px; color: var(--dsw-alias-label-secondary); }
537
+ .topbar-actions { display: flex; gap: 8px; }
538
+ .btn {
539
+ font-size: 12px;
540
+ line-height: 18px;
541
+ color: var(--dsw-alias-label-secondary);
542
+ background: transparent;
543
+ border: 1px solid var(--dsw-alias-border-l2);
544
+ border-radius: 8px;
545
+ padding: 2px 10px;
546
+ cursor: pointer;
547
+ font-family: inherit;
548
+ }
549
+ .btn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3); }
550
+ .sidenav {
551
+ position: fixed;
552
+ left: 0;
553
+ top: 36px;
554
+ bottom: 0;
555
+ width: 220px;
556
+ overflow-y: auto;
557
+ padding: 12px 8px;
558
+ background: var(--dsw-alias-markdown-code-block);
559
+ border-right: 1px solid var(--dsw-alias-border-l2);
560
+ }
561
+ .navitem {
562
+ display: block;
563
+ padding: 4px 12px;
564
+ font-size: 12px;
565
+ line-height: 20px;
566
+ color: var(--dsw-alias-label-secondary);
567
+ text-decoration: none;
568
+ border-radius: 6px;
569
+ overflow: hidden;
570
+ text-overflow: ellipsis;
571
+ white-space: nowrap;
572
+ }
573
+ .navitem:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1); }
574
+ main { margin-left: 220px; padding: 16px 24px 60px; }
575
+ .file { margin-bottom: 24px; background: var(--dsw-alias-markdown-code-block); border-radius: 12px; overflow: hidden; }
576
+ .filehead {
577
+ display: flex;
578
+ align-items: center;
579
+ gap: 8px;
580
+ flex-wrap: wrap;
581
+ padding: 8px 12px 4px;
582
+ font-size: 13px;
583
+ color: var(--dsw-alias-label-secondary);
584
+ user-select: none;
585
+ }
586
+ .badge {
587
+ flex: none;
588
+ font-size: 11px;
589
+ line-height: 16px;
590
+ color: var(--dsw-alias-label-tertiary);
591
+ border: 1px solid var(--dsw-alias-border-l2);
592
+ border-radius: 6px;
593
+ padding: 0 6px;
594
+ }
595
+ .path { overflow-wrap: anywhere; }
596
+ .meta { flex-basis: 100%; font-size: 11px; color: var(--dsw-alias-label-tertiary); }
597
+ .dsh-cr-grid {
598
+ display: grid;
599
+ grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);
600
+ }
601
+ .dsh-cr-row { display: contents; }
602
+ .dsh-cr-num {
603
+ padding: 0 10px 0 4px;
604
+ text-align: right;
605
+ color: var(--dsw-alias-label-tertiary);
606
+ font-variant-numeric: tabular-nums;
607
+ user-select: none;
608
+ white-space: nowrap;
609
+ }
610
+ .dsh-cr-cell {
611
+ padding: 0 12px 0 4px;
612
+ min-width: 0;
613
+ white-space: pre-wrap;
614
+ word-break: break-word;
615
+ }
616
+ .dsh-cr-del .dsh-cr-num-old, .dsh-cr-del .dsh-cr-cell-old,
617
+ .dsh-cr-change .dsh-cr-num-old, .dsh-cr-change .dsh-cr-cell-old {
618
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);
619
+ }
620
+ .dsh-cr-add .dsh-cr-num-new, .dsh-cr-add .dsh-cr-cell-new,
621
+ .dsh-cr-change .dsh-cr-num-new, .dsh-cr-change .dsh-cr-cell-new {
622
+ background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);
623
+ }
624
+ .dsh-cr-gaprow {
625
+ grid-column: 1 / -1;
626
+ padding: 2px 12px;
627
+ font-size: 12px;
628
+ color: var(--dsw-alias-label-tertiary);
629
+ background: color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent);
630
+ user-select: none;
631
+ }
632
+ .dsh-cr-nonl { color: var(--dsw-alias-label-tertiary); font-size: 11px; }
633
+ /* \u8BED\u6CD5\u9AD8\u4EAE:\u989C\u8272\u590D\u7528\u5B98\u65B9 shiki \u53D8\u91CF(\u4E0E DSH \u6DF1\u6D45\u4E3B\u9898\u4E00\u81F4,\u53D6\u4E0D\u5230\u65F6\u515C\u5E95) */
634
+ .tok-kw { color: var(--shiki-token-keyword, #7c3aed); }
635
+ .tok-str { color: var(--shiki-token-string, #16a34a); }
636
+ .tok-com { color: var(--shiki-token-comment, #9aa0a6); font-style: italic; }
637
+ .tok-num { color: var(--shiki-token-constant, #d97706); }
638
+ /* \u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02:change \u884C\u4E2D\u6BB5\u52A0\u6DF1\u80CC\u666F(\u65E7\u4FA7\u7EA2/\u65B0\u4FA7\u7EFF) */
639
+ .dihl-mid-old {
640
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 32%, transparent);
641
+ border-radius: 2px;
642
+ }
643
+ .dihl-mid-new {
644
+ background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 32%, transparent);
645
+ border-radius: 2px;
646
+ }
647
+ /* \u884C\u5185\u8BC4\u8BBA:\u884C\u53F7\u683C\u53EF\u70B9\u51FB,hover \u51FA\u73B0 + \u53F7 */
648
+ .dsh-cr-num[data-cr-line] {
649
+ cursor: pointer;
650
+ position: relative;
651
+ }
652
+ .dsh-cr-num[data-cr-line]:hover {
653
+ color: var(--dsw-alias-label-primary);
654
+ }
655
+ .dsh-cr-num[data-cr-line]:hover::after {
656
+ content: '+';
657
+ position: absolute;
658
+ left: 2px;
659
+ top: 0;
660
+ color: var(--dsw-alias-state-business-primary);
661
+ font-weight: 700;
662
+ }
663
+ .cc {
664
+ margin-left: 5px;
665
+ font-size: 10px;
666
+ line-height: 15px;
667
+ color: #fff;
668
+ background: var(--dsw-alias-state-business-primary);
669
+ border-radius: 8px;
670
+ padding: 0 5px;
671
+ user-select: none;
672
+ }
673
+ .comment-draft {
674
+ grid-column: 1 / -1;
675
+ padding: 8px 12px;
676
+ background: var(--dsw-alias-bg-layer-1);
677
+ border-top: 1px solid var(--dsw-alias-border-l2);
678
+ }
679
+ .comment-list { margin-bottom: 8px; }
680
+ .comment-item {
681
+ font-size: 12px;
682
+ color: var(--dsw-alias-label-secondary);
683
+ background: var(--dsw-alias-markdown-code-block);
684
+ border: 1px solid var(--dsw-alias-border-l2);
685
+ border-radius: 8px;
686
+ padding: 6px 10px;
687
+ margin-bottom: 4px;
688
+ white-space: pre-wrap;
689
+ word-break: break-word;
690
+ }
691
+ .comment-input {
692
+ width: 100%;
693
+ min-height: 60px;
694
+ resize: vertical;
695
+ box-sizing: border-box;
696
+ font: inherit;
697
+ font-size: 13px;
698
+ color: var(--dsw-alias-label-primary);
699
+ background: var(--dsw-alias-markdown-code-block);
700
+ border: 1px solid var(--dsw-alias-border-l2);
701
+ border-radius: 8px;
702
+ padding: 6px 10px;
703
+ }
704
+ .comment-actions { display: flex; gap: 8px; margin-top: 8px; }
705
+ .submit-panel {
706
+ position: fixed;
707
+ top: 44px;
708
+ right: 16px;
709
+ z-index: 20;
710
+ display: flex;
711
+ align-items: center;
712
+ gap: 8px;
713
+ padding: 10px 12px;
714
+ background: var(--dsw-alias-markdown-code-block);
715
+ border: 1px solid var(--dsw-alias-border-l2);
716
+ border-radius: 12px;
717
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
718
+ }
719
+ .submit-hint { font-size: 12px; color: var(--dsw-alias-label-secondary); }
720
+ .rawwrap { margin: 16px 24px; }
721
+ .rawsummary { font-size: 12px; color: var(--dsw-alias-label-secondary); cursor: pointer; }
722
+ .rawpre { white-space: pre-wrap; word-break: break-word; font: inherit; margin: 8px 0 0; }
723
+ `;
724
+ var STANDALONE_STATUS_LABEL = {
725
+ modified: "\u4FEE\u6539",
726
+ added: "\u65B0\u589E\u6587\u4EF6",
727
+ deleted: "\u5220\u9664\u6587\u4EF6",
728
+ renamed: "\u91CD\u547D\u540D",
729
+ binary: "\u4E8C\u8FDB\u5236",
730
+ mode: "\u6743\u9650\u53D8\u66F4"
731
+ };
732
+ function buildCommentReport(comments, kind) {
733
+ const header = kind === "lgtm" ? `Looks good to me \u2713 \u2014 Code Review \u5171 ${comments.length} \u6761\u8BC4\u8BBA:` : `Code Review \u610F\u89C1,\u5171 ${comments.length} \u6761:`;
734
+ const items = comments.map((comment, index) => {
735
+ const lines = comment.text.split("\n");
736
+ const first = `${index + 1}. ${comment.path}:${comment.lineNo} \u2014 ${lines[0]}`;
737
+ const rest = lines.slice(1).map((line) => ` ${line}`);
738
+ return [first, ...rest].join("\n");
739
+ });
740
+ return [header, "", ...items].join("\n");
741
+ }
742
+ function tokensToHtml(tokens) {
743
+ let out = "";
744
+ for (const token of tokens) {
745
+ const escaped = escapeHtml(token.text);
746
+ out += token.cls === null ? escaped : `<span class="${token.cls}">${escaped}</span>`;
747
+ }
748
+ return out;
749
+ }
750
+ function cellHtml(text, side, noEol, diffRange, lang) {
751
+ if (text === void 0) return `<div class="dsh-cr-cell dsh-cr-cell-${side}"></div>`;
752
+ const mark = noEol === true ? '<span class="dsh-cr-nonl" title="\u8BE5\u4FA7\u6587\u4EF6\u672B\u5C3E\u65E0\u6362\u884C\u7B26">\u23CE</span>' : "";
753
+ let body;
754
+ if (diffRange === null) {
755
+ body = lang === null ? escapeHtml(text) : tokensToHtml(tokenizeLine(text, lang));
756
+ } else {
757
+ const chars = Array.from(text);
758
+ const start = side === "old" ? diffRange.oldStart : diffRange.newStart;
759
+ const end = side === "old" ? diffRange.oldEnd : diffRange.newEnd;
760
+ const prefix = chars.slice(0, start).join("");
761
+ const mid = chars.slice(start, end).join("");
762
+ const suffix = chars.slice(end).join("");
763
+ const seg = (segText) => lang === null ? escapeHtml(segText) : tokensToHtml(tokenizeLine(segText, lang));
764
+ body = seg(prefix) + (mid !== "" ? `<span class="dihl-mid-${side}">${seg(mid)}</span>` : "") + seg(suffix);
765
+ }
766
+ return `<div class="dsh-cr-cell dsh-cr-cell-${side}">${body}${mark}</div>`;
767
+ }
768
+ function rowHtml(row, lang, path) {
769
+ if (row.kind === "gap") {
770
+ return `<div class="dsh-cr-gaprow">${escapeHtml(row.gapText ?? "")}</div>`;
771
+ }
772
+ const num = (no, side) => {
773
+ if (no === void 0) return `<div class="dsh-cr-num dsh-cr-num-${side}"></div>`;
774
+ return `<div class="dsh-cr-num dsh-cr-num-${side}" data-cr-path="${escapeHtml(path)}" data-cr-line="${no}" data-cr-side="${side}">${no}</div>`;
775
+ };
776
+ const line = `<div class="dsh-cr-row dsh-cr-${row.kind}">`;
777
+ if (row.kind === "ctx") {
778
+ return line + num(row.oldNo, "old") + cellHtml(row.oldText, "old", row.oldNoEol, null, lang) + num(row.newNo, "new") + cellHtml(row.newText, "new", row.newNoEol, null, lang) + "</div>";
779
+ }
780
+ if (row.kind === "del") {
781
+ return line + num(row.oldNo, "old") + cellHtml(row.oldText, "old", row.oldNoEol, null, lang) + num(void 0, "new") + cellHtml(void 0, "new", void 0, null, lang) + "</div>";
782
+ }
783
+ if (row.kind === "add") {
784
+ return line + num(void 0, "old") + cellHtml(void 0, "old", void 0, null, lang) + num(row.newNo, "new") + cellHtml(row.newText, "new", row.newNoEol, null, lang) + "</div>";
785
+ }
786
+ const range = diffMidRange(row.oldText ?? "", row.newText ?? "");
787
+ return line + num(row.oldNo, "old") + cellHtml(row.oldText, "old", row.oldNoEol, range, lang) + num(row.newNo, "new") + cellHtml(row.newText, "new", row.newNoEol, range, lang) + "</div>";
788
+ }
789
+ function buildStandaloneHtml(files, theme, rawText) {
790
+ const stats = countStats(files);
791
+ const rootVars = Object.entries(theme).map(([name, value]) => `${name}: ${value};`).join(" ");
792
+ const rawJson = JSON.stringify(rawText).replace(/</g, "\\u003c");
793
+ const nav = files.map((file, index) => `<a class="navitem" href="#file-${index}">${escapeHtml(displayPath(file.newPath) || displayPath(file.oldPath))}</a>`).join("");
794
+ const sections = files.map((file, index) => {
795
+ const oldPath = displayPath(file.oldPath);
796
+ const newPath = displayPath(file.newPath);
797
+ const path = oldPath !== "" && oldPath !== newPath ? `${oldPath} \u2192 ${newPath}` : newPath === "" ? oldPath : newPath;
798
+ const meta = file.meta.filter((m) => !m.startsWith("index "));
799
+ const lang = langFor(newPath) ?? langFor(oldPath);
800
+ const commentPath = newPath !== "" && newPath !== "/dev/null" ? newPath : oldPath;
801
+ const rows = file.rows.length === 0 ? `<div class="dsh-cr-gaprow">${escapeHtml(file.meta.join(" \xB7 ") || file.status)}</div>` : file.rows.map((row) => rowHtml(row, lang, commentPath)).join("");
802
+ const head = `<header class="filehead"><span class="badge">${STANDALONE_STATUS_LABEL[file.status]}</span><span class="path">${escapeHtml(path)}</span>` + (meta.length > 0 ? `<span class="meta">${escapeHtml(meta.join(" \xB7 "))}</span>` : "") + "</header>";
803
+ return `<section class="file" id="file-${index}">${head}<div class="dsh-cr-grid">${rows}</div></section>`;
804
+ }).join("");
805
+ return `<!doctype html>
806
+ <html lang="zh-CN">
807
+ <head>
808
+ <meta charset="utf-8">
809
+ <meta name="viewport" content="width=device-width, initial-scale=1">
810
+ <title>Code Review \xB7 +${stats.added} \u2212${stats.removed} \xB7 ${stats.files} \u4E2A\u6587\u4EF6</title>
811
+ <style>
812
+ :root { ${rootVars} }
813
+ ${STANDALONE_CSS}
814
+ </style>
815
+ </head>
816
+ <body>
817
+ <header class="topbar">
818
+ <span class="stats">+${stats.added} \u2212${stats.removed} \xB7 ${stats.files} \u4E2A\u6587\u4EF6</span>
819
+ <span class="topbar-actions">
820
+ <button class="btn" id="submit-comments" type="button">\u63D0\u4EA4\u8BC4\u8BBA</button>
821
+ <button class="btn" id="copy-raw" type="button">\u590D\u5236\u539F\u59CB diff</button>
822
+ </span>
823
+ </header>
824
+ <div class="submit-panel" id="submit-panel" hidden>
825
+ <span class="submit-hint">\u628A\u5168\u90E8\u8BC4\u8BBA\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846:</span>
826
+ <button class="btn" id="submit-lgtm" type="button">Looks Good To Me \u2713</button>
827
+ <button class="btn" id="submit-plain" type="button">\u4EC5\u63D0\u4EA4\u8BC4\u8BBA</button>
828
+ </div>
829
+ <nav class="sidenav">${nav}</nav>
830
+ <main>${sections}</main>
831
+ <div class="rawwrap">
832
+ <details><summary class="rawsummary">\u539F\u59CB diff \u6587\u672C</summary>
833
+ <pre class="rawpre">${escapeHtml(rawText)}</pre>
834
+ </details>
835
+ </div>
836
+ <script>
837
+ document.getElementById('copy-raw').addEventListener('click', function (event) {
838
+ var raw = ${rawJson}
839
+ if (navigator.clipboard && navigator.clipboard.writeText) {
840
+ navigator.clipboard.writeText(raw).then(function () {
841
+ var btn = event.currentTarget
842
+ btn.textContent = '\u5DF2\u590D\u5236'
843
+ setTimeout(function () { btn.textContent = '\u590D\u5236\u539F\u59CB diff' }, 1200)
844
+ })
845
+ }
846
+ })
847
+ <\/script>
848
+ <script>
849
+ /* \u884C\u5185\u8BC4\u8BBA:\u70B9\u51FB\u884C\u53F7\u683C\u63D0\u8BC4\u8BBA,\u6512\u9F50\u540E\u63D0\u4EA4\u56DE DSH \u5BF9\u8BDD\u6846(postMessage)\u3002 */
850
+ (function () {
851
+ var comments = []
852
+ var draft = null
853
+ var submitBtn = document.getElementById('submit-comments')
854
+ var panel = document.getElementById('submit-panel')
855
+
856
+ function esc(text) {
857
+ return String(text)
858
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;')
859
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;')
860
+ }
861
+ function syncSubmit() {
862
+ if (!submitBtn) return
863
+ submitBtn.textContent = comments.length === 0 ? '\u63D0\u4EA4\u8BC4\u8BBA' : '\u63D0\u4EA4\u8BC4\u8BBA (' + comments.length + ')'
864
+ }
865
+ function sameLine(a, b) {
866
+ return a.path === b.path && a.lineNo === b.lineNo && a.side === b.side
867
+ }
868
+ function lineOf(num) {
869
+ return {
870
+ path: num.getAttribute('data-cr-path'),
871
+ lineNo: Number(num.getAttribute('data-cr-line')),
872
+ side: num.getAttribute('data-cr-side'),
873
+ }
874
+ }
875
+ function commentsOn(line) {
876
+ return comments.filter(function (c) { return sameLine(c, line) })
877
+ }
878
+ function closeDraft() {
879
+ if (draft && draft.parentNode) draft.parentNode.removeChild(draft)
880
+ draft = null
881
+ }
882
+ function findNum(line) {
883
+ var nums = document.querySelectorAll('.dsh-cr-num[data-cr-line]')
884
+ for (var i = 0; i < nums.length; i += 1) {
885
+ if (sameLine(lineOf(nums[i]), line)) return nums[i]
886
+ }
887
+ return null
888
+ }
889
+ function updateBadge(num) {
890
+ var badge = num.querySelector('.cc')
891
+ if (badge) badge.parentNode.removeChild(badge)
892
+ var n = commentsOn(lineOf(num)).length
893
+ if (n > 0) {
894
+ var span = document.createElement('span')
895
+ span.className = 'cc'
896
+ span.textContent = String(n)
897
+ num.appendChild(span)
898
+ }
899
+ }
900
+ function openDraft(num) {
901
+ closeDraft()
902
+ var line = lineOf(num)
903
+ var existing = commentsOn(line)
904
+ draft = document.createElement('div')
905
+ draft.className = 'comment-draft'
906
+ var html = ''
907
+ if (existing.length > 0) {
908
+ html += '<div class="comment-list">'
909
+ existing.forEach(function (c) { html += '<div class="comment-item">' + esc(c.text) + '</div>' })
910
+ html += '</div>'
911
+ }
912
+ html += '<textarea class="comment-input" placeholder="\u5728\u6B64\u884C\u6DFB\u52A0\u8BC4\u8BBA\u2026"></textarea>'
913
+ html += '<div class="comment-actions">'
914
+ + '<button class="btn" type="button" data-act="save">\u6DFB\u52A0\u8BC4\u8BBA</button>'
915
+ + '<button class="btn" type="button" data-act="cancel">\u53D6\u6D88</button>'
916
+ + '</div>'
917
+ draft.innerHTML = html
918
+ num.parentElement.insertAdjacentElement('afterend', draft)
919
+ var ta = draft.querySelector('.comment-input')
920
+ ta.focus()
921
+ ta.addEventListener('keydown', function (event) {
922
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) saveDraft(line)
923
+ })
924
+ draft.querySelector('[data-act="save"]').addEventListener('click', function () { saveDraft(line) })
925
+ draft.querySelector('[data-act="cancel"]').addEventListener('click', closeDraft)
926
+ }
927
+ function saveDraft(line) {
928
+ if (!draft) return
929
+ var ta = draft.querySelector('.comment-input')
930
+ var text = ta.value.trim()
931
+ closeDraft()
932
+ if (text === '') return
933
+ comments.push({ path: line.path, lineNo: line.lineNo, side: line.side, text: text })
934
+ var num = findNum(line)
935
+ if (num) updateBadge(num)
936
+ syncSubmit()
937
+ }
938
+ function send(kind) {
939
+ if (comments.length === 0) return
940
+ var target = window.opener
941
+ if (!target) {
942
+ alert('\u539F\u6765\u7684 DSH \u9875\u9762\u5DF2\u5173\u95ED,\u65E0\u6CD5\u56DE\u4F20\u8BC4\u8BBA')
943
+ return
944
+ }
945
+ target.postMessage({
946
+ type: 'dsh-code-review-comments',
947
+ kind: kind,
948
+ comments: comments.map(function (c) {
949
+ return { path: c.path, lineNo: c.lineNo, side: c.side, text: c.text }
950
+ }),
951
+ }, '*')
952
+ panel.hidden = true
953
+ }
954
+ document.addEventListener('click', function (event) {
955
+ var target = event.target
956
+ var num = target && target.closest ? target.closest('.dsh-cr-num[data-cr-line]') : null
957
+ if (num) {
958
+ openDraft(num)
959
+ return
960
+ }
961
+ if (draft && !draft.contains(target)) closeDraft()
962
+ if (panel && target && target.closest && !panel.contains(target) && !target.closest('#submit-comments')) {
963
+ panel.hidden = true
964
+ }
965
+ })
966
+ submitBtn.addEventListener('click', function () {
967
+ if (comments.length === 0) return
968
+ panel.hidden = !panel.hidden
969
+ })
970
+ document.getElementById('submit-lgtm').addEventListener('click', function () { send('lgtm') })
971
+ document.getElementById('submit-plain').addEventListener('click', function () { send('comment') })
972
+ window.addEventListener('message', function (event) {
973
+ if (event.source !== window.opener) return
974
+ var data = event.data
975
+ if (!data || data.type !== 'dsh-code-review-ack') return
976
+ if (data.ok === false) {
977
+ alert('\u672A\u80FD\u5199\u5165 DSH \u5BF9\u8BDD\u6846:\u5F53\u524D\u6CA1\u6709\u6253\u5F00\u4E2D\u7684\u4F1A\u8BDD')
978
+ } else {
979
+ submitBtn.textContent = '\u5DF2\u53D1\u9001 \u2713'
980
+ setTimeout(syncSubmit, 3000)
981
+ }
982
+ })
983
+ syncSubmit()
984
+ })()
985
+ <\/script>
986
+ </body>
987
+ </html>
988
+ `;
989
+ }
990
+
991
+ // src/client/index.tsx
992
+ var STABLE_MS = 1e3;
993
+ var SCAN_DEBOUNCE = 300;
994
+ var RETRY_MS = 3e3;
995
+ var SAFETY_SCAN_MS = 1e4;
996
+ var styleInjected = false;
997
+ var BUTTON_CSS = `
998
+ .dsh-cr-openbtn {
999
+ margin-left: auto;
1000
+ flex: none;
1001
+ font-size: 12px;
1002
+ line-height: 18px;
1003
+ color: var(--dsw-alias-label-secondary);
1004
+ background: transparent;
1005
+ border: 1px solid var(--dsw-alias-border-l2);
1006
+ border-radius: 8px;
1007
+ padding: 1px 10px;
1008
+ cursor: pointer;
1009
+ white-space: nowrap;
1010
+ }
1011
+ .dsh-cr-openbtn:hover {
1012
+ color: var(--dsw-alias-label-primary);
1013
+ border-color: var(--dsw-alias-border-l3);
1014
+ }
1015
+ `;
1016
+ function ensureStyle() {
1017
+ if (styleInjected || typeof document === "undefined") return;
1018
+ styleInjected = true;
1019
+ const tag = document.createElement("style");
1020
+ tag.dataset.plugin = "dsh-code-review";
1021
+ tag.textContent = BUTTON_CSS;
1022
+ document.head.appendChild(tag);
1023
+ }
1024
+ var THEME_VAR_DEFAULTS = {
1025
+ "--dsw-alias-bg-base": "#ffffff",
1026
+ "--dsw-alias-bg-layer-1": "#f7f7f8",
1027
+ "--dsw-alias-markdown-code-block": "#f4f4f5",
1028
+ "--dsw-alias-label-primary": "#1a1a1a",
1029
+ "--dsw-alias-label-secondary": "#555555",
1030
+ "--dsw-alias-label-tertiary": "#888888",
1031
+ "--dsw-alias-border-l2": "#e5e5e7",
1032
+ "--dsw-alias-border-l3": "#cfcfd2",
1033
+ "--dsw-alias-state-error-primary": "#d92d20",
1034
+ "--dsw-alias-state-success-primary": "#12b76a",
1035
+ "--dsw-alias-state-business-primary": "#3b6fe0",
1036
+ "--ds-font-family-code": "ui-monospace, SFMono-Regular, Menlo, monospace",
1037
+ // 语法高亮 token 颜色:官方 shiki 主题变量(与 DSH 深浅主题一致)。
1038
+ "--shiki-token-keyword": "#7c3aed",
1039
+ "--shiki-token-string": "#16a34a",
1040
+ "--shiki-token-comment": "#9aa0a6",
1041
+ "--shiki-token-constant": "#d97706",
1042
+ "--shiki-token-function": "#2563eb"
1043
+ };
1044
+ function collectThemeVars() {
1045
+ const out = {};
1046
+ const styles = getComputedStyle(document.body);
1047
+ for (const name of Object.keys(THEME_VAR_DEFAULTS)) {
1048
+ const value = styles.getPropertyValue(name).trim();
1049
+ out[name] = value !== "" ? value : THEME_VAR_DEFAULTS[name];
1050
+ }
1051
+ return out;
1052
+ }
1053
+ function openReviewTab(text) {
1054
+ const files = parseGitDiff(text);
1055
+ if (files.length === 0) return;
1056
+ const html = buildStandaloneHtml(files, collectThemeVars(), text);
1057
+ const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
1058
+ for (const win2 of reviewWindows) {
1059
+ if (win2 === null || win2.closed) reviewWindows.delete(win2);
1060
+ }
1061
+ const win = window.open(url, "_blank");
1062
+ if (win !== null) reviewWindows.add(win);
1063
+ window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
1064
+ }
1065
+ var reviewWindows = /* @__PURE__ */ new Set();
1066
+ var composerWriter = null;
1067
+ function sanitizeComments(raw) {
1068
+ if (!Array.isArray(raw)) return [];
1069
+ const out = [];
1070
+ for (const item of raw) {
1071
+ if (typeof item !== "object" || item === null) continue;
1072
+ const c = item;
1073
+ if (typeof c.path !== "string" || typeof c.lineNo !== "number" || typeof c.text !== "string") continue;
1074
+ if (c.text.trim() === "") continue;
1075
+ out.push({ path: c.path, lineNo: Math.trunc(c.lineNo), text: c.text });
1076
+ }
1077
+ return out;
1078
+ }
1079
+ function handleReviewMessage(event) {
1080
+ const data = event.data;
1081
+ if (typeof data !== "object" || data === null) return;
1082
+ if (data.type !== "dsh-code-review-comments") return;
1083
+ if (!reviewWindows.has(event.source)) return;
1084
+ const comments = sanitizeComments(data.comments);
1085
+ if (comments.length === 0) return;
1086
+ const kind = data.kind === "lgtm" ? "lgtm" : "comment";
1087
+ const report = buildCommentReport(comments, kind);
1088
+ const source = event.source;
1089
+ if (composerWriter !== null) {
1090
+ composerWriter(report);
1091
+ source.postMessage({ type: "dsh-code-review-ack", ok: true }, "*");
1092
+ } else {
1093
+ source.postMessage({ type: "dsh-code-review-ack", ok: false }, "*");
1094
+ }
1095
+ }
1096
+ function ensureButtons(host, getText, fold) {
1097
+ if (host.querySelector("[data-dsh-cr-btn]") === null) {
1098
+ const btn = document.createElement("button");
1099
+ btn.type = "button";
1100
+ btn.className = "dsh-cr-openbtn";
1101
+ btn.dataset.dshCrBtn = "";
1102
+ btn.textContent = "\u29C9 \u65B0\u6807\u7B7E\u9875\u6253\u5F00";
1103
+ btn.title = "\u5728\u72EC\u7ACB\u7684\u6D4F\u89C8\u5668\u6807\u7B7E\u9875\u4E2D\u6253\u5F00 Code Review \u89C6\u56FE";
1104
+ btn.addEventListener("click", (event) => {
1105
+ event.stopPropagation();
1106
+ event.preventDefault();
1107
+ const text = getText();
1108
+ if (text !== null) openReviewTab(text);
1109
+ });
1110
+ host.appendChild(btn);
1111
+ }
1112
+ if (fold !== null && host.querySelector("[data-dsh-cr-fold]") === null) {
1113
+ const btn = document.createElement("button");
1114
+ btn.type = "button";
1115
+ btn.className = "dsh-cr-openbtn";
1116
+ btn.dataset.dshCrFold = "";
1117
+ btn.textContent = fold.folded ? "\u5C55\u5F00" : "\u6536\u8D77";
1118
+ btn.title = fold.folded ? "\u5C55\u5F00\u539F\u59CB diff \u6587\u672C" : "\u6536\u8D77\u539F\u59CB diff \u6587\u672C";
1119
+ btn.addEventListener("click", (event) => {
1120
+ event.stopPropagation();
1121
+ event.preventDefault();
1122
+ fold.onToggle();
1123
+ });
1124
+ host.appendChild(btn);
1125
+ }
1126
+ }
1127
+ function removeButtons(host) {
1128
+ for (const btn of host.querySelectorAll("[data-dsh-cr-btn], [data-dsh-cr-fold]")) {
1129
+ btn.remove();
1130
+ }
1131
+ }
1132
+ function findCodeBlockBody(el) {
1133
+ const first = el.firstElementChild;
1134
+ for (const child of el.children) {
1135
+ if (!(child instanceof HTMLElement)) continue;
1136
+ if (child.hasAttribute("data-dsh-cr-btn") || child.hasAttribute("data-dsh-cr-fold")) continue;
1137
+ if (child.tagName === "PRE") return child;
1138
+ }
1139
+ for (const child of el.children) {
1140
+ if (!(child instanceof HTMLElement)) continue;
1141
+ if (child.hasAttribute("data-dsh-cr-btn") || child.hasAttribute("data-dsh-cr-fold")) continue;
1142
+ if (child === first) continue;
1143
+ if (child.tagName === "DIV") return child;
1144
+ }
1145
+ return null;
1146
+ }
1147
+ function probeCodeBlock(el) {
1148
+ const banner = el.firstElementChild;
1149
+ const body = findCodeBlockBody(el);
1150
+ if (!(banner instanceof HTMLElement) || body === null) return null;
1151
+ const lang = (banner.firstElementChild?.textContent ?? "").trim().toLowerCase();
1152
+ const text = body.textContent ?? "";
1153
+ const candidate = lang === "diff" || lang === "patch" || isDiffText(text);
1154
+ return { text, candidate, running: false };
1155
+ }
1156
+ function findOutputContainer(wrapper) {
1157
+ const children = wrapper.children;
1158
+ for (let i = 1; i < children.length; i += 1) {
1159
+ const child = children[i];
1160
+ if (!(child instanceof HTMLElement) || child.tagName !== "DIV") continue;
1161
+ if (child.hasAttribute("data-dsh-cr-btn") || child.hasAttribute("data-dsh-cr-fold")) continue;
1162
+ if (isDiffText(child.textContent ?? "")) return child;
1163
+ }
1164
+ return null;
1165
+ }
1166
+ function joinOutputLines(container) {
1167
+ const parts = [];
1168
+ for (const child of container.children) {
1169
+ if (child instanceof HTMLElement && child.tagName === "DIV") {
1170
+ parts.push(child.textContent ?? "");
1171
+ }
1172
+ }
1173
+ return parts.join("\n");
1174
+ }
1175
+ function probeTerminal(el) {
1176
+ if (el.hasAttribute("data-running")) {
1177
+ return { text: "", candidate: false, running: true };
1178
+ }
1179
+ const output = findOutputContainer(el);
1180
+ if (output === null) return null;
1181
+ const text = joinOutputLines(output);
1182
+ return { text, candidate: isDiffText(text), running: false };
1183
+ }
1184
+ function probeFor(el) {
1185
+ if (el.classList.contains("md-code-block")) return probeCodeBlock(el);
1186
+ if (el.hasAttribute("data-terminal")) {
1187
+ if (el.closest('[data-sample="bash"]') !== null) return null;
1188
+ return probeTerminal(el);
1189
+ }
1190
+ return null;
1191
+ }
1192
+ function buttonHostFor(el) {
1193
+ const first = el.firstElementChild;
1194
+ return first instanceof HTMLElement ? first : null;
1195
+ }
1196
+ var pendingTexts = /* @__PURE__ */ new Map();
1197
+ function readToolRoot(data) {
1198
+ if (typeof data !== "object" || data === null) return null;
1199
+ return data.root ?? null;
1200
+ }
1201
+ function isSettledToolResult(root) {
1202
+ if (typeof root !== "object" || root === null) return false;
1203
+ const r = root;
1204
+ if (!Array.isArray(r.content)) return false;
1205
+ if (r.call !== null && (typeof r.call !== "object" || r.call === null || typeof r.call.name !== "string")) {
1206
+ return false;
1207
+ }
1208
+ return true;
1209
+ }
1210
+ function toolResultText(root) {
1211
+ if (!isSettledToolResult(root)) return "";
1212
+ const parts = [];
1213
+ for (const block of root.content) {
1214
+ if (typeof block !== "object" || block === null) continue;
1215
+ const b = block;
1216
+ if (b.type === "text" && typeof b.text === "string") parts.push(b.text);
1217
+ }
1218
+ return parts.join("\n");
1219
+ }
1220
+ function syncDiffToolTexts(sessionId, chat) {
1221
+ const found = /* @__PURE__ */ new Map();
1222
+ for (const key of chat.order) {
1223
+ const node = chat.nodes.get(key);
1224
+ if (node === void 0 || node.kind !== "tool-call") continue;
1225
+ const root = readToolRoot(node.data);
1226
+ if (root === null || !isSettledToolResult(root)) continue;
1227
+ if (root.call === null || root.call.name !== "bash") continue;
1228
+ const text = toolResultText(root);
1229
+ if (!isDiffText(text)) continue;
1230
+ found.set(key, text);
1231
+ }
1232
+ for (const [key, entry] of pendingTexts) {
1233
+ if (entry.sessionId === sessionId && !found.has(key)) pendingTexts.delete(key);
1234
+ }
1235
+ for (const [key, text] of found) pendingTexts.set(key, { sessionId, text });
1236
+ }
1237
+ function findRowByKey(key) {
1238
+ for (const el of document.querySelectorAll("[data-chat-anchor-key]")) {
1239
+ if (el instanceof HTMLElement && el.getAttribute("data-chat-anchor-key") === key) return el;
1240
+ }
1241
+ return null;
1242
+ }
1243
+ function attachToolRowButtons() {
1244
+ for (const [key, entry] of pendingTexts) {
1245
+ const row = findRowByKey(key);
1246
+ if (row === null) continue;
1247
+ const sample = row.querySelector('[data-sample="bash"]');
1248
+ if (!(sample instanceof HTMLElement)) continue;
1249
+ ensureButtons(sample, () => pendingTexts.get(key)?.text ?? entry.text, null);
1250
+ }
1251
+ }
1252
+ var DiffScanner = class {
1253
+ constructor() {
1254
+ __publicField(this, "states", /* @__PURE__ */ new WeakMap());
1255
+ __publicField(this, "observer", null);
1256
+ __publicField(this, "scanTimer", null);
1257
+ __publicField(this, "safetyTimer", null);
1258
+ }
1259
+ start() {
1260
+ if (this.observer !== null) return;
1261
+ this.observer = new MutationObserver(() => this.scheduleScan());
1262
+ this.observer.observe(document.body, { childList: true, subtree: true });
1263
+ this.scheduleScan();
1264
+ this.safetyTimer = window.setInterval(() => this.scan(), SAFETY_SCAN_MS);
1265
+ }
1266
+ stop() {
1267
+ this.observer?.disconnect();
1268
+ this.observer = null;
1269
+ if (this.scanTimer !== null) {
1270
+ clearTimeout(this.scanTimer);
1271
+ this.scanTimer = null;
1272
+ }
1273
+ if (this.safetyTimer !== null) {
1274
+ clearInterval(this.safetyTimer);
1275
+ this.safetyTimer = null;
1276
+ }
1277
+ }
1278
+ /** 合并高频 mutation 为一次扫描。 */
1279
+ scheduleScan() {
1280
+ if (this.scanTimer !== null) return;
1281
+ this.scanTimer = window.setTimeout(() => {
1282
+ this.scanTimer = null;
1283
+ this.scan();
1284
+ }, SCAN_DEBOUNCE);
1285
+ }
1286
+ /** 全量扫描:数据层工具行按钮 + DOM 候选块。 */
1287
+ scan() {
1288
+ attachToolRowButtons();
1289
+ for (const el of document.querySelectorAll(".md-code-block, [data-terminal]")) {
1290
+ this.inspect(el);
1291
+ }
1292
+ }
1293
+ /** 检查一个候选块:内容未变则不动,变了则排去抖,不再形似 diff 则移除按钮。 */
1294
+ inspect(el) {
1295
+ const probe = probeFor(el);
1296
+ if (probe === null || probe.running || !probe.candidate || probe.text.trim() === "") {
1297
+ const host = buttonHostFor(el);
1298
+ if (host !== null) removeButtons(host);
1299
+ const body = findCodeBlockBody(el);
1300
+ if (body !== null) body.style.display = "";
1301
+ this.states.delete(el);
1302
+ return;
1303
+ }
1304
+ const st = this.states.get(el);
1305
+ if (st !== void 0 && st.text === probe.text) {
1306
+ if (!st.failed) {
1307
+ this.applyFoldState(el, st);
1308
+ return;
1309
+ }
1310
+ if (Date.now() - st.lastTry < RETRY_MS) return;
1311
+ }
1312
+ if (st !== void 0 && st.timer !== null) clearTimeout(st.timer);
1313
+ const timer = window.setTimeout(() => {
1314
+ const cur = this.states.get(el);
1315
+ if (cur === void 0 || cur.timer !== timer) return;
1316
+ cur.timer = null;
1317
+ const p2 = probeFor(el);
1318
+ if (p2 === null || p2.running || !p2.candidate || p2.text !== cur.text) return;
1319
+ this.attachButton(el, cur);
1320
+ }, STABLE_MS);
1321
+ this.states.set(el, {
1322
+ text: probe.text,
1323
+ timer,
1324
+ lastTry: st?.lastTry ?? 0,
1325
+ failed: st?.failed ?? false,
1326
+ folded: st?.folded ?? true
1327
+ });
1328
+ }
1329
+ /** 内容稳定后执行:解析验证、折叠原始文本并注入按钮(失败则保持无按钮)。 */
1330
+ attachButton(el, st) {
1331
+ const host = buttonHostFor(el);
1332
+ if (host === null) return;
1333
+ if (parseGitDiff(st.text).length === 0) {
1334
+ st.failed = true;
1335
+ st.lastTry = Date.now();
1336
+ removeButtons(host);
1337
+ return;
1338
+ }
1339
+ st.failed = false;
1340
+ const isCodeBlock = el.classList.contains("md-code-block");
1341
+ if (isCodeBlock) {
1342
+ this.applyFoldState(el, st);
1343
+ }
1344
+ ensureButtons(
1345
+ host,
1346
+ () => probeFor(el)?.text ?? st.text,
1347
+ isCodeBlock ? { folded: st.folded, onToggle: () => this.toggleFold(el) } : null
1348
+ );
1349
+ }
1350
+ /** 同步代码块的折叠态(官方 body 只改 display,React 不管理这个 style)。 */
1351
+ applyFoldState(el, st) {
1352
+ if (!el.classList.contains("md-code-block")) return;
1353
+ const body = findCodeBlockBody(el);
1354
+ if (body !== null) body.style.display = st.folded ? "none" : "";
1355
+ }
1356
+ /** 展开/收起代码块原始文本,并同步按钮文案。 */
1357
+ toggleFold(el) {
1358
+ const st = this.states.get(el);
1359
+ if (st === void 0) return;
1360
+ st.folded = !st.folded;
1361
+ this.applyFoldState(el, st);
1362
+ const host = buttonHostFor(el);
1363
+ const btn = host?.querySelector("[data-dsh-cr-fold]");
1364
+ if (btn instanceof HTMLElement) btn.textContent = st.folded ? "\u5C55\u5F00" : "\u6536\u8D77";
1365
+ }
1366
+ };
1367
+ var inject = ["slots"];
1368
+ function apply(ctx) {
1369
+ ctx.slots.inject(
1370
+ "shell.overlay",
1371
+ () => ctx.slots.register({
1372
+ name: "shell.overlay",
1373
+ id: "code-review-enhancer"
1374
+ }, CodeReviewEnhancer)
1375
+ );
1376
+ ctx.slots.inject(
1377
+ "conversation.session.header.actions",
1378
+ () => ctx.slots.register({
1379
+ name: "conversation.session.header.actions",
1380
+ id: "code-review-tool-watcher",
1381
+ // 官方条目:agent-preset=-10、subagent-catalog=10、job-list=20,
1382
+ // web-enhance=15。取 25 排在最后,不与他人并列。
1383
+ order: 25
1384
+ }, DiffToolButtonWatcher)
1385
+ );
1386
+ }
1387
+ function CodeReviewEnhancer() {
1388
+ (0, import_react.useEffect)(() => {
1389
+ ensureStyle();
1390
+ const scanner = new DiffScanner();
1391
+ scanner.start();
1392
+ const onMessage = handleReviewMessage;
1393
+ window.addEventListener("message", onMessage);
1394
+ return () => {
1395
+ scanner.stop();
1396
+ window.removeEventListener("message", onMessage);
1397
+ };
1398
+ }, []);
1399
+ return null;
1400
+ }
1401
+ function DiffToolButtonWatcher({ useSession, sessionId, inputActions }) {
1402
+ const chat = useSession((state) => state.chat);
1403
+ (0, import_react.useEffect)(() => {
1404
+ if (chat !== void 0) syncDiffToolTexts(String(sessionId), chat);
1405
+ }, [chat, sessionId]);
1406
+ (0, import_react.useEffect)(() => {
1407
+ const writer = (text) => inputActions.setDraft(text);
1408
+ composerWriter = writer;
1409
+ return () => {
1410
+ if (composerWriter === writer) composerWriter = null;
1411
+ };
1412
+ }, [inputActions]);
1413
+ return null;
1414
+ }
1415
+ return module.exports; } });
1416
+ //# sourceMappingURL=client.js.map