1!function(n){"function"==typeof define&&define.amd?define(["jquery"],function(e){return n(e)}):"object"==typeof module&&"object"==typeof module.exports?exports=n(require("jquery")):n(jQuery)}(function(n){function e(n){var e=7.5625,t=2.75;return n<1/t?e*n*n:n<2/t?e*(n-=1.5/t)*n+.75:n<2.5/t?e*(n-=2.25/t)*n+.9375:e*(n-=2.625/t)*n+.984375}void 0!==n.easing&&(n.easing.jswing=n.easing.swing);var t=Math.pow,u=Math.sqrt,r=Math.sin,i=Math.cos,a=Math.PI,c=1.70158,o=1.525*c,s=2*a/3,f=2*a/4.5;n.extend(n.easing,{def:"easeOutQuad",swing:function(e){return n.easing[n.easing.def](e)},easeInQuad:function(n){return n*n},easeOutQuad:function(n){return 1-(1-n)*(1-n)},easeInOutQuad:function(n){return n<.5?2*n*n:1-t(-2*n+2,2)/2},easeInCubic:function(n){return n*n*n},easeOutCubic:function(n){return 1-t(1-n,3)},easeInOutCubic:function(n){return n<.5?4*n*n*n:1-t(-2*n+2,3)/2},easeInQuart:function(n){return n*n*n*n},easeOutQuart:function(n){return 1-t(1-n,4)},easeInOutQuart:function(n){return n<.5?8*n*n*n*n:1-t(-2*n+2,4)/2},easeInQuint:function(n){return n*n*n*n*n},easeOutQuint:function(n){return 1-t(1-n,5)},easeInOutQuint:function(n){return n<.5?16*n*n*n*n*n:1-t(-2*n+2,5)/2},easeInSine:function(n){return 1-i(n*a/2)},easeOutSine:function(n){return r(n*a/2)},easeInOutSine:function(n){return-(i(a*n)-1)/2},easeInExpo:function(n){return 0===n?0:t(2,10*n-10)},easeOutExpo:function(n){return 1===n?1:1-t(2,-10*n)},easeInOutExpo:function(n){return 0===n?0:1===n?1:n<.5?t(2,20*n-10)/2:(2-t(2,-20*n+10))/2},easeInCirc:function(n){return 1-u(1-t(n,2))},easeOutCirc:function(n){return u(1-t(n-1,2))},easeInOutCirc:function(n){return n<.5?(1-u(1-t(2*n,2)))/2:(u(1-t(-2*n+2,2))+1)/2}
1,easeInElastic:function(n){return 0===n?0:1===n?1:-t(2,10*n-10)*r((10*n-10.75)*s)},easeOutElastic:function(n){return 0===n?0:1===n?1:t(2,-10*n)*r((10*n-.75)*s)+1},easeInOutElastic:function(n){return 0===n?0:1===n?1:n<.5?-(t(2,20*n-10)*r((20*n-11.125)*f))/2:t(2,-20*n+10)*r((20*n-11.125)*f)/2+1},easeInBack:function(n){return(c+1)*n*n*n-c*n*n},easeOutBack:function(n){return 1+(c+1)*t(n-1,3)+c*t(n-1,2)},easeInOutBack:function(n){return n<.5?t(2*n,2)*(7.189819*n-o)/2:(t(2*n-2,2)*((o+1)*(2*n-2)+o)+2)/2},easeInBounce:function(n){return 1-e(1-n)},easeOutBounce:e,easeInOutBounce:function(n){return n<.5?(1-e(1-2*n))/2:(1+e(2*n-1))/2}})}); 2; 3/* global wpcom_reblog */ 4 5var jetpackLikesWidgetBatch = []; 6var jetpackLikesMasterReady = false; 7 8// Due to performance problems on pages with a large number of widget iframes that need to be loaded, 9// we are limiting the processing at any instant to unloaded widgets that are currently in viewport, 10// plus this constant that will allow processing of widgets above and bellow the current fold. 11// This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users. 12var jetpackLikesLookAhead = 2000; // pixels 13 14// Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view. 15var jetpackCommentLikesLoadedWidgets = []; 16 17var jetpackLikesDocReadyPromise = new Promise( resolve => { 18 if ( document.readyState !== 'loading' ) { 19 resolve(); 20 } else { 21 window.addEventListener( 'DOMContentLoaded', () => resolve() ); 22 } 23} ); 24 25function JetpackLikesPostMessage( message, target ) { 26 if ( typeof message === 'string' ) { 27 try { 28 message = JSON.parse( message ); 29 } catch { 30 return; 31 } 32 } 33 34 if ( target && typeof target.postMessage === 'function' ) { 35 try { 36 target.postMessage( 37 JSON.stringify( { 38 type: 'likesMessage', 39 data: message, 40 } ), 41 '*' 42 ); 43 } catch { 44 // Ignore error 45 } 46 } 47} 48 49function JetpackLikesBatchHandler() { 50 const requests = []; 51 document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => { 52 if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) { 53 return; 54 } 55 56 if ( ! jetpackIsScrolledIntoView( widget ) ) { 57 return; 58 } 59 60 jetpackLikesWidgetBatch.push( widget.id ); 61 62 var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/, 63 match = regex.exec( widget.id ), 64 info; 65 66 if ( ! match || match.length !== 5 ) { 67 return; 68 } 69 70 info = { 71 blog_id: match[ 2 ], 72 width: widget.width, 73 }; 74 75 if ( 'post' === match[ 1 ] ) { 76 info.post_id = match[ 3 ]; 77 } else if ( 'comment' === match[ 1 ] ) { 78 info.comment_id = match[ 3 ]; 79 } 80 81 info.obj_id = match[ 4 ]; 82 83 requests.push( info ); 84 } ); 85 86 if ( requests.length > 0 ) { 87 JetpackLikesPostMessage( 88 { event: 'initialBatch', requests: requests }, 89 window.frames[ 'likes-master' ] 90 ); 91 } 92} 93 94function JetpackLikesMessageListener( event ) { 95 let message = event && event.data; 96 if ( typeof message === 'string' ) { 97 try { 98 message = JSON.parse( message ); 99 } catch { 100 return; 101 } 102 } 103 104 const type = message && message.type; 105 const data = message && message.data; 106 107 if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) { 108 return; 109 } 110 111 // We only allow messages from one origin 112 const allowedOrigin = 'https://widgets.wp.com'; 113 if ( allowedOrigin !== event.origin ) { 114 return; 115 } 116 117 switch ( data.event ) { 118 case 'masterReady': 119 jetpackLikesDocReadyPromise.then( () => { 120 jetpackLikesMasterReady = true; 121 122 const stylesData = { 123 event: 'injectStyles', 124 }; 125 const sdTextColor = document.querySelector( '.sd-text-color' ); 126 const sdLinkColor = document.querySelector( '.sd-link-color' ); 127 const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {}; 128 const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {}; 129 130 // enable reblogs if we're on a single post page 131 if ( document.body.classList.contains( 'single' ) ) { 132 JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] ); 133 } 134 135 stylesData.textStyles = { 136 color: sdTextColorStyles.color,
137 fontFamily: sdTextColorStyles[ 'font-family' ], 138 fontSize: sdTextColorStyles[ 'font-size' ], 139 direction: sdTextColorStyles.direction, 140 fontWeight: sdTextColorStyles[ 'font-weight' ], 141 fontStyle: sdTextColorStyles[ 'font-style' ], 142 textDecoration: sdTextColorStyles[ 'text-decoration' ], 143 }; 144 145 stylesData.linkStyles = { 146 color: sdLinkColorStyles.color, 147 fontFamily: sdLinkColorStyles[ 'font-family' ], 148 fontSize: sdLinkColorStyles[ 'font-size' ], 149 textDecoration: sdLinkColorStyles[ 'text-decoration' ], 150 fontWeight: sdLinkColorStyles[ 'font-weight' ], 151 fontStyle: sdLinkColorStyles[ 'font-style' ], 152 }; 153 154 JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] ); 155 156 JetpackLikesBatchHandler(); 157 } ); 158 159 break; 160 161 // We're keeping this for planned future follow ups. 162 // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815 163 case 'showLikeWidget': 164 break; 165 166 // We're keeping this for planned future follow ups. 167 // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815 168 case 'showCommentLikeWidget': 169 break; 170 171 case 'killCommentLikes': 172 // If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders. 173 document 174 .querySelectorAll( '.jetpack-comment-likes-widget-wrapper' ) 175 .forEach( wrapper => wrapper.remove() ); 176 break; 177 178 case 'clickReblogFlair': 179 if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) { 180 wpcom_reblog.toggle_reblog_box_flair( data.obj_id ); 181 } 182 break; 183 184 case 'hideOtherGravatars': { 185 hideLikersPopover(); 186 break; 187 } 188 189 case 'showOtherGravatars': { 190 const container = document.querySelector( '#likes-other-gravatars' ); 191 192 if ( ! container ) { 193 break; 194 } 195 196 const list = container.querySelector( 'ul' ); 197 198 container.style.display = 'none'; 199 list.innerHTML = ''; 200 201 container 202 .querySelectorAll( '.likes-text span' ) 203 .forEach( item => ( item.textContent = data.totalLikesLabel ) ); 204 205 ( data.likers || [] ).forEach( async ( liker, index ) => { 206 if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) { 207 // We only display gravatars with http or https schema 208 return; 209 } 210 211 const element = document.createElement( 'li' ); 212 list.append( element ); 213 214 const profileLink = encodeURI( liker.profile_URL ); 215 const avatarLink = encodeURI( liker.avatar_URL ); 216 element.innerHTML = `<a href="${ profileLink }" rel="nofollow" target="_parent" class="wpl-liker"> 217 <img src="${ avatarLink }" 218 alt="" 219 style="width: 28px; height: 28px;" /> 220 <span></span> 221 </a>`; 222 223 // Add some extra attributes through native methods, to ensure strings are sanitized. 224 element.classList.add( liker.css_class ); 225 element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name ); 226 element.querySelector( 'span' ).innerText = liker.name; 227 228 if ( index === data.likers.length - 1 ) { 229 element.addEventListener( 'keydown', e => { 230 if ( e.key === 'Tab' && ! e.shiftKey ) { 231 e.preventDefault(); 232 hideLikersPopover(); 233 234 JetpackLikesPostMessage( 235 { event: 'focusLikesCount', parent: data.parent }, 236 window.frames[ 'likes-master' ] 237 ); 238 } 239 } ); 240 } 241 } ); 242 243 const positionPopup = function () { 244 const containerStyle = getComputedStyle( container ); 245 const isRtl = containerStyle.direction === 'rtl'; 246 247 const el = document.querySelector( `*[name='${ data.parent }']` ); 248 const rect = el.getBoundingClientRect(); 249 const win = el.ownerDocument.defaultView; 250 const offset = { 251 top: rect.top + win.pageYOffset, 252 left: rect.left + win.pageXOffset, 253 }; 254 255 let containerLeft; 256 container.style.top = offset.top + data.position.top - 1 + 'px'; 257 258 if ( isRtl ) { 259 const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0; 260 // 24px is the width of the avatar + 4px is the padding between avatars 261 containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4; 262 container.style.transform = 'translateX(-100%)'; 263 } else { 264 containerLeft = offset.left + data.position.left; 265 } 266 container.style.left = containerLeft + 'px'; 267 268 // If the popup overflows viewport width, we should show it on the next line. 269 // Push it offscreen to calculated rendered width. 270 container.style.left = '-9999px'; 271 container.style.display = 'block'; 272 273 // If the popup exceeds the viewport width, 274 // flip the position of the popup. 275 const containerWidth = container.offsetWidth; 276 const containerRight = containerLeft + containerWidth; 277 if ( containerRight > win.innerWidth ) { 278 containerLeft = rect.right - containerWidth; 279 } 280 281 // Set the container left 282 container.style.left = containerLeft + 'px'; 283 container.setAttribute( 'aria-hidden', 'false' ); 284 }; 285 286 positionPopup(); 287 container.focus(); 288 289 const debounce = function ( func, wait ) { 290 var timeout; 291 return function () { 292 var context = this; 293 var args = arguments; 294 clearTimeout( timeout ); 295 timeout = setTimeout( function () { 296 func.apply( context, args ); 297 }, wait ); 298 }; 299 }; 300 301 const debouncedPositionPopup = debounce( positionPopup, 100 ); 302 303 // Keep a reference of this function in the element itself 304 // so that we can destroy it later 305 container.__resizeHandler = debouncedPositionPopup; 306 307 // When window is resized, resize the popup. 308 window.addEventListener( 'resize', debouncedPositionPopup ); 309 310 container.focus(); 311 } 312 } 313} 314 315window.addEventListener( 'message', JetpackLikesMessageListener ); 316 317function hideLikersPopover() { 318 const container = document.querySelector( '#likes-other-gravatars' ); 319 320 if ( container ) { 321 container.style.display = 'none'; 322 container.setAttribute( 'aria-hidden', 'true' ); 323 324 // Remove the resize event listener and cleanup. 325 const resizeHandler = container.__resizeHandler; 326 if ( resizeHandler ) { 327 window.removeEventListener( 'resize', resizeHandler ); 328 delete container.__resizeHandler; 329 } 330 } 331} 332 333document.addEventListener( 'click', hideLikersPopover ); 334 335function JetpackLikesWidgetQueueHandler() { 336 var wrapperID; 337 338 if ( ! jetpackLikesMasterReady ) { 339 // The master iframe emits `masterReady` a single time, when it finishes loading. On 340 // script-heavy pages it can finish loading â and emit â before this script attaches its 341 // message listener above, so that one event is missed and the queue never starts. Ping the 342 // master iframe while we wait so it re-emits `masterReady` once both it and our listener 343 // are ready. 344 JetpackLikesPostMessage( { event: 'queryMasterReady' }, window.frames[ 'likes-master' ] ); 345 setTimeout( JetpackLikesWidgetQueueHandler, 500 ); 346 return; 347 } 348 349 // Restore widgets to initial unloaded state when they are scrolled out of view. 350 jetpackUnloadScrolledOutWidgets(); 351 352 jetpackObserveUnloadedWidgets(); 353 354 var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView(); 355 356 if ( unloadedWidgetsInView.length > 0 ) { 357 // Grab any unloaded widgets for a batch request 358 JetpackLikesBatchHandler(); 359 } 360 361 for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) { 362 wrapperID = unloadedWidgetsInView[ i ].id; 363 364 if ( ! wrapperID ) { 365 continue; 366 } 367 368 jetpackLoadLikeWidgetIframe( wrapperID ); 369 } 370} 371 372function jetpackLoadLikeWidgetIframe( wrapperID ) { 373 if ( typeof wrapperID === 'undefined' ) { 374 return; 375 } 376 377 const wrapper = document.querySelector( '#' + wrapperID ); 378 wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() ); 379 380 const placeholder = wrapper.querySelector( '.likes-widget-placeholder' ); 381 382 // Post like iframe 383 if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) { 384 const postLikesFrame = document.createElement( 'iframe' ); 385 386 postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' ); 387 postLikesFrame.name = wrapper.dataset.name; 388 postLikesFrame.src = wrapper.dataset.src; 389 postLikesFrame.height = '55px'; 390 postLikesFrame.width = '100%'; 391 postLikesFrame.frameBorder = '0'; 392 postLikesFrame.scrolling = 'no'; 393 postLikesFrame.title = wrapper.dataset.title; 394 395 placeholder.after( postLikesFrame ); 396 } 397 398 // Comment like iframe 399 if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) { 400 const commentLikesFrame = document.createElement( 'iframe' ); 401 402 commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame'; 403 commentLikesFrame.name = wrapper.dataset.name; 404 commentLikesFrame.src = wrapper.dataset.src; 405 commentLikesFrame.height = '18px'; 406 commentLikesFrame.width = '100%'; 407 commentLikesFrame.frameBorder = '0'; 408 commentLikesFrame.scrolling = 'no'; 409 410 wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame ); 411 412 jetpackCommentLikesLoadedWidgets.push( commentLikesFrame ); 413 } 414 415 wrapper.classList.remove( 'jetpack-likes-widget-unloaded' ); 416 wrapper.classList.add( 'jetpack-likes-widget-loading' ); 417 418 wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => { 419 // A widget scrolled out of view mid-load has its iframe dropped, and a reloaded one 420 // replaces it, either of which leaves this closure holding a detached iframe. Acting on 421 // it would hide a placeholder that is now the only thing the widget has left to show. 422 if ( ! wrapper.contains( e.target ) ) { 423 return; 424 } 425 426 JetpackLikesPostMessage( 427 { event: 'loadLikeWidget', name: e.target.name, width: e.target.width }, 428 window.frames[ 'likes-master' ] 429 ); 430 431 // The stylesheet is otherwise the only thing that hides the placeholder, so one that never 432 // applies leaves "Loadingâ¦" sitting over a widget that loaded fine. 433 if ( placeholder ) { 434 placeholder.style.display = 'none'; 435 } 436 437 wrapper.classList.remove( 'jetpack-likes-widget-loading' ); 438 wrapper.classList.add( 'jetpack-likes-widget-loaded' ); 439 } ); 440} 441 442function jetpackGetUnloadedWidgetsInView() { 443 const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ); 444 445 return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) ); 446} 447 448function jetpackIsScrolledIntoView( element ) { 449 const top = element.getBoundingClientRect().top; 450 const bottom = element.getBoundingClientRect().bottom; 451
452 // Allow some slack above and bellow the fold with jetpackLikesLookAhead, 453 // with the aim of hiding the transition from unloaded to loaded widget from users. 454 return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead; 455} 456 457function jetpackUnloadScrolledOutWidgets() { 458 for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) { 459 const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ]; 460 461 if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) { 462 const widgetWrapper = 463 currentWidgetIframe && 464 currentWidgetIframe.parentElement && 465 currentWidgetIframe.parentElement.parentElement; 466 467 // Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed. 468 widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' ); 469 widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' ); 470 widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' ); 471 472 // An empty string removes the inline declaration rather than setting one, handing the 473 // placeholder back to the stylesheet. The `display: none` set on load would otherwise 474 // outrank it, leaving an unloaded widget with neither an iframe nor a placeholder. 475 const placeholder = widgetWrapper.querySelector( '.likes-widget-placeholder' ); 476 if ( placeholder ) { 477 placeholder.style.display = ''; 478 } 479 480 // Remove it from the list of loaded widgets. 481 jetpackCommentLikesLoadedWidgets.splice( i, 1 ); 482 483 // Remove comment like widget iFrame. 484 currentWidgetIframe.remove(); 485 } 486 } 487} 488 489var jetpackWidgetsDelayedExec = function ( after, fn ) { 490 var timer; 491 return function () { 492 clearTimeout( timer ); 493 timer = setTimeout( fn, after ); 494 }; 495}; 496 497var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler ); 498 499// Scrolling is not the only thing that brings a widget into range. A stylesheet that lands late 500// reflows the page without firing a scroll event, and the queue would never look at that widget 501// again, leaving it on "Loadingâ¦" until the reader happens to scroll. 502var jetpackLikesWidgetObserver = 503 typeof IntersectionObserver === 'function' 504 ? new IntersectionObserver( jetpackOnScrollStopped, { 505 rootMargin: `${ jetpackLikesLookAhead }px`, 506 // jetpackIsScrolledIntoView() wants the widget fully inside the band, so ask to be 507 // told when it gets there - crossing into partial overlap alone would not load it. 508 threshold: [ 0, 1 ], 509 } ) 510 : null; 511 512// Observing an element twice is a no-op, so every queue pass can call this to pick up widgets 513// added after load. 514function jetpackObserveUnloadedWidgets() { 515 if ( ! jetpackLikesWidgetObserver ) { 516 return; 517 } 518 519 document 520 .querySelectorAll( 'div.jetpack-likes-widget-unloaded' ) 521 .forEach( widget => jetpackLikesWidgetObserver.observe( widget ) ); 522} 523 524// Load initial batch of widgets, prior to any scrolling events. 525JetpackLikesWidgetQueueHandler(); 526 527// Add event listener to execute queue handler after scroll. 528window.addEventListener( 'scroll', jetpackOnScrollStopped, true ); 529;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.