import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import ReactDOM from 'react-dom'; import classnames from 'classnames'; import autobind from 'autobind-decorator'; import highlight from 'highlight.js'; import * as misc from '../../common/misc'; import {markdownToHTML} from '../../common/markdown-to-html'; @autobind class MarkdownPreview extends PureComponent { constructor (props) { super(props); this.state = { compiled: '', renderError: '' }; } /** * Debounce and compile the markdown (won't debounce first render) */ _compileMarkdown (markdown) { clearTimeout(this._compileTimeout); this._compileTimeout = setTimeout(async () => { try { const rendered = await this.props.handleRender(markdown); this.setState({ compiled: markdownToHTML(rendered), renderError: '' }); } catch (err) { this.setState({ renderError: err.message, compiled: '' }); } }, this.state.compiled ? this.props.debounceMillis : 0); } _setPreviewRef (n) { this._preview = n; } _handleClickLink (e) { e.preventDefault(); misc.clickLink(e.target.getAttribute('href')); } _highlightCodeBlocks () { if (!this._preview) { return; } const el = ReactDOM.findDOMNode(this._preview); for (const block of el.querySelectorAll('pre > code')) { highlight.highlightBlock(block); } for (const a of el.querySelectorAll('a')) { a.title = `Open ${a.getAttribute('href')} in browser`; a.removeEventListener('click', this._handleClickLink); a.addEventListener('click', this._handleClickLink); } } componentWillUnmount () { clearTimeout(this._compileTimeout); } componentWillMount () { this._compileMarkdown(this.props.markdown); } componentWillReceiveProps (nextProps) { this._compileMarkdown(nextProps.markdown); } componentDidUpdate () { this._highlightCodeBlocks(); } componentDidMount () { this._highlightCodeBlocks(); } render () { const {className, heading} = this.props; const {compiled, renderError} = this.state; let html = heading ? `

${heading}

\n${compiled}` : compiled; return (
{renderError && (

Failed to render: {renderError}

)}
{/* Set from above */}
); } } MarkdownPreview.propTypes = { // Required markdown: PropTypes.string.isRequired, handleRender: PropTypes.func.isRequired, // Optional className: PropTypes.string, debounceMillis: PropTypes.number, heading: PropTypes.string }; export default MarkdownPreview;