All files / Rindu/components/SafeHTML SafeHTML.tsx

78.57% Statements 11/14
55.55% Branches 5/9
66.66% Functions 2/3
78.57% Lines 11/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 5219x   19x               13x           35x   35x   24x   24x   24x           24x             24x     35x                      
import { PropsWithChildren, ReactElement, useEffect, useState } from "react";
 
import DOMPurify from "isomorphic-dompurify";
 
interface Props {
  className?: string;
  allowedTags?: string[];
  allowedAttributes?: string[];
}
 
export default function SafeHTML({
  children,
  className = "",
  allowedTags = ["p", "br", "strong", "em", "a", "span"],
  allowedAttributes = ["href", "target", "rel"],
}: PropsWithChildren<Props>): ReactElement | null {
  const [sanitizedContent, setSanitizedContent] = useState("");
 
  useEffect(() => {
    const spotifyUrlRegex =
      /^(?:(?:(?:f|ht)tps?|spotify):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i;
 
    const htmlString = children?.toString() ?? "";
 
    DOMPurify.addHook("afterSanitizeAttributes", (node) => {
      Iif (node instanceof Element && node.tagName === "A") {
        node.setAttribute("target", "_blank");
        node.setAttribute("rel", "noopener noreferrer");
      }
    });
    const clean = DOMPurify.sanitize(htmlString, {
      ALLOWED_URI_REGEXP: spotifyUrlRegex,
      ALLOWED_TAGS: allowedTags,
      ALLOWED_ATTR: allowedAttributes,
      ADD_ATTR: ["target:_blank", "rel:noopener noreferrer"],
    });
 
    setSanitizedContent(clean);
  }, [allowedAttributes, allowedTags, children]);
 
  if (!sanitizedContent) {
    return <span className={className} />;
  }
 
  return (
    <span
      className={className}
      dangerouslySetInnerHTML={{ __html: sanitizedContent }}
    />
  );
}