diff --git a/Dockerfile b/Dockerfile index 653a92c..f57f9c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,8 @@ FROM xena/go:1.12.1 AS build ENV GOPROXY https://cache.greedo.xeserv.us COPY . /site WORKDIR /site -RUN CGO_ENABLED=0 go test ./... -RUN CGO_ENABLED=0 GOBIN=/root go install ./cmd/site +RUN CGO_ENABLED=0 go test -v ./... +RUN CGO_ENABLED=0 GOBIN=/root go install -v ./cmd/site FROM xena/alpine EXPOSE 5000 @@ -13,6 +13,7 @@ COPY --from=build /root/site . COPY ./static /site/static COPY ./templates /site/templates COPY ./blog /site/blog +COPY ./talks /site/talks COPY ./css /site/css COPY ./app /app COPY ./app.json . diff --git a/cmd/site/html.go b/cmd/site/html.go index 9f208af..b3fa484 100644 --- a/cmd/site/html.go +++ b/cmd/site/html.go @@ -66,9 +66,53 @@ func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.H var postView = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "posts_viewed", - Help: "The number of views per post", + Help: "The number of views per post or talk", }, []string{"base"}) +func (s *Site) showTalk(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/talks/" { + http.Redirect(w, r, "/talks", http.StatusSeeOther) + return + } + + cmp := r.URL.Path[1:] + var p blog.Post + var found bool + for _, pst := range s.Talks { + if pst.Link == cmp { + p = pst + found = true + } + } + + if !found { + w.WriteHeader(http.StatusNotFound) + s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r) + return + } + + const dateFormat = `2006-01-02` + h := s.renderTemplatePage("talkpost.html", struct { + Title string + Link string + BodyHTML template.HTML + Date string + SlidesLink string + }{ + Title: p.Title, + Link: p.Link, + BodyHTML: p.BodyHTML, + Date: p.Date.Format(dateFormat), + SlidesLink: p.SlidesLink, + }) + + if h == nil { + panic("how did we get here?") + } + + h.ServeHTTP(w, r) +} + func (s *Site) showPost(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/blog/" { http.Redirect(w, r, "/blog", http.StatusSeeOther) diff --git a/cmd/site/main.go b/cmd/site/main.go index b562017..055320e 100644 --- a/cmd/site/main.go +++ b/cmd/site/main.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "net/http" "os" + "sort" "time" "christine.website/internal/blog" @@ -54,6 +55,7 @@ func main() { // Site is the parent object for https://christine.website's backend. type Site struct { Posts blog.Posts + Talks blog.Posts Resume template.HTML rssFeed *feeds.Feed @@ -134,12 +136,24 @@ func Build() (*Site, error) { xffmw: xffmw, } - posts, err := blog.LoadPosts("./blog/") + posts, err := blog.LoadPosts("./blog/", "blog") if err != nil { return nil, err } s.Posts = posts + talks, err := blog.LoadPosts("./talks", "talks") + if err != nil { + return nil, err + } + s.Talks = talks + + var everything blog.Posts + everything = append(everything, posts...) + everything = append(everything, talks...) + + sort.Sort(sort.Reverse(everything)) + resumeData, err := ioutil.ReadFile("./static/resume/resume.md") if err != nil { return nil, err @@ -147,7 +161,7 @@ func Build() (*Site, error) { s.Resume = template.HTML(blackfriday.Run(resumeData)) - for _, item := range s.Posts { + for _, item := range everything { s.rssFeed.Items = append(s.rssFeed.Items, &feeds.Item{ Title: item.Title, Link: &feeds.Link{Href: "https://christine.website/" + item.Link}, @@ -184,11 +198,13 @@ func Build() (*Site, error) { s.mux.Handle("/metrics", promhttp.Handler()) s.mux.Handle("/resume", middleware.Metrics("resume", s.renderTemplatePage("resume.html", s.Resume))) s.mux.Handle("/blog", middleware.Metrics("blog", s.renderTemplatePage("blogindex.html", s.Posts))) + s.mux.Handle("/talks", middleware.Metrics("talks", s.renderTemplatePage("talkindex.html", s.Talks))) s.mux.Handle("/contact", middleware.Metrics("contact", s.renderTemplatePage("contact.html", nil))) s.mux.Handle("/blog.rss", middleware.Metrics("blog.rss", http.HandlerFunc(s.createFeed))) s.mux.Handle("/blog.atom", middleware.Metrics("blog.atom", http.HandlerFunc(s.createAtom))) s.mux.Handle("/blog.json", middleware.Metrics("blog.json", http.HandlerFunc(s.createJSONFeed))) s.mux.Handle("/blog/", middleware.Metrics("blogpost", http.HandlerFunc(s.showPost))) + s.mux.Handle("/talks/", middleware.Metrics("talks", http.HandlerFunc(s.showTalk))) s.mux.Handle("/css/", http.FileServer(http.Dir("."))) s.mux.Handle("/static/", http.FileServer(http.Dir("."))) s.mux.HandleFunc("/sw.js", func(w http.ResponseWriter, r *http.Request) { diff --git a/internal/blog/blog.go b/internal/blog/blog.go index a73f27b..eac5d64 100644 --- a/internal/blog/blog.go +++ b/internal/blog/blog.go @@ -20,6 +20,7 @@ type Post struct { Summary string `json:"summary,omitifempty"` Body string `json:"-"` BodyHTML template.HTML `json:"body"` + SlidesLink string `json:"slides_link"` Date time.Time DateString string `json:"date"` } @@ -37,10 +38,11 @@ func (p Posts) Less(i, j int) bool { func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // LoadPosts loads posts for a given directory. -func LoadPosts(path string) (Posts, error) { +func LoadPosts(path string, prepend string) (Posts, error) { type postFM struct { - Title string - Date string + Title string + Date string + SlidesLink string `yaml:"slides_link"` } var result Posts @@ -78,13 +80,17 @@ func LoadPosts(path string) (Posts, error) { return err } + fname := filepath.Base(path) + fname = strings.TrimSuffix(fname, filepath.Ext(fname)) + p := Post{ Title: fm.Title, Date: date, DateString: fm.Date, - Link: strings.Split(path, ".")[0], + Link: filepath.Join(prepend, fname), Body: string(remaining), BodyHTML: template.HTML(output), + SlidesLink: fm.SlidesLink, } result = append(result, p) diff --git a/internal/blog/blog_test.go b/internal/blog/blog_test.go index b073880..8a446cd 100644 --- a/internal/blog/blog_test.go +++ b/internal/blog/blog_test.go @@ -5,8 +5,44 @@ import ( ) func TestLoadPosts(t *testing.T) { - _, err := LoadPosts("../../blog") + posts, err := LoadPosts("../../blog", "blog") if err != nil { t.Fatal(err) } + + for _, post := range posts { + t.Run(post.Link, post.test) + } +} + +func TestLoadTalks(t *testing.T) { + talks, err := LoadPosts("../../talks", "talks") + if err != nil { + t.Fatal(err) + } + + for _, talk := range talks { + t.Run(talk.Link, talk.test) + if talk.SlidesLink == "" { + t.Errorf("talk %s (%s) doesn't have a slides link", talk.Title, talk.DateString) + } + } +} + +func (p Post) test(t *testing.T) { + if p.Title == "" { + t.Error("no post title") + } + + if p.DateString == "" { + t.Error("no date") + } + + if p.Link == "" { + t.Error("no link") + } + + if p.Body == "" { + t.Error("no body") + } } diff --git a/static/js/sw.js b/static/js/sw.js index f258902..deaa947 100755 --- a/static/js/sw.js +++ b/static/js/sw.js @@ -5,11 +5,13 @@ self.addEventListener('install', function(event) { event.waitUntil(preLoad()); }); +const cacheName = "cache-2019-05-21"; + var preLoad = function(){ console.log('[PWA Builder] Install Event processing'); - return caches.open('offline').then(function(cache) { + return caches.open(cacheName).then(function(cache) { console.log('[PWA Builder] Cached index and offline page during Install'); - return cache.addAll(['/blog/', '/blog', '/', '/contact', '/resume']); + return cache.addAll(['/blog/', '/blog', '/', '/contact', '/resume', '/talks']); }); }; @@ -37,7 +39,7 @@ var checkResponse = function(request){ }; var addToCache = function(request){ - return caches.open('offline').then(function (cache) { + return caches.open(cacheName).then(function (cache) { return fetch(request).then(function (response) { console.log('[PWA Builder] add page to offline: ' + response.url); return cache.put(request, response); @@ -46,7 +48,7 @@ var addToCache = function(request){ }; var returnFromCache = function(request){ - return caches.open('offline').then(function (cache) { + return caches.open(cacheName).then(function (cache) { return cache.match(request).then(function (matching) { if(!matching || matching.status == 404) { return cache.match('offline.html'); diff --git a/static/talks/irc-why-it-failed.pdf b/static/talks/irc-why-it-failed.pdf new file mode 100644 index 0000000..6d81031 Binary files /dev/null and b/static/talks/irc-why-it-failed.pdf differ diff --git a/static/talks/thinking-different.pdf b/static/talks/thinking-different.pdf new file mode 100644 index 0000000..fd225ee Binary files /dev/null and b/static/talks/thinking-different.pdf differ diff --git a/talks/irc-why-it-failed-2018-05-17.markdown b/talks/irc-why-it-failed-2018-05-17.markdown new file mode 100644 index 0000000..b9dcc80 --- /dev/null +++ b/talks/irc-why-it-failed-2018-05-17.markdown @@ -0,0 +1,13 @@ +--- +title: "IRC: Why it Failed" +date: 2018-05-17 +slides_link: /static/talks/irc-why-it-failed.pdf +--- + +# IRC: Why it Failed + +A brief discussion of the IRC protocol and why it has failed in today's internet. + +Originally presented at the Pony Developers panel at Everfree Northwest, 2018. + +Please check out [pony.dev](https://pony.dev) for more information. diff --git a/talks/thinking-different-2018-11-03.markdown b/talks/thinking-different-2018-11-03.markdown new file mode 100644 index 0000000..29ccd5e --- /dev/null +++ b/talks/thinking-different-2018-11-03.markdown @@ -0,0 +1,11 @@ +--- +title: "Thinking Different" +date: 2018-11-03 +slides_link: /static/talks/thinking-different.pdf +--- + +# Thinking Different + +A look over [ilo Kesi](https://github.com/Xe/x/tree/master/discord/ilo-kesi), a chatbot of mine that parses commands from the grammar of [Toki Pona](http://tokipona.org). + +Originally presented privately at an internal work get-together for Heroku. diff --git a/templates/base.html b/templates/base.html index 1cbaff3..3fdc591 100644 --- a/templates/base.html +++ b/templates/base.html @@ -59,7 +59,7 @@ {{ template "scripts" . }}
Christine Dodrill - Blog - Contact - Resume | GraphViz - When Then Zen
+Christine Dodrill - Blog - Contact - Resume - Talks | GraphViz - When Then Zen
Here is a link to all of the talks I have done at conferences. Each of these will have links to the slides (PDF) as well as some brief information about them.
+ +If you have a compatible reader, be sure to check out my RSS Feed for automatic updates. Also check out the JSONFeed.
+ ++