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 highlight = require('highlight.js'); | ||||
| 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 = { | ||||
|     allowedTags: [ | ||||
|  | @ -187,40 +192,41 @@ module.exports = { | |||
|         opts = opts || {}; | ||||
| 
 | ||||
|         var isHtml = (content.format === "org.matrix.custom.html"); | ||||
|         let body = isHtml ? content.formatted_body : escape(content.body); | ||||
| 
 | ||||
|         var safeBody; | ||||
|         if (isHtml) { | ||||
|             // 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
 | ||||
|             // 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
 | ||||
|             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; | ||||
|         // 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
 | ||||
|         // 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
 | ||||
|         try { | ||||
|             if (highlights && highlights.length > 0) { | ||||
|                 var highlighter = new TextHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); | ||||
|                 return highlighter.applyHighlights(safeBody, highlights); | ||||
|             } | ||||
|             else { | ||||
|                 return safeBody; | ||||
|                 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(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) { | ||||
|  | @ -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 AvatarLogic = require("../../../Avatar"); | ||||
| import {emojifyText} from '../../../HtmlUtils'; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'BaseAvatar', | ||||
|  | @ -137,14 +138,14 @@ module.exports = React.createClass({ | |||
|         var imageUrl = this.state.imageUrls[this.state.urlsIndex]; | ||||
| 
 | ||||
|         if (imageUrl === this.state.defaultImageUrl) { | ||||
|             var initialLetter = this._getInitialLetter(this.props.name); | ||||
|             var initialLetter = emojifyText(this._getInitialLetter(this.props.name)); | ||||
|             return ( | ||||
|                 <span className="mx_BaseAvatar" {...this.props}> | ||||
|                     <span className="mx_BaseAvatar_initial" aria-hidden="true" | ||||
|                             style={{ fontSize: (this.props.width * 0.65) + "px", | ||||
|                                     width: this.props.width + "px", | ||||
|                                     lineHeight: this.props.height + "px" }}> | ||||
|                         { initialLetter } | ||||
|                                     lineHeight: this.props.height + "px" }} | ||||
|                             dangerouslySetInnerHTML={initialLetter}> | ||||
|                     </span> | ||||
|                     <img className="mx_BaseAvatar_image" src={imageUrl} | ||||
|                         alt="" title={this.props.title} onError={this.onError} | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ limitations under the License. | |||
| var React = require('react'); | ||||
| 
 | ||||
| var TextForEvent = require('../../../TextForEvent'); | ||||
| import {emojifyText} from '../../../HtmlUtils'; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'TextualEvent', | ||||
|  | @ -31,11 +32,11 @@ module.exports = React.createClass({ | |||
| 
 | ||||
|     render: function() { | ||||
|         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 ( | ||||
|             <div className="mx_TextualEvent"> | ||||
|                 {TextForEvent.textForEvent(this.props.mxEvent)} | ||||
|             <div className="mx_TextualEvent" dangerouslySetInnerHTML={textHTML}> | ||||
|             </div> | ||||
|         ); | ||||
|     }, | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ var React = require('react'); | |||
| 
 | ||||
| var MatrixClientPeg = require('../../../MatrixClientPeg'); | ||||
| var sdk = require('../../../index'); | ||||
| import {emojifyText} from '../../../HtmlUtils'; | ||||
| 
 | ||||
| 
 | ||||
| var PRESENCE_CLASS = { | ||||
|  | @ -82,6 +83,7 @@ module.exports = React.createClass({ | |||
|         var mainClassName = "mx_EntityTile "; | ||||
|         mainClassName += presenceClass + (this.props.className ? (" " + this.props.className) : ""); | ||||
|         var nameEl; | ||||
|         let nameHTML = emojifyText(this.props.name); | ||||
| 
 | ||||
|         if (this.state.hover && !this.props.suppressOnHover) { | ||||
|             var activeAgo = this.props.presenceLastActiveAgo ? | ||||
|  | @ -92,7 +94,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">{ this.props.name }</div> | ||||
|                     <div className="mx_EntityTile_name_hover" dangerouslySetInnerHTML={nameHTML}></div> | ||||
|                     <PresenceLabel activeAgo={ activeAgo } | ||||
|                         currentlyActive={this.props.presenceCurrentlyActive} | ||||
|                         presenceState={this.props.presenceState} /> | ||||
|  | @ -101,8 +103,7 @@ module.exports = React.createClass({ | |||
|         } | ||||
|         else { | ||||
|             nameEl = ( | ||||
|                 <div className="mx_EntityTile_name"> | ||||
|                     { this.props.name } | ||||
|                 <div className="mx_EntityTile_name" dangerouslySetInnerHTML={nameHTML}> | ||||
|                 </div> | ||||
|             ); | ||||
|         } | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ 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', | ||||
|  | @ -601,6 +602,8 @@ module.exports = React.createClass({ | |||
|                 </div> | ||||
|         } | ||||
| 
 | ||||
|         let memberNameHTML = emojifyText(this.props.member.name); | ||||
| 
 | ||||
|         var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); | ||||
|         var PowerSelector = sdk.getComponent('elements.PowerSelector'); | ||||
|         return ( | ||||
|  | @ -610,7 +613,7 @@ module.exports = React.createClass({ | |||
|                     <MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} /> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <h2>{ this.props.member.name }</h2> | ||||
|                 <h2 dangerouslySetInnerHTML={memberNameHTML}></h2> | ||||
| 
 | ||||
|                 <div className="mx_MemberInfo_profile"> | ||||
|                     <div className="mx_MemberInfo_profileField"> | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ var Modal = require("../../../Modal"); | |||
| var linkify = require('linkifyjs'); | ||||
| var linkifyElement = require('linkifyjs/element'); | ||||
| var linkifyMatrix = require('../../../linkify-matrix'); | ||||
| import {emojifyText} from '../../../HtmlUtils'; | ||||
| 
 | ||||
| linkifyMatrix(linkify); | ||||
| 
 | ||||
|  | @ -211,9 +212,11 @@ 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 }>{ roomName }</div> | ||||
|                     <div className={ "mx_RoomHeader_nametext " + (settingsHint ? "mx_RoomHeader_settingsHint" : "") } title={ roomName } dangerouslySetInnerHTML={roomNameHTML}></div> | ||||
|                     { searchStatus } | ||||
|                     <div className="mx_RoomHeader_settingsButton" title="Settings"> | ||||
|                         <TintableSvg src="img/settings.svg" width="12" height="12"/> | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ var classNames = require('classnames'); | |||
| var dis = require("../../../dispatcher"); | ||||
| var MatrixClientPeg = require('../../../MatrixClientPeg'); | ||||
| var sdk = require('../../../index'); | ||||
| import {emojifyText} from '../../../HtmlUtils'; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'RoomTile', | ||||
|  | @ -104,10 +105,13 @@ module.exports = React.createClass({ | |||
|         var label; | ||||
|         if (!this.props.collapsed) { | ||||
|             var className = 'mx_RoomTile_name' + (this.props.isInvite ? ' mx_RoomTile_invite' : ''); | ||||
|             let nameHTML = emojifyText(name); | ||||
|             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) { | ||||
|             var RoomTooltip = sdk.getComponent("rooms.RoomTooltip"); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 David Baker
						David Baker