diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6f37bc3..8316093 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,12 @@ "Bash(npm run:*)", "Bash(git show-ref:*)", "Bash(git checkout:*)", - "mcp__plugin_playwright_playwright__browser_navigate" + "mcp__plugin_playwright_playwright__browser_navigate", + "Skill(code-review:code-review)", + "Bash(grep -E \"\\\\.\\(md|txt\\)$\")", + "Bash(pkill -f \"wxt\")", + "Bash(python3 -m json.tool)", + "Bash(git restore:*)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 41fce39..f98b618 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,37 +2,34 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Project Overview +## Quick Start -A browser extension built with the WXT framework, providing timestamp conversion and storage cleaning tools. The project has been streamlined to focus on core functionality, removing complex features like recording and playback. +A browser extension built with the WXT framework, providing timestamp conversion and storage cleaning tools. -## Core Commands +### Essential Commands -### Development -- `npm run dev` - Start development mode for Chrome -- `npm run dev:firefox` - Start development mode for Firefox -- `npm run build` - Build production version for Chrome -- `npm run build:firefox` - Build production version for Firefox -- `npm run zip` - Package Chrome extension -- `npm run zip:firefox` - Package Firefox extension -- `npm run compile` - TypeScript type checking (no file generation) -- `npm run lint` - Run ESLint with zero warnings allowed +| Command | Purpose | +| ----------------------- | ------------------------------------------------------ | +| `npm install` | Install dependencies (runs `wxt prepare` post-install) | +| `npm run dev` | Start development mode for Chrome | +| `npm run dev:firefox` | Start development mode for Firefox | +| `npm run build` | Build production version for Chrome | +| `npm run build:firefox` | Build production version for Firefox | +| `npm run zip` | Package Chrome extension | +| `npm run zip:firefox` | Package Firefox extension | +| `npm run compile` | TypeScript type checking (no file generation) | +| `npm run lint` | Run ESLint with zero warnings allowed | -### Dependencies & Setup -- `npm install` - Install dependencies (automatically runs `wxt prepare` via postinstall hook) -- The `prepare` hook initializes Husky Git hooks +### Setup -### CI/CD -- GitHub Actions workflow: `.github/workflows/node.js.yml` -- Triggers on push to main branch or pull requests -- Uses Node.js 20.x and 22.x for multi-version testing -- Runs ESLint, TypeScript compilation, and build steps -- Test commands are currently commented (project has no tests) +- Dependencies install automatically runs `wxt prepare` via postinstall hook +- Husky Git hooks are initialized via `prepare` script -## Project Architecture +## Architecture Overview ### Tech Stack -- **Framework**: WXT (Web Extension Toolkit) - browser extension development framework + +- **Framework**: WXT (Web Extension Toolkit) - **Frontend**: React 19 + TypeScript - **UI Library**: Material UI (MUI) - **Date Handling**: dayjs (with UTC and timezone plugins) @@ -40,40 +37,43 @@ A browser extension built with the WXT framework, providing timestamp conversion - **Storage**: Chrome Storage API with type-safe wrapper ### Directory Structure + ``` -├── entrypoints/ # Browser extension entry points -│ ├── background.ts # Background script (handles extension install/update, injects content scripts) -│ ├── content.ts # Content script (injected into pages, currently placeholder) -│ ├── popup/ # Extension popup interface -│ │ ├── App.tsx # Popup main application (handles page routing) -│ │ ├── main.tsx # Popup entry point -│ │ ├── index.html # Popup HTML -│ │ └── pages/ # Popup pages -│ │ ├── TimestampPage.tsx # Timestamp conversion page (core feature) -│ │ └── StorageCleanerPage.tsx # Storage cleaning page (added feature) -│ └── options/ # Options page (currently static HTML) -│ └── index.html # Options page HTML -├── utils/ # Utility functions -│ ├── chromeStorage.ts # Chrome Storage utility (type-safe wrapper) -│ ├── dayjs.ts # dayjs configuration (UTC + timezone plugins) -│ ├── messages.tsx # Extension messaging protocol (@webext-core/messaging) -│ └── storageCleaner.ts # Storage cleaning utilities (new feature) -├── types/ # TypeScript type definitions -│ └── storage.d.ts # StorageSchema type definitions -├── constants/ # Constants (currently empty) -└── public/ # Static assets +entrypoints/ # Browser extension entry points +├── background.ts # Background script (injects content scripts) +├── content.ts # Content script (injected into pages) +├── popup/ # Extension popup interface +│ ├── App.tsx # Popup main application (handles routing) +│ ├── main.tsx # Popup entry point +│ ├── index.html # Popup HTML +│ └── pages/ # Popup pages +│ ├── TimestampPage.tsx # Timestamp conversion +│ └── StorageCleanerPage.tsx # Storage cleaning +└── options/ # Options page (static HTML) +utils/ # Utility functions +types/ # TypeScript type definitions +public/ # Static assets ``` -### Core Features +### Extension Entry Points + +- **Background Script**: Listens for install/update events, injects content scripts into valid tabs +- **Content Script**: Matches all URLs (``), runs at document start (currently placeholder) +- **Popup**: Main interface with tab-based navigation between timestamp conversion and storage cleaning +- **Options Page**: Static HTML page, can be extended as settings interface + +## Core Features + +### Timestamp Conversion Tool (`entrypoints/popup/pages/TimestampPage.tsx`) -#### Timestamp Conversion Tool (`entrypoints/popup/pages/TimestampPage.tsx`) - Real-time current timestamp display (milliseconds/seconds toggle) - Timestamp ↔ date/time conversion - Support for multiple timezones (Asia/Shanghai, America/New_York, Europe/London) - One-click copy functionality - Input validation and error handling -#### Storage Cleaning Tool (`entrypoints/popup/pages/StorageCleanerPage.tsx`) +### Storage Cleaning Tool (`entrypoints/popup/pages/StorageCleanerPage.tsx`) + - Automatically reads current domain - Cleans multiple storage types: localStorage, sessionStorage, IndexedDB, Cookies, Cache Storage, Service Workers - User-selectable storage types (all selected by default) @@ -82,54 +82,57 @@ A browser extension built with the WXT framework, providing timestamp conversion - Auto-refresh page after cleaning option - User preferences persistence -### Extension Entry Points - -#### Background Script (`entrypoints/background.ts`) -- Listens for extension installation/update events -- Automatically injects content scripts into all valid tabs -- Filters restricted protocols (chrome://, about://, etc.) - -#### Content Script (`entrypoints/content.ts`) -- Matches all URLs (``) -- Runs at document start -- Currently a placeholder with no actual logic - -#### Popup (`entrypoints/popup/`) -- Main entry displays TimestampPage by default -- Tab-based navigation between timestamp conversion and storage cleaning -- Route persistence: remembers last visited page when popup is reopened - -#### Options Page (`entrypoints/options/`) -- Currently a static HTML page -- Can be extended as a settings interface - ### Data Storage -Uses Chrome Storage API for persistent storage: -- Type-safe wrapper (`utils/chromeStorage.ts`) -- Interface-based Schema (`types/storage.d.ts`) -- Current storage keys: +Uses Chrome Storage API with type-safe wrapper (`utils/chromeStorage.ts`): + +- **Storage Schema** (`types/storage.d.ts`): Interface-based type definitions +- **Current storage keys**: - `app/currentRoute`: Current active page route (default: 'timestamp') - `app/visiblePages`: List of visible pages (default: ['timestamp', 'storageCleaner']) - `app/lastRoute`: Last accessed route (legacy) - `app/theme`: Theme settings - `storageCleaner/preferences`: Storage cleaner preferences (autoRefresh, selectedTypes) -### Messaging +## Development Workflow -Uses `@webext-core/messaging` library for type-safe extension communication: -- Defined in `utils/messages.tsx` -- Current ProtocolMap is empty (reserved for future use) +### Browser Compatibility -### Key Configuration Files +- Supports Chrome and Firefox browsers +- Uses WXT framework to abstract browser differences + +### Code Quality + +- **ESLint**: Zero warnings enforced (`npm run lint`) +- **Husky**: Git hook management +- **lint-staged**: Ensures staged files comply (ESLint + TypeScript + Prettier) +- **Prettier**: Code formatting (100 char line width, 2 space indent, single quotes, trailing comma) + +### TypeScript Configuration + +- Strict mode enabled (`strict: true`) +- `noImplicitAny` set to `false` (allows implicit any) +- Unused variables/parameters cause errors (`noUnusedLocals`, `noUnusedParameters`) +- Module resolution mode: Bundler +- Path alias: `@/*` maps to project root +- Excludes test files from type checking + +### Path Aliases + +- Use `@/` prefix for project-relative imports (e.g., `@/utils/chromeStorage`) +- Configured in `tsconfig.json` paths + +## Configuration & Implementation + +### `wxt.config.ts` -#### `wxt.config.ts` - Enables React module (`@wxt-dev/module-react`) - Configures manifest permissions and host_permissions - Uses Terser compression (forces ASCII encoding) - Configures icons and options page -#### Manifest Permissions +### Manifest Permissions + ```typescript permissions: [ 'storage', // Chrome Storage @@ -144,38 +147,37 @@ permissions: [ host_permissions: [''] // Access all websites ``` -## Development Notes - -### Browser Compatibility -- Supports Chrome and Firefox browsers -- Uses WXT framework to abstract browser differences - -### Code Quality -- ESLint for code checking (zero warnings enforced) -- Husky for Git hook management -- Lint-staged ensures staged files comply (ESLint + TypeScript + Prettier) -- Prettier for code formatting (100 char line width, 2 space indent, single quotes, trailing comma) - -### TypeScript Configuration -- Strict mode enabled (`strict: true`) -- `noImplicitAny` set to `false` (allows implicit any) -- Unused variables/parameters cause errors (`noUnusedLocals`, `noUnusedParameters`) -- Module resolution mode: Bundler -- Excludes test files (`**/*.test.tsx`, `**/*.test.ts`) from type checking - ### Storage Cleaning Implementation Details -- Cookies: Uses `chrome.cookies` API directly in extension context -- Other storage types: Uses `chrome.scripting.executeScript` to inject cleaning scripts into page context -- Restricted page filtering (chrome://, about://, edge://, view-source://, file://, data://) -- IndexedDB: Uses `indexedDB.databases()` to get database list, handles `onblocked` events -- Service Workers: Unregisters to prevent re-caching -- Cache Storage: Uses `caches` API to clear all caches + +- **Cookies**: Uses `chrome.cookies` API directly in extension context +- **Other storage types**: Uses `chrome.scripting.executeScript` to inject cleaning scripts into page context +- **Restricted page filtering**: chrome://, about://, edge://, view-source://, file://, data:// +- **IndexedDB**: Uses `indexedDB.databases()` to get database list, handles `onblocked` events +- **Service Workers**: Unregisters to prevent re-caching +- **Cache Storage**: Uses `caches` API to clear all caches + +### Messaging System + +- Uses `@webext-core/messaging` library for type-safe extension communication +- Defined in `utils/messages.tsx` +- Current ProtocolMap is empty (reserved for future use) + +## CI/CD & Project Context + +### GitHub Actions Workflow (`.github/workflows/node.js.yml`) + +- Triggers on push to main branch or pull requests +- Uses Node.js 20.x and 22.x for multi-version testing +- Runs ESLint, TypeScript compilation, and build steps +- Test commands are currently commented (project has no tests) ### Project History -Recent refactoring (based on git history): + +Recent refactoring streamlined the project: + - Removed recording and playback functionality - Removed test pages - Streamlined to single-page timestamp tool - Renamed storage utility class to storageUtil - Added storage cleaning functionality with persistent preferences -- Added route persistence for popup navigation \ No newline at end of file +- Added route persistence for popup navigation diff --git a/entrypoints/options/App.css b/entrypoints/options/App.css new file mode 100644 index 0000000..7a5e8b7 --- /dev/null +++ b/entrypoints/options/App.css @@ -0,0 +1,9 @@ +.app { + min-height: 100vh; + background-color: #f5f5f5; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', + 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/entrypoints/options/App.tsx b/entrypoints/options/App.tsx new file mode 100644 index 0000000..2a77c9a --- /dev/null +++ b/entrypoints/options/App.tsx @@ -0,0 +1,197 @@ +import { useState, useEffect } from 'react'; +import { + Box, + Typography, + Paper, + FormControlLabel, + Switch, + Button, + Snackbar, + Alert, + CircularProgress, +} from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import type { PageType } from '@/types/storage'; +import { storageUtil } from '@/utils/chromeStorage'; + +const PAGE_CONFIG = { + timestamp: { label: '时间戳', defaultVisible: true }, + storageCleaner: { label: '存储清理', defaultVisible: true }, +} as const satisfies Record; + +function App() { + const [visiblePages, setVisiblePages] = useState([]); + const [isLoaded, setIsLoaded] = useState(false); + const [toast, setToast] = useState(null); + const [toastSeverity, setToastSeverity] = useState<'success' | 'info' | 'warning'>('info'); + + useEffect(() => { + loadConfig(); + }, []); + + const loadConfig = async () => { + try { + const saved = await storageUtil.get('app/visiblePages', [ + 'timestamp', + 'storageCleaner', + ] as PageType[]); + // Ensure we always have an array + setVisiblePages(saved ?? ['timestamp', 'storageCleaner']); + } catch (error) { + console.error('Failed to load config:', error); + setVisiblePages(['timestamp', 'storageCleaner']); + } finally { + setIsLoaded(true); + } + }; + + 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('app/visiblePages', newPages); + setVisiblePages(newPages); + showToast( + `已${isCurrentlyVisible ? '隐藏' : '显示'} ${PAGE_CONFIG[page].label}`, + 'success', + ); + } catch (error) { + console.error('Failed to save config:', error); + showToast('保存失败,请重试', 'warning'); + } + }; + + const handleRestoreDefaults = async () => { + try { + const defaults = (Object.keys(PAGE_CONFIG) as PageType[]).filter( + (key) => PAGE_CONFIG[key].defaultVisible, + ); + await storageUtil.set('app/visiblePages', defaults); + setVisiblePages(defaults); + showToast('已恢复默认设置', 'success'); + } catch (error) { + console.error('Failed to restore defaults:', error); + showToast('恢复失败,请重试', 'warning'); + } + }; + + const showToast = (message: string, severity: 'success' | 'info' | 'warning') => { + setToast(message); + setToastSeverity(severity); + }; + + const handleCloseToast = () => { + setToast(null); + }; + + if (!isLoaded) { + return ( + + + + ); + } + + return ( + + + ⚙️ 扩展设置 + + + + 自定义 popup 弹窗中显示的功能页面。更改将立即生效,无需保存。 + + + + + 可见页面设置 + + + {(Object.keys(PAGE_CONFIG) as PageType[]).map((pageKey) => { + const config = PAGE_CONFIG[pageKey]; + const isChecked = visiblePages.includes(pageKey); + const isDisabled = !isChecked && visiblePages.length === 1; + + return ( + handlePageToggle(pageKey)} + disabled={isDisabled} + color="primary" + /> + } + label={config.label} + sx={{ + width: '100%', + mb: 1, + '&:last-child': { mb: 0 }, + }} + /> + ); + })} + + + + + + + + + {toast} + + + + + + 提示:更改将立即应用到 popup 弹窗。如需查看效果,请关闭并重新打开扩展弹窗。 + + + + ); +} + +export default App; diff --git a/entrypoints/options/index.html b/entrypoints/options/index.html index 9ae608f..e74b0f1 100644 --- a/entrypoints/options/index.html +++ b/entrypoints/options/index.html @@ -1,11 +1,12 @@ - + - Document + 扩展设置 - Testing Tools - Hello Options Page +
+ diff --git a/entrypoints/options/main.tsx b/entrypoints/options/main.tsx new file mode 100644 index 0000000..db7f36a --- /dev/null +++ b/entrypoints/options/main.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './App.css'; +import '@mui/material/styles'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/entrypoints/popup/App.css b/entrypoints/popup/App.css index 5c19510..085ebb5 100644 --- a/entrypoints/popup/App.css +++ b/entrypoints/popup/App.css @@ -77,3 +77,11 @@ body { background: var(--btn-bg); font-weight: 500; } + +.nav-button.settings-button { + padding: 8px 12px; + min-width: 36px; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/entrypoints/popup/App.tsx b/entrypoints/popup/App.tsx index 029b2e7..3050ccb 100644 --- a/entrypoints/popup/App.tsx +++ b/entrypoints/popup/App.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import { Box } from '@mui/material'; +import SettingsIcon from '@mui/icons-material/Settings'; import type { PageType } from '@/types/storage'; import { storageUtil } from '@/utils/chromeStorage'; import TimestampPage from './pages/TimestampPage'; @@ -52,6 +53,10 @@ function App() { setCurrentPage(page); }; + const handleOpenOptions = () => { + chrome.runtime.openOptionsPage(); + }; + const NavButton = ({ pageKey }: { pageKey: PageType }) => { const config = PAGE_CONFIG[pageKey]; if (!config) return null; @@ -78,6 +83,15 @@ function App() { {(Object.keys(PAGE_CONFIG) as PageType[]) .filter((key) => visiblePages.includes(key)) .map((key) => )} + + + {currentPage === 'timestamp' && } {currentPage === 'storageCleaner' && } diff --git a/wxt.config.ts b/wxt.config.ts index 557c815..c189c85 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -22,6 +22,7 @@ export default defineConfig({ default_title: 'Testing Tools', }, options_ui: { + page: 'entrypoints/options/index.html', open_in_tab: true, }, // 将 favicon.ico 放入 public/ 文件夹中