Allow stage and send of video, even if we can't get screenshot
This commit is contained in:
		
					parent
					
						
							
								117cb074c7
							
						
					
				
			
			
				commit
				
					
						a024ee4b96
					
				
			
		
					 21 changed files with 224 additions and 143 deletions
				
			
		| 
						 | 
					@ -13,7 +13,7 @@ import { CompositionArea } from './CompositionArea';
 | 
				
			||||||
import { setupI18n } from '../util/setupI18n';
 | 
					import { setupI18n } from '../util/setupI18n';
 | 
				
			||||||
import enMessages from '../../_locales/en/messages.json';
 | 
					import enMessages from '../../_locales/en/messages.json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { fakeAttachment } from '../test-both/helpers/fakeAttachment';
 | 
					import { fakeDraftAttachment } from '../test-both/helpers/fakeAttachment';
 | 
				
			||||||
import { landscapeGreenUrl } from '../storybook/Fixtures';
 | 
					import { landscapeGreenUrl } from '../storybook/Fixtures';
 | 
				
			||||||
import { ThemeType } from '../types/Util';
 | 
					import { ThemeType } from '../types/Util';
 | 
				
			||||||
import { RecordingState } from '../state/ducks/audioRecorder';
 | 
					import { RecordingState } from '../state/ducks/audioRecorder';
 | 
				
			||||||
