feat: update package.json to remove lint-staged, add eslint configuration and dependencies
feat: add state management for recording in RecordeReplayPage component refactor: remove bridge and storage services as they are no longer needed chore: update tsconfig.json for improved type checking and module resolution fix: update downloadHtml function to use eventWithTime type and improve formatting fix: add error logging in formatWithZone function for better debugging
This commit is contained in:
+9
-1
@@ -8,5 +8,13 @@
|
|||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"bracketSpacing": true,
|
"bracketSpacing": true,
|
||||||
"arrowParens": "always",
|
"arrowParens": "always",
|
||||||
"endOfLine": "lf"
|
"endOfLine": "lf",
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.json",
|
||||||
|
"options": {
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ const CopyButton = ({
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
const performCopy = async () => {
|
const performCopy = useCallback(async () => {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
console.warn('没有提供要复制的文本');
|
console.warn('没有提供要复制的文本');
|
||||||
return false;
|
return false;
|
||||||
@@ -38,7 +38,9 @@ const CopyButton = ({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
return false;
|
||||||
|
}, [text]);
|
||||||
|
|
||||||
const handleClick = useCallback(async () => {
|
const handleClick = useCallback(async () => {
|
||||||
setStatus('copying');
|
setStatus('copying');
|
||||||
@@ -50,7 +52,7 @@ const CopyButton = ({
|
|||||||
console.error('复制时出错:', error);
|
console.error('复制时出错:', error);
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
}
|
}
|
||||||
}, [text]);
|
}, [performCopy]);
|
||||||
|
|
||||||
// 根据状态计算当前显示的文本
|
// 根据状态计算当前显示的文本
|
||||||
const currentText =
|
const currentText =
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export function DatetimeToTimestamp() {
|
|||||||
|
|
||||||
setResult(finalResult.toString());
|
setResult(finalResult.toString());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('转换错误:', err);
|
||||||
setError('转换失败,请检查输入格式');
|
setError('转换失败,请检查输入格式');
|
||||||
setResult('');
|
setResult('');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const TIMESTAMP_UNITS = [
|
|||||||
|
|
||||||
export function TimestampToDatetime() {
|
export function TimestampToDatetime() {
|
||||||
/** @type {[string, function]} 输入的时间戳值 */
|
/** @type {[string, function]} 输入的时间戳值 */
|
||||||
const [timestampValue, setTimestampValue] = useState(Date.now());
|
const [timestampValue, setTimestampValue] = useState(() => Date.now());
|
||||||
|
|
||||||
/** @type {[string, function]} 转换结果 */
|
/** @type {[string, function]} 转换结果 */
|
||||||
const [timestampResult, setTimestampResult] = useState('');
|
const [timestampResult, setTimestampResult] = useState('');
|
||||||
@@ -87,6 +87,7 @@ export function TimestampToDatetime() {
|
|||||||
const result = formatWithZone(numericValue, selectedZone, unit);
|
const result = formatWithZone(numericValue, selectedZone, unit);
|
||||||
setTimestampResult(result);
|
setTimestampResult(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('转换时间戳出错:', err);
|
||||||
setError('转换失败,请检查输入格式');
|
setError('转换失败,请检查输入格式');
|
||||||
setTimestampResult('');
|
setTimestampResult('');
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-14
@@ -1,3 +1,6 @@
|
|||||||
|
import '../.wxt/types/imports.d.ts';
|
||||||
|
import { browser } from 'wxt/browser';
|
||||||
|
|
||||||
export default defineBackground(() => {
|
export default defineBackground(() => {
|
||||||
console.log('Hello background!', { id: browser.runtime.id });
|
console.log('Hello background!', { id: browser.runtime.id });
|
||||||
|
|
||||||
@@ -48,18 +51,18 @@ export default defineBackground(() => {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function sendToActiveTab(message: {
|
// async function _sendToActiveTab(message: {
|
||||||
type: string;
|
// type: string;
|
||||||
data?: any;
|
// data?: unknown;
|
||||||
activeTabId?: number;
|
// activeTabId?: number;
|
||||||
[key: string]: any;
|
// [key: string]: unknown;
|
||||||
}) {
|
// }) {
|
||||||
let activeTabs = await browser.tabs.query({
|
// const activeTabs = await browser.tabs.query({
|
||||||
active: true,
|
// active: true,
|
||||||
currentWindow: true,
|
// currentWindow: true,
|
||||||
});
|
// });
|
||||||
let activeTab = activeTabs[0];
|
// const activeTab = activeTabs[0];
|
||||||
const sendTo = message.activeTabId || activeTab.id;
|
// const sendTo = message.activeTabId || activeTab.id;
|
||||||
await browser.tabs.sendMessage(sendTo!, message);
|
// await browser.tabs.sendMessage(sendTo!, message);
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// import { useRecorder } from '../hooks/useRecorder';
|
import '../.wxt/types/imports.d.ts';
|
||||||
|
import { browser } from 'wxt/browser';
|
||||||
|
|
||||||
export default defineContentScript({
|
export default defineContentScript({
|
||||||
// matches: ['*://*.google.com/*'],
|
// matches: ['*://*.google.com/*'],
|
||||||
@@ -7,7 +8,7 @@ export default defineContentScript({
|
|||||||
main() {
|
main() {
|
||||||
console.log('Content script loaded successfully', { id: browser.runtime.id });
|
console.log('Content script loaded successfully', { id: browser.runtime.id });
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
||||||
if (message.type === 'START_RECORD') {
|
if (message.type === 'START_RECORD') {
|
||||||
// startRecord();
|
// startRecord();
|
||||||
console.log('Start recording command received in content script');
|
console.log('Start recording command received in content script');
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import * as reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import * as reactPlugin from 'eslint-plugin-react';
|
||||||
|
import globals from 'globals';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist', '.wxt', 'node_modules', 'eslint.config.ts'] },
|
||||||
|
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
'hooks/**/*.{ts,tsx}',
|
||||||
|
'entrypoints/**/*.{ts,tsx}',
|
||||||
|
'pages/**/*.{ts,tsx}',
|
||||||
|
'utils/**/*.{ts,tsx}',
|
||||||
|
'components/**/*.{ts,tsx}',
|
||||||
|
'services/**/*.{ts,tsx}',
|
||||||
|
],
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
react: reactPlugin as any, // 现在这里就算写 TS 语法也没事了,因为文件被忽略了
|
||||||
|
'react-hooks': reactHooks as any,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||||
|
],
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useRef, useState, useCallback } from 'react';
|
import { useRef, useState, useCallback } from 'react';
|
||||||
import { record } from 'rrweb';
|
|
||||||
|
|
||||||
export const useRecorder = () => {
|
export const useRecorder = () => {
|
||||||
// 存储停止录制函数
|
// 存储停止录制函数
|
||||||
@@ -14,7 +13,10 @@ export const useRecorder = () => {
|
|||||||
setIsRecording(true);
|
setIsRecording(true);
|
||||||
|
|
||||||
console.log('[content] rrweb started');
|
console.log('[content] rrweb started');
|
||||||
} catch (error) {}
|
} catch (error) {
|
||||||
|
console.error('Failed to start recording:', error);
|
||||||
|
setIsRecording(false);
|
||||||
|
}
|
||||||
}, [isRecording]);
|
}, [isRecording]);
|
||||||
|
|
||||||
const stopRecord = () => {
|
const stopRecord = () => {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
// 对于代码文件:
|
||||||
|
'*.{ts,tsx,js,jsx}': [
|
||||||
|
// 1. ESLint: 依然检查具体文件,拦截未使用变量
|
||||||
|
'eslint --fix --max-warnings=0 --no-warn-ignored',
|
||||||
|
|
||||||
|
// 2. TypeScript: 使用函数形式
|
||||||
|
// 关键!这就告诉 lint-staged:“不要把文件名传给 tsc,直接运行这个命令就好”
|
||||||
|
// 这样 tsc 就会去读取 tsconfig.json,并正确排除 eslint.config.ts
|
||||||
|
() => 'tsc --noEmit --skipLibCheck',
|
||||||
|
],
|
||||||
|
|
||||||
|
// 对于其他文件:
|
||||||
|
'*.{json,css,scss,md}': ['prettier --write'],
|
||||||
|
};
|
||||||
Generated
+3303
File diff suppressed because it is too large
Load Diff
+10
-6
@@ -4,11 +4,6 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"lint-staged": {
|
|
||||||
"*.{js,jsx,ts,tsx,json,css,scss,md}": [
|
|
||||||
"prettier --write"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "wxt",
|
"dev": "wxt",
|
||||||
"dev:firefox": "wxt -b firefox",
|
"dev:firefox": "wxt -b firefox",
|
||||||
@@ -18,7 +13,8 @@
|
|||||||
"zip:firefox": "wxt zip -b firefox",
|
"zip:firefox": "wxt zip -b firefox",
|
||||||
"compile": "tsc --noEmit",
|
"compile": "tsc --noEmit",
|
||||||
"postinstall": "wxt prepare",
|
"postinstall": "wxt prepare",
|
||||||
"prepare": "husky"
|
"prepare": "husky",
|
||||||
|
"lint": "eslint . --max-warnings=0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rrweb/all": "^2.0.0-alpha.18",
|
"@rrweb/all": "^2.0.0-alpha.18",
|
||||||
@@ -44,11 +40,19 @@
|
|||||||
"@types/chrome": "^0.1.36",
|
"@types/chrome": "^0.1.36",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@types/webextension-polyfill": "^0.12.4",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||||
|
"@typescript-eslint/parser": "^8.54.0",
|
||||||
"@wxt-dev/module-react": "^1.1.5",
|
"@wxt-dev/module-react": "^1.1.5",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
|
"globals": "^17.2.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
"typescript-eslint": "^8.54.0",
|
||||||
"wxt": "^0.20.6"
|
"wxt": "^0.20.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
const RecordeReplayPage = () => {
|
const RecordeReplayPage = () => {
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
// src/services/bridge.ts
|
|
||||||
import {record} from 'rrweb';
|
|
||||||
import {isExtension} from '../utils/env';
|
|
||||||
|
|
||||||
let stopFn = null;
|
|
||||||
let events = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送指令给录制器(Content Script 或 当前页面逻辑)
|
|
||||||
* @param action 'START_RECORD' | 'STOP_RECORD'
|
|
||||||
* @returns {Promise<unknown>}
|
|
||||||
*/
|
|
||||||
export const sendRecordCommand = async (action) => {
|
|
||||||
if (isExtension()) {
|
|
||||||
// === 插件环境 ===
|
|
||||||
try {
|
|
||||||
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
|
|
||||||
if (tab?.id) {
|
|
||||||
// 使用 Promise 包装 sendMessage 以便统一 async/await 风格
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
chrome.tabs.sendMessage(tab.id, {action}, (response) => {
|
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
reject(chrome.runtime.lastError);
|
|
||||||
} else {
|
|
||||||
resolve(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Extension communication error:", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// === 网页环境 (Web Demo / Localhost 调试) ===
|
|
||||||
console.log(`[Web Mode] Simulation: Sending ${action}`);
|
|
||||||
|
|
||||||
if (action === 'START_RECORD') {
|
|
||||||
stopFn = record({
|
|
||||||
emit(event) {
|
|
||||||
if (events.length > 1000) {
|
|
||||||
console.log('events length > 1000');
|
|
||||||
stopFn();
|
|
||||||
}
|
|
||||||
events.push(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (action === 'STOP_RECORD') {
|
|
||||||
if (stopFn != null) {
|
|
||||||
stopFn();
|
|
||||||
}
|
|
||||||
console.log('[Web Mode] Simulation: Stopping recorder');
|
|
||||||
console.log('[Web Mode] Simulation: Events:', events);
|
|
||||||
console.log('[Web Mode] Simulation: Events:', events.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// src/services/storage.ts
|
|
||||||
import {isExtension} from '../utils/env';
|
|
||||||
|
|
||||||
export const storage = {
|
|
||||||
set: async (items) => {
|
|
||||||
if (isExtension()) {
|
|
||||||
return chrome.storage.local.set(items);
|
|
||||||
} else {
|
|
||||||
// Web 兼容:写入 localStorage
|
|
||||||
Object.keys(items).forEach(key => {
|
|
||||||
localStorage.setItem(key, JSON.stringify(items[key]));
|
|
||||||
});
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
get: async (keys) => {
|
|
||||||
if (isExtension()) {
|
|
||||||
return chrome.storage.local.get(keys);
|
|
||||||
} else {
|
|
||||||
// Web 兼容:读取 localStorage
|
|
||||||
const result = {};
|
|
||||||
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
||||||
keyList.forEach(key => {
|
|
||||||
const val = localStorage.getItem(key);
|
|
||||||
if (val) result[key] = JSON.parse(val);
|
|
||||||
});
|
|
||||||
return Promise.resolve(result);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
remove: async (key) => {
|
|
||||||
if (isExtension()) {
|
|
||||||
return chrome.storage.local.remove(key);
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem(key);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+13
-3
@@ -4,6 +4,9 @@
|
|||||||
/* --- 原有配置保持 --- */
|
/* --- 原有配置保持 --- */
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "ESNext", // 支持 import.meta
|
||||||
|
"moduleResolution": "Bundler", // 或者用 "Node"
|
||||||
|
|
||||||
/* --- 1. 严格类型检查 (关键) --- */
|
/* --- 1. 严格类型检查 (关键) --- */
|
||||||
// 开启所有严格检查,包括 noImplicitAny。
|
// 开启所有严格检查,包括 noImplicitAny。
|
||||||
@@ -15,6 +18,8 @@
|
|||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
// 函数参数没使用报错
|
// 函数参数没使用报错
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
|
// 函数必须有返回值,防止遗漏 return
|
||||||
|
"noImplicitReturns": true,
|
||||||
// switch 语句没有 break 时报错
|
// switch 语句没有 break 时报错
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
@@ -31,7 +36,9 @@
|
|||||||
// WXT 通常会自动处理,但在这里显式声明有助于 VS Code 智能提示。
|
// WXT 通常会自动处理,但在这里显式声明有助于 VS Code 智能提示。
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./entrypoints/*", "./components/*", "./utils/*", "./assets/*"]
|
"@/*": ["./entrypoints/*", "./components/*", "./utils/*", "./assets/*"]
|
||||||
}
|
},
|
||||||
|
"types": ["chrome", "webextension-polyfill"],
|
||||||
|
"noImplicitAny": false
|
||||||
},
|
},
|
||||||
// 确保包含你的源代码目录
|
// 确保包含你的源代码目录
|
||||||
"include": [
|
"include": [
|
||||||
@@ -39,6 +46,9 @@
|
|||||||
"components/**/*",
|
"components/**/*",
|
||||||
"utils/**/*",
|
"utils/**/*",
|
||||||
"assets/**/*",
|
"assets/**/*",
|
||||||
".wxt/types/**/*.ts"
|
"hooks/**/*",
|
||||||
]
|
".wxt/types/**/*.ts",
|
||||||
|
".wxt/types/*.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": ["node_modules", ".wxt", "eslint.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// import rrwebPlayer from 'rrweb-player';
|
import { eventWithTime } from 'rrweb';
|
||||||
|
|
||||||
export const downloadHtml = (events) => {
|
export const downloadHtml = (events: eventWithTime[]) => {
|
||||||
if (events.length === 0) return;
|
if (events.length === 0) return;
|
||||||
|
|
||||||
// 1. 构建 HTML 模板字符串
|
// 1. 构建 HTML 模板字符串
|
||||||
@@ -41,7 +41,7 @@ export const downloadHtml = (events) => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// 2. 创建 Blob 并下载
|
// 2. 创建 Blob 并下载
|
||||||
const blob = new Blob([htmlContent], {type: 'text/html'});
|
const blob = new Blob([htmlContent], { type: 'text/html' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const formatWithZone = (
|
|||||||
timeZone: zone,
|
timeZone: zone,
|
||||||
}).format(ms);
|
}).format(ms);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error('formatWithZone error:', e);
|
||||||
return '格式错误';
|
return '格式错误';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user