|
|
|
@ -1,5 +1,5 @@
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import _, { debounce } from 'lodash';
|
|
|
|
|
import _, { debounce, isEmpty } from 'lodash';
|
|
|
|
|
|
|
|
|
|
import * as MIME from '../../../types/MIME';
|
|
|
|
|
|
|
|
|
@ -46,7 +46,10 @@ import { Flex } from '../../basic/Flex';
|
|
|
|
|
import { CaptionEditor } from '../../CaptionEditor';
|
|
|
|
|
import { StagedAttachmentList } from '../StagedAttachmentList';
|
|
|
|
|
import { processNewAttachment } from '../../../types/MessageAttachment';
|
|
|
|
|
import { StagedAttachmentImportedType } from '../../../util/attachmentsUtil';
|
|
|
|
|
import {
|
|
|
|
|
StagedAttachmentImportedType,
|
|
|
|
|
StagedPreviewImportedType,
|
|
|
|
|
} from '../../../util/attachmentsUtil';
|
|
|
|
|
|
|
|
|
|
export interface ReplyingToMessageProps {
|
|
|
|
|
convoId: string;
|
|
|
|
@ -57,12 +60,20 @@ export interface ReplyingToMessageProps {
|
|
|
|
|
attachments?: Array<any>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type StagedLinkPreviewImage = {
|
|
|
|
|
data: ArrayBuffer;
|
|
|
|
|
size: number;
|
|
|
|
|
width: number;
|
|
|
|
|
height: number;
|
|
|
|
|
contentType: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export interface StagedLinkPreviewData {
|
|
|
|
|
isLoaded: boolean;
|
|
|
|
|
title: string | null;
|
|
|
|
|
url: string | null;
|
|
|
|
|
domain: string | null;
|
|
|
|
|
image?: AttachmentType;
|
|
|
|
|
image?: StagedLinkPreviewImage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface StagedAttachmentType extends AttachmentType {
|
|
|
|
@ -592,25 +603,6 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|
|
|
|
|
|
|
|
|
getPreview(firstLink, abortController.signal)
|
|
|
|
|
.then(ret => {
|
|
|
|
|
let image: AttachmentType | undefined;
|
|
|
|
|
if (ret) {
|
|
|
|
|
if (ret.image?.width) {
|
|
|
|
|
if (ret.image) {
|
|
|
|
|
const blob = new Blob([ret.image.data], {
|
|
|
|
|
type: ret.image.contentType,
|
|
|
|
|
});
|
|
|
|
|
const imageAttachment = {
|
|
|
|
|
...ret.image,
|
|
|
|
|
url: URL.createObjectURL(blob),
|
|
|
|
|
fileName: 'preview',
|
|
|
|
|
fileSize: null,
|
|
|
|
|
screenshot: null,
|
|
|
|
|
thumbnail: null,
|
|
|
|
|
};
|
|
|
|
|
image = imageAttachment;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// we finished loading the preview, and checking the abortConrtoller, we are still not aborted.
|
|
|
|
|
// => update the staged preview
|
|
|
|
|
if (this.linkPreviewAbortController && !this.linkPreviewAbortController.signal.aborted) {
|
|
|
|
@ -620,7 +612,7 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|
|
|
|
title: ret?.title || null,
|
|
|
|
|
url: ret?.url || null,
|
|
|
|
|
domain: (ret?.url && window.Signal.LinkPreviews.getDomain(ret.url)) || '',
|
|
|
|
|
image,
|
|
|
|
|
image: ret?.image,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} else if (this.linkPreviewAbortController) {
|
|
|
|
@ -827,20 +819,19 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|
|
|
|
'attachments'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// we consider that a link previews without a title at least is not a preview
|
|
|
|
|
const linkPreviews =
|
|
|
|
|
(stagedLinkPreview &&
|
|
|
|
|
stagedLinkPreview.isLoaded &&
|
|
|
|
|
stagedLinkPreview.title?.length && [_.pick(stagedLinkPreview, 'url', 'image', 'title')]) ||
|
|
|
|
|
[];
|
|
|
|
|
// we consider that a link preview without a title at least is not a preview
|
|
|
|
|
const linkPreview =
|
|
|
|
|
stagedLinkPreview?.isLoaded && stagedLinkPreview.title?.length
|
|
|
|
|
? _.pick(stagedLinkPreview, 'url', 'image', 'title')
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const attachments = await this.getFiles();
|
|
|
|
|
const { attachments, previews } = await this.getFiles(linkPreview);
|
|
|
|
|
this.props.sendMessage({
|
|
|
|
|
body: messagePlaintext,
|
|
|
|
|
attachments: attachments || [],
|
|
|
|
|
quote: extractedQuotedMessageProps,
|
|
|
|
|
preview: linkPreviews,
|
|
|
|
|
preview: previews,
|
|
|
|
|
groupInvitation: undefined,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@ -867,20 +858,54 @@ class CompositionBoxInner extends React.Component<Props, State> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this function is called right before sending a message, to gather really the files behind attachments.
|
|
|
|
|
private async getFiles(): Promise<Array<StagedAttachmentImportedType>> {
|
|
|
|
|
private async getFiles(
|
|
|
|
|
linkPreview?: Pick<StagedLinkPreviewData, 'url' | 'title' | 'image'>
|
|
|
|
|
): Promise<{
|
|
|
|
|
attachments: Array<StagedAttachmentImportedType>;
|
|
|
|
|
previews: Array<StagedPreviewImportedType>;
|
|
|
|
|
}> {
|
|
|
|
|
const { stagedAttachments } = this.props;
|
|
|
|
|
|
|
|
|
|
let attachments: Array<StagedAttachmentImportedType> = [];
|
|
|
|
|
let previews: Array<StagedPreviewImportedType> = [];
|
|
|
|
|
|
|
|
|
|
if (_.isEmpty(stagedAttachments)) {
|
|
|
|
|
return [];
|
|
|
|
|
attachments = [];
|
|
|
|
|
} else {
|
|
|
|
|
// scale them down
|
|
|
|
|
const files = await Promise.all(stagedAttachments.map(AttachmentUtil.getFileAndStoreLocally));
|
|
|
|
|
|
|
|
|
|
attachments = _.compact(files);
|
|
|
|
|
}
|
|
|
|
|
// scale them down
|
|
|
|
|
const files = await Promise.all(stagedAttachments.map(AttachmentUtil.getFileAndStoreLocally));
|
|
|
|
|
|
|
|
|
|
if (!linkPreview || _.isEmpty(linkPreview) || !linkPreview.url || !linkPreview.title) {
|
|
|
|
|
previews = [];
|
|
|
|
|
} else {
|
|
|
|
|
const sharedDetails = { url: linkPreview.url, title: linkPreview.title };
|
|
|
|
|
// store the first image preview locally and get the path and details back to include them in the message
|
|
|
|
|
const firstLinkPreviewImage = linkPreview.image;
|
|
|
|
|
if (firstLinkPreviewImage && !isEmpty(firstLinkPreviewImage)) {
|
|
|
|
|
const storedLinkPreviewAttachment = await AttachmentUtil.getFileAndStoreLocallyImageBuffer(
|
|
|
|
|
firstLinkPreviewImage.data
|
|
|
|
|
);
|
|
|
|
|
if (storedLinkPreviewAttachment) {
|
|
|
|
|
previews = [{ ...sharedDetails, image: storedLinkPreviewAttachment }];
|
|
|
|
|
} else {
|
|
|
|
|
// we couldn't save the image or whatever error happened, just return the url + title
|
|
|
|
|
previews = [sharedDetails];
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// we did not fetch an image from the server
|
|
|
|
|
previews = [sharedDetails];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.inboxStore?.dispatch(
|
|
|
|
|
removeAllStagedAttachmentsInConversation({
|
|
|
|
|
conversationKey: this.props.selectedConversationKey,
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
return _.compact(files);
|
|
|
|
|
return { attachments, previews };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async sendVoiceMessage(audioBlob: Blob) {
|
|
|
|
|