PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 12.7
Jetpack – WP Security, Backup, Speed, & Growth v12.7
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / likes / queuehandler.js

queuehandler.js in Jetpack – WP Security, Backup, Speed, & Growth 12.7, at modules/likes/queuehandler.js

430 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global wpcom_reblog */
2
3 var jetpackLikesWidgetBatch = [];
4 var jetpackLikesMasterReady = false;
5
6 // Due to performance problems on pages with a large number of widget iframes that need to be loaded,
7 // we are limiting the processing at any instant to unloaded widgets that are currently in viewport,
8 // plus this constant that will allow processing of widgets above and bellow the current fold.
9 // This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users.
10 var jetpackLikesLookAhead = 2000; // pixels
11
12 // Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view.
13 var jetpackCommentLikesLoadedWidgets = [];
14
15 var jetpackLikesDocReadyPromise = new Promise( resolve => {
16 if ( document.readyState !== 'loading' ) {
17 resolve();
18 } else {
19 window.addEventListener( 'DOMContentLoaded', () => resolve() );
20 }
21 } );
22
23 function JetpackLikesPostMessage( message, target ) {
24 if ( typeof message === 'string' ) {
25 try {
26 message = JSON.parse( message );
27 } catch ( e ) {
28 return;
29 }
30 }
31
32 if ( target && typeof target.postMessage === 'function' ) {
33 try {
34 target.postMessage(
35 JSON.stringify( {
36 type: 'likesMessage',
37 data: message,
38 } ),
39 '*'
40 );
41 } catch ( e ) {
42 return;
43 }
44 }
45 }
46
47 function JetpackLikesBatchHandler() {
48 const requests = [];
49 document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => {
50 if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) {
51 return;
52 }
53
54 if ( ! jetpackIsScrolledIntoView( widget ) ) {
55 return;
56 }
57
58 jetpackLikesWidgetBatch.push( widget.id );
59
60 var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/,
61 match = regex.exec( widget.id ),
62 info;
63
64 if ( ! match || match.length !== 5 ) {
65 return;
66 }
67
68 info = {
69 blog_id: match[ 2 ],
70 width: widget.width,
71 };
72
73 if ( 'post' === match[ 1 ] ) {
74 info.post_id = match[ 3 ];
75 } else if ( 'comment' === match[ 1 ] ) {
76 info.comment_id = match[ 3 ];
77 }
78
79 info.obj_id = match[ 4 ];
80
81 requests.push( info );
82 } );
83
84 if ( requests.length > 0 ) {
85 JetpackLikesPostMessage(
86 { event: 'initialBatch', requests: requests },
87 window.frames[ 'likes-master' ]
88 );
89 }
90 }
91
92 function JetpackLikesMessageListener( event ) {
93 let message = event && event.data;
94 if ( typeof message === 'string' ) {
95 try {
96 message = JSON.parse( message );
97 } catch ( err ) {
98 return;
99 }
100 }
101
102 const type = message && message.type;
103 const data = message && message.data;
104
105 if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) {
106 return;
107 }
108
109 // We only allow messages from one origin
110 const allowedOrigin = 'https://widgets.wp.com';
111 if ( allowedOrigin !== event.origin ) {
112 return;
113 }
114
115 switch ( data.event ) {
116 case 'masterReady':
117 jetpackLikesDocReadyPromise.then( () => {
118 jetpackLikesMasterReady = true;
119
120 const stylesData = {
121 event: 'injectStyles',
122 };
123 const sdTextColor = document.querySelector( '.sd-text-color' );
124 const sdLinkColor = document.querySelector( '.sd-link-color' );
125 const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {};
126 const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {};
127
128 if ( document.querySelectorAll( 'iframe.admin-bar-likes-widget' ).length > 0 ) {
129 JetpackLikesPostMessage( { event: 'adminBarEnabled' }, window.frames[ 'likes-master' ] );
130
131 const bgSource = document.querySelector(
132 '#wpadminbar .quicklinks li#wp-admin-bar-wpl-like > a'
133 );
134
135 const wpAdminBar = document.querySelector( '#wpadminbar' );
136
137 stylesData.adminBarStyles = {
138 background: bgSource && getComputedStyle( bgSource ).background,
139 isRtl: wpAdminBar && getComputedStyle( wpAdminBar ).direction === 'rtl',
140 };
141 }
142
143 // enable reblogs if we're on a single post page
144 if ( document.body.classList.contains( 'single' ) ) {
145 JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] );
146 }
147
148 stylesData.textStyles = {
149 color: sdTextColorStyles[ 'color' ],
150 fontFamily: sdTextColorStyles[ 'font-family' ],
151 fontSize: sdTextColorStyles[ 'font-size' ],
152 direction: sdTextColorStyles[ 'direction' ],
153 fontWeight: sdTextColorStyles[ 'font-weight' ],
154 fontStyle: sdTextColorStyles[ 'font-style' ],
155 textDecoration: sdTextColorStyles[ 'text-decoration' ],
156 };
157
158 stylesData.linkStyles = {
159 color: sdLinkColorStyles[ 'color' ],
160 fontFamily: sdLinkColorStyles[ 'font-family' ],
161 fontSize: sdLinkColorStyles[ 'font-size' ],
162 textDecoration: sdLinkColorStyles[ 'text-decoration' ],
163 fontWeight: sdLinkColorStyles[ 'font-weight' ],
164 fontStyle: sdLinkColorStyles[ 'font-style' ],
165 };
166
167 JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] );
168
169 JetpackLikesBatchHandler();
170 } );
171
172 break;
173
174 case 'showLikeWidget': {
175 const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` );
176 if ( placeholder ) {
177 placeholder.style.display = 'none';
178 }
179 break;
180 }
181
182 case 'showCommentLikeWidget': {
183 const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` );
184 if ( placeholder ) {
185 placeholder.style.display = 'none';
186 }
187 break;
188 }
189
190 case 'killCommentLikes':
191 // If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders.
192 document
193 .querySelectorAll( '.jetpack-comment-likes-widget-wrapper' )
194 .forEach( wrapper => wrapper.remove() );
195 break;
196
197 case 'clickReblogFlair':
198 if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) {
199 wpcom_reblog.toggle_reblog_box_flair( data.obj_id );
200 }
201 break;
202
203 case 'showOtherGravatars': {
204 const container = document.querySelector( '#likes-other-gravatars' );
205 if ( ! container ) {
206 break;
207 }
208
209 const list = container.querySelector( 'ul' );
210
211 container.style.display = 'none';
212 list.innerHTML = '';
213
214 container
215 .querySelectorAll( '.likes-text span' )
216 .forEach( item => ( item.textContent = data.total ) );
217
218 ( data.likers || [] ).forEach( liker => {
219 if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) {
220 // We only display gravatars with http or https schema
221 return;
222 }
223
224 const element = document.createElement( 'li' );
225 element.innerHTML = `
226 <a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker">
227 <img src="${ encodeURI( liker.avatar_URL ) }"
228 alt=""
229 style="width: 30px; height: 30px; padding-right: 3px;" />
230 </a>
231 `;
232
233 list.append( element );
234
235 // Add some extra attributes through native methods, to ensure strings are sanitized.
236 element.classList.add( liker.css_class );
237 element.querySelector( 'img' ).alt = liker.name;
238 } );
239
240 const el = document.querySelector( `*[name='${ data.parent }']` );
241 const rect = el.getBoundingClientRect();
242 const win = el.ownerDocument.defaultView;
243 const offset = {
244 top: rect.top + win.pageYOffset,
245 left: rect.left + win.pageXOffset,
246 };
247
248 container.style.left = offset.left + data.position.left - 10 + 'px';
249 container.style.top = offset.top + data.position.top - 33 + 'px';
250
251 const rowLength = Math.floor( data.width / 37 );
252 let height = Math.ceil( data.likers.length / rowLength ) * 37 + 13;
253 if ( height > 204 ) {
254 height = 204;
255 }
256
257 const containerWidth = rowLength * 37 - 7;
258 container.style.height = height + 'px';
259 container.style.width = containerWidth + 'px';
260
261 const listWidth = rowLength * 37;
262 list.style.width = listWidth + 'px';
263
264 container.style.display = 'block';
265
266 const scrollbarWidth = list.offsetWidth - list.clientWidth;
267 if ( scrollbarWidth > 0 ) {
268 container.style.width = containerWidth + scrollbarWidth + 'px';
269 list.style.width = listWidth + scrollbarWidth + 'px';
270 }
271 }
272 }
273 }
274
275 window.addEventListener( 'message', JetpackLikesMessageListener );
276
277 document.addEventListener( 'click', e => {
278 const container = document.querySelector( '#likes-other-gravatars' );
279
280 if ( container && ! container.contains( e.target ) ) {
281 container.style.display = 'none';
282 }
283 } );
284
285 function JetpackLikesWidgetQueueHandler() {
286 var wrapperID;
287
288 if ( ! jetpackLikesMasterReady ) {
289 setTimeout( JetpackLikesWidgetQueueHandler, 500 );
290 return;
291 }
292
293 // Restore widgets to initial unloaded state when they are scrolled out of view.
294 jetpackUnloadScrolledOutWidgets();
295
296 var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView();
297
298 if ( unloadedWidgetsInView.length > 0 ) {
299 // Grab any unloaded widgets for a batch request
300 JetpackLikesBatchHandler();
301 }
302
303 for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) {
304 wrapperID = unloadedWidgetsInView[ i ].id;
305
306 if ( ! wrapperID ) {
307 continue;
308 }
309
310 jetpackLoadLikeWidgetIframe( wrapperID );
311 }
312 }
313
314 function jetpackLoadLikeWidgetIframe( wrapperID ) {
315 if ( typeof wrapperID === 'undefined' ) {
316 return;
317 }
318
319 const wrapper = document.querySelector( '#' + wrapperID );
320 wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() );
321
322 const placeholder = wrapper.querySelector( '.likes-widget-placeholder' );
323
324 // Post like iframe
325 if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) {
326 const postLikesFrame = document.createElement( 'iframe' );
327
328 postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' );
329 postLikesFrame.name = wrapper.dataset.name;
330 postLikesFrame.src = wrapper.dataset.src;
331 postLikesFrame.height = '55px';
332 postLikesFrame.width = '100%';
333 postLikesFrame.frameBorder = '0';
334 postLikesFrame.scrolling = 'no';
335 postLikesFrame.title = wrapper.dataset.title;
336
337 placeholder.after( postLikesFrame );
338 }
339
340 // Comment like iframe
341 if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) {
342 const commentLikesFrame = document.createElement( 'iframe' );
343
344 commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame';
345 commentLikesFrame.name = wrapper.dataset.name;
346 commentLikesFrame.src = wrapper.dataset.src;
347 commentLikesFrame.height = '18px';
348 commentLikesFrame.width = '100%';
349 commentLikesFrame.frameBorder = '0';
350 commentLikesFrame.scrolling = 'no';
351
352 wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame );
353
354 jetpackCommentLikesLoadedWidgets.push( commentLikesFrame );
355 }
356
357 wrapper.classList.remove( 'jetpack-likes-widget-unloaded' );
358 wrapper.classList.add( 'jetpack-likes-widget-loading' );
359
360 wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => {
361 JetpackLikesPostMessage(
362 { event: 'loadLikeWidget', name: e.target.name, width: e.target.width },
363 window.frames[ 'likes-master' ]
364 );
365
366 wrapper.classList.remove( 'jetpack-likes-widget-loading' );
367 wrapper.classList.add( 'jetpack-likes-widget-loaded' );
368 } );
369 }
370
371 function jetpackGetUnloadedWidgetsInView() {
372 const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' );
373
374 return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) );
375 }
376
377 function jetpackIsScrolledIntoView( element ) {
378 const top = element.getBoundingClientRect().top;
379 const bottom = element.getBoundingClientRect().bottom;
380
381 // Allow some slack above and bellow the fold with jetpackLikesLookAhead,
382 // with the aim of hiding the transition from unloaded to loaded widget from users.
383 return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead;
384 }
385
386 function jetpackUnloadScrolledOutWidgets() {
387 for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) {
388 const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ];
389
390 if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) {
391 const widgetWrapper =
392 currentWidgetIframe &&
393 currentWidgetIframe.parentElement &&
394 currentWidgetIframe.parentElement.parentElement;
395
396 // Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed.
397 widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' );
398 widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' );
399 widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' );
400
401 // Bring back the loading placeholder into view.
402 widgetWrapper
403 .querySelectorAll( '.comment-likes-widget-placeholder' )
404 .forEach( item => ( item.style.display = 'block' ) );
405
406 // Remove it from the list of loaded widgets.
407 jetpackCommentLikesLoadedWidgets.splice( i, 1 );
408
409 // Remove comment like widget iFrame.
410 currentWidgetIframe.remove();
411 }
412 }
413 }
414
415 var jetpackWidgetsDelayedExec = function ( after, fn ) {
416 var timer;
417 return function () {
418 clearTimeout( timer );
419 timer = setTimeout( fn, after );
420 };
421 };
422
423 var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler );
424
425 // Load initial batch of widgets, prior to any scrolling events.
426 JetpackLikesWidgetQueueHandler();
427
428 // Add event listener to execute queue handler after scroll.
429 window.addEventListener( 'scroll', jetpackOnScrollStopped, true );
430