fix: resolve critical issues found in code review

- Fix marked.js v12 code highlighting (removed deprecated highlight option, use custom renderer.code)
- Fix open-file race condition (handle file association before window ready)
- Fix Tab key undo history (use execCommand/setRangeText instead of direct value assignment)
- Fix drag-drop file path tracking (use IPC readFile instead of FileReader)
- Fix CSP security (remove unsafe-inline from script-src)
- Improve scroll sync (line-based instead of percentage-based)
- Deduplicate file open dialog logic
- Fix .gitignore duplicate entry
This commit is contained in:
thzxx
2026-05-18 10:46:22 +08:00
parent fda3e5987d
commit 76e1699ee7
4 changed files with 105 additions and 64 deletions
-1
View File
@@ -1,6 +1,5 @@
# Dependencies # Dependencies
node_modules/ node_modules/
dist/
package-lock.json package-lock.json
# OS files # OS files
+39 -26
View File
@@ -4,6 +4,7 @@ const fs = require('fs');
let mainWindow = null; let mainWindow = null;
let currentFilePath = null; let currentFilePath = null;
let pendingFilePath = null; // For file association race condition
function createWindow() { function createWindow() {
// Create the browser window // Create the browser window
@@ -120,7 +121,7 @@ function buildMenu() {
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
} }
async function handleOpenFile() { async function showOpenDialog() {
const result = await dialog.showOpenDialog(mainWindow, { const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile'], properties: ['openFile'],
filters: [ filters: [
@@ -128,9 +129,16 @@ async function handleOpenFile() {
{ name: '所有文件', extensions: ['*'] } { name: '所有文件', extensions: ['*'] }
] ]
}); });
if (!result.canceled && result.filePaths.length > 0) { if (!result.canceled && result.filePaths.length > 0) {
openFile(result.filePaths[0]); return result.filePaths[0];
}
return null;
}
async function handleOpenFile() {
const filePath = await showOpenDialog();
if (filePath) {
openFile(filePath);
} }
} }
@@ -147,16 +155,8 @@ function openFile(filePath) {
// IPC Handlers // IPC Handlers
ipcMain.handle('dialog:openFile', async () => { ipcMain.handle('dialog:openFile', async () => {
const result = await dialog.showOpenDialog(mainWindow, { const filePath = await showOpenDialog();
properties: ['openFile'], if (filePath) {
filters: [
{ name: 'Markdown 文件', extensions: ['md', 'markdown', 'txt'] },
{ name: '所有文件', extensions: ['*'] }
]
});
if (!result.canceled && result.filePaths.length > 0) {
const filePath = result.filePaths[0];
const content = fs.readFileSync(filePath, 'utf-8'); const content = fs.readFileSync(filePath, 'utf-8');
currentFilePath = filePath; currentFilePath = filePath;
mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`); mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`);
@@ -227,7 +227,7 @@ ipcMain.handle('file:getCurrentPath', () => {
}); });
// Handle file open from command line or file association // Handle file open from command line or file association
function handleFileOpen() { function getCommandLineFile() {
const args = process.argv.slice(1); const args = process.argv.slice(1);
if (args.length > 0 && !args[0].startsWith('--')) { if (args.length > 0 && !args[0].startsWith('--')) {
const filePath = args[0]; const filePath = args[0];
@@ -240,24 +240,37 @@ function handleFileOpen() {
// App lifecycle // App lifecycle
app.whenReady().then(() => { 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(); createWindow();
// Check for file passed via command line // Check for file passed via command line
const fileToOpen = handleFileOpen(); const fileToOpen = getCommandLineFile();
if (fileToOpen) { if (fileToOpen) {
// Wait for window to be ready pendingFilePath = fileToOpen;
mainWindow.webContents.on('did-finish-load', () => {
openFile(fileToOpen);
});
} }
// Handle macOS open-file event // Open pending file once window is fully ready
app.on('open-file', (event, filePath) => { if (pendingFilePath) {
event.preventDefault(); mainWindow.webContents.on('did-finish-load', () => {
if (mainWindow) { if (pendingFilePath) {
openFile(filePath); openFile(pendingFilePath);
} pendingFilePath = null;
}); }
});
}
}); });
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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:;"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self'; img-src 'self' data: https:;">
<title>MarkLite</title> <title>MarkLite</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="../lib/highlight-github.css"> <link rel="stylesheet" href="../lib/highlight-github.css">
+65 -36
View File
@@ -34,29 +34,27 @@
// ===== Initialize marked.js ===== // ===== Initialize marked.js =====
function initMarked() { function initMarked() {
if (typeof marked !== 'undefined') { if (typeof marked !== 'undefined') {
// Custom renderer for code block highlighting (compatible with marked v12+)
const renderer = new marked.Renderer();
renderer.code = function ({ text, lang }) {
let highlighted = text;
if (typeof hljs !== 'undefined') {
if (lang && hljs.getLanguage(lang)) {
try { highlighted = hljs.highlight(text, { language: lang }).value; } catch (e) { /* fallback */ }
} else {
try { highlighted = hljs.highlightAuto(text).value; } catch (e) { /* fallback */ }
}
}
const langClass = lang ? ` language-${lang}` : '';
return `<pre><code class="hljs${langClass}">${highlighted}</code></pre>`;
};
marked.setOptions({ marked.setOptions({
gfm: true, gfm: true,
breaks: true, breaks: true,
pedantic: false, 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;
}
}); });
marked.use({ renderer });
} }
} }
@@ -96,13 +94,25 @@
statusCursor.textContent = `${line}, 列 ${col}`; statusCursor.textContent = `${line}, 列 ${col}`;
} }
// ===== Sync Scroll ===== // ===== Sync Scroll (line-based for more accurate sync) =====
function syncScroll() { function syncScroll() {
if (viewMode !== 'split') return; if (viewMode !== 'split') return;
const editorEl = editor;
const previewEl = previewPanel; // Calculate which line is at the top of the editor viewport
const scrollPercent = editorEl.scrollTop / (editorEl.scrollHeight - editorEl.clientHeight); const lineHeight = parseFloat(getComputedStyle(editor).lineHeight) || 20.8;
previewEl.scrollTop = scrollPercent * (previewEl.scrollHeight - previewEl.clientHeight); const scrollTop = editor.scrollTop;
const topLine = Math.floor(scrollTop / lineHeight);
// Calculate corresponding scroll position in preview
const previewChildren = preview.children;
if (!previewChildren.length) return;
// Find the element that corresponds to the current top line
// Approximate: map editor line ratio to preview element positions
const totalLines = editor.value.split('\n').length;
const ratio = totalLines > 0 ? topLine / totalLines : 0;
const targetScrollTop = ratio * (previewPanel.scrollHeight - previewPanel.clientHeight);
previewPanel.scrollTop = targetScrollTop;
} }
// ===== Update Preview ===== // ===== Update Preview =====
@@ -287,12 +297,14 @@
document.addEventListener('dragenter', (e) => { document.addEventListener('dragenter', (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
dragCounter++; dragCounter++;
dropOverlay.classList.remove('hidden'); dropOverlay.classList.remove('hidden');
}); });
document.addEventListener('dragleave', (e) => { document.addEventListener('dragleave', (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
dragCounter--; dragCounter--;
if (dragCounter === 0) { if (dragCounter === 0) {
dropOverlay.classList.add('hidden'); dropOverlay.classList.add('hidden');
@@ -301,21 +313,36 @@
document.addEventListener('dragover', (e) => { document.addEventListener('dragover', (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
}); });
document.addEventListener('drop', (e) => { document.addEventListener('drop', async (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
dragCounter = 0; dragCounter = 0;
dropOverlay.classList.add('hidden'); dropOverlay.classList.add('hidden');
const files = e.dataTransfer.files; const files = e.dataTransfer.files;
if (files.length > 0) { if (files.length > 0) {
const file = files[0]; const file = files[0];
const reader = new FileReader(); const filePath = file.path; // Electron provides the real file path
reader.onload = (ev) => {
setEditorContent(ev.target.result, file.name); if (filePath && typeof window.electronAPI !== 'undefined') {
}; // Use IPC to read file through main process (tracks currentFilePath)
reader.readAsText(file); const result = await window.electronAPI.readFile(filePath);
if (result.success) {
setEditorContent(result.content, filePath);
} else {
statusText.textContent = '打开失败: ' + result.error;
}
} else {
// Fallback: use FileReader (browser mode)
const reader = new FileReader();
reader.onload = (ev) => {
setEditorContent(ev.target.result, file.name);
};
reader.readAsText(file);
}
} }
}); });
} }
@@ -356,16 +383,18 @@
}); });
} }
// ===== Tab key support in editor ===== // ===== Tab key support in editor (preserves undo history) =====
function initEditorTab() { function initEditorTab() {
editor.addEventListener('keydown', (e) => { editor.addEventListener('keydown', (e) => {
if (e.key === 'Tab') { if (e.key === 'Tab') {
e.preventDefault(); e.preventDefault();
const start = editor.selectionStart; // Use insertText command to preserve undo/redo history
const end = editor.selectionEnd; if (!document.execCommand('insertText', false, ' ')) {
const value = editor.value; // Fallback for environments where execCommand is unsupported
editor.value = value.substring(0, start) + ' ' + value.substring(end); const start = editor.selectionStart;
editor.selectionStart = editor.selectionEnd = start + 4; const end = editor.selectionEnd;
editor.setRangeText(' ', start, end, 'end');
}
scheduleUpdate(); scheduleUpdate();
} }
}); });