233 lines
6 KiB
TypeScript
233 lines
6 KiB
TypeScript
/**
|
||
* 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(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
|
||
.replace(/<style[^>]*>[\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"
|
||
}
|