refactor: 删除分屏功能 + 代码审计修复 + 安全加固
## 删除分屏功能 - 删除 ViewMode 'split' 类型,仅保留 editor/preview - 删除 splitRatio 状态和 Resizer 组件 - 删除滚动同步模块 (scrollSync.ts, rehypeSourceLine.ts) - 更新快捷键: Ctrl+1 编辑, Ctrl+2 预览 ## Bug 修复 - 修复 Toast setTimeout 内存泄漏 (App.tsx) - 修复 ModifiedBanner reload 更新错误标签 (改用 modifiedFilePath 匹配) - 修复 FileWatcher error 未重置 isSelfWriting (file-watcher.ts) - 修复另存为时未 stop watcher (ipc-handlers.ts) ## 死代码清理 (10 项) - 删除 main/ipc-channels.ts (与 shared/ 重复) - 删除 main/file-system.ts 未使用的 formatBytes - 删除 constants.ts 6 个未使用常量 - 删除 fileUtils.ts 未使用的 formatBytes - 删除 Icons.tsx 5 个未使用图标 (SplitView/ChevronUp/ChevronDown/X/ArrowUp/ArrowDown) - 删除 useCodeMirror 未使用的 getContent/scrollTo - 删除 TabBar 未使用的 menuRef - 删除 Tab.scrollLeft/previewScrollTop 字段 - 删除 tabStore 未使用的 getTabIndex - 删除 Toast/ModifiedBanner 多余 React import ## 安全加固 - ipc-handlers: 添加路径遍历防护 (validatePath 函数) - preload: openExternal 仅允许 http/https 协议 - window-manager: 启用 sandbox: true - preload: removeAllListeners 改为精确取消订阅 (返回 Unsubscribe 函数) ## 状态持久化 - activeTabId 持久化到 IndexedDB (刷新后恢复正确标签) - sidebar 状态持久化到 IndexedDB (isVisible/sidebarWidth) ## 代码优化 - preload 使用 IPC_CHANNELS 常量替代硬编码字符串 - ipc-handlers _event 类型改为 IpcMainInvokeEvent - settingsRepository 删除 splitRatio 字段
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { useTabStore } from '../../stores/tabStore'
|
||||
import { renderMarkdown } from '../../lib/markdown'
|
||||
import { scrollPreviewToLine, getLineAtScrollOffset, invalidateScrollCache } from '../../lib/scrollSync'
|
||||
|
||||
export function Preview() {
|
||||
const [html, setHtml] = useState('')
|
||||
const previewRef = useRef<HTMLDivElement>(null)
|
||||
const requestIdRef = useRef(0)
|
||||
const isSyncingRef = useRef(false)
|
||||
|
||||
const activeTabId = useTabStore(s => s.activeTabId)
|
||||
const tabs = useTabStore(s => s.tabs)
|
||||
@@ -24,49 +22,10 @@ export function Preview() {
|
||||
renderMarkdown(activeTab.content, activeTab.filePath).then(result => {
|
||||
if (requestId === requestIdRef.current) {
|
||||
setHtml(result)
|
||||
invalidateScrollCache()
|
||||
}
|
||||
})
|
||||
}, [activeTabId, activeTab?.content])
|
||||
|
||||
// 滚动同步:监听编辑器滚动事件(VS Code 方案:行号 → 二分查找)
|
||||
useEffect(() => {
|
||||
const handleEditorScroll = (e: Event) => {
|
||||
const line = (e as CustomEvent).detail?.line
|
||||
if (typeof line !== 'number') return
|
||||
|
||||
const container = previewRef.current?.parentElement
|
||||
if (!container || isSyncingRef.current) return
|
||||
|
||||
scrollPreviewToLine(line, container, isSyncingRef)
|
||||
}
|
||||
|
||||
window.addEventListener('editor-scroll', handleEditorScroll)
|
||||
return () => window.removeEventListener('editor-scroll', handleEditorScroll)
|
||||
}, [])
|
||||
|
||||
// 反向同步:预览滚动 → 通知编辑器
|
||||
useEffect(() => {
|
||||
const container = previewRef.current?.parentElement
|
||||
if (!container) return
|
||||
|
||||
const handleScroll = () => {
|
||||
if (isSyncingRef.current) return
|
||||
|
||||
const line = getLineAtScrollOffset(container.scrollTop, container)
|
||||
if (line !== null) {
|
||||
isSyncingRef.current = true
|
||||
window.dispatchEvent(new CustomEvent('preview-scroll', { detail: { line } }))
|
||||
requestAnimationFrame(() => {
|
||||
isSyncingRef.current = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
container.addEventListener('scroll', handleScroll, { passive: true })
|
||||
return () => container.removeEventListener('scroll', handleScroll)
|
||||
}, [activeTabId])
|
||||
|
||||
// 拦截链接点击
|
||||
const handleClick = useCallback((e: React.MouseEvent) => {
|
||||
const link = (e.target as HTMLElement).closest('a')
|
||||
|
||||
Reference in New Issue
Block a user