Skip to content

Commit

Permalink
Link to previous and next post
Browse files Browse the repository at this point in the history
  • Loading branch information
jmglov committed Feb 15, 2024
1 parent 4c0c94f commit 14eca6b
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Instances of quickblog can be seen here:

## Unreleased

- Link to previous and next posts; see "Linking to previous and next posts" in
the README ([@jmglov](https://github.com/jmglov))
- Fix flaky caching tests ([@jmglov](https://github.com/jmglov))
- Fix argument passing in test runner ([@jmglov](https://github.com/jmglov))
- Add `--date` to api/new. ([@jmglov](https://github.com/jmglov))
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

### Linking to previous and next posts

If you set the `:link-prev-next-posts` option to `true`, 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 %}
<div class="post-prev-next">
{% if prev %}
<div>⏪ <a href="{{prev.file|replace:.md:.html}}">{{prev.title}}</a></div>
{% endif %}
{% if next %}
<div><a href="{{next.file|replace:.md:.html}}">{{next.title}}</a> ⏩</div>
{% endif %}
</div>
{% endif %}
```

## Templates

quickblog uses the following templates in site generation:
Expand Down
59 changes: 49 additions & 10 deletions src/quickblog/internal.clj
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,17 @@
(map first)
set))

(defn modified-posts [{:keys [force-render out-dir posts posts-dir
rendering-system-files]}]
(defn modified-posts
[{:keys [force-render out-dir posts cached-posts posts-dir rendering-system-files]}]
(->> posts
(filter (fn [[file _]]
(filter (fn [[file post]]
(let [out-file (fs/file out-dir (html-file file))
post-file (fs/file posts-dir file)]
(or force-render
(rendering-modified? out-file
(cons post-file rendering-system-files))))))
(cons post-file rendering-system-files))
(not= (select-keys post [:prev :next])
(select-keys (cached-posts file) [:prev :next]))))))
(map first)
set))

Expand All @@ -290,9 +292,38 @@
(mapcat (partial map (fn [[_ {:keys [tags]}]] tags)))
(apply set/union)))

(defn refresh-cache [{:keys [cached-posts
posts]
:as opts}]
(defn assoc-prev-next
"If the `:link-prev-next-posts` opt is true, adds to each post a :prev key
pointing to the filename of the previous post by date and a :next key pointing
to the filename of the next post by date. Preview posts are skipped unless the
`:include-preview-posts-in-linking` is true."
[{:keys [posts link-prev-next-posts include-preview-posts-in-linking]
:as opts}]
(if link-prev-next-posts
(let [remove-preview-posts (if include-preview-posts-in-linking
identity
#(remove (comp :preview val) %))
post-keys (->> posts
remove-preview-posts
(sort-by (comp :date val))
(mapv first))]
(assoc opts :posts
;; We need to merge the linked posts on top of the original ones
;; so that preview posts are still present even when they're
;; excluded from linking
(merge posts
(->> post-keys
(map-indexed
(fn [i k]
[k (assoc (posts k)
:prev (when (pos? i)
(post-keys (dec i)))
:next (when (< i (dec (count post-keys)))
(post-keys (inc i))))]))
(into {})))))
opts))

(defn refresh-cache [{:keys [cached-posts posts] :as opts}]
;; watch mode manages caching manually, so if cached-posts and posts are
;; already set, use them as is
(let [cached-posts (if cached-posts
Expand All @@ -303,6 +334,7 @@
posts
(load-posts opts))
opts (assoc opts :posts posts)
opts (assoc-prev-next opts)
opts (assoc opts
:modified-metadata (modified-metadata opts))]
(assoc opts
Expand Down Expand Up @@ -397,12 +429,19 @@
out-dir
page-suffix
page-template
post-template]
post-template
link-prev-next-posts
post-order
posts]
:as opts}
{:keys [file html description image image-alt]
{:keys [file html description image image-alt prev next]
:as post-metadata}]
(let [out-file (fs/file out-dir (html-file file))
post-metadata (merge {:discuss discuss-link :page-suffix page-suffix} (assoc post-metadata :body @html))
post-metadata (merge {:discuss discuss-link :page-suffix page-suffix}
(assoc post-metadata :body @html)
(when link-prev-next-posts
{:next (posts next)
:prev (posts prev)}))
body (selmer/render post-template post-metadata)
author (-> (:twitter-handle post-metadata) (or twitter-handle))
image (when image (if (re-matches #"^https?://.+" image)
Expand Down
61 changes: 61 additions & 0 deletions test/quickblog/api_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,64 @@
mtime (str (fs/last-modified-time file))]]
(is (= [filename (mtimes filename)]
[filename mtime]))))))

(deftest link-prev-next-posts
(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
:link-prev-next-posts true})
(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"))))

(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
:link-prev-next-posts true
:include-preview-posts-in-linking 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"))))))

0 comments on commit 14eca6b

Please sign in to comment.