195 lines
4.4 KiB
Go
195 lines
4.4 KiB
Go
// 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, "<!--")
|
||
|
||
// 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 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
|
||
}
|