Skip to content

Commit

Permalink
add support for nested lists
Browse files Browse the repository at this point in the history
  • Loading branch information
stutrek committed Nov 18, 2022
1 parent 7619436 commit 18b5f46
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 13 deletions.
51 changes: 39 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ const TAGS = {
'-': ['<hr />']
};

/** 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.
Expand All @@ -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 || {},
Expand Down Expand Up @@ -63,18 +64,39 @@ export default function parse(md, prevLinks) {
else if (t = (token[3] || token[4])) {
chunk = '<pre class="code '+(token[4]?'poetry':token[2].toLowerCase())+'"><code'+(token[2] ? ` class="language-${token[2].toLowerCase()}"` : '')+'>'+outdent(encodeAttr(t).replace(/^\n+|\n+$/g, ''))+'</code></pre>';
}
// > 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, '<li>$1</li>');
}
chunk = '<'+t+'>' + inner + '</'+t+'>';
t = t.match(/\./) ? 'ol' : 'ul';
chunk = '<'+t+'><li>';
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 + '</li><li>';
}
chunk += parse(content);
firstItemCreated = true;

firstIndent = currentSublist = '';
}
});
chunk += '</li></'+t+'>';
}
// Images:
else if (token[8]) {
Expand All @@ -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 = '<blockquote>' + inner + '</blockquote>';
}
out += prev;
out += chunk;
}
Expand Down
10 changes: 9 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ describe('snarkdown()', () => {
it('parses an ordered list', () => {
expect(snarkdown('1. Ordered\n2. Lists\n4. Numbers are ignored')).to.equal('<ol><li>Ordered</li><li>Lists</li><li>Numbers are ignored</li></ol>');
});

it('parses nested lists', () => {
expect(snarkdown('* One\n\t* Two\n* One again')).to.equal('<ul><li>One<ul><li>Two</li></ul></li><li>One again</li></ul>');
});

it('parses nested lists of different types', () => {
expect(snarkdown('* One\n\t1. Two\n* One again')).to.equal('<ul><li>One<ol><li>Two</li></ol></li><li>One again</li></ul>');
});
});

describe('line breaks', () => {
Expand Down Expand Up @@ -133,7 +141,7 @@ describe('snarkdown()', () => {
});

it('parses lists within block quotes', () => {
expect(snarkdown('> - one\n> - two\n> - **three**\nhello')).to.equal('<blockquote><ul><li>one</li><li>two</li><li><strong>three</strong></li></ul></blockquote>\nhello');
expect(snarkdown('> - one\n> - two\n> - **three**\nhello')).to.equal('<blockquote><ul><li>one</li><li>two</li><li><strong>three</strong></li></ul></blockquote>hello');
});
});

Expand Down

0 comments on commit 18b5f46

Please sign in to comment.