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 }