diff --git a/cmd/christine.website/hash.go b/cmd/christine.website/hash.go new file mode 100644 index 0000000..ed6112c --- /dev/null +++ b/cmd/christine.website/hash.go @@ -0,0 +1,14 @@ +package main + +import ( + "crypto/md5" + "fmt" +) + +// Hash is a simple wrapper around the MD5 algorithm implementation in the +// Go standard library. It takes in data and a salt and returns the hashed +// representation. +func Hash(data string, salt string) string { + output := md5.Sum([]byte(data + salt)) + return fmt.Sprintf("%x", output) +} diff --git a/cmd/christine.website/html.go b/cmd/christine.website/html.go new file mode 100644 index 0000000..a01e6a5 --- /dev/null +++ b/cmd/christine.website/html.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "html/template" + "net/http" + "time" + + "github.com/Xe/ln" +) + +func logTemplateTime(name string, from time.Time) { + now := time.Now() + ln.Log(ln.F{"action": "template_rendered", "dur": now.Sub(from).String()}) +} + +func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer logTemplateTime(templateFname, time.Now()) + s.tlock.RLock() + defer s.tlock.RUnlock() + + var t *template.Template + var err error + + if s.templates[templateFname] == nil { + t, err = template.ParseFiles("templates/base.html", "templates/"+templateFname) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + ln.Error(err, ln.F{"action": "renderTemplatePage", "page": templateFname}) + fmt.Fprintf(w, "error: %v", err) + } + + ln.Log(ln.F{"action": "loaded_new_template", "fname": templateFname}) + + s.tlock.RUnlock() + s.tlock.Lock() + s.templates[templateFname] = t + s.tlock.Unlock() + s.tlock.RLock() + } else { + t = s.templates[templateFname] + } + + err = t.Execute(w, data) + if err != nil { + panic(err) + } + }) +} + +func (s *Site) showPost(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/blog/" { + http.Redirect(w, r, "/blog", http.StatusSeeOther) + return + } + + var p *Post + for _, pst := range s.Posts { + if pst.Link == r.RequestURI[1:] { + p = pst + } + } + + if p == nil { + w.WriteHeader(http.StatusNotFound) + s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r) + return + } + + s.renderTemplatePage("blogpost.html", p).ServeHTTP(w, r) +} diff --git a/cmd/christine.website/main.go b/cmd/christine.website/main.go new file mode 100644 index 0000000..cffebd1 --- /dev/null +++ b/cmd/christine.website/main.go @@ -0,0 +1,191 @@ +package main + +import ( + "html/template" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/Xe/jsonfeed" + "github.com/Xe/ln" + "github.com/gorilla/feeds" + "github.com/russross/blackfriday" + "github.com/tj/front" +) + +var port = os.Getenv("PORT") + +func main() { + if port == "" { + port = "29384" + } + + s, err := Build() + if err != nil { + ln.Fatal(ln.F{"err": err, "action": "Build"}) + } + + http.ListenAndServe(":"+port, s) +} + +// Site is the parent object for https://christine.website's backend. +type Site struct { + Posts Posts + Resume template.HTML + + rssFeed *feeds.Feed + jsonFeed *jsonfeed.Feed + + mux *http.ServeMux + + templates map[string]*template.Template + tlock sync.RWMutex +} + +func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ln.Log(ln.F{"action": "Site.ServeHTTP"}) + s.mux.ServeHTTP(w, r) +} + +// Build creates a new Site instance or fails. +func Build() (*Site, error) { + type postFM struct { + Title string + Date string + } + + s := &Site{ + rssFeed: &feeds.Feed{ + Title: "Christine Dodrill's Blog", + Link: &feeds.Link{Href: "https://christine.website/blog"}, + Description: "My blog posts and rants about various technology things.", + Author: &feeds.Author{Name: "Christine Dodrill", Email: "me@christine.website"}, + Created: bootTime, + Copyright: "This work is copyright Christine Dodrill. My viewpoints are my own and not the view of any employer past, current or future.", + }, + jsonFeed: &jsonfeed.Feed{ + Version: jsonfeed.CurrentVersion, + Title: "Christine Dodrill's Blog", + HomePageURL: "https://christine.website", + FeedURL: "https://christine.website/blog.json", + Description: "My blog posts and rants about various technology things.", + UserComment: "This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1", + Icon: icon, + Favicon: icon, + Author: jsonfeed.Author{ + Name: "Christine Dodrill", + Avatar: icon, + }, + }, + mux: http.NewServeMux(), + templates: map[string]*template.Template{}, + } + + err := filepath.Walk("./blog/", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + fin, err := os.Open(path) + if err != nil { + return err + } + defer fin.Close() + + content, err := ioutil.ReadAll(fin) + if err != nil { + return err + } + + var fm postFM + remaining, err := front.Unmarshal(content, &fm) + if err != nil { + return err + } + + output := blackfriday.MarkdownCommon(remaining) + + p := &Post{ + Title: fm.Title, + Date: fm.Date, + Link: strings.Split(path, ".")[0], + Body: string(remaining), + BodyHTML: template.HTML(output), + } + + s.Posts = append(s.Posts, p) + + return nil + }) + if err != nil { + return nil, err + } + + sort.Sort(sort.Reverse(s.Posts)) + + resume, err := ioutil.ReadFile("./static/resume/resume.md") + if err != nil { + panic(err) + } + + s.Resume = template.HTML(blackfriday.MarkdownCommon(resume)) + + for _, item := range s.Posts { + itime, _ := time.Parse("2006-01-02", item.Date) + s.rssFeed.Items = append(s.rssFeed.Items, &feeds.Item{ + Title: item.Title, + Link: &feeds.Link{Href: "https://christine.website/" + item.Link}, + Description: item.Summary, + Created: itime, + }) + + s.jsonFeed.Items = append(s.jsonFeed.Items, jsonfeed.Item{ + ID: "https://christine.website/" + item.Link, + URL: "https://christine.website/" + item.Link, + Title: item.Title, + DatePublished: itime, + }) + } + + // Add HTTP routes here + s.mux.Handle("/", s.renderTemplatePage("index.html", nil)) + s.mux.Handle("/resume", s.renderTemplatePage("resume.html", s.Resume)) + s.mux.Handle("/blog", s.renderTemplatePage("blogindex.html", s.Posts)) + s.mux.HandleFunc("/blog/", s.showPost) + s.mux.Handle("/static/", http.FileServer(http.Dir("."))) + + return s, nil +} + +const icon = "https://christine.website/static/img/avatar.png" + +// Post is a single blogpost. +type Post struct { + Title string `json:"title"` + Link string `json:"link"` + Summary string `json:"summary,omitifempty"` + Body string `json:"-"` + BodyHTML template.HTML `json:"body"` + Date string `json:"date"` +} + +// Posts implements sort.Interface for a slice of Post objects. +type Posts []*Post + +func (p Posts) Len() int { return len(p) } +func (p Posts) Less(i, j int) bool { + iDate, _ := time.Parse("2006-01-02", p[i].Date) + jDate, _ := time.Parse("2006-01-02", p[j].Date) + + return iDate.Unix() < jDate.Unix() +} +func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/cmd/christine.website/rss.go b/cmd/christine.website/rss.go new file mode 100644 index 0000000..737bb70 --- /dev/null +++ b/cmd/christine.website/rss.go @@ -0,0 +1,45 @@ +package main + +import ( + "net/http" + "time" + + "github.com/Xe/ln" +) + +var bootTime = time.Now() + +// IncrediblySecureSalt ******* +const IncrediblySecureSalt = "hunter2" + +func (s *Site) createFeed(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/rss+xml") + w.Header().Set("ETag", Hash(bootTime.String(), IncrediblySecureSalt)) + + err := s.rssFeed.WriteRss(w) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + ln.Error(err, ln.F{ + "remote_addr": r.RemoteAddr, + "action": "generating_rss", + "uri": r.RequestURI, + "host": r.Host, + }) + } +} + +func (s *Site) createAtom(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/atom+xml") + w.Header().Set("ETag", Hash(bootTime.String(), IncrediblySecureSalt)) + + err := s.rssFeed.WriteAtom(w) + if err != nil { + http.Error(w, "Internal server error", http.StatusInternalServerError) + ln.Error(err, ln.F{ + "remote_addr": r.RemoteAddr, + "action": "generating_rss", + "uri": r.RequestURI, + "host": r.Host, + }) + } +} diff --git a/static/css/hack.css b/static/css/hack.css new file mode 100644 index 0000000..661a838 --- /dev/null +++ b/static/css/hack.css @@ -0,0 +1 @@ +html{font-size:12px}*{box-sizing:border-box;text-rendering:geometricPrecision}body{font-size:1rem;line-height:1.5rem;margin:0;font-family:Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif;word-wrap:break-word}h1,h2,h3,h4,h5,h6{line-height:1.3em}fieldset{border:none;padding:0;margin:0}pre{padding:2rem;margin:1.75rem 0;background-color:#fff;border:1px solid #ccc;overflow:auto}code[class*=language-],pre[class*=language-],pre code{font-weight:100;text-shadow:none;margin:1.75rem 0}a{cursor:pointer;color:#ff2e88;text-decoration:none;border-bottom:1px solid #ff2e88}a:hover{background-color:#ff2e88;color:#fff}.grid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap}.grid.\-top{-ms-flex-align:start;align-items:flex-start}.grid.\-middle{-ms-flex-align:center;align-items:center}.grid.\-bottom{-ms-flex-align:end;align-items:flex-end}.grid.\-stretch{-ms-flex-align:stretch;align-items:stretch}.grid.\-baseline{-ms-flex-align:baseline;align-items:baseline}.grid.\-left{-ms-flex-pack:start;justify-content:flex-start}.grid.\-center{-ms-flex-pack:center;justify-content:center}.grid.\-right{-ms-flex-pack:end;justify-content:flex-end}.grid.\-between{-ms-flex-pack:justify;justify-content:space-between}.grid.\-around{-ms-flex-pack:distribute;justify-content:space-around}.cell{-ms-flex:1;flex:1;box-sizing:border-box}@media screen and (min-width:768px){.cell.\-1of12{-ms-flex:0 0 8.33333%;flex:0 0 8.33333%}.cell.\-2of12{-ms-flex:0 0 16.66667%;flex:0 0 16.66667%}.cell.\-3of12{-ms-flex:0 0 25%;flex:0 0 25%}.cell.\-4of12{-ms-flex:0 0 33.33333%;flex:0 0 33.33333%}.cell.\-5of12{-ms-flex:0 0 41.66667%;flex:0 0 41.66667%}.cell.\-6of12{-ms-flex:0 0 50%;flex:0 0 50%}.cell.\-7of12{-ms-flex:0 0 58.33333%;flex:0 0 58.33333%}.cell.\-8of12{-ms-flex:0 0 66.66667%;flex:0 0 66.66667%}.cell.\-9of12{-ms-flex:0 0 75%;flex:0 0 75%}.cell.\-10of12{-ms-flex:0 0 83.33333%;flex:0 0 83.33333%}.cell.\-11of12{-ms-flex:0 0 91.66667%;flex:0 0 91.66667%}}@media screen and (max-width:768px){.grid{-ms-flex-direction:column;flex-direction:column}.cell{-ms-flex:0 0 auto;flex:0 0 auto}}.hack,.hack blockquote,.hack code,.hack em,.hack h1,.hack h2,.hack h3,.hack h4,.hack h5,.hack h6,.hack strong{font-size:1rem;font-style:normal;font-family:Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif}.hack blockquote,.hack code,.hack em,.hack strong{line-height:20px}.hack blockquote,.hack code,.hack footer,.hack h1,.hack h2,.hack h3,.hack h4,.hack h5,.hack h6,.hack header,.hack li,.hack ol,.hack p,.hack section,.hack ul{float:none;margin:0;padding:0}.hack blockquote,.hack h1,.hack ol,.hack p,.hack ul{margin-top:20px;margin-bottom:20px}.hack h1{position:relative;display:inline-block;display:table-cell;padding:20px 0 30px;margin:0;overflow:hidden}.hack h1:after{content:"====================================================================================================";position:absolute;bottom:10px;left:0}.hack h1+*{margin-top:0}.hack h2,.hack h3,.hack h4,.hack h5,.hack h6{position:relative;margin-bottom:1.75rem}.hack h2:before,.hack h3:before,.hack h4:before,.hack h5:before,.hack h6:before{display:inline}.hack h2:before{content:"## "}.hack h3:before{content:"### "}.hack h4:before{content:"#### "}.hack h5:before{content:"##### "}.hack h6:before{content:"###### "}.hack li{position:relative;display:block;padding-left:20px}.hack li:after{position:absolute;top:0;left:0}.hack ul>li:after{content:"-"}.hack ol{counter-reset:a}.hack ol>li:after{content:counter(a) ".";counter-increment:a}.hack blockquote{position:relative;padding-left:17px;padding-left:2ch;overflow:hidden}.hack blockquote:after{content:">\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>\A>";white-space:pre;position:absolute;top:0;left:0;line-height:20px}.hack em:after,.hack em:before{content:"*";display:inline}.hack pre code:after,.hack pre code:before{content:""}.hack code{font-weight:700}.hack code:after,.hack code:before{content:"`";display:inline}.hack hr{position:relative;height:20px;overflow:hidden;border:0;margin:20px 0}.hack hr:after{content:"----------------------------------------------------------------------------------------------------";position:absolute;top:0;left:0;line-height:20px;width:100%;word-wrap:break-word}@-moz-document url-prefix(){.hack h1{display:block}}.hack-ones ol>li:after{content:"1."}p{margin:0 0 1.75rem}.container{max-width:70rem}.container,.container-fluid{margin:0 auto;padding:0 1rem}.inner{padding:1rem}.inner2x{padding:2rem}.pull-left{float:left}.pull-right{float:right}.progress-bar{height:8px;opacity:.8;background-color:#ccc;margin-top:12px}.progress-bar.progress-bar-show-percent{margin-top:38px}.progress-bar-filled{background-color:gray;height:100%;transition:width .3s ease;position:relative;width:0}.progress-bar-filled:before{content:"";border:6px solid transparent;border-top-color:gray;position:absolute;top:-12px;right:-6px}.progress-bar-filled:after{color:gray;content:attr(data-filled);display:block;font-size:12px;white-space:nowrap;position:absolute;border:6px solid transparent;top:-38px;right:0;-ms-transform:translateX(50%);transform:translateX(50%)}table{width:100%;border-collapse:collapse;margin:1.75rem 0;color:#778087}table td,table th{vertical-align:top;border:1px solid #ccc;line-height:15px;padding:10px}table thead th{font-size:10px}table tbody td:first-child{font-weight:700;color:#333}.form{width:30rem}.form-group{margin-bottom:1.75rem;overflow:auto}.form-group label{border-bottom:2px solid #ccc;color:#333;width:10rem;display:inline-block;height:38px;line-height:38px;padding:0;float:left;position:relative}.form-group.form-success label{color:#4caf50!important;border-color:#4caf50!important}.form-group.form-warning label{color:#ff9800!important;border-color:#ff9800!important}.form-group.form-error label{color:#f44336!important;border-color:#f44336!important}.form-control{outline:none;border:none;border-bottom:2px solid #ccc;padding:.5rem 0;width:20rem;height:38px;background-color:transparent}.form-control:focus{border-color:#555}.form-group.form-textarea label:after{position:absolute;content:"";width:2px;background-color:#fff;right:-2px;top:0;bottom:0}textarea.form-control{height:auto;resize:none;padding:1rem 0;border-bottom:2px solid #ccc;border-left:2px solid #ccc;padding:.5rem}select.form-control{border-radius:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none}.help-block{color:#999;margin-top:.5rem}.form-actions{margin-bottom:1.75rem}.btn{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;cursor:pointer;outline:none;padding:.65rem 2rem;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;z-index:1}.btn:active{box-shadow:inset 0 1px 3px rgba(0,0,0,.12)}.btn.btn-ghost{border-color:#757575;color:#757575;background-color:transparent}.btn.btn-ghost:focus,.btn.btn-ghost:hover{border-color:#424242;color:#424242;z-index:2}.btn.btn-ghost:hover{background-color:transparent}.btn-block{width:100%;display:-ms-flexbox;display:flex}.btn-default{color:#fff;background-color:#e0e0e0;border:1px solid #e0e0e0;color:#333}.btn-default:focus:not(.btn-ghost),.btn-default:hover{background-color:#dcdcdc;border-color:#dcdcdc}.btn-success{color:#fff;background-color:#4caf50;border:1px solid #4caf50}.btn-success:focus:not(.btn-ghost),.btn-success:hover{background-color:#43a047;border-color:#43a047}.btn-success.btn-ghost{border-color:#4caf50;color:#4caf50}.btn-success.btn-ghost:focus,.btn-success.btn-ghost:hover{border-color:#388e3c;color:#388e3c;z-index:2}.btn-error{color:#fff;background-color:#f44336;border:1px solid #f44336}.btn-error:focus:not(.btn-ghost),.btn-error:hover{background-color:#e53935;border-color:#e53935}.btn-error.btn-ghost{border-color:#f44336;color:#f44336}.btn-error.btn-ghost:focus,.btn-error.btn-ghost:hover{border-color:#d32f2f;color:#d32f2f;z-index:2}.btn-warning{color:#fff;background-color:#ff9800;border:1px solid #ff9800}.btn-warning:focus:not(.btn-ghost),.btn-warning:hover{background-color:#fb8c00;border-color:#fb8c00}.btn-warning.btn-ghost{border-color:#ff9800;color:#ff9800}.btn-warning.btn-ghost:focus,.btn-warning.btn-ghost:hover{border-color:#f57c00;color:#f57c00;z-index:2}.btn-info{color:#fff;background-color:#00bcd4;border:1px solid #00bcd4}.btn-info:focus:not(.btn-ghost),.btn-info:hover{background-color:#00acc1;border-color:#00acc1}.btn-info.btn-ghost{border-color:#00bcd4;color:#00bcd4}.btn-info.btn-ghost:focus,.btn-info.btn-ghost:hover{border-color:#0097a7;color:#0097a7;z-index:2}.btn-primary{color:#fff;background-color:#2196f3;border:1px solid #2196f3}.btn-primary:focus:not(.btn-ghost),.btn-primary:hover{background-color:#1e88e5;border-color:#1e88e5}.btn-primary.btn-ghost{border-color:#2196f3;color:#2196f3}.btn-primary.btn-ghost:focus,.btn-primary.btn-ghost:hover{border-color:#1976d2;color:#1976d2;z-index:2}.btn-group{overflow:auto}.btn-group .btn{float:left}.btn-group .btn-ghost:not(:first-child){margin-left:-1px}.card{border:1px solid #ccc}.card .card-header{color:#333;text-align:center;background-color:#ddd;padding:.5rem 0}.alert{color:#ccc;padding:1rem;border:1px solid #ccc;margin-bottom:1.75rem}.alert-success{color:#4caf50;border-color:#4caf50}.alert-error{color:#f44336;border-color:#f44336}.alert-info{color:#00bcd4;border-color:#00bcd4}.alert-warning{color:#ff9800;border-color:#ff9800}.media:not(:last-child){margin-bottom:1.25rem}.media-left{padding-right:1rem}.media-left,.media-right{display:table-cell;vertical-align:top}.media-right{padding-left:1rem}.media-body{display:table-cell;vertical-align:top}.media-heading{font-size:1.16667rem;font-weight:700}.media-content{margin-top:.3rem}.avatarholder,.placeholder{background-color:#f0f0f0;text-align:center;color:#b9b9b9;font-size:1rem;border:1px solid #f0f0f0}.avatarholder{width:48px;height:48px;line-height:46px;font-size:2rem;background-size:cover;background-position:50%;background-repeat:no-repeat}.avatarholder.rounded{border-radius:33px}.loading{display:inline-block;content:" ";height:20px;width:20px;margin:0 .5rem;animation:a .6s infinite linear;border:2px solid #e91e63;border-right-color:transparent;border-radius:50%}.btn .loading{margin-bottom:0;width:14px;height:14px}.btn div.loading{float:left}.alert .loading{margin-bottom:-5px}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.menu{width:100%}.menu .menu-item{display:block;color:#616161;border-color:#616161}.menu .menu-item.active,.menu .menu-item:hover{color:#000;border-color:#000;background-color:transparent}@media screen and (max-width:768px){.form-group label{display:block;border-bottom:none;width:100%}.form-group.form-textarea label:after{display:none}.form-control{width:100%}textarea.form-control{border-left:none;padding:.5rem 0}pre::-webkit-scrollbar{height:3px}}@media screen and (max-width:480px){.form{width:100%}} \ No newline at end of file diff --git a/static/css/solarized-dark.css b/static/css/solarized-dark.css new file mode 100644 index 0000000..4c29736 --- /dev/null +++ b/static/css/solarized-dark.css @@ -0,0 +1 @@ +.solarized-dark{background-color:#073642;color:#78909c}.solarized-dark h1,.solarized-dark h2,.solarized-dark h3,.solarized-dark h4,.solarized-dark h5,.solarized-dark h6{color:#1e88e5}.solarized-dark h1 a,.solarized-dark h2 a,.solarized-dark h3 a,.solarized-dark h4 a,.solarized-dark h5 a,.solarized-dark h6 a{color:#1e88e5;border-bottom-color:#1e88e5}.solarized-dark h1 a:hover,.solarized-dark h2 a:hover,.solarized-dark h3 a:hover,.solarized-dark h4 a:hover,.solarized-dark h5 a:hover,.solarized-dark h6 a:hover{background-color:#1e88e5;color:#fff}.solarized-dark pre{background-color:#073642;padding:0;border:none}.solarized-dark pre code{color:#009688}.solarized-dark h1 a,.solarized-dark h2 a,.solarized-dark h3 a,.solarized-dark h4 a,.solarized-dark h5 a{color:#78909c}.solarized-dark code,.solarized-dark strong{color:#90a4ae}.solarized-dark code{font-weight:100}.solarized-dark .progress-bar-filled{background-color:#558b2f}.solarized-dark .progress-bar-filled:after,.solarized-dark .progress-bar-filled:before{color:#90a4ae}.solarized-dark table{color:#78909c}.solarized-dark table td,.solarized-dark table th{border-color:#b0bec5}.solarized-dark table tbody td:first-child{color:#b0bec5}.solarized-dark .form-group label{color:#78909c;border-color:#90a4ae}.solarized-dark .form-group.form-textarea label:after{background-color:#073642}.solarized-dark .form-control{color:#78909c;border-color:#90a4ae}.solarized-dark .form-control:focus{border-color:#cfd8dc;color:#cfd8dc}.solarized-dark textarea.form-control{color:#78909c}.solarized-dark .card{border-color:#90a4ae}.solarized-dark .card .card-header{background-color:transparent;color:#78909c;border-bottom:1px solid #90a4ae}.solarized-dark .btn.btn-ghost.btn-default{border-color:#607d8b;color:#607d8b}.solarized-dark .btn.btn-ghost.btn-default:focus,.solarized-dark .btn.btn-ghost.btn-default:hover{border-color:#90a4ae;color:#90a4ae;z-index:1}.solarized-dark .btn.btn-ghost.btn-default:focus,.solarized-dark .btn.btn-ghost.btn-default:hover{border-color:#e0e0e0;color:#e0e0e0}.solarized-dark .btn.btn-ghost.btn-primary:focus,.solarized-dark .btn.btn-ghost.btn-primary:hover{border-color:#64b5f6;color:#64b5f6}.solarized-dark .btn.btn-ghost.btn-success:focus,.solarized-dark .btn.btn-ghost.btn-success:hover{border-color:#81c784;color:#81c784}.solarized-dark .btn.btn-ghost.btn-info:focus,.solarized-dark .btn.btn-ghost.btn-info:hover{border-color:#4dd0e1;color:#4dd0e1}.solarized-dark .btn.btn-ghost.btn-error:focus,.solarized-dark .btn.btn-ghost.btn-error:hover{border-color:#e57373;color:#e57373}.solarized-dark .btn.btn-ghost.btn-warning:focus,.solarized-dark .btn.btn-ghost.btn-warning:hover{border-color:#ffb74d;color:#ffb74d}.solarized-dark .avatarholder,.solarized-dark .placeholder{background-color:transparent;border-color:#90a4ae}.solarized-dark .menu .menu-item{color:#78909c;border-color:#90a4ae}.solarized-dark .menu .menu-item.active,.solarized-dark .menu .menu-item:hover{color:#fff;border-color:#78909c} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..4099bcd --- /dev/null +++ b/templates/base.html @@ -0,0 +1,45 @@ + + + {{ template "title" . }} + + + + {{ template "styles" . }} + + + {{ template "scripts" . }} +
+
+

Christine Dodrill - Blog - Contact - Resume

+
+ {{ template "content" . }} + +
+ + + +{{ define "scripts" }}{{ end }} +{{ define "styles" }}{{ end }} diff --git a/templates/blogindex.html b/templates/blogindex.html new file mode 100644 index 0000000..107e7e1 --- /dev/null +++ b/templates/blogindex.html @@ -0,0 +1,22 @@ +{{ define "title" }} +Blog - Christine Dodrill + + +{{ end }} + +{{ define "content" }} +
+ {{ range . }} +
+
{{ .Title }}
+
+

Posted on {{ .Date }}
Read Post

+
+
+ {{ end }} +
+{{ end }} diff --git a/templates/blogpost.html b/templates/blogpost.html new file mode 100644 index 0000000..731ea1a --- /dev/null +++ b/templates/blogpost.html @@ -0,0 +1,11 @@ +{{ define "title" }} +{{ .Title }} - Christine Dodrill +{{ end }} + +{{ define "content" }} +{{ .BodyHTML }} + +
+ +Content posted on {{ .Date }}, opinions and preferences of the author may have changed since then. +{{ end }} diff --git a/templates/error.html b/templates/error.html new file mode 100644 index 0000000..cb47e83 --- /dev/null +++ b/templates/error.html @@ -0,0 +1,9 @@ +{{ define "title" }} +Error - Christine Dodrill +{{ end }} + +{{ define "content" }} +
+{{ . }}
+
+{{ end }} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..5bff551 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,30 @@ +{{ define "title" }}Christine Dodrill{{ end }} + +{{ define "content" }} +
+
+ +
+ Contact Me +
+
+

Christine Dodrill

+

Web and Backend Services Devops Specialist

+
Skills
+ + +
Highlighted Projects
+ +
+
+{{ end }} diff --git a/templates/resume.html b/templates/resume.html new file mode 100644 index 0000000..9789361 --- /dev/null +++ b/templates/resume.html @@ -0,0 +1,9 @@ +{{ define "title" }}Resume - Christine Dodrill{{ end }} + +{{ define "content" }} +{{ . }} + +
+ +Plain-text version of this resume here +{{ end }}