fixes #2176, text align was not being reflected in summary sections

This commit is contained in:
Amruth Pillai
2025-01-26 01:00:38 +01:00
parent d21983aab4
commit 27b60a4df9
18 changed files with 101 additions and 78 deletions

View File

@ -1,7 +1,7 @@
import { sanitize } from "@reactive-resume/utils";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import { Outlet } from "react-router"; import { Outlet } from "react-router";
import sanitizeHtml from "sanitize-html";
import webfontloader from "webfontloader"; import webfontloader from "webfontloader";
import { useArtboardStore } from "../store/artboard"; import { useArtboardStore } from "../store/artboard";
@ -64,7 +64,7 @@ export const ArtboardPage = () => {
<title>{name} | Reactive Resume</title> <title>{name} | Reactive Resume</title>
{metadata.css.visible && ( {metadata.css.visible && (
<style id="custom-css" lang="css"> <style id="custom-css" lang="css">
{sanitizeHtml(metadata.css.value)} {sanitize(metadata.css.value)}
</style> </style>
)} )}
</Helmet> </Helmet>

View File

@ -12,27 +12,21 @@ export const Providers = () => {
useEffect(() => { useEffect(() => {
const handleMessage = (event: MessageEvent) => { const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return; if (event.origin !== window.location.origin) return;
if (event.data.type === "SET_RESUME") setResume(event.data.payload); if (event.data.type === "SET_RESUME") setResume(event.data.payload);
if (event.data.type === "SET_THEME") {
event.data.payload === "dark"
? document.documentElement.classList.add("dark")
: document.documentElement.classList.remove("dark");
}
}; };
const resumeData = window.localStorage.getItem("resume"); window.addEventListener("message", handleMessage, false);
if (resumeData) {
setResume(JSON.parse(resumeData));
return;
}
window.addEventListener("message", handleMessage);
return () => { return () => {
window.removeEventListener("message", handleMessage); window.removeEventListener("message", handleMessage, false);
}; };
}, [setResume]); }, []);
useEffect(() => {
const resumeData = window.localStorage.getItem("resume");
if (resumeData) setResume(JSON.parse(resumeData));
}, [window.localStorage.getItem("resume")]);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!resume) return null; if (!resume) return null;

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, linearTransform } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, linearTransform, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -99,7 +98,7 @@ const Summary = () => {
<div className="absolute left-[-4.5px] top-[8px] hidden size-[8px] rounded-full bg-primary group-[.main]:block" /> <div className="absolute left-[-4.5px] top-[8px] hidden size-[8px] rounded-full bg-primary group-[.main]:block" />
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -226,7 +225,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -90,7 +89,7 @@ const Summary = () => {
</div> </div>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg col-span-4" className="wysiwyg col-span-4"
/> />
@ -207,7 +206,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -90,7 +89,7 @@ const Summary = () => {
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4> <h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -210,7 +209,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg group-[.sidebar]:prose-invert" className="wysiwyg group-[.sidebar]:prose-invert"
/> />
)} )}

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -110,7 +109,7 @@ const Summary = () => {
<h4 className="mb-2 text-base font-bold">{section.name}</h4> <h4 className="mb-2 text-base font-bold">{section.name}</h4>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -232,7 +231,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -90,7 +89,7 @@ const Summary = () => {
<div className="p-custom space-y-4" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}> <div className="p-custom space-y-4" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
<section id={section.id}> <section id={section.id}>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -212,7 +211,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -15,10 +15,16 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl, linearTransform } from "@reactive-resume/utils"; import {
cn,
hexToRgb,
isEmptyString,
isUrl,
linearTransform,
sanitize,
} from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -90,7 +96,7 @@ const Summary = () => {
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4> <h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -215,7 +221,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -14,10 +14,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -109,7 +108,7 @@ const Summary = () => {
</h4> </h4>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -223,7 +222,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -14,10 +14,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -43,7 +42,7 @@ const Header = () => {
</div> </div>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -218,7 +217,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -106,7 +105,7 @@ const Summary = () => {
</div> </div>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -219,7 +218,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}
@ -257,7 +256,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -14,10 +14,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -110,7 +109,7 @@ const Summary = () => {
<h4 className="font-bold text-primary">{section.name}</h4> <h4 className="font-bold text-primary">{section.name}</h4>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -225,7 +224,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -111,7 +110,7 @@ const Summary = () => {
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4> <h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -240,7 +239,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -15,10 +15,9 @@ import type {
URL, URL,
} from "@reactive-resume/schema"; } from "@reactive-resume/schema";
import { Education, Experience, Volunteer } from "@reactive-resume/schema"; import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils"; import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get"; import get from "lodash.get";
import { Fragment } from "react"; import { Fragment } from "react";
import sanitizeHtml from "sanitize-html";
import { BrandIcon } from "../components/brand-icon"; import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture"; import { Picture } from "../components/picture";
@ -91,7 +90,7 @@ const Summary = () => {
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4> <h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(section.content) }} dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }} style={{ columns: section.columns }}
className="wysiwyg" className="wysiwyg"
/> />
@ -206,7 +205,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && ( {summary !== undefined && !isEmptyString(summary) && (
<div <div
dangerouslySetInnerHTML={{ __html: sanitizeHtml(summary) }} dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg" className="wysiwyg"
/> />
)} )}

