{"version":3,"file":"useHighlight.mjs","names":["lobeTheme"],"sources":["../../src/hooks/useHighlight.ts"],"sourcesContent":["'use client';\n\nimport {\n  transformerNotationDiff,\n  transformerNotationErrorLevel,\n  transformerNotationFocus,\n  transformerNotationHighlight,\n  transformerNotationWordHighlight,\n} from '@shikijs/transformers';\nimport { type CSSProperties, useEffect, useMemo, useState } from 'react';\nimport type { BuiltinTheme, CodeToHastOptions, ThemedToken } from 'shiki';\nimport { Md5 } from 'ts-md5';\n\nimport { getCodeLanguageByInput } from '@/Highlighter/const';\nimport lobeTheme from '@/Highlighter/theme/lobe-theme';\n\n// Application-level cache to avoid repeated calculations\nexport const MD5_LENGTH_THRESHOLD = 10_000; // Use async MD5 for text exceeding this length\n\nexport type StreamingHighlightResult = {\n  lines: ThemedToken[][];\n  preStyle?: CSSProperties;\n};\n\n// Application-level cache for highlighted HTML\n// Key: cacheKey string, Value: Promise<string>\nconst highlightCache = new Map<string, Promise<string>>();\n\n// Maximum cache size to prevent memory leaks\nconst MAX_CACHE_SIZE = 1000;\n\n// Clean up old cache entries when limit is reached\nconst cleanupCache = () => {\n  if (highlightCache.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(highlightCache.keys()).slice(0, entriesToRemove);\n    for (const key of keysToRemove) {\n      highlightCache.delete(key);\n    }\n  }\n};\n\nexport type ICodeToHtml = (code: string, options: CodeToHastOptions) => Promise<string>;\nexport type ShikiModule = typeof import('shiki');\n\n// Use codeToHtml shorthand for better performance\n// It automatically manages highlighter instances and loads themes/languages on-demand\nlet codeToHtmlPromise: Promise<ICodeToHtml | null> | null = null;\n\nconst loadCodeToHtml = (): Promise<ICodeToHtml | null> => {\n  if (typeof window === 'undefined') return Promise.resolve(null);\n\n  if (!codeToHtmlPromise) {\n    codeToHtmlPromise = import('shiki').then((mod) => mod.codeToHtml ?? null);\n  }\n\n  return codeToHtmlPromise;\n};\n\n// Export shikiModulePromise for useStreamHighlight compatibility\nconst loadShikiModule = (): Promise<ShikiModule | null> => {\n  if (typeof window === 'undefined') return Promise.resolve(null);\n  return import('shiki');\n};\nexport const shikiModulePromise = loadShikiModule();\n\n// Helper function: Safe HTML escaping\nexport const escapeHtml = (str: string): string => {\n  return str\n    .replaceAll('&', '&amp;')\n    .replaceAll('<', '&lt;')\n    .replaceAll('>', '&gt;')\n    .replaceAll('\"', '&quot;')\n    .replaceAll(\"'\", '&#039;');\n};\n\n// Main highlight component - optimized version without SWR\nconst customThemes = {\n  'lobe-theme': lobeTheme,\n};\n\nexport const useHighlight = (\n  text: string,\n  {\n    language,\n    enableTransformer,\n    theme: builtinTheme,\n    streaming,\n  }: { enableTransformer?: boolean; language: string; streaming?: boolean; theme?: BuiltinTheme },\n): string => {\n  // Safely handle language and text with boundary checks\n  const safeText = text ?? '';\n  const lang = (language ?? 'plaintext').toLowerCase();\n\n  // Match supported languages\n  const matchedLanguage = useMemo(() => getCodeLanguageByInput(lang), [lang]);\n\n  // Optimize transformer creation\n  const transformers = useMemo(() => {\n    if (!enableTransformer) return;\n    return [\n      transformerNotationDiff(),\n      transformerNotationHighlight(),\n      transformerNotationWordHighlight(),\n      transformerNotationFocus(),\n      transformerNotationErrorLevel(),\n    ];\n  }, [enableTransformer]);\n\n  // Build cache key\n  const cacheKey = useMemo((): string | null => {\n    if (streaming) return null;\n    // Use hash for long text\n    const hash = safeText.length < MD5_LENGTH_THRESHOLD ? safeText : Md5.hashStr(safeText);\n    return [matchedLanguage, builtinTheme, hash].filter(Boolean).join('-');\n  }, [safeText, matchedLanguage, builtinTheme, streaming]);\n\n  const [data, setData] = useState<string | undefined>();\n\n  useEffect(() => {\n    if (!cacheKey) {\n      setData(undefined);\n      return;\n    }\n\n    // Check cache first\n    const cachedPromise = highlightCache.get(cacheKey);\n    if (cachedPromise) {\n      cachedPromise\n        .then((html) => {\n          setData(html);\n        })\n        .catch(() => {\n          // Silently handle errors, fallback will be handled in the promise\n        });\n      return;\n    }\n\n    // Create new promise for highlighting\n    // Using codeToHtml shorthand: automatically loads themes/languages on-demand\n    const highlightPromise = (async (): Promise<string> => {\n      try {\n        // Try full rendering with transformers\n        const shikiModule = await shikiModulePromise;\n        if (!shikiModule) return safeText;\n\n        const effectiveTheme = builtinTheme || 'lobe-theme';\n\n        // Load custom theme if using slack-dark or slack-ochin\n        if (!builtinTheme && effectiveTheme === 'lobe-theme') {\n          const customTheme = customThemes[effectiveTheme];\n          if (customTheme) {\n            // Use getSingletonHighlighter to load custom theme\n            const highlighter = await shikiModule.getSingletonHighlighter({\n              langs: [matchedLanguage],\n              themes: [customTheme as any],\n            });\n\n            const html = await highlighter.codeToHtml(safeText, {\n              lang: matchedLanguage,\n              theme: effectiveTheme,\n              transformers,\n            });\n\n            return html;\n          }\n        }\n\n        // Fallback to codeToHtml for builtin themes\n        const codeToHtml = await loadCodeToHtml();\n        if (!codeToHtml) return safeText;\n\n        const html = await codeToHtml(safeText, {\n          lang: matchedLanguage,\n          theme: effectiveTheme,\n          transformers,\n        });\n\n        return html;\n      } catch (error_) {\n        console.error('Advanced rendering failed:', error_);\n\n        try {\n          // Try simple rendering (without transformers)\n          const codeToHtml = await loadCodeToHtml();\n          if (!codeToHtml) return safeText;\n          const html = await codeToHtml(safeText, {\n            lang: matchedLanguage,\n            theme: 'lobe-theme',\n          });\n          return html;\n        } catch {\n          // Fallback to plain text\n          const fallbackHtml = `<pre class=\"fallback\"><code>${escapeHtml(safeText)}</code></pre>`;\n          return fallbackHtml;\n        }\n      }\n    })();\n\n    // Cache the promise\n    highlightCache.set(cacheKey, highlightPromise);\n    cleanupCache();\n\n    // Handle promise result\n    highlightPromise\n      .then((html) => {\n        // Only update if this is still the current cache key\n        if (highlightCache.get(cacheKey) === highlightPromise) {\n          setData(html);\n        }\n      })\n      .catch(() => {\n        // Remove failed promise from cache\n        if (highlightCache.get(cacheKey) === highlightPromise) {\n          highlightCache.delete(cacheKey);\n        }\n      });\n  }, [cacheKey, safeText, matchedLanguage, builtinTheme, transformers, customThemes]);\n\n  return data || '';\n};\n"],"mappings":"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"}