Merge pull request #332 from aviraldg/feature-emojione

feat: render unicode emoji as emojione images
pull/21833/head
David Baker 2016-07-05 10:18:33 +01:00 committed by GitHub
commit 63ad57a8d4
7 changed files with 68 additions and 43 deletions

View File

@ -20,6 +20,11 @@ var React = require('react');
var sanitizeHtml = require('sanitize-html'); var sanitizeHtml = require('sanitize-html');
var highlight = require('highlight.js'); var highlight = require('highlight.js');
var linkifyMatrix = require('./linkify-matrix'); var linkifyMatrix = require('./linkify-matrix');
import escape from 'lodash/escape';
import emojione from 'emojione';
import classNames from 'classnames';
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
var sanitizeHtmlParams = { var sanitizeHtmlParams = {
allowedTags: [ allowedTags: [
@ -187,40 +192,41 @@ module.exports = {
opts = opts || {}; opts = opts || {};
var isHtml = (content.format === "org.matrix.custom.html"); var isHtml = (content.format === "org.matrix.custom.html");
let body = isHtml ? content.formatted_body : escape(content.body);
var safeBody; var safeBody;
if (isHtml) { // XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying // to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which // are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted // by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either try {
try {
if (highlights && highlights.length > 0) {
var highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
var safeHighlights = highlights.map(function(highlight) {
return sanitizeHtml(highlight, sanitizeHtmlParams);
});
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure.
sanitizeHtmlParams.textFilter = function(safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join('');
};
}
safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams);
}
finally {
delete sanitizeHtmlParams.textFilter;
}
return <span className="markdown-body" dangerouslySetInnerHTML={{ __html: safeBody }} />;
} else {
safeBody = content.body;
if (highlights && highlights.length > 0) { if (highlights && highlights.length > 0) {
var highlighter = new TextHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); var highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
return highlighter.applyHighlights(safeBody, highlights); var safeHighlights = highlights.map(function(highlight) {
} return sanitizeHtml(highlight, sanitizeHtmlParams);
else { });
return safeBody; // XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure.
sanitizeHtmlParams.textFilter = function(safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join('');
};
} }
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
emojione.imageType = 'svg';
safeBody = emojione.unicodeToImage(safeBody);
} }
finally {
delete sanitizeHtmlParams.textFilter;
}
EMOJI_REGEX.lastIndex = 0;
let contentBodyTrimmed = content.body.trim();
let match = EMOJI_REGEX.exec(contentBodyTrimmed);
let emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length;
let className = classNames('markdown-body', {
'emoji-body': emojiBody,
});
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
}, },
highlightDom: function(element) { highlightDom: function(element) {
@ -230,5 +236,11 @@ module.exports = {
} }
}, },
} emojifyText: function(text) {
emojione.imageType = 'svg';
return {
__html: emojione.unicodeToImage(escape(text)),
};
},
};

View File

