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:
+65
-36
@@ -34,29 +34,27 @@
|
||||
// ===== Initialize marked.js =====
|
||||
function initMarked() {
|
||||
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({
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user