@yangzhe1991/dsh-code-review 0.1.1 → 0.1.3
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.md +1 -0
- package/README.zh.md +1 -0
- package/lib/client.js +141 -40
- package/lib/client.js.map +3 -3
- package/lib/types/client/index.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ The conversation itself stays untouched: when a git diff shows up, a small **"op
|
|
|
23
23
|
- 🎨 **Theme-matched** — the page inherits the DSH theme colors at open time, so light/dark mode stays consistent.
|
|
24
24
|
- 📜 **Full-page scrolling** — no height caps; thousands of lines scroll naturally, and long lines wrap.
|
|
25
25
|
- 💬 **Inline comments** — hover any line number and click to add a comment right on that line (like GitHub reviews). Comments accumulate with a per-line count badge; when done, submit them all at once — as **Looks Good To Me ✓** or **plain comments** — and they are written into the DSH composer as `file:line — comment` lines, ready to send to the agent.
|
|
26
|
+
- 🌐 **Bilingual UI** — follows the DSH interface language (official locale service): a Chinese UI gets Chinese buttons and review page, everything else gets English.
|
|
26
27
|
|
|
27
28
|
## Screenshots
|
|
28
29
|
|
package/README.zh.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- 🎨 **主题一致** —— 打开时继承 DSH 当前主题色,深浅色模式自动跟随。
|
|
24
24
|
- 📜 **整页滚动** —— 无高度限制,几千行自然滚动,超长行折行显示。
|
|
25
25
|
- 💬 **行内评论** —— 悬停任意行号点击即可在该行提评论(类似 GitHub review),行号旁显示评论数徽章;攒齐后一次性提交 —— 可选 **Looks Good To Me ✓** 或**纯评论**,提交后自动写入 DSH 对话框(`文件名:行号 — 评论` 逐条列出),直接发给 agent。
|
|
26
|
+
- 🌐 **中英双语** —— 跟随 DSH 界面语言(官方 locale 服务):中文界面显示中文按钮与审查页,其他语言一律英文。
|
|
26
27
|
|
|
27
28
|
## 截图
|
|
28
29
|
|
package/lib/client.js
CHANGED
|
@@ -612,6 +612,8 @@ main { margin-left: 220px; padding: 16px 24px 60px; }
|
|
|
612
612
|
min-width: 0;
|
|
613
613
|
white-space: pre-wrap;
|
|
614
614
|
word-break: break-word;
|
|
615
|
+
/* \u5236\u8868\u7B26\u6309 4 \u4E2A\u5B57\u7B26\u5BBD\u5EA6\u6E32\u67D3(\u6D4F\u89C8\u5668\u9ED8\u8BA4 8,\u4EE3\u7801\u9605\u8BFB\u592A\u677E) */
|
|
616
|
+
tab-size: 4;
|
|
615
617
|
}
|
|
616
618
|
.dsh-cr-del .dsh-cr-num-old, .dsh-cr-del .dsh-cr-cell-old,
|
|
617
619
|
.dsh-cr-change .dsh-cr-num-old, .dsh-cr-change .dsh-cr-cell-old {
|
|
@@ -719,7 +721,7 @@ main { margin-left: 220px; padding: 16px 24px 60px; }
|
|
|
719
721
|
.submit-hint { font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
720
722
|
.rawwrap { margin: 16px 24px; }
|
|
721
723
|
.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; }
|
|
724
|
+
.rawpre { white-space: pre-wrap; word-break: break-word; font: inherit; margin: 8px 0 0; tab-size: 4; }
|
|
723
725
|
`;
|
|
724
726
|
var STANDALONE_STATUS_LABEL = {
|
|
725
727
|
modified: "\u4FEE\u6539",
|
|
@@ -729,8 +731,88 @@ var STANDALONE_STATUS_LABEL = {
|
|
|
729
731
|
binary: "\u4E8C\u8FDB\u5236",
|
|
730
732
|
mode: "\u6743\u9650\u53D8\u66F4"
|
|
731
733
|
};
|
|
732
|
-
|
|
733
|
-
|
|
734
|
+
var CR_TEXTS = {
|
|
735
|
+
zh: {
|
|
736
|
+
openTab: "\u29C9 \u65B0\u6807\u7B7E\u9875\u6253\u5F00",
|
|
737
|
+
openTabTitle: "\u5728\u72EC\u7ACB\u7684\u6D4F\u89C8\u5668\u6807\u7B7E\u9875\u4E2D\u6253\u5F00 Code Review \u89C6\u56FE",
|
|
738
|
+
expand: "\u5C55\u5F00",
|
|
739
|
+
collapse: "\u6536\u8D77",
|
|
740
|
+
expandTitle: "\u5C55\u5F00\u539F\u59CB diff \u6587\u672C",
|
|
741
|
+
collapseTitle: "\u6536\u8D77\u539F\u59CB diff \u6587\u672C",
|
|
742
|
+
reportLgtmHeader: (count) => `Looks good to me \u2713 \u2014 Code Review \u5171 ${count} \u6761\u8BC4\u8BBA:`,
|
|
743
|
+
reportCommentHeader: (count) => `Code Review \u610F\u89C1,\u5171 ${count} \u6761:`,
|
|
744
|
+
stats: (added, removed, files) => `+${added} \u2212${removed} \xB7 ${files} \u4E2A\u6587\u4EF6`,
|
|
745
|
+
submitComments: "\u63D0\u4EA4\u8BC4\u8BBA",
|
|
746
|
+
copyRaw: "\u590D\u5236\u539F\u59CB diff",
|
|
747
|
+
copied: "\u5DF2\u590D\u5236",
|
|
748
|
+
sent: "\u5DF2\u53D1\u9001 \u2713",
|
|
749
|
+
submitHint: "\u628A\u5168\u90E8\u8BC4\u8BBA\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846:",
|
|
750
|
+
submitLgtm: "Looks Good To Me \u2713",
|
|
751
|
+
submitPlain: "\u4EC5\u63D0\u4EA4\u8BC4\u8BBA",
|
|
752
|
+
rawSummary: "\u539F\u59CB diff \u6587\u672C",
|
|
753
|
+
commentPlaceholder: "\u5728\u6B64\u884C\u6DFB\u52A0\u8BC4\u8BBA\u2026",
|
|
754
|
+
addComment: "\u6DFB\u52A0\u8BC4\u8BBA",
|
|
755
|
+
cancel: "\u53D6\u6D88",
|
|
756
|
+
noNewlineTitle: "\u8BE5\u4FA7\u6587\u4EF6\u672B\u5C3E\u65E0\u6362\u884C\u7B26",
|
|
757
|
+
openerClosed: "\u539F\u6765\u7684 DSH \u9875\u9762\u5DF2\u5173\u95ED,\u65E0\u6CD5\u56DE\u4F20\u8BC4\u8BBA",
|
|
758
|
+
noSession: "\u672A\u80FD\u5199\u5165 DSH \u5BF9\u8BDD\u6846:\u5F53\u524D\u6CA1\u6709\u6253\u5F00\u4E2D\u7684\u4F1A\u8BDD",
|
|
759
|
+
status: STANDALONE_STATUS_LABEL
|
|
760
|
+
},
|
|
761
|
+
en: {
|
|
762
|
+
openTab: "\u29C9 Open in new tab",
|
|
763
|
+
openTabTitle: "Open the code review view in a separate browser tab",
|
|
764
|
+
expand: "Expand",
|
|
765
|
+
collapse: "Collapse",
|
|
766
|
+
expandTitle: "Show raw diff text",
|
|
767
|
+
collapseTitle: "Hide raw diff text",
|
|
768
|
+
reportLgtmHeader: (count) => `Looks good to me \u2713 \u2014 Code Review with ${count} comment${count === 1 ? "" : "s"}:`,
|
|
769
|
+
reportCommentHeader: (count) => `Code Review comments (${count}):`,
|
|
770
|
+
stats: (added, removed, files) => `+${added} \u2212${removed} \xB7 ${files} file${files === 1 ? "" : "s"}`,
|
|
771
|
+
submitComments: "Submit comments",
|
|
772
|
+
copyRaw: "Copy raw diff",
|
|
773
|
+
copied: "Copied",
|
|
774
|
+
sent: "Sent \u2713",
|
|
775
|
+
submitHint: "Submit all comments to the DSH composer:",
|
|
776
|
+
submitLgtm: "Looks Good To Me \u2713",
|
|
777
|
+
submitPlain: "Comments only",
|
|
778
|
+
rawSummary: "Raw diff text",
|
|
779
|
+
commentPlaceholder: "Add a comment on this line\u2026",
|
|
780
|
+
addComment: "Add comment",
|
|
781
|
+
cancel: "Cancel",
|
|
782
|
+
noNewlineTitle: "No newline at end of file",
|
|
783
|
+
openerClosed: "The original DSH page was closed; comments cannot be sent back",
|
|
784
|
+
noSession: "Could not write to the DSH composer: no open session",
|
|
785
|
+
status: {
|
|
786
|
+
modified: "modified",
|
|
787
|
+
added: "new file",
|
|
788
|
+
deleted: "deleted",
|
|
789
|
+
renamed: "renamed",
|
|
790
|
+
binary: "binary",
|
|
791
|
+
mode: "mode change"
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
function stringTexts(texts) {
|
|
796
|
+
return {
|
|
797
|
+
submitComments: texts.submitComments,
|
|
798
|
+
copyRaw: texts.copyRaw,
|
|
799
|
+
copied: texts.copied,
|
|
800
|
+
sent: texts.sent,
|
|
801
|
+
submitHint: texts.submitHint,
|
|
802
|
+
submitLgtm: texts.submitLgtm,
|
|
803
|
+
submitPlain: texts.submitPlain,
|
|
804
|
+
rawSummary: texts.rawSummary,
|
|
805
|
+
commentPlaceholder: texts.commentPlaceholder,
|
|
806
|
+
addComment: texts.addComment,
|
|
807
|
+
cancel: texts.cancel,
|
|
808
|
+
noNewlineTitle: texts.noNewlineTitle,
|
|
809
|
+
openerClosed: texts.openerClosed,
|
|
810
|
+
noSession: texts.noSession
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
function buildCommentReport(comments, kind, lang = "zh") {
|
|
814
|
+
const texts = CR_TEXTS[lang];
|
|
815
|
+
const header = kind === "lgtm" ? texts.reportLgtmHeader(comments.length) : texts.reportCommentHeader(comments.length);
|
|
734
816
|
const items = comments.map((comment, index) => {
|
|
735
817
|
const lines = comment.text.split("\n");
|
|
736
818
|
const first = `${index + 1}. ${comment.path}:${comment.lineNo} \u2014 ${lines[0]}`;
|
|
@@ -747,9 +829,9 @@ function tokensToHtml(tokens) {
|
|
|
747
829
|
}
|
|
748
830
|
return out;
|
|
749
831
|
}
|
|
750
|
-
function cellHtml(text, side, noEol, diffRange, lang) {
|
|
832
|
+
function cellHtml(text, side, noEol, diffRange, lang, noNewlineTitle) {
|
|
751
833
|
if (text === void 0) return `<div class="dsh-cr-cell dsh-cr-cell-${side}"></div>`;
|
|
752
|
-
const mark = noEol === true ?
|
|
834
|
+
const mark = noEol === true ? `<span class="dsh-cr-nonl" title="${escapeHtml(noNewlineTitle)}">\u23CE</span>` : "";
|
|
753
835
|
let body;
|
|
754
836
|
if (diffRange === null) {
|
|
755
837
|
body = lang === null ? escapeHtml(text) : tokensToHtml(tokenizeLine(text, lang));
|
|
@@ -765,7 +847,7 @@ function cellHtml(text, side, noEol, diffRange, lang) {
|
|
|
765
847
|
}
|
|
766
848
|
return `<div class="dsh-cr-cell dsh-cr-cell-${side}">${body}${mark}</div>`;
|
|
767
849
|
}
|
|
768
|
-
function rowHtml(row, lang, path) {
|
|
850
|
+
function rowHtml(row, lang, path, noNewlineTitle) {
|
|
769
851
|
if (row.kind === "gap") {
|
|
770
852
|
return `<div class="dsh-cr-gaprow">${escapeHtml(row.gapText ?? "")}</div>`;
|
|
771
853
|
}
|
|
@@ -775,19 +857,21 @@ function rowHtml(row, lang, path) {
|
|
|
775
857
|
};
|
|
776
858
|
const line = `<div class="dsh-cr-row dsh-cr-${row.kind}">`;
|
|
777
859
|
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>";
|
|
860
|
+
return line + num(row.oldNo, "old") + cellHtml(row.oldText, "old", row.oldNoEol, null, lang, noNewlineTitle) + num(row.newNo, "new") + cellHtml(row.newText, "new", row.newNoEol, null, lang, noNewlineTitle) + "</div>";
|
|
779
861
|
}
|
|
780
862
|
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>";
|
|
863
|
+
return line + num(row.oldNo, "old") + cellHtml(row.oldText, "old", row.oldNoEol, null, lang, noNewlineTitle) + num(void 0, "new") + cellHtml(void 0, "new", void 0, null, lang, noNewlineTitle) + "</div>";
|
|
782
864
|
}
|
|
783
865
|
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>";
|
|
866
|
+
return line + num(void 0, "old") + cellHtml(void 0, "old", void 0, null, lang, noNewlineTitle) + num(row.newNo, "new") + cellHtml(row.newText, "new", row.newNoEol, null, lang, noNewlineTitle) + "</div>";
|
|
785
867
|
}
|
|
786
868
|
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>";
|
|
869
|
+
return line + num(row.oldNo, "old") + cellHtml(row.oldText, "old", row.oldNoEol, range, lang, noNewlineTitle) + num(row.newNo, "new") + cellHtml(row.newText, "new", row.newNoEol, range, lang, noNewlineTitle) + "</div>";
|
|
788
870
|
}
|
|
789
|
-
function buildStandaloneHtml(files, theme, rawText) {
|
|
871
|
+
function buildStandaloneHtml(files, theme, rawText, lang = "zh") {
|
|
872
|
+
const texts = CR_TEXTS[lang];
|
|
790
873
|
const stats = countStats(files);
|
|
874
|
+
const statsText = texts.stats(stats.added, stats.removed, stats.files);
|
|
791
875
|
const rootVars = Object.entries(theme).map(([name, value]) => `${name}: ${value};`).join(" ");
|
|
792
876
|
const rawJson = JSON.stringify(rawText).replace(/</g, "\\u003c");
|
|
793
877
|
const nav = files.map((file, index) => `<a class="navitem" href="#file-${index}">${escapeHtml(displayPath(file.newPath) || displayPath(file.oldPath))}</a>`).join("");
|
|
@@ -796,18 +880,18 @@ function buildStandaloneHtml(files, theme, rawText) {
|
|
|
796
880
|
const newPath = displayPath(file.newPath);
|
|
797
881
|
const path = oldPath !== "" && oldPath !== newPath ? `${oldPath} \u2192 ${newPath}` : newPath === "" ? oldPath : newPath;
|
|
798
882
|
const meta = file.meta.filter((m) => !m.startsWith("index "));
|
|
799
|
-
const
|
|
883
|
+
const lang2 = langFor(newPath) ?? langFor(oldPath);
|
|
800
884
|
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,
|
|
802
|
-
const head = `<header class="filehead"><span class="badge">${
|
|
885
|
+
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, lang2, commentPath, texts.noNewlineTitle)).join("");
|
|
886
|
+
const head = `<header class="filehead"><span class="badge">${texts.status[file.status]}</span><span class="path">${escapeHtml(path)}</span>` + (meta.length > 0 ? `<span class="meta">${escapeHtml(meta.join(" \xB7 "))}</span>` : "") + "</header>";
|
|
803
887
|
return `<section class="file" id="file-${index}">${head}<div class="dsh-cr-grid">${rows}</div></section>`;
|
|
804
888
|
}).join("");
|
|
805
889
|
return `<!doctype html>
|
|
806
|
-
<html lang="zh-CN">
|
|
890
|
+
<html lang="${lang === "zh" ? "zh-CN" : "en"}">
|
|
807
891
|
<head>
|
|
808
892
|
<meta charset="utf-8">
|
|
809
893
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
810
|
-
<title>Code Review \xB7
|
|
894
|
+
<title>Code Review \xB7 ${statsText}</title>
|
|
811
895
|
<style>
|
|
812
896
|
:root { ${rootVars} }
|
|
813
897
|
${STANDALONE_CSS}
|
|
@@ -815,21 +899,21 @@ ${STANDALONE_CSS}
|
|
|
815
899
|
</head>
|
|
816
900
|
<body>
|
|
817
901
|
<header class="topbar">
|
|
818
|
-
<span class="stats"
|
|
902
|
+
<span class="stats">${statsText}</span>
|
|
819
903
|
<span class="topbar-actions">
|
|
820
|
-
<button class="btn" id="submit-comments" type="button"
|
|
821
|
-
<button class="btn" id="copy-raw" type="button"
|
|
904
|
+
<button class="btn" id="submit-comments" type="button">${texts.submitComments}</button>
|
|
905
|
+
<button class="btn" id="copy-raw" type="button">${texts.copyRaw}</button>
|
|
822
906
|
</span>
|
|
823
907
|
</header>
|
|
824
908
|
<div class="submit-panel" id="submit-panel" hidden>
|
|
825
|
-
<span class="submit-hint"
|
|
826
|
-
<button class="btn" id="submit-lgtm" type="button"
|
|
827
|
-
<button class="btn" id="submit-plain" type="button"
|
|
909
|
+
<span class="submit-hint">${texts.submitHint}</span>
|
|
910
|
+
<button class="btn" id="submit-lgtm" type="button">${texts.submitLgtm}</button>
|
|
911
|
+
<button class="btn" id="submit-plain" type="button">${texts.submitPlain}</button>
|
|
828
912
|
</div>
|
|
829
913
|
<nav class="sidenav">${nav}</nav>
|
|
830
914
|
<main>${sections}</main>
|
|
831
915
|
<div class="rawwrap">
|
|
832
|
-
<details><summary class="rawsummary"
|
|
916
|
+
<details><summary class="rawsummary">${texts.rawSummary}</summary>
|
|
833
917
|
<pre class="rawpre">${escapeHtml(rawText)}</pre>
|
|
834
918
|
</details>
|
|
835
919
|
</div>
|
|
@@ -839,8 +923,8 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
|
|
|
839
923
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
840
924
|
navigator.clipboard.writeText(raw).then(function () {
|
|
841
925
|
var btn = event.currentTarget
|
|
842
|
-
btn.textContent =
|
|
843
|
-
setTimeout(function () { btn.textContent =
|
|
926
|
+
btn.textContent = T.copied
|
|
927
|
+
setTimeout(function () { btn.textContent = T.copyRaw }, 1200)
|
|
844
928
|
})
|
|
845
929
|
}
|
|
846
930
|
})
|
|
@@ -848,6 +932,7 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
|
|
|
848
932
|
<script>
|
|
849
933
|
/* \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
934
|
(function () {
|
|
935
|
+
var T = ${JSON.stringify(stringTexts(texts))}
|
|
851
936
|
var comments = []
|
|
852
937
|
var draft = null
|
|
853
938
|
var submitBtn = document.getElementById('submit-comments')
|
|
@@ -860,7 +945,7 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
|
|
|
860
945
|
}
|
|
861
946
|
function syncSubmit() {
|
|
862
947
|
if (!submitBtn) return
|
|
863
|
-
submitBtn.textContent = comments.length === 0 ?
|
|
948
|
+
submitBtn.textContent = comments.length === 0 ? T.submitComments : T.submitComments + ' (' + comments.length + ')'
|
|
864
949
|
}
|
|
865
950
|
function sameLine(a, b) {
|
|
866
951
|
return a.path === b.path && a.lineNo === b.lineNo && a.side === b.side
|
|
@@ -909,10 +994,10 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
|
|
|
909
994
|
existing.forEach(function (c) { html += '<div class="comment-item">' + esc(c.text) + '</div>' })
|
|
910
995
|
html += '</div>'
|
|
911
996
|
}
|
|
912
|
-
html += '<textarea class="comment-input" placeholder="
|
|
997
|
+
html += '<textarea class="comment-input" placeholder="' + T.commentPlaceholder + '"></textarea>'
|
|
913
998
|
html += '<div class="comment-actions">'
|
|
914
|
-
+ '<button class="btn" type="button" data-act="save"
|
|
915
|
-
+ '<button class="btn" type="button" data-act="cancel"
|
|
999
|
+
+ '<button class="btn" type="button" data-act="save">' + T.addComment + '</button>'
|
|
1000
|
+
+ '<button class="btn" type="button" data-act="cancel">' + T.cancel + '</button>'
|
|
916
1001
|
+ '</div>'
|
|
917
1002
|
draft.innerHTML = html
|
|
918
1003
|
num.parentElement.insertAdjacentElement('afterend', draft)
|
|
@@ -939,7 +1024,7 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
|
|
|
939
1024
|
if (comments.length === 0) return
|
|
940
1025
|
var target = window.opener
|
|
941
1026
|
if (!target) {
|
|
942
|
-
alert(
|
|
1027
|
+
alert(T.openerClosed)
|
|
943
1028
|
return
|
|
944
1029
|
}
|
|
945
1030
|
target.postMessage({
|
|
@@ -974,9 +1059,9 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
|
|
|
974
1059
|
var data = event.data
|
|
975
1060
|
if (!data || data.type !== 'dsh-code-review-ack') return
|
|
976
1061
|
if (data.ok === false) {
|
|
977
|
-
alert(
|
|
1062
|
+
alert(T.noSession)
|
|
978
1063
|
} else {
|
|
979
|
-
submitBtn.textContent =
|
|
1064
|
+
submitBtn.textContent = T.sent
|
|
980
1065
|
setTimeout(syncSubmit, 3000)
|
|
981
1066
|
}
|
|
982
1067
|
})
|
|
@@ -1050,10 +1135,18 @@ function collectThemeVars() {
|
|
|
1050
1135
|
}
|
|
1051
1136
|
return out;
|
|
1052
1137
|
}
|
|
1138
|
+
var localeService = null;
|
|
1139
|
+
function detectLang() {
|
|
1140
|
+
if (localeService !== null) {
|
|
1141
|
+
return localeService.getLocale().active === "en" ? "en" : "zh";
|
|
1142
|
+
}
|
|
1143
|
+
const nav = navigator.languages?.[0] ?? navigator.language ?? "";
|
|
1144
|
+
return nav.toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
1145
|
+
}
|
|
1053
1146
|
function openReviewTab(text) {
|
|
1054
1147
|
const files = parseGitDiff(text);
|
|
1055
1148
|
if (files.length === 0) return;
|
|
1056
|
-
const html = buildStandaloneHtml(files, collectThemeVars(), text);
|
|
1149
|
+
const html = buildStandaloneHtml(files, collectThemeVars(), text, detectLang());
|
|
1057
1150
|
const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
|
1058
1151
|
for (const win2 of reviewWindows) {
|
|
1059
1152
|
if (win2 === null || win2.closed) reviewWindows.delete(win2);
|
|
@@ -1084,7 +1177,7 @@ function handleReviewMessage(event) {
|
|
|
1084
1177
|
const comments = sanitizeComments(data.comments);
|
|
1085
1178
|
if (comments.length === 0) return;
|
|
1086
1179
|
const kind = data.kind === "lgtm" ? "lgtm" : "comment";
|
|
1087
|
-
const report = buildCommentReport(comments, kind);
|
|
1180
|
+
const report = buildCommentReport(comments, kind, detectLang());
|
|
1088
1181
|
const source = event.source;
|
|
1089
1182
|
if (composerWriter !== null) {
|
|
1090
1183
|
composerWriter(report);
|
|
@@ -1094,13 +1187,14 @@ function handleReviewMessage(event) {
|
|
|
1094
1187
|
}
|
|
1095
1188
|
}
|
|
1096
1189
|
function ensureButtons(host, getText, fold) {
|
|
1190
|
+
const texts = CR_TEXTS[detectLang()];
|
|
1097
1191
|
if (host.querySelector("[data-dsh-cr-btn]") === null) {
|
|
1098
1192
|
const btn = document.createElement("button");
|
|
1099
1193
|
btn.type = "button";
|
|
1100
1194
|
btn.className = "dsh-cr-openbtn";
|
|
1101
1195
|
btn.dataset.dshCrBtn = "";
|
|
1102
|
-
btn.textContent =
|
|
1103
|
-
btn.title =
|
|
1196
|
+
btn.textContent = texts.openTab;
|
|
1197
|
+
btn.title = texts.openTabTitle;
|
|
1104
1198
|
btn.addEventListener("click", (event) => {
|
|
1105
1199
|
event.stopPropagation();
|
|
1106
1200
|
event.preventDefault();
|
|
@@ -1114,8 +1208,8 @@ function ensureButtons(host, getText, fold) {
|
|
|
1114
1208
|
btn.type = "button";
|
|
1115
1209
|
btn.className = "dsh-cr-openbtn";
|
|
1116
1210
|
btn.dataset.dshCrFold = "";
|
|
1117
|
-
btn.textContent = fold.folded ?
|
|
1118
|
-
btn.title = fold.folded ?
|
|
1211
|
+
btn.textContent = fold.folded ? texts.expand : texts.collapse;
|
|
1212
|
+
btn.title = fold.folded ? texts.expandTitle : texts.collapseTitle;
|
|
1119
1213
|
btn.addEventListener("click", (event) => {
|
|
1120
1214
|
event.stopPropagation();
|
|
1121
1215
|
event.preventDefault();
|
|
@@ -1361,11 +1455,18 @@ var DiffScanner = class {
|
|
|
1361
1455
|
this.applyFoldState(el, st);
|
|
1362
1456
|
const host = buttonHostFor(el);
|
|
1363
1457
|
const btn = host?.querySelector("[data-dsh-cr-fold]");
|
|
1364
|
-
if (btn instanceof HTMLElement)
|
|
1458
|
+
if (btn instanceof HTMLElement) {
|
|
1459
|
+
const texts = CR_TEXTS[detectLang()];
|
|
1460
|
+
btn.textContent = st.folded ? texts.expand : texts.collapse;
|
|
1461
|
+
}
|
|
1365
1462
|
}
|
|
1366
1463
|
};
|
|
1367
|
-
var inject = ["slots"];
|
|
1464
|
+
var inject = ["slots", "locale"];
|
|
1368
1465
|
function apply(ctx) {
|
|
1466
|
+
const locale = ctx.locale;
|
|
1467
|
+
if (typeof locale === "object" && locale !== null && typeof locale.getLocale === "function") {
|
|
1468
|
+
localeService = locale;
|
|
1469
|
+
}
|
|
1369
1470
|
ctx.slots.inject(
|
|
1370
1471
|
"shell.overlay",
|
|
1371
1472
|
() => ctx.slots.register({
|
package/lib/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client/index.tsx", "../src/client/diff-parse.ts", "../src/client/diff-highlight.ts", "../src/client/diff-view.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * @yangzhe1991/dsh-code-review \u63D2\u4EF6,\u6D4F\u89C8\u5668\u534A \u2014\u2014 Git Diff \u72EC\u7ACB\u5BA1\u67E5\u6807\u7B7E\u9875\u3002\n *\n * \u4EA4\u4E92:\u804A\u5929\u7A97\u53E3\u5185\u4FDD\u6301\u5B98\u65B9\u539F\u59CB\u663E\u793A(\u4E0D\u5C55\u5F00\u3001\u4E0D\u8F6C\u6362),\u68C0\u6D4B\u5230 git diff\n * \u8F93\u51FA\u7684\u5757\u4E0A\u81EA\u52A8\u52A0\u4E00\u4E2A\u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D\u6309\u94AE;\u70B9\u51FB\u540E\u5728\u72EC\u7ACB\u6D4F\u89C8\u5668\u6807\u7B7E\n * \u9875\u6E32\u67D3\u5B8C\u6574\u53CC\u5217 Code Review \u89C6\u56FE(\u89C1 diff-view.ts)\u3002\u5F39\u7A97\u5FC5\u987B\u7531\u7528\u6237\n * \u624B\u52BF\u89E6\u53D1,\u6240\u4EE5\u6807\u7B7E\u9875\u53EA\u80FD\u505A\u6210\u6309\u94AE\u5F62\u6001\u3002\n *\n * \u4E24\u4E2A\u6309\u94AE\u6CE8\u5165\u6765\u6E90(\u4E92\u8865,\u8986\u76D6 diff \u51FA\u73B0\u7684\u4E24\u4E2A\u9762):\n * 1. DOM \u63A2\u6D4B:markdown \u4EE3\u7801\u5757(.md-code-block,```diff/```patch \u6216\u5185\u5BB9\n * \u5F62\u4F3C diff)\u4E0E\u8BE6\u60C5\u9762\u677F\u91CC\u7684\u7EC8\u7AEF\u5361\u7247([data-terminal])\u3002\u6D41\u5F0F\u671F\u95F4\u5185\u5BB9\n * \u6301\u7EED\u53D8\u5316,\u53BB\u6296\u7A33\u5B9A\u540E\u624D\u52A0\u6309\u94AE;\u5185\u5BB9\u4E0D\u518D\u5F62\u4F3C diff \u65F6\u79FB\u9664\u6309\u94AE\u3002\n * 2. \u6570\u636E\u5C42:bash \u5DE5\u5177\u7ED3\u679C\u8F93\u51FA\u5F62\u4F3C diff \u65F6,\u6298\u53E0\u7684\u5DE5\u5177\u884C\u5728 DOM \u91CC\u6CA1\u6709\n * \u8F93\u51FA\u5185\u5BB9(\u968F\u5C55\u5F00\u624D\u6302\u8F7D),\u7EAF DOM \u63A2\u6D4B\u770B\u4E0D\u5230 \u2014\u2014 \u7531 session \u4F5C\u7528\u57DF\n * \u7EC4\u4EF6\u8BA2\u9605 chat \u5FEB\u7167,\u628A\u7ED3\u679C\u6587\u672C\u6309\u8282\u70B9 key \u5B58\u8868,\u626B\u63CF\u5668\u7ED9\u5BF9\u5E94\u7684\n * \u5DE5\u5177\u884C\u672C\u4F53([data-sample=\"bash\"])\u52A0\u6309\u94AE\u3002\u5DE5\u5177\u884C\u5185\u7684\u7EC8\u7AEF\u5361\u7247\u63A2\u6D4B\n * \u4F1A\u8DF3\u8FC7(\u907F\u514D\u540C\u4E00\u5757\u51FA\u73B0\u4E24\u4E2A\u6309\u94AE)\u3002\n */\nimport { useEffect } from 'react'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\n// \u89E6\u53D1 SlotMap \u58F0\u660E\u5408\u5E76:shell.overlay \u7531 layout\u3001header.actions \u7531 conversation \u58F0\u660E\u3002\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport { isDiffText, parseGitDiff } from './diff-parse'\nimport { buildCommentReport, buildStandaloneHtml } from './diff-view'\nimport type { ReviewComment } from './diff-view'\n\n// \u2014\u2014 \u53EF\u8C03\u53C2\u6570 \u2014\u2014\n\n/** \u7A33\u5B9A\u53BB\u6296\u7A97\u53E3:\u5185\u5BB9\u5728\u8FD9\u4E2A\u7A97\u53E3\u5185\u4E0D\u518D\u53D8\u5316\u624D\u52A0\u6309\u94AE(\u6D41\u5F0F\u671F\u95F4\u4E0D\u65AD\u91CD\u7F6E)\u3002 */\nconst STABLE_MS = 1000\n/** \u5168\u91CF\u626B\u63CF\u53BB\u6296(MutationObserver \u89E6\u53D1\u9891\u7E41,\u5408\u5E76\u6210\u4E00\u6B21\u626B\u63CF)\u3002 */\nconst SCAN_DEBOUNCE = 300\n/** \u89E3\u6790\u5931\u8D25\u540E\u7684\u91CD\u8BD5\u95F4\u9694(\u7578\u5F62 diff \u5185\u5BB9\u7A33\u5B9A\u540E\u53EF\u80FD\u53D8\u5408\u6CD5)\u3002 */\nconst RETRY_MS = 3000\n/** \u515C\u5E95\u5168\u91CF\u626B\u63CF\u95F4\u9694:\u8986\u76D6\u4EFB\u4F55\u6F0F\u6389\u7684 mutation \u8DEF\u5F84(\u7406\u8BBA\u4E0D\u8BE5\u6709)\u3002 */\nconst SAFETY_SCAN_MS = 10000\n\n/** \u6CE8\u5165\u7684 <style> \u662F\u5426\u5DF2\u5B58\u5728,\u907F\u514D\u91CD\u590D\u6CE8\u5165\u3002 */\nlet styleInjected = false\n\n// \u2014\u2014 \u6309\u94AE\u6837\u5F0F(\u5B98\u65B9\u8BED\u4E49\u53D8\u91CF,\u6DF1\u6D45\u4E3B\u9898\u81EA\u52A8\u9002\u914D)\u2014\u2014\n\nconst BUTTON_CSS = `\n.dsh-cr-openbtn {\n margin-left: auto;\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n background: transparent;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 1px 10px;\n cursor: pointer;\n white-space: nowrap;\n}\n.dsh-cr-openbtn:hover {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-border-l3);\n}\n`\n\n/** \u5168\u5C40\u6CE8\u5165\u4E00\u6B21\u6309\u94AE\u6837\u5F0F(\u6D4F\u89C8\u5668\u7AEF bundle \u7684\u6A21\u5757\u7EA7\u526F\u4F5C\u7528)\u3002 */\nfunction ensureStyle(): void {\n if (styleInjected || typeof document === 'undefined') return\n styleInjected = true\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-code-review'\n tag.textContent = BUTTON_CSS\n document.head.appendChild(tag)\n}\n\n// \u2014\u2014 \u72EC\u7ACB\u6807\u7B7E\u9875\u6253\u5F00 \u2014\u2014\n\n/** \u72EC\u7ACB\u6807\u7B7E\u9875\u9700\u8981\u7EE7\u627F\u7684 DSW \u4E3B\u9898\u53D8\u91CF(\u53D6\u4E0D\u5230\u65F6\u7ED9\u6D45\u8272\u8FD1\u4F3C\u503C\u515C\u5E95)\u3002 */\nconst THEME_VAR_DEFAULTS: Record<string, string> = {\n '--dsw-alias-bg-base': '#ffffff',\n '--dsw-alias-bg-layer-1': '#f7f7f8',\n '--dsw-alias-markdown-code-block': '#f4f4f5',\n '--dsw-alias-label-primary': '#1a1a1a',\n '--dsw-alias-label-secondary': '#555555',\n '--dsw-alias-label-tertiary': '#888888',\n '--dsw-alias-border-l2': '#e5e5e7',\n '--dsw-alias-border-l3': '#cfcfd2',\n '--dsw-alias-state-error-primary': '#d92d20',\n '--dsw-alias-state-success-primary': '#12b76a',\n '--dsw-alias-state-business-primary': '#3b6fe0',\n '--ds-font-family-code': 'ui-monospace, SFMono-Regular, Menlo, monospace',\n // \u8BED\u6CD5\u9AD8\u4EAE token \u989C\u8272:\u5B98\u65B9 shiki \u4E3B\u9898\u53D8\u91CF(\u4E0E DSH \u6DF1\u6D45\u4E3B\u9898\u4E00\u81F4)\u3002\n '--shiki-token-keyword': '#7c3aed',\n '--shiki-token-string': '#16a34a',\n '--shiki-token-comment': '#9aa0a6',\n '--shiki-token-constant': '#d97706',\n '--shiki-token-function': '#2563eb',\n}\n\n/** \u4ECE\u5BBF\u4E3B\u9875\u9762\u6536\u96C6\u4E3B\u9898\u53D8\u91CF\u7684\u5B9E\u9645\u503C(\u72EC\u7ACB\u6807\u7B7E\u9875\u4E0E DSH \u6DF1\u6D45\u8272\u4FDD\u6301\u4E00\u81F4)\u3002 */\nfunction collectThemeVars(): Record<string, string> {\n const out: Record<string, string> = {}\n const styles = getComputedStyle(document.body)\n for (const name of Object.keys(THEME_VAR_DEFAULTS)) {\n const value = styles.getPropertyValue(name).trim()\n out[name] = value !== '' ? value : THEME_VAR_DEFAULTS[name]\n }\n return out\n}\n\n/**\n * \u6253\u5F00\u72EC\u7ACB\u5BA1\u67E5\u6807\u7B7E\u9875\u3002\u5FC5\u987B\u5728\u7528\u6237\u70B9\u51FB\u624B\u52BF\u5185\u540C\u6B65\u6267\u884C(Blob URL \u751F\u6210 +\n * window.open \u5168\u540C\u6B65),\u5426\u5219\u4F1A\u88AB\u6D4F\u89C8\u5668\u5F39\u7A97\u62E6\u622A\u3002URL \u5EF6\u8FDF\u91CA\u653E\u3002\n * \u8FD4\u56DE\u7684\u5B50\u7A97\u53E3\u5F15\u7528\u767B\u8BB0\u8FDB reviewWindows,\u8BC4\u8BBA\u56DE\u4F20\u65F6\u6821\u9A8C\u6D88\u606F\u6765\u6E90\u3002\n */\nfunction openReviewTab(text: string): void {\n const files = parseGitDiff(text)\n if (files.length === 0) return\n const html = buildStandaloneHtml(files, collectThemeVars(), text)\n const url = URL.createObjectURL(new Blob([html], { type: 'text/html' }))\n // \u987A\u5E26\u6E05\u6389\u5DF2\u5173\u95ED\u7684\u65E7\u5B50\u7A97\u53E3\u5F15\u7528\u3002\n for (const win of reviewWindows) {\n if (win === null || win.closed) reviewWindows.delete(win)\n }\n const win = window.open(url, '_blank')\n if (win !== null) reviewWindows.add(win)\n window.setTimeout(() => URL.revokeObjectURL(url), 60000)\n}\n\n// \u2014\u2014 \u8BC4\u8BBA\u56DE\u4F20(\u72EC\u7ACB\u6807\u7B7E\u9875 \u2192 DSH \u5BF9\u8BDD\u6846)\u2014\u2014\n\n/** \u672C\u63D2\u4EF6\u6253\u5F00\u7684\u5BA1\u67E5\u5B50\u7A97\u53E3(\u6D88\u606F\u6765\u6E90\u6821\u9A8C\u7528)\u3002 */\nconst reviewWindows = new Set<Window | null>()\n\n/** \u5F53\u524D\u4F1A\u8BDD\u7684 composer \u5199\u5165\u5668(header.actions \u7EC4\u4EF6\u6302\u8F7D\u65F6\u6CE8\u518C)\u3002 */\nlet composerWriter: ((text: string) => void) | null = null\n\n/** \u6821\u9A8C\u56DE\u4F20\u8BC4\u8BBA\u7684\u5F62\u72B6,\u8FC7\u6EE4\u975E\u6CD5\u6761\u76EE(\u8DE8\u7A97\u53E3\u6570\u636E\u4E0D\u53EF\u4FE1)\u3002 */\nfunction sanitizeComments(raw: unknown): ReviewComment[] {\n if (!Array.isArray(raw)) return []\n const out: ReviewComment[] = []\n for (const item of raw) {\n if (typeof item !== 'object' || item === null) continue\n const c = item as { path?: unknown; lineNo?: unknown; text?: unknown }\n if (typeof c.path !== 'string' || typeof c.lineNo !== 'number' || typeof c.text !== 'string') continue\n if (c.text.trim() === '') continue\n out.push({ path: c.path, lineNo: Math.trunc(c.lineNo), text: c.text })\n }\n return out\n}\n\n/**\n * \u5904\u7406\u5BA1\u67E5\u9875\u56DE\u4F20\u7684\u8BC4\u8BBA:\u7EC4\u88C5\u300C\u6587\u4EF6\u540D:\u884C\u53F7 \u2014 \u8BC4\u8BBA\u300D\u62A5\u544A\u6587\u672C,\u5199\u5165\n * \u5F53\u524D\u4F1A\u8BDD\u7684 composer,\u5E76\u5411\u5B50\u7A97\u53E3\u56DE\u6267\u3002\u6D88\u606F\u6765\u6E90\u6821\u9A8C:\u5FC5\u987B\u662F\u672C\u63D2\u4EF6\n * \u6253\u5F00\u7684\u7A97\u53E3(\u4E0E origin \u76F8\u6BD4\u66F4\u53EF\u9760,blob \u5B50\u7A97\u53E3\u7684 origin \u8BED\u4E49\u6709\u5DEE\u5F02)\u3002\n */\nfunction handleReviewMessage(event: MessageEvent): void {\n const data = event.data\n if (typeof data !== 'object' || data === null) return\n if (data.type !== 'dsh-code-review-comments') return\n if (!reviewWindows.has(event.source as Window | null)) return\n const comments = sanitizeComments(data.comments)\n if (comments.length === 0) return\n const kind = data.kind === 'lgtm' ? 'lgtm' : 'comment'\n const report = buildCommentReport(comments, kind)\n const source = event.source as Window\n if (composerWriter !== null) {\n composerWriter(report)\n source.postMessage({ type: 'dsh-code-review-ack', ok: true }, '*')\n } else {\n source.postMessage({ type: 'dsh-code-review-ack', ok: false }, '*')\n }\n}\n\n// \u2014\u2014 \u6309\u94AE\u6CE8\u5165(\u7EAF DOM,\u4E0D\u4F9D\u8D56 React)\u2014\u2014\n\n/**\n * \u7ED9\u5BBF\u4E3B\u5143\u7D20(\u4EE3\u7801\u5757 banner / \u7EC8\u7AEF\u5361\u7247 header / bash \u5DE5\u5177\u884C\u672C\u4F53)\u52A0\u6309\u94AE:\n * \u2460\u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D;\u2461 \u4EE3\u7801\u5757\u573A\u666F\u518D\u52A0\u300C\u5C55\u5F00/\u6536\u8D77\u300D\u5F00\u5173(\u9ED8\u8BA4\u6298\u53E0\n * \u539F\u59CB diff \u6587\u672C,\u804A\u5929\u6D41\u4FDD\u6301\u7D27\u51D1)\u3002\u5DF2\u5B58\u5728\u7684\u6309\u94AE\u8DF3\u8FC7\u3002\n * @param host \u6309\u94AE\u6302\u8F7D\u70B9(React \u4E0D\u8FFD\u8E2A\u6211\u4EEC\u7684\u8282\u70B9,\u91CD\u6E32\u67D3\u4E00\u822C\u4E0D\u52A8\u5B83)\u3002\n * @param getText \u70B9\u51FB\u65F6\u83B7\u53D6 diff \u539F\u59CB\u6587\u672C(\u73B0\u8BFB,\u907F\u514D\u8FC7\u671F\u5F15\u7528)\u3002\n * @param fold \u4EE3\u7801\u5757\u6298\u53E0\u5F00\u5173(\u975E\u4EE3\u7801\u5757\u573A\u666F\u4F20 null)\u3002\n */\nfunction ensureButtons(\n host: HTMLElement,\n getText: () => string | null,\n fold: { folded: boolean; onToggle: () => void } | null,\n): void {\n if (host.querySelector('[data-dsh-cr-btn]') === null) {\n const btn = document.createElement('button')\n btn.type = 'button'\n btn.className = 'dsh-cr-openbtn'\n btn.dataset.dshCrBtn = ''\n btn.textContent = '\u29C9 \u65B0\u6807\u7B7E\u9875\u6253\u5F00'\n btn.title = '\u5728\u72EC\u7ACB\u7684\u6D4F\u89C8\u5668\u6807\u7B7E\u9875\u4E2D\u6253\u5F00 Code Review \u89C6\u56FE'\n btn.addEventListener('click', (event) => {\n // bash \u5DE5\u5177\u884C\u672C\u4F53\u6574\u884C\u53EF\u70B9(\u5C55\u5F00),\u5FC5\u987B\u963B\u6B62\u4E8B\u4EF6\u4F20\u64AD\u5230\u5B98\u65B9\u70B9\u51FB\u3002\n event.stopPropagation()\n event.preventDefault()\n const text = getText()\n if (text !== null) openReviewTab(text)\n })\n host.appendChild(btn)\n }\n if (fold !== null && host.querySelector('[data-dsh-cr-fold]') === null) {\n const btn = document.createElement('button')\n btn.type = 'button'\n btn.className = 'dsh-cr-openbtn'\n btn.dataset.dshCrFold = ''\n btn.textContent = fold.folded ? '\u5C55\u5F00' : '\u6536\u8D77'\n btn.title = fold.folded ? '\u5C55\u5F00\u539F\u59CB diff \u6587\u672C' : '\u6536\u8D77\u539F\u59CB diff \u6587\u672C'\n btn.addEventListener('click', (event) => {\n event.stopPropagation()\n event.preventDefault()\n fold.onToggle()\n })\n host.appendChild(btn)\n }\n}\n\n/** \u79FB\u9664\u5BBF\u4E3B\u4E0A\u7684\u6240\u6709\u6309\u94AE(\u5185\u5BB9\u4E0D\u518D\u5F62\u4F3C diff / \u89E3\u6790\u5931\u8D25\u65F6)\u3002 */\nfunction removeButtons(host: HTMLElement): void {\n for (const btn of host.querySelectorAll('[data-dsh-cr-btn], [data-dsh-cr-fold]')) {\n btn.remove()\n }\n}\n\n// \u2014\u2014 \u5019\u9009\u5757\u63A2\u6D4B(DOM \u6765\u6E90)\u2014\u2014\n\n/** \u63A2\u6D4B\u7ED3\u679C:\u539F\u59CB\u5185\u5BB9 + \u662F\u5426\u503C\u5F97\u52A0\u6309\u94AE + \u662F\u5426\u8FD8\u5728\u6D41\u5F0F/\u8FD0\u884C\u3002 */\ninterface Probe {\n /** \u5B98\u65B9\u539F\u59CB\u5185\u5BB9\u6587\u672C(\u5185\u5BB9\u7B7E\u540D,\u7528\u4E8E\u5224\u65AD\u300C\u5185\u5BB9\u662F\u5426\u53D8\u5316\u300D)\u3002 */\n text: string\n /** \u662F\u5426\u503C\u5F97\u52A0\u6309\u94AE(\u8BED\u8A00\u6807\u7B7E\u662F diff/patch,\u6216\u6587\u672C\u5F62\u4F3C git diff)\u3002 */\n candidate: boolean\n /** \u7EC8\u7AEF\u5361\u7247\u4ECD\u5728\u8FD0\u884C(\u8F93\u51FA\u672A\u5B8C\u6210)\u3002 */\n running: boolean\n}\n\n/**\n * \u5B9A\u4F4D markdown \u4EE3\u7801\u5757\u7684\u5B98\u65B9\u539F\u59CB\u5185\u5BB9\u5BB9\u5668(body)\u3002\n *\n * \u4E0D\u80FD\u6309\u300C\u7B2C 2 \u4E2A\u5B50\u8282\u70B9\u300D\u53D6:React \u5728\u8BED\u6CD5\u9AD8\u4EAE\u5668\u5C31\u7EEA\u65F6\u4F1A\u628A body \u4ECE\n * `<pre>` \u6362\u6210\u9AD8\u4EAE `<div>`,\u800C\u65B0\u8282\u70B9\u4F1A append \u5230\u7236\u7EA7\u672B\u5C3E(React \u53EA\u8FFD\u8E2A\n * \u81EA\u5DF1\u7684\u5B50\u8282\u70B9,\u6211\u4EEC\u7684\u6309\u94AE\u662F\u672A\u8FFD\u8E2A\u8282\u70B9,\u4F1A\u88AB\u6324\u5230\u524D\u9762)\u3002\n * \u56E0\u6B64\u6309\u7ED3\u6784\u627E:\u8DF3\u8FC7\u6211\u4EEC\u7684\u8282\u70B9(data-dsh-cr-btn),\u7B2C\u4E00\u4E2A PRE \u5373 body;\n * \u6CA1\u6709 PRE \u65F6,\u7B2C\u4E00\u4E2A\u300C\u975E banner\u300D\u7684 DIV \u5373\u9AD8\u4EAE body\u3002\n */\nfunction findCodeBlockBody(el: HTMLElement): HTMLElement | null {\n const first = el.firstElementChild\n for (const child of el.children) {\n if (!(child instanceof HTMLElement)) continue\n if (child.hasAttribute('data-dsh-cr-btn') || child.hasAttribute('data-dsh-cr-fold')) continue\n if (child.tagName === 'PRE') return child\n }\n for (const child of el.children) {\n if (!(child instanceof HTMLElement)) continue\n if (child.hasAttribute('data-dsh-cr-btn') || child.hasAttribute('data-dsh-cr-fold')) continue\n if (child === first) continue // banner\n if (child.tagName === 'DIV') return child\n }\n return null\n}\n\n/** \u63A2\u6D4B markdown \u4EE3\u7801\u5757:\u7ED3\u6784\u4E3A [banner(\u8BED\u8A00\u6807\u7B7E+\u590D\u5236\u6309\u94AE), body]\u3002 */\nfunction probeCodeBlock(el: HTMLElement): Probe | null {\n const banner = el.firstElementChild\n const body = findCodeBlockBody(el)\n if (!(banner instanceof HTMLElement) || body === null) return null\n const lang = (banner.firstElementChild?.textContent ?? '').trim().toLowerCase()\n const text = body.textContent ?? ''\n const candidate = lang === 'diff' || lang === 'patch' || isDiffText(text)\n return { text, candidate, running: false }\n}\n\n/**\n * \u5728\u7EC8\u7AEF\u5361\u7247\u91CC\u627E\u8F93\u51FA\u5BB9\u5668:\u8DF3\u8FC7\u7B2C 0 \u4E2A\u5B50\u8282\u70B9(\u5934\u90E8:\u547D\u4EE4/\u72B6\u6001/\u590D\u5236\u6309\u94AE),\n * \u7B2C\u4E00\u4E2A\u5185\u5BB9\u5F62\u4F3C git diff \u7684 div \u5373\u8F93\u51FA\u5BB9\u5668(\u7A7A\u6001/\u5934\u90E8\u90FD\u4E0D\u4F1A\u5F62\u4F3C diff,\n * \u5929\u7136\u6392\u9664);\u6211\u4EEC\u7684\u6309\u94AE\u5E26 data-dsh-cr-btn,\u4E00\u5E76\u8DF3\u8FC7\u3002\n */\nfunction findOutputContainer(wrapper: HTMLElement): HTMLElement | null {\n const children = wrapper.children\n for (let i = 1; i < children.length; i += 1) {\n const child = children[i]\n if (!(child instanceof HTMLElement) || child.tagName !== 'DIV') continue\n if (child.hasAttribute('data-dsh-cr-btn') || child.hasAttribute('data-dsh-cr-fold')) continue\n if (isDiffText(child.textContent ?? '')) return child\n }\n return null\n}\n\n/**\n * \u8F93\u51FA\u5BB9\u5668\u9010\u884C\u53D6\u6587\u672C:\u6BCF\u884C\u662F div.line(ANSI \u5DF2\u6E32\u67D3\u6210 span,textContent \u5373\n * \u51C0\u5316\u540E\u7684\u7EAF\u6587\u672C);\u6298\u53E0\u6309\u94AE\u7B49\u975E\u884C\u5143\u7D20\u8DF3\u8FC7\u3002\n */\nfunction joinOutputLines(container: HTMLElement): string {\n const parts: string[] = []\n for (const child of container.children) {\n if (child instanceof HTMLElement && child.tagName === 'DIV') {\n parts.push(child.textContent ?? '')\n }\n }\n return parts.join('\\n')\n}\n\n/** \u63A2\u6D4B\u7EC8\u7AEF\u5361\u7247:\u8FD0\u884C\u4E2D\u76F4\u63A5\u6807\u8BB0 running,\u7531\u8C03\u7528\u65B9\u8DF3\u8FC7\u3002 */\nfunction probeTerminal(el: HTMLElement): Probe | null {\n if (el.hasAttribute('data-running')) {\n return { text: '', candidate: false, running: true }\n }\n const output = findOutputContainer(el)\n if (output === null) return null\n const text = joinOutputLines(output)\n return { text, candidate: isDiffText(text), running: false }\n}\n\n/** \u6309\u5143\u7D20\u7C7B\u578B\u5206\u53D1\u63A2\u6D4B\u3002 */\nfunction probeFor(el: HTMLElement): Probe | null {\n if (el.classList.contains('md-code-block')) return probeCodeBlock(el)\n if (el.hasAttribute('data-terminal')) {\n // \u5DE5\u5177\u884C\u5185\u7684\u7EC8\u7AEF\u5361\u7247:\u6309\u94AE\u52A0\u5728\u884C\u672C\u4F53\u4E0A(\u7531\u6570\u636E\u5C42\u6CE8\u5165,\u6298\u53E0\u65F6\u4E5F\n // \u53EF\u89C1),\u8FD9\u91CC\u8DF3\u8FC7,\u907F\u514D\u540C\u4E00\u5757\u51FA\u73B0\u4E24\u4E2A\u6309\u94AE\u3002\n if (el.closest('[data-sample=\"bash\"]') !== null) return null\n return probeTerminal(el)\n }\n return null\n}\n\n/** \u6309\u94AE\u6302\u8F7D\u70B9:\u4EE3\u7801\u5757 = banner,\u7EC8\u7AEF\u5361\u7247 = header(\u90FD\u662F\u5B98\u65B9\u7B2C\u4E00\u4E2A\u5B50\u8282\u70B9)\u3002 */\nfunction buttonHostFor(el: HTMLElement): HTMLElement | null {\n const first = el.firstElementChild\n return first instanceof HTMLElement ? first : null\n}\n\n// \u2014\u2014 \u6570\u636E\u5C42(\u6298\u53E0\u7684 bash \u5DE5\u5177\u884C)\u2014\u2014\n\n/**\n * \u6570\u636E\u5C42\u6587\u672C\u8868:node key \u2192 { sessionId, diff \u6587\u672C }\u3002\n *\n * \u6298\u53E0\u7684 bash \u5DE5\u5177\u884C\u5728 DOM \u91CC\u53EA\u6709\u4E00\u884C\u6458\u8981,\u8F93\u51FA\u5185\u5BB9\u968F\u5C55\u5F00\u624D\u6302\u8F7D,\n * DOM \u63A2\u6D4B\u770B\u4E0D\u5230 \u2014\u2014 \u53EA\u80FD\u4ECE chat \u5FEB\u7167(\u6570\u636E\u5C42)\u62FF\u5230\u7ED3\u679C\u6587\u672C\u3002\n */\nconst pendingTexts = new Map<string, { sessionId: string; text: string }>()\n\n/** chat \u5FEB\u7167\u7684\u6700\u5C0F\u5F62\u72B6(\u4E0E dsh-web-enhance \u540C\u53E3\u5F84,\u53EA\u53D6\u6240\u9700\u5B57\u6BB5)\u3002 */\ninterface ChatLike {\n order: readonly string[]\n nodes: { get(key: string): { kind: string; data: unknown } | undefined }\n}\n\n/** \u4ECE tool-call \u8282\u70B9 data \u91CC\u53D6\u5DE5\u5177\u7ED3\u679C\u6839(root: RunningToolCall | ToolResultNode)\u3002 */\nfunction readToolRoot(data: unknown): unknown {\n if (typeof data !== 'object' || data === null) return null\n return (data as { root?: unknown }).root ?? null\n}\n\n/** \u5DE5\u5177\u7ED3\u679C\u662F\u5426\u5DF2\u843D\u5730(settled ToolResultNode:\u5E26 content \u6570\u7EC4)\u3002 */\nfunction isSettledToolResult(root: unknown): root is { call: { name: string } | null; content: readonly unknown[] } {\n if (typeof root !== 'object' || root === null) return false\n const r = root as { call?: unknown; content?: unknown }\n if (!Array.isArray(r.content)) return false\n // call \u5934\u53EF\u80FD\u88AB\u7A97\u53E3\u622A\u65AD(call: null),\u4FDD\u5B88\u8D77\u89C1\u53EA\u8BA4\u5F97\u5230\u5DE5\u5177\u540D\u7684\u3002\n if (r.call !== null && (typeof r.call !== 'object' || r.call === null || typeof (r.call as { name?: unknown }).name !== 'string')) {\n return false\n }\n return true\n}\n\n/** \u62FC\u63A5\u5DE5\u5177\u7ED3\u679C content \u91CC\u7684\u6587\u672C\u5757(\u5DE5\u5177\u8F93\u51FA\u901A\u5E38\u662F\u4E00\u6BB5 text \u5757)\u3002 */\nfunction toolResultText(root: unknown): string {\n if (!isSettledToolResult(root)) return ''\n const parts: string[] = []\n for (const block of root.content) {\n if (typeof block !== 'object' || block === null) continue\n const b = block as { type?: unknown; text?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') parts.push(b.text)\n }\n return parts.join('\\n')\n}\n\n/**\n * \u628A\u5F53\u524D\u4F1A\u8BDD chat \u5FEB\u7167\u91CC\u300C\u8F93\u51FA\u5F62\u4F3C git diff \u7684 bash \u5DE5\u5177\u7ED3\u679C\u300D\u540C\u6B65\u8FDB\n * \u6587\u672C\u8868:\u5148\u6E05\u6389\u672C\u4F1A\u8BDD\u65E7\u6761\u76EE(\u9632\u5DE8\u578B\u6587\u672C\u8DE8\u4F1A\u8BDD\u7D2F\u79EF),\u518D\u5199\u5165\u65B0\u6761\u76EE\u3002\n */\nfunction syncDiffToolTexts(sessionId: string, chat: ChatLike): void {\n const found = new Map<string, string>()\n for (const key of chat.order) {\n const node = chat.nodes.get(key)\n if (node === undefined || node.kind !== 'tool-call') continue\n const root = readToolRoot(node.data)\n if (root === null || !isSettledToolResult(root)) continue\n if (root.call === null || root.call.name !== 'bash') continue\n const text = toolResultText(root)\n if (!isDiffText(text)) continue\n found.set(key, text)\n }\n for (const [key, entry] of pendingTexts) {\n if (entry.sessionId === sessionId && !found.has(key)) pendingTexts.delete(key)\n }\n for (const [key, text] of found) pendingTexts.set(key, { sessionId, text })\n}\n\n/** \u6309 data-chat-anchor-key \u627E\u6E32\u67D3\u884C(\u904D\u5386\u6BD4\u5BF9,\u907F\u514D key \u7279\u6B8A\u5B57\u7B26\u8F6C\u4E49\u95EE\u9898)\u3002 */\nfunction findRowByKey(key: string): HTMLElement | null {\n for (const el of document.querySelectorAll('[data-chat-anchor-key]')) {\n if (el instanceof HTMLElement && el.getAttribute('data-chat-anchor-key') === key) return el\n }\n return null\n}\n\n/** \u7ED9\u6570\u636E\u5C42\u6536\u96C6\u5230\u7684 bash \u5DE5\u5177\u884C\u52A0\u6309\u94AE(\u884C\u672A\u6E32\u67D3\u65F6\u7B49\u4E0B\u4E00\u8F6E\u626B\u63CF)\u3002 */\nfunction attachToolRowButtons(): void {\n for (const [key, entry] of pendingTexts) {\n const row = findRowByKey(key)\n if (row === null) continue\n const sample = row.querySelector('[data-sample=\"bash\"]')\n if (!(sample instanceof HTMLElement)) continue\n ensureButtons(sample, () => pendingTexts.get(key)?.text ?? entry.text, null)\n }\n}\n\n// \u2014\u2014 \u626B\u63CF\u5668 \u2014\u2014\n\n/** \u4E00\u4E2A\u5019\u9009\u5757\u7684\u8DDF\u8E2A\u72B6\u6001(DOM \u6765\u6E90)\u3002 */\ninterface BlockState {\n /** \u4E0A\u6B21\u68C0\u6D4B\u5230\u7684\u5185\u5BB9\u7B7E\u540D\u3002 */\n text: string\n /** \u7A33\u5B9A\u53BB\u6296\u5B9A\u65F6\u5668(null = \u6CA1\u6709\u5F85\u5904\u7406\u7684\u52A0\u6309\u94AE)\u3002 */\n timer: number | null\n /** \u4E0A\u6B21\u89E3\u6790\u5931\u8D25\u7684\u65F6\u95F4\u6233(\u91CD\u8BD5\u8282\u6D41)\u3002 */\n lastTry: number\n /** \u4E0A\u6B21\u89E3\u6790\u662F\u5426\u5931\u8D25(\u5931\u8D25\u65F6\u5185\u5BB9\u4E0D\u53D8\u4E5F\u4F1A\u6309\u8282\u6D41\u91CD\u8BD5)\u3002 */\n failed: boolean\n /** \u4EE3\u7801\u5757\u662F\u5426\u6298\u53E0\u539F\u59CB\u6587\u672C(\u9ED8\u8BA4\u6298\u53E0,\u804A\u5929\u6D41\u4FDD\u6301\u7D27\u51D1)\u3002 */\n folded: boolean\n}\n\n/**\n * \u5168\u6587\u6863\u626B\u63CF\u5668:\u53D1\u73B0\u5019\u9009\u5757\u3001\u53BB\u6296\u3001\u6CE8\u5165\u6309\u94AE\u3001\u5185\u5BB9\u53D8\u5316\u65F6\u56DE\u9000\u3002\n *\n * \u72B6\u6001\u673A(\u6BCF\u4E2A\u5757):\n * - \u5185\u5BB9\u53D8\u5316/\u65B0\u5143\u7D20 \u2192 \u6392 STABLE_MS \u53BB\u6296;\n * - \u53BB\u6296\u5230\u70B9\u4E14\u5185\u5BB9\u672A\u518D\u53D8 \u2192 \u89E3\u6790\u9A8C\u8BC1(\u80FD\u89E3\u6790\u51FA\u6587\u4EF6\u624D\u52A0\u6309\u94AE);\n * - \u5185\u5BB9\u4E0D\u518D\u5F62\u4F3C diff / \u7ED3\u6784\u6D88\u5931 \u2192 \u79FB\u9664\u6309\u94AE\u3002\n */\nclass DiffScanner {\n private readonly states = new WeakMap<HTMLElement, BlockState>()\n private observer: MutationObserver | null = null\n private scanTimer: number | null = null\n private safetyTimer: number | null = null\n\n start(): void {\n if (this.observer !== null) return\n // \u4EFB\u4F55 DOM \u589E\u5220\u90FD\u89E6\u53D1\u4E00\u6B21\u53BB\u6296\u626B\u63CF:\u8986\u76D6\u6D41\u5F0F\u8FFD\u52A0\u3001\u865A\u62DF\u5316\u91CD\u6302\u8F7D\u3001\n // \u5DE5\u5177\u884C\u5C55\u5F00\u7B49\u6240\u6709\u51FA\u73B0\u8DEF\u5F84\u3002\n this.observer = new MutationObserver(() => this.scheduleScan())\n this.observer.observe(document.body, { childList: true, subtree: true })\n this.scheduleScan()\n // \u515C\u5E95\u5468\u671F\u626B\u63CF:\u5185\u5BB9\u53D8\u5316\u7406\u8BBA\u4E0A\u90FD\u4F1A\u4EA7\u751F childList mutation,\n // \u8FD9\u91CC\u515C\u4F4F\u9057\u6F0F\u8DEF\u5F84,\u987A\u5E26\u8BA9\u89E3\u6790\u5931\u8D25\u7684\u91CD\u8BD5\u6709\u673A\u4F1A\u89E6\u53D1\u3002\n this.safetyTimer = window.setInterval(() => this.scan(), SAFETY_SCAN_MS)\n }\n\n stop(): void {\n this.observer?.disconnect()\n this.observer = null\n if (this.scanTimer !== null) {\n clearTimeout(this.scanTimer)\n this.scanTimer = null\n }\n if (this.safetyTimer !== null) {\n clearInterval(this.safetyTimer)\n this.safetyTimer = null\n }\n // states \u91CC\u7684\u53BB\u6296\u5B9A\u65F6\u5668\u4E0D\u5FC5\u9010\u4E2A\u6E05\u7406:\u5230\u70B9\u540E probe \u590D\u67E5\u4F1A\u628A\u5DF2\u65AD\u5F00\n // \u5143\u7D20\u7684\u6587\u672C\u8BFB\u6210\u7A7A\u4E32\u3001candidate=false,\u81EA\u7136\u653E\u5F03;WeakMap \u968F\u5757 GC\u3002\n }\n\n /** \u5408\u5E76\u9AD8\u9891 mutation \u4E3A\u4E00\u6B21\u626B\u63CF\u3002 */\n private scheduleScan(): void {\n if (this.scanTimer !== null) return\n this.scanTimer = window.setTimeout(() => {\n this.scanTimer = null\n this.scan()\n }, SCAN_DEBOUNCE)\n }\n\n /** \u5168\u91CF\u626B\u63CF:\u6570\u636E\u5C42\u5DE5\u5177\u884C\u6309\u94AE + DOM \u5019\u9009\u5757\u3002 */\n private scan(): void {\n attachToolRowButtons()\n for (const el of document.querySelectorAll('.md-code-block, [data-terminal]')) {\n this.inspect(el as HTMLElement)\n }\n }\n\n /** \u68C0\u67E5\u4E00\u4E2A\u5019\u9009\u5757:\u5185\u5BB9\u672A\u53D8\u5219\u4E0D\u52A8,\u53D8\u4E86\u5219\u6392\u53BB\u6296,\u4E0D\u518D\u5F62\u4F3C diff \u5219\u79FB\u9664\u6309\u94AE\u3002 */\n private inspect(el: HTMLElement): void {\n const probe = probeFor(el)\n if (probe === null || probe.running || !probe.candidate || probe.text.trim() === '') {\n const host = buttonHostFor(el)\n if (host !== null) removeButtons(host)\n // \u6062\u590D\u5B98\u65B9\u539F\u59CB\u663E\u793A(\u6B64\u524D\u53EF\u80FD\u88AB\u6298\u53E0)\u3002\n const body = findCodeBlockBody(el)\n if (body !== null) body.style.display = ''\n this.states.delete(el)\n return\n }\n const st = this.states.get(el)\n if (st !== undefined && st.text === probe.text) {\n // \u5185\u5BB9\u672A\u53D8:\u6309\u94AE\u5DF2\u52A0/\u53BB\u6296\u4E2D \u2192 \u65E0\u4E8B;\u89E3\u6790\u5931\u8D25 \u2192 \u6309\u8282\u6D41\u91CD\u8BD5\u3002\n if (!st.failed) {\n // \u91CD\u65B0\u540C\u6B65\u6298\u53E0\u6001:React \u53EF\u80FD\u5728\u540E\u53F0\u66FF\u6362\u8FC7 body \u8282\u70B9(\u8BED\u6CD5\u9AD8\u4EAE\n // \u5C31\u7EEA\u65F6 pre \u2192 div),\u65B0\u8282\u70B9\u9700\u8981\u91CD\u65B0\u9690\u85CF/\u663E\u793A\u3002\n this.applyFoldState(el, st)\n return\n }\n if (Date.now() - st.lastTry < RETRY_MS) return\n // \u5931\u8D25\u91CD\u8BD5\u5230\u70B9:\u76F4\u63A5\u8D70\u4E0B\u65B9\u52A0\u6309\u94AE\u5206\u652F(\u5185\u5BB9\u7A33\u5B9A,\u4E0D\u5FC5\u518D\u53BB\u6296)\u3002\n }\n if (st !== undefined && st.timer !== null) clearTimeout(st.timer)\n const timer = window.setTimeout(() => {\n const cur = this.states.get(el)\n if (cur === undefined || cur.timer !== timer) return\n cur.timer = null\n // \u5230\u70B9\u590D\u67E5:\u5185\u5BB9\u53C8\u53D8\u4E86(\u6D41\u5F0F\u8FD8\u5728\u7EE7\u7EED)\u5219\u653E\u5F03\u672C\u6B21,\u7B49\u4E0B\u4E00\u8F6E\u3002\n const p2 = probeFor(el)\n if (p2 === null || p2.running || !p2.candidate || p2.text !== cur.text) return\n this.attachButton(el, cur)\n }, STABLE_MS)\n this.states.set(el, {\n text: probe.text,\n timer,\n lastTry: st?.lastTry ?? 0,\n failed: st?.failed ?? false,\n folded: st?.folded ?? true,\n })\n }\n\n /** \u5185\u5BB9\u7A33\u5B9A\u540E\u6267\u884C:\u89E3\u6790\u9A8C\u8BC1\u3001\u6298\u53E0\u539F\u59CB\u6587\u672C\u5E76\u6CE8\u5165\u6309\u94AE(\u5931\u8D25\u5219\u4FDD\u6301\u65E0\u6309\u94AE)\u3002 */\n private attachButton(el: HTMLElement, st: BlockState): void {\n const host = buttonHostFor(el)\n if (host === null) return\n if (parseGitDiff(st.text).length === 0) {\n st.failed = true\n st.lastTry = Date.now()\n removeButtons(host)\n return\n }\n st.failed = false\n const isCodeBlock = el.classList.contains('md-code-block')\n if (isCodeBlock) {\n // \u9ED8\u8BA4\u6298\u53E0:diff \u4EE3\u7801\u5757\u53EA\u7559\u6807\u9898\u884C + \u6309\u94AE,\u4E0D\u644A\u5F00\u6574\u6BB5\u539F\u59CB\u6587\u672C\u3002\n this.applyFoldState(el, st)\n }\n // \u70B9\u51FB\u65F6\u73B0\u8BFB\u6587\u672C(React \u91CD\u6E32\u67D3\u540E\u65E7\u5F15\u7528\u53EF\u80FD\u8FC7\u671F)\u3002\n ensureButtons(\n host,\n () => probeFor(el)?.text ?? st.text,\n isCodeBlock ? { folded: st.folded, onToggle: () => this.toggleFold(el) } : null,\n )\n }\n\n /** \u540C\u6B65\u4EE3\u7801\u5757\u7684\u6298\u53E0\u6001(\u5B98\u65B9 body \u53EA\u6539 display,React \u4E0D\u7BA1\u7406\u8FD9\u4E2A style)\u3002 */\n private applyFoldState(el: HTMLElement, st: BlockState): void {\n if (!el.classList.contains('md-code-block')) return\n const body = findCodeBlockBody(el)\n if (body !== null) body.style.display = st.folded ? 'none' : ''\n }\n\n /** \u5C55\u5F00/\u6536\u8D77\u4EE3\u7801\u5757\u539F\u59CB\u6587\u672C,\u5E76\u540C\u6B65\u6309\u94AE\u6587\u6848\u3002 */\n private toggleFold(el: HTMLElement): void {\n const st = this.states.get(el)\n if (st === undefined) return\n st.folded = !st.folded\n this.applyFoldState(el, st)\n const host = buttonHostFor(el)\n const btn = host?.querySelector('[data-dsh-cr-fold]')\n if (btn instanceof HTMLElement) btn.textContent = st.folded ? '\u5C55\u5F00' : '\u6536\u8D77'\n }\n}\n\n// \u2014\u2014 \u63D2\u4EF6\u4E3B\u4F53 \u2014\u2014\n\n/** \u9700\u8981\u7684 client \u670D\u52A1:slots(slot \u6CE8\u518C)\u3002 */\nexport const inject = ['slots']\n\n/** Client \u63D2\u4EF6 body:\u6CE8\u518C\u626B\u63CF\u5668\u5BBF\u4E3B\u4E0E\u6570\u636E\u5C42\u76D1\u542C\u3002 */\nexport function apply(ctx: ClientContext): void {\n ctx.slots.inject(\n 'shell.overlay',\n () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'code-review-enhancer',\n }, CodeReviewEnhancer),\n )\n ctx.slots.inject(\n 'conversation.session.header.actions',\n () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: 'code-review-tool-watcher',\n // \u5B98\u65B9\u6761\u76EE:agent-preset=-10\u3001subagent-catalog=10\u3001job-list=20,\n // web-enhance=15\u3002\u53D6 25 \u6392\u5728\u6700\u540E,\u4E0D\u4E0E\u4ED6\u4EBA\u5E76\u5217\u3002\n order: 25,\n }, DiffToolButtonWatcher),\n )\n}\n\n/**\n * \u626B\u63CF\u5668\u5BBF\u4E3B\u7EC4\u4EF6(root \u4F5C\u7528\u57DF,\u5168\u5C40\u53EA\u6B64\u4E00\u4EFD):\u6302\u8F7D\u65F6\u6CE8\u5165\u6837\u5F0F\u3001\u542F\u52A8\n * \u5168\u6587\u6863\u626B\u63CF\u3001\u76D1\u542C\u5BA1\u67E5\u9875\u7684\u8BC4\u8BBA\u56DE\u4F20\u6D88\u606F,\u5378\u8F7D\u65F6\u5168\u90E8\u6E05\u7406\u3002\n * \u81EA\u8EAB\u6E32\u67D3 null,\u4E0D\u5360\u4EFB\u4F55\u754C\u9762\u7A7A\u95F4\u3002\n */\nfunction CodeReviewEnhancer() {\n useEffect(() => {\n ensureStyle()\n const scanner = new DiffScanner()\n scanner.start()\n const onMessage = handleReviewMessage\n window.addEventListener('message', onMessage)\n return () => {\n scanner.stop()\n window.removeEventListener('message', onMessage)\n }\n }, [])\n return null\n}\n\n/**\n * \u6570\u636E\u5C42\u76D1\u542C(session \u4F5C\u7528\u57DF,\u968F\u4F1A\u8BDD\u5207\u6362\u81EA\u52A8\u91CD\u8BA2\u9605):\n * 1. \u8BA2\u9605\u5F53\u524D\u4F1A\u8BDD chat \u5FEB\u7167,\u628A\u8F93\u51FA\u5F62\u4F3C git diff \u7684 bash \u5DE5\u5177\u7ED3\u679C\u540C\u6B65\n * \u8FDB\u6587\u672C\u8868,\u626B\u63CF\u5668\u636E\u6B64\u7ED9\u6298\u53E0\u7684\u5DE5\u5177\u884C\u52A0\u6309\u94AE;\n * 2. \u6CE8\u518C\u5F53\u524D\u4F1A\u8BDD\u7684 composer \u5199\u5165\u5668 \u2014\u2014 \u5BA1\u67E5\u9875\u56DE\u4F20\u7684\u8BC4\u8BBA\u7ECF\n * inputActions.setDraft(\u5B98\u65B9\u5355\u4E00\u5199\u5165\u53E3)\u5199\u5165\u5BF9\u8BDD\u6846\u3002\u81EA\u8EAB\u6E32\u67D3 null\u3002\n */\nfunction DiffToolButtonWatcher({ useSession, sessionId, inputActions }: PropsRuntime<'conversation.session.header.actions'>) {\n const chat = useSession((state) => state.chat)\n useEffect(() => {\n if (chat !== undefined) syncDiffToolTexts(String(sessionId), chat)\n }, [chat, sessionId])\n useEffect(() => {\n // \u53EA\u6E05\u7406\u81EA\u5DF1\u6CE8\u518C\u7684\u5199\u5165\u5668(\u591A\u7EC4\u4EF6\u5B9E\u4F8B\u5E76\u5B58\u65F6\u4E92\u4E0D\u8BEF\u5220)\u3002\n const writer = (text: string): void => inputActions.setDraft(text)\n composerWriter = writer\n return () => {\n if (composerWriter === writer) composerWriter = null\n }\n }, [inputActions])\n return null\n}\n", "/**\n * unified diff \u6587\u672C\u89E3\u6790\u5668(\u7EAF\u903B\u8F91,\u4E0D\u4F9D\u8D56 DOM,\u53EF\u5728 Node \u91CC\u5355\u6D4B)\u3002\n *\n * \u8F93\u5165\u662F `git diff` / `git show` / `git diff --no-index` \u7B49\u4EA7\u751F\u7684\n * unified diff \u6587\u672C,\u8F93\u51FA\u6309\u6587\u4EF6\u62C6\u5206\u7684\u7ED3\u6784\u5316\u6570\u636E,\u4F9B\u53CC\u5217\u89C6\u56FE\u6E32\u67D3\u3002\n *\n * \u884C\u6A21\u578B\u8BF4\u660E:\n * - ctx \u4E0A\u4E0B\u6587\u884C:\u65E7/\u65B0\u4E24\u4FA7\u90FD\u6709,\u884C\u53F7\u5404\u81EA\u9012\u589E;\n * - del \u5220\u9664\u884C:\u53EA\u5728\u65E7\u4FA7;\n * - add \u65B0\u589E\u884C:\u53EA\u5728\u65B0\u4FA7;\n * - change \u53D8\u66F4\u5BF9:\u8FDE\u7EED\u300C-\u300D\u5757\u540E\u7D27\u8DDF\u8FDE\u7EED\u300C+\u300D\u5757\u65F6,\u4E24\u4E24\u914D\u5BF9\u6210\u540C\u4E00\u884C\n * (GitHub split view \u7684\u5BF9\u9F50\u65B9\u5F0F),\u65E7\u884C\u53F7\u53D6 - \u884C\u3001\u65B0\u884C\u53F7\u53D6 + \u884C,\n * \u4E24\u4FA7\u6570\u91CF\u4E0D\u7B49\u65F6,\u591A\u51FA\u7684\u884C\u9000\u5316\u4E3A\u5355\u4FA7 del/add;\n * - gap \u95F4\u9699\u884C:@@ hunk \u5934,\u6216\u6298\u53E0\u540E\u7684\u300C\u22EF \u5C55\u5F00\u300D\u5360\u4F4D(\u6E32\u67D3\u5C42\u7528),\n * \u4E0D\u5360\u884C\u53F7\u3002\n *\n * \u5B9E\u73B0\u6CE8\u610F:\u4E0D\u628A\u300C\u5F53\u524D\u6587\u4EF6\u300D\u5B58\u6210\u88AB\u95ED\u5305\u6355\u83B7\u8D4B\u503C\u7684\u5C40\u90E8\u53D8\u91CF \u2014\u2014\n * TypeScript \u5BF9\u300C\u95ED\u5305\u5185\u8D4B\u503C\u7684 let\u300D\u4F1A\u505A\u4FDD\u5B88\u63A7\u5236\u6D41\u5206\u6790,\u5916\u5C42\u7A84\u5316\u4F1A\u5931\u6548\n * (\u751A\u81F3\u7A84\u5316\u6210 never);\u7EDF\u4E00\u7528 current() \u4ECE files \u6808\u9876\u53D6,\u907F\u514D\u8E29\u5751\u3002\n */\n\n/** \u53CC\u5217\u89C6\u56FE\u91CC\u7684\u4E00\u884C\u3002 */\nexport interface DiffRow {\n kind: 'ctx' | 'del' | 'add' | 'change' | 'gap'\n /** \u65E7\u4FA7\u884C\u53F7(ctx/del/change \u6709\u503C)\u3002 */\n oldNo?: number\n /** \u65B0\u4FA7\u884C\u53F7(ctx/add/change \u6709\u503C)\u3002 */\n newNo?: number\n /** \u65E7\u4FA7\u6587\u672C(\u5DF2\u53BB\u6389\u884C\u9996\u7684 - / \u7A7A\u683C\u524D\u7F00)\u3002 */\n oldText?: string\n /** \u65B0\u4FA7\u6587\u672C(\u5DF2\u53BB\u6389\u884C\u9996\u7684 + / \u7A7A\u683C\u524D\u7F00)\u3002 */\n newText?: string\n /** gap \u884C\u7684\u663E\u793A\u6587\u672C(@@ \u5934\u539F\u6587)\u3002 */\n gapText?: string\n /** \u65E7\u4FA7\u8BE5\u884C\u662F\u6587\u4EF6\u6700\u540E\u4E00\u884C\u4E14\u672B\u5C3E\u65E0\u6362\u884C\u7B26(\\ No newline \u6807\u8BB0)\u3002 */\n oldNoEol?: boolean\n /** \u65B0\u4FA7\u8BE5\u884C\u662F\u6587\u4EF6\u6700\u540E\u4E00\u884C\u4E14\u672B\u5C3E\u65E0\u6362\u884C\u7B26\u3002 */\n newNoEol?: boolean\n}\n\n/** \u4E00\u4E2A\u6587\u4EF6\u7684\u53D8\u66F4\u3002 */\nexport interface FileDiff {\n /** \u53D8\u66F4\u524D\u8DEF\u5F84(\u539F\u59CB\u6587\u672C,\u53EF\u80FD\u5E26 a/ \u524D\u7F00;/dev/null \u8868\u793A\u65B0\u5EFA)\u3002 */\n oldPath: string\n /** \u53D8\u66F4\u540E\u8DEF\u5F84(\u539F\u59CB\u6587\u672C,\u53EF\u80FD\u5E26 b/ \u524D\u7F00;/dev/null \u8868\u793A\u5220\u9664)\u3002 */\n newPath: string\n /** \u53D8\u66F4\u7C7B\u578B\u3002 */\n status: 'modified' | 'added' | 'deleted' | 'renamed' | 'binary' | 'mode'\n /** \u6587\u4EF6\u7EA7\u5143\u4FE1\u606F\u884C(new file mode / rename from / similarity index \u7B49)\u3002 */\n meta: string[]\n /** \u6E32\u67D3\u884C\u5E8F\u5217(\u542B hunk \u95F4\u9699\u884C)\u3002 */\n rows: DiffRow[]\n}\n\n/** \u5C55\u793A\u7528\u7684\u8DEF\u5F84:\u53BB\u6389 a/ b/ \u524D\u7F00(\u4EC5\u5F53\u786E\u5B9E\u5E26\u524D\u7F00\u65F6)\u3002 */\nexport function displayPath(path: string): string {\n if (path === '/dev/null') return path\n return path.replace(/^[ab]\\//, '')\n}\n\n/** \u6587\u672C\u662F\u5426\u5F62\u4F3C git diff(\u68C0\u6D4B\u7528,\u5148\u4E8E\u5B8C\u6574\u89E3\u6790)\u3002 */\nexport function isDiffText(text: string): boolean {\n const lines = text.split('\\n')\n // git \u6807\u51C6\u5934;\u6CA1\u6709\u5B83\u65F6,\u9000\u800C\u8981\u6C42\u300Chunk \u5934 + \u6587\u4EF6\u6807\u8BB0\u300D\u540C\u65F6\u51FA\u73B0,\n // \u907F\u514D\u628A\u666E\u901A\u5E26 @@ \u7684\u6587\u672C\u8BEF\u5224\u6210 diff\u3002\n if (lines.some((line) => line.startsWith('diff --git '))) return true\n const hasHunk = lines.some((line) => /^@@ -\\d+/.test(line))\n const hasFileMark = lines.some((line) => /^(---|\\+\\+\\+) \\S/.test(line))\n return hasHunk && hasFileMark\n}\n\n/** \u89E3\u6790\u6574\u6BB5 unified diff \u6587\u672C\u3002 */\nexport function parseGitDiff(text: string): FileDiff[] {\n const lines = text.split(/\\r?\\n/)\n const files: FileDiff[] = []\n // \u5F53\u524D\u662F\u5426\u5904\u4E8E\u67D0\u4E2A hunk \u7684\u6B63\u6587\u533A(\u8DEF\u5F84\u884C ---/+++ \u53EA\u5728 hunk \u5916\u51FA\u73B0)\u3002\n let inHunk = false\n // \u5F53\u524D hunk \u7684\u65E7/\u65B0\u884C\u53F7\u6E38\u6807(\u4ECE hunk \u5934\u58F0\u660E\u7684\u8D77\u70B9\u5F00\u59CB\u9012\u589E)\u3002\n let oldNo = 0\n let newNo = 0\n // \u7B49\u5F85\u914D\u5BF9\u7684\u5220\u9664\u884C:\u8FDE\u7EED\u300C-\u300D\u5757\u4F1A\u5148\u7F13\u5B58,\u9047\u5230\u300C+\u300D\u65F6\u9010\u5BF9\u914D\u5BF9\u6210\n // change \u884C;hunk \u7ED3\u675F\u6216\u9047\u5230\u4E0A\u4E0B\u6587\u884C\u65F6,\u5269\u4F59\u7F13\u5B58\u6309 del \u884C\u8F93\u51FA\u3002\n // noEol \u6807\u8BB0\u53EF\u80FD\u7D27\u8DDF\u300C-\u300D\u884C\u51FA\u73B0(\),\n // \u6240\u4EE5\u8981\u7F13\u5B58\u8FDB\u5F85\u914D\u5BF9\u6761\u76EE,\u914D\u5BF9\u65F6\u624D\u80FD\u5E26\u5230 change \u884C\u7684\u65E7\u4FA7\u3002\n let pendingDels: Array<{ text: string; no: number; noEol: boolean }> = []\n // \u6700\u8FD1\u4E00\u6761\u5185\u5BB9\u884C(\u975E gap)\u7684\u7C7B\u578B,\u7528\u4E8E\u628A \\ No newline \u6807\u8BB0\u6302\u5230\u6B63\u786E\u7684\u4E00\u4FA7\u3002\n let lastKind: 'ctx' | 'del' | 'add' | null = null\n // \u6700\u8FD1\u4E00\u6761\u5DF2\u8F93\u51FA(\u6216\u5DF2\u914D\u5BF9)\u7684\u3001\u5E26\u65B0\u4FA7\u7684\u884C,\u7528\u4E8E\u7ED9\u65B0\u4FA7\u6302 noEol\u3002\n let lastNewRow: DiffRow | null = null\n\n /** \u5F53\u524D\u6B63\u5728\u7D2F\u79EF\u7684\u6587\u4EF6(\u6808\u9876);\u8FD8\u6CA1\u6709 diff \u5934\u65F6\u4E3A null\u3002 */\n function current(): FileDiff | null {\n return files.length > 0 ? files[files.length - 1] : null\n }\n\n /** \u628A\u5F85\u914D\u5BF9\u7F13\u5B58\u6309 del \u884C\u5237\u51FA(\u9047\u4E0A\u4E0B\u6587\u3001\u65B0 hunk\u3001\u6587\u4EF6\u5207\u6362\u3001\u6587\u672C\u7ED3\u675F\u65F6\u8C03\u7528)\u3002 */\n function flushPending(): void {\n const file = current()\n if (file === null || pendingDels.length === 0) return\n for (const del of pendingDels) {\n file.rows.push({\n kind: 'del',\n oldNo: del.no,\n oldText: del.text,\n oldNoEol: del.noEol,\n })\n lastNewRow = null\n }\n pendingDels = []\n }\n\n /** \u65B0\u5EFA\u6587\u4EF6\u5E76\u63A5\u7BA1\u6E38\u6807(\u81EA\u52A8\u5237\u51FA\u4E0A\u4E00\u4E2A\u6587\u4EF6\u7684\u5F85\u914D\u5BF9\u7F13\u5B58)\u3002 */\n function startFile(oldPath: string, newPath: string): void {\n flushPending()\n inHunk = false\n lastKind = null\n lastNewRow = null\n files.push({\n oldPath,\n newPath,\n status: 'modified',\n meta: [],\n rows: [],\n })\n }\n\n /** \u89E3\u6790 @@ hunk \u5934,\u8FD4\u56DE\u662F\u5426\u6210\u529F(\u5931\u8D25\u884C\u539F\u6837\u5F53\u6B63\u6587\u5904\u7406)\u3002 */\n function openHunk(line: string): boolean {\n const match = /^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@(.*)$/.exec(line)\n if (match === null) return false\n flushPending()\n oldNo = Number(match[1])\n newNo = Number(match[3])\n inHunk = true\n lastKind = null\n lastNewRow = null\n // \u4FDD\u7559\u6574\u4E2A hunk \u5934\u539F\u6587(\u542B\u5C3E\u90E8 section \u540D),\u6E32\u67D3\u6210\u95F4\u9699\u884C\u3002\n current()?.rows.push({ kind: 'gap', gapText: line })\n return true\n }\n\n /** \u8F93\u51FA\u4E00\u6761 ctx \u884C(\u5E26\u4E24\u4FA7\u884C\u53F7\u4E0E\u6587\u672C)\u3002 */\n function emitContext(text: string): void {\n const file = current()\n if (file === null) return\n const row: DiffRow = { kind: 'ctx', oldNo, newNo, oldText: text, newText: text }\n file.rows.push(row)\n oldNo += 1\n newNo += 1\n lastKind = 'ctx'\n lastNewRow = row\n }\n\n /** \u8F93\u51FA\u4E00\u6761 change \u884C(\u65E7\u4FA7 del \u4E0E \u65B0\u4FA7 add \u914D\u5BF9)\u3002 */\n function emitChange(del: { text: string; no: number; noEol: boolean }, addText: string): void {\n const file = current()\n if (file === null) return\n const row: DiffRow = {\n kind: 'change',\n oldNo: del.no,\n newNo,\n oldText: del.text,\n newText: addText,\n oldNoEol: del.noEol,\n }\n file.rows.push(row)\n newNo += 1\n lastKind = 'add'\n lastNewRow = row\n }\n\n /** \u8F93\u51FA\u4E00\u6761 add \u884C\u3002 */\n function emitAdd(text: string): void {\n const file = current()\n if (file === null) return\n const row: DiffRow = { kind: 'add', newNo, newText: text }\n file.rows.push(row)\n newNo += 1\n lastKind = 'add'\n lastNewRow = row\n }\n\n /** \u5904\u7406 \ \u6807\u8BB0:\u6302\u5230\u4E0A\u4E00\u884C\u5BF9\u5E94\u7684\u4E00\u4FA7\u3002 */\n function markNoEol(): void {\n if (lastKind === 'del') {\n // \u4E0A\u4E00\u4E2A\u300C-\u300D\u53EF\u80FD\u8FD8\u5728\u5F85\u914D\u5BF9\u7F13\u5B58\u91CC(\u8FD8\u6CA1\u9047\u5230\u914D\u5BF9/\u5237\u51FA)\u3002\n const last = pendingDels[pendingDels.length - 1]\n if (last !== undefined) {\n last.noEol = true\n } else {\n const file = current()\n if (file !== null && file.rows.length > 0) {\n // \u5DF2\u88AB\u5237\u6210 del \u884C:\u6807\u8BB0\u65E7\u4FA7\u3002\n const row = file.rows[file.rows.length - 1]\n if (row.kind === 'del') row.oldNoEol = true\n }\n }\n } else if (lastKind === 'add') {\n // \u4E0A\u4E00\u884C\u662F change \u6216 add:\u6807\u8BB0\u65B0\u4FA7\u3002\n if (lastNewRow !== null) lastNewRow.newNoEol = true\n } else if (lastKind === 'ctx') {\n const file = current()\n if (file !== null && file.rows.length > 0) {\n const row = file.rows[file.rows.length - 1]\n if (row.kind === 'ctx') {\n row.oldNoEol = true\n row.newNoEol = true\n }\n }\n }\n }\n\n for (const line of lines) {\n // \u7A7A\u884C:\u6587\u672C\u672B\u5C3E\u6362\u884C\u7B26\u7684\u62C6\u5206\u4EA7\u7269(unified diff \u7684\u5185\u5BB9\u884C\u90FD\u5E26\u524D\u7F00\n // \u5B57\u7B26,\u771F\u6B63\u7684\u7A7A\u5185\u5BB9\u884C\u662F \" \" \u800C\u4E0D\u662F \"\"),\u76F4\u63A5\u8DF3\u8FC7\u3002\n if (line === '') continue\n if (line.startsWith('diff --git ')) {\n // \u65B0\u6587\u4EF6\u5934:\u5C1D\u8BD5\u4ECE a/X b/Y \u63D0\u53D6\u8DEF\u5F84,\u63D0\u53D6\u5931\u8D25\u4E5F\u4E0D\u963B\u585E\u89E3\u6790\u3002\n const match = /^diff --git (?:a\\/)?(\\S+) (?:b\\/)?(\\S+)$/.exec(line)\n startFile(match?.[1] ?? '', match?.[2] ?? '')\n continue\n }\n if (line.startsWith('@@ ')) {\n openHunk(line)\n continue\n }\n const cur = current()\n if (cur === null) {\n // diff \u5934\u4E4B\u524D\u7684\u524D\u5BFC\u5185\u5BB9(\u63D0\u4EA4\u4FE1\u606F\u7B49)\u8DF3\u8FC7;\u88F8 patch(\u65E0 diff --git\n // \u5934,\u5982 git diff --no-index \u7684\u5408\u5E76\u683C\u5F0F)\u4EE5 --- \u884C\u76F4\u63A5\u5F00\u6587\u4EF6\u3002\n if (line.startsWith('--- ') && line.length > 4) startFile(line.slice(4), '')\n continue\n }\n if (!inHunk) {\n // \u2014\u2014 hunk \u5916\u7684\u5143\u4FE1\u606F\u884C \u2014\u2014\n if (line.startsWith('--- ') && line.length > 4) {\n cur.oldPath = line.slice(4)\n } else if (line.startsWith('+++ ') && line.length > 4) {\n cur.newPath = line.slice(4)\n } else if (line.startsWith('new file mode ')) {\n cur.status = 'added'\n cur.meta.push(line)\n } else if (line.startsWith('deleted file mode ')) {\n cur.status = 'deleted'\n cur.meta.push(line)\n } else if (line.startsWith('rename from ') || line.startsWith('rename to ')) {\n cur.status = 'renamed'\n cur.meta.push(line)\n } else if (\n line.startsWith('old mode ') ||\n line.startsWith('new mode ') ||\n line.startsWith('similarity index ') ||\n line.startsWith('dissimilarity index ') ||\n line.startsWith('copy from ') ||\n line.startsWith('copy to ')\n ) {\n cur.meta.push(line)\n } else if (line.startsWith('Binary files ') || line.startsWith('GIT binary patch')) {\n // \u4E8C\u8FDB\u5236\u6587\u4EF6:\u6CA1\u6709 hunk,\u6E32\u67D3\u6210\u4FE1\u606F\u884C\u3002\n cur.status = 'binary'\n cur.meta.push(line)\n } else if (line.startsWith('index ')) {\n // index \u884C(blob \u54C8\u5E0C)\u4FE1\u606F\u91CF\u4F4E,\u6536\u8FDB meta \u4F46\u6E32\u67D3\u5C42\u9009\u62E9\u5FFD\u7565\u3002\n cur.meta.push(line)\n }\n // \u5176\u4ED6(\u7A7A\u884C\u7B49)\u5FFD\u7565\u3002\n continue\n }\n // \u2014\u2014 hunk \u6B63\u6587 \u2014\u2014\n const kind = line[0]\n const body = line.slice(1)\n if (kind === ' ') {\n flushPending()\n emitContext(body)\n } else if (kind === '-') {\n pendingDels.push({ text: body, no: oldNo, noEol: false })\n oldNo += 1\n lastKind = 'del'\n } else if (kind === '+') {\n const del = pendingDels.shift()\n if (del !== undefined) {\n emitChange(del, body)\n } else {\n emitAdd(body)\n }\n } else if (kind === '\\\\' && line.startsWith('\\\')) {\n markNoEol()\n } else {\n // \u65E0\u6CD5\u8BC6\u522B\u7684\u884C(\u7578\u5F62 diff):\u6309\u4E0A\u4E0B\u6587\u5BBD\u5BB9\u5904\u7406,\u4FDD\u8BC1\u89C6\u56FE\u4E0D\u9519\u4F4D\u3002\n flushPending()\n emitContext(line)\n }\n }\n flushPending()\n return files\n}\n\n/**\n * \u7EDF\u8BA1\u6574\u4E2A\u89E3\u6790\u7ED3\u679C:\u65B0\u589E\u884C\u6570 / \u5220\u9664\u884C\u6570(change \u884C\u4E24\u4FA7\u5404\u8BA1\u4E00\u6B21)\u3002\n * \u4F9B\u5DE5\u5177\u680F\u300C+N \u2212M \u00B7 F \u4E2A\u6587\u4EF6\u300D\u4F7F\u7528\u3002\n */\nexport function countStats(files: FileDiff[]): { added: number; removed: number; files: number } {\n let added = 0\n let removed = 0\n for (const file of files) {\n for (const row of file.rows) {\n if (row.kind === 'add' || row.kind === 'change') added += 1\n if (row.kind === 'del' || row.kind === 'change') removed += 1\n }\n }\n return { added, removed, files: files.length }\n}\n", "/**\n * \u4EE3\u7801\u9AD8\u4EAE\u7684\u7EAF\u903B\u8F91(\u4E0D\u4F9D\u8D56 DOM,\u53EF\u5728 Node \u91CC\u5355\u6D4B)\u3002\n *\n * \u4E00\u3001\u57FA\u7840\u8BED\u6CD5\u9AD8\u4EAE:\u8F7B\u91CF\u5355\u884C tokenizer(\u4E0D\u5F15\u5165\u9AD8\u4EAE\u5E93,\u72EC\u7ACB\u9875\u96F6\u4F9D\u8D56),\n * \u5207\u51FA \u5173\u952E\u5B57/\u5B57\u7B26\u4E32/\u6CE8\u91CA/\u6570\u5B57 \u56DB\u7C7B token,\u6E32\u67D3\u5C42\u6620\u5C04\u6210\u5B98\u65B9\n * --shiki-token-* \u989C\u8272(\u4E0E DSH \u6DF1\u6D45\u4E3B\u9898\u4E00\u81F4)\u3002\n * \u652F\u6301\u6CE8\u91CA\u98CE\u683C:\u53CC\u659C\u6760\u3001\u659C\u6760\u661F\u53F7(\u884C\u5185)\u3001\u4E95\u53F7(Python/shell \u7CFB,\n * \u6309\u6269\u5C55\u540D\u5F00\u542F)\u3002\u5757\u6CE8\u91CA\u4E0D\u8DE8\u884C(\u57FA\u7840\u7248\u53D6\u820D,\u5355\u884C\u6E32\u67D3\u573A\u666F\u5F71\u54CD\u5F88\u5C0F)\u3002\n *\n * \u4E8C\u3001\u884C\u5185\u5B57\u7B26\u7EA7 diff:\u5BF9 change \u884C(\u540C\u4E00\u884C\u65E7/\u65B0\u914D\u5BF9)\u505A\u516C\u5171\u524D\u7F00/\u540E\u7F00\n * \u5265\u79BB,\u4E2D\u95F4\u4E0D\u540C\u7684\u5B57\u7B26\u6BB5\u5C31\u662F\u300C\u53EA\u6539\u4E86\u51E0\u4E2A\u5B57\u7B26\u300D\u7684\u7CBE\u786E\u8303\u56F4,\u6E32\u67D3\u5C42\u7528\n * \u52A0\u6DF1\u80CC\u666F\u8272\u9AD8\u4EAE\u3002\u7D22\u5F15\u6309 code point(Array.from)\u8BA1\u7B97,\u6E32\u67D3\u5C42\u540C\u6837\u6309\n * code point \u5207\u7247,\u4EE3\u7406\u5BF9\u4E0D\u4F1A\u4ECE\u4E2D\u95F4\u65AD\u5F00\u3002\n */\n\n/** \u8BED\u6CD5 token(\u7EAF\u6587\u672C\u5207\u5206,HTML \u8F6C\u4E49\u7531\u6E32\u67D3\u5C42\u505A)\u3002 */\nexport interface Token {\n text: string\n /** null = \u666E\u901A\u6587\u672C;\u5176\u4F59\u4E3A\u8BED\u6CD5\u7C7B\u522B,\u6620\u5C04\u5230 .tok-* \u6837\u5F0F\u3002 */\n cls: 'tok-kw' | 'tok-str' | 'tok-com' | 'tok-num' | null\n}\n\n/** \u884C\u5185\u5DEE\u5F02\u7684\u4E2D\u6BB5\u8303\u56F4(code point \u7D22\u5F15,\u524D\u95ED\u540E\u5F00;null = \u65E0\u5DEE\u5F02)\u3002 */\nexport interface DiffMidRange {\n oldStart: number\n oldEnd: number\n newStart: number\n newEnd: number\n}\n\n/** \u5355\u884C\u9AD8\u4EAE\u914D\u7F6E:\u662F\u5426\u652F\u6301 # \u6CE8\u91CA(Python/shell/YAML \u7B49)\u3002 */\nexport interface LineLang {\n hashComment: boolean\n}\n\n/**\n * \u5408\u5E76\u5E38\u89C1\u8BED\u8A00\u7684\u5173\u952E\u5B57\u8868(\u57FA\u7840\u9AD8\u4EAE\u4E0D\u6309\u8BED\u8A00\u7EC6\u5206,\u4E00\u5957\u901A\u7528\u8BCD\u8868\u8986\u76D6\n * JS/TS\u3001Python\u3001Go\u3001Rust\u3001Java/C \u7CFB\u7684\u7EDD\u5927\u591A\u6570\u5173\u952E\u5B57)\u3002\n */\nconst KEYWORDS = new Set([\n // JS/TS\n 'const', 'let', 'var', 'function', 'class', 'extends', 'super', 'new', 'delete',\n 'typeof', 'instanceof', 'in', 'of', 'this', 'static', 'get', 'set', 'async',\n 'await', 'yield', 'return', 'if', 'else', 'switch', 'case', 'default', 'break',\n 'continue', 'try', 'catch', 'finally', 'throw', 'for', 'while', 'do', 'import',\n 'export', 'from', 'as', 'interface', 'type', 'enum', 'namespace', 'module',\n 'implements', 'public', 'private', 'protected', 'readonly', 'abstract',\n 'declare', 'keyof', 'infer', 'never', 'unknown', 'any', 'void', 'null',\n 'undefined', 'true', 'false', 'NaN', 'Infinity', 'require',\n // Python\n 'def', 'lambda', 'pass', 'raise', 'elif', 'not', 'and', 'or', 'is', 'None',\n 'True', 'False', 'print', 'with', 'global', 'nonlocal', 'assert', 'del',\n // Go\n 'go', 'func', 'package', 'struct', 'map', 'chan', 'select', 'defer', 'range',\n 'nil', 'goto', 'fallthrough',\n // Rust\n 'fn', 'let', 'mut', 'pub', 'impl', 'trait', 'match', 'use', 'mod', 'self',\n 'crate', 'unsafe', 'where', 'loop', 'move', 'ref', 'dyn',\n // Java / C \u7CFB\n 'int', 'float', 'double', 'char', 'boolean', 'byte', 'short', 'long', 'signed',\n 'unsigned', 'auto', 'register', 'extern', 'volatile', 'constexpr', 'sizeof',\n 'template', 'typename',\n])\n\n/** \u505A\u8BED\u6CD5\u9AD8\u4EAE\u7684\u6269\u5C55\u540D\u96C6\u5408(\u5176\u4F59\u6269\u5C55\u540D\u4E0D\u505A\u8BED\u6CD5\u9AD8\u4EAE,\u53EA\u505A\u5B57\u7B26\u7EA7 diff)\u3002 */\nconst HIGHLIGHT_EXTS = new Set([\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json', 'py', 'rb', 'go', 'rs',\n 'java', 'c', 'h', 'cpp', 'cc', 'cs', 'css', 'scss', 'less', 'html', 'htm',\n 'vue', 'xml', 'sql', 'md', 'yml', 'yaml', 'toml', 'sh', 'bash', 'zsh', 'ini',\n])\n\n/** # \u6CE8\u91CA\u8BED\u8A00\u7684\u6269\u5C55\u540D(Python/shell/YAML \u7CFB;JS \u7684 # \u662F\u79C1\u6709\u5B57\u6BB5,\u4E0D\u5F00)\u3002 */\nconst HASH_COMMENT_EXTS = new Set(['py', 'rb', 'sh', 'bash', 'zsh', 'yml', 'yaml', 'toml', 'ini'])\n\n/** \u6309\u6587\u4EF6\u8DEF\u5F84\u63A8\u65AD\u9AD8\u4EAE\u914D\u7F6E;\u65E0\u6CD5\u8BC6\u522B\u65F6\u8FD4\u56DE null(\u53EA\u505A\u5B57\u7B26\u7EA7 diff)\u3002 */\nexport function langFor(path: string): LineLang | null {\n const dot = path.lastIndexOf('.')\n const ext = dot >= 0 ? path.slice(dot + 1).toLowerCase() : ''\n if (ext === '' || !HIGHLIGHT_EXTS.has(ext)) return null\n return { hashComment: HASH_COMMENT_EXTS.has(ext) }\n}\n\n/** \u662F\u5426\u6807\u8BC6\u7B26\u8D77\u59CB\u5B57\u7B26\u3002 */\nfunction isIdentStart(ch: string): boolean {\n return /[A-Za-z_]/.test(ch)\n}\n\n/** \u662F\u5426\u6807\u8BC6\u7B26\u540E\u7EED\u5B57\u7B26\u3002 */\nfunction isIdentPart(ch: string): boolean {\n return /[A-Za-z0-9_]/.test(ch)\n}\n\n/** \u662F\u5426\u6570\u5B57\u8D77\u59CB\u5B57\u7B26\u3002 */\nfunction isDigitStart(ch: string): boolean {\n return /[0-9]/.test(ch)\n}\n\n/** \u662F\u5426\u6570\u5B57\u540E\u7EED\u5B57\u7B26(\u542B\u5341\u516D\u8FDB\u5236/\u4E8C\u8FDB\u5236\u524D\u7F00\u4E0E\u5C0F\u6570\u70B9)\u3002 */\nfunction isDigitPart(ch: string): boolean {\n return /[0-9a-fA-FxXbBoO._]/.test(ch)\n}\n\n/**\n * \u5355\u884C tokenize:\u6309 \u5B57\u7B26\u4E32 \u2192 \u6CE8\u91CA \u2192 \u6570\u5B57 \u2192 \u6807\u8BC6\u7B26 \u7684\u987A\u5E8F\u626B\u63CF,\n * \u5176\u4F59\u5B57\u7B26\u5F52\u5165\u666E\u901A\u6587\u672C\u3002\u884C\u5185 token \u72B6\u6001\u4E0D\u8DE8\u884C(\u57FA\u7840\u7248\u53D6\u820D)\u3002\n */\nexport function tokenizeLine(text: string, lang: LineLang): Token[] {\n const tokens: Token[] = []\n let plain = ''\n const flushPlain = (): void => {\n if (plain !== '') {\n tokens.push({ text: plain, cls: null })\n plain = ''\n }\n }\n let i = 0\n while (i < text.length) {\n const ch = text[i]\n // \u5B57\u7B26\u4E32:\u4E09\u79CD\u5F15\u53F7,\u53CD\u659C\u6760\u8F6C\u4E49\u8DF3\u4E24\u683C;\u884C\u5C3E\u672A\u95ED\u5408\u5219\u5230\u884C\u5C3E\u3002\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n let j = i + 1\n while (j < text.length) {\n if (text[j] === '\\\\') {\n j += 2\n continue\n }\n if (text[j] === ch) break\n j += 1\n }\n flushPlain()\n tokens.push({ text: text.slice(i, Math.min(j + 1, text.length)), cls: 'tok-str' })\n i = Math.min(j + 1, text.length)\n continue\n }\n // \u884C\u6CE8\u91CA // \u6216 /* */(\u884C\u5185;\u672A\u95ED\u5408\u5230\u884C\u5C3E)\n if (ch === '/' && text[i + 1] === '/') {\n flushPlain()\n tokens.push({ text: text.slice(i), cls: 'tok-com' })\n break\n }\n if (ch === '/' && text[i + 1] === '*') {\n const close = text.indexOf('*/', i + 2)\n const end = close === -1 ? text.length : close + 2\n flushPlain()\n tokens.push({ text: text.slice(i, end), cls: 'tok-com' })\n i = end\n continue\n }\n if (lang.hashComment && ch === '#') {\n flushPlain()\n tokens.push({ text: text.slice(i), cls: 'tok-com' })\n break\n }\n // \u6570\u5B57(\u524D\u7F00\u5B57\u6BCD/\u5C0F\u6570\u70B9/\u4E0B\u5212\u7EBF\u90FD\u7B97\u6570\u5B57\u7247\u6BB5)\n if (isDigitStart(ch)) {\n let j = i + 1\n while (j < text.length && isDigitPart(text[j])) j += 1\n flushPlain()\n tokens.push({ text: text.slice(i, j), cls: 'tok-num' })\n i = j\n continue\n }\n // \u6807\u8BC6\u7B26 \u2192 \u547D\u4E2D\u5173\u952E\u5B57\u8868\u5219\u6807 tok-kw\n if (isIdentStart(ch)) {\n let j = i + 1\n while (j < text.length && isIdentPart(text[j])) j += 1\n const word = text.slice(i, j)\n flushPlain()\n tokens.push({ text: word, cls: KEYWORDS.has(word) ? 'tok-kw' : null })\n i = j\n continue\n }\n plain += ch\n i += 1\n }\n flushPlain()\n return tokens\n}\n\n/**\n * \u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02\u8303\u56F4:\u516C\u5171\u524D\u7F00/\u540E\u7F00\u5265\u79BB,\u4E2D\u95F4\u5269\u4F59\u90E8\u5206\u5373\u5DEE\u5F02\u6BB5\u3002\n * \u8986\u76D6\u300C\u4E00\u884C\u53EA\u6539\u51E0\u4E2A\u5B57\u7B26\u300D\u7684\u7CBE\u786E\u9AD8\u4EAE;\u6574\u884C\u91CD\u5199\u65F6\u4E2D\u95F4\u6BB5\u63A5\u8FD1\u6574\u884C,\n * \u6548\u679C\u4E0E\u6574\u884C\u9AD8\u4EAE\u4E00\u81F4(\u53EF\u63A5\u53D7)\u3002\u76F8\u540C\u6587\u672C\u8FD4\u56DE null\u3002\n */\nexport function diffMidRange(oldText: string, newText: string): DiffMidRange | null {\n const a = Array.from(oldText)\n const b = Array.from(newText)\n // \u516C\u5171\u524D\u7F00\n let pre = 0\n const minLen = Math.min(a.length, b.length)\n while (pre < minLen && a[pre] === b[pre]) pre += 1\n // \u516C\u5171\u540E\u7F00(\u4E0D\u56DE\u9000\u8FDB\u524D\u7F00\u533A)\n let oldEnd = a.length\n let newEnd = b.length\n while (oldEnd > pre && newEnd > pre && a[oldEnd - 1] === b[newEnd - 1]) {\n oldEnd -= 1\n newEnd -= 1\n }\n if (pre === a.length && pre === b.length) return null\n return { oldStart: pre, oldEnd, newStart: pre, newEnd }\n}\n", "/**\n * \u72EC\u7ACB Code Review \u6807\u7B7E\u9875\u7684 HTML \u6784\u5EFA(\u7EAF\u5B57\u7B26\u4E32,\u53EF\u5728 Node \u91CC\u5355\u6D4B)\u3002\n *\n * \u804A\u5929\u7A97\u53E3\u5185\u4E0D\u518D\u505A\u53CC\u5217\u8F6C\u6362 \u2014\u2014 \u53EA\u5728\u68C0\u6D4B\u5230 git diff \u7684\u5757\u4E0A\u52A0\u4E00\u4E2A\n * \u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D\u6309\u94AE;\u70B9\u51FB\u540E\u5728\u72EC\u7ACB\u6D4F\u89C8\u5668\u6807\u7B7E\u9875(Blob URL,\u81EA\u5305\u542B)\n * \u6E32\u67D3\u5B8C\u6574\u53CC\u5217\u89C6\u56FE:\u5DE6\u4FA7\u6587\u4EF6\u5BFC\u822A\u3001\u5168\u5BBD\u3001\u6574\u9875\u6EDA\u52A8\u3001\u65E0\u9AD8\u5EA6\u9650\u5236\u3002\n *\n * \u5E03\u5C40(\u4E0E\u5B98\u65B9 DiffBlock \u540C\u6E90\u7684\u53CC\u5217\u8BED\u4E49):\n * - ctx \u4E0A\u4E0B\u6587\u884C:\u4E24\u4FA7\u540C\u6587\u3001\u65E0\u5E95\u8272;\n * - del \u5220\u9664\u884C:\u4EC5\u65E7\u4FA7,\u7EA2\u5E95;\n * - add \u65B0\u589E\u884C:\u4EC5\u65B0\u4FA7,\u7EFF\u5E95;\n * - change \u53D8\u66F4\u5BF9:\u65E7\u4FA7\u7EA2\u5E95 + \u65B0\u4FA7\u7EFF\u5E95(\u8FDE\u7EED - \u4E0E + \u7684\u914D\u5BF9,\u89C1 diff-parse);\n * - gap @@ hunk \u5934,\u6A2A\u8DE8\u56DB\u5217\u3002\n */\n\nimport type { DiffRow, FileDiff } from './diff-parse'\nimport { countStats, displayPath } from './diff-parse'\nimport { diffMidRange, langFor, tokenizeLine } from './diff-highlight'\nimport type { DiffMidRange, LineLang, Token } from './diff-highlight'\n\n/** HTML \u8F6C\u4E49:diff \u884C\u5185\u5BB9\u662F\u4E0D\u53EF\u4FE1\u6587\u672C,\u76F4\u63A5\u62FC HTML \u5FC5\u987B\u5148\u8F6C\u4E49\u3002 */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\n/** \u72EC\u7ACB\u9875\u7684 CSS:\u81EA\u5305\u542B,\u989C\u8272\u5168\u90E8\u8D70\u6CE8\u5165\u7684\u4E3B\u9898\u53D8\u91CF(\u4E0E DSH \u4E3B\u9898\u4E00\u81F4)\u3002 */\nconst STANDALONE_CSS = `\n* { box-sizing: border-box; }\nbody {\n margin: 0;\n font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 13px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n}\n.topbar {\n position: sticky;\n top: 0;\n z-index: 10;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 0 16px;\n background: var(--dsw-alias-markdown-code-block);\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n}\n.stats { font-size: 13px; color: var(--dsw-alias-label-secondary); }\n.topbar-actions { display: flex; gap: 8px; }\n.btn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n background: transparent;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 2px 10px;\n cursor: pointer;\n font-family: inherit;\n}\n.btn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3); }\n.sidenav {\n position: fixed;\n left: 0;\n top: 36px;\n bottom: 0;\n width: 220px;\n overflow-y: auto;\n padding: 12px 8px;\n background: var(--dsw-alias-markdown-code-block);\n border-right: 1px solid var(--dsw-alias-border-l2);\n}\n.navitem {\n display: block;\n padding: 4px 12px;\n font-size: 12px;\n line-height: 20px;\n color: var(--dsw-alias-label-secondary);\n text-decoration: none;\n border-radius: 6px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.navitem:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1); }\nmain { margin-left: 220px; padding: 16px 24px 60px; }\n.file { margin-bottom: 24px; background: var(--dsw-alias-markdown-code-block); border-radius: 12px; overflow: hidden; }\n.filehead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n padding: 8px 12px 4px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n.badge {\n flex: none;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n padding: 0 6px;\n}\n.path { overflow-wrap: anywhere; }\n.meta { flex-basis: 100%; font-size: 11px; color: var(--dsw-alias-label-tertiary); }\n.dsh-cr-grid {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);\n}\n.dsh-cr-row { display: contents; }\n.dsh-cr-num {\n padding: 0 10px 0 4px;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: nowrap;\n}\n.dsh-cr-cell {\n padding: 0 12px 0 4px;\n min-width: 0;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.dsh-cr-del .dsh-cr-num-old, .dsh-cr-del .dsh-cr-cell-old,\n.dsh-cr-change .dsh-cr-num-old, .dsh-cr-change .dsh-cr-cell-old {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n}\n.dsh-cr-add .dsh-cr-num-new, .dsh-cr-add .dsh-cr-cell-new,\n.dsh-cr-change .dsh-cr-num-new, .dsh-cr-change .dsh-cr-cell-new {\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n}\n.dsh-cr-gaprow {\n grid-column: 1 / -1;\n padding: 2px 12px;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent);\n user-select: none;\n}\n.dsh-cr-nonl { color: var(--dsw-alias-label-tertiary); font-size: 11px; }\n/* \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) */\n.tok-kw { color: var(--shiki-token-keyword, #7c3aed); }\n.tok-str { color: var(--shiki-token-string, #16a34a); }\n.tok-com { color: var(--shiki-token-comment, #9aa0a6); font-style: italic; }\n.tok-num { color: var(--shiki-token-constant, #d97706); }\n/* \u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02:change \u884C\u4E2D\u6BB5\u52A0\u6DF1\u80CC\u666F(\u65E7\u4FA7\u7EA2/\u65B0\u4FA7\u7EFF) */\n.dihl-mid-old {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 32%, transparent);\n border-radius: 2px;\n}\n.dihl-mid-new {\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 32%, transparent);\n border-radius: 2px;\n}\n/* \u884C\u5185\u8BC4\u8BBA:\u884C\u53F7\u683C\u53EF\u70B9\u51FB,hover \u51FA\u73B0 + \u53F7 */\n.dsh-cr-num[data-cr-line] {\n cursor: pointer;\n position: relative;\n}\n.dsh-cr-num[data-cr-line]:hover {\n color: var(--dsw-alias-label-primary);\n}\n.dsh-cr-num[data-cr-line]:hover::after {\n content: '+';\n position: absolute;\n left: 2px;\n top: 0;\n color: var(--dsw-alias-state-business-primary);\n font-weight: 700;\n}\n.cc {\n margin-left: 5px;\n font-size: 10px;\n line-height: 15px;\n color: #fff;\n background: var(--dsw-alias-state-business-primary);\n border-radius: 8px;\n padding: 0 5px;\n user-select: none;\n}\n.comment-draft {\n grid-column: 1 / -1;\n padding: 8px 12px;\n background: var(--dsw-alias-bg-layer-1);\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.comment-list { margin-bottom: 8px; }\n.comment-item {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-markdown-code-block);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 6px 10px;\n margin-bottom: 4px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.comment-input {\n width: 100%;\n min-height: 60px;\n resize: vertical;\n box-sizing: border-box;\n font: inherit;\n font-size: 13px;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-markdown-code-block);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 6px 10px;\n}\n.comment-actions { display: flex; gap: 8px; margin-top: 8px; }\n.submit-panel {\n position: fixed;\n top: 44px;\n right: 16px;\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n background: var(--dsw-alias-markdown-code-block);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n}\n.submit-hint { font-size: 12px; color: var(--dsw-alias-label-secondary); }\n.rawwrap { margin: 16px 24px; }\n.rawsummary { font-size: 12px; color: var(--dsw-alias-label-secondary); cursor: pointer; }\n.rawpre { white-space: pre-wrap; word-break: break-word; font: inherit; margin: 8px 0 0; }\n`\n\n/** \u53D8\u66F4\u7C7B\u578B\u7684\u5FBD\u7AE0\u6587\u6848\u3002 */\nconst STANDALONE_STATUS_LABEL: Record<FileDiff['status'], string> = {\n modified: '\u4FEE\u6539',\n added: '\u65B0\u589E\u6587\u4EF6',\n deleted: '\u5220\u9664\u6587\u4EF6',\n renamed: '\u91CD\u547D\u540D',\n binary: '\u4E8C\u8FDB\u5236',\n mode: '\u6743\u9650\u53D8\u66F4',\n}\n\n/** \u6807\u7B7E\u9875\u56DE\u4F20\u7684\u4E00\u6761\u884C\u5185\u8BC4\u8BBA(\u7ECF postMessage,\u5F62\u72B6\u7531\u63D2\u4EF6\u4FA7\u6821\u9A8C)\u3002 */\nexport interface ReviewComment {\n path: string\n lineNo: number\n side?: string\n text: string\n}\n\n/**\n * \u7EC4\u88C5\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846\u7684 Code Review \u62A5\u544A\u6587\u672C(\u7EAF\u51FD\u6570,\u5355\u6D4B\u8986\u76D6)\u3002\n * \u683C\u5F0F:\u6587\u4EF6\u540D:\u884C\u53F7 \u2014 \u8BC4\u8BBA,\u9010\u6761\u7F16\u53F7;LGTM \u65F6\u5934\u90E8\u52A0\u6279\u51C6\u58F0\u660E\u3002\n * \u8BC4\u8BBA\u6587\u672C\u542B\u6362\u884C\u65F6,\u7EED\u884C\u7F29\u8FDB\u4E09\u683C\u4E0E\u6761\u76EE\u5185\u5BB9\u5BF9\u9F50,\u4E0D\u7834\u574F\u7F16\u53F7\u7ED3\u6784\u3002\n * @param comments \u884C\u5185\u8BC4\u8BBA\u5217\u8868\u3002\n * @param kind \u63D0\u4EA4\u7C7B\u578B:lgtm(\u6279\u51C6)/ comment(\u7EAF\u8BC4\u8BBA)\u3002\n */\nexport function buildCommentReport(comments: readonly ReviewComment[], kind: 'lgtm' | 'comment'): string {\n const header = kind === 'lgtm'\n ? `Looks good to me \u2713 \u2014 Code Review \u5171 ${comments.length} \u6761\u8BC4\u8BBA:`\n : `Code Review \u610F\u89C1,\u5171 ${comments.length} \u6761:`\n const items = comments.map((comment, index) => {\n const lines = comment.text.split('\\n')\n const first = `${index + 1}. ${comment.path}:${comment.lineNo} \u2014 ${lines[0]}`\n // \u7EED\u884C\u7F29\u8FDB\u4E09\u683C(\u4E0E \"N. \" \u524D\u7F00\u7B49\u5BBD),\u907F\u514D\u4E0E\u4E0B\u4E00\u6761\u7F16\u53F7\u6DF7\u6DC6\u3002\n const rest = lines.slice(1).map((line) => ` ${line}`)\n return [first, ...rest].join('\\n')\n })\n return [header, '', ...items].join('\\n')\n}\n\n/** \u628A token \u5E8F\u5217\u6E32\u67D3\u6210 HTML(\u8F6C\u4E49\u5728 token \u7EA7\u505A,\u4FDD\u8BC1\u5B89\u5168)\u3002 */\nfunction tokensToHtml(tokens: Token[]): string {\n let out = ''\n for (const token of tokens) {\n const escaped = escapeHtml(token.text)\n out += token.cls === null ? escaped : `<span class=\"${token.cls}\">${escaped}</span>`\n }\n return out\n}\n\n/**\n * \u6E32\u67D3\u4E00\u4E2A\u5185\u5BB9\u683C:\u57FA\u7840\u8BED\u6CD5\u9AD8\u4EAE + \u53EF\u9009\u7684\u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02\u9AD8\u4EAE\u3002\n * @param text \u5185\u5BB9\u6587\u672C(\u53EF\u80FD\u4E3A\u7A7A)\u3002\n * @param side \u65E7/\u65B0\u4FA7(\u51B3\u5B9A\u4E2D\u6BB5\u80CC\u666F\u7528\u7EA2\u8FD8\u662F\u7EFF)\u3002\n * @param noEol \u672B\u5C3E\u65E0\u6362\u884C\u6807\u8BB0\u3002\n * @param diffRange \u884C\u5185\u5DEE\u5F02\u8303\u56F4(\u4EC5 change \u884C;null = \u4E0D\u505A\u5B57\u7B26\u7EA7\u9AD8\u4EAE)\u3002\n * @param lang \u8BED\u6CD5\u9AD8\u4EAE\u914D\u7F6E(\u65E0\u6CD5\u8BC6\u522B\u8BED\u8A00\u65F6 null)\u3002\n */\nfunction cellHtml(\n text: string | undefined,\n side: 'old' | 'new',\n noEol: boolean | undefined,\n diffRange: DiffMidRange | null,\n lang: LineLang | null,\n): string {\n if (text === undefined) return `<div class=\"dsh-cr-cell dsh-cr-cell-${side}\"></div>`\n const mark = noEol === true ? '<span class=\"dsh-cr-nonl\" title=\"\u8BE5\u4FA7\u6587\u4EF6\u672B\u5C3E\u65E0\u6362\u884C\u7B26\">\u23CE</span>' : ''\n let body: string\n if (diffRange === null) {\n // \u65E0\u5B57\u7B26\u7EA7\u9AD8\u4EAE:\u6574\u884C\u4E00\u4E2A\u8BED\u6CD5\u6BB5\u3002\n body = lang === null ? escapeHtml(text) : tokensToHtml(tokenizeLine(text, lang))\n } else {\n // \u6309 code point \u5207\u524D/\u4E2D/\u540E\u4E09\u6BB5(\u7D22\u5F15\u6765\u81EA diffMidRange \u7684\n // Array.from \u8BA1\u7B97,\u5207\u7247\u540C\u6837\u6309 code point,\u4EE3\u7406\u5BF9\u4E0D\u4F1A\u65AD\u88C2)\u3002\n const chars = Array.from(text)\n const start = side === 'old' ? diffRange.oldStart : diffRange.newStart\n const end = side === 'old' ? diffRange.oldEnd : diffRange.newEnd\n const prefix = chars.slice(0, start).join('')\n const mid = chars.slice(start, end).join('')\n const suffix = chars.slice(end).join('')\n const seg = (segText: string): string =>\n lang === null ? escapeHtml(segText) : tokensToHtml(tokenizeLine(segText, lang))\n body = seg(prefix)\n + (mid !== '' ? `<span class=\"dihl-mid-${side}\">${seg(mid)}</span>` : '')\n + seg(suffix)\n }\n return `<div class=\"dsh-cr-cell dsh-cr-cell-${side}\">${body}${mark}</div>`\n}\n\n/** \u6E32\u67D3\u4E00\u884C(\u53CC\u5217 4 \u683C\u7ED3\u6784,\u76F4\u63A5\u4EA7 HTML \u5B57\u7B26\u4E32)\u3002 */\nfunction rowHtml(row: DiffRow, lang: LineLang | null, path: string): string {\n if (row.kind === 'gap') {\n return `<div class=\"dsh-cr-gaprow\">${escapeHtml(row.gapText ?? '')}</div>`\n }\n // \u884C\u53F7\u683C\u5E26\u8BC4\u8BBA\u5B9A\u4F4D\u4FE1\u606F(\u6587\u4EF6\u8DEF\u5F84/\u884C\u53F7/\u4FA7\u522B),\u6807\u7B7E\u9875\u811A\u672C\u636E\u6B64\u6302\u884C\u5185\u8BC4\u8BBA\u3002\n const num = (no: number | undefined, side: string): string => {\n if (no === undefined) return `<div class=\"dsh-cr-num dsh-cr-num-${side}\"></div>`\n 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>`\n }\n const line = `<div class=\"dsh-cr-row dsh-cr-${row.kind}\">`\n if (row.kind === 'ctx') {\n return line + num(row.oldNo, 'old') + cellHtml(row.oldText, 'old', row.oldNoEol, null, lang)\n + num(row.newNo, 'new') + cellHtml(row.newText, 'new', row.newNoEol, null, lang) + '</div>'\n }\n if (row.kind === 'del') {\n return line + num(row.oldNo, 'old') + cellHtml(row.oldText, 'old', row.oldNoEol, null, lang)\n + num(undefined, 'new') + cellHtml(undefined, 'new', undefined, null, lang) + '</div>'\n }\n if (row.kind === 'add') {\n return line + num(undefined, 'old') + cellHtml(undefined, 'old', undefined, null, lang)\n + num(row.newNo, 'new') + cellHtml(row.newText, 'new', row.newNoEol, null, lang) + '</div>'\n }\n // change \u884C:\u7B97\u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02\u8303\u56F4,\u4E24\u4FA7\u4E2D\u6BB5\u5404\u81EA\u52A0\u6DF1\u80CC\u666F\u3002\n const range = diffMidRange(row.oldText ?? '', row.newText ?? '')\n return line + num(row.oldNo, 'old') + cellHtml(row.oldText, 'old', row.oldNoEol, range, lang)\n + num(row.newNo, 'new') + cellHtml(row.newText, 'new', row.newNoEol, range, lang) + '</div>'\n}\n\n/**\n * \u6784\u5EFA\u72EC\u7ACB\u6807\u7B7E\u9875\u7684\u5B8C\u6574 HTML(\u81EA\u5305\u542B:\u6837\u5F0F + \u4E3B\u9898\u53D8\u91CF + \u53CC\u5217\u5185\u5BB9)\u3002\n * \u5FC5\u987B\u7531\u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D\u6309\u94AE\u7684\u70B9\u51FB\u5904\u7406\u5668\u5728\u7528\u6237\u624B\u52BF\u5185\u540C\u6B65\u8C03\u7528\n * (window.open \u624D\u4E0D\u4F1A\u88AB\u6D4F\u89C8\u5668\u62E6\u622A)\u3002\n * @param files \u89E3\u6790\u7ED3\u679C\u3002\n * @param theme \u4ECE\u5BBF\u4E3B\u9875\u9762\u6536\u96C6\u7684 DSW \u4E3B\u9898\u53D8\u91CF(name \u2192 value)\u3002\n * @param rawText \u539F\u59CB diff \u6587\u672C(\u590D\u5236/\u4FDD\u5E95\u67E5\u770B\u7528)\u3002\n */\nexport function buildStandaloneHtml(files: FileDiff[], theme: Record<string, string>, rawText: string): string {\n const stats = countStats(files)\n const rootVars = Object.entries(theme)\n .map(([name, value]) => `${name}: ${value};`)\n .join(' ')\n // \u539F\u59CB\u6587\u672C\u8981\u5185\u5D4C\u8FDB <script> \u7684 JSON \u5B57\u7B26\u4E32:JSON.stringify \u4E0D\u8F6C\u4E49 `<`,\n // diff \u5185\u5BB9\u91CC\u4E00\u65E6\u51FA\u73B0 `</script>` \u4F1A\u63D0\u524D\u7EC8\u6B62\u6807\u7B7E(\u7ECF\u5178\u89E3\u6790\u9677\u9631),\n // \u7EDF\u4E00\u628A `<` \u5199\u6210 \\u003c(JSON \u8BED\u4E49\u7B49\u4EF7,\u89E3\u6790\u5B89\u5168)\u3002\n const rawJson = JSON.stringify(rawText).replace(/</g, '\\\\u003c')\n const nav = files\n .map((file, index) => `<a class=\"navitem\" href=\"#file-${index}\">${escapeHtml(displayPath(file.newPath) || displayPath(file.oldPath))}</a>`)\n .join('')\n const sections = files.map((file, index) => {\n const oldPath = displayPath(file.oldPath)\n const newPath = displayPath(file.newPath)\n const path = oldPath !== '' && oldPath !== newPath ? `${oldPath} \u2192 ${newPath}` : (newPath === '' ? oldPath : newPath)\n const meta = file.meta.filter((m) => !m.startsWith('index '))\n // \u8BED\u8A00\u63A8\u65AD:\u65B0\u8DEF\u5F84\u4F18\u5148(\u65B0\u589E\u6587\u4EF6\u6CA1\u6709\u65E7\u8DEF\u5F84);/dev/null \u65F6\u56DE\u9000\u65E7\u8DEF\u5F84\u3002\n const lang = langFor(newPath) ?? langFor(oldPath)\n const commentPath = newPath !== '' && newPath !== '/dev/null' ? newPath : oldPath\n const rows = file.rows.length === 0\n ? `<div class=\"dsh-cr-gaprow\">${escapeHtml(file.meta.join(' \u00B7 ') || file.status)}</div>`\n : file.rows.map((row) => rowHtml(row, lang, commentPath)).join('')\n const head = `<header class=\"filehead\"><span class=\"badge\">${STANDALONE_STATUS_LABEL[file.status]}</span><span class=\"path\">${escapeHtml(path)}</span>`\n + (meta.length > 0 ? `<span class=\"meta\">${escapeHtml(meta.join(' \u00B7 '))}</span>` : '')\n + '</header>'\n return `<section class=\"file\" id=\"file-${index}\">${head}<div class=\"dsh-cr-grid\">${rows}</div></section>`\n }).join('')\n return `<!doctype html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Code Review \u00B7 +${stats.added} \u2212${stats.removed} \u00B7 ${stats.files} \u4E2A\u6587\u4EF6</title>\n<style>\n:root { ${rootVars} }\n${STANDALONE_CSS}\n</style>\n</head>\n<body>\n<header class=\"topbar\">\n <span class=\"stats\">+${stats.added} \u2212${stats.removed} \u00B7 ${stats.files} \u4E2A\u6587\u4EF6</span>\n <span class=\"topbar-actions\">\n <button class=\"btn\" id=\"submit-comments\" type=\"button\">\u63D0\u4EA4\u8BC4\u8BBA</button>\n <button class=\"btn\" id=\"copy-raw\" type=\"button\">\u590D\u5236\u539F\u59CB diff</button>\n </span>\n</header>\n<div class=\"submit-panel\" id=\"submit-panel\" hidden>\n <span class=\"submit-hint\">\u628A\u5168\u90E8\u8BC4\u8BBA\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846:</span>\n <button class=\"btn\" id=\"submit-lgtm\" type=\"button\">Looks Good To Me \u2713</button>\n <button class=\"btn\" id=\"submit-plain\" type=\"button\">\u4EC5\u63D0\u4EA4\u8BC4\u8BBA</button>\n</div>\n<nav class=\"sidenav\">${nav}</nav>\n<main>${sections}</main>\n<div class=\"rawwrap\">\n <details><summary class=\"rawsummary\">\u539F\u59CB diff \u6587\u672C</summary>\n <pre class=\"rawpre\">${escapeHtml(rawText)}</pre>\n </details>\n</div>\n<script>\ndocument.getElementById('copy-raw').addEventListener('click', function (event) {\n var raw = ${rawJson}\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(raw).then(function () {\n var btn = event.currentTarget\n btn.textContent = '\u5DF2\u590D\u5236'\n setTimeout(function () { btn.textContent = '\u590D\u5236\u539F\u59CB diff' }, 1200)\n })\n }\n})\n</script>\n<script>\n/* \u884C\u5185\u8BC4\u8BBA:\u70B9\u51FB\u884C\u53F7\u683C\u63D0\u8BC4\u8BBA,\u6512\u9F50\u540E\u63D0\u4EA4\u56DE DSH \u5BF9\u8BDD\u6846(postMessage)\u3002 */\n(function () {\n var comments = []\n var draft = null\n var submitBtn = document.getElementById('submit-comments')\n var panel = document.getElementById('submit-panel')\n\n function esc(text) {\n return String(text)\n .replace(/&/g, '&').replace(/</g, '<')\n .replace(/>/g, '>').replace(/\"/g, '"')\n }\n function syncSubmit() {\n if (!submitBtn) return\n submitBtn.textContent = comments.length === 0 ? '\u63D0\u4EA4\u8BC4\u8BBA' : '\u63D0\u4EA4\u8BC4\u8BBA (' + comments.length + ')'\n }\n function sameLine(a, b) {\n return a.path === b.path && a.lineNo === b.lineNo && a.side === b.side\n }\n function lineOf(num) {\n return {\n path: num.getAttribute('data-cr-path'),\n lineNo: Number(num.getAttribute('data-cr-line')),\n side: num.getAttribute('data-cr-side'),\n }\n }\n function commentsOn(line) {\n return comments.filter(function (c) { return sameLine(c, line) })\n }\n function closeDraft() {\n if (draft && draft.parentNode) draft.parentNode.removeChild(draft)\n draft = null\n }\n function findNum(line) {\n var nums = document.querySelectorAll('.dsh-cr-num[data-cr-line]')\n for (var i = 0; i < nums.length; i += 1) {\n if (sameLine(lineOf(nums[i]), line)) return nums[i]\n }\n return null\n }\n function updateBadge(num) {\n var badge = num.querySelector('.cc')\n if (badge) badge.parentNode.removeChild(badge)\n var n = commentsOn(lineOf(num)).length\n if (n > 0) {\n var span = document.createElement('span')\n span.className = 'cc'\n span.textContent = String(n)\n num.appendChild(span)\n }\n }\n function openDraft(num) {\n closeDraft()\n var line = lineOf(num)\n var existing = commentsOn(line)\n draft = document.createElement('div')\n draft.className = 'comment-draft'\n var html = ''\n if (existing.length > 0) {\n html += '<div class=\"comment-list\">'\n existing.forEach(function (c) { html += '<div class=\"comment-item\">' + esc(c.text) + '</div>' })\n html += '</div>'\n }\n html += '<textarea class=\"comment-input\" placeholder=\"\u5728\u6B64\u884C\u6DFB\u52A0\u8BC4\u8BBA\u2026\"></textarea>'\n html += '<div class=\"comment-actions\">'\n + '<button class=\"btn\" type=\"button\" data-act=\"save\">\u6DFB\u52A0\u8BC4\u8BBA</button>'\n + '<button class=\"btn\" type=\"button\" data-act=\"cancel\">\u53D6\u6D88</button>'\n + '</div>'\n draft.innerHTML = html\n num.parentElement.insertAdjacentElement('afterend', draft)\n var ta = draft.querySelector('.comment-input')\n ta.focus()\n ta.addEventListener('keydown', function (event) {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) saveDraft(line)\n })\n draft.querySelector('[data-act=\"save\"]').addEventListener('click', function () { saveDraft(line) })\n draft.querySelector('[data-act=\"cancel\"]').addEventListener('click', closeDraft)\n }\n function saveDraft(line) {\n if (!draft) return\n var ta = draft.querySelector('.comment-input')\n var text = ta.value.trim()\n closeDraft()\n if (text === '') return\n comments.push({ path: line.path, lineNo: line.lineNo, side: line.side, text: text })\n var num = findNum(line)\n if (num) updateBadge(num)\n syncSubmit()\n }\n function send(kind) {\n if (comments.length === 0) return\n var target = window.opener\n if (!target) {\n alert('\u539F\u6765\u7684 DSH \u9875\u9762\u5DF2\u5173\u95ED,\u65E0\u6CD5\u56DE\u4F20\u8BC4\u8BBA')\n return\n }\n target.postMessage({\n type: 'dsh-code-review-comments',\n kind: kind,\n comments: comments.map(function (c) {\n return { path: c.path, lineNo: c.lineNo, side: c.side, text: c.text }\n }),\n }, '*')\n panel.hidden = true\n }\n document.addEventListener('click', function (event) {\n var target = event.target\n var num = target && target.closest ? target.closest('.dsh-cr-num[data-cr-line]') : null\n if (num) {\n openDraft(num)\n return\n }\n if (draft && !draft.contains(target)) closeDraft()\n if (panel && target && target.closest && !panel.contains(target) && !target.closest('#submit-comments')) {\n panel.hidden = true\n }\n })\n submitBtn.addEventListener('click', function () {\n if (comments.length === 0) return\n panel.hidden = !panel.hidden\n })\n document.getElementById('submit-lgtm').addEventListener('click', function () { send('lgtm') })\n document.getElementById('submit-plain').addEventListener('click', function () { send('comment') })\n window.addEventListener('message', function (event) {\n if (event.source !== window.opener) return\n var data = event.data\n if (!data || data.type !== 'dsh-code-review-ack') return\n if (data.ok === false) {\n alert('\u672A\u80FD\u5199\u5165 DSH \u5BF9\u8BDD\u6846:\u5F53\u524D\u6CA1\u6709\u6253\u5F00\u4E2D\u7684\u4F1A\u8BDD')\n } else {\n submitBtn.textContent = '\u5DF2\u53D1\u9001 \u2713'\n setTimeout(syncSubmit, 3000)\n }\n })\n syncSubmit()\n})()\n</script>\n</body>\n</html>\n`\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["text", "win"]
|
|
4
|
+
"sourcesContent": ["/**\n * @yangzhe1991/dsh-code-review \u63D2\u4EF6,\u6D4F\u89C8\u5668\u534A \u2014\u2014 Git Diff \u72EC\u7ACB\u5BA1\u67E5\u6807\u7B7E\u9875\u3002\n *\n * \u4EA4\u4E92:\u804A\u5929\u7A97\u53E3\u5185\u4FDD\u6301\u5B98\u65B9\u539F\u59CB\u663E\u793A(\u4E0D\u5C55\u5F00\u3001\u4E0D\u8F6C\u6362),\u68C0\u6D4B\u5230 git diff\n * \u8F93\u51FA\u7684\u5757\u4E0A\u81EA\u52A8\u52A0\u4E00\u4E2A\u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D\u6309\u94AE;\u70B9\u51FB\u540E\u5728\u72EC\u7ACB\u6D4F\u89C8\u5668\u6807\u7B7E\n * \u9875\u6E32\u67D3\u5B8C\u6574\u53CC\u5217 Code Review \u89C6\u56FE(\u89C1 diff-view.ts)\u3002\u5F39\u7A97\u5FC5\u987B\u7531\u7528\u6237\n * \u624B\u52BF\u89E6\u53D1,\u6240\u4EE5\u6807\u7B7E\u9875\u53EA\u80FD\u505A\u6210\u6309\u94AE\u5F62\u6001\u3002\n *\n * \u4E24\u4E2A\u6309\u94AE\u6CE8\u5165\u6765\u6E90(\u4E92\u8865,\u8986\u76D6 diff \u51FA\u73B0\u7684\u4E24\u4E2A\u9762):\n * 1. DOM \u63A2\u6D4B:markdown \u4EE3\u7801\u5757(.md-code-block,```diff/```patch \u6216\u5185\u5BB9\n * \u5F62\u4F3C diff)\u4E0E\u8BE6\u60C5\u9762\u677F\u91CC\u7684\u7EC8\u7AEF\u5361\u7247([data-terminal])\u3002\u6D41\u5F0F\u671F\u95F4\u5185\u5BB9\n * \u6301\u7EED\u53D8\u5316,\u53BB\u6296\u7A33\u5B9A\u540E\u624D\u52A0\u6309\u94AE;\u5185\u5BB9\u4E0D\u518D\u5F62\u4F3C diff \u65F6\u79FB\u9664\u6309\u94AE\u3002\n * 2. \u6570\u636E\u5C42:bash \u5DE5\u5177\u7ED3\u679C\u8F93\u51FA\u5F62\u4F3C diff \u65F6,\u6298\u53E0\u7684\u5DE5\u5177\u884C\u5728 DOM \u91CC\u6CA1\u6709\n * \u8F93\u51FA\u5185\u5BB9(\u968F\u5C55\u5F00\u624D\u6302\u8F7D),\u7EAF DOM \u63A2\u6D4B\u770B\u4E0D\u5230 \u2014\u2014 \u7531 session \u4F5C\u7528\u57DF\n * \u7EC4\u4EF6\u8BA2\u9605 chat \u5FEB\u7167,\u628A\u7ED3\u679C\u6587\u672C\u6309\u8282\u70B9 key \u5B58\u8868,\u626B\u63CF\u5668\u7ED9\u5BF9\u5E94\u7684\n * \u5DE5\u5177\u884C\u672C\u4F53([data-sample=\"bash\"])\u52A0\u6309\u94AE\u3002\u5DE5\u5177\u884C\u5185\u7684\u7EC8\u7AEF\u5361\u7247\u63A2\u6D4B\n * \u4F1A\u8DF3\u8FC7(\u907F\u514D\u540C\u4E00\u5757\u51FA\u73B0\u4E24\u4E2A\u6309\u94AE)\u3002\n */\nimport { useEffect } from 'react'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\n// \u89E6\u53D1 SlotMap \u58F0\u660E\u5408\u5E76:shell.overlay \u7531 layout\u3001header.actions \u7531 conversation \u58F0\u660E\u3002\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\n// \u58F0\u660E\u5408\u5E76:\u5B98\u65B9 locale \u670D\u52A1\u6302\u8F7D\u5728 cordis Context \u4E0A(\u7531 dsh-client-locale\n// \u63D0\u4F9B;\u7C7B\u578B\u4E3A unknown,\u8FD0\u884C\u65F6\u505A\u5F62\u72B6\u6821\u9A8C \u2014\u2014 \u4E0D\u76F4\u63A5\u4F9D\u8D56\u8BE5\u5305\u7684\u7C7B\u578B)\u3002\nimport type {} from '@deepseek-ai/cordis'\nimport { isDiffText, parseGitDiff } from './diff-parse'\nimport { buildCommentReport, buildStandaloneHtml, CR_TEXTS } from './diff-view'\nimport type { Lang, ReviewComment } from './diff-view'\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n /** \u5B98\u65B9 locale \u670D\u52A1(\u53EF\u9009:\u5B98\u65B9\u88C5\u914D\u4E0D\u542B locale \u65F6\u4E3A undefined)\u3002 */\n locale?: unknown\n }\n}\n\n// \u2014\u2014 \u53EF\u8C03\u53C2\u6570 \u2014\u2014\n\n/** \u7A33\u5B9A\u53BB\u6296\u7A97\u53E3:\u5185\u5BB9\u5728\u8FD9\u4E2A\u7A97\u53E3\u5185\u4E0D\u518D\u53D8\u5316\u624D\u52A0\u6309\u94AE(\u6D41\u5F0F\u671F\u95F4\u4E0D\u65AD\u91CD\u7F6E)\u3002 */\nconst STABLE_MS = 1000\n/** \u5168\u91CF\u626B\u63CF\u53BB\u6296(MutationObserver \u89E6\u53D1\u9891\u7E41,\u5408\u5E76\u6210\u4E00\u6B21\u626B\u63CF)\u3002 */\nconst SCAN_DEBOUNCE = 300\n/** \u89E3\u6790\u5931\u8D25\u540E\u7684\u91CD\u8BD5\u95F4\u9694(\u7578\u5F62 diff \u5185\u5BB9\u7A33\u5B9A\u540E\u53EF\u80FD\u53D8\u5408\u6CD5)\u3002 */\nconst RETRY_MS = 3000\n/** \u515C\u5E95\u5168\u91CF\u626B\u63CF\u95F4\u9694:\u8986\u76D6\u4EFB\u4F55\u6F0F\u6389\u7684 mutation \u8DEF\u5F84(\u7406\u8BBA\u4E0D\u8BE5\u6709)\u3002 */\nconst SAFETY_SCAN_MS = 10000\n\n/** \u6CE8\u5165\u7684 <style> \u662F\u5426\u5DF2\u5B58\u5728,\u907F\u514D\u91CD\u590D\u6CE8\u5165\u3002 */\nlet styleInjected = false\n\n// \u2014\u2014 \u6309\u94AE\u6837\u5F0F(\u5B98\u65B9\u8BED\u4E49\u53D8\u91CF,\u6DF1\u6D45\u4E3B\u9898\u81EA\u52A8\u9002\u914D)\u2014\u2014\n\nconst BUTTON_CSS = `\n.dsh-cr-openbtn {\n margin-left: auto;\n flex: none;\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n background: transparent;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 1px 10px;\n cursor: pointer;\n white-space: nowrap;\n}\n.dsh-cr-openbtn:hover {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-border-l3);\n}\n`\n\n/** \u5168\u5C40\u6CE8\u5165\u4E00\u6B21\u6309\u94AE\u6837\u5F0F(\u6D4F\u89C8\u5668\u7AEF bundle \u7684\u6A21\u5757\u7EA7\u526F\u4F5C\u7528)\u3002 */\nfunction ensureStyle(): void {\n if (styleInjected || typeof document === 'undefined') return\n styleInjected = true\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-code-review'\n tag.textContent = BUTTON_CSS\n document.head.appendChild(tag)\n}\n\n// \u2014\u2014 \u72EC\u7ACB\u6807\u7B7E\u9875\u6253\u5F00 \u2014\u2014\n\n/** \u72EC\u7ACB\u6807\u7B7E\u9875\u9700\u8981\u7EE7\u627F\u7684 DSW \u4E3B\u9898\u53D8\u91CF(\u53D6\u4E0D\u5230\u65F6\u7ED9\u6D45\u8272\u8FD1\u4F3C\u503C\u515C\u5E95)\u3002 */\nconst THEME_VAR_DEFAULTS: Record<string, string> = {\n '--dsw-alias-bg-base': '#ffffff',\n '--dsw-alias-bg-layer-1': '#f7f7f8',\n '--dsw-alias-markdown-code-block': '#f4f4f5',\n '--dsw-alias-label-primary': '#1a1a1a',\n '--dsw-alias-label-secondary': '#555555',\n '--dsw-alias-label-tertiary': '#888888',\n '--dsw-alias-border-l2': '#e5e5e7',\n '--dsw-alias-border-l3': '#cfcfd2',\n '--dsw-alias-state-error-primary': '#d92d20',\n '--dsw-alias-state-success-primary': '#12b76a',\n '--dsw-alias-state-business-primary': '#3b6fe0',\n '--ds-font-family-code': 'ui-monospace, SFMono-Regular, Menlo, monospace',\n // \u8BED\u6CD5\u9AD8\u4EAE token \u989C\u8272:\u5B98\u65B9 shiki \u4E3B\u9898\u53D8\u91CF(\u4E0E DSH \u6DF1\u6D45\u4E3B\u9898\u4E00\u81F4)\u3002\n '--shiki-token-keyword': '#7c3aed',\n '--shiki-token-string': '#16a34a',\n '--shiki-token-comment': '#9aa0a6',\n '--shiki-token-constant': '#d97706',\n '--shiki-token-function': '#2563eb',\n}\n\n/** \u4ECE\u5BBF\u4E3B\u9875\u9762\u6536\u96C6\u4E3B\u9898\u53D8\u91CF\u7684\u5B9E\u9645\u503C(\u72EC\u7ACB\u6807\u7B7E\u9875\u4E0E DSH \u6DF1\u6D45\u8272\u4FDD\u6301\u4E00\u81F4)\u3002 */\nfunction collectThemeVars(): Record<string, string> {\n const out: Record<string, string> = {}\n const styles = getComputedStyle(document.body)\n for (const name of Object.keys(THEME_VAR_DEFAULTS)) {\n const value = styles.getPropertyValue(name).trim()\n out[name] = value !== '' ? value : THEME_VAR_DEFAULTS[name]\n }\n return out\n}\n\n/**\n * \u6253\u5F00\u72EC\u7ACB\u5BA1\u67E5\u6807\u7B7E\u9875\u3002\u5FC5\u987B\u5728\u7528\u6237\u70B9\u51FB\u624B\u52BF\u5185\u540C\u6B65\u6267\u884C(Blob URL \u751F\u6210 +\n * window.open \u5168\u540C\u6B65),\u5426\u5219\u4F1A\u88AB\u6D4F\u89C8\u5668\u5F39\u7A97\u62E6\u622A\u3002URL \u5EF6\u8FDF\u91CA\u653E\u3002\n * \u8FD4\u56DE\u7684\u5B50\u7A97\u53E3\u5F15\u7528\u767B\u8BB0\u8FDB reviewWindows,\u8BC4\u8BBA\u56DE\u4F20\u65F6\u6821\u9A8C\u6D88\u606F\u6765\u6E90\u3002\n */\n/** \u5B98\u65B9 locale \u670D\u52A1\u7684\u6700\u5C0F\u5F62\u72B6(\u8FD0\u884C\u65F6\u5F62\u72B6\u6821\u9A8C,\u4E0D\u4F9D\u8D56\u5B98\u65B9\u5305\u7C7B\u578B)\u3002 */\ninterface LocaleService {\n getLocale(): { active: string }\n}\n\n/** \u5B98\u65B9 locale \u670D\u52A1(apply \u65F6\u63A5\u7BA1;\u5B98\u65B9\u88C5\u914D\u4E0D\u542B locale \u65F6\u7559 null)\u3002 */\nlet localeService: LocaleService | null = null\n\n/**\n * \u68C0\u6D4B DSH \u754C\u9762\u8BED\u8A00:\u4F18\u5148\u5B98\u65B9 locale \u670D\u52A1(\u7528\u6237\u8BBE\u7F6E > \u6D4F\u89C8\u5668\u8BED\u8A00),\n * \u62FF\u4E0D\u5230\u65F6\u9000\u56DE\u6D4F\u89C8\u5668\u8BED\u8A00(navigator),\u975E zh \u4E00\u5F8B\u6309\u82F1\u6587\u3002\n * \u6BCF\u6B21\u73B0\u8BFB \u2014\u2014 \u7528\u6237\u5728\u8BBE\u7F6E\u91CC\u5207\u6362\u8BED\u8A00\u540E,\u4E0B\u4E00\u6B21\u751F\u6210\u6309\u94AE/\u6807\u7B7E\u9875\u5373\u751F\u6548,\n * \u65E0\u9700\u8BA2\u9605 locale/change \u4E8B\u4EF6\u3002\n */\nfunction detectLang(): Lang {\n if (localeService !== null) {\n return localeService.getLocale().active === 'en' ? 'en' : 'zh'\n }\n const nav = navigator.languages?.[0] ?? navigator.language ?? ''\n return nav.toLowerCase().startsWith('zh') ? 'zh' : 'en'\n}\n\nfunction openReviewTab(text: string): void {\n const files = parseGitDiff(text)\n if (files.length === 0) return\n const html = buildStandaloneHtml(files, collectThemeVars(), text, detectLang())\n const url = URL.createObjectURL(new Blob([html], { type: 'text/html' }))\n // \u987A\u5E26\u6E05\u6389\u5DF2\u5173\u95ED\u7684\u65E7\u5B50\u7A97\u53E3\u5F15\u7528\u3002\n for (const win of reviewWindows) {\n if (win === null || win.closed) reviewWindows.delete(win)\n }\n const win = window.open(url, '_blank')\n if (win !== null) reviewWindows.add(win)\n window.setTimeout(() => URL.revokeObjectURL(url), 60000)\n}\n\n// \u2014\u2014 \u8BC4\u8BBA\u56DE\u4F20(\u72EC\u7ACB\u6807\u7B7E\u9875 \u2192 DSH \u5BF9\u8BDD\u6846)\u2014\u2014\n\n/** \u672C\u63D2\u4EF6\u6253\u5F00\u7684\u5BA1\u67E5\u5B50\u7A97\u53E3(\u6D88\u606F\u6765\u6E90\u6821\u9A8C\u7528)\u3002 */\nconst reviewWindows = new Set<Window | null>()\n\n/** \u5F53\u524D\u4F1A\u8BDD\u7684 composer \u5199\u5165\u5668(header.actions \u7EC4\u4EF6\u6302\u8F7D\u65F6\u6CE8\u518C)\u3002 */\nlet composerWriter: ((text: string) => void) | null = null\n\n/** \u6821\u9A8C\u56DE\u4F20\u8BC4\u8BBA\u7684\u5F62\u72B6,\u8FC7\u6EE4\u975E\u6CD5\u6761\u76EE(\u8DE8\u7A97\u53E3\u6570\u636E\u4E0D\u53EF\u4FE1)\u3002 */\nfunction sanitizeComments(raw: unknown): ReviewComment[] {\n if (!Array.isArray(raw)) return []\n const out: ReviewComment[] = []\n for (const item of raw) {\n if (typeof item !== 'object' || item === null) continue\n const c = item as { path?: unknown; lineNo?: unknown; text?: unknown }\n if (typeof c.path !== 'string' || typeof c.lineNo !== 'number' || typeof c.text !== 'string') continue\n if (c.text.trim() === '') continue\n out.push({ path: c.path, lineNo: Math.trunc(c.lineNo), text: c.text })\n }\n return out\n}\n\n/**\n * \u5904\u7406\u5BA1\u67E5\u9875\u56DE\u4F20\u7684\u8BC4\u8BBA:\u7EC4\u88C5\u300C\u6587\u4EF6\u540D:\u884C\u53F7 \u2014 \u8BC4\u8BBA\u300D\u62A5\u544A\u6587\u672C,\u5199\u5165\n * \u5F53\u524D\u4F1A\u8BDD\u7684 composer,\u5E76\u5411\u5B50\u7A97\u53E3\u56DE\u6267\u3002\u6D88\u606F\u6765\u6E90\u6821\u9A8C:\u5FC5\u987B\u662F\u672C\u63D2\u4EF6\n * \u6253\u5F00\u7684\u7A97\u53E3(\u4E0E origin \u76F8\u6BD4\u66F4\u53EF\u9760,blob \u5B50\u7A97\u53E3\u7684 origin \u8BED\u4E49\u6709\u5DEE\u5F02)\u3002\n */\nfunction handleReviewMessage(event: MessageEvent): void {\n const data = event.data\n if (typeof data !== 'object' || data === null) return\n if (data.type !== 'dsh-code-review-comments') return\n if (!reviewWindows.has(event.source as Window | null)) return\n const comments = sanitizeComments(data.comments)\n if (comments.length === 0) return\n const kind = data.kind === 'lgtm' ? 'lgtm' : 'comment'\n const report = buildCommentReport(comments, kind, detectLang())\n const source = event.source as Window\n if (composerWriter !== null) {\n composerWriter(report)\n source.postMessage({ type: 'dsh-code-review-ack', ok: true }, '*')\n } else {\n source.postMessage({ type: 'dsh-code-review-ack', ok: false }, '*')\n }\n}\n\n// \u2014\u2014 \u6309\u94AE\u6CE8\u5165(\u7EAF DOM,\u4E0D\u4F9D\u8D56 React)\u2014\u2014\n\n/**\n * \u7ED9\u5BBF\u4E3B\u5143\u7D20(\u4EE3\u7801\u5757 banner / \u7EC8\u7AEF\u5361\u7247 header / bash \u5DE5\u5177\u884C\u672C\u4F53)\u52A0\u6309\u94AE:\n * \u2460\u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D;\u2461 \u4EE3\u7801\u5757\u573A\u666F\u518D\u52A0\u300C\u5C55\u5F00/\u6536\u8D77\u300D\u5F00\u5173(\u9ED8\u8BA4\u6298\u53E0\n * \u539F\u59CB diff \u6587\u672C,\u804A\u5929\u6D41\u4FDD\u6301\u7D27\u51D1)\u3002\u5DF2\u5B58\u5728\u7684\u6309\u94AE\u8DF3\u8FC7\u3002\n * @param host \u6309\u94AE\u6302\u8F7D\u70B9(React \u4E0D\u8FFD\u8E2A\u6211\u4EEC\u7684\u8282\u70B9,\u91CD\u6E32\u67D3\u4E00\u822C\u4E0D\u52A8\u5B83)\u3002\n * @param getText \u70B9\u51FB\u65F6\u83B7\u53D6 diff \u539F\u59CB\u6587\u672C(\u73B0\u8BFB,\u907F\u514D\u8FC7\u671F\u5F15\u7528)\u3002\n * @param fold \u4EE3\u7801\u5757\u6298\u53E0\u5F00\u5173(\u975E\u4EE3\u7801\u5757\u573A\u666F\u4F20 null)\u3002\n */\nfunction ensureButtons(\n host: HTMLElement,\n getText: () => string | null,\n fold: { folded: boolean; onToggle: () => void } | null,\n): void {\n const texts = CR_TEXTS[detectLang()]\n if (host.querySelector('[data-dsh-cr-btn]') === null) {\n const btn = document.createElement('button')\n btn.type = 'button'\n btn.className = 'dsh-cr-openbtn'\n btn.dataset.dshCrBtn = ''\n btn.textContent = texts.openTab\n btn.title = texts.openTabTitle\n btn.addEventListener('click', (event) => {\n // bash \u5DE5\u5177\u884C\u672C\u4F53\u6574\u884C\u53EF\u70B9(\u5C55\u5F00),\u5FC5\u987B\u963B\u6B62\u4E8B\u4EF6\u4F20\u64AD\u5230\u5B98\u65B9\u70B9\u51FB\u3002\n event.stopPropagation()\n event.preventDefault()\n const text = getText()\n if (text !== null) openReviewTab(text)\n })\n host.appendChild(btn)\n }\n if (fold !== null && host.querySelector('[data-dsh-cr-fold]') === null) {\n const btn = document.createElement('button')\n btn.type = 'button'\n btn.className = 'dsh-cr-openbtn'\n btn.dataset.dshCrFold = ''\n btn.textContent = fold.folded ? texts.expand : texts.collapse\n btn.title = fold.folded ? texts.expandTitle : texts.collapseTitle\n btn.addEventListener('click', (event) => {\n event.stopPropagation()\n event.preventDefault()\n fold.onToggle()\n })\n host.appendChild(btn)\n }\n}\n\n/** \u79FB\u9664\u5BBF\u4E3B\u4E0A\u7684\u6240\u6709\u6309\u94AE(\u5185\u5BB9\u4E0D\u518D\u5F62\u4F3C diff / \u89E3\u6790\u5931\u8D25\u65F6)\u3002 */\nfunction removeButtons(host: HTMLElement): void {\n for (const btn of host.querySelectorAll('[data-dsh-cr-btn], [data-dsh-cr-fold]')) {\n btn.remove()\n }\n}\n\n// \u2014\u2014 \u5019\u9009\u5757\u63A2\u6D4B(DOM \u6765\u6E90)\u2014\u2014\n\n/** \u63A2\u6D4B\u7ED3\u679C:\u539F\u59CB\u5185\u5BB9 + \u662F\u5426\u503C\u5F97\u52A0\u6309\u94AE + \u662F\u5426\u8FD8\u5728\u6D41\u5F0F/\u8FD0\u884C\u3002 */\ninterface Probe {\n /** \u5B98\u65B9\u539F\u59CB\u5185\u5BB9\u6587\u672C(\u5185\u5BB9\u7B7E\u540D,\u7528\u4E8E\u5224\u65AD\u300C\u5185\u5BB9\u662F\u5426\u53D8\u5316\u300D)\u3002 */\n text: string\n /** \u662F\u5426\u503C\u5F97\u52A0\u6309\u94AE(\u8BED\u8A00\u6807\u7B7E\u662F diff/patch,\u6216\u6587\u672C\u5F62\u4F3C git diff)\u3002 */\n candidate: boolean\n /** \u7EC8\u7AEF\u5361\u7247\u4ECD\u5728\u8FD0\u884C(\u8F93\u51FA\u672A\u5B8C\u6210)\u3002 */\n running: boolean\n}\n\n/**\n * \u5B9A\u4F4D markdown \u4EE3\u7801\u5757\u7684\u5B98\u65B9\u539F\u59CB\u5185\u5BB9\u5BB9\u5668(body)\u3002\n *\n * \u4E0D\u80FD\u6309\u300C\u7B2C 2 \u4E2A\u5B50\u8282\u70B9\u300D\u53D6:React \u5728\u8BED\u6CD5\u9AD8\u4EAE\u5668\u5C31\u7EEA\u65F6\u4F1A\u628A body \u4ECE\n * `<pre>` \u6362\u6210\u9AD8\u4EAE `<div>`,\u800C\u65B0\u8282\u70B9\u4F1A append \u5230\u7236\u7EA7\u672B\u5C3E(React \u53EA\u8FFD\u8E2A\n * \u81EA\u5DF1\u7684\u5B50\u8282\u70B9,\u6211\u4EEC\u7684\u6309\u94AE\u662F\u672A\u8FFD\u8E2A\u8282\u70B9,\u4F1A\u88AB\u6324\u5230\u524D\u9762)\u3002\n * \u56E0\u6B64\u6309\u7ED3\u6784\u627E:\u8DF3\u8FC7\u6211\u4EEC\u7684\u8282\u70B9(data-dsh-cr-btn),\u7B2C\u4E00\u4E2A PRE \u5373 body;\n * \u6CA1\u6709 PRE \u65F6,\u7B2C\u4E00\u4E2A\u300C\u975E banner\u300D\u7684 DIV \u5373\u9AD8\u4EAE body\u3002\n */\nfunction findCodeBlockBody(el: HTMLElement): HTMLElement | null {\n const first = el.firstElementChild\n for (const child of el.children) {\n if (!(child instanceof HTMLElement)) continue\n if (child.hasAttribute('data-dsh-cr-btn') || child.hasAttribute('data-dsh-cr-fold')) continue\n if (child.tagName === 'PRE') return child\n }\n for (const child of el.children) {\n if (!(child instanceof HTMLElement)) continue\n if (child.hasAttribute('data-dsh-cr-btn') || child.hasAttribute('data-dsh-cr-fold')) continue\n if (child === first) continue // banner\n if (child.tagName === 'DIV') return child\n }\n return null\n}\n\n/** \u63A2\u6D4B markdown \u4EE3\u7801\u5757:\u7ED3\u6784\u4E3A [banner(\u8BED\u8A00\u6807\u7B7E+\u590D\u5236\u6309\u94AE), body]\u3002 */\nfunction probeCodeBlock(el: HTMLElement): Probe | null {\n const banner = el.firstElementChild\n const body = findCodeBlockBody(el)\n if (!(banner instanceof HTMLElement) || body === null) return null\n const lang = (banner.firstElementChild?.textContent ?? '').trim().toLowerCase()\n const text = body.textContent ?? ''\n const candidate = lang === 'diff' || lang === 'patch' || isDiffText(text)\n return { text, candidate, running: false }\n}\n\n/**\n * \u5728\u7EC8\u7AEF\u5361\u7247\u91CC\u627E\u8F93\u51FA\u5BB9\u5668:\u8DF3\u8FC7\u7B2C 0 \u4E2A\u5B50\u8282\u70B9(\u5934\u90E8:\u547D\u4EE4/\u72B6\u6001/\u590D\u5236\u6309\u94AE),\n * \u7B2C\u4E00\u4E2A\u5185\u5BB9\u5F62\u4F3C git diff \u7684 div \u5373\u8F93\u51FA\u5BB9\u5668(\u7A7A\u6001/\u5934\u90E8\u90FD\u4E0D\u4F1A\u5F62\u4F3C diff,\n * \u5929\u7136\u6392\u9664);\u6211\u4EEC\u7684\u6309\u94AE\u5E26 data-dsh-cr-btn,\u4E00\u5E76\u8DF3\u8FC7\u3002\n */\nfunction findOutputContainer(wrapper: HTMLElement): HTMLElement | null {\n const children = wrapper.children\n for (let i = 1; i < children.length; i += 1) {\n const child = children[i]\n if (!(child instanceof HTMLElement) || child.tagName !== 'DIV') continue\n if (child.hasAttribute('data-dsh-cr-btn') || child.hasAttribute('data-dsh-cr-fold')) continue\n if (isDiffText(child.textContent ?? '')) return child\n }\n return null\n}\n\n/**\n * \u8F93\u51FA\u5BB9\u5668\u9010\u884C\u53D6\u6587\u672C:\u6BCF\u884C\u662F div.line(ANSI \u5DF2\u6E32\u67D3\u6210 span,textContent \u5373\n * \u51C0\u5316\u540E\u7684\u7EAF\u6587\u672C);\u6298\u53E0\u6309\u94AE\u7B49\u975E\u884C\u5143\u7D20\u8DF3\u8FC7\u3002\n */\nfunction joinOutputLines(container: HTMLElement): string {\n const parts: string[] = []\n for (const child of container.children) {\n if (child instanceof HTMLElement && child.tagName === 'DIV') {\n parts.push(child.textContent ?? '')\n }\n }\n return parts.join('\\n')\n}\n\n/** \u63A2\u6D4B\u7EC8\u7AEF\u5361\u7247:\u8FD0\u884C\u4E2D\u76F4\u63A5\u6807\u8BB0 running,\u7531\u8C03\u7528\u65B9\u8DF3\u8FC7\u3002 */\nfunction probeTerminal(el: HTMLElement): Probe | null {\n if (el.hasAttribute('data-running')) {\n return { text: '', candidate: false, running: true }\n }\n const output = findOutputContainer(el)\n if (output === null) return null\n const text = joinOutputLines(output)\n return { text, candidate: isDiffText(text), running: false }\n}\n\n/** \u6309\u5143\u7D20\u7C7B\u578B\u5206\u53D1\u63A2\u6D4B\u3002 */\nfunction probeFor(el: HTMLElement): Probe | null {\n if (el.classList.contains('md-code-block')) return probeCodeBlock(el)\n if (el.hasAttribute('data-terminal')) {\n // \u5DE5\u5177\u884C\u5185\u7684\u7EC8\u7AEF\u5361\u7247:\u6309\u94AE\u52A0\u5728\u884C\u672C\u4F53\u4E0A(\u7531\u6570\u636E\u5C42\u6CE8\u5165,\u6298\u53E0\u65F6\u4E5F\n // \u53EF\u89C1),\u8FD9\u91CC\u8DF3\u8FC7,\u907F\u514D\u540C\u4E00\u5757\u51FA\u73B0\u4E24\u4E2A\u6309\u94AE\u3002\n if (el.closest('[data-sample=\"bash\"]') !== null) return null\n return probeTerminal(el)\n }\n return null\n}\n\n/** \u6309\u94AE\u6302\u8F7D\u70B9:\u4EE3\u7801\u5757 = banner,\u7EC8\u7AEF\u5361\u7247 = header(\u90FD\u662F\u5B98\u65B9\u7B2C\u4E00\u4E2A\u5B50\u8282\u70B9)\u3002 */\nfunction buttonHostFor(el: HTMLElement): HTMLElement | null {\n const first = el.firstElementChild\n return first instanceof HTMLElement ? first : null\n}\n\n// \u2014\u2014 \u6570\u636E\u5C42(\u6298\u53E0\u7684 bash \u5DE5\u5177\u884C)\u2014\u2014\n\n/**\n * \u6570\u636E\u5C42\u6587\u672C\u8868:node key \u2192 { sessionId, diff \u6587\u672C }\u3002\n *\n * \u6298\u53E0\u7684 bash \u5DE5\u5177\u884C\u5728 DOM \u91CC\u53EA\u6709\u4E00\u884C\u6458\u8981,\u8F93\u51FA\u5185\u5BB9\u968F\u5C55\u5F00\u624D\u6302\u8F7D,\n * DOM \u63A2\u6D4B\u770B\u4E0D\u5230 \u2014\u2014 \u53EA\u80FD\u4ECE chat \u5FEB\u7167(\u6570\u636E\u5C42)\u62FF\u5230\u7ED3\u679C\u6587\u672C\u3002\n */\nconst pendingTexts = new Map<string, { sessionId: string; text: string }>()\n\n/** chat \u5FEB\u7167\u7684\u6700\u5C0F\u5F62\u72B6(\u4E0E dsh-web-enhance \u540C\u53E3\u5F84,\u53EA\u53D6\u6240\u9700\u5B57\u6BB5)\u3002 */\ninterface ChatLike {\n order: readonly string[]\n nodes: { get(key: string): { kind: string; data: unknown } | undefined }\n}\n\n/** \u4ECE tool-call \u8282\u70B9 data \u91CC\u53D6\u5DE5\u5177\u7ED3\u679C\u6839(root: RunningToolCall | ToolResultNode)\u3002 */\nfunction readToolRoot(data: unknown): unknown {\n if (typeof data !== 'object' || data === null) return null\n return (data as { root?: unknown }).root ?? null\n}\n\n/** \u5DE5\u5177\u7ED3\u679C\u662F\u5426\u5DF2\u843D\u5730(settled ToolResultNode:\u5E26 content \u6570\u7EC4)\u3002 */\nfunction isSettledToolResult(root: unknown): root is { call: { name: string } | null; content: readonly unknown[] } {\n if (typeof root !== 'object' || root === null) return false\n const r = root as { call?: unknown; content?: unknown }\n if (!Array.isArray(r.content)) return false\n // call \u5934\u53EF\u80FD\u88AB\u7A97\u53E3\u622A\u65AD(call: null),\u4FDD\u5B88\u8D77\u89C1\u53EA\u8BA4\u5F97\u5230\u5DE5\u5177\u540D\u7684\u3002\n if (r.call !== null && (typeof r.call !== 'object' || r.call === null || typeof (r.call as { name?: unknown }).name !== 'string')) {\n return false\n }\n return true\n}\n\n/** \u62FC\u63A5\u5DE5\u5177\u7ED3\u679C content \u91CC\u7684\u6587\u672C\u5757(\u5DE5\u5177\u8F93\u51FA\u901A\u5E38\u662F\u4E00\u6BB5 text \u5757)\u3002 */\nfunction toolResultText(root: unknown): string {\n if (!isSettledToolResult(root)) return ''\n const parts: string[] = []\n for (const block of root.content) {\n if (typeof block !== 'object' || block === null) continue\n const b = block as { type?: unknown; text?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') parts.push(b.text)\n }\n return parts.join('\\n')\n}\n\n/**\n * \u628A\u5F53\u524D\u4F1A\u8BDD chat \u5FEB\u7167\u91CC\u300C\u8F93\u51FA\u5F62\u4F3C git diff \u7684 bash \u5DE5\u5177\u7ED3\u679C\u300D\u540C\u6B65\u8FDB\n * \u6587\u672C\u8868:\u5148\u6E05\u6389\u672C\u4F1A\u8BDD\u65E7\u6761\u76EE(\u9632\u5DE8\u578B\u6587\u672C\u8DE8\u4F1A\u8BDD\u7D2F\u79EF),\u518D\u5199\u5165\u65B0\u6761\u76EE\u3002\n */\nfunction syncDiffToolTexts(sessionId: string, chat: ChatLike): void {\n const found = new Map<string, string>()\n for (const key of chat.order) {\n const node = chat.nodes.get(key)\n if (node === undefined || node.kind !== 'tool-call') continue\n const root = readToolRoot(node.data)\n if (root === null || !isSettledToolResult(root)) continue\n if (root.call === null || root.call.name !== 'bash') continue\n const text = toolResultText(root)\n if (!isDiffText(text)) continue\n found.set(key, text)\n }\n for (const [key, entry] of pendingTexts) {\n if (entry.sessionId === sessionId && !found.has(key)) pendingTexts.delete(key)\n }\n for (const [key, text] of found) pendingTexts.set(key, { sessionId, text })\n}\n\n/** \u6309 data-chat-anchor-key \u627E\u6E32\u67D3\u884C(\u904D\u5386\u6BD4\u5BF9,\u907F\u514D key \u7279\u6B8A\u5B57\u7B26\u8F6C\u4E49\u95EE\u9898)\u3002 */\nfunction findRowByKey(key: string): HTMLElement | null {\n for (const el of document.querySelectorAll('[data-chat-anchor-key]')) {\n if (el instanceof HTMLElement && el.getAttribute('data-chat-anchor-key') === key) return el\n }\n return null\n}\n\n/** \u7ED9\u6570\u636E\u5C42\u6536\u96C6\u5230\u7684 bash \u5DE5\u5177\u884C\u52A0\u6309\u94AE(\u884C\u672A\u6E32\u67D3\u65F6\u7B49\u4E0B\u4E00\u8F6E\u626B\u63CF)\u3002 */\nfunction attachToolRowButtons(): void {\n for (const [key, entry] of pendingTexts) {\n const row = findRowByKey(key)\n if (row === null) continue\n const sample = row.querySelector('[data-sample=\"bash\"]')\n if (!(sample instanceof HTMLElement)) continue\n ensureButtons(sample, () => pendingTexts.get(key)?.text ?? entry.text, null)\n }\n}\n\n// \u2014\u2014 \u626B\u63CF\u5668 \u2014\u2014\n\n/** \u4E00\u4E2A\u5019\u9009\u5757\u7684\u8DDF\u8E2A\u72B6\u6001(DOM \u6765\u6E90)\u3002 */\ninterface BlockState {\n /** \u4E0A\u6B21\u68C0\u6D4B\u5230\u7684\u5185\u5BB9\u7B7E\u540D\u3002 */\n text: string\n /** \u7A33\u5B9A\u53BB\u6296\u5B9A\u65F6\u5668(null = \u6CA1\u6709\u5F85\u5904\u7406\u7684\u52A0\u6309\u94AE)\u3002 */\n timer: number | null\n /** \u4E0A\u6B21\u89E3\u6790\u5931\u8D25\u7684\u65F6\u95F4\u6233(\u91CD\u8BD5\u8282\u6D41)\u3002 */\n lastTry: number\n /** \u4E0A\u6B21\u89E3\u6790\u662F\u5426\u5931\u8D25(\u5931\u8D25\u65F6\u5185\u5BB9\u4E0D\u53D8\u4E5F\u4F1A\u6309\u8282\u6D41\u91CD\u8BD5)\u3002 */\n failed: boolean\n /** \u4EE3\u7801\u5757\u662F\u5426\u6298\u53E0\u539F\u59CB\u6587\u672C(\u9ED8\u8BA4\u6298\u53E0,\u804A\u5929\u6D41\u4FDD\u6301\u7D27\u51D1)\u3002 */\n folded: boolean\n}\n\n/**\n * \u5168\u6587\u6863\u626B\u63CF\u5668:\u53D1\u73B0\u5019\u9009\u5757\u3001\u53BB\u6296\u3001\u6CE8\u5165\u6309\u94AE\u3001\u5185\u5BB9\u53D8\u5316\u65F6\u56DE\u9000\u3002\n *\n * \u72B6\u6001\u673A(\u6BCF\u4E2A\u5757):\n * - \u5185\u5BB9\u53D8\u5316/\u65B0\u5143\u7D20 \u2192 \u6392 STABLE_MS \u53BB\u6296;\n * - \u53BB\u6296\u5230\u70B9\u4E14\u5185\u5BB9\u672A\u518D\u53D8 \u2192 \u89E3\u6790\u9A8C\u8BC1(\u80FD\u89E3\u6790\u51FA\u6587\u4EF6\u624D\u52A0\u6309\u94AE);\n * - \u5185\u5BB9\u4E0D\u518D\u5F62\u4F3C diff / \u7ED3\u6784\u6D88\u5931 \u2192 \u79FB\u9664\u6309\u94AE\u3002\n */\nclass DiffScanner {\n private readonly states = new WeakMap<HTMLElement, BlockState>()\n private observer: MutationObserver | null = null\n private scanTimer: number | null = null\n private safetyTimer: number | null = null\n\n start(): void {\n if (this.observer !== null) return\n // \u4EFB\u4F55 DOM \u589E\u5220\u90FD\u89E6\u53D1\u4E00\u6B21\u53BB\u6296\u626B\u63CF:\u8986\u76D6\u6D41\u5F0F\u8FFD\u52A0\u3001\u865A\u62DF\u5316\u91CD\u6302\u8F7D\u3001\n // \u5DE5\u5177\u884C\u5C55\u5F00\u7B49\u6240\u6709\u51FA\u73B0\u8DEF\u5F84\u3002\n this.observer = new MutationObserver(() => this.scheduleScan())\n this.observer.observe(document.body, { childList: true, subtree: true })\n this.scheduleScan()\n // \u515C\u5E95\u5468\u671F\u626B\u63CF:\u5185\u5BB9\u53D8\u5316\u7406\u8BBA\u4E0A\u90FD\u4F1A\u4EA7\u751F childList mutation,\n // \u8FD9\u91CC\u515C\u4F4F\u9057\u6F0F\u8DEF\u5F84,\u987A\u5E26\u8BA9\u89E3\u6790\u5931\u8D25\u7684\u91CD\u8BD5\u6709\u673A\u4F1A\u89E6\u53D1\u3002\n this.safetyTimer = window.setInterval(() => this.scan(), SAFETY_SCAN_MS)\n }\n\n stop(): void {\n this.observer?.disconnect()\n this.observer = null\n if (this.scanTimer !== null) {\n clearTimeout(this.scanTimer)\n this.scanTimer = null\n }\n if (this.safetyTimer !== null) {\n clearInterval(this.safetyTimer)\n this.safetyTimer = null\n }\n // states \u91CC\u7684\u53BB\u6296\u5B9A\u65F6\u5668\u4E0D\u5FC5\u9010\u4E2A\u6E05\u7406:\u5230\u70B9\u540E probe \u590D\u67E5\u4F1A\u628A\u5DF2\u65AD\u5F00\n // \u5143\u7D20\u7684\u6587\u672C\u8BFB\u6210\u7A7A\u4E32\u3001candidate=false,\u81EA\u7136\u653E\u5F03;WeakMap \u968F\u5757 GC\u3002\n }\n\n /** \u5408\u5E76\u9AD8\u9891 mutation \u4E3A\u4E00\u6B21\u626B\u63CF\u3002 */\n private scheduleScan(): void {\n if (this.scanTimer !== null) return\n this.scanTimer = window.setTimeout(() => {\n this.scanTimer = null\n this.scan()\n }, SCAN_DEBOUNCE)\n }\n\n /** \u5168\u91CF\u626B\u63CF:\u6570\u636E\u5C42\u5DE5\u5177\u884C\u6309\u94AE + DOM \u5019\u9009\u5757\u3002 */\n private scan(): void {\n attachToolRowButtons()\n for (const el of document.querySelectorAll('.md-code-block, [data-terminal]')) {\n this.inspect(el as HTMLElement)\n }\n }\n\n /** \u68C0\u67E5\u4E00\u4E2A\u5019\u9009\u5757:\u5185\u5BB9\u672A\u53D8\u5219\u4E0D\u52A8,\u53D8\u4E86\u5219\u6392\u53BB\u6296,\u4E0D\u518D\u5F62\u4F3C diff \u5219\u79FB\u9664\u6309\u94AE\u3002 */\n private inspect(el: HTMLElement): void {\n const probe = probeFor(el)\n if (probe === null || probe.running || !probe.candidate || probe.text.trim() === '') {\n const host = buttonHostFor(el)\n if (host !== null) removeButtons(host)\n // \u6062\u590D\u5B98\u65B9\u539F\u59CB\u663E\u793A(\u6B64\u524D\u53EF\u80FD\u88AB\u6298\u53E0)\u3002\n const body = findCodeBlockBody(el)\n if (body !== null) body.style.display = ''\n this.states.delete(el)\n return\n }\n const st = this.states.get(el)\n if (st !== undefined && st.text === probe.text) {\n // \u5185\u5BB9\u672A\u53D8:\u6309\u94AE\u5DF2\u52A0/\u53BB\u6296\u4E2D \u2192 \u65E0\u4E8B;\u89E3\u6790\u5931\u8D25 \u2192 \u6309\u8282\u6D41\u91CD\u8BD5\u3002\n if (!st.failed) {\n // \u91CD\u65B0\u540C\u6B65\u6298\u53E0\u6001:React \u53EF\u80FD\u5728\u540E\u53F0\u66FF\u6362\u8FC7 body \u8282\u70B9(\u8BED\u6CD5\u9AD8\u4EAE\n // \u5C31\u7EEA\u65F6 pre \u2192 div),\u65B0\u8282\u70B9\u9700\u8981\u91CD\u65B0\u9690\u85CF/\u663E\u793A\u3002\n this.applyFoldState(el, st)\n return\n }\n if (Date.now() - st.lastTry < RETRY_MS) return\n // \u5931\u8D25\u91CD\u8BD5\u5230\u70B9:\u76F4\u63A5\u8D70\u4E0B\u65B9\u52A0\u6309\u94AE\u5206\u652F(\u5185\u5BB9\u7A33\u5B9A,\u4E0D\u5FC5\u518D\u53BB\u6296)\u3002\n }\n if (st !== undefined && st.timer !== null) clearTimeout(st.timer)\n const timer = window.setTimeout(() => {\n const cur = this.states.get(el)\n if (cur === undefined || cur.timer !== timer) return\n cur.timer = null\n // \u5230\u70B9\u590D\u67E5:\u5185\u5BB9\u53C8\u53D8\u4E86(\u6D41\u5F0F\u8FD8\u5728\u7EE7\u7EED)\u5219\u653E\u5F03\u672C\u6B21,\u7B49\u4E0B\u4E00\u8F6E\u3002\n const p2 = probeFor(el)\n if (p2 === null || p2.running || !p2.candidate || p2.text !== cur.text) return\n this.attachButton(el, cur)\n }, STABLE_MS)\n this.states.set(el, {\n text: probe.text,\n timer,\n lastTry: st?.lastTry ?? 0,\n failed: st?.failed ?? false,\n folded: st?.folded ?? true,\n })\n }\n\n /** \u5185\u5BB9\u7A33\u5B9A\u540E\u6267\u884C:\u89E3\u6790\u9A8C\u8BC1\u3001\u6298\u53E0\u539F\u59CB\u6587\u672C\u5E76\u6CE8\u5165\u6309\u94AE(\u5931\u8D25\u5219\u4FDD\u6301\u65E0\u6309\u94AE)\u3002 */\n private attachButton(el: HTMLElement, st: BlockState): void {\n const host = buttonHostFor(el)\n if (host === null) return\n if (parseGitDiff(st.text).length === 0) {\n st.failed = true\n st.lastTry = Date.now()\n removeButtons(host)\n return\n }\n st.failed = false\n const isCodeBlock = el.classList.contains('md-code-block')\n if (isCodeBlock) {\n // \u9ED8\u8BA4\u6298\u53E0:diff \u4EE3\u7801\u5757\u53EA\u7559\u6807\u9898\u884C + \u6309\u94AE,\u4E0D\u644A\u5F00\u6574\u6BB5\u539F\u59CB\u6587\u672C\u3002\n this.applyFoldState(el, st)\n }\n // \u70B9\u51FB\u65F6\u73B0\u8BFB\u6587\u672C(React \u91CD\u6E32\u67D3\u540E\u65E7\u5F15\u7528\u53EF\u80FD\u8FC7\u671F)\u3002\n ensureButtons(\n host,\n () => probeFor(el)?.text ?? st.text,\n isCodeBlock ? { folded: st.folded, onToggle: () => this.toggleFold(el) } : null,\n )\n }\n\n /** \u540C\u6B65\u4EE3\u7801\u5757\u7684\u6298\u53E0\u6001(\u5B98\u65B9 body \u53EA\u6539 display,React \u4E0D\u7BA1\u7406\u8FD9\u4E2A style)\u3002 */\n private applyFoldState(el: HTMLElement, st: BlockState): void {\n if (!el.classList.contains('md-code-block')) return\n const body = findCodeBlockBody(el)\n if (body !== null) body.style.display = st.folded ? 'none' : ''\n }\n\n /** \u5C55\u5F00/\u6536\u8D77\u4EE3\u7801\u5757\u539F\u59CB\u6587\u672C,\u5E76\u540C\u6B65\u6309\u94AE\u6587\u6848\u3002 */\n private toggleFold(el: HTMLElement): void {\n const st = this.states.get(el)\n if (st === undefined) return\n st.folded = !st.folded\n this.applyFoldState(el, st)\n const host = buttonHostFor(el)\n const btn = host?.querySelector('[data-dsh-cr-fold]')\n if (btn instanceof HTMLElement) {\n const texts = CR_TEXTS[detectLang()]\n btn.textContent = st.folded ? texts.expand : texts.collapse\n }\n }\n}\n\n// \u2014\u2014 \u63D2\u4EF6\u4E3B\u4F53 \u2014\u2014\n\n/**\n * \u9700\u8981\u7684 client \u670D\u52A1:slots(slot \u6CE8\u518C)\u3001locale(\u754C\u9762\u8BED\u8A00\u68C0\u6D4B)\u3002\n *\n * cordis \u7684\u670D\u52A1\u8BBF\u95EE\u5FC5\u987B\u5148\u5728\u8FD9\u91CC\u58F0\u660E\u6CE8\u5165,\u5426\u5219 apply \u91CC\u8BFB ctx.locale\n * \u4F1A\u629B \"cannot get property 'locale' without inject\" \u2014\u2014 loader entry\n * \u5E94\u7528\u5931\u8D25\u4F1A\u8BA9\u6574\u4E2A\u5BA2\u6237\u7AEF\u7EC4\u5408\u6811\u5D29\u6E83(\u767D\u5C4F)\u3002\u8FD9\u4E5F\u662F\u4E0A\u6B21\u4E8B\u6545\u7684\u6839\u56E0\u3002\n * \u5B98\u65B9\u88C5\u914D\u542B dsh-client-locale,\u670D\u52A1\u5B58\u5728;\u4E07\u4E00\u672A\u6765\u7F3A\u5931,inject \u8FD4\u56DE\n * undefined,detectLang \u91CC\u4F1A\u9000\u5230\u6D4F\u89C8\u5668\u8BED\u8A00,\u4E0D\u4F1A\u5D29\u3002\n */\nexport const inject = ['slots', 'locale']\n\n/** Client \u63D2\u4EF6 body:\u63A5\u7BA1\u5B98\u65B9 locale \u670D\u52A1,\u6CE8\u518C\u626B\u63CF\u5668\u5BBF\u4E3B\u4E0E\u6570\u636E\u5C42\u76D1\u542C\u3002 */\nexport function apply(ctx: ClientContext): void {\n // \u63A5\u7BA1\u5B98\u65B9 locale \u670D\u52A1(\u8FD0\u884C\u65F6\u5F62\u72B6\u6821\u9A8C;\u7F3A\u7701\u65F6 detectLang \u9000\u6D4F\u89C8\u5668\u8BED\u8A00)\u3002\n const locale = ctx.locale\n if (typeof locale === 'object' && locale !== null && typeof (locale as { getLocale?: unknown }).getLocale === 'function') {\n localeService = locale as LocaleService\n }\n ctx.slots.inject(\n 'shell.overlay',\n () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'code-review-enhancer',\n }, CodeReviewEnhancer),\n )\n ctx.slots.inject(\n 'conversation.session.header.actions',\n () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: 'code-review-tool-watcher',\n // \u5B98\u65B9\u6761\u76EE:agent-preset=-10\u3001subagent-catalog=10\u3001job-list=20,\n // web-enhance=15\u3002\u53D6 25 \u6392\u5728\u6700\u540E,\u4E0D\u4E0E\u4ED6\u4EBA\u5E76\u5217\u3002\n order: 25,\n }, DiffToolButtonWatcher),\n )\n}\n\n/**\n * \u626B\u63CF\u5668\u5BBF\u4E3B\u7EC4\u4EF6(root \u4F5C\u7528\u57DF,\u5168\u5C40\u53EA\u6B64\u4E00\u4EFD):\u6302\u8F7D\u65F6\u6CE8\u5165\u6837\u5F0F\u3001\u542F\u52A8\n * \u5168\u6587\u6863\u626B\u63CF\u3001\u76D1\u542C\u5BA1\u67E5\u9875\u7684\u8BC4\u8BBA\u56DE\u4F20\u6D88\u606F,\u5378\u8F7D\u65F6\u5168\u90E8\u6E05\u7406\u3002\n * \u81EA\u8EAB\u6E32\u67D3 null,\u4E0D\u5360\u4EFB\u4F55\u754C\u9762\u7A7A\u95F4\u3002\n */\nfunction CodeReviewEnhancer() {\n useEffect(() => {\n ensureStyle()\n const scanner = new DiffScanner()\n scanner.start()\n const onMessage = handleReviewMessage\n window.addEventListener('message', onMessage)\n return () => {\n scanner.stop()\n window.removeEventListener('message', onMessage)\n }\n }, [])\n return null\n}\n\n/**\n * \u6570\u636E\u5C42\u76D1\u542C(session \u4F5C\u7528\u57DF,\u968F\u4F1A\u8BDD\u5207\u6362\u81EA\u52A8\u91CD\u8BA2\u9605):\n * 1. \u8BA2\u9605\u5F53\u524D\u4F1A\u8BDD chat \u5FEB\u7167,\u628A\u8F93\u51FA\u5F62\u4F3C git diff \u7684 bash \u5DE5\u5177\u7ED3\u679C\u540C\u6B65\n * \u8FDB\u6587\u672C\u8868,\u626B\u63CF\u5668\u636E\u6B64\u7ED9\u6298\u53E0\u7684\u5DE5\u5177\u884C\u52A0\u6309\u94AE;\n * 2. \u6CE8\u518C\u5F53\u524D\u4F1A\u8BDD\u7684 composer \u5199\u5165\u5668 \u2014\u2014 \u5BA1\u67E5\u9875\u56DE\u4F20\u7684\u8BC4\u8BBA\u7ECF\n * inputActions.setDraft(\u5B98\u65B9\u5355\u4E00\u5199\u5165\u53E3)\u5199\u5165\u5BF9\u8BDD\u6846\u3002\u81EA\u8EAB\u6E32\u67D3 null\u3002\n */\nfunction DiffToolButtonWatcher({ useSession, sessionId, inputActions }: PropsRuntime<'conversation.session.header.actions'>) {\n const chat = useSession((state) => state.chat)\n useEffect(() => {\n if (chat !== undefined) syncDiffToolTexts(String(sessionId), chat)\n }, [chat, sessionId])\n useEffect(() => {\n // \u53EA\u6E05\u7406\u81EA\u5DF1\u6CE8\u518C\u7684\u5199\u5165\u5668(\u591A\u7EC4\u4EF6\u5B9E\u4F8B\u5E76\u5B58\u65F6\u4E92\u4E0D\u8BEF\u5220)\u3002\n const writer = (text: string): void => inputActions.setDraft(text)\n composerWriter = writer\n return () => {\n if (composerWriter === writer) composerWriter = null\n }\n }, [inputActions])\n return null\n}\n", "/**\n * unified diff \u6587\u672C\u89E3\u6790\u5668(\u7EAF\u903B\u8F91,\u4E0D\u4F9D\u8D56 DOM,\u53EF\u5728 Node \u91CC\u5355\u6D4B)\u3002\n *\n * \u8F93\u5165\u662F `git diff` / `git show` / `git diff --no-index` \u7B49\u4EA7\u751F\u7684\n * unified diff \u6587\u672C,\u8F93\u51FA\u6309\u6587\u4EF6\u62C6\u5206\u7684\u7ED3\u6784\u5316\u6570\u636E,\u4F9B\u53CC\u5217\u89C6\u56FE\u6E32\u67D3\u3002\n *\n * \u884C\u6A21\u578B\u8BF4\u660E:\n * - ctx \u4E0A\u4E0B\u6587\u884C:\u65E7/\u65B0\u4E24\u4FA7\u90FD\u6709,\u884C\u53F7\u5404\u81EA\u9012\u589E;\n * - del \u5220\u9664\u884C:\u53EA\u5728\u65E7\u4FA7;\n * - add \u65B0\u589E\u884C:\u53EA\u5728\u65B0\u4FA7;\n * - change \u53D8\u66F4\u5BF9:\u8FDE\u7EED\u300C-\u300D\u5757\u540E\u7D27\u8DDF\u8FDE\u7EED\u300C+\u300D\u5757\u65F6,\u4E24\u4E24\u914D\u5BF9\u6210\u540C\u4E00\u884C\n * (GitHub split view \u7684\u5BF9\u9F50\u65B9\u5F0F),\u65E7\u884C\u53F7\u53D6 - \u884C\u3001\u65B0\u884C\u53F7\u53D6 + \u884C,\n * \u4E24\u4FA7\u6570\u91CF\u4E0D\u7B49\u65F6,\u591A\u51FA\u7684\u884C\u9000\u5316\u4E3A\u5355\u4FA7 del/add;\n * - gap \u95F4\u9699\u884C:@@ hunk \u5934,\u6216\u6298\u53E0\u540E\u7684\u300C\u22EF \u5C55\u5F00\u300D\u5360\u4F4D(\u6E32\u67D3\u5C42\u7528),\n * \u4E0D\u5360\u884C\u53F7\u3002\n *\n * \u5B9E\u73B0\u6CE8\u610F:\u4E0D\u628A\u300C\u5F53\u524D\u6587\u4EF6\u300D\u5B58\u6210\u88AB\u95ED\u5305\u6355\u83B7\u8D4B\u503C\u7684\u5C40\u90E8\u53D8\u91CF \u2014\u2014\n * TypeScript \u5BF9\u300C\u95ED\u5305\u5185\u8D4B\u503C\u7684 let\u300D\u4F1A\u505A\u4FDD\u5B88\u63A7\u5236\u6D41\u5206\u6790,\u5916\u5C42\u7A84\u5316\u4F1A\u5931\u6548\n * (\u751A\u81F3\u7A84\u5316\u6210 never);\u7EDF\u4E00\u7528 current() \u4ECE files \u6808\u9876\u53D6,\u907F\u514D\u8E29\u5751\u3002\n */\n\n/** \u53CC\u5217\u89C6\u56FE\u91CC\u7684\u4E00\u884C\u3002 */\nexport interface DiffRow {\n kind: 'ctx' | 'del' | 'add' | 'change' | 'gap'\n /** \u65E7\u4FA7\u884C\u53F7(ctx/del/change \u6709\u503C)\u3002 */\n oldNo?: number\n /** \u65B0\u4FA7\u884C\u53F7(ctx/add/change \u6709\u503C)\u3002 */\n newNo?: number\n /** \u65E7\u4FA7\u6587\u672C(\u5DF2\u53BB\u6389\u884C\u9996\u7684 - / \u7A7A\u683C\u524D\u7F00)\u3002 */\n oldText?: string\n /** \u65B0\u4FA7\u6587\u672C(\u5DF2\u53BB\u6389\u884C\u9996\u7684 + / \u7A7A\u683C\u524D\u7F00)\u3002 */\n newText?: string\n /** gap \u884C\u7684\u663E\u793A\u6587\u672C(@@ \u5934\u539F\u6587)\u3002 */\n gapText?: string\n /** \u65E7\u4FA7\u8BE5\u884C\u662F\u6587\u4EF6\u6700\u540E\u4E00\u884C\u4E14\u672B\u5C3E\u65E0\u6362\u884C\u7B26(\\ No newline \u6807\u8BB0)\u3002 */\n oldNoEol?: boolean\n /** \u65B0\u4FA7\u8BE5\u884C\u662F\u6587\u4EF6\u6700\u540E\u4E00\u884C\u4E14\u672B\u5C3E\u65E0\u6362\u884C\u7B26\u3002 */\n newNoEol?: boolean\n}\n\n/** \u4E00\u4E2A\u6587\u4EF6\u7684\u53D8\u66F4\u3002 */\nexport interface FileDiff {\n /** \u53D8\u66F4\u524D\u8DEF\u5F84(\u539F\u59CB\u6587\u672C,\u53EF\u80FD\u5E26 a/ \u524D\u7F00;/dev/null \u8868\u793A\u65B0\u5EFA)\u3002 */\n oldPath: string\n /** \u53D8\u66F4\u540E\u8DEF\u5F84(\u539F\u59CB\u6587\u672C,\u53EF\u80FD\u5E26 b/ \u524D\u7F00;/dev/null \u8868\u793A\u5220\u9664)\u3002 */\n newPath: string\n /** \u53D8\u66F4\u7C7B\u578B\u3002 */\n status: 'modified' | 'added' | 'deleted' | 'renamed' | 'binary' | 'mode'\n /** \u6587\u4EF6\u7EA7\u5143\u4FE1\u606F\u884C(new file mode / rename from / similarity index \u7B49)\u3002 */\n meta: string[]\n /** \u6E32\u67D3\u884C\u5E8F\u5217(\u542B hunk \u95F4\u9699\u884C)\u3002 */\n rows: DiffRow[]\n}\n\n/** \u5C55\u793A\u7528\u7684\u8DEF\u5F84:\u53BB\u6389 a/ b/ \u524D\u7F00(\u4EC5\u5F53\u786E\u5B9E\u5E26\u524D\u7F00\u65F6)\u3002 */\nexport function displayPath(path: string): string {\n if (path === '/dev/null') return path\n return path.replace(/^[ab]\\//, '')\n}\n\n/** \u6587\u672C\u662F\u5426\u5F62\u4F3C git diff(\u68C0\u6D4B\u7528,\u5148\u4E8E\u5B8C\u6574\u89E3\u6790)\u3002 */\nexport function isDiffText(text: string): boolean {\n const lines = text.split('\\n')\n // git \u6807\u51C6\u5934;\u6CA1\u6709\u5B83\u65F6,\u9000\u800C\u8981\u6C42\u300Chunk \u5934 + \u6587\u4EF6\u6807\u8BB0\u300D\u540C\u65F6\u51FA\u73B0,\n // \u907F\u514D\u628A\u666E\u901A\u5E26 @@ \u7684\u6587\u672C\u8BEF\u5224\u6210 diff\u3002\n if (lines.some((line) => line.startsWith('diff --git '))) return true\n const hasHunk = lines.some((line) => /^@@ -\\d+/.test(line))\n const hasFileMark = lines.some((line) => /^(---|\\+\\+\\+) \\S/.test(line))\n return hasHunk && hasFileMark\n}\n\n/** \u89E3\u6790\u6574\u6BB5 unified diff \u6587\u672C\u3002 */\nexport function parseGitDiff(text: string): FileDiff[] {\n const lines = text.split(/\\r?\\n/)\n const files: FileDiff[] = []\n // \u5F53\u524D\u662F\u5426\u5904\u4E8E\u67D0\u4E2A hunk \u7684\u6B63\u6587\u533A(\u8DEF\u5F84\u884C ---/+++ \u53EA\u5728 hunk \u5916\u51FA\u73B0)\u3002\n let inHunk = false\n // \u5F53\u524D hunk \u7684\u65E7/\u65B0\u884C\u53F7\u6E38\u6807(\u4ECE hunk \u5934\u58F0\u660E\u7684\u8D77\u70B9\u5F00\u59CB\u9012\u589E)\u3002\n let oldNo = 0\n let newNo = 0\n // \u7B49\u5F85\u914D\u5BF9\u7684\u5220\u9664\u884C:\u8FDE\u7EED\u300C-\u300D\u5757\u4F1A\u5148\u7F13\u5B58,\u9047\u5230\u300C+\u300D\u65F6\u9010\u5BF9\u914D\u5BF9\u6210\n // change \u884C;hunk \u7ED3\u675F\u6216\u9047\u5230\u4E0A\u4E0B\u6587\u884C\u65F6,\u5269\u4F59\u7F13\u5B58\u6309 del \u884C\u8F93\u51FA\u3002\n // noEol \u6807\u8BB0\u53EF\u80FD\u7D27\u8DDF\u300C-\u300D\u884C\u51FA\u73B0(\),\n // \u6240\u4EE5\u8981\u7F13\u5B58\u8FDB\u5F85\u914D\u5BF9\u6761\u76EE,\u914D\u5BF9\u65F6\u624D\u80FD\u5E26\u5230 change \u884C\u7684\u65E7\u4FA7\u3002\n let pendingDels: Array<{ text: string; no: number; noEol: boolean }> = []\n // \u6700\u8FD1\u4E00\u6761\u5185\u5BB9\u884C(\u975E gap)\u7684\u7C7B\u578B,\u7528\u4E8E\u628A \\ No newline \u6807\u8BB0\u6302\u5230\u6B63\u786E\u7684\u4E00\u4FA7\u3002\n let lastKind: 'ctx' | 'del' | 'add' | null = null\n // \u6700\u8FD1\u4E00\u6761\u5DF2\u8F93\u51FA(\u6216\u5DF2\u914D\u5BF9)\u7684\u3001\u5E26\u65B0\u4FA7\u7684\u884C,\u7528\u4E8E\u7ED9\u65B0\u4FA7\u6302 noEol\u3002\n let lastNewRow: DiffRow | null = null\n\n /** \u5F53\u524D\u6B63\u5728\u7D2F\u79EF\u7684\u6587\u4EF6(\u6808\u9876);\u8FD8\u6CA1\u6709 diff \u5934\u65F6\u4E3A null\u3002 */\n function current(): FileDiff | null {\n return files.length > 0 ? files[files.length - 1] : null\n }\n\n /** \u628A\u5F85\u914D\u5BF9\u7F13\u5B58\u6309 del \u884C\u5237\u51FA(\u9047\u4E0A\u4E0B\u6587\u3001\u65B0 hunk\u3001\u6587\u4EF6\u5207\u6362\u3001\u6587\u672C\u7ED3\u675F\u65F6\u8C03\u7528)\u3002 */\n function flushPending(): void {\n const file = current()\n if (file === null || pendingDels.length === 0) return\n for (const del of pendingDels) {\n file.rows.push({\n kind: 'del',\n oldNo: del.no,\n oldText: del.text,\n oldNoEol: del.noEol,\n })\n lastNewRow = null\n }\n pendingDels = []\n }\n\n /** \u65B0\u5EFA\u6587\u4EF6\u5E76\u63A5\u7BA1\u6E38\u6807(\u81EA\u52A8\u5237\u51FA\u4E0A\u4E00\u4E2A\u6587\u4EF6\u7684\u5F85\u914D\u5BF9\u7F13\u5B58)\u3002 */\n function startFile(oldPath: string, newPath: string): void {\n flushPending()\n inHunk = false\n lastKind = null\n lastNewRow = null\n files.push({\n oldPath,\n newPath,\n status: 'modified',\n meta: [],\n rows: [],\n })\n }\n\n /** \u89E3\u6790 @@ hunk \u5934,\u8FD4\u56DE\u662F\u5426\u6210\u529F(\u5931\u8D25\u884C\u539F\u6837\u5F53\u6B63\u6587\u5904\u7406)\u3002 */\n function openHunk(line: string): boolean {\n const match = /^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@(.*)$/.exec(line)\n if (match === null) return false\n flushPending()\n oldNo = Number(match[1])\n newNo = Number(match[3])\n inHunk = true\n lastKind = null\n lastNewRow = null\n // \u4FDD\u7559\u6574\u4E2A hunk \u5934\u539F\u6587(\u542B\u5C3E\u90E8 section \u540D),\u6E32\u67D3\u6210\u95F4\u9699\u884C\u3002\n current()?.rows.push({ kind: 'gap', gapText: line })\n return true\n }\n\n /** \u8F93\u51FA\u4E00\u6761 ctx \u884C(\u5E26\u4E24\u4FA7\u884C\u53F7\u4E0E\u6587\u672C)\u3002 */\n function emitContext(text: string): void {\n const file = current()\n if (file === null) return\n const row: DiffRow = { kind: 'ctx', oldNo, newNo, oldText: text, newText: text }\n file.rows.push(row)\n oldNo += 1\n newNo += 1\n lastKind = 'ctx'\n lastNewRow = row\n }\n\n /** \u8F93\u51FA\u4E00\u6761 change \u884C(\u65E7\u4FA7 del \u4E0E \u65B0\u4FA7 add \u914D\u5BF9)\u3002 */\n function emitChange(del: { text: string; no: number; noEol: boolean }, addText: string): void {\n const file = current()\n if (file === null) return\n const row: DiffRow = {\n kind: 'change',\n oldNo: del.no,\n newNo,\n oldText: del.text,\n newText: addText,\n oldNoEol: del.noEol,\n }\n file.rows.push(row)\n newNo += 1\n lastKind = 'add'\n lastNewRow = row\n }\n\n /** \u8F93\u51FA\u4E00\u6761 add \u884C\u3002 */\n function emitAdd(text: string): void {\n const file = current()\n if (file === null) return\n const row: DiffRow = { kind: 'add', newNo, newText: text }\n file.rows.push(row)\n newNo += 1\n lastKind = 'add'\n lastNewRow = row\n }\n\n /** \u5904\u7406 \ \u6807\u8BB0:\u6302\u5230\u4E0A\u4E00\u884C\u5BF9\u5E94\u7684\u4E00\u4FA7\u3002 */\n function markNoEol(): void {\n if (lastKind === 'del') {\n // \u4E0A\u4E00\u4E2A\u300C-\u300D\u53EF\u80FD\u8FD8\u5728\u5F85\u914D\u5BF9\u7F13\u5B58\u91CC(\u8FD8\u6CA1\u9047\u5230\u914D\u5BF9/\u5237\u51FA)\u3002\n const last = pendingDels[pendingDels.length - 1]\n if (last !== undefined) {\n last.noEol = true\n } else {\n const file = current()\n if (file !== null && file.rows.length > 0) {\n // \u5DF2\u88AB\u5237\u6210 del \u884C:\u6807\u8BB0\u65E7\u4FA7\u3002\n const row = file.rows[file.rows.length - 1]\n if (row.kind === 'del') row.oldNoEol = true\n }\n }\n } else if (lastKind === 'add') {\n // \u4E0A\u4E00\u884C\u662F change \u6216 add:\u6807\u8BB0\u65B0\u4FA7\u3002\n if (lastNewRow !== null) lastNewRow.newNoEol = true\n } else if (lastKind === 'ctx') {\n const file = current()\n if (file !== null && file.rows.length > 0) {\n const row = file.rows[file.rows.length - 1]\n if (row.kind === 'ctx') {\n row.oldNoEol = true\n row.newNoEol = true\n }\n }\n }\n }\n\n for (const line of lines) {\n // \u7A7A\u884C:\u6587\u672C\u672B\u5C3E\u6362\u884C\u7B26\u7684\u62C6\u5206\u4EA7\u7269(unified diff \u7684\u5185\u5BB9\u884C\u90FD\u5E26\u524D\u7F00\n // \u5B57\u7B26,\u771F\u6B63\u7684\u7A7A\u5185\u5BB9\u884C\u662F \" \" \u800C\u4E0D\u662F \"\"),\u76F4\u63A5\u8DF3\u8FC7\u3002\n if (line === '') continue\n if (line.startsWith('diff --git ')) {\n // \u65B0\u6587\u4EF6\u5934:\u5C1D\u8BD5\u4ECE a/X b/Y \u63D0\u53D6\u8DEF\u5F84,\u63D0\u53D6\u5931\u8D25\u4E5F\u4E0D\u963B\u585E\u89E3\u6790\u3002\n const match = /^diff --git (?:a\\/)?(\\S+) (?:b\\/)?(\\S+)$/.exec(line)\n startFile(match?.[1] ?? '', match?.[2] ?? '')\n continue\n }\n if (line.startsWith('@@ ')) {\n openHunk(line)\n continue\n }\n const cur = current()\n if (cur === null) {\n // diff \u5934\u4E4B\u524D\u7684\u524D\u5BFC\u5185\u5BB9(\u63D0\u4EA4\u4FE1\u606F\u7B49)\u8DF3\u8FC7;\u88F8 patch(\u65E0 diff --git\n // \u5934,\u5982 git diff --no-index \u7684\u5408\u5E76\u683C\u5F0F)\u4EE5 --- \u884C\u76F4\u63A5\u5F00\u6587\u4EF6\u3002\n if (line.startsWith('--- ') && line.length > 4) startFile(line.slice(4), '')\n continue\n }\n if (!inHunk) {\n // \u2014\u2014 hunk \u5916\u7684\u5143\u4FE1\u606F\u884C \u2014\u2014\n if (line.startsWith('--- ') && line.length > 4) {\n cur.oldPath = line.slice(4)\n } else if (line.startsWith('+++ ') && line.length > 4) {\n cur.newPath = line.slice(4)\n } else if (line.startsWith('new file mode ')) {\n cur.status = 'added'\n cur.meta.push(line)\n } else if (line.startsWith('deleted file mode ')) {\n cur.status = 'deleted'\n cur.meta.push(line)\n } else if (line.startsWith('rename from ') || line.startsWith('rename to ')) {\n cur.status = 'renamed'\n cur.meta.push(line)\n } else if (\n line.startsWith('old mode ') ||\n line.startsWith('new mode ') ||\n line.startsWith('similarity index ') ||\n line.startsWith('dissimilarity index ') ||\n line.startsWith('copy from ') ||\n line.startsWith('copy to ')\n ) {\n cur.meta.push(line)\n } else if (line.startsWith('Binary files ') || line.startsWith('GIT binary patch')) {\n // \u4E8C\u8FDB\u5236\u6587\u4EF6:\u6CA1\u6709 hunk,\u6E32\u67D3\u6210\u4FE1\u606F\u884C\u3002\n cur.status = 'binary'\n cur.meta.push(line)\n } else if (line.startsWith('index ')) {\n // index \u884C(blob \u54C8\u5E0C)\u4FE1\u606F\u91CF\u4F4E,\u6536\u8FDB meta \u4F46\u6E32\u67D3\u5C42\u9009\u62E9\u5FFD\u7565\u3002\n cur.meta.push(line)\n }\n // \u5176\u4ED6(\u7A7A\u884C\u7B49)\u5FFD\u7565\u3002\n continue\n }\n // \u2014\u2014 hunk \u6B63\u6587 \u2014\u2014\n const kind = line[0]\n const body = line.slice(1)\n if (kind === ' ') {\n flushPending()\n emitContext(body)\n } else if (kind === '-') {\n pendingDels.push({ text: body, no: oldNo, noEol: false })\n oldNo += 1\n lastKind = 'del'\n } else if (kind === '+') {\n const del = pendingDels.shift()\n if (del !== undefined) {\n emitChange(del, body)\n } else {\n emitAdd(body)\n }\n } else if (kind === '\\\\' && line.startsWith('\\\')) {\n markNoEol()\n } else {\n // \u65E0\u6CD5\u8BC6\u522B\u7684\u884C(\u7578\u5F62 diff):\u6309\u4E0A\u4E0B\u6587\u5BBD\u5BB9\u5904\u7406,\u4FDD\u8BC1\u89C6\u56FE\u4E0D\u9519\u4F4D\u3002\n flushPending()\n emitContext(line)\n }\n }\n flushPending()\n return files\n}\n\n/**\n * \u7EDF\u8BA1\u6574\u4E2A\u89E3\u6790\u7ED3\u679C:\u65B0\u589E\u884C\u6570 / \u5220\u9664\u884C\u6570(change \u884C\u4E24\u4FA7\u5404\u8BA1\u4E00\u6B21)\u3002\n * \u4F9B\u5DE5\u5177\u680F\u300C+N \u2212M \u00B7 F \u4E2A\u6587\u4EF6\u300D\u4F7F\u7528\u3002\n */\nexport function countStats(files: FileDiff[]): { added: number; removed: number; files: number } {\n let added = 0\n let removed = 0\n for (const file of files) {\n for (const row of file.rows) {\n if (row.kind === 'add' || row.kind === 'change') added += 1\n if (row.kind === 'del' || row.kind === 'change') removed += 1\n }\n }\n return { added, removed, files: files.length }\n}\n", "/**\n * \u4EE3\u7801\u9AD8\u4EAE\u7684\u7EAF\u903B\u8F91(\u4E0D\u4F9D\u8D56 DOM,\u53EF\u5728 Node \u91CC\u5355\u6D4B)\u3002\n *\n * \u4E00\u3001\u57FA\u7840\u8BED\u6CD5\u9AD8\u4EAE:\u8F7B\u91CF\u5355\u884C tokenizer(\u4E0D\u5F15\u5165\u9AD8\u4EAE\u5E93,\u72EC\u7ACB\u9875\u96F6\u4F9D\u8D56),\n * \u5207\u51FA \u5173\u952E\u5B57/\u5B57\u7B26\u4E32/\u6CE8\u91CA/\u6570\u5B57 \u56DB\u7C7B token,\u6E32\u67D3\u5C42\u6620\u5C04\u6210\u5B98\u65B9\n * --shiki-token-* \u989C\u8272(\u4E0E DSH \u6DF1\u6D45\u4E3B\u9898\u4E00\u81F4)\u3002\n * \u652F\u6301\u6CE8\u91CA\u98CE\u683C:\u53CC\u659C\u6760\u3001\u659C\u6760\u661F\u53F7(\u884C\u5185)\u3001\u4E95\u53F7(Python/shell \u7CFB,\n * \u6309\u6269\u5C55\u540D\u5F00\u542F)\u3002\u5757\u6CE8\u91CA\u4E0D\u8DE8\u884C(\u57FA\u7840\u7248\u53D6\u820D,\u5355\u884C\u6E32\u67D3\u573A\u666F\u5F71\u54CD\u5F88\u5C0F)\u3002\n *\n * \u4E8C\u3001\u884C\u5185\u5B57\u7B26\u7EA7 diff:\u5BF9 change \u884C(\u540C\u4E00\u884C\u65E7/\u65B0\u914D\u5BF9)\u505A\u516C\u5171\u524D\u7F00/\u540E\u7F00\n * \u5265\u79BB,\u4E2D\u95F4\u4E0D\u540C\u7684\u5B57\u7B26\u6BB5\u5C31\u662F\u300C\u53EA\u6539\u4E86\u51E0\u4E2A\u5B57\u7B26\u300D\u7684\u7CBE\u786E\u8303\u56F4,\u6E32\u67D3\u5C42\u7528\n * \u52A0\u6DF1\u80CC\u666F\u8272\u9AD8\u4EAE\u3002\u7D22\u5F15\u6309 code point(Array.from)\u8BA1\u7B97,\u6E32\u67D3\u5C42\u540C\u6837\u6309\n * code point \u5207\u7247,\u4EE3\u7406\u5BF9\u4E0D\u4F1A\u4ECE\u4E2D\u95F4\u65AD\u5F00\u3002\n */\n\n/** \u8BED\u6CD5 token(\u7EAF\u6587\u672C\u5207\u5206,HTML \u8F6C\u4E49\u7531\u6E32\u67D3\u5C42\u505A)\u3002 */\nexport interface Token {\n text: string\n /** null = \u666E\u901A\u6587\u672C;\u5176\u4F59\u4E3A\u8BED\u6CD5\u7C7B\u522B,\u6620\u5C04\u5230 .tok-* \u6837\u5F0F\u3002 */\n cls: 'tok-kw' | 'tok-str' | 'tok-com' | 'tok-num' | null\n}\n\n/** \u884C\u5185\u5DEE\u5F02\u7684\u4E2D\u6BB5\u8303\u56F4(code point \u7D22\u5F15,\u524D\u95ED\u540E\u5F00;null = \u65E0\u5DEE\u5F02)\u3002 */\nexport interface DiffMidRange {\n oldStart: number\n oldEnd: number\n newStart: number\n newEnd: number\n}\n\n/** \u5355\u884C\u9AD8\u4EAE\u914D\u7F6E:\u662F\u5426\u652F\u6301 # \u6CE8\u91CA(Python/shell/YAML \u7B49)\u3002 */\nexport interface LineLang {\n hashComment: boolean\n}\n\n/**\n * \u5408\u5E76\u5E38\u89C1\u8BED\u8A00\u7684\u5173\u952E\u5B57\u8868(\u57FA\u7840\u9AD8\u4EAE\u4E0D\u6309\u8BED\u8A00\u7EC6\u5206,\u4E00\u5957\u901A\u7528\u8BCD\u8868\u8986\u76D6\n * JS/TS\u3001Python\u3001Go\u3001Rust\u3001Java/C \u7CFB\u7684\u7EDD\u5927\u591A\u6570\u5173\u952E\u5B57)\u3002\n */\nconst KEYWORDS = new Set([\n // JS/TS\n 'const', 'let', 'var', 'function', 'class', 'extends', 'super', 'new', 'delete',\n 'typeof', 'instanceof', 'in', 'of', 'this', 'static', 'get', 'set', 'async',\n 'await', 'yield', 'return', 'if', 'else', 'switch', 'case', 'default', 'break',\n 'continue', 'try', 'catch', 'finally', 'throw', 'for', 'while', 'do', 'import',\n 'export', 'from', 'as', 'interface', 'type', 'enum', 'namespace', 'module',\n 'implements', 'public', 'private', 'protected', 'readonly', 'abstract',\n 'declare', 'keyof', 'infer', 'never', 'unknown', 'any', 'void', 'null',\n 'undefined', 'true', 'false', 'NaN', 'Infinity', 'require',\n // Python\n 'def', 'lambda', 'pass', 'raise', 'elif', 'not', 'and', 'or', 'is', 'None',\n 'True', 'False', 'print', 'with', 'global', 'nonlocal', 'assert', 'del',\n // Go\n 'go', 'func', 'package', 'struct', 'map', 'chan', 'select', 'defer', 'range',\n 'nil', 'goto', 'fallthrough',\n // Rust\n 'fn', 'let', 'mut', 'pub', 'impl', 'trait', 'match', 'use', 'mod', 'self',\n 'crate', 'unsafe', 'where', 'loop', 'move', 'ref', 'dyn',\n // Java / C \u7CFB\n 'int', 'float', 'double', 'char', 'boolean', 'byte', 'short', 'long', 'signed',\n 'unsigned', 'auto', 'register', 'extern', 'volatile', 'constexpr', 'sizeof',\n 'template', 'typename',\n])\n\n/** \u505A\u8BED\u6CD5\u9AD8\u4EAE\u7684\u6269\u5C55\u540D\u96C6\u5408(\u5176\u4F59\u6269\u5C55\u540D\u4E0D\u505A\u8BED\u6CD5\u9AD8\u4EAE,\u53EA\u505A\u5B57\u7B26\u7EA7 diff)\u3002 */\nconst HIGHLIGHT_EXTS = new Set([\n 'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json', 'py', 'rb', 'go', 'rs',\n 'java', 'c', 'h', 'cpp', 'cc', 'cs', 'css', 'scss', 'less', 'html', 'htm',\n 'vue', 'xml', 'sql', 'md', 'yml', 'yaml', 'toml', 'sh', 'bash', 'zsh', 'ini',\n])\n\n/** # \u6CE8\u91CA\u8BED\u8A00\u7684\u6269\u5C55\u540D(Python/shell/YAML \u7CFB;JS \u7684 # \u662F\u79C1\u6709\u5B57\u6BB5,\u4E0D\u5F00)\u3002 */\nconst HASH_COMMENT_EXTS = new Set(['py', 'rb', 'sh', 'bash', 'zsh', 'yml', 'yaml', 'toml', 'ini'])\n\n/** \u6309\u6587\u4EF6\u8DEF\u5F84\u63A8\u65AD\u9AD8\u4EAE\u914D\u7F6E;\u65E0\u6CD5\u8BC6\u522B\u65F6\u8FD4\u56DE null(\u53EA\u505A\u5B57\u7B26\u7EA7 diff)\u3002 */\nexport function langFor(path: string): LineLang | null {\n const dot = path.lastIndexOf('.')\n const ext = dot >= 0 ? path.slice(dot + 1).toLowerCase() : ''\n if (ext === '' || !HIGHLIGHT_EXTS.has(ext)) return null\n return { hashComment: HASH_COMMENT_EXTS.has(ext) }\n}\n\n/** \u662F\u5426\u6807\u8BC6\u7B26\u8D77\u59CB\u5B57\u7B26\u3002 */\nfunction isIdentStart(ch: string): boolean {\n return /[A-Za-z_]/.test(ch)\n}\n\n/** \u662F\u5426\u6807\u8BC6\u7B26\u540E\u7EED\u5B57\u7B26\u3002 */\nfunction isIdentPart(ch: string): boolean {\n return /[A-Za-z0-9_]/.test(ch)\n}\n\n/** \u662F\u5426\u6570\u5B57\u8D77\u59CB\u5B57\u7B26\u3002 */\nfunction isDigitStart(ch: string): boolean {\n return /[0-9]/.test(ch)\n}\n\n/** \u662F\u5426\u6570\u5B57\u540E\u7EED\u5B57\u7B26(\u542B\u5341\u516D\u8FDB\u5236/\u4E8C\u8FDB\u5236\u524D\u7F00\u4E0E\u5C0F\u6570\u70B9)\u3002 */\nfunction isDigitPart(ch: string): boolean {\n return /[0-9a-fA-FxXbBoO._]/.test(ch)\n}\n\n/**\n * \u5355\u884C tokenize:\u6309 \u5B57\u7B26\u4E32 \u2192 \u6CE8\u91CA \u2192 \u6570\u5B57 \u2192 \u6807\u8BC6\u7B26 \u7684\u987A\u5E8F\u626B\u63CF,\n * \u5176\u4F59\u5B57\u7B26\u5F52\u5165\u666E\u901A\u6587\u672C\u3002\u884C\u5185 token \u72B6\u6001\u4E0D\u8DE8\u884C(\u57FA\u7840\u7248\u53D6\u820D)\u3002\n */\nexport function tokenizeLine(text: string, lang: LineLang): Token[] {\n const tokens: Token[] = []\n let plain = ''\n const flushPlain = (): void => {\n if (plain !== '') {\n tokens.push({ text: plain, cls: null })\n plain = ''\n }\n }\n let i = 0\n while (i < text.length) {\n const ch = text[i]\n // \u5B57\u7B26\u4E32:\u4E09\u79CD\u5F15\u53F7,\u53CD\u659C\u6760\u8F6C\u4E49\u8DF3\u4E24\u683C;\u884C\u5C3E\u672A\u95ED\u5408\u5219\u5230\u884C\u5C3E\u3002\n if (ch === '\"' || ch === \"'\" || ch === '`') {\n let j = i + 1\n while (j < text.length) {\n if (text[j] === '\\\\') {\n j += 2\n continue\n }\n if (text[j] === ch) break\n j += 1\n }\n flushPlain()\n tokens.push({ text: text.slice(i, Math.min(j + 1, text.length)), cls: 'tok-str' })\n i = Math.min(j + 1, text.length)\n continue\n }\n // \u884C\u6CE8\u91CA // \u6216 /* */(\u884C\u5185;\u672A\u95ED\u5408\u5230\u884C\u5C3E)\n if (ch === '/' && text[i + 1] === '/') {\n flushPlain()\n tokens.push({ text: text.slice(i), cls: 'tok-com' })\n break\n }\n if (ch === '/' && text[i + 1] === '*') {\n const close = text.indexOf('*/', i + 2)\n const end = close === -1 ? text.length : close + 2\n flushPlain()\n tokens.push({ text: text.slice(i, end), cls: 'tok-com' })\n i = end\n continue\n }\n if (lang.hashComment && ch === '#') {\n flushPlain()\n tokens.push({ text: text.slice(i), cls: 'tok-com' })\n break\n }\n // \u6570\u5B57(\u524D\u7F00\u5B57\u6BCD/\u5C0F\u6570\u70B9/\u4E0B\u5212\u7EBF\u90FD\u7B97\u6570\u5B57\u7247\u6BB5)\n if (isDigitStart(ch)) {\n let j = i + 1\n while (j < text.length && isDigitPart(text[j])) j += 1\n flushPlain()\n tokens.push({ text: text.slice(i, j), cls: 'tok-num' })\n i = j\n continue\n }\n // \u6807\u8BC6\u7B26 \u2192 \u547D\u4E2D\u5173\u952E\u5B57\u8868\u5219\u6807 tok-kw\n if (isIdentStart(ch)) {\n let j = i + 1\n while (j < text.length && isIdentPart(text[j])) j += 1\n const word = text.slice(i, j)\n flushPlain()\n tokens.push({ text: word, cls: KEYWORDS.has(word) ? 'tok-kw' : null })\n i = j\n continue\n }\n plain += ch\n i += 1\n }\n flushPlain()\n return tokens\n}\n\n/**\n * \u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02\u8303\u56F4:\u516C\u5171\u524D\u7F00/\u540E\u7F00\u5265\u79BB,\u4E2D\u95F4\u5269\u4F59\u90E8\u5206\u5373\u5DEE\u5F02\u6BB5\u3002\n * \u8986\u76D6\u300C\u4E00\u884C\u53EA\u6539\u51E0\u4E2A\u5B57\u7B26\u300D\u7684\u7CBE\u786E\u9AD8\u4EAE;\u6574\u884C\u91CD\u5199\u65F6\u4E2D\u95F4\u6BB5\u63A5\u8FD1\u6574\u884C,\n * \u6548\u679C\u4E0E\u6574\u884C\u9AD8\u4EAE\u4E00\u81F4(\u53EF\u63A5\u53D7)\u3002\u76F8\u540C\u6587\u672C\u8FD4\u56DE null\u3002\n */\nexport function diffMidRange(oldText: string, newText: string): DiffMidRange | null {\n const a = Array.from(oldText)\n const b = Array.from(newText)\n // \u516C\u5171\u524D\u7F00\n let pre = 0\n const minLen = Math.min(a.length, b.length)\n while (pre < minLen && a[pre] === b[pre]) pre += 1\n // \u516C\u5171\u540E\u7F00(\u4E0D\u56DE\u9000\u8FDB\u524D\u7F00\u533A)\n let oldEnd = a.length\n let newEnd = b.length\n while (oldEnd > pre && newEnd > pre && a[oldEnd - 1] === b[newEnd - 1]) {\n oldEnd -= 1\n newEnd -= 1\n }\n if (pre === a.length && pre === b.length) return null\n return { oldStart: pre, oldEnd, newStart: pre, newEnd }\n}\n", "/**\n * \u72EC\u7ACB Code Review \u6807\u7B7E\u9875\u7684 HTML \u6784\u5EFA(\u7EAF\u5B57\u7B26\u4E32,\u53EF\u5728 Node \u91CC\u5355\u6D4B)\u3002\n *\n * \u804A\u5929\u7A97\u53E3\u5185\u4E0D\u518D\u505A\u53CC\u5217\u8F6C\u6362 \u2014\u2014 \u53EA\u5728\u68C0\u6D4B\u5230 git diff \u7684\u5757\u4E0A\u52A0\u4E00\u4E2A\n * \u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D\u6309\u94AE;\u70B9\u51FB\u540E\u5728\u72EC\u7ACB\u6D4F\u89C8\u5668\u6807\u7B7E\u9875(Blob URL,\u81EA\u5305\u542B)\n * \u6E32\u67D3\u5B8C\u6574\u53CC\u5217\u89C6\u56FE:\u5DE6\u4FA7\u6587\u4EF6\u5BFC\u822A\u3001\u5168\u5BBD\u3001\u6574\u9875\u6EDA\u52A8\u3001\u65E0\u9AD8\u5EA6\u9650\u5236\u3002\n *\n * \u5E03\u5C40(\u4E0E\u5B98\u65B9 DiffBlock \u540C\u6E90\u7684\u53CC\u5217\u8BED\u4E49):\n * - ctx \u4E0A\u4E0B\u6587\u884C:\u4E24\u4FA7\u540C\u6587\u3001\u65E0\u5E95\u8272;\n * - del \u5220\u9664\u884C:\u4EC5\u65E7\u4FA7,\u7EA2\u5E95;\n * - add \u65B0\u589E\u884C:\u4EC5\u65B0\u4FA7,\u7EFF\u5E95;\n * - change \u53D8\u66F4\u5BF9:\u65E7\u4FA7\u7EA2\u5E95 + \u65B0\u4FA7\u7EFF\u5E95(\u8FDE\u7EED - \u4E0E + \u7684\u914D\u5BF9,\u89C1 diff-parse);\n * - gap @@ hunk \u5934,\u6A2A\u8DE8\u56DB\u5217\u3002\n */\n\nimport type { DiffRow, FileDiff } from './diff-parse'\nimport { countStats, displayPath } from './diff-parse'\nimport { diffMidRange, langFor, tokenizeLine } from './diff-highlight'\nimport type { DiffMidRange, LineLang, Token } from './diff-highlight'\n\n/** HTML \u8F6C\u4E49:diff \u884C\u5185\u5BB9\u662F\u4E0D\u53EF\u4FE1\u6587\u672C,\u76F4\u63A5\u62FC HTML \u5FC5\u987B\u5148\u8F6C\u4E49\u3002 */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\n/** \u72EC\u7ACB\u9875\u7684 CSS:\u81EA\u5305\u542B,\u989C\u8272\u5168\u90E8\u8D70\u6CE8\u5165\u7684\u4E3B\u9898\u53D8\u91CF(\u4E0E DSH \u4E3B\u9898\u4E00\u81F4)\u3002 */\nconst STANDALONE_CSS = `\n* { box-sizing: border-box; }\nbody {\n margin: 0;\n font-family: var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace);\n font-size: 13px;\n line-height: 22px;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n}\n.topbar {\n position: sticky;\n top: 0;\n z-index: 10;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 0 16px;\n background: var(--dsw-alias-markdown-code-block);\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n}\n.stats { font-size: 13px; color: var(--dsw-alias-label-secondary); }\n.topbar-actions { display: flex; gap: 8px; }\n.btn {\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n background: transparent;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 2px 10px;\n cursor: pointer;\n font-family: inherit;\n}\n.btn:hover { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-border-l3); }\n.sidenav {\n position: fixed;\n left: 0;\n top: 36px;\n bottom: 0;\n width: 220px;\n overflow-y: auto;\n padding: 12px 8px;\n background: var(--dsw-alias-markdown-code-block);\n border-right: 1px solid var(--dsw-alias-border-l2);\n}\n.navitem {\n display: block;\n padding: 4px 12px;\n font-size: 12px;\n line-height: 20px;\n color: var(--dsw-alias-label-secondary);\n text-decoration: none;\n border-radius: 6px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.navitem:hover { color: var(--dsw-alias-label-primary); background: var(--dsw-alias-bg-layer-1); }\nmain { margin-left: 220px; padding: 16px 24px 60px; }\n.file { margin-bottom: 24px; background: var(--dsw-alias-markdown-code-block); border-radius: 12px; overflow: hidden; }\n.filehead {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n padding: 8px 12px 4px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n user-select: none;\n}\n.badge {\n flex: none;\n font-size: 11px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n padding: 0 6px;\n}\n.path { overflow-wrap: anywhere; }\n.meta { flex-basis: 100%; font-size: 11px; color: var(--dsw-alias-label-tertiary); }\n.dsh-cr-grid {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);\n}\n.dsh-cr-row { display: contents; }\n.dsh-cr-num {\n padding: 0 10px 0 4px;\n text-align: right;\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: nowrap;\n}\n.dsh-cr-cell {\n padding: 0 12px 0 4px;\n min-width: 0;\n white-space: pre-wrap;\n word-break: break-word;\n /* \u5236\u8868\u7B26\u6309 4 \u4E2A\u5B57\u7B26\u5BBD\u5EA6\u6E32\u67D3(\u6D4F\u89C8\u5668\u9ED8\u8BA4 8,\u4EE3\u7801\u9605\u8BFB\u592A\u677E) */\n tab-size: 4;\n}\n.dsh-cr-del .dsh-cr-num-old, .dsh-cr-del .dsh-cr-cell-old,\n.dsh-cr-change .dsh-cr-num-old, .dsh-cr-change .dsh-cr-cell-old {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n}\n.dsh-cr-add .dsh-cr-num-new, .dsh-cr-add .dsh-cr-cell-new,\n.dsh-cr-change .dsh-cr-num-new, .dsh-cr-change .dsh-cr-cell-new {\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n}\n.dsh-cr-gaprow {\n grid-column: 1 / -1;\n padding: 2px 12px;\n font-size: 12px;\n color: var(--dsw-alias-label-tertiary);\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent);\n user-select: none;\n}\n.dsh-cr-nonl { color: var(--dsw-alias-label-tertiary); font-size: 11px; }\n/* \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) */\n.tok-kw { color: var(--shiki-token-keyword, #7c3aed); }\n.tok-str { color: var(--shiki-token-string, #16a34a); }\n.tok-com { color: var(--shiki-token-comment, #9aa0a6); font-style: italic; }\n.tok-num { color: var(--shiki-token-constant, #d97706); }\n/* \u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02:change \u884C\u4E2D\u6BB5\u52A0\u6DF1\u80CC\u666F(\u65E7\u4FA7\u7EA2/\u65B0\u4FA7\u7EFF) */\n.dihl-mid-old {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 32%, transparent);\n border-radius: 2px;\n}\n.dihl-mid-new {\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 32%, transparent);\n border-radius: 2px;\n}\n/* \u884C\u5185\u8BC4\u8BBA:\u884C\u53F7\u683C\u53EF\u70B9\u51FB,hover \u51FA\u73B0 + \u53F7 */\n.dsh-cr-num[data-cr-line] {\n cursor: pointer;\n position: relative;\n}\n.dsh-cr-num[data-cr-line]:hover {\n color: var(--dsw-alias-label-primary);\n}\n.dsh-cr-num[data-cr-line]:hover::after {\n content: '+';\n position: absolute;\n left: 2px;\n top: 0;\n color: var(--dsw-alias-state-business-primary);\n font-weight: 700;\n}\n.cc {\n margin-left: 5px;\n font-size: 10px;\n line-height: 15px;\n color: #fff;\n background: var(--dsw-alias-state-business-primary);\n border-radius: 8px;\n padding: 0 5px;\n user-select: none;\n}\n.comment-draft {\n grid-column: 1 / -1;\n padding: 8px 12px;\n background: var(--dsw-alias-bg-layer-1);\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.comment-list { margin-bottom: 8px; }\n.comment-item {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-markdown-code-block);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 6px 10px;\n margin-bottom: 4px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.comment-input {\n width: 100%;\n min-height: 60px;\n resize: vertical;\n box-sizing: border-box;\n font: inherit;\n font-size: 13px;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-markdown-code-block);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 6px 10px;\n}\n.comment-actions { display: flex; gap: 8px; margin-top: 8px; }\n.submit-panel {\n position: fixed;\n top: 44px;\n right: 16px;\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n background: var(--dsw-alias-markdown-code-block);\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);\n}\n.submit-hint { font-size: 12px; color: var(--dsw-alias-label-secondary); }\n.rawwrap { margin: 16px 24px; }\n.rawsummary { font-size: 12px; color: var(--dsw-alias-label-secondary); cursor: pointer; }\n.rawpre { white-space: pre-wrap; word-break: break-word; font: inherit; margin: 8px 0 0; tab-size: 4; }\n`\n\n/** \u53D8\u66F4\u7C7B\u578B\u7684\u5FBD\u7AE0\u6587\u6848(\u4E2D\u6587\u9ED8\u8BA4,\u7531 CR_TEXTS \u8986\u76D6)\u3002 */\nconst STANDALONE_STATUS_LABEL: Record<FileDiff['status'], string> = {\n modified: '\u4FEE\u6539',\n added: '\u65B0\u589E\u6587\u4EF6',\n deleted: '\u5220\u9664\u6587\u4EF6',\n renamed: '\u91CD\u547D\u540D',\n binary: '\u4E8C\u8FDB\u5236',\n mode: '\u6743\u9650\u53D8\u66F4',\n}\n\n/** \u754C\u9762\u8BED\u8A00(DSH \u9875\u9762 lang \u4EE5 zh \u5F00\u5934\u7B97\u4E2D\u6587,\u5176\u4F59\u4E00\u5F8B\u82F1\u6587)\u3002 */\nexport type Lang = 'zh' | 'en'\n\n/**\n * \u5168\u90E8\u7528\u6237\u53EF\u89C1\u6587\u6848\u7684\u4E2D/\u82F1\u8BCD\u5178\u3002\u804A\u5929\u5185\u6309\u94AE\u3001\u72EC\u7ACB\u6807\u7B7E\u9875\u3001\u8BC4\u8BBA\u62A5\u544A\n * \u4E09\u5904\u5171\u7528;\u72EC\u7ACB\u6807\u7B7E\u9875\u8131\u79BB DSH \u9875\u9762\u62FF\u4E0D\u5230\u5B98\u65B9 locale,\u7531\u751F\u6210\u65F6\u628A\n * \u8BED\u8A00\u8FDE\u540C\u8BCD\u5178\u4E00\u8D77\u4F20\u5165\u3002\u51FD\u6570\u5B57\u6BB5\u4EC5\u4F9B TS \u4FA7\u6C42\u503C,\u6CE8\u5165\u6807\u7B7E\u9875\u811A\u672C\u65F6\n * \u53EA\u6311\u5B57\u7B26\u4E32\u5B57\u6BB5(JSON \u5E8F\u5217\u5316\u4F1A\u4E22\u51FD\u6570)\u3002\n */\nexport interface CrTexts {\n // \u2014\u2014 \u804A\u5929\u5185\u6309\u94AE \u2014\u2014\n openTab: string\n openTabTitle: string\n expand: string\n collapse: string\n expandTitle: string\n collapseTitle: string\n // \u2014\u2014 \u8BC4\u8BBA\u62A5\u544A(\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846)\u2014\u2014\n reportLgtmHeader: (count: number) => string\n reportCommentHeader: (count: number) => string\n // \u2014\u2014 \u72EC\u7ACB\u6807\u7B7E\u9875 \u2014\u2014\n stats: (added: number, removed: number, files: number) => string\n submitComments: string\n copyRaw: string\n copied: string\n sent: string\n submitHint: string\n submitLgtm: string\n submitPlain: string\n rawSummary: string\n commentPlaceholder: string\n addComment: string\n cancel: string\n noNewlineTitle: string\n openerClosed: string\n noSession: string\n status: Record<FileDiff['status'], string>\n}\n\nexport const CR_TEXTS: Record<Lang, CrTexts> = {\n zh: {\n openTab: '\u29C9 \u65B0\u6807\u7B7E\u9875\u6253\u5F00',\n openTabTitle: '\u5728\u72EC\u7ACB\u7684\u6D4F\u89C8\u5668\u6807\u7B7E\u9875\u4E2D\u6253\u5F00 Code Review \u89C6\u56FE',\n expand: '\u5C55\u5F00',\n collapse: '\u6536\u8D77',\n expandTitle: '\u5C55\u5F00\u539F\u59CB diff \u6587\u672C',\n collapseTitle: '\u6536\u8D77\u539F\u59CB diff \u6587\u672C',\n reportLgtmHeader: (count) => `Looks good to me \u2713 \u2014 Code Review \u5171 ${count} \u6761\u8BC4\u8BBA:`,\n reportCommentHeader: (count) => `Code Review \u610F\u89C1,\u5171 ${count} \u6761:`,\n stats: (added, removed, files) => `+${added} \u2212${removed} \u00B7 ${files} \u4E2A\u6587\u4EF6`,\n submitComments: '\u63D0\u4EA4\u8BC4\u8BBA',\n copyRaw: '\u590D\u5236\u539F\u59CB diff',\n copied: '\u5DF2\u590D\u5236',\n sent: '\u5DF2\u53D1\u9001 \u2713',\n submitHint: '\u628A\u5168\u90E8\u8BC4\u8BBA\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846:',\n submitLgtm: 'Looks Good To Me \u2713',\n submitPlain: '\u4EC5\u63D0\u4EA4\u8BC4\u8BBA',\n rawSummary: '\u539F\u59CB diff \u6587\u672C',\n commentPlaceholder: '\u5728\u6B64\u884C\u6DFB\u52A0\u8BC4\u8BBA\u2026',\n addComment: '\u6DFB\u52A0\u8BC4\u8BBA',\n cancel: '\u53D6\u6D88',\n noNewlineTitle: '\u8BE5\u4FA7\u6587\u4EF6\u672B\u5C3E\u65E0\u6362\u884C\u7B26',\n openerClosed: '\u539F\u6765\u7684 DSH \u9875\u9762\u5DF2\u5173\u95ED,\u65E0\u6CD5\u56DE\u4F20\u8BC4\u8BBA',\n noSession: '\u672A\u80FD\u5199\u5165 DSH \u5BF9\u8BDD\u6846:\u5F53\u524D\u6CA1\u6709\u6253\u5F00\u4E2D\u7684\u4F1A\u8BDD',\n status: STANDALONE_STATUS_LABEL,\n },\n en: {\n openTab: '\u29C9 Open in new tab',\n openTabTitle: 'Open the code review view in a separate browser tab',\n expand: 'Expand',\n collapse: 'Collapse',\n expandTitle: 'Show raw diff text',\n collapseTitle: 'Hide raw diff text',\n reportLgtmHeader: (count) => `Looks good to me \u2713 \u2014 Code Review with ${count} comment${count === 1 ? '' : 's'}:`,\n reportCommentHeader: (count) => `Code Review comments (${count}):`,\n stats: (added, removed, files) => `+${added} \u2212${removed} \u00B7 ${files} file${files === 1 ? '' : 's'}`,\n submitComments: 'Submit comments',\n copyRaw: 'Copy raw diff',\n copied: 'Copied',\n sent: 'Sent \u2713',\n submitHint: 'Submit all comments to the DSH composer:',\n submitLgtm: 'Looks Good To Me \u2713',\n submitPlain: 'Comments only',\n rawSummary: 'Raw diff text',\n commentPlaceholder: 'Add a comment on this line\u2026',\n addComment: 'Add comment',\n cancel: 'Cancel',\n noNewlineTitle: 'No newline at end of file',\n openerClosed: 'The original DSH page was closed; comments cannot be sent back',\n noSession: 'Could not write to the DSH composer: no open session',\n status: {\n modified: 'modified',\n added: 'new file',\n deleted: 'deleted',\n renamed: 'renamed',\n binary: 'binary',\n mode: 'mode change',\n },\n },\n}\n\n/** \u53EA\u6311\u5B57\u7B26\u4E32\u5B57\u6BB5,\u5E8F\u5217\u5316\u8FDB\u72EC\u7ACB\u9875\u811A\u672C(JSON \u4F1A\u4E22\u51FD\u6570\u5B57\u6BB5)\u3002 */\nfunction stringTexts(texts: CrTexts): Record<string, string> {\n return {\n submitComments: texts.submitComments,\n copyRaw: texts.copyRaw,\n copied: texts.copied,\n sent: texts.sent,\n submitHint: texts.submitHint,\n submitLgtm: texts.submitLgtm,\n submitPlain: texts.submitPlain,\n rawSummary: texts.rawSummary,\n commentPlaceholder: texts.commentPlaceholder,\n addComment: texts.addComment,\n cancel: texts.cancel,\n noNewlineTitle: texts.noNewlineTitle,\n openerClosed: texts.openerClosed,\n noSession: texts.noSession,\n }\n}\n\n/** \u6807\u7B7E\u9875\u56DE\u4F20\u7684\u4E00\u6761\u884C\u5185\u8BC4\u8BBA(\u7ECF postMessage,\u5F62\u72B6\u7531\u63D2\u4EF6\u4FA7\u6821\u9A8C)\u3002 */\nexport interface ReviewComment {\n path: string\n lineNo: number\n side?: string\n text: string\n}\n\n/**\n * \u7EC4\u88C5\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846\u7684 Code Review \u62A5\u544A\u6587\u672C(\u7EAF\u51FD\u6570,\u5355\u6D4B\u8986\u76D6)\u3002\n * \u683C\u5F0F:\u6587\u4EF6\u540D:\u884C\u53F7 \u2014 \u8BC4\u8BBA,\u9010\u6761\u7F16\u53F7;LGTM \u65F6\u5934\u90E8\u52A0\u6279\u51C6\u58F0\u660E\u3002\n * \u8BC4\u8BBA\u6587\u672C\u542B\u6362\u884C\u65F6,\u7EED\u884C\u7F29\u8FDB\u4E09\u683C\u4E0E\u6761\u76EE\u5185\u5BB9\u5BF9\u9F50,\u4E0D\u7834\u574F\u7F16\u53F7\u7ED3\u6784\u3002\n * @param comments \u884C\u5185\u8BC4\u8BBA\u5217\u8868\u3002\n * @param kind \u63D0\u4EA4\u7C7B\u578B:lgtm(\u6279\u51C6)/ comment(\u7EAF\u8BC4\u8BBA)\u3002\n * @param lang \u62A5\u544A\u8BED\u8A00(\u9ED8\u8BA4\u4E2D\u6587)\u3002\n */\nexport function buildCommentReport(\n comments: readonly ReviewComment[],\n kind: 'lgtm' | 'comment',\n lang: Lang = 'zh',\n): string {\n const texts = CR_TEXTS[lang]\n const header = kind === 'lgtm' ? texts.reportLgtmHeader(comments.length) : texts.reportCommentHeader(comments.length)\n const items = comments.map((comment, index) => {\n const lines = comment.text.split('\\n')\n const first = `${index + 1}. ${comment.path}:${comment.lineNo} \u2014 ${lines[0]}`\n // \u7EED\u884C\u7F29\u8FDB\u4E09\u683C(\u4E0E \"N. \" \u524D\u7F00\u7B49\u5BBD),\u907F\u514D\u4E0E\u4E0B\u4E00\u6761\u7F16\u53F7\u6DF7\u6DC6\u3002\n const rest = lines.slice(1).map((line) => ` ${line}`)\n return [first, ...rest].join('\\n')\n })\n return [header, '', ...items].join('\\n')\n}\n\n/** \u628A token \u5E8F\u5217\u6E32\u67D3\u6210 HTML(\u8F6C\u4E49\u5728 token \u7EA7\u505A,\u4FDD\u8BC1\u5B89\u5168)\u3002 */\nfunction tokensToHtml(tokens: Token[]): string {\n let out = ''\n for (const token of tokens) {\n const escaped = escapeHtml(token.text)\n out += token.cls === null ? escaped : `<span class=\"${token.cls}\">${escaped}</span>`\n }\n return out\n}\n\n/**\n * \u6E32\u67D3\u4E00\u4E2A\u5185\u5BB9\u683C:\u57FA\u7840\u8BED\u6CD5\u9AD8\u4EAE + \u53EF\u9009\u7684\u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02\u9AD8\u4EAE\u3002\n * @param text \u5185\u5BB9\u6587\u672C(\u53EF\u80FD\u4E3A\u7A7A)\u3002\n * @param side \u65E7/\u65B0\u4FA7(\u51B3\u5B9A\u4E2D\u6BB5\u80CC\u666F\u7528\u7EA2\u8FD8\u662F\u7EFF)\u3002\n * @param noEol \u672B\u5C3E\u65E0\u6362\u884C\u6807\u8BB0\u3002\n * @param diffRange \u884C\u5185\u5DEE\u5F02\u8303\u56F4(\u4EC5 change \u884C;null = \u4E0D\u505A\u5B57\u7B26\u7EA7\u9AD8\u4EAE)\u3002\n * @param lang \u8BED\u6CD5\u9AD8\u4EAE\u914D\u7F6E(\u65E0\u6CD5\u8BC6\u522B\u8BED\u8A00\u65F6 null)\u3002\n */\nfunction cellHtml(\n text: string | undefined,\n side: 'old' | 'new',\n noEol: boolean | undefined,\n diffRange: DiffMidRange | null,\n lang: LineLang | null,\n noNewlineTitle: string,\n): string {\n if (text === undefined) return `<div class=\"dsh-cr-cell dsh-cr-cell-${side}\"></div>`\n const mark = noEol === true ? `<span class=\"dsh-cr-nonl\" title=\"${escapeHtml(noNewlineTitle)}\">\u23CE</span>` : ''\n let body: string\n if (diffRange === null) {\n // \u65E0\u5B57\u7B26\u7EA7\u9AD8\u4EAE:\u6574\u884C\u4E00\u4E2A\u8BED\u6CD5\u6BB5\u3002\n body = lang === null ? escapeHtml(text) : tokensToHtml(tokenizeLine(text, lang))\n } else {\n // \u6309 code point \u5207\u524D/\u4E2D/\u540E\u4E09\u6BB5(\u7D22\u5F15\u6765\u81EA diffMidRange \u7684\n // Array.from \u8BA1\u7B97,\u5207\u7247\u540C\u6837\u6309 code point,\u4EE3\u7406\u5BF9\u4E0D\u4F1A\u65AD\u88C2)\u3002\n const chars = Array.from(text)\n const start = side === 'old' ? diffRange.oldStart : diffRange.newStart\n const end = side === 'old' ? diffRange.oldEnd : diffRange.newEnd\n const prefix = chars.slice(0, start).join('')\n const mid = chars.slice(start, end).join('')\n const suffix = chars.slice(end).join('')\n const seg = (segText: string): string =>\n lang === null ? escapeHtml(segText) : tokensToHtml(tokenizeLine(segText, lang))\n body = seg(prefix)\n + (mid !== '' ? `<span class=\"dihl-mid-${side}\">${seg(mid)}</span>` : '')\n + seg(suffix)\n }\n return `<div class=\"dsh-cr-cell dsh-cr-cell-${side}\">${body}${mark}</div>`\n}\n\n/** \u6E32\u67D3\u4E00\u884C(\u53CC\u5217 4 \u683C\u7ED3\u6784,\u76F4\u63A5\u4EA7 HTML \u5B57\u7B26\u4E32)\u3002 */\nfunction rowHtml(row: DiffRow, lang: LineLang | null, path: string, noNewlineTitle: string): string {\n if (row.kind === 'gap') {\n return `<div class=\"dsh-cr-gaprow\">${escapeHtml(row.gapText ?? '')}</div>`\n }\n // \u884C\u53F7\u683C\u5E26\u8BC4\u8BBA\u5B9A\u4F4D\u4FE1\u606F(\u6587\u4EF6\u8DEF\u5F84/\u884C\u53F7/\u4FA7\u522B),\u6807\u7B7E\u9875\u811A\u672C\u636E\u6B64\u6302\u884C\u5185\u8BC4\u8BBA\u3002\n const num = (no: number | undefined, side: string): string => {\n if (no === undefined) return `<div class=\"dsh-cr-num dsh-cr-num-${side}\"></div>`\n 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>`\n }\n const line = `<div class=\"dsh-cr-row dsh-cr-${row.kind}\">`\n if (row.kind === 'ctx') {\n return line + num(row.oldNo, 'old') + cellHtml(row.oldText, 'old', row.oldNoEol, null, lang, noNewlineTitle)\n + num(row.newNo, 'new') + cellHtml(row.newText, 'new', row.newNoEol, null, lang, noNewlineTitle) + '</div>'\n }\n if (row.kind === 'del') {\n return line + num(row.oldNo, 'old') + cellHtml(row.oldText, 'old', row.oldNoEol, null, lang, noNewlineTitle)\n + num(undefined, 'new') + cellHtml(undefined, 'new', undefined, null, lang, noNewlineTitle) + '</div>'\n }\n if (row.kind === 'add') {\n return line + num(undefined, 'old') + cellHtml(undefined, 'old', undefined, null, lang, noNewlineTitle)\n + num(row.newNo, 'new') + cellHtml(row.newText, 'new', row.newNoEol, null, lang, noNewlineTitle) + '</div>'\n }\n // change \u884C:\u7B97\u884C\u5185\u5B57\u7B26\u7EA7\u5DEE\u5F02\u8303\u56F4,\u4E24\u4FA7\u4E2D\u6BB5\u5404\u81EA\u52A0\u6DF1\u80CC\u666F\u3002\n const range = diffMidRange(row.oldText ?? '', row.newText ?? '')\n return line + num(row.oldNo, 'old') + cellHtml(row.oldText, 'old', row.oldNoEol, range, lang, noNewlineTitle)\n + num(row.newNo, 'new') + cellHtml(row.newText, 'new', row.newNoEol, range, lang, noNewlineTitle) + '</div>'\n}\n\n/**\n * \u6784\u5EFA\u72EC\u7ACB\u6807\u7B7E\u9875\u7684\u5B8C\u6574 HTML(\u81EA\u5305\u542B:\u6837\u5F0F + \u4E3B\u9898\u53D8\u91CF + \u53CC\u5217\u5185\u5BB9)\u3002\n * \u5FC5\u987B\u7531\u300C\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u300D\u6309\u94AE\u7684\u70B9\u51FB\u5904\u7406\u5668\u5728\u7528\u6237\u624B\u52BF\u5185\u540C\u6B65\u8C03\u7528\n * (window.open \u624D\u4E0D\u4F1A\u88AB\u6D4F\u89C8\u5668\u62E6\u622A)\u3002\n * @param files \u89E3\u6790\u7ED3\u679C\u3002\n * @param theme \u4ECE\u5BBF\u4E3B\u9875\u9762\u6536\u96C6\u7684 DSW \u4E3B\u9898\u53D8\u91CF(name \u2192 value)\u3002\n * @param rawText \u539F\u59CB diff \u6587\u672C(\u590D\u5236/\u4FDD\u5E95\u67E5\u770B\u7528)\u3002\n */\nexport function buildStandaloneHtml(\n files: FileDiff[],\n theme: Record<string, string>,\n rawText: string,\n lang: Lang = 'zh',\n): string {\n const texts = CR_TEXTS[lang]\n const stats = countStats(files)\n const statsText = texts.stats(stats.added, stats.removed, stats.files)\n const rootVars = Object.entries(theme)\n .map(([name, value]) => `${name}: ${value};`)\n .join(' ')\n // \u539F\u59CB\u6587\u672C\u8981\u5185\u5D4C\u8FDB <script> \u7684 JSON \u5B57\u7B26\u4E32:JSON.stringify \u4E0D\u8F6C\u4E49 `<`,\n // diff \u5185\u5BB9\u91CC\u4E00\u65E6\u51FA\u73B0 `</script>` \u4F1A\u63D0\u524D\u7EC8\u6B62\u6807\u7B7E(\u7ECF\u5178\u89E3\u6790\u9677\u9631),\n // \u7EDF\u4E00\u628A `<` \u5199\u6210 \\u003c(JSON \u8BED\u4E49\u7B49\u4EF7,\u89E3\u6790\u5B89\u5168)\u3002\n const rawJson = JSON.stringify(rawText).replace(/</g, '\\\\u003c')\n const nav = files\n .map((file, index) => `<a class=\"navitem\" href=\"#file-${index}\">${escapeHtml(displayPath(file.newPath) || displayPath(file.oldPath))}</a>`)\n .join('')\n const sections = files.map((file, index) => {\n const oldPath = displayPath(file.oldPath)\n const newPath = displayPath(file.newPath)\n const path = oldPath !== '' && oldPath !== newPath ? `${oldPath} \u2192 ${newPath}` : (newPath === '' ? oldPath : newPath)\n const meta = file.meta.filter((m) => !m.startsWith('index '))\n // \u8BED\u8A00\u63A8\u65AD:\u65B0\u8DEF\u5F84\u4F18\u5148(\u65B0\u589E\u6587\u4EF6\u6CA1\u6709\u65E7\u8DEF\u5F84);/dev/null \u65F6\u56DE\u9000\u65E7\u8DEF\u5F84\u3002\n const lang = langFor(newPath) ?? langFor(oldPath)\n const commentPath = newPath !== '' && newPath !== '/dev/null' ? newPath : oldPath\n const rows = file.rows.length === 0\n ? `<div class=\"dsh-cr-gaprow\">${escapeHtml(file.meta.join(' \u00B7 ') || file.status)}</div>`\n : file.rows.map((row) => rowHtml(row, lang, commentPath, texts.noNewlineTitle)).join('')\n const head = `<header class=\"filehead\"><span class=\"badge\">${texts.status[file.status]}</span><span class=\"path\">${escapeHtml(path)}</span>`\n + (meta.length > 0 ? `<span class=\"meta\">${escapeHtml(meta.join(' \u00B7 '))}</span>` : '')\n + '</header>'\n return `<section class=\"file\" id=\"file-${index}\">${head}<div class=\"dsh-cr-grid\">${rows}</div></section>`\n }).join('')\n return `<!doctype html>\n<html lang=\"${lang === 'zh' ? 'zh-CN' : 'en'}\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Code Review \u00B7 ${statsText}</title>\n<style>\n:root { ${rootVars} }\n${STANDALONE_CSS}\n</style>\n</head>\n<body>\n<header class=\"topbar\">\n <span class=\"stats\">${statsText}</span>\n <span class=\"topbar-actions\">\n <button class=\"btn\" id=\"submit-comments\" type=\"button\">${texts.submitComments}</button>\n <button class=\"btn\" id=\"copy-raw\" type=\"button\">${texts.copyRaw}</button>\n </span>\n</header>\n<div class=\"submit-panel\" id=\"submit-panel\" hidden>\n <span class=\"submit-hint\">${texts.submitHint}</span>\n <button class=\"btn\" id=\"submit-lgtm\" type=\"button\">${texts.submitLgtm}</button>\n <button class=\"btn\" id=\"submit-plain\" type=\"button\">${texts.submitPlain}</button>\n</div>\n<nav class=\"sidenav\">${nav}</nav>\n<main>${sections}</main>\n<div class=\"rawwrap\">\n <details><summary class=\"rawsummary\">${texts.rawSummary}</summary>\n <pre class=\"rawpre\">${escapeHtml(rawText)}</pre>\n </details>\n</div>\n<script>\ndocument.getElementById('copy-raw').addEventListener('click', function (event) {\n var raw = ${rawJson}\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(raw).then(function () {\n var btn = event.currentTarget\n btn.textContent = T.copied\n setTimeout(function () { btn.textContent = T.copyRaw }, 1200)\n })\n }\n})\n</script>\n<script>\n/* \u884C\u5185\u8BC4\u8BBA:\u70B9\u51FB\u884C\u53F7\u683C\u63D0\u8BC4\u8BBA,\u6512\u9F50\u540E\u63D0\u4EA4\u56DE DSH \u5BF9\u8BDD\u6846(postMessage)\u3002 */\n(function () {\n var T = ${JSON.stringify(stringTexts(texts))}\n var comments = []\n var draft = null\n var submitBtn = document.getElementById('submit-comments')\n var panel = document.getElementById('submit-panel')\n\n function esc(text) {\n return String(text)\n .replace(/&/g, '&').replace(/</g, '<')\n .replace(/>/g, '>').replace(/\"/g, '"')\n }\n function syncSubmit() {\n if (!submitBtn) return\n submitBtn.textContent = comments.length === 0 ? T.submitComments : T.submitComments + ' (' + comments.length + ')'\n }\n function sameLine(a, b) {\n return a.path === b.path && a.lineNo === b.lineNo && a.side === b.side\n }\n function lineOf(num) {\n return {\n path: num.getAttribute('data-cr-path'),\n lineNo: Number(num.getAttribute('data-cr-line')),\n side: num.getAttribute('data-cr-side'),\n }\n }\n function commentsOn(line) {\n return comments.filter(function (c) { return sameLine(c, line) })\n }\n function closeDraft() {\n if (draft && draft.parentNode) draft.parentNode.removeChild(draft)\n draft = null\n }\n function findNum(line) {\n var nums = document.querySelectorAll('.dsh-cr-num[data-cr-line]')\n for (var i = 0; i < nums.length; i += 1) {\n if (sameLine(lineOf(nums[i]), line)) return nums[i]\n }\n return null\n }\n function updateBadge(num) {\n var badge = num.querySelector('.cc')\n if (badge) badge.parentNode.removeChild(badge)\n var n = commentsOn(lineOf(num)).length\n if (n > 0) {\n var span = document.createElement('span')\n span.className = 'cc'\n span.textContent = String(n)\n num.appendChild(span)\n }\n }\n function openDraft(num) {\n closeDraft()\n var line = lineOf(num)\n var existing = commentsOn(line)\n draft = document.createElement('div')\n draft.className = 'comment-draft'\n var html = ''\n if (existing.length > 0) {\n html += '<div class=\"comment-list\">'\n existing.forEach(function (c) { html += '<div class=\"comment-item\">' + esc(c.text) + '</div>' })\n html += '</div>'\n }\n html += '<textarea class=\"comment-input\" placeholder=\"' + T.commentPlaceholder + '\"></textarea>'\n html += '<div class=\"comment-actions\">'\n + '<button class=\"btn\" type=\"button\" data-act=\"save\">' + T.addComment + '</button>'\n + '<button class=\"btn\" type=\"button\" data-act=\"cancel\">' + T.cancel + '</button>'\n + '</div>'\n draft.innerHTML = html\n num.parentElement.insertAdjacentElement('afterend', draft)\n var ta = draft.querySelector('.comment-input')\n ta.focus()\n ta.addEventListener('keydown', function (event) {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) saveDraft(line)\n })\n draft.querySelector('[data-act=\"save\"]').addEventListener('click', function () { saveDraft(line) })\n draft.querySelector('[data-act=\"cancel\"]').addEventListener('click', closeDraft)\n }\n function saveDraft(line) {\n if (!draft) return\n var ta = draft.querySelector('.comment-input')\n var text = ta.value.trim()\n closeDraft()\n if (text === '') return\n comments.push({ path: line.path, lineNo: line.lineNo, side: line.side, text: text })\n var num = findNum(line)\n if (num) updateBadge(num)\n syncSubmit()\n }\n function send(kind) {\n if (comments.length === 0) return\n var target = window.opener\n if (!target) {\n alert(T.openerClosed)\n return\n }\n target.postMessage({\n type: 'dsh-code-review-comments',\n kind: kind,\n comments: comments.map(function (c) {\n return { path: c.path, lineNo: c.lineNo, side: c.side, text: c.text }\n }),\n }, '*')\n panel.hidden = true\n }\n document.addEventListener('click', function (event) {\n var target = event.target\n var num = target && target.closest ? target.closest('.dsh-cr-num[data-cr-line]') : null\n if (num) {\n openDraft(num)\n return\n }\n if (draft && !draft.contains(target)) closeDraft()\n if (panel && target && target.closest && !panel.contains(target) && !target.closest('#submit-comments')) {\n panel.hidden = true\n }\n })\n submitBtn.addEventListener('click', function () {\n if (comments.length === 0) return\n panel.hidden = !panel.hidden\n })\n document.getElementById('submit-lgtm').addEventListener('click', function () { send('lgtm') })\n document.getElementById('submit-plain').addEventListener('click', function () { send('comment') })\n window.addEventListener('message', function (event) {\n if (event.source !== window.opener) return\n var data = event.data\n if (!data || data.type !== 'dsh-code-review-ack') return\n if (data.ok === false) {\n alert(T.noSession)\n } else {\n submitBtn.textContent = T.sent\n setTimeout(syncSubmit, 3000)\n }\n })\n syncSubmit()\n})()\n</script>\n</body>\n</html>\n`\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["text", "lang", "win"]
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** dsh-code-review 插件,浏览器半。 */
|
|
2
2
|
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
3
|
-
/** 需要的 client 服务:slots。 */
|
|
3
|
+
/** 需要的 client 服务:slots、locale。 */
|
|
4
4
|
export declare const inject: string[];
|
|
5
5
|
/** Client 插件 body。 */
|
|
6
6
|
export declare function apply(ctx: ClientContext): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yangzhe1991/dsh-code-review",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "DSH Web 插件:git diff 一键打开独立双列 Code Review
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "DSH Web 插件:git diff 一键打开独立双列 Code Review 页,支持语法高亮、行内评论与中英双语 | DSH web plugin: one-click standalone two-column code review page for git diff, with syntax highlighting, inline comments & bilingual UI",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|