put back quote a message logic with hook

This commit is contained in:
Audric Ackermann 2021-07-14 16:36:55 +10:00
parent 9a380b716b
commit a54345a42e
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4
11 changed files with 102 additions and 99 deletions

View File

@ -43,7 +43,7 @@ import autoBind from 'auto-bind';
import { AudioPlayerWithEncryptedFile } from './H5AudioPlayer';
import { ClickToTrustSender } from './message/ClickToTrustSender';
import { getMessageById } from '../../data/data';
import { deleteMessagesById } from '../../interactions/conversationInteractions';
import { deleteMessagesById, replyToMessage } from '../../interactions/conversationInteractions';
import { connect } from 'react-redux';
import { StateType } from '../../state/reducer';
import { getSelectedMessageIds } from '../../state/selectors/conversations';
@ -56,6 +56,7 @@ import {
} from '../../state/ducks/conversations';
import { saveAttachmentToDisk } from '../../util/attachmentsUtil';
import { LightBoxOptions } from '../session/conversation/SessionConversation';
import { pushUnblockToSend } from '../../session/utils/Toast';
// Same as MIN_WIDTH in ImageGrid.tsx
const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200;
@ -944,9 +945,12 @@ class MessageInner extends React.PureComponent<Props, State> {
}
private onReplyPrivate(e: any) {
if (this.props && this.props.onReply) {
this.props.onReply(this.props.timestamp);
if (this.props.isBlocked) {
pushUnblockToSend();
return;
}
void replyToMessage(this.props.id);
}
private async onAddModerator() {

View File

@ -45,6 +45,9 @@ import {
getItemById,
hasLinkPreviewPopupBeenDisplayed,
} from '../../../data/data';
import { getQuotedMessage } from '../../../state/selectors/conversations';
import { connect } from 'react-redux';
import { StateType } from '../../../state/reducer';
export interface ReplyingToMessageProps {
convoId: string;
@ -80,10 +83,7 @@ interface Props {
selectedConversationKey: string;
selectedConversation: ReduxConversationType | undefined;
isPublic: boolean;
quotedMessageProps?: ReplyingToMessageProps;
removeQuotedMessage: () => void;
stagedAttachments: Array<StagedAttachmentType>;
clearAttachments: () => any;
removeAttachment: (toRemove: AttachmentType) => void;
@ -135,7 +135,7 @@ const getDefaultState = () => {
};
};
export class SessionCompositionBox extends React.Component<Props, State> {
class SessionCompositionBoxInner extends React.Component<Props, State> {
private readonly textarea: React.RefObject<any>;
private readonly fileInput: React.RefObject<HTMLInputElement>;
private emojiPanel: any;
@ -188,7 +188,7 @@ export class SessionCompositionBox extends React.Component<Props, State> {
return (
<Flex flexDirection="column">
{this.renderQuotedMessage()}
<SessionQuotedMessageComposition />
{this.renderStagedLinkPreview()}
{this.renderAttachmentsStaged()}
<div className="composition-container">
@ -665,19 +665,6 @@ export class SessionCompositionBox extends React.Component<Props, State> {
});
}
private renderQuotedMessage() {
const { quotedMessageProps, removeQuotedMessage } = this.props;
if (quotedMessageProps?.id) {
return (
<SessionQuotedMessageComposition
quotedMessageProps={quotedMessageProps}
removeQuotedMessage={removeQuotedMessage}
/>
);
}
return <></>;
}
private onClickAttachment(attachment: AttachmentType) {
this.setState({ showCaptionEditor: attachment });
}
@ -839,6 +826,8 @@ export class SessionCompositionBox extends React.Component<Props, State> {
}
const { quotedMessageProps } = this.props;
console.warn('quotedMessageProps', quotedMessageProps);
const { stagedLinkPreview } = this.state;
// Send message
@ -999,3 +988,13 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.linkPreviewAbortController?.abort();
}
}
const mapStateToProps = (state: StateType) => {
return {
quotedMessageProps: getQuotedMessage(state),
};
};
const smart = connect(mapStateToProps);
export const SessionCompositionBox = smart(SessionCompositionBoxInner);

View File

@ -21,6 +21,7 @@ import { SessionFileDropzone } from './SessionFileDropzone';
import {
fetchMessagesForConversation,
PropsForMessage,
quoteMessage,
ReduxConversationType,
resetSelectedMessageIds,
showLightBox,
@ -44,10 +45,6 @@ interface State {
stagedAttachments: Array<StagedAttachmentType>;
isDraggingFile: boolean;
// quoted message
quotedMessageTimestamp?: number;
quotedMessageProps?: any;
}
export interface LightBoxOptions {
@ -151,8 +148,6 @@ export class SessionConversation extends React.Component<Props, State> {
showRecordingView: false,
stagedAttachments: [],
isDraggingFile: false,
quotedMessageProps: undefined,
quotedMessageTimestamp: undefined,
});
}
}
@ -174,7 +169,7 @@ export class SessionConversation extends React.Component<Props, State> {
// ~~~~~~~~~~~~~~ RENDER METHODS ~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public render() {
const { showRecordingView, quotedMessageProps, isDraggingFile, stagedAttachments } = this.state;
const { showRecordingView, isDraggingFile, stagedAttachments } = this.state;
const {
selectedConversation,
@ -210,6 +205,8 @@ export class SessionConversation extends React.Component<Props, State> {
(this.messageContainerRef
.current as any).scrollTop = this.messageContainerRef.current?.scrollHeight;
}
window.inboxStore?.dispatch(quoteMessage(undefined));
};
return (
@ -231,7 +228,7 @@ export class SessionConversation extends React.Component<Props, State> {
{lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)}
<div className="conversation-messages">
<SessionMessagesList {...this.getMessagesListProps()} />
<SessionMessagesList messageContainerRef={this.messageContainerRef} />
{showRecordingView && <div className="conversation-messages__blocking-overlay" />}
{isDraggingFile && <SessionFileDropzone />}
@ -249,10 +246,6 @@ export class SessionConversation extends React.Component<Props, State> {
stagedAttachments={stagedAttachments}
onLoadVoiceNoteView={this.onLoadVoiceNoteView}
onExitVoiceNoteView={this.onExitVoiceNoteView}
quotedMessageProps={quotedMessageProps}
removeQuotedMessage={() => {
void this.replyToMessage(undefined);
}}
clearAttachments={this.clearAttachments}
removeAttachment={this.removeAttachment}
onChoseAttachments={this.onChoseAttachments}
@ -292,13 +285,6 @@ export class SessionConversation extends React.Component<Props, State> {
);
}
public getMessagesListProps(): SessionMessageListProps {
return {
messageContainerRef: this.messageContainerRef,
replyToMessage: this.replyToMessage,
};
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~ MICROPHONE METHODS ~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -315,37 +301,6 @@ export class SessionConversation extends React.Component<Props, State> {
});
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~ MESSAGE QUOTE ~~~~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private async replyToMessage(quotedMessageTimestamp?: number) {
if (this.props.selectedConversation?.isBlocked) {
pushUnblockToSend();
return;
}
if (!_.isEqual(this.state.quotedMessageTimestamp, quotedMessageTimestamp)) {
const { messagesProps, selectedConversationKey } = this.props;
const conversationModel = getConversationController().getOrThrow(selectedConversationKey);
let quotedMessageProps = null;
if (quotedMessageTimestamp) {
const quotedMessage = messagesProps.find(
m =>
m.propsForMessage.timestamp === quotedMessageTimestamp ||
m.propsForMessage.serverTimestamp === quotedMessageTimestamp
);
if (quotedMessage) {
const quotedMessageModel = await getMessageById(quotedMessage.propsForMessage.id);
if (quotedMessageModel) {
quotedMessageProps = await conversationModel.makeQuote(quotedMessageModel);
}
}
}
this.setState({ quotedMessageTimestamp, quotedMessageProps });
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ~~~~~~~~~~~ KEYBOARD NAVIGATION ~~~~~~~~~~~~
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -52,8 +52,6 @@ interface State {
export type SessionMessageListProps = {
messageContainerRef: React.RefObject<any>;
replyToMessage: (messageId: number) => Promise<void>;
};
type Props = SessionMessageListProps & {
@ -138,10 +136,6 @@ const GenericMessageItem = (props: {
console.warn('FIXME audric');
if (!props.messageProps) {
debugger;
}
// const onQuoteClick = props.messageProps.propsForMessage.quote
// ? this.scrollToQuoteMessage
// : async () => {};
@ -152,7 +146,6 @@ const GenericMessageItem = (props: {
multiSelectMode,
// isQuotedMessageToAnimate: messageId === this.state.animateQuotedMessageId,
// nextMessageToPlay: this.state.nextMessageToPlay,
onReply: props.replyToMessage,
// playNextMessage: this.playNextMessage,
// onQuoteClick,
};

View File

@ -1,17 +1,13 @@
import React, { useContext } from 'react';
import React, { useCallback } from 'react';
import { Flex } from '../../basic/Flex';
import { SessionIcon, SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { ReplyingToMessageProps } from './SessionCompositionBox';
import styled, { DefaultTheme, ThemeContext } from 'styled-components';
import { getAlt, isAudio, isImageAttachment } from '../../../types/Attachment';
import styled, { useTheme } from 'styled-components';
import { getAlt, isAudio } from '../../../types/Attachment';
import { Image } from '../../conversation/Image';
import { AUDIO_MP3 } from '../../../types/MIME';
// tslint:disable: react-unused-props-and-state
interface Props {
quotedMessageProps: ReplyingToMessageProps;
removeQuotedMessage: any;
}
import { useDispatch, useSelector } from 'react-redux';
import { getQuotedMessage } from '../../../state/selectors/conversations';
import { quoteMessage } from '../../../state/ducks/conversations';
const QuotedMessageComposition = styled.div`
width: 100%;
@ -41,11 +37,13 @@ const ReplyingTo = styled.div`
color: ${props => props.theme.colors.textColor};
`;
export const SessionQuotedMessageComposition = (props: Props) => {
const { quotedMessageProps, removeQuotedMessage } = props;
const theme = useContext(ThemeContext);
export const SessionQuotedMessageComposition = () => {
const theme = useTheme();
const quotedMessageProps = useSelector(getQuotedMessage);
const { text: body, attachments } = quotedMessageProps;
const dispatch = useDispatch();
const { text: body, attachments } = quotedMessageProps || {};
const hasAttachments = attachments && attachments.length > 0;
let hasImageAttachment = false;
@ -61,6 +59,14 @@ export const SessionQuotedMessageComposition = (props: Props) => {
const hasAudioAttachment =
hasAttachments && attachments && attachments.length > 0 && isAudio(attachments);
const removeQuotedMessage = useCallback(() => {
dispatch(quoteMessage(undefined));
}, []);
if (!quotedMessageProps?.id) {
return null;
}
return (
<QuotedMessageComposition theme={theme}>
<Flex

View File

@ -34,6 +34,7 @@ import {
} from '../data/data';
import {
conversationReset,
quoteMessage,
resetSelectedMessageIds,
SortedMessageModelProps,
} from '../state/ducks/conversations';
@ -43,6 +44,7 @@ import { FSv2 } from '../fileserver';
import { fromBase64ToArray, toHex } from '../session/utils/String';
import { SessionButtonColor } from '../components/session/SessionButton';
import { perfEnd, perfStart } from '../session/utils/Performance';
import { ReplyingToMessageProps } from '../components/session/conversation/SessionCompositionBox';
export const getCompleteUrlForV2ConvoId = async (convoId: string) => {
if (convoId.match(openGroupV2ConversationIdRegex)) {
@ -532,3 +534,22 @@ export async function deleteMessagesById(
void doDelete();
}
}
export async function replyToMessage(messageId: string) {
const quotedMessageModel = await getMessageById(messageId);
if (!quotedMessageModel) {
window.log.warn('Failed to find message to reply to');
return;
}
const conversationModel = getConversationController().getOrThrow(
quotedMessageModel.get('conversationId')
);
const quotedMessageProps = await conversationModel.makeQuote(quotedMessageModel);
if (quotedMessageProps) {
window.inboxStore?.dispatch(quoteMessage(quotedMessageProps));
} else {
window.inboxStore?.dispatch(quoteMessage(undefined));
}
}

View File

@ -43,6 +43,7 @@ import { OpenGroupRequestCommonType } from '../opengroup/opengroupV2/ApiUtil';
import { getOpenGroupV2FromConversationId } from '../opengroup/utils/OpenGroupUtils';
import { createTaskWithTimeout } from '../session/utils/TaskWithTimeout';
import { perfEnd, perfStart } from '../session/utils/Performance';
import { ReplyingToMessageProps } from '../components/session/conversation/SessionCompositionBox';
export enum ConversationTypeEnum {
GROUP = 'group',
@ -566,17 +567,24 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return [];
}
public async makeQuote(quotedMessage: MessageModel) {
public async makeQuote(quotedMessage: MessageModel): Promise<ReplyingToMessageProps | null> {
const attachments = quotedMessage.get('attachments');
const preview = quotedMessage.get('preview');
const body = quotedMessage.get('body');
const quotedAttachments = await this.getQuoteAttachment(attachments, preview);
if (!quotedMessage.get('sent_at')) {
window.log.warn('tried to make a quote without a sent_at timestamp');
return null;
}
return {
author: quotedMessage.getSource(),
id: quotedMessage.get('sent_at'),
id: `${quotedMessage.get('sent_at')}` || '',
text: body,
attachments: quotedAttachments,
timestamp: quotedMessage.get('sent_at') || 0,
convoId: this.id,
};
}

View File

@ -529,6 +529,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const conversation = this.getConversation();
const isGroup = !!conversation && !conversation.isPrivate();
const isBlocked = conversation?.isBlocked() || false;
const isPublic = !!this.get('isPublic');
const isPublicOpenGroupV2 = isOpenGroupV2(this.getConversation()?.id || '');
@ -568,6 +569,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
expirationLength,
expirationTimestamp,
isPublic,
isBlocked,
isOpenGroupV2: isPublicOpenGroupV2,
isKickedFromGroup: conversation?.get('isKickedFromGroup'),
isTrustedForAttachmentDownload,
@ -774,9 +776,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
conversationType: ConversationTypeEnum.PRIVATE,
multiSelectMode: false,
firstMessageOfSeries: false,
onReply: noop,
// tslint:disable-next-line: no-async-without-await no-empty
onQuoteClick: async () => {},
},
errors,
contacts: sortedContacts || [],

View File

@ -237,6 +237,7 @@ export interface MessageRegularProps {
expirationTimestamp?: number;
convoId: string;
isPublic?: boolean;
isBlocked: boolean;
isOpenGroupV2?: boolean;
isKickedFromGroup: boolean;
// whether or not to show check boxes
@ -245,8 +246,6 @@ export interface MessageRegularProps {
isUnread: boolean;
isQuotedMessageToAnimate?: boolean;
isTrustedForAttachmentDownload: boolean;
onReply: (messagId: number) => void;
onQuoteClick: (options: QuoteClickOptions) => Promise<void>;
playableMessageIndex?: number;

View File

@ -16,6 +16,7 @@ import {
} from '../../models/messageType';
import { NotificationForConvoOption } from '../../components/conversation/ConversationHeader';
import { LightBoxOptions } from '../../components/session/conversation/SessionConversation';
import { ReplyingToMessageProps } from '../../components/session/conversation/SessionCompositionBox';
export type MessageModelProps = {
propsForMessage: PropsForMessage;
@ -181,6 +182,7 @@ export type PropsForMessage = {
isSenderAdmin: boolean;
isDeletable: boolean;
isExpired: boolean;
isBlocked: boolean;
};
export type LastMessageType = {
@ -235,6 +237,7 @@ export type ConversationsStateType = {
showRightPanel: boolean;
selectedMessageIds: Array<string>;
lightBox?: LightBoxOptions;
quotedMessage?: ReplyingToMessageProps;
};
async function getMessages(
@ -708,6 +711,8 @@ const conversationsSlice = createSlice({
state.selectedMessageIds = [];
state.selectedConversation = action.payload.id;
state.messages = [];
state.quotedMessage = undefined;
state.lightBox = undefined;
return state;
},
showLightBox(
@ -717,6 +722,13 @@ const conversationsSlice = createSlice({
state.lightBox = action.payload;
return state;
},
quoteMessage(
state: ConversationsStateType,
action: PayloadAction<ReplyingToMessageProps | undefined>
) {
state.quotedMessage = action.payload;
return state;
},
},
extraReducers: (builder: any) => {
// Add reducers for additional action types here, and handle loading state as needed
@ -762,4 +774,5 @@ export const {
resetSelectedMessageIds,
toggleSelectedMessageId,
showLightBox,
quoteMessage,
} = actions;

View File

@ -18,6 +18,7 @@ import {
ConversationHeaderTitleProps,
} from '../../components/conversation/ConversationHeader';
import { LightBoxOptions } from '../../components/session/conversation/SessionConversation';
import { ReplyingToMessageProps } from '../../components/session/conversation/SessionCompositionBox';
export const getConversations = (state: StateType): ConversationsStateType => state.conversations;
@ -289,3 +290,8 @@ export const getLightBoxOptions = createSelector(
getConversations,
(state: ConversationsStateType): LightBoxOptions | undefined => state.lightBox
);
export const getQuotedMessage = createSelector(
getConversations,
(state: ConversationsStateType): ReplyingToMessageProps | undefined => state.quotedMessage
);