| @@ -1,0 +1,723 @@ | ||
| 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.nodeType === 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 | + // Unpredictable, but stable per page load so repeat clicks reuse the same popup. | |
| 552 | + var pressThisWindowName = 'wp-press-this-' + Math.random().toString( 36 ).slice( 2 ); | |
| 553 | + pressThisButton.addEventListener( 'click', function ( event ) { | |
| 554 | + event.preventDefault(); | |
| 555 | + event.stopPropagation(); | |
| 556 | + | |
| 557 | + var s = ''; | |
| 558 | + | |
| 559 | + if ( window.getSelection ) { | |
| 560 | + s = window.getSelection(); | |
| 561 | + } else if ( document.getSelection ) { | |
| 562 | + s = document.getSelection(); | |
| 563 | + } else if ( document.selection ) { | |
| 564 | + s = document.selection.createRange().text; | |
| 565 | + } | |
| 566 | + | |
| 567 | + if ( s ) { | |
| 568 | + var href = pressThisButton.getAttribute( 'href' ); | |
| 569 | + pressThisButton.setAttribute( 'href', href + '&sel=' + encodeURI( s ) ); | |
| 570 | + } | |
| 571 | + | |
| 572 | + if ( | |
| 573 | + ! window.open( | |
| 574 | + pressThisButton.getAttribute( 'href' ), | |
| 575 | + pressThisWindowName, | |
| 576 | + 'toolbar=0,resizable=1,scrollbars=1,status=1,width=720,height=570' | |
| 577 | + ) | |
| 578 | + ) { | |
| 579 | + document.location.href = pressThisButton.getAttribute( 'href' ); | |
| 580 | + } | |
| 581 | + } ); | |
| 582 | + } ); | |
| 583 | + | |
| 584 | + // Email button | |
| 585 | + forEachNode( group.querySelectorAll( 'a.share-email' ), function ( emailButton ) { | |
| 586 | + setClickCountForButton( emailButton, 0 ); | |
| 587 | + | |
| 588 | + var emailShareNonce = emailButton.getAttribute( 'data-email-share-nonce' ); | |
| 589 | + var emailShareTrackerUrl = emailButton.getAttribute( 'data-email-share-track-url' ); | |
| 590 | + | |
| 591 | + if ( | |
| 592 | + emailShareNonce && | |
| 593 | + emailShareTrackerUrl && | |
| 594 | + isUrlForCurrentHost( emailShareTrackerUrl ) | |
| 595 | + ) { | |
| 596 | + emailButton.addEventListener( 'click', function () { | |
| 597 | + trackButtonClick( emailButton ); | |
| 598 | + | |
| 599 | + if ( getClickCountForButton( emailButton ) > 2 ) { | |
| 600 | + showEmailShareError( emailButton, group ); | |
| 601 | + } | |
| 602 | + | |
| 603 | + recordEmailShareClick( emailShareTrackerUrl, emailShareNonce ); | |
| 604 | + } ); | |
| 605 | + } | |
| 606 | + } ); | |
| 607 | + } ); | |
| 608 | + | |
| 609 | + forEachNode( | |
| 610 | + document.querySelectorAll( 'li.share-email, li.share-custom a.sharing-anchor' ), | |
| 611 | + function ( node ) { | |
| 612 | + node.classList.add( 'share-service-visible' ); | |
| 613 | + } | |
| 614 | + ); | |
| 615 | + } | |
| 616 | + | |
| 617 | + // ---------------------------- SHARE POPUPS ---------------------------- // | |
| 618 | + | |
| 619 | + // Services that open their share link in a popup publish the window features | |
| 620 | + // they need in `window.WPCOM_sharing_popups`, keyed by the service name that | |
| 621 | + // also appears in the link's `share-<service>` class. Sharing_Source::js_dialog() | |
| 622 | + // prints that map in an inline script after this file, and the one delegated | |
| 623 | + // listener below serves every service, with only one popup open at a time. | |
| 624 | + // | |
| 625 | + // The Sharing Buttons block runs a parallel popup handler in | |
| 626 | + // extensions/blocks/sharing-buttons/view.js; a change here usually belongs there too. | |
| 627 | + var sharePopup; | |
| 628 | + var sharePopupNames = {}; | |
| 629 | + | |
| 630 | + // Popup names are unpredictable, so another page cannot pre-register one and | |
| 631 | + // claim the popup. They are stable per service per page load, so repeat clicks | |
| 632 | + // on a service reuse its window. | |
| 633 | + function getSharePopupName( service ) { | |
| 634 | + if ( typeof sharePopupNames[ service ] !== 'string' ) { | |
| 635 | + sharePopupNames[ service ] = | |
| 636 | + 'wpcom' + service + '-' + Math.random().toString( 36 ).slice( 2 ); | |
| 637 | + } | |
| 638 | + | |
| 639 | + return sharePopupNames[ service ]; | |
| 640 | + } | |
| 641 | + | |
| 642 | + // Find the share link a click landed on. Sharing buttons nest at most one | |
| 643 | + // element inside the anchor, and this stays as shallow as the per-service | |
| 644 | + // handlers it replaces, so nothing that used to reach its own click handler | |
| 645 | + // starts being claimed by this one instead. | |
| 646 | + function getShareLink( target ) { | |
| 647 | + if ( target.nodeName === 'A' ) { | |
| 648 | + return target; | |
| 649 | + } | |
| 650 | + | |
| 651 | + var parent = target.parentNode; | |
| 652 | + return parent && parent.nodeName === 'A' ? parent : null; | |
| 653 | + } | |
| 654 | + | |
| 655 | + // Find the popup service a share link belongs to, if it belongs to one. | |
| 656 | + function getSharePopupService( link ) { | |
| 657 | + var popups = window.WPCOM_sharing_popups; | |
| 658 | + if ( ! popups ) { | |
| 659 | + return null; | |
| 660 | + } | |
| 661 | + | |
| 662 | + var prefix = 'share-'; | |
| 663 | + var classes = ( link.getAttribute( 'class' ) || '' ).split( /\s+/ ); | |
| 664 | + for ( var i = 0; i < classes.length; i++ ) { | |
| 665 | + if ( classes[ i ].indexOf( prefix ) !== 0 ) { | |
| 666 | + continue; | |
| 667 | + } | |
| 668 | + | |
| 669 | + var service = classes[ i ].slice( prefix.length ); | |
| 670 | + if ( typeof popups[ service ] === 'string' ) { | |
| 671 | + return service; | |
| 672 | + } | |
| 673 | + } | |
| 674 | + | |
| 675 | + return null; | |
| 676 | + } | |
| 677 | + | |
| 678 | + function openSharePopup( event ) { | |
| 679 | + var target = event.target; | |
| 680 | + if ( ! target || target.nodeType !== 1 ) { | |
| 681 | + return; | |
| 682 | + } | |
| 683 | + | |
| 684 | + var link = getShareLink( target ); | |
| 685 | + if ( ! link ) { | |
| 686 | + return; | |
| 687 | + } | |
| 688 | + | |
| 689 | + var service = getSharePopupService( link ); | |
| 690 | + if ( ! service ) { | |
| 691 | + return; | |
| 692 | + } | |
| 693 | + | |
| 694 | + event.preventDefault(); | |
| 695 | + | |
| 696 | + // If there's another sharing window open, close it. | |
| 697 | + if ( sharePopup ) { | |
| 698 | + sharePopup.close(); | |
| 699 | + } | |
| 700 | + | |
| 701 | + sharePopup = window.open( | |
| 702 | + link.getAttribute( 'href' ), | |
| 703 | + getSharePopupName( service ), | |
| 704 | + window.WPCOM_sharing_popups[ service ] | |
| 705 | + ); | |
| 706 | + } | |
| 707 | + | |
| 708 | + // Listen on `document.body`, where the per-service handlers this replaces listened, | |
| 709 | + // so a page script that stops a click from propagating past the body keeps having | |
| 710 | + // the same effect it always had. The bind flag has to be global: if this file is | |
| 711 | + // enqueued twice under different handles, each copy gets its own closure, and two | |
| 712 | + // listeners would open two popups per click. Compare against `true` so a page | |
| 713 | + // element named after the flag cannot clobber it and leave every button dead. | |
| 714 | + // | |
| 715 | + // This has to stay below the `is.post-load` listener above: both dereference | |
| 716 | + // `document.body`, and if this file is ever evaluated before the body exists it | |
| 717 | + // should fail where it already failed, after `init()` has registered, rather than | |
| 718 | + // take the rest of the file down with it. | |
| 719 | + if ( window.WPCOM_sharing_popups_bound !== true ) { | |
| 720 | + document.body.addEventListener( 'click', openSharePopup ); | |
| 721 | + window.WPCOM_sharing_popups_bound = true; | |
| 722 | + } | |
| 723 | +} )(); | |