Skip to content

Commit dec4b1e

Browse files
Implement blog generator
1 parent ddc12c5 commit dec4b1e

File tree

7 files changed

+125
-72
lines changed

7 files changed

+125
-72
lines changed

README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,3 @@
22

33
Code for [my website](https://gearsco.de), written in [Gleam](https://gleam.run),
44
using [Lustre](https://lustre.build) and [Lustre SSG](https://hexdocs.pm/lustre_ssg).
5-
6-
I have recently restructured the website from a multi-page site to an SPA, so for
7-
now Lustre SSG is overkill for what I need, and the codebase is a little messy.
8-
I do want to eventually make a blog and other pages for this site, so at some point
9-
it will get another rewrite.
10-
11-
For an example of Gleam/Lustre SSG, see: https://github.com/giacomocavalieri/giacomocavalieri.me

blog/test.djot

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title = "Test article"
3+
date = "2025-11-25"
4+
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
5+
---
6+
7+
Hello, world!

src/build.gleam

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
11
import gleam/io
2+
import gleam/list
3+
import lustre/element
24
import lustre/ssg
5+
import website/data/blog
6+
import website/page/blog_home
7+
import website/page/blog_post
38
import website/page/index
49

510
pub fn main() {
11+
let posts = blog.posts()
12+
613
let build =
714
ssg.new("./priv")
815
|> ssg.add_static_route("/", index.view())
916
|> ssg.add_static_dir("./static")
17+
|> ssg.add_static_route("/blog/index", blog_home.view(posts))
18+
|> add_dynamic_routes(
19+
"/blog",
20+
posts,
21+
fn(post) { post.slug },
22+
blog_post.view,
23+
)
1024
|> ssg.build
1125

1226
case build {
@@ -17,3 +31,19 @@ pub fn main() {
1731
}
1832
}
1933
}
34+
35+
fn add_dynamic_routes(
36+
config: ssg.Config(ssg.HasStaticRoutes, a, b),
37+
path: String,
38+
list: List(data),
39+
slug: fn(data) -> String,
40+
view: fn(data) -> element.Element(msg),
41+
) -> ssg.Config(ssg.HasStaticRoutes, a, b) {
42+
list.fold(list, config, fn(config, data) {
43+
ssg.add_static_route(
44+
config,
45+
path <> "/" <> slug(data) <> "/index",
46+
view(data),
47+
)
48+
})
49+
}

src/website/data/blog.gleam

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import gleam/bool
2+
import gleam/dict.{type Dict}
3+
import gleam/list
4+
import gleam/string
5+
import lustre/element
6+
import lustre/ssg/djot
7+
import simplifile
8+
import tom.{type Toml}
9+
10+
pub fn posts() -> List(Post(a)) {
11+
let assert Ok(files) = simplifile.read_directory("blog/")
12+
13+
list.filter_map(files, fn(file) {
14+
use <- bool.guard(!string.ends_with(file, ".djot"), Error(Nil))
15+
let file_path = "blog/" <> file
16+
let assert Ok(contents) = simplifile.read(file_path)
17+
18+
let assert Ok(metadata) = djot.metadata(contents)
19+
let contents = djot.render(contents, djot.default_renderer())
20+
21+
let title = get_string_key(metadata, "title")
22+
let description = get_string_key(metadata, "description")
23+
let date = get_string_key(metadata, "date")
24+
// Remove .djot suffix
25+
let slug = string.drop_end(file, 5)
26+
27+
Ok(Post(title:, slug:, date:, contents:, description:))
28+
})
29+
}
30+
31+
fn get_string_key(toml: Dict(String, Toml), key: String) -> String {
32+
let assert Ok(value) = dict.get(toml, key)
33+
let assert tom.String(value) = value
34+
value
35+
}
36+
37+
pub type Post(a) {
38+
Post(
39+
title: String,
40+
slug: String,
41+
date: String,
42+
description: String,
43+
contents: List(element.Element(a)),
44+
)
45+
}

src/website/data/projects.gleam

Lines changed: 0 additions & 65 deletions
This file was deleted.

src/website/page/blog_home.gleam

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import gleam/list
2+
import lustre/attribute
3+
import lustre/element
4+
import lustre/element/html
5+
import website/component
6+
import website/data/blog
7+
8+
pub fn view(posts: List(blog.Post(_))) -> element.Element(_) {
9+
component.page("Blog", [
10+
component.text_page("My Blog", list.map(posts, post)),
11+
])
12+
}
13+
14+
fn post(post: blog.Post(_)) -> element.Element(_) {
15+
html.div([], [
16+
html.a(
17+
[attribute.href("/blog/" <> post.slug), attribute.class("underline")],
18+
[
19+
html.h2([attribute.class("text-xl font-bold")], [
20+
element.text(post.title),
21+
]),
22+
],
23+
),
24+
html.span([attribute.class("text-sm")], [element.text(post.date)]),
25+
html.p([attribute.class("text-lg")], [element.text(post.description)]),
26+
])
27+
}

src/website/page/blog_post.gleam

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import lustre/attribute
2+
import lustre/element
3+
import lustre/element/html
4+
import website/component
5+
import website/data/blog
6+
7+
pub fn view(post: blog.Post(a)) -> element.Element(a) {
8+
component.page(post.title, [
9+
component.text_page(post.title, [
10+
html.h2([attribute.class("text-xl font-bold text-center m-0")], [
11+
element.text(post.date),
12+
]),
13+
..post.contents
14+
]),
15+
])
16+
}

0 commit comments

Comments
 (0)