Include sender in group update notifications
This commit is contained in:
parent
d88c21e5b6
commit
71436d18e2
28 changed files with 1016 additions and 472 deletions
|
@ -268,7 +268,7 @@
|
||||||
"description": "Used as a label on a button allowing user to see more information"
|
"description": "Used as a label on a button allowing user to see more information"
|
||||||
},
|
},
|
||||||
"youLeftTheGroup": {
|
"youLeftTheGroup": {
|
||||||
"message": "You left the group",
|
"message": "You left the group.",
|
||||||
"description": "Displayed when a user can't send a message because they have left the group"
|
"description": "Displayed when a user can't send a message because they have left the group"
|
||||||
},
|
},
|
||||||
"scrollDown": {
|
"scrollDown": {
|
||||||
|
@ -1591,7 +1591,7 @@
|
||||||
"message": "Later"
|
"message": "Later"
|
||||||
},
|
},
|
||||||
"leftTheGroup": {
|
"leftTheGroup": {
|
||||||
"message": "$name$ left the group",
|
"message": "$name$ left the group.",
|
||||||
"description": "Shown in the conversation history when a single person leaves the group",
|
"description": "Shown in the conversation history when a single person leaves the group",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": {
|
"name": {
|
||||||
|
@ -1601,7 +1601,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"multipleLeftTheGroup": {
|
"multipleLeftTheGroup": {
|
||||||
"message": "$name$ left the group",
|
"message": "$name$ left the group.",
|
||||||
"description": "Shown in the conversation history when multiple people leave the group",
|
"description": "Shown in the conversation history when multiple people leave the group",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": {
|
"name": {
|
||||||
|
@ -1611,11 +1611,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"updatedTheGroup": {
|
"updatedTheGroup": {
|
||||||
"message": "Group updated",
|
"message": "$name$ updated the group.",
|
||||||
|
"description": "Shown in the conversation history when someone updates the group",
|
||||||
|
"placeholders": {
|
||||||
|
"name": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "Alice"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"youUpdatedTheGroup": {
|
||||||
|
"message": "You updated the group.",
|
||||||
|
"description": "Shown in the conversation history when you update a group"
|
||||||
|
},
|
||||||
|
"updatedGroupAvatar": {
|
||||||
|
"message": "Group avatar was updated.",
|
||||||
"description": "Shown in the conversation history when someone updates the group"
|
"description": "Shown in the conversation history when someone updates the group"
|
||||||
},
|
},
|
||||||
"titleIsNow": {
|
"titleIsNow": {
|
||||||
"message": "Title is now '$name$'",
|
"message": "Group name is now '$name$'.",
|
||||||
"description": "Shown in the conversation history when someone changes the title of the group",
|
"description": "Shown in the conversation history when someone changes the title of the group",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": {
|
"name": {
|
||||||
|
@ -1624,8 +1638,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"youJoinedTheGroup": {
|
||||||
|
"message": "You joined the group.",
|
||||||
|
"description": "Shown in the conversation history when you are added to a group."
|
||||||
|
},
|
||||||
"joinedTheGroup": {
|
"joinedTheGroup": {
|
||||||
"message": "$name$ joined the group",
|
"message": "$name$ joined the group.",
|
||||||
"description": "Shown in the conversation history when a single person joins the group",
|
"description": "Shown in the conversation history when a single person joins the group",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"name": {
|
"name": {
|
||||||
|
@ -1635,7 +1653,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"multipleJoinedTheGroup": {
|
"multipleJoinedTheGroup": {
|
||||||
"message": "$names$ joined the group",
|
"message": "$names$ joined the group.",
|
||||||
"description": "Shown in the conversation history when more than one person joins the group",
|
"description": "Shown in the conversation history when more than one person joins the group",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"names": {
|
"names": {
|
||||||
|
|
|
@ -1549,6 +1549,9 @@
|
||||||
) {
|
) {
|
||||||
let expireTimer = providedExpireTimer;
|
let expireTimer = providedExpireTimer;
|
||||||
let source = providedSource;
|
let source = providedSource;
|
||||||
|
if (this.get('left')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_.defaults(options, { fromSync: false, fromGroupUpdate: false });
|
_.defaults(options, { fromSync: false, fromGroupUpdate: false });
|
||||||
|
|
||||||
|
|
|
@ -432,7 +432,12 @@
|
||||||
const groupUpdate = this.get('group_update');
|
const groupUpdate = this.get('group_update');
|
||||||
const changes = [];
|
const changes = [];
|
||||||
|
|
||||||
if (!groupUpdate.name && !groupUpdate.left && !groupUpdate.joined) {
|
if (
|
||||||
|
!groupUpdate.avatarUpdated &&
|
||||||
|
!groupUpdate.left &&
|
||||||
|
!groupUpdate.joined &&
|
||||||
|
!groupUpdate.name
|
||||||
|
) {
|
||||||
changes.push({
|
changes.push({
|
||||||
type: 'general',
|
type: 'general',
|
||||||
});
|
});
|
||||||
|
@ -474,7 +479,18 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (groupUpdate.avatarUpdated) {
|
||||||
|
changes.push({
|
||||||
|
type: 'avatar',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceE164 = this.getSource();
|
||||||
|
const sourceUuid = this.getSourceUuid();
|
||||||
|
const from = this.findAndFormatContact(sourceE164 || sourceUuid);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
from,
|
||||||
changes,
|
changes,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -834,34 +850,72 @@
|
||||||
|
|
||||||
return i18n('mediaMessage');
|
return i18n('mediaMessage');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isGroupUpdate()) {
|
if (this.isGroupUpdate()) {
|
||||||
const groupUpdate = this.get('group_update');
|
const groupUpdate = this.get('group_update');
|
||||||
|
const fromContact = this.getContact();
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
if (groupUpdate.left === 'You') {
|
if (groupUpdate.left === 'You') {
|
||||||
return i18n('youLeftTheGroup');
|
return i18n('youLeftTheGroup');
|
||||||
} else if (groupUpdate.left) {
|
} else if (groupUpdate.left) {
|
||||||
return i18n('leftTheGroup', this.getNameForNumber(groupUpdate.left));
|
return i18n('leftTheGroup', this.getNameForNumber(groupUpdate.left));
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = [];
|
if (!fromContact) {
|
||||||
if (!groupUpdate.name && !groupUpdate.joined) {
|
return '';
|
||||||
messages.push(i18n('updatedTheGroup'));
|
|
||||||
}
|
}
|
||||||
if (groupUpdate.name) {
|
|
||||||
messages.push(i18n('titleIsNow', groupUpdate.name));
|
if (fromContact.isMe()) {
|
||||||
}
|
messages.push(i18n('youUpdatedTheGroup'));
|
||||||
if (groupUpdate.joined && groupUpdate.joined.length) {
|
|
||||||
const names = _.map(
|
|
||||||
groupUpdate.joined,
|
|
||||||
this.getNameForNumber.bind(this)
|
|
||||||
);
|
|
||||||
if (names.length > 1) {
|
|
||||||
messages.push(i18n('multipleJoinedTheGroup', names.join(', ')));
|
|
||||||
} else {
|
} else {
|
||||||
messages.push(i18n('joinedTheGroup', names[0]));
|
messages.push(i18n('updatedTheGroup', fromContact.getDisplayName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupUpdate.joined && groupUpdate.joined.length) {
|
||||||
|
const joinedContacts = _.map(groupUpdate.joined, item =>
|
||||||
|
ConversationController.getOrCreate(item, 'private')
|
||||||
|
);
|
||||||
|
const joinedWithoutMe = joinedContacts.filter(
|
||||||
|
contact => !contact.isMe()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (joinedContacts.length > 1) {
|
||||||
|
messages.push(
|
||||||
|
i18n(
|
||||||
|
'multipleJoinedTheGroup',
|
||||||
|
_.map(joinedWithoutMe, contact =>
|
||||||
|
contact.getDisplayName()
|
||||||
|
).join(', ')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (joinedWithoutMe.length < joinedContacts.length) {
|
||||||
|
messages.push(i18n('youJoinedTheGroup'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const joinedContact = ConversationController.getOrCreate(
|
||||||
|
groupUpdate.joined[0],
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
if (joinedContact.isMe()) {
|
||||||
|
messages.push(i18n('youJoinedTheGroup'));
|
||||||
|
} else {
|
||||||
|
messages.push(
|
||||||
|
i18n('joinedTheGroup', joinedContacts[0].getDisplayName())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages.join(', ');
|
if (groupUpdate.name) {
|
||||||
|
messages.push(i18n('titleIsNow', groupUpdate.name));
|
||||||
|
}
|
||||||
|
if (groupUpdate.avatarUpdated) {
|
||||||
|
messages.push(i18n('updatedGroupAvatar'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.join(' ');
|
||||||
}
|
}
|
||||||
if (this.isEndSession()) {
|
if (this.isEndSession()) {
|
||||||
return i18n('sessionEnded');
|
return i18n('sessionEnded');
|
||||||
|
@ -2165,10 +2219,13 @@
|
||||||
members: _.union(members, conversation.get('members')),
|
members: _.union(members, conversation.get('members')),
|
||||||
};
|
};
|
||||||
|
|
||||||
groupUpdate =
|
groupUpdate = {};
|
||||||
conversation.changedAttributes(
|
if (dataMessage.group.name !== conversation.get('name')) {
|
||||||
_.pick(dataMessage.group, 'name', 'avatar')
|
groupUpdate.name = dataMessage.group.name;
|
||||||
) || {};
|
}
|
||||||
|
|
||||||
|
// Note: used and later cleared by background attachment downloader
|
||||||
|
groupUpdate.avatar = dataMessage.group.avatar;
|
||||||
|
|
||||||
const difference = _.difference(
|
const difference = _.difference(
|
||||||
members,
|
members,
|
||||||
|
|
|
@ -422,21 +422,39 @@ async function _addAttachmentToMessage(message, attachment, { type, index }) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingAvatar = conversation.get('avatar');
|
|
||||||
if (existingAvatar && existingAvatar.path) {
|
|
||||||
await Signal.Migrations.deleteAttachmentData(existingAvatar.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadedAttachment = await Signal.Migrations.loadAttachmentData(
|
const loadedAttachment = await Signal.Migrations.loadAttachmentData(
|
||||||
attachment
|
attachment
|
||||||
);
|
);
|
||||||
|
const hash = await computeHash(loadedAttachment.data);
|
||||||
|
const existingAvatar = conversation.get('avatar');
|
||||||
|
|
||||||
|
if (existingAvatar) {
|
||||||
|
if (existingAvatar.hash === hash) {
|
||||||
|
logger.info(
|
||||||
|
'_addAttachmentToMessage: Group avatar hash matched; not replacing group avatar'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Signal.Migrations.deleteAttachmentData(existingAvatar.path);
|
||||||
|
}
|
||||||
|
|
||||||
conversation.set({
|
conversation.set({
|
||||||
avatar: {
|
avatar: {
|
||||||
...attachment,
|
...attachment,
|
||||||
hash: await computeHash(loadedAttachment.data),
|
hash,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Signal.Data.updateConversation(conversationId, conversation.attributes);
|
Signal.Data.updateConversation(conversationId, conversation.attributes);
|
||||||
|
|
||||||
|
message.set({
|
||||||
|
group_update: {
|
||||||
|
...message.get('group_update'),
|
||||||
|
avatar: null,
|
||||||
|
avatarUpdated: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -371,6 +371,7 @@
|
||||||
isMe: this.model.isMe(),
|
isMe: this.model.isMe(),
|
||||||
isGroup: !this.model.isPrivate(),
|
isGroup: !this.model.isPrivate(),
|
||||||
isArchived: this.model.get('isArchived'),
|
isArchived: this.model.get('isArchived'),
|
||||||
|
leftGroup: this.model.get('left'),
|
||||||
|
|
||||||
expirationSettingName,
|
expirationSettingName,
|
||||||
showBackButton: Boolean(this.panels && this.panels.length),
|
showBackButton: Boolean(this.panels && this.panels.length),
|
||||||
|
|
|
@ -2255,6 +2255,8 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
.module-group-notification {
|
.module-group-notification {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
@ -2267,8 +2269,8 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-group-notification__change {
|
.module-group-notification__change {
|
||||||
margin-top: 5px;
|
margin-top: 2px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-group-notification__contact {
|
.module-group-notification__contact {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global ConversationController, i18n, Whisper */
|
/* global ConversationController, i18n, Whisper, textsecure */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -10,15 +10,21 @@ const attributes = {
|
||||||
received_at: new Date().getTime(),
|
received_at: new Date().getTime(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const source = '+14155555555';
|
const source = '+1 415-555-5555';
|
||||||
|
const me = '+14155555556';
|
||||||
|
const ourUuid = window.getGuid();
|
||||||
|
|
||||||
describe('MessageCollection', () => {
|
describe('MessageCollection', () => {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
await clearDatabase();
|
await clearDatabase();
|
||||||
ConversationController.reset();
|
ConversationController.reset();
|
||||||
await ConversationController.load();
|
await ConversationController.load();
|
||||||
|
textsecure.storage.put('number_id', `${me}.2`);
|
||||||
|
textsecure.storage.put('uuid_id', `${ourUuid}.2`);
|
||||||
});
|
});
|
||||||
after(() => {
|
after(() => {
|
||||||
|
textsecure.storage.put('number_id', null);
|
||||||
|
textsecure.storage.put('uuid_id', null);
|
||||||
return clearDatabase();
|
return clearDatabase();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -91,46 +97,128 @@ describe('MessageCollection', () => {
|
||||||
'If no group updates or end session flags, return message body.'
|
'If no group updates or end session flags, return message body.'
|
||||||
);
|
);
|
||||||
|
|
||||||
message = messages.add({ group_update: { left: 'Alice' } });
|
message = messages.add({
|
||||||
|
group_update: {},
|
||||||
|
source: 'Alice',
|
||||||
|
type: 'incoming',
|
||||||
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
message.getDescription(),
|
message.getDescription(),
|
||||||
'Alice left the group',
|
'Alice updated the group.',
|
||||||
|
'Empty group updates - generic message.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
|
group_update: { left: 'Alice' },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'Alice left the group.',
|
||||||
'Notes one person leaving the group.'
|
'Notes one person leaving the group.'
|
||||||
);
|
);
|
||||||
|
|
||||||
message = messages.add({ group_update: { name: 'blerg' } });
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source: me,
|
||||||
|
group_update: { left: 'You' },
|
||||||
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
message.getDescription(),
|
message.getDescription(),
|
||||||
"Title is now 'blerg'",
|
'You left the group.',
|
||||||
'Returns a single notice if only group_updates.name changes.'
|
'Notes that you left the group.'
|
||||||
);
|
);
|
||||||
|
|
||||||
message = messages.add({ group_update: { joined: ['Bob'] } });
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
|
group_update: { name: 'blerg' },
|
||||||
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
message.getDescription(),
|
message.getDescription(),
|
||||||
'Bob joined the group',
|
"+1 415-555-5555 updated the group. Group name is now 'blerg'.",
|
||||||
|
'Returns sender and name change.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source: me,
|
||||||
|
group_update: { name: 'blerg' },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
"You updated the group. Group name is now 'blerg'.",
|
||||||
|
'Includes "you" as sender along with group name change.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
|
group_update: { avatarUpdated: true },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'+1 415-555-5555 updated the group. Group avatar was updated.',
|
||||||
|
'Includes sender and avatar update.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
|
group_update: { joined: [me] },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'+1 415-555-5555 updated the group. You joined the group.',
|
||||||
|
'Includes both sender and person added with join.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
|
group_update: { joined: ['Bob'] },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'+1 415-555-5555 updated the group. Bob joined the group.',
|
||||||
'Returns a single notice if only group_updates.joined changes.'
|
'Returns a single notice if only group_updates.joined changes.'
|
||||||
);
|
);
|
||||||
|
|
||||||
message = messages.add({
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
group_update: { joined: ['Bob', 'Alice', 'Eve'] },
|
group_update: { joined: ['Bob', 'Alice', 'Eve'] },
|
||||||
});
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
message.getDescription(),
|
message.getDescription(),
|
||||||
'Bob, Alice, Eve joined the group',
|
'+1 415-555-5555 updated the group. Bob, Alice, Eve joined the group.',
|
||||||
'Notes when >1 person joins the group.'
|
'Notes when >1 person joins the group.'
|
||||||
);
|
);
|
||||||
|
|
||||||
message = messages.add({
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
|
group_update: { joined: ['Bob', me, 'Alice', 'Eve'] },
|
||||||
|
});
|
||||||
|
assert.equal(
|
||||||
|
message.getDescription(),
|
||||||
|
'+1 415-555-5555 updated the group. Bob, Alice, Eve joined the group. You joined the group.',
|
||||||
|
'Splits "You" out when multiple people are added along with you.'
|
||||||
|
);
|
||||||
|
|
||||||
|
message = messages.add({
|
||||||
|
type: 'incoming',
|
||||||
|
source,
|
||||||
group_update: { joined: ['Bob'], name: 'blerg' },
|
group_update: { joined: ['Bob'], name: 'blerg' },
|
||||||
});
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
message.getDescription(),
|
message.getDescription(),
|
||||||
"Title is now 'blerg', Bob joined the group",
|
"+1 415-555-5555 updated the group. Bob joined the group. Group name is now 'blerg'.",
|
||||||
'Notes when there are multiple changes to group_updates properties.'
|
'Notes when there are multiple changes to group_updates properties.'
|
||||||
);
|
);
|
||||||
|
|
||||||
message = messages.add({ flags: true });
|
message = messages.add({ type: 'incoming', source, flags: true });
|
||||||
assert.equal(message.getDescription(), i18n('sessionEnded'));
|
assert.equal(message.getDescription(), i18n('sessionEnded'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,7 +227,7 @@ describe('MessageCollection', () => {
|
||||||
let message = messages.add(attributes);
|
let message = messages.add(attributes);
|
||||||
assert.notOk(message.isEndSession());
|
assert.notOk(message.isEndSession());
|
||||||
|
|
||||||
message = messages.add({ flags: true });
|
message = messages.add({ type: 'incoming', source, flags: true });
|
||||||
assert.ok(message.isEndSession());
|
assert.ok(message.isEndSession());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,11 +2,12 @@ import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { getInitials } from '../util/getInitials';
|
import { getInitials } from '../util/getInitials';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { ColorType, LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
color?: string;
|
color?: ColorType;
|
||||||
|
|
||||||
conversationType: 'group' | 'direct';
|
conversationType: 'group' | 'direct';
|
||||||
noteToSelf?: boolean;
|
noteToSelf?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
|
@ -4,13 +4,13 @@ import classNames from 'classnames';
|
||||||
import { Avatar } from './Avatar';
|
import { Avatar } from './Avatar';
|
||||||
import { Emojify } from './conversation/Emojify';
|
import { Emojify } from './conversation/Emojify';
|
||||||
|
|
||||||
import { LocalizerType } from '../types/Util';
|
import { ColorType, LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
isMe?: boolean;
|
isMe?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
color: string;
|
color: ColorType;
|
||||||
verified: boolean;
|
verified: boolean;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
|
|
|
@ -8,12 +8,12 @@ import { ContactName } from './conversation/ContactName';
|
||||||
import { TypingAnimation } from './conversation/TypingAnimation';
|
import { TypingAnimation } from './conversation/TypingAnimation';
|
||||||
import { cleanId } from './_util';
|
import { cleanId } from './_util';
|
||||||
|
|
||||||
import { LocalizerType } from '../types/Util';
|
import { ColorType, LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
export type PropsData = {
|
export type PropsData = {
|
||||||
id: string;
|
id: string;
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
color?: string;
|
color?: ColorType;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
type: 'group' | 'direct';
|
type: 'group' | 'direct';
|
||||||
|
|
|
@ -2,13 +2,13 @@ import React from 'react';
|
||||||
|
|
||||||
import { LocalizerType, RenderTextCallbackType } from '../types/Util';
|
import { LocalizerType, RenderTextCallbackType } from '../types/Util';
|
||||||
|
|
||||||
type FullJSX = Array<JSX.Element | string> | JSX.Element | string;
|
export type FullJSXType = Array<JSX.Element | string> | JSX.Element | string;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** The translation string id */
|
/** The translation string id */
|
||||||
id: string;
|
id: string;
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
components?: Array<FullJSX>;
|
components?: Array<FullJSXType>;
|
||||||
renderText?: RenderTextCallbackType;
|
renderText?: RenderTextCallbackType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ export class Intl extends React.Component<Props> {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
public getComponent(index: number, key: number): FullJSX | undefined {
|
public getComponent(index: number, key: number): FullJSXType | undefined {
|
||||||
const { id, components } = this.props;
|
const { id, components } = this.props;
|
||||||
|
|
||||||
if (!components || !components.length || components.length <= index) {
|
if (!components || !components.length || components.length <= index) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { createPortal } from 'react-dom';
|
||||||
import { showSettings } from '../shims/Whisper';
|
import { showSettings } from '../shims/Whisper';
|
||||||
import { Avatar } from './Avatar';
|
import { Avatar } from './Avatar';
|
||||||
import { AvatarPopup } from './AvatarPopup';
|
import { AvatarPopup } from './AvatarPopup';
|
||||||
import { LocalizerType } from '../types/Util';
|
import { ColorType, LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
export interface PropsType {
|
export interface PropsType {
|
||||||
searchTerm: string;
|
searchTerm: string;
|
||||||
|
@ -25,7 +25,7 @@ export interface PropsType {
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
isMe: boolean;
|
isMe: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
color: string;
|
color: ColorType;
|
||||||
verified: boolean;
|
verified: boolean;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { MessageBodyHighlight } from './MessageBodyHighlight';
|
||||||
import { Timestamp } from './conversation/Timestamp';
|
import { Timestamp } from './conversation/Timestamp';
|
||||||
import { ContactName } from './conversation/ContactName';
|
import { ContactName } from './conversation/ContactName';
|
||||||
|
|
||||||
import { LocalizerType } from '../types/Util';
|
import { ColorType, LocalizerType } from '../types/Util';
|
||||||
|
|
||||||
export type PropsDataType = {
|
export type PropsDataType = {
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
|
@ -22,7 +22,7 @@ export type PropsDataType = {
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
isMe?: boolean;
|
isMe?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
color?: string;
|
color?: ColorType;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const NetworkStatus = ({
|
||||||
|
|
||||||
const [isConnecting, setIsConnecting] = React.useState<boolean>(false);
|
const [isConnecting, setIsConnecting] = React.useState<boolean>(false);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let timeout: NodeJS.Timeout;
|
let timeout: any;
|
||||||
|
|
||||||
if (isConnecting) {
|
if (isConnecting) {
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
|
|
|
@ -14,32 +14,15 @@ import { storiesOf } from '@storybook/react';
|
||||||
//import { boolean, select } from '@storybook/addon-knobs';
|
//import { boolean, select } from '@storybook/addon-knobs';
|
||||||
import { action } from '@storybook/addon-actions';
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
// @ts-ignore
|
import {
|
||||||
import gif from '../../fixtures/giphy-GVNvOUpeYmI7e.gif';
|
gifObjectUrl,
|
||||||
// @ts-ignore
|
landscapeGreenObjectUrl,
|
||||||
import png from '../../fixtures/freepngs-2cd43b_bed7d1327e88454487397574d87b64dc_mv2.png';
|
landscapePurpleObjectUrl,
|
||||||
// @ts-ignore
|
pngObjectUrl,
|
||||||
import landscapeGreen from '../../fixtures/1000x50-green.jpeg';
|
} from '../storybook/Fixtures';
|
||||||
// @ts-ignore
|
|
||||||
import landscapePurple from '../../fixtures/200x50-purple.png';
|
|
||||||
|
|
||||||
const i18n = setupI18n('en', enMessages);
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
function makeObjectUrl(data: ArrayBuffer, contentType: string): string {
|
|
||||||
const blob = new Blob([data], {
|
|
||||||
type: contentType,
|
|
||||||
});
|
|
||||||
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 320x240
|
|
||||||
const gifObjectUrl = makeObjectUrl(gif, 'image/gif');
|
|
||||||
// 800×1200
|
|
||||||
const pngObjectUrl = makeObjectUrl(png, 'image/png');
|
|
||||||
const landscapeGreenObjectUrl = makeObjectUrl(landscapeGreen, 'image/jpeg');
|
|
||||||
const landscapePurpleObjectUrl = makeObjectUrl(landscapePurple, 'image/png');
|
|
||||||
|
|
||||||
const messageLookup: Map<string, MessageSearchResultPropsType> = new Map();
|
const messageLookup: Map<string, MessageSearchResultPropsType> = new Map();
|
||||||
|
|
||||||
const CONTACT = 'contact' as 'contact';
|
const CONTACT = 'contact' as 'contact';
|
||||||
|
@ -64,6 +47,7 @@ messageLookup.set('1-guid-guid-guid-guid-guid', {
|
||||||
from: {
|
from: {
|
||||||
phoneNumber: '(202) 555-0020',
|
phoneNumber: '(202) 555-0020',
|
||||||
isMe: true,
|
isMe: true,
|
||||||
|
color: 'blue',
|
||||||
avatarPath: gifObjectUrl,
|
avatarPath: gifObjectUrl,
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
|
@ -116,6 +100,7 @@ messageLookup.set('4-guid-guid-guid-guid-guid', {
|
||||||
from: {
|
from: {
|
||||||
phoneNumber: '(202) 555-0020',
|
phoneNumber: '(202) 555-0020',
|
||||||
isMe: true,
|
isMe: true,
|
||||||
|
color: 'light_green',
|
||||||
avatarPath: gifObjectUrl,
|
avatarPath: gifObjectUrl,
|
||||||
},
|
},
|
||||||
to: {
|
to: {
|
||||||
|
@ -160,6 +145,7 @@ const conversations = [
|
||||||
phoneNumber: '(202) 555-0011',
|
phoneNumber: '(202) 555-0011',
|
||||||
name: 'Everyone 🌆',
|
name: 'Everyone 🌆',
|
||||||
type: GROUP,
|
type: GROUP,
|
||||||
|
color: 'signal-blue' as 'signal-blue',
|
||||||
avatarPath: landscapeGreenObjectUrl,
|
avatarPath: landscapeGreenObjectUrl,
|
||||||
isMe: false,
|
isMe: false,
|
||||||
lastUpdated: Date.now() - 5 * 60 * 1000,
|
lastUpdated: Date.now() - 5 * 60 * 1000,
|
||||||
|
@ -177,6 +163,7 @@ const conversations = [
|
||||||
id: '+12025550012',
|
id: '+12025550012',
|
||||||
phoneNumber: '(202) 555-0012',
|
phoneNumber: '(202) 555-0012',
|
||||||
name: 'Everyone Else 🔥',
|
name: 'Everyone Else 🔥',
|
||||||
|
color: 'pink' as 'pink',
|
||||||
type: DIRECT,
|
type: DIRECT,
|
||||||
avatarPath: landscapePurpleObjectUrl,
|
avatarPath: landscapePurpleObjectUrl,
|
||||||
isMe: false,
|
isMe: false,
|
||||||
|
@ -198,6 +185,7 @@ const contacts = [
|
||||||
id: '+12025550013',
|
id: '+12025550013',
|
||||||
phoneNumber: '(202) 555-0013',
|
phoneNumber: '(202) 555-0013',
|
||||||
name: 'The one Everyone',
|
name: 'The one Everyone',
|
||||||
|
color: 'blue' as 'blue',
|
||||||
type: DIRECT,
|
type: DIRECT,
|
||||||
avatarPath: gifObjectUrl,
|
avatarPath: gifObjectUrl,
|
||||||
isMe: false,
|
isMe: false,
|
||||||
|
@ -213,7 +201,7 @@ const contacts = [
|
||||||
phoneNumber: '(202) 555-0014',
|
phoneNumber: '(202) 555-0014',
|
||||||
name: 'No likey everyone',
|
name: 'No likey everyone',
|
||||||
type: DIRECT,
|
type: DIRECT,
|
||||||
color: 'red',
|
color: 'red' as 'red',
|
||||||
isMe: false,
|
isMe: false,
|
||||||
lastUpdated: Date.now() - 11 * 60 * 1000,
|
lastUpdated: Date.now() - 11 * 60 * 1000,
|
||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
|
|
|
@ -1,156 +0,0 @@
|
||||||
### Name variations, 1:1 conversation
|
|
||||||
|
|
||||||
Note the five items in menu, and the second-level menu with disappearing messages options. Disappearing message set to 'off'.
|
|
||||||
|
|
||||||
#### With name and profile, verified
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader
|
|
||||||
i18n={util.i18n}
|
|
||||||
color="red"
|
|
||||||
isVerified={true}
|
|
||||||
avatarPath={util.gifObjectUrl}
|
|
||||||
name="Someone 🔥 Somewhere"
|
|
||||||
phoneNumber="(202) 555-0001"
|
|
||||||
id="1"
|
|
||||||
profileName="🔥Flames🔥"
|
|
||||||
onSetDisappearingMessages={seconds =>
|
|
||||||
console.log('onSetDisappearingMessages', seconds)
|
|
||||||
}
|
|
||||||
onDeleteMessages={() => console.log('onDeleteMessages')}
|
|
||||||
onResetSession={() => console.log('onResetSession')}
|
|
||||||
onShowSafetyNumber={() => console.log('onShowSafetyNumber')}
|
|
||||||
onShowAllMedia={() => console.log('onShowAllMedia')}
|
|
||||||
onShowGroupMembers={() => console.log('onShowGroupMembers')}
|
|
||||||
onGoBack={() => console.log('onGoBack')}
|
|
||||||
onSearchInConversation={() => console.log('onSearchInConversation')}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### With name, not verified, no avatar
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader
|
|
||||||
i18n={util.i18n}
|
|
||||||
color="blue"
|
|
||||||
isVerified={false}
|
|
||||||
name="Someone 🔥 Somewhere"
|
|
||||||
phoneNumber="(202) 555-0002"
|
|
||||||
id="2"
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Profile, no name
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader
|
|
||||||
i18n={util.i18n}
|
|
||||||
color="teal"
|
|
||||||
isVerified={false}
|
|
||||||
phoneNumber="(202) 555-0003"
|
|
||||||
id="3"
|
|
||||||
profileName="🔥Flames🔥"
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### No name, no profile, no color
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader i18n={util.i18n} phoneNumber="(202) 555-0011" id="11" />
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### With back button
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader
|
|
||||||
showBackButton={true}
|
|
||||||
color="deep_orange"
|
|
||||||
i18n={util.i18n}
|
|
||||||
phoneNumber="(202) 555-0004"
|
|
||||||
id="4"
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Disappearing messages set
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader
|
|
||||||
color="indigo"
|
|
||||||
i18n={util.i18n}
|
|
||||||
phoneNumber="(202) 555-0005"
|
|
||||||
id="5"
|
|
||||||
expirationSettingName="10 seconds"
|
|
||||||
timerOptions={[
|
|
||||||
{
|
|
||||||
name: 'off',
|
|
||||||
value: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '10 seconds',
|
|
||||||
value: 10,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
onSetDisappearingMessages={seconds =>
|
|
||||||
console.log('onSetDisappearingMessages', seconds)
|
|
||||||
}
|
|
||||||
onDeleteMessages={() => console.log('onDeleteMessages')}
|
|
||||||
onResetSession={() => console.log('onResetSession')}
|
|
||||||
onShowSafetyNumber={() => console.log('onShowSafetyNumber')}
|
|
||||||
onShowAllMedia={() => console.log('onShowAllMedia')}
|
|
||||||
onShowGroupMembers={() => console.log('onShowGroupMembers')}
|
|
||||||
onGoBack={() => console.log('onGoBack')}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### In a group
|
|
||||||
|
|
||||||
Note that the menu should includes 'Show Members' instead of 'Show Safety Number'
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader
|
|
||||||
i18n={util.i18n}
|
|
||||||
color="green"
|
|
||||||
phoneNumber="(202) 555-0006"
|
|
||||||
id="6"
|
|
||||||
isGroup={true}
|
|
||||||
onSetDisappearingMessages={seconds =>
|
|
||||||
console.log('onSetDisappearingMessages', seconds)
|
|
||||||
}
|
|
||||||
onDeleteMessages={() => console.log('onDeleteMessages')}
|
|
||||||
onResetSession={() => console.log('onResetSession')}
|
|
||||||
onShowSafetyNumber={() => console.log('onShowSafetyNumber')}
|
|
||||||
onShowAllMedia={() => console.log('onShowAllMedia')}
|
|
||||||
onShowGroupMembers={() => console.log('onShowGroupMembers')}
|
|
||||||
onGoBack={() => console.log('onGoBack')}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### In chat with yourself
|
|
||||||
|
|
||||||
This is the 'Note to self' conversation. Note that the menu should not have a 'Show Safety Number' entry.
|
|
||||||
|
|
||||||
```jsx
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<ConversationHeader
|
|
||||||
color="cyan"
|
|
||||||
i18n={util.i18n}
|
|
||||||
phoneNumber="(202) 555-0007"
|
|
||||||
id="7"
|
|
||||||
isMe={true}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
227
ts/components/conversation/ConversationHeader.stories.tsx
Normal file
227
ts/components/conversation/ConversationHeader.stories.tsx
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||||
|
// @ts-ignore
|
||||||
|
import enMessages from '../../../\_locales/en/messages.json';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ConversationHeader,
|
||||||
|
Props,
|
||||||
|
PropsActions,
|
||||||
|
PropsHousekeeping,
|
||||||
|
} from './ConversationHeader';
|
||||||
|
|
||||||
|
import { gifObjectUrl } from '../../storybook/Fixtures';
|
||||||
|
|
||||||
|
const book = storiesOf('Components/Conversation/ConversationHeader', module);
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
type ConversationHeaderStory = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
items: Array<{
|
||||||
|
title: string;
|
||||||
|
props: Props;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionProps: PropsActions = {
|
||||||
|
onSetDisappearingMessages: action('onSetDisappearingMessages'),
|
||||||
|
onDeleteMessages: action('onDeleteMessages'),
|
||||||
|
onResetSession: action('onResetSession'),
|
||||||
|
onSearchInConversation: action('onSearchInConversation'),
|
||||||
|
|
||||||
|
onShowSafetyNumber: action('onShowSafetyNumber'),
|
||||||
|
onShowAllMedia: action('onShowAllMedia'),
|
||||||
|
onShowGroupMembers: action('onShowGroupMembers'),
|
||||||
|
onGoBack: action('onGoBack'),
|
||||||
|
|
||||||
|
onArchive: action('onArchive'),
|
||||||
|
onMoveToInbox: action('onMoveToInbox'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const housekeepingProps: PropsHousekeeping = {
|
||||||
|
i18n,
|
||||||
|
};
|
||||||
|
|
||||||
|
const stories: Array<ConversationHeaderStory> = [
|
||||||
|
{
|
||||||
|
title: '1:1 conversation',
|
||||||
|
description:
|
||||||
|
"Note the five items in menu, and the second-level menu with disappearing messages options. Disappearing message set to 'off'.",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'With name and profile, verified',
|
||||||
|
props: {
|
||||||
|
color: 'red',
|
||||||
|
isVerified: true,
|
||||||
|
avatarPath: gifObjectUrl,
|
||||||
|
name: 'Someone 🔥 Somewhere',
|
||||||
|
phoneNumber: '(202) 555-0001',
|
||||||
|
id: '1',
|
||||||
|
profileName: '🔥Flames🔥',
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'With name, not verified, no avatar',
|
||||||
|
props: {
|
||||||
|
color: 'blue',
|
||||||
|
isVerified: false,
|
||||||
|
name: 'Someone 🔥 Somewhere',
|
||||||
|
phoneNumber: '(202) 555-0002',
|
||||||
|
id: '2',
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Profile, no name',
|
||||||
|
props: {
|
||||||
|
color: 'teal',
|
||||||
|
isVerified: false,
|
||||||
|
phoneNumber: '(202) 555-0003',
|
||||||
|
id: '3',
|
||||||
|
profileName: '🔥Flames🔥',
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'No name, no profile, no color',
|
||||||
|
props: {
|
||||||
|
phoneNumber: '(202) 555-0011',
|
||||||
|
id: '11',
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'With back button',
|
||||||
|
props: {
|
||||||
|
showBackButton: true,
|
||||||
|
color: 'deep_orange',
|
||||||
|
phoneNumber: '(202) 555-0004',
|
||||||
|
id: '4',
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Disappearing messages set',
|
||||||
|
props: {
|
||||||
|
color: 'indigo',
|
||||||
|
phoneNumber: '(202) 555-0005',
|
||||||
|
id: '5',
|
||||||
|
expirationSettingName: '10 seconds',
|
||||||
|
timerOptions: [
|
||||||
|
{
|
||||||
|
name: 'off',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '10 seconds',
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'In a group',
|
||||||
|
description:
|
||||||
|
"Note that the menu should includes 'Show Members' instead of 'Show Safety Number'",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'Basic',
|
||||||
|
props: {
|
||||||
|
color: 'signal-blue',
|
||||||
|
name: 'Typescript support group',
|
||||||
|
phoneNumber: '',
|
||||||
|
id: '1',
|
||||||
|
isGroup: true,
|
||||||
|
expirationSettingName: '10 seconds',
|
||||||
|
timerOptions: [
|
||||||
|
{
|
||||||
|
name: 'off',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '10 seconds',
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'In a group you left - no disappearing messages',
|
||||||
|
props: {
|
||||||
|
color: 'signal-blue',
|
||||||
|
name: 'Typescript support group',
|
||||||
|
phoneNumber: '',
|
||||||
|
id: '2',
|
||||||
|
isGroup: true,
|
||||||
|
leftGroup: true,
|
||||||
|
expirationSettingName: '10 seconds',
|
||||||
|
timerOptions: [
|
||||||
|
{
|
||||||
|
name: 'off',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '10 seconds',
|
||||||
|
value: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Note to Self',
|
||||||
|
description: 'No safety number entry.',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'In chat with yourself',
|
||||||
|
props: {
|
||||||
|
color: 'blue',
|
||||||
|
phoneNumber: '(202) 555-0007',
|
||||||
|
id: '7',
|
||||||
|
isMe: true,
|
||||||
|
...actionProps,
|
||||||
|
...housekeepingProps,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
stories.forEach(({ title, description, items }) =>
|
||||||
|
book.add(
|
||||||
|
title,
|
||||||
|
() =>
|
||||||
|
items.map(({ title: subtitle, props }, i) => {
|
||||||
|
return (
|
||||||
|
<div key={i}>
|
||||||
|
{subtitle ? <h3>{subtitle}</h3> : null}
|
||||||
|
<ConversationHeader {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
docs: description,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
|
@ -3,7 +3,7 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import { Emojify } from './Emojify';
|
import { Emojify } from './Emojify';
|
||||||
import { Avatar } from '../Avatar';
|
import { Avatar } from '../Avatar';
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { ColorType, LocalizerType } from '../../types/Util';
|
||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
|
@ -16,24 +16,27 @@ interface TimerOption {
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
export interface PropsData {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
color: string;
|
color?: ColorType;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
|
|
||||||
isVerified: boolean;
|
isVerified?: boolean;
|
||||||
isMe: boolean;
|
isMe?: boolean;
|
||||||
isGroup: boolean;
|
isGroup?: boolean;
|
||||||
isArchived: boolean;
|
isArchived?: boolean;
|
||||||
|
leftGroup?: boolean;
|
||||||
|
|
||||||
expirationSettingName?: string;
|
expirationSettingName?: string;
|
||||||
showBackButton: boolean;
|
showBackButton?: boolean;
|
||||||
timerOptions: Array<TimerOption>;
|
timerOptions?: Array<TimerOption>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PropsActions {
|
||||||
onSetDisappearingMessages: (seconds: number) => void;
|
onSetDisappearingMessages: (seconds: number) => void;
|
||||||
onDeleteMessages: () => void;
|
onDeleteMessages: () => void;
|
||||||
onResetSession: () => void;
|
onResetSession: () => void;
|
||||||
|
@ -46,10 +49,14 @@ interface Props {
|
||||||
|
|
||||||
onArchive: () => void;
|
onArchive: () => void;
|
||||||
onMoveToInbox: () => void;
|
onMoveToInbox: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PropsHousekeeping {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Props = PropsData & PropsActions & PropsHousekeeping;
|
||||||
|
|
||||||
export class ConversationHeader extends React.Component<Props> {
|
export class ConversationHeader extends React.Component<Props> {
|
||||||
public showMenuBound: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
public showMenuBound: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||||
public menuTriggerRef: React.RefObject<any>;
|
public menuTriggerRef: React.RefObject<any>;
|
||||||
|
@ -218,6 +225,7 @@ export class ConversationHeader extends React.Component<Props> {
|
||||||
isMe,
|
isMe,
|
||||||
isGroup,
|
isGroup,
|
||||||
isArchived,
|
isArchived,
|
||||||
|
leftGroup,
|
||||||
onDeleteMessages,
|
onDeleteMessages,
|
||||||
onResetSession,
|
onResetSession,
|
||||||
onSetDisappearingMessages,
|
onSetDisappearingMessages,
|
||||||
|
@ -233,6 +241,7 @@ export class ConversationHeader extends React.Component<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu id={triggerId}>
|
<ContextMenu id={triggerId}>
|
||||||
|
{leftGroup ? null : (
|
||||||
<SubMenu title={disappearingTitle}>
|
<SubMenu title={disappearingTitle}>
|
||||||
{(timerOptions || []).map(item => (
|
{(timerOptions || []).map(item => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@ -245,6 +254,7 @@ export class ConversationHeader extends React.Component<Props> {
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</SubMenu>
|
</SubMenu>
|
||||||
|
)}
|
||||||
<MenuItem onClick={onShowAllMedia}>{i18n('viewAllMedia')}</MenuItem>
|
<MenuItem onClick={onShowAllMedia}>{i18n('viewAllMedia')}</MenuItem>
|
||||||
{isGroup ? (
|
{isGroup ? (
|
||||||
<MenuItem onClick={onShowGroupMembers}>
|
<MenuItem onClick={onShowGroupMembers}>
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
### Three changes, all types
|
|
||||||
|
|
||||||
```js
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'add',
|
|
||||||
contacts: [
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1001',
|
|
||||||
profileName: 'Mrs. Ice',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1002',
|
|
||||||
name: 'Ms. Earth',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'name',
|
|
||||||
newName: 'New Group Name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'remove',
|
|
||||||
contacts: [
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1000',
|
|
||||||
profileName: 'Mr. Fire',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Joined group
|
|
||||||
|
|
||||||
```js
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'add',
|
|
||||||
contacts: [
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1000',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1001',
|
|
||||||
profileName: 'Mrs. Ice',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1002',
|
|
||||||
name: 'Ms. Earth',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'add',
|
|
||||||
contacts: [
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1000',
|
|
||||||
profileName: 'Mr. Fire',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Left group
|
|
||||||
|
|
||||||
```js
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'remove',
|
|
||||||
contacts: [
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1000',
|
|
||||||
profileName: 'Mr. Fire',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1001',
|
|
||||||
profileName: 'Mrs. Ice',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1002',
|
|
||||||
name: 'Ms. Earth',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'remove',
|
|
||||||
contacts: [
|
|
||||||
{
|
|
||||||
phoneNumber: '(202) 555-1000',
|
|
||||||
profileName: 'Mr. Fire',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'remove',
|
|
||||||
isMe: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Title changed
|
|
||||||
|
|
||||||
```js
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'name',
|
|
||||||
newName: 'New Group Name',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Generic group update
|
|
||||||
|
|
||||||
```js
|
|
||||||
<util.ConversationContext theme={util.theme}>
|
|
||||||
<GroupNotification
|
|
||||||
changes={[
|
|
||||||
{
|
|
||||||
type: 'general',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
i18n={util.i18n}
|
|
||||||
/>
|
|
||||||
</util.ConversationContext>
|
|
||||||
```
|
|
353
ts/components/conversation/GroupNotification.stories.tsx
Normal file
353
ts/components/conversation/GroupNotification.stories.tsx
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { setup as setupI18n } from '../../../js/modules/i18n';
|
||||||
|
// @ts-ignore
|
||||||
|
import enMessages from '../../../_locales/en/messages.json';
|
||||||
|
|
||||||
|
import { GroupNotification, Props } from './GroupNotification';
|
||||||
|
|
||||||
|
const book = storiesOf('Components/Conversation', module);
|
||||||
|
const i18n = setupI18n('en', enMessages);
|
||||||
|
|
||||||
|
type GroupNotificationStory = [string, Array<Props>];
|
||||||
|
|
||||||
|
const stories: Array<GroupNotificationStory> = [
|
||||||
|
[
|
||||||
|
'Combo',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'add',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1001',
|
||||||
|
profileName: 'Mrs. Ice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1002',
|
||||||
|
name: 'Ms. Earth',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: 'name', newName: 'Fishing Stories' },
|
||||||
|
{ type: 'avatar' },
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'add',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1001',
|
||||||
|
profileName: 'Mrs. Ice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1002',
|
||||||
|
name: 'Ms. Earth',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: 'name', newName: 'Fishing Stories' },
|
||||||
|
{ type: 'avatar' },
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Joined group',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'add',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1001',
|
||||||
|
profileName: 'Mrs. Ice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1002',
|
||||||
|
name: 'Ms. Earth',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'add',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1001',
|
||||||
|
profileName: 'Mrs. Ice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1002',
|
||||||
|
name: 'Ms. Earth',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'add',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
profileName: 'Mr. Fire',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'add',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
profileName: 'Mr. Fire',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'add',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
profileName: 'Mr. Fire',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Left group',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'remove',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
profileName: 'Mr. Fire',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1001',
|
||||||
|
profileName: 'Mrs. Ice',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1002',
|
||||||
|
name: 'Ms. Earth',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'remove',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
profileName: 'Mr. Fire',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'remove',
|
||||||
|
contacts: [
|
||||||
|
{
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Title changed',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'name',
|
||||||
|
newName: 'New Group Name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'name',
|
||||||
|
newName: 'New Group Name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Avatar changed',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'avatar',
|
||||||
|
newName: 'New Group Name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
isMe: true,
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'avatar',
|
||||||
|
newName: 'New Group Name',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Generic group update',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
from: {
|
||||||
|
name: 'Alice',
|
||||||
|
phoneNumber: '(202) 555-1000',
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: 'general',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
i18n,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
book.add('GroupNotification', () =>
|
||||||
|
stories.map(([title, propsArray]) => (
|
||||||
|
<>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
{propsArray.map((props, i) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div key={i} className="module-message-container">
|
||||||
|
<div className="module-inline-notification-wrapper">
|
||||||
|
<GroupNotification {...props} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
);
|
|
@ -1,10 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// import classNames from 'classnames';
|
|
||||||
import { compact, flatten } from 'lodash';
|
import { compact, flatten } from 'lodash';
|
||||||
|
|
||||||
import { ContactName } from './ContactName';
|
import { ContactName } from './ContactName';
|
||||||
import { Emojify } from './Emojify';
|
import { FullJSXType, Intl } from '../Intl';
|
||||||
import { Intl } from '../Intl';
|
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { LocalizerType } from '../../types/Util';
|
||||||
|
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
|
@ -13,16 +11,17 @@ interface Contact {
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
isMe?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Change {
|
interface Change {
|
||||||
type: 'add' | 'remove' | 'name' | 'general';
|
type: 'add' | 'remove' | 'name' | 'avatar' | 'general';
|
||||||
isMe: boolean;
|
|
||||||
newName?: string;
|
newName?: string;
|
||||||
contacts?: Array<Contact>;
|
contacts?: Array<Contact>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PropsData = {
|
export type PropsData = {
|
||||||
|
from: Contact;
|
||||||
changes: Array<Change>;
|
changes: Array<Change>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,17 +29,20 @@ type PropsHousekeeping = {
|
||||||
i18n: LocalizerType;
|
i18n: LocalizerType;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Props = PropsData & PropsHousekeeping;
|
export type Props = PropsData & PropsHousekeeping;
|
||||||
|
|
||||||
export class GroupNotification extends React.Component<Props> {
|
export class GroupNotification extends React.Component<Props> {
|
||||||
public renderChange(change: Change) {
|
public renderChange(change: Change, from: Contact) {
|
||||||
const { isMe, contacts, type, newName } = change;
|
const { contacts, type, newName } = change;
|
||||||
const { i18n } = this.props;
|
const { i18n } = this.props;
|
||||||
|
|
||||||
const people = compact(
|
const otherPeople = compact(
|
||||||
flatten(
|
(contacts || []).map(contact => {
|
||||||
(contacts || []).map((contact, index) => {
|
if (contact.isMe) {
|
||||||
const element = (
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<span
|
<span
|
||||||
key={`external-${contact.phoneNumber}`}
|
key={`external-${contact.phoneNumber}`}
|
||||||
className="module-group-notification__contact"
|
className="module-group-notification__contact"
|
||||||
|
@ -52,26 +54,57 @@ export class GroupNotification extends React.Component<Props> {
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
return [index > 0 ? ', ' : null, element];
|
|
||||||
})
|
})
|
||||||
|
);
|
||||||
|
const otherPeopleWithCommas: FullJSXType = compact(
|
||||||
|
flatten(
|
||||||
|
otherPeople.map((person, index) => [index > 0 ? ', ' : null, person])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const contactsIncludesMe = (contacts || []).length !== otherPeople.length;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'name':
|
case 'name':
|
||||||
return <Emojify text={i18n('titleIsNow', [newName || ''])} />;
|
return (
|
||||||
|
<Intl i18n={i18n} id="titleIsNow" components={[newName || '']} />
|
||||||
|
);
|
||||||
|
case 'avatar':
|
||||||
|
return <Intl i18n={i18n} id="updatedGroupAvatar" />;
|
||||||
case 'add':
|
case 'add':
|
||||||
if (!contacts || !contacts.length) {
|
if (!contacts || !contacts.length) {
|
||||||
throw new Error('Group update is missing contacts');
|
throw new Error('Group update is missing contacts');
|
||||||
}
|
}
|
||||||
|
|
||||||
const joinKey =
|
if (contacts.length === 1) {
|
||||||
contacts.length > 1 ? 'multipleJoinedTheGroup' : 'joinedTheGroup';
|
if (contactsIncludesMe) {
|
||||||
|
return <Intl i18n={i18n} id="youJoinedTheGroup" />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Intl
|
||||||
|
i18n={i18n}
|
||||||
|
id="joinedTheGroup"
|
||||||
|
components={[otherPeopleWithCommas]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return <Intl i18n={i18n} id={joinKey} components={[people]} />;
|
return (
|
||||||
|
<>
|
||||||
|
<Intl
|
||||||
|
i18n={i18n}
|
||||||
|
id="multipleJoinedTheGroup"
|
||||||
|
components={[otherPeopleWithCommas]}
|
||||||
|
/>
|
||||||
|
{contactsIncludesMe ? (
|
||||||
|
<div className="module-group-notification__change">
|
||||||
|
<Intl i18n={i18n} id="youJoinedTheGroup" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
case 'remove':
|
case 'remove':
|
||||||
if (isMe) {
|
if (from && from.isMe) {
|
||||||
return i18n('youLeftTheGroup');
|
return i18n('youLeftTheGroup');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,22 +115,49 @@ export class GroupNotification extends React.Component<Props> {
|
||||||
const leftKey =
|
const leftKey =
|
||||||
contacts.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup';
|
contacts.length > 1 ? 'multipleLeftTheGroup' : 'leftTheGroup';
|
||||||
|
|
||||||
return <Intl i18n={i18n} id={leftKey} components={[people]} />;
|
return (
|
||||||
|
<Intl i18n={i18n} id={leftKey} components={[otherPeopleWithCommas]} />
|
||||||
|
);
|
||||||
case 'general':
|
case 'general':
|
||||||
return i18n('updatedTheGroup');
|
return;
|
||||||
default:
|
default:
|
||||||
throw missingCaseError(type);
|
throw missingCaseError(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { changes } = this.props;
|
const { changes, i18n, from } = this.props;
|
||||||
|
|
||||||
|
// Leave messages are always from the person leaving, so we omit the fromLabel if
|
||||||
|
// the change is a 'leave.'
|
||||||
|
const isLeftOnly =
|
||||||
|
changes && changes.length === 1 && changes[0].type === 'remove';
|
||||||
|
|
||||||
|
const fromContact = (
|
||||||
|
<ContactName
|
||||||
|
phoneNumber={from.phoneNumber}
|
||||||
|
profileName={from.profileName}
|
||||||
|
name={from.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const fromLabel = from.isMe ? (
|
||||||
|
<Intl i18n={i18n} id="youUpdatedTheGroup" />
|
||||||
|
) : (
|
||||||
|
<Intl i18n={i18n} id="updatedTheGroup" components={[fromContact]} />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-group-notification">
|
<div className="module-group-notification">
|
||||||
|
{isLeftOnly ? null : (
|
||||||
|
<>
|
||||||
|
{fromLabel}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{(changes || []).map((change, index) => (
|
{(changes || []).map((change, index) => (
|
||||||
<div key={index} className="module-group-notification__change">
|
<div key={index} className="module-group-notification__change">
|
||||||
{this.renderChange(change)}
|
{this.renderChange(change, from)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -106,7 +106,7 @@ const stories: Array<MessageStory> = [
|
||||||
makeDataProps: () => ({
|
makeDataProps: () => ({
|
||||||
...baseDataProps,
|
...baseDataProps,
|
||||||
direction: 'incoming',
|
direction: 'incoming',
|
||||||
authorColor: 'gray',
|
authorColor: 'grey',
|
||||||
text:
|
text:
|
||||||
'Hello there from the new world! And this is multiple lines of text. Lines and lines and lines.',
|
'Hello there from the new world! And this is multiple lines of text. Lines and lines and lines.',
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -5,7 +5,7 @@ import moment from 'moment';
|
||||||
import { Avatar } from '../Avatar';
|
import { Avatar } from '../Avatar';
|
||||||
import { ContactName } from './ContactName';
|
import { ContactName } from './ContactName';
|
||||||
import { Message, Props as MessageProps } from './Message';
|
import { Message, Props as MessageProps } from './Message';
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { ColorType, LocalizerType } from '../../types/Util';
|
||||||
|
|
||||||
interface Contact {
|
interface Contact {
|
||||||
status: string;
|
status: string;
|
||||||
|
@ -13,7 +13,7 @@ interface Contact {
|
||||||
name?: string;
|
name?: string;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
color: string;
|
color: ColorType;
|
||||||
isOutgoingKeyError: boolean;
|
isOutgoingKeyError: boolean;
|
||||||
isUnidentifiedDelivery: boolean;
|
isUnidentifiedDelivery: boolean;
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ import classNames from 'classnames';
|
||||||
import { TypingAnimation } from './TypingAnimation';
|
import { TypingAnimation } from './TypingAnimation';
|
||||||
import { Avatar } from '../Avatar';
|
import { Avatar } from '../Avatar';
|
||||||
|
|
||||||
import { LocalizerType } from '../../types/Util';
|
import { ColorType, LocalizerType } from '../../types/Util';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
avatarPath?: string;
|
avatarPath?: string;
|
||||||
color: string;
|
color: ColorType;
|
||||||
name?: string;
|
name?: string;
|
||||||
phoneNumber: string;
|
phoneNumber: string;
|
||||||
profileName?: string;
|
profileName?: string;
|
||||||
|
|
|
@ -90,6 +90,7 @@ export type MessageType = {
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
errors?: Array<Error>;
|
errors?: Array<Error>;
|
||||||
|
group_update?: any;
|
||||||
|
|
||||||
// No need to go beyond this; unused at this stage, since this goes into
|
// No need to go beyond this; unused at this stage, since this goes into
|
||||||
// a reducer still in plain JavaScript and comes out well-formed
|
// a reducer still in plain JavaScript and comes out well-formed
|
||||||
|
@ -581,6 +582,11 @@ function hasMessageHeightChanged(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupUpdateChanged = message.group_update !== previous.group_update;
|
||||||
|
if (groupUpdateChanged) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const stickerPendingChanged =
|
const stickerPendingChanged =
|
||||||
message.sticker &&
|
message.sticker &&
|
||||||
message.sticker.data &&
|
message.sticker.data &&
|
||||||
|
|
30
ts/storybook/Fixtures.ts
Normal file
30
ts/storybook/Fixtures.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import gif from '../../fixtures/giphy-GVNvOUpeYmI7e.gif';
|
||||||
|
// @ts-ignore
|
||||||
|
import png from '../../fixtures/freepngs-2cd43b_bed7d1327e88454487397574d87b64dc_mv2.png';
|
||||||
|
// @ts-ignore
|
||||||
|
import landscapeGreen from '../../fixtures/1000x50-green.jpeg';
|
||||||
|
// @ts-ignore
|
||||||
|
import landscapePurple from '../../fixtures/200x50-purple.png';
|
||||||
|
|
||||||
|
function makeObjectUrl(data: ArrayBuffer, contentType: string): string {
|
||||||
|
const blob = new Blob([data], {
|
||||||
|
type: contentType,
|
||||||
|
});
|
||||||
|
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 320x240
|
||||||
|
export const gifObjectUrl = makeObjectUrl(gif, 'image/gif');
|
||||||
|
|
||||||
|
// 800×1200
|
||||||
|
export const pngObjectUrl = makeObjectUrl(png, 'image/png');
|
||||||
|
export const landscapeGreenObjectUrl = makeObjectUrl(
|
||||||
|
landscapeGreen,
|
||||||
|
'image/jpeg'
|
||||||
|
);
|
||||||
|
export const landscapePurpleObjectUrl = makeObjectUrl(
|
||||||
|
landscapePurple,
|
||||||
|
'image/png'
|
||||||
|
);
|
|
@ -6,14 +6,17 @@ export type RenderTextCallbackType = (options: {
|
||||||
export type LocalizerType = (key: string, values?: Array<string>) => string;
|
export type LocalizerType = (key: string, values?: Array<string>) => string;
|
||||||
|
|
||||||
export type ColorType =
|
export type ColorType =
|
||||||
| 'gray'
|
| 'red'
|
||||||
| 'blue'
|
|
||||||
| 'cyan'
|
|
||||||
| 'deep_orange'
|
| 'deep_orange'
|
||||||
| 'green'
|
| 'brown'
|
||||||
| 'indigo'
|
|
||||||
| 'pink'
|
| 'pink'
|
||||||
| 'purple'
|
| 'purple'
|
||||||
| 'red'
|
| 'indigo'
|
||||||
|
| 'blue'
|
||||||
| 'teal'
|
| 'teal'
|
||||||
| 'ultramarine';
|
| 'green'
|
||||||
|
| 'light_green'
|
||||||
|
| 'blue_grey'
|
||||||
|
| 'grey'
|
||||||
|
| 'ultramarine'
|
||||||
|
| 'signal-blue';
|
||||||
|
|
|
@ -11508,7 +11508,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/conversation/ConversationHeader.tsx",
|
"path": "ts/components/conversation/ConversationHeader.tsx",
|
||||||
"line": " this.menuTriggerRef = React.createRef();",
|
"line": " this.menuTriggerRef = React.createRef();",
|
||||||
"lineNumber": 60,
|
"lineNumber": 67,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-07-31T00:19:18.696Z",
|
"updated": "2019-07-31T00:19:18.696Z",
|
||||||
"reasonDetail": "Used to reference popup menu"
|
"reasonDetail": "Used to reference popup menu"
|
||||||
|
|
Loading…
Reference in a new issue