From 0054e97a0ad9db5c04da3390df205839ee676843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Thu, 28 May 2026 22:15:56 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 options 入口点目录及相关文件 - 移除 wxt.config.ts 中 options_ui 配置 - 移除 TopBar 组件中的设置按钮和 onOpenOptions prop - 移除 popup/sidepanel App.tsx 中的 handleOpenOptions - 移除相关测试中的设置页面引用 - 移除 messages.json 中设置相关翻译键 - 通过 lint、typecheck、test 验证 --- public/_locales/zh/messages.json | 10 +- src/components/TopBar.tsx | 7 +- src/components/__tests__/TopBar.test.tsx | 19 +- src/entrypoints/options/App.tsx | 383 ------------------ .../options/__tests__/App.test.tsx | 125 ------ src/entrypoints/options/index.html | 12 - src/entrypoints/options/main.tsx | 10 - src/entrypoints/popup/App.tsx | 6 +- src/entrypoints/sidepanel/App.tsx | 8 +- src/utils/__tests__/chromeTabs.test.ts | 8 - wxt.config.ts | 4 - 11 files changed, 9 insertions(+), 583 deletions(-) delete mode 100644 src/entrypoints/options/App.tsx delete mode 100644 src/entrypoints/options/__tests__/App.test.tsx delete mode 100644 src/entrypoints/options/index.html delete mode 100644 src/entrypoints/options/main.tsx diff --git a/public/_locales/zh/messages.json b/public/_locales/zh/messages.json index b4ce328..720c853 100644 --- a/public/_locales/zh/messages.json +++ b/public/_locales/zh/messages.json @@ -7,10 +7,7 @@ "message": "清理", "description": "Translation key: buttons_clear" }, - "buttons_settings": { - "message": "设置", - "description": "Translation key: buttons_settings" - }, + "messages_copySuccess": { "message": "已复制到剪贴板", "description": "Translation key: messages_copySuccess" @@ -851,10 +848,7 @@ "message": "在标签页打开", "description": "Translation key: buttons_openInTab" }, - "common_buttons_settings": { - "message": "设置", - "description": "Translation key: buttons_settings" - }, + "common_errorBoundary_title": { "message": "糟糕,出了点问题", "description": "Translation key: errorBoundary_title" diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index fc1adad..919c1a7 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { ArrowLeft, ExternalLink, Monitor, Moon, Search, Settings, Sun, X } from 'lucide-react'; +import { ArrowLeft, ExternalLink, Monitor, Moon, Search, Sun, X } from 'lucide-react'; import { useRouter } from '@/providers/RouterProvider'; import { useThemeMode } from '@/providers/ThemeModeProvider'; import { FeatureConfig, FEATURES } from '@/config/features'; @@ -11,7 +11,7 @@ import { cn } from '@/lib/utils'; const SEARCH_HISTORY_LIMIT = 10; const SEARCH_HISTORY_DISPLAY = 5; -export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void }) { +export default function TopBar() { const { currentPage, goBack, navigateTo } = useRouter(); const { mode, setMode } = useThemeMode(); const { t } = useI18n(['common', 'features']); @@ -284,9 +284,6 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void }) - - - ); diff --git a/src/components/__tests__/TopBar.test.tsx b/src/components/__tests__/TopBar.test.tsx index e3b9460..28366cc 100644 --- a/src/components/__tests__/TopBar.test.tsx +++ b/src/components/__tests__/TopBar.test.tsx @@ -53,34 +53,21 @@ describe('TopBar 组件', () => { describe('渲染测试', () => { it('不在 dashboard 时应渲染返回按钮', () => { mockRouterValue.currentPage = 'timestamp'; - renderWithProvider(); + renderWithProvider(); expect(screen.getByLabelText('返回')).toBeInTheDocument(); }); it('在 dashboard 上不应渲染返回按钮', () => { mockRouterValue.currentPage = 'dashboard'; - renderWithProvider(); + renderWithProvider(); expect(screen.queryByLabelText('返回')).not.toBeInTheDocument(); }); - - it('应渲染设置按钮', () => { - renderWithProvider(); - expect(screen.getByLabelText('设置')).toBeInTheDocument(); - }); }); describe('交互测试', () => { - it('点击设置按钮时应调用 onOpenOptions', () => { - const handleOpenOptions = vi.fn(); - renderWithProvider(); - - fireEvent.click(screen.getByLabelText('设置')); - expect(handleOpenOptions).toHaveBeenCalledTimes(1); - }); - it('点击返回按钮时应调用 goBack', () => { mockRouterValue.currentPage = 'timestamp'; - renderWithProvider(); + renderWithProvider(); fireEvent.click(screen.getByLabelText('返回')); expect(mockRouterValue.goBack).toHaveBeenCalledTimes(1); diff --git a/src/entrypoints/options/App.tsx b/src/entrypoints/options/App.tsx deleted file mode 100644 index 3850346..0000000 --- a/src/entrypoints/options/App.tsx +++ /dev/null @@ -1,383 +0,0 @@ -import { SyntheticEvent, useEffect, useMemo, useState } from 'react'; -import { GripVertical, RefreshCw } from 'lucide-react'; -import { - closestCenter, - DndContext, - type DragEndEvent, - KeyboardSensor, - PointerSensor, - useSensor, - useSensors, -} from '@dnd-kit/core'; -import { - arrayMove, - SortableContext, - sortableKeyboardCoordinates, - useSortable, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; -import { CSS } from '@dnd-kit/utilities'; -import type { PageType, StorageSchema } from '@/types/storage'; -import { storageUtil } from '@/utils/chromeStorage'; -import type { PaletteColorKey } from '@/config/features'; -import { - getAllFeatureKeys, - getDefaultPageOrder, - getDefaultVisibleFeatureKeys, - getFeatureByKey, -} from '@/config/features'; -import GlobalSnackbar, { useSnackbarState } from '@/components/GlobalSnackbar'; -import PageErrorBoundary from '@/components/PageErrorBoundary'; -import { useI18n } from '@/utils/chromeI18n'; - -const PALETTE_COLORS: Record = { - primary: '#1976d2', - success: '#2e7d32', - warning: '#e65100', - error: '#c62828', - secondary: '#9c27b0', - info: '#0288d1', -}; - -const getColorCode = (key: PaletteColorKey): string => PALETTE_COLORS[key]; - -const isValidPage = (page: unknown): page is PageType => { - return typeof page === 'string' && (getAllFeatureKeys() as string[]).includes(page); -}; - -const isValidPageList = (pages: unknown): pages is PageType[] => { - return Array.isArray(pages) && pages.every(isValidPage); -}; - -type WindowType = 'popup' | 'sidepanel' | 'tab'; - -interface SortableFeatureRowProps { - pageKey: PageType; - isLast: boolean; - isChecked: boolean; - isDisabled: boolean; - onToggle: (key: PageType) => void; -} - -function SortableFeatureRow({ - pageKey, - isLast, - isChecked, - isDisabled, - onToggle, -}: SortableFeatureRowProps) { - const { t } = useI18n(['features']); - const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ - id: pageKey, - }); - - const feature = getFeatureByKey(pageKey); - if (!feature) return null; - - const colorKey = feature.themeColorKey ?? 'primary'; - const colorCode = getColorCode(colorKey); - - const style = { - transform: CSS.Transform.toString(transform), - transition, - zIndex: isDragging ? 1 : 'auto', - position: 'relative' as const, - backgroundColor: isDragging ? `${colorCode}0a` : 'transparent', - boxShadow: isDragging ? '0 8px 20px rgba(0,0,0,0.08)' : 'none', - }; - - return ( -
-
- {/* 拖拽手柄 */} -
- -
- - {/* 功能图标 */} -
- {feature.icon && } -
- - {/* 文本信息 */} -
-
- {t(feature.labelKey)} -
- {feature.descriptionKey && ( -
- {t(feature.descriptionKey)} -
- )} -
-
- - {/* 开关 */} - -
- ); -} - -export default function App() { - const { t } = useI18n(['features', 'common']); - - const initialWindowType = useMemo(() => { - if (typeof window === 'undefined') return 'popup'; - const params = new URLSearchParams(window.location.search); - const tab = params.get('tab'); - if (tab === 'popup' || tab === 'sidepanel' || tab === 'tab') { - return tab as WindowType; - } - return 'popup'; - }, []); - - const [windowType, setWindowType] = useState(initialWindowType); - const [visiblePages, setVisiblePages] = useState([]); - const [pageOrder, setPageOrder] = useState([]); - const [isLoaded, setIsLoaded] = useState(false); - const { snackbarProps, showMessage } = useSnackbarState(); - - const sensors = useSensors( - useSensor(PointerSensor, { activationConstraint: { distance: 4 } }), - useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }), - ); - - const configKeys = useMemo(() => { - switch (windowType) { - case 'sidepanel': - return { - visible: 'app/sidepanelVisiblePages' as keyof StorageSchema, - order: 'app/sidepanelPageOrder' as keyof StorageSchema, - }; - case 'tab': - return { - visible: 'app/tabVisiblePages' as keyof StorageSchema, - order: 'app/tabPageOrder' as keyof StorageSchema, - }; - case 'popup': - default: - return { - visible: 'app/popupVisiblePages' as keyof StorageSchema, - order: 'app/popupPageOrder' as keyof StorageSchema, - }; - } - }, [windowType]); - - useEffect(() => { - const url = new URL(window.location.href); - url.searchParams.set('tab', windowType); - window.history.replaceState({}, '', url.toString()); - }, [windowType]); - - useEffect(() => { - const loadConfig = async () => { - setIsLoaded(false); - try { - const [savedVisible, savedOrder] = await Promise.all([ - storageUtil.get(configKeys.visible, getDefaultVisibleFeatureKeys()), - storageUtil.get(configKeys.order, getDefaultPageOrder()), - ]); - setVisiblePages( - isValidPageList(savedVisible) ? savedVisible : getDefaultVisibleFeatureKeys(), - ); - setPageOrder(isValidPageList(savedOrder) ? savedOrder : getDefaultPageOrder()); - } catch (error) { - console.error('Failed to load config:', error); - setVisiblePages(getDefaultVisibleFeatureKeys()); - setPageOrder(getDefaultPageOrder()); - } finally { - setIsLoaded(true); - } - }; - - loadConfig().catch(console.error); - }, [configKeys]); - - const showToast = (message: string, severity: 'success' | 'info' | 'warning') => { - showMessage(message, { severity }); - }; - - const handlePageToggle = async (page: PageType) => { - const isCurrentlyVisible = visiblePages.includes(page); - let newPages: PageType[]; - - if (isCurrentlyVisible) { - if (visiblePages.length <= 1) { - showToast('至少需要保留一个可见页面', 'warning'); - return; - } - newPages = visiblePages.filter((p) => p !== page); - } else { - newPages = [...visiblePages, page]; - } - - try { - await storageUtil.set(configKeys.visible, newPages); - setVisiblePages(newPages); - const feature = getFeatureByKey(page); - const label = feature ? t(feature.labelKey) : page; - showToast(`已${isCurrentlyVisible ? '隐藏' : '显示'} ${label}`, 'success'); - } catch (error) { - console.error('Failed to save config:', error); - showToast('保存失败', 'warning'); - } - }; - - const handleDragEnd = async (event: DragEndEvent) => { - const { active, over } = event; - if (!over || active.id === over.id) return; - - const oldIndex = pageOrder.indexOf(active.id as PageType); - const newIndex = pageOrder.indexOf(over.id as PageType); - if (oldIndex < 0 || newIndex < 0) return; - - const newOrder = arrayMove(pageOrder, oldIndex, newIndex); - setPageOrder(newOrder); - - try { - await storageUtil.set(configKeys.order, newOrder); - } catch (error) { - console.error('Failed to save order:', error); - showToast('排序保存失败', 'warning'); - } - }; - - const handleRestoreDefaults = async () => { - try { - const defaults = getDefaultVisibleFeatureKeys(); - const defaultOrder = getDefaultPageOrder(); - - await Promise.all([ - storageUtil.set(configKeys.visible, defaults), - storageUtil.set(configKeys.order, defaultOrder), - ]); - - setVisiblePages(defaults); - setPageOrder(defaultOrder); - showToast('已恢复当前模式默认设置', 'success'); - } catch (error) { - console.error('Failed to restore defaults:', error); - showToast('恢复失败', 'warning'); - } - }; - - const handleWindowTypeChange = (_event: SyntheticEvent, newType: WindowType) => { - if (newType !== null) { - setWindowType(newType); - } - }; - - if (!isLoaded) { - return ( -
-
-
- ); - } - - return ( -
- - {/* 顶部标题与导航栏 */} -
-
- {/* Tab 与恢复按钮同行 */} -
-
- {(['popup', 'sidepanel', 'tab'] as WindowType[]).map((type) => ( - - ))} -
- -
-
-
- - {/* 主内容区域 */} -
-
-
- - -
- {pageOrder.map((key, index, array) => { - const isChecked = visiblePages.includes(key); - const isDisabled = isChecked && visiblePages.length === 1; - return ( - - ); - })} -
-
-
-
-
-
-
- - -
- ); -} diff --git a/src/entrypoints/options/__tests__/App.test.tsx b/src/entrypoints/options/__tests__/App.test.tsx deleted file mode 100644 index a1430fa..0000000 --- a/src/entrypoints/options/__tests__/App.test.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { describe, expect, it, vi, beforeEach } from 'vitest'; -import { render, screen, waitFor } from '@testing-library/react'; -import App from '../App'; -import { storageUtil } from '@/utils/chromeStorage'; -import { - getDefaultVisibleFeatureKeys, - getDefaultPageOrder, - getFeatureByKey, -} from '@/config/features'; -import { getMessage } from '@/utils/chromeI18n'; - -// Mock storageUtil -vi.mock('@/utils/chromeStorage', () => ({ - storageUtil: { - get: vi.fn(), - set: vi.fn(() => Promise.resolve()), - }, -})); - -describe('Options App', () => { - beforeEach(() => { - vi.clearAllMocks(); - localStorage.clear(); - }); - - it('合法数据应正常加载并显示正确的功能列表', async () => { - const defaultOrder = getDefaultPageOrder(); - - (storageUtil.get as any).mockImplementation((_key: string, defaultValue: any) => - Promise.resolve(defaultValue), - ); - - render(); - - await waitFor(() => { - expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); - }); - - // 验证默认可见的功能都被渲染出来了 - for (const key of defaultOrder) { - const feature = getFeatureByKey(key); - if (feature) { - expect(screen.getByText(getMessage(feature.labelKey))).toBeInTheDocument(); - } - } - }); - - it('非法 visiblePages 数据应回退到默认值', async () => { - const defaultVisible = getDefaultVisibleFeatureKeys(); - - (storageUtil.get as any).mockImplementation((key: string, defaultValue: any) => { - if (key.includes('VisiblePages')) { - return Promise.resolve(['invalidPage', 'anotherInvalid']); - } - return Promise.resolve(defaultValue); - }); - - render(); - - await waitFor(() => { - expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); - }); - - // 验证默认值的功能仍然被渲染(非法数据被回退) - for (const key of defaultVisible) { - const feature = getFeatureByKey(key); - if (feature && key !== 'dashboard') { - expect(screen.getByText(getMessage(feature.labelKey))).toBeInTheDocument(); - } - } - }); - - it('非法 pageOrder 数据应回退到默认值', async () => { - const defaultOrder = getDefaultPageOrder(); - - (storageUtil.get as any).mockImplementation((key: string, defaultValue: any) => { - if (key.includes('PageOrder')) { - return Promise.resolve(['notARealPage', 123, null]); - } - return Promise.resolve(defaultValue); - }); - - render(); - - await waitFor(() => { - expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); - }); - - // 验证默认顺序的功能都被渲染 - for (const key of defaultOrder) { - const feature = getFeatureByKey(key); - if (feature) { - expect(screen.getByText(getMessage(feature.labelKey))).toBeInTheDocument(); - } - } - }); - - it('非数组数据应回退到默认值', async () => { - const defaultOrder = getDefaultPageOrder(); - - (storageUtil.get as any).mockImplementation((key: string, defaultValue: any) => { - if (key.includes('VisiblePages')) { - return Promise.resolve('not-an-array'); - } - if (key.includes('PageOrder')) { - return Promise.resolve({ foo: 'bar' }); - } - return Promise.resolve(defaultValue); - }); - - render(); - - await waitFor(() => { - expect(screen.queryByRole('progressbar')).not.toBeInTheDocument(); - }); - - // 验证默认功能都被渲染 - for (const key of defaultOrder) { - const feature = getFeatureByKey(key); - if (feature) { - expect(screen.getByText(getMessage(feature.labelKey))).toBeInTheDocument(); - } - } - }); -}); diff --git a/src/entrypoints/options/index.html b/src/entrypoints/options/index.html deleted file mode 100644 index e74b0f1..0000000 --- a/src/entrypoints/options/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - 扩展设置 - Testing Tools - - -
- - - diff --git a/src/entrypoints/options/main.tsx b/src/entrypoints/options/main.tsx deleted file mode 100644 index ea46171..0000000 --- a/src/entrypoints/options/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import ReactDOM from 'react-dom/client'; -import AppRoot from '@/providers/AppRoot'; -import '@/index.css'; -import App from './App'; - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -); diff --git a/src/entrypoints/popup/App.tsx b/src/entrypoints/popup/App.tsx index bc5f22a..cdec377 100644 --- a/src/entrypoints/popup/App.tsx +++ b/src/entrypoints/popup/App.tsx @@ -7,10 +7,6 @@ import { getEntryPointType } from '@/config/features'; import { useMemo } from 'react'; export default function App() { - const handleOpenOptions = () => { - chrome.runtime.openOptionsPage().catch(console.error); - }; - const entryType = useMemo(() => getEntryPointType(), []); const routerConfig = useMemo(() => { @@ -36,7 +32,7 @@ export default function App() { >
- + diff --git a/src/entrypoints/sidepanel/App.tsx b/src/entrypoints/sidepanel/App.tsx index ec3285a..9709e84 100644 --- a/src/entrypoints/sidepanel/App.tsx +++ b/src/entrypoints/sidepanel/App.tsx @@ -7,12 +7,6 @@ import { SnackbarProvider } from '@/components/GlobalSnackbar'; import { MessageAction, sendMessage } from '@/utils/messages'; export default function App() { - const handleOpenOptions = () => { - chrome.runtime.openOptionsPage().catch((err) => { - console.error('Failed to open options page:', err); - }); - }; - useEffect(() => { sendMessage(MessageAction.SIDE_PANEL_STATE_CHANGED, { isOpen: true }); return () => { @@ -24,7 +18,7 @@ export default function App() {
- + diff --git a/src/utils/__tests__/chromeTabs.test.ts b/src/utils/__tests__/chromeTabs.test.ts index 2f7e4f8..c992863 100644 --- a/src/utils/__tests__/chromeTabs.test.ts +++ b/src/utils/__tests__/chromeTabs.test.ts @@ -106,14 +106,6 @@ describe('chromeTabs', () => { }); }); - it('应该支持带查询参数的扩展页面', async () => { - await openExtensionPage('options.html', { tab: 'settings', id: '123' }); - - expect(chrome.tabs.create).toHaveBeenCalledWith({ - url: 'chrome-extension://test-extension-id/options.html?tab=settings&id=123', - }); - }); - it('当创建标签页失败时应记录错误', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); (chrome.tabs.create as any).mockRejectedValue(new Error('Tab creation failed')); diff --git a/wxt.config.ts b/wxt.config.ts index abd383c..97afe7d 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -65,10 +65,6 @@ export default defineConfig({ side_panel: { default_path: 'entrypoints/sidepanel/index.html', }, - options_ui: { - page: 'entrypoints/options/index.html', - open_in_tab: true, - }, }, vite: () => ({ plugins: [manualChunksForHtmlOnly()],