@yangzhe1991/dsh-code-review 0.1.1 → 0.1.2

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 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
@@ -729,8 +729,88 @@ var STANDALONE_STATUS_LABEL = {
729
729
  binary: "\u4E8C\u8FDB\u5236",
730
730
  mode: "\u6743\u9650\u53D8\u66F4"
731
731
  };
732
- function buildCommentReport(comments, kind) {
733
- const header = kind === "lgtm" ? `Looks good to me \u2713 \u2014 Code Review \u5171 ${comments.length} \u6761\u8BC4\u8BBA:` : `Code Review \u610F\u89C1,\u5171 ${comments.length} \u6761:`;
732
+ var CR_TEXTS = {
733
+ zh: {
734
+ openTab: "\u29C9 \u65B0\u6807\u7B7E\u9875\u6253\u5F00",
735
+ openTabTitle: "\u5728\u72EC\u7ACB\u7684\u6D4F\u89C8\u5668\u6807\u7B7E\u9875\u4E2D\u6253\u5F00 Code Review \u89C6\u56FE",
736
+ expand: "\u5C55\u5F00",
737
+ collapse: "\u6536\u8D77",
738
+ expandTitle: "\u5C55\u5F00\u539F\u59CB diff \u6587\u672C",
739
+ collapseTitle: "\u6536\u8D77\u539F\u59CB diff \u6587\u672C",
740
+ reportLgtmHeader: (count) => `Looks good to me \u2713 \u2014 Code Review \u5171 ${count} \u6761\u8BC4\u8BBA:`,
741
+ reportCommentHeader: (count) => `Code Review \u610F\u89C1,\u5171 ${count} \u6761:`,
742
+ stats: (added, removed, files) => `+${added} \u2212${removed} \xB7 ${files} \u4E2A\u6587\u4EF6`,
743
+ submitComments: "\u63D0\u4EA4\u8BC4\u8BBA",
744
+ copyRaw: "\u590D\u5236\u539F\u59CB diff",
745
+ copied: "\u5DF2\u590D\u5236",
746
+ sent: "\u5DF2\u53D1\u9001 \u2713",
747
+ submitHint: "\u628A\u5168\u90E8\u8BC4\u8BBA\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846:",
748
+ submitLgtm: "Looks Good To Me \u2713",
749
+ submitPlain: "\u4EC5\u63D0\u4EA4\u8BC4\u8BBA",
750
+ rawSummary: "\u539F\u59CB diff \u6587\u672C",
751
+ commentPlaceholder: "\u5728\u6B64\u884C\u6DFB\u52A0\u8BC4\u8BBA\u2026",
752
+ addComment: "\u6DFB\u52A0\u8BC4\u8BBA",
753
+ cancel: "\u53D6\u6D88",
754
+ noNewlineTitle: "\u8BE5\u4FA7\u6587\u4EF6\u672B\u5C3E\u65E0\u6362\u884C\u7B26",
755
+ openerClosed: "\u539F\u6765\u7684 DSH \u9875\u9762\u5DF2\u5173\u95ED,\u65E0\u6CD5\u56DE\u4F20\u8BC4\u8BBA",
756
+ noSession: "\u672A\u80FD\u5199\u5165 DSH \u5BF9\u8BDD\u6846:\u5F53\u524D\u6CA1\u6709\u6253\u5F00\u4E2D\u7684\u4F1A\u8BDD",
757
+ status: STANDALONE_STATUS_LABEL
758
+ },
759
+ en: {
760
+ openTab: "\u29C9 Open in new tab",
761
+ openTabTitle: "Open the code review view in a separate browser tab",
762
+ expand: "Expand",
763
+ collapse: "Collapse",
764
+ expandTitle: "Show raw diff text",
765
+ collapseTitle: "Hide raw diff text",
766
+ reportLgtmHeader: (count) => `Looks good to me \u2713 \u2014 Code Review with ${count} comment${count === 1 ? "" : "s"}:`,
767
+ reportCommentHeader: (count) => `Code Review comments (${count}):`,
768
+ stats: (added, removed, files) => `+${added} \u2212${removed} \xB7 ${files} file${files === 1 ? "" : "s"}`,
769
+ submitComments: "Submit comments",
770
+ copyRaw: "Copy raw diff",
771
+ copied: "Copied",
772
+ sent: "Sent \u2713",
773
+ submitHint: "Submit all comments to the DSH composer:",
774
+ submitLgtm: "Looks Good To Me \u2713",
775
+ submitPlain: "Comments only",
776
+ rawSummary: "Raw diff text",
777
+ commentPlaceholder: "Add a comment on this line\u2026",
778
+ addComment: "Add comment",
779
+ cancel: "Cancel",
780
+ noNewlineTitle: "No newline at end of file",
781
+ openerClosed: "The original DSH page was closed; comments cannot be sent back",
782
+ noSession: "Could not write to the DSH composer: no open session",
783
+ status: {
784
+ modified: "modified",
785
+ added: "new file",
786
+ deleted: "deleted",
787
+ renamed: "renamed",
788
+ binary: "binary",
789
+ mode: "mode change"
790
+ }
791
+ }
792
+ };
793
+ function stringTexts(texts) {
794
+ return {
795
+ submitComments: texts.submitComments,
796
+ copyRaw: texts.copyRaw,
797
+ copied: texts.copied,
798
+ sent: texts.sent,
799
+ submitHint: texts.submitHint,
800
+ submitLgtm: texts.submitLgtm,
801
+ submitPlain: texts.submitPlain,
802
+ rawSummary: texts.rawSummary,
803
+ commentPlaceholder: texts.commentPlaceholder,
804
+ addComment: texts.addComment,
805
+ cancel: texts.cancel,
806
+ noNewlineTitle: texts.noNewlineTitle,
807
+ openerClosed: texts.openerClosed,
808
+ noSession: texts.noSession
809
+ };
810
+ }
811
+ function buildCommentReport(comments, kind, lang = "zh") {
812
+ const texts = CR_TEXTS[lang];
813
+ const header = kind === "lgtm" ? texts.reportLgtmHeader(comments.length) : texts.reportCommentHeader(comments.length);
734
814
  const items = comments.map((comment, index) => {
735
815
  const lines = comment.text.split("\n");
736
816
  const first = `${index + 1}. ${comment.path}:${comment.lineNo} \u2014 ${lines[0]}`;
@@ -747,9 +827,9 @@ function tokensToHtml(tokens) {
747
827
  }
748
828
  return out;
749
829
  }
750
- function cellHtml(text, side, noEol, diffRange, lang) {
830
+ function cellHtml(text, side, noEol, diffRange, lang, noNewlineTitle) {
751
831
  if (text === void 0) return `<div class="dsh-cr-cell dsh-cr-cell-${side}"></div>`;
752
- const mark = noEol === true ? '<span class="dsh-cr-nonl" title="\u8BE5\u4FA7\u6587\u4EF6\u672B\u5C3E\u65E0\u6362\u884C\u7B26">\u23CE</span>' : "";
832
+ const mark = noEol === true ? `<span class="dsh-cr-nonl" title="${escapeHtml(noNewlineTitle)}">\u23CE</span>` : "";
753
833
  let body;
754
834
  if (diffRange === null) {
755
835
  body = lang === null ? escapeHtml(text) : tokensToHtml(tokenizeLine(text, lang));
@@ -765,7 +845,7 @@ function cellHtml(text, side, noEol, diffRange, lang) {
765
845
  }
766
846
  return `<div class="dsh-cr-cell dsh-cr-cell-${side}">${body}${mark}</div>`;
767
847
  }
768
- function rowHtml(row, lang, path) {
848
+ function rowHtml(row, lang, path, noNewlineTitle) {
769
849
  if (row.kind === "gap") {
770
850
  return `<div class="dsh-cr-gaprow">${escapeHtml(row.gapText ?? "")}</div>`;
771
851
  }
@@ -775,19 +855,21 @@ function rowHtml(row, lang, path) {
775
855
  };
776
856
  const line = `<div class="dsh-cr-row dsh-cr-${row.kind}">`;
777
857
  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>";
858
+ 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
859
  }
780
860
  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>";
861
+ 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
862
  }
783
863
  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>";
864
+ 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
865
  }
786
866
  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>";
867
+ 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
868
  }
789
- function buildStandaloneHtml(files, theme, rawText) {
869
+ function buildStandaloneHtml(files, theme, rawText, lang = "zh") {
870
+ const texts = CR_TEXTS[lang];
790
871
  const stats = countStats(files);
872
+ const statsText = texts.stats(stats.added, stats.removed, stats.files);
791
873
  const rootVars = Object.entries(theme).map(([name, value]) => `${name}: ${value};`).join(" ");
792
874
  const rawJson = JSON.stringify(rawText).replace(/</g, "\\u003c");
793
875
  const nav = files.map((file, index) => `<a class="navitem" href="#file-${index}">${escapeHtml(displayPath(file.newPath) || displayPath(file.oldPath))}</a>`).join("");
@@ -796,18 +878,18 @@ function buildStandaloneHtml(files, theme, rawText) {
796
878
  const newPath = displayPath(file.newPath);
797
879
  const path = oldPath !== "" && oldPath !== newPath ? `${oldPath} \u2192 ${newPath}` : newPath === "" ? oldPath : newPath;
798
880
  const meta = file.meta.filter((m) => !m.startsWith("index "));
799
- const lang = langFor(newPath) ?? langFor(oldPath);
881
+ const lang2 = langFor(newPath) ?? langFor(oldPath);
800
882
  const commentPath = newPath !== "" && newPath !== "/dev/null" ? newPath : oldPath;
801
- const rows = file.rows.length === 0 ? `<div class="dsh-cr-gaprow">${escapeHtml(file.meta.join(" \xB7 ") || file.status)}</div>` : file.rows.map((row) => rowHtml(row, lang, commentPath)).join("");
802
- const head = `<header class="filehead"><span class="badge">${STANDALONE_STATUS_LABEL[file.status]}</span><span class="path">${escapeHtml(path)}</span>` + (meta.length > 0 ? `<span class="meta">${escapeHtml(meta.join(" \xB7 "))}</span>` : "") + "</header>";
883
+ 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("");
884
+ 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
885
  return `<section class="file" id="file-${index}">${head}<div class="dsh-cr-grid">${rows}</div></section>`;
804
886
  }).join("");
805
887
  return `<!doctype html>
806
- <html lang="zh-CN">
888
+ <html lang="${lang === "zh" ? "zh-CN" : "en"}">
807
889
  <head>
808
890
  <meta charset="utf-8">
809
891
  <meta name="viewport" content="width=device-width, initial-scale=1">
810
- <title>Code Review \xB7 +${stats.added} \u2212${stats.removed} \xB7 ${stats.files} \u4E2A\u6587\u4EF6</title>
892
+ <title>Code Review \xB7 ${statsText}</title>
811
893
  <style>
812
894
  :root { ${rootVars} }
813
895
  ${STANDALONE_CSS}
@@ -815,21 +897,21 @@ ${STANDALONE_CSS}
815
897
  </head>
816
898
  <body>
817
899
  <header class="topbar">
818
- <span class="stats">+${stats.added} \u2212${stats.removed} \xB7 ${stats.files} \u4E2A\u6587\u4EF6</span>
900
+ <span class="stats">${statsText}</span>
819
901
  <span class="topbar-actions">
820
- <button class="btn" id="submit-comments" type="button">\u63D0\u4EA4\u8BC4\u8BBA</button>
821
- <button class="btn" id="copy-raw" type="button">\u590D\u5236\u539F\u59CB diff</button>
902
+ <button class="btn" id="submit-comments" type="button">${texts.submitComments}</button>
903
+ <button class="btn" id="copy-raw" type="button">${texts.copyRaw}</button>
822
904
  </span>
823
905
  </header>
824
906
  <div class="submit-panel" id="submit-panel" hidden>
825
- <span class="submit-hint">\u628A\u5168\u90E8\u8BC4\u8BBA\u63D0\u4EA4\u5230 DSH \u5BF9\u8BDD\u6846:</span>
826
- <button class="btn" id="submit-lgtm" type="button">Looks Good To Me \u2713</button>
827
- <button class="btn" id="submit-plain" type="button">\u4EC5\u63D0\u4EA4\u8BC4\u8BBA</button>
907
+ <span class="submit-hint">${texts.submitHint}</span>
908
+ <button class="btn" id="submit-lgtm" type="button">${texts.submitLgtm}</button>
909
+ <button class="btn" id="submit-plain" type="button">${texts.submitPlain}</button>
828
910
  </div>
829
911
  <nav class="sidenav">${nav}</nav>
830
912
  <main>${sections}</main>
831
913
  <div class="rawwrap">
832
- <details><summary class="rawsummary">\u539F\u59CB diff \u6587\u672C</summary>
914
+ <details><summary class="rawsummary">${texts.rawSummary}</summary>
833
915
  <pre class="rawpre">${escapeHtml(rawText)}</pre>
834
916
  </details>
835
917
  </div>
@@ -839,8 +921,8 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
839
921
  if (navigator.clipboard && navigator.clipboard.writeText) {
840
922
  navigator.clipboard.writeText(raw).then(function () {
841
923
  var btn = event.currentTarget
842
- btn.textContent = '\u5DF2\u590D\u5236'
843
- setTimeout(function () { btn.textContent = '\u590D\u5236\u539F\u59CB diff' }, 1200)
924
+ btn.textContent = T.copied
925
+ setTimeout(function () { btn.textContent = T.copyRaw }, 1200)
844
926
  })
845
927
  }
846
928
  })
@@ -848,6 +930,7 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
848
930
  <script>
849
931
  /* \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
932
  (function () {
933
+ var T = ${JSON.stringify(stringTexts(texts))}
851
934
  var comments = []
852
935
  var draft = null
853
936
  var submitBtn = document.getElementById('submit-comments')
@@ -860,7 +943,7 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
860
943
  }
861
944
  function syncSubmit() {
862
945
  if (!submitBtn) return
863
- submitBtn.textContent = comments.length === 0 ? '\u63D0\u4EA4\u8BC4\u8BBA' : '\u63D0\u4EA4\u8BC4\u8BBA (' + comments.length + ')'
946
+ submitBtn.textContent = comments.length === 0 ? T.submitComments : T.submitComments + ' (' + comments.length + ')'
864
947
  }
865
948
  function sameLine(a, b) {
866
949
  return a.path === b.path && a.lineNo === b.lineNo && a.side === b.side
@@ -909,10 +992,10 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
909
992
  existing.forEach(function (c) { html += '<div class="comment-item">' + esc(c.text) + '</div>' })
910
993
  html += '</div>'
911
994
  }
912
- html += '<textarea class="comment-input" placeholder="\u5728\u6B64\u884C\u6DFB\u52A0\u8BC4\u8BBA\u2026"></textarea>'
995
+ html += '<textarea class="comment-input" placeholder="' + T.commentPlaceholder + '"></textarea>'
913
996
  html += '<div class="comment-actions">'
914
- + '<button class="btn" type="button" data-act="save">\u6DFB\u52A0\u8BC4\u8BBA</button>'
915
- + '<button class="btn" type="button" data-act="cancel">\u53D6\u6D88</button>'
997
+ + '<button class="btn" type="button" data-act="save">' + T.addComment + '</button>'
998
+ + '<button class="btn" type="button" data-act="cancel">' + T.cancel + '</button>'
916
999
  + '</div>'
917
1000
  draft.innerHTML = html
918
1001
  num.parentElement.insertAdjacentElement('afterend', draft)
@@ -939,7 +1022,7 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
939
1022
  if (comments.length === 0) return
940
1023
  var target = window.opener
941
1024
  if (!target) {
942
- alert('\u539F\u6765\u7684 DSH \u9875\u9762\u5DF2\u5173\u95ED,\u65E0\u6CD5\u56DE\u4F20\u8BC4\u8BBA')
1025
+ alert(T.openerClosed)
943
1026
  return
944
1027
  }
945
1028
  target.postMessage({
@@ -974,9 +1057,9 @@ document.getElementById('copy-raw').addEventListener('click', function (event) {
974
1057
  var data = event.data
975
1058
  if (!data || data.type !== 'dsh-code-review-ack') return
976
1059
  if (data.ok === false) {
977
- alert('\u672A\u80FD\u5199\u5165 DSH \u5BF9\u8BDD\u6846:\u5F53\u524D\u6CA1\u6709\u6253\u5F00\u4E2D\u7684\u4F1A\u8BDD')
1060
+ alert(T.noSession)
978
1061
  } else {
979
- submitBtn.textContent = '\u5DF2\u53D1\u9001 \u2713'
1062
+ submitBtn.textContent = T.sent
980
1063
  setTimeout(syncSubmit, 3000)
981
1064
  }
982
1065
  })
@@ -1050,10 +1133,18 @@ function collectThemeVars() {
1050
1133
  }
1051
1134
  return out;
1052
1135
  }
1136
+ var localeService = null;
1137
+ function detectLang() {
1138
+ if (localeService !== null) {
1139
+ return localeService.getLocale().active === "en" ? "en" : "zh";
1140
+ }
1141
+ const nav = navigator.languages?.[0] ?? navigator.language ?? "";
1142
+ return nav.toLowerCase().startsWith("zh") ? "zh" : "en";
1143
+ }
1053
1144
  function openReviewTab(text) {
1054
1145
  const files = parseGitDiff(text);
1055
1146
  if (files.length === 0) return;
1056
- const html = buildStandaloneHtml(files, collectThemeVars(), text);
1147
+ const html = buildStandaloneHtml(files, collectThemeVars(), text, detectLang());
1057
1148
  const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
1058
1149
  for (const win2 of reviewWindows) {
1059
1150
  if (win2 === null || win2.closed) reviewWindows.delete(win2);
@@ -1084,7 +1175,7 @@ function handleReviewMessage(event) {
1084
1175
  const comments = sanitizeComments(data.comments);
1085
1176
  if (comments.length === 0) return;
1086
1177
  const kind = data.kind === "lgtm" ? "lgtm" : "comment";
1087
- const report = buildCommentReport(comments, kind);
1178
+ const report = buildCommentReport(comments, kind, detectLang());
1088
1179
  const source = event.source;
1089
1180
  if (composerWriter !== null) {
1090
1181
  composerWriter(report);
@@ -1094,13 +1185,14 @@ function handleReviewMessage(event) {
1094
1185
  }
1095
1186
  }
1096
1187
  function ensureButtons(host, getText, fold) {
1188
+ const texts = CR_TEXTS[detectLang()];
1097
1189
  if (host.querySelector("[data-dsh-cr-btn]") === null) {
1098
1190
  const btn = document.createElement("button");
1099
1191
  btn.type = "button";
1100
1192
  btn.className = "dsh-cr-openbtn";
1101
1193
  btn.dataset.dshCrBtn = "";
1102
- btn.textContent = "\u29C9 \u65B0\u6807\u7B7E\u9875\u6253\u5F00";
1103
- btn.title = "\u5728\u72EC\u7ACB\u7684\u6D4F\u89C8\u5668\u6807\u7B7E\u9875\u4E2D\u6253\u5F00 Code Review \u89C6\u56FE";
1194
+ btn.textContent = texts.openTab;
1195
+ btn.title = texts.openTabTitle;
1104
1196
  btn.addEventListener("click", (event) => {
1105
1197
  event.stopPropagation();
1106
1198
  event.preventDefault();
@@ -1114,8 +1206,8 @@ function ensureButtons(host, getText, fold) {
1114
1206
  btn.type = "button";
1115
1207
  btn.className = "dsh-cr-openbtn";
1116
1208
  btn.dataset.dshCrFold = "";
1117
- btn.textContent = fold.folded ? "\u5C55\u5F00" : "\u6536\u8D77";
1118
- btn.title = fold.folded ? "\u5C55\u5F00\u539F\u59CB diff \u6587\u672C" : "\u6536\u8D77\u539F\u59CB diff \u6587\u672C";
1209
+ btn.textContent = fold.folded ? texts.expand : texts.collapse;
1210
+ btn.title = fold.folded ? texts.expandTitle : texts.collapseTitle;
1119
1211
  btn.addEventListener("click", (event) => {
1120
1212
  event.stopPropagation();
1121
1213
  event.preventDefault();
@@ -1361,11 +1453,18 @@ var DiffScanner = class {
1361
1453
  this.applyFoldState(el, st);
1362
1454
  const host = buttonHostFor(el);
1363
1455
  const btn = host?.querySelector("[data-dsh-cr-fold]");
1364
- if (btn instanceof HTMLElement) btn.textContent = st.folded ? "\u5C55\u5F00" : "\u6536\u8D77";
1456
+ if (btn instanceof HTMLElement) {
1457
+ const texts = CR_TEXTS[detectLang()];
1458
+ btn.textContent = st.folded ? texts.expand : texts.collapse;
1459
+ }
1365
1460
  }
1366
1461
  };
1367
- var inject = ["slots"];
1462
+ var inject = ["slots", "locale"];
1368
1463
  function apply(ctx) {
1464
+ const locale = ctx.locale;
1465
+ if (typeof locale === "object" && locale !== null && typeof locale.getLocale === "function") {
1466
+ localeService = locale;
1467
+ }
1369
1468
  ctx.slots.inject(
1370
1469
  "shell.overlay",
1371
1470
  () => 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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\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, '&amp;').replace(/</g, '&lt;')\n .replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\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(\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, '&amp;').replace(/</g, '&lt;')\n .replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\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.1",
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",
3
+ "version": "0.1.2",
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
  },