Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
6b37fcc0
Commit
6b37fcc0
authored
Mar 02, 2026
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: improve KaTeX and Mermaid error handling and overflow
parent
7f753bf6
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
47 additions
and
45 deletions
+47
-45
MermaidBlock.tsx
web/src/components/MemoContent/MermaidBlock.tsx
+39
-44
index.tsx
web/src/components/MemoContent/index.tsx
+1
-1
index.css
web/src/index.css
+7
-0
No files found.
web/src/components/MemoContent/MermaidBlock.tsx
View file @
6b37fcc0
import
mermaid
from
"mermaid"
;
import
mermaid
from
"mermaid"
;
import
{
useEffect
,
useMemo
,
use
Ref
,
use
State
}
from
"react"
;
import
{
useEffect
,
useMemo
,
useState
}
from
"react"
;
import
{
useAuth
}
from
"@/contexts/AuthContext"
;
import
{
useAuth
}
from
"@/contexts/AuthContext"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
cn
}
from
"@/lib/utils"
;
import
{
getThemeWithFallback
,
resolveTheme
,
setupSystemThemeListener
}
from
"@/utils/theme"
;
import
{
getThemeWithFallback
,
resolveTheme
,
setupSystemThemeListener
}
from
"@/utils/theme"
;
...
@@ -10,82 +10,77 @@ interface MermaidBlockProps {
...
@@ -10,82 +10,77 @@ interface MermaidBlockProps {
className
?:
string
;
className
?:
string
;
}
}
const
getMermaidTheme
=
(
appTheme
:
string
):
"default"
|
"dark"
=>
{
type
MermaidTheme
=
"default"
|
"dark"
;
return
appTheme
===
"default-dark"
?
"dark"
:
"default"
;
const
toMermaidTheme
=
(
appTheme
:
string
):
MermaidTheme
=>
(
appTheme
===
"default-dark"
?
"dark"
:
"default"
);
const
formatErrorMessage
=
(
err
:
unknown
):
string
=>
{
const
msg
=
err
instanceof
Error
?
err
.
message
:
"Failed to render diagram"
;
if
(
/no diagram type detected/i
.
test
(
msg
))
{
return
`
${
msg
}
— check that the diagram type is valid (e.g. sequenceDiagram, classDiagram, erDiagram)`
;
}
return
msg
;
};
};
export
const
MermaidBlock
=
({
children
,
className
}:
MermaidBlockProps
)
=>
{
export
const
MermaidBlock
=
({
children
,
className
}:
MermaidBlockProps
)
=>
{
const
{
userGeneralSetting
}
=
useAuth
();
const
{
userGeneralSetting
}
=
useAuth
();
const
containerRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
[
svg
,
setSvg
]
=
useState
<
string
>
(
""
);
const
[
svg
,
setSvg
]
=
useState
<
string
>
(
""
);
const
[
error
,
setError
]
=
useState
<
string
>
(
""
);
const
[
error
,
setError
]
=
useState
<
string
>
(
""
);
const
[
systemThemeChange
,
setSystemThemeChange
]
=
useState
(
0
);
const
[
systemThemeChange
,
setSystemThemeChange
]
=
useState
(
0
);
const
codeContent
=
extractCodeContent
(
children
);
const
codeContent
=
extractCodeContent
(
children
);
// Get theme preference (reactive via AuthContext)
// Falls back to localStorage or system preference if no user setting
const
themePreference
=
getThemeWithFallback
(
userGeneralSetting
?.
theme
);
const
themePreference
=
getThemeWithFallback
(
userGeneralSetting
?.
theme
);
// Resolve theme to actual value (handles "system" theme + system theme changes)
const
currentTheme
=
useMemo
(()
=>
resolveTheme
(
themePreference
),
[
themePreference
,
systemThemeChange
]);
const
currentTheme
=
useMemo
(()
=>
resolveTheme
(
themePreference
),
[
themePreference
,
systemThemeChange
]);
//
Listen for OS theme changes when using "system" theme preference
//
Re-resolve theme when OS preference changes (only relevant when using "system" theme)
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
themePreference
!==
"system"
)
{
if
(
themePreference
!==
"system"
)
return
;
return
;
return
setupSystemThemeListener
(()
=>
setSystemThemeChange
((
n
)
=>
n
+
1
));
}
return
setupSystemThemeListener
(()
=>
{
setSystemThemeChange
((
prev
)
=>
prev
+
1
);
});
},
[
themePreference
]);
},
[
themePreference
]);
//
Render Mermaid diagram when content or
theme changes
//
Initialize Mermaid when
theme changes
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
codeContent
||
!
containerRef
.
current
)
{
mermaid
.
initialize
({
return
;
startOnLoad
:
false
,
}
theme
:
toMermaidTheme
(
currentTheme
),
securityLevel
:
"strict"
,
fontFamily
:
"inherit"
,
suppressErrorRendering
:
true
,
});
},
[
currentTheme
]);
const
renderDiagram
=
async
()
=>
{
// Render diagram when content or theme changes
try
{
useEffect
(()
=>
{
const
id
=
`mermaid-
${
Math
.
random
().
toString
(
36
).
substring
(
7
)}
`
;
if
(
!
codeContent
)
return
;
const
mermaidTheme
=
getMermaidTheme
(
currentTheme
);
mermaid
.
initialize
({
const
id
=
`mermaid-
${
Math
.
random
().
toString
(
36
).
substring
(
7
)}
`
;
startOnLoad
:
false
,
theme
:
mermaidTheme
,
securityLevel
:
"strict"
,
fontFamily
:
"inherit"
,
});
const
{
svg
:
renderedSvg
}
=
await
mermaid
.
render
(
id
,
codeContent
);
mermaid
.
render
(
id
,
codeContent
)
.
then
(({
svg
:
renderedSvg
})
=>
{
setSvg
(
renderedSvg
);
setSvg
(
renderedSvg
);
setError
(
""
);
setError
(
""
);
}
catch
(
err
)
{
})
.
catch
((
err
)
=>
{
console
.
error
(
"Failed to render mermaid diagram:"
,
err
);
console
.
error
(
"Failed to render mermaid diagram:"
,
err
);
setError
(
err
instanceof
Error
?
err
.
message
:
"Failed to render diagram"
);
setSvg
(
""
);
}
setError
(
formatErrorMessage
(
err
));
};
});
renderDiagram
();
},
[
codeContent
,
currentTheme
]);
},
[
codeContent
,
currentTheme
]);
// If there's an error, fall back to showing the code
if
(
error
)
{
if
(
error
)
{
return
(
return
(
<
div
className=
"w-full"
>
<
div
className=
"w-full"
>
<
div
className=
"text-sm text-destructive mb-2"
>
Mermaid Error:
{
error
}
</
div
>
<
div
className=
"text-sm text-destructive mb-2 whitespace-normal break-words"
>
Mermaid Error:
{
error
}
</
div
>
<
pre
className=
{
className
}
>
<
code
className=
"block language-mermaid whitespace-pre text-sm"
>
{
codeContent
}
</
code
>
<
code
className=
"language-mermaid"
>
{
codeContent
}
</
code
>
</
pre
>
</
div
>
</
div
>
);
);
}
}
if
(
!
svg
)
return
null
;
return
(
return
(
<
div
<
div
ref=
{
containerRef
}
className=
{
cn
(
"mermaid-diagram w-full flex justify-center items-center my-2 overflow-x-auto"
,
className
)
}
className=
{
cn
(
"mermaid-diagram w-full flex justify-center items-center my-2 overflow-x-auto"
,
className
)
}
dangerouslySetInnerHTML=
{
{
__html
:
svg
}
}
dangerouslySetInnerHTML=
{
{
__html
:
svg
}
}
/>
/>
...
...
web/src/components/MemoContent/index.tsx
View file @
6b37fcc0
...
@@ -51,7 +51,7 @@ const MemoContent = (props: MemoContentProps) => {
...
@@ -51,7 +51,7 @@ const MemoContent = (props: MemoContentProps) => {
>
>
<
ReactMarkdown
<
ReactMarkdown
remarkPlugins=
{
[
remarkDisableSetext
,
remarkMath
,
remarkGfm
,
remarkBreaks
,
remarkTag
,
remarkPreserveType
]
}
remarkPlugins=
{
[
remarkDisableSetext
,
remarkMath
,
remarkGfm
,
remarkBreaks
,
remarkTag
,
remarkPreserveType
]
}
rehypePlugins=
{
[
rehypeRaw
,
[
rehypeSanitize
,
SANITIZE_SCHEMA
],
rehypeKatex
]
}
rehypePlugins=
{
[
rehypeRaw
,
[
rehypeSanitize
,
SANITIZE_SCHEMA
],
[
rehypeKatex
,
{
throwOnError
:
false
,
strict
:
false
}]
]
}
components=
{
{
components=
{
{
// Child components consume from MemoViewContext directly
// Child components consume from MemoViewContext directly
input
:
((
inputProps
:
React
.
ComponentProps
<
"input"
>
&
{
node
?:
Element
})
=>
{
input
:
((
inputProps
:
React
.
ComponentProps
<
"input"
>
&
{
node
?:
Element
})
=>
{
...
...
web/src/index.css
View file @
6b37fcc0
...
@@ -25,6 +25,13 @@
...
@@ -25,6 +25,13 @@
border
:
1px
solid
var
(
--border
);
border
:
1px
solid
var
(
--border
);
}
}
/* KaTeX math rendering */
.katex-display
{
overflow-x
:
auto
;
overflow-y
:
hidden
;
max-width
:
100%
;
}
/* Leaflet Popup Overrides */
/* Leaflet Popup Overrides */
.leaflet-popup-content-wrapper
{
.leaflet-popup-content-wrapper
{
border-radius
:
0.5rem
!important
;
border-radius
:
0.5rem
!important
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment