diff --git a/renderer/core.js b/renderer/core.js deleted file mode 100644 index 1c177d4..0000000 --- a/renderer/core.js +++ /dev/null @@ -1,454 +0,0 @@ -// MarkLite — Core Module: state, DOM refs, settings, utilities, init orchestration -(function () { - 'use strict'; - - // ===== Shared Namespace ===== - const ML = {}; - window.MarkLite = ML; - - // ===== DOM Elements ===== - ML.editor = document.getElementById('editor'); - ML.preview = document.getElementById('preview'); - ML.lineNumbers = document.getElementById('line-numbers'); - ML.welcomeScreen = document.getElementById('welcome-screen'); - ML.dropOverlay = document.getElementById('drop-overlay'); - ML.modifiedBanner = document.getElementById('modified-banner'); - ML.statusText = document.getElementById('status-text'); - ML.statusCursor = document.getElementById('status-cursor'); - ML.statusSize = document.getElementById('status-size'); - ML.statusSizeDivider = document.querySelector('.status-size-divider'); - ML.resizer = document.getElementById('resizer'); - ML.editorPanel = document.getElementById('editor-panel'); - ML.previewPanel = document.getElementById('preview-panel'); - ML.mainContent = document.getElementById('main-content'); - ML.contentWrapper = document.getElementById('content-wrapper'); - ML.tabList = document.getElementById('tab-list'); - - // Sidebar - ML.sidebar = document.getElementById('sidebar'); - ML.sidebarTree = document.getElementById('sidebar-tree'); - ML.btnOpenFolder = document.getElementById('btn-open-folder'); - - // Search - ML.searchBar = document.getElementById('search-bar'); - ML.searchInput = document.getElementById('search-input'); - ML.replaceInput = document.getElementById('replace-input'); - ML.searchCount = document.getElementById('search-count'); - ML.replaceRow = document.getElementById('replace-row'); - ML.btnCase = document.getElementById('btn-case'); - ML.btnRegex = document.getElementById('btn-regex'); - ML.btnNext = document.getElementById('btn-next'); - ML.btnPrev = document.getElementById('btn-prev'); - ML.btnSearchClose = document.getElementById('btn-search-close'); - ML.btnReplace = document.getElementById('btn-replace'); - ML.btnReplaceAll = document.getElementById('btn-replace-all'); - - // Buttons - ML.btnOpen = document.getElementById('btn-open'); - ML.btnSave = document.getElementById('btn-save'); - ML.btnSplit = document.getElementById('btn-split'); - ML.btnEditor = document.getElementById('btn-editor'); - ML.btnPreview = document.getElementById('btn-preview'); - ML.btnWelcomeOpen = document.getElementById('btn-welcome-open'); - ML.btnWelcomeNew = document.getElementById('btn-welcome-new'); - ML.btnDark = document.getElementById('btn-dark'); - ML.btnReload = document.getElementById('btn-reload'); - ML.btnDismiss = document.getElementById('btn-dismiss'); - ML.btnNewTab = document.getElementById('btn-new-tab'); - - // ===== Shared State ===== - ML.viewMode = 'split'; - ML.updateTimer = null; - ML.lineCountCache = 0; - ML.currentRootPath = null; - ML.MAX_FILE_SIZE = 20 * 1024 * 1024; - - // ===== Settings ===== - ML.loadSettings = function () { - try { return JSON.parse(localStorage.getItem('marklite-settings') || '{}'); } - catch { return {}; } - }; - ML.saveSetting = function (key, value) { - try { - const s = ML.loadSettings(); - s[key] = value; - localStorage.setItem('marklite-settings', JSON.stringify(s)); - } catch { /* ignore */ } - }; - - // ===== Utilities ===== - ML.getFileName = function (filePath) { - return filePath.split(/[/\\]/).pop(); - }; - - ML.formatBytes = function (bytes) { - if (bytes < 1024) return bytes + ' B'; - if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; - return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; - }; - - // Character width measurement - var _charWidthCanvas = document.createElement('canvas'); - var _charWidthCtx = _charWidthCanvas.getContext('2d'); - var _cachedCharWidth = null; - ML.getCharWidth = function () { - if (_cachedCharWidth !== null) return _cachedCharWidth; - var style = getComputedStyle(ML.editor); - _charWidthCtx.font = style.fontSize + ' ' + style.fontFamily; - _cachedCharWidth = _charWidthCtx.measureText('M').width; - return _cachedCharWidth; - }; - new MutationObserver(function () { _cachedCharWidth = null; }) - .observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); - - // Toast notification - ML.showToast = function (message, duration) { - duration = duration || 3000; - var toast = document.getElementById('toast-notification'); - if (!toast) { - toast = document.createElement('div'); - toast.id = 'toast-notification'; - document.getElementById('app').appendChild(toast); - } - toast.textContent = message; - toast.classList.add('show'); - clearTimeout(toast._timer); - toast._timer = setTimeout(function () { toast.classList.remove('show'); }, duration); - }; - - // ===== Dark Mode ===== - ML.initDarkMode = function () { - var settings = ML.loadSettings(); - var iconDark = document.getElementById('icon-dark'); - var iconLight = document.getElementById('icon-light'); - function apply(isDark) { - document.documentElement.classList.toggle('dark', isDark); - iconDark.style.display = isDark ? 'none' : ''; - iconLight.style.display = isDark ? '' : 'none'; - } - var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - apply(settings.darkMode !== undefined ? !!settings.darkMode : prefersDark); - ML.btnDark.addEventListener('click', function () { - var isDark = !document.documentElement.classList.contains('dark'); - apply(isDark); - ML.saveSetting('darkMode', isDark); - }); - }; - - // ===== File Operations ===== - ML.handleOpenFile = async function () { - if (typeof window.electronAPI !== 'undefined') { - var result = await window.electronAPI.openFile(); - if (result && !result.error) { - ML.createNewTab(result.filePath, result.content); - } - } else { - var input = document.createElement('input'); - input.type = 'file'; - input.accept = '.md,.markdown,.txt'; - input.multiple = true; - input.onchange = function (e) { - Array.from(e.target.files).forEach(function (file) { - var ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase(); - if (!['.md', '.markdown', '.txt'].includes(ext)) return; - if (file.size > ML.MAX_FILE_SIZE) { - alert('"' + file.name + '" 过大(' + ML.formatBytes(file.size) + '),暂不支持超过 20MB 的文件'); - return; - } - var reader = new FileReader(); - reader.onload = function (ev) { ML.createNewTab(file.name, ev.target.result); }; - reader.readAsText(file); - }); - }; - input.click(); - } - }; - - ML.handleSave = async function () { - var tab = ML.getActiveTab(); - if (!tab) return; - if (typeof window.electronAPI !== 'undefined') { - var result = await window.electronAPI.saveFile({ filePath: tab.filePath, content: ML.editor.value }); - if (result.success) { - tab.filePath = result.filePath; - tab.content = ML.editor.value; - tab.isModified = false; - ML.renderTabBar(); - ML.updateTitle(); - ML.updateSidebarHighlight(); - ML.statusText.textContent = '已保存'; - setTimeout(function () { ML.updateTitle(); }, 2000); - } - } else { - var blob = new Blob([ML.editor.value], { type: 'text/markdown' }); - var a = document.createElement('a'); - a.href = URL.createObjectURL(blob); - a.download = tab.filePath || 'untitled.md'; - a.click(); - setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000); - tab.isModified = false; - ML.renderTabBar(); - ML.updateTitle(); - } - }; - - ML.handleSaveAs = async function () { - var tab = ML.getActiveTab(); - if (!tab) return; - if (typeof window.electronAPI !== 'undefined') { - var result = await window.electronAPI.saveFileAs({ content: ML.editor.value }); - if (result.success) { - tab.filePath = result.filePath; - tab.content = ML.editor.value; - tab.isModified = false; - ML.renderTabBar(); - ML.updateTitle(); - ML.updateSidebarHighlight(); - ML.statusText.textContent = '已保存'; - setTimeout(function () { ML.updateTitle(); }, 2000); - } - } else { - ML.handleSave(); - } - }; - - // ===== View Mode ===== - ML.setViewMode = function (mode) { - ML.viewMode = mode; - var app = document.getElementById('app'); - app.classList.remove('mode-editor', 'mode-preview'); - ML.btnSplit.classList.remove('active'); - ML.btnEditor.classList.remove('active'); - ML.btnPreview.classList.remove('active'); - switch (mode) { - case 'split': ML.btnSplit.classList.add('active'); break; - case 'editor': ML.btnEditor.classList.add('active'); app.classList.add('mode-editor'); break; - case 'preview': ML.btnPreview.classList.add('active'); app.classList.add('mode-preview'); ML.renderMarkdown(ML.editor.value); break; - } - ML.saveSetting('viewMode', mode); - }; - - // ===== Resizer ===== - ML.initResizer = function () { - var settings = ML.loadSettings(); - if (settings.splitRatio) { - ML.editorPanel.style.flex = '0 0 ' + settings.splitRatio + '%'; - ML.previewPanel.style.flex = '0 0 ' + (100 - settings.splitRatio) + '%'; - } - var isResizing = false; - ML.resizer.addEventListener('mousedown', function (e) { - isResizing = true; - ML.resizer.classList.add('active'); - document.body.style.cursor = 'col-resize'; - document.body.style.userSelect = 'none'; - e.preventDefault(); - }); - document.addEventListener('mousemove', function (e) { - if (!isResizing) return; - var rect = ML.contentWrapper.getBoundingClientRect(); - var pct = Math.max(20, Math.min(80, ((e.clientX - rect.left) / rect.width) * 100)); - ML.editorPanel.style.flex = '0 0 ' + pct + '%'; - ML.previewPanel.style.flex = '0 0 ' + (100 - pct) + '%'; - ML.saveSetting('splitRatio', pct); - }); - document.addEventListener('mouseup', function () { - if (isResizing) { - isResizing = false; - ML.resizer.classList.remove('active'); - document.body.style.cursor = ''; - document.body.style.userSelect = ''; - } - }); - }; - - // ===== Drag & Drop ===== - ML.initDragDrop = function () { - var dragCounter = 0; - var allowedExts = ['.md', '.markdown', '.txt']; - document.addEventListener('dragenter', function (e) { e.preventDefault(); e.stopPropagation(); dragCounter++; ML.dropOverlay.classList.remove('hidden'); }); - document.addEventListener('dragleave', function (e) { e.preventDefault(); e.stopPropagation(); dragCounter--; if (dragCounter === 0) ML.dropOverlay.classList.add('hidden'); }); - document.addEventListener('dragover', function (e) { e.preventDefault(); e.stopPropagation(); }); - document.addEventListener('drop', async function (e) { - e.preventDefault(); e.stopPropagation(); dragCounter = 0; ML.dropOverlay.classList.add('hidden'); - var files = e.dataTransfer.files; - var rejected = 0; - for (var _i = 0; _i < files.length; _i++) { - var file = files[_i]; - var filePath = file.path; - var ext = filePath ? filePath.substring(filePath.lastIndexOf('.')).toLowerCase() : ''; - if (!allowedExts.includes(ext)) { rejected++; continue; } - if (file.size > ML.MAX_FILE_SIZE) { ML.showToast('"' + file.name + '" 过大(' + ML.formatBytes(file.size) + '),暂不支持超过 20MB 的文件'); continue; } - if (filePath && typeof window.electronAPI !== 'undefined') { - var result = await window.electronAPI.readFile(filePath); - if (result.success) ML.createNewTab(filePath, result.content); - } else { - var reader = new FileReader(); - reader.onload = function (ev) { ML.createNewTab(file.name, ev.target.result); }; - reader.readAsText(file); - } - } - if (rejected > 0) ML.showToast('仅支持 .md / .markdown / .txt 文件,已忽略 ' + rejected + ' 个文件'); - }); - }; - - // ===== Keyboard Shortcuts ===== - ML.initKeyboard = function () { - document.addEventListener('keydown', function (e) { - if (e.ctrlKey && e.key === 'o') { e.preventDefault(); ML.handleOpenFile(); } - if (e.ctrlKey && e.key === 's' && !e.shiftKey) { e.preventDefault(); ML.handleSave(); } - if (e.ctrlKey && e.shiftKey && e.key === 'S') { e.preventDefault(); ML.handleSaveAs(); } - if (e.ctrlKey && e.key === '1') { e.preventDefault(); ML.setViewMode('split'); } - if (e.ctrlKey && e.key === '2') { e.preventDefault(); ML.setViewMode('editor'); } - if (e.ctrlKey && e.key === '3') { e.preventDefault(); ML.setViewMode('preview'); } - if (e.ctrlKey && e.key === 't') { e.preventDefault(); ML.createNewTab(null, ''); } - if (e.ctrlKey && e.key === 'w') { e.preventDefault(); if (ML.activeTabId) ML.closeTab(ML.activeTabId); } - if (e.ctrlKey && e.key === 'Tab') { - e.preventDefault(); - if (ML.tabs.length > 1) { - if (e.shiftKey) { - if (ML.mruTabStack.length > 0) { - var targetId = ML.mruTabStack.shift(); - if (ML.tabs.find(function (t) { return t.id === targetId; })) ML.switchToTab(targetId); - } - } else { - var idx = ML.getTabIndex(ML.activeTabId); - var next = (idx + 1) % ML.tabs.length; - ML.switchToTab(ML.tabs[next].id); - } - } - } - if (e.ctrlKey && e.key === 'f') { e.preventDefault(); ML.openSearch(false); } - if (e.ctrlKey && e.key === 'h') { e.preventDefault(); ML.openSearch(true); } - if (e.key === 'Escape' && !ML.searchBar.classList.contains('hidden')) { e.preventDefault(); ML.closeSearch(); } - if (e.key === 'Enter' && document.activeElement === ML.searchInput) { e.preventDefault(); e.shiftKey ? ML.findPrev() : ML.findNext(); } - if (e.altKey && e.key === 'c') { e.preventDefault(); ML.toggleSearchOption('case'); } - if (e.altKey && e.key === 'r') { e.preventDefault(); ML.toggleSearchOption('regex'); } - if (e.ctrlKey && e.shiftKey && e.key === 'H') { e.preventDefault(); ML.replaceAll(); } - if (e.ctrlKey && e.shiftKey && e.key === 'G') { e.preventDefault(); ML.replaceCurrent(); } - }); - }; - - // ===== Tab Key Support ===== - ML.initEditorTab = function () { - ML.editor.addEventListener('keydown', function (e) { - if (e.key === 'Tab') { - e.preventDefault(); - var start = ML.editor.selectionStart; - var end = ML.editor.selectionEnd; - if (start !== end) { - var lines = ML.editor.value.substring(start, end).split('\n'); - var indented = lines.map(function (line) { return ' ' + line; }).join('\n'); - ML.editor.setRangeText(indented, start, end, 'end'); - } else { - ML.editor.setRangeText(' ', start, end, 'end'); - } - ML.scheduleUpdate(); - } - }); - }; - - // ===== External File Modification ===== - ML.initFileWatch = function () { - if (typeof window.electronAPI === 'undefined') return; - window.electronAPI.onExternalModification(function (filePath) { - var tab = ML.getActiveTab(); - if (tab && tab.filePath === filePath) ML.modifiedBanner.classList.remove('hidden'); - }); - ML.btnReload.addEventListener('click', async function () { - var tab = ML.getActiveTab(); - if (!tab || !tab.filePath) return; - var result = await window.electronAPI.reloadFile(); - if (result.success) { - ML.editor.value = result.content; - tab.content = result.content; - tab.isModified = false; - ML.lineCountCache = 0; - ML.updateLineNumbers(); - ML.renderMarkdown(result.content); - ML.updateCursorPosition(); - ML.updateFileSize(); - ML.cachePreviewPositions(); - ML.renderTabBar(); - ML.updateTitle(); - } - ML.modifiedBanner.classList.add('hidden'); - }); - ML.btnDismiss.addEventListener('click', function () { ML.modifiedBanner.classList.add('hidden'); }); - }; - - // ===== Unsaved Changes Warning ===== - ML.initUnsavedWarning = function () { - var hasUnsaved = function () { return ML.tabs.some(function (t) { return t.isModified; }); }; - if (typeof window.electronAPI === 'undefined') { - window.addEventListener('beforeunload', function (e) { if (hasUnsaved()) { e.preventDefault(); e.returnValue = ''; } }); - return; - } - window.electronAPI.onConfirmClose(function () { - if (!hasUnsaved()) { window.electronAPI.forceClose(); return; } - var shouldClose = confirm('有文件尚未保存,确定要关闭吗?'); - if (shouldClose) window.electronAPI.forceClose(); - else window.electronAPI.cancelClose(); - }); - }; - - // ===== Event Listeners ===== - ML.initEventListeners = function () { - ML.editor.addEventListener('input', function () { - var tab = ML.getActiveTab(); - if (tab) { tab.isModified = true; tab.content = ML.editor.value; ML.setModified(true); } - ML.scheduleUpdate(); - }); - ML.editor.addEventListener('scroll', function () { - ML.syncScroll(); - ML.lineNumbers.scrollTop = ML.editor.scrollTop; - if (ML.lineCountCache > 2000) ML.renderVirtualLineNumbers(ML.lineCountCache); - }); - ML.editor.addEventListener('click', ML.updateCursorPosition); - ML.editor.addEventListener('keyup', ML.updateCursorPosition); - ML.editor.addEventListener('select', ML.updateCursorPosition); - - ML.btnOpen.addEventListener('click', ML.handleOpenFile); - ML.btnSave.addEventListener('click', ML.handleSave); - ML.btnSplit.addEventListener('click', function () { ML.setViewMode('split'); }); - ML.btnEditor.addEventListener('click', function () { ML.setViewMode('editor'); }); - ML.btnPreview.addEventListener('click', function () { ML.setViewMode('preview'); }); - ML.btnNewTab.addEventListener('click', function () { ML.createNewTab(null, ''); }); - ML.btnWelcomeOpen.addEventListener('click', ML.handleOpenFile); - ML.btnWelcomeNew.addEventListener('click', function () { ML.createNewTab(null, ''); }); - - if (typeof window.electronAPI !== 'undefined') { - window.electronAPI.removeAllListeners('file:openInTab'); - window.electronAPI.removeAllListeners('menu:save'); - window.electronAPI.removeAllListeners('menu:saveAs'); - window.electronAPI.removeAllListeners('menu:viewMode'); - window.electronAPI.onFileOpenInTab(function (data) { ML.createNewTab(data.filePath, data.content); }); - window.electronAPI.onMenuSave(function () { ML.handleSave(); }); - window.electronAPI.onMenuSaveAs(function () { ML.handleSaveAs(); }); - window.electronAPI.onViewModeChange(function (mode) { ML.setViewMode(mode); }); - } - }; - - // ===== Init ===== - ML.init = function () { - ML.initMarked(); - ML.initDarkMode(); - ML.initResizer(); - ML.initDragDrop(); - ML.initKeyboard(); - ML.initEditorTab(); - ML.initPreviewLinks(); - ML.initEventListeners(); - ML.initFileWatch(); - ML.initUnsavedWarning(); - ML.initSidebar(); - ML.initSearchBar(); - var settings = ML.loadSettings(); - ML.setViewMode(settings.viewMode || 'split'); - }; - - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', ML.init); - } else { - ML.init(); - } -})(); diff --git a/renderer/editor.js b/renderer/editor.js deleted file mode 100644 index fd3fa37..0000000 --- a/renderer/editor.js +++ /dev/null @@ -1,147 +0,0 @@ -// MarkLite — Editor Module: line numbers, cursor, file size, scroll sync -(function () { - 'use strict'; - var ML = window.MarkLite; - - // ===== Line Numbers ===== - function countNewlines(text) { - var count = 1; - for (var i = 0, len = text.length; i < len; i++) { - if (text.charCodeAt(i) === 10) count++; - } - return count; - } - - ML.updateLineNumbers = function () { - var count = countNewlines(ML.editor.value); - if (count === ML.lineCountCache) return; - ML.lineCountCache = count; - if (count > 2000) { - ML.renderVirtualLineNumbers(count); - } else { - var arr = new Array(count); - for (var i = 0; i < count; i++) arr[i] = '