wp/wp-includes/js/twemoji.js
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
    22     /////////////////////////
    22     /////////////////////////
    23     //      properties     //
    23     //      properties     //
    24     /////////////////////////
    24     /////////////////////////
    25 
    25 
    26       // default assets url, by default will be Twitter Inc. CDN
    26       // default assets url, by default will be Twitter Inc. CDN
    27       base: (location.protocol === 'https:' ? 'https:' : 'http:') +
    27       base: 'https://twemoji.maxcdn.com/2/',
    28             '//twemoji.maxcdn.com/',
       
    29 
    28 
    30       // default assets file extensions, by default '.png'
    29       // default assets file extensions, by default '.png'
    31       ext: '.png',
    30       ext: '.png',
    32 
    31 
    33       // default assets/folder size, by default "36x36"
    32       // default assets/folder size, by default "72x72"
    34       // available via Twitter CDN: 16, 36, 72
    33       // available via Twitter CDN: 72
    35       size: '36x36',
    34       size: '72x72',
    36 
    35 
    37       // default class name, by default 'emoji'
    36       // default class name, by default 'emoji'
    38       className: 'emoji',
    37       className: 'emoji',
    39 
    38 
    40       // basic utilities / helpers to convert code points
    39       // basic utilities / helpers to convert code points
    87        * It could be recycled for string operations via:
    86        * It could be recycled for string operations via:
    88        *  $('img.emoji').on('error', twemoji.onerror)
    87        *  $('img.emoji').on('error', twemoji.onerror)
    89        */
    88        */
    90       onerror: function onerror() {
    89       onerror: function onerror() {
    91         if (this.parentNode) {
    90         if (this.parentNode) {
    92           this.parentNode.replaceChild(createText(this.alt), this);
    91           this.parentNode.replaceChild(createText(this.alt, false), this);
    93         }
    92         }
    94       },
    93       },
    95 
    94 
    96       /**
    95       /**
    97        * Main method/logic to generate either <img> tags or HTMLImage nodes.
    96        * Main method/logic to generate either <img> tags or HTMLImage nodes.
   133        *          Function            if specified, this will be invoked per each emoji
   132        *          Function            if specified, this will be invoked per each emoji
   134        *                              that has been found through the RegExp except
   133        *                              that has been found through the RegExp except
   135        *                              those follwed by the invariant \uFE0E ("as text").
   134        *                              those follwed by the invariant \uFE0E ("as text").
   136        *                              Once invoked, parameters will be:
   135        *                              Once invoked, parameters will be:
   137        *
   136        *
   138        *                                codePoint:string  the lower case HEX code point
   137        *                                iconId:string     the lower case HEX code point
   139        *                                                  i.e. "1f4a9"
   138        *                                                  i.e. "1f4a9"
   140        *
   139        *
   141        *                                options:Object    all info for this parsing operation
   140        *                                options:Object    all info for this parsing operation
   142        *
   141        *
   143        *                                variant:char      the optional \uFE0F ("as image")
   142        *                                variant:char      the optional \uFE0F ("as image")
   158        *            size       string    the assets size, by default twemoji.size
   157        *            size       string    the assets size, by default twemoji.size
   159        *
   158        *
   160        * @example
   159        * @example
   161        *
   160        *
   162        *  twemoji.parse("I \u2764\uFE0F emoji!");
   161        *  twemoji.parse("I \u2764\uFE0F emoji!");
   163        *  // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji!
   162        *  // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"/> emoji!
   164        *
   163        *
   165        *
   164        *
   166        *  twemoji.parse("I \u2764\uFE0F emoji!", function(icon, options, variant) {
   165        *  twemoji.parse("I \u2764\uFE0F emoji!", function(iconId, options) {
   167        *    return '/assets/' + icon + '.gif';
   166        *    return '/assets/' + iconId + '.gif';
   168        *  });
   167        *  });
   169        *  // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"> emoji!
   168        *  // I <img class="emoji" draggable="false" alt="❤️" src="/assets/2764.gif"/> emoji!
   170        *
   169        *
   171        *
   170        *
   172        * twemoji.parse("I \u2764\uFE0F emoji!", {
   171        * twemoji.parse("I \u2764\uFE0F emoji!", {
   173        *   size: 72,
   172        *   size: 72,
   174        *   callback: function(icon, options, variant) {
   173        *   callback: function(iconId, options) {
   175        *     return '/assets/' + options.size + '/' + icon + options.ext;
   174        *     return '/assets/' + options.size + '/' + iconId + options.ext;
   176        *   }
   175        *   }
   177        * });
   176        * });
   178        *  // I <img class="emoji" draggable="false" alt="❤️" src="/assets/72x72/2764.png"> emoji!
   177        *  // I <img class="emoji" draggable="false" alt="❤️" src="/assets/72x72/2764.png"/> emoji!
   179        *
   178        *
   180        */
   179        */
   181       parse: parse,
   180       parse: parse,
   182 
   181 
   183       /**
   182       /**
   191        *                    invoked to replace the content.
   190        *                    invoked to replace the content.
   192        *                    This calback wil receive standard
   191        *                    This calback wil receive standard
   193        *                    String.prototype.replace(str, callback)
   192        *                    String.prototype.replace(str, callback)
   194        *                    arguments such:
   193        *                    arguments such:
   195        *  callback(
   194        *  callback(
   196        *    match,  // the emoji match
   195        *    rawText,  // the emoji match
   197        *    icon,   // the emoji text (same as text)
       
   198        *    variant // either '\uFE0E' or '\uFE0F', if present
       
   199        *  );
   196        *  );
   200        *
   197        *
   201        *                    and others commonly received via replace.
   198        *                    and others commonly received via replace.
   202        *
       
   203        *  NOTE: When the variant \uFE0E is found, remember this is an explicit intent
       
   204        *  from the user: the emoji should **not** be replaced with an image.
       
   205        *  In \uFE0F case one, it's the opposite, it should be graphic.
       
   206        *  This utility convetion is that only \uFE0E are not translated into images.
       
   207        */
   199        */
   208       replace: replace,
   200       replace: replace,
   209 
   201 
   210       /**
   202       /**
   211        * Simplify string tests against emoji.
   203        * Simplify string tests against emoji.
   220        *  }
   212        *  }
   221        */
   213        */
   222       test: test
   214       test: test
   223     },
   215     },
   224 
   216 
       
   217     // used to escape HTML special chars in attributes
       
   218     escaper = {
       
   219       '&': '&amp;',
       
   220       '<': '&lt;',
       
   221       '>': '&gt;',
       
   222       "'": '&#39;',
       
   223       '"': '&quot;'
       
   224     },
       
   225 
   225     // RegExp based on emoji's official Unicode standards
   226     // RegExp based on emoji's official Unicode standards
   226     // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
   227     // http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
   227     re = /((?:\ud83c\udde8\ud83c\uddf3|\ud83c\uddfa\ud83c\uddf8|\ud83c\uddf7\ud83c\uddfa|\ud83c\uddf0\ud83c\uddf7|\ud83c\uddef\ud83c\uddf5|\ud83c\uddee\ud83c\uddf9|\ud83c\uddec\ud83c\udde7|\ud83c\uddeb\ud83c\uddf7|\ud83c\uddea\ud83c\uddf8|\ud83c\udde9\ud83c\uddea|\u0039\ufe0f?\u20e3|\u0038\ufe0f?\u20e3|\u0037\ufe0f?\u20e3|\u0036\ufe0f?\u20e3|\u0035\ufe0f?\u20e3|\u0034\ufe0f?\u20e3|\u0033\ufe0f?\u20e3|\u0032\ufe0f?\u20e3|\u0031\ufe0f?\u20e3|\u0030\ufe0f?\u20e3|\u0023\ufe0f?\u20e3|\ud83d\udeb3|\ud83d\udeb1|\ud83d\udeb0|\ud83d\udeaf|\ud83d\udeae|\ud83d\udea6|\ud83d\udea3|\ud83d\udea1|\ud83d\udea0|\ud83d\ude9f|\ud83d\ude9e|\ud83d\ude9d|\ud83d\ude9c|\ud83d\ude9b|\ud83d\ude98|\ud83d\ude96|\ud83d\ude94|\ud83d\ude90|\ud83d\ude8e|\ud83d\ude8d|\ud83d\ude8b|\ud83d\ude8a|\ud83d\ude88|\ud83d\ude86|\ud83d\ude82|\ud83d\ude81|\ud83d\ude36|\ud83d\ude34|\ud83d\ude2f|\ud83d\ude2e|\ud83d\ude2c|\ud83d\ude27|\ud83d\ude26|\ud83d\ude1f|\ud83d\ude1b|\ud83d\ude19|\ud83d\ude17|\ud83d\ude15|\ud83d\ude11|\ud83d\ude10|\ud83d\ude0e|\ud83d\ude08|\ud83d\ude07|\ud83d\ude00|\ud83d\udd67|\ud83d\udd66|\ud83d\udd65|\ud83d\udd64|\ud83d\udd63|\ud83d\udd62|\ud83d\udd61|\ud83d\udd60|\ud83d\udd5f|\ud83d\udd5e|\ud83d\udd5d|\ud83d\udd5c|\ud83d\udd2d|\ud83d\udd2c|\ud83d\udd15|\ud83d\udd09|\ud83d\udd08|\ud83d\udd07|\ud83d\udd06|\ud83d\udd05|\ud83d\udd04|\ud83d\udd02|\ud83d\udd01|\ud83d\udd00|\ud83d\udcf5|\ud83d\udcef|\ud83d\udced|\ud83d\udcec|\ud83d\udcb7|\ud83d\udcb6|\ud83d\udcad|\ud83d\udc6d|\ud83d\udc6c|\ud83d\udc65|\ud83d\udc2a|\ud83d\udc16|\ud83d\udc15|\ud83d\udc13|\ud83d\udc10|\ud83d\udc0f|\ud83d\udc0b|\ud83d\udc0a|\ud83d\udc09|\ud83d\udc08|\ud83d\udc07|\ud83d\udc06|\ud83d\udc05|\ud83d\udc04|\ud83d\udc03|\ud83d\udc02|\ud83d\udc01|\ud83d\udc00|\ud83c\udfe4|\ud83c\udfc9|\ud83c\udfc7|\ud83c\udf7c|\ud83c\udf50|\ud83c\udf4b|\ud83c\udf33|\ud83c\udf32|\ud83c\udf1e|\ud83c\udf1d|\ud83c\udf1c|\ud83c\udf1a|\ud83c\udf18|\ud83c\udccf|\ud83c\udd70|\ud83c\udd71|\ud83c\udd7e|\ud83c\udd8e|\ud83c\udd91|\ud83c\udd92|\ud83c\udd93|\ud83c\udd94|\ud83c\udd95|\ud83c\udd96|\ud83c\udd97|\ud83c\udd98|\ud83c\udd99|\ud83c\udd9a|\ud83d\udc77|\ud83d\udec5|\ud83d\udec4|\ud83d\udec3|\ud83d\udec2|\ud83d\udec1|\ud83d\udebf|\ud83d\udeb8|\ud83d\udeb7|\ud83d\udeb5|\ud83c\ude01|\ud83c\ude02|\ud83c\ude32|\ud83c\ude33|\ud83c\ude34|\ud83c\ude35|\ud83c\ude36|\ud83c\ude37|\ud83c\ude38|\ud83c\ude39|\ud83c\ude3a|\ud83c\ude50|\ud83c\ude51|\ud83c\udf00|\ud83c\udf01|\ud83c\udf02|\ud83c\udf03|\ud83c\udf04|\ud83c\udf05|\ud83c\udf06|\ud83c\udf07|\ud83c\udf08|\ud83c\udf09|\ud83c\udf0a|\ud83c\udf0b|\ud83c\udf0c|\ud83c\udf0f|\ud83c\udf11|\ud83c\udf13|\ud83c\udf14|\ud83c\udf15|\ud83c\udf19|\ud83c\udf1b|\ud83c\udf1f|\ud83c\udf20|\ud83c\udf30|\ud83c\udf31|\ud83c\udf34|\ud83c\udf35|\ud83c\udf37|\ud83c\udf38|\ud83c\udf39|\ud83c\udf3a|\ud83c\udf3b|\ud83c\udf3c|\ud83c\udf3d|\ud83c\udf3e|\ud83c\udf3f|\ud83c\udf40|\ud83c\udf41|\ud83c\udf42|\ud83c\udf43|\ud83c\udf44|\ud83c\udf45|\ud83c\udf46|\ud83c\udf47|\ud83c\udf48|\ud83c\udf49|\ud83c\udf4a|\ud83c\udf4c|\ud83c\udf4d|\ud83c\udf4e|\ud83c\udf4f|\ud83c\udf51|\ud83c\udf52|\ud83c\udf53|\ud83c\udf54|\ud83c\udf55|\ud83c\udf56|\ud83c\udf57|\ud83c\udf58|\ud83c\udf59|\ud83c\udf5a|\ud83c\udf5b|\ud83c\udf5c|\ud83c\udf5d|\ud83c\udf5e|\ud83c\udf5f|\ud83c\udf60|\ud83c\udf61|\ud83c\udf62|\ud83c\udf63|\ud83c\udf64|\ud83c\udf65|\ud83c\udf66|\ud83c\udf67|\ud83c\udf68|\ud83c\udf69|\ud83c\udf6a|\ud83c\udf6b|\ud83c\udf6c|\ud83c\udf6d|\ud83c\udf6e|\ud83c\udf6f|\ud83c\udf70|\ud83c\udf71|\ud83c\udf72|\ud83c\udf73|\ud83c\udf74|\ud83c\udf75|\ud83c\udf76|\ud83c\udf77|\ud83c\udf78|\ud83c\udf79|\ud83c\udf7a|\ud83c\udf7b|\ud83c\udf80|\ud83c\udf81|\ud83c\udf82|\ud83c\udf83|\ud83c\udf84|\ud83c\udf85|\ud83c\udf86|\ud83c\udf87|\ud83c\udf88|\ud83c\udf89|\ud83c\udf8a|\ud83c\udf8b|\ud83c\udf8c|\ud83c\udf8d|\ud83c\udf8e|\ud83c\udf8f|\ud83c\udf90|\ud83c\udf91|\ud83c\udf92|\ud83c\udf93|\ud83c\udfa0|\ud83c\udfa1|\ud83c\udfa2|\ud83c\udfa3|\ud83c\udfa4|\ud83c\udfa5|\ud83c\udfa6|\ud83c\udfa7|\ud83c\udfa8|\ud83c\udfa9|\ud83c\udfaa|\ud83c\udfab|\ud83c\udfac|\ud83c\udfad|\ud83c\udfae|\ud83c\udfaf|\ud83c\udfb0|\ud83c\udfb1|\ud83c\udfb2|\ud83c\udfb3|\ud83c\udfb4|\ud83c\udfb5|\ud83c\udfb6|\ud83c\udfb7|\ud83c\udfb8|\ud83c\udfb9|\ud83c\udfba|\ud83c\udfbb|\ud83c\udfbc|\ud83c\udfbd|\ud83c\udfbe|\ud83c\udfbf|\ud83c\udfc0|\ud83c\udfc1|\ud83c\udfc2|\ud83c\udfc3|\ud83c\udfc4|\ud83c\udfc6|\ud83c\udfc8|\ud83c\udfca|\ud83c\udfe0|\ud83c\udfe1|\ud83c\udfe2|\ud83c\udfe3|\ud83c\udfe5|\ud83c\udfe6|\ud83c\udfe7|\ud83c\udfe8|\ud83c\udfe9|\ud83c\udfea|\ud83c\udfeb|\ud83c\udfec|\ud83c\udfed|\ud83c\udfee|\ud83c\udfef|\ud83c\udff0|\ud83d\udc0c|\ud83d\udc0d|\ud83d\udc0e|\ud83d\udc11|\ud83d\udc12|\ud83d\udc14|\ud83d\udc17|\ud83d\udc18|\ud83d\udc19|\ud83d\udc1a|\ud83d\udc1b|\ud83d\udc1c|\ud83d\udc1d|\ud83d\udc1e|\ud83d\udc1f|\ud83d\udc20|\ud83d\udc21|\ud83d\udc22|\ud83d\udc23|\ud83d\udc24|\ud83d\udc25|\ud83d\udc26|\ud83d\udc27|\ud83d\udc28|\ud83d\udc29|\ud83d\udc2b|\ud83d\udc2c|\ud83d\udc2d|\ud83d\udc2e|\ud83d\udc2f|\ud83d\udc30|\ud83d\udc31|\ud83d\udc32|\ud83d\udc33|\ud83d\udc34|\ud83d\udc35|\ud83d\udc36|\ud83d\udc37|\ud83d\udc38|\ud83d\udc39|\ud83d\udc3a|\ud83d\udc3b|\ud83d\udc3c|\ud83d\udc3d|\ud83d\udc3e|\ud83d\udc40|\ud83d\udc42|\ud83d\udc43|\ud83d\udc44|\ud83d\udc45|\ud83d\udc46|\ud83d\udc47|\ud83d\udc48|\ud83d\udc49|\ud83d\udc4a|\ud83d\udc4b|\ud83d\udc4c|\ud83d\udc4d|\ud83d\udc4e|\ud83d\udc4f|\ud83d\udc50|\ud83d\udc51|\ud83d\udc52|\ud83d\udc53|\ud83d\udc54|\ud83d\udc55|\ud83d\udc56|\ud83d\udc57|\ud83d\udc58|\ud83d\udc59|\ud83d\udc5a|\ud83d\udc5b|\ud83d\udc5c|\ud83d\udc5d|\ud83d\udc5e|\ud83d\udc5f|\ud83d\udc60|\ud83d\udc61|\ud83d\udc62|\ud83d\udc63|\ud83d\udc64|\ud83d\udc66|\ud83d\udc67|\ud83d\udc68|\ud83d\udc69|\ud83d\udc6a|\ud83d\udc6b|\ud83d\udc6e|\ud83d\udc6f|\ud83d\udc70|\ud83d\udc71|\ud83d\udc72|\ud83d\udc73|\ud83d\udc74|\ud83d\udc75|\ud83d\udc76|\ud83d\udeb4|\ud83d\udc78|\ud83d\udc79|\ud83d\udc7a|\ud83d\udc7b|\ud83d\udc7c|\ud83d\udc7d|\ud83d\udc7e|\ud83d\udc7f|\ud83d\udc80|\ud83d\udc81|\ud83d\udc82|\ud83d\udc83|\ud83d\udc84|\ud83d\udc85|\ud83d\udc86|\ud83d\udc87|\ud83d\udc88|\ud83d\udc89|\ud83d\udc8a|\ud83d\udc8b|\ud83d\udc8c|\ud83d\udc8d|\ud83d\udc8e|\ud83d\udc8f|\ud83d\udc90|\ud83d\udc91|\ud83d\udc92|\ud83d\udc93|\ud83d\udc94|\ud83d\udc95|\ud83d\udc96|\ud83d\udc97|\ud83d\udc98|\ud83d\udc99|\ud83d\udc9a|\ud83d\udc9b|\ud83d\udc9c|\ud83d\udc9d|\ud83d\udc9e|\ud83d\udc9f|\ud83d\udca0|\ud83d\udca1|\ud83d\udca2|\ud83d\udca3|\ud83d\udca4|\ud83d\udca5|\ud83d\udca6|\ud83d\udca7|\ud83d\udca8|\ud83d\udca9|\ud83d\udcaa|\ud83d\udcab|\ud83d\udcac|\ud83d\udcae|\ud83d\udcaf|\ud83d\udcb0|\ud83d\udcb1|\ud83d\udcb2|\ud83d\udcb3|\ud83d\udcb4|\ud83d\udcb5|\ud83d\udcb8|\ud83d\udcb9|\ud83d\udcba|\ud83d\udcbb|\ud83d\udcbc|\ud83d\udcbd|\ud83d\udcbe|\ud83d\udcbf|\ud83d\udcc0|\ud83d\udcc1|\ud83d\udcc2|\ud83d\udcc3|\ud83d\udcc4|\ud83d\udcc5|\ud83d\udcc6|\ud83d\udcc7|\ud83d\udcc8|\ud83d\udcc9|\ud83d\udcca|\ud83d\udccb|\ud83d\udccc|\ud83d\udccd|\ud83d\udcce|\ud83d\udccf|\ud83d\udcd0|\ud83d\udcd1|\ud83d\udcd2|\ud83d\udcd3|\ud83d\udcd4|\ud83d\udcd5|\ud83d\udcd6|\ud83d\udcd7|\ud83d\udcd8|\ud83d\udcd9|\ud83d\udcda|\ud83d\udcdb|\ud83d\udcdc|\ud83d\udcdd|\ud83d\udcde|\ud83d\udcdf|\ud83d\udce0|\ud83d\udce1|\ud83d\udce2|\ud83d\udce3|\ud83d\udce4|\ud83d\udce5|\ud83d\udce6|\ud83d\udce7|\ud83d\udce8|\ud83d\udce9|\ud83d\udcea|\ud83d\udceb|\ud83d\udcee|\ud83d\udcf0|\ud83d\udcf1|\ud83d\udcf2|\ud83d\udcf3|\ud83d\udcf4|\ud83d\udcf6|\ud83d\udcf7|\ud83d\udcf9|\ud83d\udcfa|\ud83d\udcfb|\ud83d\udcfc|\ud83d\udd03|\ud83d\udd0a|\ud83d\udd0b|\ud83d\udd0c|\ud83d\udd0d|\ud83d\udd0e|\ud83d\udd0f|\ud83d\udd10|\ud83d\udd11|\ud83d\udd12|\ud83d\udd13|\ud83d\udd14|\ud83d\udd16|\ud83d\udd17|\ud83d\udd18|\ud83d\udd19|\ud83d\udd1a|\ud83d\udd1b|\ud83d\udd1c|\ud83d\udd1d|\ud83d\udd1e|\ud83d\udd1f|\ud83d\udd20|\ud83d\udd21|\ud83d\udd22|\ud83d\udd23|\ud83d\udd24|\ud83d\udd25|\ud83d\udd26|\ud83d\udd27|\ud83d\udd28|\ud83d\udd29|\ud83d\udd2a|\ud83d\udd2b|\ud83d\udd2e|\ud83d\udd2f|\ud83d\udd30|\ud83d\udd31|\ud83d\udd32|\ud83d\udd33|\ud83d\udd34|\ud83d\udd35|\ud83d\udd36|\ud83d\udd37|\ud83d\udd38|\ud83d\udd39|\ud83d\udd3a|\ud83d\udd3b|\ud83d\udd3c|\ud83d\udd3d|\ud83d\udd50|\ud83d\udd51|\ud83d\udd52|\ud83d\udd53|\ud83d\udd54|\ud83d\udd55|\ud83d\udd56|\ud83d\udd57|\ud83d\udd58|\ud83d\udd59|\ud83d\udd5a|\ud83d\udd5b|\ud83d\uddfb|\ud83d\uddfc|\ud83d\uddfd|\ud83d\uddfe|\ud83d\uddff|\ud83d\ude01|\ud83d\ude02|\ud83d\ude03|\ud83d\ude04|\ud83d\ude05|\ud83d\ude06|\ud83d\ude09|\ud83d\ude0a|\ud83d\ude0b|\ud83d\ude0c|\ud83d\ude0d|\ud83d\ude0f|\ud83d\ude12|\ud83d\ude13|\ud83d\ude14|\ud83d\ude16|\ud83d\ude18|\ud83d\ude1a|\ud83d\ude1c|\ud83d\ude1d|\ud83d\ude1e|\ud83d\ude20|\ud83d\ude21|\ud83d\ude22|\ud83d\ude23|\ud83d\ude24|\ud83d\ude25|\ud83d\ude28|\ud83d\ude29|\ud83d\ude2a|\ud83d\ude2b|\ud83d\ude2d|\ud83d\ude30|\ud83d\ude31|\ud83d\ude32|\ud83d\ude33|\ud83d\ude35|\ud83d\ude37|\ud83d\ude38|\ud83d\ude39|\ud83d\ude3a|\ud83d\ude3b|\ud83d\ude3c|\ud83d\ude3d|\ud83d\ude3e|\ud83d\ude3f|\ud83d\ude40|\ud83d\ude45|\ud83d\ude46|\ud83d\ude47|\ud83d\ude48|\ud83d\ude49|\ud83d\ude4a|\ud83d\ude4b|\ud83d\ude4c|\ud83d\ude4d|\ud83d\ude4e|\ud83d\ude4f|\ud83d\ude80|\ud83d\ude83|\ud83d\ude84|\ud83d\ude85|\ud83d\ude87|\ud83d\ude89|\ud83d\ude8c|\ud83d\ude8f|\ud83d\ude91|\ud83d\ude92|\ud83d\ude93|\ud83d\ude95|\ud83d\ude97|\ud83d\ude99|\ud83d\ude9a|\ud83d\udea2|\ud83d\udea4|\ud83d\udea5|\ud83d\udea7|\ud83d\udea8|\ud83d\udea9|\ud83d\udeaa|\ud83d\udeab|\ud83d\udeac|\ud83d\udead|\ud83d\udeb2|\ud83d\udeb6|\ud83d\udeb9|\ud83d\udeba|\ud83d\udebb|\ud83d\udebc|\ud83d\udebd|\ud83d\udebe|\ud83d\udec0|\ud83c\udde6|\ud83c\udde7|\ud83c\udde8|\ud83c\udde9|\ud83c\uddea|\ud83c\uddeb|\ud83c\uddec|\ud83c\udded|\ud83c\uddee|\ud83c\uddef|\ud83c\uddf0|\ud83c\uddf1|\ud83c\uddf2|\ud83c\uddf3|\ud83c\uddf4|\ud83c\uddf5|\ud83c\uddf6|\ud83c\uddf7|\ud83c\uddf8|\ud83c\uddf9|\ud83c\uddfa|\ud83c\uddfb|\ud83c\uddfc|\ud83c\uddfd|\ud83c\uddfe|\ud83c\uddff|\ud83c\udf0d|\ud83c\udf0e|\ud83c\udf10|\ud83c\udf12|\ud83c\udf16|\ud83c\udf17|\ue50a|\u3030|\u27b0|\u2797|\u2796|\u2795|\u2755|\u2754|\u2753|\u274e|\u274c|\u2728|\u270b|\u270a|\u2705|\u26ce|\u23f3|\u23f0|\u23ec|\u23eb|\u23ea|\u23e9|\u2122|\u27bf|\u00a9|\u00ae)|(?:(?:\ud83c\udc04|\ud83c\udd7f|\ud83c\ude1a|\ud83c\ude2f|\u3299|\u303d|\u2b55|\u2b50|\u2b1c|\u2b1b|\u2b07|\u2b06|\u2b05|\u2935|\u2934|\u27a1|\u2764|\u2757|\u2747|\u2744|\u2734|\u2733|\u2716|\u2714|\u2712|\u270f|\u270c|\u2709|\u2708|\u2702|\u26fd|\u26fa|\u26f5|\u26f3|\u26f2|\u26ea|\u26d4|\u26c5|\u26c4|\u26be|\u26bd|\u26ab|\u26aa|\u26a1|\u26a0|\u2693|\u267f|\u267b|\u3297|\u2666|\u2665|\u2663|\u2660|\u2653|\u2652|\u2651|\u2650|\u264f|\u264e|\u264d|\u264c|\u264b|\u264a|\u2649|\u2648|\u263a|\u261d|\u2615|\u2614|\u2611|\u260e|\u2601|\u2600|\u25fe|\u25fd|\u25fc|\u25fb|\u25c0|\u25b6|\u25ab|\u25aa|\u24c2|\u231b|\u231a|\u21aa|\u21a9|\u2199|\u2198|\u2197|\u2196|\u2195|\u2194|\u2139|\u2049|\u203c|\u2668)([\uFE0E\uFE0F]?)))/g,
   228     re = /(?:\ud83d[\udc68\udc69])(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddb0-\uddb3])|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f|(?:\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f)|[\u0023\u002a\u0030-\u0039]\ufe0f?\u20e3|(?:[\u00a9\u00ae\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c[\udf85\udfc2-\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4-\udeb6\udec0\udecc]|\ud83e[\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\uddb5\uddb6\uddb8\uddb9\uddd1-\udddd]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf5\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a-\udc6d\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\udeeb\udeec\udef4-\udef9]|\ud83e[\udd10-\udd17\udd1d\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd40-\udd45\udd47-\udd70\udd73-\udd76\udd7a\udd7c-\udda2\uddb4\uddb7\uddc0-\uddc2\uddd0\uddde-\uddff]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/g,
       
   229 
       
   230     // avoid runtime RegExp creation for not so smart,
       
   231     // not JIT based, and old browsers / engines
       
   232     UFE0Fg = /\uFE0F/g,
       
   233 
       
   234     // avoid using a string literal like '\u200D' here because minifiers expand it inline
       
   235     U200D = String.fromCharCode(0x200D),
       
   236 
       
   237     // used to find HTML special chars in attributes
       
   238     rescaper = /[&<>'"]/g,
   228 
   239 
   229     // nodes with type 1 which should **not** be parsed
   240     // nodes with type 1 which should **not** be parsed
   230     shouldntBeParsed = /IFRAME|NOFRAMES|NOSCRIPT|SCRIPT|SELECT|STYLE|TEXTAREA/,
   241     shouldntBeParsed = /^(?:iframe|noframes|noscript|script|select|style|textarea)$/,
   231 
   242 
   232     // just a private shortcut
   243     // just a private shortcut
   233     fromCharCode = String.fromCharCode;
   244     fromCharCode = String.fromCharCode;
   234 
   245 
   235   return twemoji;
   246   return twemoji;
   243   /**
   254   /**
   244    * Shortcut to create text nodes
   255    * Shortcut to create text nodes
   245    * @param   string  text used to create DOM text node
   256    * @param   string  text used to create DOM text node
   246    * @return  Node  a DOM node with that text
   257    * @return  Node  a DOM node with that text
   247    */
   258    */
   248   function createText(text) {
   259   function createText(text, clean) {
   249     return document.createTextNode(text);
   260     return document.createTextNode(clean ? text.replace(UFE0Fg, '') : text);
       
   261   }
       
   262 
       
   263   /**
       
   264    * Utility function to escape html attribute text
       
   265    * @param   string  text use in HTML attribute
       
   266    * @return  string  text encoded to use in HTML attribute
       
   267    */
       
   268   function escapeHTML(s) {
       
   269     return s.replace(rescaper, replacer);
   250   }
   270   }
   251 
   271 
   252   /**
   272   /**
   253    * Default callback used to generate emoji src
   273    * Default callback used to generate emoji src
   254    *  based on Twitter CDN
   274    *  based on Twitter CDN
   255    * @param   string    the emoji codepoint string
   275    * @param   string    the emoji codepoint string
   256    * @param   string    the default size to use, i.e. "36x36"
   276    * @param   string    the default size to use, i.e. "36x36"
   257    * @param   string    optional "\uFE0F" variant char, ignored by default
       
   258    * @return  string    the image source to use
   277    * @return  string    the image source to use
   259    */
   278    */
   260   function defaultImageSrcGenerator(icon, options) {
   279   function defaultImageSrcGenerator(icon, options) {
   261     return ''.concat(options.base, options.size, '/', icon, options.ext);
   280     return ''.concat(options.base, options.size, '/', icon, options.ext);
   262   }
   281   }
   280       // parse emoji only in text nodes
   299       // parse emoji only in text nodes
   281       if (nodeType === 3) {
   300       if (nodeType === 3) {
   282         // collect them to process emoji later
   301         // collect them to process emoji later
   283         allText.push(subnode);
   302         allText.push(subnode);
   284       }
   303       }
   285       // ignore all nodes that are not type 1 or that
   304       // ignore all nodes that are not type 1, that are svg, or that
   286       // should not be parsed as script, style, and others
   305       // should not be parsed as script, style, and others
   287       else if (nodeType === 1 && !shouldntBeParsed.test(subnode.nodeName)) {
   306       else if (nodeType === 1 && !('ownerSVGElement' in subnode) &&
       
   307           !shouldntBeParsed.test(subnode.nodeName.toLowerCase())) {
   288         grabAllTextNodes(subnode, allText);
   308         grabAllTextNodes(subnode, allText);
   289       }
   309       }
   290     }
   310     }
   291     return allText;
   311     return allText;
   292   }
   312   }
   293 
   313 
   294   /**
   314   /**
   295    * Used to both remove the possible variant
   315    * Used to both remove the possible variant
   296    *  and to convert utf16 into code points
   316    *  and to convert utf16 into code points.
   297    * @param   string    the emoji surrogate pair
   317    *  If there is a zero-width-joiner (U+200D), leave the variants in.
   298    * @param   string    the optional variant char, if any
   318    * @param   string    the raw text of the emoji match
   299    */
   319    * @return  string    the code point
   300   function grabTheRightIcon(icon, variant) {
   320    */
       
   321   function grabTheRightIcon(rawText) {
   301     // if variant is present as \uFE0F
   322     // if variant is present as \uFE0F
   302     return toCodePoint(
   323     return toCodePoint(rawText.indexOf(U200D) < 0 ?
   303       variant === '\uFE0F' ?
   324       rawText.replace(UFE0Fg, '') :
   304         // the icon should not contain it
   325       rawText
   305         icon.slice(0, -1) :
       
   306         // fix non standard OSX behavior
       
   307         (icon.length === 3 && icon.charAt(1) === '\uFE0F' ?
       
   308           icon.charAt(0) + icon.charAt(2) : icon)
       
   309     );
   326     );
   310   }
   327   }
   311 
   328 
   312   /**
   329   /**
   313    * DOM version of the same logic / parser:
   330    * DOM version of the same logic / parser:
   324    */
   341    */
   325   function parseNode(node, options) {
   342   function parseNode(node, options) {
   326     var
   343     var
   327       allText = grabAllTextNodes(node, []),
   344       allText = grabAllTextNodes(node, []),
   328       length = allText.length,
   345       length = allText.length,
       
   346       attrib,
       
   347       attrname,
   329       modified,
   348       modified,
   330       fragment,
   349       fragment,
   331       subnode,
   350       subnode,
   332       text,
   351       text,
   333       match,
   352       match,
   334       i,
   353       i,
   335       index,
   354       index,
   336       img,
   355       img,
   337       alt,
   356       rawText,
   338       icon,
   357       iconId,
   339       variant,
   358       src;
   340       src,
       
   341       attr;
       
   342     while (length--) {
   359     while (length--) {
   343       modified = false;
   360       modified = false;
   344       fragment = document.createDocumentFragment();
   361       fragment = document.createDocumentFragment();
   345       subnode = allText[length];
   362       subnode = allText[length];
   346       text = subnode.nodeValue;
   363       text = subnode.nodeValue;
   347       i = 0;
   364       i = 0;
   348       while ((match = re.exec(text))) {
   365       while ((match = re.exec(text))) {
   349         index = match.index;
   366         index = match.index;
   350         if (index !== i) {
   367         if (index !== i) {
   351           fragment.appendChild(
   368           fragment.appendChild(
   352             createText(text.slice(i, index))
   369             createText(text.slice(i, index), true)
   353           );
   370           );
   354         }
   371         }
   355         alt = match[0];
   372         rawText = match[0];
   356         icon = match[1];
   373         iconId = grabTheRightIcon(rawText);
   357         variant = match[2];
   374         i = index + rawText.length;
   358         i = index + alt.length;
   375         src = options.callback(iconId, options);
   359         if (variant !== '\uFE0E') {
   376         if (src) {
   360           src = options.callback(
   377           img = new Image();
   361             grabTheRightIcon(icon, variant),
   378           img.onerror = options.onerror;
   362             options,
   379           img.setAttribute('draggable', 'false');
   363             variant
   380           attrib = options.attributes(rawText, iconId);
   364           );
   381           for (attrname in attrib) {
   365           if (src) {
   382             if (
   366             img = new Image();
   383               attrib.hasOwnProperty(attrname) &&
   367 
   384               // don't allow any handlers to be set + don't allow overrides
   368 			// Set additional image attributes.
   385               attrname.indexOf('on') !== 0 &&
   369             if ( options.imgAttr ) {
   386               !img.hasAttribute(attrname)
   370               for ( attr in options.imgAttr ) {
   387             ) {
   371                 img.setAttribute( attr, options.imgAttr[attr] );
   388               img.setAttribute(attrname, attrib[attrname]);
   372               }
       
   373             }
   389             }
   374 
       
   375             img.onerror = twemoji.onerror;
       
   376             img.className = options.className;
       
   377             img.setAttribute('draggable', 'false');
       
   378             img.alt = alt;
       
   379             img.src = src;
       
   380             modified = true;
       
   381             fragment.appendChild(img);
       
   382           }
   390           }
       
   391           img.className = options.className;
       
   392           img.alt = rawText;
       
   393           img.src = src;
       
   394           modified = true;
       
   395           fragment.appendChild(img);
   383         }
   396         }
   384         if (!img) fragment.appendChild(createText(alt));
   397         if (!img) fragment.appendChild(createText(rawText, false));
   385         img = null;
   398         img = null;
   386       }
   399       }
   387       // is there actually anything to replace in here ?
   400       // is there actually anything to replace in here ?
   388       if (modified) {
   401       if (modified) {
   389         // any text left to be added ?
   402         // any text left to be added ?
   390         if (i < text.length) {
   403         if (i < text.length) {
   391           fragment.appendChild(
   404           fragment.appendChild(
   392             createText(text.slice(i))
   405             createText(text.slice(i), true)
   393           );
   406           );
   394         }
   407         }
   395         // replace the text node only, leave intact
   408         // replace the text node only, leave intact
   396         // anything else surrounding such text
   409         // anything else surrounding such text
   397         subnode.parentNode.replaceChild(fragment, subnode);
   410         subnode.parentNode.replaceChild(fragment, subnode);
   412    *            .size       string    the assets size, by default twemoji.size
   425    *            .size       string    the assets size, by default twemoji.size
   413    *
   426    *
   414    * @return  the string with <img tags> replacing all found and parsed emoji
   427    * @return  the string with <img tags> replacing all found and parsed emoji
   415    */
   428    */
   416   function parseString(str, options) {
   429   function parseString(str, options) {
   417     return replace(str, function (match, icon, variant) {
   430     return replace(str, function (rawText) {
   418       var src, attr, attributes = '';
   431       var
   419       // verify the variant is not the FE0E one
   432         ret = rawText,
   420       // this variant means "emoji as text" and should not
   433         iconId = grabTheRightIcon(rawText),
   421       // require any action/replacement
   434         src = options.callback(iconId, options),
   422       // http://unicode.org/Public/UNIDATA/StandardizedVariants.html
   435         attrib,
   423       if (variant !== '\uFE0E') {
   436         attrname;
   424         src = options.callback(
   437       if (src) {
   425           grabTheRightIcon(icon, variant),
   438         // recycle the match string replacing the emoji
   426           options,
   439         // with its image counter part
   427           variant
   440         ret = '<img '.concat(
       
   441           'class="', options.className, '" ',
       
   442           'draggable="false" ',
       
   443           // needs to preserve user original intent
       
   444           // when variants should be copied and pasted too
       
   445           'alt="',
       
   446           rawText,
       
   447           '"',
       
   448           ' src="',
       
   449           src,
       
   450           '"'
   428         );
   451         );
   429         if (src) {
   452         attrib = options.attributes(rawText, iconId);
   430           // Set additional image attributes.
   453         for (attrname in attrib) {
   431           if ( options.imgAttr ) {
   454           if (
   432             for ( attr in options.imgAttr ) {
   455             attrib.hasOwnProperty(attrname) &&
   433               if ( ! /draggable|class|alt|src/i.test( attr ) ) {
   456             // don't allow any handlers to be set + don't allow overrides
   434                 attributes += ' '.concat( attr, '="', options.imgAttr[attr], '"' );
   457             attrname.indexOf('on') !== 0 &&
   435               }
   458             ret.indexOf(' ' + attrname + '=') === -1
   436             }
   459           ) {
       
   460             ret = ret.concat(' ', attrname, '="', escapeHTML(attrib[attrname]), '"');
   437           }
   461           }
   438 
       
   439           // recycle the match string replacing the emoji
       
   440           // with its image counter part
       
   441           match = '<img '.concat(
       
   442             'class="', options.className, '" ',
       
   443             'draggable="false" ',
       
   444             // needs to preserve user original intent
       
   445             // when variants should be copied and pasted too
       
   446             'alt="',
       
   447             match,
       
   448             '" ',
       
   449             'src="',
       
   450             src,
       
   451             '"',
       
   452             attributes,
       
   453             '>'
       
   454           );
       
   455         }
   462         }
       
   463         ret = ret.concat('/>');
   456       }
   464       }
   457       return match;
   465       return ret;
   458     });
   466     });
       
   467   }
       
   468 
       
   469   /**
       
   470    * Function used to actually replace HTML special chars
       
   471    * @param   string  HTML special char
       
   472    * @return  string  encoded HTML special char
       
   473    */
       
   474   function replacer(m) {
       
   475     return escaper[m];
       
   476   }
       
   477 
       
   478   /**
       
   479    * Default options.attribute callback
       
   480    * @return  null
       
   481    */
       
   482   function returnNull() {
       
   483     return null;
   459   }
   484   }
   460 
   485 
   461   /**
   486   /**
   462    * Given a generic value, creates its squared counterpart if it's a number.
   487    * Given a generic value, creates its squared counterpart if it's a number.
   463    *  As example, number 36 will return '36x36'.
   488    *  As example, number 36 will return '36x36'.
   496       how = {callback: how};
   521       how = {callback: how};
   497     }
   522     }
   498     // if first argument is string, inject html <img> tags
   523     // if first argument is string, inject html <img> tags
   499     // otherwise use the DOM tree and parse text nodes only
   524     // otherwise use the DOM tree and parse text nodes only
   500     return (typeof what === 'string' ? parseString : parseNode)(what, {
   525     return (typeof what === 'string' ? parseString : parseNode)(what, {
   501       callback: how.callback || defaultImageSrcGenerator,
   526       callback:   how.callback || defaultImageSrcGenerator,
   502       base:     typeof how.base === 'string' ? how.base : twemoji.base,
   527       attributes: typeof how.attributes === 'function' ? how.attributes : returnNull,
   503       ext:      how.ext || twemoji.ext,
   528       base:       typeof how.base === 'string' ? how.base : twemoji.base,
   504       size:     how.folder || toSizeSquaredAsset(how.size || twemoji.size),
   529       ext:        how.ext || twemoji.ext,
   505       className:how.className || twemoji.className,
   530       size:       how.folder || toSizeSquaredAsset(how.size || twemoji.size),
   506       imgAttr:  how.imgAttr
   531       className:  how.className || twemoji.className,
       
   532       onerror:    how.onerror || twemoji.onerror
   507     });
   533     });
   508   }
   534   }
   509 
   535 
   510   function replace(text, callback) {
   536   function replace(text, callback) {
   511     return String(text).replace(re, callback);
   537     return String(text).replace(re, callback);