summaryrefslogtreecommitdiff
path: root/vrsite/page.go
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-24 11:39:36 -0300
committerGabriel Schneider <[email protected]>2026-07-30 15:18:55 -0300
commitcb05c363045bf0e7af5858f6d7bc26f026fa9d70 (patch)
treec3e89426ff044ffe339dd3a77e7f3b7115cba636 /vrsite/page.go
downloadnotevi-cb05c363045bf0e7af5858f6d7bc26f026fa9d70.tar.gz
notevi-cb05c363045bf0e7af5858f6d7bc26f026fa9d70.zip
Diffstat (limited to 'vrsite/page.go')
-rw-r--r--vrsite/page.go172
1 files changed, 172 insertions, 0 deletions
diff --git a/vrsite/page.go b/vrsite/page.go
new file mode 100644
index 0000000..71a8343
--- /dev/null
+++ b/vrsite/page.go
@@ -0,0 +1,172 @@
+// page.go — what a template gets, and how links are formed.
+//
+// The same templates render two ways: as a static export (relative links,
+// ".html" suffixes, no server-side features) and as the live web app
+// (absolute routes, htmx-driven filters, comments and jj queries). Pages carry
+// Live so a template can ask for the difference in one place.
+package main
+
+import (
+ "html/template"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+type page struct {
+ Site *Site
+ Title string
+ Kind string // index | file | files | notes | jj | change
+ Root string // static: relative prefix; live: "/"
+ Live bool
+ Rev string // revision being browsed ("" = the site change)
+ ChangeID string // resolved change id for Rev
+ Current string // current file path, for the tree
+ User string // who web comments are attributed to
+
+ Tree template.HTML
+ File *File
+
+ Notes []*Note
+ Filter NoteFilter
+ Facets *Facets
+ Sessions []*Session
+ Compact bool
+ Window string
+
+ JJ *JJView
+ Change *Change
+ Err string
+}
+
+// JJView is one rendered jj query: what was asked, and what came back.
+type JJView struct {
+ Kind string // log | op
+ Revset string
+ AtOp string
+ Limit int
+ Rows []Row
+ Err string
+}
+
+func (p *page) revQuery(first bool) string {
+ if p.Rev == "" || p.Rev == "@" {
+ return ""
+ }
+ sep := "&"
+ if first {
+ sep = "?"
+ }
+ return sep + "rev=" + url.QueryEscape(p.Rev)
+}
+
+// Href maps a logical destination to a URL for this page's mode. Kinds the
+// static export has no page for return "", and templates guard on .Live.
+func (p *page) Href(kind string, arg ...string) string {
+ a := ""
+ if len(arg) > 0 {
+ a = arg[0]
+ }
+ if p.Live {
+ switch kind {
+ case "":
+ return "/" + p.revQuery(true)
+ case "notes":
+ return "/notes"
+ case "files":
+ return "/files" + p.revQuery(true)
+ case "tree":
+ return "/tree" + p.revQuery(true)
+ case "jj":
+ return "/jj"
+ case "code":
+ return "/code/" + escPath(a) + p.revQuery(true)
+ case "change":
+ return "/change/" + a
+ case "asset":
+ return "/" + a
+ }
+ return "/"
+ }
+ switch kind {
+ case "":
+ return p.Root + "index.html"
+ case "notes":
+ return p.Root + "notes.html"
+ case "files":
+ return p.Root + "files.html"
+ case "tree":
+ return p.Root + "tree.html"
+ case "code":
+ return p.Root + "code/" + a + ".html"
+ case "asset":
+ return p.Root + a
+ }
+ return ""
+}
+
+// codeLink is the tree's per-file linker for this page's mode.
+func (p *page) codeLink() func(string) string {
+ return func(path string) string { return p.Href("code", path) }
+}
+
+func escPath(p string) string {
+ parts := strings.Split(p, "/")
+ for i, s := range parts {
+ parts[i] = url.PathEscape(s)
+ }
+ return strings.Join(parts, "/")
+}
+
+// NoteHref points at the noted line, or the note list when it is unpinned.
+func (p *page) NoteHref(n *Note) string {
+ if n.File == "" {
+ return p.Href("notes")
+ }
+ h := p.Href("code", n.File)
+ if n.Start > 0 {
+ h += "#L" + strconv.Itoa(n.Start)
+ }
+ return h
+}
+
+func newTemplates() *template.Template {
+ funcs := template.FuncMap{
+ "short": short,
+ "band": func(a, b uint8) bool { return b != 0 && a&b != 0 },
+ "lines": func(n *Note) string {
+ if n.Start <= 0 {
+ return "whole file"
+ }
+ if n.End > n.Start {
+ return ":" + strconv.Itoa(n.Start) + "-" + strconv.Itoa(n.End)
+ }
+ return ":" + strconv.Itoa(n.Start)
+ },
+ "base": func(p string) string {
+ if i := strings.LastIndex(p, "/"); i >= 0 {
+ return p[i+1:]
+ }
+ return p
+ },
+ "dict": func(kv ...any) map[string]any {
+ m := map[string]any{}
+ for i := 0; i+1 < len(kv); i += 2 {
+ m[kv[i].(string)] = kv[i+1]
+ }
+ return m
+ },
+ }
+ tpl, err := template.New("layout.tmpl").Funcs(funcs).ParseFS(tfs, "templates/*.tmpl")
+ if err != nil {
+ fatal("%v", err)
+ }
+ return tpl
+}
+
+func short(s string) string {
+ if len(s) > 8 {
+ return s[:8]
+ }
+ return s
+}