fix(products): extract lexical AST and JSON-LD data correctly from productTabs block
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 52s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 52s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
This commit is contained in:
@@ -17,6 +17,7 @@ import Link from 'next/link';
|
|||||||
import { notFound, redirect } from 'next/navigation';
|
import { notFound, redirect } from 'next/navigation';
|
||||||
import ProductEngagementTracker from '@/components/analytics/ProductEngagementTracker';
|
import ProductEngagementTracker from '@/components/analytics/ProductEngagementTracker';
|
||||||
import MDXContent from '@/components/MDXContent';
|
import MDXContent from '@/components/MDXContent';
|
||||||
|
import { lexicalToMarkdown } from '@/lib/mdx-utils';
|
||||||
|
|
||||||
interface ProductPageProps {
|
interface ProductPageProps {
|
||||||
params: Promise<{
|
params: Promise<{
|
||||||
@@ -266,18 +267,7 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
let technicalItems = [];
|
let technicalItems: any[] = [];
|
||||||
try {
|
|
||||||
const tabMatch =
|
|
||||||
typeof product.content === 'string' &&
|
|
||||||
product.content.match(/<Block type="productTabs" data={({.*?})}\s*\/>/s);
|
|
||||||
if (tabMatch && tabMatch[1]) {
|
|
||||||
const data = JSON.parse(tabMatch[1]);
|
|
||||||
technicalItems = data.technicalItems || [];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Ignore JSON parse errors for AST
|
|
||||||
}
|
|
||||||
|
|
||||||
const datasheetPath = getDatasheetPath(productSlug, locale);
|
const datasheetPath = getDatasheetPath(productSlug, locale);
|
||||||
const isFallback = (product.frontmatter as any).isFallback;
|
const isFallback = (product.frontmatter as any).isFallback;
|
||||||
@@ -296,8 +286,24 @@ export default async function ProductPage({ params }: ProductPageProps) {
|
|||||||
if (typeof product.content === 'string') {
|
if (typeof product.content === 'string') {
|
||||||
const tabBlockMatch = product.content.match(/<Block type="productTabs".*?\/>/s);
|
const tabBlockMatch = product.content.match(/<Block type="productTabs".*?\/>/s);
|
||||||
if (tabBlockMatch) {
|
if (tabBlockMatch) {
|
||||||
descriptionContent = product.content.replace(tabBlockMatch[0], '');
|
|
||||||
technicalContent = tabBlockMatch[0];
|
technicalContent = tabBlockMatch[0];
|
||||||
|
descriptionContent = product.content.replace(tabBlockMatch[0], '');
|
||||||
|
|
||||||
|
const dataMatch =
|
||||||
|
technicalContent.match(/data="({.*?})"/s) || technicalContent.match(/data={({.*?})}/s);
|
||||||
|
if (dataMatch && dataMatch[1]) {
|
||||||
|
try {
|
||||||
|
const rawJsonStr = dataMatch[1].replace(/"/g, '"');
|
||||||
|
const data = JSON.parse(rawJsonStr);
|
||||||
|
technicalItems = data.technicalItems || [];
|
||||||
|
|
||||||
|
if (data.content?.root && descriptionContent.trim() === '') {
|
||||||
|
descriptionContent = lexicalToMarkdown(data.content.root);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse productTabs data for page:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,3 +66,81 @@ export function fixMdxDataProps(content: string): string {
|
|||||||
|
|
||||||
return fixedContent;
|
return fixedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function lexicalToMarkdown(node: any, depth = 0): string {
|
||||||
|
if (!node) return '';
|
||||||
|
|
||||||
|
if (typeof node === 'string') return node;
|
||||||
|
|
||||||
|
if (node.type === 'text') {
|
||||||
|
let text = node.text || '';
|
||||||
|
if (node.format === 1) text = `**${text}**`; // Bold
|
||||||
|
if (node.format === 2) text = `*${text}*`; // Italic
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'heading') {
|
||||||
|
const level = parseInt((node.tag || 'h1').replace('h', '')) || 1;
|
||||||
|
const prefix = '#'.repeat(level);
|
||||||
|
const text = (node.children || []).map((c: any) => lexicalToMarkdown(c, depth)).join('');
|
||||||
|
return `${prefix} ${text}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'paragraph') {
|
||||||
|
const text = (node.children || []).map((c: any) => lexicalToMarkdown(c, depth)).join('');
|
||||||
|
return text.trim() ? `${text}\n\n` : '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'list') {
|
||||||
|
const isOrdered = node.tag === 'ol';
|
||||||
|
const items = (node.children || [])
|
||||||
|
.map((c: any, index: number) => {
|
||||||
|
const prefix = isOrdered ? `${index + 1}.` : '-';
|
||||||
|
return `${prefix} ${lexicalToMarkdown(c, depth + 1).trim()}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
return `${items}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'listitem') {
|
||||||
|
return (node.children || []).map((c: any) => lexicalToMarkdown(c, depth)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'quote') {
|
||||||
|
const text = (node.children || []).map((c: any) => lexicalToMarkdown(c, depth)).join('');
|
||||||
|
return `> ${text}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'link') {
|
||||||
|
const url = node.fields?.url || node.url || '';
|
||||||
|
const text = (node.children || []).map((c: any) => lexicalToMarkdown(c, depth)).join('');
|
||||||
|
return `[${text}](${url})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'upload') {
|
||||||
|
const url = node.value?.url || '';
|
||||||
|
const alt = node.value?.alt || node.value?.filename || '';
|
||||||
|
return `\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === 'block') {
|
||||||
|
const blockType = node.fields?.blockType;
|
||||||
|
if (blockType === 'contactSection') {
|
||||||
|
return `<ContactSection showMap={${!!node.fields?.showMap}} showForm={${!!node.fields?.showForm}} showHours={${!!node.fields?.showHours}} />\n\n`;
|
||||||
|
}
|
||||||
|
if (blockType === 'heroSection') {
|
||||||
|
return `<HeroSection badge="${node.fields?.badge || ''}" title="${node.fields?.title || ''}" subtitle="${node.fields?.subtitle || ''}" alignment="${node.fields?.alignment || 'left'}" />\n\n`;
|
||||||
|
}
|
||||||
|
if (blockType === 'features') {
|
||||||
|
return `<FeaturesSection layout="${node.fields?.layout || ''}" />\n\n`;
|
||||||
|
}
|
||||||
|
// Generic MDX block wrapper if unknown
|
||||||
|
return `<Block type="${blockType}" data={${JSON.stringify(node.fields)}} />\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.children && Array.isArray(node.children)) {
|
||||||
|
return node.children.map((c: any) => lexicalToMarkdown(c, depth)).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|||||||
27
tests/lexical.test.ts
Normal file
27
tests/lexical.test.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { lexicalToMarkdown } from '../lib/mdx-utils';
|
||||||
|
|
||||||
|
describe('lexicalToMarkdown', () => {
|
||||||
|
it('should convert lexical AST to markdown', () => {
|
||||||
|
const ast = {
|
||||||
|
type: 'root',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
children: [
|
||||||
|
{ type: 'text', text: 'Hello ' },
|
||||||
|
{ type: 'text', text: 'World', format: 1 }, // bold
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'heading',
|
||||||
|
tag: 'h2',
|
||||||
|
children: [{ type: 'text', text: 'Title' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = lexicalToMarkdown(ast);
|
||||||
|
expect(result).toBe('Hello **World**\n\n## Title\n\n');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user