import type { PopsPanelContentConfig } from "@whitesev/pops/dist/types/src/components/panel/types/index.js"; import type { UtilsDictionary } from "@whitesev/utils/dist/types/src/Dictionary.js"; import Qmsg from "qmsg"; import { GM_deleteValue, GM_getValue, GM_info, GM_listValues, GM_setValue, GM_setValues } from "ViteGM"; import { AnyTouch, DOMUtils, httpx, log, pops, SCRIPT_NAME, utils } from "../env.base"; import { CommonUtil } from "../utils/CommonUtil"; import { KEY } from "./panel-config"; import { PanelSizeUtil } from "./panel-size-util"; import { PanelUISize } from "./panel-ui-size"; /** * 油猴菜单内容配置 */ const PanelContent = { $data: { /** * @private */ __contentConfig: null as null | UtilsDictionary, get contentConfig() { if (this.__contentConfig == null) { this.__contentConfig = new utils.Dictionary(); } return this.__contentConfig; }, /** * @private */ __defaultBottomContentConfig: [] as PopsPanelContentConfig[], }, /** * 设置所有配置项,用于初始化默认的值 * * 如果是第一组添加的话,那么它默认就是设置菜单打开的配置 * @param configList 配置项 */ addContentConfig(configList: PopsPanelContentConfig | PopsPanelContentConfig[]) { if (!Array.isArray(configList)) { configList = [configList]; } let index = this.$data.contentConfig.keys().length; this.$data.contentConfig.set(index, configList); }, /** * 获取所有的配置内容,用于初始化默认的值 */ getAllContentConfig(): PopsPanelContentConfig[] { return this.$data.contentConfig.values().flat(); }, /** * 获取配置内容 * @param index 配置索引 */ getConfig(index: number = 0): PopsPanelContentConfig[] { return this.$data.contentConfig.get(index) ?? []; }, /** * 获取左侧底部默认的配置项 * @param config 配置项 */ getDefaultBottomContentConfig(config?: { /** * 翻译函数 */ translateCallback?: (text: string, translateMap?: Record) => string; }): PopsPanelContentConfig[] { if (this.$data.__defaultBottomContentConfig.length) { return this.$data.__defaultBottomContentConfig; } let isDoubleClick = false; let timer: number | undefined = void 0; /** * 翻译函数的回调 * @param text 文本 */ const translateCallback = (text: string, translateMap?: Record) => { if (config && typeof config.translateCallback === "function") { return config.translateCallback(text, translateMap); } else { if (typeof translateMap === "object" && translateMap) { for (const key in translateMap) { text = text.replaceAll(`{{${key}}}`, translateMap[key]); } } return text; } }; /** * 导出至文件 */ const exportToFile = (fileName: string, fileData: any) => { if (typeof fileData !== "string") { fileData = CommonUtil.toStr(fileData); } const blob = new Blob([fileData]); const blobUrl = globalThis.URL.createObjectURL(blob); const $anchor = DOMUtils.createElement("a", { href: blobUrl, download: fileName, }); $anchor.click(); utils.workerSetTimeout(() => { globalThis.URL.revokeObjectURL(blobUrl); }, 500); }; /** * 双击 - 打开脚本配置 */ const dbclick_callback = () => { /** * 导入配置 * @param importEndCallBack 导入完毕后的回调 */ const importConfig = (importEndCallBack?: () => void) => { const $alert = pops.alert({ title: { text: translateCallback("请选择导入方式"), position: "center", }, content: { text: /*html*/ `
${translateCallback("本地导入")}
${translateCallback("网络导入")}
${translateCallback("剪贴板导入")}
`, html: true, }, btn: { ok: { enable: false }, close: { enable: true, callback(details) { details.close(); }, }, }, drag: true, mask: { enable: true, }, width: PanelUISize.info.width, height: PanelUISize.info.height, style: /*css*/ ` .btn-control{ display: inline-block; margin: 10px; padding: 10px; border: 1px solid #ccc; border-radius: 5px; cursor: pointer; } .btn-control:hover{ color: #409eff; border-color: #c6e2ff; background-color: #ecf5ff; }`, }); /** 本地导入 */ const $local = $alert.$shadowRoot.querySelector(".btn-control[data-mode='local']")!; /** 网络导入 */ const $network = $alert.$shadowRoot.querySelector(".btn-control[data-mode='network']")!; /** 剪贴板导入 */ const $clipboard = $alert.$shadowRoot.querySelector(".btn-control[data-mode='clipboard']")!; /** * 将获取到的配置更新至存储 */ const updateConfigToStorage = async (data: any) => { const clearLocalStorage = confirm( translateCallback("是否清空脚本存储的配置?(如果点击取消按钮,则仅做配置覆盖处理)") ); if (clearLocalStorage) { if (typeof GM_listValues === "function") { if (typeof GM_deleteValue === "function") { const localStorageKeys = GM_listValues(); localStorageKeys.forEach((key) => { GM_deleteValue(key); }); Qmsg.success(translateCallback("已清空脚本存储的配置")); } else { Qmsg.error(translateCallback("不支持GM_deleteValue函数,无法执行删除脚本配置")); } } else { Qmsg.error(translateCallback("不支持GM_listValues函数,无法清空脚本存储的配置")); } } if (typeof GM_setValues === "function") { GM_setValues(data); } else { const keys = Object.keys(data); keys.forEach((key) => { const value = data[key]; GM_setValue(key, value); }); } Qmsg.success(translateCallback("配置导入完毕")); importEndCallBack?.(); }; /** * @param configText 订阅文件文本 */ const importFile = (configText: string) => { return new Promise(async (resolve) => { const data = utils.toJSON(configText); if (Object.keys(data).length === 0) { Qmsg.warning(translateCallback("解析为空配置,不导入")); } else { await updateConfigToStorage(data); } resolve(true); }); }; // 本地导入 DOMUtils.on($local, "click", (event) => { DOMUtils.preventEvent(event); $alert.close(); const $input = DOMUtils.createElement("input", { type: "file", accept: ".json", }); DOMUtils.on($input, ["propertychange", "input"], () => { if (!$input.files?.length) { return; } const uploadFile = $input.files![0]; const fileReader = new FileReader(); fileReader.onload = () => { importFile(fileReader.result as string); }; fileReader.readAsText(uploadFile, "UTF-8"); }); $input.click(); }); // 网络导入 DOMUtils.on($network, "click", (event) => { DOMUtils.preventEvent(event); $alert.close(); const $prompt = pops.prompt({ title: { text: translateCallback("网络导入"), position: "center", }, content: { text: "", placeholder: translateCallback("请填写URL"), focus: true, }, btn: { close: { enable: true, callback(details) { details.close(); }, }, ok: { text: translateCallback("导入"), callback: async (details) => { const url = details.text; if (utils.isNull(url)) { Qmsg.error(translateCallback("请填入完整的url")); return; } const $loading = Qmsg.loading(translateCallback("正在获取配置...")); const response = await httpx.get(url, { allowInterceptConfig: false, }); $loading.close(); if (!response.status) { log.error(response); Qmsg.error(translateCallback("获取配置失败"), { consoleLogContent: true }); return; } const flag = await importFile(response.data.responseText); if (!flag) { return; } details.close(); }, }, cancel: { enable: false, }, }, drag: true, mask: { enable: true, }, width: PanelUISize.info.width, height: "auto", }); const $promptInput = $prompt.$shadowRoot.querySelector("input")!; const $promptOk = $prompt.$shadowRoot.querySelector(".pops-prompt-btn-ok")!; DOMUtils.on($promptInput, ["input", "propertychange"], () => { const value = DOMUtils.val($promptInput); if (value === "") { DOMUtils.attr($promptOk, "disabled", "true"); } else { DOMUtils.removeAttr($promptOk, "disabled"); } }); DOMUtils.onKeyboard($promptInput, "keydown", (keyName, keyValue, otherCodeList) => { if (keyName === "Enter" && otherCodeList.length === 0) { const value = DOMUtils.val($promptInput); if (value !== "") { DOMUtils.emit($promptOk, "click"); } } }); DOMUtils.emit($promptInput, "input"); }); // 剪贴板导入 DOMUtils.on($clipboard, "click", async (event) => { DOMUtils.preventEvent(event); $alert.close(); let clipboardText = await CommonUtil.getClipboardText(); if (clipboardText.trim() === "") { Qmsg.warning(translateCallback("获取到的剪贴板内容为空")); return; } const flag = await importFile(clipboardText); if (!flag) { return; } }); }; /** * 导出配置 */ const exportConfig = ( fileName: string = `${SCRIPT_NAME}_panel-setting-${utils.formatTime(Date.now(), "yyyy_MM_dd_HH_mm_ss")}.json`, fileData: any ) => { const $alert = pops.alert({ title: { text: translateCallback("请选择导出方式"), position: "center", }, content: { text: /*html*/ `
${translateCallback("导出至文件")}
${translateCallback("导出至剪贴板")}
`, html: true, }, btn: { ok: { enable: false }, close: { enable: true, callback(details) { details.close(); }, }, }, drag: true, mask: { enable: true, }, width: PanelUISize.info.width, height: PanelUISize.info.height, style: /*css*/ ` .btn-control{ display: inline-block; margin: 10px; padding: 10px; border: 1px solid #ccc; border-radius: 5px; cursor: pointer; } .btn-control:hover{ color: #409eff; border-color: #c6e2ff; background-color: #ecf5ff; }`, }); const $exportToFile = $alert.$shadowRoot.querySelector( ".btn-control[data-mode='export-to-file']" )!; const $exportToClipboard = $alert.$shadowRoot.querySelector( ".btn-control[data-mode='export-to-clipboard']" )!; DOMUtils.on($exportToFile, "click", (event) => { DOMUtils.preventEvent(event); try { exportToFile(fileName, fileData); $alert.close(); } catch (error: any) { Qmsg.error(error.toString(), { consoleLogContent: true }); } }); DOMUtils.on($exportToClipboard, "click", async () => { const result = await utils.copy(fileData); if (result) { Qmsg.success(translateCallback("复制成功")); $alert.close(); } else { Qmsg.error(translateCallback("复制失败")); } }); }; const $dialog = pops.confirm({ title: { text: translateCallback("配置"), position: "center", }, content: { text: /*html*/ ``, html: true, }, btn: { ok: { enable: true, type: "primary", text: translateCallback("导入"), callback() { importConfig(); }, }, cancel: { enable: true, text: translateCallback("导出"), callback() { exportConfig(void 0, configDataStr); }, }, }, width: PanelSizeUtil.width < 450 ? "90vw" : "450px", height: "auto", style: /*css*/ ` .pops-content textarea { --textarea-bd-color: #dcdfe6; display: inline-block; resize: vertical; padding: 5px 15px; margin: 0; line-height: normal; box-sizing: border-box; color: #606266; border: 0; border-radius: 0; outline: none; -webkit-appearance: none; -moz-appearance: none; appearance: none; background: none; width: 100%; height: 100%; appearance: none; resize: none; } .pops-content textarea{ height: 500px; } .pops-content textarea:focus { --textarea-bd-color: #3677f0; } .pops-content textarea:hover { --textarea-bd-color: #c0c4cc; } `, }); const $textarea = $dialog.$shadowRoot.querySelector("textarea")!; const configData = {}; if (typeof GM_listValues === "function") { const LocalKeys = GM_listValues(); LocalKeys.forEach((key) => { const value = GM_getValue(key); Reflect.set(configData, key, value); }); } else { Qmsg.warning(translateCallback("不支持函数GM_listValues,仅导出菜单配置")); const panelLocalValue = GM_getValue(KEY); Reflect.set(configData, KEY, panelLocalValue); } const configDataStr = CommonUtil.toStr(configData); $textarea.value = configDataStr; }; /** * 单击 - 打开脚本反馈页面 */ const click_callback = () => { let supportURL = GM_info?.script?.supportURL || GM_info?.script?.namespace; if (typeof supportURL === "string" && utils.isNotNull(supportURL)) { window.open(supportURL, "_blank"); } }; return [ { id: "script-version", title: translateCallback(`版本:{{version}}`, { version: GM_info?.script?.version || translateCallback("未知"), }), isBottom: true, views: [], clickFirstCallback() { return false; }, afterRender(config) { /* 是否是双击 */ const anyTouch = new AnyTouch(config.$asideLiElement); anyTouch.on("tap", function () { clearTimeout(timer); timer = void 0; if (isDoubleClick) { isDoubleClick = false; /* 判定为双击 */ dbclick_callback(); } else { timer = setTimeout(() => { isDoubleClick = false; // 判断为单击 click_callback(); }, 200); isDoubleClick = true; } }); }, }, ]; }, /** * 设置左侧底部默认的配置项 */ setDefaultBottomContentConfig(config: PopsPanelContentConfig[]) { this.$data.__defaultBottomContentConfig = config; }, }; export { PanelContent };