Markdown Badges for VSCode markdown-preview-enhanced users
Tags: markdown, vscode, badges, javascript
Published: 2021-01-08 00:57:21

**requirements**: 
- VSCode
- markdown-preview-enhanced extension

# Intro
I recently stumbled across [this site](https://docs.microsoft.com/en-us/contribute/markdown-reference#alerts-note-tip-important-caution-warning) from microsoft and I was thinking: "Hey, why don't we have this in regular markdown!? This looks usefull!"

I am a markdown enthusiast and love to take notes quickly in a informative way. I personally use the VSCode Extension: [markdown-preview-enhanced](https://shd101wyy.github.io/markdown-preview-enhanced).
I love it because you can:
- define your own styling within a `.less` file
- have multiple options to convert markdown into HTML/PDF/PNG/JPEG
- embed images easily

To enhance my experience I was thinking to embed those badges myself but it should be extensible and easy to use in markdown itself. The question just was how?
Luckily the extension provides a [extended parser](https://shd101wyy.github.io/markdown-preview-enhanced/#/extend-parser) which includes
handlers to decide what should happen before the file gets converted into markdown and what should happen with the data after it got converted into HTML.

You can adjust them in: `~/.mume/parser.js` or simply press `CTRL+SHIFT+P` and type "extended parser".

Be aware that this was used primarily to parse as HTML, basically the preview window. It may cause issues in PDF.

# Images

Markdown Version compiled as citations:

![Code_2DQrGqt7oX](https://dev-to-uploads.s3.amazonaws.com/i/613mcns64y0a9m1zw5kn.png)
 
HTML Version

![Code_LHxLxhhOkO](https://dev-to-uploads.s3.amazonaws.com/i/kw5xes2mepvkl9nczmfm.png) 

# Usage and adjustable variables

Before you copy the code over, I want to let you know how to configure everything.

I implemented two options: either use plain markdown and display a kind of badges or use the advanced styled HTML version.
Just toggle this one:
```js
const useHTMLBadges = true;
```

Here comes the fun part, in this object you can specify some colors and emojis to use for your badges or even include new ones or alter existing ones.

To find emojis:
- for markdown you can use this [github gist](https://gist.github.com/rxaviers/7360908)
- in windows you can press `WIN + .` to open the emoji keyboard

```js
const notificationTypes = {
  NOTE: {
    header: 'Note',
    md_icon: ':hand:',
    html_icon: '✋',
    background: '#e2daf1',
    color: '#38225d',
  },
  TIP: {
    header: 'Tip',
    md_icon: ':bulb:',
    html_icon: '💡',
    background: '#d2f9d2',
    color: '#094409',
  },
  INFO: {
    header: 'Information',
    md_icon: ':heavy_exclamation_mark:',
    html_icon: '❗',
    background: '#e0f2ff',
    color: '#002b4d',
  },
  WARNING: {
    header: 'Warning',
    md_icon: ':warning:',
    html_icon: '⚠',
    background: '#fff1cc',
    color: '#664b00',
  },
  CAUTION: {
    header: 'Be careful!',
    md_icon: ':no_entry_sign:',
    html_icon: '🚫',
    background: '#ffdacc',
    color: '#651b01',
  },
};
```

## Usage

In markdown you use your specified things in the object above.
Be sure to make at least 1 empty line between two badges.

```md
[!NOTE] This is a note!

[!WARNING] This is a warning!
```

# Code

Open the file: `~/.mume/parser.js` and copy the code below in there or even adjust it as you like.

```js
// ? https://shd101wyy.github.io/markdown-preview-enhanced/#/extend-parser

const useHTMLBadges = true;
const notificationTypes = {
  NOTE: {
    header: 'Note',
    md_icon: ':hand:',
    html_icon: '✋',
    background: '#e2daf1',
    color: '#38225d',
  },
  TIP: {
    header: 'Tip',
    md_icon: ':bulb:',
    html_icon: '💡',
    background: '#d2f9d2',
    color: '#094409',
  },
  INFO: {
    header: 'Information',
    md_icon: ':heavy_exclamation_mark:',
    html_icon: '❗',
    background: '#e0f2ff',
    color: '#002b4d',
  },
  WARNING: {
    header: 'Warning',
    md_icon: ':warning:',
    html_icon: '⚠',
    background: '#fff1cc',
    color: '#664b00',
  },
  CAUTION: {
    header: 'Caution',
    md_icon: ':no_entry_sign:',
    html_icon: '🚫',
    background: '#ffdacc',
    color: '#651b01',
  },
};

// HELPERS
const errorParser = (err) => `<pre>${err.stack}</pre>`;

const markdownParse = (markdown) => {
  // [!NOTE] Example Text
  let notificationRegex = null;

  for ([notificationType, notificationInfo] of Object.entries(
    notificationTypes,
  )) {
    // multi line comments
    notificationRegex = new RegExp(
      String.raw`\[\!${notificationType}\](.+\r?\n?)+(?=(\r?\n)?)`,
      'gm',
    );

    markdown = markdown.replace(notificationRegex, (message) => {
      return `> ${notificationInfo.md_icon} **${notificationInfo.header}**
                ${message.substr(message.indexOf(' ') + 1)}`;
    });
  }

  return markdown;
};

const htmlParse = (html) => {
  const findCitations = (html) =>
    html.match(/<blockquote>[\S\s]*?<\/blockquote>/gi);

  const findBadges = (citationArray) => {
    let realBadges = [];

    for (index in citationArray) {
      for ([_, info] of Object.entries(notificationTypes)) {
        // minified version spits out <br> instead of <br />
        if (
          citationArray[index].match(
            `<strong>${info.header}<\/strong><br>`,
            'gm',
          )
        ) {
          realBadges[index] = citationArray[index];
        }
      }
    }

    return realBadges;
  };

  let badges = findCitations(html);
  badges = findBadges(badges);

  const getBadgeInfos = (badgeElement) => {
    let findTitle = '';

    for ([_, info] of Object.entries(notificationTypes)) {
      // returns a array of matches
      // minified version spits out <br> instead of <br />
      findTitle = badgeElement.match(
        `.*<strong>${info.header}<\/strong><br>`,
        'gm',
      );
      if (findTitle != null) {
        if (findTitle.length != 0) return info;
      }
    }

    return {};
  };

  const getBody = (badgeElement) => {
    // minified version spits out <br> instead of <br />
    const findBody = badgeElement.match(`(?<=<br>)(.|\r?\n)*?(?=<\/p>)`);

    if (findBody != null) {
      if (findBody.length != 0) return findBody[0].trim();
    }

    return '';
  };

  let infos = {};
  let body = '';

  for (index in badges) {
    html = html.replace(badges[index], (foundElement) => {
      infos = getBadgeInfos(foundElement);
      body = getBody(foundElement);

      if (infos == null)
        return `<div style="color:red">parsing error, see: <pre>~/.mume/parser.js</pre></div>`;

      const styling = `style="padding: 0.8rem 1rem; border-radius: 6px; margin: 1rem 0; background-color:${info.background}"`;

      return `<div ${styling}><p>${info.html_icon} <strong style="color: ${info.color}">${info.header}</strong></p><p style="margin: 0; text-align: left; line-height:1.3;">${body}</p></div>`;
    });
  }

  return html;
};

module.exports = {
  // do something with the markdown before it gets parsed to HTML
  onWillParseMarkdown: function (markdown) {
    return new Promise((resolve, reject) => {
      try {
        markdown = markdownParse(markdown);
      } catch (error) {
        markdown = errorParser(error);
      }

      return resolve(markdown);
    });
  },
  // do something with the parsed HTML string
  onDidParseMarkdown: function (html) {
    return new Promise((resolve, reject) => {
      try {
        html = useHTMLBadges ? htmlParse(html) : html;
      } catch (error) {
        html = errorParser(error);
      }

      return resolve(html);
    });
  },
};
```

# Last words

If you want to revert the changes or something broke, just remove everything except the handlers:

```js
module.exports = {
  // do something with the markdown before it gets parsed to HTML
  onWillParseMarkdown: function (markdown) {
    return new Promise((resolve, reject) => {
      try {
        markdown = markdownParse(markdown);
      } catch (error) {
        markdown = errorParser(error);
      }

      return resolve(markdown);
    });
  },
  // do something with the parsed HTML string
  onDidParseMarkdown: function (html) {
    return new Promise((resolve, reject) => {
      try {
        html = useHTMLBadges ? htmlParse(html) : html;
      } catch (error) {
        html = errorParser(error);
      }

      return resolve(html);
    });
  },

  onWillTransformMarkdown: function (markdown) {
    return new Promise((resolve, reject) => {
      return resolve(markdown);
    });
  },
  onDidTransformMarkdown: function (markdown) {
    return new Promise((resolve, reject) => {
      return resolve(markdown);
    });
  },
};
```

My code won't be perfect but I am happy with my result.
Any improvements are welcome! Happy coding. :)