Message Context Menu: Fix message link copying in Safari (#4530)

This commit is contained in:
Alexander Zinchuk 2024-05-03 14:38:14 +02:00
parent 6def917d1c
commit 209564ca6f
4 changed files with 74 additions and 24 deletions

View File

@ -397,7 +397,13 @@ const MessageContextMenu: FC<OwnProps> = ({
<MenuItem icon="web" onClick={onSelectLanguage}>{lang('lng_settings_change_lang')}</MenuItem>
)}
{copyOptions.map((option) => (
<MenuItem key={option.label} icon={option.icon} onClick={option.handler}>{lang(option.label)}</MenuItem>
<MenuItem
key={option.label}
icon={option.icon}
onClick={option.handler}
withPreventDefaultOnMouseDown
>{lang(option.label)}
</MenuItem>
))}
{canPin && <MenuItem icon="pin" onClick={onPin}>{lang('DialogPin')}</MenuItem>}
{canUnpin && <MenuItem icon="unpin" onClick={onUnpin}>{lang('DialogUnpin')}</MenuItem>}

View File

@ -27,6 +27,7 @@ export type MenuItemProps = {
destructive?: boolean;
ariaLabel?: string;
withWrap?: boolean;
withPreventDefaultOnMouseDown?: boolean;
};
const MenuItem: FC<MenuItemProps> = (props) => {
@ -45,6 +46,7 @@ const MenuItem: FC<MenuItemProps> = (props) => {
withWrap,
onContextMenu,
clickArg,
withPreventDefaultOnMouseDown,
} = props;
const lang = useLang();
@ -74,6 +76,11 @@ const MenuItem: FC<MenuItemProps> = (props) => {
onClick(e, clickArg);
});
const handleMouseDown = useLastCallback((e: React.SyntheticEvent<HTMLDivElement | HTMLAnchorElement>) => {
if (withPreventDefaultOnMouseDown) {
e.preventDefault();
}
});
const fullClassName = buildClassName(
'MenuItem',
@ -110,6 +117,7 @@ const MenuItem: FC<MenuItemProps> = (props) => {
rel="noopener noreferrer"
dir={lang.isRtl ? 'rtl' : undefined}
onClick={onClick}
onMouseDown={handleMouseDown}
>
{content}
</a>
@ -123,6 +131,7 @@ const MenuItem: FC<MenuItemProps> = (props) => {
className={fullClassName}
onClick={handleClick}
onKeyDown={handleKeyDown}
onMouseDown={handleMouseDown}
onContextMenu={onContextMenu}
aria-label={ariaLabel}
title={ariaLabel}

View File

@ -33,7 +33,7 @@ import {
SUPPORTED_IMAGE_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES,
} from '../../../config';
import { copyTextToClipboard } from '../../../util/clipboard';
import { copyTextToClipboardFromPromise } from '../../../util/clipboard';
import { isDeepLink } from '../../../util/deepLinkParser';
import { ensureProtocol } from '../../../util/ensureProtocol';
import { getCurrentTabId } from '../../../util/establishMultitabRole';
@ -1962,35 +1962,25 @@ addActionHandler('copyMessageLink', async (global, actions, payload): Promise<vo
});
return;
}
if (!isChatChannel(chat) && !isChatSuperGroup(chat)) {
actions.showNotification({
message: translate('lng_filters_link_private_error'),
tabId,
});
return;
}
const link = await callApi('exportMessageLink', {
chat,
id: messageId,
shouldIncludeThread,
shouldIncludeGrouped,
const showErrorOccurredNotification = () => actions.showNotification({
message: translate('ErrorOccurred'),
tabId,
});
if (!link) {
actions.showNotification({
message: translate('ErrorOccurred'),
tabId,
});
if (!isChatChannel(chat) && !isChatSuperGroup(chat)) {
showErrorOccurredNotification();
return;
}
copyTextToClipboard(link);
actions.showNotification({
const showLinkCopiedNotification = () => actions.showNotification({
message: translate('LinkCopied'),
tabId,
});
const callApiExportMessageLinkPromise = callApi('exportMessageLink', {
chat, id: messageId, shouldIncludeThread, shouldIncludeGrouped,
});
await copyTextToClipboardFromPromise(
callApiExportMessageLinkPromise, showLinkCopiedNotification, showErrorOccurredNotification,
);
});
function countSortedIds(ids: number[], from: number, to: number) {

View File

@ -59,6 +59,51 @@ export const copyImageToClipboard = (imageUrl?: string) => {
imageEl.src = imageUrl;
};
export const copyTextToClipboardFromPromise = async (
getTextPromise: Promise<string | undefined>,
onSuccess: NoneToVoidFunction,
onFailure: NoneToVoidFunction,
) => {
const copyTextToClipboardFallback = async () => {
try {
const text = await getTextPromise;
if (text) {
copyTextToClipboard(text);
} else {
onFailure();
}
return Boolean(text);
} catch {
onFailure();
return false;
}
};
if (!CLIPBOARD_ITEM_SUPPORTED || !navigator.clipboard.write) {
if (await copyTextToClipboardFallback()) onSuccess();
return;
}
try {
let hasGetDataError = false;
const rejectGetDataError = () => Promise.reject(new Error('GET_DATA_ERROR'));
const clipboardTextItem = new ClipboardItem({
'text/plain': getTextPromise.then((text) => text || rejectGetDataError()).catch(() => {
hasGetDataError = true;
return '';
}),
});
await navigator.clipboard.write([clipboardTextItem]);
if (hasGetDataError) {
onFailure();
return;
}
} catch {
// Promises in ClipboardItem aren't supported in older Chrome versions
if (!await copyTextToClipboardFallback()) return;
}
onSuccess();
};
async function copyBlobToClipboard(pngBlob: Blob | null) {
if (!pngBlob || !CLIPBOARD_ITEM_SUPPORTED) {
return;