Fixed some bugs for publishing a first site

This commit is contained in:
Leon Mika 2026-02-18 22:38:05 +11:00
parent 77d3ff4852
commit 3591e0c723
12 changed files with 86 additions and 30 deletions

2
.gitignore vendored
View file

@ -1 +1,3 @@
build/ build/
# Local Netlify folder
.netlify

View file

@ -0,0 +1,22 @@
---
date: 2026-02-18T22:17:00+11:00
title: First Post
slug: /2026/02/18/first-post
---
Hello World!
This is the first post of Weiro, a simple blogging CMS. At this stage, I've only managed to
build the site reader and site builder, making this little more than a simple little static
site generator. But I eventually want to add a proper UI for editing posts.
Much like Kev Quirk's Pure Blogging CMS, of which this is inspired by, the contents of this
site will be stored as plain text files on the file system (or in any `fs.FS` implementation).
But unlike Kev Quirk's CMS, the generated file will not be rendered on demand. Instead, it
will be built as a static site, a bit like Micro.blog, and served from something else.
I start with Netlify but there's no reason it needs to be that. It could be anything that
can serve static files.
In addition to this, I also want to make sure titleless posts are a first-class citizen.
That's how I like writing my posts, and many blogging platforms don't have great support for
it.

6
_test-site/site.yaml Normal file
View file

@ -0,0 +1,6 @@
title: Weiro
tagline: A blogging CMS
base_url: https://jolly-boba-9e2486.netlify.app/
public:
netlify:
site_id: 55c878a7-189e-42cf-aa02-5c60908143f3

17
main.go
View file

@ -10,8 +10,14 @@ import (
) )
func main() { func main() {
sr := sitereader.New(os.DirFS("build/test-site")) sr := sitereader.New(os.DirFS("_test-site"))
sb, err := sitebuilder.New("build/out", sitebuilder.Options{
site, err := sr.ReadSite()
if err != nil {
log.Fatal(err)
}
sb, err := sitebuilder.New(site, sitebuilder.Options{
BasePosts: "/posts", BasePosts: "/posts",
TemplatesFS: site_templates.FS, TemplatesFS: site_templates.FS,
}) })
@ -19,12 +25,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
site, err := sr.ReadSite() if err := sb.BuildSite("build/out"); err != nil {
if err != nil {
log.Fatal(err)
}
if err := sb.BuildSite(site); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View file

@ -8,9 +8,9 @@ type Site struct {
} }
type SiteMeta struct { type SiteMeta struct {
Title string Title string `yaml:"title"`
Tagline string Tagline string `yaml:"tagline"`
BaseURL string BaseURL string `yaml:"base_url"`
} }
type PostMeta struct { type PostMeta struct {

View file

@ -43,7 +43,6 @@ func New(site models.Site, opts Options) (*Builder, error) {
parser.WithAutoHeadingID(), parser.WithAutoHeadingID(),
), ),
goldmark.WithRendererOptions( goldmark.WithRendererOptions(
html.WithHardWraps(),
html.WithUnsafe(), html.WithUnsafe(),
), ),
), ),
@ -79,7 +78,9 @@ func (b *Builder) renderPostList(ctx buildContext, postList []*models.Post) erro
return postCopy[i].Meta.Date.After(postCopy[j].Meta.Date) return postCopy[i].Meta.Date.After(postCopy[j].Meta.Date)
}) })
pl := postListData{} pl := postListData{
commonData: commonData{Site: b.site.Meta},
}
for _, post := range postCopy { for _, post := range postCopy {
rp, err := b.renderPost(post) rp, err := b.renderPost(post)
if err != nil { if err != nil {
@ -105,6 +106,7 @@ func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
} }
return postSingleData{ return postSingleData{
commonData: commonData{Site: b.site.Meta},
Path: postPath, Path: postPath,
Meta: post.Meta, Meta: post.Meta,
HTML: template.HTML(md.String()), HTML: template.HTML(md.String()),
@ -150,6 +152,7 @@ func (b *Builder) renderTemplate(w io.Writer, name string, data interface{}) err
} }
return b.tmpls.ExecuteTemplate(w, tmplNameLayoutMain, layoutData{ return b.tmpls.ExecuteTemplate(w, tmplNameLayoutMain, layoutData{
commonData: commonData{Site: b.site.Meta},
Body: template.HTML(buf.String()), Body: template.HTML(buf.String()),
}) })
} }

