using Markdig; using Markdig.Extensions.Yaml; using Markdig.Syntax; namespace Elternbeirat.Web.Services; /// /// Liest die statischen Inhaltsseiten aus Content/seiten/*.md ein, /// rendert sie beim Start einmalig zu HTML und hält sie im Speicher. /// Als Singleton registriert — Inhalte liegen im Image und ändern sich zur /// Laufzeit nicht (plan.md AE-3). /// public sealed class SeitenService { private readonly IReadOnlyDictionary _seitenNachSlug; /// /// Liest alle Markdown-Seiten aus dem Verzeichnis Content/seiten /// unterhalb des Anwendungsstamms ein und cached sie. /// public SeitenService(IWebHostEnvironment umgebung) { var pipeline = new MarkdownPipelineBuilder() .UseYamlFrontMatter() .Build(); var verzeichnis = Path.Combine(umgebung.ContentRootPath, "Content", "seiten"); var seiten = new Dictionary(StringComparer.OrdinalIgnoreCase); if (Directory.Exists(verzeichnis)) { foreach (var pfad in Directory.EnumerateFiles(verzeichnis, "*.md")) { var seite = Lies(pfad, pipeline); seiten[seite.Slug] = seite; } } _seitenNachSlug = seiten; } /// /// Liefert die Seite zum angegebenen Slug oder null, wenn es keine /// gibt. Groß-/Kleinschreibung wird ignoriert. /// public Seite? Finde(string slug) => _seitenNachSlug.GetValueOrDefault(slug); private static Seite Lies(string pfad, MarkdownPipeline pipeline) { var slug = Path.GetFileNameWithoutExtension(pfad); var quelle = File.ReadAllText(pfad); var dokument = Markdown.Parse(quelle, pipeline); var titel = LiesTitel(dokument, quelle) ?? slug; var html = Markdown.ToHtml(quelle, pipeline); return new Seite { Slug = slug, Titel = titel, InhaltHtml = html }; } /// /// Zieht den titel:-Wert aus dem YAML-Frontmatter. Bewusst simpel /// gehalten (eine Zeile schlüssel: wert) — für strukturiertere /// Metadaten kommt später YamlDotNet. /// private static string? LiesTitel(MarkdownDocument dokument, string quelle) { var block = dokument.Descendants().FirstOrDefault(); if (block is null) { return null; } var frontmatter = quelle.Substring(block.Span.Start, block.Span.Length); foreach (var zeile in frontmatter.Split('\n')) { var getrimmt = zeile.Trim(); if (getrimmt.StartsWith("titel:", StringComparison.OrdinalIgnoreCase)) { return getrimmt["titel:".Length..].Trim().Trim('"'); } } return null; } }