@diegopetrucci/pi-extensions 0.1.73 → 0.1.76
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/LICENSE +2 -0
- package/README.md +9 -4
- package/extensions/agent-workflow-audit/index.ts +1398 -1009
- package/extensions/agent-workflow-audit/package.json +1 -1
- package/extensions/annotate-git-diff/README.md +3 -1
- package/extensions/annotate-git-diff/clipboard.ts +109 -100
- package/extensions/annotate-git-diff/git.ts +853 -767
- package/extensions/annotate-git-diff/glimpseui.d.ts +87 -78
- package/extensions/annotate-git-diff/index.ts +712 -671
- package/extensions/annotate-git-diff/package.json +1 -1
- package/extensions/annotate-git-diff/prompt.ts +45 -45
- package/extensions/annotate-git-diff/quiet-glimpse.ts +161 -155
- package/extensions/annotate-git-diff/review-server.ts +337 -311
- package/extensions/annotate-git-diff/types.ts +132 -132
- package/extensions/annotate-git-diff/ui.ts +27 -27
- package/extensions/annotate-git-diff/watch.ts +74 -74
- package/extensions/annotate-git-diff/web/app.js +1947 -1884
- package/extensions/annotate-git-diff/web/index.html +5 -6
- package/extensions/annotate-last-message/README.md +1 -1
- package/extensions/annotate-last-message/glimpseui.d.ts +87 -78
- package/extensions/annotate-last-message/index.ts +210 -206
- package/extensions/annotate-last-message/package.json +1 -1
- package/extensions/annotate-last-message/prompt.ts +87 -66
- package/extensions/annotate-last-message/quiet-glimpse.ts +161 -155
- package/extensions/annotate-last-message/session.ts +94 -89
- package/extensions/annotate-last-message/theme.ts +199 -193
- package/extensions/annotate-last-message/types.ts +23 -23
- package/extensions/annotate-last-message/ui.ts +27 -25
- package/extensions/annotate-last-message/web/app.js +4 -7
- package/extensions/annotate-last-message/web/md-renderer.js +1 -1
- package/extensions/brrr/README.md +1 -1
- package/extensions/brrr/index.ts +213 -191
- package/extensions/brrr/package.json +1 -1
- package/extensions/claude-fast/README.md +5 -79
- package/extensions/claude-fast/index.ts +31 -289
- package/extensions/claude-fast/package.json +2 -3
- package/extensions/code-reviewer/README.md +1 -1
- package/extensions/code-reviewer/index.ts +1598 -1350
- package/extensions/code-reviewer/package.json +1 -1
- package/extensions/confirm-destructive/README.md +1 -1
- package/extensions/confirm-destructive/index.ts +55 -51
- package/extensions/confirm-destructive/package.json +1 -1
- package/extensions/context-cap/index.ts +312 -304
- package/extensions/context-cap/package.json +1 -1
- package/extensions/context-inspector/index.ts +1211 -1064
- package/extensions/context-inspector/package.json +1 -1
- package/extensions/contrarian/index.ts +1854 -1735
- package/extensions/contrarian/package.json +1 -1
- package/extensions/dirty-repo-guard/index.ts +41 -41
- package/extensions/dynamic-context-pruning/README.md +6 -38
- package/extensions/dynamic-context-pruning/index.ts +2124 -1980
- package/extensions/dynamic-context-pruning/package.json +1 -1
- package/extensions/fast/README.md +71 -21
- package/extensions/fast/index.ts +513 -313
- package/extensions/fast/package.json +1 -1
- package/extensions/git-footer/README.md +1 -1
- package/extensions/git-footer/index.ts +458 -411
- package/extensions/git-footer/package.json +2 -3
- package/extensions/gnosis/index.ts +180 -130
- package/extensions/gnosis/package.json +1 -1
- package/extensions/inline-bash/index.ts +88 -88
- package/extensions/inline-bash/package.json +1 -1
- package/extensions/librarian/index.ts +1669 -1419
- package/extensions/librarian/package.json +1 -1
- package/extensions/minimal-footer/index.ts +994 -1012
- package/extensions/minimal-footer/openai-usage.ts +195 -190
- package/extensions/notify/index.ts +222 -230
- package/extensions/notify/package.json +1 -1
- package/extensions/openai-fast/README.md +5 -73
- package/extensions/openai-fast/index.ts +32 -272
- package/extensions/openai-fast/package.json +2 -3
- package/extensions/oracle/index.ts +1731 -1625
- package/extensions/oracle/package.json +1 -1
- package/extensions/permission-gate/index.ts +658 -608
- package/extensions/permission-gate/package.json +1 -1
- package/extensions/permission-gate/powershell-safety.ts +840 -793
- package/extensions/project-mcp-json/.pi-fleet-tested-version +1 -0
- package/extensions/project-mcp-json/README.md +199 -0
- package/extensions/project-mcp-json/config.ts +564 -0
- package/extensions/project-mcp-json/index.ts +119 -0
- package/extensions/project-mcp-json/package.json +38 -0
- package/extensions/quiet-tools/index.ts +282 -264
- package/extensions/review/README.md +2 -2
- package/extensions/review/index.ts +1745 -1872
- package/extensions/review/package.json +1 -1
- package/extensions/todo/index.ts +284 -265
- package/extensions/triage-comments/README.md +2 -2
- package/extensions/triage-comments/index.ts +2294 -1882
- package/extensions/triage-comments/package.json +1 -1
- package/package.json +6 -3
- package/assets/social-preview.png +0 -0
- package/extensions/claude-fast/claude-fast.example.json +0 -4
- package/extensions/openai-fast/openai-fast.example.json +0 -4
|
@@ -2,43 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
let suppressAutoSubmitOnClose = false;
|
|
4
4
|
|
|
5
|
-
const reviewData = JSON.parse(
|
|
5
|
+
const reviewData = JSON.parse(
|
|
6
|
+
document.getElementById("annotate-git-diff-data").textContent || "{}",
|
|
7
|
+
);
|
|
6
8
|
const reviewAssetConfig = window.__reviewAssetConfig || {};
|
|
7
9
|
if (!Array.isArray(reviewData.files)) reviewData.files = [];
|
|
8
10
|
if (!Array.isArray(reviewData.commits)) reviewData.commits = [];
|
|
9
11
|
|
|
10
12
|
const defaultScope = reviewData.files.some((file) => file.inGitDiff)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
? "branch"
|
|
14
|
+
: reviewData.commits.length > 0
|
|
15
|
+
? "commits"
|
|
16
|
+
: "all";
|
|
15
17
|
|
|
16
18
|
const state = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
activeFileId: null,
|
|
20
|
+
currentScope: defaultScope,
|
|
21
|
+
selectedCommitSha: reviewData.commits[0]?.sha ?? null,
|
|
22
|
+
commitFilesBySha: {}, // sha -> ReviewFile[]
|
|
23
|
+
commitRequestIds: {}, // sha -> requestId (in-flight)
|
|
24
|
+
commitErrors: {}, // sha -> error message
|
|
25
|
+
reviewDataRequestId: null,
|
|
26
|
+
reviewDataRequestStartedAt: null,
|
|
27
|
+
comments: [],
|
|
28
|
+
overallComment: "",
|
|
29
|
+
hideUnchanged: true,
|
|
30
|
+
wrapLines: true,
|
|
31
|
+
collapsedDirs: {},
|
|
32
|
+
reviewedFiles: {},
|
|
33
|
+
scrollPositions: {},
|
|
34
|
+
sidebarCollapsed: false,
|
|
35
|
+
fileFilter: "",
|
|
36
|
+
fileContents: {},
|
|
37
|
+
fileErrors: {},
|
|
38
|
+
pendingRequestIds: {},
|
|
39
|
+
lastWorkingTreeLoadAt: null,
|
|
40
|
+
localChangesDetected: false,
|
|
41
|
+
lastLocalChangeDetectedAt: null,
|
|
42
|
+
lastLocalChangeObservedAt: null,
|
|
43
|
+
assetInitFailed: false,
|
|
42
44
|
};
|
|
43
45
|
|
|
44
46
|
const sidebarEl = document.getElementById("sidebar");
|
|
@@ -79,13 +81,13 @@ const assetFailureDetailEl = document.getElementById("asset-failure-detail");
|
|
|
79
81
|
|
|
80
82
|
// Octicon-style inline SVGs used by the GitHub-style sidebar tree.
|
|
81
83
|
const OCTICON_CHEVRON_DOWN =
|
|
82
|
-
|
|
84
|
+
'<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M12.78 6.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 7.28a.749.749 0 1 1 1.06-1.06L8 9.939l3.72-3.719a.749.749 0 0 1 1.06 0Z"/></svg>';
|
|
83
85
|
const OCTICON_CHEVRON_RIGHT =
|
|
84
|
-
|
|
86
|
+
'<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><path d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z"/></svg>';
|
|
85
87
|
const OCTICON_FOLDER =
|
|
86
|
-
|
|
88
|
+
'<svg class="gh-dir-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M1.75 1A1.75 1.75 0 0 0 0 2.75v10.5C0 14.216.784 15 1.75 15h12.5A1.75 1.75 0 0 0 16 13.25v-8.5A1.75 1.75 0 0 0 14.25 3H7.5a.25.25 0 0 1-.2-.1l-.9-1.2A1.75 1.75 0 0 0 5 1H1.75Z"/></svg>';
|
|
87
89
|
const OCTICON_FILE =
|
|
88
|
-
|
|
90
|
+
'<svg class="gh-file-icon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M2 1.75C2 .784 2.784 0 3.75 0h6.586c.464 0 .909.184 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v9.586A1.75 1.75 0 0 1 13.25 16h-9.5A1.75 1.75 0 0 1 2 14.25V1.75Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h9.5a.25.25 0 0 0 .25-.25V6h-2.75A1.75 1.75 0 0 1 9 4.25V1.5H3.75Zm6.75.5v2.25c0 .138.112.25.25.25h2.25a.25.25 0 0 0 .177-.427L10.927 1.573a.25.25 0 0 0-.427.177Z"/></svg>';
|
|
89
91
|
|
|
90
92
|
repoRootEl.textContent = reviewData.repoRoot || "";
|
|
91
93
|
windowTitleEl.textContent = "Review";
|
|
@@ -105,372 +107,377 @@ let editorResizeObserver = null;
|
|
|
105
107
|
let requestSequence = 0;
|
|
106
108
|
|
|
107
109
|
function escapeHtml(value) {
|
|
108
|
-
|
|
110
|
+
return String(value)
|
|
111
|
+
.replace(/&/g, "&")
|
|
112
|
+
.replace(/</g, "<")
|
|
113
|
+
.replace(/>/g, ">")
|
|
114
|
+
.replace(/"/g, """);
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
function formatAssetErrorDetail(error) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
if (error == null) return "";
|
|
119
|
+
if (typeof error === "string") return error;
|
|
120
|
+
if (error instanceof Error) return error.stack || error.message;
|
|
121
|
+
try {
|
|
122
|
+
return JSON.stringify(error, null, 2);
|
|
123
|
+
} catch {
|
|
124
|
+
return String(error);
|
|
125
|
+
}
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
function showAssetFailure(title, message, detail = "") {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
129
|
+
state.assetInitFailed = true;
|
|
130
|
+
if (editorContainerEl) {
|
|
131
|
+
editorContainerEl.dataset.assetFailure = "true";
|
|
132
|
+
}
|
|
133
|
+
if (assetFailurePanelEl) {
|
|
134
|
+
assetFailurePanelEl.setAttribute("aria-hidden", "false");
|
|
135
|
+
}
|
|
136
|
+
if (assetFailureTitleEl) {
|
|
137
|
+
assetFailureTitleEl.textContent = title;
|
|
138
|
+
}
|
|
139
|
+
if (assetFailureMessageEl) {
|
|
140
|
+
assetFailureMessageEl.textContent = message;
|
|
141
|
+
}
|
|
142
|
+
if (assetFailureDetailEl) {
|
|
143
|
+
const detailText = String(detail || "").trim();
|
|
144
|
+
assetFailureDetailEl.textContent = detailText;
|
|
145
|
+
assetFailureDetailEl.hidden = detailText.length === 0;
|
|
146
|
+
}
|
|
147
|
+
hideBinaryPreview();
|
|
148
|
+
modeHintEl.textContent = message;
|
|
149
|
+
overallCommentButton.disabled = true;
|
|
150
|
+
fileCommentButton.disabled = true;
|
|
151
|
+
toggleReviewedButton.disabled = true;
|
|
152
|
+
toggleWrapButton.disabled = true;
|
|
153
|
+
toggleUnchangedButton.style.display = "none";
|
|
154
|
+
submitButton.disabled = true;
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
function clearAssetFailure() {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
158
|
+
state.assetInitFailed = false;
|
|
159
|
+
if (editorContainerEl) {
|
|
160
|
+
editorContainerEl.dataset.assetFailure = "false";
|
|
161
|
+
}
|
|
162
|
+
if (assetFailurePanelEl) {
|
|
163
|
+
assetFailurePanelEl.setAttribute("aria-hidden", "true");
|
|
164
|
+
}
|
|
165
|
+
if (assetFailureDetailEl) {
|
|
166
|
+
assetFailureDetailEl.textContent = "";
|
|
167
|
+
assetFailureDetailEl.hidden = true;
|
|
168
|
+
}
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
function inferLanguage(path) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
172
|
+
if (!path) return "plaintext";
|
|
173
|
+
const lower = path.toLowerCase();
|
|
174
|
+
const fileName = lower.split(/[\\/]/).pop() || lower;
|
|
175
|
+
|
|
176
|
+
if (fileName === "dockerfile" || fileName === "containerfile") return "dockerfile";
|
|
177
|
+
|
|
178
|
+
const extensionLanguagePairs = [
|
|
179
|
+
[[".ts", ".tsx", ".mts", ".cts"], "typescript"],
|
|
180
|
+
[[".js", ".jsx", ".mjs", ".cjs"], "javascript"],
|
|
181
|
+
[[".json", ".jsonc", ".json5"], "json"],
|
|
182
|
+
[[".md"], "markdown"],
|
|
183
|
+
[[".mdx"], "mdx"],
|
|
184
|
+
[[".css"], "css"],
|
|
185
|
+
[[".scss", ".sass"], "scss"],
|
|
186
|
+
[[".less"], "less"],
|
|
187
|
+
[[".html", ".htm", ".vue", ".svelte"], "html"],
|
|
188
|
+
[[".xml", ".xhtml", ".svg"], "xml"],
|
|
189
|
+
[[".sh", ".bash", ".zsh", ".fish", ".env"], "shell"],
|
|
190
|
+
[[".ps1", ".psm1", ".psd1"], "powershell"],
|
|
191
|
+
[[".bat", ".cmd"], "bat"],
|
|
192
|
+
[[".yml", ".yaml"], "yaml"],
|
|
193
|
+
[[".rs"], "rust"],
|
|
194
|
+
[[".java"], "java"],
|
|
195
|
+
[[".kt", ".kts"], "kotlin"],
|
|
196
|
+
[[".swift"], "swift"],
|
|
197
|
+
[[".m", ".mm"], "objective-c"],
|
|
198
|
+
[[".py", ".pyw"], "python"],
|
|
199
|
+
[[".go"], "go"],
|
|
200
|
+
[[".c"], "c"],
|
|
201
|
+
[[".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"], "cpp"],
|
|
202
|
+
[[".cs"], "csharp"],
|
|
203
|
+
[[".php"], "php"],
|
|
204
|
+
[[".rb"], "ruby"],
|
|
205
|
+
[[".sql"], "sql"],
|
|
206
|
+
[[".mysql"], "mysql"],
|
|
207
|
+
[[".pgsql"], "pgsql"],
|
|
208
|
+
[[".graphql", ".gql"], "graphql"],
|
|
209
|
+
[[".dart"], "dart"],
|
|
210
|
+
[[".lua"], "lua"],
|
|
211
|
+
[[".r"], "r"],
|
|
212
|
+
[[".pl", ".pm"], "perl"],
|
|
213
|
+
[[".scala", ".sc"], "scala"],
|
|
214
|
+
[[".clj", ".cljs", ".cljc"], "clojure"],
|
|
215
|
+
[[".ex", ".exs"], "elixir"],
|
|
216
|
+
[[".fs", ".fsx", ".fsi"], "fsharp"],
|
|
217
|
+
[[".vb"], "vb"],
|
|
218
|
+
[[".tf", ".tfvars", ".hcl"], "hcl"],
|
|
219
|
+
[[".proto"], "protobuf"],
|
|
220
|
+
[[".sol"], "solidity"],
|
|
221
|
+
[[".wgsl"], "wgsl"],
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
for (const [extensions, language] of extensionLanguagePairs) {
|
|
225
|
+
if (extensions.some((extension) => lower.endsWith(extension))) return language;
|
|
226
|
+
}
|
|
227
|
+
return "plaintext";
|
|
222
228
|
}
|
|
223
229
|
|
|
224
230
|
function scopeLabel(scope) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
switch (scope) {
|
|
232
|
+
case "branch":
|
|
233
|
+
return "Branch";
|
|
234
|
+
case "commits":
|
|
235
|
+
return "Commits";
|
|
236
|
+
default:
|
|
237
|
+
return "All files";
|
|
238
|
+
}
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
function commitInfoBySha(sha) {
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
if (!sha) return null;
|
|
243
|
+
return reviewData.commits.find((c) => c.sha === sha) ?? null;
|
|
238
244
|
}
|
|
239
245
|
|
|
240
246
|
function selectedCommitInfo() {
|
|
241
|
-
|
|
247
|
+
return commitInfoBySha(state.selectedCommitSha);
|
|
242
248
|
}
|
|
243
249
|
|
|
244
250
|
function selectedCommitKind() {
|
|
245
|
-
|
|
251
|
+
return selectedCommitInfo()?.kind ?? "commit";
|
|
246
252
|
}
|
|
247
253
|
|
|
248
254
|
function hasRepositoryHead() {
|
|
249
|
-
|
|
255
|
+
return reviewData.repositoryHasHead === true;
|
|
250
256
|
}
|
|
251
257
|
|
|
252
258
|
function workingTreeOriginalLabel() {
|
|
253
|
-
|
|
259
|
+
return hasRepositoryHead() ? "HEAD" : "Empty tree";
|
|
254
260
|
}
|
|
255
261
|
|
|
256
262
|
function workingTreeRangeLabel() {
|
|
257
|
-
|
|
263
|
+
return hasRepositoryHead() ? "HEAD → working tree" : "Empty tree → working tree";
|
|
258
264
|
}
|
|
259
265
|
|
|
260
266
|
function isWorkingTreeCommit(sha) {
|
|
261
|
-
|
|
267
|
+
return commitInfoBySha(sha)?.kind === "working-tree";
|
|
262
268
|
}
|
|
263
269
|
|
|
264
270
|
function isSelectedWorkingTreeCommit() {
|
|
265
|
-
|
|
271
|
+
return state.currentScope === "commits" && isWorkingTreeCommit(state.selectedCommitSha);
|
|
266
272
|
}
|
|
267
273
|
|
|
268
274
|
function scopeHint(scope) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
275
|
+
if (state.localChangesDetected) {
|
|
276
|
+
return "Local file changes detected. Click Refresh changes to reload the diff when you are ready.";
|
|
277
|
+
}
|
|
278
|
+
const baseRefLabel = reviewData.branchBaseRef || "the selected base";
|
|
279
|
+
switch (scope) {
|
|
280
|
+
case "branch":
|
|
281
|
+
return `Review current branch changes against ${baseRefLabel}. This view can include uncommitted working tree edits. Hover or click line numbers in the gutter to add an inline comment.`;
|
|
282
|
+
case "commits": {
|
|
283
|
+
const info = selectedCommitInfo();
|
|
284
|
+
if (!info) return "Pick a branch commit from the list to review its diff.";
|
|
285
|
+
if (info.kind === "working-tree") {
|
|
286
|
+
return hasRepositoryHead()
|
|
287
|
+
? "Review uncommitted working tree changes against HEAD. Use Refresh changes when local edits are detected."
|
|
288
|
+
: "Review uncommitted working tree changes against the empty tree in this new repository. Use Refresh changes when local edits are detected.";
|
|
289
|
+
}
|
|
290
|
+
return `Review commit ${info.shortSha} — ${info.subject}`;
|
|
291
|
+
}
|
|
292
|
+
default:
|
|
293
|
+
return "Review repository file snapshots across tracked and untracked files. Unchanged files open as single-file snapshots; changed files remain available in the Branch and Commits scopes for diff-based comments.";
|
|
294
|
+
}
|
|
289
295
|
}
|
|
290
296
|
|
|
291
297
|
function currentModeHint() {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
298
|
+
const file = activeFile();
|
|
299
|
+
if (!file) return scopeHint(state.currentScope);
|
|
300
|
+
if (file.kind === "image") {
|
|
301
|
+
return state.currentScope === "all"
|
|
302
|
+
? "Review the current image snapshot. File-level comments are supported; inline line comments are unavailable for image previews."
|
|
303
|
+
: "Review image changes side by side. File-level comments are supported; inline line comments are unavailable for image previews.";
|
|
304
|
+
}
|
|
305
|
+
if (file.kind === "binary") {
|
|
306
|
+
return "Binary files show side existence instead of a text diff. File-level comments are supported; inline line comments are unavailable.";
|
|
307
|
+
}
|
|
308
|
+
return scopeHint(state.currentScope);
|
|
303
309
|
}
|
|
304
310
|
|
|
305
311
|
function statusLabel(status) {
|
|
306
|
-
|
|
307
|
-
|
|
312
|
+
if (!status) return "";
|
|
313
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
308
314
|
}
|
|
309
315
|
|
|
310
316
|
function statusBadgeClass(status) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
317
|
+
switch (status) {
|
|
318
|
+
case "added":
|
|
319
|
+
return "gh-status gh-status-added";
|
|
320
|
+
case "deleted":
|
|
321
|
+
return "gh-status gh-status-deleted";
|
|
322
|
+
case "renamed":
|
|
323
|
+
return "gh-status gh-status-renamed";
|
|
324
|
+
case "modified":
|
|
325
|
+
return "gh-status gh-status-modified";
|
|
326
|
+
default:
|
|
327
|
+
return "gh-status gh-status-modified";
|
|
328
|
+
}
|
|
323
329
|
}
|
|
324
330
|
|
|
325
331
|
function statusCode(status) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
332
|
+
if (!status) return "";
|
|
333
|
+
if (status === "renamed") return "R";
|
|
334
|
+
return status.charAt(0).toUpperCase();
|
|
329
335
|
}
|
|
330
336
|
|
|
331
337
|
function isFileReviewed(fileId) {
|
|
332
|
-
|
|
338
|
+
return state.reviewedFiles[fileId] === true;
|
|
333
339
|
}
|
|
334
340
|
|
|
335
341
|
function annotateCommentWithCommit(comment) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
342
|
+
if (comment.scope !== "commits") return comment;
|
|
343
|
+
const info = selectedCommitInfo();
|
|
344
|
+
if (!info) return comment;
|
|
345
|
+
return { ...comment, commitSha: info.sha, commitShort: info.shortSha, commitKind: info.kind };
|
|
340
346
|
}
|
|
341
347
|
|
|
342
348
|
function activeFileList() {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
+
if (state.currentScope === "commits") {
|
|
350
|
+
const sha = state.selectedCommitSha;
|
|
351
|
+
if (!sha) return [];
|
|
352
|
+
return state.commitFilesBySha[sha] ?? [];
|
|
353
|
+
}
|
|
354
|
+
return reviewData.files;
|
|
349
355
|
}
|
|
350
356
|
|
|
351
357
|
function getScopedFiles() {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
358
|
+
switch (state.currentScope) {
|
|
359
|
+
case "branch":
|
|
360
|
+
return reviewData.files.filter((file) => file.inGitDiff);
|
|
361
|
+
case "commits":
|
|
362
|
+
return activeFileList();
|
|
363
|
+
default:
|
|
364
|
+
return reviewData.files;
|
|
365
|
+
}
|
|
360
366
|
}
|
|
361
367
|
|
|
362
368
|
function ensureActiveFileForScope() {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
369
|
+
const scopedFiles = getScopedFiles();
|
|
370
|
+
if (scopedFiles.length === 0) {
|
|
371
|
+
state.activeFileId = null;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (scopedFiles.some((file) => file.id === state.activeFileId)) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
state.activeFileId = scopedFiles[0].id;
|
|
372
378
|
}
|
|
373
379
|
|
|
374
380
|
function activeFile() {
|
|
375
|
-
|
|
376
|
-
|
|
381
|
+
const list = activeFileList();
|
|
382
|
+
return list.find((file) => file.id === state.activeFileId) ?? null;
|
|
377
383
|
}
|
|
378
384
|
|
|
379
385
|
function getScopeComparison(file, scope = state.currentScope) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
386
|
+
if (!file) return null;
|
|
387
|
+
if (scope === "branch" || scope === "commits") return file.gitDiff;
|
|
388
|
+
return null;
|
|
383
389
|
}
|
|
384
390
|
|
|
385
391
|
function activeComparison() {
|
|
386
|
-
|
|
392
|
+
return getScopeComparison(activeFile(), state.currentScope);
|
|
387
393
|
}
|
|
388
394
|
|
|
389
395
|
function isAddedOnlyComparison(comparison) {
|
|
390
|
-
|
|
396
|
+
return comparison != null && comparison.status === "added";
|
|
391
397
|
}
|
|
392
398
|
|
|
393
399
|
function activeFileIsAddedOnly() {
|
|
394
|
-
|
|
400
|
+
return isAddedOnlyComparison(activeComparison());
|
|
395
401
|
}
|
|
396
402
|
|
|
397
403
|
function activeFileShowsDiff() {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
+
const file = activeFile();
|
|
405
|
+
if (file == null || file.kind !== "text") return false;
|
|
406
|
+
const comparison = activeComparison();
|
|
407
|
+
if (comparison == null) return false;
|
|
408
|
+
if (isAddedOnlyComparison(comparison)) return false;
|
|
409
|
+
return true;
|
|
404
410
|
}
|
|
405
411
|
|
|
406
412
|
function activeFileUsesSingleEditor() {
|
|
407
|
-
|
|
408
|
-
|
|
413
|
+
const file = activeFile();
|
|
414
|
+
return file != null && file.kind === "text" && activeFileIsAddedOnly();
|
|
409
415
|
}
|
|
410
416
|
|
|
411
417
|
function setTextEditorMode(mode) {
|
|
412
|
-
|
|
413
|
-
|
|
418
|
+
if (!editorContainerEl) return;
|
|
419
|
+
editorContainerEl.dataset.textEditorMode = mode;
|
|
414
420
|
}
|
|
415
421
|
|
|
416
422
|
function activeFileUsesBinaryPreview() {
|
|
417
|
-
|
|
418
|
-
|
|
423
|
+
const file = activeFile();
|
|
424
|
+
return file != null && file.kind !== "text";
|
|
419
425
|
}
|
|
420
426
|
|
|
421
427
|
function fileKindBadgeMarkup(file) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
428
|
+
if (!file || file.kind === "text") return "";
|
|
429
|
+
const label = file.kind === "image" ? "IMG" : "BIN";
|
|
430
|
+
return `<span class="gh-kind-badge">${label}</span>`;
|
|
425
431
|
}
|
|
426
432
|
|
|
427
433
|
function hideBinaryPreview() {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
434
|
+
if (!editorContainerEl || !binaryPreviewEl) return;
|
|
435
|
+
editorContainerEl.dataset.previewActive = "false";
|
|
436
|
+
binaryPreviewEl.dataset.active = "false";
|
|
437
|
+
binaryPreviewEl.innerHTML = "";
|
|
432
438
|
}
|
|
433
439
|
|
|
434
440
|
function previewSideLabel(file, side) {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
441
|
+
if (state.currentScope === "commits") {
|
|
442
|
+
return selectedCommitKind() === "working-tree"
|
|
443
|
+
? side === "original"
|
|
444
|
+
? workingTreeOriginalLabel()
|
|
445
|
+
: "Working tree"
|
|
446
|
+
: side === "original"
|
|
447
|
+
? "Parent"
|
|
448
|
+
: "Commit";
|
|
449
|
+
}
|
|
450
|
+
if (state.currentScope === "branch") {
|
|
451
|
+
if (side === "original")
|
|
452
|
+
return reviewData.branchBaseRef ? `Base (${reviewData.branchBaseRef})` : "Base";
|
|
453
|
+
return file.hasWorkingTreeFile ? "Working tree" : "HEAD";
|
|
454
|
+
}
|
|
455
|
+
return "Snapshot";
|
|
449
456
|
}
|
|
450
457
|
|
|
451
458
|
function renderBinaryPreview(file, contents) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
459
|
+
if (!editorContainerEl || !binaryPreviewEl) return;
|
|
460
|
+
editorContainerEl.dataset.previewActive = "true";
|
|
461
|
+
binaryPreviewEl.dataset.active = "true";
|
|
462
|
+
const requestState = getRequestState(file.id, state.currentScope);
|
|
463
|
+
if (requestState.error) {
|
|
464
|
+
binaryPreviewEl.innerHTML = `<div class="binary-preview-note">Failed to load ${escapeHtml(getScopeDisplayPath(file, state.currentScope))}<br><br>${escapeHtml(requestState.error)}</div>`;
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (requestState.requestId != null && requestState.contents == null) {
|
|
468
|
+
binaryPreviewEl.innerHTML = `<div class="binary-preview-note">Loading preview for ${escapeHtml(getScopeDisplayPath(file, state.currentScope))}...</div>`;
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const showComparison = state.currentScope !== "all";
|
|
473
|
+
const cards = [];
|
|
474
|
+
const pushCard = (label, exists, previewUrl) => {
|
|
475
|
+
const body = exists
|
|
476
|
+
? file.kind === "image" && previewUrl
|
|
477
|
+
? `<img class="binary-preview-image" src="${previewUrl}" alt="${escapeHtml(label)} preview">`
|
|
478
|
+
: `<div class="binary-preview-empty">Binary file present</div>`
|
|
479
|
+
: `<div class="binary-preview-empty">Not present in this side</div>`;
|
|
480
|
+
cards.push(`
|
|
474
481
|
<div class="binary-preview-card">
|
|
475
482
|
<div class="binary-preview-card-header">
|
|
476
483
|
<span>${escapeHtml(label)}</span>
|
|
@@ -479,496 +486,526 @@ function renderBinaryPreview(file, contents) {
|
|
|
479
486
|
<div class="binary-preview-card-body">${body}</div>
|
|
480
487
|
</div>
|
|
481
488
|
`);
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
if (showComparison) {
|
|
492
|
+
pushCard(
|
|
493
|
+
previewSideLabel(file, "original"),
|
|
494
|
+
contents.originalExists,
|
|
495
|
+
contents.originalPreviewUrl,
|
|
496
|
+
);
|
|
497
|
+
pushCard(
|
|
498
|
+
previewSideLabel(file, "modified"),
|
|
499
|
+
contents.modifiedExists,
|
|
500
|
+
contents.modifiedPreviewUrl,
|
|
501
|
+
);
|
|
502
|
+
} else {
|
|
503
|
+
pushCard(
|
|
504
|
+
previewSideLabel(file, "modified"),
|
|
505
|
+
contents.modifiedExists,
|
|
506
|
+
contents.modifiedPreviewUrl,
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const note =
|
|
511
|
+
file.kind === "image"
|
|
512
|
+
? "Image files are rendered directly in the review pane."
|
|
513
|
+
: "Binary files do not have a text diff here. The review pane shows whether each side exists.";
|
|
514
|
+
binaryPreviewEl.innerHTML = `<div class="binary-preview-grid">${cards.join("")}</div><div class="binary-preview-note">${escapeHtml(note)}</div>`;
|
|
496
515
|
}
|
|
497
516
|
|
|
498
517
|
function getScopeFilePath(file) {
|
|
499
|
-
|
|
500
|
-
|
|
518
|
+
const comparison = getScopeComparison(file, state.currentScope);
|
|
519
|
+
return comparison?.newPath || comparison?.oldPath || file?.path || "";
|
|
501
520
|
}
|
|
502
521
|
|
|
503
522
|
function getScopeDisplayPath(file, scope = state.currentScope) {
|
|
504
|
-
|
|
505
|
-
|
|
523
|
+
const comparison = getScopeComparison(file, scope);
|
|
524
|
+
return comparison?.displayPath || file?.path || "";
|
|
506
525
|
}
|
|
507
526
|
|
|
508
527
|
function getFileSearchPath(file) {
|
|
509
|
-
|
|
528
|
+
return file?.path || "";
|
|
510
529
|
}
|
|
511
530
|
|
|
512
531
|
function getBaseName(path) {
|
|
513
|
-
|
|
514
|
-
|
|
532
|
+
const parts = path.split("/");
|
|
533
|
+
return parts[parts.length - 1] || path;
|
|
515
534
|
}
|
|
516
535
|
|
|
517
536
|
function getActiveStatus(file) {
|
|
518
|
-
|
|
519
|
-
|
|
537
|
+
const comparison = getScopeComparison(file, state.currentScope);
|
|
538
|
+
return comparison?.status ?? file?.worktreeStatus ?? null;
|
|
520
539
|
}
|
|
521
540
|
|
|
522
541
|
function normalizeQuery(query) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
542
|
+
return String(query || "")
|
|
543
|
+
.trim()
|
|
544
|
+
.toLowerCase()
|
|
545
|
+
.replace(/\s+/g, "");
|
|
527
546
|
}
|
|
528
547
|
|
|
529
548
|
function scoreSubsequence(query, candidate) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
549
|
+
if (!query) return 0;
|
|
550
|
+
let queryIndex = 0;
|
|
551
|
+
let score = 0;
|
|
552
|
+
let firstMatchIndex = -1;
|
|
553
|
+
let previousMatchIndex = -2;
|
|
554
|
+
|
|
555
|
+
for (let i = 0; i < candidate.length && queryIndex < query.length; i += 1) {
|
|
556
|
+
if (candidate[i] !== query[queryIndex]) continue;
|
|
557
|
+
|
|
558
|
+
if (firstMatchIndex === -1) firstMatchIndex = i;
|
|
559
|
+
score += 10;
|
|
560
|
+
|
|
561
|
+
if (i === previousMatchIndex + 1) {
|
|
562
|
+
score += 8;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const previousChar = i > 0 ? candidate[i - 1] : "";
|
|
566
|
+
if (
|
|
567
|
+
i === 0 ||
|
|
568
|
+
previousChar === "/" ||
|
|
569
|
+
previousChar === "_" ||
|
|
570
|
+
previousChar === "-" ||
|
|
571
|
+
previousChar === "."
|
|
572
|
+
) {
|
|
573
|
+
score += 12;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
previousMatchIndex = i;
|
|
577
|
+
queryIndex += 1;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (queryIndex !== query.length) return -1;
|
|
581
|
+
if (firstMatchIndex >= 0) score += Math.max(0, 20 - firstMatchIndex);
|
|
582
|
+
return score;
|
|
558
583
|
}
|
|
559
584
|
|
|
560
585
|
function getFileSearchScore(query, file) {
|
|
561
|
-
|
|
562
|
-
|
|
586
|
+
const normalizedQuery = normalizeQuery(query);
|
|
587
|
+
if (!normalizedQuery) return 0;
|
|
563
588
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
589
|
+
const path = getFileSearchPath(file).toLowerCase();
|
|
590
|
+
const baseName = getBaseName(path);
|
|
591
|
+
const pathScore = scoreSubsequence(normalizedQuery, path);
|
|
592
|
+
const baseScore = scoreSubsequence(normalizedQuery, baseName);
|
|
593
|
+
let score = Math.max(pathScore, baseScore >= 0 ? baseScore + 40 : -1);
|
|
569
594
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
595
|
+
if (score < 0) return -1;
|
|
596
|
+
if (baseName === normalizedQuery) score += 200;
|
|
597
|
+
else if (baseName.startsWith(normalizedQuery)) score += 120;
|
|
598
|
+
else if (path.includes(normalizedQuery)) score += 35;
|
|
574
599
|
|
|
575
|
-
|
|
600
|
+
return score;
|
|
576
601
|
}
|
|
577
602
|
|
|
578
603
|
function getFilteredFiles() {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
604
|
+
const scopedFiles = getScopedFiles();
|
|
605
|
+
const query = state.fileFilter.trim();
|
|
606
|
+
if (!query) return [...scopedFiles];
|
|
582
607
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
608
|
+
return scopedFiles
|
|
609
|
+
.map((file) => ({ file, score: getFileSearchScore(query, file) }))
|
|
610
|
+
.filter((entry) => entry.score >= 0)
|
|
611
|
+
.sort((a, b) => {
|
|
612
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
613
|
+
return getFileSearchPath(a.file).localeCompare(getFileSearchPath(b.file));
|
|
614
|
+
})
|
|
615
|
+
.map((entry) => entry.file);
|
|
591
616
|
}
|
|
592
617
|
|
|
593
618
|
function collapseTreeNode(node, isRoot = false) {
|
|
594
|
-
|
|
619
|
+
if (node.kind === "file") return node;
|
|
595
620
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
621
|
+
const collapsedChildren = [...node.children.values()].map((child) => collapseTreeNode(child));
|
|
622
|
+
let collapsed = {
|
|
623
|
+
...node,
|
|
624
|
+
children: new Map(collapsedChildren.map((child) => [child.name, child])),
|
|
625
|
+
};
|
|
601
626
|
|
|
602
|
-
|
|
627
|
+
if (isRoot) return collapsed;
|
|
603
628
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
629
|
+
while (collapsed.children.size === 1) {
|
|
630
|
+
const [onlyChild] = collapsed.children.values();
|
|
631
|
+
if (!onlyChild || onlyChild.kind !== "dir") break;
|
|
632
|
+
collapsed = {
|
|
633
|
+
name: `${collapsed.name}/${onlyChild.name}`,
|
|
634
|
+
path: onlyChild.path,
|
|
635
|
+
kind: "dir",
|
|
636
|
+
children: onlyChild.children,
|
|
637
|
+
file: null,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
615
640
|
|
|
616
|
-
|
|
641
|
+
return collapsed;
|
|
617
642
|
}
|
|
618
643
|
|
|
619
644
|
function buildTree(files) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
645
|
+
const root = { name: "", path: "", kind: "dir", children: new Map(), file: null };
|
|
646
|
+
for (const file of files) {
|
|
647
|
+
const path = getFileSearchPath(file);
|
|
648
|
+
const parts = path.split("/");
|
|
649
|
+
let node = root;
|
|
650
|
+
let currentPath = "";
|
|
651
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
652
|
+
const part = parts[i];
|
|
653
|
+
const isLeaf = i === parts.length - 1;
|
|
654
|
+
currentPath = currentPath ? `${currentPath}/${part}` : part;
|
|
655
|
+
if (!node.children.has(part)) {
|
|
656
|
+
node.children.set(part, {
|
|
657
|
+
name: part,
|
|
658
|
+
path: currentPath,
|
|
659
|
+
kind: isLeaf ? "file" : "dir",
|
|
660
|
+
children: new Map(),
|
|
661
|
+
file: isLeaf ? file : null,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
node = node.children.get(part);
|
|
665
|
+
if (isLeaf) node.file = file;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return collapseTreeNode(root, true);
|
|
644
669
|
}
|
|
645
670
|
|
|
646
671
|
function cacheKey(scope, fileId, commitSha = null) {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
672
|
+
if (scope === "commits") {
|
|
673
|
+
return `commits:${commitSha ?? state.selectedCommitSha ?? ""}:${fileId}`;
|
|
674
|
+
}
|
|
675
|
+
return `${scope}:${fileId}`;
|
|
651
676
|
}
|
|
652
677
|
|
|
653
678
|
function scrollKey(scope, fileId, commitSha = null) {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
679
|
+
if (scope === "commits") {
|
|
680
|
+
return `commits:${commitSha ?? state.selectedCommitSha ?? ""}:${fileId}`;
|
|
681
|
+
}
|
|
682
|
+
return `${scope}:${fileId}`;
|
|
658
683
|
}
|
|
659
684
|
|
|
660
685
|
function saveCurrentScrollPosition() {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
686
|
+
if (!diffEditor || !state.activeFileId) return;
|
|
687
|
+
const originalEditor = diffEditor.getOriginalEditor();
|
|
688
|
+
const modifiedEditor = diffEditor.getModifiedEditor();
|
|
689
|
+
state.scrollPositions[scrollKey(state.currentScope, state.activeFileId)] = {
|
|
690
|
+
originalTop: originalEditor.getScrollTop(),
|
|
691
|
+
originalLeft: originalEditor.getScrollLeft(),
|
|
692
|
+
modifiedTop: modifiedEditor.getScrollTop(),
|
|
693
|
+
modifiedLeft: modifiedEditor.getScrollLeft(),
|
|
694
|
+
};
|
|
670
695
|
}
|
|
671
696
|
|
|
672
697
|
function restoreFileScrollPosition() {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
698
|
+
if (!diffEditor || !state.activeFileId) return;
|
|
699
|
+
const scrollState = state.scrollPositions[scrollKey(state.currentScope, state.activeFileId)];
|
|
700
|
+
if (!scrollState) return;
|
|
701
|
+
const originalEditor = diffEditor.getOriginalEditor();
|
|
702
|
+
const modifiedEditor = diffEditor.getModifiedEditor();
|
|
703
|
+
originalEditor.setScrollTop(scrollState.originalTop);
|
|
704
|
+
originalEditor.setScrollLeft(scrollState.originalLeft);
|
|
705
|
+
modifiedEditor.setScrollTop(scrollState.modifiedTop);
|
|
706
|
+
modifiedEditor.setScrollLeft(scrollState.modifiedLeft);
|
|
682
707
|
}
|
|
683
708
|
|
|
684
709
|
function captureScrollState() {
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
710
|
+
if (!diffEditor) return null;
|
|
711
|
+
const originalEditor = diffEditor.getOriginalEditor();
|
|
712
|
+
const modifiedEditor = diffEditor.getModifiedEditor();
|
|
713
|
+
return {
|
|
714
|
+
originalTop: originalEditor.getScrollTop(),
|
|
715
|
+
originalLeft: originalEditor.getScrollLeft(),
|
|
716
|
+
modifiedTop: modifiedEditor.getScrollTop(),
|
|
717
|
+
modifiedLeft: modifiedEditor.getScrollLeft(),
|
|
718
|
+
};
|
|
694
719
|
}
|
|
695
720
|
|
|
696
721
|
function restoreScrollState(scrollState) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
722
|
+
if (!diffEditor || !scrollState) return;
|
|
723
|
+
const originalEditor = diffEditor.getOriginalEditor();
|
|
724
|
+
const modifiedEditor = diffEditor.getModifiedEditor();
|
|
725
|
+
originalEditor.setScrollTop(scrollState.originalTop);
|
|
726
|
+
originalEditor.setScrollLeft(scrollState.originalLeft);
|
|
727
|
+
modifiedEditor.setScrollTop(scrollState.modifiedTop);
|
|
728
|
+
modifiedEditor.setScrollLeft(scrollState.modifiedLeft);
|
|
704
729
|
}
|
|
705
730
|
|
|
706
731
|
function getRequestState(fileId, scope = state.currentScope, commitSha = null) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
732
|
+
const key = cacheKey(scope, fileId, commitSha);
|
|
733
|
+
return {
|
|
734
|
+
contents: state.fileContents[key],
|
|
735
|
+
error: state.fileErrors[key],
|
|
736
|
+
requestId: state.pendingRequestIds[key],
|
|
737
|
+
};
|
|
713
738
|
}
|
|
714
739
|
|
|
715
740
|
function ensureFileLoaded(fileId, scope = state.currentScope, commitSha = null, options = {}) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
741
|
+
if (!fileId) return;
|
|
742
|
+
const forceRefresh = options.forceRefresh === true;
|
|
743
|
+
const resolvedCommitSha =
|
|
744
|
+
scope === "commits" ? (commitSha ?? state.selectedCommitSha ?? null) : null;
|
|
745
|
+
const key = cacheKey(scope, fileId, resolvedCommitSha);
|
|
746
|
+
if (!forceRefresh) {
|
|
747
|
+
if (state.fileContents[key] != null) return;
|
|
748
|
+
if (state.fileErrors[key] != null) return;
|
|
749
|
+
if (state.pendingRequestIds[key] != null) return;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const requestId = `request:${Date.now()}:${++requestSequence}`;
|
|
753
|
+
state.pendingRequestIds[key] = requestId;
|
|
754
|
+
renderTree();
|
|
755
|
+
if (window.glimpse?.send) {
|
|
756
|
+
const payload = { type: "request-file", requestId, fileId, scope };
|
|
757
|
+
if (scope === "commits" && resolvedCommitSha) {
|
|
758
|
+
payload.commitSha = resolvedCommitSha;
|
|
759
|
+
}
|
|
760
|
+
window.glimpse.send(payload);
|
|
761
|
+
}
|
|
736
762
|
}
|
|
737
763
|
|
|
738
764
|
function ensureCommitFilesLoaded(sha, options = {}) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
765
|
+
if (!sha) return;
|
|
766
|
+
const forceRefresh = options.forceRefresh === true;
|
|
767
|
+
if (!forceRefresh) {
|
|
768
|
+
if (state.commitFilesBySha[sha] != null) return;
|
|
769
|
+
if (state.commitErrors[sha] != null) return;
|
|
770
|
+
}
|
|
771
|
+
if (state.commitRequestIds[sha] != null) return;
|
|
772
|
+
|
|
773
|
+
if (forceRefresh) {
|
|
774
|
+
delete state.commitErrors[sha];
|
|
775
|
+
}
|
|
776
|
+
const requestId = `commit-request:${Date.now()}:${++requestSequence}`;
|
|
777
|
+
state.commitRequestIds[sha] = requestId;
|
|
778
|
+
if (window.glimpse?.send) {
|
|
779
|
+
window.glimpse.send({ type: "request-commit", requestId, sha });
|
|
780
|
+
}
|
|
781
|
+
renderCommitList();
|
|
756
782
|
}
|
|
757
783
|
|
|
758
784
|
function formatWorkingTreeLoadLabel(timestamp) {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
785
|
+
if (!timestamp) return "Not loaded yet";
|
|
786
|
+
const date = new Date(timestamp);
|
|
787
|
+
if (Number.isNaN(date.getTime())) return "Not loaded yet";
|
|
788
|
+
return date.toLocaleTimeString(undefined, {
|
|
789
|
+
hour: "numeric",
|
|
790
|
+
minute: "2-digit",
|
|
791
|
+
second: "2-digit",
|
|
792
|
+
});
|
|
763
793
|
}
|
|
764
794
|
|
|
765
795
|
function formatLocalChangeLabel(timestamp) {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
796
|
+
if (!timestamp) return "just now";
|
|
797
|
+
const date = new Date(timestamp);
|
|
798
|
+
if (Number.isNaN(date.getTime())) return "just now";
|
|
799
|
+
return date.toLocaleTimeString(undefined, {
|
|
800
|
+
hour: "numeric",
|
|
801
|
+
minute: "2-digit",
|
|
802
|
+
second: "2-digit",
|
|
803
|
+
});
|
|
770
804
|
}
|
|
771
805
|
|
|
772
806
|
function updateReviewRefreshButton() {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
807
|
+
if (!refreshReviewButton) return;
|
|
808
|
+
const shouldShow = state.localChangesDetected || state.currentScope === "commits";
|
|
809
|
+
if (!shouldShow) {
|
|
810
|
+
refreshReviewButton.style.display = "none";
|
|
811
|
+
refreshReviewButton.disabled = true;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const sha = state.currentScope === "commits" ? state.selectedCommitSha : null;
|
|
815
|
+
const commitLoading = sha ? state.commitRequestIds[sha] != null : false;
|
|
816
|
+
const reviewDataLoading = state.reviewDataRequestId != null;
|
|
817
|
+
const loading = commitLoading || reviewDataLoading;
|
|
818
|
+
refreshReviewButton.style.display = "inline-flex";
|
|
819
|
+
refreshReviewButton.disabled = loading;
|
|
820
|
+
refreshReviewButton.style.borderColor =
|
|
821
|
+
state.localChangesDetected && !loading ? "rgba(210,153,34,0.65)" : "";
|
|
822
|
+
refreshReviewButton.style.color = state.localChangesDetected && !loading ? "#d29922" : "";
|
|
823
|
+
refreshReviewButton.textContent = loading
|
|
824
|
+
? "Refreshing…"
|
|
825
|
+
: state.localChangesDetected
|
|
826
|
+
? "Changes detected · Refresh"
|
|
827
|
+
: isSelectedWorkingTreeCommit()
|
|
828
|
+
? "Refresh live diff"
|
|
829
|
+
: "Refresh review";
|
|
830
|
+
refreshReviewButton.title = state.localChangesDetected
|
|
831
|
+
? `Local file changes detected at ${formatLocalChangeLabel(state.lastLocalChangeDetectedAt)}. Click to reload review data.`
|
|
832
|
+
: isSelectedWorkingTreeCommit()
|
|
833
|
+
? `Working tree diff. Last loaded ${formatWorkingTreeLoadLabel(state.lastWorkingTreeLoadAt)}.`
|
|
834
|
+
: "Refresh review data for all scopes.";
|
|
800
835
|
}
|
|
801
836
|
|
|
802
837
|
function clearFileRequestStateByPrefixes(prefixes) {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
838
|
+
const matchesPrefix = (key) => prefixes.some((prefix) => key.startsWith(prefix));
|
|
839
|
+
for (const key of Object.keys(state.fileContents)) {
|
|
840
|
+
if (matchesPrefix(key)) delete state.fileContents[key];
|
|
841
|
+
}
|
|
842
|
+
for (const key of Object.keys(state.fileErrors)) {
|
|
843
|
+
if (matchesPrefix(key)) delete state.fileErrors[key];
|
|
844
|
+
}
|
|
845
|
+
for (const key of Object.keys(state.pendingRequestIds)) {
|
|
846
|
+
if (matchesPrefix(key)) delete state.pendingRequestIds[key];
|
|
847
|
+
}
|
|
813
848
|
}
|
|
814
849
|
|
|
815
850
|
function clearCommitScopedState(sha) {
|
|
816
|
-
|
|
851
|
+
clearFileRequestStateByPrefixes([`commits:${sha}:`]);
|
|
817
852
|
}
|
|
818
853
|
|
|
819
854
|
function clearRefreshableFileState() {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
855
|
+
const prefixes = ["branch:", "all:"];
|
|
856
|
+
for (const commit of reviewData.commits) {
|
|
857
|
+
if (commit.kind === "working-tree") {
|
|
858
|
+
prefixes.push(`commits:${commit.sha}:`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
clearFileRequestStateByPrefixes(prefixes);
|
|
827
862
|
}
|
|
828
863
|
|
|
829
864
|
function clearWorkingTreeReviewArtifacts(sha) {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
865
|
+
state.comments = state.comments.filter(
|
|
866
|
+
(comment) => !(comment.scope === "commits" && comment.commitSha === sha),
|
|
867
|
+
);
|
|
868
|
+
const previousFiles = state.commitFilesBySha[sha] ?? [];
|
|
869
|
+
for (const file of previousFiles) {
|
|
870
|
+
delete state.reviewedFiles[file.id];
|
|
871
|
+
delete state.scrollPositions[scrollKey("commits", file.id, sha)];
|
|
872
|
+
}
|
|
836
873
|
}
|
|
837
874
|
|
|
838
875
|
function clearWorkingTreeCommitState() {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
876
|
+
for (const commit of reviewData.commits) {
|
|
877
|
+
if (commit.kind !== "working-tree") continue;
|
|
878
|
+
clearWorkingTreeReviewArtifacts(commit.sha);
|
|
879
|
+
clearCommitScopedState(commit.sha);
|
|
880
|
+
delete state.commitFilesBySha[commit.sha];
|
|
881
|
+
delete state.commitErrors[commit.sha];
|
|
882
|
+
delete state.commitRequestIds[commit.sha];
|
|
883
|
+
}
|
|
847
884
|
}
|
|
848
885
|
|
|
849
886
|
function clearCommitScopedErrors(sha) {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
887
|
+
for (const key of Object.keys(state.fileErrors)) {
|
|
888
|
+
if (key.startsWith(`commits:${sha}:`)) delete state.fileErrors[key];
|
|
889
|
+
}
|
|
853
890
|
}
|
|
854
891
|
|
|
855
892
|
function reconcileCommitState(nextCommits) {
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
893
|
+
const retainedImmutableShas = new Set(
|
|
894
|
+
nextCommits.filter((commit) => commit.kind === "commit").map((commit) => commit.sha),
|
|
895
|
+
);
|
|
896
|
+
|
|
897
|
+
const knownShas = new Set([
|
|
898
|
+
...Object.keys(state.commitFilesBySha),
|
|
899
|
+
...Object.keys(state.commitErrors),
|
|
900
|
+
...Object.keys(state.commitRequestIds),
|
|
901
|
+
]);
|
|
902
|
+
for (const key of [
|
|
903
|
+
...Object.keys(state.fileContents),
|
|
904
|
+
...Object.keys(state.fileErrors),
|
|
905
|
+
...Object.keys(state.pendingRequestIds),
|
|
906
|
+
]) {
|
|
907
|
+
if (!key.startsWith("commits:")) continue;
|
|
908
|
+
const rest = key.slice("commits:".length);
|
|
909
|
+
const separator = rest.indexOf(":");
|
|
910
|
+
if (separator > 0) knownShas.add(rest.slice(0, separator));
|
|
911
|
+
}
|
|
912
|
+
for (const sha of knownShas) {
|
|
913
|
+
if (retainedImmutableShas.has(sha)) continue;
|
|
914
|
+
const previousFiles = state.commitFilesBySha[sha] ?? [];
|
|
915
|
+
for (const file of previousFiles) {
|
|
916
|
+
delete state.reviewedFiles[file.id];
|
|
917
|
+
delete state.scrollPositions[scrollKey("commits", file.id, sha)];
|
|
918
|
+
}
|
|
919
|
+
clearCommitScopedState(sha);
|
|
920
|
+
delete state.commitFilesBySha[sha];
|
|
921
|
+
delete state.commitErrors[sha];
|
|
922
|
+
delete state.commitRequestIds[sha];
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
for (const sha of retainedImmutableShas) {
|
|
926
|
+
// A failed immutable request is safe to retry after a successful refresh;
|
|
927
|
+
// successful immutable contents and in-flight requests remain untouched.
|
|
928
|
+
delete state.commitErrors[sha];
|
|
929
|
+
clearCommitScopedErrors(sha);
|
|
930
|
+
}
|
|
894
931
|
}
|
|
895
932
|
|
|
896
933
|
function requestLatestReviewData() {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
934
|
+
if (!window.glimpse?.send) return;
|
|
935
|
+
if (state.reviewDataRequestId != null) return;
|
|
936
|
+
const requestStartedAt = Date.now();
|
|
937
|
+
const requestId = `review-data-request:${requestStartedAt}:${++requestSequence}`;
|
|
938
|
+
state.reviewDataRequestId = requestId;
|
|
939
|
+
state.reviewDataRequestStartedAt = requestStartedAt;
|
|
940
|
+
updateReviewRefreshButton();
|
|
941
|
+
window.glimpse.send({ type: "request-review-data", requestId });
|
|
905
942
|
}
|
|
906
943
|
|
|
907
944
|
function refreshReviewData() {
|
|
908
|
-
|
|
945
|
+
requestLatestReviewData();
|
|
909
946
|
}
|
|
910
947
|
|
|
911
948
|
function openFile(fileId) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
949
|
+
if (state.activeFileId === fileId) {
|
|
950
|
+
ensureFileLoaded(fileId, state.currentScope);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
saveCurrentScrollPosition();
|
|
954
|
+
state.activeFileId = fileId;
|
|
955
|
+
renderAll({ restoreFileScroll: true });
|
|
956
|
+
ensureFileLoaded(fileId, state.currentScope);
|
|
920
957
|
}
|
|
921
958
|
|
|
922
959
|
function renderTreeNode(node, depth) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
960
|
+
const children = [...node.children.values()].sort((a, b) => {
|
|
961
|
+
if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1;
|
|
962
|
+
return a.name.localeCompare(b.name);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
const indentPx = 12;
|
|
966
|
+
|
|
967
|
+
for (const child of children) {
|
|
968
|
+
if (child.kind === "dir") {
|
|
969
|
+
const collapsed = state.collapsedDirs[child.path] === true;
|
|
970
|
+
const row = document.createElement("button");
|
|
971
|
+
row.type = "button";
|
|
972
|
+
row.className = "gh-tree-row";
|
|
973
|
+
row.style.paddingLeft = `${depth * indentPx + 8}px`;
|
|
974
|
+
row.innerHTML = `
|
|
938
975
|
<span class="flex h-3 w-3 items-center justify-center text-review-muted">${collapsed ? OCTICON_CHEVRON_RIGHT : OCTICON_CHEVRON_DOWN}</span>
|
|
939
976
|
${OCTICON_FOLDER}
|
|
940
977
|
<span class="gh-row-name">${escapeHtml(child.name)}</span>
|
|
941
978
|
`;
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
979
|
+
row.addEventListener("click", () => {
|
|
980
|
+
state.collapsedDirs[child.path] = !collapsed;
|
|
981
|
+
renderTree();
|
|
982
|
+
});
|
|
983
|
+
fileTreeEl.appendChild(row);
|
|
984
|
+
if (!collapsed) renderTreeNode(child, depth + 1);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const file = child.file;
|
|
989
|
+
const count = state.comments.filter(
|
|
990
|
+
(comment) => comment.fileId === file.id && comment.scope === state.currentScope,
|
|
991
|
+
).length;
|
|
992
|
+
const reviewed = isFileReviewed(file.id);
|
|
993
|
+
const requestState = getRequestState(file.id, state.currentScope);
|
|
994
|
+
const loading = requestState.requestId != null && requestState.contents == null;
|
|
995
|
+
const errored = requestState.error != null;
|
|
996
|
+
const status = getActiveStatus(file);
|
|
997
|
+
const button = document.createElement("button");
|
|
998
|
+
button.type = "button";
|
|
999
|
+
button.className = "gh-tree-row";
|
|
1000
|
+
button.dataset.selected = file.id === state.activeFileId ? "true" : "false";
|
|
1001
|
+
if (reviewed) button.style.opacity = "0.6";
|
|
1002
|
+
button.style.paddingLeft = `${depth * indentPx + 26}px`;
|
|
1003
|
+
const loadingBadge = loading
|
|
1004
|
+
? '<span class="text-[10px] text-review-accent">…</span>'
|
|
1005
|
+
: errored
|
|
1006
|
+
? '<span class="text-[10px] text-review-danger">!</span>'
|
|
1007
|
+
: "";
|
|
1008
|
+
button.innerHTML = `
|
|
972
1009
|
${OCTICON_FILE}
|
|
973
1010
|
<span class="gh-row-name">${escapeHtml(child.name)}</span>
|
|
974
1011
|
<span class="gh-row-trail">
|
|
@@ -978,37 +1015,37 @@ function renderTreeNode(node, depth) {
|
|
|
978
1015
|
${status ? `<span class="${statusBadgeClass(status)}">${escapeHtml(statusCode(status))}</span>` : ""}
|
|
979
1016
|
</span>
|
|
980
1017
|
`;
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1018
|
+
button.addEventListener("click", () => openFile(file.id));
|
|
1019
|
+
fileTreeEl.appendChild(button);
|
|
1020
|
+
}
|
|
984
1021
|
}
|
|
985
1022
|
|
|
986
1023
|
function renderSearchResults(files) {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1024
|
+
files.forEach((file) => {
|
|
1025
|
+
const path = getFileSearchPath(file);
|
|
1026
|
+
const baseName = getBaseName(path);
|
|
1027
|
+
const parentPath = path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
|
|
1028
|
+
const count = state.comments.filter(
|
|
1029
|
+
(comment) => comment.fileId === file.id && comment.scope === state.currentScope,
|
|
1030
|
+
).length;
|
|
1031
|
+
const reviewed = isFileReviewed(file.id);
|
|
1032
|
+
const requestState = getRequestState(file.id, state.currentScope);
|
|
1033
|
+
const loading = requestState.requestId != null && requestState.contents == null;
|
|
1034
|
+
const errored = requestState.error != null;
|
|
1035
|
+
const status = getActiveStatus(file);
|
|
1036
|
+
const button = document.createElement("button");
|
|
1037
|
+
button.type = "button";
|
|
1038
|
+
button.className = "gh-tree-row";
|
|
1039
|
+
button.dataset.selected = file.id === state.activeFileId ? "true" : "false";
|
|
1040
|
+
if (reviewed) button.style.opacity = "0.6";
|
|
1041
|
+
button.style.alignItems = "flex-start";
|
|
1042
|
+
button.style.padding = "6px 8px";
|
|
1043
|
+
const loadingBadge = loading
|
|
1044
|
+
? '<span class="text-[10px] text-review-accent">…</span>'
|
|
1045
|
+
: errored
|
|
1046
|
+
? '<span class="text-[10px] text-review-danger">!</span>'
|
|
1047
|
+
: "";
|
|
1048
|
+
button.innerHTML = `
|
|
1012
1049
|
${OCTICON_FILE}
|
|
1013
1050
|
<span class="min-w-0 flex-1">
|
|
1014
1051
|
<span class="block truncate text-[13px]">${escapeHtml(baseName)}</span>
|
|
@@ -1021,76 +1058,78 @@ function renderSearchResults(files) {
|
|
|
1021
1058
|
${status ? `<span class="${statusBadgeClass(status)}">${escapeHtml(statusCode(status))}</span>` : ""}
|
|
1022
1059
|
</span>
|
|
1023
1060
|
`;
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1061
|
+
button.addEventListener("click", () => openFile(file.id));
|
|
1062
|
+
fileTreeEl.appendChild(button);
|
|
1063
|
+
});
|
|
1027
1064
|
}
|
|
1028
1065
|
|
|
1029
1066
|
function updateSidebarLayout() {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1067
|
+
const collapsed = state.sidebarCollapsed;
|
|
1068
|
+
sidebarEl.style.width = collapsed ? "0px" : "280px";
|
|
1069
|
+
sidebarEl.style.minWidth = collapsed ? "0px" : "280px";
|
|
1070
|
+
sidebarEl.style.flexBasis = collapsed ? "0px" : "280px";
|
|
1071
|
+
sidebarEl.style.borderRightWidth = collapsed ? "0px" : "1px";
|
|
1072
|
+
sidebarEl.style.pointerEvents = collapsed ? "none" : "auto";
|
|
1073
|
+
toggleSidebarButton.dataset.active = collapsed ? "false" : "true";
|
|
1074
|
+
toggleSidebarButton.title = collapsed ? "Show sidebar" : "Hide sidebar";
|
|
1038
1075
|
}
|
|
1039
1076
|
|
|
1040
1077
|
function updateScopeButtons() {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1078
|
+
const counts = {
|
|
1079
|
+
branch: reviewData.files.filter((file) => file.inGitDiff).length,
|
|
1080
|
+
commits: reviewData.commits.length,
|
|
1081
|
+
all: reviewData.files.length,
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
const applyButtonClasses = (button, active, disabled) => {
|
|
1085
|
+
button.disabled = disabled;
|
|
1086
|
+
button.className = disabled
|
|
1087
|
+
? "cursor-default rounded-md border border-review-border bg-review-bg px-2.5 py-1 text-[11px] font-medium text-review-muted opacity-60"
|
|
1088
|
+
: active
|
|
1089
|
+
? "cursor-pointer rounded-md border border-review-success-emphasis/40 bg-review-success-emphasis/15 px-2.5 py-1 text-[11px] font-medium text-review-success hover:bg-review-success-emphasis/25"
|
|
1090
|
+
: "cursor-pointer rounded-md border border-review-border bg-review-panel px-2.5 py-1 text-[11px] font-medium text-review-text hover:bg-[#1f242c]";
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
scopeBranchButton.textContent = `Branch${counts.branch > 0 ? ` (${counts.branch})` : ""}`;
|
|
1094
|
+
scopeCommitsButton.textContent = `Commits${counts.commits > 0 ? ` (${counts.commits})` : ""}`;
|
|
1095
|
+
scopeAllButton.textContent = `All${counts.all > 0 ? ` (${counts.all})` : ""}`;
|
|
1096
|
+
|
|
1097
|
+
applyButtonClasses(scopeBranchButton, state.currentScope === "branch", counts.branch === 0);
|
|
1098
|
+
applyButtonClasses(scopeCommitsButton, state.currentScope === "commits", counts.commits === 0);
|
|
1099
|
+
applyButtonClasses(scopeAllButton, state.currentScope === "all", counts.all === 0);
|
|
1100
|
+
|
|
1101
|
+
if (commitPickerEl) {
|
|
1102
|
+
commitPickerEl.style.display = state.currentScope === "commits" ? "" : "none";
|
|
1103
|
+
}
|
|
1067
1104
|
}
|
|
1068
1105
|
|
|
1069
1106
|
function renderCommitList() {
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1107
|
+
if (!commitListEl) return;
|
|
1108
|
+
commitListEl.innerHTML = "";
|
|
1109
|
+
if (reviewData.commits.length === 0) {
|
|
1110
|
+
commitListEl.innerHTML =
|
|
1111
|
+
'<div class="px-3 py-2 text-[11px] text-review-muted">No commits to review.</div>';
|
|
1112
|
+
updateReviewRefreshButton();
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
for (const commit of reviewData.commits) {
|
|
1116
|
+
const row = document.createElement("button");
|
|
1117
|
+
row.type = "button";
|
|
1118
|
+
row.className = "commit-row";
|
|
1119
|
+
row.dataset.selected = commit.sha === state.selectedCommitSha ? "true" : "false";
|
|
1120
|
+
const loading =
|
|
1121
|
+
state.commitRequestIds[commit.sha] != null && state.commitFilesBySha[commit.sha] == null;
|
|
1122
|
+
const errored = state.commitErrors[commit.sha] != null;
|
|
1123
|
+
const isWorkingTreeCommit = commit.kind === "working-tree";
|
|
1124
|
+
const date = !isWorkingTreeCommit && commit.authorDate ? new Date(commit.authorDate) : null;
|
|
1125
|
+
const dateLabel =
|
|
1126
|
+
date && !Number.isNaN(date.getTime())
|
|
1127
|
+
? date.toLocaleDateString(undefined, { month: "short", day: "numeric" })
|
|
1128
|
+
: "";
|
|
1129
|
+
const metaLabel = isWorkingTreeCommit
|
|
1130
|
+
? `Live · ${workingTreeRangeLabel()}`
|
|
1131
|
+
: `${commit.authorName || ""}${dateLabel ? ` · ${dateLabel}` : ""}`;
|
|
1132
|
+
row.innerHTML = `
|
|
1094
1133
|
<span class="commit-row-sha">${escapeHtml(commit.shortSha)}</span>
|
|
1095
1134
|
<span class="commit-row-body">
|
|
1096
1135
|
<span class="commit-row-subject">${escapeHtml(commit.subject)}</span>
|
|
@@ -1098,156 +1137,157 @@ function renderCommitList() {
|
|
|
1098
1137
|
</span>
|
|
1099
1138
|
<span class="commit-row-status">${loading ? "…" : errored ? "!" : ""}</span>
|
|
1100
1139
|
`;
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1140
|
+
row.addEventListener("click", () => selectCommit(commit.sha));
|
|
1141
|
+
commitListEl.appendChild(row);
|
|
1142
|
+
}
|
|
1143
|
+
updateReviewRefreshButton();
|
|
1105
1144
|
}
|
|
1106
1145
|
|
|
1107
1146
|
function selectCommit(sha) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1147
|
+
if (!sha) return;
|
|
1148
|
+
if (state.selectedCommitSha === sha && state.commitFilesBySha[sha] != null) return;
|
|
1149
|
+
saveCurrentScrollPosition();
|
|
1150
|
+
state.selectedCommitSha = sha;
|
|
1151
|
+
state.activeFileId = null;
|
|
1152
|
+
ensureCommitFilesLoaded(sha);
|
|
1153
|
+
renderAll({ restoreFileScroll: false });
|
|
1154
|
+
const file = activeFile();
|
|
1155
|
+
if (file) ensureFileLoaded(file.id, state.currentScope);
|
|
1117
1156
|
}
|
|
1118
1157
|
|
|
1119
1158
|
function updateToggleButtons() {
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1159
|
+
const file = activeFile();
|
|
1160
|
+
const reviewed = file ? isFileReviewed(file.id) : false;
|
|
1161
|
+
const usesBinaryPreview = activeFileUsesBinaryPreview();
|
|
1162
|
+
toggleReviewedButton.dataset.active = reviewed ? "true" : "false";
|
|
1163
|
+
toggleReviewedButton.title = reviewed ? "Viewed (click to unmark)" : "Mark this file as viewed";
|
|
1164
|
+
if (state.assetInitFailed) {
|
|
1165
|
+
overallCommentButton.disabled = true;
|
|
1166
|
+
toggleReviewedButton.disabled = true;
|
|
1167
|
+
fileCommentButton.disabled = true;
|
|
1168
|
+
toggleWrapButton.disabled = true;
|
|
1169
|
+
toggleUnchangedButton.style.display = "none";
|
|
1170
|
+
updateScopeButtons();
|
|
1171
|
+
updateReviewRefreshButton();
|
|
1172
|
+
submitButton.disabled = true;
|
|
1173
|
+
updateFileHeaderMeta(file);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
overallCommentButton.disabled = false;
|
|
1177
|
+
toggleReviewedButton.disabled = !file;
|
|
1178
|
+
fileCommentButton.disabled = !file;
|
|
1179
|
+
toggleWrapButton.dataset.active = !usesBinaryPreview && state.wrapLines ? "true" : "false";
|
|
1180
|
+
toggleWrapButton.disabled = !file || usesBinaryPreview;
|
|
1181
|
+
toggleWrapButton.title = usesBinaryPreview
|
|
1182
|
+
? "Wrap is unavailable for binary previews"
|
|
1183
|
+
: `Wrap long lines (${state.wrapLines ? "on" : "off"})`;
|
|
1184
|
+
toggleUnchangedButton.dataset.active = state.hideUnchanged ? "true" : "false";
|
|
1185
|
+
toggleUnchangedButton.title = state.hideUnchanged
|
|
1186
|
+
? "Showing changed areas only — click to show full file"
|
|
1187
|
+
: "Show changed areas only";
|
|
1188
|
+
toggleUnchangedButton.style.display =
|
|
1189
|
+
!usesBinaryPreview && activeFileShowsDiff() ? "inline-flex" : "none";
|
|
1190
|
+
updateScopeButtons();
|
|
1191
|
+
updateReviewRefreshButton();
|
|
1192
|
+
modeHintEl.textContent = currentModeHint();
|
|
1193
|
+
submitButton.disabled = false;
|
|
1194
|
+
updateFileHeaderMeta(file);
|
|
1155
1195
|
}
|
|
1156
1196
|
|
|
1157
1197
|
function applyEditorOptions() {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1198
|
+
const addedOnly = activeFileIsAddedOnly();
|
|
1199
|
+
const showDiff = activeFileShowsDiff();
|
|
1200
|
+
const wrapMode = state.wrapLines ? "on" : "off";
|
|
1201
|
+
editorContainerEl.dataset.addedOnly = addedOnly ? "true" : "false";
|
|
1202
|
+
if (singleEditor) {
|
|
1203
|
+
singleEditor.updateOptions({
|
|
1204
|
+
wordWrap: wrapMode,
|
|
1205
|
+
wordWrapOverride1: wrapMode,
|
|
1206
|
+
wordWrapOverride2: wrapMode,
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
if (!diffEditor) return;
|
|
1210
|
+
// Why both `wordWrap` and `wordWrapOverride1/2`:
|
|
1211
|
+
// Monaco's diff editor internally applies `wordWrapOverride1` (and sometimes
|
|
1212
|
+
// `wordWrapOverride2`) on the original/modified sides to enforce diff-specific
|
|
1213
|
+
// behavior. Those overrides take precedence over `wordWrap`, so setting only
|
|
1214
|
+
// `wordWrap` on each side leaves the effective wrapping column out of sync
|
|
1215
|
+
// (original keeps wrap off, modified honors wrap on -> asymmetric rendering).
|
|
1216
|
+
// Setting both overrides to the same value forces the effective wrap to match.
|
|
1217
|
+
diffEditor.updateOptions({
|
|
1218
|
+
renderSideBySide: showDiff,
|
|
1219
|
+
diffWordWrap: "inherit",
|
|
1220
|
+
hideUnchangedRegions: {
|
|
1221
|
+
enabled: showDiff && state.hideUnchanged,
|
|
1222
|
+
contextLineCount: 4,
|
|
1223
|
+
minimumLineCount: 2,
|
|
1224
|
+
revealLineCount: 12,
|
|
1225
|
+
},
|
|
1226
|
+
});
|
|
1227
|
+
// For added-only files, hide the original editor's line numbers so the inline diff
|
|
1228
|
+
// gutter doesn't paint a redundant two-column layout alongside the modified numbers.
|
|
1229
|
+
diffEditor.getOriginalEditor().updateOptions({
|
|
1230
|
+
wordWrap: wrapMode,
|
|
1231
|
+
wordWrapOverride1: wrapMode,
|
|
1232
|
+
wordWrapOverride2: wrapMode,
|
|
1233
|
+
lineNumbers: addedOnly ? "off" : "on",
|
|
1234
|
+
});
|
|
1235
|
+
diffEditor.getModifiedEditor().updateOptions({
|
|
1236
|
+
wordWrap: wrapMode,
|
|
1237
|
+
wordWrapOverride1: wrapMode,
|
|
1238
|
+
wordWrapOverride2: wrapMode,
|
|
1239
|
+
});
|
|
1200
1240
|
}
|
|
1201
1241
|
|
|
1202
1242
|
function renderTree() {
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1243
|
+
ensureActiveFileForScope();
|
|
1244
|
+
fileTreeEl.innerHTML = "";
|
|
1245
|
+
const scopedFiles = getScopedFiles();
|
|
1246
|
+
const visibleFiles = getFilteredFiles();
|
|
1247
|
+
|
|
1248
|
+
if (visibleFiles.length === 0) {
|
|
1249
|
+
const message = state.fileFilter.trim()
|
|
1250
|
+
? `No files match <span class="text-review-text">${escapeHtml(state.fileFilter.trim())}</span>.`
|
|
1251
|
+
: `No files in <span class="text-review-text">${escapeHtml(scopeLabel(state.currentScope).toLowerCase())}</span>.`;
|
|
1252
|
+
fileTreeEl.innerHTML = `
|
|
1213
1253
|
<div class="px-3 py-4 text-sm text-review-muted">
|
|
1214
1254
|
${message}
|
|
1215
1255
|
</div>
|
|
1216
1256
|
`;
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1257
|
+
} else if (state.fileFilter.trim()) {
|
|
1258
|
+
renderSearchResults(visibleFiles);
|
|
1259
|
+
} else {
|
|
1260
|
+
renderTreeNode(buildTree(visibleFiles), 0);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
sidebarTitleEl.textContent = scopeLabel(state.currentScope);
|
|
1264
|
+
const comments = state.comments.length;
|
|
1265
|
+
const filteredSuffix = state.fileFilter.trim() ? ` • ${visibleFiles.length} shown` : "";
|
|
1266
|
+
const liveSuffix = isSelectedWorkingTreeCommit() ? " • live working tree" : "";
|
|
1267
|
+
const staleSuffix = state.localChangesDetected ? " • local changes detected" : "";
|
|
1268
|
+
summaryEl.textContent = `${scopedFiles.length} file(s) • ${comments} comment(s)${state.overallComment ? " • overall note" : ""}${filteredSuffix}${liveSuffix}${staleSuffix}`;
|
|
1269
|
+
updateToggleButtons();
|
|
1270
|
+
updateSidebarLayout();
|
|
1231
1271
|
}
|
|
1232
1272
|
|
|
1233
1273
|
let activePopover = null;
|
|
1234
1274
|
|
|
1235
1275
|
function closeActivePopover() {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1276
|
+
if (!activePopover) return;
|
|
1277
|
+
activePopover.dispose();
|
|
1278
|
+
activePopover = null;
|
|
1239
1279
|
}
|
|
1240
1280
|
|
|
1241
1281
|
function showTextPopover(trigger, options) {
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1282
|
+
if (activePopover && activePopover.trigger === trigger) {
|
|
1283
|
+
closeActivePopover();
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
closeActivePopover();
|
|
1287
|
+
|
|
1288
|
+
const popover = document.createElement("div");
|
|
1289
|
+
popover.className = "review-popover";
|
|
1290
|
+
popover.innerHTML = `
|
|
1251
1291
|
<div class="mb-1 text-[13px] font-semibold text-review-text">${escapeHtml(options.title)}</div>
|
|
1252
1292
|
<div class="mb-2 text-[11px] text-review-muted">${escapeHtml(options.description)}</div>
|
|
1253
1293
|
<textarea id="review-popover-text" class="scrollbar-thin w-full resize-y rounded-md border border-review-border bg-[#010409] px-2.5 py-1.5 text-[13px] text-review-text outline-none focus:border-review-accent focus:ring-1 focus:ring-review-accent" placeholder="${escapeHtml(options.placeholder ?? "")}">${escapeHtml(options.initialValue ?? "")}</textarea>
|
|
@@ -1259,348 +1299,365 @@ function showTextPopover(trigger, options) {
|
|
|
1259
1299
|
</div>
|
|
1260
1300
|
</div>
|
|
1261
1301
|
`;
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1302
|
+
document.body.appendChild(popover);
|
|
1303
|
+
|
|
1304
|
+
// Position anchored to trigger button, preferring below-right alignment.
|
|
1305
|
+
const rect = trigger.getBoundingClientRect();
|
|
1306
|
+
const popRect = popover.getBoundingClientRect();
|
|
1307
|
+
const viewportW = document.documentElement.clientWidth;
|
|
1308
|
+
const margin = 8;
|
|
1309
|
+
const gap = 6;
|
|
1310
|
+
const top = rect.bottom + gap;
|
|
1311
|
+
let left = rect.right - popRect.width;
|
|
1312
|
+
if (left < margin) left = margin;
|
|
1313
|
+
if (left + popRect.width > viewportW - margin) left = viewportW - margin - popRect.width;
|
|
1314
|
+
popover.style.top = `${top}px`;
|
|
1315
|
+
popover.style.left = `${left}px`;
|
|
1316
|
+
popover.dataset.arrow = rect.right - left > popRect.width / 2 ? "right" : "left";
|
|
1317
|
+
|
|
1318
|
+
const textarea = popover.querySelector("#review-popover-text");
|
|
1319
|
+
const save = () => {
|
|
1320
|
+
const value = textarea.value.trim();
|
|
1321
|
+
options.onSave(value);
|
|
1322
|
+
closePopover();
|
|
1323
|
+
};
|
|
1324
|
+
const closePopover = () => {
|
|
1325
|
+
popover.remove();
|
|
1326
|
+
document.removeEventListener("mousedown", onOutside, true);
|
|
1327
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1328
|
+
if (activePopover && activePopover.element === popover) activePopover = null;
|
|
1329
|
+
};
|
|
1330
|
+
const onOutside = (event) => {
|
|
1331
|
+
if (popover.contains(event.target) || trigger.contains(event.target)) return;
|
|
1332
|
+
closePopover();
|
|
1333
|
+
};
|
|
1334
|
+
const onKey = (event) => {
|
|
1335
|
+
if (event.key === "Escape") {
|
|
1336
|
+
event.preventDefault();
|
|
1337
|
+
closePopover();
|
|
1338
|
+
trigger.focus?.();
|
|
1339
|
+
} else if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
1340
|
+
event.preventDefault();
|
|
1341
|
+
save();
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
popover.querySelector("#review-popover-cancel").addEventListener("click", closePopover);
|
|
1345
|
+
popover.querySelector("#review-popover-save").addEventListener("click", save);
|
|
1346
|
+
document.addEventListener("mousedown", onOutside, true);
|
|
1347
|
+
document.addEventListener("keydown", onKey, true);
|
|
1348
|
+
|
|
1349
|
+
activePopover = { trigger, element: popover, dispose: closePopover };
|
|
1350
|
+
requestAnimationFrame(() => textarea.focus());
|
|
1311
1351
|
}
|
|
1312
1352
|
|
|
1313
1353
|
function showOverallCommentPopover() {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1354
|
+
showTextPopover(overallCommentButton, {
|
|
1355
|
+
title: "Overall review note",
|
|
1356
|
+
description: "Prepended to the generated prompt above inline comments.",
|
|
1357
|
+
initialValue: state.overallComment,
|
|
1358
|
+
placeholder: "High-level notes for this review...",
|
|
1359
|
+
saveLabel: "Save note",
|
|
1360
|
+
onSave: (value) => {
|
|
1361
|
+
state.overallComment = value;
|
|
1362
|
+
renderTree();
|
|
1363
|
+
},
|
|
1364
|
+
});
|
|
1325
1365
|
}
|
|
1326
1366
|
|
|
1327
1367
|
function showFileCommentPopover() {
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1368
|
+
const file = activeFile();
|
|
1369
|
+
if (!file) return;
|
|
1370
|
+
showTextPopover(fileCommentButton, {
|
|
1371
|
+
title: `File comment — ${getScopeDisplayPath(file, state.currentScope)}`,
|
|
1372
|
+
description: `Applies to the whole file in ${scopeLabel(state.currentScope).toLowerCase()}.`,
|
|
1373
|
+
initialValue: "",
|
|
1374
|
+
placeholder: "Comment on the whole file...",
|
|
1375
|
+
saveLabel: "Add comment",
|
|
1376
|
+
onSave: (value) => {
|
|
1377
|
+
if (!value) return;
|
|
1378
|
+
state.comments.push(
|
|
1379
|
+
annotateCommentWithCommit({
|
|
1380
|
+
id: `${Date.now()}:${Math.random().toString(16).slice(2)}`,
|
|
1381
|
+
fileId: file.id,
|
|
1382
|
+
scope: state.currentScope,
|
|
1383
|
+
side: "file",
|
|
1384
|
+
startLine: null,
|
|
1385
|
+
endLine: null,
|
|
1386
|
+
body: value,
|
|
1387
|
+
}),
|
|
1388
|
+
);
|
|
1389
|
+
submitButton.disabled = false;
|
|
1390
|
+
updateCommentsUI();
|
|
1391
|
+
},
|
|
1392
|
+
});
|
|
1353
1393
|
}
|
|
1354
1394
|
|
|
1355
1395
|
function layoutEditor() {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1396
|
+
const width = editorContainerEl.clientWidth;
|
|
1397
|
+
const height = editorContainerEl.clientHeight;
|
|
1398
|
+
if (width <= 0 || height <= 0) return;
|
|
1399
|
+
if (diffEditor) diffEditor.layout({ width, height });
|
|
1400
|
+
if (singleEditor) singleEditor.layout({ width, height });
|
|
1361
1401
|
}
|
|
1362
1402
|
|
|
1363
1403
|
function clearViewZones() {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1404
|
+
if (activeViewZones.length === 0) return;
|
|
1405
|
+
const zonesByEditor = new Map();
|
|
1406
|
+
for (const zone of activeViewZones) {
|
|
1407
|
+
if (!zone.editor) continue;
|
|
1408
|
+
const zones = zonesByEditor.get(zone.editor) ?? [];
|
|
1409
|
+
zones.push(zone.id);
|
|
1410
|
+
zonesByEditor.set(zone.editor, zones);
|
|
1411
|
+
}
|
|
1412
|
+
for (const [editor, zoneIds] of zonesByEditor.entries()) {
|
|
1413
|
+
editor.changeViewZones((accessor) => {
|
|
1414
|
+
for (const id of zoneIds) accessor.removeZone(id);
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
activeViewZones = [];
|
|
1378
1418
|
}
|
|
1379
1419
|
|
|
1380
1420
|
function renderCommentDOM(comment, onDelete) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1421
|
+
const container = document.createElement("div");
|
|
1422
|
+
container.className = "view-zone-container";
|
|
1423
|
+
const title =
|
|
1424
|
+
comment.side === "file"
|
|
1425
|
+
? `File comment • ${scopeLabel(comment.scope)}`
|
|
1426
|
+
: `${comment.side === "original" ? "Original" : "Modified"} line ${comment.startLine} • ${scopeLabel(comment.scope)}`;
|
|
1427
|
+
|
|
1428
|
+
container.innerHTML = `
|
|
1389
1429
|
<div class="mb-2 flex items-center justify-between gap-3">
|
|
1390
1430
|
<div class="text-xs font-semibold text-review-text">${escapeHtml(title)}</div>
|
|
1391
1431
|
<button data-action="delete" class="cursor-pointer rounded-md border border-transparent bg-transparent px-2 py-1 text-xs font-medium text-review-muted hover:bg-red-500/10 hover:text-red-400">Delete</button>
|
|
1392
1432
|
</div>
|
|
1393
1433
|
<textarea data-comment-id="${escapeHtml(comment.id)}" class="scrollbar-thin min-h-[76px] w-full resize-y rounded-md border border-review-border bg-[#010409] px-3 py-2 text-sm text-review-text outline-none focus:border-review-accent focus:ring-1 focus:ring-review-accent" placeholder="Leave a comment"></textarea>
|
|
1394
1434
|
`;
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1435
|
+
const textarea = container.querySelector("textarea");
|
|
1436
|
+
textarea.value = comment.body || "";
|
|
1437
|
+
textarea.addEventListener("input", () => {
|
|
1438
|
+
comment.body = textarea.value;
|
|
1439
|
+
});
|
|
1440
|
+
container.querySelector("[data-action='delete']").addEventListener("click", onDelete);
|
|
1441
|
+
if (!comment.body) setTimeout(() => textarea.focus(), 50);
|
|
1442
|
+
return container;
|
|
1403
1443
|
}
|
|
1404
1444
|
|
|
1405
1445
|
function canCommentOnSide(file, side) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1446
|
+
if (!file || file.kind !== "text") return false;
|
|
1447
|
+
const comparison = activeComparison();
|
|
1448
|
+
if (side === "original") {
|
|
1449
|
+
return comparison?.hasOriginal ?? false;
|
|
1450
|
+
}
|
|
1451
|
+
return comparison != null ? comparison.hasModified : file.hasWorkingTreeFile;
|
|
1412
1452
|
}
|
|
1413
1453
|
|
|
1414
1454
|
function isActiveFileReady() {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1455
|
+
const file = activeFile();
|
|
1456
|
+
if (!file) return false;
|
|
1457
|
+
const requestState = getRequestState(file.id, state.currentScope);
|
|
1458
|
+
return requestState.contents != null && requestState.error == null;
|
|
1419
1459
|
}
|
|
1420
1460
|
|
|
1421
1461
|
function syncViewZones() {
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1462
|
+
clearViewZones();
|
|
1463
|
+
if (!isActiveFileReady()) return;
|
|
1464
|
+
const file = activeFile();
|
|
1465
|
+
if (!file) return;
|
|
1466
|
+
|
|
1467
|
+
const usingSingleEditor = activeFileUsesSingleEditor();
|
|
1468
|
+
const originalEditor = usingSingleEditor ? null : diffEditor?.getOriginalEditor?.();
|
|
1469
|
+
const modifiedEditor = usingSingleEditor ? singleEditor : diffEditor?.getModifiedEditor?.();
|
|
1470
|
+
if (!modifiedEditor || (!usingSingleEditor && !originalEditor)) return;
|
|
1471
|
+
|
|
1472
|
+
const inlineComments = state.comments.filter(
|
|
1473
|
+
(comment) =>
|
|
1474
|
+
comment.fileId === file.id && comment.scope === state.currentScope && comment.side !== "file",
|
|
1475
|
+
);
|
|
1476
|
+
|
|
1477
|
+
inlineComments.forEach((item) => {
|
|
1478
|
+
const editor = item.side === "original" ? originalEditor : modifiedEditor;
|
|
1479
|
+
if (!editor) return;
|
|
1480
|
+
const domNode = renderCommentDOM(item, () => {
|
|
1481
|
+
state.comments = state.comments.filter((comment) => comment.id !== item.id);
|
|
1482
|
+
updateCommentsUI();
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
editor.changeViewZones((accessor) => {
|
|
1486
|
+
const lineCount =
|
|
1487
|
+
typeof item.body === "string" && item.body.length > 0 ? item.body.split("\n").length : 1;
|
|
1488
|
+
const id = accessor.addZone({
|
|
1489
|
+
afterLineNumber: item.startLine,
|
|
1490
|
+
heightInPx: Math.max(150, lineCount * 22 + 86),
|
|
1491
|
+
domNode,
|
|
1492
|
+
});
|
|
1493
|
+
activeViewZones.push({ id, editor });
|
|
1494
|
+
});
|
|
1495
|
+
});
|
|
1454
1496
|
}
|
|
1455
1497
|
|
|
1456
1498
|
function updateDecorations() {
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1499
|
+
if (!monacoApi) return;
|
|
1500
|
+
const file = activeFile();
|
|
1501
|
+
const comments = file
|
|
1502
|
+
? state.comments.filter(
|
|
1503
|
+
(comment) =>
|
|
1504
|
+
comment.fileId === file.id &&
|
|
1505
|
+
comment.scope === state.currentScope &&
|
|
1506
|
+
comment.side !== "file",
|
|
1507
|
+
)
|
|
1508
|
+
: [];
|
|
1509
|
+
const originalRanges = [];
|
|
1510
|
+
const modifiedRanges = [];
|
|
1511
|
+
|
|
1512
|
+
for (const comment of comments) {
|
|
1513
|
+
const range = {
|
|
1514
|
+
range: new monacoApi.Range(comment.startLine, 1, comment.startLine, 1),
|
|
1515
|
+
options: {
|
|
1516
|
+
isWholeLine: true,
|
|
1517
|
+
className:
|
|
1518
|
+
comment.side === "original"
|
|
1519
|
+
? "review-comment-line-original"
|
|
1520
|
+
: "review-comment-line-modified",
|
|
1521
|
+
glyphMarginClassName:
|
|
1522
|
+
comment.side === "original"
|
|
1523
|
+
? "review-comment-glyph-original"
|
|
1524
|
+
: "review-comment-glyph-modified",
|
|
1525
|
+
},
|
|
1526
|
+
};
|
|
1527
|
+
if (comment.side === "original") originalRanges.push(range);
|
|
1528
|
+
else modifiedRanges.push(range);
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
if (diffEditor) {
|
|
1532
|
+
originalDecorations = diffEditor
|
|
1533
|
+
.getOriginalEditor()
|
|
1534
|
+
.deltaDecorations(originalDecorations, originalRanges);
|
|
1535
|
+
modifiedDecorations = diffEditor
|
|
1536
|
+
.getModifiedEditor()
|
|
1537
|
+
.deltaDecorations(modifiedDecorations, activeFileUsesSingleEditor() ? [] : modifiedRanges);
|
|
1538
|
+
}
|
|
1539
|
+
singleDecorations = singleEditor
|
|
1540
|
+
? singleEditor.deltaDecorations(
|
|
1541
|
+
singleDecorations,
|
|
1542
|
+
activeFileUsesSingleEditor() ? modifiedRanges : [],
|
|
1543
|
+
)
|
|
1544
|
+
: [];
|
|
1490
1545
|
}
|
|
1491
1546
|
|
|
1492
1547
|
function renderFileComments() {
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1548
|
+
fileCommentsContainer.innerHTML = "";
|
|
1549
|
+
const file = activeFile();
|
|
1550
|
+
if (!file) {
|
|
1551
|
+
fileCommentsContainer.className = "hidden overflow-hidden px-0 py-0";
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
const fileComments = state.comments.filter(
|
|
1556
|
+
(comment) =>
|
|
1557
|
+
comment.fileId === file.id && comment.scope === state.currentScope && comment.side === "file",
|
|
1558
|
+
);
|
|
1559
|
+
|
|
1560
|
+
if (fileComments.length === 0) {
|
|
1561
|
+
fileCommentsContainer.className = "hidden overflow-hidden px-0 py-0";
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
fileCommentsContainer.className =
|
|
1566
|
+
"border-b border-review-border bg-[#0d1117] px-4 py-4 space-y-4";
|
|
1567
|
+
fileComments.forEach((comment) => {
|
|
1568
|
+
const dom = renderCommentDOM(comment, () => {
|
|
1569
|
+
state.comments = state.comments.filter((item) => item.id !== comment.id);
|
|
1570
|
+
updateCommentsUI();
|
|
1571
|
+
});
|
|
1572
|
+
dom.className = "rounded-lg border border-review-border bg-review-panel p-4";
|
|
1573
|
+
fileCommentsContainer.appendChild(dom);
|
|
1574
|
+
});
|
|
1518
1575
|
}
|
|
1519
1576
|
|
|
1520
1577
|
function getPlaceholderContents(file, scope) {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1578
|
+
const path = getScopeDisplayPath(file, scope);
|
|
1579
|
+
const requestState = getRequestState(file.id, scope);
|
|
1580
|
+
if (requestState.error) {
|
|
1581
|
+
const body = `Failed to load ${path}\n\n${requestState.error}`;
|
|
1582
|
+
return {
|
|
1583
|
+
originalContent: body,
|
|
1584
|
+
modifiedContent: body,
|
|
1585
|
+
kind: file.kind,
|
|
1586
|
+
mimeType: file.mimeType,
|
|
1587
|
+
originalExists: false,
|
|
1588
|
+
modifiedExists: false,
|
|
1589
|
+
originalPreviewUrl: null,
|
|
1590
|
+
modifiedPreviewUrl: null,
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
const body = `Loading ${path}...`;
|
|
1594
|
+
return {
|
|
1595
|
+
originalContent: body,
|
|
1596
|
+
modifiedContent: body,
|
|
1597
|
+
kind: file.kind,
|
|
1598
|
+
mimeType: file.mimeType,
|
|
1599
|
+
originalExists: false,
|
|
1600
|
+
modifiedExists: false,
|
|
1601
|
+
originalPreviewUrl: null,
|
|
1602
|
+
modifiedPreviewUrl: null,
|
|
1603
|
+
};
|
|
1547
1604
|
}
|
|
1548
1605
|
|
|
1549
1606
|
function getMountedContents(file, scope = state.currentScope) {
|
|
1550
|
-
|
|
1607
|
+
return getRequestState(file.id, scope).contents || getPlaceholderContents(file, scope);
|
|
1551
1608
|
}
|
|
1552
1609
|
|
|
1553
1610
|
function renderFilePathLabel(path) {
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1611
|
+
if (!path) {
|
|
1612
|
+
currentFileLabelEl.textContent = "";
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
const idx = path.lastIndexOf("/");
|
|
1616
|
+
if (idx < 0) {
|
|
1617
|
+
currentFileLabelEl.textContent = path;
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
const dir = path.slice(0, idx + 1);
|
|
1621
|
+
const base = path.slice(idx + 1);
|
|
1622
|
+
currentFileLabelEl.innerHTML = `<span class="gh-file-path-dir">${escapeHtml(dir)}</span>${escapeHtml(base)}`;
|
|
1566
1623
|
}
|
|
1567
1624
|
|
|
1568
1625
|
function updateFileHeaderMeta(file) {
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1626
|
+
if (!file) {
|
|
1627
|
+
fileStatusBadgeEl.style.display = "none";
|
|
1628
|
+
fileDiffStatsEl.style.display = "none";
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
const status = getActiveStatus(file);
|
|
1632
|
+
if (status) {
|
|
1633
|
+
fileStatusBadgeEl.style.display = "inline-flex";
|
|
1634
|
+
fileStatusBadgeEl.className = statusBadgeClass(status);
|
|
1635
|
+
fileStatusBadgeEl.textContent = statusCode(status);
|
|
1636
|
+
fileStatusBadgeEl.title = statusLabel(status);
|
|
1637
|
+
} else {
|
|
1638
|
+
fileStatusBadgeEl.style.display = "none";
|
|
1639
|
+
}
|
|
1640
|
+
if (file.kind !== "text") {
|
|
1641
|
+
fileDiffStatsEl.style.display = "none";
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const contents = getRequestState(file.id, state.currentScope).contents;
|
|
1645
|
+
if (!contents || !activeFileShowsDiff()) {
|
|
1646
|
+
fileDiffStatsEl.style.display = "none";
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
const originalLines = contents.originalContent ? contents.originalContent.split("\n").length : 0;
|
|
1650
|
+
const modifiedLines = contents.modifiedContent ? contents.modifiedContent.split("\n").length : 0;
|
|
1651
|
+
// Cheap estimate — we don't compute a full diff; the counts are naive line
|
|
1652
|
+
// deltas, which is enough for the GitHub-style badge.
|
|
1653
|
+
const added = Math.max(0, modifiedLines - originalLines);
|
|
1654
|
+
const removed = Math.max(0, originalLines - modifiedLines);
|
|
1655
|
+
if (added === 0 && removed === 0) {
|
|
1656
|
+
fileDiffStatsEl.style.display = "none";
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
fileDiffStatsEl.style.display = "inline-flex";
|
|
1660
|
+
fileDiffStatsEl.innerHTML = `${added > 0 ? `<span class="gh-diff-stats-plus">+${added}</span>` : ""}${removed > 0 ? `<span class="gh-diff-stats-minus">−${removed}</span>` : ""}`;
|
|
1604
1661
|
}
|
|
1605
1662
|
|
|
1606
1663
|
// Hide the diff editor while Monaco is computing the diff, then reveal once
|
|
@@ -1609,819 +1666,825 @@ function updateFileHeaderMeta(file) {
|
|
|
1609
1666
|
// `hideUnchangedRegions` has nothing to fold, so the first frame always shows the
|
|
1610
1667
|
// full file and only collapses on the next tick. That's the layout shift.
|
|
1611
1668
|
function cancelPendingReveal() {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1669
|
+
if (!pendingDiffReveal) return;
|
|
1670
|
+
try {
|
|
1671
|
+
pendingDiffReveal.dispose?.dispose();
|
|
1672
|
+
} catch {
|
|
1673
|
+
// Ignore stale Monaco disposables during remounts.
|
|
1674
|
+
}
|
|
1675
|
+
clearTimeout(pendingDiffReveal.timeoutId);
|
|
1676
|
+
pendingDiffReveal = null;
|
|
1620
1677
|
}
|
|
1621
1678
|
|
|
1622
1679
|
function hideEditorForMount() {
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1680
|
+
// Paint an opaque cover OVER Monaco (not on the container itself). This keeps
|
|
1681
|
+
// Monaco fully visible to the browser's layout engine so it renders, computes
|
|
1682
|
+
// its diff, inserts hide-unchanged view zones and paints the final folded
|
|
1683
|
+
// geometry at normal speed. Hiding the container via opacity:0 can make WebKit
|
|
1684
|
+
// skip paints/compositing for Monaco's nested absolute children, which in turn
|
|
1685
|
+
// makes `onDidUpdateDiff` + RAF reveal land while the fold is still settling.
|
|
1686
|
+
if (editorCoverEl) editorCoverEl.dataset.active = "true";
|
|
1630
1687
|
}
|
|
1631
1688
|
|
|
1632
1689
|
function armDiffRevealListener(onSettled) {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1690
|
+
// Attach listener BEFORE setModel to avoid missing a synchronously-fired
|
|
1691
|
+
// onDidUpdateDiff event (Monaco can compute diffs synchronously for small files).
|
|
1692
|
+
cancelPendingReveal();
|
|
1693
|
+
if (!diffEditor || typeof diffEditor.onDidUpdateDiff !== "function") {
|
|
1694
|
+
pendingDiffReveal = { dispose: null, timeoutId: setTimeout(onSettled, 0) };
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
let fired = false;
|
|
1698
|
+
const settleOnce = () => {
|
|
1699
|
+
if (fired) return;
|
|
1700
|
+
fired = true;
|
|
1701
|
+
onSettled();
|
|
1702
|
+
};
|
|
1703
|
+
const dispose = diffEditor.onDidUpdateDiff(() => {
|
|
1704
|
+
// onDidUpdateDiff only signals that diff DATA is ready. Monaco still needs
|
|
1705
|
+
// follow-up frames to insert hide-unchanged view zones and repaint the
|
|
1706
|
+
// folded geometry. Wait RAF → force layout → RAF → small setTimeout so the
|
|
1707
|
+
// paint pipeline is guaranteed to have flushed before we drop the cover.
|
|
1708
|
+
requestAnimationFrame(() => {
|
|
1709
|
+
try {
|
|
1710
|
+
layoutEditor();
|
|
1711
|
+
} catch {
|
|
1712
|
+
// Ignore transient layout failures while Monaco settles.
|
|
1713
|
+
}
|
|
1714
|
+
requestAnimationFrame(() => {
|
|
1715
|
+
setTimeout(settleOnce, 40);
|
|
1716
|
+
});
|
|
1717
|
+
});
|
|
1718
|
+
});
|
|
1719
|
+
// Safety net: force-reveal after 600ms so the UI never stays hidden on edge
|
|
1720
|
+
// cases where onDidUpdateDiff doesn't fire (identical models, Monaco quirks).
|
|
1721
|
+
const timeoutId = setTimeout(settleOnce, 600);
|
|
1722
|
+
pendingDiffReveal = { dispose: { dispose: () => dispose.dispose() }, timeoutId };
|
|
1666
1723
|
}
|
|
1667
1724
|
|
|
1668
1725
|
function revealEditorNow() {
|
|
1669
|
-
|
|
1726
|
+
if (editorCoverEl) editorCoverEl.dataset.active = "false";
|
|
1670
1727
|
}
|
|
1671
1728
|
|
|
1672
1729
|
function mountFile(options = {}) {
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1730
|
+
if (!diffEditor || !singleEditor || !monacoApi) return;
|
|
1731
|
+
const file = activeFile();
|
|
1732
|
+
if (!file) {
|
|
1733
|
+
hideBinaryPreview();
|
|
1734
|
+
setTextEditorMode("diff");
|
|
1735
|
+
currentFileLabelEl.textContent = "No file selected";
|
|
1736
|
+
updateFileHeaderMeta(null);
|
|
1737
|
+
clearViewZones();
|
|
1738
|
+
if (singleModel) {
|
|
1739
|
+
singleEditor.setModel(null);
|
|
1740
|
+
singleModel.dispose();
|
|
1741
|
+
singleModel = null;
|
|
1742
|
+
}
|
|
1743
|
+
if (originalModel) originalModel.dispose();
|
|
1744
|
+
if (modifiedModel) modifiedModel.dispose();
|
|
1745
|
+
originalModel = null;
|
|
1746
|
+
modifiedModel = null;
|
|
1747
|
+
originalModel = monacoApi.editor.createModel("", "plaintext");
|
|
1748
|
+
modifiedModel = monacoApi.editor.createModel("", "plaintext");
|
|
1749
|
+
// Apply options BEFORE setModel so Monaco renders the first frame with the
|
|
1750
|
+
// correct hideUnchangedRegions / renderSideBySide / added-only state.
|
|
1751
|
+
applyEditorOptions();
|
|
1752
|
+
hideEditorForMount();
|
|
1753
|
+
armDiffRevealListener(() => {
|
|
1754
|
+
cancelPendingReveal();
|
|
1755
|
+
revealEditorNow();
|
|
1756
|
+
});
|
|
1757
|
+
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
|
|
1758
|
+
updateDecorations();
|
|
1759
|
+
renderFileComments();
|
|
1760
|
+
requestAnimationFrame(layoutEditor);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
ensureFileLoaded(file.id, state.currentScope);
|
|
1765
|
+
|
|
1766
|
+
const preserveScroll = options.preserveScroll === true;
|
|
1767
|
+
const scrollState = preserveScroll ? captureScrollState() : null;
|
|
1768
|
+
const language = inferLanguage(getScopeFilePath(file) || file.path);
|
|
1769
|
+
const contents = getMountedContents(file, state.currentScope);
|
|
1770
|
+
|
|
1771
|
+
clearViewZones();
|
|
1772
|
+
renderFilePathLabel(getScopeDisplayPath(file, state.currentScope));
|
|
1773
|
+
updateFileHeaderMeta(file);
|
|
1774
|
+
|
|
1775
|
+
if (file.kind !== "text") {
|
|
1776
|
+
cancelPendingReveal();
|
|
1777
|
+
revealEditorNow();
|
|
1778
|
+
renderBinaryPreview(file, contents);
|
|
1779
|
+
renderFileComments();
|
|
1780
|
+
requestAnimationFrame(layoutEditor);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
hideBinaryPreview();
|
|
1785
|
+
if (originalModel) originalModel.dispose();
|
|
1786
|
+
if (modifiedModel) modifiedModel.dispose();
|
|
1787
|
+
originalModel = null;
|
|
1788
|
+
modifiedModel = null;
|
|
1789
|
+
|
|
1790
|
+
if (activeFileUsesSingleEditor()) {
|
|
1791
|
+
cancelPendingReveal();
|
|
1792
|
+
setTextEditorMode("single");
|
|
1793
|
+
if (singleModel) {
|
|
1794
|
+
singleEditor.setModel(null);
|
|
1795
|
+
singleModel.dispose();
|
|
1796
|
+
}
|
|
1797
|
+
singleModel = monacoApi.editor.createModel(contents.modifiedContent, language);
|
|
1798
|
+
singleEditor.setModel(singleModel);
|
|
1799
|
+
applyEditorOptions();
|
|
1800
|
+
hideEditorForMount();
|
|
1801
|
+
syncViewZones();
|
|
1802
|
+
updateDecorations();
|
|
1803
|
+
renderFileComments();
|
|
1804
|
+
requestAnimationFrame(() => {
|
|
1805
|
+
layoutEditor();
|
|
1806
|
+
requestAnimationFrame(() => {
|
|
1807
|
+
layoutEditor();
|
|
1808
|
+
revealEditorNow();
|
|
1809
|
+
});
|
|
1810
|
+
});
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
setTextEditorMode("diff");
|
|
1815
|
+
if (singleModel) {
|
|
1816
|
+
singleEditor.setModel(null);
|
|
1817
|
+
singleModel.dispose();
|
|
1818
|
+
singleModel = null;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
originalModel = monacoApi.editor.createModel(contents.originalContent, language);
|
|
1822
|
+
modifiedModel = monacoApi.editor.createModel(contents.modifiedContent, language);
|
|
1823
|
+
|
|
1824
|
+
// Apply options BEFORE setModel. Otherwise Monaco first renders the new diff
|
|
1825
|
+
// using the PREVIOUS file's options (e.g. hideUnchangedRegions off because the
|
|
1826
|
+
// last file was added-only, or a stale renderSideBySide value) and only collapses
|
|
1827
|
+
// once updateOptions runs a frame later — causing a visible layout shift.
|
|
1828
|
+
applyEditorOptions();
|
|
1829
|
+
hideEditorForMount();
|
|
1830
|
+
// Arm the reveal listener BEFORE setModel so a synchronously-fired
|
|
1831
|
+
// onDidUpdateDiff (possible for small files) can't be missed.
|
|
1832
|
+
armDiffRevealListener(() => {
|
|
1833
|
+
cancelPendingReveal();
|
|
1834
|
+
revealEditorNow();
|
|
1835
|
+
});
|
|
1836
|
+
diffEditor.setModel({ original: originalModel, modified: modifiedModel });
|
|
1837
|
+
// Re-apply editor options AFTER setModel: Monaco can derive original-side
|
|
1838
|
+
// wrap overrides (`wordWrapOverride1`) from the swapped-in model, leaving the
|
|
1839
|
+
// pre-setModel `applyEditorOptions()` call partially overridden. A second
|
|
1840
|
+
// call ensures both sides observe the user's wrap toggle symmetrically.
|
|
1841
|
+
applyEditorOptions();
|
|
1842
|
+
syncViewZones();
|
|
1843
|
+
updateDecorations();
|
|
1844
|
+
renderFileComments();
|
|
1845
|
+
requestAnimationFrame(() => {
|
|
1846
|
+
layoutEditor();
|
|
1847
|
+
if (options.restoreFileScroll) restoreFileScrollPosition();
|
|
1848
|
+
if (options.preserveScroll) restoreScrollState(scrollState);
|
|
1849
|
+
setTimeout(() => {
|
|
1850
|
+
layoutEditor();
|
|
1851
|
+
if (options.restoreFileScroll) restoreFileScrollPosition();
|
|
1852
|
+
if (options.preserveScroll) restoreScrollState(scrollState);
|
|
1853
|
+
}, 50);
|
|
1854
|
+
});
|
|
1798
1855
|
}
|
|
1799
1856
|
|
|
1800
1857
|
function syncCommentBodiesFromDOM() {
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1858
|
+
const textareas = document.querySelectorAll("textarea[data-comment-id]");
|
|
1859
|
+
textareas.forEach((textarea) => {
|
|
1860
|
+
const commentId = textarea.getAttribute("data-comment-id");
|
|
1861
|
+
const comment = state.comments.find((item) => item.id === commentId);
|
|
1862
|
+
if (comment) comment.body = textarea.value;
|
|
1863
|
+
});
|
|
1807
1864
|
}
|
|
1808
1865
|
|
|
1809
1866
|
function buildSubmitPayload() {
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1867
|
+
syncCommentBodiesFromDOM();
|
|
1868
|
+
return {
|
|
1869
|
+
type: "submit",
|
|
1870
|
+
overallComment: state.overallComment.trim(),
|
|
1871
|
+
comments: state.comments
|
|
1872
|
+
.map((comment) => ({ ...comment, body: comment.body.trim() }))
|
|
1873
|
+
.filter((comment) => comment.body.length > 0),
|
|
1874
|
+
};
|
|
1818
1875
|
}
|
|
1819
1876
|
|
|
1820
1877
|
function hasSubmitPayloadContent(payload) {
|
|
1821
|
-
|
|
1878
|
+
return payload.overallComment.length > 0 || payload.comments.length > 0;
|
|
1822
1879
|
}
|
|
1823
1880
|
|
|
1824
1881
|
function submitDraftOnClose() {
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1882
|
+
if (suppressAutoSubmitOnClose) return;
|
|
1883
|
+
const payload = buildSubmitPayload();
|
|
1884
|
+
if (!hasSubmitPayloadContent(payload)) return;
|
|
1885
|
+
suppressAutoSubmitOnClose = true;
|
|
1886
|
+
window.glimpse?.send?.({ ...payload, draft: true });
|
|
1830
1887
|
}
|
|
1831
1888
|
|
|
1832
1889
|
function updateCommentsUI() {
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1890
|
+
renderTree();
|
|
1891
|
+
syncViewZones();
|
|
1892
|
+
updateDecorations();
|
|
1893
|
+
renderFileComments();
|
|
1837
1894
|
}
|
|
1838
1895
|
|
|
1839
1896
|
function renderAll(options = {}) {
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1897
|
+
renderTree();
|
|
1898
|
+
renderCommitList();
|
|
1899
|
+
submitButton.disabled = state.assetInitFailed;
|
|
1900
|
+
if (state.assetInitFailed) {
|
|
1901
|
+
renderFileComments();
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
if (diffEditor && singleEditor && monacoApi) {
|
|
1905
|
+
mountFile(options);
|
|
1906
|
+
requestAnimationFrame(() => {
|
|
1907
|
+
layoutEditor();
|
|
1908
|
+
setTimeout(layoutEditor, 50);
|
|
1909
|
+
});
|
|
1910
|
+
} else {
|
|
1911
|
+
renderFileComments();
|
|
1912
|
+
}
|
|
1856
1913
|
}
|
|
1857
1914
|
|
|
1858
1915
|
function getFocusedTextEditor() {
|
|
1859
|
-
|
|
1860
|
-
|
|
1916
|
+
const editors = [
|
|
1917
|
+
singleEditor,
|
|
1918
|
+
diffEditor?.getModifiedEditor?.(),
|
|
1919
|
+
diffEditor?.getOriginalEditor?.(),
|
|
1920
|
+
].filter(Boolean);
|
|
1921
|
+
return editors.find((editor) => editor?.hasTextFocus?.() || editor?.hasWidgetFocus?.()) ?? null;
|
|
1861
1922
|
}
|
|
1862
1923
|
|
|
1863
1924
|
function getEditorSelectionText(editor) {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1925
|
+
const model = editor?.getModel?.();
|
|
1926
|
+
const selections = editor?.getSelections?.() ?? [];
|
|
1927
|
+
if (!model || selections.length === 0) return "";
|
|
1928
|
+
return selections
|
|
1929
|
+
.filter((selection) => !selection.isEmpty())
|
|
1930
|
+
.map((selection) => model.getValueInRange(selection))
|
|
1931
|
+
.join("\n");
|
|
1871
1932
|
}
|
|
1872
1933
|
|
|
1873
1934
|
window.__reviewClipboard = {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1935
|
+
getSelectedText() {
|
|
1936
|
+
const editor = getFocusedTextEditor();
|
|
1937
|
+
return editor ? getEditorSelectionText(editor) : "";
|
|
1938
|
+
},
|
|
1939
|
+
selectAll() {
|
|
1940
|
+
const editor = getFocusedTextEditor();
|
|
1941
|
+
const model = editor?.getModel?.();
|
|
1942
|
+
if (!editor || !model) return false;
|
|
1943
|
+
editor.setSelection(model.getFullModelRange());
|
|
1944
|
+
return true;
|
|
1945
|
+
},
|
|
1885
1946
|
};
|
|
1886
1947
|
|
|
1887
1948
|
function createGlyphHoverActions(editor, side) {
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1949
|
+
let hoverDecoration = [];
|
|
1950
|
+
|
|
1951
|
+
function openDraftAtLine(line) {
|
|
1952
|
+
const file = activeFile();
|
|
1953
|
+
if (!file || !canCommentOnSide(file, side) || !isActiveFileReady()) return;
|
|
1954
|
+
state.comments.push(
|
|
1955
|
+
annotateCommentWithCommit({
|
|
1956
|
+
id: `${Date.now()}:${Math.random().toString(16).slice(2)}`,
|
|
1957
|
+
fileId: file.id,
|
|
1958
|
+
scope: state.currentScope,
|
|
1959
|
+
side,
|
|
1960
|
+
startLine: line,
|
|
1961
|
+
endLine: line,
|
|
1962
|
+
body: "",
|
|
1963
|
+
}),
|
|
1964
|
+
);
|
|
1965
|
+
updateCommentsUI();
|
|
1966
|
+
editor.revealLineInCenter(line);
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
editor.onMouseMove((event) => {
|
|
1970
|
+
const file = activeFile();
|
|
1971
|
+
if (!file || !canCommentOnSide(file, side) || !isActiveFileReady()) {
|
|
1972
|
+
hoverDecoration = editor.deltaDecorations(hoverDecoration, []);
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
const target = event.target;
|
|
1977
|
+
if (
|
|
1978
|
+
target.type === monacoApi.editor.MouseTargetType.GUTTER_GLYPH_MARGIN ||
|
|
1979
|
+
target.type === monacoApi.editor.MouseTargetType.GUTTER_LINE_NUMBERS
|
|
1980
|
+
) {
|
|
1981
|
+
const line = target.position?.lineNumber;
|
|
1982
|
+
if (!line) return;
|
|
1983
|
+
hoverDecoration = editor.deltaDecorations(hoverDecoration, [
|
|
1984
|
+
{
|
|
1985
|
+
range: new monacoApi.Range(line, 1, line, 1),
|
|
1986
|
+
options: { glyphMarginClassName: "review-glyph-plus" },
|
|
1987
|
+
},
|
|
1988
|
+
]);
|
|
1989
|
+
} else {
|
|
1990
|
+
hoverDecoration = editor.deltaDecorations(hoverDecoration, []);
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
|
|
1994
|
+
editor.onMouseLeave(() => {
|
|
1995
|
+
hoverDecoration = editor.deltaDecorations(hoverDecoration, []);
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
editor.onMouseDown((event) => {
|
|
1999
|
+
const file = activeFile();
|
|
2000
|
+
if (!file || !canCommentOnSide(file, side) || !isActiveFileReady()) return;
|
|
2001
|
+
|
|
2002
|
+
const target = event.target;
|
|
2003
|
+
if (
|
|
2004
|
+
target.type === monacoApi.editor.MouseTargetType.GUTTER_GLYPH_MARGIN ||
|
|
2005
|
+
target.type === monacoApi.editor.MouseTargetType.GUTTER_LINE_NUMBERS
|
|
2006
|
+
) {
|
|
2007
|
+
const line = target.position?.lineNumber;
|
|
2008
|
+
if (!line) return;
|
|
2009
|
+
openDraftAtLine(line);
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
1951
2012
|
}
|
|
1952
2013
|
|
|
1953
2014
|
window.__reviewReceive = (message) => {
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2015
|
+
if (!message || typeof message !== "object") return;
|
|
2016
|
+
|
|
2017
|
+
if (message.type === "clipboard-data") {
|
|
2018
|
+
window.__reviewReceiveClipboardData?.(message);
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (message.type === "working-tree-changed") {
|
|
2023
|
+
const observedAt = Date.now();
|
|
2024
|
+
state.localChangesDetected = true;
|
|
2025
|
+
state.lastLocalChangeDetectedAt = message.changedAt ?? observedAt;
|
|
2026
|
+
state.lastLocalChangeObservedAt = observedAt;
|
|
2027
|
+
renderTree();
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
if (message.type === "review-data") {
|
|
2032
|
+
if (state.reviewDataRequestId !== message.requestId) return;
|
|
2033
|
+
clearRefreshableFileState();
|
|
2034
|
+
clearWorkingTreeCommitState();
|
|
2035
|
+
const nextFiles = Array.isArray(message.files) ? message.files : [];
|
|
2036
|
+
const nextCommits = Array.isArray(message.commits) ? message.commits : [];
|
|
2037
|
+
reconcileCommitState(nextCommits);
|
|
2038
|
+
reviewData.files = nextFiles;
|
|
2039
|
+
reviewData.commits = nextCommits;
|
|
2040
|
+
reviewData.branchBaseRef = message.branchBaseRef ?? null;
|
|
2041
|
+
reviewData.branchMergeBaseSha = message.branchMergeBaseSha ?? null;
|
|
2042
|
+
reviewData.repositoryHasHead = message.repositoryHasHead === true;
|
|
2043
|
+
const requestStartedAt = state.reviewDataRequestStartedAt;
|
|
2044
|
+
state.reviewDataRequestId = null;
|
|
2045
|
+
state.reviewDataRequestStartedAt = null;
|
|
2046
|
+
state.lastWorkingTreeLoadAt = null;
|
|
2047
|
+
const localChangeAfterRequest =
|
|
2048
|
+
requestStartedAt == null ||
|
|
2049
|
+
(state.lastLocalChangeObservedAt != null &&
|
|
2050
|
+
state.lastLocalChangeObservedAt > requestStartedAt);
|
|
2051
|
+
if (!localChangeAfterRequest) {
|
|
2052
|
+
state.localChangesDetected = false;
|
|
2053
|
+
state.lastLocalChangeDetectedAt = null;
|
|
2054
|
+
state.lastLocalChangeObservedAt = null;
|
|
2055
|
+
}
|
|
2056
|
+
if (!reviewData.commits.some((commit) => commit.sha === state.selectedCommitSha)) {
|
|
2057
|
+
state.selectedCommitSha = reviewData.commits[0]?.sha ?? null;
|
|
2058
|
+
}
|
|
2059
|
+
renderCommitList();
|
|
2060
|
+
renderAll({ restoreFileScroll: false });
|
|
2061
|
+
if (state.currentScope === "commits" && state.selectedCommitSha) {
|
|
2062
|
+
ensureCommitFilesLoaded(state.selectedCommitSha, {
|
|
2063
|
+
forceRefresh: isWorkingTreeCommit(state.selectedCommitSha),
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
if (message.type === "review-data-error") {
|
|
2070
|
+
if (state.reviewDataRequestId !== message.requestId) return;
|
|
2071
|
+
state.reviewDataRequestId = null;
|
|
2072
|
+
state.reviewDataRequestStartedAt = null;
|
|
2073
|
+
updateReviewRefreshButton();
|
|
2074
|
+
alert(`Failed to refresh review data: ${message.message || "Unknown error"}`);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
if (message.type === "commit-data") {
|
|
2079
|
+
if (state.commitRequestIds[message.sha] !== message.requestId) return;
|
|
2080
|
+
state.commitFilesBySha[message.sha] = Array.isArray(message.files) ? message.files : [];
|
|
2081
|
+
delete state.commitErrors[message.sha];
|
|
2082
|
+
delete state.commitRequestIds[message.sha];
|
|
2083
|
+
if (isWorkingTreeCommit(message.sha)) {
|
|
2084
|
+
state.lastWorkingTreeLoadAt = Date.now();
|
|
2085
|
+
}
|
|
2086
|
+
renderCommitList();
|
|
2087
|
+
if (state.currentScope === "commits" && state.selectedCommitSha === message.sha) {
|
|
2088
|
+
ensureActiveFileForScope();
|
|
2089
|
+
renderAll({ restoreFileScroll: false });
|
|
2090
|
+
const file = activeFile();
|
|
2091
|
+
if (file) {
|
|
2092
|
+
ensureFileLoaded(file.id, state.currentScope, message.sha, {
|
|
2093
|
+
forceRefresh: isWorkingTreeCommit(message.sha),
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
if (message.type === "commit-error") {
|
|
2101
|
+
if (state.commitRequestIds[message.sha] !== message.requestId) return;
|
|
2102
|
+
state.commitErrors[message.sha] = message.message || "Unknown error";
|
|
2103
|
+
delete state.commitRequestIds[message.sha];
|
|
2104
|
+
renderCommitList();
|
|
2105
|
+
updateReviewRefreshButton();
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
const key = cacheKey(message.scope, message.fileId, message.commitSha ?? null);
|
|
2110
|
+
|
|
2111
|
+
if (message.type === "file-data") {
|
|
2112
|
+
if (state.pendingRequestIds[key] !== message.requestId) return;
|
|
2113
|
+
state.fileContents[key] = {
|
|
2114
|
+
originalContent: message.originalContent,
|
|
2115
|
+
modifiedContent: message.modifiedContent,
|
|
2116
|
+
kind: message.kind,
|
|
2117
|
+
mimeType: message.mimeType,
|
|
2118
|
+
originalExists: message.originalExists,
|
|
2119
|
+
modifiedExists: message.modifiedExists,
|
|
2120
|
+
originalPreviewUrl: message.originalPreviewUrl,
|
|
2121
|
+
modifiedPreviewUrl: message.modifiedPreviewUrl,
|
|
2122
|
+
};
|
|
2123
|
+
delete state.fileErrors[key];
|
|
2124
|
+
delete state.pendingRequestIds[key];
|
|
2125
|
+
renderTree();
|
|
2126
|
+
if (state.activeFileId === message.fileId && state.currentScope === message.scope) {
|
|
2127
|
+
mountFile({ restoreFileScroll: true });
|
|
2128
|
+
}
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
if (message.type === "file-error") {
|
|
2133
|
+
if (state.pendingRequestIds[key] !== message.requestId) return;
|
|
2134
|
+
state.fileErrors[key] = message.message || "Unknown error";
|
|
2135
|
+
delete state.pendingRequestIds[key];
|
|
2136
|
+
renderTree();
|
|
2137
|
+
if (state.activeFileId === message.fileId && state.currentScope === message.scope) {
|
|
2138
|
+
mountFile({ preserveScroll: false });
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2079
2141
|
};
|
|
2080
2142
|
|
|
2081
2143
|
function resolveMonacoWorkerPath(vsBaseUrl, label) {
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2144
|
+
switch (label) {
|
|
2145
|
+
case "json":
|
|
2146
|
+
return `${vsBaseUrl}/language/json/jsonWorker.js`;
|
|
2147
|
+
case "css":
|
|
2148
|
+
case "scss":
|
|
2149
|
+
case "less":
|
|
2150
|
+
return `${vsBaseUrl}/language/css/cssWorker.js`;
|
|
2151
|
+
case "html":
|
|
2152
|
+
case "handlebars":
|
|
2153
|
+
case "razor":
|
|
2154
|
+
return `${vsBaseUrl}/language/html/htmlWorker.js`;
|
|
2155
|
+
case "typescript":
|
|
2156
|
+
case "javascript":
|
|
2157
|
+
return `${vsBaseUrl}/language/typescript/tsWorker.js`;
|
|
2158
|
+
default:
|
|
2159
|
+
return `${vsBaseUrl}/base/worker/workerMain.js`;
|
|
2160
|
+
}
|
|
2099
2161
|
}
|
|
2100
2162
|
|
|
2101
2163
|
function setupMonaco() {
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2164
|
+
if (reviewAssetConfig.bootstrapError) {
|
|
2165
|
+
showAssetFailure(
|
|
2166
|
+
"The review window could not finish loading.",
|
|
2167
|
+
"annotate-git-diff could not load its packaged review assets. Close this window and rerun /annotate-git-diff after reinstalling or updating the annotate-git-diff package.",
|
|
2168
|
+
reviewAssetConfig.bootstrapError,
|
|
2169
|
+
);
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
const vsBaseUrl =
|
|
2174
|
+
typeof reviewAssetConfig.monacoVsBaseUrl === "string" ? reviewAssetConfig.monacoVsBaseUrl : "";
|
|
2175
|
+
if (!vsBaseUrl) {
|
|
2176
|
+
showAssetFailure(
|
|
2177
|
+
"The review window could not finish loading.",
|
|
2178
|
+
"annotate-git-diff could not locate its packaged Monaco assets. Close this window and rerun /annotate-git-diff after reinstalling or updating the annotate-git-diff package.",
|
|
2179
|
+
);
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
if (!window.require || typeof window.require.config !== "function") {
|
|
2184
|
+
showAssetFailure(
|
|
2185
|
+
"The review window could not finish loading.",
|
|
2186
|
+
"annotate-git-diff could not initialize the packaged Monaco loader. Close this window and rerun /annotate-git-diff.",
|
|
2187
|
+
);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
const workerBaseUrl = vsBaseUrl.endsWith("/") ? vsBaseUrl : `${vsBaseUrl}/`;
|
|
2192
|
+
const workerUrls = new Map();
|
|
2193
|
+
window.MonacoEnvironment = {
|
|
2194
|
+
baseUrl: workerBaseUrl,
|
|
2195
|
+
getWorkerUrl(_moduleId, label) {
|
|
2196
|
+
const cacheKey = label || "default";
|
|
2197
|
+
if (!workerUrls.has(cacheKey)) {
|
|
2198
|
+
const workerSource = [
|
|
2199
|
+
`self.MonacoEnvironment = { baseUrl: ${JSON.stringify(workerBaseUrl)} };`,
|
|
2200
|
+
`importScripts(${JSON.stringify(resolveMonacoWorkerPath(vsBaseUrl, label))});`,
|
|
2201
|
+
].join("\n");
|
|
2202
|
+
workerUrls.set(
|
|
2203
|
+
cacheKey,
|
|
2204
|
+
URL.createObjectURL(new Blob([workerSource], { type: "text/javascript" })),
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
return workerUrls.get(cacheKey);
|
|
2208
|
+
},
|
|
2209
|
+
};
|
|
2210
|
+
window.addEventListener(
|
|
2211
|
+
"unload",
|
|
2212
|
+
() => {
|
|
2213
|
+
for (const workerUrl of workerUrls.values()) {
|
|
2214
|
+
URL.revokeObjectURL(workerUrl);
|
|
2215
|
+
}
|
|
2216
|
+
workerUrls.clear();
|
|
2217
|
+
},
|
|
2218
|
+
{ once: true },
|
|
2219
|
+
);
|
|
2220
|
+
|
|
2221
|
+
let settled = false;
|
|
2222
|
+
const settleFailure = (message, error) => {
|
|
2223
|
+
if (settled) return;
|
|
2224
|
+
settled = true;
|
|
2225
|
+
showAssetFailure(
|
|
2226
|
+
"The review window could not finish loading.",
|
|
2227
|
+
message,
|
|
2228
|
+
formatAssetErrorDetail(error),
|
|
2229
|
+
);
|
|
2230
|
+
updateToggleButtons();
|
|
2231
|
+
};
|
|
2232
|
+
const settleSuccess = () => {
|
|
2233
|
+
if (settled) return false;
|
|
2234
|
+
settled = true;
|
|
2235
|
+
clearTimeout(loadTimeoutId);
|
|
2236
|
+
clearAssetFailure();
|
|
2237
|
+
return true;
|
|
2238
|
+
};
|
|
2239
|
+
const loadTimeoutId = setTimeout(() => {
|
|
2240
|
+
settleFailure(
|
|
2241
|
+
"annotate-git-diff timed out while loading the packaged Monaco editor. Close this window and rerun /annotate-git-diff.",
|
|
2242
|
+
"Timed out waiting for Monaco to finish loading.",
|
|
2243
|
+
);
|
|
2244
|
+
}, 10000);
|
|
2245
|
+
|
|
2246
|
+
try {
|
|
2247
|
+
window.require.config({
|
|
2248
|
+
paths: {
|
|
2249
|
+
vs: vsBaseUrl,
|
|
2250
|
+
},
|
|
2251
|
+
});
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
clearTimeout(loadTimeoutId);
|
|
2254
|
+
settleFailure(
|
|
2255
|
+
"annotate-git-diff could not configure the packaged Monaco editor. Close this window and rerun /annotate-git-diff.",
|
|
2256
|
+
error,
|
|
2257
|
+
);
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
window.require(
|
|
2262
|
+
["vs/editor/editor.main"],
|
|
2263
|
+
() => {
|
|
2264
|
+
if (!settleSuccess()) return;
|
|
2265
|
+
monacoApi = window.monaco;
|
|
2266
|
+
|
|
2267
|
+
// GitHub-style diff colors.
|
|
2268
|
+
monacoApi.editor.defineTheme("review-dark", {
|
|
2269
|
+
base: "vs-dark",
|
|
2270
|
+
inherit: true,
|
|
2271
|
+
rules: [],
|
|
2272
|
+
colors: {
|
|
2273
|
+
"editor.background": "#0d1117",
|
|
2274
|
+
"editor.foreground": "#f0f6fc",
|
|
2275
|
+
"editorLineNumber.foreground": "#6e7681",
|
|
2276
|
+
"editorLineNumber.activeForeground": "#c9d1d9",
|
|
2277
|
+
"editorGutter.background": "#0d1117",
|
|
2278
|
+
"diffEditor.insertedLineBackground": "#1c7c3c33",
|
|
2279
|
+
"diffEditor.insertedTextBackground": "#2ea04366",
|
|
2280
|
+
"diffEditor.removedLineBackground": "#b62a2233",
|
|
2281
|
+
"diffEditor.removedTextBackground": "#f8514966",
|
|
2282
|
+
"diffEditor.border": "#30363d",
|
|
2283
|
+
"editorOverviewRuler.border": "#30363d",
|
|
2284
|
+
},
|
|
2285
|
+
});
|
|
2286
|
+
monacoApi.editor.setTheme("review-dark");
|
|
2287
|
+
|
|
2288
|
+
diffEditor = monacoApi.editor.createDiffEditor(diffEditorHostEl, {
|
|
2289
|
+
automaticLayout: true,
|
|
2290
|
+
renderSideBySide: activeFileShowsDiff(),
|
|
2291
|
+
readOnly: true,
|
|
2292
|
+
originalEditable: false,
|
|
2293
|
+
// GitHub-style review: no minimap and no diff overview ruler on the side.
|
|
2294
|
+
// Those were the panels that visibly flashed during remount because Monaco
|
|
2295
|
+
// repaints them as hide-unchanged view zones settle.
|
|
2296
|
+
minimap: { enabled: false },
|
|
2297
|
+
renderOverviewRuler: false,
|
|
2298
|
+
overviewRulerLanes: 0,
|
|
2299
|
+
diffWordWrap: "on",
|
|
2300
|
+
scrollBeyondLastLine: false,
|
|
2301
|
+
lineNumbersMinChars: 4,
|
|
2302
|
+
glyphMargin: true,
|
|
2303
|
+
folding: true,
|
|
2304
|
+
lineDecorationsWidth: 10,
|
|
2305
|
+
overviewRulerBorder: false,
|
|
2306
|
+
wordWrap: "on",
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2309
|
+
singleEditor = monacoApi.editor.create(singleEditorHostEl, {
|
|
2310
|
+
automaticLayout: true,
|
|
2311
|
+
readOnly: true,
|
|
2312
|
+
minimap: { enabled: false },
|
|
2313
|
+
renderOverviewRuler: false,
|
|
2314
|
+
overviewRulerLanes: 0,
|
|
2315
|
+
scrollBeyondLastLine: false,
|
|
2316
|
+
lineNumbersMinChars: 4,
|
|
2317
|
+
glyphMargin: true,
|
|
2318
|
+
folding: true,
|
|
2319
|
+
lineDecorationsWidth: 10,
|
|
2320
|
+
overviewRulerBorder: false,
|
|
2321
|
+
wordWrap: state.wrapLines ? "on" : "off",
|
|
2322
|
+
});
|
|
2323
|
+
|
|
2324
|
+
createGlyphHoverActions(diffEditor.getOriginalEditor(), "original");
|
|
2325
|
+
createGlyphHoverActions(diffEditor.getModifiedEditor(), "modified");
|
|
2326
|
+
createGlyphHoverActions(singleEditor, "modified");
|
|
2327
|
+
|
|
2328
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2329
|
+
editorResizeObserver = new ResizeObserver(() => {
|
|
2330
|
+
layoutEditor();
|
|
2331
|
+
});
|
|
2332
|
+
editorResizeObserver.observe(editorContainerEl);
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
requestAnimationFrame(() => {
|
|
2336
|
+
layoutEditor();
|
|
2337
|
+
setTimeout(layoutEditor, 50);
|
|
2338
|
+
setTimeout(layoutEditor, 150);
|
|
2339
|
+
});
|
|
2340
|
+
|
|
2341
|
+
mountFile();
|
|
2342
|
+
updateToggleButtons();
|
|
2343
|
+
},
|
|
2344
|
+
(error) => {
|
|
2345
|
+
clearTimeout(loadTimeoutId);
|
|
2346
|
+
settleFailure(
|
|
2347
|
+
"annotate-git-diff could not load the packaged Monaco editor. Close this window and rerun /annotate-git-diff.",
|
|
2348
|
+
error,
|
|
2349
|
+
);
|
|
2350
|
+
},
|
|
2351
|
+
);
|
|
2289
2352
|
}
|
|
2290
2353
|
|
|
2291
2354
|
function switchScope(scope) {
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2355
|
+
const hasScopeFiles = {
|
|
2356
|
+
branch: reviewData.files.some((file) => file.inGitDiff),
|
|
2357
|
+
commits: reviewData.commits.length > 0,
|
|
2358
|
+
all: reviewData.files.length > 0,
|
|
2359
|
+
};
|
|
2360
|
+
if (!hasScopeFiles[scope] || state.currentScope === scope) return;
|
|
2361
|
+
saveCurrentScrollPosition();
|
|
2362
|
+
state.currentScope = scope;
|
|
2363
|
+
state.activeFileId = null;
|
|
2364
|
+
if (scope === "commits") {
|
|
2365
|
+
if (!state.selectedCommitSha && reviewData.commits[0]) {
|
|
2366
|
+
state.selectedCommitSha = reviewData.commits[0].sha;
|
|
2367
|
+
}
|
|
2368
|
+
if (state.selectedCommitSha) ensureCommitFilesLoaded(state.selectedCommitSha);
|
|
2369
|
+
}
|
|
2370
|
+
renderAll({ restoreFileScroll: true });
|
|
2371
|
+
const file = activeFile();
|
|
2372
|
+
if (file) ensureFileLoaded(file.id, state.currentScope);
|
|
2310
2373
|
}
|
|
2311
2374
|
|
|
2312
2375
|
window.addEventListener("beforeunload", submitDraftOnClose);
|
|
2313
2376
|
window.addEventListener("pagehide", submitDraftOnClose);
|
|
2314
2377
|
|
|
2315
2378
|
submitButton.addEventListener("click", () => {
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2379
|
+
const payload = buildSubmitPayload();
|
|
2380
|
+
suppressAutoSubmitOnClose = true;
|
|
2381
|
+
window.glimpse.send({ ...payload, draft: false });
|
|
2382
|
+
window.glimpse.close();
|
|
2320
2383
|
});
|
|
2321
2384
|
|
|
2322
2385
|
cancelButton.addEventListener("click", () => {
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2386
|
+
suppressAutoSubmitOnClose = true;
|
|
2387
|
+
window.glimpse.send({ type: "cancel" });
|
|
2388
|
+
window.glimpse.close();
|
|
2326
2389
|
});
|
|
2327
2390
|
|
|
2328
2391
|
overallCommentButton.addEventListener("click", () => {
|
|
2329
|
-
|
|
2392
|
+
showOverallCommentPopover();
|
|
2330
2393
|
});
|
|
2331
2394
|
|
|
2332
2395
|
fileCommentButton.addEventListener("click", () => {
|
|
2333
|
-
|
|
2396
|
+
showFileCommentPopover();
|
|
2334
2397
|
});
|
|
2335
2398
|
|
|
2336
2399
|
if (refreshReviewButton) {
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2400
|
+
refreshReviewButton.addEventListener("click", () => {
|
|
2401
|
+
refreshReviewData();
|
|
2402
|
+
});
|
|
2340
2403
|
}
|
|
2341
2404
|
|
|
2342
2405
|
toggleUnchangedButton.addEventListener("click", () => {
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2406
|
+
state.hideUnchanged = !state.hideUnchanged;
|
|
2407
|
+
applyEditorOptions();
|
|
2408
|
+
updateToggleButtons();
|
|
2409
|
+
requestAnimationFrame(layoutEditor);
|
|
2347
2410
|
});
|
|
2348
2411
|
|
|
2349
2412
|
toggleWrapButton.addEventListener("click", () => {
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2413
|
+
state.wrapLines = !state.wrapLines;
|
|
2414
|
+
applyEditorOptions();
|
|
2415
|
+
updateToggleButtons();
|
|
2416
|
+
requestAnimationFrame(() => {
|
|
2417
|
+
layoutEditor();
|
|
2418
|
+
setTimeout(layoutEditor, 50);
|
|
2419
|
+
});
|
|
2357
2420
|
});
|
|
2358
2421
|
|
|
2359
2422
|
toggleReviewedButton.addEventListener("click", () => {
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2423
|
+
const file = activeFile();
|
|
2424
|
+
if (!file) return;
|
|
2425
|
+
state.reviewedFiles[file.id] = !isFileReviewed(file.id);
|
|
2426
|
+
renderTree();
|
|
2364
2427
|
});
|
|
2365
2428
|
|
|
2366
2429
|
scopeBranchButton.addEventListener("click", () => {
|
|
2367
|
-
|
|
2430
|
+
switchScope("branch");
|
|
2368
2431
|
});
|
|
2369
2432
|
|
|
2370
2433
|
scopeCommitsButton.addEventListener("click", () => {
|
|
2371
|
-
|
|
2434
|
+
switchScope("commits");
|
|
2372
2435
|
});
|
|
2373
2436
|
|
|
2374
2437
|
scopeAllButton.addEventListener("click", () => {
|
|
2375
|
-
|
|
2438
|
+
switchScope("all");
|
|
2376
2439
|
});
|
|
2377
2440
|
|
|
2378
2441
|
toggleSidebarButton.addEventListener("click", () => {
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2442
|
+
state.sidebarCollapsed = !state.sidebarCollapsed;
|
|
2443
|
+
updateSidebarLayout();
|
|
2444
|
+
requestAnimationFrame(() => {
|
|
2445
|
+
layoutEditor();
|
|
2446
|
+
setTimeout(layoutEditor, 50);
|
|
2447
|
+
});
|
|
2385
2448
|
});
|
|
2386
2449
|
|
|
2387
2450
|
const fileCollapseButton = document.getElementById("file-collapse-button");
|
|
2388
2451
|
if (fileCollapseButton) {
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2452
|
+
fileCollapseButton.addEventListener("click", () => {
|
|
2453
|
+
const collapsed = editorContainerEl.style.display === "none";
|
|
2454
|
+
editorContainerEl.style.display = collapsed ? "" : "none";
|
|
2455
|
+
fileCollapseButton.dataset.active = collapsed ? "true" : "false";
|
|
2456
|
+
fileCollapseButton.title = collapsed ? "Collapse file" : "Expand file";
|
|
2457
|
+
const svg = fileCollapseButton.querySelector("svg path");
|
|
2458
|
+
if (svg) {
|
|
2459
|
+
svg.setAttribute(
|
|
2460
|
+
"d",
|
|
2461
|
+
collapsed
|
|
2462
|
+
? "M12.78 6.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 7.28a.749.749 0 1 1 1.06-1.06L8 9.939l3.72-3.719a.749.749 0 0 1 1.06 0Z"
|
|
2463
|
+
: "M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z",
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
requestAnimationFrame(() => {
|
|
2467
|
+
layoutEditor();
|
|
2468
|
+
setTimeout(layoutEditor, 50);
|
|
2469
|
+
});
|
|
2470
|
+
});
|
|
2408
2471
|
}
|
|
2409
2472
|
|
|
2410
2473
|
sidebarSearchInputEl.addEventListener("input", () => {
|
|
2411
|
-
|
|
2412
|
-
|
|
2474
|
+
state.fileFilter = sidebarSearchInputEl.value;
|
|
2475
|
+
renderTree();
|
|
2413
2476
|
});
|
|
2414
2477
|
|
|
2415
2478
|
sidebarSearchInputEl.addEventListener("keydown", (event) => {
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2479
|
+
if (event.key === "Escape") {
|
|
2480
|
+
sidebarSearchInputEl.value = "";
|
|
2481
|
+
state.fileFilter = "";
|
|
2482
|
+
renderTree();
|
|
2483
|
+
}
|
|
2421
2484
|
});
|
|
2422
2485
|
|
|
2423
2486
|
if (state.currentScope === "commits" && state.selectedCommitSha) {
|
|
2424
|
-
|
|
2487
|
+
ensureCommitFilesLoaded(state.selectedCommitSha);
|
|
2425
2488
|
}
|
|
2426
2489
|
ensureActiveFileForScope();
|
|
2427
2490
|
renderTree();
|