Passt so
This commit is contained in:
97
internal/article/load.go
Normal file
97
internal/article/load.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// internal/article/load.go
|
||||
package article
|
||||
|
||||
// internal/article/load.go (gekürzt)
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
md "github.com/gomarkdown/markdown"
|
||||
"github.com/gomarkdown/markdown/html"
|
||||
"github.com/gomarkdown/markdown/parser"
|
||||
)
|
||||
|
||||
func LoadDir(root string) ([]Article, error) {
|
||||
var out []Article
|
||||
|
||||
// gültige Extension‑Maske
|
||||
exts := parser.CommonExtensions | parser.AutoHeadingIDs | parser.DefinitionLists
|
||||
mdParser := parser.NewWithExtensions(exts)
|
||||
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
|
||||
}
|
||||
|
||||
ext := filepath.Ext(p)
|
||||
if ext != ".html" && ext != ".md" {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
r := bufio.NewReader(f)
|
||||
headerLine, err := r.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
var meta struct {
|
||||
Title string `json:"title"`
|
||||
Date string `json:"date"`
|
||||
Slug string `json:"slug"`
|
||||
}
|
||||
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("Front‑Matter in %s: %w (%q)", p, err, clean)
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
htmlBody := bodyBytes
|
||||
if ext == ".md" {
|
||||
htmlBody = md.ToHTML(bodyBytes, mdParser, mdRenderer)
|
||||
}
|
||||
|
||||
date, _ := time.Parse("2006-01-02", meta.Date)
|
||||
out = append(out, Article{
|
||||
Title: meta.Title,
|
||||
Slug: meta.Slug,
|
||||
Date: date,
|
||||
Body: template.HTML(htmlBody),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].Date.After(out[j].Date) })
|
||||
return out, err
|
||||
}
|
14
internal/article/model.go
Normal file
14
internal/article/model.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// internal/article/model.go
|
||||
package article
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
Title string
|
||||
Slug string
|
||||
Date time.Time
|
||||
Body template.HTML // already trusted
|
||||
}
|
20
internal/web/templates/article.html
Normal file
20
internal/web/templates/article.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{{ define "article" }}
|
||||
{{ template "base.html" . }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "title" }}{{ .Title }} – B1tsblog{{ end }}
|
||||
|
||||
{{ define "body" }}
|
||||
<article class="article">
|
||||
<header>
|
||||
<h1>{{ .Title }}</h1>
|
||||
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "02.01.2006" }}</time>
|
||||
</header>
|
||||
|
||||
<div class="article-content">
|
||||
{{ .Body }}
|
||||
</div>
|
||||
|
||||
<p><a href="/">← Alle Artikel</a></p>
|
||||
</article>
|
||||
{{ end }}
|
15
internal/web/templates/base.html
Normal file
15
internal/web/templates/base.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html><html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{{ block "title" . }}B1tsblog{{ end }}</title>
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<!-- Monospace‑Font für Code (optional) -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header><h1><a href="/">B1tsblog</a></h1></header>
|
||||
<main>{{ block "body" . }}{{ end }}</main>
|
||||
<footer>© {{ now.Year }}</footer>
|
||||
</body></html>
|
16
internal/web/templates/list.html
Normal file
16
internal/web/templates/list.html
Normal file
@@ -0,0 +1,16 @@
|
||||
{{ define "list" }} {{/* ‑‑ ausführbarer Entry‑Point */}}
|
||||
{{ template "base.html" . }} {{/* ruft das Layout auf */}}
|
||||
{{ end }}
|
||||
|
||||
{{ define "title" }}Alle Artikel{{ end }}
|
||||
|
||||
{{ define "body" }}
|
||||
<ul>
|
||||
{{ range . }}
|
||||
<li>
|
||||
<a href="/post/{{ .Slug }}">{{ .Title }}</a>
|
||||
— {{ .Date.Format "02.01.2006" }}
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
Reference in New Issue
Block a user