Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 28 additions & 24 deletions src/app/service/content/gm_api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Message, MessageConnect } from "@Packages/message/types";
import type { CustomEventMessage } from "@Packages/message/custom_event_message";
import type { NotificationMessageOption, ScriptMenuItem } from "../service_worker/types";
import type { NotificationMessageOption, ScriptMenuItemOption } from "../service_worker/types";
import { base64ToBlob, strToBase64 } from "@App/pkg/utils/utils";
import LoggerCore from "@App/app/logger/core";
import EventEmitter from "eventemitter3";
Expand Down Expand Up @@ -446,39 +446,43 @@ export default class GMApi extends GM_Base {
GM_registerMenuCommand(
name: string,
listener: (inputValue?: any) => void,
options_or_accessKey?: ScriptMenuItem["options"] | string
options_or_accessKey?: ScriptMenuItemOption | string
): number {
if (!this.menuMap) {
this.menuMap = new Map();
}
if (typeof options_or_accessKey === "object") {
const option: ScriptMenuItem["options"] = options_or_accessKey;
// 如果是对象,并且有id属性,则直接使用id
if (option.id && this.menuMap.has(option.id)) {
// 如果id存在,则直接使用
this.EE.removeAllListeners("menuClick:" + option.id);
this.EE.addListener("menuClick:" + option.id, listener);
this.sendMessage("GM_registerMenuCommand", [option.id, name, option]);
return option.id;
}
} else {
options_or_accessKey = { accessKey: options_or_accessKey };
let flag = 0;
this.menuMap.forEach((val, menuId) => {
if (val === name) {
flag = menuId;
}
});
if (flag) {
return flag;
const options: ScriptMenuItemOption =
typeof options_or_accessKey === "string"
? ({ accessKey: options_or_accessKey! } as ScriptMenuItemOption)
: options_or_accessKey! || ({} as ScriptMenuItemOption);

if (options.autoClose === undefined) {
options.autoClose = true;
}

if (options.nested === undefined) {
options.nested = true;
}

// 如果是对象,并且有id属性,则直接使用id
if (options.id && this.menuMap.has(options.id)) {
// 如果id存在,则直接使用
this.EE.removeAllListeners("menuClick:" + options.id);
this.EE.addListener("menuClick:" + options.id, listener);
this.sendMessage("GM_registerMenuCommand", [options.id, name, options]);
return options.id;
}
for (const [mappedId, mappedName] of this.menuMap) {
if (mappedName === name) {
return mappedId;
}
}
this.eventId += 1;
const id = this.eventId;
options_or_accessKey.id = id;
options.id = id;
this.menuMap.set(id, name);
this.EE.addListener("menuClick:" + id, listener);
this.sendMessage("GM_registerMenuCommand", [id, name, options_or_accessKey]);
this.sendMessage("GM_registerMenuCommand", [id, name, options]);
return id;
}

Expand Down
97 changes: 63 additions & 34 deletions src/app/service/service_worker/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,60 @@ export class PopupService {
private systemConfig: SystemConfig
) {}

genScriptMenuByTabMap(menu: ScriptMenu[]) {
let n = 0;
genScriptMenuByTabMap(menuEntries: chrome.contextMenus.CreateProperties[], menu: ScriptMenu[]) {
for (const { uuid, name, menus } of menu) {
// 如果是带输入框的菜单则不在页面内注册
const nonInputMenus = menus.filter((item) => !item.options?.inputType);
// 创建脚本菜单
if (nonInputMenus.length) {
n += nonInputMenus.length;
chrome.contextMenus.create({
id: `scriptMenu_${uuid}`,
title: name,
contexts: ["all"],
parentId: "scriptMenu",
});
nonInputMenus.forEach((menu) => {
// 创建菜单
chrome.contextMenus.create({
id: `scriptMenu_menu_${uuid}_${menu.id}`,
title: menu.name,
const subMenuEntries = [] as chrome.contextMenus.CreateProperties[];
let withMenuItem = false;
// eslint-disable-next-line prefer-const
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用 eslint-disable 注释来绕过 prefer-const 规则不是最佳实践。既然需要修改 name 变量,应该使用 let 声明并移除这个 eslint-disable 注释。

Suggested change
// eslint-disable-next-line prefer-const

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不要

for (let { id, name, options } of menus) {
// 如果是带输入框的菜单则不在页面内注册
if (options?.inputType) continue;
let level = 3;
if (options?.nested === false) level = 2;
if (name[0] === "\xA7") {
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

使用十六进制转义序列 \xA7 不如直接使用字符 § 清晰易读。建议改为 if (name[0] === '§') { 以提高代码可读性。

Suggested change
if (name[0] === "\xA7") {
if (name[0] === "§") {

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

避免build后脚本代码包括非通用ASCII字符

// section sign (§)
level = 2;
name = name.substring(1);
// chrome.contextMenus的API限制:不支持一级菜单创建 (不支持 §§)
}
let createProperties: chrome.contextMenus.CreateProperties;
name = name.trim();
if (!name.length) {
// 创建菜单分隔线
createProperties = {
id: `scriptMenu_menu_${uuid}_${id}`,
type: "separator",
contexts: ["all"],
parentId: `scriptMenu_${uuid}`,
});
});
};
} else {
// 创建菜单项目
createProperties = {
id: `scriptMenu_menu_${uuid}_${id}`,
title: name,
contexts: ["all"],
};
withMenuItem = true;
}
if (level === 3) {
createProperties.parentId = `scriptMenu_${uuid}`;
} else if (level === 2) {
createProperties.parentId = `scriptMenu`;
}
subMenuEntries.push(createProperties);
}
if (withMenuItem) {
menuEntries.push(
{
// 创建脚本菜单
id: `scriptMenu_${uuid}`,
title: name,
contexts: ["all"],
parentId: "scriptMenu",
},
...subMenuEntries
);
}
}
return n;
}

// 生成chrome菜单
Expand All @@ -82,23 +110,24 @@ export class PopupService {
if (!menu.length && !backgroundMenu.length) {
return;
}
let n = 0;
// 创建根菜单
chrome.contextMenus.create({
id: "scriptMenu",
title: "ScriptCat",
contexts: ["all"],
});
const menuEntries = [] as chrome.contextMenus.CreateProperties[];
if (menu) {
n += this.genScriptMenuByTabMap(menu);
this.genScriptMenuByTabMap(menuEntries, menu);
}
// 后台脚本的菜单
if (backgroundMenu) {
n += this.genScriptMenuByTabMap(backgroundMenu);
this.genScriptMenuByTabMap(menuEntries, backgroundMenu);
}
if (n === 0) {
// 如果没有菜单,删除菜单
await chrome.contextMenus.remove("scriptMenu");
if (menuEntries.length > 0) {
// 创建根菜单
menuEntries.unshift({
id: "scriptMenu",
title: "ScriptCat",
contexts: ["all"],
});
for (const menuEntry of menuEntries) {
chrome.contextMenus.create(menuEntry);
}
}
}

Expand Down
25 changes: 14 additions & 11 deletions src/app/service/service_worker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,23 @@ export type Api = (request: Request, con: IGetSender) => Promise<any>;

// popup

export type ScriptMenuItemOption = {
id?: number;
autoClose?: boolean; // default true
title?: string;
accessKey?: string;
nested?: boolean; // default true
// 可选输入框
inputType?: "text" | "number" | "boolean";
inputLabel?: string;
inputDefaultValue?: string | number | boolean;
inputPlaceholder?: string;
};

export type ScriptMenuItem = {
id: number;
name: string;
options?: {
id?: number;
autoClose?: boolean;
title?: string;
accessKey?: string;
// 可选输入框
inputType?: "text" | "number" | "boolean";
inputLabel?: string;
inputDefaultValue?: string | number | boolean;
inputPlaceholder?: string;
};
options?: ScriptMenuItemOption;
tabId: number; //-1表示后台脚本
frameId?: number;
documentId?: string;
Expand Down
5 changes: 3 additions & 2 deletions src/pages/components/ScriptMenuList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ const MenuItem = React.memo(({ menu, uuid }: MenuItemProps) => {
return null;
}
})();

const menuName = menu.name.replace(/^\xA7+/, "").trim();
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

与上面相同,这里的正则表达式使用 \xA7 不如使用 § 字符清晰。建议改为 /^§+/ 以提高代码可读性。

Suggested change
const menuName = menu.name.replace(/^\xA7+/, "").trim();
const menuName = menu.name.replace(/^§+/, "").trim();

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

避免build后脚本代码包括非通用ASCII字符

if (!menuName) return <></>;
return (
<Form
initialValues={{ inputValue: initialValue }}
Expand All @@ -84,7 +85,7 @@ const MenuItem = React.memo(({ menu, uuid }: MenuItemProps) => {
title={menu.options?.title}
style={{ display: "block", width: "100%" }}
>
{menu.name}
{menuName}
{menu.options?.accessKey && `(${menu.options.accessKey.toUpperCase()})`}
</Button>
{InputMenu && (
Expand Down
Loading