|
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', |