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

View File

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

View File

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

View File

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

View File

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

View File

@ -105,8 +105,11 @@ export default function useChatListEntry({
}
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 (
<p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}>
<span className="draft">{lang('Draft')}</span>

View File

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

View File

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

View File

@ -71,7 +71,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
switch (update['@type']) {
case 'newMessage': {
const {
chatId, id, message, shouldForceReply,
chatId, id, message, shouldForceReply, wasDrafted,
} = update;
global = updateWithLocalMedia(global, chatId, id, message);
global = updateListedAndViewportIds(global, actions, message as ApiMessage);
@ -89,6 +89,11 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
Object.values(global.byTabId).forEach(({ id: tabId }) => {
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 (isLocal && message.isOutgoing && !(message.content?.action) && !storyReplyInfo?.storyId
&& !message.content?.storyData) {