]*swiper-wrapper[^>]*>([\s\S]*?)<\/div>\s*<\/div>/gi;
sliderMatch = swiperWrapperRegex.exec(sectionHtml);
}
let sliderImages: Array<{ src: string; alt: string; width?: number; height?: number; title?: string }> = [];
let sliderIndex: number | null = null;
if (sliderMatch) {
sliderIndex = sliderMatch.index;
const sliderContent = sliderMatch[1];
// Extract all images from the slider - look in swiper-slide divs
const slideRegex = /
]*swiper-slide[^>]*>([\s\S]*?)<\/div>\s*<\/div>/gi;
let slideMatch;
slideRegex.lastIndex = 0;
while ((slideMatch = slideRegex.exec(sliderContent)) !== null) {
const slideContent = slideMatch[1];
const imgMatch = slideContent.match(/
![]()
]*)>/i);
if (imgMatch) {
const imgTag = imgMatch[0];
const srcMatch = imgTag.match(/src=["']([^"']+)["']/i);
const altMatch = imgTag.match(/alt=["']([^"']*)["']/i);
const titleMatch = slideContent.match(/title=["']([^"']*)["']/i) || imgTag.match(/title=["']([^"']*)["']/i);
const widthMatch = imgTag.match(/width=["']?(\d+)["']?/i);
const heightMatch = imgTag.match(/height=["']?(\d+)["']?/i);
if (srcMatch) {
// Fix malformed URLs (e.g., https:///wp-content -> https://rockymountainvending.com/wp-content)
let imageSrc = srcMatch[1];
if (imageSrc.startsWith('https:///') || imageSrc.startsWith('http:///')) {
imageSrc = imageSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imageSrc.startsWith('https://wp-content') || imageSrc.startsWith('http://wp-content')) {
imageSrc = imageSrc.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
}
sliderImages.push({
src: imageSrc,
alt: altMatch ? cleanHtmlEntities(altMatch[1]) : (titleMatch ? cleanHtmlEntities(titleMatch[1]) : ''),
width: widthMatch ? parseInt(widthMatch[1]) : undefined,
height: heightMatch ? parseInt(heightMatch[1]) : undefined,
title: titleMatch ? cleanHtmlEntities(titleMatch[1]) : undefined
});
}
}
}
// If no slides found, try direct image extraction from slider content
if (sliderImages.length === 0) {
const sliderImageRegex = /
![]()
]*)>/gi;
let sliderImgMatch;
sliderImageRegex.lastIndex = 0;
while ((sliderImgMatch = sliderImageRegex.exec(sliderContent)) !== null) {
const imgTag = sliderImgMatch[0];
const srcMatch = imgTag.match(/src=["']([^"']+)["']/i);
const altMatch = imgTag.match(/alt=["']([^"']*)["']/i);
const titleMatch = imgTag.match(/title=["']([^"']*)["']/i);
const widthMatch = imgTag.match(/width=["']?(\d+)["']?/i);
const heightMatch = imgTag.match(/height=["']?(\d+)["']?/i);
if (srcMatch) {
// Fix malformed URLs (e.g., https:///wp-content -> https://rockymountainvending.com/wp-content)
let imageSrc = srcMatch[1];
if (imageSrc.startsWith('https:///') || imageSrc.startsWith('http:///')) {
imageSrc = imageSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imageSrc.startsWith('https://wp-content') || imageSrc.startsWith('http://wp-content')) {
imageSrc = imageSrc.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
}
sliderImages.push({
src: imageSrc,
alt: altMatch ? cleanHtmlEntities(altMatch[1]) : (titleMatch ? cleanHtmlEntities(titleMatch[1]) : ''),
width: widthMatch ? parseInt(widthMatch[1]) : undefined,
height: heightMatch ? parseInt(heightMatch[1]) : undefined,
title: titleMatch ? cleanHtmlEntities(titleMatch[1]) : undefined
});
}
}
}
}
// Extract Elementor gallery widget items (e.g., repairs page image grid)
const galleryImages: Array<{ src: string; alt: string; width?: number; height?: number; index: number }> = [];
const galleryImageRegex = /
]*class="[^"]*e-gallery-image[^"]*"[^>]*data-thumbnail="([^"]+)"[^>]*data-width="(\d+)"[^>]*data-height="(\d+)"[^>]*aria-label="([^"]*)"[^>]*>/gi;
let galleryMatch;
galleryImageRegex.lastIndex = 0;
while ((galleryMatch = galleryImageRegex.exec(sectionHtml)) !== null) {
let imageSrc = galleryMatch[1];
// Fix malformed URLs (e.g., https:///wp-content -> https://rockymountainvending.com/wp-content)
if (imageSrc.startsWith('https:///') || imageSrc.startsWith('http:///')) {
imageSrc = imageSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imageSrc.startsWith('https://wp-content') || imageSrc.startsWith('http://wp-content')) {
imageSrc = imageSrc.replace(
/^https?:\/\/wp-content/,
'https://rockymountainvending.com/wp-content'
);
}
const width = parseInt(galleryMatch[2], 10);
const height = parseInt(galleryMatch[3], 10);
const alt = cleanHtmlEntities(galleryMatch[4] || '');
galleryImages.push({
src: imageSrc,
alt,
width: Number.isNaN(width) ? undefined : width,
height: Number.isNaN(height) ? undefined : height,
index: galleryMatch.index
});
}
// Extract and process regular images (excluding slider images)
const imageRegex = /
![]()
]*)>/gi;
const images: Array<{ src: string; alt: string; width?: number; height?: number; index: number }> = [];
let imageMatch;
imageRegex.lastIndex = 0;
while ((imageMatch = imageRegex.exec(sectionHtml)) !== null) {
// Skip if this image is part of the slider
if (sliderIndex !== null && imageMatch.index >= sliderIndex && imageMatch.index < sliderIndex + sliderMatch![0].length) {
continue;
}
const imgTag = imageMatch[0];
const srcMatch = imgTag.match(/src=["']([^"']+)["']/i);
const altMatch = imgTag.match(/alt=["']([^"']*)["']/i);
const widthMatch = imgTag.match(/width=["']?(\d+)["']?/i);
const heightMatch = imgTag.match(/height=["']?(\d+)["']?/i);
if (srcMatch) {
// Fix malformed URLs (e.g., https:///wp-content -> https://rockymountainvending.com/wp-content)
let imageSrc = srcMatch[1];
if (imageSrc.startsWith('https:///') || imageSrc.startsWith('http:///')) {
imageSrc = imageSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imageSrc.startsWith('https://wp-content') || imageSrc.startsWith('http://wp-content')) {
imageSrc = imageSrc.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
}
// Fix specific auto-repair image URL
if (imageSrc.includes('Vending-Machine-at-an-Auto-Repair-Facility') && !imageSrc.includes('rockymountainvending.com')) {
imageSrc = 'https://rockymountainvending.com/wp-content/uploads/2024/01/Vending-Machine-at-an-Auto-Repair-Facility-768x1024.webp';
}
images.push({
src: imageSrc,
alt: altMatch ? cleanHtmlEntities(altMatch[1]) : '',
width: widthMatch ? parseInt(widthMatch[1]) : undefined,
height: heightMatch ? parseInt(heightMatch[1]) : undefined,
index: imageMatch.index
});
}
}
// Extract paragraphs
const paragraphRegex = /
]*>(.*?)<\/p>/gi;
const paragraphs: Array<{ content: string; index: number; hasList: boolean }> = [];
let paraMatch;
paragraphRegex.lastIndex = 0;
while ((paraMatch = paragraphRegex.exec(sectionHtml)) !== null) {
const content = paraMatch[1];
const hasList = /<(ul|ol)[^>]*>/i.test(content);
const cleanContent = cleanHtmlEntities(content).trim();
if (cleanContent.length > 0) {
paragraphs.push({
content: cleanContent,
index: paraMatch.index,
hasList
});
}
}
// Extract lists - handle nested structures better
// First try to find lists at the top level
const listRegex = /<(ul|ol)[^>]*>([\s\S]*?)<\/\1>/gi;
const lists: Array<{ listType: 'ul' | 'ol'; items: Array<{ text: string; html: string }>; index: number }> = [];
let listMatch;
// Reset regex lastIndex
listRegex.lastIndex = 0;
while ((listMatch = listRegex.exec(sectionHtml)) !== null) {
const listType = listMatch[1] as 'ul' | 'ol';
const listContent = listMatch[2];
const itemRegex = /
]*>(.*?)<\/li>/gi;
const items: Array<{ text: string; html: string }> = [];
let itemMatch;
// Reset item regex
itemRegex.lastIndex = 0;
while ((itemMatch = itemRegex.exec(listContent)) !== null) {
const itemHtml = itemMatch[1];
// Clean HTML entities but preserve basic formatting
const cleanText = cleanHtmlEntities(itemHtml);
items.push({
text: cleanText,
html: itemHtml // Keep original HTML for processing
});
}
if (items.length > 0) {
lists.push({
listType: listType,
items,
index: listMatch.index
});
}
}
// If no lists found at top level, try to find them in paragraphs (they might be nested)
if (lists.length === 0) {
// Look for lists inside paragraph content - need to use original HTML, not cleaned
const paragraphRegexOriginal = /]*>(.*?)<\/p>/gi;
let paraMatchOriginal;
paragraphRegexOriginal.lastIndex = 0;
while ((paraMatchOriginal = paragraphRegexOriginal.exec(sectionHtml)) !== null) {
const paraContent = paraMatchOriginal[1];
const paraListRegex = /<(ul|ol)[^>]*>([\s\S]*?)<\/\1>/gi;
let paraListMatch;
paraListRegex.lastIndex = 0;
while ((paraListMatch = paraListRegex.exec(paraContent)) !== null) {
const listType = paraListMatch[1] as 'ul' | 'ol';
const listContent = paraListMatch[2];
const itemRegex = /
]*>(.*?)<\/li>/gi;
const items: Array<{ text: string; html: string }> = [];
let itemMatch;
itemRegex.lastIndex = 0;
while ((itemMatch = itemRegex.exec(listContent)) !== null) {
const itemHtml = itemMatch[1];
items.push({
text: cleanHtmlEntities(itemHtml),
html: itemHtml
});
}
if (items.length > 0) {
lists.push({
listType: listType,
items,
index: paraMatchOriginal.index + (paraListMatch.index || 0)
});
}
}
}
}
// Group images that should be side-by-side (images close together in same section)
const imageGroups: Array> = [];
const processedImageIndices = new Set();
images.forEach((img, idx) => {
if (processedImageIndices.has(idx)) return;
// Check if this image is in a column layout
const imgContext = sectionHtml.substring(
Math.max(0, img.index - 2000),
Math.min(sectionHtml.length, img.index + 2000)
);
const isInColumn = /elementor-col-(33|50|66|25|75)/i.test(imgContext);
const group = [img];
processedImageIndices.add(idx);
// Find other images close to this one (within 2000 chars)
images.forEach((otherImg, otherIdx) => {
if (otherIdx !== idx && !processedImageIndices.has(otherIdx)) {
const distance = Math.abs(otherImg.index - img.index);
// Check if they're in the same section (no section break between them)
const betweenContent = sectionHtml.substring(
Math.min(img.index, otherImg.index),
Math.max(img.index, otherImg.index)
);
const hasSectionBreak = /<\/section>/i.test(betweenContent);
if (distance < 2000 && !hasSectionBreak) {
// If first image is in a column, check if second is too
if (isInColumn) {
const otherContext = sectionHtml.substring(
Math.max(0, otherImg.index - 2000),
Math.min(sectionHtml.length, otherImg.index + 2000)
);
const otherIsInColumn = /elementor-col-(33|50|66|25|75)/i.test(otherContext);
if (otherIsInColumn) {
group.push(otherImg);
processedImageIndices.add(otherIdx);
}
} else {
// If first image is not in column, group any nearby images (for general side-by-side)
group.push(otherImg);
processedImageIndices.add(otherIdx);
}
}
}
});
// Only create group if there are 2+ images
if (group.length > 1) {
imageGroups.push(group.map(img => ({ ...img, type: 'image' as const })));
} else {
// Single image, don't group
processedImageIndices.delete(idx);
}
});
// Check if this section should have machine cards (detect "Vending Machines for Gyms" pattern)
let shouldShowMachineCards = false;
let machineCardsHeadingIndex: number | null = null;
let machineCardsParagraphIndex: number | null = null;
// Look for heading "Vending Machines for Gyms" or similar patterns
// Also match "Vending Machines We Use for..." variations
const gymMachinesHeading = headings.find(h => {
const cleanHeading = cleanHtmlEntities(h.content.replace(/<[^>]+>/g, '')).toLowerCase();
return (cleanHeading.includes('vending machines for') ||
cleanHeading.includes('vending machines we use')) &&
(cleanHeading.includes('gym') || cleanHeading.includes('studio') ||
cleanHeading.includes('community') || cleanHeading.includes('dance') ||
cleanHeading.includes('car wash'));
});
if (gymMachinesHeading) {
machineCardsHeadingIndex = gymMachinesHeading.index;
// Look for paragraph about machines - more flexible pattern matching
const gymMachinesParagraph = paragraphs.find(p => {
const cleanPara = cleanHtmlEntities(p.content.replace(/<[^>]+>/g, '')).toLowerCase();
// Match various patterns:
// - "Many of our [gyms/studios/community centers/dance studios/car washes]"
// - "Primarily we will install"
// - "Many of our dance studios have just one"
// - "Many of our community centers have many machines"
// - "We offer vending machines that are perfectly suited"
// - Any paragraph that mentions QFV, AMS, Crane, USI, or Seaga machines
return (cleanPara.includes('many of our') &&
(cleanPara.includes('gym') || cleanPara.includes('studio') ||
cleanPara.includes('community') || cleanPara.includes('dance') ||
cleanPara.includes('car wash'))) ||
cleanPara.includes('primarily we will install') ||
(cleanPara.includes('many of our') && cleanPara.includes('vending machine')) ||
(cleanPara.includes('we offer vending machines') &&
(cleanPara.includes('car wash') || cleanPara.includes('location'))) ||
(cleanPara.includes('qfv') || cleanPara.includes('ams') ||
cleanPara.includes('crane') || cleanPara.includes('usi') ||
cleanPara.includes('seaga'));
});
if (gymMachinesParagraph) {
machineCardsParagraphIndex = gymMachinesParagraph.index;
shouldShowMachineCards = true;
}
}
// Machine data from machines-we-use page - QFV 50 should always be first
const gymMachines = [
{
name: "QFV 50 Combo",
description: "The Ultimate Combo Machine: The QFV 50 stands out as our top combo machine, offering a remarkable blend of style and functionality. With 30 customizable drink selections, it caters to a variety of tastes and preferences.",
image: "/images/wordpress/EH0A1551-HDR.webp",
alt: "Quick Fresh Vending QFV 50 combo vending machine showcasing dual compartments for snacks and drinks, with dimensions and payment system details.",
selections: 50,
items: 300,
},
{
name: "Crane Bev Max 4",
description: "A Beverage Powerhouse: Bev Max 4 is a testament to efficiency in the vending world, boasting an impressive 48 drink selections. Its standout feature is the incredibly low fault rate of just 0.003%.",
image: "/images/wordpress/Crane-Bev-Max-4-Drink.webp",
alt: "Crane Bev Max 4 Classic",
selections: 48,
items: 400,
},
{
name: "USI 3130",
description: "Compact Snack Solution: The USI 3130 is designed for locations where space is at a premium. Despite its compact size, it doesn't compromise on functionality.",
image: "/images/wordpress/USI-3130-pps9s29iuucicxizctvma2nj0qezz66y25gy5nrsf4.webp",
alt: "Rocky Mountain Vending USI 3130",
selections: 24,
items: 420,
},
{
name: "AMS 39 VCB",
description: "Reliability Redefined: The AMS 39 VCB is a dependable combo machine that consistently meets our high standards. Its robust design ensures long-term reliability.",
image: "/images/wordpress/AMS-39-VCB-Combo.webp",
alt: "AMS 39 VCB",
selections: 36,
items: 375,
},
{
name: "Crane Merchant Media Snack",
description: "The Snack Giant: When it comes to snack machines, the Crane Merchant Media Snack is unparalleled in capacity. It's the largest snack machine available, capable of holding a diverse range of snack options.",
image: "/images/wordpress/Crane-Merchant-6-Media-Snack-pps9rxkbwo62qvpt49uhflu81t25wooadi7ir9yra8.webp",
alt: "Crane Merchant Media 6",
selections: 44,
items: 375,
},
];
// Filter out images that are part of the machine cards section (they'll be replaced)
const imagesToExclude = new Set();
const imageGroupsToExclude = new Set();
if (shouldShowMachineCards && machineCardsParagraphIndex !== null) {
// Find all images after the paragraph that should be replaced
// Look for images in the next section after the paragraph (within 10000 chars to catch all)
const sectionEndIndex = machineCardsParagraphIndex + 10000;
images.forEach((img, idx) => {
if (img.index > machineCardsParagraphIndex! && img.index < sectionEndIndex) {
// Check if it's one of the machine images (by checking alt text or src)
const imgAlt = img.alt.toLowerCase();
const imgSrc = img.src.toLowerCase();
// Exclude ANY image in elementor/thumbs folder (these are the old machine images)
// Also exclude any image that matches machine patterns
if (imgSrc.includes('elementor/thumbs') ||
imgAlt.includes('crane bev max') || imgAlt.includes('bev max') ||
imgAlt.includes('usi 3130') || imgAlt.includes('usi') ||
imgAlt.includes('ams 39') || imgAlt.includes('ams') ||
imgAlt.includes('crane merchant') || imgAlt.includes('merchant media') ||
imgAlt.includes('qfv') || imgAlt.includes('quick fresh') ||
imgAlt.includes('seaga') || imgAlt.includes('seage') ||
imgSrc.includes('bev-max') || imgSrc.includes('usi-3130') ||
imgSrc.includes('ams-39') || imgSrc.includes('merchant') ||
imgSrc.includes('qfv') || imgSrc.includes('qfv-50') ||
imgSrc.includes('seaga') || imgSrc.includes('seage')) {
imagesToExclude.add(idx);
}
}
});
// Also exclude image groups if they're in the machine cards section
imageGroups.forEach((group, groupIdx) => {
if (group[0].index > machineCardsParagraphIndex! && group[0].index < sectionEndIndex) {
// Check if any image in the group matches machine patterns
// Prioritize checking for elementor/thumbs first (most reliable indicator)
const hasMachineImage = group.some((img) => {
const imgAlt = img.alt.toLowerCase();
const imgSrc = img.src.toLowerCase();
return imgSrc.includes('elementor/thumbs') ||
imgAlt.includes('crane bev max') || imgAlt.includes('bev max') ||
imgAlt.includes('usi 3130') || imgAlt.includes('usi') ||
imgAlt.includes('ams 39') || imgAlt.includes('ams') ||
imgAlt.includes('crane merchant') || imgAlt.includes('merchant media') ||
imgAlt.includes('qfv') || imgAlt.includes('quick fresh') ||
imgAlt.includes('seaga') || imgAlt.includes('seage') ||
imgSrc.includes('bev-max') || imgSrc.includes('usi-3130') ||
imgSrc.includes('ams-39') || imgSrc.includes('merchant') ||
imgSrc.includes('qfv') || imgSrc.includes('qfv-50') ||
imgSrc.includes('seaga') || imgSrc.includes('seage');
});
if (hasMachineImage) {
imageGroupsToExclude.add(groupIdx);
// Mark all images in this group as processed so they're excluded
group.forEach((img) => {
const imgIndex = images.findIndex(i => i.index === img.index);
if (imgIndex !== -1) {
processedImageIndices.add(imgIndex);
}
});
}
}
});
}
// Process content in order
const allElements = [
...headings.map(h => ({ type: 'heading' as const, ...h })),
...funfacts.map(f => ({ type: 'funfact' as const, ...f })),
...(sliderImages.length > 0 ? [{ type: 'slider' as const, images: sliderImages, index: sliderIndex! }] : []),
...(galleryImages.length > 0
? galleryImages.map(img => ({
type: 'gallery' as const,
...img
}))
: []),
...imageGroups.filter((group, groupIdx) => !imageGroupsToExclude.has(groupIdx)).map((group, groupIdx) => ({
type: 'image-group' as const,
images: group,
index: group[0].index
})),
...images.filter((img, idx) => !processedImageIndices.has(idx) && !imagesToExclude.has(idx)).map(img => ({ type: 'image' as const, ...img })),
...paragraphs.map(p => ({ type: 'paragraph' as const, ...p })),
...lists.map(l => ({ type: 'list' as const, listType: l.listType, items: l.items, index: l.index })),
// Add machine cards after the paragraph if detected
...(shouldShowMachineCards && machineCardsParagraphIndex !== null
? [{ type: 'machine-cards' as const, index: machineCardsParagraphIndex + 1000 }]
: [])
].sort((a, b) => a.index - b.index);
// Render elements
allElements.forEach((element) => {
if (element.type === 'heading') {
const headingElement = element as { type: 'heading'; level: number; content: string; index: number };
const HeadingTag = `h${headingElement.level}` as keyof React.JSX.IntrinsicElements;
// Clean HTML tags from heading content
let cleanHeadingContent = headingElement.content;
// Remove HTML tags but preserve text content
cleanHeadingContent = cleanHeadingContent.replace(/<[^>]+>/g, '');
// Clean HTML entities
cleanHeadingContent = cleanHtmlEntities(cleanHeadingContent);
sectionComponents.push(
{cleanHeadingContent}
);
} else if (element.type === 'slider') {
// Render slider images as a horizontal scrolling carousel with auto-rotation
const carouselImages = element.images.map((img) => {
const imagePath = getImagePath(img.src, imageMapping);
const alt = getImageAlt(img.src, imageMapping, img.alt || img.title || '');
// Use the mapped local path if available, otherwise fix malformed URLs
let finalSrc = imagePath;
// If imagePath is a local path (starts with /), use it directly
if (imagePath.startsWith('/') && !imagePath.startsWith('//')) {
finalSrc = imagePath;
} else {
// Fix malformed URLs in imagePath first
if (imagePath.startsWith('https:///') || imagePath.startsWith('http:///')) {
finalSrc = imagePath.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imagePath.startsWith('https://wp-content') || imagePath.startsWith('http://wp-content')) {
finalSrc = imagePath.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
} else if (imagePath.startsWith('http')) {
finalSrc = imagePath; // Keep as-is if valid URL
} else {
// Fallback to original src and fix it
finalSrc = img.src;
if (finalSrc.startsWith('https:///') || finalSrc.startsWith('http:///')) {
finalSrc = finalSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (finalSrc.startsWith('https://wp-content') || finalSrc.startsWith('http://wp-content')) {
finalSrc = finalSrc.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
} else if (finalSrc.startsWith('http')) {
finalSrc = finalSrc; // Keep as-is if valid URL
} else if (finalSrc.startsWith('/')) {
finalSrc = `https://rockymountainvending.com${finalSrc}`;
} else {
finalSrc = `https://rockymountainvending.com/${finalSrc}`;
}
}
}
return {
src: finalSrc,
alt: alt,
title: img.title
};
});
sectionComponents.push(
);
} else if (element.type === 'gallery') {
const imagePath = getImagePath(element.src, imageMapping);
const alt = getImageAlt(element.src, imageMapping, element.alt);
// Use the mapped local path if available, otherwise fix malformed URLs
let finalSrc = imagePath;
if (imagePath.startsWith('/') && !imagePath.startsWith('//')) {
finalSrc = imagePath;
} else {
if (imagePath.startsWith('https:///') || imagePath.startsWith('http:///')) {
finalSrc = imagePath.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imagePath.startsWith('https://wp-content') || imagePath.startsWith('http://wp-content')) {
finalSrc = imagePath.replace(
/^https?:\/\/wp-content/,
'https://rockymountainvending.com/wp-content'
);
} else if (imagePath.startsWith('http')) {
finalSrc = imagePath;
} else {
finalSrc = element.src;
if (finalSrc.startsWith('https:///') || finalSrc.startsWith('http:///')) {
finalSrc = finalSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (finalSrc.startsWith('https://wp-content') || finalSrc.startsWith('http://wp-content')) {
finalSrc = finalSrc.replace(
/^https?:\/\/wp-content/,
'https://rockymountainvending.com/wp-content'
);
} else if (finalSrc.startsWith('http')) {
finalSrc = finalSrc;
} else if (finalSrc.startsWith('/')) {
finalSrc = `https://rockymountainvending.com${finalSrc}`;
} else {
finalSrc = `https://rockymountainvending.com/${finalSrc}`;
}
}
}
// Constrain image dimensions per style guide (max 600px for standalone images)
const constrainedWidth = element.width ? Math.min(element.width, 600) : 600;
const constrainedHeight = element.height ? Math.min(element.height, 600) : 600;
sectionComponents.push(
{element.width && element.height ? (
) : (
)}
);
if (prioritizeFirstImage && !hasPrioritizedImage) {
hasPrioritizedImage = true;
}
} else if (element.type === 'image-group') {
// Render grouped images side-by-side
const groupImages = element.images;
const imageCount = groupImages.length;
sectionComponents.push(
{groupImages.map((img, imgIdx) => {
const imagePath = getImagePath(img.src, imageMapping);
const alt = getImageAlt(img.src, imageMapping, img.alt);
// Use the mapped local path if available, otherwise fix malformed URLs
let finalSrc = imagePath;
// If imagePath is a local path (starts with /), use it directly
if (imagePath.startsWith('/') && !imagePath.startsWith('//')) {
finalSrc = imagePath;
} else {
// Fix malformed URLs in imagePath first
if (imagePath.startsWith('https:///') || imagePath.startsWith('http:///')) {
finalSrc = imagePath.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imagePath.startsWith('https://wp-content') || imagePath.startsWith('http://wp-content')) {
finalSrc = imagePath.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
} else if (imagePath.startsWith('http')) {
finalSrc = imagePath; // Keep as-is if valid URL
} else {
// Fallback to original src and fix it
finalSrc = img.src;
if (finalSrc.startsWith('https:///') || finalSrc.startsWith('http:///')) {
finalSrc = finalSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (finalSrc.startsWith('https://wp-content') || finalSrc.startsWith('http://wp-content')) {
finalSrc = finalSrc.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
} else if (finalSrc.startsWith('http')) {
finalSrc = finalSrc; // Keep as-is if valid URL
} else if (finalSrc.startsWith('/')) {
finalSrc = `https://rockymountainvending.com${finalSrc}`;
} else {
finalSrc = `https://rockymountainvending.com/${finalSrc}`;
}
}
}
// Constrain image dimensions per style guide (max 300px for grid images)
const constrainedWidth = img.width ? Math.min(img.width, 300) : 300;
const constrainedHeight = img.height ? Math.min(img.height, 300) : 300;
return (
{img.width && img.height ? (
) : (
)}
);
})}
);
if (prioritizeFirstImage && !hasPrioritizedImage) {
hasPrioritizedImage = true;
}
} else if (element.type === 'image') {
const imagePath = getImagePath(element.src, imageMapping);
const alt = getImageAlt(element.src, imageMapping, element.alt);
// Use the mapped local path if available, otherwise fix malformed URLs
let finalSrc = imagePath;
// If imagePath is a local path (starts with /), use it directly
if (imagePath.startsWith('/') && !imagePath.startsWith('//')) {
finalSrc = imagePath;
} else {
// Fix malformed URLs in imagePath first
if (imagePath.startsWith('https:///') || imagePath.startsWith('http:///')) {
finalSrc = imagePath.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (imagePath.startsWith('https://wp-content') || imagePath.startsWith('http://wp-content')) {
finalSrc = imagePath.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
} else if (imagePath.startsWith('http')) {
finalSrc = imagePath; // Keep as-is if valid URL
} else {
// Fallback to original src and fix it
finalSrc = element.src;
if (finalSrc.startsWith('https:///') || finalSrc.startsWith('http:///')) {
finalSrc = finalSrc.replace(/^https?:\/\//, 'https://rockymountainvending.com/');
} else if (finalSrc.startsWith('https://wp-content') || finalSrc.startsWith('http://wp-content')) {
finalSrc = finalSrc.replace(/^https?:\/\/wp-content/, 'https://rockymountainvending.com/wp-content');
} else if (finalSrc.startsWith('http')) {
finalSrc = finalSrc; // Keep as-is if valid URL
} else if (finalSrc.startsWith('/')) {
finalSrc = `https://rockymountainvending.com${finalSrc}`;
} else {
finalSrc = `https://rockymountainvending.com/${finalSrc}`;
}
}
}
// Check if this image was in a column layout by looking at surrounding HTML
// Look for elementor-col classes near this image's position
const imageContext = sectionHtml.substring(
Math.max(0, element.index - 1000),
Math.min(sectionHtml.length, element.index + 1000)
);
const hasColumn33 = /elementor-col-33|elementor-col-25/i.test(imageContext);
const hasColumn50 = /elementor-col-50/i.test(imageContext);
const hasColumn66 = /elementor-col-66|elementor-col-75/i.test(imageContext);
// Check if there are multiple images in the same section (side-by-side layout)
const imagesInSection = images.filter(img =>
img.index >= element.index - 2000 &&
img.index <= element.index + 2000 &&
img.index !== element.index
);
// Determine container width based on original layout - follow style guide
let containerClass = 'w-full';
let maxWidthClass = 'max-w-md';
let imageMaxWidth = 600; // Max 600px for standalone images per style guide
let imageMaxHeight = 600;
if (hasColumn33 || (imagesInSection.length >= 2 && imagesInSection.length < 4)) {
containerClass = 'w-full md:w-1/3';
maxWidthClass = 'max-w-xs';
imageMaxWidth = 300; // Max 300px for grid images per style guide
imageMaxHeight = 300;
} else if (hasColumn50 || (imagesInSection.length === 1)) {
containerClass = 'w-full md:w-1/2';
maxWidthClass = 'max-w-sm';
imageMaxWidth = 300; // Max 300px for grid images per style guide
imageMaxHeight = 300;
} else if (hasColumn66) {
containerClass = 'w-full md:w-2/3';
maxWidthClass = 'max-w-md';
imageMaxWidth = 500; // Slightly larger for 2/3 width
imageMaxHeight = 500;
}
// Constrain image dimensions per style guide
const constrainedWidth = element.width ? Math.min(element.width, imageMaxWidth) : imageMaxWidth;
const constrainedHeight = element.height ? Math.min(element.height, imageMaxHeight) : imageMaxHeight;
sectionComponents.push(
{element.width && element.height ? (
) : (
)}
);
if (prioritizeFirstImage && !hasPrioritizedImage) {
hasPrioritizedImage = true;
}
} else if (element.type === 'paragraph') {
// Process paragraph HTML to handle nested tags (em, strong, links, etc.)
let processedHtml = element.content;
// Convert strong/bold
processedHtml = processedHtml.replace(/]*>(.*?)<\/strong>/gi, '$1');
processedHtml = processedHtml.replace(/]*>(.*?)<\/b>/gi, '$1');
// Convert emphasis/italic
processedHtml = processedHtml.replace(/]*>(.*?)<\/em>/gi, '$1');
processedHtml = processedHtml.replace(/]*>(.*?)<\/i>/gi, '$1');
// Convert links
processedHtml = processedHtml.replace(/]+href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, (_match: string, href: string, text: string) => {
return `${text}`;
});
// Clean HTML entities
processedHtml = cleanHtmlEntities(processedHtml);
sectionComponents.push(
);
} else if (element.type === 'list') {
const listElement = element as { type: 'list'; listType: 'ul' | 'ol'; items: Array<{ text: string; html: string }>; index: number };
const ListTag = listElement.listType === 'ul' ? 'ul' : 'ol';
// Process list items to handle nested HTML
const processedItems = listElement.items.map((item: { text: string; html: string }) => {
// Extract and process nested HTML (strong, em, links, etc.)
let processed = item.html;
// Convert strong/bold
processed = processed.replace(/]*>(.*?)<\/strong>/gi, '$1');
processed = processed.replace(/]*>(.*?)<\/b>/gi, '$1');
// Convert emphasis/italic
processed = processed.replace(/]*>(.*?)<\/em>/gi, '$1');
processed = processed.replace(/]*>(.*?)<\/i>/gi, '$1');
// Convert links
processed = processed.replace(/]+href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, (match, href, text) => {
return `${text}`;
});
// Clean remaining HTML entities
processed = cleanHtmlEntities(processed);
return processed;
});
sectionComponents.push(
{processedItems.map((item: string, itemIndex: number) => (
))}
);
} else if (element.type === 'machine-cards') {
// Render machine cards using the same component from machines-we-use page
sectionComponents.push(
{gymMachines.map((machine) => (
))}
);
}
});
// If we have components, wrap them in a section
if (sectionComponents.length > 0) {
components.push(
);
}
});
// If no components were created, create a fallback
if (components.length === 0) {
const textContent = cleanHtmlEntities(
html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim()
);
if (textContent.length > 50) {
components.push(
);
}
}
return components;
} catch (error) {
// Enhanced error logging for development
if (process.env.NODE_ENV === 'development') {
console.error('Error cleaning WordPress content:', error);
if (error instanceof Error) {
console.error('Error message:', error.message);
console.error('Error stack:', error.stack);
}
console.error('HTML length:', html?.length || 0);
console.error('HTML preview:', html?.substring(0, 500) || 'No HTML provided');
} else {
// Minimal logging in production
console.error('Error processing WordPress content');
}
// Return error fallback with raw content preview
return [
Error processing content.
{process.env.NODE_ENV === 'development' && error instanceof Error && (
{error.message}
)}
{html && html.length > 0 && (
)}
];
}
}