fix: resolve rich text rendering and data mapping issues

Hardened rich text conversion logic to handle malformed Tiptap documents and added null checks for CMS data in About section.
This commit is contained in:
2026-02-16 01:01:27 +01:00
parent 18f8fb7407
commit 3cf1b9144d
2 changed files with 80 additions and 59 deletions

View File

@@ -118,8 +118,8 @@ const About = () => {
description="Tools & Technologies" description="Tools & Technologies"
header={ header={
<div className="flex flex-wrap gap-2 p-2 overflow-y-auto max-h-40 scrollbar-hide"> <div className="flex flex-wrap gap-2 p-2 overflow-y-auto max-h-40 scrollbar-hide">
{techStack.length > 0 ? ( {techStack && techStack.length > 0 ? (
techStack.flatMap(cat => cat.items.map((item: any) => ( techStack.flatMap(cat => cat.items?.map((item: any) => (
<motion.span <motion.span
key={item.id} key={item.id}
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
@@ -184,7 +184,7 @@ const About = () => {
description="Beyond the screen" description="Beyond the screen"
header={ header={
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mt-2"> <div className="grid grid-cols-2 md:grid-cols-4 gap-3 mt-2">
{hobbies.length > 0 ? hobbies.map((hobby, i) => { {hobbies && hobbies.length > 0 ? hobbies.map((hobby, i) => {
const Icon = iconMap[hobby.icon] || Lightbulb; const Icon = iconMap[hobby.icon] || Lightbulb;
return ( return (
<motion.div <motion.div

View File

@@ -10,62 +10,83 @@ import Highlight from "@tiptap/extension-highlight";
import { FontFamily } from "@/lib/tiptap/fontFamily"; import { FontFamily } from "@/lib/tiptap/fontFamily";
export function richTextToSafeHtml(doc: JSONContent): string { export function richTextToSafeHtml(doc: JSONContent): string {
const raw = generateHTML(doc, [ if (!doc || typeof doc !== "object" || Object.keys(doc).length === 0) {
StarterKit, return "";
Underline, }
Link.configure({
openOnClick: false,
autolink: false,
HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" },
}),
TextStyle,
FontFamily,
Color,
Highlight,
]);
return sanitizeHtml(raw, { // Ensure type is present to satisfy Tiptap requirement
allowedTags: [ const typedDoc = { ...doc };
"p", if (!typedDoc.type) {
"br", typedDoc.type = "doc";
"h1", }
"h2",
"h3", // Ensure content is an array
"blockquote", if (!typedDoc.content) {
"strong", typedDoc.content = [];
"em", }
"u",
"a", try {
"ul", const raw = generateHTML(typedDoc, [
"ol", StarterKit,
"li", Underline,
"code", Link.configure({
"pre", openOnClick: false,
"span" autolink: false,
], HTMLAttributes: { rel: "noopener noreferrer", target: "_blank" },
allowedAttributes: { }),
a: ["href", "rel", "target"], TextStyle,
span: ["style"], FontFamily,
code: ["class"], Color,
pre: ["class"], Highlight,
p: ["class"], ]);
h1: ["class"],
h2: ["class"], return sanitizeHtml(raw, {
h3: ["class"], allowedTags: [
blockquote: ["class"], "p",
ul: ["class"], "br",
ol: ["class"], "h1",
li: ["class"] "h2",
}, "h3",
allowedSchemes: ["http", "https", "mailto"], "blockquote",
allowProtocolRelative: false, "strong",
allowedStyles: { "em",
span: { "u",
color: [/^#[0-9a-fA-F]{3,8}$/], "a",
"background-color": [/^#[0-9a-fA-F]{3,8}$/], "ul",
"font-family": [/^(Inter|ui-sans-serif|ui-serif|ui-monospace)$/], "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 "";
}
} }