import { useCallback, useEffect, useMemo, useState } from 'react'; import TextInputArea from '@/components/TextInputArea'; import { useI18n } from '@/utils/chromeI18n'; import CopyButton from '@/components/CopyButton'; import { base64ToText, textToBase64 } from '@/utils/base64Converter'; import SwitchButtonGroup from '@/components/SwitchButtonGroup'; import { useContextMenuData } from '@/utils/useContextMenuData'; import { Button } from '@/components/ui/button'; const IMAGE_DATA_URI_PATTERN = /^\s*data:image\//i; const ERROR_MESSAGE_TO_I18N: Record = { 'Invalid Base64 string': 'invalidBase64', 'Input appears to be binary data (e.g. an image). Please use the Image tab instead.': 'binaryDataDetected', }; interface TextModeProps { onSwitchToImageMode?: () => void; } export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) { const { t } = useI18n('base64Converter'); const [input, setInput] = useState(''); const [debouncedInput, setDebouncedInput] = useState(''); const [direction, setDirection] = useState<'encode' | 'decode'>('encode'); useEffect(() => { const handle = setTimeout(() => { setDebouncedInput(input); }, 200); return () => clearTimeout(handle); }, [input]); const handleContextMenuData = useCallback((payload: string) => { setInput(payload); setDebouncedInput(payload); setDirection('decode'); }, []); useContextMenuData({ featureKey: 'base64Converter', onData: handleContextMenuData }); const conversionPipeline = useMemo(() => { const trimmed = debouncedInput.trim(); if (!trimmed) return { output: '', error: null }; try { if (direction === 'encode') { const result = textToBase64(debouncedInput); return { output: result.output, error: null }; } else { const decoded = base64ToText(trimmed); return { output: decoded, error: null }; } } catch (e) { const message = e instanceof Error ? e.message : ''; const i18nKey = ERROR_MESSAGE_TO_I18N[message]; return { output: '', error: i18nKey ? t(i18nKey) : message || t('conversionFailed'), }; } }, [debouncedInput, direction, t]); const output = conversionPipeline.output; const error = conversionPipeline.error; const placeholder = direction === 'encode' ? t('textInputPlaceholder') : t('base64InputPlaceholder'); const outputLabel = direction === 'encode' ? t('base64Output') : t('textOutput'); const showImageHint = useMemo( () => direction === 'decode' && IMAGE_DATA_URI_PATTERN.test(input), [direction, input], ); const handleDirectionChange = (value: 'encode' | 'decode') => { if (value === direction) return; setDirection(value); setInput(''); setDebouncedInput(''); }; const handleClear = () => { setInput(''); setDebouncedInput(''); }; return (
{/* 受控方向切流中枢 */}
{/* 高性能受控文本输入端 */} {/* 图片 URI 类型劫持警告引导区: - 💡 修复点:彻底废除原生亮色硬编码 hover:bg-blue-100 类名, - 完美向全站 shadcn 暗黑生态看齐,采用标准的 bg-primary/10 混合变体。 */} {showImageHint && (
{t('imageDataUriHint')}
)} {/* 5. 编码/解码核心数据承载流卡片 */} {output && (
{outputLabel}
2000 ? `${output.substring(0, 2000)}...` : output} showClear={false} minRows={4} />
)}
); }