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 = 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 += ''+t+'>';
}
// 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('- Ordered
- Lists
- 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');
});
});