diff --git a/src/pages/components/CodeEditor/index.tsx b/src/pages/components/CodeEditor/index.tsx index 6cde1e74d..894ad2437 100644 --- a/src/pages/components/CodeEditor/index.tsx +++ b/src/pages/components/CodeEditor/index.tsx @@ -135,6 +135,8 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod largeFileOptimizations: true, colorDecorators: true, } as const; + let originalModel: editor.ITextModel | undefined; + let modifiedModel: editor.ITextModel | undefined; if (diffCode) { edit = editor.createDiffEditor(inlineDiv, { hideUnchangedRegions: { @@ -146,9 +148,12 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod diffWordWrap: "off", ...commonEditorOptions, }); + // standalone model 不会随 editor.dispose 自动清理,需手动跟踪并在 cleanup 释放 + originalModel = editor.createModel(diffCode, "javascript"); + modifiedModel = editor.createModel(code, "javascript"); edit.setModel({ - original: editor.createModel(diffCode, "javascript"), - modified: editor.createModel(code, "javascript"), + original: originalModel, + modified: modifiedModel, }); } else { edit = editor.create(inlineDiv, { @@ -165,6 +170,8 @@ const CodeEditor: React.ForwardRefRenderFunction<{ editor: editor.IStandaloneCod // 目前会出现:Uncaught (in promise) Canceled: Canceled // 问题追踪:https://github.com/microsoft/monaco-editor/issues/4702 edit?.dispose(); + originalModel?.dispose(); + modifiedModel?.dispose(); }; }, [div, code, diffCode, editable, id]); diff --git a/src/pages/options/routes/script/ScriptEditor.tsx b/src/pages/options/routes/script/ScriptEditor.tsx index 8fc3322d8..51a511745 100644 --- a/src/pages/options/routes/script/ScriptEditor.tsx +++ b/src/pages/options/routes/script/ScriptEditor.tsx @@ -1,9 +1,9 @@ import type { Script } from "@App/app/repo/scripts"; import { SCRIPT_TYPE_NORMAL, ScriptCodeDAO, ScriptDAO } from "@App/app/repo/scripts"; import CodeEditor from "@App/pages/components/CodeEditor"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useParams, useSearchParams } from "react-router-dom"; -import type { editor } from "monaco-editor"; +import type { editor, IDisposable } from "monaco-editor"; import { KeyCode, KeyMod } from "monaco-editor"; import { Button, Dropdown, Grid, Input, Menu, Message, Modal, Space, Tabs, Tooltip } from "@arco-design/web-react"; import TabPane from "@arco-design/web-react/es/Tabs/tab-pane"; @@ -53,6 +53,17 @@ const Editor: React.FC<{ }, [node] ); + // 用 ref 拿到最新的 hotKeys/onChange/callbackEditor,避免 stale closure + // 同时让 effect 仅在 editor 实例变化时重跑(不会因父组件重渲染重复 addAction) + const hotKeysRef = useRef(hotKeys); + const onChangeRef = useRef(onChange); + const callbackEditorRef = useRef(callbackEditor); + const scriptRef = useRef(script); + hotKeysRef.current = hotKeys; + onChangeRef.current = onChange; + callbackEditorRef.current = callbackEditor; + scriptRef.current = script; + useEffect(() => { if (!node || !node.editor) { return; @@ -60,24 +71,32 @@ const Editor: React.FC<{ // @ts-ignore if (!node.editor.uuid) { // @ts-ignore - node.editor.uuid = script.uuid; + node.editor.uuid = scriptRef.current.uuid; } - hotKeys.forEach((item) => { - node.editor.addAction({ - id: item.id, - label: item.title, - keybindings: [item.hotKey], - run(editor) { - // @ts-ignore - item.action(script, editor); - }, - }); - }); - node.editor.onKeyUp(() => { - onChange(node.editor.getValue() || ""); + const disposables: IDisposable[] = []; + hotKeysRef.current.forEach((item) => { + disposables.push( + node.editor.addAction({ + id: item.id, + label: item.title, + keybindings: [item.hotKey], + run(editor) { + // @ts-ignore + item.action(scriptRef.current, editor); + }, + }) + ); }); - callbackEditor(node.editor); - return node.editor.dispose.bind(node.editor); + disposables.push( + node.editor.onKeyUp(() => { + onChangeRef.current(node.editor.getValue() || ""); + }) + ); + callbackEditorRef.current(node.editor); + // editor 实例本身由 CodeEditor 自身负责 dispose,这里仅清理本 effect 注册的 listener/action + return () => { + disposables.forEach((d) => d.dispose()); + }; }, [node?.editor]); return ; diff --git a/src/pkg/utils/monaco-editor/index.ts b/src/pkg/utils/monaco-editor/index.ts index 6c91de174..c1505e0b6 100644 --- a/src/pkg/utils/monaco-editor/index.ts +++ b/src/pkg/utils/monaco-editor/index.ts @@ -15,6 +15,7 @@ const langs = { quickfix: "修复 {0} 问题", addEslintDisableNextLine: "添加 eslint-disable-next-line 注释", addEslintDisable: "添加 eslint-disable 注释", + declareGlobal: "将 '{0}' 声明为全局变量 (/* global */)", prompt: { name: "脚本名称", namespace: "脚本命名空间", @@ -84,6 +85,7 @@ tracking:该脚本会追踪你的用户信息`.replace(/\n/g, "
"), quickfix: "Fix {0} Issue", addEslintDisableNextLine: "Add eslint-disable-next-line Comment", addEslintDisable: "Add eslint-disable Comment", + declareGlobal: "Declare '{0}' as a global variable (/* global */)", prompt: { name: "Script name", namespace: "Script namespace", @@ -146,6 +148,7 @@ tracking:该脚本会追踪你的用户信息`.replace(/\n/g, "
"), quickfix: "修復 {0} 問題", addEslintDisableNextLine: "新增 eslint-disable-next-line 註解", addEslintDisable: "新增 eslint-disable 註解", + declareGlobal: "將 '{0}' 宣告為全域變數 (/* global */)", prompt: { name: "腳本名稱", namespace: "腳本命名空間", @@ -208,6 +211,7 @@ tracking:该脚本会追踪你的用户信息`.replace(/\n/g, "
"), quickfix: "{0} の問題を修正", addEslintDisableNextLine: "eslint-disable-next-line コメントを追加", addEslintDisable: "eslint-disable コメントを追加", + declareGlobal: "'{0}' をグローバル変数として宣言 (/* global */)", prompt: { name: "スクリプト名", namespace: "スクリプトの名前空間", @@ -270,6 +274,7 @@ tracking:该脚本会追踪你的用户信息`.replace(/\n/g, "
"), quickfix: "{0}-Problem beheben", addEslintDisableNextLine: "eslint-disable-next-line Kommentar hinzufügen", addEslintDisable: "eslint-disable Kommentar hinzufügen", + declareGlobal: "'{0}' als globale Variable deklarieren (/* global */)", prompt: { name: "Skriptname", namespace: "Skript-Namensraum", @@ -332,6 +337,7 @@ tracking:该脚本会追踪你的用户信息`.replace(/\n/g, "
"), quickfix: "Sửa lỗi {0}", addEslintDisableNextLine: "Thêm chú thích eslint-disable-next-line", addEslintDisable: "Thêm chú thích eslint-disable", + declareGlobal: "Khai báo '{0}' là biến toàn cục (/* global */)", prompt: { name: "Tên script", namespace: "Namespace của script", @@ -394,6 +400,7 @@ tracking:该脚本会追踪你的用户信息`.replace(/\n/g, "
"), quickfix: "Исправить проблему {0}", addEslintDisableNextLine: "Добавить комментарий eslint-disable-next-line", addEslintDisable: "Добавить комментарий eslint-disable", + declareGlobal: "Объявить '{0}' как глобальную переменную (/* global */)", prompt: { name: "Имя скрипта", namespace: "Пространство имён скрипта", @@ -587,7 +594,7 @@ export default function registerEditor() { } actions.push({ - title: `将 '${globalName}' 声明为全局变量 (/* global */)`, + title: multiLang.declareGlobal.replace("{0}", globalName), diagnostics: [val], kind: "quickfix", edit: {