DEV Community

Cover image for Markdown Badges for VSCode markdown-preview-enhanced users
Supportic
Supportic

Posted on • Updated on

Markdown Badges for VSCode markdown-preview-enhanced users

requirements:

  • VSCode
  • markdown-preview-enhanced extension

Intro

I recently stumbled across this site 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.
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 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

HTML Version

Code_LHxLxhhOkO

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:

const useHTMLBadges = true;
Enter fullscreen mode Exit fullscreen mode

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
  • in windows you can press WIN + . to open the emoji keyboard
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',
  },
};
Enter fullscreen mode Exit fullscreen mode

Usage

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

[!NOTE] This is a note!

[!WARNING] This is a warning!
Enter fullscreen mode Exit fullscreen mode

Code

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

// ? 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);
    });
  },
};
Enter fullscreen mode Exit fullscreen mode

Last words

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

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);
    });
  },
};
Enter fullscreen mode Exit fullscreen mode

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

Discussion (3)

Collapse
ludoloops profile image
Ludo Loops • Edited on

Thanks, for the tips!
To sync my note to the cloud and my phone, I have also included joplin-vscode extension
rxliuli.com/joplin-vscode-plugin/#/

Then to make it nicer with my dark theme, I swapped the background with the text color.

dark theme

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

Enter fullscreen mode Exit fullscreen mode
Collapse
patarapolw profile image
Pacharapol Withayasakpunt

Great thing about MPE is that you can enhance it.

I have also tried added music sheet parser, and some custom markdown syntaxes.

Collapse
jeden profile image
Eden Jose

I was searching for articles on how to improve an aesthetic problem that I was having in VScode when I came across this post and this thread I'm using markdown-preview-enhanced and I find your post to be extremely helpful! - searching for something lead me to another! :D