UNPKG

@ai-growth/nextjs

Version:

Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering

226 lines (225 loc) 8.39 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import React from 'react'; import { CmsImage } from './OptimizedImage'; import styles from './DefaultTemplate.module.css'; /** * ContentBody component for rendering content body with different strategies */ export const ContentBody = ({ content, contentType, customRenderer, className = '', }) => { // Use custom renderer if provided if (customRenderer) { return (_jsx("div", { className: `${styles.contentBody} ${className}`, children: customRenderer(content) })); } // Handle different content types const renderContent = () => { if (!content) { return (_jsx("div", { className: styles.errorContainer, children: _jsx("p", { className: styles.errorMessage, children: "No content available to display." }) })); } // Handle string content if (typeof content === 'string') { return renderStringContent(content); } // Handle array content (Portable Text or array of blocks) if (Array.isArray(content)) { return renderArrayContent(content); } // Handle object content if (typeof content === 'object') { return renderObjectContent(content); } // Fallback for unknown content types return (_jsx("div", { className: styles.errorContainer, children: _jsxs("p", { className: styles.errorMessage, children: ["Unsupported content format for type: ", contentType] }) })); }; return (_jsx("div", { className: `${styles.contentBody} ${className}`, children: renderContent() })); }; /** * Render string content with basic HTML support */ const renderStringContent = (content) => { // Check if content looks like HTML const htmlRegex = /<[^>]*>/; if (htmlRegex.test(content)) { // For HTML content, use dangerouslySetInnerHTML with caution return (_jsx("div", { dangerouslySetInnerHTML: { __html: sanitizeHtml(content) } })); } // For plain text, preserve line breaks return (_jsx("div", { children: content.split('\n').map((line, index) => (_jsxs(React.Fragment, { children: [line, index < content.split('\n').length - 1 && _jsx("br", {})] }, index))) })); }; /** * Render array content (Portable Text blocks) */ const renderArrayContent = (content) => { return (_jsx("div", { children: content.map((block, index) => renderBlock(block, index)) })); }; /** * Render object content */ const renderObjectContent = (content) => { // Handle Portable Text single block if (content._type && content._type === 'block') { return renderBlock(content, 0); } // Handle objects with specific content fields if (content.html) { return (_jsx("div", { dangerouslySetInnerHTML: { __html: sanitizeHtml(content.html) } })); } if (content.text) { return renderStringContent(content.text); } if (content.markdown) { // Basic markdown support could be added here return renderStringContent(content.markdown); } // Fallback: try to extract any string values const textContent = extractTextFromObject(content); if (textContent) { return renderStringContent(textContent); } return (_jsx("div", { className: styles.errorContainer, children: _jsx("p", { className: styles.errorMessage, children: "Unable to render content object" }) })); }; /** * Render a Portable Text block */ const renderBlock = (block, index) => { if (!block || !block._type) { return null; } const key = block._key || index; switch (block._type) { case 'block': return renderTextBlock(block, key); case 'image': return renderImageBlock(block, key); case 'code': return renderCodeBlock(block, key); default: { // For unknown block types, try to extract text const text = extractTextFromObject(block); if (text) { return _jsx("p", { children: text }, key); } return null; } } }; /** * Render a text block with style support */ const renderTextBlock = (block, key) => { const { style = 'normal', children = [] } = block; // Handle different block styles const content = children.map((child, childIndex) => renderInlineContent(child, `${key}-${childIndex}`)); switch (style) { case 'h1': return _jsx("h1", { children: content }, key); case 'h2': return _jsx("h2", { children: content }, key); case 'h3': return _jsx("h3", { children: content }, key); case 'h4': return _jsx("h4", { children: content }, key); case 'h5': return _jsx("h5", { children: content }, key); case 'h6': return _jsx("h6", { children: content }, key); case 'blockquote': return _jsx("blockquote", { children: content }, key); default: return _jsx("p", { children: content }, key); } }; /** * Render inline content with mark support */ const renderInlineContent = (child, key) => { if (!child || typeof child !== 'object') { return String(child || ''); } const { text = '', marks = [] } = child; if (marks.length === 0) { return text; } // Apply marks (bold, italic, etc.) let element = text; marks.forEach((mark) => { switch (mark) { case 'strong': element = _jsx("strong", { children: element }); break; case 'em': element = _jsx("em", { children: element }); break; case 'code': element = _jsx("code", { children: element }); break; case 'underline': element = _jsx("u", { children: element }); break; default: // For unknown marks, just return the text break; } }); return _jsx(React.Fragment, { children: element }, key); }; /** * Render an image block */ const renderImageBlock = (block, key) => { const { asset, alt, caption } = block; if (!asset) { return null; } // Handle both Sanity asset objects and direct URLs const imageAsset = typeof asset === 'string' ? asset : asset; return (_jsxs("figure", { children: [_jsx(CmsImage, { image: imageAsset, alt: alt || 'Content image', width: 800, height: 600, quality: 85, showFallback: true, sizes: "(max-width: 768px) 100vw, (max-width: 1024px) 80vw, 800px" }), caption && _jsx("figcaption", { children: caption })] }, key)); }; /** * Render a code block */ const renderCodeBlock = (block, key) => { const { code, language } = block; if (!code) { return null; } return (_jsx("pre", { children: _jsx("code", { className: language ? `language-${language}` : undefined, children: code }) }, key)); }; /** * Extract text content from an object */ const extractTextFromObject = (obj) => { if (typeof obj === 'string') { return obj; } if (typeof obj !== 'object' || obj === null) { return ''; } // Try common text fields const textFields = ['text', 'content', 'body', 'description', 'title']; for (const field of textFields) { if (obj[field] && typeof obj[field] === 'string') { return obj[field]; } } // If it's an array, try to extract text from items if (Array.isArray(obj)) { return obj.map(item => extractTextFromObject(item)).filter(Boolean).join(' '); } // Try to get text from children if (obj.children && Array.isArray(obj.children)) { return obj.children.map((child) => child.text || extractTextFromObject(child)).filter(Boolean).join(''); } return ''; }; /** * Basic HTML sanitization (remove dangerous tags/attributes) */ const sanitizeHtml = (html) => { // This is a basic sanitization - in production, use a proper library like DOMPurify return html .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '') .replace(/on\w+="[^"]*"/gi, '') // Remove event handlers .replace(/javascript:/gi, ''); // Remove javascript: URLs }; export default ContentBody;