Skip to content

Instantly share code, notes, and snippets.

@FrameMuse
Created April 27, 2023 14:47
Show Gist options
  • Save FrameMuse/b758c1fd1cca489648532adcfc2813fc to your computer and use it in GitHub Desktop.
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.
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