Various fixes for drafts (#4036)

This commit is contained in:
Alexander Zinchuk 2023-12-04 14:39:47 +01:00
parent dcba75a11a
commit df76638fe1
9 changed files with 72 additions and 24 deletions

View File

@ -256,6 +256,7 @@ export function sendMessage(
noWebPage, noWebPage,
sendAs, sendAs,
shouldUpdateStickerSetOrder, shouldUpdateStickerSetOrder,
wasDrafted,
}: { }: {
chat: ApiChat; chat: ApiChat;
lastMessageId?: number; lastMessageId?: number;
@ -274,6 +275,7 @@ export function sendMessage(
noWebPage?: boolean; noWebPage?: boolean;
sendAs?: ApiPeer; sendAs?: ApiPeer;
shouldUpdateStickerSetOrder?: boolean; shouldUpdateStickerSetOrder?: boolean;
wasDrafted?: boolean;
}, },
onProgress?: ApiOnProgress, onProgress?: ApiOnProgress,
) { ) {
@ -298,6 +300,7 @@ export function sendMessage(
id: localMessage.id, id: localMessage.id,
chatId: chat.id, chatId: chat.id,
message: localMessage, message: localMessage,
wasDrafted,
}); });
// This is expected to arrive after `updateMessageSendSucceeded` which replaces the local ID, // This is expected to arrive after `updateMessageSendSucceeded` which replaces the local ID,
@ -1284,6 +1287,7 @@ export async function forwardMessages({
noAuthors, noAuthors,
noCaptions, noCaptions,
isCurrentUserPremium, isCurrentUserPremium,
wasDrafted,
}: { }: {
fromChat: ApiChat; fromChat: ApiChat;
toChat: ApiChat; toChat: ApiChat;
@ -1296,6 +1300,7 @@ export async function forwardMessages({
noAuthors?: boolean; noAuthors?: boolean;
noCaptions?: boolean; noCaptions?: boolean;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
wasDrafted?: boolean;
}) { }) {
const messageIds = messages.map(({ id }) => id); const messageIds = messages.map(({ id }) => id);
const randomIds = messages.map(generateRandomBigInt); const randomIds = messages.map(generateRandomBigInt);
@ -1318,6 +1323,7 @@ export async function forwardMessages({
id: localMessage.id, id: localMessage.id,
chatId: toChat.id, chatId: toChat.id,
message: localMessage, message: localMessage,
wasDrafted,
}); });
}); });

View File

