Commit: 43a3878
Parent: 464f74a

Link to lines

Mårten Åsberg committed on 2025-11-23 at 10:39
GitBrowser/Components/Shared/FilePreview.razor +33 -3
diff --git a/GitBrowser/Components/Shared/FilePreview.razor b/GitBrowser/Components/Shared/FilePreview.razor
index f889ff5..0d91740 100644
@@ -4,6 +4,7 @@
@using System.Text
@using LibGit2Sharp
@using Markdig
@using Microsoft.AspNetCore.Http
<div class="file-viewer @(IsReadme ? "readme-viewer" : "")">
<div class="file-viewer-header">
@@ -29,6 +30,9 @@
[Parameter]
public required Blob FileContent { get; set; }
[Inject]
public required NavigationManager NavigationManager { get; init; }
private MarkupString filePreview = (MarkupString)"";
protected override async Task OnInitializedAsync()
@@ -48,19 +52,45 @@
}
else if (string.Equals(extension, ".md", StringComparison.OrdinalIgnoreCase))
{
filePreview = (MarkupString)$"<div class=\"markdown\">{Markdown.ToHtml(FileContent.GetContentText(), markdownPipeline)}</div>";
filePreview = (MarkupString)$"<div class=\"markdown\">{Markdown.ToHtml(FileContent.GetContentText(),
markdownPipeline)}</div>";
}
else if (extension is not null && Syntax.CanHighlightFile(extension))
{
filePreview = (MarkupString)Syntax.HighlightSyntaxHtml(extension, FileContent.GetContentText());
var content = FileContent.GetContentText();
var highlightedHtml = Syntax.HighlightSyntaxHtml(extension, content);
filePreview = (MarkupString)AddLineNumberGutter(highlightedHtml, content);
}
else if (!FileContent.IsBinary)
{
filePreview = (MarkupString)$"<pre class=\"plain\">{HttpUtility.HtmlEncode(FileContent.GetContentText())}</pre>";
var content = FileContent.GetContentText();
var plainHtml = $"<pre class=\"plain\">{HttpUtility.HtmlEncode(content)}</pre>";
filePreview = (MarkupString)AddLineNumberGutter(plainHtml, content);
}
else
{
filePreview = (MarkupString)"<p class=\"binary\">Binary content</p>";
}
}
private string AddLineNumberGutter(string contentHtml, string originalContent)
{
// Count lines in the original content
var lineCount = originalContent.Count(c => c is '\n') + 1;
// Generate line numbers
var lineNumbers = new StringBuilder();
for (int i = 1; i <= lineCount; i++)
{
lineNumbers.AppendLine($"<a id=\"L{i}\" href=\"{NavigationManager.Uri}#L{i}\" class=\"line-number\">{i}</a>");
}
// Wrap content with line number gutter
return $"""
<div class="file-with-lines">
<div class="line-numbers">{lineNumbers}</div>
<div class="file-content-lines">{contentHtml}</div>
</div>
""";
}
}
GitBrowser/Components/Shared/FilePreview.razor.css +56 -0
diff --git a/GitBrowser/Components/Shared/FilePreview.razor.css b/GitBrowser/Components/Shared/FilePreview.razor.css
index 502828a..6554de6 100644
@@ -39,3 +39,59 @@
.file-content ::deep .binary {
text-align: center;
}
.file-content ::deep .file-with-lines {
display: grid;
grid-template-columns: auto 1fr;
margin: 0;
overflow-x: auto;
}
.file-content ::deep .line-numbers {
padding: 1rem 0;
text-align: right;
user-select: none;
background: var(--gh-color-canvas-subtle);
border-right: 1px solid var(--gh-color-border-default);
font-family: "Consolas", "Monaco", "Courier New", monospace;
font-size: 0.875rem;
line-height: 1.5;
min-width: 3.5rem;
position: sticky;
left: 0;
z-index: 1;
}
.file-content ::deep .line-number {
display: block;
padding: 0 1rem;
color: var(--gh-color-fg-muted);
cursor: pointer;
text-decoration: none;
}
.file-content ::deep .line-number:hover {
color: var(--gh-color-fg-default);
background: var(--gh-color-neutral-muted);
}
.file-content ::deep .line-number:target:not(:has(~ .line-number:focus)),
.file-content ::deep .line-number:focus {
background: var(--gh-color-attention-subtle);
color: var(--gh-color-fg-default);
outline: none;
}
.file-content ::deep .line-number:focus ~ .line-number:target {
background: unset;
color: var(--gh-color-fg-muted);
}
.file-content ::deep .file-content-lines {
overflow-x: auto;
}
.file-content ::deep .file-content-lines pre {
margin: 0;
padding: 1rem;
}