Commit: 9bb0c77
Parent: 1732d66

Refactor to code-behind

Mårten Åsberg committed on 2025-11-09 at 20:43
GitBrowser/Components/Layout/MainLayout.razor +0 -6
diff --git a/GitBrowser/Components/Layout/MainLayout.razor b/GitBrowser/Components/Layout/MainLayout.razor
index e44a591..a267164 100644
@@ -16,9 +16,3 @@
@Body
</main>
</div>
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>
GitBrowser/Components/Layout/MainLayout.razor.css +0 -23
diff --git a/GitBrowser/Components/Layout/MainLayout.razor.css b/GitBrowser/Components/Layout/MainLayout.razor.css
index c8453bc..2c996ca 100644
@@ -64,26 +64,3 @@
padding: 16px 16px;
}
}
#blazor-error-ui {
color-scheme: light only;
background: #fff3cd;
border-top: 1px solid #ffc107;
bottom: 0;
box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
box-sizing: border-box;
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
color: #856404;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
GitBrowser/Components/Pages/Commit.razor +0 -167
diff --git a/GitBrowser/Components/Pages/Commit.razor b/GitBrowser/Components/Pages/Commit.razor
index 4851364..c918f83 100644
@@ -1,10 +1,4 @@
@page "/repo/commit/{Hash}"
@using LibGit2Sharp
@using Microsoft.Extensions.Options
@using Microsoft.AspNetCore.Http
@inject IOptions<RepositoryConfiguration> repoConfig
@inject IHttpContextAccessor HttpContextAccessor
@implements IDisposable
<PageTitle>Commit @Hash</PageTitle>
@@ -100,164 +94,3 @@ else if (commit != null)
}
</div>
}
@code {
[Parameter]
public string? Hash { get; set; }
private Repository? repo = null;
private LibGit2Sharp.Commit? commit = null;
private LibGit2Sharp.Commit? childCommit = null;
private bool notFound = false;
private string commitTitle = "";
private string commitBody = "";
private List<FileDiff> fileDiffs = new();
private class FileDiff
{
public string Path { get; set; } = "";
public int LinesAdded { get; set; }
public int LinesDeleted { get; set; }
public bool IsLarge { get; set; }
public List<DiffLine> Lines { get; set; } = new();
}
private class DiffLine
{
public string Type { get; set; } = ""; // "add", "del", "context", "hunk"
public string Content { get; set; } = "";
}
protected override void OnInitialized()
{
if (string.IsNullOrEmpty(Hash))
{
SetNotFound();
return;
}
var repoPath = repoConfig.Value.Path;
if (string.IsNullOrEmpty(repoPath) || !Repository.IsValid(repoPath))
{
SetNotFound();
return;
}
repo = new Repository(repoPath);
try
{
commit = repo.Lookup<LibGit2Sharp.Commit>(Hash);
if (commit == null)
{
SetNotFound();
return;
}
// Split message into title and body
var message = commit.Message;
var firstLineEnd = message.IndexOf('\n');
if (firstLineEnd > 0)
{
commitTitle = message.Substring(0, firstLineEnd).Trim();
commitBody = message.Substring(firstLineEnd + 1).Trim();
}
else
{
commitTitle = message.Trim();
commitBody = "";
}
// Find child commit
try
{
foreach (var c in repo.Commits.QueryBy(new CommitFilter { FirstParentOnly = false }))
{
if (c.Parents.Any(p => p.Sha == commit.Sha))
{
childCommit = c;
break;
}
}
}
catch
{
// If we can't find a child, just continue
}
// Load diffs (including initial commit with no parents)
var parentTree = commit.Parents.Any() ? commit.Parents.First().Tree : null;
var patch = repo.Diff.Compare<Patch>(parentTree, commit.Tree);
foreach (var fileChange in patch)
{
var totalLines = fileChange.LinesAdded + fileChange.LinesDeleted;
var fileDiff = new FileDiff
{
Path = fileChange.Path,
LinesAdded = fileChange.LinesAdded,
LinesDeleted = fileChange.LinesDeleted,
IsLarge = totalLines > 1000
};
if (!fileDiff.IsLarge)
{
fileDiff.Lines = ParseDiff(fileChange.Patch);
}
fileDiffs.Add(fileDiff);
}
}
catch
{
SetNotFound();
}
}
private List<DiffLine> ParseDiff(string patch)
{
var lines = new List<DiffLine>();
foreach (var line in patch.Split('\n'))
{
if (string.IsNullOrEmpty(line))
continue;
if (line.StartsWith("@@"))
{
lines.Add(new DiffLine { Type = "hunk", Content = line });
}
else if (line.StartsWith("+") && !line.StartsWith("+++"))
{
lines.Add(new DiffLine { Type = "add", Content = line.Substring(1) });
}
else if (line.StartsWith("-") && !line.StartsWith("---"))
{
lines.Add(new DiffLine { Type = "del", Content = line.Substring(1) });
}
else if (!line.StartsWith("+++") && !line.StartsWith("---"))
{
// Context line or other content
var content = line.StartsWith(" ") ? line.Substring(1) : line;
lines.Add(new DiffLine { Type = "context", Content = content });
}
}
return lines;
}
private void SetNotFound()
{
notFound = true;
var httpContext = HttpContextAccessor.HttpContext;
if (httpContext != null)
{
httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
}
}
public void Dispose()
{
repo?.Dispose();
}
}
GitBrowser/Components/Pages/Commit.razor.cs +177 -0
diff --git a/GitBrowser/Components/Pages/Commit.razor.cs b/GitBrowser/Components/Pages/Commit.razor.cs
new file mode 100644
index 0000000..56c8456
@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LibGit2Sharp;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace GitBrowser.Components.Pages;
public partial class Commit : IDisposable
{
[Parameter]
public string? Hash { get; set; }
[Inject]
public required IOptions<RepositoryConfiguration> RepoConfig { get; set; }
[Inject]
public required IHttpContextAccessor HttpContextAccessor { get; set; }
private Repository? repo = null;
private LibGit2Sharp.Commit? commit = null;
private LibGit2Sharp.Commit? childCommit = null;
private bool notFound = false;
private string commitTitle = "";
private string commitBody = "";
private List<FileDiff> fileDiffs = new();
private class FileDiff
{
public string Path { get; set; } = "";
public int LinesAdded { get; set; }
public int LinesDeleted { get; set; }
public bool IsLarge { get; set; }
public List<DiffLine> Lines { get; set; } = new();
}
private class DiffLine
{
public string Type { get; set; } = ""; // "add", "del", "context", "hunk"
public string Content { get; set; } = "";
}
protected override void OnInitialized()
{
if (string.IsNullOrEmpty(Hash))
{
SetNotFound();
return;
}
var repoPath = RepoConfig.Value.Path;
if (string.IsNullOrEmpty(repoPath) || !Repository.IsValid(repoPath))
{
SetNotFound();
return;
}
repo = new Repository(repoPath);
try
{
commit = repo.Lookup<LibGit2Sharp.Commit>(Hash);
if (commit == null)
{
SetNotFound();
return;
}
// Split message into title and body
var message = commit.Message;
var firstLineEnd = message.IndexOf('\n');
if (firstLineEnd > 0)
{
commitTitle = message.Substring(0, firstLineEnd).Trim();
commitBody = message.Substring(firstLineEnd + 1).Trim();
}
else
{
commitTitle = message.Trim();
commitBody = "";
}
// Find child commit
try
{
foreach (var c in repo.Commits.QueryBy(new CommitFilter { FirstParentOnly = false }))
{
if (c.Parents.Any(p => p.Sha == commit.Sha))
{
childCommit = c;
break;
}
}
}
catch
{
// If we can't find a child, just continue
}
// Load diffs (including initial commit with no parents)
var parentTree = commit.Parents.Any() ? commit.Parents.First().Tree : null;
var patch = repo.Diff.Compare<Patch>(parentTree, commit.Tree);
foreach (var fileChange in patch)
{
var totalLines = fileChange.LinesAdded + fileChange.LinesDeleted;
var fileDiff = new FileDiff
{
Path = fileChange.Path,
LinesAdded = fileChange.LinesAdded,
LinesDeleted = fileChange.LinesDeleted,
IsLarge = totalLines > 1000,
};
if (!fileDiff.IsLarge)
{
fileDiff.Lines = ParseDiff(fileChange.Patch);
}
fileDiffs.Add(fileDiff);
}
}
catch
{
SetNotFound();
}
}
private List<DiffLine> ParseDiff(string patch)
{
var lines = new List<DiffLine>();
foreach (var line in patch.Split('\n'))
{
if (string.IsNullOrEmpty(line))
continue;
if (line.StartsWith("@@"))
{
lines.Add(new DiffLine { Type = "hunk", Content = line });
}
else if (line.StartsWith("+") && !line.StartsWith("+++"))
{
lines.Add(new DiffLine { Type = "add", Content = line.Substring(1) });
}
else if (line.StartsWith("-") && !line.StartsWith("---"))
{
lines.Add(new DiffLine { Type = "del", Content = line.Substring(1) });
}
else if (!line.StartsWith("+++") && !line.StartsWith("---"))
{
// Context line or other content
var content = line.StartsWith(" ") ? line.Substring(1) : line;
lines.Add(new DiffLine { Type = "context", Content = content });
}
}
return lines;
}
private void SetNotFound()
{
notFound = true;
var httpContext = HttpContextAccessor.HttpContext;
if (httpContext != null)
{
httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
}
}
public void Dispose()
{
repo?.Dispose();
}
}
GitBrowser/Components/Pages/Repo.razor +0 -245
diff --git a/GitBrowser/Components/Pages/Repo.razor b/GitBrowser/Components/Pages/Repo.razor
index 65b5bc2..72a89c3 100644
@@ -1,12 +1,6 @@
@page "/repo"
@page "/repo/tree/{Branch}/{*Path}"
@using LibGit2Sharp
@using Microsoft.Extensions.Options
@using Microsoft.AspNetCore.Http
@using System.Text
@inject IOptions<RepositoryConfiguration> repoConfig
@inject IHttpContextAccessor HttpContextAccessor
@implements IDisposable
<PageTitle>Repository</PageTitle>
@@ -127,242 +121,3 @@ else
}
</div>
}
@code {
[Parameter]
public string? Branch { get; set; }
[Parameter]
public string? Path { get; set; }
private IEnumerable<TreeEntry>? entries;
private string currentBranch = "main";
private string currentPath = "";
private string displayPath = "";
private bool notFound = false;
private bool isViewingFile = false;
private string? fileContent = null;
private string? fileName = null;
private bool isReadme = false;
private Repository? repo = null;
private LibGit2Sharp.Commit? currentCommit = null;
private class CommitInfo
{
public string MessageShort { get; set; } = "";
public DateTimeOffset When { get; set; }
public string Sha { get; set; } = "";
}
private Dictionary<string, CommitInfo?> commitCache = new();
protected override void OnInitialized()
{
var repoPath = repoConfig.Value.Path;
if (string.IsNullOrEmpty(repoPath) || !Repository.IsValid(repoPath))
{
entries = Enumerable.Empty<TreeEntry>();
return;
}
repo = new Repository(repoPath);
// Determine which branch/commit to use
var branchOrCommit = Branch ?? repo.Head.FriendlyName;
currentPath = Path ?? "";
// Try to find it as a branch first
var branch = repo.Branches[branchOrCommit];
if (branch != null)
{
currentBranch = branch.FriendlyName;
currentCommit = branch.Tip;
}
else
{
// Try to look it up as a commit hash
currentCommit = repo.Lookup<LibGit2Sharp.Commit>(branchOrCommit);
if (currentCommit != null)
{
currentBranch = currentCommit.Sha.Substring(0, 7);
}
}
if (currentCommit == null)
{
SetNotFound();
return;
}
// Check if the path is a file or directory
if (!string.IsNullOrEmpty(currentPath))
{
var treeEntry = currentCommit[currentPath];
if (treeEntry == null)
{
SetNotFound();
return;
}
if (treeEntry.TargetType == TreeEntryTargetType.Blob)
{
// It's a file - display it
isViewingFile = true;
var blob = (Blob)treeEntry.Target;
fileContent = blob.GetContentText();
fileName = System.IO.Path.GetFileName(currentPath);
isReadme = false;
// Show the directory contents
displayPath = System.IO.Path.GetDirectoryName(currentPath)?.Replace("\\", "/") ?? "";
var parentPath = string.IsNullOrEmpty(displayPath) ? "" : displayPath;
var parentEntry = string.IsNullOrEmpty(parentPath) ? null : currentCommit[parentPath];
if (parentEntry?.TargetType == TreeEntryTargetType.Tree)
{
entries = ((Tree)parentEntry.Target).ToList();
}
else if (string.IsNullOrEmpty(parentPath))
{
entries = currentCommit.Tree.ToList();
}
else
{
entries = Enumerable.Empty<TreeEntry>();
}
}
else if (treeEntry.TargetType == TreeEntryTargetType.Tree)
{
// It's a directory
var tree = (Tree)treeEntry.Target;
entries = tree.ToList();
displayPath = currentPath;
// Look for README file
TryFindAndDisplayReadme(tree);
}
else
{
SetNotFound();
return;
}
}
else
{
// Root directory
entries = currentCommit.Tree.ToList();
displayPath = "";
// Look for README file
TryFindAndDisplayReadme(currentCommit.Tree);
}
}
private void TryFindAndDisplayReadme(Tree tree)
{
var readmeNames = new[] { "README.md", "README.txt", "README", "Readme.md", "Readme.txt", "Readme", "readme.md", "readme.txt", "readme" };
foreach (var readmeName in readmeNames)
{
var readmeEntry = tree.FirstOrDefault(e => e.Name.Equals(readmeName, StringComparison.OrdinalIgnoreCase));
if (readmeEntry != null && readmeEntry.TargetType == TreeEntryTargetType.Blob)
{
var blob = (Blob)readmeEntry.Target;
fileContent = blob.GetContentText();
fileName = readmeEntry.Name;
isReadme = true;
break;
}
}
}
private CommitInfo? GetLastCommitForPath(string path)
{
if (repo == null || currentCommit == null)
return null;
if (commitCache.TryGetValue(path, out var cached))
return cached;
try
{
var commits = repo.Commits.QueryBy(path, new CommitFilter {
IncludeReachableFrom = currentCommit,
FirstParentOnly = true
});
var lastCommit = commits.FirstOrDefault()?.Commit;
if (lastCommit != null)
{
var info = new CommitInfo
{
MessageShort = lastCommit.MessageShort,
When = lastCommit.Author.When,
Sha = lastCommit.Sha
};
commitCache[path] = info;
return info;
}
}
catch
{
// If we can't get commit info, just return null
}
commitCache[path] = null;
return null;
}
private bool IsValidCommitHash(string value)
{
if (string.IsNullOrEmpty(value))
return false;
// Git commit hashes are 7-40 hex characters
if (value.Length < 7 || value.Length > 40)
return false;
// Check if all characters are valid hex digits
foreach (var c in value)
{
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
return false;
}
return true;
}
private string GetRelativeTime(DateTimeOffset when)
{
var now = DateTimeOffset.Now;
var diff = now - when;
if (diff.TotalSeconds < 60)
return "just now";
if (diff.TotalMinutes < 60)
return $"{(int)diff.TotalMinutes} minute{((int)diff.TotalMinutes == 1 ? "" : "s")} ago";
if (diff.TotalHours < 24)
return $"{(int)diff.TotalHours} hour{((int)diff.TotalHours == 1 ? "" : "s")} ago";
if (diff.TotalDays < 30)
return $"{(int)diff.TotalDays} day{((int)diff.TotalDays == 1 ? "" : "s")} ago";
if (diff.TotalDays < 365)
return $"{(int)(diff.TotalDays / 30)} month{((int)(diff.TotalDays / 30) == 1 ? "" : "s")} ago";
return $"{(int)(diff.TotalDays / 365)} year{((int)(diff.TotalDays / 365) == 1 ? "" : "s")} ago";
}
private void SetNotFound()
{
notFound = true;
var httpContext = HttpContextAccessor.HttpContext;
if (httpContext != null)
{
httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
}
}
public void Dispose()
{
repo?.Dispose();
}
}
GitBrowser/Components/Pages/Repo.razor.cs +266 -0
diff --git a/GitBrowser/Components/Pages/Repo.razor.cs b/GitBrowser/Components/Pages/Repo.razor.cs
new file mode 100644
index 0000000..15797a2
@@ -0,0 +1,266 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LibGit2Sharp;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
namespace GitBrowser.Components.Pages;
public partial class Repo : IDisposable
{
[Parameter]
public string? Branch { get; set; }
[Parameter]
public string? Path { get; set; }
[Inject]
public required IOptions<RepositoryConfiguration> RepoConfig { get; set; }
[Inject]
public required IHttpContextAccessor HttpContextAccessor { get; set; }
private IEnumerable<TreeEntry>? entries;
private string currentBranch = "main";
private string currentPath = "";
private string displayPath = "";
private bool notFound = false;
private bool isViewingFile = false;
private string? fileContent = null;
private string? fileName = null;
private bool isReadme = false;
private Repository? repo = null;
private LibGit2Sharp.Commit? currentCommit = null;
private class CommitInfo
{
public string MessageShort { get; set; } = "";
public DateTimeOffset When { get; set; }
public string Sha { get; set; } = "";
}
private Dictionary<string, CommitInfo?> commitCache = new();
protected override void OnInitialized()
{
var repoPath = RepoConfig.Value.Path;
if (string.IsNullOrEmpty(repoPath) || !Repository.IsValid(repoPath))
{
entries = Enumerable.Empty<TreeEntry>();
return;
}
repo = new Repository(repoPath);
// Determine which branch/commit to use
var branchOrCommit = Branch ?? repo.Head.FriendlyName;
currentPath = Path ?? "";
// Try to find it as a branch first
var branch = repo.Branches[branchOrCommit];
if (branch != null)
{
currentBranch = branch.FriendlyName;
currentCommit = branch.Tip;
}
else
{
// Try to look it up as a commit hash
currentCommit = repo.Lookup<LibGit2Sharp.Commit>(branchOrCommit);
if (currentCommit != null)
{
currentBranch = currentCommit.Sha.Substring(0, 7);
}
}
if (currentCommit == null)
{
SetNotFound();
return;
}
// Check if the path is a file or directory
if (!string.IsNullOrEmpty(currentPath))
{
var treeEntry = currentCommit[currentPath];
if (treeEntry == null)
{
SetNotFound();
return;
}
if (treeEntry.TargetType == TreeEntryTargetType.Blob)
{
// It's a file - display it
isViewingFile = true;
var blob = (Blob)treeEntry.Target;
fileContent = blob.GetContentText();
fileName = System.IO.Path.GetFileName(currentPath);
isReadme = false;
// Show the directory contents
displayPath = System.IO.Path.GetDirectoryName(currentPath)?.Replace("\\", "/") ?? "";
var parentPath = string.IsNullOrEmpty(displayPath) ? "" : displayPath;
var parentEntry = string.IsNullOrEmpty(parentPath) ? null : currentCommit[parentPath];
if (parentEntry?.TargetType == TreeEntryTargetType.Tree)
{
entries = ((Tree)parentEntry.Target).ToList();
}
else if (string.IsNullOrEmpty(parentPath))
{
entries = currentCommit.Tree.ToList();
}
else
{
entries = Enumerable.Empty<TreeEntry>();
}
}
else if (treeEntry.TargetType == TreeEntryTargetType.Tree)
{
// It's a directory
var tree = (Tree)treeEntry.Target;
entries = tree.ToList();
displayPath = currentPath;
// Look for README file
TryFindAndDisplayReadme(tree);
}
else
{
SetNotFound();
return;
}
}
else
{
// Root directory
entries = currentCommit.Tree.ToList();
displayPath = "";
// Look for README file
TryFindAndDisplayReadme(currentCommit.Tree);
}
}
private void TryFindAndDisplayReadme(Tree tree)
{
var readmeNames = new[]
{
"README.md",
"README.txt",
"README",
"Readme.md",
"Readme.txt",
"Readme",
"readme.md",
"readme.txt",
"readme",
};
foreach (var readmeName in readmeNames)
{
var readmeEntry = tree.FirstOrDefault(e => e.Name.Equals(readmeName, StringComparison.OrdinalIgnoreCase));
if (readmeEntry != null && readmeEntry.TargetType == TreeEntryTargetType.Blob)
{
var blob = (Blob)readmeEntry.Target;
fileContent = blob.GetContentText();
fileName = readmeEntry.Name;
isReadme = true;
break;
}
}
}
private CommitInfo? GetLastCommitForPath(string path)
{
if (repo == null || currentCommit == null)
return null;
if (commitCache.TryGetValue(path, out var cached))
return cached;
try
{
var commits = repo.Commits.QueryBy(
path,
new CommitFilter { IncludeReachableFrom = currentCommit, FirstParentOnly = true }
);
var lastCommit = commits.FirstOrDefault()?.Commit;
if (lastCommit != null)
{
var info = new CommitInfo
{
MessageShort = lastCommit.MessageShort,
When = lastCommit.Author.When,
Sha = lastCommit.Sha,
};
commitCache[path] = info;
return info;
}
}
catch
{
// If we can't get commit info, just return null
}
commitCache[path] = null;
return null;
}
private bool IsValidCommitHash(string value)
{
if (string.IsNullOrEmpty(value))
return false;
// Git commit hashes are 7-40 hex characters
if (value.Length < 7 || value.Length > 40)
return false;
// Check if all characters are valid hex digits
foreach (var c in value)
{
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')))
return false;
}
return true;
}
private string GetRelativeTime(DateTimeOffset when)
{
var now = DateTimeOffset.Now;
var diff = now - when;
if (diff.TotalSeconds < 60)
return "just now";
if (diff.TotalMinutes < 60)
return $"{(int)diff.TotalMinutes} minute{((int)diff.TotalMinutes == 1 ? "" : "s")} ago";
if (diff.TotalHours < 24)
return $"{(int)diff.TotalHours} hour{((int)diff.TotalHours == 1 ? "" : "s")} ago";
if (diff.TotalDays < 30)
return $"{(int)diff.TotalDays} day{((int)diff.TotalDays == 1 ? "" : "s")} ago";
if (diff.TotalDays < 365)
return $"{(int)(diff.TotalDays / 30)} month{((int)(diff.TotalDays / 30) == 1 ? "" : "s")} ago";
return $"{(int)(diff.TotalDays / 365)} year{((int)(diff.TotalDays / 365) == 1 ? "" : "s")} ago";
}
private void SetNotFound()
{
notFound = true;
var httpContext = HttpContextAccessor.HttpContext;
if (httpContext != null)
{
httpContext.Response.StatusCode = StatusCodes.Status404NotFound;
}
}
public void Dispose()
{
repo?.Dispose();
}
}
GitBrowser/wwwroot/app.css +0 -64
diff --git a/GitBrowser/wwwroot/app.css b/GitBrowser/wwwroot/app.css
index 38094f5..77cc3f6 100644
@@ -106,67 +106,3 @@ pre code {
padding: 0;
border-radius: 0;
}
.btn-primary {
color: #ffffff;
background-color: #2da44e;
border: 1px solid rgba(31, 35, 40, 0.15);
padding: 5px 16px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
border-radius: 6px;
cursor: pointer;
transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1);
}
.btn-primary:hover {
background-color: #2c974b;
border-color: rgba(31, 35, 40, 0.15);
}
.btn {
color: var(--gh-color-fg-default);
background-color: var(--gh-color-btn-bg);
border: 1px solid var(--gh-color-btn-border);
padding: 5px 16px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
border-radius: 6px;
cursor: pointer;
transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1);
}
.btn:hover {
background-color: var(--gh-color-btn-hover-bg);
border-color: var(--gh-color-btn-hover-border);
}
.valid.modified:not([type="checkbox"]) {
border-color: var(--gh-color-success-fg);
}
.invalid {
border-color: var(--gh-color-danger-fg);
}
.validation-message {
color: var(--gh-color-danger-fg);
font-size: 12px;
margin-top: 4px;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=)
no-repeat 1rem/1.8rem,
#d1242f;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
border-radius: 6px;
margin: 16px;
}
.blazor-error-boundary::after {
content: "An error has occurred.";
}