all repos — homestead @ 65039b065ba634b9c4b4c7f4b42ebccdbfd40ce0

Code for my website

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

package templates

import (
	"io"

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

	"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 DoctypeHTML(FormattedDocument(
		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
	})
}

func DoctypeHTML(sibling g.Node) g.NodeWriter {
	return g.NodeWriterFunc(func(w io.Writer) (int64, error) {
		if _, err := w.Write([]byte("\n")); err != nil {
			return 0, err
		}

		if sibling, ok := sibling.(g.NodeWriter); ok {
			return sibling.WriteTo(w)
		}

		return 0, sibling.Render(w)
	})
}