layout.go
| 1 | // Package views renders the server-side HTML with gomponents. |
| 2 | // Every function returns a g.Node. Handlers pass the result to Render. |
| 3 | package views |
| 4 | |
| 5 | import ( |
| 6 | "net/http" |
| 7 | |
| 8 | "hearthforge/internal/highlight" |
| 9 | |
| 10 | "hearthforge/internal/config" |
| 11 | "hearthforge/internal/db" |
| 12 | "hearthforge/internal/util" |
| 13 | |
| 14 | g "maragu.dev/gomponents" |
| 15 | . "maragu.dev/gomponents/html" |
| 16 | ) |
| 17 | |
| 18 | // Page carries the data the page shell needs. |
| 19 | type Page struct { |
| 20 | Title string |
| 21 | User *db.SessionUser |
| 22 | Cfg *config.Config |
| 23 | } |
| 24 | |
| 25 | // Layout renders the full HTML document around the body nodes. |
| 26 | func Layout(p Page, body ...g.Node) g.Node { |
| 27 | pageTitle := "Hearthforge" |
| 28 | if p.Title != "" { |
| 29 | pageTitle = p.Title + " — Hearthforge" |
| 30 | } |
| 31 | owner := "" |
| 32 | if p.Cfg != nil { |
| 33 | owner = p.Cfg.OwnerDisplayName |
| 34 | } |
| 35 | return HTML(Lang("en"), |
| 36 | Head( |
| 37 | Meta(Charset("UTF-8")), |
| 38 | Meta(Name("viewport"), Content("width=500")), |
| 39 | TitleEl(g.Text(pageTitle)), |
| 40 | Script(Src("/assets/theme.js")), |
| 41 | Link(Rel("icon"), Type("image/svg+xml"), Href("/assets/favicon.svg")), |
| 42 | Link(Rel("stylesheet"), Href("/assets/css/main.css")), |
| 43 | Link(Rel("stylesheet"), Href("/assets/code-theme.css?v="+highlight.CSSVersion())), |
| 44 | Script(Src("/assets/app.js"), Defer()), |
| 45 | ), |
| 46 | Body( |
| 47 | Header(Class("site-header"), |
| 48 | Nav(Class("site-nav"), |
| 49 | A(Href("/"), Class("site-logo"), |
| 50 | Img(Src("/assets/favicon.svg"), Class("logo-icon"), Alt(""), Aria("hidden", "true")), |
| 51 | Span(Class("logo-text"), g.Text("Hearthforge")), |
| 52 | ), |
| 53 | Div(Class("nav-links"), |
| 54 | g.Iff(p.User != nil, func() g.Node { |
| 55 | return g.Group{ |
| 56 | A(Href("/settings"), Class("nav-user"), |
| 57 | Avatar(&p.User.ID, &p.User.AvatarVersion, 24), |
| 58 | Span(g.Text(util.DisplayName(p.User.Username, owner))), |
| 59 | ), |
| 60 | Form(Method("POST"), Action("/logout"), Class("inline-form"), |
| 61 | Button(Type("submit"), Class("btn btn-sm"), g.Text("Sign out")), |
| 62 | ), |
| 63 | } |
| 64 | }), |
| 65 | g.If(p.User == nil, g.Group{ |
| 66 | A(Href("/login"), Class("btn btn-sm"), g.Text("Sign in")), |
| 67 | A(Href("/register"), Class("btn btn-sm btn-primary"), g.Text("Register")), |
| 68 | }), |
| 69 | ), |
| 70 | ), |
| 71 | ), |
| 72 | Main(Class("site-main"), g.Group(body)), |
| 73 | Footer(Class("site-footer"), |
| 74 | P(g.Text("Hearthforge — hosted by "+owner)), |
| 75 | ), |
| 76 | ), |
| 77 | ) |
| 78 | } |
| 79 | |
| 80 | // Render writes a full HTML document to the response. |
| 81 | func Render(w http.ResponseWriter, status int, node g.Node) { |
| 82 | w.Header().Set("Content-Type", "text/html; charset=utf-8") |
| 83 | w.Header().Set("Cache-Control", "no-store") |
| 84 | w.WriteHeader(status) |
| 85 | _ = Doctype(node).Render(w) |
| 86 | } |
| 87 |