Chat List: Allow mouse to open chats in new tabs (#2443)
This commit is contained in:
parent
ec1895b5d1
commit
6afb17978d
@ -13,6 +13,8 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
|
||||||
&.animate-opacity {
|
&.animate-opacity {
|
||||||
will-change: opacity;
|
will-change: opacity;
|
||||||
transition: opacity 0.2s ease-out;
|
transition: opacity 0.2s ease-out;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
|
||||||
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import type {
|
import type {
|
||||||
ApiChat,
|
ApiChat,
|
||||||
@ -17,6 +17,7 @@ import type { AnimationLevel } from '../../../types';
|
|||||||
import type { ChatAnimationTypes } from './hooks';
|
import type { ChatAnimationTypes } from './hooks';
|
||||||
|
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
import { IS_MULTITAB_SUPPORTED } from '../../../util/environment';
|
||||||
import {
|
import {
|
||||||
isUserId,
|
isUserId,
|
||||||
getPrivateChatUserId,
|
getPrivateChatUserId,
|
||||||
@ -37,6 +38,7 @@ import {
|
|||||||
selectThreadParam, selectTabState,
|
selectThreadParam, selectTabState,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { createLocationHash } from '../../../util/routing';
|
||||||
|
|
||||||
import useChatContextActions from '../../../hooks/useChatContextActions';
|
import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
@ -225,6 +227,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
<ListItem
|
<ListItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={className}
|
className={className}
|
||||||
|
href={IS_MULTITAB_SUPPORTED ? `#${createLocationHash(chatId, 'thread', MAIN_THREAD_ID)}` : undefined}
|
||||||
style={`top: ${offsetTop}px`}
|
style={`top: ${offsetTop}px`}
|
||||||
ripple={!isForum && !isMobile}
|
ripple={!isForum && !isMobile}
|
||||||
contextActions={contextActions}
|
contextActions={contextActions}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
|||||||
import type { ChatAnimationTypes } from './hooks';
|
import type { ChatAnimationTypes } from './hooks';
|
||||||
import type { AnimationLevel } from '../../../types';
|
import type { AnimationLevel } from '../../../types';
|
||||||
|
|
||||||
|
import { IS_MULTITAB_SUPPORTED } from '../../../util/environment';
|
||||||
import {
|
import {
|
||||||
selectCanDeleteTopic,
|
selectCanDeleteTopic,
|
||||||
selectChat,
|
selectChat,
|
||||||
@ -19,9 +20,11 @@ import {
|
|||||||
selectOutgoingStatus, selectThreadInfo, selectThreadParam, selectUser,
|
selectOutgoingStatus, selectThreadInfo, selectThreadParam, selectUser,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import useChatListEntry from './hooks/useChatListEntry';
|
import { createLocationHash } from '../../../util/routing';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import { getMessageAction } from '../../../global/helpers';
|
import { getMessageAction } from '../../../global/helpers';
|
||||||
|
|
||||||
|
import useChatListEntry from './hooks/useChatListEntry';
|
||||||
import useTopicContextActions from './hooks/useTopicContextActions';
|
import useTopicContextActions from './hooks/useTopicContextActions';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -143,6 +146,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
onClick={handleOpenTopic}
|
onClick={handleOpenTopic}
|
||||||
style={style}
|
style={style}
|
||||||
|
href={IS_MULTITAB_SUPPORTED ? `#${createLocationHash(chatId, 'thread', topic.id)}` : undefined}
|
||||||
contextActions={contextActions}
|
contextActions={contextActions}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
|
import { useMemo } from '../../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiChat, ApiTopic } from '../../../../api/types';
|
import type { ApiChat, ApiTopic } from '../../../../api/types';
|
||||||
|
import type { MenuItemContextAction } from '../../../ui/ListItem';
|
||||||
|
|
||||||
import { compact } from '../../../../util/iteratees';
|
import { compact } from '../../../../util/iteratees';
|
||||||
import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers';
|
import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers';
|
||||||
|
import { IS_MULTITAB_SUPPORTED } from '../../../../util/environment';
|
||||||
|
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLang from '../../../../hooks/useLang';
|
||||||
import { useMemo } from '../../../../lib/teact/teact';
|
|
||||||
|
|
||||||
export default function useTopicContextActions(
|
export default function useTopicContextActions(
|
||||||
topic: ApiTopic,
|
topic: ApiTopic,
|
||||||
@ -29,11 +31,22 @@ export default function useTopicContextActions(
|
|||||||
toggleTopicPinned,
|
toggleTopicPinned,
|
||||||
markTopicRead,
|
markTopicRead,
|
||||||
updateTopicMutedState,
|
updateTopicMutedState,
|
||||||
|
openChatInNewTab,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const canToggleClosed = getCanManageTopic(chat, topic);
|
const canToggleClosed = getCanManageTopic(chat, topic);
|
||||||
const canTogglePinned = chat.isCreator || getHasAdminRight(chat, 'manageTopics');
|
const canTogglePinned = chat.isCreator || getHasAdminRight(chat, 'manageTopics');
|
||||||
|
|
||||||
|
const actionOpenInNewTab = IS_MULTITAB_SUPPORTED && {
|
||||||
|
title: 'Open in new tab',
|
||||||
|
icon: 'open-in-new-tab',
|
||||||
|
handler: () => {
|
||||||
|
openChatInNewTab({ chatId: chat.id, threadId: topicId });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const newTabActionSeparator = actionOpenInNewTab && { isSeparator: true, key: 'newTabSeparator' };
|
||||||
|
|
||||||
const actionUnreadMark = topic.unreadCount || !wasOpened
|
const actionUnreadMark = topic.unreadCount || !wasOpened
|
||||||
? {
|
? {
|
||||||
title: lang('MarkAsRead'),
|
title: lang('MarkAsRead'),
|
||||||
@ -88,11 +101,13 @@ export default function useTopicContextActions(
|
|||||||
} : undefined;
|
} : undefined;
|
||||||
|
|
||||||
return compact([
|
return compact([
|
||||||
|
actionOpenInNewTab,
|
||||||
|
newTabActionSeparator,
|
||||||
actionPin,
|
actionPin,
|
||||||
actionUnreadMark,
|
actionUnreadMark,
|
||||||
actionMute,
|
actionMute,
|
||||||
actionCloseTopic,
|
actionCloseTopic,
|
||||||
actionDelete,
|
actionDelete,
|
||||||
]);
|
]) as MenuItemContextAction[];
|
||||||
}, [topic, chat, wasOpened, lang, canDelete, handleDelete]);
|
}, [topic, chat, wasOpened, lang, canDelete, handleDelete]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,8 @@
|
|||||||
border-radius: var(--border-radius-default);
|
border-radius: var(--border-radius-default);
|
||||||
--ripple-color: rgba(0, 0, 0, 0.08);
|
--ripple-color: rgba(0, 0, 0, 0.08);
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
> i {
|
> i {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin-right: 2rem;
|
margin-right: 2rem;
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import type { RefObject } from 'react';
|
|||||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||||
import React, { useRef, useCallback } from '../../lib/teact/teact';
|
import React, { useRef, useCallback } from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../util/environment';
|
import { IS_TOUCH_ENV, MouseButton } from '../../util/environment';
|
||||||
import { fastRaf } from '../../util/schedulers';
|
import { fastRaf } from '../../util/schedulers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
|
|
||||||
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import useContextMenuPosition from '../../hooks/useContextMenuPosition';
|
import useContextMenuPosition from '../../hooks/useContextMenuPosition';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
@ -13,20 +14,28 @@ import useLang from '../../hooks/useLang';
|
|||||||
import RippleEffect from './RippleEffect';
|
import RippleEffect from './RippleEffect';
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
import MenuItem from './MenuItem';
|
import MenuItem from './MenuItem';
|
||||||
|
import MenuSeparator from './MenuSeparator';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
import './ListItem.scss';
|
import './ListItem.scss';
|
||||||
|
|
||||||
interface MenuItemContextAction {
|
type MenuItemContextActionItem = {
|
||||||
title: string;
|
title: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
destructive?: boolean;
|
destructive?: boolean;
|
||||||
handler?: () => void;
|
handler?: () => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
type MenuItemContextActionSeparator = {
|
||||||
|
isSeparator: true;
|
||||||
|
key?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MenuItemContextAction = MenuItemContextActionItem | MenuItemContextActionSeparator;
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
ref?: RefObject<HTMLDivElement>;
|
ref?: RefObject<HTMLDivElement>;
|
||||||
buttonRef?: RefObject<HTMLDivElement>;
|
buttonRef?: RefObject<HTMLDivElement | HTMLAnchorElement>;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
leftElement?: TeactNode;
|
leftElement?: TeactNode;
|
||||||
secondaryIcon?: string;
|
secondaryIcon?: string;
|
||||||
@ -47,13 +56,13 @@ interface OwnProps {
|
|||||||
contextActions?: MenuItemContextAction[];
|
contextActions?: MenuItemContextAction[];
|
||||||
offsetCollapseDelta?: number;
|
offsetCollapseDelta?: number;
|
||||||
withPortalForMenu?: boolean;
|
withPortalForMenu?: boolean;
|
||||||
|
href?: string;
|
||||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
onClick?: (e: React.MouseEvent<HTMLDivElement>, arg?: any) => void;
|
onClick?: (e: React.MouseEvent<HTMLElement>, arg?: any) => void;
|
||||||
clickArg?: any;
|
clickArg?: any;
|
||||||
onSecondaryIconClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
onSecondaryIconClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
onDragEnter?: (e: React.DragEvent<HTMLDivElement>) => void;
|
onDragEnter?: (e: React.DragEvent<HTMLDivElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListItem: FC<OwnProps> = ({
|
const ListItem: FC<OwnProps> = ({
|
||||||
ref,
|
ref,
|
||||||
buttonRef,
|
buttonRef,
|
||||||
@ -77,6 +86,7 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
contextActions,
|
contextActions,
|
||||||
withPortalForMenu,
|
withPortalForMenu,
|
||||||
offsetCollapseDelta,
|
offsetCollapseDelta,
|
||||||
|
href,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
onClick,
|
onClick,
|
||||||
clickArg,
|
clickArg,
|
||||||
@ -124,17 +134,35 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
getLayout,
|
getLayout,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleClickEvent = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
|
const hasModifierKey = e.ctrlKey || e.metaKey || e.shiftKey;
|
||||||
|
if (!hasModifierKey && e.button === MouseButton.Main) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClick = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
if ((disabled && !allowDisabledClick) || !onClick) {
|
if ((disabled && !allowDisabledClick) || !onClick) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (href) {
|
||||||
|
// Allow default behavior for opening links in new tab
|
||||||
|
const hasModifierKey = e.ctrlKey || e.metaKey || e.shiftKey;
|
||||||
|
if ((hasModifierKey && e.button === MouseButton.Main) || e.button === MouseButton.Auxiliary) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
onClick(e, clickArg);
|
onClick(e, clickArg);
|
||||||
|
|
||||||
if (IS_TOUCH_ENV && !ripple) {
|
if (IS_TOUCH_ENV && !ripple) {
|
||||||
markIsTouched();
|
markIsTouched();
|
||||||
fastRaf(unmarkIsTouched);
|
fastRaf(unmarkIsTouched);
|
||||||
}
|
}
|
||||||
}, [allowDisabledClick, clickArg, disabled, markIsTouched, onClick, ripple, unmarkIsTouched]);
|
}, [allowDisabledClick, clickArg, disabled, markIsTouched, onClick, ripple, unmarkIsTouched, href]);
|
||||||
|
|
||||||
const handleSecondaryIconClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleSecondaryIconClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
if ((disabled && !allowDisabledClick) || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return;
|
if ((disabled && !allowDisabledClick) || e.button !== 0 || (!onSecondaryIconClick && !contextActions)) return;
|
||||||
@ -146,14 +174,14 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
const handleMouseDown = useCallback((e: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||||
if (inactive || IS_TOUCH_ENV) {
|
if (inactive || IS_TOUCH_ENV) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (contextActions && (e.button === 2 || !onClick)) {
|
if (contextActions && (e.button === MouseButton.Secondary || !onClick)) {
|
||||||
handleBeforeContextMenu(e);
|
handleBeforeContextMenu(e);
|
||||||
}
|
}
|
||||||
if (e.button === 0) {
|
if (e.button === MouseButton.Main) {
|
||||||
if (!onClick) {
|
if (!onClick) {
|
||||||
handleContextMenu(e);
|
handleContextMenu(e);
|
||||||
} else {
|
} else {
|
||||||
@ -180,6 +208,8 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
isStatic && 'is-static',
|
isStatic && 'is-static',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ButtonElementTag = href ? 'a' : 'div';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
@ -190,12 +220,13 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
onMouseDown={onMouseDown}
|
onMouseDown={onMouseDown}
|
||||||
onDragEnter={onDragEnter}
|
onDragEnter={onDragEnter}
|
||||||
>
|
>
|
||||||
<div
|
<ButtonElementTag
|
||||||
className={buildClassName('ListItem-button', isTouched && 'active', buttonClassName)}
|
className={buildClassName('ListItem-button', isTouched && 'active', buttonClassName)}
|
||||||
role={!isStatic ? 'button' : undefined}
|
role={!isStatic ? 'button' : undefined}
|
||||||
ref={buttonRef}
|
href={href}
|
||||||
|
ref={buttonRef as any /* TS requires specific types for refs */}
|
||||||
tabIndex={!isStatic ? 0 : undefined}
|
tabIndex={!isStatic ? 0 : undefined}
|
||||||
onClick={(!inactive && IS_TOUCH_ENV) ? handleClick : undefined}
|
onClick={(!inactive && IS_TOUCH_ENV) ? handleClick : handleClickEvent}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onContextMenu={(!inactive && contextActions) ? handleContextMenu : undefined}
|
onContextMenu={(!inactive && contextActions) ? handleContextMenu : undefined}
|
||||||
>
|
>
|
||||||
@ -221,7 +252,7 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{rightElement}
|
{rightElement}
|
||||||
</div>
|
</ButtonElementTag>
|
||||||
{contextActions && contextMenuPosition !== undefined && (
|
{contextActions && contextMenuPosition !== undefined && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isContextMenuOpen}
|
isOpen={isContextMenuOpen}
|
||||||
@ -237,15 +268,19 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
withPortal={withPortalForMenu}
|
withPortal={withPortalForMenu}
|
||||||
>
|
>
|
||||||
{contextActions.map((action) => (
|
{contextActions.map((action) => (
|
||||||
<MenuItem
|
('isSeparator' in action) ? (
|
||||||
key={action.title}
|
<MenuSeparator key={action.key || 'separator'} />
|
||||||
icon={action.icon}
|
) : (
|
||||||
destructive={action.destructive}
|
<MenuItem
|
||||||
disabled={!action.handler}
|
key={action.title}
|
||||||
onClick={action.handler}
|
icon={action.icon}
|
||||||
>
|
destructive={action.destructive}
|
||||||
{action.title}
|
disabled={!action.handler}
|
||||||
</MenuItem>
|
onClick={action.handler}
|
||||||
|
>
|
||||||
|
{action.title}
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -72,9 +72,9 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openChatInNewTab', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openChatInNewTab', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId } = payload;
|
const { chatId, threadId = MAIN_THREAD_ID } = payload;
|
||||||
|
|
||||||
window.open(createMessageHashUrl(chatId, 'thread', MAIN_THREAD_ID), '_blank');
|
window.open(createMessageHashUrl(chatId, 'thread', threadId), '_blank');
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('openPreviousChat', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openPreviousChat', (global, actions, payload): ActionReturnType => {
|
||||||
|
|||||||
@ -1489,6 +1489,7 @@ export interface ActionPayloads {
|
|||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
openChatInNewTab: {
|
openChatInNewTab: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
|
threadId?: number;
|
||||||
};
|
};
|
||||||
onTabFocusChange: {
|
onTabFocusChange: {
|
||||||
isBlurred: boolean;
|
isBlurred: boolean;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useMemo } from '../lib/teact/teact';
|
|||||||
import { getActions } from '../global';
|
import { getActions } from '../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiUser } from '../api/types';
|
import type { ApiChat, ApiUser } from '../api/types';
|
||||||
|
import type { MenuItemContextAction } from '../components/ui/ListItem';
|
||||||
|
|
||||||
import { IS_MULTITAB_SUPPORTED } from '../util/environment';
|
import { IS_MULTITAB_SUPPORTED } from '../util/environment';
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../config';
|
import { SERVICE_NOTIFICATIONS_USER_ID } from '../config';
|
||||||
@ -58,6 +59,8 @@ const useChatContextActions = ({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const newTabActionSeparator = actionOpenInNewTab && { isSeparator: true, key: 'newTabSeparator' };
|
||||||
|
|
||||||
const actionAddToFolder = canChangeFolder ? {
|
const actionAddToFolder = canChangeFolder ? {
|
||||||
title: lang('ChatList.Filter.AddToFolder'),
|
title: lang('ChatList.Filter.AddToFolder'),
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
@ -119,6 +122,7 @@ const useChatContextActions = ({
|
|||||||
|
|
||||||
return compact([
|
return compact([
|
||||||
actionOpenInNewTab,
|
actionOpenInNewTab,
|
||||||
|
newTabActionSeparator,
|
||||||
actionAddToFolder,
|
actionAddToFolder,
|
||||||
actionMaskAsRead,
|
actionMaskAsRead,
|
||||||
actionMarkAsUnread,
|
actionMarkAsUnread,
|
||||||
@ -127,7 +131,7 @@ const useChatContextActions = ({
|
|||||||
!isSelf && !isServiceNotifications && !isInFolder && actionArchive,
|
!isSelf && !isServiceNotifications && !isInFolder && actionArchive,
|
||||||
actionReport,
|
actionReport,
|
||||||
actionDelete,
|
actionDelete,
|
||||||
]);
|
]) as MenuItemContextAction[];
|
||||||
}, [
|
}, [
|
||||||
chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted,
|
chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted,
|
||||||
handleDelete, handleReport, folderId, isSelf, isServiceNotifications,
|
handleDelete, handleReport, folderId, isSelf, isServiceNotifications,
|
||||||
|
|||||||
@ -42,6 +42,14 @@ export const IS_ANDROID = PLATFORM_ENV === 'Android';
|
|||||||
export const IS_SAFARI = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
export const IS_SAFARI = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||||
export const IS_YA_BROWSER = navigator.userAgent.includes('YaBrowser');
|
export const IS_YA_BROWSER = navigator.userAgent.includes('YaBrowser');
|
||||||
|
|
||||||
|
export enum MouseButton {
|
||||||
|
Main = 0,
|
||||||
|
Auxiliary = 1,
|
||||||
|
Secondary = 2,
|
||||||
|
Fourth = 3,
|
||||||
|
Fifth = 4,
|
||||||
|
}
|
||||||
|
|
||||||
export const IS_PWA = (
|
export const IS_PWA = (
|
||||||
window.matchMedia('(display-mode: standalone)').matches
|
window.matchMedia('(display-mode: standalone)').matches
|
||||||
|| (window.navigator as any).standalone
|
|| (window.navigator as any).standalone
|
||||||
|
|||||||
@ -7,7 +7,7 @@ let parsedInitialLocationHash: Record<string, string> | undefined;
|
|||||||
let messageHash: string | undefined;
|
let messageHash: string | undefined;
|
||||||
let isAlreadyParsed = false;
|
let isAlreadyParsed = false;
|
||||||
|
|
||||||
export const createLocationHash = (chatId: string, type: string, threadId: number): string => {
|
export const createLocationHash = (chatId: string, type: MessageListType, threadId: number): string => {
|
||||||
const displayType = type === 'thread' ? undefined : type;
|
const displayType = type === 'thread' ? undefined : type;
|
||||||
const parts = threadId === MAIN_THREAD_ID ? [chatId, displayType] : [chatId, threadId, displayType];
|
const parts = threadId === MAIN_THREAD_ID ? [chatId, displayType] : [chatId, threadId, displayType];
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ export function parseLocationHash() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createMessageHashUrl = (chatId: string, type: string, threadId: number): string => {
|
export const createMessageHashUrl = (chatId: string, type: MessageListType, threadId: number): string => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.hash = createLocationHash(chatId, type, threadId);
|
url.hash = createLocationHash(chatId, type, threadId);
|
||||||
return url.href;
|
return url.href;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user