|
| 1 | +@using System.Globalization |
| 2 | +@using Downpatch.Web.Services |
| 3 | +@inject MarkdownPageService Pages |
| 4 | + |
| 5 | +<nav class="docs-breadcrumb" aria-label="Breadcrumb"> |
| 6 | + <a href="/guide">Guide</a> |
| 7 | + |
| 8 | + @foreach (var crumb in _crumbs) |
| 9 | + { |
| 10 | + <span class="docs-breadcrumb-sep">/</span> |
| 11 | + @if (crumb.IsCurrent || string.IsNullOrWhiteSpace(crumb.Href)) |
| 12 | + { |
| 13 | + <span aria-current="page">@crumb.Label</span> |
| 14 | + } |
| 15 | + else |
| 16 | + { |
| 17 | + <a href="@crumb.Href">@crumb.Label</a> |
| 18 | + } |
| 19 | + } |
| 20 | +</nav> |
| 21 | + |
| 22 | +@code { |
| 23 | + [Parameter] public string? ResolvedEntrySlug { get; set; } |
| 24 | + [Parameter] public string? CurrentTitle { get; set; } |
| 25 | + |
| 26 | + private List<BreadcrumbItem> _crumbs = new(); |
| 27 | + |
| 28 | + protected override void OnParametersSet() |
| 29 | + { |
| 30 | + _crumbs = BuildCrumbs(ResolvedEntrySlug, CurrentTitle); |
| 31 | + } |
| 32 | + |
| 33 | + private List<BreadcrumbItem> BuildCrumbs(string? resolvedEntrySlug, string? currentTitle) |
| 34 | + { |
| 35 | + var crumbs = new List<BreadcrumbItem>(); |
| 36 | + |
| 37 | + var path = (resolvedEntrySlug ?? "").Replace('\\', '/').Trim('/'); |
| 38 | + if (path.StartsWith("guide/", StringComparison.OrdinalIgnoreCase)) |
| 39 | + path = path["guide/".Length..]; |
| 40 | + |
| 41 | + if (path.EndsWith("/index", StringComparison.OrdinalIgnoreCase)) |
| 42 | + path = path[..^"/index".Length]; |
| 43 | + |
| 44 | + if (string.IsNullOrWhiteSpace(path)) |
| 45 | + return crumbs; |
| 46 | + |
| 47 | + var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); |
| 48 | + for (var i = 0; i < segments.Length; i++) |
| 49 | + { |
| 50 | + var segPath = string.Join('/', segments.Take(i + 1)); |
| 51 | + var isCurrent = i == segments.Length - 1; |
| 52 | + |
| 53 | + var label = isCurrent && !string.IsNullOrWhiteSpace(currentTitle) |
| 54 | + ? currentTitle! |
| 55 | + : ResolveCrumbLabel(segPath, segments[i]); |
| 56 | + |
| 57 | + crumbs.Add(new BreadcrumbItem( |
| 58 | + Label: label, |
| 59 | + Href: isCurrent ? null : "/guide/" + segPath, |
| 60 | + IsCurrent: isCurrent)); |
| 61 | + } |
| 62 | + |
| 63 | + return crumbs; |
| 64 | + } |
| 65 | + |
| 66 | + private string ResolveCrumbLabel(string routeSlug, string fallbackSegment) |
| 67 | + { |
| 68 | + if (Pages.TryGetRendered(routeSlug, out var page)) |
| 69 | + { |
| 70 | + if (page.FrontMatter.TryGetValue("nav_title", out var navTitle) && |
| 71 | + !string.IsNullOrWhiteSpace(navTitle)) |
| 72 | + { |
| 73 | + return navTitle; |
| 74 | + } |
| 75 | + |
| 76 | + if (!string.IsNullOrWhiteSpace(page.Title)) |
| 77 | + return page.Title; |
| 78 | + } |
| 79 | + |
| 80 | + return HumanizeSegment(fallbackSegment); |
| 81 | + } |
| 82 | + |
| 83 | + private static string HumanizeSegment(string value) |
| 84 | + { |
| 85 | + value = (value ?? "").Trim().Replace('-', ' ').Replace('_', ' '); |
| 86 | + if (value.Length == 0) |
| 87 | + return "Untitled"; |
| 88 | + |
| 89 | + var textInfo = CultureInfo.CurrentCulture.TextInfo; |
| 90 | + return textInfo.ToTitleCase(value.ToLowerInvariant()); |
| 91 | + } |
| 92 | + |
| 93 | + private sealed record BreadcrumbItem(string Label, string? Href, bool IsCurrent); |
| 94 | +} |
0 commit comments