Commit: 4732976
Parent: e76c909

Image and Markdown previews

Mårten Åsberg committed on 2025-11-20 at 18:40
Directory.Packages.props +5 -0
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 1257cde..286f4b3 100644
@@ -6,6 +6,11 @@
<GlobalPackageReference Include="CSharpier.MsBuild" Version="1.0.2" />
<PackageVersion Include="ColorfulCode" Version="1.0.0-preview1" />
<PackageVersion Include="LibGit2Sharp" Version="0.30.0" />
<PackageVersion Include="Markdig" Version="0.43.0" />
<PackageVersion Include="MimeTypes" Version="2.5.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.13.1" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.13.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.13.0" />
GitBrowser/Components/Pages/Repo.razor +1 -1
diff --git a/GitBrowser/Components/Pages/Repo.razor b/GitBrowser/Components/Pages/Repo.razor
index 8fcb7cd..a3e81a6 100644
@@ -122,7 +122,7 @@
</div>
}
@if (!string.IsNullOrEmpty(fileContent))
@if (fileContent?.Length is > 0)
{
<FilePreview IsReadme="@isReadme" FileName="@fileName" FileContent="@fileContent" />
}
GitBrowser/Components/Pages/Repo.razor.cs +4 -3
diff --git a/GitBrowser/Components/Pages/Repo.razor.cs b/GitBrowser/Components/Pages/Repo.razor.cs
index 770b343..bfcf6ff 100644
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GitBrowser.Services;
using LibGit2Sharp;
@@ -28,7 +29,7 @@ public sealed partial class Repo(
private string currentPath = "";
private string displayPath = "";
private bool isViewingFile = false;
private string? fileContent = null;
private Stream? fileContent = null;
private string? fileName = null;
private bool isReadme = false;
private Repository? repo = null;
@@ -118,7 +119,7 @@ public sealed partial class Repo(
// It's a file - display it
isViewingFile = true;
var blob = (Blob)treeEntry.Target;
fileContent = blob.GetContentText();
fileContent = blob.GetContentStream();
fileName = System.IO.Path.GetFileName(currentPath);
isReadme = false;
@@ -188,7 +189,7 @@ public sealed partial class Repo(
if (readmeEntry != null && readmeEntry.TargetType == TreeEntryTargetType.Blob)
{
var blob = (Blob)readmeEntry.Target;
fileContent = blob.GetContentText();
fileContent = blob.GetContentStream();
fileName = readmeEntry.Name;
isReadme = true;
break;
GitBrowser/Components/Shared/FilePreview.razor +27 -6
diff --git a/GitBrowser/Components/Shared/FilePreview.razor b/GitBrowser/Components/Shared/FilePreview.razor
index 53239c2..2adfaa4 100644
@@ -1,6 +1,8 @@
@using System.IO
@using GitBrowser.SyntaxHighlighter
@using System.Web
@using System.Text
@using Markdig
<div class="file-viewer @(IsReadme ? "readme-viewer" : "")">
<div class="file-viewer-header">
@@ -13,6 +15,10 @@
</div>
@code {
private static readonly MarkdownPipeline markdownPipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
[Parameter]
public bool IsReadme { get; set; } = false;
@@ -20,20 +26,35 @@
public required string FileName { get; set; }
[Parameter]
public required string FileContent { get; set; }
public required Stream FileContent { get; set; }
private string filePreview = "";
private MarkupString filePreview = (MarkupString)"";
protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
byte[] fileBytes;
using (var ms = new MemoryStream())
{
await FileContent.CopyToAsync(ms);
fileBytes = ms.ToArray();
}
var extension = Path.GetExtension(FileName);
if (Syntax.CanHighlightFile(extension))
if (MimeTypes.TryGetMimeType(FileName, out var mimeType) && mimeType.StartsWith("image/"))
{
filePreview = (MarkupString)$"<img src=\"data:{mimeType};base64,{Convert.ToBase64String(fileBytes)}\">";
}
else if (string.Equals(extension, ".md", StringComparison.OrdinalIgnoreCase))
{
filePreview = (MarkupString)$"<div class=\"markdown\">{Markdown.ToHtml(Encoding.UTF8.GetString(fileBytes), markdownPipeline)}</div>";
}
else if (extension is not null && Syntax.CanHighlightFile(extension))
{
filePreview = Syntax.HighlightSyntaxHtml(extension, FileContent);
filePreview = (MarkupString)Syntax.HighlightSyntaxHtml(extension, Encoding.UTF8.GetString(fileBytes));
}
else
{
filePreview = $"<pre class=\"plain\">{HttpUtility.HtmlEncode(FileContent)}</pre>";
filePreview = (MarkupString)$"<pre class=\"plain\">{HttpUtility.HtmlEncode(Encoding.UTF8.GetString(fileBytes))}</pre>";
}
}
}
GitBrowser/Components/Shared/FilePreview.razor.css +6 -1
diff --git a/GitBrowser/Components/Shared/FilePreview.razor.css b/GitBrowser/Components/Shared/FilePreview.razor.css
index ba52a4c..3a73348 100644
@@ -20,7 +20,7 @@
overflow-x: auto;
}
.file-content pre {
.file-content ::deep pre {
margin: 0;
padding: 1rem;
font-family: "Consolas", "Monaco", "Courier New", monospace;
@@ -30,3 +30,8 @@
white-space: pre;
overflow-x: auto;
}
.file-content ::deep .markdown {
margin: 0;
padding: 1rem;
}
GitBrowser/GitBrowser.csproj +5 -0
diff --git a/GitBrowser/GitBrowser.csproj b/GitBrowser/GitBrowser.csproj
index a3506b8..3c586ca 100644
@@ -5,6 +5,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LibGit2Sharp" />
<PackageReference Include="Markdig" />
<PackageReference Include="MimeTypes">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
GitBrowser/packages.lock.json +12 -0
diff --git a/GitBrowser/packages.lock.json b/GitBrowser/packages.lock.json
index f0fab1f..e5a3eee 100644
@@ -17,12 +17,24 @@
"LibGit2Sharp.NativeBinaries": "[2.0.322]"
}
},
"Markdig": {
"type": "Direct",
"requested": "[0.43.0, )",
"resolved": "0.43.0",
"contentHash": "tbP3Y/GYC5pIUWXw6Iqf+R5CwBGA4VJswnlgS2x3TgcHoH4IlnGVmL7hfA1sXkZoCPm/4mhzS8cJUkWhQthaPg=="
},
"Microsoft.AspNetCore.App.Internal.Assets": {
"type": "Direct",
"requested": "[10.0.0, )",
"resolved": "10.0.0",
"contentHash": "0gZrESKwnlmbE8Br8XIy3kk7Pj0++9T2Ly+A8BFYYgo5EgfqWEln26cho+l92KOaHUzclhvz314RiwE910s24g=="
},
"MimeTypes": {
"type": "Direct",
"requested": "[2.5.2, )",
"resolved": "2.5.2",
"contentHash": "vm4xrNt+i6OVRQ8vhfCcmDIUg3qvjyCTkSTNVTDFohsG6CXEpMaVFkidECL6yRYpHDnz4TqXhDoEQAcnHCu/tw=="
},
"OpenTelemetry.Exporter.OpenTelemetryProtocol": {
"type": "Direct",
"requested": "[1.13.1, )",
GitBrowser/wwwroot/app.css +1 -1
diff --git a/GitBrowser/wwwroot/app.css b/GitBrowser/wwwroot/app.css
index 8a9e3e1..435fc2b 100644
@@ -105,7 +105,7 @@ pre {
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: var(--gh-color-canvas-default);
background-color: var(--gh-color-canvas-default) !important;
}
pre code {