diff --git a/lib/lexer_block.js b/lib/lexer_block.js index 6b552fa..f2d0d6c 100644 --- a/lib/lexer_block.js +++ b/lib/lexer_block.js @@ -6,11 +6,13 @@ var rules = []; +// `list` should be after `hr`, but before `heading` rules.push(require('./lexer_block/code')); rules.push(require('./lexer_block/fences')); +rules.push(require('./lexer_block/hr')); +rules.push(require('./lexer_block/list')); rules.push(require('./lexer_block/heading')); rules.push(require('./lexer_block/lheading')); -rules.push(require('./lexer_block/hr')); rules.push(require('./lexer_block/blockquote')); rules.push(require('./lexer_block/paragraph')); diff --git a/lib/lexer_block/blockquote.js b/lib/lexer_block/blockquote.js index 9f98f64..0eb9e91 100644 --- a/lib/lexer_block/blockquote.js +++ b/lib/lexer_block/blockquote.js @@ -69,6 +69,7 @@ module.exports = function blockquote(state, startLine, endLine, silent) { // Case 3: another tag found. if (rules_named.fences(state, nextLine, endLine, true)) { break; } if (rules_named.hr(state, nextLine, endLine, true)) { break; } + if (rules_named.list(state, nextLine, endLine, true)) { break; } if (rules_named.heading(state, nextLine, endLine, true)) { break; } if (rules_named.lheading(state, nextLine, endLine, true)) { break; } } diff --git a/lib/lexer_block/list.js b/lib/lexer_block/list.js new file mode 100644 index 0000000..609c04f --- /dev/null +++ b/lib/lexer_block/list.js @@ -0,0 +1,82 @@ +// Lists + +'use strict'; + + +var isEmpty = require('../helpers').isEmpty; +var skipEmptyLines = require('../helpers').skipEmptyLines; + + +function bullet_item(state, startLine, endLine, silent) { + var marker, nextLine, start, + rules_named = state.lexerBlock.rules_named, + pos = state.bMarks[startLine] + state.tShift[startLine], + max = state.eMarks[startLine]; + + // TODO: supporting list with only one paragraph for now + + start = pos; + if (pos > max) { return false; } + + marker = state.src.charCodeAt(pos++); + + // Check bullet + if (marker !== 0x2A/* * */ && + marker !== 0x2D/* - */ && + marker !== 0x2B/* + */) { + return false; + } + + // Empty list item + if (pos > max) { + state.tokens.push({ type: 'list_item_open' }); + state.tokens.push({ type: 'list_item_close' }); + return true; + } + + if (state.src.charCodeAt(pos++) !== 0x20) { return false; } + + // If we reached this, it's surely a list item + if (silent) { return true; } + + nextLine = startLine + 1; + + // jump line-by-line until empty one or EOF + while (nextLine < endLine && !isEmpty(state, nextLine)) { + // Some tags can terminate paragraph without empty line. + // Try those tags in validation more (without tokens generation) + if (rules_named.fences(state, nextLine, endLine, true)) { break; } + if (rules_named.hr(state, nextLine, endLine, true)) { break; } + if (rules_named.heading(state, nextLine, endLine, true)) { break; } + if (rules_named.lheading(state, nextLine, endLine, true)) { break; } + if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } + if (bullet_item(state, nextLine, endLine, true)) { break; } + //if (rules_named.tag(state, nextLine, endLine, true)) { break; } + //if (rules_named.def(state, nextLine, endLine, true)) { break; } + nextLine++; + } + + state.tokens.push({ type: 'list_item_open' }); + state.lexerInline.tokenize( + state, + state.bMarks[startLine], + state.eMarks[nextLine - 1] + ); + state.tokens.push({ type: 'list_item_close' }); + + state.line = skipEmptyLines(state, nextLine); + return true; +} + +module.exports = function list(state, startLine, endLine, silent) { + // TODO: supporting list with only one element for now + if (bullet_item(state, startLine, endLine, true)) { + if (silent) { return true; } + state.tokens.push({ type: 'bullet_list_open' }); + bullet_item(state, startLine, endLine); + state.tokens.push({ type: 'bullet_list_close' }); + return true; + } + return false; +}; + diff --git a/lib/lexer_block/paragraph.js b/lib/lexer_block/paragraph.js index e7050c6..900421a 100644 --- a/lib/lexer_block/paragraph.js +++ b/lib/lexer_block/paragraph.js @@ -17,6 +17,7 @@ module.exports = function paragraph(state, startLine, endLine) { // Try those tags in validation more (without tokens generation) if (rules_named.fences(state, nextLine, endLine, true)) { break; } if (rules_named.hr(state, nextLine, endLine, true)) { break; } + if (rules_named.list(state, nextLine, endLine, true)) { break; } if (rules_named.heading(state, nextLine, endLine, true)) { break; } if (rules_named.lheading(state, nextLine, endLine, true)) { break; } if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } diff --git a/lib/renderer.js b/lib/renderer.js index d2754ca..4cf477f 100644 --- a/lib/renderer.js +++ b/lib/renderer.js @@ -29,6 +29,14 @@ rules.blockquote_close = function (state, token) { }; +rules.bullet_list_open = function (state, token) { + state.result += '\n'; +}; + + rules.code = function (state, token) { var content = joinLines(state, token.startLine, token.endLine).replace(/^ {4}/gm, ''); @@ -61,6 +69,14 @@ rules.hr = function (state, token) { }; +rules.list_item_open = function (state, token) { + state.result += '
  • '; +}; +rules.list_item_close = function (state, token) { + state.result += '
  • \n'; +}; + + rules.paragraph_open = function (state, token) { state.result += '

    '; };