5/30/18 - There's a Catch: Earnie Smith(@ShortFormErnie) pointed out that this doesn't work in all views. Specifically RSS and AMP. After digging around the Ghost source, it looks like AMP - at the very least - will require modifying the built-in AMP app that comes with Ghost. I'm still looking into it.

Part 1 is Hiding Over Here

Code is Buried in the Sand Here

Last time, we made a simple shortcode replacement app. Now it's time to add a little bit of functionality to it, but first, did you spot the hanging issue?

Awesome4

Yeah... Our shortcodes don't render unless the view directly renders markdown to HTML. This is actually a good thing. For our simple app, It's fine if it just renders everywhere, but for shortcodes that make bigger changes, you may not want it to render the same on an overview page where space is limited.

Let's Fix That First

We know that our payload doesn't have an html attribute, but what does it have? I'm running my dev setup using node --inspect, so let's just dump the payload straight to the console and take a look.

Awesome5-1

Looks like it's just an array of post payloads. Perfect. This will be easy. You may even want to take a second and see what you'd do here.

Great, you figured it out on you own... right? Fine, I'll tell you. Currently, our index.js should look something like this:

var App = require('ghost-app')
,   MyAwesomeApp;

MyAwesomeApp = App.extend({
  filters: {
    prePostsRender: 'handlePrePostsRender'
  },
  handlePrePostsRender: function handlePrePostsRender(payload) {
    if (!payload.html) return payload;
    payload.html = payload.html.replace(
      /\[awesome\]/g
    , '<span style="color:darkorange;">Awesome!</span>'
    );
    return payload;
  }
});

module.exports = MyAwesomeApp;

We want to handle arrays of posts, so this needs to go:

if (!payload.html) return payload;

Instead, we'll use this:

if (Array.isArray(payload))
  return payload.map(handlePrePostsRender);

Wait! Shouldn't it be this.handlePrePostsRender? Nope. In the filter handle context, this is your global object, not an instance of MyAwesomeApp. And we can't just access MyAwesomeApp.handlePrePostsRender either since MyAwesomeApp is a function that returns a new object. Yeah. Weird. Anyway, there are a few reasons I always type out full function names even when assigning the function to a variable. This is one of them. Now I can just reference the function directly. Lazy, I know, but I'm not going to worry about it right now.

Now let's restart our Ghost server and take a look:
Awesome6
Yay.

On, Teb. On.

So, now that we've got that issue fixed, how do we accept arguments in our shortcode?

Regex!
ss76a9n.jpg

But how? First thing I want to do, is allow an argument. So let's change that regex bit so this:

/\[awesome\]/g

becomes:

/\[awesome( +\w+)?\]/g

But what does it mean? /\[awesome\]/ looked for the exact string [awesome]. We added ( +\w+)?, which identifies an optional substring matching a word of any length greater than 1 following at least 1 space.

Next, we're going to change our replacement operand to a function. ??? Yep. That way we can add logic based on what is matched. Right now, it looks like this:

<span style="color:darkorange;">Awesome!</span>

I'm going to change it so it looks like this:

(match, param) => {
  return '<span style="color:darkorange;">'
  + (param ? param.trim() + ' is ' : '')
  + 'Awesome!</span>'
}

Hopefully, you understood that but - for those that don't - () => {}, also called a fat-arrow function is a relatively new thing. It's for quickly defining anonymous functions. My function takes 2 arguments, the first will be the whole string matched, and the second will be the first substring matched. If you have more substrings(parameters) just add more arguments to your function[1]. Inside the function, I just used a ternary operator(<expression> ? <truthy> : <falsy>) to check if a parameter was found, and change my output accordingly. For those that understood the code: Great! You can just skip this parag... oh.

Giving it a Try

You made those changes and saved your files right? Great, now lets give it a try. Fire up ghost and open up your test post. It should look the same as it did before. Edit it and add something like the last line below:
Awesome7-1
Save it, and give it a try. It should give you something like this:
Awesome8

If it works, then:
congratulations7-1
It's not very useful, but you can play around with it, make it your own, have fun with it, and maybe make it worth something.

If it doesn't work, maybe check out the code over on my GitHub. See if you did anything wrong. Maybe Ghost just changed something and everything broke. Apps aren't officially supported. It can happen.

References


  1. String.prototype.replace @ MDN - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace ↩︎