create-extension-react 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -0
  3. package/package.json +54 -0
  4. package/scripts/create.js +418 -0
  5. package/scripts/dev.js +26 -0
  6. package/src/background.ts +9 -0
  7. package/src/bookmarks/index.module.css +17 -0
  8. package/src/bookmarks/index.tsx +42 -0
  9. package/src/bookmarks.html +12 -0
  10. package/src/components/BookmarksContent/index.module.css +95 -0
  11. package/src/components/BookmarksContent/index.tsx +100 -0
  12. package/src/components/ConfigInfo/index.module.css +30 -0
  13. package/src/components/ConfigInfo/index.tsx +36 -0
  14. package/src/components/HistoryContent/index.module.css +99 -0
  15. package/src/components/HistoryContent/index.tsx +86 -0
  16. package/src/components/NewTabContent/index.module.css +86 -0
  17. package/src/components/NewTabContent/index.tsx +30 -0
  18. package/src/components/NewTabHeader/index.module.css +76 -0
  19. package/src/components/NewTabHeader/index.tsx +14 -0
  20. package/src/components/PopupContent/index.module.css +46 -0
  21. package/src/components/PopupContent/index.tsx +29 -0
  22. package/src/components/PopupHeader/index.module.css +15 -0
  23. package/src/components/PopupHeader/index.tsx +14 -0
  24. package/src/components/ThemeButton/index.module.css +202 -0
  25. package/src/components/ThemeButton/index.tsx +48 -0
  26. package/src/content.ts +10 -0
  27. package/src/history/index.module.css +17 -0
  28. package/src/history/index.tsx +42 -0
  29. package/src/history.html +12 -0
  30. package/src/hooks/useStorage.ts +50 -0
  31. package/src/hooks/useTabs.ts +37 -0
  32. package/src/hooks/useTheme.ts +27 -0
  33. package/src/manifest.json +26 -0
  34. package/src/newtab/index.module.css +17 -0
  35. package/src/newtab/index.tsx +43 -0
  36. package/src/newtab.html +12 -0
  37. package/src/popup/index.module.css +8 -0
  38. package/src/popup/index.tsx +36 -0
  39. package/src/popup.html +12 -0
  40. package/src/store/README.md +176 -0
  41. package/src/store/chromeStorage.ts +88 -0
  42. package/src/store/configSlice.ts +40 -0
  43. package/src/store/hooks.ts +6 -0
  44. package/src/store/initConfig.ts +78 -0
  45. package/src/store/storageMiddleware.ts +70 -0
  46. package/src/store/store.ts +49 -0
  47. package/src/style.css +107 -0
  48. package/src/utils/initTheme.ts +40 -0
  49. package/src/variables.css +146 -0
  50. package/src/vite-env.d.ts +6 -0
  51. package/tsconfig.json +22 -0
  52. package/tsconfig.node.json +10 -0
  53. package/vite-plugin-fix-extension.ts +81 -0
  54. package/vite.config.ts +50 -0
