From 2b07ba164a25ce4d69a388412f06b5fc135c1922 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 16 May 2026 15:39:35 +0300 Subject: [PATCH] blob: fix 404 for uncached top-files paths, redesign blob page header Top-files links pointed at deep paths that the on-demand tree cache never inserted into the DB, so the blob route 404'd. Fall back to a direct git ls-tree lookup when the DB has no row for the path. Also refresh the blob page UI: file name now appears in the breadcrumb, the pipe-separated info bar is replaced with a proper toolbar (stats + Raw/Edit buttons) joined to the file content. Drop stray U+25BC arrow from the Code button so the CSS ::after triangle isn't doubled. --- repo/repo.v | 21 ++++++ repo/repo_routes.v | 4 +- static/assets/version | 2 +- static/css/blob.scss | 171 +++++++++++++++++++++++++++++++++++++----- templates/blob.html | 42 +++++++---- templates/tree.html | 2 +- 6 files changed, 205 insertions(+), 37 deletions(-) diff --git a/repo/repo.v b/repo/repo.v index a419653..ca0dbde 100644 --- a/repo/repo.v +++ b/repo/repo.v @@ -660,6 +660,13 @@ fn (r &Repo) parse_top_file_line(line string, branch string) ?File { } } + excluded_extensions := ['.png', '.jpg', '.jpeg', '.obj', '.json', '.pdf'] + for ext in excluded_extensions { + if lower_path.ends_with(ext) { + return none + } + } + item_name := item_path.after('/') if item_name == '' { return none @@ -679,6 +686,20 @@ fn (r &Repo) parse_top_file_line(line string, branch string) ?File { } } +fn (r &Repo) lookup_file_via_git(branch string, path string) ?File { + git_result := git.Git.exec_in_dir(r.git_dir, ['ls-tree', '--full-name', '--long', branch, '--', + path]) + if git_result.exit_code != 0 { + return none + } + for line in git_result.output.split_into_lines() { + if file := r.parse_top_file_line(line, branch) { + return file + } + } + return none +} + fn (r &Repo) top_files(branch string, limit int) []File { git_result := git.Git.exec_in_dir(r.git_dir, ['ls-tree', '-r', '--full-name', '--long', branch]) if git_result.exit_code != 0 { diff --git a/repo/repo_routes.v b/repo/repo_routes.v index fa0ced9..2905d3a 100644 --- a/repo/repo_routes.v +++ b/repo/repo_routes.v @@ -642,7 +642,9 @@ pub fn (mut app App) blob(mut ctx Context, username string, repo_name string, br } raw_url := '/${username}/${repo_name}/raw/${branch_name}/${path}' - file := app.find_repo_file_by_path(repo.id, branch_name, path) or { return ctx.not_found() } + file := app.find_repo_file_by_path(repo.id, branch_name, path) or { + repo.lookup_file_via_git(branch_name, path) or { return ctx.not_found() } + } is_markdown := file.name.to_lower().ends_with('.md') plain_text := repo.read_file(branch_name, path) highlighted_source, _, _ := highlight.highlight_text(plain_text, file.name, false) diff --git a/static/assets/version b/static/assets/version index 62095b5..07281be 100644 --- a/static/assets/version +++ b/static/assets/version @@ -1 +1 @@ -9d8beeb \ No newline at end of file +2a78491 \ No newline at end of file diff --git a/static/css/blob.scss b/static/css/blob.scss index ff2b0dc..32503d4 100644 --- a/static/css/blob.scss +++ b/static/css/blob.scss @@ -1,53 +1,186 @@ -.blob-options-block { - border: 1px solid $gray; - border-radius: $medium-radius; - margin-bottom: 10px; - padding: 5px 10px 5px 10px; - margin-top: 10px; +.blob-page { + margin-top: 16px; +} + +.blob-breadcrumb { + font-size: 18px; + margin: 0 0 14px; + color: #57606a; + line-height: 1.4; + word-break: break-all; +} + +.blob-breadcrumb__link { + color: #0969da; + font-weight: 500; + + &:hover { + text-decoration: underline; + } +} + +.blob-breadcrumb__sep { + color: #8c959f; + margin: 0 6px; +} + +.blob-breadcrumb__file { + color: #1f2328; + font-weight: 600; +} + +.blob-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 10px 16px; + background-color: #f6f8fa; + border: 1px solid #d0d7de; + border-bottom: none; + border-radius: 6px 6px 0 0; + font-size: 13px; +} + +.blob-toolbar__stats { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + color: #57606a; + min-width: 0; +} + +.blob-toolbar__dot { + color: #d0d7de; + user-select: none; +} + +.blob-stat { + color: #57606a; + + b { + color: #1f2328; + font-weight: 600; + } +} + +.blob-stat--hash { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 12px; + color: #0969da !important; + overflow: hidden; + text-overflow: ellipsis; + max-width: 220px; + white-space: nowrap; + + &:hover { + text-decoration: underline; + } +} + +.blob-toolbar__actions { + display: flex; + gap: 6px; +} + +.blob-action { + display: inline-flex; + align-items: center; + padding: 4px 12px; + background-color: #ffffff; + border: 1px solid #d0d7de; + border-radius: 6px; + color: #1f2328 !important; + font-size: 13px; + font-weight: 500; + line-height: 1.5; + transition: background-color 0.07s, border-color 0.07s; + + &:hover { + background-color: #f3f4f6; + border-color: #afb8c1; + text-decoration: none; + } } .blob-content { - border: 1px solid #dfdfdf; - border-radius: 3px; - margin-top: 10px; + border: 1px solid #d0d7de; + border-radius: 0 0 6px 6px; + margin: 0 0 16px; + background-color: #ffffff; + overflow: auto; +} + +.blob-content__code { + margin: 0; + padding: 0; + font-size: 12px; + line-height: 1.5; } .blob-content__markdown { - padding: 20px 30px 20px 30px; + padding: 24px 32px; + + > *:first-child { + margin-top: 0; + } + + > *:last-child { + margin-bottom: 0; + } > * { margin: 10px 0; } code { - border: 1px solid $black; - border-radius: $small-radius; - padding: 5px; + background-color: rgba(175, 184, 193, 0.2); + padding: 0.2em 0.4em; + border-radius: 6px; + font-size: 85%; + } + + pre code { + background: transparent; + padding: 0; + } + + pre { + background-color: #f6f8fa; + padding: 16px; + border-radius: 6px; + overflow: auto; } h1 { font-size: 2em; - border-bottom: 1px solid #dfdfdf; + border-bottom: 1px solid #d8dee4; + padding-bottom: 0.3em; } h2 { - font-size: 1.75em; + font-size: 1.5em; + border-bottom: 1px solid #d8dee4; + padding-bottom: 0.3em; } h3 { - font-size: 1.5em; + font-size: 1.25em; } h4 { - font-size: 1.25em; + font-size: 1em; } h5 { - font-size: 1em; + font-size: 0.875em; } h6 { - font-size: 0.75em; + font-size: 0.85em; + color: #57606a; } img { diff --git a/templates/blob.html b/templates/blob.html index 05128ea..b7bd257 100644 --- a/templates/blob.html +++ b/templates/blob.html @@ -6,21 +6,33 @@ @include 'layout/header.html' -
- @include './layout/tree_path.html' - -
- Raw file - | - ${loc} loc (${sloc} sloc) - | - ${file.pretty_size()} - | - Latest commit hash ${file.last_hash} - @if repo.user_id == ctx.user.id - | - Edit +
+
+ @for i, p in ctx.path_split + @p + / @end + @file.name +
+ +
+
+ ${loc} lines + · + ${sloc} sloc + · + ${file.pretty_size()} + @if file.last_hash != '' + · + @file.last_hash + @end +
+
+ Raw + @if repo.user_id == ctx.user.id + Edit + @end +
@if is_markdown @@ -28,7 +40,7 @@ @source
@else -
@source
+
@source
@end
diff --git a/templates/tree.html b/templates/tree.html index 3a3dc7d..5fe1d1b 100644 --- a/templates/tree.html +++ b/templates/tree.html @@ -40,7 +40,7 @@
-
Code ▼
+
Code
Clone with HTTPS: -- 2.39.5