From 710bffd2f8b85779fcd5c5951d529a32c86155ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Wed, 28 Jan 2026 20:26:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20husky=20=E5=92=8C?= =?UTF-8?q?=20lint-staged=20=E6=94=AF=E6=8C=81=EF=BC=8C=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=20prettier=20=E8=BF=9B=E8=A1=8C=E4=BB=A3=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .husky/pre-commit | 1 + .prettierrc | 12 ++ components/CopyButton.tsx | 78 +++++-------- entrypoints/background.ts | 27 ----- entrypoints/options/index.html | 14 +-- package-lock.json | 207 +++++++++++++++++++++++++++++++++ package.json | 15 ++- pages/RecordeReplayPage.tsx | 35 +++--- pages/TimestampPage.tsx | 20 ++-- tsconfig.json | 41 ++++++- 10 files changed, 338 insertions(+), 112 deletions(-) create mode 100644 .husky/pre-commit create mode 100644 .prettierrc diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f7bac94 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf" +} \ No newline at end of file diff --git a/components/CopyButton.tsx b/components/CopyButton.tsx index 4a2612b..b2132ed 100644 --- a/components/CopyButton.tsx +++ b/components/CopyButton.tsx @@ -1,5 +1,4 @@ -import { useState, useCallback } from 'react'; -import { copyToClipboard } from '@/utils/chromeUtils'; +import { useEffect, useState, useCallback } from 'react'; const CopyButton = ({ text = '要复制的文本', @@ -7,14 +6,13 @@ const CopyButton = ({ className = 'action-btn', successMessage = '复制成功!', errorMessage = '复制失败,请手动复制。', - onCopyOverride = null, }) => { const [status, setStatus] = useState('idle'); // 'idle' | 'copying' | 'success' | 'error' useEffect(() => { - let timer; + let timer: number; if (status === 'success' || status === 'error') { - timer = setTimeout(() => { + timer = window.setTimeout(() => { setStatus('idle'); }, 2000); } @@ -30,29 +28,15 @@ const CopyButton = ({ const safeText = String(text); - // 优先使用传入的复制函数 - if (onCopyOverride) { - return await onCopyOverride(safeText); + if (navigator.clipboard && navigator.clipboard.writeText) { + try { + await navigator.clipboard.writeText(safeText); + return true; + } catch (err) { + console.error('使用 Clipboard API 复制失败:', err); + return false; + } } - - const textArea = document.createElement("textarea"); - textArea.value = safeText; - - // 确保元素存在但不可见,且不影响布局 - textArea.style.position = "fixed"; - textArea.style.left = "-9999px"; - textArea.style.top = "0"; - textArea.style.opacity = "0"; - - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - const successful = document.execCommand('copy'); - document.body.removeChild(textArea); - - // 使用通用的复制函数 - return true; }; const handleClick = useCallback(async () => { @@ -65,35 +49,37 @@ const CopyButton = ({ console.error('复制时出错:', error); setStatus('error'); } - }, [text, onCopyOverride]); + }, [text]); // 根据状态计算当前显示的文本 - const currentText = status === 'success' ? successMessage - : status === 'error' ? errorMessage - : buttonText; + const currentText = + status === 'success' ? successMessage : status === 'error' ? errorMessage : buttonText; - // 根据状态计算样式 (建议使用 CSS Module 或 Tailwind,这里为了演示保留内联) - const getBackgroundColor = () => { - switch (status) { - case 'success': return '#4CAF50'; - case 'error': return '#f44336'; - default: return '#4CAF50'; // 让 CSS 类控制默认颜色 + // 动态样式:只在非默认状态下覆盖颜色,平时让 className 控制 + const getStyle = () => { + const baseStyle = { + transition: 'all 0.3s ease', + cursor: 'pointer', // 确保有手型光标 + // 这里去掉了 padding/border/radius 的硬编码,建议在 CSS 类中定义 + // 除非你想强制覆盖 + }; + + if (status === 'success') { + return { ...baseStyle, backgroundColor: '#4CAF50', color: 'white' }; } + if (status === 'error') { + return { ...baseStyle, backgroundColor: '#f44336', color: 'white' }; + } + + return baseStyle; }; return ( diff --git a/entrypoints/background.ts b/entrypoints/background.ts index 97f9541..f96fa48 100644 --- a/entrypoints/background.ts +++ b/entrypoints/background.ts @@ -1,30 +1,3 @@ export default defineBackground(() => { console.log('Hello background!', { id: browser.runtime.id }); - - console.log('Background script loaded'); - - chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { - console.log('收到消息:', request); - - if (request.action === 'copy') { - console.log('开始复制文本:', request.text); - - navigator.clipboard - .writeText(request.text) - .then(() => { - console.log('复制成功'); - sendResponse({ success: true }); - }) - .catch((err) => { - console.error('复制失败:', err); - sendResponse({ success: false, error: err.message }); - }); - - return true; // 保持消息端口开放以支持异步响应 - } - - // 对于未知的操作,也返回响应 - sendResponse({ success: false, error: '未知操作' }); - return false; - }); }); diff --git a/entrypoints/options/index.html b/entrypoints/options/index.html index 057e5d2..9ae608f 100644 --- a/entrypoints/options/index.html +++ b/entrypoints/options/index.html @@ -1,11 +1,11 @@ - - - + + + Document - - + + Hello Options Page - - \ No newline at end of file + + diff --git a/package-lock.json b/package-lock.json index 64da8e9..c824624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,9 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@wxt-dev/module-react": "^1.1.5", + "husky": "^9.1.7", + "lint-staged": "^16.2.7", + "prettier": "^3.8.1", "typescript": "^5.9.3", "wxt": "^0.20.6" } @@ -3634,6 +3637,22 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmmirror.com/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", @@ -4198,6 +4217,139 @@ } } }, + "node_modules/lint-staged": { + "version": "16.2.7", + "resolved": "https://registry.npmmirror.com/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.2", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/lint-staged/node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/lint-staged/node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/listr2": { "version": "8.3.3", "resolved": "https://registry.npmmirror.com/listr2/-/listr2-8.3.3.tgz", @@ -5278,6 +5430,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pino": { "version": "9.7.0", "resolved": "https://registry.npmmirror.com/pino/-/pino-9.7.0.tgz", @@ -5358,6 +5523,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz", @@ -6251,6 +6432,16 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", @@ -7160,6 +7351,22 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index d06a8be..77f6b16 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,11 @@ "private": true, "version": "0.0.0", "type": "module", + "lint-staged": { + "*.{js,jsx,ts,tsx,json,css,scss,md}": [ + "prettier --write" + ] + }, "scripts": { "dev": "wxt", "dev:firefox": "wxt -b firefox", @@ -12,11 +17,10 @@ "zip": "wxt zip", "zip:firefox": "wxt zip -b firefox", "compile": "tsc --noEmit", - "postinstall": "wxt prepare" + "postinstall": "wxt prepare", + "prepare": "husky" }, "dependencies": { - "react": "^19.2.3", - "react-dom": "^19.2.3", "@rrweb/all": "^2.0.0-alpha.18", "@rrweb/rrweb-plugin-console-record": "^2.0.0-alpha.18", "@rrweb/rrweb-plugin-console-replay": "^2.0.0-alpha.18", @@ -27,6 +31,8 @@ "date-fns": "^4.1.0", "dexie": "^4.2.1", "dexie-react-hooks": "^4.2.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", "react-markdown": "^6.0.3", "react-router-dom": "^6.30.2", "remark-gfm": "^1.0.0", @@ -39,6 +45,9 @@ "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", "@wxt-dev/module-react": "^1.1.5", + "husky": "^9.1.7", + "lint-staged": "^16.2.7", + "prettier": "^3.8.1", "typescript": "^5.9.3", "wxt": "^0.20.6" } diff --git a/pages/RecordeReplayPage.tsx b/pages/RecordeReplayPage.tsx index 96e8d27..7985fa4 100644 --- a/pages/RecordeReplayPage.tsx +++ b/pages/RecordeReplayPage.tsx @@ -1,14 +1,14 @@ import React from 'react'; // import {sendRecordCommand} from '../services/bridge' // import {storage} from "../services/storage"; -import {useRecorder} from "../hooks/useRecorder"; -import {downloadHtml} from '../utils/recordUtils'; +import { useRecorder } from '../hooks/useRecorder'; +import { downloadHtml } from '../utils/recordUtils'; const RecordeReplayPage = () => { // const [isRecording, setIsRecording] = useState(false); - - const {startRecord, stopRecord, getEvents, isRecording} = useRecorder(); - + + const { startRecord, stopRecord, getEvents, isRecording } = useRecorder(); + const handleStart = async () => { try { // await sendRecordCommand("START_RECORD"); @@ -20,10 +20,10 @@ const RecordeReplayPage = () => { // await storage.set({isRecording: true, recordingStartTime: Date.now()}); // console.log('events', events); } catch (e) { - console.error("Error starting recording:", e); + console.error('Error starting recording:', e); } - } - + }; + const handleStop = async () => { try { // await sendRecordCommand("STOP_RECORD"); @@ -35,24 +35,25 @@ const RecordeReplayPage = () => { downloadHtml(getEvents()); // console.log('events', getEvents()); } catch (e) { - console.error("Error stopping recording:", e) + console.error('Error stopping recording:', e); } }; - + return ( -
+

RRWeb Recorder

- + {/* 3. 选择标签页逻辑:默认为当前页,如果需要跨页,需先列出 chrome.tabs.query */} - -
+ +
{!isRecording ? ( - ) : ( - + )}
diff --git a/pages/TimestampPage.tsx b/pages/TimestampPage.tsx index 97eef1e..f53057b 100644 --- a/pages/TimestampPage.tsx +++ b/pages/TimestampPage.tsx @@ -1,15 +1,15 @@ -import {TimestampToDatetime} from "../components/TimestampToDatetime"; -import {DatetimeToTimestamp} from "../components/DatetimeToTimestamp"; -import {TimestampExecution} from "../components/TimestampExecution"; - +import { TimestampToDatetime } from '../components/TimestampToDatetime'; +import { DatetimeToTimestamp } from '../components/DatetimeToTimestamp'; +import { TimestampExecution } from '../components/TimestampExecution'; const TimestampPage = () => { - - return (
- - - -
); + return ( +
+ + + +
+ ); }; export default TimestampPage; diff --git a/tsconfig.json b/tsconfig.json index 9b364da..edad694 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,44 @@ { "extends": "./.wxt/tsconfig.json", "compilerOptions": { + /* --- 原有配置保持 --- */ "allowImportingTsExtensions": true, - "jsx": "react-jsx" - } + "jsx": "react-jsx", + + /* --- 1. 严格类型检查 (关键) --- */ + // 开启所有严格检查,包括 noImplicitAny。 + // 这能帮你捕获 "timer" 隐式 any 等错误,强制你写出更高质量的代码。 + "strict": true, + + /* --- 2. 代码质量检查 --- */ + // 声明了但没使用的变量报错(防止代码冗余) + "noUnusedLocals": true, + // 函数参数没使用报错 + "noUnusedParameters": true, + // switch 语句没有 break 时报错 + "noFallthroughCasesInSwitch": true, + + /* --- 3. 路径与环境 --- */ + // 设置基础目录,方便解析相对路径 + "baseUrl": ".", + // 确保包含 DOM 类型(解决 setTimeout、document 等报错) + "lib": ["DOM", "DOM.Iterable", "ESNext"], + // 编译目标设置为最新,WXT 底层 Vite 会处理降级兼容 + "target": "ESNext", + + /* --- 4. 路径别名 (可选) --- */ + // 如果你的 @/utils/... 爆红,可以手动添加这个映射。 + // WXT 通常会自动处理,但在这里显式声明有助于 VS Code 智能提示。 + "paths": { + "@/*": ["./entrypoints/*", "./components/*", "./utils/*", "./assets/*"] + } + }, + // 确保包含你的源代码目录 + "include": [ + "entrypoints/**/*", + "components/**/*", + "utils/**/*", + "assets/**/*", + ".wxt/types/**/*.ts" + ] }