@ -191,6 +191,7 @@ export type ApiUpdateNewScheduledMessage = {
chatId: string; chatId: string;
id: number; id: number;
message: ApiMessage; message: ApiMessage;
wasDrafted?: boolean;
}; };
export type ApiUpdateNewMessage = { export type ApiUpdateNewMessage = {
@ -199,6 +200,7 @@ export type ApiUpdateNewMessage = {
id: number; id: number;
message: Partial<ApiMessage>; message: Partial<ApiMessage>;
shouldForceReply?: boolean; shouldForceReply?: boolean;
wasDrafted?: boolean;
}; };
export type ApiUpdateMessage = { export type ApiUpdateMessage = {

View File

@ -14,16 +14,21 @@ import './LastMessageMeta.scss';
type OwnProps = { type OwnProps = {
message: ApiMessage; message: ApiMessage;
outgoingStatus?: ApiMessageOutgoingStatus; outgoingStatus?: ApiMessageOutgoingStatus;
draftDate?: number;
}; };
const LastMessageMeta: FC<OwnProps> = ({ message, outgoingStatus }) => { const LastMessageMeta: FC<OwnProps> = ({ message, outgoingStatus, draftDate }) => {
const lang = useLang(); const lang = useLang();
const shouldUseDraft = draftDate && draftDate > message.date;
return ( return (
<div className="LastMessageMeta"> <div className="LastMessageMeta">
{outgoingStatus && ( {outgoingStatus && !shouldUseDraft && (
<MessageOutgoingStatus status={outgoingStatus} /> <MessageOutgoingStatus status={outgoingStatus} />
)} )}
<span className="time">{formatPastTimeShort(lang, message.date * 1000)}</span> <span className="time">
{formatPastTimeShort(lang, (shouldUseDraft ? draftDate : message.date) * 1000)}
</span>
</div> </div>
); );
}; };

View File

@ -30,10 +30,6 @@
background-color: var(--color-reply-active); background-color: var(--color-reply-active);
box-shadow: 0 1px 2px var(--color-default-shadow); box-shadow: 0 1px 2px var(--color-default-shadow);
.embedded-thumb {
margin-inline-start: 0.5rem;
}
&:dir(rtl) { &:dir(rtl) {
padding: 0.5rem; padding: 0.5rem;
} }

View File

@ -293,6 +293,7 @@ const Chat: FC<OwnProps & StateProps> = ({
<LastMessageMeta <LastMessageMeta
message={chat.lastMessage} message={chat.lastMessage}
outgoingStatus={lastMessageOutgoingStatus} outgoingStatus={lastMessageOutgoingStatus}
draftDate={draft?.date}
/> />
)} )}
</div> </div>

View File

@ -105,8 +105,11 @@ export default function useChatListEntry({
} }
const isDraftReplyToTopic = draft && draft.replyInfo?.replyToMsgId === lastMessageTopic?.id; const isDraftReplyToTopic = draft && draft.replyInfo?.replyToMsgId === lastMessageTopic?.id;
const isEmptyLocalReply = draft?.replyInfo && !draft.text && draft.isLocal;
if (draft && (!chat?.isForum || (isTopic && !isDraftReplyToTopic))) { const canDisplayDraft = !chat?.isForum && draft && !isEmptyLocalReply && (!isTopic || !isDraftReplyToTopic);
if (canDisplayDraft) {
return ( return (
<p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}> <p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}>
<span className="draft">{lang('Draft')}</span> <span className="draft">{lang('Draft')}</span>

View File

@ -53,6 +53,7 @@ type StateProps = {
forwardsHaveCaptions?: boolean; forwardsHaveCaptions?: boolean;
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
isContextMenuDisabled?: boolean; isContextMenuDisabled?: boolean;
isReplyToDiscussion?: boolean;
}; };
type OwnProps = { type OwnProps = {
@ -75,6 +76,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
shouldForceShowEditing, shouldForceShowEditing,
isCurrentUserPremium, isCurrentUserPremium,
isContextMenuDisabled, isContextMenuDisabled,
isReplyToDiscussion,
onClear, onClear,
}) => { }) => {
const { const {
@ -105,7 +107,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
const { const {
shouldRender, transitionClassNames, shouldRender, transitionClassNames,
} = useShowTransition( } = useShowTransition(
canAnimate && isShown && !isReplyToTopicStart, canAnimate && isShown && !isReplyToTopicStart && !isReplyToDiscussion,
undefined, undefined,
!shouldAnimate, !shouldAnimate,
undefined, undefined,
@ -167,8 +169,10 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
getPeerColorClass(sender), getPeerColorClass(sender),
); );
const isShowingReply = replyInfo && !shouldForceShowEditing;
const leftIcon = useMemo(() => { const leftIcon = useMemo(() => {
if (replyInfo && !shouldForceShowEditing) { if (isShowingReply) {
return 'icon-reply'; return 'icon-reply';
} }
if (editingId) { if (editingId) {
@ -179,7 +183,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
} }
return undefined; return undefined;
}, [editingId, isForwarding, replyInfo, shouldForceShowEditing]); }, [editingId, isForwarding, isShowingReply]);
const customText = forwardedMessagesCount && forwardedMessagesCount > 1 const customText = forwardedMessagesCount && forwardedMessagesCount > 1
? lang('ForwardedMessageCount', forwardedMessagesCount) ? lang('ForwardedMessageCount', forwardedMessagesCount)
@ -214,7 +218,8 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
message={strippedMessage} message={strippedMessage}
sender={!noAuthors ? sender : undefined} sender={!noAuthors ? sender : undefined}
customText={customText} customText={customText}
title={editingId ? lang('EditMessage') : noAuthors ? lang('HiddenSendersNameDescription') : undefined} title={(editingId && !isShowingReply) ? lang('EditMessage')
: noAuthors ? lang('HiddenSendersNameDescription') : undefined}
onClick={handleMessageClick} onClick={handleMessageClick}
/> />
<Button <Button
@ -358,6 +363,8 @@ export default memo(withGlobal<OwnProps>(
const isContextMenuDisabled = isForwarding && forwardMessageIds!.length === 1 const isContextMenuDisabled = isForwarding && forwardMessageIds!.length === 1
&& Boolean(message?.content.storyData); && Boolean(message?.content.storyData);
const isReplyToDiscussion = replyInfo?.replyToMsgId === threadId && !replyInfo.replyToPeerId;
return { return {
replyInfo, replyInfo,
editingId, editingId,
@ -370,6 +377,7 @@ export default memo(withGlobal<OwnProps>(
forwardsHaveCaptions, forwardsHaveCaptions,
isCurrentUserPremium: selectIsCurrentUserPremium(global), isCurrentUserPremium: selectIsCurrentUserPremium(global),
isContextMenuDisabled, isContextMenuDisabled,
isReplyToDiscussion,
}; };
}, },
)(ComposerEmbeddedMessage)); )(ComposerEmbeddedMessage));

View File

@ -39,7 +39,9 @@ import {
areSortedArraysIntersecting, buildCollectionByKey, omit, partition, split, unique, areSortedArraysIntersecting, buildCollectionByKey, omit, partition, split, unique,
} from '../../../util/iteratees'; } from '../../../util/iteratees';
import { translate } from '../../../util/langProvider'; import { translate } from '../../../util/langProvider';
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers'; import {
debounce, onTickEnd, rafPromise,
} from '../../../util/schedulers';
import { IS_IOS } from '../../../util/windowEnvironment'; import { IS_IOS } from '../../../util/windowEnvironment';
import { callApi, cancelApiProgress } from '../../../api/gramjs'; import { callApi, cancelApiProgress } from '../../../api/gramjs';
import { import {
@ -292,7 +294,8 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
} }
const chat = selectChat(global, chatId!)!; const chat = selectChat(global, chatId!)!;
const draftReplyInfo = !isStoryReply ? selectDraft(global, chatId!, threadId!)?.replyInfo : undefined; const draft = selectDraft(global, chatId!, threadId!);
const draftReplyInfo = !isStoryReply ? draft?.replyInfo : undefined;
const storyReplyInfo = isStoryReply ? { const storyReplyInfo = isStoryReply ? {
type: 'story', type: 'story',
@ -313,7 +316,6 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
}; };
if (!isStoryReply) { if (!isStoryReply) {
actions.resetDraftReplyInfo({ tabId });
actions.clearWebPagePreview({ tabId }); actions.clearWebPagePreview({ tabId });
} }
@ -325,6 +327,7 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
sendMessage(global, { sendMessage(global, {
...restParams, ...restParams,
attachment: attachments ? attachments[0] : undefined, attachment: attachments ? attachments[0] : undefined,
wasDrafted: Boolean(draft),
}); });
} else if (isGrouped) { } else if (isGrouped) {
const { const {
@ -346,6 +349,7 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
entities: isFirst ? entities : undefined, entities: isFirst ? entities : undefined,
attachment: firstAttachment, attachment: firstAttachment,
groupedId: restAttachments.length > 0 ? groupedId : undefined, groupedId: restAttachments.length > 0 ? groupedId : undefined,
wasDrafted: Boolean(draft),
}); });
restAttachments.forEach((attachment: ApiAttachment) => { restAttachments.forEach((attachment: ApiAttachment) => {
@ -368,6 +372,7 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
text, text,
entities, entities,
replyInfo: replyToForFirstMessage, replyInfo: replyToForFirstMessage,
wasDrafted: Boolean(draft),
}); });
} }
@ -462,7 +467,9 @@ addActionHandler('saveDraft', (global, actions, payload): ActionReturnType => {
replyInfo: currentDraft?.replyInfo, replyInfo: currentDraft?.replyInfo,
}; };
saveDraft(global, chatId, threadId, newDraft); saveDraft({
global, chatId, threadId, draft: newDraft,
});
}); });
addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => { addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => {
@ -480,9 +487,9 @@ addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => {
replyInfo: currentReplyInfo, replyInfo: currentReplyInfo,
} : undefined; } : undefined;
if (!isLocalOnly) { saveDraft({
saveDraft(global, chatId, threadId, newDraft); global, chatId, threadId, draft: newDraft, isLocalOnly,
} });
}); });
addActionHandler('updateDraftReplyInfo', (global, actions, payload): ActionReturnType => { addActionHandler('updateDraftReplyInfo', (global, actions, payload): ActionReturnType => {
@ -509,7 +516,9 @@ addActionHandler('updateDraftReplyInfo', (global, actions, payload): ActionRetur
replyInfo: updatedReplyInfo, replyInfo: updatedReplyInfo,
}; };
saveDraft(global, chatId, threadId, newDraft); saveDraft({
global, chatId, threadId, draft: newDraft, isLocalOnly: true, noLocalTimeUpdate: true,
});
}); });
addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturnType => { addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturnType => {
@ -526,10 +535,16 @@ addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturn
replyInfo: undefined, replyInfo: undefined,
}; };
saveDraft(global, chatId, threadId, newDraft); saveDraft({
global, chatId, threadId, draft: newDraft, isLocalOnly: Boolean(newDraft),
});
}); });
async function saveDraft<T extends GlobalState>(global: T, chatId: string, threadId: number, draft?: ApiDraft) { async function saveDraft<T extends GlobalState>({
global, chatId, threadId, draft, isLocalOnly, noLocalTimeUpdate,
} : {
global: T; chatId: string; threadId: number; draft?: ApiDraft; isLocalOnly?: boolean; noLocalTimeUpdate?: boolean;
}) {
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
const user = selectUser(global, chatId); const user = selectUser(global, chatId);
if (!chat || (user && isDeletedUser(user))) return; if (!chat || (user && isDeletedUser(user))) return;
@ -544,10 +559,14 @@ async function saveDraft<T extends GlobalState>(global: T, chatId: string, threa
} : undefined; } : undefined;
global = replaceThreadParam(global, chatId, threadId, 'draft', newDraft); global = replaceThreadParam(global, chatId, threadId, 'draft', newDraft);
global = updateChat(global, chatId, { draftDate: newDraft?.date }); if (!noLocalTimeUpdate) {
global = updateChat(global, chatId, { draftDate: newDraft?.date });
}
setGlobal(global); setGlobal(global);
if (isLocalOnly) return;
const result = await callApi('saveDraft', { const result = await callApi('saveDraft', {
chat, chat,
draft: newDraft, draft: newDraft,
@ -929,6 +948,7 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
} }
const sendAs = selectSendAs(global, toChatId!); const sendAs = selectSendAs(global, toChatId!);
const draft = selectDraft(global, toChatId!, toThreadId || MAIN_THREAD_ID);
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m)); const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
if (realMessages.length) { if (realMessages.length) {
@ -946,6 +966,7 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
noAuthors, noAuthors,
noCaptions, noCaptions,
isCurrentUserPremium, isCurrentUserPremium,
wasDrafted: Boolean(draft),
}); });
})(); })();
} }
@ -1290,6 +1311,7 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
scheduledAt?: number; scheduledAt?: number;
sendAs?: ApiPeer; sendAs?: ApiPeer;
groupedId?: string; groupedId?: string;
wasDrafted?: boolean;
}) { }) {
let localId: number | undefined; let localId: number | undefined;
const progressCallback = params.attachment ? (progress: number, messageLocalId: number) => { const progressCallback = params.attachment ? (progress: number, messageLocalId: number) => {

View File

@ -71,7 +71,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
switch (update['@type']) { switch (update['@type']) {
case 'newMessage': { case 'newMessage': {
const { const {
chatId, id, message, shouldForceReply, chatId, id, message, shouldForceReply, wasDrafted,
} = update; } = update;
global = updateWithLocalMedia(global, chatId, id, message); global = updateWithLocalMedia(global, chatId, id, message);
global = updateListedAndViewportIds(global, actions, message as ApiMessage); global = updateListedAndViewportIds(global, actions, message as ApiMessage);
@ -89,6 +89,11 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
Object.values(global.byTabId).forEach(({ id: tabId }) => { Object.values(global.byTabId).forEach(({ id: tabId }) => {
const isLocal = isMessageLocal(message as ApiMessage); const isLocal = isMessageLocal(message as ApiMessage);
// Force update for last message on drafted messages to prevent flickering
if (isLocal && wasDrafted) {
global = updateChatLastMessage(global, chatId, newMessage);
}
if (selectIsMessageInCurrentMessageList(global, chatId, message as ApiMessage, tabId)) { if (selectIsMessageInCurrentMessageList(global, chatId, message as ApiMessage, tabId)) {
if (isLocal && message.isOutgoing && !(message.content?.action) && !storyReplyInfo?.storyId if (isLocal && message.isOutgoing && !(message.content?.action) && !storyReplyInfo?.storyId
&& !message.content?.storyData) { && !message.content?.storyData) {