doc.silverstripe.org/src/utils/parseHTML.ts

77 lines
3.3 KiB
TypeScript

import parse, { DomElement, domToReact } from 'html-react-parser';
import cleanChildrenTags from './cleanChildrenTags';
import cleanWhitespace from './cleanWhitespace';
import cleanApiTags from './cleanApiTags';
import rewriteLink from './rewriteLink';
import parseChildrenOf from './parseChildrenOf';
import { ReactElement } from 'react';
import rewriteTable from './rewriteTable';
import rewriteHeader from './rewriteHeader';
import cleanHeaders from './cleanHeaders';
import parseCalloutTags from './parseCalloutTags';
/**
* Replace all the [CHILDREN] and callout tags with proper React components.
* @param html
* @return ReactElement | ReactElement[] | string
*/
const parseHTML = (html: string): ReactElement | ReactElement[] | string => {
let cleanHTML = html;
cleanHTML = cleanChildrenTags(cleanHTML);
cleanHTML = cleanWhitespace(cleanHTML);
cleanHTML = cleanApiTags(cleanHTML);
cleanHTML = cleanHeaders(cleanHTML);
const parseOptions = {
replace(domNode: DomElement): ReactElement | object | undefined | false {
const { name, attribs, children } = domNode;
const domChildren = children || [];
if (name && attribs) {
if (name === 'a') {
return rewriteLink(attribs, domChildren, parseOptions, domNode);
}
if (name === 'table') {
return rewriteTable(domChildren, parseOptions);
}
if (name.match(/^h[0-9]$/)) {
return rewriteHeader(domNode);
}
if (name === 'blockquote') {
for (const child of children) {
// For some reason blockquotes start with an empty new line with this parser.
if (child.type === 'text' && child.data === "\n") {
continue;
}
// If the first relevant child isn't a paragraph or is empty, it's not a callout block.
if (child.type !== 'tag' || child.name !== 'p' || !child.children?.length || child.children[0].type !== 'text') {
break;
}
// Check if the first text node marks this as a callout block
const firstTextNode = child.children[0];
const calloutTypeRegex = /^\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\n/;
const matches = firstTextNode.data.match(calloutTypeRegex);
if (!matches) {
break;
}
// Remove the type marker and render the component
firstTextNode.data = firstTextNode.data.replace(calloutTypeRegex, '');
return parseCalloutTags(matches[1], domToReact(children));
}
}
}
if (domNode.data && domNode.parent?.name !== 'code') {
const { data } = domNode;
if (data.match(/\[CHILDREN.*?\]/)) {
return parseChildrenOf(data);
}
}
return false;
}
};
const component = parse(cleanHTML, parseOptions);
return component;
};
export default parseHTML;