release: v0.4.0 — MetonaEditor 集成与架构重构

feat(editor): 将 Milkdown/ProseMirror 替换为 @metona-team/metona-editor v0.1.3
  - 三模式视图 (edit/split/preview) 由编辑器内置工具栏切换
  - searchReplace + imagePaste 预设插件
  - unified/rehype 渲染管线通过 render 钩子集成
  - 主题双向同步 (应用暗色模式 ↔ 编辑器主题)

feat(toast): metona-toast 迁移为 @metona-team/metona-toast v2.0.1

refactor: 删除冗余组件与代码
  - 移除 SourceEditor、Preview、SearchReplace、EditorToolbar、useMilkdown
  - 移除 StatusBar 组件及状态栏扩展架构(编辑器内置底栏替代)
  - 移除 useSettings、useStatusBarItem、useStatusBarItems、statusBarStore
  - 移除 EditMode/PreviewMode/SourceMode 图标

refactor(ui): 简化布局
  - 工具栏移除模式切换按钮,新增自动保存开关
  - useKeyboard 移除 Ctrl+1/2/3/B/I 快捷键
  - Editor 三模式统一由 MetonaEditor 容器渲染

chore: 版本号 v0.3.13 → v0.4.0
docs: 全面更新 README/DESIGN/CONTRIBUTING/DEVSETUP
This commit is contained in:
thzxx
2026-07-23 22:55:45 +08:00
parent 43b0778849
commit 05e5667e48
33 changed files with 460 additions and 4335 deletions
+6 -9
View File
@@ -1,19 +1,18 @@
import { useEffect, useCallback } from 'react'
import { useTabStore } from '../stores/tabStore'
import { useEditorStore } from '../stores/editorStore'
/**
* 全局键盘快捷键 hook。
* MetonaEditor 内置工具栏处理格式化和模式切换(Ctrl+B/I/1/2/3),
* 本 hook 仅处理应用级快捷键。
*/
export function useKeyboard(handleOpenFile: () => void, handleSave: () => void, handleSaveAs: () => void) {
const setViewMode = useEditorStore(s => s.setViewMode)
const handleKeydown = useCallback((e: KeyboardEvent) => {
const isCtrl = e.ctrlKey || e.metaKey
if (isCtrl && e.key === 'o') { e.preventDefault(); handleOpenFile(); return }
if (isCtrl && e.key === 's' && !e.shiftKey) { e.preventDefault(); handleSave(); return }
if (isCtrl && e.shiftKey && e.key === 'S') { e.preventDefault(); handleSaveAs(); return }
if (isCtrl && e.key === '1') { e.preventDefault(); setViewMode('editor'); return }
if (isCtrl && e.key === '2') { e.preventDefault(); setViewMode('preview'); return }
if (isCtrl && e.key === '3') { e.preventDefault(); setViewMode('source'); return }
const tabState = useTabStore.getState()
if (isCtrl && e.key === 't') { e.preventDefault(); tabState.createTab(null, ''); return }
@@ -28,7 +27,6 @@ export function useKeyboard(handleOpenFile: () => void, handleSave: () => void,
e.preventDefault()
const { tabs, activeTabId, mruStack } = tabState
if (tabs.length > 1) {
// mruStack[0] 是最近一个被切走的标签(即上一个活动标签)
if (mruStack.length > 0) {
const targetId = mruStack[0]
if (tabs.find(t => t.id === targetId)) {
@@ -36,7 +34,6 @@ export function useKeyboard(handleOpenFile: () => void, handleSave: () => void,
return
}
}
// MRU 栈为空或不合法时回退到顺序切换
const idx = tabs.findIndex(t => t.id === activeTabId)
const next = e.shiftKey
? (idx - 1 + tabs.length) % tabs.length
@@ -45,7 +42,7 @@ export function useKeyboard(handleOpenFile: () => void, handleSave: () => void,
}
return
}
}, [handleOpenFile, handleSave, handleSaveAs, setViewMode])
}, [handleOpenFile, handleSave, handleSaveAs])
useEffect(() => {
document.addEventListener('keydown', handleKeydown)
-21
View File
@@ -1,21 +0,0 @@
import { useCallback } from 'react'
import { useEditorStore } from '../stores/editorStore'
import { settingsRepository } from '../db/settingsRepository'
import type { ViewMode } from '../types/settings'
/**
* AR-04: 设置 hook
* 不再独立加载设置(由 useSettingsInit 统一加载)
* 仅负责视图模式的读取和保存
*/
export function useSettings() {
const viewMode = useEditorStore(s => s.viewMode)
const setViewMode = useEditorStore(s => s.setViewMode)
const saveViewMode = useCallback((mode: ViewMode) => {
setViewMode(mode)
settingsRepository.save({ viewMode: mode })
}, [setViewMode])
return { viewMode, saveViewMode }
}
+2 -2
View File
@@ -6,8 +6,8 @@ import { logError } from '../lib/errorHandler'
/**
* AR-04: 统一设置加载 hook
* 一次性从 IndexedDB 加载所有设置,分发到各 store
* 替代 useTheme、useSettings、sidebarStore.loadFromDB 各自独立加载的模式
* 一次性从 IndexedDB 加载所有设置,分发到各 store
* 替代各 hook 各自独立加载设置的模式
*/
export function useSettingsInit() {
const setDarkMode = useEditorStore(s => s.setDarkMode)
-21
View File
@@ -1,21 +0,0 @@
import { useEffect } from 'react'
import { useStatusBarStore, type StatusBarItem } from '../stores/statusBarStore'
/**
* 便捷 hook — 挂载时注册一个状态栏项,卸载时自动清除。
*
* 适合在 App.tsx 或自定义 hook 中注册需要响应式更新的状态栏项。
* 当 item 对象变化时(通过 deps 控制),旧项会被新项替换。
*
* @param item - 状态栏项(含 Component 函数组件)
*/
export function useStatusBarItem(item: StatusBarItem | null): void {
const register = useStatusBarStore(s => s.register)
const unregister = useStatusBarStore(s => s.unregister)
useEffect(() => {
if (!item) return
register(item)
return () => unregister(item.id)
}, [item, register, unregister])
}
-36
View File
@@ -1,36 +0,0 @@
import { useEffect } from 'react'
import { useStatusBarStore } from '../stores/statusBarStore'
import {
FileInfoItem,
LoadingItem,
DocStatsItem,
EncodingItem,
LangItem
} from '../components/StatusBar/StatusBarItems'
/**
* useDefaultStatusBarItems — 注册所有默认状态栏项(不含 auto-save)。
*
* auto-save 项由 App.tsx 单独注册,因为它需要从 autoSaveStore
* 读取状态并通过 toggleAutoSaveExternal() 触发切换。
*/
export function useDefaultStatusBarItems() {
const register = useStatusBarStore(s => s.register)
const unregister = useStatusBarStore(s => s.unregister)
useEffect(() => {
register({ id: 'statusbar.file-info', alignment: 'left', priority: 0, Component: FileInfoItem })
register({ id: 'statusbar.loading', alignment: 'left', priority: 1, Component: LoadingItem })
register({ id: 'statusbar.doc-stats', alignment: 'right', priority: 100, Component: DocStatsItem })
register({ id: 'statusbar.encoding', alignment: 'right', priority: 300, Component: EncodingItem })
register({ id: 'statusbar.lang', alignment: 'right', priority: 400, Component: LangItem })
return () => {
unregister('statusbar.file-info')
unregister('statusbar.loading')
unregister('statusbar.doc-stats')
unregister('statusbar.encoding')
unregister('statusbar.lang')
}
}, [register, unregister])
}