fix: safe copy to clipboard

This commit is contained in:
Carlos Valente
2025-11-30 20:03:07 +01:00
committed by Carlos Valente
parent 9ba5ebd4bd
commit d116670318
3 changed files with 32 additions and 14 deletions
@@ -1,7 +1,7 @@
import { PropsWithChildren, useRef, useState } from 'react'; import { PropsWithChildren, useRef, useState } from 'react';
import { IoCheckmark, IoCopy } from 'react-icons/io5'; import { IoCheckmark, IoCopy } from 'react-icons/io5';
import copyToClipboard from '../../utils/copyToClipboard'; import { copyToClipboard } from '../../utils/copyToClipboard';
import { cx } from '../../utils/styleUtils'; import { cx } from '../../utils/styleUtils';
import Button from '../buttons/Button'; import Button from '../buttons/Button';
import IconButton from '../buttons/IconButton'; import IconButton from '../buttons/IconButton';
@@ -25,15 +25,19 @@ export default function CopyTag({
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null); const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const handleClick = () => { const handleClick = async () => {
copyToClipboard(copyValue); try {
setCopied(true); await copyToClipboard(copyValue);
setCopied(true);
// reset copied state // reset copied state
if (timeoutRef.current) { if (timeoutRef.current) {
clearTimeout(timeoutRef.current); clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => setCopied(false), 2000);
} catch {
// ignore errors
} }
timeoutRef.current = setTimeout(() => setCopied(false), 2000);
}; };
return ( return (
@@ -1,4 +1,18 @@
// we need to this as a promise because safari /**
export default async function copyToClipboard(text: string) { * copy text to clipboard
setTimeout(async () => await navigator.clipboard?.writeText(text)); * @throws if not supported or permission denied
*/
export async function copyToClipboard(text: string) {
await navigator.clipboard?.writeText(text);
}
/**
* Copy to clipboard but safely ignore errors
*/
export async function safeCopyToClipboard(text: string): Promise<void> {
try {
await copyToClipboard(text);
} catch {
// Silently ignore errors
}
} }
@@ -13,7 +13,7 @@ import Input from '../../common/components/input/input/Input';
import Select from '../../common/components/select/Select'; import Select from '../../common/components/select/Select';
import Switch from '../../common/components/switch/Switch'; import Switch from '../../common/components/switch/Switch';
import { useUpdateUrlPreset } from '../../common/hooks-query/useUrlPresets'; import { useUpdateUrlPreset } from '../../common/hooks-query/useUrlPresets';
import copyToClipboard from '../../common/utils/copyToClipboard'; import { safeCopyToClipboard } from '../../common/utils/copyToClipboard';
import { preventEscape } from '../../common/utils/keyEvent'; import { preventEscape } from '../../common/utils/keyEvent';
import { isUrlSafe } from '../../common/utils/regex'; import { isUrlSafe } from '../../common/utils/regex';
import { isOntimeCloud, serverURL } from '../../externals'; import { isOntimeCloud, serverURL } from '../../externals';
@@ -125,7 +125,7 @@ export default function GenerateLinkForm({ hostOptions, pathOptions, presets, is
lockNav: options.lockNav, lockNav: options.lockNav,
preset: urlPreset.alias, preset: urlPreset.alias,
}); });
await copyToClipboard(url); await safeCopyToClipboard(url);
setUrl(url); setUrl(url);
} else { } else {
const presetPath = options.path.startsWith('preset-') ? options.path.replace('preset-', '') : undefined; const presetPath = options.path.startsWith('preset-') ? options.path.replace('preset-', '') : undefined;
@@ -143,7 +143,7 @@ export default function GenerateLinkForm({ hostOptions, pathOptions, presets, is
preset: presetPath, preset: presetPath,
}); });
await copyToClipboard(url); await safeCopyToClipboard(url);
setUrl(url); setUrl(url);
} }
reset(options, { reset(options, {