diff --git a/lib/parser_inline.js b/lib/parser_inline.js index a3408ba..ea505fa 100644 --- a/lib/parser_inline.js +++ b/lib/parser_inline.js @@ -19,7 +19,8 @@ var _rules = [ [ 'backticks', require('./rules_inline/backticks') ], [ 'del', require('./rules_inline/del') ], [ 'emphasis', require('./rules_inline/emphasis') ], - [ 'links', require('./rules_inline/links') ], + [ 'link', require('./rules_inline/link') ], + [ 'image', require('./rules_inline/image') ], [ 'footnote_inline', require('./rules_inline/footnote_inline') ], [ 'footnote_ref', require('./rules_inline/footnote_ref') ], [ 'autolink', require('./rules_inline/autolink') ], diff --git a/lib/presets/commonmark.js b/lib/presets/commonmark.js index 2c4b1e3..ee98206 100644 --- a/lib/presets/commonmark.js +++ b/lib/presets/commonmark.js @@ -61,7 +61,8 @@ module.exports = { 'entity', 'escape', 'htmltag', - 'links', + 'image', + 'link', 'newline', 'text' ] diff --git a/lib/presets/default.js b/lib/presets/default.js index bb979b8..c47e6b4 100644 --- a/lib/presets/default.js +++ b/lib/presets/default.js @@ -69,7 +69,8 @@ module.exports = { 'escape', 'footnote_ref', 'htmltag', - 'links', + 'image', + 'link', 'newline', 'text' ] diff --git a/lib/rules_inline/image.js b/lib/rules_inline/image.js new file mode 100644 index 0000000..45ae9f7 --- /dev/null +++ b/lib/rules_inline/image.js @@ -0,0 +1,154 @@ +// Process ![image]( "title") + +'use strict'; + +var parseLinkLabel = require('../helpers/parse_link_label'); +var parseLinkDestination = require('../helpers/parse_link_destination'); +var parseLinkTitle = require('../helpers/parse_link_title'); +var normalizeReference = require('../helpers/normalize_reference'); + + +module.exports = function image(state, silent) { + var code, + href, + label, + labelEnd, + labelStart, + pos, + ref, + title, + tokens, + start, + oldPos = state.pos, + max = state.posMax; + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + + labelStart = state.pos + 2; + labelEnd = parseLinkLabel(state, state.pos + 1, false); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + if (parseLinkDestination(state, pos)) { + href = state.linkContent; + pos = state.pos; + } else { + href = ''; + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + if (pos < max && start !== pos && parseLinkTitle(state, pos)) { + title = state.linkContent; + pos = state.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + // [foo] [bar] + // ^^ optional whitespace (can include newlines) + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + if (!label) { label = state.src.slice(labelStart, labelEnd); } + + ref = state.env.references[normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + var newState = new state.md.inline.State( + state.src.slice(labelStart, labelEnd), + state.md, + state.env, + tokens = [] + ); + newState.md.inline.tokenize(newState); + + state.push({ + type: 'image', + src: href, + title: title, + tokens: tokens, + level: state.level + }); + } + + state.pos = pos; + state.posMax = max; + return true; +}; diff --git a/lib/rules_inline/links.js b/lib/rules_inline/link.js similarity index 75% rename from lib/rules_inline/links.js rename to lib/rules_inline/link.js index ae3a793..2b9a6eb 100644 --- a/lib/rules_inline/links.js +++ b/lib/rules_inline/link.js @@ -1,4 +1,4 @@ -// Process [links]( "stuff") +// Process [link]( "stuff") 'use strict'; @@ -8,7 +8,7 @@ var parseLinkTitle = require('../helpers/parse_link_title'); var normalizeReference = require('../helpers/normalize_reference'); -module.exports = function links(state, silent) { +module.exports = function link(state, silent) { var code, href, label, @@ -17,22 +17,14 @@ module.exports = function links(state, silent) { pos, ref, title, - tokens, - isImage = false, oldPos = state.pos, max = state.posMax, - start = state.pos, - marker = state.src.charCodeAt(start); + start = state.pos; - if (marker === 0x21/* ! */) { - isImage = true; - marker = state.src.charCodeAt(++start); - } - - if (marker !== 0x5B/* [ */) { return false; } + if (state.src.charCodeAt(state.pos) !== 0x5B/* [ */) { return false; } - labelStart = start + 1; - labelEnd = parseLinkLabel(state, start, !isImage); + labelStart = state.pos + 1; + labelEnd = parseLinkLabel(state, state.pos, true); // parser failed to find ']', so it's not a valid link if (labelEnd < 0) { return false; } @@ -137,33 +129,15 @@ module.exports = function links(state, silent) { state.pos = labelStart; state.posMax = labelEnd; - if (isImage) { - var newState = new state.md.inline.State( - state.src.slice(labelStart, labelEnd), - state.md, - state.env, - tokens = [] - ); - newState.md.inline.tokenize(newState); - - state.push({ - type: 'image', - src: href, - title: title, - tokens: tokens, - level: state.level - }); - } else { - state.push({ - type: 'link_open', - href: href, - target: '', - title: title, - level: state.level++ - }); - state.md.inline.tokenize(state); - state.push({ type: 'link_close', level: --state.level }); - } + state.push({ + type: 'link_open', + href: href, + target: '', + title: title, + level: state.level++ + }); + state.md.inline.tokenize(state); + state.push({ type: 'link_close', level: --state.level }); } state.pos = pos;