View File

@ -17,25 +17,41 @@ export const BuilderPage = () => {
const resume = useResumeStore((state) => state.resume); const resume = useResumeStore((state) => state.resume);
const title = useResumeStore((state) => state.resume.title); const title = useResumeStore((state) => state.resume.title);
const updateResumeInFrame = useCallback(() => { const syncResumeToArtboard = useCallback(() => {
const message = { type: "SET_RESUME", payload: resume.data };
setImmediate(() => { setImmediate(() => {
frameRef?.contentWindow?.postMessage(message, "*"); if (!frameRef?.contentWindow) return;
const message = { type: "SET_RESUME", payload: resume.data };
frameRef.contentWindow.postMessage(message, "*");
}); });
}, [frameRef?.contentWindow, resume.data]); }, [frameRef?.contentWindow, resume.data]);
// Send resume data to iframe on initial load // Send resume data to iframe on initial load
useEffect(() => { useEffect(() => {
if (!frameRef) return; if (!frameRef) return;
frameRef.addEventListener("load", updateResumeInFrame);
frameRef.addEventListener("load", syncResumeToArtboard);
return () => { return () => {
frameRef.removeEventListener("load", updateResumeInFrame); frameRef.removeEventListener("load", syncResumeToArtboard);
};
}, [frameRef]);
// Persistently check if iframe has loaded using setInterval
useEffect(() => {
const interval = setInterval(() => {
if (frameRef?.contentWindow?.document.readyState === "complete") {
syncResumeToArtboard();
clearInterval(interval);
}
}, 100);
return () => {
clearInterval(interval);
}; };
}, [frameRef]); }, [frameRef]);
// Send resume data to iframe on change of resume data // Send resume data to iframe on change of resume data
useEffect(updateResumeInFrame, [resume.data]); useEffect(syncResumeToArtboard, [resume.data]);
return ( return (
<> <>

View File

@ -43,6 +43,8 @@ export const TypographySection = () => {
const loadFontSuggestions = useCallback(() => { const loadFontSuggestions = useCallback(() => {
for (const font of fontSuggestions) { for (const font of fontSuggestions) {
if (localFonts.includes(font)) continue;
webfontloader.load({ webfontloader.load({
events: false, events: false,
classes: false, classes: false,

View File

@ -9,12 +9,13 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"papaparse": "^5.4.1",
"dayjs": "^1.11.11",
"unique-names-generator": "^4.7.1",
"clsx": "^2.1.1",
"tailwind-merge": "^2.3.0",
"@swc/helpers": "~0.5.11", "@swc/helpers": "~0.5.11",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"papaparse": "^5.4.1",
"sanitize-html": "^2.14.0",
"tailwind-merge": "^2.3.0",
"unique-names-generator": "^4.7.1",
"zod": "^3.24.1" "zod": "^3.24.1"
} }
} }

View File

@ -1,3 +1,4 @@
import sanitizeHtml from "sanitize-html";
import type { Config as UniqueNamesConfig } from "unique-names-generator"; import type { Config as UniqueNamesConfig } from "unique-names-generator";
import { adjectives, animals, uniqueNamesGenerator } from "unique-names-generator"; import { adjectives, animals, uniqueNamesGenerator } from "unique-names-generator";
@ -55,3 +56,17 @@ export const parseLayoutLocator = (payload: SortablePayload | null): LayoutLocat
return { page, column, section }; return { page, column, section };
}; };
export const sanitize = (html: string, options?: sanitizeHtml.IOptions) => {
return sanitizeHtml(html, {
...options,
allowedAttributes: {
...options?.allowedAttributes,
"*": ["class", "style"],
},
allowedStyles: {
...options?.allowedStyles,
"*": { "text-align": [/^left$/, /^right$/, /^center$/, /^justify$/] },
},
});
};