1var _____WB$wombat$assign$function_____=function(name){return (self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init(name))||self[name];};if(!self.__WB_pmw){self.__WB_pmw=function(obj){this.__WB_source=obj;return this;}}{ 2let window = _____WB$wombat$assign$function_____("window"); 3let self = _____WB$wombat$assign$function_____("self"); 4let document = _____WB$wombat$assign$function_____("document"); 5let location = _____WB$wombat$assign$function_____("location"); 6let top = _____WB$wombat$assign$function_____("top"); 7let parent = _____WB$wombat$assign$function_____("parent"); 8let frames = _____WB$wombat$assign$function_____("frames"); 9let opens = _____WB$wombat$assign$function_____("opens"); 10/* Detect-zoom 11 * ----------- 12 * Cross Browser Zoom and Pixel Ratio Detector 13 * Version 1.0.4 | Apr 1 2013 14 * dual-licensed under the WTFPL and MIT license 15 * Maintained by https://github/tombigel 16 * Original developer https://github.com/yonran 17 */ 18 19//AMD and CommonJS initialization copied from https://github.com/zohararad/audio5js 20(function (root, ns, factory) { 21 "use strict"; 22 23 if (typeof (module) !== 'undefined' && module.exports) { // CommonJS 24 module.exports = factory(ns, root); 25 } else if (typeof (define) === 'function' && define.amd) { // AMD 26 define("factory", function () { 27 return factory(ns, root); 28 }); 29 } else { 30 root[ns] = factory(ns, root); 31 } 32 33}(window, 'detectZoom', function () { 34 35 /** 36 * Use devicePixelRatio if supported by the browser 37 * @return {Number} 38 * @private 39 */ 40 var devicePixelRatio = function () { 41 return window.devicePixelRatio || 1; 42 }; 43 44 /** 45 * Fallback function to set default values 46 * @return {Object} 47 * @private 48 */ 49 var fallback = function () { 50 return { 51 zoom: 1, 52 devicePxPerCssPx: 1 53 }; 54 }; 55 /** 56 * IE 8 and 9: no trick needed! 57 * TODO: Test on IE10 and Windows 8 RT 58 * @return {Object} 59 * @private 60 **/ 61 var ie8 = function () { 62 var zoom = Math.round((screen.deviceXDPI / screen.logicalXDPI) * 100) / 100; 63 return { 64 zoom: zoom, 65 devicePxPerCssPx: zoom * devicePixelRatio() 66 }; 67 }; 68 69 /** 70 * For IE10 we need to change our technique again... 71 * thanks https://github.com/stefanvanburen 72 * @return {Object} 73 * @private 74 */ 75 var ie10 = function () { 76 var zoom = Math.round((document.documentElement.offsetHeight / window.innerHeight) * 100) / 100; 77 return { 78 zoom: zoom, 79 devicePxPerCssPx: zoom * devicePixelRatio() 80 }; 81 }; 82 83 /** 84 * Mobile WebKit 85 * the trick: window.innerWIdth is in CSS pixels, while 86 * screen.width and screen.height are in system pixels. 87 * And there are no scrollbars to mess up the measurement. 88 * @return {Object} 89 * @private 90 */ 91 var webkitMobile = function () { 92 var deviceWidth = (Math.abs(window.orientation) == 90) ? screen.height : screen.width; 93 var zoom = deviceWidth / window.innerWidth; 94 return { 95 zoom: zoom, 96 devicePxPerCssPx: zoom * devicePixelRatio() 97 }; 98 }; 99 100 /** 101 * Desktop Webkit 102 * the trick: an element's clientHeight is in CSS pixels, while you can 103 * set its line-height in system pixels using font-size and 104 * -webkit-text-size-adjust:none. 105 * device-pixel-ratio: http://www.webkit.org/blog/55/high-dpi-web-sites/ 106 * 107 * Previous trick (used before http://trac.webkit.org/changeset/100847): 108 * documentElement.scrollWidth is in CSS pixels, while 109 * document.width was in system pixels. Note that this is the 110 * layout width of the document, which is slightly different from viewport 111 * because document width does not include scrollbars and might be wider 112 * due to big elements. 113 * @return {Object} 114 * @private 115 */ 116 var webkit = function () { 117 var important = function (str) { 118 return str.replace(/;/g, " !important;"); 119 }; 120 121 var div = document.createElement('div'); 122 div.innerHTML = "1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9<br>0"; 123 div.setAttribute('style', important('font: 100px/1em sans-serif; -webkit-text-size-adjust: none; text-size-adjust: none; height: auto; width: 1em; padding: 0; overflow: visible;')); 124 125 // The container exists so that the div will be laid out in its own flow 126 // while not impacting the layout, viewport size, or display of the 127 // webpage as a whole. 128 // Add !important and relevant CSS rule resets 129 // so that other rules cannot affect the results. 130 var container = document.createElement('div'); 131 container.setAttribute('style', important('width:0; height:0; overflow:hidden; visibility:hidden; position: absolute;')); 132 container.appendChild(div); 133 134 document.body.appendChild(container); 135 var zoom = 1000 / div.clientHeight; 136 zoom = Math.round(zoom * 100) / 100; 137 document.body.removeChild(container); 138 139 return{ 140 zoom: zoom, 141 devicePxPerCssPx: zoom * devicePixelRatio() 142 }; 143 }; 144 145 /** 146 * no real trick; device-pixel-ratio is the ratio of device dpi / css dpi. 147 * (Note that this is a different interpretation than Webkit's device 148 * pixel ratio, which is the ratio device dpi / system dpi). 149 * 150 * Also, for Mozilla, there is no difference between the zoom factor and the device ratio. 151 * 152 * @return {Object} 153 * @private 154 */ 155 var firefox4 = function () { 156 var zoom = mediaQueryBinarySearch('min--moz-device-pixel-ratio', '', 0, 10, 20, 0.0001); 157 zoom = Math.round(zoom * 100) / 100; 158 return { 159 zoom: zoom, 160 devicePxPerCssPx: zoom 161 }; 162 }; 163 164 /** 165 * Firefox 18.x
166 * Mozilla added support for devicePixelRatio to Firefox 18, 167 * but it is affected by the zoom level, so, like in older 168 * Firefox we can't tell if we are in zoom mode or in a device 169 * with a different pixel ratio 170 * @return {Object} 171 * @private 172 */ 173 var firefox18 = function () { 174 return { 175 zoom: firefox4().zoom, 176 devicePxPerCssPx: devicePixelRatio() 177 }; 178 }; 179 180 /** 181 * works starting Opera 11.11 182 * the trick: outerWidth is the viewport width including scrollbars in 183 * system px, while innerWidth is the viewport width including scrollbars 184 * in CSS px 185 * @return {Object} 186 * @private 187 */ 188 var opera11 = function () { 189 var zoom = window.top.outerWidth / window.top.innerWidth; 190 zoom = Math.round(zoom * 100) / 100; 191 return { 192 zoom: zoom, 193 devicePxPerCssPx: zoom * devicePixelRatio() 194 }; 195 }; 196 197 /** 198 * Use a binary search through media queries to find zoom level in Firefox 199 * @param property 200 * @param unit 201 * @param a 202 * @param b 203 * @param maxIter 204 * @param epsilon 205 * @return {Number} 206 */ 207 var mediaQueryBinarySearch = function (property, unit, a, b, maxIter, epsilon) { 208 var matchMedia; 209 var head, style, div; 210 if (window.matchMedia) { 211 matchMedia = window.matchMedia; 212 } else { 213 head = document.getElementsByTagName('head')[0]; 214 style = document.createElement('style'); 215 head.appendChild(style); 216 217 div = document.createElement('div'); 218 div.className = 'mediaQueryBinarySearch'; 219 div.style.display = 'none'; 220 document.body.appendChild(div); 221 222 matchMedia = function (query) { 223 style.sheet.insertRule('@media ' + query + '{.mediaQueryBinarySearch ' + '{text-decoration: underline} }', 0); 224 var matched = getComputedStyle(div, null).textDecoration == 'underline'; 225 style.sheet.deleteRule(0); 226 return {matches: matched}; 227 }; 228 } 229 var ratio = binarySearch(a, b, maxIter); 230 if (div) { 231 head.removeChild(style); 232 document.body.removeChild(div); 233 } 234 return ratio; 235 236 function binarySearch(a, b, maxIter) { 237 var mid = (a + b) / 2; 238 if (maxIter <= 0 || b - a < epsilon) { 239 return mid; 240 } 241 var query = "(" + property + ":" + mid + unit + ")"; 242 if (matchMedia(query).matches) { 243 return binarySearch(mid, b, maxIter - 1); 244 } else { 245 return binarySearch(a, mid, maxIter - 1); 246 } 247 } 248 }; 249 250 /** 251 * Generate detection function 252 * @private 253 */ 254 var detectFunction = (function () { 255 var func = fallback; 256 //IE8+ 257 if (!isNaN(screen.logicalXDPI) && !isNaN(screen.systemXDPI)) { 258 func = ie8; 259 } 260 // IE10+ / Touch 261 else if (window.navigator.msMaxTouchPoints) { 262 func = ie10; 263 } 264 //Mobile Webkit 265 else if ('orientation' in window && typeof document.body.style.webkitMarquee === 'string') { 266 func = webkitMobile; 267 } 268 //WebKit 269 else if (typeof document.body.style.webkitMarquee === 'string') { 270 func = webkit; 271 } 272 //Opera 273 else if (navigator.userAgent.indexOf('Opera') >= 0) { 274 func = opera11; 275 } 276 //Last one is Firefox 277 //FF 18.x 278 else if (window.devicePixelRatio) { 279 func = firefox18; 280 } 281 //FF 4.0 - 17.x 282 else if (firefox4().zoom > 0.001) { 283 func = firefox4; 284 } 285 286 return func; 287 }()); 288 289 290 return ({ 291 292 /** 293 * Ratios.zoom shorthand 294 * @return {Number} Zoom level 295 */ 296 zoom: function () { 297 return detectFunction().zoom; 298 }, 299 300 /** 301 * Ratios.devicePxPerCssPx shorthand 302 * @return {Number} devicePxPerCssPx level 303 */ 304 device: function () { 305 return detectFunction().devicePxPerCssPx; 306 } 307 }); 308})); 309 310var wpcom_img_zoomer = { 311 clientHintSupport: { 312 gravatar: false, 313 files: false, 314 photon: false, 315 mshots: false, 316 staticAssets: false, 317 latex: false, 318 imgpress: false, 319 }, 320 useHints: false, 321 zoomed: false, 322 timer: null, 323 interval: 1000, // zoom polling interval in millisecond 324 325 // Should we apply width/height attributes to control the image size? 326 imgNeedsSizeAtts: function( img ) {
327 // Do not overwrite existing width/height attributes. 328 if ( img.getAttribute('width') !== null || img.getAttribute('height') !== null ) 329 return false; 330 // Do not apply the attributes if the image is already constrained by a parent element. 331 if ( img.width < img.naturalWidth || img.height < img.naturalHeight ) 332 return false; 333 return true; 334 }, 335 336 hintsFor: function( service ) { 337 if ( this.useHints === false ) { 338 return false; 339 } 340 if ( this.hints() === false ) { 341 return false; 342 } 343 if ( typeof this.clientHintSupport[service] === "undefined" ) { 344 return false; 345 } 346 if ( this.clientHintSupport[service] === true ) { 347 return true; 348 } 349 return false; 350 }, 351 352 hints: function() { 353 try { 354 var chrome = window.navigator.userAgent.match(/\sChrome\/([0-9]+)\.[.0-9]+\s/) 355 if (chrome !== null) { 356 var version = parseInt(chrome[1], 10) 357 if (isNaN(version) === false && version >= 46) { 358 return true 359 } 360 } 361 } catch (e) { 362 return false 363 } 364 return false 365 }, 366 367 init: function() { 368 var t = this; 369 try{ 370 t.zoomImages(); 371 t.timer = setInterval( function() { t.zoomImages(); }, t.interval ); 372 } 373 catch(e){ 374 } 375 }, 376 377 stop: function() { 378 if ( this.timer ) 379 clearInterval( this.timer ); 380 }, 381 382 getScale: function() { 383 var scale = detectZoom.device(); 384 // Round up to 1.5 or the next integer below the cap. 385 if ( scale <= 1.0 ) scale = 1.0; 386 else if ( scale <= 1.5 ) scale = 1.5; 387 else if ( scale <= 2.0 ) scale = 2.0; 388 else if ( scale <= 3.0 ) scale = 3.0; 389 else if ( scale <= 4.0 ) scale = 4.0; 390 else scale = 5.0; 391 return scale; 392 }, 393 394 shouldZoom: function( scale ) { 395 var t = this; 396 // Do not operate on hidden frames. 397 if ( "innerWidth" in window && !window.innerWidth ) 398 return false; 399 // Don't do anything until scale > 1 400 if ( scale == 1.0 && t.zoomed == false ) 401 return false; 402 return true; 403 }, 404 405 zoomImages: function() { 406 var t = this; 407 var scale = t.getScale(); 408 if ( ! t.shouldZoom( scale ) ){ 409 return; 410 } 411 t.zoomed = true; 412 // Loop through all the <img> elements on the page. 413 var imgs = document.getElementsByTagName("img"); 414 415 for ( var i = 0; i < imgs.length; i++ ) { 416 // Wait for original images to load 417 if ( "complete" in imgs[i] && ! imgs[i].complete ) 418 continue; 419 420 // Skip images that have srcset attributes. 421 if ( imgs[i].hasAttribute('srcset') ) { 422 continue; 423 } 424 425 // Skip images that don't need processing. 426 var imgScale = imgs[i].getAttribute("scale"); 427 if ( imgScale == scale || imgScale == "0" ) 428 continue; 429 430 // Skip images that have already failed at this scale 431 var scaleFail = imgs[i].getAttribute("scale-fail"); 432 if ( scaleFail && scaleFail <= scale ) 433 continue; 434 435 // Skip images that have no dimensions yet. 436 if ( ! ( imgs[i].width && imgs[i].height ) ) 437 continue; 438 439 // Skip images from Lazy Load plugins 440 if ( ! imgScale && imgs[i].getAttribute("data-lazy-src") && (imgs[i].getAttribute("data-lazy-src") !== imgs[i].getAttribute("src"))) 441 continue; 442 443 if ( t.scaleImage( imgs[i], scale ) ) { 444 // Mark the img as having been processed at this scale. 445 imgs[i].setAttribute("scale", scale); 446 } 447 else { 448 // Set the flag to skip this image. 449 imgs[i].setAttribute("scale", "0"); 450 } 451 } 452 }, 453 454 scaleImage: function( img, scale ) { 455 var t = this; 456 var newSrc = img.src; 457 458 var isFiles = false; 459 var isLatex = false; 460 var isPhoton = false; 461 462 // Skip slideshow images 463 if ( img.parentNode.className.match(/slideshow-slide/) ) 464 return false; 465 466 // Skip CoBlocks Lightbox images 467 if ( img.parentNode.className.match(/coblocks-lightbox__image/) ) 468 return false; 469 470 // Scale gravatars that have ?s= or ?size= 471 if ( img.src.match( /^https?:\/\/([^\/]*\.)?gravatar\.com\/.+[?&](s|size)=/ ) ) { 472 if ( this.hintsFor( "gravatar" ) === true ) { 473 return false;
474 } 475 newSrc = img.src.replace( /([?&](s|size)=)(\d+)/, function( $0, $1, $2, $3 ) { 476 // Stash the original size 477 var originalAtt = "originals", 478 originalSize = img.getAttribute(originalAtt); 479 if ( originalSize === null ) { 480 originalSize = $3; 481 img.setAttribute(originalAtt, originalSize); 482 if ( t.imgNeedsSizeAtts( img ) ) { 483 // Fix width and height attributes to rendered dimensions. 484 img.width = img.width; 485 img.height = img.height; 486 } 487 } 488 // Get the width/height of the image in CSS pixels 489 var size = img.clientWidth; 490 // Convert CSS pixels to device pixels 491 var targetSize = Math.ceil(img.clientWidth * scale); 492 // Don't go smaller than the original size 493 targetSize = Math.max( targetSize, originalSize ); 494 // Don't go larger than the service supports 495 targetSize = Math.min( targetSize, 512 ); 496 return $1 + targetSize; 497 }); 498 } 499 500 // Scale mshots that have width 501 else if ( img.src.match(/^https?:\/\/([^\/]+\.)*(wordpress|wp)\.com\/mshots\/.+[?&]w=\d+/) ) { 502 if ( this.hintsFor( "mshots" ) === true ) { 503 return false; 504 } 505 newSrc = img.src.replace( /([?&]w=)(\d+)/, function($0, $1, $2) { 506 // Stash the original size 507 var originalAtt = 'originalw', originalSize = img.getAttribute(originalAtt); 508 if ( originalSize === null ) { 509 originalSize = $2; 510 img.setAttribute(originalAtt, originalSize); 511 if ( t.imgNeedsSizeAtts( img ) ) { 512 // Fix width and height attributes to rendered dimensions. 513 img.width = img.width; 514 img.height = img.height; 515 } 516 } 517 // Get the width of the image in CSS pixels 518 var size = img.clientWidth; 519 // Convert CSS pixels to device pixels 520 var targetSize = Math.ceil(size * scale); 521 // Don't go smaller than the original size 522 targetSize = Math.max( targetSize, originalSize ); 523 // Don't go bigger unless the current one is actually lacking 524 if ( scale > img.getAttribute("scale") && targetSize <= img.naturalWidth ) 525 targetSize = $2; 526 if ( $2 != targetSize ) 527 return $1 + targetSize; 528 return $0; 529 }); 530 531 // Update height attribute to match width 532 newSrc = newSrc.replace( /([?&]h=)(\d+)/, function($0, $1, $2) { 533 if ( newSrc == img.src ) { 534 return $0; 535 } 536 // Stash the original size 537 var originalAtt = 'originalh', originalSize = img.getAttribute(originalAtt); 538 if ( originalSize === null ) { 539 originalSize = $2; 540 img.setAttribute(originalAtt, originalSize); 541 } 542 // Get the height of the image in CSS pixels 543 var size = img.clientHeight; 544 // Convert CSS pixels to device pixels 545 var targetSize = Math.ceil(size * scale); 546 // Don't go smaller than the original size 547 targetSize = Math.max( targetSize, originalSize ); 548 // Don't go bigger unless the current one is actually lacking 549 if ( scale > img.getAttribute("scale") && targetSize <= img.naturalHeight ) 550 targetSize = $2; 551 if ( $2 != targetSize ) 552 return $1 + targetSize; 553 return $0; 554 }); 555 } 556 557 // Scale simple imgpress queries (s0.wp.com) that only specify w/h/fit 558 else if ( img.src.match(/^https?:\/\/([^\/.]+\.)*(wp|wordpress)\.com\/imgpress\?(.+)/) ) { 559 if ( this.hintsFor( "imgpress" ) === true ) { 560 return false; 561 } 562 var imgpressSafeFunctions = ["zoom", "url", "h", "w", "fit", "filter", "brightness", "contrast", "colorize", "smooth", "unsharpmask"]; 563 // Search the query string for unsupported functions. 564 var qs = RegExp.$3.split('&'); 565 for ( var q in qs ) { 566 q = qs[q].split('=')[0]; 567 if ( imgpressSafeFunctions.indexOf(q) == -1 ) { 568 return false; 569 } 570 } 571 // Fix width and height attributes to rendered dimensions. 572 img.width = img.width; 573 img.height = img.height; 574 // Compute new src 575 if ( scale == 1 ) 576 newSrc = img.src.replace(/\?(zoom=[^&]+&)?/, '?'); 577 else 578 newSrc = img.src.replace(/\?(zoom=[^&]+&)?/, '?zoom=' + scale + '&'); 579 } 580 581 // Scale files.wordpress.com, LaTeX, or Photon images (i#.wp.com) 582 else if ( 583 ( isFiles = img.src.match(/^https?:\/\/([^\/]+)\.files\.wordpress\.com\/.+[?&][wh]=/) ) || 584 ( isLatex = img.src.match(/^https?:\/\/([^\/.]+\.)*(wp|wordpress)\.com\/latex\.php\?(latex|zoom)=(.+)/) ) || 585 ( isPhoton = img.src.match(/^https?:\/\/i[\d]{1}\.wp\.com\/(.+)/) ) 586 ) { 587 if ( false !== isFiles && this.hintsFor( "files" ) === true ) { 588 return false 589 } 590 if ( false !== isLatex && this.hintsFor( "latex" ) === true ) { 591 return false 592 } 593 if ( false !== isPhoton && this.hintsFor( "photon" ) === true ) { 594 return false 595 } 596 // Fix width and height attributes to rendered dimensions. 597 img.width = img.width;
598 img.height = img.height; 599 // Compute new src 600 if ( scale == 1 ) { 601 newSrc = img.src.replace(/\?(zoom=[^&]+&)?/, '?'); 602 } else { 603 newSrc = img.src; 604 605 var url_var = newSrc.match( /([?&]w=)(\d+)/ ); 606 if ( url_var !== null && url_var[2] ) { 607 newSrc = newSrc.replace( url_var[0], url_var[1] + img.width ); 608 } 609 610 url_var = newSrc.match( /([?&]h=)(\d+)/ ); 611 if ( url_var !== null && url_var[2] ) { 612 newSrc = newSrc.replace( url_var[0], url_var[1] + img.height ); 613 } 614 615 var zoom_arg = '&zoom=2'; 616 if ( !newSrc.match( /\?/ ) ) { 617 zoom_arg = '?zoom=2'; 618 } 619 img.setAttribute( 'srcset', newSrc + zoom_arg + ' ' + scale + 'x' ); 620 } 621 } 622 623 // Scale static assets that have a name matching *-1x.png or *@1x.png 624 else if ( img.src.match(/^https?:\/\/[^\/]+\/.*[-@]([12])x\.(gif|jpeg|jpg|png)(\?|$)/) ) { 625 if ( this.hintsFor( "staticAssets" ) === true ) { 626 return false; 627 } 628 // Fix width and height attributes to rendered dimensions. 629 img.width = img.width; 630 img.height = img.height; 631 var currentSize = RegExp.$1, newSize = currentSize; 632 if ( scale <= 1 ) 633 newSize = 1; 634 else 635 newSize = 2; 636 if ( currentSize != newSize ) 637 newSrc = img.src.replace(/([-@])[12]x\.(gif|jpeg|jpg|png)(\?|$)/, '$1'+newSize+'x.$2$3'); 638 } 639 640 else { 641 return false; 642 } 643 644 // Don't set img.src unless it has changed. This avoids unnecessary reloads. 645 if ( newSrc != img.src ) { 646 // Store the original img.src 647 var prevSrc, origSrc = img.getAttribute("src-orig"); 648 if ( !origSrc ) { 649 origSrc = img.src; 650 img.setAttribute("src-orig", origSrc); 651 } 652 // In case of error, revert img.src 653 prevSrc = img.src; 654 img.onerror = function(){ 655 img.src = prevSrc; 656 if ( img.getAttribute("scale-fail") < scale ) 657 img.setAttribute("scale-fail", scale); 658 img.onerror = null; 659 }; 660 // Finally load the new image 661 img.src = newSrc; 662 } 663 664 return true; 665 } 666}; 667 668wpcom_img_zoomer.init(); 669; 670( function() { 671 var cookieValue = document.cookie.replace( /(?:(?:^|.*;\s*)eucookielaw\s*\=\s*([^;]*).*$)|^.*$/, '$1' ); 672 var overlay = document.querySelector( '#eu-cookie-law' ); 673 var container = document.querySelector( '.widget_eu_cookie_law_widget' ); 674 var initialScrollPosition, scrollFunction; 675 676 function remove( el ) { 677 return el && el.parentElement && el.parentElement.removeChild( el ); 678 } 679 680 function triggerDismissEvent() { 681 try { 682 const dismissEvent = new Event( 'eucookielaw-dismissed' ); 683 document.dispatchEvent( dismissEvent ); 684 } catch ( err ) { } 685 } 686 687 function removeOverlay() { 688 remove( overlay ); 689 triggerDismissEvent(); 690 } 691 692 function fade( el, type, fn ) { 693 var duration = 400; 694 695 el.style.display = 'block'; 696 el.style.transitionProperty = 'opacity'; 697 el.style.transitionDuration = duration + 'ms'; 698 el.style.opacity = type === 'in' ? 0 : 1; 699 700 // Double rAF to ensure styles are applied cross-browser. 701 requestAnimationFrame( function () { 702 requestAnimationFrame( function() { 703 el.style.opacity = type === 'in' ? 1 : 0; 704 // Wait for animation. 705 setTimeout( function () { 706 // Clean up. 707 el.style.removeProperty( 'opacity' ); 708 el.style.removeProperty( 'transition-property' ); 709 el.style.removeProperty( 'transition-duration' ); 710 711 if ( type === 'out' ) { 712 el.style.display = 'none'; 713 } 714 715 if ( typeof fn === 'function' ) { 716 fn(); 717 } 718 }, duration + 50 ); 719 } ); 720 } ); 721 } 722 723 function appendWidget() { 724 document.body.appendChild( container ); 725 overlay.style.display = 'block'; 726 fade( container, 'in' ); 727 } 728 729 if ( typeof wp !== 'undefined' && !! wp.customize ) { 730 appendWidget(); 731 return; 732 } 733 734 if ( ! overlay || ! container ) { 735 return; 736 } 737 738 if ( overlay.classList.contains( 'ads-active' ) ) { 739 var adsCookieValue = document.cookie.replace( /(?:(?:^|.*;\s*)personalized-ads-consent\s*\=\s*([^;]*).*$)|^.*$/, '$1' ); 740 if ( cookieValue !== '' && adsCookieValue !== '' ) { 741 removeOverlay(); 742 } 743 } else if ( cookieValue !== '' ) { 744 removeOverlay(); 745 } 746 747 appendWidget(); 748 749 overlay.querySelector( 'form' ).addEventListener( 'submit', accept ); 750 751 if ( overlay.classList.contains( 'hide-on-scroll' ) ) { 752 initialScrollPosition = window.pageYOffset; 753 754 scrollFunction = function() { 755 if ( Math.abs( window.pageYOffset - initialScrollPosition ) > 50 ) { 756 accept(); 757 } 758 }; 759 760 window.addEventListener( 'scroll', scrollFunction ); 761 } else if ( overlay.classList.contains( 'hide-on-time' ) ) { 762 var timeout = parseInt( overlay.getAttribute( 'data-hide-timeout' ), 10 ) || 0; 763 setTimeout( accept, timeout * 1000 ); 764 } 765 766 var accepted = false;
767 function accept( event ) { 768 if ( accepted ) { 769 return; 770 } 771 accepted = true; 772 773 if ( event && event.preventDefault ) { 774 event.preventDefault(); 775 } 776 777 if ( overlay.classList.contains( 'hide-on-scroll' ) ) { 778 window.removeEventListener( 'scroll', scrollFunction ); 779 } 780 781 var expireTime = new Date(); 782 var consentExpiration = parseInt( overlay.getAttribute( 'data-consent-expiration' ), 10 ) || 0; 783 expireTime.setTime( expireTime.getTime() + ( consentExpiration * 24 * 60 * 60 * 1000 ) ); 784 785 document.cookie = 'eucookielaw=' + expireTime.getTime() + ';path=/;expires=' + expireTime.toGMTString(); 786 if ( overlay.classList.contains( 'ads-active' ) && overlay.classList.contains( 'hide-on-button' ) ) { 787 document.cookie = 'personalized-ads-consent=' + expireTime.getTime() + ';path=/;expires=' + expireTime.toGMTString(); 788 } 789 790 fade( overlay, 'out', function() { 791 removeOverlay(); 792 remove( container ); 793 } ); 794 } 795} )(); 796; 797/* global wpcom_reblog */ 798 799var jetpackLikesWidgetBatch = []; 800var jetpackLikesMasterReady = false; 801 802// Due to performance problems on pages with a large number of widget iframes that need to be loaded, 803// we are limiting the processing at any instant to unloaded widgets that are currently in viewport, 804// plus this constant that will allow processing of widgets above and bellow the current fold. 805// This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users. 806var jetpackLikesLookAhead = 2000; // pixels 807 808// Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view. 809var jetpackCommentLikesLoadedWidgets = []; 810 811var jetpackLikesDocReadyPromise = new Promise( resolve => { 812 if ( document.readyState !== 'loading' ) { 813 resolve(); 814 } else { 815 window.addEventListener( 'DOMContentLoaded', () => resolve() ); 816 } 817} ); 818 819function JetpackLikesPostMessage( message, target ) { 820 if ( typeof message === 'string' ) { 821 try { 822 message = JSON.parse( message ); 823 } catch ( e ) { 824 return; 825 } 826 } 827 828 if ( target && typeof target.postMessage === 'function' ) { 829 try { 830 target.postMessage( 831 JSON.stringify( { 832 type: 'likesMessage', 833 data: message, 834 } ), 835 '*' 836 ); 837 } catch ( e ) { 838 return; 839 } 840 } 841} 842 843function JetpackLikesBatchHandler() { 844 const requests = []; 845 document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => { 846 if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) { 847 return; 848 } 849 850 if ( ! jetpackIsScrolledIntoView( widget ) ) { 851 return; 852 } 853 854 jetpackLikesWidgetBatch.push( widget.id ); 855 856 var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/, 857 match = regex.exec( widget.id ), 858 info; 859 860 if ( ! match || match.length !== 5 ) { 861 return; 862 } 863 864 info = { 865 blog_id: match[ 2 ], 866 width: widget.width, 867 }; 868 869 if ( 'post' === match[ 1 ] ) { 870 info.post_id = match[ 3 ]; 871 } else if ( 'comment' === match[ 1 ] ) { 872 info.comment_id = match[ 3 ]; 873 } 874 875 info.obj_id = match[ 4 ]; 876 877 requests.push( info ); 878 } ); 879 880 if ( requests.length > 0 ) { 881 JetpackLikesPostMessage( 882 { event: 'initialBatch', requests: requests }, 883 window.frames[ 'likes-master' ] 884 ); 885 } 886} 887 888function JetpackLikesMessageListener( event ) { 889 let message = event && event.data; 890 if ( typeof message === 'string' ) { 891 try { 892 message = JSON.parse( message ); 893 } catch ( err ) { 894 return; 895 } 896 } 897 898 const type = message && message.type; 899 const data = message && message.data; 900 901 if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) { 902 return; 903 } 904 905 // We only allow messages from one origin 906 const allowedOrigin = 'https://widgets.wp.com'; 907 if ( allowedOrigin !== event.origin ) { 908 return; 909 } 910 911 switch ( data.event ) { 912 case 'masterReady': 913 jetpackLikesDocReadyPromise.then( () => { 914 jetpackLikesMasterReady = true; 915 916 const stylesData = { 917 event: 'injectStyles', 918 }; 919 const sdTextColor = document.querySelector( '.sd-text-color' ); 920 const sdLinkColor = document.querySelector( '.sd-link-color' ); 921 const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {}; 922 const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {}; 923 924 if ( document.querySelectorAll( 'iframe.admin-bar-likes-widget' ).length > 0 ) { 925 JetpackLikesPostMessage( { event: 'adminBarEnabled' }, window.frames[ 'likes-master' ] ); 926 927 const bgSource = document.querySelector( 928 '#wpadminbar .quicklinks li#wp-admin-bar-wpl-like > a' 929 ); 930 931 const wpAdminBar = document.querySelector( '#wpadminbar' ); 932 933 stylesData.adminBarStyles = { 934 background: bgSource && getComputedStyle( bgSource ).background, 935 isRtl: wpAdminBar && getComputedStyle( wpAdminBar ).direction === 'rtl', 936 }; 937 } 938 939 // enable reblogs if they are enabled for the page 940 if ( document.body.classList.contains( 'jetpack-reblog-enabled' ) ) { 941 JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] ); 942 } 943 944 stylesData.textStyles = { 945 color: sdTextColorStyles[ 'color' ],
946 fontFamily: sdTextColorStyles[ 'font-family' ], 947 fontSize: sdTextColorStyles[ 'font-size' ], 948 direction: sdTextColorStyles[ 'direction' ], 949 fontWeight: sdTextColorStyles[ 'font-weight' ], 950 fontStyle: sdTextColorStyles[ 'font-style' ], 951 textDecoration: sdTextColorStyles[ 'text-decoration' ], 952 }; 953 954 stylesData.linkStyles = { 955 color: sdLinkColorStyles[ 'color' ], 956 fontFamily: sdLinkColorStyles[ 'font-family' ], 957 fontSize: sdLinkColorStyles[ 'font-size' ], 958 textDecoration: sdLinkColorStyles[ 'text-decoration' ], 959 fontWeight: sdLinkColorStyles[ 'font-weight' ], 960 fontStyle: sdLinkColorStyles[ 'font-style' ], 961 }; 962 963 JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] ); 964 965 JetpackLikesBatchHandler(); 966 } ); 967 968 break; 969 970 case 'showLikeWidget': { 971 const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` ); 972 if ( placeholder ) { 973 placeholder.style.display = 'none'; 974 } 975 break; 976 } 977 978 case 'showCommentLikeWidget': { 979 const placeholder = document.querySelector( `#${ data.id } .likes-widget-placeholder` ); 980 if ( placeholder ) { 981 placeholder.style.display = 'none'; 982 } 983 break; 984 } 985 986 case 'killCommentLikes': 987 // If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders. 988 document 989 .querySelectorAll( '.jetpack-comment-likes-widget-wrapper' ) 990 .forEach( wrapper => wrapper.remove() ); 991 break; 992 993 case 'clickReblogFlair': 994 if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) { 995 wpcom_reblog.toggle_reblog_box_flair( data.obj_id, data.post_id ); 996 } 997 break; 998 999 case 'showOtherGravatars': { 1000 const container = document.querySelector( '#likes-other-gravatars' ); 1001 if ( ! container ) { 1002 break; 1003 } 1004 1005 const list = container.querySelector( 'ul' ); 1006 1007 container.style.display = 'none'; 1008 list.innerHTML = ''; 1009 1010 container 1011 .querySelectorAll( '.likes-text span' ) 1012 .forEach( item => ( item.textContent = data.total ) ); 1013 1014 ( data.likers || [] ).forEach( async liker => { 1015 if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) { 1016 // We only display gravatars with http or https schema 1017 return; 1018 } 1019 1020 try { 1021 const response = await fetch( liker.avatar_URL, { method: 'HEAD' } ); 1022 if ( !response.ok ) { 1023 // Image doesn't exist, don't create the element 1024 return; 1025 } 1026 } catch ( error ) { 1027 // Error occurred while checking image existence, don't create the element 1028 return; 1029 } 1030 1031 const element = document.createElement( 'li' ); 1032 element.innerHTML = ` 1033 <a href="${ encodeURI( liker.profile_URL ) }" rel="nofollow" target="_parent" class="wpl-liker"> 1034 <img src="${ encodeURI( liker.avatar_URL ) }" 1035 alt="" 1036 style="width: 30px; height: 30px; padding-right: 3px;" /> 1037 </a> 1038 `; 1039 1040 list.append( element ); 1041 1042 // Add some extra attributes through native methods, to ensure strings are sanitized. 1043 element.classList.add( liker.css_class ); 1044 element.querySelector( 'img' ).alt = liker.name; 1045 } ); 1046 1047 const el = document.querySelector( `*[name='${ data.parent }']` ); 1048 const rect = el.getBoundingClientRect(); 1049 const win = el.ownerDocument.defaultView; 1050 const offset = { 1051 top: rect.top + win.pageYOffset, 1052 left: rect.left + win.pageXOffset, 1053 }; 1054 1055 container.style.left = offset.left + data.position.left - 10 + 'px'; 1056 container.style.top = offset.top + data.position.top - 33 + 'px'; 1057 1058 // Container width - padding 1059 const initContainerWidth = data.width - 20; 1060 const rowLength = Math.floor( initContainerWidth / 37 ); 1061 // # of rows + (avatar + avatar padding) + text above + container padding 1062 let height = Math.ceil( data.likers.length / rowLength ) * 37 + 17 + 22; 1063 if ( height > 204 ) { 1064 height = 204; 1065 } 1066 1067 // Avatars + padding 1068 const containerWidth = rowLength * 37 + 13; 1069 container.style.height = height + 'px'; 1070 container.style.width = containerWidth + 'px'; 1071 1072 const listWidth = rowLength * 37; 1073 list.style.width = listWidth + 'px'; 1074 1075 container.style.display = 'block'; 1076 } 1077 } 1078} 1079 1080window.addEventListener( 'message', JetpackLikesMessageListener ); 1081 1082document.addEventListener( 'click', e => { 1083 const container = document.querySelector( '#likes-other-gravatars' ); 1084 1085 if ( container && ! container.contains( e.target ) ) { 1086 container.style.display = 'none'; 1087 } 1088} ); 1089 1090function JetpackLikesWidgetQueueHandler() { 1091 var wrapperID; 1092 1093 if ( ! jetpackLikesMasterReady ) { 1094 setTimeout( JetpackLikesWidgetQueueHandler, 500 ); 1095 return; 1096 } 1097 1098 // Restore widgets to initial unloaded state when they are scrolled out of view. 1099 jetpackUnloadScrolledOutWidgets(); 1100 1101 var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView(); 1102 1103 if ( unloadedWidgetsInView.length > 0 ) { 1104 // Grab any unloaded widgets for a batch request 1105 JetpackLikesBatchHandler(); 1106 } 1107 1108 for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) { 1109 wrapperID = unloadedWidgetsInView[ i ].id; 1110 1111 if ( ! wrapperID ) { 1112 continue; 1113 } 1114 1115 jetpackLoadLikeWidgetIframe( wrapperID ); 1116 } 1117} 1118 1119function jetpackLoadLikeWidgetIframe( wrapperID ) { 1120 if ( typeof wrapperID === 'undefined' ) { 1121 return; 1122 } 1123 1124 const wrapper = document.querySelector( '#' + wrapperID ); 1125 wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() ); 1126 1127 const placeholder = wrapper.querySelector( '.likes-widget-placeholder' ); 1128 1129 // Post like iframe 1130 if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) { 1131 const postLikesFrame = document.createElement( 'iframe' ); 1132 1133 postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' ); 1134 postLikesFrame.name = wrapper.dataset.name; 1135 postLikesFrame.src = wrapper.dataset.src; 1136 postLikesFrame.height = '55px'; 1137 postLikesFrame.width = '100%'; 1138 postLikesFrame.frameBorder = '0'; 1139 postLikesFrame.scrolling = 'no'; 1140 postLikesFrame.title = wrapper.dataset.title; 1141 1142 placeholder.after( postLikesFrame ); 1143 } 1144 1145 // Comment like iframe 1146 if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) { 1147 const commentLikesFrame = document.createElement( 'iframe' ); 1148 1149 commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame'; 1150 commentLikesFrame.name = wrapper.dataset.name; 1151 commentLikesFrame.src = wrapper.dataset.src; 1152 commentLikesFrame.height = '18px'; 1153 commentLikesFrame.width = '100%'; 1154 commentLikesFrame.frameBorder = '0'; 1155 commentLikesFrame.scrolling = 'no'; 1156 1157 wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame ); 1158 1159 jetpackCommentLikesLoadedWidgets.push( commentLikesFrame ); 1160 } 1161 1162 wrapper.classList.remove( 'jetpack-likes-widget-unloaded' ); 1163 wrapper.classList.add( 'jetpack-likes-widget-loading' ); 1164 1165 wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => { 1166 JetpackLikesPostMessage( 1167 { event: 'loadLikeWidget', name: e.target.name, width: e.target.width }, 1168 window.frames[ 'likes-master' ] 1169 ); 1170 1171 wrapper.classList.remove( 'jetpack-likes-widget-loading' ); 1172 wrapper.classList.add( 'jetpack-likes-widget-loaded' ); 1173 } ); 1174} 1175 1176function jetpackGetUnloadedWidgetsInView() { 1177 const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ); 1178 1179 return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) ); 1180} 1181 1182function jetpackIsScrolledIntoView( element ) { 1183 const top = element.getBoundingClientRect().top; 1184 const bottom = element.getBoundingClientRect().bottom; 1185
1186 // Allow some slack above and bellow the fold with jetpackLikesLookAhead, 1187 // with the aim of hiding the transition from unloaded to loaded widget from users. 1188 return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead; 1189} 1190 1191function jetpackUnloadScrolledOutWidgets() { 1192 for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) { 1193 const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ]; 1194 1195 if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) { 1196 const widgetWrapper = 1197 currentWidgetIframe && 1198 currentWidgetIframe.parentElement && 1199 currentWidgetIframe.parentElement.parentElement; 1200 1201 // Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed. 1202 widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' ); 1203 widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' ); 1204 widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' ); 1205 1206 // Bring back the loading placeholder into view. 1207 widgetWrapper 1208 .querySelectorAll( '.comment-likes-widget-placeholder' ) 1209 .forEach( item => ( item.style.display = 'block' ) ); 1210 1211 // Remove it from the list of loaded widgets. 1212 jetpackCommentLikesLoadedWidgets.splice( i, 1 ); 1213 1214 // Remove comment like widget iFrame. 1215 currentWidgetIframe.remove(); 1216 } 1217 } 1218} 1219 1220var jetpackWidgetsDelayedExec = function ( after, fn ) { 1221 var timer; 1222 return function () { 1223 clearTimeout( timer ); 1224 timer = setTimeout( fn, after ); 1225 }; 1226}; 1227 1228var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler ); 1229 1230// Load initial batch of widgets, prior to any scrolling events. 1231JetpackLikesWidgetQueueHandler(); 1232 1233// Add event listener to execute queue handler after scroll. 1234window.addEventListener( 'scroll', jetpackOnScrollStopped, true ); 1235; 1236/** 1237 * Comment Likes - JavaScript 1238 * 1239 * This handles liking and unliking comments, as well as viewing who has 1240 * liked a particular comment. 1241 * 1242 * @dependency Swipe (dynamically loaded when needed) 1243 * 1244 * @package Comment_Likes 1245 * @subpackage JavaScript 1246 */ 1247(function () { 1248 function init() { 1249 let extWin; 1250 let extWinCheck; 1251 let commentLikeEvent; 1252 1253 // Only run once. 1254 if (window.comment_likes_loaded) { 1255 return; 1256 } 1257 window.comment_likes_loaded = true; 1258 1259 // Client-side cache of who liked a particular comment to avoid 1260 // having to hit the server multiple times for the same data. 1261 const commentLikeCache = {}; 1262 1263 let swipeLibPromise; 1264 1265 // Load the Swipe library, if it's not already loaded. 1266 function swipeLibLoader() { 1267 if (!swipeLibPromise) { 1268 swipeLibPromise = new Promise((resolve, reject) => { 1269 if (window.Swipe) { 1270 resolve(window.Swipe); 1271 } else { 1272 const swipeScript = document.createElement('script'); 1273 swipeScript.src = comment_like_text.swipeUrl; 1274 swipeScript.async = true; 1275 document.body.appendChild(swipeScript); 1276 swipeScript.addEventListener('load', () => resolve(window.Swipe)); 1277 swipeScript.addEventListener('error', error => reject(error)); 1278 } 1279 }); 1280 } 1281 return swipeLibPromise; 1282 } 1283 1284 /** 1285 * Parse the comment ID from a comment like link. 1286 */ 1287 function getCommentId(link) { 1288 const commentId = 1289 link && link.getAttribute('href') && link.getAttribute('href').split('like_comment='); 1290 return commentId[1].split('&_wpnonce=')[0]; 1291 } 1292 1293 /** 1294 * Handle an ajax action on the comment like link. 1295 */ 1296 function handleLinkAction(link, action, commentId, callback) { 1297 const nonce = 1298 link && link.getAttribute('href') && link.getAttribute('href').split('_wpnonce=')[1]; 1299 1300 fetch('/wp-admin/admin-ajax.php', { 1301 method: 'POST', 1302 body: new URLSearchParams({ 1303 action: action, 1304 _wpnonce: nonce, 1305 like_comment: commentId, 1306 blog_id: Number(link.dataset.blog), 1307 }), 1308 headers: { 1309 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 1310 'X-Requested-With': 'XMLHttpRequest', 1311 Accept: 'application/json', 1312 'cache-control': 'no-cache', 1313 pragma: 'no-cache', 1314 }, 1315 }) 1316 .then(response => response.json()) 1317 .then(callback); 1318 } 1319 1320 function startPolling() { 1321 // Append cookie polling login iframe to this window to wait for user to finish logging in (or cancel) 1322 const loginIframe = document.createElement('iframe'); 1323 loginIframe.id = 'wp-login-polling-iframe'; 1324 loginIframe.src = 'https://wordpress.com/public.api/connect/?iframe=true'; 1325 document.body.appendChild(loginIframe); 1326 loginIframe.style.display = 'none'; 1327 } 1328 1329 function stopPolling() { 1330 const iframe = document.querySelector('#wp-login-polling-iframe'); 1331 if (iframe) { 1332 iframe.remove(); 1333 } 1334 } 1335 1336 function hide(el) { 1337 if (el && el.style) { 1338 el.style.display = 'none'; 1339 } 1340 } 1341 1342 function show(el) { 1343 if (el && el.style) { 1344 el.style.removeProperty('display'); 1345 } 1346 } 1347 1348 // Overlay used for displaying comment like info. 1349 class Overlay { 1350 constructor() { 1351 // Overlay element. 1352 this.el = document.createElement('div'); 1353 this.el.classList.add('comment-likes-overlay'); 1354 document.body.appendChild(this.el); 1355 hide(this.el); 1356 1357 this.el.addEventListener('mouseenter', () => { 1358 // Don't hide the overlay if the user is mousing over it. 1359 overlay.cancelHide(); 1360 }); 1361 1362 this.el.addEventListener('mouseleave', () => overlay.requestHide()); 1363 1364 // Inner contents of overlay. 1365 this.innerEl = null; 1366 1367 // Instance of the Swipe library. 1368 this.swipe = null; 1369 1370 // Timeout used for hiding the overlay. 1371 this.hideTimeout = null; 1372 } 1373 1374 // Initialise the overlay for use, removing any old content. 1375 clear() { 1376 // Unload any previous instance of Swipe (to avoid leaking a global 1377 // event handler). This is done before clearing the contents of the 1378 // overlay because Swipe expects the slides to still be present. 1379 if (this.swipe) { 1380 this.swipe.kill(); 1381 this.swipe = null; 1382 } 1383 this.el.innerHTML = ''; 1384 this.innerEl = document.createElement('div'); 1385 this.innerEl.classList.add('inner'); 1386 this.el.appendChild(this.innerEl); 1387 } 1388 1389 /** 1390 * Construct a list (<ul>) of user (gravatar, name) details. 1391 * 1392 * @param data liker data returned from the server 1393 * @param klass CSS class to apply to the <ul> element 1394 * @param start index of user to start at 1395 * @param length number of users to include in the list 1396 * 1397 * @return A container element with the list 1398 */ 1399 getUserBits(data, klass, start, length) { 1400 start = start || 0; 1401 let last = start + (length || data.length); 1402 last = last > data.length ? data.length : last; 1403 const container = document.createElement('div'); 1404 container.classList.add('liker-list'); 1405 let html = `<ul class="${klass || ''}">`; 1406 for (let i = start; i < last; ++i) { 1407 const user = data[i]; 1408 html += ` 1409 <li> 1410 <a rel="nofollow" title="${user.display_name_esc}" href="${user.profile_url_esc}"> 1411 <img src="${user.avatar_url_esc}" alt="${user.display_name_esc}" /> 1412 <span class="user-name">${user.display_name_esc}</span> 1413 </a> 1414 </li> 1415 `; 1416 } 1417 html += '</ul>'; 1418 container.innerHTML = html; 1419 return container; 1420 } 1421 1422 /** 1423 * Render the display of who has liked this comment. The type of 1424 * display depends on how many people have liked the comment. 1425 * If more than 10 people have liked the comment, this function 1426 * renders navigation controls and sets up the Swipe library for 1427 * changing between pages. 1428 * 1429 * @param link the element over which the user is hovering 1430 * @param data the results retrieved from the server 1431 */ 1432 showLikes(link, data) { 1433 this.clear(); 1434 1435 link.dataset.likeCount = data.length; 1436 if (data.length === 0) { 1437 // No likers after all. 1438 hide(this.el); 1439 return; 1440 } 1441 1442 this.innerEl.style.padding = '12px'; 1443 1444 if (data.length < 6) { 1445 // Only one column needed. 1446 this.innerEl.style.maxWidth = '200px'; 1447 this.innerEl.innerHTML = ''; 1448 this.innerEl.appendChild(this.getUserBits(data, 'single')); 1449 this.setPosition(link); 1450 } else if (data.length < 11) { 1451 // Two columns, but only one page. 1452 this.innerEl.innerHTML = ''; 1453 this.innerEl.appendChild(this.getUserBits(data, 'double')); 1454 this.setPosition(link); 1455 } else { 1456 // Multiple pages. 1457 this.renderLikesWithPagination(data, link); 1458 } 1459 } 1460 1461 /**
1462 * Render multiple pages of likes with pagination controls. 1463 * This function is intended to be called by `showLikes` above. 1464 * 1465 * @param data the results retrieved from the server 1466 */ 1467 renderLikesWithPagination(data, link) { 1468 swipeLibLoader().then(() => { 1469 const page_count = Math.ceil(data.length / 10); 1470 // Swipe requires two nested containers. 1471 const swipe = document.createElement('div'); 1472 swipe.classList.add('swipe'); 1473 this.innerEl.appendChild(swipe); 1474 1475 const wrap = document.createElement('div'); 1476 wrap.classList.add('swipe-wrap'); 1477 swipe.appendChild(wrap); 1478 1479 for (let i = 0; i < page_count; ++i) { 1480 wrap.appendChild(this.getUserBits(data, 'double', i * 10, 10)); 1481 } 1482 1483 /** 1484 * Navigation controls. 1485 * This is based on the Newdash controls found in 1486 * reader/recommendations-templates.php 1487 */ 1488 const nav = document.createElement('nav'); 1489 nav.classList.add('slider-nav'); 1490 1491 let navContents = ` 1492 <a href="#" class="prev"> 1493 <span class="noticon noticon-previous" title="Previous" alt="<"></span> 1494 </a> 1495 <span class="position"> 1496 `; 1497 for (let i = 0; i < page_count; ++i) { 1498 navContents += `<em data-page="${i}" class="${i === 0 ? 'on' : ''}">•</em>`; 1499 } 1500 navContents += ` 1501 </span> 1502 <a href="#" class="next"> 1503 <span class="noticon noticon-next" title="Next" alt=">"></span> 1504 </a> 1505 `; 1506 this.innerEl.appendChild(nav); 1507 nav.innerHTML = navContents; 1508 1509 /** Set up Swipe. **/ 1510 // Swipe cannot be set up successfully unless its container 1511 // is visible, so we show it now. 1512 show(this.el); 1513 this.setPosition(link); 1514 1515 this.swipe = new Swipe(swipe, { 1516 callback: function (pos) { 1517 // Update the pagination indicators. 1518 // 1519 // If there are exactly two pages, Swipe has a weird 1520 // special case where it duplicates both pages and 1521 // can return index 2 and 3 even though those aren't 1522 // real pages (see swipe.js, line 47). To deal with 1523 // this, we use the expression `pos % page_count`. 1524 pos = pos % page_count; 1525 nav.querySelectorAll('em').forEach(em => { 1526 const page = Number(em.dataset.page); 1527 em.setAttribute('class', pos === page ? 'on' : ''); 1528 }); 1529 }, 1530 }); 1531 1532 nav.querySelectorAll('em').forEach(em => { 1533 em.addEventListener('click', e => { 1534 // Go to the page corresponding to the indicator clicked. 1535 this.swipe.slide(Number(em.dataset.page)); 1536 e.preventDefault(); 1537 }); 1538 }); 1539 // Previous and next buttons. 1540 nav.querySelector('.prev').addEventListener('click', e => { 1541 this.swipe.prev(); 1542 e.preventDefault(); 1543 }); 1544 nav.querySelector('.next').addEventListener('click', e => { 1545 this.swipe.next(); 1546 e.preventDefault(); 1547 }); 1548 }); 1549 } 1550 1551 /** 1552 * Open the overlay and show a loading message. 1553 */ 1554 showLoadingMessage(link) { 1555 this.clear(); 1556 this.innerEl.textContent = comment_like_text.loading; 1557 this.setPosition(link); 1558 } 1559 1560 /** 1561 * Position the overlay near the current comment. 1562 * 1563 * @param link element near which to position the overlay 1564 */ 1565 setPosition(link) { 1566 // Prepare a down arrow icon for the bottom of the overlay. 1567 const icon = document.createElement('span'); 1568 this.el.appendChild(icon); 1569 icon.classList.add('icon', 'noticon', 'noticon-downarrow'); 1570 icon.style.textShadow = '0px 1px 1px rgb(223, 223, 223)'; 1571 1572 const rect = link.getBoundingClientRect(); 1573 const win = document.defaultView; 1574 const offset = { 1575 top: rect.top + win.scrollY, 1576 left: rect.left + win.scrollX, 1577 }; 1578 1579 // Take measurements with the element fully visible. 1580 show(this.el); 1581 let left = offset.left - (this.el.offsetWidth - link.offsetWidth) / 2; 1582 left = left < 5 ? 5 : left; 1583 let top = offset.top - this.el.offsetHeight + 5; 1584 hide(this.el); 1585 1586 const adminBar = document.querySelector('#wpadminbar'); 1587 1588 // Check if the overlay would appear off the screen. 1589 if (top < win.scrollY + ((adminBar && adminBar.offsetHeight) || 0)) { 1590 // We'll display the overlay beneath the link instead. 1591 top = offset.top + link.offsetHeight; 1592 // Instead of using the down arrow icon, use an up arrow. 1593 icon.remove(); 1594 this.el.prepend(icon); 1595 icon.classList.remove('noticon-downarrow'); 1596 icon.classList.add('noticon-uparrow'); 1597 icon.style.textShadow = '0px -1px 1px rgb(223, 223, 223)'; 1598 icon.style.verticalAlign = 'bottom'; 1599 } 1600 1601 this.el.style.left = `${left}px`; 1602 this.el.style.top = `${top}px`; 1603 show(this.el); 1604 1605 // The height of the arrow icon differs slightly between browsers, 1606 // so we compute the margin here to make sure it isn't disjointed 1607 // from the overlay. 1608 icon.style.marginTop = `${icon.scrollHeight - 26}px`; 1609 icon.style.marginBottom = `${20 - icon.scrollHeight}px`; 1610 1611 // Position the arrow to be horizontally centred on the link. 1612 icon.style.paddingLeft = `${ 1613 offset.left - left + (link.offsetWidth - icon.scrollWidth) / 2 1614 }px`; 1615 } 1616 1617 /** 1618 * Return whether the overlay is visible. 1619 */ 1620 isVisible() { 1621 return this.el.style.getPropertyValue('display') !== 'none'; 1622 } 1623 1624 /** 1625 * Request that the overlay be hidden after a short delay. 1626 */ 1627 requestHide() { 1628 if (this.hideTimeout !== null) { 1629 return; 1630 } 1631 this.hideTimeout = setTimeout(() => { 1632 hide(this.el); 1633 this.clear(); 1634 }, 300); 1635 } 1636 1637 /** 1638 * Cancel a request to hide the overlay. 1639 */ 1640 cancelHide() { 1641 if (this.hideTimeout !== null) { 1642 clearTimeout(this.hideTimeout); 1643 this.hideTimeout = null; 1644 } 1645 } 1646 } 1647 1648 // Overlay used for displaying comment like info.
1649 const overlay = new Overlay(); 1650 1651 // The most recent comment for which the user has requested to see 1652 // who liked it. 1653 var relevantComment; 1654 1655 // Precache after this timeout. 1656 var precacheTimeout = null; 1657 1658 /** 1659 * Fetch the like data for a particular comment. 1660 */ 1661 function fetchLikeData(link, commentId) { 1662 commentLikeCache[commentId] = null; 1663 1664 const container = link && link.parentElement && link.parentElement.parentElement; 1665 const star = container.querySelector('a.comment-like-link'); 1666 star && 1667 handleLinkAction(star, 'view_comment_likes', commentId, data => { 1668 // Populate the cache. 1669 commentLikeCache[commentId] = data; 1670 1671 // Only show the overlay if the user is interested. 1672 if (overlay.isVisible() && relevantComment === commentId) { 1673 overlay.showLikes(link, data); 1674 } 1675 }); 1676 } 1677 1678 function readCookie(c) { 1679 const nameEQ = c + '='; 1680 const cookieStrings = document.cookie.split(';'); 1681 1682 for (let i = 0; i < cookieStrings.length; i++) { 1683 let cookieString = cookieStrings[i]; 1684 while (cookieString.charAt(0) === ' ') { 1685 cookieString = cookieString.substring(1, cookieString.length); 1686 } 1687 if (cookieString.indexOf(nameEQ) === 0) { 1688 const chunk = cookieString.substring(nameEQ.length, cookieString.length); 1689 const pairs = chunk.split('&'); 1690 const cookieData = {}; 1691 for (let num = pairs.length - 1; num >= 0; num--) { 1692 const pair = pairs[num].split('='); 1693 cookieData[pair[0]] = decodeURIComponent(pair[1]); 1694 } 1695 return cookieData; 1696 } 1697 } 1698 return null; 1699 } 1700 1701 function getServiceData() { 1702 const data = readCookie('wpc_wpc'); 1703 if (data === null || typeof data.access_token === 'undefined' || !data.access_token) { 1704 return false; 1705 } 1706 return data; 1707 } 1708 1709 function readMessage(msg) { 1710 const event = msg.data; 1711 1712 if (typeof event.event === 'undefined') { 1713 return; 1714 } 1715 1716 if (event.event === 'login' && event.success) { 1717 extWinCheck = setInterval(function () { 1718 if (!extWin || extWin.closed) { 1719 clearInterval(extWinCheck); 1720 if (getServiceData()) { 1721 // Load page in an iframe to get the current comment nonce 1722 const nonceIframe = document.createElement('iframe'); 1723 nonceIframe.id = 'wp-login-comment-nonce-iframe'; 1724 nonceIframe.style.display = 'none'; 1725 nonceIframe.src = commentLikeEvent + ''; 1726 document.body.appendChild(nonceIframe); 1727 1728 const commentLikeId = (commentLikeEvent + '') 1729 .split('like_comment=')[1] 1730 .split('&_wpnonce=')[0]; 1731 let c; 1732 1733 // Set a 5 second timeout to redirect to the comment page without doing the Like as a fallback 1734 const commentLikeTimeout = setTimeout(() => { 1735 window.location = commentLikeEvent; 1736 }, 5000); 1737 1738 // Check for a new nonced redirect and use that if available before timing out 1739 const commentLikeCheck = setInterval(() => { 1740 const iframe = document.querySelector('#wp-login-comment-nonce-iframe'); 1741 if (iframe) { 1742 c = iframe.querySelector(`#comment-like-${commentLikeId} .comment-like-link`); 1743 } 1744 if (c && typeof c.href !== 'undefined') { 1745 clearTimeout(commentLikeTimeout); 1746 clearInterval(commentLikeCheck); 1747 window.location = c.href; 1748 } 1749 }, 100); 1750 } 1751 } 1752 }, 100); 1753 1754 if (extWin) { 1755 if (!extWin.closed) { 1756 extWin.close(); 1757 } 1758 extWin = false; 1759 } 1760 1761 stopPolling(); 1762 } 1763 } 1764 1765 if (typeof window.postMessage !== 'undefined') { 1766 window.addEventListener('message', e => { 1767 let message = e && e.data; 1768 if (typeof message === 'string') { 1769 try { 1770 message = JSON.parse(message); 1771 } catch (err) { 1772 return; 1773 } 1774 } 1775 1776 const type = message && message.type; 1777 if (type === 'loginMessage') { 1778 readMessage(message); 1779 } 1780 }); 1781 } 1782 1783 document.body.addEventListener('click', e => { 1784 let target = e.target; 1785 1786 // Don't do anything when clicking on the "X people" link. 1787 if (target.matches('p.comment-likes a.view-likers')) { 1788 e.preventDefault(); 1789 return; 1790 } 1791 1792 // Retrieve the surrounding paragraph to the star, if it hasn't been liked. 1793 const notLikedPar = target.closest('p.comment-not-liked'); 1794 1795 // Return if not clicking on star or surrounding paragraph. 1796 if (!target.matches('a.comment-like-link') && !notLikedPar) { 1797 return; 1798 } 1799 1800 // When a comment hasn't been liked, make the text clickable, too. 1801 if (notLikedPar) { 1802 target = notLikedPar.querySelector('a.comment-like-link'); 1803 if (!target) { 1804 return; 1805 } 1806 } 1807 1808 if (target.classList.contains('needs-login')) { 1809 e.preventDefault(); 1810 commentLikeEvent = target; 1811 if (extWin) { 1812 if (!extWin.closed) { 1813 extWin.close(); 1814 } 1815 extWin = false;
1816 } 1817 1818 stopPolling(); 1819 1820 const url = 'https://wordpress.com/public.api/connect/?action=request&service=wordpress'; 1821 extWin = window.open( 1822 url, 1823 'likeconn', 1824 'status=0,toolbar=0,location=1,menubar=0,directories=0,resizable=1,scrollbars=1,height=560,width=500' 1825 ); 1826 1827 startPolling(); 1828 1829 return false; 1830 } 1831 1832 // Record that the user likes or does not like this comment. 1833 const commentId = getCommentId(target); 1834 target.classList.add('loading'); 1835 1836 let commentEl = document.querySelector(`p#comment-like-${commentId}`); 1837 // Determine whether to like or unlike based on whether the comment is 1838 // currently liked. 1839 const action = 1840 commentEl && commentEl.dataset.liked === 'comment-liked' 1841 ? 'unlike_comment' 1842 : 'like_comment'; 1843 handleLinkAction(target, action, commentId, data => { 1844 // Invalidate the like cache for this comment. 1845 delete commentLikeCache[commentId]; 1846 1847 const countEl = document.querySelector(`#comment-like-count-${data.context}`); 1848 if (countEl) { 1849 countEl.innerHTML = data.display; 1850 } 1851 1852 commentEl = document.querySelector(`p#comment-like-${data.context}`); 1853 if (action === 'like_comment') { 1854 commentEl.classList.remove('comment-not-liked'); 1855 commentEl.classList.add('comment-liked'); 1856 commentEl.dataset.liked = 'comment-liked'; 1857 } else { 1858 commentEl.classList.remove('comment-liked'); 1859 commentEl.classList.add('comment-not-liked'); 1860 commentEl.dataset.liked = 'comment-not-liked'; 1861 } 1862 1863 // Prefetch new data for this comment (if there are likers left). 1864 const parent = target.closest('.comment-likes'); 1865 const link = parent && parent.querySelector('a.view-likers'); 1866 if (link) { 1867 fetchLikeData(link, commentId); 1868 } 1869 1870 target.classList.remove('loading'); 1871 }); 1872 e.preventDefault(); 1873 e.stopPropagation(); 1874 }); 1875 1876 document.body.addEventListener( 1877 'mouseenter', 1878 function (e) { 1879 if (!e.target.matches('p.comment-likes a.view-likers')) { 1880 return; 1881 } 1882 // Show the user a list of who has liked this comment. 1883 1884 const link = e.target; 1885 if (Number(link.dataset.likeCount || 0) === 0) { 1886 // No one has liked this comment. 1887 return; 1888 } 1889 1890 // Don't hide the overlay. 1891 overlay.cancelHide(); 1892 1893 // Get the comment ID. 1894 const container = link.parentElement && link.parentElement.parentElement; 1895 const star = container && container.querySelector('a.comment-like-link'); 1896 const commentId = star && getCommentId(star); 1897 relevantComment = commentId; 1898 1899 // Check if the list of likes for this comment is already in 1900 // the cache. 1901 if (commentId in commentLikeCache) { 1902 const entry = commentLikeCache[commentId];
1903 // Only display the likes if the ajax request is 1904 // actually done. 1905 if (entry !== null) { 1906 overlay.showLikes(link, entry); 1907 } else { 1908 // Make sure the overlay is visible (in case 1909 // the user moved the mouse away while loading 1910 // but then came back before it finished 1911 // loading). 1912 overlay.showLoadingMessage(link); 1913 } 1914 return; 1915 } 1916 1917 // Position the "Loading..." overlay. 1918 overlay.showLoadingMessage(link); 1919 1920 // Fetch the data. 1921 fetchLikeData(link, commentId); 1922 }, 1923 true 1924 ); 1925 1926 document.body.addEventListener( 1927 'mouseleave', 1928 e => { 1929 if (!e.target.matches('p.comment-likes a.view-likers')) { 1930 return; 1931 } 1932 // User has moved cursor away - hide the overlay. 1933 overlay.requestHide(); 1934 }, 1935 true 1936 ); 1937 1938 document.body.addEventListener( 1939 'mouseenter', 1940 e => { 1941 if (!e.target.matches('.comment') || !e.target.querySelector('a.comment-like-link')) { 1942 return; 1943 } 1944 // User is moving over a comment - precache the comment like data.
1945 if (precacheTimeout !== null) { 1946 clearTimeout(precacheTimeout); 1947 precacheTimeout = null; 1948 } 1949 1950 const star = e.target.querySelector('a.comment-like-link'); 1951 const parent = star.closest('.comment-likes'); 1952 const link = parent && parent.querySelector('a.view-likers'); 1953 if (!link || Number(link.dataset.likeCount || 0) === 0) { 1954 // No likes. 1955 return; 1956 } 1957 const commentId = getCommentId(star); 1958 if (commentId in commentLikeCache) { 1959 // Already in cache. 1960 return; 1961 } 1962 1963 precacheTimeout = setTimeout(() => { 1964 precacheTimeout = null; 1965 if (commentId in commentLikeCache) { 1966 // Was cached in the interim. 1967 return; 1968 } 1969 fetchLikeData(link, commentId); 1970 }, 1000); 1971 }, 1972 true 1973 ); 1974 } 1975 1976 if (document.readyState !== 'loading') { 1977 init(); 1978 } else { 1979 document.addEventListener('DOMContentLoaded', init); 1980 } 1981})(); 1982; 1983/** 1984 * navigation.js 1985 * 1986 * Handles toggling the navigation menu for small screens and enables tab 1987 * support for dropdown menus. 1988 */ 1989( function() { 1990 var container, button, menu, links, subMenus; 1991 1992 container = document.getElementById( 'site-navigation' ); 1993 if ( ! container ) { 1994 return; 1995 } 1996 1997 button = container.getElementsByTagName( 'button' )[0]; 1998 if ( 'undefined' === typeof button ) { 1999 return; 2000 } 2001 2002 menu = container.getElementsByTagName( 'ul' )[0]; 2003 socialMenu = container.getElementsByTagName( 'ul' )[1]; 2004 2005 // Hide menu toggle button if both menus are empty and return early. 2006 if ( 'undefined' === typeof menu && 'undefined' === typeof socialMenu ) { 2007 button.style.display = 'none'; 2008 return; 2009 } 2010 2011 menu.setAttribute( 'aria-expanded', 'false' ); 2012 if ( -1 === menu.className.indexOf( 'nav-menu' ) ) { 2013 menu.className += ' nav-menu'; 2014 } 2015 2016 button.onclick = function() { 2017 if ( -1 !== container.className.indexOf( 'toggled' ) ) { 2018 container.className = container.className.replace( ' toggled', '' ); 2019 button.setAttribute( 'aria-expanded', 'false' ); 2020 menu.setAttribute( 'aria-expanded', 'false' ); 2021 socialMenu.setAttribute( 'aria-expanded', 'false' ); 2022 } else { 2023 container.className += ' toggled'; 2024 button.setAttribute( 'aria-expanded', 'true' ); 2025 menu.setAttribute( 'aria-expanded', 'true' ); 2026 socialMenu.setAttribute( 'aria-expanded', 'true' ); 2027 } 2028 }; 2029 2030 // Get all the link elements within the menu. 2031 links = menu.getElementsByTagName( 'a' ); 2032 subMenus = menu.getElementsByTagName( 'ul' ); 2033 2034 // Set menu items with submenus to aria-haspopup="true". 2035 for ( var i = 0, len = subMenus.length; i < len; i++ ) { 2036 subMenus[i].parentNode.setAttribute( 'aria-haspopup', 'true' ); 2037 } 2038 2039 // Each time a menu link is focused or blurred, toggle focus. 2040 for ( i = 0, len = links.length; i < len; i++ ) { 2041 links[i].addEventListener( 'focus', toggleFocus, true ); 2042 links[i].addEventListener( 'blur', toggleFocus, true ); 2043 } 2044 2045 /** 2046 * Sets or removes .focus class on an element. 2047 */ 2048 function toggleFocus() { 2049 var self = this; 2050 2051 // Move up through the ancestors of the current link until we hit .nav-menu. 2052 while ( -1 === self.className.indexOf( 'nav-menu' ) ) { 2053 2054 // On li elements toggle the class .focus. 2055 if ( 'li' === self.tagName.toLowerCase() ) { 2056 if ( -1 !== self.className.indexOf( 'focus' ) ) { 2057 self.className = self.className.replace( ' focus', '' ); 2058 } else { 2059 self.className += ' focus'; 2060 } 2061 } 2062 2063 self = self.parentElement; 2064 } 2065 } 2066 2067 // Fix child menus for touch devices. 2068 function fixMenuTouchTaps( container ) { 2069 var touchStartFn, 2070 parentLink = container.querySelectorAll( '.menu-item-has-children > a, .page_item_has_children > a' ); 2071 2072 if ( 'ontouchstart' in window ) { 2073 touchStartFn = function( e ) { 2074 var menuItem = this.parentNode; 2075 2076 if ( ! menuItem.classList.contains( 'focus' ) ) { 2077 e.preventDefault(); 2078 for( var i = 0; i < menuItem.parentNode.children.length; ++i ) { 2079 if ( menuItem === menuItem.parentNode.children[i] ) { 2080 continue; 2081 } 2082 menuItem.parentNode.children[i].classList.remove( 'focus' ); 2083 } 2084 menuItem.classList.add( 'focus' ); 2085 } else { 2086 menuItem.classList.remove( 'focus' ); 2087 } 2088 }; 2089 2090 for ( var i = 0; i < parentLink.length; ++i ) { 2091 parentLink[i].addEventListener( 'touchstart', touchStartFn, false ) 2092 } 2093 } 2094 } 2095 2096 fixMenuTouchTaps( container ); 2097} )(); 2098; 2099( function() { 2100 var is_webkit = navigator.userAgent.toLowerCase().indexOf( 'webkit' ) > -1, 2101 is_opera = navigator.userAgent.toLowerCase().indexOf( 'opera' ) > -1, 2102 is_ie = navigator.userAgent.toLowerCase().indexOf( 'msie' ) > -1; 2103 2104 if ( ( is_webkit || is_opera || is_ie ) && document.getElementById && window.addEventListener ) { 2105 window.addEventListener( 'hashchange', function() { 2106 var id = location.hash.substring( 1 ), 2107 element; 2108 2109 if ( ! ( /^[A-z0-9_-]+$/.test( id ) ) ) { 2110 return; 2111 } 2112 2113 element = document.getElementById( id ); 2114 2115 if ( element ) { 2116 if ( ! ( /^(?:a|select|input|button|textarea)$/i.test( element.tagName ) ) ) { 2117 element.tabIndex = -1; 2118 } 2119 2120 element.focus(); 2121 } 2122 }, false ); 2123 } 2124})(); 2125; 2126/* global FB, jpfbembed */ 2127( function ( window ) { 2128 var facebookEmbed = function () { 2129 var fbroot, src, newScript, firstScript; 2130 2131 if ( 'undefined' !== typeof FB && FB.XFBML ) {
2132 FB.XFBML.parse(); 2133 } else { 2134 fbroot = document.createElement( 'div' ); 2135 fbroot.id = 'fb-root'; 2136 document.getElementsByTagName( 'body' )[ 0 ].appendChild( fbroot ); 2137 2138 src = 'https://connect.facebook.net/' + jpfbembed.locale + '/sdk.js#xfbml=1'; 2139 if ( jpfbembed.appid ) { 2140 src += '&appId=' + jpfbembed.appid; 2141 } 2142 src += '&version=v2.3'; 2143 2144 newScript = document.createElement( 'script' ); 2145 newScript.setAttribute( 'src', src ); 2146 firstScript = document.querySelector( 'script' ); 2147 firstScript.parentNode.insertBefore( newScript, firstScript ); 2148 } 2149 }; 2150 2151 window.fbAsyncInit = function () { 2152 FB.init( { 2153 appId: jpfbembed.appid, 2154 version: 'v2.3', 2155 } ); 2156 2157 FB.XFBML.parse(); 2158 }; 2159 2160 if ( 'undefined' !== typeof infiniteScroll ) { 2161 document.body.addEventListener( 'post-load', facebookEmbed ); 2162 } 2163 2164 facebookEmbed(); 2165} )( window ); 2166; 2167! ( function ( d, s, id ) { 2168 var js, 2169 fjs = d.getElementsByTagName( s )[ 0 ], 2170 p = /^http:/.test( d.location ) ? 'http' : 'https'; 2171 if ( ! d.getElementById( id ) ) { 2172 js = d.createElement( s ); 2173 js.id = id; 2174 js.src = p + '://platform.twitter.com/widgets.js'; 2175 fjs.parentNode.insertBefore( js, fjs ); 2176 } 2177} )( document, 'script', 'twitter-wjs' ); 2178; 2179!function(){var e=document.currentScript;function t(t){var n=document.createElement("script"),o=e||document.getElementsByTagName("script")[0];n.setAttribute("async",!0),n.setAttribute("src",t),o.parentNode.insertBefore(n,o)}function n(e,t){return Element.prototype.matches?e.matches(t):Element.prototype.msMatchesSelector?e.msMatchesSelector(t):void 0}function o(e,t){if(e.closest)return e.closest(t);var o=e;do{if(n(o,t))return o;o=o.parentElement||o.parentNode}while(null!==o&&1===o.nodeType);return null}function i(e,t){for(var n=0;n<e.length;n++)t(e[n],n,e)}var r=".sharing-hidden .inner",s="data-sharing-more-button-id";function a(e){this.button=e,this.pane=o(e,"div").querySelector(r),this.openedBy=null,this.recentlyOpenedByHover=!1,a.instances.push(this),this.pane.setAttribute(s,a.instances.length-1),this.attachHandlers()}if(a.instances=[],a.hoverOpenDelay=200,a.recentOpenDelay=400,a.hoverCloseDelay=300,a.instantiateOrReuse=function(e){var t=o(e,"div").querySelector(r),n=t&&t.getAttribute(s),i=a.instances[n];return i||new a(e)},a.getButtonInstanceFromPane=function(e){var t=e&&e.getAttribute(s);return a.instances[t]},a.closeAll=function(){for(var e=0;e<a.instances.length;e++)a.instances[e].close()},a.prototype.open=function(){var e,t,n=[0,0];function o(e){var t=e.getBoundingClientRect();return[t.left+(window.scrollX||window.pageXOffset||0),t.top+(window.scrollY||window.pageYOffset||0)]}function i(e,t){return parseInt(getComputedStyle(e).getPropertyValue(t)||0)}for(e=o(this.button),t=this.button.offsetParent||document.documentElement;t&&(t===document.body||t===document.documentElement)&&"static"===getComputedStyle(t).getPropertyValue("position");)t=t.parentNode;t&&t!==this.button&&1===t.nodeType&&(n=[(n=o(t))[0]+i(t,"border-left-width"),n[1]+i(t,"border-top-width")]);var r,s=e[0]-n[0]-i(this.button,"margin-left"),a=e[1]-n[1]-i(this.button,"margin-top");this.pane.style.left=s+"px",this.pane.style.top=a+this.button.offsetHeight+3+"px",(r=this.pane)&&r.style.removeProperty("display")},a.prototype.close=function(){var e;(e=this.pane)&&(e.style.display="none"),this.openedBy=null},a.prototype.toggle=function(){var e;(e=this.pane)&&"none"!==e.style.display?this.close():this.open()},a.prototype.nonHoverOpen=function(){clearTimeout(this.openTimer),clearTimeout(this.closeTimer),this.recentlyOpenedByHover?(this.recentlyOpenedByHover=!1,clearTimeout(this.hoverOpenTimer),this.open()):this.toggle()},a.prototype.resetCloseTimer=function(){clearTimeout(this.closeTimer),this.closeTimer=setTimeout(this.close.bind(this),a.hoverCloseDelay)},a.prototype.attachHandlers=function(){this.buttonClick=function(e){e.preventDefault(),e.stopPropagation(),this.openedBy="click",this.nonHoverOpen()}.bind(this),this.buttonKeydown=function(e){13!==e.keyCode&&32!==e.keyCode||(e.preventDefault(),e.stopPropagation(),this.openedBy="keydown",this.nonHoverOpen())}.bind(this),this.buttonEnter=function(){this.openedBy||(this.openTimer=setTimeout(function(){this.open(),this.openedBy="hover",this.recentlyOpenedByHover=!0,this.hoverOpenTimer=setTimeout(function(){this.recentlyOpenedByHover=!1}.bind(this),a.recentOpenDelay)}.bind(this),a.hoverOpenDelay)),clearTimeout(this.closeTimer)}.bind(this),this.buttonLeave=function(){"hover"===this.openedBy&&this.resetCloseTimer(),clearTimeout(this.openTimer)}.bind(this),this.paneEnter=function(){clearTimeout(this.closeTimer)}.bind(this),this.paneLeave=function(){"hover"===this.openedBy&&this.resetCloseTimer()}.bind(this),this.documentClick=function(){this.close()}.bind(this),this.button.addEventListener("click",this.buttonClick),this.button.addEventListener("keydown",this.buttonKeydown),document.addEventListener("click",this.documentClick),void 0===document.ontouchstart&&(this.button.addEventListener("mouseenter",this.buttonEnter),this.button.addEventListener("mouseleave",this.buttonLeave),this.pane.addEventListener("mouseenter",this.paneEnter),this.pane.addEventListener("mouseleave",this.paneLeave))},window.sharing_js_options&&window.sharing_js_options.counts){var c={done_urls:[],get_counts:function(){var e,n,o,i,r;if("undefined"!=typeof WPCOM_sharing_counts)for(e in WPCOM_sharing_counts)if(o=WPCOM_sharing_counts[e],void 0===c.done_urls[o]){for(i in n={pinterest:[window.location.protocol+"https://api.pinterest.com/v1/urls/count.json?callback=WPCOMSharing.update_pinterest_count&url="+encodeURIComponent(e)]})if(document.querySelector("a[data-shared=sharing-"+i+"-"+o+"]")){for(;r=n[i].pop();)t(r);window.sharing_js_options.is_stats_active&&c.bump_sharing_count_stat(i)}c.done_urls[o]=!0}},update_pinterest_count:function(e){void 0!==e.count&&1*e.count>0&&c.inject_share_count("sharing-pinterest-"+WPCOM_sharing_counts[e.url],e.count)},inject_share_count:function(e,t){i(document.querySelectorAll("a[data-shared="+e+"] > span"),(function(e){var n,o=e.querySelector(".share-count");(n=o)&&n.parentNode&&n.parentNode.removeChild(n);var i=document.createElement("span");
2179i.className="share-count",i.textContent=c.format_count(t),e.appendChild(i)}))},format_count:function(e){return e<1e3?e:e>=1e3&&e<1e4?String(e).substring(0,1)+"K+":"10K+"},bump_sharing_count_stat:function(e){(new Image).src=document.location.protocol+"https://pixel.wp.com/g.gif?v=wpcom-no-pv&x_sharing-count-request="+e+"&r="+Math.random()}};window.WPCOMSharing=c}function u(e,t){e.setAttribute("jetpack-share-click-count",t)}function d(e){var t=e.getAttribute("jetpack-share-click-count");return null===t?0:parseInt(t,10)}function l(e,t){var n,o=new XMLHttpRequest;o.open("POST",e,!0),o.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"),o.setRequestHeader("x-requested-with","XMLHttpRequest"),o.send((n=t,(encodeURIComponent("email-share-nonce")+"="+encodeURIComponent(n)).replace(/%20/g,"+")))}function h(){p()}function p(){window.WPCOMSharing&&window.WPCOMSharing.get_counts(),i(document.querySelectorAll(".sharedaddy a"),(function(e){var t=e.getAttribute("href");t&&-1!==t.indexOf("share=")&&-1===t.indexOf("&nb=1")&&e.setAttribute("href",t+"&nb=1")})),i(document.querySelectorAll(".sharedaddy a.sharing-anchor"),(function(e){a.instantiateOrReuse(e)})),void 0!==document.ontouchstart&&document.body.classList.add("jp-sharing-input-touch"),i(document.querySelectorAll(".sharedaddy ul"),(function(e){"true"!==e.getAttribute("data-sharing-events-added")&&(e.setAttribute("data-sharing-events-added","true"),i(e.querySelectorAll("a.share-print"),(function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopPropagation();var n=e.getAttribute("href")||"",i=function(){if(-1===n.indexOf("#print")){var e=(new Date).getTime();t=e,o=n,(i=document.createElement("iframe")).setAttribute("style","position:fixed; top:100; left:100; height:1px; width:1px; border:none;"),i.setAttribute("id","printFrame-"+t),i.setAttribute("name",i.getAttribute("id")),i.setAttribute("src",o),i.setAttribute("onload",'frames["printFrame-'+t+'"].focus();frames["printFrame-'+t+'"].print();'),document.body.appendChild(i)}else window.print();var t,o,i},s=o(e,r);if(s){var c=a.getButtonInstanceFromPane(s);c&&(c.close(),i())}else i()}))})),i(e.querySelectorAll("a.share-press-this"),(function(e){e.addEventListener("click",(function(t){t.preventDefault(),t.stopPropagation();var n="";if(window.getSelection?n=window.getSelection():document.getSelection?n=document.getSelection():document.selection&&(n=document.selection.createRange().text),n){var o=e.getAttribute("href");e.setAttribute("href",o+"&sel="+encodeURI(n))}window.open(e.getAttribute("href"),"t","toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=570")||(document.location.href=e.getAttribute("href"))}))})),i(e.querySelectorAll("a.share-email"),(function(t){u(t,0);var n,o,r=t.getAttribute("data-email-share-nonce"),s=t.getAttribute("data-email-share-track-url");r&&s&&(n=s,o=window.location.protocol+"//"+window.location.hostname+"/",0===String(n).indexOf(o))&&t.addEventListener("click",(function(){var n;u(n=t,d(n)+1),d(t)>2&&function(e,t){var n=t.parentElement;if(n.classList.contains("sd-content")){i(n.querySelectorAll(".share-email-error"),(function(e){e.parentElement.removeChild(e)}));var o=document.createElement("div");o.className="share-email-error";var r=document.createElement("h6");r.className="share-email-error-title",r.innerText=e.getAttribute("data-email-share-error-title"),o.appendChild(r);var s=document.createElement("p");s.className="share-email-error-text",s.innerText=e.getAttribute("data-email-share-error-text"),o.appendChild(s),n.appendChild(o)}}(t,e),l(s,r)}))})))})),i(document.querySelectorAll("li.share-email, li.share-custom a.sharing-anchor"),(function(e){e.classList.add("share-service-visible")}))}"loading"!==document.readyState?h():document.addEventListener("DOMContentLoaded",h),document.body.addEventListener("is.post-load",p)}();; 2180 2181}
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.