/** * Utilities for converting WordPress HTML content to React components */ import Image from 'next/image'; import { ReactNode } from 'react'; export interface WordPressImage { originalUrl: string; localPath: string; alt: string; needsAltText: boolean; } export interface ImageMapping { [originalUrl: string]: WordPressImage; } /** * Load image mapping */ export function loadImageMapping(): ImageMapping { try { // Use dynamic import for fs in Next.js server components const fs = require('fs'); const path = require('path'); // In Next.js, process.cwd() returns the code directory (where Next.js runs from) const mappingPath = path.join(process.cwd(), 'lib', 'wordpress-data', 'image-mapping.json'); if (!fs.existsSync(mappingPath)) { console.warn('Image mapping file not found at:', mappingPath); return {}; } const mappingData = JSON.parse(fs.readFileSync(mappingPath, 'utf8')); const mapping: ImageMapping = {}; // Handle both array and object formats if (Array.isArray(mappingData)) { mappingData.forEach((img: any) => { if (img && img.originalUrl) { // Map the structure to match WordPressImage interface mapping[img.originalUrl] = { originalUrl: img.originalUrl, localPath: img.localPath || img.path || '', alt: img.alt || img.altText || '', needsAltText: img.needsAltText || false, }; } }); } else if (typeof mappingData === 'object' && mappingData !== null) { Object.assign(mapping, mappingData); } return mapping; } catch (e) { console.warn('Could not load image mapping:', e); // Return empty object instead of throwing return {}; } } /** * Clean HTML entities */ export function cleanHtmlEntities(text: string): string { if (!text || typeof text !== 'string') { return ''; } return text // First, handle HTML entities .replace(/’/g, "'") .replace(/‘/g, "'") .replace(/“/g, '"') .replace(/”/g, '"') .replace(/–/g, '–') .replace(/—/g, '—') .replace(/&/g, '&') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/ /g, ' ') .replace(/…/g, '...') .replace(/\[…\]/g, '...') // Remove any remaining HTML tags (except those we want to preserve in dangerouslySetInnerHTML contexts) // This strips out any stray tags that shouldn't be in plain text content .replace(/<(?!\/?(strong|b|em|i|a)\b)[^>]+>/gi, '') .trim(); } /** * Clean HTML from headings - strips ALL HTML tags and entities * Use this for headings where we never want any HTML markup */ export function cleanHeadingText(text: string): string { if (!text || typeof text !== 'string') { return ''; } return text // First, handle HTML entities .replace(/’/g, "'") .replace(/‘/g, "'") .replace(/“/g, '"') .replace(/”/g, '"') .replace(/–/g, '–') .replace(/—/g, '—') .replace(/&/g, '&') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/ /g, ' ') .replace(/…/g, '...') .replace(/\[…\]/g, '...') // Remove ALL HTML tags - no exceptions for headings .replace(/<[^>]+>/g, '') // Clean up any leftover whitespace .replace(/\s+/g, ' ') .trim(); } /** * Strip HTML tags and get clean text */ export function stripHtml(html: string): string { if (!html || typeof html !== 'string') { return ''; } return html .replace(/]*>[\s\S]*?<\/script>/gi, '') .replace(/]*>[\s\S]*?<\/style>/gi, '') .replace(/<[^>]+>/g, ' ') .replace(/\s+/g, ' ') .trim(); } /** * Extract text content from HTML */ export function extractTextContent(html: string): string { return cleanHtmlEntities(stripHtml(html)); } /** * Normalize WordPress image URL (fix malformed URLs) */ function normalizeImageUrl(url: string): string { if (!url) return url; // Fix malformed URLs like https:///wp-content -> https://rockymountainvending.com/wp-content if (url.startsWith('https:///') || url.startsWith('http:///')) { return url.replace(/^https?:\/\//, 'https://rockymountainvending.com/'); } return url; } /** * Convert WordPress image URL to local path */ export function getImagePath(originalUrl: string, imageMapping: ImageMapping): string { // Normalize the URL first const normalizedUrl = normalizeImageUrl(originalUrl); // Try lookup with normalized URL first let mapping = imageMapping[normalizedUrl]; // If not found, try with original URL if (!mapping) { mapping = imageMapping[originalUrl]; } if (mapping) { // Handle both 'localPath' and 'altText' properties const path = (mapping as any).localPath || (mapping as any).path; if (path && !path.startsWith('http')) { return path; } } // If we have a normalized URL that's different from original, use it if (normalizedUrl !== originalUrl && normalizedUrl.startsWith('http')) { return normalizedUrl; } // Fallback to original URL if not found in mapping return originalUrl; } /** * Get alt text for image */ export function getImageAlt(originalUrl: string, imageMapping: ImageMapping, fallback: string = ''): string { // Normalize the URL first const normalizedUrl = normalizeImageUrl(originalUrl); // Try lookup with normalized URL first let mapping = imageMapping[normalizedUrl]; // If not found, try with original URL if (!mapping) { mapping = imageMapping[originalUrl]; } if (mapping) { // Handle both 'alt' and 'altText' properties const alt = (mapping as any).alt || (mapping as any).altText; if (alt) { return alt; } } return fallback || 'Vending machine image'; }