diff --git a/.gitignore b/.gitignore index e090697..e7ff74d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Dependencies node_modules/ -dist/ package-lock.json # OS files diff --git a/main.js b/main.js index 9e93156..7db877e 100644 --- a/main.js +++ b/main.js @@ -4,6 +4,7 @@ const fs = require('fs'); let mainWindow = null; let currentFilePath = null; +let pendingFilePath = null; // For file association race condition function createWindow() { // Create the browser window @@ -120,7 +121,7 @@ function buildMenu() { Menu.setApplicationMenu(menu); } -async function handleOpenFile() { +async function showOpenDialog() { const result = await dialog.showOpenDialog(mainWindow, { properties: ['openFile'], filters: [ @@ -128,9 +129,16 @@ async function handleOpenFile() { { name: '所有文件', extensions: ['*'] } ] }); - 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 ipcMain.handle('dialog:openFile', async () => { - const result = await dialog.showOpenDialog(mainWindow, { - properties: ['openFile'], - filters: [ - { name: 'Markdown 文件', extensions: ['md', 'markdown', 'txt'] }, - { name: '所有文件', extensions: ['*'] } - ] - }); - - if (!result.canceled && result.filePaths.length > 0) { - const filePath = result.filePaths[0]; + const filePath = await showOpenDialog(); + if (filePath) { const content = fs.readFileSync(filePath, 'utf-8'); currentFilePath = filePath; mainWindow.setTitle(`MarkLite - ${path.basename(filePath)}`); @@ -227,7 +227,7 @@ ipcMain.handle('file:getCurrentPath', () => { }); // Handle file open from command line or file association -function handleFileOpen() { +function getCommandLineFile() { const args = process.argv.slice(1); if (args.length > 0 && !args[0].startsWith('--')) { const filePath = args[0]; @@ -240,24 +240,37 @@ function handleFileOpen() { // 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 = handleFileOpen(); + const fileToOpen = getCommandLineFile(); if (fileToOpen) { - // Wait for window to be ready - mainWindow.webContents.on('did-finish-load', () => { - openFile(fileToOpen); - }); + pendingFilePath = fileToOpen; } - // Handle macOS open-file event - app.on('open-file', (event, filePath) => { - event.preventDefault(); - if (mainWindow) { - openFile(filePath); - } - }); + // Open pending file once window is fully ready + if (pendingFilePath) { + mainWindow.webContents.on('did-finish-load', () => { + if (pendingFilePath) { + openFile(pendingFilePath); + pendingFilePath = null; + } + }); + } }); app.on('window-all-closed', () => { diff --git a/renderer/index.html b/renderer/index.html index 11b579d..c65a81c 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -3,7 +3,7 @@
- +${highlighted}`;
+ };
+
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;
- }
+ pedantic: false
});
+ marked.use({ renderer });
}
}
@@ -96,13 +94,25 @@
statusCursor.textContent = `行 ${line}, 列 ${col}`;
}
- // ===== Sync Scroll =====
+ // ===== Sync Scroll (line-based for more accurate sync) =====
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);
+
+ // Calculate which line is at the top of the editor viewport
+ const lineHeight = parseFloat(getComputedStyle(editor).lineHeight) || 20.8;
+ 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 =====
@@ -287,12 +297,14 @@
document.addEventListener('dragenter', (e) => {
e.preventDefault();
+ e.stopPropagation();
dragCounter++;
dropOverlay.classList.remove('hidden');
});
document.addEventListener('dragleave', (e) => {
e.preventDefault();
+ e.stopPropagation();
dragCounter--;
if (dragCounter === 0) {
dropOverlay.classList.add('hidden');
@@ -301,21 +313,36 @@
document.addEventListener('dragover', (e) => {
e.preventDefault();
+ e.stopPropagation();
});
- document.addEventListener('drop', (e) => {
+ document.addEventListener('drop', async (e) => {
e.preventDefault();
+ e.stopPropagation();
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);
+ const filePath = file.path; // Electron provides the real file path
+
+ if (filePath && typeof window.electronAPI !== 'undefined') {
+ // Use IPC to read file through main process (tracks currentFilePath)
+ 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() {
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;
+ // Use insertText command to preserve undo/redo history
+ if (!document.execCommand('insertText', false, ' ')) {
+ // Fallback for environments where execCommand is unsupported
+ const start = editor.selectionStart;
+ const end = editor.selectionEnd;
+ editor.setRangeText(' ', start, end, 'end');
+ }
scheduleUpdate();
}
});