Skip to content

Commit

Permalink
Emojification now all done with react via a few new components
Browse files Browse the repository at this point in the history
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
  • Loading branch information
scottnonnenberg-signal committed May 23, 2018
1 parent d9e5338 commit 548c8e6
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 73 deletions.
16 changes: 1 addition & 15 deletions background.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,6 @@ <h3>{{ welcomeToSignal }}</h3>
<script type='text/x-tmpl-mustache' id='hint'>
<p> {{ content }}</p>
</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'>
<div class='conversation-header {{ avatar.color }}'>
<div class='header-buttons left'>
Expand Down Expand Up @@ -372,7 +358,7 @@ <h3 class='name' dir='auto'>
{{> avatar }}
<div class='contact-details'>
<span class='last-timestamp' data-timestamp='{{ last_message_timestamp }}' dir='auto' > </span>
{{> contact_name_and_number }}
<h3 class='name' dir='auto'></h3>
{{ #unreadCount }}
<span class='unread-count'>{{ unreadCount }}</span>
{{ /unreadCount }}
Expand Down
8 changes: 8 additions & 0 deletions js/signal.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ const Util = require('../ts/util');
const {
ContactDetail,
} = require('../ts/components/conversation/ContactDetail');
const { ContactName } = require('../ts/components/conversation/ContactName');
const {
ConversationTitle,
} = require('../ts/components/conversation/ConversationTitle');
const {
EmbeddedContact,
} = require('../ts/components/conversation/EmbeddedContact');
const { Emojify } = require('../ts/components/conversation/Emojify');
const { Lightbox } = require('../ts/components/Lightbox');
const { LightboxGallery } = require('../ts/components/LightboxGallery');
const {
Expand Down Expand Up @@ -56,7 +61,10 @@ exports.setup = (options = {}) => {

const Components = {
ContactDetail,
ContactName,
ConversationTitle,
EmbeddedContact,
Emojify,
Lightbox,
LightboxGallery,
MediaGallery,
Expand Down
28 changes: 26 additions & 2 deletions js/views/conversation_list_item_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,29 @@
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() {
const lastMessage = this.model.get('lastMessage');

this.$el.html(
Mustache.render(
_.result(this, 'template', ''),
{
title: this.model.getTitle(),
last_message: Boolean(lastMessage),
last_message_timestamp: this.model.get('timestamp'),
number: this.model.getNumber(),
avatar: this.model.getAvatar(),
profileName: this.model.getProfileName(),
unreadCount: this.model.get('unreadCount'),
},
this.render_partials()
Expand All @@ -75,6 +85,20 @@
this.timeStampView.setElement(this.$('.last-timestamp'));
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 (this.bodyView) {
Expand Down
49 changes: 14 additions & 35 deletions js/views/conversation_view.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
/* global $: false */
/* global _: false */
/* global emoji: false */
/* global emoji_util: false */
/* global emojiData: false */
/* global EmojiPanel: false */
/* global moment: false */

/* global extension: false */
/* global i18n: false */
/* global Signal: false */
Expand Down Expand Up @@ -75,22 +72,6 @@
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({
className() {
return ['conversation', this.model.get('type')].join(' ');
Expand Down Expand Up @@ -177,18 +158,27 @@
model: this.model,
});


this.window = options.window;
this.fileInput = new Whisper.FileInputView({
el: this.$('form.send'),
window: this.window,
});

this.titleView = new Whisper.ConversationTitleView({
el: this.$('.conversation-title'),
model: this.model,
const getTitleProps = model => ({
isVerified: model.isVerified(),
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({
collection: this.model.messageCollection,
Expand Down Expand Up @@ -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) {
const header = this.$('.conversation-header');
header.removeClass(Whisper.Conversation.COLORS);
Expand Down
35 changes: 26 additions & 9 deletions js/views/message_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
/* global i18n: false */
/* global textsecure: false */
/* global _: false */
/* global emoji_util: false */
/* global Mustache: false */
/* global $: false */
/* global storage: false */
Expand Down Expand Up @@ -279,24 +278,30 @@
if (this.avatarView) {
this.avatarView.remove();
}
if (this.bodyView) {
this.bodyView.remove();
}
if (this.contactView) {
this.contactView.remove();
}
if (this.controlView) {
this.controlView.remove();
}
if (this.errorIconView) {
this.errorIconView.remove();
}
if (this.networkErrorView) {
this.networkErrorView.remove();
}
if (this.quoteView) {
this.quoteView.remove();
}
if (this.someFailedView) {
this.someFailedView.remove();
}
if (this.timeStampView) {
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`)
// as our tests rely on `onUnload` synchronously removing the view from
Expand Down Expand Up @@ -406,8 +411,20 @@
renderControl() {
if (this.model.isEndSession() || this.model.isGroupUpdate()) {
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 {
this.$el.removeClass('control');
}
Expand Down
5 changes: 5 additions & 0 deletions stylesheets/_global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,18 @@ $avatar-size: 44px;
}
}
}
// the old way
.profileName {
font-size: smaller;

&:before {
content: '~';
}
}
// the new way
.profile-name {
font-size: smaller;
}

.conversation-list-item {
cursor: pointer;
Expand Down
11 changes: 0 additions & 11 deletions test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,6 @@ <h3>{{ welcomeToSignal }}</h3>
<script type='text/x-tmpl-mustache' id='hint'>
<p> {{ content }}</p>
</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'>
<div class='conversation-header {{ avatar.color }}'>
<div class='header-buttons left'>
Expand Down
29 changes: 29 additions & 0 deletions ts/components/conversation/ContactName.tsx
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions ts/components/conversation/ConversationTitle.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
}
2 changes: 1 addition & 1 deletion ts/styleguide/StyleGuideUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ const COLORS = [
];

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 id = `+1202555${padStart(index.toString(), 4, '0')}`;

Expand Down

0 comments on commit 548c8e6

Please sign in to comment.