wp/wp-includes/js/wp-emoji.js
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
       
     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  */
     1 
     7 
     2 ( function( window, settings ) {
     8 ( function( window, settings ) {
       
     9 	/**
       
    10 	 * Replaces emoji with images when browsers don't support emoji.
       
    11 	 *
       
    12 	 * @since      4.2.0
       
    13 	 * @access     private
       
    14 	 *
       
    15 	 * @class
       
    16 	 *
       
    17 	 * @see  Twitter Emoji library
       
    18 	 * @link https://github.com/twitter/twemoji
       
    19 	 *
       
    20 	 * @return {Object} The wpEmoji parse and test functions.
       
    21 	 */
     3 	function wpEmoji() {
    22 	function wpEmoji() {
     4 		var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
    23 		var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
     5 
    24 
     6 		// Compression and maintain local scope
    25 		// Compression and maintain local scope
     7 		document = window.document,
    26 		document = window.document,
    14 
    33 
    15 		/**
    34 		/**
    16 		 * Detect if the browser supports SVG.
    35 		 * Detect if the browser supports SVG.
    17 		 *
    36 		 *
    18 		 * @since 4.6.0
    37 		 * @since 4.6.0
    19 		 *
    38 		 * @private
    20 		 * @return {Boolean} True if the browser supports svg, false if not.
    39 		 *
       
    40 		 * @see Modernizr
       
    41 		 * @link https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
       
    42 		 *
       
    43 		 * @return {boolean} True if the browser supports svg, false if not.
    21 		 */
    44 		 */
    22 		function browserSupportsSvgAsImage() {
    45 		function browserSupportsSvgAsImage() {
    23 			if ( !! document.implementation.hasFeature ) {
    46 			if ( !! document.implementation.hasFeature ) {
    24 				// Source: Modernizr
       
    25 				// https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
       
    26 				return document.implementation.hasFeature( 'http://www.w3.org/TR/SVG11/feature#Image', '1.1' );
    47 				return document.implementation.hasFeature( 'http://www.w3.org/TR/SVG11/feature#Image', '1.1' );
    27 			}
    48 			}
    28 
    49 
    29 			// document.implementation.hasFeature is deprecated. It can be presumed
    50 			// document.implementation.hasFeature is deprecated. It can be presumed
    30 			// if future browsers remove it, the browser will support SVGs as images.
    51 			// if future browsers remove it, the browser will support SVGs as images.
    31 			return true;
    52 			return true;
    32 		}
    53 		}
    33 
    54 
    34 		/**
    55 		/**
    35 		 * Runs when the document load event is fired, so we can do our first parse of the page.
    56 		 * Runs when the document load event is fired, so we can do our first parse of
       
    57 		 * the page.
       
    58 		 *
       
    59 		 * Listens to all the DOM mutations and checks for added nodes that contain
       
    60 		 * emoji characters and replaces those with twitter emoji images.
    36 		 *
    61 		 *
    37 		 * @since 4.2.0
    62 		 * @since 4.2.0
       
    63 		 * @private
    38 		 */
    64 		 */
    39 		function load() {
    65 		function load() {
    40 			if ( loaded ) {
    66 			if ( loaded ) {
    41 				return;
    67 				return;
    42 			}
    68 			}
    43 
    69 
       
    70 			// Ensure twemoji is available on the global window before proceeding.
    44 			if ( typeof window.twemoji === 'undefined' ) {
    71 			if ( typeof window.twemoji === 'undefined' ) {
    45 				// Break if waiting for longer than 30 sec.
    72 				// Break if waiting for longer than 30 sec.
    46 				if ( count > 600 ) {
    73 				if ( count > 600 ) {
    47 					return;
    74 					return;
    48 				}
    75 				}
    56 			}
    83 			}
    57 
    84 
    58 			twemoji = window.twemoji;
    85 			twemoji = window.twemoji;
    59 			loaded = true;
    86 			loaded = true;
    60 
    87 
       
    88 			// Initialize the mutation observer, which checks all added nodes for
       
    89 			// replaceable emoji characters.
    61 			if ( MutationObserver ) {
    90 			if ( MutationObserver ) {
    62 				new MutationObserver( function( mutationRecords ) {
    91 				new MutationObserver( function( mutationRecords ) {
    63 					var i = mutationRecords.length,
    92 					var i = mutationRecords.length,
    64 						addedNodes, removedNodes, ii, node;
    93 						addedNodes, removedNodes, ii, node;
    65 
    94 
    66 					while ( i-- ) {
    95 					while ( i-- ) {
    67 						addedNodes = mutationRecords[ i ].addedNodes;
    96 						addedNodes = mutationRecords[ i ].addedNodes;
    68 						removedNodes = mutationRecords[ i ].removedNodes;
    97 						removedNodes = mutationRecords[ i ].removedNodes;
    69 						ii = addedNodes.length;
    98 						ii = addedNodes.length;
    70 
    99 
       
   100 						/*
       
   101 						 * Checks if an image has been replaced by a text element
       
   102 						 * with the same text as the alternate description of the replaced image.
       
   103 						 * (presumably because the image could not be loaded).
       
   104 						 * If it is, do absolutely nothing.
       
   105 						 *
       
   106 						 * Node type 3 is a TEXT_NODE.
       
   107 						 *
       
   108 						 * @link https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
       
   109 						 */
    71 						if (
   110 						if (
    72 							ii === 1 && removedNodes.length === 1 &&
   111 							ii === 1 && removedNodes.length === 1 &&
    73 							addedNodes[0].nodeType === 3 &&
   112 							addedNodes[0].nodeType === 3 &&
    74 							removedNodes[0].nodeName === 'IMG' &&
   113 							removedNodes[0].nodeName === 'IMG' &&
    75 							addedNodes[0].data === removedNodes[0].alt &&
   114 							addedNodes[0].data === removedNodes[0].alt &&
    76 							'load-failed' === removedNodes[0].getAttribute( 'data-error' )
   115 							'load-failed' === removedNodes[0].getAttribute( 'data-error' )
    77 						) {
   116 						) {
    78 							return;
   117 							return;
    79 						}
   118 						}
    80 
   119 
       
   120 						// Loop through all the added nodes.
    81 						while ( ii-- ) {
   121 						while ( ii-- ) {
    82 							node = addedNodes[ ii ];
   122 							node = addedNodes[ ii ];
    83 
   123 
       
   124 							// Node type 3 is a TEXT_NODE.
    84 							if ( node.nodeType === 3 ) {
   125 							if ( node.nodeType === 3 ) {
    85 								if ( ! node.parentNode ) {
   126 								if ( ! node.parentNode ) {
    86 									continue;
   127 									continue;
    87 								}
   128 								}
    88 
   129 
    90 									/*
   131 									/*
    91 									 * IE 11's implementation of MutationObserver is buggy.
   132 									 * IE 11's implementation of MutationObserver is buggy.
    92 									 * It unnecessarily splits text nodes when it encounters a HTML
   133 									 * It unnecessarily splits text nodes when it encounters a HTML
    93 									 * template interpolation symbol ( "{{", for example ). So, we
   134 									 * template interpolation symbol ( "{{", for example ). So, we
    94 									 * join the text nodes back together as a work-around.
   135 									 * join the text nodes back together as a work-around.
       
   136 									 *
       
   137 									 * Node type 3 is a TEXT_NODE.
    95 									 */
   138 									 */
    96 									while( node.nextSibling && 3 === node.nextSibling.nodeType ) {
   139 									while( node.nextSibling && 3 === node.nextSibling.nodeType ) {
    97 										node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
   140 										node.nodeValue = node.nodeValue + node.nextSibling.nodeValue;
    98 										node.parentNode.removeChild( node.nextSibling );
   141 										node.parentNode.removeChild( node.nextSibling );
    99 									}
   142 									}
   100 								}
   143 								}
   101 
   144 
   102 								node = node.parentNode;
   145 								node = node.parentNode;
   103 							}
   146 							}
   104 
   147 
       
   148 							/*
       
   149 							 * If the class name of a non-element node contains 'wp-exclude-emoji' ignore it.
       
   150 							 *
       
   151 							 * Node type 1 is an ELEMENT_NODE.
       
   152 							 */
   105 							if ( ! node || node.nodeType !== 1 ||
   153 							if ( ! node || node.nodeType !== 1 ||
   106 								( node.className && typeof node.className === 'string' && node.className.indexOf( 'wp-exclude-emoji' ) !== -1 ) ) {
   154 								( node.className && typeof node.className === 'string' && node.className.indexOf( 'wp-exclude-emoji' ) !== -1 ) ) {
   107 
   155 
   108 								continue;
   156 								continue;
   109 							}
   157 							}
   121 
   169 
   122 			parse( document.body );
   170 			parse( document.body );
   123 		}
   171 		}
   124 
   172 
   125 		/**
   173 		/**
   126 		 * Test if a text string contains emoji characters.
   174 		 * Tests if a text string contains emoji characters.
   127 		 *
   175 		 *
   128 		 * @since 4.3.0
   176 		 * @since 4.3.0
   129 		 *
   177 		 *
   130 		 * @param {String} text The string to test
   178 		 * @memberOf wp.emoji
   131 		 *
   179 		 *
   132 		 * @return {Boolean} Whether the string contains emoji characters.
   180 		 * @param {string} text The string to test.
       
   181 		 *
       
   182 		 * @return {boolean} Whether the string contains emoji characters.
   133 		 */
   183 		 */
   134 		function test( text ) {
   184 		function test( text ) {
   135 			// Single char. U+20E3 to detect keycaps. U+00A9 "copyright sign" and U+00AE "registered sign" not included.
   185 			// Single char. U+20E3 to detect keycaps. U+00A9 "copyright sign" and U+00AE "registered sign" not included.
   136 			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]/,
   186 			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]/,
   137 			// Surrogate pair range. Only tests for the second half.
   187 			// Surrogate pair range. Only tests for the second half.
   143 
   193 
   144 			return false;
   194 			return false;
   145 		}
   195 		}
   146 
   196 
   147 		/**
   197 		/**
   148 		 * Given an element or string, parse any emoji characters into Twemoji images.
   198 		 * Parses any emoji characters into Twemoji images.
       
   199 		 *
       
   200 		 * - When passed an element the emoji characters are replaced inline.
       
   201 		 * - When passed a string the emoji characters are replaced and the result is
       
   202 		 *   returned.
   149 		 *
   203 		 *
   150 		 * @since 4.2.0
   204 		 * @since 4.2.0
   151 		 *
   205 		 *
   152 		 * @param {HTMLElement|String} object The element or string to parse.
   206 		 * @memberOf wp.emoji
   153 		 * @param {Object} args Additional options for Twemoji.
   207 		 *
       
   208 		 * @param {HTMLElement|string} object The element or string to parse.
       
   209 		 * @param {Object}             args   Additional options for Twemoji.
       
   210 		 *
       
   211 		 * @return {HTMLElement|string} A string where all emoji are now image tags of
       
   212 		 *                              emoji. Or the element that was passed as the first argument.
   154 		 */
   213 		 */
   155 		function parse( object, args ) {
   214 		function parse( object, args ) {
   156 			var params;
   215 			var params;
   157 
   216 
       
   217 			/*
       
   218 			 * If the browser has full support, twemoji is not loaded or our
       
   219 			 * object is not what was expected, we do not parse anything.
       
   220 			 */
   158 			if ( settings.supports.everything || ! twemoji || ! object ||
   221 			if ( settings.supports.everything || ! twemoji || ! object ||
   159 				( 'string' !== typeof object && ( ! object.childNodes || ! object.childNodes.length ) ) ) {
   222 				( 'string' !== typeof object && ( ! object.childNodes || ! object.childNodes.length ) ) ) {
   160 
   223 
   161 				return object;
   224 				return object;
   162 			}
   225 			}
   163 
   226 
       
   227 			// Compose the params for the twitter emoji library.
   164 			args = args || {};
   228 			args = args || {};
   165 			params = {
   229 			params = {
   166 				base: browserSupportsSvgAsImage() ? settings.svgUrl : settings.baseUrl,
   230 				base: browserSupportsSvgAsImage() ? settings.svgUrl : settings.baseUrl,
   167 				ext:  browserSupportsSvgAsImage() ? settings.svgExt : settings.ext,
   231 				ext:  browserSupportsSvgAsImage() ? settings.svgExt : settings.ext,
   168 				className: args.className || 'emoji',
   232 				className: args.className || 'emoji',
   222 			test: test
   286 			test: test
   223 		};
   287 		};
   224 	}
   288 	}
   225 
   289 
   226 	window.wp = window.wp || {};
   290 	window.wp = window.wp || {};
       
   291 
       
   292 	/**
       
   293 	 * @namespace wp.emoji
       
   294 	 */
   227 	window.wp.emoji = new wpEmoji();
   295 	window.wp.emoji = new wpEmoji();
   228 
   296 
   229 } )( window, window._wpemojiSettings );
   297 } )( window, window._wpemojiSettings );