dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

render.tsx (4146B)


      1 import { Fragment, ReactElement } from 'react'
      2 import { parse } from './parse'
      3 import { InlineToken, Token } from './token'
      4 
      5 export interface RenderOptions {
      6   linkRenderer?: (path: string, text: string) => ReactElement
      7 }
      8 
      9 export function render (tokens: Token[], opts?: RenderOptions): ReactElement[]
     10 export function render (source: string, opts?: RenderOptions): ReactElement[]
     11 export function render (sourceOrTokens: string | Token[], opts?: RenderOptions): ReactElement[] {
     12   if (typeof sourceOrTokens === 'string') {
     13     sourceOrTokens = parse(sourceOrTokens)
     14   }
     15 
     16   const idxRef = { idx: 0 }
     17 
     18   return renderBlock(sourceOrTokens, opts, idxRef)
     19 }
     20 
     21 function renderBlock (tokens: Token[], opts: RenderOptions | undefined, idxRef: { idx: number }): ReactElement[] {
     22   const elements: ReactElement[] = []
     23   for (let i = 0; i < tokens.length; i++) {
     24     const token = tokens[i]
     25 
     26     switch (token.key) {
     27       case 'heading':
     28         elements.push(
     29           <h1 key={idxRef.idx++}>
     30             {renderInline(token.children, opts, idxRef)}
     31           </h1>,
     32         )
     33         break
     34       case 'horizontal-rule':
     35         elements.push(
     36           <hr key={idxRef.idx++}/>,
     37         )
     38         break
     39       case 'list':
     40         elements.push(
     41           <Fragment key={idxRef.idx++}>
     42             {token.listType === 'unordered'
     43               ? <ul>{renderList(token.children, opts, idxRef)}</ul>
     44               : <ol>{renderList(token.children, opts, idxRef)}</ol>}
     45           </Fragment>,
     46         )
     47         break
     48       case 'blockquote':
     49         elements.push(
     50           <blockquote key={idxRef.idx++}>
     51             {renderBlock(token.children, opts, idxRef)}
     52           </blockquote>,
     53         )
     54         break
     55       case 'paragraph':
     56         elements.push(
     57           <p key={idxRef.idx++}>
     58             {renderInline(token.children, opts, idxRef)}
     59           </p>,
     60         )
     61         break
     62       case 'text':
     63         for (let isBegin = true; i < tokens.length; i++) {
     64           const token = tokens[i]
     65           if (token.key !== 'text') {
     66             i--
     67             break
     68           }
     69 
     70           if (isBegin) {
     71             isBegin = false
     72           } else {
     73             elements.push(<br key={idxRef.idx++}/>)
     74           }
     75 
     76           elements.push(
     77             <Fragment key={idxRef.idx++}>
     78               {renderInline(token.children, opts, idxRef)}
     79             </Fragment>,
     80           )
     81         }
     82         break
     83     }
     84   }
     85 
     86   return elements
     87 }
     88 
     89 function renderList (tokens: Token[], opts: RenderOptions | undefined, idxRef: { idx: number }): ReactElement[] {
     90   const elements: ReactElement[] = []
     91   for (const token of tokens) {
     92     if (token.key !== 'list-item') {
     93       continue
     94     }
     95 
     96     elements.push(
     97       <li key={idxRef.idx++}>
     98         {renderBlock(token.children, opts, idxRef)}
     99       </li>,
    100     )
    101   }
    102   return elements
    103 }
    104 
    105 function renderInline (tokens: InlineToken[], opts: RenderOptions | undefined, idxRef: { idx: number }): ReactElement[] {
    106   const elements: ReactElement[] = []
    107   for (const token of tokens) {
    108     switch (token.key) {
    109       case 'text':
    110         elements.push(
    111           <Fragment key={idxRef.idx++}>{token.text}</Fragment>,
    112         )
    113         break
    114       case 'linebreak':
    115         elements.push(
    116           <br key={idxRef.idx++}/>,
    117         )
    118         break
    119       case 'bold':
    120         elements.push(
    121           <b key={idxRef.idx++}>{token.text}</b>,
    122         )
    123         break
    124       case 'italic':
    125         elements.push(
    126           <i key={idxRef.idx++}>{token.text}</i>,
    127         )
    128         break
    129       case 'strikethrough':
    130         elements.push(
    131           <s key={idxRef.idx++}>{token.text}</s>,
    132         )
    133         break
    134       case 'underline':
    135         elements.push(
    136           <u key={idxRef.idx++}>{token.text}</u>,
    137         )
    138         break
    139       case 'code':
    140         elements.push(
    141           <code key={idxRef.idx++}>{token.text}</code>,
    142         )
    143         break
    144       case 'link':
    145         elements.push(
    146           <Fragment key={idxRef.idx++}>
    147             {opts?.linkRenderer?.(token.path, token.text)
    148               ?? <a href={token.path}>{token.text}</a>}
    149           </Fragment>,
    150         )
    151         break
    152     }
    153   }
    154   return elements
    155 }