Skip to content

Commit aba43ee

Browse files
🐛 fix: Fix mermaid on switch theme
1 parent 2e5e202 commit aba43ee

File tree

4 files changed

+91
-88
lines changed

4 files changed

+91
-88
lines changed

src/Mermaid/components/MermaidContainer.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ import { memo, useId } from 'react';
44
import { Center, Flexbox } from 'react-layout-kit';
55

66
import Icon from '@/Icon';
7-
import { useMermaid, useMermaidInit } from '@/hooks/useMermaid';
7+
import { useMermaid } from '@/hooks/useMermaid';
88

99
import { useStyles } from './style';
1010

1111
const MermaidContainer = memo<{ children?: string }>(({ children = '' }) => {
1212
const { styles } = useStyles();
1313
const id = useId();
1414
const mermaidId = kebabCase(`mermaid-${id}`);
15-
useMermaidInit();
1615
const { data, isLoading } = useMermaid(mermaidId, children);
1716

1817
if (!data) return null;

src/hooks/useHighlight.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import languageMap from './languageMap';
1515
export const FALLBACK_LANG = 'txt';
1616

1717
// 应用级缓存,避免重复计算
18-
const highlightCache = new Map<string, string>();
1918
const MD5_LENGTH_THRESHOLD = 10_000; // 超过该长度使用异步MD5
2019

2120
// 颜色替换映射类型
@@ -107,11 +106,6 @@ export const useHighlight = (
107106
return useSWR(
108107
cacheKey,
109108
async (): Promise<string> => {
110-
// 检查内存缓存
111-
if (cacheKey && highlightCache.has(cacheKey)) {
112-
return highlightCache.get(cacheKey)!;
113-
}
114-
115109
try {
116110
// 尝试完整渲染
117111
const codeToHtml = await shikiPromise;
@@ -122,8 +116,6 @@ export const useHighlight = (
122116
transformers,
123117
});
124118

125-
// 缓存结果
126-
if (cacheKey) highlightCache.set(cacheKey, html);
127119
return html;
128120
} catch (error) {
129121
console.error('高级渲染失败:', error);
@@ -135,13 +127,10 @@ export const useHighlight = (
135127
lang: matchedLanguage,
136128
theme: isDarkMode ? 'dark-plus' : 'light-plus',
137129
});
138-
139-
if (cacheKey) highlightCache.set(cacheKey, html);
140130
return html;
141131
} catch {
142132
// 最终降级到纯文本
143133
const fallbackHtml = `<pre class="fallback"><code>${escapeHtml(text)}</code></pre>`;
144-
if (cacheKey) highlightCache.set(cacheKey, fallbackHtml);
145134
return fallbackHtml;
146135
}
147136
}
@@ -157,4 +146,4 @@ export const useHighlight = (
157146

158147
export { default as languageMap } from './languageMap';
159148

160-
export { escapeHtml, highlightCache, loadShiki, MD5_LENGTH_THRESHOLD, shikiPromise };
149+
export { escapeHtml, loadShiki, MD5_LENGTH_THRESHOLD, shikiPromise };

src/hooks/useMermaid.ts

Lines changed: 89 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import useSWR, { SWRResponse } from 'swr';
55
import { Md5 } from 'ts-md5';
66

77
// 缓存已验证的图表内容
8-
const mermaidCache = new Map<string, boolean>();
98
const MD5_LENGTH_THRESHOLD = 10_000;
109

1110
// 懒加载 mermaid 实例
@@ -15,61 +14,10 @@ const loadMermaid = () => {
1514
};
1615
const mermaidPromise = loadMermaid();
1716

18-
/**
19-
* 验证并处理 Mermaid 图表内容
20-
*/
21-
export const useMermaid = (id: string, content: string): SWRResponse<string, Error> => {
22-
// 用于存储最近一次有效的内容
23-
const [validContent, setValidContent] = useState<string>('');
24-
25-
// 为长内容生成哈希键
26-
const cacheKey = useMemo((): string => {
27-
const hash = content.length < MD5_LENGTH_THRESHOLD ? content : Md5.hashStr(content);
28-
return `mermaid-${id}-${hash}`;
29-
}, [content, id]);
30-
31-
return useSWR(
32-
cacheKey,
33-
async (): Promise<string> => {
34-
// 检查缓存中是否已验证过
35-
if (mermaidCache.has(cacheKey)) return validContent;
36-
37-
try {
38-
const mermaidInstance = await mermaidPromise;
39-
if (!mermaidInstance) return content;
40-
41-
// 验证语法
42-
const isValid = await mermaidInstance.parse(content);
43-
44-
if (isValid) {
45-
// 更新有效内容状态
46-
const { svg } = await mermaidInstance.render(id, content);
47-
setValidContent(svg);
48-
// 缓存验证结果
49-
mermaidCache.set(cacheKey, true);
50-
return svg;
51-
} else {
52-
throw new Error('Mermaid 语法无效');
53-
}
54-
} catch (error) {
55-
console.error('Mermaid 解析错误:', error);
56-
// 返回最近一次有效的内容,或空字符串
57-
return validContent || '';
58-
}
59-
},
60-
{
61-
dedupingInterval: 3000,
62-
errorRetryCount: 2,
63-
revalidateOnFocus: false,
64-
revalidateOnReconnect: false,
65-
},
66-
);
67-
};
68-
6917
/**
7018
* 初始化 Mermaid 配置
7119
*/
72-
export const useMermaidInit = (): void => {
20+
export const useMermaidInit = () => {
7321
const theme = useTheme();
7422

7523
// 提取主题相关配置到 useMemo 中
@@ -120,3 +68,91 @@ export const useMermaidInit = (): void => {
12068
initMermaid();
12169
}, [mermaidConfig]);
12270
};
71+
72+
/**
73+
* 验证并处理 Mermaid 图表内容
74+
*/
75+
export const useMermaid = (id: string, content: string): SWRResponse<string, Error> => {
76+
const theme = useTheme();
77+
// 用于存储最近一次有效的内容
78+
const [validContent, setValidContent] = useState<string>('');
79+
80+
// 提取主题相关配置到 useMemo 中
81+
const mermaidConfig: MermaidConfig = useMemo(
82+
() => ({
83+
fontFamily: theme.fontFamilyCode,
84+
gantt: {
85+
useWidth: 1920,
86+
},
87+
securityLevel: 'loose',
88+
startOnLoad: false,
89+
theme: theme.isDarkMode ? 'dark' : 'neutral',
90+
themeVariables: {
91+
errorBkgColor: theme.colorTextDescription,
92+
errorTextColor: theme.colorTextDescription,
93+
fontFamily: theme.fontFamily,
94+
fontSize: 14,
95+
lineColor: theme.colorTextSecondary,
96+
mainBkg: theme.colorBgContainer,
97+
noteBkgColor: theme.colorInfoBg,
98+
noteTextColor: theme.colorInfoText,
99+
pie1: theme.geekblue,
100+
pie2: theme.colorWarning,
101+
pie3: theme.colorSuccess,
102+
pie4: theme.colorError,
103+
primaryBorderColor: theme.colorBorder,
104+
primaryColor: theme.colorBgContainer,
105+
primaryTextColor: theme.colorText,
106+
secondaryBorderColor: theme.colorInfoBorder,
107+
secondaryColor: theme.colorInfoBg,
108+
secondaryTextColor: theme.colorInfoText,
109+
tertiaryBorderColor: theme.colorSuccessBorder,
110+
tertiaryColor: theme.colorSuccessBg,
111+
tertiaryTextColor: theme.colorSuccessText,
112+
textColor: theme.colorText,
113+
},
114+
}),
115+
[theme.isDarkMode],
116+
);
117+
118+
// 为长内容生成哈希键
119+
const cacheKey = useMemo((): string => {
120+
const hash = content.length < MD5_LENGTH_THRESHOLD ? content : Md5.hashStr(content);
121+
return `${id}-${theme.isDarkMode ? 'd' : 'l'}-${hash}`;
122+
}, [content, id, theme.isDarkMode]);
123+
124+
return useSWR(
125+
cacheKey,
126+
async (): Promise<string> => {
127+
// 检查缓存中是否已验证过
128+
try {
129+
const mermaidInstance = await mermaidPromise;
130+
if (!mermaidInstance) return content;
131+
132+
// 验证语法
133+
const isValid = await mermaidInstance.parse(content);
134+
135+
if (isValid) {
136+
// 更新有效内容状态
137+
mermaidInstance.initialize(mermaidConfig);
138+
const { svg } = await mermaidInstance.render(id, content);
139+
setValidContent(svg);
140+
// 缓存验证结果
141+
return svg;
142+
} else {
143+
throw new Error('Mermaid 语法无效');
144+
}
145+
} catch (error) {
146+
console.error('Mermaid 解析错误:', error);
147+
// 返回最近一次有效的内容,或空字符串
148+
return validContent || '';
149+
}
150+
},
151+
{
152+
dedupingInterval: 3000,
153+
errorRetryCount: 2,
154+
revalidateOnFocus: false,
155+
revalidateOnReconnect: false,
156+
},
157+
);
158+
};

tests/useHighlight.test.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { describe, expect, it } from 'vitest';
33
import {
44
MD5_LENGTH_THRESHOLD,
55
escapeHtml,
6-
highlightCache,
76
loadShiki,
87
shikiPromise,
98
} from '../src/hooks/useHighlight';
@@ -43,26 +42,6 @@ describe('escapeHtml', () => {
4342
});
4443
});
4544

46-
describe('highlightCache', () => {
47-
it('should be initialized as empty Map', () => {
48-
expect(highlightCache).toBeInstanceOf(Map);
49-
expect(highlightCache.size).toBe(0);
50-
});
51-
52-
it('should store and retrieve values', () => {
53-
highlightCache.set('test-key', 'test-value');
54-
expect(highlightCache.get('test-key')).toBe('test-value');
55-
highlightCache.clear();
56-
});
57-
58-
it('should handle overwriting existing values', () => {
59-
highlightCache.set('test-key', 'value1');
60-
highlightCache.set('test-key', 'value2');
61-
expect(highlightCache.get('test-key')).toBe('value2');
62-
highlightCache.clear();
63-
});
64-
});
65-
6645
describe('MD5_LENGTH_THRESHOLD', () => {
6746
it('should be set to 10000', () => {
6847
expect(MD5_LENGTH_THRESHOLD).toBe(10_000);

0 commit comments

Comments
 (0)