Ghost handlebars helpers + Javascript = secure filtering
Ghost has the next_post and prev_post helpers to enable theme developers to build standard blog navigation with minimal code. They're very simple helpers and do what their names say. They have one attribute, in, which lets you limit which posts it selects from using primary_tag, primary_author, and author.
That didn't quite do it for me.
I didn't want non-public posts to be returned by next_post and prev_post. I wanted the navigation to end rather than link to a page with content a visitor can't access with just a subscribe form on it.
I couldn't work out how to use the content API in a way that returned only accessible posts for the current user. And I didn't want to embed an admin API key in my pages so I came up with the following workaround.
Build the data structure I need by using Ghost's inbuilt helpers to grab all the posts the current user can access:
var chapter = 0
var accessiblePosts = {};
{{#get "posts" filter="tags:chapter" limit="all" as | chaps |}}
{{#foreach chaps }}
{{#if access}}
chapter = parseInt("{{slug}}".split("-")[1], 10);
accessiblePosts[chapter] = { url: "{{url}}", slug: "{{slug}}", title: "{{title}}" };
{{/if}}
{{/foreach}}
{{/get}}
The key here is the combination of the get helper and the Ghost template variable access, which allows me to do all the fetching and filtering server side before the user ever sees the page.
Note that chunk of code is part of a larger block that is contained within a <script> tag.
It's a bit of a hack, but it allows me to do even hackier things, like use accessiblePosts to build the next/previous navigation items if they are available. For example:
const previousChapter = accessiblePosts[currentChapterNum - 1] || false;
if (previousChapter) {
var prevPostDiv = document.getElementById("prevpost");
var prevPostLink = document.createElement("a");
prevPostLink.setAttribute("href", previousChapter.url);
prevPostLink.setAttribute("class", "flex no-underline justify-start");
var prevPostLinkText = document.createElement("h4");
prevPostLinkText.setAttribute("class", "ml-2");
prevPostLinkText.innerHTML = previousChapter.title;
prevPostLink.appendChild(left_svg);
prevPostLink.appendChild(prevPostLinkText);
prevPostDiv.appendChild(prevPostLink);
}
(If you're wondering about the class names, I'm using Tailwind for the CSS )
It is hackier than I would like, but since Ghost doesn't yet allow us to integrate our own custom handlebars helpers it's the only way I could find to get it working. If you know of a simpler, cleaner solution get in touch.
If you want to see the code in action check it out on my serial epic fantasy The Public Testimony of the Mercenary Called Graef.