@readme/markdown
Version:
ReadMe's React-based Markdown parser
97 lines (82 loc) • 3 kB
JSX
const React = require('react');
const PropTypes = require('prop-types');
const copy = require('copy-to-clipboard');
// Only load CodeMirror in the browser, for SSR
// apps. Necessary because of people like this:
// https://github.com/codemirror/CodeMirror/issues/3701#issuecomment-164904534
let syntaxHighlighter;
let canonicalLanguage = () => {};
if (typeof window !== 'undefined') {
// eslint-disable-next-line global-require
syntaxHighlighter = require('@readme/syntax-highlighter').default;
// eslint-disable-next-line global-require
({ canonical: canonicalLanguage } = require('@readme/syntax-highlighter'));
}
function CopyCode({ codeRef, rootClass = 'rdmd-code-copy', className = '' }) {
const copyClass = `${rootClass}_copied`;
const button = React.createRef();
/* istanbul ignore next */
const copier = () => {
const code = codeRef.current.textContent;
if (copy(code)) {
const $el = button.current;
$el.classList.add(copyClass);
setTimeout(() => $el.classList.remove(copyClass), 1500);
}
};
return <button ref={button} aria-label="Copy Code" className={`${rootClass} ${className}`} onClick={copier} />;
}
CopyCode.propTypes = {
className: PropTypes.string,
codeRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.instanceOf(React.Element) })])
.isRequired,
rootClass: PropTypes.string,
};
function Code(props) {
const { children, className, copyButtons, lang, meta, theme } = props;
const langClass = className.search(/lang(?:uage)?-\w+/) >= 0 ? className.match(/\s?lang(?:uage)?-(\w+)/)[1] : '';
const language = canonicalLanguage(lang) || langClass;
const codeRef = React.createRef();
const codeOpts = {
inline: !lang,
tokenizeVariables: true,
dark: theme === 'dark',
};
const codeContent =
syntaxHighlighter && children ? syntaxHighlighter(children[0], language, codeOpts) : children?.[0] || '';
return (
<React.Fragment>
<code
ref={codeRef}
className={['rdmd-code', `lang-${language}`, `theme-${theme}`].join(' ')}
data-lang={language}
name={meta}
suppressHydrationWarning={true}
>
{copyButtons && <CopyCode className="fa" codeRef={codeRef} />}
{codeContent}
</code>
</React.Fragment>
);
}
function CreateCode(sanitizeSchema, { copyButtons, theme }) {
// This is for code blocks class name
sanitizeSchema.attributes.code = ['className', 'lang', 'meta', 'value'];
// eslint-disable-next-line react/display-name
return props => <Code {...props} copyButtons={copyButtons} theme={theme} />;
}
Code.propTypes = {
children: PropTypes.arrayOf(PropTypes.string),
className: PropTypes.string,
copyButtons: PropTypes.bool,
lang: PropTypes.string,
meta: PropTypes.string,
theme: PropTypes.string,
};
Code.defaultProps = {
className: '',
copyButtons: true,
lang: '',
meta: '',
};
module.exports = (sanitizeSchema, opts) => CreateCode(sanitizeSchema, opts);