UNPKG

react-native-markdown-renderer

Version:

Markdown renderer for react-native, with CommonMark spec support + adds syntax extensions & sugar (URL autolinking, typographer).

216 lines (195 loc) 5.42 kB
/** * Base Markdown component * @author Mient-jan Stelling */ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { View } from 'react-native'; import parser from './lib/parser'; import applyStyle from './lib/util/applyStyle'; import getUniqueID from './lib/util/getUniqueID'; import hasParents from './lib/util/hasParents'; import openUrl from './lib/util/openUrl'; import tokensToAST from './lib/util/tokensToAST'; import renderRules from './lib/renderRules'; import AstRenderer from './lib/AstRenderer'; import MarkdownIt from 'markdown-it'; import PluginContainer from './lib/plugin/PluginContainer'; import blockPlugin from './lib/plugin/blockPlugin'; import { styles } from './lib/styles'; import { stringToTokens } from './lib/util/stringToTokens'; /** * */ export { applyStyle, getUniqueID, openUrl, hasParents, renderRules, AstRenderer, parser, stringToTokens, tokensToAST, MarkdownIt, PluginContainer, blockPlugin, styles, }; /** * react-native-markdown-renderer */ export default class Markdown extends Component { /** * Definition of the prop types */ static propTypes = { children: PropTypes.node.isRequired, renderer: PropTypes.oneOfType([PropTypes.func, PropTypes.instanceOf(AstRenderer)]), rules: (props, propName, componentName) => { let invalidProps = []; const prop = props[propName]; if (!prop) { return; } if (typeof prop === 'object') { invalidProps = Object.keys(prop).filter(key => typeof prop[key] !== 'function'); } if (typeof prop !== 'object') { return new Error( `Invalid prop \`${propName}\` supplied to \`${componentName}\`. Must be of shape {[index:string]:function} ` ); } else if (invalidProps.length > 0) { return new Error( `Invalid prop \`${propName}\` supplied to \`${componentName}\`. These ` + `props are not of type function \`${invalidProps.join(', ')}\` ` ); } }, markdownit: PropTypes.instanceOf(MarkdownIt), plugins: PropTypes.arrayOf(PropTypes.instanceOf(PluginContainer)), style: PropTypes.any, }; /** * Default Props */ static defaultProps = { renderer: null, rules: null, plugins: [], style: null, markdownit: MarkdownIt({ typographer: true, }), }; copy = ''; renderer = null; markdownParser = null; /** * Only when the copy changes will the markdown render again. * @param nextProps * @param nextState * @return {boolean} */ shouldComponentUpdate(nextProps, nextState) { const copy = this.getCopyFromChildren(nextProps.children); if (copy !== this.copy) { this.copy = copy; return true; } if ( nextProps.renderer !== this.props.renderer || nextProps.style !== this.props.style || nextProps.plugins !== this.props.plugins || nextProps.rules !== this.props.rules || nextProps.markdownit !== this.props.markdownit ) { return true; } return false; } /** * * @param props */ updateSettings(props = this.props) { const { renderer, rules, style, plugins, markdownit } = props; if (renderer && rules) { console.warn( 'react-native-markdown-renderer you are using renderer and rules at the same time. This is not possible, props.rules is ignored' ); } if (renderer && style) { console.warn( 'react-native-markdown-renderer you are using renderer and style at the same time. This is not possible, props.style is ignored' ); } // these checks are here to prevent extra overhead. if (renderer) { if (typeof renderer === 'function') { if (!this.renderer || this.renderer.render !== renderer) { this.renderer = { render: renderer, }; } } else if (renderer instanceof AstRenderer) { if (this.renderer !== renderer) { this.renderer = renderer; } } else { throw new Error('Provided renderer is not compatible with function or AstRenderer. please change'); } } else { if (!this.renderer || this.props.renderer || this.props.rules !== rules || this.props.style !== style) { this.renderer = new AstRenderer( { ...renderRules, ...(rules || {}), }, { ...styles, ...style, } ); } } if (!this.markdownParser || this.props.markdownit !== markdownit || plugins !== this.props.plugins) { let md = markdownit; if (plugins && plugins.length > 0) { plugins.forEach(plugin => { md = md.use.apply(md, plugin.toArray()); }); } this.markdownParser = md; } } /** * */ componentWillMount() { this.updateSettings(this.props); } /** * * @param nextProps */ componentWillReceiveProps(nextProps) { this.updateSettings(nextProps); } /** * * @param children * @return {string} */ getCopyFromChildren(children = this.props.children) { return children instanceof Array ? children.join('') : children; } /** * * @return {View} */ render() { const copy = (this.copy = this.getCopyFromChildren()); return parser(copy, this.renderer.render, this.markdownParser); } }