Created
April 27, 2023 14:47
-
-
Save FrameMuse/b758c1fd1cca489648532adcfc2813fc to your computer and use it in GitHub Desktop.
Tiny React markdown converter | Converts directly to React elements, so it's completle safe.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { marked } from "marked" | |
import { Lexer, Renderer } from "marked" | |
import { createElement, Key, ReactNode, useState } from "react" | |
// You can use your own external link component. | |
const ExternalLink = "a" | |
/** | |
* | |
* Tries to create `ReactElement`, if text is not `marked`, return as it is. | |
* | |
* @param token - is `marked.Token` from which is created `ReactNode`. | |
* @param key - is a `React Element Key` in the case the function is mapped. | |
*/ | |
function createChildFromToken(token: marked.Token, key: Key): ReactNode { | |
switch (token.type) { | |
// IDK, but it works. | |
// `\n` is not tokenized but `\n\n` is. | |
case "space": | |
return "\n\n" | |
case "html": | |
case "text": | |
// If there is no type, return as it is | |
return token.text | |
case "heading": | |
return createElement("h" + token.depth, { key }, token.tokens.map(createChildFromToken)) | |
case "br": | |
case "hr": | |
case "def": | |
case "table": | |
return createElement(token.type, { key }) | |
case "list": | |
return createElement(token.ordered ? "ol" : "ul", { key }, token.items.map(createChildFromToken)) | |
case "list_item": | |
return createElement("li", { key }, token.tokens.map(createChildFromToken)) | |
case "link": { | |
if (token.href.startsWith("http") || token.href.startsWith("//")) { | |
return createElement(ExternalLink, { key, href: token.href }, token.text) | |
} | |
return createElement(Link, { key, to: token.href }, token.text) | |
} | |
default: | |
return createElement(token.type, { key }, token.text) | |
} | |
} | |
function process(value: string) { | |
const lexer = new Lexer({ smartypants: true, xhtml: true, gfm: true }) | |
const tokens = lexer.lex(value) | |
const children = tokens.flatMap((token, index) => { | |
// I don't why but for the first tokens it always creates paragraphs | |
// So go through the tokens of the first `paragraph` tokens :< | |
if (token.type === "paragraph") { | |
return token.tokens.map(createChildFromToken) | |
} | |
// Key (index) must always be passed | |
return createChildFromToken(token, index) | |
}) | |
// If all children are plain `string`, return as it is | |
if (children.every(child => typeof child === "string")) { | |
return value | |
} | |
return children | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment