diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 2ab635081f..c0792e6d14 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -24,8 +24,39 @@ import escape from 'lodash/escape'; import emojione from 'emojione'; import classNames from 'classnames'; +emojione.imagePathSVG = 'emojione/svg/'; +emojione.imageType = 'svg'; + const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi"); +/* modified from https://github.com/Ranks/emojione/blob/master/lib/js/emojione.js + * because we want to include emoji shortnames in title text + */ +export function unicodeToImage(str) { + let replaceWith, unicode, alt; + 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]; + + // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname + alt = (emojione.unicodeAlt) ? emojione.convert(unicode.toUpperCase()) : mappedUnicode[unicode]; + const title = mappedUnicode[unicode]; + + replaceWith = `${alt}`; + return replaceWith; + } + }); + + return str; +}; + var sanitizeHtmlParams = { allowedTags: [ 'font', // custom to matrix for IRC-style font coloring @@ -211,8 +242,7 @@ module.exports = { }; } safeBody = sanitizeHtml(body, sanitizeHtmlParams); - emojione.imageType = 'svg'; - safeBody = emojione.unicodeToImage(safeBody); + safeBody = unicodeToImage(safeBody); } finally { delete sanitizeHtmlParams.textFilter; @@ -239,7 +269,6 @@ module.exports = { }, emojifyText: function(text) { - emojione.imageType = 'svg'; return { __html: emojione.unicodeToImage(escape(text)), }; diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js index 574144e95b..37a50ee8d8 100644 --- a/src/autocomplete/EmojiProvider.js +++ b/src/autocomplete/EmojiProvider.js @@ -26,7 +26,7 @@ export default class EmojiProvider extends AutocompleteProvider { completion: shortnameToUnicode(shortname), component: (
- {shortname} +   {shortname}
), range, diff --git a/src/component-index.js b/src/component-index.js index 97f8882b82..c7355b5b4c 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -54,6 +54,7 @@ module.exports.components['views.dialogs.SetDisplayNameDialog'] = require('./com module.exports.components['views.dialogs.TextInputDialog'] = require('./components/views/dialogs/TextInputDialog'); module.exports.components['views.elements.EditableText'] = require('./components/views/elements/EditableText'); module.exports.components['views.elements.EditableTextContainer'] = require('./components/views/elements/EditableTextContainer'); +module.exports.components['views.elements.EmojiText'] = require('./components/views/elements/EmojiText'); module.exports.components['views.elements.PowerSelector'] = require('./components/views/elements/PowerSelector'); module.exports.components['views.elements.ProgressBar'] = require('./components/views/elements/ProgressBar'); module.exports.components['views.elements.TintableSvg'] = require('./components/views/elements/TintableSvg'); @@ -72,6 +73,7 @@ module.exports.components['views.messages.MFileBody'] = require('./components/vi module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody'); module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody'); module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent'); +module.exports.components['views.messages.SenderProfile'] = require('./components/views/messages/SenderProfile'); module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody'); module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent'); module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody'); diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 9a0d3dbbdd..c6f2d6500b 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -189,6 +189,7 @@ module.exports = React.createClass({ _getContent: function() { var TabCompleteBar = sdk.getComponent('rooms.TabCompleteBar'); var TintableSvg = sdk.getComponent("elements.TintableSvg"); + const EmojiText = sdk.getComponent('elements.EmojiText'); // no conn bar trumps unread count since you can't get unread messages // without a connection! (technically may already have some but meh) @@ -262,7 +263,7 @@ module.exports = React.createClass({ if (typingString) { return (
- {typingString} + {typingString}
); } diff --git a/src/components/views/avatars/BaseAvatar.js b/src/components/views/avatars/BaseAvatar.js index f0a36c6608..47f0a76891 100644 --- a/src/components/views/avatars/BaseAvatar.js +++ b/src/components/views/avatars/BaseAvatar.js @@ -18,7 +18,7 @@ limitations under the License. var React = require('react'); var AvatarLogic = require("../../../Avatar"); -import {emojifyText} from '../../../HtmlUtils'; +import sdk from '../../../index'; module.exports = React.createClass({ displayName: 'BaseAvatar', @@ -133,6 +133,7 @@ module.exports = React.createClass({ }, render: function() { + const EmojiText = sdk.getComponent('elements.EmojiText'); var imageUrl = this.state.imageUrls[this.state.urlsIndex]; const { @@ -142,15 +143,13 @@ module.exports = React.createClass({ } = this.props; if (imageUrl === this.state.defaultImageUrl) { - var initialLetter = emojifyText(this._getInitialLetter(name)); + const initialLetter = this._getInitialLetter(name); return ( - + lineHeight: height + "px" }}>{initialLetter} diff --git a/src/components/views/elements/EmojiText.js b/src/components/views/elements/EmojiText.js new file mode 100644 index 0000000000..cb6cd2ef5e --- /dev/null +++ b/src/components/views/elements/EmojiText.js @@ -0,0 +1,33 @@ +/* + Copyright 2016 Aviral Dasgupta + + 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 {emojifyText} from '../../../HtmlUtils'; + +export default function EmojiText(props) { + const {element, children, ...restProps} = props; + restProps.dangerouslySetInnerHTML = emojifyText(children); + return React.createElement(element, restProps); +} + +EmojiText.propTypes = { + element: React.PropTypes.string, + children: React.PropTypes.string.isRequired, +}; + +EmojiText.defaultProps = { + element: 'span', +}; diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js new file mode 100644 index 0000000000..9e6fba2127 --- /dev/null +++ b/src/components/views/messages/SenderProfile.js @@ -0,0 +1,42 @@ +/* + Copyright 2015, 2016 OpenMarket 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. + */ + +'use strict'; + +import React from 'react'; +import sdk from '../../../index'; + +export default function SenderProfile(props) { + const EmojiText = sdk.getComponent('elements.EmojiText'); + const {mxEvent} = props; + const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); + const {msgtype} = mxEvent.getContent(); + + if (msgtype === 'm.emote') { + return ; // emote message must include the name so don't duplicate it + } + + return ( + {`${name || ''} ${props.aux || ''}`} + ); +} + +SenderProfile.propTypes = { + mxEvent: React.PropTypes.object.isRequired, // event whose sender we're showing + aux: React.PropTypes.string, // stuff to go after the sender name, if anything + onClick: React.PropTypes.func, +}; diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 8c6cf455dc..19ca9657c2 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -177,6 +177,7 @@ module.exports = React.createClass({ }, render: function() { + const EmojiText = sdk.getComponent('elements.EmojiText'); var mxEvent = this.props.mxEvent; var content = mxEvent.getContent(); var body = HtmlUtils.bodyToHtml(content, this.props.highlights, {}); @@ -200,10 +201,10 @@ module.exports = React.createClass({ switch (content.msgtype) { case "m.emote": - var name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); + const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); return ( - * { name } { body } + * {name} { body } { widgets } ); diff --git a/src/components/views/messages/TextualEvent.js b/src/components/views/messages/TextualEvent.js index 251a44a30a..7cab98ea84 100644 --- a/src/components/views/messages/TextualEvent.js +++ b/src/components/views/messages/TextualEvent.js @@ -19,7 +19,7 @@ limitations under the License. var React = require('react'); var TextForEvent = require('../../../TextForEvent'); -import {emojifyText} from '../../../HtmlUtils'; +import sdk from '../../../index'; module.exports = React.createClass({ displayName: 'TextualEvent', @@ -31,13 +31,11 @@ module.exports = React.createClass({ }, render: function() { + const EmojiText = sdk.getComponent('elements.EmojiText'); var text = TextForEvent.textForEvent(this.props.mxEvent); if (text == null || text.length === 0) return null; - let textHTML = emojifyText(TextForEvent.textForEvent(this.props.mxEvent)); - return ( -
-
+ {text} ); }, }); diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js index 8a99b4c565..d29137ffc2 100644 --- a/src/components/views/rooms/EntityTile.js +++ b/src/components/views/rooms/EntityTile.js @@ -20,7 +20,6 @@ var React = require('react'); var MatrixClientPeg = require('../../../MatrixClientPeg'); var sdk = require('../../../index'); -import {emojifyText} from '../../../HtmlUtils'; var PRESENCE_CLASS = { @@ -103,8 +102,9 @@ module.exports = React.createClass({ var mainClassName = "mx_EntityTile "; mainClassName += presenceClass + (this.props.className ? (" " + this.props.className) : ""); var nameEl; - let nameHTML = emojifyText(this.props.name); + const {name} = this.props; + const EmojiText = sdk.getComponent('elements.EmojiText'); if (this.state.hover && !this.props.suppressOnHover) { var activeAgo = this.props.presenceLastActiveAgo ? (Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1; @@ -114,7 +114,7 @@ module.exports = React.createClass({ nameEl = (
-
+ {name} @@ -123,8 +123,7 @@ module.exports = React.createClass({ } else { nameEl = ( -
-
+ {name} ); } diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index c087e7dc71..0cca402be6 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -32,7 +32,6 @@ var Modal = require("../../../Modal"); var sdk = require('../../../index'); var UserSettingsStore = require('../../../UserSettingsStore'); var createRoom = require('../../../createRoom'); -import {emojifyText} from '../../../HtmlUtils'; module.exports = React.createClass({ displayName: 'MemberInfo', @@ -637,10 +636,11 @@ module.exports = React.createClass({
} - let memberNameHTML = emojifyText(this.props.member.name); + const memberName = this.props.member.name; var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); var PowerSelector = sdk.getComponent('elements.PowerSelector'); + const EmojiText = sdk.getComponent('elements.EmojiText'); return (
@@ -648,7 +648,7 @@ module.exports = React.createClass({
-

+ {memberNameHTML}
diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 8c1b2aaff8..ff023ee043 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -24,7 +24,6 @@ var Modal = require("../../../Modal"); var linkify = require('linkifyjs'); var linkifyElement = require('linkifyjs/element'); var linkifyMatrix = require('../../../linkify-matrix'); -import {emojifyText} from '../../../HtmlUtils'; linkifyMatrix(linkify); @@ -145,6 +144,7 @@ module.exports = React.createClass({ var RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); var ChangeAvatar = sdk.getComponent("settings.ChangeAvatar"); var TintableSvg = sdk.getComponent("elements.TintableSvg"); + const EmojiText = sdk.getComponent('elements.EmojiText'); var header; var name = null; @@ -212,13 +212,12 @@ module.exports = React.createClass({ roomName = this.props.room.name; } - let roomNameHTML = emojifyText(roomName); name =
-
+ {roomName} { searchStatus } -
+
; } if (can_set_room_topic) { diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index 111ead05b7..07b4f2618b 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -22,7 +22,6 @@ var dis = require("../../../dispatcher"); var MatrixClientPeg = require('../../../MatrixClientPeg'); var sdk = require('../../../index'); var ContextualMenu = require('../../structures/ContextualMenu'); -import {emojifyText} from '../../../HtmlUtils'; module.exports = React.createClass({ displayName: 'RoomTile', @@ -187,6 +186,7 @@ module.exports = React.createClass({ badge =
{ badgeContent }
; + const EmojiText = sdk.getComponent('elements.EmojiText'); var label; var tooltip; if (!this.props.collapsed) { @@ -196,13 +196,12 @@ module.exports = React.createClass({ 'mx_RoomTile_badgeShown': this.props.highlight || (notificationCount > 0 && !this.state.areNotifsMuted) || this.state.badgeHover || this.state.menu, }); - let nameHTML = emojifyText(name); if (this.props.selected) { - let nameSelected = ; + let nameSelected = {name}; label =
{ nameSelected }
; } else { - label =
; + label = {name}; } } else if (this.state.hover) {