diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 index fd92f4e77..e65f3812b --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,3 +1,13 @@ #!/bin/sh -pnpm run lint-fix \ No newline at end of file +pnpm run lint-fix + +# 将已暂存且被 lint-fix 修改过的文件重新加入暂存区 +STAGED=$(git diff --cached --name-only --diff-filter=d) +CHANGED=$(git diff --name-only) + +for file in $CHANGED; do + case "$STAGED" in + *"$file"*) git add "$file" ;; + esac +done diff --git a/.husky/pre-push b/.husky/pre-push old mode 100644 new mode 100755 diff --git a/example/tests/gm_xhr_test.js b/example/tests/gm_xhr_test.js index 2a03deead..dff6d2377 100644 --- a/example/tests/gm_xhr_test.js +++ b/example/tests/gm_xhr_test.js @@ -1,7 +1,7 @@ // ==UserScript== // @name GM_xmlhttpRequest Exhaustive Test Harness v3 // @namespace tm-gmxhr-test -// @version 1.2.3 +// @version 1.2.4 // @description Comprehensive in-page tests for GM_xmlhttpRequest: normal, abnormal, and edge cases with clear pass/fail output. // @author you // @match *://*/*?GM_XHR_TEST_SC @@ -878,6 +878,36 @@ const enableTool = true; assertEq(objectProps(res), "ok", "Object Props OK"); }, }, + { + name: "responseType=document(parse ok)", + async run(fetch) { + const { res } = await gmRequest({ + method: "GET", + url: `${HB}/base64/PHRlc3QtMTIzPmhlbGxvPC90ZXN0LTEyMz4=`, + responseType: "document", + fetch, + }); + assertEq(res.status, 200); + assert(res.response instanceof Document, "xml present"); + assert(res.responseXML !== null, "xml OK"); + assert(!!res.responseXML.querySelector("test-123"), "xml content ok"); + }, + }, + { + name: "responseType=document(parser error)", + async run(fetch) { + const { res } = await gmRequest({ + method: "GET", + url: `${HB}/base64/AAAAAAEAAQA=`, + responseType: "document", + fetch, + }); + assertEq(res.status, 200); + assert(res.response instanceof Document, "xml present"); + assert(res.responseXML !== null, "xml OK"); + assert(!!res.responseXML.querySelector("parsererror"), "xml content ok"); + }, + }, { name: "overrideMimeType (force text)", async run(fetch) { diff --git a/packages/filesystem/auth.ts b/packages/filesystem/auth.ts index 328308457..a15e55fa7 100644 --- a/packages/filesystem/auth.ts +++ b/packages/filesystem/auth.ts @@ -136,6 +136,17 @@ export const netDiskTypeMap: Partial> = { dropbox: "dropbox", }; +export async function HasNetDiskToken(netDiskType: NetDiskType): Promise { + const localStorageDAO = new LocalStorageDAO(); + const key = `netdisk:token:${netDiskType}`; + try { + const token = await localStorageDAO.getValue(key); + return !!token?.accessToken; + } catch { + return false; + } +} + export async function ClearNetDiskToken(netDiskType: NetDiskType) { const localStorageDAO = new LocalStorageDAO(); const key = `netdisk:token:${netDiskType}`; diff --git a/src/locales/ach-UG/translation.json b/src/locales/ach-UG/translation.json index e6f3850b5..ed59a0ece 100644 --- a/src/locales/ach-UG/translation.json +++ b/src/locales/ach-UG/translation.json @@ -103,6 +103,7 @@ "delete_current_logs": "crwdns8080:0crwdne8080:0", "clear_completed": "crwdns8082:0crwdne8082:0", "clear_logs": "crwdns8084:0crwdne8084:0", + "now": "Now", "total_logs": "crwdns8086:0{{length}}crwdne8086:0", "filtered_logs": "crwdns8088:0{{length}}crwdne8088:0", "enter_filter_conditions": "crwdns8090:0crwdne8090:0", diff --git a/src/locales/de-DE/translation.json b/src/locales/de-DE/translation.json index 9df67cfd8..796d3290b 100644 --- a/src/locales/de-DE/translation.json +++ b/src/locales/de-DE/translation.json @@ -104,6 +104,7 @@ "delete_current_logs": "Aktuelle Protokolle löschen", "clear_completed": "Bereinigung abgeschlossen", "clear_logs": "Protokolle bereinigen", + "now": "Jetzt", "total_logs": "Insgesamt {{length}} Protokolle gefunden", "filtered_logs": "Nach Filterung {{length}} Protokolle", "enter_filter_conditions": "Bitte geben Sie Filterbedingungen für die Abfrage ein", diff --git a/src/locales/en-US/translation.json b/src/locales/en-US/translation.json index 9769ef7f7..9471186db 100644 --- a/src/locales/en-US/translation.json +++ b/src/locales/en-US/translation.json @@ -104,6 +104,7 @@ "delete_current_logs": "Delete Current Logs", "clear_completed": "Clearing Completed", "clear_logs": "Clear Logs", + "now": "Now", "total_logs": "A total of {{length}} logs were queried", "filtered_logs": "{{length}} logs after filtering", "enter_filter_conditions": "Please Enter Filter Conditions for Query", diff --git a/src/locales/ja-JP/translation.json b/src/locales/ja-JP/translation.json index 823949c52..79879376c 100644 --- a/src/locales/ja-JP/translation.json +++ b/src/locales/ja-JP/translation.json @@ -104,6 +104,7 @@ "delete_current_logs": "現在のログを削除", "clear_completed": "クリア完了", "clear_logs": "ログをクリア", + "now": "現在", "total_logs": "合計{{length}}件のログが見つかりました", "filtered_logs": "フィルタリング後{{length}}件のログ", "enter_filter_conditions": "フィルタリング条件を入力してクエリしてください", diff --git a/src/locales/ru-RU/translation.json b/src/locales/ru-RU/translation.json index 1b6b72973..8fbc3b0ef 100644 --- a/src/locales/ru-RU/translation.json +++ b/src/locales/ru-RU/translation.json @@ -104,6 +104,7 @@ "delete_current_logs": "Удалить текущие журналы", "clear_completed": "Очистка завершена", "clear_logs": "Очистить журналы", + "now": "Сейчас", "total_logs": "Найдено {{length}} записей журнала", "filtered_logs": "После фильтрации {{length}} записей журнала", "enter_filter_conditions": "Введите условия фильтрации для поиска", diff --git a/src/locales/vi-VN/translation.json b/src/locales/vi-VN/translation.json index c69cb10be..e82302edd 100644 --- a/src/locales/vi-VN/translation.json +++ b/src/locales/vi-VN/translation.json @@ -104,6 +104,7 @@ "delete_current_logs": "Xóa nhật ký hiện tại", "clear_completed": "Xóa hoàn tất", "clear_logs": "Xóa nhật ký", + "now": "Hiện tại", "total_logs": "Tổng cộng {{length}} nhật ký đã được truy vấn", "filtered_logs": "{{length}} nhật ký sau khi lọc", "enter_filter_conditions": "Vui lòng nhập điều kiện lọc để truy vấn", diff --git a/src/locales/zh-CN/translation.json b/src/locales/zh-CN/translation.json index 1267d3472..683124829 100644 --- a/src/locales/zh-CN/translation.json +++ b/src/locales/zh-CN/translation.json @@ -104,6 +104,7 @@ "delete_current_logs": "删除当前日志", "clear_completed": "清空完成", "clear_logs": "清空日志", + "now": "至今", "total_logs": "共查询到 {{length}} 条日志", "filtered_logs": "筛选后 {{length}} 条日志", "enter_filter_conditions": "请输入筛选条件进行查询", diff --git a/src/locales/zh-TW/translation.json b/src/locales/zh-TW/translation.json index a56ddd57e..d618b9144 100644 --- a/src/locales/zh-TW/translation.json +++ b/src/locales/zh-TW/translation.json @@ -104,6 +104,7 @@ "delete_current_logs": "刪除目前紀錄", "clear_completed": "清除完成", "clear_logs": "清除紀錄", + "now": "至今", "total_logs": "共查詢到 {{length}} 筆紀錄", "filtered_logs": "篩選後 {{length}} 條紀錄", "enter_filter_conditions": "請輸入篩選條件進行查詢", diff --git a/src/pages/components/FileSystemParams/index.tsx b/src/pages/components/FileSystemParams/index.tsx index 8e7549985..ebf74473a 100644 --- a/src/pages/components/FileSystemParams/index.tsx +++ b/src/pages/components/FileSystemParams/index.tsx @@ -1,9 +1,9 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Button, Input, Message, Popconfirm, Select, Space } from "@arco-design/web-react"; import type { FileSystemType } from "@Packages/filesystem/factory"; import FileSystemFactory from "@Packages/filesystem/factory"; import { useTranslation } from "react-i18next"; -import { ClearNetDiskToken, netDiskTypeMap } from "@Packages/filesystem/auth"; +import { ClearNetDiskToken, HasNetDiskToken, netDiskTypeMap } from "@Packages/filesystem/auth"; const FileSystemParams: React.FC<{ headerContent: React.ReactNode | string; @@ -22,6 +22,17 @@ const FileSystemParams: React.FC<{ }) => { const fsParams = FileSystemFactory.params(); const { t } = useTranslation(); + const [hasBoundToken, setHasBoundToken] = useState(false); + + const netDiskType = netDiskTypeMap[fileSystemType]; + + useEffect(() => { + if (!netDiskType) { + setHasBoundToken(false); + return; + } + HasNetDiskToken(netDiskType).then(setHasBoundToken); + }, [netDiskType]); const fileSystemList: { key: FileSystemType; @@ -53,7 +64,6 @@ const FileSystemParams: React.FC<{ }, ]; - const netDiskType = netDiskTypeMap[fileSystemType]; const netDiskName = netDiskType ? fileSystemList.find((item) => item.key === fileSystemType)?.name : null; return ( @@ -74,13 +84,14 @@ const FileSystemParams: React.FC<{ ))} {children} - {netDiskType && netDiskName && ( + {netDiskType && netDiskName && hasBoundToken && ( { try { await ClearNetDiskToken(netDiskType); + setHasBoundToken(false); Message.success(t("netdisk_unbind_success", { provider: netDiskName })!); } catch (error) { Message.error(`${t("netdisk_unbind_error", { provider: netDiskName })}: ${String(error)}`); diff --git a/src/pages/components/layout/MainLayout.tsx b/src/pages/components/layout/MainLayout.tsx index e2c670fbc..b254dca97 100644 --- a/src/pages/components/layout/MainLayout.tsx +++ b/src/pages/components/layout/MainLayout.tsx @@ -119,6 +119,15 @@ const importByUrls = async (urls: string[]): Promise => return stat; }; +const getSafePopupParent = (p: Element) => { + p = (p.closest("button")?.parentNode as Element) || p; // 確保 ancestor 沒有 button 元素 + p = (p.closest("span")?.parentNode as Element) || p; // 確保 ancestor 沒有 span 元素 + p = (p.closest(".arco-collapse-item-content")?.parentNode as Element) || p; // 確保 ancestor 沒有 .arco-collapse-item-content 元素 + p = (p.closest(".arco-card")?.parentNode as Element) || p; // 確保 ancestor 沒有 .arco-card 元素 + p = (p.closest("aside")?.parentNode as Element) || p; // 確保 ancestor 沒有 aside 元素 + return p; +}; + // --- 子组件:提取拖拽遮罩以优化性能 --- const DropzoneOverlay: React.FC<{ active: boolean; text: string }> = React.memo(({ active, text }) => { if (!active) return null; @@ -297,16 +306,12 @@ const MainLayout: React.FC<{ componentConfig={{ Select: { getPopupContainer: (node) => { - return node; + return getSafePopupParent(node as Element); }, }, }} getPopupContainer={(node) => { - let p = node.parentNode as Element; - p = (p.closest("button")?.parentNode as Element) || p; // 確保 ancestor 沒有 button 元素 - p = (p.closest("span")?.parentNode as Element) || p; // 確保 ancestor 沒有 span 元素 - p = (p.closest("aside")?.parentNode as Element) || p; // 確保 ancestor 沒有 aside 元素 - return p; + return getSafePopupParent(node.parentNode as Element); }} > {contextHolder} diff --git a/src/pages/options/routes/Logger.tsx b/src/pages/options/routes/Logger.tsx index 8cf1db927..d77b5db94 100644 --- a/src/pages/options/routes/Logger.tsx +++ b/src/pages/options/routes/Logger.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useRef } from "react"; import { BackTop, Button, Card, DatePicker, Input, List, Message, Space, Typography } from "@arco-design/web-react"; import dayjs from "dayjs"; import type { Logger } from "@App/app/repo/logger"; @@ -20,14 +20,23 @@ function LoggerPage() { const [search, setSearch] = React.useState(""); const [startTime, setStartTime] = React.useState(dayjs().subtract(24, "hour").unix()); const [endTime, setEndTime] = React.useState(dayjs().unix()); + // 标记 endTime 是否代表"当前时间",默认为 true + const [isNow, setIsNow] = React.useState(true); + // 用于强制触发数据重新加载 + const [refreshToken, setRefreshToken] = React.useState(0); + // 标记数据加载后是否需要自动执行过滤 + const needFilterRef = useRef(false); + // 标记本次 onChange 是否由快捷方式触发 + const shortcutClickRef = useRef(false); const loggerDAO = new LoggerDAO(); const systemConfig = { logCleanCycle: 1 }; const { t } = useTranslation(); - const onQueryLog = () => { + const onQueryLog = (logsToFilter?: Logger[]) => { + const data = logsToFilter ?? logs; const newQueryLogs: Logger[] = []; const regex = search && new RegExp(search); - logs.forEach((log) => { + data.forEach((log) => { for (let i = 0; i < querys.length; i += 1) { const query = querys[i]; if (query.key) { @@ -99,12 +108,18 @@ function LoggerPage() { }); }); setLabels(newLabels); - setQueryLogs([]); + // 如果是查询按钮触发的刷新,自动执行过滤 + if (needFilterRef.current) { + needFilterRef.current = false; + onQueryLog(l); + } else { + setQueryLogs([]); + } if (init === 0) { setInit(1); } }); - }, [startTime, endTime]); + }, [startTime, endTime, refreshToken]); return ( <> @@ -133,10 +148,27 @@ function LoggerPage() { style={{ width: 400 }} showTime shortcutsPlacementLeft - value={[startTime * 1000, endTime * 1000]} + placeholder={isNow ? ["", t("now")] : undefined} + value={isNow ? [startTime * 1000] : [startTime * 1000, endTime * 1000]} onChange={(_, time) => { + if (!time || !time[0]) { + // 清除操作,恢复默认状态 + setStartTime(dayjs().subtract(24, "hour").unix()); + setEndTime(dayjs().unix()); + setIsNow(true); + return; + } setStartTime(time[0].unix()); setEndTime(time[1].unix()); + if (shortcutClickRef.current) { + shortcutClickRef.current = false; + setIsNow(true); + } else { + setIsNow(false); + } + }} + onSelectShortcut={() => { + shortcutClickRef.current = true; }} shortcuts={[ { @@ -177,7 +209,20 @@ function LoggerPage() { }, ]} /> - @@ -289,7 +334,8 @@ function LoggerPage() { }} > - {formatUnixTime(startTime)} {t("to")} {formatUnixTime(endTime)} {t("total_logs", { length: logs.length })} + {formatUnixTime(startTime)} {t("to")} {isNow ? t("now") : formatUnixTime(endTime)}{" "} + {t("total_logs", { length: logs.length })} {init === 4 ? `, ${t("filtered_logs", { length: queryLogs.length })}` : `, ${t("enter_filter_conditions")}`}