{"version":3,"file":"index-CBsAMRpk.mjs","sources":["../../src/renderers/Text/index.tsx"],"sourcesContent":["import { useState, useEffect, Fragment, forwardRef, useCallback, useImperativeHandle, useRef } from 'react';\nimport { fetchTextUtf8, getLanguageFromFileName } from '@eternalheart/file-preview-core';\nimport { useTranslator } from '../../i18n/LocaleContext';\nimport { useFetcher } from '../../RequestContext';\nimport { useShikiHighlight } from '../../hooks/useShikiHighlight';\nimport { RendererError } from '../RendererError';\nimport { WrapText, Code, Eye } from 'lucide-react';\nimport type { RendererHandle } from '../base.types';\nimport type { ToolbarGroup } from '../toolbar.types';\n\ninterface TextRendererProps {\n  url: string;\n  fileName: string;\n}\n\nexport const TextRenderer = forwardRef<RendererHandle, TextRendererProps>(({\n  url,\n  fileName,\n}, ref) => {\n  const t = useTranslator();\n  const fetcher = useFetcher();\n\n  // 内部状态管理\n  const [content, setContent] = useState<string>('');\n  const [loading, setLoading] = useState(true);\n  const [error, setError] = useState<string | null>(null);\n  const [wordWrap, setWordWrap] = useState(true);\n  const [htmlPreview, setHtmlPreview] = useState(false);\n\n  const language = getLanguageFromFileName(fileName);\n  const { lineHtmls } = useShikiHighlight(\n    language !== 'text' ? content : '',\n    language,\n  );\n\n  useEffect(() => {\n    const controller = new AbortController();\n    const loadText = async () => {\n      try {\n        setLoading(true);\n        setError(null);\n        const text = await fetchTextUtf8(url, { fetcher, signal: controller.signal });\n        setContent(text);\n      } catch (err: any) {\n        if (err.name === 'AbortError') return;\n        setError(t('text.load_failed'));\n        console.error(err);\n      } finally {\n        setLoading(false);\n      }\n    };\n\n    loadText();\n    return () => controller.abort();\n  }, [url]);\n\n  // 事件发射器：用于通知主组件工具栏状态变化\n  const listenersRef = useRef<Set<() => void>>(new Set());\n  const notifyToolbarChange = useCallback(() => {\n    listenersRef.current.forEach(listener => listener());\n  }, []);\n\n  // 监听影响工具栏的状态变化\n  useEffect(() => {\n    notifyToolbarChange();\n  }, [wordWrap, notifyToolbarChange]);\n\n  useEffect(() => {\n    notifyToolbarChange();\n  }, [htmlPreview, notifyToolbarChange]);\n\n  // 切换操作\n  const toggleWordWrap = useCallback(() => {\n    setWordWrap(prev => !prev);\n  }, []);\n\n  const toggleHtmlPreview = useCallback(() => {\n    setHtmlPreview(prev => !prev);\n  }, []);\n\n  // 工具栏配置\n  const getToolbarGroups = useCallback((): ToolbarGroup[] => {\n    const groups: ToolbarGroup[] = [\n      {\n        items: [\n          {\n            type: 'button',\n            icon: <WrapText className=\"rfp-w-4 rfp-h-4\" />,\n            tooltip: wordWrap ? t('toolbar.wrap_off') : t('toolbar.wrap_on'),\n            action: toggleWordWrap,\n            active: wordWrap,\n          },\n        ],\n      },\n    ];\n\n    // HTML 文件显示预览按钮\n    if (language === 'html') {\n      groups.push({\n        items: [\n          {\n            type: 'button',\n            icon: htmlPreview\n              ? <Code className=\"rfp-w-4 rfp-h-4\" />\n              : <Eye className=\"rfp-w-4 rfp-h-4\" />,\n            tooltip: htmlPreview ? t('toolbar.source') : t('toolbar.preview'),\n            action: toggleHtmlPreview,\n            active: htmlPreview,\n          },\n        ],\n      });\n    }\n\n    return groups;\n  }, [wordWrap, htmlPreview, language, t, toggleWordWrap, toggleHtmlPreview]);\n\n  // 暴露接口给父组件\n  useImperativeHandle(ref, () => ({\n    getToolbarGroups,\n    onToolbarChange: (listener: () => void) => {\n      listenersRef.current.add(listener);\n      return () => listenersRef.current.delete(listener);\n    },\n  }), [getToolbarGroups]);\n\n  if (loading) {\n    return (\n      <div className=\"rfp-flex rfp-items-center rfp-justify-center rfp-w-full rfp-h-full\">\n        <div className=\"rfp-w-12 rfp-h-12 rfp-border-4 rfp-border-line-strong rfp-border-t-spinner-head rfp-rounded-full rfp-animate-spin\" />\n      </div>\n    );\n  }\n\n  if (error) {\n    return <RendererError message={error} />;\n  }\n\n  // HTML 预览模式\n  if (htmlPreview && (language === 'html')) {\n    return (\n      <div className=\"rfp-w-full rfp-h-full rfp-bg-surface-toolbar\">\n        <iframe\n          srcDoc={content}\n          sandbox=\"allow-same-origin\"\n          className=\"rfp-w-full rfp-h-full rfp-border-0\"\n          title={fileName}\n        />\n      </div>\n    );\n  }\n\n  // 纯文本或高亮未就绪：fallback 到普通 pre\n  if (language === 'text' || lineHtmls.length === 0) {\n    return (\n      <div className=\"rfp-w-full rfp-h-full rfp-overflow-auto rfp-bg-code-bg\">\n        <pre\n          className={`rfp-py-6 rfp-px-4 rfp-text-fg-primary rfp-font-mono rfp-text-sm ${\n            wordWrap ? 'rfp-whitespace-pre-wrap rfp-break-words' : 'rfp-whitespace-pre'\n          }`}\n        >\n          {content}\n        </pre>\n      </div>\n    );\n  }\n\n  // 双列布局：左 gutter（行号），右 code（shiki 高亮）\n  const lines = content.split('\\n');\n  return (\n    <div className=\"rfp-w-full rfp-h-full rfp-overflow-auto rfp-bg-code-bg\">\n      <div\n        className={`rfp-code-block with-line-numbers ${wordWrap ? '' : 'no-wrap'} rfp-w-full`}\n        style={{ gridTemplateRows: `repeat(${lines.length}, auto) minmax(1.5rem, 1fr)` }}\n      >\n        {lines.map((_, i) => (\n          <Fragment key={i}>\n            <span className=\"rfp-code-gutter\">{i + 1}</span>\n            <span\n              className=\"rfp-code-line\"\n              dangerouslySetInnerHTML={{ __html: lineHtmls[i] ?? '' }}\n            />\n          </Fragment>\n        ))}\n        {/* 占位行：撑满剩余高度，让 gutter border 延伸到底部 */}\n        <span className=\"rfp-code-gutter-filler\" />\n        <span className=\"rfp-code-line-filler\" />\n      </div>\n    </div>\n  );\n});\n"],"names":["TextRenderer","forwardRef","url","fileName","ref","useTranslator","fetcher","useFetcher","content","setContent","useState","loading","setLoading","error","setError","wordWrap","setWordWrap","htmlPreview","setHtmlPreview","language","getLanguageFromFileName","lineHtmls","useShikiHighlight","useEffect","controller","text","fetchTextUtf8","err","listenersRef","useRef","notifyToolbarChange","useCallback","listener","toggleWordWrap","prev","toggleHtmlPreview","getToolbarGroups","groups","jsx","WrapText","Code","Eye","useImperativeHandle","RendererError","lines","jsxs","_","i","Fragment"],"mappings":"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"}