migration wip
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -18,7 +18,7 @@
|
||||
"assets": [],
|
||||
"env": {
|
||||
"__NEXT_BUILD_ID": "development",
|
||||
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "FV9R3duv3e6hZD53ocfPgBrQwGX7rHfUyZRgkqwqFtk="
|
||||
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "0R3AeKRiCnSumpEO3wgP/k8sKq7OCUs1SBp+q3dmhMw="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"node": {},
|
||||
"edge": {},
|
||||
"encryptionKey": "FV9R3duv3e6hZD53ocfPgBrQwGX7rHfUyZRgkqwqFtk="
|
||||
"encryptionKey": "0R3AeKRiCnSumpEO3wgP/k8sKq7OCUs1SBp+q3dmhMw="
|
||||
}
|
||||
@@ -125,7 +125,7 @@
|
||||
/******/
|
||||
/******/ /* webpack/runtime/getFullHash */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.h = () => ("f11be5ba913d1b5f")
|
||||
/******/ __webpack_require__.h = () => ("c80591bbb933a4a4")
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||
|
||||
+4
-14
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ import { getLocalizedPath } from '@/lib/i18n';
|
||||
import { t } from '@/lib/i18n';
|
||||
import { SEO } from '@/components/SEO';
|
||||
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
|
||||
import { ContentRenderer } from '@/components/content/ContentRenderer';
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
@@ -211,10 +212,12 @@ export default async function BlogDetailPage({ params }: PageProps) {
|
||||
})()}
|
||||
|
||||
{/* Article Content */}
|
||||
<div
|
||||
className="prose prose-lg prose-blue max-w-none mb-12"
|
||||
dangerouslySetInnerHTML={{ __html: processedContent }}
|
||||
/>
|
||||
<div className="mb-12">
|
||||
<ContentRenderer
|
||||
content={post.contentHtml}
|
||||
className="prose prose-lg prose-blue"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Article Footer */}
|
||||
<footer className="border-t border-gray-200 pt-8 mt-12">
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getPostsByLocale, getCategoriesByLocale, getMediaById } from '@/lib/dat
|
||||
import { getSiteInfo, t, getLocalizedPath } from '@/lib/i18n';
|
||||
import { SEO } from '@/components/SEO';
|
||||
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
|
||||
import { processHTML } from '@/lib/html-compat';
|
||||
import { ContentRenderer } from '@/components/content/ContentRenderer';
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
@@ -140,10 +140,12 @@ export default async function BlogPage({ params }: PageProps) {
|
||||
{post.title}
|
||||
</Link>
|
||||
</h3>
|
||||
<div
|
||||
className="text-gray-600 line-clamp-3 text-sm mb-4"
|
||||
dangerouslySetInnerHTML={{ __html: processHTML(post.excerptHtml) }}
|
||||
/>
|
||||
<div className="text-gray-600 line-clamp-3 text-sm mb-4">
|
||||
<ContentRenderer
|
||||
content={post.excerptHtml}
|
||||
className="text-gray-600 line-clamp-3 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
href={getLocalizedPath(`/blog/${post.slug}`, locale as 'en' | 'de')}
|
||||
className="inline-flex items-center text-sm font-medium text-blue-600 hover:text-blue-700"
|
||||
@@ -205,10 +207,12 @@ export default async function BlogPage({ params }: PageProps) {
|
||||
{post.title}
|
||||
</Link>
|
||||
</h3>
|
||||
<div
|
||||
className="text-gray-600 mb-3"
|
||||
dangerouslySetInnerHTML={{ __html: processHTML(post.excerptHtml) }}
|
||||
/>
|
||||
<div className="text-gray-600 mb-3">
|
||||
<ContentRenderer
|
||||
content={post.excerptHtml}
|
||||
className="text-gray-600 mb-3"
|
||||
/>
|
||||
</div>
|
||||
<Link
|
||||
href={getLocalizedPath(`/blog/${post.slug}`, locale as 'en' | 'de')}
|
||||
className="inline-flex items-center text-sm font-medium text-blue-600 hover:text-blue-700"
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ResponsiveSection, ResponsiveWrapper, ResponsiveGrid } from '@/componen
|
||||
import { FeaturedImage } from '@/components/content/FeaturedImage';
|
||||
import { Container } from '@/components/ui/Container';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { ContentRenderer } from '@/components/content/ContentRenderer';
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
@@ -117,9 +118,9 @@ export default async function Page({ params }: PageProps) {
|
||||
{page.title}
|
||||
</h1>
|
||||
{page.excerptHtml && (
|
||||
<div
|
||||
<ContentRenderer
|
||||
content={page.excerptHtml}
|
||||
className="text-lg sm:text-xl text-gray-600 leading-relaxed"
|
||||
dangerouslySetInnerHTML={{ __html: processHTML(page.excerptHtml) }}
|
||||
/>
|
||||
)}
|
||||
</ResponsiveWrapper>
|
||||
@@ -127,9 +128,9 @@ export default async function Page({ params }: PageProps) {
|
||||
|
||||
{processedContent && (
|
||||
<ResponsiveWrapper className="bg-white rounded-lg shadow-sm p-6 sm:p-8" container={true} maxWidth="full">
|
||||
<div
|
||||
<ContentRenderer
|
||||
content={contentToDisplay || ''}
|
||||
className="prose prose-lg max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: processedContent }}
|
||||
/>
|
||||
</ResponsiveWrapper>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { notFound } from 'next/navigation'
|
||||
import { getAllCategories, getProductsByCategory } from '@/lib/data'
|
||||
import { ProductList } from '@/components/ProductList'
|
||||
import { Metadata } from 'next'
|
||||
import { ContentRenderer } from '@/components/content/ContentRenderer'
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
@@ -48,9 +49,9 @@ export default async function ProductCategoryPage({ params }: PageProps) {
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<h1 className="text-4xl font-bold mb-6">{category.name}</h1>
|
||||
{category.description && (
|
||||
<div
|
||||
<ContentRenderer
|
||||
content={category.description}
|
||||
className="mb-8 prose max-w-none"
|
||||
dangerouslySetInnerHTML={{ __html: category.description }}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getSiteInfo, t, getLocaleFromPath, getLocalizedPath } from '@/lib/i18n'
|
||||
import { processHTML } from '@/lib/html-compat';
|
||||
import { SEO } from '@/components/SEO';
|
||||
import { LocaleSwitcher } from '@/components/LocaleSwitcher';
|
||||
import { ContentRenderer } from '@/components/content/ContentRenderer';
|
||||
|
||||
interface PageProps {
|
||||
params: {
|
||||
@@ -187,9 +188,9 @@ export default async function ProductDetailPage({ params }: PageProps) {
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-3">
|
||||
{t('products.description', locale as 'en' | 'de')}
|
||||
</h3>
|
||||
<div
|
||||
<ContentRenderer
|
||||
content={product.descriptionHtml || ''}
|
||||
className="prose prose-sm max-w-none text-gray-600"
|
||||
dangerouslySetInnerHTML={{ __html: processedDescription }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -89,8 +89,16 @@ export const ContentRenderer: React.FC<ContentRendererProps> = ({
|
||||
/**
|
||||
* Parse HTML string to React elements
|
||||
* This is a safe parser that only allows specific tags and attributes
|
||||
* Works in both server and client environments
|
||||
*/
|
||||
function parseHTMLToReact(html: string): React.ReactNode {
|
||||
// For server-side rendering, use a simple approach with dangerouslySetInnerHTML
|
||||
// The HTML has already been sanitized by processHTML, so it's safe
|
||||
if (typeof window === 'undefined') {
|
||||
return <div dangerouslySetInnerHTML={{ __html: html }} />;
|
||||
}
|
||||
|
||||
// Client-side: use DOMParser for proper parsing
|
||||
// Define allowed tags and their properties
|
||||
const allowedTags = {
|
||||
div: ['className', 'id', 'style'],
|
||||
|
||||
+104
-11
@@ -113,8 +113,8 @@ function sanitizeHTML(html: string): string {
|
||||
// Remove dangerous attributes
|
||||
processed = processed.replace(/\s+(href|src)\s*=\s*["']\s*javascript:/gi, '');
|
||||
|
||||
// Remove any remaining WordPress shortcode-like content (e.g., [vc_row...])
|
||||
processed = processed.replace(/\[[^\]]*\]/g, '');
|
||||
// Note: Shortcode removal is handled in processShortcodes function
|
||||
// Don't remove shortcodes here as they need to be processed first
|
||||
|
||||
// Allow safe HTML tags
|
||||
const allowedTags = [
|
||||
@@ -170,23 +170,72 @@ function processVcRowShortcodes(html: string): string {
|
||||
const bgImage = extractAttribute(attrs, 'bg_image');
|
||||
const bgColor = extractAttribute(attrs, 'bg_color');
|
||||
const colorOverlay = extractAttribute(attrs, 'color_overlay');
|
||||
const colorOverlay2 = extractAttribute(attrs, 'color_overlay_2');
|
||||
const overlayStrength = extractAttribute(attrs, 'overlay_strength');
|
||||
const enableGradient = extractAttribute(attrs, 'enable_gradient');
|
||||
const gradientDirection = extractAttribute(attrs, 'gradient_direction');
|
||||
const topPadding = extractAttribute(attrs, 'top_padding');
|
||||
const bottomPadding = extractAttribute(attrs, 'bottom_padding');
|
||||
const fullScreen = extractAttribute(attrs, 'full_screen_row_position');
|
||||
const videoBg = extractAttribute(attrs, 'video_bg');
|
||||
const videoMp4 = extractAttribute(attrs, 'video_mp4');
|
||||
const videoWebm = extractAttribute(attrs, 'video_webm');
|
||||
const textAlign = extractAttribute(attrs, 'text_align');
|
||||
const textColor = extractAttribute(attrs, 'text_color');
|
||||
const overflow = extractAttribute(attrs, 'overflow');
|
||||
const equalHeight = extractAttribute(attrs, 'equal_height');
|
||||
const contentPlacement = extractAttribute(attrs, 'content_placement');
|
||||
const columnDirection = extractAttribute(attrs, 'column_direction');
|
||||
const rowBorderRadius = extractAttribute(attrs, 'row_border_radius');
|
||||
const rowBorderRadiusApplies = extractAttribute(attrs, 'row_border_radius_applies');
|
||||
|
||||
// Build style string
|
||||
let style = '';
|
||||
let wrapperClasses = [...classes];
|
||||
|
||||
// Handle text alignment
|
||||
if (textAlign === 'center') wrapperClasses.push('text-center');
|
||||
if (textAlign === 'right') wrapperClasses.push('text-right');
|
||||
if (textAlign === 'left') wrapperClasses.push('text-left');
|
||||
|
||||
// Handle text color
|
||||
if (textColor === 'light') wrapperClasses.push('text-white');
|
||||
|
||||
// Handle overflow
|
||||
if (overflow === 'visible') wrapperClasses.push('overflow-visible');
|
||||
|
||||
// Handle equal height
|
||||
if (equalHeight === 'yes') {
|
||||
wrapperClasses.push('items-stretch');
|
||||
wrapperClasses.push('flex');
|
||||
}
|
||||
|
||||
// Handle content placement
|
||||
if (contentPlacement === 'bottom') wrapperClasses.push('justify-end');
|
||||
if (contentPlacement === 'middle') wrapperClasses.push('justify-center');
|
||||
|
||||
// Handle column direction
|
||||
if (columnDirection === 'column') wrapperClasses.push('flex-col');
|
||||
|
||||
// Handle border radius
|
||||
if (rowBorderRadius === 'none' && rowBorderRadiusApplies === 'bg') {
|
||||
wrapperClasses.push('rounded-none');
|
||||
}
|
||||
|
||||
// Handle background image
|
||||
if (bgImage) {
|
||||
style += `background-image: url(/media/${bgImage}.webp); `;
|
||||
// Try to get media by ID first
|
||||
const mediaId = parseInt(bgImage);
|
||||
if (!isNaN(mediaId)) {
|
||||
// This will be handled by ContentRenderer with data attributes
|
||||
wrapperClasses.push('bg-cover', 'bg-center');
|
||||
style += `background-image: url(/media/${bgImage}.webp); `;
|
||||
} else {
|
||||
// Assume it's a direct URL
|
||||
style += `background-image: url(${bgImage}); `;
|
||||
}
|
||||
style += `background-size: cover; `;
|
||||
style += `background-position: center; `;
|
||||
wrapperClasses.push('bg-cover', 'bg-center');
|
||||
}
|
||||
|
||||
// Handle background color
|
||||
@@ -194,21 +243,63 @@ function processVcRowShortcodes(html: string): string {
|
||||
style += `background-color: ${bgColor}; `;
|
||||
}
|
||||
|
||||
// Handle color overlay
|
||||
if (colorOverlay) {
|
||||
const opacity = overlayStrength ? parseFloat(overlayStrength) : 0.5;
|
||||
// Handle video background
|
||||
if (videoBg === 'use_video' && (videoMp4 || videoWebm)) {
|
||||
// Mark for ContentRenderer to handle
|
||||
wrapperClasses.push('relative', 'overflow-hidden');
|
||||
style += `position: relative; `;
|
||||
|
||||
// Create video background structure
|
||||
const videoAttrs = [];
|
||||
if (videoMp4) videoAttrs.push(`data-video-mp4="${videoMp4}"`);
|
||||
if (videoWebm) videoAttrs.push(`data-video-webm="${videoWebm}"`);
|
||||
videoAttrs.push('data-video-bg="true"');
|
||||
|
||||
return `<div class="${wrapperClasses.join(' ')}" style="${style}" ${videoAttrs.join(' ')}>
|
||||
<div class="relative flex flex-wrap -mx-4 w-full h-full">${content}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Handle color overlay (single or gradient)
|
||||
if (colorOverlay || colorOverlay2 || enableGradient === 'true' || enableGradient === '1') {
|
||||
style += `position: relative; `;
|
||||
wrapperClasses.push('relative');
|
||||
|
||||
// Create overlay div
|
||||
const overlayStyle = `background-color: ${colorOverlay}; opacity: ${opacity};`;
|
||||
let overlayStyle = '';
|
||||
if (colorOverlay2 && enableGradient === 'true') {
|
||||
// Gradient overlay
|
||||
const gradientDir = gradientDirection || 'left_to_right';
|
||||
let gradientCSS = '';
|
||||
switch(gradientDir) {
|
||||
case 'left_to_right':
|
||||
gradientCSS = `linear-gradient(to right, ${colorOverlay}, ${colorOverlay2})`;
|
||||
break;
|
||||
case 'right_to_left':
|
||||
gradientCSS = `linear-gradient(to left, ${colorOverlay}, ${colorOverlay2})`;
|
||||
break;
|
||||
case 'top_to_bottom':
|
||||
gradientCSS = `linear-gradient(to bottom, ${colorOverlay}, ${colorOverlay2})`;
|
||||
break;
|
||||
case 'bottom_to_top':
|
||||
gradientCSS = `linear-gradient(to top, ${colorOverlay}, ${colorOverlay2})`;
|
||||
break;
|
||||
default:
|
||||
gradientCSS = `linear-gradient(to right, ${colorOverlay}, ${colorOverlay2})`;
|
||||
}
|
||||
overlayStyle = `background: ${gradientCSS}; opacity: 0.32;`;
|
||||
} else if (colorOverlay) {
|
||||
// Solid color overlay
|
||||
const opacity = overlayStrength ? parseFloat(overlayStrength) : 0.5;
|
||||
overlayStyle = `background-color: ${colorOverlay}; opacity: ${opacity};`;
|
||||
}
|
||||
|
||||
return `<div class="${wrapperClasses.join(' ')}" style="${style}">
|
||||
<div class="absolute inset-0" style="${overlayStyle}"></div>
|
||||
<div class="relative flex flex-wrap -mx-4 w-full">${content}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Handle gradient
|
||||
// Handle gradient (without overlay)
|
||||
if (enableGradient === 'true' || enableGradient === '1') {
|
||||
const gradientClass = getGradientClass(gradientDirection);
|
||||
wrapperClasses.push(gradientClass);
|
||||
@@ -216,9 +307,11 @@ function processVcRowShortcodes(html: string): string {
|
||||
|
||||
// Handle padding
|
||||
if (topPadding || bottomPadding) {
|
||||
// Convert percentage values to Tailwind arbitrary values
|
||||
const pt = topPadding ? `pt-[${topPadding}]` : '';
|
||||
const pb = bottomPadding ? `pb-[${bottomPadding}]` : '';
|
||||
wrapperClasses.push(pt, pb);
|
||||
if (pt) wrapperClasses.push(pt);
|
||||
if (pb) wrapperClasses.push(pb);
|
||||
}
|
||||
|
||||
// Handle full screen
|
||||
|
||||
Reference in New Issue
Block a user