UNPKG

react-markdown

Version:
270 lines (219 loc) 7.37 kB
'use strict'; var React = require('react'); var xtend = require('xtend'); var ReactIs = require('react-is'); var defaultNodePosition = { start: { line: 1, column: 1, offset: 0 }, end: { line: 1, column: 1, offset: 0 } }; function astToReact(node, options) { var parent = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var renderer = options.renderers[node.type]; // nodes generated by plugins may not have position data // much of the code after this point will attempt to access properties of the node.position // this will set the node position to the parent node's position to prevent errors if (node.position === undefined) { node.position = parent.node && parent.node.position || defaultNodePosition; } var pos = node.position.start; var key = [node.type, pos.line, pos.column, index].join('-'); if (!ReactIs.isValidElementType(renderer)) { throw new Error("Renderer for type `".concat(node.type, "` not defined or is not renderable")); } var nodeProps = getNodeProps(node, key, options, renderer, parent, index); return React.createElement(renderer, nodeProps, nodeProps.children || resolveChildren() || undefined); function resolveChildren() { return node.children && node.children.map(function (childNode, i) { return astToReact(childNode, options, { node: node, props: nodeProps }, i); }); } } // eslint-disable-next-line max-params, complexity function getNodeProps(node, key, opts, renderer, parent, index) { var props = { key: key }; var isTagRenderer = typeof renderer === 'string'; // `sourcePos` is true if the user wants source information (line/column info from markdown source) if (opts.sourcePos && node.position) { props['data-sourcepos'] = flattenPosition(node.position); } if (opts.rawSourcePos && !isTagRenderer) { props.sourcePosition = node.position; } // If `includeNodeIndex` is true, pass node index info to all non-tag renderers if (opts.includeNodeIndex && parent.node && parent.node.children && !isTagRenderer) { props.index = parent.node.children.indexOf(node); props.parentChildCount = parent.node.children.length; } var ref = node.identifier !== null && node.identifier !== undefined ? opts.definitions[node.identifier] || {} : null; switch (node.type) { case 'root': assignDefined(props, { className: opts.className }); break; case 'text': props.nodeKey = key; props.children = node.value; break; case 'heading': props.level = node.depth; break; case 'list': props.start = node.start; props.ordered = node.ordered; props.tight = !node.loose; props.depth = node.depth; break; case 'listItem': props.checked = node.checked; props.tight = !node.loose; props.ordered = node.ordered; props.index = node.index; props.children = getListItemChildren(node, parent).map(function (childNode, i) { return astToReact(childNode, opts, { node: node, props: props }, i); }); break; case 'definition': assignDefined(props, { identifier: node.identifier, title: node.title, url: node.url }); break; case 'code': assignDefined(props, { language: node.lang && node.lang.split(/\s/, 1)[0] }); break; case 'inlineCode': props.children = node.value; props.inline = true; break; case 'link': assignDefined(props, { title: node.title || undefined, target: typeof opts.linkTarget === 'function' ? opts.linkTarget(node.url, node.children, node.title) : opts.linkTarget, href: opts.transformLinkUri ? opts.transformLinkUri(node.url, node.children, node.title) : node.url }); break; case 'image': assignDefined(props, { alt: node.alt || undefined, title: node.title || undefined, src: opts.transformImageUri ? opts.transformImageUri(node.url, node.children, node.title, node.alt) : node.url }); break; case 'linkReference': assignDefined(props, xtend(ref, { href: opts.transformLinkUri ? opts.transformLinkUri(ref.href) : ref.href })); break; case 'imageReference': assignDefined(props, { src: opts.transformImageUri && ref.href ? opts.transformImageUri(ref.href, node.children, ref.title, node.alt) : ref.href, title: ref.title || undefined, alt: node.alt || undefined }); break; case 'table': case 'tableHead': case 'tableBody': props.columnAlignment = node.align; break; case 'tableRow': props.isHeader = parent.node.type === 'tableHead'; props.columnAlignment = parent.props.columnAlignment; break; case 'tableCell': assignDefined(props, { isHeader: parent.props.isHeader, align: parent.props.columnAlignment[index] }); break; case 'virtualHtml': props.tag = node.tag; break; case 'html': // @todo find a better way than this props.isBlock = node.position.start.line !== node.position.end.line; props.escapeHtml = opts.escapeHtml; props.skipHtml = opts.skipHtml; break; case 'parsedHtml': { var parsedChildren; if (node.children) { parsedChildren = node.children.map(function (child, i) { return astToReact(child, opts, { node: node, props: props }, i); }); } props.escapeHtml = opts.escapeHtml; props.skipHtml = opts.skipHtml; props.element = mergeNodeChildren(node, parsedChildren); break; } default: assignDefined(props, xtend(node, { type: undefined, position: undefined, children: undefined })); } if (!isTagRenderer && node.value) { props.value = node.value; } return props; } function assignDefined(target, attrs) { for (var key in attrs) { if (typeof attrs[key] !== 'undefined') { target[key] = attrs[key]; } } } function mergeNodeChildren(node, parsedChildren) { var el = node.element; if (Array.isArray(el)) { var Fragment = React.Fragment || 'div'; return React.createElement(Fragment, null, el); } if (el.props.children || parsedChildren) { var children = React.Children.toArray(el.props.children).concat(parsedChildren); return React.cloneElement(el, null, children); } return React.cloneElement(el, null); } function flattenPosition(pos) { return [pos.start.line, ':', pos.start.column, '-', pos.end.line, ':', pos.end.column].map(String).join(''); } function getListItemChildren(node, parent) { if (node.loose) { return node.children; } if (parent.node && node.index > 0 && parent.node.children[node.index - 1].loose) { return node.children; } return unwrapParagraphs(node); } function unwrapParagraphs(node) { return node.children.reduce(function (array, child) { return array.concat(child.type === 'paragraph' ? child.children || [] : [child]); }, []); } module.exports = astToReact;