Skip to content

Instantly share code, notes, and snippets.

@renehamburger
Last active September 4, 2023 07:55
Show Gist options
  • Save renehamburger/12f14a9bd9297394e5bd to your computer and use it in GitHub Desktop.
Save renehamburger/12f14a9bd9297394e5bd to your computer and use it in GitHub Desktop.
slimdown.js
'use strict';
/**
* Javascript version of https://gist.github.com/jbroadway/2836900
*
* Slimdown - A very basic regex-based Markdown parser. Supports the
* following elements (and can be extended via Slimdown::add_rule()):
*
* - Headers
* - Links
* - Bold
* - Emphasis
* - Deletions
* - Quotes
* - Inline code
* - Blockquotes
* - Ordered/unordered lists
* - Horizontal rules
*
* Author: Johnny Broadway <johnny@johnnybroadway.com>
* Website: https://gist.github.com/jbroadway/2836900
* License: MIT
*/
function Slimdown() {
// Rules
this.rules = [
{regex: /(#+)(.*)/g, replacement: header}, // headers
{regex: /!\[([^\[]+)\]\(([^\)]+)\)/g, replacement: '<img src=\'$2\' alt=\'$1\'>'}, // image
{regex: /\[([^\[]+)\]\(([^\)]+)\)/g, replacement: '<a href=\'$2\'>$1</a>'}, // hyperlink
{regex: /(\*\*|__)(.*?)\1/g, replacement: '<strong>$2</strong>'}, // bold
{regex: /(\*|_)(.*?)\1/g, replacement: '<em>$2</em>'}, // emphasis
{regex: /\~\~(.*?)\~\~/g, replacement: '<del>$1</del>'}, // del
{regex: /\:\"(.*?)\"\:/g, replacement: '<q>$1</q>'}, // quote
{regex: /`(.*?)`/g, replacement: '<code>$1</code>'}, // inline code
{regex: /\n\*(.*)/g, replacement: ulList}, // ul lists
{regex: /\n[0-9]+\.(.*)/g, replacement: olList}, // ol lists
{regex: /\n(&gt;|\>)(.*)/g, replacement: blockquote}, // blockquotes
{regex: /\n-{5,}/g, replacement: '\n<hr />'}, // horizontal rule
{regex: /\n([^\n]+)\n/g, replacement: para}, // add paragraphs
{regex: /<\/ul>\s?<ul>/g, replacement: ''}, // fix extra ul
{regex: /<\/ol>\s?<ol>/g, replacement: ''}, // fix extra ol
{regex: /<\/blockquote><blockquote>/g, replacement: '\n'} // fix extra blockquote
];
// Add a rule.
this.addRule = function (regex, replacement) {
regex.global = true;
regex.multiline = false;
this.rules.push({regex: regex, replacement: replacement});
};
// Render some Markdown into HTML.
this.render = function (text) {
text = '\n' + text + '\n';
this.rules.forEach(function (rule) {
text = text.replace(rule.regex, rule.replacement);
});
return text.trim();
};
function para (text, line) {
debugger;
var trimmed = line.trim();
if (/^<\/?(ul|ol|li|h|p|bl)/i.test(trimmed)) {
return '\n' + line + '\n';
}
return '\n<p>' + trimmed + '</p>\n';
}
function ulList (text, item) {
return '\n<ul>\n\t<li>' + item.trim() + '</li>\n</ul>';
}
function olList (text, item) {
return '\n<ol>\n\t<li>' + item.trim() + '</li>\n</ol>';
}
function blockquote (text, tmp, item) {
return '\n<blockquote>' + item.trim() + '</blockquote>';
}
function header (text, chars, content) {
var level = chars.length;
return '<h' + level + '>' + content.trim() + '</h' + level + '>';
}
}
var sd = new Slimdown();
$('body').append(sd.render('# Title\n\nAnd *now* [a link](http://www.google.com) to **follow** and [another](http://yahoo.com/).\n\n* One\n* Two\n* Three\n\n## Subhead\n\nOne **two** three **four** five.\n\nOne __two__ three _four_ five __six__ seven _eight_.\n\n1. One\n2. Two\n3. Three\n\nMore text with `inline($code)` sample.\n\n> A block quote\n> across two lines.\nMore text...'));
@mmuman
Copy link

mmuman commented Sep 3, 2023

I tried using it in a little web app to import markdown from Nextcloud and Etherpad, however I found some issues:

  • it finds headers when there are none, including inside URL fragments,
  • underscores inside URLs are turned into EM or STRONG,
  • Etherpad seems to start UL with " *" so they aren't detected,
  • dashes are not handled as UL,
  • all newlines are converted into paragraphs, when actually only two in a row should mark end of paragraph, single newlines should covnert to <br/>,
  • Escaped chars with \ are not de-escaped.

Let's see if I can fix that… Yeah, this seems to work for me (I didn't touch OL as I don't use them):

// from https://gist.github.com/renehamburger/12f14a9bd9297394e5bd

'use strict';

/**
 * Javascript version of https://gist.github.com/jbroadway/2836900
 *
 * Slimdown - A very basic regex-based Markdown parser. Supports the
 * following elements (and can be extended via Slimdown::add_rule()):
 *
 * - Headers
 * - Links
 * - Bold
 * - Emphasis
 * - Deletions
 * - Quotes
 * - Inline code
 * - Blockquotes
 * - Ordered/unordered lists
 * - Horizontal rules
 *
 * Author: Johnny Broadway <johnny@johnnybroadway.com>
 * Website: https://gist.github.com/jbroadway/2836900
 * License: MIT
 */
function Slimdown() {

  // Rules
  this.rules =  [
    {regex: /\n(#+)(.*)/g, replacement: header},                                         // headers
    {regex: /!\[([^\[]+)\]\(([^\)]+)\)/g, replacement: '<img src=\'$2\' alt=\'$1\'>'}, // image
    {regex: /\[([^\[]+)\]\(([^\)]+)\)/g, replacement: '<a href=\'$2\'>$1</a>'},        // hyperlink
// _ can be used in URLs and we do not want to mess them up.
//    {regex: /(\*\*|__)(.*?)\1/g, replacement: '<strong>$2</strong>'},                // bold
//    {regex: /(\*\*)(.*?)\1/g, replacement: '<strong>$2</strong>'},                   // bold
    {regex: /([^\\])(\*\*|__)(.*?)\2/g, replacement: '$1<strong>$3</strong>'},         // bold
//    {regex: /(\*|_)(.*?)\1/g, replacement: '<em>$2</em>'},                           // emphasis
//    {regex: /(\*)(.*?)\1/g, replacement: '<em>$2</em>'},                             // emphasis
    {regex: /([^\\])(\*|_)(.*?)\2/g, replacement: '$1<em>$3</em>'},                    // emphasis
    {regex: /\~\~(.*?)\~\~/g, replacement: '<del>$1</del>'},                           // del
    {regex: /\:\"(.*?)\"\:/g, replacement: '<q>$1</q>'},                               // quote
    {regex: /`(.*?)`/g, replacement: '<code>$1</code>'},                               // inline code
    {regex: /\n( *)(\*|-)(.*)/g, replacement: ulList},                                         // ul lists
    {regex: /\n[0-9]+\.(.*)/g, replacement: olList},                                   // ol lists
    {regex: /\n(&gt;|\>)(.*)/g, replacement: blockquote},                              // blockquotes
    {regex: /\n-{5,}/g, replacement: '\n<hr />'},                                      // horizontal rule
    {regex: /\n\n([^\n]+)\n\n/g, replacement: para},                                       // add paragraphs
    {regex: /([^>])\\?\n([^<])/g, replacement: '$1<br/>$2'},                           // newlines
    {regex: /\\(.)/g, replacement: '$1'},                                              // unescape
    {regex: /<\/ul>\s?<ul>/g, replacement: ''},                                        // fix extra ul
    {regex: /<\/ul>\s?<ul>/g, replacement: ''},                                        // fix extra ul
    {regex: /<\/ul>\s?<ul>/g, replacement: ''},                                        // fix extra ul
    {regex: /<\/ul>\s?<ul>/g, replacement: ''},                                        // fix extra ul
    {regex: /<\/ul>\s?<ul>/g, replacement: ''},                                        // fix extra ul
    {regex: /<\/ul>\s?<ul>/g, replacement: ''},                                        // fix extra ul
    {regex: /<\/ol>\s?<ol>/g, replacement: ''},                                        // fix extra ol
    {regex: /<\/blockquote><blockquote>/g, replacement: '\n'}                          // fix extra blockquote
  ];

  // Add a rule.
  this.addRule = function (regex, replacement) {
    regex.global = true;
    regex.multiline = false;
    this.rules.push({regex: regex, replacement: replacement});
  };

  // Render some Markdown into HTML.
  this.render = function (text) {
    text = '\n' + text + '\n';
    this.rules.forEach(function (rule) {
      text = text.replace(rule.regex, rule.replacement);
    });
    return text.trim();
  };

  function para (text, line) {
    //debugger;
    var trimmed = line.trim();
    if (/^<\/?(ul|ol|li|h|p|bl)/i.test(trimmed)) {
      return '\n' + line + '\n';
    }
    return '\n<p>' + trimmed + '</p>\n';
  }

  function ulList (text, level, bullet, item) {
    level = 1+Math.floor(level.length/2);
    // Not really correct but seems to work in Firefox and Chromium at least
    return '\n<ul>'.repeat(level) + '\n\t<li>' + item.trim() + '</li>' + '\n</ul>'.repeat(level);
  }

  function olList (text, item) {
    return '\n<ol>\n\t<li>' + item.trim() + '</li>\n</ol>';
  }

  function blockquote (text, tmp, item) {
    return '\n<blockquote>' + item.trim() + '</blockquote>';
  }

  function header (text, chars, content) {
    var level = chars.length;
    return '\n<h' + level + '>' + content.trim() + '</h' + level + '>';
  }
}

@erikvullings
Copy link

@mmuman Pretty cool - thanks for sharing your solution!
There is also a npm package with the same name, https://github.com/erikvullings/slimdown-js, so if you would like to submit a PR, I will gladly include it. If this is too much work, let me know, and I'll do it instead (but I prefer to give you the credits :-))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment