diff --git a/src/index.js b/src/index.js index 3938c5c..7d22922 100644 --- a/src/index.js +++ b/src/index.js @@ -8,11 +8,12 @@ const TAGS = { '-': ['
'] }; -/** Outdent a string based on the first indented line's leading whitespace +/** Outdent a string based on a selected replacement, defaulting to the first indented line's leading whitespace * @private */ -function outdent(str) { - return str.replace(RegExp('^'+(str.match(/^(\t| )+/) || '')[0], 'gm'), ''); +function outdent(str, replacement) { + replacement = replacement || str.match(/^(\t| )*/)[0] || ''; + return str.replace(RegExp('^'+(replacement), 'gm'), ''); } /** Encode special attribute characters to HTML entities in a String. @@ -24,7 +25,7 @@ function encodeAttr(str) { /** Parse Markdown into an HTML String. */ export default function parse(md, prevLinks) { - let tokenizer = /((?:^|\n+)(?:\n---+|\* \*(?: \*)+)\n)|(?:^``` *(\w*)\n([\s\S]*?)\n```$)|((?:(?:^|\n+)(?:\t| {2,}).+)+\n*)|((?:(?:^|\n)([>*+-]|\d+\.)\s+.*)+)|(?:!\[([^\]]*?)\]\(([^)]+?)\))|(\[)|(\](?:\(([^)]+?)\))?)|(?:(?:^|\n+)([^\s].*)\n(-{3,}|={3,})(?:\n+|$))|(?:(?:^|\n+)(#{1,6})\s*(.+)(?:\n+|$))|(?:`([^`].*?)`)|( \n\n*|\n{2,}|__|\*\*|[_*]|~~)/gm, + let tokenizer = /((?:^|\n+)(?:\n---+|\* \*(?: \*)+)\n)|(?:^``` *(\w*)\n([\s\S]*?)\n```$)|((?:(?:^|\n+)(?:\t| {2,}).+)+\n*)|((?:(?:^|\n\s*)([*+-]|\d+\.)\s+.*)+)|(?:!\[([^\]]*?)\]\(([^)]+?)\))|(\[)|(\](?:\(([^)]+?)\))?)|(?:(?:^|\n+)([^\s].*)\n(-{3,}|={3,})(?:\n+|$))|(?:(?:^|\n+)(#{1,6})\s*(.+)(?:\n+|$))|(?:`([^`].*?)`)|( \n\n*|\n{2,}|__|\*\*|[_*]|~~)|((?:(?:^|\n)(?:>\s+[^\n]*\n?)+))/gm, context = [], out = '', links = prevLinks || {}, @@ -63,18 +64,39 @@ export default function parse(md, prevLinks) { else if (t = (token[3] || token[4])) { chunk = '
'+outdent(encodeAttr(t).replace(/^\n+|\n+$/g, ''))+'
'; } - // > Quotes, -* lists: + // -* lists: else if (t = token[6]) { if (t.match(/\./)) { token[5] = token[5].replace(/^\d+/gm, ''); } - inner = parse(outdent(token[5].replace(/^\s*[>*+.-]/gm, ''))); - if (t=='>') t = 'blockquote'; - else { - t = t.match(/\./) ? 'ol' : 'ul'; - inner = inner.replace(/^(.*)(\n|$)/gm, '
  • $1
  • '); - } - chunk = '<'+t+'>' + inner + ''; + t = t.match(/\./) ? 'ol' : 'ul'; + chunk = '<'+t+'>
  • '; + let firstIndent = ''; + let currentSublist = ''; + let firstItemCreated = false; + + token[5].replace(/^(\s*)(?:[*+.-]|\d+\.)\s+(.*)\n?/gm, (match, indent, content) => { + if (indent) { + if (!firstIndent) { + firstIndent = indent; + } + currentSublist += match; + } + else { + let parsedSublist = ''; + if (currentSublist) { + parsedSublist = parse(outdent(currentSublist, firstIndent)); + } + if (firstItemCreated) { + chunk += parsedSublist + '
  • '; + } + chunk += parse(content); + firstItemCreated = true; + + firstIndent = currentSublist = ''; + } + }); + chunk += '
  • '; } // Images: else if (token[8]) { @@ -101,6 +123,11 @@ export default function parse(md, prevLinks) { else if (token[17] || token[1]) { chunk = tag(token[17] || '--'); } + // > Quotes + else if (token[18]) { + inner = parse(outdent(token[18].replace(/^\s*>/gm, ''))); + chunk = '
    ' + inner + '
    '; + } out += prev; out += chunk; } diff --git a/test/index.js b/test/index.js index 5262b16..b40a382 100644 --- a/test/index.js +++ b/test/index.js @@ -92,6 +92,14 @@ describe('snarkdown()', () => { it('parses an ordered list', () => { expect(snarkdown('1. Ordered\n2. Lists\n4. Numbers are ignored')).to.equal('
    1. Ordered
    2. Lists
    3. Numbers are ignored
    '); }); + + it('parses nested lists', () => { + expect(snarkdown('* One\n\t* Two\n* One again')).to.equal(''); + }); + + it('parses nested lists of different types', () => { + expect(snarkdown('* One\n\t1. Two\n* One again')).to.equal(''); + }); }); describe('line breaks', () => { @@ -133,7 +141,7 @@ describe('snarkdown()', () => { }); it('parses lists within block quotes', () => { - expect(snarkdown('> - one\n> - two\n> - **three**\nhello')).to.equal('
    \nhello'); + expect(snarkdown('> - one\n> - two\n> - **three**\nhello')).to.equal('
    hello'); }); });