fix(mermaid): Remove aggressive syntax parsing to fix rendering errors

pull/21281/head
xuzijie1995 11 months ago
parent 14dc62a6cc
commit b8318bc9e3

@ -191,18 +191,22 @@ const Flowchart = React.forwardRef((props: {
setIsInitialized(true) setIsInitialized(true)
}, []) }, [])
// Update theme when prop changes // Update theme when prop changes, but allow internal override.
const prevThemeRef = useRef<string>()
useEffect(() => { useEffect(() => {
if (props.theme && props.theme !== currentTheme) { // Only react if the theme prop from the outside has actually changed.
// When the global theme prop changes, we should clear the cache to ensure if (props.theme && props.theme !== prevThemeRef.current) {
// a fresh render. // When the global theme prop changes, it should act as the source of truth,
// overriding any local theme selection.
diagramCache.clear() diagramCache.clear()
setSvgString(null) setSvgString(null)
setCurrentTheme(props.theme) setCurrentTheme(props.theme)
// Per user request, also reset the look to 'classic' to ensure a consistent state. // Reset look to classic for a consistent state after a global change.
setLook('classic') setLook('classic')
} }
}, [props.theme, currentTheme]) // Update the ref to the current prop value for the next render.
prevThemeRef.current = props.theme
}, [props.theme])
const renderFlowchart = useCallback(async (primitiveCode: string) => { const renderFlowchart = useCallback(async (primitiveCode: string) => {
if (!isInitialized || !containerRef.current) { if (!isInitialized || !containerRef.current) {
@ -229,8 +233,9 @@ const Flowchart = React.forwardRef((props: {
const trimmedCode = primitiveCode.trim() const trimmedCode = primitiveCode.trim()
const isGantt = trimmedCode.startsWith('gantt') const isGantt = trimmedCode.startsWith('gantt')
const isMindMap = trimmedCode.startsWith('mindmap') const isMindMap = trimmedCode.startsWith('mindmap')
const isSequence = trimmedCode.startsWith('sequenceDiagram')
if (isGantt || isMindMap) { if (isGantt || isMindMap || isSequence) {
if (isGantt) { if (isGantt) {
finalCode = trimmedCode finalCode = trimmedCode
.split('\n') .split('\n')
@ -256,7 +261,7 @@ const Flowchart = React.forwardRef((props: {
.join('\n') .join('\n')
} }
else { else {
// For gantt and mindmap charts, which have syntax sensitive to whitespace, // For mindmap and sequence charts, which are sensitive to syntax,
// pass the code through directly. // pass the code through directly.
finalCode = trimmedCode finalCode = trimmedCode
} }
@ -372,7 +377,6 @@ const Flowchart = React.forwardRef((props: {
} }
} }
console.log('%c[Mermaid Debug] Mermaid config being applied:', 'color: #ADD8E6;', JSON.parse(JSON.stringify(config)));
try { try {
mermaid.initialize(config) mermaid.initialize(config)
return true return true
@ -437,8 +441,6 @@ const Flowchart = React.forwardRef((props: {
containerRef.current.innerHTML = '' containerRef.current.innerHTML = ''
if (renderTimeoutRef.current) if (renderTimeoutRef.current)
clearTimeout(renderTimeoutRef.current) clearTimeout(renderTimeoutRef.current)
if (codeCompletionCheckRef.current)
clearTimeout(codeCompletionCheckRef.current)
} }
}, []) }, [])
@ -450,11 +452,11 @@ const Flowchart = React.forwardRef((props: {
} }
const toggleTheme = () => { const toggleTheme = () => {
// Clear cache only if theme actually changes const newTheme = currentTheme === 'light' ? 'dark' : 'light'
if (currentTheme !== (currentTheme === 'light' ? Theme.dark : Theme.light)) { // Ensure a full, clean re-render cycle, consistent with global theme change.
diagramCache.clear() diagramCache.clear()
} setSvgString(null)
setCurrentTheme(prevTheme => prevTheme === 'light' ? Theme.dark : Theme.light) setCurrentTheme(newTheme)
} }
// Style classes for theme-dependent elements // Style classes for theme-dependent elements

@ -14,45 +14,13 @@ export const prepareMermaidCode = (mermaidCode: string, style: 'classic' | 'hand
let code = mermaidCode.trim() let code = mermaidCode.trim()
// --- Start of robust sanitization for flowcharts --- // Security: Sanitize against javascript: protocol in click events (XSS vector)
code = code.replace(/(\bclick\s+\w+\s+")javascript:[^"]*(")/g, '$1#$2')
// 1. Ensure a direction is present for `graph` or `flowchart`. // Convenience: Basic BR replacement. This is a common and safe operation.
if (code.startsWith('graph') || code.startsWith('flowchart')) { code = code.replace(/<br\s*\/?>/g, '\n')
const firstLine = code.split('\n')[0].trim();
if (!/^(graph|flowchart)\s+(TD|TB|LR|RL)/.test(firstLine))
code = code.replace(/^(graph|flowchart)/, '$1 TD');
}
// 2. Fix for subgraph titles with quotes, e.g., subgraph "title"
// Converts to the more robust `subgraph id[title]` syntax.
const subgraphReplacer = (match: string, title: string): string => {
// Create a valid ID from the title by removing any character that is not a
// letter, number, or underscore. This supports unicode characters.
const id = title.replace(/[^\p{L}\p{N}_]/gu, '');
// If the ID is empty after sanitization (e.g., title was "---"),
// create a random fallback ID.
const finalId = id || `gen-id-${Math.random().toString(36).substring(2, 7)}`;
return `subgraph ${finalId} [${title}]`;
};
code = code.replace(/subgraph\s+"([^"]+)"/g, subgraphReplacer);
code = code.replace(/subgraph\s+'([^']+)'/g, subgraphReplacer);
// 3. Sanitize against javascript: protocol in click events (XSS vector)
code = code.replace(/(\bclick\s+\w+\s+")javascript:[^"]*(")/g, '$1#$2');
// 4. Fix for edge labels with quotes, e.g., -- "text" -->
code = code.replace(/(--\s*)"([^"]+)"(\s*--[->]?)/g, '$1$2$3');
code = code.replace(/(--\s*)'([^']+)'(\s*--[->]?)/g, '$1$2$3');
// 5. Basic BR replacement. This should be safe.
code = code.replace(/<br\s*\/?>/g, '\n');
// --- End of sanitization ---
let finalCode = code; let finalCode = code
// Hand-drawn style requires some specific clean-up. // Hand-drawn style requires some specific clean-up.
if (style === 'handDrawn') { if (style === 'handDrawn') {

Loading…
Cancel
Save