Hardened rich text conversion logic to handle malformed Tiptap documents and added null checks for CMS data in About section.
93 lines
2.3 KiB
TypeScript
93 lines
2.3 KiB
TypeScript
import sanitizeHtml from "sanitize-html";
|
|
import type { JSONContent } from "@tiptap/react";
|
|
import { generateHTML } from "@tiptap/html";
|
|
import StarterKit from "@tiptap/starter-kit";
|
|
import Underline from "@tiptap/extension-underline";
|
|
import Link from "@tiptap/extension-link";
|
|
import { TextStyle } from "@tiptap/extension-text-style";
|
|
import Color from "@tiptap/extension-color";
|
|
import Highlight from "@tiptap/extension-highlight";
|
|
import { FontFamily } from "@/lib/tiptap/fontFamily";
|
|
|
|
export function richTextToSafeHtml(doc: JSONContent): string {
|
|
if (!doc || typeof doc !== "object" || Object.keys(doc).length === 0) {
|
|
return "";
|
|
}
|
|
|
|
// Ensure type is present to satisfy Tiptap requirement
|
|
const typedDoc = { ...doc };
|
|
if (!typedDoc.type) {
|
|
typedDoc.type = "doc";
|
|
}
|
|
|
|
// Ensure content is an array
|
|
if (!typedDoc.content) {
|
|
typedDoc.content = [];
|
|
}
|
|
|
|
try {
|
|
const raw = generateHTML(typedDoc, [
|
|
StarterKit,
|
|
Underline,
|
|
Link.configure({
|
|
openOnClick: false,
|
|
autolink: false,
|
|
HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" },
|
|
}),
|
|
TextStyle,
|
|
FontFamily,
|
|
Color,
|
|
Highlight,
|
|
]);
|
|
|
|
return sanitizeHtml(raw, {
|
|
allowedTags: [
|
|
"p",
|
|
"br",
|
|
"h1",
|
|
"h2",
|
|
"h3",
|
|
"blockquote",
|
|
"strong",
|
|
"em",
|
|
"u",
|
|
"a",
|
|
"ul",
|
|
"ol",
|
|
"li",
|
|
"code",
|
|
"pre",
|
|
"span"
|
|
],
|
|
allowedAttributes: {
|
|
a: ["href", "rel", "target"],
|
|
span: ["style"],
|
|
code: ["class"],
|
|
pre: ["class"],
|
|
p: ["class"],
|
|
h1: ["class"],
|
|
h2: ["class"],
|
|
h3: ["class"],
|
|
blockquote: ["class"],
|
|
ul: ["class"],
|
|
ol: ["class"],
|
|
li: ["class"]
|
|
},
|
|
allowedSchemes: ["http", "https", "mailto"],
|
|
allowProtocolRelative: false,
|
|
allowedStyles: {
|
|
span: {
|
|
color: [/^#[0-9a-fA-F]{3,8}$/],
|
|
"background-color": [/^#[0-9a-fA-F]{3,8}$/],
|
|
"font-family": [/^(Inter|ui-sans-serif|ui-serif|ui-monospace)$/],
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
if (process.env.NODE_ENV === "development") {
|
|
console.error("Error generating HTML from rich text:", error);
|
|
}
|
|
return "";
|
|
}
|
|
}
|