Skip to content

Commit

Permalink
feat: add mermaid support (TuanManhCao#20)
Browse files Browse the repository at this point in the history
* chore: add mermaid

* feat: add mermaid support
  • Loading branch information
turtton authored Mar 25, 2023
1 parent f5ed8be commit 39c8204
Show file tree
Hide file tree
Showing 9 changed files with 487 additions and 22 deletions.
7 changes: 4 additions & 3 deletions kotlin/src/main/kotlin/Cache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import csstype.ClassName
import external.CodeEncoder
import external.MermaidRender
import external.NextRouter
import io.github.xxfast.kstore.KStore
import io.github.xxfast.kstore.storeOf
Expand Down Expand Up @@ -80,7 +81,7 @@ fun initCache(
val content = readContent(filePath)
if (filePath.contains("\\.md$".toRegex())) {
// Analyze Dependencies
convertMarkdownToReactElement(PathString(filePath).toFileName(postFolder, duplicatedFile), content, dependencyData, fileNameInfo, null, null)
convertMarkdownToReactElement(PathString(filePath).toFileName(postFolder, duplicatedFile), content, dependencyData, fileNameInfo, null, null, null)
}
}

Expand All @@ -95,7 +96,7 @@ fun getCacheData(): Promise<Array<String>> = cacheScope.promise {
}

@JsExport
fun getContent(fileNameString: String, content: String, cacheData: String, router: NextRouter, codeEncoder: CodeEncoder): FC<Props> {
fun getContent(fileNameString: String, content: String, cacheData: String, router: NextRouter, codeEncoder: CodeEncoder, mermaidRender: MermaidRender): FC<Props> {
val (dependingLinks, fileNameInfo) = deserialize<CacheData>(cacheData)
val fileName = FileNameString(fileNameString)
return if (fileName.isImageFile) {
Expand All @@ -108,7 +109,7 @@ fun getContent(fileNameString: String, content: String, cacheData: String, route
}
}
} else {
convertMarkdownToReactElement(fileName, content, dependingLinks, fileNameInfo, router, codeEncoder)
convertMarkdownToReactElement(fileName, content, dependingLinks, fileNameInfo, router, codeEncoder, mermaidRender)
}
}

Expand Down
10 changes: 10 additions & 0 deletions kotlin/src/main/kotlin/CustomString.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@file:OptIn(ExperimentalJsExport::class)

import kotlinx.serialization.Serializable
import org.intellij.markdown.html.entities.EntityConverter

/**
* Refers actual file
Expand Down Expand Up @@ -106,3 +107,12 @@ fun toFileName(slug: String, cacheData: String): String {
}

fun String.removeMdExtension(): String = replace("\\.md$".toRegex(), "")

/**
* Restores [EntityConverter.replacements]
*/
fun String.restoreReplacements(): String =
replace("&quot;", "\"")
.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
9 changes: 9 additions & 0 deletions kotlin/src/main/kotlin/external/MermaidRender.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package external

import react.StateSetter

/**
* covert contents to mermaid element
* resultHtmlSetter == [StateSetter]
*/
typealias MermaidRender = (content: String, resultHtmlSetter: (String) -> Unit) -> Unit
5 changes: 3 additions & 2 deletions kotlin/src/main/kotlin/markdown/Markdown.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import DependencyData
import FileNameInfo
import FileNameString
import external.CodeEncoder
import external.MermaidRender
import external.NextRouter
import markdown.processor.element.createReactElementGeneratingProcessors
import org.intellij.markdown.parser.LinkMap
import org.intellij.markdown.parser.MarkdownParser
import react.FC
import react.Props

fun convertMarkdownToReactElement(fileName: FileNameString, content: String, dependencyData: DependencyData, fileNameInfo: FileNameInfo, router: NextRouter?, codeEncoder: CodeEncoder?): FC<Props> {
fun convertMarkdownToReactElement(fileName: FileNameString, content: String, dependencyData: DependencyData, fileNameInfo: FileNameInfo, router: NextRouter?, codeEncoder: CodeEncoder?, mermaidRender: MermaidRender?): FC<Props> {
val flavour = ObsidianMarkFlavourDescriptor()
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(content)
return ReactElementGenerator(content, parsedTree, createReactElementGeneratingProcessors(LinkMap.buildLinkMap(parsedTree, content), null, fileName, router, codeEncoder, dependencyData, fileNameInfo)).generateElement()
return ReactElementGenerator(content, parsedTree, createReactElementGeneratingProcessors(LinkMap.buildLinkMap(parsedTree, content), null, fileName, router, codeEncoder, mermaidRender, dependencyData, fileNameInfo)).generateElement()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package markdown.processor.element

import csstype.ClassName
import external.CodeEncoder
import external.MermaidRender
import kotlinx.js.jso
import markdown.LeafVisitor
import markdown.TagConsumer
Expand All @@ -12,16 +13,22 @@ import org.intellij.markdown.ast.getTextInNode
import org.intellij.markdown.html.HtmlGenerator
import org.w3c.dom.HTMLElement
import react.ChildrenBuilder
import react.FC
import react.IntrinsicType
import react.Props
import react.dom.html.HTMLAttributes
import react.dom.html.ReactHTML.code
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.pre
import react.useEffect
import react.useState
import restoreReplacements

/**
* Related [org.intellij.markdown.html.CodeFenceGeneratingProvider]
*/
@Suppress("KDocUnresolvedReference")
class CodeFenceElementProcessor<Parent>(private val encoder: CodeEncoder?) :
class CodeFenceElementProcessor<Parent>(private val encoder: CodeEncoder?, private val mermaidRender: MermaidRender?) :
NodeProcessor<IntrinsicType<HTMLAttributes<HTMLElement>>, Parent> where Parent : HTMLAttributes<HTMLElement>, Parent : ChildrenBuilder {
override fun <Visitor> processNode(visitor: Visitor, markdownText: String, node: ASTNode) where Visitor : TagConsumer<IntrinsicType<HTMLAttributes<HTMLElement>>, Parent>, Visitor : org.intellij.markdown.ast.visitors.Visitor, Visitor : LeafVisitor {
val indentBefore = node.getTextInNode(markdownText).commonPrefixWith(" ".repeat(10)).length
Expand Down Expand Up @@ -52,26 +59,48 @@ class CodeFenceElementProcessor<Parent>(private val encoder: CodeEncoder?) :
val html = encoder?.invoke(codes.joinToString(separator = "").removeSurrounding("\n"), language)
?.split('\n')
?.joinToString(separator = "\n") { "<span class=\"code-line\">$it</span>" }
// remove line break
codes.removeFirstOrNull()

val codeElement = if (language == "mermaid" && mermaidRender != null) {
FC {
val content = codes.joinToString(separator = "").restoreReplacements()
val (mermaidHtml, htmlSetter) = useState("")
useEffect(content) {
mermaidRender.invoke(content) { htmlSetter(it) }
}
div {
dangerouslySetInnerHTML = jso {
__html = mermaidHtml
}
}
}
} else {
generateCodeBlock(codes, html, language)
}
visitor.consume {
if (html != null && language.isNotEmpty()) {
className = ClassName("language-$language")
}
code {
if (language.isNotEmpty()) {
className = ClassName("language-$language")
codeElement()
}
visitor.consumeTagClose(pre.unsafeCast<IntrinsicType<HTMLAttributes<HTMLElement>>>())
}

private fun generateCodeBlock(codes: List<String>, html: String?, language: String): FC<Props> = FC {
code {
if (language.isNotEmpty()) {
className = ClassName("language-$language")
}
if (html != null) {
dangerouslySetInnerHTML = jso {
__html = html
}
if (html != null) {
dangerouslySetInnerHTML = jso {
__html = html
}
} else {
codes.removeFirstOrNull()
codes.forEach {
+it
}
} else {
codes.forEach {
+it
}
}
}
visitor.consumeTagClose(pre.unsafeCast<IntrinsicType<HTMLAttributes<HTMLElement>>>())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import DependencyData
import FileNameInfo
import FileNameString
import external.CodeEncoder
import external.MermaidRender
import external.NextRouter
import markdown.processor.EmptyNodeProcessor
import markdown.processor.NodeProcessor
Expand Down Expand Up @@ -44,6 +45,7 @@ fun <Parent> createReactElementGeneratingProcessors(
filename: FileNameString,
router: NextRouter? = null,
encoder: CodeEncoder? = null,
mermaidRender: MermaidRender? = null,
dependencyData: DependencyData = DependencyData(),
fileNameInfo: FileNameInfo = FileNameInfo(),
useSafeLinks: Boolean = true,
Expand Down Expand Up @@ -88,7 +90,7 @@ fun <Parent> createReactElementGeneratingProcessors(

MarkdownElementTypes.LINK_DEFINITION to EmptyNodeProcessor(),

MarkdownElementTypes.CODE_FENCE to CodeFenceElementProcessor(encoder),
MarkdownElementTypes.CODE_FENCE to CodeFenceElementProcessor(encoder, mermaidRender),
MarkdownElementTypes.CODE_BLOCK to CodeBlockElementProcessor(),

MarkdownTokenTypes.HORIZONTAL_RULE to SingleElementProcessor(hr),
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"fuse.js": "^6.6.2",
"hast-util-to-html": "^8.0.4",
"jotai": "^2.0.1",
"mermaid": "^10.0.2",
"next": "^12.1.0",
"path": "^0.12.7",
"react": "^18.2.0",
Expand Down
Loading

0 comments on commit 39c8204

Please sign in to comment.