diff --git a/.air/appview.toml b/.air/appview.toml index a669704d..194bd8d2 100644 --- a/.air/appview.toml +++ b/.air/appview.toml @@ -4,5 +4,5 @@ bin = ".bin/app" root = "." exclude_regex = [".*_templ.go"] -include_ext = ["go", "templ"] +include_ext = ["go", "templ", "html"] exclude_dir = ["target", "atrium"] diff --git a/appview/pages/layout.html b/appview/pages/layout.html deleted file mode 100644 index 18d28f77..00000000 --- a/appview/pages/layout.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - {{block "title" .}}tangled{{end}} - - - - - -
- {{block "content" .}}{{end}} -
- - - diff --git a/appview/pages/login.html b/appview/pages/login.html deleted file mode 100644 index 961a69e8..00000000 --- a/appview/pages/login.html +++ /dev/null @@ -1,14 +0,0 @@ -{{define "title"}}login{{end}} - -{{define "content"}} -

login

-
- - - - - - - -
-{{end}} diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 0143fd57..31262d40 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -2,51 +2,80 @@ package pages import ( "embed" + "fmt" "html/template" "io" - "sync" + "io/fs" + "log" + "strings" "github.com/sotangled/tangled/appview/auth" "github.com/sotangled/tangled/appview/db" ) -//go:embed *.html +//go:embed templates/* var files embed.FS -var ( - cache = make(map[string]*template.Template) - mutex sync.Mutex -) - -func parse(file string) *template.Template { - mutex.Lock() - defer mutex.Unlock() +type Pages struct { + t map[string]*template.Template +} - if tmpl, found := cache[file]; found { - return tmpl +func NewPages() *Pages { + templates := make(map[string]*template.Template) + + // Walk through embedded templates directory and parse all .html files + err := fs.WalkDir(files, "templates", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !d.IsDir() && strings.HasSuffix(path, ".html") { + name := strings.TrimPrefix(path, "templates/") + name = strings.TrimSuffix(name, ".html") + + if !strings.HasPrefix(path, "templates/layouts/") { + // Add the page template on top of the base + tmpl, err := template.New(name).ParseFS(files, path, "templates/layouts/*.html") + if err != nil { + return fmt.Errorf("setting up template: %w", err) + } + + templates[name] = tmpl + log.Printf("loaded template: %s", name) + } + + return nil + } + return nil + }) + if err != nil { + log.Fatalf("walking template dir: %v", err) } - tmpl := template.Must( - template.New("layout.html").ParseFS(files, "layout.html", file), - ) + log.Printf("total templates loaded: %d", len(templates)) - cache[file] = tmpl - return tmpl + return &Pages{ + t: templates, + } } type LoginParams struct { } -func Login(w io.Writer, p LoginParams) error { - return parse("login.html").Execute(w, p) +func (p *Pages) execute(name string, w io.Writer, params any) error { + return p.t[name].ExecuteTemplate(w, "layouts/base", params) +} + +func (p *Pages) Login(w io.Writer, params LoginParams) error { + return p.t["user/login"].ExecuteTemplate(w, "layouts/base", params) } type TimelineParams struct { User *auth.User } -func Timeline(w io.Writer, p TimelineParams) error { - return parse("timeline.html").Execute(w, p) +func (p *Pages) Timeline(w io.Writer, params TimelineParams) error { + return p.execute("timeline", w, params) } type SettingsParams struct { @@ -54,8 +83,8 @@ type SettingsParams struct { PubKeys []db.PublicKey } -func Settings(w io.Writer, p SettingsParams) error { - return parse("settings.html").Execute(w, p) +func (p *Pages) Settings(w io.Writer, params SettingsParams) error { + return p.execute("settings/keys", w, params) } type KnotsParams struct { @@ -63,8 +92,8 @@ type KnotsParams struct { Registrations []db.Registration } -func Knots(w io.Writer, p KnotsParams) error { - return parse("knots.html").Execute(w, p) +func (p *Pages) Knots(w io.Writer, params KnotsParams) error { + return p.execute("knots", w, params) } type KnotParams struct { @@ -74,16 +103,16 @@ type KnotParams struct { IsOwner bool } -func Knot(w io.Writer, p KnotParams) error { - return parse("knot.html").Execute(w, p) +func (p *Pages) Knot(w io.Writer, params KnotParams) error { + return p.execute("knot", w, params) } type NewRepoParams struct { User *auth.User } -func NewRepo(w io.Writer, p NewRepoParams) error { - return parse("new-repo.html").Execute(w, p) +func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { + return p.execute("repo/new", w, params) } type ProfilePageParams struct { @@ -93,6 +122,6 @@ type ProfilePageParams struct { Repos []db.Repo } -func ProfilePage(w io.Writer, p ProfilePageParams) error { - return parse("profile.html").Execute(w, p) +func (p *Pages) ProfilePage(w io.Writer, params ProfilePageParams) error { + return p.execute("user/profile", w, params) } diff --git a/appview/pages/templates/errors/404.html b/appview/pages/templates/errors/404.html new file mode 100644 index 00000000..285c2d6f --- /dev/null +++ b/appview/pages/templates/errors/404.html @@ -0,0 +1,10 @@ + + 404 + {{ template "layouts/head" . }} + + {{ template "layouts/nav" . }} +
+

404 — nothing like that here.

+
+ + diff --git a/appview/pages/templates/errors/500.html b/appview/pages/templates/errors/500.html new file mode 100644 index 00000000..642f821c --- /dev/null +++ b/appview/pages/templates/errors/500.html @@ -0,0 +1,10 @@ + + 500 + {{ template "layouts/head" . }} + + {{ template "layouts/nav" . }} +
+

500 — something broke!

+
+ + diff --git a/appview/pages/templates/index.html b/appview/pages/templates/index.html new file mode 100644 index 00000000..eeef7077 --- /dev/null +++ b/appview/pages/templates/index.html @@ -0,0 +1,21 @@ + + {{ template "layouts/head" . }} + +
+

{{ .meta.Title }}

+

{{ .meta.Description }}

+
+ +
+
+ {{ range .info }} + +
{{ .Desc }}
+
{{ .Idle }}
+ {{ end }} +
+
+ + diff --git a/appview/pages/knot.html b/appview/pages/templates/knot.html similarity index 100% rename from appview/pages/knot.html rename to appview/pages/templates/knot.html diff --git a/appview/pages/knots.html b/appview/pages/templates/knots.html similarity index 100% rename from appview/pages/knots.html rename to appview/pages/templates/knots.html diff --git a/appview/pages/templates/layouts/base.html b/appview/pages/templates/layouts/base.html new file mode 100644 index 00000000..d5f3bc70 --- /dev/null +++ b/appview/pages/templates/layouts/base.html @@ -0,0 +1,18 @@ +{{ define "layouts/base" }} + + + + + + + {{block "title" .}}{{end}} + + +
{{block "content" .}}{{end}}
+ + +{{ end }} diff --git a/appview/pages/templates/layouts/head.html b/appview/pages/templates/layouts/head.html new file mode 100644 index 00000000..ce6c02ae --- /dev/null +++ b/appview/pages/templates/layouts/head.html @@ -0,0 +1,37 @@ + + + + + + + + + {{ if .parent }} + + {{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .parent }}/ + + + {{ else if .path }} + + {{ .meta.Title }} — {{ .name }} ({{ .ref }}): {{ .path }} + + {{ else if .files }} + {{ .meta.Title }} — {{ .name }} ({{ .ref }}) + {{ else if .commit }} + {{ .meta.Title }} — {{ .name }}: {{ .commit.This }} + {{ else if .branches }} + {{ .meta.Title }} — {{ .name }}: refs + {{ else if .commits }} {{ if .log }} + {{ .meta.Title }} — {{ .name }}: log + {{ else }} + {{ .meta.Title }} — {{ .name }} + {{ end }} {{ else }} + {{ .meta.Title }} + {{ end }} {{ if and .servername .gomod }} + + {{ end }} + + diff --git a/appview/pages/templates/layouts/nav.html b/appview/pages/templates/layouts/nav.html new file mode 100644 index 00000000..804d2eff --- /dev/null +++ b/appview/pages/templates/layouts/nav.html @@ -0,0 +1,13 @@ +{{ define "nav" }} + +{{ end }} diff --git a/appview/pages/templates/layouts/repo-header.html b/appview/pages/templates/layouts/repo-header.html new file mode 100644 index 00000000..4b261a44 --- /dev/null +++ b/appview/pages/templates/layouts/repo-header.html @@ -0,0 +1,11 @@ +{{ define "repo-header" }} +
+

