diff --git a/package.json b/package.json
index 9c55ff43c8..d5cad9dc0d 100644
--- a/package.json
+++ b/package.json
@@ -65,7 +65,8 @@
"classnames": "^2.1.2",
"commonmark": "^0.28.1",
"counterpart": "^0.18.0",
- "emojione": "2.2.7",
+ "emojibase-data": "^4.0.0",
+ "emojibase-regex": "^4.0.0",
"file-saver": "^1.3.3",
"filesize": "3.5.6",
"flux": "2.1.1",
diff --git a/res/fonts/Twemoji_Mozilla/TwemojiMozilla.woff2 b/res/fonts/Twemoji_Mozilla/TwemojiMozilla.woff2
new file mode 100644
index 0000000000..70f9b03c4d
Binary files /dev/null and b/res/fonts/Twemoji_Mozilla/TwemojiMozilla.woff2 differ
diff --git a/res/themes/light/css/_fonts.scss b/res/themes/light/css/_fonts.scss
index ac15847e44..ffd3dffee1 100644
--- a/res/themes/light/css/_fonts.scss
+++ b/res/themes/light/css/_fonts.scss
@@ -15,22 +15,22 @@
/* the 'src' links are relative to the bundle.css, which is in a subdirectory.
*/
@font-face {
- font-family: 'Nunito';
- font-style: normal;
- font-weight: 400;
- src: url('$(res)/fonts/Nunito/Nunito-Regular.ttf') format('truetype');
+ font-family: 'Nunito';
+ font-style: normal;
+ font-weight: 400;
+ src: url('$(res)/fonts/Nunito/Nunito-Regular.ttf') format('truetype');
}
@font-face {
- font-family: 'Nunito';
- font-style: normal;
- font-weight: 600;
- src: url('$(res)/fonts/Nunito/Nunito-SemiBold.ttf') format('truetype');
+ font-family: 'Nunito';
+ font-style: normal;
+ font-weight: 600;
+ src: url('$(res)/fonts/Nunito/Nunito-SemiBold.ttf') format('truetype');
}
@font-face {
- font-family: 'Nunito';
- font-style: normal;
- font-weight: 700;
- src: url('$(res)/fonts/Nunito/Nunito-Bold.ttf') format('truetype');
+ font-family: 'Nunito';
+ font-style: normal;
+ font-weight: 700;
+ src: url('$(res)/fonts/Nunito/Nunito-Bold.ttf') format('truetype');
}
/*
@@ -51,3 +51,13 @@
font-weight: 700;
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');
+}
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index d11dfebda3..c4de8ecdd3 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -1,10 +1,11 @@
// XXX: check this?
/* Nunito lacks combining diacritics, so these will fall through
to the next font. Helevetica's diacritics however do not combine
- nicely with Open Sans (on OSX, at least) and result in a huge
- horizontal mess. Arial empirically gets it right, hence prioritising
- Arial here. */
-$font-family: 'Nunito', Arial, Helvetica, Sans-Serif;
+ nicely (on OSX, at least) and result in a huge horizontal mess.
+ Arial empirically gets it right, hence prioritising Arial here. */
+/* We fall through to Twemoji for emoji rather than falling through
+ to native Emoji fonts (if any) to ensure cross-browser consistency */
+$font-family: Nunito, Arial, Helvetica, Sans-Serif, 'Twemoji Mozilla';
// unified palette
// try to use these colors when possible
diff --git a/scripts/emoji-data-strip.js b/scripts/emoji-data-strip.js
index 42bf2ac2de..2b9b67c859 100644
--- a/scripts/emoji-data-strip.js
+++ b/scripts/emoji-data-strip.js
@@ -1,4 +1,12 @@
#!/usr/bin/env node
+
+// This generates src/stripped-emoji.json as used by the EmojiProvider autocomplete
+// provider.
+
+// FIXME: we no longer depends on emojione, so this generation script no longer
+// works, but the expectation is that we will shift to using emojimart or
+// similar as an emoji picker before this next needs to be run again.
+
const EMOJI_DATA = require('emojione/emoji.json');
const EMOJI_SUPPORTED = Object.keys(require('emojione').emojioneList);
const fs = require('fs');
diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js
index 1032c52e32..d53d8e4864 100644
--- a/src/HtmlUtils.js
+++ b/src/HtmlUtils.js
@@ -27,22 +27,18 @@ import linkifyMatrix from './linkify-matrix';
import _linkifyElement from 'linkifyjs/element';
import _linkifyString from 'linkifyjs/string';
import escape from 'lodash/escape';
-import emojione from 'emojione';
import classNames from 'classnames';
import MatrixClientPeg from './MatrixClientPeg';
import url from 'url';
-linkifyMatrix(linkify);
+import EMOJIBASE from 'emojibase-data/en/compact.json';
+import EMOJI_REGEX from 'emojibase-regex';
-emojione.imagePathSVG = 'emojione/svg/';
-// 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';
+linkifyMatrix(linkify);
// Anything outside the basic multilingual plane will be a surrogate pair
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
// 'miscellaneous symbols and arrows' which should catch all of them
// (with plenty of false positives, but that's OK)
@@ -54,15 +50,13 @@ const ZWJ_REGEX = new RegExp("\u200D|\u2003", "g");
// Regex pattern for whitespace characters
const WHITESPACE_REGEX = new RegExp("\\s", "g");
-// And this is emojione's complete regex
-const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
/*
* 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
* need emojification.
* unicodeToImage uses this function.
@@ -71,73 +65,26 @@ export function containsEmoji(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 = ``;
- } else {
- replaceWith = ``;
- }
- return replaceWith;
- }
- });
-
- return str;
-}
-
/**
* Returns the shortcode for an emoji character.
*
* @param {String} char The emoji character
* @return {String} The shortcode (such as :thumbup:)
*/
-export function unicodeToShort(char) {
- const unicode = emojione.jsEscapeMap[char];
- return emojione.mapUnicodeToShort()[unicode];
+export function unicodeToShortcode(char) {
+ const data = EMOJIBASE.find((e)=>{ e.unicode === char });
+ return (data && data.shortcodes ? data.shortcodes[0] : '';
}
/**
- * Given one or more unicode characters (represented by unicode
- * character number), return an image node with the corresponding
- * emoji.
+ * Returns the unicode character for an emoji shortcode
*
- * @param alt {string} String to use for the image alt text
- * @param useSvg {boolean} Whether to use SVG image src. If False, PNG will be used.
- * @param unicode {integer} One or more integers representing unicode characters
- * @returns A img node with the corresponding emoji
+ * @param {String} shortcode The shortcode (such as :thumbup:)
+ * @return {String} The emoji character
*/
-export function charactersToImageNode(alt, useSvg, ...unicode) {
- const fileName = unicode.map((u) => {
- return u.toString(16);
- }).join('-');
- const path = useSvg ? emojione.imagePathSVG : emojione.imagePathPNG;
- const fileType = useSvg ? 'svg' : 'png';
- return ;
+export function shortcodeToUnicode(shortcode) {
+ const data = EMOJIBASE.find((e)=>{ e.shortcodes && e.shortcodes.contains(shortcode) });
+ return data.unicode;
}
export function processHtmlForSending(html: string): string {
@@ -444,13 +391,10 @@ class TextHighlighter extends BaseHighlighter {
* 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.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
*/
export function bodyToHtml(content, highlights, opts={}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
-
- const doEmojiOne = opts.emojiOne === undefined ? true : opts.emojiOne;
let bodyHasEmoji = false;
let sanitizeParams = sanitizeHtmlParams;
@@ -481,28 +425,12 @@ export function bodyToHtml(content, highlights, opts={}) {
if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody);
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
if (isHtmlMessage) {
isDisplayedWithHtml = true;
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 {
delete sanitizeParams.textFilter;
@@ -545,12 +473,6 @@ export function bodyToHtml(content, highlights, opts={}) {
{ strippedBody };
}
-export function emojifyText(text, addAlt) {
- return {
- __html: unicodeToImage(escape(text), addAlt),
- };
-}
-
/**
* Linkifies the given string. This is a wrapper around 'linkifyjs/string'.
*
diff --git a/src/RichText.js b/src/RichText.js
deleted file mode 100644
index 3e8f834da6..0000000000
--- a/src/RichText.js
+++ /dev/null
@@ -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;
- }
- });
-}
diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js
index 704cdbd55d..50da9266dd 100644
--- a/src/autocomplete/EmojiProvider.js
+++ b/src/autocomplete/EmojiProvider.js
@@ -19,7 +19,6 @@ limitations under the License.
import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
-import {shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione';
import QueryMatcher from './QueryMatcher';
import sdk from '../index';
import {PillCompletion} from './Components';
@@ -27,7 +26,11 @@ import type {Completion, SelectionRange} from './Autocompleter';
import _uniq from 'lodash/uniq';
import _sortBy from 'lodash/sortBy';
import SettingsStore from "../settings/SettingsStore";
+import { shortcodeToUnicode } from './HtmlUtils';
+import UNICODE_REGEX from 'emojibase-regex';
+import EMOTICON_REGEX from 'emojibase-regex/emoticon';
+import SHORTCODE_REGEX from 'emojibase-regex/shortcode';
import EmojiData from '../stripped-emoji.json';
const LIMIT = 20;
@@ -44,15 +47,15 @@ const CATEGORY_ORDER = [
'modifier',
];
-// Match for ":wink:" or ascii-style ";-)" provided by emojione
+// Match for ":wink:" or ascii-style ";-)" provided by emojibase
// (^|\s|(emojiUnicode)) to make sure we're either at the start of the string or there's a
// 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');
+const EMOJI_REGEX = new RegExp('(?:^|\\s|' + UNICODE_REGEX + ')(' + EMOTICON_REGEX + '|:[+-\\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 + ')');
+const MATCH_PREFIX_REGEX = new RegExp('(\\s|' + UNICODE_REGEX + ')');
const EMOJI_SHORTNAMES = Object.keys(EmojiData).map((key) => EmojiData[key]).sort(
(a, b) => {
@@ -101,8 +104,6 @@ export default class EmojiProvider extends AutocompleteProvider {
return []; // don't give any suggestions if the user doesn't want them
}
- const EmojiText = sdk.getComponent('views.elements.EmojiText');
-
let completions = [];
const {command, range} = this.getCurrentCommand(query, selection);
if (command) {
@@ -133,12 +134,12 @@ export default class EmojiProvider extends AutocompleteProvider {
completions = _sortBy(_uniq(completions), sorters);
completions = completions.map((result) => {
- const {shortname} = result;
- const unicode = shortnameToUnicode(shortname);
+ const { shortname } = result;
+ const unicode = shortcodeToUnicode(shortname);
return {
completion: unicode,
component: (
- { unicode }} />
+ { unicode }} />
),
range,
};
diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js
index 1df0581b89..b30fc49c56 100644
--- a/src/components/structures/RoomStatusBar.js
+++ b/src/components/structures/RoomStatusBar.js
@@ -303,9 +303,7 @@ module.exports = React.createClass({
},
// return suitable content for the main (text) part of the status bar.
- _getContent: function() {
- const EmojiText = sdk.getComponent('elements.EmojiText');
-
+ _getContent: function() {s
if (this._shouldShowConnectionError()) {
return (