From 2922edba84f5e58e0cba6049bc22139f713054f9 Mon Sep 17 00:00:00 2001 From: Josh Glover Date: Sun, 4 Feb 2024 09:47:51 +0100 Subject: [PATCH 1/5] Add prev and next post to post metadata --- src/quickblog/internal.clj | 55 +++++++++++++++++++++++++++++++------ test/quickblog/api_test.clj | 6 ++-- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/quickblog/internal.clj b/src/quickblog/internal.clj index 4c8022b..13e4fd1 100644 --- a/src/quickblog/internal.clj +++ b/src/quickblog/internal.clj @@ -217,6 +217,42 @@ (println error) true)) +(defn- delink-prev + "Make sure we don't have a chain of previous posts" + [post] + (update post :prev dissoc :prev)) + +(defn link-posts-reduce-f [{:keys [posts prev] :as acc} post] + (let [cur (when prev + (-> prev (assoc :next post) delink-prev)) + post' (if prev + (assoc post :prev prev) + post) + posts' (if cur + (conj posts cur) + posts)] + {:posts posts', :prev post'})) + +(defn link-posts + "For a map of files to posts, sorts them adds to each post a :prev key pointing + to the previous post and a :next key pointing to the next post, where previous + and next are defined by the order imposed by `sort-posts`." + [posts] + (->> (let [ps (->> posts + vals + remove-previews + sort-posts + reverse + (reduce link-posts-reduce-f {:posts [], :prev nil}))] + (-> ps :posts (conj (-> ps :prev delink-prev)))) + (map (fn [{:keys [file] :as post}] + [file post])) + (into {}) + ;; We need to merge this with the original posts map because that + ;; map might contain preview posts, whereas the linking process + ;; intentionally skips preview posts in the ordering + (merge posts))) + (defn load-posts [{:keys [cache-dir cached-posts posts-dir] :as opts}] (if (fs/exists? posts-dir) (let [cache-file (fs/file cache-dir cache-filename) @@ -225,19 +261,20 @@ (set post-paths) (set (fs/modified-since cache-file post-paths))) _cached-post-paths (set/difference post-paths modified-post-paths)] - (merge (->> cached-posts - (map (fn [[file post]] - [file (assoc post :html (read-cached-post opts file))])) - (into {})) - (->> modified-post-paths - (map (juxt ->filename (partial load-post opts))) - (remove (partial has-error? opts)) - (into {})))) + (->> (merge (->> cached-posts + (map (fn [[file post]] + [file (assoc post :html (read-cached-post opts file))])) + (into {})) + (->> modified-post-paths + (map (juxt ->filename (partial load-post opts))) + (remove (partial has-error? opts)) + (into {}))) + link-posts)) {})) (defn only-metadata [posts] (->> posts - (map (fn [[file post]] [file (dissoc post :html)])) + (map (fn [[file post]] [file (dissoc post :html :prev :next)])) (into {}))) (defn load-cache [{:keys [cache-dir rendering-system-files]}] diff --git a/test/quickblog/api_test.clj b/test/quickblog/api_test.clj index 344bc9e..a00f13d 100644 --- a/test/quickblog/api_test.clj +++ b/test/quickblog/api_test.clj @@ -160,12 +160,14 @@ (testing "with blank page suffix" (with-dirs [posts-dir templates-dir - out-dir] + out-dir + cache-dir] (write-test-post posts-dir {:tags #{"foobar" "tag with spaces"}}) (api/render {:page-suffix "" :posts-dir posts-dir :templates-dir templates-dir - :out-dir out-dir}) + :out-dir out-dir + :cache-dir cache-dir}) (doseq [filename ["test.html" "index.html" "archive.html" (fs/file "tags" "index.html") (fs/file "tags" "tag-with-spaces.html") From 1983c01a2056f5ecacf8ab6da3c5833b684dda1a Mon Sep 17 00:00:00 2001 From: Josh Glover Date: Sun, 4 Feb 2024 11:29:01 +0100 Subject: [PATCH 2/5] Fix test runner See https://github.com/babashka/cli/pull/79 --- test/quickblog/test_runner.clj | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/quickblog/test_runner.clj b/test/quickblog/test_runner.clj index c5e32d4..d051630 100644 --- a/test/quickblog/test_runner.clj +++ b/test/quickblog/test_runner.clj @@ -1,11 +1,11 @@ (ns quickblog.test-runner - {:org.babashka/cli {:coerce {:dirs :strings - :nses :symbols - :patterns :strings - :vars :symbols - :includes :keywords - :excludes :keywords - :only :symbols}}} + {:org.babashka/cli {:coerce {:dirs [:string] + :nses [:symbol] + :patterns [:string] + :vars [:symbol] + :includes [:keyword] + :excludes [:keyword] + :only :symbol}}} (:refer-clojure :exclude [test]) (:require [clojure.test :as test] From 593e69f6e5d147d7a70744e5c456cb83373a1f33 Mon Sep 17 00:00:00 2001 From: Josh Glover Date: Sun, 4 Feb 2024 11:40:32 +0100 Subject: [PATCH 3/5] Add test for linking posts --- test/quickblog/api_test.clj | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/quickblog/api_test.clj b/test/quickblog/api_test.clj index a00f13d..461f197 100644 --- a/test/quickblog/api_test.clj +++ b/test/quickblog/api_test.clj @@ -300,6 +300,46 @@ :out-dir out-dir}) (is (not (str/includes? (slurp (fs/file out-dir "test.html")) lib/live-reload-script)))))) +(deftest link-posts + (testing "add prev and next posts to post metadata" + (with-dirs [posts-dir + templates-dir + cache-dir + out-dir] + (write-test-post posts-dir {:file "post1.md", :title "post1", :date "2024-02-01"}) + (write-test-post posts-dir {:file "post2.md", :title "post2", :date "2024-02-02"}) + (write-test-post posts-dir {:file "post3.md", :title "post3", :date "2024-02-03" + :preview? true}) + (write-test-post posts-dir {:file "post4.md", :title "post4", :date "2024-02-04"}) + (fs/create-dirs templates-dir) + (spit (fs/file templates-dir "base.html") + "{{body | safe }}") + (spit (fs/file templates-dir "post.html") + (str "{% if prev %}prev: {{prev.title}}{% endif %}" + "\n" + "{% if next %}next: {{next.title}}{% endif %}")) + (api/render {:posts-dir posts-dir + :templates-dir templates-dir + :cache-dir cache-dir + :out-dir out-dir}) + (is (= (slurp (fs/file out-dir "post1.html")) + "\nnext: post2")) + (is (= (slurp (fs/file out-dir "post2.html")) + "prev: post1\nnext: post4")) + (is (= (slurp (fs/file out-dir "post3.html")) + "\n")) + (is (= (slurp (fs/file out-dir "post4.html")) + "prev: post2\n")) + (let [cache (lib/load-cache {:cache-dir cache-dir})] + (is (nil? (get-in cache ["post1.md" :prev]))) + (is (= (get-in cache ["post1.md" :next :title "post2"]))) + (is (= (get-in cache ["post2.md" :prev :title "post1"]))) + (is (= (get-in cache ["post2.md" :next :title "post4"]))) + (is (nil? (get-in cache ["post3.md" :prev]))) + (is (nil? (get-in cache ["post3.md" :next]))) + (is (= (get-in cache ["post4.md" :prev :title "post2"]))) + (is (nil? (get-in cache ["post4.md" :next]))))))) + ;; disabled, flaky in CI, cc @jmglov #_(deftest caching (testing "assets" From 26e712a38a669b45d5183f57280cb753ef490b07 Mon Sep 17 00:00:00 2001 From: Josh Glover Date: Sun, 4 Feb 2024 12:01:21 +0100 Subject: [PATCH 4/5] Include preview posts in linking for watch mode --- src/quickblog/api.clj | 4 +- src/quickblog/internal.clj | 15 +++--- test/quickblog/api_test.clj | 104 +++++++++++++++++++++++------------- 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/src/quickblog/api.clj b/src/quickblog/api.clj index 6cf563e..824c747 100644 --- a/src/quickblog/api.clj +++ b/src/quickblog/api.clj @@ -630,7 +630,9 @@ :as opts} (-> opts (assoc :watch (format "" - lib/live-reload-script)) + lib/live-reload-script) + ;; Include preview posts in post linking (prev and next) + :include-preview-posts? true) apply-default-opts render)] (reset! posts-cache (:posts opts)) diff --git a/src/quickblog/internal.clj b/src/quickblog/internal.clj index 13e4fd1..08596e0 100644 --- a/src/quickblog/internal.clj +++ b/src/quickblog/internal.clj @@ -236,11 +236,13 @@ (defn link-posts "For a map of files to posts, sorts them adds to each post a :prev key pointing to the previous post and a :next key pointing to the next post, where previous - and next are defined by the order imposed by `sort-posts`." - [posts] - (->> (let [ps (->> posts + and next are defined by the order imposed by `sort-posts`. Preview posts are + skipped unless `:include-preview-posts?` is set true in `opts`." + [{:keys [include-preview-posts?]} posts] + (->> (let [maybe-remove-previews (if include-preview-posts? identity remove-previews) + ps (->> posts vals - remove-previews + maybe-remove-previews sort-posts reverse (reduce link-posts-reduce-f {:posts [], :prev nil}))] @@ -250,7 +252,8 @@ (into {}) ;; We need to merge this with the original posts map because that ;; map might contain preview posts, whereas the linking process - ;; intentionally skips preview posts in the ordering + ;; intentionally skips preview posts unless the `include-preview-posts?` + ;; param is true (merge posts))) (defn load-posts [{:keys [cache-dir cached-posts posts-dir] :as opts}] @@ -269,7 +272,7 @@ (map (juxt ->filename (partial load-post opts))) (remove (partial has-error? opts)) (into {}))) - link-posts)) + (link-posts opts))) {})) (defn only-metadata [posts] diff --git a/test/quickblog/api_test.clj b/test/quickblog/api_test.clj index 461f197..25eec3d 100644 --- a/test/quickblog/api_test.clj +++ b/test/quickblog/api_test.clj @@ -301,44 +301,72 @@ (is (not (str/includes? (slurp (fs/file out-dir "test.html")) lib/live-reload-script)))))) (deftest link-posts - (testing "add prev and next posts to post metadata" - (with-dirs [posts-dir - templates-dir - cache-dir - out-dir] - (write-test-post posts-dir {:file "post1.md", :title "post1", :date "2024-02-01"}) - (write-test-post posts-dir {:file "post2.md", :title "post2", :date "2024-02-02"}) - (write-test-post posts-dir {:file "post3.md", :title "post3", :date "2024-02-03" - :preview? true}) - (write-test-post posts-dir {:file "post4.md", :title "post4", :date "2024-02-04"}) - (fs/create-dirs templates-dir) - (spit (fs/file templates-dir "base.html") - "{{body | safe }}") - (spit (fs/file templates-dir "post.html") - (str "{% if prev %}prev: {{prev.title}}{% endif %}" - "\n" - "{% if next %}next: {{next.title}}{% endif %}")) - (api/render {:posts-dir posts-dir - :templates-dir templates-dir - :cache-dir cache-dir - :out-dir out-dir}) - (is (= (slurp (fs/file out-dir "post1.html")) - "\nnext: post2")) - (is (= (slurp (fs/file out-dir "post2.html")) - "prev: post1\nnext: post4")) - (is (= (slurp (fs/file out-dir "post3.html")) - "\n")) - (is (= (slurp (fs/file out-dir "post4.html")) - "prev: post2\n")) - (let [cache (lib/load-cache {:cache-dir cache-dir})] - (is (nil? (get-in cache ["post1.md" :prev]))) - (is (= (get-in cache ["post1.md" :next :title "post2"]))) - (is (= (get-in cache ["post2.md" :prev :title "post1"]))) - (is (= (get-in cache ["post2.md" :next :title "post4"]))) - (is (nil? (get-in cache ["post3.md" :prev]))) - (is (nil? (get-in cache ["post3.md" :next]))) - (is (= (get-in cache ["post4.md" :prev :title "post2"]))) - (is (nil? (get-in cache ["post4.md" :next]))))))) + (let [posts (->> (range 1 5) + (map (fn [i] + {:file (format "post%s.md" i) + :title (format "post%s" i) + :date (format "2024-02-0%s" i) + :preview? (= 3 i)}))) + write-templates! (fn [templates-dir] + (fs/create-dirs templates-dir) + (spit (fs/file templates-dir "base.html") + "{{body | safe }}") + (spit (fs/file templates-dir "post.html") + (str "{% if prev %}prev: {{prev.title}}{% endif %}" + "\n" + "{% if next %}next: {{next.title}}{% endif %}")))] + + (testing "add prev and next posts to post metadata" + (with-dirs [posts-dir + templates-dir + cache-dir + out-dir] + (doseq [post posts] + (write-test-post posts-dir post)) + (write-templates! templates-dir) + (api/render {:posts-dir posts-dir + :templates-dir templates-dir + :cache-dir cache-dir + :out-dir out-dir}) + (is (= (slurp (fs/file out-dir "post1.html")) + "\nnext: post2")) + (is (= (slurp (fs/file out-dir "post2.html")) + "prev: post1\nnext: post4")) + (is (= (slurp (fs/file out-dir "post3.html")) + "\n")) + (is (= (slurp (fs/file out-dir "post4.html")) + "prev: post2\n")) + (let [cache (lib/load-cache {:cache-dir cache-dir})] + (is (nil? (get-in cache ["post1.md" :prev]))) + (is (= (get-in cache ["post1.md" :next :title "post2"]))) + (is (= (get-in cache ["post2.md" :prev :title "post1"]))) + (is (= (get-in cache ["post2.md" :next :title "post4"]))) + (is (nil? (get-in cache ["post3.md" :prev]))) + (is (nil? (get-in cache ["post3.md" :next]))) + (is (= (get-in cache ["post4.md" :prev :title "post2"]))) + (is (nil? (get-in cache ["post4.md" :next])))))) + + (testing "include preview posts" + (with-dirs [posts-dir + templates-dir + cache-dir + out-dir] + (doseq [post posts] + (write-test-post posts-dir post)) + (write-templates! templates-dir) + (api/render {:posts-dir posts-dir + :templates-dir templates-dir + :cache-dir cache-dir + :out-dir out-dir + :include-preview-posts? true}) + (is (= (slurp (fs/file out-dir "post1.html")) + "\nnext: post2")) + (is (= (slurp (fs/file out-dir "post2.html")) + "prev: post1\nnext: post3")) + (is (= (slurp (fs/file out-dir "post3.html")) + "prev: post2\nnext: post4")) + (is (= (slurp (fs/file out-dir "post4.html")) + "prev: post3\n")))))) ;; disabled, flaky in CI, cc @jmglov #_(deftest caching From 334d0443b6cbeec372bd0ee1288e7f0813b85a7d Mon Sep 17 00:00:00 2001 From: Josh Glover Date: Sun, 4 Feb 2024 15:42:57 +0100 Subject: [PATCH 5/5] Add page links example to README --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index bddb575..0d5605a 100644 --- a/README.md +++ b/README.md @@ -217,6 +217,26 @@ Resources for understanding and testing social sharing: - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) - [Twitter Card Validator](https://cards-dev.twitter.com/validator) +### Post links + +quickblog adds `prev` and `next` metadata to each post (where `prev` is the +previous post and `next` is the next post in date order, oldest to newest). You +can make use of these by adding something similar to this to your `post.html` +template: + +``` html +{% if any prev next %} +
+{% if prev %} + +{% endif %} +{% if next %} + +{% endif %} +
+{% endif %} +``` + ## Templates quickblog uses the following templates in site generation: