2021-09-07 19:55:03 +00:00
|
|
|
// Copyright 2018-2021 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2021-09-07 19:55:03 +00:00
|
|
|
import React, { ReactNode } from 'react';
|
2018-07-09 21:29:13 +00:00
|
|
|
import { compact, flatten } from 'lodash';
|
|
|
|
|
|
|
|
import { ContactName } from './ContactName';
|
2021-09-07 19:55:03 +00:00
|
|
|
import { SystemMessage } from './SystemMessage';
|
2020-06-11 17:32:21 +00:00
|
|
|
import { Intl } from '../Intl';
|
2019-01-14 21:49:58 +00:00
|
|
|
import { LocalizerType } from '../../types/Util';
|
2018-07-09 21:29:13 +00:00
|
|
|
|
|
|
|
import { missingCaseError } from '../../util/missingCaseError';
|
|
|
|
|
2021-01-14 18:07:05 +00:00
|
|
|
type Contact = {
|
2020-07-24 01:35:32 +00:00
|
|
|
phoneNumber?: string;
|
2018-07-09 21:29:13 +00:00
|
|
|
profileName?: string;
|
|
|
|
name?: string;
|
2020-07-24 01:35:32 +00:00
|
|
|
title: string;
|
2020-03-26 21:47:35 +00:00
|
|
|
isMe?: boolean;
|
2021-01-14 18:07:05 +00:00
|
|
|
};
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2020-09-24 20:57:54 +00:00
|
|
|
export type ChangeType = 'add' | 'remove' | 'name' | 'avatar' | 'general';
|
|
|
|
|
2021-01-14 18:07:05 +00:00
|
|
|
type Change = {
|
2020-09-24 20:57:54 +00:00
|
|
|
type: ChangeType;
|
2018-07-09 21:29:13 +00:00
|
|
|
newName?: string;
|
|
|
|
contacts?: Array<Contact>;
|
2021-01-14 18:07:05 +00:00
|
|
|
};
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2019-03-20 17:42:28 +00:00
|
|
|
export type PropsData = {
|
2020-03-26 21:47:35 +00:00
|
|
|
from: Contact;
|
2018-07-09 21:29:13 +00:00
|
|
|
changes: Array<Change>;
|
2019-03-20 17:42:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
type PropsHousekeeping = {
|
2019-01-14 21:49:58 +00:00
|
|
|
i18n: LocalizerType;
|
2019-03-20 17:42:28 +00:00
|
|
|
};
|
|
|
|
|
2020-03-26 21:47:35 +00:00
|
|
|
export type Props = PropsData & PropsHousekeeping;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
|
|
|
export class GroupNotification extends React.Component<Props> {
|
2020-09-14 19:51:27 +00:00
|
|
|
public renderChange(
|
|
|
|
change: Change,
|
|
|
|
from: Contact
|
|
|
|
): JSX.Element | string | null | undefined {
|
2020-03-26 21:47:35 +00:00
|
|
|
const { contacts, type, newName } = change;
|
2018-07-09 21:29:13 +00:00
|
|
|
const { i18n } = this.props;
|
|
|
|
|
2020-06-11 17:32:21 +00:00
|
|
|
const otherPeople: Array<JSX.Element> = compact(
|
2020-03-26 21:47:35 +00:00
|
|
|
(contacts || []).map(contact => {
|
|
|
|
if (contact.isMe) {
|
|
|
|
return null;
|
|
|
|
}
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2020-03-26 21:47:35 +00:00
|
|
|
return (
|
|
|
|
<span
|
|
|
|
key={`external-${contact.phoneNumber}`}
|
|
|
|
className="module-group-notification__contact"
|
|
|
|
>
|
|
|
|
<ContactName
|
2020-07-24 01:35:32 +00:00
|
|
|
title={contact.title}
|
2020-03-26 21:47:35 +00:00
|
|
|
phoneNumber={contact.phoneNumber}
|
|
|
|
profileName={contact.profileName}
|
|
|
|
name={contact.name}
|
2020-07-24 01:35:32 +00:00
|
|
|
i18n={i18n}
|
2020-03-26 21:47:35 +00:00
|
|
|
/>
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
})
|
|
|
|
);
|
2020-06-11 17:32:21 +00:00
|
|
|
const otherPeopleWithCommas: Array<JSX.Element | string> = compact(
|
2020-03-26 21:47:35 +00:00
|
|
|
flatten(
|
|
|
|
otherPeople.map((person, index) => [index > 0 ? ', ' : null, person])
|
2018-07-09 21:29:13 +00:00
|
|
|
)
|
|
|
|
);
|
2020-03-26 21:47:35 +00:00
|
|
|
const contactsIncludesMe = (contacts || []).length !== otherPeople.length;
|
2018-07-09 21:29:13 +00:00
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'name':
|
2020-03-26 21:47:35 +00:00
|
|
|
return (
|
|
|
|
<Intl i18n={i18n} id="titleIsNow" components={[newName || '']} />
|
|
|
|
);
|
|
|
|
case 'avatar':
|
|
|
|
return <Intl i18n={i18n} id="updatedGroupAvatar" />;
|
2018-07-09 21:29:13 +00:00
|
|
|
case 'add':
|
|
|
|
if (!contacts || !contacts.length) {
|
|
|
|
throw new Error('Group update is missing contacts');
|
|
|
|
}
|
|
|
|
|
2020-09-14 19:51:27 +00:00
|
|
|
// eslint-disable-next-line no-case-declarations
|
2020-06-25 22:36:24 +00:00
|
|
|
const otherPeopleNotifMsg =
|
|
|
|
otherPeople.length === 1
|
|
|
|
? 'joinedTheGroup'
|
|
|
|
: 'multipleJoinedTheGroup';
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{otherPeople.length > 0 && (
|
2020-03-26 21:47:35 +00:00
|
|
|
<Intl
|
|
|
|
i18n={i18n}
|
2020-06-25 22:36:24 +00:00
|
|
|
id={otherPeopleNotifMsg}
|
2020-03-26 21:47:35 +00:00
|
|
|
components={[otherPeopleWithCommas]}
|
|
|
|
/>
|
2020-06-25 22:36:24 +00:00
|
|
|
)}
|
|
|
|
{contactsIncludesMe && (
|
2020-03-26 21:47:35 +00:00
|
|
|
<div className="module-group-notification__change">
|
|
|
|
<Intl i18n={i18n} id="youJoinedTheGroup" />
|
|
|
|
</div>
|
2020-06-25 22:36:24 +00:00
|
|
|
)}
|
2020-03-26 21:47:35 +00:00
|
|
|
</>
|
|
|
|
);
|
2018-07-09 21:29:13 +00:00
|
|
|
case 'remove':
|
2020-03-26 21:47:35 +00:00
|
|
|
if (from && from.isMe) {
|
2018-07-09 21:29:13 +00:00
|
|
|
return i18n('youLeftTheGroup');
|
|
|
|
}
|
|
|
|
|
2018-09-13 19:57:07 +00:00
|
|
|
if (!contacts || !contacts.length) {
|
|
|
|
throw new Error('Group update is missing contacts');
|
|
|
|
}
|
|
|
|
|
2020-09-14 19:51:27 +00:00
|
|
|
// eslint-disable-next-line no-case-declarations
|
2019-01-14 21:49:58 +00:00
|
|
|
const leftKey =
|
|
|
|
contacts.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup';
|
|
|
|
|
2020-03-26 21:47:35 +00:00
|
|
|
return (
|
|
|
|
<Intl i18n={i18n} id={leftKey} components={[otherPeopleWithCommas]} />
|
|
|
|
);
|
2018-07-09 21:29:13 +00:00
|
|
|
case 'general':
|
2020-09-14 19:51:27 +00:00
|
|
|
// eslint-disable-next-line consistent-return
|
2020-03-26 21:47:35 +00:00
|
|
|
return;
|
2018-07-09 21:29:13 +00:00
|
|
|
default:
|
|
|
|
throw missingCaseError(type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-14 19:51:27 +00:00
|
|
|
public render(): JSX.Element {
|
2021-09-07 19:55:03 +00:00
|
|
|
const { changes: rawChanges, i18n, from } = this.props;
|
|
|
|
|
|
|
|
// This check is just to be extra careful, and can probably be removed.
|
|
|
|
const changes: Array<Change> = Array.isArray(rawChanges) ? rawChanges : [];
|
2020-03-26 21:47:35 +00:00
|
|
|
|
|
|
|
// Leave messages are always from the person leaving, so we omit the fromLabel if
|
|
|
|
// the change is a 'leave.'
|
2021-09-07 19:55:03 +00:00
|
|
|
const firstChange: undefined | Change = changes[0];
|
|
|
|
const isLeftOnly = changes.length === 1 && firstChange?.type === 'remove';
|
2020-03-26 21:47:35 +00:00
|
|
|
|
|
|
|
const fromContact = (
|
|
|
|
<ContactName
|
2020-07-24 01:35:32 +00:00
|
|
|
title={from.title}
|
2020-03-26 21:47:35 +00:00
|
|
|
phoneNumber={from.phoneNumber}
|
|
|
|
profileName={from.profileName}
|
|
|
|
name={from.name}
|
2020-07-24 01:35:32 +00:00
|
|
|
i18n={i18n}
|
2020-03-26 21:47:35 +00:00
|
|
|
/>
|
|
|
|
);
|
|
|
|
|
|
|
|
const fromLabel = from.isMe ? (
|
|
|
|
<Intl i18n={i18n} id="youUpdatedTheGroup" />
|
|
|
|
) : (
|
|
|
|
<Intl i18n={i18n} id="updatedTheGroup" components={[fromContact]} />
|
|
|
|
);
|
2018-07-09 21:29:13 +00:00
|
|
|
|
2021-09-07 19:55:03 +00:00
|
|
|
let contents: ReactNode;
|
|
|
|
if (isLeftOnly) {
|
|
|
|
contents = this.renderChange(firstChange, from);
|
|
|
|
} else {
|
|
|
|
contents = (
|
|
|
|
<>
|
|
|
|
<p>{fromLabel}</p>
|
|
|
|
{changes.map((change, i) => (
|
2021-08-26 20:51:55 +00:00
|
|
|
// eslint-disable-next-line react/no-array-index-key
|
2021-09-07 19:55:03 +00:00
|
|
|
<p key={i} className="module-group-notification__change">
|
2021-08-26 20:51:55 +00:00
|
|
|
{this.renderChange(change, from)}
|
2021-09-07 19:55:03 +00:00
|
|
|
</p>
|
2021-08-26 20:51:55 +00:00
|
|
|
))}
|
2021-09-07 19:55:03 +00:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return <SystemMessage contents={contents} icon="group" />;
|
2018-07-09 21:29:13 +00:00
|
|
|
}
|
|
|
|
}
|