diff --git a/.gitignore b/.gitignore index 4697f6a..7190c6e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,12 +5,12 @@ # go webserver code !cmd/ !internal/ -!vendor/ !static/ !.air.toml !go.mod !go.sum !gomod2nix.toml +static/js/ # blogs !posts/ diff --git a/cmd/webserver/pages/cache.go b/cmd/webserver/cache/cache.go similarity index 99% rename from cmd/webserver/pages/cache.go rename to cmd/webserver/cache/cache.go index 587bd10..6cf0d40 100644 --- a/cmd/webserver/pages/cache.go +++ b/cmd/webserver/cache/cache.go @@ -1,4 +1,4 @@ -package pages +package cache import ( "fmt" diff --git a/static/styles/components/ascii.css b/cmd/webserver/components/ascii/ascii.css similarity index 60% rename from static/styles/components/ascii.css rename to cmd/webserver/components/ascii/ascii.css index 81cf822..328fef4 100644 --- a/static/styles/components/ascii.css +++ b/cmd/webserver/components/ascii/ascii.css @@ -5,10 +5,10 @@ text-wrap: nowrap; white-space-collapse: preserve; text-align: center; -} -.ascii-char { - display: inline-block; - font-size: 5px; - width: 3px; + & .ascii-char { + display: inline-block; + font-size: 5px; + width: 3px; + } } diff --git a/cmd/webserver/components/ascii/ascii.go b/cmd/webserver/components/ascii/ascii.go new file mode 100644 index 0000000..ea67071 --- /dev/null +++ b/cmd/webserver/components/ascii/ascii.go @@ -0,0 +1,21 @@ +package components + +import ( + "html/template" +) + +type Props struct { + Ascii [][]string +} + +func (props Props) Component(t *template.Template) error { + name := "ascii" + + filepath := "cmd/webserver/components/" + name + "/" + name + ".tmpl" + + if _, err := t.New(name + "-component").ParseFiles(filepath); err != nil { + return err + } else { + return nil + } +} diff --git a/cmd/webserver/components/ascii.tmpl b/cmd/webserver/components/ascii/ascii.tmpl similarity index 100% rename from cmd/webserver/components/ascii.tmpl rename to cmd/webserver/components/ascii/ascii.tmpl diff --git a/cmd/webserver/components/base.tmpl b/cmd/webserver/components/base.tmpl deleted file mode 100644 index bdcf94c..0000000 --- a/cmd/webserver/components/base.tmpl +++ /dev/null @@ -1,13 +0,0 @@ -{{ define "base" }} - - -{{ template "head" . }} - - {{ template "header" . }} -
- {{ template "content" . }} -
- {{ template "footer" . }} - - -{{ end }} diff --git a/static/styles/components/footer.css b/cmd/webserver/components/footer/footer.css similarity index 100% rename from static/styles/components/footer.css rename to cmd/webserver/components/footer/footer.css diff --git a/cmd/webserver/components/footer/footer.go b/cmd/webserver/components/footer/footer.go new file mode 100644 index 0000000..b05e665 --- /dev/null +++ b/cmd/webserver/components/footer/footer.go @@ -0,0 +1,19 @@ +package components + +import ( + "html/template" +) + +type Props struct{} + +func (props Props) Component(t *template.Template) error { + name := "footer" + + filepath := "cmd/webserver/components/" + name + "/" + name + ".tmpl" + + if _, err := t.New(name + "-component").ParseFiles(filepath); err != nil { + return err + } else { + return nil + } +} diff --git a/cmd/webserver/components/footer.tmpl b/cmd/webserver/components/footer/footer.tmpl similarity index 100% rename from cmd/webserver/components/footer.tmpl rename to cmd/webserver/components/footer/footer.tmpl diff --git a/cmd/webserver/components/head.tmpl b/cmd/webserver/components/head.tmpl deleted file mode 100644 index ee3de00..0000000 --- a/cmd/webserver/components/head.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -{{ define "head" }} - - - - - - - - - - - - - - -{{ end }} diff --git a/static/styles/components/header.css b/cmd/webserver/components/header/header.css similarity index 86% rename from static/styles/components/header.css rename to cmd/webserver/components/header/header.css index 11334ef..d9b1c3b 100644 --- a/static/styles/components/header.css +++ b/cmd/webserver/components/header/header.css @@ -30,4 +30,9 @@ header { } } } + + .divider { + border-block-start: var(--space-3xs) solid var(--clr-high); + width: 100%; + } } diff --git a/cmd/webserver/components/header/header.go b/cmd/webserver/components/header/header.go new file mode 100644 index 0000000..c2f3b2e --- /dev/null +++ b/cmd/webserver/components/header/header.go @@ -0,0 +1,39 @@ +package components + +import ( + "html/template" + + ascii "personal-website/cmd/webserver/components/ascii" + nav "personal-website/cmd/webserver/components/nav" +) + +type Props struct { + Ascii [][]string + PageCurrent string + Pages []string +} + +func (props Props) Component(t *template.Template) error { + name := "header" + + filepath := "cmd/webserver/components/" + name + "/" + name + ".tmpl" + + if err := (ascii.Props{ + Ascii: props.Ascii, + }.Component(t)); err != nil { + return err + } + + if err := (nav.Props{ + PageCurrent: props.PageCurrent, + Pages: props.Pages, + }.Component(t)); err != nil { + return err + } + + if _, err := t.New(name + "-component").ParseFiles(filepath); err != nil { + return err + } else { + return nil + } +} diff --git a/cmd/webserver/components/header/header.tmpl b/cmd/webserver/components/header/header.tmpl new file mode 100644 index 0000000..eedc4d6 --- /dev/null +++ b/cmd/webserver/components/header/header.tmpl @@ -0,0 +1,11 @@ +{{ define "header" }} +
+ {{ template "ascii" . }} + {{ template "nav" . }} +
+
+{{ end }} + +{{ define "header-oob" }} + {{ template "nav-oob" . }} +{{ end }} diff --git a/cmd/webserver/components/nav/nav.css b/cmd/webserver/components/nav/nav.css new file mode 100644 index 0000000..f908eec --- /dev/null +++ b/cmd/webserver/components/nav/nav.css @@ -0,0 +1,25 @@ +#nav { + width: 100%; + + & a.selected { + font-weight: 700; + text-decoration: underline; + } + + & ul { + display: flex; + justify-content: space-evenly; + list-style-type: none; + text-align: center; + + & li { + flex: 1; + + & a { + display: block; + padding-block: var(--space-2xs); + padding-inline: var(--space-xs); + } + } + } +} diff --git a/cmd/webserver/components/nav/nav.go b/cmd/webserver/components/nav/nav.go new file mode 100644 index 0000000..56afe3c --- /dev/null +++ b/cmd/webserver/components/nav/nav.go @@ -0,0 +1,22 @@ +package components + +import ( + "html/template" +) + +type Props struct { + PageCurrent string + Pages []string +} + +func (props Props) Component(t *template.Template) error { + name := "nav" + + filepath := "cmd/webserver/components/" + name + "/" + name + ".tmpl" + + if _, err := t.New(name + "-component").ParseFiles(filepath); err != nil { + return err + } else { + return nil + } +} diff --git a/cmd/webserver/components/header.tmpl b/cmd/webserver/components/nav/nav.tmpl similarity index 55% rename from cmd/webserver/components/header.tmpl rename to cmd/webserver/components/nav/nav.tmpl index d4a16ed..d26f9a0 100644 --- a/cmd/webserver/components/header.tmpl +++ b/cmd/webserver/components/nav/nav.tmpl @@ -1,16 +1,3 @@ -{{ define "nav-links" }} - {{ range $name := newRange "home" "blog" "projects" }} -
  • {{ title $name }}
  • - {{ end }} -{{ end }} - {{ define "nav" }} {{ end }} -{{ define "nav-links-updater" }} +{{ define "nav-oob" }} {{ template "nav" . }} {{ end }} -{{ define "header" }} -
    - {{ template "ascii" . }} - {{ template "nav" . }} -
    -
    +{{ define "nav-links" }} + {{ range $name := .Pages }} +
  • {{ $name }}
  • + {{ end }} {{ end }} diff --git a/static/styles/components/spacer.css b/cmd/webserver/components/spacer/spacer.css similarity index 100% rename from static/styles/components/spacer.css rename to cmd/webserver/components/spacer/spacer.css diff --git a/cmd/webserver/components/spacer/spacer.go b/cmd/webserver/components/spacer/spacer.go new file mode 100644 index 0000000..71cf87b --- /dev/null +++ b/cmd/webserver/components/spacer/spacer.go @@ -0,0 +1,19 @@ +package components + +import ( + "html/template" +) + +type Props struct{} + +func (props Props) Component(t *template.Template) error { + name := "spacer" + + filepath := "cmd/webserver/components/" + name + "/" + name + ".tmpl" + + if _, err := t.New(name + "-component").ParseFiles(filepath); err != nil { + return err + } else { + return nil + } +} diff --git a/cmd/webserver/components/spacer/spacer.tmpl b/cmd/webserver/components/spacer/spacer.tmpl new file mode 100644 index 0000000..4d3e96a --- /dev/null +++ b/cmd/webserver/components/spacer/spacer.tmpl @@ -0,0 +1,3 @@ +{{ define "spacer" }} + - +{{ end }} diff --git a/cmd/webserver/layouts/base.go b/cmd/webserver/layouts/base.go new file mode 100644 index 0000000..0213032 --- /dev/null +++ b/cmd/webserver/layouts/base.go @@ -0,0 +1,40 @@ +package layouts + +import ( + "html/template" + + // Components + footer "personal-website/cmd/webserver/components/footer" + header "personal-website/cmd/webserver/components/header" +) + +type BaseLayout struct { + Ascii [][]string + Pages []string + PageCurrent string +} + +func (props BaseLayout) Layout(t *template.Template) error { + name := "base" + + filepath := "cmd/webserver/layouts/" + name + ".tmpl" + + if _, err := t.New(name + "-layout").ParseFiles(filepath); err != nil { + return err + } + + // Components + if err := (footer.Props{}.Component(t)); err != nil { + return err + } + + if err := (header.Props{ + Ascii: props.Ascii, + PageCurrent: props.PageCurrent, + Pages: props.Pages, + }.Component(t)); err != nil { + return err + } + + return nil +} diff --git a/cmd/webserver/layouts/base.tmpl b/cmd/webserver/layouts/base.tmpl new file mode 100644 index 0000000..f5aa560 --- /dev/null +++ b/cmd/webserver/layouts/base.tmpl @@ -0,0 +1,26 @@ +{{ define "base-layout" }} + + + + + + + + + + + + + + + + + + {{ template "header" . }} +
    + {{ template "content" . }} +
    + {{ template "footer" . }} + + +{{ end }} diff --git a/cmd/webserver/main.go b/cmd/webserver/main.go index faab813..d44b881 100644 --- a/cmd/webserver/main.go +++ b/cmd/webserver/main.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "personal-website/cmd/webserver/cache" "personal-website/cmd/webserver/pages" ) @@ -18,7 +19,7 @@ var logRequests = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request func main() { log.Println("Starting server...") - pages.InitCache() + cache.InitCache() http.HandleFunc("GET /healthy", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -26,39 +27,36 @@ func main() { http.HandleFunc("GET /robots.txt", staticHandler(http.Dir("static/seo"))) - renderer := pages.NewRenderer("cmd/webserver/components", "cmd/webserver/pages") ascii := createAscii() - pageHome := &pages.Home{Renderer: renderer, Ascii: ascii} + pageHome := &pages.Home{Pages: []string{"home", "blog", "projects"}, Ascii: ascii} http.Handle("GET /", pageHome) http.Handle("GET /home", pageHome) http.Handle("GET /home/content", pageHome) - pageBlog := &pages.Blog{Renderer: renderer, Ascii: ascii} + pageBlog := &pages.Blog{Pages: []string{"home", "blog", "projects"}, Ascii: ascii} http.Handle("GET /blog", pageBlog) http.Handle("GET /blog/content", pageBlog) - pagePost := &pages.Post{Renderer: renderer, Ascii: ascii} + pagePost := &pages.Post{Pages: []string{"home", "blog", "projects"}, Ascii: ascii} http.Handle("GET /post/{slug}", pagePost) http.Handle("GET /post/{slug}/content", pagePost) - pageProject := &pages.Project{Renderer: renderer, Ascii: ascii} - http.Handle("GET /projects", pageProject) - http.Handle("GET /projects/content", pageProject) - - pageWasm := &pages.Wasm{Renderer: renderer, Ascii: ascii} - http.Handle("GET /wasm", pageWasm) - http.Handle("GET /wasm/content", pageWasm) + pageProjects := &pages.Projects{Pages: []string{"home", "blog", "projects"}, Ascii: ascii} + http.Handle("GET /projects", pageProjects) + http.Handle("GET /projects/content", pageProjects) http.Handle("GET /static/", http.StripPrefix("/static/", staticHandler(http.Dir("static")))) + //http.Handle("GET /cmd/webserver/components/", http.StripPrefix("/cmd/webserver/components", staticHandler(http.Dir("cmd/webserver/components")))) + log.Fatal(http.ListenAndServe(":8080", logRequests)) } func createAscii() [][]string { fileBuffer, err := os.ReadFile("static/images/ascii.txt") if err != nil { - log.Fatalf("Error reading ascii: %v", err) + log.Printf("main: reading ascii (%v)", err) } lines := strings.Split(string(fileBuffer), "\n") diff --git a/cmd/webserver/pages/blog.go b/cmd/webserver/pages/blog.go old mode 100644 new mode 100755 index 1e7de5c..110a0c9 --- a/cmd/webserver/pages/blog.go +++ b/cmd/webserver/pages/blog.go @@ -1,41 +1,85 @@ package pages import ( + "html/template" "log" "net/http" + "strings" + "time" + "personal-website/cmd/webserver/layouts" + + spacer "personal-website/cmd/webserver/components/spacer" + + "personal-website/cmd/webserver/cache" "personal-website/internal" ) type Blog struct { - Renderer *Renderer - Ascii [][]string + Ascii [][]string + PageCurrent string + Pages []string + Posts []internal.Post } func (p *Blog) ServeHTTP(w http.ResponseWriter, r *http.Request) { - type data struct { - Ascii [][]string - CurrentPage string - Posts []internal.Post + name := "blog" + + // Page props + p.PageCurrent = name + + posts, err := cache.Cache.GetPosts() + if err != nil { + log.Printf(name+": failed to get posts (%v)", err) } + p.Posts = posts - name := "blog" + // Page Template + t, err := template.New("").Funcs(template.FuncMap{ + "formatDate": func(date time.Time) string { + return date.Format("20060102") + }, + }).ParseFiles("cmd/webserver/pages/" + name + ".tmpl") + if err != nil { + log.Printf(name+": failed to parse tmpl file (%v)", err) + w.WriteHeader(http.StatusInternalServerError) + return + } - d := data{ + // Layout + if err = (layouts.BaseLayout{ Ascii: p.Ascii, - CurrentPage: name, + PageCurrent: p.PageCurrent, + Pages: p.Pages, + }.Layout(t)); err != nil { + log.Printf(name+": failed to render layout (%v)", err) + w.WriteHeader(http.StatusInternalServerError) + return } - posts, err := Cache.GetPosts() - if err != nil { - log.Printf("Error failed to get posts: %v", err) + // Components + if err = (spacer.Props{}).Component(t); err != nil { + log.Printf(name+": failed to render spacer (%v)", err) + w.WriteHeader(http.StatusInternalServerError) + return } - d.Posts = posts + // Render + if strings.HasSuffix(r.URL.Path, "/content") { + if err = t.ExecuteTemplate(w, "content", p); err != nil { + log.Printf(name+": failed to render content (%v)", err) + w.WriteHeader(http.StatusInternalServerError) + return + } - err = p.Renderer.page(w, r, http.StatusOK, name, d) - if err != nil { - log.Printf("Error rendering %s: %v", name, err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + if err = t.ExecuteTemplate(w, "oob", p); err != nil { + log.Printf(name+": failed to render oob (%v)", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + } else if err = t.ExecuteTemplate(w, "page", p); err != nil { + log.Printf(name+": failed to render page (%v)", err) + w.WriteHeader(http.StatusInternalServerError) + return } } diff --git a/cmd/webserver/pages/blog.tmpl b/cmd/webserver/pages/blog.tmpl index 2e6888d..e8b28bb 100644 --- a/cmd/webserver/pages/blog.tmpl +++ b/cmd/webserver/pages/blog.tmpl @@ -1,15 +1,15 @@ -{{ define "title" }} - Ethan Thoma \ Blog +{{ define "page" }} + {{ template "base-layout" . }} {{ end }} {{ define "content" }} - {{ template "title" . }} + Ethan Thoma \ Blog

    Blog