v0.1.1: 多Agent代码质量审查+优化
- fix: useCodeMirror onChange闭包过期导致编辑器内容丢失同步(P0) - feat: 全部组件添加displayName(React DevTools调试支持) - feat: 全部组件添加ARIA属性(WCAG无障碍支持) - fix: App异步操作添加try-catch错误边界 - fix: tabStore空catch块添加错误日志 - fix: sidebarStore Set→string[](Zustand可序列化) - fix: useDragDrop readFile错误处理+空文件修复 - fix: useSettings/useTheme load()添加错误处理 - fix: useTheme toggleDarkMode引用优化 - chore: 版本号升级至0.1.1
This commit is contained in:
@@ -87,9 +87,11 @@ export function Editor({ darkMode }: EditorProps) {
|
||||
}, [viewRef])
|
||||
|
||||
return (
|
||||
<div className="editor-container">
|
||||
<div className="editor-container" role="region" aria-label="Markdown编辑器">
|
||||
<EditorToolbar viewRef={viewRef} />
|
||||
<div ref={containerRef} className="codemirror-wrapper" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Editor.displayName = 'Editor'
|
||||
@@ -17,6 +17,12 @@ export function useCodeMirror({ content, onChange, darkMode }: UseCodeMirrorOpti
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const viewRef = useRef<EditorView | null>(null)
|
||||
const isExternalUpdate = useRef(false)
|
||||
const onChangeRef = useRef(onChange)
|
||||
|
||||
// 始终保持 onChangeRef 为最新回调
|
||||
useEffect(() => {
|
||||
onChangeRef.current = onChange
|
||||
}, [onChange])
|
||||
|
||||
// 初始化编辑器
|
||||
useEffect(() => {
|
||||
@@ -61,7 +67,7 @@ export function useCodeMirror({ content, onChange, darkMode }: UseCodeMirrorOpti
|
||||
EditorView.lineWrapping,
|
||||
EditorView.updateListener.of(update => {
|
||||
if (update.docChanged && !isExternalUpdate.current) {
|
||||
onChange(update.state.doc.toString())
|
||||
onChangeRef.current(update.state.doc.toString())
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
@@ -56,8 +56,13 @@ export function Preview() {
|
||||
ref={previewRef}
|
||||
id="preview"
|
||||
className="markdown-body"
|
||||
role="region"
|
||||
aria-label="Markdown预览"
|
||||
aria-live="polite"
|
||||
onClick={handleClick}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Preview.displayName = 'Preview'
|
||||
@@ -128,7 +128,7 @@ export function Sidebar() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="sidebar-tree">
|
||||
<div id="sidebar-tree" role="tree" aria-label="文件树">
|
||||
{/* 独立文件区 */}
|
||||
{independentFiles.length > 0 && (
|
||||
<div className="independent-files-section">
|
||||
@@ -138,6 +138,7 @@ export function Sidebar() {
|
||||
key={tab.id}
|
||||
className={`tree-item independent-file-item ${tab.id === activeTabId ? 'active' : ''}`}
|
||||
style={{ paddingLeft: '8px' }}
|
||||
role="treeitem"
|
||||
onClick={() => switchToTab(tab.id)}
|
||||
>
|
||||
<span className="tree-icon">
|
||||
@@ -179,7 +180,7 @@ export function Sidebar() {
|
||||
function FileTree({ nodes, depth, expandedDirs, toggleDir, activeTabId, activeFilePath, onFileClick }: {
|
||||
nodes: FileNode[]
|
||||
depth: number
|
||||
expandedDirs: Set<string>
|
||||
expandedDirs: string[]
|
||||
toggleDir: (path: string) => void
|
||||
activeTabId: string | null
|
||||
activeFilePath: string | null
|
||||
@@ -192,6 +193,7 @@ function FileTree({ nodes, depth, expandedDirs, toggleDir, activeTabId, activeFi
|
||||
<div
|
||||
className={`tree-item ${node.type === 'file' && node.path === activeFilePath ? 'active' : ''}`}
|
||||
style={{ paddingLeft: (8 + depth * 16) + 'px' }}
|
||||
role="treeitem"
|
||||
onClick={() => {
|
||||
if (node.type === 'dir') {
|
||||
toggleDir(node.path)
|
||||
@@ -202,7 +204,7 @@ function FileTree({ nodes, depth, expandedDirs, toggleDir, activeTabId, activeFi
|
||||
>
|
||||
{node.type === 'dir' ? (
|
||||
<>
|
||||
<span className={`tree-arrow ${expandedDirs.has(node.path) ? 'expanded' : ''}`}>
|
||||
<span className={`tree-arrow ${expandedDirs.includes(node.path) ? 'expanded' : ''}`}>
|
||||
<ChevronRight size={10} />
|
||||
</span>
|
||||
<span className="tree-icon">
|
||||
@@ -219,7 +221,7 @@ function FileTree({ nodes, depth, expandedDirs, toggleDir, activeTabId, activeFi
|
||||
)}
|
||||
<span className="tree-name">{node.name}</span>
|
||||
</div>
|
||||
{node.type === 'dir' && expandedDirs.has(node.path) && node.children && (
|
||||
{node.type === 'dir' && expandedDirs.includes(node.path) && node.children && (
|
||||
<FileTree
|
||||
nodes={node.children}
|
||||
depth={depth + 1}
|
||||
@@ -235,3 +237,6 @@ function FileTree({ nodes, depth, expandedDirs, toggleDir, activeTabId, activeFi
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Sidebar.displayName = 'Sidebar'
|
||||
FileTree.displayName = 'FileTree'
|
||||
@@ -9,7 +9,7 @@ export function StatusBar() {
|
||||
const tab = tabs.find(t => t.id === activeTabId) ?? null
|
||||
|
||||
return (
|
||||
<div id="statusbar">
|
||||
<div id="statusbar" role="status" aria-live="polite">
|
||||
<div className="status-left">
|
||||
<span id="status-text">
|
||||
{tab ? (tab.filePath ? getFileName(tab.filePath) : '未命名') : '就绪'}
|
||||
@@ -23,3 +23,5 @@ export function StatusBar() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
StatusBar.displayName = 'StatusBar'
|
||||
@@ -149,11 +149,13 @@ export function TabBar() {
|
||||
|
||||
return (
|
||||
<div id="tab-bar">
|
||||
<div id="tab-list" ref={tabListRef}>
|
||||
<div id="tab-list" ref={tabListRef} role="tablist" aria-label="标签页">
|
||||
{tabs.map(tab => (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={`tab-item ${tab.id === activeTabId ? 'active' : ''} ${tab.isModified ? 'modified' : ''}`}
|
||||
role="tab"
|
||||
aria-selected={tab.id === activeTabId}
|
||||
onClick={() => switchToTab(tab.id)}
|
||||
onContextMenu={(e) => handleContextMenu(e, tab.id)}
|
||||
>
|
||||
@@ -206,3 +208,5 @@ export function TabBar() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TabBar.displayName = 'TabBar'
|
||||
@@ -13,7 +13,7 @@ interface ToolbarProps {
|
||||
|
||||
export function Toolbar({ onOpen, onSave, viewMode, onViewModeChange, darkMode, onToggleDark, onShowAbout }: ToolbarProps) {
|
||||
return (
|
||||
<div id="toolbar">
|
||||
<div id="toolbar" role="toolbar" aria-label="工具栏">
|
||||
<div className="toolbar-left">
|
||||
<button className="toolbar-btn" onClick={onOpen} title="打开文件 (Ctrl+O)">
|
||||
<FolderOpen size={18} />
|
||||
@@ -44,3 +44,5 @@ export function Toolbar({ onOpen, onSave, viewMode, onViewModeChange, darkMode,
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Toolbar.displayName = 'Toolbar'
|
||||
Reference in New Issue
Block a user