mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
feat(notice): override context menu to copy error details
This commit is contained in:
@@ -40,5 +40,6 @@
|
||||
- 完善对 AnyTLS / Mieru / Sudoku 的 GUI 支持
|
||||
- macOS 和 Linux 对服务 IPC 权限进一步限制
|
||||
- 移除 Windows 自启动计划任务中冗余的 3 秒延时
|
||||
- 右键错误通知可复制错误详情
|
||||
|
||||
</details>
|
||||
|
||||
@@ -6,13 +6,14 @@ import {
|
||||
Box,
|
||||
type SnackbarOrigin,
|
||||
} from "@mui/material";
|
||||
import React, { useMemo, useSyncExternalStore } from "react";
|
||||
import React, { useCallback, useMemo, useSyncExternalStore } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
subscribeNotices,
|
||||
hideNotice,
|
||||
getSnapshotNotices,
|
||||
showNotice,
|
||||
} from "@/services/notice-service";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
|
||||
@@ -85,6 +86,45 @@ const resolveNoticeMessage = (
|
||||
});
|
||||
};
|
||||
|
||||
const extractNoticeCopyText = (input: unknown): string | undefined => {
|
||||
if (input === null || input === undefined) return undefined;
|
||||
if (typeof input === "string") return input;
|
||||
if (typeof input === "number" || typeof input === "boolean") {
|
||||
return String(input);
|
||||
}
|
||||
if (input instanceof Error) {
|
||||
return input.message || input.name;
|
||||
}
|
||||
if (React.isValidElement(input)) return undefined;
|
||||
if (typeof input === "object") {
|
||||
const maybeMessage = (input as { message?: unknown }).message;
|
||||
if (typeof maybeMessage === "string") return maybeMessage;
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(input);
|
||||
} catch {
|
||||
return String(input);
|
||||
}
|
||||
};
|
||||
|
||||
const resolveNoticeCopyText = (
|
||||
notice: NoticeItem,
|
||||
t: TranslationFn,
|
||||
): string | undefined => {
|
||||
if (
|
||||
notice.i18n?.key === "shared.feedback.notices.prefixedRaw" ||
|
||||
notice.i18n?.key === "shared.feedback.notices.raw"
|
||||
) {
|
||||
const rawText = extractNoticeCopyText(notice.i18n?.params?.message);
|
||||
if (rawText) return rawText;
|
||||
}
|
||||
|
||||
return (
|
||||
extractNoticeCopyText(resolveNoticeMessage(notice, t)) ??
|
||||
extractNoticeCopyText(notice.message)
|
||||
);
|
||||
};
|
||||
|
||||
interface NoticeManagerProps {
|
||||
position?: NoticePosition | null;
|
||||
}
|
||||
@@ -105,6 +145,23 @@ export const NoticeManager: React.FC<NoticeManagerProps> = ({ position }) => {
|
||||
hideNotice(id);
|
||||
};
|
||||
|
||||
const handleNoticeCopy = useCallback(
|
||||
async (notice: NoticeItem) => {
|
||||
const text = resolveNoticeCopyText(notice, t);
|
||||
if (!text) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
showNotice.success(
|
||||
"shared.feedback.notifications.common.copySuccess",
|
||||
1000,
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn("[NoticeManager] copy to clipboard failed:", error);
|
||||
}
|
||||
},
|
||||
[t],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -139,6 +196,11 @@ export const NoticeManager: React.FC<NoticeManagerProps> = ({ position }) => {
|
||||
severity={notice.type}
|
||||
variant="filled"
|
||||
sx={{ width: "100%" }}
|
||||
onContextMenu={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
void handleNoticeCopy(notice);
|
||||
}}
|
||||
action={
|
||||
<IconButton
|
||||
size="small"
|
||||
|
||||
Reference in New Issue
Block a user