7b9138ed08
The copy in the extraction method has been added with a fallback mechanism to improve adaptability
169 lines
5.5 KiB
TypeScript
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,
|
|
};
|
|
};
|