{"version":3,"file":"useMermaid.mjs","names":[],"sources":["../../src/hooks/useMermaid.ts"],"sourcesContent":["'use client';\n\nimport { useTheme } from 'antd-style';\nimport type { MermaidConfig } from 'mermaid';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Md5 } from 'ts-md5';\n\n// 缓存已验证的图表内容\nexport const MD5_LENGTH_THRESHOLD = 10_000;\n\n// Application-level cache for rendered Mermaid SVG\n// Key: cacheKey string, Value: Promise<string>\nconst mermaidCache = new Map<string, Promise<string>>();\n\n// Maximum cache size to prevent memory leaks\nconst MAX_CACHE_SIZE = 500;\n\n// Clean up old cache entries when limit is reached\nconst cleanupCache = () => {\n  if (mermaidCache.size > MAX_CACHE_SIZE) {\n    // Remove oldest 20% of entries\n    const entriesToRemove = Math.floor(MAX_CACHE_SIZE * 0.2);\n    const keysToRemove = Array.from(mermaidCache.keys()).slice(0, entriesToRemove);\n    for (const key of keysToRemove) {\n      mermaidCache.delete(key);\n    }\n  }\n};\n\n// 懒加载 mermaid 实例\nlet mermaidPromise: Promise<typeof import('mermaid').default | null> | null = null;\n\nexport const loadMermaid = (): Promise<typeof import('mermaid').default | null> => {\n  if (typeof window === 'undefined') return Promise.resolve(null);\n\n  if (!mermaidPromise) {\n    mermaidPromise = import('mermaid').then((mod) => mod.default);\n  }\n\n  return mermaidPromise;\n};\n\n// Helper to create mermaid config\nexport const createMermaidConfig = (\n  theme: ReturnType<typeof useTheme>,\n  customTheme?: MermaidConfig['theme'],\n  // SECURITY: Keep 'strict' as the default. Using 'loose' causes Mermaid to render\n  // node labels via innerHTML, enabling XSS when diagram content is user-controlled.\n  // Only pass 'loose' if your use case explicitly requires HTML labels and you control\n  // the diagram source entirely.\n  securityLevel: MermaidConfig['securityLevel'] = 'strict',\n): MermaidConfig => ({\n  fontFamily: theme.fontFamilyCode,\n  gantt: {\n    useWidth: 1920,\n  },\n  securityLevel,\n  startOnLoad: false,\n  theme: customTheme || (theme.isDarkMode ? 'dark' : 'neutral'),\n  themeVariables: customTheme\n    ? undefined\n    : {\n        errorBkgColor: theme.colorTextDescription,\n        errorTextColor: theme.colorTextDescription,\n        fontFamily: theme.fontFamily,\n        lineColor: theme.colorTextSecondary,\n        mainBkg: theme.colorBgContainer,\n        noteBkgColor: theme.colorInfoBg,\n        noteTextColor: theme.colorInfoText,\n        pie1: theme.geekblue,\n        pie2: theme.colorWarning,\n        pie3: theme.colorSuccess,\n        pie4: theme.colorError,\n        primaryBorderColor: theme.colorBorder,\n        primaryColor: theme.colorBgContainer,\n        primaryTextColor: theme.colorText,\n        secondaryBorderColor: theme.colorInfoBorder,\n        secondaryColor: theme.colorInfoBg,\n        secondaryTextColor: theme.colorInfoText,\n        tertiaryBorderColor: theme.colorSuccessBorder,\n        tertiaryColor: theme.colorSuccessBg,\n        tertiaryTextColor: theme.colorSuccessText,\n        textColor: theme.colorText,\n      },\n});\n\n/**\n * 验证并处理 Mermaid 图表内容 - 优化版本（移除 SWR）\n */\nexport const useMermaid = (\n  content: string,\n  {\n    id,\n    theme: customTheme,\n    securityLevel,\n  }: {\n    id: string;\n    // SECURITY: Defaults to 'strict'. Set to 'loose' only when you fully control\n    // the diagram source and intentionally need HTML rendering in node labels.\n    securityLevel?: MermaidConfig['securityLevel'];\n    theme?: MermaidConfig['theme'];\n  },\n): string => {\n  const theme = useTheme();\n  const [data, setData] = useState<string>('');\n\n  // 提取主题相关配置到 useMemo 中 - 只依赖实际使用的 theme 属性\n  const mermaidConfig = useMemo(\n    () => createMermaidConfig(theme, customTheme, securityLevel),\n    [\n      theme.fontFamilyCode,\n      theme.isDarkMode,\n      theme.colorTextDescription,\n      theme.fontFamily,\n      theme.colorTextSecondary,\n      theme.colorBgContainer,\n      theme.colorInfoBg,\n      theme.colorInfoText,\n      theme.geekblue,\n      theme.colorWarning,\n      theme.colorSuccess,\n      theme.colorError,\n      theme.colorBorder,\n      theme.colorInfoBorder,\n      theme.colorSuccessBorder,\n      theme.colorSuccessBg,\n      theme.colorSuccessText,\n      theme.colorText,\n      customTheme,\n      securityLevel,\n    ],\n  );\n\n  // 为长内容生成哈希键\n  const cacheKey = useMemo((): string => {\n    const hash = content.length < MD5_LENGTH_THRESHOLD ? content : Md5.hashStr(content);\n    return [id, customTheme || (theme.isDarkMode ? 'd' : 'l'), hash].filter(Boolean).join('-');\n  }, [content, id, theme.isDarkMode, customTheme]);\n\n  useEffect(() => {\n    // Check cache first\n    const cachedPromise = mermaidCache.get(cacheKey);\n    if (cachedPromise) {\n      cachedPromise\n        .then((svg) => {\n          setData(svg);\n        })\n        .catch(() => {\n          // Silently handle errors, fallback will be handled in the promise\n        });\n      return;\n    }\n\n    // Create new promise for rendering\n    const renderPromise = (async (): Promise<string> => {\n      try {\n        const mermaidInstance = await loadMermaid();\n        if (!mermaidInstance) return '';\n\n        // 验证语法\n        const isValid = await mermaidInstance.parse(content);\n\n        if (isValid) {\n          // 初始化并渲染\n          mermaidInstance.initialize(mermaidConfig);\n          const { svg } = await mermaidInstance.render(id, content);\n          return svg;\n        } else {\n          throw new Error('Mermaid 语法无效');\n        }\n      } catch (error_) {\n        console.error('Mermaid 解析错误:', error_);\n        return '';\n      }\n    })();\n\n    // Cache the promise\n    mermaidCache.set(cacheKey, renderPromise);\n    cleanupCache();\n\n    // Handle promise result\n    renderPromise\n      .then((svg) => {\n        // Only update if this is still the current cache key\n        if (mermaidCache.get(cacheKey) === renderPromise) {\n          setData(svg);\n        }\n      })\n      .catch(() => {\n        // Remove failed promise from cache\n        if (mermaidCache.get(cacheKey) === renderPromise) {\n          mermaidCache.delete(cacheKey);\n        }\n      });\n  }, [cacheKey, content, id, mermaidConfig]);\n\n  return data;\n};\n"],"mappings":"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"}