all repos — homestead @ ea267852dae599821d1cb4b2bc60dd3f37a83d41

Code for my website

domain/web/templates/layout.go (view raw)

package templates

import (
	"io"

	g "alin.ovh/gomponents"
	. "alin.ovh/gomponents/html"
	"alin.ovh/htmlformat"
	"github.com/Southclaws/fault"

	"alin.ovh/homestead/shared/config"
)

type SiteSettings struct {
	Title            string
	Language         string
	Timezone         config.Timezone
	Menu             []config.MenuItem
	InjectLiveReload bool
}

type Attrs map[string]string

type PageSettings struct {
	Title      string
	TitleAttrs Attrs
	BodyAttrs  Attrs
	HeadExtra  []g.Node
}

var LiveReload = Script(Defer(), g.Raw(`
    new EventSource("/_/reload").onmessage = event => {
        console.log("got message", event)
        window.location.reload()
    };
`))

func ExtendAttrs(base Attrs, attrs Attrs) g.Node {
	m := base
	for key, value := range attrs {
		if v, found := base[key]; found {
			m[key] = v + " " + value
		} else {
			m[key] = value
		}
	}

	return g.MapMap(m, func(k string, v string) g.Node {
		return g.Attr(k, v) // can't inline this because it uses ...value, grr
	})
}

func Layout(site SiteSettings, page PageSettings, children ...g.Node) g.NodeWriter {
	return FormattedDocument(
		Doctype(
			HTML(
				Lang(site.Language),
				Head(
					Meta(Name("viewport"), Content("width=device-width, initial-scale=1.0")),
					TitleEl(g.Text(page.Title)),
					Link(
						Rel("stylesheet"),
						Type("text/css"),
						Href("/style.css"),
					),
					Link(
						Rel("alternate"),
						Type("application/atom+xml"),
						Title(site.Title),
						Href("/atom.xml"),
					),
					g.Group(page.HeadExtra),
				),
				Body(
					ExtendAttrs(page.BodyAttrs, nil),
					A(Class("skip"), Href("#main"), g.Text("Skip to main content")),
					Header(
						H2(
							A(
								ExtendAttrs(Attrs{
									"class": "title p-name",
									"href":  "/",
								}, page.TitleAttrs),
								g.Text(site.Title)),
						),
						Nav(
							g.Map(site.Menu, MenuLink),
						),
					),
					Main(ID("main"), g.Group(children)),
					Footer(
						A(Href("https://git.alin.ovh/website"), g.Text("Content")),
						g.Text(" is "),
						A(
							Rel("license"),
							Href("http://creativecommons.org/licenses/by/4.0/"),
							g.Text("CC BY 4.0"),
						),
						g.Text(". "),
						A(Href("https://git.alin.ovh/homestead"), g.Text("Site source code")),
						g.Text(" is "),
						A(Href("https://opensource.org/licenses/MIT"), g.Text("MIT")),
					),
					g.If(site.InjectLiveReload,
						LiveReload,
					),
				),
			)))
}

func MenuLink(item config.MenuItem) g.Node {
	return A(
		Href(item.URL.String()),
		g.If(item.URL.IsAbs(), Target("_blank")),
		g.Text(item.Name),
	)
}

func FormattedDocument(doc g.Node) g.NodeWriter {
	return g.NodeWriterFunc(func(w io.Writer) (int64, error) {
		pr, pw := io.Pipe()
		defer pr.Close()

		go func() {
			pw.CloseWithError(doc.Render(pw))
		}()

		if err := htmlformat.Document(w, pr); err != nil {
			return 0, fault.Wrap(err)
		}

		return 0, nil
	})
}