{"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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"}