markdown hotplugging
Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
parent
ab5aae8bd1
commit
00f27f0cbe
4 changed files with 93 additions and 5 deletions
49
page.go
49
page.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/aymerick/raymond"
|
"github.com/aymerick/raymond"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
@ -20,7 +21,7 @@ import (
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Title string
|
Title string
|
||||||
Summary string
|
Summary string
|
||||||
Date string // TODO: better representation? time.Time might cause timezone issues...
|
Time int64 // unix timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page stores the contents of a blog post.
|
// Page stores the contents of a blog post.
|
||||||
|
@ -76,3 +77,49 @@ func newPage(slug string) (*Page, error) {
|
||||||
func (p *Page) render(tpl *raymond.Template) (string, error) {
|
func (p *Page) render(tpl *raymond.Template) (string, error) {
|
||||||
return tpl.Exec(p)
|
return tpl.Exec(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Page) String() string {
|
||||||
|
return p.Slug
|
||||||
|
}
|
||||||
|
|
||||||
|
type pages []*Page
|
||||||
|
|
||||||
|
func insertOrUpdate(ps pages, p *Page) pages {
|
||||||
|
defer sort.Sort(ps)
|
||||||
|
for i, pg := range ps {
|
||||||
|
if pg.Slug == p.Slug {
|
||||||
|
ps[i] = p
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ps = append(ps, p)
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(ps pages, slug string) pages {
|
||||||
|
for i, pg := range ps {
|
||||||
|
if pg.Slug == slug {
|
||||||
|
ps = append(ps[:i], ps[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(ps)
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements sort.Interface
|
||||||
|
func (ps pages) Len() int {
|
||||||
|
return len(ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less implements sort.Interface
|
||||||
|
func (ps pages) Less(i, j int) bool {
|
||||||
|
return ps[i].Metadata.Time > ps[j].Metadata.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap implements sort.Interface
|
||||||
|
func (ps pages) Swap(i, j int) {
|
||||||
|
temp := ps[i]
|
||||||
|
ps[i] = ps[j]
|
||||||
|
ps[j] = temp
|
||||||
|
}
|
||||||
|
|
45
server.go
45
server.go
|
@ -7,8 +7,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aymerick/raymond"
|
"github.com/aymerick/raymond"
|
||||||
"github.com/rjeczalik/notify"
|
"github.com/rjeczalik/notify"
|
||||||
|
@ -16,11 +19,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
pages []*Page
|
|
||||||
staticHandler http.Handler
|
staticHandler http.Handler
|
||||||
|
|
||||||
tplMutex sync.RWMutex
|
tplMutex sync.RWMutex
|
||||||
templates map[string]*raymond.Template
|
templates map[string]*raymond.Template
|
||||||
|
|
||||||
|
pgMutex sync.RWMutex
|
||||||
|
pages
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer() (*server, error) {
|
func newServer() (*server, error) {
|
||||||
|
@ -43,6 +48,27 @@ func newServer() (*server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pagesLn := &listener{
|
||||||
|
folder: "posts/",
|
||||||
|
update: func(file string) error {
|
||||||
|
s.pgMutex.Lock()
|
||||||
|
defer s.pgMutex.Unlock()
|
||||||
|
page, err := newPage(strings.TrimSuffix(file, ".md"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.pages = insertOrUpdate(s.pages, page)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
clean: func(file string) error {
|
||||||
|
s.pgMutex.Lock()
|
||||||
|
defer s.pgMutex.Unlock()
|
||||||
|
s.pages = remove(s.pages, strings.TrimSuffix(file, ".md"))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
go pagesLn.listen()
|
||||||
|
|
||||||
templatesLn := &listener{
|
templatesLn := &listener{
|
||||||
folder: "templates/",
|
folder: "templates/",
|
||||||
update: func(file string) error {
|
update: func(file string) error {
|
||||||
|
@ -99,7 +125,9 @@ func (s *server) refreshPages() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.pages = make([]*Page, 0, len(files))
|
s.pgMutex.Lock()
|
||||||
|
defer s.pgMutex.Unlock()
|
||||||
|
s.pages = make(pages, 0, len(files))
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
filename := f.Name()
|
filename := f.Name()
|
||||||
|
|
||||||
|
@ -112,6 +140,7 @@ func (s *server) refreshPages() error {
|
||||||
log.Printf("Loaded page %s", filename)
|
log.Printf("Loaded page %s", filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sort.Sort(s.pages)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -121,6 +150,14 @@ func loadTemplate(file string) (*raymond.Template, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not parse %s template: %w", file, err)
|
return nil, fmt.Errorf("Could not parse %s template: %w", file, err)
|
||||||
}
|
}
|
||||||
|
tpl.RegisterHelper("datetime", func(timeStr string) string {
|
||||||
|
timestamp, err := strconv.ParseInt(timeStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not parse timestamp '%v', falling back to current time", timeStr)
|
||||||
|
timestamp = time.Now().Unix()
|
||||||
|
}
|
||||||
|
return time.Unix(timestamp, 0).Format("Jan 2 2006, 3:04 PM")
|
||||||
|
})
|
||||||
log.Printf("Loaded template: %s", file)
|
log.Printf("Loaded template: %s", file)
|
||||||
return tpl, nil
|
return tpl, nil
|
||||||
}
|
}
|
||||||
|
@ -303,6 +340,8 @@ func (s *server) router(res http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.pgMutex.RLock()
|
||||||
|
defer s.pgMutex.RUnlock()
|
||||||
for _, p := range s.pages {
|
for _, p := range s.pages {
|
||||||
if p.Slug == slug {
|
if p.Slug == slug {
|
||||||
s.renderPage(p, res, req)
|
s.renderPage(p, res, req)
|
||||||
|
@ -345,6 +384,8 @@ func (s *server) homePage(res http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
var posts string
|
var posts string
|
||||||
|
|
||||||
|
s.pgMutex.RLock()
|
||||||
|
defer s.pgMutex.RUnlock()
|
||||||
for _, p := range s.pages {
|
for _, p := range s.pages {
|
||||||
summary, err := p.render(s.templates["summary"])
|
summary, err := p.render(s.templates["summary"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<article>
|
<article>
|
||||||
<date>{{metadata.date}}</date>
|
<date>{{datetime metadata.time}}</date>
|
||||||
<h1>{{metadata.title}}</h1>
|
<h1>{{metadata.title}}</h1>
|
||||||
<h2>{{metadata.summary}}</h2>
|
<h2>{{metadata.summary}}</h2>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<article>
|
<article>
|
||||||
<date>{{metadata.date}}</date>
|
<date>{{datetime metadata.time}}</date>
|
||||||
<h1>{{metadata.title}}</h1>
|
<h1>{{metadata.title}}</h1>
|
||||||
<h2>{{metadata.summary}}</h2>
|
<h2>{{metadata.summary}}</h2>
|
||||||
<a class="load-content" href="/{{slug}}">Read more →</a>
|
<a class="load-content" href="/{{slug}}">Read more →</a>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue