页面组件
This commit is contained in:
parent
804266c6dd
commit
84261e22bd
@ -24,6 +24,8 @@ export default [
|
|||||||
"react-hooks": reactHooks,
|
"react-hooks": reactHooks,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
"dexie": "^4.0.10",
|
"dexie": "^4.0.10",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"i18next": "^23.16.4",
|
"i18next": "^23.16.4",
|
||||||
|
"monaco-editor": "^0.52.2",
|
||||||
|
"pako": "^2.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^15.1.0",
|
"react-i18next": "^15.1.0",
|
||||||
@ -38,6 +40,7 @@
|
|||||||
"@rspack/cli": "^1.0.14",
|
"@rspack/cli": "^1.0.14",
|
||||||
"@rspack/core": "^1.0.14",
|
"@rspack/core": "^1.0.14",
|
||||||
"@types/chrome": "^0.0.279",
|
"@types/chrome": "^0.0.279",
|
||||||
|
"@types/pako": "^2.0.3",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^18.2.48",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react-dom": "^18.2.18",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
|
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -29,6 +29,12 @@ importers:
|
|||||||
i18next:
|
i18next:
|
||||||
specifier: ^23.16.4
|
specifier: ^23.16.4
|
||||||
version: 23.16.4
|
version: 23.16.4
|
||||||
|
monaco-editor:
|
||||||
|
specifier: ^0.52.2
|
||||||
|
version: 0.52.2
|
||||||
|
pako:
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0
|
||||||
react:
|
react:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.3.1
|
version: 18.3.1
|
||||||
@ -69,6 +75,9 @@ importers:
|
|||||||
'@types/chrome':
|
'@types/chrome':
|
||||||
specifier: ^0.0.279
|
specifier: ^0.0.279
|
||||||
version: 0.0.279
|
version: 0.0.279
|
||||||
|
'@types/pako':
|
||||||
|
specifier: ^2.0.3
|
||||||
|
version: 2.0.3
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^18.2.48
|
specifier: ^18.2.48
|
||||||
version: 18.3.12
|
version: 18.3.12
|
||||||
@ -1019,6 +1028,9 @@ packages:
|
|||||||
'@types/node@22.8.1':
|
'@types/node@22.8.1':
|
||||||
resolution: {integrity: sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==}
|
resolution: {integrity: sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==}
|
||||||
|
|
||||||
|
'@types/pako@2.0.3':
|
||||||
|
resolution: {integrity: sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==}
|
||||||
|
|
||||||
'@types/prop-types@15.7.13':
|
'@types/prop-types@15.7.13':
|
||||||
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
|
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
|
||||||
|
|
||||||
@ -2630,6 +2642,9 @@ packages:
|
|||||||
mlly@1.7.3:
|
mlly@1.7.3:
|
||||||
resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
|
resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==}
|
||||||
|
|
||||||
|
monaco-editor@0.52.2:
|
||||||
|
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
|
||||||
|
|
||||||
mrmime@1.0.1:
|
mrmime@1.0.1:
|
||||||
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
|
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -2771,6 +2786,9 @@ packages:
|
|||||||
package-manager-detector@0.2.5:
|
package-manager-detector@0.2.5:
|
||||||
resolution: {integrity: sha512-3dS7y28uua+UDbRCLBqltMBrbI+A5U2mI9YuxHRxIWYmLj3DwntEBmERYzIAQ4DMeuCUOBSak7dBHHoXKpOTYQ==}
|
resolution: {integrity: sha512-3dS7y28uua+UDbRCLBqltMBrbI+A5U2mI9YuxHRxIWYmLj3DwntEBmERYzIAQ4DMeuCUOBSak7dBHHoXKpOTYQ==}
|
||||||
|
|
||||||
|
pako@2.1.0:
|
||||||
|
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -4484,6 +4502,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
|
|
||||||
|
'@types/pako@2.0.3': {}
|
||||||
|
|
||||||
'@types/prop-types@15.7.13': {}
|
'@types/prop-types@15.7.13': {}
|
||||||
|
|
||||||
'@types/qs@6.9.16': {}
|
'@types/qs@6.9.16': {}
|
||||||
@ -6597,6 +6617,8 @@ snapshots:
|
|||||||
pkg-types: 1.2.1
|
pkg-types: 1.2.1
|
||||||
ufo: 1.5.4
|
ufo: 1.5.4
|
||||||
|
|
||||||
|
monaco-editor@0.52.2: {}
|
||||||
|
|
||||||
mrmime@1.0.1: {}
|
mrmime@1.0.1: {}
|
||||||
|
|
||||||
mrmime@2.0.0: {}
|
mrmime@2.0.0: {}
|
||||||
@ -6728,6 +6750,8 @@ snapshots:
|
|||||||
|
|
||||||
package-manager-detector@0.2.5: {}
|
package-manager-detector@0.2.5: {}
|
||||||
|
|
||||||
|
pako@2.1.0: {}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites: 3.1.0
|
callsites: 3.1.0
|
||||||
|
@ -89,6 +89,16 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "asset",
|
||||||
|
test: /\.d\.ts$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "asset",
|
||||||
|
test: /\.tpl$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
225
src/pages/components/CodeEditor/index.tsx
Normal file
225
src/pages/components/CodeEditor/index.tsx
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import Cache from "@App/app/cache";
|
||||||
|
import { LinterWorker } from "@App/pkg/utils/monaco-editor";
|
||||||
|
import { useAppSelector } from "@App/store/hooks";
|
||||||
|
import { editor, Range } from "monaco-editor";
|
||||||
|
import React, { useEffect, useImperativeHandle, useState } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
diffCode?: string; // 因为代码加载是异步的,diifCode有3种状态:undefined不确定,""没有diff,有diff,不确定的情况下,编辑器不会加载
|
||||||
|
editable?: boolean;
|
||||||
|
id: string;
|
||||||
|
code?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.ICodeEditor | undefined }, Props> = (
|
||||||
|
{ id, className, code, diffCode, editable },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const settings = useAppSelector((state) => state.setting);
|
||||||
|
const [monacoEditor, setEditor] = useState<editor.ICodeEditor>();
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
editor: monacoEditor,
|
||||||
|
}));
|
||||||
|
useEffect(() => {
|
||||||
|
if (diffCode === undefined || code === undefined) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
let edit: editor.IStandaloneDiffEditor | editor.IStandaloneCodeEditor;
|
||||||
|
// @ts-ignore
|
||||||
|
const ts = window.tsUrl ? 0 : 200;
|
||||||
|
setTimeout(() => {
|
||||||
|
const div = document.getElementById(id) as HTMLDivElement;
|
||||||
|
if (diffCode) {
|
||||||
|
edit = editor.createDiffEditor(div, {
|
||||||
|
enableSplitViewResizing: false,
|
||||||
|
renderSideBySide: false,
|
||||||
|
folding: true,
|
||||||
|
foldingStrategy: "indentation",
|
||||||
|
automaticLayout: true,
|
||||||
|
overviewRulerBorder: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
readOnly: true,
|
||||||
|
diffWordWrap: "off",
|
||||||
|
glyphMargin: true,
|
||||||
|
});
|
||||||
|
edit.setModel({
|
||||||
|
original: editor.createModel(diffCode, "javascript"),
|
||||||
|
modified: editor.createModel(code, "javascript"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
edit = editor.create(div, {
|
||||||
|
language: "javascript",
|
||||||
|
theme: document.body.getAttribute("arco-theme") === "dark" ? "vs-dark" : "vs",
|
||||||
|
folding: true,
|
||||||
|
foldingStrategy: "indentation",
|
||||||
|
automaticLayout: true,
|
||||||
|
overviewRulerBorder: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
readOnly: !editable,
|
||||||
|
glyphMargin: true,
|
||||||
|
});
|
||||||
|
edit.setValue(code);
|
||||||
|
|
||||||
|
setEditor(edit);
|
||||||
|
}
|
||||||
|
}, ts);
|
||||||
|
return () => {
|
||||||
|
if (edit) {
|
||||||
|
edit.dispose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [code, diffCode, editable, id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!settings.eslint.enable) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
if (!monacoEditor) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
const model = monacoEditor.getModel();
|
||||||
|
if (!model) {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
let timer: NodeJS.Timeout | null;
|
||||||
|
const lint = () => {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
timer = null;
|
||||||
|
LinterWorker.sendLinterMessage({
|
||||||
|
code: model.getValue(),
|
||||||
|
id,
|
||||||
|
config: JSON.parse(settings.eslint.config),
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
// 加载完成就检测一次
|
||||||
|
lint();
|
||||||
|
model.onDidChangeContent(() => {
|
||||||
|
lint();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在行号旁显示ESLint错误/警告图标
|
||||||
|
const diffEslint = (
|
||||||
|
makers: {
|
||||||
|
startLineNumber: number;
|
||||||
|
endLineNumber: number;
|
||||||
|
severity: number;
|
||||||
|
}[]
|
||||||
|
) => {
|
||||||
|
// 定义glyph class
|
||||||
|
const glyphMarginClassList = {
|
||||||
|
4: "icon-warn",
|
||||||
|
8: "icon-error",
|
||||||
|
};
|
||||||
|
|
||||||
|
// 先移除所有旧的Decorations
|
||||||
|
const oldDecorations = model
|
||||||
|
.getAllDecorations()
|
||||||
|
.filter(
|
||||||
|
(i) =>
|
||||||
|
i.options.glyphMarginClassName &&
|
||||||
|
Object.values(glyphMarginClassList).includes(i.options.glyphMarginClassName)
|
||||||
|
);
|
||||||
|
monacoEditor.removeDecorations(oldDecorations.map((i) => i.id));
|
||||||
|
|
||||||
|
/* 待改进 目前似乎monaco无法满足需求
|
||||||
|
// 获取所有ESLint ModelMarkers
|
||||||
|
const allMarkers = editor.getModelMarkers({ owner: "ESLint" });
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 再重新添加新的Decorations
|
||||||
|
monacoEditor.createDecorationsCollection(
|
||||||
|
makers.map(({ startLineNumber, endLineNumber, severity }) => ({
|
||||||
|
range: new Range(startLineNumber, 1, endLineNumber, 1),
|
||||||
|
options: {
|
||||||
|
isWholeLine: true,
|
||||||
|
// @ts-ignore
|
||||||
|
glyphMarginClassName: glyphMarginClassList[severity],
|
||||||
|
|
||||||
|
/* 待改进 目前monaco似乎无法满足需求
|
||||||
|
glyphMarginHoverMessage: allMarkers.reduce(
|
||||||
|
(prev: any, next: any) => {
|
||||||
|
if (
|
||||||
|
next.startLineNumber === startLineNumber &&
|
||||||
|
next.endLineNumber === endLineNumber
|
||||||
|
) {
|
||||||
|
prev.push({
|
||||||
|
value: `${next.message} ESLinter [(${next.code.value})](${next.code.target})`,
|
||||||
|
isTrusted: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = (message: any) => {
|
||||||
|
if (id !== message.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editor.setModelMarkers(model, "ESLint", message.markers);
|
||||||
|
const fix = new Map();
|
||||||
|
// 设置fix
|
||||||
|
message.markers.forEach(
|
||||||
|
(val: {
|
||||||
|
code: { value: any };
|
||||||
|
startLineNumber: any;
|
||||||
|
endLineNumber: any;
|
||||||
|
startColumn: any;
|
||||||
|
endColumn: any;
|
||||||
|
fix: any;
|
||||||
|
}) => {
|
||||||
|
if (val.fix) {
|
||||||
|
fix.set(
|
||||||
|
`${val.code.value}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`,
|
||||||
|
val.fix
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Cache.getInstance().set("eslint-fix", fix);
|
||||||
|
|
||||||
|
// 在行号旁显示ESLint错误/警告图标
|
||||||
|
const formatMarkers = message.markers.map(
|
||||||
|
({
|
||||||
|
startLineNumber,
|
||||||
|
endLineNumber,
|
||||||
|
severity,
|
||||||
|
}: {
|
||||||
|
startLineNumber: number;
|
||||||
|
endLineNumber: number;
|
||||||
|
severity: number;
|
||||||
|
}) => ({ startLineNumber, endLineNumber, severity })
|
||||||
|
);
|
||||||
|
diffEslint(formatMarkers);
|
||||||
|
};
|
||||||
|
LinterWorker.hook.addListener("message", handler);
|
||||||
|
return () => {
|
||||||
|
LinterWorker.hook.removeListener("message", handler);
|
||||||
|
};
|
||||||
|
}, [id, monacoEditor, settings.eslint.config, settings.eslint.enable]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id={id}
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
border: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.forwardRef(CodeEditor);
|
@ -15,12 +15,15 @@ import { IconDesktop, IconMoonFill, IconSunFill } from "@arco-design/web-react/i
|
|||||||
import React, { ReactNode, useRef, useState } from "react";
|
import React, { ReactNode, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
import { useAppDispatch, useAppSelector } from "@App/store/hooks";
|
||||||
|
import { selectThemeMode, setDarkMode } from "@App/store/features/setting";
|
||||||
|
|
||||||
const MainLayout: React.FC<{
|
const MainLayout: React.FC<{
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
className: string;
|
className: string;
|
||||||
}> = ({ children, className }) => {
|
}> = ({ children, className }) => {
|
||||||
const [lightMode, setLightMode] = useState(localStorage.lightMode || "auto");
|
const lightMode = useAppSelector(selectThemeMode);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const importRef = useRef<RefInputType>(null);
|
const importRef = useRef<RefInputType>(null);
|
||||||
const [importVisible, setImportVisible] = useState(false);
|
const [importVisible, setImportVisible] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -62,8 +65,7 @@ const MainLayout: React.FC<{
|
|||||||
droplist={
|
droplist={
|
||||||
<Menu
|
<Menu
|
||||||
onClickMenuItem={(key) => {
|
onClickMenuItem={(key) => {
|
||||||
setLightMode(key);
|
dispatch(setDarkMode(key as "light" | "dark" | "auto"));
|
||||||
localStorage.lightMode = key;
|
|
||||||
}}
|
}}
|
||||||
selectedKeys={[lightMode]}
|
selectedKeys={[lightMode]}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,275 @@
|
|||||||
|
import { Avatar, Button, Grid, Message, Space, Switch, Tag, Tooltip, Typography } from "@arco-design/web-react";
|
||||||
|
import CodeEditor from "../components/CodeEditor";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Metadata, Script, SCRIPT_STATUS_DISABLE, SCRIPT_STATUS_ENABLE } from "@App/app/repo/scripts";
|
||||||
|
import { Subscribe } from "@App/app/repo/subscribe";
|
||||||
|
import { i18nDescription, i18nName } from "@App/locales/locales";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ScriptInfo } from "@App/pkg/utils/script";
|
||||||
|
|
||||||
|
type Permission = { label: string; color?: string; value: string[] }[];
|
||||||
|
|
||||||
|
const closeWindow = () => {
|
||||||
|
window.close();
|
||||||
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [permission, setPermission] = useState<Permission>([]);
|
||||||
|
const [metadata, setMetadata] = useState<Metadata>({});
|
||||||
|
// 脚本信息包括脚本代码、下载url,但是不包括解析代码后得到的metadata,通过background的缓存获取
|
||||||
|
const [info, setInfo] = useState<ScriptInfo>();
|
||||||
|
// 对脚本详细的描述
|
||||||
|
const [description, setDescription] = useState<any>();
|
||||||
|
// 是系统检测到脚本更新时打开的窗口会有一个倒计时
|
||||||
|
const [countdown, setCountdown] = useState<number>(-1);
|
||||||
|
// 是否为更新
|
||||||
|
const [isUpdate, setIsUpdate] = useState<boolean>(false);
|
||||||
|
// 脚本信息
|
||||||
|
const [upsertScript, setUpsertScript] = useState<Script | Subscribe>();
|
||||||
|
// 更新的情况下会有老版本的脚本信息
|
||||||
|
const [oldScript, setOldScript] = useState<Script | Subscribe>();
|
||||||
|
// 脚本开启状态
|
||||||
|
const [enable, setEnable] = useState<boolean>(false);
|
||||||
|
// 是否是订阅脚本
|
||||||
|
const [isSub, setIsSub] = useState<boolean>(false);
|
||||||
|
// 按钮文案
|
||||||
|
const [btnText, setBtnText] = useState<string>();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// 不推荐的内容标签与描述
|
||||||
|
const antifeatures: {
|
||||||
|
[key: string]: { color: string; title: string; description: string };
|
||||||
|
} = {
|
||||||
|
"referral-link": {
|
||||||
|
color: "purple",
|
||||||
|
title: t("antifeature_referral_link_title"),
|
||||||
|
description: t("antifeature_referral_link_description"),
|
||||||
|
},
|
||||||
|
ads: {
|
||||||
|
color: "orange",
|
||||||
|
title: t("antifeature_ads_title"),
|
||||||
|
description: t("antifeature_ads_description"),
|
||||||
|
},
|
||||||
|
payment: {
|
||||||
|
color: "magenta",
|
||||||
|
title: t("antifeature_payment_title"),
|
||||||
|
description: t("antifeature_payment_description"),
|
||||||
|
},
|
||||||
|
miner: {
|
||||||
|
color: "orangered",
|
||||||
|
title: t("antifeature_miner_title"),
|
||||||
|
description: t("antifeature_miner_description"),
|
||||||
|
},
|
||||||
|
membership: {
|
||||||
|
color: "blue",
|
||||||
|
title: t("antifeature_membership_title"),
|
||||||
|
description: t("antifeature_membership_description"),
|
||||||
|
},
|
||||||
|
tracking: {
|
||||||
|
color: "pinkpurple",
|
||||||
|
title: t("antifeature_tracking_title"),
|
||||||
|
description: t("antifeature_tracking_description"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<p className="text-lg">aa</p>
|
<div className="h-full">
|
||||||
|
<Grid.Row gutter={8}>
|
||||||
|
<Grid.Col flex={1} className="flex-col p-8px">
|
||||||
|
<Space direction="vertical">
|
||||||
|
<div>
|
||||||
|
{upsertScript?.metadata.icon && (
|
||||||
|
<Avatar size={32} shape="square" style={{ marginRight: "8px" }}>
|
||||||
|
<img src={upsertScript.metadata.icon[0]} alt={upsertScript?.name} />
|
||||||
|
</Avatar>
|
||||||
|
)}
|
||||||
|
<Typography.Text bold className="text-size-lg">
|
||||||
|
{upsertScript && i18nName(upsertScript)}
|
||||||
|
<Tooltip content={isSub ? t("subscribe_source_tooltip") : t("script_status_tooltip")}>
|
||||||
|
<Switch
|
||||||
|
style={{ marginLeft: "8px" }}
|
||||||
|
checked={enable}
|
||||||
|
onChange={(checked) => {
|
||||||
|
setUpsertScript((script) => {
|
||||||
|
if (!script) {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
script.status = checked ? SCRIPT_STATUS_ENABLE : SCRIPT_STATUS_DISABLE;
|
||||||
|
setEnable(checked);
|
||||||
|
return script;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography.Text bold>{upsertScript && i18nDescription(upsertScript)}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography.Text bold>
|
||||||
|
{t("author")}: {metadata.author}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Typography.Text
|
||||||
|
bold
|
||||||
|
style={{
|
||||||
|
overflowWrap: "break-word",
|
||||||
|
wordBreak: "break-all",
|
||||||
|
maxHeight: "70px",
|
||||||
|
display: "block",
|
||||||
|
overflowY: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("source")}: {info?.url}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="text-end">
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
if (!upsertScript) {
|
||||||
|
Message.error(t("script_info_load_failed")!);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isSub) {
|
||||||
|
// subscribeCtrl
|
||||||
|
// .upsert(upsertScript as Subscribe)
|
||||||
|
// .then(() => {
|
||||||
|
// Message.success(t("subscribe_success")!);
|
||||||
|
// setBtnText(t("subscribe_success")!);
|
||||||
|
// setTimeout(() => {
|
||||||
|
// closeWindow();
|
||||||
|
// }, 200);
|
||||||
|
// })
|
||||||
|
// .catch((e) => {
|
||||||
|
// Message.error(`${t("subscribe_failed")}: ${e}`);
|
||||||
|
// });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// scriptCtrl
|
||||||
|
// .upsert(upsertScript as Script)
|
||||||
|
// .then(() => {
|
||||||
|
// if (isUpdate) {
|
||||||
|
// Message.success(t("install.update_success")!);
|
||||||
|
// setBtnText(t("install.update_success")!);
|
||||||
|
// } else {
|
||||||
|
// Message.success(t("install_success")!);
|
||||||
|
// setBtnText(t("install_success")!);
|
||||||
|
// }
|
||||||
|
// setTimeout(() => {
|
||||||
|
// closeWindow();
|
||||||
|
// }, 200);
|
||||||
|
// })
|
||||||
|
// .catch((e) => {
|
||||||
|
// Message.error(`${t("install_failed")}: ${e}`);
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{btnText}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
status="danger"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
if (countdown === -1) {
|
||||||
|
closeWindow();
|
||||||
|
} else {
|
||||||
|
setCountdown(-1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{countdown === -1 ? t("close") : `${t("stop")} (${countdown})`}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col flex={1} className="p-8px">
|
||||||
|
<Space direction="vertical">
|
||||||
|
<div>
|
||||||
|
<Space>
|
||||||
|
{oldScript && (
|
||||||
|
<Tooltip content={`${t("current_version")}: v${oldScript.metadata.version[0]}`}>
|
||||||
|
<Tag bordered>{oldScript.metadata.version[0]}</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{metadata.version && (
|
||||||
|
<Tooltip color="red" content={`${t("update_version")}: v${metadata.version[0]}`}>
|
||||||
|
<Tag bordered color="red">
|
||||||
|
{metadata.version[0]}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{(metadata.background || metadata.crontab) && (
|
||||||
|
<Tooltip color="green" content={t("background_script_tag")}>
|
||||||
|
<Tag bordered color="green">
|
||||||
|
{t("background_script")}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{metadata.crontab && (
|
||||||
|
<Tooltip color="green" content={t("scheduled_script_tag")}>
|
||||||
|
<Tag bordered color="green">
|
||||||
|
{t("scheduled_script")}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{metadata.antifeature &&
|
||||||
|
metadata.antifeature.map((antifeature) => {
|
||||||
|
const item = antifeature.split(" ")[0];
|
||||||
|
return (
|
||||||
|
antifeatures[item] && (
|
||||||
|
<Tooltip color={antifeatures[item].color} content={antifeatures[item].description}>
|
||||||
|
<Tag bordered color={antifeatures[item].color}>
|
||||||
|
{antifeatures[item].title}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
{description && description}
|
||||||
|
<div>
|
||||||
|
<Typography.Text type="error">{t("install_from_legitimate_sources_warning")}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Grid.Col>
|
||||||
|
<Grid.Col span={24}>
|
||||||
|
<Grid.Row>
|
||||||
|
{permission.map((item) => (
|
||||||
|
<Grid.Col
|
||||||
|
key={item.label}
|
||||||
|
span={8}
|
||||||
|
style={{
|
||||||
|
maxHeight: "200px",
|
||||||
|
overflowY: "auto",
|
||||||
|
overflowX: "auto",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
className="p-8px"
|
||||||
|
>
|
||||||
|
<Typography.Text bold color={item.color}>
|
||||||
|
{item.label}
|
||||||
|
</Typography.Text>
|
||||||
|
{item.value.map((v) => (
|
||||||
|
<div key={v}>
|
||||||
|
<Typography.Text style={{ wordBreak: "unset", color: item.color }}>{v}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Grid.Col>
|
||||||
|
))}
|
||||||
|
</Grid.Row>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid.Row>
|
||||||
|
<CodeEditor id="show-code" code={upsertScript?.code || undefined} diffCode={oldScript?.code || ""} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,15 @@ import MainLayout from "../components/layout/MainLayout.tsx";
|
|||||||
import "@arco-design/web-react/dist/css/arco.css";
|
import "@arco-design/web-react/dist/css/arco.css";
|
||||||
import "@App/locales/locales";
|
import "@App/locales/locales";
|
||||||
import "@App/index.css";
|
import "@App/index.css";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
import { store } from "@App/store/store.ts";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<Provider store={store}>
|
||||||
<MainLayout className="!flex-col !px-4 box-border">
|
<MainLayout className="!flex-col !px-4 box-border">
|
||||||
<App />
|
<App />
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
149
src/pkg/utils/monaco-editor.ts
Normal file
149
src/pkg/utils/monaco-editor.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import dts from "@App/types/scriptcat.d.ts";
|
||||||
|
import { languages } from "monaco-editor";
|
||||||
|
import pako from "pako";
|
||||||
|
import Cache from "@App/app/cache";
|
||||||
|
import { isFirefox } from "./utils";
|
||||||
|
import EventEmitter from "eventemitter3";
|
||||||
|
|
||||||
|
// 注册eslint
|
||||||
|
const linterWorker = new Worker("/src/linter.worker.js");
|
||||||
|
|
||||||
|
export default function registerEditor() {
|
||||||
|
// @ts-ignore
|
||||||
|
window.tsUrl = "";
|
||||||
|
|
||||||
|
fetch(chrome.runtime.getURL(`/src/ts.worker.js${isFirefox() ? ".gz" : ""}`))
|
||||||
|
.then((resp) => resp.blob())
|
||||||
|
.then(async (blob) => {
|
||||||
|
const result = pako.inflate(await blob.arrayBuffer());
|
||||||
|
// @ts-ignore
|
||||||
|
window.tsUrl = URL.createObjectURL(new Blob([result]));
|
||||||
|
});
|
||||||
|
// @ts-ignore
|
||||||
|
window.MonacoEnvironment = {
|
||||||
|
getWorkerUrl(moduleId: any, label: any) {
|
||||||
|
if (label === "typescript" || label === "javascript") {
|
||||||
|
// return "/src/ts.worker.js";
|
||||||
|
// @ts-ignore
|
||||||
|
return window.tsUrl;
|
||||||
|
}
|
||||||
|
return "/src/editor.worker.js";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
languages.typescript.javascriptDefaults.addExtraLib(dts, "tampermonkey.d.ts");
|
||||||
|
|
||||||
|
// 悬停提示
|
||||||
|
const prompt: { [key: string]: any } = {
|
||||||
|
name: "脚本名称",
|
||||||
|
description: "脚本描述",
|
||||||
|
namespace: "脚本命名空间",
|
||||||
|
version: "脚本版本",
|
||||||
|
author: "脚本作者",
|
||||||
|
background: "后台脚本",
|
||||||
|
crontab: `定时脚本 crontab 参考(不适用于云端脚本)
|
||||||
|
* * * * * * 每秒运行一次
|
||||||
|
* * * * * 每分钟运行一次
|
||||||
|
0 */6 * * * 每6小时的0分时执行一次
|
||||||
|
15 */6 * * * 每6小时的15分时执行一次
|
||||||
|
* once * * * 每小时运行一次
|
||||||
|
* * once * * 每天运行一次
|
||||||
|
* 10 once * * 每天10点-10:59中运行一次,假设当10:04时运行了一次,10:05-10:59的后续的时间将不会再运行
|
||||||
|
* 1,3,5 once * * 每天1点3点5点中运行一次,假设当1点时运行了一次,3,5点将不会再运行
|
||||||
|
* */4 once * * 每天每隔4小时检测运行一次,假设当4点时运行了一次,8,12,16,20,24点等后续的时间将不会再运行
|
||||||
|
* 10-23 once * * 每天10点-23:59中运行一次,假设当10:04时运行了一次,10:05-23:59的后续时间将不会再运行
|
||||||
|
* once 13 * * 每个月的13号的每小时运行一次`.replace(/\n/g, "<br>"),
|
||||||
|
};
|
||||||
|
|
||||||
|
languages.registerHoverProvider("javascript", {
|
||||||
|
provideHover: (model, position) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const line = model.getLineContent(position.lineNumber);
|
||||||
|
const flag = /^\/\/\s*@(\w+?)(\s+(.*?)|)$/.exec(line);
|
||||||
|
if (flag) {
|
||||||
|
resolve({
|
||||||
|
contents: [{ value: prompt[flag[1]], supportHtml: true }],
|
||||||
|
});
|
||||||
|
} else if (/==UserScript==/.test(line)) {
|
||||||
|
// 匹配==UserScript==
|
||||||
|
resolve({
|
||||||
|
contents: [{ value: "一个用户脚本" }],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理quick fix
|
||||||
|
languages.registerCodeActionProvider("javascript", {
|
||||||
|
provideCodeActions: (model /** ITextModel */, range /** Range */, context /** CodeActionContext */) => {
|
||||||
|
const actions: languages.CodeAction[] = [];
|
||||||
|
const eslintFix = <Map<string, any>>Cache.getInstance().get("eslint-fix");
|
||||||
|
for (let i = 0; i < context.markers.length; i += 1) {
|
||||||
|
// 判断有没有修复方案
|
||||||
|
const val = context.markers[i];
|
||||||
|
const code = typeof val.code === "string" ? val.code : val.code!.value;
|
||||||
|
const fix = eslintFix.get(
|
||||||
|
`${code}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`
|
||||||
|
);
|
||||||
|
if (fix) {
|
||||||
|
const edit: languages.IWorkspaceTextEdit = {
|
||||||
|
resource: model.uri,
|
||||||
|
textEdit: {
|
||||||
|
range: fix.range,
|
||||||
|
text: fix.text,
|
||||||
|
},
|
||||||
|
versionId: undefined,
|
||||||
|
};
|
||||||
|
actions.push(<languages.CodeAction>{
|
||||||
|
title: `修复 ${code} 问题`,
|
||||||
|
diagnostics: [val],
|
||||||
|
kind: "quickfix",
|
||||||
|
edit: {
|
||||||
|
edits: [edit],
|
||||||
|
},
|
||||||
|
isPreferred: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const actions = context.markers.map((error) => {
|
||||||
|
// const edit: languages.IWorkspaceTextEdit = {
|
||||||
|
// resource: model.uri,
|
||||||
|
// textEdit: {
|
||||||
|
// range,
|
||||||
|
// text: "console.log(1)",
|
||||||
|
// },
|
||||||
|
// versionId: undefined,
|
||||||
|
// };
|
||||||
|
// return <languages.CodeAction>{
|
||||||
|
// title: ``,
|
||||||
|
// diagnostics: [error],
|
||||||
|
// kind: "quickfix",
|
||||||
|
// edit: {
|
||||||
|
// edits: [edit],
|
||||||
|
// },
|
||||||
|
// isPreferred: true,
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
return {
|
||||||
|
actions,
|
||||||
|
dispose: () => {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LinterWorker {
|
||||||
|
static hook = new EventEmitter();
|
||||||
|
|
||||||
|
static sendLinterMessage(data: unknown) {
|
||||||
|
linterWorker.postMessage(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
linterWorker.onmessage = (event) => {
|
||||||
|
LinterWorker.hook.emit("message", event.data);
|
||||||
|
};
|
53
src/store/features/setting.ts
Normal file
53
src/store/features/setting.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { createAppSlice } from "../hooks";
|
||||||
|
import { PayloadAction } from "@reduxjs/toolkit";
|
||||||
|
import { editor } from "monaco-editor";
|
||||||
|
|
||||||
|
export const settingSlice = createAppSlice({
|
||||||
|
name: "setting",
|
||||||
|
initialState: {
|
||||||
|
lightMode: localStorage.lightMode || "auto",
|
||||||
|
eslint: {
|
||||||
|
enable: true,
|
||||||
|
config: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
reducers: (create) => {
|
||||||
|
// 初始化黑夜模式
|
||||||
|
const setAutoMode = () => {
|
||||||
|
const darkTheme = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
const isMatch = (match: boolean) => {
|
||||||
|
if (match) {
|
||||||
|
document.body.setAttribute("arco-theme", "dark");
|
||||||
|
editor.setTheme("vs-dark");
|
||||||
|
} else {
|
||||||
|
document.body.removeAttribute("arco-theme");
|
||||||
|
editor.setTheme("vs");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
darkTheme.addEventListener("change", (e) => {
|
||||||
|
isMatch(e.matches);
|
||||||
|
});
|
||||||
|
isMatch(darkTheme.matches);
|
||||||
|
};
|
||||||
|
setAutoMode();
|
||||||
|
return {
|
||||||
|
setDarkMode: create.reducer((state, action: PayloadAction<"light" | "dark" | "auto">) => {
|
||||||
|
localStorage.loghtMode = action.payload;
|
||||||
|
state.lightMode = action.payload;
|
||||||
|
if (action.payload === "auto") {
|
||||||
|
setAutoMode();
|
||||||
|
} else {
|
||||||
|
document.body.setAttribute("arco-theme", action.payload);
|
||||||
|
editor.setTheme(action.payload === "dark" ? "vs-dark" : "vs");
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
selectThemeMode: (state) => state.lightMode,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { setDarkMode } = settingSlice.actions;
|
||||||
|
|
||||||
|
export const { selectThemeMode } = settingSlice.selectors;
|
@ -1,10 +1,11 @@
|
|||||||
import type { Action, ThunkAction } from "@reduxjs/toolkit";
|
import type { Action, ThunkAction } from "@reduxjs/toolkit";
|
||||||
import { combineSlices, configureStore } from "@reduxjs/toolkit";
|
import { combineSlices, configureStore } from "@reduxjs/toolkit";
|
||||||
import { setupListeners } from "@reduxjs/toolkit/query";
|
import { setupListeners } from "@reduxjs/toolkit/query";
|
||||||
|
import { settingSlice } from "./features/setting";
|
||||||
|
|
||||||
// `combineSlices` automatically combines the reducers using
|
// `combineSlices` automatically combines the reducers using
|
||||||
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
|
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
|
||||||
const rootReducer = combineSlices();
|
const rootReducer = combineSlices(settingSlice);
|
||||||
// Infer the `RootState` type from the root reducer
|
// Infer the `RootState` type from the root reducer
|
||||||
export type RootState = ReturnType<typeof rootReducer>;
|
export type RootState = ReturnType<typeof rootReducer>;
|
||||||
|
|
||||||
|
4
src/types/main.d.ts
vendored
Normal file
4
src/types/main.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module "@App/types/scriptcat.d.ts";
|
||||||
|
declare module "*.tpl";
|
||||||
|
declare module "*.json";
|
||||||
|
declare module "*.yaml";
|
2
src/types/scriptcat.d.ts
vendored
2
src/types/scriptcat.d.ts
vendored
@ -94,7 +94,7 @@ declare function GM_log(message: string, level?: GMTypes.LoggerLevel, labels?: G
|
|||||||
|
|
||||||
declare function GM_getResourceText(name: string): string | undefined;
|
declare function GM_getResourceText(name: string): string | undefined;
|
||||||
|
|
||||||
declare function GM_getResourceURL(name: string, isBlobUrl?: boolean = false): string | undefined;
|
declare function GM_getResourceURL(name: string, isBlobUrl?: boolean): string | undefined;
|
||||||
|
|
||||||
declare function GM_registerMenuCommand(name: string, listener: () => void, accessKey?: string): number;
|
declare function GM_registerMenuCommand(name: string, listener: () => void, accessKey?: string): number;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user