Files
PrivyDrop/frontend/hooks/useClipboardActions.ts
T
david_bai 7b9138ed08 fix(UI):Optimize mobile layout
The copy in the extraction method has been added with a fallback mechanism to improve adaptability
2025-08-25 00:04:03 +08:00

169 lines
5.5 KiB
TypeScript

import { useState, useCallback, useEffect } from "react";
import { useLocale } from "@/hooks/useLocale";
import { getDictionary } from "@/lib/dictionary";
import type { Messages } from "@/types/messages";
interface ClipboardMessages {
copiedSuccess?: string;
pastedSuccess?: string;
copyError?: string;
readError?: string;
loading?: string;
}
interface ClipboardActions {
copyText: (text: string) => Promise<void>;
readClipboard: () => Promise<string>;
isCopied: boolean;
isPasted: boolean;
isLoadingMessages: boolean;
error: string | null;
clipboardMessages: ClipboardMessages;
}
export const useClipboardActions = (): ClipboardActions => {
const locale = useLocale();
const [messages, setMessages] = useState<Messages | null>(null);
const [isLoadingMessages, setIsLoadingMessages] = useState<boolean>(true);
const [isCopied, setIsCopied] = useState<boolean>(false);
const [isPasted, setIsPasted] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [clipboardMessages, setClipboardMessages] = useState<ClipboardMessages>(
{}
);
useEffect(() => {
setIsLoadingMessages(true);
getDictionary(locale)
.then((dict) => {
setMessages(dict);
setClipboardMessages({
copiedSuccess: dict.text.clipboard_btn.Copied_dis,
pastedSuccess: dict.text.clipboard_btn.Pasted_dis,
copyError:
dict.text.clipboard_btn.Copy_failed_dis || "Failed to copy.", // Fallback
readError:
dict.text.clipboard_btn.Paste_failed_dis ||
"Failed to read clipboard.", // Fallback
loading: dict.text.Loading_dis || "Loading...", // Fallback
});
setIsLoadingMessages(false);
})
.catch((err) => {
console.error("Failed to load messages for useClipboardActions:", err);
setError("Failed to load messages");
setClipboardMessages({
// Provide fallbacks even on error
copyError: "Failed to copy.",
readError: "Failed to read clipboard.",
loading: "Loading...",
});
setIsLoadingMessages(false);
});
}, [locale]);
const copyText = useCallback(
async (textToCopy: string) => {
setError(null);
setIsCopied(false);
// Modern API: navigator.clipboard.writeText
if (navigator.clipboard && navigator.clipboard.writeText) {
try {
await navigator.clipboard.writeText(textToCopy);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
return; // Success
} catch (err) {
console.error("Failed to copy text with navigator.clipboard: ", err);
// Fallback will be attempted below
}
}
// Fallback: document.execCommand('copy')
let textArea: HTMLTextAreaElement | null = null;
try {
textArea = document.createElement("textarea");
textArea.value = textToCopy;
textArea.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge.
textArea.style.left = "-9999px";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand("copy");
if (successful) {
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
} else {
throw new Error("document.execCommand failed");
}
} catch (err) {
console.error("Fallback copy method failed: ", err);
setError(clipboardMessages.copyError || "Failed to copy.");
} finally {
if (textArea) {
document.body.removeChild(textArea);
}
}
},
[clipboardMessages.copyError]
);
const readClipboard = useCallback(async (): Promise<string> => {
setError(null);
setIsPasted(false);
if (!navigator.clipboard) {
setError(clipboardMessages.readError || "Clipboard API not available.");
return "";
}
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
if (clipboardItem.types.includes("text/html")) {
const blob = await clipboardItem.getType("text/html");
const html = await blob.text();
setIsPasted(true);
setTimeout(() => setIsPasted(false), 2000);
return html;
}
if (clipboardItem.types.includes("text/plain")) {
const blob = await clipboardItem.getType("text/plain");
const text = await blob.text();
const formattedText = text.replace(/\n/g, "<br>");
setIsPasted(true);
setTimeout(() => setIsPasted(false), 2000);
return formattedText;
}
}
console.warn("No suitable content type found in clipboard.");
setError(
clipboardMessages.readError || "No suitable content type found."
);
return "";
} catch (err) {
try {
const text = await navigator.clipboard.readText();
const formattedText = text.replace(/\n/g, "<br>");
setIsPasted(true);
setTimeout(() => setIsPasted(false), 2000);
return formattedText;
} catch (fallbackErr) {
console.error("Failed to read clipboard: ", fallbackErr);
setError(clipboardMessages.readError || "Failed to read clipboard.");
return "";
}
}
}, [clipboardMessages.readError]);
return {
copyText,
readClipboard,
isCopied,
isPasted,
isLoadingMessages,
error,
clipboardMessages,
};
};