2022-01-07 18:01:23 +00:00
|
|
|
|
// Copyright 2020-2022 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
2021-10-19 00:09:55 +00:00
|
|
|
|
/* eslint-disable camelcase */
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
import type * as Backbone from 'backbone';
|
|
|
|
|
import type { ComponentProps } from 'react';
|
|
|
|
|
import * as React from 'react';
|
2022-06-17 00:48:57 +00:00
|
|
|
|
import { debounce, flatten, throttle } from 'lodash';
|
2021-08-30 21:32:56 +00:00
|
|
|
|
import { render } from 'mustache';
|
2021-08-11 16:23:21 +00:00
|
|
|
|
|
2021-12-03 01:05:32 +00:00
|
|
|
|
import type { AttachmentType } from '../types/Attachment';
|
2021-10-26 19:15:33 +00:00
|
|
|
|
import { isGIF } from '../types/Attachment';
|
2021-09-24 00:49:05 +00:00
|
|
|
|
import * as Attachment from '../types/Attachment';
|
2021-07-09 19:36:10 +00:00
|
|
|
|
import * as Stickers from '../types/Stickers';
|
2021-10-26 19:15:33 +00:00
|
|
|
|
import type { BodyRangeType, BodyRangesType } from '../types/Util';
|
|
|
|
|
import type { MIMEType } from '../types/MIME';
|
|
|
|
|
import type { ConversationModel } from '../models/conversations';
|
|
|
|
|
import type {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
GroupV2PendingMemberType,
|
2021-06-17 17:15:10 +00:00
|
|
|
|
MessageAttributesType,
|
2021-08-30 21:32:56 +00:00
|
|
|
|
ConversationModelCollectionType,
|
|
|
|
|
QuotedMessageType,
|
2021-06-16 00:44:14 +00:00
|
|
|
|
} from '../model-types.d';
|
2021-10-26 19:15:33 +00:00
|
|
|
|
import type { LinkPreviewType } from '../types/message/LinkPreviews';
|
2022-06-03 16:33:39 +00:00
|
|
|
|
import type { MediaItemType, MediaItemMessageType } from '../types/MediaItem';
|
2021-10-26 19:15:33 +00:00
|
|
|
|
import type { MessageModel } from '../models/messages';
|
2022-01-31 21:52:09 +00:00
|
|
|
|
import { getMessageById } from '../messages/getMessageById';
|
2021-12-10 22:51:54 +00:00
|
|
|
|
import { getContactId } from '../messages/helpers';
|
2021-08-30 21:32:56 +00:00
|
|
|
|
import { strictAssert } from '../util/assert';
|
2021-10-29 23:19:44 +00:00
|
|
|
|
import { enqueueReactionForSend } from '../reactions/enqueueReactionForSend';
|
2021-05-27 20:17:05 +00:00
|
|
|
|
import { addReportSpamJob } from '../jobs/helpers/addReportSpamJob';
|
|
|
|
|
import { reportSpamJobQueue } from '../jobs/reportSpamJobQueue';
|
2021-10-26 19:15:33 +00:00
|
|
|
|
import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions';
|
2021-06-07 16:39:13 +00:00
|
|
|
|
import {
|
|
|
|
|
isDirectConversation,
|
2022-02-23 01:06:19 +00:00
|
|
|
|
isGroup,
|
2021-06-07 16:39:13 +00:00
|
|
|
|
isGroupV1,
|
|
|
|
|
} from '../util/whatTypeOfConversation';
|
2021-06-22 23:16:50 +00:00
|
|
|
|
import { findAndFormatContact } from '../util/findAndFormatContact';
|
2022-06-03 16:33:39 +00:00
|
|
|
|
import { getPreferredBadgeSelector } from '../state/selectors/badges';
|
2021-06-17 17:15:10 +00:00
|
|
|
|
import {
|
|
|
|
|
canReply,
|
|
|
|
|
getAttachmentsForMessage,
|
2021-11-03 17:02:26 +00:00
|
|
|
|
isIncoming,
|
2021-06-17 17:15:10 +00:00
|
|
|
|
isOutgoing,
|
|
|
|
|
isTapToView,
|
|
|
|
|
} from '../state/selectors/message';
|
2021-08-23 23:14:53 +00:00
|
|
|
|
import {
|
|
|
|
|
getConversationSelector,
|
|
|
|
|
getMessagesByConversation,
|
|
|
|
|
} from '../state/selectors/conversations';
|
2022-02-15 23:00:47 +00:00
|
|
|
|
import { getActiveCallState } from '../state/selectors/calling';
|
2022-06-03 16:33:39 +00:00
|
|
|
|
import { getTheme } from '../state/selectors/user';
|
|
|
|
|
import { ReactWrapperView } from './ReactWrapperView';
|
2022-06-16 19:12:50 +00:00
|
|
|
|
import type { Lightbox } from '../components/Lightbox';
|
2021-06-17 21:15:09 +00:00
|
|
|
|
import { ConversationDetailsMembershipList } from '../components/conversation/conversation-details/ConversationDetailsMembershipList';
|
|
|
|
|
import { showSafetyNumberChangeDialog } from '../shims/showSafetyNumberChangeDialog';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import * as log from '../logging/log';
|
|
|
|
|
import type { EmbeddedContactType } from '../types/EmbeddedContact';
|
2021-10-05 16:47:06 +00:00
|
|
|
|
import { createConversationView } from '../state/roots/createConversationView';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import { AttachmentToastType } from '../types/AttachmentToastType';
|
2021-10-26 19:15:33 +00:00
|
|
|
|
import type { CompositionAPIType } from '../components/CompositionArea';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import { ReadStatus } from '../messages/MessageReadStatus';
|
|
|
|
|
import { SignalService as Proto } from '../protobuf';
|
2021-09-22 20:59:54 +00:00
|
|
|
|
import { ToastBlocked } from '../components/ToastBlocked';
|
|
|
|
|
import { ToastBlockedGroup } from '../components/ToastBlockedGroup';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import { ToastCannotMixImageAndNonImageAttachments } from '../components/ToastCannotMixImageAndNonImageAttachments';
|
|
|
|
|
import { ToastCannotStartGroupCall } from '../components/ToastCannotStartGroupCall';
|
2021-09-22 20:59:54 +00:00
|
|
|
|
import { ToastConversationArchived } from '../components/ToastConversationArchived';
|
|
|
|
|
import { ToastConversationMarkedUnread } from '../components/ToastConversationMarkedUnread';
|
|
|
|
|
import { ToastConversationUnarchived } from '../components/ToastConversationUnarchived';
|
|
|
|
|
import { ToastDangerousFileType } from '../components/ToastDangerousFileType';
|
|
|
|
|
import { ToastDeleteForEveryoneFailed } from '../components/ToastDeleteForEveryoneFailed';
|
|
|
|
|
import { ToastExpired } from '../components/ToastExpired';
|
|
|
|
|
import { ToastFileSaved } from '../components/ToastFileSaved';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import { ToastFileSize } from '../components/ToastFileSize';
|
2021-09-22 20:59:54 +00:00
|
|
|
|
import { ToastInvalidConversation } from '../components/ToastInvalidConversation';
|
|
|
|
|
import { ToastLeftGroup } from '../components/ToastLeftGroup';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import { ToastMaxAttachments } from '../components/ToastMaxAttachments';
|
2021-09-22 20:59:54 +00:00
|
|
|
|
import { ToastMessageBodyTooLong } from '../components/ToastMessageBodyTooLong';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import { ToastOneNonImageAtATime } from '../components/ToastOneNonImageAtATime';
|
2021-09-22 20:59:54 +00:00
|
|
|
|
import { ToastOriginalMessageNotFound } from '../components/ToastOriginalMessageNotFound';
|
|
|
|
|
import { ToastPinnedConversationsFull } from '../components/ToastPinnedConversationsFull';
|
|
|
|
|
import { ToastReactionFailed } from '../components/ToastReactionFailed';
|
|
|
|
|
import { ToastReportedSpamAndBlocked } from '../components/ToastReportedSpamAndBlocked';
|
|
|
|
|
import { ToastTapToViewExpiredIncoming } from '../components/ToastTapToViewExpiredIncoming';
|
|
|
|
|
import { ToastTapToViewExpiredOutgoing } from '../components/ToastTapToViewExpiredOutgoing';
|
2021-09-24 20:02:30 +00:00
|
|
|
|
import { ToastUnableToLoadAttachment } from '../components/ToastUnableToLoadAttachment';
|
2022-05-11 20:59:58 +00:00
|
|
|
|
import { ToastCannotOpenGiftBadge } from '../components/ToastCannotOpenGiftBadge';
|
2021-09-24 20:02:30 +00:00
|
|
|
|
import { deleteDraftAttachment } from '../util/deleteDraftAttachment';
|
2021-09-27 16:29:00 +00:00
|
|
|
|
import { markAllAsApproved } from '../util/markAllAsApproved';
|
|
|
|
|
import { markAllAsVerifiedDefault } from '../util/markAllAsVerifiedDefault';
|
|
|
|
|
import { retryMessageSend } from '../util/retryMessageSend';
|
2021-09-29 20:23:06 +00:00
|
|
|
|
import { isNotNil } from '../util/isNotNil';
|
|
|
|
|
import { markViewed } from '../services/MessageUpdater';
|
|
|
|
|
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
|
|
|
|
|
import { resolveAttachmentDraftData } from '../util/resolveAttachmentDraftData';
|
|
|
|
|
import { showToast } from '../util/showToast';
|
|
|
|
|
import { viewSyncJobQueue } from '../jobs/viewSyncJobQueue';
|
|
|
|
|
import { viewedReceiptsJobQueue } from '../jobs/viewedReceiptsJobQueue';
|
2022-02-11 20:18:04 +00:00
|
|
|
|
import { RecordingState } from '../state/ducks/audioRecorder';
|
2022-02-23 01:06:19 +00:00
|
|
|
|
import { UUIDKind } from '../types/UUID';
|
2022-03-16 00:11:28 +00:00
|
|
|
|
import type { UUIDStringType } from '../types/UUID';
|
2022-03-04 19:22:31 +00:00
|
|
|
|
import { retryDeleteForEveryone } from '../util/retryDeleteForEveryone';
|
2022-06-03 16:33:39 +00:00
|
|
|
|
import { ContactDetail } from '../components/conversation/ContactDetail';
|
|
|
|
|
import { MediaGallery } from '../components/conversation/media-gallery/MediaGallery';
|
|
|
|
|
import type { ItemClickEvent } from '../components/conversation/media-gallery/types/ItemClickEvent';
|
2022-06-17 00:48:57 +00:00
|
|
|
|
import {
|
|
|
|
|
getLinkPreviewForSend,
|
|
|
|
|
hasLinkPreviewLoaded,
|
|
|
|
|
maybeGrabLinkPreview,
|
|
|
|
|
removeLinkPreview,
|
|
|
|
|
resetLinkPreview,
|
|
|
|
|
suspendLinkPreviews,
|
|
|
|
|
} from '../services/LinkPreview';
|
|
|
|
|
import { LinkPreviewSourceType } from '../types/LinkPreview';
|
2022-06-16 19:12:50 +00:00
|
|
|
|
import {
|
|
|
|
|
closeLightbox,
|
|
|
|
|
isLightboxOpen,
|
|
|
|
|
showLightbox,
|
|
|
|
|
} from '../util/showLightbox';
|
2020-09-28 23:46:31 +00:00
|
|
|
|
|
2021-03-22 18:51:53 +00:00
|
|
|
|
type AttachmentOptions = {
|
|
|
|
|
messageId: string;
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
type PanelType = { view: Backbone.View; headerTitle?: string };
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const FIVE_MINUTES = 1000 * 60 * 5;
|
|
|
|
|
|
2021-09-24 00:49:05 +00:00
|
|
|
|
const { Message } = window.Signal.Types;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
copyIntoTempDirectory,
|
|
|
|
|
deleteTempFile,
|
|
|
|
|
getAbsoluteAttachmentPath,
|
|
|
|
|
getAbsoluteTempPath,
|
2021-06-03 21:26:56 +00:00
|
|
|
|
loadAttachmentData,
|
2022-04-11 20:57:44 +00:00
|
|
|
|
loadContactData,
|
2021-04-27 22:35:35 +00:00
|
|
|
|
loadPreviewData,
|
|
|
|
|
loadStickerData,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
openFileInFolder,
|
|
|
|
|
readAttachmentData,
|
|
|
|
|
saveAttachmentToDisk,
|
|
|
|
|
upgradeMessageSchema,
|
|
|
|
|
} = window.Signal.Migrations;
|
|
|
|
|
|
2022-01-31 21:52:09 +00:00
|
|
|
|
const { getMessagesBySentAt } = window.Signal.Data;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
type MessageActionsType = {
|
|
|
|
|
deleteMessage: (messageId: string) => unknown;
|
|
|
|
|
deleteMessageForEveryone: (messageId: string) => unknown;
|
|
|
|
|
displayTapToViewMessage: (messageId: string) => unknown;
|
|
|
|
|
downloadAttachment: (options: {
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
timestamp: number;
|
|
|
|
|
isDangerous: boolean;
|
|
|
|
|
}) => unknown;
|
|
|
|
|
downloadNewVersion: () => unknown;
|
|
|
|
|
kickOffAttachmentDownload: (
|
|
|
|
|
options: Readonly<{ messageId: string }>
|
|
|
|
|
) => unknown;
|
|
|
|
|
markAttachmentAsCorrupted: (options: AttachmentOptions) => unknown;
|
|
|
|
|
markViewed: (messageId: string) => unknown;
|
|
|
|
|
openConversation: (conversationId: string, messageId?: string) => unknown;
|
2022-05-11 20:59:58 +00:00
|
|
|
|
openGiftBadge: (messageId: string) => unknown;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
openLink: (url: string) => unknown;
|
|
|
|
|
reactToMessage: (
|
|
|
|
|
messageId: string,
|
|
|
|
|
reaction: { emoji: string; remove: boolean }
|
|
|
|
|
) => unknown;
|
|
|
|
|
replyToMessage: (messageId: string) => unknown;
|
|
|
|
|
retrySend: (messageId: string) => unknown;
|
2022-03-04 19:22:31 +00:00
|
|
|
|
retryDeleteForEveryone: (messageId: string) => unknown;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showContactDetail: (options: {
|
|
|
|
|
contact: EmbeddedContactType;
|
2022-04-12 00:26:09 +00:00
|
|
|
|
signalAccount?: {
|
|
|
|
|
phoneNumber: string;
|
|
|
|
|
uuid: UUIDStringType;
|
|
|
|
|
};
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}) => unknown;
|
|
|
|
|
showContactModal: (contactId: string) => unknown;
|
|
|
|
|
showSafetyNumber: (contactId: string) => unknown;
|
|
|
|
|
showExpiredIncomingTapToViewToast: () => unknown;
|
|
|
|
|
showExpiredOutgoingTapToViewToast: () => unknown;
|
|
|
|
|
showForwardMessageModal: (messageId: string) => unknown;
|
|
|
|
|
showIdentity: (conversationId: string) => unknown;
|
|
|
|
|
showMessageDetail: (messageId: string) => unknown;
|
|
|
|
|
showVisualAttachment: (options: {
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
messageId: string;
|
|
|
|
|
showSingle?: boolean;
|
|
|
|
|
}) => unknown;
|
2022-04-12 00:26:09 +00:00
|
|
|
|
startConversation: (e164: string, uuid: UUIDStringType) => unknown;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type MediaType = {
|
|
|
|
|
path: string;
|
|
|
|
|
objectURL: string;
|
|
|
|
|
thumbnailObjectUrl?: string;
|
|
|
|
|
contentType: MIMEType;
|
|
|
|
|
index: number;
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
message: {
|
|
|
|
|
attachments: Array<AttachmentType>;
|
|
|
|
|
conversationId: string;
|
|
|
|
|
id: string;
|
|
|
|
|
received_at: number;
|
|
|
|
|
received_at_ms: number;
|
|
|
|
|
sent_at: number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const MAX_MESSAGE_BODY_LENGTH = 64 * 1024;
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
export class ConversationView extends window.Backbone.View<ConversationModel> {
|
|
|
|
|
private debouncedSaveDraft: (
|
|
|
|
|
messageText: string,
|
|
|
|
|
bodyRanges: Array<BodyRangeType>
|
|
|
|
|
) => Promise<void>;
|
|
|
|
|
private lazyUpdateVerified: () => void;
|
|
|
|
|
|
|
|
|
|
// Composing messages
|
|
|
|
|
private compositionApi: {
|
2021-10-05 16:47:06 +00:00
|
|
|
|
current: CompositionAPIType;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
} = { current: undefined };
|
|
|
|
|
private sendStart?: number;
|
|
|
|
|
|
|
|
|
|
// Quotes
|
|
|
|
|
private quote?: QuotedMessageType;
|
|
|
|
|
private quotedMessage?: MessageModel;
|
|
|
|
|
|
|
|
|
|
// Sub-views
|
|
|
|
|
private contactModalView?: Backbone.View;
|
2022-06-03 16:33:39 +00:00
|
|
|
|
private conversationView?: Backbone.View;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
private forwardMessageModal?: Backbone.View;
|
2022-06-03 16:33:39 +00:00
|
|
|
|
private lightboxView?: ReactWrapperView;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
private migrationDialog?: Backbone.View;
|
|
|
|
|
private stickerPreviewModalView?: Backbone.View;
|
|
|
|
|
|
|
|
|
|
// Panel support
|
2022-06-03 16:33:39 +00:00
|
|
|
|
private panels: Array<PanelType> = [];
|
2021-08-30 21:32:56 +00:00
|
|
|
|
private previousFocus?: HTMLElement;
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
constructor(...args: Array<any>) {
|
|
|
|
|
super(...args);
|
|
|
|
|
|
|
|
|
|
this.lazyUpdateVerified = debounce(
|
|
|
|
|
this.model.updateVerified.bind(this.model),
|
|
|
|
|
1000 // one second
|
|
|
|
|
);
|
|
|
|
|
this.model.throttledGetProfiles =
|
|
|
|
|
this.model.throttledGetProfiles ||
|
|
|
|
|
throttle(this.model.getProfiles.bind(this.model), FIVE_MINUTES);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.debouncedSaveDraft = debounce(this.saveDraft.bind(this), 200);
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
// Events on Conversation model
|
2021-06-16 00:44:14 +00:00
|
|
|
|
this.listenTo(this.model, 'destroy', this.stopListening);
|
|
|
|
|
this.listenTo(this.model, 'newmessage', this.lazyUpdateVerified);
|
|
|
|
|
|
|
|
|
|
// These are triggered by InboxView
|
|
|
|
|
this.listenTo(this.model, 'opened', this.onOpened);
|
|
|
|
|
this.listenTo(this.model, 'scroll-to-message', this.scrollToMessage);
|
|
|
|
|
this.listenTo(this.model, 'unload', (reason: string) =>
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.unload(`model trigger - ${reason}`)
|
|
|
|
|
);
|
2021-06-16 00:44:14 +00:00
|
|
|
|
|
|
|
|
|
// These are triggered by background.ts for keyboard handling
|
|
|
|
|
this.listenTo(this.model, 'focus-composer', this.focusMessageField);
|
|
|
|
|
this.listenTo(this.model, 'open-all-media', this.showAllMedia);
|
|
|
|
|
this.listenTo(this.model, 'escape-pressed', this.resetPanel);
|
|
|
|
|
this.listenTo(this.model, 'show-message-details', this.showMessageDetail);
|
|
|
|
|
this.listenTo(this.model, 'show-contact-modal', this.showContactModal);
|
|
|
|
|
this.listenTo(
|
|
|
|
|
this.model,
|
|
|
|
|
'toggle-reply',
|
|
|
|
|
(messageId: string | undefined) => {
|
|
|
|
|
const target = this.quote || !messageId ? null : messageId;
|
|
|
|
|
this.setQuoteMessage(target);
|
|
|
|
|
}
|
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.listenTo(
|
|
|
|
|
this.model,
|
|
|
|
|
'save-attachment',
|
|
|
|
|
this.downloadAttachmentWrapper
|
2020-09-24 20:57:54 +00:00
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.listenTo(this.model, 'delete-message', this.deleteMessage);
|
2022-06-17 00:48:57 +00:00
|
|
|
|
this.listenTo(this.model, 'remove-link-review', removeLinkPreview);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.listenTo(
|
|
|
|
|
this.model,
|
|
|
|
|
'remove-all-draft-attachments',
|
|
|
|
|
this.clearAttachments
|
2020-09-24 20:57:54 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.render();
|
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
this.setupConversationView();
|
2021-06-25 16:08:16 +00:00
|
|
|
|
this.updateAttachmentsView();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-11-12 23:44:20 +00:00
|
|
|
|
override events(): Record<string, string> {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
return {
|
|
|
|
|
drop: 'onDrop',
|
|
|
|
|
paste: 'onPaste',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We need this ignore because the backbone types really want this to be a string
|
|
|
|
|
// property, but the property isn't set until after super() is run, meaning that this
|
|
|
|
|
// classname wouldn't be applied when Backbone creates our el.
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
className(): string {
|
|
|
|
|
return 'conversation';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same situation as className().
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
id(): string {
|
|
|
|
|
return `conversation-${this.model.cid}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Backbone.View<ConversationModel> is demanded as the return type here, and we can't
|
|
|
|
|
// satisfy it because of the above difference in signature: className is a function
|
|
|
|
|
// when it should be a plain string property.
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
render(): ConversationView {
|
|
|
|
|
const template = $('#conversation').html();
|
|
|
|
|
this.$el.html(render(template, {}));
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-05 12:35:33 +00:00
|
|
|
|
setMuteExpiration(ms = 0): void {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.setMuteExpiration(
|
2021-08-05 12:35:33 +00:00
|
|
|
|
ms >= Number.MAX_SAFE_INTEGER ? ms : Date.now() + ms
|
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
setPin(value: boolean): void {
|
2020-10-02 18:30:43 +00:00
|
|
|
|
if (value) {
|
2021-06-15 00:09:37 +00:00
|
|
|
|
const pinnedConversationIds = window.storage.get(
|
2020-11-09 18:30:05 +00:00
|
|
|
|
'pinnedConversationIds',
|
2021-06-15 00:09:37 +00:00
|
|
|
|
new Array<string>()
|
2020-11-09 18:30:05 +00:00
|
|
|
|
);
|
2020-10-10 14:25:17 +00:00
|
|
|
|
|
|
|
|
|
if (pinnedConversationIds.length >= 4) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastPinnedConversationsFull);
|
2020-10-02 18:30:43 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.pin();
|
2020-10-02 18:30:43 +00:00
|
|
|
|
} else {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.unpin();
|
2020-10-02 18:30:43 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
setupConversationView(): void {
|
|
|
|
|
// setupHeader
|
|
|
|
|
const conversationHeaderProps = {
|
|
|
|
|
id: this.model.id,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
onSetDisappearingMessages: (seconds: number) =>
|
|
|
|
|
this.setDisappearingMessages(seconds),
|
|
|
|
|
onDeleteMessages: () => this.destroyMessages(),
|
|
|
|
|
onSearchInConversation: () => {
|
|
|
|
|
const { searchInConversation } = window.reduxActions.search;
|
2021-11-01 18:43:02 +00:00
|
|
|
|
searchInConversation(this.model.id);
|
2021-10-05 16:47:06 +00:00
|
|
|
|
},
|
|
|
|
|
onSetMuteNotifications: this.setMuteExpiration.bind(this),
|
|
|
|
|
onSetPin: this.setPin.bind(this),
|
|
|
|
|
// These are view only and don't update the Conversation model, so they
|
|
|
|
|
// need a manual update call.
|
2021-11-11 22:43:05 +00:00
|
|
|
|
onOutgoingAudioCallInConversation:
|
|
|
|
|
this.onOutgoingAudioCallInConversation.bind(this),
|
|
|
|
|
onOutgoingVideoCallInConversation:
|
|
|
|
|
this.onOutgoingVideoCallInConversation.bind(this),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
onShowConversationDetails: () => {
|
|
|
|
|
this.showConversationDetails();
|
|
|
|
|
},
|
|
|
|
|
onShowAllMedia: () => {
|
|
|
|
|
this.showAllMedia();
|
|
|
|
|
},
|
|
|
|
|
onShowGroupMembers: () => {
|
|
|
|
|
this.showGV1Members();
|
|
|
|
|
},
|
|
|
|
|
onGoBack: () => {
|
|
|
|
|
this.resetPanel();
|
|
|
|
|
},
|
2021-06-25 16:08:16 +00:00
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
onArchive: () => {
|
|
|
|
|
this.model.setArchived(true);
|
|
|
|
|
this.model.trigger('unload', 'archive');
|
|
|
|
|
|
2021-10-06 21:00:51 +00:00
|
|
|
|
showToast(ToastConversationArchived, {
|
|
|
|
|
undo: () => {
|
|
|
|
|
this.model.setArchived(false);
|
|
|
|
|
this.openConversation(this.model.get('id'));
|
|
|
|
|
},
|
|
|
|
|
});
|
2021-10-05 16:47:06 +00:00
|
|
|
|
},
|
|
|
|
|
onMarkUnread: () => {
|
|
|
|
|
this.model.setMarkedUnread(true);
|
|
|
|
|
|
|
|
|
|
showToast(ToastConversationMarkedUnread);
|
|
|
|
|
},
|
|
|
|
|
onMoveToInbox: () => {
|
|
|
|
|
this.model.setArchived(false);
|
|
|
|
|
|
|
|
|
|
showToast(ToastConversationUnarchived);
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
window.reduxActions.conversations.setSelectedConversationHeaderTitle();
|
|
|
|
|
|
|
|
|
|
// setupTimeline
|
2021-07-09 19:36:10 +00:00
|
|
|
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
const contactSupport = () => {
|
|
|
|
|
const baseUrl =
|
|
|
|
|
'https://support.signal.org/hc/LOCALE/requests/new?desktop&chat_refreshed';
|
|
|
|
|
const locale = window.getLocale();
|
|
|
|
|
const supportLocale = window.Signal.Util.mapToSupportLocale(locale);
|
|
|
|
|
const url = baseUrl.replace('LOCALE', supportLocale);
|
|
|
|
|
|
|
|
|
|
openLinkInWebBrowser(url);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const learnMoreAboutDeliveryIssue = () => {
|
|
|
|
|
openLinkInWebBrowser(
|
|
|
|
|
'https://support.signal.org/hc/articles/4404859745690'
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const scrollToQuotedMessage = async (
|
|
|
|
|
options: Readonly<{
|
|
|
|
|
authorId: string;
|
|
|
|
|
sentAt: number;
|
|
|
|
|
}>
|
|
|
|
|
) => {
|
|
|
|
|
const { authorId, sentAt } = options;
|
|
|
|
|
|
|
|
|
|
const conversationId = this.model.id;
|
2021-12-10 22:51:54 +00:00
|
|
|
|
const messages = await getMessagesBySentAt(sentAt);
|
2021-10-05 16:47:06 +00:00
|
|
|
|
const message = messages.find(item =>
|
|
|
|
|
Boolean(
|
2021-12-10 22:51:54 +00:00
|
|
|
|
item.conversationId === conversationId &&
|
2021-10-05 16:47:06 +00:00
|
|
|
|
authorId &&
|
2021-12-10 22:51:54 +00:00
|
|
|
|
getContactId(item) === authorId
|
2021-10-05 16:47:06 +00:00
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!message) {
|
|
|
|
|
showToast(ToastOriginalMessageNotFound);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.scrollToMessage(message.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const markMessageRead = async (messageId: string) => {
|
|
|
|
|
if (!window.isActive()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-15 23:00:47 +00:00
|
|
|
|
const activeCall = getActiveCallState(window.reduxStore.getState());
|
|
|
|
|
if (activeCall && !activeCall.pip) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-10 22:51:54 +00:00
|
|
|
|
const message = await getMessageById(messageId);
|
2021-10-05 16:47:06 +00:00
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(`markMessageRead: failed to load message ${messageId}`);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 18:35:14 +00:00
|
|
|
|
await this.model.markRead(message.get('received_at'), {
|
|
|
|
|
newestSentAt: message.get('sent_at'),
|
|
|
|
|
sendReadReceipts: true,
|
|
|
|
|
});
|
2021-10-05 16:47:06 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-11-11 22:43:05 +00:00
|
|
|
|
const createMessageRequestResponseHandler =
|
|
|
|
|
(name: string, enumValue: number): ((conversationId: string) => void) =>
|
|
|
|
|
conversationId => {
|
|
|
|
|
const conversation = window.ConversationController.get(conversationId);
|
|
|
|
|
if (!conversation) {
|
|
|
|
|
log.error(
|
|
|
|
|
`createMessageRequestResponseHandler: Expected a conversation to be found in ${name}. Doing nothing`
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.syncMessageRequestResponse(name, conversation, enumValue);
|
|
|
|
|
};
|
2021-10-05 16:47:06 +00:00
|
|
|
|
|
|
|
|
|
const timelineProps = {
|
|
|
|
|
id: this.model.id,
|
|
|
|
|
|
|
|
|
|
...this.getMessageActions(),
|
|
|
|
|
|
|
|
|
|
acknowledgeGroupMemberNameCollisions: (
|
|
|
|
|
groupNameCollisions: Readonly<GroupNameCollisionsWithIdsByTitle>
|
|
|
|
|
): void => {
|
|
|
|
|
this.model.acknowledgeGroupMemberNameCollisions(groupNameCollisions);
|
|
|
|
|
},
|
2022-03-16 00:11:28 +00:00
|
|
|
|
blockGroupLinkRequests: (uuid: UUIDStringType) => {
|
|
|
|
|
this.model.blockGroupLinkRequests(uuid);
|
|
|
|
|
},
|
2021-10-05 16:47:06 +00:00
|
|
|
|
contactSupport,
|
|
|
|
|
learnMoreAboutDeliveryIssue,
|
2021-11-30 11:25:24 +00:00
|
|
|
|
loadNewerMessages: this.model.loadNewerMessages.bind(this.model),
|
|
|
|
|
loadNewestMessages: this.model.loadNewestMessages.bind(this.model),
|
|
|
|
|
loadAndScroll: this.model.loadAndScroll.bind(this.model),
|
|
|
|
|
loadOlderMessages: this.model.loadOlderMessages.bind(this.model),
|
2021-10-05 16:47:06 +00:00
|
|
|
|
markMessageRead,
|
|
|
|
|
onBlock: createMessageRequestResponseHandler(
|
|
|
|
|
'onBlock',
|
|
|
|
|
messageRequestEnum.BLOCK
|
|
|
|
|
),
|
|
|
|
|
onBlockAndReportSpam: (conversationId: string) => {
|
|
|
|
|
const conversation = window.ConversationController.get(conversationId);
|
|
|
|
|
if (!conversation) {
|
|
|
|
|
log.error(
|
|
|
|
|
`onBlockAndReportSpam: Expected a conversation to be found for ${conversationId}. Doing nothing.`
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.blockAndReportSpam(conversation);
|
|
|
|
|
},
|
|
|
|
|
onDelete: createMessageRequestResponseHandler(
|
|
|
|
|
'onDelete',
|
|
|
|
|
messageRequestEnum.DELETE
|
|
|
|
|
),
|
|
|
|
|
onUnblock: createMessageRequestResponseHandler(
|
|
|
|
|
'onUnblock',
|
|
|
|
|
messageRequestEnum.ACCEPT
|
|
|
|
|
),
|
|
|
|
|
removeMember: (conversationId: string) => {
|
|
|
|
|
this.longRunningTaskWrapper({
|
|
|
|
|
name: 'removeMember',
|
|
|
|
|
task: () => this.model.removeFromGroupV2(conversationId),
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
scrollToQuotedMessage,
|
|
|
|
|
unblurAvatar: () => {
|
|
|
|
|
this.model.unblurAvatar();
|
|
|
|
|
},
|
|
|
|
|
updateSharedGroups: () => this.model.throttledUpdateSharedGroups?.(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// setupCompositionArea
|
|
|
|
|
window.reduxActions.composer.resetComposer();
|
|
|
|
|
|
|
|
|
|
const compositionAreaProps = {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
id: this.model.id,
|
|
|
|
|
compositionApi: this.compositionApi,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
onClickAddPack: () => this.showStickerManager(),
|
|
|
|
|
onPickSticker: (packId: string, stickerId: number) =>
|
|
|
|
|
this.sendStickerMessage({ packId, stickerId }),
|
2020-11-03 01:19:52 +00:00
|
|
|
|
onEditorStateChange: (
|
|
|
|
|
msg: string,
|
2021-08-30 21:32:56 +00:00
|
|
|
|
bodyRanges: Array<BodyRangeType>,
|
2021-04-27 22:35:35 +00:00
|
|
|
|
caretLocation?: number
|
2020-11-03 01:19:52 +00:00
|
|
|
|
) => this.onEditorStateChange(msg, bodyRanges, caretLocation),
|
2021-09-22 20:59:54 +00:00
|
|
|
|
onTextTooLong: () => showToast(ToastMessageBodyTooLong),
|
2021-08-30 21:32:56 +00:00
|
|
|
|
getQuotedMessage: () => this.model.get('quotedMessageId'),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
clearQuotedMessage: () => this.setQuoteMessage(null),
|
2020-10-06 17:06:34 +00:00
|
|
|
|
onAccept: () => {
|
2021-06-01 23:30:25 +00:00
|
|
|
|
this.syncMessageRequestResponse(
|
|
|
|
|
'onAccept',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model,
|
2021-06-01 23:30:25 +00:00
|
|
|
|
messageRequestEnum.ACCEPT
|
|
|
|
|
);
|
2020-10-06 17:06:34 +00:00
|
|
|
|
},
|
|
|
|
|
onBlock: () => {
|
2021-06-01 23:30:25 +00:00
|
|
|
|
this.syncMessageRequestResponse(
|
|
|
|
|
'onBlock',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model,
|
2021-06-01 23:30:25 +00:00
|
|
|
|
messageRequestEnum.BLOCK
|
|
|
|
|
);
|
2020-10-06 17:06:34 +00:00
|
|
|
|
},
|
|
|
|
|
onUnblock: () => {
|
2021-06-01 23:30:25 +00:00
|
|
|
|
this.syncMessageRequestResponse(
|
|
|
|
|
'onUnblock',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model,
|
2021-06-01 23:30:25 +00:00
|
|
|
|
messageRequestEnum.ACCEPT
|
|
|
|
|
);
|
2020-10-06 17:06:34 +00:00
|
|
|
|
},
|
|
|
|
|
onDelete: () => {
|
2021-06-01 23:30:25 +00:00
|
|
|
|
this.syncMessageRequestResponse(
|
|
|
|
|
'onDelete',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model,
|
2021-06-01 23:30:25 +00:00
|
|
|
|
messageRequestEnum.DELETE
|
|
|
|
|
);
|
2020-10-06 17:06:34 +00:00
|
|
|
|
},
|
2021-05-27 20:17:05 +00:00
|
|
|
|
onBlockAndReportSpam: () => {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.blockAndReportSpam(this.model);
|
2020-10-06 17:06:34 +00:00
|
|
|
|
},
|
2020-12-01 16:42:35 +00:00
|
|
|
|
onStartGroupMigration: () => this.startMigrationToGV2(),
|
2021-01-29 22:16:48 +00:00
|
|
|
|
onCancelJoinRequest: async () => {
|
|
|
|
|
await window.showConfirmationDialog({
|
|
|
|
|
message: window.i18n(
|
|
|
|
|
'GroupV2--join--cancel-request-to-join--confirmation'
|
|
|
|
|
),
|
|
|
|
|
okText: window.i18n('GroupV2--join--cancel-request-to-join--yes'),
|
|
|
|
|
cancelText: window.i18n('GroupV2--join--cancel-request-to-join--no'),
|
|
|
|
|
resolve: () => {
|
|
|
|
|
this.longRunningTaskWrapper({
|
|
|
|
|
name: 'onCancelJoinRequest',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
task: async () => this.model.cancelJoinRequest(),
|
2021-01-29 22:16:48 +00:00
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
2021-06-25 16:08:16 +00:00
|
|
|
|
|
|
|
|
|
onClearAttachments: this.clearAttachments.bind(this),
|
|
|
|
|
onSelectMediaQuality: (isHQ: boolean) => {
|
|
|
|
|
window.reduxActions.composer.setMediaQualitySetting(isHQ);
|
|
|
|
|
},
|
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
handleClickQuotedMessage: (id: string) => this.scrollToMessage(id),
|
2021-06-25 16:08:16 +00:00
|
|
|
|
|
|
|
|
|
onCloseLinkPreview: () => {
|
2022-06-17 00:48:57 +00:00
|
|
|
|
suspendLinkPreviews();
|
|
|
|
|
removeLinkPreview();
|
2021-06-25 16:08:16 +00:00
|
|
|
|
},
|
2021-07-20 20:18:35 +00:00
|
|
|
|
|
|
|
|
|
openConversation: this.openConversation.bind(this),
|
2021-09-29 20:23:06 +00:00
|
|
|
|
|
|
|
|
|
onSendMessage: ({
|
|
|
|
|
draftAttachments,
|
|
|
|
|
mentions = [],
|
|
|
|
|
message = '',
|
|
|
|
|
timestamp,
|
|
|
|
|
voiceNoteAttachment,
|
|
|
|
|
}: {
|
|
|
|
|
draftAttachments?: ReadonlyArray<AttachmentType>;
|
|
|
|
|
mentions?: BodyRangesType;
|
|
|
|
|
message?: string;
|
|
|
|
|
timestamp?: number;
|
|
|
|
|
voiceNoteAttachment?: AttachmentType;
|
|
|
|
|
}): void => {
|
|
|
|
|
this.sendMessage(message, mentions, {
|
|
|
|
|
draftAttachments,
|
|
|
|
|
timestamp,
|
|
|
|
|
voiceNoteAttachment,
|
|
|
|
|
});
|
|
|
|
|
},
|
2020-09-24 20:57:54 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
// createConversationView root
|
|
|
|
|
|
|
|
|
|
const JSX = createConversationView(window.reduxStore, {
|
|
|
|
|
compositionAreaProps,
|
|
|
|
|
conversationHeaderProps,
|
|
|
|
|
timelineProps,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.conversationView = new ReactWrapperView({ JSX });
|
2021-10-05 16:47:06 +00:00
|
|
|
|
this.$('.ConversationView__template').append(this.conversationView.el);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-10-20 23:46:41 +00:00
|
|
|
|
async onOutgoingVideoCallInConversation(): Promise<void> {
|
|
|
|
|
log.info('onOutgoingVideoCallInConversation: about to start a video call');
|
|
|
|
|
|
|
|
|
|
if (this.model.get('announcementsOnly') && !this.model.areWeAdmin()) {
|
|
|
|
|
showToast(ToastCannotStartGroupCall);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (await this.isCallSafe()) {
|
|
|
|
|
log.info(
|
|
|
|
|
'onOutgoingVideoCallInConversation: call is deemed "safe". Making call'
|
|
|
|
|
);
|
2022-01-07 18:01:23 +00:00
|
|
|
|
window.reduxActions.calling.startCallingLobby({
|
|
|
|
|
conversationId: this.model.id,
|
|
|
|
|
isVideoCall: true,
|
|
|
|
|
});
|
2021-10-20 23:46:41 +00:00
|
|
|
|
log.info('onOutgoingVideoCallInConversation: started the call');
|
|
|
|
|
} else {
|
|
|
|
|
log.info(
|
|
|
|
|
'onOutgoingVideoCallInConversation: call is deemed "unsafe". Stopping'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async onOutgoingAudioCallInConversation(): Promise<void> {
|
|
|
|
|
log.info('onOutgoingAudioCallInConversation: about to start an audio call');
|
|
|
|
|
|
|
|
|
|
if (await this.isCallSafe()) {
|
|
|
|
|
log.info(
|
|
|
|
|
'onOutgoingAudioCallInConversation: call is deemed "safe". Making call'
|
|
|
|
|
);
|
2022-01-07 18:01:23 +00:00
|
|
|
|
window.reduxActions.calling.startCallingLobby({
|
|
|
|
|
conversationId: this.model.id,
|
|
|
|
|
isVideoCall: false,
|
|
|
|
|
});
|
2021-10-20 23:46:41 +00:00
|
|
|
|
log.info('onOutgoingAudioCallInConversation: started the call');
|
|
|
|
|
} else {
|
|
|
|
|
log.info(
|
|
|
|
|
'onOutgoingAudioCallInConversation: call is deemed "unsafe". Stopping'
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-01 16:42:35 +00:00
|
|
|
|
async longRunningTaskWrapper<T>({
|
2020-10-06 17:06:34 +00:00
|
|
|
|
name,
|
|
|
|
|
task,
|
|
|
|
|
}: {
|
|
|
|
|
name: string;
|
2020-12-01 16:42:35 +00:00
|
|
|
|
task: () => Promise<T>;
|
|
|
|
|
}): Promise<T> {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const idForLogging = this.model.idForLogging();
|
2021-01-29 22:16:48 +00:00
|
|
|
|
return window.Signal.Util.longRunningTaskWrapper({
|
|
|
|
|
name,
|
|
|
|
|
idForLogging,
|
|
|
|
|
task,
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-10-06 17:06:34 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
getMessageActions(): MessageActionsType {
|
2021-10-29 23:19:44 +00:00
|
|
|
|
const reactToMessage = async (
|
2021-06-08 14:59:38 +00:00
|
|
|
|
messageId: string,
|
|
|
|
|
reaction: { emoji: string; remove: boolean }
|
|
|
|
|
) => {
|
2021-10-29 23:19:44 +00:00
|
|
|
|
const { emoji, remove } = reaction;
|
|
|
|
|
try {
|
|
|
|
|
await enqueueReactionForSend({
|
|
|
|
|
messageId,
|
|
|
|
|
emoji,
|
|
|
|
|
remove,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
log.error('Error sending reaction', error, messageId, reaction);
|
|
|
|
|
showToast(ToastReactionFailed);
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
};
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const replyToMessage = (messageId: string) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.setQuoteMessage(messageId);
|
|
|
|
|
};
|
2021-09-27 16:29:00 +00:00
|
|
|
|
const retrySend = retryMessageSend;
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const deleteMessage = (messageId: string) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.deleteMessage(messageId);
|
|
|
|
|
};
|
2020-09-29 22:55:56 +00:00
|
|
|
|
const deleteMessageForEveryone = (messageId: string) => {
|
|
|
|
|
this.deleteMessageForEveryone(messageId);
|
|
|
|
|
};
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const showMessageDetail = (messageId: string) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.showMessageDetail(messageId);
|
|
|
|
|
};
|
2020-11-11 17:36:05 +00:00
|
|
|
|
const showContactModal = (contactId: string) => {
|
|
|
|
|
this.showContactModal(contactId);
|
|
|
|
|
};
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const openConversation = (conversationId: string, messageId?: string) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.openConversation(conversationId, messageId);
|
|
|
|
|
};
|
2021-08-16 21:35:54 +00:00
|
|
|
|
const showContactDetail = (options: {
|
2021-08-20 01:56:39 +00:00
|
|
|
|
contact: EmbeddedContactType;
|
2022-04-12 00:26:09 +00:00
|
|
|
|
signalAccount?: {
|
|
|
|
|
phoneNumber: string;
|
|
|
|
|
uuid: UUIDStringType;
|
|
|
|
|
};
|
2021-08-16 21:35:54 +00:00
|
|
|
|
}) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.showContactDetail(options);
|
|
|
|
|
};
|
2021-08-16 21:35:54 +00:00
|
|
|
|
const kickOffAttachmentDownload = async (
|
|
|
|
|
options: Readonly<{ messageId: string }>
|
|
|
|
|
) => {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(options.messageId);
|
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`kickOffAttachmentDownload: Message ${options.messageId} missing!`
|
|
|
|
|
);
|
2021-01-29 22:58:28 +00:00
|
|
|
|
}
|
|
|
|
|
await message.queueAttachmentDownloads();
|
|
|
|
|
};
|
2021-03-22 18:51:53 +00:00
|
|
|
|
const markAttachmentAsCorrupted = (options: AttachmentOptions) => {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(options.messageId);
|
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`markAttachmentAsCorrupted: Message ${options.messageId} missing!`
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-03-22 18:51:53 +00:00
|
|
|
|
message.markAttachmentAsCorrupted(options.attachment);
|
|
|
|
|
};
|
2021-08-12 18:15:55 +00:00
|
|
|
|
const onMarkViewed = (messageId: string): void => {
|
|
|
|
|
const message = window.MessageController.getById(messageId);
|
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(`onMarkViewed: Message ${messageId} missing!`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.get('readStatus') === ReadStatus.Viewed) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-17 15:43:26 +00:00
|
|
|
|
const senderE164 = message.get('source');
|
|
|
|
|
const senderUuid = message.get('sourceUuid');
|
|
|
|
|
const timestamp = message.get('sent_at');
|
|
|
|
|
|
2021-08-12 18:15:55 +00:00
|
|
|
|
message.set(markViewed(message.attributes, Date.now()));
|
2021-08-17 15:43:26 +00:00
|
|
|
|
|
2021-11-03 17:02:26 +00:00
|
|
|
|
if (isIncoming(message.attributes)) {
|
|
|
|
|
viewedReceiptsJobQueue.add({
|
|
|
|
|
viewedReceipt: {
|
|
|
|
|
messageId,
|
|
|
|
|
senderE164,
|
|
|
|
|
senderUuid,
|
|
|
|
|
timestamp,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-08-17 15:43:26 +00:00
|
|
|
|
|
2021-08-12 18:15:55 +00:00
|
|
|
|
viewSyncJobQueue.add({
|
|
|
|
|
viewSyncs: [
|
|
|
|
|
{
|
|
|
|
|
messageId,
|
2021-08-17 15:43:26 +00:00
|
|
|
|
senderE164,
|
|
|
|
|
senderUuid,
|
|
|
|
|
timestamp,
|
2021-08-12 18:15:55 +00:00
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
};
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const showVisualAttachment = (options: {
|
2021-07-14 23:39:52 +00:00
|
|
|
|
attachment: AttachmentType;
|
2021-06-08 14:59:38 +00:00
|
|
|
|
messageId: string;
|
|
|
|
|
showSingle?: boolean;
|
|
|
|
|
}) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.showLightbox(options);
|
|
|
|
|
};
|
2021-08-16 21:35:54 +00:00
|
|
|
|
const downloadAttachment = (options: {
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
timestamp: number;
|
|
|
|
|
isDangerous: boolean;
|
|
|
|
|
}) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.downloadAttachment(options);
|
|
|
|
|
};
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const displayTapToViewMessage = (messageId: string) =>
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.displayTapToViewMessage(messageId);
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const showIdentity = (conversationId: string) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.showSafetyNumber(conversationId);
|
|
|
|
|
};
|
2022-05-11 20:59:58 +00:00
|
|
|
|
const openGiftBadge = (messageId: string): void => {
|
|
|
|
|
const message = window.MessageController.getById(messageId);
|
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(`openGiftBadge: Message ${messageId} missing!`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
showToast(ToastCannotOpenGiftBadge, {
|
|
|
|
|
isIncoming: isIncoming(message.attributes),
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2021-09-21 20:45:25 +00:00
|
|
|
|
const openLink = openLinkInWebBrowser;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const downloadNewVersion = () => {
|
2021-09-21 20:45:25 +00:00
|
|
|
|
openLinkInWebBrowser('https://signal.org/download');
|
2020-09-24 20:57:54 +00:00
|
|
|
|
};
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const showSafetyNumber = (contactId: string) => {
|
|
|
|
|
this.showSafetyNumber(contactId);
|
|
|
|
|
};
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const showExpiredIncomingTapToViewToast = () => {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('Showing expired tap-to-view toast for an incoming message');
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastTapToViewExpiredIncoming);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
};
|
|
|
|
|
const showExpiredOutgoingTapToViewToast = () => {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('Showing expired tap-to-view toast for an outgoing message');
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastTapToViewExpiredOutgoing);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
};
|
2021-08-30 21:32:56 +00:00
|
|
|
|
|
2021-04-27 22:35:35 +00:00
|
|
|
|
const showForwardMessageModal = this.showForwardMessageModal.bind(this);
|
2022-04-12 00:26:09 +00:00
|
|
|
|
const startConversation = this.startConversation.bind(this);
|
2021-03-24 22:06:12 +00:00
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
deleteMessage,
|
|
|
|
|
deleteMessageForEveryone,
|
|
|
|
|
displayTapToViewMessage,
|
|
|
|
|
downloadAttachment,
|
|
|
|
|
downloadNewVersion,
|
|
|
|
|
kickOffAttachmentDownload,
|
|
|
|
|
markAttachmentAsCorrupted,
|
2021-08-12 18:15:55 +00:00
|
|
|
|
markViewed: onMarkViewed,
|
2021-03-24 22:06:12 +00:00
|
|
|
|
openConversation,
|
2022-05-11 20:59:58 +00:00
|
|
|
|
openGiftBadge,
|
2021-03-24 22:06:12 +00:00
|
|
|
|
openLink,
|
|
|
|
|
reactToMessage,
|
|
|
|
|
replyToMessage,
|
|
|
|
|
retrySend,
|
2022-03-04 19:22:31 +00:00
|
|
|
|
retryDeleteForEveryone,
|
2021-03-24 22:06:12 +00:00
|
|
|
|
showContactDetail,
|
|
|
|
|
showContactModal,
|
2021-06-16 00:44:14 +00:00
|
|
|
|
showSafetyNumber,
|
2021-03-24 22:06:12 +00:00
|
|
|
|
showExpiredIncomingTapToViewToast,
|
|
|
|
|
showExpiredOutgoingTapToViewToast,
|
2021-04-27 22:35:35 +00:00
|
|
|
|
showForwardMessageModal,
|
2021-03-24 22:06:12 +00:00
|
|
|
|
showIdentity,
|
|
|
|
|
showMessageDetail,
|
|
|
|
|
showVisualAttachment,
|
2022-04-12 00:26:09 +00:00
|
|
|
|
startConversation,
|
2021-03-24 22:06:12 +00:00
|
|
|
|
};
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-03-24 22:06:12 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async scrollToMessage(messageId: string): Promise<void> {
|
2021-12-10 22:51:54 +00:00
|
|
|
|
const message = await getMessageById(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(`scrollToMessage: failed to load message ${messageId}`);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-23 22:57:39 +00:00
|
|
|
|
const state = window.reduxStore.getState();
|
|
|
|
|
|
|
|
|
|
let isInMemory = true;
|
|
|
|
|
|
|
|
|
|
if (!window.MessageController.getById(messageId)) {
|
|
|
|
|
isInMemory = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Message might be in memory, but not in the redux anymore because
|
|
|
|
|
// we call `messageReset()` in `loadAndScroll()`.
|
2021-11-11 22:43:05 +00:00
|
|
|
|
const messagesByConversation =
|
|
|
|
|
getMessagesByConversation(state)[this.model.id];
|
2021-06-23 22:57:39 +00:00
|
|
|
|
if (!messagesByConversation?.messageIds.includes(messageId)) {
|
|
|
|
|
isInMemory = false;
|
|
|
|
|
}
|
2021-06-16 00:44:14 +00:00
|
|
|
|
|
|
|
|
|
if (isInMemory) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const { scrollToMessage } = window.reduxActions.conversations;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
scrollToMessage(this.model.id, messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-30 11:25:24 +00:00
|
|
|
|
this.model.loadAndScroll(messageId);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2020-12-01 16:42:35 +00:00
|
|
|
|
async startMigrationToGV2(): Promise<void> {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const logId = this.model.idForLogging();
|
2020-12-01 16:42:35 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
if (!isGroupV1(this.model.attributes)) {
|
2020-12-01 16:42:35 +00:00
|
|
|
|
throw new Error(
|
|
|
|
|
`startMigrationToGV2/${logId}: Cannot start, not a GroupV1 group`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onClose = () => {
|
|
|
|
|
if (this.migrationDialog) {
|
|
|
|
|
this.migrationDialog.remove();
|
|
|
|
|
this.migrationDialog = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
onClose();
|
|
|
|
|
|
|
|
|
|
const migrate = () => {
|
|
|
|
|
onClose();
|
|
|
|
|
|
|
|
|
|
this.longRunningTaskWrapper({
|
|
|
|
|
name: 'initiateMigrationToGroupV2',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
task: () => window.Signal.Groups.initiateMigrationToGroupV2(this.model),
|
2020-12-01 16:42:35 +00:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2020-12-01 23:45:39 +00:00
|
|
|
|
// Note: this call will throw if, after generating member lists, we are no longer a
|
|
|
|
|
// member or are in the pending member list.
|
2021-11-11 22:43:05 +00:00
|
|
|
|
const { droppedGV2MemberIds, pendingMembersV2 } =
|
|
|
|
|
await this.longRunningTaskWrapper({
|
|
|
|
|
name: 'getGroupMigrationMembers',
|
|
|
|
|
task: () => window.Signal.Groups.getGroupMigrationMembers(this.model),
|
|
|
|
|
});
|
2020-12-01 16:42:35 +00:00
|
|
|
|
|
|
|
|
|
const invitedMemberIds = pendingMembersV2.map(
|
2021-10-26 22:59:08 +00:00
|
|
|
|
(item: GroupV2PendingMemberType) => item.uuid
|
2020-12-01 16:42:35 +00:00
|
|
|
|
);
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.migrationDialog = new ReactWrapperView({
|
2020-12-01 16:42:35 +00:00
|
|
|
|
className: 'group-v1-migration-wrapper',
|
|
|
|
|
JSX: window.Signal.State.Roots.createGroupV1MigrationModal(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
{
|
2020-12-01 23:45:39 +00:00
|
|
|
|
areWeInvited: false,
|
2020-12-01 16:42:35 +00:00
|
|
|
|
droppedMemberIds: droppedGV2MemberIds,
|
|
|
|
|
hasMigrated: false,
|
|
|
|
|
invitedMemberIds,
|
|
|
|
|
migrate,
|
|
|
|
|
onClose,
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-09-24 20:02:30 +00:00
|
|
|
|
// TODO DESKTOP-2426
|
|
|
|
|
async processAttachments(files: Array<File>): Promise<void> {
|
2022-02-11 20:18:04 +00:00
|
|
|
|
const state = window.reduxStore.getState();
|
|
|
|
|
|
|
|
|
|
const isRecording =
|
|
|
|
|
state.audioRecorder.recordingState === RecordingState.Recording;
|
|
|
|
|
|
2022-06-17 00:48:57 +00:00
|
|
|
|
if (hasLinkPreviewLoaded() || isRecording) {
|
2022-01-21 21:46:05 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-24 20:02:30 +00:00
|
|
|
|
const {
|
|
|
|
|
addAttachment,
|
|
|
|
|
addPendingAttachment,
|
|
|
|
|
processAttachments,
|
|
|
|
|
removeAttachment,
|
|
|
|
|
} = window.reduxActions.composer;
|
|
|
|
|
|
|
|
|
|
await processAttachments({
|
|
|
|
|
addAttachment,
|
|
|
|
|
addPendingAttachment,
|
|
|
|
|
conversationId: this.model.id,
|
|
|
|
|
draftAttachments: this.model.get('draftAttachments') || [],
|
|
|
|
|
files,
|
|
|
|
|
onShowToast: (toastType: AttachmentToastType) => {
|
|
|
|
|
if (toastType === AttachmentToastType.ToastFileSize) {
|
|
|
|
|
showToast(ToastFileSize, {
|
|
|
|
|
limit: 100,
|
|
|
|
|
units: 'MB',
|
|
|
|
|
});
|
|
|
|
|
} else if (toastType === AttachmentToastType.ToastDangerousFileType) {
|
|
|
|
|
showToast(ToastDangerousFileType);
|
|
|
|
|
} else if (toastType === AttachmentToastType.ToastMaxAttachments) {
|
|
|
|
|
showToast(ToastMaxAttachments);
|
|
|
|
|
} else if (toastType === AttachmentToastType.ToastOneNonImageAtATime) {
|
|
|
|
|
showToast(ToastOneNonImageAtATime);
|
|
|
|
|
} else if (
|
|
|
|
|
toastType ===
|
|
|
|
|
AttachmentToastType.ToastCannotMixImageAndNonImageAttachments
|
|
|
|
|
) {
|
|
|
|
|
showToast(ToastCannotMixImageAndNonImageAttachments);
|
|
|
|
|
} else if (
|
|
|
|
|
toastType === AttachmentToastType.ToastUnableToLoadAttachment
|
|
|
|
|
) {
|
|
|
|
|
showToast(ToastUnableToLoadAttachment);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
removeAttachment,
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
unload(reason: string): void {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info(
|
2020-09-24 20:57:54 +00:00
|
|
|
|
'unloading conversation',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.idForLogging(),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
'due to:',
|
|
|
|
|
reason
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const { conversationUnloaded } = window.reduxActions.conversations;
|
|
|
|
|
if (conversationUnloaded) {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
conversationUnloaded(this.model.id);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.model.get('draftChanged')) {
|
|
|
|
|
if (this.model.hasDraft()) {
|
2021-10-19 00:09:55 +00:00
|
|
|
|
const now = Date.now();
|
|
|
|
|
const active_at = this.model.get('active_at') || now;
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.model.set({
|
2021-10-19 00:09:55 +00:00
|
|
|
|
active_at,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
draftChanged: false,
|
2021-10-19 00:09:55 +00:00
|
|
|
|
draftTimestamp: now,
|
|
|
|
|
timestamp: now,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.model.set({
|
|
|
|
|
draftChanged: false,
|
|
|
|
|
draftTimestamp: null,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We don't wait here; we need to take down the view
|
|
|
|
|
this.saveModel();
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.updateLastMessage();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-05 16:47:06 +00:00
|
|
|
|
this.conversationView?.remove();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2020-11-11 17:36:05 +00:00
|
|
|
|
if (this.contactModalView) {
|
|
|
|
|
this.contactModalView.remove();
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (this.stickerPreviewModalView) {
|
|
|
|
|
this.stickerPreviewModalView.remove();
|
|
|
|
|
}
|
|
|
|
|
if (this.lightboxView) {
|
|
|
|
|
this.lightboxView.remove();
|
|
|
|
|
}
|
|
|
|
|
if (this.panels && this.panels.length) {
|
|
|
|
|
for (let i = 0, max = this.panels.length; i < max; i += 1) {
|
|
|
|
|
const panel = this.panels[i];
|
2022-06-03 16:33:39 +00:00
|
|
|
|
panel.view.remove();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
2020-10-30 17:52:21 +00:00
|
|
|
|
window.reduxActions.conversations.setSelectedConversationPanelDepth(0);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-17 00:48:57 +00:00
|
|
|
|
removeLinkPreview();
|
|
|
|
|
suspendLinkPreviews();
|
2021-08-23 18:40:49 +00:00
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.remove();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async onDrop(e: JQuery.TriggeredEvent): Promise<void> {
|
|
|
|
|
if (!e.originalEvent) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const event = e.originalEvent as DragEvent;
|
|
|
|
|
if (!event.dataTransfer) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
if (event.dataTransfer.types[0] !== 'Files') {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const { files } = event.dataTransfer;
|
2021-09-24 20:02:30 +00:00
|
|
|
|
this.processAttachments(Array.from(files));
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onPaste(e: JQuery.TriggeredEvent): void {
|
|
|
|
|
if (!e.originalEvent) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const event = e.originalEvent as ClipboardEvent;
|
|
|
|
|
if (!event.clipboardData) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const { items } = event.clipboardData;
|
|
|
|
|
|
|
|
|
|
const anyImages = [...items].some(
|
|
|
|
|
item => item.type.split('/')[0] === 'image'
|
|
|
|
|
);
|
|
|
|
|
if (!anyImages) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
e.preventDefault();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-09-24 20:02:30 +00:00
|
|
|
|
const files: Array<File> = [];
|
2020-09-24 20:57:54 +00:00
|
|
|
|
for (let i = 0; i < items.length; i += 1) {
|
|
|
|
|
if (items[i].type.split('/')[0] === 'image') {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const file = items[i].getAsFile();
|
|
|
|
|
if (file) {
|
2021-09-24 20:02:30 +00:00
|
|
|
|
files.push(file);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-09-24 20:02:30 +00:00
|
|
|
|
|
|
|
|
|
this.processAttachments(files);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-04-20 14:46:09 +00:00
|
|
|
|
syncMessageRequestResponse(
|
|
|
|
|
name: string,
|
2021-06-01 23:30:25 +00:00
|
|
|
|
model: ConversationModel,
|
2021-04-20 14:46:09 +00:00
|
|
|
|
messageRequestType: number
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
return this.longRunningTaskWrapper({
|
|
|
|
|
name,
|
|
|
|
|
task: model.syncMessageRequestResponse.bind(model, messageRequestType),
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-04-20 14:46:09 +00:00
|
|
|
|
|
2021-06-01 23:30:25 +00:00
|
|
|
|
blockAndReportSpam(model: ConversationModel): Promise<void> {
|
2021-07-09 19:36:10 +00:00
|
|
|
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
2021-05-27 20:17:05 +00:00
|
|
|
|
|
|
|
|
|
return this.longRunningTaskWrapper({
|
|
|
|
|
name: 'blockAndReportSpam',
|
|
|
|
|
task: async () => {
|
|
|
|
|
await Promise.all([
|
|
|
|
|
model.syncMessageRequestResponse(messageRequestEnum.BLOCK),
|
|
|
|
|
addReportSpamJob({
|
|
|
|
|
conversation: model.format(),
|
|
|
|
|
getMessageServerGuidsForSpam:
|
|
|
|
|
window.Signal.Data.getMessageServerGuidsForSpam,
|
|
|
|
|
jobQueue: reportSpamJobQueue,
|
|
|
|
|
}),
|
|
|
|
|
]);
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastReportedSpamAndBlocked);
|
2021-05-27 20:17:05 +00:00
|
|
|
|
},
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-05-27 20:17:05 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async saveModel(): Promise<void> {
|
|
|
|
|
window.Signal.Data.updateConversation(this.model.attributes);
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async clearAttachments(): Promise<void> {
|
|
|
|
|
const draftAttachments = this.model.get('draftAttachments') || [];
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.model.set({
|
|
|
|
|
draftAttachments: [],
|
|
|
|
|
draftChanged: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.updateAttachmentsView();
|
|
|
|
|
|
|
|
|
|
// We're fine doing this all at once; at most it should be 32 attachments
|
|
|
|
|
await Promise.all([
|
|
|
|
|
this.saveModel(),
|
|
|
|
|
Promise.all(
|
2021-09-24 20:02:30 +00:00
|
|
|
|
draftAttachments.map(attachment => deleteDraftAttachment(attachment))
|
2020-09-24 20:57:54 +00:00
|
|
|
|
),
|
|
|
|
|
]);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-09-08 23:23:44 +00:00
|
|
|
|
hasFiles(options: { includePending: boolean }): boolean {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const draftAttachments = this.model.get('draftAttachments') || [];
|
2021-09-08 23:23:44 +00:00
|
|
|
|
if (options.includePending) {
|
|
|
|
|
return draftAttachments.length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return draftAttachments.some(item => !item.pending);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
updateAttachmentsView(): void {
|
|
|
|
|
const draftAttachments = this.model.get('draftAttachments') || [];
|
2021-06-25 16:08:16 +00:00
|
|
|
|
window.reduxActions.composer.replaceAttachments(
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.get('id'),
|
2021-09-24 20:02:30 +00:00
|
|
|
|
draftAttachments
|
2021-06-25 16:08:16 +00:00
|
|
|
|
);
|
2021-09-08 23:23:44 +00:00
|
|
|
|
if (this.hasFiles({ includePending: true })) {
|
2022-06-17 00:48:57 +00:00
|
|
|
|
removeLinkPreview();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async onOpened(messageId: string): Promise<void> {
|
2022-01-20 00:40:29 +00:00
|
|
|
|
this.model.onOpenStart();
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (messageId) {
|
2021-12-10 22:51:54 +00:00
|
|
|
|
const message = await getMessageById(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
if (message) {
|
2021-11-30 11:25:24 +00:00
|
|
|
|
this.model.loadAndScroll(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.warn(`onOpened: Did not find message ${messageId}`);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-28 19:11:19 +00:00
|
|
|
|
const { retryPlaceholders } = window.Signal.Services;
|
|
|
|
|
if (retryPlaceholders) {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
await retryPlaceholders.findByConversationAndMarkOpened(this.model.id);
|
2021-05-28 19:11:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-07 18:20:29 +00:00
|
|
|
|
const loadAndUpdate = async () => {
|
|
|
|
|
await this.model.loadNewestMessages(undefined, undefined);
|
|
|
|
|
await this.model.updateLastMessage();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loadAndUpdate();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
this.focusMessageField();
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const quotedMessageId = this.model.get('quotedMessageId');
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (quotedMessageId) {
|
|
|
|
|
this.setQuoteMessage(quotedMessageId);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.fetchLatestGroupV2Data();
|
|
|
|
|
strictAssert(
|
|
|
|
|
this.model.throttledMaybeMigrateV1Group !== undefined,
|
2021-05-28 19:11:19 +00:00
|
|
|
|
'Conversation model should be initialized'
|
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.throttledMaybeMigrateV1Group();
|
|
|
|
|
strictAssert(
|
|
|
|
|
this.model.throttledFetchSMSOnlyUUID !== undefined,
|
2021-05-13 20:57:27 +00:00
|
|
|
|
'Conversation model should be initialized'
|
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.throttledFetchSMSOnlyUUID();
|
|
|
|
|
|
2022-02-23 01:06:19 +00:00
|
|
|
|
const ourUuid = window.textsecure.storage.user.getUuid(UUIDKind.ACI);
|
|
|
|
|
if (
|
|
|
|
|
!isGroup(this.model.attributes) ||
|
|
|
|
|
(ourUuid && this.model.hasMember(ourUuid.toString()))
|
|
|
|
|
) {
|
|
|
|
|
strictAssert(
|
|
|
|
|
this.model.throttledGetProfiles !== undefined,
|
|
|
|
|
'Conversation model should be initialized'
|
|
|
|
|
);
|
|
|
|
|
await this.model.throttledGetProfiles();
|
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
|
|
|
|
|
this.model.updateVerified();
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-09-07 16:12:26 +00:00
|
|
|
|
async showForwardMessageModal(messageId: string): Promise<void> {
|
2022-01-31 21:52:09 +00:00
|
|
|
|
const message = await getMessageById(messageId);
|
|
|
|
|
if (!message) {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
throw new Error(`showForwardMessageModal: Message ${messageId} missing!`);
|
2021-04-27 22:35:35 +00:00
|
|
|
|
}
|
2021-06-17 17:15:10 +00:00
|
|
|
|
const attachments = getAttachmentsForMessage(message.attributes);
|
2021-11-15 21:54:33 +00:00
|
|
|
|
|
2022-04-11 20:57:44 +00:00
|
|
|
|
const doForwardMessage = async (
|
|
|
|
|
conversationIds: Array<string>,
|
|
|
|
|
messageBody?: string,
|
|
|
|
|
includedAttachments?: Array<AttachmentType>,
|
|
|
|
|
linkPreview?: LinkPreviewType
|
|
|
|
|
) => {
|
|
|
|
|
try {
|
|
|
|
|
const didForwardSuccessfully = await this.maybeForwardMessage(
|
|
|
|
|
message,
|
|
|
|
|
conversationIds,
|
|
|
|
|
messageBody,
|
|
|
|
|
includedAttachments,
|
|
|
|
|
linkPreview
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (didForwardSuccessfully && this.forwardMessageModal) {
|
|
|
|
|
this.forwardMessageModal.remove();
|
|
|
|
|
this.forwardMessageModal = undefined;
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
log.warn('doForwardMessage', err && err.stack ? err.stack : err);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.forwardMessageModal = new ReactWrapperView({
|
2021-04-27 22:35:35 +00:00
|
|
|
|
JSX: window.Signal.State.Roots.createForwardMessageModal(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
{
|
2021-12-03 01:05:32 +00:00
|
|
|
|
attachments,
|
2022-04-11 20:57:44 +00:00
|
|
|
|
doForwardMessage,
|
2022-04-12 19:37:10 +00:00
|
|
|
|
hasContact: Boolean(message.get('contact')?.length),
|
2021-04-27 22:35:35 +00:00
|
|
|
|
isSticker: Boolean(message.get('sticker')),
|
|
|
|
|
messageBody: message.getRawText(),
|
|
|
|
|
onClose: () => {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
if (this.forwardMessageModal) {
|
|
|
|
|
this.forwardMessageModal.remove();
|
|
|
|
|
this.forwardMessageModal = undefined;
|
|
|
|
|
}
|
2022-06-17 00:48:57 +00:00
|
|
|
|
resetLinkPreview();
|
2021-04-27 22:35:35 +00:00
|
|
|
|
},
|
|
|
|
|
onEditorStateChange: (
|
|
|
|
|
messageText: string,
|
2021-08-30 21:32:56 +00:00
|
|
|
|
_: Array<BodyRangeType>,
|
2021-04-27 22:35:35 +00:00
|
|
|
|
caretLocation?: number
|
|
|
|
|
) => {
|
|
|
|
|
if (!attachments.length) {
|
2022-06-17 00:48:57 +00:00
|
|
|
|
maybeGrabLinkPreview(
|
|
|
|
|
messageText,
|
|
|
|
|
LinkPreviewSourceType.ForwardMessageModal,
|
|
|
|
|
caretLocation
|
|
|
|
|
);
|
2021-04-27 22:35:35 +00:00
|
|
|
|
}
|
|
|
|
|
},
|
2021-09-22 20:59:54 +00:00
|
|
|
|
onTextTooLong: () => showToast(ToastMessageBodyTooLong),
|
2021-04-27 22:35:35 +00:00
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
this.forwardMessageModal.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-04-27 22:35:35 +00:00
|
|
|
|
|
|
|
|
|
async maybeForwardMessage(
|
|
|
|
|
message: MessageModel,
|
|
|
|
|
conversationIds: Array<string>,
|
|
|
|
|
messageBody?: string,
|
2021-12-03 01:05:32 +00:00
|
|
|
|
attachments?: Array<AttachmentType>,
|
2021-04-27 22:35:35 +00:00
|
|
|
|
linkPreview?: LinkPreviewType
|
|
|
|
|
): Promise<boolean> {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info(`maybeForwardMessage/${message.idForLogging()}: Starting...`);
|
2021-04-27 22:35:35 +00:00
|
|
|
|
const attachmentLookup = new Set();
|
|
|
|
|
if (attachments) {
|
|
|
|
|
attachments.forEach(attachment => {
|
|
|
|
|
attachmentLookup.add(
|
|
|
|
|
`${attachment.fileName}/${attachment.contentType}`
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const conversations = conversationIds.map(id =>
|
|
|
|
|
window.ConversationController.get(id)
|
|
|
|
|
);
|
|
|
|
|
|
2021-07-20 20:18:35 +00:00
|
|
|
|
const cannotSend = conversations.some(
|
|
|
|
|
conversation =>
|
|
|
|
|
conversation?.get('announcementsOnly') && !conversation.areWeAdmin()
|
|
|
|
|
);
|
2021-07-21 20:45:41 +00:00
|
|
|
|
if (cannotSend) {
|
2021-07-20 20:18:35 +00:00
|
|
|
|
throw new Error('Cannot send to group');
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-27 22:35:35 +00:00
|
|
|
|
// Verify that all contacts that we're forwarding
|
|
|
|
|
// to are verified and trusted
|
|
|
|
|
const unverifiedContacts: Array<ConversationModel> = [];
|
|
|
|
|
const untrustedContacts: Array<ConversationModel> = [];
|
|
|
|
|
await Promise.all(
|
|
|
|
|
conversations.map(async conversation => {
|
|
|
|
|
if (conversation) {
|
|
|
|
|
await conversation.updateVerified();
|
|
|
|
|
const unverifieds = conversation.getUnverified();
|
|
|
|
|
if (unverifieds.length) {
|
|
|
|
|
unverifieds.forEach(unverifiedConversation =>
|
|
|
|
|
unverifiedContacts.push(unverifiedConversation)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const untrusted = conversation.getUntrusted();
|
|
|
|
|
if (untrusted.length) {
|
|
|
|
|
untrusted.forEach(untrustedConversation =>
|
|
|
|
|
untrustedContacts.push(untrustedConversation)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// If there are any unverified or untrusted contacts, show the
|
|
|
|
|
// SendAnywayDialog and if we're fine with sending then mark all as
|
|
|
|
|
// verified and trusted and continue the send.
|
|
|
|
|
const iffyConversations = [...unverifiedContacts, ...untrustedContacts];
|
|
|
|
|
if (iffyConversations.length) {
|
|
|
|
|
const forwardMessageModal = document.querySelector<HTMLElement>(
|
|
|
|
|
'.module-ForwardMessageModal'
|
|
|
|
|
);
|
|
|
|
|
if (forwardMessageModal) {
|
|
|
|
|
forwardMessageModal.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
const sendAnyway = await this.showSendAnywayDialog(iffyConversations);
|
|
|
|
|
|
|
|
|
|
if (!sendAnyway) {
|
|
|
|
|
if (forwardMessageModal) {
|
|
|
|
|
forwardMessageModal.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let verifyPromise: Promise<void> | undefined;
|
|
|
|
|
let approvePromise: Promise<void> | undefined;
|
|
|
|
|
if (unverifiedContacts.length) {
|
2021-09-27 16:29:00 +00:00
|
|
|
|
verifyPromise = markAllAsVerifiedDefault(unverifiedContacts);
|
2021-04-27 22:35:35 +00:00
|
|
|
|
}
|
|
|
|
|
if (untrustedContacts.length) {
|
2021-09-27 16:29:00 +00:00
|
|
|
|
approvePromise = markAllAsApproved(untrustedContacts);
|
2021-04-27 22:35:35 +00:00
|
|
|
|
}
|
|
|
|
|
await Promise.all([verifyPromise, approvePromise]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sendMessageOptions = { dontClearDraft: true };
|
2021-06-24 17:38:52 +00:00
|
|
|
|
const baseTimestamp = Date.now();
|
2021-04-27 22:35:35 +00:00
|
|
|
|
|
|
|
|
|
// Actually send the message
|
|
|
|
|
// load any sticker data, attachments, or link previews that we need to
|
|
|
|
|
// send along with the message and do the send to each conversation.
|
|
|
|
|
await Promise.all(
|
2021-06-24 17:38:52 +00:00
|
|
|
|
conversations.map(async (conversation, offset) => {
|
|
|
|
|
const timestamp = baseTimestamp + offset;
|
2021-04-27 22:35:35 +00:00
|
|
|
|
if (conversation) {
|
|
|
|
|
const sticker = message.get('sticker');
|
2022-04-11 20:57:44 +00:00
|
|
|
|
const contact = message.get('contact');
|
|
|
|
|
|
2021-04-27 22:35:35 +00:00
|
|
|
|
if (sticker) {
|
|
|
|
|
const stickerWithData = await loadStickerData(sticker);
|
2021-06-03 21:26:56 +00:00
|
|
|
|
const stickerNoPath = stickerWithData
|
|
|
|
|
? {
|
|
|
|
|
...stickerWithData,
|
|
|
|
|
data: {
|
|
|
|
|
...stickerWithData.data,
|
|
|
|
|
path: undefined,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
: undefined;
|
|
|
|
|
|
2021-08-31 20:58:39 +00:00
|
|
|
|
conversation.enqueueMessageForSend(
|
2022-04-11 20:57:44 +00:00
|
|
|
|
{
|
|
|
|
|
body: undefined,
|
|
|
|
|
attachments: [],
|
|
|
|
|
sticker: stickerNoPath,
|
|
|
|
|
},
|
|
|
|
|
{ ...sendMessageOptions, timestamp }
|
|
|
|
|
);
|
2022-04-12 19:37:10 +00:00
|
|
|
|
} else if (contact?.length) {
|
2022-04-11 20:57:44 +00:00
|
|
|
|
const contactWithHydratedAvatar = await loadContactData(contact);
|
|
|
|
|
conversation.enqueueMessageForSend(
|
|
|
|
|
{
|
|
|
|
|
body: undefined,
|
|
|
|
|
attachments: [],
|
|
|
|
|
contact: contactWithHydratedAvatar,
|
|
|
|
|
},
|
2021-06-03 21:26:56 +00:00
|
|
|
|
{ ...sendMessageOptions, timestamp }
|
2021-04-27 22:35:35 +00:00
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
const preview = linkPreview
|
|
|
|
|
? await loadPreviewData([linkPreview])
|
|
|
|
|
: [];
|
2021-06-03 21:26:56 +00:00
|
|
|
|
const attachmentsWithData = await Promise.all(
|
|
|
|
|
(attachments || []).map(async item => ({
|
|
|
|
|
...(await loadAttachmentData(item)),
|
|
|
|
|
path: undefined,
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
const attachmentsToSend = attachmentsWithData.filter(
|
2021-04-27 22:35:35 +00:00
|
|
|
|
(attachment: Partial<AttachmentType>) =>
|
|
|
|
|
attachmentLookup.has(
|
|
|
|
|
`${attachment.fileName}/${attachment.contentType}`
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
2021-08-31 20:58:39 +00:00
|
|
|
|
conversation.enqueueMessageForSend(
|
2022-04-11 20:57:44 +00:00
|
|
|
|
{
|
|
|
|
|
body: messageBody || undefined,
|
|
|
|
|
attachments: attachmentsToSend,
|
|
|
|
|
preview,
|
|
|
|
|
},
|
2021-06-03 21:26:56 +00:00
|
|
|
|
{ ...sendMessageOptions, timestamp }
|
2021-04-27 22:35:35 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
2021-05-04 14:57:14 +00:00
|
|
|
|
// Cancel any link still pending, even if it didn't make it into the message
|
2022-06-17 00:48:57 +00:00
|
|
|
|
resetLinkPreview();
|
2021-04-27 22:35:35 +00:00
|
|
|
|
|
|
|
|
|
return true;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-04-27 22:35:35 +00:00
|
|
|
|
|
2021-09-29 20:18:27 +00:00
|
|
|
|
showAllMedia(): void {
|
2021-10-13 17:05:18 +00:00
|
|
|
|
if (document.querySelectorAll('.module-media-gallery').length) {
|
2021-09-29 20:18:27 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
// We fetch more documents than media as they don’t require to be loaded
|
|
|
|
|
// into memory right away. Revisit this once we have infinite scrolling:
|
|
|
|
|
const DEFAULT_MEDIA_FETCH_COUNT = 50;
|
|
|
|
|
const DEFAULT_DOCUMENTS_FETCH_COUNT = 150;
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const conversationId = this.model.get('id');
|
2021-12-20 21:04:02 +00:00
|
|
|
|
const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
const getProps = async () => {
|
2021-11-11 22:43:05 +00:00
|
|
|
|
const rawMedia =
|
|
|
|
|
await window.Signal.Data.getMessagesWithVisualMediaAttachments(
|
|
|
|
|
conversationId,
|
|
|
|
|
{
|
|
|
|
|
limit: DEFAULT_MEDIA_FETCH_COUNT,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
const rawDocuments =
|
|
|
|
|
await window.Signal.Data.getMessagesWithFileAttachments(
|
|
|
|
|
conversationId,
|
|
|
|
|
{
|
|
|
|
|
limit: DEFAULT_DOCUMENTS_FETCH_COUNT,
|
|
|
|
|
}
|
|
|
|
|
);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
// First we upgrade these messages to ensure that they have thumbnails
|
|
|
|
|
for (let max = rawMedia.length, i = 0; i < max; i += 1) {
|
|
|
|
|
const message = rawMedia[i];
|
|
|
|
|
const { schemaVersion } = message;
|
|
|
|
|
|
2021-06-17 17:15:10 +00:00
|
|
|
|
if (
|
|
|
|
|
schemaVersion &&
|
|
|
|
|
schemaVersion < Message.VERSION_NEEDED_FOR_DISPLAY
|
|
|
|
|
) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
// Yep, we really do want to wait for each of these
|
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
|
|
|
|
rawMedia[i] = await upgradeMessageSchema(message);
|
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2021-12-20 21:04:02 +00:00
|
|
|
|
await window.Signal.Data.saveMessage(rawMedia[i], { ourUuid });
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const media: Array<MediaType> = flatten(
|
2020-09-24 20:57:54 +00:00
|
|
|
|
rawMedia.map(message => {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
return (message.attachments || []).map(
|
|
|
|
|
(
|
|
|
|
|
attachment: AttachmentType,
|
|
|
|
|
index: number
|
|
|
|
|
): MediaType | undefined => {
|
|
|
|
|
if (
|
|
|
|
|
!attachment.path ||
|
|
|
|
|
!attachment.thumbnail ||
|
|
|
|
|
attachment.pending ||
|
|
|
|
|
attachment.error
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const { thumbnail } = attachment;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
path: attachment.path,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
objectURL: getAbsoluteAttachmentPath(attachment.path),
|
2022-06-10 01:10:20 +00:00
|
|
|
|
thumbnailObjectUrl: thumbnail?.path
|
2020-09-24 20:57:54 +00:00
|
|
|
|
? getAbsoluteAttachmentPath(thumbnail.path)
|
2021-08-30 21:32:56 +00:00
|
|
|
|
: undefined,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index,
|
|
|
|
|
attachment,
|
2021-08-24 21:47:14 +00:00
|
|
|
|
message: {
|
|
|
|
|
attachments: message.attachments || [],
|
|
|
|
|
conversationId:
|
|
|
|
|
window.ConversationController.get(
|
|
|
|
|
window.ConversationController.ensureContactIds({
|
|
|
|
|
uuid: message.sourceUuid,
|
|
|
|
|
e164: message.source,
|
|
|
|
|
})
|
|
|
|
|
)?.id || message.conversationId,
|
|
|
|
|
id: message.id,
|
|
|
|
|
received_at: message.received_at,
|
|
|
|
|
received_at_ms: Number(message.received_at_ms),
|
2021-08-25 21:08:32 +00:00
|
|
|
|
sent_at: message.sent_at,
|
2021-08-24 21:47:14 +00:00
|
|
|
|
},
|
2020-09-24 20:57:54 +00:00
|
|
|
|
};
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
|
|
|
|
);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
})
|
2021-08-30 21:32:56 +00:00
|
|
|
|
).filter(isNotNil);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
// Unlike visual media, only one non-image attachment is supported
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const documents: Array<MediaItemType> = [];
|
|
|
|
|
rawDocuments.forEach(message => {
|
|
|
|
|
const attachments = message.attachments || [];
|
|
|
|
|
const attachment = attachments[0];
|
|
|
|
|
if (!attachment) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
documents.push({
|
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index: 0,
|
|
|
|
|
attachment,
|
|
|
|
|
// We do this cast because we know there attachments (see the checks above).
|
|
|
|
|
message: message as MessageAttributesType & {
|
|
|
|
|
attachments: Array<AttachmentType>;
|
|
|
|
|
},
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
});
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-16 21:35:54 +00:00
|
|
|
|
const saveAttachment = async ({
|
|
|
|
|
attachment,
|
|
|
|
|
message,
|
|
|
|
|
}: {
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
message: Pick<MessageAttributesType, 'sent_at'>;
|
|
|
|
|
}) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const timestamp = message.sent_at;
|
2021-09-24 00:49:05 +00:00
|
|
|
|
const fullPath = await Attachment.save({
|
2020-09-24 20:57:54 +00:00
|
|
|
|
attachment,
|
|
|
|
|
readAttachmentData,
|
|
|
|
|
saveAttachmentToDisk,
|
|
|
|
|
timestamp,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (fullPath) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastFileSaved, {
|
|
|
|
|
onOpenFile: () => {
|
|
|
|
|
openFileInFolder(fullPath);
|
|
|
|
|
},
|
|
|
|
|
});
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const onItemClick = async ({
|
|
|
|
|
message,
|
|
|
|
|
attachment,
|
|
|
|
|
type,
|
2022-06-03 16:33:39 +00:00
|
|
|
|
}: ItemClickEvent) => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
switch (type) {
|
|
|
|
|
case 'documents': {
|
|
|
|
|
saveAttachment({ message, attachment });
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'media': {
|
2021-08-24 21:47:14 +00:00
|
|
|
|
const selectedMedia =
|
|
|
|
|
media.find(item => attachment.path === item.path) || media[0];
|
|
|
|
|
this.showLightboxForMedia(selectedMedia, media);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new TypeError(`Unknown attachment type: '${type}'`);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
documents,
|
|
|
|
|
media,
|
|
|
|
|
onItemClick,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2021-06-16 00:44:14 +00:00
|
|
|
|
function getMessageIds(): Array<string | undefined> | undefined {
|
|
|
|
|
const state = window.reduxStore.getState();
|
|
|
|
|
const byConversation = state?.conversations?.messagesByConversation;
|
|
|
|
|
const messages = byConversation && byConversation[conversationId];
|
|
|
|
|
if (!messages || !messages.messageIds) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return messages.messageIds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Detect message changes in the current conversation
|
|
|
|
|
let previousMessageList: Array<string | undefined> | undefined;
|
|
|
|
|
previousMessageList = getMessageIds();
|
|
|
|
|
|
|
|
|
|
const unsubscribe = window.reduxStore.subscribe(() => {
|
|
|
|
|
const currentMessageList = getMessageIds();
|
|
|
|
|
if (currentMessageList !== previousMessageList) {
|
|
|
|
|
update();
|
|
|
|
|
previousMessageList = currentMessageList;
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2021-09-29 20:18:27 +00:00
|
|
|
|
className: 'panel',
|
2022-06-03 16:33:39 +00:00
|
|
|
|
// We present an empty panel briefly, while we wait for props to load.
|
|
|
|
|
JSX: <></>,
|
2021-09-29 20:18:27 +00:00
|
|
|
|
onClose: () => {
|
|
|
|
|
unsubscribe();
|
|
|
|
|
},
|
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const headerTitle = window.i18n('allMedia');
|
2021-09-29 20:18:27 +00:00
|
|
|
|
|
|
|
|
|
const update = async () => {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const props = await getProps();
|
|
|
|
|
view.update(<MediaGallery i18n={window.i18n} {...props} />);
|
2021-09-29 20:18:27 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view, headerTitle });
|
2021-09-29 20:18:27 +00:00
|
|
|
|
|
|
|
|
|
update();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
focusMessageField(): void {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (this.panels && this.panels.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.compositionApi.current?.focusInput();
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
disableMessageField(): void {
|
|
|
|
|
this.compositionApi.current?.setDisabled(true);
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
enableMessageField(): void {
|
|
|
|
|
this.compositionApi.current?.setDisabled(false);
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
resetEmojiResults(): void {
|
|
|
|
|
this.compositionApi.current?.resetEmojiResults();
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showGV1Members(): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const { contactCollection, id } = this.model;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-06-17 21:15:09 +00:00
|
|
|
|
const memberships =
|
|
|
|
|
contactCollection?.map((conversation: ConversationModel) => {
|
|
|
|
|
return {
|
|
|
|
|
isAdmin: false,
|
|
|
|
|
member: conversation.format(),
|
|
|
|
|
};
|
|
|
|
|
}) || [];
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const reduxState = window.reduxStore.getState();
|
|
|
|
|
const getPreferredBadge = getPreferredBadgeSelector(reduxState);
|
|
|
|
|
const theme = getTheme(reduxState);
|
|
|
|
|
|
|
|
|
|
const view = new ReactWrapperView({
|
2021-06-17 21:15:09 +00:00
|
|
|
|
className: 'group-member-list panel',
|
2022-06-03 16:33:39 +00:00
|
|
|
|
JSX: (
|
|
|
|
|
<ConversationDetailsMembershipList
|
|
|
|
|
canAddNewMembers={false}
|
|
|
|
|
conversationId={id}
|
|
|
|
|
i18n={window.i18n}
|
|
|
|
|
getPreferredBadge={getPreferredBadge}
|
|
|
|
|
maxShownMemberCount={32}
|
|
|
|
|
memberships={memberships}
|
|
|
|
|
showContactModal={contactId => {
|
|
|
|
|
this.showContactModal(contactId);
|
|
|
|
|
}}
|
|
|
|
|
theme={theme}
|
|
|
|
|
/>
|
|
|
|
|
),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view });
|
2021-06-17 21:15:09 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showSafetyNumber(id?: string): void {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
let conversation: undefined | ConversationModel;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
if (!id && isDirectConversation(this.model.attributes)) {
|
|
|
|
|
conversation = this.model;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
} else {
|
|
|
|
|
conversation = window.ConversationController.get(id);
|
|
|
|
|
}
|
|
|
|
|
if (conversation) {
|
2021-10-06 20:27:14 +00:00
|
|
|
|
window.reduxActions.globalModals.toggleSafetyNumberModal(
|
|
|
|
|
conversation.get('id')
|
|
|
|
|
);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
downloadAttachmentWrapper(
|
|
|
|
|
messageId: string,
|
|
|
|
|
providedAttachment?: AttachmentType
|
|
|
|
|
): void {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(
|
2021-06-16 00:44:14 +00:00
|
|
|
|
`downloadAttachmentWrapper: Message ${messageId} missing!`
|
2020-09-24 20:57:54 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { attachments, sent_at: timestamp } = message.attributes;
|
|
|
|
|
if (!attachments || attachments.length < 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const attachment =
|
|
|
|
|
providedAttachment && attachments.includes(providedAttachment)
|
|
|
|
|
? providedAttachment
|
|
|
|
|
: attachments[0];
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const { fileName } = attachment;
|
|
|
|
|
|
|
|
|
|
const isDangerous = window.Signal.Util.isFileDangerous(fileName || '');
|
|
|
|
|
|
|
|
|
|
this.downloadAttachment({ attachment, timestamp, isDangerous });
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
async downloadAttachment({
|
|
|
|
|
attachment,
|
|
|
|
|
timestamp,
|
|
|
|
|
isDangerous,
|
|
|
|
|
}: {
|
2021-07-14 23:39:52 +00:00
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
timestamp: number;
|
2021-06-08 14:59:38 +00:00
|
|
|
|
isDangerous: boolean;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}): Promise<void> {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (isDangerous) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastDangerousFileType);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-24 00:49:05 +00:00
|
|
|
|
const fullPath = await Attachment.save({
|
2020-09-24 20:57:54 +00:00
|
|
|
|
attachment,
|
|
|
|
|
readAttachmentData,
|
|
|
|
|
saveAttachmentToDisk,
|
|
|
|
|
timestamp,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (fullPath) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastFileSaved, {
|
|
|
|
|
onOpenFile: () => {
|
|
|
|
|
openFileInFolder(fullPath);
|
|
|
|
|
},
|
|
|
|
|
});
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async displayTapToViewMessage(messageId: string): Promise<void> {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('displayTapToViewMessage: attempting to display message');
|
2021-08-12 16:20:22 +00:00
|
|
|
|
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!message) {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
throw new Error(`displayTapToViewMessage: Message ${messageId} missing!`);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 17:15:10 +00:00
|
|
|
|
if (!isTapToView(message.attributes)) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
throw new Error(
|
|
|
|
|
`displayTapToViewMessage: Message ${message.idForLogging()} is not a tap to view message`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.isErased()) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`displayTapToViewMessage: Message ${message.idForLogging()} is already erased`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 17:15:10 +00:00
|
|
|
|
const firstAttachment = (message.get('attachments') || [])[0];
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!firstAttachment || !firstAttachment.path) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`displayTapToViewMessage: Message ${message.idForLogging()} had no first attachment with path`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const absolutePath = getAbsoluteAttachmentPath(firstAttachment.path);
|
2021-10-07 17:08:55 +00:00
|
|
|
|
const { path: tempPath } = await copyIntoTempDirectory(absolutePath);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const tempAttachment = {
|
|
|
|
|
...firstAttachment,
|
|
|
|
|
path: tempPath,
|
|
|
|
|
};
|
|
|
|
|
|
2021-07-22 17:07:53 +00:00
|
|
|
|
await message.markViewOnceMessageViewed();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2022-06-16 19:12:50 +00:00
|
|
|
|
this.listenTo(message, 'expired', async () => {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('displayTapToViewMessage: attempting to close lightbox');
|
2021-08-12 16:20:22 +00:00
|
|
|
|
|
2022-06-16 19:12:50 +00:00
|
|
|
|
// This isn't really a bullet-proof check because the lightbox could
|
|
|
|
|
// be open while we're viewing a regular media message
|
|
|
|
|
if (!isLightboxOpen()) {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('displayTapToViewMessage: lightbox was already closed');
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.stopListening(message);
|
2022-06-16 19:12:50 +00:00
|
|
|
|
closeLightbox();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
await deleteTempFile(tempPath);
|
2022-06-16 19:12:50 +00:00
|
|
|
|
});
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.listenTo(message, 'change', () => {
|
2022-06-16 19:12:50 +00:00
|
|
|
|
showLightbox(getProps());
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const getProps = (): ComponentProps<typeof Lightbox> => {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const { path, contentType } = tempAttachment;
|
|
|
|
|
|
|
|
|
|
return {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
close: () => {
|
2022-06-16 19:12:50 +00:00
|
|
|
|
closeLightbox();
|
2022-06-03 16:33:39 +00:00
|
|
|
|
},
|
|
|
|
|
i18n: window.i18n,
|
2021-08-24 21:47:14 +00:00
|
|
|
|
media: [
|
|
|
|
|
{
|
|
|
|
|
attachment: tempAttachment,
|
|
|
|
|
objectURL: getAbsoluteTempPath(path),
|
|
|
|
|
contentType,
|
|
|
|
|
index: 0,
|
|
|
|
|
message: {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
attachments: message.get('attachments') || [],
|
2021-08-24 21:47:14 +00:00
|
|
|
|
id: message.get('id'),
|
|
|
|
|
conversationId: message.get('conversationId'),
|
|
|
|
|
received_at: message.get('received_at'),
|
2021-08-30 21:32:56 +00:00
|
|
|
|
received_at_ms: Number(message.get('received_at_ms')),
|
2021-08-25 21:08:32 +00:00
|
|
|
|
sent_at: message.get('sent_at'),
|
2021-08-24 21:47:14 +00:00
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
2020-09-24 20:57:54 +00:00
|
|
|
|
isViewOnce: true,
|
|
|
|
|
};
|
|
|
|
|
};
|
2021-08-24 21:47:14 +00:00
|
|
|
|
|
2022-06-16 19:12:50 +00:00
|
|
|
|
showLightbox(getProps());
|
2021-08-12 16:20:22 +00:00
|
|
|
|
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('displayTapToViewMessage: showed lightbox');
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
deleteMessage(messageId: string): void {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!message) {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
throw new Error(`deleteMessage: Message ${messageId} missing!`);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 18:47:14 +00:00
|
|
|
|
window.showConfirmationDialog({
|
|
|
|
|
confirmStyle: 'negative',
|
2020-09-24 20:57:54 +00:00
|
|
|
|
message: window.i18n('deleteWarning'),
|
|
|
|
|
okText: window.i18n('delete'),
|
|
|
|
|
resolve: () => {
|
2021-12-10 22:51:54 +00:00
|
|
|
|
window.Signal.Data.removeMessage(message.id);
|
2021-06-17 17:15:10 +00:00
|
|
|
|
if (isOutgoing(message.attributes)) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.model.decrementSentMessageCount();
|
|
|
|
|
} else {
|
|
|
|
|
this.model.decrementMessageCount();
|
|
|
|
|
}
|
|
|
|
|
this.resetPanel();
|
|
|
|
|
},
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
deleteMessageForEveryone(messageId: string): void {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(messageId);
|
2020-09-29 22:55:56 +00:00
|
|
|
|
if (!message) {
|
|
|
|
|
throw new Error(
|
2021-06-16 00:44:14 +00:00
|
|
|
|
`deleteMessageForEveryone: Message ${messageId} missing!`
|
2020-09-29 22:55:56 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-04 18:47:14 +00:00
|
|
|
|
window.showConfirmationDialog({
|
|
|
|
|
confirmStyle: 'negative',
|
2020-09-29 22:55:56 +00:00
|
|
|
|
message: window.i18n('deleteForEveryoneWarning'),
|
|
|
|
|
okText: window.i18n('delete'),
|
|
|
|
|
resolve: async () => {
|
2021-05-06 00:09:29 +00:00
|
|
|
|
try {
|
2021-07-15 23:48:09 +00:00
|
|
|
|
await this.model.sendDeleteForEveryoneMessage({
|
|
|
|
|
id: message.id,
|
|
|
|
|
timestamp: message.get('sent_at'),
|
|
|
|
|
});
|
2021-05-06 00:09:29 +00:00
|
|
|
|
} catch (error) {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.error(
|
2021-05-06 00:09:29 +00:00
|
|
|
|
'Error sending delete-for-everyone',
|
2021-06-23 17:01:13 +00:00
|
|
|
|
error && error.stack,
|
2021-05-06 00:09:29 +00:00
|
|
|
|
messageId
|
|
|
|
|
);
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastDeleteForEveryoneFailed);
|
2021-05-06 00:09:29 +00:00
|
|
|
|
}
|
2020-09-29 22:55:56 +00:00
|
|
|
|
this.resetPanel();
|
|
|
|
|
},
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-29 22:55:56 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showStickerPackPreview(packId: string, packKey: string): void {
|
2021-07-09 19:36:10 +00:00
|
|
|
|
Stickers.downloadEphemeralPack(packId, packKey);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
const props = {
|
|
|
|
|
packId,
|
|
|
|
|
onClose: async () => {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
if (this.stickerPreviewModalView) {
|
|
|
|
|
this.stickerPreviewModalView.remove();
|
|
|
|
|
this.stickerPreviewModalView = undefined;
|
|
|
|
|
}
|
2021-07-09 19:36:10 +00:00
|
|
|
|
await Stickers.removeEphemeralPack(packId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.stickerPreviewModalView = new ReactWrapperView({
|
2020-09-24 20:57:54 +00:00
|
|
|
|
className: 'sticker-preview-modal-wrapper',
|
|
|
|
|
JSX: window.Signal.State.Roots.createStickerPreviewModal(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
props
|
|
|
|
|
),
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-16 21:35:54 +00:00
|
|
|
|
showLightboxForMedia(
|
2021-08-23 23:14:53 +00:00
|
|
|
|
selectedMediaItem: MediaItemType,
|
2021-08-24 21:47:14 +00:00
|
|
|
|
media: Array<MediaItemType> = []
|
2021-08-30 21:32:56 +00:00
|
|
|
|
): void {
|
2021-08-25 21:08:32 +00:00
|
|
|
|
const onSave = async ({
|
|
|
|
|
attachment,
|
|
|
|
|
message,
|
|
|
|
|
index,
|
|
|
|
|
}: {
|
|
|
|
|
attachment: AttachmentType;
|
|
|
|
|
message: MediaItemMessageType;
|
|
|
|
|
index: number;
|
|
|
|
|
}) => {
|
2021-09-24 00:49:05 +00:00
|
|
|
|
const fullPath = await Attachment.save({
|
2021-08-25 21:08:32 +00:00
|
|
|
|
attachment,
|
|
|
|
|
index: index + 1,
|
2021-01-29 21:19:24 +00:00
|
|
|
|
readAttachmentData,
|
|
|
|
|
saveAttachmentToDisk,
|
2021-08-25 21:08:32 +00:00
|
|
|
|
timestamp: message.sent_at,
|
2021-01-29 21:19:24 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (fullPath) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
showToast(ToastFileSaved, {
|
|
|
|
|
onOpenFile: () => {
|
|
|
|
|
openFileInFolder(fullPath);
|
|
|
|
|
},
|
|
|
|
|
});
|
2021-01-29 21:19:24 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const selectedIndex = media.findIndex(
|
|
|
|
|
mediaItem =>
|
|
|
|
|
mediaItem.attachment.path === selectedMediaItem.attachment.path
|
|
|
|
|
);
|
|
|
|
|
|
2022-06-16 19:12:50 +00:00
|
|
|
|
showLightbox({
|
|
|
|
|
close: closeLightbox,
|
|
|
|
|
i18n: window.i18n,
|
|
|
|
|
getConversation: getConversationSelector(window.reduxStore.getState()),
|
|
|
|
|
media,
|
|
|
|
|
onForward: messageId => {
|
|
|
|
|
this.showForwardMessageModal(messageId);
|
|
|
|
|
},
|
|
|
|
|
onSave,
|
|
|
|
|
selectedIndex: selectedIndex >= 0 ? selectedIndex : 0,
|
2021-01-29 21:19:24 +00:00
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
|
|
|
|
showLightbox({
|
|
|
|
|
attachment,
|
|
|
|
|
messageId,
|
|
|
|
|
}: {
|
2021-07-14 23:39:52 +00:00
|
|
|
|
attachment: AttachmentType;
|
2021-01-29 21:19:24 +00:00
|
|
|
|
messageId: string;
|
|
|
|
|
showSingle?: boolean;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}): void {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!message) {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
throw new Error(`showLightbox: Message ${messageId} missing!`);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
const sticker = message.get('sticker');
|
|
|
|
|
if (sticker) {
|
|
|
|
|
const { packId, packKey } = sticker;
|
|
|
|
|
this.showStickerPackPreview(packId, packKey);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 23:14:53 +00:00
|
|
|
|
const { contentType } = attachment;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!window.Signal.Util.GoogleChrome.isImageTypeSupported(contentType) &&
|
|
|
|
|
!window.Signal.Util.GoogleChrome.isVideoTypeSupported(contentType)
|
|
|
|
|
) {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.downloadAttachmentWrapper(messageId, attachment);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-14 23:39:52 +00:00
|
|
|
|
const attachments: Array<AttachmentType> = message.get('attachments') || [];
|
|
|
|
|
|
|
|
|
|
const loop = isGIF(attachments);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
const media = attachments
|
2021-07-14 23:39:52 +00:00
|
|
|
|
.filter(item => item.thumbnail && !item.pending && !item.error)
|
|
|
|
|
.map((item, index) => ({
|
|
|
|
|
objectURL: getAbsoluteAttachmentPath(item.path ?? ''),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
path: item.path,
|
|
|
|
|
contentType: item.contentType,
|
2021-07-14 23:39:52 +00:00
|
|
|
|
loop,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
index,
|
2021-08-23 23:14:53 +00:00
|
|
|
|
message: {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
attachments: message.get('attachments') || [],
|
2021-08-23 23:14:53 +00:00
|
|
|
|
id: message.get('id'),
|
2021-08-24 21:47:14 +00:00
|
|
|
|
conversationId:
|
|
|
|
|
window.ConversationController.get(
|
|
|
|
|
window.ConversationController.ensureContactIds({
|
|
|
|
|
uuid: message.get('sourceUuid'),
|
|
|
|
|
e164: message.get('source'),
|
|
|
|
|
})
|
|
|
|
|
)?.id || message.get('conversationId'),
|
2021-08-23 23:14:53 +00:00
|
|
|
|
received_at: message.get('received_at'),
|
2021-08-30 21:32:56 +00:00
|
|
|
|
received_at_ms: Number(message.get('received_at_ms')),
|
2021-08-25 21:08:32 +00:00
|
|
|
|
sent_at: message.get('sent_at'),
|
2021-08-23 23:14:53 +00:00
|
|
|
|
},
|
2020-09-24 20:57:54 +00:00
|
|
|
|
attachment: item,
|
2021-08-23 23:14:53 +00:00
|
|
|
|
thumbnailObjectUrl:
|
|
|
|
|
item.thumbnail?.objectUrl ||
|
|
|
|
|
getAbsoluteAttachmentPath(item.thumbnail?.path ?? ''),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}));
|
|
|
|
|
|
2022-04-29 18:48:26 +00:00
|
|
|
|
if (!media.length) {
|
|
|
|
|
log.error(
|
|
|
|
|
'showLightbox: unable to load attachment',
|
|
|
|
|
attachments.map(x => ({
|
|
|
|
|
contentType: x.contentType,
|
|
|
|
|
error: x.error,
|
|
|
|
|
flags: x.flags,
|
|
|
|
|
path: x.path,
|
|
|
|
|
size: x.size,
|
|
|
|
|
}))
|
|
|
|
|
);
|
|
|
|
|
showToast(ToastUnableToLoadAttachment);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 23:14:53 +00:00
|
|
|
|
const selectedMedia =
|
|
|
|
|
media.find(item => attachment.path === item.path) || media[0];
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-24 21:47:14 +00:00
|
|
|
|
this.showLightboxForMedia(selectedMedia, media);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showContactModal(contactId: string): void {
|
2021-09-21 22:37:10 +00:00
|
|
|
|
window.reduxActions.globalModals.showContactModal(contactId, this.model.id);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showGroupLinkManagement(): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2021-01-29 21:19:24 +00:00
|
|
|
|
className: 'panel',
|
|
|
|
|
JSX: window.Signal.State.Roots.createGroupLinkManagement(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
{
|
2021-08-30 21:32:56 +00:00
|
|
|
|
conversationId: this.model.id,
|
2021-01-29 21:19:24 +00:00
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const headerTitle = window.i18n('ConversationDetails--group-link');
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view, headerTitle });
|
2021-01-29 21:19:24 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showGroupV2Permissions(): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2021-01-29 21:19:24 +00:00
|
|
|
|
className: 'panel',
|
|
|
|
|
JSX: window.Signal.State.Roots.createGroupV2Permissions(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
{
|
2021-08-30 21:32:56 +00:00
|
|
|
|
conversationId: this.model.id,
|
2021-11-11 22:43:05 +00:00
|
|
|
|
setAccessControlAttributesSetting:
|
|
|
|
|
this.setAccessControlAttributesSetting.bind(this),
|
|
|
|
|
setAccessControlMembersSetting:
|
|
|
|
|
this.setAccessControlMembersSetting.bind(this),
|
2021-07-20 20:18:35 +00:00
|
|
|
|
setAnnouncementsOnly: this.setAnnouncementsOnly.bind(this),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const headerTitle = window.i18n('permissions');
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view, headerTitle });
|
2021-01-29 21:19:24 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showPendingInvites(): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2021-01-29 21:19:24 +00:00
|
|
|
|
className: 'panel',
|
|
|
|
|
JSX: window.Signal.State.Roots.createPendingInvites(window.reduxStore, {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
conversationId: this.model.id,
|
2021-11-10 23:01:06 +00:00
|
|
|
|
ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
approvePendingMembership: (conversationId: string) => {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.approvePendingMembershipFromGroupV2(conversationId);
|
2021-01-29 21:19:24 +00:00
|
|
|
|
},
|
|
|
|
|
revokePendingMemberships: conversationIds => {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model.revokePendingMembershipsFromGroupV2(conversationIds);
|
2021-01-29 21:19:24 +00:00
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const headerTitle = window.i18n(
|
|
|
|
|
'ConversationDetails--requests-and-invites'
|
|
|
|
|
);
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view, headerTitle });
|
2021-01-29 21:19:24 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-08-05 12:35:33 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showConversationNotificationsSettings(): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2021-08-05 12:35:33 +00:00
|
|
|
|
className: 'panel',
|
|
|
|
|
JSX: window.Signal.State.Roots.createConversationNotificationsSettings(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
{
|
2021-08-30 21:32:56 +00:00
|
|
|
|
conversationId: this.model.id,
|
2021-11-11 22:43:05 +00:00
|
|
|
|
setDontNotifyForMentionsIfMuted:
|
|
|
|
|
this.model.setDontNotifyForMentionsIfMuted.bind(this.model),
|
2021-08-05 12:35:33 +00:00
|
|
|
|
setMuteExpiration: this.setMuteExpiration.bind(this),
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const headerTitle = window.i18n('ConversationDetails--notifications');
|
2021-08-05 12:35:33 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view, headerTitle });
|
2021-08-05 12:35:33 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-05-28 16:15:17 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showChatColorEditor(): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2021-05-28 16:15:17 +00:00
|
|
|
|
className: 'panel',
|
|
|
|
|
JSX: window.Signal.State.Roots.createChatColorPicker(window.reduxStore, {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
conversationId: this.model.get('id'),
|
2021-05-28 16:15:17 +00:00
|
|
|
|
}),
|
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const headerTitle = window.i18n('ChatColorPicker__menu-title');
|
2021-05-28 16:15:17 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view, headerTitle });
|
2021-05-28 16:15:17 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-05-28 16:15:17 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showConversationDetails(): void {
|
2021-07-20 20:18:35 +00:00
|
|
|
|
// Run a getProfiles in case member's capabilities have changed
|
|
|
|
|
// Redux should cover us on the return here so no need to await this.
|
2021-08-30 21:32:56 +00:00
|
|
|
|
if (this.model.throttledGetProfiles) {
|
|
|
|
|
this.model.throttledGetProfiles();
|
|
|
|
|
}
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2021-07-09 19:36:10 +00:00
|
|
|
|
const messageRequestEnum = Proto.SyncMessage.MessageRequestResponse.Type;
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
|
|
|
|
// these methods are used in more than one place and should probably be
|
|
|
|
|
// dried up and hoisted to methods on ConversationView
|
|
|
|
|
|
2021-04-28 20:27:16 +00:00
|
|
|
|
const onLeave = () => {
|
2021-01-29 21:19:24 +00:00
|
|
|
|
this.longRunningTaskWrapper({
|
2021-04-28 20:27:16 +00:00
|
|
|
|
name: 'onLeave',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
task: () => this.model.leaveGroupV2(),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2021-04-28 20:27:16 +00:00
|
|
|
|
const onBlock = () => {
|
2021-06-01 23:30:25 +00:00
|
|
|
|
this.syncMessageRequestResponse(
|
|
|
|
|
'onBlock',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.model,
|
2021-06-01 23:30:25 +00:00
|
|
|
|
messageRequestEnum.BLOCK
|
|
|
|
|
);
|
2021-01-29 21:19:24 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const props = {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
addMembers: this.model.addMembersV2.bind(this.model),
|
|
|
|
|
conversationId: this.model.get('id'),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
loadRecentMediaItems: this.loadRecentMediaItems.bind(this),
|
|
|
|
|
setDisappearingMessages: this.setDisappearingMessages.bind(this),
|
|
|
|
|
showAllMedia: this.showAllMedia.bind(this),
|
|
|
|
|
showContactModal: this.showContactModal.bind(this),
|
2021-10-20 23:46:41 +00:00
|
|
|
|
showChatColorEditor: this.showChatColorEditor.bind(this),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
showGroupLinkManagement: this.showGroupLinkManagement.bind(this),
|
|
|
|
|
showGroupV2Permissions: this.showGroupV2Permissions.bind(this),
|
2021-11-11 22:43:05 +00:00
|
|
|
|
showConversationNotificationsSettings:
|
|
|
|
|
this.showConversationNotificationsSettings.bind(this),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
showPendingInvites: this.showPendingInvites.bind(this),
|
|
|
|
|
showLightboxForMedia: this.showLightboxForMedia.bind(this),
|
2021-08-30 21:32:56 +00:00
|
|
|
|
updateGroupAttributes: this.model.updateGroupAttributesV2.bind(
|
|
|
|
|
this.model
|
|
|
|
|
),
|
2021-04-28 20:27:16 +00:00
|
|
|
|
onLeave,
|
|
|
|
|
onBlock,
|
2021-10-20 23:46:41 +00:00
|
|
|
|
onUnblock: () => {
|
|
|
|
|
this.syncMessageRequestResponse(
|
|
|
|
|
'onUnblock',
|
|
|
|
|
this.model,
|
|
|
|
|
messageRequestEnum.ACCEPT
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
setMuteExpiration: this.setMuteExpiration.bind(this),
|
2021-11-11 22:43:05 +00:00
|
|
|
|
onOutgoingAudioCallInConversation:
|
|
|
|
|
this.onOutgoingAudioCallInConversation.bind(this),
|
|
|
|
|
onOutgoingVideoCallInConversation:
|
|
|
|
|
this.onOutgoingVideoCallInConversation.bind(this),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2021-01-29 21:19:24 +00:00
|
|
|
|
className: 'conversation-details-pane panel',
|
|
|
|
|
JSX: window.Signal.State.Roots.createConversationDetails(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
props
|
|
|
|
|
),
|
|
|
|
|
});
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const headerTitle = '';
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view, headerTitle });
|
2021-01-29 21:19:24 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showMessageDetail(messageId: string): void {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
const message = window.MessageController.getById(messageId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!message) {
|
2021-06-16 00:44:14 +00:00
|
|
|
|
throw new Error(`showMessageDetail: Message ${messageId} missing!`);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!message.isNormalBubble()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-24 22:06:12 +00:00
|
|
|
|
const getProps = () => ({
|
2021-07-19 22:44:49 +00:00
|
|
|
|
...message.getPropsForMessageDetail(
|
|
|
|
|
window.ConversationController.getOurConversationIdOrThrow()
|
|
|
|
|
),
|
2021-03-24 22:06:12 +00:00
|
|
|
|
...this.getMessageActions(),
|
|
|
|
|
});
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const onClose = () => {
|
|
|
|
|
this.stopListening(message, 'change', update);
|
|
|
|
|
this.resetPanel();
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2020-09-24 20:57:54 +00:00
|
|
|
|
className: 'panel message-detail-wrapper',
|
2021-03-24 22:06:12 +00:00
|
|
|
|
JSX: window.Signal.State.Roots.createMessageDetail(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
getProps()
|
|
|
|
|
),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
onClose,
|
|
|
|
|
});
|
|
|
|
|
|
2021-09-29 20:18:27 +00:00
|
|
|
|
const update = () =>
|
|
|
|
|
view.update(
|
|
|
|
|
window.Signal.State.Roots.createMessageDetail(
|
|
|
|
|
window.reduxStore,
|
|
|
|
|
getProps()
|
|
|
|
|
)
|
|
|
|
|
);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.listenTo(message, 'change', update);
|
|
|
|
|
this.listenTo(message, 'expired', onClose);
|
|
|
|
|
// We could listen to all involved contacts, but we'll call that overkill
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view });
|
2020-09-24 20:57:54 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
showStickerManager(): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2020-09-24 20:57:54 +00:00
|
|
|
|
className: ['sticker-manager-wrapper', 'panel'].join(' '),
|
|
|
|
|
JSX: window.Signal.State.Roots.createStickerManager(window.reduxStore),
|
|
|
|
|
onClose: () => {
|
|
|
|
|
this.resetPanel();
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view });
|
2020-09-24 20:57:54 +00:00
|
|
|
|
view.render();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
showContactDetail({
|
|
|
|
|
contact,
|
|
|
|
|
signalAccount,
|
|
|
|
|
}: {
|
2021-08-20 01:56:39 +00:00
|
|
|
|
contact: EmbeddedContactType;
|
2022-04-12 00:26:09 +00:00
|
|
|
|
signalAccount?: {
|
|
|
|
|
phoneNumber: string;
|
|
|
|
|
uuid: UUIDStringType;
|
|
|
|
|
};
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}): void {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const view = new ReactWrapperView({
|
2020-09-24 20:57:54 +00:00
|
|
|
|
className: 'contact-detail-pane panel',
|
2022-06-03 16:33:39 +00:00
|
|
|
|
JSX: (
|
|
|
|
|
<ContactDetail
|
|
|
|
|
i18n={window.i18n}
|
|
|
|
|
contact={contact}
|
|
|
|
|
hasSignalAccount={Boolean(signalAccount)}
|
|
|
|
|
onSendMessage={() => {
|
|
|
|
|
if (signalAccount) {
|
|
|
|
|
this.startConversation(
|
|
|
|
|
signalAccount.phoneNumber,
|
|
|
|
|
signalAccount.uuid
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
),
|
2020-09-24 20:57:54 +00:00
|
|
|
|
onClose: () => {
|
|
|
|
|
this.resetPanel();
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.addPanel({ view });
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2022-04-12 00:26:09 +00:00
|
|
|
|
startConversation(e164: string, uuid: UUIDStringType): void {
|
|
|
|
|
const conversationId = window.ConversationController.ensureContactIds({
|
|
|
|
|
e164,
|
|
|
|
|
uuid,
|
|
|
|
|
});
|
|
|
|
|
strictAssert(
|
|
|
|
|
conversationId,
|
|
|
|
|
`startConversation failed given ${e164}/${uuid} combination`
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
this.openConversation(conversationId);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async openConversation(
|
|
|
|
|
conversationId: string,
|
|
|
|
|
messageId?: string
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
window.Whisper.events.trigger(
|
|
|
|
|
'showConversation',
|
|
|
|
|
conversationId,
|
|
|
|
|
messageId
|
|
|
|
|
);
|
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
addPanel(panel: PanelType): void {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.panels = this.panels || [];
|
|
|
|
|
|
|
|
|
|
if (this.panels.length === 0) {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.previousFocus = document.activeElement as HTMLElement;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.panels.unshift(panel);
|
|
|
|
|
panel.view.$el.insertAfter(this.$('.panel').last());
|
|
|
|
|
panel.view.$el.one('animationend', () => {
|
|
|
|
|
panel.view.$el.addClass('panel--static');
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
2020-10-30 17:52:21 +00:00
|
|
|
|
|
|
|
|
|
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
|
|
|
|
this.panels.length
|
|
|
|
|
);
|
2021-01-29 21:19:24 +00:00
|
|
|
|
window.reduxActions.conversations.setSelectedConversationHeaderTitle(
|
2022-06-03 16:33:39 +00:00
|
|
|
|
panel.headerTitle
|
2021-01-29 21:19:24 +00:00
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
|
|
|
|
resetPanel(): void {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!this.panels || !this.panels.length) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
const panel = this.panels.shift();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
this.panels.length === 0 &&
|
|
|
|
|
this.previousFocus &&
|
|
|
|
|
this.previousFocus.focus
|
|
|
|
|
) {
|
|
|
|
|
this.previousFocus.focus();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.previousFocus = undefined;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.panels.length > 0) {
|
2022-06-03 16:33:39 +00:00
|
|
|
|
this.panels[0].view.$el.fadeIn(250);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-03 16:33:39 +00:00
|
|
|
|
if (panel) {
|
|
|
|
|
panel.view.$el.addClass('panel--remove').one('transitionend', () => {
|
|
|
|
|
panel.view.remove();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
if (this.panels.length === 0) {
|
|
|
|
|
// Make sure poppers are positioned properly
|
|
|
|
|
window.dispatchEvent(new Event('resize'));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-10-30 17:52:21 +00:00
|
|
|
|
|
|
|
|
|
window.reduxActions.conversations.setSelectedConversationPanelDepth(
|
|
|
|
|
this.panels.length
|
|
|
|
|
);
|
2021-01-29 21:19:24 +00:00
|
|
|
|
window.reduxActions.conversations.setSelectedConversationHeaderTitle(
|
|
|
|
|
this.panels[0]?.headerTitle
|
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-01-29 21:19:24 +00:00
|
|
|
|
async loadRecentMediaItems(limit: number): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2021-11-11 22:43:05 +00:00
|
|
|
|
const messages: Array<MessageAttributesType> =
|
|
|
|
|
await window.Signal.Data.getMessagesWithVisualMediaAttachments(model.id, {
|
2021-01-29 21:19:24 +00:00
|
|
|
|
limit,
|
2021-11-11 22:43:05 +00:00
|
|
|
|
});
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
|
|
|
|
const loadedRecentMediaItems = messages
|
|
|
|
|
.filter(message => message.attachments !== undefined)
|
|
|
|
|
.reduce(
|
|
|
|
|
(acc, message) => [
|
|
|
|
|
...acc,
|
2021-06-17 17:15:10 +00:00
|
|
|
|
...(message.attachments || []).map(
|
2021-01-29 21:19:24 +00:00
|
|
|
|
(attachment: AttachmentType, index: number): MediaItemType => {
|
|
|
|
|
const { thumbnail } = attachment;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
objectURL: getAbsoluteAttachmentPath(attachment.path || ''),
|
2022-06-10 01:10:20 +00:00
|
|
|
|
thumbnailObjectUrl: thumbnail?.path
|
2021-01-29 21:19:24 +00:00
|
|
|
|
? getAbsoluteAttachmentPath(thumbnail.path)
|
|
|
|
|
: '',
|
|
|
|
|
contentType: attachment.contentType,
|
|
|
|
|
index,
|
|
|
|
|
attachment,
|
2021-08-23 23:14:53 +00:00
|
|
|
|
message: {
|
|
|
|
|
attachments: message.attachments || [],
|
|
|
|
|
conversationId:
|
|
|
|
|
window.ConversationController.get(message.sourceUuid)?.id ||
|
|
|
|
|
message.conversationId,
|
|
|
|
|
id: message.id,
|
|
|
|
|
received_at: message.received_at,
|
|
|
|
|
received_at_ms: Number(message.received_at_ms),
|
2021-08-25 21:08:32 +00:00
|
|
|
|
sent_at: message.sent_at,
|
2021-08-23 23:14:53 +00:00
|
|
|
|
},
|
2021-01-29 21:19:24 +00:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
[] as Array<MediaItemType>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
window.reduxActions.conversations.setRecentMediaItems(
|
2021-06-08 14:59:38 +00:00
|
|
|
|
model.id,
|
2021-01-29 21:19:24 +00:00
|
|
|
|
loadedRecentMediaItems
|
|
|
|
|
);
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async setDisappearingMessages(seconds: number): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2021-08-16 21:35:54 +00:00
|
|
|
|
const valueToSet = seconds > 0 ? seconds : undefined;
|
2020-10-06 17:06:34 +00:00
|
|
|
|
|
|
|
|
|
await this.longRunningTaskWrapper({
|
|
|
|
|
name: 'updateExpirationTimer',
|
2021-06-08 14:59:38 +00:00
|
|
|
|
task: async () => model.updateExpirationTimer(valueToSet),
|
2020-10-06 17:06:34 +00:00
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-07-20 20:18:35 +00:00
|
|
|
|
async setAccessControlAttributesSetting(value: number): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2021-01-29 21:19:24 +00:00
|
|
|
|
await this.longRunningTaskWrapper({
|
|
|
|
|
name: 'updateAccessControlAttributes',
|
2021-06-08 14:59:38 +00:00
|
|
|
|
task: async () => model.updateAccessControlAttributes(value),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2021-07-20 20:18:35 +00:00
|
|
|
|
async setAccessControlMembersSetting(value: number): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2021-01-29 21:19:24 +00:00
|
|
|
|
await this.longRunningTaskWrapper({
|
|
|
|
|
name: 'updateAccessControlMembers',
|
2021-06-08 14:59:38 +00:00
|
|
|
|
task: async () => model.updateAccessControlMembers(value),
|
2021-01-29 21:19:24 +00:00
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-01-29 21:19:24 +00:00
|
|
|
|
|
2021-07-20 20:18:35 +00:00
|
|
|
|
async setAnnouncementsOnly(value: boolean): Promise<void> {
|
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
|
|
|
|
await this.longRunningTaskWrapper({
|
|
|
|
|
name: 'updateAnnouncementsOnly',
|
|
|
|
|
task: async () => model.updateAnnouncementsOnly(value),
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2021-07-20 20:18:35 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async destroyMessages(): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2021-02-26 21:06:37 +00:00
|
|
|
|
window.showConfirmationDialog({
|
2021-05-28 19:14:15 +00:00
|
|
|
|
confirmStyle: 'negative',
|
2021-02-26 21:06:37 +00:00
|
|
|
|
message: window.i18n('deleteConversationConfirmation'),
|
|
|
|
|
okText: window.i18n('delete'),
|
|
|
|
|
resolve: () => {
|
|
|
|
|
this.longRunningTaskWrapper({
|
|
|
|
|
name: 'destroymessages',
|
|
|
|
|
task: async () => {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
model.trigger('unload', 'delete messages');
|
|
|
|
|
await model.destroyMessages();
|
|
|
|
|
model.updateLastMessage();
|
2021-02-26 21:06:37 +00:00
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
reject: () => {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('destroyMessages: User canceled delete');
|
2021-02-26 21:06:37 +00:00
|
|
|
|
},
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async isCallSafe(): Promise<boolean> {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const contacts = await this.getUntrustedContacts();
|
|
|
|
|
if (contacts && contacts.length) {
|
|
|
|
|
const callAnyway = await this.showSendAnywayDialog(
|
2021-08-30 21:32:56 +00:00
|
|
|
|
contacts.models,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
window.i18n('callAnyway')
|
|
|
|
|
);
|
|
|
|
|
if (!callAnyway) {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info(
|
2020-09-24 20:57:54 +00:00
|
|
|
|
'Safety number change dialog not accepted, new call not allowed.'
|
|
|
|
|
);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-04-27 22:35:35 +00:00
|
|
|
|
showSendAnywayDialog(
|
|
|
|
|
contacts: Array<ConversationModel>,
|
|
|
|
|
confirmText?: string
|
2021-08-30 21:32:56 +00:00
|
|
|
|
): Promise<boolean> {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return new Promise(resolve => {
|
2021-06-17 21:15:09 +00:00
|
|
|
|
showSafetyNumberChangeDialog({
|
2020-09-24 20:57:54 +00:00
|
|
|
|
confirmText,
|
|
|
|
|
contacts,
|
|
|
|
|
reject: () => {
|
|
|
|
|
resolve(false);
|
|
|
|
|
},
|
|
|
|
|
resolve: () => {
|
|
|
|
|
resolve(true);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async sendStickerMessage(options: {
|
|
|
|
|
packId: string;
|
|
|
|
|
stickerId: number;
|
|
|
|
|
force?: boolean;
|
|
|
|
|
}): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
try {
|
|
|
|
|
const contacts = await this.getUntrustedContacts(options);
|
|
|
|
|
|
|
|
|
|
if (contacts && contacts.length) {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const sendAnyway = await this.showSendAnywayDialog(contacts.models);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (sendAnyway) {
|
|
|
|
|
this.sendStickerMessage({ ...options, force: true });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-16 21:34:41 +00:00
|
|
|
|
if (this.showInvalidMessageToast()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const { packId, stickerId } = options;
|
2021-06-08 14:59:38 +00:00
|
|
|
|
model.sendStickerMessage(packId, stickerId);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
} catch (error) {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.error('clickSend error:', error && error.stack ? error.stack : error);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async getUntrustedContacts(
|
|
|
|
|
options: { force?: boolean } = {}
|
|
|
|
|
): Promise<null | ConversationModelCollectionType> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
// This will go to the trust store for the latest identity key information,
|
|
|
|
|
// and may result in the display of a new banner for this conversation.
|
2021-06-08 14:59:38 +00:00
|
|
|
|
await model.updateVerified();
|
|
|
|
|
const unverifiedContacts = model.getUnverified();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
if (options.force) {
|
|
|
|
|
if (unverifiedContacts.length) {
|
2021-09-27 16:29:00 +00:00
|
|
|
|
await markAllAsVerifiedDefault(unverifiedContacts.models);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
// We only want force to break us through one layer of checks
|
|
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
|
|
options.force = false;
|
|
|
|
|
}
|
|
|
|
|
} else if (unverifiedContacts.length) {
|
|
|
|
|
return unverifiedContacts;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const untrustedContacts = model.getUntrusted();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
if (options.force) {
|
|
|
|
|
if (untrustedContacts.length) {
|
2021-09-27 16:29:00 +00:00
|
|
|
|
await markAllAsApproved(untrustedContacts.models);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
} else if (untrustedContacts.length) {
|
|
|
|
|
return untrustedContacts;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
async setQuoteMessage(messageId: null | string): Promise<void> {
|
2021-12-10 22:51:54 +00:00
|
|
|
|
const { model } = this;
|
2022-01-31 21:52:09 +00:00
|
|
|
|
const message = messageId ? await getMessageById(messageId) : undefined;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-07-19 22:44:49 +00:00
|
|
|
|
if (
|
2022-01-31 21:52:09 +00:00
|
|
|
|
message &&
|
2021-07-19 22:44:49 +00:00
|
|
|
|
!canReply(
|
2022-01-31 21:52:09 +00:00
|
|
|
|
message.attributes,
|
2021-07-19 22:44:49 +00:00
|
|
|
|
window.ConversationController.getOurConversationIdOrThrow(),
|
|
|
|
|
findAndFormatContact
|
|
|
|
|
)
|
|
|
|
|
) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
if (message && !message.isNormalBubble()) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
this.quote = undefined;
|
|
|
|
|
this.quotedMessage = undefined;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const existing = model.get('quotedMessageId');
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (existing !== messageId) {
|
2021-10-19 00:09:55 +00:00
|
|
|
|
const now = Date.now();
|
|
|
|
|
let active_at = this.model.get('active_at');
|
|
|
|
|
let timestamp = this.model.get('timestamp');
|
|
|
|
|
|
|
|
|
|
if (!active_at && messageId) {
|
|
|
|
|
active_at = now;
|
|
|
|
|
timestamp = now;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.model.set({
|
2021-10-19 00:09:55 +00:00
|
|
|
|
active_at,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
draftChanged: true,
|
2021-10-19 00:09:55 +00:00
|
|
|
|
quotedMessageId: messageId,
|
|
|
|
|
timestamp,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await this.saveModel();
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
if (message) {
|
2021-12-10 22:51:54 +00:00
|
|
|
|
this.quotedMessage = message;
|
|
|
|
|
this.quote = await model.makeQuote(this.quotedMessage);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-12-10 22:51:54 +00:00
|
|
|
|
this.enableMessageField();
|
|
|
|
|
this.focusMessageField();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.renderQuotedMessage();
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-08-30 21:32:56 +00:00
|
|
|
|
renderQuotedMessage(): void {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (!this.quotedMessage) {
|
2021-06-25 16:08:16 +00:00
|
|
|
|
window.reduxActions.composer.setQuotedMessage(undefined);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-25 16:08:16 +00:00
|
|
|
|
window.reduxActions.composer.setQuotedMessage({
|
|
|
|
|
conversationId: model.id,
|
|
|
|
|
quote: this.quote,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2020-11-16 21:34:41 +00:00
|
|
|
|
showInvalidMessageToast(messageText?: string): boolean {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2021-09-22 20:59:54 +00:00
|
|
|
|
let toastView:
|
|
|
|
|
| undefined
|
|
|
|
|
| typeof ToastBlocked
|
|
|
|
|
| typeof ToastBlockedGroup
|
|
|
|
|
| typeof ToastExpired
|
|
|
|
|
| typeof ToastInvalidConversation
|
|
|
|
|
| typeof ToastLeftGroup
|
|
|
|
|
| typeof ToastMessageBodyTooLong;
|
2020-11-16 21:34:41 +00:00
|
|
|
|
|
|
|
|
|
if (window.reduxStore.getState().expiration.hasExpired) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
toastView = ToastExpired;
|
2020-11-16 21:34:41 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
if (!model.isValid()) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
toastView = ToastInvalidConversation;
|
2020-11-16 21:34:41 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
|
|
|
|
|
const e164 = this.model.get('e164');
|
|
|
|
|
const uuid = this.model.get('uuid');
|
2020-11-16 21:34:41 +00:00
|
|
|
|
if (
|
2021-06-07 16:39:13 +00:00
|
|
|
|
isDirectConversation(this.model.attributes) &&
|
2021-08-30 21:32:56 +00:00
|
|
|
|
((e164 && window.storage.blocked.isBlocked(e164)) ||
|
|
|
|
|
(uuid && window.storage.blocked.isUuidBlocked(uuid)))
|
2020-11-16 21:34:41 +00:00
|
|
|
|
) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
toastView = ToastBlocked;
|
2020-11-16 21:34:41 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
|
|
|
|
|
const groupId = this.model.get('groupId');
|
2020-11-16 21:34:41 +00:00
|
|
|
|
if (
|
2021-06-07 16:39:13 +00:00
|
|
|
|
!isDirectConversation(this.model.attributes) &&
|
2021-08-30 21:32:56 +00:00
|
|
|
|
groupId &&
|
|
|
|
|
window.storage.blocked.isGroupBlocked(groupId)
|
2020-11-16 21:34:41 +00:00
|
|
|
|
) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
toastView = ToastBlockedGroup;
|
2020-11-16 21:34:41 +00:00
|
|
|
|
}
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
|
|
|
|
if (!isDirectConversation(model.attributes) && model.get('left')) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
toastView = ToastLeftGroup;
|
2020-11-16 21:34:41 +00:00
|
|
|
|
}
|
|
|
|
|
if (messageText && messageText.length > MAX_MESSAGE_BODY_LENGTH) {
|
2021-09-22 20:59:54 +00:00
|
|
|
|
toastView = ToastMessageBodyTooLong;
|
2020-11-16 21:34:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-22 20:59:54 +00:00
|
|
|
|
if (toastView) {
|
|
|
|
|
showToast(toastView);
|
2020-11-16 21:34:41 +00:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-11-16 21:34:41 +00:00
|
|
|
|
|
2021-07-30 18:37:03 +00:00
|
|
|
|
async sendMessage(
|
|
|
|
|
message = '',
|
2021-08-30 21:32:56 +00:00
|
|
|
|
mentions: BodyRangesType = [],
|
2021-09-29 20:23:06 +00:00
|
|
|
|
options: {
|
|
|
|
|
draftAttachments?: ReadonlyArray<AttachmentType>;
|
|
|
|
|
force?: boolean;
|
|
|
|
|
timestamp?: number;
|
|
|
|
|
voiceNoteAttachment?: AttachmentType;
|
|
|
|
|
} = {}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
2021-07-30 18:37:03 +00:00
|
|
|
|
const timestamp = options.timestamp || Date.now();
|
2021-06-08 14:59:38 +00:00
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.sendStart = Date.now();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
this.disableMessageField();
|
2022-06-13 16:30:14 +00:00
|
|
|
|
const contacts = await this.getUntrustedContacts(options);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
if (contacts && contacts.length) {
|
2021-08-30 21:32:56 +00:00
|
|
|
|
const sendAnyway = await this.showSendAnywayDialog(contacts.models);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
if (sendAnyway) {
|
2021-07-30 18:37:03 +00:00
|
|
|
|
this.sendMessage(message, mentions, { force: true, timestamp });
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 16:53:32 +00:00
|
|
|
|
this.enableMessageField();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
2020-10-21 16:53:32 +00:00
|
|
|
|
this.enableMessageField();
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.error(
|
2020-09-24 20:57:54 +00:00
|
|
|
|
'sendMessage error:',
|
|
|
|
|
error && error.stack ? error.stack : error
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
model.clearTypingTimers();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2020-11-16 21:34:41 +00:00
|
|
|
|
if (this.showInvalidMessageToast(message)) {
|
2020-10-21 16:53:32 +00:00
|
|
|
|
this.enableMessageField();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2021-09-08 23:23:44 +00:00
|
|
|
|
if (
|
|
|
|
|
!message.length &&
|
|
|
|
|
!this.hasFiles({ includePending: false }) &&
|
2021-09-29 20:23:06 +00:00
|
|
|
|
!options.voiceNoteAttachment
|
2021-09-08 23:23:44 +00:00
|
|
|
|
) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-29 20:23:06 +00:00
|
|
|
|
let attachments: Array<AttachmentType> = [];
|
|
|
|
|
if (options.voiceNoteAttachment) {
|
|
|
|
|
attachments = [options.voiceNoteAttachment];
|
|
|
|
|
} else if (options.draftAttachments) {
|
|
|
|
|
attachments = (
|
|
|
|
|
await Promise.all(
|
|
|
|
|
options.draftAttachments.map(resolveAttachmentDraftData)
|
|
|
|
|
)
|
|
|
|
|
).filter(isNotNil);
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-30 18:37:03 +00:00
|
|
|
|
const sendHQImages =
|
|
|
|
|
window.reduxStore &&
|
|
|
|
|
window.reduxStore.getState().composer.shouldSendHighQualityAttachments;
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const sendDelta = Date.now() - this.sendStart;
|
2021-07-30 18:37:03 +00:00
|
|
|
|
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.info('Send pre-checks took', sendDelta, 'milliseconds');
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2021-11-01 23:38:08 +00:00
|
|
|
|
model.enqueueMessageForSend(
|
2022-04-11 20:57:44 +00:00
|
|
|
|
{
|
|
|
|
|
body: message,
|
|
|
|
|
attachments,
|
|
|
|
|
quote: this.quote,
|
2022-06-17 00:48:57 +00:00
|
|
|
|
preview: getLinkPreviewForSend(message),
|
2022-04-11 20:57:44 +00:00
|
|
|
|
mentions,
|
|
|
|
|
},
|
2021-11-01 23:38:08 +00:00
|
|
|
|
{
|
|
|
|
|
sendHQImages,
|
|
|
|
|
timestamp,
|
2022-05-23 22:04:33 +00:00
|
|
|
|
extraReduxActions: () => {
|
|
|
|
|
this.compositionApi.current?.reset();
|
|
|
|
|
model.setMarkedUnread(false);
|
|
|
|
|
this.setQuoteMessage(null);
|
2022-06-17 00:48:57 +00:00
|
|
|
|
resetLinkPreview();
|
2022-05-23 22:04:33 +00:00
|
|
|
|
this.clearAttachments();
|
|
|
|
|
window.reduxActions.composer.resetComposer();
|
|
|
|
|
},
|
2021-11-01 23:38:08 +00:00
|
|
|
|
}
|
|
|
|
|
);
|
2020-09-24 20:57:54 +00:00
|
|
|
|
} catch (error) {
|
2021-09-17 18:27:53 +00:00
|
|
|
|
log.error(
|
2020-09-24 20:57:54 +00:00
|
|
|
|
'Error pulling attached files before send',
|
|
|
|
|
error && error.stack ? error.stack : error
|
|
|
|
|
);
|
|
|
|
|
} finally {
|
2020-10-21 16:53:32 +00:00
|
|
|
|
this.enableMessageField();
|
2020-09-24 20:57:54 +00:00
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2020-11-03 01:19:52 +00:00
|
|
|
|
onEditorStateChange(
|
|
|
|
|
messageText: string,
|
2021-08-30 21:32:56 +00:00
|
|
|
|
bodyRanges: Array<BodyRangeType>,
|
2020-11-03 01:19:52 +00:00
|
|
|
|
caretLocation?: number
|
2021-08-30 21:32:56 +00:00
|
|
|
|
): void {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.maybeBumpTyping(messageText);
|
2020-11-03 01:19:52 +00:00
|
|
|
|
this.debouncedSaveDraft(messageText, bodyRanges);
|
2022-06-17 00:48:57 +00:00
|
|
|
|
|
|
|
|
|
// If we have attachments, don't add link preview
|
|
|
|
|
if (!this.hasFiles({ includePending: true })) {
|
|
|
|
|
maybeGrabLinkPreview(
|
|
|
|
|
messageText,
|
|
|
|
|
LinkPreviewSourceType.Composer,
|
|
|
|
|
caretLocation
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
2020-11-03 01:19:52 +00:00
|
|
|
|
async saveDraft(
|
2021-06-08 14:59:38 +00:00
|
|
|
|
messageText: string,
|
2021-08-30 21:32:56 +00:00
|
|
|
|
bodyRanges: Array<BodyRangeType>
|
|
|
|
|
): Promise<void> {
|
2021-06-08 14:59:38 +00:00
|
|
|
|
const { model }: { model: ConversationModel } = this;
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
const trimmed =
|
|
|
|
|
messageText && messageText.length > 0 ? messageText.trim() : '';
|
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
if (model.get('draft') && (!messageText || trimmed.length === 0)) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.model.set({
|
|
|
|
|
draft: null,
|
|
|
|
|
draftChanged: true,
|
2020-11-03 01:19:52 +00:00
|
|
|
|
draftBodyRanges: [],
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
|
|
|
|
await this.saveModel();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-08 14:59:38 +00:00
|
|
|
|
if (messageText !== model.get('draft')) {
|
2021-10-19 00:09:55 +00:00
|
|
|
|
const now = Date.now();
|
|
|
|
|
let active_at = this.model.get('active_at');
|
|
|
|
|
let timestamp = this.model.get('timestamp');
|
|
|
|
|
|
|
|
|
|
if (!active_at) {
|
|
|
|
|
active_at = now;
|
|
|
|
|
timestamp = now;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.model.set({
|
2021-10-19 00:09:55 +00:00
|
|
|
|
active_at,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
draft: messageText,
|
2020-11-03 01:19:52 +00:00
|
|
|
|
draftBodyRanges: bodyRanges,
|
2021-10-19 00:09:55 +00:00
|
|
|
|
draftChanged: true,
|
|
|
|
|
timestamp,
|
2020-09-24 20:57:54 +00:00
|
|
|
|
});
|
|
|
|
|
await this.saveModel();
|
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
2020-09-24 20:57:54 +00:00
|
|
|
|
|
|
|
|
|
// Called whenever the user changes the message composition field. But only
|
|
|
|
|
// fires if there's content in the message field after the change.
|
2021-08-30 21:32:56 +00:00
|
|
|
|
maybeBumpTyping(messageText: string): void {
|
|
|
|
|
if (messageText.length && this.model.throttledBumpTyping) {
|
2020-09-24 20:57:54 +00:00
|
|
|
|
this.model.throttledBumpTyping();
|
|
|
|
|
}
|
2021-08-30 21:32:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.Whisper.ConversationView = ConversationView;
|