+ all repos + — {{ .displayname }} {{ if .ref }} + @ {{ .ref }} + {{ end }} +

+

{{ .desc }}

+
+{{ end }} diff --git a/appview/pages/templates/layouts/test.html b/appview/pages/templates/layouts/test.html new file mode 100644 index 00000000..f3cca8d4 --- /dev/null +++ b/appview/pages/templates/layouts/test.html @@ -0,0 +1,5 @@ +

Hello world!

+
+

Welcome

+

This is a simple HTML example

+
diff --git a/appview/pages/new-repo.html b/appview/pages/templates/new-repo.html similarity index 100% rename from appview/pages/new-repo.html rename to appview/pages/templates/new-repo.html diff --git a/appview/pages/templates/repo/commit.html b/appview/pages/templates/repo/commit.html new file mode 100644 index 00000000..83a6e5c0 --- /dev/null +++ b/appview/pages/templates/repo/commit.html @@ -0,0 +1,100 @@ + +{{ template "layouts/head" . }} + + {{ template "layouts/repo-header" . }} + + {{ template "layouts/nav" . }} +
+
+
{{- .commit.Message -}}
+
+ {{ .commit.Author.Name }} {{ .commit.Author.Email}} +
{{ .commit.Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}
+
+ +
+ commit +

+ {{ .commit.This }} + +

+
+ + {{ if .commit.Parent }} +
+ parent +

+ {{ .commit.Parent }} +

+
+ + {{ end }} +
+
+ {{ .stat.FilesChanged }} files changed, + {{ .stat.Insertions }} insertions(+), + {{ .stat.Deletions }} deletions(-) +
+
+
+ jump to + {{ range .diff }} + + {{ end }} +
+
+
+
+ {{ $repo := .name }} + {{ $this := .commit.This }} + {{ $parent := .commit.Parent }} + {{ range .diff }} +
+
+ {{ if .IsNew }} + A + {{ end }} + {{ if .IsDelete }} + D + {{ end }} + {{ if not (or .IsNew .IsDelete) }} + M + {{ end }} + {{ if .Name.Old }} + {{ .Name.Old }} + {{ if .Name.New }} + → + {{ .Name.New }} + {{ end }} + {{ else }} + {{ .Name.New }} + {{- end -}} + {{ if .IsBinary }} +

Not showing binary file.

+ {{ else }} +
+            {{- range .TextFragments -}}
+            

{{- .Header -}}

+ {{- range .Lines -}} + {{- if eq .Op.String "+" -}} + {{ .String }} + {{- end -}} + {{- if eq .Op.String "-" -}} + {{ .String }} + {{- end -}} + {{- if eq .Op.String " " -}} + {{ .String }} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} +
+
+
+ {{ end }} +
+
+ + \ No newline at end of file diff --git a/appview/pages/templates/repo/empty.html b/appview/pages/templates/repo/empty.html new file mode 100644 index 00000000..da899787 --- /dev/null +++ b/appview/pages/templates/repo/empty.html @@ -0,0 +1,9 @@ + + {{ template "layouts/head" . }} + + +
+

This is an empty Git repository. Push some commits here.

+
+ + diff --git a/appview/pages/templates/repo/file.html b/appview/pages/templates/repo/file.html new file mode 100644 index 00000000..5dea3f60 --- /dev/null +++ b/appview/pages/templates/repo/file.html @@ -0,0 +1,34 @@ + + {{ template "layouts/head" . }} + {{ template "layouts/repo-header" . }} + + {{ template "layouts/nav" . }} +
+

{{ .path }} (view raw)

