mirror of
https://github.com/element-hq/element-web
synced 2024-11-24 02:05:45 +03:00
Merge pull request #395 from aviraldg/fix-emoji
Various fixes and improvements to emojification.
This commit is contained in:
commit
46899a0086
13 changed files with 136 additions and 34 deletions
|
@ -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)),
|
||||
};
|
||||
|
|
|
@ -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> {shortname}
|
||||
</div>
|
||||
),
|
||||
range,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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} />
|
||||
|
|
33
src/components/views/elements/EmojiText.js
Normal file
33
src/components/views/elements/EmojiText.js
Normal 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',
|
||||
};
|
42
src/components/views/messages/SenderProfile.js
Normal file
42
src/components/views/messages/SenderProfile.js
Normal 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,
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue