💻 My personal website blog.kacaii.dev/
blog gleam lustre

:recycle: store post slug inside the frontmatter

+11 -26
+1 -1
dev/blog_dev.gleam
··· 24 24 let file_path = path.join(posts_path, file_name) 25 25 let assert Ok(post) = post.from_string(path: file_path) as "Parse post" 26 26 27 - let new_file_name = post.slug(post) <> ".md" 27 + let new_file_name = post.meta.slug <> ".md" 28 28 let new_file_path = 29 29 string.replace(in: file_path, each: file_name, with: new_file_name) 30 30
+1
priv/posts/building-my-college-project-with-gleam.md
··· 1 1 --- 2 + slug = "building-my-college-project-with-gleam" 2 3 title = "Building my college project with Gleam" 3 4 description = "The power of simplicity" 4 5 date = 2025-12-31
+1
priv/posts/uses.md
··· 1 1 --- 2 + slug = "uses" 2 3 title = "/uses" 3 4 description = "Everything I use for building production-ready systems" 4 5 date = 2026-01-01
+1 -1
src/blog/page/content.gleam
··· 13 13 import wisp 14 14 15 15 pub fn handle_request(ctx: web.Context, post_uri: String) -> wisp.Response { 16 - case list.find(ctx.posts, fn(post) { post.slug(post) == post_uri }) { 16 + case list.find(ctx.posts, fn(post) { post.meta.slug == post_uri }) { 17 17 Error(_) -> wisp.not_found() 18 18 Ok(post) -> { 19 19 let title = post.meta.title
+2 -5
src/blog/page/posts.gleam
··· 14 14 15 15 pub fn handle_request(ctx: web.Context) -> wisp.Response { 16 16 let ul_styles = class("grid grid-cols-1 gap-4 mx-auto w-full") 17 - let posts = 18 - list.filter(ctx.posts, fn(post) { 19 - !string.starts_with(post.meta.title, "/") 20 - }) 17 + let posts = list.filter(ctx.posts, fn(post) { post.meta.slug != "uses" }) 21 18 22 19 let content = [ 23 20 navbar.view([]), ··· 46 43 class("flex-col p-4 mx-auto w-full rounded-lg shadow-sm bg-ctp-mantle") 47 44 48 45 html.li([li_styles], [ 49 - html.a([attr.href("/posts/" <> post.slug(post))], [ 46 + html.a([attr.href("/posts/" <> post.meta.slug)], [ 50 47 html.h2([class("text-2xl font-bold text-pretty")], [ 51 48 html.text(meta.title), 52 49 ]),
+2 -4
src/blog/page/recent_posts.gleam
··· 8 8 9 9 pub fn view(recent_posts: List(post.Post)) { 10 10 let previews = 11 - list.filter(recent_posts, fn(post) { 12 - !string.starts_with(post.meta.title, "/") 13 - }) 11 + list.filter(recent_posts, fn(post) { post.meta.slug != "uses" }) 14 12 |> list.take(2) 15 13 |> list.map(post_to_li) 16 14 ··· 39 37 string.join([day, month, year], with: " ") 40 38 } 41 39 42 - let href = attr.href("/posts/" <> post.slug(post)) 40 + let href = attr.href("/posts/" <> post.meta.slug) 43 41 let style = class("flex flex-col p-4 mx-auto w-full rounded-lg bg-ctp-mantle") 44 42 45 43 html.li([style], [
-14
src/blog/post.gleam
··· 4 4 import gleam/option 5 5 import gleam/order 6 6 import gleam/result 7 - import gleam/string 8 7 import gleam/time/calendar 9 8 import jot 10 9 import simplifile ··· 17 16 18 17 pub type Post { 19 18 Post(meta: metadata.Metadata, body: jot.Document) 20 - } 21 - 22 - pub fn slug(post: Post) -> String { 23 - string.lowercase(post.meta.title) 24 - |> string.replace(" ", "-") 25 - |> string.replace("_", "-") 26 - |> string.replace("(", "") 27 - |> string.replace(")", "") 28 - |> string.replace("[", "") 29 - |> string.replace("]", "") 30 - |> string.replace("{", "") 31 - |> string.replace("}", "") 32 - |> string.replace("/", "") 33 19 } 34 20 35 21 pub fn from_string(path file: String) -> Result(Post, PostError) {
+3 -1
src/blog/post/metadata.gleam
··· 10 10 11 11 pub type Metadata { 12 12 Metadata( 13 + slug: String, 13 14 title: String, 14 15 description: String, 15 16 date: calendar.Date, ··· 23 24 |> result.map_error(MetadataError), 24 25 ) 25 26 27 + use slug <- toml_field(toml, tom.get_string, "slug") 26 28 use title <- toml_field(toml, tom.get_string, "title") 27 29 use description <- toml_field(toml, tom.get_string, "description") 28 30 use date <- toml_field(toml, tom.get_date, "date") 29 31 use toml_tags <- toml_field(toml, tom.get_array, "tags") 30 32 use tags <- result.map(parse_tags(toml_tags)) 31 33 32 - Metadata(title:, description:, date:, tags:) 34 + Metadata(slug:, title:, description:, date:, tags:) 33 35 } 34 36 35 37 fn parse_tags(tags: List(tom.Toml)) -> Result(List(String), MetadataError) {