wp-emoji.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. /**
  2. * wp-emoji.js is used to replace emoji with images in browsers when the browser
  3. * doesn't support emoji natively.
  4. *
  5. * @output wp-includes/js/wp-emoji.js
  6. */
  7. ( function( window, settings ) {
  8. /**
  9. * Replaces emoji with images when browsers don't support emoji.
  10. *
  11. * @since 4.2.0
  12. * @access private
  13. *
  14. * @class
  15. *
  16. * @see Twitter Emoji library
  17. * @link https://github.com/twitter/twemoji
  18. *
  19. * @return {Object} The wpEmoji parse and test functions.
  20. */
  21. function wpEmoji() {
  22. var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
  23. // Compression and maintain local scope.
  24. document = window.document,
  25. // Private.
  26. twemoji, timer,
  27. loaded = false,
  28. count = 0,
  29. ie11 = window.navigator.userAgent.indexOf( 'Trident/7.0' ) > 0;
  30. /**
  31. * Detect if the browser supports SVG.
  32. *
  33. * @since 4.6.0
  34. * @private
  35. *
  36. * @see Modernizr
  37. * @link https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
  38. *
  39. * @return {boolean} True if the browser supports svg, false if not.
  40. */
  41. function browserSupportsSvgAsImage() {
  42. if ( !! document.implementation.hasFeature ) {
  43. return document.implementation.hasFeature( 'http://www.w3.org/TR/SVG11/feature#Image', '1.1' );
  44. }
  45. // document.implementation.hasFeature is deprecated. It can be presumed
  46. // if future browsers remove it, the browser will support SVGs as images.
  47. return true;
  48. }
  49. /**
  50. * Runs when the document load event is fired, so we can do our first parse of
  51. * the page.
  52. *
  53. * Listens to all the DOM mutations and checks for added nodes that contain
  54. * emoji characters and replaces those with twitter emoji images.
  55. *
  56. * @since 4.2.0
  57. * @private
  58. */
  59. function load() {
  60. if ( loaded ) {
  61. return;
  62. }
  63. // Ensure twemoji is available on the global window before proceeding.
  64. if ( typeof window.twemoji === 'undefined' ) {
  65. // Break if waiting for longer than 30 seconds.
  66. if ( count > 600 ) {
  67. return;
  68. }
  69. // Still waiting.
  70. window.clearTimeout( timer );
  71. timer = window.setTimeout( load, 50 );
  72. count++;
  73. return;
  74. }
  75. twemoji = window.twemoji;
  76. loaded = true;
  77. // Initialize the mutation observer, which checks all added nodes for
  78. // replaceable emoji characters.
  79. if ( MutationObserver ) {
  80. new MutationObserver( function( mutationRecords ) {
  81. var i = mutationRecords.length,
  82. addedNodes, removedNodes, ii, node;
  83. while ( i-- ) {
  84. addedNodes = mutationRecords[ i ].addedNodes;
  85. removedNodes = mutationRecords[ i ].removedNodes;
  86. ii = addedNodes.length;
  87. /*
  88. * Checks if an image has been replaced by a text element
  89. * with the same text as the alternate description of the replaced image.
  90. * (presumably because the image could not be loaded).
  91. * If it is, do absolutely nothing.
  92. *
  93. * Node type 3 is a TEXT_NODE.
  94. *
  95. * @link https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
  96. */
  97. if (
  98. ii === 1 && removedNodes.length === 1 &&
  99. addedNodes[0].nodeType === 3 &&
  100. removedNodes[0].nodeName === 'IMG' &&
  101. addedNodes[0].data === removedNodes[0].alt &&
  102. 'load-failed' === removedNodes[0].getAttribute( 'data-error' )
  103. ) {
  104. return;
  105. }
  106. // Loop through all the added nodes.
  107. while ( ii-- ) {
  108. node = addedNodes[ ii ];
  109. // Node type 3 is a TEXT_NODE.
  110. if ( node.nodeType === 3 ) {
  111. if ( ! node.parentNode ) {
  112. continue;
  113. }
  114. if ( ie11 ) {
  115. /*
  116. * IE 11's implementation of MutationObserver is buggy.
  117. * It unnecessarily splits text nodes when it encounters a HTML
  118. * template interpolation symbol ( "{{", for example ). So, we
  119. * join the text nodes back together as a work-around.
  120. *
  121. * Node type 3 is a TEXT_NODE.
  122. */
  123. while( node.nextSibling && 3 === node.nextSibling.nodeType ) {
  124. node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
  125. node.parentNode.removeChild( node.nextSibling );
  126. }
  127. }
  128. node = node.parentNode;
  129. }
  130. /*
  131. * If the class name of a non-element node contains 'wp-exclude-emoji' ignore it.
  132. *
  133. * Node type 1 is an ELEMENT_NODE.
  134. */
  135. if ( ! node || node.nodeType !== 1 ||
  136. ( node.className && typeof node.className === 'string' && node.className.indexOf( 'wp-exclude-emoji' ) !== -1 ) ) {
  137. continue;
  138. }
  139. if ( test( node.textContent ) ) {
  140. parse( node );
  141. }
  142. }
  143. }
  144. } ).observe( document.body, {
  145. childList: true,
  146. subtree: true
  147. } );
  148. }
  149. parse( document.body );
  150. }
  151. /**
  152. * Tests if a text string contains emoji characters.
  153. *
  154. * @since 4.3.0
  155. *
  156. * @memberOf wp.emoji
  157. *
  158. * @param {string} text The string to test.
  159. *
  160. * @return {boolean} Whether the string contains emoji characters.
  161. */
  162. function test( text ) {
  163. // Single char. U+20E3 to detect keycaps. U+00A9 "copyright sign" and U+00AE "registered sign" not included.
  164. var single = /[\u203C\u2049\u20E3\u2122\u2139\u2194-\u2199\u21A9\u21AA\u2300\u231A\u231B\u2328\u2388\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638\u2639\u263A\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692\u2693\u2694\u2696\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753\u2754\u2755\u2757\u2763\u2764\u2795\u2796\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05\u2B06\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]/,
  165. // Surrogate pair range. Only tests for the second half.
  166. pair = /[\uDC00-\uDFFF]/;
  167. if ( text ) {
  168. return pair.test( text ) || single.test( text );
  169. }
  170. return false;
  171. }
  172. /**
  173. * Parses any emoji characters into Twemoji images.
  174. *
  175. * - When passed an element the emoji characters are replaced inline.
  176. * - When passed a string the emoji characters are replaced and the result is
  177. * returned.
  178. *
  179. * @since 4.2.0
  180. *
  181. * @memberOf wp.emoji
  182. *
  183. * @param {HTMLElement|string} object The element or string to parse.
  184. * @param {Object} args Additional options for Twemoji.
  185. *
  186. * @return {HTMLElement|string} A string where all emoji are now image tags of
  187. * emoji. Or the element that was passed as the first argument.
  188. */
  189. function parse( object, args ) {
  190. var params;
  191. /*
  192. * If the browser has full support, twemoji is not loaded or our
  193. * object is not what was expected, we do not parse anything.
  194. */
  195. if ( settings.supports.everything || ! twemoji || ! object ||
  196. ( 'string' !== typeof object && ( ! object.childNodes || ! object.childNodes.length ) ) ) {
  197. return object;
  198. }
  199. // Compose the params for the twitter emoji library.
  200. args = args || {};
  201. params = {
  202. base: browserSupportsSvgAsImage() ? settings.svgUrl : settings.baseUrl,
  203. ext: browserSupportsSvgAsImage() ? settings.svgExt : settings.ext,
  204. className: args.className || 'emoji',
  205. callback: function( icon, options ) {
  206. // Ignore some standard characters that TinyMCE recommends in its character map.
  207. switch ( icon ) {
  208. case 'a9':
  209. case 'ae':
  210. case '2122':
  211. case '2194':
  212. case '2660':
  213. case '2663':
  214. case '2665':
  215. case '2666':
  216. return false;
  217. }
  218. if ( settings.supports.everythingExceptFlag &&
  219. ! /^1f1(?:e[6-9a-f]|f[0-9a-f])-1f1(?:e[6-9a-f]|f[0-9a-f])$/.test( icon ) && // Country flags.
  220. ! /^(1f3f3-fe0f-200d-1f308|1f3f4-200d-2620-fe0f)$/.test( icon ) // Rainbow and pirate flags.
  221. ) {
  222. return false;
  223. }
  224. return ''.concat( options.base, icon, options.ext );
  225. },
  226. attributes: function() {
  227. return {
  228. role: 'img'
  229. };
  230. },
  231. onerror: function() {
  232. if ( twemoji.parentNode ) {
  233. this.setAttribute( 'data-error', 'load-failed' );
  234. twemoji.parentNode.replaceChild( document.createTextNode( twemoji.alt ), twemoji );
  235. }
  236. }
  237. };
  238. if ( typeof args.imgAttr === 'object' ) {
  239. params.attributes = function() {
  240. return args.imgAttr;
  241. };
  242. }
  243. return twemoji.parse( object, params );
  244. }
  245. /**
  246. * Initialize our emoji support, and set up listeners.
  247. */
  248. if ( settings ) {
  249. if ( settings.DOMReady ) {
  250. load();
  251. } else {
  252. settings.readyCallback = load;
  253. }
  254. }
  255. return {
  256. parse: parse,
  257. test: test
  258. };
  259. }
  260. window.wp = window.wp || {};
  261. /**
  262. * @namespace wp.emoji
  263. */
  264. window.wp.emoji = new wpEmoji();
  265. } )( window, window._wpemojiSettings );