blob: 6c4feb1b9074a56ce31c67cc3d94f23ea113d074 [file] [log] [blame]
Sean McCullough71941bd2025-04-18 13:31:48 -07001import { css, html, LitElement, unsafeCSS } from "lit";
2import { customElement, property, state } from "lit/decorators.js";
Sean McCullough86b56862025-04-18 13:04:03 -07003import * as Diff2Html from "diff2html";
4
Sean McCullough71941bd2025-04-18 13:31:48 -07005@customElement("sketch-diff-view")
Sean McCullough86b56862025-04-18 13:04:03 -07006export class SketchDiffView extends LitElement {
7 // Current commit hash being viewed
Sean McCullough71941bd2025-04-18 13:31:48 -07008 @property({ type: String })
Sean McCullough86b56862025-04-18 13:04:03 -07009 commitHash: string = "";
10
11 // Selected line in the diff for commenting
12 @state()
13 private selectedDiffLine: string | null = null;
14
15 // View format (side-by-side or line-by-line)
16 @state()
17 private viewFormat: "side-by-side" | "line-by-line" = "side-by-side";
18
19 static styles = css`
20 .diff-view {
21 flex: 1;
22 display: flex;
23 flex-direction: column;
24 overflow: hidden;
25 height: 100%;
26 }
Sean McCullough71941bd2025-04-18 13:31:48 -070027
Sean McCullough86b56862025-04-18 13:04:03 -070028 .diff-container {
29 height: 100%;
30 overflow: auto;
31 flex: 1;
32 padding: 0 1rem;
33 }
Sean McCullough71941bd2025-04-18 13:31:48 -070034
Sean McCullough86b56862025-04-18 13:04:03 -070035 #diff-view-controls {
36 display: flex;
37 justify-content: flex-end;
38 padding: 10px;
39 background: #f8f8f8;
40 border-bottom: 1px solid #eee;
41 }
Sean McCullough71941bd2025-04-18 13:31:48 -070042
Sean McCullough86b56862025-04-18 13:04:03 -070043 .diff-view-format {
44 display: flex;
45 gap: 10px;
46 }
Sean McCullough71941bd2025-04-18 13:31:48 -070047
Sean McCullough86b56862025-04-18 13:04:03 -070048 .diff-view-format label {
49 cursor: pointer;
50 }
Sean McCullough71941bd2025-04-18 13:31:48 -070051
Sean McCullough86b56862025-04-18 13:04:03 -070052 .diff2html-content {
53 font-family: var(--monospace-font);
54 position: relative;
55 }
Sean McCullough71941bd2025-04-18 13:31:48 -070056
Sean McCullough86b56862025-04-18 13:04:03 -070057 /* Comment box styles */
58 .diff-comment-box {
59 position: fixed;
60 bottom: 80px;
61 right: 20px;
62 width: 400px;
63 background-color: white;
64 border: 1px solid #ddd;
65 border-radius: 4px;
66 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
67 padding: 16px;
68 z-index: 1000;
69 }
Sean McCullough71941bd2025-04-18 13:31:48 -070070
Sean McCullough86b56862025-04-18 13:04:03 -070071 .diff-comment-box h3 {
72 margin-top: 0;
73 margin-bottom: 10px;
74 font-size: 16px;
75 }
Sean McCullough71941bd2025-04-18 13:31:48 -070076
Sean McCullough86b56862025-04-18 13:04:03 -070077 .selected-line {
78 margin-bottom: 10px;
79 font-size: 14px;
80 }
Sean McCullough71941bd2025-04-18 13:31:48 -070081
Sean McCullough86b56862025-04-18 13:04:03 -070082 .selected-line pre {
83 padding: 6px;
84 background: #f5f5f5;
85 border: 1px solid #eee;
86 border-radius: 3px;
87 margin: 5px 0;
88 max-height: 100px;
89 overflow: auto;
90 font-family: var(--monospace-font);
91 font-size: 13px;
92 white-space: pre-wrap;
93 }
Sean McCullough71941bd2025-04-18 13:31:48 -070094
Sean McCullough86b56862025-04-18 13:04:03 -070095 #diffCommentInput {
96 width: 100%;
97 height: 100px;
98 padding: 8px;
99 border: 1px solid #ddd;
100 border-radius: 4px;
101 resize: vertical;
102 font-family: inherit;
103 margin-bottom: 10px;
104 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700105
Sean McCullough86b56862025-04-18 13:04:03 -0700106 .diff-comment-buttons {
107 display: flex;
108 justify-content: flex-end;
109 gap: 8px;
110 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700111
Sean McCullough86b56862025-04-18 13:04:03 -0700112 .diff-comment-buttons button {
113 padding: 6px 12px;
114 border-radius: 4px;
115 border: 1px solid #ddd;
116 background: white;
117 cursor: pointer;
118 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700119
Sean McCullough86b56862025-04-18 13:04:03 -0700120 .diff-comment-buttons button:hover {
121 background: #f5f5f5;
122 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700123
Sean McCullough86b56862025-04-18 13:04:03 -0700124 .diff-comment-buttons button#submitDiffComment {
125 background: #1a73e8;
126 color: white;
127 border-color: #1a73e8;
128 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700129
Sean McCullough86b56862025-04-18 13:04:03 -0700130 .diff-comment-buttons button#submitDiffComment:hover {
131 background: #1967d2;
132 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700133
Sean McCullough86b56862025-04-18 13:04:03 -0700134 /* Styles for the comment button on diff lines */
135 .d2h-gutter-comment-button {
136 position: absolute;
137 right: 0;
138 top: 50%;
139 transform: translateY(-50%);
140 visibility: hidden;
141 background: rgba(255, 255, 255, 0.8);
142 border-radius: 50%;
143 width: 16px;
144 height: 16px;
145 line-height: 13px;
146 text-align: center;
147 font-size: 14px;
148 cursor: pointer;
149 color: #666;
150 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
151 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700152
Sean McCullough86b56862025-04-18 13:04:03 -0700153 tr:hover .d2h-gutter-comment-button {
154 visibility: visible;
155 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700156
Sean McCullough86b56862025-04-18 13:04:03 -0700157 .d2h-gutter-comment-button:hover {
158 background: white;
159 color: #333;
160 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
161 }
162 `;
Sean McCullough71941bd2025-04-18 13:31:48 -0700163
Sean McCullough86b56862025-04-18 13:04:03 -0700164 constructor() {
165 super();
166 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700167
Sean McCullough86b56862025-04-18 13:04:03 -0700168 // See https://lit.dev/docs/components/lifecycle/
169 connectedCallback() {
170 super.connectedCallback();
Sean McCullough71941bd2025-04-18 13:31:48 -0700171
Sean McCullough86b56862025-04-18 13:04:03 -0700172 // Load the diff2html CSS if needed
173 this.loadDiff2HtmlCSS();
174 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700175
Sean McCullough86b56862025-04-18 13:04:03 -0700176 // Load diff2html CSS into the shadow DOM
177 private async loadDiff2HtmlCSS() {
178 try {
179 // Check if diff2html styles are already loaded
Sean McCullough71941bd2025-04-18 13:31:48 -0700180 const styleId = "diff2html-styles";
Sean McCullough86b56862025-04-18 13:04:03 -0700181 if (this.shadowRoot?.getElementById(styleId)) {
182 return; // Already loaded
183 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700184
Sean McCullough86b56862025-04-18 13:04:03 -0700185 // Fetch the diff2html CSS
Sean McCullough71941bd2025-04-18 13:31:48 -0700186 const response = await fetch("static/diff2html.min.css");
187
Sean McCullough86b56862025-04-18 13:04:03 -0700188 if (!response.ok) {
Sean McCullough71941bd2025-04-18 13:31:48 -0700189 console.error(
190 `Failed to load diff2html CSS: ${response.status} ${response.statusText}`,
191 );
Sean McCullough86b56862025-04-18 13:04:03 -0700192 return;
193 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700194
Sean McCullough86b56862025-04-18 13:04:03 -0700195 const cssText = await response.text();
Sean McCullough71941bd2025-04-18 13:31:48 -0700196
Sean McCullough86b56862025-04-18 13:04:03 -0700197 // Create a style element and append to shadow DOM
Sean McCullough71941bd2025-04-18 13:31:48 -0700198 const style = document.createElement("style");
Sean McCullough86b56862025-04-18 13:04:03 -0700199 style.id = styleId;
200 style.textContent = cssText;
201 this.shadowRoot?.appendChild(style);
Sean McCullough71941bd2025-04-18 13:31:48 -0700202
203 console.log("diff2html CSS loaded into shadow DOM");
Sean McCullough86b56862025-04-18 13:04:03 -0700204 } catch (error) {
Sean McCullough71941bd2025-04-18 13:31:48 -0700205 console.error("Error loading diff2html CSS:", error);
Sean McCullough86b56862025-04-18 13:04:03 -0700206 }
207 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700208
Sean McCullough86b56862025-04-18 13:04:03 -0700209 // See https://lit.dev/docs/components/lifecycle/
210 disconnectedCallback() {
211 super.disconnectedCallback();
212 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700213
Sean McCullough86b56862025-04-18 13:04:03 -0700214 // Method called to load diff content
215 async loadDiffContent() {
216 // Wait for the component to be rendered
217 await this.updateComplete;
Sean McCullough71941bd2025-04-18 13:31:48 -0700218
219 const diff2htmlContent =
220 this.shadowRoot?.getElementById("diff2htmlContent");
Sean McCullough86b56862025-04-18 13:04:03 -0700221 if (!diff2htmlContent) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700222
Sean McCullough86b56862025-04-18 13:04:03 -0700223 try {
224 // Show loading state
225 diff2htmlContent.innerHTML = "Loading enhanced diff...";
Sean McCullough71941bd2025-04-18 13:31:48 -0700226
Sean McCullough86b56862025-04-18 13:04:03 -0700227 // Build the diff URL - include commit hash if specified
Sean McCullough71941bd2025-04-18 13:31:48 -0700228 const diffUrl = this.commitHash
229 ? `diff?commit=${this.commitHash}`
230 : "diff";
231
Sean McCullough86b56862025-04-18 13:04:03 -0700232 // Fetch the diff from the server
233 const response = await fetch(diffUrl);
Sean McCullough71941bd2025-04-18 13:31:48 -0700234
Sean McCullough86b56862025-04-18 13:04:03 -0700235 if (!response.ok) {
236 throw new Error(
237 `Server returned ${response.status}: ${response.statusText}`,
238 );
239 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700240
Sean McCullough86b56862025-04-18 13:04:03 -0700241 const diffText = await response.text();
Sean McCullough71941bd2025-04-18 13:31:48 -0700242
Sean McCullough86b56862025-04-18 13:04:03 -0700243 if (!diffText || diffText.trim() === "") {
244 diff2htmlContent.innerHTML =
245 "<span style='color: #666; font-style: italic;'>No changes detected since conversation started.</span>";
246 return;
247 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700248
Sean McCullough86b56862025-04-18 13:04:03 -0700249 // Render the diff using diff2html
250 const diffHtml = Diff2Html.html(diffText, {
251 outputFormat: this.viewFormat,
252 drawFileList: true,
253 matching: "lines",
254 renderNothingWhenEmpty: false,
255 colorScheme: "light" as any, // Force light mode to match the rest of the UI
256 });
Sean McCullough71941bd2025-04-18 13:31:48 -0700257
Sean McCullough86b56862025-04-18 13:04:03 -0700258 // Insert the generated HTML
259 diff2htmlContent.innerHTML = diffHtml;
Sean McCullough71941bd2025-04-18 13:31:48 -0700260
Sean McCullough86b56862025-04-18 13:04:03 -0700261 // Add CSS styles to ensure we don't have double scrollbars
262 const d2hFiles = diff2htmlContent.querySelectorAll(".d2h-file-wrapper");
263 d2hFiles.forEach((file) => {
264 const contentElem = file.querySelector(".d2h-files-diff");
265 if (contentElem) {
266 // Remove internal scrollbar - the outer container will handle scrolling
267 (contentElem as HTMLElement).style.overflow = "visible";
268 (contentElem as HTMLElement).style.maxHeight = "none";
269 }
270 });
Sean McCullough71941bd2025-04-18 13:31:48 -0700271
Sean McCullough86b56862025-04-18 13:04:03 -0700272 // Add click event handlers to each code line for commenting
273 this.setupDiffLineComments();
Sean McCullough86b56862025-04-18 13:04:03 -0700274 } catch (error) {
275 console.error("Error loading diff2html content:", error);
276 const errorMessage =
277 error instanceof Error ? error.message : "Unknown error";
278 diff2htmlContent.innerHTML = `<span style='color: #dc3545;'>Error loading enhanced diff: ${errorMessage}</span>`;
279 }
280 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700281
Sean McCullough86b56862025-04-18 13:04:03 -0700282 // Handle view format changes
283 private handleViewFormatChange(event: Event) {
284 const input = event.target as HTMLInputElement;
285 if (input.checked) {
286 this.viewFormat = input.value as "side-by-side" | "line-by-line";
287 this.loadDiffContent();
288 }
289 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700290
Sean McCullough86b56862025-04-18 13:04:03 -0700291 /**
292 * Setup handlers for diff code lines to enable commenting
293 */
294 private setupDiffLineComments(): void {
Sean McCullough71941bd2025-04-18 13:31:48 -0700295 const diff2htmlContent =
296 this.shadowRoot?.getElementById("diff2htmlContent");
Sean McCullough86b56862025-04-18 13:04:03 -0700297 if (!diff2htmlContent) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700298
Sean McCullough86b56862025-04-18 13:04:03 -0700299 console.log("Setting up diff line comments");
Sean McCullough71941bd2025-04-18 13:31:48 -0700300
Sean McCullough86b56862025-04-18 13:04:03 -0700301 // Add plus buttons to each code line
302 this.addCommentButtonsToCodeLines();
Sean McCullough71941bd2025-04-18 13:31:48 -0700303
Sean McCullough86b56862025-04-18 13:04:03 -0700304 // Use event delegation for handling clicks on plus buttons
305 diff2htmlContent.addEventListener("click", (event) => {
306 const target = event.target as HTMLElement;
Sean McCullough71941bd2025-04-18 13:31:48 -0700307
Sean McCullough86b56862025-04-18 13:04:03 -0700308 // Only respond to clicks on the plus button
309 if (target.classList.contains("d2h-gutter-comment-button")) {
310 // Find the parent row first
311 const row = target.closest("tr");
312 if (!row) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700313
Sean McCullough86b56862025-04-18 13:04:03 -0700314 // Then find the code line in that row
Sean McCullough71941bd2025-04-18 13:31:48 -0700315 const codeLine =
316 row.querySelector(".d2h-code-side-line") ||
317 row.querySelector(".d2h-code-line");
Sean McCullough86b56862025-04-18 13:04:03 -0700318 if (!codeLine) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700319
Sean McCullough86b56862025-04-18 13:04:03 -0700320 // Get the line text content
321 const lineContent = codeLine.querySelector(".d2h-code-line-ctn");
322 if (!lineContent) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700323
Sean McCullough86b56862025-04-18 13:04:03 -0700324 const lineText = lineContent.textContent?.trim() || "";
Sean McCullough71941bd2025-04-18 13:31:48 -0700325
Sean McCullough86b56862025-04-18 13:04:03 -0700326 // Get file name to add context
327 const fileHeader = codeLine
328 .closest(".d2h-file-wrapper")
329 ?.querySelector(".d2h-file-name");
330 const fileName = fileHeader
331 ? fileHeader.textContent?.trim()
332 : "Unknown file";
Sean McCullough71941bd2025-04-18 13:31:48 -0700333
Sean McCullough86b56862025-04-18 13:04:03 -0700334 // Get line number if available
335 const lineNumElem = codeLine
336 .closest("tr")
337 ?.querySelector(".d2h-code-side-linenumber");
338 const lineNum = lineNumElem ? lineNumElem.textContent?.trim() : "";
339 const lineInfo = lineNum ? `Line ${lineNum}: ` : "";
Sean McCullough71941bd2025-04-18 13:31:48 -0700340
Sean McCullough86b56862025-04-18 13:04:03 -0700341 // Format the line for the comment box with file context and line number
342 const formattedLine = `${fileName} ${lineInfo}${lineText}`;
Sean McCullough71941bd2025-04-18 13:31:48 -0700343
Sean McCullough86b56862025-04-18 13:04:03 -0700344 console.log("Comment button clicked for line: ", formattedLine);
Sean McCullough71941bd2025-04-18 13:31:48 -0700345
Sean McCullough86b56862025-04-18 13:04:03 -0700346 // Open the comment box with this line
347 this.openDiffCommentBox(formattedLine);
Sean McCullough71941bd2025-04-18 13:31:48 -0700348
Sean McCullough86b56862025-04-18 13:04:03 -0700349 // Prevent event from bubbling up
350 event.stopPropagation();
351 }
352 });
353 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700354
Sean McCullough86b56862025-04-18 13:04:03 -0700355 /**
356 * Add plus buttons to each table row in the diff for commenting
357 */
358 private addCommentButtonsToCodeLines(): void {
Sean McCullough71941bd2025-04-18 13:31:48 -0700359 const diff2htmlContent =
360 this.shadowRoot?.getElementById("diff2htmlContent");
Sean McCullough86b56862025-04-18 13:04:03 -0700361 if (!diff2htmlContent) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700362
Sean McCullough86b56862025-04-18 13:04:03 -0700363 // Target code lines first, then find their parent rows
364 const codeLines = diff2htmlContent.querySelectorAll(
Sean McCullough71941bd2025-04-18 13:31:48 -0700365 ".d2h-code-side-line, .d2h-code-line",
Sean McCullough86b56862025-04-18 13:04:03 -0700366 );
Sean McCullough71941bd2025-04-18 13:31:48 -0700367
Sean McCullough86b56862025-04-18 13:04:03 -0700368 // Create a Set to store unique rows to avoid duplicates
369 const rowsSet = new Set<HTMLElement>();
Sean McCullough71941bd2025-04-18 13:31:48 -0700370
Sean McCullough86b56862025-04-18 13:04:03 -0700371 // Get all rows that contain code lines
Sean McCullough71941bd2025-04-18 13:31:48 -0700372 codeLines.forEach((line) => {
373 const row = line.closest("tr");
Sean McCullough86b56862025-04-18 13:04:03 -0700374 if (row) rowsSet.add(row as HTMLElement);
375 });
Sean McCullough71941bd2025-04-18 13:31:48 -0700376
Sean McCullough86b56862025-04-18 13:04:03 -0700377 // Convert Set back to array for processing
378 const codeRows = Array.from(rowsSet);
Sean McCullough71941bd2025-04-18 13:31:48 -0700379
Sean McCullough86b56862025-04-18 13:04:03 -0700380 codeRows.forEach((row) => {
381 const rowElem = row as HTMLElement;
Sean McCullough71941bd2025-04-18 13:31:48 -0700382
Sean McCullough86b56862025-04-18 13:04:03 -0700383 // Skip info lines without actual code (e.g., "file added")
384 if (rowElem.querySelector(".d2h-info")) {
385 return;
386 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700387
Sean McCullough86b56862025-04-18 13:04:03 -0700388 // Find the code line number element (first TD in the row)
389 const lineNumberCell = rowElem.querySelector(
Sean McCullough71941bd2025-04-18 13:31:48 -0700390 ".d2h-code-side-linenumber, .d2h-code-linenumber",
Sean McCullough86b56862025-04-18 13:04:03 -0700391 );
Sean McCullough71941bd2025-04-18 13:31:48 -0700392
Sean McCullough86b56862025-04-18 13:04:03 -0700393 if (!lineNumberCell) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700394
Sean McCullough86b56862025-04-18 13:04:03 -0700395 // Create the plus button
396 const plusButton = document.createElement("span");
397 plusButton.className = "d2h-gutter-comment-button";
398 plusButton.innerHTML = "+";
399 plusButton.title = "Add a comment on this line";
Sean McCullough71941bd2025-04-18 13:31:48 -0700400
Sean McCullough86b56862025-04-18 13:04:03 -0700401 // Add button to the line number cell for proper positioning
402 (lineNumberCell as HTMLElement).style.position = "relative"; // Ensure positioning context
403 lineNumberCell.appendChild(plusButton);
404 });
405 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700406
Sean McCullough86b56862025-04-18 13:04:03 -0700407 /**
408 * Open the comment box for a selected diff line
409 */
410 private openDiffCommentBox(lineText: string): void {
411 // Make sure the comment box div exists
412 const commentBoxId = "diffCommentBox";
413 let commentBox = this.shadowRoot?.getElementById(commentBoxId);
Sean McCullough71941bd2025-04-18 13:31:48 -0700414
Sean McCullough86b56862025-04-18 13:04:03 -0700415 // If it doesn't exist, create it
416 if (!commentBox) {
417 commentBox = document.createElement("div");
418 commentBox.id = commentBoxId;
419 commentBox.className = "diff-comment-box";
Sean McCullough71941bd2025-04-18 13:31:48 -0700420
Sean McCullough86b56862025-04-18 13:04:03 -0700421 // Create the comment box contents
422 commentBox.innerHTML = `
423 <h3>Add a comment</h3>
424 <div class="selected-line">
425 Line:
426 <pre id="selectedLine"></pre>
427 </div>
428 <textarea
429 id="diffCommentInput"
430 placeholder="Enter your comment about this line..."
431 ></textarea>
432 <div class="diff-comment-buttons">
433 <button id="cancelDiffComment">Cancel</button>
434 <button id="submitDiffComment">Add Comment</button>
435 </div>
436 `;
Sean McCullough71941bd2025-04-18 13:31:48 -0700437
Sean McCullough86b56862025-04-18 13:04:03 -0700438 this.shadowRoot?.appendChild(commentBox);
439 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700440
Sean McCullough86b56862025-04-18 13:04:03 -0700441 // Store the selected line
442 this.selectedDiffLine = lineText;
Sean McCullough71941bd2025-04-18 13:31:48 -0700443
Sean McCullough86b56862025-04-18 13:04:03 -0700444 // Display the line in the comment box
445 const selectedLine = this.shadowRoot?.getElementById("selectedLine");
446 if (selectedLine) {
447 selectedLine.textContent = lineText;
448 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700449
Sean McCullough86b56862025-04-18 13:04:03 -0700450 // Reset the comment input
451 const commentInput = this.shadowRoot?.getElementById(
Sean McCullough71941bd2025-04-18 13:31:48 -0700452 "diffCommentInput",
Sean McCullough86b56862025-04-18 13:04:03 -0700453 ) as HTMLTextAreaElement;
454 if (commentInput) {
455 commentInput.value = "";
456 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700457
Sean McCullough86b56862025-04-18 13:04:03 -0700458 // Show the comment box
459 if (commentBox) {
460 commentBox.style.display = "block";
461 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700462
Sean McCullough86b56862025-04-18 13:04:03 -0700463 // Add event listeners for submit and cancel buttons
464 const submitButton = this.shadowRoot?.getElementById("submitDiffComment");
465 if (submitButton) {
466 submitButton.onclick = () => this.submitDiffComment();
467 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700468
Sean McCullough86b56862025-04-18 13:04:03 -0700469 const cancelButton = this.shadowRoot?.getElementById("cancelDiffComment");
470 if (cancelButton) {
471 cancelButton.onclick = () => this.closeDiffCommentBox();
472 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700473
Sean McCullough86b56862025-04-18 13:04:03 -0700474 // Focus on the comment input
475 if (commentInput) {
476 commentInput.focus();
477 }
478 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700479
Sean McCullough86b56862025-04-18 13:04:03 -0700480 /**
481 * Close the diff comment box without submitting
482 */
483 private closeDiffCommentBox(): void {
484 const commentBox = this.shadowRoot?.getElementById("diffCommentBox");
485 if (commentBox) {
486 commentBox.style.display = "none";
487 }
488 this.selectedDiffLine = null;
489 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700490
Sean McCullough86b56862025-04-18 13:04:03 -0700491 /**
492 * Submit a comment on a diff line
493 */
494 private submitDiffComment(): void {
495 const commentInput = this.shadowRoot?.getElementById(
Sean McCullough71941bd2025-04-18 13:31:48 -0700496 "diffCommentInput",
Sean McCullough86b56862025-04-18 13:04:03 -0700497 ) as HTMLTextAreaElement;
Sean McCullough71941bd2025-04-18 13:31:48 -0700498
Sean McCullough86b56862025-04-18 13:04:03 -0700499 if (!commentInput) return;
Sean McCullough71941bd2025-04-18 13:31:48 -0700500
Sean McCullough86b56862025-04-18 13:04:03 -0700501 const comment = commentInput.value.trim();
Sean McCullough71941bd2025-04-18 13:31:48 -0700502
Sean McCullough86b56862025-04-18 13:04:03 -0700503 // Validate inputs
504 if (!this.selectedDiffLine || !comment) {
505 alert("Please select a line and enter a comment.");
506 return;
507 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700508
Sean McCullough86b56862025-04-18 13:04:03 -0700509 // Format the comment in a readable way
510 const formattedComment = `\`\`\`\n${this.selectedDiffLine}\n\`\`\`\n\n${comment}`;
Sean McCullough71941bd2025-04-18 13:31:48 -0700511
Sean McCullough86b56862025-04-18 13:04:03 -0700512 // Dispatch a custom event with the formatted comment
Sean McCullough71941bd2025-04-18 13:31:48 -0700513 const event = new CustomEvent("diff-comment", {
Sean McCullough86b56862025-04-18 13:04:03 -0700514 detail: { comment: formattedComment },
515 bubbles: true,
Sean McCullough71941bd2025-04-18 13:31:48 -0700516 composed: true,
Sean McCullough86b56862025-04-18 13:04:03 -0700517 });
518 this.dispatchEvent(event);
Sean McCullough71941bd2025-04-18 13:31:48 -0700519
Sean McCullough86b56862025-04-18 13:04:03 -0700520 // Close only the comment box but keep the diff view open
521 this.closeDiffCommentBox();
522 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700523
Sean McCullough86b56862025-04-18 13:04:03 -0700524 // Clear the current state
525 public clearState(): void {
526 this.commitHash = "";
527 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700528
Sean McCullough86b56862025-04-18 13:04:03 -0700529 // Show diff for a specific commit
530 public showCommitDiff(commitHash: string): void {
531 // Store the commit hash
532 this.commitHash = commitHash;
533 // Load the diff content
534 this.loadDiffContent();
535 }
Sean McCullough71941bd2025-04-18 13:31:48 -0700536
Sean McCullough86b56862025-04-18 13:04:03 -0700537 render() {
538 return html`
539 <div class="diff-view">
540 <div class="diff-container">
541 <div id="diff-view-controls">
542 <div class="diff-view-format">
543 <label>
Sean McCullough71941bd2025-04-18 13:31:48 -0700544 <input
545 type="radio"
546 name="diffViewFormat"
547 value="side-by-side"
Sean McCullough86b56862025-04-18 13:04:03 -0700548 ?checked=${this.viewFormat === "side-by-side"}
549 @change=${this.handleViewFormatChange}
Sean McCullough71941bd2025-04-18 13:31:48 -0700550 />
551 Side-by-side
Sean McCullough86b56862025-04-18 13:04:03 -0700552 </label>
553 <label>
Sean McCullough71941bd2025-04-18 13:31:48 -0700554 <input
555 type="radio"
556 name="diffViewFormat"
Sean McCullough86b56862025-04-18 13:04:03 -0700557 value="line-by-line"
558 ?checked=${this.viewFormat === "line-by-line"}
559 @change=${this.handleViewFormatChange}
Sean McCullough71941bd2025-04-18 13:31:48 -0700560 />
561 Line-by-line
Sean McCullough86b56862025-04-18 13:04:03 -0700562 </label>
563 </div>
564 </div>
565 <div id="diff2htmlContent" class="diff2html-content"></div>
566 </div>
567 </div>
568 `;
569 }
570}
571
572declare global {
573 interface HTMLElementTagNameMap {
574 "sketch-diff-view": SketchDiffView;
575 }
576}