Cleanup redux store (#1925)

* do not consider expire timer update unread messages #1881

* cleanup conversation props in redux to only have what cannot be derived

* fix app not starting without the await on convo creation

* cleanup props of message model
This commit is contained in:
Audric Ackermann 2021-09-17 08:41:04 +02:00 committed by GitHub
parent 945ecf34a1
commit b17312c13c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 464 additions and 324 deletions

View File

@ -100,7 +100,7 @@ button.grey {
}
a {
cursor: auto;
cursor: pointer;
user-select: text;
}

View File

@ -17,7 +17,7 @@ export enum AvatarSize {
type Props = {
avatarPath?: string | null;
name?: string; // display name, profileName or phoneNumber, whatever is set first
name?: string; // display name, profileName or pubkey, whatever is set first
pubkey?: string;
size: AvatarSize;
base64Data?: string; // if this is not empty, it will be used to render the avatar with base64 encoded data
@ -65,7 +65,7 @@ const NoImage = (props: {
const AvatarImage = (props: {
avatarPath?: string;
base64Data?: string;
name?: string; // display name, profileName or phoneNumber, whatever is set first
name?: string; // display name, profileName or pubkey, whatever is set first
imageBroken: boolean;
handleImageError: () => any;
}) => {

View File

@ -5,7 +5,7 @@ import { Avatar, AvatarSize } from './Avatar';
import { Emojify } from './conversation/Emojify';
interface Props {
phoneNumber: string;
pubkey: string;
isMe?: boolean;
name?: string;
profileName?: string;
@ -15,26 +15,24 @@ interface Props {
export class ContactListItem extends React.Component<Props> {
public renderAvatar() {
const { avatarPath, name, phoneNumber, profileName } = this.props;
const { avatarPath, name, pubkey, profileName } = this.props;
const userName = name || profileName || phoneNumber;
const userName = name || profileName || pubkey;
return (
<Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={phoneNumber} />
);
return <Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={pubkey} />;
}
public render() {
const { name, onClick, isMe, phoneNumber, profileName } = this.props;
const { name, onClick, isMe, pubkey, profileName } = this.props;
const title = name ? name : phoneNumber;
const title = name ? name : pubkey;
const displayName = isMe ? window.i18n('me') : title;
const profileElement =
!isMe && profileName && !name ? (
<span className="module-contact-list-item__text__profile-name">
~
<Emojify text={profileName} key={`emojify-list-item-${phoneNumber}`} />
<Emojify text={profileName} key={`emojify-list-item-${pubkey}`} />
</span>
) : null;

View File

@ -43,6 +43,7 @@ export const StyledConversationListItemIconWrapper = styled.div`
type PropsHousekeeping = {
style?: Object;
};
// tslint:disable: use-simple-attributes
type Props = ConversationListItemProps & PropsHousekeeping;
@ -165,7 +166,7 @@ const UserItem = (props: {
return (
<div className="module-conversation__user">
<ContactName
phoneNumber={displayedPubkey}
pubkey={displayedPubkey}
name={name}
profileName={displayName}
module="module-conversation__user"
@ -258,7 +259,7 @@ const ConversationListItem = (props: Props) => {
type,
isPublic,
avatarPath,
notificationForConvo,
isPrivate,
currentNotificationSetting,
} = props;
const triggerId = `conversation-item-${conversationId}-ctxmenu`;
@ -294,47 +295,53 @@ const ConversationListItem = (props: Props) => {
style={style}
className={classNames(
'module-conversation-list-item',
unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
unreadCount > 0 && mentionedUs ? 'module-conversation-list-item--mentioned-us' : null,
unreadCount && unreadCount > 0 ? 'module-conversation-list-item--has-unread' : null,
unreadCount && unreadCount > 0 && mentionedUs
? 'module-conversation-list-item--mentioned-us'
: null,
isSelected ? 'module-conversation-list-item--is-selected' : null,
isBlocked ? 'module-conversation-list-item--is-blocked' : null
)}
>
<AvatarItem
conversationId={conversationId}
avatarPath={avatarPath}
avatarPath={avatarPath || null}
memberAvatars={membersAvatar}
profileName={profileName}
name={name}
/>
<div className="module-conversation-list-item__content">
<HeaderItem
mentionedUs={mentionedUs}
unreadCount={unreadCount}
mentionedUs={!!mentionedUs}
unreadCount={unreadCount || 0}
activeAt={activeAt}
isMe={isMe}
isPinned={isPinned}
isMe={!!isMe}
isPinned={!!isPinned}
conversationId={conversationId}
name={name}
profileName={profileName}
currentNotificationSetting={currentNotificationSetting}
currentNotificationSetting={currentNotificationSetting || 'all'}
/>
<MessageItem
isTyping={!!isTyping}
unreadCount={unreadCount || 0}
lastMessage={lastMessage}
/>
<MessageItem isTyping={isTyping} unreadCount={unreadCount} lastMessage={lastMessage} />
</div>
</div>
<Portal>
<MemoConversationListItemContextMenu
triggerId={triggerId}
conversationId={conversationId}
hasNickname={hasNickname}
isBlocked={isBlocked}
isKickedFromGroup={isKickedFromGroup}
isMe={isMe}
isPublic={isPublic}
left={left}
hasNickname={!!hasNickname}
isBlocked={!!isBlocked}
isPrivate={!!isPrivate}
isKickedFromGroup={!!isKickedFromGroup}
isMe={!!isMe}
isPublic={!!isPublic}
left={!!left}
type={type}
notificationForConvo={notificationForConvo}
currentNotificationSetting={currentNotificationSetting}
currentNotificationSetting={currentNotificationSetting || 'all'}
/>
</Portal>
</div>

View File

@ -43,10 +43,10 @@ export class UserSearchResults extends React.Component<Props> {
}
private renderContact(contact: ConversationListItemProps, index: Number) {
const { profileName, phoneNumber } = contact;
const { profileName, id } = contact;
const { selectedContact } = this.props;
const shortenedPubkey = PubKey.shorten(phoneNumber);
const shortenedPubkey = PubKey.shorten(id);
const rowContent = `${profileName} ${shortenedPubkey}`;
return (
@ -55,8 +55,8 @@ export class UserSearchResults extends React.Component<Props> {
'contacts-dropdown-row',
selectedContact === index && 'contacts-dropdown-row-selected'
)}
key={contact.phoneNumber}
onClick={() => this.props.onContactSelected(contact.phoneNumber)}
key={contact.id}
onClick={() => this.props.onContactSelected(contact.id)}
role="button"
>
{rowContent}

View File

@ -4,7 +4,7 @@ import classNames from 'classnames';
import { Emojify } from './Emojify';
type Props = {
phoneNumber: string;
pubkey: string;
name?: string | null;
profileName?: string | null;
module?: string;
@ -14,15 +14,7 @@ type Props = {
};
export const ContactName = (props: Props) => {
const {
phoneNumber,
name,
profileName,
module,
boldProfileName,
compact,
shouldShowPubkey,
} = props;
const { pubkey, name, profileName, module, boldProfileName, compact, shouldShowPubkey } = props;
const prefix = module ? module : 'module-contact-name';
const shouldShowProfile = Boolean(profileName || name);
@ -40,7 +32,7 @@ export const ContactName = (props: Props) => {
const pubKeyElement = shouldShowPubkey ? (
<span className={`${prefix}__profile-number`}>
<Emojify text={phoneNumber} />
<Emojify text={pubkey} />
</span>
) : null;

View File

@ -27,7 +27,6 @@ import { deleteMessagesById } from '../../interactions/conversationInteractions'
import {
closeMessageDetailsView,
closeRightPanel,
NotificationForConvoOption,
openRightPanel,
resetSelectedMessageIds,
} from '../../state/ducks/conversations';
@ -38,10 +37,9 @@ export interface TimerOption {
}
export type ConversationHeaderProps = {
id: string;
conversationKey: string;
name?: string;
phoneNumber: string;
profileName?: string;
avatarPath: string | null;
@ -60,7 +58,6 @@ export type ConversationHeaderProps = {
subscriberCount?: number;
expirationSettingName?: string;
notificationForConvo: Array<NotificationForConvoOption>;
currentNotificationSetting: ConversationNotificationSettingType;
hasNickname: boolean;
@ -138,13 +135,13 @@ const AvatarHeader = (props: {
avatarPath: string | null;
memberAvatars?: Array<ConversationAvatar>;
name?: string;
phoneNumber: string;
pubkey: string;
profileName?: string;
showBackButton: boolean;
onAvatarClick?: (pubkey: string) => void;
}) => {
const { avatarPath, memberAvatars, name, phoneNumber, profileName } = props;
const userName = name || profileName || phoneNumber;
const { avatarPath, memberAvatars, name, pubkey, profileName } = props;
const userName = name || profileName || pubkey;
return (
<span className="module-conversation-header__avatar">
@ -155,11 +152,11 @@ const AvatarHeader = (props: {
onAvatarClick={() => {
// do not allow right panel to appear if another button is shown on the SessionConversation
if (props.onAvatarClick && !props.showBackButton) {
props.onAvatarClick(phoneNumber);
props.onAvatarClick(pubkey);
}
}}
memberAvatars={memberAvatars}
pubkey={phoneNumber}
pubkey={pubkey}
/>
</span>
);
@ -188,7 +185,7 @@ export const StyledSubtitleContainer = styled.div`
`;
export type ConversationHeaderTitleProps = {
phoneNumber: string;
conversationKey: string;
profileName?: string;
isMe: boolean;
isGroup: boolean;
@ -210,7 +207,7 @@ const ConversationHeaderTitle = () => {
}
const {
phoneNumber,
conversationKey,
profileName,
isGroup,
isPublic,
@ -249,7 +246,7 @@ const ConversationHeaderTitle = () => {
? `${memberCountText}${notificationSubtitle}`
: `${notificationSubtitle}`;
const title = profileName || name || phoneNumber;
const title = profileName || name || conversationKey;
return (
<div
@ -301,19 +298,17 @@ export const ConversationHeaderWithDetails = () => {
const {
isKickedFromGroup,
expirationSettingName,
phoneNumber,
avatarPath,
name,
profileName,
id,
isMe,
isPublic,
notificationForConvo,
currentNotificationSetting,
hasNickname,
weAreAdmin,
isBlocked,
left,
conversationKey,
isPrivate,
isGroup,
} = headerProps;
@ -343,7 +338,7 @@ export const ConversationHeaderWithDetails = () => {
onAvatarClick={() => {
dispatch(openRightPanel());
}}
phoneNumber={phoneNumber}
pubkey={conversationKey}
showBackButton={isMessageDetailOpened}
avatarPath={avatarPath}
memberAvatars={memberDetails}
@ -353,7 +348,7 @@ export const ConversationHeaderWithDetails = () => {
)}
<MemoConversationHeaderMenu
conversationId={id}
conversationId={conversationKey}
triggerId={triggerId}
isMe={isMe}
isPublic={isPublic}
@ -364,7 +359,6 @@ export const ConversationHeaderWithDetails = () => {
isPrivate={isPrivate}
left={left}
hasNickname={hasNickname}
notificationForConvo={notificationForConvo}
currentNotificationSetting={currentNotificationSetting}
/>
</div>
@ -374,7 +368,7 @@ export const ConversationHeaderWithDetails = () => {
isPublic={isPublic}
onCloseOverlay={() => dispatch(resetSelectedMessageIds())}
onDeleteSelectedMessages={() => {
void deleteMessagesById(selectedMessageIds, id, true);
void deleteMessagesById(selectedMessageIds, conversationKey, true);
}}
/>
)}

View File

@ -29,11 +29,8 @@ function getPeople(change: TypeWithContacts) {
flatten(
(change.contacts || []).map((contact, index) => {
const element = (
<span
key={`external-${contact.phoneNumber}`}
className="module-group-notification__contact"
>
{contact.profileName || contact.phoneNumber}
<span key={`external-${contact.pubkey}`} className="module-group-notification__contact">
{contact.profileName || contact.pubkey}
</span>
);

View File

@ -14,12 +14,10 @@ import {
} from '../../state/selectors/conversations';
const AvatarItem = (props: { contact: ContactPropsMessageDetail }) => {
const { avatarPath, phoneNumber, name, profileName } = props.contact;
const userName = name || profileName || phoneNumber;
const { avatarPath, pubkey, name, profileName } = props.contact;
const userName = name || profileName || pubkey;
return (
<Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={phoneNumber} />
);
return <Avatar avatarPath={avatarPath} name={userName} size={AvatarSize.S} pubkey={pubkey} />;
};
const DeleteButtonItem = (props: { messageId: string; convoId: string; isDeletable: boolean }) => {
@ -49,7 +47,7 @@ const ContactsItem = (props: { contacts: Array<ContactPropsMessageDetail> }) =>
return (
<div className="module-message-detail__contact-container">
{contacts.map(contact => (
<ContactItem key={contact.phoneNumber} contact={contact} />
<ContactItem key={contact.pubkey} contact={contact} />
))}
</div>
);
@ -69,12 +67,12 @@ const ContactItem = (props: { contact: ContactPropsMessageDetail }) => {
) : null;
return (
<div key={contact.phoneNumber} className="module-message-detail__contact">
<div key={contact.pubkey} className="module-message-detail__contact">
<AvatarItem contact={contact} />
<div className="module-message-detail__contact__text">
<div className="module-message-detail__contact__name">
<ContactName
phoneNumber={contact.phoneNumber}
pubkey={contact.pubkey}
name={contact.name}
profileName={contact.profileName}
shouldShowPubkey={true}

View File

@ -296,7 +296,7 @@ const QuoteAuthor = (props: QuoteAuthorProps) => {
window.i18n('you')
) : (
<ContactName
phoneNumber={PubKey.shorten(authorPhoneNumber)}
pubkey={PubKey.shorten(authorPhoneNumber)}
name={authorName}
profileName={authorProfileName}
compact={true}

View File

@ -8,12 +8,12 @@ import { PropsForExpirationTimer } from '../../state/ducks/conversations';
import { ReadableMessage } from './ReadableMessage';
const TimerNotificationContent = (props: PropsForExpirationTimer) => {
const { phoneNumber, profileName, timespan, type, disabled } = props;
const { pubkey, profileName, timespan, type, disabled } = props;
const changeKey = disabled ? 'disabledDisappearingMessages' : 'theyChangedTheTimer';
const contact = (
<span key={`external-${phoneNumber}`} className="module-timer-notification__contact">
{profileName || phoneNumber}
<span key={`external-${pubkey}`} className="module-timer-notification__contact">
{profileName || pubkey}
</span>
);

View File

@ -6,7 +6,7 @@ import { ConversationTypeEnum } from '../../models/conversation';
interface TypingBubbleProps {
avatarPath?: string;
phoneNumber: string;
pubkey: string;
displayedName: string | null;
conversationType: ConversationTypeEnum;
isTyping: boolean;

View File

@ -94,6 +94,7 @@ type Props = {
ctxMenuID: string;
isDetailView?: boolean;
};
// tslint:disable: use-simple-attributes
export const GenericReadableMessage = (props: Props) => {
const msgProps = useSelector(state =>
@ -167,14 +168,14 @@ export const GenericReadableMessage = (props: Props) => {
)}
onContextMenu={handleContextMenu}
receivedAt={receivedAt}
isUnread={isUnread}
isUnread={!!isUnread}
key={`readable-message-${messageId}`}
>
<MessageAvatar messageId={messageId} />
<ExpireTimer
isCorrectSide={!isIncoming}
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
expirationLength={expirationLength || 0}
expirationTimestamp={expirationTimestamp || null}
/>
<MessageContentWithStatuses
ctxMenuID={props.ctxMenuID}
@ -184,8 +185,8 @@ export const GenericReadableMessage = (props: Props) => {
/>
<ExpireTimer
isCorrectSide={isIncoming}
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
expirationLength={expirationLength || 0}
expirationTimestamp={expirationTimestamp || null}
/>
</ReadableMessage>
);

View File

@ -48,7 +48,7 @@ export const MessageAuthorText = (props: Props) => {
return (
<Flex container={true}>
<ContactName
phoneNumber={displayedPubkey}
pubkey={displayedPubkey}
name={authorName}
profileName={authorProfileName}
module="module-message__author"

View File

@ -9,6 +9,7 @@ import {
isMessageSelectionMode,
} from '../../../state/selectors/conversations';
import { Quote } from '../Quote';
// tslint:disable: use-simple-attributes
type Props = {
onQuoteClick?: (quote: QuoteClickOptions) => void;
@ -44,7 +45,7 @@ export const MessageQuote = (props: Props) => {
scrollToQuote?.({
quoteAuthor: authorPhoneNumber,
quoteId,
referencedMessageNotFound,
referencedMessageNotFound: referencedMessageNotFound || false,
});
},
[scrollToQuote, selected?.quote, multiSelectMode, props.messageId]
@ -65,14 +66,14 @@ export const MessageQuote = (props: Props) => {
return (
<Quote
onClick={onQuoteClick}
text={quote.text}
text={quote.text || ''}
attachment={quote.attachment}
isIncoming={direction === 'incoming'}
authorPhoneNumber={displayedPubkey}
authorProfileName={quote.authorProfileName}
authorName={quote.authorName}
referencedMessageNotFound={quote.referencedMessageNotFound}
isFromMe={quote.isFromMe}
referencedMessageNotFound={quote.referencedMessageNotFound || false}
isFromMe={quote.isFromMe || false}
/>
);
};

View File

@ -1,7 +1,6 @@
import React from 'react';
import { Provider } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ConversationModel } from '../../models/conversation';
import { getConversationController } from '../../session/conversations';
import { UserUtils } from '../../session/utils';
import { createStore } from '../../state/createStore';
@ -43,8 +42,10 @@ export class SessionInboxView extends React.Component<any, State> {
this.state = {
isInitialLoadComplete: false,
};
}
void this.setupLeftPane();
public componentDidMount() {
this.setupLeftPane();
}
public render() {
@ -72,18 +73,11 @@ export class SessionInboxView extends React.Component<any, State> {
return <LeftPane />;
}
private async setupLeftPane() {
private setupLeftPane() {
// Here we set up a full redux store with initial state for our LeftPane Root
const convoCollection = getConversationController().getConversations();
const conversations = convoCollection.map((conversation: ConversationModel) =>
conversation.getConversationModelProps()
);
const filledConversations = conversations.map((conv: any) => {
return { ...conv, messages: [] };
});
const fullFilledConversations = await Promise.all(filledConversations);
const conversations = getConversationController()
.getConversations()
.map(conversation => conversation.getConversationModelProps());
const timerOptions: TimerOptionsArray = window.Whisper.ExpirationTimerOptions.map(
(item: any) => ({
@ -95,7 +89,7 @@ export class SessionInboxView extends React.Component<any, State> {
const initialState: StateType = {
conversations: {
...getEmptyConversationState(),
conversationLookup: makeLookup(fullFilledConversations, 'id'),
conversationLookup: makeLookup(conversations, 'id'),
},
user: {
ourNumber: UserUtils.getOurPubKeyStrFromCache(),

View File

@ -140,10 +140,10 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
ref={this.props.messageContainerRef}
>
<TypingBubble
phoneNumber={conversationKey}
pubkey={conversationKey}
conversationType={conversation.type}
displayedName={displayedName}
isTyping={conversation.isTyping}
isTyping={!!conversation.isTyping}
key="typing-bubble"
/>
@ -170,7 +170,11 @@ class SessionMessagesListContainerInner extends React.Component<Props> {
return;
}
if (conversation.unreadCount <= 0 || firstUnreadOnOpen === undefined) {
if (
conversation.unreadCount ||
(conversation.unreadCount && conversation.unreadCount <= 0) ||
firstUnreadOnOpen === undefined
) {
this.scrollToBottom();
} else {
// just assume that this need to be shown by default

View File

@ -121,14 +121,13 @@ const HeaderItem = () => {
isGroup,
isKickedFromGroup,
profileName,
phoneNumber,
isBlocked,
left,
name,
} = selectedConversation;
const showInviteContacts = isGroup && !isKickedFromGroup && !isBlocked && !left;
const userName = name || profileName || phoneNumber;
const userName = name || profileName || id;
return (
<div className="group-settings-header">

View File

@ -19,7 +19,6 @@ import {
} from './Menu';
import _ from 'lodash';
import { ConversationNotificationSettingType } from '../../../models/conversation';
import { NotificationForConvoOption } from '../../../state/ducks/conversations';
export type PropsConversationHeaderMenu = {
conversationId: string;
@ -30,7 +29,6 @@ export type PropsConversationHeaderMenu = {
left: boolean;
isGroup: boolean;
weAreAdmin: boolean;
notificationForConvo: Array<NotificationForConvoOption>;
currentNotificationSetting: ConversationNotificationSettingType;
isPrivate: boolean;
isBlocked: boolean;
@ -50,21 +48,20 @@ const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
isPrivate,
left,
hasNickname,
notificationForConvo,
currentNotificationSetting,
} = props;
return (
<Menu id={triggerId} animation={animation.fade}>
{getDisappearingMenuItem(isPublic, isKickedFromGroup, left, isBlocked, conversationId)}
{getNotificationForConvoMenuItem(
{getNotificationForConvoMenuItem({
isKickedFromGroup,
left,
isBlocked,
notificationForConvo,
isPrivate,
currentNotificationSetting,
conversationId
)}
conversationId,
})}
{getPinConversationMenuItem(conversationId)}
{getBlockMenuItem(isMe, isPrivate, isBlocked, conversationId)}
{getCopyMenuItem(isPublic, isGroup, conversationId)}

View File

@ -5,7 +5,6 @@ import {
ConversationNotificationSettingType,
ConversationTypeEnum,
} from '../../../models/conversation';
import { NotificationForConvoOption } from '../../../state/ducks/conversations';
import {
getBlockMenuItem,
@ -27,12 +26,12 @@ export type PropsContextConversationItem = {
type: ConversationTypeEnum;
isMe: boolean;
isPublic: boolean;
isPrivate: boolean;
isBlocked: boolean;
hasNickname: boolean;
isKickedFromGroup: boolean;
left: boolean;
theme?: any;
notificationForConvo: Array<NotificationForConvoOption>;
currentNotificationSetting: ConversationNotificationSettingType;
};
@ -47,21 +46,21 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
type,
left,
isKickedFromGroup,
notificationForConvo,
currentNotificationSetting,
isPrivate,
} = props;
const isGroup = type === 'group';
return (
<Menu id={triggerId} animation={animation.fade}>
{getNotificationForConvoMenuItem(
{getNotificationForConvoMenuItem({
isPrivate,
isKickedFromGroup,
left,
isBlocked,
notificationForConvo,
currentNotificationSetting,
conversationId
)}
conversationId,
})}
{getPinConversationMenuItem(conversationId)}
{getBlockMenuItem(isMe, type === ConversationTypeEnum.PRIVATE, isBlocked, conversationId)}
{getCopyMenuItem(isPublic, isGroup, conversationId)}

View File

@ -3,7 +3,10 @@ import React from 'react';
import { getNumberOfPinnedConversations } from '../../../state/selectors/conversations';
import { getFocusedSection } from '../../../state/selectors/section';
import { Item, Submenu } from 'react-contexify';
import { ConversationNotificationSettingType } from '../../../models/conversation';
import {
ConversationNotificationSetting,
ConversationNotificationSettingType,
} from '../../../models/conversation';
import { useDispatch, useSelector } from 'react-redux';
import { changeNickNameModal, updateConfirmModal } from '../../../state/ducks/modalDialog';
import { SectionType } from '../../../state/ducks/section';
@ -26,7 +29,6 @@ import {
import { SessionButtonColor } from '../SessionButton';
import { getTimerOptions } from '../../../state/selectors/timerOptions';
import { ToastUtils } from '../../../session/utils';
import { NotificationForConvoOption } from '../../../state/ducks/conversations';
const maxNumberOfPinnedConversations = 5;
@ -357,17 +359,32 @@ export function getDisappearingMenuItem(
return null;
}
export function getNotificationForConvoMenuItem(
isKickedFromGroup: boolean | undefined,
left: boolean | undefined,
isBlocked: boolean | undefined,
notificationForConvoOptions: Array<NotificationForConvoOption>,
currentNotificationSetting: ConversationNotificationSettingType,
conversationId: string
): JSX.Element | null {
export function getNotificationForConvoMenuItem({
conversationId,
currentNotificationSetting,
isBlocked,
isKickedFromGroup,
left,
isPrivate,
}: {
isKickedFromGroup: boolean | undefined;
left: boolean | undefined;
isBlocked: boolean | undefined;
isPrivate: boolean | undefined;
currentNotificationSetting: ConversationNotificationSettingType;
conversationId: string;
}): JSX.Element | null {
if (showNotificationConvo(Boolean(isKickedFromGroup), Boolean(left), Boolean(isBlocked))) {
// const isRtlMode = isRtlBody();'
// exclude mentions_only settings for private chats as this does not make much sense
const notificationForConvoOptions = ConversationNotificationSetting.filter(n =>
isPrivate ? n !== 'mentions_only' : true
).map((n: ConversationNotificationSettingType) => {
// this link to the notificationForConvo_all, notificationForConvo_mentions_only, ...
return { value: n, name: window.i18n(`notificationForConvo_${n}`) };
});
return (
// Remove the && false to make context menu work with RTL support
<Submenu

View File

@ -36,10 +36,10 @@ export function usingClosedConversationDetails(WrappedComponent: any) {
}
private fetchClosedConversationDetails() {
const { isPublic, type, conversationType, isGroup, phoneNumber, id } = this.props;
const { isPublic, type, conversationType, isGroup, id } = this.props;
if (!isPublic && (conversationType === 'group' || type === 'group' || isGroup)) {
const groupId = id || phoneNumber;
const groupId = id;
const ourPrimary = UserUtils.getOurPubKeyFromCache();
let members = GroupUtils.getGroupMembers(PubKey.cast(groupId));

View File

@ -24,9 +24,9 @@ export function useMembersAvatars(conversation: ReduxConversationType | undefine
if (!isPublic && isGroup) {
const ourPrimary = UserUtils.getOurPubKeyStrFromCache();
const ourself = convoMembers.find(m => m !== ourPrimary);
const ourself = convoMembers?.find(m => m !== ourPrimary) || undefined;
// add ourself back at the back, so it's shown only if only 1 member and we are still a member
let membersFiltered = convoMembers.filter(m => m !== ourPrimary);
let membersFiltered = convoMembers?.filter(m => m !== ourPrimary) || [];
membersFiltered.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
if (ourself) {
membersFiltered.push(ourPrimary);

View File

@ -25,7 +25,6 @@ import {
conversationChanged,
LastMessageStatusType,
MessageModelPropsWithoutConvoProps,
NotificationForConvoOption,
ReduxConversationType,
} from '../state/ducks/conversations';
import { ExpirationTimerUpdateMessage } from '../session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage';
@ -413,57 +412,136 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return this.get('moderators');
}
// tslint:disable-next-line: cyclomatic-complexity
public getConversationModelProps(): ReduxConversationType {
const groupAdmins = this.getGroupAdmins();
const members = this.isGroup() && !this.isPublic() ? this.get('members') : [];
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
const isPublic = this.isPublic();
// isSelected is overriden by redux
return {
isSelected: false,
const members = this.isGroup() && !isPublic ? this.get('members') : [];
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
const avatarPath = this.getAvatarPath();
const isPrivate = this.isPrivate();
const isGroup = !isPrivate;
const weAreAdmin = this.isAdmin(ourNumber);
const isMe = this.isMe();
const isTyping = !!this.typingTimer;
const name = this.getName();
const profileName = this.getProfileName();
const unreadCount = this.get('unreadCount') || undefined;
const mentionedUs = this.get('mentionedUs') || undefined;
const isBlocked = this.isBlocked();
const subscriberCount = this.get('subscriberCount');
const isPinned = this.isPinned();
const hasNickname = !!this.getNickname();
const isKickedFromGroup = !!this.get('isKickedFromGroup');
const left = !!this.get('left');
const expireTimer = this.get('expireTimer');
const currentNotificationSetting = this.get('triggerNotificationsFor');
// to reduce the redux store size, only set fields which cannot be undefined
// for instance, a boolean can usually be not set if false, etc
const toRet: ReduxConversationType = {
id: this.id as string,
activeAt: this.get('active_at'),
avatarPath: this.getAvatarPath() || null,
type: this.isPrivate() ? ConversationTypeEnum.PRIVATE : ConversationTypeEnum.GROUP,
weAreAdmin: this.isAdmin(ourNumber),
isGroup: !this.isPrivate(),
isPrivate: this.isPrivate(),
isMe: this.isMe(),
isPublic: this.isPublic(),
isTyping: !!this.typingTimer,
name: this.getName(),
profileName: this.getProfileName(),
// title: this.getTitle(),
unreadCount: this.get('unreadCount') || 0,
mentionedUs: this.get('mentionedUs') || false,
isBlocked: this.isBlocked(),
phoneNumber: this.getNumber(),
lastMessage: {
status: this.get('lastMessageStatus'),
text: this.get('lastMessage'),
},
hasNickname: !!this.getNickname(),
isKickedFromGroup: !!this.get('isKickedFromGroup'),
left: !!this.get('left'),
groupAdmins,
members,
expireTimer: this.get('expireTimer') || 0,
subscriberCount: this.get('subscriberCount') || 0,
isPinned: this.isPinned(),
notificationForConvo: this.getConversationNotificationSettingType(),
currentNotificationSetting: this.get('triggerNotificationsFor'),
type: isPrivate ? ConversationTypeEnum.PRIVATE : ConversationTypeEnum.GROUP,
};
}
public getConversationNotificationSettingType(): Array<NotificationForConvoOption> {
// exclude mentions_only settings for private chats as this does not make much sense
return ConversationNotificationSetting.filter(n =>
this.isPrivate() ? n !== 'mentions_only' : true
).map((n: ConversationNotificationSettingType) => {
// this link to the notificationForConvo_all, notificationForConvo_mentions_only, ...
return { value: n, name: window.i18n(`notificationForConvo_${n}`) };
});
if (isPrivate) {
toRet.isPrivate = true;
}
if (isGroup) {
toRet.isGroup = true;
}
if (weAreAdmin) {
toRet.weAreAdmin = true;
}
if (isMe) {
toRet.isMe = true;
}
if (isPublic) {
toRet.isPublic = true;
}
if (isTyping) {
toRet.isTyping = true;
}
if (isTyping) {
toRet.isTyping = true;
}
if (avatarPath) {
toRet.avatarPath = avatarPath;
}
if (name) {
toRet.name = name;
}
if (profileName) {
toRet.profileName = profileName;
}
if (unreadCount) {
toRet.unreadCount = unreadCount;
}
if (mentionedUs) {
toRet.mentionedUs = mentionedUs;
}
if (isBlocked) {
toRet.isBlocked = isBlocked;
}
if (hasNickname) {
toRet.hasNickname = hasNickname;
}
if (isKickedFromGroup) {
toRet.isKickedFromGroup = isKickedFromGroup;
}
if (left) {
toRet.left = left;
}
if (isPinned) {
toRet.isPinned = isPinned;
}
if (subscriberCount) {
toRet.subscriberCount = subscriberCount;
}
if (groupAdmins && groupAdmins.length) {
toRet.groupAdmins = groupAdmins;
}
if (members && members.length) {
toRet.members = members;
}
if (members && members.length) {
toRet.members = members;
}
if (expireTimer) {
toRet.expireTimer = expireTimer;
}
if (
currentNotificationSetting &&
currentNotificationSetting !== ConversationNotificationSetting[0]
) {
toRet.currentNotificationSetting = currentNotificationSetting;
}
const lastMessageText = this.get('lastMessage');
if (lastMessageText && lastMessageText.length) {
const lastMessageStatus = this.get('lastMessageStatus');
toRet.lastMessage = {
status: lastMessageStatus,
text: lastMessageText,
};
}
return toRet;
}
public async updateGroupAdmins(groupAdmins: Array<string>) {
@ -921,7 +999,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
id: this.id,
data: {
...this.getConversationModelProps(),
isSelected: false,
},
})
);

View File

@ -84,13 +84,25 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
public getMessageModelProps(): MessageModelPropsWithoutConvoProps {
perfStart(`getPropsMessage-${this.id}`);
const propsForDataExtractionNotification = this.getPropsForDataExtractionNotification();
const propsForGroupInvitation = this.getPropsForGroupInvitation();
const propsForGroupNotification = this.getPropsForGroupNotification();
const propsForTimerNotification = this.getPropsForTimerNotification();
const messageProps: MessageModelPropsWithoutConvoProps = {
propsForMessage: this.getPropsForMessage(),
propsForDataExtractionNotification: this.getPropsForDataExtractionNotification(),
propsForGroupInvitation: this.getPropsForGroupInvitation(),
propsForGroupNotification: this.getPropsForGroupNotification(),
propsForTimerNotification: this.getPropsForTimerNotification(),
};
if (propsForDataExtractionNotification) {
messageProps.propsForDataExtractionNotification = propsForDataExtractionNotification;
}
if (propsForGroupInvitation) {
messageProps.propsForGroupInvitation = propsForGroupInvitation;
}
if (propsForGroupNotification) {
messageProps.propsForGroupNotification = propsForGroupNotification;
}
if (propsForTimerNotification) {
messageProps.propsForTimerNotification = propsForTimerNotification;
}
perfEnd(`getPropsMessage-${this.id}`, 'getPropsMessage');
return messageProps;
}
@ -365,8 +377,8 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
}
return {
phoneNumber: pubkey as string,
avatarPath: (contactModel ? contactModel.getAvatarPath() : null) as string | null,
pubkey: pubkey,
avatarPath: contactModel ? contactModel.getAvatarPath() : null,
name: (contactModel ? contactModel.getName() : null) as string | null,
profileName: profileName as string | null,
title: (contactModel ? contactModel.getTitle() : null) as string | null,
@ -393,7 +405,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
type: 'add',
contacts: _.map(
Array.isArray(groupUpdate.joined) ? groupUpdate.joined : [groupUpdate.joined],
phoneNumber => this.findAndFormatContact(phoneNumber)
pubkey => this.findAndFormatContact(pubkey)
),
};
changes.push(change);
@ -411,7 +423,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
isMe: false,
contacts: _.map(
Array.isArray(groupUpdate.kicked) ? groupUpdate.kicked : [groupUpdate.kicked],
phoneNumber => this.findAndFormatContact(phoneNumber)
pubkey => this.findAndFormatContact(pubkey)
),
};
changes.push(change);
@ -443,7 +455,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
isMe: false,
contacts: _.map(
Array.isArray(groupUpdate.left) ? groupUpdate.left : [groupUpdate.left],
phoneNumber => this.findAndFormatContact(phoneNumber)
pubkey => this.findAndFormatContact(pubkey)
),
};
changes.push(change);
@ -473,11 +485,11 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
// Only return the status on outgoing messages
if (!this.isOutgoing()) {
return null;
return undefined;
}
if (this.isDataExtractionNotification()) {
return null;
return undefined;
}
const readBy = this.get('read_by') || [];
@ -493,6 +505,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return 'sending';
}
// tslint:disable-next-line: cyclomatic-complexity
public getPropsForMessage(options: any = {}): PropsForMessageWithoutConvoProps {
const sender = this.getSource();
const expirationLength = this.get('expireTimer') * 1000;
@ -502,38 +515,67 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const attachments = this.get('attachments') || [];
const isTrustedForAttachmentDownload = this.isTrustedForAttachmentDownload();
const body = this.get('body');
const props: PropsForMessageWithoutConvoProps = {
text: this.createNonBreakingLastSeparator(this.get('body') || null),
id: this.id as string,
id: this.id,
direction: (this.isIncoming() ? 'incoming' : 'outgoing') as MessageModelType,
timestamp: this.get('sent_at') || 0,
receivedAt: this.get('received_at'),
serverTimestamp: this.get('serverTimestamp'),
serverId: this.get('serverId'),
status: this.getMessagePropStatus(),
authorPhoneNumber: sender,
convoId: this.get('conversationId'),
attachments: attachments
.filter((attachment: any) => !attachment.error)
.map((attachment: any) => this.getPropsForAttachment(attachment)),
previews: this.getPropsForPreview(),
quote: this.getPropsForQuote(options),
isUnread: this.isUnread(),
expirationLength,
expirationTimestamp,
isExpired: this.isExpired(),
isTrustedForAttachmentDownload,
};
if (body) {
props.text = this.createNonBreakingLastSeparator(body);
}
if (this.get('received_at')) {
props.receivedAt = this.get('received_at');
}
if (this.get('serverTimestamp')) {
props.serverTimestamp = this.get('serverTimestamp');
}
if (this.get('serverId')) {
props.serverId = this.get('serverId');
}
if (expirationLength) {
props.expirationLength = expirationLength;
}
if (expirationTimestamp) {
props.expirationTimestamp = expirationTimestamp;
}
if (isTrustedForAttachmentDownload) {
props.isTrustedForAttachmentDownload = isTrustedForAttachmentDownload;
}
const isUnread = this.isUnread();
if (isUnread) {
props.isUnread = isUnread;
}
const isExpired = this.isExpired();
if (isExpired) {
props.isExpired = isExpired;
}
const previews = this.getPropsForPreview();
if (previews && previews.length) {
props.previews = previews;
}
const quote = this.getPropsForQuote(options);
if (quote) {
props.quote = quote;
}
const status = this.getMessagePropStatus();
if (status) {
props.status = status;
}
const attachmentsProps = attachments
.filter((attachment: any) => !attachment.error)
.map((attachment: any) => this.getPropsForAttachment(attachment));
if (attachmentsProps && attachmentsProps.length) {
props.attachments = attachmentsProps;
}
return props;
}
public createNonBreakingLastSeparator(text: string | null) {
if (!text) {
return null;
}
public createNonBreakingLastSeparator(text: string) {
const nbsp = '\xa0';
const regex = /(\S)( +)(\S+\s*)$/;
return text.replace(regex, (_match, start, spaces, end) => {
@ -566,8 +608,12 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
// tslint:enable: prefer-object-spread
}
public getPropsForPreview() {
const previews = this.get('preview') || [];
public getPropsForPreview(): Array<any> | null {
const previews = this.get('preview') || null;
if (!previews || previews.length === 0) {
return null;
}
return previews.map((preview: any) => {
let image: PropsForAttachment | null = null;
@ -602,16 +648,44 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const isFromMe = contact ? contact.id === UserUtils.getOurPubKeyStrFromCache() : false;
const firstAttachment = quote.attachments && quote.attachments[0];
return {
text: this.createNonBreakingLastSeparator(quote.text),
attachment: firstAttachment ? this.processQuoteAttachment(firstAttachment) : null,
isFromMe,
const quoteProps: {
referencedMessageNotFound?: boolean;
authorPhoneNumber: string;
messageId: string;
authorName: string;
text?: string;
attachment?: any;
isFromMe?: boolean;
} = {
authorPhoneNumber: author,
messageId: id,
authorName,
referencedMessageNotFound,
};
if (referencedMessageNotFound) {
quoteProps.referencedMessageNotFound = true;
}
if (!referencedMessageNotFound) {
if (quote.text) {
// do not show text of not found messages.
// if the message was deleted better not show it's text content in the message
quoteProps.text = this.createNonBreakingLastSeparator(quote.text);
}
const quoteAttachment = firstAttachment
? this.processQuoteAttachment(firstAttachment)
: undefined;
if (quoteAttachment) {
// only set attachment if referencedMessageNotFound is false and we have one
quoteProps.attachment = quoteAttachment;
}
}
if (isFromMe) {
quoteProps.isFromMe = true;
}
return quoteProps;
}
public getPropsForAttachment(attachment: AttachmentTypeWithPath): PropsForAttachment | null {
@ -708,7 +782,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
// first; otherwise it's alphabetical
const sortedContacts = _.sortBy(
finalContacts,
contact => `${contact.isPrimaryDevice ? '0' : '1'}${contact.phoneNumber}`
contact => `${contact.isPrimaryDevice ? '0' : '1'}${contact.pubkey}`
);
const toRet: MessagePropsDetails = {
sentAt: this.get('sent_at') || 0,

View File

@ -354,13 +354,12 @@ async function handleExpirationTimerUpdate(
source: string,
expireTimer: number
) {
// TODO: if the message is an expiration timer update, it
// shouldn't be responsible for anything else!!!
message.set({
expirationTimerUpdate: {
source,
expireTimer,
},
unread: 0, // mark the message as read.
});
conversation.set({ expireTimer });

View File

@ -1,6 +1,7 @@
{
"extends": ["../../tslint.json"],
"rules": {
"no-unused-variable": false
"no-unused-variable": false,
"use-simple-attributes": false
}
}

View File

@ -19,10 +19,10 @@ import { omit } from 'lodash';
export type MessageModelPropsWithoutConvoProps = {
propsForMessage: PropsForMessageWithoutConvoProps;
propsForGroupInvitation: PropsForGroupInvitation | null;
propsForTimerNotification: PropsForExpirationTimer | null;
propsForDataExtractionNotification: PropsForDataExtractionNotification | null;
propsForGroupNotification: PropsForGroupUpdate | null;
propsForGroupInvitation?: PropsForGroupInvitation;
propsForTimerNotification?: PropsForExpirationTimer;
propsForDataExtractionNotification?: PropsForDataExtractionNotification;
propsForGroupNotification?: PropsForGroupUpdate;
};
export type MessageModelPropsWithConvoProps = SortedMessageModelProps & {
@ -30,8 +30,8 @@ export type MessageModelPropsWithConvoProps = SortedMessageModelProps & {
};
export type ContactPropsMessageDetail = {
status: string | null;
phoneNumber: string;
status: string | undefined;
pubkey: string;
name?: string | null;
profileName?: string | null;
avatarPath?: string | null;
@ -50,10 +50,10 @@ export type MessagePropsDetails = {
direction: MessageModelType;
};
export type LastMessageStatusType = MessageDeliveryStatus | null;
export type LastMessageStatusType = MessageDeliveryStatus | undefined;
export type FindAndFormatContactType = {
phoneNumber: string;
pubkey: string;
avatarPath: string | null;
name: string | null;
profileName: string | null;
@ -64,7 +64,7 @@ export type FindAndFormatContactType = {
export type PropsForExpirationTimer = {
timespan: string;
disabled: boolean;
phoneNumber: string;
pubkey: string;
avatarPath: string | null;
name: string | null;
profileName: string | null;
@ -157,33 +157,34 @@ export type PropsForAttachment = {
};
export type PropsForMessageWithoutConvoProps = {
text: string | null;
id: string; // messageId
direction: MessageModelType;
timestamp: number;
receivedAt: number | undefined;
serverTimestamp: number | undefined;
serverId: number | undefined;
status: LastMessageStatusType | null;
authorPhoneNumber: string; // this is the sender
convoId: string; // this is the conversation in which this message was sent
attachments: Array<PropsForAttachment>;
previews: Array<any>;
text?: string;
receivedAt?: number;
serverTimestamp?: number;
serverId?: number;
status?: LastMessageStatusType;
attachments?: Array<PropsForAttachment>;
previews?: Array<any>;
quote?: {
text: string | null;
text?: string;
attachment?: QuotedAttachmentType;
isFromMe: boolean;
isFromMe?: boolean;
authorPhoneNumber: string;
authorProfileName?: string;
authorName?: string;
messageId?: string;
referencedMessageNotFound: boolean;
referencedMessageNotFound?: boolean;
} | null;
isUnread: boolean;
expirationLength: number;
expirationTimestamp: number | null;
isExpired: boolean;
isTrustedForAttachmentDownload: boolean;
isUnread?: boolean;
expirationLength?: number;
expirationTimestamp?: number | null;
isExpired?: boolean;
isTrustedForAttachmentDownload?: boolean;
};
export type PropsForMessageWithConvoProps = PropsForMessageWithoutConvoProps & {
@ -209,35 +210,36 @@ export interface ReduxConversationType {
id: string;
name?: string;
profileName?: string;
hasNickname: boolean;
hasNickname?: boolean;
activeAt?: number;
lastMessage?: LastMessageType;
phoneNumber: string;
type: ConversationTypeEnum;
isMe: boolean;
isPublic: boolean;
isGroup: boolean;
isPrivate: boolean;
weAreAdmin: boolean;
unreadCount: number;
mentionedUs: boolean;
isSelected: boolean;
expireTimer: number;
isMe?: boolean;
isPublic?: boolean;
isGroup?: boolean;
isPrivate?: boolean;
weAreAdmin?: boolean;
unreadCount?: number;
mentionedUs?: boolean;
isSelected?: boolean;
expireTimer?: number;
isTyping: boolean;
isBlocked: boolean;
isKickedFromGroup: boolean;
subscriberCount: number;
left: boolean;
avatarPath: string | null; // absolute filepath to the avatar
isTyping?: boolean;
isBlocked?: boolean;
isKickedFromGroup?: boolean;
subscriberCount?: number;
left?: boolean;
avatarPath?: string | null; // absolute filepath to the avatar
groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
members: Array<string>; // members for closed groups only
members?: Array<string>; // members for closed groups only
currentNotificationSetting: ConversationNotificationSettingType;
notificationForConvo: Array<NotificationForConvoOption>;
/**
* If this is undefined, it means all notification are enabled
*/
currentNotificationSetting?: ConversationNotificationSettingType;
isPinned: boolean;
isPinned?: boolean;
}
export interface NotificationForConvoOption {

View File

@ -14,7 +14,7 @@ import {
import { getIntl, getOurNumber } from './user';
import { BlockedNumberController } from '../../util';
import { ConversationTypeEnum } from '../../models/conversation';
import { ConversationNotificationSetting, ConversationTypeEnum } from '../../models/conversation';
import { LocalizerType } from '../../types/Util';
import {
ConversationHeaderProps,
@ -319,6 +319,7 @@ export const _getLeftPaneLists = (
if (
unreadCount < 9 &&
conversation.unreadCount &&
conversation.unreadCount > 0 &&
conversation.currentNotificationSetting !== 'disabled'
) {
@ -369,11 +370,11 @@ export const getConversationHeaderTitleProps = createSelector(getSelectedConvers
return undefined;
}
return {
isKickedFromGroup: state.isKickedFromGroup,
phoneNumber: state.phoneNumber,
isMe: state.isMe,
isKickedFromGroup: !!state.isKickedFromGroup,
conversationKey: state.id,
isMe: !!state.isMe,
members: state.members || [],
isPublic: state.isPublic,
isPublic: !!state.isPublic,
profileName: state.profileName,
name: state.name,
subscriberCount: state.subscriberCount,
@ -415,25 +416,24 @@ export const getConversationHeaderProps = createSelector(getSelectedConversation
: null;
return {
id: state.id,
isPrivate: state.isPrivate,
notificationForConvo: state.notificationForConvo,
currentNotificationSetting: state.currentNotificationSetting,
isBlocked: state.isBlocked,
left: state.left,
avatarPath: state.avatarPath,
conversationKey: state.id,
isPrivate: !!state.isPrivate,
currentNotificationSetting:
state.currentNotificationSetting || ConversationNotificationSetting[0], // if undefined, it is 'all'
isBlocked: !!state.isBlocked,
left: !!state.left,
avatarPath: state.avatarPath || null,
expirationSettingName: expirationSettingName,
hasNickname: state.hasNickname,
weAreAdmin: state.weAreAdmin,
isKickedFromGroup: state.isKickedFromGroup,
phoneNumber: state.phoneNumber,
isMe: state.isMe,
hasNickname: !!state.hasNickname,
weAreAdmin: !!state.weAreAdmin,
isKickedFromGroup: !!state.isKickedFromGroup,
isMe: !!state.isMe,
members: state.members || [],
isPublic: state.isPublic,
isPublic: !!state.isPublic,
profileName: state.profileName,
name: state.name,
subscriberCount: state.subscriberCount,
isGroup: state.isGroup,
isGroup: !!state.isGroup,
};
});
@ -657,16 +657,16 @@ export const getMessagePropsByMessageId = createSelector(
...foundMessageProps,
propsForMessage: {
...foundMessageProps.propsForMessage,
isBlocked: foundMessageConversation.isBlocked,
isPublic,
isOpenGroupV2: isPublic,
isBlocked: !!foundMessageConversation.isBlocked,
isPublic: !!isPublic,
isOpenGroupV2: !!isPublic,
isSenderAdmin,
isDeletable,
weAreAdmin,
conversationType: foundMessageConversation.type,
authorPhoneNumber,
authorAvatarPath: foundSenderConversation.avatarPath,
isKickedFromGroup: foundMessageConversation.isKickedFromGroup,
authorAvatarPath: foundSenderConversation.avatarPath || null,
isKickedFromGroup: foundMessageConversation.isKickedFromGroup || false,
authorProfileName,
authorName,
},
@ -875,7 +875,7 @@ export const getMessageAttachmentProps = createSelector(getMessagePropsByMessage
convoId,
} = props.propsForMessage;
const msgProps: MessageAttachmentSelectorProps = {
attachments,
attachments: attachments || [],
direction,
isTrustedForAttachmentDownload,
timestamp,

View File

@ -17,8 +17,6 @@ describe('state/selectors/conversations', () => {
id: 'id1',
activeAt: 0,
name: 'No timestamp',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
unreadCount: 1,
@ -35,7 +33,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -48,8 +46,6 @@ describe('state/selectors/conversations', () => {
id: 'id2',
activeAt: 20,
name: 'B',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
unreadCount: 1,
@ -66,7 +62,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -79,7 +75,6 @@ describe('state/selectors/conversations', () => {
id: 'id3',
activeAt: 20,
name: 'C',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
unreadCount: 1,
@ -96,7 +91,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -109,7 +104,6 @@ describe('state/selectors/conversations', () => {
id: 'id4',
activeAt: 20,
name: 'Á',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
unreadCount: 1,
@ -126,7 +120,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
expireTimer: 0,
@ -139,7 +133,6 @@ describe('state/selectors/conversations', () => {
id: 'id5',
activeAt: 30,
name: 'First!',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
unreadCount: 1,
@ -157,7 +150,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -185,7 +178,6 @@ describe('state/selectors/conversations', () => {
id: 'id1',
activeAt: 0,
name: 'No timestamp',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
@ -202,7 +194,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -216,7 +208,6 @@ describe('state/selectors/conversations', () => {
id: 'id2',
activeAt: 20,
name: 'B',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
@ -233,7 +224,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -247,7 +238,6 @@ describe('state/selectors/conversations', () => {
id: 'id3',
activeAt: 20,
name: 'C',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
@ -264,7 +254,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -278,7 +268,6 @@ describe('state/selectors/conversations', () => {
id: 'id4',
activeAt: 20,
name: 'Á',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
unreadCount: 1,
@ -294,7 +283,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,
@ -308,7 +297,6 @@ describe('state/selectors/conversations', () => {
id: 'id5',
activeAt: 30,
name: 'First!',
phoneNumber: 'notused',
type: ConversationTypeEnum.PRIVATE,
isMe: false,
unreadCount: 1,
@ -325,7 +313,7 @@ describe('state/selectors/conversations', () => {
weAreAdmin: false,
isGroup: false,
isPrivate: false,
notificationForConvo: [{ value: 'all', name: 'all' }],
avatarPath: '',
groupAdmins: [],
lastMessage: undefined,

View File

@ -89,7 +89,7 @@ export class MockConversation {
isKickedFromGroup: false,
active_at: Date.now(),
lastJoinedTimestamp: Date.now(),
lastMessageStatus: null,
lastMessageStatus: undefined,
lastMessage: null,
zombies: [],
triggerNotificationsFor: 'all',

View File

@ -3,6 +3,7 @@
"extends": ["tslint:recommended", "tslint-react", "tslint-microsoft-contrib"],
"jsRules": {},
"rules": {
"use-simple-attributes": false,
"align": false,
"newline-per-chained-call": false,
"array-type": [true, "generic"],