UNPKG

markdown-to-jsx

Version:

Convert markdown to JSX with ease for React and React-like projects. Super lightweight and highly configurable.

1,481 lines (1,477 loc) 60.4 kB
import * as React from 'react'; function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } /** * Analogous to `node.type`. Please note that the values here may change at any time, * so do not hard code against the value directly. */ var RuleType$1 = { blockQuote: '0', breakLine: '1', breakThematic: '2', codeBlock: '3', codeFenced: '4', codeInline: '5', footnote: '6', footnoteReference: '7', gfmTask: '8', heading: '9', headingSetext: '10', htmlBlock: '11', htmlComment: '12', htmlSelfClosing: '13', image: '14', link: '15', linkAngleBraceStyleDetector: '16', linkBareUrlDetector: '17', linkMailtoDetector: '18', newlineCoalescer: '19', orderedList: '20', paragraph: '21', ref: '22', refImage: '23', refLink: '24', table: '25', tableSeparator: '26', text: '27', textEscaped: '28', textFormatted: '34', unorderedList: '30' }; var T = ['strong', 'em', 'del', 'mark']; var DELS = [['**', T[0]], ['__', T[0]], ['~~', T[2]], ['==', T[3]], ['*', T[1]], ['_', T[1]]]; function skipLinkOrImage(source, pos) { var bracketDepth = 1; var i = pos + 1; while (i < source.length && bracketDepth > 0) { if (source[i] === '\\') { i += 2; continue; } if (source[i] === '[') bracketDepth++; if (source[i] === ']') bracketDepth--; i++; } if (bracketDepth === 0 && i < source.length && (source[i] === '(' || source[i] === '[')) { var closingChar = source[i] === '(' ? ')' : ']'; var parenDepth = 1; i++; while (i < source.length && parenDepth > 0) { if (source[i] === '\\') { i += 2; continue; } if (source[i] === '(' && closingChar === ')') parenDepth++; if (source[i] === closingChar) parenDepth--; i++; } if (parenDepth === 0) return i; } return -1; } function matchInlineFormatting(source, state) { if (!state || !state.inline && !state.simple) return null; var c = source[0]; if (c !== '*' && c !== '_' && c !== '~' && c !== '=') return null; var delimiter = ''; var startLength = 0; var tag = ''; for (var i = 0; i < 6; i++) { var d = DELS[i][0]; if (source.startsWith(d) && source.length >= d.length * 2) { delimiter = d; startLength = d.length; tag = DELS[i][1]; break; } } if (!delimiter) return null; var pos = startLength; var inCode = false; var inHTMLTag = false; var inHTMLQuote = ''; var htmlDepth = 0; var content = ''; var lastWasEscape = false; var lastChar = ''; while (pos < source.length) { var _char = source[pos]; if (lastWasEscape) { content += _char; lastWasEscape = false; lastChar = _char; pos++; continue; } if (_char === '\\') { content += _char; lastWasEscape = true; lastChar = _char; pos++; continue; } if (_char === '`' && htmlDepth === 0) { inCode = !inCode; content += _char; lastChar = _char; pos++; continue; } if (_char === '[' && !inCode && htmlDepth === 0) { var linkEnd = skipLinkOrImage(source, pos); if (linkEnd !== -1) { content += source.slice(pos, linkEnd); pos = linkEnd; lastChar = source[linkEnd - 1]; continue; } } if (inHTMLTag) { content += _char; if (inHTMLQuote) { if (_char === inHTMLQuote) inHTMLQuote = ''; } else if (_char === '"' || _char === "'") { inHTMLQuote = _char; } else if (_char === '>') { inHTMLTag = false; } lastChar = _char; pos++; continue; } if (_char === '<' && !inCode) { var nextChar = source[pos + 1]; var tagEnd = source.indexOf('>', pos); if (tagEnd !== -1) { var tagContent = source.slice(pos, tagEnd + 1); var isSelfClosing = tagContent.endsWith('/>'); if (nextChar === '/') { htmlDepth = Math.max(0, htmlDepth - 1); } else if (!isSelfClosing) { htmlDepth++; } } inHTMLTag = true; content += _char; lastChar = _char; pos++; continue; } if (_char === '\n' && lastChar === '\n' && !inCode && htmlDepth === 0) { return null; } if (!inCode && htmlDepth === 0) { var delimiterRunLength = 0; while (pos + delimiterRunLength < source.length && source[pos + delimiterRunLength] === delimiter[0]) { delimiterRunLength++; } if (delimiterRunLength >= startLength) { if (startLength !== 1 || delimiter !== '*' && delimiter !== '_' || source[pos - 1] !== delimiter && source[pos + 1] !== delimiter) { var result = [source.slice(0, pos + delimiterRunLength), tag, content + source.slice(pos + startLength, pos + delimiterRunLength)]; result.index = 0; result.input = source; return result; } } } content += _char; lastChar = _char; pos++; } return null; } var _excluded = ["children", "options"]; var RuleType = RuleType$1; var Priority = { /** * anything that must scan the tree before everything else */ MAX: 0, /** * scans for block-level constructs */ HIGH: 1, /** * inline w/ more priority than other inline */ MED: 2, /** * inline elements */ LOW: 3, /** * bare text and stuff that is considered leftovers */ MIN: 4 }; /** TODO: Drop for React 16? */ var ATTRIBUTE_TO_JSX_PROP_MAP = ['allowFullScreen', 'allowTransparency', 'autoComplete', 'autoFocus', 'autoPlay', 'cellPadding', 'cellSpacing', 'charSet', 'classId', 'colSpan', 'contentEditable', 'contextMenu', 'crossOrigin', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget', 'frameBorder', 'hrefLang', 'inputMode', 'keyParams', 'keyType', 'marginHeight', 'marginWidth', 'maxLength', 'mediaGroup', 'minLength', 'noValidate', 'radioGroup', 'readOnly', 'rowSpan', 'spellCheck', 'srcDoc', 'srcLang', 'srcSet', 'tabIndex', 'useMap'].reduce(function (obj, x) { obj[x.toLowerCase()] = x; return obj; }, { "class": 'className', "for": 'htmlFor' }); var namedCodesToUnicode = { amp: "&", apos: "'", gt: ">", lt: "<", nbsp: "\xA0", quot: "\u201C" }; var DO_NOT_PROCESS_HTML_ELEMENTS = ['style', 'script', 'pre']; var ATTRIBUTES_TO_SANITIZE = ['src', 'href', 'data', 'formAction', 'srcDoc', 'action']; /** * the attribute extractor regex looks for a valid attribute name, * followed by an equal sign (whitespace around the equal sign is allowed), followed * by one of the following: * * 1. a single quote-bounded string, e.g. 'foo' * 2. a double quote-bounded string, e.g. "bar" * 3. an interpolation, e.g. {something} * * JSX can be be interpolated into itself and is passed through the compiler using * the same options and setup as the current run. * * <Something children={<SomeOtherThing />} /> * ================== * ↳ children: [<SomeOtherThing />] * * Otherwise, interpolations are handled as strings or simple booleans * unless HTML syntax is detected. * * <Something color={green} disabled={true} /> * ===== ==== * ↓ ↳ disabled: true * ↳ color: "green" * * Numbers are not parsed at this time due to complexities around int, float, * and the upcoming bigint functionality that would make handling it unwieldy. * Parse the string in your component as desired. * * <Something someBigNumber={123456789123456789} /> * ================== * ↳ someBigNumber: "123456789123456789" */ var ATTR_EXTRACTOR_R = /([-A-Z0-9_:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|(?:\{((?:\\.|{[^}]*?}|[^}])*)\})))?/gi; /** TODO: Write explainers for each of these */ var BLOCK_END_R = /\n{2,}$/; var BLOCKQUOTE_R = /^(\s*>[\s\S]*?)(?=\n\n|$)/; var BLOCKQUOTE_TRIM_LEFT_MULTILINE_R = /^ *> ?/gm; var BLOCKQUOTE_ALERT_R = /^(?:\[!([^\]]*)\]\n)?([\s\S]*)/; var BREAK_LINE_R = /^ {2,}\n/; var BREAK_THEMATIC_R = /^(?:([-*_])( *\1){2,}) *(?:\n *)+\n/; var CODE_BLOCK_FENCED_R = /^(?: {1,3})?(`{3,}|~{3,}) *(\S+)? *([^\n]*?)?\n([\s\S]*?)(?:\1\n?|$)/; var CODE_BLOCK_R = /^(?: {4}[^\n]+\n*)+(?:\n *)+\n?/; var CODE_INLINE_R = /^(`+)((?:\\`|(?!\1)`|[^`])+)\1/; var CONSECUTIVE_NEWLINE_R = /^(?:\n *)*\n/; var CR_NEWLINE_R = /\r\n?/g; /** * Matches footnotes on the format: * * [^key]: value * * Matches multiline footnotes * * [^key]: row * row * row * * And empty lines in indented multiline footnotes * * [^key]: indented with * row * * row * * Explanation: * * 1. Look for the starting tag, eg: [^key] * ^\[\^([^\]]+)] * * 2. The first line starts with a colon, and continues for the rest of the line * :(.*) * * 3. Parse as many additional lines as possible. Matches new non-empty lines that doesn't begin with a new footnote definition. * (\n(?!\[\^).+) * * 4. ...or allows for repeated newlines if the next line begins with at least four whitespaces. * (\n+ {4,}.*) */ var FOOTNOTE_R = /^\[\^([^\]]+)](:(.*)((\n+ {4,}.*)|(\n(?!\[\^).+))*)/; var FOOTNOTE_REFERENCE_R = /^\[\^([^\]]+)]/; var FORMFEED_R = /\f/g; var FRONT_MATTER_R = /^---[ \t]*\n(.|\n)*\n---[ \t]*\n/; var GFM_TASK_R = /^\[(x|\s)\]/; var HEADING_R = /^(#{1,6}) *([^\n]+?)(?: +#*)?(?:\n *)*(?:\n|$)/; var HEADING_ATX_COMPLIANT_R = /^ *(#{1,6}) +([^\n]+?)(?: +#*)?(?:\n *)*(?:\n|$)/; var HEADING_SETEXT_R = /^([^\n]+)\n *(=|-)\2{2,} *\n/; var HTML_BLOCK_ELEMENT_START_R = /^<([a-z][^ >/]*) ?((?:[^>]*[^/])?)>/i; function matchHTMLBlock(source) { var m = HTML_BLOCK_ELEMENT_START_R.exec(source); if (!m) return null; var tagName = m[1]; var tagLower = tagName.toLowerCase(); var openTagLen = tagLower.length + 1; var pos = m[0].length; if (source[pos] === '\n') pos++; var contentStart = pos; var contentEnd = pos; var depth = 1; var sourceLen = source.length; while (depth > 0) { var idx = source.indexOf('<', pos); if (idx === -1) return null; var openIdx = -1; var closeIdx = -1; if (source[idx + 1] === '/') { closeIdx = idx; } else if (source[idx + 1] === tagLower[0] || source[idx + 1] === tagName[0]) { var match = true; for (var i = 0; i < tagLower.length; i++) { var c = source[idx + 1 + i]; if (c !== tagLower[i] && c !== tagName[i]) { match = false; break; } } if (match && (source[idx + openTagLen] === ' ' || source[idx + openTagLen] === '>')) { openIdx = idx; } } if (openIdx === -1 && closeIdx === -1) { pos = idx + 1; continue; } if (openIdx !== -1 && (closeIdx === -1 || openIdx < closeIdx)) { pos = openIdx + openTagLen + 1; depth++; } else { var p = closeIdx + 2; while (p < sourceLen) { var _c = source[p]; if (_c !== ' ' && _c !== '\t' && _c !== '\n' && _c !== '\r') break; p++; } if (p + tagLower.length > sourceLen) return null; var _match = true; for (var _i = 0; _i < tagLower.length; _i++) { var _c2 = source[p + _i]; if (_c2 !== tagLower[_i] && _c2 !== tagName[_i]) { _match = false; break; } } if (!_match) { pos = p; continue; } p += tagLower.length; while (p < sourceLen) { var _c3 = source[p]; if (_c3 !== ' ' && _c3 !== '\t' && _c3 !== '\n' && _c3 !== '\r') break; p++; } if (p >= sourceLen || source[p] !== '>') { pos = p; continue; } contentEnd = closeIdx; pos = p + 1; depth--; } } var trailingNl = 0; while (pos + trailingNl < sourceLen && source[pos + trailingNl] === '\n') trailingNl++; return [source.slice(0, pos + trailingNl), tagName, m[2], source.slice(contentStart, contentEnd)]; } var HTML_CHAR_CODE_R = /&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});/gi; var HTML_COMMENT_R = /^<!--[\s\S]*?(?:-->)/; /** * borrowed from React 15(https://github.com/facebook/react/blob/894d20744cba99383ffd847dbd5b6e0800355a5c/src/renderers/dom/shared/HTMLDOMPropertyConfig.js) */ var HTML_CUSTOM_ATTR_R = /^(data|aria|x)-[a-z_][a-z\d_.-]*$/; var HTML_SELF_CLOSING_ELEMENT_R = /^ *<([a-z][a-z0-9:]*)(?:\s+((?:<.*?>|[^>])*))?\/?>(?!<\/\1>)(\s*\n)?/i; var INTERPOLATION_R = /^\{.*\}$/; var LINK_AUTOLINK_BARE_URL_R = /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/; var LINK_AUTOLINK_R = /^<([^ >]+[:@\/][^ >]+)>/; var CAPTURE_LETTER_AFTER_HYPHEN = /-([a-z])?/gi; var NP_TABLE_R = /^(\|.*)\n(?: *(\|? *[-:]+ *\|[-| :]*)\n((?:.*\|.*\n)*))?\n?/; var PARAGRAPH_R = /^[^\n]+(?: \n|\n{2,})/; var REFERENCE_IMAGE_OR_LINK = /^\[([^\]]*)\]:\s+<?([^\s>]+)>?\s*("([^"]*)")?/; var REFERENCE_IMAGE_R = /^!\[([^\]]*)\] ?\[([^\]]*)\]/; var REFERENCE_LINK_R = /^\[([^\]]*)\] ?\[([^\]]*)\]/; var SHOULD_RENDER_AS_BLOCK_R = /(\n|^[-*]\s|^#|^ {2,}|^-{2,}|^>\s)/; var TAB_R = /\t/g; var TABLE_TRIM_PIPES = /(^ *\||\| *$)/g; var TABLE_CENTER_ALIGN = /^ *:-+: *$/; var TABLE_LEFT_ALIGN = /^ *:-+ *$/; var TABLE_RIGHT_ALIGN = /^ *-+: *$/; /** * Special case for shortcodes like :big-smile: or :emoji: */ var SHORTCODE_R = /^(:[a-zA-Z0-9-_]+:)/; var TEXT_ESCAPED_R = /^\\([^0-9A-Za-z\s])/; var UNESCAPE_R = /\\([^0-9A-Za-z\s])/g; /** * Always take the first character, then eagerly take text until a double space * (potential line break) or some markdown-like punctuation is reached. */ var TEXT_PLAIN_R = /^[\s\S](?:(?! \n|[0-9]\.|http)[^=*_~\-\n:<`\\\[!])*/; var TRIM_STARTING_NEWLINES = /^\n+/; var HTML_LEFT_TRIM_AMOUNT_R = /^([ \t]*)/; var ORDERED = 1; var UNORDERED = 2; var LIST_LOOKBEHIND_R = /(?:^|\n)( *)$/; // recognize a `*` `-`, `+`, `1.`, `2.`... list bullet var ORDERED_LIST_BULLET = '(?:\\d+\\.)'; var UNORDERED_LIST_BULLET = '(?:[*+-])'; function generateListItemPrefix(type) { return '( *)(' + (type === ORDERED ? ORDERED_LIST_BULLET : UNORDERED_LIST_BULLET) + ') +'; } // recognize the start of a list item: // leading space plus a bullet plus a space (` * `) var ORDERED_LIST_ITEM_PREFIX = generateListItemPrefix(ORDERED); var UNORDERED_LIST_ITEM_PREFIX = generateListItemPrefix(UNORDERED); function generateListItemPrefixRegex(type) { return new RegExp('^' + (type === ORDERED ? ORDERED_LIST_ITEM_PREFIX : UNORDERED_LIST_ITEM_PREFIX)); } var ORDERED_LIST_ITEM_PREFIX_R = generateListItemPrefixRegex(ORDERED); var UNORDERED_LIST_ITEM_PREFIX_R = generateListItemPrefixRegex(UNORDERED); function generateListItemRegex(type) { // recognize an individual list item: // * hi // this is part of the same item // // as is this, which is a new paragraph in the same item // // * but this is not part of the same item return new RegExp('^' + (type === ORDERED ? ORDERED_LIST_ITEM_PREFIX : UNORDERED_LIST_ITEM_PREFIX) + '[^\\n]*(?:\\n' + '(?!\\1' + (type === ORDERED ? ORDERED_LIST_BULLET : UNORDERED_LIST_BULLET) + ' )[^\\n]*)*(\\n|$)', 'gm'); } var ORDERED_LIST_ITEM_R = generateListItemRegex(ORDERED); var UNORDERED_LIST_ITEM_R = generateListItemRegex(UNORDERED); // check whether a list item has paragraphs: if it does, // we leave the newlines at the end function generateListRegex(type) { var bullet = type === ORDERED ? ORDERED_LIST_BULLET : UNORDERED_LIST_BULLET; return new RegExp('^( *)(' + bullet + ') ' + '[\\s\\S]+?(?:\\n{2,}(?! )' + '(?!\\1' + bullet + ' (?!' + 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*$)'); } var ORDERED_LIST_R = generateListRegex(ORDERED); var UNORDERED_LIST_R = generateListRegex(UNORDERED); function generateListRule(h, type) { var ordered = type === ORDERED; var LIST_R = ordered ? ORDERED_LIST_R : UNORDERED_LIST_R; var LIST_ITEM_R = ordered ? ORDERED_LIST_ITEM_R : UNORDERED_LIST_ITEM_R; var LIST_ITEM_PREFIX_R = ordered ? ORDERED_LIST_ITEM_PREFIX_R : UNORDERED_LIST_ITEM_PREFIX_R; return { _qualify: function _qualify(source) { return LIST_ITEM_PREFIX_R.test(source); }, _match: allowInline(function (source, state) { // 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. var isStartOfLine = LIST_LOOKBEHIND_R.exec(state.prevCapture); var isListAllowed = state.list || !state.inline && !state.simple; if (isStartOfLine && isListAllowed) { source = isStartOfLine[1] + source; return LIST_R.exec(source); } else { return null; } }), _order: Priority.HIGH, _parse: function _parse(capture, parse, state) { var bullet = capture[2]; var start = ordered ? +bullet : undefined; var 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); var firstPrefixMatch = LIST_ITEM_PREFIX_R.exec(items[0]); var space = firstPrefixMatch ? firstPrefixMatch[0].length : 0; var spaceRegex = new RegExp('^ {1,' + space + '}', 'gm'); var lastItemWasAParagraph = false; var itemContent = items.map(function (item, i) { // Before processing the item, we need a couple things var 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 var isLastItem = i === items.length - 1; var containsBlocks = includes(content, '\n\n'); // 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). var thisItemIsAParagraph = containsBlocks || isLastItem && lastItemWasAParagraph; lastItemWasAParagraph = thisItemIsAParagraph; // backup our state for delta afterwards. We're going to // want to set state.list to true, and state.inline depending // on our list's looseness. var oldStateInline = state.inline; var oldStateList = state.list; state.list = true; // Parse inline if we're in a tight list, or block if we're in // a loose list. var adjustedContent; if (thisItemIsAParagraph) { state.inline = false; adjustedContent = trimEnd(content) + '\n\n'; } else { state.inline = true; adjustedContent = trimEnd(content); } var result = parse(adjustedContent, state); // Restore our state before returning state.inline = oldStateInline; state.list = oldStateList; return result; }); return { items: itemContent, ordered: ordered, start: start }; } }; } var LINK_INSIDE = '(?:\\[[^\\[\\]]*(?:\\[[^\\[\\]]*\\][^\\[\\]]*)*\\]|[^\\[\\]])*'; var LINK_HREF_AND_TITLE = '\\s*<?((?:\\([^)]*\\)|[^\\s\\\\]|\\\\.)*?)>?(?:\\s+[\'"]([\\s\\S]*?)[\'"])?\\s*'; var LINK_R = new RegExp('^\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)'); var IMAGE_R = /^!\[(.*?)\]\( *((?:\([^)]*\)|[^() ])*) *"?([^)"]*)?"?\)/; function isString(value) { return typeof value === 'string'; } function trimEnd(str) { var end = str.length; while (end > 0 && str[end - 1] <= ' ') end--; return str.slice(0, end); } function startsWith(str, prefix) { return str.startsWith(prefix); } function includes(str, search) { return str.indexOf(search) !== -1; } function qualifies(source, state, qualify) { if (Array.isArray(qualify)) { for (var i = 0; i < qualify.length; i++) { if (startsWith(source, qualify[i])) return true; } return false; } return qualify(source, state); } /** Remove symmetrical leading and trailing quotes */ function unquote(str) { var first = str[0]; if ((first === '"' || first === "'") && str.length >= 2 && str[str.length - 1] === first) { return str.slice(1, -1); } return str; } // 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(); } 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'; } return null; } function parseTableRow(source, parse, state, tableOutput) { var prevInTable = state.inTable; state.inTable = true; var cells = [[]]; var acc = ''; function flush() { if (!acc) return; var cell = cells[cells.length - 1]; cell.push.apply(cell, parse(acc, state)); acc = ''; } source.trim() // isolate situations where a pipe should be ignored (inline code, escaped, etc) .split(/(`[^`]*`|\\\||\|)/).filter(Boolean).forEach(function (fragment, i, arr) { if (fragment.trim() === '|') { flush(); if (tableOutput) { if (i !== 0 && i !== arr.length - 1) { // Split the current row cells.push([]); } return; } } acc += fragment; }); flush(); state.inTable = prevInTable; return cells; } function parseTableAlign(source /*, parse, state*/) { var alignText = source.replace(TABLE_TRIM_PIPES, '').split('|'); return alignText.map(parseTableAlignCapture); } function parseTableCells(source, parse, state) { var rowsText = source.trim().split('\n'); return rowsText.map(function (rowText) { return parseTableRow(rowText, parse, state, true); }); } function parseTable(capture, parse, state) { /** * The table syntax makes some other parsing angry so as a bit of a hack even if alignment and/or cell rows are missing, * we'll still run a detected first row through the parser and then just emit a paragraph. */ state.inline = true; var align = capture[2] ? parseTableAlign(capture[2]) : []; var cells = capture[3] ? parseTableCells(capture[3], parse, state) : []; var header = parseTableRow(capture[1], parse, state, !!cells.length); state.inline = false; return cells.length ? { align: align, cells: cells, header: header, type: RuleType.table } : { children: header, type: RuleType.paragraph }; } function getTableStyle(node, colIndex) { return node.align[colIndex] == null ? {} : { textAlign: node.align[colIndex] }; } /** TODO: remove for react 16 */ function normalizeAttributeKey(key) { var 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(); }); } return key; } function parseStyleAttribute(styleString) { var styles = []; if (!styleString) return styles; var buffer = ''; var depth = 0; var quoteChar = ''; for (var i = 0; i < styleString.length; i++) { var _char = styleString[i]; if (_char === '"' || _char === "'") { if (!quoteChar) { quoteChar = _char; depth++; } else if (_char === quoteChar) { quoteChar = ''; depth--; } } else if (_char === '(' && buffer.endsWith('url')) { depth++; } else if (_char === ')' && depth > 0) { depth--; } else if (_char === ';' && depth === 0) { var _colonIndex = buffer.indexOf(':'); if (_colonIndex > 0) { styles.push([buffer.slice(0, _colonIndex).trim(), buffer.slice(_colonIndex + 1).trim()]); } buffer = ''; continue; } buffer += _char; } var colonIndex = buffer.indexOf(':'); if (colonIndex > 0) { styles.push([buffer.slice(0, colonIndex).trim(), buffer.slice(colonIndex + 1).trim()]); } return styles; } function attributeValueToJSXPropValue(tag, key, value, sanitizeUrlFn) { if (key === 'style') { return parseStyleAttribute(value).reduce(function (styles, _ref) { var key = _ref[0], value = _ref[1]; styles[key.replace(/(-[a-z])/g, function (substr) { return substr[1].toUpperCase(); })] = sanitizeUrlFn(value, tag, key); return styles; }, {}); } if (ATTRIBUTES_TO_SANITIZE.indexOf(key) !== -1) { return sanitizeUrlFn(unescape(value), tag, key); } if (value.match(INTERPOLATION_R)) { value = unescape(value.slice(1, value.length - 1)); } return value === 'true' ? true : value === 'false' ? false : value; } function normalizeWhitespace(source) { return source.replace(CR_NEWLINE_R, '\n').replace(FORMFEED_R, '').replace(TAB_R, ' '); } /** * Creates a parser for a given set of rules, with the precedence * specified as a list of rules. * * @rules: an object containing * rule type -> {match, order, parse} objects * (lower order is higher precedence) * (Note: `order` is added to defaultRules after creation so that * the `order` of defaultRules in the source matches the `order` * of defaultRules in terms of `order` fields.) * * @returns The resulting parse function, with the following parameters: * @source: the input source string to be parsed * @state: an optional object to be threaded through parse * calls. Allows clients to add stateful operations to * parsing, such as keeping track of how many levels deep * some nesting is. For an example use-case, see passage-ref * parsing in src/widgets/passage/passage-markdown.jsx */ function parserFor(rules) { var ruleList = Object.keys(rules); // Sorts rules in order of increasing order, then // ascending rule name in case of ties. ruleList.sort(function (a, b) { return rules[a]._order - rules[b]._order || (a < b ? -1 : 1); }); function nestedParse(source, state) { var result = []; state.prevCapture = state.prevCapture || ''; if (source.trim()) { while (source) { var i = 0; while (i < ruleList.length) { var ruleType = ruleList[i]; var rule = rules[ruleType]; if (rule._qualify && !qualifies(source, state, rule._qualify)) { i++; continue; } var capture = rule._match(source, state); if (capture && capture[0]) { source = source.substring(capture[0].length); var parsed = rule._parse(capture, nestedParse, state); state.prevCapture += capture[0]; if (!parsed.type) parsed.type = ruleType; result.push(parsed); break; } i++; } } } // reset on exit state.prevCapture = ''; return result; } return function (source, state) { return nestedParse(normalizeWhitespace(source), state); }; } /** * Marks a matcher function as eligible for being run inside an inline context; * allows us to do a little less work in the nested parser. */ function allowInline(fn) { fn.inline = 1; return fn; } // Creates a match function for an inline scoped or simple element from a regex function inlineRegex(regex) { return allowInline(function match(source, state) { if (state.inline) { return regex.exec(source); } else { return null; } }); } // basically any inline element except links function simpleInlineRegex(regex) { return allowInline(function match(source, state) { if (state.inline || state.simple) { return regex.exec(source); } else { return null; } }); } // Creates a match function for a block scoped element from a regex function blockRegex(regex) { return function match(source, state) { if (state.inline || state.simple) { return null; } else { return regex.exec(source); } }; } // Creates a match function from a regex, ignoring block/inline scope function anyScopeRegex(regex) { return allowInline(function match(source /*, state*/) { return regex.exec(source); }); } var SANITIZE_R = /(javascript|vbscript|data(?!:image)):/i; function sanitizer(input) { try { var decoded = decodeURIComponent(input).replace(/[^A-Za-z0-9/:]/g, ''); if (SANITIZE_R.test(decoded)) { if ("production" !== 'production') ; 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 input; } function unescape(rawString) { return rawString ? rawString.replace(UNESCAPE_R, '$1') : rawString; } /** * Everything inline, including links. */ function parseInline(parse, children, state) { var isCurrentlyInline = state.inline || false; var isCurrentlySimple = state.simple || false; state.inline = true; state.simple = true; var result = parse(children, state); state.inline = isCurrentlyInline; state.simple = isCurrentlySimple; return result; } /** * Anything inline that isn't a link. */ function parseSimpleInline(parse, children, state) { var isCurrentlyInline = state.inline || false; var isCurrentlySimple = state.simple || false; state.inline = false; state.simple = true; var result = parse(children, state); state.inline = isCurrentlyInline; state.simple = isCurrentlySimple; return result; } function parseBlock(parse, children, state) { var isCurrentlyInline = state.inline || false; state.inline = false; var result = parse(children, state); state.inline = isCurrentlyInline; return result; } var parseCaptureInline = function parseCaptureInline(capture, parse, state) { return { children: parseInline(parse, capture[2], state) }; }; function captureNothing() { return {}; } function render(node, output, state, h, sanitize, slug, refs) { switch (node.type) { case RuleType.blockQuote: { var props = { key: state.key }; if (node.alert) { props.className = 'markdown-alert-' + slug(node.alert.toLowerCase(), slugify); node.children.unshift({ attrs: {}, children: [{ type: RuleType.text, text: node.alert }], noInnerParse: true, type: RuleType.htmlBlock, tag: 'header' }); } return h('blockquote', props, output(node.children, state)); } case RuleType.breakLine: return h("br", { key: state.key }); case RuleType.breakThematic: return h("hr", { key: state.key }); case RuleType.codeBlock: return h("pre", { key: state.key }, h("code", _extends({}, node.attrs, { className: node.lang ? "lang-" + node.lang : '' }), node.text)); case RuleType.codeInline: return h("code", { key: state.key }, node.text); case RuleType.footnoteReference: return h("a", { key: state.key, href: sanitize(node.target, 'a', 'href') }, h("sup", { key: state.key }, node.text)); case RuleType.gfmTask: return h("input", { checked: node.completed, key: state.key, readOnly: true, type: "checkbox" }); case RuleType.heading: return h("h" + node.level, { id: node.id, key: state.key }, output(node.children, state)); case RuleType.htmlBlock: return h(node.tag, _extends({ key: state.key }, node.attrs), node.text || (node.children ? output(node.children, state) : '')); case RuleType.htmlSelfClosing: return h(node.tag, _extends({}, node.attrs, { key: state.key })); case RuleType.image: return h("img", { key: state.key, alt: node.alt || undefined, title: node.title || undefined, src: sanitize(node.target, 'img', 'src') }); case RuleType.link: return h("a", { key: state.key, href: sanitize(node.target, 'a', 'href'), title: node.title }, output(node.children, state)); case RuleType.refImage: return refs[node.ref] ? h("img", { key: state.key, alt: node.alt, src: sanitize(refs[node.ref].target, 'img', 'src'), title: refs[node.ref].title }) : null; case RuleType.refLink: return refs[node.ref] ? h("a", { key: state.key, href: sanitize(refs[node.ref].target, 'a', 'href'), title: refs[node.ref].title }, output(node.children, state)) : h("span", { key: state.key }, node.fallbackChildren); case RuleType.table: { var table = node; return h("table", { key: state.key }, h("thead", null, h("tr", null, table.header.map(function generateHeaderCell(content, i) { return h("th", { key: i, style: getTableStyle(table, i) }, output(content, state)); }))), h("tbody", null, table.cells.map(function generateTableRow(row, i) { return h("tr", { key: i }, row.map(function generateTableCell(content, c) { return h("td", { key: c, style: getTableStyle(table, c) }, output(content, state)); })); }))); } case RuleType.text: return node.text; case RuleType.textFormatted: return h(node.tag, { key: state.key }, output(node.children, state)); case RuleType.orderedList: case RuleType.unorderedList: { var Tag = node.ordered ? 'ol' : 'ul'; return h(Tag, { key: state.key, start: node.type === RuleType.orderedList ? node.start : undefined }, node.items.map(function generateListItem(item, i) { return h("li", { key: i }, output(item, state)); })); } case RuleType.newlineCoalescer: return '\n'; case RuleType.paragraph: return h("p", { key: state.key }, output(node.children, state)); default: return null; } } function createRenderer(userRender, h, sanitize, slug, refs) { function renderRule(ast, renderer, state) { var nodeRender = function nodeRender() { return render(ast, renderer, state, h, sanitize, slug, refs); }; return userRender ? userRender(nodeRender, ast, renderer, state) : nodeRender(); } // Return plain text as fallback to prevent stack overflow function handleStackOverflow(ast) { if (Array.isArray(ast)) { return ast.map(function (node) { return 'text' in node ? node.text : ''; }); } return 'text' in ast ? ast.text : ''; } return function patchedRender(ast, state) { if (state === void 0) { state = {}; } // Track render depth to prevent stack overflow from extremely deep nesting var currentDepth = (state.renderDepth || 0) + 1; var MAX_RENDER_DEPTH = 2500; if (currentDepth > MAX_RENDER_DEPTH) { return handleStackOverflow(ast); } state.renderDepth = currentDepth; try { if (Array.isArray(ast)) { var oldKey = state.key; var _result = []; // map nestedOutput over the ast, except group any text // nodes together into a single string output. var lastWasString = false; for (var i = 0; i < ast.length; i++) { state.key = i; var nodeOut = patchedRender(ast[i], state); var _isString = isString(nodeOut); if (_isString && lastWasString) { _result[_result.length - 1] += nodeOut; } else if (nodeOut !== null) { _result.push(nodeOut); } lastWasString = _isString; } state.key = oldKey; state.renderDepth = currentDepth - 1; return _result; } var result = renderRule(ast, patchedRender, state); state.renderDepth = currentDepth - 1; return result; } catch (error) { // Catch stack overflow or other unexpected errors if (error instanceof RangeError && error.message.includes('Maximum call stack')) { return handleStackOverflow(ast); } // Re-throw other errors throw error; } }; } function cx() { return [].slice.call(arguments).filter(Boolean).join(' '); } function get(src, path, fb) { var ptr = src; var frags = path.split('.'); while (frags.length) { ptr = ptr[frags[0]]; if (ptr === undefined) break;else frags.shift(); } return ptr || fb; } function getTag(tag, overrides) { var override = get(overrides, tag); if (!override) return tag; return typeof override === 'function' || typeof override === 'object' && 'render' in override ? override : get(overrides, tag + ".component", tag); } function attrStringToMap(tag, str, sanitize, compile) { if (!str || !str.trim()) return null; var attributes = str.match(ATTR_EXTRACTOR_R); if (!attributes) return null; return attributes.reduce(function (map, raw) { var delimiterIdx = raw.indexOf('='); if (delimiterIdx !== -1) { var key = normalizeAttributeKey(raw.slice(0, delimiterIdx)).trim(); var mappedKey = ATTRIBUTE_TO_JSX_PROP_MAP[key] || key; if (mappedKey === 'ref') return map; var normalizedValue = map[mappedKey] = attributeValueToJSXPropValue(tag, key, unquote(raw.slice(delimiterIdx + 1).trim()), sanitize); if (typeof normalizedValue === 'string' && (HTML_BLOCK_ELEMENT_START_R.test(normalizedValue) || HTML_SELF_CLOSING_ELEMENT_R.test(normalizedValue))) { map[mappedKey] = compile(normalizedValue.trim()); } } else if (raw !== 'style') { map[ATTRIBUTE_TO_JSX_PROP_MAP[raw] || raw] = true; } return map; }, {}); } function some(regexes, input) { for (var i = 0; i < regexes.length; i++) { if (regexes[i].test(input)) { return true; } } return false; } function compiler(markdown, options) { var _rules; if (markdown === void 0) { markdown = ''; } if (options === void 0) { options = {}; } options.overrides = options.overrides || {}; options.namedCodesToUnicode = options.namedCodesToUnicode ? _extends({}, namedCodesToUnicode, options.namedCodesToUnicode) : namedCodesToUnicode; var slug = options.slugify || slugify; var sanitize = options.sanitizer || sanitizer; var createElement = options.createElement || React.createElement; var NON_PARAGRAPH_BLOCK_SYNTAXES = [BLOCKQUOTE_R, CODE_BLOCK_FENCED_R, CODE_BLOCK_R, options.enforceAtxHeadings ? HEADING_ATX_COMPLIANT_R : HEADING_R, HEADING_SETEXT_R, NP_TABLE_R, ORDERED_LIST_R, UNORDERED_LIST_R]; var BLOCK_SYNTAXES = [].concat(NON_PARAGRAPH_BLOCK_SYNTAXES, [PARAGRAPH_R, HTML_BLOCK_ELEMENT_START_R, HTML_COMMENT_R, HTML_SELF_CLOSING_ELEMENT_R]); function matchParagraph(source, state) { if (state.inline || state.simple || state.inHTML && !includes(source, '\n\n') && !includes(state.prevCapture, '\n\n')) return null; var match = ''; var start = 0; while (true) { var nlIdx = source.indexOf('\n', start); var line = source.slice(start, nlIdx === -1 ? undefined : nlIdx + 1); var c = source[start]; if ((c === '>' || c === '#' || c === '|' || c === '`' || c === '~' || c === '*' || c === '-' || c === '_' || c === ' ') && some(NON_PARAGRAPH_BLOCK_SYNTAXES, line)) break; match += line; if (nlIdx === -1 || !line.trim()) break; start = nlIdx + 1; } var captured = trimEnd(match); if (captured === '') return null; return [match,, captured]; } // JSX custom pragma // eslint-disable-next-line no-unused-vars function h( // locally we always will render a known string tag tag, props) { var overrideProps = get(options.overrides, tag + ".props", {}); return createElement.apply(void 0, [getTag(tag, options.overrides), _extends({}, props, overrideProps, { className: cx(props == null ? void 0 : props.className, overrideProps.className) || undefined })].concat([].slice.call(arguments, 2))); } function compile(input) { input = input.replace(FRONT_MATTER_R, ''); var 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; } var astNodes = parser(inline ? input : trimEnd(input).replace(TRIM_STARTING_NEWLINES, '') + "\n\n", { inline: inline }); if (options.ast) { return astNodes; } var arr = emitter(astNodes); while (isString(arr[arr.length - 1]) && !arr[arr.length - 1].trim()) { arr.pop(); } if (footnotes.length) { arr.push(h("footer", { key: "footer" }, footnotes.map(function createFootnote(def) { return h("div", { id: slug(def.identifier, slugify), key: def.identifier }, def.identifier, emitter(parser(def.footnote, { inline: true }))); }))); } if (options.wrapper === null) { return arr; } var wrapper = options.wrapper || (inline ? 'span' : 'div'); var jsx; if (arr.length > 1 || options.forceWrapper) { jsx = arr; } else if (arr.length === 1) { jsx = arr[0]; // TODO: remove this for React 16 if (typeof jsx === 'string') { return h("span", { key: "outer" }, jsx); } else { return jsx; } } else { // TODO: return null for React 16 jsx = null; } return createElement(wrapper, _extends({ key: 'outer' }, options.wrapperProps), jsx); } var footnotes = []; var refs = {}; /** * each rule's react() output function goes through our custom * h() JSX pragma; this allows the override functionality to be * automatically applied */ // @ts-ignore var rules = (_rules = {}, _rules[RuleType.blockQuote] = { _qualify: ['>'], _match: blockRegex(BLOCKQUOTE_R), _order: Priority.HIGH, _parse: function _parse(capture, parse, state) { var _capture$0$replace$ma = capture[0].replace(BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, '').match(BLOCKQUOTE_ALERT_R), alert = _capture$0$replace$ma[1], content = _capture$0$replace$ma[2]; return { alert: alert, children: parse(content, state) }; } }, _rules[RuleType.breakLine] = { _qualify: [' '], _match: inlineRegex(BREAK_LINE_R), _order: Priority.HIGH, _parse: captureNothing }, _rules[RuleType.breakThematic] = { _qualify: function _qualify(source, state) { // Only attempt in block mode if (state.inline || state.simple) return false; var c = source[0]; return c === '-' || c === '*' || c === '_'; }, _match: blockRegex(BREAK_THEMATIC_R), _order: Priority.HIGH, _parse: captureNothing }, _rules[RuleType.codeBlock] = { _qualify: [' '], _match: blockRegex(CODE_BLOCK_R), _order: Priority.MAX, _parse: function _parse(capture /*, parse, state*/) { return { lang: undefined, text: unescape(trimEnd(capture[0].replace(/^ {4}/gm, ''))) }; } }, _rules[RuleType.codeFenced] = { _qualify: ['```', '~~~'], _match: blockRegex(CODE_BLOCK_FENCED_R), _order: Priority.MAX, _parse: function _parse(capture /*, parse, state*/) { return { // if capture[3] it's additional metadata attrs: attrStringToMap('code', capture[3] || '', sanitize, compile), lang: capture[2] || undefined, text: capture[4], type: RuleType.codeBlock }; } }, _rules[RuleType.codeInline] = { _qualify: ['`'], _match: simpleInlineRegex(CODE_INLINE_R), _order: Priority.LOW, _parse: function _parse(capture /*, parse, state*/) { return { text: unescape(capture[2]) }; } }, _rules[RuleType.footnote] = { _qualify: ['[^'], _match: blockRegex(FOOTNOTE_R), _order: Priority.MAX, _parse: function _parse(capture /*, parse, state*/) { footnotes.push({ footnote: capture[2], identifier: capture[1] }); return {}; } }, _rules[RuleType.footnoteReference] = { _qualify: ['[^'], _match: inlineRegex(FOOTNOTE_REFERENCE_R), _order: Priority.HIGH, _parse: function _parse(capture /*, parse*/) { return { target: "#" + slug(capture[1], slugify), text: capture[1] }; } }, _rules[RuleType.gfmTask] = { _qualify: ['[ ]', '[x]'], _match: inlineRegex(GFM_TASK_R), _order: Priority.HIGH, _parse: function _parse(capture /*, parse, state*/) { return { completed: capture[1].toLowerCase() === 'x' }; } }, _rules[RuleType.heading] = { _qualify: ['#'], _match: blockRegex(options.enforceAtxHeadings ? HEADING_ATX_COMPLIANT_R : HEADING_R), _order: Priority.HIGH, _parse: function _parse(capture, parse, state) { return { children: parseInline(parse, capture[2], state), id: slug(capture[2], slugify), level: capture[1].length }; } }, _rules[RuleType.headingSetext] = { _qualify: function _qualify(source) { var nlIndex = source.indexOf('\n'); return nlIndex > 0 && nlIndex < source.length - 1 && (source[nlIndex + 1] === '=' || source[nlIndex + 1] === '-'); }, _match: blockRegex(HEADING_SETEXT_R), _order: Priority.HIGH, _parse: function _parse(capture, parse, state) { return { children: parseInline(parse, capture[1], state), level: capture[2] === '=' ? 1 : 2, type: RuleType.heading }; } }, _rules[RuleType.htmlBlock] = { _qualify: ['<'], /** * find the first matching end tag and process the interior */ _match: allowInline(matchHTMLBlock), _order: Priority.HIGH, _parse: function _parse(capture, parse, state) { var _capture$3$match = capture[3].match(HTML_LEFT_TRIM_AMOUNT_R), whitespace = _capture$3$match[1]; var trimmer = new RegExp("^" + whitespace, 'gm'); var trimmed = capture[3].replace(trimmer, ''); var parseFunc = some(BLOCK_SYNTAXES, trimmed) ? parseBlock : parseInline; var tagName = capture[1].toLowerCase(); var noInnerParse = DO_NOT_PROCESS_HTML_ELEMENTS.indexOf(tagName) !== -1; var tag = (noInnerParse ? tagName : capture[1]).trim(); var ast = { attrs: attrStringToMap(tag, capture[2], sanitize, compile), noInnerParse: noInnerParse, tag: tag }; state.inAnchor = state.inAnchor || tagName === 'a'; if (noInnerParse) { ast.text = capture[3]; } else { var prevInHTML = state.inHTML; state.inHTML = true; ast.children = parseFunc(parse, trimmed, state); state.inHTML = prevInHTML; } /** * if another html block is detected within, parse as block, * otherwise parse as inline to pick up any further markdown */ state.inAnchor = false; return ast; } }, _rules[RuleType.htmlSelfClosing] = { _qualify: ['<'], /** * find the first matching end tag and process the interior */ _match: anyScopeRegex(HTML_SELF_CLOSING_ELEMENT_R), _order: Priority.HIGH, _parse: function _parse(capture /*, parse, state*/) { var tag = capture[1].trim(); return { attrs: attrStringToMap(tag, capture[2] || '', sanitize, compile), tag: tag }; } }, _rules[RuleType.htmlComment] = { _qualify: ['<!--'], _match: anyScopeRegex(HTML_COMMENT_R), _order: Priority.HIGH, _parse: function _parse() { return {}; } }, _rules[RuleType.image] = { _qualify: ['!['], _match: simpleInlineRegex(IMAGE_R), _order: Priority.HIGH, _parse: function _parse(capture /*, parse, state*/) { return { alt: unescape(capture[1]), target: unescape(capture[2]), title: unescape(capture[3]) }; } }, _rules[RuleType.link] = { _qualify: ['['], _match: inlineRegex(LINK_R), _order: Priority.LOW, _parse: function _parse(capture, parse, state) { return { children: parseSimpleInline(parse, capture[1], state), target: unescape(capture[2]), title: unescape(capture[3]) }; } }, _rules[RuleType.linkAngleBraceStyleDetector] = {