@ -18,6 +18,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var AvatarLogic = require("../../../Avatar"); var AvatarLogic = require("../../../Avatar");
import {emojifyText} from '../../../HtmlUtils';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'BaseAvatar', displayName: 'BaseAvatar',
@ -137,14 +138,14 @@ module.exports = React.createClass({
var imageUrl = this.state.imageUrls[this.state.urlsIndex]; var imageUrl = this.state.imageUrls[this.state.urlsIndex];
if (imageUrl === this.state.defaultImageUrl) { if (imageUrl === this.state.defaultImageUrl) {
var initialLetter = this._getInitialLetter(this.props.name); var initialLetter = emojifyText(this._getInitialLetter(this.props.name));
return ( return (
<span className="mx_BaseAvatar" {...this.props}> <span className="mx_BaseAvatar" {...this.props}>
<span className="mx_BaseAvatar_initial" aria-hidden="true" <span className="mx_BaseAvatar_initial" aria-hidden="true"
style={{ fontSize: (this.props.width * 0.65) + "px", style={{ fontSize: (this.props.width * 0.65) + "px",
width: this.props.width + "px", width: this.props.width + "px",
lineHeight: this.props.height + "px" }}> lineHeight: this.props.height + "px" }}
{ initialLetter } dangerouslySetInnerHTML={initialLetter}>
</span> </span>
<img className="mx_BaseAvatar_image" src={imageUrl} <img className="mx_BaseAvatar_image" src={imageUrl}
alt="" title={this.props.title} onError={this.onError} alt="" title={this.props.title} onError={this.onError}

View File

@ -19,6 +19,7 @@ limitations under the License.
var React = require('react'); var React = require('react');
var TextForEvent = require('../../../TextForEvent'); var TextForEvent = require('../../../TextForEvent');
import {emojifyText} from '../../../HtmlUtils';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'TextualEvent', displayName: 'TextualEvent',
@ -31,11 +32,11 @@ module.exports = React.createClass({
render: function() { render: function() {
var text = TextForEvent.textForEvent(this.props.mxEvent); var text = TextForEvent.textForEvent(this.props.mxEvent);
if (text == null || text.length == 0) return null; if (text == null || text.length === 0) return null;
let textHTML = emojifyText(TextForEvent.textForEvent(this.props.mxEvent));
return ( return (
<div className="mx_TextualEvent"> <div className="mx_TextualEvent" dangerouslySetInnerHTML={textHTML}>
{TextForEvent.textForEvent(this.props.mxEvent)}
</div> </div>
); );
}, },

View File

@ -20,6 +20,7 @@ var React = require('react');
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index'); var sdk = require('../../../index');
import {emojifyText} from '../../../HtmlUtils';
var PRESENCE_CLASS = { var PRESENCE_CLASS = {
@ -82,6 +83,7 @@ module.exports = React.createClass({
var mainClassName = "mx_EntityTile "; var mainClassName = "mx_EntityTile ";
mainClassName += presenceClass + (this.props.className ? (" " + this.props.className) : ""); mainClassName += presenceClass + (this.props.className ? (" " + this.props.className) : "");
var nameEl; var nameEl;
let nameHTML = emojifyText(this.props.name);
if (this.state.hover && !this.props.suppressOnHover) { if (this.state.hover && !this.props.suppressOnHover) {
var activeAgo = this.props.presenceLastActiveAgo ? var activeAgo = this.props.presenceLastActiveAgo ?
@ -92,7 +94,7 @@ module.exports = React.createClass({
nameEl = ( nameEl = (
<div className="mx_EntityTile_details"> <div className="mx_EntityTile_details">
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/> <img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
<div className="mx_EntityTile_name_hover">{ this.props.name }</div> <div className="mx_EntityTile_name_hover" dangerouslySetInnerHTML={nameHTML}></div>
<PresenceLabel activeAgo={ activeAgo } <PresenceLabel activeAgo={ activeAgo }
currentlyActive={this.props.presenceCurrentlyActive} currentlyActive={this.props.presenceCurrentlyActive}
presenceState={this.props.presenceState} /> presenceState={this.props.presenceState} />
@ -101,8 +103,7 @@ module.exports = React.createClass({
} }
else { else {
nameEl = ( nameEl = (
<div className="mx_EntityTile_name"> <div className="mx_EntityTile_name" dangerouslySetInnerHTML={nameHTML}>
{ this.props.name }
</div> </div>
); );
} }

View File

@ -32,6 +32,7 @@ var Modal = require("../../../Modal");
var sdk = require('../../../index'); var sdk = require('../../../index');
var UserSettingsStore = require('../../../UserSettingsStore'); var UserSettingsStore = require('../../../UserSettingsStore');
var createRoom = require('../../../createRoom'); var createRoom = require('../../../createRoom');
import {emojifyText} from '../../../HtmlUtils';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MemberInfo', displayName: 'MemberInfo',
@ -601,6 +602,8 @@ module.exports = React.createClass({
</div> </div>
} }
let memberNameHTML = emojifyText(this.props.member.name);
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
var PowerSelector = sdk.getComponent('elements.PowerSelector'); var PowerSelector = sdk.getComponent('elements.PowerSelector');
return ( return (
@ -610,7 +613,7 @@ module.exports = React.createClass({
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} /> <MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />
</div> </div>
<h2>{ this.props.member.name }</h2> <h2 dangerouslySetInnerHTML={memberNameHTML}></h2>
<div className="mx_MemberInfo_profile"> <div className="mx_MemberInfo_profile">
<div className="mx_MemberInfo_profileField"> <div className="mx_MemberInfo_profileField">

View File

@ -24,6 +24,7 @@ var Modal = require("../../../Modal");
var linkify = require('linkifyjs'); var linkify = require('linkifyjs');
var linkifyElement = require('linkifyjs/element'); var linkifyElement = require('linkifyjs/element');
var linkifyMatrix = require('../../../linkify-matrix'); var linkifyMatrix = require('../../../linkify-matrix');
import {emojifyText} from '../../../HtmlUtils';
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -211,9 +212,11 @@ module.exports = React.createClass({
roomName = this.props.room.name; roomName = this.props.room.name;
} }
let roomNameHTML = emojifyText(roomName);
name = name =
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}> <div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
<div className={ "mx_RoomHeader_nametext " + (settingsHint ? "mx_RoomHeader_settingsHint" : "") } title={ roomName }>{ roomName }</div> <div className={ "mx_RoomHeader_nametext " + (settingsHint ? "mx_RoomHeader_settingsHint" : "") } title={ roomName } dangerouslySetInnerHTML={roomNameHTML}></div>
{ searchStatus } { searchStatus }
<div className="mx_RoomHeader_settingsButton" title="Settings"> <div className="mx_RoomHeader_settingsButton" title="Settings">
<TintableSvg src="img/settings.svg" width="12" height="12"/> <TintableSvg src="img/settings.svg" width="12" height="12"/>

View File

@ -21,6 +21,7 @@ var classNames = require('classnames');
var dis = require("../../../dispatcher"); var dis = require("../../../dispatcher");
var MatrixClientPeg = require('../../../MatrixClientPeg'); var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index'); var sdk = require('../../../index');
import {emojifyText} from '../../../HtmlUtils';
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomTile', displayName: 'RoomTile',
@ -104,10 +105,13 @@ module.exports = React.createClass({
var label; var label;
if (!this.props.collapsed) { if (!this.props.collapsed) {
var className = 'mx_RoomTile_name' + (this.props.isInvite ? ' mx_RoomTile_invite' : ''); var className = 'mx_RoomTile_name' + (this.props.isInvite ? ' mx_RoomTile_invite' : '');
let nameHTML = emojifyText(name);
if (this.props.selected) { if (this.props.selected) {
name = <span>{ name }</span>; name = <span dangerouslySetInnerHTML={nameHTML}></span>;
label = <div className={ className }>{ name }</div>;
} else {
label = <div className={ className } dangerouslySetInnerHTML={nameHTML}></div>;
} }
label = <div className={ className }>{ name }</div>;
} }
else if (this.state.hover) { else if (this.state.hover) {
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");