137 lines
4.4 KiB
Go
137 lines
4.4 KiB
Go
// Package render ist zuständig für alles rund ums Lesen und Umwandeln von Markdown-Dateien.
|
|
package render
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/yuin/goldmark"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Page repräsentiert eine einzelne Wiki-Seite.
|
|
// Ein Struct in Go ist wie eine Datenklasse: es bündelt zusammengehörige Felder.
|
|
type Page struct {
|
|
Title string // Titel aus dem Frontmatter
|
|
Password string // Passwort aus dem Frontmatter (leer = öffentlich)
|
|
Tags []string // Tags aus dem Frontmatter (optional)
|
|
Hidden bool // Wenn true: Seite erscheint nicht in der Navigation (aber URL bleibt erreichbar)
|
|
Body string // Der fertig gerenderte HTML-Inhalt der Seite
|
|
RawBody string // Der originale Markdown-Text ohne Frontmatter — wird für die Volltextsuche genutzt
|
|
}
|
|
|
|
// frontmatter ist ein internes Struct nur zum Parsen des YAML-Blocks.
|
|
// Wir brauchen es nur kurz, deshalb ist es kleingeschrieben (= nicht exportiert,
|
|
// also nur innerhalb dieses Packages sichtbar).
|
|
type frontmatter struct {
|
|
Title string `yaml:"title"`
|
|
Password string `yaml:"password"`
|
|
Tags []string `yaml:"tags"`
|
|
Hidden bool `yaml:"hidden"`
|
|
}
|
|
|
|
// LoadPage liest eine Markdown-Datei vom Dateisystem, parst das Frontmatter
|
|
// und wandelt den Markdown-Inhalt in HTML um.
|
|
//
|
|
// In Go geben Funktionen oft zwei Werte zurück: das Ergebnis und einen Fehler.
|
|
// Der Aufrufer muss den Fehler prüfen — Go hat keine Exceptions.
|
|
func LoadPage(filePath string) (*Page, error) {
|
|
// os.ReadFile liest die komplette Datei in einen Byte-Slice ([]byte).
|
|
// Ein Byte-Slice ist einfach eine Liste von Bytes — rohe Dateidaten.
|
|
rawContent, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
// Wir geben nil (kein Ergebnis) und den Fehler zurück.
|
|
return nil, err
|
|
}
|
|
|
|
// Wir wandeln den Byte-Slice in einen String um, damit wir damit arbeiten können.
|
|
content := string(rawContent)
|
|
|
|
// Frontmatter und Markdown-Body trennen.
|
|
fm, body, err := splitFrontmatter(content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Den Markdown-Body in HTML umwandeln.
|
|
htmlBody, err := markdownToHTML(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Das fertige Page-Struct befüllen und zurückgeben.
|
|
// Das * vor Page bedeutet: wir geben einen Zeiger zurück (eine Referenz auf
|
|
// das Struct im Speicher), nicht eine Kopie davon.
|
|
return &Page{
|
|
Title: fm.Title,
|
|
Password: fm.Password,
|
|
Tags: fm.Tags,
|
|
Hidden: fm.Hidden,
|
|
Body: htmlBody,
|
|
RawBody: body, // Rohtext für die BM25-Volltextsuche — HTML-Tags verfälschen die Tokenisierung
|
|
}, nil
|
|
}
|
|
|
|
// splitFrontmatter trennt den YAML-Frontmatter-Block vom Markdown-Inhalt.
|
|
//
|
|
// Eine typische Datei sieht so aus:
|
|
//
|
|
// ---
|
|
// title: Meine Seite
|
|
// password: geheim
|
|
// ---
|
|
//
|
|
// # Überschrift
|
|
// Inhalt hier...
|
|
func splitFrontmatter(content string) (frontmatter, string, error) {
|
|
var fm frontmatter
|
|
|
|
// Frontmatter beginnt und endet mit "---".
|
|
// Wir prüfen ob die Datei überhaupt Frontmatter hat.
|
|
if !strings.HasPrefix(content, "---") {
|
|
// Kein Frontmatter — leeres Struct zurückgeben, ganzer Inhalt ist Body.
|
|
return fm, content, nil
|
|
}
|
|
|
|
// Den ersten "---" abschneiden und nach dem zweiten suchen.
|
|
rest := strings.TrimPrefix(content, "---\n")
|
|
closingIndex := strings.Index(rest, "---")
|
|
|
|
if closingIndex == -1 {
|
|
// Öffnendes "---" gefunden, aber kein schließendes — Datei ist kaputt.
|
|
return fm, content, nil
|
|
}
|
|
|
|
// Den YAML-Block und den Body-Teil extrahieren.
|
|
yamlBlock := rest[:closingIndex]
|
|
body := rest[closingIndex+4:] // +4 um "---\n" zu überspringen
|
|
|
|
// Den YAML-Block parsen und in unser frontmatter-Struct füllen.
|
|
// yaml.Unmarshal erwartet einen Byte-Slice, deshalb []byte(yamlBlock).
|
|
err := yaml.Unmarshal([]byte(yamlBlock), &fm)
|
|
if err != nil {
|
|
return fm, body, err
|
|
}
|
|
|
|
return fm, body, nil
|
|
}
|
|
|
|
// markdownToHTML wandelt einen Markdown-String in einen HTML-String um.
|
|
// Dafür nutzen wir die goldmark-Bibliothek.
|
|
func markdownToHTML(markdownContent string) (string, error) {
|
|
// bytes.Buffer ist ein beschreibbarer Puffer im Speicher — wie eine Datei,
|
|
// aber im RAM. Goldmark schreibt das HTML-Ergebnis hinein.
|
|
var htmlBuffer bytes.Buffer
|
|
|
|
// goldmark.Convert macht die eigentliche Umwandlung.
|
|
// []byte(markdownContent) wandelt den String zurück in Bytes,
|
|
// weil die Bibliothek Bytes erwartet.
|
|
err := goldmark.Convert([]byte(markdownContent), &htmlBuffer)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// htmlBuffer.String() holt den fertigen HTML-String aus dem Puffer.
|
|
return htmlBuffer.String(), nil
|
|
}
|