// internal/article/load.go package article // internal/article/load.go (gekürzt) import ( "bytes" "encoding/json" "fmt" "html/template" "io/fs" "os" "path/filepath" "sort" "strings" "time" md "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" ) // LoadDir liest alle *.html und *.md unter root/content, // parst Front‑Matter (JSON‑Kommentar in der 1. Zeile) und liefert []Article. func LoadDir(root string) ([]Article, error) { var out []Article seen := make(map[string]bool) // Duplikats‑Check mdRenderer := html.NewRenderer(html.RendererOptions{ Flags: html.CommonFlags | html.HrefTargetBlank, }) err := filepath.WalkDir(root, func(path string, d fs.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } if d.IsDir() { return nil } ext := strings.ToLower(filepath.Ext(path)) if ext != ".md" && ext != ".html" { return nil // uninteressante Datei } 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 front‑matter", 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"` } if err := json.Unmarshal(headerJSON, &meta); err != nil { return fmt.Errorf("%s: %w", path, err) } // Fallback‑Slug 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 body := parts[1] if ext == ".md" { body = md.ToHTML(body, nil, mdRenderer) // frischer Parser pro Call } date, err := time.Parse("2006-01-02", meta.Date) if err != nil { return fmt.Errorf("%s: parse date: %w", path, err) } out = append(out, Article{ Title: meta.Title, Slug: meta.Slug, Date: date, 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, 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, "" 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 front‑matter found") } return []byte(s), nil } // LoadStatic liest alle .md/.html‑Dateien 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) } // Front‑Matter (erste Zeile) herauslösen parts := bytes.SplitN(raw, []byte("\n"), 2) if len(parts) < 2 { return nil, fmt.Errorf("%s: missing front‑matter", 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 }