fix: async clipboard support in Safari (#16619)

This commit is contained in:
Omar López authored and GitHub committed 2024-09-13 09:27:28 +01:00
1 parent 111e2f30c6
commit fcb79888f3
2 files changed
+46 -11

No files matched your search

+13 -10
View File
@@ -929,7 +929,7 @@ function SideBarContainer({ bannersHeight, isPlatformUser = false }: SideBarCont
}
function SideBar({ bannersHeight, user }: SideBarProps) {
const { copyToClipboard } = useCopy();
const { fetchAndCopyToClipboard } = useCopy();
const { t, isLocaleReady } = useLocale();
const orgBranding = useOrgBranding();
const pathname = usePathname();
@@ -967,16 +967,19 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
? {
name: "copy_referral_link",
href: "",
onClick: async (e: { preventDefault: () => void }) => {
onClick: (e: { preventDefault: () => void }) => {
e.preventDefault();
const res = await fetch("/api/generate-referral-link", {
method: "POST",
});
const { shortLink } = await res.json();
copyToClipboard(shortLink, {
onSuccess: () => showToast(t("link_copied"), "success"),
onFailure: () => showToast("Copy to clipboard failed", "error"),
});
fetchAndCopyToClipboard(
fetch("/api/generate-referral-link", {
method: "POST",
})
.then((res) => res.json())
.then((res) => res.shortLink),
{
onSuccess: () => showToast(t("link_copied"), "success"),
onFailure: () => showToast("Copy to clipboard failed", "error"),
}
);
},
icon: "gift",
}
+33 -1
View File
@@ -27,6 +27,7 @@ export function useCopy() {
"You need to use a secure context to use clipboard \n Please use the following link: ",
text
);
onFailure();
}
};
@@ -34,6 +35,37 @@ export function useCopy() {
setIsCopied(false);
};
/** @see https://wolfgangrittner.dev/how-to-use-clipboard-api-in-safari/ */
const fetchAndCopyToClipboard = (
promise: Promise<string>,
options: { onSuccess?: () => void; onFailure?: () => void } = {}
) => {
const { onSuccess = noop, onFailure = noop } = options;
if (typeof ClipboardItem && navigator.clipboard.write) {
// NOTE: Safari locks down the clipboard API to only work when triggered
// by a direct user interaction. You can't use it async in a promise.
// But! You can wrap the promise in a ClipboardItem, and give that to
// the clipboard API.
// Found this on https://developer.apple.com/forums/thread/691873
const text = new ClipboardItem({
"text/plain": promise
.then((text) => new Blob([text], { type: "text/plain" }))
.catch(() => {
onFailure();
return "";
}),
});
navigator.clipboard.write([text]);
onSuccess();
} else {
// NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
// but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
// Good news is that other than Safari, Firefox does not care about
// Clipboard API being used async in a Promise.
promise.then((text) => copyToClipboard(text, options)).catch(() => onFailure());
}
};
useEffect(() => {
if (isCopied) {
const timer = setTimeout(resetCopyStatus, 3000); // Reset copy status after 3 seconds
@@ -41,5 +73,5 @@ export function useCopy() {
}
}, [isCopied]);
return { isCopied, copyToClipboard, resetCopyStatus };
return { isCopied, copyToClipboard, resetCopyStatus, fetchAndCopyToClipboard };
}