major refactor of render

- split many parts of RenderTemplate into their own functions so they can be used individually
- move web apps into their own page, add projects page, both generated by executing links.tmpl
  with their own yaml data
- handle / specially, generating LinkMap from Handlers
This commit is contained in:
Lilian Jónsdóttir 2024-01-24 00:20:07 -08:00
parent e29bc93f35
commit 3dbd5a86e2
13 changed files with 232 additions and 67 deletions

View file

@ -3,11 +3,14 @@
Code for the [burning.moe](https://burning.moe) homepage Code for the [burning.moe](https://burning.moe) homepage
## technologies used ## technologies used
- [go](https://go.dev) - written in [go](https://go.dev) using:
- [chi](https://github.com/go-chi/chi) - [chi](https://github.com/go-chi/chi)
- [cleanenv](https://github.com/ilyakaznacheev/cleanenv) - [cleanenv](https://github.com/ilyakaznacheev/cleanenv)
- [log](https://github.com/charmbracelet/log) - [log](https://github.com/charmbracelet/log)
- [mage](https://github.com/magefile/mage) - [mage](https://github.com/magefile/mage)
- written using [helix](https://helix-editor.com/)
- tested on [void linux](https://voidlinux.org/)
- hosted on [debian linux](https://www.debian.org/)
## why? ## why?

View file

@ -27,5 +27,8 @@ func routes(app *config.AppConfig) http.Handler {
mux.Get(handler.Handles, handler.Handler) mux.Get(handler.Handles, handler.Handler)
} }
// Setup home handler
mux.Get("/", handlers.HomeHandler)
return mux return mux
} }

View file

@ -1,10 +1,16 @@
package handlers package handlers
import ( import (
"fmt"
"html/template"
"net/http" "net/http"
"strings"
"time"
"git.burning.moe/celediel/burning.moe/internal/config" "git.burning.moe/celediel/burning.moe/internal/config"
"git.burning.moe/celediel/burning.moe/internal/models"
"git.burning.moe/celediel/burning.moe/internal/render" "git.burning.moe/celediel/burning.moe/internal/render"
"git.burning.moe/celediel/burning.moe/internal/td"
) )
// Handler holds data required for handlers. // Handler holds data required for handlers.
@ -17,15 +23,17 @@ var app *config.AppConfig
// The actual handlers // The actual handlers
var Handlers = []Handler{ var Handlers = []Handler{
// /about
{ {
Handles: "/about", Handles: "/about",
Handler: makeBasicHandler("about"), Handler: makeBasicHandler("about"),
}, },
// / comes last
{ {
Handles: "/", Handles: "/projects",
Handler: makeBasicHandler("home"), Handler: makeLinksHandler("projects"),
},
{
Handles: "/apps",
Handler: makeLinksHandler("apps"),
}, },
} }
@ -34,6 +42,26 @@ func Initialise(a *config.AppConfig) {
app = a app = a
} }
// HomeHandler handles /, generating data from Handlers
func HomeHandler(w http.ResponseWriter, r *http.Request) {
app.Logger.Info("Got request for homepage")
d := td.MakeBasicTemplateData(time.Now())
var pages []models.Link = []models.Link{}
for _, handler := range Handlers {
href := strings.TrimPrefix(handler.Handles, "/")
pages = append(pages, models.Link{
Href: template.URL(href),
Text: href,
})
}
d.LinkMap = make(map[string][]models.Link)
d.LinkMap["Pages"] = pages
app.Logger.Debug("handling home with some data", "data", &d)
render.RenderTemplateWithData(w, "home.page.tmpl", &d)
}
// makeBasicHandler returns a simple handler that renders a template from `name`.page.tmpl // makeBasicHandler returns a simple handler that renders a template from `name`.page.tmpl
func makeBasicHandler(name string) func(w http.ResponseWriter, r *http.Request) { func makeBasicHandler(name string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
@ -42,3 +70,25 @@ func makeBasicHandler(name string) func(w http.ResponseWriter, r *http.Request)
render.RenderTemplate(w, pageName) render.RenderTemplate(w, pageName)
} }
} }
// makeLinksHandler returns a handler for links.tmpl with template data from `name`
func makeLinksHandler(name string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
page := "links.tmpl"
template, err := render.GetTemplateFromCache(page)
if err != nil {
app.Logger.Error(fmt.Sprintf("couldn't get %s from cache", page), "err", err)
}
app.Logger.Infof("Got request for %s links page", name)
data, err := td.LoadTemplateData(name)
if err != nil {
app.Logger.Fatal("couldn't load template data for "+name, "err", err)
} else {
data.StringMap["GeneratedAt"] = template.GeneratedAt.Format(time.UnixDate)
}
app.Logger.Debug("handling a links page", "data", &data)
render.RenderTemplateWithData(w, page, &data)
}
}

View file

@ -1,7 +1,9 @@
package models package models
import ( import (
"bytes"
"html/template" "html/template"
"net/http"
"time" "time"
) )
@ -16,3 +18,19 @@ type TemplateCacheItem struct {
Template *template.Template Template *template.Template
GeneratedAt time.Time GeneratedAt time.Time
} }
// Execute writes the template to the supplied writer using the supplied data.
func (self *TemplateCacheItem) Execute(d *TemplateData, w http.ResponseWriter) error {
buf := new(bytes.Buffer)
err := self.Template.Execute(buf, d)
if err != nil {
return err
}
_, err = buf.WriteTo(w)
if err != nil {
return err
}
return nil
}

View file

@ -1,7 +1,7 @@
package render package render
import ( import (
"bytes" "errors"
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
@ -16,21 +16,21 @@ import (
const ( const (
templatesDir string = "./templates/" templatesDir string = "./templates/"
layoutGlob string = "*.layout.tmpl" layoutGlob string = "*.layout.tmpl"
pageGlob string = "*.page.tmpl" pageGlob string = "*.tmpl"
) )
var app *config.AppConfig var app *config.AppConfig
// Initialise the render package. // Initialise the render package.
func Initialise(a *config.AppConfig) { func Initialise(a *config.AppConfig) {
var err error
app = a app = a
if app.UseCache { if app.UseCache {
var err error
app.TemplateCache, err = GenerateNewTemplateCache() app.TemplateCache, err = GenerateNewTemplateCache()
}
if err != nil { if err != nil {
app.Logger.Fatal("Error generating template cache, bailing out!") app.Logger.Fatal("Error generating template cache, bailing out!")
} }
}
} }
// GenerateNewTemplateCache generates a new template cache. // GenerateNewTemplateCache generates a new template cache.
@ -83,30 +83,73 @@ func GenerateNewTemplateCache() (models.TemplateCache, error) {
// RenderTemplate renders requested template (t), pulling from cache. // RenderTemplate renders requested template (t), pulling from cache.
func RenderTemplate(w http.ResponseWriter, filename string) { func RenderTemplate(w http.ResponseWriter, filename string) {
// TODO: implement this better
if !app.UseCache { if !app.UseCache {
RegenerateTemplateCache()
}
template, err := GetTemplateFromCache(filename)
if err != nil {
app.Logger.Fatalf("Tried loading %s from the cache, but %s!", filename, err)
}
data, err := GetOrGenerateTemplateData(filename)
if err != nil {
app.Logger.Error(err)
}
app.Logger.Debug(fmt.Sprintf("Executing template %s", filename), "data", &data)
err = template.Execute(data, w)
if err != nil {
app.Logger.Fatalf("Failed to execute template %s: %s", filename, err)
}
}
func RenderTemplateWithData(w http.ResponseWriter, filename string, data *models.TemplateData) {
if !app.UseCache {
RegenerateTemplateCache()
}
template, err := GetTemplateFromCache(filename)
if err != nil {
app.Logger.Fatalf("Tried loading %s from the cache, but %s!", filename, err)
}
app.Logger.Debug(fmt.Sprintf("Executing template %s", filename), "data", &data)
err = template.Execute(data, w)
if err != nil {
app.Logger.Fatalf("Failed to execute template %s: %s", filename, err)
}
}
func RegenerateTemplateCache() {
c, err := GenerateNewTemplateCache() c, err := GenerateNewTemplateCache()
if err != nil { if err != nil {
app.Logger.Fatal("Error generating template cache, bailing out!") app.Logger.Fatal("Error generating template cache, bailing out!")
} }
app.TemplateCache = c app.TemplateCache = c
}
// Get templates from cache }
template, ok := app.TemplateCache.Cache[filename]
if !ok { // GetTemplateFromCache gets templates from cache
app.Logger.Errorf("Couldn't get %s from template cache, dunno what happened, but we're gonna generate a new one", filename) func GetTemplateFromCache(filename string) (*models.TemplateCacheItem, error) {
c, err := GenerateNewTemplateCache() if template, ok := app.TemplateCache.Cache[filename]; ok {
return &template, nil
} else {
return &models.TemplateCacheItem{}, errors.New("Couldn't load template from cache")
}
}
// GetOrGenerateTemplateData gets template data from file, or generate simple
func GetOrGenerateTemplateData(filename string) (*models.TemplateData, error) {
template, err := GetTemplateFromCache(filename)
if err != nil { if err != nil {
app.Logger.Fatal("Error generating template cache, bailing out!") return &models.TemplateData{}, err
}
app.TemplateCache = c
template = app.TemplateCache.Cache[filename]
} }
// Get template data from file, or generate simple
data, err := td.LoadTemplateData(filename) data, err := td.LoadTemplateData(filename)
if err == nil { if err == nil {
app.Logger.Debug(fmt.Sprintf("Loaded data for template %s.", filename), "data", data) app.Logger.Debug(fmt.Sprintf("Loaded data for template %s.", filename), "data", &data)
if _, ok := data.StringMap["GeneratedAt"]; !ok { if _, ok := data.StringMap["GeneratedAt"]; !ok {
data.StringMap["GeneratedAt"] = template.GeneratedAt.Format(time.UnixDate) data.StringMap["GeneratedAt"] = template.GeneratedAt.Format(time.UnixDate)
} }
@ -115,15 +158,5 @@ func RenderTemplate(w http.ResponseWriter, filename string) {
data = td.MakeBasicTemplateData(template.GeneratedAt) data = td.MakeBasicTemplateData(template.GeneratedAt)
} }
// Execute templates in a new buffer return &data, nil
buf := new(bytes.Buffer)
err = template.Template.Execute(buf, data)
if err != nil {
app.Logger.Fatal(fmt.Sprintf("Error executing template %s! Goodbye!", filename), "err", err)
}
_, err = buf.WriteTo(w)
if err != nil {
app.Logger.Error(fmt.Sprintf("Error writing template %s!", filename), "err", err)
}
} }

View file

@ -16,8 +16,8 @@ const dataDir string = "./templates/data/"
// in order of precedence // in order of precedence
var dataExtensions = [4]string{"yml", "yaml", "toml", "json"} var dataExtensions = [4]string{"yml", "yaml", "toml", "json"}
// makeBasicTemplateData creates a blank TemplateData containing only the // makeBasicTemplateData creates a blank TemplateData
// time the related template was generated // containing only the time the template was generated
func MakeBasicTemplateData(when time.Time) models.TemplateData { func MakeBasicTemplateData(when time.Time) models.TemplateData {
strMap := map[string]string{ strMap := map[string]string{
"GeneratedAt": when.Format(time.UnixDate), "GeneratedAt": when.Format(time.UnixDate),
@ -33,13 +33,16 @@ func MakeBasicTemplateData(when time.Time) models.TemplateData {
// fails, it returns an empty TemplateData and an error // fails, it returns an empty TemplateData and an error
func LoadTemplateData(page string) (models.TemplateData, error) { func LoadTemplateData(page string) (models.TemplateData, error) {
var data models.TemplateData var data models.TemplateData
output := dataDir + strings.ReplaceAll(page, "tmpl", "") output := dataDir + strings.ReplaceAll(page, ".tmpl", "")
for _, extension := range dataExtensions { for _, extension := range dataExtensions {
if info, err := os.Stat(output + extension); err == nil && !info.IsDir() { if info, err := os.Stat(output + "." + extension); err == nil && !info.IsDir() {
err = cleanenv.ReadConfig(output+extension, &data) err = cleanenv.ReadConfig(output+"."+extension, &data)
if err == nil { if err == nil {
// don't try anymore files // don't try anymore files, but do setup an empty StringMap
if len(data.StringMap) == 0 {
data.StringMap = make(map[string]string)
}
return data, nil return data, nil
} }
} }

View file

@ -1,4 +1,12 @@
@import url('https://fonts.googleapis.com/css2?family=Gloria+Hallelujah&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Gloria+Hallelujah&display=swap');
/*
TODO: update this to be a bit more modern
maybe use SASS or something
the real TODO here is finding an image that doesn't require a white background
also TODO: proper mobile layout
*/
body { body {
background: #FFF; background: #FFF;

View file

@ -9,16 +9,7 @@
<!-- personal links --> <!-- personal links -->
<h4>links</h4> <h4>links</h4>
{{ range (index .LinkMap "Personal") }} {{- range (index .LinkMap "Personal") }}
<a href="{{ .Href }}">
<span class="iconify" data-icon="{{ .Icon }}"></span>
{{ .Text }}
</a><br />
{{ end }}
<!-- web app links -->
<h4>hosted apps</h4>
{{ range (index .LinkMap "HostedApps") }}
<a href="{{ .Href }}"> <a href="{{ .Href }}">
<span class="iconify" data-icon="{{ .Icon }}"></span> <span class="iconify" data-icon="{{ .Icon }}"></span>
{{ .Text }} {{ .Text }}

View file

@ -14,13 +14,3 @@ LinkMap:
- href: https://github.com/celediel - href: https://github.com/celediel
text: github text: github
icon: bi:github icon: bi:github
HostedApps:
- href: https://git.burning.moe/
text: self-hosted git
icon: mdi:git
- href: https://bin.burning.moe/
text: wastebin
icon: fluent:bin-recycle-20-filled
- href: https://gist.burning.moe/
text: gist
icon: carbon:paste

11
templates/data/apps.yaml Normal file
View file

@ -0,0 +1,11 @@
LinkMap:
hosted apps:
- href: https://git.burning.moe/
text: self-hosted git
icon: mdi:git
- href: https://bin.burning.moe/
text: wastebin
icon: fluent:bin-recycle-20-filled
- href: https://gist.burning.moe/
text: gist
icon: carbon:paste

View file

@ -0,0 +1,25 @@
LinkMap:
morrowind lua mods:
- href: https://git.burning.moe/celediel/morrowind-a-sinking-feeling
text: a sinking feeling - armour causes sinking in water
icon: simple-icons:lua
- href: https://git.burning.moe/celediel/morrowind-no-more-friendly-fire
text: no more friendly fire - does what it says on the tin
icon: simple-icons:lua
- href: https://git.burning.moe/celediel/morrowind-door-randomizer
text: door randomizer - randomizes destinations of cell change doors
icon: simple-icons:lua
- href: https://git.burning.moe/celediel/morrowind-more-attentive-guards
text: more attentive guards - guards follow sneaking characters and help out when attacked unjustly
icon: simple-icons:lua
other modding:
- href: https://git.burning.moe/celediel/qud-mods
text: Assorted mods for Caves of Qud (C#)
icon: teenyicons:c-sharp-solid
misc:
- href: https://git.burning.moe/celediel/poiekolon.nvim
text: poiekolon - neovim plugin to add or toggle a char at the end of a line
icon: simple-icons:lua
- href: https://git.burning.moe/celediel/burning.moe
text: burning.moe - this website
icon: file-icons:go-old

View file

@ -11,6 +11,11 @@
</a> </a>
<br /> <br />
<span id="words"> <span id="words">
<!--
<a href="about">about</a> <a href="about">about</a>
-->
{{ range (index .LinkMap "Pages")}}
<a href="{{ .Href }}">{{ .Text }}</a><br />
{{ end }}
</span> </span>
{{ end -}} {{ end -}}

25
templates/links.tmpl Normal file
View file

@ -0,0 +1,25 @@
{{- template "base" . -}}
{{- define "content" }}
<span id="words">
{{- range $key, $value := .LinkMap }}
<h4>{{ $key }}</h4>
{{- range $value }}
<a href="{{ .Href }}">
{{- if (ne .Icon "") }}
<span class="iconify" data-icon="{{ .Icon }}"></span>
{{ end }}
{{- .Text }}
</a><br />
{{- end }}
{{ end -}}
<br />
<a href="/">
<span class="iconify" data-icon="material-symbols:keyboard-arrow-left"></span>
back
</a>
</span>
{{ end -}}
{{- define "js" }}
<script src="https://code.iconify.design/3/3.1.0/iconify.min.js"></script>
{{ end -}}