v0.3.10: 全面优化增强 — 17项Bug修复/稳定性/功能改进
P0 致命Bug修复: - A1: openFolderDialog 类型/运行时崩溃(文件夹打开功能完全失效) - A2: SourceEditor 受控textarea手动改DOM反模式 - A3: tabStore.updateTabContent 内容相同时误标isModified 死代码清理: - B1: 删除menu:*/save/as/viewMode 三个永不触发的IPC通道 健壮性修复: - E1: rehypeFixImages 路径规范化+防越界加固 - E2: getFileName 尾斜杠返回正确文件名 - E3: EditorToolbar 行内代码按钮改用toggleInlineCodeCommand - E4: ErrorBoundary 内联样式抽为CSS类 功能增强: - D1: 标签页拖拽排序(tabStore.moveTab + TabBar DnD + CSS) - D2: Milkdown自动配对括号/引号(ProseMirror插件) - D3: 状态栏自动保存开关可点击 - D4: 文档大纲活跃标题高亮(useActiveHeading hook) - D5: 关闭窗口前flush防抖数据(flushSaveToDB) - D6: 搜索替换支持正则表达式 类型/架构: - C2: preload类型集中定义(ElectronAPI契约) - __pycache__ typo修复 文档/版本: - README/DESIGN同步为Milkdown + v0.3.10 - 项目结构树/技术栈/快捷键表更新 验证: typecheck(仅7预存), lint✅, test 96/96, vite build✅
This commit is contained in:
@@ -21,9 +21,12 @@ export const TabBar = React.memo(function TabBar() {
|
||||
const closeOtherTabs = useTabStore(s => s.closeOtherTabs)
|
||||
const closeAllTabs = useTabStore(s => s.closeAllTabs)
|
||||
const closeTabsToRight = useTabStore(s => s.closeTabsToRight)
|
||||
const moveTab = useTabStore(s => s.moveTab)
|
||||
|
||||
const tabListRef = useRef<HTMLDivElement>(null)
|
||||
const [menu, setMenu] = useState<ContextMenuState>({ visible: false, x: 0, y: 0, tabId: '' })
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null)
|
||||
const dragTabIdRef = useRef<string | null>(null)
|
||||
const { confirm, confirmDialogProps } = useConfirm()
|
||||
|
||||
// 滚动到活动标签
|
||||
@@ -173,6 +176,46 @@ export const TabBar = React.memo(function TabBar() {
|
||||
setMenu(prev => ({ ...prev, visible: false }))
|
||||
}, [tabs, menu.tabId, closeTabsToRight, confirm])
|
||||
|
||||
// D1: 拖拽排序事件处理
|
||||
const handleDragStart = useCallback((e: React.DragEvent, tabId: string) => {
|
||||
dragTabIdRef.current = tabId
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
e.dataTransfer.setData('text/plain', tabId)
|
||||
// 延迟添加 dragging 类,避免拖拽图像被 CSS 捕获
|
||||
requestAnimationFrame(() => {
|
||||
const el = document.querySelector(`[data-tab-id="${tabId}"]`) as HTMLElement
|
||||
el?.classList.add('dragging')
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent, index: number) => {
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
setDragOverIndex(index)
|
||||
}, [])
|
||||
|
||||
const handleDragLeave = useCallback(() => {
|
||||
setDragOverIndex(null)
|
||||
}, [])
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent, toIndex: number) => {
|
||||
e.preventDefault()
|
||||
setDragOverIndex(null)
|
||||
const fromId = dragTabIdRef.current
|
||||
if (fromId) {
|
||||
moveTab(fromId, toIndex)
|
||||
}
|
||||
dragTabIdRef.current = null
|
||||
// 清理 dragging 类
|
||||
document.querySelectorAll('.tab-item.dragging').forEach(el => el.classList.remove('dragging'))
|
||||
}, [moveTab])
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
setDragOverIndex(null)
|
||||
document.querySelectorAll('.tab-item.dragging').forEach(el => el.classList.remove('dragging'))
|
||||
dragTabIdRef.current = null
|
||||
}, [])
|
||||
|
||||
const hasRightTabs = menu.visible && (() => {
|
||||
const index = tabs.findIndex(t => t.id === menu.tabId)
|
||||
return index < tabs.length - 1
|
||||
@@ -184,15 +227,22 @@ export const TabBar = React.memo(function TabBar() {
|
||||
<>
|
||||
<div id="tab-bar">
|
||||
<div id="tab-list" ref={tabListRef} role="tablist" aria-label="标签页">
|
||||
{tabs.map(tab => (
|
||||
{tabs.map((tab, index) => (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={`tab-item ${tab.id === activeTabId ? 'active' : ''} ${tab.isModified ? 'modified' : ''}`}
|
||||
className={`tab-item ${tab.id === activeTabId ? 'active' : ''} ${tab.isModified ? 'modified' : ''} ${dragOverIndex === index ? 'drag-over' : ''}`}
|
||||
role="tab"
|
||||
aria-selected={tab.id === activeTabId}
|
||||
tabIndex={tab.id === activeTabId ? 0 : -1}
|
||||
data-tab-id={tab.id}
|
||||
draggable
|
||||
onClick={() => switchToTab(tab.id)}
|
||||
onContextMenu={(e) => handleContextMenu(e, tab.id)}
|
||||
onDragStart={(e) => handleDragStart(e, tab.id)}
|
||||
onDragOver={(e) => handleDragOver(e, index)}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={(e) => handleDrop(e, index)}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<span className="tab-name">
|
||||
{tab.filePath ? getFileName(tab.filePath) : '未命名'}
|
||||
|
||||
Reference in New Issue
Block a user