Message Context Menu: Fix message link copying in Safari (#4530)
This commit is contained in:
parent
6def917d1c
commit
209564ca6f
@ -397,7 +397,13 @@ const MessageContextMenu: FC<OwnProps> = ({
|
|||||||
<MenuItem icon="web" onClick={onSelectLanguage}>{lang('lng_settings_change_lang')}</MenuItem>
|
<MenuItem icon="web" onClick={onSelectLanguage}>{lang('lng_settings_change_lang')}</MenuItem>
|
||||||
)}
|
)}
|
||||||
{copyOptions.map((option) => (
|
{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>}
|
{canPin && <MenuItem icon="pin" onClick={onPin}>{lang('DialogPin')}</MenuItem>}
|
||||||
{canUnpin && <MenuItem icon="unpin" onClick={onUnpin}>{lang('DialogUnpin')}</MenuItem>}
|
{canUnpin && <MenuItem icon="unpin" onClick={onUnpin}>{lang('DialogUnpin')}</MenuItem>}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export type MenuItemProps = {
|
|||||||
destructive?: boolean;
|
destructive?: boolean;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
withWrap?: boolean;
|
withWrap?: boolean;
|
||||||
|
withPreventDefaultOnMouseDown?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuItem: FC<MenuItemProps> = (props) => {
|
const MenuItem: FC<MenuItemProps> = (props) => {
|
||||||
@ -45,6 +46,7 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
|||||||
withWrap,
|
withWrap,
|
||||||
onContextMenu,
|
onContextMenu,
|
||||||
clickArg,
|
clickArg,
|
||||||
|
withPreventDefaultOnMouseDown,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -74,6 +76,11 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
|||||||
|
|
||||||
onClick(e, clickArg);
|
onClick(e, clickArg);
|
||||||
});
|
});
|
||||||
|
const handleMouseDown = useLastCallback((e: React.SyntheticEvent<HTMLDivElement | HTMLAnchorElement>) => {
|
||||||
|
if (withPreventDefaultOnMouseDown) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
'MenuItem',
|
'MenuItem',
|
||||||
@ -110,6 +117,7 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
dir={lang.isRtl ? 'rtl' : undefined}
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</a>
|
</a>
|
||||||
@ -123,6 +131,7 @@ const MenuItem: FC<MenuItemProps> = (props) => {
|
|||||||
className={fullClassName}
|
className={fullClassName}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
title={ariaLabel}
|
title={ariaLabel}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import {
|
|||||||
SUPPORTED_IMAGE_CONTENT_TYPES,
|
SUPPORTED_IMAGE_CONTENT_TYPES,
|
||||||
SUPPORTED_VIDEO_CONTENT_TYPES,
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboardFromPromise } from '../../../util/clipboard';
|
||||||
import { isDeepLink } from '../../../util/deepLinkParser';
|
import { isDeepLink } from '../../../util/deepLinkParser';
|
||||||
import { ensureProtocol } from '../../../util/ensureProtocol';
|
import { ensureProtocol } from '../../../util/ensureProtocol';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
@ -1962,35 +1962,25 @@ addActionHandler('copyMessageLink', async (global, actions, payload): Promise<vo
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const showErrorOccurredNotification = () => actions.showNotification({
|
||||||
if (!isChatChannel(chat) && !isChatSuperGroup(chat)) {
|
message: translate('ErrorOccurred'),
|
||||||
actions.showNotification({
|
tabId,
|
||||||
message: translate('lng_filters_link_private_error'),
|
|
||||||
tabId,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const link = await callApi('exportMessageLink', {
|
|
||||||
chat,
|
|
||||||
id: messageId,
|
|
||||||
shouldIncludeThread,
|
|
||||||
shouldIncludeGrouped,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!link) {
|
if (!isChatChannel(chat) && !isChatSuperGroup(chat)) {
|
||||||
actions.showNotification({
|
showErrorOccurredNotification();
|
||||||
message: translate('ErrorOccurred'),
|
|
||||||
tabId,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const showLinkCopiedNotification = () => actions.showNotification({
|
||||||
copyTextToClipboard(link);
|
|
||||||
actions.showNotification({
|
|
||||||
message: translate('LinkCopied'),
|
message: translate('LinkCopied'),
|
||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
|
const callApiExportMessageLinkPromise = callApi('exportMessageLink', {
|
||||||
|
chat, id: messageId, shouldIncludeThread, shouldIncludeGrouped,
|
||||||
|
});
|
||||||
|
await copyTextToClipboardFromPromise(
|
||||||
|
callApiExportMessageLinkPromise, showLinkCopiedNotification, showErrorOccurredNotification,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
function countSortedIds(ids: number[], from: number, to: number) {
|
function countSortedIds(ids: number[], from: number, to: number) {
|
||||||
|
|||||||
@ -59,6 +59,51 @@ export const copyImageToClipboard = (imageUrl?: string) => {
|
|||||||
imageEl.src = imageUrl;
|
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) {
|
async function copyBlobToClipboard(pngBlob: Blob | null) {
|
||||||
if (!pngBlob || !CLIPBOARD_ITEM_SUPPORTED) {
|
if (!pngBlob || !CLIPBOARD_ITEM_SUPPORTED) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user