Load HTML templates and SCSS styling
This commit is contained in:
parent
8daaa1b330
commit
d347551455
10 changed files with 581 additions and 22 deletions
|
@ -4,7 +4,7 @@ Prose is a blogging platform written in Go, which I am building to serve my own
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Blog posts should be created in the format `DATE-title-slug.md`. Work in progress posts should be stored as `WIP-title-slug.md`. Static content should be stored in the `static/` folder, appropriately arranged.
|
Blog posts should be created in the format `title-slug.md`. Work in progress posts should be stored as `WIP-title-slug.md`. Static content should be stored in the `static/` folder, appropriately arranged.
|
||||||
|
|
||||||
Posts will be served as `/title-slug`, and files like `static/random/file/structure.txt` will be served as `/random/file/structure.txt`. When title slugs and static files conflict, slugs will have higher precdence.
|
Posts will be served as `/title-slug`, and files like `static/random/file/structure.txt` will be served as `/random/file/structure.txt`. When title slugs and static files conflict, slugs will have higher precdence.
|
||||||
|
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -2,4 +2,12 @@ module prose
|
||||||
|
|
||||||
go 1.15
|
go 1.15
|
||||||
|
|
||||||
require github.com/yuin/goldmark v1.3.1
|
require (
|
||||||
|
github.com/aymerick/raymond v2.0.2+incompatible
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1
|
||||||
|
github.com/wellington/go-libsass v0.9.2
|
||||||
|
github.com/yuin/goldmark v1.3.1
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
||||||
|
github.com/yuin/goldmark-meta v1.0.0
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||||
|
)
|
||||||
|
|
60
go.sum
60
go.sum
|
@ -1,9 +1,60 @@
|
||||||
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
|
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
|
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a h1:3v1NrYWWqp2S72e4HLgxKt83B3l0lnORDholH/ihoMM=
|
||||||
|
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
|
||||||
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
|
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
|
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
|
github.com/aymerick/raymond v1.1.0 h1:phuNN2s67eI/HtO8CrvqFcdR2JP+BtkGJZ9n692Hr2Y=
|
||||||
|
github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0=
|
||||||
|
github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||||
|
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||||
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||||
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||||
|
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||||
|
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||||
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/wellington/go-libsass v0.9.2 h1:6Ims04UDdBs6/CGSVK5JC8FNikR5ssrsMMKE/uaO5Q8=
|
||||||
|
github.com/wellington/go-libsass v0.9.2/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs=
|
||||||
|
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I=
|
github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I=
|
||||||
github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
|
||||||
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
|
||||||
|
github.com/yuin/goldmark-meta v1.0.0 h1:ScsatUIT2gFS6azqzLGUjgOnELsBOxMXerM3ogdJhAM=
|
||||||
|
github.com/yuin/goldmark-meta v1.0.0/go.mod h1:zsNNOrZ4nLuyHAJeLQEZcQat8dm70SmB2kHbls092Gc=
|
||||||
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 h1:K+bMSIx9A7mLES1rtG+qKduLIXq40DAzYHtb0XuCukA=
|
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 h1:K+bMSIx9A7mLES1rtG+qKduLIXq40DAzYHtb0XuCukA=
|
||||||
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181/go.mod h1:dzYhVIwWCtzPAa4QP98wfB9+mzt33MSmM8wsKiMi2ow=
|
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181/go.mod h1:dzYhVIwWCtzPAa4QP98wfB9+mzt33MSmM8wsKiMi2ow=
|
||||||
gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 h1:oYrL81N608MLZhma3ruL8qTM4xcpYECGut8KSxRY59g=
|
gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 h1:oYrL81N608MLZhma3ruL8qTM4xcpYECGut8KSxRY59g=
|
||||||
|
@ -17,8 +68,17 @@ gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84/go.mod h1:
|
||||||
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f h1:Wku8eEdeJqIOFHtrfkYUByc4bCaTeA6fL0UJgfEiFMI=
|
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f h1:Wku8eEdeJqIOFHtrfkYUByc4bCaTeA6fL0UJgfEiFMI=
|
||||||
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs=
|
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs=
|
||||||
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638/go.mod h1:EGRJaqe2eO9XGmFtQCvV3Lm9NLico3UhFwUpCG/+mVU=
|
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638/go.mod h1:EGRJaqe2eO9XGmFtQCvV3Lm9NLico3UhFwUpCG/+mVU=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||||
|
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
61
page.go
61
page.go
|
@ -5,33 +5,74 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/aymerick/raymond"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
|
highlighting "github.com/yuin/goldmark-highlighting"
|
||||||
|
meta "github.com/yuin/goldmark-meta"
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
)
|
)
|
||||||
|
|
||||||
type page struct {
|
// Metadata stores the data about a page that needs to be visible
|
||||||
slug string
|
// at the home page.
|
||||||
|
type Metadata struct {
|
||||||
|
Title string
|
||||||
|
Summary string
|
||||||
|
Date string // TODO: better representation? time.Time might cause timezone issues...
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *page) render() ([]byte, error) {
|
// Page stores the contents of a blog post.
|
||||||
data, err := ioutil.ReadFile("posts/" + p.slug + ".md")
|
type Page struct {
|
||||||
|
Slug string
|
||||||
|
Metadata Metadata
|
||||||
|
Contents string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPage(slug string) (*Page, error) {
|
||||||
|
data, err := ioutil.ReadFile("posts/" + slug + ".md")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not read from %s.md: %s", p.slug, err)
|
return nil, fmt.Errorf("could not read file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
md := goldmark.New(
|
md := goldmark.New(
|
||||||
goldmark.WithExtensions(extension.Linkify),
|
goldmark.WithExtensions(
|
||||||
|
extension.Linkify,
|
||||||
|
extension.Strikethrough,
|
||||||
|
extension.Typographer,
|
||||||
|
meta.Meta,
|
||||||
|
highlighting.Highlighting,
|
||||||
|
),
|
||||||
goldmark.WithRendererOptions(
|
goldmark.WithRendererOptions(
|
||||||
html.WithUnsafe(),
|
html.WithUnsafe(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
var converted bytes.Buffer
|
var converted bytes.Buffer
|
||||||
err = md.Convert(data, &converted)
|
ctx := parser.NewContext()
|
||||||
|
err = md.Convert(data, &converted, parser.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Could not parse markdown from %s.md: %s", p.slug, err)
|
return nil, fmt.Errorf("could not parse markdown: %s", err)
|
||||||
|
}
|
||||||
|
mdMap, err := meta.TryGet(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse metadata: %s", err)
|
||||||
|
}
|
||||||
|
var metadata Metadata
|
||||||
|
err = mapstructure.Decode(mdMap, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not destructure metadata: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return converted.Bytes(), nil
|
page := &Page{
|
||||||
|
Slug: slug,
|
||||||
|
Metadata: metadata,
|
||||||
|
Contents: converted.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return page, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Page) render(tpl *raymond.Template) (string, error) {
|
||||||
|
return tpl.Exec(p)
|
||||||
}
|
}
|
||||||
|
|
121
server.go
121
server.go
|
@ -2,15 +2,23 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/aymerick/raymond"
|
||||||
|
"github.com/wellington/go-libsass"
|
||||||
)
|
)
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
pages []page
|
pages []*Page
|
||||||
staticHandler http.Handler
|
staticHandler http.Handler
|
||||||
|
pageTpl *raymond.Template
|
||||||
|
fullPostTpl *raymond.Template
|
||||||
|
summaryTpl *raymond.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer() (*server, error) {
|
func newServer() (*server, error) {
|
||||||
|
@ -21,7 +29,7 @@ func newServer() (*server, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &server{
|
s := &server{
|
||||||
pages: make([]page, 0, len(files)),
|
pages: make([]*Page, 0, len(files)),
|
||||||
staticHandler: http.FileServer(http.Dir("static/")),
|
staticHandler: http.FileServer(http.Dir("static/")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,8 +37,71 @@ func newServer() (*server, error) {
|
||||||
filename := f.Name()
|
filename := f.Name()
|
||||||
|
|
||||||
if strings.HasSuffix(filename, ".md") {
|
if strings.HasSuffix(filename, ".md") {
|
||||||
s.pages = append(s.pages, page{slug: strings.TrimSuffix(filename, ".md")})
|
page, err := newPage(strings.TrimSuffix(filename, ".md"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not render %s: %s", filename, err)
|
||||||
}
|
}
|
||||||
|
s.pages = append(s.pages, page)
|
||||||
|
log.Printf("Loaded page %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.pageTpl, err = raymond.ParseFile("templates/page.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse page template")
|
||||||
|
}
|
||||||
|
log.Printf("Loaded page template")
|
||||||
|
|
||||||
|
s.fullPostTpl, err = raymond.ParseFile("templates/fullpost.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse full post template")
|
||||||
|
}
|
||||||
|
log.Printf("Loaded full post template")
|
||||||
|
|
||||||
|
s.summaryTpl, err = raymond.ParseFile("templates/summary.html")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse summary template")
|
||||||
|
}
|
||||||
|
log.Printf("Loaded summary template")
|
||||||
|
|
||||||
|
styles, err := ioutil.ReadDir("styles/")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not load styles directory: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range styles {
|
||||||
|
filename := s.Name()
|
||||||
|
in, err := os.Open("styles/" + filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not open style infile %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(filename, ".scss") {
|
||||||
|
outFilename := strings.TrimSuffix(filename, ".scss") + ".css"
|
||||||
|
out, err := os.Create("static/css/" + outFilename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not open style outfile %s: %s", outFilename, err)
|
||||||
|
}
|
||||||
|
comp, err := libsass.New(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not start sass compiler for file %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
if err = comp.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not generate stylesheet %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
} else if strings.HasSuffix(filename, ".css") {
|
||||||
|
out, err := os.Create("static/css/" + filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not open style outfile %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not copy stylesheet %s: %s", filename, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Skipping stylesheet %s, don't know how to handle", filename)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("Loaded stylesheet %s", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -50,16 +121,20 @@ func (s *server) router(res http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range s.pages {
|
for _, p := range s.pages {
|
||||||
if p.slug == slug {
|
if p.Slug == slug {
|
||||||
buf, err := p.render()
|
res.Header().Add("content-type", "text/html")
|
||||||
|
|
||||||
|
contents, err := p.render(s.fullPostTpl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.WriteHeader(http.StatusInternalServerError)
|
s.errorInRequest(res, req, err)
|
||||||
res.Write([]byte("oh no"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Write(buf)
|
page, err := s.createPage(p.Metadata.Title, contents)
|
||||||
|
if err != nil {
|
||||||
|
s.errorInRequest(res, req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Write([]byte(page))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,12 +142,38 @@ func (s *server) router(res http.ResponseWriter, req *http.Request) {
|
||||||
s.staticHandler.ServeHTTP(res, req)
|
s.staticHandler.ServeHTTP(res, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *server) errorInRequest(res http.ResponseWriter, req *http.Request, err error) {
|
||||||
|
res.WriteHeader(http.StatusInternalServerError)
|
||||||
|
res.Write([]byte("oh no"))
|
||||||
|
log.Printf("ERR %s: %s", req.URL.Path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) createPage(title, contents string) (string, error) {
|
||||||
|
ctx := map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
"contents": contents,
|
||||||
|
}
|
||||||
|
return s.pageTpl.Exec(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *server) homePage(res http.ResponseWriter, req *http.Request) {
|
func (s *server) homePage(res http.ResponseWriter, req *http.Request) {
|
||||||
res.Header().Add("content-type", "text/html")
|
res.Header().Add("content-type", "text/html")
|
||||||
|
|
||||||
res.Write([]byte("<h1>blog</h1>"))
|
var posts string
|
||||||
|
|
||||||
for _, p := range s.pages {
|
for _, p := range s.pages {
|
||||||
fmt.Fprintf(res, "<a href=\"/%s\">%s</a><br>", p.slug, p.slug)
|
summary, err := p.render(s.summaryTpl)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("could not render page summary for %s", p.Slug)
|
||||||
}
|
}
|
||||||
|
posts = posts + summary
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := s.createPage("Home", posts)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.errorInRequest(res, req, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Write([]byte(page))
|
||||||
}
|
}
|
||||||
|
|
1
static/.gitignore
vendored
Normal file
1
static/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
css
|
306
styles/main.scss
Normal file
306
styles/main.scss
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin transition-all {
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 14pt;
|
||||||
|
min-height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ls: 1.4rem;
|
||||||
|
|
||||||
|
$darkMode: true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$bgColor: white;
|
||||||
|
$bodyColor: #555;
|
||||||
|
$strongColor: #333;
|
||||||
|
$lightColor: #999;
|
||||||
|
$accentColor: #3498db;
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
background-color: $bgColor;
|
||||||
|
font-family: 'Nunito', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
color: $bodyColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 5000;
|
||||||
|
background-image: linear-gradient(transparent 93%, rgba(0,0,0,0.3) 93%, rgba(0,0,0,0.3));
|
||||||
|
background-size: 1*$ls 1*$ls;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.grid-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0.5*$ls;
|
||||||
|
right: 0.5*$ls;
|
||||||
|
height: 2*$ls;
|
||||||
|
padding: 0 1*$ls;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
background: $accentColor;
|
||||||
|
color: white;
|
||||||
|
z-index: 6000;
|
||||||
|
border-radius: 0.2*$ls;
|
||||||
|
box-shadow: 0 0.1*$ls 0.1*$ls 0.0125*$ls rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
height: 3*$ls;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 40*$ls;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
h1.title {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 3*$ls;
|
||||||
|
color: $strongColor;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 80%;
|
||||||
|
margin: 0 0.5*$ls;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $strongColor;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
line-height: 3*$ls;
|
||||||
|
padding: 0 $ls;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 0.15rem;
|
||||||
|
background: white;
|
||||||
|
opacity: 0;
|
||||||
|
@include transition-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 40*$ls;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 4*$ls auto 2*$ls;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
padding: 2*$ls;
|
||||||
|
//background: white;
|
||||||
|
//box-shadow: 0 0.05*$ls 0.1*$ls rgba(0,0,0,0.5);
|
||||||
|
border-radius: 0.2*$ls;
|
||||||
|
margin: 1*$ls 0 0;
|
||||||
|
|
||||||
|
date {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
height: $ls;
|
||||||
|
line-height: $ls;
|
||||||
|
display: block;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.6rem;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 2*$ls;
|
||||||
|
color: $strongColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: italic;
|
||||||
|
color: $lightColor;
|
||||||
|
line-height: 1*$ls;
|
||||||
|
margin: 1*$ls 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
line-height: $ls;
|
||||||
|
margin: $ls 0;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: $strongColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
line-height: $ls;
|
||||||
|
margin: $ls 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: $strongColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: $ls;
|
||||||
|
margin: $ls 0 0;
|
||||||
|
color: $bodyColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: $ls 0;
|
||||||
|
padding: 0 $ls;
|
||||||
|
border-left: 0.2rem solid $lightColor;
|
||||||
|
color: $lightColor;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: $strongColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: $ls 0;
|
||||||
|
line-height: $ls;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0 2*$ls;
|
||||||
|
|
||||||
|
code {
|
||||||
|
line-height: $ls;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
height: 20*$ls;
|
||||||
|
padding-bottom: 2*$ls;
|
||||||
|
margin: 2*$ls 0 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.fill {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
height: 2*$ls;
|
||||||
|
line-height: 2*$ls;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
color: $lightColor;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.content {
|
||||||
|
a {
|
||||||
|
color: $strongColor;
|
||||||
|
text-decoration: none;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
transparent 80%,
|
||||||
|
$lightColor 80%,
|
||||||
|
$lightColor 85%,
|
||||||
|
transparent 85%
|
||||||
|
);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $bodyColor;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
transparent 80%,
|
||||||
|
lighten($lightColor, 15%) 80%,
|
||||||
|
lighten($lightColor, 15%) 85%,
|
||||||
|
transparent 85%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
color: $bodyColor;
|
||||||
|
margin: $ls 0;
|
||||||
|
padding: 0 0 0 $ls;
|
||||||
|
line-height: $ls;
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '·';
|
||||||
|
height: $ls;
|
||||||
|
width: $ls;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -$ls;
|
||||||
|
text-align: center;
|
||||||
|
line-height: $ls;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $strongColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.load-content {
|
||||||
|
display: block;
|
||||||
|
height: 3*$ls;
|
||||||
|
line-height: 3*$ls;
|
||||||
|
color: $accentColor;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: $ls 0;
|
||||||
|
}
|
9
templates/fullpost.html
Normal file
9
templates/fullpost.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<article>
|
||||||
|
<date>{{metadata.date}}</date>
|
||||||
|
<h1>{{metadata.title}}</h1>
|
||||||
|
<h2>{{metadata.summary}}</h2>
|
||||||
|
<section class="content">
|
||||||
|
{{{contents}}}
|
||||||
|
</section>
|
||||||
|
<a class="load-content" href="/">← Back home</a>
|
||||||
|
</article>
|
27
templates/page.html
Normal file
27
templates/page.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono&family=Nunito:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap" rel="stylesheet">
|
||||||
|
<title>{{title}} – Prose</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="grid"></div>
|
||||||
|
<header>
|
||||||
|
<h1 class="title">Prose</h1>
|
||||||
|
<nav>
|
||||||
|
<a href="https://nsood.in">Home</a>
|
||||||
|
<a href="https://notes.nsood.in">Blog</a>
|
||||||
|
<a href="https://nsood.in/#contact">Contact</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{{{contents}}}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
Made by Naman Sood, with love.
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
6
templates/summary.html
Normal file
6
templates/summary.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<article>
|
||||||
|
<date>{{metadata.date}}</date>
|
||||||
|
<h1>{{metadata.title}}</h1>
|
||||||
|
<h2>{{metadata.summary}}</h2>
|
||||||
|
<a class="load-content" href="/{{slug}}">Read more →</a>
|
||||||
|
</article>
|
Loading…
Add table
Add a link
Reference in a new issue