= ({ align = 'left', style = {}, className = '', buttons = [] }) => {
23 | return (
24 |
28 | {buttons.map(({ children, component, ...otherProps }, index) =>
29 | component ? (
30 | {component}
31 | ) : (
32 |
35 | ),
36 | )}
37 |
38 | )
39 | }
40 |
41 | export default ButtonGroup
42 |
--------------------------------------------------------------------------------
/src/assets/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "background": {
3 | "persistent": true,
4 | "scripts": ["request.js"]
5 | },
6 | "browser_action": {
7 | "default_icon": "packet proxy.png",
8 | "default_popup": "index.html",
9 | "default_title": "Choose your proxy."
10 | },
11 | "content_scripts": [
12 | {
13 | "js": ["/content.js"],
14 | "matches": ["\u003Call_urls>"],
15 | "run_at": "document_start",
16 | "all_frames": true
17 | }
18 | ],
19 | "web_accessible_resources": ["script/main.js"],
20 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
21 | "commands": {
22 | "_execute_browser_action": {
23 | "suggested_key": {
24 | "chromeos": "Ctrl+Shift+U",
25 | "linux": "Ctrl+Shift+J",
26 | "mac": "Command+Shift+Y",
27 | "windows": "Ctrl+Shift+Y"
28 | }
29 | }
30 | },
31 | "description": "A quick proxy plugin for requests, cookies and headers",
32 | "manifest_version": 2,
33 | "name": "Packet Proxy",
34 | "permissions": [
35 | "storage",
36 | "webRequest",
37 | "webRequestBlocking",
38 | "\u003Call_urls>",
39 | "tabs",
40 | "cookies"
41 | ],
42 | "update_url": "http://clients2.google.com/service/update2/crx",
43 | "version": "0.0.7"
44 | }
45 |
--------------------------------------------------------------------------------
/src/pages/Home/components/Replacer/index.less:
--------------------------------------------------------------------------------
1 | .replace-with {
2 | margin-bottom: 6px;
3 | font-weight: 500;
4 | text-align: left;
5 | }
6 | .overrideTxt {
7 | position: relative;
8 | display: inline-block;
9 | box-sizing: border-box;
10 | width: 100%;
11 | height: 100px;
12 | margin: 0;
13 | padding: 4px 11px;
14 | color: rgba(0, 0, 0, 0.65);
15 | font-size: 14px;
16 | font-variant: tabular-nums;
17 | line-height: 1.5;
18 | list-style: none;
19 | background-color: #fff;
20 | background-image: none;
21 | border: 1px solid #d9d9d9;
22 | border-radius: 4px;
23 | resize: none;
24 | font-feature-settings: 'tnum';
25 | }
26 |
27 | .errorTxt {
28 | border: 1px solid red !important;
29 | }
30 |
31 | .errorTip {
32 | color: red;
33 | }
34 |
35 | .overrideTxt::-webkit-input-placeholder {
36 | color: hsv(0, 0, 75%);
37 | }
38 |
39 | .overrideTxt:focus {
40 | border-color: #40a9ff;
41 | border-right-width: 1px !important;
42 | outline: 0;
43 | -webkit-box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
44 | box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
45 | }
46 | .overrideTxt:hover {
47 | border-color: #40a9ff;
48 | border-right-width: 1px !important;
49 | }
50 | .JsonEditor {
51 | width: calc(100% + 10px);
52 | // height: 300px;
53 | // margin-top: 15px;
54 | overflow: auto;
55 | text-align: left;
56 | }
57 | .invalid {
58 | color: rgb(168, 168, 168);
59 | font-size: 12px;
60 | text-align: center;
61 | }
--------------------------------------------------------------------------------
/src/utils/request.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { notification } from 'antd';
3 |
4 | const axiosInstance = axios.create({
5 | timeout: 10000,
6 | });
7 |
8 | // 过滤掉请求参数为 null | undefined 的属性;去除字符串两端的特殊字符
9 | const filterParams = (params) => {
10 | const newPrams = {};
11 | for (const key in params) {
12 | const paramsValue = params[key];
13 | if (typeof paramsValue === 'string' && paramsValue !== '') {
14 | newPrams[key] = paramsValue.trim();
15 | } else if (paramsValue !== null && typeof paramsValue !== 'undefined') {
16 | newPrams[key] = paramsValue;
17 | }
18 | }
19 | return newPrams;
20 | };
21 |
22 | const errHandler = (description: string = '') => {
23 | notification.error({
24 | message: '接口失败',
25 | description
26 | });
27 | }
28 |
29 | const request = ({ method, url, params = {}, data = {}, restConfig = {} }) => {
30 | return new Promise((resolve, reject) => {
31 | const config = {
32 | method,
33 | url,
34 | data: filterParams(data),
35 | params: filterParams(params),
36 | ...restConfig,
37 | };
38 | axiosInstance(config).then(
39 | (res: any) => {
40 | // 根据服务端的接口定义判断请求成功的条件
41 | // 例如接口的状态码为 200 为成功条件
42 | if (res.data && res.data.code === 200) {
43 | resolve(res.data);
44 | } else {
45 | reject(res.data);
46 | errHandler(res.message || '服务错误');
47 | }
48 | },
49 | (err) => {
50 | reject(err);
51 | errHandler(String(err));
52 | }
53 | )
54 | })
55 | };
56 |
57 | export default request;
58 |
--------------------------------------------------------------------------------
/src/pages/Home/components/GroupModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useState } from 'react'
2 | import { Modal } from 'antd'
3 | import { SchemaForm, createFormActions } from '@formily/antd'
4 | import BaseInput from '../BaseInput'
5 |
6 | const actions = createFormActions()
7 |
8 | const GroupModal = ({ visible, onOk, onCancel, mode, data }) => {
9 | const [formValue, setFormValue] = useState({})
10 |
11 | const onModalOk = useCallback(() => {
12 | actions.validate().then(() => {
13 | onOk(formValue)
14 | })
15 | }, [onOk, formValue])
16 |
17 | useEffect(() => {
18 | visible && setFormValue(data)
19 | }, [visible])
20 |
21 | return (
22 |
29 |
50 |
51 | )
52 | }
53 |
54 | export default GroupModal
55 |
--------------------------------------------------------------------------------
/src/pages/Home/components/ItemModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState, useEffect } from 'react'
2 | import { Modal } from 'antd'
3 | import { SchemaForm, createFormActions } from '@formily/antd'
4 | import BaseInput from '../BaseInput'
5 |
6 | const actions = createFormActions()
7 |
8 | const ItemModal = ({ visible, onOk, onCancel, mode, data }) => {
9 | const [formValue, setFormValue] = useState({})
10 |
11 | const onModalOk = useCallback(() => {
12 | actions.validate().then(() => {
13 | onOk(formValue)
14 | })
15 | }, [onOk, formValue])
16 |
17 | useEffect(() => {
18 | visible && setFormValue(data)
19 | }, [visible])
20 |
21 | return (
22 |
29 |
61 |
62 | )
63 | }
64 |
65 | export default ItemModal
66 |
--------------------------------------------------------------------------------
/src/pages/Home/components/ItemDataModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState, useEffect } from 'react'
2 | import { Modal } from 'antd'
3 | import { SchemaForm, createFormActions } from '@formily/antd'
4 | import BaseInput from '../BaseInput'
5 | import Replacer from '../Replacer'
6 |
7 | const actions = createFormActions()
8 |
9 | const ItemModal = ({ visible, onOk, onCancel, mode, data }) => {
10 | const [formValue, setFormValue] = useState({})
11 |
12 | const onModalOk = useCallback(() => {
13 | actions.validate().then(() => {
14 | onOk(formValue)
15 | })
16 | }, [onOk, formValue])
17 |
18 | useEffect(() => {
19 | visible && setFormValue(data)
20 | }, [visible])
21 |
22 | return (
23 |
30 |
60 |
61 | )
62 | }
63 |
64 | export default ItemModal
65 |
--------------------------------------------------------------------------------
/src/pages/Home/components/Replacer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useCallback, useState, useEffect } from 'react'
2 | import { Input } from 'antd'
3 | import { InputProps } from 'antd/lib/input'
4 | import ReactJson from 'react-json-view'
5 |
6 | import './index.less'
7 |
8 | const { TextArea } = Input
9 |
10 | interface IProps extends InputProps {
11 | onChange: (v: any) => void;
12 | }
13 |
14 | const Replacer: FC = ({ onChange, value, placeholder }) => {
15 | const [formatValue, setFormatValue] = useState(null)
16 | const onInputChange = useCallback(
17 | (e) => {
18 | onChange(e.target.value)
19 | },
20 | [onChange],
21 | )
22 |
23 | useEffect(() => {
24 | try {
25 | const newFormat = JSON.parse(value as string)
26 | setFormatValue(newFormat)
27 | } catch {
28 | setFormatValue(null)
29 | }
30 | }, [value, setFormatValue])
31 |
32 | const handleJSONEditorChange = useCallback(
33 | // eslint-disable-next-line camelcase, @typescript-eslint/camelcase
34 | ({ updated_src }) => {
35 | const txt = JSON.stringify(updated_src)
36 | setFormatValue(updated_src)
37 | onChange(txt)
38 | },
39 | [onChange, setFormatValue],
40 | )
41 |
42 | return (
43 | <>
44 |
45 | {formatValue ? (
46 |
47 | {/* @ts-ignore */}
48 |
58 |
59 | ) : (
60 | Invalid JSON
61 | )}
62 | >
63 | )
64 | }
65 |
66 | export default Replacer
67 |
--------------------------------------------------------------------------------
/src/assets/metadata/verified_contents.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "description":"treehash per file",
4 | "signed_content":{
5 | "payload":"eyJjb250ZW50X2hhc2hlcyI6W3siYmxvY2tfc2l6ZSI6NDA5NiwiZGlnZXN0Ijoic2hhMjU2IiwiZmlsZXMiOlt7InBhdGgiOiJqcXVlcnkuanMiLCJyb290X2hhc2giOiJQLUpHSW9mbDM5MVNSeV9aMHhvWk9MNm02ZFIxSVB5ZXZjLWQ0YTFLVllvIn0seyJjYW5vbmljYWxfanNvbl9yb290X2hhc2giOiJBRHRmTjZVbFhqYkpuZ3p2TnBlNlpIUUVJRENiMTJpYW00eHlWZ0p4eUtBIiwicGF0aCI6Im1hbmlmZXN0Lmpzb24iLCJyb290X2hhc2giOiJDQU5xQld4VjBMaWdMRF80T3pNR2NJQ0J5RnhMd0hucTF1dC1xdmRFM0VJIn0seyJwYXRoIjoicmVxdWVzdC5qcyIsInJvb3RfaGFzaCI6InRVa1VZMlZrNGFrN184RkpzZmUzVXd4endOXzF4MVRCRVpVNlQ0T2dwTXcifSx7InBhdGgiOiJzd2l0Y2gtYnJvd3Nlci5wbmciLCJyb290X2hhc2giOiJOQkR6NU9LamthOUcxcW9UbUM5V1Zia3p0Mk11Z2dPLS1uRzBCOXpYU244In0seyJwYXRoIjoic3dpdGNoZXJvby5odG1sIiwicm9vdF9oYXNoIjoiWXdNZVJfRXFiOWlFR2VLSGxidE9pbDJMU09ZeTU1azVlb2IzMTA0RHBidyJ9LHsicGF0aCI6InN3aXRjaGVyb28uanMiLCJyb290X2hhc2giOiJaQnhZemZyNUtzTUNDcUlROGFmR3c0cW9DM01keVRHalVfcFNCNEFwQlBzIn1dLCJmb3JtYXQiOiJ0cmVlaGFzaCIsImhhc2hfYmxvY2tfc2l6ZSI6NDA5Nn1dLCJpdGVtX2lkIjoiY25tY2ljbGhuZ2hhbG5wZmhobGVnZ2xkbmlwbGVsYmciLCJpdGVtX3ZlcnNpb24iOiIxLjMzIiwicHJvdG9jb2xfdmVyc2lvbiI6MX0",
6 | "signatures":[
7 | {
8 | "header":{
9 | "kid":"webstore"
10 | },
11 | "protected":"eyJhbGciOiJSUzI1NiJ9",
12 | "signature":"IIl2uDNYbVlOc4cjZ2xWF-VaMh_yF8wq9IvM7PXybzOn_0uq4LbIFAfDRtpF4ejr1gWy14HHOl3X6GH7Lp8W7gClfRsB5fOd4HlQ5U4Yiykr5wcO2utPwbFo8z-Rktd9kyMAA9pZe7fyH48vUX0A7QkVGCGliAm8jOAlNHPW11NB7JDunaeumdQd-OAZS4N4y6v1pQA9xkIaMxv9et6SJsfQjntuyQY5tetEM2pXMzM99G1-mgxX6M8CyBw0M_-Zi5nxrbr_LcCjST79IrcWubQsNbCLtiAayvanOPU0nCFRUz9Phw0RQ6Suk5J1kLZIfPv2MFDFs1JNTdLWhxh_dg"
13 | },
14 | {
15 | "header":{
16 | "kid":"publisher"
17 | },
18 | "protected":"eyJhbGciOiJSUzI1NiJ9",
19 | "signature":"X88H_5SOTEfS2ErcqEfvNBSmRv3xPdFS8Jn-MzDFr9G9xWISEya6CwQCGqifRwWNZZqlCFzjQ5t1iQs1J3-TVxfMp7vxwKscs2wNK_fyi11_y_t_GeNHQSCUg5MFKB15MJ24FnUrFC5pEcmrJmjrw6Eg2pFx96SxuoF_LwIN2mM"
20 | }
21 | ]
22 | }
23 | }
24 | ]
25 |
--------------------------------------------------------------------------------
/src/pages/Home/components/ItemHeaderModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState, useEffect } from 'react'
2 | import { Modal } from 'antd'
3 | import { SchemaForm, createFormActions } from '@formily/antd'
4 | import BaseInput from '../BaseInput'
5 | import AutoComplete from '../AutoComplete'
6 |
7 | const actions = createFormActions()
8 |
9 | const ItemModal = ({ visible, onOk, onCancel, mode, data }) => {
10 | const [formValue, setFormValue] = useState({})
11 |
12 | const onModalOk = useCallback(() => {
13 | actions.validate().then(() => {
14 | onOk(formValue)
15 | })
16 | }, [onOk, formValue])
17 |
18 | useEffect(() => {
19 | visible && setFormValue(data)
20 | }, [visible])
21 |
22 | return (
23 |
30 |
64 |
65 | )
66 | }
67 |
68 | export default ItemModal
69 |
--------------------------------------------------------------------------------
/src/pages/Home/components/ImportModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useState } from 'react'
2 | import { Modal, Input } from 'antd'
3 | import { SchemaForm, createFormActions } from '@formily/antd'
4 |
5 | const actions = createFormActions()
6 |
7 | const ImportModal = ({ visible, onOk, onCancel }) => {
8 | const [formValue, setFormValue] = useState({ json: '' })
9 | const onModalOk = useCallback(() => {
10 | actions.validate().then(() => {
11 | onOk(formValue.json)
12 | })
13 | }, [onOk, formValue])
14 |
15 | useEffect(() => {
16 | visible && setFormValue({ json: '' })
17 | }, [visible])
18 |
19 | return (
20 |
21 | {
24 | setFormValue(newValue)
25 | }}
26 | actions={actions}
27 | components={{ TextArea: Input.TextArea }}
28 | schema={{
29 | type: 'object',
30 | properties: {
31 | json: {
32 | 'type': 'string',
33 | 'title': 'JSON',
34 | 'required': true,
35 | 'x-component': 'TextArea',
36 | 'x-component-props': {
37 | placeholder: '请输入JSON',
38 | autoSize: { minRows: 6 },
39 | },
40 | 'x-rules': [
41 | {
42 | validator(value) {
43 | if (!(value && value.trim().length)) {
44 | return false
45 | }
46 |
47 | try {
48 | const json = JSON.parse(value)
49 | if (json && typeof json === 'object') {
50 | return false
51 | }
52 | return true
53 | } catch (err) {
54 | return true
55 | }
56 | },
57 | message: '请输入JSON格式的数据',
58 | },
59 | ],
60 | },
61 | },
62 | }}
63 | />
64 |
65 | )
66 | }
67 |
68 | export default ImportModal
69 |
--------------------------------------------------------------------------------
/src/pages/Home/components/DataDetail/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useMemo } from 'react'
2 | import { Table, Space, Divider, Checkbox, Empty } from 'antd'
3 | import ButtonGroup from '../ButtonGroup'
4 |
5 | const GroupDetail: FC = ({ group, onAddItem, onExport, onCheckItem, onEditItem, onDeleteItem }) => {
6 | const tableData = useMemo(() => (group ? group.children : []), [group])
7 |
8 | if (!group) {
9 | return (
10 |
15 | )
16 | }
17 |
18 | return (
19 | <>
20 | 组名:{group ? group.name : ''}
21 |
28 | (
36 | {
39 | onCheckItem(record, e.target.checked)
40 | }}
41 | />
42 | ),
43 | },
44 | { title: 'path', key: 'match', dataIndex: 'match' },
45 | {
46 | title: '操作',
47 | width: 120,
48 | key: 'Action',
49 | render(_, record) {
50 | return (
51 | }>
52 | {
54 | onEditItem(record)
55 | }}
56 | >
57 | 编辑
58 |
59 | {
61 | onDeleteItem(record)
62 | }}
63 | >
64 | 删除
65 |
66 |
67 | )
68 | },
69 | },
70 | ]}
71 | dataSource={tableData || []}
72 | size="small"
73 | scroll={{ y: 406 }}
74 | />
75 | >
76 | )
77 | }
78 |
79 | export default GroupDetail
80 |
--------------------------------------------------------------------------------
/src/pages/Home/components/GroupDetail/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useMemo } from 'react'
2 | import { Table, Space, Divider, Checkbox, Empty } from 'antd'
3 | import ButtonGroup from '../ButtonGroup'
4 |
5 | const GroupDetail: FC = ({ group, onAddItem, onExport, onCheckItem, onEditItem, onDeleteItem }) => {
6 | const tableData = useMemo(() => (group ? group.children : []), [group])
7 |
8 | if (!group) {
9 | return (
10 |
15 | )
16 | }
17 |
18 | return (
19 | <>
20 | 组名:{group ? group.name : ''}
21 |
28 | (
36 | {
39 | onCheckItem(record, e.target.checked)
40 | }}
41 | />
42 | ),
43 | },
44 | { title: '源地址', key: 'from', dataIndex: 'from' },
45 | { title: '代理地址', key: 'to', dataIndex: 'to' },
46 | {
47 | title: '操作',
48 | width: 120,
49 | key: 'Action',
50 | render(_, record) {
51 | return (
52 | }>
53 | {
55 | onEditItem(record)
56 | }}
57 | >
58 | 编辑
59 |
60 | {
62 | onDeleteItem(record)
63 | }}
64 | >
65 | 删除
66 |
67 |
68 | )
69 | },
70 | },
71 | ]}
72 | dataSource={tableData || []}
73 | size="small"
74 | scroll={{ y: 406 }}
75 | />
76 | >
77 | )
78 | }
79 |
80 | export default GroupDetail
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "packet-proxy",
3 | "version": "0.0.1",
4 | "description": "chrome插件",
5 | "scripts": {
6 | "dev": "ols dev",
7 | "build": "ols build",
8 | "commit": "git-cz",
9 | "lint:script": "eslint --ext .tsx,.ts,.js,.jsx --fix ./src",
10 | "lint:style": "stylelint --fix 'src/**/*.{less,css}' --syntax less"
11 | },
12 | "husky": {
13 | "hooks": {
14 | "pre-commit": "lint-staged"
15 | }
16 | },
17 | "lint-staged": {
18 | "*.{json,md,yml}": "prettier --write",
19 | "*.{less,css}": [
20 | "yarn lint:style"
21 | ],
22 | "*.{tsx,ts,js,jsx}": [
23 | "yarn lint:script"
24 | ]
25 | },
26 | "sideEffects": [
27 | "dist/*",
28 | "*.less",
29 | "*.css"
30 | ],
31 | "author": "wisestcoder",
32 | "license": "ISC",
33 | "dependencies": {
34 | "@ant-design/icons": "^4.2.2",
35 | "@formily/antd": "^1.3.8",
36 | "antd": "^4.0.2",
37 | "axios": "^0.19.2",
38 | "classnames": "^2.2.6",
39 | "cross-env": "^7.0.2",
40 | "lodash": "^4.17.20",
41 | "query-string": "^6.13.7",
42 | "react": "^16.12.0",
43 | "react-dom": "^16.12.0",
44 | "styled-components": "^5.2.1"
45 | },
46 | "devDependencies": {
47 | "@ols-scripts/cli": "^0.0.2",
48 | "@types/node": "^13.7.4",
49 | "@types/prop-types": "^15.7.1",
50 | "@types/react": "^16.9.22",
51 | "@types/react-dom": "^16.9.5",
52 | "@types/react-redux": "^7.1.9",
53 | "@types/react-router": "^5.1.8",
54 | "@types/react-router-dom": "^5.1.5",
55 | "@types/styled-components": "^5.1.4",
56 | "@typescript-eslint/eslint-plugin": "^2.19.0",
57 | "@typescript-eslint/parser": "^2.19.0",
58 | "babel-runtime": "^6.26.0",
59 | "copy-webpack-plugin": "^6.0.0",
60 | "eslint": "^6.8.0",
61 | "eslint-config-prettier": "^6.0.0",
62 | "eslint-plugin-babel": "^5.3.0",
63 | "eslint-plugin-import": "~2.20.0",
64 | "eslint-plugin-prettier": "^3.1.2",
65 | "eslint-plugin-react": "^7.14.2",
66 | "eslint-plugin-react-hooks": "^3.0.0",
67 | "husky": "^4.2.5",
68 | "lint-staged": "^10.1.3",
69 | "prettier": "^2.0.1",
70 | "stylelint": "^13.0.0",
71 | "stylelint-config-prettier": "^8.0.0",
72 | "stylelint-config-rational-order": "^0.1.2",
73 | "stylelint-config-standard": "^20.0.0",
74 | "stylelint-declaration-block-no-ignored-properties": "^2.1.0",
75 | "stylelint-order": "^4.0.0",
76 | "typescript": "^3.8.2"
77 | },
78 | "browserslist": [
79 | "last 2 versions",
80 | "Firefox ESR",
81 | "> 1%",
82 | "ie >= 11"
83 | ]
84 | }
85 |
--------------------------------------------------------------------------------
/src/pages/Home/components/Group/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useCallback } from 'react'
2 | import { Menu, Dropdown, Modal, Checkbox, Empty, Button } from 'antd'
3 | import { FormOutlined } from '@ant-design/icons'
4 | import './index.less'
5 |
6 | const Group: FC = ({ onEdit, onDelete, onAdd, value, activeKey, onItemClick, onItemCheck }) => {
7 | const onClickDelete = useCallback(
8 | (item) => {
9 | Modal.confirm({
10 | title: '温馨提示',
11 | content: '确认要删除该数据吗?',
12 | onOk() {
13 | onDelete(item)
14 | },
15 | })
16 | },
17 | [onDelete],
18 | )
19 |
20 | const onOperator = useCallback(
21 | (e, item) => {
22 | e.domEvent.preventDefault()
23 | if (e.key === 'edit') {
24 | onEdit(item)
25 | }
26 | if (e.key === 'delete') {
27 | onClickDelete(item)
28 | }
29 | },
30 | [onEdit, onClickDelete],
31 | )
32 |
33 | if (!(value && value.length)) {
34 | return (
35 | 当前无分组,请先创建}
42 | >
43 |
46 |
47 | )
48 | }
49 |
50 | return (
51 |
82 | }
83 | >
84 |
85 |
86 |
87 | )
88 | })}
89 |
90 | )
91 | }
92 |
93 | export default Group
94 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const isProduction = process.env.NODE_ENV === 'production';
2 |
3 | module.exports = {
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react-hooks/recommended',
8 | 'plugin:import/typescript',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'plugin:prettier/recommended',
11 | ],
12 | parserOptions: {
13 | ecmaVersion: 6,
14 | sourceType: 'module',
15 | ecmaFeatures: {
16 | modules: true,
17 | },
18 | },
19 | globals: {
20 | document: true,
21 | localStorage: true,
22 | window: true,
23 | },
24 | env: {
25 | browser: true,
26 | node: true,
27 | es6: true,
28 | },
29 | settings: {
30 | react: {
31 | version: 'detect',
32 | },
33 | },
34 | parser: '@typescript-eslint/parser',
35 | plugins: ['babel', '@typescript-eslint'],
36 | rules: {
37 | 'prettier/prettier': 0,
38 | 'default-case': 2,
39 | 'linebreak-style': [2, 'unix'],
40 | 'no-useless-escape': 2,
41 | 'key-spacing': [
42 | 2,
43 | {
44 | beforeColon: false,
45 | afterColon: true,
46 | },
47 | ],
48 | 'semi-spacing': [
49 | 2,
50 | {
51 | before: false,
52 | after: true,
53 | },
54 | ],
55 | 'comma-spacing': [
56 | 2,
57 | {
58 | before: false,
59 | after: true,
60 | },
61 | ],
62 | 'space-unary-ops': [
63 | 2,
64 | {
65 | words: true,
66 | nonwords: false,
67 | },
68 | ],
69 | 'space-infix-ops': [
70 | 2,
71 | {
72 | int32Hint: false,
73 | },
74 | ],
75 | 'no-mixed-spaces-and-tabs': 0,
76 | 'object-curly-spacing': [2, 'always'],
77 | 'no-duplicate-imports': 2,
78 | 'standard/no-callback-literal': 0,
79 | // 'no-cond-assign': [2, 'always'],
80 | 'no-console': isProduction ? 2 : 0,
81 | 'no-debugger': isProduction ? 2 : 0,
82 | // 'spaced-comment': [2, 'always'],
83 | // 'no-constant-condition': 2,
84 | // 'no-dupe-args': 2,
85 | // 'no-dupe-keys': 2,
86 | // 'no-duplicate-case': 2,
87 | 'no-empty': 0,
88 | // 'no-empty-character-class': 2,
89 | // 'no-control-regex': 2,
90 | // 'no-regex-spaces': 2,
91 | // 'no-ex-assign': 2,
92 | // 'no-extra-boolean-cast': 2,
93 | // 'no-func-assign': 2,
94 | // 'no-invalid-regexp': 2,
95 | // 'no-irregular-whitespace': 2,
96 | // 'no-negated-in-lhs': 2,
97 | // 'no-obj-calls': 2,
98 | // 'no-sparse-arrays': 2,
99 | // 'no-unexpected-multiline': 2,
100 | // 'no-unreachable': 2,
101 | // 'use-isnan': 2,
102 | 'valid-jsdoc': 0,
103 | // 'valid-typeof': 2,
104 | // 'accessor-pairs': 2,
105 | // eqeqeq: 2,
106 | 'no-case-declarations': 0,
107 | 'no-div-regex': 0,
108 | // 'no-empty-pattern': 2,
109 | // 'no-eq-null': 1,
110 | 'no-eval': 0,
111 | // 'no-extra-bind': 2,
112 | // 'no-fallthrough': 2,
113 | // 'no-floating-decimal': 2,
114 | 'no-implied-eval': 0,
115 | // 'no-invalid-this': 2,
116 | // 'no-iterator': 2,
117 | // 'no-labels': 2,
118 | // 'no-lone-blocks': 2,
119 | 'no-loop-func': 0,
120 | // 'no-multi-str': 2,
121 | // 'no-native-reassign': 2,
122 | // 'no-redeclare': 2,
123 | // 'no-self-assign': 2,
124 | // 'no-self-compare': 2,
125 | // 'no-sequences': 2,
126 | // 'no-throw-literal': 2,
127 | // 'no-unmodified-loop-condition': 2,
128 | // 'no-unused-labels': 2,
129 | // 'no-useless-call': 2,
130 | // 'no-useless-concat': 2,
131 | // 'no-with': 2,
132 | radix: 0,
133 | // 'wrap-iife': [2, 'any'],
134 | // yoda: [2, 'never'],
135 | // 'no-delete-var': 2,
136 | // 'no-label-var': 2,
137 | // 'no-shadow-restricted-names': 2,
138 | 'no-undef': 0,
139 | // 'no-undef-init': 2,
140 | // 'no-unneeded-ternary': 2,
141 | // // 为了避免和 @typescript-eslint/no-unused-vars 冲突
142 | 'no-unused-vars': 0,
143 | 'react/no-multi-comp': 0,
144 | // 'react/no-direct-mutation-state': 2,
145 | // 'react/react-in-jsx-scope': 2,
146 | // 'react/sort-comp': 2,
147 | // 'react/no-deprecated': 1,
148 | 'react/display-name': 0,
149 | // 'react/no-find-dom-node': 2,
150 | // 'react/no-unescaped-entities': 2,
151 | // 'react/no-string-refs': 2,
152 | // 'react/no-is-mounted': 2,
153 | // 'react/no-children-prop': 2,
154 | 'react/prop-types': 0,
155 | // 'react/boolean-prop-naming': 2,
156 | 'react/button-has-type': 0,
157 | // 'react/default-props-match-prop-types': 2,
158 | // 'react/no-access-state-in-setstate': 2,
159 | // 'react/no-danger-with-children': 2,
160 | // 'react/no-render-return-value': 2,
161 | 'react/no-set-state': 0,
162 | 'react/no-this-in-sfc': 0,
163 | // 'react/no-unknown-property': 2,
164 | // 'react/no-unsafe': 2,
165 | 'react/no-unused-state': 0,
166 | 'react/prefer-es6-class': 0,
167 | 'react/require-default-props': 0,
168 | // 'react/require-render-return': 2,
169 | // 'react/self-closing-comp': 2,
170 | // 'react/style-prop-object': 2,
171 | // 'react/void-dom-elements-no-children': 2,
172 | // 'react/jsx-curly-spacing': 2,
173 | // 'react/jsx-equals-spacing': 2,
174 | // 'react/jsx-key': 2,
175 | // 'react/jsx-no-target-blank': 2,
176 | // 'react/jsx-no-duplicate-props': 2,
177 | // 'react/jsx-props-no-multi-spaces': 2,
178 | // 'react/jsx-uses-react': 2,
179 | // 'react/jsx-tag-spacing': 2,
180 | // 'react/jsx-uses-vars': 2,
181 | // 'react/jsx-wrap-multilines': 2,
182 | // 'react/no-did-update-set-state': 2,
183 | '@typescript-eslint/explicit-function-return-type': 0,
184 | '@typescript-eslint/no-var-requires': 0,
185 | '@typescript-eslint/interface-name-prefix': 0,
186 | '@typescript-eslint/no-use-before-define': 0,
187 | 'react-hooks/exhaustive-deps': 0,
188 | 'space-infix-ops': 0,
189 | '@typescript-eslint/no-explicit-any': 0,
190 | '@typescript-eslint/ban-ts-ignore': 0,
191 | '@typescript-eslint/no-inferrable-types': 0,
192 | 'react/jsx-no-target-blank': 0,
193 | '@typescript-eslint/no-empty-function': 0
194 | },
195 | };
196 |
--------------------------------------------------------------------------------
/src/assets/request.js:
--------------------------------------------------------------------------------
1 | let lastRequestId
2 |
3 | // 获取localStorage
4 | function getLocalStorage(key, defaultValue) {
5 | if (key) {
6 | let result
7 | try {
8 | result = JSON.parse(localStorage[key])
9 | } catch (error) {
10 | result = defaultValue
11 | }
12 |
13 | return result
14 | }
15 |
16 | return localStorage
17 | }
18 |
19 | // 设置localStorage
20 | function setLocalStorage(key, value) {
21 | localStorage[key] = JSON.stringify(value)
22 | }
23 |
24 | function getChromeVersion() {
25 | const pieces = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/)
26 | if (pieces == null || pieces.length !== 5) {
27 | return {}
28 | }
29 | const version = pieces.map((piece) => parseInt(piece, 10))
30 | return {
31 | major: version[1],
32 | minor: version[2],
33 | build: version[3],
34 | patch: version[4],
35 | }
36 | }
37 |
38 | function getRequiresExtraRequestHeaders() {
39 | let requiresExtraRequestHeaders = false
40 | if (getChromeVersion().major >= 72) {
41 | requiresExtraRequestHeaders = true
42 | }
43 |
44 | return requiresExtraRequestHeaders
45 | }
46 |
47 | // 监听请求发送前,做重定向
48 | chrome.webRequest.onBeforeRequest.addListener(
49 | (details) => {
50 | const groups = getLocalStorage('groups', [])
51 | const onUseGroups = groups.filter((item) => item.checked)
52 | const flatternArr = []
53 | onUseGroups.forEach((item) => {
54 | if (item.checked && item.children && item.children.length) {
55 | item.children.forEach((x) => {
56 | flatternArr.push(x)
57 | })
58 | }
59 | })
60 | // eslint-disable-next-line @typescript-eslint/prefer-for-of
61 | for (let i = 0; i < flatternArr.length; i += 1) {
62 | const rule = flatternArr[i]
63 | if (rule.checked && details.url.indexOf(rule.from) > -1 && details.requestId !== lastRequestId) {
64 | lastRequestId = details.requestId
65 | return {
66 | redirectUrl: details.url.replace(rule.from, rule.to),
67 | }
68 | }
69 | }
70 | },
71 | {
72 | urls: [''],
73 | },
74 | ['blocking'],
75 | )
76 |
77 | // 监听请求,修改请求Headers
78 | chrome.webRequest.onBeforeSendHeaders.addListener(
79 | (details) => {
80 | const headers = getLocalStorage('headers', [])
81 | const onUseHeaders = headers.filter((item) => item.checked)
82 | const requestHeaders = details.requestHeaders || []
83 | onUseHeaders.forEach(({ name, value }) => {
84 | requestHeaders.push({
85 | name,
86 | value,
87 | })
88 | })
89 |
90 | return { requestHeaders }
91 | },
92 | {
93 | urls: [''],
94 | },
95 | getRequiresExtraRequestHeaders()
96 | ? ['requestHeaders', 'blocking', 'extraHeaders']
97 | : ['requestHeaders', 'blocking'],
98 | )
99 |
100 | // 监听页面载入,设置cookie
101 | chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
102 | if (changeInfo.status === 'complete') {
103 | const cookies = getLocalStorage('cookies', [])
104 | const onUseCookies = cookies.filter((item) => item.checked)
105 |
106 | onUseCookies.forEach((item) => {
107 | const targetDomin = item.to.replace(/^(?:https?:\/\/)?/i, '').split('/')[0]
108 | const targetReg = new RegExp(`^https?://${targetDomin}/`)
109 | const currentUrl = tab.url || null
110 |
111 | if (currentUrl && targetReg.test(currentUrl)) {
112 | chrome.cookies.getAll(
113 | {
114 | url: item.from,
115 | },
116 | (cookieL = []) => {
117 | cookieL.forEach(({ name, value }) => {
118 | chrome.cookies.set({
119 | url: currentUrl,
120 | name,
121 | value,
122 | path: '/',
123 | })
124 | })
125 | },
126 | )
127 | }
128 | })
129 | }
130 | })
131 |
132 | // 监听扩展程序的页面发送的请求体
133 | chrome.extension.onMessage.addListener((request, sender, sendResponse) => {
134 | switch (request.type) {
135 | case 'getGroups':
136 | sendResponse({
137 | data: getLocalStorage('groups', []),
138 | isSuccess: true,
139 | })
140 | break
141 | case 'setGroups':
142 | setLocalStorage('groups', request.data)
143 | sendResponse({
144 | data: getLocalStorage('groups', []),
145 | isSuccess: true,
146 | })
147 | break
148 | case 'getCookies':
149 | sendResponse({
150 | data: getLocalStorage('cookies', []),
151 | isSuccess: true,
152 | })
153 | break
154 | case 'setCookies':
155 | setLocalStorage('cookies', request.data)
156 | sendResponse({
157 | data: getLocalStorage('cookies', []),
158 | isSuccess: true,
159 | })
160 | break
161 | case 'getHeaders':
162 | sendResponse({
163 | data: getLocalStorage('headers', []),
164 | isSuccess: true,
165 | })
166 | break
167 | case 'setHeaders':
168 | setLocalStorage('headers', request.data)
169 | sendResponse({
170 | data: getLocalStorage('headers', []),
171 | isSuccess: true,
172 | })
173 | break
174 | case 'getCode':
175 | sendResponse({
176 | data: getLocalStorage('code', ''),
177 | isSuccess: true,
178 | })
179 | break
180 | case 'setCode':
181 | setLocalStorage('code', request.data)
182 | execCoder(request.data)
183 | sendResponse({
184 | data: getLocalStorage('code', ''),
185 | isSuccess: true,
186 | })
187 | break
188 | default:
189 | sendResponse({
190 | isSuccess: false,
191 | error: `Unknow request type: ${request.type}`,
192 | })
193 | }
194 | })
195 |
196 | // 执行coder
197 | function execCoder(code = getLocalStorage('code', '')) {
198 | try {
199 | code && eval(code)
200 | } catch (error) {
201 | Promise.reject(error)
202 | }
203 | }
204 |
205 | execCoder()
206 |
--------------------------------------------------------------------------------
/src/pages/Home/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useCallback, useState, useEffect, useRef } from 'react'
2 | import { Table, Space, Divider, Checkbox, message, Modal } from 'antd'
3 | import cloneDeep from 'lodash/cloneDeep'
4 | import isObject from 'lodash/isObject'
5 | import isArray from 'lodash/isArray'
6 | import ButtonGroup from '../components/ButtonGroup'
7 | import ImportModal from '../components/ImportModal'
8 | import ExportModal from '../components/ExportModal'
9 | import ItemModal from '../components/ItemHeaderModal'
10 |
11 | import './index.less'
12 |
13 | interface CookieProp {
14 | uniqueKey: React.Key;
15 | name: string;
16 | value: string;
17 | checked?: boolean;
18 | }
19 |
20 | const GroupDetail: FC = () => {
21 | const [headers, setHeaders] = useState([])
22 | const [cookieModalVisible, setCookieModalVisible] = useState(false)
23 | const [importModalVisible, setImportModalVisible] = useState(false)
24 | const [exportModalVisible, setExportModalVisible] = useState(false)
25 | const cookieModalRef = useRef({
26 | mode: 'add',
27 | data: {},
28 | })
29 |
30 | const setChromeCookie = useCallback((newHeaders, fn = () => {}) => {
31 | chrome.extension.sendMessage(
32 | {
33 | type: 'setHeaders',
34 | data: newHeaders,
35 | },
36 | (response) => {
37 | if (response.isSuccess) {
38 | fn()
39 | setHeaders(response.data)
40 | } else {
41 | message.error('添加失败!')
42 | }
43 | },
44 | )
45 | }, [])
46 |
47 | const onAddItem = useCallback(() => {
48 | setCookieModalVisible(true)
49 | cookieModalRef.current = {
50 | mode: 'add',
51 | data: {
52 | uniqueKey: Math.random().toString().slice(2),
53 | },
54 | }
55 | }, [])
56 |
57 | // 导入
58 | const onImport = useCallback(() => {
59 | setImportModalVisible(true)
60 | }, [])
61 |
62 | const onExport = useCallback(() => {
63 | setExportModalVisible(true)
64 | }, [])
65 |
66 | const onCheckItem = useCallback(
67 | (record, checked) => {
68 | const newHeaders = cloneDeep(headers)
69 | const groupIndex = newHeaders.findIndex((x) => x.uniqueKey === record.uniqueKey)
70 | newHeaders[groupIndex].checked = checked
71 |
72 | setChromeCookie(newHeaders)
73 | },
74 | [headers, setChromeCookie],
75 | )
76 |
77 | const onEditItem = useCallback((record) => {
78 | setCookieModalVisible(true)
79 | cookieModalRef.current = {
80 | mode: 'edit',
81 | data: record,
82 | }
83 | }, [])
84 |
85 | const onDeleteItem = useCallback(
86 | (item) => {
87 | Modal.confirm({
88 | title: '温馨提示',
89 | content: '确认要删除该数据吗?',
90 | onOk() {
91 | const newGroups = headers.filter((x: any) => x.uniqueKey !== item.uniqueKey)
92 |
93 | setChromeCookie(newGroups)
94 | },
95 | })
96 | },
97 | [headers, setChromeCookie],
98 | )
99 |
100 | useEffect(() => {
101 | chrome.extension.sendMessage(
102 | {
103 | type: 'getHeaders',
104 | },
105 | (response) => {
106 | if (response.isSuccess) {
107 | setHeaders(response.data)
108 | }
109 | },
110 | )
111 | }, [])
112 |
113 | return (
114 | <>
115 |
122 | (
131 | {
134 | onCheckItem(record, e.target.checked)
135 | }}
136 | />
137 | ),
138 | },
139 | { title: 'name', key: 'name', dataIndex: 'name' },
140 | { title: 'value', key: 'value', dataIndex: 'value' },
141 | {
142 | title: '操作',
143 | width: 120,
144 | key: 'Action',
145 | render(_, record) {
146 | return (
147 | }>
148 | {
150 | onEditItem(record)
151 | }}
152 | >
153 | 编辑
154 |
155 | {
157 | onDeleteItem(record)
158 | }}
159 | >
160 | 删除
161 |
162 |
163 | )
164 | },
165 | },
166 | ]}
167 | dataSource={headers || []}
168 | size="small"
169 | scroll={{ y: 406 }}
170 | />
171 | {
174 | let newHeaders = cloneDeep(headers)
175 | const json = JSON.parse(newJson)
176 | if (isArray(json)) {
177 | newHeaders = [...newHeaders, ...json]
178 | } else if (isObject(json)) {
179 | // @ts-ignore
180 | newHeaders.push(json)
181 | }
182 |
183 | setChromeCookie(newHeaders, () => {
184 | setImportModalVisible(false)
185 | })
186 | }}
187 | onCancel={() => {
188 | setImportModalVisible(false)
189 | }}
190 | />
191 | {
195 | setExportModalVisible(false)
196 | }}
197 | />
198 | {
203 | let newHeaders = cloneDeep(headers)
204 | if (cookieModalRef.current.mode === 'add') {
205 | newHeaders = [...newHeaders, newItem]
206 | } else {
207 | const findIndex = newHeaders.findIndex((x) => x.uniqueKey === newItem.uniqueKey)
208 | newHeaders[findIndex] = newItem
209 | }
210 |
211 | setChromeCookie(newHeaders, () => {
212 | setCookieModalVisible(false)
213 | })
214 | }}
215 | onCancel={() => {
216 | setCookieModalVisible(false)
217 | }}
218 | />
219 | >
220 | )
221 | }
222 |
223 | export default GroupDetail
224 |
--------------------------------------------------------------------------------
/src/pages/Home/Cookie/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useCallback, useState, useEffect, useRef } from 'react'
2 | import { Table, Space, Divider, Checkbox, message, Modal } from 'antd'
3 | import cloneDeep from 'lodash/cloneDeep'
4 | import isObject from 'lodash/isObject'
5 | import isArray from 'lodash/isArray'
6 | import ButtonGroup from '../components/ButtonGroup'
7 | import ImportModal from '../components/ImportModal'
8 | import ExportModal from '../components/ExportModal'
9 | import ItemModal from '../components/ItemModal'
10 |
11 | import './index.less'
12 |
13 | interface CookieProp {
14 | uniqueKey: React.Key;
15 | from: string;
16 | to: string;
17 | checked?: boolean;
18 | }
19 |
20 | const GroupDetail: FC = () => {
21 | const [cookies, setCookies] = useState([])
22 | const [cookieModalVisible, setCookieModalVisible] = useState(false)
23 | const [importModalVisible, setImportModalVisible] = useState(false)
24 | const [exportModalVisible, setExportModalVisible] = useState(false)
25 | const cookieModalRef = useRef({
26 | mode: 'add',
27 | data: {},
28 | })
29 |
30 | const setChromeCookie = useCallback((newCookies, fn = () => {}) => {
31 | chrome.extension.sendMessage(
32 | {
33 | type: 'setCookies',
34 | data: newCookies,
35 | },
36 | (response) => {
37 | if (response.isSuccess) {
38 | fn()
39 | setCookies(response.data)
40 | } else {
41 | message.error('添加失败!')
42 | }
43 | },
44 | )
45 | }, [])
46 |
47 | const onAddItem = useCallback(() => {
48 | setCookieModalVisible(true)
49 | cookieModalRef.current = {
50 | mode: 'add',
51 | data: {
52 | uniqueKey: Math.random().toString().slice(2),
53 | },
54 | }
55 | }, [])
56 |
57 | // 导入
58 | const onImport = useCallback(() => {
59 | setImportModalVisible(true)
60 | }, [])
61 |
62 | const onExport = useCallback(() => {
63 | setExportModalVisible(true)
64 | }, [])
65 |
66 | const onCheckItem = useCallback(
67 | (record, checked) => {
68 | const newCookies = cloneDeep(cookies)
69 | const groupIndex = newCookies.findIndex((x) => x.uniqueKey === record.uniqueKey)
70 | newCookies[groupIndex].checked = checked
71 |
72 | setChromeCookie(newCookies)
73 | },
74 | [cookies, setChromeCookie],
75 | )
76 |
77 | const onEditItem = useCallback((record) => {
78 | setCookieModalVisible(true)
79 | cookieModalRef.current = {
80 | mode: 'edit',
81 | data: record,
82 | }
83 | }, [])
84 |
85 | const onDeleteItem = useCallback(
86 | (item) => {
87 | Modal.confirm({
88 | title: '温馨提示',
89 | content: '确认要删除该数据吗?',
90 | onOk() {
91 | const newGroups = cookies.filter((x: any) => x.uniqueKey !== item.uniqueKey)
92 |
93 | setChromeCookie(newGroups)
94 | },
95 | })
96 | },
97 | [cookies, setChromeCookie],
98 | )
99 |
100 | useEffect(() => {
101 | chrome.extension.sendMessage(
102 | {
103 | type: 'getCookies',
104 | },
105 | function (response) {
106 | if (response.isSuccess) {
107 | setCookies(response.data)
108 | }
109 | },
110 | )
111 | }, [])
112 |
113 | return (
114 | <>
115 |
122 | (
131 | {
134 | onCheckItem(record, e.target.checked)
135 | }}
136 | />
137 | ),
138 | },
139 | { title: '源地址', key: 'from', dataIndex: 'from' },
140 | { title: '代理地址', key: 'to', dataIndex: 'to' },
141 | {
142 | title: '操作',
143 | width: 120,
144 | key: 'Action',
145 | render(_, record) {
146 | return (
147 | }>
148 | {
150 | onEditItem(record)
151 | }}
152 | >
153 | 编辑
154 |
155 | {
157 | onDeleteItem(record)
158 | }}
159 | >
160 | 删除
161 |
162 |
163 | )
164 | },
165 | },
166 | ]}
167 | dataSource={cookies || []}
168 | size="small"
169 | scroll={{ y: 406 }}
170 | />
171 | {
174 | let newCookies = cloneDeep(cookies)
175 | const json = JSON.parse(newJson)
176 | console.log('json', json)
177 | if (isArray(json)) {
178 | newCookies = [...newCookies, ...json]
179 | } else if (isObject(json)) {
180 | // @ts-ignore
181 | newCookies.push(json)
182 | }
183 |
184 | setChromeCookie(newCookies, () => {
185 | setImportModalVisible(false)
186 | })
187 | }}
188 | onCancel={() => {
189 | setImportModalVisible(false)
190 | }}
191 | />
192 | {
196 | setExportModalVisible(false)
197 | }}
198 | />
199 | {
204 | let newCookies = cloneDeep(cookies)
205 | if (cookieModalRef.current.mode === 'add') {
206 | newCookies = [...newCookies, newItem]
207 | } else {
208 | const findIndex = newCookies.findIndex((x) => x.uniqueKey === newItem.uniqueKey)
209 | newCookies[findIndex] = newItem
210 | }
211 |
212 | setChromeCookie(newCookies, () => {
213 | setCookieModalVisible(false)
214 | })
215 | }}
216 | onCancel={() => {
217 | setCookieModalVisible(false)
218 | }}
219 | />
220 | >
221 | )
222 | }
223 |
224 | export default GroupDetail
225 |
--------------------------------------------------------------------------------
/src/styles/normalize.less:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | margin: 0.67em 0;
42 | font-size: 2em;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-size: 1em; /* 2 */
66 | font-family: monospace; /* 1 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | text-decoration: underline; /* 2 */
87 | text-decoration: underline dotted; /* 2 */
88 | border-bottom: none; /* 1 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-size: 1em; /* 2 */
109 | font-family: monospace; /* 1 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | position: relative;
128 | font-size: 75%;
129 | line-height: 0;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | margin: 0; /* 2 */
166 | font-size: 100%; /* 1 */
167 | font-family: inherit; /* 1 */
168 | line-height: 1.15; /* 1 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | padding: 0;
211 | border-style: none;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | display: table; /* 1 */
242 | box-sizing: border-box; /* 1 */
243 | max-width: 100%; /* 1 */
244 | padding: 0; /* 3 */
245 | color: inherit; /* 2 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | outline-offset: -2px; /* 2 */
292 | -webkit-appearance: textfield; /* 1 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | font: inherit; /* 2 */
310 | -webkit-appearance: button; /* 1 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------
/src/pages/Home/components/AutoComplete/HEADER_OPTIONS.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | 'A-IM',
3 | 'Accept',
4 | 'Accept-Additions',
5 | 'Accept-CH',
6 | 'Accept-Charset',
7 | 'Accept-Datetime',
8 | 'Accept-Encoding',
9 | 'Accept-Features',
10 | 'Accept-Language',
11 | 'Accept-Patch',
12 | 'Accept-Post',
13 | 'Accept-Ranges',
14 | 'Age',
15 | 'Allow',
16 | 'ALPN',
17 | 'Also-Control',
18 | 'Alt-Svc',
19 | 'Alt-Used',
20 | 'Alternate-Recipient',
21 | 'Alternates',
22 | 'Apply-To-Redirect-Ref',
23 | 'Approved',
24 | 'ARC-Authentication-Results',
25 | 'ARC-Message-Signature',
26 | 'ARC-Seal',
27 | 'Archive',
28 | 'Archived-At',
29 | 'Article-Names',
30 | 'Article-Updates',
31 | 'Authentication-Control',
32 | 'Authentication-Info',
33 | 'Authentication-Results',
34 | 'Authorization',
35 | 'Auto-Submitted',
36 | 'Autoforwarded',
37 | 'Autosubmitted',
38 | 'Base',
39 | 'Bcc',
40 | 'Body',
41 | 'C-Ext',
42 | 'C-Man',
43 | 'C-Opt',
44 | 'C-PEP',
45 | 'C-PEP-Info',
46 | 'Cache-Control',
47 | 'Cal-Managed-ID',
48 | 'CalDAV-Timezones',
49 | 'Cancel-Key',
50 | 'Cancel-Lock',
51 | 'Cc',
52 | 'CDN-Loop',
53 | 'Cert-Not-After',
54 | 'Cert-Not-Before',
55 | 'Close',
56 | 'Comments',
57 | 'Connection',
58 | 'Content-Alternative',
59 | 'Content-Base',
60 | 'Content-Description',
61 | 'Content-Disposition',
62 | 'Content-Duration',
63 | 'Content-Encoding',
64 | 'Content-features',
65 | 'Content-ID',
66 | 'Content-Identifier',
67 | 'Content-Language',
68 | 'Content-Length',
69 | 'Content-Location',
70 | 'Content-MD5',
71 | 'Content-Range',
72 | 'Content-Return',
73 | 'Content-Script-Type',
74 | 'Content-Style-Type',
75 | 'Content-Transfer-Encoding',
76 | 'Content-Translation-Type',
77 | 'Content-Type',
78 | 'Content-Version',
79 | 'Control',
80 | 'Conversion',
81 | 'Conversion-With-Loss',
82 | 'Cookie',
83 | 'Cookie2',
84 | 'DASL',
85 | 'DAV',
86 | 'DL-Expansion-History',
87 | 'Date',
88 | 'Date-Received',
89 | 'Default-Style',
90 | 'Deferred-Delivery',
91 | 'Delivery-Date',
92 | 'Delta-Base',
93 | 'Depth',
94 | 'Derived-From',
95 | 'Destination',
96 | 'Differential-ID',
97 | 'Digest',
98 | 'Discarded-X400-IPMS-Extensions',
99 | 'Discarded-X400-MTS-Extensions',
100 | 'Disclose-Recipients',
101 | 'Disposition-Notification-Options',
102 | 'Disposition-Notification-To',
103 | 'Distribution',
104 | 'DKIM-Signature',
105 | 'Downgraded-Bcc',
106 | 'Downgraded-Cc',
107 | 'Downgraded-Disposition-Notification-To',
108 | 'Downgraded-Final-Recipient',
109 | 'Downgraded-From',
110 | 'Downgraded-In-Reply-To',
111 | 'Downgraded-Mail-From',
112 | 'Downgraded-Message-Id',
113 | 'Downgraded-Original-Recipient',
114 | 'Downgraded-Rcpt-To',
115 | 'Downgraded-References',
116 | 'Downgraded-Reply-To',
117 | 'Downgraded-Resent-Bcc',
118 | 'Downgraded-Resent-Cc',
119 | 'Downgraded-Resent-From',
120 | 'Downgraded-Resent-Reply-To',
121 | 'Downgraded-Resent-Sender',
122 | 'Downgraded-Resent-To',
123 | 'Downgraded-Return-Path',
124 | 'Downgraded-Sender',
125 | 'Downgraded-To',
126 | 'Early-Data',
127 | 'Encoding',
128 | 'Encrypted',
129 | 'ETag',
130 | 'Expect',
131 | 'Expect-CT',
132 | 'Expires',
133 | 'Expiry-Date',
134 | 'Ext',
135 | 'Followup-To',
136 | 'Forwarded',
137 | 'From',
138 | 'Generate-Delivery-Report',
139 | 'GetProfile',
140 | 'Hobareg',
141 | 'Host',
142 | 'HTTP2-Settings',
143 | 'IM',
144 | 'If',
145 | 'If-Match',
146 | 'If-Modified-Since',
147 | 'If-None-Match',
148 | 'If-Range',
149 | 'If-Schedule-Tag-Match',
150 | 'If-Unmodified-Since',
151 | 'Importance',
152 | 'In-Reply-To',
153 | 'Include-Referred-Token-Binding-ID',
154 | 'Incomplete-Copy',
155 | 'Injection-Date',
156 | 'Injection-Info',
157 | 'Keep-Alive',
158 | 'Keywords',
159 | 'Label',
160 | 'Language',
161 | 'Last-Modified',
162 | 'Latest-Delivery-Time',
163 | 'Lines',
164 | 'Link',
165 | 'List-Archive',
166 | 'List-Help',
167 | 'List-ID',
168 | 'List-Owner',
169 | 'List-Post',
170 | 'List-Subscribe',
171 | 'List-Unsubscribe',
172 | 'List-Unsubscribe-Post',
173 | 'Location',
174 | 'Lock-Token',
175 | 'Man',
176 | 'Max-Forwards',
177 | 'Memento-Datetime',
178 | 'Message-Context',
179 | 'Message-ID',
180 | 'Message-Type',
181 | 'Meter',
182 | 'MIME-Version',
183 | 'MMHS-Exempted-Address',
184 | 'MMHS-Extended-Authorisation-Info',
185 | 'MMHS-Subject-Indicator-Codes',
186 | 'MMHS-Handling-Instructions',
187 | 'MMHS-Message-Instructions',
188 | 'MMHS-Codress-Message-Indicator',
189 | 'MMHS-Originator-Reference',
190 | 'MMHS-Primary-Precedence',
191 | 'MMHS-Copy-Precedence',
192 | 'MMHS-Message-Type',
193 | 'MMHS-Other-Recipients-Indicator-To',
194 | 'MMHS-Other-Recipients-Indicator-CC',
195 | 'MMHS-Acp127-Message-Identifier',
196 | 'MMHS-Originator-PLAD',
197 | 'MT-Priority',
198 | 'Negotiate',
199 | 'Newsgroups',
200 | 'NNTP-Posting-Date',
201 | 'NNTP-Posting-Host',
202 | 'Obsoletes',
203 | 'OData-EntityId',
204 | 'OData-Isolation',
205 | 'OData-MaxVersion',
206 | 'OData-Version',
207 | 'Opt',
208 | 'Optional-WWW-Authenticate',
209 | 'Ordering-Type',
210 | 'Organization',
211 | 'Origin',
212 | 'Original-Encoded-Information-Types',
213 | 'Original-From',
214 | 'Original-Message-ID',
215 | 'Original-Recipient',
216 | 'Original-Sender',
217 | 'Originator-Return-Address',
218 | 'Original-Subject',
219 | 'OSCORE',
220 | 'Overwrite',
221 | 'P3P',
222 | 'Path',
223 | 'PEP',
224 | 'PICS-Label',
225 | 'Pep-Info',
226 | 'Position',
227 | 'Posting-Version',
228 | 'Pragma',
229 | 'Prefer',
230 | 'Preference-Applied',
231 | 'Prevent-NonDelivery-Report',
232 | 'Priority',
233 | 'ProfileObject',
234 | 'Protocol',
235 | 'Protocol-Info',
236 | 'Protocol-Query',
237 | 'Protocol-Request',
238 | 'Proxy-Authenticate',
239 | 'Proxy-Authentication-Info',
240 | 'Proxy-Authorization',
241 | 'Proxy-Features',
242 | 'Proxy-Instruction',
243 | 'Public',
244 | 'Public-Key-Pins',
245 | 'Public-Key-Pins-Report-Only',
246 | 'Range',
247 | 'Received',
248 | 'Received-SPF',
249 | 'Redirect-Ref',
250 | 'References',
251 | 'Referer',
252 | 'Relay-Version',
253 | 'Replay-Nonce',
254 | 'Reply-By',
255 | 'Reply-To',
256 | 'Require-Recipient-Valid-Since',
257 | 'Resent-Bcc',
258 | 'Resent-Cc',
259 | 'Resent-Date',
260 | 'Resent-From',
261 | 'Resent-Message-ID',
262 | 'Resent-Reply-To',
263 | 'Resent-Sender',
264 | 'Resent-To',
265 | 'Retry-After',
266 | 'Return-Path',
267 | 'Safe',
268 | 'Schedule-Reply',
269 | 'Schedule-Tag',
270 | 'Sec-Token-Binding',
271 | 'Sec-WebSocket-Accept',
272 | 'Sec-WebSocket-Extensions',
273 | 'Sec-WebSocket-Key',
274 | 'Sec-WebSocket-Protocol',
275 | 'Sec-WebSocket-Version',
276 | 'Security-Scheme',
277 | 'See-Also',
278 | 'Sender',
279 | 'Sensitivity',
280 | 'Server',
281 | 'Set-Cookie',
282 | 'Set-Cookie2',
283 | 'SetProfile',
284 | 'SLUG',
285 | 'SoapAction',
286 | 'Solicitation',
287 | 'Status-URI',
288 | 'Strict-Transport-Security',
289 | 'Subject',
290 | 'Summary',
291 | 'Sunset',
292 | 'Supersedes',
293 | 'Surrogate-Capability',
294 | 'Surrogate-Control',
295 | 'TCN',
296 | 'TE',
297 | 'Timeout',
298 | 'TLS-Report-Domain',
299 | 'TLS-Report-Submitter',
300 | 'TLS-Required',
301 | 'To',
302 | 'Topic',
303 | 'Trailer',
304 | 'Transfer-Encoding',
305 | 'TTL',
306 | 'Urgency',
307 | 'URI',
308 | 'Upgrade',
309 | 'User-Agent',
310 | 'Variant-Vary',
311 | 'Vary',
312 | 'VBR-Info',
313 | 'Via',
314 | 'WWW-Authenticate',
315 | 'Want-Digest',
316 | 'Warning',
317 | 'X400-Content-Identifier',
318 | 'X400-Content-Return',
319 | 'X400-Content-Type',
320 | 'X400-MTS-Identifier',
321 | 'X400-Originator',
322 | 'X400-Received',
323 | 'X400-Recipients',
324 | 'X400-Trace',
325 | 'X-Content-Type-Options',
326 | 'X-Frame-Options',
327 | 'Xref',
328 | 'Upgrade-Insecure-Requests',
329 | 'X-Requested-With',
330 | 'DNT',
331 | 'X-Forwarded-For',
332 | 'X-Forwarded-Host',
333 | 'X-Forwarded-Proto',
334 | 'Front-End-Https',
335 | 'X-Http-Method-Override',
336 | 'X-ATT-DeviceId',
337 | 'X-Wap-Profile',
338 | 'Proxy-Connection',
339 | 'X-UIDH',
340 | 'X-Csrf-Token',
341 | 'X-Request-ID',
342 | 'X-Correlation-ID',
343 | 'Save-Data',
344 | ]
345 |
--------------------------------------------------------------------------------
/src/pages/Home/Source/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useCallback, useState, useRef, useEffect, useMemo } from 'react'
2 | import { Divider, Row, Col, Modal, message } from 'antd'
3 | import cloneDeep from 'lodash/cloneDeep'
4 | import isObject from 'lodash/isObject'
5 | import isArray from 'lodash/isArray'
6 | import GroupDetail from '../components/GroupDetail'
7 | import Group from '../components/Group'
8 | import ButtonGroup from '../components/ButtonGroup'
9 | import GroupModal from '../components/GroupModal'
10 | import ImportModal from '../components/ImportModal'
11 | import ExportModal from '../components/ExportModal'
12 | import ItemModal from '../components/ItemModal'
13 |
14 | import './index.less'
15 |
16 | const Source: FC = () => {
17 | const [groups, setGroups] = useState([])
18 | const [activeGroupKey, setActiveGroupKey] = useState('')
19 | const [groupModalVisible, setGroupModalVisible] = useState(false)
20 | const [itemModalVisible, setItemModalVisible] = useState(false)
21 | const [importModalVisible, setImportModalVisible] = useState(false)
22 | const [exportModalVisible, setExportModalVisible] = useState(false)
23 | const groupModalRef = useRef({
24 | mode: 'add',
25 | data: {},
26 | })
27 | const itemModalRef = useRef({
28 | mode: 'add',
29 | data: {},
30 | })
31 |
32 | const activeGroup = useMemo(
33 | () => groups.find((x) => x.uniqueKey === activeGroupKey),
34 | [groups, activeGroupKey],
35 | )
36 | const tableData = useMemo(() => (activeGroup ? activeGroup.children : []), [activeGroup])
37 |
38 | const setChromeGroup = (newGroups, fn = () => {}) => {
39 | chrome.extension.sendMessage(
40 | {
41 | type: 'setGroups',
42 | data: newGroups,
43 | },
44 | function (response) {
45 | if (response.isSuccess) {
46 | fn()
47 | setGroups(response.data)
48 | } else {
49 | message.error('添加失败!')
50 | }
51 | },
52 | )
53 | }
54 |
55 | // 添加组
56 | const onAddGroup = useCallback(() => {
57 | setGroupModalVisible(true)
58 | groupModalRef.current = {
59 | mode: 'add',
60 | data: {
61 | uniqueKey: Math.random().toString().slice(2),
62 | },
63 | }
64 | }, [])
65 |
66 | const onEditGroup = useCallback((item) => {
67 | setGroupModalVisible(true)
68 | groupModalRef.current = {
69 | mode: 'edit',
70 | data: item,
71 | }
72 | }, [])
73 |
74 | const onCheckGroup = useCallback(
75 | (item, checked) => {
76 | const newGroups = cloneDeep(groups)
77 | const findIndex = newGroups.findIndex((x) => x.uniqueKey === item.uniqueKey)
78 | newGroups[findIndex].checked = checked
79 |
80 | setChromeGroup(newGroups)
81 | },
82 | [groups, setChromeGroup],
83 | )
84 |
85 | const onDeleteGroup = useCallback(
86 | (item) => {
87 | Modal.confirm({
88 | title: '温馨提示',
89 | content: '确认要删除该数据吗?',
90 | onOk() {
91 | const newGroups = groups.filter((x) => x.uniqueKey !== item.uniqueKey)
92 |
93 | setChromeGroup(newGroups)
94 | },
95 | })
96 | },
97 | [groups, setChromeGroup],
98 | )
99 |
100 | // 导入
101 | const onImport = useCallback(() => {
102 | // import
103 | setImportModalVisible(true)
104 | }, [])
105 |
106 | // 导出
107 | const onExport = useCallback(() => {
108 | setExportModalVisible(true)
109 | }, [])
110 |
111 | // 添加项
112 | const onAddItem = useCallback(() => {
113 | setItemModalVisible(true)
114 | itemModalRef.current = {
115 | mode: 'add',
116 | data: {
117 | uniqueKey: Math.random().toString().slice(2),
118 | },
119 | }
120 | }, [])
121 |
122 | // 编辑项
123 | const onEditItem = useCallback((record) => {
124 | setItemModalVisible(true)
125 | itemModalRef.current = {
126 | mode: 'edit',
127 | data: record,
128 | }
129 | }, [])
130 |
131 | // 选中项
132 | const onCheckItem = useCallback(
133 | (record, checked) => {
134 | const newGroups = cloneDeep(groups)
135 | const groupIndex = newGroups.findIndex((x) => x.uniqueKey === activeGroupKey)
136 | const tableIndex = newGroups[groupIndex].children.findIndex((x) => x.uniqueKey === record.uniqueKey)
137 | newGroups[groupIndex].children[tableIndex].checked = checked
138 |
139 | setChromeGroup(newGroups)
140 | },
141 | [groups, activeGroupKey, setChromeGroup],
142 | )
143 |
144 | // 删除项
145 | const onDeleteItem = useCallback(
146 | (record) => {
147 | Modal.confirm({
148 | title: '温馨提示',
149 | content: '确认要删除该数据吗?',
150 | onOk() {
151 | const newGroups = cloneDeep(groups)
152 | const groupIndex = newGroups.findIndex((x) => x.uniqueKey === activeGroupKey)
153 | newGroups[groupIndex].children = newGroups[groupIndex].children.filter(
154 | (x) => x.uniqueKey !== record.uniqueKey,
155 | )
156 | setChromeGroup(newGroups)
157 | },
158 | })
159 | },
160 | [groups, activeGroupKey, setChromeGroup],
161 | )
162 |
163 | useEffect(() => {
164 | chrome.extension.sendMessage(
165 | {
166 | type: 'getGroups',
167 | },
168 | function (response) {
169 | if (response.isSuccess) {
170 | setGroups(response.data)
171 | if (response.data.length > 0) {
172 | setActiveGroupKey(response.data[0].uniqueKey)
173 | }
174 | }
175 | },
176 | )
177 | }, [])
178 |
179 | return (
180 | <>
181 |
187 |
188 |
189 |
190 |
199 |
200 |
201 |
209 |
210 |
211 | {
216 | let newGroups = []
217 | if (groupModalRef.current.mode === 'add') {
218 | newGroups = [...groups, newGroup]
219 | if (newGroups.length === 1) {
220 | setActiveGroupKey(newGroup.uniqueKey)
221 | }
222 | } else {
223 | newGroups = cloneDeep(groups)
224 | const findIndex = newGroups.findIndex((x) => x.uniqueKey === newGroup.uniqueKey)
225 | newGroups[findIndex] = newGroup
226 | }
227 |
228 | setChromeGroup(newGroups, () => {
229 | setGroupModalVisible(false)
230 | })
231 | }}
232 | onCancel={() => {
233 | setGroupModalVisible(false)
234 | }}
235 | />
236 | {
239 | let newGroups = cloneDeep(groups)
240 | const json = JSON.parse(newJson)
241 | if (isArray(json)) {
242 | newGroups = [...newGroups, ...json]
243 | } else if (isObject(json)) {
244 | newGroups.push(json)
245 | }
246 |
247 | setChromeGroup(newGroups, () => {
248 | setImportModalVisible(false)
249 | })
250 | }}
251 | onCancel={() => {
252 | setImportModalVisible(false)
253 | }}
254 | />
255 | {
259 | setExportModalVisible(false)
260 | }}
261 | />
262 | {
267 | const newGroups = cloneDeep(groups)
268 | const groupIndex = newGroups.findIndex((x) => x.uniqueKey === activeGroupKey)
269 | if (itemModalRef.current.mode === 'add') {
270 | if (!(tableData && tableData.length)) {
271 | newGroups[groupIndex].children = []
272 | }
273 | newGroups[groupIndex].children = [...newGroups[groupIndex].children, newItem]
274 | } else {
275 | const tableIndex = newGroups[groupIndex].children.findIndex(
276 | (x) => x.uniqueKey === newItem.uniqueKey,
277 | )
278 | newGroups[groupIndex].children[tableIndex] = newItem
279 | }
280 |
281 | setChromeGroup(newGroups, () => {
282 | setItemModalVisible(false)
283 | })
284 | }}
285 | onCancel={() => {
286 | setItemModalVisible(false)
287 | }}
288 | />
289 | >
290 | )
291 | }
292 |
293 | export default Source
294 |
--------------------------------------------------------------------------------