HackMD/public/vendor/emojify/js/emojify.js
2015-05-04 15:53:29 +08:00

381 lines
23 KiB
JavaScript
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function (root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.emojify = factory();
}
}(this, function () {
'use strict';
var emojify = (function () {
/**
* NB!
* The namedEmojiString variable is updated automatically by the
* "update" gulp task. Do not remove the comment as this will
* cause the gulp task to stop working.
*/
var namedEmojiString =
/*##EMOJILIST*/"+1,-1,100,1234,8ball,a,ab,abc,abcd,accept,aerial_tramway,airplane,alarm_clock,alien,ambulance,anchor,angel,anger,angry,anguished,ant,apple,aquarius,aries,arrow_backward,arrow_double_down,arrow_double_up,arrow_down,arrow_down_small,arrow_forward,arrow_heading_down,arrow_heading_up,arrow_left,arrow_lower_left,arrow_lower_right,arrow_right,arrow_right_hook,arrow_up,arrow_up_down,arrow_up_small,arrow_upper_left,arrow_upper_right,arrows_clockwise,arrows_counterclockwise,art,articulated_lorry,astonished,atm,b,baby,baby_bottle,baby_chick,baby_symbol,back,baggage_claim,balloon,ballot_box_with_check,bamboo,banana,bangbang,bank,bar_chart,barber,baseball,basketball,bath,bathtub,battery,bear,bee,beer,beers,beetle,beginner,bell,bento,bicyclist,bike,bikini,bird,birthday,black_circle,black_joker,black_medium_small_square,black_medium_square,black_nib,black_small_square,black_square,black_square_button,blossom,blowfish,blue_book,blue_car,blue_heart,blush,boar,boat,bomb,book,bookmark,bookmark_tabs,books,boom,boot,bouquet,bow,bowling,bowtie,boy,bread,bride_with_veil,bridge_at_night,briefcase,broken_heart,bug,bulb,bullettrain_front,bullettrain_side,bus,busstop,bust_in_silhouette,busts_in_silhouette,cactus,cake,calendar,calling,camel,camera,cancer,candy,capital_abcd,capricorn,car,card_index,carousel_horse,cat,cat2,cd,chart,chart_with_downwards_trend,chart_with_upwards_trend,checkered_flag,cherries,cherry_blossom,chestnut,chicken,children_crossing,chocolate_bar,christmas_tree,church,cinema,circus_tent,city_sunrise,city_sunset,cl,clap,clapper,clipboard,clock1,clock10,clock1030,clock11,clock1130,clock12,clock1230,clock130,clock2,clock230,clock3,clock330,clock4,clock430,clock5,clock530,clock6,clock630,clock7,clock730,clock8,clock830,clock9,clock930,closed_book,closed_lock_with_key,closed_umbrella,cloud,clubs,cn,cocktail,coffee,cold_sweat,collision,computer,confetti_ball,confounded,confused,congratulations,construction,construction_worker,convenience_store,cookie,cool,cop,copyright,corn,couple,couple_with_heart,couplekiss,cow,cow2,credit_card,crocodile,crossed_flags,crown,cry,crying_cat_face,crystal_ball,cupid,curly_loop,currency_exchange,curry,custard,customs,cyclone,dancer,dancers,dango,dart,dash,date,de,deciduous_tree,department_store,diamond_shape_with_a_dot_inside,diamonds,disappointed,disappointed_relieved,dizzy,dizzy_face,do_not_litter,dog,dog2,dollar,dolls,dolphin,donut,door,doughnut,dragon,dragon_face,dress,dromedary_camel,droplet,dvd,e-mail,ear,ear_of_rice,earth_africa,earth_americas,earth_asia,egg,eggplant,eight,eight_pointed_black_star,eight_spoked_asterisk,electric_plug,elephant,email,end,envelope,es,euro,european_castle,european_post_office,evergreen_tree,exclamation,expressionless,eyeglasses,eyes,facepunch,factory,fallen_leaf,family,fast_forward,fax,fearful,feelsgood,feet,ferris_wheel,file_folder,finnadie,fire,fire_engine,fireworks,first_quarter_moon,first_quarter_moon_with_face,fish,fish_cake,fishing_pole_and_fish,fist,five,flags,flashlight,floppy_disk,flower_playing_cards,flushed,foggy,football,fork_and_knife,fountain,four,four_leaf_clover,fr,free,fried_shrimp,fries,frog,frowning,fu,fuelpump,full_moon,full_moon_with_face,game_die,gb,gem,gemini,ghost,gift,gift_heart,girl,globe_with_meridians,goat,goberserk,godmode,golf,grapes,green_apple,green_book,green_heart,grey_exclamation,grey_question,grimacing,grin,grinning,guardsman,guitar,gun,haircut,hamburger,hammer,hamster,hand,handbag,hankey,hash,hatched_chick,hatching_chick,headphones,hear_no_evil,heart,heart_decoration,heart_eyes,heart_eyes_cat,heartbeat,heartpulse,hearts,heavy_check_mark,heavy_division_sign,heavy_dollar_sign,heavy_exclamation_mark,heavy_minus_sign,heavy_multiplication_x,heavy_plus_sign,helicopter,herb,hibiscus,high_brightness,high_heel,hocho,honey_pot,honeybee,horse,horse_racing,hospital,hotel,hotsprings,hourglass,hourglass_flowing_sand,house,house_with_garden,hurtrealbad,hushed,ice_cream,icecream,id,ideograph_advantage,imp,inbox_tray,incoming_envelope,information_desk_person,information_source,innocent,interrobang,iphone,it,izakaya_lantern,jack_o_lantern,japan,japanese_castle,japanese_goblin,japanese_ogre,jeans,joy,joy_cat,jp,key,keycap_ten,kimono,kiss,kissing,kissing_cat,kissing_closed_eyes,kissing_face,kissing_heart,kissing_smiling_eyes,koala,koko,kr,large_blue_circle,large_blue_diamond,large_orange_diamond,last_quarter_moon,last_quarter_moon_with_face,laughing,leaves,ledger,left_luggage,left_right_arrow,leftwards_arrow_with_hook,lemon,leo,leopard,libra,light_rail,link,lips,lipstick,lock,lock_with_ink_pen,lollipop,loop,loudspeaker,love_hotel,love_letter,low_brightness,m,mag,mag_right,mahjong,mailbox,mailbox_closed,mailbox_with_mail,mailbox_with_no_mail,man,man_with_gua_pi_mao,man_with_turban,mans_shoe,maple_leaf,mask,massage,meat_on_bone,mega,melon,memo,mens,metal,metro,microphone,microscope,milky_way,minibus,minidisc,mobile_phone_off,money_with_wings,moneybag,monkey,monkey_face,monorail,moon,mortar_board,mount_fuji,mountain_bicyclist,mountain_cableway,mountain_railway,mouse,mouse2,movie_camera,moyai,muscle,mushroom,musical_keyboard,musical_note,musical_score,mute,nail_care,name_badge,neckbeard,necktie,negative_squared_cross_mark,neutral_face,new,new_moon,new_moon_with_face,newspaper,ng,nine,no_bell,no_bicycles,no_entry,no_entry_sign,no_good,no_mobile_phones,no_mouth,no_pedestrians,no_smoking,non-potable_water,nose,notebook,notebook_with_decorative_cover,notes,nut_and_bolt,o,o2,ocean,octocat,octopus,oden,office,ok,ok_hand,ok_woman,older_man,older_woman,on,oncoming_automobile,oncoming_bus,oncoming_police_car,oncoming_taxi,one,open_file_folder,open_hands,open_mouth,ophiuchus,orange_book,outbox_tray,ox,package,page_facing_up,page_with_curl,pager,palm_tree,panda_face,paperclip,parking,part_alternation_mark,partly_sunny,passport_control,paw_prints,peach,pear,pencil,pencil2,penguin,pensive,performing_arts,persevere,person_frowning,person_with_blond_hair,person_with_pouting_face,phone,pig,pig2,pig_nose,pill,pineapple,pisces,pizza,plus1,point_down,point_left,point_right,point_up,point_up_2,police_car,poodle,poop,post_office,postal_horn,postbox,potable_water,pouch,poultry_leg,pound,pouting_cat,pray,princess,punch,purple_heart,purse,pushpin,put_litter_in_its_place,question,rabbit,rabbit2,racehorse,radio,radio_button,rage,rage1,rage2,rage3,rage4,railway_car,rainbow,raised_hand,raised_hands,raising_hand,ram,ramen,rat,recycle,red_car,red_circle,registered,relaxed,relieved,repeat,repeat_one,restroom,revolving_hearts,rewind,ribbon,rice,rice_ball,rice_cracker,rice_scene,ring,rocket,roller_coaster,rooster,rose,rotating_light,round_pushpin,rowboat,ru,rugby_football,runner,running,running_shirt_with_sash,sa,sagittarius,sailboat,sake,sandal,santa,satellite,satisfied,saxophone,school,school_satchel,scissors,scorpius,scream,scream_cat,scroll,seat,secret,see_no_evil,seedling,seven,shaved_ice,sheep,shell,ship,shipit,shirt,shit,shoe,shower,signal_strength,six,six_pointed_star,ski,skull,sleeping,sleepy,slot_machine,small_blue_diamond,small_orange_diamond,small_red_triangle,small_red_triangle_down,smile,smile_cat,smiley,smiley_cat,smiling_imp,smirk,smirk_cat,smoking,snail,snake,snowboarder,snowflake,snowman,sob,soccer,soon,sos,sound,space_invader,spades,spaghetti,sparkle,sparkler,sparkles,sparkling_heart,speak_no_evil,speaker,speech_balloon,speedboat,squirrel,star,star2,stars,station,statue_of_liberty,steam_locomotive,stew,straight_ruler,strawberry,stuck_out_tongue,stuck_out_tongue_closed_eyes,stuck_out_tongue_winking_eye,sun_with_face,sunflower,sunglasses,sunny,sunrise,sunrise_over_mountains,surfer,sushi,suspect,suspension_railway,sweat,sweat_drops,sweat_smile,sweet_potato,swimmer,symbols,syringe,tada,tanabata_tree,tangerine,taurus,taxi,tea,telephone,telephone_receiver,telescope,tennis,tent,thought_balloon,three,thumbsdown,thumbsup,ticket,tiger,tiger2,tired_face,tm,toilet,tokyo_tower,tomato,tongue,top,tophat,tractor,traffic_light,train,train2,tram,triangular_flag_on_post,triangular_ruler,trident,triumph,trolleybus,trollface,trophy,tropical_drink,tropical_fish,truck,trumpet,tshirt,tulip,turtle,tv,twisted_rightwards_arrows,two,two_hearts,two_men_holding_hands,two_women_holding_hands,u5272,u5408,u55b6,u6307,u6708,u6709,u6e80,u7121,u7533,u7981,u7a7a,uk,umbrella,unamused,underage,unlock,up,us,v,vertical_traffic_light,vhs,vibration_mode,video_camera,video_game,violin,virgo,volcano,vs,walking,waning_crescent_moon,waning_gibbous_moon,warning,watch,water_buffalo,watermelon,wave,wavy_dash,waxing_crescent_moon,waxing_gibbous_moon,wc,weary,wedding,whale,whale2,wheelchair,white_check_mark,white_circle,white_flower,white_large_square,white_medium_small_square,white_medium_square,white_small_square,white_square_button,wind_chime,wine_glass,wink,wolf,woman,womans_clothes,womans_hat,womens,worried,wrench,x,yellow_heart,yen,yum,zap,zero,zzz";
var namedEmoji = namedEmojiString.split(/,/);
/* A hash with the named emoji as keys */
var namedMatchHash = namedEmoji.reduce(function(memo, v) {
memo[v] = true;
return memo;
}, {});
var emoticonsProcessed;
var emojiMegaRe;
function initEmoticonsProcessed() {
/* List of emoticons used in the regular expression */
var emoticons = {
/* :..: */ named: /:([a-z0-9A-Z_-]+):/,
/* :-) */ smile: /:-?\)/g,
/* :-o */ scream: /:-o/gi,
/* :-] */ smirk: /[:;]-?]/g,
/* :-D */ grinning: /[:;]-?d/gi,
/* X-D */ stuck_out_tongue_closed_eyes: /x-d/gi,
/* ;-p */ stuck_out_tongue_winking_eye: /[:;]-?p/gi,
/* :-[ / :-@ */ rage: /:-?[\[@]/g,
/* :-( */ frowning: /:-?\(/g,
/* :'-( */ sob: /:[']-?\(|:'\(/g,
/* :-* */ kissing_heart: /:-?\*/g,
/* ;-) */ wink: /;-?\)/g,
/* :-/ */ pensive: /:-?\//g,
/* :-s */ confounded: /:-?s/gi,
/* :-| */ flushed: /:-?\|/g,
/* :-$ */ relaxed: /:-?\$/g,
/* :-x */ mask: /:-x/gi,
/* <3 */ heart: /<3|&lt;3/g,
/* </3 */ broken_heart: /<\/3|&lt;&#x2F;3/g,
/* :+1: */ thumbsup: /:\+1:/g,
/* :-1: */ thumbsdown: /:\-1:/g
};
if (defaultConfig.ignore_emoticons) {
emoticons = {
/* :..: */ named: /:([a-z0-9A-Z_-]+):/,
/* :+1: */ thumbsup: /:\+1:/g,
/* :-1: */ thumbsdown: /:\-1:/g
};
}
return Object.keys(emoticons).map(function(key) {
return [emoticons[key], key];
});
}
function initMegaRe() {
/* The source for our mega-regex */
var mega = emoticonsProcessed
.map(function(v) {
var re = v[0];
var val = re.source || re;
val = val.replace(/(^|[^\[])\^/g, '$1');
return "(" + val + ")";
})
.join('|');
/* The regex used to find emoji */
return new RegExp(mega, "gi");
}
var defaultConfig = {
blacklist: {
'ids': [],
'classes': ['no-emojify'],
'elements': ['script', 'textarea', 'a', 'pre', 'code']
},
tag_type: null,
only_crawl_id: null,
img_dir: 'images/emoji',
ignore_emoticons: false,
mode: 'img'
};
/* Returns true if the given char is whitespace */
function isWhitespace(s) {
return s === ' ' || s === '\t' || s === '\r' || s === '\n' || s === '' || s === String.fromCharCode(160);
}
var modeToElementTagType = {
'img': 'img',
'sprite': 'span',
'data-uri': 'span'
};
/* Given a match in a node, replace the text with an image */
function insertEmojicon(args) {
var emojiElement = null;
if(args.replacer){
emojiElement = args.replacer.apply({
config: defaultConfig
},
[':' + args.emojiName + ':', args.emojiName]
);
}
else {
var elementType = defaultConfig.tag_type || modeToElementTagType[defaultConfig.mode];
emojiElement = args.win.document.createElement(elementType);
if (elementType !== 'img') {
emojiElement.setAttribute('class', 'emoji emoji-' + args.emojiName);
} else {
emojiElement.setAttribute('align', 'absmiddle');
emojiElement.setAttribute('alt', ':' + args.emojiName + ':');
emojiElement.setAttribute('class', 'emoji');
emojiElement.setAttribute('src', defaultConfig.img_dir + '/' + args.emojiName + '.png');
}
emojiElement.setAttribute('title', ':' + args.emojiName + ':');
}
args.node.splitText(args.match.index);
args.node.nextSibling.nodeValue = args.node.nextSibling.nodeValue.substr(
args.match[0].length,
args.node.nextSibling.nodeValue.length
);
emojiElement.appendChild(args.node.splitText(args.match.index));
args.node.parentNode.insertBefore(emojiElement, args.node.nextSibling);
}
/* Given an regex match, return the name of the matching emoji */
function getEmojiNameForMatch(match) {
/* Special case for named emoji */
if(match[1] && match[2]) {
var named = match[2];
if(namedMatchHash[named]) { return named; }
return;
}
for(var i = 3; i < match.length - 1; i++) {
if(match[i]) {
return emoticonsProcessed[i - 2][1];
}
}
}
function defaultReplacer(emoji, name) {
/*jshint validthis: true */
var elementType = this.config.tag_type || modeToElementTagType[this.config.mode];
if (elementType !== 'img') {
return "<" + elementType +" class='emoji emoji-" + name + "' title=':" + name + ":'></" + elementType+ ">";
} else {
return "<img align='absmiddle' alt=':" + name + ":' class='emoji' src='" + this.config.img_dir + '/' + name + ".png' title=':" + name + ":' />";
}
}
function Validator() {
this.lastEmojiTerminatedAt = -1;
}
Validator.prototype = {
validate: function(match, index, input) {
var self = this;
/* Validator */
var emojiName = getEmojiNameForMatch(match);
if(!emojiName) { return; }
var m = match[0];
var length = m.length;
// var index = match.index;
// var input = match.input;
function success() {
self.lastEmojiTerminatedAt = length + index;
return emojiName;
}
/* At the beginning? */
if(index === 0) { return success(); }
/* At the end? */
if(input.length === m.length + index) { return success(); }
var hasEmojiBefore = this.lastEmojiTerminatedAt === index;
if (hasEmojiBefore) { return success();}
/* Has a whitespace before? */
if(isWhitespace(input.charAt(index - 1))) { return success(); }
var hasWhitespaceAfter = isWhitespace(input.charAt(m.length + index));
/* Has a whitespace after? */
if(hasWhitespaceAfter && hasEmojiBefore) { return success(); }
return;
}
};
function emojifyString (htmlString, replacer) {
if(!htmlString) { return htmlString; }
if(!replacer) { replacer = defaultReplacer; }
emoticonsProcessed = initEmoticonsProcessed();
emojiMegaRe = initMegaRe();
var validator = new Validator();
return htmlString.replace(emojiMegaRe, function() {
var matches = Array.prototype.slice.call(arguments, 0, -2);
var index = arguments[arguments.length - 2];
var input = arguments[arguments.length - 1];
var emojiName = validator.validate(matches, index, input);
if(emojiName) {
return replacer.apply({
config: defaultConfig
},
[arguments[0], emojiName]
);
}
/* Did not validate, return the original value */
return arguments[0];
});
}
function run(el, replacer) {
// Check if an element was not passed.
// This will only work in the browser
if(typeof el === 'undefined'){
// Check if an element was configured. If not, default to the body.
if (defaultConfig.only_crawl_id) {
el = document.getElementById(defaultConfig.only_crawl_id);
} else {
el = document.body;
}
}
// Get the window object from the passed element.
var doc = el.ownerDocument,
win = doc.defaultView || doc.parentWindow;
var treeTraverse = function (parent, cb){
var child;
if (parent.hasChildNodes()) {
child = parent.firstChild;
while(child){
if(cb(child)) {
treeTraverse(child, cb);
}
child = child.nextSibling;
}
}
};
var matchAndInsertEmoji = function(node) {
var match;
var matches = [];
var validator = new Validator();
while ((match = emojiMegaRe.exec(node.data)) !== null) {
if(validator.validate(match, match.index, match.input)) {
matches.push(match);
}
}
for (var i = matches.length; i-- > 0;) {
/* Replace the text with the emoji */
var emojiName = getEmojiNameForMatch(matches[i]);
insertEmojicon({
node: node,
match: matches[i],
emojiName: emojiName,
replacer: replacer,
win: win
});
}
};
emoticonsProcessed = initEmoticonsProcessed();
emojiMegaRe = initMegaRe();
var nodes = [];
var elementsBlacklist = new RegExp(defaultConfig.blacklist.elements.join('|'), 'i'),
classesBlacklist = new RegExp(defaultConfig.blacklist.classes.join('|'), 'i');
if(typeof win.document.createTreeWalker !== 'undefined') {
var nodeIterator = win.document.createTreeWalker(
el,
win.NodeFilter.SHOW_TEXT | win.NodeFilter.SHOW_ELEMENT,
function(node) {
if(node.nodeType !== 1) {
/* Text Node? Good! */
return win.NodeFilter.FILTER_ACCEPT;
}
try {
if(node.tagName.match(elementsBlacklist) || node.className.match(classesBlacklist)) {
return win.NodeFilter.FILTER_REJECT;
}
} catch(err) {
return win.NodeFilter.FILTER_SKIP;
}
return win.NodeFilter.FILTER_SKIP;
},
false
);
var node;
while((node = nodeIterator.nextNode()) !== null) {
nodes.push(node);
}
}
else {
treeTraverse(el, function(node){
if(
(typeof node.tagName !== 'undefined' && node.tagName.match(elementsBlacklist)) ||
(typeof node.className !== 'undefined' && node.className.match(classesBlacklist))
){
return false;
}
if (node.nodeType === 1) {
return true;
}
nodes.push(node);
return true;
});
}
nodes.forEach(matchAndInsertEmoji);
}
return {
// Sane defaults
defaultConfig: defaultConfig,
emojiNames: namedEmoji,
setConfig: function (newConfig) {
Object.keys(defaultConfig).forEach(function(f) {
if(f in newConfig) {
defaultConfig[f] = newConfig[f];
}
});
},
replace: emojifyString,
// Main method
run: run
};
})();
return emojify;
}
));