Modern profile sharing in 1:1 and GroupV1 groups
This commit is contained in:
parent
60f2422e2a
commit
04b7a29229
22 changed files with 371 additions and 115 deletions
|
@ -2727,6 +2727,38 @@
|
||||||
"message": "Accept",
|
"message": "Accept",
|
||||||
"description": "Shown as a button to let the user accept a message request"
|
"description": "Shown as a button to let the user accept a message request"
|
||||||
},
|
},
|
||||||
|
"MessageRequests--continue": {
|
||||||
|
"message": "Continue",
|
||||||
|
"description": "Shown as a button to share your profile, necessary to continue messaging in a conversation"
|
||||||
|
},
|
||||||
|
"MessageRequests--profile-sharing--group": {
|
||||||
|
"message": "Continue your conversation with this group and share your name and photo with its members? $learnMore$",
|
||||||
|
"description": "Shown when user hasn't shared their profile in a group yet",
|
||||||
|
"placeholders": {
|
||||||
|
"learnMore": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Learn More."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MessageRequests--profile-sharing--direct": {
|
||||||
|
"message": "Continue this conversation with $firstName$ and share your name and photo with them? $learnMore$",
|
||||||
|
"description": "Shown when user hasn't shared their profile in a 1:1 conversation yet",
|
||||||
|
"placeholders": {
|
||||||
|
"firstName": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Alice"
|
||||||
|
},
|
||||||
|
"learnMore": {
|
||||||
|
"content": "$2",
|
||||||
|
"example": "Learn More."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MessageRequests--learn-more": {
|
||||||
|
"message": "Learn more.",
|
||||||
|
"description": "Shown at the end of profile sharing messages as a link."
|
||||||
|
},
|
||||||
"ConversationHero--members": {
|
"ConversationHero--members": {
|
||||||
"message": "$count$ members",
|
"message": "$count$ members",
|
||||||
"description": "Specifies the number of members in a group conversation",
|
"description": "Specifies the number of members in a group conversation",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
Component: window.Signal.Components.SafetyNumberChangeDialog,
|
Component: window.Signal.Components.SafetyNumberChangeDialog,
|
||||||
props: {
|
props: {
|
||||||
confirmText: options.confirmText,
|
confirmText: options.confirmText,
|
||||||
contacts: options.contacts.map(contact => contact.cachedProps),
|
contacts: options.contacts.map(contact => contact.format()),
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
dialog.remove();
|
dialog.remove();
|
||||||
|
|
|
@ -3597,6 +3597,10 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
&__name {
|
&__name {
|
||||||
@include font-body-2-bold;
|
@include font-body-2-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__learn-more {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__buttons {
|
&__buttons {
|
||||||
|
|
|
@ -2,12 +2,13 @@ import { get, throttle } from 'lodash';
|
||||||
import { WebAPIType } from './textsecure/WebAPI';
|
import { WebAPIType } from './textsecure/WebAPI';
|
||||||
|
|
||||||
type ConfigKeyType =
|
type ConfigKeyType =
|
||||||
| 'desktop.messageRequests'
|
|
||||||
| 'desktop.gv2'
|
|
||||||
| 'desktop.cds'
|
| 'desktop.cds'
|
||||||
|
| 'desktop.clientExpiration'
|
||||||
|
| 'desktop.gv2'
|
||||||
|
| 'desktop.mandatoryProfileSharing'
|
||||||
|
| 'desktop.messageRequests'
|
||||||
| 'desktop.storage'
|
| 'desktop.storage'
|
||||||
| 'desktop.storageWrite'
|
| 'desktop.storageWrite';
|
||||||
| 'desktop.clientExpiration';
|
|
||||||
type ConfigValueType = {
|
type ConfigValueType = {
|
||||||
name: ConfigKeyType;
|
name: ConfigKeyType;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
|
|
@ -652,12 +652,13 @@ type WhatIsThis = typeof window.WhatIsThis;
|
||||||
function initializeRedux() {
|
function initializeRedux() {
|
||||||
// Here we set up a full redux store with initial state for our LeftPane Root
|
// Here we set up a full redux store with initial state for our LeftPane Root
|
||||||
const convoCollection = window.getConversations();
|
const convoCollection = window.getConversations();
|
||||||
const conversations = convoCollection.map(
|
const conversations = convoCollection.map(conversation =>
|
||||||
conversation => conversation.cachedProps
|
conversation.format()
|
||||||
);
|
);
|
||||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
const ourUuid = window.textsecure.storage.user.getUuid();
|
||||||
const ourConversationId = window.ConversationController.getOurConversationId();
|
const ourConversationId = window.ConversationController.getOurConversationId();
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
conversations: {
|
conversations: {
|
||||||
conversationLookup: window.Signal.Util.makeLookup(conversations, 'id'),
|
conversationLookup: window.Signal.Util.makeLookup(conversations, 'id'),
|
||||||
|
@ -1580,7 +1581,7 @@ type WhatIsThis = typeof window.WhatIsThis;
|
||||||
'desktop.clientExpiration',
|
'desktop.clientExpiration',
|
||||||
({ value }) => {
|
({ value }) => {
|
||||||
const remoteBuildExpirationTimestamp = window.Signal.Util.parseRemoteClientExpiration(
|
const remoteBuildExpirationTimestamp = window.Signal.Util.parseRemoteClientExpiration(
|
||||||
value
|
value as string
|
||||||
);
|
);
|
||||||
if (remoteBuildExpirationTimestamp) {
|
if (remoteBuildExpirationTimestamp) {
|
||||||
window.storage.put(
|
window.storage.put(
|
||||||
|
|
|
@ -16,12 +16,15 @@ import {
|
||||||
MessageRequestActions,
|
MessageRequestActions,
|
||||||
Props as MessageRequestActionsProps,
|
Props as MessageRequestActionsProps,
|
||||||
} from './conversation/MessageRequestActions';
|
} from './conversation/MessageRequestActions';
|
||||||
|
import { MandatoryProfileSharingActions } from './conversation/MandatoryProfileSharingActions';
|
||||||
import { countStickers } from './stickers/lib';
|
import { countStickers } from './stickers/lib';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
import { EmojiPickDataType } from './emoji/EmojiPicker';
|
import { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
readonly i18n: LocalizerType;
|
readonly i18n: LocalizerType;
|
||||||
|
readonly groupVersion?: 1 | 2;
|
||||||
|
readonly isMissingMandatoryProfileSharing?: boolean;
|
||||||
readonly messageRequestsEnabled?: boolean;
|
readonly messageRequestsEnabled?: boolean;
|
||||||
readonly acceptedMessageRequest?: boolean;
|
readonly acceptedMessageRequest?: boolean;
|
||||||
readonly compositionApi?: React.MutableRefObject<{
|
readonly compositionApi?: React.MutableRefObject<{
|
||||||
|
@ -113,7 +116,9 @@ export const CompositionArea = ({
|
||||||
// Message Requests
|
// Message Requests
|
||||||
acceptedMessageRequest,
|
acceptedMessageRequest,
|
||||||
conversationType,
|
conversationType,
|
||||||
|
groupVersion,
|
||||||
isBlocked,
|
isBlocked,
|
||||||
|
isMissingMandatoryProfileSharing,
|
||||||
messageRequestsEnabled,
|
messageRequestsEnabled,
|
||||||
name,
|
name,
|
||||||
onAccept,
|
onAccept,
|
||||||
|
@ -326,7 +331,7 @@ export const CompositionArea = ({
|
||||||
};
|
};
|
||||||
}, [setLarge]);
|
}, [setLarge]);
|
||||||
|
|
||||||
if ((!acceptedMessageRequest || isBlocked) && messageRequestsEnabled) {
|
if (messageRequestsEnabled && (!acceptedMessageRequest || isBlocked)) {
|
||||||
return (
|
return (
|
||||||
<MessageRequestActions
|
<MessageRequestActions
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
@ -345,6 +350,28 @@ export const CompositionArea = ({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no message request, but we haven't shared profile yet, we show profile-sharing UI
|
||||||
|
if (
|
||||||
|
(conversationType === 'direct' ||
|
||||||
|
(conversationType === 'group' && groupVersion === 1)) &&
|
||||||
|
isMissingMandatoryProfileSharing
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<MandatoryProfileSharingActions
|
||||||
|
i18n={i18n}
|
||||||
|
conversationType={conversationType}
|
||||||
|
onBlock={onBlock}
|
||||||
|
onBlockAndDelete={onBlockAndDelete}
|
||||||
|
onDelete={onDelete}
|
||||||
|
onAccept={onAccept}
|
||||||
|
name={name}
|
||||||
|
profileName={profileName}
|
||||||
|
phoneNumber={phoneNumber}
|
||||||
|
title={title}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-composition-area">
|
<div className="module-composition-area">
|
||||||
<div className="module-composition-area__toggle-large">
|
<div className="module-composition-area__toggle-large">
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { text } from '@storybook/addon-knobs';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MandatoryProfileSharingActions,
|
||||||
|
Props as MandatoryProfileSharingActionsProps,
|
||||||
|
} from './MandatoryProfileSharingActions';
|
||||||
|
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||||
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
const getBaseProps = (
|
||||||
|
isGroup = false
|
||||||
|
): MandatoryProfileSharingActionsProps => ({
|
||||||
|
i18n,
|
||||||
|
conversationType: isGroup ? 'group' : 'direct',
|
||||||
|
firstName: text('firstName', 'Cayce'),
|
||||||
|
title: isGroup
|
||||||
|
? text('title', 'NYC Rock Climbers')
|
||||||
|
: text('title', 'Cayce Bollard'),
|
||||||
|
name: isGroup
|
||||||
|
? text('name', 'NYC Rock Climbers')
|
||||||
|
: text('name', 'Cayce Bollard'),
|
||||||
|
onBlock: action('block'),
|
||||||
|
onBlockAndDelete: action('onBlockAndDelete'),
|
||||||
|
onDelete: action('delete'),
|
||||||
|
onAccept: action('accept'),
|
||||||
|
});
|
||||||
|
|
||||||
|
storiesOf('Components/Conversation/MandatoryProfileSharingActions', module)
|
||||||
|
.add('Direct', () => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '480px' }}>
|
||||||
|
<MandatoryProfileSharingActions {...getBaseProps()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add('Group', () => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '480px' }}>
|
||||||
|
<MandatoryProfileSharingActions {...getBaseProps(true)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
134
ts/components/conversation/MandatoryProfileSharingActions.tsx
Normal file
134
ts/components/conversation/MandatoryProfileSharingActions.tsx
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { ContactName, PropsType as ContactNameProps } from './ContactName';
|
||||||
|
import {
|
||||||
|
MessageRequestActionsConfirmation,
|
||||||
|
MessageRequestState,
|
||||||
|
Props as MessageRequestActionsConfirmationProps,
|
||||||
|
} from './MessageRequestActionsConfirmation';
|
||||||
|
import { Intl } from '../Intl';
|
||||||
|
import { LocalizerType } from '../../types/Util';
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
i18n: LocalizerType;
|
||||||
|
firstName?: string;
|
||||||
|
onAccept(): unknown;
|
||||||
|
} & Omit<ContactNameProps, 'module' | 'i18n'> &
|
||||||
|
Pick<
|
||||||
|
MessageRequestActionsConfirmationProps,
|
||||||
|
'conversationType' | 'onBlock' | 'onBlockAndDelete' | 'onDelete'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const MandatoryProfileSharingActions = ({
|
||||||
|
conversationType,
|
||||||
|
firstName,
|
||||||
|
i18n,
|
||||||
|
name,
|
||||||
|
onAccept,
|
||||||
|
onBlock,
|
||||||
|
onBlockAndDelete,
|
||||||
|
onDelete,
|
||||||
|
phoneNumber,
|
||||||
|
profileName,
|
||||||
|
title,
|
||||||
|
}: Props): JSX.Element => {
|
||||||
|
const [mrState, setMrState] = React.useState(MessageRequestState.default);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{mrState !== MessageRequestState.default ? (
|
||||||
|
<MessageRequestActionsConfirmation
|
||||||
|
i18n={i18n}
|
||||||
|
onBlock={onBlock}
|
||||||
|
onBlockAndDelete={onBlockAndDelete}
|
||||||
|
onUnblock={() => {
|
||||||
|
throw new Error(
|
||||||
|
'Should not be able to unblock from MandatoryProfileSharingActions'
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onDelete={onDelete}
|
||||||
|
name={name}
|
||||||
|
profileName={profileName}
|
||||||
|
phoneNumber={phoneNumber}
|
||||||
|
title={title}
|
||||||
|
conversationType={conversationType}
|
||||||
|
state={mrState}
|
||||||
|
onChangeState={setMrState}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<div className="module-message-request-actions">
|
||||||
|
<p className="module-message-request-actions__message">
|
||||||
|
<Intl
|
||||||
|
i18n={i18n}
|
||||||
|
id={`MessageRequests--profile-sharing--${conversationType}`}
|
||||||
|
components={{
|
||||||
|
firstName: (
|
||||||
|
<strong
|
||||||
|
key="name"
|
||||||
|
className="module-message-request-actions__message__name"
|
||||||
|
>
|
||||||
|
<ContactName
|
||||||
|
name={name}
|
||||||
|
profileName={profileName}
|
||||||
|
phoneNumber={phoneNumber}
|
||||||
|
title={firstName || title}
|
||||||
|
i18n={i18n}
|
||||||
|
/>
|
||||||
|
</strong>
|
||||||
|
),
|
||||||
|
learnMore: (
|
||||||
|
<a
|
||||||
|
href="https://support.signal.org/hc/articles/360007459591"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="module-message-request-actions__message__learn-more"
|
||||||
|
>
|
||||||
|
{i18n('MessageRequests--learn-more')}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
<div className="module-message-request-actions__buttons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setMrState(MessageRequestState.blocking);
|
||||||
|
}}
|
||||||
|
tabIndex={0}
|
||||||
|
className={classNames(
|
||||||
|
'module-message-request-actions__buttons__button',
|
||||||
|
'module-message-request-actions__buttons__button--deny'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{i18n('MessageRequests--block')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setMrState(MessageRequestState.deleting);
|
||||||
|
}}
|
||||||
|
tabIndex={0}
|
||||||
|
className={classNames(
|
||||||
|
'module-message-request-actions__buttons__button',
|
||||||
|
'module-message-request-actions__buttons__button--deny'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{i18n('MessageRequests--delete')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onAccept}
|
||||||
|
tabIndex={0}
|
||||||
|
className={classNames(
|
||||||
|
'module-message-request-actions__buttons__button',
|
||||||
|
'module-message-request-actions__buttons__button--accept'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{i18n('MessageRequests--continue')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -45,6 +45,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
|
||||||
authorTitle: text('authorTitle', overrideProps.authorTitle || ''),
|
authorTitle: text('authorTitle', overrideProps.authorTitle || ''),
|
||||||
bodyRanges: overrideProps.bodyRanges,
|
bodyRanges: overrideProps.bodyRanges,
|
||||||
canReply: true,
|
canReply: true,
|
||||||
|
canDownload: true,
|
||||||
canDeleteForEveryone: overrideProps.canDeleteForEveryone || false,
|
canDeleteForEveryone: overrideProps.canDeleteForEveryone || false,
|
||||||
clearSelectedMessage: action('clearSelectedMessage'),
|
clearSelectedMessage: action('clearSelectedMessage'),
|
||||||
collapseMetadata: overrideProps.collapseMetadata,
|
collapseMetadata: overrideProps.collapseMetadata,
|
||||||
|
|
|
@ -137,6 +137,7 @@ export type PropsData = {
|
||||||
deletedForEveryone?: boolean;
|
deletedForEveryone?: boolean;
|
||||||
|
|
||||||
canReply: boolean;
|
canReply: boolean;
|
||||||
|
canDownload: boolean;
|
||||||
canDeleteForEveryone: boolean;
|
canDeleteForEveryone: boolean;
|
||||||
bodyRanges?: BodyRangesType;
|
bodyRanges?: BodyRangesType;
|
||||||
};
|
};
|
||||||
|
@ -1159,6 +1160,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
): JSX.Element | null {
|
): JSX.Element | null {
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
|
canDownload,
|
||||||
canReply,
|
canReply,
|
||||||
direction,
|
direction,
|
||||||
disableMenu,
|
disableMenu,
|
||||||
|
@ -1294,7 +1296,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{canReply ? reactButton : null}
|
{canReply ? reactButton : null}
|
||||||
{canReply ? downloadButton : null}
|
{canDownload ? downloadButton : null}
|
||||||
{canReply ? replyButton : null}
|
{canReply ? replyButton : null}
|
||||||
{menuButton}
|
{menuButton}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1328,6 +1330,7 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
public renderContextMenu(triggerId: string): JSX.Element {
|
public renderContextMenu(triggerId: string): JSX.Element {
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
|
canDownload,
|
||||||
canReply,
|
canReply,
|
||||||
deleteMessage,
|
deleteMessage,
|
||||||
deleteMessageForEveryone,
|
deleteMessageForEveryone,
|
||||||
|
@ -1349,7 +1352,8 @@ export class Message extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<ContextMenu id={triggerId}>
|
<ContextMenu id={triggerId}>
|
||||||
{!isSticker &&
|
{canDownload &&
|
||||||
|
!isSticker &&
|
||||||
!multipleAttachments &&
|
!multipleAttachments &&
|
||||||
!isTapToView &&
|
!isTapToView &&
|
||||||
attachments &&
|
attachments &&
|
||||||
|
|
|
@ -17,6 +17,7 @@ const defaultMessage: MessageProps = {
|
||||||
authorTitle: 'Max',
|
authorTitle: 'Max',
|
||||||
canReply: true,
|
canReply: true,
|
||||||
canDeleteForEveryone: true,
|
canDeleteForEveryone: true,
|
||||||
|
canDownload: true,
|
||||||
clearSelectedMessage: () => null,
|
clearSelectedMessage: () => null,
|
||||||
conversationId: 'my-convo',
|
conversationId: 'my-convo',
|
||||||
conversationType: 'direct',
|
conversationType: 'direct',
|
||||||
|
|
|
@ -20,6 +20,7 @@ const defaultMessageProps: MessagesProps = {
|
||||||
authorTitle: 'Person X',
|
authorTitle: 'Person X',
|
||||||
canReply: true,
|
canReply: true,
|
||||||
canDeleteForEveryone: true,
|
canDeleteForEveryone: true,
|
||||||
|
canDownload: true,
|
||||||
clearSelectedMessage: () => null,
|
clearSelectedMessage: () => null,
|
||||||
conversationId: 'conversationId',
|
conversationId: 'conversationId',
|
||||||
conversationType: 'direct', // override
|
conversationType: 'direct', // override
|
||||||
|
|
4
ts/model-types.d.ts
vendored
4
ts/model-types.d.ts
vendored
|
@ -84,7 +84,7 @@ export type MessageAttributesType = {
|
||||||
referencedMessageNotFound: boolean;
|
referencedMessageNotFound: boolean;
|
||||||
text: string;
|
text: string;
|
||||||
} | null;
|
} | null;
|
||||||
reactions: Array<{ fromId: string; emoji: unknown; timestamp: unknown }>;
|
reactions: Array<{ fromId: string; emoji: string; timestamp: number }>;
|
||||||
read_by: Array<string | null>;
|
read_by: Array<string | null>;
|
||||||
requiredProtocolVersion: number;
|
requiredProtocolVersion: number;
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
|
@ -141,7 +141,7 @@ export type ConversationAttributesType = {
|
||||||
accessKey: string | null;
|
accessKey: string | null;
|
||||||
addedBy?: string;
|
addedBy?: string;
|
||||||
capabilities: { uuid: string };
|
capabilities: { uuid: string };
|
||||||
color?: ColorType;
|
color?: string;
|
||||||
discoveredUnregisteredAt: number;
|
discoveredUnregisteredAt: number;
|
||||||
draftAttachments: Array<unknown>;
|
draftAttachments: Array<unknown>;
|
||||||
draftTimestamp: number | null;
|
draftTimestamp: number | null;
|
||||||
|
|
|
@ -239,9 +239,16 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
|
|
||||||
// Keep props ready
|
// Keep props ready
|
||||||
this.generateProps = () => {
|
this.generateProps = () => {
|
||||||
|
// This is to prevent race conditions on startup; Conversation models are created
|
||||||
|
// but the full window.ConversationController.load() sequence isn't complete.
|
||||||
|
if (!window.ConversationController.isFetchComplete()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.cachedProps = this.getProps();
|
this.cachedProps = this.getProps();
|
||||||
};
|
};
|
||||||
this.on('change', this.generateProps);
|
this.on('change', this.generateProps);
|
||||||
|
|
||||||
this.generateProps();
|
this.generateProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1027,19 +1034,13 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
format(): ConversationType | null | undefined {
|
format(): ConversationType {
|
||||||
|
this.cachedProps = this.cachedProps || this.getProps();
|
||||||
|
|
||||||
return this.cachedProps;
|
return this.cachedProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
getProps(): ConversationType | null {
|
getProps(): ConversationType {
|
||||||
// This is to prevent race conditions on startup; Conversation models are created
|
|
||||||
// but the full window.ConversationController.load() sequence isn't complete. So, we
|
|
||||||
// don't cache props on create, but we do later when load() calls generateProps()
|
|
||||||
// for us.
|
|
||||||
if (!window.ConversationController.isFetchComplete()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const color = this.getColor()!;
|
const color = this.getColor()!;
|
||||||
|
|
||||||
|
@ -1064,6 +1065,13 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
'desktop.messageRequests'
|
'desktop.messageRequests'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let groupVersion: undefined | 1 | 2;
|
||||||
|
if (this.isGroupV1()) {
|
||||||
|
groupVersion = 1;
|
||||||
|
} else if (this.isGroupV2()) {
|
||||||
|
groupVersion = 2;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: DESKTOP-720
|
// TODO: DESKTOP-720
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
const result = {
|
const result = {
|
||||||
|
@ -1078,12 +1086,14 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
draftPreview,
|
draftPreview,
|
||||||
draftText,
|
draftText,
|
||||||
firstName: this.get('profileName')!,
|
firstName: this.get('profileName')!,
|
||||||
|
groupVersion,
|
||||||
inboxPosition,
|
inboxPosition,
|
||||||
isAccepted: this.getAccepted(),
|
isAccepted: this.getAccepted(),
|
||||||
isArchived: this.get('isArchived')!,
|
isArchived: this.get('isArchived')!,
|
||||||
isBlocked: this.isBlocked(),
|
isBlocked: this.isBlocked(),
|
||||||
isMe: this.isMe(),
|
isMe: this.isMe(),
|
||||||
isPinned: this.get('isPinned'),
|
isPinned: this.get('isPinned'),
|
||||||
|
isMissingMandatoryProfileSharing: this.isMissingRequiredProfileSharing(),
|
||||||
isVerified: this.isVerified(),
|
isVerified: this.isVerified(),
|
||||||
lastMessage: {
|
lastMessage: {
|
||||||
status: this.get('lastMessageStatus')!,
|
status: this.get('lastMessageStatus')!,
|
||||||
|
@ -1091,6 +1101,7 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
deletedForEveryone: this.get('lastMessageDeletedForEveryone')!,
|
deletedForEveryone: this.get('lastMessageDeletedForEveryone')!,
|
||||||
},
|
},
|
||||||
lastUpdated: this.get('timestamp')!,
|
lastUpdated: this.get('timestamp')!,
|
||||||
|
|
||||||
membersCount: this.isPrivate()
|
membersCount: this.isPrivate()
|
||||||
? undefined
|
? undefined
|
||||||
: (this.get('membersV2')! || this.get('members')! || []).length,
|
: (this.get('membersV2')! || this.get('members')! || []).length,
|
||||||
|
@ -1693,6 +1704,18 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
return this.get('messageRequestResponseType') || 0;
|
return this.get('messageRequestResponseType') || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMissingRequiredProfileSharing(): boolean {
|
||||||
|
const mandatoryProfileSharingEnabled = window.Signal.RemoteConfig.isEnabled(
|
||||||
|
'desktop.mandatoryProfileSharing'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mandatoryProfileSharingEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !this.get('profileSharing');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if this conversation should be considered "accepted" in terms
|
* Determine if this conversation should be considered "accepted" in terms
|
||||||
* of message requests
|
* of message requests
|
||||||
|
@ -3864,20 +3887,19 @@ export class ConversationModel extends window.Backbone.Model<
|
||||||
}
|
}
|
||||||
|
|
||||||
const { migrateColor } = Util;
|
const { migrateColor } = Util;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
return migrateColor(this.get('color'));
|
||||||
return migrateColor(this.get('color')!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvatarPath(): string | null {
|
getAvatarPath(): string | undefined {
|
||||||
const avatar = this.isMe()
|
const avatar = this.isMe()
|
||||||
? this.get('profileAvatar') || this.get('avatar')
|
? this.get('profileAvatar') || this.get('avatar')
|
||||||
: this.get('avatar') || this.get('profileAvatar');
|
: this.get('avatar') || this.get('profileAvatar');
|
||||||
|
|
||||||
if (avatar && avatar.path) {
|
if (!avatar || !avatar.path) {
|
||||||
return getAbsoluteAttachmentPath(avatar.path);
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return getAbsoluteAttachmentPath(avatar.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
canChangeTimer(): boolean {
|
canChangeTimer(): boolean {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
LastMessageStatus,
|
LastMessageStatus,
|
||||||
ConversationType,
|
ConversationType,
|
||||||
} from '../state/ducks/conversations';
|
} from '../state/ducks/conversations';
|
||||||
|
import { PropsData } from '../components/conversation/Message';
|
||||||
import { CallbackResultType } from '../textsecure/SendMessage';
|
import { CallbackResultType } from '../textsecure/SendMessage';
|
||||||
import { BodyRangesType } from '../types/Util';
|
import { BodyRangesType } from '../types/Util';
|
||||||
import { PropsDataType as GroupsV2Props } from '../components/conversation/GroupV2Change';
|
import { PropsDataType as GroupsV2Props } from '../components/conversation/GroupV2Change';
|
||||||
|
@ -59,7 +60,9 @@ const { getTextWithMentions, GoogleChrome } = window.Signal.Util;
|
||||||
|
|
||||||
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
|
const { addStickerPackReference, getMessageBySender } = window.Signal.Data;
|
||||||
const { bytesFromString } = window.Signal.Crypto;
|
const { bytesFromString } = window.Signal.Crypto;
|
||||||
const PLACEHOLDER_CONTACT = {
|
const PLACEHOLDER_CONTACT: Pick<ConversationType, 'title' | 'type' | 'id'> = {
|
||||||
|
id: 'placeholder-contact',
|
||||||
|
type: 'direct',
|
||||||
title: window.i18n('unknownContact'),
|
title: window.i18n('unknownContact'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -694,26 +697,26 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
.map(attachment => this.getPropsForAttachment(attachment));
|
.map(attachment => this.getPropsForAttachment(attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
getPropsForMessage(): WhatIsThis {
|
// Note: interactionMode is mixed in via selectors/conversations._messageSelector
|
||||||
|
getPropsForMessage(): Omit<PropsData, 'interactionMode'> {
|
||||||
const sourceId = this.getContactId();
|
const sourceId = this.getContactId();
|
||||||
const contact = this.findAndFormatContact(sourceId);
|
const contact = this.findAndFormatContact(sourceId);
|
||||||
const contactModel = this.findContact(sourceId);
|
const contactModel = this.findContact(sourceId);
|
||||||
|
|
||||||
const authorColor = contactModel ? contactModel.getColor() : null;
|
const authorColor = contactModel ? contactModel.getColor() : undefined;
|
||||||
const authorAvatarPath = contactModel ? contactModel.getAvatarPath() : null;
|
const authorAvatarPath = contactModel
|
||||||
|
? contactModel.getAvatarPath()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const expirationLength = this.get('expireTimer') * 1000;
|
const expirationLength = this.get('expireTimer') * 1000;
|
||||||
const expireTimerStart = this.get('expirationStartTimestamp');
|
const expireTimerStart = this.get('expirationStartTimestamp');
|
||||||
const expirationTimestamp =
|
const expirationTimestamp =
|
||||||
expirationLength && expireTimerStart
|
expirationLength && expireTimerStart
|
||||||
? expireTimerStart + expirationLength
|
? expireTimerStart + expirationLength
|
||||||
: null;
|
: undefined;
|
||||||
|
|
||||||
const conversation = this.getConversation();
|
const conversation = this.getConversation();
|
||||||
const isGroup = conversation && !conversation.isPrivate();
|
const isGroup = conversation && !conversation.isPrivate();
|
||||||
const conversationAccepted = Boolean(
|
|
||||||
conversation && conversation.getAccepted()
|
|
||||||
);
|
|
||||||
const sticker = this.get('sticker');
|
const sticker = this.get('sticker');
|
||||||
|
|
||||||
const isTapToView = this.isTapToView();
|
const isTapToView = this.isTapToView();
|
||||||
|
@ -739,7 +742,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
textPending: this.get('bodyPending'),
|
textPending: this.get('bodyPending'),
|
||||||
id: this.id,
|
id: this.id,
|
||||||
conversationId: this.get('conversationId'),
|
conversationId: this.get('conversationId'),
|
||||||
conversationAccepted,
|
|
||||||
isSticker: Boolean(sticker),
|
isSticker: Boolean(sticker),
|
||||||
direction: this.isIncoming() ? 'incoming' : 'outgoing',
|
direction: this.isIncoming() ? 'incoming' : 'outgoing',
|
||||||
timestamp: this.get('sent_at'),
|
timestamp: this.get('sent_at'),
|
||||||
|
@ -747,6 +749,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
contact: this.getPropsForEmbeddedContact(),
|
contact: this.getPropsForEmbeddedContact(),
|
||||||
canReply: this.canReply(),
|
canReply: this.canReply(),
|
||||||
canDeleteForEveryone: this.canDeleteForEveryone(),
|
canDeleteForEveryone: this.canDeleteForEveryone(),
|
||||||
|
canDownload: this.canDownload(),
|
||||||
authorTitle: contact.title,
|
authorTitle: contact.title,
|
||||||
authorColor,
|
authorColor,
|
||||||
authorName: contact.name,
|
authorName: contact.name,
|
||||||
|
@ -801,7 +804,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
// Dependencies of prop-generation functions
|
// Dependencies of prop-generation functions
|
||||||
findAndFormatContact(
|
findAndFormatContact(
|
||||||
identifier?: string
|
identifier?: string
|
||||||
): Partial<ConversationType> & Pick<ConversationType, 'title'> {
|
): Partial<ConversationType> &
|
||||||
|
Pick<ConversationType, 'title' | 'id' | 'type'> {
|
||||||
if (!identifier) {
|
if (!identifier) {
|
||||||
return PLACEHOLDER_CONTACT;
|
return PLACEHOLDER_CONTACT;
|
||||||
}
|
}
|
||||||
|
@ -824,6 +828,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: 'phone-only',
|
||||||
|
type: 'direct',
|
||||||
title: phoneNumber,
|
title: phoneNumber,
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
};
|
};
|
||||||
|
@ -839,9 +845,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
createNonBreakingLastSeparator(text: string): string | null {
|
createNonBreakingLastSeparator(text: string): string | undefined {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nbsp = '\xa0';
|
const nbsp = '\xa0';
|
||||||
|
@ -859,7 +865,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return this.get('type') === 'incoming';
|
return this.get('type') === 'incoming';
|
||||||
}
|
}
|
||||||
|
|
||||||
getMessagePropStatus(): LastMessageStatus | null {
|
getMessagePropStatus(): LastMessageStatus | undefined {
|
||||||
const sent = this.get('sent');
|
const sent = this.get('sent');
|
||||||
const sentTo = this.get('sent_to') || [];
|
const sentTo = this.get('sent_to') || [];
|
||||||
|
|
||||||
|
@ -870,7 +876,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return 'error';
|
return 'error';
|
||||||
}
|
}
|
||||||
if (!this.isOutgoing()) {
|
if (!this.isOutgoing()) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const readBy = this.get('read_by') || [];
|
const readBy = this.get('read_by') || [];
|
||||||
|
@ -2010,34 +2016,50 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canDownload(): boolean {
|
||||||
|
const conversation = this.getConversation();
|
||||||
|
const isAccepted = Boolean(conversation && conversation.getAccepted());
|
||||||
|
|
||||||
|
if (this.isOutgoing()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAccepted;
|
||||||
|
}
|
||||||
|
|
||||||
canReply(): boolean {
|
canReply(): boolean {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
const conversation = this.getConversation();
|
||||||
const isAccepted = this.getConversation()!.getAccepted();
|
|
||||||
const errors = this.get('errors');
|
const errors = this.get('errors');
|
||||||
const isOutgoing = this.get('type') === 'outgoing';
|
const isOutgoing = this.get('type') === 'outgoing';
|
||||||
const numDelivered = this.get('delivered');
|
const numDelivered = this.get('delivered');
|
||||||
|
|
||||||
// Case 1: We cannot reply if we have accepted the message request
|
// Case 1: If mandatory profile sharing is enabled, and we haven't shared yet, then
|
||||||
if (!isAccepted) {
|
// we can't reply.
|
||||||
|
if (conversation?.isMissingRequiredProfileSharing()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: We cannot reply if this message is deleted for everyone
|
// Case 2: We cannot reply if we have accepted the message request
|
||||||
|
if (!conversation?.getAccepted()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3: We cannot reply if this message is deleted for everyone
|
||||||
if (this.get('deletedForEveryone')) {
|
if (this.get('deletedForEveryone')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 3: We can reply if this is outgoing and delievered to at least one recipient
|
// Case 4: We can reply if this is outgoing and delievered to at least one recipient
|
||||||
if (isOutgoing && numDelivered > 0) {
|
if (isOutgoing && numDelivered > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 4: We can reply if there are no errors
|
// Case 5: We can reply if there are no errors
|
||||||
if (!errors || (errors && errors.length === 0)) {
|
if (!errors || (errors && errors.length === 0)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 5: default
|
// Case 6: default
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,14 +117,7 @@ export class CallingClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationProps = conversation.cachedProps;
|
const conversationProps = conversation.format();
|
||||||
|
|
||||||
if (!conversationProps) {
|
|
||||||
window.log.error(
|
|
||||||
'CallingClass.startCallingLobby(): No conversation props?'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.log.info('CallingClass.startCallingLobby(): Starting lobby');
|
window.log.info('CallingClass.startCallingLobby(): Starting lobby');
|
||||||
this.uxActions.showCallLobby({
|
this.uxActions.showCallLobby({
|
||||||
|
@ -829,10 +822,7 @@ export class CallingClass {
|
||||||
conversation: ConversationModel,
|
conversation: ConversationModel,
|
||||||
call: Call
|
call: Call
|
||||||
): CallDetailsType {
|
): CallDetailsType {
|
||||||
const conversationProps = conversation.cachedProps;
|
const conversationProps = conversation.format();
|
||||||
if (!conversationProps) {
|
|
||||||
throw new Error('getAcceptedCallDetails: No conversation props?');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...conversationProps,
|
...conversationProps,
|
||||||
|
|
|
@ -75,6 +75,8 @@ export type ConversationType = {
|
||||||
draftText?: string | null;
|
draftText?: string | null;
|
||||||
draftPreview?: string;
|
draftPreview?: string;
|
||||||
|
|
||||||
|
groupVersion?: 1 | 2;
|
||||||
|
isMissingMandatoryProfileSharing?: boolean;
|
||||||
messageRequestsEnabled?: boolean;
|
messageRequestsEnabled?: boolean;
|
||||||
acceptedMessageRequest?: boolean;
|
acceptedMessageRequest?: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { LocalizerType } from '../types/Util';
|
import { LocalizerType } from '../types/Util';
|
||||||
import { ConversationType } from '../state/ducks/conversations';
|
|
||||||
|
|
||||||
export type ProfileNameChangeType = {
|
export type ProfileNameChangeType = {
|
||||||
type: 'name';
|
type: 'name';
|
||||||
oldName: string;
|
oldName: string;
|
||||||
newName: string;
|
newName: string;
|
||||||
};
|
};
|
||||||
|
type ContactType = {
|
||||||
|
title: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export function getStringForProfileChange(
|
export function getStringForProfileChange(
|
||||||
change: ProfileNameChangeType,
|
change: ProfileNameChangeType,
|
||||||
changedContact: ConversationType,
|
changedContact: ContactType,
|
||||||
i18n: LocalizerType
|
i18n: LocalizerType
|
||||||
): string {
|
): string {
|
||||||
if (change.type === 'name') {
|
if (change.type === 'name') {
|
||||||
|
|
|
@ -13120,7 +13120,7 @@
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "ts/components/CompositionArea.js",
|
"path": "ts/components/CompositionArea.js",
|
||||||
"line": " el.innerHTML = '';",
|
"line": " el.innerHTML = '';",
|
||||||
"lineNumber": 24,
|
"lineNumber": 25,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-05-20T20:10:43.540Z",
|
"updated": "2020-05-20T20:10:43.540Z",
|
||||||
"reasonDetail": "Our code, no user input, only clearing out the dom"
|
"reasonDetail": "Our code, no user input, only clearing out the dom"
|
||||||
|
@ -13129,7 +13129,7 @@
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "ts/components/CompositionArea.tsx",
|
"path": "ts/components/CompositionArea.tsx",
|
||||||
"line": " el.innerHTML = '';",
|
"line": " el.innerHTML = '';",
|
||||||
"lineNumber": 78,
|
"lineNumber": 81,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-06-03T19:23:21.195Z",
|
"updated": "2020-06-03T19:23:21.195Z",
|
||||||
"reasonDetail": "Our code, no user input, only clearing out the dom"
|
"reasonDetail": "Our code, no user input, only clearing out the dom"
|
||||||
|
@ -13305,7 +13305,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.tsx",
|
"path": "ts/components/conversation/Message.tsx",
|
||||||
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
|
"line": " public audioRef: React.RefObject<HTMLAudioElement> = React.createRef();",
|
||||||
"lineNumber": 217,
|
"lineNumber": 218,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-09-08T20:19:01.913Z"
|
"updated": "2020-09-08T20:19:01.913Z"
|
||||||
},
|
},
|
||||||
|
@ -13313,7 +13313,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.tsx",
|
"path": "ts/components/conversation/Message.tsx",
|
||||||
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
"line": " public focusRef: React.RefObject<HTMLDivElement> = React.createRef();",
|
||||||
"lineNumber": 219,
|
"lineNumber": 220,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-09-08T20:19:01.913Z"
|
"updated": "2020-09-08T20:19:01.913Z"
|
||||||
},
|
},
|
||||||
|
@ -13321,7 +13321,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/Message.tsx",
|
"path": "ts/components/conversation/Message.tsx",
|
||||||
"line": " > = React.createRef();",
|
"line": " > = React.createRef();",
|
||||||
"lineNumber": 223,
|
"lineNumber": 224,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2020-08-28T19:36:40.817Z"
|
"updated": "2020-08-28T19:36:40.817Z"
|
||||||
},
|
},
|
||||||
|
@ -13548,4 +13548,4 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-09-08T23:07:22.682Z"
|
"updated": "2020-09-08T23:07:22.682Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { ColorType } from '../types/Colors';
|
import { ColorType } from '../types/Colors';
|
||||||
|
|
||||||
// import { missingCaseError } from './missingCaseError';
|
|
||||||
|
|
||||||
type OldColorType =
|
type OldColorType =
|
||||||
| 'amber'
|
| 'amber'
|
||||||
| 'blue'
|
| 'blue'
|
||||||
|
@ -22,9 +20,11 @@ type OldColorType =
|
||||||
| 'red'
|
| 'red'
|
||||||
| 'teal'
|
| 'teal'
|
||||||
| 'yellow'
|
| 'yellow'
|
||||||
| 'ultramarine';
|
| 'ultramarine'
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
|
||||||
export function migrateColor(color: OldColorType): ColorType {
|
export function migrateColor(color?: OldColorType): ColorType {
|
||||||
switch (color) {
|
switch (color) {
|
||||||
// These colors no longer exist
|
// These colors no longer exist
|
||||||
case 'orange':
|
case 'orange':
|
||||||
|
@ -62,10 +62,6 @@ export function migrateColor(color: OldColorType): ColorType {
|
||||||
case 'ultramarine':
|
case 'ultramarine':
|
||||||
return color;
|
return color;
|
||||||
|
|
||||||
// Can uncomment this to ensure that we've covered all potential cases
|
|
||||||
// default:
|
|
||||||
// throw missingCaseError(color);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 'grey';
|
return 'grey';
|
||||||
}
|
}
|
||||||
|
|
|
@ -436,11 +436,12 @@ Whisper.ConversationView = Whisper.View.extend({
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...this.model.cachedProps,
|
...this.model.format(),
|
||||||
|
|
||||||
leftGroup: this.model.get('left'),
|
leftGroup: this.model.get('left'),
|
||||||
|
|
||||||
disableTimerChanges:
|
disableTimerChanges:
|
||||||
|
this.model.isMissingRequiredProfileSharing() ||
|
||||||
this.model.get('left') ||
|
this.model.get('left') ||
|
||||||
!this.model.getAccepted() ||
|
!this.model.getAccepted() ||
|
||||||
!this.model.canChangeTimer(),
|
!this.model.canChangeTimer(),
|
||||||
|
|
37
ts/window.d.ts
vendored
37
ts/window.d.ts
vendored
|
@ -3,6 +3,7 @@
|
||||||
import * as Backbone from 'backbone';
|
import * as Backbone from 'backbone';
|
||||||
import * as Underscore from 'underscore';
|
import * as Underscore from 'underscore';
|
||||||
import { Ref } from 'react';
|
import { Ref } from 'react';
|
||||||
|
import * as Util from './util';
|
||||||
import {
|
import {
|
||||||
ConversationModelCollectionType,
|
ConversationModelCollectionType,
|
||||||
MessageModelCollectionType,
|
MessageModelCollectionType,
|
||||||
|
@ -356,41 +357,7 @@ declare global {
|
||||||
};
|
};
|
||||||
VisualAttachment: any;
|
VisualAttachment: any;
|
||||||
};
|
};
|
||||||
Util: {
|
Util: typeof Util;
|
||||||
isFileDangerous: any;
|
|
||||||
GoogleChrome: {
|
|
||||||
isImageTypeSupported: (contentType: string) => unknown;
|
|
||||||
isVideoTypeSupported: (contentType: string) => unknown;
|
|
||||||
};
|
|
||||||
downloadAttachment: (attachment: WhatIsThis) => WhatIsThis;
|
|
||||||
getStringForProfileChange: (
|
|
||||||
change: unknown,
|
|
||||||
changedContact: unknown,
|
|
||||||
i18n: unknown
|
|
||||||
) => string;
|
|
||||||
getTextWithMentions: (
|
|
||||||
bodyRanges: BodyRangesType,
|
|
||||||
text: string
|
|
||||||
) => string;
|
|
||||||
deleteForEveryone: (
|
|
||||||
message: unknown,
|
|
||||||
del: unknown,
|
|
||||||
bool: boolean
|
|
||||||
) => void;
|
|
||||||
zkgroup: typeof zkgroup;
|
|
||||||
combineNames: typeof combineNames;
|
|
||||||
migrateColor: (color: string) => ColorType;
|
|
||||||
createBatcher: (options: WhatIsThis) => WhatIsThis;
|
|
||||||
Registration: {
|
|
||||||
everDone: () => boolean;
|
|
||||||
markDone: () => void;
|
|
||||||
markEverDone: () => void;
|
|
||||||
remove: () => void;
|
|
||||||
};
|
|
||||||
hasExpired: () => boolean;
|
|
||||||
makeLookup: (conversations: WhatIsThis, key: string) => void;
|
|
||||||
parseRemoteClientExpiration: (value: WhatIsThis) => WhatIsThis;
|
|
||||||
};
|
|
||||||
LinkPreviews: {
|
LinkPreviews: {
|
||||||
isMediaLinkInWhitelist: any;
|
isMediaLinkInWhitelist: any;
|
||||||
getTitleMetaTag: any;
|
getTitleMetaTag: any;
|
||||||
|
|
Loading…
Reference in a new issue