{"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":";;;;;AAQA,MAAa,uBAAuB;AAIpC,MAAM,+BAAe,IAAI,IAA6B;AAGtD,MAAM,iBAAiB;AAGvB,MAAM,qBAAqB;CACzB,IAAI,aAAa,OAAO,gBAAgB;EAEtC,MAAM,kBAAkB,KAAK,MAAM,iBAAiB,EAAG;EACvD,MAAM,eAAe,MAAM,KAAK,aAAa,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,eAAe;EAC7E,KAAK,MAAM,OAAO,cAChB,aAAa,OAAO,GAAG;CAE3B;AACF;AAGA,IAAI,iBAA0E;AAE9E,MAAa,oBAAsE;CACjF,IAAI,OAAO,WAAW,aAAa,OAAO,QAAQ,QAAQ,IAAI;CAE9D,IAAI,CAAC,gBACH,iBAAiB,OAAO,UAAU,CAAC,MAAM,QAAQ,IAAI,OAAO;CAG9D,OAAO;AACT;AAGA,MAAa,uBACX,OACA,aAKA,gBAAgD,cAC7B;CACnB,YAAY,MAAM;CAClB,OAAO,EACL,UAAU,KACZ;CACA;CACA,aAAa;CACb,OAAO,gBAAgB,MAAM,aAAa,SAAS;CACnD,gBAAgB,cACZ,KAAA,IACA;EACE,eAAe,MAAM;EACrB,gBAAgB,MAAM;EACtB,YAAY,MAAM;EAClB,WAAW,MAAM;EACjB,SAAS,MAAM;EACf,cAAc,MAAM;EACpB,eAAe,MAAM;EACrB,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,oBAAoB,MAAM;EAC1B,cAAc,MAAM;EACpB,kBAAkB,MAAM;EACxB,sBAAsB,MAAM;EAC5B,gBAAgB,MAAM;EACtB,oBAAoB,MAAM;EAC1B,qBAAqB,MAAM;EAC3B,eAAe,MAAM;EACrB,mBAAmB,MAAM;EACzB,WAAW,MAAM;CACnB;AACN;;;;AAKA,MAAa,cACX,SACA,EACE,IACA,OAAO,aACP,oBAQS;CACX,MAAM,QAAQ,SAAS;CACvB,MAAM,CAAC,MAAM,WAAW,SAAiB,EAAE;CAG3C,MAAM,gBAAgB,cACd,oBAAoB,OAAO,aAAa,aAAa,GAC3D;EACE,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN;EACA;CACF,CACF;CAGA,MAAM,WAAW,cAAsB;EACrC,MAAM,OAAO,QAAQ,SAAA,MAAgC,UAAU,IAAI,QAAQ,OAAO;EAClF,OAAO;GAAC;GAAI,gBAAgB,MAAM,aAAa,MAAM;GAAM;EAAI,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;CAC3F,GAAG;EAAC;EAAS;EAAI,MAAM;EAAY;CAAW,CAAC;CAE/C,gBAAgB;EAEd,MAAM,gBAAgB,aAAa,IAAI,QAAQ;EAC/C,IAAI,eAAe;GACjB,cACG,MAAM,QAAQ;IACb,QAAQ,GAAG;GACb,CAAC,CAAC,CACD,YAAY,CAEb,CAAC;GACH;EACF;EAGA,MAAM,iBAAiB,YAA6B;GAClD,IAAI;IACF,MAAM,kBAAkB,MAAM,YAAY;IAC1C,IAAI,CAAC,iBAAiB,OAAO;IAK7B,IAAI,MAFkB,gBAAgB,MAAM,OAAO,GAEtC;KAEX,gBAAgB,WAAW,aAAa;KACxC,MAAM,EAAE,QAAQ,MAAM,gBAAgB,OAAO,IAAI,OAAO;KACxD,OAAO;IACT,OACE,MAAM,IAAI,MAAM,cAAc;GAElC,SAAS,QAAQ;IACf,QAAQ,MAAM,iBAAiB,MAAM;IACrC,OAAO;GACT;EACF,EAAA,CAAG;EAGH,aAAa,IAAI,UAAU,aAAa;EACxC,aAAa;EAGb,cACG,MAAM,QAAQ;GAEb,IAAI,aAAa,IAAI,QAAQ,MAAM,eACjC,QAAQ,GAAG;EAEf,CAAC,CAAC,CACD,YAAY;GAEX,IAAI,aAAa,IAAI,QAAQ,MAAM,eACjC,aAAa,OAAO,QAAQ;EAEhC,CAAC;CACL,GAAG;EAAC;EAAU;EAAS;EAAI;CAAa,CAAC;CAEzC,OAAO;AACT"}