Merge pull request #395 from aviraldg/fix-emoji

Various fixes and improvements to emojification.
This commit is contained in:
David Baker 2016-08-11 10:23:00 +01:00 committed by GitHub
commit 46899a0086
13 changed files with 136 additions and 34 deletions

View file

@ -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 = `<img class="emojione" title="${title}" alt="${alt}" src="${emojione.imagePathSVG}${unicode}.svg${emojione.cacheBustParam}"/>`;
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)),
};

View file

@ -26,7 +26,7 @@ export default class EmojiProvider extends AutocompleteProvider {
completion: shortnameToUnicode(shortname),
component: (
<div className="mx_Autocomplete_Completion">
<span dangerouslySetInnerHTML={{__html: imageHTML}}></span> {shortname}
<span style={{maxWidth: '1em'}} dangerouslySetInnerHTML={{__html: imageHTML}}></span>&nbsp;&nbsp;{shortname}
</div>
),
range,

View file

@ -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');

View file

@ -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 (
<div className="mx_RoomStatusBar_typingBar">
{typingString}
<EmojiText>{typingString}</EmojiText>
</div>
);
}

View file

@ -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 (
<span className="mx_BaseAvatar" {...otherProps}>
<span className="mx_BaseAvatar_initial" aria-hidden="true"
<EmojiText className="mx_BaseAvatar_initial" aria-hidden="true"
style={{ fontSize: (width * 0.65) + "px",
width: width + "px",
lineHeight: height + "px" }}
dangerouslySetInnerHTML={initialLetter}>
</span>
lineHeight: height + "px" }}>{initialLetter}</EmojiText>
<img className="mx_BaseAvatar_image" src={imageUrl}
alt="" title={title} onError={this.onError}
width={width} height={height} />

View file

@ -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',
};

View file

@ -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 <span />; // emote message must include the name so don't duplicate it
}
return (
<EmojiText className="mx_SenderProfile"
onClick={props.onClick}>{`${name || ''} ${props.aux || ''}`}</EmojiText>
);
}
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,
};

View file

@ -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 (
<span ref="content" className="mx_MEmoteBody mx_EventTile_content">
* { name } { body }
* <EmojiText>{name}</EmojiText> { body }
{ widgets }
</span>
);

View file

@ -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 (
<div className="mx_TextualEvent" dangerouslySetInnerHTML={textHTML}>
</div>
<EmojiText element="div" className="mx_TextualEvent">{text}</EmojiText>
);
},
});

View file

@ -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 = (
<div className="mx_EntityTile_details">
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
<div className="mx_EntityTile_name_hover" dangerouslySetInnerHTML={nameHTML}></div>
<EmojiText element="div" className="mx_EntityTile_name_hover">{name}</EmojiText>
<PresenceLabel activeAgo={ activeAgo }
currentlyActive={this.props.presenceCurrentlyActive}
presenceState={this.props.presenceState} />
@ -123,8 +123,7 @@ module.exports = React.createClass({
}
else {
nameEl = (
<div className="mx_EntityTile_name" dangerouslySetInnerHTML={nameHTML}>
</div>
<EmojiText element="div" className="mx_EntityTile_name">{name}</EmojiText>
);
}

View file

@ -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({
</div>
}
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 (
<div className="mx_MemberInfo">
<img className="mx_MemberInfo_cancel" src="img/cancel.svg" width="18" height="18" onClick={this.onCancel}/>
@ -648,7 +648,7 @@ module.exports = React.createClass({
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />
</div>
<h2 dangerouslySetInnerHTML={memberNameHTML}></h2>
<EmojiText element="h2">{memberNameHTML}</EmojiText>
<div className="mx_MemberInfo_profile">
<div className="mx_MemberInfo_profileField">

View file

@ -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 =
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
<div className={ "mx_RoomHeader_nametext " + (settingsHint ? "mx_RoomHeader_settingsHint" : "") } title={ roomName } dangerouslySetInnerHTML={roomNameHTML}></div>
<EmojiText element="div" className={ "mx_RoomHeader_nametext " + (settingsHint ? "mx_RoomHeader_settingsHint" : "") } title={ roomName }>{roomName}</EmojiText>
{ searchStatus }
</div>
</div>;
}
if (can_set_room_topic) {

View file

@ -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 = <div className={ badgeClasses } onClick={this.onBadgeClicked} onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}>{ badgeContent }</div>;
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 = <span dangerouslySetInnerHTML={nameHTML}></span>;
let nameSelected = <EmojiText>{name}</EmojiText>;
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
} else {
label = <div title={ name } className={ nameClasses } dangerouslySetInnerHTML={nameHTML}></div>;
label = <EmojiText element="div" title={ name } className={ nameClasses }>{name}</EmojiText>;
}
}
else if (this.state.hover) {