main
ts 41 lines 1.68 KB
Raw
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { createElement as h, Fragment, HTMLAttributes, ReactNode, useMemo } from 'react'
4
5 export const MD_TAGS = {
6 a: 'a',
7 '`': 'code',
8 '*': 'i',
9 '**': 'b',
10 }
11 type OnText = (s: string) => ReactNode
12 // md-inspired formatting, very simplified
13 export function md(text: string | TemplateStringsArray, { html=true, linkTarget='_blank', onText=(x=>x) as OnText }={}) {
14 if (typeof text !== 'string')
15 text = text[0]
16 return replaceStringToReact(text, /(`|_|\*\*?)(.+?)\1|(\n)|\[(.+?)\]\((.+?)\)|(<(\w+?)(?:\s+[^>]*?)?>(?:.*?<\/\7>)?)/g, m =>
17 m[4] ? h(MD_TAGS.a, { href: m[5], target: linkTarget }, onText(m[4]))
18 : m[3] ? h('br')
19 : m[1] ? h((MD_TAGS as any)[ m[1] ] || Fragment, {}, onText(m[2]))
20 : html ? h(Html, {}, m[6]) : m[6],
21 onText)
22 }
23
24 export function replaceStringToReact(text: string, re: RegExp, cb: (match: RegExpExecArray) => ReactNode, onText=(x=>x) as OnText ) {
25 const res = []
26 let last = 0
27 let match
28 while (match = re.exec(text)) { //eslint-disable-line no-cond-assign
29 res.push(onText(text.slice(last, match.index)))
30 res.push(cb(match))
31 last = match.index + match[0].length
32 if (!re.global) break
33 }
34 return h(Fragment, {}, ...res, onText(text.slice(last, Infinity)))
35 }
36
37 export function Html({ children, ...rest }: { children?: string } & HTMLAttributes<any>) {
38 return useMemo(() => !children ? null
39 : h('span', { ...rest, ref: x => x && x.replaceChildren(document.createRange().createContextualFragment(children)) }),
40 [children])
41 }