markdown-to-jsx
Version:
Convert markdown to JSX with ease for React and React-like projects. Super lightweight and highly configurable.
1 lines • 101 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../index.tsx","../index.cjs.tsx"],"sourcesContent":["/* @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'\n\n/**\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 /** only available if not `disableHTMLParsing` */\n htmlBlock: '11',\n htmlComment: '12',\n /** only available if not `disableHTMLParsing` */\n htmlSelfClosing: '13',\n image: '14',\n link: '15',\n /** emits a `link` 'node', does not render directly */\n linkAngleBraceStyleDetector: '16',\n /** emits a `link` 'node', does not render directly */\n linkBareUrlDetector: '17',\n /** emits a `link` 'node', does not render directly */\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 textBolded: '28',\n textEmphasized: '29',\n textEscaped: '30',\n textMarked: '31',\n textStrikethroughed: '32',\n unorderedList: '33',\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 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']\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 AUTOLINK_MAILTO_CHECK_R = /mailto:/i\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 = /^(?:( *[-*_])){3,} *(?:\\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 = /^\\s*?\\[(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 *(=|-){3,} *\\n/\n\n/**\n * Explanation:\n *\n * 1. Look for a starting tag, preceded by any amount of spaces\n * ^ *<\n *\n * 2. Capture the tag name (capture 1)\n * ([^ >/]+)\n *\n * 3. Ignore a space after the starting tag and capture the attribute portion of the tag (capture 2)\n * ?([^>]*)>\n *\n * 4. Ensure a matching closing tag is present in the rest of the input string\n * (?=[\\s\\S]*<\\/\\1>)\n *\n * 5. Capture everything until the matching closing tag -- this might include additional pairs\n * of the same tag type found in step 2 (capture 3)\n * ((?:[\\s\\S]*?(?:<\\1[^>]*>[\\s\\S]*?<\\/\\1>)*[\\s\\S]*?)*?)<\\/\\1>\n *\n * 6. Capture excess newlines afterward\n * \\n*\n */\nconst HTML_BLOCK_ELEMENT_R =\n /^ *(?!<[a-z][^ >/]* ?\\/>)<([a-z][^ >/]*) ?((?:[^>]*[^/])?)>\\n?(\\s*(?:<\\1[^>]*?>[\\s\\S]*?<\\/\\1>|(?!<\\1\\b)[\\s\\S])*?)<\\/\\1>(?!<\\/\\1>)\\n*/i\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_MAILTO_R = /^<([^ >]+@[^ >]+)>/\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 * For inline formatting, this partial attempts to ignore characters that\n * may appear in nested formatting that could prematurely trigger detection\n * and therefore miss content that should have been included.\n */\nconst INLINE_SKIP_R =\n '((?:\\\\[.*?\\\\][([].*?[)\\\\]]|<.*?>(?:.*?<.*?>)?|`.*?`|\\\\\\\\\\\\1|[\\\\s\\\\S])+?)'\n\n/**\n * Detect a sequence like **foo** or __foo__. Note that bold has a higher priority\n * than emphasized to support nesting of both since they share a delimiter.\n */\nconst TEXT_BOLD_R = new RegExp(`^([*_])\\\\1${INLINE_SKIP_R}\\\\1\\\\1(?!\\\\1)`)\n\n/**\n * Detect a sequence like *foo* or _foo_.\n */\nconst TEXT_EMPHASIZED_R = new RegExp(`^([*_])${INLINE_SKIP_R}\\\\1(?!\\\\1)`)\n\n/**\n * Detect a sequence like ==foo==.\n */\nconst TEXT_MARKED_R = new RegExp(`^(==)${INLINE_SKIP_R}\\\\1`)\n\n/**\n * Detect a sequence like ~~foo~~.\n */\nconst TEXT_STRIKETHROUGHED_R = new RegExp(`^(~~)${INLINE_SKIP_R}\\\\1`)\n\nconst TEXT_ESCAPED_R = /^\\\\([^0-9A-Za-z\\s])/\nconst TEXT_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](?:(?! |[0-9]\\.|http)[^=*_~\\-\\n<`\\\\\\[!])*/\n\nconst TRIM_STARTING_NEWLINES = /^\\n+/\n\nconst HTML_LEFT_TRIM_AMOUNT_R = /^([ \\t]*)/\n\nconst UNESCAPE_URL_R = /\\\\([^\\\\])/g\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 let lastItemWasAParagraph = false\n\n const itemContent = items.map(function (item, i) {\n // We need to see how far indented the item is:\n const space = LIST_ITEM_PREFIX_R.exec(item)[0].length\n\n // And then we construct a regex to \"unindent\" the subsequent\n // lines of the items by that amount:\n const spaceRegex = new RegExp('^ {1,' + space + '}', 'gm')\n\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 = content.indexOf('\\n\\n') !== -1\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 _render(node, output, state) {\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}\n\nconst LINK_INSIDE = '(?:\\\\[[^\\\\]]*\\\\]|[^\\\\[\\\\]]|\\\\](?=[^\\\\[]*\\\\]))*'\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 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 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.ParserResult[][] {\n const prevInTable = state.inTable\n\n state.inTable = true\n\n let cells: MarkdownToJSX.ParserResult[][] = [[]]\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 let buffer = ''\n let inUrl = false\n let inQuotes = false\n let quoteChar: '\"' | \"'\" | '' = ''\n\n if (!styleString) return styles\n\n for (let i = 0; i < styleString.length; i++) {\n const char = styleString[i]\n\n // Handle quotes\n if ((char === '\"' || char === \"'\") && !inUrl) {\n if (!inQuotes) {\n inQuotes = true\n quoteChar = char\n } else if (char === quoteChar) {\n inQuotes = false\n quoteChar = ''\n }\n }\n\n // Track url() values\n if (char === '(' && buffer.endsWith('url')) {\n inUrl = true\n } else if (char === ')' && inUrl) {\n inUrl = false\n }\n\n // Only split on semicolons when not in quotes or url()\n if (char === ';' && !inQuotes && !inUrl) {\n const declaration = buffer.trim()\n if (declaration) {\n const colonIndex = declaration.indexOf(':')\n if (colonIndex > 0) {\n const key = declaration.slice(0, colonIndex).trim()\n const value = declaration.slice(colonIndex + 1).trim()\n styles.push([key, value])\n }\n }\n buffer = ''\n } else {\n buffer += char\n }\n }\n\n // Handle the last declaration\n const declaration = buffer.trim()\n if (declaration) {\n const colonIndex = declaration.indexOf(':')\n if (colonIndex > 0) {\n const key = declaration.slice(0, colonIndex).trim()\n const value = declaration.slice(colonIndex + 1).trim()\n styles.push([key, value])\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 // snake-case to camelCase\n // also handles PascalCasing vendor prefixes\n const camelCasedKey = key.replace(/(-[a-z])/g, substr =>\n substr[1].toUpperCase()\n )\n\n // key.length + 1 to skip over the colon\n styles[camelCasedKey] = sanitizeUrlFn(value, tag, key)\n\n return styles\n }, {})\n } else if (ATTRIBUTES_TO_SANITIZE.indexOf(key) !== -1) {\n return sanitizeUrlFn(value, tag, key)\n } else if (value.match(INTERPOLATION_R)) {\n // return as a string and let the consumer decide what to do with it\n value = value.slice(1, value.length - 1)\n }\n\n if (value === 'true') {\n return true\n } else if (value === 'false') {\n return false\n }\n\n return 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.ParserResult[] {\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 state.prevCapture += capture[0]\n source = source.substring(capture[0].length)\n\n var parsed = rule._parse(capture, nestedParse, state)\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 unescapeUrl(rawUrlString: string): string {\n return rawUrlString.replace(UNESCAPE_URL_R, '$1')\n}\n\n/**\n * Everything inline, including links.\n */\nfunction parseInline(\n parse: MarkdownToJSX.NestedParser,\n children: string,\n state: MarkdownToJSX.State\n): MarkdownToJSX.ParserResult[] {\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.ParserResult[] {\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.ParserResult[] {\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.ParserResult[]\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 renderNothing() {\n return null\n}\n\nfunction reactFor(render) {\n return function patchedRender(\n ast: MarkdownToJSX.ParserResult | MarkdownToJSX.ParserResult[],\n state: MarkdownToJSX.State = {}\n ): React.ReactNode[] {\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 = typeof nodeOut === 'string'\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\n return result\n }\n\n return render(ast, patchedRender, state)\n }\n}\n\nfunction createRenderer(\n rules: MarkdownToJSX.Rules,\n userRender?: MarkdownToJSX.Options['renderRule']\n) {\n return function renderRule(\n ast: MarkdownToJSX.ParserResult,\n render: MarkdownToJSX.RuleOutput,\n state: MarkdownToJSX.State\n ): React.ReactNode {\n const renderer = rules[ast.type]._render as MarkdownToJSX.Rule['_render']\n\n return userRender\n ? userRender(() => renderer(ast, render, state), ast, render, state)\n : renderer(ast, render, state)\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\nexport function compiler(\n markdown: string = '',\n options: MarkdownToJSX.Options = {}\n): React.JSX.Element {\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_R,\n HTML_COMMENT_R,\n HTML_SELF_CLOSING_ELEMENT_R,\n ]\n\n function containsBlockSyntax(input: string) {\n return BLOCK_SYNTAXES.some(r => r.test(input))\n }\n\n function matchParagraph(source: string, state: MarkdownToJSX.State) {\n if (state.inline || state.simple) {\n return null\n }\n\n let match = ''\n\n source.split('\\n').every(line => {\n line += '\\n'\n\n // bail out on first sign of non-paragraph block\n if (NON_PARAGRAPH_BLOCK_SYNTAXES.some(regex => regex.test(line))) {\n return false\n }\n\n match += line\n\n return !!line.trim()\n })\n\n const captured = trimEnd(match)\n if (captured === '') {\n return null\n }\n\n // parseCaptureInline expects the inner content to be at index 2\n // because index 1 is the delimiter for text formatting syntaxes\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(input: string): React.JSX.Element {\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 block-level markdown like newlines, lists, headings,\n * thematic breaks, blockquotes, tables, etc\n */\n inline = SHOULD_RENDER_AS_BLOCK_R.test(input) === false\n }\n\n const arr = emitter(\n parser(\n inline\n ? input\n : `${trimEnd(input).replace(TRIM_STARTING_NEWLINES, '')}\\n\\n`,\n {\n inline,\n }\n )\n )\n\n while (\n typeof arr[arr.length - 1] === 'string' &&\n !arr[arr.length - 1].trim()\n ) {\n arr.pop()\n }\n\n if (options.wrapper === null) {\n return arr\n }\n\n const wrapper = options.wrapper || (inline ? 'span' : 'div')\n let jsx\n\n if (arr.length > 1 || options.forceWrapper) {\n jsx = arr\n } else if (arr.length === 1) {\n jsx = arr[0]\n\n // TODO: remove this for React 16\n if (typeof jsx === 'string') {\n return <span key=\"outer\">{jsx}</span>\n } else {\n return jsx\n }\n } else {\n // TODO: return null for React 16\n jsx = null\n }\n\n return createElement(wrapper, { key: 'outer' }, jsx) as React.JSX.Element\n }\n\n function attrStringToMap(\n tag: MarkdownToJSX.HTMLTags,\n str: string\n ): React.JSX.IntrinsicAttributes {\n if (!str || !str.trim()) {\n return null\n }\n\n const attributes = str.match(ATTR_EXTRACTOR_R)\n if (!attributes) {\n return null\n }\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 value = unquote(raw.slice(delimiterIdx + 1).trim())\n\n const mappedKey = ATTRIBUTE_TO_JSX_PROP_MAP[key] || key\n\n // bail out, not supported\n if (mappedKey === 'ref') return map\n\n const normalizedValue = (map[mappedKey] = attributeValueToJSXPropValue(\n tag,\n key,\n value,\n sanitize\n ))\n\n if (\n typeof normalizedValue === 'string' &&\n (HTML_BLOCK_ELEMENT_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\n if (process.env.NODE_ENV !== 'production') {\n if (typeof markdown !== 'string') {\n throw new Error(`markdown-to-jsx: the first argument must be\n a string`)\n }\n\n if (\n Object.prototype.toString.call(options.overrides) !== '[object Object]'\n ) {\n throw new Error(`markdown-to-jsx: options.overrides (second argument property) must be\n undefined or an object literal with shape:\n {\n htmltagname: {\n component: string|ReactComponent(optional),\n props: object(optional)\n }\n }`)\n }\n }\n\n const footnotes: { footnote: string; identifier: string }[] = []\n const refs: { [key: string]: { target: string; title: string } } = {}\n\n /**\n * each rule's react() output function goes through our custom\n * h() JSX pragma; this allows the override functionality to be\n * automatically applied\n */\n // @ts-ignore\n const rules: MarkdownToJSX.Rules = {\n [RuleType.blockQuote]: {\n _qualify: ['>'],\n _match: blockRegex(BLOCKQUOTE_R),\n _order: Priority.HIGH,\n _parse(capture, parse, state) {\n const [, alert, content] = capture[0]\n .replace(BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, '')\n .match(BLOCKQUOTE_ALERT_R)\n\n return {\n alert,\n children: parse(content, state),\n }\n },\n _render(node, output, state) {\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\n [RuleType.breakLine]: {\n _match: anyScopeRegex(BREAK_LINE_R),\n _order: Priority.HIGH,\n _parse: captureNothing,\n _render(_, __, state) {\n return <br key={state.key} />\n },\n },\n\n [RuleType.breakThematic]: {\n _qualify: source => {\n const char = source[0]\n return char === '-' || char === '*' || char === '_'\n },\n _match: blockRegex(BREAK_THEMATIC_R),\n _order: Priority.HIGH,\n _parse: captureNothing,\n _render(_, __, state) {\n return <hr key={state.key} />\n },\n },\n\n [RuleType.codeBlock]: {\n _qualify: [' '],\n _match: blockRegex(CODE_BLOCK_R),\n _order: Priority.MAX,\n _parse(capture /*, parse, state*/) {\n return {\n lang: undefined,\n text: trimEnd(capture[0].replace(/^ {4}/gm, '')).replace(\n TEXT_UNESCAPE_R,\n '$1'\n ),\n }\n },\n\n _render(node, output, state) {\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 } as MarkdownToJSX.Rule<{\n attrs?: ReturnType<typeof attrStringToMap>\n lang?: string\n text: string\n }>,\n\n [RuleType.codeFenced]: {\n _qualify: ['```', '~~~'],\n _match: blockRegex(CODE_BLOCK_FENCED_R),\n _order: Priority.MAX,\n _parse(capture /*, parse, state*/) {\n return {\n // if capture[3] it's additional metadata\n attrs: attrStringToMap('code', capture[3] || ''),\n lang: capture[2] || undefined,\n text: capture[4],\n type: RuleType.codeBlock,\n }\n },\n },\n\n [RuleType.codeInline]: {\n _qualify: ['`'],\n _match: simpleInlineRegex(CODE_INLINE_R),\n _order: Priority.LOW,\n _parse(capture /*, parse, state*/) {\n return {\n text: capture[2].replace(TEXT_UNESCAPE_R, '$1'),\n }\n },\n _render(node, output, state) {\n return <code key={state.key}>{node.text}</code>\n },\n },\n\n /**\n * footnotes are emitted at the end of compilation in a special <footer> block\n */\n [RuleType.footnote]: {\n _qualify: ['[^'],\n _match: blockRegex(FOOTNOTE_R),\n _order: Priority.MAX,\n _parse(capture /*, parse, state*/) {\n footnotes.push({\n footnote: capture[2],\n identifier: capture[1],\n })\n\n return {}\n },\n _render: renderNothing,\n },\n\n [RuleType.footnoteReference]: {\n _qualify: ['[^'],\n _match: inlineRegex(FOOTNOTE_REFERENCE_R),\n _order: Priority.HIGH,\n _parse(capture /*, parse*/) {\n return {\n target: `#${slug(capture[1], slugify)}`,\n text: capture[1],\n }\n },\n _render(node, output, state) {\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 } as MarkdownToJSX.Rule<{ target: string; text: string }>,\n\n [RuleType.gfmTask]: {\n _qualify: ['[ ]', '[x]'],\n _match: inlineRegex(GFM_TASK_R),\n _order: Priority.HIGH,\n _parse(capture /*, parse, state*/) {\n return {\n completed: capture[1].toLowerCase() === 'x',\n }\n },\n _render(node, output, state) {\n return (\n <input\n checked={node.completed}\n key={state.key}\n readOnly\n type=\"checkbox\"\n />\n )\n },\n } as MarkdownToJSX.Rule<{ completed: boolean }>,\n\n [RuleType.heading]: {\n _qualify: ['#'],\n _match: blockRegex(\n options.enforceAtxHeadings ? HEADING_ATX_COMPLIANT_R : HEADING_R\n ),\n _order: Priority.HIGH,\n _parse(capture, parse, state) {\n return {\n children: parseInline(parse, capture[2], state),\n id: slug(capture[2], slugify),\n level: capture[1].length as MarkdownToJSX.HeadingNode['level'],\n }\n },\n _render(node, output, state) {\n return h(\n `h${node.level}`,\n { id: node.id, key: state.key },\n output(node.children, state)\n )\n },\n },\n\n [RuleType.headingSetext]: {\n _match: blockRegex(HEADING_SETEXT_R),\n _order: Priority.MAX,\n _parse(capture, parse, state) {\n return {\n children: parseInline(parse, capture[1], state),\n level: capture[2] === '=' ? 1 : 2,\n type: RuleType.heading,\n }\n },\n },\n\n [RuleType.htmlBlock]: {\n _qualify: ['<'],\n /**\n * find the first matching end tag and process the interior\n */\n _match: anyScopeRegex(HTML_BLOCK_ELEMENT_R),\n _order: Priority.HIGH,\n _parse(capture, parse, state) {\n const [, whitespace] = capture[3].match(HTML_LEFT_TRIM_AMOUNT_R)\n\n const trimmer = new RegExp(`^${whitespace}`, 'gm')\n const trimmed = capture[3].replace(trimmer, '')\n\n const parseFunc = containsBlockSyntax(trimmed)\n ? parseBlock\n : parseInline\n\n const tagName = capture[1].toLowerCase() as MarkdownToJSX.HTMLTags\n const noInnerParse =\n DO_NOT_PROCESS_HTML_ELEMENTS.indexOf(tagName) !== -1\n\n const tag = (\n noInnerParse ? tagName : capture[1]\n ).trim() as MarkdownToJSX.HTMLTags\n\n const ast = {\n attrs: attrStringToMap(tag, capture[2]),\n noInnerParse: noInnerParse,\n tag,\n } as {\n attrs: ReturnType<typeof attrStringToMap>\n children?: ReturnType<MarkdownToJSX.NestedParser> | undefined\n noInnerParse: Boolean\n tag: MarkdownToJSX.HTMLTags\n text?: string | undefined\n }\n\n state.inAnchor = state.inAnchor || tagName === 'a'\n\n if (noInnerParse) {\n ast.text = capture[3]\n } else {\n ast.children = parseFunc(parse, trimmed, state)\n }\n\n /**\n * if another html block is detected within, parse as block,\n * otherwise parse as inline to pick up any further markdown\n */\n state.inAnchor = false\n\n return ast\n },\n _render(node, output, state) {\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 },\n\n [RuleType.htmlSelfClosing]: {\n _qualify: ['<'],\n /**\n * find the first matching end tag and process the interior\n */\n _match: anyScopeRegex(HTML_SELF_CLOSING_ELEMENT_R),\n _order: Priority.HIGH,\n _parse(capture /*, parse, state*/) {\n const tag = capture[1].trim() as MarkdownToJSX.HTMLTags\n return {\n attrs: attrStringToMap(tag, capture[2] || ''),\n tag,\n }\n },\n _render(node, output, state) {\n return <node.tag {...node.attrs} key={state.key} />\n },\n },\n\n [RuleType.htmlComment]: {\n _qualify: ['<!--'],\n _match: anyScopeRegex(HTML_COMMENT_R),\n _order: Priority.HIGH,\n _parse() {\n return {}\n },\n _render: renderNothing,\n },\n\n [RuleType.image]: {\n _qualify: ['!['],\n _match: simpleInlineRegex(IMAGE_R),\n _order: Priority.HIGH,\n _parse(capture /*, parse, state*/) {\n return {\n alt: capture[1],\n target: unescapeUrl(capture[2]),\n title: capture[3],\n }\n },\n _render(node, output, state) {\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 } as MarkdownToJSX.Rule<{\n alt?: string\n target: string\n title?: string\n }>,\n\n [RuleType.link]: {\n _qualify: ['['],\n _match: inlineRegex(LINK_R),\n _order: Priority.LOW,\n _parse(capture, parse, state) {\n return {\n