Learning reactjs flux, node, electron ... (Part-20)

fb-messenger Comments

Adding support for receiving emoji and emoticons.

I have two cards on my waffle board for receiving emoji and emoticons. Emoji are unicode characters showing various expressions. Where as emoticons are combination of plain ascii characters to denote emotions eg: “:)” represents happy face.

As of now most of softwares uses image replacements to display the emoji. Pure font support for emoji (e.g Segoe UI) are emerging but it will take time to get mainstream. And probably longer time for chromium to support it. Electron is based on chromium. More over cross platform solution hasn’t emerged yet. So I needed to support emoji with image replacement technique. There are thousands of emojis so it was absolutely necessary to get a 3rd party solution, or else emoji which is just one small feature in whole chat application, would turn into a subproject of its own requiring a lot of attension. Luckily I found one: minEmoji. In fact I had found couple of them:

But minEmoji appealed to me due to its small size and simplicity, and I found it more near to what I really want. It was inline with the way facebook works, replacing unicode emoji directly..

Getting the desired library was one big relief, now another major hurdle was to get it working with current solution. It was mainly done in couple of steps:

1. Preliminary integration

First step was to download minEmoji and integrate into the project. For this purpose I created a spearate folder named “min-emoji” in styles folder:

    |-Styles
        |-font-awesome
        |-min-emoji
            |-css
            |-img
            |-js
        |-main.less

Then I added the artifiacts into the respective folders. After this I added “Link” tag pointing to the css, and “script” tag pointing to the javascript file, in our index.html. To integrate into the code I simply called minEmoji Function:

render() {
		if((this.props.message.attachments || []).length == 0) {
			let justify = {'textAlign':'justify'};
			return (<div className={this.props.className} style={justify}>{minEmoji(this.props.message.body)}</div>);
		} else {
			...
		}
	}

Running it, the span tag generated by minEmoji was printed as it is!! Hence showing something like <span class="em emj186"></span> instead of beautiful emoji icon. So, I had to use dangerouslySetInnerHTML as:

var s = {__html: minEmoji(this.props.message.body)};
return (<div className={this.props.className} style={justify} dangerouslySetInnerHTML={s}></div>);

Finally!! it worked!

2. Safer solution

As it’s name suggests “dangerouslySetInnerHTML” is “dangerous”! Now if someone types in html, it will messup with our rendering, as well as can be used as an attack vector. Had to replace it somehow. Searching for solution I found this stackoverflow post. Good! Now I modified the code in minEmoji returning span like this:

//original code in minEmoji
return s.replace(regx, function (a, b) {
    return '<span class="em emj'+emoji[b]+'"></span>';
})
//changed to:

return s.replace(regx, function (a, b) {
    return '{{emoji:'+ this.emoji[b]+'}}';
});

After this I did simply copy pasted the code suggested in the stackoverflow post, something like this:

var s = minEmoji(this.props.message.body);
var parts = s.split(/\{\{emoji:|\}\}/g);
    var children = this.mapAlternate(parts, 
								(x:string) => { return <span>{x}</span>; }, 
								(x:string) => { return <span className={'em emj'+ x}></span> });
              
    return (<div className={this.props.className} style={justify}>{children}</div>);

Yes!! It did work!

3. Emoticon support

While I was at it, I thought ‘if I can just map emoticon to respective unicode, just before minEmoji replaces unicode to image, then this solution would work as is without much change’. Seems to be my lucky day! found one at emoji-emoticon-to-unicode So, I copied the whole code and pasted into minEmoji code, and just before minEmoji replaces the emoji unicode with image, I replaced emoticon with emoji unicode:

for(var k in emoticons) {
    var regxEmoticon = new RegExp('(' + escapeRegExp(k) + ')', 'g'); 

    s = s.replace(regxEmoticon,  function (a, b) {
        return String.fromCodePoint(parseInt(emoticons[b], 16));
    });
}

return s.replace(regx, function (a, b) {
    return '';
})

Now, emoticons were also replaced! Wasn’t much change in code.

4. More reacty solution

I was quite satisfied with the solution, but I didn’t like the fact that I was adding script tag for minEmoji, and the solution wasn’t fully react. So, I went ahead and created a new component out of minEmoji, the code it self is quite huge due to the amount of mapped data in both minEmoji and emoji-emoticon-to-unicode..

/*! jMinEmoji v1.0.0 | (c) 2014 RodrigoPolo.com | https://github.com/rodrigopolo/minEmoji/blob/master/LICENSE */
//based on minEmoji: https://github.com/rodrigopolo/minEmoji

import * as React from 'react';

export class EmojiProps {
	messageText: string
}

export default class Emojify extends React.Component<EmojiProps, any> {
    private emoji: { [key: string]: number; };
    private emoticons: { [key: string]: string; };
    private regx: RegExp;
    constructor(props: EmojiProps) {
        super();
        this.props = props;
	    this.emoji = {
            //minEmoji mapping...
        };
		
        //Based on: https://github.com/banyan/emoji-emoticon-to-unicode
        // emoticon lists are from https://github.com/Ranks/emojione/blob/master/lib/js/emojione.js
        this.emoticons = {
            //emoji-emoticon-to-unicode mappings...
        };
  
        var regx_arr=new Array<string>();
        for(var k in this.emoji){
            regx_arr.push(this.ca(k));
        }
        this.regx = new RegExp('(' + regx_arr.join('|') + ')', 'g');
        regx_arr = null;
    }
	
	ca(r:string){
		for(var t="",n=0;n<r.length;n++)
			t+="\\u"+("000"+r[n].charCodeAt(0).toString(16)).substr(-4);
		return t;
	}
	
	mapAlternate(array: Array<string>, fn1: Function, fn2: Function) {
		var fn = fn1, output = new Array<React.Component<any, any>>();
		for (var i=0; i<array.length; i++){
			output[i] = fn.call(this, array[i], i, array);
			// toggle between the two functions
			fn = fn === fn1 ? fn2 : fn1;
		}
		return output;
	}
  
    escapeRegExp(str: string) {
        return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    }

    render() {
        var s = this.props.messageText;
    
        for(var k in this.emoticons) {
            var regxEmoticon = new RegExp('(' + this.escapeRegExp(k) + ')', 'g'); 
            if(k == ':/') {
                //mostly based on hit and trial... matches :/ but not followed by /
                regxEmoticon = /(:\/(?!\/))/g;
            }
            s = s.replace(regxEmoticon,  (function (a:string, b:string) {
                return String.fromCodePoint(parseInt(this.emoticons[b], 16));
            }).bind(this));
        }
		
	    s = s.replace(this.regx, (function (a:string, b:string) {
		    return '';
	    }).bind(this));
    
        //@ref: http://stackoverflow.com/questions/24348662/reactjs-how-to-insert-react-component-into-string-and-then-render
        var parts = s.split(/\{\{emoji:|\}\}/g);
        var children = this.mapAlternate(parts, 
								    (x:string) => { return <span>{x}</span>; }, 
								    (x:string) => { return <span className={'em emj'+ x}></span> });
              
        return (<div>{children}</div>);
    }
}

Above code is shown removing the maps, but it is clear that code isn’t changed much from what minEmoji was already doing. It’s almost like renaming “minEmoji” function to “render”.

For usage:

render() {
		if((this.props.message.attachments || []).length == 0) {
			let justify = {'textAlign':'justify'};
			return (<div className={this.props.className} style={justify} >
				<Emojify messageText={this.props.message.body} />
			</div>);
		} else {
			...
		}
}
blog comments powered by Disqus