DocsPage.razor 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. @page "/docs/{Page}"
  2. @inject HttpClient Http
  3. @inject NavigationManager Nav
  4. @inject IJSRuntime JS
  5. @using Markdig
  6. @implements IDisposable
  7. <PageTitle>Docs: @Page</PageTitle>
  8. <div class="min-h-screen bg-zinc-950 text-zinc-200 font-mono p-6 space-y-6">
  9. <!-- Header -->
  10. <div class="space-y-2">
  11. <h1 class="text-lg text-zinc-100">
  12. Docs:
  13. <span class="text-emerald-400">@Page</span>
  14. </h1>
  15. </div>
  16. @if (_isLoading)
  17. {
  18. <div class="text-zinc-500">loading documentation…</div>
  19. }
  20. else if (_notFound)
  21. {
  22. <div class="text-zinc-500">document not found</div>
  23. }
  24. else
  25. {
  26. <div class="markdown">
  27. @((MarkupString)_htmlContent!)
  28. </div>
  29. }
  30. </div>
  31. @code {
  32. [Parameter] public string Page { get; set; } = string.Empty;
  33. private string? _htmlContent;
  34. private bool _isLoading = true;
  35. private bool _notFound;
  36. private static readonly MarkdownPipeline Pipeline =
  37. new MarkdownPipelineBuilder()
  38. .UseAdvancedExtensions()
  39. .UseAutoIdentifiers()
  40. .Build();
  41. private bool _pendingScroll;
  42. protected override void OnInitialized()
  43. {
  44. Nav.LocationChanged += HandleLocationChanged;
  45. }
  46. private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
  47. {
  48. // Trigger re-render so OnAfterRenderAsync runs again
  49. _pendingScroll = true;
  50. InvokeAsync(StateHasChanged);
  51. }
  52. protected override async Task OnParametersSetAsync()
  53. {
  54. _isLoading = true;
  55. _notFound = false;
  56. _pendingScroll = true;
  57. try
  58. {
  59. var decoded = Uri.UnescapeDataString(Page);
  60. if (!decoded.EndsWith(".md", StringComparison.OrdinalIgnoreCase))
  61. decoded += ".md";
  62. var url = $"_content/Shared.Rcl/raw_docs/{decoded}";
  63. var markdown = await Http.GetStringAsync(url);
  64. _htmlContent = Markdown.ToHtml(markdown, Pipeline);
  65. }
  66. catch
  67. {
  68. _notFound = true;
  69. _htmlContent = null;
  70. }
  71. finally
  72. {
  73. _isLoading = false;
  74. }
  75. }
  76. protected override async Task OnAfterRenderAsync(bool firstRender)
  77. {
  78. if (_pendingScroll && !_isLoading && !_notFound)
  79. {
  80. _pendingScroll = false;
  81. await ScrollToFragmentAsync();
  82. }
  83. }
  84. private async Task ScrollToFragmentAsync()
  85. {
  86. var uri = new Uri(Nav.Uri);
  87. var fragment = uri.Fragment;
  88. if (!string.IsNullOrWhiteSpace(fragment))
  89. {
  90. var anchor = fragment.TrimStart('#');
  91. await JS.InvokeVoidAsync("scrollToAnchor", anchor);
  92. }
  93. }
  94. public void Dispose()
  95. {
  96. Nav.LocationChanged -= HandleLocationChanged;
  97. }
  98. }