diff --git a/src/app/service/service_worker/client.ts b/src/app/service/service_worker/client.ts index 0961b1f16..fd4349177 100644 --- a/src/app/service/service_worker/client.ts +++ b/src/app/service/service_worker/client.ts @@ -11,7 +11,13 @@ import { type FileSystemType } from "@Packages/filesystem/factory"; import { type ResourceBackup } from "@App/pkg/backup/struct"; import { type VSCodeConnect } from "../offscreen/vscode-connect"; import { type ScriptInfo } from "@App/pkg/utils/scriptInstall"; -import type { ScriptService, TCheckScriptUpdateOption, TOpenBatchUpdatePageOption } from "./script"; +import type { + ScriptService, + TCheckScriptUpdateOption, + TOpenBatchUpdatePageOption, + TScriptInstallParam, + TScriptInstallReturn, +} from "./script"; import { encodeRValue, type TKeyValuePair } from "@App/pkg/utils/message_value"; import { type TSetValuesParams } from "./value"; @@ -40,15 +46,9 @@ export class ScriptClient extends Client { return this.do<[boolean, ScriptInfo, { byWebRequest?: boolean }]>("getInstallInfo", uuid); } - install(params: { - script: Script; - code: string; - upsertBy?: InstallSource; - createtime?: number; - updatetime?: number; - }): Promise<{ update: boolean }> { + install(params: TScriptInstallParam): Promise { if (!params.upsertBy) params.upsertBy = "user"; - return this.doThrow("install", { ...params }); + return this.doThrow("install", { ...params } satisfies TScriptInstallParam); } // delete(uuid: string) { diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index 227be28d1..e2cd92c01 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -54,6 +54,19 @@ export type TCheckScriptUpdateOption = Partial< export type TOpenBatchUpdatePageOption = { q: string; dontCheckNow: boolean }; +export type TScriptInstallParam = { + script: Script; // 脚本信息(包含脚本的基础元数据) + code: string; // 脚本源码内容 + upsertBy?: InstallSource; // 安装/更新来源(用于标识脚本来源渠道) + createtime?: number; // 导入时指定的创建时间(时间戳,毫秒) + updatetime?: number; // 导入时指定的最后更新时间(时间戳,毫秒) +}; + +export type TScriptInstallReturn = { + update: boolean; // 是否为更新操作(true 表示更新,false 表示新增) + updatetime: number | undefined; // 实际生效的更新时间(时间戳,毫秒) +}; + export class ScriptService { logger: Logger; scriptCodeDAO: ScriptCodeDAO = new ScriptCodeDAO(); @@ -369,14 +382,8 @@ export class ScriptService { return this.mq.publish("installScript", { script, ...options }); } - // 安装脚本 / 更新腳本 - async installScript(param: { - script: Script; - code: string; - upsertBy?: InstallSource; - createtime?: number; - updatetime?: number; - }) { + // 安装脚本 / 更新脚本 + async installScript(param: TScriptInstallParam): Promise { param.upsertBy = param.upsertBy || "user"; const { script, upsertBy, createtime, updatetime } = param; // 删 storage cache @@ -427,10 +434,11 @@ export class ScriptService { ]); // 广播一下 - // Runtime 會負責更新 CompiledResource + // Runtime 会负责更新 CompiledResource this.publishInstallScript(script, { update, upsertBy }); - return { update }; + // 传回(由后台控制的)实际更新时间,让 editor 中的script能保持正确的更新时间 + return { update, updatetime: script.updatetime }; }) .catch((e: any) => { logger.error("install error", Logger.E(e)); @@ -1144,7 +1152,7 @@ export class ScriptService { } isInstalled({ name, namespace }: { name: string; namespace: string }): Promise { - // 用於 window.external + // 用于 window.external return this.scriptDAO.findByNameAndNamespace(name, namespace).then((script) => { if (script) { return { diff --git a/src/locales/ach-UG/translation.json b/src/locales/ach-UG/translation.json index 139220493..501671fad 100644 --- a/src/locales/ach-UG/translation.json +++ b/src/locales/ach-UG/translation.json @@ -422,7 +422,6 @@ "develop_mode_guide": "crwdns8624:0crwdne8624:0", "allow_user_script_guide": "crwdns8626:0crwdne8626:0", "lower_version_browser_guide": "crwdns8628:0crwdne8628:0", - "confirm_leave_page": "crwdns8634:0crwdne8634:0", "page_in_blacklist": "crwdns8636:0crwdne8636:0", "baidu_netdisk": "crwdns8638:0crwdne8638:0", "save_only_current_group": "crwdns8640:0crwdne8640:0", diff --git a/src/locales/de-DE/translation.json b/src/locales/de-DE/translation.json index 2f77838fc..9a3277183 100644 --- a/src/locales/de-DE/translation.json +++ b/src/locales/de-DE/translation.json @@ -432,7 +432,6 @@ "allow_user_script_guide": "'Nutzerskripts zulassen' ist derzeit nicht aktiviert, daher können die Skripte nicht richtig ausgeführt werden. 👉Hier klicken, um zu erfahren, wie man es aktiviert", "lower_version_browser_guide": "Ihr Browser ist zu veraltet, daher können die Skripte nicht richtig ausgeführt werden. 👉Hier klicken, um mehr zu erfahren", "click_to_reload": "👉Zum Neuladen klicken", - "confirm_leave_page": "Derzeit im Bearbeitungsstatus. Das Navigieren zu anderen Seiten führt zum Verlust des aktuellen Inhalts. Navigieren?", "page_in_blacklist": "Die aktuelle Seite ist auf der Blacklist und kann keine Skripte verwenden", "baidu_netdisk": "Baidu Netdisk", "netdisk_unbind": "{{provider}} trennen", @@ -448,7 +447,7 @@ "expression_format_error": "Ausdrucksformat-Fehler", "migration_confirm_message": "Das erneute Versuchen der Speicher-Engine-Migration wird vorhandene Daten ändern. Bitte bestätigen Sie. Details siehe: https://docs.scriptcat.org/docs/change/v0.17/", "retry_migration": "Speicher-Engine-Migration erneut versuchen", - "script_modified_leave_confirm": "Skript wurde geändert. Das Verlassen führt zum Verlust der Änderungen. Fortfahren?", + "script_modified_leave_confirm": "Ihre Änderungen wurden noch nicht gespeichert. Wenn Sie die Seite verlassen, gehen die Änderungen verloren. Möchten Sie die Seite wirklich verlassen?", "create_success_note": "Erstellung erfolgreich. Beachten Sie, dass Hintergrundskripte standardmäßig nicht aktiviert werden", "save_as_failed": "Speichern unter fehlgeschlagen", "save_as_success": "Speichern unter erfolgreich", diff --git a/src/locales/en-US/translation.json b/src/locales/en-US/translation.json index e55cdd949..ca060b941 100644 --- a/src/locales/en-US/translation.json +++ b/src/locales/en-US/translation.json @@ -432,7 +432,6 @@ "allow_user_script_guide": "'Allow User Scripts' is currently not enabled, so the scripts cannot run properly. 👉tap to learn how to enable", "lower_version_browser_guide": "Your browser is too outdated, so the scripts cannot run properly. 👉Click me to learn more", "click_to_reload": "👉Click to Reload", - "confirm_leave_page": "Currently editing status. Leaving this page will lose the current content. Do you want to leave?", "page_in_blacklist": "The current page is blacklisted, cannot use script", "baidu_netdisk": "BaiduNetdisk", "netdisk_unbind": "Unbind {{provider}}", @@ -448,7 +447,7 @@ "expression_format_error": "Condition expression format error", "migration_confirm_message": "Retry the migration storage engine to modify existing data. Please confirm, see:https://docs.scriptcat.org/docs/change/v0.17/.", "retry_migration": "Retry Migration Storage Engine", - "script_modified_leave_confirm": "The script has been modified. Changes will be lost after leaving, continue?", + "script_modified_leave_confirm": "Your changes have not been saved. If you leave, your changes will be lost. Are you sure you want to leave?", "create_success_note": "New script successfully created. Note that the background script will not be enabled by default.", "save_as_failed": "Save As Failed", "save_as_success": "Saved Successfully", diff --git a/src/locales/ja-JP/translation.json b/src/locales/ja-JP/translation.json index 1e3cee58e..184589278 100644 --- a/src/locales/ja-JP/translation.json +++ b/src/locales/ja-JP/translation.json @@ -432,7 +432,6 @@ "allow_user_script_guide": "現在「ユーザー スクリプトを許可する」が有効ではないため、スクリプトは正常に動作しません。👉有効化の方法はこちら", "lower_version_browser_guide": "ご使用のブラウザは古すぎるため、スクリプトは正常に動作しません。👉詳しくはこちら", "click_to_reload": "👉再読み込みする", - "confirm_leave_page": "現在編集中です。他のページに移動すると現在の内容が失われます。移動しますか?", "page_in_blacklist": "現在のページはブラックリストにあり、スクリプトを使用できません", "baidu_netdisk": "百度ネットディスク", "netdisk_unbind": "{{provider}} の連携を解除", @@ -448,7 +447,7 @@ "expression_format_error": "式フォーマットエラー", "migration_confirm_message": "ストレージエンジンの移行を再試行すると既存のデータが変更されます。確認してください。詳細はこちら:https://docs.scriptcat.org/docs/change/v0.17/", "retry_migration": "ストレージエンジンの移行を再試行", - "script_modified_leave_confirm": "スクリプトが変更されています。離れると変更が失われます。続行しますか?", + "script_modified_leave_confirm": "現在の内容はまだ保存されていません。このままページを離れると変更は失われます。よろしいですか?", "create_success_note": "作成に成功しました。バックグラウンドスクリプトはデフォルトで有効になりませんのでご注意ください", "save_as_failed": "名前を付けて保存に失敗しました", "save_as_success": "名前を付けて保存に成功しました", diff --git a/src/locales/ru-RU/translation.json b/src/locales/ru-RU/translation.json index 038eb00d9..ca5dce2c9 100644 --- a/src/locales/ru-RU/translation.json +++ b/src/locales/ru-RU/translation.json @@ -432,7 +432,6 @@ "allow_user_script_guide": "«Разрешить пользовательские скрипты» сейчас отключён, поэтому скрипты не могут работать корректно. 👉Нажмите, чтобы узнать, как включить", "lower_version_browser_guide": "Ваш браузер слишком устарел, поэтому скрипты не могут работать корректно. 👉Нажмите, чтобы узнать подробнее", "click_to_reload": "👉Нажмите для перезагрузки", - "confirm_leave_page": "В настоящее время идет редактирование. Переход на другую страницу приведет к потере текущего содержимого. Продолжить переход?", "page_in_blacklist": "Текущая страница находится в черном списке, невозможно использовать скрипты", "baidu_netdisk": "Baidu Netdisk", "netdisk_unbind": "Отвязать {{provider}}", @@ -448,7 +447,7 @@ "expression_format_error": "Ошибка формата выражения", "migration_confirm_message": "Повторная попытка миграции движка хранения изменит существующие данные. Пожалуйста, подтвердите. Подробности см.: https://docs.scriptcat.org/docs/change/v0.17/", "retry_migration": "Повторить миграцию движка хранения", - "script_modified_leave_confirm": "Скрипт был изменен. Изменения будут потеряны после выхода. Продолжить?", + "script_modified_leave_confirm": "Изменения не сохранены. Если вы покинете страницу, они будут потеряны. Вы уверены, что хотите уйти?", "create_success_note": "Создание успешно. Обратите внимание, что фоновые скрипты не включаются по умолчанию", "save_as_failed": "Ошибка «Сохранить как»", "save_as_success": "«Сохранить как» успешно", diff --git a/src/locales/vi-VN/translation.json b/src/locales/vi-VN/translation.json index 9da54ee74..e9b02348f 100644 --- a/src/locales/vi-VN/translation.json +++ b/src/locales/vi-VN/translation.json @@ -432,7 +432,6 @@ "allow_user_script_guide": "'Cho phép tập lệnh người dùng' hiện chưa được bật, nên các script không thể hoạt động đúng cách. 👉Nhấn để xem cách bật", "lower_version_browser_guide": "Trình duyệt của bạn quá cũ, nên các script không thể hoạt động đúng cách. 👉Nhấn để xem thêm", "click_to_reload": "👉Nhấp chuột để tải lại", - "confirm_leave_page": "Hiện đang ở trạng thái chỉnh sửa. Rời khỏi trang này sẽ làm mất nội dung hiện tại. Bạn có muốn rời đi không?", "page_in_blacklist": "Trang hiện tại nằm trong danh sách đen, không thể sử dụng script", "baidu_netdisk": "Baidunetdisk", "netdisk_unbind": "Hủy liên kết {{provider}}", @@ -448,7 +447,7 @@ "expression_format_error": "Lỗi định dạng biểu thức điều kiện", "migration_confirm_message": "Thử lại công cụ lưu trữ di chuyển để sửa đổi dữ liệu hiện có. Vui lòng xác nhận, xem: https://docs.scriptcat.org/docs/change/v0.17/.", "retry_migration": "Thử lại công cụ lưu trữ di chuyển", - "script_modified_leave_confirm": "Script đã được sửa đổi. Các thay đổi sẽ bị mất sau khi rời đi, tiếp tục?", + "script_modified_leave_confirm": "Nội dung hiện chưa được lưu. Nếu rời khỏi trang, các thay đổi sẽ bị mất. Bạn có chắc chắn muốn rời đi không?", "create_success_note": "Script mới được tạo thành công. Lưu ý rằng script nền sẽ không được bật theo mặc định.", "save_as_failed": "Lưu thành thất bại", "save_as_success": "Lưu thành công", diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index ca7fe2dc8..021a7863f 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -432,7 +432,6 @@ "allow_user_script_guide": "当前未启用“允许运行用户脚本”,脚本无法正常运行。👉点击查看启用方法", "lower_version_browser_guide": "您的浏览器版本过低,脚本无法正常运行。👉点击了解更多", "click_to_reload": "👉点击重新加载", - "confirm_leave_page": "当前正在编辑状态,跳转其它页面将会丢失当前内容,是否跳转?", "page_in_blacklist": "当前页面在黑名单中,无法使用脚本", "baidu_netdisk": "百度网盘", "netdisk_unbind": "解除绑定 {{provider}}", @@ -448,7 +447,7 @@ "expression_format_error": "表达式格式错误", "migration_confirm_message": "重试迁移储存引擎会对现有数据造成修改,请确认,详情请看:https://docs.scriptcat.org/docs/change/v0.17/", "retry_migration": "重试迁移储存引擎", - "script_modified_leave_confirm": "脚本已修改, 离开后会丢失修改, 是否继续?", + "script_modified_leave_confirm": "当前内容尚未保存,离开后更改将丢失,确定要离开吗?", "create_success_note": "新建成功,请注意后台脚本不会默认开启", "save_as_failed": "另存为失败", "save_as_success": "另存为成功", diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index 6b50bf11a..8db6b4040 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -432,7 +432,6 @@ "allow_user_script_guide": "目前尚未啟用「允許使用者指令碼」,腳本無法正常執行。👉點此查看啟用方式", "lower_version_browser_guide": "您的瀏覽器版本過舊,腳本無法正常執行。👉點擊了解更多", "click_to_reload": "👉點擊重新載入", - "confirm_leave_page": "目前正在編輯狀態,跳轉其他頁面將會遺失目前內容,是否跳轉?", "page_in_blacklist": "目前頁面在黑名單中,無法使用腳本", "baidu_netdisk": "百度網盤", "netdisk_unbind": "解除綁定 {{provider}}", @@ -448,7 +447,7 @@ "expression_format_error": "表達式格式錯誤", "migration_confirm_message": "重試遷移儲存引擎會對現有資料造成修改,請確認,詳情請參閱:https://docs.scriptcat.org/docs/change/v0.17/", "retry_migration": "重試遷移儲存引擎", - "script_modified_leave_confirm": "腳本已修改,離開後會遺失修改,是否繼續?", + "script_modified_leave_confirm": "目前內容尚未儲存,離開後變更將會遺失,確定要離開嗎?", "create_success_note": "新建成功,請注意背景腳本不會預設開啟", "save_as_failed": "另存新檔失敗", "save_as_success": "另存新檔成功", diff --git a/src/pages/components/CustomLink/index.tsx b/src/pages/components/CustomLink/index.tsx index f37e2d715..85b0af973 100644 --- a/src/pages/components/CustomLink/index.tsx +++ b/src/pages/components/CustomLink/index.tsx @@ -14,7 +14,8 @@ const CustomLink: React.FC<{ const click = () => { if (window.onbeforeunload) { - if (confirm(t("confirm_leave_page"))) { + // 目前仅用于 ScriptEditor 编辑内容修改提示 + if (confirm(t("script_modified_leave_confirm"))) { nav({ pathname: to, search, diff --git a/src/pages/options/routes/script/ScriptEditor.tsx b/src/pages/options/routes/script/ScriptEditor.tsx index 8fc3322d8..89001bbc8 100644 --- a/src/pages/options/routes/script/ScriptEditor.tsx +++ b/src/pages/options/routes/script/ScriptEditor.tsx @@ -1,8 +1,8 @@ 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 { useParams, useSearchParams } from "react-router-dom"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import type { editor } 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"; @@ -32,18 +32,18 @@ type HotKey = { id: string; title: string; hotKey: number; - action: (script: Script, codeEditor: editor.IStandaloneCodeEditor) => void; + action: (script: Script, codeEditor: editor.ICodeEditor) => void; }; const Editor: React.FC<{ id: string; - script: Script; + getScript: (uuid: string) => Script | undefined; code: string; hotKeys: HotKey[]; - callbackEditor: (e: editor.IStandaloneCodeEditor) => void; + callbackEditor: (e: editor.ICodeEditor) => void; onChange: (code: string) => void; className: string; -}> = ({ id, script, code, hotKeys, callbackEditor, onChange, className }) => { +}> = ({ id, getScript, code, hotKeys, callbackEditor, onChange, className }) => { const [node, setNode] = useState<{ editor: editor.IStandaloneCodeEditor }>(); const ref = useCallback<(node: { editor: editor.IStandaloneCodeEditor }) => void>( (inlineNode) => { @@ -60,7 +60,7 @@ const Editor: React.FC<{ // @ts-ignore if (!node.editor.uuid) { // @ts-ignore - node.editor.uuid = script.uuid; + node.editor.uuid = id; } hotKeys.forEach((item) => { node.editor.addAction({ @@ -68,8 +68,10 @@ const Editor: React.FC<{ label: item.title, keybindings: [item.hotKey], run(editor) { - // @ts-ignore - item.action(script, editor); + const script = getScript(id); + if (script) { + item.action(script, editor); + } }, }); }); @@ -84,20 +86,20 @@ const Editor: React.FC<{ }; const WarpEditor = React.memo(Editor, (prev, next) => { - return prev.script.uuid === next.script.uuid; + return prev.id === next.id; }); type EditorMenu = { title: string; tooltip?: string; - action?: (script: Script, e: editor.IStandaloneCodeEditor) => void; + action?: (script: Script, e: editor.ICodeEditor) => void; items?: { id: string; title: string; tooltip?: string; hotKey?: number; hotKeyString?: string; - action: (script: Script, e: editor.IStandaloneCodeEditor) => void; + action: (script: Script, e: editor.ICodeEditor) => void; }[]; }; @@ -171,7 +173,13 @@ const emptyScript = async (template: string, hotKeys: any, target?: string) => { type visibleItem = "scriptStorage" | "scriptSetting" | "scriptResource"; -const popstate = () => { +let cid: ReturnType; + +const popstate: EventListener = (e: Event) => { + if (!e.isTrusted) return; + if (location.href.startsWith(chrome.runtime.getURL("/src/options.html#/script/editor"))) { + return; + } if (confirm(i18n.t("script_modified_leave_confirm"))) { window.history.back(); window.removeEventListener("popstate", popstate); @@ -181,42 +189,109 @@ const popstate = () => { return false; }; +type EditorState = { + script: Script; + code: string; + active: boolean; + hotKeys: HotKey[]; + editor?: editor.ICodeEditor; + isChanged: boolean; +}; + +const scriptDAO = new ScriptDAO(); +const scriptCodeDAO = new ScriptCodeDAO(); + function ScriptEditor() { const [visible, setVisible] = useState<{ [key: string]: boolean }>({}); const [searchKeyword, setSearchKeyword] = useState(""); const [showSearchInput, setShowSearchInput] = useState(false); const [modal, contextHolder] = Modal.useModal(); - const [editors, setEditors] = useState< - { - script: Script; - code: string; - active: boolean; - hotKeys: HotKey[]; - editor?: editor.IStandaloneCodeEditor; - isChanged: boolean; - }[] - >([]); + const [editors, setEditors] = useState([]); + const editorsRef = useRef(editors); // 取出资料用 + // Sync during render (no useEffect needed) + editorsRef.current = editors; + // The function identity is now permanent (empty dependency array) + const getScript = useCallback((uuid: string) => { + return editorsRef.current.find((e) => e.script.uuid === uuid)?.script; + }, []); + const editorFindIndex = (uuid: string) => { + return editorsRef.current.findIndex((e) => e.script.uuid === uuid); + }; + const editorFindItem = (uuid: string) => { + return editorsRef.current.find((e) => e.script.uuid === uuid); + }; + const delayedEditorFocus = (editor: editor.ICodeEditor | null | undefined, delayMs: number = 100) => { + editor = !editor ? editorsRef.current.find((e) => e.active && e.script.uuid === selectedScript)?.editor : editor; + if (editor) { + setTimeout(editor.focus.bind(editor), delayMs); + } + }; const [scriptList, setScriptList] = useState([]); const [currentScript, setCurrentScript] = useState