Fixed some bugs for publishing a first site
This commit is contained in:
parent
77d3ff4852
commit
3591e0c723
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1,3 @@
|
||||||
build/
|
build/
|
||||||
|
# Local Netlify folder
|
||||||
|
.netlify
|
||||||
|
|
|
||||||
22
_test-site/posts/2026/02/18-first-post.md
Normal file
22
_test-site/posts/2026/02/18-first-post.md
Normal 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
6
_test-site/site.yaml
Normal 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
17
main.go
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,9 +106,10 @@ func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return postSingleData{
|
return postSingleData{
|
||||||
Path: postPath,
|
commonData: commonData{Site: b.site.Meta},
|
||||||
Meta: post.Meta,
|
Path: postPath,
|
||||||
HTML: template.HTML(md.String()),
|
Meta: post.Meta,
|
||||||
|
HTML: template.HTML(md.String()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +152,8 @@ 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{
|
||||||
Body: template.HTML(buf.String()),
|
commonData: commonData{Site: b.site.Meta},
|
||||||
|
Body: template.HTML(buf.String()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
{{ .Body }}
|
<main>
|
||||||
|
{{ .Body }}
|
||||||
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Test stuff</p>
|
<p>This site under construction.</p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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>
|
||||||
Loading…
Reference in a new issue