mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 18:50:49 +08:00
fix(clipboard): add fallback helper and unify copy actions
This commit is contained in:
@@ -8,6 +8,7 @@ import { IconChevronDown } from '@/components/ui/icons';
|
|||||||
import { ConfigSection } from '@/components/config/ConfigSection';
|
import { ConfigSection } from '@/components/config/ConfigSection';
|
||||||
import { useNotificationStore } from '@/stores';
|
import { useNotificationStore } from '@/stores';
|
||||||
import styles from './VisualConfigEditor.module.scss';
|
import styles from './VisualConfigEditor.module.scss';
|
||||||
|
import { copyToClipboard } from '@/utils/clipboard';
|
||||||
import type {
|
import type {
|
||||||
PayloadFilterRule,
|
PayloadFilterRule,
|
||||||
PayloadModelEntry,
|
PayloadModelEntry,
|
||||||
@@ -268,31 +269,11 @@ function ApiKeysCardEditor({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCopy = async (apiKey: string) => {
|
const handleCopy = async (apiKey: string) => {
|
||||||
const copyByExecCommand = () => {
|
const copied = await copyToClipboard(apiKey);
|
||||||
const textarea = document.createElement('textarea');
|
showNotification(
|
||||||
textarea.value = apiKey;
|
t(copied ? 'notification.link_copied' : 'notification.copy_failed'),
|
||||||
textarea.setAttribute('readonly', '');
|
copied ? 'success' : 'error'
|
||||||
textarea.style.position = 'fixed';
|
);
|
||||||
textarea.style.opacity = '0';
|
|
||||||
textarea.style.pointerEvents = 'none';
|
|
||||||
document.body.appendChild(textarea);
|
|
||||||
textarea.select();
|
|
||||||
textarea.setSelectionRange(0, textarea.value.length);
|
|
||||||
const copied = document.execCommand('copy');
|
|
||||||
document.body.removeChild(textarea);
|
|
||||||
if (!copied) throw new Error('copy_failed');
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (navigator.clipboard?.writeText) {
|
|
||||||
await navigator.clipboard.writeText(apiKey);
|
|
||||||
} else {
|
|
||||||
copyByExecCommand();
|
|
||||||
}
|
|
||||||
showNotification(t('notification.link_copied'), 'success');
|
|
||||||
} catch {
|
|
||||||
showNotification(t('notification.copy_failed'), 'error');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Card } from '@/components/ui/Card';
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { EmptyState } from '@/components/ui/EmptyState';
|
import { EmptyState } from '@/components/ui/EmptyState';
|
||||||
import { copyToClipboard } from '@/features/authFiles/clipboard';
|
import { copyToClipboard } from '@/utils/clipboard';
|
||||||
import {
|
import {
|
||||||
MAX_CARD_PAGE_SIZE,
|
MAX_CARD_PAGE_SIZE,
|
||||||
MIN_CARD_PAGE_SIZE,
|
MIN_CARD_PAGE_SIZE,
|
||||||
@@ -523,4 +523,3 @@ export function AuthFilesPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
|
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
|
||||||
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
||||||
import { logsApi } from '@/services/api/logs';
|
import { logsApi } from '@/services/api/logs';
|
||||||
|
import { copyToClipboard } from '@/utils/clipboard';
|
||||||
import { MANAGEMENT_API_PREFIX } from '@/utils/constants';
|
import { MANAGEMENT_API_PREFIX } from '@/utils/constants';
|
||||||
import { formatUnixTimestamp } from '@/utils/format';
|
import { formatUnixTimestamp } from '@/utils/format';
|
||||||
import styles from './LogsPage.module.scss';
|
import styles from './LogsPage.module.scss';
|
||||||
@@ -344,30 +345,6 @@ const getErrorMessage = (err: unknown): string => {
|
|||||||
return typeof message === 'string' ? message : '';
|
return typeof message === 'string' ? message : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyToClipboard = async (text: string) => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
textarea.value = text;
|
|
||||||
textarea.style.position = 'fixed';
|
|
||||||
textarea.style.opacity = '0';
|
|
||||||
textarea.style.left = '-9999px';
|
|
||||||
textarea.style.top = '0';
|
|
||||||
document.body.appendChild(textarea);
|
|
||||||
textarea.focus();
|
|
||||||
textarea.select();
|
|
||||||
const ok = document.execCommand('copy');
|
|
||||||
document.body.removeChild(textarea);
|
|
||||||
return ok;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type TabType = 'logs' | 'errors';
|
type TabType = 'logs' | 'errors';
|
||||||
|
|
||||||
export function LogsPage() {
|
export function LogsPage() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Input } from '@/components/ui/Input';
|
|||||||
import { useNotificationStore, useThemeStore } from '@/stores';
|
import { useNotificationStore, useThemeStore } from '@/stores';
|
||||||
import { oauthApi, type OAuthProvider, type IFlowCookieAuthResponse } from '@/services/api/oauth';
|
import { oauthApi, type OAuthProvider, type IFlowCookieAuthResponse } from '@/services/api/oauth';
|
||||||
import { vertexApi, type VertexImportResponse } from '@/services/api/vertex';
|
import { vertexApi, type VertexImportResponse } from '@/services/api/vertex';
|
||||||
|
import { copyToClipboard } from '@/utils/clipboard';
|
||||||
import styles from './OAuthPage.module.scss';
|
import styles from './OAuthPage.module.scss';
|
||||||
import iconCodexLight from '@/assets/icons/codex_light.svg';
|
import iconCodexLight from '@/assets/icons/codex_light.svg';
|
||||||
import iconCodexDark from '@/assets/icons/codex_drak.svg';
|
import iconCodexDark from '@/assets/icons/codex_drak.svg';
|
||||||
@@ -186,12 +187,11 @@ export function OAuthPage() {
|
|||||||
|
|
||||||
const copyLink = async (url?: string) => {
|
const copyLink = async (url?: string) => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
try {
|
const copied = await copyToClipboard(url);
|
||||||
await navigator.clipboard.writeText(url);
|
showNotification(
|
||||||
showNotification(t('notification.link_copied'), 'success');
|
t(copied ? 'notification.link_copied' : 'notification.copy_failed'),
|
||||||
} catch {
|
copied ? 'success' : 'error'
|
||||||
showNotification(t('notification.copy_failed'), 'error');
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitCallback = async (provider: OAuthProvider) => {
|
const submitCallback = async (provider: OAuthProvider) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const copyToClipboard = async (text: string): Promise<boolean> => {
|
export async function copyToClipboard(text: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
@@ -9,20 +9,41 @@ export const copyToClipboard = async (text: string): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (typeof document === 'undefined') return false;
|
||||||
|
if (!document.body) return false;
|
||||||
|
|
||||||
|
const activeElement = document.activeElement as HTMLElement | null;
|
||||||
|
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.value = text;
|
textarea.value = text;
|
||||||
|
textarea.setAttribute('readonly', '');
|
||||||
textarea.style.position = 'fixed';
|
textarea.style.position = 'fixed';
|
||||||
textarea.style.opacity = '0';
|
textarea.style.opacity = '0';
|
||||||
|
textarea.style.pointerEvents = 'none';
|
||||||
textarea.style.left = '-9999px';
|
textarea.style.left = '-9999px';
|
||||||
textarea.style.top = '0';
|
textarea.style.top = '0';
|
||||||
|
textarea.style.width = '1px';
|
||||||
|
textarea.style.height = '1px';
|
||||||
|
textarea.style.padding = '0';
|
||||||
|
textarea.style.border = '0';
|
||||||
|
|
||||||
document.body.appendChild(textarea);
|
document.body.appendChild(textarea);
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
textarea.select();
|
textarea.select();
|
||||||
|
textarea.setSelectionRange(0, textarea.value.length);
|
||||||
const copied = document.execCommand('copy');
|
const copied = document.execCommand('copy');
|
||||||
document.body.removeChild(textarea);
|
document.body.removeChild(textarea);
|
||||||
|
|
||||||
|
if (activeElement?.focus) {
|
||||||
|
try {
|
||||||
|
activeElement.focus();
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return copied;
|
return copied;
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user