// Default renderers for react-native-hyper-markdown import React from 'react' import { Text, View, Image as RNImage, Pressable } from 'react-native' import type { MarkdownNode } from '../types/ast' import type { RenderContext, RendererFn } from '../types/renderers' // Text node renderer export function renderText( node: MarkdownNode, _key: string | number, _ctx: RenderContext ): React.ReactNode { return node.content ?? '' } // Strong (bold) renderer export function renderStrong( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Emphasis (italic) renderer export function renderEmphasis( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Strikethrough renderer export function renderStrikethrough( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Heading renderer export function renderHeading( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { const level = node.level ?? 1 const styleKey = `heading${level}` as keyof typeof ctx.theme.textStyles const style = ctx.theme.textStyles[styleKey] ?? ctx.theme.textStyles.heading1 return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Paragraph renderer export function renderParagraph( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { // Check if paragraph contains block-level elements (like images) // that can't be nested inside Text const hasBlockChildren = node.children?.some( (child) => child.type === 'image' ) if (hasBlockChildren) { // For paragraphs with images, render as View with Text wrapper for inline content return ( {node.children?.map((child, i) => { if (child.type === 'image') { return ctx.renderNode(child, `${key}-${i}`) } // Wrap non-image children in Text return ( {ctx.renderNode(child, `${key}-${i}-inner`)} ) })} ) } // Standard text-only paragraph return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Link renderer export function renderLink( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { const handlePress = () => { if (ctx.onLinkPress && node.href) { ctx.onLinkPress(node.href, node.title) } } return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Image renderer export function renderImage( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { const handlePress = () => { if (ctx.onImagePress && node.src) { ctx.onImagePress(node.src, node.alt, node.title) } } if (!node.src) { return null } return ( {node.alt && ( {node.alt} )} ) } // Code inline renderer export function renderCodeInline( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.content ?? node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Code block renderer with optional syntax highlighting export function renderCodeBlock( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { const code = node.content ?? node.children?.map((child) => child.content ?? '').join('') ?? '' const language = node.language ?? 'text' // Try to use syntax highlighting if enabled const syntaxOptions = ctx.theme.syntaxHighlighting const isEnabled = syntaxOptions?.enabled !== false if (isEnabled) { try { // Dynamic import to handle optional dependency const { SyntaxHighlighter, isSyntaxHighlightingAvailable, } = require('../components/SyntaxHighlighter') if (isSyntaxHighlightingAvailable()) { return ( {code} ) } } catch { // Fallback to plain text if SyntaxHighlighter is not available } } // Fallback: plain text rendering return ( {code} ) } // Blockquote renderer export function renderBlockquote( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // List renderer export function renderList( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // List item renderer export function renderListItem( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Task list item renderer export function renderTaskListItem( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { const handleToggle = () => { if (ctx.onCheckboxToggle) { ctx.onCheckboxToggle(!node.checked, node) } } return ( {node.checked && } {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Table renderer export function renderTable( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Table row renderer export function renderTableRow( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Table cell renderer export function renderTableCell( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { const textStyle = node.isHeader ? ctx.theme.textStyles.tableHeader : ctx.theme.textStyles.tableCell const alignStyle = node.align ? { textAlign: node.align as 'left' | 'center' | 'right' } : {} return ( {node.children?.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } // Thematic break (hr) renderer export function renderThematicBreak( _node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { return } // Soft break renderer export function renderSoftbreak( _node: MarkdownNode, key: string | number, _ctx: RenderContext ): React.ReactNode { return } // Hard break renderer export function renderHardbreak( _node: MarkdownNode, key: string | number, _ctx: RenderContext ): React.ReactNode { return {'\n'} } // Default renderer for unknown types export function renderDefault( node: MarkdownNode, key: string | number, ctx: RenderContext ): React.ReactNode { if (node.children && node.children.length > 0) { return ( {node.children.map((child, i) => ctx.renderNode(child, `${key}-${i}`))} ) } return node.content ? {node.content} : null } /** * Map of all default renderers by node type */ export const defaultRenderers: Record = { text: renderText, strong: renderStrong, emphasis: renderEmphasis, strikethrough: renderStrikethrough, heading: renderHeading, paragraph: renderParagraph, link: renderLink, image: renderImage, code_inline: renderCodeInline, code_block: renderCodeBlock, blockquote: renderBlockquote, list: renderList, list_item: renderListItem, task_list_item: renderTaskListItem, table: renderTable, table_row: renderTableRow, table_cell: renderTableCell, thematic_break: renderThematicBreak, softbreak: renderSoftbreak, hardbreak: renderHardbreak, } /** * Get default renderer by node type * Returns renderDefault for unknown types */ export function getDefaultRenderer(nodeType: string): RendererFn { return defaultRenderers[nodeType] ?? renderDefault } // Get renderer for node type (backward compatibility) export function getRenderer( nodeType: string ): ( node: MarkdownNode, key: string | number, ctx: RenderContext ) => React.ReactNode { switch (nodeType) { case 'text': return renderText case 'strong': return renderStrong case 'emphasis': return renderEmphasis case 'strikethrough': return renderStrikethrough case 'heading': return renderHeading case 'paragraph': return renderParagraph case 'link': return renderLink case 'image': return renderImage case 'code_inline': return renderCodeInline case 'code_block': return renderCodeBlock case 'blockquote': return renderBlockquote case 'list': return renderList case 'list_item': return renderListItem case 'task_list_item': return renderTaskListItem case 'table': return renderTable case 'table_head': case 'table_body': return renderDefault case 'table_row': return renderTableRow case 'table_cell': return renderTableCell case 'thematic_break': return renderThematicBreak case 'softbreak': return renderSoftbreak case 'hardbreak': return renderHardbreak default: return renderDefault } }