sitesmith: null-safe esc() across all toHtml + WorkingIndicator
Real-world AI output frequently sends mismatched prop names (e.g. items vs features, cta object vs buttonText/Href). The toHtml functions of section/form/sections-folder components each defined a local esc = (s: string) => s.replace(...) that crashed when called with undefined, taking the auto-save export with it. Patched every local esc() to coerce non-strings: const esc = (s: any) => String(s ?? "").replace(...) 17 files touched; behavior unchanged for valid string inputs. Also adds a WorkingIndicator (Claude Code-style spinner + rotating phrase + elapsed seconds) shown in the modal footer while a generation is in flight, replacing the disabled "Thinking..." placeholder.
This commit is contained in:
@@ -171,7 +171,7 @@ SearchBar.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(SearchBar as any).toHtml = (props: SearchBarProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const {
|
||||
placeholder = 'Search...',
|
||||
buttonText = 'Search',
|
||||
|
||||
@@ -372,7 +372,7 @@ ContactForm.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(ContactForm as any).toHtml = (props: ContactFormProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const formStyle = cssPropsToString({
|
||||
padding: '32px',
|
||||
display: 'flex',
|
||||
|
||||
@@ -165,7 +165,7 @@ InputField.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(InputField as any).toHtml = (props: InputFieldProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const wrapStyle = cssPropsToString({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
@@ -249,7 +249,7 @@ SubscribeForm.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(SubscribeForm as any).toHtml = (props: SubscribeFormProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const {
|
||||
heading = 'Subscribe to our newsletter',
|
||||
placeholder = 'Enter your email',
|
||||
|
||||
@@ -167,7 +167,7 @@ TextareaField.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(TextareaField as any).toHtml = (props: TextareaFieldProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const wrapStyle = cssPropsToString({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
@@ -293,7 +293,7 @@ Accordion.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(Accordion as any).toHtml = (props: AccordionProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>');
|
||||
const sectionStyle = cssPropsToString({
|
||||
padding: '60px 20px',
|
||||
...props.style,
|
||||
|
||||
@@ -173,7 +173,7 @@ CTASection.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(CTASection as any).toHtml = (props: CTASectionProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>');
|
||||
const sectionStyle = cssPropsToString({
|
||||
background: props.gradient || defaultGradient,
|
||||
padding: '80px 20px',
|
||||
|
||||
@@ -405,7 +405,7 @@ CallToAction.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(CallToAction as any).toHtml = (props: CallToActionProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>');
|
||||
|
||||
const bgType = props.bgType || 'gradient';
|
||||
const bgValue = props.bgValue || defaultGradient;
|
||||
|
||||
@@ -443,7 +443,7 @@ ContentSlider.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(ContentSlider as any).toHtml = (props: ContentSliderProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const {
|
||||
slides = defaultSlides,
|
||||
autoplay = true,
|
||||
|
||||
@@ -249,7 +249,7 @@ Countdown.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(Countdown as any).toHtml = (props: CountdownProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const {
|
||||
targetDate = DEFAULT_TARGET,
|
||||
heading = 'Coming Soon',
|
||||
|
||||
@@ -177,7 +177,7 @@ FeaturesGrid.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(FeaturesGrid as any).toHtml = (props: FeaturesGridProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>');
|
||||
const sectionStyle = cssPropsToString({
|
||||
padding: '80px 20px',
|
||||
...props.style,
|
||||
|
||||
@@ -277,7 +277,7 @@ Gallery.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(Gallery as any).toHtml = (props: GalleryProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const sectionStyle = cssPropsToString({
|
||||
padding: '60px 20px',
|
||||
...props.style,
|
||||
|
||||
@@ -404,7 +404,7 @@ HeroSimple.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(HeroSimple as any).toHtml = (props: HeroProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>');
|
||||
const bg = buildBackground(props);
|
||||
const justifyMap: Record<string, string> = { top: 'flex-start', center: 'center', bottom: 'flex-end' };
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@ NumberCounter.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(NumberCounter as any).toHtml = (props: NumberCounterProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const {
|
||||
counters = defaultCounters,
|
||||
columns = 4,
|
||||
|
||||
@@ -398,7 +398,7 @@ PricingTable.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(PricingTable as any).toHtml = (props: PricingTableProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>');
|
||||
const bulletType = props.bulletType || 'check';
|
||||
const sectionStyle = cssPropsToString({
|
||||
padding: '80px 20px',
|
||||
|
||||
@@ -290,7 +290,7 @@ Tabs.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(Tabs as any).toHtml = (props: TabsProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>');
|
||||
const sectionStyle = cssPropsToString({
|
||||
padding: '60px 20px',
|
||||
...props.style,
|
||||
|
||||
@@ -371,7 +371,7 @@ Testimonials.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(Testimonials as any).toHtml = (props: TestimonialsProps, _childrenHtml: string) => {
|
||||
const esc = (s: string) => s.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const {
|
||||
testimonials = defaultTestimonials,
|
||||
layout = 'grid',
|
||||
|
||||
Reference in New Issue
Block a user