Switch code, scripts and docs to English; drop plan.md references

This commit is contained in:
tleininger committed 2026-09-21 15:25:01 +02:00
1 parent 76f1ad7825
commit c4c7ca2ee4
15 files changed
+532 -540

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
namespace Elternbeirat.Web.Services;
/// <summary>
/// A static content page, read from a Markdown file in <c>Content/pages/</c>.
/// The file name (without extension) is the slug.
/// </summary>
public sealed class Page
{
/// <summary>
/// URL identifier of the page, taken from the file name without umlauts
/// (e.g. <c>vorstandsteam</c>). It appears in the route <c>/{slug}</c>.
/// </summary>
public required string Slug { get; init; }
/// <summary>
/// Display title from the YAML front matter (<c>titel:</c>). Falls back to
/// the slug when no title is set.
/// </summary>
public required string Title { get; init; }
/// <summary>
/// The page body rendered from Markdown to HTML (front matter excluded).
/// </summary>
public required string ContentHtml { get; init; }
}
+86
View File
@@ -0,0 +1,86 @@
using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
namespace Elternbeirat.Web.Services;
/// <summary>
/// Reads the static content pages from <c>Content/pages/*.md</c>, renders them
/// to HTML once at startup and keeps them in memory. Registered as a singleton
/// because the content lives in the image and does not change at runtime.
/// </summary>
public sealed class PageService
{
private readonly IReadOnlyDictionary<string, Page> _pagesBySlug;
/// <summary>
/// Reads every Markdown page from the <c>Content/pages</c> directory below
/// the application root and caches it.
/// </summary>
public PageService(IWebHostEnvironment environment)
{
var pipeline = new MarkdownPipelineBuilder()
.UseYamlFrontMatter()
.Build();
var directory = Path.Combine(environment.ContentRootPath, "Content", "pages");
var pages = new Dictionary<string, Page>(StringComparer.OrdinalIgnoreCase);
if (Directory.Exists(directory))
{
foreach (var path in Directory.EnumerateFiles(directory, "*.md"))
{
var page = Read(path, pipeline);
pages[page.Slug] = page;
}
}
_pagesBySlug = pages;
}
/// <summary>
/// Returns the page for the given slug, or <c>null</c> if there is none.
/// The lookup is case-insensitive.
/// </summary>
public Page? Find(string slug) =>
_pagesBySlug.GetValueOrDefault(slug);
private static Page Read(string path, MarkdownPipeline pipeline)
{
var slug = Path.GetFileNameWithoutExtension(path);
var source = File.ReadAllText(path);
var document = Markdown.Parse(source, pipeline);
var title = ReadTitle(document, source) ?? slug;
var html = Markdown.ToHtml(source, pipeline);
return new Page { Slug = slug, Title = title, ContentHtml = html };
}
/// <summary>
/// Pulls the <c>titel:</c> value out of the YAML front matter. Kept
/// deliberately simple (a single <c>key: value</c> line) — YamlDotNet will
/// take over once we need structured metadata. The key stays German because
/// the Markdown files are edited by German-speaking editors.
/// </summary>
private static string? ReadTitle(MarkdownDocument document, string source)
{
var block = document.Descendants<YamlFrontMatterBlock>().FirstOrDefault();
if (block is null)
{
return null;
}
var frontMatter = source.Substring(block.Span.Start, block.Span.Length);
foreach (var line in frontMatter.Split('\n'))
{
var trimmed = line.Trim();
if (trimmed.StartsWith("titel:", StringComparison.OrdinalIgnoreCase))
{
return trimmed["titel:".Length..].Trim().Trim('"');
}
}
return null;
}
}
-25
View File
@@ -1,25 +0,0 @@
namespace Elternbeirat.Web.Services;
/// <summary>
/// Eine statische Inhaltsseite, eingelesen aus einer Markdown-Datei in
/// <c>Content/seiten/</c>. Der Dateiname (ohne Endung) ist der Slug.
/// </summary>
public sealed class Seite
{
/// <summary>
/// URL-Kennung der Seite, abgeleitet aus dem Dateinamen ohne Umlaute
/// (z. B. <c>vorstandsteam</c>). Erscheint in der Route <c>/{slug}</c>.
/// </summary>
public required string Slug { get; init; }
/// <summary>
/// Anzeigetitel aus dem YAML-Frontmatter (<c>titel:</c>). Fällt auf den
/// Slug zurück, wenn kein Titel gesetzt ist.
/// </summary>
public required string Titel { get; init; }
/// <summary>
/// Der aus Markdown gerenderte HTML-Rumpf der Seite (ohne Frontmatter).
/// </summary>
public required string InhaltHtml { get; init; }
}
@@ -1,86 +0,0 @@
using Markdig;
using Markdig.Extensions.Yaml;
using Markdig.Syntax;
namespace Elternbeirat.Web.Services;
/// <summary>
/// Liest die statischen Inhaltsseiten aus <c>Content/seiten/*.md</c> 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).
/// </summary>
public sealed class SeitenService
{
private readonly IReadOnlyDictionary<string, Seite> _seitenNachSlug;
/// <summary>
/// Liest alle Markdown-Seiten aus dem Verzeichnis <c>Content/seiten</c>
/// unterhalb des Anwendungsstamms ein und cached sie.
/// </summary>
public SeitenService(IWebHostEnvironment umgebung)
{
var pipeline = new MarkdownPipelineBuilder()
.UseYamlFrontMatter()
.Build();
var verzeichnis = Path.Combine(umgebung.ContentRootPath, "Content", "seiten");
var seiten = new Dictionary<string, Seite>(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;
}
/// <summary>
/// Liefert die Seite zum angegebenen Slug oder <c>null</c>, wenn es keine
/// gibt. Groß-/Kleinschreibung wird ignoriert.
/// </summary>
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 };
}
/// <summary>
/// Zieht den <c>titel:</c>-Wert aus dem YAML-Frontmatter. Bewusst simpel
/// gehalten (eine Zeile <c>schlüssel: wert</c>) — für strukturiertere
/// Metadaten kommt später YamlDotNet.
/// </summary>
private static string? LiesTitel(MarkdownDocument dokument, string quelle)
{
var block = dokument.Descendants<YamlFrontMatterBlock>().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;
}
}