@dfinity/gix-components
Version:
A UI kit developed by the GIX team
72 lines (71 loc) • 3.29 kB
JavaScript
import { isNullish } from "@dfinity/utils";
export const targetBlankLinkRenderer = (href, title, text) => `<a${href === null || href === undefined
? ""
: ` target="_blank" rel="noopener noreferrer" href="${href}"`}${title === null || title === undefined ? "" : ` title="${title}"`}>${text.length === 0 ? (href ?? title) : text}</a>`;
/**
* Based on https://github.com/markedjs/marked/blob/master/src/Renderer.js#L186
* @returns <a> tag to image
*/
export const imageToLinkRenderer = (src, title, alt) => {
if (src === undefined || src === null || src?.length === 0) {
return alt;
}
const fileExtention = src.includes(".")
? src.split(".").pop()
: "";
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-type
const typeProp = fileExtention === "" ? undefined : ` type="image/${fileExtention}"`;
const titleDefined = title !== undefined && title !== null;
const titleProp = titleDefined ? ` title="${title}"` : undefined;
const text = alt === "" ? (titleDefined ? title : src) : alt;
return `<a href="${src}" target="_blank" rel="noopener noreferrer"${typeProp ?? ""}${titleProp ?? ""}>${text}</a>`;
};
const escapeHtml = (html) => html.replace(/</g, "<").replace(/>/g, ">");
const escapeSvgs = (html) => html.replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, escapeHtml);
/**
* Escape <img> tags or convert them to links
*/
const transformImg = (img) => {
const src = img.match(/src="([^"]+)"/)?.[1];
const alt = img.match(/alt="([^"]+)"/)?.[1] || "img";
const title = img.match(/title="([^"]+)"/)?.[1];
const shouldEscape = isNullish(src) || src.startsWith("data:image");
const imageHtml = shouldEscape
? escapeHtml(img)
: imageToLinkRenderer(src, title, alt);
return imageHtml;
};
/** Avoid <img> tags; instead, apply the same logic as for markdown images by either escaping them or converting them to links. */
export const htmlRenderer = (html) => /<img\s+[^>]*>/gi.test(html) ? transformImg(html) : html;
/**
* Marked.js renderer for proposal summary.
* Customized renderers
* - targetBlankLinkRenderer
* - imageToLinkRenderer
* - htmlRenderer
*
* @param marked
*/
const proposalSummaryRenderer = (marked) => {
const renderer = new marked.Renderer();
renderer.link = targetBlankLinkRenderer;
renderer.image = imageToLinkRenderer;
renderer.html = htmlRenderer;
return renderer;
};
/**
* Uses markedjs.
* Escape or transform to links some raw HTML tags (img, svg)
* @see {@link https://github.com/markedjs/marked}
*/
export const markdownToHTML = async (text) => {
// Replace the SVG elements in the HTML with their escaped versions to improve security.
// It's not possible to do it with html renderer because the svg consists of multiple tags.
// One edge case is not covered: if the svg is inside the <code> tag, it will be rendered as with < & > instead of "<" & ">"
const escapedText = escapeSvgs(text);
// The dynamic import cannot be analyzed by Vite. As it is intended, we use the /* @vite-ignore */ comment inside the import() call to suppress this warning.
const { marked } = await import("marked");
return marked(escapedText, {
renderer: proposalSummaryRenderer(marked),
});
};