{children}
+ },
table: ({ children, node, ...properties }) => {
void node
return (
@@ -149,7 +199,11 @@ export const MarkdownRenderer = memo(function MarkdownRenderer({
}: MarkdownRendererProps): React.JSX.Element {
const { t } = useTranslation('app')
const components = useMemo(
- () => markdownComponents(t('markdown.scrollableTable')),
+ () =>
+ markdownComponents(
+ t('markdown.scrollableTable'),
+ t('markdown.mermaidLoading')
+ ),
[t]
)
diff --git a/src/renderer/src/MermaidDiagram.tsx b/src/renderer/src/MermaidDiagram.tsx
new file mode 100644
index 0000000..64485c3
--- /dev/null
+++ b/src/renderer/src/MermaidDiagram.tsx
@@ -0,0 +1,617 @@
+import DOMPurify from 'dompurify'
+import {
+ Code2,
+ Maximize2,
+ RotateCcw,
+ X,
+ ZoomIn,
+ ZoomOut
+} from 'lucide-react'
+import {
+ useEffect,
+ useId,
+ useMemo,
+ useRef,
+ useState
+} from 'react'
+import { createPortal } from 'react-dom'
+import { useTranslation } from 'react-i18next'
+import type { Mermaid, MermaidConfig } from 'mermaid'
+import { activateModalFocus, trapTabFocus } from './dialog-focus'
+import { useDocumentTheme } from './use-document-theme'
+import './markdown-mermaid.css'
+
+const MAX_MERMAID_TEXT_SIZE = 20_000
+const MAX_MERMAID_EDGES = 300
+const MAX_MERMAID_DIMENSION = 4_096
+const MIN_VIEWER_ZOOM = 0.5
+const MAX_VIEWER_ZOOM = 3
+const VIEWER_ZOOM_STEP = 0.25
+const SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
+const unsafeCssPattern =
+ /(?:@import|\bexpression\s*\(|(?:https?|ftp|file|data|javascript|vbscript):|\/\/|-moz-binding|\bbehavior\s*:)/iu
+const cssUrlPattern = /url\s*\(\s*(['"]?)(.*?)\1\s*\)/giu
+
+const forbiddenTags = [
+ 'a',
+ 'animate',
+ 'animatemotion',
+ 'animatetransform',
+ 'discard',
+ 'foreignobject',
+ 'iframe',
+ 'image',
+ 'mpath',
+ 'set',
+ 'script'
+]
+const forbiddenAttributes = ['href', 'xlink:href']
+const sharedSanitizerConfig = {
+ ALLOW_DATA_ATTR: false,
+ ALLOW_UNKNOWN_PROTOCOLS: false,
+ FORBID_ATTR: forbiddenAttributes,
+ FORBID_TAGS: forbiddenTags
+} as const
+const secureConfigKeys = [
+ 'secure',
+ 'securityLevel',
+ 'startOnLoad',
+ 'maxTextSize',
+ 'suppressErrorRendering',
+ 'maxEdges',
+ 'htmlLabels',
+ 'dompurifyConfig',
+ 'theme',
+ 'themeCSS',
+ 'themeVariables',
+ 'fontFamily',
+ 'altFontFamily',
+ 'flowchart'
+]
+
+let mermaidModulePromise: Promise
+ {source}
+
+
+ {source}
+
+ )}
+ {viewerOpen && (
+