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 = `${alt}`; - } 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 {alt}; +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 (
diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index 47de7c9dc4..5a6c5ca581 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -166,7 +166,6 @@ module.exports = React.createClass({ }, render: function() { - const EmojiText = sdk.getComponent('elements.EmojiText'); const imageUrl = this.state.imageUrls[this.state.urlsIndex]; const { @@ -178,13 +177,13 @@ module.exports = React.createClass({ if (imageUrl === this.state.defaultImageUrl) { const initialLetter = this._getInitialLetter(name); const textNode = ( - + ); const imgNode = ( - - { summaries.join(", ") } - + { summaries.join(", ") } ); }, diff --git a/src/components/views/groups/GroupInviteTile.js b/src/components/views/groups/GroupInviteTile.js index 8482bce593..843bb29055 100644 --- a/src/components/views/groups/GroupInviteTile.js +++ b/src/components/views/groups/GroupInviteTile.js @@ -117,7 +117,6 @@ export default React.createClass({ render: function() { const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); - const EmojiText = sdk.getComponent('elements.EmojiText'); const groupName = this.props.group.name || this.props.group.groupId; const httpAvatarUrl = this.props.group.avatarUrl ? @@ -129,9 +128,9 @@ export default React.createClass({ 'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed, }); - const label = + const label =
{ groupName } - ; +
; const badgeEllipsis = this.state.badgeHover || this.state.menuDisplayed; const badgeClasses = classNames('mx_RoomTile_badge mx_RoomTile_highlight', { diff --git a/src/components/views/groups/GroupMemberInfo.js b/src/components/views/groups/GroupMemberInfo.js index a25d4271ed..34a7e139fd 100644 --- a/src/components/views/groups/GroupMemberInfo.js +++ b/src/components/views/groups/GroupMemberInfo.js @@ -180,7 +180,6 @@ module.exports = React.createClass({ this.props.groupMember.displayname || this.props.groupMember.userId ); - const EmojiText = sdk.getComponent('elements.EmojiText'); const GeminiScrollbarWrapper = sdk.getComponent('elements.GeminiScrollbarWrapper'); return (
@@ -189,7 +188,7 @@ module.exports = React.createClass({ { avatarElement } - { groupMemberName } +

{ groupMemberName }

diff --git a/src/components/views/groups/GroupRoomInfo.js b/src/components/views/groups/GroupRoomInfo.js index df1803fa11..db060218d4 100644 --- a/src/components/views/groups/GroupRoomInfo.js +++ b/src/components/views/groups/GroupRoomInfo.js @@ -149,7 +149,6 @@ module.exports = React.createClass({ }, render: function() { - const EmojiText = sdk.getComponent('elements.EmojiText'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const InlineSpinner = sdk.getComponent('elements.InlineSpinner'); const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); @@ -221,7 +220,7 @@ module.exports = React.createClass({ { avatarElement } - { groupRoomName } +

{ groupRoomName }

diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.js b/src/components/views/messages/ReactionsRowButtonTooltip.js index 4f26cea708..5a71bbdf84 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.js +++ b/src/components/views/messages/ReactionsRowButtonTooltip.js @@ -19,7 +19,7 @@ import PropTypes from 'prop-types'; import MatrixClientPeg from '../../../MatrixClientPeg'; import sdk from '../../../index'; -import { unicodeToShort } from '../../../HtmlUtils'; +import { unicodeToShortcode } from '../../../HtmlUtils'; import { _t } from '../../../languageHandler'; export default class ReactionsRowButtonTooltip extends React.PureComponent { @@ -45,7 +45,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent { const { name } = room.getMember(reactionEvent.getSender()); senders.push(name); } - const shortName = unicodeToShort(content) || content; + const shortName = unicodeToShortcode(content) || content; tooltipLabel =
{_t( "reacted with %(shortName)s", { diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index 75898736f1..2ccf5a3315 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -95,7 +95,6 @@ export default React.createClass({ }, render() { - const EmojiText = sdk.getComponent('elements.EmojiText'); const {mxEvent} = this.props; const colorClass = getUserNameColorClass(mxEvent.getSender()); const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); @@ -117,7 +116,7 @@ export default React.createClass({ />; } - const nameElem = { name || '' }; + const nameElem = name || ''; // Name + flair const nameFlair = diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index ea7f634691..3d9807878d 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -463,7 +463,6 @@ module.exports = React.createClass({ }, render: function() { - const EmojiText = sdk.getComponent('elements.EmojiText'); const mxEvent = this.props.mxEvent; const content = mxEvent.getContent(); @@ -502,12 +501,12 @@ module.exports = React.createClass({ return ( *  - { name } - +   { body } { widgets } diff --git a/src/components/views/messages/TextualEvent.js b/src/components/views/messages/TextualEvent.js index 6c87000615..c94e79f2d9 100644 --- a/src/components/views/messages/TextualEvent.js +++ b/src/components/views/messages/TextualEvent.js @@ -31,11 +31,10 @@ module.exports = React.createClass({ }, render: function() { - const EmojiText = sdk.getComponent('elements.EmojiText'); const text = TextForEvent.textForEvent(this.props.mxEvent); if (text == null || text.length === 0) return null; return ( - { text } +
{ text }
); }, }); diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js index a19a4eaad0..466deeba28 100644 --- a/src/components/views/rooms/Autocomplete.js +++ b/src/components/views/rooms/Autocomplete.js @@ -256,8 +256,6 @@ export default class Autocomplete extends React.Component { } render() { - const EmojiText = sdk.getComponent('views.elements.EmojiText'); - let position = 1; const renderedCompletions = this.state.completions.map((completionResult, i) => { const completions = completionResult.completions.map((completion, i) => { @@ -282,7 +280,7 @@ export default class Autocomplete extends React.Component { return completions.length > 0 ? (
- { completionResult.provider.getName() } +
{ completionResult.provider.getName() }
{ completionResult.provider.renderCompletions(completions) }
) : null; diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js index d2ae6217b7..bfeeced339 100644 --- a/src/components/views/rooms/EntityTile.js +++ b/src/components/views/rooms/EntityTile.js @@ -111,7 +111,6 @@ const EntityTile = React.createClass({ let nameEl; const {name} = this.props; - const EmojiText = sdk.getComponent('elements.EmojiText'); if (!this.props.suppressOnHover) { const activeAgo = this.props.presenceLastActiveAgo ? (Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1; @@ -128,24 +127,24 @@ const EntityTile = React.createClass({ } nameEl = (
- +
{ name } - +
{presenceLabel}
); } else if (this.props.subtextLabel) { nameEl = (
- +
{name} - +
{this.props.subtextLabel}
); } else { nameEl = ( - { name } +
{ name }
); } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f38e3c3946..2267c942ba 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -674,14 +674,13 @@ module.exports = withMatrixClient(React.createClass({ switch (this.props.tileShape) { case 'notif': { - const EmojiText = sdk.getComponent('elements.EmojiText'); const room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId()); return (
{ avatar } diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index b9eea2b455..3c098b3d7a 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -978,7 +978,6 @@ module.exports = withMatrixClient(React.createClass({ } const GeminiScrollbarWrapper = sdk.getComponent("elements.GeminiScrollbarWrapper"); - const EmojiText = sdk.getComponent('elements.EmojiText'); let backButton; if (this.props.member.roomId) { @@ -993,7 +992,7 @@ module.exports = withMatrixClient(React.createClass({
{ backButton } { e2eIconElement } - { memberName } +

{ memberName }

{ avatarElement }
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index e54ddd6787..536c077a06 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -51,10 +51,9 @@ import ContentMessages from '../../../ContentMessages'; import {MATRIXTO_URL_PATTERN} from '../../../linkify-matrix'; -import { - asciiRegexp, unicodeRegexp, shortnameToUnicode, - asciiList, mapUnicodeToShort, toShort, -} from 'emojione'; +import EMOJIBASE from 'emojibase-data/en/compact.json'; +import EMOTICON_REGEX from 'emojibase-regex/emoticon'; + import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore"; import {makeUserPermalink} from "../../../matrix-to"; import ReplyPreview from "./ReplyPreview"; @@ -63,9 +62,7 @@ import ReplyThread from "../elements/ReplyThread"; import {ContentHelpers} from 'matrix-js-sdk'; import AccessibleButton from '../elements/AccessibleButton'; -const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort(); -const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + asciiRegexp + ')\\s$'); -const EMOJI_REGEX = new RegExp(unicodeRegexp, 'g'); +const REGEX_EMOJI_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX + ')\\s$'); const TYPING_USER_TIMEOUT = 10000; const TYPING_SERVER_TIMEOUT = 30000; @@ -273,9 +270,8 @@ export default class MessageComposerInput extends React.Component { case 'emoji': // XXX: apparently you can't return plain strings from serializer rules // until https://github.com/ianstormtaylor/slate/pull/1854 is merged. - // So instead we temporarily wrap emoji from RTE in an arbitrary tag - // (). would be nicer, but in practice it causes CSS issues. - return { obj.data.get('emojiUnicode') }; + // So instead we temporarily wrap emoji from RTE in a span. + return { obj.data.get('emojiUnicode') }; } return this.renderNode({ node: obj, @@ -375,7 +371,6 @@ export default class MessageComposerInput extends React.Component { const html = HtmlUtils.bodyToHtml(payload.event.getContent(), null, { forComposerQuote: true, returnString: true, - emojiOne: false, }); const fragment = this.html.deserialize(html); // FIXME: do we want to put in a permalink to the original quote here? @@ -540,10 +535,7 @@ export default class MessageComposerInput extends React.Component { // The first matched group includes just the matched plaintext emoji const emojiMatch = REGEX_EMOJI_WHITESPACE.exec(text.slice(0, currentStartOffset)); if (emojiMatch) { - // plaintext -> hex unicode - const emojiUc = asciiList[emojiMatch[1]]; - // hex unicode -> shortname -> actual unicode - const unicodeEmoji = shortnameToUnicode(EMOJI_UNICODE_TO_SHORTNAME[emojiUc]); + const unicodeEmoji = EMOJIBASE.find(e => e.emoticon && e.emoticon.contains(emoijMatch[1])); const range = Range.create({ anchor: { @@ -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) { let blockType = editorState.blocks.first().type; // 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 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. editorState = change.value; @@ -1473,20 +1417,8 @@ export default class MessageComposerInput extends React.Component { ; } } - case 'emoji': { - const { data } = node; - const emojiUnicode = 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 {; - } + case 'emoji': + return data.get('emojiUnicode'); } }; diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index 7599c5c308..3b7874a875 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -66,13 +66,12 @@ export default class ReplyPreview extends React.Component { if (!this.state.event) return null; const EventTile = sdk.getComponent('rooms.EventTile'); - const EmojiText = sdk.getComponent('views.elements.EmojiText'); return
- +
{ '💬 ' + _t('Replying') } - +
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 435b41f828..a40746dd04 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -147,7 +147,6 @@ module.exports = React.createClass({ render: function() { const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); - const EmojiText = sdk.getComponent('elements.EmojiText'); let searchStatus = null; let cancelButton = null; @@ -191,10 +190,10 @@ module.exports = React.createClass({ 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 =
- { roomName } +
{ roomName }
{ searchStatus }
; diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 93b4a59fca..f67a7d55c7 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -342,7 +342,6 @@ module.exports = React.createClass({ badge =
{ badgeContent }
; } - const EmojiText = sdk.getComponent('elements.EmojiText'); let label; let subtextLabel; let tooltip; @@ -354,14 +353,7 @@ module.exports = React.createClass({ }); subtextLabel = subtext ? { subtext } : null; - - if (this.state.selected) { - const nameSelected = { name }; - - label =
{ nameSelected }
; - } else { - label = { name }; - } + label =
{ nameSelected }
; } else if (this.state.hover) { const Tooltip = sdk.getComponent("elements.Tooltip"); tooltip = ; diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index eb5e14876d..08fc6d2c70 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -212,15 +212,13 @@ module.exports = React.createClass({ return (
); } - const EmojiText = sdk.getComponent('elements.EmojiText'); - return (
  • { this._renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) }
    - { typingString } + { typingString }
  • ); diff --git a/src/components/views/settings/KeyBackupPanel.js b/src/components/views/settings/KeyBackupPanel.js index 2ba05a0e6b..d8ed959dae 100644 --- a/src/components/views/settings/KeyBackupPanel.js +++ b/src/components/views/settings/KeyBackupPanel.js @@ -174,14 +174,13 @@ export default class KeyBackupPanel extends React.PureComponent { } else if (this.state.loading) { return ; } else if (this.state.backupInfo) { - const EmojiText = sdk.getComponent('elements.EmojiText'); let clientBackupStatus; let restoreButtonCaption = _t("Restore from Backup"); if (MatrixClientPeg.get().getKeyBackupEnabled()) { clientBackupStatus =

    {encryptedMessageAreEncrypted}

    -

    {_t("This device is backing up your keys. ")}✅

    +

    {_t("This device is backing up your keys. ")}✅

    ; } else { clientBackupStatus =
    diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js index a2531800e5..e7846a0199 100644 --- a/src/components/views/verification/VerificationShowSas.js +++ b/src/components/views/verification/VerificationShowSas.js @@ -36,7 +36,6 @@ export default class VerificationShowSas extends React.Component { render() { const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - const EmojiText = sdk.getComponent('views.elements.EmojiText'); let sasDisplay; let sasCaption; @@ -44,7 +43,7 @@ export default class VerificationShowSas extends React.Component { const emojiBlocks = this.props.sas.emoji.map( (emoji, i) =>
    - {emoji[0]} + { emoji[0] }
    {_t(capFirst(emoji[1]))}