mirror of
https://github.com/danog/telegram-tt.git
synced 2025-01-22 05:11:55 +01:00
Add chat to folder from context menu (#1477)
This commit is contained in:
parent
54d6b581da
commit
609c0cb68e
@ -19,6 +19,7 @@ export { default as NewChat } from '../components/left/newChat/NewChat';
|
||||
export { default as NewChatStep1 } from '../components/left/newChat/NewChatStep1';
|
||||
export { default as NewChatStep2 } from '../components/left/newChat/NewChatStep2';
|
||||
export { default as ArchivedChats } from '../components/left/ArchivedChats';
|
||||
export { default as ChatFolderModal } from '../components/left/ChatFolderModal';
|
||||
|
||||
export { default as ContextMenuContainer } from '../components/middle/message/ContextMenuContainer';
|
||||
export { default as StickerSetModal } from '../components/common/StickerSetModal';
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
getMessageContent,
|
||||
getMessageSummaryText,
|
||||
getUserFullName,
|
||||
isChat,
|
||||
isChatPrivate,
|
||||
} from '../../../modules/helpers';
|
||||
import trimText from '../../../util/trimText';
|
||||
import { formatCurrency } from '../../../util/formatCurrency';
|
||||
@ -167,9 +167,9 @@ function renderMessageContent(lang: LangFn, message: ApiMessage, options: Action
|
||||
}
|
||||
|
||||
function renderOriginContent(lang: LangFn, origin: ApiUser | ApiChat, asPlain?: boolean) {
|
||||
return isChat(origin)
|
||||
? renderChatContent(lang, origin, asPlain)
|
||||
: renderUserContent(origin, asPlain);
|
||||
return isChatPrivate(origin.id)
|
||||
? renderUserContent(origin as ApiUser, asPlain)
|
||||
: renderChatContent(lang, origin as ApiChat, asPlain);
|
||||
}
|
||||
|
||||
function renderUserContent(sender: ApiUser, asPlain?: boolean): string | TextPart | undefined {
|
||||
|
15
src/components/left/ChatFolderModal.async.tsx
Normal file
15
src/components/left/ChatFolderModal.async.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React, { FC, memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
import { OwnProps } from './ChatFolderModal';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const ChatFolderModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const ChatFolderModal = useModuleLoader(Bundles.Extra, 'ChatFolderModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return ChatFolderModal ? <ChatFolderModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(ChatFolderModalAsync);
|
110
src/components/left/ChatFolderModal.tsx
Normal file
110
src/components/left/ChatFolderModal.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import React, {
|
||||
FC, useCallback, memo, useMemo, useState,
|
||||
} from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../global/types';
|
||||
import { ApiChatFolder } from '../../api/types';
|
||||
|
||||
import { pick } from '../../util/iteratees';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
import Button from '../ui/Button';
|
||||
import CheckboxGroup from '../ui/CheckboxGroup';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
chatId: number;
|
||||
onClose: () => void;
|
||||
onCloseAnimationEnd?: () => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
foldersById?: Record<number, ApiChatFolder>;
|
||||
folderOrderedIds?: number[];
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'editChatFolders'>;
|
||||
|
||||
const ChatFolderModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isOpen,
|
||||
chatId,
|
||||
foldersById,
|
||||
folderOrderedIds,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
editChatFolders,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const initialSelectedFolderIds = useMemo(() => {
|
||||
if (!foldersById) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.keys(foldersById).reduce((result, folderId) => {
|
||||
const { includedChatIds, pinnedChatIds } = foldersById[Number(folderId)];
|
||||
if (includedChatIds.includes(chatId) || pinnedChatIds?.includes(chatId)) {
|
||||
result.push(folderId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [] as string[]);
|
||||
}, [chatId, foldersById]);
|
||||
|
||||
const [selectedFolderIds, setSelectedFolderIds] = useState<string[]>(initialSelectedFolderIds);
|
||||
|
||||
const folders = useMemo(() => {
|
||||
return folderOrderedIds?.map((folderId) => ({
|
||||
label: foldersById ? foldersById[folderId].title : '',
|
||||
value: String(folderId),
|
||||
})) || [];
|
||||
}, [folderOrderedIds, foldersById]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const idsToRemove = initialSelectedFolderIds.filter((id) => !selectedFolderIds.includes(id)).map(Number);
|
||||
const idsToAdd = selectedFolderIds.filter((id) => !initialSelectedFolderIds.includes(id)).map(Number);
|
||||
|
||||
editChatFolders({ chatId, idsToRemove, idsToAdd });
|
||||
onClose();
|
||||
}, [chatId, editChatFolders, initialSelectedFolderIds, onClose, selectedFolderIds]);
|
||||
|
||||
if (!foldersById || !folderOrderedIds) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||
onEnter={handleSubmit}
|
||||
className="delete"
|
||||
title={lang('FilterAddTo')}
|
||||
>
|
||||
<CheckboxGroup
|
||||
options={folders}
|
||||
selected={selectedFolderIds}
|
||||
onChange={setSelectedFolderIds}
|
||||
round
|
||||
/>
|
||||
<Button color="primary" className="confirm-dialog-button" isText onClick={handleSubmit}>
|
||||
{lang('FilterAddTo')}
|
||||
</Button>
|
||||
<Button className="confirm-dialog-button" isText onClick={onClose}>{lang('Cancel')}</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { byId: foldersById, orderedIds: folderOrderedIds } = global.chatFolders;
|
||||
|
||||
return {
|
||||
foldersById,
|
||||
folderOrderedIds,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['editChatFolders']),
|
||||
)(ChatFolderModal));
|
@ -50,6 +50,7 @@ import LastMessageMeta from '../../common/LastMessageMeta';
|
||||
import DeleteChatModal from '../../common/DeleteChatModal';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import Badge from './Badge';
|
||||
import ChatFolderModal from '../ChatFolderModal.async';
|
||||
|
||||
import './Chat.scss';
|
||||
|
||||
@ -111,7 +112,9 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||
const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag();
|
||||
const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag();
|
||||
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
|
||||
|
||||
const { lastMessage, typingStatus } = chat || {};
|
||||
const isAction = lastMessage && isActionMessage(lastMessage);
|
||||
@ -185,10 +188,16 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
openDeleteModal();
|
||||
}
|
||||
|
||||
function handleChatFolderChange() {
|
||||
markRenderChatFolderModal();
|
||||
openChatFolderModal();
|
||||
}
|
||||
|
||||
const contextActions = useChatContextActions({
|
||||
chat,
|
||||
privateChatUser,
|
||||
handleDelete,
|
||||
handleChatFolderChange,
|
||||
folderId,
|
||||
isPinned,
|
||||
isMuted,
|
||||
@ -299,6 +308,14 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
chat={chat}
|
||||
/>
|
||||
)}
|
||||
{shouldRenderChatFolderModal && (
|
||||
<ChatFolderModal
|
||||
isOpen={isChatFolderModalOpen}
|
||||
onClose={closeChatFolderModal}
|
||||
onCloseAnimationEnd={unmarkRenderChatFolderModal}
|
||||
chatId={chatId}
|
||||
/>
|
||||
)}
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ import PrivateChatInfo from '../../common/PrivateChatInfo';
|
||||
import GroupChatInfo from '../../common/GroupChatInfo';
|
||||
import DeleteChatModal from '../../common/DeleteChatModal';
|
||||
import ListItem from '../../ui/ListItem';
|
||||
import ChatFolderModal from '../ChatFolderModal.async';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: number;
|
||||
@ -41,6 +42,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
onClick,
|
||||
}) => {
|
||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
|
||||
const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag();
|
||||
|
||||
const contextActions = useChatContextActions({
|
||||
chat,
|
||||
@ -48,6 +50,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
isPinned,
|
||||
isMuted,
|
||||
handleDelete: openDeleteModal,
|
||||
handleChatFolderChange: openChatFolderModal,
|
||||
}, true);
|
||||
|
||||
const handleClick = () => {
|
||||
@ -77,6 +80,11 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
onClose={closeDeleteModal}
|
||||
chat={chat}
|
||||
/>
|
||||
<ChatFolderModal
|
||||
isOpen={isChatFolderModalOpen}
|
||||
onClose={closeChatFolderModal}
|
||||
chatId={chatId}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ import React, { FC, memo, useCallback } from '../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
|
||||
import Spinner from './Spinner';
|
||||
|
||||
@ -69,8 +70,8 @@ const Checkbox: FC<OwnProps> = ({
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className="Checkbox-main">
|
||||
<span className="label" dir="auto">{label}</span>
|
||||
{subLabel && <span className="subLabel" dir="auto">{subLabel}</span>}
|
||||
<span className="label" dir="auto">{renderText(label)}</span>
|
||||
{subLabel && <span className="subLabel" dir="auto">{renderText(subLabel)}</span>}
|
||||
</div>
|
||||
{isLoading && <Spinner />}
|
||||
</label>
|
||||
|
@ -30,7 +30,7 @@ const CheckboxGroup: FC<OwnProps> = ({
|
||||
loadingOptions,
|
||||
onChange,
|
||||
}) => {
|
||||
const [values, setValues] = useState<string[]>([]);
|
||||
const [values, setValues] = useState<string[]>(selected || []);
|
||||
|
||||
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value, checked } = event.currentTarget;
|
||||
|
@ -460,7 +460,7 @@ export type ActionTypes = (
|
||||
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
||||
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
|
||||
'loadProfilePhotos' | 'loadMoreMembers' | 'setActiveChatFolder' | 'openNextChat' |
|
||||
'addChatMembers' | 'deleteChatMember' | 'openPreviousChat' |
|
||||
'addChatMembers' | 'deleteChatMember' | 'openPreviousChat' | 'editChatFolders' |
|
||||
// messages
|
||||
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
|
||||
'markMessageListRead' | 'markMessagesRead' | 'loadMessage' | 'focusMessage' | 'focusLastMessage' | 'sendPollVote' |
|
||||
|
@ -12,6 +12,7 @@ export default ({
|
||||
chat,
|
||||
privateChatUser,
|
||||
handleDelete,
|
||||
handleChatFolderChange,
|
||||
folderId,
|
||||
isPinned,
|
||||
isMuted,
|
||||
@ -19,6 +20,7 @@ export default ({
|
||||
chat: ApiChat | undefined;
|
||||
privateChatUser: ApiUser | undefined;
|
||||
handleDelete: () => void;
|
||||
handleChatFolderChange: () => void;
|
||||
folderId?: number;
|
||||
isPinned?: boolean;
|
||||
isMuted?: boolean;
|
||||
@ -37,6 +39,12 @@ export default ({
|
||||
toggleChatUnread,
|
||||
} = getDispatch();
|
||||
|
||||
const actionAddToFolder = {
|
||||
title: lang('ChatList.Filter.AddToFolder'),
|
||||
icon: 'folder',
|
||||
handler: handleChatFolderChange,
|
||||
};
|
||||
|
||||
const actionPin = isPinned
|
||||
? {
|
||||
title: lang('UnpinFromTop'),
|
||||
@ -46,7 +54,7 @@ export default ({
|
||||
: { title: lang('PinToTop'), icon: 'pin', handler: () => toggleChatPinned({ id: chat.id, folderId }) };
|
||||
|
||||
if (isInSearch) {
|
||||
return [actionPin];
|
||||
return [actionPin, actionAddToFolder];
|
||||
}
|
||||
|
||||
const actionUnreadMark = chat.unreadCount || chat.hasUnreadMark
|
||||
@ -81,6 +89,7 @@ export default ({
|
||||
};
|
||||
|
||||
return [
|
||||
actionAddToFolder,
|
||||
actionUnreadMark,
|
||||
actionPin,
|
||||
...(!privateChatUser?.isSelf ? [
|
||||
@ -89,5 +98,7 @@ export default ({
|
||||
] : []),
|
||||
actionDelete,
|
||||
];
|
||||
}, [chat, isPinned, lang, isInSearch, isMuted, handleDelete, privateChatUser?.isSelf, folderId]);
|
||||
}, [
|
||||
chat, isPinned, lang, isInSearch, isMuted, handleDelete, handleChatFolderChange, privateChatUser?.isSelf, folderId,
|
||||
]);
|
||||
};
|
||||
|
@ -435,6 +435,37 @@ addReducer('loadRecommendedChatFolders', () => {
|
||||
void loadRecommendedChatFolders();
|
||||
});
|
||||
|
||||
addReducer('editChatFolders', (global, actions, payload) => {
|
||||
const { chatId, idsToRemove, idsToAdd } = payload!;
|
||||
|
||||
(idsToRemove as number[]).forEach(async (id) => {
|
||||
const folder = selectChatFolder(global, id);
|
||||
if (folder) {
|
||||
await callApi('editChatFolder', {
|
||||
id,
|
||||
folderUpdate: {
|
||||
...folder,
|
||||
pinnedChatIds: folder.pinnedChatIds?.filter((pinnedId) => pinnedId !== chatId),
|
||||
includedChatIds: folder.includedChatIds.filter((includedId) => includedId !== chatId),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
(idsToAdd as number[]).forEach(async (id) => {
|
||||
const folder = selectChatFolder(global, id);
|
||||
if (folder) {
|
||||
await callApi('editChatFolder', {
|
||||
id,
|
||||
folderUpdate: {
|
||||
...folder,
|
||||
includedChatIds: folder.includedChatIds.concat(chatId),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
addReducer('editChatFolder', (global, actions, payload) => {
|
||||
const { id, folderUpdate } = payload!;
|
||||
const folder = selectChatFolder(global, id);
|
||||
|
@ -498,14 +498,6 @@ function getFolderChatsCount(
|
||||
return pinnedChats.length + otherChats.length;
|
||||
}
|
||||
|
||||
export function isChat(chatOrUser?: ApiUser | ApiChat): chatOrUser is ApiChat {
|
||||
if (!chatOrUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return chatOrUser.id < 0;
|
||||
}
|
||||
|
||||
export function getMessageSenderName(lang: LangFn, chatId: number, sender?: ApiUser) {
|
||||
if (!sender || isChatPrivate(chatId)) {
|
||||
return undefined;
|
||||
|
Loading…
x
Reference in New Issue
Block a user