@namespace Shared.Pages @page "/docs" Docs

Documentation

Rules, keywords, factions, and more.

@if (selectedDoc == null) {
@foreach (var doc in filteredDocs) {
@doc.Category

@doc.Title

@{ var desc = GetDescription(doc); } @if (desc != null) {

@desc

}
} @if (!filteredDocs.Any()) {

No documentation found matching your search.

}
} else {
@selectedDoc.Category

@selectedDoc.Title

@{ var desc = GetDescription(selectedDoc); } @if (desc != null) {

@desc

}
@if (!string.IsNullOrWhiteSpace(selectedDoc.Content)) {
@((MarkupString)FormatContent(selectedDoc.Content))
} @if (HasExtraFrontmatter(selectedDoc)) {

Details

@foreach (var kvp in selectedDoc.Frontmatter.Where(kvp => kvp.Key != "category" && kvp.Key != "description")) {
@kvp.Key @kvp.Value
}
}
}
@code { private string searchQuery = ""; private string? selectedCategory; private DocData? selectedDoc; private List categories = []; private List allDocs = []; protected override void OnInitialized() { allDocs = DocDatabase.Docs.OrderBy(d => d.Category).ThenBy(d => d.Title).ToList(); categories = allDocs.Select(d => d.Category).Distinct().OrderBy(c => c).ToList(); } private IEnumerable filteredDocs => allDocs .Where(d => (selectedCategory == null || d.Category == selectedCategory) && (string.IsNullOrWhiteSpace(searchQuery) || d.Title.Contains(searchQuery, StringComparison.OrdinalIgnoreCase) || (GetDescription(d)?.Contains(searchQuery, StringComparison.OrdinalIgnoreCase) ?? false) || d.Content.Contains(searchQuery, StringComparison.OrdinalIgnoreCase))); private void SelectCategory(string? category) { selectedCategory = category; selectedDoc = null; } private void SelectDoc(DocData? doc) { selectedDoc = doc; } private static string? GetDescription(DocData doc) { return doc.Frontmatter.TryGetValue("description", out var desc) && !string.IsNullOrWhiteSpace(desc) ? desc : null; } private static bool HasExtraFrontmatter(DocData doc) { var count = doc.Frontmatter.Count; if (count == 0) return false; if (count == 1 && doc.Frontmatter.ContainsKey("category")) return false; if (count == 2 && doc.Frontmatter.ContainsKey("category") && doc.Frontmatter.ContainsKey("description")) return false; return doc.Frontmatter.Any(kvp => kvp.Key != "category" && kvp.Key != "description"); } private string FormatContent(string content) { if (string.IsNullOrWhiteSpace(content)) return ""; var lines = content.Replace("\r\n", "\n").Split('\n'); var html = new System.Text.StringBuilder(); var inList = false; foreach (var rawLine in lines) { var line = rawLine.TrimEnd(); if (string.IsNullOrWhiteSpace(line)) { if (inList) { html.AppendLine(""); inList = false; } continue; } if (line.StartsWith("- ")) { var item = line[2..].Trim(); if (item.Length > 0) { if (!inList) { html.AppendLine("
    "); inList = true; } html.Append("
  • "); html.Append(EncodeInline(item)); html.AppendLine("
  • "); } continue; } if (inList) { html.AppendLine("
"); inList = false; } if (line.EndsWith(":") || line.Contains(":\n")) { var trimmed = line.TrimEnd(':'); if (trimmed.Length > 0) { html.Append("

"); html.Append(System.Web.HttpUtility.HtmlEncode(trimmed)); html.AppendLine("

"); } } else { html.Append("

"); html.Append(EncodeInline(line)); html.AppendLine("

"); } } if (inList) html.AppendLine(""); return html.ToString(); } private string EncodeInline(string text) { var result = new System.Text.StringBuilder(); var remaining = text; while (remaining.Length > 0) { var boldIdx = remaining.IndexOf("**", StringComparison.Ordinal); if (boldIdx >= 0) { var endBold = remaining.IndexOf("**", boldIdx + 2, StringComparison.Ordinal); if (endBold >= 0) { result.Append(System.Web.HttpUtility.HtmlEncode(remaining[..boldIdx])); result.Append(""); result.Append(System.Web.HttpUtility.HtmlEncode(remaining[(boldIdx + 2)..endBold])); result.Append(""); remaining = remaining[(endBold + 2)..]; continue; } } result.Append(System.Web.HttpUtility.HtmlEncode(remaining)); break; } return result.ToString(); } }