chore: 删除旧版 v1 代码 (main.js/preload.js/renderer/lib/)

重构完成后清理遗留的旧版 JavaScript 代码:
- main.js (447行) — 旧版主进程
- preload.js (40行) — 旧版预加载脚本
- renderer/ — 旧版 HTML/JS/CSS
- lib/ — 旧版第三方库 (marked.min.js, highlight.min.js)

新版代码统一在 src/ 目录下,技术栈:Electron 28 + React 18 + TypeScript 5.6 + Zustand 5 + CodeMirror 6
This commit is contained in:
thzxx
2026-05-28 10:51:56 +08:00
parent d57a2b2aac
commit 70566efc4a
8 changed files with 0 additions and 4786 deletions
-10
View File
@@ -1,10 +0,0 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: GitHub
Description: Light theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-light
Current colors taken from GitHub's CSS
*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}
-1213
View File
File diff suppressed because one or more lines are too long
-6
View File
File diff suppressed because one or more lines are too long
-447
View File
@@ -1,447 +0,0 @@
const { app, BrowserWindow, dialog, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');
const { readFile, stat } = require('fs/promises');
const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB
let mainWindow = null;
let activeFilePath = null; // Currently active tab's file path
let pendingFilePath = null;
let fileWatcher = null;
let closeTimeout = null;
let isClosing = false; // Prevent re-entrant close
let isSelfWriting = false; // Skip fs.watch events triggered by our own writes
// ===== Single Instance Lock =====
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// A second instance was launched (e.g. double-clicking a .md file)
// Focus the existing window and open the file in it
if (mainWindow && !mainWindow.isDestroyed()) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
// Extract file path from the second instance's command line args
const filePath = getFilePathFromArgs(commandLine);
if (filePath) {
openFileInTab(filePath);
}
}
});
}
// Extract a valid file path from command line arguments (skip Electron flags)
function getFilePathFromArgs(args) {
for (let i = 1; i < args.length; i++) {
const arg = args[i];
if (!arg.startsWith('--') && !arg.startsWith('-')) {
try {
if (fs.existsSync(arg)) return arg;
} catch (e) { /* ignore */ }
}
}
return null;
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
icon: path.join(__dirname, 'assets', 'icon.ico'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
},
titleBarStyle: 'default',
show: false
});
mainWindow.webContents.on('did-finish-load', () => {
if (pendingFilePath) {
openFileInTab(pendingFilePath);
pendingFilePath = null;
}
});
mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
mainWindow.setMenu(null);
// Unsaved changes warning on close
mainWindow.on('close', (e) => {
if (isClosing) return; // Prevent re-entrant close
isClosing = true;
e.preventDefault();
try {
// 检查 webContents 是否仍可用
if (!mainWindow.webContents.isDestroyed()) {
mainWindow.webContents.send('window:confirmClose');
} else {
// 渲染进程已销毁,直接关闭
mainWindow.removeAllListeners('close');
mainWindow.close();
return;
}
} catch (err) {
mainWindow.removeAllListeners('close');
mainWindow.close();
return;
}
// 5s safety timeout in case renderer is unresponsive
closeTimeout = setTimeout(() => {
closeTimeout = null;
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.removeAllListeners('close');
mainWindow.close();
}
}, 5000);
});
mainWindow.on('closed', () => {
stopWatching();
stopSidebarWatching();
mainWindow = null;
});
}
// File watcher
function startWatching(filePath) {
stopWatching();
if (!filePath) return;
try {
fileWatcher = fs.watch(filePath, (eventType) => {
if (eventType === 'change' && mainWindow && !mainWindow.isDestroyed()) {
// 跳过自身写入触发的 change 事件
if (isSelfWriting) return;
mainWindow.webContents.send('file:externallyModified', filePath);
}
});
} catch (err) {
// Silently ignore
}
}
function stopWatching() {
if (fileWatcher) {
fileWatcher.close();
fileWatcher = null;
}
}
async function showOpenDialog() {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Markdown 文件', extensions: ['md', 'markdown', 'txt'] }
]
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths;
}
return null;
}
async function readFileContent(filePath) {
// 文件大小检查
const stats = await stat(filePath);
if (stats.size > MAX_FILE_SIZE) {
throw new Error(`文件过大(${(stats.size / 1024 / 1024).toFixed(1)} MB),暂不支持超过 20MB 的文件`);
}
let content = await readFile(filePath, 'utf-8');
// 剥离 UTF-8 BOMWindows 某些编辑器会添加)
if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1);
return content;
}
// Open file in a new tab (renderer manages tabs)
async function openFileInTab(filePath) {
if (!mainWindow || mainWindow.isDestroyed()) return;
try {
const content = await readFileContent(filePath);
activeFilePath = filePath;
startWatching(filePath);
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
mainWindow.webContents.send('file:openInTab', { filePath, content });
} catch (err) {
dialog.showErrorBox('错误', `无法读取文件: ${err.message}`);
}
}
// Switch active file (for tab switching)
function switchActiveFile(filePath) {
activeFilePath = filePath || null;
startWatching(filePath);
if (mainWindow && !mainWindow.isDestroyed()) {
if (filePath) {
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
} else {
mainWindow.setTitle('MarkLite');
}
}
}
// IPC Handlers
// Save file to a specific path with watcher management
async function saveToPath(filePath, content) {
stopWatching();
try {
isSelfWriting = true;
await fs.promises.writeFile(filePath, content, 'utf-8');
isSelfWriting = false;
} catch (err) {
isSelfWriting = false;
startWatching(activeFilePath);
return { success: false, error: err.message };
}
activeFilePath = filePath;
// 延迟 300ms 重新监听,确保文件系统的 change 事件已过期
setTimeout(() => {
if (activeFilePath === filePath) {
startWatching(filePath);
}
}, 300);
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
}
return { success: true, filePath };
}
// Open single file via dialog
ipcMain.handle('dialog:openFile', async () => {
try {
const files = await showOpenDialog();
if (files && files.length > 0) {
const content = await readFileContent(files[0]);
activeFilePath = files[0];
startWatching(files[0]);
mainWindow.setTitle(`MarkLite - ${path.basename(files[0])}`);
return { filePath: files[0], content };
}
return null;
} catch (err) {
return { error: err.message };
}
});
ipcMain.handle('file:read', async (event, filePath) => {
try {
const content = await readFileContent(filePath);
return { success: true, content };
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('file:save', async (event, { filePath, content }) => {
try {
if (filePath) {
return await saveToPath(filePath, content);
} else {
const result = await dialog.showSaveDialog(mainWindow, {
filters: [
{ name: 'Markdown 文件', extensions: ['md'] }
]
});
if (!result.canceled) {
return await saveToPath(result.filePath, content);
}
return { success: false, canceled: true };
}
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('file:saveAs', async (event, { content }) => {
try {
const result = await dialog.showSaveDialog(mainWindow, {
filters: [
{ name: 'Markdown 文件', extensions: ['md'] }
]
});
if (!result.canceled) {
return await saveToPath(result.filePath, content);
}
return { success: false, canceled: true };
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('file:getCurrentPath', () => {
return activeFilePath;
});
ipcMain.handle('file:stats', async (event, filePath) => {
try {
const fileStat = await stat(filePath);
return { success: true, size: fileStat.size, mtime: fileStat.mtime.toISOString() };
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('file:reload', async () => {
if (!activeFilePath) return { success: false, error: '没有打开的文件' };
try {
const content = await readFileContent(activeFilePath);
return { success: true, content, filePath: activeFilePath };
} catch (err) {
return { success: false, error: err.message };
}
});
// ===== File Tree (Sidebar) =====
const SKIP_DIRS = new Set(['node_modules', '.git', '.svn', '.hg', 'dist', 'out', '.next', '.nuxt', '__pycache__', '.DS_Store']);
const MARKDOWN_EXTS = new Set(['.md', '.markdown', '.txt']);
async function buildDirTree(dirPath) {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
// Sort: directories first, then files, alphabetically
entries.sort((a, b) => {
if (a.isDirectory() && !b.isDirectory()) return -1;
if (!a.isDirectory() && b.isDirectory()) return 1;
return a.name.localeCompare(b.name);
});
const children = [];
for (const entry of entries) {
if (SKIP_DIRS.has(entry.name)) continue;
if (entry.name.startsWith('.')) continue;
const childPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
const subChildren = await buildDirTree(childPath);
if (subChildren.length > 0) {
children.push({ name: entry.name, path: childPath, type: 'dir', children: subChildren });
}
} else {
const ext = path.extname(entry.name).toLowerCase();
if (MARKDOWN_EXTS.has(ext)) {
children.push({ name: entry.name, path: childPath, type: 'file' });
}
}
}
return children;
}
ipcMain.handle('dir:readTree', async (event, dirPath) => {
try {
const tree = await buildDirTree(dirPath);
return { success: true, tree, rootPath: dirPath };
} catch (err) {
return { success: false, error: err.message };
}
});
let sidebarWatcher = null;
let sidebarWatchPath = null;
function startSidebarWatching(dirPath) {
stopSidebarWatching();
if (!dirPath) return;
try {
sidebarWatchPath = dirPath;
sidebarWatcher = fs.watch(dirPath, { recursive: true }, (eventType, filename) => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('sidebar:dirChanged');
}
});
} catch (err) { /* ignore */ }
}
function stopSidebarWatching() {
if (sidebarWatcher) { sidebarWatcher.close(); sidebarWatcher = null; }
sidebarWatchPath = null;
}
ipcMain.handle('dir:watch', (event, dirPath) => {
startSidebarWatching(dirPath);
});
ipcMain.handle('dir:unwatch', () => {
stopSidebarWatching();
});
ipcMain.handle('dir:openDialog', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory']
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
}
return null;
});
// Tab switched - update active file tracking
ipcMain.handle('tab:switched', (event, filePath) => {
switchActiveFile(filePath);
});
ipcMain.handle('window:forceClose', () => {
if (closeTimeout) {
clearTimeout(closeTimeout);
closeTimeout = null;
}
// 窗口已销毁则无需操作(超时兜底可能已经关闭了窗口)
if (!mainWindow || mainWindow.isDestroyed()) return;
stopWatching();
mainWindow.removeAllListeners('close');
mainWindow.close();
});
ipcMain.handle('window:cancelClose', () => {
if (closeTimeout) {
clearTimeout(closeTimeout);
closeTimeout = null;
}
isClosing = false; // Reset so close can be triggered again
});
// Command line file (first instance)
function getCommandLineFile() {
return getFilePathFromArgs(process.argv);
}
// App lifecycle
app.whenReady().then(() => {
app.on('open-file', (event, filePath) => {
event.preventDefault();
if (mainWindow && !mainWindow.isDestroyed() && mainWindow.webContents.isLoading()) {
pendingFilePath = filePath;
} else if (mainWindow && !mainWindow.isDestroyed()) {
openFileInTab(filePath);
} else {
pendingFilePath = filePath;
}
});
createWindow();
const fileToOpen = getCommandLineFile();
if (fileToOpen) {
pendingFilePath = fileToOpen;
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
-40
View File
@@ -1,40 +0,0 @@
const { contextBridge, ipcRenderer, shell } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// File operations
openFile: () => ipcRenderer.invoke('dialog:openFile'),
readFile: (filePath) => ipcRenderer.invoke('file:read', filePath),
saveFile: (data) => ipcRenderer.invoke('file:save', data),
saveFileAs: (data) => ipcRenderer.invoke('file:saveAs', data),
getCurrentPath: () => ipcRenderer.invoke('file:getCurrentPath'),
getFileStats: (filePath) => ipcRenderer.invoke('file:stats', filePath),
reloadFile: () => ipcRenderer.invoke('file:reload'),
// Tab management
tabSwitched: (filePath) => ipcRenderer.invoke('tab:switched', filePath),
// Window control
forceClose: () => ipcRenderer.invoke('window:forceClose'),
cancelClose: () => ipcRenderer.invoke('window:cancelClose'),
// Shell
openExternal: (url) => shell.openExternal(url),
// File Tree (Sidebar)
readDirTree: (dirPath) => ipcRenderer.invoke('dir:readTree', dirPath),
openFolderDialog: () => ipcRenderer.invoke('dir:openDialog'),
watchDir: (dirPath) => ipcRenderer.invoke('dir:watch', dirPath),
unwatchDir: () => ipcRenderer.invoke('dir:unwatch'),
// Events from main process
onFileOpenInTab: (callback) => ipcRenderer.on('file:openInTab', (event, data) => callback(data)),
onMenuSave: (callback) => ipcRenderer.on('menu:save', () => callback()),
onMenuSaveAs: (callback) => ipcRenderer.on('menu:saveAs', () => callback()),
onViewModeChange: (callback) => ipcRenderer.on('menu:viewMode', (event, mode) => callback(mode)),
onExternalModification: (callback) => ipcRenderer.on('file:externallyModified', (event, filePath) => callback(filePath)),
onDirChanged: (callback) => ipcRenderer.on('sidebar:dirChanged', () => callback()),
onConfirmClose: (callback) => ipcRenderer.on('window:confirmClose', () => callback()),
// Remove listeners
removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel)
});
-216
View File
@@ -1,216 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- CSP: script-src 只允许 self,阻断内联脚本执行 -->
<!-- style-src 保留 unsafe-inlinehighlight.js 代码高亮依赖 CSS class
但已通过 sanitizeHTML 移除恶意 style 属性和 expression() -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self';">
<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 class="toolbar-right">
<button id="btn-dark" class="toolbar-btn" title="切换暗色主题">
<svg id="icon-dark" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
<svg id="icon-light" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</button>
</div>
</div>
<!-- Workspace: Sidebar + Main Content -->
<div id="workspace">
<!-- Sidebar -->
<div id="sidebar">
<div id="sidebar-header">
<span id="sidebar-title">资源管理器</span>
<button id="btn-open-folder" class="sidebar-header-btn" title="打开文件夹">
<svg width="14" height="14" 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>
</div>
<div id="sidebar-tree"></div>
</div>
<!-- Main content area -->
<div id="main-content">
<!-- Tab bar -->
<div id="tab-bar">
<div id="tab-list"></div>
<button id="btn-new-tab" class="tab-add-btn" title="新建标签页 (Ctrl+T)">
<svg width="14" height="14" 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 id="content-wrapper">
<!-- External modification banner -->
<div id="modified-banner" class="hidden">
<span>文件已被外部程序修改</span>
<button id="btn-reload" class="banner-btn">重新加载</button>
<button id="btn-dismiss" class="banner-btn">忽略</button>
</div>
<!-- Editor panel -->
<div id="editor-panel">
<!-- Search & Replace Bar -->
<div id="search-bar" class="hidden">
<div class="search-row">
<button id="btn-toggle-replace" class="search-opt-btn" title="展开替换行"></button>
<input type="text" id="search-input" placeholder="查找..." />
<span id="search-count"></span>
<button id="btn-case" class="search-opt-btn" title="区分大小写 (Alt+C)">Aa</button>
<button id="btn-regex" class="search-opt-btn" title="正则表达式 (Alt+R)">.*</button>
<button id="btn-prev" class="search-nav-btn" title="上一个 (Shift+Enter)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="18 15 12 9 6 15"></polyline></svg>
</button>
<button id="btn-next" class="search-nav-btn" title="下一个 (Enter)">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="6 9 12 15 18 9"></polyline></svg>
</button>
<button id="btn-search-close" class="search-nav-btn" title="关闭 (Escape)"></button>
</div>
<div id="replace-row" class="hidden">
<input type="text" id="replace-input" placeholder="替换..." />
<button id="btn-replace" class="replace-btn" title="替换 (Ctrl+Shift+G)">替换</button>
<button id="btn-replace-all" class="replace-btn" title="全部替换 (Ctrl+Shift+H)">全部</button>
</div>
</div>
<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>
<!-- 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+F 搜索 | Ctrl+H 替换 | Ctrl+1/2/3 视图 | Ctrl+T 标签</p>
</div>
</div>
</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-size"></span>
<span class="status-divider status-size-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>
</div>
<script src="../lib/marked.min.js"></script>
<script src="../lib/highlight.min.js"></script>
<script src="renderer.js"></script>
</body>
</html>
-1735
View File
File diff suppressed because it is too large Load Diff
-1119
View File
File diff suppressed because it is too large Load Diff