// 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 `
${paragraphText}
` } case 'bulletList': case 'ul': { const listItems = (block.content || []) .map((item: any) => { const itemText = item.content || item.text || '' return `` } case 'codeBlock': case 'code': { const codeText = block.content || block.text || '' const language = block.attrs?.language || block.language || '' return `${quoteText}
${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 `${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 '${content}
` } case 'heading': { const level = node.attrs?.level || 1 const content = renderInlineContent(node.content || []) 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 `${text}`
break
case 'link':
const href = mark.attrs?.href || '#'
const target = mark.attrs?.target || '_blank'
text = `${text}`
break
case 'highlight':
text = `${text}`
break
}
})
}
return text
}
// Handle other inline nodes
return renderNode(node)
})
.join('')
}
// Helper to escape HTML
const escapeHtml = (str: string): string => {
return str
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
return doc.content.map(renderNode).join('')
}
// Extract text content from Edra JSON for excerpt
export const getContentExcerpt = (content: any, maxLength = 200): string => {
if (!content) return ''
// Handle Tiptap format first (has type: 'doc')
if (content.type === 'doc' && content.content) {
return extractTiptapText(content, maxLength)
}
// Handle both { blocks: [...] } and { content: [...] } formats
const blocks = content.blocks || content.content || []
if (!Array.isArray(blocks)) return ''
const extractText = (node: any): string => {
// For block-level content
if (node.type && node.content && typeof node.content === 'string') {
return node.content
}
// For inline content with text property
if (node.text) return node.text
// For nested content
if (node.content && Array.isArray(node.content)) {
return node.content.map(extractText).join(' ')
}
return ''
}
const text = blocks.map(extractText).join(' ').trim()
if (text.length <= maxLength) return text
return text.substring(0, maxLength).trim() + '...'
}
// Extract text from Tiptap content
function extractTiptapText(doc: any, maxLength: number): string {
const extractFromNode = (node: any): string => {
if (node.type === 'text') {
return node.text || ''
}
if (node.content && Array.isArray(node.content)) {
return node.content.map(extractFromNode).join(' ')
}
return ''
}
const text = doc.content.map(extractFromNode).join(' ').trim()
if (text.length <= maxLength) return text
return text.substring(0, maxLength).trim() + '...'
}