@using System.Text.RegularExpressions @using Microsoft.AspNetCore.Components.Rendering @namespace Shared.Components @if (Doc != null) {
@if (Doc.Syndicates != null) { foreach (var syn in Doc.Syndicates) { } }

@Doc.Title

@if (!string.IsNullOrWhiteSpace(Doc.Content)) {
@RenderDescription(Doc.Content)
}
} @code { [Parameter] public DocData? Doc { get; set; } [Parameter] public EventCallback OnClose { get; set; } [Parameter] public EventCallback OnNavigate { get; set; } private void HandleClose() { _ = OnClose.InvokeAsync(); } private void HandleBackdropClick() { _ = OnClose.InvokeAsync(); } private void Navigate(string name) { _ = OnNavigate.InvokeAsync(name); } private static CardData? LookupCard(string cardName) { return CardDatabase.Cards.FirstOrDefault(c => string.Equals(c.Name, cardName, StringComparison.OrdinalIgnoreCase)); } private static DocData? LookupDoc(string title) { return DocDatabase.Docs.FirstOrDefault(d => string.Equals(d.Title, title, StringComparison.OrdinalIgnoreCase)); } private RenderFragment RenderDescription(string content) { return builder => { if (string.IsNullOrWhiteSpace(content)) return; var seq = 0; var lines = content.Split(['\r', '\n'], StringSplitOptions.None); var inList = false; var tableLines = new List(); var inTable = false; for (var i = 0; i < lines.Length; i++) { var line = lines[i]; var trimmedLine = line.Trim(); // Handle Tables if (trimmedLine.StartsWith("|") && trimmedLine.EndsWith("|")) { inTable = true; tableLines.Add(trimmedLine); continue; } if (inTable) { RenderTable(builder, ref seq, tableLines); tableLines.Clear(); inTable = false; } // Handle Lists if (trimmedLine.StartsWith("- ") || trimmedLine.StartsWith("* ")) { if (!inList) { builder.OpenElement(seq++, "ul"); inList = true; } var isCardListItem = trimmedLine.Contains("[[") && trimmedLine.Contains("]]"); builder.OpenElement(seq++, "li"); RenderInlines(builder, ref seq, trimmedLine[2..], true); builder.CloseElement(); continue; } if (inList) { builder.CloseElement(); // Close ul inList = false; } // Handle Headings if (trimmedLine.StartsWith("### ")) { builder.OpenElement(seq++, "h4"); RenderInlines(builder, ref seq, trimmedLine[4..], false); builder.CloseElement(); } else if (trimmedLine.StartsWith("## ")) { builder.OpenElement(seq++, "h3"); RenderInlines(builder, ref seq, trimmedLine[3..], false); builder.CloseElement(); } else if (trimmedLine.StartsWith("# ")) { builder.OpenElement(seq++, "h2"); RenderInlines(builder, ref seq, trimmedLine[2..], false); builder.CloseElement(); } else if (!string.IsNullOrWhiteSpace(trimmedLine)) { builder.OpenElement(seq++, "p"); RenderInlines(builder, ref seq, line, false); builder.CloseElement(); } } if (inTable) RenderTable(builder, ref seq, tableLines); if (inList) builder.CloseElement(); }; } private void RenderTable(RenderTreeBuilder builder, ref int seq, List lines) { if (lines.Count < 2) return; builder.OpenElement(seq++, "div"); builder.AddAttribute(seq++, "class", "table-responsive"); builder.OpenElement(seq++, "table"); builder.AddAttribute(seq++, "class", "markdown-table"); var hasHeader = lines.Count > 2 && lines[1].Contains("---"); var startIdx = 0; if (hasHeader) { builder.OpenElement(seq++, "thead"); builder.OpenElement(seq++, "tr"); var cells = lines[0].Split('|', StringSplitOptions.RemoveEmptyEntries); foreach (var cell in cells) { builder.OpenElement(seq++, "th"); RenderInlines(builder, ref seq, cell.Trim(), false); builder.CloseElement(); } builder.CloseElement(); builder.CloseElement(); startIdx = 2; } builder.OpenElement(seq++, "tbody"); for (var i = startIdx; i < lines.Count; i++) { if (i == 1 && hasHeader) continue; builder.OpenElement(seq++, "tr"); var cells = lines[i].Split('|', StringSplitOptions.RemoveEmptyEntries); foreach (var cell in cells) { builder.OpenElement(seq++, "td"); RenderInlines(builder, ref seq, cell.Trim(), false); builder.CloseElement(); } builder.CloseElement(); } builder.CloseElement(); builder.CloseElement(); builder.CloseElement(); } private void RenderInlines(RenderTreeBuilder builder, ref int seq, string text, bool renderAsImage) { // Simple regex-based inline parser for Bold, Italics, and [[Card Links]] var remaining = text; while (!string.IsNullOrEmpty(remaining)) { var boldMatch = Regex.Match(remaining, @"\*\*(.*?)\*\*"); var italicMatch = Regex.Match(remaining, @"\*(.*?)\*"); var cardMatch = Regex.Match(remaining, @"\[\[(.*?)\]\]"); var bestIdx = int.MaxValue; Match? bestMatch = null; var matchType = 0; // 1: bold, 2: italic, 3: card if (boldMatch.Success && boldMatch.Index < bestIdx) { bestIdx = boldMatch.Index; bestMatch = boldMatch; matchType = 1; } if (italicMatch.Success && italicMatch.Index < bestIdx) { bestIdx = italicMatch.Index; bestMatch = italicMatch; matchType = 2; } if (cardMatch.Success && cardMatch.Index < bestIdx) { bestIdx = cardMatch.Index; bestMatch = cardMatch; matchType = 3; } if (bestMatch != null) { if (bestIdx > 0) builder.AddContent(seq++, remaining[..bestIdx]); var innerText = bestMatch.Groups[1].Value; if (matchType == 1) // Bold { builder.OpenElement(seq++, "strong"); builder.AddContent(seq++, innerText); builder.CloseElement(); } else if (matchType == 2) // Italic { builder.OpenElement(seq++, "em"); builder.AddContent(seq++, innerText); builder.CloseElement(); } else if (matchType == 3) // Card Link / Doc Link { var card = LookupCard(innerText); var doc = LookupDoc(innerText); if (card != null || doc != null) { builder.OpenElement(seq++, "button"); builder.AddAttribute(seq++, "class", "inline-card-btn"); builder.AddAttribute(seq++, "onclick", EventCallback.Factory.Create(this, () => Navigate(innerText))); builder.AddContent(seq++, innerText); builder.CloseElement(); } else { builder.AddContent(seq++, innerText); } } remaining = remaining[(bestIdx + bestMatch.Length)..]; } else { builder.AddContent(seq++, remaining); remaining = ""; } } } }