diff --git a/ts/components/LightboxGallery.tsx b/ts/components/LightboxGallery.tsx index 83f4a03bc..0b1ad4cf5 100644 --- a/ts/components/LightboxGallery.tsx +++ b/ts/components/LightboxGallery.tsx @@ -10,7 +10,9 @@ import { AttachmentTypeWithPath } from '../types/Attachment'; // tslint:disable-next-line: no-submodule-imports import useKey from 'react-use/lib/useKey'; import { showLightBox } from '../state/ducks/conversations'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; +import { saveAttachmentToDisk } from '../util/attachmentsUtil'; +import { getSelectedConversationKey } from '../state/selectors/conversations'; export interface MediaItemType { objectURL?: string; @@ -25,13 +27,13 @@ export interface MediaItemType { type Props = { media: Array; - onSave?: (saveData: MediaItemType) => void; selectedIndex: number; }; export const LightboxGallery = (props: Props) => { - const { media, onSave } = props; + const { media } = props; const [currentIndex, setCurrentIndex] = useState(-1); + const selectedConversation = useSelector(getSelectedConversationKey) as string; const dispatch = useDispatch(); @@ -56,12 +58,8 @@ export const LightboxGallery = (props: Props) => { }, [currentIndex, lastIndex]); const handleSave = useCallback(() => { - if (!onSave) { - return; - } - const mediaItem = media[currentIndex]; - onSave(mediaItem); + void saveAttachmentToDisk({ ...mediaItem, conversationId: selectedConversation }); }, [currentIndex, media]); useKey( @@ -96,14 +94,13 @@ export const LightboxGallery = (props: Props) => { const objectURL = selectedMedia?.objectURL || 'images/alert-outline.svg'; const { attachment } = selectedMedia; - const saveCallback = onSave ? handleSave : undefined; const caption = attachment?.caption; return ( // tslint:disable: use-simple-attributes ; // onError: () => void; onClickAttachment: (attachment: AttachmentType) => void; onCloseAttachment: (attachment: AttachmentType) => void; onAddAttachment: () => void; onClose: () => void; -} +}; const IMAGE_WIDTH = 120; const IMAGE_HEIGHT = 120; -export class AttachmentList extends React.Component { - // tslint:disable-next-line max-func-body-length */ - public render() { - const { - attachments, - onAddAttachment, - onClickAttachment, - onCloseAttachment, - onClose, - } = this.props; +export const AttachmentList = (props: Props) => { + const { attachments, onAddAttachment, onClickAttachment, onCloseAttachment, onClose } = props; - if (!attachments.length) { - return null; - } - - const allVisualAttachments = areAllAttachmentsVisual(attachments); - - return ( -
- {attachments.length > 1 ? ( -
-
-
- ) : null} -
- {(attachments || []).map((attachment, index) => { - const { contentType } = attachment; - if (isImageTypeSupported(contentType) || isVideoTypeSupported(contentType)) { - const imageKey = getUrl(attachment) || attachment.fileName || index; - const clickCallback = attachments.length > 1 ? onClickAttachment : undefined; + if (!attachments.length) { + return null; + } - return ( - {window.i18n('stagedImageAttachment', - ); - } + const allVisualAttachments = areAllAttachmentsVisual(attachments); - const genericKey = getUrl(attachment) || attachment.fileName || index; + return ( +
+ {attachments.length > 1 ? ( +
+
+
+ ) : null} +
+ {(attachments || []).map((attachment, index) => { + const { contentType } = attachment; + if (isImageTypeSupported(contentType) || isVideoTypeSupported(contentType)) { + const imageKey = getUrl(attachment) || attachment.fileName || index; + const clickCallback = attachments.length > 1 ? onClickAttachment : undefined; return ( - ); - })} - {allVisualAttachments ? : null} -
+ } + + const genericKey = getUrl(attachment) || attachment.fileName || index; + + return ( + + ); + })} + {allVisualAttachments ? : null}
- ); - } -} +
+ ); +}; diff --git a/ts/components/conversation/Image.tsx b/ts/components/conversation/Image.tsx index 76dc1c687..9ce7142f6 100644 --- a/ts/components/conversation/Image.tsx +++ b/ts/components/conversation/Image.tsx @@ -2,12 +2,12 @@ import React from 'react'; import classNames from 'classnames'; import { Spinner } from '../basic/Spinner'; -import { AttachmentType } from '../../types/Attachment'; +import { AttachmentTypeWithPath } from '../../types/Attachment'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; type Props = { alt: string; - attachment: AttachmentType; + attachment: AttachmentTypeWithPath; url: string; height?: number; @@ -28,8 +28,8 @@ type Props = { playIconOverlay?: boolean; softCorners?: boolean; - onClick?: (attachment: AttachmentType) => void; - onClickClose?: (attachment: AttachmentType) => void; + onClick?: (attachment: AttachmentTypeWithPath) => void; + onClickClose?: (attachment: AttachmentTypeWithPath) => void; onError?: () => void; }; diff --git a/ts/components/conversation/ImageGrid.tsx b/ts/components/conversation/ImageGrid.tsx index 6b74da56c..a12a8b3d4 100644 --- a/ts/components/conversation/ImageGrid.tsx +++ b/ts/components/conversation/ImageGrid.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { areAllAttachmentsVisual, - AttachmentType, + AttachmentTypeWithPath, getAlt, getImageDimensions, getThumbnailUrl, @@ -14,13 +14,13 @@ import { import { Image } from './Image'; type Props = { - attachments: Array; + attachments: Array; withContentAbove?: boolean; withContentBelow?: boolean; bottomOverlay?: boolean; onError: () => void; - onClickAttachment?: (attachment: AttachmentType) => void; + onClickAttachment?: (attachment: AttachmentTypeWithPath) => void; }; export const ImageGrid = (props: Props) => { diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index a1e5e3e6a..2af472cdf 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -10,6 +10,7 @@ import { ContactName } from './ContactName'; import { Quote } from './Quote'; import { + AttachmentTypeWithPath, canDisplayImage, getExtensionForDisplay, getGridDimensions, @@ -43,11 +44,18 @@ import { AudioPlayerWithEncryptedFile } from './H5AudioPlayer'; import { ClickToTrustSender } from './message/ClickToTrustSender'; import { getMessageById } from '../../data/data'; import { deleteMessagesById } from '../../interactions/conversationInteractions'; -import { getSelectedMessage } from '../../state/selectors/search'; import { connect } from 'react-redux'; import { StateType } from '../../state/reducer'; import { getSelectedMessageIds } from '../../state/selectors/conversations'; -import { showMessageDetailsView, toggleSelectedMessageId } from '../../state/ducks/conversations'; +import { + PropsForAttachment, + PropsForMessage, + showLightBox, + showMessageDetailsView, + toggleSelectedMessageId, +} from '../../state/ducks/conversations'; +import { saveAttachmentToDisk } from '../../util/attachmentsUtil'; +import { LightBoxOptions } from '../session/conversation/SessionConversation'; // Same as MIN_WIDTH in ImageGrid.tsx const MINIMUM_LINK_PREVIEW_IMAGE_WIDTH = 200; @@ -63,6 +71,41 @@ const EXPIRED_DELAY = 600; type Props = MessageRegularProps & { selectedMessages: Array }; +const onClickAttachment = async (onClickProps: { + attachment: AttachmentTypeWithPath; + messageId: string; +}) => { + let index = -1; + + const found = await getMessageById(onClickProps.messageId); + if (!found) { + window.log.warn('Such message not found'); + return; + } + const msgAttachments = found.getPropsForMessage().attachments; + + const media = (msgAttachments || []).map(attachmentForMedia => { + index++; + const messageTimestamp = + found.get('timestamp') || found.get('serverTimestamp') || found.get('received_at'); + + return { + index: _.clone(index), + objectURL: attachmentForMedia.url || undefined, + contentType: attachmentForMedia.contentType, + attachment: attachmentForMedia, + messageSender: found.getSource(), + messageTimestamp, + messageId: onClickProps.messageId, + }; + }); + const lightBoxOptions: LightBoxOptions = { + media: media as any, + attachment: onClickProps.attachment, + }; + window.inboxStore?.dispatch(showLightBox(lightBoxOptions)); +}; + class MessageInner extends React.PureComponent { public expirationCheckInterval: any; public expiredTimeout: any; @@ -150,7 +193,6 @@ class MessageInner extends React.PureComponent { conversationType, direction, quote, - onClickAttachment, multiSelectMode, isTrustedForAttachmentDownload, } = this.props; @@ -191,11 +233,14 @@ class MessageInner extends React.PureComponent { withContentBelow={withContentBelow} bottomOverlay={!collapseMetadata} onError={this.handleImageError} - onClickAttachment={(attachment: AttachmentType) => { + onClickAttachment={(attachment: AttachmentTypeWithPath) => { if (multiSelectMode) { window.inboxStore?.dispatch(toggleSelectedMessageId(id)); - } else if (onClickAttachment) { - onClickAttachment(attachment); + } else { + void onClickAttachment({ + attachment, + messageId: id, + }); } }} /> @@ -241,10 +286,15 @@ class MessageInner extends React.PureComponent { role="button" className="module-message__generic-attachment__icon" onClick={(e: any) => { - if (this.props?.onDownload) { - e.stopPropagation(); - this.props.onDownload(firstAttachment); - } + e.stopPropagation(); + + const messageTimestamp = this.props.timestamp || this.props.serverTimestamp || 0; + void saveAttachmentToDisk({ + attachment: firstAttachment, + messageTimestamp, + messageSender: this.props.authorPhoneNumber, + conversationId: this.props.convoId, + }); }} > {extension ? ( @@ -520,7 +570,6 @@ class MessageInner extends React.PureComponent { status, isDeletable, id, - onDownload, isPublic, isOpenGroupV2, weAreAdmin, @@ -567,9 +616,14 @@ class MessageInner extends React.PureComponent { {!multipleAttachments && attachments && attachments[0] ? ( { - if (onDownload) { - onDownload(attachments[0]); - } + e.stopPropagation(); + const messageTimestamp = this.props.timestamp || this.props.serverTimestamp || 0; + void saveAttachmentToDisk({ + attachment: attachments[0], + messageTimestamp, + messageSender: this.props.authorPhoneNumber, + conversationId: this.props.convoId, + }); }} > {window.i18n('downloadAttachment')} diff --git a/ts/components/conversation/media-gallery/DocumentListItem.tsx b/ts/components/conversation/media-gallery/DocumentListItem.tsx index 36795c5f1..ed5d8eee8 100644 --- a/ts/components/conversation/media-gallery/DocumentListItem.tsx +++ b/ts/components/conversation/media-gallery/DocumentListItem.tsx @@ -10,6 +10,7 @@ import { AttachmentTypeWithPath, save } from '../../../types/Attachment'; import { MediaItemType } from '../../LightboxGallery'; import { useSelector } from 'react-redux'; import { getSelectedConversationKey } from '../../../state/selectors/conversations'; +import { saveAttachmentToDisk } from '../../../util/attachmentsUtil'; type Props = { // Required @@ -22,27 +23,6 @@ type Props = { mediaItem: MediaItemType; }; -const saveAttachment = async ({ - attachment, - messageTimestamp, - messageSender, - conversationId, -}: { - attachment: AttachmentTypeWithPath; - messageTimestamp: number; - messageSender: string; - conversationId: string; -}) => { - attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType); - save({ - attachment, - document, - getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath, - timestamp: messageTimestamp, - }); - await sendDataExtractionNotification(conversationId, messageSender, messageTimestamp); -}; - export const DocumentListItem = (props: Props) => { const { shouldShowSeparator, fileName, fileSize, timestamp } = props; @@ -50,7 +30,7 @@ export const DocumentListItem = (props: Props) => { const selectedConversationKey = useSelector(getSelectedConversationKey) as string; const saveAttachmentCallback = useCallback(() => { - void saveAttachment({ + void saveAttachmentToDisk({ messageSender: props.mediaItem.messageSender, messageTimestamp: props.mediaItem.messageTimestamp, attachment: props.mediaItem.attachment, diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index 86fddc66b..db8b6b259 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -84,7 +84,6 @@ interface Props { quotedMessageProps?: ReplyingToMessageProps; removeQuotedMessage: () => void; - textarea: React.RefObject; stagedAttachments: Array; clearAttachments: () => any; removeAttachment: (toRemove: AttachmentType) => void; @@ -149,7 +148,7 @@ export class SessionCompositionBox extends React.Component { super(props); this.state = getDefaultState(); - this.textarea = props.textarea; + this.textarea = React.createRef(); this.fileInput = React.createRef(); // Emojis diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index daafe49be..0b48b8ef4 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -70,7 +70,6 @@ interface Props { } export class SessionConversation extends React.Component { - private readonly compositionBoxRef: React.RefObject; private readonly messageContainerRef: React.RefObject; private dragCounter: number; private publicMembersRefreshTimeout?: NodeJS.Timeout; @@ -87,7 +86,6 @@ export class SessionConversation extends React.Component { stagedAttachments: [], isDraggingFile: false, }; - this.compositionBoxRef = React.createRef(); this.messageContainerRef = React.createRef(); this.dragCounter = 0; this.updateMemberList = _.debounce(this.updateMemberListBouncy.bind(this), 1000); @@ -255,7 +253,6 @@ export class SessionConversation extends React.Component { removeQuotedMessage={() => { void this.replyToMessage(undefined); }} - textarea={this.compositionBoxRef} clearAttachments={this.clearAttachments} removeAttachment={this.removeAttachment} onChoseAttachments={this.onChoseAttachments} @@ -296,14 +293,9 @@ export class SessionConversation extends React.Component { } public getMessagesListProps(): SessionMessageListProps { - const { messagesProps } = this.props; - return { - messagesProps, messageContainerRef: this.messageContainerRef, replyToMessage: this.replyToMessage, - onClickAttachment: this.onClickAttachment, - onDownloadAttachment: this.saveAttachment, }; } @@ -350,36 +342,10 @@ export class SessionConversation extends React.Component { } } } - this.setState({ quotedMessageTimestamp, quotedMessageProps }, () => { - this.compositionBoxRef.current?.focus(); - }); + this.setState({ quotedMessageTimestamp, quotedMessageProps }); } } - private onClickAttachment(attachment: AttachmentTypeWithPath, propsForMessage: PropsForMessage) { - let index = -1; - const media = (propsForMessage.attachments || []).map(attachmentForMedia => { - index++; - const messageTimestamp = - propsForMessage.timestamp || propsForMessage.serverTimestamp || propsForMessage.receivedAt; - - return { - index: _.clone(index), - objectURL: attachmentForMedia.url || undefined, - contentType: attachmentForMedia.contentType, - attachment: attachmentForMedia, - messageSender: propsForMessage.authorPhoneNumber, - messageTimestamp, - messageId: propsForMessage.id, - }; - }); - const lightBoxOptions: LightBoxOptions = { - media: media as any, - attachment, - }; - window.inboxStore?.dispatch(showLightBox(lightBoxOptions)); - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~ KEYBOARD NAVIGATION ~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -470,35 +436,7 @@ export class SessionConversation extends React.Component { ? media.findIndex(mediaMessage => mediaMessage.attachment.path === attachment.path) : 0; console.warn('renderLightBox', { media, attachment }); - return ( - - ); - } - - // THIS DOES NOT DOWNLOAD ANYTHING! it just saves it where the user wants - private async saveAttachment({ - attachment, - messageTimestamp, - messageSender, - }: { - attachment: AttachmentType; - messageTimestamp: number; - messageSender: string; - }) { - const { getAbsoluteAttachmentPath } = window.Signal.Migrations; - attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType); - save({ - attachment, - document, - getAbsolutePath: getAbsoluteAttachmentPath, - timestamp: messageTimestamp, - }); - - await sendDataExtractionNotification( - this.props.selectedConversationKey, - messageSender, - messageTimestamp - ); + return ; } private async onChoseAttachments(attachmentsFileList: Array) { diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index cdda6f551..3431ea6f3 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -7,11 +7,15 @@ import { SessionScrollButton } from '../SessionScrollButton'; import { Constants } from '../../../session'; import _ from 'lodash'; import { contextMenu } from 'react-contexify'; -import { AttachmentType } from '../../../types/Attachment'; +import { AttachmentType, AttachmentTypeWithPath } from '../../../types/Attachment'; import { GroupNotification } from '../../conversation/GroupNotification'; import { GroupInvitation } from '../../conversation/GroupInvitation'; import { fetchMessagesForConversation, + PropsForExpirationTimer, + PropsForGroupInvitation, + PropsForGroupUpdate, + PropsForMessage, ReduxConversationType, SortedMessageModelProps, } from '../../../state/ducks/conversations'; @@ -20,18 +24,25 @@ import { ToastUtils } from '../../../session/utils'; import { TypingBubble } from '../../conversation/TypingBubble'; import { getConversationController } from '../../../session/conversations'; import { MessageModel } from '../../../models/message'; -import { MessageRegularProps, QuoteClickOptions } from '../../../models/messageType'; +import { + MessageRegularProps, + PropsForDataExtractionNotification, + QuoteClickOptions, +} from '../../../models/messageType'; import { getMessagesBySentAt } from '../../../data/data'; import autoBind from 'auto-bind'; import { ConversationTypeEnum } from '../../../models/conversation'; import { DataExtractionNotification } from '../../conversation/DataExtractionNotification'; import { StateType } from '../../../state/reducer'; -import { connect } from 'react-redux'; +import { connect, useSelector } from 'react-redux'; import { + getMessagesOfSelectedConversation, getSelectedConversation, getSelectedConversationKey, getSelectedMessageIds, + isMessageSelectionMode, } from '../../../state/selectors/conversations'; +import { saveAttachmentToDisk } from '../../../util/attachmentsUtil'; interface State { showScrollButton: boolean; @@ -39,24 +50,125 @@ interface State { nextMessageToPlay: number | undefined; } +export type SessionMessageListProps = { + messageContainerRef: React.RefObject; + + replyToMessage: (messageId: number) => Promise; +}; + type Props = SessionMessageListProps & { conversationKey?: string; - selectedMessages: Array; + messagesProps: Array; conversation?: ReduxConversationType; }; -export type SessionMessageListProps = { - messagesProps: Array; - messageContainerRef: React.RefObject; +const UnreadIndicator = (props: { messageId: string; show: boolean }) => ( + +); + +const GroupUpdateItem = (props: { + messageId: string; + groupNotificationProps: PropsForGroupUpdate; + showUnreadIndicator: boolean; +}) => { + return ( + + + + + ); +}; - replyToMessage: (messageId: number) => Promise; - onClickAttachment: (attachment: any, message: any) => void; - onDownloadAttachment: (toDownload: { - attachment: any; - messageTimestamp: number; - messageSender: string; - }) => void; +const GroupInvitationItem = (props: { + messageId: string; + propsForGroupInvitation: PropsForGroupInvitation; + showUnreadIndicator: boolean; +}) => { + return ( + + + + + + ); +}; + +const DataExtractionNotificationItem = (props: { + messageId: string; + propsForDataExtractionNotification: PropsForDataExtractionNotification; + showUnreadIndicator: boolean; +}) => { + return ( + + + + + + ); +}; + +const TimerNotificationItem = (props: { + messageId: string; + timerProps: PropsForExpirationTimer; + showUnreadIndicator: boolean; +}) => { + return ( + + + + + + ); +}; + +const GenericMessageItem = (props: { + messageId: string; + messageProps: SortedMessageModelProps; + playableMessageIndex?: number; + showUnreadIndicator: boolean; +}) => { + const multiSelectMode = useSelector(isMessageSelectionMode); + // const selectedConversation = useSelector(getSelectedConversationKey) as string; + + const messageId = props.messageId; + + console.warn('FIXME audric'); + + if (!props.messageProps) { + debugger; + } + + // const onQuoteClick = props.messageProps.propsForMessage.quote + // ? this.scrollToQuoteMessage + // : async () => {}; + + const regularProps: MessageRegularProps = { + ...props.messageProps.propsForMessage, + // firstMessageOfSeries, + multiSelectMode, + // isQuotedMessageToAnimate: messageId === this.state.animateQuotedMessageId, + // nextMessageToPlay: this.state.nextMessageToPlay, + onReply: props.replyToMessage, + // playNextMessage: this.playNextMessage, + // onQuoteClick, + }; + + return ( + + + + + ); }; class SessionMessagesListInner extends React.Component { @@ -171,8 +283,7 @@ class SessionMessagesListInner extends React.Component { } private renderMessages() { - const { selectedMessages, messagesProps } = this.props; - const multiSelectMode = Boolean(selectedMessages.length); + const { messagesProps } = this.props; let playableMessageIndex = 0; return ( @@ -185,62 +296,56 @@ class SessionMessagesListInner extends React.Component { const groupNotificationProps = messageProps.propsForGroupNotification; - // IF we found the last read message + // IF we found the first unread message // AND we are not scrolled all the way to the bottom // THEN, show the unread banner for the current message const showUnreadIndicator = Boolean(messageProps.firstUnread) && this.getScrollOffsetBottomPx() !== 0; - const unreadIndicator = ( - - ); if (groupNotificationProps) { return ( - - - {unreadIndicator} - + ); } if (propsForGroupInvitation) { return ( - - - {unreadIndicator} - + ); } if (propsForDataExtractionNotification) { return ( - - - {unreadIndicator} - + ); } if (timerProps) { return ( - - - {unreadIndicator} - + ); } + if (!messageProps) { return; } @@ -250,68 +355,19 @@ class SessionMessagesListInner extends React.Component { // firstMessageOfSeries tells us to render the avatar only for the first message // in a series of messages from the same user return ( - - {this.renderMessage( - messageProps, - messageProps.firstMessageOfSeries, - multiSelectMode, - playableMessageIndex - )} - {unreadIndicator} - + ); })} ); } - private renderMessage( - messageProps: SortedMessageModelProps, - firstMessageOfSeries: boolean, - multiSelectMode: boolean, - playableMessageIndex: number - ) { - const messageId = messageProps.propsForMessage.id; - - const onClickAttachment = (attachment: AttachmentType) => { - this.props.onClickAttachment(attachment, messageProps.propsForMessage); - }; - - // tslint:disable-next-line: no-async-without-await - const onQuoteClick = messageProps.propsForMessage.quote - ? this.scrollToQuoteMessage - : async () => {}; - - const onDownload = (attachment: AttachmentType) => { - const messageTimestamp = - messageProps.propsForMessage.timestamp || - messageProps.propsForMessage.serverTimestamp || - messageProps.propsForMessage.receivedAt || - 0; - this.props.onDownloadAttachment({ - attachment, - messageTimestamp, - messageSender: messageProps.propsForMessage.authorPhoneNumber, - }); - }; - - const regularProps: MessageRegularProps = { - ...messageProps.propsForMessage, - firstMessageOfSeries, - multiSelectMode, - isQuotedMessageToAnimate: messageId === this.state.animateQuotedMessageId, - nextMessageToPlay: this.state.nextMessageToPlay, - playableMessageIndex, - onReply: this.props.replyToMessage, - onClickAttachment, - onDownload, - playNextMessage: this.playNextMessage, - onQuoteClick, - }; - - return ; - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~ MESSAGE HANDLING ~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -586,9 +642,9 @@ class SessionMessagesListInner extends React.Component { const mapStateToProps = (state: StateType) => { return { - selectedMessages: getSelectedMessageIds(state), conversationKey: getSelectedConversationKey(state), conversation: getSelectedConversation(state), + messagesProps: getMessagesOfSelectedConversation(state), }; }; diff --git a/ts/models/message.ts b/ts/models/message.ts index 4a8bc2c96..86b56c6d8 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -774,9 +774,7 @@ export class MessageModel extends Backbone.Model { conversationType: ConversationTypeEnum.PRIVATE, multiSelectMode: false, firstMessageOfSeries: false, - onClickAttachment: noop, onReply: noop, - onDownload: noop, // tslint:disable-next-line: no-async-without-await no-empty onQuoteClick: async () => {}, }, diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index cebcecfc1..50f26c4c3 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -2,7 +2,7 @@ import { DefaultTheme } from 'styled-components'; import _ from 'underscore'; import { v4 as uuidv4 } from 'uuid'; import { QuotedAttachmentType } from '../components/conversation/Quote'; -import { AttachmentType } from '../types/Attachment'; +import { AttachmentType, AttachmentTypeWithPath } from '../types/Attachment'; import { Contact } from '../types/Contact'; import { ConversationTypeEnum } from './conversation'; @@ -219,7 +219,7 @@ export interface MessageRegularProps { /** Note: this should be formatted for display */ authorPhoneNumber: string; conversationType: ConversationTypeEnum; - attachments?: Array; + attachments?: Array; quote?: { text: string; attachment?: QuotedAttachmentType; @@ -246,9 +246,7 @@ export interface MessageRegularProps { isQuotedMessageToAnimate?: boolean; isTrustedForAttachmentDownload: boolean; - onClickAttachment: (attachment: AttachmentType) => void; onReply: (messagId: number) => void; - onDownload: (attachment: AttachmentType) => void; onQuoteClick: (options: QuoteClickOptions) => Promise; playableMessageIndex?: number; diff --git a/ts/util/attachmentsUtil.ts b/ts/util/attachmentsUtil.ts index cb1cc90f3..7c01438a6 100644 --- a/ts/util/attachmentsUtil.ts +++ b/ts/util/attachmentsUtil.ts @@ -2,6 +2,9 @@ import { StagedAttachmentType } from '../components/session/conversation/Session import { SignalService } from '../protobuf'; import { Constants } from '../session'; import loadImage from 'blueimp-load-image'; +import { getDecryptedMediaUrl } from '../session/crypto/DecryptedAttachmentsManager'; +import { sendDataExtractionNotification } from '../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage'; +import { AttachmentType, AttachmentTypeWithPath, save } from '../types/Attachment'; export interface MaxScaleSize { maxSize?: number; maxHeight?: number; @@ -135,3 +138,24 @@ export async function readFile(attachment: any): Promise { FR.readAsArrayBuffer(attachment.file); }); } + +export const saveAttachmentToDisk = async ({ + attachment, + messageTimestamp, + messageSender, + conversationId, +}: { + attachment: AttachmentType; + messageTimestamp: number; + messageSender: string; + conversationId: string; +}) => { + const decryptedUrl = await getDecryptedMediaUrl(attachment.url, attachment.contentType); + save({ + attachment: { ...attachment, url: decryptedUrl }, + document, + getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath, + timestamp: messageTimestamp, + }); + await sendDataExtractionNotification(conversationId, messageSender, messageTimestamp); +};