+ {{if .chroma }} +
+ {{ .content }} +
+ {{else}} +
+ + + + + +
+
+            {{- range .linecount }}
+ {{ . }}
+            {{- end -}}
+              
+
+
+             {{- .content -}}
+              
+
+
+ {{end}} +
+ + diff --git a/appview/pages/templates/repo/log.html b/appview/pages/templates/repo/log.html new file mode 100644 index 00000000..02f3a798 --- /dev/null +++ b/appview/pages/templates/repo/log.html @@ -0,0 +1,23 @@ + +{{ template "layouts/head" . }} + + {{ template "layouts/repo-header" . }} + + {{ template "layouts/nav" . }} +
+ {{ $repo := .name }} +
+ {{ range .commits }} +
+
{{ slice .Hash.String 0 8 }}
+
{{ .Message }}
+
+
+ {{ .Author.Name }} {{ .Author.Email }} +
{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}
+
+ {{ end }} +
+
+ + diff --git a/appview/pages/templates/repo/new.html b/appview/pages/templates/repo/new.html new file mode 100644 index 00000000..f4fe0d87 --- /dev/null +++ b/appview/pages/templates/repo/new.html @@ -0,0 +1,14 @@ +{{define "title"}}new repo{{end}} + +{{define "content"}} +

new repo

+
+ + + + + + + +
+{{end}} diff --git a/appview/pages/templates/repo/refs.html b/appview/pages/templates/repo/refs.html new file mode 100644 index 00000000..cb24aeb0 --- /dev/null +++ b/appview/pages/templates/repo/refs.html @@ -0,0 +1,38 @@ + +{{ template "layouts/head" . }} + + {{ template "layouts/repo-header" . }} + + {{ template "layouts/nav" . }} +
+ {{ $name := .name }} +

branches

+
+ {{ range .branches }} +
+ {{ .Name.Short }} + browse + log + tar.gz +
+ {{ end }} +
+ {{ if .tags }} +

tags

