Skip to content
Open
7 changes: 7 additions & 0 deletions packages/eslint/compat-headers.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// compat-headers.js 是给 rspack alias 用的 CommonJS 覆盖文件(见 rspack.config.ts),
// 这里只声明测试与类型检查需要的形状。
export declare const compatMap: {
localized: Record<string, unknown>;
unlocalized: Record<string, unknown>;
nonFunctional: Record<string, unknown>;
};
1 change: 1 addition & 0 deletions packages/eslint/compat-headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const compatMap = {
storageName: [],
"early-start": [],
"require-css": [],
definition: [],
allFrames: [],
},
};
Expand Down
6 changes: 6 additions & 0 deletions src/app/service/content/gm_api/gm_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export function GMContextApiGet(name: string): ApiValue[] | undefined {
return apis.get(name);
}

// 已注册的全部 @grant 名。注册表由装饰器在模块载入时填充,无法静态推导,
// 供 script_compat.ts 的静态支持表做一致性守卫(新增 GM API 若漏进表会被测出来)。
export function GMContextApiNames(): string[] {
return [...apis.keys()];
}

function GMContextApiSet(grant: string, fnKey: string, api: any, param: ApiParam): void {
// 一个 @grant 可以扩充多个 API 函数
let m: ApiValue[] | undefined = apis.get(grant);
Expand Down
12 changes: 11 additions & 1 deletion src/locales/de-DE/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "Update-Inhalt ist abgelaufen",
"expired_desc": "Der für dieses Update vorbereitete Code wurde bereinigt. Bitte erneut nach Updates suchen.",
"expired_recheck": "Erneut nach Updates suchen",
"code_loading": "Code wird geladen"
"code_loading": "Code wird geladen",
"compat_ineffective": "ohne Wirkung",
"compat_tag_desc": "ScriptCat unterstützt diese Deklaration nicht; sie wird nach der Installation ignoriert.",
"compat_value_desc": "ScriptCat unterstützt diesen Wert nicht; die Deklaration wirkt nicht wie angegeben.",
"compat_grant_desc": "ScriptCat implementiert diese API nicht; Aufrufe schlagen fehl und darauf aufbauende Funktionen arbeiten nicht.",
"compat_jump": "Zu Zeile {{line}} springen",
"compat_docs": "Metadaten-Dokumentation",
"compat_count": "{{count}} ohne Wirkung",
"compat_scriptcat_only": "nur ScriptCat",
"perm_other_label": "Weitere Deklarationen",
"perm_other_summary": "Verhält sich anders als in anderen Skriptmanagern"
}
12 changes: 11 additions & 1 deletion src/locales/en-US/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "Update content has expired",
"expired_desc": "The code prepared for this update has been cleaned up. Please check for updates again.",
"expired_recheck": "Check for updates again",
"code_loading": "Loading code"
"code_loading": "Loading code",
"compat_ineffective": "no effect",
"compat_tag_desc": "ScriptCat does not support this declaration; it is ignored after installation.",
"compat_value_desc": "ScriptCat does not support this value; the declaration won't take effect as written.",
"compat_grant_desc": "ScriptCat has not implemented this API; calls to it fail and features relying on it won't work.",
"compat_jump": "Go to line {{line}}",
"compat_docs": "Metadata docs",
"compat_count": "{{count}} with no effect",
"compat_scriptcat_only": "ScriptCat only",
"perm_other_label": "Other declarations",
"perm_other_summary": "Behaves differently from other script managers"
}
12 changes: 11 additions & 1 deletion src/locales/ja-JP/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "更新内容の有効期限が切れました",
"expired_desc": "この更新のために用意されたコードは削除されました。もう一度更新を確認してください。",
"expired_recheck": "更新を再確認",
"code_loading": "コードを読み込み中"
"code_loading": "コードを読み込み中",
"compat_ineffective": "無効",
"compat_tag_desc": "ScriptCat はこの宣言に対応していないため、インストール後は無視されます。",
"compat_value_desc": "ScriptCat はこの値に対応していないため、宣言は記述どおりには機能しません。",
"compat_grant_desc": "ScriptCat はこの API を実装していないため、スクリプトから呼び出すとエラーになり、これに依存する機能は動作しません。",
"compat_jump": "{{line}} 行目へ移動",
"compat_docs": "メタデータのドキュメント",
"compat_count": "無効な項目 {{count}} 件",
"compat_scriptcat_only": "ScriptCat 専用",
"perm_other_label": "その他の宣言",
"perm_other_summary": "他のスクリプトマネージャーとは互換性が異なります"
}
12 changes: 11 additions & 1 deletion src/locales/ko-KR/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "업데이트 내용이 만료되었습니다",
"expired_desc": "이번 업데이트를 위해 준비한 코드가 정리되었습니다. 업데이트를 다시 확인해 주세요.",
"expired_recheck": "업데이트 다시 확인",
"code_loading": "코드를 불러오는 중"
"code_loading": "코드를 불러오는 중",
"compat_ineffective": "적용되지 않음",
"compat_tag_desc": "ScriptCat은 이 선언을 지원하지 않으므로 설치 후 무시됩니다.",
"compat_value_desc": "ScriptCat은 이 값을 지원하지 않으므로 선언이 작성한 대로 적용되지 않습니다.",
"compat_grant_desc": "ScriptCat은 이 API를 구현하지 않았습니다. 스크립트에서 호출하면 오류가 발생하고 이에 의존하는 기능은 동작하지 않습니다.",
"compat_jump": "{{line}}번째 줄로 이동",
"compat_docs": "메타데이터 문서",
"compat_count": "적용되지 않는 항목 {{count}}개",
"compat_scriptcat_only": "ScriptCat 전용",
"perm_other_label": "기타 선언",
"perm_other_summary": "다른 스크립트 관리자와 호환성이 다릅니다"
}
12 changes: 11 additions & 1 deletion src/locales/pt-BR/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "O conteúdo da atualização expirou",
"expired_desc": "O código preparado para esta atualização foi removido. Verifique as atualizações novamente.",
"expired_recheck": "Verificar atualizações novamente",
"code_loading": "Carregando código"
"code_loading": "Carregando código",
"compat_ineffective": "sem efeito",
"compat_tag_desc": "O ScriptCat não oferece suporte a esta declaração; ela é ignorada após a instalação.",
"compat_value_desc": "O ScriptCat não oferece suporte a este valor; a declaração não terá o efeito escrito.",
"compat_grant_desc": "O ScriptCat não implementou esta API; as chamadas falham e os recursos que dependem dela não funcionam.",
"compat_jump": "Ir para a linha {{line}}",
"compat_docs": "Documentação de metadados",
"compat_count": "{{count}} sem efeito",
"compat_scriptcat_only": "Somente ScriptCat",
"perm_other_label": "Outras declarações",
"perm_other_summary": "Comportamento diferente de outros gerenciadores de scripts"
}
12 changes: 11 additions & 1 deletion src/locales/ru-RU/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "Содержимое обновления устарело",
"expired_desc": "Код, подготовленный для этого обновления, был очищен. Проверьте обновления ещё раз.",
"expired_recheck": "Проверить обновления ещё раз",
"code_loading": "Загрузка кода"
"code_loading": "Загрузка кода",
"compat_ineffective": "не действует",
"compat_tag_desc": "ScriptCat не поддерживает это объявление — после установки оно игнорируется.",
"compat_value_desc": "ScriptCat не поддерживает это значение — объявление не сработает так, как написано.",
"compat_grant_desc": "ScriptCat не реализует этот API: вызов из скрипта завершится ошибкой, а зависящие от него функции работать не будут.",
"compat_jump": "Перейти к строке {{line}}",
"compat_docs": "Документация по метаданным",
"compat_count": "Не действует: {{count}}",
"compat_scriptcat_only": "только ScriptCat",
"perm_other_label": "Прочие объявления",
"perm_other_summary": "Работает не так, как в других менеджерах скриптов"
}
12 changes: 11 additions & 1 deletion src/locales/tr-TR/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "Güncelleme içeriğinin süresi doldu",
"expired_desc": "Bu güncelleme için hazırlanan kod temizlendi. Lütfen güncellemeleri yeniden denetleyin.",
"expired_recheck": "Güncellemeleri yeniden denetle",
"code_loading": "Kod yükleniyor"
"code_loading": "Kod yükleniyor",
"compat_ineffective": "etkisiz",
"compat_tag_desc": "ScriptCat bu bildirimi desteklemiyor; kurulumdan sonra yok sayılır.",
"compat_value_desc": "ScriptCat bu değeri desteklemiyor; bildirim yazıldığı gibi uygulanmaz.",
"compat_grant_desc": "ScriptCat bu API'yi uygulamadı; betik çağırdığında hata verir ve buna dayanan özellikler çalışmaz.",
"compat_jump": "{{line}}. satıra git",
"compat_docs": "Meta veri belgeleri",
"compat_count": "{{count}} etkisiz",
"compat_scriptcat_only": "Yalnızca ScriptCat",
"perm_other_label": "Diğer bildirimler",
"perm_other_summary": "Diğer betik yöneticilerinden farklı davranır"
}
12 changes: 11 additions & 1 deletion src/locales/vi-VN/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "Nội dung cập nhật đã hết hạn",
"expired_desc": "Mã đã chuẩn bị cho lần cập nhật này đã bị dọn dẹp. Vui lòng kiểm tra cập nhật lại.",
"expired_recheck": "Kiểm tra cập nhật lại",
"code_loading": "Đang tải mã"
"code_loading": "Đang tải mã",
"compat_ineffective": "không có tác dụng",
"compat_tag_desc": "ScriptCat không hỗ trợ khai báo này; nó sẽ bị bỏ qua sau khi cài đặt.",
"compat_value_desc": "ScriptCat không hỗ trợ giá trị này; khai báo sẽ không có hiệu lực như đã viết.",
"compat_grant_desc": "ScriptCat chưa triển khai API này; script gọi đến sẽ báo lỗi và các tính năng phụ thuộc vào nó sẽ không hoạt động.",
"compat_jump": "Đến dòng {{line}}",
"compat_docs": "Tài liệu metadata",
"compat_count": "{{count}} mục không có tác dụng",
"compat_scriptcat_only": "Chỉ ScriptCat",
"perm_other_label": "Khai báo khác",
"perm_other_summary": "Hoạt động khác với các trình quản lý script khác"
}
12 changes: 11 additions & 1 deletion src/locales/zh-CN/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "更新内容已过期",
"expired_desc": "这次更新准备好的代码已被清理,需要重新检查一次更新。",
"expired_recheck": "重新检查更新",
"code_loading": "正在加载代码"
"code_loading": "正在加载代码",
"compat_ineffective": "不生效",
"compat_tag_desc": "脚本猫不支持该声明,安装后会被忽略。",
"compat_value_desc": "脚本猫不支持这个取值,该声明不会按写法生效。",
"compat_grant_desc": "脚本猫未实现该 API,脚本调用时会报错,依赖它的功能不可用。",
"compat_jump": "跳到第 {{line}} 行",
"compat_docs": "描述文档",
"compat_count": "{{count}} 项不生效",
"compat_scriptcat_only": "仅限脚本猫",
"perm_other_label": "其他声明",
"perm_other_summary": "兼容性与其他脚本管理器不同"
}
12 changes: 11 additions & 1 deletion src/locales/zh-TW/install.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,15 @@
"expired_title": "更新內容已過期",
"expired_desc": "這次更新準備好的程式碼已被清理,需要重新檢查一次更新。",
"expired_recheck": "重新檢查更新",
"code_loading": "正在載入程式碼"
"code_loading": "正在載入程式碼",
"compat_ineffective": "不生效",
"compat_tag_desc": "腳本貓不支援此宣告,安裝後會被忽略。",
"compat_value_desc": "腳本貓不支援此取值,這項宣告不會照寫法生效。",
"compat_grant_desc": "腳本貓尚未實作此 API,腳本呼叫時會出錯,依賴它的功能無法使用。",
"compat_jump": "跳至第 {{line}} 行",
"compat_docs": "描述文件",
"compat_count": "{{count}} 項不生效",
"compat_scriptcat_only": "僅限腳本貓",
"perm_other_label": "其他宣告",
"perm_other_summary": "相容性與其他腳本管理器不同"
}
53 changes: 51 additions & 2 deletions src/pages/components/CodeEditor/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { render, cleanup, act, waitFor } from "@testing-library/react";
// 用 hoisted 持有可在测试内变更的主题与 monaco 桩,供被提升的 vi.mock 工厂引用
const h = vi.hoisted(() => {
const makeEditor = () => ({
revealLineInCenter: vi.fn(),
setSelection: vi.fn(),
setModel: vi.fn(),
setValue: vi.fn(),
updateOptions: vi.fn(),
Expand All @@ -18,7 +20,10 @@ const h = vi.hoisted(() => {
return {
resolvedTheme: "light" as string,
setTheme: vi.fn(),
createDiffEditor: vi.fn((_container?: unknown, _options?: any) => makeEditor()),
createDiffEditor: vi.fn((_container?: unknown, _options?: any) => {
const modified = makeEditor();
return { ...makeEditor(), getModifiedEditor: () => modified, __modified: modified };
}),
create: vi.fn((_container?: unknown, _options?: any) => makeEditor()),
createModel: vi.fn(() => ({
dispose: vi.fn(),
Expand All @@ -36,7 +41,14 @@ vi.mock("monaco-editor", () => ({
setTheme: h.setTheme,
setModelMarkers: vi.fn(),
},
Range: class {},
Range: class {
constructor(
public startLineNumber: number,
public startColumn: number,
public endLineNumber: number,
public endColumn: number
) {}
},
}));
vi.mock("./theme", () => ({ resolveMonacoTheme: (t: string) => t }));
vi.mock("@App/pkg/utils/monaco-editor", () => ({
Expand Down Expand Up @@ -126,3 +138,40 @@ describe("CodeEditor 可访问性与主题", () => {
expect(h.setTheme).toHaveBeenCalledWith("dark");
});
});

describe("CodeEditor 就绪信号与定位", () => {
it("内联 diff 也报告就绪——否则加载占位会一直留在无障碍树里说「正在加载」", async () => {
const onReady = vi.fn();
render(<CodeEditor id="ed-ready-diff" code="const a = 2;" diffCode="const a = 1;" onReady={onReady} />);
await waitFor(() => expect(onReady).toHaveBeenCalled());
});

it("普通编辑器同样报告就绪", async () => {
const onReady = vi.fn();
await act(async () => {
render(<CodeEditor id="ed-ready-plain" code="const a = 1;" diffCode="" onReady={onReady} />);
});
await waitFor(() => expect(onReady).toHaveBeenCalled());
});

it("revealLine 在普通编辑器上滚动并选中整行", async () => {
const ref = createRef<ComponentRef<typeof CodeEditor>>();
await act(async () => {
render(<CodeEditor ref={ref} id="ed-reveal" code={"a\nb\nc"} diffCode="" />);
});
await waitFor(() => expect(h.create).toHaveBeenCalled());
act(() => ref.current?.revealLine(2));
const instance = h.create.mock.results[0].value;
expect(instance.revealLineInCenter).toHaveBeenCalledWith(2);
expect(instance.setSelection).toHaveBeenCalled();
});

it("revealLine 在 diff 预览里定位到修改侧——诊断说的是新版本的那一行", async () => {
const ref = createRef<ComponentRef<typeof CodeEditor>>();
render(<CodeEditor ref={ref} id="ed-reveal-diff" code={"a\nb"} diffCode={"a"} />);
await waitFor(() => expect(h.createDiffEditor).toHaveBeenCalled());
act(() => ref.current?.revealLine(2));
const modified = (h.createDiffEditor.mock.results[0].value as any).__modified;
expect(modified.revealLineInCenter).toHaveBeenCalledWith(2);
});
});
31 changes: 28 additions & 3 deletions src/pages/components/CodeEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,25 @@ import { clearModelEslintFixes, getModelEslintFixKey } from "@App/pkg/utils/mona
import { useTheme } from "@App/pages/components/theme-provider";
import { resolveMonacoTheme } from "./theme";

export interface CodeEditorHandle {
/** 普通编辑器实例;diff 预览没有可编辑实例,为 undefined */
editor: editor.IStandaloneCodeEditor | undefined;
/** 滚动到指定行并选中整行;diff 预览定位到修改侧 */
revealLine: (line: number) => void;
}

type Props = {
ref?: Ref<{ editor: editor.IStandaloneCodeEditor | undefined }>;
ref?: Ref<CodeEditorHandle>;
className?: string;
diffCode?: string; // 代码加载是异步的:undefined=不确定(不加载),""=无 diff,有值=diff
editable?: boolean;
id: string;
code?: string;
onChange?: (code: string) => void;
/** 普通编辑器实例就绪;diff 预览没有可编辑实例,不会触发 */
onEditorMount?: (editor: editor.IStandaloneCodeEditor) => void;
/** 编辑器已创建(含 diff 预览);用于收起加载占位 */
onReady?: () => void;
};

type TMarker = {
Expand Down Expand Up @@ -44,7 +54,7 @@ function toMonacoEditorPreferenceOptions(
} satisfies editor.IEditorOptions;
}

function CodeEditor({ id, className, code, diffCode, editable, onChange, onEditorMount, ref }: Props) {
function CodeEditor({ id, className, code, diffCode, editable, onChange, onEditorMount, onReady, ref }: Props) {
const [monacoEditor, setEditor] = useState<editor.IStandaloneCodeEditor>();
const editorInstanceRef = useRef<editor.IStandaloneCodeEditor | editor.IStandaloneDiffEditor | undefined>(undefined);
// 普通 editor 与 diff editor 都会置位,供主题切换 effect 判断实例是否就绪
Expand All @@ -58,14 +68,27 @@ function CodeEditor({ id, className, code, diffCode, editable, onChange, onEdito
// 用 ref 保存最新回调,避免 stale closure 同时不让创建 effect 重跑
const onChangeRef = useRef(onChange);
const onEditorMountRef = useRef(onEditorMount);
const onReadyRef = useRef(onReady);
// ref 赋值须在创建 effect 之前,确保 mount 时创建 effect 同步读到最新 onEditorMount
useEffect(() => {
onChangeRef.current = onChange;
onEditorMountRef.current = onEditorMount;
onReadyRef.current = onReady;
});

const divRef = useRef<HTMLDivElement>(null);
useImperativeHandle(ref, () => ({ editor: monacoEditor }));
useImperativeHandle(ref, () => ({
editor: monacoEditor,
revealLine: (line: number) => {
const instance = editorInstanceRef.current;
if (!instance) return;
// diff 预览的行号说的是新版本,定位到修改侧
const target = "getModifiedEditor" in instance ? instance.getModifiedEditor() : instance;
target.revealLineInCenter(line);
const maxColumn = target.getModel()?.getLineMaxColumn(line) ?? 1;
target.setSelection(new Range(line, 1, line, maxColumn));
},
}));

// 注册 monaco 全局环境(只需执行一次)
useEffect(() => {
Expand Down Expand Up @@ -180,6 +203,7 @@ function CodeEditor({ id, className, code, diffCode, editable, onChange, onEdito
});
editorInstanceRef.current = edit;
editorReadyRef.current = true;
onReadyRef.current?.();
} else {
const standaloneEdit = editor.create(container, {
language: "javascript",
Expand All @@ -199,6 +223,7 @@ function CodeEditor({ id, className, code, diffCode, editable, onChange, onEdito
editorInstanceRef.current = standaloneEdit;
editorReadyRef.current = true;
onEditorMountRef.current?.(standaloneEdit);
onReadyRef.current?.();
}
});

Expand Down
Loading
Loading