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:
parent
e29bc93f35
commit
3dbd5a86e2
|
@ -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?
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -16,20 +16,20 @@ 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!")
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
c, err := GenerateNewTemplateCache()
|
RegenerateTemplateCache()
|
||||||
if err != nil {
|
|
||||||
app.Logger.Fatal("Error generating template cache, bailing out!")
|
|
||||||
}
|
|
||||||
app.TemplateCache = c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get templates from cache
|
template, err := GetTemplateFromCache(filename)
|
||||||
template, ok := app.TemplateCache.Cache[filename]
|
if err != nil {
|
||||||
if !ok {
|
app.Logger.Fatalf("Tried loading %s from the cache, but %s!", filename, err)
|
||||||
app.Logger.Errorf("Couldn't get %s from template cache, dunno what happened, but we're gonna generate a new one", filename)
|
}
|
||||||
c, err := GenerateNewTemplateCache()
|
|
||||||
if err != nil {
|
data, err := GetOrGenerateTemplateData(filename)
|
||||||
app.Logger.Fatal("Error generating template cache, bailing out!")
|
if err != nil {
|
||||||
}
|
app.Logger.Error(err)
|
||||||
app.TemplateCache = c
|
}
|
||||||
template = app.TemplateCache.Cache[filename]
|
|
||||||
|
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()
|
||||||
|
if err != nil {
|
||||||
|
app.Logger.Fatal("Error generating template cache, bailing out!")
|
||||||
|
}
|
||||||
|
app.TemplateCache = c
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTemplateFromCache gets templates from cache
|
||||||
|
func GetTemplateFromCache(filename string) (*models.TemplateCacheItem, error) {
|
||||||
|
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 {
|
||||||
|
return &models.TemplateData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 }}
|
||||||
|
@ -26,8 +17,8 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<br />
|
<br />
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<span class="iconify" data-icon="material-symbols:keyboard-arrow-left"></span>
|
<span class="iconify" data-icon="material-symbols:keyboard-arrow-left"></span>
|
||||||
back
|
back
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
|
|
|
@ -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
11
templates/data/apps.yaml
Normal 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
|
25
templates/data/projects.yaml
Normal file
25
templates/data/projects.yaml
Normal 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
|
|
@ -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
25
templates/links.tmpl
Normal 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 -}}
|
Loading…
Reference in a new issue