mirror of https://github.com/vector-im/riot-web
Merge pull request #332 from aviraldg/feature-emojione
feat: render unicode emoji as emojione imagespull/21833/head
commit
63ad57a8d4
|
@ -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)),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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");
|
||||||
|
|
Loading…
Reference in New Issue