finale Anpassung

This commit is contained in:
2025-05-04 17:10:38 +02:00
parent eb2d05f082
commit a1471fc310
9 changed files with 386 additions and 193 deletions

View File

@@ -4,11 +4,10 @@ package article
// internal/article/load.go (gekürzt)
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"io/fs"
"os"
"path/filepath"
@@ -18,85 +17,178 @@ import (
md "github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
// LoadDir liest alle *.html und *.md unter root/content,
// parst FrontMatter (JSONKommentar in der 1. Zeile) und liefert []Article.
func LoadDir(root string) ([]Article, error) {
var out []Article
seen := make(map[string]bool) // DuplikatsCheck
// gültige ExtensionMaske
exts := parser.CommonExtensions | parser.AutoHeadingIDs | parser.DefinitionLists
mdRenderer := html.NewRenderer(html.RendererOptions{
Flags: html.CommonFlags | html.HrefTargetBlank,
})
err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() {
return err
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
ext := filepath.Ext(p)
if ext != ".html" && ext != ".md" {
if d.IsDir() {
return nil
}
f, err := os.Open(p)
if err != nil {
return err
ext := strings.ToLower(filepath.Ext(path))
if ext != ".md" && ext != ".html" {
return nil // uninteressante Datei
}
defer f.Close()
r := bufio.NewReader(f)
headerLine, err := r.ReadBytes('\n')
if err != nil && err != io.EOF {
return err
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read %s: %w", path, err)
}
parts := bytes.SplitN(raw, []byte("\n"), 2)
if len(parts) < 2 {
return fmt.Errorf("%s: missing frontmatter", path)
}
headerJSON, err := extractJSONHeader(parts[0])
if err != nil {
return fmt.Errorf("%s: %w", path, err)
}
var meta struct {
Title string `json:"title"`
Date string `json:"date"`
Slug string `json:"slug"`
Cover string `json:"cover"` // NEW
Cover string `json:"cover"`
}
clean := strings.TrimSpace(string(headerLine))
clean = strings.TrimPrefix(clean, "<!--")
clean = strings.TrimSpace(clean)
// alles nach dem ersten "-->" abschneiden (egal ob Leerzeichen davor)
if idx := strings.Index(clean, "-->"); idx != -1 {
clean = clean[:idx]
}
clean = strings.TrimSpace(clean)
if err := json.Unmarshal([]byte(clean), &meta); err != nil {
return fmt.Errorf("FrontMatter in %s: %w (%q)", p, err, clean)
if err := json.Unmarshal(headerJSON, &meta); err != nil {
return fmt.Errorf("%s: %w", path, err)
}
bodyBytes, err := io.ReadAll(r)
if err != nil {
return err
// FallbackSlug aus Dateinamen
if meta.Slug == "" {
meta.Slug = strings.TrimSuffix(filepath.Base(path), ext)
}
if seen[meta.Slug] {
return fmt.Errorf("%s: duplicate slug %q", path, meta.Slug)
}
seen[meta.Slug] = true
htmlBody := bodyBytes
body := parts[1]
if ext == ".md" {
mdParser := parser.NewWithExtensions(exts)
htmlBody = md.ToHTML(bodyBytes, mdParser, mdRenderer)
body = md.ToHTML(body, nil, mdRenderer) // frischer Parser pro Call
}
date, err := time.Parse("2006-01-02", meta.Date)
if err != nil {
fmt.Println("Time", err, date)
return fmt.Errorf("%s: parse date: %w", path, err)
}
out = append(out, Article{
Title: meta.Title,
Slug: meta.Slug,
Date: date,
Cover: meta.Cover, // NEW
Body: template.HTML(htmlBody),
Cover: meta.Cover,
Body: template.HTML(body),
})
return nil
})
if err != nil {
return nil, err
}
sort.Slice(out, func(i, j int) bool { return out[i].Date.After(out[j].Date) })
return out, err
return out, nil
}
// ---------- NEU ----------
type StaticPage struct {
Title string
Slug string
Body template.HTML
}
// helper.go einmal zentral verwenden
func extractJSONHeader(line []byte) ([]byte, error) {
s := strings.TrimSpace(string(line))
s = strings.TrimPrefix(s, "<!--")
// alles bis zum ersten "-->" abschneiden
if idx := strings.Index(s, "-->"); idx != -1 {
s = s[:idx]
}
s = strings.TrimSpace(s)
// optional: falls doch kein JSON, besserer Fehler
if !strings.HasPrefix(s, "{") {
return nil, fmt.Errorf("no JSON frontmatter found")
}
return []byte(s), nil
}
// LoadStatic liest alle .md/.htmlDateien unter dir und liefert sie als Map[slug]StaticPage.
func LoadStatic(dir string) (map[string]StaticPage, error) {
pages := make(map[string]StaticPage)
entries, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
for _, e := range entries {
if e.IsDir() {
continue
}
ext := filepath.Ext(e.Name())
if ext != ".md" && ext != ".html" {
continue // unbekanntes Format
}
path := filepath.Join(dir, e.Name())
raw, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read %s: %w", path, err)
}
// FrontMatter (erste Zeile) herauslösen
parts := bytes.SplitN(raw, []byte("\n"), 2)
if len(parts) < 2 {
return nil, fmt.Errorf("%s: missing frontmatter", path)
}
headerJSON, err := extractJSONHeader(parts[0])
if err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
var meta struct {
Title string `json:"title"`
Slug string `json:"slug"`
}
if err := json.Unmarshal(headerJSON, &meta); err != nil {
return nil, fmt.Errorf("%s: %w", path, err)
}
// Fallback: Slug aus Dateinamen ableiten, falls im Header leer
if meta.Slug == "" {
meta.Slug = strings.TrimSuffix(e.Name(), ext)
}
if _, dup := pages[meta.Slug]; dup {
return nil, fmt.Errorf("%s: duplicate slug %q", path, meta.Slug)
}
body := parts[1]
if ext == ".md" {
body = md.ToHTML(body, nil, nil) // eigener Parser je Aufruf
}
pages[meta.Slug] = StaticPage{
Title: meta.Title,
Slug: meta.Slug,
Body: template.HTML(body),
}
}
return pages, nil
}