~evanj/evanjon.es

0a6753b61f38bf8def40586d70f86818a4a8da5d — Evan M Jones 1 year, 4 months ago 420d270
Feat(*): Redesign. Hooking into new cms.evanjon.es updates.
44 files changed, 1205 insertions(+), 1845 deletions(-)

M .build.yml
M evanjon.es.go
M go.mod
M go.sum
D internal/c/home/home.go
D internal/c/home/home_test.go
M internal/c/list/list.go
M internal/c/rss/rss.go
M internal/c/search/search.go
D internal/m/content/content.go
D internal/m/content/content_test.go
D internal/m/m.go
D internal/m/m_test.go
M internal/s/cms/cms.go
M internal/s/rss/rss.go
A internal/s/tmpl/css/hyde.css
M internal/s/tmpl/css/main.css
A internal/s/tmpl/css/poole.css
A internal/s/tmpl/css/print.css
A internal/s/tmpl/css/syntax.css
A internal/s/tmpl/html/_aside.html
M internal/s/tmpl/html/_head.html
A internal/s/tmpl/html/_styles.html
D internal/s/tmpl/html/index.html
M internal/s/tmpl/html/item.html
M internal/s/tmpl/html/list.html
M internal/s/tmpl/tmpls_embed.go
D internal/v/v.go
D internal/v/v_test.go
M pkg/cms/cms.go
D vendor/github.com/gorilla/feeds/.travis.yml
D vendor/github.com/gorilla/feeds/AUTHORS
D vendor/github.com/gorilla/feeds/LICENSE
D vendor/github.com/gorilla/feeds/README.md
D vendor/github.com/gorilla/feeds/atom.go
D vendor/github.com/gorilla/feeds/doc.go
D vendor/github.com/gorilla/feeds/feed.go
D vendor/github.com/gorilla/feeds/json.go
D vendor/github.com/gorilla/feeds/rss.go
D vendor/github.com/gorilla/feeds/test.atom
D vendor/github.com/gorilla/feeds/test.rss
D vendor/github.com/gorilla/feeds/to-implement.md
D vendor/github.com/gorilla/feeds/uuid.go
M vendor/modules.txt
M .build.yml => .build.yml +9 -3
@@ 11,13 11,19 @@ tasks:
      export PATH=$PATH:$HOME/go/bin
      cd evanjon.es
      touch .env
      make gen
      make build
      if [ `git branch | head -n 1 | awk '{print$5}' | sed 's/)//g' | xargs -I X bash -c "git log --pretty=oneline origin/master | grep X" | wc -l` == "1" ]; then
      make setup gen build
      if [ `git branch | head -n 1 | awk '{print$5}' | sed 's/)//g' | xargs -I X bash -c "git log --pretty=oneline origin/master | head -n 1 | grep X" | wc -l` == "1" ]; then
        echo "deploying to production"
        ssh -o 'StrictHostKeyChecking=no' evan@140.82.14.80 'bash -c "rm ~/evanjon.es/evanjon.es 2>/dev/null"'
        scp -o 'StrictHostKeyChecking=no' evanjon.es evan@140.82.14.80:~/evanjon.es
        scp -r -o 'StrictHostKeyChecking=no' static evan@140.82.14.80:~/evanjon.es
        ssh -o 'StrictHostKeyChecking=no' evan@140.82.14.80 'bash -c "sudo systemctl restart evanjon.es"'
      elif [ `git branch | head -n 1 | awk '{print$5}' | sed 's/)//g' | xargs -I X bash -c "git log --pretty=oneline origin/tip | head -n 1 | grep X" | wc -l` == "1" ]; then
        echo "deploying to tip"
        ssh -o 'StrictHostKeyChecking=no' evan@140.82.14.80 'bash -c "rm ~/evanjon.es.tip/evanjon.es 2>/dev/null"'
        scp -o 'StrictHostKeyChecking=no' evanjon.es evan@140.82.14.80:~/evanjon.es.tip
        scp -r -o 'StrictHostKeyChecking=no' static evan@140.82.14.80:~/evanjon.es.tip
        ssh -o 'StrictHostKeyChecking=no' evan@140.82.14.80 'bash -c "sudo systemctl restart evanjon.es.tip"'
      else 
        echo "not deploying"
      fi

M evanjon.es.go => evanjon.es.go +24 -44
@@ 9,12 9,9 @@ import (
	"strconv"

	"git.sr.ht/~evanj/evanjon.es/internal/c"
	"git.sr.ht/~evanj/evanjon.es/internal/c/home"
	"git.sr.ht/~evanj/evanjon.es/internal/c/list"
	"git.sr.ht/~evanj/evanjon.es/internal/c/rss"
	"git.sr.ht/~evanj/evanjon.es/internal/c/search"
	"git.sr.ht/~evanj/evanjon.es/internal/s/cms"
	rssencoder "git.sr.ht/~evanj/evanjon.es/internal/s/rss"
	api "git.sr.ht/~evanj/evanjon.es/pkg/cms"
)



@@ 26,9 23,8 @@ type App struct {
	homeRouter   http.Handler
	searchRouter http.Handler
	listRouter   http.Handler
	// staticRouter http.Handler
	cacheRouter http.Handler
	rssRouter   http.Handler
	cacheRouter  http.Handler
	rssRouter    http.Handler
}

