├── src ├── core │ ├── hook.ts │ ├── proxy.ts │ ├── detector.ts │ ├── view │ │ └── panel │ │ │ ├── panel.css │ │ │ ├── ViewPanel │ │ │ ├── lib │ │ │ │ ├── recordScreen.ts │ │ │ │ ├── shutdown.ts │ │ │ │ ├── showFdAndId.ts │ │ │ │ ├── useConsole.ts │ │ │ │ ├── watchSheetPageReload.ts │ │ │ │ ├── showRecordsList.ts │ │ │ │ ├── emptyDom.ts │ │ │ │ ├── handleSheetSearchTree.ts │ │ │ │ └── showTips.ts │ │ │ ├── component │ │ │ │ ├── ReloadTipModel.tsx │ │ │ │ ├── TableSearchTree.tsx │ │ │ │ └── SheetSearchTree.tsx │ │ │ └── index.tsx │ │ │ ├── ModelPanel │ │ │ ├── lib │ │ │ │ ├── showNetworkSpeed.ts │ │ │ │ └── showFpsAndMemory.ts │ │ │ └── index.tsx │ │ │ ├── PastePanel │ │ │ ├── component │ │ │ │ └── PasteList.tsx │ │ │ └── index.tsx │ │ │ ├── DebugPanel │ │ │ └── index.tsx │ │ │ └── panel.tsx │ ├── backend.ts │ ├── popup.ts │ ├── inject.ts │ ├── store │ │ └── index.ts │ ├── content.ts │ ├── devtool.ts │ ├── panel.ts │ └── background.ts ├── public │ ├── page │ │ ├── index.html │ │ ├── devtool.html │ │ ├── panel.html │ │ ├── background.html │ │ └── popup.html │ └── logo │ │ ├── logo.jpeg │ │ └── logo-128.png ├── screenshot │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ └── 6.png ├── __tests__ │ ├── sum.ts │ ├── menu.html │ ├── paste.html │ ├── alpha.html │ ├── spaceDom.html │ ├── record.html │ ├── expend.html │ ├── expend2.html │ └── MediaStreamRecorder.js └── manifest.json ├── .gitignore ├── dist.crx ├── .gitattributes ├── .eslintignore ├── .travis.yml ├── jest.config.js ├── webpack ├── webpack.prod.js ├── webpack.dev.js └── webpack.common.js ├── tsconfig.json ├── .eslintrc.js ├── README.md ├── package.json ├── .prettierrc.js └── dist.pem /src/core/hook.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/proxy.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/detector.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/view/panel/panel.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/public/page/index.html: -------------------------------------------------------------------------------- 1 | 24asd123 -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/recordScreen.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/core/backend.ts: -------------------------------------------------------------------------------- 1 | window.postMessage({"test": '你好!'}, '*'); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | dist 4 | tmp 5 | .vscode -------------------------------------------------------------------------------- /dist.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/dist.crx -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .steamer 3 | config 4 | dev 5 | node_modules 6 | package.json 7 | -------------------------------------------------------------------------------- /src/screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/screenshot/1.png -------------------------------------------------------------------------------- /src/screenshot/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/screenshot/2.png -------------------------------------------------------------------------------- /src/screenshot/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/screenshot/3.png -------------------------------------------------------------------------------- /src/screenshot/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/screenshot/4.png -------------------------------------------------------------------------------- /src/screenshot/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/screenshot/5.png -------------------------------------------------------------------------------- /src/screenshot/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/screenshot/6.png -------------------------------------------------------------------------------- /src/public/logo/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/public/logo/logo.jpeg -------------------------------------------------------------------------------- /src/public/logo/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/chrome-devtools-cli/main/src/public/logo/logo-128.png -------------------------------------------------------------------------------- /src/public/page/devtool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | cache: 5 | directories: 6 | - "node_modules" 7 | script: 8 | - npm build 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "roots": [ 3 | "src" 4 | ], 5 | "transform": { 6 | "^.+\\.ts$": "ts-jest" 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /src/__tests__/sum.ts: -------------------------------------------------------------------------------- 1 | test('1 + 1 = 2', () => { 2 | expect(2).toBe(2); 3 | }); 4 | 5 | test('1 + 2 != 2', () => { 6 | expect(2).not.toBe(2); 7 | }); 8 | -------------------------------------------------------------------------------- /src/core/popup.ts: -------------------------------------------------------------------------------- 1 | (() => { 2 | const status = document.querySelector('.status'); 3 | // status.innerHTML = window.location.host === 'docs.qq.com' ? '✅' : '❌'; 4 | })(); 5 | -------------------------------------------------------------------------------- /webpack/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'production' 6 | }); -------------------------------------------------------------------------------- /src/__tests__/menu.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | devtool: 'inline-source-map', 6 | mode: 'development' 7 | }); -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/shutdown.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | window.postMessage( 3 | { 4 | source: 'tencent-docs-devtools-devtool-script', 5 | payload: 'shutdown' 6 | }, 7 | '*' 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/showFdAndId.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | window.postMessage( 3 | { 4 | source: 'tencent-docs-devtools-inject', 5 | payload: { 6 | cookie: window.document.cookie 7 | } 8 | }, 9 | '*' 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/core/inject.ts: -------------------------------------------------------------------------------- 1 | declare interface Window { 2 | T_SHEET: any; 3 | animationFrameID: any; 4 | // getrangeSelections: any; 5 | SpreadsheetApp: any; 6 | app: any; 7 | stream: any; 8 | // 录屏 9 | recordScreen: any; 10 | __console: any; 11 | // 粘贴板定时器 12 | pasteRecord: any; 13 | networkSpeedTimer: any; 14 | } 15 | -------------------------------------------------------------------------------- /src/public/page/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Panel 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/useConsole.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | // 设置 cookie 的方法 3 | const setCookie = (name, value) => { 4 | let Days = 30; 5 | let exp = new Date(); 6 | exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000); 7 | // @ts-ignore 8 | document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString(); 9 | debugger; 10 | } 11 | 12 | setCookie('useConsole', true); 13 | // @ts-ignore 14 | console = __console; 15 | }; 16 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/watchSheetPageReload.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | window.onbeforeunload = function(event) { 3 | window.postMessage( 4 | { 5 | source: 'tencent-docs-devtools-inject', 6 | payload: 'sheet-reload' 7 | }, 8 | '*' 9 | ); 10 | event = event || window.event; 11 | if (event) { 12 | event.returnValue = '确定要关闭窗口吗?'; 13 | } 14 | return '确定要关闭窗口吗?'; 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "rootDir": "src", 8 | "outDir": "dist/core", 9 | "noEmitOnError": true, 10 | "typeRoots": [ 11 | "node_modules/@types" 12 | ], 13 | // 给错误和消息设置样式,使用颜色和上下文 14 | "pretty": true, 15 | // 兼容 babel 模块导入的模式(不需要 * as) 16 | "esModuleInterop": true, 17 | // 支持引入 json 模块 18 | "resolveJsonModule": true, 19 | // React 项目需要配置此项 20 | "jsx": "react", 21 | } 22 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'alloy', 4 | 'alloy/typescript', 5 | ], 6 | env: { 7 | // 您的环境变量(包含多个预定义的全局变量) 8 | // Your environments (which contains several predefined global variables) 9 | // 10 | // browser: true, 11 | // node: true, 12 | // mocha: true, 13 | // jest: true, 14 | // jquery: true 15 | }, 16 | globals: { 17 | // 您的全局变量(设置为 false 表示它不允许被重新赋值) 18 | // Your global variables (setting to false means it's not allowed to be reassigned) 19 | // 20 | // myGlobal: false 21 | }, 22 | rules: { 23 | // 自定义您的规则 24 | // Customize your rules 25 | } 26 | }; -------------------------------------------------------------------------------- /src/core/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | const counter = ( 3 | state = { 4 | rangeSelections: [], 5 | skill: ['ps', 'ts'] 6 | }, 7 | action 8 | ) => { 9 | switch (action.type) { 10 | case 'SET_RANGE_SELECTIONS': 11 | return { 12 | ...state, 13 | rangeSelections: action.rangeSelections 14 | }; 15 | case 'SET_FD_AND_ROUTE_ID': 16 | return { 17 | ...state, 18 | fd: action.fd, 19 | routeId: action.routeId 20 | }; 21 | default: 22 | return state; 23 | } 24 | }; 25 | let store = createStore(counter); 26 | export default store; 27 | -------------------------------------------------------------------------------- /src/core/content.ts: -------------------------------------------------------------------------------- 1 | const handshake = (e) => { 2 | // 断开 inject.ts 和 content.ts 的通信 3 | if (e.data.payload === 'shutdown') { 4 | window.removeEventListener('message', handshake); 5 | } 6 | try { 7 | chrome.runtime.sendMessage(e.data, function(response) { 8 | console.log('收到来自后台的回复:' + response); 9 | }); 10 | } catch (error) { 11 | console.log(error); 12 | } 13 | }; 14 | 15 | // inject.ts 和 content.ts 的通信 16 | window.addEventListener('message', handshake); 17 | 18 | const sendListening = () => { 19 | window.postMessage( 20 | { 21 | source: 'tencent-docs-devtools-content-script', 22 | payload: 'listening' 23 | }, 24 | '*' 25 | ); 26 | }; 27 | sendListening(); 28 | -------------------------------------------------------------------------------- /src/core/view/panel/ModelPanel/lib/showNetworkSpeed.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | window.networkSpeedTimer = setInterval(() => { 3 | let startTime = Date.now(); 4 | let xhr = new XMLHttpRequest(); 5 | xhr.open( 6 | 'GET', 7 | 'https://docs.qq.com/static/dev/sheet_static/img/brandlogo/logo-sprites-new.png' 8 | ); 9 | xhr.onload = function () { 10 | let duration = (Date.now() - startTime) / 1000; 11 | // @ts-ignore 12 | let size = xhr.getResponseHeader('Content-Length') / 1024; 13 | let speed = (size / duration).toFixed(2); 14 | window.postMessage({ networkSpeed: speed }, '*'); 15 | console.log(speed); 16 | }; 17 | xhr.send(); 18 | }, 1000); 19 | } -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/showRecordsList.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export default () => { 3 | window.__console.log('-----start ajax-----'); 4 | $.ajax({ 5 | url: '/dop-api/get/history', 6 | data: { 7 | padId: window.clientVars.globalPadId, 8 | page: 1, 9 | _r: 10000 * Math.random() 10 | }, 11 | success(data) { 12 | window.__console.log(data.history.items); 13 | window.postMessage( 14 | { 15 | source: 'tencent-docs-devtools-inject', 16 | payload: { 17 | history: data 18 | } 19 | }, 20 | '*' 21 | ); 22 | window.__console.log('-----end ajax-----'); 23 | } 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /src/public/page/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |

背景页

11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/component/ReloadTipModel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'antd'; 3 | export default class ReloadTipModel extends React.Component { 4 | state = { 5 | visible: true 6 | }; 7 | 8 | handleOk = (e) => { 9 | this.setState({ 10 | visible: false 11 | }); 12 | window.location.reload(); 13 | }; 14 | 15 | handleCancel = (e) => { 16 | this.setState({ 17 | visible: false 18 | }); 19 | }; 20 | 21 | render() { 22 | return ( 23 | 31 |

如果刷新了表格页面,请重新刷新当前调试面板更新数据。

32 |
33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 开发 2 | 3 | ```sh 4 | # 下载项目 5 | git clone https://git.code.oa.com/tencent-doc/tencent-docs-devtools 6 | # 进入项目目录 7 | cd tencent-docs-devtools 8 | # 安装依赖 9 | npm install 10 | # 调试 11 | npm run watch 12 | # 编译打包 13 | npm run build 14 | ``` 15 | 16 | # 安装 17 | 18 | 将下载好的 tencent-docs-extension.zip 压缩文件解压,得到一个文件夹,里面内容如下。 19 | 20 | - tencent-docs-extension文件夹目录 21 | - dist 22 | - manifest.json 23 | - src 24 | - README.md 25 | 26 | 27 | 28 | 然后打开 Chrome 浏览器,点击右上角三个点,点击`更多工具`,选择`扩展程序`进入插件管理界面。 29 | 30 | 31 | 32 | 打开右上角的`开发者模式`,会在左边出现点击`加载已解压的扩展程序`,选取解压后的文件夹,并点击`选择`。 33 | 34 | 35 | 36 | 如果出现面板出现`Tencent Docs Dev Tools`则证明安装成功。 37 | 38 | 39 | 40 | 然后打开你的需要调试的[文档](https://docs.qq.com/desktop)进行测试。 41 | 42 | 现在的界面会比较粗糙,比如当你打开文档后会在右下角出现一个红色背景的弹窗,里面会有错误弹窗的测试按钮,点击可运行测试,后续会变更到调试控制台里面。 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/emptyDom.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | // 查找空节点 3 | function traverseNodes(node) { 4 | // 判断是否是元素节点 5 | if (node.nodeType === 1) { 6 | // 判断该元素节点是否有子节点 7 | if (node.childNodes.length) { 8 | // 得到所有的子节点 9 | let sonnodes = node.childNodes; 10 | // 遍历所有的子节点 11 | for (let i = 0; i < sonnodes.length; i++) { 12 | // 找出有空格字符的节点

13 | if (!node.innerText && !node.childNodes.length) { 14 | window.__console.log(node); 15 | } 16 | // 得到具体的某个子节点 17 | let sonnode = sonnodes.item(i); 18 | // 递归遍历 19 | traverseNodes(sonnode); 20 | } 21 | } else { 22 | window.__console.log(node); 23 | } 24 | } 25 | } 26 | traverseNodes(document.body); 27 | }; 28 | -------------------------------------------------------------------------------- /src/public/page/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 34 | 35 | 36 | 37 |

This extension is using in tencent documents applications. 38 | 39 |

40 |

Open the developer tools, and the Tencent Docs Dev Tools tab will appear to the right.😁

41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/__tests__/paste.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/core/view/panel/ModelPanel/lib/showFpsAndMemory.ts: -------------------------------------------------------------------------------- 1 | const showFpsAndMemory = () => { 2 | (() => { 3 | let frame = 0; 4 | let lastTime = Date.now(); 5 | const getMemoryInfo = () => { 6 | // @ts-ignore 7 | if (!window.performance || !(window.performance).memory) { 8 | return 0; 9 | } 10 | // @ts-ignore 11 | return Math.round((window.performance).memory.usedJSHeapSize / (1024 * 1024)); 12 | }; 13 | const loop = () => { 14 | const now = Date.now(); 15 | // 每运行一次, frame++ 16 | frame += 1; 17 | // 1s更新一次面板 18 | if (now > 1000 + lastTime) { 19 | const fps = Math.round((frame * 1000) / (now - lastTime)); 20 | frame = 0; 21 | lastTime = now; 22 | window.postMessage({ 23 | source: 'tencent-docs-devtools-inject', 24 | payload: { 25 | memory: getMemoryInfo(), 26 | fps 27 | } 28 | }, '*'); 29 | } 30 | window.animationFrameID = window.requestAnimationFrame(loop); 31 | }; 32 | window.animationFrameID = window.requestAnimationFrame(loop); 33 | })(); 34 | } 35 | 36 | export default showFpsAndMemory; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tencent-docs-devtools", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "watch": "webpack --config webpack/webpack.dev.js --watch", 6 | "build": "webpack --config webpack/webpack.prod.js", 7 | "clean": "rimraf dist", 8 | "test": "npx jest" 9 | }, 10 | "author": "enoyao", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://git.code.oa.com/tencent-doc/tencent-docs-devtools" 15 | }, 16 | "devDependencies": { 17 | "@types/chrome": "0.0.91", 18 | "@types/jest": "24.0.23", 19 | "@types/jquery": "3.3.31", 20 | "@typescript-eslint/eslint-plugin": "2.14.0", 21 | "@typescript-eslint/parser": "2.14.0", 22 | "copy-webpack-plugin": "5.0.5", 23 | "css-loader": "3.4.0", 24 | "eslint": "6.8.0", 25 | "eslint-config-alloy": "3.5.0", 26 | "jest": "24.9.0", 27 | "prettier": "1.19.1", 28 | "rimraf": "3.0.0", 29 | "style-loader": "1.1.2", 30 | "ts-jest": "24.2.0", 31 | "ts-loader": "6.2.1", 32 | "typescript": "3.7.4", 33 | "webpack": "~4.41.2", 34 | "webpack-cli": "~3.3.10", 35 | "webpack-merge": "~4.2.2" 36 | }, 37 | "dependencies": { 38 | "antd": "3.26.5", 39 | "echarts": "4.6.0", 40 | "html2canvas": "1.0.0-rc.5", 41 | "react": "16.12.0", 42 | "react-dom": "16.12.0", 43 | "react-redux": "7.1.3", 44 | "redux": "4.0.5" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // .prettierrc.js 2 | module.exports = { 3 | // max 100 characters per line 4 | printWidth: 100, 5 | // use 4 spaces for indentation 6 | tabWidth: 4, 7 | // use spaces instead of indentations 8 | useTabs: false, 9 | // semicolon at the end of the line 10 | semi: true, 11 | // use single quotes 12 | singleQuote: true, 13 | // object's key is quoted only when necessary 14 | quoteProps: 'as-needed', 15 | // use double quotes instead of single quotes in jsx 16 | jsxSingleQuote: false, 17 | // no comma at the end 18 | trailingComma: 'none', 19 | // spaces are required at the beginning and end of the braces 20 | bracketSpacing: true, 21 | // end tag of jsx need to wrap 22 | jsxBracketSameLine: false, 23 | // brackets are required for arrow function parameter, even when there is only one parameter 24 | arrowParens: 'always', 25 | // format the entire contents of the file 26 | rangeStart: 0, 27 | rangeEnd: Infinity, 28 | // no need to write the beginning @prettier of the file 29 | requirePragma: false, 30 | // No need to automatically insert @prettier at the beginning of the file 31 | insertPragma: false, 32 | // use default break criteria 33 | proseWrap: 'preserve', 34 | // decide whether to break the html according to the display style 35 | htmlWhitespaceSensitivity: 'css', 36 | // lf for newline 37 | endOfLine: 'lf' 38 | }; -------------------------------------------------------------------------------- /src/__tests__/alpha.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tencent Docs Dev Tools", 3 | "manifest_version": 2, 4 | "version": "1.00", 5 | "description": "Chrome devtools extension for debugging tencent documents applications.", 6 | "icons": { 7 | "16": "./public/logo/logo-128.png", 8 | "48": "./public/logo/logo-128.png", 9 | "128": "./public/logo/logo-128.png" 10 | }, 11 | "content_scripts": [ 12 | { 13 | "matches": ["https://docs.qq.com/*"], 14 | "run_at": "document_end", 15 | "js": ["./core/content.js"] 16 | }, 17 | { 18 | "matches": [""], 19 | "js": ["./core/hook.js"], 20 | "run_at": "document_start" 21 | }, 22 | { 23 | "matches": [""], 24 | "js": ["./core/detector.js"], 25 | "run_at": "document_idle" 26 | } 27 | ], 28 | "devtools_page": "./public/page/devtool.html", 29 | "background": { 30 | "page": "./public/page/background.html", 31 | "persistent": false 32 | }, 33 | "permissions": [ 34 | "http://*/*", 35 | "https://*/*", 36 | "file:///*", 37 | "tabs", 38 | "contextMenus", 39 | "storage", 40 | "clipboardWrite", 41 | "clipboardRead", 42 | "" 43 | ], 44 | "web_accessible_resources": ["core/*", "public/*"], 45 | "browser_action": { 46 | "default_title": "", 47 | "default_icon": { 48 | "16": "./public/logo/logo-128.png", 49 | "48": "./public/logo/logo-128.png", 50 | "128": "./public/logo/logo-128.png" 51 | }, 52 | "default_popup": "./public/page/popup.html" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /dist.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6K88EMMd8ULYE 3 | lidMk3C+Z9KqKSPunEarbyXWf48FHpi7vfIowQqf8MJcRGsee74UF9wDx/u90ry3 4 | LFUcamfgjTQN5i73ITRw6sSd+5OTy62+UA3L3+BlldFdetKqVrp3JCf+WbfhO8qH 5 | jDX2dJVgVGg6jfnEoSnM44z4cPmhMhI91lB6pYiSvDWz8turmrmHZHIhl04Wco7S 6 | /qOfhkpsJ7inMNeLXezBeHA8lL7RFn+lFH81fL9OdcmzrSgxvhXsF+nyPZNNWUHX 7 | OJZSQLOm3Aa2KS/lX2oBxhUIRfoJ4hUAPDko5vUGDL1o96CreekPYEQ4EQt2i/Et 8 | wA7PQ5qRAgMBAAECggEAB/vZ+GGJJIUnkSHJVKjcBGZaa+6wukpcY3y0QDMfNIZ/ 9 | 4Uk/iczhgkNmamOKvndrCtyNFGJuotDXQ/m0OrA7fS9BBLmS2QlFAPSocuf3h7aL 10 | 4GX5eX1D18dpf5tFToXCoFpeFrPNRWz1fgOeys9sGoH0KFKvW+XXb5BwmrTpl6aF 11 | mzTdhv8YxrtyWm1s9PUxSSpYhcUv18+4Le+nbT6ysqITm1oZnXbbyazS0w02UDx4 12 | lGhgYSpTrzYb8CM628ZaoPB6qcQCX6+zbY7YrflHP5hosdUOyRFLgy8l1WNDDEFQ 13 | FA5e0KkjSPaQAIevXlt4XTzAr93f/zjaU7VNAQNv4wKBgQD/Cj9T3+IKENmSwX1m 14 | qU9OkDUy0qRDmgo0kac31JhL5TM8XV9BoHJ3lNO2oH3HWm3/u04O237PmOZei7bx 15 | fJGtHm3p/xWRb8gCm5EacEdJhbS75eQXH9q5bZe2srp2KwKNYc0lz9WOiSSCYqKI 16 | bE4s8aE4qP6I15ZnGIcIzQ0jawKBgQC63zNFTfdMPi/2KqJT9Hs3YLJlPE2SNKvB 17 | /Tk8+7IEyDGg6W4H0Oq0wAMXWYB1KxNciXRjjMy3r+5/8m7QMFlqR5L9siZmi1rE 18 | PfOzvESh1zjYejcarEeneNcmrUDIuxaytwg0XTd6yobz7EO00kqAhsJJH3CGQW0t 19 | fMbq7Vv08wKBgQCXq0Jp4NSN4+Nf8Q1gRgPR1tSsOrRRN4QmKxBklVtUlyNXHLgP 20 | URmBn9If42Wpbk/IK12KXGIXlvg49aGADvKbH2OCKp5q9Sze0CEEtgzO3mLS8y4n 21 | ylvKti/pCHT23TERXz4e5HA96bT6jSnXM4FyHOghx+5G9t6MxPtY8oiDrQKBgCp5 22 | cP3fqsX5nI0nQ54xg5AiummoOzlwH1oIZGooC15q4lzgsdG0+qP6wLkd7sSy9cOi 23 | uoGmDyHxKxSJ1gYb9zbg7dgcFf5YzlisfCfuMutWL+WEYeH+Jos6PCzeVEtLuu0E 24 | DSlZxrJyNhpbKUiAQ87S0nYsXzckx9v+8HeVW8FhAoGBAIpRi5hUEvHFNHIYRINp 25 | U6nbjsRowsJMkkxrtL24/3CZBEgugTOlUvBphTVArM3m0z44xTK65Ql6e5FU5YvD 26 | FwpoK6q2IoW1GqacP8MplzL7Lgl4IS5nvnlBiKFGipuwOCondZT/RM9WisXEyZys 27 | AdGvgVNB2jn5U5d1+ypU1r1r 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | const coreDir = '../src/core/'; 5 | const viewDir = '../src/core/view/'; 6 | module.exports = { 7 | entry: { 8 | // Chrome Extension 逻辑层 9 | hook: path.join(__dirname, coreDir + 'hook.ts'), 10 | devtool: path.join(__dirname, coreDir + 'devtool.ts'), 11 | background: path.join(__dirname, coreDir + 'background.ts'), 12 | panel: path.join(__dirname, coreDir + 'panel.ts'), 13 | backend: path.join(__dirname, coreDir + 'backend.ts'), 14 | proxy: path.join(__dirname, coreDir + 'proxy.ts'), 15 | detector: path.join(__dirname, coreDir + 'detector.ts'), 16 | content: path.join(__dirname, coreDir + 'content.ts'), 17 | inject: path.join(__dirname, coreDir + 'inject.ts'), 18 | popup: path.join(__dirname, coreDir + 'popup.ts'), 19 | // React 视图层 20 | panelView: path.join(__dirname, viewDir, 'panel/panel.tsx') 21 | }, 22 | output: { 23 | path: path.join(__dirname, '../dist/core'), 24 | filename: '[name].js' 25 | }, 26 | optimization: { 27 | splitChunks: { 28 | name: 'vendor', 29 | chunks: 'initial' 30 | } 31 | }, 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.tsx?$/, 36 | use: 'ts-loader', 37 | exclude: /node_modules/ 38 | }, 39 | { 40 | test: /\.css?$/, 41 | use: ['style-loader', 'css-loader'] 42 | } 43 | ] 44 | }, 45 | resolve: { 46 | extensions: ['.ts', '.tsx', '.js'] 47 | }, 48 | plugins: [ 49 | new webpack.IgnorePlugin(/screenshot$/), 50 | new CopyPlugin([ 51 | { from: '.', to: '../public', context: './src/public' }, 52 | { from: './src/manifest.json', to: '../manifest.json' } 53 | ]) 54 | ] 55 | }; 56 | -------------------------------------------------------------------------------- /src/__tests__/spaceDom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
值1值2值3
aaabbbccc
dddeeefff
ggghhhkkk
pppooo
35 | 36 |

37 | 38 | 64 | 65 | -------------------------------------------------------------------------------- /src/core/view/panel/PastePanel/component/PasteList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { List, Typography, Button, PageHeader, Tag } from 'antd'; 3 | 4 | interface State { 5 | pasteRecord: string[] 6 | } 7 | interface Props { 8 | isWatchClipboardContents: boolean 9 | } 10 | export default class SheetSearchTree extends React.Component { 11 | constructor(props) { 12 | super(props); 13 | } 14 | state: State = { 15 | pasteRecord: [] 16 | } 17 | clearPasteRecord() { 18 | localStorage.removeItem('paste-info'); 19 | } 20 | render() { 21 | return ( 22 | {this.props.isWatchClipboardContents ? 'Stop' : 'Running'}} 30 | />} 31 | footer={} 32 | bordered 33 | dataSource={this.state.pasteRecord} 34 | renderItem={item => ( 35 | item ? 36 | 37 | {item} 38 | 39 | : 40 | 41 | 空数据 42 | 43 | )} 44 | /> 45 | ); 46 | } 47 | componentDidMount() { 48 | // 设置粘贴板信息到列表数据里面 49 | this.setState({ 50 | pasteRecord: localStorage.getItem('paste-info') ? JSON.parse(localStorage.getItem('paste-info')) : [] 51 | }) 52 | // 监听Storage的改变,更新面板内容 53 | window.addEventListener("storage", (e) => { 54 | this.setState({ 55 | pasteRecord: JSON.parse(e.newValue) 56 | }) 57 | }); 58 | } 59 | } -------------------------------------------------------------------------------- /src/core/view/panel/PastePanel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button, Input, Statistic, Card, Row, Col } from 'antd'; 3 | // 通讯库 4 | import tool from '../../../panel'; 5 | // 粘贴板列表 6 | import PasteList from './component/PasteList'; 7 | interface State { 8 | isWatchClipboardContents: boolean; 9 | } 10 | 11 | interface Props {} 12 | 13 | export default class ModelPanel extends React.Component { 14 | state: State = { 15 | // 用于判断当前是否监听粘贴板状态状态 16 | isWatchClipboardContents: true 17 | }; 18 | componentDidMount() { 19 | // 首次加载启动监听粘贴板的功能 20 | this.startWatchClipboardContents(); 21 | } 22 | // 开始监听粘贴板 23 | startWatchClipboardContents() { 24 | this.setState({ 25 | isWatchClipboardContents: false 26 | }); 27 | tool.injectScriptStringToPage( 28 | `window.postMessage({payload:'startWatchClipboardContents'},'*')` 29 | ); 30 | } 31 | // 停止监听粘贴板 32 | stopWatchClipboardContents() { 33 | this.setState({ 34 | isWatchClipboardContents: true 35 | }); 36 | tool.injectScriptStringToPage( 37 | `window.postMessage({payload:'stopWatchClipboardContents'},'*')` 38 | ); 39 | } 40 | render() { 41 | return ( 42 |
43 | {this.state.isWatchClipboardContents ? ( 44 | 51 | ) : ( 52 | 59 | )} 60 | 61 |
62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/__tests__/record.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 17 | 18 | 19 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/__tests__/expend.html: -------------------------------------------------------------------------------- 1 | 64 | -------------------------------------------------------------------------------- /src/core/devtool.ts: -------------------------------------------------------------------------------- 1 | import tool from './panel'; 2 | // 面板创建开关 3 | let created = false; 4 | // 检查次数 5 | let checkCount = 0; 6 | 7 | const createPanelIfHasDocs = () => { 8 | if (created || checkCount++ > 10) { 9 | return; 10 | } 11 | // 根据全局变量 window.T_SHEET 判断是否是在腾讯文档页面 12 | chrome.devtools.inspectedWindow.eval('!!(window.T_SHEET)', (hasDocs) => { 13 | if (!hasDocs || created) { 14 | return; 15 | } 16 | clearInterval(checkDocsInterval); 17 | created = true; 18 | createPanels(); 19 | }); 20 | }; 21 | // 在控制台创建调试面板 Tencent Docs Dev Tools 22 | const createPanels = () => { 23 | // alert(created); 24 | // alert(checkCount); 25 | // 创建自定义面板,同一个插件可以创建多个自定义面板 26 | // 几个参数依次为:panel 标题、图标(其实设置了也没地方显示)、要加载的页面、加载成功后的回调 27 | chrome.devtools.panels.create( 28 | 'Tencent Docs Dev Tools', 29 | '../public/logo/logo-128.png', 30 | '../public/page/panel.html', 31 | (panel) => { 32 | let _window; 33 | // 注意这个log一般看不到 34 | console.log('自定义面板创建成功!'); 35 | // 监听调试面板显示 36 | panel.onShown.addListener((panelWindow) => { 37 | // alert(2); 38 | console.log(panelWindow); 39 | // 以后可以在这里对面板封装一些公共方法 40 | _window = panelWindow; 41 | // 审查窗口 42 | _window.inspectedWindow = chrome.devtools.inspectedWindow; 43 | }); 44 | // 监听调试面板隐藏 45 | panel.onHidden.addListener(() => { 46 | // 断开 content.ts 文件中 inject.ts 和 content.ts 的通信 47 | // tool.injectScriptStringToPage(` 48 | // window.postMessage( 49 | // { 50 | // source: 'tencent-docs-devtools-devtool-script', 51 | // payload: 'shutdown' 52 | // }, 53 | // '*' 54 | // ); 55 | // `); 56 | }); 57 | } 58 | ); 59 | }; 60 | chrome.devtools.network.onNavigated.addListener(createPanelIfHasDocs); 61 | // 使用定时器每秒检查一次 62 | const checkDocsInterval = setInterval(createPanelIfHasDocs, 1000); 63 | createPanelIfHasDocs(); 64 | -------------------------------------------------------------------------------- /src/core/view/panel/DebugPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Cascader } from 'antd'; 3 | import { TreeSelect } from 'antd'; 4 | // 通讯库 5 | import tool from '../../../panel'; 6 | // 类型接口 7 | interface State { 8 | 9 | } 10 | 11 | export default class ModelPanel extends React.Component { 12 | state = { 13 | value: undefined, 14 | treeData: [ 15 | { id: 1, pId: 0, value: '1', title: 'Expand to load' }, 16 | { id: 2, pId: 0, value: '2', title: 'Expand to load' }, 17 | { id: 3, pId: 0, value: '3', title: 'Tree Node', isLeaf: true }, 18 | ], 19 | }; 20 | 21 | genTreeNode = (parentId, isLeaf = false) => { 22 | const random = Math.random() 23 | .toString(36) 24 | .substring(2, 6); 25 | return { 26 | id: random, 27 | pId: parentId, 28 | value: random, 29 | title: isLeaf ? 'Tree Node' : 'Expand to load', 30 | isLeaf, 31 | }; 32 | }; 33 | 34 | onLoadData = treeNode => 35 | new Promise(resolve => { 36 | console.log(treeNode) 37 | const { id } = treeNode.props; 38 | setTimeout(() => { 39 | const treeData = [ 40 | ...this.state.treeData, 41 | this.genTreeNode(id, false), 42 | this.genTreeNode(id, false), 43 | ]; 44 | this.setState({ 45 | treeData, 46 | }); 47 | resolve(); 48 | }, 300); 49 | }); 50 | 51 | onChange = value => { 52 | console.log(value); 53 | this.setState({ value }); 54 | }; 55 | 56 | render() { 57 | const { treeData } = this.state; 58 | return ( 59 | 69 | ); 70 | } 71 | } -------------------------------------------------------------------------------- /src/core/panel.ts: -------------------------------------------------------------------------------- 1 | class Tool { 2 | public port: any; 3 | public listen: any; 4 | public send: any; 5 | constructor() { 6 | // 初始化,与 background.ts 建立连接 7 | let { port, listen, send } = this.sendMessageToBackground(); 8 | this.port = port; 9 | this.listen = listen; 10 | this.send = send; 11 | } 12 | // 1.panel.ts 与 inject.ts 的通信方案,使用链接的方式 13 | injectScriptLinkToPage = (scriptName: string, cb: Function): void => { 14 | const src = ` 15 | (function() { 16 | var script = document.constructor.prototype.createElement.call(document, 'script'); 17 | script.src = "${chrome.runtime.getURL(scriptName)}"; 18 | document.documentElement.appendChild(script); 19 | // script.parentNode.removeChild(script); 20 | })() 21 | `; 22 | chrome.devtools.inspectedWindow.eval(src, (res, err) => { 23 | if (err) { 24 | console.log(err); 25 | } 26 | cb(); 27 | }); 28 | }; 29 | // 2.panel.ts 与 inject.ts 的通信方案,使用字符串的方式 30 | injectScriptStringToPage = (message: string, callback: Function = null) => { 31 | chrome.devtools.inspectedWindow.eval(message, (value) => { 32 | callback && callback(value); 33 | }); 34 | }; 35 | // 3.panel.ts 与 content.ts 的通信方案 36 | sendMessageToContent = (message: any) => { 37 | window.postMessage(message, '*'); 38 | }; 39 | 40 | // 4.panel.ts 与 background.ts 的通信方案,注意是一个长连接 41 | // 使用 window.postMessage({name:1}, '*')测试 42 | sendMessageToBackground = () => { 43 | // 建立长连接 44 | const port = chrome.runtime.connect({ name: 'devtools' }); 45 | // 监听断开 46 | // 主动关闭使用 port.disconnect(); 47 | port.onDisconnect.addListener(() => { }); 48 | // 监听 background.ts 消息 49 | // port.onMessage.addListener((message) => { 50 | // console.log(`后台已传来消息:${JSON.stringify(message)}`); 51 | // return true; 52 | // }); 53 | // 往 background.ts 发送消息 54 | port.postMessage({ 55 | name: 'original', 56 | tabId: chrome.devtools.inspectedWindow.tabId 57 | }); 58 | 59 | // 释放 port 用于在外部监听 60 | return { 61 | port, 62 | listen(fn) { 63 | port.onMessage.addListener(fn); 64 | }, 65 | send(data) { 66 | port.postMessage(data); 67 | } 68 | }; 69 | }; 70 | } 71 | // 提供通讯手段的工具方法 72 | export default new Tool(); 73 | -------------------------------------------------------------------------------- /src/__tests__/expend2.html: -------------------------------------------------------------------------------- 1 | 59 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/handleSheetSearchTree.ts: -------------------------------------------------------------------------------- 1 | export default () => { 2 | window.SpreadsheetApp.view.canvas.normalSelectData.emitter.on(window.SpreadsheetApp.view.canvas.normalSelectData.EVENT.MODIFY_SELECTION, (e) => { 3 | // window.__console.log(e); 4 | // try { 5 | // window.clearInterval(window.getrangeSelections); 6 | // window.getrangeSelections = setInterval(() => { 7 | let rangeSelections = [...window.SpreadsheetApp.view.getSelection().rangeSelections]; 8 | // 这个方法主要就是把选中表格范围的数据的每一条详情遍历取出来 9 | rangeSelections.map((item) => { 10 | // 行起始位置 11 | let startRowIndex = item.startRowIndex; 12 | // 列起始位置 13 | let startColIndex = item.startColIndex; 14 | // 初始化 15 | item.children = []; 16 | item.title = ''; 17 | // 行的长度 18 | let rowLength = 19 | item.endRowIndex - startRowIndex > 0 ? item.endRowIndex - startRowIndex : 0; 20 | // 列的长度 21 | let colLength = 22 | item.endColIndex - startColIndex > 0 ? item.endColIndex - startColIndex : 0; 23 | // 显示 xxx 行 xxx 列 24 | if (item.startRowIndex - item.endRowIndex === 0) { 25 | item.title = `${item.startRowIndex + 1}行`; 26 | } else { 27 | item.title = `${item.startRowIndex + 1}-${item.endRowIndex + 1}行`; 28 | } 29 | if (item.startColIndex - item.endColIndex === 0) { 30 | item.title += `${item.startColIndex + 1}列`; 31 | } else { 32 | item.title += `${item.startColIndex + 1}-${item.endColIndex + 1}列`; 33 | } 34 | for (let i = 0; i <= rowLength; i++) { 35 | let rowData = window.SpreadsheetApp.spreadsheet.activeSheet.data.rowData; 36 | // SpreadsheetApp.spreadsheet.activeSheet.data.rowData[0].values[0] 37 | // @ts-ignore 38 | // window.__console.log(rowData, startRowIndex + i, startColIndex + i); 39 | // console.log(rowData[startRowIndex + i].values[startColIndex]); 40 | // 如果没有所有行数据,将中断循环 41 | if (!rowData) { 42 | break; 43 | } 44 | // 如果遍历的当前行没数据将不处理该行 45 | if (!rowData[startRowIndex + i]) { 46 | continue; 47 | } 48 | // e 为该单元格详细的数据信息 49 | let e = rowData[startRowIndex + i].values[startColIndex]; 50 | // 设置第n行的第一个 51 | item.children.push([{ e, position: [startRowIndex + i, startColIndex] }]); 52 | for (let j = 0; j < colLength; j++) { 53 | // e 为该单元格详细的数据信息 54 | let e = rowData[startRowIndex + i].values[startColIndex + j + 1]; 55 | // 设置第 n 行第二个之后的元素 56 | item.children[i].push({ 57 | e, 58 | position: [startRowIndex + i, startColIndex + j + 1] 59 | }); 60 | } 61 | } 62 | }); 63 | // 传给面板 64 | window.postMessage( 65 | { 66 | source: 'tencent-docs-devtools-inject', 67 | payload: { rangeSelections } 68 | }, 69 | '*' 70 | ); 71 | // }, 1000); 72 | // } catch (error) {} 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/component/TableSearchTree.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Table, Descriptions } from 'antd'; 3 | 4 | const columns = [ 5 | { 6 | title: 'setSlots', 7 | dataIndex: 'setSlots', 8 | key: 'setSlots' 9 | }, 10 | { 11 | title: 'effectiveValue', 12 | dataIndex: 'effectiveValue', 13 | key: 'effectiveValue' 14 | }, 15 | { 16 | title: 'formattedValue', 17 | dataIndex: 'formattedValue', 18 | key: 'formattedValue' 19 | }, 20 | { 21 | title: 'editValue', 22 | key: 'editValue', 23 | dataIndex: 'editValue' 24 | }, 25 | { 26 | title: 'userEnteredFormat', 27 | dataIndex: 'userEnteredFormat', 28 | key: 'userEnteredFormat', 29 | render(userEnteredFormat) { 30 | console.log(userEnteredFormat); 31 | return ( 32 | 33 | Cloud Database 34 | Prepaid 35 | YES 36 | 37 | ); 38 | } 39 | }, 40 | { 41 | title: 'effectiveFormat', 42 | dataIndex: 'effectiveFormat', 43 | key: 'effectiveFormat' 44 | }, 45 | { 46 | title: 'conditionalFormatResult', 47 | dataIndex: 'conditionalFormatResult', 48 | key: 'conditionalFormatResult' 49 | }, 50 | { 51 | title: 'mergeReference', 52 | dataIndex: 'mergeReference', 53 | key: 'mergeReference' 54 | }, 55 | { 56 | title: 'hyperlink', 57 | dataIndex: 'hyperlink', 58 | key: 'hyperlink' 59 | }, 60 | { 61 | title: 'formula', 62 | dataIndex: 'formula', 63 | key: 'formula' 64 | }, 65 | { 66 | title: 'textFormatRuns', 67 | dataIndex: 'textFormatRuns', 68 | key: 'textFormatRuns' 69 | }, 70 | { 71 | title: 'isDataValid', 72 | dataIndex: 'isDataValid', 73 | key: 'isDataValid' 74 | }, 75 | { 76 | title: 'sheetRangeReferences', 77 | dataIndex: 'sheetRangeReferences', 78 | key: 'sheetRangeReferences' 79 | }, 80 | { 81 | title: 'author', 82 | dataIndex: 'author', 83 | key: 'author' 84 | }, 85 | { 86 | title: 'mentionDetails', 87 | dataIndex: 'mentionDetails', 88 | key: 'mentionDetails' 89 | } 90 | ]; 91 | 92 | const data = [ 93 | { 94 | setSlots: 5, 95 | effectiveValue: { type: 0, value: 33333333, originValue: null }, 96 | formattedValue: { 97 | type: 'normal', 98 | value: '33333333', 99 | textColor: null 100 | }, 101 | editValue: '33333333', 102 | userEnteredFormat: { 103 | setSlots: 1, 104 | numberFormat: { __type: null, __id: 0, __pattern: 'General' }, 105 | dataValidation: null, 106 | backgroundColor: null, 107 | borders: null, 108 | horizontalAlign: null, 109 | verticalAlign: null, 110 | wrapStrategy: null, 111 | textFormat: null 112 | }, 113 | effectiveFormat: null, 114 | conditionalFormatResult: null, 115 | mergeReference: null, 116 | hyperlink: null, 117 | formula: null, 118 | textFormatRuns: null, 119 | isDataValid: null, 120 | sheetRangeReferences: null, 121 | author: null, 122 | mentionDetails: null 123 | } 124 | ]; 125 | 126 | interface Props { 127 | tableData: any[]; 128 | } 129 | export default class SheetSearchTree extends React.Component { 130 | constructor(props) { 131 | super(props); 132 | } 133 | render() { 134 | console.log(this.props.tableData); 135 | return ( 136 | JSON.stringify(this.props.tableData) 137 | // !this.props.tableData || 138 | ); 139 | // return
{JSON.stringify(this.props.tableData)}
; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/core/view/panel/panel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import { Provider } from 'react-redux'; 5 | import store from '../../store'; 6 | 7 | import tool from '../../panel'; 8 | import 'antd/dist/antd.css'; 9 | import './panel.css'; 10 | 11 | // 数据层面板 12 | import ModelPanel from './ModelPanel'; 13 | // 视图层面板 14 | import ViewPanel from './ViewPanel'; 15 | // 视图层面板 16 | import PastePanel from './PastePanel'; 17 | // debug调试面板 18 | import DebugPanel from './DebugPanel'; 19 | 20 | // 提醒调试面板刷新的提示窗口 21 | import ReloadTipModel from './ViewPanel/component/ReloadTipModel'; 22 | 23 | import { Layout, Menu, Icon } from 'antd'; 24 | 25 | import watchSheetPageReload from './ViewPanel/lib/watchSheetPageReload'; 26 | 27 | const { Content, Sider } = Layout; 28 | 29 | // 侧边栏 30 | const menus = [ 31 | { 32 | key: '1', 33 | type: 'user', 34 | className: 'nav-text', 35 | text: '基本功能' 36 | }, 37 | { 38 | key: '2', 39 | type: 'video-camera', 40 | className: 'nav-text', 41 | text: '性能' 42 | }, 43 | { 44 | key: '3', 45 | type: 'upload', 46 | className: 'nav-text', 47 | text: '粘贴板记录' 48 | }, 49 | { 50 | key: '4', 51 | type: 'upload', 52 | className: 'nav-text', 53 | text: '断点调试' 54 | } 55 | // { 56 | // key: '4', 57 | // type: 'user', 58 | // className: 'nav-text', 59 | // text: '其他' 60 | // } 61 | ]; 62 | class DevtoolLayout extends React.Component { 63 | state = { 64 | visible: false, 65 | selectedKeys: '1', 66 | menus 67 | }; 68 | toggle = (selectedKeys) => { 69 | this.setState({ 70 | selectedKeys 71 | }); 72 | }; 73 | render() { 74 | return ( 75 | 76 | 77 | 78 |
79 | 80 | {this.state.menus.map((item) => { 81 | return ( 82 | 86 | 87 | {item.text} 88 | 89 | ); 90 | })} 91 | 92 | 93 | 94 | 95 |
96 | {(() => { 97 | switch (this.state.selectedKeys) { 98 | case '1': 99 | return ; 100 | case '2': 101 | return ; 102 | case '3': 103 | return 104 | case '4': 105 | return 106 | } 107 | })()} 108 |
109 |
110 |
111 | {this.state.visible && } 112 | 113 | 114 | ); 115 | } 116 | // 监听表格页的刷新操作 117 | watchSheetPageReload() { 118 | tool.listen((message) => { 119 | if (message.payload === 'sheet-reload') { 120 | this.setState({ 121 | visible: true 122 | }); 123 | } 124 | }); 125 | tool.injectScriptStringToPage(`;(${watchSheetPageReload.toString()})();`); 126 | } 127 | componentDidMount() { 128 | this.watchSheetPageReload(); 129 | } 130 | } 131 | 132 | ReactDOM.render( 133 |
134 | 135 |
, 136 | document.querySelector('#panel') 137 | ); 138 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/lib/showTips.ts: -------------------------------------------------------------------------------- 1 | const showTips = () => { 2 | let i = 0; 3 | let timer = setInterval(() => { 4 | if (!window.T_SHEET) { 5 | clearInterval(timer); 6 | return; 7 | } 8 | window.console.log(window.T_SHEET); 9 | window.console.log(i); 10 | [ 11 | // showSaveFrontEndErrorTip 60003 60016 60017 40002 40003 40030 12 | () => { 13 | window.T_SHEET.ErrorCodeModal.client.showSaveFrontEndErrorTip('60003'); 14 | }, 15 | // showFileNotExsitErrorTip 16 | () => { 17 | window.T_SHEET.ErrorCodeModal.client.showFileNotExsitErrorTip('40001'); 18 | }, 19 | // showLoginExpiredTip 20 | () => { 21 | window.T_SHEET.ErrorCodeModal.client.showLoginExpiredTip('60006'); 22 | }, 23 | // showUnAuthTip 60007 60015 24 | () => { 25 | window.T_SHEET.ErrorCodeModal.client.showUnAuthTip('60007'); 26 | }, 27 | // showBigDataFromFrontTip C0001 C0002 28 | () => { 29 | window.T_SHEET.ErrorCodeModal.client.showBigDataFromFrontTip( 30 | 'C0002', 31 | '数据量较大,操作不成功' 32 | ); 33 | }, 34 | // showSecurityTip 35 | () => { 36 | window.T_SHEET.ErrorCodeModal.client.showSecurityTip('40025'); 37 | }, 38 | // showSheetBarDuplicateSheetId 39 | () => { 40 | window.T_SHEET.ErrorCodeModal.client.showSheetBarDuplicateSheetId( 41 | '888888', 42 | 'insertNewSheet' 43 | ); 44 | }, 45 | // window.T_SHEET.ErrorCodeModal.client.showPermissionChangesTip(); 46 | // showCommonErrorModal1 47 | () => { 48 | window.T_SHEET.ErrorCodeModal.client.showCommonErrorModal1('通用错误提示modal1'); 49 | }, 50 | // showCommonCrashAndFeedBackModal 51 | () => { 52 | window.T_SHEET.ErrorCodeModal.client.showCommonCrashAndFeedBackModal( 53 | '通用型的引导反馈的 modal' 54 | ); 55 | }, 56 | // checkVipToast 57 | () => { 58 | window.T_SHEET.ErrorCodeModal.server.checkVipToast('vip', () => { 59 | console.log('测试'); 60 | }); 61 | }, 62 | // showServerErrorTip 60000 60001(有可能走这) 63 | () => { 64 | window.T_SHEET.ErrorCodeModal.server.showServerErrorTip('60000'); 65 | }, 66 | // showServerConnectTimeout 60002 67 | () => { 68 | window.T_SHEET.ErrorCodeModal.server.showServerConnectTimeout('60002'); 69 | }, 70 | // showServerTaskTimeout 服务器处理超时 60022 71 | () => { 72 | window.T_SHEET.ErrorCodeModal.server.showServerTaskTimeout('60022'); 73 | }, 74 | // showFullMemberTip 75 | () => { 76 | window.T_SHEET.ErrorCodeModal.server.showFullMemberTip('满员'); 77 | }, 78 | // showFullMemberTipWithRefresh 79 | () => { 80 | window.T_SHEET.ErrorCodeModal.server.showFullMemberTipWithRefresh('INACTIVE'); 81 | }, 82 | // showBigDataFromServerTip 60008 40026 40028 83 | () => { 84 | window.T_SHEET.ErrorCodeModal.server.showBigDataFromServerTip('40026'); 85 | }, 86 | // showFrequentEditTip 60014 87 | () => { 88 | window.T_SHEET.ErrorCodeModal.server.showFrequentEditTip('60014'); 89 | }, 90 | // showSaveBackEndErrorTip 40000 40005 40006 40020 40022 40023 40035 40036 40037 41001 41002 10000 10001 91 | () => { 92 | window.T_SHEET.ErrorCodeModal.server.showSaveBackEndErrorTip('40000'); 93 | }, 94 | // showUpgradeTip 95 | () => { 96 | window.T_SHEET.ErrorCodeModal.server.showUpgradeTip('40034'); 97 | }, 98 | // showUpdatePadTip 99 | // ()=>{window.T_SHEET.ErrorCodeModal.server.showUpdatePadTip()}, 100 | // showApplyNewChangeErrorTip 101 | () => { 102 | window.T_SHEET.ErrorCodeModal.server.showApplyNewChangeErrorTip('aaaaaa'); 103 | }, 104 | // switchSheetError 105 | () => { 106 | window.T_SHEET.ErrorCodeModal.server.switchSheetError('aaaaa'); 107 | }, 108 | () => { 109 | clearInterval(timer); 110 | } 111 | ][i](); 112 | i += 1; 113 | }, 1000); 114 | }; 115 | 116 | export default showTips; 117 | -------------------------------------------------------------------------------- /src/core/view/panel/ModelPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row, Col, Progress } from 'antd'; 3 | // 通讯库 4 | import tool from '../../../panel'; 5 | import showFpsAndMemory from './lib/showFpsAndMemory'; 6 | import showNetworkSpeed from './lib/showNetworkSpeed'; 7 | // 引入 echarts 绘图 8 | import echarts from 'echarts'; 9 | // 类型接口 10 | interface State { 11 | memory: number; 12 | fps: number; 13 | networkSpeed: number; 14 | memoryCharts: any; 15 | fpsCharts: any; 16 | networkSpeedCharts: any; 17 | } 18 | const memoryChartsData = { 19 | title: { 20 | text: '' 21 | }, 22 | tooltip: {}, 23 | xAxis: { 24 | data: [] 25 | }, 26 | yAxis: {}, 27 | series: [ 28 | { 29 | name: 'memory', 30 | type: 'line', 31 | smooth: true, 32 | data: [] 33 | } 34 | ] 35 | }; 36 | 37 | const fpsChartsData = { 38 | title: { 39 | text: '' 40 | }, 41 | tooltip: {}, 42 | xAxis: { 43 | data: [] 44 | }, 45 | yAxis: {}, 46 | series: [ 47 | { 48 | name: 'fps', 49 | type: 'line', 50 | smooth: true, 51 | data: [] 52 | } 53 | ] 54 | }; 55 | 56 | const networkSpeedChartsData = { 57 | title: { 58 | text: '' 59 | }, 60 | tooltip: {}, 61 | xAxis: { 62 | data: [] 63 | }, 64 | yAxis: {}, 65 | series: [ 66 | { 67 | name: 'speed', 68 | type: 'line', 69 | smooth: true, 70 | data: [] 71 | } 72 | ] 73 | }; 74 | 75 | export default class ModelPanel extends React.Component { 76 | state: State = { 77 | memory: 60, 78 | fps: 60, 79 | networkSpeed: 0, 80 | memoryCharts: null, 81 | fpsCharts: null, 82 | networkSpeedCharts: null 83 | }; 84 | 85 | componentDidMount() { 86 | // 显示 fps 和 memory 的功能 87 | this.showFpsAndMemory(); 88 | // 网速测速 89 | // this.showNetworkSpeed(); 90 | } 91 | 92 | showNetworkSpeed() { 93 | tool.listen((message) => { 94 | if (!message.networkSpeed) { 95 | return true; 96 | } 97 | console.log(message.networkSpeed); 98 | this.setState( 99 | { 100 | networkSpeed: message.networkSpeed, 101 | networkSpeedCharts: echarts.init(this.refs.renderNetworkSpeedCharts) 102 | }, 103 | () => { 104 | // 设置网络速度变化的图标数据 105 | networkSpeedChartsData.series[0].data.push(message.networkSpeed); 106 | this.state.networkSpeedCharts.setOption(networkSpeedChartsData); 107 | } 108 | ); 109 | return true; 110 | }); 111 | tool.injectScriptStringToPage(`;(${showNetworkSpeed.toString()})();`); 112 | } 113 | 114 | showFpsAndMemory() { 115 | tool.listen((message) => { 116 | let memory = message.payload.memory; 117 | let fps = message.payload.fps; 118 | if (!memory || !fps) { 119 | return true; 120 | } 121 | if (!this.refs.renderMemoryCharts || !this.refs.renderFpsCharts) { 122 | return true; 123 | } 124 | this.setState( 125 | { 126 | memory: memory, 127 | fps: fps, 128 | memoryCharts: echarts.init(this.refs.renderMemoryCharts), 129 | fpsCharts: echarts.init(this.refs.renderFpsCharts) 130 | }, 131 | () => { 132 | // 设置内存变化的图标数据 133 | memoryChartsData.series[0].data.push(memory); 134 | // 设置 fps 变化的图标数据 135 | fpsChartsData.series[0].data.push(fps); 136 | // 绘制图表 137 | this.state.memoryCharts.setOption(memoryChartsData); 138 | this.state.fpsCharts.setOption(fpsChartsData); 139 | } 140 | ); 141 | return true; 142 | }); 143 | tool.injectScriptStringToPage(`;(${showFpsAndMemory.toString()})();`); 144 | } 145 | 146 | componentWillUnmount() { 147 | tool.injectScriptStringToPage(`window.cancelAnimationFrame(window.animationFrameID);`); 148 | // 销毁监听网速的定时器 149 | tool.injectScriptStringToPage(`clearInterval(window.networkSpeedTimer);`); 150 | // 清空 echarts 数据队列 151 | memoryChartsData.series[0].data = []; 152 | fpsChartsData.series[0].data = []; 153 | networkSpeedChartsData.series[0].data = []; 154 | } 155 | 156 | render() { 157 | return ( 158 |
159 | 160 |
161 | `${percent * 10} M`} 165 | /> 166 | 167 | 168 |
172 | 173 | 174 | 175 | 176 | `${percent} FPS`} 180 | /> 181 | 182 | 183 |
187 | 188 | 189 | {/* 190 | 191 | `${percent * 10} K`} 195 | /> 196 | 197 | 198 |
202 | 203 | */} 204 | 205 | ); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/component/SheetSearchTree.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tree, Input, Drawer, Tag } from 'antd'; 3 | import TableSearchTree from './TableSearchTree'; 4 | import tool from '../../../../panel'; 5 | import showRecordsList from '../lib/showRecordsList'; 6 | 7 | const { TreeNode } = Tree; 8 | const { Search } = Input; 9 | interface Props { 10 | rangeSelections: [ 11 | { 12 | isSheet: boolean; 13 | sheetId: string; 14 | startRowIndex: number; 15 | endRowIndex: number; 16 | startColIndex: number; 17 | endColIndex: number; 18 | title: string; 19 | children?: any[]; 20 | } 21 | ]; 22 | } 23 | 24 | interface State { 25 | searchValue: string; 26 | autoExpandParent: boolean; 27 | tableData: any; 28 | history: any; 29 | visible: boolean; 30 | selectedKeys: string; 31 | } 32 | 33 | export default class SheetSearchTree extends React.Component { 34 | constructor(props) { 35 | super(props); 36 | } 37 | state: State = { 38 | searchValue: '', 39 | autoExpandParent: true, 40 | tableData: null, 41 | history: null, 42 | visible: false, 43 | selectedKeys: '' 44 | }; 45 | num2letter(index: number) { 46 | let letter = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 47 | if (index <= 25) { 48 | return letter[index]; 49 | } else { 50 | let result = index / 26; 51 | let l1 = parseInt(`${result}`) - 1; 52 | let l2 = index % 26; 53 | return letter[l1] + letter[l2]; 54 | } 55 | } 56 | getSheetDetail(selectedKeys, e) { 57 | try { 58 | const tableData = e.selectedNodes[0].props.tableData; 59 | // console.log(selectedKeys, e); 60 | // 获取单行 61 | if (tableData instanceof Array) { 62 | this.showDrawer(selectedKeys, { e: tableData }); 63 | } 64 | // 整列和行的所有信息 65 | else if (tableData.children instanceof Array) { 66 | this.showDrawer(selectedKeys, { e: tableData.children }); 67 | } 68 | // 获取一个单元格的信息 69 | else { 70 | // 展开抽屉 71 | this.showDrawer(selectedKeys, tableData); 72 | // 获取修订记录 73 | // this.getRecordsList(); 74 | let postion = tableData.position; 75 | tool.injectScriptStringToPage(` 76 | window.__console.log('-----第${postion[0] + 1}行,第${this.num2letter( 77 | postion[1] 78 | )}列-----') 79 | window.__console.log(window.SpreadsheetApp.spreadsheet.activeSheet.data.rowData[${ 80 | postion[0] 81 | }].values[${postion[1]}]) 82 | `); 83 | } 84 | } catch (error) { 85 | console.log(error); 86 | } 87 | } 88 | onChange() { 89 | this.setState({ 90 | autoExpandParent: true 91 | }); 92 | } 93 | treeNode(item) { 94 | // 渲染列 95 | let colTreeNode = (col) => { 96 | return ( 97 | 102 | ); 103 | }; 104 | // 渲染行 105 | let rowTreeNode = (row) => { 106 | return ( 107 | 112 | {row 113 | ? row.map((col) => { 114 | return colTreeNode(col); 115 | }) 116 | : '加载树结构...'} 117 | 118 | ); 119 | }; 120 | return ( 121 | // 渲染行和列总体的数据 122 | 123 | {item.children.length 124 | ? item.children.map((row) => { 125 | return rowTreeNode(row); 126 | }) 127 | : '加载树结构...'} 128 | 129 | ); 130 | } 131 | 132 | getRecordsList(page?: number, callBack?: Function) { 133 | tool.listen((message) => { 134 | try { 135 | const history = message.payload.history; 136 | if (history) { 137 | this.setState({ 138 | history 139 | }); 140 | } 141 | } catch (error) {} 142 | }); 143 | tool.injectScriptStringToPage(`;(${showRecordsList.toString()})();`); 144 | } 145 | showDrawer = (selectedKeys, tableData) => { 146 | this.setState({ 147 | selectedKeys, 148 | visible: true, 149 | tableData: tableData ? tableData : null 150 | }); 151 | }; 152 | onClose() { 153 | this.setState({ 154 | visible: false 155 | }); 156 | } 157 | // 把键值对渲染树状图上 158 | showKeyAndValue(data) { 159 | let arr = []; 160 | Object.keys(data).forEach((key, index) => { 161 | switch (typeof data[key]) { 162 | case 'object': 163 | // null类型 164 | if (data[key] === null) { 165 | arr.push( 166 | 170 | ); 171 | } 172 | // 数组类型 173 | else if (data[key] instanceof Array) { 174 | arr.push( 175 | 176 | {/* 渲染子元素 */} 177 | {this.showKeyAndValue(data[key])} 178 | 179 | ); 180 | } 181 | // 对象类型 182 | else { 183 | arr.push( 184 | 185 | {/* 渲染子元素 */} 186 | {this.showKeyAndValue(data[key])} 187 | 188 | ); 189 | } 190 | break; 191 | case 'string': 192 | arr.push( 193 | 197 | ); 198 | break; 199 | case 'number': 200 | arr.push( 201 | 205 | ); 206 | break; 207 | case 'undefined': 208 | arr.push( 209 | 213 | ); 214 | break; 215 | } 216 | }); 217 | return arr; 218 | } 219 | componentDidMount() {} 220 | render() { 221 | return ( 222 | <> 223 |
224 | {this.props.rangeSelections.length > 0 && ( 225 | 230 | {this.props.rangeSelections.length 231 | ? this.props.rangeSelections.map((item) => { 232 | return this.treeNode(item); 233 | }) 234 | : '加载树结构...'} 235 | 236 | )} 237 | {/* */} 238 |
239 | {/* 弹窗 */} 240 | 248 | {/* */} 253 | 254 | {this.state.tableData 255 | ? this.showKeyAndValue(this.state.tableData.e) 256 | : '加载树结构...'} 257 | 258 | 259 | 260 | ); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/core/background.ts: -------------------------------------------------------------------------------- 1 | // import recordScreen from '../core/view/panel/ViewPanel/lib/recordScreen'; 2 | 3 | // 作为 content.ts 与 devtool.ts 通信的桥 4 | const connections = {}; 5 | // panel.ts 和 background.ts 的通信 6 | chrome.runtime.onConnect.addListener(function (port) { 7 | const extensionListener = function (message) { 8 | if (message.name == 'original') { 9 | connections[message.tabId] = port; 10 | } 11 | return true; 12 | }; 13 | port.onMessage.addListener(extensionListener); 14 | port.onDisconnect.addListener(function (port) { 15 | port.onMessage.removeListener(extensionListener); 16 | const tabs = Object.keys(connections); 17 | for (let i = 0, len = tabs.length; i < len; i++) { 18 | if (connections[tabs[i]] == port) { 19 | delete connections[tabs[i]]; 20 | break; 21 | } 22 | } 23 | }); 24 | }); 25 | 26 | declare const MediaRecorder: any; 27 | 28 | let mediaRecorder; 29 | let recordedBlobs; 30 | let sourceBuffer; 31 | 32 | let localStream; 33 | 34 | let localVideo = document.getElementById('localVideo') as HTMLVideoElement; 35 | let recordedVideo = document.getElementById('recordedVideo') as HTMLVideoElement; 36 | 37 | let recordButton = document.getElementById('record') as HTMLButtonElement; 38 | let playButton = document.getElementById('play') as HTMLButtonElement; 39 | let downloadButton = document.getElementById('download') as HTMLButtonElement; 40 | playButton.disabled = true; 41 | downloadButton.disabled = true; 42 | 43 | recordButton.onclick = toggleRecording; 44 | playButton.onclick = call; 45 | downloadButton.onclick = download; 46 | 47 | function trace(param) { 48 | let time = (window.performance.now() / 1000).toFixed(3); 49 | console.log(time + ':' + param); 50 | } 51 | 52 | function handleSuccess(stream) { 53 | console.log(stream); 54 | stream.onremovetrack = () => { 55 | // Click on browser UI stop sharing button 56 | alert('Recording has ended'); 57 | }; 58 | trace('getUserMedia() got stream!'); 59 | recordButton.disabled = false; 60 | window.stream = stream; // 录制视频的时候要用到 61 | localStream = stream; 62 | localVideo.srcObject = stream; 63 | // 点击录制视频的按钮 64 | document.getElementById('record').click(); 65 | } 66 | 67 | function handleError(error) { 68 | // 点击暂停按钮 69 | document.getElementById('record').click(); 70 | // 点击下载按钮 71 | document.getElementById('download').click(); 72 | // trace(error.message); 73 | } 74 | 75 | let constraints = { 76 | video: true, 77 | audio: false 78 | }; 79 | 80 | let mediaSource = new MediaSource(); 81 | // 通过注册事件event::sourceopen来触发当前连接之后的的回调处理 82 | mediaSource.addEventListener('sourceopen', handleSourceOpen, false); 83 | 84 | /* 85 | * 回调处理就是需要赋值视频数据的地方, 86 | * 调用MediaSourceBuffer::addSourceBuffer方法来 ==> 构建一个存放视频数据的Buffer; 87 | */ 88 | function handleSourceOpen(event) { 89 | trace('MediaSource opened!'); 90 | sourceBuffer = mediaSource.addSourceBuffer("video/webm;codecs='vp8'"); 91 | trace(sourceBuffer); 92 | } 93 | 94 | /* 这里把每10ms获取的数据放入recordedBlobs */ 95 | function handleDataAvailable(event) { 96 | if (event.data && event.data.size > 0) { 97 | recordedBlobs.push(event.data); 98 | } 99 | } 100 | 101 | /* 102 | * 录制button状态转换 103 | */ 104 | function toggleRecording() { 105 | if (recordButton.textContent === 'Start Recording') { 106 | startRecording(); 107 | } else { 108 | stopRecording(); 109 | recordButton.textContent = 'Start Recording'; 110 | playButton.disabled = false; 111 | downloadButton.disabled = false; 112 | 113 | let test = document.getElementById('test') as HTMLVideoElement; 114 | let buffer = new Blob(recordedBlobs, { 115 | type: 'video/webm' 116 | }); 117 | test.src = window.URL.createObjectURL(buffer); 118 | } 119 | } 120 | 121 | /* 开始录制 */ 122 | function startRecording() { 123 | recordedBlobs = []; 124 | let options = { 125 | mimeType: 'video/webm;codecs=vp9' 126 | }; 127 | // isTypeSupporteed:判断是否支持要解码播放的视频文件编码和类型。 128 | if (!MediaRecorder.isTypeSupported(options.mimeType)) { 129 | console.log(options.mimeType + 'is not Supported'); 130 | options = { 131 | mimeType: 'video/webm;codecs=vp8' 132 | }; 133 | if (!MediaRecorder.isTypeSupported(options.mimeType)) { 134 | console.log(options.mimeType + 'is not Supported'); 135 | options = { 136 | mimeType: 'video/webm;codecs=vp8' 137 | }; 138 | if (!MediaRecorder.isTypeSupported(options.mimeType)) { 139 | console.log(options.mimeType + 'is not Supported'); 140 | options = { 141 | mimeType: '' 142 | }; 143 | } 144 | } 145 | } 146 | try { 147 | mediaRecorder = new MediaRecorder(window.stream, options); // 设置音频录入源、格式 148 | } catch (e) { 149 | console.log('Exception while creating MediaRecorder: ' + e); 150 | // alert('Exception while creating MediaRecorder: ' + e + '. mimeType: ' + options.mimeType); 151 | return; 152 | } 153 | console.log('Created MediaRecorder', mediaRecorder, 'with options', options); 154 | recordButton.textContent = 'Stop Recording'; 155 | playButton.disabled = true; 156 | downloadButton.disabled = true; 157 | mediaRecorder.onstop = handleStop; 158 | mediaRecorder.ondataavailable = handleDataAvailable; // 存放获取的数据 159 | mediaRecorder.start(10); // 开始录制 collect 10ms of data 160 | console.log('MediaRecorder started', mediaRecorder); 161 | } 162 | 163 | function handleStop(event) { 164 | console.log('Recorder stopped: ', event); 165 | } 166 | 167 | /* 结束录制 */ 168 | function stopRecording() { 169 | mediaRecorder.stop(); 170 | console.log('Recorded Blobs: ', recordedBlobs); 171 | recordedVideo.controls = true; 172 | } 173 | 174 | /* 175 | * 录制视频添加监听事件 176 | */ 177 | recordedVideo.addEventListener( 178 | 'error', 179 | function (ev) { 180 | // 点击暂停按钮 181 | document.getElementById('record').click(); 182 | // 点击下载按钮 183 | document.getElementById('download').click(); 184 | trace('MediaRecording.recordedMedia.error()'); 185 | }, 186 | true 187 | ); 188 | 189 | /* 190 | * 播放录制的视频 191 | */ 192 | function call() { 193 | trace('Starting call'); 194 | if (localStream.getVideoTracks().length > 0) { 195 | trace('Using video device: ' + localStream.getVideoTracks()[0].label); 196 | } 197 | if (localStream.getAudioTracks().length > 0) { 198 | trace('Using audio device: ' + localStream.getAudioTracks()[0].label); 199 | } 200 | } 201 | 202 | /* 203 | * 下载视频 204 | */ 205 | function download() { 206 | let blob = new Blob(recordedBlobs, { 207 | type: 'video/webm' 208 | }); 209 | let url = window.URL.createObjectURL(blob); 210 | let a = document.createElement('a'); 211 | a.style.display = 'none'; 212 | a.href = url; 213 | a.download = 'test.webm'; 214 | document.body.appendChild(a); 215 | a.click(); 216 | setTimeout(function () { 217 | document.body.removeChild(a); 218 | window.URL.revokeObjectURL(url); 219 | }, 100); 220 | } 221 | 222 | // 通信线路:inject->content->background->devtool/panel->inject 223 | // 接收 content.ts 的消息,并发送到 devtool.ts/panel.ts 的消息 224 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 225 | // 开始录屏 226 | if (message.payload === 'startRecordScreen') { 227 | navigator.mediaDevices 228 | // @ts-ignore 229 | .getDisplayMedia(constraints) 230 | .then(handleSuccess) 231 | .catch(handleError); 232 | } 233 | // 暂停录屏 234 | if (message.payload === 'stopRecordScreen') { 235 | // 点击暂停按钮 236 | document.getElementById('record').click(); 237 | // 点击下载按钮 238 | document.getElementById('download').click(); 239 | } 240 | 241 | // 截图 242 | if (message.payload === 'startScreenShot') { 243 | chrome.tabs.captureVisibleTab(null, {}, (dataUrl) => { 244 | const a = document.createElement('a'); 245 | a.download = 'screenshot.png'; 246 | a.href = dataUrl; 247 | document.body.appendChild(a); 248 | a.click(); 249 | setTimeout(() => { 250 | document.body.removeChild(a); 251 | }, 100); 252 | }); 253 | return true; 254 | } 255 | 256 | // 监听粘贴板内容 257 | if (message.payload === 'startWatchClipboardContents') { 258 | clearInterval(window.pasteRecord); 259 | window.pasteRecord = setInterval(() => { 260 | let pasteInfoFromStorage = localStorage.getItem('paste-info') 261 | ? JSON.parse(localStorage.getItem('paste-info')) 262 | : []; 263 | // 继承原来Storage的粘贴板数据 264 | let pasteArr = [...pasteInfoFromStorage]; 265 | console.log(pasteArr); 266 | let input = document.createElement('input'); 267 | document.body.appendChild(input); 268 | input.focus(); 269 | document.execCommand('paste'); 270 | let clipboardText = input.value; 271 | if (pasteArr.length > 0) { 272 | // 当当前粘贴板的数据和上一次的数据一样,则不存进Storage里面,否则才加入 273 | !(pasteArr[pasteArr.length - 1] === clipboardText) && 274 | (() => { 275 | pasteArr.push(clipboardText); 276 | localStorage.setItem('paste-info', JSON.stringify(pasteArr)); 277 | })(); 278 | } 279 | // 处理第一个放入数组的数据 280 | else if (pasteArr.length === 0) { 281 | pasteArr.push(clipboardText); 282 | localStorage.setItem('paste-info', JSON.stringify(pasteArr)); 283 | } 284 | console.log(clipboardText); 285 | document.body.removeChild(input); 286 | }, 1000); 287 | } 288 | if (message.payload === 'stopWatchClipboardContents') { 289 | clearInterval(window.pasteRecord); 290 | } 291 | // 测试代码 292 | console.log('收到来自content-script的消息:'); 293 | console.log(message, sender, sendResponse); 294 | if (sender.tab) { 295 | const tabId = sender.tab.id; 296 | if (tabId in connections) { 297 | console.log('sender.tab is defined.'); 298 | connections[tabId].postMessage(message); 299 | } else { 300 | console.log('Tab not found in connection list.'); 301 | } 302 | } else { 303 | console.log('sender.tab not defined.'); 304 | } 305 | return true; 306 | }); 307 | -------------------------------------------------------------------------------- /src/core/view/panel/ViewPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button, Input, Statistic, Card, Row, Col } from 'antd'; 3 | // 通讯库 4 | import tool from '../../../panel'; 5 | // 弹窗测试 6 | import showTips from './lib/showTips'; 7 | import showFdAndId from './lib/showFdAndId'; 8 | // 列表树组件 9 | import SheetSearchTree from './component/SheetSearchTree'; 10 | import handleSheetSearchTree from './lib/handleSheetSearchTree'; 11 | 12 | // 启用 console.log 方法 13 | import useConsole from './lib/useConsole'; 14 | 15 | // 断开调试面板和页面的连接 16 | import shutdown from './lib/shutdown'; 17 | // 查找空节点 18 | import emptyDom from './lib/emptyDom'; 19 | import { connect } from 'react-redux'; 20 | 21 | const { TextArea } = Input; 22 | interface State { 23 | textAreaScript: string; 24 | rangeSelections: [ 25 | { 26 | isSheet: boolean; 27 | sheetId: string; 28 | startRowIndex: number; 29 | endRowIndex: number; 30 | startColIndex: number; 31 | endColIndex: number; 32 | children?: any[]; 33 | title: string; 34 | } 35 | ]; 36 | SheetSearchTree: any; 37 | isRecordScreen: boolean; 38 | } 39 | 40 | interface Props { 41 | setRangeSelections: (author: string) => void; 42 | setFdAndRouteId: (any) => void; 43 | rangeSelections: [ 44 | { 45 | isSheet: boolean; 46 | sheetId: string; 47 | startRowIndex: number; 48 | endRowIndex: number; 49 | startColIndex: number; 50 | endColIndex: number; 51 | children?: any[]; 52 | title: string; 53 | } 54 | ]; 55 | fd: string; 56 | routeId: string; 57 | } 58 | 59 | class ModelPanel extends React.Component { 60 | constructor(props) { 61 | super(props); 62 | } 63 | state: State = { 64 | textAreaScript: '', 65 | rangeSelections: [ 66 | { 67 | isSheet: false, 68 | sheetId: 'BB08J2', 69 | startRowIndex: 20, 70 | endRowIndex: 20, 71 | startColIndex: 2, 72 | endColIndex: 2, 73 | children: [ 74 | [ 75 | { 76 | e: { 77 | setSlots: 5, 78 | effectiveValue: { type: 0, value: 13123123, originValue: null }, 79 | formattedValue: { 80 | type: 'normal', 81 | value: '13123123', 82 | textColor: null 83 | }, 84 | editValue: '13123123', 85 | userEnteredFormat: { 86 | setSlots: 1, 87 | numberFormat: { __type: null, __id: 0, __pattern: 'General' }, 88 | dataValidation: null, 89 | backgroundColor: null, 90 | borders: null, 91 | horizontalAlign: null, 92 | verticalAlign: null, 93 | wrapStrategy: null, 94 | textFormat: null 95 | }, 96 | effectiveFormat: null, 97 | conditionalFormatResult: null, 98 | mergeReference: null, 99 | hyperlink: null, 100 | formula: null, 101 | textFormatRuns: null, 102 | isDataValid: null, 103 | sheetRangeReferences: null, 104 | author: null, 105 | mentionDetails: null 106 | }, 107 | position: [20, 2] 108 | } 109 | ] 110 | ], 111 | title: '21行3列' 112 | } 113 | ], 114 | // 存放树组件 115 | SheetSearchTree: null, 116 | // 用于判断当前状态是否录屏状态 117 | isRecordScreen: false 118 | }; 119 | componentDidMount() { 120 | this.showFdAndId(); 121 | // 获取页面上用户在表格中选取范围的数据信息 122 | this.sheetSearchTree(); 123 | // console.log(kscreenshot); 124 | } 125 | componentWillUnmount() { 126 | // 清空获取页面上用户在表格中选取范围的数据信息的定时器 127 | // window.clearInterval(window.getrangeSelections); 128 | } 129 | render() { 130 | return ( 131 |
132 | {/* 显示 fd 和 id 信息 */} 133 |
134 | 135 |
136 | 137 | 143 | 144 | 145 | 146 | 147 | 153 | 154 | 155 | 156 | 157 | {/* 如果表格页面刷新了,那么请把调试工具面板重新刷新一次,不然无法监听最新的表格数据 */} 158 |
159 | 162 | 169 | 170 | 177 | 178 | 185 | 186 | 193 | 194 | {/* */} 201 | {this.state.isRecordScreen ? ( 202 | 209 | ) : ( 210 | 217 | )} 218 | 221 | 222 |
223 | {/*
224 | 225 |
226 |
227 |