From 39c820440e85dbe3fcf2faf61a8d083a0d64818f Mon Sep 17 00:00:00 2001 From: "turtton(watagame)" Date: Sat, 25 Mar 2023 19:28:21 +0900 Subject: [PATCH] feat: add mermaid support (#20) * chore: add mermaid * feat: add mermaid support --- kotlin/src/main/kotlin/Cache.kt | 7 +- kotlin/src/main/kotlin/CustomString.kt | 10 + .../src/main/kotlin/external/MermaidRender.kt | 9 + kotlin/src/main/kotlin/markdown/Markdown.kt | 5 +- .../element/CodeFenceElementProcessor.kt | 57 ++- .../markdown/processor/element/Processor.kt | 4 +- package.json | 1 + pnpm-lock.yaml | 393 ++++++++++++++++++ src/components/MDContentData.tsx | 23 +- 9 files changed, 487 insertions(+), 22 deletions(-) create mode 100644 kotlin/src/main/kotlin/external/MermaidRender.kt diff --git a/kotlin/src/main/kotlin/Cache.kt b/kotlin/src/main/kotlin/Cache.kt index f0daeb38..916b5f35 100644 --- a/kotlin/src/main/kotlin/Cache.kt +++ b/kotlin/src/main/kotlin/Cache.kt @@ -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 @@ -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) } } @@ -95,7 +96,7 @@ fun getCacheData(): Promise> = cacheScope.promise { } @JsExport -fun getContent(fileNameString: String, content: String, cacheData: String, router: NextRouter, codeEncoder: CodeEncoder): FC { +fun getContent(fileNameString: String, content: String, cacheData: String, router: NextRouter, codeEncoder: CodeEncoder, mermaidRender: MermaidRender): FC { val (dependingLinks, fileNameInfo) = deserialize(cacheData) val fileName = FileNameString(fileNameString) return if (fileName.isImageFile) { @@ -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) } } diff --git a/kotlin/src/main/kotlin/CustomString.kt b/kotlin/src/main/kotlin/CustomString.kt index 629dd084..96784a95 100644 --- a/kotlin/src/main/kotlin/CustomString.kt +++ b/kotlin/src/main/kotlin/CustomString.kt @@ -1,6 +1,7 @@ @file:OptIn(ExperimentalJsExport::class) import kotlinx.serialization.Serializable +import org.intellij.markdown.html.entities.EntityConverter /** * Refers actual file @@ -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(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") diff --git a/kotlin/src/main/kotlin/external/MermaidRender.kt b/kotlin/src/main/kotlin/external/MermaidRender.kt new file mode 100644 index 00000000..919af891 --- /dev/null +++ b/kotlin/src/main/kotlin/external/MermaidRender.kt @@ -0,0 +1,9 @@ +package external + +import react.StateSetter + +/** + * covert contents to mermaid element + * resultHtmlSetter == [StateSetter] + */ +typealias MermaidRender = (content: String, resultHtmlSetter: (String) -> Unit) -> Unit diff --git a/kotlin/src/main/kotlin/markdown/Markdown.kt b/kotlin/src/main/kotlin/markdown/Markdown.kt index 2f083743..db214c9c 100644 --- a/kotlin/src/main/kotlin/markdown/Markdown.kt +++ b/kotlin/src/main/kotlin/markdown/Markdown.kt @@ -4,6 +4,7 @@ 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 @@ -11,8 +12,8 @@ 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 { +fun convertMarkdownToReactElement(fileName: FileNameString, content: String, dependencyData: DependencyData, fileNameInfo: FileNameInfo, router: NextRouter?, codeEncoder: CodeEncoder?, mermaidRender: MermaidRender?): FC { 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() } diff --git a/kotlin/src/main/kotlin/markdown/processor/element/CodeFenceElementProcessor.kt b/kotlin/src/main/kotlin/markdown/processor/element/CodeFenceElementProcessor.kt index 70dffdf1..74baa88d 100644 --- a/kotlin/src/main/kotlin/markdown/processor/element/CodeFenceElementProcessor.kt +++ b/kotlin/src/main/kotlin/markdown/processor/element/CodeFenceElementProcessor.kt @@ -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 @@ -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(private val encoder: CodeEncoder?) : +class CodeFenceElementProcessor(private val encoder: CodeEncoder?, private val mermaidRender: MermaidRender?) : NodeProcessor>, Parent> where Parent : HTMLAttributes, Parent : ChildrenBuilder { override fun processNode(visitor: Visitor, markdownText: String, node: ASTNode) where Visitor : TagConsumer>, Parent>, Visitor : org.intellij.markdown.ast.visitors.Visitor, Visitor : LeafVisitor { val indentBefore = node.getTextInNode(markdownText).commonPrefixWith(" ".repeat(10)).length @@ -52,26 +59,48 @@ class CodeFenceElementProcessor(private val encoder: CodeEncoder?) : val html = encoder?.invoke(codes.joinToString(separator = "").removeSurrounding("\n"), language) ?.split('\n') ?.joinToString(separator = "\n") { "$it" } + // 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>>()) + } + + private fun generateCodeBlock(codes: List, html: String?, language: String): FC = 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>>()) } } diff --git a/kotlin/src/main/kotlin/markdown/processor/element/Processor.kt b/kotlin/src/main/kotlin/markdown/processor/element/Processor.kt index bc58b34b..65782673 100644 --- a/kotlin/src/main/kotlin/markdown/processor/element/Processor.kt +++ b/kotlin/src/main/kotlin/markdown/processor/element/Processor.kt @@ -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 @@ -44,6 +45,7 @@ fun createReactElementGeneratingProcessors( filename: FileNameString, router: NextRouter? = null, encoder: CodeEncoder? = null, + mermaidRender: MermaidRender? = null, dependencyData: DependencyData = DependencyData(), fileNameInfo: FileNameInfo = FileNameInfo(), useSafeLinks: Boolean = true, @@ -88,7 +90,7 @@ fun 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), diff --git a/package.json b/package.json index a080cae4..6556cdb4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 715b54ab..508e2bab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,7 @@ specifiers: 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 postcss: ^8.4.21 @@ -54,6 +55,7 @@ dependencies: fuse.js: 6.6.2 hast-util-to-html: 8.0.4 jotai: 2.0.1_react@18.2.0 + mermaid: 10.0.2 next: 12.3.4_biqbaboplfbrettd7655fr4n2y path: 0.12.7 react: 18.2.0 @@ -138,6 +140,10 @@ packages: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 + /@braintree/sanitize-url/6.0.2: + resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==} + dev: false + /@emotion/babel-plugin/11.10.6: resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==} dependencies: @@ -1179,6 +1185,11 @@ packages: typical: 5.2.0 dev: false + /commander/7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: false + /convert-source-map/1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: false @@ -1187,6 +1198,18 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: false + /cose-base/1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + dependencies: + layout-base: 1.0.2 + dev: false + + /cose-base/2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + dependencies: + layout-base: 2.0.1 + dev: false + /cosmiconfig/7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -1207,6 +1230,24 @@ packages: /csstype/3.1.1: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + /cytoscape-cose-bilkent/4.1.0_cytoscape@3.23.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + dependencies: + cose-base: 1.0.3 + cytoscape: 3.23.0 + dev: false + + /cytoscape-fcose/2.2.0_cytoscape@3.23.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + dependencies: + cose-base: 2.2.0 + cytoscape: 3.23.0 + dev: false + /cytoscape/3.23.0: resolution: {integrity: sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==} engines: {node: '>=0.10'} @@ -1214,6 +1255,265 @@ packages: heap: 0.2.7 lodash: 4.17.21 + /d3-array/3.2.3: + resolution: {integrity: sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==} + engines: {node: '>=12'} + dependencies: + internmap: 2.0.3 + dev: false + + /d3-axis/3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + dev: false + + /d3-brush/3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1_d3-selection@3.0.0 + dev: false + + /d3-chord/3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-color/3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-contour/4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + dev: false + + /d3-delaunay/6.0.3: + resolution: {integrity: sha512-1gPbiMuikAgU/rFcT6WMu17zx0aelw9Hh80go7/TwZQ+/uq8DqqmiNYy+EqPEvTSp/BkJFIpQxjac4Gk/w0zOg==} + engines: {node: '>=12'} + dependencies: + delaunator: 5.0.0 + dev: false + + /d3-dispatch/3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + dev: false + + /d3-drag/3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + dev: false + + /d3-dsv/3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + dev: false + + /d3-ease/3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-fetch/3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + dependencies: + d3-dsv: 3.0.1 + dev: false + + /d3-force/3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + dev: false + + /d3-format/3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + dev: false + + /d3-geo/3.1.0: + resolution: {integrity: sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + dev: false + + /d3-hierarchy/3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate/3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-path/3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + dev: false + + /d3-polygon/3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + dev: false + + /d3-quadtree/3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + dev: false + + /d3-random/3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale-chromatic/3.0.0: + resolution: {integrity: sha512-Lx9thtxAKrO2Pq6OO2Ua474opeziKr279P/TKZsMAhYyNDD3EnCffdbgeSYN5O7m2ByQsxtuP2CSDczNUIZ22g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + dev: false + + /d3-scale/4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + dev: false + + /d3-selection/3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + dev: false + + /d3-shape/3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-time-format/4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + dependencies: + d3-time: 3.1.0 + dev: false + + /d3-time/3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + dev: false + + /d3-timer/3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + + /d3-transition/3.0.1_d3-selection@3.0.0: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + dev: false + + /d3-zoom/3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1_d3-selection@3.0.0 + dev: false + + /d3/7.8.3: + resolution: {integrity: sha512-cAa866AkPXtt4IzRx6nzGf50uerq6VYks7p/tTH94be4QfhUkyTfJfaxXGPOB5ZRVUZmUV1wcM1dism/Ua0lCw==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.3 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.3 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.0.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1_d3-selection@3.0.0 + d3-zoom: 3.0.0 + dev: false + + /dagre-d3-es/7.0.9: + resolution: {integrity: sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==} + dependencies: + d3: 7.8.3 + lodash-es: 4.17.21 + dev: false + + /dayjs/1.11.7: + resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} + dev: false + /decode-named-character-reference/1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: @@ -1229,6 +1529,12 @@ packages: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} dev: true + /delaunator/5.0.0: + resolution: {integrity: sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==} + dependencies: + robust-predicates: 3.0.1 + dev: false + /detective/5.2.1: resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} engines: {node: '>=0.8.0'} @@ -1263,6 +1569,10 @@ packages: csstype: 3.1.1 dev: false + /dompurify/2.4.3: + resolution: {integrity: sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==} + dev: false + /duplexer2/0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} dependencies: @@ -1273,6 +1583,10 @@ packages: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} dev: true + /elkjs/0.8.2: + resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} + dev: false + /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -1477,6 +1791,13 @@ packages: resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} dev: false + /iconv-lite/0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1493,6 +1814,11 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: false + /internmap/2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + dev: false + /is-alphabetical/2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} dev: false @@ -1578,6 +1904,18 @@ packages: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: false + /khroma/2.0.0: + resolution: {integrity: sha512-2J8rDNlQWbtiNYThZRvmMv5yt44ZakX+Tz5ZIp/mN1pt4snn+m030Va5Z4v8xA0cQFDXBwO/8i42xL4QPsVk3g==} + dev: false + + /layout-base/1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + dev: false + + /layout-base/2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + dev: false + /lilconfig/2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} engines: {node: '>=10'} @@ -1587,6 +1925,10 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: false + /lodash-es/4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: false + /lodash.camelcase/4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: false @@ -1606,6 +1948,27 @@ packages: engines: {node: '>= 8'} dev: true + /mermaid/10.0.2: + resolution: {integrity: sha512-slwoB9WdNUT+/W9VhxLYRLZ0Ey12fIE+cAZjm3FmHTD+0F1uoJETfsNbVS1POnvQZhFYzfT6/z6hJZXgecqVBA==} + dependencies: + '@braintree/sanitize-url': 6.0.2 + cytoscape: 3.23.0 + cytoscape-cose-bilkent: 4.1.0_cytoscape@3.23.0 + cytoscape-fcose: 2.2.0_cytoscape@3.23.0 + d3: 7.8.3 + dagre-d3-es: 7.0.9 + dayjs: 1.11.7 + dompurify: 2.4.3 + elkjs: 0.8.2 + khroma: 2.0.0 + lodash-es: 4.17.21 + non-layered-tidy-tree-layout: 2.0.2 + stylis: 4.1.3 + ts-dedent: 2.2.0 + uuid: 9.0.0 + web-worker: 1.2.0 + dev: false + /micromatch/4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -1678,6 +2041,10 @@ packages: resolution: {integrity: sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==} dev: true + /non-layered-tidy-tree-layout/2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + dev: false + /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -1995,6 +2362,10 @@ packages: engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true + /robust-predicates/3.0.1: + resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} + dev: false + /rome/11.0.0: resolution: {integrity: sha512-rRo6JOwpMLc3OkeTDRXkrmrDqnxDvZ75GS4f0jLDBNmRgDXWbu0F8eVnJoRn+VbK2AE7vWvhVOMBjnWowcopkQ==} engines: {node: '>=14.*'} @@ -2015,10 +2386,18 @@ packages: queue-microtask: 1.2.3 dev: true + /rw/1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + dev: false + /safe-buffer/5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} dev: false + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + /scheduler/0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -2152,6 +2531,11 @@ packages: is-number: 7.0.0 dev: true + /ts-dedent/2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + dev: false + /ts-pattern/4.1.4: resolution: {integrity: sha512-Mcw65oUd1w5ktKi5BRwrnz16Otwk9iv7P0dKgvbi+A1albCDgnixohSqNLuFwIp5dzxPmTPm0iDQ6p1ZJr9uGw==} dev: false @@ -2235,6 +2619,11 @@ packages: inherits: 2.0.3 dev: false + /uuid/9.0.0: + resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + hasBin: true + dev: false + /vfile-location/4.0.1: resolution: {integrity: sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw==} dependencies: @@ -2267,6 +2656,10 @@ packages: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} dev: false + /web-worker/1.2.0: + resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} + dev: false + /wordwrapjs/4.0.1: resolution: {integrity: sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==} engines: {node: '>=8.0.0'} diff --git a/src/components/MDContentData.tsx b/src/components/MDContentData.tsx index 0cc21e30..558118a8 100644 --- a/src/components/MDContentData.tsx +++ b/src/components/MDContentData.tsx @@ -1,9 +1,12 @@ -import React from "react"; +import React, { FC, useEffect, useLayoutEffect, useState } from "react"; import Footer from "./Footer"; import { useRouter } from "next/router"; import { deserializeBackLinks, getContent } from "volglass-backend"; import { refractor } from "refractor/lib/all"; import { toHtml } from "hast-util-to-html"; +import dynamic from "next/dynamic"; +import mermaid from "mermaid"; +import { useCurrentTheme } from "./ThemeSwitcher"; function BackLinks({ backLink }: { backLink: string }): JSX.Element { const linkList = deserializeBackLinks(backLink); @@ -41,6 +44,22 @@ function BackLinks({ backLink }: { backLink: string }): JSX.Element { ); } +let currentId = 0; +const createUuid = () => `mermaid-${currentId++}`; +const renderMermaid = + (isDark: boolean) => (content: string, setter: (string) => void) => { + mermaid.initialize({ theme: isDark ? "dark" : "light" }); + mermaid + .render(createUuid(), content) + .then((result) => setter(result.svg)) + .catch((e) => { + setter( + "

Mermaid: Syntax error in graph

", + ); + console.error(e); + }); + }; + export interface MDContentData { fileName: string; content: string; @@ -55,13 +74,13 @@ function MDContent({ backLinks, }: MDContentData): JSX.Element { const router = useRouter(); - const Content = getContent( fileName, `# ${fileName}\n${content}`, cacheData, router, (code, language) => toHtml(refractor.highlight(code, language)), + renderMermaid(useCurrentTheme() === "dark"), ); return (