func New(out io.Writer) *App {


@@ 37,7 33,8 @@ func New(out io.Writer) *App {

	typePage, err1 := strconv.Atoi(os.Getenv("CMS_TYPE_PAGE"))
	typePost, err2 := strconv.Atoi(os.Getenv("CMS_TYPE_POST"))
	if err1 != nil || err2 != nil {
	typeMeta, err3 := strconv.Atoi(os.Getenv("CMS_TYPE_META"))
	if err1 != nil || err2 != nil || err3 != nil {
		l.Fatal("contenttype not set correctly")
	}



@@ 63,20 60,13 @@ func New(out io.Writer) *App {
		log:        l,
		baseRouter: e,

		homeRouter: home.New(
			e,
			log.New(out, "[evanjon.es:home] ", 0),
			cms,
			typePage,
			typePost,
		),

		searchRouter: search.New(
			e,
			log.New(out, "[evanjon.es:search] ", 0),
			cms,
			typePage,
			typePost,
			typeMeta,
		),

		listRouter: list.New(


@@ 85,36 75,29 @@ func New(out io.Writer) *App {
			cms,
			typePage,
			typePost,
			typeMeta,
		),

		// staticRouter: http.FileServer(http.Dir(".")),
		cacheRouter: cms,
		rssRouter: rss.New(
			e,
			log.New(out, "[evanjon.es:rss] ", 0),
			cms,
			rssencoder.New(
				"evanjon.es",
				"My personal corner of th WWW.",
				"https://evanjon.es/",
				"Evan Jones",
				"me@evanjon.es",
			),
			typePage,
			typePost,
		),

		// rssRouter: rss.New(
		// 	e,
		// 	log.New(out, "[evanjon.es:rss] ", 0),
		// 	cms,
		// 	rssencoder.New(
		// 		"evanjon.es",
		// 		"My personal corner of th WWW.",
		// 		"https://evanjon.es/",
		// 		"Evan Jones",
		// 		"me@evanjon.es",
		// 	),
		// 	typePage,
		// 	typePost,
		// ),
	}
}

func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// // Check for static files first.
	// p := strings.Split(r.URL.Path, "/")
	// if len(p) > 1 && p[1] == "static" {
	// 	w.Header().Add("Content-Type", "text/plain")
	// 	app.staticRouter.ServeHTTP(w, r)
	// 	return
	// }

	// Regular routers.
	switch r.URL.Path {
	case "/cache/break":


@@ 126,17 109,14 @@ func (app *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	case "/ping":
		app.baseRouter.ServeHTTP(w, r)
		break
	case "/rss":
		app.rssRouter.ServeHTTP(w, r)
		break
	// case "/rss":
	// 	app.rssRouter.ServeHTTP(w, r)
	// 	break
	case "/blog":
		fallthrough
	case "/posts":
		app.listRouter.ServeHTTP(w, r)
		break
	case "/":
		app.homeRouter.ServeHTTP(w, r)
		break
	default:
		app.searchRouter.ServeHTTP(w, r)
		break

M go.mod => go.mod +0 -2
@@ 4,7 4,5 @@ go 1.14

require (
	git.sr.ht/~evanj/embed v0.0.0-20200525225021-2cde7dae7bfa // indirect
	github.com/gorilla/feeds v1.1.1
	github.com/kr/pretty v0.2.0 // indirect
	github.com/tdewolff/minify/v2 v2.7.3
)

M go.sum => go.sum +0 -4
@@ 4,12 4,8 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dR
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=

D internal/c/home/home.go => internal/c/home/home.go +0 -53
@@ 1,53 0,0 @@
package home

import (
	"context"
	"log"
	"net/http"

	"git.sr.ht/~evanj/evanjon.es/internal/c"
	"git.sr.ht/~evanj/evanjon.es/internal/m/content"
	"git.sr.ht/~evanj/evanjon.es/internal/s/tmpl"
)

var (
	ItemHTML  = tmpl.MustParse("html/item.html")
	IndexHTML = tmpl.MustParse("html/index.html")
)

type HomeEndpoint struct {
	*c.Endpoint
	log                *log.Logger
	cms                cmsProvider
	typePage, typePost int
}

type cmsProvider interface {
	Find(ctx context.Context, typeID int, field, query string) (content.Content, error)
	List(ctx context.Context, typeID int) ([]content.Content, error)
}

func New(base *c.Endpoint, log *log.Logger, cms cmsProvider, typePage, typePost int) *HomeEndpoint {
	return &HomeEndpoint{
		base,
		log,
		cms,
		typePage, typePost,
	}
}

func (e *HomeEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := e.ContextTimeout(r)

	c, err := e.cms.Find(ctx, e.typePage, "slug", "home")
	if err != nil {
		e.log.Println("failed to find home content", err)
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("failed find home content"))
		return
	}

	e.HTML(w, r, IndexHTML, map[string]interface{}{
		"Item": c,
	})
}

D internal/c/home/home_test.go => internal/c/home/home_test.go +0 -1
@@ 1,1 0,0 @@
package home_test

M internal/c/list/list.go => internal/c/list/list.go +20 -13
@@ 6,40 6,38 @@ import (
	"net/http"

	"git.sr.ht/~evanj/evanjon.es/internal/c"
	"git.sr.ht/~evanj/evanjon.es/internal/m/content"
	"git.sr.ht/~evanj/evanjon.es/internal/s/cms"
	"git.sr.ht/~evanj/evanjon.es/internal/s/tmpl"
)

var (
	ListHTML = tmpl.MustParse("html/list.html")
)
var ListHTML = tmpl.MustParse("html/list.html")

type ListEndpoint struct {
	*c.Endpoint
	log                *log.Logger
	cms                cmsProvider
	typePage, typePost int
	log                          *log.Logger
	cms                          cmsProvider
	typePage, typePost, typeMeta int
}

type cmsProvider interface {
	List(ctx context.Context, typeID int) ([]content.Content, error)
	List(ctx context.Context, typeID int, order, field string) ([]cms.Content, error)
	FindMeta(ctx context.Context, typeID int, field, query string) (cms.Meta, error)
}

func New(base *c.Endpoint, log *log.Logger, cms cmsProvider, typePage, typePost int) *ListEndpoint {
func New(base *c.Endpoint, log *log.Logger, cms cmsProvider, typePage, typePost, typeMeta int) *ListEndpoint {
	return &ListEndpoint{
		base,
		log,
		cms,
		typePage, typePost,
		typePage, typePost, typeMeta,
	}
}

func (e *ListEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := e.ContextTimeout(r)

	e.log.Println("searching for list with type of:", e.typePost)

	l, err := e.cms.List(ctx, e.typePost)
	l, err := e.cms.List(ctx, e.typePost, "desc", "date")
	if err != nil {
		e.log.Println("failed to find list", err)
		w.WriteHeader(http.StatusNotFound)


@@ 47,8 45,17 @@ func (e *ListEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
		return
	}

	a, err := e.cms.FindMeta(ctx, e.typeMeta, "slug", "aside")
	if err != nil {
		e.log.Println(err)
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("failed to find content"))
		return
	}

	e.log.Println("list has been found")
	e.HTML(w, r, ListHTML, map[string]interface{}{
		"List": l,
		"List":  l,
		"Aside": a,
	})
}

M internal/c/rss/rss.go => internal/c/rss/rss.go +58 -58
@@ 1,60 1,60 @@
package rss

import (
	"context"
	"log"
	"net/http"

	"git.sr.ht/~evanj/evanjon.es/internal/c"
	"git.sr.ht/~evanj/evanjon.es/internal/m/content"
)

type ListEndpoint struct {
	*c.Endpoint
	log                *log.Logger
	cms                cmsProvider
	rss                rssProvider
	typePage, typePost int
}

type cmsProvider interface {
	List(ctx context.Context, typeID int) ([]content.Content, error)
}

type rssProvider interface {
	List(items []content.Content) (string, error)
}

func New(base *c.Endpoint, log *log.Logger, cms cmsProvider, rss rssProvider, typePage, typePost int) *ListEndpoint {
	return &ListEndpoint{
		base,
		log,
		cms,
		rss,
		typePage, typePost,
	}
}

func (e *ListEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := e.ContextTimeout(r)

	l, err := e.cms.List(ctx, e.typePost)
	if err != nil {
		e.log.Println(err)
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("failed to find content"))
		return
	}

	feed, err := e.rss.List(l)
	if err != nil {
		e.log.Println(err)
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("failed to generate feed"))
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Header().Add("Content-Type", "application/rss+xml")
	w.Write([]byte(feed))
}
// import (
// 	"context"
// 	"log"
// 	"net/http"
//
// 	"git.sr.ht/~evanj/evanjon.es/internal/c"
// 	"git.sr.ht/~evanj/evanjon.es/internal/m/content"
// )
//
// type ListEndpoint struct {
// 	*c.Endpoint
// 	log                *log.Logger
// 	cms                cmsProvider
// 	rss                rssProvider
// 	typePage, typePost int
// }
//
// type cmsProvider interface {
// 	List(ctx context.Context, typeID int) ([]content.Content, error)
// }
//
// type rssProvider interface {
// 	List(items []content.Content) (string, error)
// }
//
// func New(base *c.Endpoint, log *log.Logger, cms cmsProvider, rss rssProvider, typePage, typePost int) *ListEndpoint {
// 	return &ListEndpoint{
// 		base,
// 		log,
// 		cms,
// 		rss,
// 		typePage, typePost,
// 	}
// }
//
// func (e *ListEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 	ctx := e.ContextTimeout(r)
//
// 	l, err := e.cms.List(ctx, e.typePost)
// 	if err != nil {
// 		e.log.Println(err)
// 		w.WriteHeader(http.StatusNotFound)
// 		w.Write([]byte("failed to find content"))
// 		return
// 	}
//
// 	feed, err := e.rss.List(l)
// 	if err != nil {
// 		e.log.Println(err)
// 		w.WriteHeader(http.StatusNotFound)
// 		w.Write([]byte("failed to generate feed"))
// 		return
// 	}
//
// 	w.WriteHeader(http.StatusOK)
// 	w.Header().Add("Content-Type", "application/rss+xml")
// 	w.Write([]byte(feed))
// }

M internal/c/search/search.go => internal/c/search/search.go +27 -19
@@ 6,7 6,7 @@ import (
	"net/http"

	"git.sr.ht/~evanj/evanjon.es/internal/c"
	"git.sr.ht/~evanj/evanjon.es/internal/m/content"
	"git.sr.ht/~evanj/evanjon.es/internal/s/cms"
	"git.sr.ht/~evanj/evanjon.es/internal/s/tmpl"
)



@@ 16,27 16,27 @@ var (

type SearchEndpoint struct {
	*c.Endpoint
	log                *log.Logger
	cms                cmsProvider
	typePage, typePost int
	log                          *log.Logger
	cms                          cmsProvider
	typePage, typePost, typeMeta int
}

type cmsProvider interface {
	Find(ctx context.Context, typeID int, field, query string) (content.Content, error)
	Find(ctx context.Context, typeID int, field, query string) (cms.Content, error)
	FindMeta(ctx context.Context, typeID int, field, query string) (cms.Meta, error)
}

func New(base *c.Endpoint, log *log.Logger, cms cmsProvider, typePage, typePost int) *SearchEndpoint {
func New(base *c.Endpoint, log *log.Logger, cms cmsProvider, typePage, typePost, typeMeta int) *SearchEndpoint {
	return &SearchEndpoint{
		base,
		log,
		cms,
		typePage, typePost,
		typePage, typePost, typeMeta,
	}
}

func (e *SearchEndpoint) search(ctx context.Context, slug string) (content.Content, error) {

	do := func(ctx context.Context, typ int, slug string, ch chan content.Content, ech chan error) {
func (e *SearchEndpoint) search(ctx context.Context, slug string) (c cms.Content, err error) {
	do := func(ctx context.Context, typ int, slug string, ch chan cms.Content, ech chan error) {
		c, err := e.cms.Find(ctx, typ, "slug", slug)
		if err != nil {
			ech <- err


@@ 45,11 45,11 @@ func (e *SearchEndpoint) search(ctx context.Context, slug string) (content.Conte
		ch <- c
	}

	contentCh := make(chan content.Content)
	contentCh := make(chan cms.Content)
	errCh := make(chan error)

	go do(ctx, e.typePost, slug, contentCh, errCh)
	go do(ctx, e.typePost, slug, contentCh, errCh)
	go do(ctx, e.typePage, slug, contentCh, errCh)

	errcount := 0



@@ 62,7 62,7 @@ func (e *SearchEndpoint) search(ctx context.Context, slug string) (content.Conte
		case err := <-errCh:
			errcount++
			if errcount > 1 {
				return nil, err
				return c, err
			}
			break
		}


@@ 72,17 72,24 @@ func (e *SearchEndpoint) search(ctx context.Context, slug string) (content.Conte
func (e *SearchEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := e.ContextTimeout(r)

	slug := "ahoj"
	path := r.URL.Path[1:]
	if path != "" {
		slug = path
	slug := r.URL.Path[1:]
	if slug == "" {
		slug = "home"
	}

	e.log.Println("searching for content with slug of:", slug)

	c, err := e.search(ctx, slug)
	if err != nil {
		e.log.Println("failed to find content", err)
		e.log.Println(err)
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("failed to find content"))
		return
	}

	a, err := e.cms.FindMeta(ctx, e.typeMeta, "slug", "aside")
	if err != nil {
		e.log.Println(err)
		w.WriteHeader(http.StatusNotFound)
		w.Write([]byte("failed to find content"))
		return


@@ 90,6 97,7 @@ func (e *SearchEndpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {

	e.HTML(w, r, ItemHTML, map[string]interface{}{
		"Item":     c,
		"ShowDate": c.Type() != e.typePage,
		"Aside":    a,
		"ShowDate": c.Typ != e.typePage,
	})
}

D internal/m/content/content.go => internal/m/content/content.go +0 -20
@@ 1,20 0,0 @@
package content

import (
	"html/template"
	"time"
)

type Content interface {
	Name() string
	Slug() string
	Date() time.Time
	PrettyDate() string
	Category() string
	Tags() []string
	HasImage() bool
	Image() (URL string)
	Short() (shortDescription string)
	Desc() template.HTML
	Type() int // ContentType
}

D internal/m/content/content_test.go => internal/m/content/content_test.go +0 -1
@@ 1,1 0,0 @@
package content_test

D internal/m/m.go => internal/m/m.go +0 -1
@@ 1,1 0,0 @@
package m

D internal/m/m_test.go => internal/m/m_test.go +0 -1
@@ 1,1 0,0 @@
package m_test

M internal/s/cms/cms.go => internal/s/cms/cms.go +144 -196
@@ 11,30 11,41 @@ import (
	"sync"
	"time"

	i "git.sr.ht/~evanj/evanjon.es/internal/m/content"
	orig "git.sr.ht/~evanj/evanjon.es/pkg/cms"
)

var (
	ErrNoValue = errors.New("content does not have all required fields")
	ErrNoValue  = errors.New("content does not have all required fields")
	ErrBadValue = errors.New("content has malformed field")
)

type CMS struct {
	provider      provider
	log           *log.Logger
	cacheSecret   string
	dumbListCache sync.Map
	dumbFindCache sync.Map
	provider    provider
	log         *log.Logger
	cacheSecret string
	cache       sync.Map
}

type Content struct {
	name, slug, category, short, desc string
	tags                              []string
	published                         time.Time
	hasImage                          bool
	imageURL                          string
	typ                               int
	prev, next                        *Content
	Name, Slug, Short string
	Desc              template.HTML
	Date              time.Time
	HasPrev, HasNext  bool
	Prev, Next        *Content
	Typ               int
}

func (c Content) PrettyDate() string {
	return c.Date.Format("Jan 2, 2006")
}

type Meta struct {
	String1, String2, Desc string
	Nav                    []Nav
}

type Nav struct {
	Name, Slug string
}

type provider interface {


@@ 48,17 59,10 @@ func New(log *log.Logger, cms provider, cacheSecret string) *CMS {
		log,
		cacheSecret,
		sync.Map{},
		sync.Map{},
	}
}

/*****************
 * Cache things. *
 *****************/

func (cms *CMS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Clear caches.

	if cms.cacheSecret != r.FormValue("secret") {
		w.WriteHeader(http.StatusForbidden)
		w.Write([]byte("you cannot do this"))


@@ 75,243 79,187 @@ func (cms *CMS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

func (cms *CMS) breakcache() {
	cms.dumbFindCache.Range(func(key, val interface{}) bool {
		cms.dumbFindCache.Delete(key)
		return true
	})
	cms.dumbListCache.Range(func(key, val interface{}) bool {
		cms.dumbListCache.Delete(key)
	cms.cache.Range(func(key, val interface{}) bool {
		cms.cache.Delete(key)
		return true
	})
}

/*********************
 * Data/type things. *
 *********************/

func (cms *CMS) transform(in *orig.Content) (*Content, error) {
	getstr := func(key string) (string, error) {
		for _, item := range in.ContentValues {
			if item.FieldName == key {
				return item.FieldValue, nil
			}
		}
		return "", ErrNoValue
	}

	getdate := func(key string) (niltime time.Time, err error) {
		str, err := getstr(key)
		if err != nil {
			return niltime, err
		}

		t, err := time.Parse("2006-01-02", str)
		if err != nil {
			return niltime, err
		}

		return t, nil
	}
func (cms *CMS) FindMeta(ctx context.Context, typeID int, field, query string) (c Meta, err error) {
	key := fmt.Sprintf("meta::%d::%s::%s", typeID, field, query)

	getref := func(key string) *Content {
		for _, item := range in.ContentValues {
			if item.FieldName == key {
				// We don't care about errors here. Content can be bogus.
				ref, _ := cms.transform(&item.FieldReference)
				return ref
			}
	cached, ok := cms.cache.Load(key)
	if ok {
		item, ok := cached.(Meta)
		if ok {
			return item, nil
		}
		return nil
	}

	name, err := getstr("name")
	if err != nil {
		return nil, err
	}

	slug, err := getstr("slug")
	if err != nil {
		return nil, err
	}

	short, err := getstr("short")
	orig, err := cms.provider.Find(ctx, typeID, field, query)
	if err != nil {
		return nil, err
		return c, err
	}

	desc, err := getstr("desc")
	if err != nil {
		return nil, err
	string1, ok := orig.Val("string1")
	if !ok {
		return c, fmt.Errorf("string1: %w", ErrNoValue)
	}

	publishDate, err := getdate("date")
	if err != nil {
		return nil, err
	string2, ok := orig.Val("string2")
	if !ok {
		return c, fmt.Errorf("string2: %w", ErrNoValue)
	}

	typ, err := strconv.Atoi(in.ContentParentTypeID)
	if err != nil {
		return nil, err
	desc, ok := orig.Val("desc")
	if !ok {
		return c, fmt.Errorf("desc: %w", ErrNoValue)
	}

	// imageURL, err := getstr("image")
	// if err != nil {
	// 	return nil, err
	// }

	out := &Content{
		name:      name,
		slug:      slug,
		short:     short,
		desc:      desc,
		published: publishDate,
		typ:       typ,
		prev:      getref("prev"),
		next:      getref("next"),
		// hasImage:  imageURL != "",
		// imageURL:  imageURL,
		// // TODO: Reference, ReferenceList
		// category: "",
		// tags:     []string{},
	refList, ok := orig.List("nav")
	if !ok {
		return c, fmt.Errorf("nav: %w", ErrNoValue)
	}

	return out, nil
}
	var nav []Nav
	for _, item := range refList {
		name, ok := item.Val("name")
		if !ok {
			return c, fmt.Errorf("name: %w", ErrNoValue)
		}

/**********
 * Finder *
 **********/
		slug, ok := item.Val("slug")
		if !ok {
			return c, fmt.Errorf("slug: %w", ErrNoValue)
		}

func (cms *CMS) find(ctx context.Context, typeID int, field, query string) (*Content, error) {
	in, err := cms.provider.Find(ctx, typeID, field, query)
	if err != nil {
		return nil, err
		nav = append(nav, Nav{name, slug})
	}
	return cms.transform(in)

	c = Meta{string1, string2, desc, nav}
	cms.cache.Store(key, c)
	return c, nil
}

func (cms *CMS) Find(ctx context.Context, typeID int, field, query string) (i.Content, error) {
	key := fmt.Sprintf("item::%s::%s", field, query)
func (cms *CMS) Find(ctx context.Context, typeID int, field, query string) (c Content, err error) {
	key := fmt.Sprintf("find::%d::%s::%s", typeID, field, query)

	inter, ok := cms.dumbFindCache.Load(key)
	cached, ok := cms.cache.Load(key)
	if ok {
		item, ok := inter.(*Content)
		item, ok := cached.(Content)
		if ok {
			return item, nil
		}
	}

	item, err := cms.find(ctx, typeID, field, query)
	orig, err := cms.provider.Find(ctx, typeID, field, query)
	if err != nil {
		return nil, err
		return c, err
	}

	cms.dumbFindCache.Store(key, item)
	return item, nil

}

/**********
 * Lister *
 **********/

func (cms *CMS) list(ctx context.Context, typeID int) ([]*Content, error) {
	l, err := cms.provider.List(ctx, typeID, "desc", "date")
	c, err = transform(orig)
	if err != nil {
		return nil, err
	}

	var list []*Content

	for _, i := range l {
		c, err := cms.transform(&i)
		if err != nil {
			return nil, err
		}

		list = append(list, c)
		return c, err
	}

	return list, nil
	cms.cache.Store(key, c)
	return c, nil
}

func (cms *CMS) List(ctx context.Context, typeID int) (ret []i.Content, e error) {
	key := fmt.Sprintf("list::%d", typeID)
func (cms *CMS) List(ctx context.Context, typeID int, order, field string) ([]Content, error) {
	key := fmt.Sprintf("list::%d::%s::%s", typeID, order, field)

	transform := func(in []*Content) (out []i.Content) {
		for _, item := range in {
			out = append(out, item)
		}
		return out
	}

	inter, ok := cms.dumbListCache.Load(key)
	cached, ok := cms.cache.Load(key)
	if ok {
		l, ok := inter.([]*Content)
		list, ok := cached.([]Content)
		if ok {
			return transform(l), nil
			return list, nil
		}
	}

	l, err := cms.list(ctx, typeID)
	orig, err := cms.provider.List(ctx, typeID, order, field)
	if err != nil {
		return nil, err
	}

	list, err := transformList(orig)
	if err != nil {
		return nil, err
	}

	cms.dumbListCache.Store(key, l)
	return transform(l), nil
	cms.cache.Store(key, list)
	return list, nil
}

/*******************
 * Interface impl. *
 *******************/
func transformList(in []orig.Content) (out []Content, err error) {
	for _, orig := range in {
		item, err := transform(&orig)
		if err != nil {
			return out, err
		}
		out = append(out, item)
	}

func (c *Content) Name() string {
	return c.name
	return out, nil
}

func (c *Content) Slug() string {
	return c.slug
}
func transform(orig *orig.Content) (out Content, err error) {
	name, ok := orig.Val("name")
	if !ok {
		return out, fmt.Errorf("name: %w", ErrNoValue)
	}

func (c *Content) Date() time.Time {
	return c.published
}
	slug, ok := orig.Val("slug")
	if !ok {
		return out, fmt.Errorf("slug: %w", ErrNoValue)
	}

func (c *Content) PrettyDate() string {
	return c.published.Format("Jan 2, 2006")
}
	short, ok := orig.Val("short")
	if !ok {
		return out, fmt.Errorf("short: %w", ErrNoValue)
	}

func (c *Content) Category() string {
	return c.category
}
	descStr, ok := orig.Val("desc")
	if !ok {
		return out, fmt.Errorf("desc: %w", ErrNoValue)
	}
	desc := template.HTML(descStr)

func (c *Content) Tags() []string {
	return c.tags
}
	dateStr, ok := orig.Val("date")
	if !ok {
		return out, fmt.Errorf("date: %w", ErrNoValue)
	}

func (c *Content) HasImage() bool {
	return c.hasImage
}
	date, err := time.Parse("2006-01-02", dateStr)
	if err != nil {
		return out, fmt.Errorf("date: %w", ErrBadValue)
	}

func (c *Content) Image() (URL string) {
	return c.imageURL
}
	prev, hasPrev := getref(orig, "prev")
	next, hasNext := getref(orig, "next")

func (c *Content) Short() (shortDescription string) {
	return c.short
}
	typ, err := strconv.Atoi(orig.ContentParentTypeID)
	if err != nil {
		return out, fmt.Errorf("type: %w", ErrBadValue)
	}

func (c *Content) Desc() template.HTML {
	return template.HTML(c.desc)
}
	out = Content{
		name, slug, short,
		desc,
		date,
		hasPrev, hasNext,
		prev, next,
		typ,
	}

func (c *Content) Type() int {
	return c.typ
	return out, nil
}

func (c *Content) HasPrev() bool  { return c.prev != nil }
func (c *Content) Prev() *Content { return c.prev }

func (c *Content) HasNext() bool  { return c.next != nil }
func (c *Content) Next() *Content { return c.next }
func getref(c *orig.Content, key string) (*Content, bool) {
	ref, ok := c.Ref(key)
	if !ok {
		return nil, false
	}
	next, err := transform(ref)
	return &next, err == nil
}

M internal/s/rss/rss.go => internal/s/rss/rss.go +36 -36
@@ 1,38 1,38 @@
package rss

import (
	"git.sr.ht/~evanj/evanjon.es/internal/m/content"
	"github.com/gorilla/feeds"
)

type rss struct {
	title, desc, link, author, email string
}

func New(title, desc, link, author, email string) rss {
	return rss{title, desc, link, author, email}
}

func (r rss) rss(item content.Content) *feeds.Item {
	return &feeds.Item{
		Title:       item.Name(),
		Description: string(item.Desc()),
		Author:      &feeds.Author{Name: r.author, Email: r.email},
		Link:        &feeds.Link{Href: r.link + item.Slug()},
	}
}

func (r rss) List(items []content.Content) (string, error) {
	feed := &feeds.Feed{
		Title:       r.title,
		Link:        &feeds.Link{Href: r.link},
		Description: r.desc,
		Author:      &feeds.Author{Name: r.author, Email: r.email},
	}

	for _, item := range items {
		feed.Items = append(feed.Items, r.rss(item))
	}

	return feed.ToRss()
}
// import (
// 	"git.sr.ht/~evanj/evanjon.es/internal/m/content"
// 	"github.com/gorilla/feeds"
// )
//
// type rss struct {
// 	title, desc, link, author, email string
// }
//
// func New(title, desc, link, author, email string) rss {
// 	return rss{title, desc, link, author, email}
// }
//
// func (r rss) rss(item content.Content) *feeds.Item {
// 	return &feeds.Item{
// 		Title:       item.Name(),
// 		Description: string(item.Desc()),
// 		Author:      &feeds.Author{Name: r.author, Email: r.email},
// 		Link:        &feeds.Link{Href: r.link + item.Slug()},
// 	}
// }
//
// func (r rss) List(items []content.Content) (string, error) {
// 	feed := &feeds.Feed{
// 		Title:       r.title,
// 		Link:        &feeds.Link{Href: r.link},
// 		Description: r.desc,
// 		Author:      &feeds.Author{Name: r.author, Email: r.email},
// 	}
//
// 	for _, item := range items {
// 		feed.Items = append(feed.Items, r.rss(item))
// 	}
//
// 	return feed.ToRss()
// }

A internal/s/tmpl/css/hyde.css => internal/s/tmpl/css/hyde.css +250 -0
@@ 0,0 1,250 @@
/*
 *  __                  __
 * /\ \                /\ \
 * \ \ \___   __  __   \_\ \     __
 *  \ \  _ `\/\ \/\ \  /'_` \  /'__`\
 *   \ \ \ \ \ \ \_\ \/\ \_\ \/\  __/
 *    \ \_\ \_\/`____ \ \___,_\ \____\
 *     \/_/\/_/`/___/> \/__,_ /\/____/
 *                /\___/
 *                \/__/
 *
 * Designed, built, and released under MIT license by @mdo. Learn more at
 * https://github.com/poole/hyde.
 */


/*
 * Contents
 *
 * Global resets
 * Sidebar
 * Container
 * Reverse layout
 * Themes
 */


/*
 * Global resets
 *
 * Update the foundational and global aspects of the page.
 */

html {
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
}
@media (min-width: 48em) {
  html {
    font-size: 16px;
  }
}
@media (min-width: 58em) {
  html {
    font-size: 20px;
  }
}


/*
 * Sidebar
 *
 * Flexible banner for housing site name, intro, and "footer" content. Starts
 * out above content in mobile and later moves to the side with wider viewports.
 */

.sidebar {
  text-align: center;
  padding: 2rem 1rem;
  color: rgba(255,255,255,.5);
  background-color: #202020;
}
@media (min-width: 48em) {
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 18rem;
    text-align: left;
  }
}

/* Sidebar links */
.sidebar a {
  color: #fff;
}

/* About section */
.sidebar-about h1 {
  color: #fff;
  margin-top: 0;
  font-family: "Abril Fatface", serif;
  font-size: 3.25rem;
}

/* Sidebar nav */
.sidebar-nav {
  padding-left: 0;
  list-style: none;
}
.sidebar-nav-item {
  display: block;
}
a.sidebar-nav-item:hover,
a.sidebar-nav-item:focus {
  text-decoration: underline;
}
.sidebar-nav-item.active {
  font-weight: bold;
}

/* Sticky sidebar
 *
 * Add the `sidebar-sticky` class to the sidebar's container to affix it the
 * contents to the bottom of the sidebar in tablets and up.
 */

@media (min-width: 48em) {
  .sidebar-sticky {
    position: absolute;
    right:  1rem;
    bottom: 1rem;
    left:   1rem;
  }
}


/* Container
 *
 * Align the contents of the site above the proper threshold with some margin-fu
 * with a 25%-wide `.sidebar`.
 */

.content {
  padding-top:    4rem;
  padding-bottom: 4rem;
}

@media (min-width: 48em) {
  .content {
    max-width: 38rem;
    margin-left: 20rem;
    margin-right: 2rem;
  }
}

@media (min-width: 64em) {
  .content {
    margin-left: 22rem;
    margin-right: 4rem;
  }
}


/*
 * Reverse layout
 *
 * Flip the orientation of the page by placing the `.sidebar` on the right.
 */

@media (min-width: 48em) {
  .layout-reverse .sidebar {
    left: auto;
    right: 0;
  }
  .layout-reverse .content {
    margin-left: 2rem;
    margin-right: 20rem;
  }
}

@media (min-width: 64em) {
  .layout-reverse .content {
    margin-left: 4rem;
    margin-right: 22rem;
  }
}



/*
 * Themes
 *
 * As of v1.1, Hyde includes optional themes to color the sidebar and links
 * within blog posts. To use, add the class of your choosing to the `body`.
 */

/* Base16 (http://chriskempson.github.io/base16/#default) */

/* Red */
.theme-base-08 .sidebar {
  background-color: #ac4142;
}
.theme-base-08 .content a,
.theme-base-08 .related-posts li a:hover {
  color: #ac4142;
}

/* Orange */
.theme-base-09 .sidebar {
  background-color: #d28445;
}
.theme-base-09 .content a,
.theme-base-09 .related-posts li a:hover {
  color: #d28445;
}

/* Yellow */
.theme-base-0a .sidebar {
  background-color: #f4bf75;
}
.theme-base-0a .content a,
.theme-base-0a .related-posts li a:hover {
  color: #f4bf75;
}

/* Green */
.theme-base-0b .sidebar {
  background-color: #90a959;
}
.theme-base-0b .content a,
.theme-base-0b .related-posts li a:hover {
  color: #90a959;
}

/* Cyan */
.theme-base-0c .sidebar {
  background-color: #75b5aa;
}
.theme-base-0c .content a,
.theme-base-0c .related-posts li a:hover {
  color: #75b5aa;
}

/* Blue */
.theme-base-0d .sidebar {
  background-color: #6a9fb5;
}
.theme-base-0d .content a,
.theme-base-0d .related-posts li a:hover {
  color: #6a9fb5;
}

/* Magenta */
.theme-base-0e .sidebar {
  background-color: #aa759f;
}
.theme-base-0e .content a,
.theme-base-0e .related-posts li a:hover {
  color: #aa759f;
}

/* Brown */
.theme-base-0f .sidebar {
  background-color: #8f5536;
}
.theme-base-0f .content a,
.theme-base-0f .related-posts li a:hover {
  color: #8f5536;
}

M internal/s/tmpl/css/main.css => internal/s/tmpl/css/main.css +2 -69
@@ 1,70 1,3 @@
body { 
  line-height: 1.4;
}

pre { 
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

header h1 { 
  margin: 0;
}

header h1 a,
a {
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

body main { 
  padding: 15px;
  max-width: 500px;
  margin-left: auto;
  margin-right: auto;
}

blockquote { 
  margin-left: initial;
  font-style: italic;
}

blockquote + cite,
blockquote + p > cite,
blockquote + p > em {
  display: block; 
  margin-top: -10px;
}

blockquote + cite:before,
blockquote + p > cite:before,
blockquote + p > em:before { 
  font-style: italic;
  content: '-- ';
  padding-left: 40px;
}

body.list > main > a {
  text-decoration: none;
}

body.list > main > a:hover > section > h2 {
  text-decoration: underline;
}

body.list > main > a > section > h2 {
  margin-top: 0;
  margin-bottom: 0;
}

body.list > main > a > section > div { 
  margin-left: 40px;
}

img { 
  max-width: 100%;
  vertical-align: middle;
p { 
  white-space: pre-wrap;
}

A internal/s/tmpl/css/poole.css => internal/s/tmpl/css/poole.css +404 -0
@@ 0,0 1,404 @@
/*
 *                        ___
 *                       /\_ \
 *  _____     ___     ___\//\ \      __
 * /\ '__`\  / __`\  / __`\\ \ \   /'__`\
 * \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\  __/
 *  \ \ ,__/\ \____/\ \____//\____\ \____\
 *   \ \ \/  \/___/  \/___/ \/____/\/____/
 *    \ \_\
 *     \/_/
 *
 * Designed, built, and released under MIT license by @mdo. Learn more at
 * https://github.com/poole/poole.
 */


/*
 * Contents
 *
 * Body resets
 * Custom type
 * Messages
 * Container
 * Masthead
 * Posts and pages
 * Pagination
 * Reverse layout
 * Themes
 */


/*
 * Body resets
 *
 * Update the foundational and global aspects of the page.
 */

* {
  -webkit-box-sizing: border-box;
     -moz-box-sizing: border-box;
          box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

html {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.5;
}
@media (min-width: 38em) {
  html {
    font-size: 20px;
  }
}

body {
  color: #515151;
  background-color: #fff;
  -webkit-text-size-adjust: 100%;
      -ms-text-size-adjust: 100%;
          text-size-adjust: 100%;
}

/* No `:visited` state is required by default (browsers will use `a`) */
a {
  color: #227bb9;
  text-decoration: none;
}
/* `:focus` is linked to `:hover` for basic accessibility */
a:hover,
a:focus {
  text-decoration: underline;
}

/* Headings */
h1, h2, h3, h4, h5, h6 {
  margin-bottom: .5rem;
  font-weight: bold;
  line-height: 1.25;
  color: #313131;
  text-rendering: optimizeLegibility;
}
h1 {
  font-size: 2rem;
}
h2 {
  margin-top: 1rem;
  font-size: 1.5rem;
}
h3 {
  margin-top: 1.5rem;
  font-size: 1.25rem;
}
h4, h5, h6 {
  margin-top: 1rem;
  font-size: 1rem;
}

/* Body text */
p {
  margin-top: 0;
  margin-bottom: 1rem;
}

strong {
  color: #303030;
}


/* Lists */
ul, ol, dl {
  margin-top: 0;
  margin-bottom: 1rem;
}

dt {
  font-weight: bold;
}
dd {
  margin-bottom: .5rem;
}

/* Misc */
hr {
  position: relative;
  margin: 1.5rem 0;
  border: 0;
  border-top: 1px solid #eee;
  border-bottom: 1px solid #fff;
}

abbr {
  font-size: 85%;
  font-weight: bold;
  color: #555;
  text-transform: uppercase;
}
abbr[title] {
  cursor: help;
  border-bottom: 1px dotted #e5e5e5;
}

/* Code */
code,
pre {
  font-family: Menlo, Monaco, "Courier New", monospace;
}
code {
  padding: .25em .5em;
  font-size: 85%;
  color: #b3555e;
  background-color: #f9f9f9;
  border-radius: 3px;
}
pre {
  display: block;
  margin-top: 0;
  margin-bottom: 1rem;
  padding: 1rem;
  font-size: .8rem;
  line-height: 1.4;
  white-space: pre;
  white-space: pre-wrap;
  word-break: break-all;
  word-wrap: break-word;
  background-color: #f9f9f9;
}
pre code {
  padding: 0;
  font-size: 100%;
  color: inherit;
  background-color: transparent;
}
.highlight {
  margin-bottom: 1rem;
  border-radius: 4px;
}
.highlight pre {
  margin-bottom: 0;
}

/* Quotes */
blockquote {
  padding: .5rem 1rem;
  margin: .8rem 0;
  color: #7a7a7a;
  border-left: .25rem solid #e5e5e5;
}
blockquote p:last-child {
  margin-bottom: 0;
}
@media (min-width: 30em) {
  blockquote {
    padding-right: 5rem;
    padding-left: 1.25rem;
  }
}

img {
  display: block;
  margin: 0 0 1rem;
  border-radius: 5px;
  max-width: 100%;
}

/* Tables */
table {
  margin-bottom: 1rem;
  width: 100%;
  border: 1px solid #e5e5e5;
  border-collapse: collapse;
}
td,
th {
  padding: .25rem .5rem;
  border: 1px solid #e5e5e5;
}
tbody tr:nth-child(odd) td,
tbody tr:nth-child(odd) th {
  background-color: #f9f9f9;
}


/*
 * Custom type
 *
 * Extend paragraphs with `.lead` for larger introductory text.
 */

.lead {
  font-size: 1.25rem;
  font-weight: 300;
}


/*
 * Messages
 *
 * Show alert messages to users. You may add it to single elements like a `<p>`,
 * or to a parent if there are multiple elements to show.
 */

.message {
  margin-bottom: 1rem;
  padding: 1rem;
  color: #717171;
  background-color: #f9f9f9;
}


/*
 * Container
 *
 * Center the page content.
 */

.container {
  max-width: 38rem;
  padding-left:  1rem;
  padding-right: 1rem;
  margin-left:  auto;
  margin-right: auto;
}


/*
 * Masthead
 *
 * Super small header above the content for site name and short description.
 */

.masthead {
  padding-top:    1rem;
  padding-bottom: 1rem;
  margin-bottom: 3rem;
}
.masthead-title {
  margin-top: 0;
  margin-bottom: 0;
  color: #505050;
}
.masthead-title a {
  color: #505050;
}
.masthead-title small {
  font-size: 75%;
  font-weight: 400;
  color: #c0c0c0;
  letter-spacing: 0;
}


/*
 * Posts and pages
 *
 * Each post is wrapped in `.post` and is used on default and post layouts. Each
 * page is wrapped in `.page` and is only used on the page layout.
 */

.page,
.post {
  margin-bottom: 4em;
}

/* Blog post or page title */
.page-title,
.post-title,
.post-title a {
  color: #303030;
}
.page-title,
.post-title {
  margin-top: 0;
}

/* Meta data line below post title */
.post-date {
  display: block;
  margin-top: -.5rem;
  margin-bottom: 1rem;
  color: #757575;
}

/* Related posts */
.related {
  padding-top: 2rem;
  padding-bottom: 2rem;
  border-top: 1px solid #eee;
}
.related-posts {
  padding-left: 0;
  list-style: none;
}
.related-posts h3 {
  margin-top: 0;
}
.related-posts li small {
  font-size: 75%;
  color: #999;
}
.related-posts li a:hover {
  color: #227bb9;
  text-decoration: none;
}
.related-posts li a:hover small {
  color: inherit;
}


/*
 * Pagination
 *
 * Super lightweight (HTML-wise) blog pagination. `span`s are provide for when
 * there are no more previous or next posts to show.
 */

.pagination {
  overflow: hidden; /* clearfix */
  margin-left: -1rem;
  margin-right: -1rem;
  font-family: "PT Sans", Helvetica, Arial, sans-serif;
  color: #ccc;
  text-align: center;
}

/* Pagination items can be `span`s or `a`s */
.pagination-item {
  display: block;
  padding: 1rem;
  border: 1px solid #eee;
}
.pagination-item:first-child {
  margin-bottom: -1px;
}

/* Only provide a hover state for linked pagination items */
a.pagination-item:hover {
  background-color: #f5f5f5;
}

@media (min-width: 30em) {
  .pagination {
    margin: 3rem 0;
  }
  .pagination-item {
    float: left;
    width: 50%;
  }
  .pagination-item:first-child {
    margin-bottom: 0;
    border-top-left-radius:    4px;
    border-bottom-left-radius: 4px;
  }
  .pagination-item:last-child {
    margin-left: -1px;
    border-top-right-radius:    4px;
    border-bottom-right-radius: 4px;
  }
}

A internal/s/tmpl/css/print.css => internal/s/tmpl/css/print.css +19 -0
@@ 0,0 1,19 @@
.sidebar {
  display: none !important;
}

.content {
  margin: 0 auto;
  width: 100%;
  float: none;
  display: initial;
}

.container {
  width: 100%;
  float: none;
  display: initial;
  padding-left:  1rem;
  padding-right: 1rem;
  margin: 0 auto;
}

A internal/s/tmpl/css/syntax.css => internal/s/tmpl/css/syntax.css +66 -0
@@ 0,0 1,66 @@
.hll { background-color: #ffffcc }
 /*{ background: #f0f3f3; }*/
.c { color: #999; } /* Comment */
.err { color: #AA0000; background-color: #FFAAAA } /* Error */
.k { color: #006699; } /* Keyword */
.o { color: #555555 } /* Operator */
.cm { color: #0099FF; font-style: italic } /* Comment.Multiline */
.cp { color: #009999 } /* Comment.Preproc */
.c1 { color: #999; } /* Comment.Single */
.cs { color: #999; } /* Comment.Special */
.gd { background-color: #FFCCCC; border: 1px solid #CC0000 } /* Generic.Deleted */
.ge { font-style: italic } /* Generic.Emph */
.gr { color: #FF0000 } /* Generic.Error */
.gh { color: #003300; } /* Generic.Heading */
.gi { background-color: #CCFFCC; border: 1px solid #00CC00 } /* Generic.Inserted */
.go { color: #AAAAAA } /* Generic.Output */
.gp { color: #000099; } /* Generic.Prompt */
.gs { } /* Generic.Strong */
.gu { color: #003300; } /* Generic.Subheading */
.gt { color: #99CC66 } /* Generic.Traceback */
.kc { color: #006699; } /* Keyword.Constant */
.kd { color: #006699; } /* Keyword.Declaration */
.kn { color: #006699; } /* Keyword.Namespace */
.kp { color: #006699 } /* Keyword.Pseudo */
.kr { color: #006699; } /* Keyword.Reserved */
.kt { color: #007788; } /* Keyword.Type */
.m { color: #FF6600 } /* Literal.Number */
.s { color: #d44950 } /* Literal.String */
.na { color: #4f9fcf } /* Name.Attribute */
.nb { color: #336666 } /* Name.Builtin */
.nc { color: #00AA88; } /* Name.Class */
.no { color: #336600 } /* Name.Constant */
.nd { color: #9999FF } /* Name.Decorator */
.ni { color: #999999; } /* Name.Entity */
.ne { color: #CC0000; } /* Name.Exception */
.nf { color: #CC00FF } /* Name.Function */
.nl { color: #9999FF } /* Name.Label */
.nn { color: #00CCFF; } /* Name.Namespace */
.nt { color: #2f6f9f; } /* Name.Tag */
.nv { color: #003333 } /* Name.Variable */
.ow { color: #000000; } /* Operator.Word */
.w { color: #bbbbbb } /* Text.Whitespace */
.mf { color: #FF6600 } /* Literal.Number.Float */
.mh { color: #FF6600 } /* Literal.Number.Hex */
.mi { color: #FF6600 } /* Literal.Number.Integer */
.mo { color: #FF6600 } /* Literal.Number.Oct */
.sb { color: #CC3300 } /* Literal.String.Backtick */
.sc { color: #CC3300 } /* Literal.String.Char */
.sd { color: #CC3300; font-style: italic } /* Literal.String.Doc */
.s2 { color: #CC3300 } /* Literal.String.Double */
.se { color: #CC3300; } /* Literal.String.Escape */
.sh { color: #CC3300 } /* Literal.String.Heredoc */
.si { color: #AA0000 } /* Literal.String.Interpol */
.sx { color: #CC3300 } /* Literal.String.Other */
.sr { color: #33AAAA } /* Literal.String.Regex */
.s1 { color: #CC3300 } /* Literal.String.Single */
.ss { color: #FFCC33 } /* Literal.String.Symbol */
.bp { color: #336666 } /* Name.Builtin.Pseudo */
.vc { color: #003333 } /* Name.Variable.Class */
.vg { color: #003333 } /* Name.Variable.Global */
.vi { color: #003333 } /* Name.Variable.Instance */
.il { color: #FF6600 } /* Literal.Number.Integer.Long */

.css .o,
.css .o + .nt,
.css .nt + .nt { color: #999; }

A internal/s/tmpl/html/_aside.html => internal/s/tmpl/html/_aside.html +17 -0
@@ 0,0 1,17 @@
<aside class="sidebar">
  <div class="container sidebar-sticky">
    <div class="sidebar-about">
      <a href="/">
        <h1>{{.Aside.String1}}</h1>
      </a>
      <p class="lead">{{.Aside.Desc}}</p>
      <nav>
        <ul class="sidebar-nav">
          {{range .Aside.Nav}}
          <li><a href="{{.Slug}}">{{.Name}}</a></li>
          {{end}}
        </ul>
      </nav>
      <p>{{.Aside.String2}}</p>
  </div>
</aside>

M internal/s/tmpl/html/_head.html => internal/s/tmpl/html/_head.html +3 -2
@@ 1,2 1,3 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Abril+Fatface|PT+Sans:400,400i,700">

A internal/s/tmpl/html/_styles.html => internal/s/tmpl/html/_styles.html +6 -0
@@ 0,0 1,6 @@
<style>
  {{ template "css/poole.css" }}
  {{ template "css/syntax.css" }}
  {{ template "css/hyde.css" }}
  {{ template "css/main.css" }}
</style>

D internal/s/tmpl/html/index.html => internal/s/tmpl/html/index.html +0 -34
@@ 1,34 0,0 @@
<!DOCTYPE html>
<html lang=en>
<head>
  {{ template "html/_head.html" }}
  <title>Evan's Site</title>
  <meta name=description content="{{ .Item.Short }}">
</head>
<body>
  <style>{{ template "css/main.css" }}</style>
  <main>
    <header>
      <table> 
        <tr>
          <td valign=bottom width=94 height=79>
            <img alt="The 'brand' image of the website; it's a close of up Charles' face from Eureka 7" src="//e3.evanjon.es/b889bd3e-4404-49b7-6a25-6ce2838868f6.jpg"/>
          </td>
          <td valign=bottom>
            <h1><a href='/'>{{ .Item.Name }}</a></h1>
          </td>
        </tr>
      </table>
    </header>
    <article>{{ .Item.Desc }}</article>
    <hr>
    <a href='https://git.sr.ht/~evanj/evanjon.es/'>Source Code</a>, 
    <a href='mailto:me@evanjon.es'>Email</a>, 
    <a href='/rss'>RSS Feed</a>, 
    <a href='https://e3.evanjon.es/25dce601-7dab-4647-6f54-4d2710ef18fa.gpg'>GPG</a>
    <hr>
    <footer>Last edit {{ .Item.PrettyDate }}</footer>
  </main>
</body>

</html>

M internal/s/tmpl/html/item.html => internal/s/tmpl/html/item.html +27 -30
@@ 1,38 1,35 @@
<!DOCTYPE html>
<html lang=en>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  {{ template "html/_head.html" }}
  <title>Evan's Site</title>
  <meta name=description content="{{ .Item.Short }}">
  {{ template "html/_head.html" $ }}
  <title>{{.Item.Name}}</title>
  <meta name="description" content="{{.Item.Short}}" />
</head>
<body>
  <style>{{ template "css/main.css" }}</style>
  <main>
    <header>
      <table> 
        <tr>
          <td valign=bottom width=94 height=79>
            <img alt="The 'brand' image of the website; it's a close of up Charles' face from Eureka 7" src="//e3.evanjon.es/b889bd3e-4404-49b7-6a25-6ce2838868f6.jpg"/>
          </td>
          <td valign=bottom>
            <h1><a href='/{{ .Item.Slug }}'>{{ .Item.Name }}</a></h1>
          </td>
        </tr>
      </table>
    </header>
    <article>{{ .Item.Desc }}</article>
    <hr>
<body class="theme-base-08">
  {{ template "html/_styles.html" $ }}
  {{ template "html/_aside.html" $ }}
  <main class="content container">
    <div class="post">
      <h1>{{.Item.Name}}</h1>
      {{if .ShowDate}}
      <time class="post-date">{{.Item.PrettyDate}}</time>
      {{end}}
      <article>{{.Item.Desc}}</article>
    </div>
    {{if .Item.HasPrev}}
    <div>
      <a href="{{.Item.Prev.Slug}}">
        ⬅️ {{.Item.Prev.Name}}
      </a>
    </div>
    {{end}}
    {{if .Item.HasNext}}
    <div>
      {{ if .Item.HasPrev }}<div><a href='/{{.Item.Prev.Slug}}'>⬅️ {{.Item.Prev.Name}}</div></a>{{ end }}
      {{ if .Item.HasNext }}<div><a href='/{{.Item.Next.Slug}}'>{{.Item.Next.Name}} ➡️</div></a>{{ end }}
      <a href="{{.Item.Next.Slug}}">
        ➡️ {{.Item.Next.Name}}
      </a>
    </div>
    <hr>
    <nav>
      <a href='/'>Home</a>,
      <a href='/blog'>Blog</a>
    </nav>
    <hr>
    <footer>Last edit {{ .Item.PrettyDate }}</footer>
    {{end}}
  </main>
</body>
</html>

M internal/s/tmpl/html/list.html => internal/s/tmpl/html/list.html +26 -18
@@ 1,24 1,32 @@
<!DOCTYPE html>
<html lang=en>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  {{ template "html/_head.html" }}
  <title>Evan's Site</title>
  <meta name=description content="{{ .Item.Short }}">
  {{ template "html/_head.html" $ }}
  <title>TODO</title>
  <meta name="description" content="TODO" />
</head>
<body class=list>
  <style>{{ template "css/main.css" }}</style>
  <main>
    {{ range .List }}
    <a href='/{{ .Slug }}'>
      <section>
        <h2>{{ .Name }}</h2>
        <div>{{ .PrettyDate }}</div>
        <div>{{ .Short }}</div>
      </section>
    </a>
    <br/> 
    {{ end }}
<body class="theme-base-08">
  <style>
    {{ template "css/poole.css" }}
    {{ template "css/syntax.css" }}
    {{ template "css/hyde.css" }}
  </style>
  {{ template "html/_aside.html" $ }}
  <main class="content container">
    <div class="posts">
      {{ range .List }}
      <article class="post">
        <h1 class="post-title">
          <a href="/{{.Slug}}">{{.Name}}</a>
        </h1>
        <time class="post-date">{{.PrettyDate}}</time>
        <p>{{.Short}}</p>
        <div class="read-more-link">
          <a href="/{{.Slug}}">Read More…</a>
        </div>
      </article>
      {{ end }}
    </div>
  </main>
</body>

</html>

M internal/s/tmpl/tmpls_embed.go => internal/s/tmpl/tmpls_embed.go +15 -5
@@ 14,15 14,25 @@ func tostring(in string) string {
func init() {
	tmpls = make(map[string]string)

	tmpls["css/main.css"] = tostring("Ym9keSB7IAogIGxpbmUtaGVpZ2h0OiAxLjQ7Cn0KCnByZSB7IAogIG92ZXJmbG93LXg6IGF1dG87CiAgLXdlYmtpdC1vdmVyZmxvdy1zY3JvbGxpbmc6IHRvdWNoOwp9CgpoZWFkZXIgaDEgeyAKICBtYXJnaW46IDA7Cn0KCmhlYWRlciBoMSBhLAphIHsKICB0ZXh0LWRlY29yYXRpb246IG5vbmU7Cn0KCmE6aG92ZXIgewogIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOwp9Cgpib2R5IG1haW4geyAKICBwYWRkaW5nOiAxNXB4OwogIG1heC13aWR0aDogNTAwcHg7CiAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgbWFyZ2luLXJpZ2h0OiBhdXRvOwp9CgpibG9ja3F1b3RlIHsgCiAgbWFyZ2luLWxlZnQ6IGluaXRpYWw7CiAgZm9udC1zdHlsZTogaXRhbGljOwp9CgpibG9ja3F1b3RlICsgY2l0ZSwKYmxvY2txdW90ZSArIHAgPiBjaXRlLApibG9ja3F1b3RlICsgcCA+IGVtIHsKICBkaXNwbGF5OiBibG9jazsgCiAgbWFyZ2luLXRvcDogLTEwcHg7Cn0KCmJsb2NrcXVvdGUgKyBjaXRlOmJlZm9yZSwKYmxvY2txdW90ZSArIHAgPiBjaXRlOmJlZm9yZSwKYmxvY2txdW90ZSArIHAgPiBlbTpiZWZvcmUgeyAKICBmb250LXN0eWxlOiBpdGFsaWM7CiAgY29udGVudDogJy0tICc7CiAgcGFkZGluZy1sZWZ0OiA0MHB4Owp9Cgpib2R5Lmxpc3QgPiBtYWluID4gYSB7CiAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwp9Cgpib2R5Lmxpc3QgPiBtYWluID4gYTpob3ZlciA+IHNlY3Rpb24gPiBoMiB7CiAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7Cn0KCmJvZHkubGlzdCA+IG1haW4gPiBhID4gc2VjdGlvbiA+IGgyIHsKICBtYXJnaW4tdG9wOiAwOwogIG1hcmdpbi1ib3R0b206IDA7Cn0KCmJvZHkubGlzdCA+IG1haW4gPiBhID4gc2VjdGlvbiA+IGRpdiB7IAogIG1hcmdpbi1sZWZ0OiA0MHB4Owp9CgppbWcgeyAKICBtYXgtd2lkdGg6IDEwMCU7CiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsKfQo=")
	tmpls["css/hyde.css"] = tostring("LyoKICogIF9fICAgICAgICAgICAgICAgICAgX18KICogL1wgXCAgICAgICAgICAgICAgICAvXCBcCiAqIFwgXCBcX19fICAgX18gIF9fICAgXF9cIFwgICAgIF9fCiAqICBcIFwgIF8gYFwvXCBcL1wgXCAgLydfYCBcICAvJ19fYFwKICogICBcIFwgXCBcIFwgXCBcX1wgXC9cIFxfXCBcL1wgIF9fLwogKiAgICBcIFxfXCBcX1wvYF9fX18gXCBcX19fLF9cIFxfX19fXAogKiAgICAgXC9fL1wvXy9gL19fXy8+IFwvX18sXyAvXC9fX19fLwogKiAgICAgICAgICAgICAgICAvXF9fXy8KICogICAgICAgICAgICAgICAgXC9fXy8KICoKICogRGVzaWduZWQsIGJ1aWx0LCBhbmQgcmVsZWFzZWQgdW5kZXIgTUlUIGxpY2Vuc2UgYnkgQG1kby4gTGVhcm4gbW9yZSBhdAogKiBodHRwczovL2dpdGh1Yi5jb20vcG9vbGUvaHlkZS4KICovCgoKLyoKICogQ29udGVudHMKICoKICogR2xvYmFsIHJlc2V0cwogKiBTaWRlYmFyCiAqIENvbnRhaW5lcgogKiBSZXZlcnNlIGxheW91dAogKiBUaGVtZXMKICovCgoKLyoKICogR2xvYmFsIHJlc2V0cwogKgogKiBVcGRhdGUgdGhlIGZvdW5kYXRpb25hbCBhbmQgZ2xvYmFsIGFzcGVjdHMgb2YgdGhlIHBhZ2UuCiAqLwoKaHRtbCB7CiAgZm9udC1mYW1pbHk6ICJQVCBTYW5zIiwgSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjsKfQpAbWVkaWEgKG1pbi13aWR0aDogNDhlbSkgewogIGh0bWwgewogICAgZm9udC1zaXplOiAxNnB4OwogIH0KfQpAbWVkaWEgKG1pbi13aWR0aDogNThlbSkgewogIGh0bWwgewogICAgZm9udC1zaXplOiAyMHB4OwogIH0KfQoKCi8qCiAqIFNpZGViYXIKICoKICogRmxleGlibGUgYmFubmVyIGZvciBob3VzaW5nIHNpdGUgbmFtZSwgaW50cm8sIGFuZCAiZm9vdGVyIiBjb250ZW50LiBTdGFydHMKICogb3V0IGFib3ZlIGNvbnRlbnQgaW4gbW9iaWxlIGFuZCBsYXRlciBtb3ZlcyB0byB0aGUgc2lkZSB3aXRoIHdpZGVyIHZpZXdwb3J0cy4KICovCgouc2lkZWJhciB7CiAgdGV4dC1hbGlnbjogY2VudGVyOwogIHBhZGRpbmc6IDJyZW0gMXJlbTsKICBjb2xvcjogcmdiYSgyNTUsMjU1LDI1NSwuNSk7CiAgYmFja2dyb3VuZC1jb2xvcjogIzIwMjAyMDsKfQpAbWVkaWEgKG1pbi13aWR0aDogNDhlbSkgewogIC5zaWRlYmFyIHsKICAgIHBvc2l0aW9uOiBmaXhlZDsKICAgIHRvcDogMDsKICAgIGxlZnQ6IDA7CiAgICBib3R0b206IDA7CiAgICB3aWR0aDogMThyZW07CiAgICB0ZXh0LWFsaWduOiBsZWZ0OwogIH0KfQoKLyogU2lkZWJhciBsaW5rcyAqLwouc2lkZWJhciBhIHsKICBjb2xvcjogI2ZmZjsKfQoKLyogQWJvdXQgc2VjdGlvbiAqLwouc2lkZWJhci1hYm91dCBoMSB7CiAgY29sb3I6ICNmZmY7CiAgbWFyZ2luLXRvcDogMDsKICBmb250LWZhbWlseTogIkFicmlsIEZhdGZhY2UiLCBzZXJpZjsKICBmb250LXNpemU6IDMuMjVyZW07Cn0KCi8qIFNpZGViYXIgbmF2ICovCi5zaWRlYmFyLW5hdiB7CiAgcGFkZGluZy1sZWZ0OiAwOwogIGxpc3Qtc3R5bGU6IG5vbmU7Cn0KLnNpZGViYXItbmF2LWl0ZW0gewogIGRpc3BsYXk6IGJsb2NrOwp9CmEuc2lkZWJhci1uYXYtaXRlbTpob3ZlciwKYS5zaWRlYmFyLW5hdi1pdGVtOmZvY3VzIHsKICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKfQouc2lkZWJhci1uYXYtaXRlbS5hY3RpdmUgewogIGZvbnQtd2VpZ2h0OiBib2xkOwp9CgovKiBTdGlja3kgc2lkZWJhcgogKgogKiBBZGQgdGhlIGBzaWRlYmFyLXN0aWNreWAgY2xhc3MgdG8gdGhlIHNpZGViYXIncyBjb250YWluZXIgdG8gYWZmaXggaXQgdGhlCiAqIGNvbnRlbnRzIHRvIHRoZSBib3R0b20gb2YgdGhlIHNpZGViYXIgaW4gdGFibGV0cyBhbmQgdXAuCiAqLwoKQG1lZGlhIChtaW4td2lkdGg6IDQ4ZW0pIHsKICAuc2lkZWJhci1zdGlja3kgewogICAgcG9zaXRpb246IGFic29sdXRlOwogICAgcmlnaHQ6ICAxcmVtOwogICAgYm90dG9tOiAxcmVtOwogICAgbGVmdDogICAxcmVtOwogIH0KfQoKCi8qIENvbnRhaW5lcgogKgogKiBBbGlnbiB0aGUgY29udGVudHMgb2YgdGhlIHNpdGUgYWJvdmUgdGhlIHByb3BlciB0aHJlc2hvbGQgd2l0aCBzb21lIG1hcmdpbi1mdQogKiB3aXRoIGEgMjUlLXdpZGUgYC5zaWRlYmFyYC4KICovCgouY29udGVudCB7CiAgcGFkZGluZy10b3A6ICAgIDRyZW07CiAgcGFkZGluZy1ib3R0b206IDRyZW07Cn0KCkBtZWRpYSAobWluLXdpZHRoOiA0OGVtKSB7CiAgLmNvbnRlbnQgewogICAgbWF4LXdpZHRoOiAzOHJlbTsKICAgIG1hcmdpbi1sZWZ0OiAyMHJlbTsKICAgIG1hcmdpbi1yaWdodDogMnJlbTsKICB9Cn0KCkBtZWRpYSAobWluLXdpZHRoOiA2NGVtKSB7CiAgLmNvbnRlbnQgewogICAgbWFyZ2luLWxlZnQ6IDIycmVtOwogICAgbWFyZ2luLXJpZ2h0OiA0cmVtOwogIH0KfQoKCi8qCiAqIFJldmVyc2UgbGF5b3V0CiAqCiAqIEZsaXAgdGhlIG9yaWVudGF0aW9uIG9mIHRoZSBwYWdlIGJ5IHBsYWNpbmcgdGhlIGAuc2lkZWJhcmAgb24gdGhlIHJpZ2h0LgogKi8KCkBtZWRpYSAobWluLXdpZHRoOiA0OGVtKSB7CiAgLmxheW91dC1yZXZlcnNlIC5zaWRlYmFyIHsKICAgIGxlZnQ6IGF1dG87CiAgICByaWdodDogMDsKICB9CiAgLmxheW91dC1yZXZlcnNlIC5jb250ZW50IHsKICAgIG1hcmdpbi1sZWZ0OiAycmVtOwogICAgbWFyZ2luLXJpZ2h0OiAyMHJlbTsKICB9Cn0KCkBtZWRpYSAobWluLXdpZHRoOiA2NGVtKSB7CiAgLmxheW91dC1yZXZlcnNlIC5jb250ZW50IHsKICAgIG1hcmdpbi1sZWZ0OiA0cmVtOwogICAgbWFyZ2luLXJpZ2h0OiAyMnJlbTsKICB9Cn0KCgoKLyoKICogVGhlbWVzCiAqCiAqIEFzIG9mIHYxLjEsIEh5ZGUgaW5jbHVkZXMgb3B0aW9uYWwgdGhlbWVzIHRvIGNvbG9yIHRoZSBzaWRlYmFyIGFuZCBsaW5rcwogKiB3aXRoaW4gYmxvZyBwb3N0cy4gVG8gdXNlLCBhZGQgdGhlIGNsYXNzIG9mIHlvdXIgY2hvb3NpbmcgdG8gdGhlIGBib2R5YC4KICovCgovKiBCYXNlMTYgKGh0dHA6Ly9jaHJpc2tlbXBzb24uZ2l0aHViLmlvL2Jhc2UxNi8jZGVmYXVsdCkgKi8KCi8qIFJlZCAqLwoudGhlbWUtYmFzZS0wOCAuc2lkZWJhciB7CiAgYmFja2dyb3VuZC1jb2xvcjogI2FjNDE0MjsKfQoudGhlbWUtYmFzZS0wOCAuY29udGVudCBhLAoudGhlbWUtYmFzZS0wOCAucmVsYXRlZC1wb3N0cyBsaSBhOmhvdmVyIHsKICBjb2xvcjogI2FjNDE0MjsKfQoKLyogT3JhbmdlICovCi50aGVtZS1iYXNlLTA5IC5zaWRlYmFyIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjZDI4NDQ1Owp9Ci50aGVtZS1iYXNlLTA5IC5jb250ZW50IGEsCi50aGVtZS1iYXNlLTA5IC5yZWxhdGVkLXBvc3RzIGxpIGE6aG92ZXIgewogIGNvbG9yOiAjZDI4NDQ1Owp9CgovKiBZZWxsb3cgKi8KLnRoZW1lLWJhc2UtMGEgLnNpZGViYXIgewogIGJhY2tncm91bmQtY29sb3I6ICNmNGJmNzU7Cn0KLnRoZW1lLWJhc2UtMGEgLmNvbnRlbnQgYSwKLnRoZW1lLWJhc2UtMGEgLnJlbGF0ZWQtcG9zdHMgbGkgYTpob3ZlciB7CiAgY29sb3I6ICNmNGJmNzU7Cn0KCi8qIEdyZWVuICovCi50aGVtZS1iYXNlLTBiIC5zaWRlYmFyIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjOTBhOTU5Owp9Ci50aGVtZS1iYXNlLTBiIC5jb250ZW50IGEsCi50aGVtZS1iYXNlLTBiIC5yZWxhdGVkLXBvc3RzIGxpIGE6aG92ZXIgewogIGNvbG9yOiAjOTBhOTU5Owp9CgovKiBDeWFuICovCi50aGVtZS1iYXNlLTBjIC5zaWRlYmFyIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjNzViNWFhOwp9Ci50aGVtZS1iYXNlLTBjIC5jb250ZW50IGEsCi50aGVtZS1iYXNlLTBjIC5yZWxhdGVkLXBvc3RzIGxpIGE6aG92ZXIgewogIGNvbG9yOiAjNzViNWFhOwp9CgovKiBCbHVlICovCi50aGVtZS1iYXNlLTBkIC5zaWRlYmFyIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjNmE5ZmI1Owp9Ci50aGVtZS1iYXNlLTBkIC5jb250ZW50IGEsCi50aGVtZS1iYXNlLTBkIC5yZWxhdGVkLXBvc3RzIGxpIGE6aG92ZXIgewogIGNvbG9yOiAjNmE5ZmI1Owp9CgovKiBNYWdlbnRhICovCi50aGVtZS1iYXNlLTBlIC5zaWRlYmFyIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjYWE3NTlmOwp9Ci50aGVtZS1iYXNlLTBlIC5jb250ZW50IGEsCi50aGVtZS1iYXNlLTBlIC5yZWxhdGVkLXBvc3RzIGxpIGE6aG92ZXIgewogIGNvbG9yOiAjYWE3NTlmOwp9CgovKiBCcm93biAqLwoudGhlbWUtYmFzZS0wZiAuc2lkZWJhciB7CiAgYmFja2dyb3VuZC1jb2xvcjogIzhmNTUzNjsKfQoudGhlbWUtYmFzZS0wZiAuY29udGVudCBhLAoudGhlbWUtYmFzZS0wZiAucmVsYXRlZC1wb3N0cyBsaSBhOmhvdmVyIHsKICBjb2xvcjogIzhmNTUzNjsKfQo=")

	tmpls["html/_head.html"] = tostring("PG1ldGEgY2hhcnNldD0idXRmLTgiPgo8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiPgo=")
	tmpls["css/main.css"] = tostring("cCB7IAogIHdoaXRlLXNwYWNlOiBwcmUtd3JhcDsKfQo=")

	tmpls["html/index.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ZW4+CjxoZWFkPgogIHt7IHRlbXBsYXRlICJodG1sL19oZWFkLmh0bWwiIH19CiAgPHRpdGxlPkV2YW4ncyBTaXRlPC90aXRsZT4KICA8bWV0YSBuYW1lPWRlc2NyaXB0aW9uIGNvbnRlbnQ9Int7IC5JdGVtLlNob3J0IH19Ij4KPC9oZWFkPgo8Ym9keT4KICA8c3R5bGU+e3sgdGVtcGxhdGUgImNzcy9tYWluLmNzcyIgfX08L3N0eWxlPgogIDxtYWluPgogICAgPGhlYWRlcj4KICAgICAgPHRhYmxlPiAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQgdmFsaWduPWJvdHRvbSB3aWR0aD05NCBoZWlnaHQ9Nzk+CiAgICAgICAgICAgIDxpbWcgYWx0PSJUaGUgJ2JyYW5kJyBpbWFnZSBvZiB0aGUgd2Vic2l0ZTsgaXQncyBhIGNsb3NlIG9mIHVwIENoYXJsZXMnIGZhY2UgZnJvbSBFdXJla2EgNyIgc3JjPSIvL2UzLmV2YW5qb24uZXMvYjg4OWJkM2UtNDQwNC00OWI3LTZhMjUtNmNlMjgzODg2OGY2LmpwZyIvPgogICAgICAgICAgPC90ZD4KICAgICAgICAgIDx0ZCB2YWxpZ249Ym90dG9tPgogICAgICAgICAgICA8aDE+PGEgaHJlZj0nLyc+e3sgLkl0ZW0uTmFtZSB9fTwvYT48L2gxPgogICAgICAgICAgPC90ZD4KICAgICAgICA8L3RyPgogICAgICA8L3RhYmxlPgogICAgPC9oZWFkZXI+CiAgICA8YXJ0aWNsZT57eyAuSXRlbS5EZXNjIH19PC9hcnRpY2xlPgogICAgPGhyPgogICAgPGEgaHJlZj0naHR0cHM6Ly9naXQuc3IuaHQvfmV2YW5qL2V2YW5qb24uZXMvJz5Tb3VyY2UgQ29kZTwvYT4sIAogICAgPGEgaHJlZj0nbWFpbHRvOm1lQGV2YW5qb24uZXMnPkVtYWlsPC9hPiwgCiAgICA8YSBocmVmPScvcnNzJz5SU1MgRmVlZDwvYT4sIAogICAgPGEgaHJlZj0naHR0cHM6Ly9lMy5ldmFuam9uLmVzLzI1ZGNlNjAxLTdkYWItNDY0Ny02ZjU0LTRkMjcxMGVmMThmYS5ncGcnPkdQRzwvYT4KICAgIDxocj4KICAgIDxmb290ZXI+TGFzdCBlZGl0IHt7IC5JdGVtLlByZXR0eURhdGUgfX08L2Zvb3Rlcj4KICA8L21haW4+CjwvYm9keT4KCjwvaHRtbD4K")
	tmpls["css/poole.css"] = tostring("LyoKICogICAgICAgICAgICAgICAgICAgICAgICBfX18KICogICAgICAgICAgICAgICAgICAgICAgIC9cXyBcCiAqICBfX19fXyAgICAgX19fICAgICBfX19cLy9cIFwgICAgICBfXwogKiAvXCAnX19gXCAgLyBfX2BcICAvIF9fYFxcIFwgXCAgIC8nX19gXAogKiBcIFwgXF9cIFwvXCBcX1wgXC9cIFxfXCBcXF9cIFxfL1wgIF9fLwogKiAgXCBcICxfXy9cIFxfX19fL1wgXF9fX18vL1xfX19fXCBcX19fX1wKICogICBcIFwgXC8gIFwvX19fLyAgXC9fX18vIFwvX19fXy9cL19fX18vCiAqICAgIFwgXF9cCiAqICAgICBcL18vCiAqCiAqIERlc2lnbmVkLCBidWlsdCwgYW5kIHJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIGJ5IEBtZG8uIExlYXJuIG1vcmUgYXQKICogaHR0cHM6Ly9naXRodWIuY29tL3Bvb2xlL3Bvb2xlLgogKi8KCgovKgogKiBDb250ZW50cwogKgogKiBCb2R5IHJlc2V0cwogKiBDdXN0b20gdHlwZQogKiBNZXNzYWdlcwogKiBDb250YWluZXIKICogTWFzdGhlYWQKICogUG9zdHMgYW5kIHBhZ2VzCiAqIFBhZ2luYXRpb24KICogUmV2ZXJzZSBsYXlvdXQKICogVGhlbWVzCiAqLwoKCi8qCiAqIEJvZHkgcmVzZXRzCiAqCiAqIFVwZGF0ZSB0aGUgZm91bmRhdGlvbmFsIGFuZCBnbG9iYWwgYXNwZWN0cyBvZiB0aGUgcGFnZS4KICovCgoqIHsKICAtd2Via2l0LWJveC1zaXppbmc6IGJvcmRlci1ib3g7CiAgICAgLW1vei1ib3gtc2l6aW5nOiBib3JkZXItYm94OwogICAgICAgICAgYm94LXNpemluZzogYm9yZGVyLWJveDsKfQoKaHRtbCwKYm9keSB7CiAgbWFyZ2luOiAwOwogIHBhZGRpbmc6IDA7Cn0KCmh0bWwgewogIGZvbnQtZmFtaWx5OiAiSGVsdmV0aWNhIE5ldWUiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOwogIGZvbnQtc2l6ZTogMTZweDsKICBsaW5lLWhlaWdodDogMS41Owp9CkBtZWRpYSAobWluLXdpZHRoOiAzOGVtKSB7CiAgaHRtbCB7CiAgICBmb250LXNpemU6IDIwcHg7CiAgfQp9Cgpib2R5IHsKICBjb2xvcjogIzUxNTE1MTsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOwogIC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTsKICAgICAgLW1zLXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7CiAgICAgICAgICB0ZXh0LXNpemUtYWRqdXN0OiAxMDAlOwp9CgovKiBObyBgOnZpc2l0ZWRgIHN0YXRlIGlzIHJlcXVpcmVkIGJ5IGRlZmF1bHQgKGJyb3dzZXJzIHdpbGwgdXNlIGBhYCkgKi8KYSB7CiAgY29sb3I6ICMyMjdiYjk7CiAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwp9Ci8qIGA6Zm9jdXNgIGlzIGxpbmtlZCB0byBgOmhvdmVyYCBmb3IgYmFzaWMgYWNjZXNzaWJpbGl0eSAqLwphOmhvdmVyLAphOmZvY3VzIHsKICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKfQoKLyogSGVhZGluZ3MgKi8KaDEsIGgyLCBoMywgaDQsIGg1LCBoNiB7CiAgbWFyZ2luLWJvdHRvbTogLjVyZW07CiAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgbGluZS1oZWlnaHQ6IDEuMjU7CiAgY29sb3I6ICMzMTMxMzE7CiAgdGV4dC1yZW5kZXJpbmc6IG9wdGltaXplTGVnaWJpbGl0eTsKfQpoMSB7CiAgZm9udC1zaXplOiAycmVtOwp9CmgyIHsKICBtYXJnaW4tdG9wOiAxcmVtOwogIGZvbnQtc2l6ZTogMS41cmVtOwp9CmgzIHsKICBtYXJnaW4tdG9wOiAxLjVyZW07CiAgZm9udC1zaXplOiAxLjI1cmVtOwp9Cmg0LCBoNSwgaDYgewogIG1hcmdpbi10b3A6IDFyZW07CiAgZm9udC1zaXplOiAxcmVtOwp9CgovKiBCb2R5IHRleHQgKi8KcCB7CiAgbWFyZ2luLXRvcDogMDsKICBtYXJnaW4tYm90dG9tOiAxcmVtOwp9CgpzdHJvbmcgewogIGNvbG9yOiAjMzAzMDMwOwp9CgoKLyogTGlzdHMgKi8KdWwsIG9sLCBkbCB7CiAgbWFyZ2luLXRvcDogMDsKICBtYXJnaW4tYm90dG9tOiAxcmVtOwp9CgpkdCB7CiAgZm9udC13ZWlnaHQ6IGJvbGQ7Cn0KZGQgewogIG1hcmdpbi1ib3R0b206IC41cmVtOwp9CgovKiBNaXNjICovCmhyIHsKICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgbWFyZ2luOiAxLjVyZW0gMDsKICBib3JkZXI6IDA7CiAgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNlZWU7CiAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNmZmY7Cn0KCmFiYnIgewogIGZvbnQtc2l6ZTogODUlOwogIGZvbnQtd2VpZ2h0OiBib2xkOwogIGNvbG9yOiAjNTU1OwogIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7Cn0KYWJiclt0aXRsZV0gewogIGN1cnNvcjogaGVscDsKICBib3JkZXItYm90dG9tOiAxcHggZG90dGVkICNlNWU1ZTU7Cn0KCi8qIENvZGUgKi8KY29kZSwKcHJlIHsKICBmb250LWZhbWlseTogTWVubG8sIE1vbmFjbywgIkNvdXJpZXIgTmV3IiwgbW9ub3NwYWNlOwp9CmNvZGUgewogIHBhZGRpbmc6IC4yNWVtIC41ZW07CiAgZm9udC1zaXplOiA4NSU7CiAgY29sb3I6ICNiMzU1NWU7CiAgYmFja2dyb3VuZC1jb2xvcjogI2Y5ZjlmOTsKICBib3JkZXItcmFkaXVzOiAzcHg7Cn0KcHJlIHsKICBkaXNwbGF5OiBibG9jazsKICBtYXJnaW4tdG9wOiAwOwogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgcGFkZGluZzogMXJlbTsKICBmb250LXNpemU6IC44cmVtOwogIGxpbmUtaGVpZ2h0OiAxLjQ7CiAgd2hpdGUtc3BhY2U6IHByZTsKICB3aGl0ZS1zcGFjZTogcHJlLXdyYXA7CiAgd29yZC1icmVhazogYnJlYWstYWxsOwogIHdvcmQtd3JhcDogYnJlYWstd29yZDsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjlmOWY5Owp9CnByZSBjb2RlIHsKICBwYWRkaW5nOiAwOwogIGZvbnQtc2l6ZTogMTAwJTsKICBjb2xvcjogaW5oZXJpdDsKICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsKfQouaGlnaGxpZ2h0IHsKICBtYXJnaW4tYm90dG9tOiAxcmVtOwogIGJvcmRlci1yYWRpdXM6IDRweDsKfQouaGlnaGxpZ2h0IHByZSB7CiAgbWFyZ2luLWJvdHRvbTogMDsKfQoKLyogUXVvdGVzICovCmJsb2NrcXVvdGUgewogIHBhZGRpbmc6IC41cmVtIDFyZW07CiAgbWFyZ2luOiAuOHJlbSAwOwogIGNvbG9yOiAjN2E3YTdhOwogIGJvcmRlci1sZWZ0OiAuMjVyZW0gc29saWQgI2U1ZTVlNTsKfQpibG9ja3F1b3RlIHA6bGFzdC1jaGlsZCB7CiAgbWFyZ2luLWJvdHRvbTogMDsKfQpAbWVkaWEgKG1pbi13aWR0aDogMzBlbSkgewogIGJsb2NrcXVvdGUgewogICAgcGFkZGluZy1yaWdodDogNXJlbTsKICAgIHBhZGRpbmctbGVmdDogMS4yNXJlbTsKICB9Cn0KCmltZyB7CiAgZGlzcGxheTogYmxvY2s7CiAgbWFyZ2luOiAwIDAgMXJlbTsKICBib3JkZXItcmFkaXVzOiA1cHg7CiAgbWF4LXdpZHRoOiAxMDAlOwp9CgovKiBUYWJsZXMgKi8KdGFibGUgewogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgd2lkdGg6IDEwMCU7CiAgYm9yZGVyOiAxcHggc29saWQgI2U1ZTVlNTsKICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwp9CnRkLAp0aCB7CiAgcGFkZGluZzogLjI1cmVtIC41cmVtOwogIGJvcmRlcjogMXB4IHNvbGlkICNlNWU1ZTU7Cn0KdGJvZHkgdHI6bnRoLWNoaWxkKG9kZCkgdGQsCnRib2R5IHRyOm50aC1jaGlsZChvZGQpIHRoIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjlmOWY5Owp9CgoKLyoKICogQ3VzdG9tIHR5cGUKICoKICogRXh0ZW5kIHBhcmFncmFwaHMgd2l0aCBgLmxlYWRgIGZvciBsYXJnZXIgaW50cm9kdWN0b3J5IHRleHQuCiAqLwoKLmxlYWQgewogIGZvbnQtc2l6ZTogMS4yNXJlbTsKICBmb250LXdlaWdodDogMzAwOwp9CgoKLyoKICogTWVzc2FnZXMKICoKICogU2hvdyBhbGVydCBtZXNzYWdlcyB0byB1c2Vycy4gWW91IG1heSBhZGQgaXQgdG8gc2luZ2xlIGVsZW1lbnRzIGxpa2UgYSBgPHA+YCwKICogb3IgdG8gYSBwYXJlbnQgaWYgdGhlcmUgYXJlIG11bHRpcGxlIGVsZW1lbnRzIHRvIHNob3cuCiAqLwoKLm1lc3NhZ2UgewogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgcGFkZGluZzogMXJlbTsKICBjb2xvcjogIzcxNzE3MTsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjlmOWY5Owp9CgoKLyoKICogQ29udGFpbmVyCiAqCiAqIENlbnRlciB0aGUgcGFnZSBjb250ZW50LgogKi8KCi5jb250YWluZXIgewogIG1heC13aWR0aDogMzhyZW07CiAgcGFkZGluZy1sZWZ0OiAgMXJlbTsKICBwYWRkaW5nLXJpZ2h0OiAxcmVtOwogIG1hcmdpbi1sZWZ0OiAgYXV0bzsKICBtYXJnaW4tcmlnaHQ6IGF1dG87Cn0KCgovKgogKiBNYXN0aGVhZAogKgogKiBTdXBlciBzbWFsbCBoZWFkZXIgYWJvdmUgdGhlIGNvbnRlbnQgZm9yIHNpdGUgbmFtZSBhbmQgc2hvcnQgZGVzY3JpcHRpb24uCiAqLwoKLm1hc3RoZWFkIHsKICBwYWRkaW5nLXRvcDogICAgMXJlbTsKICBwYWRkaW5nLWJvdHRvbTogMXJlbTsKICBtYXJnaW4tYm90dG9tOiAzcmVtOwp9Ci5tYXN0aGVhZC10aXRsZSB7CiAgbWFyZ2luLXRvcDogMDsKICBtYXJnaW4tYm90dG9tOiAwOwogIGNvbG9yOiAjNTA1MDUwOwp9Ci5tYXN0aGVhZC10aXRsZSBhIHsKICBjb2xvcjogIzUwNTA1MDsKfQoubWFzdGhlYWQtdGl0bGUgc21hbGwgewogIGZvbnQtc2l6ZTogNzUlOwogIGZvbnQtd2VpZ2h0OiA0MDA7CiAgY29sb3I6ICNjMGMwYzA7CiAgbGV0dGVyLXNwYWNpbmc6IDA7Cn0KCgovKgogKiBQb3N0cyBhbmQgcGFnZXMKICoKICogRWFjaCBwb3N0IGlzIHdyYXBwZWQgaW4gYC5wb3N0YCBhbmQgaXMgdXNlZCBvbiBkZWZhdWx0IGFuZCBwb3N0IGxheW91dHMuIEVhY2gKICogcGFnZSBpcyB3cmFwcGVkIGluIGAucGFnZWAgYW5kIGlzIG9ubHkgdXNlZCBvbiB0aGUgcGFnZSBsYXlvdXQuCiAqLwoKLnBhZ2UsCi5wb3N0IHsKICBtYXJnaW4tYm90dG9tOiA0ZW07Cn0KCi8qIEJsb2cgcG9zdCBvciBwYWdlIHRpdGxlICovCi5wYWdlLXRpdGxlLAoucG9zdC10aXRsZSwKLnBvc3QtdGl0bGUgYSB7CiAgY29sb3I6ICMzMDMwMzA7Cn0KLnBhZ2UtdGl0bGUsCi5wb3N0LXRpdGxlIHsKICBtYXJnaW4tdG9wOiAwOwp9CgovKiBNZXRhIGRhdGEgbGluZSBiZWxvdyBwb3N0IHRpdGxlICovCi5wb3N0LWRhdGUgewogIGRpc3BsYXk6IGJsb2NrOwogIG1hcmdpbi10b3A6IC0uNXJlbTsKICBtYXJnaW4tYm90dG9tOiAxcmVtOwogIGNvbG9yOiAjNzU3NTc1Owp9CgovKiBSZWxhdGVkIHBvc3RzICovCi5yZWxhdGVkIHsKICBwYWRkaW5nLXRvcDogMnJlbTsKICBwYWRkaW5nLWJvdHRvbTogMnJlbTsKICBib3JkZXItdG9wOiAxcHggc29saWQgI2VlZTsKfQoucmVsYXRlZC1wb3N0cyB7CiAgcGFkZGluZy1sZWZ0OiAwOwogIGxpc3Qtc3R5bGU6IG5vbmU7Cn0KLnJlbGF0ZWQtcG9zdHMgaDMgewogIG1hcmdpbi10b3A6IDA7Cn0KLnJlbGF0ZWQtcG9zdHMgbGkgc21hbGwgewogIGZvbnQtc2l6ZTogNzUlOwogIGNvbG9yOiAjOTk5Owp9Ci5yZWxhdGVkLXBvc3RzIGxpIGE6aG92ZXIgewogIGNvbG9yOiAjMjI3YmI5OwogIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKfQoucmVsYXRlZC1wb3N0cyBsaSBhOmhvdmVyIHNtYWxsIHsKICBjb2xvcjogaW5oZXJpdDsKfQoKCi8qCiAqIFBhZ2luYXRpb24KICoKICogU3VwZXIgbGlnaHR3ZWlnaHQgKEhUTUwtd2lzZSkgYmxvZyBwYWdpbmF0aW9uLiBgc3BhbmBzIGFyZSBwcm92aWRlIGZvciB3aGVuCiAqIHRoZXJlIGFyZSBubyBtb3JlIHByZXZpb3VzIG9yIG5leHQgcG9zdHMgdG8gc2hvdy4KICovCgoucGFnaW5hdGlvbiB7CiAgb3ZlcmZsb3c6IGhpZGRlbjsgLyogY2xlYXJmaXggKi8KICBtYXJnaW4tbGVmdDogLTFyZW07CiAgbWFyZ2luLXJpZ2h0OiAtMXJlbTsKICBmb250LWZhbWlseTogIlBUIFNhbnMiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmOwogIGNvbG9yOiAjY2NjOwogIHRleHQtYWxpZ246IGNlbnRlcjsKfQoKLyogUGFnaW5hdGlvbiBpdGVtcyBjYW4gYmUgYHNwYW5gcyBvciBgYWBzICovCi5wYWdpbmF0aW9uLWl0ZW0gewogIGRpc3BsYXk6IGJsb2NrOwogIHBhZGRpbmc6IDFyZW07CiAgYm9yZGVyOiAxcHggc29saWQgI2VlZTsKfQoucGFnaW5hdGlvbi1pdGVtOmZpcnN0LWNoaWxkIHsKICBtYXJnaW4tYm90dG9tOiAtMXB4Owp9CgovKiBPbmx5IHByb3ZpZGUgYSBob3ZlciBzdGF0ZSBmb3IgbGlua2VkIHBhZ2luYXRpb24gaXRlbXMgKi8KYS5wYWdpbmF0aW9uLWl0ZW06aG92ZXIgewogIGJhY2tncm91bmQtY29sb3I6ICNmNWY1ZjU7Cn0KCkBtZWRpYSAobWluLXdpZHRoOiAzMGVtKSB7CiAgLnBhZ2luYXRpb24gewogICAgbWFyZ2luOiAzcmVtIDA7CiAgfQogIC5wYWdpbmF0aW9uLWl0ZW0gewogICAgZmxvYXQ6IGxlZnQ7CiAgICB3aWR0aDogNTAlOwogIH0KICAucGFnaW5hdGlvbi1pdGVtOmZpcnN0LWNoaWxkIHsKICAgIG1hcmdpbi1ib3R0b206IDA7CiAgICBib3JkZXItdG9wLWxlZnQtcmFkaXVzOiAgICA0cHg7CiAgICBib3JkZXItYm90dG9tLWxlZnQtcmFkaXVzOiA0cHg7CiAgfQogIC5wYWdpbmF0aW9uLWl0ZW06bGFzdC1jaGlsZCB7CiAgICBtYXJnaW4tbGVmdDogLTFweDsKICAgIGJvcmRlci10b3AtcmlnaHQtcmFkaXVzOiAgICA0cHg7CiAgICBib3JkZXItYm90dG9tLXJpZ2h0LXJhZGl1czogNHB4OwogIH0KfQo=")

	tmpls["html/item.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ZW4+CjxoZWFkPgogIHt7IHRlbXBsYXRlICJodG1sL19oZWFkLmh0bWwiIH19CiAgPHRpdGxlPkV2YW4ncyBTaXRlPC90aXRsZT4KICA8bWV0YSBuYW1lPWRlc2NyaXB0aW9uIGNvbnRlbnQ9Int7IC5JdGVtLlNob3J0IH19Ij4KPC9oZWFkPgo8Ym9keT4KICA8c3R5bGU+e3sgdGVtcGxhdGUgImNzcy9tYWluLmNzcyIgfX08L3N0eWxlPgogIDxtYWluPgogICAgPGhlYWRlcj4KICAgICAgPHRhYmxlPiAKICAgICAgICA8dHI+CiAgICAgICAgICA8dGQgdmFsaWduPWJvdHRvbSB3aWR0aD05NCBoZWlnaHQ9Nzk+CiAgICAgICAgICAgIDxpbWcgYWx0PSJUaGUgJ2JyYW5kJyBpbWFnZSBvZiB0aGUgd2Vic2l0ZTsgaXQncyBhIGNsb3NlIG9mIHVwIENoYXJsZXMnIGZhY2UgZnJvbSBFdXJla2EgNyIgc3JjPSIvL2UzLmV2YW5qb24uZXMvYjg4OWJkM2UtNDQwNC00OWI3LTZhMjUtNmNlMjgzODg2OGY2LmpwZyIvPgogICAgICAgICAgPC90ZD4KICAgICAgICAgIDx0ZCB2YWxpZ249Ym90dG9tPgogICAgICAgICAgICA8aDE+PGEgaHJlZj0nL3t7IC5JdGVtLlNsdWcgfX0nPnt7IC5JdGVtLk5hbWUgfX08L2E+PC9oMT4KICAgICAgICAgIDwvdGQ+CiAgICAgICAgPC90cj4KICAgICAgPC90YWJsZT4KICAgIDwvaGVhZGVyPgogICAgPGFydGljbGU+e3sgLkl0ZW0uRGVzYyB9fTwvYXJ0aWNsZT4KICAgIDxocj4KICAgIDxkaXY+CiAgICAgIHt7IGlmIC5JdGVtLkhhc1ByZXYgfX08ZGl2PjxhIGhyZWY9Jy97ey5JdGVtLlByZXYuU2x1Z319Jz7irIXvuI8ge3suSXRlbS5QcmV2Lk5hbWV9fTwvZGl2PjwvYT57eyBlbmQgfX0KICAgICAge3sgaWYgLkl0ZW0uSGFzTmV4dCB9fTxkaXY+PGEgaHJlZj0nL3t7Lkl0ZW0uTmV4dC5TbHVnfX0nPnt7Lkl0ZW0uTmV4dC5OYW1lfX0g4p6h77iPPC9kaXY+PC9hPnt7IGVuZCB9fQogICAgPC9kaXY+CiAgICA8aHI+CiAgICA8bmF2PgogICAgICA8YSBocmVmPScvJz5Ib21lPC9hPiwKICAgICAgPGEgaHJlZj0nL2Jsb2cnPkJsb2c8L2E+CiAgICA8L25hdj4KICAgIDxocj4KICAgIDxmb290ZXI+TGFzdCBlZGl0IHt7IC5JdGVtLlByZXR0eURhdGUgfX08L2Zvb3Rlcj4KICA8L21haW4+CjwvYm9keT4KPC9odG1sPgo=")
	tmpls["css/print.css"] = tostring("LnNpZGViYXIgewogIGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDsKfQoKLmNvbnRlbnQgewogIG1hcmdpbjogMCBhdXRvOwogIHdpZHRoOiAxMDAlOwogIGZsb2F0OiBub25lOwogIGRpc3BsYXk6IGluaXRpYWw7Cn0KCi5jb250YWluZXIgewogIHdpZHRoOiAxMDAlOwogIGZsb2F0OiBub25lOwogIGRpc3BsYXk6IGluaXRpYWw7CiAgcGFkZGluZy1sZWZ0OiAgMXJlbTsKICBwYWRkaW5nLXJpZ2h0OiAxcmVtOwogIG1hcmdpbjogMCBhdXRvOwp9Cg==")

	tmpls["html/list.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ZW4+CjxoZWFkPgogIHt7IHRlbXBsYXRlICJodG1sL19oZWFkLmh0bWwiIH19CiAgPHRpdGxlPkV2YW4ncyBTaXRlPC90aXRsZT4KICA8bWV0YSBuYW1lPWRlc2NyaXB0aW9uIGNvbnRlbnQ9Int7IC5JdGVtLlNob3J0IH19Ij4KPC9oZWFkPgo8Ym9keSBjbGFzcz1saXN0PgogIDxzdHlsZT57eyB0ZW1wbGF0ZSAiY3NzL21haW4uY3NzIiB9fTwvc3R5bGU+CiAgPG1haW4+CiAgICB7eyByYW5nZSAuTGlzdCB9fQogICAgPGEgaHJlZj0nL3t7IC5TbHVnIH19Jz4KICAgICAgPHNlY3Rpb24+CiAgICAgICAgPGgyPnt7IC5OYW1lIH19PC9oMj4KICAgICAgICA8ZGl2Pnt7IC5QcmV0dHlEYXRlIH19PC9kaXY+CiAgICAgICAgPGRpdj57eyAuU2hvcnQgfX08L2Rpdj4KICAgICAgPC9zZWN0aW9uPgogICAgPC9hPgogICAgPGJyLz4gCiAgICB7eyBlbmQgfX0KICA8L21haW4+CjwvYm9keT4KCjwvaHRtbD4K")
	tmpls["css/syntax.css"] = tostring("LmhsbCB7IGJhY2tncm91bmQtY29sb3I6ICNmZmZmY2MgfQogLyp7IGJhY2tncm91bmQ6ICNmMGYzZjM7IH0qLwouYyB7IGNvbG9yOiAjOTk5OyB9IC8qIENvbW1lbnQgKi8KLmVyciB7IGNvbG9yOiAjQUEwMDAwOyBiYWNrZ3JvdW5kLWNvbG9yOiAjRkZBQUFBIH0gLyogRXJyb3IgKi8KLmsgeyBjb2xvcjogIzAwNjY5OTsgfSAvKiBLZXl3b3JkICovCi5vIHsgY29sb3I6ICM1NTU1NTUgfSAvKiBPcGVyYXRvciAqLwouY20geyBjb2xvcjogIzAwOTlGRjsgZm9udC1zdHlsZTogaXRhbGljIH0gLyogQ29tbWVudC5NdWx0aWxpbmUgKi8KLmNwIHsgY29sb3I6ICMwMDk5OTkgfSAvKiBDb21tZW50LlByZXByb2MgKi8KLmMxIHsgY29sb3I6ICM5OTk7IH0gLyogQ29tbWVudC5TaW5nbGUgKi8KLmNzIHsgY29sb3I6ICM5OTk7IH0gLyogQ29tbWVudC5TcGVjaWFsICovCi5nZCB7IGJhY2tncm91bmQtY29sb3I6ICNGRkNDQ0M7IGJvcmRlcjogMXB4IHNvbGlkICNDQzAwMDAgfSAvKiBHZW5lcmljLkRlbGV0ZWQgKi8KLmdlIHsgZm9udC1zdHlsZTogaXRhbGljIH0gLyogR2VuZXJpYy5FbXBoICovCi5nciB7IGNvbG9yOiAjRkYwMDAwIH0gLyogR2VuZXJpYy5FcnJvciAqLwouZ2ggeyBjb2xvcjogIzAwMzMwMDsgfSAvKiBHZW5lcmljLkhlYWRpbmcgKi8KLmdpIHsgYmFja2dyb3VuZC1jb2xvcjogI0NDRkZDQzsgYm9yZGVyOiAxcHggc29saWQgIzAwQ0MwMCB9IC8qIEdlbmVyaWMuSW5zZXJ0ZWQgKi8KLmdvIHsgY29sb3I6ICNBQUFBQUEgfSAvKiBHZW5lcmljLk91dHB1dCAqLwouZ3AgeyBjb2xvcjogIzAwMDA5OTsgfSAvKiBHZW5lcmljLlByb21wdCAqLwouZ3MgeyB9IC8qIEdlbmVyaWMuU3Ryb25nICovCi5ndSB7IGNvbG9yOiAjMDAzMzAwOyB9IC8qIEdlbmVyaWMuU3ViaGVhZGluZyAqLwouZ3QgeyBjb2xvcjogIzk5Q0M2NiB9IC8qIEdlbmVyaWMuVHJhY2ViYWNrICovCi5rYyB7IGNvbG9yOiAjMDA2Njk5OyB9IC8qIEtleXdvcmQuQ29uc3RhbnQgKi8KLmtkIHsgY29sb3I6ICMwMDY2OTk7IH0gLyogS2V5d29yZC5EZWNsYXJhdGlvbiAqLwoua24geyBjb2xvcjogIzAwNjY5OTsgfSAvKiBLZXl3b3JkLk5hbWVzcGFjZSAqLwoua3AgeyBjb2xvcjogIzAwNjY5OSB9IC8qIEtleXdvcmQuUHNldWRvICovCi5rciB7IGNvbG9yOiAjMDA2Njk5OyB9IC8qIEtleXdvcmQuUmVzZXJ2ZWQgKi8KLmt0IHsgY29sb3I6ICMwMDc3ODg7IH0gLyogS2V5d29yZC5UeXBlICovCi5tIHsgY29sb3I6ICNGRjY2MDAgfSAvKiBMaXRlcmFsLk51bWJlciAqLwoucyB7IGNvbG9yOiAjZDQ0OTUwIH0gLyogTGl0ZXJhbC5TdHJpbmcgKi8KLm5hIHsgY29sb3I6ICM0ZjlmY2YgfSAvKiBOYW1lLkF0dHJpYnV0ZSAqLwoubmIgeyBjb2xvcjogIzMzNjY2NiB9IC8qIE5hbWUuQnVpbHRpbiAqLwoubmMgeyBjb2xvcjogIzAwQUE4ODsgfSAvKiBOYW1lLkNsYXNzICovCi5ubyB7IGNvbG9yOiAjMzM2NjAwIH0gLyogTmFtZS5Db25zdGFudCAqLwoubmQgeyBjb2xvcjogIzk5OTlGRiB9IC8qIE5hbWUuRGVjb3JhdG9yICovCi5uaSB7IGNvbG9yOiAjOTk5OTk5OyB9IC8qIE5hbWUuRW50aXR5ICovCi5uZSB7IGNvbG9yOiAjQ0MwMDAwOyB9IC8qIE5hbWUuRXhjZXB0aW9uICovCi5uZiB7IGNvbG9yOiAjQ0MwMEZGIH0gLyogTmFtZS5GdW5jdGlvbiAqLwoubmwgeyBjb2xvcjogIzk5OTlGRiB9IC8qIE5hbWUuTGFiZWwgKi8KLm5uIHsgY29sb3I6ICMwMENDRkY7IH0gLyogTmFtZS5OYW1lc3BhY2UgKi8KLm50IHsgY29sb3I6ICMyZjZmOWY7IH0gLyogTmFtZS5UYWcgKi8KLm52IHsgY29sb3I6ICMwMDMzMzMgfSAvKiBOYW1lLlZhcmlhYmxlICovCi5vdyB7IGNvbG9yOiAjMDAwMDAwOyB9IC8qIE9wZXJhdG9yLldvcmQgKi8KLncgeyBjb2xvcjogI2JiYmJiYiB9IC8qIFRleHQuV2hpdGVzcGFjZSAqLwoubWYgeyBjb2xvcjogI0ZGNjYwMCB9IC8qIExpdGVyYWwuTnVtYmVyLkZsb2F0ICovCi5taCB7IGNvbG9yOiAjRkY2NjAwIH0gLyogTGl0ZXJhbC5OdW1iZXIuSGV4ICovCi5taSB7IGNvbG9yOiAjRkY2NjAwIH0gLyogTGl0ZXJhbC5OdW1iZXIuSW50ZWdlciAqLwoubW8geyBjb2xvcjogI0ZGNjYwMCB9IC8qIExpdGVyYWwuTnVtYmVyLk9jdCAqLwouc2IgeyBjb2xvcjogI0NDMzMwMCB9IC8qIExpdGVyYWwuU3RyaW5nLkJhY2t0aWNrICovCi5zYyB7IGNvbG9yOiAjQ0MzMzAwIH0gLyogTGl0ZXJhbC5TdHJpbmcuQ2hhciAqLwouc2QgeyBjb2xvcjogI0NDMzMwMDsgZm9udC1zdHlsZTogaXRhbGljIH0gLyogTGl0ZXJhbC5TdHJpbmcuRG9jICovCi5zMiB7IGNvbG9yOiAjQ0MzMzAwIH0gLyogTGl0ZXJhbC5TdHJpbmcuRG91YmxlICovCi5zZSB7IGNvbG9yOiAjQ0MzMzAwOyB9IC8qIExpdGVyYWwuU3RyaW5nLkVzY2FwZSAqLwouc2ggeyBjb2xvcjogI0NDMzMwMCB9IC8qIExpdGVyYWwuU3RyaW5nLkhlcmVkb2MgKi8KLnNpIHsgY29sb3I6ICNBQTAwMDAgfSAvKiBMaXRlcmFsLlN0cmluZy5JbnRlcnBvbCAqLwouc3ggeyBjb2xvcjogI0NDMzMwMCB9IC8qIExpdGVyYWwuU3RyaW5nLk90aGVyICovCi5zciB7IGNvbG9yOiAjMzNBQUFBIH0gLyogTGl0ZXJhbC5TdHJpbmcuUmVnZXggKi8KLnMxIHsgY29sb3I6ICNDQzMzMDAgfSAvKiBMaXRlcmFsLlN0cmluZy5TaW5nbGUgKi8KLnNzIHsgY29sb3I6ICNGRkNDMzMgfSAvKiBMaXRlcmFsLlN0cmluZy5TeW1ib2wgKi8KLmJwIHsgY29sb3I6ICMzMzY2NjYgfSAvKiBOYW1lLkJ1aWx0aW4uUHNldWRvICovCi52YyB7IGNvbG9yOiAjMDAzMzMzIH0gLyogTmFtZS5WYXJpYWJsZS5DbGFzcyAqLwoudmcgeyBjb2xvcjogIzAwMzMzMyB9IC8qIE5hbWUuVmFyaWFibGUuR2xvYmFsICovCi52aSB7IGNvbG9yOiAjMDAzMzMzIH0gLyogTmFtZS5WYXJpYWJsZS5JbnN0YW5jZSAqLwouaWwgeyBjb2xvcjogI0ZGNjYwMCB9IC8qIExpdGVyYWwuTnVtYmVyLkludGVnZXIuTG9uZyAqLwoKLmNzcyAubywKLmNzcyAubyArIC5udCwKLmNzcyAubnQgKyAubnQgeyBjb2xvcjogIzk5OTsgfQo=")

	tmpls["html/_aside.html"] = tostring("PGFzaWRlIGNsYXNzPSJzaWRlYmFyIj4KICA8ZGl2IGNsYXNzPSJjb250YWluZXIgc2lkZWJhci1zdGlja3kiPgogICAgPGRpdiBjbGFzcz0ic2lkZWJhci1hYm91dCI+CiAgICAgIDxhIGhyZWY9Ii8iPgogICAgICAgIDxoMT57ey5Bc2lkZS5TdHJpbmcxfX08L2gxPgogICAgICA8L2E+CiAgICAgIDxwIGNsYXNzPSJsZWFkIj57ey5Bc2lkZS5EZXNjfX08L3A+CiAgICAgIDxuYXY+CiAgICAgICAgPHVsIGNsYXNzPSJzaWRlYmFyLW5hdiI+CiAgICAgICAgICB7e3JhbmdlIC5Bc2lkZS5OYXZ9fQogICAgICAgICAgPGxpPjxhIGhyZWY9Int7LlNsdWd9fSI+e3suTmFtZX19PC9hPjwvbGk+CiAgICAgICAgICB7e2VuZH19CiAgICAgICAgPC91bD4KICAgICAgPC9uYXY+CiAgICAgIDxwPnt7LkFzaWRlLlN0cmluZzJ9fTwvcD4KICA8L2Rpdj4KPC9hc2lkZT4K")

	tmpls["html/_head.html"] = tostring("PG1ldGEgaHR0cC1lcXVpdj0iY29udGVudC10eXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiPgo8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PUFicmlsK0ZhdGZhY2V8UFQrU2Fuczo0MDAsNDAwaSw3MDAiPgo=")

	tmpls["html/_styles.html"] = tostring("PHN0eWxlPgogIHt7IHRlbXBsYXRlICJjc3MvcG9vbGUuY3NzIiB9fQogIHt7IHRlbXBsYXRlICJjc3Mvc3ludGF4LmNzcyIgfX0KICB7eyB0ZW1wbGF0ZSAiY3NzL2h5ZGUuY3NzIiB9fQogIHt7IHRlbXBsYXRlICJjc3MvbWFpbi5jc3MiIH19Cjwvc3R5bGU+Cg==")

	tmpls["html/item.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIj4KPGhlYWQ+CiAge3sgdGVtcGxhdGUgImh0bWwvX2hlYWQuaHRtbCIgJCB9fQogIDx0aXRsZT57ey5JdGVtLk5hbWV9fTwvdGl0bGU+CiAgPG1ldGEgbmFtZT0iZGVzY3JpcHRpb24iIGNvbnRlbnQ9Int7Lkl0ZW0uU2hvcnR9fSIgLz4KPC9oZWFkPgo8Ym9keSBjbGFzcz0idGhlbWUtYmFzZS0wOCI+CiAge3sgdGVtcGxhdGUgImh0bWwvX3N0eWxlcy5odG1sIiAkIH19CiAge3sgdGVtcGxhdGUgImh0bWwvX2FzaWRlLmh0bWwiICQgfX0KICA8bWFpbiBjbGFzcz0iY29udGVudCBjb250YWluZXIiPgogICAgPGRpdiBjbGFzcz0icG9zdCI+CiAgICAgIDxoMT57ey5JdGVtLk5hbWV9fTwvaDE+CiAgICAgIHt7aWYgLlNob3dEYXRlfX0KICAgICAgPHRpbWUgY2xhc3M9InBvc3QtZGF0ZSI+e3suSXRlbS5QcmV0dHlEYXRlfX08L3RpbWU+CiAgICAgIHt7ZW5kfX0KICAgICAgPGFydGljbGU+e3suSXRlbS5EZXNjfX08L2FydGljbGU+CiAgICA8L2Rpdj4KICAgIHt7aWYgLkl0ZW0uSGFzUHJldn19CiAgICA8ZGl2PgogICAgICA8YSBocmVmPSJ7ey5JdGVtLlByZXYuU2x1Z319Ij4KICAgICAgICDirIXvuI8ge3suSXRlbS5QcmV2Lk5hbWV9fQogICAgICA8L2E+CiAgICA8L2Rpdj4KICAgIHt7ZW5kfX0KICAgIHt7aWYgLkl0ZW0uSGFzTmV4dH19CiAgICA8ZGl2PgogICAgICA8YSBocmVmPSJ7ey5JdGVtLk5leHQuU2x1Z319Ij4KICAgICAgICDinqHvuI8ge3suSXRlbS5OZXh0Lk5hbWV9fQogICAgICA8L2E+CiAgICA8L2Rpdj4KICAgIHt7ZW5kfX0KICA8L21haW4+CjwvYm9keT4KPC9odG1sPgo=")

	tmpls["html/list.html"] = tostring("PCFET0NUWVBFIGh0bWw+CjxodG1sIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIj4KPGhlYWQ+CiAge3sgdGVtcGxhdGUgImh0bWwvX2hlYWQuaHRtbCIgJCB9fQogIDx0aXRsZT5UT0RPPC90aXRsZT4KICA8bWV0YSBuYW1lPSJkZXNjcmlwdGlvbiIgY29udGVudD0iVE9ETyIgLz4KPC9oZWFkPgo8Ym9keSBjbGFzcz0idGhlbWUtYmFzZS0wOCI+CiAgPHN0eWxlPgogICAge3sgdGVtcGxhdGUgImNzcy9wb29sZS5jc3MiIH19CiAgICB7eyB0ZW1wbGF0ZSAiY3NzL3N5bnRheC5jc3MiIH19CiAgICB7eyB0ZW1wbGF0ZSAiY3NzL2h5ZGUuY3NzIiB9fQogIDwvc3R5bGU+CiAge3sgdGVtcGxhdGUgImh0bWwvX2FzaWRlLmh0bWwiICQgfX0KICA8bWFpbiBjbGFzcz0iY29udGVudCBjb250YWluZXIiPgogICAgPGRpdiBjbGFzcz0icG9zdHMiPgogICAgICB7eyByYW5nZSAuTGlzdCB9fQogICAgICA8YXJ0aWNsZSBjbGFzcz0icG9zdCI+CiAgICAgICAgPGgxIGNsYXNzPSJwb3N0LXRpdGxlIj4KICAgICAgICAgIDxhIGhyZWY9Ii97ey5TbHVnfX0iPnt7Lk5hbWV9fTwvYT4KICAgICAgICA8L2gxPgogICAgICAgIDx0aW1lIGNsYXNzPSJwb3N0LWRhdGUiPnt7LlByZXR0eURhdGV9fTwvdGltZT4KICAgICAgICA8cD57ey5TaG9ydH19PC9wPgogICAgICAgIDxkaXYgY2xhc3M9InJlYWQtbW9yZS1saW5rIj4KICAgICAgICAgIDxhIGhyZWY9Ii97ey5TbHVnfX0iPlJlYWQgTW9yZeKApjwvYT4KICAgICAgICA8L2Rpdj4KICAgICAgPC9hcnRpY2xlPgogICAgICB7eyBlbmQgfX0KICAgIDwvZGl2PgogIDwvbWFpbj4KPC9ib2R5Pgo8L2h0bWw+Cg==")

	tmpls["img/_code-24px.svg"] = tostring("PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDBWMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNOS40IDE2LjZMNC44IDEybDQuNi00LjZMOCA2bC02IDYgNiA2IDEuNC0xLjR6bTUuMiAwbDQuNi00LjYtNC42LTQuNkwxNiA2bDYgNi02IDYtMS40LTEuNHoiLz48L3N2Zz4=")


D internal/v/v.go => internal/v/v.go +0 -1
@@ 1,1 0,0 @@
package v

D internal/v/v_test.go => internal/v/v_test.go +0 -1
@@ 1,1 0,0 @@
package v_test

M pkg/cms/cms.go => pkg/cms/cms.go +52 -3
@@ 24,6 24,39 @@ type Content struct {
	ContentID           string
	ContentParentTypeID string
	ContentValues       []ContentValue
	valueMap            map[string]ContentValue
}

func (c *Content) Val(key string) (string, bool) {
	val, ok := c.valueMap[key]
	if !ok {
		return "", false
	}
	return val.FieldValue, true
}

func (c *Content) Ref(key string) (*Content, bool) {
	val, ok := c.valueMap[key]
	if !ok || val.FieldReference == nil {
		return nil, false
	}

	ref := transform(*val.FieldReference)
	return &ref, true
}

func (c *Content) List(key string) ([]Content, bool) {
	val, ok := c.valueMap[key]
	if !ok || val.FieldReferenceList == nil {
		return nil, false
	}

	var list []Content
	for _, item := range val.FieldReferenceList {
		list = append(list, transform(item))
	}

	return list, true
}

type ContentList struct {


@@ 35,7 68,7 @@ type ContentValue struct {
	FieldType          string
	FieldName          string
	FieldValue         string
	FieldReference     Content
	FieldReference     *Content
	FieldReferenceList []Content
}



@@ 80,7 113,7 @@ func (cms *CMS) List(ctx context.Context, typeID int, order, field string) ([]Co
		return nil, err
	}

	return body.ContentList.ContentList, nil
	return transformList(body.ContentList.ContentList), nil
}

func (cms *CMS) Find(ctx context.Context, typeID int, field, query string) (*Content, error) {


@@ 114,5 147,21 @@ func (cms *CMS) Find(ctx context.Context, typeID int, field, query string) (*Con
		return nil, ErrNoContent
	}

	return &body.ContentList[0], nil
	c := transform(body.ContentList[0])
	return &c, nil
}

func transformList(in []Content) []Content {
	for i, c := range in {
		in[i] = transform(c)
	}
	return in
}

func transform(in Content) Content {
	in.valueMap = make(map[string]ContentValue)
	for _, val := range in.ContentValues {
		in.valueMap[val.FieldName] = val
	}
	return in
}

D vendor/github.com/gorilla/feeds/.travis.yml => vendor/github.com/gorilla/feeds/.travis.yml +0 -16
@@ 1,16 0,0 @@
language: go
sudo: false
matrix:
  include:
    - go: 1.8
    - go: 1.9
    - go: "1.10"
    - go: 1.x
    - go: tip
  allow_failures:
    - go: tip
script:
  - go get -t -v ./...
  - diff -u <(echo -n) <(gofmt -d -s .)
  - go vet .
  - go test -v -race ./...

D vendor/github.com/gorilla/feeds/AUTHORS => vendor/github.com/gorilla/feeds/AUTHORS +0 -29
@@ 1,29 0,0 @@
# This is the official list of gorilla/feeds authors for copyright purposes.
# Please keep the list sorted.

Dmitry Chestnykh <dmitry@codingrobots.com>
Eddie Scholtz <eascholtz@gmail.com>
Gabriel Simmer <bladesimmer@gmail.com>
Google LLC (https://opensource.google.com/)
honky <honky@defendtheplanet.net>
James Gregory <james@jagregory.com>
Jason Hall <imjasonh@gmail.com>
Jason Moiron <jmoiron@jmoiron.net>
Kamil Kisiel <kamil@kamilkisiel.net>
Kevin Stock <kevinstock@tantalic.com>
Markus Zimmermann <markus.zimmermann@nethead.at>
Matt Silverlock <matt@eatsleeprepeat.net>
Matthew Dawson <matthew@mjdsystems.ca>
Milan Aleksic <milanaleksic@gmail.com>
Milan Aleksić <milanaleksic@gmail.com>
nlimpid <jshuangzl@gmail.com>
Paul Petring <paul@defendtheplanet.net>
Sean Enck <enckse@users.noreply.github.com>
Sue Spence <virtuallysue@gmail.com>
Supermighty <ukiah@faction.com>
Toru Fukui <fukuimone@gmail.com>
Vabd <vabd@anon.acme>
Volker <lists.volker@gmail.com>
ZhiFeng Hu <hufeng1987@gmail.com>
weberc2 <weberc2@gmail.com>


D vendor/github.com/gorilla/feeds/LICENSE => vendor/github.com/gorilla/feeds/LICENSE +0 -22
@@ 1,22 0,0 @@
Copyright (c) 2013-2018 The Gorilla Feeds Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.

  Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

D vendor/github.com/gorilla/feeds/README.md => vendor/github.com/gorilla/feeds/README.md +0 -185
@@ 1,185 0,0 @@
## gorilla/feeds
[![GoDoc](https://godoc.org/github.com/gorilla/feeds?status.svg)](https://godoc.org/github.com/gorilla/feeds)
[![Build Status](https://travis-ci.org/gorilla/feeds.svg?branch=master)](https://travis-ci.org/gorilla/feeds)

feeds is a web feed generator library for generating RSS, Atom and JSON feeds from Go
applications.

### Goals

 * Provide a simple interface to create both Atom & RSS 2.0 feeds
 * Full support for [Atom][atom], [RSS 2.0][rss], and [JSON Feed Version 1][jsonfeed] spec elements
 * Ability to modify particulars for each spec

[atom]: https://tools.ietf.org/html/rfc4287
[rss]: http://www.rssboard.org/rss-specification
[jsonfeed]: https://jsonfeed.org/version/1

### Usage

```go
package main

import (
    "fmt"
    "log"
    "time"
    "github.com/gorilla/feeds"
)

func main() {
    now := time.Now()
    feed := &feeds.Feed{
        Title:       "jmoiron.net blog",
        Link:        &feeds.Link{Href: "http://jmoiron.net/blog"},
        Description: "discussion about tech, footie, photos",
        Author:      &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
        Created:     now,
    }

    feed.Items = []*feeds.Item{
        &feeds.Item{
            Title:       "Limiting Concurrency in Go",
            Link:        &feeds.Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
            Description: "A discussion on controlled parallelism in golang",
            Author:      &feeds.Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
            Created:     now,
        },
        &feeds.Item{
            Title:       "Logic-less Template Redux",
            Link:        &feeds.Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
            Description: "More thoughts on logicless templates",
            Created:     now,
        },
        &feeds.Item{
            Title:       "Idiomatic Code Reuse in Go",
            Link:        &feeds.Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
            Description: "How to use interfaces <em>effectively</em>",
            Created:     now,
        },
    }

    atom, err := feed.ToAtom()
    if err != nil {
        log.Fatal(err)
    }

    rss, err := feed.ToRss()
    if err != nil {
        log.Fatal(err)
    }

    json, err := feed.ToJSON()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(atom, "\n", rss, "\n", json)
}
```

Outputs:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>jmoiron.net blog</title>
  <link href="http://jmoiron.net/blog"></link>
  <id>http://jmoiron.net/blog</id>
  <updated>2013-01-16T03:26:01-05:00</updated>
  <summary>discussion about tech, footie, photos</summary>
  <entry>
    <title>Limiting Concurrency in Go</title>
    <link href="http://jmoiron.net/blog/limiting-concurrency-in-go/"></link>
    <updated>2013-01-16T03:26:01-05:00</updated>
    <id>tag:jmoiron.net,2013-01-16:/blog/limiting-concurrency-in-go/</id>
    <summary type="html">A discussion on controlled parallelism in golang</summary>
    <author>
      <name>Jason Moiron</name>
      <email>jmoiron@jmoiron.net</email>
    </author>
  </entry>
  <entry>
    <title>Logic-less Template Redux</title>
    <link href="http://jmoiron.net/blog/logicless-template-redux/"></link>
    <updated>2013-01-16T03:26:01-05:00</updated>
    <id>tag:jmoiron.net,2013-01-16:/blog/logicless-template-redux/</id>
    <summary type="html">More thoughts on logicless templates</summary>
    <author></author>
  </entry>
  <entry>
    <title>Idiomatic Code Reuse in Go</title>
    <link href="http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"></link>
    <updated>2013-01-16T03:26:01-05:00</updated>
    <id>tag:jmoiron.net,2013-01-16:/blog/idiomatic-code-reuse-in-go/</id>
    <summary type="html">How to use interfaces &lt;em&gt;effectively&lt;/em&gt;</summary>
    <author></author>
  </entry>
</feed>

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>jmoiron.net blog</title>
    <link>http://jmoiron.net/blog</link>
    <description>discussion about tech, footie, photos</description>
    <managingEditor>jmoiron@jmoiron.net (Jason Moiron)</managingEditor>
    <pubDate>2013-01-16T03:22:24-05:00</pubDate>
    <item>
      <title>Limiting Concurrency in Go</title>
      <link>http://jmoiron.net/blog/limiting-concurrency-in-go/</link>
      <description>A discussion on controlled parallelism in golang</description>
      <pubDate>2013-01-16T03:22:24-05:00</pubDate>
    </item>
    <item>
      <title>Logic-less Template Redux</title>
      <link>http://jmoiron.net/blog/logicless-template-redux/</link>
      <description>More thoughts on logicless templates</description>
      <pubDate>2013-01-16T03:22:24-05:00</pubDate>
    </item>
    <item>
      <title>Idiomatic Code Reuse in Go</title>
      <link>http://jmoiron.net/blog/idiomatic-code-reuse-in-go/</link>
      <description>How to use interfaces &lt;em&gt;effectively&lt;/em&gt;</description>
      <pubDate>2013-01-16T03:22:24-05:00</pubDate>
    </item>
  </channel>
</rss>

{
  "version": "https://jsonfeed.org/version/1",
  "title": "jmoiron.net blog",
  "home_page_url": "http://jmoiron.net/blog",
  "description": "discussion about tech, footie, photos",
  "author": {
    "name": "Jason Moiron"
  },
  "items": [
    {
      "id": "",
      "url": "http://jmoiron.net/blog/limiting-concurrency-in-go/",
      "title": "Limiting Concurrency in Go",
      "summary": "A discussion on controlled parallelism in golang",
      "date_published": "2013-01-16T03:22:24.530817846-05:00",
      "author": {
        "name": "Jason Moiron"
      }
    },
    {
      "id": "",
      "url": "http://jmoiron.net/blog/logicless-template-redux/",
      "title": "Logic-less Template Redux",
      "summary": "More thoughts on logicless templates",
      "date_published": "2013-01-16T03:22:24.530817846-05:00"
    },
    {
      "id": "",
      "url": "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/",
      "title": "Idiomatic Code Reuse in Go",
      "summary": "How to use interfaces \u003cem\u003eeffectively\u003c/em\u003e",
      "date_published": "2013-01-16T03:22:24.530817846-05:00"
    }
  ]
}
```


D vendor/github.com/gorilla/feeds/atom.go => vendor/github.com/gorilla/feeds/atom.go +0 -169
@@ 1,169 0,0 @@
package feeds

import (
	"encoding/xml"
	"fmt"
	"net/url"
	"time"
)

// Generates Atom feed as XML

const ns = "http://www.w3.org/2005/Atom"

type AtomPerson struct {
	Name  string `xml:"name,omitempty"`
	Uri   string `xml:"uri,omitempty"`
	Email string `xml:"email,omitempty"`
}

type AtomSummary struct {
	XMLName xml.Name `xml:"summary"`
	Content string   `xml:",chardata"`
	Type    string   `xml:"type,attr"`
}

type AtomContent struct {
	XMLName xml.Name `xml:"content"`
	Content string   `xml:",chardata"`
	Type    string   `xml:"type,attr"`
}

type AtomAuthor struct {
	XMLName xml.Name `xml:"author"`
	AtomPerson
}

type AtomContributor struct {
	XMLName xml.Name `xml:"contributor"`
	AtomPerson
}

type AtomEntry struct {
	XMLName     xml.Name `xml:"entry"`
	Xmlns       string   `xml:"xmlns,attr,omitempty"`
	Title       string   `xml:"title"`   // required
	Updated     string   `xml:"updated"` // required
	Id          string   `xml:"id"`      // required
	Category    string   `xml:"category,omitempty"`
	Content     *AtomContent
	Rights      string `xml:"rights,omitempty"`
	Source      string `xml:"source,omitempty"`
	Published   string `xml:"published,omitempty"`
	Contributor *AtomContributor
	Links       []AtomLink   // required if no child 'content' elements
	Summary     *AtomSummary // required if content has src or content is base64
	Author      *AtomAuthor  // required if feed lacks an author
}

// Multiple links with different rel can coexist
type AtomLink struct {
	//Atom 1.0 <link rel="enclosure" type="audio/mpeg" title="MP3" href="http://www.example.org/myaudiofile.mp3" length="1234" />
	XMLName xml.Name `xml:"link"`
	Href    string   `xml:"href,attr"`
	Rel     string   `xml:"rel,attr,omitempty"`
	Type    string   `xml:"type,attr,omitempty"`
	Length  string   `xml:"length,attr,omitempty"`
}

type AtomFeed struct {
	XMLName     xml.Name `xml:"feed"`
	Xmlns       string   `xml:"xmlns,attr"`
	Title       string   `xml:"title"`   // required
	Id          string   `xml:"id"`      // required
	Updated     string   `xml:"updated"` // required
	Category    string   `xml:"category,omitempty"`
	Icon        string   `xml:"icon,omitempty"`
	Logo        string   `xml:"logo,omitempty"`
	Rights      string   `xml:"rights,omitempty"` // copyright used
	Subtitle    string   `xml:"subtitle,omitempty"`
	Link        *AtomLink
	Author      *AtomAuthor `xml:"author,omitempty"`
	Contributor *AtomContributor
	Entries     []*AtomEntry `xml:"entry"`
}

type Atom struct {
	*Feed
}

func newAtomEntry(i *Item) *AtomEntry {
	id := i.Id
	// assume the description is html
	s := &AtomSummary{Content: i.Description, Type: "html"}

	if len(id) == 0 {
		// if there's no id set, try to create one, either from data or just a uuid
		if len(i.Link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) {
			dateStr := anyTimeFormat("2006-01-02", i.Updated, i.Created)
			host, path := i.Link.Href, "/invalid.html"
			if url, err := url.Parse(i.Link.Href); err == nil {
				host, path = url.Host, url.Path
			}
			id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path)
		} else {
			id = "urn:uuid:" + NewUUID().String()
		}
	}
	var name, email string
	if i.Author != nil {
		name, email = i.Author.Name, i.Author.Email
	}

	link_rel := i.Link.Rel
	if link_rel == "" {
		link_rel = "alternate"
	}
	x := &AtomEntry{
		Title:   i.Title,
		Links:   []AtomLink{{Href: i.Link.Href, Rel: link_rel, Type: i.Link.Type}},
		Id:      id,
		Updated: anyTimeFormat(time.RFC3339, i.Updated, i.Created),
		Summary: s,
	}

	// if there's a content, assume it's html
	if len(i.Content) > 0 {
		x.Content = &AtomContent{Content: i.Content, Type: "html"}
	}

	if i.Enclosure != nil && link_rel != "enclosure" {
		x.Links = append(x.Links, AtomLink{Href: i.Enclosure.Url, Rel: "enclosure", Type: i.Enclosure.Type, Length: i.Enclosure.Length})
	}

	if len(name) > 0 || len(email) > 0 {
		x.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: name, Email: email}}
	}
	return x
}

// create a new AtomFeed with a generic Feed struct's data
func (a *Atom) AtomFeed() *AtomFeed {
	updated := anyTimeFormat(time.RFC3339, a.Updated, a.Created)
	feed := &AtomFeed{
		Xmlns:    ns,
		Title:    a.Title,
		Link:     &AtomLink{Href: a.Link.Href, Rel: a.Link.Rel},
		Subtitle: a.Description,
		Id:       a.Link.Href,
		Updated:  updated,
		Rights:   a.Copyright,
	}
	if a.Author != nil {
		feed.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: a.Author.Name, Email: a.Author.Email}}
	}
	for _, e := range a.Items {
		feed.Entries = append(feed.Entries, newAtomEntry(e))
	}
	return feed
}

// FeedXml returns an XML-Ready object for an Atom object
func (a *Atom) FeedXml() interface{} {
	return a.AtomFeed()
}

// FeedXml returns an XML-ready object for an AtomFeed object
func (a *AtomFeed) FeedXml() interface{} {
	return a
}

D vendor/github.com/gorilla/feeds/doc.go => vendor/github.com/gorilla/feeds/doc.go +0 -73
@@ 1,73 0,0 @@
/*
Syndication (feed) generator library for golang.

Installing

	go get github.com/gorilla/feeds

Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS, Atom and JSON Feed specific RssFeed, AtomFeed and JSONFeed objects which allow access to all of each spec's defined elements.

Examples

Create a Feed and some Items in that feed using the generic interfaces:

	import (
		"time"
		. "github.com/gorilla/feeds"
	)

	now = time.Now()

	feed := &Feed{
		Title:       "jmoiron.net blog",
		Link:        &Link{Href: "http://jmoiron.net/blog"},
		Description: "discussion about tech, footie, photos",
		Author:      &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
		Created:     now,
		Copyright:   "This work is copyright © Benjamin Button",
	}

	feed.Items = []*Item{
		&Item{
			Title:       "Limiting Concurrency in Go",
			Link:        &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
			Description: "A discussion on controlled parallelism in golang",
			Author:      &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
			Created:     now,
		},
		&Item{
			Title:       "Logic-less Template Redux",
			Link:        &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
			Description: "More thoughts on logicless templates",
			Created:     now,
		},
		&Item{
			Title:       "Idiomatic Code Reuse in Go",
			Link:        &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
			Description: "How to use interfaces <em>effectively</em>",
			Created:     now,
		},
	}

From here, you can output Atom, RSS, or JSON Feed versions of this feed easily

	atom, err := feed.ToAtom()
	rss, err := feed.ToRss()
	json, err := feed.ToJSON()

You can also get access to the underlying objects that feeds uses to export its XML

	atomFeed := (&Atom{Feed: feed}).AtomFeed()
	rssFeed := (&Rss{Feed: feed}).RssFeed()
	jsonFeed := (&JSON{Feed: feed}).JSONFeed()

From here, you can modify or add each syndication's specific fields before outputting

	atomFeed.Subtitle = "plays the blues"
	atom, err := ToXML(atomFeed)
	rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)"
	rss, err := ToXML(rssFeed)
	jsonFeed.NextUrl = "https://www.example.com/feed.json?page=2"
	json, err := jsonFeed.ToJSON()
*/
package feeds

D vendor/github.com/gorilla/feeds/feed.go => vendor/github.com/gorilla/feeds/feed.go +0 -145
@@ 1,145 0,0 @@
package feeds

import (
	"encoding/json"
	"encoding/xml"
	"io"
	"sort"
	"time"
)

type Link struct {
	Href, Rel, Type, Length string
}

type Author struct {
	Name, Email string
}

type Image struct {
	Url, Title, Link string
	Width, Height    int
}

type Enclosure struct {
	Url, Length, Type string
}

type Item struct {
	Title       string
	Link        *Link
	Source      *Link
	Author      *Author
	Description string // used as description in rss, summary in atom
	Id          string // used as guid in rss, id in atom
	Updated     time.Time
	Created     time.Time
	Enclosure   *Enclosure
	Content     string
}

type Feed struct {
	Title       string
	Link        *Link
	Description string
	Author      *Author
	Updated     time.Time
	Created     time.Time
	Id          string
	Subtitle    string
	Items       []*Item
	Copyright   string
	Image       *Image
}

// add a new Item to a Feed
func (f *Feed) Add(item *Item) {
	f.Items = append(f.Items, item)
}

// returns the first non-zero time formatted as a string or ""
func anyTimeFormat(format string, times ...time.Time) string {
	for _, t := range times {
		if !t.IsZero() {
			return t.Format(format)
		}
	}
	return ""
}

// interface used by ToXML to get a object suitable for exporting XML.
type XmlFeed interface {
	FeedXml() interface{}
}

// turn a feed object (either a Feed, AtomFeed, or RssFeed) into xml
// returns an error if xml marshaling fails
func ToXML(feed XmlFeed) (string, error) {
	x := feed.FeedXml()
	data, err := xml.MarshalIndent(x, "", "  ")
	if err != nil {
		return "", err
	}
	// strip empty line from default xml header
	s := xml.Header[:len(xml.Header)-1] + string(data)
	return s, nil
}

// WriteXML writes a feed object (either a Feed, AtomFeed, or RssFeed) as XML into
// the writer. Returns an error if XML marshaling fails.
func WriteXML(feed XmlFeed, w io.Writer) error {
	x := feed.FeedXml()
	// write default xml header, without the newline
	if _, err := w.Write([]byte(xml.Header[:len(xml.Header)-1])); err != nil {
		return err
	}
	e := xml.NewEncoder(w)
	e.Indent("", "  ")
	return e.Encode(x)
}

// creates an Atom representation of this feed
func (f *Feed) ToAtom() (string, error) {
	a := &Atom{f}
	return ToXML(a)
}

// WriteAtom writes an Atom representation of this feed to the writer.
func (f *Feed) WriteAtom(w io.Writer) error {
	return WriteXML(&Atom{f}, w)
}

// creates an Rss representation of this feed
func (f *Feed) ToRss() (string, error) {
	r := &Rss{f}
	return ToXML(r)
}

// WriteRss writes an RSS representation of this feed to the writer.
func (f *Feed) WriteRss(w io.Writer) error {
	return WriteXML(&Rss{f}, w)
}

// ToJSON creates a JSON Feed representation of this feed
func (f *Feed) ToJSON() (string, error) {
	j := &JSON{f}
	return j.ToJSON()
}

// WriteJSON writes an JSON representation of this feed to the writer.
func (f *Feed) WriteJSON(w io.Writer) error {
	j := &JSON{f}
	feed := j.JSONFeed()

	e := json.NewEncoder(w)
	e.SetIndent("", "  ")
	return e.Encode(feed)
}

// Sort sorts the Items in the feed with the given less function.
func (f *Feed) Sort(less func(a, b *Item) bool) {
	lessFunc := func(i, j int) bool {
		return less(f.Items[i], f.Items[j])
	}
	sort.SliceStable(f.Items, lessFunc)
}

D vendor/github.com/gorilla/feeds/json.go => vendor/github.com/gorilla/feeds/json.go +0 -183
@@ 1,183 0,0 @@
package feeds

import (
	"encoding/json"
	"strings"
	"time"
)

const jsonFeedVersion = "https://jsonfeed.org/version/1"

// JSONAuthor represents the author of the feed or of an individual item
// in the feed
type JSONAuthor struct {
	Name   string `json:"name,omitempty"`
	Url    string `json:"url,omitempty"`
	Avatar string `json:"avatar,omitempty"`
}

// JSONAttachment represents a related resource. Podcasts, for instance, would
// include an attachment that’s an audio or video file.
type JSONAttachment struct {
	Url      string        `json:"url,omitempty"`
	MIMEType string        `json:"mime_type,omitempty"`
	Title    string        `json:"title,omitempty"`
	Size     int32         `json:"size,omitempty"`
	Duration time.Duration `json:"duration_in_seconds,omitempty"`
}

// MarshalJSON implements the json.Marshaler interface.
// The Duration field is marshaled in seconds, all other fields are marshaled
// based upon the definitions in struct tags.
func (a *JSONAttachment) MarshalJSON() ([]byte, error) {
	type EmbeddedJSONAttachment JSONAttachment
	return json.Marshal(&struct {
		Duration float64 `json:"duration_in_seconds,omitempty"`
		*EmbeddedJSONAttachment
	}{
		EmbeddedJSONAttachment: (*EmbeddedJSONAttachment)(a),
		Duration:               a.Duration.Seconds(),
	})
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// The Duration field is expected to be in seconds, all other field types
// match the struct definition.
func (a *JSONAttachment) UnmarshalJSON(data []byte) error {
	type EmbeddedJSONAttachment JSONAttachment
	var raw struct {
		Duration float64 `json:"duration_in_seconds,omitempty"`
		*EmbeddedJSONAttachment
	}
	raw.EmbeddedJSONAttachment = (*EmbeddedJSONAttachment)(a)

	err := json.Unmarshal(data, &raw)
	if err != nil {
		return err
	}

	if raw.Duration > 0 {
		nsec := int64(raw.Duration * float64(time.Second))
		raw.EmbeddedJSONAttachment.Duration = time.Duration(nsec)
	}

	return nil
}

// JSONItem represents a single entry/post for the feed.
type JSONItem struct {
	Id            string           `json:"id"`
	Url           string           `json:"url,omitempty"`
	ExternalUrl   string           `json:"external_url,omitempty"`
	Title         string           `json:"title,omitempty"`
	ContentHTML   string           `json:"content_html,omitempty"`
	ContentText   string           `json:"content_text,omitempty"`
	Summary       string           `json:"summary,omitempty"`
	Image         string           `json:"image,omitempty"`
	BannerImage   string           `json:"banner_,omitempty"`
	PublishedDate *time.Time       `json:"date_published,omitempty"`
	ModifiedDate  *time.Time       `json:"date_modified,omitempty"`
	Author        *JSONAuthor      `json:"author,omitempty"`
	Tags          []string         `json:"tags,omitempty"`
	Attachments   []JSONAttachment `json:"attachments,omitempty"`
}

// JSONHub describes an endpoint that can be used to subscribe to real-time
// notifications from the publisher of this feed.
type JSONHub struct {
	Type string `json:"type"`
	Url  string `json:"url"`
}

// JSONFeed represents a syndication feed in the JSON Feed Version 1 format.
// Matching the specification found here: https://jsonfeed.org/version/1.
type JSONFeed struct {
	Version     string      `json:"version"`
	Title       string      `json:"title"`
	HomePageUrl string      `json:"home_page_url,omitempty"`
	FeedUrl     string      `json:"feed_url,omitempty"`
	Description string      `json:"description,omitempty"`
	UserComment string      `json:"user_comment,omitempty"`
	NextUrl     string      `json:"next_url,omitempty"`
	Icon        string      `json:"icon,omitempty"`
	Favicon     string      `json:"favicon,omitempty"`
	Author      *JSONAuthor `json:"author,omitempty"`
	Expired     *bool       `json:"expired,omitempty"`
	Hubs        []*JSONItem `json:"hubs,omitempty"`
	Items       []*JSONItem `json:"items,omitempty"`
}

// JSON is used to convert a generic Feed to a JSONFeed.
type JSON struct {
	*Feed
}

// ToJSON encodes f into a JSON string. Returns an error if marshalling fails.
func (f *JSON) ToJSON() (string, error) {
	return f.JSONFeed().ToJSON()
}

// ToJSON encodes f into a JSON string. Returns an error if marshalling fails.
func (f *JSONFeed) ToJSON() (string, error) {
	data, err := json.MarshalIndent(f, "", "  ")
	if err != nil {
		return "", err
	}

	return string(data), nil
}

// JSONFeed creates a new JSONFeed with a generic Feed struct's data.
func (f *JSON) JSONFeed() *JSONFeed {
	feed := &JSONFeed{
		Version:     jsonFeedVersion,
		Title:       f.Title,
		Description: f.Description,
	}

	if f.Link != nil {
		feed.HomePageUrl = f.Link.Href
	}
	if f.Author != nil {
		feed.Author = &JSONAuthor{
			Name: f.Author.Name,
		}
	}
	for _, e := range f.Items {
		feed.Items = append(feed.Items, newJSONItem(e))
	}
	return feed
}

func newJSONItem(i *Item) *JSONItem {
	item := &JSONItem{
		Id:      i.Id,
		Title:   i.Title,
		Summary: i.Description,

		ContentHTML: i.Content,
	}

	if i.Link != nil {
		item.Url = i.Link.Href
	}
	if i.Source != nil {
		item.ExternalUrl = i.Source.Href
	}
	if i.Author != nil {
		item.Author = &JSONAuthor{
			Name: i.Author.Name,
		}
	}
	if !i.Created.IsZero() {
		item.PublishedDate = &i.Created
	}
	if !i.Updated.IsZero() {
		item.ModifiedDate = &i.Updated
	}
	if i.Enclosure != nil && strings.HasPrefix(i.Enclosure.Type, "image/") {
		item.Image = i.Enclosure.Url
	}

	return item
}

D vendor/github.com/gorilla/feeds/rss.go => vendor/github.com/gorilla/feeds/rss.go +0 -168
@@ 1,168 0,0 @@
package feeds

// rss support
// validation done according to spec here:
//    http://cyber.law.harvard.edu/rss/rss.html

import (
	"encoding/xml"
	"fmt"
	"time"
)

// private wrapper around the RssFeed which gives us the <rss>..</rss> xml
type RssFeedXml struct {
	XMLName          xml.Name `xml:"rss"`
	Version          string   `xml:"version,attr"`
	ContentNamespace string   `xml:"xmlns:content,attr"`
	Channel          *RssFeed
}

type RssContent struct {
	XMLName xml.Name `xml:"content:encoded"`
	Content string   `xml:",cdata"`
}

type RssImage struct {
	XMLName xml.Name `xml:"image"`
	Url     string   `xml:"url"`
	Title   string   `xml:"title"`
	Link    string   `xml:"link"`
	Width   int      `xml:"width,omitempty"`
	Height  int      `xml:"height,omitempty"`
}

type RssTextInput struct {
	XMLName     xml.Name `xml:"textInput"`
	Title       string   `xml:"title"`
	Description string   `xml:"description"`
	Name        string   `xml:"name"`
	Link        string   `xml:"link"`
}

type RssFeed struct {
	XMLName        xml.Name `xml:"channel"`
	Title          string   `xml:"title"`       // required
	Link           string   `xml:"link"`        // required
	Description    string   `xml:"description"` // required
	Language       string   `xml:"language,omitempty"`
	Copyright      string   `xml:"copyright,omitempty"`
	ManagingEditor string   `xml:"managingEditor,omitempty"` // Author used
	WebMaster      string   `xml:"webMaster,omitempty"`
	PubDate        string   `xml:"pubDate,omitempty"`       // created or updated
	LastBuildDate  string   `xml:"lastBuildDate,omitempty"` // updated used
	Category       string   `xml:"category,omitempty"`
	Generator      string   `xml:"generator,omitempty"`
	Docs           string   `xml:"docs,omitempty"`
	Cloud          string   `xml:"cloud,omitempty"`
	Ttl            int      `xml:"ttl,omitempty"`
	Rating         string   `xml:"rating,omitempty"`
	SkipHours      string   `xml:"skipHours,omitempty"`
	SkipDays       string   `xml:"skipDays,omitempty"`
	Image          *RssImage
	TextInput      *RssTextInput
	Items          []*RssItem `xml:"item"`
}

type RssItem struct {
	XMLName     xml.Name `xml:"item"`
	Title       string   `xml:"title"`       // required
	Link        string   `xml:"link"`        // required
	Description string   `xml:"description"` // required
	Content     *RssContent
	Author      string `xml:"author,omitempty"`
	Category    string `xml:"category,omitempty"`
	Comments    string `xml:"comments,omitempty"`
	Enclosure   *RssEnclosure
	Guid        string `xml:"guid,omitempty"`    // Id used
	PubDate     string `xml:"pubDate,omitempty"` // created or updated
	Source      string `xml:"source,omitempty"`
}

type RssEnclosure struct {
	//RSS 2.0 <enclosure url="http://example.com/file.mp3" length="123456789" type="audio/mpeg" />
	XMLName xml.Name `xml:"enclosure"`
	Url     string   `xml:"url,attr"`
	Length  string   `xml:"length,attr"`
	Type    string   `xml:"type,attr"`
}

type Rss struct {
	*Feed
}

// create a new RssItem with a generic Item struct's data
func newRssItem(i *Item) *RssItem {
	item := &RssItem{
		Title:       i.Title,
		Link:        i.Link.Href,
		Description: i.Description,
		Guid:        i.Id,
		PubDate:     anyTimeFormat(time.RFC1123Z, i.Created, i.Updated),
	}
	if len(i.Content) > 0 {
		item.Content = &RssContent{Content: i.Content}
	}
	if i.Source != nil {
		item.Source = i.Source.Href
	}

	// Define a closure
	if i.Enclosure != nil && i.Enclosure.Type != "" && i.Enclosure.Length != "" {
		item.Enclosure = &RssEnclosure{Url: i.Enclosure.Url, Type: i.Enclosure.Type, Length: i.Enclosure.Length}
	}

	if i.Author != nil {
		item.Author = i.Author.Name
	}
	return item
}

// create a new RssFeed with a generic Feed struct's data
func (r *Rss) RssFeed() *RssFeed {
	pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated)
	build := anyTimeFormat(time.RFC1123Z, r.Updated)
	author := ""
	if r.Author != nil {
		author = r.Author.Email
		if len(r.Author.Name) > 0 {
			author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name)
		}
	}

	var image *RssImage
	if r.Image != nil {
		image = &RssImage{Url: r.Image.Url, Title: r.Image.Title, Link: r.Image.Link, Width: r.Image.Width, Height: r.Image.Height}
	}

	channel := &RssFeed{
		Title:          r.Title,
		Link:           r.Link.Href,
		Description:    r.Description,
		ManagingEditor: author,
		PubDate:        pub,
		LastBuildDate:  build,
		Copyright:      r.Copyright,
		Image:          image,
	}
	for _, i := range r.Items {
		channel.Items = append(channel.Items, newRssItem(i))
	}
	return channel
}

// FeedXml returns an XML-Ready object for an Rss object
func (r *Rss) FeedXml() interface{} {
	// only generate version 2.0 feeds for now
	return r.RssFeed().FeedXml()

}

// FeedXml returns an XML-ready object for an RssFeed object
func (r *RssFeed) FeedXml() interface{} {
	return &RssFeedXml{
		Version:          "2.0",
		Channel:          r,
		ContentNamespace: "http://purl.org/rss/1.0/modules/content/",
	}
}

D vendor/github.com/gorilla/feeds/test.atom => vendor/github.com/gorilla/feeds/test.atom +0 -92
@@ 1,92 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns:atom="http://www.w3.org/2005/Atom">
        <title><![CDATA[Lorem ipsum feed for an interval of 1 minutes]]></title>
        <description><![CDATA[This is a constantly updating lorem ipsum feed]]></description>
        <link>http://example.com/</link>
        <generator>RSS for Node</generator>
        <lastBuildDate>Tue, 30 Oct 2018 23:22:37 GMT</lastBuildDate>
        <author><![CDATA[John Smith]]></author>
        <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
        <copyright><![CDATA[Michael Bertolacci, licensed under a Creative Commons Attribution 3.0 Unported License.]]></copyright>
        <ttl>60</ttl>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]></title>
            <description><![CDATA[Exercitation ut Lorem sint proident.]]></description>
            <link>http://example.com/test/1540941720</link>
            <guid isPermaLink="true">http://example.com/test/1540941720</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]></title>
            <description><![CDATA[Ea est do quis fugiat exercitation.]]></description>
            <link>http://example.com/test/1540941660</link>
            <guid isPermaLink="true">http://example.com/test/1540941660</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:21:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]></title>
            <description><![CDATA[Ipsum velit cillum ad laborum sit nulla exercitation consequat sint veniam culpa veniam voluptate incididunt.]]></description>
            <link>http://example.com/test/1540941600</link>
            <guid isPermaLink="true">http://example.com/test/1540941600</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:20:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]></title>
            <description><![CDATA[Ullamco pariatur aliqua consequat ea veniam id qui incididunt laborum.]]></description>
            <link>http://example.com/test/1540941540</link>
            <guid isPermaLink="true">http://example.com/test/1540941540</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:19:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]></title>
            <description><![CDATA[Velit proident aliquip aliquip anim mollit voluptate laboris voluptate et occaecat occaecat laboris ea nulla.]]></description>
            <link>http://example.com/test/1540941480</link>
            <guid isPermaLink="true">http://example.com/test/1540941480</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:18:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]></title>
            <description><![CDATA[Do in quis mollit consequat id in minim laborum sint exercitation laborum elit officia.]]></description>
            <link>http://example.com/test/1540941420</link>
            <guid isPermaLink="true">http://example.com/test/1540941420</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:17:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]></title>
            <description><![CDATA[Irure id sint ullamco Lorem magna consectetur officia adipisicing duis incididunt.]]></description>
            <link>http://example.com/test/1540941360</link>
            <guid isPermaLink="true">http://example.com/test/1540941360</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:16:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]></title>
            <description><![CDATA[Sunt anim excepteur esse nisi commodo culpa laborum exercitation ad anim ex elit.]]></description>
            <link>http://example.com/test/1540941300</link>
            <guid isPermaLink="true">http://example.com/test/1540941300</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:15:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]></title>
            <description><![CDATA[Excepteur aliquip fugiat ex labore nisi.]]></description>
            <link>http://example.com/test/1540941240</link>
            <guid isPermaLink="true">http://example.com/test/1540941240</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:14:00 GMT</pubDate>
        </entry>
        <entry>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]></title>
            <description><![CDATA[Id proident adipisicing proident pariatur aute pariatur pariatur dolor dolor in voluptate dolor.]]></description>
            <link>http://example.com/test/1540941180</link>
            <guid isPermaLink="true">http://example.com/test/1540941180</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:13:00 GMT</pubDate>
        </entry>
</feed>
\ No newline at end of file

D vendor/github.com/gorilla/feeds/test.rss => vendor/github.com/gorilla/feeds/test.rss +0 -96
@@ 1,96 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" 
    xmlns:content="http://purl.org/rss/1.0/modules/content/" 
    xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
        <title><![CDATA[Lorem ipsum feed for an interval of 1 minutes]]></title>
        <description><![CDATA[This is a constantly updating lorem ipsum feed]]></description>
        <link>http://example.com/</link>
        <generator>RSS for Node</generator>
        <lastBuildDate>Tue, 30 Oct 2018 23:22:37 GMT</lastBuildDate>
        <author><![CDATA[John Smith]]></author>
        <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
        <copyright><![CDATA[Michael Bertolacci, licensed under a Creative Commons Attribution 3.0 Unported License.]]></copyright>
        <ttl>60</ttl>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:22:00+00:00]]></title>
            <description><![CDATA[Exercitation ut Lorem sint proident.]]></description>
            <link>http://example.com/test/1540941720</link>
            <guid isPermaLink="true">http://example.com/test/1540941720</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:22:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:21:00+00:00]]></title>
            <description><![CDATA[Ea est do quis fugiat exercitation.]]></description>
            <link>http://example.com/test/1540941660</link>
            <guid isPermaLink="true">http://example.com/test/1540941660</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:21:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:20:00+00:00]]></title>
            <description><![CDATA[Ipsum velit cillum ad laborum sit nulla exercitation consequat sint veniam culpa veniam voluptate incididunt.]]></description>
            <link>http://example.com/test/1540941600</link>
            <guid isPermaLink="true">http://example.com/test/1540941600</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:20:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:19:00+00:00]]></title>
            <description><![CDATA[Ullamco pariatur aliqua consequat ea veniam id qui incididunt laborum.]]></description>
            <link>http://example.com/test/1540941540</link>
            <guid isPermaLink="true">http://example.com/test/1540941540</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:19:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:18:00+00:00]]></title>
            <description><![CDATA[Velit proident aliquip aliquip anim mollit voluptate laboris voluptate et occaecat occaecat laboris ea nulla.]]></description>
            <link>http://example.com/test/1540941480</link>
            <guid isPermaLink="true">http://example.com/test/1540941480</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:18:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:17:00+00:00]]></title>
            <description><![CDATA[Do in quis mollit consequat id in minim laborum sint exercitation laborum elit officia.]]></description>
            <link>http://example.com/test/1540941420</link>
            <guid isPermaLink="true">http://example.com/test/1540941420</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:17:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:16:00+00:00]]></title>
            <description><![CDATA[Irure id sint ullamco Lorem magna consectetur officia adipisicing duis incididunt.]]></description>
            <link>http://example.com/test/1540941360</link>
            <guid isPermaLink="true">http://example.com/test/1540941360</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:16:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:15:00+00:00]]></title>
            <description><![CDATA[Sunt anim excepteur esse nisi commodo culpa laborum exercitation ad anim ex elit.]]></description>
            <link>http://example.com/test/1540941300</link>
            <guid isPermaLink="true">http://example.com/test/1540941300</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:15:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:14:00+00:00]]></title>
            <description><![CDATA[Excepteur aliquip fugiat ex labore nisi.]]></description>
            <link>http://example.com/test/1540941240</link>
            <guid isPermaLink="true">http://example.com/test/1540941240</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:14:00 GMT</pubDate>
        </item>
        <item>
            <title><![CDATA[Lorem ipsum 2018-10-30T23:13:00+00:00]]></title>
            <description><![CDATA[Id proident adipisicing proident pariatur aute pariatur pariatur dolor dolor in voluptate dolor.]]></description>
            <link>http://example.com/test/1540941180</link>
            <guid isPermaLink="true">http://example.com/test/1540941180</guid>
            <dc:creator><![CDATA[John Smith]]></dc:creator>
            <pubDate>Tue, 30 Oct 2018 23:13:00 GMT</pubDate>
        </item>
    </channel>
</rss>
\ No newline at end of file

D vendor/github.com/gorilla/feeds/to-implement.md => vendor/github.com/gorilla/feeds/to-implement.md +0 -20
@@ 1,20 0,0 @@
[Full iTunes list](https://help.apple.com/itc/podcasts_connect/#/itcb54353390)

[Example of ideal iTunes RSS feed](https://help.apple.com/itc/podcasts_connect/#/itcbaf351599)

```
<itunes:author>
<itunes:block>
<itunes:catergory>
<itunes:image>
<itunes:duration>
<itunes:explicit>
<itunes:isClosedCaptioned>
<itunes:order>
<itunes:complete>
<itunes:new-feed-url>
<itunes:owner>
<itunes:subtitle>
<itunes:summary>
<language>
```
\ No newline at end of file

D vendor/github.com/gorilla/feeds/uuid.go => vendor/github.com/gorilla/feeds/uuid.go +0 -27
@@ 1,27 0,0 @@
package feeds

// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go

import (
	"crypto/rand"
	"fmt"
)

type UUID [16]byte

// create a new uuid v4
func NewUUID() *UUID {
	u := &UUID{}
	_, err := rand.Read(u[:16])
	if err != nil {
		panic(err)
	}

	u[8] = (u[8] | 0x80) & 0xBf
	u[6] = (u[6] | 0x40) & 0x4f
	return u
}

func (u *UUID) String() string {
	return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
}

M vendor/modules.txt => vendor/modules.txt +0 -5
@@ 1,10 1,5 @@
# git.sr.ht/~evanj/embed v0.0.0-20200525225021-2cde7dae7bfa
## explicit
# github.com/gorilla/feeds v1.1.1
## explicit
github.com/gorilla/feeds
# github.com/kr/pretty v0.2.0
## explicit
# github.com/tdewolff/minify/v2 v2.7.3
## explicit
github.com/tdewolff/minify/v2