Successive Approximations

Running a Ghost Blog, Part 2

Running a Ghost Blog, Part 2

I mentioned in the previous post in this series that I was looking forward to working on this blog like it was a car, and that has turned out truer than even I expected. This website is barely two weeks old as I write this, and I'm already modifying the source of the blogging platform to fix tiny details that are just... bugging me. I'll admit it was a slippery slope to live-editing the source of the CMS, but it didn't start there.

The first step down the slippery slope was uploading a custom theme, like I talked about last time. Then when that didn't go as planned, I had to make an efficient system for updating the CSS styles of the page.

The next step down the slope was starting to edit the Handlebars templates that actually generate the HTML that is then styled by the generated CSS. Of course, I'm lucky that Ghost even uses Handlebars, which is a credit to it being written relatively recently, in node.js. Handlebars is one of those engineering solutions that has had so many attempts made at it, that eventually someone got it exactly right: decently powerful, but still simple.

I started reading the Handlebars docs as well as the documentation on the helpers that Ghost provides for rendering pages. After tinkering and testing and tinkering and testing, I fixed almost every gripe I had with the template. All except one: ellipses.

The main page will show a truncated preview of each post with a button to read more, but because of reasons we'll see in a minute, the preview would just end in the middle of a sentence:

Now, I know what you're thinking. Yes, this is a minor gripe. Could I live with it that way? Yes.

But what if I didn't have to?

Over the edge

So I did a little poking around Google and stumbled on this article, which pointed out that I could always just directly edit the source code of the blog platform to get the minor tweak I wanted.

I wasn't sure if I thought this was madness or genius.

But I found the file in question, core/server/helpers/content.js:

module.exports = function content(options = {}) {
    const hash = options.hash || {};
    const truncateOptions = {};
    let runTruncate = false;

    for (const key of ['words', 'characters']) {
        if (hash.hasOwnProperty(key)) {
            runTruncate = true;
            truncateOptions[key] = parseInt(hash[key], 10);
        }
    }

    if (this.html === null) {
        this.html = '';
    }

    if (runTruncate) {
        return new SafeString(
            downsize(this.html, truncateOptions)
        );
    }

    return new SafeString(this.html);
};

After rooting around a little more, I figured out that SafeString is a wrapper around a String that tells Handlebars that the contents has already been escaped, and so it's safe to display as-is. I tried just putting an ellipsis in there, but alas it would come out printed outside the paragraph or blockquote that ended the preview:

Even worse.

But what about that downsize function in there? Turns out it's a single-function utility library that just does the job of keeping track of what HTML tags are open when you want to truncate an HTML string, and cleanly closes them after however many words you want your preview to be. (As an aside, it's essentially a single-function library that hasn't been updated in 5 years, which gives me an queasy, left-pad-ish, feeling. Hopefully it doesn't go anywhere.)

I held my breath and dove into the source for that function to see if I could add the ellipsis in just the right spot. But as I read the code--which, thankfully, is JavaScript, and not PHP as it would be if this were a WordPress blog--I found the function accepted an option for just exactly this purpose:

        if (options.append && isAtLimit()) {
            truncatedText += options.append;
        }

And lo, thus I was saved. No need to hack on a dependency of Ghost. All it took was inserting a single line to Ghost itself:

    if (runTruncate) {
        truncateOptions['append'] = '...'
        return new SafeString(
            downsize(this.html, truncateOptions)
        );
    }

Et voilĂ ! Just the right punctuation at just the right place:

Now, yes, I will have to re-do this fix when I update the version of Ghost running my blog. I made notes in my work log about this and the fix if I ever had to implement it again. But, I can deal with that.

Ben Berry

I don't have comments on my posts, but if you want to reach me, email me at my first name at benb.us.