Check out "Do you speak JavaScript?" - my latest video course on advanced JavaScript.
Language APIs, Popular Concepts, Design Patterns, Advanced Techniques In the Browser

Smart placeholders in your markdown

Smart placeholders in your markdown Photo by Kelly Sikkema

I ❤️ markdown. I like its simplicity and minimalistic API which is good enough to cover most of needed HTML markup. At least for textual content. At work we have a small node based microservice that delivers data from Contentful in exactly markdown format. It's all working well but we started having cases where part of the text is in Contentful and the other part is in that microservice. For example when we have a button with a catchy design. We want to content manage the label of the button but the actual markup to be on our side.

One approach to such requirement is to create a new field in Contentful and place the button's label there. This however is not scaling well. We can't keep adding more and more fields. The other approach is to have markers in the content which are later replaced. We have already some placeholders in the format of {placeholder name here}. A bit like mustache template language. For example:

Hey,

Here's a list of stuff that you can do at home:

{stuff}

{seeMore}

Then when a request comes in we fetch that markdown and replace {stuff} with a list of links and {seeMore} with a button. Now, let's say that we want to internationalize the label of the button. Right now that label lives into the microservice but we want to add it in Contentful. So something like this will make more sense:

Hey,

Here's a list of stuff that you can do at home:

{stuff}

{seeMore=Click here to see more suggestions}

The thing is that when we transform this markdown to HTML we are getting:

<p>Hey,</p>
<p>Here's a list of stuff that you can do at home:</p>
<p>{stuff}</p>
<p>{seeMore=Click here to see more suggestions}</p>
<p>Best regards</p>

We need Click here to see more suggestions extracted from the markdown and only {seeMore} to appear so we can do a proper replacement. And here is where the marked advanced usage comes into play.

const marked = require('marked');

const rawContent = '...'; // the raw markdown from contentful
const renderer = new marked.Renderer();
const placeholders = {};

renderer.paragraph = function(text) {
  const lines = text.split('\n');
  let content = lines[0];
  if (
    lines.length === 1 &&
    content.indexOf('{') === 0 &&
    content.lastIndexOf('}') === content.length - 1
  ) {
    content = content.substr(1, content.length - 2);
    const equalSign = content.indexOf('=');
    let key;
    let value;
    if (equalSign > 0) {
      key = content.substr(0, equalSign);
      value = content.substr(equalSign + 1);
    } else {
      key = content;
      value = '';
    }
    placeholders[key] = value;
    return `{${key}}`;
  }
  return `<p>${text}</p>`;
};

console.log(marked(rawContent, { renderer }));
console.log(placeholders);

The marked API allows us to define a custom renderer and process each element. In our case we need to recognize a one-line paragraph that starts with { and ends with }. Inside there may be =. The result of this code is:

<p>Hey,</p>
<p>Here's a list of stuff that you can do at home:</p>
<p>{stuff}</p>
<p>{seeMore}</p>
<p>Best regards</p>

{
  stuff: '',
  seeMore: 'Click here to see more suggestions'
}

And this is how with just ~20 lines of code we have a little bit smarter placeholders.


Later on I realized that we may use those new placeholders to pass variables-like data to the microservice. For example:

Hey,

Here's a list of stuff that you can do at home:

{stuff}

{category=games}

Then later we just replace {category} with an empty string. Hope you find this helpful. It works pretty well for us. 😎

If you enjoy this post, share it on Twitter, Facebook or LinkedIn.