markdown-to-jsx
Version:
Convert markdown to JSX with ease for React and React-like projects. Super lightweight and highly configurable.
1 lines • 117 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../match.ts","../index.tsx","../index.cjs.tsx"],"sourcesContent":["/**\n * Analogous to `node.type`. Please note that the values here may change at any time,\n * so do not hard code against the value directly.\n */\nexport const RuleType = {\n blockQuote: '0',\n breakLine: '1',\n breakThematic: '2',\n codeBlock: '3',\n codeFenced: '4',\n codeInline: '5',\n footnote: '6',\n footnoteReference: '7',\n gfmTask: '8',\n heading: '9',\n headingSetext: '10',\n htmlBlock: '11',\n htmlComment: '12',\n htmlSelfClosing: '13',\n image: '14',\n link: '15',\n linkAngleBraceStyleDetector: '16',\n linkBareUrlDetector: '17',\n linkMailtoDetector: '18',\n newlineCoalescer: '19',\n orderedList: '20',\n paragraph: '21',\n ref: '22',\n refImage: '23',\n refLink: '24',\n table: '25',\n tableSeparator: '26',\n text: '27',\n textEscaped: '28',\n textFormatted: '34',\n unorderedList: '30',\n} as const\n\nif (process.env.NODE_ENV === 'test') {\n Object.keys(RuleType).forEach(key => (RuleType[key] = key))\n}\n\nexport type RuleType = (typeof RuleType)[keyof typeof RuleType]\n\nconst T = ['strong', 'em', 'del', 'mark']\nconst DELS = [\n ['**', T[0]],\n ['__', T[0]],\n ['~~', T[2]],\n ['==', T[3]],\n ['*', T[1]],\n ['_', T[1]],\n]\n\nfunction skipLinkOrImage(source, pos) {\n var bracketDepth = 1\n var i = pos + 1\n\n while (i < source.length && bracketDepth > 0) {\n if (source[i] === '\\\\') {\n i += 2\n continue\n }\n if (source[i] === '[') bracketDepth++\n if (source[i] === ']') bracketDepth--\n i++\n }\n\n if (\n bracketDepth === 0 &&\n i < source.length &&\n (source[i] === '(' || source[i] === '[')\n ) {\n var closingChar = source[i] === '(' ? ')' : ']'\n var parenDepth = 1\n i++\n\n while (i < source.length && parenDepth > 0) {\n if (source[i] === '\\\\') {\n i += 2\n continue\n }\n if (source[i] === '(' && closingChar === ')') parenDepth++\n if (source[i] === closingChar) parenDepth--\n i++\n }\n\n if (parenDepth === 0) return i\n }\n\n return -1\n}\n\nexport function matchInlineFormatting(\n source: string,\n state?: { inline?: boolean; simple?: boolean }\n): RegExpMatchArray | null {\n if (!state || (!state.inline && !state.simple)) return null\n\n var c = source[0]\n if (c !== '*' && c !== '_' && c !== '~' && c !== '=') return null\n\n var delimiter = ''\n var startLength = 0\n var tag = ''\n\n for (var i = 0; i < 6; i++) {\n var d = DELS[i][0]\n if (source.startsWith(d) && source.length >= d.length * 2) {\n delimiter = d\n startLength = d.length\n tag = DELS[i][1]\n break\n }\n }\n\n if (!delimiter) return null\n\n var pos = startLength\n var inCode = false\n var inHTMLTag = false\n var inHTMLQuote = ''\n var htmlDepth = 0\n var content = ''\n var lastWasEscape = false\n var lastChar = ''\n\n while (pos < source.length) {\n var char = source[pos]\n\n if (lastWasEscape) {\n content += char\n lastWasEscape = false\n lastChar = char\n pos++\n continue\n }\n\n if (char === '\\\\') {\n content += char\n lastWasEscape = true\n lastChar = char\n pos++\n continue\n }\n\n if (char === '`' && htmlDepth === 0) {\n inCode = !inCode\n content += char\n lastChar = char\n pos++\n continue\n }\n\n if (char === '[' && !inCode && htmlDepth === 0) {\n var linkEnd = skipLinkOrImage(source, pos)\n if (linkEnd !== -1) {\n content += source.slice(pos, linkEnd)\n pos = linkEnd\n lastChar = source[linkEnd - 1]\n continue\n }\n }\n\n if (inHTMLTag) {\n content += char\n if (inHTMLQuote) {\n if (char === inHTMLQuote) inHTMLQuote = ''\n } else if (char === '\"' || char === \"'\") {\n inHTMLQuote = char\n } else if (char === '>') {\n inHTMLTag = false\n }\n lastChar = char\n pos++\n continue\n }\n\n if (char === '<' && !inCode) {\n var nextChar = source[pos + 1]\n var tagEnd = source.indexOf('>', pos)\n\n if (tagEnd !== -1) {\n var tagContent = source.slice(pos, tagEnd + 1)\n var isSelfClosing = tagContent.endsWith('/>')\n\n if (nextChar === '/') {\n htmlDepth = Math.max(0, htmlDepth - 1)\n } else if (!isSelfClosing) {\n htmlDepth++\n }\n }\n\n inHTMLTag = true\n content += char\n lastChar = char\n pos++\n continue\n }\n\n if (char === '\\n' && lastChar === '\\n' && !inCode && htmlDepth === 0) {\n return null\n }\n\n if (!inCode && htmlDepth === 0) {\n var delimiterRunLength = 0\n while (\n pos + delimiterRunLength < source.length &&\n source[pos + delimiterRunLength] === delimiter[0]\n ) {\n delimiterRunLength++\n }\n\n if (delimiterRunLength >= startLength) {\n if (\n startLength !== 1 ||\n (delimiter !== '*' && delimiter !== '_') ||\n (source[pos - 1] !== delimiter && source[pos + 1] !== delimiter)\n ) {\n var result = [\n source.slice(0, pos + delimiterRunLength),\n tag,\n content + source.slice(pos + startLength, pos + delimiterRunLength),\n ] as unknown as RegExpMatchArray\n result.index = 0\n result.input = source\n return result\n }\n }\n }\n\n content += char\n lastChar = char\n pos++\n }\n\n return null\n}\n","/* @jsx h */\n/**\n * markdown-to-jsx is a fork of\n * [simple-markdown v0.2.2](https://github.com/Khan/simple-markdown)\n * from Khan Academy. Thank you Khan devs for making such an awesome\n * and extensible parsing infra... without it, half of the\n * optimizations here wouldn't be feasible. 🙏🏼\n */\nimport * as React from 'react'\nimport {\n RuleType as RuleTypeConst,\n matchInlineFormatting,\n type RuleType as RuleTypeValue,\n} from './match'\n\nexport const RuleType = RuleTypeConst\nexport type RuleType = RuleTypeValue\n\nconst Priority = {\n /**\n * anything that must scan the tree before everything else\n */\n MAX: 0,\n /**\n * scans for block-level constructs\n */\n HIGH: 1,\n /**\n * inline w/ more priority than other inline\n */\n MED: 2,\n /**\n * inline elements\n */\n LOW: 3,\n /**\n * bare text and stuff that is considered leftovers\n */\n MIN: 4,\n}\n\n/** TODO: Drop for React 16? */\nconst ATTRIBUTE_TO_JSX_PROP_MAP = [\n 'allowFullScreen',\n 'allowTransparency',\n 'autoComplete',\n 'autoFocus',\n 'autoPlay',\n 'cellPadding',\n 'cellSpacing',\n 'charSet',\n 'classId',\n 'colSpan',\n 'contentEditable',\n 'contextMenu',\n 'crossOrigin',\n 'encType',\n 'formAction',\n 'formEncType',\n 'formMethod',\n 'formNoValidate',\n 'formTarget',\n 'frameBorder',\n 'hrefLang',\n 'inputMode',\n 'keyParams',\n 'keyType',\n 'marginHeight',\n 'marginWidth',\n 'maxLength',\n 'mediaGroup',\n 'minLength',\n 'noValidate',\n 'radioGroup',\n 'readOnly',\n 'rowSpan',\n 'spellCheck',\n 'srcDoc',\n 'srcLang',\n 'srcSet',\n 'tabIndex',\n 'useMap',\n].reduce(\n (obj, x) => {\n obj[x.toLowerCase()] = x\n return obj\n },\n { class: 'className', for: 'htmlFor' }\n)\n\nconst namedCodesToUnicode = {\n amp: '\\u0026',\n apos: '\\u0027',\n gt: '\\u003e',\n lt: '\\u003c',\n nbsp: '\\u00a0',\n quot: '\\u201c',\n} as const\n\nconst DO_NOT_PROCESS_HTML_ELEMENTS = ['style', 'script', 'pre']\nconst ATTRIBUTES_TO_SANITIZE = [\n 'src',\n 'href',\n 'data',\n 'formAction',\n 'srcDoc',\n 'action',\n]\n\n/**\n * the attribute extractor regex looks for a valid attribute name,\n * followed by an equal sign (whitespace around the equal sign is allowed), followed\n * by one of the following:\n *\n * 1. a single quote-bounded string, e.g. 'foo'\n * 2. a double quote-bounded string, e.g. \"bar\"\n * 3. an interpolation, e.g. {something}\n *\n * JSX can be be interpolated into itself and is passed through the compiler using\n * the same options and setup as the current run.\n *\n * <Something children={<SomeOtherThing />} />\n * ==================\n * ↳ children: [<SomeOtherThing />]\n *\n * Otherwise, interpolations are handled as strings or simple booleans\n * unless HTML syntax is detected.\n *\n * <Something color={green} disabled={true} />\n * ===== ====\n * ↓ ↳ disabled: true\n * ↳ color: \"green\"\n *\n * Numbers are not parsed at this time due to complexities around int, float,\n * and the upcoming bigint functionality that would make handling it unwieldy.\n * Parse the string in your component as desired.\n *\n * <Something someBigNumber={123456789123456789} />\n * ==================\n * ↳ someBigNumber: \"123456789123456789\"\n */\nconst ATTR_EXTRACTOR_R =\n /([-A-Z0-9_:]+)(?:\\s*=\\s*(?:(?:\"((?:\\\\.|[^\"])*)\")|(?:'((?:\\\\.|[^'])*)')|(?:\\{((?:\\\\.|{[^}]*?}|[^}])*)\\})))?/gi\n\n/** TODO: Write explainers for each of these */\n\nconst BLOCK_END_R = /\\n{2,}$/\nconst BLOCKQUOTE_R = /^(\\s*>[\\s\\S]*?)(?=\\n\\n|$)/\nconst BLOCKQUOTE_TRIM_LEFT_MULTILINE_R = /^ *> ?/gm\nconst BLOCKQUOTE_ALERT_R = /^(?:\\[!([^\\]]*)\\]\\n)?([\\s\\S]*)/\nconst BREAK_LINE_R = /^ {2,}\\n/\nconst BREAK_THEMATIC_R = /^(?:([-*_])( *\\1){2,}) *(?:\\n *)+\\n/\nconst CODE_BLOCK_FENCED_R =\n /^(?: {1,3})?(`{3,}|~{3,}) *(\\S+)? *([^\\n]*?)?\\n([\\s\\S]*?)(?:\\1\\n?|$)/\nconst CODE_BLOCK_R = /^(?: {4}[^\\n]+\\n*)+(?:\\n *)+\\n?/\nconst CODE_INLINE_R = /^(`+)((?:\\\\`|(?!\\1)`|[^`])+)\\1/\nconst CONSECUTIVE_NEWLINE_R = /^(?:\\n *)*\\n/\nconst CR_NEWLINE_R = /\\r\\n?/g\n\n/**\n * Matches footnotes on the format:\n *\n * [^key]: value\n *\n * Matches multiline footnotes\n *\n * [^key]: row\n * row\n * row\n *\n * And empty lines in indented multiline footnotes\n *\n * [^key]: indented with\n * row\n *\n * row\n *\n * Explanation:\n *\n * 1. Look for the starting tag, eg: [^key]\n * ^\\[\\^([^\\]]+)]\n *\n * 2. The first line starts with a colon, and continues for the rest of the line\n * :(.*)\n *\n * 3. Parse as many additional lines as possible. Matches new non-empty lines that doesn't begin with a new footnote definition.\n * (\\n(?!\\[\\^).+)\n *\n * 4. ...or allows for repeated newlines if the next line begins with at least four whitespaces.\n * (\\n+ {4,}.*)\n */\nconst FOOTNOTE_R = /^\\[\\^([^\\]]+)](:(.*)((\\n+ {4,}.*)|(\\n(?!\\[\\^).+))*)/\n\nconst FOOTNOTE_REFERENCE_R = /^\\[\\^([^\\]]+)]/\nconst FORMFEED_R = /\\f/g\nconst FRONT_MATTER_R = /^---[ \\t]*\\n(.|\\n)*\\n---[ \\t]*\\n/\nconst GFM_TASK_R = /^\\[(x|\\s)\\]/\nconst HEADING_R = /^(#{1,6}) *([^\\n]+?)(?: +#*)?(?:\\n *)*(?:\\n|$)/\nconst HEADING_ATX_COMPLIANT_R =\n /^ *(#{1,6}) +([^\\n]+?)(?: +#*)?(?:\\n *)*(?:\\n|$)/\nconst HEADING_SETEXT_R = /^([^\\n]+)\\n *(=|-)\\2{2,} *\\n/\n\nconst HTML_BLOCK_ELEMENT_START_R = /^<([a-z][^ >/]*) ?((?:[^>]*[^/])?)>/i\n\nfunction matchHTMLBlock(source: string): RegExpMatchArray | null {\n const m = HTML_BLOCK_ELEMENT_START_R.exec(source)\n if (!m) return null\n\n const tagName = m[1]\n const tagLower = tagName.toLowerCase()\n const openTagLen = tagLower.length + 1\n\n let pos = m[0].length\n if (source[pos] === '\\n') pos++\n const contentStart = pos\n let contentEnd = pos\n let depth = 1\n const sourceLen = source.length\n\n while (depth > 0) {\n const idx = source.indexOf('<', pos)\n if (idx === -1) return null\n\n let openIdx = -1\n let closeIdx = -1\n\n if (source[idx + 1] === '/') {\n closeIdx = idx\n } else if (\n source[idx + 1] === tagLower[0] ||\n source[idx + 1] === tagName[0]\n ) {\n let match = true\n for (let i = 0; i < tagLower.length; i++) {\n const c = source[idx + 1 + i]\n if (c !== tagLower[i] && c !== tagName[i]) {\n match = false\n break\n }\n }\n if (\n match &&\n (source[idx + openTagLen] === ' ' || source[idx + openTagLen] === '>')\n ) {\n openIdx = idx\n }\n }\n\n if (openIdx === -1 && closeIdx === -1) {\n pos = idx + 1\n continue\n }\n\n if (openIdx !== -1 && (closeIdx === -1 || openIdx < closeIdx)) {\n pos = openIdx + openTagLen + 1\n depth++\n } else {\n let p = closeIdx + 2\n while (p < sourceLen) {\n const c = source[p]\n if (c !== ' ' && c !== '\\t' && c !== '\\n' && c !== '\\r') break\n p++\n }\n if (p + tagLower.length > sourceLen) return null\n\n let match = true\n for (let i = 0; i < tagLower.length; i++) {\n const c = source[p + i]\n if (c !== tagLower[i] && c !== tagName[i]) {\n match = false\n break\n }\n }\n if (!match) {\n pos = p\n continue\n }\n\n p += tagLower.length\n while (p < sourceLen) {\n const c = source[p]\n if (c !== ' ' && c !== '\\t' && c !== '\\n' && c !== '\\r') break\n p++\n }\n if (p >= sourceLen || source[p] !== '>') {\n pos = p\n continue\n }\n\n contentEnd = closeIdx\n pos = p + 1\n depth--\n }\n }\n\n let trailingNl = 0\n while (pos + trailingNl < sourceLen && source[pos + trailingNl] === '\\n')\n trailingNl++\n\n return [\n source.slice(0, pos + trailingNl),\n tagName,\n m[2],\n source.slice(contentStart, contentEnd),\n ] as RegExpMatchArray\n}\n\nconst HTML_CHAR_CODE_R = /&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});/gi\n\nconst HTML_COMMENT_R = /^<!--[\\s\\S]*?(?:-->)/\n\n/**\n * borrowed from React 15(https://github.com/facebook/react/blob/894d20744cba99383ffd847dbd5b6e0800355a5c/src/renderers/dom/shared/HTMLDOMPropertyConfig.js)\n */\nconst HTML_CUSTOM_ATTR_R = /^(data|aria|x)-[a-z_][a-z\\d_.-]*$/\n\nconst HTML_SELF_CLOSING_ELEMENT_R =\n /^ *<([a-z][a-z0-9:]*)(?:\\s+((?:<.*?>|[^>])*))?\\/?>(?!<\\/\\1>)(\\s*\\n)?/i\nconst INTERPOLATION_R = /^\\{.*\\}$/\nconst LINK_AUTOLINK_BARE_URL_R = /^(https?:\\/\\/[^\\s<]+[^<.,:;\"')\\]\\s])/\nconst LINK_AUTOLINK_R = /^<([^ >]+[:@\\/][^ >]+)>/\nconst CAPTURE_LETTER_AFTER_HYPHEN = /-([a-z])?/gi\nconst NP_TABLE_R = /^(\\|.*)\\n(?: *(\\|? *[-:]+ *\\|[-| :]*)\\n((?:.*\\|.*\\n)*))?\\n?/\nconst PARAGRAPH_R = /^[^\\n]+(?: \\n|\\n{2,})/\nconst REFERENCE_IMAGE_OR_LINK = /^\\[([^\\]]*)\\]:\\s+<?([^\\s>]+)>?\\s*(\"([^\"]*)\")?/\nconst REFERENCE_IMAGE_R = /^!\\[([^\\]]*)\\] ?\\[([^\\]]*)\\]/\nconst REFERENCE_LINK_R = /^\\[([^\\]]*)\\] ?\\[([^\\]]*)\\]/\nconst SHOULD_RENDER_AS_BLOCK_R = /(\\n|^[-*]\\s|^#|^ {2,}|^-{2,}|^>\\s)/\nconst TAB_R = /\\t/g\nconst TABLE_TRIM_PIPES = /(^ *\\||\\| *$)/g\nconst TABLE_CENTER_ALIGN = /^ *:-+: *$/\nconst TABLE_LEFT_ALIGN = /^ *:-+ *$/\nconst TABLE_RIGHT_ALIGN = /^ *-+: *$/\n\n/**\n * Special case for shortcodes like :big-smile: or :emoji:\n */\nconst SHORTCODE_R = /^(:[a-zA-Z0-9-_]+:)/\n\nconst TEXT_ESCAPED_R = /^\\\\([^0-9A-Za-z\\s])/\nconst UNESCAPE_R = /\\\\([^0-9A-Za-z\\s])/g\n\n/**\n * Always take the first character, then eagerly take text until a double space\n * (potential line break) or some markdown-like punctuation is reached.\n */\nconst TEXT_PLAIN_R = /^[\\s\\S](?:(?! \\n|[0-9]\\.|http)[^=*_~\\-\\n:<`\\\\\\[!])*/\n\nconst TRIM_STARTING_NEWLINES = /^\\n+/\n\nconst HTML_LEFT_TRIM_AMOUNT_R = /^([ \\t]*)/\n\ntype LIST_TYPE = 1 | 2\nconst ORDERED: LIST_TYPE = 1\nconst UNORDERED: LIST_TYPE = 2\n\nconst LIST_LOOKBEHIND_R = /(?:^|\\n)( *)$/\n\n// recognize a `*` `-`, `+`, `1.`, `2.`... list bullet\nconst ORDERED_LIST_BULLET = '(?:\\\\d+\\\\.)'\nconst UNORDERED_LIST_BULLET = '(?:[*+-])'\n\nfunction generateListItemPrefix(type: LIST_TYPE) {\n return (\n '( *)(' +\n (type === ORDERED ? ORDERED_LIST_BULLET : UNORDERED_LIST_BULLET) +\n ') +'\n )\n}\n\n// recognize the start of a list item:\n// leading space plus a bullet plus a space (` * `)\nconst ORDERED_LIST_ITEM_PREFIX = generateListItemPrefix(ORDERED)\nconst UNORDERED_LIST_ITEM_PREFIX = generateListItemPrefix(UNORDERED)\n\nfunction generateListItemPrefixRegex(type: LIST_TYPE) {\n return new RegExp(\n '^' +\n (type === ORDERED ? ORDERED_LIST_ITEM_PREFIX : UNORDERED_LIST_ITEM_PREFIX)\n )\n}\n\nconst ORDERED_LIST_ITEM_PREFIX_R = generateListItemPrefixRegex(ORDERED)\nconst UNORDERED_LIST_ITEM_PREFIX_R = generateListItemPrefixRegex(UNORDERED)\n\nfunction generateListItemRegex(type: LIST_TYPE) {\n // recognize an individual list item:\n // * hi\n // this is part of the same item\n //\n // as is this, which is a new paragraph in the same item\n //\n // * but this is not part of the same item\n return new RegExp(\n '^' +\n (type === ORDERED\n ? ORDERED_LIST_ITEM_PREFIX\n : UNORDERED_LIST_ITEM_PREFIX) +\n '[^\\\\n]*(?:\\\\n' +\n '(?!\\\\1' +\n (type === ORDERED ? ORDERED_LIST_BULLET : UNORDERED_LIST_BULLET) +\n ' )[^\\\\n]*)*(\\\\n|$)',\n 'gm'\n )\n}\n\nconst ORDERED_LIST_ITEM_R = generateListItemRegex(ORDERED)\nconst UNORDERED_LIST_ITEM_R = generateListItemRegex(UNORDERED)\n\n// check whether a list item has paragraphs: if it does,\n// we leave the newlines at the end\nfunction generateListRegex(type: LIST_TYPE) {\n const bullet = type === ORDERED ? ORDERED_LIST_BULLET : UNORDERED_LIST_BULLET\n\n return new RegExp(\n '^( *)(' +\n bullet +\n ') ' +\n '[\\\\s\\\\S]+?(?:\\\\n{2,}(?! )' +\n '(?!\\\\1' +\n bullet +\n ' (?!' +\n bullet +\n ' ))\\\\n*' +\n // the \\\\s*$ here is so that we can parse the inside of nested\n // lists, where our content might end before we receive two `\\n`s\n '|\\\\s*\\\\n*$)'\n )\n}\n\nconst ORDERED_LIST_R = generateListRegex(ORDERED)\nconst UNORDERED_LIST_R = generateListRegex(UNORDERED)\n\nfunction generateListRule(\n h: any,\n type: LIST_TYPE\n): MarkdownToJSX.Rule<\n MarkdownToJSX.OrderedListNode | MarkdownToJSX.UnorderedListNode\n> {\n const ordered = type === ORDERED\n const LIST_R = ordered ? ORDERED_LIST_R : UNORDERED_LIST_R\n const LIST_ITEM_R = ordered ? ORDERED_LIST_ITEM_R : UNORDERED_LIST_ITEM_R\n const LIST_ITEM_PREFIX_R = ordered\n ? ORDERED_LIST_ITEM_PREFIX_R\n : UNORDERED_LIST_ITEM_PREFIX_R\n\n return {\n _qualify: source => LIST_ITEM_PREFIX_R.test(source),\n _match: allowInline(function (source, state) {\n // We only want to break into a list if we are at the start of a\n // line. This is to avoid parsing \"hi * there\" with \"* there\"\n // becoming a part of a list.\n // You might wonder, \"but that's inline, so of course it wouldn't\n // start a list?\". You would be correct! Except that some of our\n // lists can be inline, because they might be inside another list,\n // in which case we can parse with inline scope, but need to allow\n // nested lists inside this inline scope.\n const isStartOfLine = LIST_LOOKBEHIND_R.exec(state.prevCapture)\n const isListAllowed = state.list || (!state.inline && !state.simple)\n\n if (isStartOfLine && isListAllowed) {\n source = isStartOfLine[1] + source\n\n return LIST_R.exec(source)\n } else {\n return null\n }\n }),\n _order: Priority.HIGH,\n _parse(capture, parse, state) {\n const bullet = capture[2]\n const start = ordered ? +bullet : undefined\n const items = capture[0]\n // recognize the end of a paragraph block inside a list item:\n // two or more newlines at end end of the item\n .replace(BLOCK_END_R, '\\n')\n .match(LIST_ITEM_R)\n\n const firstPrefixMatch = LIST_ITEM_PREFIX_R.exec(items[0])\n const space = firstPrefixMatch ? firstPrefixMatch[0].length : 0\n const spaceRegex = new RegExp('^ {1,' + space + '}', 'gm')\n\n let lastItemWasAParagraph = false\n\n const itemContent = items.map(function (item, i) {\n // Before processing the item, we need a couple things\n const content = item\n // remove indents on trailing lines:\n .replace(spaceRegex, '')\n // remove the bullet:\n .replace(LIST_ITEM_PREFIX_R, '')\n\n // Handling \"loose\" lists, like:\n //\n // * this is wrapped in a paragraph\n //\n // * as is this\n //\n // * as is this\n const isLastItem = i === items.length - 1\n const containsBlocks = includes(content, '\\n\\n')\n\n // Any element in a list is a block if it contains multiple\n // newlines. The last element in the list can also be a block\n // if the previous item in the list was a block (this is\n // because non-last items in the list can end with \\n\\n, but\n // the last item can't, so we just \"inherit\" this property\n // from our previous element).\n const thisItemIsAParagraph =\n containsBlocks || (isLastItem && lastItemWasAParagraph)\n lastItemWasAParagraph = thisItemIsAParagraph\n\n // backup our state for delta afterwards. We're going to\n // want to set state.list to true, and state.inline depending\n // on our list's looseness.\n const oldStateInline = state.inline\n const oldStateList = state.list\n state.list = true\n\n // Parse inline if we're in a tight list, or block if we're in\n // a loose list.\n let adjustedContent\n if (thisItemIsAParagraph) {\n state.inline = false\n adjustedContent = trimEnd(content) + '\\n\\n'\n } else {\n state.inline = true\n adjustedContent = trimEnd(content)\n }\n\n const result = parse(adjustedContent, state)\n\n // Restore our state before returning\n state.inline = oldStateInline\n state.list = oldStateList\n\n return result\n })\n\n return {\n items: itemContent,\n ordered: ordered,\n start: start,\n }\n },\n }\n}\n\nconst LINK_INSIDE =\n '(?:\\\\[[^\\\\[\\\\]]*(?:\\\\[[^\\\\[\\\\]]*\\\\][^\\\\[\\\\]]*)*\\\\]|[^\\\\[\\\\]])*'\nconst LINK_HREF_AND_TITLE =\n '\\\\s*<?((?:\\\\([^)]*\\\\)|[^\\\\s\\\\\\\\]|\\\\\\\\.)*?)>?(?:\\\\s+[\\'\"]([\\\\s\\\\S]*?)[\\'\"])?\\\\s*'\nconst LINK_R = new RegExp(\n '^\\\\[(' + LINK_INSIDE + ')\\\\]\\\\(' + LINK_HREF_AND_TITLE + '\\\\)'\n)\nconst IMAGE_R = /^!\\[(.*?)\\]\\( *((?:\\([^)]*\\)|[^() ])*) *\"?([^)\"]*)?\"?\\)/\n\nfunction isString(value: any): value is string {\n return typeof value === 'string'\n}\n\nfunction trimEnd(str: string) {\n let end = str.length\n while (end > 0 && str[end - 1] <= ' ') end--\n return str.slice(0, end)\n}\n\nfunction startsWith(str: string, prefix: string) {\n return str.startsWith(prefix)\n}\n\nfunction includes(str: string, search: string) {\n return str.indexOf(search) !== -1\n}\n\nfunction qualifies(\n source: string,\n state: MarkdownToJSX.State,\n qualify: MarkdownToJSX.Rule<any>['_qualify']\n) {\n if (Array.isArray(qualify)) {\n for (let i = 0; i < qualify.length; i++) {\n if (startsWith(source, qualify[i])) return true\n }\n\n return false\n }\n\n return qualify(source, state)\n}\n\n/** Remove symmetrical leading and trailing quotes */\nfunction unquote(str: string) {\n const first = str[0]\n if (\n (first === '\"' || first === \"'\") &&\n str.length >= 2 &&\n str[str.length - 1] === first\n ) {\n return str.slice(1, -1)\n }\n return str\n}\n\n// based on https://stackoverflow.com/a/18123682/1141611\n// not complete, but probably good enough\nexport function slugify(str: string) {\n return str\n .replace(/[ÀÁÂÃÄÅàáâãä忯]/g, 'a')\n .replace(/[çÇ]/g, 'c')\n .replace(/[ðÐ]/g, 'd')\n .replace(/[ÈÉÊËéèêë]/g, 'e')\n .replace(/[ÏïÎîÍíÌì]/g, 'i')\n .replace(/[Ññ]/g, 'n')\n .replace(/[øØœŒÕõÔôÓóÒò]/g, 'o')\n .replace(/[ÜüÛûÚúÙù]/g, 'u')\n .replace(/[ŸÿÝý]/g, 'y')\n .replace(/[^a-z0-9- ]/gi, '')\n .replace(/ /gi, '-')\n .toLowerCase()\n}\n\nfunction parseTableAlignCapture(alignCapture: string) {\n if (TABLE_RIGHT_ALIGN.test(alignCapture)) {\n return 'right'\n } else if (TABLE_CENTER_ALIGN.test(alignCapture)) {\n return 'center'\n } else if (TABLE_LEFT_ALIGN.test(alignCapture)) {\n return 'left'\n }\n\n return null\n}\n\nfunction parseTableRow(\n source: string,\n parse: MarkdownToJSX.NestedParser,\n state: MarkdownToJSX.State,\n tableOutput: boolean\n): MarkdownToJSX.ASTNode[][] {\n const prevInTable = state.inTable\n\n state.inTable = true\n\n let cells: MarkdownToJSX.ASTNode[][] = [[]]\n let acc = ''\n\n function flush() {\n if (!acc) return\n\n const cell = cells[cells.length - 1]\n cell.push.apply(cell, parse(acc, state))\n acc = ''\n }\n\n source\n .trim()\n // isolate situations where a pipe should be ignored (inline code, escaped, etc)\n .split(/(`[^`]*`|\\\\\\||\\|)/)\n .filter(Boolean)\n .forEach((fragment, i, arr) => {\n if (fragment.trim() === '|') {\n flush()\n\n if (tableOutput) {\n if (i !== 0 && i !== arr.length - 1) {\n // Split the current row\n cells.push([])\n }\n\n return\n }\n }\n\n acc += fragment\n })\n\n flush()\n\n state.inTable = prevInTable\n\n return cells\n}\n\nfunction parseTableAlign(source: string /*, parse, state*/) {\n const alignText = source.replace(TABLE_TRIM_PIPES, '').split('|')\n\n return alignText.map(parseTableAlignCapture)\n}\n\nfunction parseTableCells(\n source: string,\n parse: MarkdownToJSX.NestedParser,\n state: MarkdownToJSX.State\n) {\n const rowsText = source.trim().split('\\n')\n\n return rowsText.map(function (rowText) {\n return parseTableRow(rowText, parse, state, true)\n })\n}\n\nfunction parseTable(\n capture: RegExpMatchArray,\n parse: MarkdownToJSX.NestedParser,\n state: MarkdownToJSX.State\n) {\n /**\n * The table syntax makes some other parsing angry so as a bit of a hack even if alignment and/or cell rows are missing,\n * we'll still run a detected first row through the parser and then just emit a paragraph.\n */\n state.inline = true\n const align = capture[2] ? parseTableAlign(capture[2]) : []\n const cells = capture[3] ? parseTableCells(capture[3], parse, state) : []\n const header = parseTableRow(capture[1], parse, state, !!cells.length)\n state.inline = false\n\n return cells.length\n ? {\n align: align,\n cells: cells,\n header: header,\n type: RuleType.table,\n }\n : {\n children: header,\n type: RuleType.paragraph,\n }\n}\n\nfunction getTableStyle(node, colIndex) {\n return node.align[colIndex] == null\n ? {}\n : {\n textAlign: node.align[colIndex],\n }\n}\n\n/** TODO: remove for react 16 */\nfunction normalizeAttributeKey(key) {\n const hyphenIndex = key.indexOf('-')\n\n if (hyphenIndex !== -1 && key.match(HTML_CUSTOM_ATTR_R) === null) {\n key = key.replace(CAPTURE_LETTER_AFTER_HYPHEN, function (_, letter) {\n return letter.toUpperCase()\n })\n }\n\n return key\n}\n\ntype StyleTuple = [key: string, value: string]\n\nfunction parseStyleAttribute(styleString: string): StyleTuple[] {\n const styles: StyleTuple[] = []\n if (!styleString) return styles\n\n let buffer = ''\n let depth = 0\n let quoteChar = ''\n\n for (let i = 0; i < styleString.length; i++) {\n const char = styleString[i]\n\n if (char === '\"' || char === \"'\") {\n if (!quoteChar) {\n quoteChar = char\n depth++\n } else if (char === quoteChar) {\n quoteChar = ''\n depth--\n }\n } else if (char === '(' && buffer.endsWith('url')) {\n depth++\n } else if (char === ')' && depth > 0) {\n depth--\n } else if (char === ';' && depth === 0) {\n const colonIndex = buffer.indexOf(':')\n if (colonIndex > 0) {\n styles.push([\n buffer.slice(0, colonIndex).trim(),\n buffer.slice(colonIndex + 1).trim(),\n ])\n }\n buffer = ''\n continue\n }\n\n buffer += char\n }\n\n const colonIndex = buffer.indexOf(':')\n if (colonIndex > 0) {\n styles.push([\n buffer.slice(0, colonIndex).trim(),\n buffer.slice(colonIndex + 1).trim(),\n ])\n }\n\n return styles\n}\n\nfunction attributeValueToJSXPropValue(\n tag: MarkdownToJSX.HTMLTags,\n key: keyof React.AllHTMLAttributes<Element>,\n value: string,\n sanitizeUrlFn: MarkdownToJSX.Options['sanitizer']\n): any {\n if (key === 'style') {\n return parseStyleAttribute(value).reduce(function (styles, [key, value]) {\n styles[key.replace(/(-[a-z])/g, substr => substr[1].toUpperCase())] =\n sanitizeUrlFn(value, tag, key)\n return styles\n }, {})\n }\n\n if (ATTRIBUTES_TO_SANITIZE.indexOf(key) !== -1) {\n return sanitizeUrlFn(unescape(value), tag, key)\n }\n\n if (value.match(INTERPOLATION_R)) {\n value = unescape(value.slice(1, value.length - 1))\n }\n\n return value === 'true' ? true : value === 'false' ? false : value\n}\n\nfunction normalizeWhitespace(source: string): string {\n return source\n .replace(CR_NEWLINE_R, '\\n')\n .replace(FORMFEED_R, '')\n .replace(TAB_R, ' ')\n}\n\n/**\n * Creates a parser for a given set of rules, with the precedence\n * specified as a list of rules.\n *\n * @rules: an object containing\n * rule type -> {match, order, parse} objects\n * (lower order is higher precedence)\n * (Note: `order` is added to defaultRules after creation so that\n * the `order` of defaultRules in the source matches the `order`\n * of defaultRules in terms of `order` fields.)\n *\n * @returns The resulting parse function, with the following parameters:\n * @source: the input source string to be parsed\n * @state: an optional object to be threaded through parse\n * calls. Allows clients to add stateful operations to\n * parsing, such as keeping track of how many levels deep\n * some nesting is. For an example use-case, see passage-ref\n * parsing in src/widgets/passage/passage-markdown.jsx\n */\nfunction parserFor(\n rules: MarkdownToJSX.Rules\n): (\n source: string,\n state: MarkdownToJSX.State\n) => ReturnType<MarkdownToJSX.NestedParser> {\n var ruleList = Object.keys(rules)\n\n if (process.env.NODE_ENV !== 'production') {\n ruleList.forEach(function (type) {\n const order = rules[type]._order\n if (typeof order !== 'number' || !isFinite(order)) {\n console.warn(\n 'markdown-to-jsx: Invalid order for rule `' + type + '`: ' + order\n )\n }\n })\n }\n\n // Sorts rules in order of increasing order, then\n // ascending rule name in case of ties.\n ruleList.sort(function (a, b) {\n return rules[a]._order - rules[b]._order || (a < b ? -1 : 1)\n })\n\n function nestedParse(\n source: string,\n state: MarkdownToJSX.State\n ): MarkdownToJSX.ASTNode[] {\n var result = []\n state.prevCapture = state.prevCapture || ''\n\n if (source.trim()) {\n while (source) {\n var i = 0\n while (i < ruleList.length) {\n var ruleType = ruleList[i]\n var rule = rules[ruleType]\n\n if (rule._qualify && !qualifies(source, state, rule._qualify)) {\n i++\n continue\n }\n\n var capture = rule._match(source, state)\n if (capture && capture[0]) {\n source = source.substring(capture[0].length)\n\n var parsed = rule._parse(capture, nestedParse, state)\n\n state.prevCapture += capture[0]\n\n if (!parsed.type) parsed.type = ruleType as unknown as RuleType\n result.push(parsed)\n break\n }\n i++\n }\n }\n }\n\n // reset on exit\n state.prevCapture = ''\n\n return result\n }\n\n return function (source, state) {\n return nestedParse(normalizeWhitespace(source), state)\n }\n}\n\n/**\n * Marks a matcher function as eligible for being run inside an inline context;\n * allows us to do a little less work in the nested parser.\n */\nfunction allowInline<T extends Function & { inline?: 0 | 1 }>(fn: T) {\n fn.inline = 1\n\n return fn\n}\n\n// Creates a match function for an inline scoped or simple element from a regex\nfunction inlineRegex(regex: RegExp) {\n return allowInline(function match(source, state: MarkdownToJSX.State) {\n if (state.inline) {\n return regex.exec(source)\n } else {\n return null\n }\n })\n}\n\n// basically any inline element except links\nfunction simpleInlineRegex(regex: RegExp) {\n return allowInline(function match(\n source: string,\n state: MarkdownToJSX.State\n ) {\n if (state.inline || state.simple) {\n return regex.exec(source)\n } else {\n return null\n }\n })\n}\n\n// Creates a match function for a block scoped element from a regex\nfunction blockRegex(regex: RegExp) {\n return function match(source: string, state: MarkdownToJSX.State) {\n if (state.inline || state.simple) {\n return null\n } else {\n return regex.exec(source)\n }\n }\n}\n\n// Creates a match function from a regex, ignoring block/inline scope\nfunction anyScopeRegex(regex: RegExp) {\n return allowInline(function match(source: string /*, state*/) {\n return regex.exec(source)\n })\n}\n\nconst SANITIZE_R = /(javascript|vbscript|data(?!:image)):/i\n\nexport function sanitizer(input: string): string {\n try {\n const decoded = decodeURIComponent(input).replace(/[^A-Za-z0-9/:]/g, '')\n\n if (SANITIZE_R.test(decoded)) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n 'Input contains an unsafe JavaScript/VBScript/data expression, it will not be rendered.',\n decoded\n )\n }\n\n return null\n }\n } catch (e) {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n 'Input could not be decoded due to malformed syntax or characters, it will not be rendered.',\n input\n )\n }\n\n // decodeURIComponent sometimes throws a URIError\n // See `decodeURIComponent('a%AFc');`\n // http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception\n return null\n }\n\n return input\n}\n\nfunction unescape(rawString: string): string {\n return rawString ? rawString.replace(UNESCAPE_R, '$1') : rawString\n}\n\n/**\n * Everything inline, including links.\n */\nfunction parseInline(\n parse: MarkdownToJSX.NestedParser,\n children: string,\n state: MarkdownToJSX.State\n): MarkdownToJSX.ASTNode[] {\n const isCurrentlyInline = state.inline || false\n const isCurrentlySimple = state.simple || false\n state.inline = true\n state.simple = true\n const result = parse(children, state)\n state.inline = isCurrentlyInline\n state.simple = isCurrentlySimple\n return result\n}\n\n/**\n * Anything inline that isn't a link.\n */\nfunction parseSimpleInline(\n parse: MarkdownToJSX.NestedParser,\n children: string,\n state: MarkdownToJSX.State\n): MarkdownToJSX.ASTNode[] {\n const isCurrentlyInline = state.inline || false\n const isCurrentlySimple = state.simple || false\n state.inline = false\n state.simple = true\n const result = parse(children, state)\n state.inline = isCurrentlyInline\n state.simple = isCurrentlySimple\n return result\n}\n\nfunction parseBlock(\n parse,\n children,\n state: MarkdownToJSX.State\n): MarkdownToJSX.ASTNode[] {\n const isCurrentlyInline = state.inline || false\n state.inline = false\n const result = parse(children, state)\n state.inline = isCurrentlyInline\n return result\n}\n\nconst parseCaptureInline: MarkdownToJSX.Parser<{\n children: MarkdownToJSX.ASTNode[]\n}> = (capture, parse, state: MarkdownToJSX.State) => {\n return {\n children: parseInline(parse, capture[2], state),\n }\n}\n\nfunction captureNothing() {\n return {}\n}\n\nfunction render(\n node: MarkdownToJSX.ASTNode,\n output: MarkdownToJSX.RuleOutput,\n state: MarkdownToJSX.State,\n h: (tag: any, props: any, ...children: any[]) => any,\n sanitize: (value: string, tag: string, attribute: string) => string | null,\n slug: (input: string, defaultFn: (input: string) => string) => string,\n refs: { [key: string]: { target: string; title: string } }\n): React.ReactNode {\n switch (node.type) {\n case RuleType.blockQuote: {\n const props = {\n key: state.key,\n } as Record<string, unknown>\n\n if (node.alert) {\n props.className =\n 'markdown-alert-' + slug(node.alert.toLowerCase(), slugify)\n\n node.children.unshift({\n attrs: {},\n children: [{ type: RuleType.text, text: node.alert }],\n noInnerParse: true,\n type: RuleType.htmlBlock,\n tag: 'header',\n })\n }\n\n return h('blockquote', props, output(node.children, state))\n }\n\n case RuleType.breakLine:\n return <br key={state.key} />\n\n case RuleType.breakThematic:\n return <hr key={state.key} />\n\n case RuleType.codeBlock:\n return (\n <pre key={state.key}>\n <code\n {...node.attrs}\n className={node.lang ? `lang-${node.lang}` : ''}\n >\n {node.text}\n </code>\n </pre>\n )\n\n case RuleType.codeInline:\n return <code key={state.key}>{node.text}</code>\n\n case RuleType.footnoteReference:\n return (\n <a key={state.key} href={sanitize(node.target, 'a', 'href')}>\n <sup key={state.key}>{node.text}</sup>\n </a>\n )\n\n case RuleType.gfmTask:\n return (\n <input\n checked={node.completed}\n key={state.key}\n readOnly\n type=\"checkbox\"\n />\n )\n\n case RuleType.heading:\n return h(\n `h${node.level}`,\n { id: node.id, key: state.key },\n output(node.children, state)\n )\n\n case RuleType.htmlBlock:\n return (\n <node.tag key={state.key} {...node.attrs}>\n {node.text || (node.children ? output(node.children, state) : '')}\n </node.tag>\n )\n\n case RuleType.htmlSelfClosing:\n return <node.tag {...node.attrs} key={state.key} />\n\n case RuleType.image:\n return (\n <img\n key={state.key}\n alt={node.alt || undefined}\n title={node.title || undefined}\n src={sanitize(node.target, 'img', 'src')}\n />\n )\n\n case RuleType.link:\n return (\n <a\n key={state.key}\n href={sanitize(node.target, 'a', 'href')}\n title={node.title}\n >\n {output(node.children, state)}\n </a>\n )\n\n case RuleType.refImage:\n return refs[node.ref] ? (\n <img\n key={state.key}\n alt={node.alt}\n src={sanitize(refs[node.ref].target, 'img', 'src')}\n title={refs[node.ref].title}\n />\n ) : null\n\n case RuleType.refLink:\n return refs[node.ref] ? (\n <a\n key={state.key}\n href={sanitize(refs[node.ref].target, 'a', 'href')}\n title={refs[node.ref].title}\n >\n {output(node.children, state)}\n </a>\n ) : (\n <span key={state.key}>{node.fallbackChildren}</span>\n )\n\n case RuleType.table: {\n const table = node as MarkdownToJSX.TableNode\n return (\n <table key={state.key}>\n <thead>\n <tr>\n {table.header.map(function generateHeaderCell(content, i) {\n return (\n <th key={i} style={getTableStyle(table, i)}>\n {output(content, state)}\n </th>\n )\n })}\n </tr>\n </thead>\n\n <tbody>\n {table.cells.map(function generateTableRow(row, i) {\n return (\n <tr key={i}>\n {row.map(function generateTableCell(content, c) {\n return (\n <td key={c} style={getTableStyle(table, c)}>\n {output(content, state)}\n </td>\n )\n })}\n </tr>\n )\n })}\n </tbody>\n </table>\n )\n }\n\n case RuleType.text:\n return node.text\n\n case RuleType.textFormatted:\n return h(\n node.tag as MarkdownToJSX.HTMLTags,\n { key: state.key },\n output(node.children, state)\n )\n\n case RuleType.orderedList:\n case RuleType.unorderedList: {\n const Tag = node.ordered ? 'ol' : 'ul'\n\n return (\n <Tag\n key={state.key}\n start={node.type === RuleType.orderedList ? node.start : undefined}\n >\n {node.items.map(function generateListItem(item, i) {\n return <li key={i}>{output(item, state)}</li>\n })}\n </Tag>\n )\n }\n\n case RuleType.newlineCoalescer:\n return '\\n'\n\n case RuleType.paragraph:\n return <p key={state.key}>{output(node.children, state)}</p>\n\n default:\n return null\n }\n}\n\nfunction createRenderer(\n userRender: MarkdownToJSX.Options['renderRule'] | undefined,\n h: (tag: any, props: any, ...children: any[]) => any,\n sanitize: (value: string, tag: string, attribute: string) => string | null,\n slug: (input: string, defaultFn: (input: string) => string) => string,\n refs: { [key: string]: { target: string; title: string } }\n) {\n function renderRule(\n ast: MarkdownToJSX.ASTNode,\n renderer: MarkdownToJSX.RuleOutput,\n state: MarkdownToJSX.State\n ): React.ReactNode {\n const nodeRender = () =>\n render(ast, renderer, state, h, sanitize, slug, refs)\n return userRender\n ? userRender(nodeRender, ast, renderer, state)\n : nodeRender()\n }\n\n // Return plain text as fallback to prevent stack overflow\n function handleStackOverflow(\n ast: MarkdownToJSX.ASTNode | MarkdownToJSX.ASTNode[]\n ) {\n if (Array.isArray(ast)) {\n return ast.map(node => ('text' in node ? node.text : ''))\n }\n return 'text' in ast ? ast.text : ''\n }\n\n return function patchedRender(\n ast: MarkdownToJSX.ASTNode | MarkdownToJSX.ASTNode[],\n state: MarkdownToJSX.State = {}\n ): React.ReactNode[] | React.ReactNode {\n // Track render depth to prevent stack overflow from extremely deep nesting\n const currentDepth = (state.renderDepth || 0) + 1\n const MAX_RENDER_DEPTH = 2500\n\n if (currentDepth > MAX_RENDER_DEPTH) {\n return handleStackOverflow(ast)\n }\n\n state.renderDepth = currentDepth\n\n try {\n if (Array.isArray(ast)) {\n const oldKey = state.key\n const result = []\n\n // map nestedOutput over the ast, except group any text\n // nodes together into a single string output.\n let lastWasString = false\n\n for (let i = 0; i < ast.length; i++) {\n state.key = i\n\n const nodeOut = patchedRender(ast[i], state)\n const _isString = isString(nodeOut)\n\n if (_isString && lastWasString) {\n result[result.length - 1] += nodeOut\n } else if (nodeOut !== null) {\n result.push(nodeOut)\n }\n\n lastWasString = _isString\n }\n\n state.key = oldKey\n state.renderDepth = currentDepth - 1\n\n return result\n }\n\n const result = renderRule(ast, patchedRender, state)\n state.renderDepth = currentDepth - 1\n\n return result\n } catch (error) {\n // Catch stack overflow or other unexpected errors\n if (\n error instanceof RangeError &&\n error.message.includes('Maximum call stack')\n ) {\n // Log error asynchronously to avoid stack overflow\n if (process.env.NODE_ENV !== 'production') {\n try {\n console.error(\n 'markdown-to-jsx: Stack overflow during rendering. ' +\n 'This usually indicates extremely nested content. ' +\n 'Consider breaking up the nested structure.'\n )\n } catch (e) {\n // If console.error fails, silently continue - no more stack available\n }\n }\n\n return handleStackOverflow(ast)\n }\n\n // Re-throw other errors\n throw error\n }\n }\n}\n\nfunction cx(...args) {\n return args.filter(Boolean).join(' ')\n}\n\nfunction get(src: Object, path: string, fb?: any) {\n let ptr = src\n const frags = path.split('.')\n\n while (frags.length) {\n ptr = ptr[frags[0]]\n\n if (ptr === undefined) break\n else frags.shift()\n }\n\n return ptr || fb\n}\n\nfunction getTag(tag: string, overrides: MarkdownToJSX.Overrides) {\n const override = get(overrides, tag)\n\n if (!override) return tag\n\n return typeof override === 'function' ||\n (typeof override === 'object' && 'render' in override)\n ? override\n : get(overrides, `${tag}.component`, tag)\n}\n\nfunction attrStringToMap(\n tag: MarkdownToJSX.HTMLTags,\n str: string,\n sanitize: (value: string, tag: string, attribute: string) => string | null,\n compile: (value: string) => React.ReactNode\n): React.JSX.IntrinsicAttributes {\n if (!str || !str.trim()) return null\n\n const attributes = str.match(ATTR_EXTRACTOR_R)\n if (!attributes) return null\n\n return attributes.reduce(function (map, raw) {\n const delimiterIdx = raw.indexOf('=')\n\n if (delimiterIdx !== -1) {\n const key = normalizeAttributeKey(raw.slice(0, delimiterIdx)).trim()\n const mappedKey = ATTRIBUTE_TO_JSX_PROP_MAP[key] || key\n\n if (mappedKey === 'ref') return map\n\n const normalizedValue = (map[mappedKey] = attributeValueToJSXPropValue(\n tag,\n key,\n unquote(raw.slice(delimiterIdx + 1).trim()),\n sanitize\n ))\n\n if (\n typeof normalizedValue === 'string' &&\n (HTML_BLOCK_ELEMENT_START_R.test(normalizedValue) ||\n HTML_SELF_CLOSING_ELEMENT_R.test(normalizedValue))\n ) {\n map[mappedKey] = compile(normalizedValue.trim())\n }\n } else if (raw !== 'style') {\n map[ATTRIBUTE_TO_JSX_PROP_MAP[raw] || raw] = true\n }\n\n return map\n }, {})\n}\n\nfunction some(regexes: RegExp[], input: string) {\n for (let i = 0; i < regexes.length; i++) {\n if (regexes[i].test(input)) {\n return true\n }\n }\n return false\n}\n\nexport function compiler(\n markdown: string,\n options: MarkdownToJSX.Options & {\n wrapper: null\n }\n): React.ReactNode[]\nexport function compiler(\n markdown: string,\n options?: MarkdownToJSX.Options\n): React.JSX.Element\nexport function compiler(\n markdown: string = '',\n options: MarkdownToJSX.Options = {}\n): React.JSX.Element | React.ReactNode[] {\n options.overrides = options.overrides || {}\n options.namedCodesToUnicode = options.namedCodesToUnicode\n ? { ...namedCodesToUnicode, ...options.namedCodesToUnicode }\n : namedCodesToUnicode\n\n const slug = options.slugify || slugify\n const sanitize = options.sanitizer || sanitizer\n const createElement = options.createElement || React.createElement\n\n const NON_PARAGRAPH_BLOCK_SYNTAXES = [\n BLOCKQUOTE_R,\n CODE_BLOCK_FENCED_R,\n CODE_BLOCK_R,\n options.enforceAtxHeadings ? HEADING_ATX_COMPLIANT_R : HEADING_R,\n HEADING_SETEXT_R,\n NP_TABLE_R,\n ORDERED_LIST_R,\n UNORDERED_LIST_R,\n ]\n\n const BLOCK_SYNTAXES = [\n ...NON_PARAGRAPH_BLOCK_SYNTAXES,\n PARAGRAPH_R,\n HTML_BLOCK_ELEMENT_START_R,\n HTML_COMMENT_R,\n HTML_SELF_CLOSING_ELEMENT_R,\n ]\n\n function matchParagraph(source: string, state: MarkdownToJSX.State) {\n if (\n state.inline ||\n state.simple ||\n (state.inHTML &&\n !includes(source, '\\n\\n') &&\n !includes(state.prevCapture, '\\n\\n'))\n )\n return null\n\n let match = ''\n let start = 0\n\n while (true) {\n const nlIdx = source.indexOf('\\n', start)\n const line = source.slice(start, nlIdx === -1 ? undefined : nlIdx + 1)\n const c = source[start]\n\n if (\n (c === '>' ||\n c === '#' ||\n c === '|' ||\n c === '`' ||\n c === '~' ||\n c === '*' ||\n c === '-' ||\n c === '_' ||\n c === ' ') &&\n some(NON_PARAGRAPH_BLOCK_SYNTAXES, line)\n )\n break\n\n match += line\n if (nlIdx === -1 || !line.trim()) break\n start = nlIdx + 1\n }\n\n const captured = trimEnd(match)\n if (captured === '') return null\n\n return [match, , captured] as RegExpMatchArray\n }\n\n // JSX custom pragma\n // eslint-disable-next-line no-unused-vars\n function h(\n // locally we always will render a known string tag\n tag: MarkdownToJSX.HTMLTags,\n props: Parameters<MarkdownToJSX.CreateElement>[1] & {\n className?: string\n id?: string\n },\n ...children\n ) {\n const overrideProps = get(options.overrides, `${tag}.props`, {})\n\n return createElement(\n getTag(tag, options.overrides),\n {\n ...props,\n ...overrideProps,\n className: cx(props?.className, overrideProps.className) || undefined,\n },\n ...children\n )\n }\n\n function compile(\n input: string\n ): React.JSX.Element | React.ReactNode[] | MarkdownToJSX.ASTNode[] {\n input = input.replace(FRONT_MATTER_R, '')\n\n let inline = false\n\n if (options.forceInline) {\n inline = true\n } else if (!options.forceBlock) {\n /**\n * should not contain any