View file

@ -33,16 +33,23 @@ type Options struct {
RenderTZ *time.Location RenderTZ *time.Location
} }
type commonData struct {
Site models.SiteMeta
}
type postSingleData struct { type postSingleData struct {
commonData
Meta models.PostMeta Meta models.PostMeta
HTML template.HTML HTML template.HTML
Path string Path string
} }
type postListData struct { type postListData struct {
commonData
Posts []postSingleData Posts []postSingleData
} }
type layoutData struct { type layoutData struct {
commonData
Body template.HTML Body template.HTML
} }

View file

@ -19,13 +19,23 @@ func New(fs fs.FS) *Provider {
} }
} }
func (p *Provider) ReadSite() (models.Sites, error) { func (p *Provider) ReadSite() (models.Site, error) {
posts, err := p.ListPosts() posts, err := p.ListPosts()
if err != nil { if err != nil {
return models.Sites{}, err return models.Site{}, err
} }
return models.Sites{ meta := models.SiteMeta{}
metaBytes, err := fs.ReadFile(p.fs, "site.yaml")
if err != nil {
return models.Site{}, err
}
if err := yaml.Unmarshal(metaBytes, &meta); err != nil {
return models.Site{}, err
}
return models.Site{
Meta: meta,
Posts: posts, Posts: posts,
}, nil }, nil
} }
@ -60,7 +70,7 @@ func (p *Provider) ReadPost(path string) (*models.Post, error) {
return nil, io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
} }
var meta models.Meta var meta models.PostMeta
if err := yaml.Unmarshal(parts[1], &meta); err != nil { if err := yaml.Unmarshal(parts[1], &meta); err != nil {
return nil, err return nil, err
} }

View file

@ -13,6 +13,7 @@ import (
func TestProvider_ReadPost(t *testing.T) { func TestProvider_ReadPost(t *testing.T) {
t.Run("with meta", func(t *testing.T) { t.Run("with meta", func(t *testing.T) {
testFS := fstest.MapFS{ testFS := fstest.MapFS{
"site.yaml": {Data: []byte(`base_url: https://example.com`)},
"posts/test.md": {Data: []byte(`--- "posts/test.md": {Data: []byte(`---
date: 2026-02-18T19:59:00Z date: 2026-02-18T19:59:00Z
title: Test Post Here title: Test Post Here
@ -44,7 +45,7 @@ This is just a test post.
post, err := pr.ReadPost("posts/test.md") post, err := pr.ReadPost("posts/test.md")
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, models.Meta{}, post.Meta) assert.Equal(t, models.PostMeta{}, post.Meta)
assert.Equal(t, "This is just a test post.\n", post.Content) assert.Equal(t, "This is just a test post.\n", post.Content)
}) })
} }

View file

@ -3,19 +3,21 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My New Website</title> <title>{{ .Site.Title }}</title>
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css"> <link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
</head> </head>
<body> <body>
<header> <header>
<h1>Hello, world</h1> <h1>{{ .Site.Title }}</h1>
<p>Welcome to my website!</p> <p>{{ .Site.Tagline }}</p>
</header> </header>
<main>
{{ .Body }} {{ .Body }}
</main>
<footer> <footer>
<p>Test stuff</p> <p>This site under construction.</p>
</footer> </footer>
</body> </body>
</html> </html>

View file

@ -1,4 +1,5 @@
{{ range .Posts }} {{ range .Posts }}
{{ if .Meta.Title }}<h3>{{ .Meta.Title }}</h3>{{ end }}
{{ .HTML }} {{ .HTML }}
<a href="{{ .Path }}">{{ format_date .Meta.Date }}</a> <a href="{{ url_abs .Path }}">{{ format_date .Meta.Date }}</a>
{{ end }} {{ end }}

View file

@ -1,2 +1,3 @@
{{ if .Meta.Title }}<h3>{{ .Meta.Title }}</h3>{{ end }}
{{ .HTML }} {{ .HTML }}
<a href="{{ .Path }}">{{ format_date .Meta.Date }}</a> <a href="{{ url_abs .Path }}">{{ format_date .Meta.Date }}</a>