Chat List: Introduce Archive item (#2566)

This commit is contained in:
Alexander Zinchuk 2023-02-28 18:43:21 +01:00
parent 3cc27156cb
commit 5055f70f77
28 changed files with 859 additions and 254 deletions

Binary file not shown.

Binary file not shown.

View File

@ -235,7 +235,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
<ListItem <ListItem
key={id} key={id}
className="chat-item-clickable force-rounded-corners small-icon" className="chat-item-clickable force-rounded-corners small-icon"
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`} style={`height: ${CHAT_HEIGHT_PX}px; top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
onClick={handleClick} onClick={handleClick}
clickArg={id} clickArg={id}
> >

View File

@ -28,4 +28,8 @@
transition: none; transition: none;
} }
} }
.archived-chats-more-menu {
margin-left: auto !important;
}
} }

View File

@ -1,6 +1,10 @@
import React, { memo } from '../../lib/teact/teact'; import React, { memo, useCallback } from '../../lib/teact/teact';
import { getActions } from '../../global';
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import type { LeftColumnContent, SettingsScreens } from '../../types';
import type { FolderEditDispatch } from '../../hooks/reducers/useFoldersReducer';
import type { GlobalState } from '../../global/types';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -12,22 +16,33 @@ import useForumPanelRender from '../../hooks/useForumPanelRender';
import Button from '../ui/Button'; import Button from '../ui/Button';
import ChatList from './main/ChatList'; import ChatList from './main/ChatList';
import ForumPanel from './main/ForumPanel'; import ForumPanel from './main/ForumPanel';
import DropdownMenu from '../ui/DropdownMenu';
import MenuItem from '../ui/MenuItem';
import './ArchivedChats.scss'; import './ArchivedChats.scss';
export type OwnProps = { export type OwnProps = {
isActive: boolean; isActive: boolean;
isForumPanelOpen?: boolean;
archiveSettings: GlobalState['archiveSettings'];
onReset: () => void; onReset: () => void;
onTopicSearch: NoneToVoidFunction; onTopicSearch: NoneToVoidFunction;
isForumPanelOpen?: boolean; onSettingsScreenSelect: (screen: SettingsScreens) => void;
foldersDispatch: FolderEditDispatch;
onLeftColumnContentChange: (content: LeftColumnContent) => void;
}; };
const ArchivedChats: FC<OwnProps> = ({ const ArchivedChats: FC<OwnProps> = ({
isActive, isActive,
isForumPanelOpen, isForumPanelOpen,
archiveSettings,
onReset, onReset,
onTopicSearch, onTopicSearch,
onSettingsScreenSelect,
onLeftColumnContentChange,
foldersDispatch,
}) => { }) => {
const { updateArchiveSettings } = getActions();
const lang = useLang(); const lang = useLang();
useHistoryBack({ useHistoryBack({
@ -35,6 +50,10 @@ const ArchivedChats: FC<OwnProps> = ({
onBack: onReset, onBack: onReset,
}); });
const handleDisplayArchiveInChats = useCallback(() => {
updateArchiveSettings({ isHidden: false });
}, [updateArchiveSettings]);
const { const {
shouldDisableDropdownMenuTransitionRef, shouldDisableDropdownMenuTransitionRef,
handleDropdownMenuTransitionEnd, handleDropdownMenuTransitionEnd,
@ -67,8 +86,27 @@ const ArchivedChats: FC<OwnProps> = ({
<i className="icon-arrow-left" /> <i className="icon-arrow-left" />
</Button> </Button>
{shouldRenderTitle && <h3 className={titleClassNames}>{lang('ArchivedChats')}</h3>} {shouldRenderTitle && <h3 className={titleClassNames}>{lang('ArchivedChats')}</h3>}
{archiveSettings.isHidden && (
<DropdownMenu
className="archived-chats-more-menu"
positionX="right"
onTransitionEnd={lang.isRtl ? handleDropdownMenuTransitionEnd : undefined}
>
<MenuItem icon="archive-from-main" onClick={handleDisplayArchiveInChats}>
{lang('lng_context_archive_to_list')}
</MenuItem>
</DropdownMenu>
)}
</div> </div>
<ChatList folderType="archived" isActive={isActive} isForumPanelOpen={isForumPanelOpen} /> <ChatList
folderType="archived"
isActive={isActive}
isForumPanelOpen={isForumPanelOpen}
onSettingsScreenSelect={onSettingsScreenSelect}
onLeftColumnContentChange={onLeftColumnContentChange}
foldersDispatch={foldersDispatch}
archiveSettings={archiveSettings}
/>
{shouldRenderForumPanel && ( {shouldRenderForumPanel && (
<ForumPanel <ForumPanel
isOpen={isForumPanelOpen} isOpen={isForumPanelOpen}

View File

@ -1,9 +1,10 @@
import type { FC } from '../../lib/teact/teact';
import React, { import React, {
memo, useCallback, useEffect, useRef, useState, memo, useCallback, useEffect, useRef, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { GlobalState } from '../../global/types';
import { LeftColumnContent, SettingsScreens } from '../../types'; import { LeftColumnContent, SettingsScreens } from '../../types';
import { IS_MAC_OS, IS_PWA, LAYERS_ANIMATION_NAME } from '../../util/environment'; import { IS_MAC_OS, IS_PWA, LAYERS_ANIMATION_NAME } from '../../util/environment';
@ -36,6 +37,7 @@ type StateProps = {
isForumPanelOpen?: boolean; isForumPanelOpen?: boolean;
forumPanelChatId?: string; forumPanelChatId?: string;
isClosingSearch?: boolean; isClosingSearch?: boolean;
archiveSettings: GlobalState['archiveSettings'];
}; };
enum ContentType { enum ContentType {
@ -66,6 +68,7 @@ const LeftColumn: FC<StateProps> = ({
isForumPanelOpen, isForumPanelOpen,
forumPanelChatId, forumPanelChatId,
isClosingSearch, isClosingSearch,
archiveSettings,
}) => { }) => {
const { const {
setGlobalSearchQuery, setGlobalSearchQuery,
@ -352,6 +355,11 @@ const LeftColumn: FC<StateProps> = ({
openChat({ id: currentUserId, shouldReplaceHistory: true }); openChat({ id: currentUserId, shouldReplaceHistory: true });
}, [currentUserId, openChat]); }, [currentUserId, openChat]);
const handleArchivedChats = useCallback((e: KeyboardEvent) => {
e.preventDefault();
setContent(LeftColumnContent.Archived);
}, []);
const handleHotkeySettings = useCallback((e: KeyboardEvent) => { const handleHotkeySettings = useCallback((e: KeyboardEvent) => {
e.preventDefault(); e.preventDefault();
setContent(LeftColumnContent.Settings); setContent(LeftColumnContent.Settings);
@ -360,7 +368,10 @@ const LeftColumn: FC<StateProps> = ({
useHotkeys({ useHotkeys({
'Mod+Shift+F': handleHotkeySearch, 'Mod+Shift+F': handleHotkeySearch,
'Mod+Shift+S': handleHotkeySavedMessages, 'Mod+Shift+S': handleHotkeySavedMessages,
'Mod+0': handleHotkeySavedMessages, ...(IS_PWA && {
'Mod+0': handleHotkeySavedMessages,
'Mod+9': handleArchivedChats,
}),
...(IS_MAC_OS && IS_PWA && { 'Mod+,': handleHotkeySettings }), ...(IS_MAC_OS && IS_PWA && { 'Mod+,': handleHotkeySettings }),
}); });
@ -411,7 +422,11 @@ const LeftColumn: FC<StateProps> = ({
isActive={isActive} isActive={isActive}
onReset={handleReset} onReset={handleReset}
onTopicSearch={handleTopicSearch} onTopicSearch={handleTopicSearch}
foldersDispatch={foldersDispatch}
onSettingsScreenSelect={handleSettingsScreenSelect}
onLeftColumnContentChange={setContent}
isForumPanelOpen={isForumPanelOpen} isForumPanelOpen={isForumPanelOpen}
archiveSettings={archiveSettings}
/> />
); );
case ContentType.Settings: case ContentType.Settings:
@ -458,7 +473,7 @@ const LeftColumn: FC<StateProps> = ({
foldersDispatch={foldersDispatch} foldersDispatch={foldersDispatch}
onContentChange={setContent} onContentChange={setContent}
onSearchQuery={handleSearchQuery} onSearchQuery={handleSearchQuery}
onScreenSelect={handleSettingsScreenSelect} onSettingsScreenSelect={handleSettingsScreenSelect}
onReset={handleReset} onReset={handleReset}
shouldSkipTransition={shouldSkipHistoryAnimations} shouldSkipTransition={shouldSkipHistoryAnimations}
isUpdateAvailable={isUpdateAvailable} isUpdateAvailable={isUpdateAvailable}
@ -498,6 +513,7 @@ export default memo(withGlobal(
hasPasscode, hasPasscode,
}, },
isUpdateAvailable, isUpdateAvailable,
archiveSettings,
} = global; } = global;
const currentChat = selectCurrentChat(global); const currentChat = selectCurrentChat(global);
@ -519,6 +535,7 @@ export default memo(withGlobal(
isForumPanelOpen, isForumPanelOpen,
forumPanelChatId, forumPanelChatId,
isClosingSearch: tabState.globalSearch.isClosing, isClosingSearch: tabState.globalSearch.isClosing,
archiveSettings,
}; };
}, },
)(LeftColumn)); )(LeftColumn));

View File

@ -0,0 +1,90 @@
.root {
--background-color: var(--color-background);
}
.minimized {
background-color: var(--color-background-secondary);
margin: -0.5rem -0.5rem 0 -0.5rem !important;
&:hover {
opacity: 0.85;
}
.button {
border-radius: 0;
padding: 0.375rem !important;
background-color: transparent;
}
:global(body.is-ios) &,
:global(body.is-android) & {
.button {
padding-bottom: 0.3125rem !important;
}
}
.title {
justify-content: center !important;
color: var(--color-text-secondary);
}
.unread-count {
position: absolute;
right: 0.75rem;
background-color: transparent;
color: var(--color-text-secondary);
font-size: 0.8125rem;
}
.info {
transform: none !important;
}
.name {
line-height: 1.25rem !important;
font-size: 0.8125rem !important;
}
&::after {
opacity: 0;
}
}
.info {
transition: opacity 0.3s ease, transform var(--layer-transition);
}
.icon {
margin-inline-end: 0.5rem;
}
.name {
display: flex;
align-items: center;
}
.avatarWrapper {
flex-shrink: 0;
background-color: var(--background-color);
z-index: 1;
}
.avatar {
font-size: 1.625rem;
background: linear-gradient(var(--color-white) -125%, var(--color-archive)) !important;
}
.title {
flex-grow: 1;
}
.chatsPreview {
color: var(--color-text-secondary);
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
}
.unread {
color: var(--color-text);
}

View File

@ -0,0 +1,166 @@
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact';
import type { GlobalState } from '../../../global/types';
import { ARCHIVED_FOLDER_ID } from '../../../config';
import buildClassName from '../../../util/buildClassName';
import { compact } from '../../../util/iteratees';
import { formatIntegerCompact } from '../../../util/textFormat';
import renderText from '../../common/helpers/renderText';
import useLang from '../../../hooks/useLang';
import { useFolderManagerForOrderedIds, useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
import ListItem from '../../ui/ListItem';
import AnimatedCounter from '../../common/AnimatedCounter';
import styles from './Archive.module.scss';
type OwnProps = {
archiveSettings: GlobalState['archiveSettings'];
onDragEnter?: NoneToVoidFunction;
onClick?: NoneToVoidFunction;
};
const PREVIEW_SLICE = 5;
const Archive: FC<OwnProps> = ({
archiveSettings,
onDragEnter,
onClick,
}) => {
const { updateArchiveSettings } = getActions();
const lang = useLang();
const orderedChatIds = useFolderManagerForOrderedIds(ARCHIVED_FOLDER_ID);
const unreadCounters = useFolderManagerForUnreadCounters();
const archiveUnreadCount = unreadCounters[ARCHIVED_FOLDER_ID]?.chatsCount;
const previewItems = useMemo(() => {
if (!orderedChatIds?.length) return lang('Loading');
const chatsById = getGlobal().chats.byId;
return orderedChatIds.slice(0, PREVIEW_SLICE).map((chatId, i, arr) => {
const isLast = i === arr.length - 1;
const chat = chatsById[chatId];
if (!chat) {
return undefined;
}
return (
<>
<span className={buildClassName(styles.chat, archiveUnreadCount && chat.unreadCount && styles.unread)}>
{renderText(chat.title)}
</span>
{isLast ? '' : ', '}
</>
);
});
}, [orderedChatIds, lang, archiveUnreadCount]);
const contextActions = useMemo(() => {
const actionMinimize = !archiveSettings.isMinimized && {
title: lang('lng_context_archive_collapse'),
icon: 'collapse',
handler: () => {
updateArchiveSettings({ isMinimized: true });
},
};
const actionExpand = archiveSettings.isMinimized && {
title: lang('lng_context_archive_expand'),
icon: 'expand',
handler: () => {
updateArchiveSettings({ isMinimized: false });
},
};
const actionHide = {
title: lang('lng_context_archive_to_menu'),
icon: 'archive-to-main',
handler: () => {
updateArchiveSettings({ isHidden: true });
},
};
return compact([actionMinimize, actionExpand, actionHide]);
}, [archiveSettings.isMinimized, lang, updateArchiveSettings]);
const handleDragEnter = useCallback((e) => {
e.preventDefault();
onDragEnter?.();
}, [onDragEnter]);
function renderCollapsed() {
return (
<div className={buildClassName(styles.info, 'info')}>
<div className="info-row">
<div className={buildClassName('title', styles.title)}>
<h3 dir="auto" className={buildClassName(styles.name, 'fullName')}>
<i className={buildClassName(styles.icon, 'icon-archive-filled')} />
{lang('ArchivedChats')}
</h3>
</div>
{Boolean(archiveUnreadCount) && (
<div className={styles.unreadCount}>
<AnimatedCounter text={formatIntegerCompact(archiveUnreadCount)} />
</div>
)}
</div>
</div>
);
}
function renderRegular() {
return (
<>
<div className={buildClassName('status', styles.avatarWrapper)}>
<div className={buildClassName('Avatar', styles.avatar)}>
<i className="icon-archive-filled" />
</div>
</div>
<div className={buildClassName(styles.info, 'info')}>
<div className="info-row">
<div className={buildClassName('title', styles.title)}>
<h3 dir="auto" className={buildClassName(styles.name, 'fullName')}>{lang('ArchivedChats')}</h3>
</div>
</div>
<div className="subtitle">
<div className={buildClassName('status', styles.chatsPreview)}>
{previewItems}
</div>
{Boolean(archiveUnreadCount) && (
<div className="Badge">
<AnimatedCounter text={formatIntegerCompact(archiveUnreadCount)} />
</div>
)}
</div>
</div>
</>
);
}
return (
<ListItem
onClick={onClick}
onDragEnter={handleDragEnter}
className={buildClassName(
styles.root,
archiveSettings.isMinimized && styles.minimized,
'chat-item-clickable',
'chat-item-archive',
)}
buttonClassName={styles.button}
offsetCollapseDelta={0}
contextActions={contextActions}
withPortalForMenu
>
{archiveSettings.isMinimized ? renderCollapsed() : renderRegular()}
</ListItem>
);
};
export default memo(Archive);

View File

@ -5,8 +5,9 @@ import React, {
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiChatFolder } from '../../../api/types'; import type { ApiChatFolder } from '../../../api/types';
import type { SettingsScreens } from '../../../types'; import type { LeftColumnContent, SettingsScreens } from '../../../types';
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
import type { GlobalState } from '../../../global/types';
import { ALL_FOLDER_ID } from '../../../config'; import { ALL_FOLDER_ID } from '../../../config';
import { IS_TOUCH_ENV } from '../../../util/environment'; import { IS_TOUCH_ENV } from '../../../util/environment';
@ -25,8 +26,9 @@ import TabList from '../../ui/TabList';
import ChatList from './ChatList'; import ChatList from './ChatList';
type OwnProps = { type OwnProps = {
onScreenSelect: (screen: SettingsScreens) => void; onSettingsScreenSelect: (screen: SettingsScreens) => void;
foldersDispatch: FolderEditDispatch; foldersDispatch: FolderEditDispatch;
onLeftColumnContentChange: (content: LeftColumnContent) => void;
shouldHideFolderTabs?: boolean; shouldHideFolderTabs?: boolean;
}; };
@ -39,6 +41,8 @@ type StateProps = {
lastSyncTime?: number; lastSyncTime?: number;
shouldSkipHistoryAnimations?: boolean; shouldSkipHistoryAnimations?: boolean;
maxFolders: number; maxFolders: number;
hasArchivedChats?: boolean;
archiveSettings: GlobalState['archiveSettings'];
}; };
const SAVED_MESSAGES_HOTKEY = '0'; const SAVED_MESSAGES_HOTKEY = '0';
@ -46,7 +50,8 @@ const FIRST_FOLDER_INDEX = 0;
const ChatFolders: FC<OwnProps & StateProps> = ({ const ChatFolders: FC<OwnProps & StateProps> = ({
foldersDispatch, foldersDispatch,
onScreenSelect, onSettingsScreenSelect,
onLeftColumnContentChange,
chatFoldersById, chatFoldersById,
orderedFolderIds, orderedFolderIds,
activeChatFolder, activeChatFolder,
@ -56,6 +61,8 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
maxFolders, maxFolders,
shouldHideFolderTabs, shouldHideFolderTabs,
hasArchivedChats,
archiveSettings,
}) => { }) => {
const { const {
loadChatFolders, loadChatFolders,
@ -122,7 +129,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
// Prevent `activeTab` pointing at non-existing folder after update // Prevent `activeTab` pointing at non-existing folder after update
useEffect(() => { useEffect(() => {
if (!folderTabs || !folderTabs.length) { if (!folderTabs?.length) {
return; return;
} }
@ -211,7 +218,10 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
isForumPanelOpen={isForumPanelOpen} isForumPanelOpen={isForumPanelOpen}
lastSyncTime={lastSyncTime} lastSyncTime={lastSyncTime}
foldersDispatch={foldersDispatch} foldersDispatch={foldersDispatch}
onScreenSelect={onScreenSelect} onSettingsScreenSelect={onSettingsScreenSelect}
onLeftColumnContentChange={onLeftColumnContentChange}
canDisplayArchive={hasArchivedChats && !archiveSettings.isHidden}
archiveSettings={archiveSettings}
/> />
); );
} }
@ -249,8 +259,14 @@ export default memo(withGlobal<OwnProps>(
byId: chatFoldersById, byId: chatFoldersById,
orderedIds: orderedFolderIds, orderedIds: orderedFolderIds,
}, },
chats: {
listIds: {
archived,
},
},
currentUserId, currentUserId,
lastSyncTime, lastSyncTime,
archiveSettings,
} = global; } = global;
const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global); const { shouldSkipHistoryAnimations, activeChatFolder } = selectTabState(global);
@ -262,7 +278,9 @@ export default memo(withGlobal<OwnProps>(
isForumPanelOpen: selectIsForumPanelOpen(global), isForumPanelOpen: selectIsForumPanelOpen(global),
lastSyncTime, lastSyncTime,
shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
hasArchivedChats: Boolean(archived?.length),
maxFolders: selectCurrentLimit(global, 'dialogFilters'), maxFolders: selectCurrentLimit(global, 'dialogFilters'),
archiveSettings,
}; };
}, },
)(ChatFolders)); )(ChatFolders));

View File

@ -4,12 +4,14 @@ import React, {
import { getActions, getGlobal } from '../../../global'; import { getActions, getGlobal } from '../../../global';
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import type { SettingsScreens } from '../../../types';
import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer'; import type { FolderEditDispatch } from '../../../hooks/reducers/useFoldersReducer';
import { LeftColumnContent } from '../../../types';
import type { SettingsScreens } from '../../../types';
import type { GlobalState } from '../../../global/types';
import { import {
ALL_FOLDER_ID, ALL_FOLDER_ID,
ARCHIVED_FOLDER_ID, CHAT_HEIGHT_FORUM_PX, ARCHIVED_FOLDER_ID, ARCHIVE_MINIMIZED_HEIGHT, CHAT_HEIGHT_FORUM_PX,
CHAT_HEIGHT_PX, CHAT_HEIGHT_PX,
CHAT_LIST_SLICE, CHAT_LIST_SLICE,
} from '../../../config'; } from '../../../config';
@ -24,35 +26,43 @@ import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver'
import { useHotkeys } from '../../../hooks/useHotkeys'; import { useHotkeys } from '../../../hooks/useHotkeys';
import useDebouncedCallback from '../../../hooks/useDebouncedCallback'; import useDebouncedCallback from '../../../hooks/useDebouncedCallback';
import useChatOrderDiff from './hooks/useChatOrderDiff'; import useChatOrderDiff from './hooks/useChatOrderDiff';
import useCollapseWithForumPanel from './hooks/useCollapseWithForumPanel';
import InfiniteScroll from '../../ui/InfiniteScroll'; import InfiniteScroll from '../../ui/InfiniteScroll';
import Loading from '../../ui/Loading'; import Loading from '../../ui/Loading';
import Chat from './Chat'; import Chat from './Chat';
import EmptyFolder from './EmptyFolder'; import EmptyFolder from './EmptyFolder';
import useCollapseWithForumPanel from './hooks/useCollapseWithForumPanel'; import Archive from './Archive';
type OwnProps = { type OwnProps = {
folderType: 'all' | 'archived' | 'folder'; folderType: 'all' | 'archived' | 'folder';
folderId?: number; folderId?: number;
isActive: boolean; isActive: boolean;
canDisplayArchive?: boolean;
archiveSettings: GlobalState['archiveSettings'];
isForumPanelOpen?: boolean; isForumPanelOpen?: boolean;
lastSyncTime?: number; lastSyncTime?: number;
foldersDispatch?: FolderEditDispatch; foldersDispatch: FolderEditDispatch;
onScreenSelect?: (screen: SettingsScreens) => void; onSettingsScreenSelect: (screen: SettingsScreens) => void;
onLeftColumnContentChange: (content: LeftColumnContent) => void;
}; };
const INTERSECTION_THROTTLE = 200; const INTERSECTION_THROTTLE = 200;
const DRAG_ENTER_DEBOUNCE = 500; const DRAG_ENTER_DEBOUNCE = 500;
const RESERVED_HOTKEYS = new Set(['9', '0']);
const ChatList: FC<OwnProps> = ({ const ChatList: FC<OwnProps> = ({
folderType, folderType,
folderId, folderId,
isActive, isActive,
isForumPanelOpen, isForumPanelOpen,
canDisplayArchive,
archiveSettings,
foldersDispatch, foldersDispatch,
onScreenSelect, onSettingsScreenSelect,
onLeftColumnContentChange,
}) => { }) => {
const { openChat, openNextChat } = getActions(); const { openChat, openNextChat, closeForumPanel } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const shouldIgnoreDragRef = useRef(false); const shouldIgnoreDragRef = useRef(false);
@ -61,8 +71,14 @@ const ChatList: FC<OwnProps> = ({
folderType === 'all' ? ALL_FOLDER_ID : folderType === 'archived' ? ARCHIVED_FOLDER_ID : folderId! folderType === 'all' ? ALL_FOLDER_ID : folderType === 'archived' ? ARCHIVED_FOLDER_ID : folderId!
); );
const shouldDisplayArchive = folderType === 'all' && canDisplayArchive;
const orderedIds = useFolderManagerForOrderedIds(resolvedFolderId); const orderedIds = useFolderManagerForOrderedIds(resolvedFolderId);
const chatsHeight = (orderedIds?.length || 0) * CHAT_HEIGHT_PX;
const archiveHeight = shouldDisplayArchive
? archiveSettings.isMinimized ? ARCHIVE_MINIMIZED_HEIGHT : CHAT_HEIGHT_PX : 0;
const { orderDiffById, getAnimationType } = useChatOrderDiff(orderedIds); const { orderDiffById, getAnimationType } = useChatOrderDiff(orderedIds);
const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE); const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE);
@ -88,10 +104,19 @@ const ChatList: FC<OwnProps> = ({
function handleKeyDown(e: KeyboardEvent) { function handleKeyDown(e: KeyboardEvent) {
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.code.startsWith('Digit')) { if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.code.startsWith('Digit')) {
const [, digit] = e.code.match(/Digit(\d)/) || []; const [, digit] = e.code.match(/Digit(\d)/) || [];
if (!digit) return; if (!digit || RESERVED_HOTKEYS.has(digit)) return;
const position = Number(digit) - 1; const isArchiveInList = shouldDisplayArchive && !archiveSettings.isMinimized;
if (position > orderedIds!.length - 1 || position < 0) return;
const shift = isArchiveInList ? -1 : 0;
const position = Number(digit) + shift - 1;
if (isArchiveInList && position === -1) {
onLeftColumnContentChange(LeftColumnContent.Archived);
return;
}
if (position > orderedIds!.length - 1) return;
openChat({ id: orderedIds![position], shouldReplaceHistory: true }); openChat({ id: orderedIds![position], shouldReplaceHistory: true });
} }
@ -102,7 +127,7 @@ const ChatList: FC<OwnProps> = ({
return () => { return () => {
document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', handleKeyDown);
}; };
}, [isActive, openChat, openNextChat, orderedIds]); }, [archiveSettings, isActive, onLeftColumnContentChange, openChat, openNextChat, orderedIds, shouldDisplayArchive]);
const { observe } = useIntersectionObserver({ const { observe } = useIntersectionObserver({
rootRef: containerRef, rootRef: containerRef,
@ -111,6 +136,19 @@ const ChatList: FC<OwnProps> = ({
useCollapseWithForumPanel(containerRef, isForumPanelOpen); useCollapseWithForumPanel(containerRef, isForumPanelOpen);
const handleArchivedClick = useCallback(() => {
onLeftColumnContentChange(LeftColumnContent.Archived);
closeForumPanel();
}, [closeForumPanel, onLeftColumnContentChange]);
const handleArchivedDragEnter = useCallback(() => {
if (shouldIgnoreDragRef.current) {
shouldIgnoreDragRef.current = false;
return;
}
handleArchivedClick();
}, [handleArchivedClick]);
const handleDragEnter = useDebouncedCallback((chatId: string) => { const handleDragEnter = useDebouncedCallback((chatId: string) => {
if (shouldIgnoreDragRef.current) { if (shouldIgnoreDragRef.current) {
shouldIgnoreDragRef.current = false; shouldIgnoreDragRef.current = false;
@ -131,13 +169,13 @@ const ChatList: FC<OwnProps> = ({
if (!viewportIds?.length) return 0; if (!viewportIds?.length) return 0;
const global = getGlobal(); const global = getGlobal();
const viewportOffset = orderedIds!.indexOf(viewportIds![0]); const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
return orderedIds!.reduce((acc, id, i) => { return archiveHeight + orderedIds!.reduce((acc, id, i) => {
if (i >= viewportOffset) { if (i >= viewportOffset) {
return acc; return acc;
} }
return acc + (selectChat(global, id)!.isForum ? CHAT_HEIGHT_FORUM_PX : CHAT_HEIGHT_PX); return acc + (selectChat(global, id)!.isForum ? CHAT_HEIGHT_FORUM_PX : CHAT_HEIGHT_PX);
}, 0); }, 0);
}, [orderedIds, viewportIds]); }, [archiveHeight, orderedIds, viewportIds]);
function renderChats() { function renderChats() {
const viewportOffset = orderedIds!.indexOf(viewportIds![0]); const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
@ -150,7 +188,7 @@ const ChatList: FC<OwnProps> = ({
return viewportIds!.map((id, i) => { return viewportIds!.map((id, i) => {
const isPinned = viewportOffset + i < pinnedCount; const isPinned = viewportOffset + i < pinnedCount;
const expendedOffsetTop = currentChatListHeight; const expendedOffsetTop = currentChatListHeight;
const collapsedOffsetTop = (viewportOffset + i) * CHAT_HEIGHT_PX; const collapsedOffsetTop = archiveHeight + (viewportOffset + i) * CHAT_HEIGHT_PX;
currentChatListHeight += (selectChat(global, id)?.isForum ? CHAT_HEIGHT_FORUM_PX : CHAT_HEIGHT_PX); currentChatListHeight += (selectChat(global, id)?.isForum ? CHAT_HEIGHT_FORUM_PX : CHAT_HEIGHT_PX);
@ -177,12 +215,21 @@ const ChatList: FC<OwnProps> = ({
className={buildClassName('chat-list custom-scroll', isForumPanelOpen && 'forum-panel-open')} className={buildClassName('chat-list custom-scroll', isForumPanelOpen && 'forum-panel-open')}
ref={containerRef} ref={containerRef}
items={viewportIds} items={viewportIds}
itemSelector=".ListItem:not(.chat-item-archive)"
preloadBackwards={CHAT_LIST_SLICE} preloadBackwards={CHAT_LIST_SLICE}
withAbsolutePositioning withAbsolutePositioning
maxHeight={(orderedIds?.length || 0) * CHAT_HEIGHT_PX} maxHeight={chatsHeight + archiveHeight}
onLoadMore={getMore} onLoadMore={getMore}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
> >
{shouldDisplayArchive && (
<Archive
key="archive"
archiveSettings={archiveSettings}
onClick={handleArchivedClick}
onDragEnter={handleArchivedDragEnter}
/>
)}
{viewportIds?.length ? ( {viewportIds?.length ? (
renderChats() renderChats()
) : viewportIds && !viewportIds.length ? ( ) : viewportIds && !viewportIds.length ? (
@ -191,7 +238,7 @@ const ChatList: FC<OwnProps> = ({
folderId={folderId} folderId={folderId}
folderType={folderType} folderType={folderType}
foldersDispatch={foldersDispatch} foldersDispatch={foldersDispatch}
onScreenSelect={onScreenSelect} onSettingsScreenSelect={onSettingsScreenSelect}
/> />
) )
) : ( ) : (

View File

@ -18,8 +18,8 @@ import styles from './EmptyFolder.module.scss';
type OwnProps = { type OwnProps = {
folderId?: number; folderId?: number;
folderType: 'all' | 'archived' | 'folder'; folderType: 'all' | 'archived' | 'folder';
foldersDispatch?: FolderEditDispatch; foldersDispatch: FolderEditDispatch;
onScreenSelect?: (screen: SettingsScreens) => void; onSettingsScreenSelect: (screen: SettingsScreens) => void;
}; };
type StateProps = { type StateProps = {
@ -30,15 +30,15 @@ type StateProps = {
const ICON_SIZE = 96; const ICON_SIZE = 96;
const EmptyFolder: FC<OwnProps & StateProps> = ({ const EmptyFolder: FC<OwnProps & StateProps> = ({
chatFolder, animatedEmoji, foldersDispatch, onScreenSelect, chatFolder, animatedEmoji, foldersDispatch, onSettingsScreenSelect,
}) => { }) => {
const lang = useLang(); const lang = useLang();
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const handleEditFolder = useCallback(() => { const handleEditFolder = useCallback(() => {
foldersDispatch!({ type: 'editFolder', payload: chatFolder }); foldersDispatch({ type: 'editFolder', payload: chatFolder });
onScreenSelect!(SettingsScreens.FoldersEditFolderFromChatList); onSettingsScreenSelect(SettingsScreens.FoldersEditFolderFromChatList);
}, [chatFolder, foldersDispatch, onScreenSelect]); }, [chatFolder, foldersDispatch, onSettingsScreenSelect]);
return ( return (
<div className={styles.root}> <div className={styles.root}>
@ -49,7 +49,7 @@ const EmptyFolder: FC<OwnProps & StateProps> = ({
<p className={styles.description} dir="auto"> <p className={styles.description} dir="auto">
{lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')} {lang(chatFolder ? 'ChatList.EmptyChatListFilterText' : 'Chat.EmptyChat')}
</p> </p>
{chatFolder && foldersDispatch && onScreenSelect && ( {chatFolder && (
<Button <Button
ripple={!isMobile} ripple={!isMobile}
fluid fluid

View File

@ -23,6 +23,7 @@ import Button from '../../ui/Button';
import ForumPanel from './ForumPanel'; import ForumPanel from './ForumPanel';
import './LeftMain.scss'; import './LeftMain.scss';
import { getActions } from '../../../global';
type OwnProps = { type OwnProps = {
content: LeftColumnContent; content: LeftColumnContent;
@ -36,7 +37,7 @@ type OwnProps = {
isClosingSearch?: boolean; isClosingSearch?: boolean;
onSearchQuery: (query: string) => void; onSearchQuery: (query: string) => void;
onContentChange: (content: LeftColumnContent) => void; onContentChange: (content: LeftColumnContent) => void;
onScreenSelect: (screen: SettingsScreens) => void; onSettingsScreenSelect: (screen: SettingsScreens) => void;
onTopicSearch: NoneToVoidFunction; onTopicSearch: NoneToVoidFunction;
onReset: () => void; onReset: () => void;
}; };
@ -58,10 +59,11 @@ const LeftMain: FC<OwnProps> = ({
isForumPanelOpen, isForumPanelOpen,
onSearchQuery, onSearchQuery,
onContentChange, onContentChange,
onScreenSelect, onSettingsScreenSelect,
onReset, onReset,
onTopicSearch, onTopicSearch,
}) => { }) => {
const { closeForumPanel } = getActions();
const [isNewChatButtonShown, setIsNewChatButtonShown] = useState(IS_TOUCH_ENV); const [isNewChatButtonShown, setIsNewChatButtonShown] = useState(IS_TOUCH_ENV);
const { shouldRenderForumPanel, handleForumPanelAnimationEnd } = useForumPanelRender(isForumPanelOpen); const { shouldRenderForumPanel, handleForumPanelAnimationEnd } = useForumPanelRender(isForumPanelOpen);
@ -107,7 +109,8 @@ const LeftMain: FC<OwnProps> = ({
const handleSelectArchived = useCallback(() => { const handleSelectArchived = useCallback(() => {
onContentChange(LeftColumnContent.Archived); onContentChange(LeftColumnContent.Archived);
}, [onContentChange]); closeForumPanel();
}, [closeForumPanel, onContentChange]);
const handleUpdateClick = useCallback(() => { const handleUpdateClick = useCallback(() => {
window.location.reload(); window.location.reload();
@ -172,7 +175,8 @@ const LeftMain: FC<OwnProps> = ({
return ( return (
<ChatFolders <ChatFolders
shouldHideFolderTabs={isForumPanelVisible} shouldHideFolderTabs={isForumPanelVisible}
onScreenSelect={onScreenSelect} onSettingsScreenSelect={onSettingsScreenSelect}
onLeftColumnContentChange={onContentChange}
foldersDispatch={foldersDispatch} foldersDispatch={foldersDispatch}
/> />
); );

View File

@ -78,7 +78,7 @@ type StateProps =
areChatsLoaded?: boolean; areChatsLoaded?: boolean;
hasPasscode?: boolean; hasPasscode?: boolean;
} }
& Pick<GlobalState, 'connectionState' | 'isSyncing'> & Pick<TabState, 'canInstall'>; & Pick<GlobalState, 'connectionState' | 'isSyncing' | 'archiveSettings'> & Pick<TabState, 'canInstall'>;
const ANIMATION_LEVEL_OPTIONS = [0, 1, 2]; const ANIMATION_LEVEL_OPTIONS = [0, 1, 2];
const WEBK_VERSION_URL = 'https://web.telegram.org/k/'; const WEBK_VERSION_URL = 'https://web.telegram.org/k/';
@ -110,6 +110,7 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
areChatsLoaded, areChatsLoaded,
hasPasscode, hasPasscode,
canInstall, canInstall,
archiveSettings,
}) => { }) => {
const { const {
openChat, openChat,
@ -276,15 +277,17 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
> >
{lang('SavedMessages')} {lang('SavedMessages')}
</MenuItem> </MenuItem>
<MenuItem {archiveSettings.isHidden && (
icon="archive" <MenuItem
onClick={onSelectArchived} icon="archive"
> onClick={onSelectArchived}
<span className="menu-item-name">{lang('ArchivedChats')}</span> >
{archivedUnreadChatsCount > 0 && ( <span className="menu-item-name">{lang('ArchivedChats')}</span>
<div className="right-badge">{archivedUnreadChatsCount}</div> {archivedUnreadChatsCount > 0 && (
)} <div className="right-badge">{archivedUnreadChatsCount}</div>
</MenuItem> )}
</MenuItem>
)}
<MenuItem <MenuItem
icon="user" icon="user"
onClick={onSelectContacts} onClick={onSelectContacts}
@ -359,9 +362,9 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
)} )}
</> </>
), [ ), [
animationLevel, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange, handleBugReportClick, animationLevel, archiveSettings.isHidden, archivedUnreadChatsCount, canInstall, handleAnimationLevelChange,
handleChangelogClick, handleDarkModeToggle, handleOpenTipsChat, handleSelectSaved, handleSwitchToWebK, lang, handleBugReportClick, handleChangelogClick, handleDarkModeToggle, handleOpenTipsChat, handleSelectSaved,
onSelectArchived, onSelectContacts, onSelectSettings, theme, withOtherVersions, handleSwitchToWebK, lang, onSelectArchived, onSelectContacts, onSelectSettings, theme, withOtherVersions,
]); ]);
return ( return (
@ -457,7 +460,9 @@ export default memo(withGlobal<OwnProps>(
const { const {
query: searchQuery, fetchingStatus, chatId, date, query: searchQuery, fetchingStatus, chatId, date,
} = tabState.globalSearch; } = tabState.globalSearch;
const { currentUserId, connectionState, isSyncing } = global; const {
currentUserId, connectionState, isSyncing, archiveSettings,
} = global;
const { byId: chatsById } = global.chats; const { byId: chatsById } = global.chats;
const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey; const { isConnectionStatusMinimized, animationLevel } = global.settings.byKey;
@ -478,6 +483,7 @@ export default memo(withGlobal<OwnProps>(
areChatsLoaded: Boolean(global.chats.listIds.active), areChatsLoaded: Boolean(global.chats.listIds.active),
hasPasscode: Boolean(global.passcode.hasPasscode), hasPasscode: Boolean(global.passcode.hasPasscode),
canInstall: Boolean(tabState.canInstall), canInstall: Boolean(tabState.canInstall),
archiveSettings,
}; };
}, },
)(LeftMainHeader)); )(LeftMainHeader));

View File

@ -1,5 +1,5 @@
import { getActions } from '../../../../global';
import { useMemo } from '../../../../lib/teact/teact'; import { useMemo } from '../../../../lib/teact/teact';
import { getActions } from '../../../../global';
import type { ApiChat, ApiTopic } from '../../../../api/types'; import type { ApiChat, ApiTopic } from '../../../../api/types';
import type { MenuItemContextAction } from '../../../ui/ListItem'; import type { MenuItemContextAction } from '../../../ui/ListItem';

View File

@ -18,6 +18,8 @@
.info { .info {
display: flex; display: flex;
flex-direction: row;
justify-content: flex-start;
} }
.title { .title {

View File

@ -128,10 +128,11 @@
&.commonChats-list, &.commonChats-list,
&.members-list { &.members-list {
padding: 0.5rem 1rem; padding: 0.5rem;
@media (max-width: 600px) { @media (max-width: 600px) {
padding: 0.5rem 0; padding: 0.5rem 0;
.ListItem.chat-item-clickable { .ListItem.chat-item-clickable {
margin: 0; margin: 0;
} }

View File

@ -1,13 +1,17 @@
import React, {
useState, useRef, useCallback, useMemo,
} from '../../lib/teact/teact';
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import React, { useState, useRef, useCallback } from '../../lib/teact/teact';
import Menu from './Menu'; import Menu from './Menu';
import Button from './Button';
import './DropdownMenu.scss'; import './DropdownMenu.scss';
type OwnProps = { type OwnProps = {
className?: string; className?: string;
trigger: FC<{ onTrigger: () => void; isOpen?: boolean }>; trigger?: FC<{ onTrigger: () => void; isOpen?: boolean }>;
positionX?: 'left' | 'right'; positionX?: 'left' | 'right';
positionY?: 'top' | 'bottom'; positionY?: 'top' | 'bottom';
footer?: string; footer?: string;
@ -69,6 +73,23 @@ const DropdownMenu: FC<OwnProps> = ({
onClose?.(); onClose?.();
}, [onClose]); }, [onClose]);
const triggerComponent: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
if (trigger) return trigger;
return ({ onTrigger, isOpen: isMenuOpen }) => (
<Button
round
size="smaller"
color="translucent"
className={isMenuOpen ? 'active' : ''}
onClick={onTrigger}
ariaLabel="More actions"
>
<i className="icon-more" />
</Button>
);
}, [trigger]);
return ( return (
<div <div
ref={dropdownRef} ref={dropdownRef}
@ -76,7 +97,7 @@ const DropdownMenu: FC<OwnProps> = ({
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onTransitionEnd={onTransitionEnd} onTransitionEnd={onTransitionEnd}
> >
{trigger({ onTrigger: toggleIsOpen, isOpen })} {triggerComponent({ onTrigger: toggleIsOpen, isOpen })}
<Menu <Menu
ref={menuRef} ref={menuRef}

View File

@ -32,6 +32,7 @@
.ListItem-button { .ListItem-button {
width: 100%; width: 100%;
height: 100%;
background-color: var(--background-color); background-color: var(--background-color);
border: none !important; border: none !important;
box-shadow: none !important; box-shadow: none !important;

View File

@ -61,8 +61,9 @@ export const MIN_PASSWORD_LENGTH = 1;
export const MESSAGE_LIST_SLICE = isBigScreen ? 60 : 40; export const MESSAGE_LIST_SLICE = isBigScreen ? 60 : 40;
export const MESSAGE_LIST_VIEWPORT_LIMIT = MESSAGE_LIST_SLICE * 2; export const MESSAGE_LIST_VIEWPORT_LIMIT = MESSAGE_LIST_SLICE * 2;
export const ARCHIVE_MINIMIZED_HEIGHT = 36;
export const CHAT_HEIGHT_PX = 72; export const CHAT_HEIGHT_PX = 72;
export const CHAT_HEIGHT_FORUM_PX = 96; export const CHAT_HEIGHT_FORUM_PX = 94;
export const TOPIC_HEIGHT_PX = 65; export const TOPIC_HEIGHT_PX = 65;
export const CHAT_LIST_SLICE = isBigScreen ? 30 : 25; export const CHAT_LIST_SLICE = isBigScreen ? 30 : 25;
export const CHAT_LIST_LOAD_SLICE = 100; export const CHAT_LIST_LOAD_SLICE = 100;

View File

@ -561,6 +561,19 @@ addActionHandler('closeEditTopicPanel', (global, actions, payload): ActionReturn
}, tabId); }, tabId);
}); });
addActionHandler('updateArchiveSettings', (global, actions, payload): ActionReturnType => {
const { archiveSettings } = global;
const { isHidden = archiveSettings.isHidden, isMinimized = archiveSettings.isMinimized } = payload;
return {
...global,
archiveSettings: {
isHidden,
isMinimized,
},
};
});
addActionHandler('checkAppVersion', (global): ActionReturnType => { addActionHandler('checkAppVersion', (global): ActionReturnType => {
const APP_VERSION_REGEX = /^\d+\.\d+(\.\d+)?$/; const APP_VERSION_REGEX = /^\d+\.\d+(\.\d+)?$/;

View File

@ -195,10 +195,6 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
cached.serviceNotifications = []; cached.serviceNotifications = [];
} }
if (!cached.groupCalls) {
cached.groupCalls = initialState.groupCalls;
}
if (!cached.users.statusesById) { if (!cached.users.statusesById) {
cached.users.statusesById = {}; cached.users.statusesById = {};
} }
@ -336,10 +332,6 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
cached.availableReactions = cached.availableReactions cached.availableReactions = cached.availableReactions
.map((r) => ({ ...r, reaction: { emoticon: r.reaction as unknown as string } })); .map((r) => ({ ...r, reaction: { emoticon: r.reaction as unknown as string } }));
} }
if (!cached.attachmentSettings) {
cached.attachmentSettings = initialState.attachmentSettings;
}
} }
function updateCache() { function updateCache() {
@ -390,6 +382,7 @@ export function serializeGlobal<T extends GlobalState>(global: T) {
'attachmentSettings', 'attachmentSettings',
'leftColumnWidth', 'leftColumnWidth',
'lastIsChatInfoShown', 'lastIsChatInfoShown',
'archiveSettings',
'mediaViewer', 'mediaViewer',
'audioPlayer', 'audioPlayer',
]), ]),

View File

@ -186,6 +186,11 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
transcriptions: {}, transcriptions: {},
byTabId: {}, byTabId: {},
archiveSettings: {
isMinimized: false,
isHidden: false,
},
}; };
export const INITIAL_TAB_STATE: TabState = { export const INITIAL_TAB_STATE: TabState = {

View File

@ -781,6 +781,11 @@ export type GlobalState = {
serviceNotifications: ServiceNotification[]; serviceNotifications: ServiceNotification[];
byTabId: Record<number, TabState>; byTabId: Record<number, TabState>;
archiveSettings: {
isMinimized: boolean;
isHidden: boolean;
};
}; };
export type CallSound = ( export type CallSound = (
@ -2088,6 +2093,11 @@ export interface ActionPayloads {
shouldSendGrouped?: boolean; shouldSendGrouped?: boolean;
}; };
updateArchiveSettings: {
isMinimized?: boolean;
isHidden?: boolean;
};
openUrl: { openUrl: {
url: string; url: string;
shouldSkipModal?: boolean; shouldSkipModal?: boolean;

View File

@ -332,6 +332,7 @@ export function typify<
if (DEBUG) { if (DEBUG) {
(window as any).getGlobal = getGlobal; (window as any).getGlobal = getGlobal;
(window as any).setGlobal = setGlobal;
document.addEventListener('dblclick', () => { document.addEventListener('dblclick', () => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console

File diff suppressed because it is too large Load Diff

View File

@ -81,24 +81,22 @@
.chat-list { .chat-list {
background: var(--color-background); background: var(--color-background);
height: 100%; height: 100%;
padding: 0.5rem 0.125rem 0.5rem 0.4375rem; padding: 0.5rem 0.4375rem 0.5rem 0.4375rem;
overflow-y: auto; overflow-y: auto;
body.is-android & { @include overflow-y-overlay();
@include overflow-y-overlay();
}
&.forum-panel-open { &.forum-panel-open {
.info { .info {
transform: translateX(-25%);
opacity: 0; opacity: 0;
transform: translateX(-25%);
} }
.Chat[dir="rtl"] .info { .Chat[dir="rtl"] .info {
transform: translateX(25%); transform: translateX(25%);
} }
.ListItem-button { .group .ListItem-button {
height: 4.5rem; height: 4.5rem;
} }
} }
@ -159,6 +157,6 @@
} }
&.deleted-account { &.deleted-account {
--color-user: #9eaab5; --color-user: var(--color-deleted-account);
} }
} }

View File

@ -180,6 +180,9 @@ $color-message-reaction-own-hover: #b5e0a4;
--color-forum-hover-unread-topic: #e9e9e9; --color-forum-hover-unread-topic: #e9e9e9;
--color-forum-hover-unread-topic-hover: #dcdcdc; --color-forum-hover-unread-topic-hover: #dcdcdc;
--color-deleted-account: #9eaab5;
--color-archive: #9eaab5;
--vh: 1vh; --vh: 1vh;
--border-radius-default: 0.75rem; --border-radius-default: 0.75rem;

View File

@ -51,6 +51,21 @@
.icon-volume-3:before { .icon-volume-3:before {
content: "\e991"; content: "\e991";
} }
.icon-archive-filled:before {
content: "\e9ba";
}
.icon-archive-from-main:before {
content: "\e9bb";
}
.icon-archive-to-main:before {
content: "\e9bc";
}
.icon-collapse:before {
content: "\e9bd";
}
.icon-expand:before {
content: "\e9be";
}
.icon-replies:before { .icon-replies:before {
content: "\e9b9"; content: "\e9b9";
} }