From fad1882117fa6cb9d5eb71265acd16dfbbe5b6aa Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Sun, 12 Jul 2026 11:49:10 -0700 Subject: [PATCH] refactor(builder): use shared escaper everywhere, drop 26 local copies Replace divergent, buggy local esc/escapeHtml helpers across 26 files with imports from src/utils/escape (escapeHtml/escapeAttr). Attribute call sites use escapeAttr, text-content sites use escapeHtml. Several toHtml outputs now correctly escape & where old local escapers omitted it. Co-Authored-By: Claude Opus 4.8 (1M context) --- craft/src/components/basic/Logo.tsx | 13 ++++-------- craft/src/components/basic/Menu.tsx | 9 ++------ craft/src/components/basic/Navbar.tsx | 21 +++++++------------ craft/src/components/basic/SearchBar.tsx | 6 +++--- craft/src/components/forms/ContactForm.tsx | 14 ++++++------- craft/src/components/forms/InputField.tsx | 6 +++--- craft/src/components/forms/SubscribeForm.tsx | 8 +++---- craft/src/components/forms/TextareaField.tsx | 6 +++--- .../components/layout/BackgroundSection.tsx | 4 ++-- craft/src/components/layout/ColumnLayout.tsx | 4 ++-- craft/src/components/layout/Container.tsx | 4 ++-- craft/src/components/layout/Section.tsx | 4 ++-- craft/src/components/sections/Accordion.tsx | 8 +++---- craft/src/components/sections/CTASection.tsx | 8 +++---- .../src/components/sections/CallToAction.tsx | 9 ++++---- .../src/components/sections/ContentSlider.tsx | 10 ++++----- craft/src/components/sections/Countdown.tsx | 6 +++--- .../src/components/sections/FeaturesGrid.tsx | 14 ++++++------- craft/src/components/sections/Gallery.tsx | 8 +++---- craft/src/components/sections/HeroSimple.tsx | 8 +++---- .../src/components/sections/NumberCounter.tsx | 6 +++--- .../src/components/sections/PricingTable.tsx | 14 ++++++------- craft/src/components/sections/Tabs.tsx | 8 +++---- .../src/components/sections/Testimonials.tsx | 10 ++++----- .../src/components/sections/_cta-helpers.tsx | 5 ++--- craft/src/utils/form-relay-wiring.ts | 7 +++---- 26 files changed, 101 insertions(+), 119 deletions(-) diff --git a/craft/src/components/basic/Logo.tsx b/craft/src/components/basic/Logo.tsx index 984a01a..1442e79 100644 --- a/craft/src/components/basic/Logo.tsx +++ b/craft/src/components/basic/Logo.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties, useCallback, useRef, useState } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { useSiteDesign } from '../../state/SiteDesignContext'; +import { escapeHtml, escapeAttr } from '../../utils/escape'; /* ---------- Types ---------- */ @@ -37,12 +38,6 @@ async function uploadToWhp(file: File): Promise { } catch { return null; } } -/* ---------- Helper: escape HTML ---------- */ -function esc(str: any): string { - str = String(str ?? ""); - return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} - /* ---------- Component ---------- */ export const Logo: UserComponent = ({ @@ -394,7 +389,7 @@ Logo.craft = { let innerHtml: string; if (props.type === 'image' && props.imageSrc) { const imgStyle = cssPropsToString({ width: props.imageWidth || '120px', height: 'auto', display: 'block' }); - innerHtml = `${esc(props.text || 'Logo')}`; + innerHtml = `${escapeAttr(props.text || 'Logo')}`; } else { const spanStyle = cssPropsToString({ fontWeight: props.fontWeight || '700', @@ -402,7 +397,7 @@ Logo.craft = { fontFamily: props.fontFamily || 'Inter, sans-serif', color: props.color || '#1f2937', }); - innerHtml = `${esc(props.text || 'MySite')}`; + innerHtml = `${escapeHtml(props.text || 'MySite')}`; } const aStyle = cssPropsToString({ @@ -414,6 +409,6 @@ Logo.craft = { }); return { - html: `${innerHtml}`, + html: `${innerHtml}`, }; }; diff --git a/craft/src/components/basic/Menu.tsx b/craft/src/components/basic/Menu.tsx index 5963187..aad7af3 100644 --- a/craft/src/components/basic/Menu.tsx +++ b/craft/src/components/basic/Menu.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties, useState } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { usePages } from '../../state/PageContext'; +import { escapeHtml, escapeAttr } from '../../utils/escape'; /* ---------- Types ---------- */ @@ -34,12 +35,6 @@ const defaultLinks: MenuLink[] = [ { text: 'Contact', href: '#contact', isCta: true }, ]; -/* ---------- Helper: escape HTML ---------- */ -function esc(str: any): string { - str = String(str ?? ""); - return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); -} - /* ---------- Component ---------- */ export const Menu: UserComponent = ({ @@ -494,7 +489,7 @@ Menu.craft = { borderRadius: link.isCta ? '6px' : '0', transition: 'color 0.15s, background-color 0.15s', }); - return `${esc(link.text)}`; + return `${escapeHtml(link.text)}`; }).join('\n '); const hoverCss = `