| 
						 | 
					@ -165,7 +165,7 @@ story.add('SMS-only', () => {
 | 
				
			||||||
story.add('Attachments', () => {
 | 
					story.add('Attachments', () => {
 | 
				
			||||||
  const props = createProps({
 | 
					  const props = createProps({
 | 
				
			||||||
    draftAttachments: [
 | 
					    draftAttachments: [
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: IMAGE_JPEG,
 | 
					        contentType: IMAGE_JPEG,
 | 
				
			||||||
        url: landscapeGreenUrl,
 | 
					        url: landscapeGreenUrl,
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,10 @@ import type { PropsType as GroupV2PendingApprovalActionsPropsType } from './conv
 | 
				
			||||||
import { GroupV2PendingApprovalActions } from './conversation/GroupV2PendingApprovalActions';
 | 
					import { GroupV2PendingApprovalActions } from './conversation/GroupV2PendingApprovalActions';
 | 
				
			||||||
import { AnnouncementsOnlyGroupBanner } from './AnnouncementsOnlyGroupBanner';
 | 
					import { AnnouncementsOnlyGroupBanner } from './AnnouncementsOnlyGroupBanner';
 | 
				
			||||||
import { AttachmentList } from './conversation/AttachmentList';
 | 
					import { AttachmentList } from './conversation/AttachmentList';
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					  InMemoryAttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../types/Attachment';
 | 
				
			||||||
import { isImageAttachment } from '../types/Attachment';
 | 
					import { isImageAttachment } from '../types/Attachment';
 | 
				
			||||||
import { AudioCapture } from './conversation/AudioCapture';
 | 
					import { AudioCapture } from './conversation/AudioCapture';
 | 
				
			||||||
import { CompositionUpload } from './CompositionUpload';
 | 
					import { CompositionUpload } from './CompositionUpload';
 | 
				
			||||||
| 
						 | 
					@ -67,11 +70,11 @@ export type OwnProps = Readonly<{
 | 
				
			||||||
  acceptedMessageRequest?: boolean;
 | 
					  acceptedMessageRequest?: boolean;
 | 
				
			||||||
  addAttachment: (
 | 
					  addAttachment: (
 | 
				
			||||||
    conversationId: string,
 | 
					    conversationId: string,
 | 
				
			||||||
    attachment: AttachmentType
 | 
					    attachment: InMemoryAttachmentDraftType
 | 
				
			||||||
  ) => unknown;
 | 
					  ) => unknown;
 | 
				
			||||||
  addPendingAttachment: (
 | 
					  addPendingAttachment: (
 | 
				
			||||||
    conversationId: string,
 | 
					    conversationId: string,
 | 
				
			||||||
    pendingAttachment: AttachmentType
 | 
					    pendingAttachment: AttachmentDraftType
 | 
				
			||||||
  ) => unknown;
 | 
					  ) => unknown;
 | 
				
			||||||
  announcementsOnly?: boolean;
 | 
					  announcementsOnly?: boolean;
 | 
				
			||||||
  areWeAdmin?: boolean;
 | 
					  areWeAdmin?: boolean;
 | 
				
			||||||
| 
						 | 
					@ -80,11 +83,11 @@ export type OwnProps = Readonly<{
 | 
				
			||||||
  cancelRecording: () => unknown;
 | 
					  cancelRecording: () => unknown;
 | 
				
			||||||
  completeRecording: (
 | 
					  completeRecording: (
 | 
				
			||||||
    conversationId: string,
 | 
					    conversationId: string,
 | 
				
			||||||
    onSendAudioRecording?: (rec: AttachmentType) => unknown
 | 
					    onSendAudioRecording?: (rec: InMemoryAttachmentDraftType) => unknown
 | 
				
			||||||
  ) => unknown;
 | 
					  ) => unknown;
 | 
				
			||||||
  compositionApi?: MutableRefObject<CompositionAPIType>;
 | 
					  compositionApi?: MutableRefObject<CompositionAPIType>;
 | 
				
			||||||
  conversationId: string;
 | 
					  conversationId: string;
 | 
				
			||||||
  draftAttachments: ReadonlyArray<AttachmentType>;
 | 
					  draftAttachments: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
  errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType;
 | 
					  errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType;
 | 
				
			||||||
  errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
 | 
					  errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
 | 
				
			||||||
  groupAdmins: Array<ConversationType>;
 | 
					  groupAdmins: Array<ConversationType>;
 | 
				
			||||||
| 
						 | 
					@ -105,11 +108,11 @@ export type OwnProps = Readonly<{
 | 
				
			||||||
  processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown;
 | 
					  processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown;
 | 
				
			||||||
  onSelectMediaQuality(isHQ: boolean): unknown;
 | 
					  onSelectMediaQuality(isHQ: boolean): unknown;
 | 
				
			||||||
  onSendMessage(options: {
 | 
					  onSendMessage(options: {
 | 
				
			||||||
    draftAttachments?: ReadonlyArray<AttachmentType>;
 | 
					    draftAttachments?: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
    mentions?: BodyRangesType;
 | 
					    mentions?: BodyRangesType;
 | 
				
			||||||
    message?: string;
 | 
					    message?: string;
 | 
				
			||||||
    timestamp?: number;
 | 
					    timestamp?: number;
 | 
				
			||||||
    voiceNoteAttachment?: AttachmentType;
 | 
					    voiceNoteAttachment?: InMemoryAttachmentDraftType;
 | 
				
			||||||
  }): unknown;
 | 
					  }): unknown;
 | 
				
			||||||
  openConversation(conversationId: string): unknown;
 | 
					  openConversation(conversationId: string): unknown;
 | 
				
			||||||
  quotedMessageProps?: Omit<
 | 
					  quotedMessageProps?: Omit<
 | 
				
			||||||
| 
						 | 
					@ -373,7 +376,9 @@ export const CompositionArea = ({
 | 
				
			||||||
      errorRecording={errorRecording}
 | 
					      errorRecording={errorRecording}
 | 
				
			||||||
      i18n={i18n}
 | 
					      i18n={i18n}
 | 
				
			||||||
      recordingState={recordingState}
 | 
					      recordingState={recordingState}
 | 
				
			||||||
      onSendAudioRecording={(voiceNoteAttachment: AttachmentType) => {
 | 
					      onSendAudioRecording={(
 | 
				
			||||||
 | 
					        voiceNoteAttachment: InMemoryAttachmentDraftType
 | 
				
			||||||
 | 
					      ) => {
 | 
				
			||||||
        onSendMessage({ voiceNoteAttachment });
 | 
					        onSendMessage({ voiceNoteAttachment });
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      startRecording={startRecording}
 | 
					      startRecording={startRecording}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,10 @@
 | 
				
			||||||
import type { ChangeEventHandler } from 'react';
 | 
					import type { ChangeEventHandler } from 'react';
 | 
				
			||||||
import React, { forwardRef, useState } from 'react';
 | 
					import React, { forwardRef, useState } from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  InMemoryAttachmentDraftType,
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../types/Attachment';
 | 
				
			||||||
import { AttachmentToastType } from '../types/AttachmentToastType';
 | 
					import { AttachmentToastType } from '../types/AttachmentToastType';
 | 
				
			||||||
import type { LocalizerType } from '../types/Util';
 | 
					import type { LocalizerType } from '../types/Util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,14 +22,14 @@ import type { HandleAttachmentsProcessingArgsType } from '../util/handleAttachme
 | 
				
			||||||
export type PropsType = {
 | 
					export type PropsType = {
 | 
				
			||||||
  addAttachment: (
 | 
					  addAttachment: (
 | 
				
			||||||
    conversationId: string,
 | 
					    conversationId: string,
 | 
				
			||||||
    attachment: AttachmentType
 | 
					    attachment: InMemoryAttachmentDraftType
 | 
				
			||||||
  ) => unknown;
 | 
					  ) => unknown;
 | 
				
			||||||
  addPendingAttachment: (
 | 
					  addPendingAttachment: (
 | 
				
			||||||
    conversationId: string,
 | 
					    conversationId: string,
 | 
				
			||||||
    pendingAttachment: AttachmentType
 | 
					    pendingAttachment: AttachmentDraftType
 | 
				
			||||||
  ) => unknown;
 | 
					  ) => unknown;
 | 
				
			||||||
  conversationId: string;
 | 
					  conversationId: string;
 | 
				
			||||||
  draftAttachments: ReadonlyArray<AttachmentType>;
 | 
					  draftAttachments: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
  processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown;
 | 
					  processAttachments: (options: HandleAttachmentsProcessingArgsType) => unknown;
 | 
				
			||||||
  removeAttachment: (conversationId: string, filePath: string) => unknown;
 | 
					  removeAttachment: (conversationId: string, filePath: string) => unknown;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions';
 | 
				
			||||||
import { text } from '@storybook/addon-knobs';
 | 
					import { text } from '@storybook/addon-knobs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import enMessages from '../../_locales/en/messages.json';
 | 
					import enMessages from '../../_locales/en/messages.json';
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type { AttachmentDraftType } from '../types/Attachment';
 | 
				
			||||||
import type { PropsType } from './ForwardMessageModal';
 | 
					import type { PropsType } from './ForwardMessageModal';
 | 
				
			||||||
import { ForwardMessageModal } from './ForwardMessageModal';
 | 
					import { ForwardMessageModal } from './ForwardMessageModal';
 | 
				
			||||||
import { IMAGE_JPEG, VIDEO_MP4, stringToMIMEType } from '../types/MIME';
 | 
					import { IMAGE_JPEG, VIDEO_MP4, stringToMIMEType } from '../types/MIME';
 | 
				
			||||||
| 
						 | 
					@ -16,15 +16,17 @@ import { getDefaultConversation } from '../test-both/helpers/getDefaultConversat
 | 
				
			||||||
import { setupI18n } from '../util/setupI18n';
 | 
					import { setupI18n } from '../util/setupI18n';
 | 
				
			||||||
import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
 | 
					import { StorybookThemeContext } from '../../.storybook/StorybookThemeContext';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createAttachment = (
 | 
					const createDraftAttachment = (
 | 
				
			||||||
  props: Partial<AttachmentType> = {}
 | 
					  props: Partial<AttachmentDraftType> = {}
 | 
				
			||||||
): AttachmentType => ({
 | 
					): AttachmentDraftType => ({
 | 
				
			||||||
 | 
					  pending: false,
 | 
				
			||||||
 | 
					  path: 'fileName.jpg',
 | 
				
			||||||
  contentType: stringToMIMEType(
 | 
					  contentType: stringToMIMEType(
 | 
				
			||||||
    text('attachment contentType', props.contentType || '')
 | 
					    text('attachment contentType', props.contentType || '')
 | 
				
			||||||
  ),
 | 
					  ),
 | 
				
			||||||
  fileName: text('attachment fileName', props.fileName || ''),
 | 
					  fileName: text('attachment fileName', props.fileName || ''),
 | 
				
			||||||
  screenshot: props.screenshot,
 | 
					  screenshotPath: props.pending === false ? props.screenshotPath : undefined,
 | 
				
			||||||
  url: text('attachment url', props.url || ''),
 | 
					  url: text('attachment url', props.pending === false ? props.url || '' : ''),
 | 
				
			||||||
  size: 3433,
 | 
					  size: 3433,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,7 +83,7 @@ story.add('link preview', () => {
 | 
				
			||||||
          date: Date.now(),
 | 
					          date: Date.now(),
 | 
				
			||||||
          domain: 'https://www.signal.org',
 | 
					          domain: 'https://www.signal.org',
 | 
				
			||||||
          url: 'signal.org',
 | 
					          url: 'signal.org',
 | 
				
			||||||
          image: createAttachment({
 | 
					          image: createDraftAttachment({
 | 
				
			||||||
            url: '/fixtures/kitten-4-112-112.jpg',
 | 
					            url: '/fixtures/kitten-4-112-112.jpg',
 | 
				
			||||||
            contentType: IMAGE_JPEG,
 | 
					            contentType: IMAGE_JPEG,
 | 
				
			||||||
          }),
 | 
					          }),
 | 
				
			||||||
| 
						 | 
					@ -99,22 +101,19 @@ story.add('media attachments', () => {
 | 
				
			||||||
    <ForwardMessageModal
 | 
					    <ForwardMessageModal
 | 
				
			||||||
      {...useProps({
 | 
					      {...useProps({
 | 
				
			||||||
        attachments: [
 | 
					        attachments: [
 | 
				
			||||||
          createAttachment({
 | 
					          createDraftAttachment({
 | 
				
			||||||
 | 
					            pending: true,
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					          createDraftAttachment({
 | 
				
			||||||
            contentType: IMAGE_JPEG,
 | 
					            contentType: IMAGE_JPEG,
 | 
				
			||||||
            fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
					            fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
            url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
					            url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
          }),
 | 
					          }),
 | 
				
			||||||
          createAttachment({
 | 
					          createDraftAttachment({
 | 
				
			||||||
            contentType: VIDEO_MP4,
 | 
					            contentType: VIDEO_MP4,
 | 
				
			||||||
            fileName: 'pixabay-Soap-Bubble-7141.mp4',
 | 
					            fileName: 'pixabay-Soap-Bubble-7141.mp4',
 | 
				
			||||||
            url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
 | 
					            url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
 | 
				
			||||||
            screenshot: {
 | 
					            screenshotPath: '/fixtures/kitten-4-112-112.jpg',
 | 
				
			||||||
              height: 112,
 | 
					 | 
				
			||||||
              width: 112,
 | 
					 | 
				
			||||||
              url: '/fixtures/kitten-4-112-112.jpg',
 | 
					 | 
				
			||||||
              contentType: IMAGE_JPEG,
 | 
					 | 
				
			||||||
              path: 'originalPath',
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          }),
 | 
					          }),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        messageBody: 'cats',
 | 
					        messageBody: 'cats',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,7 @@ import { animated } from '@react-spring/web';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import classNames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import { AttachmentList } from './conversation/AttachmentList';
 | 
					import { AttachmentList } from './conversation/AttachmentList';
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type { AttachmentDraftType } from '../types/Attachment';
 | 
				
			||||||
import { Button } from './Button';
 | 
					import { Button } from './Button';
 | 
				
			||||||
import type { InputApi } from './CompositionInput';
 | 
					import type { InputApi } from './CompositionInput';
 | 
				
			||||||
import { CompositionInput } from './CompositionInput';
 | 
					import { CompositionInput } from './CompositionInput';
 | 
				
			||||||
| 
						 | 
					@ -38,13 +38,13 @@ import { filterAndSortConversationsByRecent } from '../util/filterAndSortConvers
 | 
				
			||||||
import { useAnimated } from '../hooks/useAnimated';
 | 
					import { useAnimated } from '../hooks/useAnimated';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type DataPropsType = {
 | 
					export type DataPropsType = {
 | 
				
			||||||
  attachments?: Array<AttachmentType>;
 | 
					  attachments?: Array<AttachmentDraftType>;
 | 
				
			||||||
  candidateConversations: ReadonlyArray<ConversationType>;
 | 
					  candidateConversations: ReadonlyArray<ConversationType>;
 | 
				
			||||||
  conversationId: string;
 | 
					  conversationId: string;
 | 
				
			||||||
  doForwardMessage: (
 | 
					  doForwardMessage: (
 | 
				
			||||||
    selectedContacts: Array<string>,
 | 
					    selectedContacts: Array<string>,
 | 
				
			||||||
    messageBody?: string,
 | 
					    messageBody?: string,
 | 
				
			||||||
    attachments?: Array<AttachmentType>,
 | 
					    attachments?: Array<AttachmentDraftType>,
 | 
				
			||||||
    linkPreview?: LinkPreviewType
 | 
					    linkPreview?: LinkPreviewType
 | 
				
			||||||
  ) => void;
 | 
					  ) => void;
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
| 
						 | 
					@ -100,7 +100,9 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
 | 
				
			||||||
  const [filteredConversations, setFilteredConversations] = useState(
 | 
					  const [filteredConversations, setFilteredConversations] = useState(
 | 
				
			||||||
    filterAndSortConversationsByRecent(candidateConversations, '')
 | 
					    filterAndSortConversationsByRecent(candidateConversations, '')
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  const [attachmentsToForward, setAttachmentsToForward] = useState(attachments);
 | 
					  const [attachmentsToForward, setAttachmentsToForward] = useState<
 | 
				
			||||||
 | 
					    Array<AttachmentDraftType>
 | 
				
			||||||
 | 
					  >(attachments || []);
 | 
				
			||||||
  const [isEditingMessage, setIsEditingMessage] = useState(false);
 | 
					  const [isEditingMessage, setIsEditingMessage] = useState(false);
 | 
				
			||||||
  const [messageBodyText, setMessageBodyText] = useState(messageBody || '');
 | 
					  const [messageBodyText, setMessageBodyText] = useState(messageBody || '');
 | 
				
			||||||
  const [cannotMessage, setCannotMessage] = useState(false);
 | 
					  const [cannotMessage, setCannotMessage] = useState(false);
 | 
				
			||||||
| 
						 | 
					@ -322,7 +324,7 @@ export const ForwardMessageModal: FunctionComponent<PropsType> = ({
 | 
				
			||||||
                <AttachmentList
 | 
					                <AttachmentList
 | 
				
			||||||
                  attachments={attachmentsToForward}
 | 
					                  attachments={attachmentsToForward}
 | 
				
			||||||
                  i18n={i18n}
 | 
					                  i18n={i18n}
 | 
				
			||||||
                  onCloseAttachment={(attachment: AttachmentType) => {
 | 
					                  onCloseAttachment={(attachment: AttachmentDraftType) => {
 | 
				
			||||||
                    const newAttachments = attachmentsToForward.filter(
 | 
					                    const newAttachments = attachmentsToForward.filter(
 | 
				
			||||||
                      currentAttachment => currentAttachment !== attachment
 | 
					                      currentAttachment => currentAttachment !== attachment
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ import {
 | 
				
			||||||
import { setupI18n } from '../../util/setupI18n';
 | 
					import { setupI18n } from '../../util/setupI18n';
 | 
				
			||||||
import enMessages from '../../../_locales/en/messages.json';
 | 
					import enMessages from '../../../_locales/en/messages.json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { fakeAttachment } from '../../test-both/helpers/fakeAttachment';
 | 
					import { fakeDraftAttachment } from '../../test-both/helpers/fakeAttachment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const i18n = setupI18n('en', enMessages);
 | 
					const i18n = setupI18n('en', enMessages);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
 | 
				
			||||||
story.add('One File', () => {
 | 
					story.add('One File', () => {
 | 
				
			||||||
  const props = createProps({
 | 
					  const props = createProps({
 | 
				
			||||||
    attachments: [
 | 
					    attachments: [
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: IMAGE_JPEG,
 | 
					        contentType: IMAGE_JPEG,
 | 
				
			||||||
        fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
					        fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
        url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
					        url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
| 
						 | 
					@ -49,24 +49,18 @@ story.add('One File', () => {
 | 
				
			||||||
story.add('Multiple Visual Attachments', () => {
 | 
					story.add('Multiple Visual Attachments', () => {
 | 
				
			||||||
  const props = createProps({
 | 
					  const props = createProps({
 | 
				
			||||||
    attachments: [
 | 
					    attachments: [
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: IMAGE_JPEG,
 | 
					        contentType: IMAGE_JPEG,
 | 
				
			||||||
        fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
					        fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
        url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
					        url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: VIDEO_MP4,
 | 
					        contentType: VIDEO_MP4,
 | 
				
			||||||
        fileName: 'pixabay-Soap-Bubble-7141.mp4',
 | 
					        fileName: 'pixabay-Soap-Bubble-7141.mp4',
 | 
				
			||||||
        url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
 | 
					        url: '/fixtures/kitten-4-112-112.jpg',
 | 
				
			||||||
        screenshot: {
 | 
					        screenshotPath: '/fixtures/kitten-4-112-112.jpg',
 | 
				
			||||||
          height: 112,
 | 
					 | 
				
			||||||
          width: 112,
 | 
					 | 
				
			||||||
          url: '/fixtures/kitten-4-112-112.jpg',
 | 
					 | 
				
			||||||
          contentType: IMAGE_JPEG,
 | 
					 | 
				
			||||||
          path: 'originalpath',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: IMAGE_GIF,
 | 
					        contentType: IMAGE_GIF,
 | 
				
			||||||
        fileName: 'giphy-GVNv0UpeYm17e',
 | 
					        fileName: 'giphy-GVNv0UpeYm17e',
 | 
				
			||||||
        url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
 | 
					        url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
 | 
				
			||||||
| 
						 | 
					@ -80,34 +74,28 @@ story.add('Multiple Visual Attachments', () => {
 | 
				
			||||||
story.add('Multiple with Non-Visual Types', () => {
 | 
					story.add('Multiple with Non-Visual Types', () => {
 | 
				
			||||||
  const props = createProps({
 | 
					  const props = createProps({
 | 
				
			||||||
    attachments: [
 | 
					    attachments: [
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: IMAGE_JPEG,
 | 
					        contentType: IMAGE_JPEG,
 | 
				
			||||||
        fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
					        fileName: 'tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
        url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
					        url: '/fixtures/tina-rolf-269345-unsplash.jpg',
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: stringToMIMEType('text/plain'),
 | 
					        contentType: stringToMIMEType('text/plain'),
 | 
				
			||||||
        fileName: 'lorem-ipsum.txt',
 | 
					        fileName: 'lorem-ipsum.txt',
 | 
				
			||||||
        url: '/fixtures/lorem-ipsum.txt',
 | 
					        url: '/fixtures/lorem-ipsum.txt',
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: AUDIO_MP3,
 | 
					        contentType: AUDIO_MP3,
 | 
				
			||||||
        fileName: 'incompetech-com-Agnus-Dei-X.mp3',
 | 
					        fileName: 'incompetech-com-Agnus-Dei-X.mp3',
 | 
				
			||||||
        url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
 | 
					        url: '/fixtures/incompetech-com-Agnus-Dei-X.mp3',
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: VIDEO_MP4,
 | 
					        contentType: VIDEO_MP4,
 | 
				
			||||||
        fileName: 'pixabay-Soap-Bubble-7141.mp4',
 | 
					        fileName: 'pixabay-Soap-Bubble-7141.mp4',
 | 
				
			||||||
        url: '/fixtures/pixabay-Soap-Bubble-7141.mp4',
 | 
					        url: '/fixtures/kitten-4-112-112.jpg',
 | 
				
			||||||
        screenshot: {
 | 
					        screenshotPath: '/fixtures/kitten-4-112-112.jpg',
 | 
				
			||||||
          height: 112,
 | 
					 | 
				
			||||||
          width: 112,
 | 
					 | 
				
			||||||
          url: '/fixtures/kitten-4-112-112.jpg',
 | 
					 | 
				
			||||||
          contentType: IMAGE_JPEG,
 | 
					 | 
				
			||||||
          path: 'originalpath',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
      fakeAttachment({
 | 
					      fakeDraftAttachment({
 | 
				
			||||||
        contentType: IMAGE_GIF,
 | 
					        contentType: IMAGE_GIF,
 | 
				
			||||||
        fileName: 'giphy-GVNv0UpeYm17e',
 | 
					        fileName: 'giphy-GVNv0UpeYm17e',
 | 
				
			||||||
        url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
 | 
					        url: '/fixtures/giphy-GVNvOUpeYmI7e.gif',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,21 +7,20 @@ import { Image } from './Image';
 | 
				
			||||||
import { StagedGenericAttachment } from './StagedGenericAttachment';
 | 
					import { StagedGenericAttachment } from './StagedGenericAttachment';
 | 
				
			||||||
import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment';
 | 
					import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment';
 | 
				
			||||||
import type { LocalizerType } from '../../types/Util';
 | 
					import type { LocalizerType } from '../../types/Util';
 | 
				
			||||||
import type { AttachmentType } from '../../types/Attachment';
 | 
					import type { AttachmentDraftType } from '../../types/Attachment';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  areAllAttachmentsVisual,
 | 
					  areAllAttachmentsVisual,
 | 
				
			||||||
  getUrl,
 | 
					 | 
				
			||||||
  isImageAttachment,
 | 
					  isImageAttachment,
 | 
				
			||||||
  isVideoAttachment,
 | 
					  isVideoAttachment,
 | 
				
			||||||
} from '../../types/Attachment';
 | 
					} from '../../types/Attachment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Props = Readonly<{
 | 
					export type Props = Readonly<{
 | 
				
			||||||
  attachments: ReadonlyArray<AttachmentType>;
 | 
					  attachments: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
  onAddAttachment?: () => void;
 | 
					  onAddAttachment?: () => void;
 | 
				
			||||||
  onClickAttachment?: (attachment: AttachmentType) => void;
 | 
					  onClickAttachment?: (attachment: AttachmentDraftType) => void;
 | 
				
			||||||
  onClose?: () => void;
 | 
					  onClose?: () => void;
 | 
				
			||||||
  onCloseAttachment: (attachment: AttachmentType) => void;
 | 
					  onCloseAttachment: (attachment: AttachmentDraftType) => void;
 | 
				
			||||||
}>;
 | 
					}>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const IMAGE_WIDTH = 120;
 | 
					const IMAGE_WIDTH = 120;
 | 
				
			||||||
| 
						 | 
					@ -31,6 +30,14 @@ const IMAGE_HEIGHT = 120;
 | 
				
			||||||
const BLANK_VIDEO_THUMBNAIL =
 | 
					const BLANK_VIDEO_THUMBNAIL =
 | 
				
			||||||
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR42mNiAAAABgADm78GJQAAAABJRU5ErkJggg==';
 | 
					  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR42mNiAAAABgADm78GJQAAAABJRU5ErkJggg==';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getUrl(attachment: AttachmentDraftType): string | undefined {
 | 
				
			||||||
 | 
					  if (attachment.pending) {
 | 
				
			||||||
 | 
					    return undefined;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return attachment.url;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const AttachmentList = ({
 | 
					export const AttachmentList = ({
 | 
				
			||||||
  attachments,
 | 
					  attachments,
 | 
				
			||||||
  i18n,
 | 
					  i18n,
 | 
				
			||||||
| 
						 | 
					@ -65,11 +72,17 @@ export const AttachmentList = ({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const isImage = isImageAttachment(attachment);
 | 
					          const isImage = isImageAttachment(attachment);
 | 
				
			||||||
          const isVideo = isVideoAttachment(attachment);
 | 
					          const isVideo = isVideoAttachment(attachment);
 | 
				
			||||||
 | 
					          const closeAttachment = () => onCloseAttachment(attachment);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (isImage || isVideo || attachment.pending) {
 | 
					          if (isImage || isVideo || attachment.pending) {
 | 
				
			||||||
 | 
					            const isDownloaded = !attachment.pending;
 | 
				
			||||||
            const imageUrl =
 | 
					            const imageUrl =
 | 
				
			||||||
              url || (isVideo ? BLANK_VIDEO_THUMBNAIL : undefined);
 | 
					              url || (isVideo ? BLANK_VIDEO_THUMBNAIL : undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const clickAttachment = onClickAttachment
 | 
				
			||||||
 | 
					              ? () => onClickAttachment(attachment)
 | 
				
			||||||
 | 
					              : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return (
 | 
					            return (
 | 
				
			||||||
              <Image
 | 
					              <Image
 | 
				
			||||||
                key={key}
 | 
					                key={key}
 | 
				
			||||||
| 
						 | 
					@ -79,17 +92,16 @@ export const AttachmentList = ({
 | 
				
			||||||
                className="module-staged-attachment"
 | 
					                className="module-staged-attachment"
 | 
				
			||||||
                i18n={i18n}
 | 
					                i18n={i18n}
 | 
				
			||||||
                attachment={attachment}
 | 
					                attachment={attachment}
 | 
				
			||||||
 | 
					                isDownloaded={isDownloaded}
 | 
				
			||||||
                softCorners
 | 
					                softCorners
 | 
				
			||||||
                playIconOverlay={isVideo}
 | 
					                playIconOverlay={isVideo}
 | 
				
			||||||
                height={IMAGE_HEIGHT}
 | 
					                height={IMAGE_HEIGHT}
 | 
				
			||||||
                width={IMAGE_WIDTH}
 | 
					                width={IMAGE_WIDTH}
 | 
				
			||||||
                url={imageUrl}
 | 
					                url={imageUrl}
 | 
				
			||||||
                closeButton
 | 
					                closeButton
 | 
				
			||||||
                onClick={onClickAttachment}
 | 
					                onClick={clickAttachment}
 | 
				
			||||||
                onClickClose={onCloseAttachment}
 | 
					                onClickClose={closeAttachment}
 | 
				
			||||||
                onError={() => {
 | 
					                onError={closeAttachment}
 | 
				
			||||||
                  onCloseAttachment(attachment);
 | 
					 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
| 
						 | 
					@ -99,7 +111,7 @@ export const AttachmentList = ({
 | 
				
			||||||
              key={key}
 | 
					              key={key}
 | 
				
			||||||
              attachment={attachment}
 | 
					              attachment={attachment}
 | 
				
			||||||
              i18n={i18n}
 | 
					              i18n={i18n}
 | 
				
			||||||
              onClose={onCloseAttachment}
 | 
					              onClose={closeAttachment}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
        })}
 | 
					        })}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,10 @@ import React, { useCallback, useEffect, useState } from 'react';
 | 
				
			||||||
import * as moment from 'moment';
 | 
					import * as moment from 'moment';
 | 
				
			||||||
import { noop } from 'lodash';
 | 
					import { noop } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { AttachmentType } from '../../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					  InMemoryAttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../../types/Attachment';
 | 
				
			||||||
import { ConfirmationDialog } from '../ConfirmationDialog';
 | 
					import { ConfirmationDialog } from '../ConfirmationDialog';
 | 
				
			||||||
import type { LocalizerType } from '../../types/Util';
 | 
					import type { LocalizerType } from '../../types/Util';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
| 
						 | 
					@ -20,7 +23,7 @@ import {
 | 
				
			||||||
  useKeyboardShortcuts,
 | 
					  useKeyboardShortcuts,
 | 
				
			||||||
} from '../../hooks/useKeyboardShortcuts';
 | 
					} from '../../hooks/useKeyboardShortcuts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type OnSendAudioRecordingType = (rec: AttachmentType) => unknown;
 | 
					type OnSendAudioRecordingType = (rec: InMemoryAttachmentDraftType) => unknown;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type PropsType = {
 | 
					export type PropsType = {
 | 
				
			||||||
  cancelRecording: () => unknown;
 | 
					  cancelRecording: () => unknown;
 | 
				
			||||||
| 
						 | 
					@ -29,7 +32,7 @@ export type PropsType = {
 | 
				
			||||||
    conversationId: string,
 | 
					    conversationId: string,
 | 
				
			||||||
    onSendAudioRecording?: OnSendAudioRecordingType
 | 
					    onSendAudioRecording?: OnSendAudioRecordingType
 | 
				
			||||||
  ) => unknown;
 | 
					  ) => unknown;
 | 
				
			||||||
  draftAttachments: ReadonlyArray<AttachmentType>;
 | 
					  draftAttachments: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
  errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType;
 | 
					  errorDialogAudioRecorderType?: ErrorDialogAudioRecorderType;
 | 
				
			||||||
  errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
 | 
					  errorRecording: (e: ErrorDialogAudioRecorderType) => unknown;
 | 
				
			||||||
  i18n: LocalizerType;
 | 
					  i18n: LocalizerType;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,7 @@ export type Props = {
 | 
				
			||||||
  attachment: AttachmentType;
 | 
					  attachment: AttachmentType;
 | 
				
			||||||
  url?: string;
 | 
					  url?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  isDownloaded?: boolean;
 | 
				
			||||||
  className?: string;
 | 
					  className?: string;
 | 
				
			||||||
  height?: number;
 | 
					  height?: number;
 | 
				
			||||||
  width?: number;
 | 
					  width?: number;
 | 
				
			||||||
| 
						 | 
					@ -145,6 +146,7 @@ export class Image extends React.Component<Props> {
 | 
				
			||||||
      curveTopLeft,
 | 
					      curveTopLeft,
 | 
				
			||||||
      curveTopRight,
 | 
					      curveTopRight,
 | 
				
			||||||
      darkOverlay,
 | 
					      darkOverlay,
 | 
				
			||||||
 | 
					      isDownloaded,
 | 
				
			||||||
      height = 0,
 | 
					      height = 0,
 | 
				
			||||||
      i18n,
 | 
					      i18n,
 | 
				
			||||||
      noBackground,
 | 
					      noBackground,
 | 
				
			||||||
| 
						 | 
					@ -165,7 +167,9 @@ export class Image extends React.Component<Props> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { caption, pending } = attachment || { caption: null, pending: true };
 | 
					    const { caption, pending } = attachment || { caption: null, pending: true };
 | 
				
			||||||
    const canClick = this.canClick();
 | 
					    const canClick = this.canClick();
 | 
				
			||||||
    const imgNotDownloaded = hasNotDownloaded(attachment);
 | 
					    const imgNotDownloaded = isDownloaded
 | 
				
			||||||
 | 
					      ? false
 | 
				
			||||||
 | 
					      : hasNotDownloaded(attachment);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const resolvedBlurHash = blurHash || defaultBlurHash(theme);
 | 
					    const resolvedBlurHash = blurHash || defaultBlurHash(theme);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								ts/model-types.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								ts/model-types.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -22,7 +22,11 @@ import {
 | 
				
			||||||
} from './messages/MessageSendState';
 | 
					} from './messages/MessageSendState';
 | 
				
			||||||
import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions';
 | 
					import { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions';
 | 
				
			||||||
import { ConversationColorType } from './types/Colors';
 | 
					import { ConversationColorType } from './types/Colors';
 | 
				
			||||||
import { AttachmentType, ThumbnailType } from './types/Attachment';
 | 
					import {
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					  AttachmentType,
 | 
				
			||||||
 | 
					  ThumbnailType,
 | 
				
			||||||
 | 
					} from './types/Attachment';
 | 
				
			||||||
import { EmbeddedContactType } from './types/EmbeddedContact';
 | 
					import { EmbeddedContactType } from './types/EmbeddedContact';
 | 
				
			||||||
import { SignalService as Proto } from './protobuf';
 | 
					import { SignalService as Proto } from './protobuf';
 | 
				
			||||||
import { AvatarDataType } from './types/Avatar';
 | 
					import { AvatarDataType } from './types/Avatar';
 | 
				
			||||||
| 
						 | 
					@ -223,7 +227,7 @@ export type ConversationAttributesType = {
 | 
				
			||||||
  customColorId?: string;
 | 
					  customColorId?: string;
 | 
				
			||||||
  discoveredUnregisteredAt?: number;
 | 
					  discoveredUnregisteredAt?: number;
 | 
				
			||||||
  draftChanged?: boolean;
 | 
					  draftChanged?: boolean;
 | 
				
			||||||
  draftAttachments?: Array<AttachmentType>;
 | 
					  draftAttachments?: Array<AttachmentDraftType>;
 | 
				
			||||||
  draftBodyRanges?: Array<BodyRangeType>;
 | 
					  draftBodyRanges?: Array<BodyRangeType>;
 | 
				
			||||||
  draftTimestamp?: number | null;
 | 
					  draftTimestamp?: number | null;
 | 
				
			||||||
  inbox_position: number;
 | 
					  inbox_position: number;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@
 | 
				
			||||||
import type { ThunkAction } from 'redux-thunk';
 | 
					import type { ThunkAction } from 'redux-thunk';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as log from '../../logging/log';
 | 
					import * as log from '../../logging/log';
 | 
				
			||||||
import type { AttachmentType } from '../../types/Attachment';
 | 
					import type { InMemoryAttachmentDraftType } from '../../types/Attachment';
 | 
				
			||||||
import { SignalService as Proto } from '../../protobuf';
 | 
					import { SignalService as Proto } from '../../protobuf';
 | 
				
			||||||
import type { StateType as RootStateType } from '../reducer';
 | 
					import type { StateType as RootStateType } from '../reducer';
 | 
				
			||||||
import { fileToBytes } from '../../util/fileToBytes';
 | 
					import { fileToBytes } from '../../util/fileToBytes';
 | 
				
			||||||
| 
						 | 
					@ -129,7 +129,7 @@ function completeRecordingAction(): CompleteRecordingAction {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function completeRecording(
 | 
					function completeRecording(
 | 
				
			||||||
  conversationId: string,
 | 
					  conversationId: string,
 | 
				
			||||||
  onSendAudioRecording?: (rec: AttachmentType) => unknown
 | 
					  onSendAudioRecording?: (rec: InMemoryAttachmentDraftType) => unknown
 | 
				
			||||||
): ThunkAction<
 | 
					): ThunkAction<
 | 
				
			||||||
  void,
 | 
					  void,
 | 
				
			||||||
  RootStateType,
 | 
					  RootStateType,
 | 
				
			||||||
| 
						 | 
					@ -158,7 +158,8 @@ function completeRecording(
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      const data = await fileToBytes(blob);
 | 
					      const data = await fileToBytes(blob);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const voiceNoteAttachment = {
 | 
					      const voiceNoteAttachment: InMemoryAttachmentDraftType = {
 | 
				
			||||||
 | 
					        pending: false,
 | 
				
			||||||
        contentType: stringToMIMEType(blob.type),
 | 
					        contentType: stringToMIMEType(blob.type),
 | 
				
			||||||
        data,
 | 
					        data,
 | 
				
			||||||
        size: data.byteLength,
 | 
					        size: data.byteLength,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,10 @@ import type { ThunkAction } from 'redux-thunk';
 | 
				
			||||||
import * as log from '../../logging/log';
 | 
					import * as log from '../../logging/log';
 | 
				
			||||||
import type { NoopActionType } from './noop';
 | 
					import type { NoopActionType } from './noop';
 | 
				
			||||||
import type { StateType as RootStateType } from '../reducer';
 | 
					import type { StateType as RootStateType } from '../reducer';
 | 
				
			||||||
import type { AttachmentType } from '../../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					  InMemoryAttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../../types/Attachment';
 | 
				
			||||||
import type { MessageAttributesType } from '../../model-types.d';
 | 
					import type { MessageAttributesType } from '../../model-types.d';
 | 
				
			||||||
import type { LinkPreviewWithDomain } from '../../types/LinkPreview';
 | 
					import type { LinkPreviewWithDomain } from '../../types/LinkPreview';
 | 
				
			||||||
import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
 | 
					import { assignWithNoUnnecessaryAllocation } from '../../util/assignWithNoUnnecessaryAllocation';
 | 
				
			||||||
| 
						 | 
					@ -15,14 +18,14 @@ import { REMOVE_PREVIEW as REMOVE_LINK_PREVIEW } from './linkPreviews';
 | 
				
			||||||
import { writeDraftAttachment } from '../../util/writeDraftAttachment';
 | 
					import { writeDraftAttachment } from '../../util/writeDraftAttachment';
 | 
				
			||||||
import { deleteDraftAttachment } from '../../util/deleteDraftAttachment';
 | 
					import { deleteDraftAttachment } from '../../util/deleteDraftAttachment';
 | 
				
			||||||
import { replaceIndex } from '../../util/replaceIndex';
 | 
					import { replaceIndex } from '../../util/replaceIndex';
 | 
				
			||||||
import { resolveAttachmentOnDisk } from '../../util/resolveAttachmentOnDisk';
 | 
					import { resolveDraftAttachmentOnDisk } from '../../util/resolveDraftAttachmentOnDisk';
 | 
				
			||||||
import type { HandleAttachmentsProcessingArgsType } from '../../util/handleAttachmentsProcessing';
 | 
					import type { HandleAttachmentsProcessingArgsType } from '../../util/handleAttachmentsProcessing';
 | 
				
			||||||
import { handleAttachmentsProcessing } from '../../util/handleAttachmentsProcessing';
 | 
					import { handleAttachmentsProcessing } from '../../util/handleAttachmentsProcessing';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// State
 | 
					// State
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ComposerStateType = {
 | 
					export type ComposerStateType = {
 | 
				
			||||||
  attachments: ReadonlyArray<AttachmentType>;
 | 
					  attachments: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
  linkPreviewLoading: boolean;
 | 
					  linkPreviewLoading: boolean;
 | 
				
			||||||
  linkPreviewResult?: LinkPreviewWithDomain;
 | 
					  linkPreviewResult?: LinkPreviewWithDomain;
 | 
				
			||||||
  quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
 | 
					  quotedMessage?: Pick<MessageAttributesType, 'conversationId' | 'quote'>;
 | 
				
			||||||
| 
						 | 
					@ -40,12 +43,12 @@ const SET_QUOTED_MESSAGE = 'composer/SET_QUOTED_MESSAGE';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AddPendingAttachmentActionType = {
 | 
					type AddPendingAttachmentActionType = {
 | 
				
			||||||
  type: typeof ADD_PENDING_ATTACHMENT;
 | 
					  type: typeof ADD_PENDING_ATTACHMENT;
 | 
				
			||||||
  payload: AttachmentType;
 | 
					  payload: AttachmentDraftType;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ReplaceAttachmentsActionType = {
 | 
					type ReplaceAttachmentsActionType = {
 | 
				
			||||||
  type: typeof REPLACE_ATTACHMENTS;
 | 
					  type: typeof REPLACE_ATTACHMENTS;
 | 
				
			||||||
  payload: ReadonlyArray<AttachmentType>;
 | 
					  payload: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ResetComposerActionType = {
 | 
					type ResetComposerActionType = {
 | 
				
			||||||
| 
						 | 
					@ -99,14 +102,14 @@ export const actions = {
 | 
				
			||||||
// next in-memory store.
 | 
					// next in-memory store.
 | 
				
			||||||
function getAttachmentsFromConversationModel(
 | 
					function getAttachmentsFromConversationModel(
 | 
				
			||||||
  conversationId: string
 | 
					  conversationId: string
 | 
				
			||||||
): Array<AttachmentType> {
 | 
					): Array<AttachmentDraftType> {
 | 
				
			||||||
  const conversation = window.ConversationController.get(conversationId);
 | 
					  const conversation = window.ConversationController.get(conversationId);
 | 
				
			||||||
  return conversation?.get('draftAttachments') || [];
 | 
					  return conversation?.get('draftAttachments') || [];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function addAttachment(
 | 
					function addAttachment(
 | 
				
			||||||
  conversationId: string,
 | 
					  conversationId: string,
 | 
				
			||||||
  attachment: AttachmentType
 | 
					  attachment: InMemoryAttachmentDraftType
 | 
				
			||||||
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
 | 
					): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
 | 
				
			||||||
  return async (dispatch, getState) => {
 | 
					  return async (dispatch, getState) => {
 | 
				
			||||||
    // We do async operations first so multiple in-process addAttachments don't stomp on
 | 
					    // We do async operations first so multiple in-process addAttachments don't stomp on
 | 
				
			||||||
| 
						 | 
					@ -161,7 +164,7 @@ function addAttachment(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function addPendingAttachment(
 | 
					function addPendingAttachment(
 | 
				
			||||||
  conversationId: string,
 | 
					  conversationId: string,
 | 
				
			||||||
  pendingAttachment: AttachmentType
 | 
					  pendingAttachment: AttachmentDraftType
 | 
				
			||||||
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
 | 
					): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					  return (dispatch, getState) => {
 | 
				
			||||||
    const isSelectedConversation =
 | 
					    const isSelectedConversation =
 | 
				
			||||||
| 
						 | 
					@ -240,7 +243,7 @@ function removeAttachment(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function replaceAttachments(
 | 
					function replaceAttachments(
 | 
				
			||||||
  conversationId: string,
 | 
					  conversationId: string,
 | 
				
			||||||
  attachments: ReadonlyArray<AttachmentType>
 | 
					  attachments: ReadonlyArray<AttachmentDraftType>
 | 
				
			||||||
): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
 | 
					): ThunkAction<void, RootStateType, unknown, ReplaceAttachmentsActionType> {
 | 
				
			||||||
  return (dispatch, getState) => {
 | 
					  return (dispatch, getState) => {
 | 
				
			||||||
    // If the call came from a conversation we are no longer in we do not
 | 
					    // If the call came from a conversation we are no longer in we do not
 | 
				
			||||||
| 
						 | 
					@ -251,7 +254,7 @@ function replaceAttachments(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dispatch({
 | 
					    dispatch({
 | 
				
			||||||
      type: REPLACE_ATTACHMENTS,
 | 
					      type: REPLACE_ATTACHMENTS,
 | 
				
			||||||
      payload: attachments.map(resolveAttachmentOnDisk),
 | 
					      payload: attachments.map(resolveDraftAttachmentOnDisk),
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,15 +13,15 @@ import { getLinkPreview } from '../selectors/linkPreviews';
 | 
				
			||||||
import { getIntl, getTheme } from '../selectors/user';
 | 
					import { getIntl, getTheme } from '../selectors/user';
 | 
				
			||||||
import { getEmojiSkinTone } from '../selectors/items';
 | 
					import { getEmojiSkinTone } from '../selectors/items';
 | 
				
			||||||
import { selectRecentEmojis } from '../selectors/emojis';
 | 
					import { selectRecentEmojis } from '../selectors/emojis';
 | 
				
			||||||
import type { AttachmentType } from '../../types/Attachment';
 | 
					import type { AttachmentDraftType } from '../../types/Attachment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SmartForwardMessageModalProps = {
 | 
					export type SmartForwardMessageModalProps = {
 | 
				
			||||||
  attachments?: Array<AttachmentType>;
 | 
					  attachments?: Array<AttachmentDraftType>;
 | 
				
			||||||
  conversationId: string;
 | 
					  conversationId: string;
 | 
				
			||||||
  doForwardMessage: (
 | 
					  doForwardMessage: (
 | 
				
			||||||
    selectedContacts: Array<string>,
 | 
					    selectedContacts: Array<string>,
 | 
				
			||||||
    messageBody?: string,
 | 
					    messageBody?: string,
 | 
				
			||||||
    attachments?: Array<AttachmentType>,
 | 
					    attachments?: Array<AttachmentDraftType>,
 | 
				
			||||||
    linkPreview?: LinkPreviewType
 | 
					    linkPreview?: LinkPreviewType
 | 
				
			||||||
  ) => void;
 | 
					  ) => void;
 | 
				
			||||||
  isSticker: boolean;
 | 
					  isSticker: boolean;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,10 @@
 | 
				
			||||||
// Copyright 2021 Signal Messenger, LLC
 | 
					// Copyright 2021 Signal Messenger, LLC
 | 
				
			||||||
// SPDX-License-Identifier: AGPL-3.0-only
 | 
					// SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { AttachmentType } from '../../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  AttachmentType,
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../../types/Attachment';
 | 
				
			||||||
import { IMAGE_JPEG } from '../../types/MIME';
 | 
					import { IMAGE_JPEG } from '../../types/MIME';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const fakeAttachment = (
 | 
					export const fakeAttachment = (
 | 
				
			||||||
| 
						 | 
					@ -13,3 +16,13 @@ export const fakeAttachment = (
 | 
				
			||||||
  size: 10304,
 | 
					  size: 10304,
 | 
				
			||||||
  ...overrides,
 | 
					  ...overrides,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const fakeDraftAttachment = (
 | 
				
			||||||
 | 
					  overrides: Partial<AttachmentDraftType> = {}
 | 
				
			||||||
 | 
					): AttachmentDraftType => ({
 | 
				
			||||||
 | 
					  pending: false,
 | 
				
			||||||
 | 
					  contentType: IMAGE_JPEG,
 | 
				
			||||||
 | 
					  path: 'file.jpg',
 | 
				
			||||||
 | 
					  size: 10304,
 | 
				
			||||||
 | 
					  ...overrides,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,8 +9,8 @@ import { noopAction } from '../../../state/ducks/noop';
 | 
				
			||||||
import { reducer as rootReducer } from '../../../state/reducer';
 | 
					import { reducer as rootReducer } from '../../../state/reducer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { IMAGE_JPEG } from '../../../types/MIME';
 | 
					import { IMAGE_JPEG } from '../../../types/MIME';
 | 
				
			||||||
import type { AttachmentType } from '../../../types/Attachment';
 | 
					import type { AttachmentDraftType } from '../../../types/Attachment';
 | 
				
			||||||
import { fakeAttachment } from '../../helpers/fakeAttachment';
 | 
					import { fakeDraftAttachment } from '../../helpers/fakeAttachment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('both/state/ducks/composer', () => {
 | 
					describe('both/state/ducks/composer', () => {
 | 
				
			||||||
  const QUOTED_MESSAGE = {
 | 
					  const QUOTED_MESSAGE = {
 | 
				
			||||||
| 
						 | 
					@ -40,8 +40,13 @@ describe('both/state/ducks/composer', () => {
 | 
				
			||||||
      const { replaceAttachments } = actions;
 | 
					      const { replaceAttachments } = actions;
 | 
				
			||||||
      const dispatch = sinon.spy();
 | 
					      const dispatch = sinon.spy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const attachments: Array<AttachmentType> = [
 | 
					      const attachments: Array<AttachmentDraftType> = [
 | 
				
			||||||
        { contentType: IMAGE_JPEG, pending: false, url: '', size: 2433 },
 | 
					        {
 | 
				
			||||||
 | 
					          contentType: IMAGE_JPEG,
 | 
				
			||||||
 | 
					          pending: true,
 | 
				
			||||||
 | 
					          size: 2433,
 | 
				
			||||||
 | 
					          path: 'image.jpg',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
      ];
 | 
					      ];
 | 
				
			||||||
      replaceAttachments('123', attachments)(
 | 
					      replaceAttachments('123', attachments)(
 | 
				
			||||||
        dispatch,
 | 
					        dispatch,
 | 
				
			||||||
| 
						 | 
					@ -57,7 +62,7 @@ describe('both/state/ducks/composer', () => {
 | 
				
			||||||
    it('sets the high quality setting to false when there are no attachments', () => {
 | 
					    it('sets the high quality setting to false when there are no attachments', () => {
 | 
				
			||||||
      const { replaceAttachments } = actions;
 | 
					      const { replaceAttachments } = actions;
 | 
				
			||||||
      const dispatch = sinon.spy();
 | 
					      const dispatch = sinon.spy();
 | 
				
			||||||
      const attachments: Array<AttachmentType> = [];
 | 
					      const attachments: Array<AttachmentDraftType> = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      replaceAttachments('123', attachments)(
 | 
					      replaceAttachments('123', attachments)(
 | 
				
			||||||
        dispatch,
 | 
					        dispatch,
 | 
				
			||||||
| 
						 | 
					@ -83,7 +88,7 @@ describe('both/state/ducks/composer', () => {
 | 
				
			||||||
      const { replaceAttachments } = actions;
 | 
					      const { replaceAttachments } = actions;
 | 
				
			||||||
      const dispatch = sinon.spy();
 | 
					      const dispatch = sinon.spy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const attachments = [fakeAttachment()];
 | 
					      const attachments = [fakeDraftAttachment()];
 | 
				
			||||||
      replaceAttachments('123', attachments)(
 | 
					      replaceAttachments('123', attachments)(
 | 
				
			||||||
        dispatch,
 | 
					        dispatch,
 | 
				
			||||||
        getRootStateFunction('456'),
 | 
					        getRootStateFunction('456'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,40 +78,46 @@ export type DownloadedAttachmentType = AttachmentType & {
 | 
				
			||||||
export type BaseAttachmentDraftType = {
 | 
					export type BaseAttachmentDraftType = {
 | 
				
			||||||
  blurHash?: string;
 | 
					  blurHash?: string;
 | 
				
			||||||
  contentType: MIME.MIMEType;
 | 
					  contentType: MIME.MIMEType;
 | 
				
			||||||
  fileName: string;
 | 
					 | 
				
			||||||
  path: string;
 | 
					 | 
				
			||||||
  screenshotContentType?: string;
 | 
					  screenshotContentType?: string;
 | 
				
			||||||
  screenshotSize?: number;
 | 
					  screenshotSize?: number;
 | 
				
			||||||
  size: number;
 | 
					  size: number;
 | 
				
			||||||
 | 
					  flags?: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// An ephemeral attachment type, used between user's request to add the attachment as
 | 
				
			||||||
 | 
					//   a draft and final save on disk and in conversation.draftAttachments.
 | 
				
			||||||
export type InMemoryAttachmentDraftType =
 | 
					export type InMemoryAttachmentDraftType =
 | 
				
			||||||
  | ({
 | 
					  | ({
 | 
				
			||||||
      data?: Uint8Array;
 | 
					      data: Uint8Array;
 | 
				
			||||||
      pending: false;
 | 
					      pending: false;
 | 
				
			||||||
      screenshotData?: Uint8Array;
 | 
					      screenshotData?: Uint8Array;
 | 
				
			||||||
 | 
					      fileName?: string;
 | 
				
			||||||
 | 
					      path?: string;
 | 
				
			||||||
    } & BaseAttachmentDraftType)
 | 
					    } & BaseAttachmentDraftType)
 | 
				
			||||||
  | {
 | 
					  | {
 | 
				
			||||||
      contentType: MIME.MIMEType;
 | 
					      contentType: MIME.MIMEType;
 | 
				
			||||||
      fileName: string;
 | 
					      fileName?: string;
 | 
				
			||||||
      path: string;
 | 
					      path?: string;
 | 
				
			||||||
      pending: true;
 | 
					      pending: true;
 | 
				
			||||||
      size: number;
 | 
					      size: number;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// What's stored in conversation.draftAttachments
 | 
				
			||||||
export type AttachmentDraftType =
 | 
					export type AttachmentDraftType =
 | 
				
			||||||
  | ({
 | 
					  | ({
 | 
				
			||||||
      url: string;
 | 
					      url?: string;
 | 
				
			||||||
      screenshotPath?: string;
 | 
					      screenshotPath?: string;
 | 
				
			||||||
      pending: false;
 | 
					      pending: false;
 | 
				
			||||||
      // Old draft attachments may have a caption, though they are no longer editable
 | 
					      // Old draft attachments may have a caption, though they are no longer editable
 | 
				
			||||||
      //   because we removed the caption editor.
 | 
					      //   because we removed the caption editor.
 | 
				
			||||||
      caption?: string;
 | 
					      caption?: string;
 | 
				
			||||||
 | 
					      fileName?: string;
 | 
				
			||||||
 | 
					      path: string;
 | 
				
			||||||
    } & BaseAttachmentDraftType)
 | 
					    } & BaseAttachmentDraftType)
 | 
				
			||||||
  | {
 | 
					  | {
 | 
				
			||||||
      contentType: MIME.MIMEType;
 | 
					      contentType: MIME.MIMEType;
 | 
				
			||||||
      fileName: string;
 | 
					      fileName?: string;
 | 
				
			||||||
      path: string;
 | 
					      path?: string;
 | 
				
			||||||
      pending: true;
 | 
					      pending: true;
 | 
				
			||||||
      size: number;
 | 
					      size: number;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
| 
						 | 
					@ -614,6 +620,10 @@ export function getUrl(attachment: AttachmentType): string | undefined {
 | 
				
			||||||
    return attachment.screenshot.url;
 | 
					    return attachment.screenshot.url;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isVideoAttachment(attachment)) {
 | 
				
			||||||
 | 
					    return undefined;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return attachment.url;
 | 
					  return attachment.url;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,17 +6,20 @@ import {
 | 
				
			||||||
  preProcessAttachment,
 | 
					  preProcessAttachment,
 | 
				
			||||||
  processAttachment,
 | 
					  processAttachment,
 | 
				
			||||||
} from './processAttachment';
 | 
					} from './processAttachment';
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					  InMemoryAttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../types/Attachment';
 | 
				
			||||||
import { AttachmentToastType } from '../types/AttachmentToastType';
 | 
					import { AttachmentToastType } from '../types/AttachmentToastType';
 | 
				
			||||||
import * as log from '../logging/log';
 | 
					import * as log from '../logging/log';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AddAttachmentActionType = (
 | 
					export type AddAttachmentActionType = (
 | 
				
			||||||
  conversationId: string,
 | 
					  conversationId: string,
 | 
				
			||||||
  attachment: AttachmentType
 | 
					  attachment: InMemoryAttachmentDraftType
 | 
				
			||||||
) => unknown;
 | 
					) => unknown;
 | 
				
			||||||
export type AddPendingAttachmentActionType = (
 | 
					export type AddPendingAttachmentActionType = (
 | 
				
			||||||
  conversationId: string,
 | 
					  conversationId: string,
 | 
				
			||||||
  pendingAttachment: AttachmentType
 | 
					  pendingAttachment: AttachmentDraftType
 | 
				
			||||||
) => unknown;
 | 
					) => unknown;
 | 
				
			||||||
export type RemoveAttachmentActionType = (
 | 
					export type RemoveAttachmentActionType = (
 | 
				
			||||||
  conversationId: string,
 | 
					  conversationId: string,
 | 
				
			||||||
| 
						 | 
					@ -27,7 +30,7 @@ export type HandleAttachmentsProcessingArgsType = {
 | 
				
			||||||
  addAttachment: AddAttachmentActionType;
 | 
					  addAttachment: AddAttachmentActionType;
 | 
				
			||||||
  addPendingAttachment: AddPendingAttachmentActionType;
 | 
					  addPendingAttachment: AddPendingAttachmentActionType;
 | 
				
			||||||
  conversationId: string;
 | 
					  conversationId: string;
 | 
				
			||||||
  draftAttachments: ReadonlyArray<AttachmentType>;
 | 
					  draftAttachments: ReadonlyArray<AttachmentDraftType>;
 | 
				
			||||||
  files: ReadonlyArray<File>;
 | 
					  files: ReadonlyArray<File>;
 | 
				
			||||||
  onShowToast: (toastType: AttachmentToastType) => unknown;
 | 
					  onShowToast: (toastType: AttachmentToastType) => unknown;
 | 
				
			||||||
  removeAttachment: RemoveAttachmentActionType;
 | 
					  removeAttachment: RemoveAttachmentActionType;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,10 @@
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as log from '../logging/log';
 | 
					import * as log from '../logging/log';
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					  InMemoryAttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../types/Attachment';
 | 
				
			||||||
import { AttachmentToastType } from '../types/AttachmentToastType';
 | 
					import { AttachmentToastType } from '../types/AttachmentToastType';
 | 
				
			||||||
import { fileToBytes } from './fileToBytes';
 | 
					import { fileToBytes } from './fileToBytes';
 | 
				
			||||||
import { handleImageAttachment } from './handleImageAttachment';
 | 
					import { handleImageAttachment } from './handleImageAttachment';
 | 
				
			||||||
| 
						 | 
					@ -14,7 +17,9 @@ import { isFileDangerous } from './isFileDangerous';
 | 
				
			||||||
import { isHeic, isImage, stringToMIMEType } from '../types/MIME';
 | 
					import { isHeic, isImage, stringToMIMEType } from '../types/MIME';
 | 
				
			||||||
import { isImageTypeSupported, isVideoTypeSupported } from './GoogleChrome';
 | 
					import { isImageTypeSupported, isVideoTypeSupported } from './GoogleChrome';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getPendingAttachment(file: File): AttachmentType | undefined {
 | 
					export function getPendingAttachment(
 | 
				
			||||||
 | 
					  file: File
 | 
				
			||||||
 | 
					): AttachmentDraftType | undefined {
 | 
				
			||||||
  if (!file) {
 | 
					  if (!file) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -33,7 +38,7 @@ export function getPendingAttachment(file: File): AttachmentType | undefined {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function preProcessAttachment(
 | 
					export function preProcessAttachment(
 | 
				
			||||||
  file: File,
 | 
					  file: File,
 | 
				
			||||||
  draftAttachments: Array<AttachmentType>
 | 
					  draftAttachments: Array<AttachmentDraftType>
 | 
				
			||||||
): AttachmentToastType | undefined {
 | 
					): AttachmentToastType | undefined {
 | 
				
			||||||
  if (!file) {
 | 
					  if (!file) {
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
| 
						 | 
					@ -53,7 +58,7 @@ export function preProcessAttachment(
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const haveNonImage = draftAttachments.some(
 | 
					  const haveNonImage = draftAttachments.some(
 | 
				
			||||||
    (attachment: AttachmentType) => !isImage(attachment.contentType)
 | 
					    (attachment: AttachmentDraftType) => !isImage(attachment.contentType)
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
  // You can't add another attachment if you already have a non-image staged
 | 
					  // You can't add another attachment if you already have a non-image staged
 | 
				
			||||||
  if (haveNonImage) {
 | 
					  if (haveNonImage) {
 | 
				
			||||||
| 
						 | 
					@ -72,10 +77,10 @@ export function preProcessAttachment(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function processAttachment(
 | 
					export async function processAttachment(
 | 
				
			||||||
  file: File
 | 
					  file: File
 | 
				
			||||||
): Promise<AttachmentType | void> {
 | 
					): Promise<InMemoryAttachmentDraftType | void> {
 | 
				
			||||||
  const fileType = stringToMIMEType(file.type);
 | 
					  const fileType = stringToMIMEType(file.type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let attachment: AttachmentType;
 | 
					  let attachment: InMemoryAttachmentDraftType;
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    if (isImageTypeSupported(fileType) || isHeic(fileType)) {
 | 
					    if (isImageTypeSupported(fileType) || isHeic(fileType)) {
 | 
				
			||||||
      attachment = await handleImageAttachment(file);
 | 
					      attachment = await handleImageAttachment(file);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,11 +4,12 @@
 | 
				
			||||||
import { pick } from 'lodash';
 | 
					import { pick } from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import * as log from '../logging/log';
 | 
					import * as log from '../logging/log';
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type { AttachmentDraftType } from '../types/Attachment';
 | 
				
			||||||
 | 
					import { isVideoAttachment } from '../types/Attachment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function resolveAttachmentOnDisk(
 | 
					export function resolveDraftAttachmentOnDisk(
 | 
				
			||||||
  attachment: AttachmentType
 | 
					  attachment: AttachmentDraftType
 | 
				
			||||||
): AttachmentType {
 | 
					): AttachmentDraftType {
 | 
				
			||||||
  let url = '';
 | 
					  let url = '';
 | 
				
			||||||
  if (attachment.pending) {
 | 
					  if (attachment.pending) {
 | 
				
			||||||
    return attachment;
 | 
					    return attachment;
 | 
				
			||||||
| 
						 | 
					@ -18,7 +19,7 @@ export function resolveAttachmentOnDisk(
 | 
				
			||||||
    url = window.Signal.Migrations.getAbsoluteDraftPath(
 | 
					    url = window.Signal.Migrations.getAbsoluteDraftPath(
 | 
				
			||||||
      attachment.screenshotPath
 | 
					      attachment.screenshotPath
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  } else if (attachment.path) {
 | 
					  } else if (!isVideoAttachment(attachment) && attachment.path) {
 | 
				
			||||||
    url = window.Signal.Migrations.getAbsoluteDraftPath(attachment.path);
 | 
					    url = window.Signal.Migrations.getAbsoluteDraftPath(attachment.path);
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    log.warn(
 | 
					    log.warn(
 | 
				
			||||||
| 
						 | 
					@ -2,28 +2,32 @@
 | 
				
			||||||
// SPDX-License-Identifier: AGPL-3.0-only
 | 
					// SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { omit } from 'lodash';
 | 
					import { omit } from 'lodash';
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type {
 | 
				
			||||||
 | 
					  InMemoryAttachmentDraftType,
 | 
				
			||||||
 | 
					  AttachmentDraftType,
 | 
				
			||||||
 | 
					} from '../types/Attachment';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function writeDraftAttachment(
 | 
					export async function writeDraftAttachment(
 | 
				
			||||||
  attachment: AttachmentType
 | 
					  attachment: InMemoryAttachmentDraftType
 | 
				
			||||||
): Promise<AttachmentType> {
 | 
					): Promise<AttachmentDraftType> {
 | 
				
			||||||
  if (attachment.pending) {
 | 
					  if (attachment.pending) {
 | 
				
			||||||
    throw new Error('writeDraftAttachment: Cannot write pending attachment');
 | 
					    throw new Error('writeDraftAttachment: Cannot write pending attachment');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const result: AttachmentType = {
 | 
					  const path = await window.Signal.Migrations.writeNewDraftData(
 | 
				
			||||||
 | 
					    attachment.data
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const screenshotPath = attachment.screenshotData
 | 
				
			||||||
 | 
					    ? await window.Signal.Migrations.writeNewDraftData(
 | 
				
			||||||
 | 
					        attachment.screenshotData
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
    ...omit(attachment, ['data', 'screenshotData']),
 | 
					    ...omit(attachment, ['data', 'screenshotData']),
 | 
				
			||||||
 | 
					    path,
 | 
				
			||||||
 | 
					    screenshotPath,
 | 
				
			||||||
    pending: false,
 | 
					    pending: false,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  if (attachment.data) {
 | 
					 | 
				
			||||||
    result.path = await window.Signal.Migrations.writeNewDraftData(
 | 
					 | 
				
			||||||
      attachment.data
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (attachment.screenshotData) {
 | 
					 | 
				
			||||||
    result.screenshotPath = await window.Signal.Migrations.writeNewDraftData(
 | 
					 | 
				
			||||||
      attachment.screenshotData
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return result;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ import { batch as batchDispatch } from 'react-redux';
 | 
				
			||||||
import { debounce, flatten, omit, throttle } from 'lodash';
 | 
					import { debounce, flatten, omit, throttle } from 'lodash';
 | 
				
			||||||
import { render } from 'mustache';
 | 
					import { render } from 'mustache';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import type { AttachmentType } from '../types/Attachment';
 | 
					import type { AttachmentDraftType, AttachmentType } from '../types/Attachment';
 | 
				
			||||||
import { isGIF } from '../types/Attachment';
 | 
					import { isGIF } from '../types/Attachment';
 | 
				
			||||||
import * as Attachment from '../types/Attachment';
 | 
					import * as Attachment from '../types/Attachment';
 | 
				
			||||||
import type { StickerPackType as StickerPackDBType } from '../sql/Interface';
 | 
					import type { StickerPackType as StickerPackDBType } from '../sql/Interface';
 | 
				
			||||||
| 
						 | 
					@ -1561,16 +1561,32 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const attachments = getAttachmentsForMessage(message.attributes);
 | 
					    const attachments = getAttachmentsForMessage(message.attributes);
 | 
				
			||||||
 | 
					    const draftAttachments = attachments
 | 
				
			||||||
 | 
					      .map((item: AttachmentType): AttachmentDraftType | null => {
 | 
				
			||||||
 | 
					        const { path } = item;
 | 
				
			||||||
 | 
					        if (!path) {
 | 
				
			||||||
 | 
					          return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          ...item,
 | 
				
			||||||
 | 
					          path,
 | 
				
			||||||
 | 
					          pending: false as const,
 | 
				
			||||||
 | 
					          screenshotPath: item.screenshot?.path,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      .filter(isNotNil);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.forwardMessageModal = new Whisper.ReactWrapperView({
 | 
					    this.forwardMessageModal = new Whisper.ReactWrapperView({
 | 
				
			||||||
      JSX: window.Signal.State.Roots.createForwardMessageModal(
 | 
					      JSX: window.Signal.State.Roots.createForwardMessageModal(
 | 
				
			||||||
        window.reduxStore,
 | 
					        window.reduxStore,
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          attachments,
 | 
					          attachments: draftAttachments,
 | 
				
			||||||
          conversationId: this.model.id,
 | 
					          conversationId: this.model.id,
 | 
				
			||||||
          doForwardMessage: async (
 | 
					          doForwardMessage: async (
 | 
				
			||||||
            conversationIds: Array<string>,
 | 
					            conversationIds: Array<string>,
 | 
				
			||||||
            messageBody?: string,
 | 
					            messageBody?: string,
 | 
				
			||||||
            includedAttachments?: Array<AttachmentType>,
 | 
					            includedAttachments?: Array<AttachmentDraftType>,
 | 
				
			||||||
            linkPreview?: LinkPreviewType
 | 
					            linkPreview?: LinkPreviewType
 | 
				
			||||||
          ) => {
 | 
					          ) => {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
| 
						 | 
					@ -1619,7 +1635,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
 | 
				
			||||||
    message: MessageModel,
 | 
					    message: MessageModel,
 | 
				
			||||||
    conversationIds: Array<string>,
 | 
					    conversationIds: Array<string>,
 | 
				
			||||||
    messageBody?: string,
 | 
					    messageBody?: string,
 | 
				
			||||||
    attachments?: Array<AttachmentType>,
 | 
					    attachments?: Array<AttachmentDraftType>,
 | 
				
			||||||
    linkPreview?: LinkPreviewType
 | 
					    linkPreview?: LinkPreviewType
 | 
				
			||||||
  ): Promise<boolean> {
 | 
					  ): Promise<boolean> {
 | 
				
			||||||
    log.info(`maybeForwardMessage/${message.idForLogging()}: Starting...`);
 | 
					    log.info(`maybeForwardMessage/${message.idForLogging()}: Starting...`);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue