From e833e187488677f5e9563b6acf192c4f012c2e32 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Tue, 4 Feb 2025 22:07:59 -0500 Subject: [PATCH 01/10] header styling --- resources/main.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/main.css b/resources/main.css index 602362d..7d02554 100644 --- a/resources/main.css +++ b/resources/main.css @@ -26,9 +26,14 @@ body { .site-logo { color: light-dark(#d6000c, #ed4a46); + font-size: 2rem; + font-weight: bold; } .site-tagline { + color: light-dark(#909995, #777777); + font-size: 0.9rem; + font-style: italic; } .post-body { From e7fdf59618538966f8cf4c8ecebeffcef2d0b6e9 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Tue, 4 Feb 2025 22:24:37 -0500 Subject: [PATCH 02/10] Seperate out title into seperate visual block --- lib/Config.rakumod | 15 +++++++++++++-- lib/Pandoc.rakumod | 5 ++++- resources/main.css | 26 ++++++++++++++++++++------ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/lib/Config.rakumod b/lib/Config.rakumod index 5503bbb..31559d6 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -48,6 +48,14 @@ method site-header(BlogMeta:D $meta) { ] } +method post-header(Post:D $post) { + header :class, [ + div :class, [ + h1 $post.title; + ] + ] +} + # TODO: Support GFM admonitions method generate-post(Post:D $post, BlogMeta:D $meta) { my $content = $post.render-html; @@ -55,8 +63,11 @@ method generate-post(Post:D $post, BlogMeta:D $meta) { my $body = body [ self.site-header: $meta; - div :class, [ - $content + article :class, [ + self.post-header: $post; + div :class, [ + $content; + ] ] ]; # TODO: Setup footer diff --git a/lib/Pandoc.rakumod b/lib/Pandoc.rakumod index 74048f0..75887e3 100644 --- a/lib/Pandoc.rakumod +++ b/lib/Pandoc.rakumod @@ -92,5 +92,8 @@ sub markdown-first-paragraph(IO::Path:D $file --> Str:D) is export { #| Use pandoc to render a markdown document to html sub markdown-to-html(IO::Path:D $file --> Str:D) is export { - pandoc <-f gfm>, $file + # Remove the header, we'll regenerate it later + my $output = pandoc <-f gfm>, $file; + $output ~~ s:g/''//; + $output } diff --git a/resources/main.css b/resources/main.css index 7d02554..1c2c288 100644 --- a/resources/main.css +++ b/resources/main.css @@ -5,7 +5,7 @@ font-family: "Open Sans", sans-serif, serif; } -body { +body, .post { display: flex; align-items: center; justify-content: center; @@ -26,7 +26,7 @@ body { .site-logo { color: light-dark(#d6000c, #ed4a46); - font-size: 2rem; + font-size: 2.5rem; font-weight: bold; } @@ -36,21 +36,35 @@ body { font-style: italic; } -.post-body { +.post-body, .post-header { width: 66%; display: block; - padding-left: 2rem; - padding-right: 2rem; + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; border-radius: 1rem; background-color: light-dark(#ffffff, #181818); /* text-align: justify; */ } -.post-body > h1 { +.post-header { + padding: 1.5rem; + display: flex; + flex-direction: column; + align-items: center; +} + +.post-title { text-align: center; color: light-dark(#dd0f9d, #eb6eb7); } +.post-title > h1 { + margin-top: 0px; + margin-bottom: 0px; +} + .post-body > h2 { text-align: center; color: light-dark(#282828, #dedede); From 9ccc512533cce410849144d5dfef7a0214e5da80 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Tue, 4 Feb 2025 23:43:42 -0500 Subject: [PATCH 03/10] tweaks --- lib/Config.rakumod | 63 ++++++++++++++++++++++++++++++++++++++++++--- lib/DB/Post.rakumod | 6 +++++ resources/code.css | 2 +- resources/main.css | 23 ++++++++++++++--- 4 files changed, 86 insertions(+), 8 deletions(-) diff --git a/lib/Config.rakumod b/lib/Config.rakumod index 31559d6..9dc7b04 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -22,11 +22,15 @@ method generate-head(Str:D $title, BlogMeta:D $meta, $description?) { link :rel :href; link :rel :href; link :rel :href :crossorigin; - # Load fonts, Iosevka Alie for code, and Open Sans for content + link :rel :href; + # Load fonts, Iosevka for code, Open Sans for content, and boxicons for + # icons link :rel, - :href; + :href; link :rel, :href; + link :rel, + :href; # Inline our style sheets style %?RESOURCES.slurp; style %?RESOURCES.slurp; @@ -48,11 +52,60 @@ method site-header(BlogMeta:D $meta) { ] } +method post-date(Post:D $post) { + my $datetime = $post.posted-at; + my $timestamp = sprintf( + "%s %02d:%02d %s", + $datetime.yyyy-mm-dd, + ($datetime.hour % 12) || 12, + $datetime.minute, + $datetime.hour < 12 ?? 'am' !! 'pm' + ); + + div :class, :title("Posted At $timestamp"), [ + icon 'time'; + ' '; + $timestamp + ] +} + +sub mins-to-string($mins) { + if $mins < 60 { + $mins.Str ~ "m" + } else { + my $h = $mins div 60; + my $m = $mins mod 60; + $h.Str ~ "h" ~ $m.Str ~ "m" + } +} + +method post-read-time(Post:D $post) { + my ($slow, $average, $fast) = $post.readtimes; + div :class, :title, [ + icon 'timer'; + ' '; + mins-to-string $slow; + ' '; + '/'; + ' '; + mins-to-string $average; + ' '; + '/'; + ' '; + mins-to-string $fast; + ] +} + method post-header(Post:D $post) { header :class, [ div :class, [ h1 $post.title; - ] + ]; + div :class, [ + self.post-date: $post; + self.post-read-time: $post; + ]; + # TODO: Add tags once we have support for that ] } @@ -80,3 +133,7 @@ method generate-post(Post:D $post, BlogMeta:D $meta) { "$html" } + +sub icon($icon) { + i(:class("bx bx-$icon")) +} diff --git a/lib/DB/Post.rakumod b/lib/DB/Post.rakumod index 0c43fdb..3e7c377 100644 --- a/lib/DB/Post.rakumod +++ b/lib/DB/Post.rakumod @@ -58,3 +58,9 @@ method render-html(--> Str:D) {...} method description(--> Str) { Nil } + +#| Estimated readtimes at 140/180/220 wpm +method readtimes() { + my $word-count = $!source.slurp.words.elems; + ($word-count / 140, $word-count / 180, $word-count / 220).map(*.ceiling) +} diff --git a/resources/code.css b/resources/code.css index 4635ff2..1ee4316 100644 --- a/resources/code.css +++ b/resources/code.css @@ -1,5 +1,5 @@ code { - font-family: "Iosevka Aile Web", monospace; + font-family: "Iosevka Web", monospace; background-color: light-dark(#fbf3db, #103c48); color: light-dark(#53676d, #adbcbc); min-width: 80ch; diff --git a/resources/main.css b/resources/main.css index 1c2c288..a14b9c1 100644 --- a/resources/main.css +++ b/resources/main.css @@ -14,7 +14,7 @@ body, .post { } .site-header { - width: 60%; + width: 50%; display: block; padding: 1rem; border-radius: 1rem; @@ -53,6 +53,7 @@ body, .post { display: flex; flex-direction: column; align-items: center; + gap: 0.5rem; } .post-title { @@ -65,6 +66,20 @@ body, .post { margin-bottom: 0px; } +.post-info { + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + color: light-dark(#909995, #777777); + font-size: 0.9rem; + flex-wrap: wrap; +} + +.post-read-time { + text-decoration: underline dotted; +} + .post-body > h2 { text-align: center; color: light-dark(#282828, #dedede); @@ -79,6 +94,6 @@ body, .post { text-align: center; color: light-dark(#282828, #dedede); } -.post-body > p { - text-indent: 2ch; -} +/* .post-body > p { */ +/* text-indent: 2ch; */ +/* } */ From d4f11a5585645fae22aebe76b0600ca06e44f687 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Tue, 4 Feb 2025 23:53:00 -0500 Subject: [PATCH 04/10] Make code blocks behave reasonably on small displays --- resources/code.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/code.css b/resources/code.css index 1ee4316..da050c2 100644 --- a/resources/code.css +++ b/resources/code.css @@ -2,11 +2,12 @@ code { font-family: "Iosevka Web", monospace; background-color: light-dark(#fbf3db, #103c48); color: light-dark(#53676d, #adbcbc); - min-width: 80ch; width: 80%; display: block; padding: 1rem; border-radius: 0.55rem / 0.5rem; + white-space: pre-wrap; + overflow-x: auto; } pre { From d861c7ef87b37ba9f483c0a1751090e704fb654c Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Wed, 5 Feb 2025 00:26:12 -0500 Subject: [PATCH 05/10] header links --- lib/Config.rakumod | 33 ++++++++++++++++++++++++++++++--- lib/DB.rakumod | 1 + resources/main.css | 18 ++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/lib/Config.rakumod b/lib/Config.rakumod index 9dc7b04..8348145 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -11,7 +11,7 @@ method generate-head(Str:D $title, BlogMeta:D $meta, $description?) { meta :charset; meta :name, :content; meta :author :content; - title "{$meta.title} — $title"; + title "$title — {$meta.title}"; # Add description, if one exists do if $description ~~ Str:D { meta :description :content($description) @@ -47,8 +47,35 @@ method site-header(BlogMeta:D $meta) { $meta.tagline ]; div :class, [ - - ] + a :href, [ + icon 'home'; + ' '; + span [ + 'Home'; + ]; + ]; + a :href, [ + icon 'archive'; + ' '; + span [ + 'Archive'; + ]; + ]; + a :href, [ + icon 'info-circle'; + ' '; + span [ + 'About'; + ]; + ]; + a :href, [ + icon 'rss'; + ' '; + span [ + 'Feed'; + ]; + ]; + ]; ] } diff --git a/lib/DB.rakumod b/lib/DB.rakumod index fd95f6f..576c309 100644 --- a/lib/DB.rakumod +++ b/lib/DB.rakumod @@ -91,6 +91,7 @@ class PostDB { # Render the archive # Render the rss/atom feed # Render the index + # Symlink the about article die "Not Implemented" } } diff --git a/resources/main.css b/resources/main.css index a14b9c1..4a530e1 100644 --- a/resources/main.css +++ b/resources/main.css @@ -13,6 +13,16 @@ body, .post { gap: 1rem; } +.header-links { + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + font-size: 1.1rem; + flex-wrap: wrap; + margin-top: 0.5rem; +} + .site-header { width: 50%; display: block; @@ -97,3 +107,11 @@ body, .post { /* .post-body > p { */ /* text-indent: 2ch; */ /* } */ + +a > span { + text-decoration: underline; +} + +a { + text-decoration: none; +} From c59c266ee1b65ef2e8dae30ad51e508cfea866eb Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Wed, 5 Feb 2025 03:26:39 -0500 Subject: [PATCH 06/10] Generate Index --- lib/Config.rakumod | 73 +++++++++++++++++++++++++++++++++++++---- lib/DB.rakumod | 14 +++++--- lib/DB/BlogMeta.rakumod | 3 ++ resources/main.css | 37 +++++++++++++++++++-- 4 files changed, 115 insertions(+), 12 deletions(-) diff --git a/lib/Config.rakumod b/lib/Config.rakumod index 8348145..c8f31d9 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -6,12 +6,16 @@ use DB::Post; unit class Config; -method generate-head(Str:D $title, BlogMeta:D $meta, $description?) { +method generate-head($title, BlogMeta:D $meta, $description?) { head [ meta :charset; meta :name, :content; meta :author :content; - title "$title — {$meta.title}"; + do if $title ~~ Str:D { + title "$title — {$meta.title}"; + } else { + title $meta.title; + } # Add description, if one exists do if $description ~~ Str:D { meta :description :content($description) @@ -123,15 +127,19 @@ method post-read-time(Post:D $post) { ] } +method post-info(Post:D $post) { + div :class, [ + self.post-date: $post; + self.post-read-time: $post; + ]; +} + method post-header(Post:D $post) { header :class, [ div :class, [ h1 $post.title; ]; - div :class, [ - self.post-date: $post; - self.post-read-time: $post; - ]; + self.post-info: $post; # TODO: Add tags once we have support for that ] } @@ -161,6 +169,59 @@ method generate-post(Post:D $post, BlogMeta:D $meta) { "$html" } +method generate-blurb(Int:D $id, $db) { + my $post = $db.posts{$id}; + my $desc = $post.description; + my @slugs = $post.all-slugs; + # Use the primary slug if there is one, the id if there isn't + my $link = do if @slugs.elems > 0 { + "/posts/by-slug/{@slugs[0]}.html" + } else { + "/posts/by-id/$id.html" + } + div :class, [ + div :class, [ + a :href($link), span [ + h2 $post.title; + ]; + ]; + self.post-info: $post; + if $desc ~~ Str:D { + div :class, [ + p $post.description; + ]; + } else { + [] + } + ] +} + +method generate-index($db) { + my @most-recent = + $db.sorted-posts + .head(10) + .grep(!*.value.hidden) + .map(-> $pair { + self.generate-blurb: $pair.key, $db + }); + + my $head = self.generate-head(Nil, $db.meta); + my $body = body [ + self.site-header: $db.meta; + div :class, [ + h1 "Recent Posts" + ], @most-recent; + ]; + + my $html = + html :lang, [ + $head, + $body + ]; + + "$html" +} + sub icon($icon) { i(:class("bx bx-$icon")) } diff --git a/lib/DB.rakumod b/lib/DB.rakumod index 576c309..653e4c7 100644 --- a/lib/DB.rakumod +++ b/lib/DB.rakumod @@ -83,17 +83,23 @@ class PostDB { $id-path.spurt: $html; for $post.all-slugs -> $slug { # remove the symlink if it already exists - my $slug-path = $by-slug.add: $slug; + my $slug-path = $by-slug.add: "$slug.html"; $slug-path.unlink if $slug-path.l; $id-path.symlink: $slug-path; } } - # Render the archive - # Render the rss/atom feed # Render the index - # Symlink the about article + $out-dir.add('index.html').spurt: $config.generate-index(self); + # TODO: Render the archive + # TODO: Render the rss/atom feed + # TODO: Symlink the about article die "Not Implemented" } + + #| Get a list of posts sorted by date + method sorted-posts() { + %!posts.sort(*.value.posted-at).reverse + } } #| Read the database out of a directory diff --git a/lib/DB/BlogMeta.rakumod b/lib/DB/BlogMeta.rakumod index f2a0382..b8477ae 100644 --- a/lib/DB/BlogMeta.rakumod +++ b/lib/DB/BlogMeta.rakumod @@ -13,3 +13,6 @@ has Str:D $.tagline is required is rw; #| The id of the placeholder post has Int:D $.placeholder-id is rw = 0; + +#| The id of the about post +has Int:D $.about-id is rw = 0; diff --git a/resources/main.css b/resources/main.css index 4a530e1..6f12ebf 100644 --- a/resources/main.css +++ b/resources/main.css @@ -46,7 +46,7 @@ body, .post { font-style: italic; } -.post-body, .post-header { +.post-body, .post-header, .post-blurbs { width: 66%; display: block; padding-left: 1.5rem; @@ -58,6 +58,28 @@ body, .post { /* text-align: justify; */ } +.post-blurbs { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 1rem; +} + +.post-blurb { + width: 100%; + display: block; + border-radius: 1rem; + padding: 0.5rem; + background-color: light-dark(#ebebeb, #252525); + /* background-color: light-dark(#cdcdcd, #3b3b3b); */ + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + /* gap: 0.25rem; */ +} + .post-header { padding: 1.5rem; display: flex; @@ -71,7 +93,18 @@ body, .post { color: light-dark(#dd0f9d, #eb6eb7); } -.post-title > h1 { +.post-blurbs > h1 { + color: light-dark(#1d9700, #70b433); +} + +a:link { + color: light-dark(#009c8f, #41c7b9); +} + +a:visited { + color: light-dark(#dd0f9d, #eb6eb7); +} +.post-title > h1, .post-blurb-title > h2 { margin-top: 0px; margin-bottom: 0px; } From d1eb6f17621d59f778db8cda0d820e079274f642 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Wed, 5 Feb 2025 03:28:49 -0500 Subject: [PATCH 07/10] Generate archive --- lib/Config.rakumod | 25 +++++++++++++++++++++++++ lib/DB.rakumod | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/Config.rakumod b/lib/Config.rakumod index c8f31d9..e5a1dd4 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -222,6 +222,31 @@ method generate-index($db) { "$html" } +method generate-archive($db) { + my @most-recent = + $db.sorted-posts + .grep(!*.value.hidden) + .map(-> $pair { + self.generate-blurb: $pair.key, $db + }); + + my $head = self.generate-head(Nil, $db.meta); + my $body = body [ + self.site-header: $db.meta; + div :class, [ + h1 "All Posts" + ], @most-recent; + ]; + + my $html = + html :lang, [ + $head, + $body + ]; + + "$html" +} + sub icon($icon) { i(:class("bx bx-$icon")) } diff --git a/lib/DB.rakumod b/lib/DB.rakumod index 653e4c7..ca3f101 100644 --- a/lib/DB.rakumod +++ b/lib/DB.rakumod @@ -90,7 +90,8 @@ class PostDB { } # Render the index $out-dir.add('index.html').spurt: $config.generate-index(self); - # TODO: Render the archive + # Render the archive + $out-dir.add('archive.html').spurt: $config.generate-archive(self); # TODO: Render the rss/atom feed # TODO: Symlink the about article die "Not Implemented" From 73aefa28eb3fec8eef917b30fd57940f4038c115 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Wed, 5 Feb 2025 03:31:26 -0500 Subject: [PATCH 08/10] Symlink about page --- lib/DB.rakumod | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/DB.rakumod b/lib/DB.rakumod index ca3f101..c4e7827 100644 --- a/lib/DB.rakumod +++ b/lib/DB.rakumod @@ -92,8 +92,11 @@ class PostDB { $out-dir.add('index.html').spurt: $config.generate-index(self); # Render the archive $out-dir.add('archive.html').spurt: $config.generate-archive(self); - # TODO: Render the rss/atom feed # TODO: Symlink the about article + my $about-path = $out-dir.add('about.html'); + $about-path.unlink if $about-path.l; + $by-id.add("{$!meta.about-id}.html").symlink: $about-path; + # TODO: Render the rss/atom feed die "Not Implemented" } From 7d5cbfba3c79b4783162d35e154e30badc1ded93 Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Wed, 5 Feb 2025 03:45:01 -0500 Subject: [PATCH 09/10] Edited at time --- blog | 16 ++++++++++++++++ lib/Config.rakumod | 23 +++++++++++++++++++++-- lib/DB/Post.rakumod | 1 + 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/blog b/blog index 4198bdd..83f331c 100755 --- a/blog +++ b/blog @@ -118,6 +118,22 @@ multi MAIN( say 'Post has slugs: ', $db.posts{$id}.all-slugs; } +#| Update the last editied time on a post +multi MAIN( + "touch", + #| The post id to touch + Int:D $id, + #| The path of the database file + IO::Path(Str) :$db-dir = $default-db-dir, + #| The date/time the post should be recorded as laste edited at + DateTime(Str) :$edited-at = DateTime.now, +) { + my $db = read-db $db-dir; + my $post = $db.posts{$id.Int}; + $post.edited-at.push: $edited-at; + $db.write: $db-dir; +} + #| Render the blog to html multi MAIN( "render", diff --git a/lib/Config.rakumod b/lib/Config.rakumod index e5a1dd4..c7a32df 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -86,7 +86,7 @@ method site-header(BlogMeta:D $meta) { method post-date(Post:D $post) { my $datetime = $post.posted-at; my $timestamp = sprintf( - "%s %02d:%02d %s", + "%s %02d:%02d%s", $datetime.yyyy-mm-dd, ($datetime.hour % 12) || 12, $datetime.minute, @@ -100,6 +100,24 @@ method post-date(Post:D $post) { ] } +method post-edit(Post:D $post) { + return [] unless $post.edited-at.elems; + my $datetime = $post.edited-at.max; + my $timestamp = sprintf( + "%s %02d:%02d%s", + $datetime.yyyy-mm-dd, + ($datetime.hour % 12) || 12, + $datetime.minute, + $datetime.hour < 12 ?? 'am' !! 'pm' + ); + + div :class, :title("Laste Edited At $timestamp"), [ + icon 'edit'; + ' '; + $timestamp + ] +} + sub mins-to-string($mins) { if $mins < 60 { $mins.Str ~ "m" @@ -130,7 +148,9 @@ method post-read-time(Post:D $post) { method post-info(Post:D $post) { div :class, [ self.post-date: $post; + self.post-edit: $post; self.post-read-time: $post; + # TODO: Add tags once we have support for that ]; } @@ -140,7 +160,6 @@ method post-header(Post:D $post) { h1 $post.title; ]; self.post-info: $post; - # TODO: Add tags once we have support for that ] } diff --git a/lib/DB/Post.rakumod b/lib/DB/Post.rakumod index 3e7c377..dbdc0f7 100644 --- a/lib/DB/Post.rakumod +++ b/lib/DB/Post.rakumod @@ -24,6 +24,7 @@ DateTime:D $.posted-at #| An optional list of edit times for the post has DateTime:D @.edited-at + is rw is json( :to-json( value => { $^value.Str } From 53c0c6a9d6c403513066135e4f4ea0a97a93585f Mon Sep 17 00:00:00 2001 From: Nathan McCarty Date: Wed, 5 Feb 2025 04:37:28 -0500 Subject: [PATCH 10/10] Basci atom feed generation --- blog | 6 +++- lib/Atom.rakumod | 64 +++++++++++++++++++++++++++++++++++++++++ lib/Config.rakumod | 6 ++-- lib/DB.rakumod | 10 +++++-- lib/DB/BlogMeta.rakumod | 13 +++++++++ lib/DB/Post.rakumod | 9 ++++++ 6 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 lib/Atom.rakumod diff --git a/blog b/blog index 83f331c..5145556 100755 --- a/blog +++ b/blog @@ -44,8 +44,12 @@ multi MAIN( my $title = get; print "Tagline: "; my $tagline = get; + print "Base URL: "; + my $base-url = get; - my $meta = BlogMeta.new: title => $title, tagline => $tagline; + my $meta = + BlogMeta.new: + title => $title, tagline => $tagline, base-url => $base-url; my $db = DB::PostDB.init: $meta; diff --git a/lib/Atom.rakumod b/lib/Atom.rakumod new file mode 100644 index 0000000..0c0ad7b --- /dev/null +++ b/lib/Atom.rakumod @@ -0,0 +1,64 @@ +use v6.e.PREVIEW; + +use DB::Post; +use DB::BlogMeta; + +use XML; + +unit module Atom; + +# get the link for a post +sub post-link(BlogMeta:D $meta, Int:D $id, Post:D $post --> Str:D) { + my @slugs = $post.all-slugs; + my $base = $meta.get-base-url; + if @slugs.elems { + "$base/posts/by-slug/{@slugs[*-1]}.html" + } else { + "$base/posts/by-id/$id.html" + } +} + +#| Convert a single post to an atom item +sub post-to-item(BlogMeta:D $meta, Int:D $id, Post:D $post --> XML::Element) { + my $link = post-link $meta, $id, $post; + my $desc = $post.description; + my $xml = XML::Element.new(:name); + $xml.append: + XML::Element.new(:name, :nodes([$link])); + $xml.append: + XML::Element.new(:name, :nodes([$post.title])); + $xml.append: + XML::Element.new(:name<updated>, :nodes([$post.updated])); + $xml.append: + XML::Element.new(:name<published>, :nodes([$post.posted-at])); + my $author = XML::Element.new(:name<author>); + $author.append: + XML::Element.new(:name<email>, :nodes(["thatonelutenist@stranger.systems"])); + $author.append: + XML::Element.new(:name<name>, :nodes(["Nathan McCarty"])); + $xml.append: $author; + $xml.append: + XML::Element.new(:name<link>, :attribs({:href($link), :rel<alternate>})); + $xml.append: + XML::Element.new(:name<summary>, :nodes([$desc])) if $desc; + + $xml +} + +#| Produce an atom feed from the database +sub posts-to-atom($db --> XML::Element) is export { + my $updated = $db.posts.values.map(*.updated).max; + my $xml = + XML::Element.new( + :name<feed>, + :attribs({:xmlns('http://www.w3.org/2005/Atom')})); + $xml.append: XML::Element.new(:name<id>, :nodes([$db.meta.get-base-url])); + $xml.append: XML::Element.new(:name<title>, :nodes([$db.meta.title])); + $xml.append: XML::Element.new(:name<updated>, :nodes([$updated])); + for $db.sorted-posts -> $pair { + unless $pair.value.hidden { + $xml.append: post-to-item $db.meta, $pair.key, $pair.value; + } + } + $xml +} diff --git a/lib/Config.rakumod b/lib/Config.rakumod index c7a32df..595d2ce 100644 --- a/lib/Config.rakumod +++ b/lib/Config.rakumod @@ -72,7 +72,7 @@ method site-header(BlogMeta:D $meta) { 'About'; ]; ]; - a :href</feed.xml>, [ + a :href</atom.xml>, [ icon 'rss'; ' '; span [ @@ -193,8 +193,8 @@ method generate-blurb(Int:D $id, $db) { my $desc = $post.description; my @slugs = $post.all-slugs; # Use the primary slug if there is one, the id if there isn't - my $link = do if @slugs.elems > 0 { - "/posts/by-slug/{@slugs[0]}.html" + my $link = do if @slugs.elems { + "/posts/by-slug/{@slugs[*-1]}.html" } else { "/posts/by-id/$id.html" } diff --git a/lib/DB.rakumod b/lib/DB.rakumod index c4e7827..fe30ec0 100644 --- a/lib/DB.rakumod +++ b/lib/DB.rakumod @@ -5,12 +5,14 @@ unit module DB; use Pandoc; use JSON::Class:auth<zef:vrurg>; +use XML; use DB::Post; use DB::BlogMeta; use DB::MarkdownPost; use DB::IdrisPost; use DB::PlaceholderPost; +use Atom; use Config; subset PostTypes where MarkdownPost:D | IdrisPost:D | PlaceholderPost:D; @@ -92,12 +94,14 @@ class PostDB { $out-dir.add('index.html').spurt: $config.generate-index(self); # Render the archive $out-dir.add('archive.html').spurt: $config.generate-archive(self); - # TODO: Symlink the about article + # Symlink the about article my $about-path = $out-dir.add('about.html'); $about-path.unlink if $about-path.l; $by-id.add("{$!meta.about-id}.html").symlink: $about-path; - # TODO: Render the rss/atom feed - die "Not Implemented" + # Render the rss/atom feed + my $atom-path = $out-dir.add('atom.xml'); + my $atom = posts-to-atom self; + $atom-path.spurt: ~$atom; } #| Get a list of posts sorted by date diff --git a/lib/DB/BlogMeta.rakumod b/lib/DB/BlogMeta.rakumod index b8477ae..e6ccf5d 100644 --- a/lib/DB/BlogMeta.rakumod +++ b/lib/DB/BlogMeta.rakumod @@ -16,3 +16,16 @@ has Int:D $.placeholder-id is rw = 0; #| The id of the about post has Int:D $.about-id is rw = 0; + +#| The base url of this post +has Str:D $.base-url is required; + +#| Return the base url, but substitute it out if the test environment variable +#| is set +method get-base-url(--> Str:D) { + if %*ENV<BLOG_TEST> { + "http://localhost:8080" + } else { + $!base-url + } +} diff --git a/lib/DB/Post.rakumod b/lib/DB/Post.rakumod index dbdc0f7..ed0819d 100644 --- a/lib/DB/Post.rakumod +++ b/lib/DB/Post.rakumod @@ -43,6 +43,15 @@ has Bool:D $.hidden is json is rw = False; #| document produced it method title(--> Str:D) {...} +#| The time the post was last updated at +method updated(--> DateTime:D) { + if @!edited-at { + @!edited-at.max + } else { + $!posted-at + } +} + #| Get the list of slugs for this post, including ones auto generated from #| the title, as well as any additional slugs method all-slugs(--> Array[Str:D]) {