+
+ {{ range .tags }} +
+ {{ .Name }} + browse + log + tar.gz + {{ if .Message }} +
{{ .Message }}
+
+ {{ end }} + {{ end }} +
+ {{ end }} +
+ + \ No newline at end of file diff --git a/appview/pages/templates/repo/repo.html b/appview/pages/templates/repo/repo.html new file mode 100644 index 00000000..dbb09edf --- /dev/null +++ b/appview/pages/templates/repo/repo.html @@ -0,0 +1,36 @@ + +{{ template "layouts/head" . }} + +{{ template "layouts/repo-header" . }} + + + {{ template "layouts/nav" . }} +
+ {{ $repo := .name }} +
+ {{ range .commits }} +
+
{{ slice .Hash.String 0 8 }}
+
{{ .Message }}
+
+
+ {{ .Author.Name }} {{ .Author.Email }} +
{{ .Author.When.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}
+
+ {{ end }} +
+{{- if .readme }} +
+ {{- .readme -}} +
+{{- end -}} + +
+ clone +
+git clone https://{{ .servername }}/{{ .name }}
+        
+
+
+ + diff --git a/appview/pages/templates/repo/tree.html b/appview/pages/templates/repo/tree.html new file mode 100644 index 00000000..3f425245 --- /dev/null +++ b/appview/pages/templates/repo/tree.html @@ -0,0 +1,53 @@ + + +{{ template "layouts/head" . }} + + {{ template "layouts/repo-header" . }} + + {{ template "layouts/nav" . }} +
+ {{ $repo := .name }} + {{ $ref := .ref }} + {{ $parent := .parent }} + +
+ {{ if $parent }} +
+
+
..
+ {{ end }} + {{ range .files }} + {{ if not .IsFile }} +
{{ .Mode }}
+
{{ .Size }}
+
+ {{ if $parent }} + {{ .Name }}/ + {{ else }} + {{ .Name }}/ + {{ end }} +
+ {{ end }} + {{ end }} + {{ range .files }} + {{ if .IsFile }} +
{{ .Mode }}
+
{{ .Size }}
+
+ {{ if $parent }} + {{ .Name }} + {{ else }} + {{ .Name }} + {{ end }} +
+ {{ end }} + {{ end }} +
+
+
+          {{- if .readme }}{{ .readme }}{{- end -}}
+        
+
+
+ + \ No newline at end of file diff --git a/appview/pages/settings.html b/appview/pages/templates/settings/keys.html similarity index 100% rename from appview/pages/settings.html rename to appview/pages/templates/settings/keys.html diff --git a/appview/pages/timeline.html b/appview/pages/templates/timeline.html similarity index 100% rename from appview/pages/timeline.html rename to appview/pages/templates/timeline.html diff --git a/appview/pages/templates/user/login.html b/appview/pages/templates/user/login.html new file mode 100644 index 00000000..cf3e2fb1 --- /dev/null +++ b/appview/pages/templates/user/login.html @@ -0,0 +1,12 @@ +{{ define "title" }}login{{end}} {{define "content"}} +

login

+
+ + + + + + + +
+{{end}} diff --git a/appview/pages/profile.html b/appview/pages/templates/user/profile.html similarity index 100% rename from appview/pages/profile.html rename to appview/pages/templates/user/profile.html diff --git a/appview/state/state.go b/appview/state/state.go index 67a3b222..40f3487a 100644 --- a/appview/state/state.go +++ b/appview/state/state.go @@ -29,10 +29,10 @@ type State struct { auth *auth.Auth enforcer *rbac.Enforcer tidClock *syntax.TIDClock + pages *pages.Pages } func Make() (*State, error) { - db, err := db.Make(appview.SqliteDbPath) if err != nil { return nil, err @@ -50,7 +50,9 @@ func Make() (*State, error) { clock := syntax.NewTIDClock(0) - return &State{db, auth, enforcer, clock}, nil + pgs := pages.NewPages() + + return &State{db, auth, enforcer, clock, pgs}, nil } func (s *State) TID() string { @@ -62,7 +64,10 @@ func (s *State) Login(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - pages.Login(w, pages.LoginParams{}) + err := s.pages.Login(w, pages.LoginParams{}) + if err != nil { + log.Printf("rendering login page: %s", err) + } return case http.MethodPost: handle := r.FormValue("handle") @@ -99,7 +104,7 @@ func (s *State) Login(w http.ResponseWriter, r *http.Request) { func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { user := s.auth.GetUser(r) - pages.Timeline(w, pages.TimelineParams{ + s.pages.Timeline(w, pages.TimelineParams{ User: user, }) return @@ -149,7 +154,7 @@ func (s *State) Settings(w http.ResponseWriter, r *http.Request) { log.Println(err) } - pages.Settings(w, pages.SettingsParams{ + s.pages.Settings(w, pages.SettingsParams{ User: user, PubKeys: pubKeys, }) @@ -358,7 +363,7 @@ func (s *State) KnotServerInfo(w http.ResponseWriter, r *http.Request) { IsOwner: isOwner, } - pages.Knot(w, p) + s.pages.Knot(w, p) } // get knots registered by this user @@ -370,7 +375,7 @@ func (s *State) Knots(w http.ResponseWriter, r *http.Request) { log.Println(err) } - pages.Knots(w, pages.KnotsParams{ + s.pages.Knots(w, pages.KnotsParams{ User: user, Registrations: registrations, }) @@ -475,7 +480,7 @@ func (s *State) RemoveMember(w http.ResponseWriter, r *http.Request) { func (s *State) AddRepo(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: - pages.NewRepo(w, pages.NewRepoParams{ + s.pages.NewRepo(w, pages.NewRepoParams{ User: s.auth.GetUser(r), }) case http.MethodPost: @@ -562,7 +567,7 @@ func (s *State) ProfilePage(w http.ResponseWriter, r *http.Request) { log.Printf("getting repos for %s: %s", ident.DID.String(), err) } - pages.ProfilePage(w, pages.ProfilePageParams{ + s.pages.ProfilePage(w, pages.ProfilePageParams{ LoggedInUser: s.auth.GetUser(r), UserDid: ident.DID.String(), UserHandle: ident.Handle.String(),