Socket
Socket
Sign inDemoInstall

markdown-to-jsx

Package Overview
Dependencies
Maintainers
1
Versions
110
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

markdown-to-jsx - npm Package Compare versions

Comparing version 5.4.2 to 6.0.2

1430

index.cjs.js

@@ -7,9 +7,13 @@ 'use strict';

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
exports.compiler = compiler;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; /* @jsx h */
/**
* markdown-to-jsx@6 is a fork of [simple-markdown v0.2.2](https://github.com/Khan/simple-markdown)
* from Khan Academy. Thank you Khan devs for making such an awesome and extensible
* parsing infra... without it, half of the optimizations here wouldn't be feasible. 🙏🏼
*/
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
exports.compiler = compiler;

@@ -20,29 +24,21 @@ var _propTypes = require('prop-types');

var _lodash = require('lodash.get');
var _react = require('react');
var _lodash2 = _interopRequireDefault(_lodash);
var _react2 = _interopRequireDefault(_react);
var _unified = require('unified');
var _unquote = require('unquote');
var _unified2 = _interopRequireDefault(_unified);
var _unquote2 = _interopRequireDefault(_unquote);
var _remarkParse = require('remark-parse');
var _remarkParse2 = _interopRequireDefault(_remarkParse);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
var BLOCK_ELEMENT_TAGS = ['article', 'aside', 'blockquote', 'body', 'button', 'canvas', 'caption', 'col', 'colgroup', 'dd', 'details', 'div', 'dl', 'dt', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'iframe', 'li', 'map', 'object', 'ol', 'output', 'p', 'pre', 'progress', 'script', 'section', 'style', 'summary', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'ul', 'video'];
var BLOCK_ELEMENT_REGEX = new RegExp('^<(' + BLOCK_ELEMENT_TAGS.join('|') + ')', 'i');
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
// [0] === tag, [...] = attribute pairs
var HTML_EXTRACTOR_REGEX = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
var SELF_CLOSING_ELEMENT_TAGS = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
var SELF_CLOSING_ELEMENT_REGEX = new RegExp('^<(' + SELF_CLOSING_ELEMENT_TAGS.join('|') + ')', 'i');
var TEXT_AST_TYPES = ['text', 'textNode'];
var getType = Object.prototype.toString;
/** TODO: Drop for React 16? */
var ATTRIBUTE_TO_JSX_PROP_MAP = {

@@ -95,458 +91,1151 @@ 'accept-charset': 'acceptCharset',

var getType = Object.prototype.toString;
/** TODO: Write explainers for each of these */
var ATTR_EXTRACTOR_R = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
var AUTOLINK_MAILTO_CHECK_R = /mailto:/i;
var BLOCK_END_R = /\n{2,}$/;
var BLOCKQUOTE_R = /^( *>[^\n]+(\n[^\n]+)*\n*)+\n{2,}/;
var BLOCKQUOTE_TRIM_LEFT_MULTILINE_R = /^ *> ?/gm;
var BREAK_LINE_R = /^ {2,}\n/;
var BREAK_THEMATIC_R = /^( *[-*_]){3,} *(?:\n *)+\n/;
var CODE_BLOCK_FENCED_R = /^\s*(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/;
var CODE_BLOCK_R = /^(?: {4}[^\n]+\n*)+(?:\n *)+\n/;
var CODE_INLINE_R = /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/;
var CONSECUTIVE_NEWLINE_R = /^(?:\n *)*\n/;
var CR_NEWLINE_R = /\r\n?/g;
var FOOTNOTE_R = /^\[\^(.*)\](:.*)\n/;
var FOOTNOTE_REFERENCE_R = /^\[\^(.*)\]/;
var FORMFEED_R = /\f/g;
var GFM_TASK_R = /^\s*?\[(x|\s)\]/;
var HEADING_R = /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n *)+\n/;
var HEADING_SETEXT_R = /^([^\n]+)\n *(=|-){3,} *(?:\n *)+\n/;
var HTML_BLOCK_ELEMENT_R = /^<(.*)\s?(.*?)>(.*?)<\/\1>/;
var HTML_COMMENT_R = /^<!--.*?-->/;
var HTML_SELF_CLOSING_ELEMENT_R = /^<([^\s]*)\s?(.*?)>(.*?)(?!<\/\1>)/;
var LINK_AUTOLINK_BARE_URL_R = /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/;
var LINK_AUTOLINK_MAILTO_R = /^<([^ >]+@[^ >]+)>/;
var LINK_AUTOLINK_R = /^<([^ >]+:\/[^ >]+)>/;
var LIST_ITEM_END_R = / *\n+$/;
var LIST_LOOKBEHIND_R = /^$|\n *$/;
var NP_TABLE_R = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/;
var NPTABLE_CELLS_TRIM = /\n$/;
var PARAGRAPH_R = /^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/;
var REFERENCE_IMAGE_OR_LINK = /^\[([^\]]*)\]:\s*(\S+)\s*("([^"]*)")?/;
var REFERENCE_IMAGE_R = /^!\[([^\]]*)\]\[([^\]]*)\]/;
var REFERENCE_LINK_R = /^\[([^\]]*)\]\[([^\]]*)\]/;
var TAB_R = /\t/g;
var TABLE_ALIGN_TRIM = /^ *|\| *$/g;
var TABLE_CENTER_ALIGN = /^ *:-+: *$/;
var TABLE_HEADER_TRIM = /^ *| *\| *$/g;
var TABLE_LEFT_ALIGN = /^ *:-+ *$/;
var TABLE_RIGHT_ALIGN = /^ *-+: *$/;
var TABLE_ROW_SPLIT = / *\| */;
var TEXT_BOLD_R = /^[*_]{2}([\s\S]+?)[*_]{2}(?!\*|_)/;
var TEXT_EMPHASIZED_R = /^[*_]{1}([\s\S]+?)[*_]{1}(?!\*|_)/;
var TEXT_ESCAPED_R = /^\\([^0-9A-Za-z\s])/;
var TEXT_PLAIN_R = /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/;
var TEXT_STRIKETHROUGHED_R = /^~~(?=\S)([\s\S]*?\S)~~/;
var UNESCAPE_URL_R = /\\([^0-9A-Za-z\s])/g;
function extractDefinitionsFromASTTree(ast, parser) {
function reducer(aggregator, node) {
if (node.type === 'definition' || node.type === 'footnoteDefinition') {
aggregator.definitions[node.identifier] = node;
// recognize a `*` `-`, `+`, `1.`, `2.`... list bullet
var LIST_BULLET = '(?:[*+-]|\\d+\\.)';
if (node.type === 'footnoteDefinition') {
if (node.children && node.children.length === 1 && node.children[0].type === 'paragraph') {
node.children[0].children.unshift({
type: 'textNode',
value: '[' + node.identifier + ']: '
});
} /* package the prefix inside the first child */
// recognize the start of a list item:
// leading space plus a bullet plus a space (` * `)
var LIST_ITEM_PREFIX = '( *)(' + LIST_BULLET + ') +';
var LIST_ITEM_PREFIX_R = new RegExp('^' + LIST_ITEM_PREFIX);
aggregator.footnotes.push(_react2.default.createElement(
'div',
{ key: node.identifier, id: node.identifier },
node.value || node.children.map(parser)
));
}
}
// 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
var LIST_ITEM_R = new RegExp(LIST_ITEM_PREFIX + '[^\\n]*(?:\\n' + '(?!\\1' + LIST_BULLET + ' )[^\\n]*)*(\n|$)', 'gm');
return Array.isArray(node.children) ? node.children.reduce(reducer, aggregator) : aggregator;
};
// check whether a list item has paragraphs: if it does,
// we leave the newlines at the end
var LIST_R = new RegExp('^( *)(' + LIST_BULLET + ') ' + '[\\s\\S]+?(?:\n{2,}(?! )' + '(?!\\1' + LIST_BULLET + ' )\\n*' +
// the \\s*$ here is so that we can parse the inside of nested
// lists, where our content might end before we receive two `\n`s
'|\\s*\n*$)');
return [ast].reduce(reducer, {
definitions: {},
footnotes: []
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 = new RegExp('^!\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)');
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';
} else {
return null;
}
}
function parseTableHeader(capture, parse, state) {
var headerText = capture[1].replace(TABLE_HEADER_TRIM, '').split(TABLE_ROW_SPLIT);
return headerText.map(function (text) {
return parse(text, state);
});
}
function formExtraPropsForHTMLNodeType() {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var ast = arguments[1];
var definitions = arguments[2];
function parseTableAlign(capture /*, parse, state*/) {
var alignText = capture[2].replace(TABLE_ALIGN_TRIM, '').split(TABLE_ROW_SPLIT);
switch (ast.type) {
case 'footnoteReference':
return _extends({}, props, {
href: '#' + ast.identifier
});
return alignText.map(parseTableAlignCapture);
}
case 'image':
return _extends({}, props, {
title: ast.title,
alt: ast.alt,
src: ast.url
});
function parseTableCells(capture, parse, state) {
var rowsText = capture[3].replace(NPTABLE_CELLS_TRIM, '').split('\n');
case 'imageReference':
return _extends({}, props, {
title: (0, _lodash2.default)(definitions, '[\'' + ast.identifier + '\'].title'),
alt: ast.alt,
src: (0, _lodash2.default)(definitions, '[\'' + ast.identifier + '\'].url')
});
return rowsText.map(function (rowText) {
var cellText = rowText.split(TABLE_ROW_SPLIT);
return cellText.map(function (text) {
return parse(text, state);
});
});
}
case 'link':
return _extends({}, props, {
title: ast.title,
href: ast.url
});
function parseTable(capture, parse, state) {
state.inline = true;
var header = parseTableHeader(capture, parse, state);
var align = parseTableAlign(capture, parse, state);
var cells = parseTableCells(capture, parse, state);
state.inline = false;
case 'linkReference':
return _extends({}, props, {
title: (0, _lodash2.default)(definitions, '[\'' + ast.identifier + '\'].title'),
href: (0, _lodash2.default)(definitions, '[\'' + ast.identifier + '\'].url')
});
return {
align: align,
cells: cells,
header: header,
type: 'table'
};
}
case 'list':
return _extends({}, props, {
start: ast.start
function attributeValueToJSXPropValue(key, value) {
if (key === 'style') {
return value.split(/;\s?/).reduce(function (styles, kvPair) {
var key = kvPair.slice(0, kvPair.indexOf(':'));
// snake-case to camelCase
// also handles PascalCasing vendor prefixes
var camelCasedKey = key.replace(/(-[a-z])/g, function toUpper(substr) {
return substr[1].toUpperCase();
});
case 'tableCell':
case 'th':
return _extends({}, props, {
style: { textAlign: ast.align }
});
// key.length + 1 to skip over the colon
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim();
return styles;
}, {});
}
return props;
return value;
}
function getHTMLNodeTypeFromASTNodeType(node) {
switch (node.type) {
case 'break':
return 'br';
function attrStringToMap(str) {
var attributes = str.match(ATTR_EXTRACTOR_R);
case 'delete':
return 'del';
return attributes ? attributes.reduce(function (map, raw) {
var tuple = raw.split('=');
var key = tuple[0];
var value = tuple[1];
case 'emphasis':
return 'em';
map[ATTRIBUTE_TO_JSX_PROP_MAP[key] || key] = attributeValueToJSXPropValue(key, (0, _unquote2.default)(value));
case 'footnoteReference':
return 'a';
return map;
}, {}) : undefined;
}
case 'heading':
return 'h' + node.depth;
// Turn various crazy whitespace into easy to process things
function preprocess(source) {
return source.replace(CR_NEWLINE_R, '\n').replace(FORMFEED_R, '').replace(TAB_R, ' ');
}
case 'image':
case 'imageReference':
return 'img';
/**
* 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) {
// Sorts rules in order of increasing order, then
// ascending rule name in case of ties.
var ruleList = Object.keys(rules);
case 'inlineCode':
return 'code';
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production') {
ruleList.forEach(function (type) {
var order = rules[type].order;
if (process.env.NODE_ENV !== 'production' && (typeof order !== 'number' || !isFinite(order)) && typeof console !== 'undefined') {
console.warn('simple-markdown: Invalid order for rule `' + type + '`: ' + order);
}
});
}
case 'link':
case 'linkReference':
return 'a';
ruleList.sort(function (typeA, typeB) {
var orderA = rules[typeA].order;
var orderB = rules[typeB].order;
case 'list':
return node.ordered ? 'ol' : 'ul';
// First sort based on increasing order
if (orderA !== orderB) {
return orderA - orderB;
case 'listItem':
return 'li';
// Then based on increasing unicode lexicographic ordering
} else if (typeA < typeB) {
return -1;
}
case 'paragraph':
return 'p';
return 1;
});
case 'root':
return 'div';
function nestedParse(source, state) {
var result = [];
case 'tableHeader':
return 'thead';
// We store the previous capture so that match functions can
// use some limited amount of lookbehind. Lists use this to
// ensure they don't match arbitrary '- ' or '* ' in inline
// text (see the list rule for more information).
var prevCapture = '';
while (source) {
var i = 0;
while (i < ruleList.length) {
var ruleType = ruleList[i];
var rule = rules[ruleType];
var capture = rule.match(source, state, prevCapture);
case 'tableRow':
return 'tr';
if (capture) {
var currCaptureString = capture[0];
source = source.substring(currCaptureString.length);
var parsed = rule.parse(capture, nestedParse, state);
case 'tableCell':
return 'td';
// We also let rules override the default type of
// their parsed node if they would like to, so that
// there can be a single output function for all links,
// even if there are several rules to parse them.
if (parsed.type == null) {
parsed.type = ruleType;
}
case 'thematicBreak':
return 'hr';
result.push(parsed);
case 'definition':
case 'footnoteDefinition':
case 'yaml':
return null;
prevCapture = currCaptureString;
break;
}
default:
return node.type;
i++;
}
}
return result;
}
return function outerParse(source, state) {
return nestedParse(preprocess(source), state);
};
}
function seekCellsAndAlignThemIfNecessary(root, alignmentValues) {
var mapper = function mapper(child, index) {
if (child.type === 'tableCell') {
return _extends({}, child, {
align: alignmentValues[index]
});
} else if (Array.isArray(child.children) && child.children.length) {
return child.children.map(mapper);
// Creates a match function for an inline scoped element from a regex
function inlineRegex(regex) {
function match(source, state) {
if (state.inline) {
return regex.exec(source);
} else {
return null;
}
}
return child;
};
match.regex = regex;
if (Array.isArray(root.children) && root.children.length) {
root.children = root.children.map(mapper);
return match;
}
// Creates a match function for a block scoped element from a regex
function blockRegex(regex) {
function match(source, state) {
if (state.inline) {
return null;
} else {
return regex.exec(source);
}
}
return root;
match.regex = regex;
return match;
}
function attributeValueToJSXPropValue(key, value) {
if (key === 'style') {
return value.split(/;\s?/).reduce(function (styles, kvPair) {
// Creates a match function from a regex, ignoring block/inline scope
function anyScopeRegex(regex) {
function match(source /*, state*/) {
return regex.exec(source);
}
var key = kvPair.slice(0, kvPair.indexOf(':'));
match.regex = regex;
// snake-case to camelCase
// also handles PascalCasing vendor prefixes
var camelCasedKey = key.replace(/(\-[a-z])/g, function (substr) {
return substr[1].toUpperCase();
});
return match;
}
// key.length + 1 to skip over the colon
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim();
function reactFor(outputFunc) {
return function nestedReactOutput(ast, state) {
state = state || {};
if (Array.isArray(ast)) {
var oldKey = state.key;
var result = [];
return styles;
}, {});
// 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 = nestedReactOutput(ast[i], state);
var isString = typeof nodeOut === 'string';
if (isString && lastWasString) {
result[result.length - 1] += nodeOut;
} else {
result.push(nodeOut);
}
lastWasString = isString;
}
state.key = oldKey;
return result;
} else {
return outputFunc(ast, nestedReactOutput, state);
}
};
}
function sanitizeUrl(url) {
try {
var prot = decodeURIComponent(url).replace(/[^A-Za-z0-9/:]/g, '').toLowerCase();
if (prot.indexOf('javascript:') === 0) {
return null;
}
} catch (e) {
// decodeURIComponent sometimes throws a URIError
// See `decodeURIComponent('a%AFc');`
// http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception
return null;
}
return value;
return url;
}
function isCoalesceableHTML(html) {
// ignore block-level elements
// ignore self-closing or non-content-bearing elements
return html.match(BLOCK_ELEMENT_REGEX) || html.match(SELF_CLOSING_ELEMENT_REGEX) ? false : true;
function unescapeUrl(rawUrlString) {
return rawUrlString.replace(UNESCAPE_URL_R, '$1');
}
function coalesceInlineHTML(ast) {
function coalescer(node, index, siblings) {
if (node.type === 'html') {
if (!isCoalesceableHTML(node.value)) {
return;
} else if (node.value.indexOf('<!--') !== -1) {
// throw out HTML comments
siblings.splice(index, 1);
// Parse some content with the parser `parse`, with state.inline
// set to true. Useful for block elements; not generally necessary
// to be used by inline elements (where state.inline is already true.
function parseInline(parse, content, state) {
var isCurrentlyInline = state.inline || false;
state.inline = true;
var result = parse(content, state);
state.inline = isCurrentlyInline;
return result;
}
function parseBlock(parse, content, state) {
state.inline = false;
return parse(content + '\n\n', state);
}
function parseCaptureInline(capture, parse, state) {
return {
content: parseInline(parse, capture[1], state)
};
}
function captureNothing() {
return {};
}
function renderNothing() {
return null;
}
function ruleOutput(rules) {
return function nestedRuleOutput(ast, outputFunc, state) {
return rules[ast.type].react(ast, outputFunc, state);
};
}
/**
* anything that must scan the tree before everything else
*/
var PARSE_PRIORITY_MAX = 1;
/**
* scans for block-level constructs
*/
var PARSE_PRIORITY_HIGH = 2;
/**
* inline w/ more priority than other inline
*/
var PARSE_PRIORITY_MED = 3;
/**
* inline elements
*/
var PARSE_PRIORITY_LOW = 4;
/**
* bare text and stuff that is considered leftovers
*/
var PARSE_PRIORITY_MIN = 5;
function compiler(markdown) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$overrides = _ref.overrides,
overrides = _ref$overrides === undefined ? {} : _ref$overrides;
// eslint-disable-next-line no-unused-vars
function h(tag, props) {
for (var _len = arguments.length, children = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
children[_key - 2] = arguments[_key];
}
return _react2.default.createElement.apply(_react2.default, [overrides[tag] && overrides[tag].component || tag, _extends({}, overrides[tag] && overrides[tag].props, props)].concat(children));
}
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production') {
if (typeof markdown !== 'string') {
throw new Error('markdown-to-jsx: the first argument must be\n a string');
}
if (getType.call(overrides) !== '[object Object]') {
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 }');
}
}
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
*/
var rules = {
blockQuote: {
match: blockRegex(BLOCKQUOTE_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse, state) {
return {
content: _parse(capture[0].replace(BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, ''), state)
};
},
react: function react(node, output, state) {
return h(
'blockquote',
{ key: state.key },
output(node.content, state)
);
}
},
// are there more html nodes directly after? if so, fold them into the current node
if (index < siblings.length - 1 && siblings[index + 1].type === 'html') {
// create a new coalescer context
coalescer(siblings[index + 1], index + 1, siblings);
breakLine: {
match: anyScopeRegex(BREAK_LINE_R),
order: PARSE_PRIORITY_HIGH,
parse: captureNothing,
react: function react(_, __, state) {
return h('br', { key: state.key });
}
},
var i = index + 1;
var end = void 0;
breakThematic: {
match: blockRegex(BREAK_THEMATIC_R),
order: PARSE_PRIORITY_HIGH,
parse: captureNothing,
react: function react(_, __, state) {
return h('hr', { key: state.key });
}
},
// where's the end tag?
while (end === undefined && i < siblings.length) {
if (siblings[i].type !== 'html' || siblings[i].type === 'html' && !isCoalesceableHTML(siblings[i].value)) {
i += 1;
continue;
}
codeBlock: {
match: blockRegex(CODE_BLOCK_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
var content = capture[0].replace(/^ {4}/gm, '').replace(/\n+$/, '');
return {
content: content,
lang: undefined
};
},
react: function react(node, output, state) {
return h(
'pre',
{ key: state.key },
h(
'code',
{ className: node.lang ? 'lang-' + node.lang : '' },
node.content
)
);
}
},
end = siblings[i];
codeFenced: {
match: blockRegex(CODE_BLOCK_FENCED_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[3],
lang: capture[2] || undefined,
type: 'codeBlock'
};
}
},
/* all interim elements now become children of the current node, and we splice them (including end tag)
out of the sibling array so they will not be iterated-over by forEach */
codeInline: {
match: inlineRegex(CODE_INLINE_R),
order: PARSE_PRIORITY_LOW,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[2]
};
},
react: function react(node, output, state) {
return h(
'code',
{ key: state.key },
node.content
);
}
},
node.children = siblings.slice(index + 1, i);
siblings.splice(index + 1, i - index);
/**
* footnotes are emitted at the end of compilation in a special <footer> block
*/
footnote: {
match: blockRegex(FOOTNOTE_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
footnotes.push({
footnote: capture[2],
identifier: capture[1]
});
var _node$value$match = node.value.match(HTML_EXTRACTOR_REGEX),
_node$value$match2 = _toArray(_node$value$match),
tag = _node$value$match2[0],
attributePairs = _node$value$match2.slice(1);
return {};
},
// reassign the current node to whatever its tag is
react: renderNothing
},
footnoteReference: {
match: inlineRegex(FOOTNOTE_REFERENCE_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse*/) {
return {
content: capture[1],
target: '#' + capture[1]
};
},
react: function react(node, output, state) {
return h(
'a',
{ key: state.key, href: sanitizeUrl(node.target) },
h(
'sup',
{ key: state.key },
node.content
)
);
}
},
node.type = tag.toLowerCase();
gfmTask: {
match: inlineRegex(GFM_TASK_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
return {
completed: capture[1].toLowerCase() === 'x'
};
},
react: function react(node, output, state) {
return h('input', {
checked: node.completed,
key: state.key,
readOnly: true,
type: 'checkbox'
});
}
},
// make a best-effort conversion to JSX props
node.props = attributePairs.reduce(function (props, kvPair) {
var valueIndex = kvPair.indexOf('=');
var key = kvPair.slice(0, valueIndex === -1 ? undefined : valueIndex);
heading: {
match: blockRegex(HEADING_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse2, state) {
return {
content: parseInline(_parse2, capture[2], state),
level: capture[1].length
};
},
react: function react(node, output, state) {
var Tag = 'h' + node.level;
return h(
Tag,
{ key: state.key },
output(node.content, state)
);
}
},
// ignoring inline event handlers at this time - they pose enough of a security risk that they're
// not worth preserving; there's a reason React calls it "dangerouslySetInnerHTML"!
headingSetext: {
match: blockRegex(HEADING_SETEXT_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture, _parse3, state) {
return {
content: parseInline(_parse3, capture[1], state),
level: capture[2] === '=' ? 1 : 2,
type: 'heading'
};
}
},
if (key.indexOf('on') !== 0) {
var value = kvPair.slice(key.length + 1);
htmlBlock: {
/**
* find the first matching end tag and process the interior
*/
match: anyScopeRegex(HTML_BLOCK_ELEMENT_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse4, state) {
var parseFunc = capture[3].match(HTML_BLOCK_ELEMENT_R) ? parseBlock : parseInline;
// strip the outermost single/double quote if it exists
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1, value.length - 1);
}
return {
attrs: attrStringToMap(capture[2]),
/**
* if another html block is detected within, parse as block,
* otherwise parse as inline to pick up any further markdown
*/
content: parseFunc(_parse4, capture[3], state),
props[ATTRIBUTE_TO_JSX_PROP_MAP[key] || key] = attributeValueToJSXPropValue(key, value) || true;
}
tag: capture[1]
};
},
react: function react(node, output, state) {
return h(
node.tag,
_extends({ key: state.key }, node.attrs),
output(node.content, state)
);
}
},
return props;
}, {});
htmlComment: {
match: anyScopeRegex(HTML_COMMENT_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse() {
return {};
},
// null out .value or astToJSX() will set it as the child
node.value = null;
}
react: renderNothing
},
if (node.children) {
node.children.forEach(coalescer);
}
};
htmlSelfClosing: {
ast.children.forEach(coalescer);
}
/**
* find the first matching end tag and process the interior
*/
match: inlineRegex(HTML_SELF_CLOSING_ELEMENT_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
return {
attrs: attrStringToMap(capture[2]),
tag: capture[1]
};
},
react: function react(node, output, state) {
return h(node.tag, _extends({}, node.attrs, {
key: state.key
}));
}
},
function compiler(markdown) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$overrides = _ref.overrides,
overrides = _ref$overrides === undefined ? {} : _ref$overrides;
image: {
match: inlineRegex(IMAGE_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
var image = {
alt: capture[1],
target: unescapeUrl(capture[2]),
title: capture[3]
};
return image;
},
react: function react(node, output, state) {
return h('img', {
key: state.key,
alt: node.alt || undefined,
title: node.title || undefined,
src: sanitizeUrl(node.target)
});
}
},
var definitions = void 0;
var footnotes = void 0;
link: {
match: inlineRegex(LINK_R),
order: PARSE_PRIORITY_LOW,
parse: function parse(capture, _parse5, state) {
var link = {
content: _parse5(capture[1], state),
target: unescapeUrl(capture[2]),
title: capture[3]
};
return link;
},
react: function react(node, output, state) {
return h(
'a',
{
key: state.key,
href: sanitizeUrl(node.target),
title: node.title
},
output(node.content, state)
);
}
},
function astToJSX(ast, index) {
/* `this` is the dictionary of definitions */
if (TEXT_AST_TYPES.indexOf(ast.type) !== -1) {
return ast.value;
}
// https://daringfireball.net/projects/markdown/syntax#autolink
linkAngleBraceStyleDetector: {
match: inlineRegex(LINK_AUTOLINK_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
return {
content: [{
content: capture[1],
type: 'text'
}],
target: capture[1],
type: 'link'
};
}
},
var key = index || '0';
linkBareUrlDetector: {
match: inlineRegex(LINK_AUTOLINK_BARE_URL_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
return {
content: [{
content: capture[1],
type: 'text'
}],
target: capture[1],
title: undefined,
type: 'link'
};
}
},
if (ast.type === 'code' && ast.value) {
var preProps = _extends({}, (0, _lodash2.default)(overrides, 'pre.props', {}), {
key: key
});
linkMailtoDetector: {
match: inlineRegex(LINK_AUTOLINK_MAILTO_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
var address = capture[1];
var target = capture[1];
var langClassName = 'lang-' + ast.lang;
var codeBaseProps = (0, _lodash2.default)(overrides, 'code.props', {});
var codeProps = _extends({}, codeBaseProps, {
className: codeBaseProps.className ? codeBaseProps.className + ' ' + langClassName : langClassName
});
// Check for a `mailto:` already existing in the link:
if (!AUTOLINK_MAILTO_CHECK_R.test(target)) {
target = 'mailto:' + target;
}
return _react2.default.createElement((0, _lodash2.default)(overrides, 'pre.component', 'pre'), preProps, _react2.default.createElement((0, _lodash2.default)(overrides, 'code.component', 'code'), codeProps, ast.value));
} /* Refers to fenced blocks, need to create a pre:code nested structure */
return {
content: [{
content: address.replace('mailto:', ''),
type: 'text'
}],
target: target,
type: 'link'
};
}
},
if (ast.type === 'list' && ast.loose === false) {
ast.children = ast.children.map(function (item) {
if (item.children.length === 1 && item.children[0].type === 'paragraph') {
return _extends({}, item, {
children: item.children[0].children
});
list: {
match: function match(source, state, prevCapture) {
// We only want to break into a list if we are at the start of a
// line. This is to avoid parsing "hi * there" with "* there"
// becoming a part of a list.
// You might wonder, "but that's inline, so of course it wouldn't
// start a list?". You would be correct! Except that some of our
// lists can be inline, because they might be inside another list,
// in which case we can parse with inline scope, but need to allow
// nested lists inside this inline scope.
var isStartOfLine = LIST_LOOKBEHIND_R.test(prevCapture);
var isListBlock = state._list || !state.inline;
if (isStartOfLine && isListBlock) {
return LIST_R.exec(source);
} else {
return null;
}
},
return item;
});
} /* tight list, remove the paragraph wrapper just inside the listItem */
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse6, state) {
var bullet = capture[2];
var ordered = bullet.length > 1;
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);
if (ast.type === 'listItem') {
if (ast.checked === true || ast.checked === false) {
var liProps = _extends({}, (0, _lodash2.default)(overrides, 'li.props', {}), {
key: key
});
var lastItemWasAParagraph = false;
var itemContent = items.map(function (item, i) {
// We need to see how far indented this item is:
var space = LIST_ITEM_PREFIX_R.exec(item)[0].length;
// And then we construct a regex to "unindent" the subsequent
// lines of the items by that amount:
var spaceRegex = new RegExp('^ {1,' + space + '}', 'gm');
var inputProps = _extends({}, (0, _lodash2.default)(overrides, 'input.props', {}), {
key: 'checkbox',
type: 'checkbox',
checked: ast.checked,
readOnly: true
});
// 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, '');
return _react2.default.createElement((0, _lodash2.default)(overrides, 'li.component', 'li'), liProps, [_react2.default.createElement((0, _lodash2.default)(overrides, 'input.component', 'input'), inputProps), ast.children.map(astToJSX)]);
} /* gfm task list, need to add a checkbox */
}
// Handling "loose" lists, like:
//
// * this is wrapped in a paragraph
//
// * as is this
//
// * as is this
var isLastItem = i === items.length - 1;
var containsBlocks = content.indexOf('\n\n') !== -1;
if (ast.type === 'html') {
return _react2.default.createElement('div', { key: key, dangerouslySetInnerHTML: { __html: ast.value } });
} /* arbitrary HTML, do the gross thing for now */
// 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;
if (ast.type === 'table') {
var tbody = { type: 'tbody', children: [] };
// backup our state for restoration afterwards. We're going to
// want to set state._list to true, and state.inline depending
// on our list's looseness.
var oldStateInline = state.inline;
var oldStateList = state._list;
state._list = true;
ast.children = ast.children.reduce(function (children, child, index) {
if (index === 0) {
/* manually marking the first row as tableHeader since that was removed in remark@4.x; it's important semantically. */
child.type = 'tableHeader';
children.unshift(seekCellsAndAlignThemIfNecessary(child, ast.align));
} else if (child.type === 'tableRow') {
tbody.children.push(seekCellsAndAlignThemIfNecessary(child, ast.align));
} else if (child.type === 'tableFooter') {
children.push(seekCellsAndAlignThemIfNecessary(child, ast.align));
}
// Parse inline if we're in a tight list, or block if we're in
// a loose list.
var adjustedContent = void 0;
if (thisItemIsAParagraph) {
state.inline = false;
adjustedContent = content.replace(LIST_ITEM_END_R, '\n\n');
} else {
state.inline = true;
adjustedContent = content.replace(LIST_ITEM_END_R, '');
}
return children;
}, [tbody]);
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap tablerow(s) in a tbody */
var result = _parse6(adjustedContent, state);
if (ast.type === 'tableFooter') {
ast.children = [{
type: 'tr',
children: ast.children
}];
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap the cells in a tablerow */
// Restore our state before returning
state.inline = oldStateInline;
state._list = oldStateList;
return result;
});
if (ast.type === 'tableHeader') {
ast.children = [{
type: 'tr',
children: ast.children.map(function (child) {
if (child.type === 'tableCell') {
child.type = 'th';
} /* et voila, a proper table header */
return {
items: itemContent,
ordered: ordered,
start: start
};
},
react: function react(node, output, state) {
var Tag = node.ordered ? 'ol' : 'ul';
return child;
})
}];
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap the cells in a tablerow */
return h(
Tag,
{ key: state.key, start: node.start },
node.items.map(function generateListItem(item, i) {
return h(
'li',
{ key: i },
output(item, state)
);
})
);
}
},
if (ast.type === 'footnoteReference') {
ast.children = [{ type: 'sup', value: ast.identifier }];
} /* place the identifier inside a superscript tag for the link */
newlineCoalescer: {
match: blockRegex(CONSECUTIVE_NEWLINE_R),
order: PARSE_PRIORITY_LOW,
parse: captureNothing,
react: function react() /*node, output, state*/{
return '\n';
}
},
var htmlNodeType = getHTMLNodeTypeFromASTNodeType(ast);
if (htmlNodeType === null) {
return null;
} /* bail out, not convertable to any HTML representation */
paragraph: {
match: blockRegex(PARAGRAPH_R),
order: PARSE_PRIORITY_LOW,
parse: parseCaptureInline,
react: function react(node, output, state) {
return h(
'p',
{ key: state.key },
output(node.content, state)
);
}
},
var props = _extends({ key: key }, ast.props);
ref: {
match: inlineRegex(REFERENCE_IMAGE_OR_LINK),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse*/) {
refs[capture[1]] = {
target: capture[2],
title: capture[4]
};
if (Array.isArray(ast.children) && ast.children.length === 1 && ast.children[0].type === 'html') {
props.dangerouslySetInnerHTML = { __html: ast.children[0].value };
ast.children = null;
}
return {};
},
var override = overrides[htmlNodeType];
if (override) {
if (override.component) {
htmlNodeType = override.component;
} /* sub out the normal html tag name for the JSX / ReactFactory
passed in by the caller */
react: renderNothing
},
if (override.props) {
props = _extends({}, override.props, props);
} /* apply the prop overrides beneath the minimal set that are necessary
to have the markdown conversion work as expected */
}
refImage: {
match: inlineRegex(REFERENCE_IMAGE_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture) {
return {
alt: capture[1] || undefined,
ref: capture[2]
};
},
react: function react(node, output, state) {
return h('img', {
key: state.key,
alt: node.alt,
src: sanitizeUrl(refs[node.ref].target),
title: refs[node.ref].title
});
}
},
/* their props + our props, with any duplicate keys overwritten by us
(necessary evil, file an issue if something comes up that needs
extra attention, only props specified in `formExtraPropsForHTMLNodeType`
will be overwritten on a key collision) */
var finalProps = formExtraPropsForHTMLNodeType(props, ast, definitions);
refLink: {
match: inlineRegex(REFERENCE_LINK_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture, _parse7, state) {
return {
content: _parse7(capture[1], state),
ref: capture[2]
};
},
react: function react(node, output, state) {
return h(
'a',
{
key: state.key,
href: sanitizeUrl(refs[node.ref].target),
title: refs[node.ref].title
},
output(node.content, state)
);
}
},
if (ast.children && ast.children.length === 1) {
if (TEXT_AST_TYPES.indexOf(ast.children[0].type) !== -1) {
ast.children = ast.children[0].value;
table: {
match: blockRegex(NP_TABLE_R),
order: PARSE_PRIORITY_HIGH,
parse: parseTable,
react: function react(node, output, state) {
function getStyle(colIndex) {
return node.align[colIndex] == null ? {} : {
textAlign: node.align[colIndex]
};
}
return h(
'table',
{ key: state.key },
h(
'thead',
null,
h(
'tr',
null,
node.header.map(function generateHeaderCell(content, i) {
return h(
'th',
{
key: i,
style: getStyle(i),
scope: 'col'
},
output(content, state)
);
})
)
),
h(
'tbody',
null,
node.cells.map(function generateTableRow(row, i) {
return h(
'tr',
{ key: i },
row.map(function generateTableCell(content, c) {
return h(
'td',
{ key: c, style: getStyle(c) },
output(content, state)
);
})
);
})
)
);
}
} /* solitary text children don't need full parsing or React will add a wrapper */
},
var children = Array.isArray(ast.children) ? ast.children.map(astToJSX) : ast.children;
text: {
// Here we look for anything followed by non-symbols,
// double newlines, or double-space-newlines
// We break on any symbol characters so that this grammar
// is easy to extend without needing to modify this regex
match: inlineRegex(TEXT_PLAIN_R),
order: PARSE_PRIORITY_MIN,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[0]
};
},
react: function react(node /*, output, state*/) {
return node.content;
}
},
return _react2.default.createElement(htmlNodeType, finalProps, ast.value || children);
}
textBolded: {
match: inlineRegex(TEXT_BOLD_R),
order: PARSE_PRIORITY_MED,
parse: parseCaptureInline,
react: function react(node, output, state) {
return h(
'strong',
{ key: state.key },
output(node.content, state)
);
}
},
if (typeof markdown !== 'string') {
throw new Error('markdown-to-jsx: the first argument must be\n a string');
}
textEmphasized: {
match: inlineRegex(TEXT_EMPHASIZED_R),
order: PARSE_PRIORITY_LOW,
parse: function parse(capture, _parse8, state) {
return {
content: _parse8(capture[2] || capture[1], state)
};
},
react: function react(node, output, state) {
return h(
'em',
{ key: state.key },
output(node.content, state)
);
}
},
if (getType.call(overrides) !== '[object Object]') {
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 }');
}
textEscaped: {
// We don't allow escaping numbers, letters, or spaces here so that
// backslashes used in plain text still get rendered. But allowing
// escaping anything else provides a very flexible escape mechanism,
// regardless of how this grammar is extended.
match: inlineRegex(TEXT_ESCAPED_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[1],
type: 'text'
};
}
},
var remarkAST = (0, _unified2.default)().data('settings', {
footnotes: true,
gfm: true,
position: false
}).use(_remarkParse2.default).parse(markdown);
textStrikethroughed: {
match: inlineRegex(TEXT_STRIKETHROUGHED_R),
order: PARSE_PRIORITY_LOW,
parse: parseCaptureInline,
react: function react(node, output, state) {
return h(
'del',
{ key: state.key },
output(node.content, state)
);
}
}
};
var extracted = extractDefinitionsFromASTTree(remarkAST, astToJSX);
var parser = parserFor(rules);
var emitter = reactFor(ruleOutput(rules));
definitions = extracted.definitions;
footnotes = extracted.footnotes;
/**
* should not contain any block-level markdown like newlines, lists, headings,
* thematic breaks, blockquotes, etc
*/
var inline = /(\n|^[-*]\s|^#|^ {2,}|^-{2,}|^>\s)/g.test(markdown) === false;
coalesceInlineHTML(remarkAST);
var arr = emitter(parser(inline ? markdown : markdown + '\n\n', { inline: inline }));
var jsx = astToJSX(remarkAST);
var jsx = void 0;
if (arr.length > 1) {
jsx = h(
'div',
null,
arr
);
} else if (arr.length === 1) {
jsx = arr[0];
// discard the root <div> node if there is only one valid initial child
if (jsx.props.children && jsx.props.children.length === 1) {
jsx = jsx.props.children[0];
// TODO: remove this for React 16
if (typeof jsx === 'string') {
jsx = h(
'span',
null,
jsx
);
}
}
if (footnotes.length) {
jsx.props.children.push(_react2.default.createElement(
jsx.props.children.push(h(
'footer',
{ key: 'footnotes' },
footnotes
null,
footnotes.map(function createFootnote(def) {
return h(
'div',
{ id: def.identifier, key: def.identifier },
def.identifier,
emitter(parser(def.footnote, { inline: true }))
);
})
));

@@ -556,3 +1245,3 @@ }

return jsx;
};
}

@@ -568,15 +1257,26 @@ /**

*/
var Component = function Component(_ref2) {
var children = _ref2.children,
options = _ref2.options,
props = _objectWithoutProperties(_ref2, ['children', 'options']);
return compiler(children, options);
};
var Markdown = function (_React$PureComponent) {
_inherits(Markdown, _React$PureComponent);
Component.propTypes = {
function Markdown() {
_classCallCheck(this, Markdown);
return _possibleConstructorReturn(this, (Markdown.__proto__ || Object.getPrototypeOf(Markdown)).apply(this, arguments));
}
_createClass(Markdown, [{
key: 'render',
value: function render() {
return compiler(this.props.children, this.props.options);
}
}]);
return Markdown;
}(_react2.default.PureComponent);
Markdown.propTypes = {
children: _propTypes2.default.string.isRequired,
options: _propTypes2.default.object
};
exports.default = Component;
exports.default = Markdown;

@@ -0,22 +1,24 @@

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/* @jsx h */
/**
* markdown-to-jsx@6 is a fork of [simple-markdown v0.2.2](https://github.com/Khan/simple-markdown)
* from Khan Academy. Thank you Khan devs for making such an awesome and extensible
* parsing infra... without it, half of the optimizations here wouldn't be feasible. 🙏🏼
*/
import PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash.get';
import unified from 'unified';
import parser from 'remark-parse';
import unquote from 'unquote';
var BLOCK_ELEMENT_TAGS = ['article', 'aside', 'blockquote', 'body', 'button', 'canvas', 'caption', 'col', 'colgroup', 'dd', 'details', 'div', 'dl', 'dt', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'iframe', 'li', 'map', 'object', 'ol', 'output', 'p', 'pre', 'progress', 'script', 'section', 'style', 'summary', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'ul', 'video'];
var BLOCK_ELEMENT_REGEX = new RegExp('^<(' + BLOCK_ELEMENT_TAGS.join('|') + ')', 'i');
var getType = Object.prototype.toString;
// [0] === tag, [...] = attribute pairs
var HTML_EXTRACTOR_REGEX = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
var SELF_CLOSING_ELEMENT_TAGS = ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
var SELF_CLOSING_ELEMENT_REGEX = new RegExp('^<(' + SELF_CLOSING_ELEMENT_TAGS.join('|') + ')', 'i');
var TEXT_AST_TYPES = ['text', 'textNode'];
/** TODO: Drop for React 16? */
var ATTRIBUTE_TO_JSX_PROP_MAP = {

@@ -69,458 +71,1151 @@ 'accept-charset': 'acceptCharset',

var getType = Object.prototype.toString;
/** TODO: Write explainers for each of these */
var ATTR_EXTRACTOR_R = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
var AUTOLINK_MAILTO_CHECK_R = /mailto:/i;
var BLOCK_END_R = /\n{2,}$/;
var BLOCKQUOTE_R = /^( *>[^\n]+(\n[^\n]+)*\n*)+\n{2,}/;
var BLOCKQUOTE_TRIM_LEFT_MULTILINE_R = /^ *> ?/gm;
var BREAK_LINE_R = /^ {2,}\n/;
var BREAK_THEMATIC_R = /^( *[-*_]){3,} *(?:\n *)+\n/;
var CODE_BLOCK_FENCED_R = /^\s*(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/;
var CODE_BLOCK_R = /^(?: {4}[^\n]+\n*)+(?:\n *)+\n/;
var CODE_INLINE_R = /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/;
var CONSECUTIVE_NEWLINE_R = /^(?:\n *)*\n/;
var CR_NEWLINE_R = /\r\n?/g;
var FOOTNOTE_R = /^\[\^(.*)\](:.*)\n/;
var FOOTNOTE_REFERENCE_R = /^\[\^(.*)\]/;
var FORMFEED_R = /\f/g;
var GFM_TASK_R = /^\s*?\[(x|\s)\]/;
var HEADING_R = /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n *)+\n/;
var HEADING_SETEXT_R = /^([^\n]+)\n *(=|-){3,} *(?:\n *)+\n/;
var HTML_BLOCK_ELEMENT_R = /^<(.*)\s?(.*?)>(.*?)<\/\1>/;
var HTML_COMMENT_R = /^<!--.*?-->/;
var HTML_SELF_CLOSING_ELEMENT_R = /^<([^\s]*)\s?(.*?)>(.*?)(?!<\/\1>)/;
var LINK_AUTOLINK_BARE_URL_R = /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/;
var LINK_AUTOLINK_MAILTO_R = /^<([^ >]+@[^ >]+)>/;
var LINK_AUTOLINK_R = /^<([^ >]+:\/[^ >]+)>/;
var LIST_ITEM_END_R = / *\n+$/;
var LIST_LOOKBEHIND_R = /^$|\n *$/;
var NP_TABLE_R = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/;
var NPTABLE_CELLS_TRIM = /\n$/;
var PARAGRAPH_R = /^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/;
var REFERENCE_IMAGE_OR_LINK = /^\[([^\]]*)\]:\s*(\S+)\s*("([^"]*)")?/;
var REFERENCE_IMAGE_R = /^!\[([^\]]*)\]\[([^\]]*)\]/;
var REFERENCE_LINK_R = /^\[([^\]]*)\]\[([^\]]*)\]/;
var TAB_R = /\t/g;
var TABLE_ALIGN_TRIM = /^ *|\| *$/g;
var TABLE_CENTER_ALIGN = /^ *:-+: *$/;
var TABLE_HEADER_TRIM = /^ *| *\| *$/g;
var TABLE_LEFT_ALIGN = /^ *:-+ *$/;
var TABLE_RIGHT_ALIGN = /^ *-+: *$/;
var TABLE_ROW_SPLIT = / *\| */;
var TEXT_BOLD_R = /^[*_]{2}([\s\S]+?)[*_]{2}(?!\*|_)/;
var TEXT_EMPHASIZED_R = /^[*_]{1}([\s\S]+?)[*_]{1}(?!\*|_)/;
var TEXT_ESCAPED_R = /^\\([^0-9A-Za-z\s])/;
var TEXT_PLAIN_R = /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/;
var TEXT_STRIKETHROUGHED_R = /^~~(?=\S)([\s\S]*?\S)~~/;
var UNESCAPE_URL_R = /\\([^0-9A-Za-z\s])/g;
function extractDefinitionsFromASTTree(ast, parser) {
function reducer(aggregator, node) {
if (node.type === 'definition' || node.type === 'footnoteDefinition') {
aggregator.definitions[node.identifier] = node;
// recognize a `*` `-`, `+`, `1.`, `2.`... list bullet
var LIST_BULLET = '(?:[*+-]|\\d+\\.)';
if (node.type === 'footnoteDefinition') {
if (node.children && node.children.length === 1 && node.children[0].type === 'paragraph') {
node.children[0].children.unshift({
type: 'textNode',
value: '[' + node.identifier + ']: '
});
} /* package the prefix inside the first child */
// recognize the start of a list item:
// leading space plus a bullet plus a space (` * `)
var LIST_ITEM_PREFIX = '( *)(' + LIST_BULLET + ') +';
var LIST_ITEM_PREFIX_R = new RegExp('^' + LIST_ITEM_PREFIX);
aggregator.footnotes.push(React.createElement(
'div',
{ key: node.identifier, id: node.identifier },
node.value || node.children.map(parser)
));
}
}
// 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
var LIST_ITEM_R = new RegExp(LIST_ITEM_PREFIX + '[^\\n]*(?:\\n' + '(?!\\1' + LIST_BULLET + ' )[^\\n]*)*(\n|$)', 'gm');
return Array.isArray(node.children) ? node.children.reduce(reducer, aggregator) : aggregator;
};
// check whether a list item has paragraphs: if it does,
// we leave the newlines at the end
var LIST_R = new RegExp('^( *)(' + LIST_BULLET + ') ' + '[\\s\\S]+?(?:\n{2,}(?! )' + '(?!\\1' + LIST_BULLET + ' )\\n*' +
// the \\s*$ here is so that we can parse the inside of nested
// lists, where our content might end before we receive two `\n`s
'|\\s*\n*$)');
return [ast].reduce(reducer, {
definitions: {},
footnotes: []
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 = new RegExp('^!\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)');
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';
} else {
return null;
}
}
function parseTableHeader(capture, parse, state) {
var headerText = capture[1].replace(TABLE_HEADER_TRIM, '').split(TABLE_ROW_SPLIT);
return headerText.map(function (text) {
return parse(text, state);
});
}
function formExtraPropsForHTMLNodeType() {
var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var ast = arguments[1];
var definitions = arguments[2];
function parseTableAlign(capture /*, parse, state*/) {
var alignText = capture[2].replace(TABLE_ALIGN_TRIM, '').split(TABLE_ROW_SPLIT);
switch (ast.type) {
case 'footnoteReference':
return _extends({}, props, {
href: '#' + ast.identifier
});
return alignText.map(parseTableAlignCapture);
}
case 'image':
return _extends({}, props, {
title: ast.title,
alt: ast.alt,
src: ast.url
});
function parseTableCells(capture, parse, state) {
var rowsText = capture[3].replace(NPTABLE_CELLS_TRIM, '').split('\n');
case 'imageReference':
return _extends({}, props, {
title: get(definitions, '[\'' + ast.identifier + '\'].title'),
alt: ast.alt,
src: get(definitions, '[\'' + ast.identifier + '\'].url')
});
return rowsText.map(function (rowText) {
var cellText = rowText.split(TABLE_ROW_SPLIT);
return cellText.map(function (text) {
return parse(text, state);
});
});
}
case 'link':
return _extends({}, props, {
title: ast.title,
href: ast.url
});
function parseTable(capture, parse, state) {
state.inline = true;
var header = parseTableHeader(capture, parse, state);
var align = parseTableAlign(capture, parse, state);
var cells = parseTableCells(capture, parse, state);
state.inline = false;
case 'linkReference':
return _extends({}, props, {
title: get(definitions, '[\'' + ast.identifier + '\'].title'),
href: get(definitions, '[\'' + ast.identifier + '\'].url')
});
return {
align: align,
cells: cells,
header: header,
type: 'table'
};
}
case 'list':
return _extends({}, props, {
start: ast.start
function attributeValueToJSXPropValue(key, value) {
if (key === 'style') {
return value.split(/;\s?/).reduce(function (styles, kvPair) {
var key = kvPair.slice(0, kvPair.indexOf(':'));
// snake-case to camelCase
// also handles PascalCasing vendor prefixes
var camelCasedKey = key.replace(/(-[a-z])/g, function toUpper(substr) {
return substr[1].toUpperCase();
});
case 'tableCell':
case 'th':
return _extends({}, props, {
style: { textAlign: ast.align }
});
// key.length + 1 to skip over the colon
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim();
return styles;
}, {});
}
return props;
return value;
}
function getHTMLNodeTypeFromASTNodeType(node) {
switch (node.type) {
case 'break':
return 'br';
function attrStringToMap(str) {
var attributes = str.match(ATTR_EXTRACTOR_R);
case 'delete':
return 'del';
return attributes ? attributes.reduce(function (map, raw) {
var tuple = raw.split('=');
var key = tuple[0];
var value = tuple[1];
case 'emphasis':
return 'em';
map[ATTRIBUTE_TO_JSX_PROP_MAP[key] || key] = attributeValueToJSXPropValue(key, unquote(value));
case 'footnoteReference':
return 'a';
return map;
}, {}) : undefined;
}
case 'heading':
return 'h' + node.depth;
// Turn various crazy whitespace into easy to process things
function preprocess(source) {
return source.replace(CR_NEWLINE_R, '\n').replace(FORMFEED_R, '').replace(TAB_R, ' ');
}
case 'image':
case 'imageReference':
return 'img';
/**
* 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) {
// Sorts rules in order of increasing order, then
// ascending rule name in case of ties.
var ruleList = Object.keys(rules);
case 'inlineCode':
return 'code';
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production') {
ruleList.forEach(function (type) {
var order = rules[type].order;
if (process.env.NODE_ENV !== 'production' && (typeof order !== 'number' || !isFinite(order)) && typeof console !== 'undefined') {
console.warn('simple-markdown: Invalid order for rule `' + type + '`: ' + order);
}
});
}
case 'link':
case 'linkReference':
return 'a';
ruleList.sort(function (typeA, typeB) {
var orderA = rules[typeA].order;
var orderB = rules[typeB].order;
case 'list':
return node.ordered ? 'ol' : 'ul';
// First sort based on increasing order
if (orderA !== orderB) {
return orderA - orderB;
case 'listItem':
return 'li';
// Then based on increasing unicode lexicographic ordering
} else if (typeA < typeB) {
return -1;
}
case 'paragraph':
return 'p';
return 1;
});
case 'root':
return 'div';
function nestedParse(source, state) {
var result = [];
case 'tableHeader':
return 'thead';
// We store the previous capture so that match functions can
// use some limited amount of lookbehind. Lists use this to
// ensure they don't match arbitrary '- ' or '* ' in inline
// text (see the list rule for more information).
var prevCapture = '';
while (source) {
var i = 0;
while (i < ruleList.length) {
var ruleType = ruleList[i];
var rule = rules[ruleType];
var capture = rule.match(source, state, prevCapture);
case 'tableRow':
return 'tr';
if (capture) {
var currCaptureString = capture[0];
source = source.substring(currCaptureString.length);
var parsed = rule.parse(capture, nestedParse, state);
case 'tableCell':
return 'td';
// We also let rules override the default type of
// their parsed node if they would like to, so that
// there can be a single output function for all links,
// even if there are several rules to parse them.
if (parsed.type == null) {
parsed.type = ruleType;
}
case 'thematicBreak':
return 'hr';
result.push(parsed);
case 'definition':
case 'footnoteDefinition':
case 'yaml':
return null;
prevCapture = currCaptureString;
break;
}
default:
return node.type;
i++;
}
}
return result;
}
return function outerParse(source, state) {
return nestedParse(preprocess(source), state);
};
}
function seekCellsAndAlignThemIfNecessary(root, alignmentValues) {
var mapper = function mapper(child, index) {
if (child.type === 'tableCell') {
return _extends({}, child, {
align: alignmentValues[index]
});
} else if (Array.isArray(child.children) && child.children.length) {
return child.children.map(mapper);
// Creates a match function for an inline scoped element from a regex
function inlineRegex(regex) {
function match(source, state) {
if (state.inline) {
return regex.exec(source);
} else {
return null;
}
}
return child;
};
match.regex = regex;
if (Array.isArray(root.children) && root.children.length) {
root.children = root.children.map(mapper);
return match;
}
// Creates a match function for a block scoped element from a regex
function blockRegex(regex) {
function match(source, state) {
if (state.inline) {
return null;
} else {
return regex.exec(source);
}
}
return root;
match.regex = regex;
return match;
}
function attributeValueToJSXPropValue(key, value) {
if (key === 'style') {
return value.split(/;\s?/).reduce(function (styles, kvPair) {
// Creates a match function from a regex, ignoring block/inline scope
function anyScopeRegex(regex) {
function match(source /*, state*/) {
return regex.exec(source);
}
var key = kvPair.slice(0, kvPair.indexOf(':'));
match.regex = regex;
// snake-case to camelCase
// also handles PascalCasing vendor prefixes
var camelCasedKey = key.replace(/(\-[a-z])/g, function (substr) {
return substr[1].toUpperCase();
});
return match;
}
// key.length + 1 to skip over the colon
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim();
function reactFor(outputFunc) {
return function nestedReactOutput(ast, state) {
state = state || {};
if (Array.isArray(ast)) {
var oldKey = state.key;
var result = [];
return styles;
}, {});
// 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 = nestedReactOutput(ast[i], state);
var isString = typeof nodeOut === 'string';
if (isString && lastWasString) {
result[result.length - 1] += nodeOut;
} else {
result.push(nodeOut);
}
lastWasString = isString;
}
state.key = oldKey;
return result;
} else {
return outputFunc(ast, nestedReactOutput, state);
}
};
}
function sanitizeUrl(url) {
try {
var prot = decodeURIComponent(url).replace(/[^A-Za-z0-9/:]/g, '').toLowerCase();
if (prot.indexOf('javascript:') === 0) {
return null;
}
} catch (e) {
// decodeURIComponent sometimes throws a URIError
// See `decodeURIComponent('a%AFc');`
// http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception
return null;
}
return value;
return url;
}
function isCoalesceableHTML(html) {
// ignore block-level elements
// ignore self-closing or non-content-bearing elements
return html.match(BLOCK_ELEMENT_REGEX) || html.match(SELF_CLOSING_ELEMENT_REGEX) ? false : true;
function unescapeUrl(rawUrlString) {
return rawUrlString.replace(UNESCAPE_URL_R, '$1');
}
function coalesceInlineHTML(ast) {
function coalescer(node, index, siblings) {
if (node.type === 'html') {
if (!isCoalesceableHTML(node.value)) {
return;
} else if (node.value.indexOf('<!--') !== -1) {
// throw out HTML comments
siblings.splice(index, 1);
// Parse some content with the parser `parse`, with state.inline
// set to true. Useful for block elements; not generally necessary
// to be used by inline elements (where state.inline is already true.
function parseInline(parse, content, state) {
var isCurrentlyInline = state.inline || false;
state.inline = true;
var result = parse(content, state);
state.inline = isCurrentlyInline;
return result;
}
function parseBlock(parse, content, state) {
state.inline = false;
return parse(content + '\n\n', state);
}
function parseCaptureInline(capture, parse, state) {
return {
content: parseInline(parse, capture[1], state)
};
}
function captureNothing() {
return {};
}
function renderNothing() {
return null;
}
function ruleOutput(rules) {
return function nestedRuleOutput(ast, outputFunc, state) {
return rules[ast.type].react(ast, outputFunc, state);
};
}
/**
* anything that must scan the tree before everything else
*/
var PARSE_PRIORITY_MAX = 1;
/**
* scans for block-level constructs
*/
var PARSE_PRIORITY_HIGH = 2;
/**
* inline w/ more priority than other inline
*/
var PARSE_PRIORITY_MED = 3;
/**
* inline elements
*/
var PARSE_PRIORITY_LOW = 4;
/**
* bare text and stuff that is considered leftovers
*/
var PARSE_PRIORITY_MIN = 5;
export function compiler(markdown) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$overrides = _ref.overrides,
overrides = _ref$overrides === undefined ? {} : _ref$overrides;
// eslint-disable-next-line no-unused-vars
function h(tag, props) {
for (var _len = arguments.length, children = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
children[_key - 2] = arguments[_key];
}
return React.createElement.apply(React, [overrides[tag] && overrides[tag].component || tag, _extends({}, overrides[tag] && overrides[tag].props, props)].concat(children));
}
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production') {
if (typeof markdown !== 'string') {
throw new Error('markdown-to-jsx: the first argument must be\n a string');
}
if (getType.call(overrides) !== '[object Object]') {
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 }');
}
}
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
*/
var rules = {
blockQuote: {
match: blockRegex(BLOCKQUOTE_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse, state) {
return {
content: _parse(capture[0].replace(BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, ''), state)
};
},
react: function react(node, output, state) {
return h(
'blockquote',
{ key: state.key },
output(node.content, state)
);
}
},
// are there more html nodes directly after? if so, fold them into the current node
if (index < siblings.length - 1 && siblings[index + 1].type === 'html') {
// create a new coalescer context
coalescer(siblings[index + 1], index + 1, siblings);
breakLine: {
match: anyScopeRegex(BREAK_LINE_R),
order: PARSE_PRIORITY_HIGH,
parse: captureNothing,
react: function react(_, __, state) {
return h('br', { key: state.key });
}
},
var i = index + 1;
var end = void 0;
breakThematic: {
match: blockRegex(BREAK_THEMATIC_R),
order: PARSE_PRIORITY_HIGH,
parse: captureNothing,
react: function react(_, __, state) {
return h('hr', { key: state.key });
}
},
// where's the end tag?
while (end === undefined && i < siblings.length) {
if (siblings[i].type !== 'html' || siblings[i].type === 'html' && !isCoalesceableHTML(siblings[i].value)) {
i += 1;
continue;
}
codeBlock: {
match: blockRegex(CODE_BLOCK_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
var content = capture[0].replace(/^ {4}/gm, '').replace(/\n+$/, '');
return {
content: content,
lang: undefined
};
},
react: function react(node, output, state) {
return h(
'pre',
{ key: state.key },
h(
'code',
{ className: node.lang ? 'lang-' + node.lang : '' },
node.content
)
);
}
},
end = siblings[i];
codeFenced: {
match: blockRegex(CODE_BLOCK_FENCED_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[3],
lang: capture[2] || undefined,
type: 'codeBlock'
};
}
},
/* all interim elements now become children of the current node, and we splice them (including end tag)
out of the sibling array so they will not be iterated-over by forEach */
codeInline: {
match: inlineRegex(CODE_INLINE_R),
order: PARSE_PRIORITY_LOW,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[2]
};
},
react: function react(node, output, state) {
return h(
'code',
{ key: state.key },
node.content
);
}
},
node.children = siblings.slice(index + 1, i);
siblings.splice(index + 1, i - index);
/**
* footnotes are emitted at the end of compilation in a special <footer> block
*/
footnote: {
match: blockRegex(FOOTNOTE_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
footnotes.push({
footnote: capture[2],
identifier: capture[1]
});
var _node$value$match = node.value.match(HTML_EXTRACTOR_REGEX),
_node$value$match2 = _toArray(_node$value$match),
tag = _node$value$match2[0],
attributePairs = _node$value$match2.slice(1);
return {};
},
// reassign the current node to whatever its tag is
react: renderNothing
},
footnoteReference: {
match: inlineRegex(FOOTNOTE_REFERENCE_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse*/) {
return {
content: capture[1],
target: '#' + capture[1]
};
},
react: function react(node, output, state) {
return h(
'a',
{ key: state.key, href: sanitizeUrl(node.target) },
h(
'sup',
{ key: state.key },
node.content
)
);
}
},
node.type = tag.toLowerCase();
gfmTask: {
match: inlineRegex(GFM_TASK_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
return {
completed: capture[1].toLowerCase() === 'x'
};
},
react: function react(node, output, state) {
return h('input', {
checked: node.completed,
key: state.key,
readOnly: true,
type: 'checkbox'
});
}
},
// make a best-effort conversion to JSX props
node.props = attributePairs.reduce(function (props, kvPair) {
var valueIndex = kvPair.indexOf('=');
var key = kvPair.slice(0, valueIndex === -1 ? undefined : valueIndex);
heading: {
match: blockRegex(HEADING_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse2, state) {
return {
content: parseInline(_parse2, capture[2], state),
level: capture[1].length
};
},
react: function react(node, output, state) {
var Tag = 'h' + node.level;
return h(
Tag,
{ key: state.key },
output(node.content, state)
);
}
},
// ignoring inline event handlers at this time - they pose enough of a security risk that they're
// not worth preserving; there's a reason React calls it "dangerouslySetInnerHTML"!
headingSetext: {
match: blockRegex(HEADING_SETEXT_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture, _parse3, state) {
return {
content: parseInline(_parse3, capture[1], state),
level: capture[2] === '=' ? 1 : 2,
type: 'heading'
};
}
},
if (key.indexOf('on') !== 0) {
var value = kvPair.slice(key.length + 1);
htmlBlock: {
/**
* find the first matching end tag and process the interior
*/
match: anyScopeRegex(HTML_BLOCK_ELEMENT_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse4, state) {
var parseFunc = capture[3].match(HTML_BLOCK_ELEMENT_R) ? parseBlock : parseInline;
// strip the outermost single/double quote if it exists
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1, value.length - 1);
}
return {
attrs: attrStringToMap(capture[2]),
/**
* if another html block is detected within, parse as block,
* otherwise parse as inline to pick up any further markdown
*/
content: parseFunc(_parse4, capture[3], state),
props[ATTRIBUTE_TO_JSX_PROP_MAP[key] || key] = attributeValueToJSXPropValue(key, value) || true;
}
tag: capture[1]
};
},
react: function react(node, output, state) {
return h(
node.tag,
_extends({ key: state.key }, node.attrs),
output(node.content, state)
);
}
},
return props;
}, {});
htmlComment: {
match: anyScopeRegex(HTML_COMMENT_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse() {
return {};
},
// null out .value or astToJSX() will set it as the child
node.value = null;
}
react: renderNothing
},
if (node.children) {
node.children.forEach(coalescer);
}
};
htmlSelfClosing: {
ast.children.forEach(coalescer);
}
/**
* find the first matching end tag and process the interior
*/
match: inlineRegex(HTML_SELF_CLOSING_ELEMENT_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
return {
attrs: attrStringToMap(capture[2]),
tag: capture[1]
};
},
react: function react(node, output, state) {
return h(node.tag, _extends({}, node.attrs, {
key: state.key
}));
}
},
export function compiler(markdown) {
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$overrides = _ref.overrides,
overrides = _ref$overrides === undefined ? {} : _ref$overrides;
image: {
match: inlineRegex(IMAGE_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
var image = {
alt: capture[1],
target: unescapeUrl(capture[2]),
title: capture[3]
};
return image;
},
react: function react(node, output, state) {
return h('img', {
key: state.key,
alt: node.alt || undefined,
title: node.title || undefined,
src: sanitizeUrl(node.target)
});
}
},
var definitions = void 0;
var footnotes = void 0;
link: {
match: inlineRegex(LINK_R),
order: PARSE_PRIORITY_LOW,
parse: function parse(capture, _parse5, state) {
var link = {
content: _parse5(capture[1], state),
target: unescapeUrl(capture[2]),
title: capture[3]
};
return link;
},
react: function react(node, output, state) {
return h(
'a',
{
key: state.key,
href: sanitizeUrl(node.target),
title: node.title
},
output(node.content, state)
);
}
},
function astToJSX(ast, index) {
/* `this` is the dictionary of definitions */
if (TEXT_AST_TYPES.indexOf(ast.type) !== -1) {
return ast.value;
}
// https://daringfireball.net/projects/markdown/syntax#autolink
linkAngleBraceStyleDetector: {
match: inlineRegex(LINK_AUTOLINK_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
return {
content: [{
content: capture[1],
type: 'text'
}],
target: capture[1],
type: 'link'
};
}
},
var key = index || '0';
linkBareUrlDetector: {
match: inlineRegex(LINK_AUTOLINK_BARE_URL_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
return {
content: [{
content: capture[1],
type: 'text'
}],
target: capture[1],
title: undefined,
type: 'link'
};
}
},
if (ast.type === 'code' && ast.value) {
var preProps = _extends({}, get(overrides, 'pre.props', {}), {
key: key
});
linkMailtoDetector: {
match: inlineRegex(LINK_AUTOLINK_MAILTO_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse, state*/) {
var address = capture[1];
var target = capture[1];
var langClassName = 'lang-' + ast.lang;
var codeBaseProps = get(overrides, 'code.props', {});
var codeProps = _extends({}, codeBaseProps, {
className: codeBaseProps.className ? codeBaseProps.className + ' ' + langClassName : langClassName
});
// Check for a `mailto:` already existing in the link:
if (!AUTOLINK_MAILTO_CHECK_R.test(target)) {
target = 'mailto:' + target;
}
return React.createElement(get(overrides, 'pre.component', 'pre'), preProps, React.createElement(get(overrides, 'code.component', 'code'), codeProps, ast.value));
} /* Refers to fenced blocks, need to create a pre:code nested structure */
return {
content: [{
content: address.replace('mailto:', ''),
type: 'text'
}],
target: target,
type: 'link'
};
}
},
if (ast.type === 'list' && ast.loose === false) {
ast.children = ast.children.map(function (item) {
if (item.children.length === 1 && item.children[0].type === 'paragraph') {
return _extends({}, item, {
children: item.children[0].children
});
list: {
match: function match(source, state, prevCapture) {
// We only want to break into a list if we are at the start of a
// line. This is to avoid parsing "hi * there" with "* there"
// becoming a part of a list.
// You might wonder, "but that's inline, so of course it wouldn't
// start a list?". You would be correct! Except that some of our
// lists can be inline, because they might be inside another list,
// in which case we can parse with inline scope, but need to allow
// nested lists inside this inline scope.
var isStartOfLine = LIST_LOOKBEHIND_R.test(prevCapture);
var isListBlock = state._list || !state.inline;
if (isStartOfLine && isListBlock) {
return LIST_R.exec(source);
} else {
return null;
}
},
return item;
});
} /* tight list, remove the paragraph wrapper just inside the listItem */
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture, _parse6, state) {
var bullet = capture[2];
var ordered = bullet.length > 1;
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);
if (ast.type === 'listItem') {
if (ast.checked === true || ast.checked === false) {
var liProps = _extends({}, get(overrides, 'li.props', {}), {
key: key
});
var lastItemWasAParagraph = false;
var itemContent = items.map(function (item, i) {
// We need to see how far indented this item is:
var space = LIST_ITEM_PREFIX_R.exec(item)[0].length;
// And then we construct a regex to "unindent" the subsequent
// lines of the items by that amount:
var spaceRegex = new RegExp('^ {1,' + space + '}', 'gm');
var inputProps = _extends({}, get(overrides, 'input.props', {}), {
key: 'checkbox',
type: 'checkbox',
checked: ast.checked,
readOnly: true
});
// 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, '');
return React.createElement(get(overrides, 'li.component', 'li'), liProps, [React.createElement(get(overrides, 'input.component', 'input'), inputProps), ast.children.map(astToJSX)]);
} /* gfm task list, need to add a checkbox */
}
// Handling "loose" lists, like:
//
// * this is wrapped in a paragraph
//
// * as is this
//
// * as is this
var isLastItem = i === items.length - 1;
var containsBlocks = content.indexOf('\n\n') !== -1;
if (ast.type === 'html') {
return React.createElement('div', { key: key, dangerouslySetInnerHTML: { __html: ast.value } });
} /* arbitrary HTML, do the gross thing for now */
// 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;
if (ast.type === 'table') {
var tbody = { type: 'tbody', children: [] };
// backup our state for restoration afterwards. We're going to
// want to set state._list to true, and state.inline depending
// on our list's looseness.
var oldStateInline = state.inline;
var oldStateList = state._list;
state._list = true;
ast.children = ast.children.reduce(function (children, child, index) {
if (index === 0) {
/* manually marking the first row as tableHeader since that was removed in remark@4.x; it's important semantically. */
child.type = 'tableHeader';
children.unshift(seekCellsAndAlignThemIfNecessary(child, ast.align));
} else if (child.type === 'tableRow') {
tbody.children.push(seekCellsAndAlignThemIfNecessary(child, ast.align));
} else if (child.type === 'tableFooter') {
children.push(seekCellsAndAlignThemIfNecessary(child, ast.align));
}
// Parse inline if we're in a tight list, or block if we're in
// a loose list.
var adjustedContent = void 0;
if (thisItemIsAParagraph) {
state.inline = false;
adjustedContent = content.replace(LIST_ITEM_END_R, '\n\n');
} else {
state.inline = true;
adjustedContent = content.replace(LIST_ITEM_END_R, '');
}
return children;
}, [tbody]);
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap tablerow(s) in a tbody */
var result = _parse6(adjustedContent, state);
if (ast.type === 'tableFooter') {
ast.children = [{
type: 'tr',
children: ast.children
}];
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap the cells in a tablerow */
// Restore our state before returning
state.inline = oldStateInline;
state._list = oldStateList;
return result;
});
if (ast.type === 'tableHeader') {
ast.children = [{
type: 'tr',
children: ast.children.map(function (child) {
if (child.type === 'tableCell') {
child.type = 'th';
} /* et voila, a proper table header */
return {
items: itemContent,
ordered: ordered,
start: start
};
},
react: function react(node, output, state) {
var Tag = node.ordered ? 'ol' : 'ul';
return child;
})
}];
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap the cells in a tablerow */
return h(
Tag,
{ key: state.key, start: node.start },
node.items.map(function generateListItem(item, i) {
return h(
'li',
{ key: i },
output(item, state)
);
})
);
}
},
if (ast.type === 'footnoteReference') {
ast.children = [{ type: 'sup', value: ast.identifier }];
} /* place the identifier inside a superscript tag for the link */
newlineCoalescer: {
match: blockRegex(CONSECUTIVE_NEWLINE_R),
order: PARSE_PRIORITY_LOW,
parse: captureNothing,
react: function react() /*node, output, state*/{
return '\n';
}
},
var htmlNodeType = getHTMLNodeTypeFromASTNodeType(ast);
if (htmlNodeType === null) {
return null;
} /* bail out, not convertable to any HTML representation */
paragraph: {
match: blockRegex(PARAGRAPH_R),
order: PARSE_PRIORITY_LOW,
parse: parseCaptureInline,
react: function react(node, output, state) {
return h(
'p',
{ key: state.key },
output(node.content, state)
);
}
},
var props = _extends({ key: key }, ast.props);
ref: {
match: inlineRegex(REFERENCE_IMAGE_OR_LINK),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture /*, parse*/) {
refs[capture[1]] = {
target: capture[2],
title: capture[4]
};
if (Array.isArray(ast.children) && ast.children.length === 1 && ast.children[0].type === 'html') {
props.dangerouslySetInnerHTML = { __html: ast.children[0].value };
ast.children = null;
}
return {};
},
var override = overrides[htmlNodeType];
if (override) {
if (override.component) {
htmlNodeType = override.component;
} /* sub out the normal html tag name for the JSX / ReactFactory
passed in by the caller */
react: renderNothing
},
if (override.props) {
props = _extends({}, override.props, props);
} /* apply the prop overrides beneath the minimal set that are necessary
to have the markdown conversion work as expected */
}
refImage: {
match: inlineRegex(REFERENCE_IMAGE_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture) {
return {
alt: capture[1] || undefined,
ref: capture[2]
};
},
react: function react(node, output, state) {
return h('img', {
key: state.key,
alt: node.alt,
src: sanitizeUrl(refs[node.ref].target),
title: refs[node.ref].title
});
}
},
/* their props + our props, with any duplicate keys overwritten by us
(necessary evil, file an issue if something comes up that needs
extra attention, only props specified in `formExtraPropsForHTMLNodeType`
will be overwritten on a key collision) */
var finalProps = formExtraPropsForHTMLNodeType(props, ast, definitions);
refLink: {
match: inlineRegex(REFERENCE_LINK_R),
order: PARSE_PRIORITY_MAX,
parse: function parse(capture, _parse7, state) {
return {
content: _parse7(capture[1], state),
ref: capture[2]
};
},
react: function react(node, output, state) {
return h(
'a',
{
key: state.key,
href: sanitizeUrl(refs[node.ref].target),
title: refs[node.ref].title
},
output(node.content, state)
);
}
},
if (ast.children && ast.children.length === 1) {
if (TEXT_AST_TYPES.indexOf(ast.children[0].type) !== -1) {
ast.children = ast.children[0].value;
table: {
match: blockRegex(NP_TABLE_R),
order: PARSE_PRIORITY_HIGH,
parse: parseTable,
react: function react(node, output, state) {
function getStyle(colIndex) {
return node.align[colIndex] == null ? {} : {
textAlign: node.align[colIndex]
};
}
return h(
'table',
{ key: state.key },
h(
'thead',
null,
h(
'tr',
null,
node.header.map(function generateHeaderCell(content, i) {
return h(
'th',
{
key: i,
style: getStyle(i),
scope: 'col'
},
output(content, state)
);
})
)
),
h(
'tbody',
null,
node.cells.map(function generateTableRow(row, i) {
return h(
'tr',
{ key: i },
row.map(function generateTableCell(content, c) {
return h(
'td',
{ key: c, style: getStyle(c) },
output(content, state)
);
})
);
})
)
);
}
} /* solitary text children don't need full parsing or React will add a wrapper */
},
var children = Array.isArray(ast.children) ? ast.children.map(astToJSX) : ast.children;
text: {
// Here we look for anything followed by non-symbols,
// double newlines, or double-space-newlines
// We break on any symbol characters so that this grammar
// is easy to extend without needing to modify this regex
match: inlineRegex(TEXT_PLAIN_R),
order: PARSE_PRIORITY_MIN,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[0]
};
},
react: function react(node /*, output, state*/) {
return node.content;
}
},
return React.createElement(htmlNodeType, finalProps, ast.value || children);
}
textBolded: {
match: inlineRegex(TEXT_BOLD_R),
order: PARSE_PRIORITY_MED,
parse: parseCaptureInline,
react: function react(node, output, state) {
return h(
'strong',
{ key: state.key },
output(node.content, state)
);
}
},
if (typeof markdown !== 'string') {
throw new Error('markdown-to-jsx: the first argument must be\n a string');
}
textEmphasized: {
match: inlineRegex(TEXT_EMPHASIZED_R),
order: PARSE_PRIORITY_LOW,
parse: function parse(capture, _parse8, state) {
return {
content: _parse8(capture[2] || capture[1], state)
};
},
react: function react(node, output, state) {
return h(
'em',
{ key: state.key },
output(node.content, state)
);
}
},
if (getType.call(overrides) !== '[object Object]') {
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 }');
}
textEscaped: {
// We don't allow escaping numbers, letters, or spaces here so that
// backslashes used in plain text still get rendered. But allowing
// escaping anything else provides a very flexible escape mechanism,
// regardless of how this grammar is extended.
match: inlineRegex(TEXT_ESCAPED_R),
order: PARSE_PRIORITY_HIGH,
parse: function parse(capture /*, parse, state*/) {
return {
content: capture[1],
type: 'text'
};
}
},
var remarkAST = unified().data('settings', {
footnotes: true,
gfm: true,
position: false
}).use(parser).parse(markdown);
textStrikethroughed: {
match: inlineRegex(TEXT_STRIKETHROUGHED_R),
order: PARSE_PRIORITY_LOW,
parse: parseCaptureInline,
react: function react(node, output, state) {
return h(
'del',
{ key: state.key },
output(node.content, state)
);
}
}
};
var extracted = extractDefinitionsFromASTTree(remarkAST, astToJSX);
var parser = parserFor(rules);
var emitter = reactFor(ruleOutput(rules));
definitions = extracted.definitions;
footnotes = extracted.footnotes;
/**
* should not contain any block-level markdown like newlines, lists, headings,
* thematic breaks, blockquotes, etc
*/
var inline = /(\n|^[-*]\s|^#|^ {2,}|^-{2,}|^>\s)/g.test(markdown) === false;
coalesceInlineHTML(remarkAST);
var arr = emitter(parser(inline ? markdown : markdown + '\n\n', { inline: inline }));
var jsx = astToJSX(remarkAST);
var jsx = void 0;
if (arr.length > 1) {
jsx = h(
'div',
null,
arr
);
} else if (arr.length === 1) {
jsx = arr[0];
// discard the root <div> node if there is only one valid initial child
if (jsx.props.children && jsx.props.children.length === 1) {
jsx = jsx.props.children[0];
// TODO: remove this for React 16
if (typeof jsx === 'string') {
jsx = h(
'span',
null,
jsx
);
}
}
if (footnotes.length) {
jsx.props.children.push(React.createElement(
jsx.props.children.push(h(
'footer',
{ key: 'footnotes' },
footnotes
null,
footnotes.map(function createFootnote(def) {
return h(
'div',
{ id: def.identifier, key: def.identifier },
def.identifier,
emitter(parser(def.footnote, { inline: true }))
);
})
));

@@ -530,3 +1225,3 @@ }

return jsx;
};
}

@@ -542,15 +1237,26 @@ /**

*/
var Component = function Component(_ref2) {
var children = _ref2.children,
options = _ref2.options,
props = _objectWithoutProperties(_ref2, ['children', 'options']);
return compiler(children, options);
};
var Markdown = function (_React$PureComponent) {
_inherits(Markdown, _React$PureComponent);
Component.propTypes = {
function Markdown() {
_classCallCheck(this, Markdown);
return _possibleConstructorReturn(this, (Markdown.__proto__ || Object.getPrototypeOf(Markdown)).apply(this, arguments));
}
_createClass(Markdown, [{
key: 'render',
value: function render() {
return compiler(this.props.children, this.props.options);
}
}]);
return Markdown;
}(React.PureComponent);
Markdown.propTypes = {
children: PropTypes.string.isRequired,
options: PropTypes.object
};
export default Component;
export default Markdown;

@@ -0,86 +1,14 @@

/* @jsx h */
/**
* markdown-to-jsx@6 is a fork of [simple-markdown v0.2.2](https://github.com/Khan/simple-markdown)
* from Khan Academy. Thank you Khan devs for making such an awesome and extensible
* parsing infra... without it, half of the optimizations here wouldn't be feasible. 🙏🏼
*/
import PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import get from 'lodash.get';
import unified from 'unified';
import parser from 'remark-parse';
import unquote from 'unquote';
const BLOCK_ELEMENT_TAGS = [
'article',
'aside',
'blockquote',
'body',
'button',
'canvas',
'caption',
'col',
'colgroup',
'dd',
'details',
'div',
'dl',
'dt',
'embed',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'header',
'hgroup',
'hr',
'iframe',
'li',
'map',
'object',
'ol',
'output',
'p',
'pre',
'progress',
'script',
'section',
'style',
'summary',
'table',
'tbody',
'td',
'textarea',
'tfoot',
'th',
'thead',
'tr',
'ul',
'video',
];
const BLOCK_ELEMENT_REGEX = new RegExp(`^<(${BLOCK_ELEMENT_TAGS.join('|')})`, 'i');
const getType = Object.prototype.toString;
// [0] === tag, [...] = attribute pairs
const HTML_EXTRACTOR_REGEX = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
const SELF_CLOSING_ELEMENT_TAGS = [
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr',
];
const SELF_CLOSING_ELEMENT_REGEX = new RegExp(`^<(${SELF_CLOSING_ELEMENT_TAGS.join('|')})`, 'i');
const TEXT_AST_TYPES = ['text', 'textNode'];
/** TODO: Drop for React 16? */
const ATTRIBUTE_TO_JSX_PROP_MAP = {

@@ -133,492 +61,1166 @@ 'accept-charset': 'acceptCharset',

const getType = Object.prototype.toString;
/** TODO: Write explainers for each of these */
const ATTR_EXTRACTOR_R = /([-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
const AUTOLINK_MAILTO_CHECK_R = /mailto:/i;
const BLOCK_END_R = /\n{2,}$/;
const BLOCKQUOTE_R = /^( *>[^\n]+(\n[^\n]+)*\n*)+\n{2,}/;
const BLOCKQUOTE_TRIM_LEFT_MULTILINE_R = /^ *> ?/gm;
const BREAK_LINE_R = /^ {2,}\n/;
const BREAK_THEMATIC_R = /^( *[-*_]){3,} *(?:\n *)+\n/;
const CODE_BLOCK_FENCED_R = /^\s*(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n *)+\n/;
const CODE_BLOCK_R = /^(?: {4}[^\n]+\n*)+(?:\n *)+\n/;
const CODE_INLINE_R = /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/;
const CONSECUTIVE_NEWLINE_R = /^(?:\n *)*\n/;
const CR_NEWLINE_R = /\r\n?/g;
const FOOTNOTE_R = /^\[\^(.*)\](:.*)\n/;
const FOOTNOTE_REFERENCE_R = /^\[\^(.*)\]/;
const FORMFEED_R = /\f/g;
const GFM_TASK_R = /^\s*?\[(x|\s)\]/;
const HEADING_R = /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n *)+\n/;
const HEADING_SETEXT_R = /^([^\n]+)\n *(=|-){3,} *(?:\n *)+\n/;
const HTML_BLOCK_ELEMENT_R = /^<(.*)\s?(.*?)>(.*?)<\/\1>/;
const HTML_COMMENT_R = /^<!--.*?-->/;
const HTML_SELF_CLOSING_ELEMENT_R = /^<([^\s]*)\s?(.*?)>(.*?)(?!<\/\1>)/;
const LINK_AUTOLINK_BARE_URL_R = /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/;
const LINK_AUTOLINK_MAILTO_R = /^<([^ >]+@[^ >]+)>/;
const LINK_AUTOLINK_R = /^<([^ >]+:\/[^ >]+)>/;
const LIST_ITEM_END_R = / *\n+$/;
const LIST_LOOKBEHIND_R = /^$|\n *$/;
const NP_TABLE_R = /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/;
const NPTABLE_CELLS_TRIM = /\n$/;
const PARAGRAPH_R = /^((?:[^\n]|\n(?! *\n))+)(?:\n *)+\n/;
const REFERENCE_IMAGE_OR_LINK = /^\[([^\]]*)\]:\s*(\S+)\s*("([^"]*)")?/;
const REFERENCE_IMAGE_R = /^!\[([^\]]*)\]\[([^\]]*)\]/;
const REFERENCE_LINK_R = /^\[([^\]]*)\]\[([^\]]*)\]/;
const TAB_R = /\t/g;
const TABLE_ALIGN_TRIM = /^ *|\| *$/g;
const TABLE_CENTER_ALIGN = /^ *:-+: *$/;
const TABLE_HEADER_TRIM = /^ *| *\| *$/g;
const TABLE_LEFT_ALIGN = /^ *:-+ *$/;
const TABLE_RIGHT_ALIGN = /^ *-+: *$/;
const TABLE_ROW_SPLIT = / *\| */;
const TEXT_BOLD_R = /^[*_]{2}([\s\S]+?)[*_]{2}(?!\*|_)/;
const TEXT_EMPHASIZED_R = /^[*_]{1}([\s\S]+?)[*_]{1}(?!\*|_)/;
const TEXT_ESCAPED_R = /^\\([^0-9A-Za-z\s])/;
const TEXT_PLAIN_R = /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/;
const TEXT_STRIKETHROUGHED_R = /^~~(?=\S)([\s\S]*?\S)~~/;
const UNESCAPE_URL_R = /\\([^0-9A-Za-z\s])/g;
function extractDefinitionsFromASTTree(ast, parser) {
function reducer(aggregator, node) {
if (node.type === 'definition' || node.type === 'footnoteDefinition') {
aggregator.definitions[node.identifier] = node;
// recognize a `*` `-`, `+`, `1.`, `2.`... list bullet
const LIST_BULLET = '(?:[*+-]|\\d+\\.)';
if (node.type === 'footnoteDefinition') {
if ( node.children
&& node.children.length === 1
&& node.children[0].type === 'paragraph') {
node.children[0].children.unshift({
type: 'textNode',
value: `[${node.identifier}]: `,
});
} /* package the prefix inside the first child */
// recognize the start of a list item:
// leading space plus a bullet plus a space (` * `)
const LIST_ITEM_PREFIX = '( *)(' + LIST_BULLET + ') +';
const LIST_ITEM_PREFIX_R = new RegExp('^' + LIST_ITEM_PREFIX);
aggregator.footnotes.push(
<div key={node.identifier} id={node.identifier}>
{node.value || node.children.map(parser)}
</div>
);
}
}
// 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
const LIST_ITEM_R = new RegExp(
LIST_ITEM_PREFIX +
'[^\\n]*(?:\\n' +
'(?!\\1' + LIST_BULLET + ' )[^\\n]*)*(\n|$)',
'gm'
);
return Array.isArray(node.children)
? node.children.reduce(reducer, aggregator)
: aggregator;
};
// check whether a list item has paragraphs: if it does,
// we leave the newlines at the end
const LIST_R = new RegExp(
'^( *)(' + LIST_BULLET + ') ' +
'[\\s\\S]+?(?:\n{2,}(?! )' +
'(?!\\1' + LIST_BULLET + ' )\\n*' +
// the \\s*$ here is so that we can parse the inside of nested
// lists, where our content might end before we receive two `\n`s
'|\\s*\n*$)'
);
return [ast].reduce(reducer, {
definitions: {},
footnotes: []
const LINK_INSIDE = '(?:\\[[^\\]]*\\]|[^\\[\\]]|\\](?=[^\\[]*\\]))*';
const LINK_HREF_AND_TITLE = '\\s*<?((?:[^\\s\\\\]|\\\\.)*?)>?(?:\\s+[\'"]([\\s\\S]*?)[\'"])?\\s*';
const LINK_R = new RegExp(
'^\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)'
);
const IMAGE_R = new RegExp(
'^!\\[(' + LINK_INSIDE + ')\\]\\(' + LINK_HREF_AND_TITLE + '\\)'
);
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';
} else {
return null;
}
}
function parseTableHeader (capture, parse, state) {
let headerText = capture[1]
.replace(TABLE_HEADER_TRIM, '')
.split(TABLE_ROW_SPLIT);
return headerText.map(function (text) {
return parse(text, state);
});
}
function formExtraPropsForHTMLNodeType(props = {}, ast, definitions) {
switch (ast.type) {
case 'footnoteReference':
return {
...props,
href: `#${ast.identifier}`,
};
function parseTableAlign (capture/*, parse, state*/) {
let alignText = capture[2]
.replace(TABLE_ALIGN_TRIM, '')
.split(TABLE_ROW_SPLIT);
case 'image':
return {
...props,
title: ast.title,
alt: ast.alt,
src: ast.url,
};
return alignText.map(parseTableAlignCapture);
}
case 'imageReference':
return {
...props,
title: get(definitions, `['${ast.identifier}'].title`),
alt: ast.alt,
src: get(definitions, `['${ast.identifier}'].url`),
};
function parseTableCells (capture, parse, state) {
let rowsText = capture[3]
.replace(NPTABLE_CELLS_TRIM, '')
.split('\n');
case 'link':
return {
...props,
title: ast.title,
href: ast.url,
};
return rowsText.map(function (rowText) {
let cellText = rowText.split(TABLE_ROW_SPLIT);
return cellText.map(function (text) {
return parse(text, state);
});
});
}
case 'linkReference':
return {
...props,
title: get(definitions, `['${ast.identifier}'].title`),
href: get(definitions, `['${ast.identifier}'].url`),
};
function parseTable (capture, parse, state) {
state.inline = true;
let header = parseTableHeader(capture, parse, state);
let align = parseTableAlign(capture, parse, state);
let cells = parseTableCells(capture, parse, state);
state.inline = false;
case 'list':
return {
...props,
start: ast.start,
};
return {
align: align,
cells: cells,
header: header,
type: 'table',
};
}
case 'tableCell':
case 'th':
return {
...props,
style: {textAlign: ast.align},
};
function attributeValueToJSXPropValue (key, value) {
if (key === 'style') {
return value.split(/;\s?/).reduce(function (styles, kvPair) {
const key = kvPair.slice(0, kvPair.indexOf(':'));
// snake-case to camelCase
// also handles PascalCasing vendor prefixes
const camelCasedKey = key.replace(/(-[a-z])/g, function toUpper (substr) {
return substr[1].toUpperCase();
});
// key.length + 1 to skip over the colon
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim();
return styles;
}, {});
}
return props;
return value;
}
function getHTMLNodeTypeFromASTNodeType(node) {
switch (node.type) {
case 'break':
return 'br';
function attrStringToMap (str) {
const attributes = str.match(ATTR_EXTRACTOR_R);
case 'delete':
return 'del';
return attributes ? attributes.reduce(function (map, raw) {
const tuple = raw.split('=');
const key = tuple[0];
const value = tuple[1];
case 'emphasis':
return 'em';
map[ATTRIBUTE_TO_JSX_PROP_MAP[key] || key] = attributeValueToJSXPropValue(key, unquote(value));
case 'footnoteReference':
return 'a';
return map;
}, {}) : undefined;
}
case 'heading':
return `h${node.depth}`;
// Turn various crazy whitespace into easy to process things
function preprocess (source) {
return source.replace(CR_NEWLINE_R, '\n')
.replace(FORMFEED_R, '')
.replace(TAB_R, ' ');
}
case 'image':
case 'imageReference':
return 'img';
/**
* 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) {
// Sorts rules in order of increasing order, then
// ascending rule name in case of ties.
let ruleList = Object.keys(rules);
case 'inlineCode':
return 'code';
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production') {
ruleList.forEach(function (type) {
let order = rules[type].order;
if (
process.env.NODE_ENV !== 'production'
&& (typeof order !== 'number' || !isFinite(order))
&& typeof console !== 'undefined'
) {
console.warn(
'simple-markdown: Invalid order for rule `' + type + '`: ' +
order
);
}
});
}
case 'link':
case 'linkReference':
return 'a';
ruleList.sort(function (typeA, typeB) {
let orderA = rules[typeA].order;
let orderB = rules[typeB].order;
case 'list':
return node.ordered ? 'ol' : 'ul';
// First sort based on increasing order
if (orderA !== orderB) {
return orderA - orderB;
case 'listItem':
return 'li';
// Then based on increasing unicode lexicographic ordering
} else if (typeA < typeB) {
return -1;
}
case 'paragraph':
return 'p';
return 1;
});
case 'root':
return 'div';
function nestedParse (source, state) {
let result = [];
case 'tableHeader':
return 'thead';
// We store the previous capture so that match functions can
// use some limited amount of lookbehind. Lists use this to
// ensure they don't match arbitrary '- ' or '* ' in inline
// text (see the list rule for more information).
let prevCapture = '';
while (source) {
let i = 0;
while (i < ruleList.length) {
let ruleType = ruleList[i];
let rule = rules[ruleType];
let capture = rule.match(source, state, prevCapture);
case 'tableRow':
return 'tr';
if (capture) {
let currCaptureString = capture[0];
source = source.substring(currCaptureString.length);
let parsed = rule.parse(capture, nestedParse, state);
case 'tableCell':
return 'td';
// We also let rules override the default type of
// their parsed node if they would like to, so that
// there can be a single output function for all links,
// even if there are several rules to parse them.
if (parsed.type == null) {
parsed.type = ruleType;
}
case 'thematicBreak':
return 'hr';
result.push(parsed);
case 'definition':
case 'footnoteDefinition':
case 'yaml':
return null;
prevCapture = currCaptureString;
break;
}
default:
return node.type;
i++;
}
}
return result;
}
return function outerParse (source, state) {
return nestedParse(preprocess(source), state);
};
}
function seekCellsAndAlignThemIfNecessary(root, alignmentValues) {
const mapper = (child, index) => {
if (child.type === 'tableCell') {
return {
...child,
align: alignmentValues[index],
};
} else if (Array.isArray(child.children) && child.children.length) {
return child.children.map(mapper);
// Creates a match function for an inline scoped element from a regex
function inlineRegex (regex) {
function match (source, state) {
if (state.inline) {
return regex.exec(source);
} else {
return null;
}
}
return child;
};
match.regex = regex;
if (Array.isArray(root.children) && root.children.length) {
root.children = root.children.map(mapper);
return match;
}
// Creates a match function for a block scoped element from a regex
function blockRegex (regex) {
function match (source, state) {
if (state.inline) {
return null;
} else {
return regex.exec(source);
}
}
return root;
match.regex = regex;
return match;
}
function attributeValueToJSXPropValue(key, value) {
if (key === 'style') {
return value.split(/;\s?/).reduce((styles, kvPair) => {
// Creates a match function from a regex, ignoring block/inline scope
function anyScopeRegex (regex) {
function match (source/*, state*/) {
return regex.exec(source);
}
const key = kvPair.slice(0, kvPair.indexOf(':'));
match.regex = regex;
// snake-case to camelCase
// also handles PascalCasing vendor prefixes
const camelCasedKey = key.replace(/(\-[a-z])/g, (substr) => substr[1].toUpperCase());
return match;
}
// key.length + 1 to skip over the colon
styles[camelCasedKey] = kvPair.slice(key.length + 1).trim();
function reactFor (outputFunc) {
return function nestedReactOutput (ast, state) {
state = state || {};
if (Array.isArray(ast)) {
let oldKey = state.key;
let result = [];
return styles;
// map nestedOutput over the ast, except group any text
// nodes together into a single string output.
let lastWasString = false;
for (let i = 0; i < ast.length; i++) {
state.key = i;
let nodeOut = nestedReactOutput(ast[i], state);
let isString = (typeof nodeOut === 'string');
if (isString && lastWasString) {
result[result.length - 1] += nodeOut;
} else {
result.push(nodeOut);
}
lastWasString = isString;
}
}, {});
state.key = oldKey;
return result;
} else {
return outputFunc(ast, nestedReactOutput, state);
}
};
}
function sanitizeUrl (url) {
try {
let prot = decodeURIComponent(url)
.replace(/[^A-Za-z0-9/:]/g, '')
.toLowerCase();
if (prot.indexOf('javascript:') === 0) {
return null;
}
} catch (e) {
// decodeURIComponent sometimes throws a URIError
// See `decodeURIComponent('a%AFc');`
// http://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception
return null;
}
return value;
return url;
}
function isCoalesceableHTML(html) {
// ignore block-level elements
// ignore self-closing or non-content-bearing elements
return html.match(BLOCK_ELEMENT_REGEX) || html.match(SELF_CLOSING_ELEMENT_REGEX) ? false : true;
function unescapeUrl (rawUrlString) {
return rawUrlString.replace(UNESCAPE_URL_R, '$1');
}
function coalesceInlineHTML(ast) {
function coalescer(node, index, siblings) {
if (node.type === 'html') {
if (!isCoalesceableHTML(node.value)) {
return;
} else if (node.value.indexOf('<!--') !== -1) {
// throw out HTML comments
siblings.splice(index, 1);
}
// Parse some content with the parser `parse`, with state.inline
// set to true. Useful for block elements; not generally necessary
// to be used by inline elements (where state.inline is already true.
function parseInline (parse, content, state) {
let isCurrentlyInline = state.inline || false;
state.inline = true;
let result = parse(content, state);
state.inline = isCurrentlyInline;
return result;
}
// are there more html nodes directly after? if so, fold them into the current node
if (index < siblings.length - 1 && siblings[index + 1].type === 'html') {
// create a new coalescer context
coalescer(siblings[index + 1], index + 1, siblings);
}
function parseBlock (parse, content, state) {
state.inline = false;
return parse(content + '\n\n', state);
}
let i = index + 1;
let end;
function parseCaptureInline (capture, parse, state) {
return {
content: parseInline(parse, capture[1], state),
};
}
// where's the end tag?
while (end === undefined && i < siblings.length) {
if ( siblings[i].type !== 'html'
|| (siblings[i].type === 'html' && !isCoalesceableHTML(siblings[i].value))) {
i += 1;
continue;
}
function captureNothing () { return {}; }
function renderNothing () { return null; }
end = siblings[i];
}
function ruleOutput (rules) {
return function nestedRuleOutput (ast, outputFunc, state) {
return rules[ast.type].react(ast, outputFunc, state);
};
}
/* all interim elements now become children of the current node, and we splice them (including end tag)
out of the sibling array so they will not be iterated-over by forEach */
/**
* anything that must scan the tree before everything else
*/
const PARSE_PRIORITY_MAX = 1;
node.children = siblings.slice(index + 1, i);
siblings.splice(index + 1, i - index);
/**
* scans for block-level constructs
*/
const PARSE_PRIORITY_HIGH = 2;
const [tag, ...attributePairs] = node.value.match(HTML_EXTRACTOR_REGEX);
/**
* inline w/ more priority than other inline
*/
const PARSE_PRIORITY_MED = 3;
// reassign the current node to whatever its tag is
node.type = tag.toLowerCase();
/**
* inline elements
*/
const PARSE_PRIORITY_LOW = 4;
// make a best-effort conversion to JSX props
node.props = attributePairs.reduce(function(props, kvPair) {
const valueIndex = kvPair.indexOf('=');
const key = kvPair.slice(0, valueIndex === -1 ? undefined : valueIndex);
/**
* bare text and stuff that is considered leftovers
*/
const PARSE_PRIORITY_MIN = 5;
// ignoring inline event handlers at this time - they pose enough of a security risk that they're
// not worth preserving; there's a reason React calls it "dangerouslySetInnerHTML"!
export function compiler (markdown, {overrides = {}} = {}) {
// eslint-disable-next-line no-unused-vars
function h (tag, props, ...children) {
return React.createElement(
overrides[tag] && overrides[tag].component || tag, {
...(overrides[tag] && overrides[tag].props),
...props,
}, ...children
);
}
if (key.indexOf('on') !== 0) {
let value = kvPair.slice(key.length + 1);
/* istanbul ignore next */
if (process.env.NODE_ENV !== 'production') {
if (typeof markdown !== 'string') {
throw new Error(`markdown-to-jsx: the first argument must be
a string`);
}
// strip the outermost single/double quote if it exists
if (value[0] === '"' || value[0] === '\'') {
value = value.slice(1, value.length - 1);
}
if (getType.call(overrides) !== '[object Object]') {
throw new Error(`markdown-to-jsx: options.overrides (second argument property) must be
undefined or an object literal with shape:
{
htmltagname: {
component: string|ReactComponent(optional),
props: object(optional)
}
}`);
}
}
props[ATTRIBUTE_TO_JSX_PROP_MAP[key] || key] = attributeValueToJSXPropValue(key, value) || true;
}
const footnotes = [];
const refs = {};
return props;
/**
* each rule's react() output function goes through our custom h() JSX pragma;
* this allows the override functionality to be automatically applied
*/
const rules = {
blockQuote: {
match: blockRegex(BLOCKQUOTE_R),
order: PARSE_PRIORITY_HIGH,
parse (capture, parse, state) {
return {
content: parse(capture[0].replace(BLOCKQUOTE_TRIM_LEFT_MULTILINE_R, ''), state),
};
},
react (node, output, state) {
return (
<blockquote key={state.key}>
{output(node.content, state)}
</blockquote>
);
},
},
}, {});
breakLine: {
match: anyScopeRegex(BREAK_LINE_R),
order: PARSE_PRIORITY_HIGH,
parse: captureNothing,
react (_, __, state) {
return (
<br key={state.key} />
);
},
},
// null out .value or astToJSX() will set it as the child
node.value = null;
}
breakThematic: {
match: blockRegex(BREAK_THEMATIC_R),
order: PARSE_PRIORITY_HIGH,
parse: captureNothing,
react (_, __, state) {
return (
<hr key={state.key} />
);
},
},
if (node.children) {
node.children.forEach(coalescer);
}
};
codeBlock: {
match: blockRegex(CODE_BLOCK_R),
order: PARSE_PRIORITY_MAX,
parse (capture/*, parse, state*/) {
let content = capture[0]
.replace(/^ {4}/gm, '')
.replace(/\n+$/, '');
return {
content: content,
lang: undefined,
};
},
ast.children.forEach(coalescer);
}
react (node, output, state) {
return (
<pre key={state.key}>
<code className={node.lang ? `lang-${node.lang}` : ''}>
{node.content}
</code>
</pre>
);
},
},
export function compiler(markdown, {overrides = {}} = {}) {
let definitions;
let footnotes;
codeFenced: {
match: blockRegex(CODE_BLOCK_FENCED_R),
order: PARSE_PRIORITY_MAX,
parse (capture/*, parse, state*/) {
return {
content: capture[3],
lang: capture[2] || undefined,
type: 'codeBlock',
};
},
},
function astToJSX(ast, index) { /* `this` is the dictionary of definitions */
if (TEXT_AST_TYPES.indexOf(ast.type) !== -1) {
return ast.value;
}
codeInline: {
match: inlineRegex(CODE_INLINE_R),
order: PARSE_PRIORITY_LOW,
parse (capture/*, parse, state*/) {
return {
content: capture[2],
};
},
react (node, output, state) {
return (
<code key={state.key}>
{node.content}
</code>
);
},
},
const key = index || '0';
/**
* footnotes are emitted at the end of compilation in a special <footer> block
*/
footnote: {
match: blockRegex(FOOTNOTE_R),
order: PARSE_PRIORITY_MAX,
parse (capture/*, parse, state*/) {
footnotes.push({
footnote: capture[2],
identifier: capture[1],
});
if (ast.type === 'code' && ast.value) {
const preProps = {
...get(overrides, 'pre.props', {}),
key,
};
return {};
},
react: renderNothing,
},
const langClassName = `lang-${ast.lang}`;
const codeBaseProps = get(overrides, 'code.props', {});
const codeProps = {
...codeBaseProps,
className: codeBaseProps.className
? `${codeBaseProps.className} ${langClassName}`
: langClassName
};
footnoteReference: {
match: inlineRegex(FOOTNOTE_REFERENCE_R),
order: PARSE_PRIORITY_HIGH,
parse (capture/*, parse*/) {
return {
content: capture[1],
target: `#${capture[1]}`,
};
},
react (node, output, state) {
return (
<a key={state.key} href={sanitizeUrl(node.target)}>
<sup key={state.key}>
{node.content}
</sup>
</a>
);
},
},
return React.createElement(
get(overrides, 'pre.component', 'pre'),
preProps,
React.createElement(
get(overrides, 'code.component', 'code'),
codeProps,
ast.value
),
);
} /* Refers to fenced blocks, need to create a pre:code nested structure */
gfmTask: {
match: inlineRegex(GFM_TASK_R),
order: PARSE_PRIORITY_HIGH,
parse (capture/*, parse, state*/) {
return {
completed: capture[1].toLowerCase() === 'x',
};
},
react (node, output, state) {
return (
<input
checked={node.completed}
key={state.key}
readOnly
type="checkbox"
/>
);
},
},
if (ast.type === 'list' && ast.loose === false) {
ast.children = ast.children.map(item => {
if (item.children.length === 1 && item.children[0].type === 'paragraph') {
return {
...item,
children: item.children[0].children,
};
}
heading: {
match: blockRegex(HEADING_R),
order: PARSE_PRIORITY_HIGH,
parse (capture, parse, state) {
return {
content: parseInline(parse, capture[2], state),
level: capture[1].length,
};
},
react (node, output, state) {
const Tag = `h${node.level}`;
return (
<Tag key={state.key}>
{output(node.content, state)}
</Tag>
);
},
},
return item;
});
} /* tight list, remove the paragraph wrapper just inside the listItem */
headingSetext: {
match: blockRegex(HEADING_SETEXT_R),
order: PARSE_PRIORITY_MAX,
parse (capture, parse, state) {
return {
content: parseInline(parse, capture[1], state),
level: capture[2] === '=' ? 1 : 2,
type: 'heading',
};
},
},
if (ast.type === 'listItem') {
if (ast.checked === true || ast.checked === false) {
const liProps = {
...get(overrides, 'li.props', {}),
key,
htmlBlock: {
/**
* find the first matching end tag and process the interior
*/
match: anyScopeRegex(HTML_BLOCK_ELEMENT_R),
order: PARSE_PRIORITY_HIGH,
parse (capture, parse, state) {
const parseFunc = capture[3].match(HTML_BLOCK_ELEMENT_R) ? parseBlock : parseInline;
return {
attrs: attrStringToMap(capture[2]),
/**
* if another html block is detected within, parse as block,
* otherwise parse as inline to pick up any further markdown
*/
content: parseFunc(parse, capture[3], state),
tag: capture[1],
};
},
react (node, output, state) {
return (
<node.tag key={state.key} {...node.attrs}>
{output(node.content, state)}
</node.tag>
);
},
},
const inputProps = {
...get(overrides, 'input.props', {}),
key: 'checkbox',
type: 'checkbox',
checked: ast.checked,
readOnly: true
htmlComment: {
match: anyScopeRegex(HTML_COMMENT_R),
order: PARSE_PRIORITY_HIGH,
parse () { return {}; },
react: renderNothing,
},
htmlSelfClosing: {
/**
* find the first matching end tag and process the interior
*/
match: inlineRegex(HTML_SELF_CLOSING_ELEMENT_R),
order: PARSE_PRIORITY_HIGH,
parse (capture/*, parse, state*/) {
return {
attrs: attrStringToMap(capture[2]),
tag: capture[1],
};
},
react (node, output, state) {
return (
<node.tag
{...node.attrs}
key={state.key}
/>
);
},
},
return React.createElement(
get(overrides, 'li.component', 'li'),
liProps,
[
React.createElement(get(overrides, 'input.component', 'input'), inputProps),
ast.children.map(astToJSX),
],
image: {
match: inlineRegex(IMAGE_R),
order: PARSE_PRIORITY_HIGH,
parse (capture/*, parse, state*/) {
let image = {
alt: capture[1],
target: unescapeUrl(capture[2]),
title: capture[3],
};
return image;
},
react (node, output, state) {
return (
<img
key={state.key}
alt={node.alt || undefined}
title={node.title || undefined}
src={sanitizeUrl(node.target)}
/>
);
} /* gfm task list, need to add a checkbox */
}
},
},
if (ast.type === 'html') {
return (
<div key={key} dangerouslySetInnerHTML={{__html: ast.value}} />
);
} /* arbitrary HTML, do the gross thing for now */
link: {
match: inlineRegex(LINK_R),
order: PARSE_PRIORITY_LOW,
parse (capture, parse, state) {
let link ={
content: parse(capture[1], state),
target: unescapeUrl(capture[2]),
title: capture[3],
};
return link;
},
react (node, output, state) {
return (
<a
key={state.key}
href={sanitizeUrl(node.target)}
title={node.title}
>
{output(node.content, state)}
</a>
);
},
},
if (ast.type === 'table') {
const tbody = {type: 'tbody', children: []};
// https://daringfireball.net/projects/markdown/syntax#autolink
linkAngleBraceStyleDetector: {
match: inlineRegex(LINK_AUTOLINK_R),
order: PARSE_PRIORITY_MAX,
parse (capture/*, parse, state*/) {
return {
content: [{
content: capture[1],
type: 'text',
}],
target: capture[1],
type: 'link',
};
},
},
ast.children = ast.children.reduce((children, child, index) => {
if (index === 0) {
/* manually marking the first row as tableHeader since that was removed in remark@4.x; it's important semantically. */
child.type = 'tableHeader';
children.unshift(
seekCellsAndAlignThemIfNecessary(child, ast.align)
);
} else if (child.type === 'tableRow') {
tbody.children.push(
seekCellsAndAlignThemIfNecessary(child, ast.align)
);
} else if (child.type === 'tableFooter') {
children.push(
seekCellsAndAlignThemIfNecessary(child, ast.align)
);
linkBareUrlDetector: {
match: inlineRegex(LINK_AUTOLINK_BARE_URL_R),
order: PARSE_PRIORITY_MAX,
parse (capture/*, parse, state*/) {
return {
content: [{
content: capture[1],
type: 'text',
}],
target: capture[1],
title: undefined,
type: 'link',
};
},
},
linkMailtoDetector: {
match: inlineRegex(LINK_AUTOLINK_MAILTO_R),
order: PARSE_PRIORITY_MAX,
parse (capture/*, parse, state*/) {
let address = capture[1];
let target = capture[1];
// Check for a `mailto:` already existing in the link:
if (!AUTOLINK_MAILTO_CHECK_R.test(target)) {
target = 'mailto:' + target;
}
return children;
return {
content: [{
content: address.replace('mailto:', ''),
type: 'text',
}],
target: target,
type: 'link',
};
},
},
}, [tbody]);
list: {
match (source, state, prevCapture) {
// We only want to break into a list if we are at the start of a
// line. This is to avoid parsing "hi * there" with "* there"
// becoming a part of a list.
// You might wonder, "but that's inline, so of course it wouldn't
// start a list?". You would be correct! Except that some of our
// lists can be inline, because they might be inside another list,
// in which case we can parse with inline scope, but need to allow
// nested lists inside this inline scope.
let isStartOfLine = LIST_LOOKBEHIND_R.test(prevCapture);
let isListBlock = state._list || !state.inline;
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap tablerow(s) in a tbody */
if (isStartOfLine && isListBlock) {
return LIST_R.exec(source);
} else {
return null;
}
},
order: PARSE_PRIORITY_HIGH,
parse (capture, parse, state) {
let bullet = capture[2];
let ordered = bullet.length > 1;
let start = ordered ? +bullet : undefined;
let 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);
if (ast.type === 'tableFooter') {
ast.children = [{
type: 'tr',
children: ast.children
}];
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap the cells in a tablerow */
let lastItemWasAParagraph = false;
let itemContent = items.map(function (item, i) {
// We need to see how far indented this item is:
let space = LIST_ITEM_PREFIX_R.exec(item)[0].length;
// And then we construct a regex to "unindent" the subsequent
// lines of the items by that amount:
let spaceRegex = new RegExp('^ {1,' + space + '}', 'gm');
if (ast.type === 'tableHeader') {
ast.children = [{
type: 'tr',
children: ast.children.map(child => {
if (child.type === 'tableCell') {
child.type = 'th';
} /* et voila, a proper table header */
// Before processing the item, we need a couple things
let content = item
// remove indents on trailing lines:
.replace(spaceRegex, '')
// remove the bullet:
.replace(LIST_ITEM_PREFIX_R, '');
return child;
})
}];
} /* React yells if things aren't in the proper structure, so need to
delve into the immediate children and wrap the cells in a tablerow */
// Handling "loose" lists, like:
//
// * this is wrapped in a paragraph
//
// * as is this
//
// * as is this
let isLastItem = (i === items.length - 1);
let containsBlocks = content.indexOf('\n\n') !== -1;
if (ast.type === 'footnoteReference') {
ast.children = [{type: 'sup', value: ast.identifier}];
} /* place the identifier inside a superscript tag for the link */
// 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).
let thisItemIsAParagraph = containsBlocks ||
(isLastItem && lastItemWasAParagraph);
lastItemWasAParagraph = thisItemIsAParagraph;
let htmlNodeType = getHTMLNodeTypeFromASTNodeType(ast);
if (htmlNodeType === null) {
return null;
} /* bail out, not convertable to any HTML representation */
// backup our state for restoration afterwards. We're going to
// want to set state._list to true, and state.inline depending
// on our list's looseness.
let oldStateInline = state.inline;
let oldStateList = state._list;
state._list = true;
let props = {key, ...ast.props};
// Parse inline if we're in a tight list, or block if we're in
// a loose list.
let adjustedContent;
if (thisItemIsAParagraph) {
state.inline = false;
adjustedContent = content.replace(LIST_ITEM_END_R, '\n\n');
} else {
state.inline = true;
adjustedContent = content.replace(LIST_ITEM_END_R, '');
}
if (Array.isArray(ast.children) && ast.children.length === 1 && ast.children[0].type === 'html') {
props.dangerouslySetInnerHTML = {__html: ast.children[0].value};
ast.children = null;
}
let result = parse(adjustedContent, state);
const override = overrides[htmlNodeType];
if (override) {
if (override.component) {
htmlNodeType = override.component;
} /* sub out the normal html tag name for the JSX / ReactFactory
passed in by the caller */
// Restore our state before returning
state.inline = oldStateInline;
state._list = oldStateList;
return result;
});
if (override.props) {
props = {...override.props, ...props};
} /* apply the prop overrides beneath the minimal set that are necessary
to have the markdown conversion work as expected */
}
return {
items: itemContent,
ordered: ordered,
start: start,
};
},
react (node, output, state) {
const Tag = node.ordered ? 'ol' : 'ul';
/* their props + our props, with any duplicate keys overwritten by us
(necessary evil, file an issue if something comes up that needs
extra attention, only props specified in `formExtraPropsForHTMLNodeType`
will be overwritten on a key collision) */
const finalProps = formExtraPropsForHTMLNodeType(props, ast, definitions);
return (
<Tag key={state.key} start={node.start}>
{node.items.map(function generateListItem (item, i) {
return (
<li key={i}>
{output(item, state)}
</li>
);
})}
</Tag>
);
},
},
if (ast.children && ast.children.length === 1) {
if (TEXT_AST_TYPES.indexOf(ast.children[0].type) !== -1) {
ast.children = ast.children[0].value;
}
} /* solitary text children don't need full parsing or React will add a wrapper */
newlineCoalescer: {
match: blockRegex(CONSECUTIVE_NEWLINE_R),
order: PARSE_PRIORITY_LOW,
parse: captureNothing,
react (/*node, output, state*/) { return '\n'; },
},
const children = Array.isArray(ast.children)
? ast.children.map(astToJSX)
: ast.children;
paragraph: {
match: blockRegex(PARAGRAPH_R),
order: PARSE_PRIORITY_LOW,
parse: parseCaptureInline,
react (node, output, state) {
return (
<p key={state.key}>
{output(node.content, state)}
</p>
);
},
},
return React.createElement(htmlNodeType, finalProps, ast.value || children);
}
ref: {
match: inlineRegex(REFERENCE_IMAGE_OR_LINK),
order: PARSE_PRIORITY_MAX,
parse (capture/*, parse*/) {
refs[capture[1]] = {
target: capture[2],
title: capture[4],
};
if (typeof markdown !== 'string') {
throw new Error(`markdown-to-jsx: the first argument must be
a string`);
}
return {};
},
react: renderNothing,
},
if (getType.call(overrides) !== '[object Object]') {
throw new Error(`markdown-to-jsx: options.overrides (second argument property) must be
undefined or an object literal with shape:
{
htmltagname: {
component: string|ReactComponent(optional),
props: object(optional)
}
}`);
}
refImage: {
match: inlineRegex(REFERENCE_IMAGE_R),
order: PARSE_PRIORITY_MAX,
parse (capture) {
return {
alt: capture[1] || undefined,
ref: capture[2],
};
},
react (node, output, state) {
return (
<img
key={state.key}
alt={node.alt}
src={sanitizeUrl(refs[node.ref].target)}
title={refs[node.ref].title}
/>
);
},
},
const remarkAST = unified().data('settings', {
footnotes: true,
gfm: true,
position: false,
}).use(parser).parse(markdown);
refLink: {
match: inlineRegex(REFERENCE_LINK_R),
order: PARSE_PRIORITY_MAX,
parse (capture, parse, state) {
return {
content: parse(capture[1], state),
ref: capture[2],
};
},
react (node, output, state) {
return (
<a
key={state.key}
href={sanitizeUrl(refs[node.ref].target)}
title={refs[node.ref].title}
>
{output(node.content, state)}
</a>
);
},
},
const extracted = extractDefinitionsFromASTTree(remarkAST, astToJSX);
table: {
match: blockRegex(NP_TABLE_R),
order: PARSE_PRIORITY_HIGH,
parse: parseTable,
react (node, output, state) {
function getStyle (colIndex) {
return node.align[colIndex] == null ? {} : {
textAlign: node.align[colIndex],
};
}
definitions = extracted.definitions;
footnotes = extracted.footnotes;
return (
<table key={state.key}>
<thead>
<tr>
{node.header.map(function generateHeaderCell (content, i) {
return (
<th
key={i}
style={getStyle(i)}
scope="col"
>
{output(content, state)}
</th>
);
})}
</tr>
</thead>
coalesceInlineHTML(remarkAST);
<tbody>
{node.cells.map(function generateTableRow (row, i) {
return (
<tr key={i}>
{row.map(function generateTableCell (content, c) {
return (
<td key={c} style={getStyle(c)}>
{output(content, state)}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
},
},
let jsx = astToJSX(remarkAST);
text: {
// Here we look for anything followed by non-symbols,
// double newlines, or double-space-newlines
// We break on any symbol characters so that this grammar
// is easy to extend without needing to modify this regex
match: inlineRegex(TEXT_PLAIN_R),
order: PARSE_PRIORITY_MIN,
parse (capture/*, parse, state*/) {
return {
content: capture[0],
};
},
react (node/*, output, state*/) {
return node.content;
},
},
// discard the root <div> node if there is only one valid initial child
if (jsx.props.children && jsx.props.children.length === 1) {
jsx = jsx.props.children[0];
textBolded: {
match: inlineRegex(TEXT_BOLD_R),
order: PARSE_PRIORITY_MED,
parse: parseCaptureInline,
react (node, output, state) {
return (
<strong key={state.key}>
{output(node.content, state)}
</strong>
);
},
},
textEmphasized: {
match: inlineRegex(TEXT_EMPHASIZED_R),
order: PARSE_PRIORITY_LOW,
parse (capture, parse, state) {
return {
content: parse(capture[2] || capture[1], state),
};
},
react (node, output, state) {
return (
<em key={state.key}>
{output(node.content, state)}
</em>
);
},
},
textEscaped: {
// We don't allow escaping numbers, letters, or spaces here so that
// backslashes used in plain text still get rendered. But allowing
// escaping anything else provides a very flexible escape mechanism,
// regardless of how this grammar is extended.
match: inlineRegex(TEXT_ESCAPED_R),
order: PARSE_PRIORITY_HIGH,
parse (capture/*, parse, state*/) {
return {
content: capture[1],
type: 'text',
};
},
},
textStrikethroughed: {
match: inlineRegex(TEXT_STRIKETHROUGHED_R),
order: PARSE_PRIORITY_LOW,
parse: parseCaptureInline,
react (node, output, state) {
return (
<del key={state.key}>
{output(node.content, state)}
</del>
);
},
},
};
const parser = parserFor(rules);
const emitter = reactFor(ruleOutput(rules));
/**
* should not contain any block-level markdown like newlines, lists, headings,
* thematic breaks, blockquotes, etc
*/
const inline = /(\n|^[-*]\s|^#|^ {2,}|^-{2,}|^>\s)/g.test(markdown) === false;
const arr = emitter(parser(inline ? markdown : `${markdown}\n\n`, { inline }));
let jsx;
if (arr.length > 1) {
jsx = (
<div>
{arr}
</div>
);
} else if (arr.length === 1) {
jsx = arr[0];
// TODO: remove this for React 16
if (typeof jsx === 'string') {
jsx = <span>{jsx}</span>;
}
}

@@ -628,3 +1230,11 @@

jsx.props.children.push(
<footer key='footnotes'>{footnotes}</footer>
<footer>
{footnotes.map(function createFootnote (def) {
return (
<div id={def.identifier} key={def.identifier}>
{def.identifier}{emitter(parser(def.footnote, { inline: true }))}
</div>
);
})}
</footer>
);

@@ -634,3 +1244,3 @@ }

return jsx;
};
}

@@ -646,9 +1256,11 @@ /**

*/
const Component = ({children, options, ...props}) => compiler(children, options);
export default class Markdown extends React.PureComponent {
static propTypes = {
children: PropTypes.string.isRequired,
options: PropTypes.object,
};
Component.propTypes = {
children: PropTypes.string.isRequired,
options: PropTypes.object,
};
export default Component;
render () {
return compiler(this.props.children, this.props.options);
}
}

@@ -6,3 +6,3 @@ {

"license": "MIT",
"version": "5.4.2",
"version": "6.0.2",
"engines": {

@@ -14,4 +14,3 @@ "node": ">= 4"

"react",
"jsx",
"remark"
"jsx"
],

@@ -33,5 +32,6 @@ "author": "Evan Scott <probablyup@gmail.com>",

"babel-cli": "^6.14.0",
"babel-eslint": "^8.0.3",
"babel-jest": "^20.0.3",
"babel-loader": "^7.1.1",
"babel-plugin-styled-components": "^1.1.7",
"babel-plugin-emotion": "^8.0.11",
"babel-plugin-transform-react-remove-prop-types": "^0.4.6",

@@ -42,10 +42,15 @@ "babel-preset-es2015": "^6.14.0",

"codecov": "^2.1.0",
"emotion": "^8.0.11",
"eslint": "^4.12.1",
"eslint-plugin-react": "^7.5.1",
"in-publish": "^2.0.0",
"jest": "^20.0.4",
"jest-serializer-html": "^4.0.1",
"polished": "^1.3.0",
"preact": "^8.2.1",
"preact-compat": "^3.16.0",
"preact-emotion": "^8.0.11",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"styled-components": "^2.1.1",
"size-limit": "^0.13.1",
"uglify-js": "^2.7.3",

@@ -58,4 +63,3 @@ "webpack": "^3.4.1",

"prop-types": "^15.5.10",
"remark-parse": "^4.0.0",
"unified": "^6.1.5"
"unquote": "^1.1.0"
},

@@ -74,7 +78,21 @@ "peerDependencies": {

"prepublish": "in-publish && npm run build || not-in-publish",
"lint": "eslint index.js index.spec.js site.js",
"build": "babel index.js --out-file index.cjs.js && BABEL_ENV=esm babel index.js --out-file index.esm.js",
"release": "webpack --config ./webpack.release.config.babel.js -p --display-optimization-bailout",
"release": "webpack --config ./webpack.config.prod.babel.js -p --display-optimization-bailout",
"release:debug": "webpack --config ./webpack.config.babel.js --display-optimization-bailout",
"start": "webpack-dev-server --hot --inline --open",
"test": "jest --verbose"
"test": "jest --verbose",
"size": "size-limit"
},
"size-limit": [
{
"path": "index.cjs.js",
"limit": "5.25 kB"
}
],
"jest": {
"snapshotSerializers": [
"jest-serializer-html"
]
}
}

@@ -5,12 +5,16 @@ # markdown to jsx compiler

Enables the safe parsing of markdown into proper React JSX objects, so you don't need to use a pattern like `dangerouslySetInnerHTML` and potentially open your application up to security issues.
`markdown-to-jsx` uses a fork of [simple-markdown](https://github.com/Khan/simple-markdown) as its parsing engine and extends it in a number of ways to make your life easier. Notably, this package offers the following additional benefits:
The only exception is arbitrary block-level HTML in the markdown (considered a markdown antipattern), which will still use the unsafe method.
- Arbitrary HTML is supported and parsed into the appropriate JSX representation
without `dangerouslySetInnerHTML`
Uses [remark-parse](https://github.com/wooorm/remark-parse) under the hood to parse markdown into a consistent AST format. The following [remark](https://github.com/wooorm/remark) settings are set by `markdown-to-jsx`:
- Any HTML tags rendered by the compiler and/or `<Markdown>` component can be overridden to include additional
props or even a different HTML representation entirely.
- footnotes: true
- gfm: true
- position: false
- GFM task list support.
- Fenced code blocks with [highlight.js](https://highlightjs.org/) support.
All this clocks in at around 5 kB gzipped, which is a fraction of the size of most other React markdown components.
Requires React >= 0.14.

@@ -29,9 +33,7 @@

const markdown = `
# Hello world!
`.trim();
const markdown = `# Hello world!`.trim();
render((
<Markdown>
{markdown}
# Hello world!
</Markdown>

@@ -47,3 +49,3 @@ ), document.body);

\* __NOTE: JSX does not natively preserve newlines in multiline text, which is why the example above is inside an ES6 template literal. In general, writing markdown directly in JSX is discouraged and it's a better idea to keep your content in separate .md files and require them, perhaps using webpack's [raw-loader](https://github.com/webpack-contrib/raw-loader).__
\* __NOTE: JSX does not natively preserve newlines in multiline text. In general, writing markdown directly in JSX is discouraged and it's a better idea to keep your content in separate .md files and require them, perhaps using webpack's [raw-loader](https://github.com/webpack-contrib/raw-loader).__

@@ -50,0 +52,0 @@ Override a particular HTML tag's output:

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc