| 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 | } |