terriajs
Version:
Geospatial data visualization platform.
104 lines • 4.52 kB
JavaScript
import DOMPurify from "dompurify";
import { createElement } from "react";
import * as React from "react";
import combine from "terriajs-cesium/Source/Core/combine";
import defined from "terriajs-cesium/Source/Core/defined";
import CustomComponent from "./CustomComponent";
import { ExternalLinkIcon, ExternalLinkWithWarning } from "./ExternalLink";
import { Parser, ProcessNodeDefinitions } from "html-to-react";
import { createElement as htmlCreateElement } from "html-to-react/lib/utils";
const htmlToReactParser = Parser({
decodeEntities: true
});
const processNodeDefinitions = ProcessNodeDefinitions();
const isValidNode = function () {
return true;
};
const shouldProcessEveryNodeExceptWhiteSpace = function (node) {
// Use this to avoid white space between table elements, eg.
// <table> <tbody> <tr>\n<td>x</td> <td>3</td> </tr> </tbody> </table>
// being rendered as empty <span> elements, and causing React errors.
return node.type !== "text" || node.data.trim();
};
let keyIndex = 0;
function shouldAppendExternalLinkIcon(url, context) {
if (!url)
return false;
const tmp = document.createElement("a");
tmp.href = url;
const isExternalLink = tmp.host !== window.location.host;
return context.disableExternalLinkIcon !== true && isExternalLink;
}
/**
* @private
*/
function getProcessingInstructions(context) {
// Process custom nodes specially.
const processingInstructions = [];
const customComponents = CustomComponent.values;
for (let i = 0; i < customComponents.length; i++) {
const customComponent = customComponents[i];
processingInstructions.push({
shouldProcessNode: customComponent.shouldProcessNode.bind(customComponent, context),
processNode: customComponent.processNode.bind(customComponent, context)
});
}
/** Process anchor elements:
* - Make sure any <a href> tags open in a new window
* - Add ExternalLinkIcon
* - Replace anchor with ExternalLinkWithWarning if `context.showExternalLinkWarning`
*/
processingInstructions.push({
shouldProcessNode: (node) => node.name === "a",
processNode: function (node, children, index) {
// Make sure any <a href> tags open in a new window
const elementProps = {
key: "anchor-" + keyIndex++,
target: "_blank",
rel: "noreferrer noopener"
};
node.attribs = combine(node.attribs, elementProps);
// If applicable - append ExternalLinkIcon
const appendExternalLink = shouldAppendExternalLinkIcon(node?.attribs?.href, context);
if (appendExternalLink) {
const externalIcon = React.createElement(ExternalLinkIcon, {});
children.push(externalIcon);
}
// Create new Anchor element
const aElement = htmlCreateElement(node, index, node.data, children);
// If external link and showExternalLinkWarning is true - replace with ExternalLinkWithWarning
if (appendExternalLink && context.showExternalLinkWarning) {
/* TODO: Fix types */
/* eslint-disable-next-line react/no-children-prop */
return createElement(ExternalLinkWithWarning, {
attributes: aElement.props,
children: aElement.props.children
});
}
return aElement;
}
});
// Process all other nodes as normal.
processingInstructions.push({
shouldProcessNode: shouldProcessEveryNodeExceptWhiteSpace,
processNode: processNodeDefinitions.processDefaultNode
});
return processingInstructions;
}
/**
* Return html as a React Element.
* HTML is purified by default. Custom components are not supported by default
* Set domPurifyOptions to specify supported custom components - for example
* - eg. {ADD_TAGS: ['component1', 'component2']} (https://github.com/cure53/DOMPurify).
*/
function parseCustomHtmlToReact(html, context, allowUnsafeHtml = false, domPurifyOptions = {}) {
if (!defined(html) || html.length === 0) {
return html;
}
if (!allowUnsafeHtml) {
html = DOMPurify.sanitize(html, domPurifyOptions);
}
return htmlToReactParser.parseWithInstructions(html, isValidNode, getProcessingInstructions(context ?? {}));
}
export default parseCustomHtmlToReact;
//# sourceMappingURL=parseCustomHtmlToReact.js.map