1/* global WPCOM_sharing_counts */ 2 3// NOTE: This file intentionally does not make use of polyfills or libraries, 4// including jQuery. Please keep all code as IE11-compatible vanilla ES5, and 5// ensure everything is inside an IIFE to avoid global namespace pollution. 6// Code follows WordPress browser support guidelines. For an up to date list, 7// see https://make.wordpress.org/core/handbook/best-practices/browser-support/ 8 9( function () { 10 var currentScript = document.currentScript; 11 12 // -------------------------- UTILITY FUNCTIONS -------------------------- // 13 14 // Helper function to load an external script. 15 function loadScript( url ) { 16 var script = document.createElement( 'script' ); 17 var prev = currentScript || document.getElementsByTagName( 'script' )[ 0 ]; 18 script.setAttribute( 'async', true ); 19 script.setAttribute( 'src', url ); 20 prev.parentNode.insertBefore( script, prev ); 21 } 22 23 // Helper matches function (not a polyfill), compatible with IE 11. 24 function matches( el, sel ) { 25 if ( Element.prototype.matches ) { 26 return el.matches( sel ); 27 } 28 29 if ( Element.prototype.msMatchesSelector ) { 30 return el.msMatchesSelector( sel ); 31 } 32 } 33 34 // Helper closest parent node function (not a polyfill) based on 35 // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill 36 function closest( el, sel ) { 37 if ( el.closest ) { 38 return el.closest( sel ); 39 } 40 41 var current = el; 42 43 do { 44 if ( matches( current, sel ) ) { 45 return current; 46 } 47 current = current.parentElement || current.parentNode; 48 } while ( current !== null && current.nodeType === 1 ); 49 50 return null; 51 } 52 53 // Helper function to iterate over a NodeList 54 // (since IE 11 doesn't have NodeList.prototype.forEach) 55 function forEachNode( list, fn ) { 56 for ( var i = 0; i < list.length; i++ ) { 57 var node = list[ i ]; 58 fn( node, i, list ); 59 } 60 } 61 62 // Helper function to remove a node from the DOM. 63 function removeNode( node ) { 64 if ( node && node.parentNode ) { 65 node.parentNode.removeChild( node ); 66 } 67 } 68 69 // Helper functions to show/hide a node, and check its status. 70 function hideNode( node ) { 71 if ( node ) { 72 node.style.display = 'none'; 73 } 74 } 75 76 function showNode( node ) { 77 if ( node ) { 78 node.style.removeProperty( 'display' ); 79 } 80 } 81 82 function isNodeHidden( node ) { 83 return ! node || node.style.display === 'none'; 84 } 85 86 // ------------------------------- CLASSES ------------------------------- // 87 88 var PANE_SELECTOR = '.sharing-hidden .inner'; 89 var PANE_DATA_ATTR = 'data-sharing-more-button-id'; 90 91 // Implements a MoreButton class, which controls the lifecycle and behavior 92 // of a "more" button and its dialog. 93 function MoreButton( buttonEl ) { 94 this.button = buttonEl; 95 this.pane = closest( buttonEl, 'div' ).querySelector( PANE_SELECTOR ); 96 this.openedBy = null; 97 this.recentlyOpenedByHover = false; 98 99 MoreButton.instances.push( this ); 100 this.pane.setAttribute( PANE_DATA_ATTR, MoreButton.instances.length - 1 ); 101 102 this.attachHandlers(); 103 } 104 105 // Keep a reference to each instance, so we can get back to it from the DOM. 106 MoreButton.instances = []; 107 108 // Delay time configs. 109 MoreButton.hoverOpenDelay = 200; 110 MoreButton.recentOpenDelay = 400; 111 MoreButton.hoverCloseDelay = 300; 112 113 // Use this to avoid creating new instances for buttons which already have one. 114 MoreButton.instantiateOrReuse = function ( buttonEl ) { 115 var pane = closest( buttonEl, 'div' ).querySelector( PANE_SELECTOR ); 116 var paneId = pane && pane.getAttribute( PANE_DATA_ATTR ); 117 118 var existingInstance = MoreButton.instances[ paneId ]; 119 if ( existingInstance ) { 120 return existingInstance; 121 } 122 123 return new MoreButton( buttonEl ); 124 }; 125 126 // Retrieve a button instance from the pane DOM element. 127 MoreButton.getButtonInstanceFromPane = function ( paneEl ) { 128 var paneId = paneEl && paneEl.getAttribute( PANE_DATA_ATTR ); 129 return MoreButton.instances[ paneId ]; 130 }; 131 132 // Close all open More Button dialogs. 133 MoreButton.closeAll = function () { 134 for ( var i = 0; i < MoreButton.instances.length; i++ ) { 135 MoreButton.instances[ i ].close(); 136 } 137 }; 138 139 MoreButton.prototype.open = function () { 140 var offset; 141 var offsetParent; 142 var parentOffset = [ 0, 0 ]; 143 144 function getOffsets( el ) { 145 var rect = el.getBoundingClientRect(); 146 return [ 147 rect.left + ( window.scrollX || window.pageXOffset || 0 ), 148 rect.top + ( window.scrollY || window.pageYOffset || 0 ), 149 ]; 150 } 151 152 function getStyleValue( el, prop ) { 153 return parseInt( getComputedStyle( el ).getPropertyValue( prop ) || 0 ); 154 } 155 156 offset = getOffsets( this.button ); 157 offsetParent = this.button.offsetParent || document.documentElement; 158 159 while ( 160 offsetParent && 161 ( offsetParent === document.body || offsetParent === document.documentElement ) && 162 getComputedStyle( offsetParent ).getPropertyValue( 'position' ) === 'static' 163 ) { 164 offsetParent = offsetParent.parentNode; 165 } 166 167 if ( offsetParent && offsetParent !== this.button && offsetParent.
167nodeType === 1 ) { 168 parentOffset = getOffsets( offsetParent ); 169 parentOffset = [ 170 parentOffset[ 0 ] + getStyleValue( offsetParent, 'border-left-width' ), 171 parentOffset[ 1 ] + getStyleValue( offsetParent, 'border-top-width' ), 172 ]; 173 } 174 175 var positionLeft = 176 offset[ 0 ] - parentOffset[ 0 ] - getStyleValue( this.button, 'margin-left' ); 177 var positionTop = offset[ 1 ] - parentOffset[ 1 ] - getStyleValue( this.button, 'margin-top' ); 178 179 this.pane.style.left = positionLeft + 'px'; 180 this.pane.style.top = positionTop + this.button.offsetHeight + 3 + 'px'; 181 182 showNode( this.pane ); 183 }; 184 185 MoreButton.prototype.close = function () { 186 hideNode( this.pane ); 187 this.openedBy = null; 188 }; 189 190 MoreButton.prototype.toggle = function () { 191 if ( isNodeHidden( this.pane ) ) { 192 this.open(); 193 } else { 194 this.close(); 195 } 196 }; 197 198 MoreButton.prototype.nonHoverOpen = function () { 199 clearTimeout( this.openTimer ); 200 clearTimeout( this.closeTimer ); 201 202 if ( this.recentlyOpenedByHover ) { 203 this.recentlyOpenedByHover = false; 204 clearTimeout( this.hoverOpenTimer ); 205 this.open(); 206 } else { 207 this.toggle(); 208 } 209 }; 210 211 MoreButton.prototype.resetCloseTimer = function () { 212 clearTimeout( this.closeTimer ); 213 this.closeTimer = setTimeout( this.close.bind( this ), MoreButton.hoverCloseDelay ); 214 }; 215 216 MoreButton.prototype.attachHandlers = function () { 217 this.buttonClick = function ( event ) { 218 event.preventDefault(); 219 event.stopPropagation(); 220 221 this.openedBy = 'click'; 222 this.nonHoverOpen(); 223 }.bind( this ); 224 225 this.buttonKeydown = function ( event ) { 226 if ( event.keyCode === 13 || event.keyCode === 32 ) { 227 event.preventDefault(); 228 event.stopPropagation(); 229 230 this.openedBy = 'keydown'; 231 this.nonHoverOpen(); 232 } 233 }.bind( this ); 234 235 this.buttonEnter = function () { 236 if ( ! this.openedBy ) { 237 this.openTimer = setTimeout( 238 function () { 239 this.open(); 240 this.openedBy = 'hover'; 241 this.recentlyOpenedByHover = true; 242 this.hoverOpenTimer = setTimeout( 243 function () { 244 this.recentlyOpenedByHover = false; 245 }.bind( this ), 246 MoreButton.recentOpenDelay 247 ); 248 }.bind( this ), 249 MoreButton.hoverOpenDelay 250 ); 251 } 252 clearTimeout( this.closeTimer ); 253 }.bind( this ); 254 255 this.buttonLeave = function () { 256 if ( this.openedBy === 'hover' ) { 257 this.resetCloseTimer(); 258 } 259 clearTimeout( this.openTimer ); 260 }.bind( this ); 261 262 this.paneEnter = function () { 263 clearTimeout( this.closeTimer ); 264 }.bind( this ); 265 266 this.paneLeave = function () { 267 if ( this.openedBy === 'hover' ) { 268 this.resetCloseTimer(); 269 } 270 }.bind( this ); 271 272 this.documentClick = function () { 273 this.close(); 274 }.bind( this ); 275 276 this.button.addEventListener( 'click', this.buttonClick ); 277 this.button.addEventListener( 'keydown', this.buttonKeydown ); 278 document.addEventListener( 'click', this.documentClick ); 279 280 if ( document.ontouchstart === undefined ) { 281 // Non-touchscreen device: use hover/mouseout with delay 282 this.button.addEventListener( 'mouseenter', this.buttonEnter ); 283 this.button.addEventListener( 'mouseleave', this.buttonLeave ); 284 this.pane.addEventListener( 'mouseenter', this.paneEnter ); 285 this.pane.addEventListener( 'mouseleave', this.paneLeave ); 286 } 287 }; 288 289 // ---------------------------- SHARE COUNTS ---------------------------- // 290 291 if ( window.sharing_js_options && window.sharing_js_options.counts ) { 292 var WPCOMSharing = { 293 done_urls: [], 294 get_counts: function () { 295 var url, requests, id, service, service_request; 296 297 if ( 'undefined' === typeof WPCOM_sharing_counts ) { 298 return; 299 } 300 301 for ( url in WPCOM_sharing_counts ) { 302 id = WPCOM_sharing_counts[ url ]; 303 304 if ( 'undefined' !== typeof WPCOMSharing.done_urls[ id ] ) { 305 continue; 306 } 307 308 requests = { 309 // Pinterest handles share counts for both http and https 310 pinterest: [ 311 window.location.protocol + 312 '//api.pinterest.com/v1/urls/count.json?callback=WPCOMSharing.update_pinterest_count&url=' + 313 encodeURIComponent( url ), 314 ], 315 }; 316 317 for ( service in requests ) { 318 if ( ! document.querySelector( 'a[data-shared=sharing-' + service + '-' + id + ']' ) ) { 319 continue; 320 } 321 322 while ( ( service_request = requests[ service ].pop() ) ) { 323 loadScript( service_request ); 324 } 325 326 if ( window.sharing_js_options.is_stats_active ) { 327 WPCOMSharing.bump_sharing_count_stat( service ); 328 } 329 } 330 331 WPCOMSharing.done_urls[ id ] = true; 332 } 333 }, 334 update_pinterest_count: function ( data ) { 335 if ( 'undefined' !== typeof data.count && data.count * 1 > 0 ) { 336 WPCOMSharing.inject_share_count( 337 'sharing-pinterest-' + WPCOM_sharing_counts[ data.url ], 338 data.count 339 ); 340 } 341 }, 342 inject_share_count: function ( id, count ) {
343 forEachNode( 344 document.querySelectorAll( 'a[data-shared=' + id + '] > span' ), 345 function ( span ) { 346 var countNode = span.querySelector( '.share-count' ); 347 removeNode( countNode ); 348 var newNode = document.createElement( 'span' ); 349 newNode.className = 'share-count'; 350 newNode.textContent = WPCOMSharing.format_count( count ); 351 span.appendChild( newNode ); 352 } 353 ); 354 }, 355 format_count: function ( count ) { 356 if ( count < 1000 ) { 357 return count; 358 } 359 if ( count >= 1000 && count < 10000 ) { 360 return String( count ).substring( 0, 1 ) + 'K+'; 361 } 362 return '10K+'; 363 }, 364 bump_sharing_count_stat: function ( service ) { 365 new Image().src = 366 document.location.protocol + 367 '//pixel.wp.com/g.gif?v=wpcom-no-pv&x_sharing-count-request=' + 368 service + 369 '&r=' + 370 Math.random(); 371 }, 372 }; 373 window.WPCOMSharing = WPCOMSharing; 374 } 375 376 // ------------------------ BUTTON FUNCTIONALITY ------------------------ // 377 function isUrlForCurrentHost( url ) { 378 var currentDomain = window.location.protocol + '//' + window.location.hostname + '/'; 379 380 return String( url ).indexOf( currentDomain ) === 0; 381 } 382 383 function getEncodedFormFieldForSubmit( name, value ) { 384 // Encode the key and value into a URI-compatible string. 385 var encoded = encodeURIComponent( name ) + '=' + encodeURIComponent( value ); 386 387 // In x-www-form-urlencoded, spaces should be `+`, not `%20`. 388 return encoded.replace( /%20/g, '+' ); 389 } 390 391 function trackButtonClick( button ) { 392 var clickCount = getClickCountForButton( button ); 393 394 setClickCountForButton( button, clickCount + 1 ); 395 } 396 397 function setClickCountForButton( button, clickCount ) { 398 button.setAttribute( 'jetpack-share-click-count', clickCount ); 399 } 400 401 function getClickCountForButton( button ) { 402 var currentClickCount = button.getAttribute( 'jetpack-share-click-count' ); 403 if ( currentClickCount === null ) { 404 return 0; 405 } 406 407 return parseInt( currentClickCount, 10 ); 408 } 409 410 function showEmailShareError( emailShareButton, sdUlGroup ) { 411 var sdContent = sdUlGroup.parentElement; 412 if ( ! sdContent.classList.contains( 'sd-content' ) ) { 413 return; 414 } 415 416 forEachNode( sdContent.querySelectorAll( '.share-email-error' ), function ( shareEmailError ) { 417 shareEmailError.parentElement.removeChild( shareEmailError ); 418 } ); 419 420 var newShareEmailError = document.createElement( 'div' ); 421 newShareEmailError.className = 'share-email-error'; 422 423 var newShareEmailErrorTitle = document.createElement( 'h6' ); 424 newShareEmailErrorTitle.className = 'share-email-error-title'; 425 newShareEmailErrorTitle.innerText = emailShareButton.getAttribute( 426 'data-email-share-error-title' 427 ); 428 newShareEmailError.appendChild( newShareEmailErrorTitle ); 429 430 var newShareEmailErrorText = document.createElement( 'p' ); 431 newShareEmailErrorText.className = 'share-email-error-text'; 432 newShareEmailErrorText.innerText = emailShareButton.getAttribute( 433 'data-email-share-error-text' 434 ); 435 newShareEmailError.appendChild( newShareEmailErrorText ); 436 437 sdContent.appendChild( newShareEmailError ); 438 } 439 440 function recordEmailShareClick( emailShareTrackerUrl, emailShareNonce ) { 441 var request = new XMLHttpRequest(); 442 request.open( 'POST', emailShareTrackerUrl, true ); 443 request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' ); 444 request.setRequestHeader( 'x-requested-with', 'XMLHttpRequest' ); 445 446 request.send( getEncodedFormFieldForSubmit( 'email-share-nonce', emailShareNonce ) ); 447 } 448 449 // Sharing initialization. 450 // Will run immediately or on `DOMContentLoaded`, depending on current page status. 451 function init() { 452 WPCOMSharing_do(); 453 } 454 if ( document.readyState !== 'loading' ) { 455 init(); 456 } else { 457 document.addEventListener( 'DOMContentLoaded', init ); 458 } 459 460 // Set up sharing again whenever a new post loads, to pick up any new buttons. 461 document.body.addEventListener( 'is.post-load', WPCOMSharing_do ); 462 463 // Set up sharing, updating counts and adding all button functionality. 464 function WPCOMSharing_do() { 465 if ( window.WPCOMSharing ) { 466 window.WPCOMSharing.get_counts(); 467 } 468 469 forEachNode( document.querySelectorAll( '.sharedaddy a' ), function ( anchor ) { 470 var href = anchor.getAttribute( 'href' ); 471 if ( href && href.indexOf( 'share=' ) !== -1 && href.indexOf( '&nb=1' ) === -1 ) { 472 anchor.setAttribute( 'href', href + '&nb=1' ); 473 } 474 } ); 475 476 // Show hidden buttons 477 478 // Touchscreen device: use click. 479 // Non-touchscreen device: use click if not already appearing due to a hover event 480 481 forEachNode( 482 document.querySelectorAll( '.sharedaddy a.sharing-anchor' ), 483 function ( buttonEl ) { 484 MoreButton.instantiateOrReuse( buttonEl ); 485 } 486 ); 487 488 if ( document.ontouchstart !== undefined ) { 489 document.body.classList.add( 'jp-sharing-input-touch' ); 490 } 491 492 // Add click functionality 493 forEachNode( document.querySelectorAll( '.sharedaddy ul' ), function ( group ) { 494 if ( group.getAttribute( 'data-sharing-events-added' ) === 'true' ) { 495 return; 496 } 497 group.setAttribute( 'data-sharing-events-added', 'true' ); 498 499 var printUrl = function ( uniqueId, urlToPrint ) { 500 var iframe = document.createElement( 'iframe' ); 501 iframe.setAttribute( 502 'style', 503 'position:fixed; top:100; left:100; height:1px; width:1px; border:none;' 504 ); 505 iframe.setAttribute( 'id', 'printFrame-' + uniqueId );
506 iframe.setAttribute( 'name', iframe.getAttribute( 'id' ) ); 507 iframe.setAttribute( 'src', urlToPrint ); 508 iframe.setAttribute( 509 'onload', 510 'frames["printFrame-' + 511 uniqueId + 512 '"].focus();frames["printFrame-' + 513 uniqueId + 514 '"].print();' 515 ); 516 document.body.appendChild( iframe ); 517 }; 518 519 // Print button 520 forEachNode( group.querySelectorAll( 'a.share-print' ), function ( printButton ) { 521 printButton.addEventListener( 'click', function ( event ) { 522 event.preventDefault(); 523 event.stopPropagation(); 524 525 var ref = printButton.getAttribute( 'href' ) || ''; 526 var doPrint = function () { 527 if ( ref.indexOf( '#print' ) === -1 ) { 528 var uid = new Date().getTime(); 529 printUrl( uid, ref ); 530 } else { 531 window.print(); 532 } 533 }; 534 535 // Is the button in a dropdown? 536 var pane = closest( printButton, PANE_SELECTOR ); 537 if ( pane ) { 538 var moreButton = MoreButton.getButtonInstanceFromPane( pane ); 539 if ( moreButton ) { 540 moreButton.close(); 541 doPrint(); 542 } 543 } else { 544 doPrint(); 545 } 546 } ); 547 } ); 548 549 // Press This button 550 forEachNode( group.querySelectorAll( 'a.share-press-this' ), function ( pressThisButton ) { 551 pressThisButton.addEventListener( 'click', function ( event ) { 552 event.preventDefault(); 553 event.stopPropagation(); 554 555 var s = ''; 556 557 if ( window.getSelection ) { 558 s = window.getSelection(); 559 } else if ( document.getSelection ) { 560 s = document.getSelection(); 561 } else if ( document.selection ) { 562 s = document.selection.createRange().text; 563 } 564 565 if ( s ) { 566 var href = pressThisButton.getAttribute( 'href' ); 567 pressThisButton.setAttribute( 'href', href + '&sel=' + encodeURI( s ) ); 568 } 569 570 if ( 571 ! window.open( 572 pressThisButton.getAttribute( 'href' ), 573 't', 574 'toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=570' 575 ) 576 ) { 577 document.location.href = pressThisButton.getAttribute( 'href' ); 578 } 579 } ); 580 } ); 581 582 // Email button 583 forEachNode( group.querySelectorAll( 'a.share-email' ), function ( emailButton ) { 584 setClickCountForButton( emailButton, 0 ); 585 586 var emailShareNonce = emailButton.getAttribute( 'data-email-share-nonce' ); 587 var emailShareTrackerUrl = emailButton.getAttribute( 'data-email-share-track-url' ); 588 589 if ( 590 emailShareNonce && 591 emailShareTrackerUrl && 592 isUrlForCurrentHost( emailShareTrackerUrl ) 593 ) { 594 emailButton.addEventListener( 'click', function () { 595 trackButtonClick( emailButton ); 596 597 if ( getClickCountForButton( emailButton ) > 2 ) { 598 showEmailShareError( emailButton, group ); 599 } 600 601 recordEmailShareClick( emailShareTrackerUrl, emailShareNonce ); 602 } ); 603 } 604 } ); 605 } ); 606 607 forEachNode( 608 document.querySelectorAll( 'li.share-email, li.share-custom a.sharing-anchor' ), 609 function ( node ) { 610 node.classList.add( 'share-service-visible' ); 611 } 612 ); 613 } 614} )();
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.