From a7620dd7e5b8375d13b0ec4fe0bfc55510c7fa73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Mon, 7 Apr 2025 01:35:43 +0800 Subject: [PATCH] =?UTF-8?q?popup=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/message/server.ts | 1 - rspack.config.ts | 2 +- src/app/service/service_worker/gm_api.ts | 44 +++- src/app/service/service_worker/index.ts | 3 + src/app/service/service_worker/popup.ts | 26 ++ src/app/service/service_worker/runtime.ts | 11 +- src/pages/components/ScriptMenuList/index.tsx | 44 ++-- src/pages/{popup/index.html => popup.html} | 3 - src/pages/popup/App.css | 41 --- src/pages/popup/App.tsx | 240 ++++++++++++++++-- src/pages/popup/index.css | 71 +----- src/pages/popup/main.tsx | 34 ++- src/runtime/content/content.ts | 9 +- src/runtime/content/gm_api.ts | 48 +++- 14 files changed, 400 insertions(+), 177 deletions(-) create mode 100644 src/app/service/service_worker/popup.ts rename src/pages/{popup/index.html => popup.html} (89%) delete mode 100644 src/pages/popup/App.css diff --git a/packages/message/server.ts b/packages/message/server.ts index 6d42788..90237aa 100644 --- a/packages/message/server.ts +++ b/packages/message/server.ts @@ -116,7 +116,6 @@ export class Group { // 转发消息 export function forwardMessage(prefix: string, path: string, from: Server, to: MessageSend, middleware?: ApiFunction) { from.on(path, async (params, fromCon) => { - console.log("forwardMessage", path, prefix, params); if (middleware) { const resp = await middleware(params, new GetSender(fromCon)); if (resp !== false) { diff --git a/rspack.config.ts b/rspack.config.ts index 7f25f1a..a00afee 100644 --- a/rspack.config.ts +++ b/rspack.config.ts @@ -152,7 +152,7 @@ export default defineConfig({ }), new rspack.HtmlRspackPlugin({ filename: `${dist}/ext/src/popup.html`, - template: `${src}/pages/popup/index.html`, + template: `${src}/pages/popup.html`, inject: "head", title: "Home - ScriptCat", minify: true, diff --git a/src/app/service/service_worker/gm_api.ts b/src/app/service/service_worker/gm_api.ts index 19938bf..0e4cab4 100644 --- a/src/app/service/service_worker/gm_api.ts +++ b/src/app/service/service_worker/gm_api.ts @@ -8,6 +8,7 @@ import { connect } from "@Packages/message/client"; import Cache, { incr } from "@App/app/cache"; import { unsafeHeaders } from "@App/runtime/utils"; import EventEmitter from "eventemitter3"; +import { MessageQueue } from "@Packages/message/message_queue"; // GMApi,处理脚本的GM API调用请求 @@ -35,6 +36,7 @@ export default class GMApi { constructor( private group: Group, private send: MessageSend, + private mq: MessageQueue, private value: ValueService ) { this.logger = LoggerCore.logger().with({ service: "runtime/gm_api" }); @@ -83,7 +85,6 @@ export default class GMApi { async buildDNRRule(reqeustId: number, params: GMSend.XHRDetails): Promise<{ [key: string]: string }> { // 检查是否有unsafe header,有则生成dnr规则 const headers = params.headers; - console.log(headers, !headers); if (!headers) { return Promise.resolve({}); } @@ -181,12 +182,42 @@ export default class GMApi { }); } - start() { - this.group.on("gmApi", this.handlerRequest.bind(this)); + @PermissionVerify.API() + GM_registerMenuCommand(request: Request, con: GetSender) { + console.log("registerMenuCommand", request.params); + const [id, name, accessKey] = request.params; + // 触发菜单注册, 在popup中处理 + this.mq.emit("registerMenuCommand", { + uuid: request.script.uuid, + id: id, + name: name, + accessKey: accessKey, + con: con.getConnect(), + }); + con.getConnect().onDisconnect(() => { + // 取消注册 + this.mq.emit("unregisterMenuCommand", { + uuid: request.script.uuid, + name: name, + }); + }); + } + + @PermissionVerify.API() + GM_unregisterMenuCommand(request: Request) { + const [id] = request.params; + // 触发菜单取消注册, 在popup中处理 + this.mq.emit("unregisterMenuCommand", { + uuid: request.script.uuid, + id: id, + }); + } + + // 处理GM_xmlhttpRequest请求 + handlerGmXhr() { chrome.webRequest.onBeforeSendHeaders.addListener( (details) => { if (details.tabId === -1) { - console.log(details); // 判断是否存在X-Scriptcat-GM-XHR-Request-Id // 讲请求id与chrome.webRequest的请求id关联 if (details.requestHeaders) { @@ -228,4 +259,9 @@ export default class GMApi { ["responseHeaders", "extraHeaders"] ); } + + start() { + this.group.on("gmApi", this.handlerRequest.bind(this)); + this.handlerGmXhr(); + } } diff --git a/src/app/service/service_worker/index.ts b/src/app/service/service_worker/index.ts index a41e688..98df8a4 100644 --- a/src/app/service/service_worker/index.ts +++ b/src/app/service/service_worker/index.ts @@ -5,6 +5,7 @@ import { ResourceService } from "./resource"; import { ValueService } from "./value"; import { RuntimeService } from "./runtime"; import { ServiceWorkerMessageSend } from "@Packages/message/window_message"; +import { PopupService } from "./popup"; export type InstallSource = "user" | "system" | "sync" | "subscribe" | "vscode"; @@ -31,5 +32,7 @@ export default class ServiceWorkerManager { script.init(); const runtime = new RuntimeService(this.api.group("runtime"), this.sender, this.mq, value, script); runtime.init(); + const popup = new PopupService(this.api.group("popup"), this.mq, runtime); + popup.init(); } } diff --git a/src/app/service/service_worker/popup.ts b/src/app/service/service_worker/popup.ts new file mode 100644 index 0000000..1ae17c0 --- /dev/null +++ b/src/app/service/service_worker/popup.ts @@ -0,0 +1,26 @@ +import { MessageQueue } from "@Packages/message/message_queue"; +import { GetSender, Group } from "@Packages/message/server"; +import { RuntimeService } from "./runtime"; + +// 处理popup页面的数据 +export class PopupService { + constructor( + private group: Group, + private mq: MessageQueue, + private runtime: RuntimeService + ) {} + + registerMenuCommand(message: { uuid: string; id: string; name: string; accessKey: string; con: GetSender }) { + console.log("registerMenuCommand", message); + } + + unregisterMenuCommand(message: { id: string }) { + console.log("unregisterMenuCommand", message); + } + + init() { + // 处理脚本菜单数据 + this.mq.subscribe("registerMenuCommand", this.registerMenuCommand.bind(this)); + this.mq.subscribe("unregisterMenuCommand", this.unregisterMenuCommand.bind(this)); + } +} diff --git a/src/app/service/service_worker/runtime.ts b/src/app/service/service_worker/runtime.ts index 8185141..c421027 100644 --- a/src/app/service/service_worker/runtime.ts +++ b/src/app/service/service_worker/runtime.ts @@ -34,7 +34,7 @@ export class RuntimeService { async init() { // 启动gm api - const gmApi = new GMApi(this.group, this.sender, this.value); + const gmApi = new GMApi(this.group, this.sender, this.mq, this.value); gmApi.start(); this.group.on("stopScript", this.stopScript.bind(this)); @@ -238,6 +238,15 @@ export class RuntimeService { this.saveScriptMatchInfo(); } + async deleteScriptMatch(uuid: string) { + if (!this.scriptMatchCache) { + await this.loadScriptMatchInfo(); + } + this.scriptMatchCache!.delete(uuid); + this.scriptMatch.del(uuid); + this.saveScriptMatchInfo(); + } + async registryPageScript(script: Script) { if (await Cache.getInstance().has("registryScript:" + script.uuid)) { return; diff --git a/src/pages/components/ScriptMenuList/index.tsx b/src/pages/components/ScriptMenuList/index.tsx index a25e997..c4c91e8 100644 --- a/src/pages/components/ScriptMenuList/index.tsx +++ b/src/pages/components/ScriptMenuList/index.tsx @@ -1,7 +1,5 @@ /* eslint-disable no-nested-ternary */ import React, { useEffect, useState } from "react"; -import MessageInternal from "@App/app/message/internal"; -import { MessageSender } from "@App/app/message/message"; import { ScriptMenu } from "@App/runtime/service_worker/runtime"; import { Button, @@ -21,13 +19,9 @@ import { IconMinus, IconSettings, } from "@arco-design/web-react/icon"; -import IoC from "@App/app/ioc"; -import ScriptController from "@App/app/service/script/controller"; import { SCRIPT_RUN_STATUS_RUNNING } from "@App/app/repo/scripts"; import { RiPlayFill, RiStopFill } from "react-icons/ri"; -import RuntimeController from "@App/runtime/content/runtime"; import { useTranslation } from "react-i18next"; -import { SystemConfig } from "@App/pkg/config/config"; import { ScriptIcons } from "@App/pages/options/routes/utils"; const CollapseItem = Collapse.Item; @@ -51,10 +45,6 @@ const ScriptMenuList: React.FC<{ currentUrl: string; }> = ({ script, isBackscript, currentUrl }) => { const [list, setList] = useState([] as ScriptMenu[]); - const message = IoC.instance(MessageInternal) as MessageInternal; - const scriptCtrl = IoC.instance(ScriptController) as ScriptController; - const runtimeCtrl = IoC.instance(RuntimeController) as RuntimeController; - const systemConfig = IoC.instance(SystemConfig) as SystemConfig; const [expandMenuIndex, setExpandMenuIndex] = useState<{ [key: string]: boolean; }>({}); @@ -70,23 +60,23 @@ const ScriptMenuList: React.FC<{ setList(script); }, [script]); - useEffect(() => { - // 监听脚本运行状态 - const channel = runtimeCtrl.watchRunStatus(); - channel.setHandler(([id, status]: any) => { - setList((prev) => { - const newList = [...prev]; - const index = newList.findIndex((item) => item.id === id); - if (index !== -1) { - newList[index].runStatus = status; - } - return newList; - }); - }); - return () => { - channel.disChannel(); - }; - }, []); + // useEffect(() => { + // // 监听脚本运行状态 + // const channel = runtimeCtrl.watchRunStatus(); + // channel.setHandler(([id, status]: any) => { + // setList((prev) => { + // const newList = [...prev]; + // const index = newList.findIndex((item) => item.id === id); + // if (index !== -1) { + // newList[index].runStatus = status; + // } + // return newList; + // }); + // }); + // return () => { + // channel.disChannel(); + // }; + // }, []); const sendMenuAction = (sender: MessageSender, channelFlag: string) => { let id = sender.tabId; diff --git a/src/pages/popup/index.html b/src/pages/popup.html similarity index 89% rename from src/pages/popup/index.html rename to src/pages/popup.html index d65c49e..a814937 100644 --- a/src/pages/popup/index.html +++ b/src/pages/popup.html @@ -14,11 +14,8 @@ padding: 0; border: 0; width: 320px; - /* height: 500px; */ min-height: 150px; max-height: 500px; - /* overflow-y: auto; */ - /* overflow: hidden; */ } diff --git a/src/pages/popup/App.css b/src/pages/popup/App.css deleted file mode 100644 index 1b83399..0000000 --- a/src/pages/popup/App.css +++ /dev/null @@ -1,41 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a > .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/pages/popup/App.tsx b/src/pages/popup/App.tsx index 072d69f..eb5fa58 100644 --- a/src/pages/popup/App.tsx +++ b/src/pages/popup/App.tsx @@ -1,31 +1,221 @@ -import { useState } from "react"; -import reactLogo from "@App/assets/logo.png"; -import "./App.css"; +import { ExtVersion } from "@App/app/const"; +import { Alert, Badge, Button, Card, Collapse, Dropdown, Menu, Switch } from "@arco-design/web-react"; +import { + IconBook, + IconBug, + IconGithub, + IconHome, + IconMoreVertical, + IconNotification, + IconPlus, + IconSearch, +} from "@arco-design/web-react/icon"; +import React, { useEffect, useState } from "react"; +import { RiMessage2Line } from "react-icons/ri"; +import semver from "semver"; +import { useTranslation } from "react-i18next"; +import ScriptMenuList from "../components/ScriptMenuList"; +import { ScriptMenu } from "@App/runtime/service_worker/runtime"; + +const CollapseItem = Collapse.Item; + +const iconStyle = { + marginRight: 8, + fontSize: 16, + transform: "translateY(1px)", +}; function App() { - const [count, setCount] = useState(0); + const [scriptList, setScriptList] = useState([]); + const [backScriptList, setBackScriptList] = useState([]); + const [showAlert, setShowAlert] = useState(false); + const [notice, setNotice] = useState(""); + const [isRead, setIsRead] = useState(true); + const [version, setVersion] = useState(ExtVersion); + const [currentUrl, setCurrentUrl] = useState(""); + const [isEnableScript, setIsEnableScript] = useState(localStorage.enable_script !== "false"); + const { t } = useTranslation(); - return ( -
-
- - React logo - -
-

Rspack + React3 + TypeScript

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Rspack and React logos to learn more -

-
- ); + let url: URL | undefined; + try { + url = new URL(currentUrl); + } catch (e) { + // ignore error + } + +// const message = IoC.instance(MessageInternal) as MessageInternal; +// useEffect(() => { +// systemManage.getNotice().then((res) => { +// if (res) { +// setNotice(res.notice); +// setIsRead(res.isRead); +// } +// }); +// systemManage.getVersion().then((res) => { +// res && setVersion(res); +// }); +// chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { +// if (!tabs.length) { +// return; +// } +// setCurrentUrl(tabs[0].url || ""); +// message +// .syncSend("queryPageScript", { url: tabs[0].url, tabId: tabs[0].id }) +// .then((resp: { scriptList: ScriptMenu[]; backScriptList: ScriptMenu[] }) => { +// // 按照开启状态和更新时间排序 +// const list = resp.scriptList; +// list.sort((a, b) => { +// if (a.enable === b.enable) { +// if (a.runNum !== b.runNum) { +// return b.runNum - a.runNum; +// } +// return b.updatetime - a.updatetime; +// } +// return a.enable ? -1 : 1; +// }); +// setScriptList(list); +// setBackScriptList(resp.backScriptList); +// }); +// }); +// }, []); + return ( + + ScriptCat +
+ { + setIsEnableScript(val); + if (val) { + localStorage.enable_script = "true"; + } else { + localStorage.enable_script = "false"; + } + }} + /> +
+ + } + bodyStyle={{ padding: 0 }} + > + } + /> + + + + + + + + + +
+ {`v${ExtVersion}`} + {semver.lt(ExtVersion, version) && ( + { + window.open(`https://github.com/scriptscat/scriptcat/releases/tag/v${version}`); + }} + className="text-1 font-500" + style={{ cursor: "pointer" }} + > + {t("popup.new_version_available")} + + )} +
+
+ ); } export default App; diff --git a/src/pages/popup/index.css b/src/pages/popup/index.css index 917888c..f71ef6e 100644 --- a/src/pages/popup/index.css +++ b/src/pages/popup/index.css @@ -1,70 +1,19 @@ -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; +.arco-collapse-item-header-title { + width: 100%; } -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; +.arco-collapse-item-header-title .arco-space { + width: 100%; } -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; +.arco-space-item:last-child { + overflow: hidden; } -h1 { - font-size: 3.2em; - line-height: 1.1; +.arco-collapse { + border-bottom: 1px solid var(--color-neutral-3) !important; } -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +.arco-collapse-item { + border: 0; } diff --git a/src/pages/popup/main.tsx b/src/pages/popup/main.tsx index 29baf78..18bd8c0 100644 --- a/src/pages/popup/main.tsx +++ b/src/pages/popup/main.tsx @@ -1,10 +1,36 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; -import "./index.css"; +import LoggerCore from "@App/app/logger/core.ts"; +import migrate from "@App/app/migrate.ts"; +import { LoggerDAO } from "@App/app/repo/logger.ts"; +import DBWriter from "@App/app/logger/db_writer.ts"; +import "@arco-design/web-react/dist/css/arco.css"; +import "@App/locales/locales"; +import "@App/index.css"; +import { Provider } from "react-redux"; +import { store } from "../store/store.ts"; + +// 初始化数据库 +migrate(); +// 初始化日志组件 +const loggerCore = new LoggerCore({ + writer: new DBWriter(new LoggerDAO()), + labels: { env: "install" }, +}); + +loggerCore.logger().debug("page start"); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - + + +
+ +
+
+
); diff --git a/src/runtime/content/content.ts b/src/runtime/content/content.ts index 6b0ea7b..a3bbd81 100644 --- a/src/runtime/content/content.ts +++ b/src/runtime/content/content.ts @@ -11,20 +11,13 @@ export default class ContentRuntime { ) {} start(scripts: ScriptRunResouce[]) { - this.msg.onMessage((msg, sendResponse) => { - console.log("content onMessage", msg); - }); - this.msg.onConnect((msg, connect) => { - console.log(msg, connect); - }); forwardMessage( "serviceWorker", "runtime/gmApi", this.server, this.send, (data: { api: string; params: any }, con: GetSender) => { - // 拦截关注的action - console.log("拦截", data); + // 拦截关注的api switch (data.api) { case "CAT_createBlobUrl": { const file = data.params[0] as File; diff --git a/src/runtime/content/gm_api.ts b/src/runtime/content/gm_api.ts index e342bca..13c30bd 100644 --- a/src/runtime/content/gm_api.ts +++ b/src/runtime/content/gm_api.ts @@ -198,6 +198,53 @@ export default class GMApi { return (this.message).getAndDelRelatedTarget(data.relatedTarget); } + menuId: number | undefined; + + menuMap: Map | undefined; + + @GMContext.API() + GM_registerMenuCommand(name: string, listener: () => void, accessKey?: string): number { + if (!this.menuMap) { + this.menuMap = new Map(); + } + let flag = 0; + this.menuMap.forEach((val, key) => { + if (val === name) { + flag = key; + } + }); + if (flag) { + return flag; + } + if (!this.menuId) { + this.menuId = 1; + } else { + this.menuId += 1; + } + const id = this.menuId; + this.connect("GM_registerMenuCommand", [id, name, accessKey]).then((con) => { + con.onMessage((data: { action: string; data: any }) => { + if (data.action === "onClick") { + listener(); + } + }); + con.onDisconnect(() => { + this.menuMap?.delete(id); + }); + }); + this.menuMap.set(id, name); + return id; + } + + @GMContext.API() + GM_unregisterMenuCommand(id: number): void { + if (!this.menuMap) { + this.menuMap = new Map(); + } + this.menuMap.delete(id); + this.sendMessage("GM_unregisterMenuCommand", [id]); + } + // 用于脚本跨域请求,需要@connect domain指定允许的域名 @GMContext.API({ depend: ["CAT_fetchBlob", "CAT_createBlobUrl", "CAT_fetchDocument"], @@ -252,7 +299,6 @@ export default class GMApi { values.map(async (val) => { if (val instanceof File) { const url = await this.CAT_createBlobUrl(val); - console.log(url); data.push({ key, type: "file",