PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9-a.1
Jetpack – WP Security, Backup, Speed, & Growth v15.9-a.1
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / likes / queuehandler.js
jetpack / modules / likes Last commit date
jetpack-likes-master-iframe.php 6 months ago jetpack-likes-settings.php 6 months ago post-count-jetpack.js 4 years ago post-count.js 1 year ago queuehandler.js 6 months ago style.css 5 months ago
queuehandler.js
482 lines
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 {
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 {
42 // Ignore error
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 {
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 // enable reblogs if we're on a single post page
129 if ( document.body.classList.contains( 'single' ) ) {
130 JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] );
131 }
132
133 stylesData.textStyles = {
134 color: sdTextColorStyles.color,
135 fontFamily: sdTextColorStyles[ 'font-family' ],
136 fontSize: sdTextColorStyles[ 'font-size' ],
137 direction: sdTextColorStyles.direction,
138 fontWeight: sdTextColorStyles[ 'font-weight' ],
139 fontStyle: sdTextColorStyles[ 'font-style' ],
140 textDecoration: sdTextColorStyles[ 'text-decoration' ],
141 };
142
143 stylesData.linkStyles = {
144 color: sdLinkColorStyles.color,
145 fontFamily: sdLinkColorStyles[ 'font-family' ],
146 fontSize: sdLinkColorStyles[ 'font-size' ],
147 textDecoration: sdLinkColorStyles[ 'text-decoration' ],
148 fontWeight: sdLinkColorStyles[ 'font-weight' ],
149 fontStyle: sdLinkColorStyles[ 'font-style' ],
150 };
151
152 JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] );
153
154 JetpackLikesBatchHandler();
155 } );
156
157 break;
158
159 // We're keeping this for planned future follow ups.
160 // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
161 case 'showLikeWidget':
162 break;
163
164 // We're keeping this for planned future follow ups.
165 // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815
166 case 'showCommentLikeWidget':
167 break;
168
169 case 'killCommentLikes':
170 // If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders.
171 document
172 .querySelectorAll( '.jetpack-comment-likes-widget-wrapper' )
173 .forEach( wrapper => wrapper.remove() );
174 break;
175
176 case 'clickReblogFlair':
177 if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) {
178 wpcom_reblog.toggle_reblog_box_flair( data.obj_id );
179 }
180 break;
181
182 case 'hideOtherGravatars': {
183 hideLikersPopover();
184 break;
185 }
186
187 case 'showOtherGravatars': {
188 const container = document.querySelector( '#likes-other-gravatars' );
189
190 if ( ! container ) {
191 break;
192 }
193
194 const list = container.querySelector( 'ul' );
195
196 container.style.display = 'none';
197 list.innerHTML = '';
198
199 container
200 .querySelectorAll( '.likes-text span' )
201 .forEach( item => ( item.textContent = data.totalLikesLabel ) );
202
203 ( data.likers || [] ).forEach( async ( liker, index ) => {
204 if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) {
205 // We only display gravatars with http or https schema
206 return;
207 }
208
209 const element = document.createElement( 'li' );
210 list.append( element );
211
212 const profileLink = encodeURI( liker.profile_URL );
213 const avatarLink = encodeURI( liker.avatar_URL );
214 element.innerHTML = `<a href="${ profileLink }" rel="nofollow" target="_parent" class="wpl-liker">
215 <img src="${ avatarLink }"
216 alt=""
217 style="width: 28px; height: 28px;" />
218 <span></span>
219 </a>`;
220
221 // Add some extra attributes through native methods, to ensure strings are sanitized.
222 element.classList.add( liker.css_class );
223 element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name );
224 element.querySelector( 'span' ).innerText = liker.name;
225
226 if ( index === data.likers.length - 1 ) {
227 element.addEventListener( 'keydown', e => {
228 if ( e.key === 'Tab' && ! e.shiftKey ) {
229 e.preventDefault();
230 hideLikersPopover();
231
232 JetpackLikesPostMessage(
233 { event: 'focusLikesCount', parent: data.parent },
234 window.frames[ 'likes-master' ]
235 );
236 }
237 } );
238 }
239 } );
240
241 const positionPopup = function () {
242 const containerStyle = getComputedStyle( container );
243 const isRtl = containerStyle.direction === 'rtl';
244
245 const el = document.querySelector( `*[name='${ data.parent }']` );
246 const rect = el.getBoundingClientRect();
247 const win = el.ownerDocument.defaultView;
248 const offset = {
249 top: rect.top + win.pageYOffset,
250 left: rect.left + win.pageXOffset,
251 };
252
253 let containerLeft = 0;
254 container.style.top = offset.top + data.position.top - 1 + 'px';
255
256 if ( isRtl ) {
257 const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0;
258 // 24px is the width of the avatar + 4px is the padding between avatars
259 containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4;
260 container.style.transform = 'translateX(-100%)';
261 } else {
262 containerLeft = offset.left + data.position.left;
263 }
264 container.style.left = containerLeft + 'px';
265
266 // Container width - padding
267 const initContainerWidth = data.width - 20;
268 const rowLength = Math.floor( initContainerWidth / 37 );
269 // # of rows + (avatar + avatar padding) + text above + container padding
270 let height = Math.ceil( data.likers.length / rowLength ) * 37 + 17 + 22;
271 if ( height > 204 ) {
272 height = 204;
273 }
274
275 // If the popup overflows viewport width, we should show it on the next line.
276 // Push it offscreen to calculated rendered width.
277 container.style.left = '-9999px';
278 container.style.display = 'block';
279
280 // If the popup exceeds the viewport width,
281 // flip the position of the popup.
282 const containerWidth = container.offsetWidth;
283 const containerRight = containerLeft + containerWidth;
284 if ( containerRight > win.innerWidth ) {
285 containerLeft = rect.right - containerWidth;
286 }
287
288 // Set the container left
289 container.style.left = containerLeft + 'px';
290 container.setAttribute( 'aria-hidden', 'false' );
291 };
292
293 positionPopup();
294 container.focus();
295
296 const debounce = function ( func, wait ) {
297 var timeout;
298 return function () {
299 var context = this;
300 var args = arguments;
301 clearTimeout( timeout );
302 timeout = setTimeout( function () {
303 func.apply( context, args );
304 }, wait );
305 };
306 };
307
308 const debouncedPositionPopup = debounce( positionPopup, 100 );
309
310 // Keep a reference of this function in the element itself
311 // so that we can destroy it later
312 container.__resizeHandler = debouncedPositionPopup;
313
314 // When window is resized, resize the popup.
315 window.addEventListener( 'resize', debouncedPositionPopup );
316
317 container.focus();
318 }
319 }
320 }
321
322 window.addEventListener( 'message', JetpackLikesMessageListener );
323
324 function hideLikersPopover() {
325 const container = document.querySelector( '#likes-other-gravatars' );
326
327 if ( container ) {
328 container.style.display = 'none';
329 container.setAttribute( 'aria-hidden', 'true' );
330
331 // Remove the resize event listener and cleanup.
332 const resizeHandler = container.__resizeHandler;
333 if ( resizeHandler ) {
334 window.removeEventListener( 'resize', resizeHandler );
335 delete container.__resizeHandler;
336 }
337 }
338 }
339
340 document.addEventListener( 'click', hideLikersPopover );
341
342 function JetpackLikesWidgetQueueHandler() {
343 var wrapperID;
344
345 if ( ! jetpackLikesMasterReady ) {
346 setTimeout( JetpackLikesWidgetQueueHandler, 500 );
347 return;
348 }
349
350 // Restore widgets to initial unloaded state when they are scrolled out of view.
351 jetpackUnloadScrolledOutWidgets();
352
353 var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView();
354
355 if ( unloadedWidgetsInView.length > 0 ) {
356 // Grab any unloaded widgets for a batch request
357 JetpackLikesBatchHandler();
358 }
359
360 for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) {
361 wrapperID = unloadedWidgetsInView[ i ].id;
362
363 if ( ! wrapperID ) {
364 continue;
365 }
366
367 jetpackLoadLikeWidgetIframe( wrapperID );
368 }
369 }
370
371 function jetpackLoadLikeWidgetIframe( wrapperID ) {
372 if ( typeof wrapperID === 'undefined' ) {
373 return;
374 }
375
376 const wrapper = document.querySelector( '#' + wrapperID );
377 wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() );
378
379 const placeholder = wrapper.querySelector( '.likes-widget-placeholder' );
380
381 // Post like iframe
382 if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) {
383 const postLikesFrame = document.createElement( 'iframe' );
384
385 postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' );
386 postLikesFrame.name = wrapper.dataset.name;
387 postLikesFrame.src = wrapper.dataset.src;
388 postLikesFrame.height = '55px';
389 postLikesFrame.width = '100%';
390 postLikesFrame.frameBorder = '0';
391 postLikesFrame.scrolling = 'no';
392 postLikesFrame.title = wrapper.dataset.title;
393
394 placeholder.after( postLikesFrame );
395 }
396
397 // Comment like iframe
398 if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) {
399 const commentLikesFrame = document.createElement( 'iframe' );
400
401 commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame';
402 commentLikesFrame.name = wrapper.dataset.name;
403 commentLikesFrame.src = wrapper.dataset.src;
404 commentLikesFrame.height = '18px';
405 commentLikesFrame.width = '100%';
406 commentLikesFrame.frameBorder = '0';
407 commentLikesFrame.scrolling = 'no';
408
409 wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame );
410
411 jetpackCommentLikesLoadedWidgets.push( commentLikesFrame );
412 }
413
414 wrapper.classList.remove( 'jetpack-likes-widget-unloaded' );
415 wrapper.classList.add( 'jetpack-likes-widget-loading' );
416
417 wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => {
418 JetpackLikesPostMessage(
419 { event: 'loadLikeWidget', name: e.target.name, width: e.target.width },
420 window.frames[ 'likes-master' ]
421 );
422
423 wrapper.classList.remove( 'jetpack-likes-widget-loading' );
424 wrapper.classList.add( 'jetpack-likes-widget-loaded' );
425 } );
426 }
427
428 function jetpackGetUnloadedWidgetsInView() {
429 const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' );
430
431 return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) );
432 }
433
434 function jetpackIsScrolledIntoView( element ) {
435 const top = element.getBoundingClientRect().top;
436 const bottom = element.getBoundingClientRect().bottom;
437
438 // Allow some slack above and bellow the fold with jetpackLikesLookAhead,
439 // with the aim of hiding the transition from unloaded to loaded widget from users.
440 return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead;
441 }
442
443 function jetpackUnloadScrolledOutWidgets() {
444 for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) {
445 const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ];
446
447 if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) {
448 const widgetWrapper =
449 currentWidgetIframe &&
450 currentWidgetIframe.parentElement &&
451 currentWidgetIframe.parentElement.parentElement;
452
453 // Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed.
454 widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' );
455 widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' );
456 widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' );
457
458 // Remove it from the list of loaded widgets.
459 jetpackCommentLikesLoadedWidgets.splice( i, 1 );
460
461 // Remove comment like widget iFrame.
462 currentWidgetIframe.remove();
463 }
464 }
465 }
466
467 var jetpackWidgetsDelayedExec = function ( after, fn ) {
468 var timer;
469 return function () {
470 clearTimeout( timer );
471 timer = setTimeout( fn, after );
472 };
473 };
474
475 var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler );
476
477 // Load initial batch of widgets, prior to any scrolling events.
478 JetpackLikesWidgetQueueHandler();
479
480 // Add event listener to execute queue handler after scroll.
481 window.addEventListener( 'scroll', jetpackOnScrollStopped, true );
482