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.
- package/LICENSE +21 -0
- package/README.md +135 -0
- package/package.json +54 -0
- package/scripts/create.js +418 -0
- package/scripts/dev.js +26 -0
- package/src/background.ts +9 -0
- package/src/bookmarks/index.module.css +17 -0
- package/src/bookmarks/index.tsx +42 -0
- package/src/bookmarks.html +12 -0
- package/src/components/BookmarksContent/index.module.css +95 -0
- package/src/components/BookmarksContent/index.tsx +100 -0
- package/src/components/ConfigInfo/index.module.css +30 -0
- package/src/components/ConfigInfo/index.tsx +36 -0
- package/src/components/HistoryContent/index.module.css +99 -0
- package/src/components/HistoryContent/index.tsx +86 -0
- package/src/components/NewTabContent/index.module.css +86 -0
- package/src/components/NewTabContent/index.tsx +30 -0
- package/src/components/NewTabHeader/index.module.css +76 -0
- package/src/components/NewTabHeader/index.tsx +14 -0
- package/src/components/PopupContent/index.module.css +46 -0
- package/src/components/PopupContent/index.tsx +29 -0
- package/src/components/PopupHeader/index.module.css +15 -0
- package/src/components/PopupHeader/index.tsx +14 -0
- package/src/components/ThemeButton/index.module.css +202 -0
- package/src/components/ThemeButton/index.tsx +48 -0
- package/src/content.ts +10 -0
- package/src/history/index.module.css +17 -0
- package/src/history/index.tsx +42 -0
- package/src/history.html +12 -0
- package/src/hooks/useStorage.ts +50 -0
- package/src/hooks/useTabs.ts +37 -0
- package/src/hooks/useTheme.ts +27 -0
- package/src/manifest.json +26 -0
- package/src/newtab/index.module.css +17 -0
- package/src/newtab/index.tsx +43 -0
- package/src/newtab.html +12 -0
- package/src/popup/index.module.css +8 -0
- package/src/popup/index.tsx +36 -0
- package/src/popup.html +12 -0
- package/src/store/README.md +176 -0
- package/src/store/chromeStorage.ts +88 -0
- package/src/store/configSlice.ts +40 -0
- package/src/store/hooks.ts +6 -0
- package/src/store/initConfig.ts +78 -0
- package/src/store/storageMiddleware.ts +70 -0
- package/src/store/store.ts +49 -0
- package/src/style.css +107 -0
- package/src/utils/initTheme.ts +40 -0
- package/src/variables.css +146 -0
- package/src/vite-env.d.ts +6 -0
- package/tsconfig.json +22 -0
- package/tsconfig.node.json +10 -0
- package/vite-plugin-fix-extension.ts +81 -0
- package/vite.config.ts +50 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 同步初始化主题 - 在 React 渲染前执行
|
|
3
|
+
* 避免主题闪烁
|
|
4
|
+
*/
|
|
5
|
+
export function initTheme(): void {
|
|
6
|
+
try {
|
|
7
|
+
// 检测系统主题偏好
|
|
8
|
+
const getSystemTheme = (): 'light' | 'dark' => {
|
|
9
|
+
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
10
|
+
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
11
|
+
? 'dark'
|
|
12
|
+
: 'light';
|
|
13
|
+
}
|
|
14
|
+
return 'light'; // 降级方案
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// 先设置系统主题作为默认值
|
|
18
|
+
const systemTheme = getSystemTheme();
|
|
19
|
+
document.documentElement.setAttribute('data-theme', systemTheme);
|
|
20
|
+
|
|
21
|
+
// 异步读取实际配置并更新(如果用户之前设置过主题)
|
|
22
|
+
if (typeof chrome !== 'undefined' && chrome.storage) {
|
|
23
|
+
chrome.storage.local.get(['theme'], (result) => {
|
|
24
|
+
// 如果存储中有主题配置,使用存储的主题
|
|
25
|
+
if (result.theme !== undefined) {
|
|
26
|
+
const isDarkMode = result.theme === true;
|
|
27
|
+
document.documentElement.setAttribute(
|
|
28
|
+
'data-theme',
|
|
29
|
+
isDarkMode ? 'dark' : 'light'
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
// 如果没有存储的主题配置,保持系统主题
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error initializing theme:', error);
|
|
37
|
+
// 设置默认主题
|
|
38
|
+
document.documentElement.setAttribute('data-theme', 'light');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/* 颜色变量定义 */
|
|
2
|
+
|
|
3
|
+
/* 基础颜色 */
|
|
4
|
+
:root {
|
|
5
|
+
/* 主色调 - 亮色主题 */
|
|
6
|
+
--color-primary: #667eea;
|
|
7
|
+
--color-primary-dark: #764ba2;
|
|
8
|
+
--color-primary-light: #8b9eff;
|
|
9
|
+
|
|
10
|
+
/* 背景色 - 亮色主题 */
|
|
11
|
+
--bg-primary: #ffffff;
|
|
12
|
+
--bg-secondary: #f5f5f5;
|
|
13
|
+
--bg-tertiary: #e0e0e0;
|
|
14
|
+
|
|
15
|
+
/* 文字色 - 亮色主题 */
|
|
16
|
+
--text-primary: #333333;
|
|
17
|
+
--text-secondary: #666666;
|
|
18
|
+
--text-tertiary: #999999;
|
|
19
|
+
--text-inverse: #ffffff;
|
|
20
|
+
|
|
21
|
+
/* 边框色 - 亮色主题 */
|
|
22
|
+
--border-color: #e0e0e0;
|
|
23
|
+
--border-color-light: #f0f0f0;
|
|
24
|
+
--border-color-dark: #cccccc;
|
|
25
|
+
|
|
26
|
+
/* 阴影 - 亮色主题 */
|
|
27
|
+
--shadow-sm: rgba(0, 0, 0, 0.05);
|
|
28
|
+
--shadow-md: rgba(0, 0, 0, 0.1);
|
|
29
|
+
--shadow-lg: rgba(0, 0, 0, 0.15);
|
|
30
|
+
--shadow-xl: rgba(0, 0, 0, 0.3);
|
|
31
|
+
--shadow-primary: rgba(102, 126, 234, 0.4);
|
|
32
|
+
|
|
33
|
+
/* 渐变 - 亮色主题 */
|
|
34
|
+
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
35
|
+
--gradient-bg-light: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
36
|
+
|
|
37
|
+
/* 半透明背景 */
|
|
38
|
+
--bg-overlay-light: rgba(255, 255, 255, 0.25);
|
|
39
|
+
--bg-overlay-dark: rgba(0, 0, 0, 0.1);
|
|
40
|
+
|
|
41
|
+
/* 标题背景 - 更深的半透明 */
|
|
42
|
+
--bg-header-light: rgba(255, 255, 255, 0.4);
|
|
43
|
+
--bg-header-dark: rgba(0, 0, 0, 0.5);
|
|
44
|
+
|
|
45
|
+
/* 标题文字阴影 - 增强对比度 */
|
|
46
|
+
--text-shadow-header: 0 3px 12px rgba(0, 0, 0, 0.7),
|
|
47
|
+
0 2px 6px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
48
|
+
--text-shadow-header-dark: 0 3px 12px rgba(0, 0, 0, 0.9),
|
|
49
|
+
0 2px 6px rgba(0, 0, 0, 0.7), 0 1px 2px rgba(0, 0, 0, 0.6);
|
|
50
|
+
|
|
51
|
+
/* 按钮颜色 */
|
|
52
|
+
--button-bg-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
53
|
+
--button-bg-hover: rgba(102, 126, 234, 0.4);
|
|
54
|
+
|
|
55
|
+
/* 状态颜色 */
|
|
56
|
+
--color-success: #4caf50;
|
|
57
|
+
--color-error: #f44336;
|
|
58
|
+
--color-warning: #ff9800;
|
|
59
|
+
--color-info: #2196f3;
|
|
60
|
+
|
|
61
|
+
/* 禁用状态 */
|
|
62
|
+
--opacity-disabled: 0.6;
|
|
63
|
+
|
|
64
|
+
/* 圆角 */
|
|
65
|
+
--radius-sm: 4px;
|
|
66
|
+
--radius-md: 8px;
|
|
67
|
+
--radius-lg: 12px;
|
|
68
|
+
--radius-xl: 16px;
|
|
69
|
+
--radius-full: 9999px;
|
|
70
|
+
|
|
71
|
+
/* 间距 */
|
|
72
|
+
--spacing-xs: 4px;
|
|
73
|
+
--spacing-sm: 8px;
|
|
74
|
+
--spacing-md: 16px;
|
|
75
|
+
--spacing-lg: 24px;
|
|
76
|
+
--spacing-xl: 32px;
|
|
77
|
+
--spacing-2xl: 40px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* 暗色主题 */
|
|
81
|
+
[data-theme='dark'] {
|
|
82
|
+
/* 主色调 - 暗色主题 */
|
|
83
|
+
--color-primary: #8b9eff;
|
|
84
|
+
--color-primary-dark: #4a5568;
|
|
85
|
+
--color-primary-light: #a5b4fc;
|
|
86
|
+
|
|
87
|
+
/* 背景色 - 暗色主题 */
|
|
88
|
+
--bg-primary: #1a1a1a;
|
|
89
|
+
--bg-secondary: #2d2d2d;
|
|
90
|
+
--bg-tertiary: #404040;
|
|
91
|
+
|
|
92
|
+
/* 文字色 - 暗色主题 */
|
|
93
|
+
--text-primary: #ffffff;
|
|
94
|
+
--text-secondary: #b0b0b0;
|
|
95
|
+
--text-tertiary: #808080;
|
|
96
|
+
--text-inverse: #333333;
|
|
97
|
+
|
|
98
|
+
/* 边框色 - 暗色主题 */
|
|
99
|
+
--border-color: #404040;
|
|
100
|
+
--border-color-light: #2d2d2d;
|
|
101
|
+
--border-color-dark: #555555;
|
|
102
|
+
|
|
103
|
+
/* 阴影 - 暗色主题 */
|
|
104
|
+
--shadow-sm: rgba(0, 0, 0, 0.2);
|
|
105
|
+
--shadow-md: rgba(0, 0, 0, 0.3);
|
|
106
|
+
--shadow-lg: rgba(0, 0, 0, 0.4);
|
|
107
|
+
--shadow-xl: rgba(0, 0, 0, 0.6);
|
|
108
|
+
--shadow-primary: rgba(0, 0, 0, 0.5);
|
|
109
|
+
|
|
110
|
+
/* 渐变 - 暗色主题 */
|
|
111
|
+
--gradient-primary: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
|
|
112
|
+
--gradient-bg-dark: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
|
113
|
+
|
|
114
|
+
/* 半透明背景 */
|
|
115
|
+
--bg-overlay-light: rgba(255, 255, 255, 0.05);
|
|
116
|
+
--bg-overlay-dark: rgba(0, 0, 0, 0.5);
|
|
117
|
+
|
|
118
|
+
/* 标题背景 - 更深的半透明 */
|
|
119
|
+
--bg-header-light: rgba(255, 255, 255, 0.1);
|
|
120
|
+
--bg-header-dark: rgba(0, 0, 0, 0.6);
|
|
121
|
+
|
|
122
|
+
/* 标题文字阴影 - 增强对比度 */
|
|
123
|
+
--text-shadow-header: 0 3px 12px rgba(0, 0, 0, 0.7),
|
|
124
|
+
0 2px 6px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
125
|
+
--text-shadow-header-dark: 0 3px 12px rgba(0, 0, 0, 0.9),
|
|
126
|
+
0 2px 6px rgba(0, 0, 0, 0.7), 0 1px 2px rgba(0, 0, 0, 0.6);
|
|
127
|
+
|
|
128
|
+
/* 按钮颜色 */
|
|
129
|
+
--button-bg-primary: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
|
|
130
|
+
--button-bg-hover: rgba(0, 0, 0, 0.5);
|
|
131
|
+
|
|
132
|
+
/* 圆角 */
|
|
133
|
+
--radius-sm: 4px;
|
|
134
|
+
--radius-md: 8px;
|
|
135
|
+
--radius-lg: 12px;
|
|
136
|
+
--radius-xl: 16px;
|
|
137
|
+
--radius-full: 9999px;
|
|
138
|
+
|
|
139
|
+
/* 间距 */
|
|
140
|
+
--spacing-xs: 4px;
|
|
141
|
+
--spacing-sm: 8px;
|
|
142
|
+
--spacing-md: 16px;
|
|
143
|
+
--spacing-lg: 24px;
|
|
144
|
+
--spacing-xl: 32px;
|
|
145
|
+
--spacing-2xl: 40px;
|
|
146
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"types": ["chrome"]
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"],
|
|
21
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
22
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import {
|
|
3
|
+
copyFileSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
} from 'fs';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
export function fixExtension(): Plugin {
|
|
17
|
+
return {
|
|
18
|
+
name: 'fix-extension',
|
|
19
|
+
writeBundle() {
|
|
20
|
+
// 构建完成后修复扩展文件
|
|
21
|
+
const distDir = join(__dirname, 'dist');
|
|
22
|
+
const manifestSrc = join(__dirname, 'src/manifest.json');
|
|
23
|
+
const manifestDest = join(distDir, 'manifest.json');
|
|
24
|
+
const popupHtmlSrc = join(distDir, 'src/popup.html');
|
|
25
|
+
const popupHtmlDest = join(distDir, 'popup.html');
|
|
26
|
+
const srcDir = join(distDir, 'src');
|
|
27
|
+
|
|
28
|
+
// 复制 manifest.json
|
|
29
|
+
try {
|
|
30
|
+
mkdirSync(distDir, { recursive: true });
|
|
31
|
+
copyFileSync(manifestSrc, manifestDest);
|
|
32
|
+
console.log('✓ manifest.json copied to dist');
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error('Error copying manifest.json:', error);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 移动并修复 popup.html
|
|
38
|
+
if (existsSync(popupHtmlSrc)) {
|
|
39
|
+
try {
|
|
40
|
+
let content = readFileSync(popupHtmlSrc, 'utf-8');
|
|
41
|
+
// 修复资源路径:将 /assets/ 改为 ./assets/
|
|
42
|
+
content = content.replace(/href="\/assets\//g, 'href="./assets/');
|
|
43
|
+
content = content.replace(/src="\/assets\//g, 'src="./assets/');
|
|
44
|
+
|
|
45
|
+
writeFileSync(popupHtmlDest, content);
|
|
46
|
+
console.log('✓ popup.html moved and fixed');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('Error fixing popup.html:', error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 移动并修复 newtab.html
|
|
53
|
+
const newtabHtmlSrc = join(distDir, 'src/newtab.html');
|
|
54
|
+
const newtabHtmlDest = join(distDir, 'newtab.html');
|
|
55
|
+
if (existsSync(newtabHtmlSrc)) {
|
|
56
|
+
try {
|
|
57
|
+
let content = readFileSync(newtabHtmlSrc, 'utf-8');
|
|
58
|
+
// 修复资源路径:将 /assets/ 改为 ./assets/
|
|
59
|
+
content = content.replace(/href="\/assets\//g, 'href="./assets/');
|
|
60
|
+
content = content.replace(/src="\/assets\//g, 'src="./assets/');
|
|
61
|
+
|
|
62
|
+
writeFileSync(newtabHtmlDest, content);
|
|
63
|
+
console.log('✓ newtab.html moved and fixed');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error('Error fixing newtab.html:', error);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.warn('⚠ newtab.html not found in dist/src/, skipping...');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 删除 src 目录(确保所有文件都已处理)
|
|
72
|
+
try {
|
|
73
|
+
if (existsSync(srcDir)) {
|
|
74
|
+
rmSync(srcDir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.warn('⚠ Could not remove src directory:', e);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { fixExtension } from './vite-plugin-fix-extension';
|
|
5
|
+
|
|
6
|
+
export default defineConfig(({ mode }) => {
|
|
7
|
+
// 检查是否为开发模式(通过 mode)
|
|
8
|
+
const isDev =
|
|
9
|
+
mode === 'development' || process.env.NODE_ENV === 'development';
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
plugins: [react(), fixExtension()],
|
|
13
|
+
css: {
|
|
14
|
+
modules: {
|
|
15
|
+
// CSS Modules 类名生成规则
|
|
16
|
+
generateScopedName: '[name]__[local]___[hash:base64:5]',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
build: {
|
|
20
|
+
outDir: 'dist',
|
|
21
|
+
emptyOutDir: true,
|
|
22
|
+
minify: !isDev, // 开发模式不压缩,生产模式压缩
|
|
23
|
+
rollupOptions: {
|
|
24
|
+
input: {
|
|
25
|
+
popup: resolve(__dirname, 'src/popup.html'),
|
|
26
|
+
newtab: resolve(__dirname, 'src/newtab.html'),
|
|
27
|
+
history: resolve(__dirname, 'src/history.html'),
|
|
28
|
+
bookmarks: resolve(__dirname, 'src/bookmarks.html'),
|
|
29
|
+
background: resolve(__dirname, 'src/background.ts'),
|
|
30
|
+
content: resolve(__dirname, 'src/content.ts'),
|
|
31
|
+
},
|
|
32
|
+
output: {
|
|
33
|
+
entryFileNames: (chunkInfo) => {
|
|
34
|
+
return chunkInfo.name === 'background' ||
|
|
35
|
+
chunkInfo.name === 'content'
|
|
36
|
+
? '[name].js'
|
|
37
|
+
: 'assets/[name]-[hash].js';
|
|
38
|
+
},
|
|
39
|
+
chunkFileNames: 'assets/[name]-[hash].js',
|
|
40
|
+
assetFileNames: (assetInfo) => {
|
|
41
|
+
if (assetInfo.name === 'popup.html') {
|
|
42
|
+
return '[name][extname]';
|
|
43
|
+
}
|
|
44
|
+
return 'assets/[name]-[hash].[ext]';
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
});
|