Files
MarkLite/main.js
T
thzxx c573266670 fix: resolve did-finish-load race condition, remove unused import
- Move did-finish-load listener before loadFile() to prevent missing the event
- Remove unused nativeImage import
2026-05-18 10:51:38 +08:00

286 lines
7.6 KiB
JavaScript

const { app, BrowserWindow, dialog, ipcMain, Menu } = require('electron');
const path = require('path');
const fs = require('fs');
let mainWindow = null;
let currentFilePath = null;
let pendingFilePath = null; // For file association race condition
function createWindow() {
// Create the browser window
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
});
// Register did-finish-load BEFORE loadFile to avoid race condition
mainWindow.webContents.on('did-finish-load', () => {
if (pendingFilePath) {
openFile(pendingFilePath);
pendingFilePath = null;
}
});
// Load the index.html
mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html'));
// Show window when ready
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
// Build menu
buildMenu();
mainWindow.on('closed', () => {
mainWindow = null;
});
}
function buildMenu() {
const template = [
{
label: '文件',
submenu: [
{
label: '打开文件',
accelerator: 'CmdOrCtrl+O',
click: () => handleOpenFile()
},
{
label: '保存',
accelerator: 'CmdOrCtrl+S',
click: () => mainWindow.webContents.send('menu:save')
},
{
label: '另存为',
accelerator: 'CmdOrCtrl+Shift+S',
click: () => mainWindow.webContents.send('menu:saveAs')
},
{ type: 'separator' },
{
label: '退出',
accelerator: 'CmdOrCtrl+Q',
click: () => app.quit()
}
]
},
{
label: '视图',
submenu: [
{
label: '编辑 + 预览',
accelerator: 'CmdOrCtrl+1',
click: () => mainWindow.webContents.send('menu:viewMode', 'split')
},
{
label: '纯编辑',
accelerator: 'CmdOrCtrl+2',
click: () => mainWindow.webContents.send('menu:viewMode', 'editor')
},
{
label: '纯预览',
accelerator: 'CmdOrCtrl+3',
click: () => mainWindow.webContents.send('menu:viewMode', 'preview')
},
{ type: 'separator' },
{
label: '开发者工具',
accelerator: 'F12',
click: () => mainWindow.webContents.toggleDevTools()
},
{ type: 'separator' },
{
label: '重新加载',
accelerator: 'CmdOrCtrl+R',
click: () => mainWindow.webContents.reload()
}
]
},
{
label: '帮助',
submenu: [
{
label: '关于 MarkLite',
click: () => {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: '关于 MarkLite',
message: 'MarkLite v1.0.0',
detail: '一款轻量级的 Markdown 阅读器。\n\n技术栈:Electron + marked.js + highlight.js'
});
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
async function showOpenDialog() {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Markdown 文件', extensions: ['md', 'markdown', 'txt'] },
{ name: '所有文件', extensions: ['*'] }
]
});
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
}
return null;
}
async function handleOpenFile() {
const filePath = await showOpenDialog();
if (filePath) {
openFile(filePath);
}
}
function openFile(filePath) {
try {
const content = fs.readFileSync(filePath, 'utf-8');
currentFilePath = filePath;
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
mainWindow.webContents.send('file:opened', { filePath, content });
} catch (err) {
dialog.showErrorBox('错误', `无法读取文件: ${err.message}`);
}
}
// IPC Handlers
ipcMain.handle('dialog:openFile', async () => {
const filePath = await showOpenDialog();
if (filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
currentFilePath = filePath;
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
return { filePath, content };
}
return null;
});
ipcMain.handle('file:read', async (event, filePath) => {
try {
const content = fs.readFileSync(filePath, 'utf-8');
return { success: true, content };
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('file:save', async (event, { filePath, content }) => {
try {
if (filePath) {
fs.writeFileSync(filePath, content, 'utf-8');
currentFilePath = filePath;
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
return { success: true, filePath };
} else {
// No file path, do save as
const result = await dialog.showSaveDialog(mainWindow, {
filters: [
{ name: 'Markdown 文件', extensions: ['md'] },
{ name: '文本文件', extensions: ['txt'] }
]
});
if (!result.canceled) {
fs.writeFileSync(result.filePath, content, 'utf-8');
currentFilePath = result.filePath;
mainWindow.setTitle(`MarkLite - ${path.basename(result.filePath)}`);
return { success: true, filePath: result.filePath };
}
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'] },
{ name: '文本文件', extensions: ['txt'] }
]
});
if (!result.canceled) {
fs.writeFileSync(result.filePath, content, 'utf-8');
currentFilePath = result.filePath;
mainWindow.setTitle(`MarkLite - ${path.basename(result.filePath)}`);
return { success: true, filePath: result.filePath };
}
return { success: false, canceled: true };
} catch (err) {
return { success: false, error: err.message };
}
});
ipcMain.handle('file:getCurrentPath', () => {
return currentFilePath;
});
// Handle file open from command line or file association
function getCommandLineFile() {
const args = process.argv.slice(1);
if (args.length > 0 && !args[0].startsWith('--')) {
const filePath = args[0];
if (fs.existsSync(filePath)) {
return filePath;
}
}
return null;
}
// App lifecycle
app.whenReady().then(() => {
// Register open-file handler BEFORE creating window (fixes race condition)
app.on('open-file', (event, filePath) => {
event.preventDefault();
if (mainWindow && mainWindow.webContents.isLoading()) {
// Window is still loading, queue the file
pendingFilePath = filePath;
} else if (mainWindow) {
openFile(filePath);
} else {
// Window not created yet, store for later
pendingFilePath = filePath;
}
});
createWindow();
// Check for file passed via command line
const fileToOpen = getCommandLineFile();
if (fileToOpen) {
pendingFilePath = fileToOpen;
}
// pendingFilePath will be opened by the did-finish-load handler in createWindow()
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});