aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnhgelus Morhtuuzh <william@herges.fr>2025-10-27 16:32:58 +0000
committerAnhgelus Morhtuuzh <william@herges.fr>2025-10-27 16:32:58 +0000
commitdb0b5c34432b4c0135af8c5c885fd6ad348c3691 (patch)
tree8f38b973a8dd3fc22bbd28498004940451621b41
parent7bd309f3ca44930c5207b94acc2d425b24d4b369 (diff)
parent1e2ad3a8f8cd2c12786b92210616325a33d1b209 (diff)
Merge pull request '[Feat] Custom sections' (#1) from feat/custom-sections into main
Reviewed-on: https://git.anhgelus.world/anhgelus/small-web/pulls/1
-rw-r--r--backend/config.go11
-rw-r--r--backend/data.go22
-rw-r--r--backend/home.go65
-rw-r--r--backend/logs.go201
-rw-r--r--backend/router.go2
-rw-r--r--backend/section.go277
-rw-r--r--backend/templates/base.html2
-rw-r--r--backend/templates/components.html15
-rw-r--r--backend/templates/data.html (renamed from backend/templates/log.html)2
-rw-r--r--backend/templates/home.html12
-rw-r--r--backend/templates/home_log.html3
-rw-r--r--backend/templates/home_section.html10
-rw-r--r--backend/templates/rss.xml16
-rw-r--r--frontend/scss/home.scss6
-rw-r--r--main.go12
15 files changed, 401 insertions, 255 deletions
diff --git a/backend/config.go b/backend/config.go
index b59c00b..cd24592 100644
--- a/backend/config.go
+++ b/backend/config.go
@@ -29,8 +29,10 @@ type Config struct {
Description string `toml:"description"`
DefaultImage string `toml:"default_image"`
Quotes []string `toml:"quotes"`
+ Language string `toml:"language"`
+
+ Sections []Section `toml:"section"`
- LogFolder string `toml:"log_folder"`
RootFolder string `toml:"root_folder"`
PublicFolder string `toml:"public_folder"`
@@ -56,7 +58,12 @@ func (c *Config) DefaultValues() {
Header: "logo.jpg",
Favicon: "favicon.jpg",
}
- c.LogFolder = "data/logs"
+ c.Sections = []Section{{
+ Name: "logs",
+ Description: "Aut maxime voluptatibus ut dicta voluptates et ut alias. Sunt et incidunt similique et doloremque nostrum fugit autem. Ut omnis quo nisi. Accusantium voluptas fugit autem maiores numquam doloribus.",
+ Folder: "data/logs",
+ URI: "logs",
+ }}
c.RootFolder = "data"
c.PublicFolder = "public"
c.Quotes = []string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do."}
diff --git a/backend/data.go b/backend/data.go
index 6ca6020..809e6e6 100644
--- a/backend/data.go
+++ b/backend/data.go
@@ -36,6 +36,7 @@ type data struct {
Links []Link
Logo *Logo
Quote string
+ Language string
}
func (d *data) SetData(data *data) {
@@ -74,6 +75,9 @@ func (d *data) merge(cfg *Config, r *http.Request) {
}
d.URL = r.URL.Path
}
+ if d.Language == "" {
+ d.Language = cfg.Language
+ }
}
func (d *data) handleGeneric(w http.ResponseWriter, r *http.Request, name string, custom dataUsable) {
@@ -92,6 +96,7 @@ func (d *data) handleGeneric(w http.ResponseWriter, r *http.Request, name string
},
"next": func(i int) int { return i + 1 },
"before": func(i int) int { return i - 1 },
+ "first": templateFirst,
}).ParseFS(templates, "templates/components.html", fmt.Sprintf("templates/%s.html", name), "templates/base.html")
if err != nil {
panic(err)
@@ -116,7 +121,15 @@ func (d *data) handleGeneric(w http.ResponseWriter, r *http.Request, name string
func (d *data) handleRSS(w http.ResponseWriter, r *http.Request, custom dataUsable) {
cfg := r.Context().Value(configKey).(*Config)
d.merge(cfg, r)
- t, err := txt.ParseFS(templates, "templates/rss.xml")
+ t, err := txt.New("").Funcs(txt.FuncMap{
+ "first": templateFirst,
+ "uri": func(s string) string {
+ if s == "" {
+ return ""
+ }
+ return s + "/"
+ },
+ }).ParseFS(templates, "templates/rss.xml")
if err != nil {
panic(err)
}
@@ -197,3 +210,10 @@ func getAsset(ctx context.Context, path string) *assetData {
assets[path] = asset
return asset
}
+
+func templateFirst(a []*Section) *Section {
+ if len(a) == 0 {
+ return nil
+ }
+ return a[0]
+}
diff --git a/backend/home.go b/backend/home.go
index b945313..8f274e8 100644
--- a/backend/home.go
+++ b/backend/home.go
@@ -2,25 +2,22 @@ package backend
import (
"html/template"
- "log/slog"
+ "iter"
"net/http"
"os"
"path/filepath"
- "strconv"
+ "slices"
"github.com/go-chi/chi/v5"
)
var (
- sortedLogs []*logData
rootContent = map[string]*rootData{}
)
type homeData struct {
*data
- Logs []*logData
- PagesNumber int
- CurrentPage int
+ Sections []*Section
}
func (h *homeData) SetData(d *data) {
@@ -29,7 +26,8 @@ func (h *homeData) SetData(d *data) {
func HandleHome(r *chi.Mux) {
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
- d := handleGenericLogsDisplay(w, r, 3)
+ cfg := r.Context().Value(configKey).(*Config)
+ d := handleGenericSectionDisplay(w, r, cfg.Sections, 3)
if d == nil {
return
}
@@ -62,6 +60,8 @@ func HandleRoot(r *chi.Mux, cfg *Config) {
if err != nil && !os.IsExist(err) {
panic(err)
}
+ r.Get("/rss", handleGenericRSS)
+ r.Get("/rss/", handleGenericRSS)
r.Get("/{name:[a-zA-Z-]+}", func(w http.ResponseWriter, r *http.Request) {
handleGenericRoot(w, r, chi.URLParam(r, "name"))
})
@@ -93,29 +93,42 @@ func handleGenericRoot(w http.ResponseWriter, r *http.Request, name string) {
d.handleGeneric(w, r, "simple", d)
}
-func handleGenericLogsDisplay(w http.ResponseWriter, r *http.Request, maxLogsPerPage int) *homeData {
- rawPage := r.URL.Query().Get("page")
- page := 1
- if rawPage != "" {
- var err error
- page, err = strconv.Atoi(rawPage)
- if err != nil || page < 1 {
- slog.Warn("invalid page number", "rawPage", rawPage)
- w.WriteHeader(http.StatusBadRequest)
- return nil
+func handleGenericRSS(w http.ResponseWriter, r *http.Request) {
+ cfg := r.Context().Value(configKey).(*Config)
+ var data iter.Seq[*sectionData]
+ for _, sec := range cfg.Sections {
+ if len(sec.Data) == 0 {
+ sec.sort()
+ }
+ var sl []*sectionData
+ for _, d := range sec.Data[:min(3, len(sec.Data))] {
+ dd := *d
+ dd.Slug = sec.URI + "/" + dd.Slug
+ sl = append(sl, &dd)
+ }
+ if data == nil {
+ data = slices.Values(sl)
+ } else {
+ data = slices.Values(slices.AppendSeq(sl, data))
}
}
+ var s Section
+ s.Data = sort(data)
+ s.Name = cfg.Name
+ s.Description = cfg.Description
+ s.URI = ""
+ s.handleRSS(w, r)
+}
+
+func handleGenericSectionDisplay(_ http.ResponseWriter, _ *http.Request, sections []Section, maxLogsPerPage int) *homeData {
d := new(homeData)
d.data = new(data)
- if sortedLogs == nil {
- sortLogs()
- }
- d.CurrentPage = page
- d.PagesNumber = max(1, (len(sortedLogs)-1)/maxLogsPerPage+1)
- if d.PagesNumber < page {
- notFound(w, r)
- return nil
+ for _, sec := range sections {
+ if len(sec.Data) == 0 {
+ sec.sort()
+ }
+ sec.Data = sec.Data[:min(maxLogsPerPage, len(sec.Data))]
+ d.Sections = append(d.Sections, &sec)
}
- d.Logs = sortedLogs[(page-1)*maxLogsPerPage : min(page*maxLogsPerPage, len(sortedLogs))]
return d
}
diff --git a/backend/logs.go b/backend/logs.go
deleted file mode 100644
index 03d4f1f..0000000
--- a/backend/logs.go
+++ /dev/null
@@ -1,201 +0,0 @@
-package backend
-
-import (
- "fmt"
- "html/template"
- "log/slog"
- "maps"
- "net/http"
- "os"
- "path/filepath"
- "slices"
- "strings"
- "sync"
- "time"
-
- "github.com/go-chi/chi/v5"
-)
-
-var (
- logs = map[string]*logData{}
-)
-
-type logData struct {
- *data
- EntryInfo
- LogTitle string
- Content template.HTML
- Slug string
-}
-
-func (d *logData) SetData(dt *data) {
- d.data = dt
-}
-
-func (d *logData) PubDate() string {
- return d.PubLocalDate.String()
-}
-
-func (d *logData) PubDateRSS() string {
- return d.PubLocalDate.AsTime(time.Local).Format(time.RFC1123Z) // because RFC822 in go isn't RFC822???
-}
-
-func (d *logData) Title() string {
- return d.data.Title()
-}
-
-type image struct {
- Src string `toml:"src"`
- Alt string `toml:"alt"`
- Legend string `toml:"legend"`
-}
-
-func LoadLogs(cfg *Config) bool {
- dir, err := os.ReadDir(cfg.LogFolder)
- if err != nil {
- if !os.IsNotExist(err) {
- slog.Error("reading log directory", "error", err)
- return false
- }
- slog.Info("log directory does not exist, creating...")
- err = os.MkdirAll(cfg.LogFolder, 0774)
- if err != nil {
- slog.Error("creating log directory", "error", err)
- }
- return false
- }
- slog.Info("checking log directory...", "path", cfg.LogFolder)
- err = readLogDir(cfg.LogFolder, dir)
- if err != nil {
- slog.Error("reading log directory", "error", err, "path", cfg.LogFolder)
- return false
- }
- slog.Info("all logs loaded")
- return true
-}
-
-func readLogDir(path string, dir []os.DirEntry) error {
- var wg sync.WaitGroup
- var mu sync.Mutex
- for _, d := range dir {
- p := filepath.Join(path, d.Name())
- if d.IsDir() {
- dd, err := os.ReadDir(p)
- if err != nil {
- return err
- }
- if err = readLogDir(p, dd); err != nil {
- return err
- }
- } else {
- if !strings.HasSuffix(d.Name(), ".md") {
- return fmt.Errorf("file %s is not a markdown file", d.Name())
- }
- slug := strings.TrimSuffix(p, ".md")
- _, ok := logs[slug]
- if ok {
- return fmt.Errorf("log already exists: %s", d.Name())
- }
- dd := new(logData)
- dd.data = new(data)
-
- wg.Add(1)
- go func(p string, d os.DirEntry) {
- defer wg.Done()
- ok = parseLog(dd, &mu, slug, strings.TrimSuffix(d.Name(), ".md"))
- if ok {
- slog.Debug("log parsed", "path", p)
- } else {
- slog.Debug("log skipped", "path", p)
- }
- }(p, d)
- }
- }
- wg.Wait()
- sortLogs()
- return nil
-}
-
-func HandleLogs(r *chi.Mux) {
- r.Get("/logs", handleLogList)
- r.Route("/logs", func(r chi.Router) {
- r.Get("/", handleLogList)
-
- r.Get("/rss", handleLogRSS)
- r.Get("/rss/", handleLogRSS)
-
- r.Get("/{slug:[a-zA-Z0-9-]+}", handleLog)
- r.Get("/{slug:[a-zA-Z0-9-]+}/", handleLog)
- })
-}
-
-func handleLogList(w http.ResponseWriter, r *http.Request) {
- d := handleGenericLogsDisplay(w, r, 5)
- if d == nil {
- return
- }
- d.title = "logs"
- d.handleGeneric(w, r, "home_log", d)
-}
-
-func handleLogRSS(w http.ResponseWriter, r *http.Request) {
- d := handleGenericLogsDisplay(w, r, 5)
- if d == nil {
- return
- }
- d.title = "logs"
- d.handleRSS(w, r, d)
-}
-
-func handleLog(w http.ResponseWriter, r *http.Request) {
- cfg := r.Context().Value(configKey).(*Config)
- slug := chi.URLParam(r, "slug")
- path := filepath.Join(cfg.LogFolder, slug)
- d, ok := logs[path]
- if !ok {
- d = new(logData)
- d.data = new(data)
- if ok = parseLog(d, new(sync.Mutex), path, slug); !ok {
- notFound(w, r)
- return
- }
- }
- d.handleGeneric(w, r, "log", d)
-}
-
-func parseLog(d *logData, mu *sync.Mutex, path, slug string) bool {
- d.Article = true
- d.title = slug
- d.Slug = slug
- b, err := os.ReadFile(path + ".md")
- if err != nil {
- if os.IsNotExist(err) {
- return false
- }
- panic(err)
- }
- var ok bool
- d.Content, ok = parse(b, &d.EntryInfo, d.data)
- if !ok {
- return false
- }
- d.LogTitle = d.EntryInfo.Title
- mu.Lock()
- logs[path] = d
- mu.Unlock()
- return true
-}
-
-func sortLogs() {
- sortedLogs = slices.SortedFunc(maps.Values(logs), func(l *logData, l2 *logData) int {
- lt := l.PubLocalDate.AsTime(time.UTC)
- l2t := l2.PubLocalDate.AsTime(time.UTC)
- // we want it reversed
- if lt.Before(l2t) {
- return 1
- } else if lt.After(l2t) {
- return -1
- }
- return 0
- })
-}
diff --git a/backend/router.go b/backend/router.go
index 59a02e3..fd72711 100644
--- a/backend/router.go
+++ b/backend/router.go
@@ -17,7 +17,7 @@ import (
)
const (
- Version = "0.3.0"
+ Version = "0.4.0"
configKey = "config"
isUpdateKey = "is_update"
assetsFSKey = "assets_fs"
diff --git a/backend/section.go b/backend/section.go
new file mode 100644
index 0000000..a22d286
--- /dev/null
+++ b/backend/section.go
@@ -0,0 +1,277 @@
+package backend
+
+import (
+ "fmt"
+ "html/template"
+ "iter"
+ "log/slog"
+ "maps"
+ "net/http"
+ "os"
+ "path/filepath"
+ "slices"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/go-chi/chi/v5"
+)
+
+var (
+ sections = map[string]map[string]*sectionData{}
+)
+
+type Section struct {
+ Name string `toml:"name"`
+ Folder string `toml:"folder"`
+ Description string `toml:"description"`
+ URI string `toml:"uri"`
+ Data []*sectionData `toml:"-"`
+ Paginate bool `toml:"-"`
+ PagesNumber int `toml:"-"`
+ CurrentPage int `toml:"-"`
+}
+
+type sectionData struct {
+ *data
+ EntryInfo
+ DataTitle string
+ Content template.HTML
+ Slug string
+}
+
+func (d *sectionData) SetData(dt *data) {
+ d.data = dt
+}
+
+func (d *sectionData) PubDate() string {
+ return d.PubLocalDate.String()
+}
+
+func (d *sectionData) PubDateRSS() string {
+ return d.PubLocalDate.AsTime(time.Local).Format(time.RFC1123Z) // because RFC822 in go isn't RFC822???
+}
+
+func (d *sectionData) Title() string {
+ return d.data.Title()
+}
+
+type image struct {
+ Src string `toml:"src"`
+ Alt string `toml:"alt"`
+ Legend string `toml:"legend"`
+}
+
+func (s *Section) Load(_ *Config) bool {
+ dir, err := os.ReadDir(s.Folder)
+ logger := slog.With("folder", s.Folder)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ logger.Error("reading directory", "error", err)
+ return false
+ }
+ logger.Info("log directory does not exist, creating...")
+ err = os.MkdirAll(s.Folder, 0774)
+ if err != nil {
+ slog.Error("creating directory", "error", err)
+ }
+ return false
+ }
+ logger.Info("checking directory...")
+ err = s.readDir(s.Folder, dir)
+ if err != nil {
+ slog.Error("reading directory", "error", err)
+ return false
+ }
+ logger.Info("all data loaded")
+ return true
+}
+
+func (s *Section) readDir(path string, dir []os.DirEntry) error {
+ var wg sync.WaitGroup
+ var mu sync.Mutex
+ for _, d := range dir {
+ p := filepath.Join(path, d.Name())
+ if d.IsDir() {
+ dd, err := os.ReadDir(p)
+ if err != nil {
+ return err
+ }
+ if err = s.readDir(p, dd); err != nil {
+ return err
+ }
+ } else {
+ if !strings.HasSuffix(d.Name(), ".md") {
+ return fmt.Errorf("file %s is not a markdown file", d.Name())
+ }
+ slug := strings.TrimSuffix(p, ".md")
+ sec, ok := sections[s.Name]
+ if !ok {
+ sec = make(map[string]*sectionData, 2)
+ sections[s.Name] = sec
+ }
+ _, ok = sec[slug]
+ if ok {
+ return fmt.Errorf("data already exists: %s", d.Name())
+ }
+ dd := new(sectionData)
+ dd.data = new(data)
+
+ wg.Add(1)
+ go func(p string, d os.DirEntry) {
+ defer wg.Done()
+ ok = s.parse(dd, &mu, slug, strings.TrimSuffix(d.Name(), ".md"))
+ if ok {
+ slog.Debug("data parsed", "path", p)
+ } else {
+ slog.Debug("data skipped", "path", p)
+ }
+ }(p, d)
+ }
+ }
+ wg.Wait()
+ s.sort()
+ return nil
+}
+
+func (s *Section) Handle(r *chi.Mux) {
+ base := "/" + s.URI
+ r.Get(base, s.handleList)
+ r.Route(base, func(r chi.Router) {
+ r.Get("/", s.handleList)
+
+ r.Get("/rss", s.handleRSS)
+ r.Get("/rss/", s.handleRSS)
+
+ r.Get("/{slug:[a-zA-Z0-9-]+}", s.handleOne)
+ r.Get("/{slug:[a-zA-Z0-9-]+}/", s.handleOne)
+ })
+}
+
+func (s *Section) handleList(w http.ResponseWriter, r *http.Request) {
+ p := s.handlePagination(w, r, 5)
+ if p == nil {
+ return
+ }
+ d := new(homeData)
+ d.data = new(data)
+ d.title = s.Name
+ sec := *s
+ sec.Data = sec.Data[p.Start:p.End]
+ sec.Paginate = true
+ sec.CurrentPage = p.Current
+ sec.PagesNumber = p.Max
+ d.Sections = append(d.Sections, &sec)
+ d.PageDescription = sec.Description
+ d.handleGeneric(w, r, "home_section", d)
+}
+
+func (s *Section) handleRSS(w http.ResponseWriter, r *http.Request) {
+ d := handleGenericSectionDisplay(w, r, []Section{*s}, 5)
+ if d == nil {
+ return
+ }
+ d.title = s.Name
+ d.PageDescription = s.Description
+ d.handleRSS(w, r, d)
+}
+
+func (s *Section) handleOne(w http.ResponseWriter, r *http.Request) {
+ slug := chi.URLParam(r, "slug")
+ path := filepath.Join(s.Folder, slug)
+ sec, ok := sections[s.Name]
+ var d *sectionData
+ if ok {
+ d, ok = sec[path]
+ }
+ if !ok {
+ d = new(sectionData)
+ d.data = new(data)
+ if ok = s.parse(d, new(sync.Mutex), path, slug); !ok {
+ notFound(w, r)
+ return
+ }
+ }
+ d.handleGeneric(w, r, "data", d)
+}
+
+func (s *Section) parse(d *sectionData, mu *sync.Mutex, path, slug string) bool {
+ d.Article = true
+ d.DataTitle = slug
+ d.Slug = slug
+ b, err := os.ReadFile(path + ".md")
+ if err != nil {
+ if os.IsNotExist(err) {
+ return false
+ }
+ panic(err)
+ }
+ var ok bool
+ d.Content, ok = parse(b, &d.EntryInfo, d.data)
+ if !ok {
+ return false
+ }
+ d.DataTitle = d.EntryInfo.Title
+ mu.Lock()
+ sec, ok := sections[s.Name]
+ if !ok {
+ sec = make(map[string]*sectionData, 2)
+ sections[s.Name] = sec
+ }
+ sec[path] = d
+ mu.Unlock()
+ return true
+}
+
+func (s *Section) sort() {
+ s.Data = sort(maps.Values(sections[s.Name]))
+}
+
+func sort(values iter.Seq[*sectionData]) []*sectionData {
+ return slices.SortedFunc(values, func(l *sectionData, l2 *sectionData) int {
+ lt := l.PubLocalDate.AsTime(time.UTC)
+ l2t := l2.PubLocalDate.AsTime(time.UTC)
+ // we want it reversed
+ if lt.Before(l2t) {
+ return 1
+ } else if lt.After(l2t) {
+ return -1
+ }
+ return 0
+ })
+}
+
+type pagination struct {
+ Current int
+ Max int
+ Start int
+ End int
+}
+
+func (s *Section) handlePagination(w http.ResponseWriter, r *http.Request, maxLogsPerPage int) *pagination {
+ rawPage := r.URL.Query().Get("page")
+ page := 1
+ if rawPage != "" {
+ var err error
+ page, err = strconv.Atoi(rawPage)
+ if err != nil || page < 1 {
+ slog.Warn("invalid page number", "rawPage", rawPage)
+ w.WriteHeader(http.StatusBadRequest)
+ return nil
+ }
+ }
+ if len(s.Data) == 0 {
+ s.sort()
+ }
+ p := new(pagination)
+ p.Current = page
+ p.Max = max(1, (len(s.Data)-1)/maxLogsPerPage+1)
+ if p.Max < page {
+ notFound(w, r)
+ return nil
+ }
+ p.Start = (page - 1) * maxLogsPerPage
+ p.End = min(page*maxLogsPerPage, len(s.Data))
+ return p
+}
diff --git a/backend/templates/base.html b/backend/templates/base.html
index 0a54096..436a1c2 100644
--- a/backend/templates/base.html
+++ b/backend/templates/base.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="fr" prefix="og: https://ogp.me/ns/article#">
+<html lang="{{ .Language }}" prefix="og: https://ogp.me/ns/article#">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
diff --git a/backend/templates/components.html b/backend/templates/components.html
index da133a6..5365445 100644
--- a/backend/templates/components.html
+++ b/backend/templates/components.html
@@ -1,16 +1,17 @@
-{{define "logs_display"}}
+{{define "section_display"}}
<article>
- {{ range .Logs }}
+ {{ $uri := .URI }} {{ range .Data }}
<article>
- <h2><a href="/logs/{{ .Slug }}">{{ .LogTitle }}</a></h2>
+ <h3><a href="/{{ $uri }}/{{ .Slug }}">{{ .DataTitle }}</a></h3>
<figure>
- <a href="/logs/{{ .Slug }}"><img src="{{ static .Img.Src }}" alt="{{ .Img.Alt }}" /></a>
+ <a href="/{{ $uri }}/{{ .Slug }}"><img src="{{ static .Img.Src }}" alt="{{ .Img.Alt }}" /></a>
<figcaption>{{ .Img.Legend }}</figcaption>
</figure>
<p>{{ .Description }}</p>
</article>
- {{ end }} {{ if ne .PagesNumber 1 }}
+ {{ end }}
<div class="pagination">
+ {{ if .Paginate }}
<nav>
{{ if ne .CurrentPage 1 }}<a href="?page={{ before .CurrentPage }}">Précédent</a>{{else}}
<p></p>
@@ -20,7 +21,9 @@
<p></p>
{{end}}
</nav>
+ {{ else }}
+ <a href="/{{ $uri }}/">Voir plus</a>
+ {{ end }}
</div>
- {{ end }}
</article>
{{end}}
diff --git a/backend/templates/log.html b/backend/templates/data.html
index 75e0a42..a384459 100644
--- a/backend/templates/log.html
+++ b/backend/templates/data.html
@@ -1,6 +1,6 @@
{{define "body"}}
<article id="content">
- <h1>{{ .LogTitle }}</h1>
+ <h1>{{ .DataTitle }}</h1>
<p>{{ .Description }}</p>
<figure>
<img src="{{ static .Img.Src }}" alt="{{ .Img.Alt }}" class="large" />
diff --git a/backend/templates/home.html b/backend/templates/home.html
index 7a76e48..d574525 100644
--- a/backend/templates/home.html
+++ b/backend/templates/home.html
@@ -1,9 +1,17 @@
{{define "body"}}
<main id="content">
<div class="introduction">
- <h1>logs</h1>
+ <h1>{{ .Name }}</h1>
<p>{{ .PageDescription }}</p>
</div>
- {{ template "logs_display" . }}
+ <div class="sections">
+ {{ range .Sections }}
+ <section>
+ <h2>{{ .Name }}</h2>
+ <p>{{ .Description }}</p>
+ {{ template "section_display" . }}
+ </section>
+ {{ end }}
+ </div>
</main>
{{end}}
diff --git a/backend/templates/home_log.html b/backend/templates/home_log.html
deleted file mode 100644
index 485661c..0000000
--- a/backend/templates/home_log.html
+++ /dev/null
@@ -1,3 +0,0 @@
-{{define "body"}}
-<main id="content">{{ template "logs_display" . }}</main>
-{{end}}
diff --git a/backend/templates/home_section.html b/backend/templates/home_section.html
new file mode 100644
index 0000000..cf7595d
--- /dev/null
+++ b/backend/templates/home_section.html
@@ -0,0 +1,10 @@
+{{define "body"}}
+<main id="content">
+ {{ range .Sections }}
+ <div class="introduction">
+ <h1>{{ .Name }}</h1>
+ <p>{{ .Description }}</p>
+ </div>
+ {{ template "section_display" . }} {{ end }}
+</main>
+{{end}}
diff --git a/backend/templates/rss.xml b/backend/templates/rss.xml
index c8390da..039a2f7 100644
--- a/backend/templates/rss.xml
+++ b/backend/templates/rss.xml
@@ -3,18 +3,20 @@
<channel>
<atom:link href="https://{{ .Domain }}{{ .URL }}" rel="self" type="application/rss+xml" />
<title>{{ .Title }}</title>
- <link>https://{{ .Domain }}/logs/</link>
+ {{ $sec := first .Sections }}
+ {{ $uri := uri $sec.URI }}
+ <link>https://{{ .Domain }}/{{ $uri }}</link>
<description>{{ .PageDescription }}</description>
- <language>fr-fr</language>
+ <language>{{ .Language }}</language>
{{ $domain := .Domain }}
- {{ range .Logs }}
+ {{ range $sec.Data }}
<item>
- <title>{{ .LogTitle }}</title>
- <link>https://{{ $domain }}/logs/{{ .Slug }}</link>
- <guid>https://{{ $domain }}/logs/{{ .Slug }}</guid>
+ <title>{{ .DataTitle }}</title>
+ <link>https://{{ $domain }}/{{ $uri }}{{ .Slug }}</link>
+ <guid>https://{{ $domain }}/{{ $uri }}{{ .Slug }}</guid>
<description>{{ .Description }}</description>
<pubDate>{{ .PubDateRSS }}</pubDate>
</item>
{{ end }}
</channel>
-</rss> \ No newline at end of file
+</rss>
diff --git a/frontend/scss/home.scss b/frontend/scss/home.scss
index 6fa4192..075896e 100644
--- a/frontend/scss/home.scss
+++ b/frontend/scss/home.scss
@@ -2,6 +2,12 @@
margin: var(--margin-header) 0;
}
+.sections {
+ display: flex;
+ flex-direction: column;
+ gap: calc(1.5 * var(--margin-header));
+}
+
article article {
margin-bottom: var(--margin-header);
&:last-child {
diff --git a/main.go b/main.go
index add522c..480762d 100644
--- a/main.go
+++ b/main.go
@@ -58,9 +58,11 @@ func main() {
os.Exit(1)
}
- if ok = backend.LoadLogs(cfg); !ok {
- slog.Info("exiting")
- os.Exit(2)
+ for _, sec := range cfg.Sections {
+ if ok = sec.Load(cfg); !ok {
+ slog.Info("exiting")
+ os.Exit(2)
+ }
}
assetsFS := backend.UsableEmbedFS("dist", embeds)
@@ -72,7 +74,9 @@ func main() {
backend.HandleHome(r)
backend.HandleRoot(r, cfg)
- backend.HandleLogs(r)
+ for _, sec := range cfg.Sections {
+ sec.Handle(r)
+ }
backend.Handle404(r)
backend.HandleStaticFiles(r, "/assets", assetsFS)