package templates import ( "io" "github.com/alanpearce/htmlformat" "gitlab.com/tozd/go/errors" "go.alanpearce.eu/homestead/internal/config" g "go.alanpearce.eu/gomponents" . "go.alanpearce.eu/gomponents/html" ) 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 } 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.Node { 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("alternate"), Type("application/atom+xml"), Title(site.Title), Href("/atom.xml"), ), Link( Rel("stylesheet"), Type("text/css"), Href("/style.css"), ), ), 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.alanpearce.eu/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.alanpearce.eu/homestead"), g.Text("Site source code")), g.Text(" is "), A(Href("https://opensource.org/licenses/MIT"), g.Text("MIT")), ), g.If(site.InjectLiveReload, LiveReload, ), ), ))) } var LiveReload = Script(Defer(), g.Raw(` new EventSource("/_/reload").onmessage = event => { console.log("got message", event) window.location.reload() }; `)) 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.Node { return g.NodeFunc(func(w io.Writer) error { pr, pw := io.Pipe() defer pr.Close() go func() { pw.CloseWithError(doc.Render(pw)) }() if err := htmlformat.Document(w, pr); err != nil { return errors.WithStack(err) } return nil }) } func DoctypeHTML(sibling g.Node) g.Node { return g.NodeFunc(func(w io.Writer) error { if _, err := w.Write([]byte("\n")); err != nil { return err } return sibling.Render(w) }) }