Skip to content

Commit d2c83fd

Browse files
BrianWang1990testdabing1990hyoban
authored andcommitted
fix: copy button not working on API Server and API Key pages (langgenius#34515)
Co-authored-by: Brian Wang <BrianWang1990@users.noreply.github.com> Co-authored-by: test <test@testdeMac-mini.local> Co-authored-by: BrianWang1990 <512dabing99@163.com> Co-authored-by: Stephen Zhou <hi@hyoban.cc> Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
1 parent a5e27ff commit d2c83fd

14 files changed

Lines changed: 150 additions & 46 deletions

pnpm-lock.yaml

Lines changed: 6 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ catalog:
129129
ahooks: 3.9.7
130130
autoprefixer: 10.4.27
131131
class-variance-authority: 0.7.1
132+
client-only: 0.0.1
132133
clsx: 2.1.1
133134
cmdk: 1.1.1
134135
code-inspector-plugin: 1.5.1
@@ -154,7 +155,6 @@ catalog:
154155
eslint-plugin-sonarjs: 4.0.2
155156
eslint-plugin-storybook: 10.3.5
156157
fast-deep-equal: 3.1.3
157-
foxact: 0.3.0
158158
happy-dom: 20.8.9
159159
hast-util-to-jsx-runtime: 2.3.6
160160
hono: 4.12.12

web/app/components/base/copy-feedback/__tests__/index.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const mockCopy = vi.fn()
55
const mockReset = vi.fn()
66
let mockCopied = false
77

8-
vi.mock('foxact/use-clipboard', () => ({
8+
vi.mock('@/hooks/use-clipboard', () => ({
99
useClipboard: () => ({
1010
copy: mockCopy,
1111
reset: mockReset,

web/app/components/base/copy-feedback/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ import {
33
RiClipboardFill,
44
RiClipboardLine,
55
} from '@remixicon/react'
6-
import { useClipboard } from 'foxact/use-clipboard'
76
import { useCallback } from 'react'
87
import { useTranslation } from 'react-i18next'
98
import ActionButton from '@/app/components/base/action-button'
109
import Tooltip from '@/app/components/base/tooltip'
10+
import { useClipboard } from '@/hooks/use-clipboard'
1111
import copyStyle from './style.module.css'
1212

1313
type Props = {

web/app/components/base/copy-icon/__tests__/index.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const copy = vi.fn()
55
const reset = vi.fn()
66
let copied = false
77

8-
vi.mock('foxact/use-clipboard', () => ({
8+
vi.mock('@/hooks/use-clipboard', () => ({
99
useClipboard: () => ({
1010
copy,
1111
reset,

web/app/components/base/copy-icon/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client'
2-
import { useClipboard } from 'foxact/use-clipboard'
32
import { useCallback } from 'react'
43
import { useTranslation } from 'react-i18next'
4+
import { useClipboard } from '@/hooks/use-clipboard'
55
import Tooltip from '../tooltip'
66

77
type Props = {

web/app/components/base/input-with-copy/__tests__/index.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const mockCopy = vi.fn()
66
let mockCopied = false
77
const mockReset = vi.fn()
88

9-
vi.mock('foxact/use-clipboard', () => ({
9+
vi.mock('@/hooks/use-clipboard', () => ({
1010
useClipboard: () => ({
1111
copy: mockCopy,
1212
copied: mockCopied,

web/app/components/base/input-with-copy/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client'
22
import type { InputProps } from '../input'
3-
import { useClipboard } from 'foxact/use-clipboard'
43
import * as React from 'react'
54
import { useTranslation } from 'react-i18next'
5+
import { useClipboard } from '@/hooks/use-clipboard'
66
import { cn } from '@/utils/classnames'
77
import ActionButton from '../action-button'
88
import Tooltip from '../tooltip'

web/hooks/noop.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type Noop = {
2+
// eslint-disable-next-line ts/no-explicit-any
3+
(...args: any[]): any
4+
}
5+
6+
/** @see https://foxact.skk.moe/noop */
7+
export const noop: Noop = () => { /* noop */ }

web/hooks/use-clipboard.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { useRef, useState } from 'react'
2+
import { writeTextToClipboard } from '@/utils/clipboard'
3+
import { noop } from './noop'
4+
import { useStableHandler } from './use-stable-handler-only-when-you-know-what-you-are-doing-or-you-will-be-fired'
5+
import { useCallback } from './use-typescript-happy-callback'
6+
import 'client-only'
7+
8+
type UseClipboardOption = {
9+
timeout?: number
10+
usePromptAsFallback?: boolean
11+
promptFallbackText?: string
12+
onCopyError?: (error: Error) => void
13+
}
14+
15+
/** @see https://foxact.skk.moe/use-clipboard */
16+
export function useClipboard({
17+
timeout = 1000,
18+
usePromptAsFallback = false,
19+
promptFallbackText = 'Failed to copy to clipboard automatically, please manually copy the text below.',
20+
onCopyError,
21+
}: UseClipboardOption = {}) {
22+
const [error, setError] = useState<Error | null>(null)
23+
const [copied, setCopied] = useState(false)
24+
const copyTimeoutRef = useRef<number | null>(null)
25+
26+
const stablizedOnCopyError = useStableHandler<[e: Error], void>(onCopyError || noop)
27+
28+
const handleCopyResult = useCallback((isCopied: boolean) => {
29+
if (copyTimeoutRef.current) {
30+
clearTimeout(copyTimeoutRef.current)
31+
}
32+
if (isCopied) {
33+
copyTimeoutRef.current = window.setTimeout(() => setCopied(false), timeout)
34+
}
35+
setCopied(isCopied)
36+
}, [timeout])
37+
38+
const handleCopyError = useCallback((e: Error) => {
39+
setError(e)
40+
stablizedOnCopyError(e)
41+
}, [stablizedOnCopyError])
42+
43+
const copy = useCallback(async (valueToCopy: string) => {
44+
try {
45+
await writeTextToClipboard(valueToCopy)
46+
}
47+
catch (e) {
48+
if (usePromptAsFallback) {
49+
try {
50+
// eslint-disable-next-line no-alert -- prompt as fallback in case of copy error
51+
window.prompt(promptFallbackText, valueToCopy)
52+
}
53+
catch (e2) {
54+
handleCopyError(e2 as Error)
55+
}
56+
}
57+
else {
58+
handleCopyError(e as Error)
59+
}
60+
}
61+
}, [handleCopyResult, promptFallbackText, handleCopyError, usePromptAsFallback])
62+
63+
const reset = useCallback(() => {
64+
setCopied(false)
65+
setError(null)
66+
if (copyTimeoutRef.current) {
67+
clearTimeout(copyTimeoutRef.current)
68+
}
69+
}, [])
70+
71+
return { copy, reset, error, copied }
72+
}

0 commit comments

Comments
 (0)