chore: bump version to 0.3.11 — 状态栏扩展点架构

This commit is contained in:
2026-06-23 11:36:46 +08:00
parent b65e34e288
commit f707b5c7ee
11 changed files with 270 additions and 75 deletions
+27 -69
View File
@@ -1,81 +1,39 @@
import React from 'react'
import { useTabStore } from '../../stores/tabStore'
import { useEditorStore } from '../../stores/editorStore'
import { useDocStats } from '../../hooks/useDocStats'
import { getFileName } from '../../lib/fileUtils'
import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner'
import React, { useMemo } from 'react'
import { useStatusBarStore, type StatusBarItem } from '../../stores/statusBarStore'
interface StatusBarProps {
isAutoSaving?: boolean
autoSaveEnabled?: boolean
onToggleAutoSave?: () => void
}
/**
* StatusBar — 纯渲染层
*
* 不包含任何业务逻辑。所有状态栏项由各功能模块通过
* useStatusBarStore.register() 注入,本组件按 alignment + priority 排序渲染。
* 每个 item.Component 是独立 React 组件,可自由使用 hooks。
*/
export const StatusBar = React.memo(function StatusBar({
isAutoSaving = false,
autoSaveEnabled = true,
onToggleAutoSave
}: StatusBarProps) {
const activeTab = useTabStore(s => s.getActiveTab())
const loadingStates = useEditorStore(s => s.loadingStates)
const stats = useDocStats(activeTab?.content)
export const StatusBar = React.memo(function StatusBar() {
const items = useStatusBarStore(s => s.items)
// 判断是否有任何加载状态激活
const hasLoading = Object.values(loadingStates).some(Boolean)
const loadingLabel = loadingStates['file-open']
? '正在打开文件...'
: loadingStates['dir-load']
? '正在加载目录...'
: loadingStates['markdown-render']
? '正在渲染...'
: ''
const sorted = useMemo(() => {
const list = Object.values(items)
if (list.length === 0) return { left: [] as StatusBarItem[], right: [] as StatusBarItem[] }
const byAlignment = (a: 'left' | 'right') =>
list.filter(i => i.alignment === a).sort((x, y) => x.priority - y.priority)
return { left: byAlignment('left'), right: byAlignment('right') }
}, [items])
return (
<div id="statusbar" role="status" aria-live="polite">
<div className="status-left">
{hasLoading && (
<span className="status-loading" aria-busy="true">
<LoadingSpinner size="small" />
<span>{loadingLabel}</span>
</span>
)}
<span id="status-text">
{activeTab ? (activeTab.filePath ? getFileName(activeTab.filePath) : '未命名') : '就绪'}
</span>
{sorted.left.map(({ id, Component }) => (
<Component key={id} />
))}
</div>
<div className="status-right">
{activeTab && (
<>
<span className="status-stat" title="行数" aria-label={`${stats.lines}`}>
{stats.lines}
</span>
<span className="status-divider">|</span>
<span className="status-stat" title="单词数" aria-label={`${stats.words} 个单词`}>
{stats.words}
</span>
<span className="status-divider">|</span>
<span className="status-stat" title="字符数(含空格)" aria-label={`${stats.chars} 个字符`}>
{stats.chars}
</span>
<span className="status-divider">|</span>
</>
)}
{activeTab?.filePath && onToggleAutoSave !== undefined && (
<>
<button
className={`status-auto-save${isAutoSaving ? ' saving' : ''}`}
title={isAutoSaving ? '正在自动保存...' : (autoSaveEnabled ? '自动保存已开启 — 点击关闭' : '自动保存已关闭 — 点击开启')}
aria-label={isAutoSaving ? '正在自动保存' : (autoSaveEnabled ? '关闭自动保存' : '开启自动保存')}
onClick={onToggleAutoSave}
>
{isAutoSaving ? '保存中...' : (autoSaveEnabled ? '自动' : '手动')}
</button>
<span className="status-divider">|</span>
</>
)}
<span id="status-encoding">UTF-8</span>
<span className="status-divider">|</span>
<span id="status-lang">Markdown</span>
{sorted.right.map(({ id, Component }, i) => (
<React.Fragment key={id}>
{i > 0 && <span className="status-divider">|</span>}
<Component />
</React.Fragment>
))}
</div>
</div>
)