@@ -0,0 +1,8 @@
1
+ .container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ background: var(--bg-primary);
6
+ color: var(--text-primary);
7
+ transition: background-color 0.3s ease, color 0.3s ease;
8
+ }
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { Provider } from 'react-redux';
4
+ import { PersistGate } from 'redux-persist/integration/react';
5
+ import '../style.css';
6
+ import { store, persistor } from '../store/store';
7
+ import { PopupHeader } from '../components/PopupHeader';
8
+ import { PopupContent } from '../components/PopupContent';
9
+ import { useTheme } from '../hooks/useTheme';
10
+ import styles from './index.module.css';
11
+
12
+ const Popup: React.FC = () => {
13
+ // 使用 useTheme hook 确保主题同步和响应
14
+ useTheme();
15
+
16
+ return (
17
+ <div className={styles.container}>
18
+ <PopupHeader title="React Extension" />
19
+ <PopupContent />
20
+ </div>
21
+ );
22
+ };
23
+
24
+ const container = document.getElementById('root');
25
+ if (container) {
26
+ const root = createRoot(container);
27
+
28
+ // 使用 PersistGate 等待持久化数据加载
29
+ root.render(
30
+ <Provider store={store}>
31
+ <PersistGate loading={null} persistor={persistor}>
32
+ <Popup />
33
+ </PersistGate>
34
+ </Provider>
35
+ );
36
+ }
package/src/popup.html ADDED
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>React Extension</title>
7
+ </head>
8
+ <body class="popup">
9
+ <div id="root"></div>
10
+ <script type="module" src="./popup/index.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,176 @@
1
+ # Redux 配置管理系统(持久化版本)
2
+
3
+ 这个目录包含了使用 Redux Toolkit 和 redux-persist 实现的配置管理系统,支持自动持久化存储。
4
+
5
+ ## 文件结构
6
+
7
+ - `store.ts` - Redux store 配置(包含持久化配置)
8
+ - `configSlice.ts` - 配置状态管理 slice
9
+ - `chromeStorage.ts` - Chrome Storage 适配器,用于 redux-persist
10
+ - `initConfig.ts` - 初始化配置(已废弃,redux-persist 自动处理)
11
+ - `hooks.ts` - 类型化的 Redux hooks
12
+
13
+ ## 持久化存储
14
+
15
+ 使用 `redux-persist` 实现自动持久化:
16
+
17
+ - **存储引擎**: `chrome.storage.local`(Chrome Extension 环境)
18
+ - **降级方案**: `localStorage`(非 Chrome 环境)
19
+ - **自动同步**: 配置修改后自动保存到存储
20
+ - **自动恢复**: 应用启动时自动从存储中恢复配置
21
+
22
+ ## 使用方法
23
+
24
+ ### 1. 在组件中使用配置
25
+
26
+ ```tsx
27
+ import { useAppSelector, useAppDispatch } from '../store/hooks';
28
+ import { setTheme } from '../store/configSlice';
29
+ import dayjs from 'dayjs';
30
+
31
+ function MyComponent() {
32
+ const dispatch = useAppDispatch();
33
+ const isDarkMode = useAppSelector((state) => state.config.theme);
34
+ const updatedAt = useAppSelector((state) => state.config.updatedAt);
35
+
36
+ const toggleTheme = () => {
37
+ dispatch(setTheme(!isDarkMode));
38
+ // updatedAt 会自动更新,配置会自动保存
39
+ };
40
+
41
+ return (
42
+ <div>
43
+ <button onClick={toggleTheme}>
44
+ {isDarkMode ? '切换到亮色' : '切换到暗色'}
45
+ </button>
46
+ <p>最后修改: {dayjs(updatedAt).format('YYYY-MM-DD HH:mm:ss')}</p>
47
+ </div>
48
+ );
49
+ }
50
+ ```
51
+
52
+ ### 2. 添加新的配置项
53
+
54
+ 在 `configSlice.ts` 中添加新的配置项:
55
+
56
+ ```typescript
57
+ import dayjs from 'dayjs';
58
+
59
+ export interface ConfigState {
60
+ theme: boolean;
61
+ updatedAt: string; // 配置最后修改日期(ISO 8601 格式)
62
+ language: string; // 新增配置项
63
+ fontSize: number; // 新增配置项
64
+ }
65
+
66
+ const initialState: ConfigState = {
67
+ theme: false,
68
+ updatedAt: dayjs().toISOString(),
69
+ language: 'zh-CN', // 默认值
70
+ fontSize: 14, // 默认值
71
+ };
72
+
73
+ // 添加对应的 reducer
74
+ const configSlice = createSlice({
75
+ name: 'config',
76
+ initialState,
77
+ reducers: {
78
+ setTheme: (state, action: PayloadAction<boolean>) => {
79
+ state.theme = action.payload;
80
+ state.updatedAt = dayjs().toISOString(); // 自动更新修改日期
81
+ },
82
+ setLanguage: (state, action: PayloadAction<string>) => {
83
+ state.language = action.payload;
84
+ state.updatedAt = dayjs().toISOString(); // 自动更新修改日期
85
+ },
86
+ setFontSize: (state, action: PayloadAction<number>) => {
87
+ state.fontSize = action.payload;
88
+ state.updatedAt = dayjs().toISOString(); // 自动更新修改日期
89
+ },
90
+ loadConfig: (state, action: PayloadAction<Partial<ConfigState>>) => {
91
+ return { ...state, ...action.payload };
92
+ },
93
+ },
94
+ });
95
+ ```
96
+
97
+ **注意**:添加新配置项后,redux-persist 会自动处理持久化,无需额外配置。
98
+
99
+ ### 3. 清除持久化数据
100
+
101
+ ```tsx
102
+ import { useAppDispatch } from '../store/hooks';
103
+ import { persistor } from '../store/store';
104
+
105
+ function ClearConfigButton() {
106
+ const dispatch = useAppDispatch();
107
+
108
+ const handleClear = () => {
109
+ // 清除所有持久化数据
110
+ persistor.purge();
111
+ };
112
+
113
+ return <button onClick={handleClear}>清除配置</button>;
114
+ }
115
+ ```
116
+
117
+ ## 工作原理
118
+
119
+ 1. **初始化**:
120
+ - Redux store 创建时,redux-persist 会自动从 `chrome.storage.local` 加载配置
121
+ - `PersistGate` 组件会等待数据加载完成后再渲染子组件
122
+
123
+ 2. **状态更新**:
124
+ - 当配置通过 Redux action 更新时,redux-persist 会自动保存到存储
125
+ - 无需手动调用存储 API
126
+
127
+ 3. **存储结构**:
128
+ - redux-persist 会将整个 state 序列化为 JSON 存储在 `persist:root` 键下
129
+ - 格式:`{ _persist: { version, rehydrated }, config: { theme, updatedAt } }`
130
+
131
+ 4. **双重存储**:
132
+ - `chrome.storage.local`:主要存储(Chrome Extension 环境)
133
+ - `localStorage`:降级方案(非 Chrome 环境)
134
+
135
+ ## 配置字段说明
136
+
137
+ ### updatedAt(修改日期)
138
+
139
+ - **类型**: `string` (ISO 8601 格式)
140
+ - **说明**: 记录配置最后修改的时间
141
+ - **自动更新**: 当任何配置项修改时,`updatedAt` 会自动更新为当前时间
142
+ - **使用示例**:
143
+ ```tsx
144
+ import dayjs from 'dayjs';
145
+ const updatedAt = useAppSelector((state) => state.config.updatedAt);
146
+ const formattedDate = dayjs(updatedAt).format('YYYY-MM-DD HH:mm:ss');
147
+ ```
148
+
149
+ ## 优势
150
+
151
+ - ✅ 自动持久化:无需手动管理存储
152
+ - ✅ 自动恢复:应用启动时自动加载配置
153
+ - ✅ 类型安全:完整的 TypeScript 类型支持
154
+ - ✅ 易于扩展:添加新配置项无需修改持久化逻辑
155
+ - ✅ 零闪烁:HTML 初始化脚本确保主题立即应用
156
+ - ✅ Chrome Storage 支持:专为 Chrome Extension 优化
157
+ - ✅ 降级方案:非 Chrome 环境自动使用 localStorage
158
+
159
+ ## 迁移说明
160
+
161
+ 如果你之前使用了 `initConfig` 和 `storageMiddleware`:
162
+
163
+ - ✅ **已移除**: `storageMiddleware`(redux-persist 自动处理)
164
+ - ✅ **已移除**: `initConfig` 调用(PersistGate 自动处理)
165
+ - ✅ **保留**: HTML 初始化脚本(用于避免白色闪烁)
166
+
167
+ ## 调试
168
+
169
+ 查看持久化数据:
170
+
171
+ ```javascript
172
+ // 在 Chrome DevTools Console 中
173
+ chrome.storage.local.get('persist:root', (data) => {
174
+ console.log(JSON.parse(data['persist:root']));
175
+ });
176
+ ```
@@ -0,0 +1,88 @@
1
+ import { Storage } from 'redux-persist';
2
+
3
+ /**
4
+ * Chrome Storage 适配器,用于 redux-persist
5
+ * 将 redux-persist 的存储接口适配到 chrome.storage.local
6
+ */
7
+ export const chromeStorage: Storage = {
8
+ getItem: (key: string): Promise<string | null> => {
9
+ return new Promise((resolve) => {
10
+ if (typeof chrome !== 'undefined' && chrome.storage?.local) {
11
+ chrome.storage.local.get([key], (result) => {
12
+ if (chrome.runtime?.lastError) {
13
+ console.error('Chrome storage getItem error:', chrome.runtime.lastError);
14
+ resolve(null);
15
+ } else {
16
+ const value = result[key] || null;
17
+ if (value) {
18
+ console.log(`[redux-persist] Loaded config from chrome.storage.local: ${key}`);
19
+ } else {
20
+ console.log(`[redux-persist] No config found in chrome.storage.local: ${key}`);
21
+ }
22
+ resolve(value);
23
+ }
24
+ });
25
+ } else {
26
+ // 降级到 localStorage
27
+ try {
28
+ const value = localStorage.getItem(key);
29
+ if (value) {
30
+ console.log(`[redux-persist] Loaded config from localStorage: ${key}`);
31
+ }
32
+ resolve(value);
33
+ } catch (e) {
34
+ resolve(null);
35
+ }
36
+ }
37
+ });
38
+ },
39
+
40
+ setItem: (key: string, value: string): Promise<void> => {
41
+ return new Promise((resolve, reject) => {
42
+ if (typeof chrome !== 'undefined' && chrome.storage?.local) {
43
+ chrome.storage.local.set({ [key]: value }, () => {
44
+ if (chrome.runtime?.lastError) {
45
+ console.error('Chrome storage setItem error:', chrome.runtime.lastError);
46
+ reject(chrome.runtime.lastError);
47
+ } else {
48
+ // 调试:确认数据已保存
49
+ console.log(`[redux-persist] Saved config to chrome.storage.local: ${key}`);
50
+ resolve();
51
+ }
52
+ });
53
+ } else {
54
+ // 降级到 localStorage
55
+ try {
56
+ localStorage.setItem(key, value);
57
+ console.log(`[redux-persist] Saved config to localStorage: ${key}`);
58
+ resolve();
59
+ } catch (e) {
60
+ reject(e);
61
+ }
62
+ }
63
+ });
64
+ },
65
+
66
+ removeItem: (key: string): Promise<void> => {
67
+ return new Promise((resolve, reject) => {
68
+ if (typeof chrome !== 'undefined' && chrome.storage?.local) {
69
+ chrome.storage.local.remove([key], () => {
70
+ if (chrome.runtime?.lastError) {
71
+ console.error('Chrome storage removeItem error:', chrome.runtime.lastError);
72
+ reject(chrome.runtime.lastError);
73
+ } else {
74
+ resolve();
75
+ }
76
+ });
77
+ } else {
78
+ // 降级到 localStorage
79
+ try {
80
+ localStorage.removeItem(key);
81
+ resolve();
82
+ } catch (e) {
83
+ reject(e);
84
+ }
85
+ }
86
+ });
87
+ },
88
+ };
@@ -0,0 +1,40 @@
1
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
+ import dayjs from 'dayjs';
3
+
4
+ export interface ConfigState {
5
+ theme: boolean; // true = dark, false = light
6
+ updatedAt: string; // 配置最后修改日期(ISO 8601 格式)
7
+ // 可以在这里添加其他配置项
8
+ // example: language: string;
9
+ // example: fontSize: number;
10
+ }
11
+
12
+ const initialState: ConfigState = {
13
+ theme: false, // 默认亮色主题
14
+ updatedAt: dayjs().toISOString(), // 初始化时设置为当前时间
15
+ };
16
+
17
+ const configSlice = createSlice({
18
+ name: 'config',
19
+ initialState,
20
+ reducers: {
21
+ setTheme: (state, action: PayloadAction<boolean>) => {
22
+ state.theme = action.payload;
23
+ state.updatedAt = dayjs().toISOString(); // 更新修改日期
24
+ // 调试:确认主题已更新
25
+ console.log('[configSlice] setTheme called:', action.payload, 'updatedAt:', state.updatedAt);
26
+ },
27
+ // 从存储中加载配置(不更新修改日期)
28
+ loadConfig: (state, action: PayloadAction<Partial<ConfigState>>) => {
29
+ return { ...state, ...action.payload };
30
+ },
31
+ // 可以添加其他配置的 reducer
32
+ // setLanguage: (state, action: PayloadAction<string>) => {
33
+ // state.language = action.payload;
34
+ // state.updatedAt = dayjs().toISOString();
35
+ // },
36
+ },
37
+ });
38
+
39
+ export const { setTheme, loadConfig } = configSlice.actions;
40
+ export default configSlice.reducer;
@@ -0,0 +1,6 @@
1
+ import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
2
+ import type { RootState, AppDispatch } from './store';
3
+
4
+ // 使用类型化的 hooks,避免每次使用时都要指定类型
5
+ export const useAppDispatch = () => useDispatch<AppDispatch>();
6
+ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
@@ -0,0 +1,78 @@
1
+ import { store } from './store';
2
+ import { loadConfig } from './configSlice';
3
+ import type { ConfigState } from './configSlice';
4
+ import dayjs from 'dayjs';
5
+
6
+ /**
7
+ * 初始化配置:从 chrome.storage 或 localStorage 加载配置
8
+ * 应该在应用启动时调用
9
+ */
10
+ export async function initConfig(): Promise<void> {
11
+ return new Promise((resolve) => {
12
+ // 优先从 localStorage 同步读取(最快)
13
+ let config: Partial<ConfigState> = {};
14
+ try {
15
+ const cachedTheme = localStorage.getItem('theme');
16
+ if (cachedTheme !== null) {
17
+ config.theme = JSON.parse(cachedTheme) === true;
18
+ }
19
+ const cachedUpdatedAt = localStorage.getItem('updatedAt');
20
+ if (cachedUpdatedAt !== null) {
21
+ config.updatedAt = JSON.parse(cachedUpdatedAt);
22
+ }
23
+ } catch (e) {
24
+ // localStorage 不可用
25
+ }
26
+
27
+ // 如果有缓存,先应用缓存配置
28
+ if (config.theme !== undefined || config.updatedAt !== undefined) {
29
+ store.dispatch(loadConfig(config));
30
+ }
31
+
32
+ // 异步从 chrome.storage 读取并更新(确保数据一致性)
33
+ if (typeof chrome !== 'undefined' && chrome.storage?.local) {
34
+ chrome.storage.local.get(['theme', 'updatedAt'], (result) => {
35
+ const loadedConfig: Partial<ConfigState> = {};
36
+
37
+ if (result.theme !== undefined) {
38
+ loadedConfig.theme = result.theme === true;
39
+ }
40
+ if (result.updatedAt !== undefined) {
41
+ // 验证日期格式
42
+ const parsedDate = dayjs(result.updatedAt);
43
+ loadedConfig.updatedAt = parsedDate.isValid()
44
+ ? parsedDate.toISOString()
45
+ : dayjs().toISOString();
46
+ }
47
+
48
+ // 更新 store
49
+ if (Object.keys(loadedConfig).length > 0) {
50
+ store.dispatch(loadConfig(loadedConfig));
51
+ }
52
+
53
+ // 同步到 localStorage
54
+ if (loadedConfig.theme !== undefined) {
55
+ try {
56
+ localStorage.setItem('theme', JSON.stringify(loadedConfig.theme));
57
+ } catch (e) {
58
+ // localStorage 不可用
59
+ }
60
+ }
61
+ if (loadedConfig.updatedAt !== undefined) {
62
+ try {
63
+ localStorage.setItem(
64
+ 'updatedAt',
65
+ JSON.stringify(loadedConfig.updatedAt)
66
+ );
67
+ } catch (e) {
68
+ // localStorage 不可用
69
+ }
70
+ }
71
+
72
+ resolve();
73
+ });
74
+ } else {
75
+ resolve();
76
+ }
77
+ });
78
+ }
@@ -0,0 +1,70 @@
1
+ import { Middleware } from '@reduxjs/toolkit';
2
+ import { RootState } from './store';
3
+ import dayjs from 'dayjs';
4
+
5
+ /**
6
+ * Redux middleware: 当配置修改后异步同步到 chrome.storage
7
+ * 使用防抖机制,避免频繁写入
8
+ */
9
+ let syncTimer: ReturnType<typeof setTimeout> | null = null;
10
+ const SYNC_DELAY = 300; // 防抖延迟时间(毫秒)
11
+
12
+ export const storageMiddleware: Middleware<{}, RootState> =
13
+ (store) => (next) => (action) => {
14
+ // 执行 action
15
+ const result = next(action);
16
+
17
+ // 检查是否是配置相关的 action
18
+ if (action.type?.startsWith('config/')) {
19
+ // 清除之前的定时器
20
+ if (syncTimer) {
21
+ clearTimeout(syncTimer);
22
+ }
23
+
24
+ // 设置新的定时器,延迟同步到 chrome.storage
25
+ syncTimer = setTimeout(() => {
26
+ const state = store.getState();
27
+ const config = state.config;
28
+
29
+ // 确保 updatedAt 是最新的
30
+ const configToSave = {
31
+ ...config,
32
+ updatedAt: dayjs().toISOString(),
33
+ };
34
+
35
+ // 异步同步到 chrome.storage
36
+ if (typeof chrome !== 'undefined' && chrome.storage?.local) {
37
+ chrome.storage.local.set(
38
+ {
39
+ theme: configToSave.theme,
40
+ updatedAt: configToSave.updatedAt,
41
+ // 可以添加其他配置项
42
+ // language: configToSave.language,
43
+ },
44
+ () => {
45
+ if (chrome.runtime?.lastError) {
46
+ console.error(
47
+ 'Failed to sync config to chrome.storage:',
48
+ chrome.runtime.lastError
49
+ );
50
+ }
51
+ }
52
+ );
53
+
54
+ // 同时同步到 localStorage(用于快速读取)
55
+ try {
56
+ localStorage.setItem('theme', JSON.stringify(configToSave.theme));
57
+ localStorage.setItem(
58
+ 'updatedAt',
59
+ JSON.stringify(configToSave.updatedAt)
60
+ );
61
+ } catch (e) {
62
+ // localStorage 可能不可用
63
+ console.warn('Failed to sync to localStorage:', e);
64
+ }
65
+ }
66
+ }, SYNC_DELAY);
67
+ }
68
+
69
+ return result;
70
+ };
@@ -0,0 +1,49 @@
1
+ import { configureStore } from '@reduxjs/toolkit';
2
+ import {
3
+ persistStore,
4
+ persistReducer,
5
+ FLUSH,
6
+ REHYDRATE,
7
+ PAUSE,
8
+ PERSIST,
9
+ PURGE,
10
+ REGISTER,
11
+ } from 'redux-persist';
12
+ import configReducer from './configSlice';
13
+ import { chromeStorage } from './chromeStorage';
14
+
15
+ // 配置持久化
16
+ // 注意:persistReducer 直接包装 configReducer,所以 key 应该是 'config'
17
+ // 不需要 whitelist,因为只持久化这一个 slice
18
+ const persistConfig = {
19
+ key: 'config', // 存储键名
20
+ storage: chromeStorage,
21
+ // 可选:配置版本号,用于迁移
22
+ // version: 1,
23
+ };
24
+
25
+ // 创建持久化的 reducer
26
+ const persistedReducer = persistReducer(persistConfig, configReducer);
27
+
28
+ export const store = configureStore({
29
+ reducer: {
30
+ config: persistedReducer,
31
+ },
32
+ middleware: (getDefaultMiddleware) =>
33
+ getDefaultMiddleware({
34
+ serializableCheck: {
35
+ ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
36
+ },
37
+ }),
38
+ });
39
+
40
+ export const persistor = persistStore(store);
41
+
42
+ // 调试:监听持久化状态
43
+ persistor.subscribe(() => {
44
+ const state = store.getState();
45
+ console.log('[redux-persist] Current config state:', state.config);
46
+ });
47
+
48
+ export type RootState = ReturnType<typeof store.getState>;
49
+ export type AppDispatch = typeof store.dispatch;
package/src/style.css ADDED
@@ -0,0 +1,107 @@
1
+ /* 导入颜色变量 */
2
+ @import './variables.css';
3
+
4
+ /* 全局样式 */
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ /* HTML 根元素样式 */
12
+ html {
13
+ color-scheme: light dark;
14
+ }
15
+
16
+ html[data-theme='dark'] {
17
+ color-scheme: dark;
18
+ }
19
+
20
+ html[data-theme='light'] {
21
+ color-scheme: light;
22
+ }
23
+
24
+ body {
25
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
26
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
27
+ sans-serif;
28
+ -webkit-font-smoothing: antialiased;
29
+ -moz-osx-font-smoothing: grayscale;
30
+ transition: background-color 0.3s ease, color 0.3s ease;
31
+ }
32
+
33
+ /* Popup 特定全局样式 */
34
+ body.popup {
35
+ width: 350px;
36
+ min-height: 400px;
37
+ background-color: var(--bg-primary);
38
+ color: var(--text-primary);
39
+ }
40
+
41
+ html[data-theme='dark'] body.popup {
42
+ background-color: #1a1a1a;
43
+ }
44
+
45
+ html[data-theme='light'] body.popup {
46
+ background-color: #ffffff;
47
+ }
48
+
49
+ /* NewTab 特定全局样式 */
50
+ body.newtab {
51
+ min-height: 100vh;
52
+ background-color: var(--bg-primary);
53
+ color: var(--text-primary);
54
+ transition: background 0.2s ease, color 0.2s ease;
55
+ }
56
+
57
+ /* History 特定全局样式 */
58
+ body.history {
59
+ min-height: 100vh;
60
+ background-color: var(--bg-primary);
61
+ color: var(--text-primary);
62
+ transition: background 0.2s ease, color 0.2s ease;
63
+ }
64
+
65
+ /* Bookmarks 特定全局样式 */
66
+ body.bookmarks {
67
+ min-height: 100vh;
68
+ background-color: var(--bg-primary);
69
+ color: var(--text-primary);
70
+ transition: background 0.2s ease, color 0.2s ease;
71
+ }
72
+
73
+ /* 亮色主题背景渐变 */
74
+ html[data-theme='light'] body.newtab {
75
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
76
+ color: var(--text-primary, #333333);
77
+ }
78
+
79
+ /* 亮色主题背景渐变 */
80
+ html[data-theme='light'] body.history {
81
+ background: linear-gradient(135deg, #6b8dd6 0%, #8e37d7 100%);
82
+ color: var(--text-primary, #333333);
83
+ }
84
+
85
+ /* 亮色主题背景渐变 */
86
+ html[data-theme='light'] body.bookmarks {
87
+ background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
88
+ color: var(--text-primary, #333333);
89
+ }
90
+
91
+ /* 暗色主题背景渐变 */
92
+ html[data-theme='dark'] body.newtab {
93
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
94
+ color: var(--text-primary, #ffffff);
95
+ }
96
+
97
+ /* 暗色主题背景渐变 */
98
+ html[data-theme='dark'] body.history {
99
+ background: linear-gradient(135deg, #121827 0%, #1f2a44 100%);
100
+ color: var(--text-primary, #ffffff);
101
+ }
102
+
103
+ /* 暗色主题背景渐变 */
104
+ html[data-theme='dark'] body.bookmarks {
105
+ background: linear-gradient(135deg, #0f2027 0%, #203a43 100%);
106
+ color: var(--text-primary, #ffffff);
107
+ }