From 3e6e1621f046018306c4254719b20acd01c1a8da Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 28 Jun 2026 16:44:01 +0300 Subject: [PATCH] another pr files optimization --- .gitignore | 1 + diff.v | 21 ++++++---- pr_routes.v | 39 ++++++++++++++++-- static/assets/version | 2 +- static/css/gitly.scss | 87 +++++++++++++++++++++++++-------------- static/js/gitly.js | 72 ++++++++++++++++++++++++++++---- templates/pull_files.html | 41 ++---------------- 7 files changed, 171 insertions(+), 92 deletions(-) diff --git a/.gitignore b/.gitignore index 9abb144..e63af90 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ build deploy gitly_linux gitly_ci +gitly.sqlite* diff --git a/diff.v b/diff.v index 2cd2030..03586a3 100644 --- a/diff.v +++ b/diff.v @@ -24,17 +24,12 @@ fn diff_hunk_header_html(header string) string { } fn diff_line_row_html(file_path string, dline DiffLine) string { - return '

${dline.old_line_str()}${dline.new_line_str()}${dline.compact_sign()}${highlight.highlight_line(dline.content, - file_path)}

' + return diff_line_row_html_with_attrs(file_path, dline, '') } -fn diff_comment_box_html(file_path string, dline DiffLine, lang Lang) string { - if dline.kind == 'context' { - return '' - } - name := html_escape_text(dline.comment_field_name(file_path)) - placeholder := html_escape_text(veb.tr(lang.str(), 'pr_line_comment_placeholder').trim_space()) - return '

' +fn diff_line_row_html_with_attrs(file_path string, dline DiffLine, attrs string) string { + return '

${dline.old_line_str()}${dline.new_line_str()}${dline.compact_sign()}${highlight.highlight_line(dline.content, + file_path)}

' } struct FileDiff { @@ -176,6 +171,14 @@ fn (d &DiffLine) compact_class() string { } } +fn (d &DiffLine) compact_side() string { + return match d.kind { + 'add' { 'n' } + 'del' { 'o' } + else { '' } + } +} + fn (d &DiffLine) side() string { return if d.kind == 'add' { 'new' } else { 'old' } } diff --git a/pr_routes.v b/pr_routes.v index 5a89ffc..fce6a0a 100644 --- a/pr_routes.v +++ b/pr_routes.v @@ -52,6 +52,35 @@ struct PrFileTreeRow { deletions int } +fn render_pr_file_tree(file_tree []PrFileTreeRow) veb.RawHtml { + mut out := strings.new_builder(file_tree.len * 160) + for row in file_tree { + path := html_escape_text(row.path) + name := html_escape_text(row.name) + indent := row.indent_px + if row.is_dir { + out.write_string('') + } else { + status := row.status() + out.write_string('${name}${status}+${row.additions}-${row.deletions}') + } + } + return veb.RawHtml(out.str()) +} + +fn (row PrFileTreeRow) status() string { + if row.is_new { + return 'A' + } + if row.is_deleted { + return 'D' + } + if row.is_renamed { + return 'R' + } + return 'M' +} + fn build_pr_file_tree_rows(file_diffs []FileDiff) []PrFileTreeRow { mut sorted := file_diffs.clone() sorted.sort(a.path < b.path) @@ -605,16 +634,18 @@ fn merge_branches_in_bare(repo Repo, base string, head string, author string, me return commit_sha } -fn render_pr_diff_table(fd FileDiff, comments_by_key map[string][]PrReviewCommentWithUser, can_comment bool, lang Lang) veb.RawHtml { +fn render_pr_diff_table(fd FileDiff, comments_by_key map[string][]PrReviewCommentWithUser, can_comment bool) veb.RawHtml { mut out := strings.new_builder(1024) out.write_string('
') for hunk in fd.hunks { out.write_string(diff_hunk_header_html(hunk.header)) for dline in hunk.lines { - out.write_string(diff_line_row_html(fd.path, dline)) - if can_comment && dline.kind != 'context' { - out.write_string(diff_comment_box_html(fd.path, dline, lang)) + attrs := if can_comment && dline.kind != 'context' { + ' s=${dline.compact_side()} l=${dline.effective_line()}' + } else { + '' } + out.write_string(diff_line_row_html_with_attrs(fd.path, dline, attrs)) out.write_string(inline_comments_html(fd.path, dline, comments_by_key)) } } diff --git a/static/assets/version b/static/assets/version index 827d24b..62063e2 100644 --- a/static/assets/version +++ b/static/assets/version @@ -1 +1 @@ -17255ce \ No newline at end of file +bc9003f \ No newline at end of file diff --git a/static/css/gitly.scss b/static/css/gitly.scss index e32809d..b8d7a5d 100644 --- a/static/css/gitly.scss +++ b/static/css/gitly.scss @@ -2355,14 +2355,14 @@ form { } } -.pr-file-tree-row { +.pr-files-sidebar__list > .r { display: grid; grid-template-columns: 16px 18px minmax(0, 1fr) auto auto; align-items: center; gap: 7px; width: 100% !important; box-sizing: border-box; - padding: 6px 10px; + padding: 6px 10px 6px var(--i, 10px); border-top: 1px solid #eef1f4; border-right: 0; border-bottom: 0; @@ -2387,20 +2387,18 @@ form { } } -.pr-file-tree-row[hidden], +.pr-files-sidebar__list > .r[hidden], .pr-files-filter-empty[hidden] { display: none !important; } -.pr-file-tree-row--dir { +.pr-files-sidebar__list > .d { grid-template-columns: 16px 18px minmax(0, 1fr); font-weight: 600; } -.pr-file-tree-row__chevron, -.pr-file-tree-row__spacer, -.pr-file-tree-row__folder, -.pr-file-tree-row__file { +.pr-files-sidebar__list > .r::before, +.pr-files-sidebar__list > .r > b { display: inline-flex; align-items: center; justify-content: center; @@ -2409,35 +2407,53 @@ form { color: #57606a; } -.pr-file-tree-row__chevron svg { - width: 14px; - height: 14px; +.pr-files-sidebar__list > .d::before { + content: ">"; + font-size: 13px; + line-height: 1; transition: transform 0.08s; } -.pr-file-tree-row--dir[aria-expanded="true"] .pr-file-tree-row__chevron svg { +.pr-files-sidebar__list > .d[aria-expanded="true"]::before { transform: rotate(90deg); } -.pr-file-tree-row__folder { - color: #54aeff; +.pr-files-sidebar__list > .f::before { + content: ""; +} - svg { - width: 16px; - height: 16px; - } +.pr-files-sidebar__list > .r > b { + position: relative; + flex: 0 0 auto; } -.pr-file-tree-row__file { - color: #57606a; +.pr-files-sidebar__list > .d > b { + width: 15px; + height: 11px; + border-radius: 2px; + background-color: #54aeff; +} - svg { - width: 15px; - height: 15px; - } +.pr-files-sidebar__list > .d > b::before { + content: ""; + position: absolute; + top: -3px; + left: 1px; + width: 7px; + height: 4px; + border-radius: 2px 2px 0 0; + background-color: #54aeff; +} + +.pr-files-sidebar__list > .f > b { + width: 12px; + height: 14px; + border: 1px solid #57606a; + border-radius: 2px; + box-sizing: border-box; } -.pr-file-tree-row__name { +.pr-files-sidebar__list > .r > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; @@ -2445,7 +2461,7 @@ form { line-height: 1.35; } -.pr-file-link__status { +.pr-files-sidebar__list > .r > em { display: inline-flex; align-items: center; justify-content: center; @@ -2455,11 +2471,12 @@ form { background-color: #eef3f8; color: #57606a; font-size: 10px; + font-style: normal; font-weight: 700; line-height: 1; } -.pr-file-link__counts { +.pr-files-sidebar__list > .r > small { display: inline-flex; gap: 6px; font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; @@ -2467,6 +2484,12 @@ form { font-weight: 600; } +.pr-files-sidebar__list > .r > small > b { color: #1f883d; } +.pr-files-sidebar__list > .r > small > i { + color: #cf222e; + font-style: normal; +} + .pr-files-filter-empty { padding: 12px; border-top: 1px solid #eef1f4; @@ -2578,9 +2601,9 @@ form { font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace; } -.pr-diff__table > p.c > * { background-color: $white; } -.pr-diff__table > p.a > * { background-color: #e6ffec; } -.pr-diff__table > p.d > * { background-color: #ffebe9; } +.pr-diff__table > p.c { background-color: $white; } +.pr-diff__table > p.a { background-color: #e6ffec; } +.pr-diff__table > p.d { background-color: #ffebe9; } .pr-diff__table > p.a > u, .pr-diff__table > p.a > i { @@ -2750,11 +2773,11 @@ form { flex-direction: column; } - .pr-file-tree-row--file { + .pr-files-sidebar__list > .f { grid-template-columns: 16px 18px minmax(0, 1fr) 18px; } - .pr-file-link__counts { + .pr-files-sidebar__list > .r > small { grid-column: 3 / -1; } } diff --git a/static/js/gitly.js b/static/js/gitly.js index faacecb..b80dc8f 100644 --- a/static/js/gitly.js +++ b/static/js/gitly.js @@ -54,14 +54,14 @@ function initPrFilesTree() { const clearButton = document.querySelector("[data-pr-files-filter-clear]"); const countEl = document.querySelector("[data-pr-files-count]"); const emptyEl = document.querySelector("[data-pr-files-filter-empty]"); - const rows = Array.from(tree.querySelectorAll("[data-pr-tree-row]")); - const fileRows = rows.filter((row) => row.hasAttribute("data-file-path")); + const rows = Array.from(tree.querySelectorAll(".r, [data-pr-tree-row]")); + const fileRows = rows.filter((row) => row.hasAttribute("p") || row.hasAttribute("data-file-path")); const diffEls = Array.from(document.querySelectorAll("[data-diff-path]")); const totalFiles = fileRows.length; const collapsedDirs = new Set(); function pathForRow(row) { - return row.getAttribute("data-file-path") || row.getAttribute("data-dir-path") || ""; + return row.getAttribute("p") || row.getAttribute("q") || row.getAttribute("data-file-path") || row.getAttribute("data-dir-path") || ""; } function addParentDirs(path, visibleDirs) { @@ -88,7 +88,7 @@ function initPrFilesTree() { let visibleFileCount = 0; for (const row of fileRows) { - const path = row.getAttribute("data-file-path") || ""; + const path = row.getAttribute("p") || row.getAttribute("data-file-path") || ""; if (query === "" || path.toLowerCase().indexOf(query) !== -1) { visibleFileCount++; addParentDirs(path, visibleDirs); @@ -96,8 +96,8 @@ function initPrFilesTree() { } for (const row of rows) { - const filePath = row.getAttribute("data-file-path"); - const dirPath = row.getAttribute("data-dir-path"); + const filePath = row.getAttribute("p") || row.getAttribute("data-file-path"); + const dirPath = row.getAttribute("q") || row.getAttribute("data-dir-path"); let visible = true; if (filePath) { @@ -129,11 +129,11 @@ function initPrFilesTree() { } for (const row of rows) { - if (!row.hasAttribute("data-dir-path")) { + if (!row.hasAttribute("q") && !row.hasAttribute("data-dir-path")) { continue; } row.addEventListener("click", () => { - const path = row.getAttribute("data-dir-path"); + const path = row.getAttribute("q") || row.getAttribute("data-dir-path"); if (!path) { return; } @@ -166,6 +166,62 @@ function initPrFilesTree() { applyPrFilesFilter(); } +function initPrReviewCommentBoxes() { + const root = document.querySelector(".pr-files-diffs"); + if (!root) { + return; + } + const placeholder = root.getAttribute("data-line-comment-placeholder") || ""; + + function ensureBox(row) { + if (!row || !row.hasAttribute("s") || !row.hasAttribute("l")) { + return null; + } + const next = row.nextElementSibling; + if (next && next.classList.contains("m")) { + return next; + } + const diff = row.closest("[data-diff-path]"); + const path = diff ? diff.getAttribute("data-diff-path") : ""; + const side = row.getAttribute("s") === "n" ? "new" : "old"; + const line = row.getAttribute("l") || ""; + if (!path || !line) { + return null; + } + const wrap = document.createElement("p"); + wrap.className = "m"; + const textarea = document.createElement("textarea"); + textarea.name = "rc::" + path + "::" + side + "::" + line; + textarea.rows = 2; + if (placeholder) { + textarea.placeholder = placeholder; + } + wrap.appendChild(textarea); + row.insertAdjacentElement("afterend", wrap); + return wrap; + } + + function rowFromEvent(event) { + const target = event.target instanceof Element ? event.target : null; + return target ? target.closest(".pr-diff__table > p[s][l]") : null; + } + + root.addEventListener("mouseover", (event) => { + ensureBox(rowFromEvent(event)); + }); + root.addEventListener("focusin", (event) => { + ensureBox(rowFromEvent(event)); + }); + root.addEventListener("click", (event) => { + const box = ensureBox(rowFromEvent(event)); + const textarea = box ? box.querySelector("textarea") : null; + if (textarea) { + textarea.focus(); + } + }); +} + document.addEventListener("DOMContentLoaded", () => { initPrFilesTree(); + initPrReviewCommentBoxes(); }); diff --git a/templates/pull_files.html b/templates/pull_files.html index 1b36ea4..cf85d34 100644 --- a/templates/pull_files.html +++ b/templates/pull_files.html @@ -66,47 +66,12 @@
-
+
@for fd in file_diffs
@@ -131,7 +96,7 @@ @if fd.is_binary
%pr_binary_file
@else - @{render_pr_diff_table(fd, comments_by_key, can_comment, ctx.lang)} + @{render_pr_diff_table(fd, comments_by_key, can_comment)} @end
@end -- 2.39.5