Emojification now all done with react via a few new components
Three locations were changed: 1. a group update, which lists a set of contacts 2. the contact name in the left pane 3. the conversation title Three new components were added to window.Signal.Components to support these scenarios, respectively: 1. Emojify 2. ContactName 3. ConversationTitle Note that there are a number of other places in the app that should be emojified, but never have been before. Essentially any place that a contact name might be shown. A non-exhaustive list: - Show group members - Show safety number - Verified change notification - Disappearing timer change notification - Contact verification notification - Quote contact name
This commit is contained in:
parent
d9e5338dff
commit
548c8e69cf
10 changed files with 152 additions and 73 deletions
|
@ -135,20 +135,6 @@
|
||||||
<script type='text/x-tmpl-mustache' id='hint'>
|
<script type='text/x-tmpl-mustache' id='hint'>
|
||||||
<p> {{ content }}</p>
|
<p> {{ content }}</p>
|
||||||
</script>
|
</script>
|
||||||
<script type='text/x-tmpl-mustache' id='conversation-title'>
|
|
||||||
{{ #name }}
|
|
||||||
<span class='conversation-name' dir='auto'>{{ name }}</span>
|
|
||||||
{{ /name }}
|
|
||||||
{{ #number }}
|
|
||||||
<span class='conversation-number'>{{ number }}</span>
|
|
||||||
{{ /number }}
|
|
||||||
{{ #profileName }}
|
|
||||||
<span class='profileName'>{{ profileName }} </span>
|
|
||||||
{{ /profileName }}
|
|
||||||
{{ #isVerified }}
|
|
||||||
<span class='verified'><span class='verified-icon'></span> {{ verified }}</span>
|
|
||||||
{{ /isVerified }}
|
|
||||||
</script>
|
|
||||||
<script type='text/x-tmpl-mustache' id='conversation'>
|
<script type='text/x-tmpl-mustache' id='conversation'>
|
||||||
<div class='conversation-header {{ avatar.color }}'>
|
<div class='conversation-header {{ avatar.color }}'>
|
||||||
<div class='header-buttons left'>
|
<div class='header-buttons left'>
|
||||||
|
@ -372,7 +358,7 @@
|
||||||
{{> avatar }}
|
{{> avatar }}
|
||||||
<div class='contact-details'>
|
<div class='contact-details'>
|
||||||
<span class='last-timestamp' data-timestamp='{{ last_message_timestamp }}' dir='auto' > </span>
|
<span class='last-timestamp' data-timestamp='{{ last_message_timestamp }}' dir='auto' > </span>
|
||||||
{{> contact_name_and_number }}
|
<h3 class='name' dir='auto'></h3>
|
||||||
{{ #unreadCount }}
|
{{ #unreadCount }}
|
||||||
<span class='unread-count'>{{ unreadCount }}</span>
|
<span class='unread-count'>{{ unreadCount }}</span>
|
||||||
{{ /unreadCount }}
|
{{ /unreadCount }}
|
||||||
|
|
|
@ -16,9 +16,14 @@ const Util = require('../ts/util');
|
||||||
const {
|
const {
|
||||||
ContactDetail,
|
ContactDetail,
|
||||||
} = require('../ts/components/conversation/ContactDetail');
|
} = require('../ts/components/conversation/ContactDetail');
|
||||||
|
const { ContactName } = require('../ts/components/conversation/ContactName');
|
||||||
|
const {
|
||||||
|
ConversationTitle,
|
||||||
|
} = require('../ts/components/conversation/ConversationTitle');
|
||||||
const {
|
const {
|
||||||
EmbeddedContact,
|
EmbeddedContact,
|
||||||
} = require('../ts/components/conversation/EmbeddedContact');
|
} = require('../ts/components/conversation/EmbeddedContact');
|
||||||
|
const { Emojify } = require('../ts/components/conversation/Emojify');
|
||||||
const { Lightbox } = require('../ts/components/Lightbox');
|
const { Lightbox } = require('../ts/components/Lightbox');
|
||||||
const { LightboxGallery } = require('../ts/components/LightboxGallery');
|
const { LightboxGallery } = require('../ts/components/LightboxGallery');
|
||||||
const {
|
const {
|
||||||
|
@ -56,7 +61,10 @@ exports.setup = (options = {}) => {
|
||||||
|
|
||||||
const Components = {
|
const Components = {
|
||||||
ContactDetail,
|
ContactDetail,
|
||||||
|
ContactName,
|
||||||
|
ConversationTitle,
|
||||||
EmbeddedContact,
|
EmbeddedContact,
|
||||||
|
Emojify,
|
||||||
Lightbox,
|
Lightbox,
|
||||||
LightboxGallery,
|
LightboxGallery,
|
||||||
MediaGallery,
|
MediaGallery,
|
||||||
|
|
|
@ -54,6 +54,18 @@
|
||||||
this.$el.trigger('select', this.model);
|
this.$el.trigger('select', this.model);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
if (this.nameView) {
|
||||||
|
this.nameView.remove();
|
||||||
|
this.nameView = null;
|
||||||
|
}
|
||||||
|
if (this.bodyView) {
|
||||||
|
this.bodyView.remove();
|
||||||
|
this.bodyView = null;
|
||||||
|
}
|
||||||
|
Backbone.View.prototype.remove.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const lastMessage = this.model.get('lastMessage');
|
const lastMessage = this.model.get('lastMessage');
|
||||||
|
|
||||||
|
@ -61,12 +73,10 @@
|
||||||
Mustache.render(
|
Mustache.render(
|
||||||
_.result(this, 'template', ''),
|
_.result(this, 'template', ''),
|
||||||
{
|
{
|
||||||
title: this.model.getTitle(),
|
|
||||||
last_message: Boolean(lastMessage),
|
last_message: Boolean(lastMessage),
|
||||||
last_message_timestamp: this.model.get('timestamp'),
|
last_message_timestamp: this.model.get('timestamp'),
|
||||||
number: this.model.getNumber(),
|
number: this.model.getNumber(),
|
||||||
avatar: this.model.getAvatar(),
|
avatar: this.model.getAvatar(),
|
||||||
profileName: this.model.getProfileName(),
|
|
||||||
unreadCount: this.model.get('unreadCount'),
|
unreadCount: this.model.get('unreadCount'),
|
||||||
},
|
},
|
||||||
this.render_partials()
|
this.render_partials()
|
||||||
|
@ -75,6 +85,20 @@
|
||||||
this.timeStampView.setElement(this.$('.last-timestamp'));
|
this.timeStampView.setElement(this.$('.last-timestamp'));
|
||||||
this.timeStampView.update();
|
this.timeStampView.update();
|
||||||
|
|
||||||
|
if (this.nameView) {
|
||||||
|
this.nameView.remove();
|
||||||
|
this.nameView = null;
|
||||||
|
}
|
||||||
|
this.nameView = new Whisper.ReactWrapperView({
|
||||||
|
className: 'name-wrapper',
|
||||||
|
Component: window.Signal.Components.ContactName,
|
||||||
|
props: {
|
||||||
|
phoneNumber: this.model.getNumber(),
|
||||||
|
name: this.model.getName(),
|
||||||
|
profileName: this.model.getProfileName(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.$('.name').append(this.nameView.el);
|
||||||
|
|
||||||
if (lastMessage) {
|
if (lastMessage) {
|
||||||
if (this.bodyView) {
|
if (this.bodyView) {
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
/* global $: false */
|
/* global $: false */
|
||||||
/* global _: false */
|
/* global _: false */
|
||||||
/* global emoji: false */
|
|
||||||
/* global emoji_util: false */
|
|
||||||
/* global emojiData: false */
|
/* global emojiData: false */
|
||||||
/* global EmojiPanel: false */
|
/* global EmojiPanel: false */
|
||||||
/* global moment: false */
|
/* global moment: false */
|
||||||
|
|
||||||
/* global extension: false */
|
/* global extension: false */
|
||||||
/* global i18n: false */
|
/* global i18n: false */
|
||||||
/* global Signal: false */
|
/* global Signal: false */
|
||||||
|
@ -75,22 +72,6 @@
|
||||||
className: 'conversation-loading-screen',
|
className: 'conversation-loading-screen',
|
||||||
});
|
});
|
||||||
|
|
||||||
Whisper.ConversationTitleView = Whisper.View.extend({
|
|
||||||
templateName: 'conversation-title',
|
|
||||||
initialize() {
|
|
||||||
this.listenTo(this.model, 'change', this.render);
|
|
||||||
},
|
|
||||||
render_attributes() {
|
|
||||||
return {
|
|
||||||
isVerified: this.model.isVerified(),
|
|
||||||
verified: i18n('verified'),
|
|
||||||
name: this.model.getName(),
|
|
||||||
number: this.model.getNumber(),
|
|
||||||
profileName: this.model.getProfileName(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Whisper.ConversationView = Whisper.View.extend({
|
Whisper.ConversationView = Whisper.View.extend({
|
||||||
className() {
|
className() {
|
||||||
return ['conversation', this.model.get('type')].join(' ');
|
return ['conversation', this.model.get('type')].join(' ');
|
||||||
|
@ -177,18 +158,27 @@
|
||||||
model: this.model,
|
model: this.model,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
this.window = options.window;
|
this.window = options.window;
|
||||||
this.fileInput = new Whisper.FileInputView({
|
this.fileInput = new Whisper.FileInputView({
|
||||||
el: this.$('form.send'),
|
el: this.$('form.send'),
|
||||||
window: this.window,
|
window: this.window,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.titleView = new Whisper.ConversationTitleView({
|
const getTitleProps = model => ({
|
||||||
el: this.$('.conversation-title'),
|
isVerified: model.isVerified(),
|
||||||
model: this.model,
|
name: model.getName(),
|
||||||
|
phoneNumber: model.getNumber(),
|
||||||
|
profileName: model.getProfileName(),
|
||||||
});
|
});
|
||||||
this.titleView.render();
|
this.titleView = new Whisper.ReactWrapperView({
|
||||||
|
className: 'title-wrapper',
|
||||||
|
Component: window.Signal.Components.ConversationTitle,
|
||||||
|
props: getTitleProps(this.model),
|
||||||
|
});
|
||||||
|
this.listenTo(this.model, 'change', () =>
|
||||||
|
this.titleView.update(getTitleProps(this.model))
|
||||||
|
);
|
||||||
|
this.$('.conversation-title').prepend(this.titleView.el);
|
||||||
|
|
||||||
this.view = new Whisper.MessageListView({
|
this.view = new Whisper.MessageListView({
|
||||||
collection: this.model.messageCollection,
|
collection: this.model.messageCollection,
|
||||||
|
@ -1358,17 +1348,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
replace_colons(str) {
|
|
||||||
return str.replace(emoji.rx_colons, m => {
|
|
||||||
const idx = m.substr(1, m.length - 2);
|
|
||||||
const val = emoji.map.colons[idx];
|
|
||||||
if (val) {
|
|
||||||
return emoji.data[val][0][0];
|
|
||||||
}
|
|
||||||
return m;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateColor(model, color) {
|
updateColor(model, color) {
|
||||||
const header = this.$('.conversation-header');
|
const header = this.$('.conversation-header');
|
||||||
header.removeClass(Whisper.Conversation.COLORS);
|
header.removeClass(Whisper.Conversation.COLORS);
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
/* global i18n: false */
|
/* global i18n: false */
|
||||||
/* global textsecure: false */
|
/* global textsecure: false */
|
||||||
/* global _: false */
|
/* global _: false */
|
||||||
/* global emoji_util: false */
|
|
||||||
/* global Mustache: false */
|
/* global Mustache: false */
|
||||||
/* global $: false */
|
/* global $: false */
|
||||||
/* global storage: false */
|
/* global storage: false */
|
||||||
|
@ -279,24 +278,30 @@
|
||||||
if (this.avatarView) {
|
if (this.avatarView) {
|
||||||
this.avatarView.remove();
|
this.avatarView.remove();
|
||||||
}
|
}
|
||||||
|
if (this.bodyView) {
|
||||||
|
this.bodyView.remove();
|
||||||
|
}
|
||||||
|
if (this.contactView) {
|
||||||
|
this.contactView.remove();
|
||||||
|
}
|
||||||
|
if (this.controlView) {
|
||||||
|
this.controlView.remove();
|
||||||
|
}
|
||||||
if (this.errorIconView) {
|
if (this.errorIconView) {
|
||||||
this.errorIconView.remove();
|
this.errorIconView.remove();
|
||||||
}
|
}
|
||||||
if (this.networkErrorView) {
|
if (this.networkErrorView) {
|
||||||
this.networkErrorView.remove();
|
this.networkErrorView.remove();
|
||||||
}
|
}
|
||||||
|
if (this.quoteView) {
|
||||||
|
this.quoteView.remove();
|
||||||
|
}
|
||||||
if (this.someFailedView) {
|
if (this.someFailedView) {
|
||||||
this.someFailedView.remove();
|
this.someFailedView.remove();
|
||||||
}
|
}
|
||||||
if (this.timeStampView) {
|
if (this.timeStampView) {
|
||||||
this.timeStampView.remove();
|
this.timeStampView.remove();
|
||||||
}
|
}
|
||||||
if (this.quoteView) {
|
|
||||||
this.quoteView.remove();
|
|
||||||
}
|
|
||||||
if (this.contactView) {
|
|
||||||
this.contactView.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: We have to do this in the background (`then` instead of `await`)
|
// NOTE: We have to do this in the background (`then` instead of `await`)
|
||||||
// as our tests rely on `onUnload` synchronously removing the view from
|
// as our tests rely on `onUnload` synchronously removing the view from
|
||||||
|
@ -406,8 +411,20 @@
|
||||||
renderControl() {
|
renderControl() {
|
||||||
if (this.model.isEndSession() || this.model.isGroupUpdate()) {
|
if (this.model.isEndSession() || this.model.isGroupUpdate()) {
|
||||||
this.$el.addClass('control');
|
this.$el.addClass('control');
|
||||||
const content = this.$('.content');
|
|
||||||
content.text(this.model.getDescription());
|
if (this.controlView) {
|
||||||
|
this.controlView.remove();
|
||||||
|
this.controlView = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controlView = new Whisper.ReactWrapperView({
|
||||||
|
className: 'content-wrapper',
|
||||||
|
Component: window.Signal.Components.Emojify,
|
||||||
|
props: {
|
||||||
|
text: this.model.getDescription(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.$('.content').prepend(this.controlView.el);
|
||||||
} else {
|
} else {
|
||||||
this.$el.removeClass('control');
|
this.$el.removeClass('control');
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,6 +292,7 @@ $avatar-size: 44px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// the old way
|
||||||
.profileName {
|
.profileName {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
|
|
||||||
|
@ -299,6 +300,10 @@ $avatar-size: 44px;
|
||||||
content: '~';
|
content: '~';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// the new way
|
||||||
|
.profile-name {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
|
||||||
.conversation-list-item {
|
.conversation-list-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -96,17 +96,6 @@
|
||||||
<script type='text/x-tmpl-mustache' id='hint'>
|
<script type='text/x-tmpl-mustache' id='hint'>
|
||||||
<p> {{ content }}</p>
|
<p> {{ content }}</p>
|
||||||
</script>
|
</script>
|
||||||
<script type='text/x-tmpl-mustache' id='conversation-title'>
|
|
||||||
{{ #name }}
|
|
||||||
<span class='conversation-name' dir='auto'>{{ name }}</span>
|
|
||||||
{{ /name }}
|
|
||||||
{{ #number }}
|
|
||||||
<span class='conversation-number'>{{ number }}</span>
|
|
||||||
{{ /number }}
|
|
||||||
{{ #isVerified }}
|
|
||||||
<span class='verified'><span class='verified-icon'></span> {{ verified }}</span>
|
|
||||||
{{ /isVerified }}
|
|
||||||
</script>
|
|
||||||
<script type='text/x-tmpl-mustache' id='conversation'>
|
<script type='text/x-tmpl-mustache' id='conversation'>
|
||||||
<div class='conversation-header {{ avatar.color }}'>
|
<div class='conversation-header {{ avatar.color }}'>
|
||||||
<div class='header-buttons left'>
|
<div class='header-buttons left'>
|
||||||
|
|
29
ts/components/conversation/ContactName.tsx
Normal file
29
ts/components/conversation/ContactName.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Emojify } from './Emojify';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
phoneNumber: string;
|
||||||
|
name?: string;
|
||||||
|
profileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ContactName extends React.Component<Props, {}> {
|
||||||
|
public render() {
|
||||||
|
const { phoneNumber, name, profileName } = this.props;
|
||||||
|
|
||||||
|
const title = name ? name : phoneNumber;
|
||||||
|
const profileElement =
|
||||||
|
profileName && !name ? (
|
||||||
|
<span className="profile-name">
|
||||||
|
~<Emojify text={profileName} />
|
||||||
|
</span>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Emojify text={title} /> {profileElement}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
42
ts/components/conversation/ConversationTitle.tsx
Normal file
42
ts/components/conversation/ConversationTitle.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Emojify } from './Emojify';
|
||||||
|
import { Localizer } from '../../types/Util';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
i18n: Localizer;
|
||||||
|
isVerified: boolean;
|
||||||
|
name?: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
profileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConversationTitle extends React.Component<Props, {}> {
|
||||||
|
public render() {
|
||||||
|
const { name, phoneNumber, i18n, profileName, isVerified } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className="conversation-title">
|
||||||
|
{name ? (
|
||||||
|
<span className="conversation-name" dir="auto">
|
||||||
|
<Emojify text={name} />
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{phoneNumber ? (
|
||||||
|
<span className="conversation-number">{phoneNumber}</span>
|
||||||
|
) : null}{' '}
|
||||||
|
{profileName ? (
|
||||||
|
<span className="profileName">
|
||||||
|
<Emojify text={profileName} />
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{isVerified ? (
|
||||||
|
<span className="verified">
|
||||||
|
<span className="verified-icon" />
|
||||||
|
{i18n('verified')}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -165,7 +165,7 @@ const COLORS = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const CONTACTS = COLORS.map((color, index) => {
|
const CONTACTS = COLORS.map((color, index) => {
|
||||||
const title = `${sample(['Mr.', 'Mrs.', 'Ms.', 'Unknown'])} ${color}`;
|
const title = `${sample(['Mr.', 'Mrs.', 'Ms.', 'Unknown'])} ${color} 🔥`;
|
||||||
const key = sample(['name', 'profileName']) as string;
|
const key = sample(['name', 'profileName']) as string;
|
||||||
const id = `+1202555${padStart(index.toString(), 4, '0')}`;
|
const id = `+1202555${padStart(index.toString(), 4, '0')}`;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue