UNPKG

blocks-html-renderer

Version:

Render the content of Strapi's Blocks rich text editor as HTML in your frontend.

1 lines 8.15 kB
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["\n/* -------------------------------------------------------------------------------------------------\n * TypeScript types\n * -----------------------------------------------------------------------------------------------*/\n\ninterface TextInlineNode {\n type: 'text';\n text: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n code?: boolean;\n}\n\ninterface LinkInlineNode {\n type: 'link';\n url: string;\n children: TextInlineNode[];\n}\n\ninterface ListItemInlineNode {\n type: 'list-item';\n children: DefaultInlineNode[];\n}\n\n// Inline node types\ntype DefaultInlineNode = TextInlineNode | LinkInlineNode;\ntype NonTextInlineNode = Exclude<DefaultInlineNode, TextInlineNode> | ListItemInlineNode;\n\ninterface ParagraphBlockNode {\n type: 'paragraph';\n children: DefaultInlineNode[];\n}\n\ninterface QuoteBlockNode {\n type: 'quote';\n children: DefaultInlineNode[];\n}\n\ninterface CodeBlockNode {\n type: 'code';\n children: DefaultInlineNode[];\n}\n\ninterface HeadingBlockNode {\n type: 'heading';\n level: 1 | 2 | 3 | 4 | 5 | 6;\n children: DefaultInlineNode[];\n}\n\ninterface ListBlockNode {\n type: 'list';\n format: 'ordered' | 'unordered';\n children: (ListItemInlineNode | ListBlockNode)[];\n indentLevel?: number;\n}\n\ninterface ImageBlockNode {\n type: 'image';\n image: {\n name: string;\n alternativeText?: string | null;\n url: string;\n caption?: string | null;\n width: number;\n height: number;\n formats?: Record<string, unknown>;\n hash: string;\n ext: string;\n mime: string;\n size: number;\n previewUrl?: string | null;\n provider: string;\n provider_metadata?: unknown | null;\n createdAt: string;\n updatedAt: string;\n };\n children: [{ type: 'text'; text: '' }];\n}\n\n// Block node types\ntype RootNode =\n | ParagraphBlockNode\n | QuoteBlockNode\n | CodeBlockNode\n | HeadingBlockNode\n | ListBlockNode\n | ImageBlockNode;\n\nexport type Node = RootNode | NonTextInlineNode;\n\n/* -------------------------------------------------------------------------------------------------\n * Renderer\n * -----------------------------------------------------------------------------------------------*/\n\nconst renderChildren = (children: (DefaultInlineNode | ListItemInlineNode | ListBlockNode)[]): string => {\n let html = '';\n children.forEach((child) => {\n if (child.type === 'text') {\n html += renderText(child);\n }\n else if (child.type === 'link') {\n html += `<a href=\"${child.url}\">${renderChildren(child.children)}</a>`;\n }\n else if (child.type === 'list-item') {\n html += `<li>${renderChildren(child.children)}</li>`;\n }\n else if (child.type === 'list') {\n html += renderList(child);\n }\n });\n return html;\n}\n\nconst renderText = (node: TextInlineNode): string => {\n let html = node.text;\n if (node.bold) {\n html = `<strong>${html}</strong>`;\n }\n\n if (node.italic) {\n html = `<em>${html}</em>`;\n }\n\n if (node.underline) {\n html = `<u>${html}</u>`;\n }\n\n if (node.strikethrough) {\n html = `<s>${html}</s>`;\n }\n\n if (node.code) {\n html = `<code>${html}</code>`;\n }\n\n return html;\n}\n\nconst renderList = (node: ListBlockNode): string => {\n const items: string[] = [];\n let pendingPieces: string[] | null = null;\n\n const flushPending = () => {\n if (pendingPieces && pendingPieces.length > 0) {\n items.push(`<li>${pendingPieces.join('')}</li>`);\n pendingPieces = null;\n }\n };\n\n node.children.forEach((child) => {\n if (child.type === 'list-item') {\n flushPending();\n pendingPieces = [renderChildren(child.children)];\n } else if (child.type === 'list') {\n const nestedHtml = renderList(child);\n if (pendingPieces) {\n pendingPieces.push(nestedHtml);\n } else {\n items.push(`<li>${nestedHtml}</li>`);\n }\n }\n });\n\n flushPending();\n\n const tag = node.format === 'ordered' ? 'ol' : 'ul';\n return `<${tag}>${items.join('')}</${tag}>`;\n};\n\nexport const renderBlock = (block: Node[]): string => {\n if (!block) return '';\n let html = '';\n block.forEach((block) => {\n if (block.type === 'paragraph') {\n html += `<p>${renderChildren(block.children)}</p>`;\n }\n else if (block.type === 'quote') {\n html += `<blockquote>${renderChildren(block.children)}</blockquote>`;\n }\n else if (block.type === 'code') {\n html += `<pre><code>${renderChildren(block.children)}</code></pre>`;\n }\n else if (block.type === 'heading') {\n switch (block.level) {\n case 1:\n html += `<h1>${renderChildren(block.children)}</h1>`; return;\n case 2:\n html += `<h2>${renderChildren(block.children)}</h2>`; return;\n case 3:\n html += `<h3>${renderChildren(block.children)}</h3>`; return;\n case 4:\n html += `<h4>${renderChildren(block.children)}</h4>`; return;\n case 5:\n html += `<h5>${renderChildren(block.children)}</h5>`; return;\n case 6:\n html += `<h6>${renderChildren(block.children)}</h6>`; return;\n }\n }\n else if (block.type === 'link') {\n html += `<a href=\"${block.url}\">${renderChildren(block.children)}</a>`;\n }\n else if (block.type === 'list') {\n html += renderList(block);\n }\n else if (block.type === 'list-item') {\n html += `<li>${renderChildren(block.children)}</li>`;\n }\n else if (block.type === 'image') {\n html += `<img src=\"${block.image.url}\" alt=\"${block.image.alternativeText || undefined}\" />`;\n }\n });\n return html;\n};\n"],"mappings":"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","names":["block"]}