@page "/docs/{Page}" @using Markdig @inject HttpClient Http @inject NavigationManager Nav @inject IJSRuntime JS @implements IDisposable Docs: @Page

Docs: @Page

@if (_isLoading) {
loading documentation…
} else if (_notFound) {
document not found
} else {
@((MarkupString)_htmlContent!)
}
@code { [Parameter] public string Page { get; set; } = string.Empty; private string? _htmlContent; private bool _isLoading = true; private bool _notFound; private static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseAutoIdentifiers() .Build(); private bool _pendingScroll; protected override void OnInitialized() { Nav.LocationChanged += HandleLocationChanged; } private void HandleLocationChanged(object? sender, LocationChangedEventArgs e) { // Trigger re-render so OnAfterRenderAsync runs again _pendingScroll = true; InvokeAsync(StateHasChanged); } protected override async Task OnParametersSetAsync() { _isLoading = true; _notFound = false; _pendingScroll = true; try { var decoded = Uri.UnescapeDataString(Page); if (!decoded.EndsWith(".md", StringComparison.OrdinalIgnoreCase)) decoded += ".md"; var url = $"_content/Shared.Rcl/raw_docs/{decoded}"; var markdown = await Http.GetStringAsync(url); _htmlContent = Markdown.ToHtml(markdown, Pipeline); } catch { _notFound = true; _htmlContent = null; } finally { _isLoading = false; } } protected override async Task OnAfterRenderAsync(bool firstRender) { if (_pendingScroll && !_isLoading && !_notFound) { _pendingScroll = false; await ScrollToFragmentAsync(); } } private async Task ScrollToFragmentAsync() { var uri = new Uri(Nav.Uri); var fragment = uri.Fragment; if (!string.IsNullOrWhiteSpace(fragment)) { var anchor = fragment.TrimStart('#'); await JS.InvokeVoidAsync("scrollToAnchor", anchor); } } public void Dispose() { Nav.LocationChanged -= HandleLocationChanged; } }