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

View file

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

View file

@ -43,7 +43,6 @@ func New(site models.Site, opts Options) (*Builder, error) {
parser.WithAutoHeadingID(),
),
goldmark.WithRendererOptions(
html.WithHardWraps(),
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)
})
pl := postListData{}
pl := postListData{
commonData: commonData{Site: b.site.Meta},
}
for _, post := range postCopy {
rp, err := b.renderPost(post)
if err != nil {
@ -105,6 +106,7 @@ func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
}
return postSingleData{
commonData: commonData{Site: b.site.Meta},
Path: postPath,
Meta: post.Meta,
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{
commonData: commonData{Site: b.site.Meta},
Body: template.HTML(buf.String()),
})
}

View file

@ -33,16 +33,23 @@ type Options struct {
RenderTZ *time.Location
}
type commonData struct {
Site models.SiteMeta
}
type postSingleData struct {
commonData
Meta models.PostMeta
HTML template.HTML
Path string
}
type postListData struct {
commonData
Posts []postSingleData
}
type layoutData struct {
commonData
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()
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,
}, nil
}
@ -60,7 +70,7 @@ func (p *Provider) ReadPost(path string) (*models.Post, error) {
return nil, io.ErrUnexpectedEOF
}
var meta models.Meta
var meta models.PostMeta
if err := yaml.Unmarshal(parts[1], &meta); err != nil {
return nil, err
}

View file

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

View file

@ -3,19 +3,21 @@
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<header>
<h1>Hello, world</h1>
<p>Welcome to my website!</p>
<h1>{{ .Site.Title }}</h1>
<p>{{ .Site.Tagline }}</p>
</header>
{{ .Body }}
<main>
{{ .Body }}
</main>
<footer>
<p>Test stuff</p>
<p>This site under construction.</p>
</footer>
</body>
</html>

View file

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

View file

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