@@ -132,12 +164,14 @@ export default function HtmlToMarkdownPage() {
+
{result.markdown || (
- {t('emptyHint')}
+
+ {t('emptyHint')}
+
)}
)}
diff --git a/pages/MarkdownToHtml/index.tsx b/pages/MarkdownToHtml/index.tsx
index a0e5ac0..3e9b346 100644
--- a/pages/MarkdownToHtml/index.tsx
+++ b/pages/MarkdownToHtml/index.tsx
@@ -1,97 +1,83 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { Code, Trash2, Printer, Download } from 'lucide-react';
+import { Code, Download, Printer, Trash2 } from 'lucide-react';
import { useLazyTranslation } from '@/utils/useLazyTranslation';
import PageHeader from '@/components/PageHeader';
import CopyButton from '@/components/CopyButton';
import SwitchButtonGroup from '@/components/SwitchButtonGroup';
+import { Button } from '@/components/ui/button'; // 💡 1. 全面回归原子组件规范
import { useStorageState } from '@/utils/useStorageState';
import type { MarkdownToHtmlPreviewMode } from '@/types/storage';
import {
- markdownToHtml,
- wrapHtmlDocument,
downloadHtmlFile,
+ markdownToHtml,
printHtml,
SAMPLE_MARKDOWN,
+ wrapHtmlDocument,
} from '@/utils/markdownToHtml';
+import { cn } from '@/lib/utils';
const isValidPreviewMode = (val: unknown): val is MarkdownToHtmlPreviewMode =>
typeof val === 'string' && ['split', 'preview', 'html'].includes(val);
+// 💡 2. 样式超进化:将实色系全部改为标准 rgba 通道,确保在 iframe 内部能跟随宿主主题自适应混色
const PREVIEW_STYLES = `
.markdown-body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
- color: inherit;
+ color: var(--md-foreground, #1f2328);
+ background-color: transparent;
}
- .markdown-body h1, .markdown-body h2, .markdown-body h3,
- .markdown-body h4, .markdown-body h5, .markdown-body h6 {
- margin-top: 20px;
- margin-bottom: 12px;
+ .markdown-body h1, .markdown-body h2, .markdown-body h3 {
+ margin-top: 24px;
+ margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
- .markdown-body h1 { font-size: 1.8em; border-bottom: 1px solid rgba(128,128,128,0.2); padding-bottom: 0.3em; }
- .markdown-body h2 { font-size: 1.5em; border-bottom: 1px solid rgba(128,128,128,0.2); padding-bottom: 0.3em; }
- .markdown-body h3 { font-size: 1.25em; }
- .markdown-body p { margin-top: 0; margin-bottom: 12px; }
- .markdown-body a { color: #1976d2; text-decoration: none; }
+ .markdown-body h1 { font-size: 1.6em; border-bottom: 1px solid var(--md-border, rgba(128,128,128,0.2)); padding-bottom: 0.3em; }
+ .markdown-body h2 { font-size: 1.35em; border-bottom: 1px solid var(--md-border, rgba(128,128,128,0.2)); padding-bottom: 0.3em; }
+ .markdown-body p { margin-top: 0; margin-bottom: 16px; font-size: 14px; }
+ .markdown-body a { color: #2563eb; text-decoration: none; }
.markdown-body a:hover { text-decoration: underline; }
.markdown-body code {
- background-color: rgba(128,128,128,0.1);
- border-radius: 3px;
+ background-color: var(--md-code-bg, rgba(128,128,128,0.08));
+ border-radius: 4px;
font-size: 85%;
padding: 0.2em 0.4em;
- font-family: 'SFMono-Regular', Consolas, monospace;
+ font-family: Menlo, Consolas, monospace;
}
.markdown-body pre {
- background-color: rgba(128,128,128,0.08);
- border-radius: 6px;
+ background-color: var(--md-pre-bg, rgba(128,128,128,0.05));
+ border-radius: 8px;
font-size: 85%;
line-height: 1.45;
overflow: auto;
- padding: 14px;
- margin: 0 0 12px;
+ padding: 16px;
+ margin: 0 0 16px;
+ border: 1px solid var(--md-border, rgba(128,128,128,0.15));
}
.markdown-body pre code {
background-color: transparent;
border: 0;
- display: inline;
- line-height: inherit;
- margin: 0;
padding: 0;
- word-wrap: normal;
}
.markdown-body blockquote {
- border-left: 0.25em solid rgba(128,128,128,0.3);
- color: rgba(128,128,128,0.7);
- margin: 0 0 12px;
+ border-left: 0.25em solid var(--md-quote-line, rgba(128,128,128,0.3));
+ color: var(--md-muted, rgba(128,128,128,0.6));
+ margin: 0 0 16px;
padding: 0 1em;
}
- .markdown-body ul, .markdown-body ol { margin-top: 0; margin-bottom: 12px; padding-left: 2em; }
- .markdown-body li + li { margin-top: 0.25em; }
- .markdown-body img { max-width: 100%; box-sizing: content-box; }
.markdown-body table {
border-collapse: collapse;
- border-spacing: 0;
- display: block;
- overflow: auto;
width: 100%;
- margin-bottom: 12px;
+ margin-bottom: 16px;
+ font-size: 13px;
}
.markdown-body table th, .markdown-body table td {
- border: 1px solid rgba(128,128,128,0.25);
+ border: 1px solid var(--md-border, rgba(128,128,128,0.2));
padding: 6px 13px;
}
- .markdown-body table tr:nth-child(2n) { background-color: rgba(128,128,128,0.05); }
- .markdown-body table th { font-weight: 600; background-color: rgba(128,128,128,0.05); }
- .markdown-body hr {
- background-color: rgba(128,128,128,0.2);
- border: 0;
- height: 0.25em;
- margin: 20px 0;
- padding: 0;
- }
- .markdown-body input[type="checkbox"] { margin-right: 0.5em; }
+ .markdown-body table tr:nth-child(2n) { background-color: var(--md-code-bg, rgba(128,128,128,0.03)); }
+ .markdown-body table th { font-weight: 600; background-color: var(--md-code-bg, rgba(128,128,128,0.05)); }
`;
export default function MarkdownToHtmlPage() {
@@ -107,22 +93,42 @@ export default function MarkdownToHtmlPage() {
const result = useMemo(() => markdownToHtml(markdown), [markdown]);
const error = result.hasError ? (result.error ?? null) : null;
+ // 💡 3. 沙箱暗黑模式自适应守卫(Sandbox Dark Mode Synchronizer):
+ // 根据宿主最外层的 HTML 是否包含 .dark 类名,动态向 iframe 注入对应的明暗模式 CSS 变量色轴
useEffect(() => {
const iframe = iframeRef.current;
- if (!iframe || !iframe.contentDocument) return;
+ if (!iframe) return;
- const doc = iframe.contentDocument;
- doc.open();
- doc.write(`
-
+ const isDarkMode = document.documentElement.classList.contains('dark');
+ const themeVariables = isDarkMode
+ ? `:root {
+ --md-foreground: #e6edf3;
+ --md-border: rgba(255,255,255,0.15);
+ --md-code-bg: rgba(255,255,255,0.1);
+ --md-pre-bg: rgba(255,255,255,0.05);
+ --md-muted: #8b949e;
+ }`
+ : `:root {
+ --md-foreground: #1f2328;
+ --md-border: rgba(128,128,128,0.2);
+ --md-code-bg: rgba(128,128,128,0.08);
+ --md-pre-bg: rgba(128,128,128,0.04);
+ --md-muted: #636c76;
+ }`;
+
+ iframe.srcdoc = `
+
-
+
${result.html}
-`);
- doc.close();
- }, [result.html]);
+`;
+ }, [result.html, previewMode]); // 当切换预览模式或 HTML 改变时,自发刷新沙箱
const handleModeChange = useCallback(
(newMode: MarkdownToHtmlPreviewMode) => {
@@ -148,12 +154,16 @@ export default function MarkdownToHtmlPage() {
const showPreview = previewMode !== 'html';
return (
-
-
} />
+
+
}
+ />
- {/* 工具栏 */}
-
+ {/* 工具集成控制护栏 */}
+
-
-
- {/* 错误提示 */}
+ {/* 错误拦截提示框 */}
{error && (
-
+
{error}
)}
- {/* 主内容区 */}
+ {/* 主框架多栏联动排版轴 */}
- {/* Markdown 输入区 */}
+ {/* Markdown 输入翼终端 */}
{showInput && (
-
-
-
{t('inputLabel')}
-
+
+
+
+ {t('inputLabel')}
+
+
{t('charCount', { count: markdown.length })}
@@ -216,23 +238,26 @@ export default function MarkdownToHtmlPage() {
value={markdown}
onChange={(e) => setMarkdown(e.target.value)}
placeholder={t('inputPlaceholder')}
- className="flex-1 min-h-[400px] p-4 font-mono text-[0.85rem] leading-relaxed resize-none focus:outline-none"
+ className="flex-1 min-h-[380px] p-4 bg-transparent font-mono text-xs leading-relaxed resize-none focus:outline-none text-foreground/90 select-text"
/>
)}
- {/* 预览/输出区 */}
+ {/* 实时 HTML/Iframe 预览翼终端 */}
{showPreview && (
-
-
-
+
+
+
{(previewMode as string) === 'html' ? t('htmlOutputLabel') : t('previewLabel')}
-
-
+
+
{t('charCount', { count: result.htmlLength })}
-
+
@@ -240,14 +265,14 @@ export default function MarkdownToHtmlPage() {
) : (
-
+
)}
diff --git a/utils/markdownToHtml.ts b/utils/markdownToHtml.ts
index 7ba3508..50de09b 100644
--- a/utils/markdownToHtml.ts
+++ b/utils/markdownToHtml.ts
@@ -195,16 +195,17 @@ export function downloadHtmlFile(content: string, filename: string = 'export.htm
* @param title - 打印窗口标题
*/
export function printHtml(html: string, title: string = 'Markdown Preview'): void {
- const printWindow = window.open('', '_blank', 'width=800,height=600');
+ const doc = wrapHtmlDocument(html, title);
+ const blob = new Blob([doc], { type: 'text/html;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+
+ const printWindow = window.open(url, '_blank', 'width=800,height=600');
if (!printWindow) {
+ URL.revokeObjectURL(url);
console.error('无法打开打印窗口,请检查浏览器弹窗拦截设置');
return;
}
- const doc = wrapHtmlDocument(html, title);
- printWindow.document.write(doc);
- printWindow.document.close();
-
// 等待样式加载完成后打印
let printed = false;
printWindow.onload = () => {
@@ -220,6 +221,11 @@ export function printHtml(html: string, title: string = 'Markdown Preview'): voi
printWindow.print();
}
}, 500);
+
+ // 打印完成后释放 Blob URL(浏览器标签页关闭后也会自动回收)
+ setTimeout(() => {
+ URL.revokeObjectURL(url);
+ }, 60000);
}
/**