Initial commit: MarkLite Markdown Reader

This commit is contained in:
thzxx
2026-05-18 10:40:11 +08:00
commit fda3e5987d
13 changed files with 2967 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self' data: https:;">
<title>MarkLite</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="../lib/highlight-github.css">
</head>
<body>
<div id="app">
<!-- Toolbar -->
<div id="toolbar">
<div class="toolbar-left">
<button id="btn-open" class="toolbar-btn" title="打开文件 (Ctrl+O)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span>打开</span>
</button>
<button id="btn-save" class="toolbar-btn" title="保存文件 (Ctrl+S)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
<polyline points="17 21 17 13 7 13 7 21"></polyline>
<polyline points="7 3 7 8 15 8"></polyline>
</svg>
<span>保存</span>
</button>
<div class="toolbar-divider"></div>
<button id="btn-split" class="toolbar-btn active" title="编辑+预览 (Ctrl+1)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<line x1="12" y1="3" x2="12" y2="21"></line>
</svg>
<span>分屏</span>
</button>
<button id="btn-editor" class="toolbar-btn" title="纯编辑 (Ctrl+2)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
<span>编辑</span>
</button>
<button id="btn-preview" class="toolbar-btn" title="纯预览 (Ctrl+3)">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>
<span>预览</span>
</button>
</div>
</div>
<!-- Main content area -->
<div id="main-content">
<!-- Editor panel -->
<div id="editor-panel">
<div id="editor-wrapper">
<div id="line-numbers"></div>
<textarea id="editor" spellcheck="false" placeholder="在此输入 Markdown 内容,或拖拽 .md 文件到窗口打开..."></textarea>
</div>
</div>
<!-- Resizer -->
<div id="resizer"></div>
<!-- Preview panel -->
<div id="preview-panel">
<div id="preview" class="markdown-body"></div>
</div>
</div>
<!-- Status bar -->
<div id="statusbar">
<div class="status-left">
<span id="status-text">就绪</span>
</div>
<div class="status-right">
<span id="status-encoding">UTF-8</span>
<span class="status-divider">|</span>
<span id="status-lang">Markdown</span>
<span class="status-divider">|</span>
<span id="status-cursor">行 1, 列 1</span>
</div>
</div>
<!-- Drop overlay -->
<div id="drop-overlay" class="hidden">
<div class="drop-content">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<p>释放文件以打开</p>
</div>
</div>
<!-- Welcome screen -->
<div id="welcome-screen">
<div class="welcome-content">
<div class="welcome-icon">
<svg width="80" height="80" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="10" y="5" width="80" height="90" rx="8" fill="#f0f6ff" stroke="#1a73e8" stroke-width="2"/>
<text x="50" y="45" text-anchor="middle" fill="#1a73e8" font-family="system-ui" font-weight="bold" font-size="32">M↓</text>
<text x="50" y="70" text-anchor="middle" fill="#5f6368" font-family="system-ui" font-size="12">MarkLite</text>
</svg>
</div>
<h1>欢迎使用 MarkLite</h1>
<p>一款轻量级的 Markdown 阅读器</p>
<div class="welcome-actions">
<button id="btn-welcome-open" class="welcome-btn primary">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
打开文件
</button>
<button id="btn-welcome-new" class="welcome-btn secondary">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
新建文件
</button>
</div>
<div class="welcome-tips">
<p>💡 提示:可以直接拖拽 .md 文件到窗口打开</p>
<p>⌨️ 快捷键:Ctrl+O 打开 | Ctrl+S 保存 | Ctrl+1/2/3 切换视图</p>
</div>
</div>
</div>
</div>
<script src="../lib/marked.min.js"></script>
<script src="../lib/highlight.min.js"></script>
<script src="renderer.js"></script>
</body>
</html>
+441
View File
@@ -0,0 +1,441 @@
// MarkLite Renderer Process
(function () {
'use strict';
// ===== DOM Elements =====
const editor = document.getElementById('editor');
const preview = document.getElementById('preview');
const lineNumbers = document.getElementById('line-numbers');
const welcomeScreen = document.getElementById('welcome-screen');
const dropOverlay = document.getElementById('drop-overlay');
const statusText = document.getElementById('status-text');
const statusCursor = document.getElementById('status-cursor');
const resizer = document.getElementById('resizer');
const editorPanel = document.getElementById('editor-panel');
const previewPanel = document.getElementById('preview-panel');
const mainContent = document.getElementById('main-content');
// Buttons
const btnOpen = document.getElementById('btn-open');
const btnSave = document.getElementById('btn-save');
const btnSplit = document.getElementById('btn-split');
const btnEditor = document.getElementById('btn-editor');
const btnPreview = document.getElementById('btn-preview');
const btnWelcomeOpen = document.getElementById('btn-welcome-open');
const btnWelcomeNew = document.getElementById('btn-welcome-new');
// ===== State =====
let currentFilePath = null;
let currentContent = '';
let viewMode = 'split'; // split, editor, preview
let isModified = false;
let updateTimer = null;
// ===== Initialize marked.js =====
function initMarked() {
if (typeof marked !== 'undefined') {
marked.setOptions({
gfm: true,
breaks: true,
pedantic: false,
highlight: function (code, lang) {
if (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(code, { language: lang }).value;
} catch (e) {
// fall through
}
}
// Auto detect if no language specified
if (typeof hljs !== 'undefined') {
try {
return hljs.highlightAuto(code).value;
} catch (e) {
// fall through
}
}
return code;
}
});
}
}
// ===== Markdown Rendering =====
function renderMarkdown(text) {
if (typeof marked === 'undefined') {
preview.innerHTML = '<p style="color: red;">错误: Markdown 解析库未加载</p>';
return;
}
try {
const html = marked.parse(text);
preview.innerHTML = html;
} catch (e) {
preview.innerHTML = '<p style="color: red;">渲染错误: ' + e.message + '</p>';
}
}
// ===== Line Numbers =====
function updateLineNumbers() {
const lines = editor.value.split('\n');
const count = lines.length;
let html = '';
for (let i = 1; i <= count; i++) {
html += '<div class="line-num">' + i + '</div>';
}
lineNumbers.innerHTML = html;
}
// ===== Cursor Position =====
function updateCursorPosition() {
const text = editor.value;
const pos = editor.selectionStart;
const textBeforeCursor = text.substring(0, pos);
const lines = textBeforeCursor.split('\n');
const line = lines.length;
const col = lines[lines.length - 1].length + 1;
statusCursor.textContent = `${line}, 列 ${col}`;
}
// ===== Sync Scroll =====
function syncScroll() {
if (viewMode !== 'split') return;
const editorEl = editor;
const previewEl = previewPanel;
const scrollPercent = editorEl.scrollTop / (editorEl.scrollHeight - editorEl.clientHeight);
previewEl.scrollTop = scrollPercent * (previewEl.scrollHeight - previewEl.clientHeight);
}
// ===== Update Preview =====
function scheduleUpdate() {
if (updateTimer) clearTimeout(updateTimer);
updateTimer = setTimeout(() => {
const text = editor.value;
renderMarkdown(text);
updateLineNumbers();
}, 150);
}
// ===== File Operations =====
function setModified(modified) {
isModified = modified;
const title = currentFilePath
? `MarkLite - ${getFileName(currentFilePath)}${modified ? ' *' : ''}`
: `MarkLite - 未命名${modified ? ' *' : ''}`;
document.title = title;
}
function getFileName(filePath) {
return filePath.split(/[/\\]/).pop();
}
function setEditorContent(text, filePath) {
editor.value = text;
currentContent = text;
currentFilePath = filePath || null;
isModified = false;
updateLineNumbers();
renderMarkdown(text);
updateCursorPosition();
if (filePath) {
document.title = `MarkLite - ${getFileName(filePath)}`;
statusText.textContent = getFileName(filePath);
} else {
document.title = 'MarkLite - 未命名';
statusText.textContent = '未命名';
}
// Hide welcome screen
welcomeScreen.classList.add('hidden');
}
async function handleOpenFile() {
if (typeof window.electronAPI !== 'undefined') {
const result = await window.electronAPI.openFile();
if (result) {
setEditorContent(result.content, result.filePath);
}
} else {
// Fallback: use file input
const input = document.createElement('input');
input.type = 'file';
input.accept = '.md,.markdown,.txt';
input.onchange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (ev) => {
setEditorContent(ev.target.result, file.name);
};
reader.readAsText(file);
}
};
input.click();
}
}
async function handleSave() {
if (typeof window.electronAPI !== 'undefined') {
const result = await window.electronAPI.saveFile({
filePath: currentFilePath,
content: editor.value
});
if (result.success) {
currentFilePath = result.filePath;
currentContent = editor.value;
setModified(false);
statusText.textContent = '已保存';
setTimeout(() => {
statusText.textContent = getFileName(currentFilePath) || '就绪';
}, 2000);
}
} else {
// Fallback: download file
const blob = new Blob([editor.value], { type: 'text/markdown' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = (currentFilePath || 'untitled.md');
a.click();
URL.revokeObjectURL(a.href);
setModified(false);
}
}
async function handleSaveAs() {
if (typeof window.electronAPI !== 'undefined') {
const result = await window.electronAPI.saveFileAs({
content: editor.value
});
if (result.success) {
currentFilePath = result.filePath;
currentContent = editor.value;
setModified(false);
statusText.textContent = '已保存';
setTimeout(() => {
statusText.textContent = getFileName(currentFilePath) || '就绪';
}, 2000);
}
} else {
handleSave();
}
}
// ===== View Mode =====
function setViewMode(mode) {
viewMode = mode;
const app = document.getElementById('app');
// Remove all mode classes
app.classList.remove('mode-editor', 'mode-preview');
// Update button states
btnSplit.classList.remove('active');
btnEditor.classList.remove('active');
btnPreview.classList.remove('active');
switch (mode) {
case 'split':
btnSplit.classList.add('active');
break;
case 'editor':
btnEditor.classList.add('active');
app.classList.add('mode-editor');
break;
case 'preview':
btnPreview.classList.add('active');
app.classList.add('mode-preview');
// Update preview when switching to preview mode
renderMarkdown(editor.value);
break;
}
}
// ===== Resizer =====
let isResizing = false;
function initResizer() {
resizer.addEventListener('mousedown', (e) => {
isResizing = true;
resizer.classList.add('active');
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
const containerRect = mainContent.getBoundingClientRect();
const percentage = ((e.clientX - containerRect.left) / containerRect.width) * 100;
const clamped = Math.max(20, Math.min(80, percentage));
editorPanel.style.flex = `0 0 ${clamped}%`;
previewPanel.style.flex = `0 0 ${100 - clamped}%`;
});
document.addEventListener('mouseup', () => {
if (isResizing) {
isResizing = false;
resizer.classList.remove('active');
document.body.style.cursor = '';
document.body.style.userSelect = '';
}
});
}
// ===== Drag & Drop =====
function initDragDrop() {
let dragCounter = 0;
document.addEventListener('dragenter', (e) => {
e.preventDefault();
dragCounter++;
dropOverlay.classList.remove('hidden');
});
document.addEventListener('dragleave', (e) => {
e.preventDefault();
dragCounter--;
if (dragCounter === 0) {
dropOverlay.classList.add('hidden');
}
});
document.addEventListener('dragover', (e) => {
e.preventDefault();
});
document.addEventListener('drop', (e) => {
e.preventDefault();
dragCounter = 0;
dropOverlay.classList.add('hidden');
const files = e.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
const reader = new FileReader();
reader.onload = (ev) => {
setEditorContent(ev.target.result, file.name);
};
reader.readAsText(file);
}
});
}
// ===== Keyboard Shortcuts =====
function initKeyboard() {
document.addEventListener('keydown', (e) => {
// Ctrl+O: Open
if (e.ctrlKey && e.key === 'o') {
e.preventDefault();
handleOpenFile();
}
// Ctrl+S: Save
if (e.ctrlKey && e.key === 's' && !e.shiftKey) {
e.preventDefault();
handleSave();
}
// Ctrl+Shift+S: Save As
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
e.preventDefault();
handleSaveAs();
}
// Ctrl+1: Split view
if (e.ctrlKey && e.key === '1') {
e.preventDefault();
setViewMode('split');
}
// Ctrl+2: Editor only
if (e.ctrlKey && e.key === '2') {
e.preventDefault();
setViewMode('editor');
}
// Ctrl+3: Preview only
if (e.ctrlKey && e.key === '3') {
e.preventDefault();
setViewMode('preview');
}
});
}
// ===== Tab key support in editor =====
function initEditorTab() {
editor.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
e.preventDefault();
const start = editor.selectionStart;
const end = editor.selectionEnd;
const value = editor.value;
editor.value = value.substring(0, start) + ' ' + value.substring(end);
editor.selectionStart = editor.selectionEnd = start + 4;
scheduleUpdate();
}
});
}
// ===== Event Listeners =====
function initEventListeners() {
// Editor input
editor.addEventListener('input', () => {
setModified(true);
scheduleUpdate();
});
// Editor scroll sync
editor.addEventListener('scroll', syncScroll);
// Editor cursor position
editor.addEventListener('click', updateCursorPosition);
editor.addEventListener('keyup', updateCursorPosition);
editor.addEventListener('select', updateCursorPosition);
// Toolbar buttons
btnOpen.addEventListener('click', handleOpenFile);
btnSave.addEventListener('click', handleSave);
btnSplit.addEventListener('click', () => setViewMode('split'));
btnEditor.addEventListener('click', () => setViewMode('editor'));
btnPreview.addEventListener('click', () => setViewMode('preview'));
// Welcome buttons
btnWelcomeOpen.addEventListener('click', handleOpenFile);
btnWelcomeNew.addEventListener('click', () => {
setEditorContent('', null);
});
// Electron IPC events
if (typeof window.electronAPI !== 'undefined') {
window.electronAPI.onFileOpened((data) => {
setEditorContent(data.content, data.filePath);
});
window.electronAPI.onMenuSave(() => {
handleSave();
});
window.electronAPI.onMenuSaveAs(() => {
handleSaveAs();
});
window.electronAPI.onViewModeChange((mode) => {
setViewMode(mode);
});
}
}
// ===== Initialize =====
function init() {
initMarked();
initResizer();
initDragDrop();
initKeyboard();
initEditorTab();
initEventListeners();
updateLineNumbers();
setViewMode('split');
}
// Run when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
+548
View File
@@ -0,0 +1,548 @@
/* ===== Reset & Base ===== */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary: #1a73e8;
--primary-light: #e8f0fe;
--primary-dark: #1557b0;
--bg: #ffffff;
--bg-secondary: #f8f9fa;
--bg-tertiary: #f1f3f4;
--text: #333333;
--text-secondary: #5f6368;
--text-tertiary: #9aa0a6;
--border: #e1e4e8;
--border-light: #f0f0f0;
--code-bg: #f6f8fa;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.1);
--radius: 6px;
--toolbar-height: 44px;
--statusbar-height: 28px;
--font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: "Cascadia Code", "Fira Code", "JetBrains Mono", Consolas, "Courier New", monospace;
}
html, body {
height: 100%;
font-family: var(--font-ui);
font-size: 14px;
color: var(--text);
background: var(--bg);
overflow: hidden;
user-select: none;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
}
/* ===== Toolbar ===== */
#toolbar {
height: var(--toolbar-height);
background: var(--bg);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
padding: 0 8px;
flex-shrink: 0;
z-index: 10;
}
.toolbar-left {
display: flex;
align-items: center;
gap: 2px;
}
.toolbar-btn {
display: flex;
align-items: center;
gap: 5px;
padding: 6px 12px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 13px;
font-family: var(--font-ui);
border-radius: var(--radius);
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.toolbar-btn:hover {
background: var(--bg-tertiary);
color: var(--text);
}
.toolbar-btn.active {
background: var(--primary-light);
color: var(--primary);
}
.toolbar-btn svg {
flex-shrink: 0;
}
.toolbar-divider {
width: 1px;
height: 24px;
background: var(--border);
margin: 0 6px;
}
/* ===== Main Content ===== */
#main-content {
flex: 1;
display: flex;
overflow: hidden;
position: relative;
}
/* ===== Editor Panel ===== */
#editor-panel {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
border-right: 1px solid var(--border);
}
#editor-wrapper {
flex: 1;
display: flex;
overflow: hidden;
position: relative;
}
#line-numbers {
width: 50px;
background: var(--bg-secondary);
border-right: 1px solid var(--border-light);
padding: 12px 0;
text-align: right;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
color: var(--text-tertiary);
overflow: hidden;
user-select: none;
flex-shrink: 0;
}
#line-numbers .line-num {
padding-right: 12px;
height: 20.8px;
}
#editor {
flex: 1;
width: 100%;
padding: 12px 16px;
border: none;
outline: none;
resize: none;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
color: var(--text);
background: var(--bg);
tab-size: 4;
overflow-y: auto;
user-select: text;
}
#editor::placeholder {
color: var(--text-tertiary);
}
/* ===== Resizer ===== */
#resizer {
width: 4px;
background: var(--border);
cursor: col-resize;
transition: background 0.15s ease;
flex-shrink: 0;
}
#resizer:hover,
#resizer.active {
background: var(--primary);
}
/* ===== Preview Panel ===== */
#preview-panel {
flex: 1;
overflow-y: auto;
min-width: 0;
background: var(--bg);
}
#preview {
padding: 24px 32px;
max-width: 900px;
margin: 0 auto;
}
/* ===== Markdown Body Styles ===== */
.markdown-body {
font-family: var(--font-ui);
font-size: 15px;
line-height: 1.7;
color: var(--text);
word-wrap: break-word;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3,
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
color: var(--text);
}
.markdown-body h1 {
font-size: 2em;
padding-bottom: 0.3em;
border-bottom: 1px solid var(--border);
}
.markdown-body h2 {
font-size: 1.5em;
padding-bottom: 0.3em;
border-bottom: 1px solid var(--border-light);
}
.markdown-body h3 { font-size: 1.25em; }
.markdown-body h4 { font-size: 1em; }
.markdown-body h5 { font-size: 0.875em; }
.markdown-body h6 { font-size: 0.85em; color: var(--text-secondary); }
.markdown-body p {
margin-top: 0;
margin-bottom: 16px;
}
.markdown-body a {
color: var(--primary);
text-decoration: none;
}
.markdown-body a:hover {
text-decoration: underline;
}
.markdown-body strong { font-weight: 600; }
.markdown-body img {
max-width: 100%;
height: auto;
border-radius: var(--radius);
margin: 8px 0;
}
.markdown-body hr {
height: 2px;
background: var(--border);
border: none;
margin: 24px 0;
border-radius: 1px;
}
.markdown-body blockquote {
margin: 0 0 16px 0;
padding: 4px 16px;
border-left: 4px solid var(--primary);
color: var(--text-secondary);
background: var(--bg-secondary);
border-radius: 0 var(--radius) var(--radius) 0;
}
.markdown-body blockquote p:last-child {
margin-bottom: 0;
}
.markdown-body ul,
.markdown-body ol {
margin-top: 0;
margin-bottom: 16px;
padding-left: 2em;
}
.markdown-body li {
margin-top: 4px;
}
.markdown-body li + li {
margin-top: 4px;
}
.markdown-body code {
font-family: var(--font-mono);
font-size: 0.9em;
background: var(--code-bg);
padding: 2px 6px;
border-radius: 4px;
color: #e83e8c;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 16px;
padding: 16px;
background: var(--code-bg);
border-radius: var(--radius);
overflow-x: auto;
border: 1px solid var(--border-light);
}
.markdown-body pre code {
padding: 0;
background: transparent;
color: inherit;
font-size: 13px;
line-height: 1.5;
}
.markdown-body table {
border-collapse: collapse;
width: 100%;
margin-bottom: 16px;
overflow-x: auto;
display: block;
}
.markdown-body table th,
.markdown-body table td {
padding: 8px 16px;
border: 1px solid var(--border);
text-align: left;
}
.markdown-body table th {
font-weight: 600;
background: var(--bg-secondary);
}
.markdown-body table tr:nth-child(even) {
background: var(--bg-secondary);
}
.markdown-body input[type="checkbox"] {
margin-right: 6px;
accent-color: var(--primary);
}
/* ===== Status Bar ===== */
#statusbar {
height: var(--statusbar-height);
background: var(--bg-secondary);
border-top: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
font-size: 12px;
color: var(--text-secondary);
flex-shrink: 0;
}
.status-left,
.status-right {
display: flex;
align-items: center;
gap: 8px;
}
.status-divider {
color: var(--border);
}
/* ===== Drop Overlay ===== */
#drop-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(26, 115, 232, 0.1);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
border: 3px dashed var(--primary);
margin: 8px;
border-radius: 12px;
}
#drop-overlay.hidden {
display: none;
}
.drop-content {
text-align: center;
color: var(--primary);
}
.drop-content svg {
margin-bottom: 12px;
opacity: 0.7;
}
.drop-content p {
font-size: 18px;
font-weight: 500;
}
/* ===== Welcome Screen ===== */
#welcome-screen {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg);
z-index: 5;
}
#welcome-screen.hidden {
display: none;
}
.welcome-content {
text-align: center;
max-width: 480px;
padding: 40px;
}
.welcome-icon {
margin-bottom: 24px;
}
.welcome-content h1 {
font-size: 28px;
font-weight: 600;
color: var(--text);
margin-bottom: 8px;
}
.welcome-content > p {
font-size: 16px;
color: var(--text-secondary);
margin-bottom: 32px;
}
.welcome-actions {
display: flex;
gap: 12px;
justify-content: center;
margin-bottom: 32px;
}
.welcome-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 24px;
border: none;
border-radius: var(--radius);
font-size: 14px;
font-family: var(--font-ui);
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.welcome-btn.primary {
background: var(--primary);
color: white;
}
.welcome-btn.primary:hover {
background: var(--primary-dark);
box-shadow: var(--shadow-lg);
}
.welcome-btn.secondary {
background: var(--bg-secondary);
color: var(--text);
border: 1px solid var(--border);
}
.welcome-btn.secondary:hover {
background: var(--bg-tertiary);
}
.welcome-tips {
text-align: left;
background: var(--bg-secondary);
border-radius: var(--radius);
padding: 16px 20px;
border: 1px solid var(--border-light);
}
.welcome-tips p {
font-size: 13px;
color: var(--text-secondary);
margin-bottom: 6px;
}
.welcome-tips p:last-child {
margin-bottom: 0;
}
/* ===== View Modes ===== */
#app.mode-editor #preview-panel,
#app.mode-editor #resizer {
display: none;
}
#app.mode-preview #editor-panel,
#app.mode-preview #resizer {
display: none;
}
/* ===== Scrollbar ===== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-tertiary);
}
/* ===== Animations ===== */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.welcome-content {
animation: fadeIn 0.3s ease;
}