markdown-to-jsx
Advanced tools
Comparing version 6.7.4 to 6.8.0
2209
index.js
@@ -12,46 +12,55 @@ /* @jsx h */ | ||
const ATTRIBUTE_TO_JSX_PROP_MAP = { | ||
accesskey: 'accessKey', | ||
allowfullscreen: 'allowFullScreen', | ||
allowtransparency: 'allowTransparency', | ||
autocomplete: 'autoComplete', | ||
autofocus: 'autoFocus', | ||
autoplay: 'autoPlay', | ||
cellpadding: 'cellPadding', | ||
cellspacing: 'cellSpacing', | ||
charset: 'charSet', | ||
class: 'className', | ||
classid: 'classId', | ||
colspan: 'colSpan', | ||
contenteditable: 'contentEditable', | ||
contextmenu: 'contextMenu', | ||
crossorigin: 'crossOrigin', | ||
enctype: 'encType', | ||
for: 'htmlFor', | ||
formaction: 'formAction', | ||
formenctype: 'formEncType', | ||
formmethod: 'formMethod', | ||
formnovalidate: 'formNoValidate', | ||
formtarget: 'formTarget', | ||
frameborder: 'frameBorder', | ||
hreflang: 'hrefLang', | ||
inputmode: 'inputMode', | ||
keyparams: 'keyParams', | ||
keytype: 'keyType', | ||
marginheight: 'marginHeight', | ||
marginwidth: 'marginWidth', | ||
maxlength: 'maxLength', | ||
mediagroup: 'mediaGroup', | ||
minlength: 'minLength', | ||
novalidate: 'noValidate', | ||
radiogroup: 'radioGroup', | ||
readonly: 'readOnly', | ||
rowspan: 'rowSpan', | ||
spellcheck: 'spellCheck', | ||
srcdoc: 'srcDoc', | ||
srclang: 'srcLang', | ||
srcset: 'srcSet', | ||
tabindex: 'tabIndex', | ||
usemap: 'useMap', | ||
accesskey: 'accessKey', | ||
allowfullscreen: 'allowFullScreen', | ||
allowtransparency: 'allowTransparency', | ||
autocomplete: 'autoComplete', | ||
autofocus: 'autoFocus', | ||
autoplay: 'autoPlay', | ||
cellpadding: 'cellPadding', | ||
cellspacing: 'cellSpacing', | ||
charset: 'charSet', | ||
class: 'className', | ||
classid: 'classId', | ||
colspan: 'colSpan', | ||
contenteditable: 'contentEditable', | ||
contextmenu: 'contextMenu', | ||
crossorigin: 'crossOrigin', | ||
enctype: 'encType', | ||
for: 'htmlFor', | ||
formaction: 'formAction', | ||
formenctype: 'formEncType', | ||
formmethod: 'formMethod', | ||
formnovalidate: 'formNoValidate', | ||
formtarget: 'formTarget', | ||
frameborder: 'frameBorder', | ||
hreflang: 'hrefLang', | ||
inputmode: 'inputMode', | ||
keyparams: 'keyParams', | ||
keytype: 'keyType', | ||
marginheight: 'marginHeight', | ||
marginwidth: 'marginWidth', | ||
maxlength: 'maxLength', | ||
mediagroup: 'mediaGroup', | ||
minlength: 'minLength', | ||
novalidate: 'noValidate', | ||
radiogroup: 'radioGroup', | ||
readonly: 'readOnly', | ||
rowspan: 'rowSpan', | ||
spellcheck: 'spellCheck', | ||
srcdoc: 'srcDoc', | ||
srclang: 'srcLang', | ||
srcset: 'srcSet', | ||
tabindex: 'tabIndex', | ||
usemap: 'useMap', | ||
}; | ||
const namedCodesToUnicode = { | ||
amp: '\u0026', | ||
apos: '\u0027', | ||
gt: '\u003e', | ||
lt: '\u003c', | ||
nbsp: '\u00a0', | ||
quot: '\u201c', | ||
}; | ||
const DO_NOT_PROCESS_HTML_ELEMENTS = ['style', 'script']; | ||
@@ -137,2 +146,4 @@ | ||
const HTML_CHAR_CODE_R = /&([a-z]+);/g; | ||
const HTML_COMMENT_R = /^<!--.*?-->/; | ||
@@ -188,11 +199,11 @@ | ||
*/ | ||
const TEXT_BOLD_R = /^([*_])\1((?:[^`~()\[\]<>]*?|(?:.*?([`~]).*?\3.*?)*|(?:.*?\([^)]*?\).*?)*|(?:.*?\[[^\]]*?\].*?)*|(?:.*?<.*?>.*?)*|[^\1]*?)\1?)\1{2}/; | ||
const TEXT_EMPHASIZED_R = /^([*_])((?:[^`~()\[\]<>]*?|(?:.*?([`~]).*?\3.*?)*|(?:.*?\([^)]*?\).*?)*|(?:.*?\[[^\]]*?\].*?)*|(?:.*?<.*?>.*?)*|[^\1]*?))\1/; | ||
const TEXT_BOLD_R = /^([*_])\1((?:[^`~()[\]<>]*?|(?:.*?([`~]).*?\3.*?)*|(?:.*?\([^)]*?\).*?)*|(?:.*?\[[^\]]*?\].*?)*|(?:.*?<.*?>.*?)*|[^\1]*?)\1?)\1{2}/; | ||
const TEXT_EMPHASIZED_R = /^([*_])((?:[^`~()[\]<>]*?|(?:.*?([`~]).*?\3.*?)*|(?:.*?\([^)]*?\).*?)*|(?:.*?\[[^\]]*?\].*?)*|(?:.*?<.*?>.*?)*|[^\1]*?))\1/; | ||
const TEXT_STRIKETHROUGHED_R = /^~~((?:.*?([`~]).*?\2.*?)*|(?:.*?<.*?>.*?)*|.+?)~~/; | ||
const TEXT_ESCAPED_R = /^\\([^0-9A-Za-z\s])/; | ||
const TEXT_PLAIN_R = /^[\s\S]+?(?=[^0-9A-Z\s\u00c0-\uffff]|\d+\.|\n\n| {2,}\n|\w+:\S|$)/i; | ||
const TEXT_PLAIN_R = /^[\s\S]+?(?=[^0-9A-Z\s\u00c0-\uffff&;.]|\d+\.|\n\n| {2,}\n|\w+:\S|$)/i; | ||
const TRIM_NEWLINES_AND_TRAILING_WHITESPACE_R = /(^\n+|(\n|\s)+$)/g; | ||
const HTML_LEFT_TRIM_AMOUNT_R = /^([ \t]*)/ | ||
const HTML_LEFT_TRIM_AMOUNT_R = /^([ \t]*)/; | ||
@@ -217,8 +228,8 @@ const UNESCAPE_URL_R = /\\([^0-9A-Z\s])/gi; | ||
const LIST_ITEM_R = new RegExp( | ||
LIST_ITEM_PREFIX + | ||
'[^\\n]*(?:\\n' + | ||
'(?!\\1' + | ||
LIST_BULLET + | ||
' )[^\\n]*)*(\\n|$)', | ||
'gm' | ||
LIST_ITEM_PREFIX + | ||
'[^\\n]*(?:\\n' + | ||
'(?!\\1' + | ||
LIST_BULLET + | ||
' )[^\\n]*)*(\\n|$)', | ||
'gm' | ||
); | ||
@@ -229,12 +240,14 @@ | ||
const LIST_R = new RegExp( | ||
'^( *)(' + | ||
LIST_BULLET + | ||
') ' + | ||
'[\\s\\S]+?(?:\\n{2,}(?! )' + | ||
'(?!\\1' + | ||
LIST_BULLET + | ||
' )\\n*' + | ||
// the \\s*$ here is so that we can parse the inside of nested | ||
// lists, where our content might end before we receive two `\n`s | ||
'|\\s*\\n*$)' | ||
'^( *)(' + | ||
LIST_BULLET + | ||
') ' + | ||
'[\\s\\S]+?(?:\\n{2,}(?! )' + | ||
'(?!\\1' + | ||
LIST_BULLET + | ||
' (?!' + | ||
LIST_BULLET + | ||
' ))\\n*' + | ||
// the \\s*$ here is so that we can parse the inside of nested | ||
// lists, where our content might end before we receive two `\n`s | ||
'|\\s*\\n*$)' | ||
); | ||
@@ -244,118 +257,122 @@ | ||
const LINK_HREF_AND_TITLE = | ||
'\\s*<?((?:[^\\s\\\\]|\\\\.)*?)>?(?:\\s+[\'"]([\\s\\S]*?)[\'"])?\\s*'; | ||
'\\s*<?((?:[^\\s\\\\]|\\\\.)*?)>?(?:\\s+[\'"]([\\s\\S]*?)[\'"])?\\s*'; | ||
const LINK_R = new RegExp( | ||
'^\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)' | ||
'^\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)' | ||
); | ||
const IMAGE_R = new RegExp( | ||
'^!\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)' | ||
'^!\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)' | ||
); | ||
const BLOCK_SYNTAXES = [ | ||
BLOCKQUOTE_R, | ||
CODE_BLOCK_R, | ||
CODE_BLOCK_FENCED_R, | ||
HEADING_R, | ||
HEADING_SETEXT_R, | ||
HTML_BLOCK_ELEMENT_R, | ||
HTML_COMMENT_R, | ||
HTML_SELF_CLOSING_ELEMENT_R, | ||
LIST_ITEM_R, | ||
LIST_R, | ||
NP_TABLE_R, | ||
PARAGRAPH_R | ||
BLOCKQUOTE_R, | ||
CODE_BLOCK_R, | ||
CODE_BLOCK_FENCED_R, | ||
HEADING_R, | ||
HEADING_SETEXT_R, | ||
HTML_BLOCK_ELEMENT_R, | ||
HTML_COMMENT_R, | ||
HTML_SELF_CLOSING_ELEMENT_R, | ||
LIST_ITEM_R, | ||
LIST_R, | ||
NP_TABLE_R, | ||
PARAGRAPH_R, | ||
]; | ||
function containsBlockSyntax (input) { | ||
return BLOCK_SYNTAXES.some(r => r.test(input)) | ||
function isPlainObject(input) { | ||
return typeof input === 'object' && input.constructor === Object; | ||
} | ||
function containsBlockSyntax(input) { | ||
return BLOCK_SYNTAXES.some(r => r.test(input)); | ||
} | ||
// based on https://stackoverflow.com/a/18123682/1141611 | ||
// not complete, but probably good enough | ||
function slugify(str) { | ||
return str | ||
.replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/g, 'a') | ||
.replace(/[çÇ]/g, 'c') | ||
.replace(/[ðÐ]/g, 'd') | ||
.replace(/[ÈÉÊËéèêë]/g, 'e') | ||
.replace(/[ÏïÎîÍíÌì]/g, 'i') | ||
.replace(/[Ññ]/g, 'n') | ||
.replace(/[øØœŒÕõÔôÓóÒò]/g, 'o') | ||
.replace(/[ÜüÛûÚúÙù]/g, 'u') | ||
.replace(/[ŸÿÝý]/g, 'y') | ||
.replace(/[^a-z0-9- ]/gi, '') | ||
.replace(/ /gi, '-') | ||
.toLowerCase(); | ||
return str | ||
.replace(/[ÀÁÂÃÄÅàáâãäåæÆ]/g, 'a') | ||
.replace(/[çÇ]/g, 'c') | ||
.replace(/[ðÐ]/g, 'd') | ||
.replace(/[ÈÉÊËéèêë]/g, 'e') | ||
.replace(/[ÏïÎîÍíÌì]/g, 'i') | ||
.replace(/[Ññ]/g, 'n') | ||
.replace(/[øØœŒÕõÔôÓóÒò]/g, 'o') | ||
.replace(/[ÜüÛûÚúÙù]/g, 'u') | ||
.replace(/[ŸÿÝý]/g, 'y') | ||
.replace(/[^a-z0-9- ]/gi, '') | ||
.replace(/ /gi, '-') | ||
.toLowerCase(); | ||
} | ||
function parseTableAlignCapture(alignCapture) { | ||
if (TABLE_RIGHT_ALIGN.test(alignCapture)) { | ||
return 'right'; | ||
} else if (TABLE_CENTER_ALIGN.test(alignCapture)) { | ||
return 'center'; | ||
} else if (TABLE_LEFT_ALIGN.test(alignCapture)) { | ||
return 'left'; | ||
} | ||
if (TABLE_RIGHT_ALIGN.test(alignCapture)) { | ||
return 'right'; | ||
} else if (TABLE_CENTER_ALIGN.test(alignCapture)) { | ||
return 'center'; | ||
} else if (TABLE_LEFT_ALIGN.test(alignCapture)) { | ||
return 'left'; | ||
} | ||
return null; | ||
return null; | ||
} | ||
function parseTableHeader(capture, parse, state) { | ||
const headerText = capture[1] | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.trim() | ||
.split(TABLE_ROW_SPLIT); | ||
const headerText = capture[1] | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.trim() | ||
.split(TABLE_ROW_SPLIT); | ||
return headerText.map(function(text) { | ||
return parse(text, state); | ||
}); | ||
return headerText.map(function(text) { | ||
return parse(text, state); | ||
}); | ||
} | ||
function parseTableAlign(capture /*, parse, state*/) { | ||
const alignText = capture[2] | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.trim() | ||
.split(TABLE_ROW_SPLIT); | ||
const alignText = capture[2] | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.trim() | ||
.split(TABLE_ROW_SPLIT); | ||
return alignText.map(parseTableAlignCapture); | ||
return alignText.map(parseTableAlignCapture); | ||
} | ||
function parseTableCells(capture, parse, state) { | ||
const rowsText = capture[3] | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.trim() | ||
.split('\n'); | ||
const rowsText = capture[3] | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.trim() | ||
.split('\n'); | ||
return rowsText.map(function(rowText) { | ||
return rowText | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.split(TABLE_ROW_SPLIT) | ||
.map(function(text) { | ||
return parse(text.trim(), state); | ||
}); | ||
}); | ||
return rowsText.map(function(rowText) { | ||
return rowText | ||
.replace(TABLE_TRIM_PIPES, '') | ||
.split(TABLE_ROW_SPLIT) | ||
.map(function(text) { | ||
return parse(text.trim(), state); | ||
}); | ||
}); | ||
} | ||
function parseTable(capture, parse, state) { | ||
state.inline = true; | ||
const header = parseTableHeader(capture, parse, state); | ||
const align = parseTableAlign(capture, parse, state); | ||
const cells = parseTableCells(capture, parse, state); | ||
state.inline = false; | ||
state.inline = true; | ||
const header = parseTableHeader(capture, parse, state); | ||
const align = parseTableAlign(capture, parse, state); | ||
const cells = parseTableCells(capture, parse, state); | ||
state.inline = false; | ||
return { | ||
align: align, | ||
cells: cells, | ||
header: header, | ||
type: 'table', | ||
}; | ||
return { | ||
align: align, | ||
cells: cells, | ||
header: header, | ||
type: 'table', | ||
}; | ||
} | ||
function getTableStyle(node, colIndex) { | ||
return node.align[colIndex] == null | ||
? {} | ||
: { | ||
textAlign: node.align[colIndex], | ||
}; | ||
return node.align[colIndex] == null | ||
? {} | ||
: { | ||
textAlign: node.align[colIndex], | ||
}; | ||
} | ||
@@ -365,54 +382,52 @@ | ||
function normalizeAttributeKey(key) { | ||
const hyphenIndex = key.indexOf('-'); | ||
const hyphenIndex = key.indexOf('-'); | ||
if (hyphenIndex !== -1 && key.match(HTML_CUSTOM_ATTR_R) === null) { | ||
key = key.replace(CAPTURE_LETTER_AFTER_HYPHEN, function(_, letter) { | ||
return letter.toUpperCase(); | ||
}); | ||
} | ||
if (hyphenIndex !== -1 && key.match(HTML_CUSTOM_ATTR_R) === null) { | ||
key = key.replace(CAPTURE_LETTER_AFTER_HYPHEN, function(_, letter) { | ||
return letter.toUpperCase(); | ||
}); | ||
} | ||
return key; | ||
return key; | ||
} | ||
function isInterpolation(value) { | ||
return INTERPOLATION_R.test(value); | ||
return INTERPOLATION_R.test(value); | ||
} | ||
function attributeValueToJSXPropValue(key, value) { | ||
if (key === 'style') { | ||
return value.split(/;\s?/).reduce(function(styles, kvPair) { | ||
const key = kvPair.slice(0, kvPair.indexOf(':')); | ||
if (key === 'style') { | ||
return value.split(/;\s?/).reduce(function(styles, kvPair) { | ||
const key = kvPair.slice(0, kvPair.indexOf(':')); | ||
// snake-case to camelCase | ||
// also handles PascalCasing vendor prefixes | ||
const camelCasedKey = key.replace(/(-[a-z])/g, function toUpper( | ||
substr | ||
) { | ||
return substr[1].toUpperCase(); | ||
}); | ||
// snake-case to camelCase | ||
// also handles PascalCasing vendor prefixes | ||
const camelCasedKey = key.replace(/(-[a-z])/g, function toUpper(substr) { | ||
return substr[1].toUpperCase(); | ||
}); | ||
// key.length + 1 to skip over the colon | ||
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim(); | ||
// key.length + 1 to skip over the colon | ||
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim(); | ||
return styles; | ||
}, {}); | ||
} else if (isInterpolation(value)) { | ||
// return as a string and let the consumer decide what to do with it | ||
value = value.slice(1, value.length - 1); | ||
} | ||
return styles; | ||
}, {}); | ||
} else if (isInterpolation(value)) { | ||
// return as a string and let the consumer decide what to do with it | ||
value = value.slice(1, value.length - 1); | ||
} | ||
if (value === 'true') { | ||
return true; | ||
} else if (value === 'false') { | ||
return false; | ||
} | ||
if (value === 'true') { | ||
return true; | ||
} else if (value === 'false') { | ||
return false; | ||
} | ||
return value; | ||
return value; | ||
} | ||
function normalizeWhitespace(source) { | ||
return source | ||
.replace(CR_NEWLINE_R, '\n') | ||
.replace(FORMFEED_R, '') | ||
.replace(TAB_R, ' '); | ||
return source | ||
.replace(CR_NEWLINE_R, '\n') | ||
.replace(FORMFEED_R, '') | ||
.replace(TAB_R, ' '); | ||
} | ||
@@ -440,85 +455,82 @@ | ||
function parserFor(rules) { | ||
// Sorts rules in order of increasing order, then | ||
// ascending rule name in case of ties. | ||
let ruleList = Object.keys(rules); | ||
// Sorts rules in order of increasing order, then | ||
// ascending rule name in case of ties. | ||
let ruleList = Object.keys(rules); | ||
/* istanbul ignore next */ | ||
if (process.env.NODE_ENV !== 'production') { | ||
ruleList.forEach(function(type) { | ||
let order = rules[type].order; | ||
if ( | ||
process.env.NODE_ENV !== 'production' && | ||
(typeof order !== 'number' || !isFinite(order)) && | ||
typeof console !== 'undefined' | ||
) { | ||
console.warn( | ||
'markdown-to-jsx: Invalid order for rule `' + | ||
type + | ||
'`: ' + | ||
order | ||
); | ||
} | ||
}); | ||
} | ||
/* istanbul ignore next */ | ||
if (process.env.NODE_ENV !== 'production') { | ||
ruleList.forEach(function(type) { | ||
let order = rules[type].order; | ||
if ( | ||
process.env.NODE_ENV !== 'production' && | ||
(typeof order !== 'number' || !isFinite(order)) && | ||
typeof console !== 'undefined' | ||
) { | ||
console.warn( | ||
'markdown-to-jsx: Invalid order for rule `' + type + '`: ' + order | ||
); | ||
} | ||
}); | ||
} | ||
ruleList.sort(function(typeA, typeB) { | ||
let orderA = rules[typeA].order; | ||
let orderB = rules[typeB].order; | ||
ruleList.sort(function(typeA, typeB) { | ||
let orderA = rules[typeA].order; | ||
let orderB = rules[typeB].order; | ||
// First sort based on increasing order | ||
if (orderA !== orderB) { | ||
return orderA - orderB; | ||
// First sort based on increasing order | ||
if (orderA !== orderB) { | ||
return orderA - orderB; | ||
// Then based on increasing unicode lexicographic ordering | ||
} else if (typeA < typeB) { | ||
return -1; | ||
} | ||
// Then based on increasing unicode lexicographic ordering | ||
} else if (typeA < typeB) { | ||
return -1; | ||
} | ||
return 1; | ||
}); | ||
return 1; | ||
}); | ||
function nestedParse(source, state) { | ||
let result = []; | ||
function nestedParse(source, state) { | ||
let result = []; | ||
// We store the previous capture so that match functions can | ||
// use some limited amount of lookbehind. Lists use this to | ||
// ensure they don't match arbitrary '- ' or '* ' in inline | ||
// text (see the list rule for more information). | ||
let prevCapture = ''; | ||
while (source) { | ||
let i = 0; | ||
while (i < ruleList.length) { | ||
const ruleType = ruleList[i]; | ||
const rule = rules[ruleType]; | ||
const capture = rule.match(source, state, prevCapture); | ||
// We store the previous capture so that match functions can | ||
// use some limited amount of lookbehind. Lists use this to | ||
// ensure they don't match arbitrary '- ' or '* ' in inline | ||
// text (see the list rule for more information). | ||
let prevCapture = ''; | ||
while (source) { | ||
let i = 0; | ||
while (i < ruleList.length) { | ||
const ruleType = ruleList[i]; | ||
const rule = rules[ruleType]; | ||
const capture = rule.match(source, state, prevCapture); | ||
if (capture) { | ||
const currCaptureString = capture[0]; | ||
source = source.substring(currCaptureString.length); | ||
const parsed = rule.parse(capture, nestedParse, state); | ||
if (capture) { | ||
const currCaptureString = capture[0]; | ||
source = source.substring(currCaptureString.length); | ||
const parsed = rule.parse(capture, nestedParse, state); | ||
// We also let rules override the default type of | ||
// their parsed node if they would like to, so that | ||
// there can be a single output function for all links, | ||
// even if there are several rules to parse them. | ||
if (parsed.type == null) { | ||
parsed.type = ruleType; | ||
} | ||
// We also let rules override the default type of | ||
// their parsed node if they would like to, so that | ||
// there can be a single output function for all links, | ||
// even if there are several rules to parse them. | ||
if (parsed.type == null) { | ||
parsed.type = ruleType; | ||
} | ||
result.push(parsed); | ||
result.push(parsed); | ||
prevCapture = currCaptureString; | ||
break; | ||
} | ||
i++; | ||
} | ||
prevCapture = currCaptureString; | ||
break; | ||
} | ||
return result; | ||
i++; | ||
} | ||
} | ||
return function outerParse(source, state) { | ||
return nestedParse(normalizeWhitespace(source), state); | ||
}; | ||
return result; | ||
} | ||
return function outerParse(source, state) { | ||
return nestedParse(normalizeWhitespace(source), state); | ||
}; | ||
} | ||
@@ -528,9 +540,9 @@ | ||
function inlineRegex(regex) { | ||
return function match(source, state) { | ||
if (state.inline) { | ||
return regex.exec(source); | ||
} else { | ||
return null; | ||
} | ||
}; | ||
return function match(source, state) { | ||
if (state.inline) { | ||
return regex.exec(source); | ||
} else { | ||
return null; | ||
} | ||
}; | ||
} | ||
@@ -540,9 +552,9 @@ | ||
function simpleInlineRegex(regex) { | ||
return function match(source, state) { | ||
if (state.inline || state.simple) { | ||
return regex.exec(source); | ||
} else { | ||
return null; | ||
} | ||
}; | ||
return function match(source, state) { | ||
if (state.inline || state.simple) { | ||
return regex.exec(source); | ||
} else { | ||
return null; | ||
} | ||
}; | ||
} | ||
@@ -552,9 +564,9 @@ | ||
function blockRegex(regex) { | ||
return function match(source, state) { | ||
if (state.inline || state.simple) { | ||
return null; | ||
} else { | ||
return regex.exec(source); | ||
} | ||
}; | ||
return function match(source, state) { | ||
if (state.inline || state.simple) { | ||
return null; | ||
} else { | ||
return regex.exec(source); | ||
} | ||
}; | ||
} | ||
@@ -564,63 +576,63 @@ | ||
function anyScopeRegex(regex) { | ||
return function match(source /*, state*/) { | ||
return regex.exec(source); | ||
}; | ||
return function match(source /*, state*/) { | ||
return regex.exec(source); | ||
}; | ||
} | ||
function reactFor(outputFunc) { | ||
return function nestedReactOutput(ast, state) { | ||
state = state || {}; | ||
if (Array.isArray(ast)) { | ||
const oldKey = state.key; | ||
const result = []; | ||
return function nestedReactOutput(ast, state) { | ||
state = state || {}; | ||
if (Array.isArray(ast)) { | ||
const oldKey = state.key; | ||
const result = []; | ||
// map nestedOutput over the ast, except group any text | ||
// nodes together into a single string output. | ||
let lastWasString = false; | ||
// map nestedOutput over the ast, except group any text | ||
// nodes together into a single string output. | ||
let lastWasString = false; | ||
for (let i = 0; i < ast.length; i++) { | ||
state.key = i; | ||
for (let i = 0; i < ast.length; i++) { | ||
state.key = i; | ||
const nodeOut = nestedReactOutput(ast[i], state); | ||
const isString = typeof nodeOut === 'string'; | ||
const nodeOut = nestedReactOutput(ast[i], state); | ||
const isString = typeof nodeOut === 'string'; | ||
if (isString && lastWasString) { | ||
result[result.length - 1] += nodeOut; | ||
} else { | ||
result.push(nodeOut); | ||
} | ||
if (isString && lastWasString) { | ||
result[result.length - 1] += nodeOut; | ||
} else { | ||
result.push(nodeOut); | ||
} | ||
lastWasString = isString; | ||
} | ||
lastWasString = isString; | ||
} | ||
state.key = oldKey; | ||
state.key = oldKey; | ||
return result; | ||
} | ||
return result; | ||
} | ||
return outputFunc(ast, nestedReactOutput, state); | ||
}; | ||
return outputFunc(ast, nestedReactOutput, state); | ||
}; | ||
} | ||
function sanitizeUrl(url) { | ||
try { | ||
const prot = decodeURIComponent(url) | ||
.replace(/[^A-Z0-9/:]/gi, '') | ||
.toLowerCase(); | ||
try { | ||
const prot = decodeURIComponent(url) | ||
.replace(/[^A-Z0-9/:]/gi, '') | ||
.toLowerCase(); | ||
if (prot.indexOf('javascript:') === 0) { | ||
return null; | ||
} | ||
} catch (e) { | ||
// decodeURIComponent sometimes throws a URIError | ||
// See `decodeURIComponent('a%AFc');` | ||
// http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception | ||
return null; | ||
if (prot.indexOf('javascript:') === 0) { | ||
return null; | ||
} | ||
} catch (e) { | ||
// decodeURIComponent sometimes throws a URIError | ||
// See `decodeURIComponent('a%AFc');` | ||
// http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception | ||
return null; | ||
} | ||
return url; | ||
return url; | ||
} | ||
function unescapeUrl(rawUrlString) { | ||
return rawUrlString.replace(UNESCAPE_URL_R, '$1'); | ||
return rawUrlString.replace(UNESCAPE_URL_R, '$1'); | ||
} | ||
@@ -632,10 +644,10 @@ | ||
function parseInline(parse, content, state) { | ||
const isCurrentlyInline = state.inline || false; | ||
const isCurrentlySimple = state.simple || false; | ||
state.inline = true; | ||
state.simple = true; | ||
const result = parse(content, state); | ||
state.inline = isCurrentlyInline; | ||
state.simple = isCurrentlySimple; | ||
return result; | ||
const isCurrentlyInline = state.inline || false; | ||
const isCurrentlySimple = state.simple || false; | ||
state.inline = true; | ||
state.simple = true; | ||
const result = parse(content, state); | ||
state.inline = isCurrentlyInline; | ||
state.simple = isCurrentlySimple; | ||
return result; | ||
} | ||
@@ -647,62 +659,65 @@ | ||
function parseSimpleInline(parse, content, state) { | ||
const isCurrentlyInline = state.inline || false; | ||
const isCurrentlySimple = state.simple || false; | ||
state.inline = false; | ||
state.simple = true; | ||
const result = parse(content, state); | ||
state.inline = isCurrentlyInline; | ||
state.simple = isCurrentlySimple; | ||
return result; | ||
const isCurrentlyInline = state.inline || false; | ||
const isCurrentlySimple = state.simple || false; | ||
state.inline = false; | ||
state.simple = true; | ||
const result = parse(content, state); | ||
state.inline = isCurrentlyInline; | ||
state.simple = isCurrentlySimple; | ||
return result; | ||
} | ||
function parseBlock(parse, content, state) { | ||
state.inline = false; | ||
return parse(content + '\n\n', state); | ||
state.inline = false; | ||
return parse(content + '\n\n', state); | ||
} | ||
function parseCaptureInline(capture, parse, state) { | ||
return { | ||
content: parseInline(parse, capture[1], state), | ||
}; | ||
return { | ||
content: parseInline(parse, capture[1], state), | ||
}; | ||
} | ||
function captureNothing() { | ||
return {}; | ||
return {}; | ||
} | ||
function renderNothing() { | ||
return null; | ||
return null; | ||
} | ||
function ruleOutput(rules) { | ||
return function nestedRuleOutput(ast, outputFunc, state) { | ||
return rules[ast.type].react(ast, outputFunc, state); | ||
}; | ||
return function nestedRuleOutput(ast, outputFunc, state) { | ||
return rules[ast.type].react(ast, outputFunc, state); | ||
}; | ||
} | ||
function cx() { | ||
return Array.prototype.slice | ||
.call(arguments) | ||
.filter(Boolean) | ||
.join(' '); | ||
return Array.prototype.slice | ||
.call(arguments) | ||
.filter(Boolean) | ||
.join(' '); | ||
} | ||
function get(src, path, fb) { | ||
let ptr = src; | ||
const frags = path.split('.'); | ||
let ptr = src; | ||
const frags = path.split('.'); | ||
while (frags.length) { | ||
ptr = ptr[frags[0]]; | ||
while (frags.length) { | ||
ptr = ptr[frags[0]]; | ||
if (ptr === undefined) break; | ||
else frags.shift(); | ||
} | ||
if (ptr === undefined) break; | ||
else frags.shift(); | ||
} | ||
return ptr || fb; | ||
return ptr || fb; | ||
} | ||
function getTag(tag, overrides) { | ||
const override = get(overrides, tag); | ||
return typeof override === 'function' | ||
? override | ||
: get(overrides, `${tag}.component`, tag); | ||
const override = get(overrides, tag); | ||
if (!override) return tag; | ||
return isPlainObject(override) | ||
? get(overrides, `${tag}.component`, tag) | ||
: override; | ||
} | ||
@@ -736,116 +751,111 @@ | ||
export function compiler(markdown, options) { | ||
options = options || {}; | ||
options.overrides = options.overrides || {}; | ||
options.slugify = options.slugify || slugify; | ||
options = options || {}; | ||
options.overrides = options.overrides || {}; | ||
options.slugify = options.slugify || slugify; | ||
const createElementFn = options.createElement || React.createElement; | ||
const createElementFn = options.createElement || React.createElement; | ||
// eslint-disable-next-line no-unused-vars | ||
function h(tag, props, ...children) { | ||
const overrideProps = get(options.overrides, `${tag}.props`, {}); | ||
// eslint-disable-next-line no-unused-vars | ||
function h(tag, props, ...children) { | ||
const overrideProps = get(options.overrides, `${tag}.props`, {}); | ||
return createElementFn( | ||
getTag(tag, options.overrides), | ||
{ | ||
...overrideProps, | ||
...props, | ||
className: | ||
cx(props && props.className, overrideProps.className) || | ||
undefined, | ||
}, | ||
...children | ||
); | ||
} | ||
return createElementFn( | ||
getTag(tag, options.overrides), | ||
{ | ||
...overrideProps, | ||
...props, | ||
className: | ||
cx(props && props.className, overrideProps.className) || undefined, | ||
}, | ||
...children | ||
); | ||
} | ||
function compile(input) { | ||
let inline = false; | ||
function compile(input) { | ||
let inline = false; | ||
if (options.forceInline) { | ||
inline = true; | ||
} else if (!options.forceBlock) { | ||
/** | ||
* should not contain any block-level markdown like newlines, lists, headings, | ||
* thematic breaks, blockquotes, tables, etc | ||
*/ | ||
inline = SHOULD_RENDER_AS_BLOCK_R.test(input) === false; | ||
} | ||
if (options.forceInline) { | ||
inline = true; | ||
} else if (!options.forceBlock) { | ||
/** | ||
* should not contain any block-level markdown like newlines, lists, headings, | ||
* thematic breaks, blockquotes, tables, etc | ||
*/ | ||
inline = SHOULD_RENDER_AS_BLOCK_R.test(input) === false; | ||
} | ||
const arr = emitter( | ||
parser( | ||
inline | ||
? input | ||
: `${input.replace( | ||
TRIM_NEWLINES_AND_TRAILING_WHITESPACE_R, | ||
'' | ||
)}\n\n`, | ||
{ inline } | ||
) | ||
); | ||
const arr = emitter( | ||
parser( | ||
inline | ||
? input | ||
: `${input.replace(TRIM_NEWLINES_AND_TRAILING_WHITESPACE_R, '')}\n\n`, | ||
{ inline } | ||
) | ||
); | ||
let jsx; | ||
if (arr.length > 1) { | ||
jsx = inline ? <span>{arr}</span> : <div>{arr}</div>; | ||
} else if (arr.length === 1) { | ||
jsx = arr[0]; | ||
let jsx; | ||
if (arr.length > 1) { | ||
jsx = inline ? <span>{arr}</span> : <div>{arr}</div>; | ||
} else if (arr.length === 1) { | ||
jsx = arr[0]; | ||
// TODO: remove this for React 16 | ||
if (typeof jsx === 'string') { | ||
jsx = <span>{jsx}</span>; | ||
} | ||
} else { | ||
// TODO: return null for React 16 | ||
jsx = <span />; | ||
} | ||
return jsx; | ||
// TODO: remove this for React 16 | ||
if (typeof jsx === 'string') { | ||
jsx = <span>{jsx}</span>; | ||
} | ||
} else { | ||
// TODO: return null for React 16 | ||
jsx = <span />; | ||
} | ||
function attrStringToMap(str) { | ||
const attributes = str.match(ATTR_EXTRACTOR_R); | ||
return jsx; | ||
} | ||
return attributes | ||
? attributes.reduce(function(map, raw, index) { | ||
const delimiterIdx = raw.indexOf('='); | ||
function attrStringToMap(str) { | ||
const attributes = str.match(ATTR_EXTRACTOR_R); | ||
if (delimiterIdx !== -1) { | ||
const key = normalizeAttributeKey( | ||
raw.slice(0, delimiterIdx) | ||
).trim(); | ||
const value = unquote(raw.slice(delimiterIdx + 1).trim()); | ||
return attributes | ||
? attributes.reduce(function(map, raw, index) { | ||
const delimiterIdx = raw.indexOf('='); | ||
const mappedKey = ATTRIBUTE_TO_JSX_PROP_MAP[key] || key; | ||
const normalizedValue = (map[ | ||
mappedKey | ||
] = attributeValueToJSXPropValue(key, value)); | ||
if (delimiterIdx !== -1) { | ||
const key = normalizeAttributeKey( | ||
raw.slice(0, delimiterIdx) | ||
).trim(); | ||
const value = unquote(raw.slice(delimiterIdx + 1).trim()); | ||
if ( | ||
HTML_BLOCK_ELEMENT_R.test(normalizedValue) || | ||
HTML_SELF_CLOSING_ELEMENT_R.test(normalizedValue) | ||
) { | ||
map[mappedKey] = React.cloneElement( | ||
compile(normalizedValue.trim()), | ||
{ key: index } | ||
); | ||
} | ||
} else { | ||
map[ATTRIBUTE_TO_JSX_PROP_MAP[raw] || raw] = true; | ||
} | ||
const mappedKey = ATTRIBUTE_TO_JSX_PROP_MAP[key] || key; | ||
const normalizedValue = (map[ | ||
mappedKey | ||
] = attributeValueToJSXPropValue(key, value)); | ||
return map; | ||
}, {}) | ||
: undefined; | ||
} | ||
if ( | ||
HTML_BLOCK_ELEMENT_R.test(normalizedValue) || | ||
HTML_SELF_CLOSING_ELEMENT_R.test(normalizedValue) | ||
) { | ||
map[mappedKey] = React.cloneElement( | ||
compile(normalizedValue.trim()), | ||
{ key: index } | ||
); | ||
} | ||
} else { | ||
map[ATTRIBUTE_TO_JSX_PROP_MAP[raw] || raw] = true; | ||
} | ||
/* istanbul ignore next */ | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (typeof markdown !== 'string') { | ||
throw new Error(`markdown-to-jsx: the first argument must be | ||
return map; | ||
}, {}) | ||
: undefined; | ||
} | ||
/* istanbul ignore next */ | ||
if (process.env.NODE_ENV !== 'production') { | ||
if (typeof markdown !== 'string') { | ||
throw new Error(`markdown-to-jsx: the first argument must be | ||
a string`); | ||
} | ||
} | ||
if ( | ||
Object.prototype.toString.call(options.overrides) !== | ||
'[object Object]' | ||
) { | ||
throw new Error(`markdown-to-jsx: options.overrides (second argument property) must be | ||
if ( | ||
Object.prototype.toString.call(options.overrides) !== '[object Object]' | ||
) { | ||
throw new Error(`markdown-to-jsx: options.overrides (second argument property) must be | ||
undefined or an object literal with shape: | ||
@@ -858,742 +868,709 @@ { | ||
}`); | ||
} | ||
} | ||
} | ||
const footnotes = []; | ||
const refs = {}; | ||
const footnotes = []; | ||
const refs = {}; | ||
/** | ||
* each rule's react() output function goes through our custom h() JSX pragma; | ||
* this allows the override functionality to be automatically applied | ||
*/ | ||
const rules = { | ||
blockQuote: { | ||
match: blockRegex(BLOCKQUOTE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parse( | ||
capture[0].replace( | ||
BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, | ||
'' | ||
), | ||
state | ||
), | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<blockquote key={state.key}> | ||
{output(node.content, state)} | ||
</blockquote> | ||
); | ||
}, | ||
}, | ||
/** | ||
* each rule's react() output function goes through our custom h() JSX pragma; | ||
* this allows the override functionality to be automatically applied | ||
*/ | ||
const rules = { | ||
blockQuote: { | ||
match: blockRegex(BLOCKQUOTE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parse( | ||
capture[0].replace(BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, ''), | ||
state | ||
), | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<blockquote key={state.key}>{output(node.content, state)}</blockquote> | ||
); | ||
}, | ||
}, | ||
breakLine: { | ||
match: anyScopeRegex(BREAK_LINE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse: captureNothing, | ||
react(_, __, state) { | ||
return <br key={state.key} />; | ||
}, | ||
}, | ||
breakLine: { | ||
match: anyScopeRegex(BREAK_LINE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse: captureNothing, | ||
react(_, __, state) { | ||
return <br key={state.key} />; | ||
}, | ||
}, | ||
breakThematic: { | ||
match: blockRegex(BREAK_THEMATIC_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse: captureNothing, | ||
react(_, __, state) { | ||
return <hr key={state.key} />; | ||
}, | ||
}, | ||
breakThematic: { | ||
match: blockRegex(BREAK_THEMATIC_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse: captureNothing, | ||
react(_, __, state) { | ||
return <hr key={state.key} />; | ||
}, | ||
}, | ||
codeBlock: { | ||
match: blockRegex(CODE_BLOCK_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
let content = capture[0] | ||
.replace(/^ {4}/gm, '') | ||
.replace(/\n+$/, ''); | ||
return { | ||
content: content, | ||
lang: undefined, | ||
}; | ||
}, | ||
codeBlock: { | ||
match: blockRegex(CODE_BLOCK_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
let content = capture[0].replace(/^ {4}/gm, '').replace(/\n+$/, ''); | ||
return { | ||
content: content, | ||
lang: undefined, | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<pre key={state.key}> | ||
<code className={node.lang ? `lang-${node.lang}` : ''}> | ||
{node.content} | ||
</code> | ||
</pre> | ||
); | ||
}, | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<pre key={state.key}> | ||
<code className={node.lang ? `lang-${node.lang}` : ''}> | ||
{node.content} | ||
</code> | ||
</pre> | ||
); | ||
}, | ||
}, | ||
codeFenced: { | ||
match: blockRegex(CODE_BLOCK_FENCED_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: capture[3], | ||
lang: capture[2] || undefined, | ||
type: 'codeBlock', | ||
}; | ||
}, | ||
}, | ||
codeFenced: { | ||
match: blockRegex(CODE_BLOCK_FENCED_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: capture[3], | ||
lang: capture[2] || undefined, | ||
type: 'codeBlock', | ||
}; | ||
}, | ||
}, | ||
codeInline: { | ||
match: simpleInlineRegex(CODE_INLINE_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: capture[2], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return <code key={state.key}>{node.content}</code>; | ||
}, | ||
}, | ||
codeInline: { | ||
match: simpleInlineRegex(CODE_INLINE_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: capture[2], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return <code key={state.key}>{node.content}</code>; | ||
}, | ||
}, | ||
/** | ||
* footnotes are emitted at the end of compilation in a special <footer> block | ||
*/ | ||
footnote: { | ||
match: blockRegex(FOOTNOTE_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
footnotes.push({ | ||
footnote: capture[2], | ||
identifier: capture[1], | ||
}); | ||
/** | ||
* footnotes are emitted at the end of compilation in a special <footer> block | ||
*/ | ||
footnote: { | ||
match: blockRegex(FOOTNOTE_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
footnotes.push({ | ||
footnote: capture[2], | ||
identifier: capture[1], | ||
}); | ||
return {}; | ||
}, | ||
react: renderNothing, | ||
}, | ||
return {}; | ||
}, | ||
react: renderNothing, | ||
}, | ||
footnoteReference: { | ||
match: inlineRegex(FOOTNOTE_REFERENCE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse*/) { | ||
return { | ||
content: capture[1], | ||
target: `#${capture[1]}`, | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<a key={state.key} href={sanitizeUrl(node.target)}> | ||
<sup key={state.key}>{node.content}</sup> | ||
</a> | ||
); | ||
}, | ||
}, | ||
footnoteReference: { | ||
match: inlineRegex(FOOTNOTE_REFERENCE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse*/) { | ||
return { | ||
content: capture[1], | ||
target: `#${capture[1]}`, | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<a key={state.key} href={sanitizeUrl(node.target)}> | ||
<sup key={state.key}>{node.content}</sup> | ||
</a> | ||
); | ||
}, | ||
}, | ||
gfmTask: { | ||
match: inlineRegex(GFM_TASK_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
completed: capture[1].toLowerCase() === 'x', | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<input | ||
checked={node.completed} | ||
key={state.key} | ||
readOnly | ||
type="checkbox" | ||
/> | ||
); | ||
}, | ||
}, | ||
gfmTask: { | ||
match: inlineRegex(GFM_TASK_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
completed: capture[1].toLowerCase() === 'x', | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<input | ||
checked={node.completed} | ||
key={state.key} | ||
readOnly | ||
type="checkbox" | ||
/> | ||
); | ||
}, | ||
}, | ||
heading: { | ||
match: blockRegex(HEADING_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parseInline(parse, capture[2], state), | ||
id: options.slugify(capture[2]), | ||
level: capture[1].length, | ||
}; | ||
}, | ||
react(node, output, state) { | ||
const Tag = `h${node.level}`; | ||
return ( | ||
<Tag id={node.id} key={state.key}> | ||
{output(node.content, state)} | ||
</Tag> | ||
); | ||
}, | ||
}, | ||
heading: { | ||
match: blockRegex(HEADING_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parseInline(parse, capture[2], state), | ||
id: options.slugify(capture[2]), | ||
level: capture[1].length, | ||
}; | ||
}, | ||
react(node, output, state) { | ||
const Tag = `h${node.level}`; | ||
return ( | ||
<Tag id={node.id} key={state.key}> | ||
{output(node.content, state)} | ||
</Tag> | ||
); | ||
}, | ||
}, | ||
headingSetext: { | ||
match: blockRegex(HEADING_SETEXT_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parseInline(parse, capture[1], state), | ||
level: capture[2] === '=' ? 1 : 2, | ||
type: 'heading', | ||
}; | ||
}, | ||
}, | ||
headingSetext: { | ||
match: blockRegex(HEADING_SETEXT_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parseInline(parse, capture[1], state), | ||
level: capture[2] === '=' ? 1 : 2, | ||
type: 'heading', | ||
}; | ||
}, | ||
}, | ||
htmlBlock: { | ||
/** | ||
* find the first matching end tag and process the interior | ||
*/ | ||
match: anyScopeRegex(HTML_BLOCK_ELEMENT_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
const [, whitespace] = capture[3].match(HTML_LEFT_TRIM_AMOUNT_R) | ||
const trimmer = new RegExp(`^${whitespace}`, 'gm') | ||
const trimmed = capture[3].replace(trimmer, ''); | ||
htmlBlock: { | ||
/** | ||
* find the first matching end tag and process the interior | ||
*/ | ||
match: anyScopeRegex(HTML_BLOCK_ELEMENT_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
const [, whitespace] = capture[3].match(HTML_LEFT_TRIM_AMOUNT_R); | ||
const trimmer = new RegExp(`^${whitespace}`, 'gm'); | ||
const trimmed = capture[3].replace(trimmer, ''); | ||
const parseFunc = containsBlockSyntax(trimmed) | ||
? parseBlock | ||
: parseInline; | ||
const parseFunc = containsBlockSyntax(trimmed) | ||
? parseBlock | ||
: parseInline; | ||
const noInnerParse = | ||
DO_NOT_PROCESS_HTML_ELEMENTS.indexOf(capture[1]) !== -1; | ||
const noInnerParse = | ||
DO_NOT_PROCESS_HTML_ELEMENTS.indexOf(capture[1]) !== -1; | ||
return { | ||
attrs: attrStringToMap(capture[2]), | ||
/** | ||
* if another html block is detected within, parse as block, | ||
* otherwise parse as inline to pick up any further markdown | ||
*/ | ||
content: noInnerParse | ||
? capture[3] | ||
: parseFunc(parse, trimmed, state), | ||
return { | ||
attrs: attrStringToMap(capture[2]), | ||
/** | ||
* if another html block is detected within, parse as block, | ||
* otherwise parse as inline to pick up any further markdown | ||
*/ | ||
content: noInnerParse ? capture[3] : parseFunc(parse, trimmed, state), | ||
noInnerParse, | ||
noInnerParse, | ||
tag: capture[1], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<node.tag key={state.key} {...node.attrs}> | ||
{node.noInnerParse | ||
? node.content | ||
: output(node.content, state)} | ||
</node.tag> | ||
); | ||
}, | ||
}, | ||
tag: capture[1], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<node.tag key={state.key} {...node.attrs}> | ||
{node.noInnerParse ? node.content : output(node.content, state)} | ||
</node.tag> | ||
); | ||
}, | ||
}, | ||
htmlComment: { | ||
match: anyScopeRegex(HTML_COMMENT_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse() { | ||
return {}; | ||
}, | ||
react: renderNothing, | ||
}, | ||
htmlComment: { | ||
match: anyScopeRegex(HTML_COMMENT_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse() { | ||
return {}; | ||
}, | ||
react: renderNothing, | ||
}, | ||
htmlSelfClosing: { | ||
/** | ||
* find the first matching end tag and process the interior | ||
*/ | ||
match: anyScopeRegex(HTML_SELF_CLOSING_ELEMENT_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
attrs: attrStringToMap(capture[2] || ''), | ||
tag: capture[1], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return <node.tag {...node.attrs} key={state.key} />; | ||
}, | ||
}, | ||
htmlSelfClosing: { | ||
/** | ||
* find the first matching end tag and process the interior | ||
*/ | ||
match: anyScopeRegex(HTML_SELF_CLOSING_ELEMENT_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
attrs: attrStringToMap(capture[2] || ''), | ||
tag: capture[1], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return <node.tag {...node.attrs} key={state.key} />; | ||
}, | ||
}, | ||
image: { | ||
match: simpleInlineRegex(IMAGE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
alt: capture[1], | ||
target: unescapeUrl(capture[2]), | ||
title: capture[3], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<img | ||
key={state.key} | ||
alt={node.alt || undefined} | ||
title={node.title || undefined} | ||
src={sanitizeUrl(node.target)} | ||
/> | ||
); | ||
}, | ||
}, | ||
image: { | ||
match: simpleInlineRegex(IMAGE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
alt: capture[1], | ||
target: unescapeUrl(capture[2]), | ||
title: capture[3], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<img | ||
key={state.key} | ||
alt={node.alt || undefined} | ||
title={node.title || undefined} | ||
src={sanitizeUrl(node.target)} | ||
/> | ||
); | ||
}, | ||
}, | ||
link: { | ||
match: inlineRegex(LINK_R, false), | ||
order: PARSE_PRIORITY_LOW, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parseSimpleInline(parse, capture[1], state), | ||
target: unescapeUrl(capture[2]), | ||
title: capture[3], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<a | ||
key={state.key} | ||
href={sanitizeUrl(node.target)} | ||
title={node.title} | ||
> | ||
{output(node.content, state)} | ||
</a> | ||
); | ||
}, | ||
}, | ||
link: { | ||
match: inlineRegex(LINK_R, false), | ||
order: PARSE_PRIORITY_LOW, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parseSimpleInline(parse, capture[1], state), | ||
target: unescapeUrl(capture[2]), | ||
title: capture[3], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<a key={state.key} href={sanitizeUrl(node.target)} title={node.title}> | ||
{output(node.content, state)} | ||
</a> | ||
); | ||
}, | ||
}, | ||
// https://daringfireball.net/projects/markdown/syntax#autolink | ||
linkAngleBraceStyleDetector: { | ||
match: inlineRegex(LINK_AUTOLINK_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: [ | ||
{ | ||
content: capture[1], | ||
type: 'text', | ||
}, | ||
], | ||
target: capture[1], | ||
type: 'link', | ||
}; | ||
// https://daringfireball.net/projects/markdown/syntax#autolink | ||
linkAngleBraceStyleDetector: { | ||
match: inlineRegex(LINK_AUTOLINK_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: [ | ||
{ | ||
content: capture[1], | ||
type: 'text', | ||
}, | ||
}, | ||
], | ||
target: capture[1], | ||
type: 'link', | ||
}; | ||
}, | ||
}, | ||
linkBareUrlDetector: { | ||
match: inlineRegex(LINK_AUTOLINK_BARE_URL_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: [ | ||
{ | ||
content: capture[1], | ||
type: 'text', | ||
}, | ||
], | ||
target: capture[1], | ||
title: undefined, | ||
type: 'link', | ||
}; | ||
linkBareUrlDetector: { | ||
match: inlineRegex(LINK_AUTOLINK_BARE_URL_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: [ | ||
{ | ||
content: capture[1], | ||
type: 'text', | ||
}, | ||
}, | ||
], | ||
target: capture[1], | ||
title: undefined, | ||
type: 'link', | ||
}; | ||
}, | ||
}, | ||
linkMailtoDetector: { | ||
match: inlineRegex(LINK_AUTOLINK_MAILTO_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
let address = capture[1]; | ||
let target = capture[1]; | ||
linkMailtoDetector: { | ||
match: inlineRegex(LINK_AUTOLINK_MAILTO_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse, state*/) { | ||
let address = capture[1]; | ||
let target = capture[1]; | ||
// Check for a `mailto:` already existing in the link: | ||
if (!AUTOLINK_MAILTO_CHECK_R.test(target)) { | ||
target = 'mailto:' + target; | ||
} | ||
// Check for a `mailto:` already existing in the link: | ||
if (!AUTOLINK_MAILTO_CHECK_R.test(target)) { | ||
target = 'mailto:' + target; | ||
} | ||
return { | ||
content: [ | ||
{ | ||
content: address.replace('mailto:', ''), | ||
type: 'text', | ||
}, | ||
], | ||
target: target, | ||
type: 'link', | ||
}; | ||
return { | ||
content: [ | ||
{ | ||
content: address.replace('mailto:', ''), | ||
type: 'text', | ||
}, | ||
}, | ||
], | ||
target: target, | ||
type: 'link', | ||
}; | ||
}, | ||
}, | ||
list: { | ||
match(source, state, prevCapture) { | ||
// We only want to break into a list if we are at the start of a | ||
// line. This is to avoid parsing "hi * there" with "* there" | ||
// becoming a part of a list. | ||
// You might wonder, "but that's inline, so of course it wouldn't | ||
// start a list?". You would be correct! Except that some of our | ||
// lists can be inline, because they might be inside another list, | ||
// in which case we can parse with inline scope, but need to allow | ||
// nested lists inside this inline scope. | ||
const isStartOfLine = LIST_LOOKBEHIND_R.exec(prevCapture); | ||
const isListBlock = state._list || !state.inline; | ||
list: { | ||
match(source, state, prevCapture) { | ||
// We only want to break into a list if we are at the start of a | ||
// line. This is to avoid parsing "hi * there" with "* there" | ||
// becoming a part of a list. | ||
// You might wonder, "but that's inline, so of course it wouldn't | ||
// start a list?". You would be correct! Except that some of our | ||
// lists can be inline, because they might be inside another list, | ||
// in which case we can parse with inline scope, but need to allow | ||
// nested lists inside this inline scope. | ||
const isStartOfLine = LIST_LOOKBEHIND_R.exec(prevCapture); | ||
const isListBlock = state._list || !state.inline; | ||
if (isStartOfLine && isListBlock) { | ||
source = isStartOfLine[1] + source; | ||
if (isStartOfLine && isListBlock) { | ||
source = isStartOfLine[1] + source; | ||
return LIST_R.exec(source); | ||
} else { | ||
return null; | ||
} | ||
}, | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
const bullet = capture[2]; | ||
const ordered = bullet.length > 1; | ||
const start = ordered ? +bullet : undefined; | ||
const items = capture[0] | ||
// recognize the end of a paragraph block inside a list item: | ||
// two or more newlines at end end of the item | ||
.replace(BLOCK_END_R, '\n') | ||
.match(LIST_ITEM_R); | ||
return LIST_R.exec(source); | ||
} else { | ||
return null; | ||
} | ||
}, | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture, parse, state) { | ||
const bullet = capture[2]; | ||
const ordered = bullet.length > 1; | ||
const start = ordered ? +bullet : undefined; | ||
const items = capture[0] | ||
// recognize the end of a paragraph block inside a list item: | ||
// two or more newlines at end end of the item | ||
.replace(BLOCK_END_R, '\n') | ||
.match(LIST_ITEM_R); | ||
let lastItemWasAParagraph = false; | ||
const itemContent = items.map(function(item, i) { | ||
// We need to see how far indented the item is: | ||
const space = LIST_ITEM_PREFIX_R.exec(item)[0].length; | ||
let lastItemWasAParagraph = false; | ||
const itemContent = items.map(function(item, i) { | ||
// We need to see how far indented the item is: | ||
const space = LIST_ITEM_PREFIX_R.exec(item)[0].length; | ||
// And then we construct a regex to "unindent" the subsequent | ||
// lines of the items by that amount: | ||
const spaceRegex = new RegExp('^ {1,' + space + '}', 'gm'); | ||
// And then we construct a regex to "unindent" the subsequent | ||
// lines of the items by that amount: | ||
const spaceRegex = new RegExp('^ {1,' + space + '}', 'gm'); | ||
// Before processing the item, we need a couple things | ||
const content = item | ||
// remove indents on trailing lines: | ||
.replace(spaceRegex, '') | ||
// remove the bullet: | ||
.replace(LIST_ITEM_PREFIX_R, ''); | ||
// Before processing the item, we need a couple things | ||
const content = item | ||
// remove indents on trailing lines: | ||
.replace(spaceRegex, '') | ||
// remove the bullet: | ||
.replace(LIST_ITEM_PREFIX_R, ''); | ||
// Handling "loose" lists, like: | ||
// | ||
// * this is wrapped in a paragraph | ||
// | ||
// * as is this | ||
// | ||
// * as is this | ||
const isLastItem = i === items.length - 1; | ||
const containsBlocks = content.indexOf('\n\n') !== -1; | ||
// Handling "loose" lists, like: | ||
// | ||
// * this is wrapped in a paragraph | ||
// | ||
// * as is this | ||
// | ||
// * as is this | ||
const isLastItem = i === items.length - 1; | ||
const containsBlocks = content.indexOf('\n\n') !== -1; | ||
// Any element in a list is a block if it contains multiple | ||
// newlines. The last element in the list can also be a block | ||
// if the previous item in the list was a block (this is | ||
// because non-last items in the list can end with \n\n, but | ||
// the last item can't, so we just "inherit" this property | ||
// from our previous element). | ||
const thisItemIsAParagraph = | ||
containsBlocks || (isLastItem && lastItemWasAParagraph); | ||
lastItemWasAParagraph = thisItemIsAParagraph; | ||
// Any element in a list is a block if it contains multiple | ||
// newlines. The last element in the list can also be a block | ||
// if the previous item in the list was a block (this is | ||
// because non-last items in the list can end with \n\n, but | ||
// the last item can't, so we just "inherit" this property | ||
// from our previous element). | ||
const thisItemIsAParagraph = | ||
containsBlocks || (isLastItem && lastItemWasAParagraph); | ||
lastItemWasAParagraph = thisItemIsAParagraph; | ||
// backup our state for restoration afterwards. We're going to | ||
// want to set state._list to true, and state.inline depending | ||
// on our list's looseness. | ||
const oldStateInline = state.inline; | ||
const oldStateList = state._list; | ||
state._list = true; | ||
// backup our state for restoration afterwards. We're going to | ||
// want to set state._list to true, and state.inline depending | ||
// on our list's looseness. | ||
const oldStateInline = state.inline; | ||
const oldStateList = state._list; | ||
state._list = true; | ||
// Parse inline if we're in a tight list, or block if we're in | ||
// a loose list. | ||
let adjustedContent; | ||
if (thisItemIsAParagraph) { | ||
state.inline = false; | ||
adjustedContent = content.replace( | ||
LIST_ITEM_END_R, | ||
'\n\n' | ||
); | ||
} else { | ||
state.inline = true; | ||
adjustedContent = content.replace(LIST_ITEM_END_R, ''); | ||
} | ||
// Parse inline if we're in a tight list, or block if we're in | ||
// a loose list. | ||
let adjustedContent; | ||
if (thisItemIsAParagraph) { | ||
state.inline = false; | ||
adjustedContent = content.replace(LIST_ITEM_END_R, '\n\n'); | ||
} else { | ||
state.inline = true; | ||
adjustedContent = content.replace(LIST_ITEM_END_R, ''); | ||
} | ||
const result = parse(adjustedContent, state); | ||
const result = parse(adjustedContent, state); | ||
// Restore our state before returning | ||
state.inline = oldStateInline; | ||
state._list = oldStateList; | ||
// Restore our state before returning | ||
state.inline = oldStateInline; | ||
state._list = oldStateList; | ||
return result; | ||
}); | ||
return result; | ||
}); | ||
return { | ||
items: itemContent, | ||
ordered: ordered, | ||
start: start, | ||
}; | ||
}, | ||
react(node, output, state) { | ||
const Tag = node.ordered ? 'ol' : 'ul'; | ||
return { | ||
items: itemContent, | ||
ordered: ordered, | ||
start: start, | ||
}; | ||
}, | ||
react(node, output, state) { | ||
const Tag = node.ordered ? 'ol' : 'ul'; | ||
return ( | ||
<Tag key={state.key} start={node.start}> | ||
{node.items.map(function generateListItem(item, i) { | ||
return <li key={i}>{output(item, state)}</li>; | ||
})} | ||
</Tag> | ||
); | ||
}, | ||
}, | ||
return ( | ||
<Tag key={state.key} start={node.start}> | ||
{node.items.map(function generateListItem(item, i) { | ||
return <li key={i}>{output(item, state)}</li>; | ||
})} | ||
</Tag> | ||
); | ||
}, | ||
}, | ||
newlineCoalescer: { | ||
match: blockRegex(CONSECUTIVE_NEWLINE_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse: captureNothing, | ||
react(/*node, output, state*/) { | ||
return '\n'; | ||
}, | ||
}, | ||
newlineCoalescer: { | ||
match: blockRegex(CONSECUTIVE_NEWLINE_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse: captureNothing, | ||
react(/*node, output, state*/) { | ||
return '\n'; | ||
}, | ||
}, | ||
paragraph: { | ||
match: blockRegex(PARAGRAPH_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse: parseCaptureInline, | ||
react(node, output, state) { | ||
return <p key={state.key}>{output(node.content, state)}</p>; | ||
}, | ||
}, | ||
paragraph: { | ||
match: blockRegex(PARAGRAPH_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse: parseCaptureInline, | ||
react(node, output, state) { | ||
return <p key={state.key}>{output(node.content, state)}</p>; | ||
}, | ||
}, | ||
ref: { | ||
match: inlineRegex(REFERENCE_IMAGE_OR_LINK), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse*/) { | ||
refs[capture[1]] = { | ||
target: capture[2], | ||
title: capture[4], | ||
}; | ||
ref: { | ||
match: inlineRegex(REFERENCE_IMAGE_OR_LINK), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture /*, parse*/) { | ||
refs[capture[1]] = { | ||
target: capture[2], | ||
title: capture[4], | ||
}; | ||
return {}; | ||
}, | ||
react: renderNothing, | ||
}, | ||
return {}; | ||
}, | ||
react: renderNothing, | ||
}, | ||
refImage: { | ||
match: simpleInlineRegex(REFERENCE_IMAGE_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture) { | ||
return { | ||
alt: capture[1] || undefined, | ||
ref: capture[2], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<img | ||
key={state.key} | ||
alt={node.alt} | ||
src={sanitizeUrl(refs[node.ref].target)} | ||
title={refs[node.ref].title} | ||
/> | ||
); | ||
}, | ||
}, | ||
refImage: { | ||
match: simpleInlineRegex(REFERENCE_IMAGE_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture) { | ||
return { | ||
alt: capture[1] || undefined, | ||
ref: capture[2], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<img | ||
key={state.key} | ||
alt={node.alt} | ||
src={sanitizeUrl(refs[node.ref].target)} | ||
title={refs[node.ref].title} | ||
/> | ||
); | ||
}, | ||
}, | ||
refLink: { | ||
match: inlineRegex(REFERENCE_LINK_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parse(capture[1], state), | ||
ref: capture[2], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<a | ||
key={state.key} | ||
href={sanitizeUrl(refs[node.ref].target)} | ||
title={refs[node.ref].title} | ||
> | ||
{output(node.content, state)} | ||
</a> | ||
); | ||
}, | ||
}, | ||
refLink: { | ||
match: inlineRegex(REFERENCE_LINK_R), | ||
order: PARSE_PRIORITY_MAX, | ||
parse(capture, parse, state) { | ||
return { | ||
content: parse(capture[1], state), | ||
ref: capture[2], | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<a | ||
key={state.key} | ||
href={sanitizeUrl(refs[node.ref].target)} | ||
title={refs[node.ref].title} | ||
> | ||
{output(node.content, state)} | ||
</a> | ||
); | ||
}, | ||
}, | ||
table: { | ||
match: blockRegex(NP_TABLE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse: parseTable, | ||
react(node, output, state) { | ||
table: { | ||
match: blockRegex(NP_TABLE_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse: parseTable, | ||
react(node, output, state) { | ||
return ( | ||
<table key={state.key}> | ||
<thead> | ||
<tr> | ||
{node.header.map(function generateHeaderCell(content, i) { | ||
return ( | ||
<th key={i} style={getTableStyle(node, i)} scope="col"> | ||
{output(content, state)} | ||
</th> | ||
); | ||
})} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{node.cells.map(function generateTableRow(row, i) { | ||
return ( | ||
<table key={state.key}> | ||
<thead> | ||
<tr> | ||
{node.header.map(function generateHeaderCell( | ||
content, | ||
i | ||
) { | ||
return ( | ||
<th | ||
key={i} | ||
style={getTableStyle(node, i)} | ||
scope="col" | ||
> | ||
{output(content, state)} | ||
</th> | ||
); | ||
})} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{node.cells.map(function generateTableRow(row, i) { | ||
return ( | ||
<tr key={i}> | ||
{row.map(function generateTableCell( | ||
content, | ||
c | ||
) { | ||
return ( | ||
<td | ||
key={c} | ||
style={getTableStyle( | ||
node, | ||
c | ||
)} | ||
> | ||
{output(content, state)} | ||
</td> | ||
); | ||
})} | ||
</tr> | ||
); | ||
})} | ||
</tbody> | ||
</table> | ||
<tr key={i}> | ||
{row.map(function generateTableCell(content, c) { | ||
return ( | ||
<td key={c} style={getTableStyle(node, c)}> | ||
{output(content, state)} | ||
</td> | ||
); | ||
})} | ||
</tr> | ||
); | ||
}, | ||
}, | ||
})} | ||
</tbody> | ||
</table> | ||
); | ||
}, | ||
}, | ||
text: { | ||
// Here we look for anything followed by non-symbols, | ||
// double newlines, or double-space-newlines | ||
// We break on any symbol characters so that this grammar | ||
// is easy to extend without needing to modify this regex | ||
match: anyScopeRegex(TEXT_PLAIN_R), | ||
order: PARSE_PRIORITY_MIN, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: capture[0], | ||
}; | ||
}, | ||
react(node /*, output, state*/) { | ||
return node.content; | ||
}, | ||
}, | ||
text: { | ||
// Here we look for anything followed by non-symbols, | ||
// double newlines, or double-space-newlines | ||
// We break on any symbol characters so that this grammar | ||
// is easy to extend without needing to modify this regex | ||
match: anyScopeRegex(TEXT_PLAIN_R), | ||
order: PARSE_PRIORITY_MIN, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
// nbsp -> unicode equivalent for named chars | ||
content: capture[0].replace(HTML_CHAR_CODE_R, (full, inner) => { | ||
return namedCodesToUnicode[inner] | ||
? namedCodesToUnicode[inner] | ||
: full; | ||
}), | ||
}; | ||
}, | ||
react(node /*, output, state*/) { | ||
return node.content; | ||
}, | ||
}, | ||
textBolded: { | ||
match: simpleInlineRegex(TEXT_BOLD_R), | ||
order: PARSE_PRIORITY_MED, | ||
parse(capture, parse, state) { | ||
return { | ||
// capture[1] -> the syntax control character | ||
// capture[2] -> inner content | ||
content: parse(capture[2], state), | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return ( | ||
<strong key={state.key}> | ||
{output(node.content, state)} | ||
</strong> | ||
); | ||
}, | ||
}, | ||
textBolded: { | ||
match: simpleInlineRegex(TEXT_BOLD_R), | ||
order: PARSE_PRIORITY_MED, | ||
parse(capture, parse, state) { | ||
return { | ||
// capture[1] -> the syntax control character | ||
// capture[2] -> inner content | ||
content: parse(capture[2], state), | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return <strong key={state.key}>{output(node.content, state)}</strong>; | ||
}, | ||
}, | ||
textEmphasized: { | ||
match: simpleInlineRegex(TEXT_EMPHASIZED_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse(capture, parse, state) { | ||
return { | ||
// capture[1] -> opening * or _ | ||
// capture[2] -> inner content | ||
content: parse(capture[2], state), | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return <em key={state.key}>{output(node.content, state)}</em>; | ||
}, | ||
}, | ||
textEmphasized: { | ||
match: simpleInlineRegex(TEXT_EMPHASIZED_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse(capture, parse, state) { | ||
return { | ||
// capture[1] -> opening * or _ | ||
// capture[2] -> inner content | ||
content: parse(capture[2], state), | ||
}; | ||
}, | ||
react(node, output, state) { | ||
return <em key={state.key}>{output(node.content, state)}</em>; | ||
}, | ||
}, | ||
textEscaped: { | ||
// We don't allow escaping numbers, letters, or spaces here so that | ||
// backslashes used in plain text still get rendered. But allowing | ||
// escaping anything else provides a very flexible escape mechanism, | ||
// regardless of how this grammar is extended. | ||
match: simpleInlineRegex(TEXT_ESCAPED_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: capture[1], | ||
type: 'text', | ||
}; | ||
}, | ||
}, | ||
textEscaped: { | ||
// We don't allow escaping numbers, letters, or spaces here so that | ||
// backslashes used in plain text still get rendered. But allowing | ||
// escaping anything else provides a very flexible escape mechanism, | ||
// regardless of how this grammar is extended. | ||
match: simpleInlineRegex(TEXT_ESCAPED_R), | ||
order: PARSE_PRIORITY_HIGH, | ||
parse(capture /*, parse, state*/) { | ||
return { | ||
content: capture[1], | ||
type: 'text', | ||
}; | ||
}, | ||
}, | ||
textStrikethroughed: { | ||
match: simpleInlineRegex(TEXT_STRIKETHROUGHED_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse: parseCaptureInline, | ||
react(node, output, state) { | ||
return <del key={state.key}>{output(node.content, state)}</del>; | ||
}, | ||
}, | ||
}; | ||
textStrikethroughed: { | ||
match: simpleInlineRegex(TEXT_STRIKETHROUGHED_R), | ||
order: PARSE_PRIORITY_LOW, | ||
parse: parseCaptureInline, | ||
react(node, output, state) { | ||
return <del key={state.key}>{output(node.content, state)}</del>; | ||
}, | ||
}, | ||
}; | ||
// Object.keys(rules).forEach(key => { | ||
// let { match, parse } = rules[key]; | ||
// Object.keys(rules).forEach(key => { | ||
// let { match, parse } = rules[key]; | ||
// rules[key].match = (...args) => { | ||
// const start = performance.now(); | ||
// const result = match(...args); | ||
// const delta = performance.now() - start; | ||
// rules[key].match = (...args) => { | ||
// const start = performance.now(); | ||
// const result = match(...args); | ||
// const delta = performance.now() - start; | ||
// if (delta > 5) | ||
// console.warn( | ||
// `Slow match for ${key}: ${delta.toFixed(3)}ms, input: ${ | ||
// args[0] | ||
// }` | ||
// ); | ||
// if (delta > 5) | ||
// console.warn( | ||
// `Slow match for ${key}: ${delta.toFixed(3)}ms, input: ${ | ||
// args[0] | ||
// }` | ||
// ); | ||
// return result; | ||
// }; | ||
// return result; | ||
// }; | ||
// rules[key].parse = (...args) => { | ||
// const start = performance.now(); | ||
// const result = parse(...args); | ||
// const delta = performance.now() - start; | ||
// rules[key].parse = (...args) => { | ||
// const start = performance.now(); | ||
// const result = parse(...args); | ||
// const delta = performance.now() - start; | ||
// if (delta > 5) | ||
// console.warn(`Slow parse for ${key}: ${delta.toFixed(3)}ms`); | ||
// if (delta > 5) | ||
// console.warn(`Slow parse for ${key}: ${delta.toFixed(3)}ms`); | ||
// console.log(`${key}:parse`, `${delta.toFixed(3)}ms`, args[0]); | ||
// console.log(`${key}:parse`, `${delta.toFixed(3)}ms`, args[0]); | ||
// return result; | ||
// }; | ||
// }); | ||
// return result; | ||
// }; | ||
// }); | ||
const parser = parserFor(rules); | ||
const emitter = reactFor(ruleOutput(rules)); | ||
const parser = parserFor(rules); | ||
const emitter = reactFor(ruleOutput(rules)); | ||
const jsx = compile(markdown); | ||
const jsx = compile(markdown); | ||
if (footnotes.length) { | ||
jsx.props.children.push( | ||
<footer> | ||
{footnotes.map(function createFootnote(def) { | ||
return ( | ||
<div id={def.identifier} key={def.identifier}> | ||
{def.identifier} | ||
{emitter(parser(def.footnote, { inline: true }))} | ||
</div> | ||
); | ||
})} | ||
</footer> | ||
); | ||
} | ||
if (footnotes.length) { | ||
jsx.props.children.push( | ||
<footer> | ||
{footnotes.map(function createFootnote(def) { | ||
return ( | ||
<div id={def.identifier} key={def.identifier}> | ||
{def.identifier} | ||
{emitter(parser(def.footnote, { inline: true }))} | ||
</div> | ||
); | ||
})} | ||
</footer> | ||
); | ||
} | ||
return jsx; | ||
return jsx; | ||
} | ||
@@ -1612,12 +1589,12 @@ | ||
export default function Markdown({ children, options, ...props }) { | ||
return React.cloneElement(compiler(children, options), props); | ||
return React.cloneElement(compiler(children, options), props); | ||
} | ||
if (process.env.NODE_ENV !== 'production') { | ||
const PropTypes = require('prop-types'); | ||
const PropTypes = require('prop-types'); | ||
Markdown.propTypes = { | ||
children: PropTypes.string.isRequired, | ||
options: PropTypes.object, | ||
}; | ||
Markdown.propTypes = { | ||
children: PropTypes.string.isRequired, | ||
options: PropTypes.object, | ||
}; | ||
} |
@@ -6,3 +6,3 @@ { | ||
"license": "MIT", | ||
"version": "6.7.4", | ||
"version": "6.8.0", | ||
"engines": { | ||
@@ -24,45 +24,46 @@ "node": ">= 4" | ||
"index.js", | ||
"index.cjs.js", | ||
"index.js.js.map", | ||
"index.esm.js", | ||
"index.esm.js.map", | ||
"dist", | ||
"LICENSE", | ||
"README.md" | ||
], | ||
"main": "index.cjs.js", | ||
"jsnext:main": "index.esm.js", | ||
"module": "index.esm.js", | ||
"main": "dist/cjs.js", | ||
"jsnext:main": "dist/esm.js", | ||
"module": "dist/esm.js", | ||
"devDependencies": { | ||
"babel-cli": "^6.14.0", | ||
"babel-core": "^6.26.0", | ||
"babel-eslint": "^8.2.2", | ||
"babel-jest": "^22.4.3", | ||
"babel-eslint": "^10.0.1", | ||
"babel-jest": "^23.6.0", | ||
"babel-loader": "^7.1.4", | ||
"babel-plugin-emotion": "^9.1.2", | ||
"babel-plugin-transform-react-remove-prop-types": "^0.4.13", | ||
"babel-plugin-emotion": "^9.2.11", | ||
"babel-plugin-transform-react-remove-prop-types": "^0.4.19", | ||
"babel-preset-env": "^1.7.0", | ||
"babel-preset-minify": "^0.5.0", | ||
"babel-preset-react": "^6.11.1", | ||
"babel-preset-stage-2": "^6.13.0", | ||
"codecov": "^3.0.1", | ||
"emotion": "^9.1.2", | ||
"eslint": "^4.12.1", | ||
"eslint-config-prettier": "^2.9.0", | ||
"eslint-plugin-react": "^7.5.1", | ||
"codecov": "^3.1.0", | ||
"emotion": "^9.2.12", | ||
"eslint": "^5.7.0", | ||
"eslint-config-prettier": "^3.1.0", | ||
"eslint-plugin-react": "^7.11.1", | ||
"in-publish": "^2.0.0", | ||
"jest": "^22.4.3", | ||
"jest": "^23.6.0", | ||
"jest-serializer-html": "^5.0.0", | ||
"mkdirp": "^0.5.1", | ||
"polished": "^1.3.0", | ||
"preact": "^8.2.1", | ||
"preact-compat": "^3.16.0", | ||
"preact-emotion": "^9.1.2", | ||
"preact": "^8.3.1", | ||
"preact-compat": "^3.18.4", | ||
"preact-emotion": "^9.2.12", | ||
"prettier": "^1.14.3", | ||
"react": "^15.6.1", | ||
"react-dom": "^15.6.1", | ||
"size-limit": "^0.17.0", | ||
"uglify-js": "^3.3.15", | ||
"webpack": "^4.1.1", | ||
"webpack-cli": "^2.0.12", | ||
"webpack-dev-server": "^3.1.1" | ||
"rimraf": "^2.6.2", | ||
"size-limit": "^0.20.1", | ||
"uglify-js": "^3.4.9", | ||
"webpack": "^4.21.0", | ||
"webpack-cli": "^3.1.2", | ||
"webpack-dev-server": "^3.1.9" | ||
}, | ||
"dependencies": { | ||
"prop-types": "^15.5.10", | ||
"prop-types": "^15.6.2", | ||
"unquote": "^1.1.0" | ||
@@ -75,4 +76,5 @@ }, | ||
"prepublish": "in-publish && npm run build && npm run release || not-in-publish", | ||
"lint": "eslint index.js index.spec.js site.js", | ||
"build": "babel index.js --out-file index.cjs.js --source-maps && BABEL_ENV=esm babel index.js --out-file index.esm.js --source-maps", | ||
"lint": "eslint .", | ||
"prebuild": "rimraf dist && mkdirp dist", | ||
"build": "NODE_ENV=production babel index.js --out-file dist/cjs.js --source-maps && NODE_ENV=production BABEL_ENV=esm babel index.js --out-file dist/esm.js --source-maps", | ||
"release": "webpack --config ./webpack.config.prod.babel.js -p --display-optimization-bailout", | ||
@@ -86,3 +88,3 @@ "release:debug": "webpack --config ./webpack.config.babel.js --display-optimization-bailout", | ||
{ | ||
"path": "index.cjs.js", | ||
"path": "dist/cjs.js", | ||
"limit": "5 kB" | ||
@@ -89,0 +91,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
265694
8
33
1995
1
Updatedprop-types@^15.6.2