// Render Edra/BlockNote JSON content to HTML export const renderEdraContent = (content: any): string => { if (!content) return '' // Handle Tiptap format first (has type: 'doc') if (content.type === 'doc' && content.content) { return renderTiptapContent(content) } // Handle both { blocks: [...] } and { content: [...] } formats const blocks = content.blocks || content.content || [] if (!Array.isArray(blocks)) return '' const renderBlock = (block: any): string => { switch (block.type) { case 'heading': { const level = block.attrs?.level || block.level || 1 const headingText = block.content || block.text || '' return `${headingText}` } case 'paragraph': { const paragraphText = block.content || block.text || '' if (!paragraphText) return '


' return `

${paragraphText}

` } case 'bulletList': case 'ul': { const listItems = (block.content || []) .map((item: any) => { const itemText = item.content || item.text || '' return `
  • ${itemText}
  • ` }) .join('') return `` } case 'orderedList': case 'ol': { const orderedItems = (block.content || []) .map((item: any) => { const itemText = item.content || item.text || '' return `
  • ${itemText}
  • ` }) .join('') return `
      ${orderedItems}
    ` } case 'blockquote': { const quoteText = block.content || block.text || '' return `

    ${quoteText}

    ` } case 'codeBlock': case 'code': { const codeText = block.content || block.text || '' const language = block.attrs?.language || block.language || '' return `
    ${codeText}
    ` } case 'image': { const src = block.attrs?.src || block.src || '' const alt = block.attrs?.alt || block.alt || '' const caption = block.attrs?.caption || block.caption || '' return `
    ${alt}${caption ? `
    ${caption}
    ` : ''}
    ` } case 'hr': case 'horizontalRule': return '
    ' default: { // For simple text content const text = block.content || block.text || '' if (text) { return `

    ${text}

    ` } return '' } } } return blocks.map(renderBlock).join('') } // Render Tiptap JSON content to HTML function renderTiptapContent(doc: any): string { if (!doc || !doc.content) return '' const renderNode = (node: any): string => { switch (node.type) { case 'paragraph': { const content = renderInlineContent(node.content || []) if (!content) return '


    ' return `

    ${content}

    ` } case 'heading': { const level = node.attrs?.level || 1 const content = renderInlineContent(node.content || []) return `${content}` } case 'bulletList': { const items = (node.content || []) .map((item: any) => { const itemContent = item.content?.map(renderNode).join('') || '' return `
  • ${itemContent}
  • ` }) .join('') return `` } case 'orderedList': { const items = (node.content || []) .map((item: any) => { const itemContent = item.content?.map(renderNode).join('') || '' return `
  • ${itemContent}
  • ` }) .join('') return `
      ${items}
    ` } case 'listItem': { // List items are handled by their parent list return node.content?.map(renderNode).join('') || '' } case 'blockquote': { const content = node.content?.map(renderNode).join('') || '' return `
    ${content}
    ` } case 'codeBlock': { const language = node.attrs?.language || '' const content = node.content?.[0]?.text || '' return `
    ${escapeHtml(content)}
    ` } case 'image': { const src = node.attrs?.src || '' const alt = node.attrs?.alt || '' const title = node.attrs?.title || '' const width = node.attrs?.width const height = node.attrs?.height const widthAttr = width ? ` width="${width}"` : '' const heightAttr = height ? ` height="${height}"` : '' return `
    ${alt}${title ? `
    ${title}
    ` : ''}
    ` } case 'horizontalRule': { return '
    ' } case 'hardBreak': { return '
    ' } case 'urlEmbed': { const url = node.attrs?.url || '' const title = node.attrs?.title || '' const description = node.attrs?.description || '' const image = node.attrs?.image || '' const favicon = node.attrs?.favicon || '' const siteName = node.attrs?.siteName || '' // Helper to get domain from URL const getDomain = (url: string) => { try { const urlObj = new URL(url) return urlObj.hostname.replace('www.', '') } catch { return '' } } // Helper to extract YouTube video ID const getYouTubeVideoId = (url: string): string | null => { const patterns = [ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/, /youtube\.com\/watch\?.*v=([^&\n?#]+)/ ] for (const pattern of patterns) { const match = url.match(pattern) if (match && match[1]) { return match[1] } } return null } // Check if it's a YouTube URL const isYouTube = /(?:youtube\.com|youtu\.be)/.test(url) const videoId = isYouTube ? getYouTubeVideoId(url) : null if (isYouTube && videoId) { // Render YouTube embed let embedHtml = '
    ' embedHtml += '
    ' embedHtml += `