Merge pull request #2995 from matrix-org/matthew/twemoji

Replace emojione with twemoji + emojibase
This commit is contained in:
Matthew Hodgson 2019-05-21 11:32:53 +01:00 committed by GitHub
commit 30a485bddd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 150 additions and 413 deletions

View file

@ -65,7 +65,8 @@
"classnames": "^2.1.2", "classnames": "^2.1.2",
"commonmark": "^0.28.1", "commonmark": "^0.28.1",
"counterpart": "^0.18.0", "counterpart": "^0.18.0",
"emojione": "2.2.7", "emojibase-data": "^4.0.0",
"emojibase-regex": "^3.0.0",
"file-saver": "^1.3.3", "file-saver": "^1.3.3",
"filesize": "3.5.6", "filesize": "3.5.6",
"flux": "2.1.1", "flux": "2.1.1",

View file

@ -32,6 +32,10 @@ body {
margin: 0px; margin: 0px;
} }
pre, code {
font-family: $monospace-font-family;
}
.error, .warning { .error, .warning {
color: $warning-color; color: $warning-color;
} }
@ -445,15 +449,6 @@ textarea {
background-color: $primary-bg-color; background-color: $primary-bg-color;
} }
.mx_emojione {
height: 1em;
vertical-align: middle;
}
.mx_emojione_selected {
background-color: $accent-color;
}
::-moz-selection { ::-moz-selection {
background-color: $accent-color; background-color: $accent-color;
color: $selection-fg-color; color: $selection-fg-color;

View file

@ -116,7 +116,7 @@ limitations under the License.
/* HACK to override line-height which is already marked important elsewhere */ /* HACK to override line-height which is already marked important elsewhere */
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji { .mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
font-size: 48px ! important; font-size: 48px ! important;
line-height: 48px ! important; line-height: 52px ! important;
} }
/* this is used for the tile for the event which is selected via the URL. /* this is used for the tile for the event which is selected via the URL.
@ -157,8 +157,7 @@ limitations under the License.
} }
.mx_EventTile_sending .mx_UserPill, .mx_EventTile_sending .mx_UserPill,
.mx_EventTile_sending .mx_RoomPill, .mx_EventTile_sending .mx_RoomPill {
.mx_EventTile_sending .mx_emojione {
opacity: 0.5; opacity: 0.5;
} }
@ -420,6 +419,7 @@ limitations under the License.
.mx_EventTile_content .markdown-body { .mx_EventTile_content .markdown-body {
pre, code { pre, code {
font-family: $monospace-font-family ! important;
// deliberate constants as we're behind an invert filter // deliberate constants as we're behind an invert filter
color: #333; color: #333;
} }

Binary file not shown.

View file

@ -51,3 +51,13 @@
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
} }
/* a COLR/CPAL version of Twemoji used for consistent cross-browser emoji
* taken from https://github.com/mozilla/twemoji-colr
* using the fix from https://github.com/mozilla/twemoji-colr/issues/50 to
* work on macOS
*/
@font-face {
font-family: "Twemoji Mozilla";
src: url('$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla.woff2') format('woff2');
}

View file

@ -1,10 +1,15 @@
// XXX: check this? // XXX: check this?
/* Nunito lacks combining diacritics, so these will fall through /* Nunito lacks combining diacritics, so these will fall through
to the next font. Helevetica's diacritics however do not combine to the next font. Helevetica's diacritics however do not combine
nicely with Open Sans (on OSX, at least) and result in a huge nicely (on OSX, at least) and result in a huge horizontal mess.
horizontal mess. Arial empirically gets it right, hence prioritising Arial empirically gets it right, hence prioritising Arial here. */
Arial here. */ /* We fall through to Twemoji for emoji rather than falling through
$font-family: 'Nunito', Arial, Helvetica, Sans-Serif; to native Emoji fonts (if any) to ensure cross-browser consistency */
$font-family: Nunito, 'Twemoji Mozilla', Arial, Helvetica, Sans-Serif;
// XXX: In theory this should be Fira, but it's a bit ugly.
// TODO: make it consistent cross-browser
$monospace-font-family: Consolas, 'Liberation Mono', Courier, 'Twemoji Mozilla', monospace;
// unified palette // unified palette
// try to use these colors when possible // try to use these colors when possible

View file

@ -1,28 +1,29 @@
#!/usr/bin/env node #!/usr/bin/env node
const EMOJI_DATA = require('emojione/emoji.json');
const EMOJI_SUPPORTED = Object.keys(require('emojione').emojioneList); // This generates src/stripped-emoji.json as used by the EmojiProvider autocomplete
// provider.
const EMOJIBASE = require('emojibase-data/en/compact.json');
const fs = require('fs'); const fs = require('fs');
const output = Object.keys(EMOJI_DATA).map( const output = EMOJIBASE.map(
(key) => { (datum) => {
const datum = EMOJI_DATA[key];
const newDatum = { const newDatum = {
name: datum.name, name: datum.annotation,
shortname: datum.shortname, shortname: `:${datum.shortcodes[0]}:`,
category: datum.category, category: datum.group,
emoji_order: datum.emoji_order, emoji_order: datum.order,
}; };
if (datum.aliases.length > 0) { if (datum.shortcodes.length > 1) {
newDatum.aliases = datum.aliases; newDatum.aliases = datum.shortcodes.slice(1).map(s => `:${s}:`);
} }
if (datum.aliases_ascii.length > 0) { if (datum.emoticon) {
newDatum.aliases_ascii = datum.aliases_ascii; newDatum.aliases_ascii = [ datum.emoticon ];
} }
return newDatum; return newDatum;
} }
).filter((datum) => { );
return EMOJI_SUPPORTED.includes(datum.shortname);
});
// Write to a file in src. Changes should be checked into git. This file is copied by // Write to a file in src. Changes should be checked into git. This file is copied by
// babel using --copy-files // babel using --copy-files

View file

@ -27,22 +27,18 @@ import linkifyMatrix from './linkify-matrix';
import _linkifyElement from 'linkifyjs/element'; import _linkifyElement from 'linkifyjs/element';
import _linkifyString from 'linkifyjs/string'; import _linkifyString from 'linkifyjs/string';
import escape from 'lodash/escape'; import escape from 'lodash/escape';
import emojione from 'emojione';
import classNames from 'classnames'; import classNames from 'classnames';
import MatrixClientPeg from './MatrixClientPeg'; import MatrixClientPeg from './MatrixClientPeg';
import url from 'url'; import url from 'url';
linkifyMatrix(linkify); import EMOJIBASE from 'emojibase-data/en/compact.json';
import EMOJIBASE_REGEX from 'emojibase-regex';
emojione.imagePathSVG = 'emojione/svg/'; linkifyMatrix(linkify);
// Store PNG path for displaying many flags at once (for increased performance over SVG)
emojione.imagePathPNG = 'emojione/png/';
// Use SVGs for emojis
emojione.imageType = 'svg';
// Anything outside the basic multilingual plane will be a surrogate pair // Anything outside the basic multilingual plane will be a surrogate pair
const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/; const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/;
// And there a bunch more symbol characters that emojione has within the // And there a bunch more symbol characters that emojibase has within the
// BMP, so this includes the ranges from 'letterlike symbols' to // BMP, so this includes the ranges from 'letterlike symbols' to
// 'miscellaneous symbols and arrows' which should catch all of them // 'miscellaneous symbols and arrows' which should catch all of them
// (with plenty of false positives, but that's OK) // (with plenty of false positives, but that's OK)
@ -54,15 +50,15 @@ const ZWJ_REGEX = new RegExp("\u200D|\u2003", "g");
// Regex pattern for whitespace characters // Regex pattern for whitespace characters
const WHITESPACE_REGEX = new RegExp("\\s", "g"); const WHITESPACE_REGEX = new RegExp("\\s", "g");
// And this is emojione's complete regex const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i');
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/; const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet']; const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
/* /*
* Return true if the given string contains emoji * Return true if the given string contains emoji
* Uses a much, much simpler regex than emojione's so will give false * Uses a much, much simpler regex than emojibase's so will give false
* positives, but useful for fast-path testing strings to see if they * positives, but useful for fast-path testing strings to see if they
* need emojification. * need emojification.
* unicodeToImage uses this function. * unicodeToImage uses this function.
@ -71,73 +67,27 @@ export function containsEmoji(str) {
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str); return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
} }
/* modified from https://github.com/Ranks/emojione/blob/master/lib/js/emojione.js
* because we want to include emoji shortnames in title text
*/
function unicodeToImage(str, addAlt) {
if (addAlt === undefined) addAlt = true;
let replaceWith; let unicode; let short; let fname;
const mappedUnicode = emojione.mapUnicodeToShort();
str = str.replace(emojione.regUnicode, function(unicodeChar) {
if ( (typeof unicodeChar === 'undefined') || (unicodeChar === '') || (!(unicodeChar in emojione.jsEscapeMap)) ) {
// if the unicodeChar doesnt exist just return the entire match
return unicodeChar;
} else {
// get the unicode codepoint from the actual char
unicode = emojione.jsEscapeMap[unicodeChar];
short = mappedUnicode[unicode];
fname = emojione.emojioneList[short].fname;
// depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname
const title = mappedUnicode[unicode];
if (addAlt) {
const alt = (emojione.unicodeAlt) ? emojione.convert(unicode.toUpperCase()) : mappedUnicode[unicode];
replaceWith = `<img class="mx_emojione" title="${title}" alt="${alt}" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
} else {
replaceWith = `<img class="mx_emojione" src="${emojione.imagePathSVG}${fname}.svg${emojione.cacheBustParam}"/>`;
}
return replaceWith;
}
});
return str;
}
/** /**
* Returns the shortcode for an emoji character. * Returns the shortcode for an emoji character.
* *
* @param {String} char The emoji character * @param {String} char The emoji character
* @return {String} The shortcode (such as :thumbup:) * @return {String} The shortcode (such as :thumbup:)
*/ */
export function unicodeToShort(char) { export function unicodeToShortcode(char) {
const unicode = emojione.jsEscapeMap[char]; const data = EMOJIBASE.find(e => e.unicode === char);
return emojione.mapUnicodeToShort()[unicode]; return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
} }
/** /**
* Given one or more unicode characters (represented by unicode * Returns the unicode character for an emoji shortcode
* character number), return an image node with the corresponding
* emoji.
* *
* @param alt {string} String to use for the image alt text * @param {String} shortcode The shortcode (such as :thumbup:)
* @param useSvg {boolean} Whether to use SVG image src. If False, PNG will be used. * @return {String} The emoji character; null if none exists
* @param unicode {integer} One or more integers representing unicode characters
* @returns A img node with the corresponding emoji
*/ */
export function charactersToImageNode(alt, useSvg, ...unicode) { export function shortcodeToUnicode(shortcode) {
const fileName = unicode.map((u) => { shortcode = shortcode.slice(1, shortcode.length - 1);
return u.toString(16); const data = EMOJIBASE.find(e => e.shortcodes && e.shortcodes.includes(shortcode));
}).join('-'); return data ? data.unicode : null;
const path = useSvg ? emojione.imagePathSVG : emojione.imagePathPNG;
const fileType = useSvg ? 'svg' : 'png';
return <img
alt={alt}
src={`${path}${fileName}.${fileType}${emojione.cacheBustParam}`}
/>;
} }
export function processHtmlForSending(html: string): string { export function processHtmlForSending(html: string): string {
@ -444,13 +394,10 @@ class TextHighlighter extends BaseHighlighter {
* opts.disableBigEmoji: optional argument to disable the big emoji class. * opts.disableBigEmoji: optional argument to disable the big emoji class.
* opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing * opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing
* opts.returnString: return an HTML string rather than JSX elements * opts.returnString: return an HTML string rather than JSX elements
* opts.emojiOne: optional param to do emojiOne (default true)
* opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer * opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer
*/ */
export function bodyToHtml(content, highlights, opts={}) { export function bodyToHtml(content, highlights, opts={}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body; const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
const doEmojiOne = opts.emojiOne === undefined ? true : opts.emojiOne;
let bodyHasEmoji = false; let bodyHasEmoji = false;
let sanitizeParams = sanitizeHtmlParams; let sanitizeParams = sanitizeHtmlParams;
@ -481,28 +428,12 @@ export function bodyToHtml(content, highlights, opts={}) {
if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody); if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody);
strippedBody = opts.stripReplyFallback ? ReplyThread.stripPlainReply(content.body) : content.body; strippedBody = opts.stripReplyFallback ? ReplyThread.stripPlainReply(content.body) : content.body;
if (doEmojiOne) {
bodyHasEmoji = containsEmoji(isHtmlMessage ? formattedBody : content.body); bodyHasEmoji = containsEmoji(isHtmlMessage ? formattedBody : content.body);
}
// Only generate safeBody if the message was sent as org.matrix.custom.html // Only generate safeBody if the message was sent as org.matrix.custom.html
if (isHtmlMessage) { if (isHtmlMessage) {
isDisplayedWithHtml = true; isDisplayedWithHtml = true;
safeBody = sanitizeHtml(formattedBody, sanitizeParams); safeBody = sanitizeHtml(formattedBody, sanitizeParams);
} else {
// ... or if there are emoji, which we insert as HTML alongside the
// escaped plaintext body.
if (bodyHasEmoji) {
isDisplayedWithHtml = true;
safeBody = sanitizeHtml(escape(strippedBody), sanitizeParams);
}
}
// An HTML message with emoji
// or a plaintext message with emoji that was escaped and sanitized into
// HTML.
if (bodyHasEmoji) {
safeBody = unicodeToImage(safeBody);
} }
} finally { } finally {
delete sanitizeParams.textFilter; delete sanitizeParams.textFilter;
@ -514,7 +445,6 @@ export function bodyToHtml(content, highlights, opts={}) {
let emojiBody = false; let emojiBody = false;
if (!opts.disableBigEmoji && bodyHasEmoji) { if (!opts.disableBigEmoji && bodyHasEmoji) {
EMOJI_REGEX.lastIndex = 0;
let contentBodyTrimmed = strippedBody !== undefined ? strippedBody.trim() : ''; let contentBodyTrimmed = strippedBody !== undefined ? strippedBody.trim() : '';
// Ignore spaces in body text. Emojis with spaces in between should // Ignore spaces in body text. Emojis with spaces in between should
@ -526,12 +456,14 @@ export function bodyToHtml(content, highlights, opts={}) {
// presented as large. // presented as large.
contentBodyTrimmed = contentBodyTrimmed.replace(ZWJ_REGEX, ''); contentBodyTrimmed = contentBodyTrimmed.replace(ZWJ_REGEX, '');
const match = EMOJI_REGEX.exec(contentBodyTrimmed); const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length &&
// Prevent user pills expanding for users with only emoji in // Prevent user pills expanding for users with only emoji in
// their username // their username
&& (content.formatted_body == undefined (
|| !content.formatted_body.includes("https://matrix.to/")); content.formatted_body == undefined ||
!content.formatted_body.includes("https://matrix.to/")
);
} }
const className = classNames({ const className = classNames({
@ -545,12 +477,6 @@ export function bodyToHtml(content, highlights, opts={}) {
<span key="body" className={className} dir="auto">{ strippedBody }</span>; <span key="body" className={className} dir="auto">{ strippedBody }</span>;
} }
export function emojifyText(text, addAlt) {
return {
__html: unicodeToImage(escape(text), addAlt),
};
}
/** /**
* Linkifies the given string. This is a wrapper around 'linkifyjs/string'. * Linkifies the given string. This is a wrapper around 'linkifyjs/string'.
* *

View file

@ -1,40 +0,0 @@
/*
Copyright 2015 - 2017 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import * as emojione from 'emojione';
export function unicodeToEmojiUri(str) {
const mappedUnicode = emojione.mapUnicodeToShort();
// remove any zero width joiners/spaces used in conjugate emojis as the emojione URIs don't contain them
return str.replace(emojione.regUnicode, function(unicodeChar) {
if ((typeof unicodeChar === 'undefined') || (unicodeChar === '') || (!(unicodeChar in emojione.jsEscapeMap))) {
// if the unicodeChar doesn't exist just return the entire match
return unicodeChar;
} else {
// get the unicode codepoint from the actual char
const unicode = emojione.jsEscapeMap[unicodeChar];
const short = mappedUnicode[unicode];
const fname = emojione.emojioneList[short].fname;
return emojione.imagePathSVG+fname+'.svg'+emojione.cacheBustParam;
}
});
}

View file

@ -19,47 +19,31 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { _t } from '../languageHandler'; import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import {shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione';
import QueryMatcher from './QueryMatcher'; import QueryMatcher from './QueryMatcher';
import sdk from '../index';
import {PillCompletion} from './Components'; import {PillCompletion} from './Components';
import type {Completion, SelectionRange} from './Autocompleter'; import type {Completion, SelectionRange} from './Autocompleter';
import _uniq from 'lodash/uniq'; import _uniq from 'lodash/uniq';
import _sortBy from 'lodash/sortBy'; import _sortBy from 'lodash/sortBy';
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import { shortcodeToUnicode } from '../HtmlUtils';
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
import EmojiData from '../stripped-emoji.json'; import EmojiData from '../stripped-emoji.json';
const LIMIT = 20; const LIMIT = 20;
const CATEGORY_ORDER = [
'people',
'food',
'objects',
'activity',
'nature',
'travel',
'flags',
'regional',
'symbols',
'modifier',
];
// Match for ":wink:" or ascii-style ";-)" provided by emojione // Match for ascii-style ";-)" emoticons or ":wink:" shortcodes provided by emojibase
// (^|\s|(emojiUnicode)) to make sure we're either at the start of the string or there's a const EMOJI_REGEX = new RegExp('(' + EMOTICON_REGEX.source + '|:[+-\\w]*:?)$', 'g');
// whitespace character or an emoji before the emoji. The reason for unicodeRegexp is
// that we need to support inputting multiple emoji with no space between them.
const EMOJI_REGEX = new RegExp('(?:^|\\s|' + unicodeRegexp + ')(' + asciiRegexp + '|:[+-\\w]*:?)$', 'g');
// We also need to match the non-zero-length prefixes to remove them from the final match,
// and update the range so that we don't replace the whitespace or the previous emoji.
const MATCH_PREFIX_REGEX = new RegExp('(\\s|' + unicodeRegexp + ')');
// XXX: it's very unclear why we bother with this generated emojidata file.
// all it means is that we end up bloating the bundle with precomputed stuff
// which would be trivial to calculate and cache on demand.
const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sort( const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sort(
(a, b) => { (a, b) => {
if (a.category === b.category) { if (a.category === b.category) {
return a.emoji_order - b.emoji_order; return a.emoji_order - b.emoji_order;
} }
return CATEGORY_ORDER.indexOf(a.category) - CATEGORY_ORDER.indexOf(b.category); return a.category - b.category;
}, },
).map((a, index) => { ).map((a, index) => {
return { return {
@ -101,26 +85,20 @@ export default class EmojiProvider extends AutocompleteProvider {
return []; // don't give any suggestions if the user doesn't want them return []; // don't give any suggestions if the user doesn't want them
} }
const EmojiText = sdk.getComponent('views.elements.EmojiText');
let completions = []; let completions = [];
const {command, range} = this.getCurrentCommand(query, selection); const {command, range} = this.getCurrentCommand(query, selection);
if (command) { if (command) {
let matchedString = command[0]; const matchedString = command[0];
// Remove prefix of any length (single whitespace or unicode emoji)
const prefixMatch = MATCH_PREFIX_REGEX.exec(matchedString);
if (prefixMatch) {
matchedString = matchedString.slice(prefixMatch[0].length);
range.start += prefixMatch[0].length;
}
completions = this.matcher.match(matchedString); completions = this.matcher.match(matchedString);
// Do second match with shouldMatchWordsOnly in order to match against 'name' // Do second match with shouldMatchWordsOnly in order to match against 'name'
completions = completions.concat(this.nameMatcher.match(matchedString)); completions = completions.concat(this.nameMatcher.match(matchedString));
const sorters = []; const sorters = [];
// First, sort by score (Infinity if matchedString not in shortname) // make sure that emoticons come first
sorters.push((c) => score(matchedString, c.aliases_ascii));
// then sort by score (Infinity if matchedString not in shortname)
sorters.push((c) => score(matchedString, c.shortname)); sorters.push((c) => score(matchedString, c.shortname));
// If the matchedString is not empty, sort by length of shortname. Example: // If the matchedString is not empty, sort by length of shortname. Example:
// matchedString = ":bookmark" // matchedString = ":bookmark"
@ -133,12 +111,12 @@ export default class EmojiProvider extends AutocompleteProvider {
completions = _sortBy(_uniq(completions), sorters); completions = _sortBy(_uniq(completions), sorters);
completions = completions.map((result) => { completions = completions.map((result) => {
const {shortname} = result; const { shortname } = result;
const unicode = shortnameToUnicode(shortname); const unicode = shortcodeToUnicode(shortname);
return { return {
completion: unicode, completion: unicode,
component: ( component: (
<PillCompletion title={shortname} initialComponent={<EmojiText style={{maxWidth: '1em'}}>{ unicode }</EmojiText>} /> <PillCompletion title={shortname} initialComponent={<span style={{maxWidth: '1em'}}>{ unicode }</span>} />
), ),
range, range,
}; };

View file

@ -304,8 +304,6 @@ module.exports = React.createClass({
// return suitable content for the main (text) part of the status bar. // return suitable content for the main (text) part of the status bar.
_getContent: function() { _getContent: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
if (this._shouldShowConnectionError()) { if (this._shouldShowConnectionError()) {
return ( return (
<div className="mx_RoomStatusBar_connectionLostBar"> <div className="mx_RoomStatusBar_connectionLostBar">

View file

@ -166,7 +166,6 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
const imageUrl = this.state.imageUrls[this.state.urlsIndex]; const imageUrl = this.state.imageUrls[this.state.urlsIndex];
const { const {
@ -178,13 +177,13 @@ module.exports = React.createClass({
if (imageUrl === this.state.defaultImageUrl) { if (imageUrl === this.state.defaultImageUrl) {
const initialLetter = this._getInitialLetter(name); const initialLetter = this._getInitialLetter(name);
const textNode = ( const textNode = (
<EmojiText className="mx_BaseAvatar_initial" aria-hidden="true" <span className="mx_BaseAvatar_initial" aria-hidden="true"
style={{ fontSize: (width * 0.65) + "px", style={{ fontSize: (width * 0.65) + "px",
width: width + "px", width: width + "px",
lineHeight: height + "px" }} lineHeight: height + "px" }}
> >
{ initialLetter } { initialLetter }
</EmojiText> </span>
); );
const imgNode = ( const imgNode = (
<img className="mx_BaseAvatar_image" src={imageUrl} <img className="mx_BaseAvatar_image" src={imageUrl}

View file

@ -1,43 +0,0 @@
/*
Copyright 2016 Aviral Dasgupta
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {emojifyText, containsEmoji} from '../../../HtmlUtils';
export default function EmojiText(props) {
const {element, children, addAlt, ...restProps} = props;
// fast path: simple regex to detect strings that don't contain
// emoji and just return them
if (containsEmoji(children)) {
restProps.dangerouslySetInnerHTML = emojifyText(children, addAlt);
return React.createElement(element, restProps);
} else {
return React.createElement(element, restProps, children);
}
}
EmojiText.propTypes = {
element: PropTypes.string,
children: PropTypes.string.isRequired,
};
EmojiText.defaultProps = {
element: 'span',
addAlt: true,
};

View file

@ -117,13 +117,9 @@ module.exports = React.createClass({
return null; return null;
} }
const EmojiText = sdk.getComponent('elements.EmojiText');
return ( return (
<span className="mx_TextualEvent mx_MemberEventListSummary_summary"> <span className="mx_TextualEvent mx_MemberEventListSummary_summary">
<EmojiText>
{ summaries.join(", ") } { summaries.join(", ") }
</EmojiText>
</span> </span>
); );
}, },

View file

@ -117,7 +117,6 @@ export default React.createClass({
render: function() { render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EmojiText = sdk.getComponent('elements.EmojiText');
const groupName = this.props.group.name || this.props.group.groupId; const groupName = this.props.group.name || this.props.group.groupId;
const httpAvatarUrl = this.props.group.avatarUrl ? const httpAvatarUrl = this.props.group.avatarUrl ?
@ -129,9 +128,9 @@ export default React.createClass({
'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed, 'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed,
}); });
const label = <EmojiText element="div" title={this.props.group.groupId} className={nameClasses} dir="auto"> const label = <div title={this.props.group.groupId} className={nameClasses} dir="auto">
{ groupName } { groupName }
</EmojiText>; </div>;
const badgeEllipsis = this.state.badgeHover || this.state.menuDisplayed; const badgeEllipsis = this.state.badgeHover || this.state.menuDisplayed;
const badgeClasses = classNames('mx_RoomTile_badge mx_RoomTile_highlight', { const badgeClasses = classNames('mx_RoomTile_badge mx_RoomTile_highlight', {

View file

@ -180,7 +180,6 @@ module.exports = React.createClass({
this.props.groupMember.displayname || this.props.groupMember.userId this.props.groupMember.displayname || this.props.groupMember.userId
); );
const EmojiText = sdk.getComponent('elements.EmojiText');
const GeminiScrollbarWrapper = sdk.getComponent('elements.GeminiScrollbarWrapper'); const GeminiScrollbarWrapper = sdk.getComponent('elements.GeminiScrollbarWrapper');
return ( return (
<div className="mx_MemberInfo"> <div className="mx_MemberInfo">
@ -189,7 +188,7 @@ module.exports = React.createClass({
<img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" /> <img src={require("../../../../res/img/cancel.svg")} width="18" height="18" className="mx_filterFlipColor" />
</AccessibleButton> </AccessibleButton>
{ avatarElement } { avatarElement }
<EmojiText element="h2">{ groupMemberName }</EmojiText> <h2>{ groupMemberName }</h2>
<div className="mx_MemberInfo_profile"> <div className="mx_MemberInfo_profile">
<div className="mx_MemberInfo_profileField"> <div className="mx_MemberInfo_profileField">

View file

@ -149,7 +149,6 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
@ -221,7 +220,7 @@ module.exports = React.createClass({
</AccessibleButton> </AccessibleButton>
{ avatarElement } { avatarElement }
<EmojiText element="h2">{ groupRoomName }</EmojiText> <h2>{ groupRoomName }</h2>
<div className="mx_MemberInfo_profile"> <div className="mx_MemberInfo_profile">
<div className="mx_MemberInfo_profileField"> <div className="mx_MemberInfo_profileField">

View file

@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index'; import sdk from '../../../index';
import { unicodeToShort } from '../../../HtmlUtils'; import { unicodeToShortcode } from '../../../HtmlUtils';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
@ -46,7 +46,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent {
const { name } = room.getMember(reactionEvent.getSender()); const { name } = room.getMember(reactionEvent.getSender());
senders.push(name); senders.push(name);
} }
const shortName = unicodeToShort(content) || content; const shortName = unicodeToShortcode(content) || content;
tooltipLabel = <div>{_t( tooltipLabel = <div>{_t(
"<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>", "<reactors/><reactedWith>reacted with %(shortName)s</reactedWith>",
{ {

View file

@ -19,7 +19,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk'; import {MatrixClient} from 'matrix-js-sdk';
import sdk from '../../../index';
import Flair from '../elements/Flair.js'; import Flair from '../elements/Flair.js';
import FlairStore from '../../../stores/FlairStore'; import FlairStore from '../../../stores/FlairStore';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
@ -95,7 +94,6 @@ export default React.createClass({
}, },
render() { render() {
const EmojiText = sdk.getComponent('elements.EmojiText');
const {mxEvent} = this.props; const {mxEvent} = this.props;
const colorClass = getUserNameColorClass(mxEvent.getSender()); const colorClass = getUserNameColorClass(mxEvent.getSender());
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
@ -117,7 +115,7 @@ export default React.createClass({
/>; />;
} }
const nameElem = <EmojiText key='name'>{ name || '' }</EmojiText>; const nameElem = name || '';
// Name + flair // Name + flair
const nameFlair = <span> const nameFlair = <span>

View file

@ -473,7 +473,6 @@ module.exports = React.createClass({
const MessageEditor = sdk.getComponent('elements.MessageEditor'); const MessageEditor = sdk.getComponent('elements.MessageEditor');
return <MessageEditor event={this.props.mxEvent} />; return <MessageEditor event={this.props.mxEvent} />;
} }
const EmojiText = sdk.getComponent('elements.EmojiText');
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
const content = mxEvent.getContent(); const content = mxEvent.getContent();
@ -512,12 +511,12 @@ module.exports = React.createClass({
return ( return (
<span ref="content" className="mx_MEmoteBody mx_EventTile_content"> <span ref="content" className="mx_MEmoteBody mx_EventTile_content">
*&nbsp; *&nbsp;
<EmojiText <span
className="mx_MEmoteBody_sender" className="mx_MEmoteBody_sender"
onClick={this.onEmoteSenderClick} onClick={this.onEmoteSenderClick}
> >
{ name } { name }
</EmojiText> </span>
&nbsp; &nbsp;
{ body } { body }
{ widgets } { widgets }

View file

@ -20,7 +20,6 @@ const React = require('react');
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const TextForEvent = require('../../../TextForEvent'); const TextForEvent = require('../../../TextForEvent');
import sdk from '../../../index';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'TextualEvent', displayName: 'TextualEvent',
@ -31,11 +30,10 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
const text = TextForEvent.textForEvent(this.props.mxEvent); const text = TextForEvent.textForEvent(this.props.mxEvent);
if (text == null || text.length === 0) return null; if (text == null || text.length === 0) return null;
return ( return (
<EmojiText element="div" className="mx_TextualEvent">{ text }</EmojiText> <div className="mx_TextualEvent">{ text }</div>
); );
}, },
}); });

View file

@ -256,8 +256,6 @@ export default class Autocomplete extends React.Component {
} }
render() { render() {
const EmojiText = sdk.getComponent('views.elements.EmojiText');
let position = 1; let position = 1;
const renderedCompletions = this.state.completions.map((completionResult, i) => { const renderedCompletions = this.state.completions.map((completionResult, i) => {
const completions = completionResult.completions.map((completion, i) => { const completions = completionResult.completions.map((completion, i) => {
@ -282,7 +280,7 @@ export default class Autocomplete extends React.Component {
return completions.length > 0 ? ( return completions.length > 0 ? (
<div key={i} className="mx_Autocomplete_ProviderSection"> <div key={i} className="mx_Autocomplete_ProviderSection">
<EmojiText element="div" className="mx_Autocomplete_provider_name">{ completionResult.provider.getName() }</EmojiText> <div className="mx_Autocomplete_provider_name">{ completionResult.provider.getName() }</div>
{ completionResult.provider.renderCompletions(completions) } { completionResult.provider.renderCompletions(completions) }
</div> </div>
) : null; ) : null;

View file

@ -111,7 +111,6 @@ const EntityTile = React.createClass({
let nameEl; let nameEl;
const {name} = this.props; const {name} = this.props;
const EmojiText = sdk.getComponent('elements.EmojiText');
if (!this.props.suppressOnHover) { if (!this.props.suppressOnHover) {
const activeAgo = this.props.presenceLastActiveAgo ? const activeAgo = this.props.presenceLastActiveAgo ?
(Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1; (Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1;
@ -128,24 +127,24 @@ const EntityTile = React.createClass({
} }
nameEl = ( nameEl = (
<div className="mx_EntityTile_details"> <div className="mx_EntityTile_details">
<EmojiText element="div" className="mx_EntityTile_name" dir="auto"> <div className="mx_EntityTile_name" dir="auto">
{ name } { name }
</EmojiText> </div>
{presenceLabel} {presenceLabel}
</div> </div>
); );
} else if (this.props.subtextLabel) { } else if (this.props.subtextLabel) {
nameEl = ( nameEl = (
<div className="mx_EntityTile_details"> <div className="mx_EntityTile_details">
<EmojiText element="div" className="mx_EntityTile_name" dir="auto"> <div className="mx_EntityTile_name" dir="auto">
{name} {name}
</EmojiText> </div>
<span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span> <span className="mx_EntityTile_subtext">{this.props.subtextLabel}</span>
</div> </div>
); );
} else { } else {
nameEl = ( nameEl = (
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{ name }</EmojiText> <div className="mx_EntityTile_name" dir="auto">{ name }</div>
); );
} }

View file

@ -678,14 +678,13 @@ module.exports = withMatrixClient(React.createClass({
switch (this.props.tileShape) { switch (this.props.tileShape) {
case 'notif': { case 'notif': {
const EmojiText = sdk.getComponent('elements.EmojiText');
const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId()); const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId());
return ( return (
<div className={classes}> <div className={classes}>
<div className="mx_EventTile_roomName"> <div className="mx_EventTile_roomName">
<EmojiText element="a" href={permalink} onClick={this.onPermalinkClicked}> <a href={permalink} onClick={this.onPermalinkClicked}>
{ room ? room.name : '' } { room ? room.name : '' }
</EmojiText> </a>
</div> </div>
<div className="mx_EventTile_senderDetails"> <div className="mx_EventTile_senderDetails">
{ avatar } { avatar }

View file

@ -978,7 +978,6 @@ module.exports = withMatrixClient(React.createClass({
} }
const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper");
const EmojiText = sdk.getComponent('elements.EmojiText');
let backButton; let backButton;
if (this.props.member.roomId) { if (this.props.member.roomId) {
@ -993,7 +992,7 @@ module.exports = withMatrixClient(React.createClass({
<div className="mx_MemberInfo_name"> <div className="mx_MemberInfo_name">
{ backButton } { backButton }
{ e2eIconElement } { e2eIconElement }
<EmojiText element="h2">{ memberName }</EmojiText> <h2>{ memberName }</h2>
</div> </div>
{ avatarElement } { avatarElement }
<div className="mx_MemberInfo_container"> <div className="mx_MemberInfo_container">

View file

@ -40,7 +40,6 @@ import Analytics from '../../../Analytics';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import * as RichText from '../../../RichText';
import * as HtmlUtils from '../../../HtmlUtils'; import * as HtmlUtils from '../../../HtmlUtils';
import Autocomplete from './Autocomplete'; import Autocomplete from './Autocomplete';
import {Completion} from "../../../autocomplete/Autocompleter"; import {Completion} from "../../../autocomplete/Autocompleter";
@ -51,10 +50,9 @@ import ContentMessages from '../../../ContentMessages';
import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix'; import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix';
import { import EMOJIBASE from 'emojibase-data/en/compact.json';
asciiRegexp, unicodeRegexp, shortnameToUnicode, import EMOTICON_REGEX from 'emojibase-regex/emoticon';
asciiList, mapUnicodeToShort, toShort,
} from 'emojione';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import {makeUserPermalink} from "../../../matrix-to"; import {makeUserPermalink} from "../../../matrix-to";
import ReplyPreview from "./ReplyPreview"; import ReplyPreview from "./ReplyPreview";
@ -63,9 +61,7 @@ import ReplyThread from "../elements/ReplyThread";
import {ContentHelpers} from 'matrix-js-sdk'; import {ContentHelpers} from 'matrix-js-sdk';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort(); const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$');
const EMOJI_REGEX = new RegExp(unicodeRegexp, 'g');
const TYPING_USER_TIMEOUT = 10000; const TYPING_SERVER_TIMEOUT = 30000; const TYPING_USER_TIMEOUT = 10000; const TYPING_SERVER_TIMEOUT = 30000;
@ -273,9 +269,8 @@ export default class MessageComposerInput extends React.Component {
case 'emoji': case 'emoji':
// XXX: apparently you can't return plain strings from serializer rules // XXX: apparently you can't return plain strings from serializer rules
// until https://github.com/ianstormtaylor/slate/pull/1854 is merged. // until https://github.com/ianstormtaylor/slate/pull/1854 is merged.
// So instead we temporarily wrap emoji from RTE in an arbitrary tag // So instead we temporarily wrap emoji from RTE in a span.
// (<b/>). <span/> would be nicer, but in practice it causes CSS issues. return <span>{ obj.data.get('emojiUnicode') }</span>;
return <b>{ obj.data.get('emojiUnicode') }</b>;
} }
return this.renderNode({ return this.renderNode({
node: obj, node: obj,
@ -375,7 +370,6 @@ export default class MessageComposerInput extends React.Component {
const html = HtmlUtils.bodyToHtml(payload.event.getContent(), null, { const html = HtmlUtils.bodyToHtml(payload.event.getContent(), null, {
forComposerQuote: true, forComposerQuote: true,
returnString: true, returnString: true,
emojiOne: false,
}); });
const fragment = this.html.deserialize(html); const fragment = this.html.deserialize(html);
// FIXME: do we want to put in a permalink to the original quote here? // FIXME: do we want to put in a permalink to the original quote here?
@ -538,17 +532,15 @@ export default class MessageComposerInput extends React.Component {
// Automatic replacement of plaintext emoji to Unicode emoji // Automatic replacement of plaintext emoji to Unicode emoji
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) { if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
// The first matched group includes just the matched plaintext emoji // The first matched group includes just the matched plaintext emoji
const emojiMatch = REGEX_EMOJI_WHITESPACE.exec(text.slice(0, currentStartOffset)); const emoticonMatch = REGEX_EMOTICON_WHITESPACE.exec(text.slice(0, currentStartOffset));
if (emojiMatch) { if (emoticonMatch) {
// plaintext -> hex unicode const data = EMOJIBASE.find(e => e.emoticon === emoticonMatch[1]);
const emojiUc = asciiList[emojiMatch[1]]; const unicodeEmoji = data ? data.unicode : '';
// hex unicode -> shortname -> actual unicode
const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]);
const range = Range.create({ const range = Range.create({
anchor: { anchor: {
key: editorState.startText.key, key: editorState.startText.key,
offset: currentStartOffset - emojiMatch[1].length - 1, offset: currentStartOffset - emoticonMatch[1].length - 1,
}, },
focus: { focus: {
key: editorState.startText.key, key: editorState.startText.key,
@ -561,54 +553,6 @@ export default class MessageComposerInput extends React.Component {
} }
} }
// emojioneify any emoji
let foundEmoji;
do {
foundEmoji = false;
for (const node of editorState.document.getTexts()) {
if (node.text !== '' && HtmlUtils.containsEmoji(node.text)) {
let match;
EMOJI_REGEX.lastIndex = 0;
while ((match = EMOJI_REGEX.exec(node.text)) !== null) {
const range = Range.create({
anchor: {
key: node.key,
offset: match.index,
},
focus: {
key: node.key,
offset: match.index + match[0].length,
},
});
const inline = Inline.create({
type: 'emoji',
data: { emojiUnicode: match[0] },
});
change = change.insertInlineAtRange(range, inline);
editorState = change.value;
// if we replaced an emoji, start again looking for more
// emoji in the new editor state since doing the replacement
// will change the node structure & offsets so we can't compute
// insertion ranges from node.key / match.index anymore.
foundEmoji = true;
break;
}
}
}
} while (foundEmoji);
// work around weird bug where inserting emoji via the macOS
// emoji picker can leave the selection stuck in the emoji's
// child text. This seems to happen due to selection getting
// moved in the normalisation phase after calculating these changes
if (editorState.selection.anchor.key &&
editorState.document.getParent(editorState.selection.anchor.key).type === 'emoji') {
change = change.moveToStartOfNextText();
editorState = change.value;
}
if (this.props.onInputStateChanged && editorState.blocks.size > 0) { if (this.props.onInputStateChanged && editorState.blocks.size > 0) {
let blockType = editorState.blocks.first().type; let blockType = editorState.blocks.first().type;
// console.log("onInputStateChanged; current block type is " + blockType + " and marks are " + editorState.activeMarks); // console.log("onInputStateChanged; current block type is " + blockType + " and marks are " + editorState.activeMarks);
@ -1295,7 +1239,7 @@ export default class MessageComposerInput extends React.Component {
// Move selection to the end of the selected history // Move selection to the end of the selected history
const change = editorState.change().moveToEndOfNode(editorState.document); const change = editorState.change().moveToEndOfNode(editorState.document);
// We don't call this.onChange(change) now, as fixups on stuff like emoji // We don't call this.onChange(change) now, as fixups on stuff like pills
// should already have been done and persisted in the history. // should already have been done and persisted in the history.
editorState = change.value; editorState = change.value;
@ -1475,17 +1419,7 @@ export default class MessageComposerInput extends React.Component {
} }
case 'emoji': { case 'emoji': {
const { data } = node; const { data } = node;
const emojiUnicode = data.get('emojiUnicode'); return data.get('emojiUnicode');
const uri = RichText.unicodeToEmojiUri(emojiUnicode);
const shortname = toShort(emojiUnicode);
const className = classNames('mx_emojione', {
mx_emojione_selected: isSelected,
});
const style = {};
if (props.selected) style.border = '1px solid blue';
return <img className={ className } src={ uri }
title={ shortname } alt={ emojiUnicode } style={style}
/>;
} }
} }
}; };

View file

@ -66,13 +66,12 @@ export default class ReplyPreview extends React.Component {
if (!this.state.event) return null; if (!this.state.event) return null;
const EventTile = sdk.getComponent('rooms.EventTile'); const EventTile = sdk.getComponent('rooms.EventTile');
const EmojiText = sdk.getComponent('views.elements.EmojiText');
return <div className="mx_ReplyPreview"> return <div className="mx_ReplyPreview">
<div className="mx_ReplyPreview_section"> <div className="mx_ReplyPreview_section">
<EmojiText element="div" className="mx_ReplyPreview_header mx_ReplyPreview_title"> <div className="mx_ReplyPreview_header mx_ReplyPreview_title">
{ '💬 ' + _t('Replying') } { '💬 ' + _t('Replying') }
</EmojiText> </div>
<div className="mx_ReplyPreview_header mx_ReplyPreview_cancel"> <div className="mx_ReplyPreview_header mx_ReplyPreview_cancel">
<img className="mx_filterFlipColor" src={require("../../../../res/img/cancel.svg")} width="18" height="18" <img className="mx_filterFlipColor" src={require("../../../../res/img/cancel.svg")} width="18" height="18"
onClick={cancelQuoting} /> onClick={cancelQuoting} />

View file

@ -147,7 +147,6 @@ module.exports = React.createClass({
render: function() { render: function() {
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
const EmojiText = sdk.getComponent('elements.EmojiText');
let searchStatus = null; let searchStatus = null;
let cancelButton = null; let cancelButton = null;
@ -191,10 +190,10 @@ module.exports = React.createClass({
roomName = this.props.room.name; roomName = this.props.room.name;
} }
const emojiTextClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint }); const textClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
const name = const name =
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}> <div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
<EmojiText dir="auto" element="div" className={emojiTextClasses} title={roomName}>{ roomName }</EmojiText> <div dir="auto" className={textClasses} title={roomName}>{ roomName }</div>
{ searchStatus } { searchStatus }
</div>; </div>;

View file

@ -342,7 +342,6 @@ module.exports = React.createClass({
badge = <div className={badgeClasses}>{ badgeContent }</div>; badge = <div className={badgeClasses}>{ badgeContent }</div>;
} }
const EmojiText = sdk.getComponent('elements.EmojiText');
let label; let label;
let subtextLabel; let subtextLabel;
let tooltip; let tooltip;
@ -354,14 +353,7 @@ module.exports = React.createClass({
}); });
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null; subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
label = <div title={name} className={nameClasses} dir="auto">{ name }</div>;
if (this.state.selected) {
const nameSelected = <EmojiText>{ name }</EmojiText>;
label = <div title={name} className={nameClasses} dir="auto">{ nameSelected }</div>;
} else {
label = <EmojiText element="div" title={name} className={nameClasses} dir="auto">{ name }</EmojiText>;
}
} else if (this.state.hover) { } else if (this.state.hover) {
const Tooltip = sdk.getComponent("elements.Tooltip"); const Tooltip = sdk.getComponent("elements.Tooltip");
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />; tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import sdk from '../../../index';
import WhoIsTyping from '../../../WhoIsTyping'; import WhoIsTyping from '../../../WhoIsTyping';
import Timer from '../../../utils/Timer'; import Timer from '../../../utils/Timer';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
@ -212,15 +211,13 @@ module.exports = React.createClass({
return (<div className="mx_WhoIsTypingTile_empty" />); return (<div className="mx_WhoIsTypingTile_empty" />);
} }
const EmojiText = sdk.getComponent('elements.EmojiText');
return ( return (
<li className="mx_WhoIsTypingTile"> <li className="mx_WhoIsTypingTile">
<div className="mx_WhoIsTypingTile_avatars"> <div className="mx_WhoIsTypingTile_avatars">
{ this._renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) } { this._renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) }
</div> </div>
<div className="mx_WhoIsTypingTile_label"> <div className="mx_WhoIsTypingTile_label">
<EmojiText>{ typingString }</EmojiText> { typingString }
</div> </div>
</li> </li>
); );

View file

@ -174,14 +174,13 @@ export default class KeyBackupPanel extends React.PureComponent {
} else if (this.state.loading) { } else if (this.state.loading) {
return <Spinner />; return <Spinner />;
} else if (this.state.backupInfo) { } else if (this.state.backupInfo) {
const EmojiText = sdk.getComponent('elements.EmojiText');
let clientBackupStatus; let clientBackupStatus;
let restoreButtonCaption = _t("Restore from Backup"); let restoreButtonCaption = _t("Restore from Backup");
if (MatrixClientPeg.get().getKeyBackupEnabled()) { if (MatrixClientPeg.get().getKeyBackupEnabled()) {
clientBackupStatus = <div> clientBackupStatus = <div>
<p>{encryptedMessageAreEncrypted}</p> <p>{encryptedMessageAreEncrypted}</p>
<p>{_t("This device is backing up your keys. ")}<EmojiText></EmojiText></p> <p>{_t("This device is backing up your keys. ")}</p>
</div>; </div>;
} else { } else {
clientBackupStatus = <div> clientBackupStatus = <div>

View file

@ -36,7 +36,6 @@ export default class VerificationShowSas extends React.Component {
render() { render() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const EmojiText = sdk.getComponent('views.elements.EmojiText');
let sasDisplay; let sasDisplay;
let sasCaption; let sasCaption;
@ -44,7 +43,7 @@ export default class VerificationShowSas extends React.Component {
const emojiBlocks = this.props.sas.emoji.map( const emojiBlocks = this.props.sas.emoji.map(
(emoji, i) => <div className="mx_VerificationShowSas_emojiSas_block" key={i}> (emoji, i) => <div className="mx_VerificationShowSas_emojiSas_block" key={i}>
<div className="mx_VerificationShowSas_emojiSas_emoji"> <div className="mx_VerificationShowSas_emojiSas_emoji">
<EmojiText addAlt={false}>{emoji[0]}</EmojiText> { emoji[0] }
</div> </div>
<div className="mx_VerificationShowSas_emojiSas_label"> <div className="mx_VerificationShowSas_emojiSas_label">
{_t(capFirst(emoji[1]))} {_t(capFirst(emoji[1]))}

File diff suppressed because one or more lines are too long

View file

@ -2576,10 +2576,15 @@ emoji-regex@^7.0.1:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
emojione@2.2.7: emojibase-data@^4.0.0:
version "2.2.7" version "4.0.0"
resolved "https://registry.yarnpkg.com/emojione/-/emojione-2.2.7.tgz#46457cf6b9b2f8da13ae8a2e4e547de06ee15e96" resolved "https://registry.yarnpkg.com/emojibase-data/-/emojibase-data-4.0.0.tgz#3feb3e5bb5e5f2b6373b183b0f038c60889a9e29"
integrity sha1-RkV89rmy+NoTroouTlR94G7hXpY= integrity sha512-Yi4A1IxB7iZ+09Wqr2BEpHSQfugc5I8G+wckDOhCia0F7oOdErf/85jCwbpRQy7xtBbvlyS3xQrYedSeQot5Og==
emojibase-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojibase-regex/-/emojibase-regex-3.0.0.tgz#fc7a17aa20584df5a73619f06ac236b8a5bb452d"
integrity sha512-iNDkbtn8UxKTxjIlvHLqfXovZaIulnuuyo2/emU+ZlPr2OmzxGfiDI+iIQ3WWqdlA6lgjrPNWgpbytt4lnJYrg==
emojis-list@^2.0.0: emojis-list@^2.0.0:
version "2.1.0" version "2.1.0"