feat:添加测试页面并优化时间戳工具

refactor: 优化时间戳转换组件及整体代码结构
chore: 更新构建配置和包依赖项
This commit is contained in:
雨霖铃
2026-02-13 17:29:01 +08:00
parent ee4d328b9c
commit 22985f4c18
17 changed files with 281 additions and 180 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"permissions": {
"allow": ["WebSearch"]
}
}
-1
View File
@@ -12,7 +12,6 @@ node_modules
stats.html stats.html
stats-*.json stats-*.json
.wxt .wxt
web-ext.config.ts
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
+7 -12
View File
@@ -61,10 +61,7 @@ export default defineBackground(() => {
}) })
.catch((error: Error) => { .catch((error: Error) => {
if (error.message.includes('restricted URL')) { if (error.message.includes('restricted URL')) {
console.warn( console.warn('Could not check status: on a restricted page.', error.message);
'Could not check status: on a restricted page.',
error.message,
);
} else if (error.message.includes('No active tab found')) { } else if (error.message.includes('No active tab found')) {
console.warn('Could not check status: no active tab found.'); console.warn('Could not check status: no active tab found.');
} else { } else {
@@ -84,10 +81,7 @@ export default defineBackground(() => {
}) })
.catch((error: Error) => { .catch((error: Error) => {
if (error.message.includes('restricted URL')) { if (error.message.includes('restricted URL')) {
console.warn( console.warn('Could not start recording: on a restricted page.', error.message);
'Could not start recording: on a restricted page.',
error.message,
);
} else if (error.message.includes('No active tab found')) { } else if (error.message.includes('No active tab found')) {
console.warn('Could not start recording: no active tab found.'); console.warn('Could not start recording: no active tab found.');
} else { } else {
@@ -107,10 +101,7 @@ export default defineBackground(() => {
}) })
.catch((error: Error) => { .catch((error: Error) => {
if (error.message.includes('restricted URL')) { if (error.message.includes('restricted URL')) {
console.warn( console.warn('Could not stop recording: on a restricted page.', error.message);
'Could not stop recording: on a restricted page.',
error.message,
);
} else if (error.message.includes('No active tab found')) { } else if (error.message.includes('No active tab found')) {
console.warn('Could not stop recording: no active tab found.'); console.warn('Could not stop recording: no active tab found.');
} else { } else {
@@ -132,6 +123,10 @@ export default defineBackground(() => {
} }
}); });
onMessage('getStringLength', (message) => {
return message.data.length;
});
async function sendToActiveTab(message: { async function sendToActiveTab(message: {
type: string; type: string;
data?: unknown; data?: unknown;
+4
View File
@@ -15,6 +15,10 @@ export default defineContentScript({
const recorder = createRecorder(); const recorder = createRecorder();
let isRecording = false; let isRecording = false;
onMessage('getStringLength', (message) => {
console.log(`[content] getStringLength received: ${message.data}`);
});
// 监听来自 background script 的消息 // 监听来自 background script 的消息
chrome.runtime.onMessage.addListener( chrome.runtime.onMessage.addListener(
(msg: { type: string }, _sender, sendResponse: (response: IResponse) => void) => { (msg: { type: string }, _sender, sendResponse: (response: IResponse) => void) => {
+11 -17
View File
@@ -1,38 +1,32 @@
// App.js // App.js
import { HashRouter as Router, Routes, Route } from 'react-router-dom'; import { HashRouter as Router, Routes, Route } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { SnackbarProvider } from 'notistack';
import TimestampPage from './pages/TimestampPage'; import TimestampPage from './pages/TimestampPage';
import RecordeReplayPage from './pages/RecordeReplayPage'; import RecordeReplayPage from './pages/RecordeReplayPage';
import TestPage from './pages/TestPage';
import Navbar from './components/Navbar'; import Navbar from './components/Navbar';
import RoutePersistence from './components/RoutePersistence'; import RoutePersistence from './components/RoutePersistence';
import './App.css'; import './App.css';
// 路由配置数据 // 路由配置数据
const navItems = [ const navItems = [
{ path: '/test', label: '测试页面', element: <TestPage /> },
{ path: '/', label: '时间戳', element: <TimestampPage /> }, { path: '/', label: '时间戳', element: <TimestampPage /> },
// {path: '/dzmy', label: '电子木鱼', element: <ElectronicWoodenFishPage/>},
{ path: '/recorde-replay', label: '录制与回放', element: <RecordeReplayPage /> }, { path: '/recorde-replay', label: '录制与回放', element: <RecordeReplayPage /> },
]; ];
const theme = createTheme();
function App() { function App() {
return ( return (
<Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}> <Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<RoutePersistence /> <RoutePersistence />
<ThemeProvider theme={theme}> <div className="app">
<CssBaseline /> <Navbar items={navItems} />
<SnackbarProvider maxSnack={3} anchorOrigin={{ vertical: 'top', horizontal: 'center' }}>
<Navbar items={navItems} /> <Routes>
<Routes> {navItems.map((item) => (
{navItems.map((item) => ( <Route key={item.path} path={item.path} element={item.element} />
<Route key={item.path} path={item.path} element={item.element} /> ))}
))} </Routes>
</Routes> </div>
</SnackbarProvider>
</ThemeProvider>
</Router> </Router>
); );
} }
+134
View File
@@ -0,0 +1,134 @@
import { useState, useCallback, FC, ReactNode, useEffect } from 'react';
import Button, { ButtonProps } from '@mui/material/Button';
import Snackbar from '@mui/material/Snackbar';
import Alert, { AlertColor } from '@mui/material/Alert';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import CheckIcon from '@mui/icons-material/Check';
type CopyStatus = 'idle' | 'copying' | 'success' | 'error';
interface CopyButtonProps extends Omit<ButtonProps, 'onClick' | 'variant'> {
textToCopy: string | number;
buttonText?: ReactNode;
successMessage?: string;
errorMessage?: string;
variant?: ButtonProps['variant'];
}
const CopyButton: FC<CopyButtonProps> = ({
textToCopy,
buttonText = '复制',
successMessage = '复制成功!',
errorMessage = '复制失败,请手动复制。',
variant = 'contained',
...buttonProps
}) => {
const [status, setStatus] = useState<CopyStatus>('idle');
const [openSnackbar, setOpenSnackbar] = useState(false);
const [snackbarContent, setSnackbarContent] = useState<{
message: string;
severity: AlertColor;
} | null>(null);
useEffect(() => {
if (status === 'success' || status === 'error') {
const timer = setTimeout(() => setStatus('idle'), 2000);
return () => clearTimeout(timer);
}
return undefined;
}, [status]);
const performCopy = useCallback(async () => {
if (!textToCopy) {
console.warn('没有提供要复制的文本');
return false;
}
const safeText = String(textToCopy);
try {
await navigator.clipboard.writeText(safeText);
return true;
} catch (err) {
console.error('使用 Clipboard API 复制失败:', err);
return false;
}
}, [textToCopy]);
const handleClick = useCallback(async () => {
if (status !== 'idle') return;
setStatus('copying');
let isSuccess = false;
try {
[isSuccess] = await Promise.all([
performCopy(),
new Promise((resolve) => setTimeout(resolve, 300)),
]);
} catch (error) {
console.error('复制时出错:', error);
isSuccess = false;
} finally {
const newStatus = isSuccess ? 'success' : 'error';
setStatus(newStatus);
setSnackbarContent({
message: isSuccess ? successMessage : errorMessage,
severity: newStatus,
});
setOpenSnackbar(true);
}
}, [performCopy, status, successMessage, errorMessage]);
const handleCloseSnackbar = (_event?: Event | React.SyntheticEvent, reason?: string) => {
if (reason === 'clickaway') return;
setOpenSnackbar(false);
};
const renderButtonIcon = () => {
if (status === 'success') {
return <CheckIcon sx={{ mr: 1 }} fontSize="small" />;
}
return <ContentCopyIcon sx={{ mr: 1 }} fontSize="small" />;
};
return (
<>
<Button
onClick={handleClick}
disabled={status !== 'idle'}
variant={variant}
{...buttonProps}
sx={{
...buttonProps.sx,
transition: 'background-color 0.3s',
...(status === 'success' && {
bgcolor: 'success.main',
'&:hover': {
bgcolor: 'success.dark',
},
}),
}}
>
{renderButtonIcon()}
{buttonText}
</Button>
<Snackbar
open={openSnackbar}
autoHideDuration={2000}
onClose={handleCloseSnackbar}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
{snackbarContent ? (
<Alert
onClose={handleCloseSnackbar}
severity={snackbarContent.severity}
variant="filled"
sx={{ width: '100%' }}
>
{snackbarContent.message}
</Alert>
) : undefined}
</Snackbar>
</>
);
};
export default CopyButton;
@@ -8,7 +8,6 @@ import {
FormControl, FormControl,
InputLabel, InputLabel,
Paper, Paper,
Typography,
Stack, Stack,
Box, Box,
SelectChangeEvent, SelectChangeEvent,
@@ -87,9 +86,6 @@ export function DatetimeToTimestamp() {
return ( return (
<Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2 }}> <Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2 }}>
<Typography variant="h6" component="h2" align="center" gutterBottom>
</Typography>
<Stack spacing={2} sx={{ mt: 2 }}> <Stack spacing={2} sx={{ mt: 2 }}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}> <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
<TextField <TextField
@@ -1,8 +1,6 @@
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import CopyButton from './CopyButton';
import { Button, Paper, Typography, Stack, Box } from '@mui/material'; import { Button, Paper, Typography, Stack, Box } from '@mui/material';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import CheckIcon from '@mui/icons-material/Check';
import { useCopy } from './useCopy';
/** /**
* 时间戳显示和执行组件 * 时间戳显示和执行组件
@@ -16,7 +14,9 @@ import { useCopy } from './useCopy';
* @returns {JSX.Element} 时间戳组件 * @returns {JSX.Element} 时间戳组件
*/ */
export function TimestampExecution() { export function TimestampExecution() {
const [currentTimestamp, setCurrentTimestamp] = useState(() => Math.floor(Date.now())); const [currentTimestamp, setCurrentTimestamp] = useState(() =>
Math.floor(Date.now()),
);
const [showMilliseconds, setShowMilliseconds] = useState(true); const [showMilliseconds, setShowMilliseconds] = useState(true);
const [isRunningTimestamp, setIsRunningTimestamp] = useState(true); const [isRunningTimestamp, setIsRunningTimestamp] = useState(true);
@@ -45,14 +45,19 @@ export function TimestampExecution() {
setIsRunningTimestamp((prev) => !prev); setIsRunningTimestamp((prev) => !prev);
}, []); }, []);
const unitButtonLabel = showMilliseconds ? '切换为秒显示' : '切换为毫秒显示'; const unitButtonLabel = showMilliseconds
const toggleButtonLabel = isRunningTimestamp ? '停止时间戳自动更新' : '开始时间戳自动更新'; ? '切换为秒显示'
: '切换为毫秒显示';
const toggleButtonLabel = isRunningTimestamp
? '停止时间戳自动更新'
: '开始时间戳自动更新';
const toggleButtonText = isRunningTimestamp ? '停止' : '开始'; const toggleButtonText = isRunningTimestamp ? '停止' : '开始';
const { isCopied, copy } = useCopy();
return ( return (
<Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2, minWidth: 320, textAlign: 'center' }}> <Paper
elevation={3}
sx={{ p: 2, my: 2, borderRadius: 2, minWidth: 320, textAlign: 'center' }}
>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@@ -96,21 +101,12 @@ export function TimestampExecution() {
</Button> </Button>
{/* <CopyButton <CopyButton
textToCopy={String(currentTimestamp)} textToCopy={String(currentTimestamp)}
buttonText="复制" buttonText="复制"
aria-label="复制当前时间戳到剪贴板" aria-label="复制当前时间戳到剪贴板"
color="primary" color="primary"
/> */} />
<Button
variant="contained"
onClick={() => copy(String(currentTimestamp))}
startIcon={isCopied ? <CheckIcon /> : <ContentCopyIcon />}
color={isCopied ? 'success' : 'primary'}
>
{isCopied ? '已复制' : '复制'}
</Button>
<Button <Button
variant="contained" variant="contained"
@@ -8,7 +8,6 @@ import {
FormControl, FormControl,
InputLabel, InputLabel,
Paper, Paper,
Typography,
Stack, Stack,
Box, Box,
SelectChangeEvent, SelectChangeEvent,
@@ -110,9 +109,6 @@ export function TimestampToDatetime() {
return ( return (
<Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2 }}> <Paper elevation={3} sx={{ p: 2, my: 2, borderRadius: 2 }}>
<Typography variant="h6" component="h2" align="center" gutterBottom>
</Typography>
<Stack spacing={2} sx={{ mt: 2 }}> <Stack spacing={2} sx={{ mt: 2 }}>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}> <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
<TextField <TextField
-39
View File
@@ -1,39 +0,0 @@
import { useState, useCallback } from 'react';
import { useSnackbar } from 'notistack'; // 1. 引入 hook
export const useCopy = (resetInterval = 2000) => {
const [isCopied, setIsCopied] = useState(false);
const { enqueueSnackbar } = useSnackbar(); // 2. 获取触发函数
const copy = useCallback(
async (text: string | number) => {
if (!text) {
enqueueSnackbar('没有可复制的内容', { variant: 'warning' });
return false;
}
try {
await navigator.clipboard.writeText(String(text));
setIsCopied(true);
// 3. 触发成功通知
enqueueSnackbar('复制成功!', { variant: 'success' });
// 自动重置状态
setTimeout(() => setIsCopied(false), resetInterval);
return true;
} catch (error) {
console.error('Copy failed:', error);
// 4. 触发错误通知
enqueueSnackbar('复制失败,请手动复制', { variant: 'error' });
setIsCopied(false);
return false;
}
},
[enqueueSnackbar, resetInterval],
);
return { isCopied, copy };
};
-39
View File
@@ -1,39 +0,0 @@
import { useState, useCallback } from 'react';
import { useSnackbar } from 'notistack'; // 1. 引入 hook
export const useCopy = (resetInterval = 2000) => {
const [isCopied, setIsCopied] = useState(false);
const { enqueueSnackbar } = useSnackbar();
const copy = useCallback(
async (text: string | number) => {
if (!text) {
enqueueSnackbar('没有可复制的内容', { variant: 'warning' });
return false;
}
try {
await navigator.clipboard.writeText(String(text));
setIsCopied(true);
// 3. 触发成功通知
enqueueSnackbar('复制成功!', { variant: 'success' });
// 自动重置状态
setTimeout(() => setIsCopied(false), resetInterval);
return true;
} catch (error) {
console.error('Copy failed:', error);
// 4. 触发错误通知
enqueueSnackbar('复制失败,请手动复制', { variant: 'error' });
setIsCopied(false);
return false;
}
},
[enqueueSnackbar, resetInterval],
);
return { isCopied, copy };
};
+22
View File
@@ -0,0 +1,22 @@
import { sendMessage } from '@/utils/messages';
import { Button } from '@mui/material';
const TestPage = () => {
const handleSeedMessage = async () => {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
tabs.forEach(async (tab) => {
console.log(tab.id);
const length = await sendMessage('getStringLength', 'hello world', tab.id);
console.log('字符串长度:', length);
});
};
return (
<div>
<Button variant="contained" color="primary" onClick={handleSeedMessage}>
</Button>
</div>
);
};
export default TestPage;
+66 -41
View File
@@ -20,11 +20,11 @@
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1", "@testing-library/react": "^16.3.1",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@webext-core/messaging": "^2.3.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"dexie": "^4.2.1", "dexie": "^4.2.1",
"dexie-react-hooks": "^4.2.0", "dexie-react-hooks": "^4.2.0",
"notistack": "^3.0.2",
"react": "^19.2.3", "react": "^19.2.3",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",
"react-markdown": "^6.0.3", "react-markdown": "^6.0.3",
@@ -1450,6 +1450,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@lukeed/csprng": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/@lukeed/csprng/-/csprng-1.1.0.tgz",
"integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@mui/core-downloads-tracker": { "node_modules/@mui/core-downloads-tracker": {
"version": "7.3.8", "version": "7.3.8",
"resolved": "https://registry.npmmirror.com/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.8.tgz", "resolved": "https://registry.npmmirror.com/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.8.tgz",
@@ -2875,6 +2884,17 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@webext-core/messaging": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/@webext-core/messaging/-/messaging-2.3.0.tgz",
"integrity": "sha512-gChSVKdRs7JEq5hFH0jVROSvTq+sKq9afXTA/gBswep3RWNLhXyDsXFlvPMkYbmML1XZ8QKKC9ou2MlCKRZwSQ==",
"license": "MIT",
"dependencies": {
"serialize-error": "^11.0.0",
"uid": "^2.0.2",
"webextension-polyfill": "^0.10.0"
}
},
"node_modules/@wxt-dev/browser": { "node_modules/@wxt-dev/browser": {
"version": "0.1.32", "version": "0.1.32",
"resolved": "https://registry.npmmirror.com/@wxt-dev/browser/-/browser-0.1.32.tgz", "resolved": "https://registry.npmmirror.com/@wxt-dev/browser/-/browser-0.1.32.tgz",
@@ -5896,15 +5916,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/goober": {
"version": "2.1.18",
"resolved": "https://registry.npmmirror.com/goober/-/goober-2.1.18.tgz",
"integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
@@ -8171,37 +8182,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/notistack": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/notistack/-/notistack-3.0.2.tgz",
"integrity": "sha512-0R+/arLYbK5Hh7mEfR2adt0tyXJcCC9KkA2hc56FeWik2QN6Bm/S4uW+BjzDARsJth5u06nTjelSw/VSnB1YEA==",
"license": "MIT",
"dependencies": {
"clsx": "^1.1.0",
"goober": "^2.0.33"
},
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/notistack"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/notistack/node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/nth-check": { "node_modules/nth-check": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz", "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz",
@@ -9673,6 +9653,33 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/serialize-error": {
"version": "11.0.3",
"resolved": "https://registry.npmmirror.com/serialize-error/-/serialize-error-11.0.3.tgz",
"integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==",
"license": "MIT",
"dependencies": {
"type-fest": "^2.12.2"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/serialize-error/node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -10573,6 +10580,18 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/uid": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/uid/-/uid-2.0.2.tgz",
"integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==",
"license": "MIT",
"dependencies": {
"@lukeed/csprng": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
@@ -11045,6 +11064,12 @@
"integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==", "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/webextension-polyfill": {
"version": "0.10.0",
"resolved": "https://registry.npmmirror.com/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
"integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==",
"license": "MPL-2.0"
},
"node_modules/webpack-virtual-modules": { "node_modules/webpack-virtual-modules": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+1 -1
View File
@@ -28,11 +28,11 @@
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1", "@testing-library/react": "^16.3.1",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"@webext-core/messaging": "^2.3.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dayjs": "^1.11.19", "dayjs": "^1.11.19",
"dexie": "^4.2.1", "dexie": "^4.2.1",
"dexie-react-hooks": "^4.2.0", "dexie-react-hooks": "^4.2.0",
"notistack": "^3.0.2",
"react": "^19.2.3", "react": "^19.2.3",
"react-dom": "^19.2.3", "react-dom": "^19.2.3",
"react-markdown": "^6.0.3", "react-markdown": "^6.0.3",
+8
View File
@@ -1,3 +1,5 @@
import { defineExtensionMessaging } from '@webext-core/messaging';
export const messages = { export const messages = {
popup: { popup: {
from: { from: {
@@ -28,3 +30,9 @@ export const messages = {
}, },
}, },
}; };
interface ProtocolMap {
getStringLength(data: string): number;
}
export const { sendMessage, onMessage } = defineExtensionMessaging<ProtocolMap>();
+6
View File
@@ -0,0 +1,6 @@
import { defineWebExtConfig } from 'wxt';
export default defineWebExtConfig({
startUrls: ['https://www.baidu.com', 'chrome://extensions/'],
chromiumArgs: ['chrome://extensions/', '--auto-open-devtools-for-tabs'],
});
+1 -2
View File
@@ -21,7 +21,6 @@ export default defineConfig({
action: { action: {
default_title: 'Testing Tools', default_title: 'Testing Tools',
}, },
// 注意:WXT 会根据 entrypoints/options/index.html 自动生成 options_ui 的 page 路径
options_ui: { options_ui: {
open_in_tab: true, open_in_tab: true,
}, },
@@ -41,7 +40,7 @@ export default defineConfig({
terserOptions: { terserOptions: {
format: { format: {
ascii_only: true, ascii_only: true,
comments: false, // 去掉注释,防止注释里有乱码 comments: false,
}, },
}, },
}, },