import { useCallback, useMemo, useState } from 'react'; import { Alert, alpha, Box, Button, Container, Stack, TextField, Typography, Paper, } from '@mui/material'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import DownloadIcon from '@mui/icons-material/Download'; import CodeIcon from '@mui/icons-material/Code'; import { useTranslation } from 'react-i18next'; import PageHeader from '@/components/PageHeader'; import CopyButton from '@/components/CopyButton'; import SwitchButtonGroup from '@/components/SwitchButtonGroup'; import { useStorageState } from '@/utils/useStorageState'; import type { HtmlToMarkdownPreviewMode } from '@/types/storage'; import { htmlToMarkdown, downloadMarkdownFile, SAMPLE_HTML } from '@/utils/htmlToMarkdown'; const isValidPreviewMode = (val: unknown): val is HtmlToMarkdownPreviewMode => typeof val === 'string' && ['split', 'preview', 'markdown'].includes(val); export default function HtmlToMarkdownPage() { const { t } = useTranslation('htmlToMarkdown'); const [previewMode, setPreviewMode] = useStorageState( 'htmlToMarkdown/previewMode', 'split' as HtmlToMarkdownPreviewMode, isValidPreviewMode, ); const [html, setHtml] = useState(SAMPLE_HTML); const result = useMemo(() => htmlToMarkdown(html), [html]); const error = result.hasError ? (result.error ?? null) : null; const handleModeChange = useCallback( (newMode: HtmlToMarkdownPreviewMode) => { setPreviewMode(newMode); }, [setPreviewMode], ); const handleClear = useCallback(() => { setHtml(''); }, []); const handleDownload = useCallback(() => { if (result.markdown) { downloadMarkdownFile(result.markdown, 'converted.md'); } }, [result.markdown]); const showInput = previewMode !== 'preview'; const showOutput = previewMode !== 'markdown'; return ( } /> {/* 工具栏 */} {/* 错误提示 */} {error && ( {error} )} {/* 主内容区 */} {/* HTML 输入区 */} {showInput && ( alpha(theme.palette.primary.main, 0.04), borderBottom: '1px solid', borderColor: 'divider', display: 'flex', justifyContent: 'space-between', alignItems: 'center', }} > {t('inputLabel')} {t('charCount', { count: html.length })} setHtml(e.target.value)} placeholder={t('inputPlaceholder')} sx={{ flex: 1, '& .MuiOutlinedInput-root': { borderRadius: 0, fontFamily: 'monospace', fontSize: '0.85rem', lineHeight: 1.6, alignItems: 'flex-start', '& fieldset': { border: 'none' }, }, '& .MuiInputBase-input': { py: 2, px: 2, minHeight: 400, }, }} /> )} {/* Markdown 输出区 */} {showOutput && ( alpha(theme.palette.primary.main, 0.04), borderBottom: '1px solid', borderColor: 'divider', display: 'flex', justifyContent: 'space-between', alignItems: 'center', }} > {(previewMode as string) === 'markdown' ? t('markdownOutputLabel') : t('previewLabel')} {t('charCount', { count: result.markdownLength })} {(previewMode as string) === 'markdown' ? ( ) : ( {result.markdown || ( {t('emptyHint')} )} )} )} ); }