/* global subscriptionData */ document.addEventListener( 'DOMContentLoaded', function () { const modal = document.getElementsByClassName( 'jetpack-subscription-modal' )[ 0 ]; if ( ! modal ) { return; } const close = document.getElementsByClassName( 'jetpack-subscription-modal__close' )[ 0 ]; let redirectUrl = ''; let hasLoaded = false; function reloadOnCloseSubscriptionModal( customUrl ) { const destinationUrl = customUrl ? new URL( customUrl ) : new URL( redirectUrl ); // Prevent redirect to external sites. if ( destinationUrl.hostname !== window.location.hostname ) { return; } try { localStorage.setItem( 'jetpack-subscription-modal-on-comment-scroll-to', destinationUrl.hash ); } catch { // Ok if we can't set it. } // Add cache-busting parameter destinationUrl.searchParams.set( '_ctn', Date.now() ); window.location.href = destinationUrl.toString(); } function JetpackSubscriptionModalOnCommentMessageListener( event ) { let message = event && event.data; if ( typeof message === 'string' ) { try { message = JSON.parse( message ); } catch { return; } } const type = message && message.type; const data = message && message.data; if ( type !== 'subscriptionModalShow' || typeof data.url === 'undefined' ) { return; } if ( subscriptionData.homeUrl !== event.origin ) { return; } if ( data.email ) { const emailInput = document.querySelector( '.jetpack-subscription-modal__modal-content input[type="email"]' ); if ( ! emailInput ) { reloadOnCloseSubscriptionModal( data.url ); return; } const appSource = document.querySelector( '.jetpack-subscription-modal__modal-content input[name="app_source"]' ); if ( ! appSource ) { reloadOnCloseSubscriptionModal( data.url ); return; } emailInput.value = data.email; if ( data.is_logged_in ) { emailInput.setAttribute( 'readonly', 'readonly' ); appSource.value = 'atomic-subscription-modal-li'; } } if ( ! hasLoaded ) { try { const storedCount = parseInt( sessionStorage.getItem( 'jetpack-subscription-modal-shown-count' ) ); const showCount = ( isNaN( storedCount ) ? 0 : storedCount ) + 1; sessionStorage.setItem( 'jetpack-subscription-modal-shown-count', showCount ); if ( showCount > 5 ) { new Image().src = document.location.protocol + '//pixel.wp.com/g.gif?v=wpcom-no-pv&x_jetpack-subscribe-modal-comm=hidden_views_limit&r=' + Math.random(); reloadOnCloseSubscriptionModal( data.url ); return; } } catch { // Ignore any errors. } new Image().src = document.location.protocol + '//pixel.wp.com/g.gif?v=wpcom-no-pv&x_jetpack-subscribe-modal-comm=showed&r=' + Math.random(); modal.classList.toggle( 'open' ); hasLoaded = true; redirectUrl = data.url; } } window.addEventListener( 'message', JetpackSubscriptionModalOnCommentMessageListener ); if ( close ) { close.onclick = function ( event ) { event.preventDefault(); modal.classList.toggle( 'open' ); reloadOnCloseSubscriptionModal(); }; } window.onclick = function ( event ) { if ( event.target === modal ) { modal.style.display = 'none'; reloadOnCloseSubscriptionModal(); } }; window.addEventListener( 'load', () => { // Scroll to the last comment. const subscriptionScroll = localStorage.getItem( 'jetpack-subscription-modal-on-comment-scroll-to' ); if ( subscriptionScroll ) { window.location.hash = subscriptionScroll; localStorage.removeItem( 'jetpack-subscription-modal-on-comment-scroll-to' ); const comment = document.querySelector( subscriptionScroll ); if ( comment ) { comment.scrollIntoView( { block: 'center', behavior: 'smooth' } ); } } } ); } ); ; /* global Jetpack_Subscriptions */ const { domReady } = wp; domReady( () => { const modal = document.querySelector( '.jetpack-subscribe-modal' ); const modalDismissedCookie = 'jetpack_post_subscribe_modal_dismissed'; const skipUrlParam = 'jetpack_skip_subscription_popup'; function hasEnoughTimePassed() { const lastDismissed = localStorage.getItem( modalDismissedCookie ); return lastDismissed ? Date.now() - lastDismissed > Jetpack_Subscriptions.modalInterval : true; } // Subscriber ended up here e.g. from emails: // we won't show the modal to them in future since they most likely are already a subscriber. function skipModal() { const url = new URL( window.location.href ); if ( url.searchParams.has( skipUrlParam ) ) { url.searchParams.delete( skipUrlParam ); window.history.replaceState( {}, '', url ); storeCloseTimestamp(); return true; } return false; } if ( ! modal || ! hasEnoughTimePassed() || skipModal() ) { return; } const modalLoadTimeout = setTimeout( openModal, Jetpack_Subscriptions.modalLoadTime ); const targetElement = ( document.querySelector( '.entry-content' ) || document.documentElement ).getBoundingClientRect(); function hasPassedScrollThreshold() { const scrollPosition = window.scrollY + window.innerHeight / 2; const scrollPositionThreshold = targetElement.top + ( targetElement.height * Jetpack_Subscriptions.modalScrollThreshold ) / 100; return scrollPosition > scrollPositionThreshold; } function onScroll() { requestAnimationFrame( () => { if ( hasPassedScrollThreshold() ) { openModal(); } } ); } window.addEventListener( 'scroll', onScroll, { passive: true } ); // This take care of the case where the user has multiple tabs open. function onLocalStorage( event ) { if ( event.key === modalDismissedCookie ) { closeModal(); removeEventListeners(); } } window.addEventListener( 'storage', onLocalStorage ); // When the form is submitted, and next modal loads, it'll fire "subscription-modal-loaded" signalling that this form can be hidden. const form = modal.querySelector( 'form' ); if ( form ) { form.addEventListener( 'subscription-modal-loaded', closeModal ); } // User can edit modal, and could remove close link. function onCloseButtonClick( event ) { event.preventDefault(); closeModal(); } const close = document.getElementsByClassName( 'jetpack-subscribe-modal__close' )[ 0 ]; if ( close ) { close.addEventListener( 'click', onCloseButtonClick ); } function closeOnWindowClick( event ) { if ( event.target === modal ) { closeModal(); } } function closeModalOnEscapeKeydown( event ) { if ( event.key === 'Escape' ) { closeModal(); } } function openModal() { // If the user is typing in a form, don't open the modal or has anything else focused. if ( document.activeElement && document.activeElement.tagName !== 'BODY' ) { return; } modal.classList.add( 'open' ); document.body.classList.add( 'jetpack-subscribe-modal-open' ); window.addEventListener( 'keydown', closeModalOnEscapeKeydown ); window.addEventListener( 'click', closeOnWindowClick ); removeEventListeners(); } function closeModal() { modal.classList.remove( 'open' ); document.body.classList.remove( 'jetpack-subscribe-modal-open' ); window.removeEventListener( 'keydown', closeModalOnEscapeKeydown ); window.removeEventListener( 'storage', onLocalStorage ); window.removeEventListener( 'click', closeOnWindowClick ); storeCloseTimestamp(); } // Remove all event listeners. That would add the modal again. function removeEventListeners() { window.removeEventListener( 'scroll', onScroll ); clearTimeout( modalLoadTimeout ); } function storeCloseTimestamp() { localStorage.setItem( modalDismissedCookie, Date.now() ); } } ); ; /* global wpcom_reblog */ var jetpackLikesWidgetBatch = []; var jetpackLikesMasterReady = false; // Due to performance problems on pages with a large number of widget iframes that need to be loaded, // we are limiting the processing at any instant to unloaded widgets that are currently in viewport, // plus this constant that will allow processing of widgets above and bellow the current fold. // This aim of it is to improve the UX and hide the transition from unloaded to loaded state from users. var jetpackLikesLookAhead = 2000; // pixels // Keeps track of loaded comment likes widget so we can unload them when they are scrolled out of view. var jetpackCommentLikesLoadedWidgets = []; var jetpackLikesDocReadyPromise = new Promise( resolve => { if ( document.readyState !== 'loading' ) { resolve(); } else { window.addEventListener( 'DOMContentLoaded', () => resolve() ); } } ); function JetpackLikesPostMessage( message, target ) { if ( typeof message === 'string' ) { try { message = JSON.parse( message ); } catch { return; } } if ( target && typeof target.postMessage === 'function' ) { try { target.postMessage( JSON.stringify( { type: 'likesMessage', data: message, } ), '*' ); } catch { // Ignore error } } } function JetpackLikesBatchHandler() { const requests = []; document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ).forEach( widget => { if ( jetpackLikesWidgetBatch.indexOf( widget.id ) > -1 ) { return; } if ( ! jetpackIsScrolledIntoView( widget ) ) { return; } jetpackLikesWidgetBatch.push( widget.id ); var regex = /like-(post|comment)-wrapper-(\d+)-(\d+)-(\w+)/, match = regex.exec( widget.id ), info; if ( ! match || match.length !== 5 ) { return; } info = { blog_id: match[ 2 ], width: widget.width, }; if ( 'post' === match[ 1 ] ) { info.post_id = match[ 3 ]; } else if ( 'comment' === match[ 1 ] ) { info.comment_id = match[ 3 ]; } info.obj_id = match[ 4 ]; requests.push( info ); } ); if ( requests.length > 0 ) { JetpackLikesPostMessage( { event: 'initialBatch', requests: requests }, window.frames[ 'likes-master' ] ); } } function JetpackLikesMessageListener( event ) { let message = event && event.data; if ( typeof message === 'string' ) { try { message = JSON.parse( message ); } catch { return; } } const type = message && message.type; const data = message && message.data; if ( type !== 'likesMessage' || typeof data.event === 'undefined' ) { return; } // We only allow messages from one origin const allowedOrigin = 'https://widgets.wp.com'; if ( allowedOrigin !== event.origin ) { return; } switch ( data.event ) { case 'masterReady': jetpackLikesDocReadyPromise.then( () => { jetpackLikesMasterReady = true; const stylesData = { event: 'injectStyles', }; const sdTextColor = document.querySelector( '.sd-text-color' ); const sdLinkColor = document.querySelector( '.sd-link-color' ); const sdTextColorStyles = ( sdTextColor && getComputedStyle( sdTextColor ) ) || {}; const sdLinkColorStyles = ( sdLinkColor && getComputedStyle( sdLinkColor ) ) || {}; // enable reblogs if we're on a single post page if ( document.body.classList.contains( 'single' ) ) { JetpackLikesPostMessage( { event: 'reblogsEnabled' }, window.frames[ 'likes-master' ] ); } stylesData.textStyles = { color: sdTextColorStyles.color, fontFamily: sdTextColorStyles[ 'font-family' ], fontSize: sdTextColorStyles[ 'font-size' ], direction: sdTextColorStyles.direction, fontWeight: sdTextColorStyles[ 'font-weight' ], fontStyle: sdTextColorStyles[ 'font-style' ], textDecoration: sdTextColorStyles[ 'text-decoration' ], }; stylesData.linkStyles = { color: sdLinkColorStyles.color, fontFamily: sdLinkColorStyles[ 'font-family' ], fontSize: sdLinkColorStyles[ 'font-size' ], textDecoration: sdLinkColorStyles[ 'text-decoration' ], fontWeight: sdLinkColorStyles[ 'font-weight' ], fontStyle: sdLinkColorStyles[ 'font-style' ], }; JetpackLikesPostMessage( stylesData, window.frames[ 'likes-master' ] ); JetpackLikesBatchHandler(); } ); break; // We're keeping this for planned future follow ups. // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815 case 'showLikeWidget': break; // We're keeping this for planned future follow ups. // @see: https://github.com/Automattic/jetpack/pull/42361#discussion_r1995338815 case 'showCommentLikeWidget': break; case 'killCommentLikes': // If kill switch for comment likes is enabled remove all widgets wrappers and `Loading...` placeholders. document .querySelectorAll( '.jetpack-comment-likes-widget-wrapper' ) .forEach( wrapper => wrapper.remove() ); break; case 'clickReblogFlair': if ( wpcom_reblog && typeof wpcom_reblog.toggle_reblog_box_flair === 'function' ) { wpcom_reblog.toggle_reblog_box_flair( data.obj_id ); } break; case 'hideOtherGravatars': { hideLikersPopover(); break; } case 'showOtherGravatars': { const container = document.querySelector( '#likes-other-gravatars' ); if ( ! container ) { break; } const list = container.querySelector( 'ul' ); container.style.display = 'none'; list.innerHTML = ''; container .querySelectorAll( '.likes-text span' ) .forEach( item => ( item.textContent = data.totalLikesLabel ) ); ( data.likers || [] ).forEach( async ( liker, index ) => { if ( liker.profile_URL.substr( 0, 4 ) !== 'http' ) { // We only display gravatars with http or https schema return; } const element = document.createElement( 'li' ); list.append( element ); const profileLink = encodeURI( liker.profile_URL ); const avatarLink = encodeURI( liker.avatar_URL ); element.innerHTML = ` `; // Add some extra attributes through native methods, to ensure strings are sanitized. element.classList.add( liker.css_class ); element.querySelector( 'img' ).alt = data.avatarAltTitle.replace( '%s', liker.name ); element.querySelector( 'span' ).innerText = liker.name; if ( index === data.likers.length - 1 ) { element.addEventListener( 'keydown', e => { if ( e.key === 'Tab' && ! e.shiftKey ) { e.preventDefault(); hideLikersPopover(); JetpackLikesPostMessage( { event: 'focusLikesCount', parent: data.parent }, window.frames[ 'likes-master' ] ); } } ); } } ); const positionPopup = function () { const containerStyle = getComputedStyle( container ); const isRtl = containerStyle.direction === 'rtl'; const el = document.querySelector( `*[name='${ data.parent }']` ); const rect = el.getBoundingClientRect(); const win = el.ownerDocument.defaultView; const offset = { top: rect.top + win.pageYOffset, left: rect.left + win.pageXOffset, }; let containerLeft; container.style.top = offset.top + data.position.top - 1 + 'px'; if ( isRtl ) { const visibleAvatarsCount = data && data.likers ? Math.min( data.likers.length, 5 ) : 0; // 24px is the width of the avatar + 4px is the padding between avatars containerLeft = offset.left + data.position.left + 24 * visibleAvatarsCount + 4; container.style.transform = 'translateX(-100%)'; } else { containerLeft = offset.left + data.position.left; } container.style.left = containerLeft + 'px'; // If the popup overflows viewport width, we should show it on the next line. // Push it offscreen to calculated rendered width. container.style.left = '-9999px'; container.style.display = 'block'; // If the popup exceeds the viewport width, // flip the position of the popup. const containerWidth = container.offsetWidth; const containerRight = containerLeft + containerWidth; if ( containerRight > win.innerWidth ) { containerLeft = rect.right - containerWidth; } // Set the container left container.style.left = containerLeft + 'px'; container.setAttribute( 'aria-hidden', 'false' ); }; positionPopup(); container.focus(); const debounce = function ( func, wait ) { var timeout; return function () { var context = this; var args = arguments; clearTimeout( timeout ); timeout = setTimeout( function () { func.apply( context, args ); }, wait ); }; }; const debouncedPositionPopup = debounce( positionPopup, 100 ); // Keep a reference of this function in the element itself // so that we can destroy it later container.__resizeHandler = debouncedPositionPopup; // When window is resized, resize the popup. window.addEventListener( 'resize', debouncedPositionPopup ); container.focus(); } } } window.addEventListener( 'message', JetpackLikesMessageListener ); function hideLikersPopover() { const container = document.querySelector( '#likes-other-gravatars' ); if ( container ) { container.style.display = 'none'; container.setAttribute( 'aria-hidden', 'true' ); // Remove the resize event listener and cleanup. const resizeHandler = container.__resizeHandler; if ( resizeHandler ) { window.removeEventListener( 'resize', resizeHandler ); delete container.__resizeHandler; } } } document.addEventListener( 'click', hideLikersPopover ); function JetpackLikesWidgetQueueHandler() { var wrapperID; if ( ! jetpackLikesMasterReady ) { setTimeout( JetpackLikesWidgetQueueHandler, 500 ); return; } // Restore widgets to initial unloaded state when they are scrolled out of view. jetpackUnloadScrolledOutWidgets(); var unloadedWidgetsInView = jetpackGetUnloadedWidgetsInView(); if ( unloadedWidgetsInView.length > 0 ) { // Grab any unloaded widgets for a batch request JetpackLikesBatchHandler(); } for ( var i = 0, length = unloadedWidgetsInView.length; i <= length - 1; i++ ) { wrapperID = unloadedWidgetsInView[ i ].id; if ( ! wrapperID ) { continue; } jetpackLoadLikeWidgetIframe( wrapperID ); } } function jetpackLoadLikeWidgetIframe( wrapperID ) { if ( typeof wrapperID === 'undefined' ) { return; } const wrapper = document.querySelector( '#' + wrapperID ); wrapper.querySelectorAll( 'iframe' ).forEach( iFrame => iFrame.remove() ); const placeholder = wrapper.querySelector( '.likes-widget-placeholder' ); // Post like iframe if ( placeholder && placeholder.classList.contains( 'post-likes-widget-placeholder' ) ) { const postLikesFrame = document.createElement( 'iframe' ); postLikesFrame.classList.add( 'post-likes-widget', 'jetpack-likes-widget' ); postLikesFrame.name = wrapper.dataset.name; postLikesFrame.src = wrapper.dataset.src; postLikesFrame.height = '55px'; postLikesFrame.width = '100%'; postLikesFrame.frameBorder = '0'; postLikesFrame.scrolling = 'no'; postLikesFrame.title = wrapper.dataset.title; placeholder.after( postLikesFrame ); } // Comment like iframe if ( placeholder.classList.contains( 'comment-likes-widget-placeholder' ) ) { const commentLikesFrame = document.createElement( 'iframe' ); commentLikesFrame.class = 'comment-likes-widget-frame jetpack-likes-widget-frame'; commentLikesFrame.name = wrapper.dataset.name; commentLikesFrame.src = wrapper.dataset.src; commentLikesFrame.height = '18px'; commentLikesFrame.width = '100%'; commentLikesFrame.frameBorder = '0'; commentLikesFrame.scrolling = 'no'; wrapper.querySelector( '.comment-like-feedback' ).after( commentLikesFrame ); jetpackCommentLikesLoadedWidgets.push( commentLikesFrame ); } wrapper.classList.remove( 'jetpack-likes-widget-unloaded' ); wrapper.classList.add( 'jetpack-likes-widget-loading' ); wrapper.querySelector( 'iframe' ).addEventListener( 'load', e => { JetpackLikesPostMessage( { event: 'loadLikeWidget', name: e.target.name, width: e.target.width }, window.frames[ 'likes-master' ] ); wrapper.classList.remove( 'jetpack-likes-widget-loading' ); wrapper.classList.add( 'jetpack-likes-widget-loaded' ); } ); } function jetpackGetUnloadedWidgetsInView() { const unloadedWidgets = document.querySelectorAll( 'div.jetpack-likes-widget-unloaded' ); return [ ...unloadedWidgets ].filter( item => jetpackIsScrolledIntoView( item ) ); } function jetpackIsScrolledIntoView( element ) { const top = element.getBoundingClientRect().top; const bottom = element.getBoundingClientRect().bottom; // Allow some slack above and bellow the fold with jetpackLikesLookAhead, // with the aim of hiding the transition from unloaded to loaded widget from users. return top + jetpackLikesLookAhead >= 0 && bottom <= window.innerHeight + jetpackLikesLookAhead; } function jetpackUnloadScrolledOutWidgets() { for ( let i = jetpackCommentLikesLoadedWidgets.length - 1; i >= 0; i-- ) { const currentWidgetIframe = jetpackCommentLikesLoadedWidgets[ i ]; if ( ! jetpackIsScrolledIntoView( currentWidgetIframe ) ) { const widgetWrapper = currentWidgetIframe && currentWidgetIframe.parentElement && currentWidgetIframe.parentElement.parentElement; // Restore parent class to 'unloaded' so this widget can be picked up by queue manager again if needed. widgetWrapper.classList.remove( 'jetpack-likes-widget-loaded' ); widgetWrapper.classList.remove( 'jetpack-likes-widget-loading' ); widgetWrapper.classList.add( 'jetpack-likes-widget-unloaded' ); // Remove it from the list of loaded widgets. jetpackCommentLikesLoadedWidgets.splice( i, 1 ); // Remove comment like widget iFrame. currentWidgetIframe.remove(); } } } var jetpackWidgetsDelayedExec = function ( after, fn ) { var timer; return function () { clearTimeout( timer ); timer = setTimeout( fn, after ); }; }; var jetpackOnScrollStopped = jetpackWidgetsDelayedExec( 250, JetpackLikesWidgetQueueHandler ); // Load initial batch of widgets, prior to any scrolling events. JetpackLikesWidgetQueueHandler(); // Add event listener to execute queue handler after scroll. window.addEventListener( 'scroll', jetpackOnScrollStopped, true ); ;