diff --git a/js/models/conversations.js b/js/models/conversations.js index 0c1fcfed2..32ffe48b0 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -517,6 +517,7 @@ isOnline: this.isOnline(), hasNickname: !!this.getNickname(), isKickedFromGroup: !!this.get('isKickedFromGroup'), + leftGroup: !!this.get('left'), selectedMessages: this.selectedMessages, diff --git a/js/models/messages.js b/js/models/messages.js index 567f55d68..44898a3cf 100644 --- a/js/models/messages.js +++ b/js/models/messages.js @@ -660,8 +660,8 @@ contact.number && contact.number[0] && contact.number[0].value; const onSendMessage = firstNumber ? () => { - this.trigger('open-conversation', firstNumber); - } + this.trigger('open-conversation', firstNumber); + } : null; const onClick = async () => { // First let's be sure that the signal account check is complete. @@ -692,8 +692,8 @@ !path && !objectUrl ? null : Object.assign({}, attachment.thumbnail || {}, { - objectUrl: path || objectUrl, - }); + objectUrl: path || objectUrl, + }); return Object.assign({}, attachment, { isVoiceMessage: Signal.Types.Attachment.isVoiceMessage(attachment), @@ -746,13 +746,13 @@ const onClick = noClick ? null : event => { - event.stopPropagation(); - this.trigger('scroll-to-message', { - author, - id, - referencedMessageNotFound, - }); - }; + event.stopPropagation(); + this.trigger('scroll-to-message', { + author, + id, + referencedMessageNotFound, + }); + }; const firstAttachment = quote.attachments && quote.attachments[0]; @@ -787,15 +787,15 @@ url: path ? getAbsoluteAttachmentPath(path) : null, screenshot: screenshot ? { - ...screenshot, - url: getAbsoluteAttachmentPath(screenshot.path), - } + ...screenshot, + url: getAbsoluteAttachmentPath(screenshot.path), + } : null, thumbnail: thumbnail ? { - ...thumbnail, - url: getAbsoluteAttachmentPath(thumbnail.path), - } + ...thumbnail, + url: getAbsoluteAttachmentPath(thumbnail.path), + } : null, }; }, @@ -824,9 +824,9 @@ const phoneNumbers = this.isIncoming() ? [this.get('source')] : _.union( - this.get('sent_to') || [], - this.get('recipients') || this.getConversation().getRecipients() - ); + this.get('sent_to') || [], + this.get('recipients') || this.getConversation().getRecipients() + ); // This will make the error message for outgoing key errors a bit nicer const allErrors = (this.get('errors') || []).map(error => { @@ -1052,7 +1052,6 @@ window.log.warn('retrySend: Nobody to send to!'); return this.commit(); - } const { body, attachments, preview, quote } = await this.uploadData(); @@ -1276,8 +1275,8 @@ */ const hasBodyOrAttachments = Boolean( dataMessage && - (dataMessage.body || - (dataMessage.attachments && dataMessage.attachments.length)) + (dataMessage.body || + (dataMessage.attachments && dataMessage.attachments.length)) ); const shouldNotifyPushServer = hasBodyOrAttachments && isSessionOrClosedMessage; @@ -1448,7 +1447,6 @@ }); await this.commit(); - }, getServerId() { return this.get('serverId'); @@ -1463,7 +1461,6 @@ }); await this.commit(); - }, async setServerTimestamp(serverTimestamp) { if (_.isEqual(this.get('serverTimestamp'), serverTimestamp)) { @@ -1475,7 +1472,6 @@ }); await this.commit(); - }, async setIsPublic(isPublic) { if (_.isEqual(this.get('isPublic'), isPublic)) { @@ -1487,7 +1483,6 @@ }); await this.commit(); - }, async sendSyncMessageOnly(dataMessage) { @@ -1499,7 +1494,6 @@ await this.commit(); - const data = dataMessage instanceof libsession.Messages.Outgoing.DataMessage ? dataMessage.dataProto() @@ -1531,7 +1525,6 @@ this.set({ sentSync: true }); await this.commit(); - }, someRecipientsFailed() { @@ -1613,8 +1606,6 @@ forceSave, Message: Whisper.Message, }); - console.warn('case commit') - this.trigger('change'); return id; }, diff --git a/js/modules/debug.js b/js/modules/debug.js index 7d6bee2c7..f91a5dd47 100644 --- a/js/modules/debug.js +++ b/js/modules/debug.js @@ -1,5 +1,5 @@ /* eslint-env node */ -/* global log, Signal, Whisper */ +/* global log */ const fs = require('fs-extra'); const path = require('path'); diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index eac946c70..e11a073f8 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -8,7 +8,6 @@ textsecure, Whisper, ConversationController, - BlockedNumberController, */ // eslint-disable-next-line func-names @@ -1051,45 +1050,6 @@ toastOptions.title = i18n('expiredWarning'); toastOptions.id = 'expiredWarning'; } - if (!window.clientClockSynced) { - let clockSynced = false; - if (window.setClockParams) { - // Check to see if user has updated their clock to current time - clockSynced = await window.setClockParams(); - } else { - window.log.info('setClockParams not loaded yet'); - } - if (clockSynced) { - toastOptions.title = i18n('clockOutOfSync'); - toastOptions.id = 'clockOutOfSync'; - } - } - if ( - this.model.isPrivate() && - BlockedNumberController.isBlocked(this.model.id) - ) { - toastOptions.title = i18n('unblockToSend'); - toastOptions.id = 'unblockToSend'; - } - if ( - !this.model.isPrivate() && - BlockedNumberController.isGroupBlocked(this.model.id) - ) { - toastOptions.title = i18n('unblockGroupToSend'); - toastOptions.id = 'unblockGroupToSend'; - } - if (!this.model.isPrivate() && this.model.get('left')) { - toastOptions.title = i18n('youLeftTheGroup'); - toastOptions.id = 'youLeftTheGroup'; - } - if ( - message.length > - window.libsession.Constants.CONVERSATION.MAX_MESSAGE_BODY_LENGTH - ) { - toastOptions.title = i18n('messageBodyTooLong'); - toastOptions.id = 'messageBodyTooLong'; - } - if (toastOptions.title) { window.pushToast(toastOptions); this.focusMessageFieldAndClearDisabled(); diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index db3fd2d5c..ec33d27bc 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -59,6 +59,10 @@ interface Props { onLoadVoiceNoteView: any; onExitVoiceNoteView: any; + isBlocked: boolean; + isPrivate: boolean; + isKickedFromGroup: boolean; + leftGroup: boolean; quotedMessageProps?: ReplyingToMessageProps; removeQuotedMessage: () => void; @@ -206,16 +210,35 @@ export class SessionCompositionBox extends React.Component { } private renderCompositionView() { - const { placeholder } = this.props; + const { + placeholder, + isBlocked, + isKickedFromGroup, + leftGroup, + isPrivate, + } = this.props; const { showEmojiPanel, message } = this.state; + const typingEnabled = !(isBlocked || isKickedFromGroup || leftGroup); + const { i18n } = window; + const messageWithWarning = isKickedFromGroup + ? i18n('youGotKickedFromGroup') + : leftGroup + ? i18n('youLeftTheGroup') + : isBlocked && isPrivate + ? i18n('unblockToSend') + : isBlocked && !isPrivate + ? i18n('unblockGroupToSend') + : undefined; return ( <> - + {typingEnabled && ( + + )} { onChange={this.onChoseAttachment} /> - + {typingEnabled && ( + + )}
{ placeholder={placeholder} maxLength={Constants.CONVERSATION.MAX_MESSAGE_BODY_LENGTH} onKeyDown={this.onKeyDown} - value={message} + value={messageWithWarning || message} onChange={this.onChange} + disabled={!typingEnabled} />
- + {typingEnabled && ( + + )}
{ />
-
(this.emojiPanel = ref)} - onKeyDown={this.onKeyDown} - role="button" - > - {showEmojiPanel && ( - - )} -
+ {typingEnabled && ( +
(this.emojiPanel = ref)} + onKeyDown={this.onKeyDown} + role="button" + > + {showEmojiPanel && ( + + )} +
+ )} ); } @@ -471,9 +501,19 @@ export class SessionCompositionBox extends React.Component { }, ''); } + // tslint:disable-next-line: cyclomatic-complexity private async onSendMessage() { const messagePlaintext = this.parseEmojis(this.state.message); + const { isBlocked, isPrivate, leftGroup, isKickedFromGroup } = this.props; + if (isBlocked && isPrivate) { + ToastUtils.pushUnblockToSend(); + return; + } + if (isBlocked && !isPrivate) { + ToastUtils.pushUnblockToSendGroup(); + return; + } // Verify message length const msgLen = messagePlaintext?.length || 0; if (msgLen > window.CONSTANTS.MAX_MESSAGE_BODY_LENGTH) { @@ -484,6 +524,29 @@ export class SessionCompositionBox extends React.Component { ToastUtils.pushMessageBodyMissing(); return; } + if (!window.clientClockSynced) { + let clockSynced = false; + if (window.setClockParams) { + // Check to see if user has updated their clock to current time + clockSynced = await window.setClockParams(); + } else { + window.log.info('setClockParams not loaded yet'); + } + if (clockSynced) { + ToastUtils.pushClockOutOfSync(); + return; + } + } + + if (!isPrivate && leftGroup) { + ToastUtils.pushYouLeftTheGroup(); + return; + } + if (!isPrivate && isKickedFromGroup) { + ToastUtils.pushYouLeftTheGroup(); + return; + } + const { quotedMessageProps } = this.props; const { stagedLinkPreview } = this.state; diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index 5a82d61fe..545f43b22 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -28,6 +28,8 @@ import * as MIME from '../../../types/MIME'; import { SessionFileDropzone } from './SessionFileDropzone'; import { ConversationType } from '../../../state/ducks/conversations'; import { MessageView } from '../../MainViewController'; +import { getMessageById } from '../../../../js/modules/data'; +import { pushUnblockToSend } from '../../../session/utils/Toast'; interface State { // Message sending progress @@ -161,7 +163,6 @@ export class SessionConversation extends React.Component { div?.removeEventListener('drop', this.handleDrop); } - public componentDidMount() { // Pause thread to wait for rendering to complete setTimeout(() => { @@ -253,7 +254,12 @@ export class SessionConversation extends React.Component { {!isRss && ( + // tslint:disable-next-line: use-simple-attributes { // ~~~~~~~~~~~~~~ MESSAGE QUOTE ~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private async replyToMessage(quotedMessageTimestamp?: number) { + if (this.props.conversation.isBlocked) { + pushUnblockToSend(); + return; + } if (!_.isEqual(this.state.quotedMessageTimestamp, quotedMessageTimestamp)) { const { messages, conversationKey } = this.props; const conversationModel = window.ConversationController.getOrThrow( @@ -697,7 +707,9 @@ export class SessionConversation extends React.Component { let quotedMessageProps = null; if (quotedMessageTimestamp) { - const quotedMessage = messages.find(m => m.attributes.sent_at === quotedMessageTimestamp); + const quotedMessage = messages.find( + m => m.attributes.sent_at === quotedMessageTimestamp + ); if (quotedMessage) { const quotedMessageModel = await getMessageById(quotedMessage.id, { diff --git a/ts/components/session/conversation/SessionConversationMessagesList.tsx b/ts/components/session/conversation/SessionConversationMessagesList.tsx index 359523be4..7ca18480a 100644 --- a/ts/components/session/conversation/SessionConversationMessagesList.tsx +++ b/ts/components/session/conversation/SessionConversationMessagesList.tsx @@ -144,6 +144,7 @@ export class SessionConversationMessagesList extends React.Component< if (conversation.unreadCount === 0) { findFirstUnreadIndex = -1; } + const isConvoBlocked = conversation.isBlocked; return ( <> diff --git a/ts/session/utils/Toast.tsx b/ts/session/utils/Toast.tsx index aaac6bc72..e7d58810b 100644 --- a/ts/session/utils/Toast.tsx +++ b/ts/session/utils/Toast.tsx @@ -211,3 +211,19 @@ export function pushPairingRequestReceived(alreadyLinked: boolean) { ); } } + +export function pushUnblockToSend() { + pushToastInfo('unblockToSend', window.i18n('unblockToSend')); +} + +export function pushUnblockToSendGroup() { + pushToastInfo('unblockGroupToSend', window.i18n('unblockGroupToSend')); +} + +export function pushClockOutOfSync() { + pushToastError('clockOutOfSync', window.i18n('clockOutOfSync')); +} + +export function pushYouLeftTheGroup() { + pushToastError('youLeftTheGroup', window.i18n('youLeftTheGroup')); +} diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 7a503f1ac..d2670b4d3 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -56,6 +56,8 @@ export type ConversationType = { isSecondary?: boolean; primaryDevice: string; isBlocked: boolean; + isKickedFromGroup: boolean; + leftGroup: boolean; }; export type ConversationLookupType = { [key: string]: ConversationType; diff --git a/ts/state/ducks/messages.ts b/ts/state/ducks/messages.ts index dbc7afc35..126678279 100644 --- a/ts/state/ducks/messages.ts +++ b/ts/state/ducks/messages.ts @@ -8,7 +8,7 @@ export type MessagesStateType = Array; export async function getMessages( conversationKey: string, numMessages: number -) : Promise { +): Promise { const conversation = window.ConversationController.get(conversationKey); if (!conversation) { // no valid conversation, early return @@ -88,12 +88,16 @@ const messageSlice = createSlice({ name: 'messages', initialState: [] as MessagesStateType, reducers: { - messageChanged(state, action){ - console.log('message changed ', state, action) - const messageInStoreIndex = state.findIndex(m => m.id === action.payload.id); + messageChanged(state, action) { + // console.log('message changed ', action); + const messageInStoreIndex = state.findIndex( + m => m.id === action.payload.id + ); if (messageInStoreIndex >= 0) { - state[messageInStoreIndex] = _.omit(action.payload, toOmitFromMessageModel) - ; + state[messageInStoreIndex] = _.omit( + action.payload, + toOmitFromMessageModel + ); } return state; }, diff --git a/ts/test/state/selectors/conversations_test.ts b/ts/test/state/selectors/conversations_test.ts index d29c91ae0..1c35296eb 100644 --- a/ts/test/state/selectors/conversations_test.ts +++ b/ts/test/state/selectors/conversations_test.ts @@ -30,6 +30,8 @@ describe('state/selectors/conversations', () => { isSelected: false, isTyping: false, isBlocked: false, + isKickedFromGroup: false, + leftGroup: false, }, id2: { id: 'id2', @@ -49,6 +51,8 @@ describe('state/selectors/conversations', () => { isSelected: false, isTyping: false, isBlocked: false, + isKickedFromGroup: false, + leftGroup: false, }, id3: { id: 'id3', @@ -68,6 +72,8 @@ describe('state/selectors/conversations', () => { isSelected: false, isTyping: false, isBlocked: false, + isKickedFromGroup: false, + leftGroup: false, }, id4: { id: 'id4', @@ -78,7 +84,6 @@ describe('state/selectors/conversations', () => { isArchived: false, isSecondary: false, primaryDevice: 'id4', - type: 'direct', isMe: false, lastUpdated: Date.now(), @@ -87,6 +92,8 @@ describe('state/selectors/conversations', () => { isSelected: false, isTyping: false, isBlocked: false, + isKickedFromGroup: false, + leftGroup: false, }, id5: { id: 'id5', @@ -106,6 +113,8 @@ describe('state/selectors/conversations', () => { isSelected: false, isTyping: false, isBlocked: false, + isKickedFromGroup: false, + leftGroup: false, }, }; const comparator = _getConversationComparator(i18n, regionCode); diff --git a/ts/window.d.ts b/ts/window.d.ts index a5ee275ec..97b78d4c0 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -109,5 +109,7 @@ declare global { sessionGenerateKeyPair: ( seed: ArrayBuffer ) => Promise<{ pubKey: ArrayBufferLike; privKey: ArrayBufferLike }>; + setClockParams: any; + clientClockSynced: number | undefined; } }