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:
@@ -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
|
||||||
|
|||||||
133
lib/richtext.ts
133
lib/richtext.ts
@@ -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 "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user