react-markdown
Version:
Render Markdown as React components
164 lines (147 loc) • 5.06 kB
JavaScript
'use strict'
const React = require('react')
const unified = require('unified')
const parse = require('remark-parse')
const remarkRehype = require('remark-rehype')
const PropTypes = require('prop-types')
// @ts-ignore remove when typed
const html = require('property-information/html')
const filter = require('./rehype-filter')
const uriTransformer = require('./uri-transformer')
const childrenToReact = require('./ast-to-react.js').hastChildrenToReact
/**
* @typedef {import('react').ReactNode} ReactNode
* @typedef {import('react').ReactElement<{}>} ReactElement
* @typedef {import('unified').PluggableList} PluggableList
* @typedef {import('hast').Root} Root
* @typedef {import('./rehype-filter.js').RehypeFilterOptions} FilterOptions
* @typedef {import('./ast-to-react.js').TransformOptions} TransformOptions
*
* @typedef {Object} CoreOptions
* @property {string} children
*
* @typedef {Object} PluginOptions
* @property {PluggableList} [plugins=[]] **deprecated**: use `remarkPlugins` instead
* @property {PluggableList} [remarkPlugins=[]]
* @property {PluggableList} [rehypePlugins=[]]
*
* @typedef {Object} LayoutOptions
* @property {string} [className]
*
* @typedef {CoreOptions & PluginOptions & LayoutOptions & FilterOptions & TransformOptions} ReactMarkdownOptions
*/
module.exports = ReactMarkdown
const own = {}.hasOwnProperty
const changelog =
'https://github.com/remarkjs/react-markdown/blob/main/changelog.md'
/**
* @typedef {Object} Deprecation
* @property {string} id
* @property {string} [to]
*/
/**
* @type {Object.<string, Deprecation>}
*/
const deprecated = {
renderers: {to: 'components', id: 'change-renderers-to-components'},
astPlugins: {id: 'remove-buggy-html-in-markdown-parser'},
allowDangerousHtml: {id: 'remove-buggy-html-in-markdown-parser'},
escapeHtml: {id: 'remove-buggy-html-in-markdown-parser'},
source: {to: 'children', id: 'change-source-to-children'},
allowNode: {
to: 'allowElement',
id: 'replace-allownode-allowedtypes-and-disallowedtypes'
},
allowedTypes: {
to: 'allowedElements',
id: 'replace-allownode-allowedtypes-and-disallowedtypes'
},
disallowedTypes: {
to: 'disallowedElements',
id: 'replace-allownode-allowedtypes-and-disallowedtypes'
},
includeNodeIndex: {
to: 'includeElementIndex',
id: 'change-includenodeindex-to-includeelementindex'
}
}
/**
* @param {ReactMarkdownOptions} options
* @returns {ReactElement}
*/
function ReactMarkdown(options) {
for (const key in deprecated) {
if (own.call(deprecated, key) && own.call(options, key)) {
/** @type {Deprecation} */
const deprecation = deprecated[key]
console.warn(
`[react-markdown] Warning: please ${
deprecation.to ? `use \`${deprecation.to}\` instead of` : 'remove'
} \`${key}\` (see <${changelog}#${deprecation.id}> for more info)`
)
delete deprecated[key]
}
}
const processor = unified()
.use(parse)
// TODO: deprecate `plugins` in v7.0.0.
.use(options.remarkPlugins || options.plugins || [])
.use(remarkRehype, {allowDangerousHtml: true})
.use(options.rehypePlugins || [])
.use(filter, options)
/** @type {Root} */
// @ts-ignore we’ll throw if it isn’t a root next.
const hastNode = processor.runSync(processor.parse(options.children || ''))
if (hastNode.type !== 'root') {
throw new TypeError('Expected a `root` node')
}
/** @type {ReactElement} */
let result = React.createElement(
React.Fragment,
{},
childrenToReact({options: options, schema: html, listDepth: 0}, hastNode)
)
if (options.className) {
result = React.createElement('div', {className: options.className}, result)
}
return result
}
ReactMarkdown.defaultProps = {transformLinkUri: uriTransformer}
ReactMarkdown.propTypes = {
// Core options:
children: PropTypes.string,
// Layout options:
className: PropTypes.string,
// Filter options:
allowElement: PropTypes.func,
allowedElements: PropTypes.arrayOf(PropTypes.string),
disallowedElements: PropTypes.arrayOf(PropTypes.string),
unwrapDisallowed: PropTypes.bool,
// Plugin options:
// type-coverage:ignore-next-line
remarkPlugins: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.object,
PropTypes.func,
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.func]))
])
),
// type-coverage:ignore-next-line
rehypePlugins: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.object,
PropTypes.func,
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.func]))
])
),
// Transform options:
sourcePos: PropTypes.bool,
rawSourcePos: PropTypes.bool,
skipHtml: PropTypes.bool,
includeElementIndex: PropTypes.bool,
transformLinkUri: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
linkTarget: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
transformImageUri: PropTypes.func,
components: PropTypes.object
}
ReactMarkdown.uriTransformer = uriTransformer