/* global wpcom, jetpackCarouselStrings, DocumentTouch */ /* eslint-disable no-shadow */ ( function () { 'use strict'; var swiper; ///////////////////////////////////// // Utility functions ///////////////////////////////////// var util = ( function () { var noop = function () {}; function texturize( text ) { // Ensure we get a string. text = text + ''; text = text.replace( /'/g, '’' ).replace( /'/g, '’' ); text = text .replace( /"/g, '”' ) .replace( /"/g, '”' ) .replace( /"/g, '”' ) .replace( /[\u201D]/g, '”' ); // Untexturize allowed HTML tags params double-quotes. text = text.replace( /([\w]+)=[\d]+;(.+?)[\d]+;/g, '$1="$2"' ); return text.trim(); } function applyReplacements( text, replacements ) { if ( ! text ) { return; } if ( ! replacements ) { return text; } return text.replace( /{(\d+)}/g, function ( match, number ) { return typeof replacements[ number ] !== 'undefined' ? replacements[ number ] : match; } ); } function getBackgroundImage( imgEl ) { var canvas = document.createElement( 'canvas' ), context = canvas.getContext && canvas.getContext( '2d' ); if ( ! imgEl ) { return; } context.filter = 'blur(20px) '; context.drawImage( imgEl, 0, 0 ); var url = canvas.toDataURL( 'image/png' ); canvas = null; return url; } return { noop: noop, texturize: texturize, applyReplacements: applyReplacements, getBackgroundImage: getBackgroundImage, }; } )(); ///////////////////////////////////// // DOM-related utility functions ///////////////////////////////////// var domUtil = ( function () { // Helper matches function (not a polyfill), compatible with IE 11. function matches( el, sel ) { if ( Element.prototype.matches ) { return el.matches( sel ); } if ( Element.prototype.msMatchesSelector ) { return el.msMatchesSelector( sel ); } } // Helper closest parent node function (not a polyfill) based on // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill function closest( el, sel ) { if ( el.closest ) { return el.closest( sel ); } var current = el; do { if ( matches( current, sel ) ) { return current; } current = current.parentElement || current.parentNode; } while ( current !== null && current.nodeType === 1 ); return null; } function hide( el ) { if ( el ) { el.style.display = 'none'; } } function show( el ) { if ( el ) { // Everything we show and hide in Carousel is currently a block, // so we can make this really straightforward. el.style.display = 'block'; } } function fade( el, start, end, callback ) { if ( ! el ) { return callback(); } // Prepare for transition. // Ensure the item is in the render tree, in its initial state. el.style.removeProperty( 'display' ); el.style.opacity = start; el.style.pointerEvents = 'none'; var animate = function ( t0, duration ) { var t = performance.now(); var diff = t - t0; var ratio = diff / duration; if ( ratio < 1 ) { el.style.opacity = start + ( end - start ) * ratio; requestAnimationFrame( () => animate( t0, duration ) ); } else { el.style.opacity = end; el.style.removeProperty( 'pointer-events' ); callback(); } }; requestAnimationFrame( function () { // Double rAF for browser compatibility. requestAnimationFrame( function () { animate( performance.now(), 200 ); } ); } ); } function fadeIn( el, callback ) { callback = callback || util.noop; fade( el, 0, 1, callback ); } function fadeOut( el, callback ) { callback = callback || util.noop; fade( el, 1, 0, function () { if ( el ) { el.style.display = 'none'; } callback(); } ); } function emitEvent( el, type, detail ) { var e; try { e = new CustomEvent( type, { bubbles: true, cancelable: true, detail: detail || null, } ); } catch { e = document.createEvent( 'CustomEvent' ); e.initCustomEvent( type, true, true, detail || null ); } el.dispatchEvent( e ); } // From: https://easings.net/#easeInOutQuad function easeInOutQuad( num ) { return num < 0.5 ? 2 * num * num : 1 - Math.pow( -2 * num + 2, 2 ) / 2; } function getFooterClearance( container ) { var footer = container.querySelector( '.jp-carousel-info-footer' ); var infoArea = container.querySelector( '.jp-carousel-info-extra' ); var contentArea = container.querySelector( '.jp-carousel-info-content-wrapper' ); if ( footer && infoArea && contentArea ) { var styles = window.getComputedStyle( infoArea ); var padding = parseInt( styles.paddingTop, 10 ) + parseInt( styles.paddingBottom, 10 ); padding = isNaN( padding ) ? 0 : padding; return contentArea.offsetHeight + footer.offsetHeight + padding; } return 0; } function isTouch() { return ( 'ontouchstart' in window || ( window.DocumentTouch && document instanceof DocumentTouch ) ); } function scrollToElement( el, container, callback ) { if ( ! el || ! container ) { if ( callback ) { return callback(); } return; } // For iOS Safari compatibility, use JS to set the minimum height. var infoArea = container.querySelector( '.jp-carousel-info-extra' ); if ( infoArea ) { // 64px is the same height as `.jp-carousel-info-footer` in the CSS. infoArea.style.minHeight = window.innerHeight - 64 + 'px'; } var isScrolling = true; var startTime = Date.now(); var duration = 300; var originalPosition = container.scrollTop; var targetPosition = Math.max( 0, el.offsetTop - Math.max( 0, window.innerHeight - getFooterClearance( container ) ) ); var distance = targetPosition - container.scrollTop; distance = Math.min( distance, container.scrollHeight - window.innerHeight ); function stopScroll() { isScrolling = false; } function runScroll() { var now = Date.now(); var progress = easeInOutQuad( ( now - startTime ) / duration ); progress = progress > 1 ? 1 : progress; var newVal = progress * distance; container.scrollTop = originalPosition + newVal; if ( now <= startTime + duration && isScrolling ) { return requestAnimationFrame( runScroll ); } if ( callback ) { callback(); } if ( infoArea ) { infoArea.style.minHeight = ''; } isScrolling = false; container.removeEventListener( 'wheel', stopScroll ); } // Allow scroll to be cancelled by user interaction. container.addEventListener( 'wheel', stopScroll ); runScroll(); } function getJSONAttribute( el, attr ) { if ( ! el || ! el.hasAttribute( attr ) ) { return undefined; } try { return JSON.parse( el.getAttribute( attr ) ); } catch { return undefined; } } function convertToPlainText( html ) { var dummy = document.createElement( 'div' ); dummy.textContent = html; return dummy.innerHTML; } function stripHTML( text ) { return text.replace( /<[^>]*>?/gm, '' ); } return { closest: closest, matches: matches, hide: hide, show: show, fadeIn: fadeIn, fadeOut: fadeOut, scrollToElement: scrollToElement, getJSONAttribute: getJSONAttribute, convertToPlainText: convertToPlainText, stripHTML: stripHTML, emitEvent: emitEvent, isTouch: isTouch, }; } )(); ///////////////////////////////////// // Carousel implementation ///////////////////////////////////// function init() { var commentInterval; var screenPadding; var originalOverflow; var originalHOverflow; var scrollPos; var lastKnownLocationHash = ''; var isUserTyping = false; var gallerySelector = 'div.gallery, div.tiled-gallery, ul.wp-block-gallery, ul.blocks-gallery-grid, ' + 'figure.wp-block-gallery.has-nested-images, div.wp-block-jetpack-tiled-gallery, a.single-image-gallery'; // Selector for items within a gallery or tiled gallery. var galleryItemSelector = '.gallery-item, .tiled-gallery-item, .blocks-gallery-item, ' + ' .tiled-gallery__item'; // Selector for all items including single images. var itemSelector = galleryItemSelector + ', .wp-block-image'; var carousel = {}; var stat = typeof wpcom !== 'undefined' && wpcom.carousel && wpcom.carousel.stat ? wpcom.carousel.stat : util.noop; var pageview = typeof wpcom !== 'undefined' && wpcom.carousel && wpcom.carousel.pageview ? wpcom.carousel.pageview : util.noop; function handleKeyboardEvent( e ) { if ( ! isUserTyping ) { switch ( e.which ) { case 38: // up e.preventDefault(); carousel.overlay.scrollTop -= 100; break; case 40: // down e.preventDefault(); carousel.overlay.scrollTop += 100; break; case 39: // right e.preventDefault(); swiper.slideNext(); break; case 37: // left case 8: // backspace e.preventDefault(); swiper.slidePrev(); break; case 27: // escape e.preventDefault(); closeCarousel(); break; default: break; } } } function disableKeyboardNavigation() { isUserTyping = true; } function enableKeyboardNavigation() { isUserTyping = false; } function calculatePadding() { var baseScreenPadding = 110; screenPadding = baseScreenPadding; if ( window.innerWidth <= 760 ) { screenPadding = Math.round( ( window.innerWidth / 760 ) * baseScreenPadding ); if ( screenPadding < 40 && domUtil.isTouch() ) { screenPadding = 0; } } } function makeGalleryImageAccessible( img ) { img.role = 'button'; img.tabIndex = 0; img.ariaLabel = jetpackCarouselStrings.image_label; } function initializeCarousel() { if ( ! carousel.overlay ) { carousel.overlay = document.querySelector( '.jp-carousel-overlay' ); carousel.container = carousel.overlay.querySelector( '.jp-carousel-wrap' ); carousel.gallery = carousel.container.querySelector( '.jp-carousel' ); carousel.info = carousel.overlay.querySelector( '.jp-carousel-info' ); carousel.caption = carousel.info.querySelector( '.jp-carousel-caption' ); carousel.commentField = carousel.overlay.querySelector( '#jp-carousel-comment-form-comment-field' ); carousel.emailField = carousel.overlay.querySelector( '#jp-carousel-comment-form-email-field' ); carousel.authorField = carousel.overlay.querySelector( '#jp-carousel-comment-form-author-field' ); carousel.urlField = carousel.overlay.querySelector( '#jp-carousel-comment-form-url-field' ); calculatePadding(); [ carousel.commentField, carousel.emailField, carousel.authorField, carousel.urlField, ].forEach( function ( field ) { if ( field ) { field.addEventListener( 'focus', disableKeyboardNavigation ); field.addEventListener( 'blur', enableKeyboardNavigation ); } } ); carousel.overlay.addEventListener( 'click', function ( e ) { var target = e.target; var isTargetCloseHint = !! domUtil.closest( target, '.jp-carousel-close-hint' ); var isSmallScreen = !! window.matchMedia( '(max-device-width: 760px)' ).matches; if ( target === carousel.overlay ) { if ( ! isSmallScreen ) { closeCarousel(); } } else if ( isTargetCloseHint ) { closeCarousel(); } else if ( target.classList.contains( 'jp-carousel-image-download' ) ) { stat( 'download_original_click' ); } else if ( target.classList.contains( 'jp-carousel-comment-login' ) ) { handleCommentLoginClick( e ); } else if ( domUtil.closest( target, '#jp-carousel-comment-form-container' ) ) { handleCommentFormClick( e ); } else if ( domUtil.closest( target, '.jp-carousel-photo-icons-container' ) || target.classList.contains( 'jp-carousel-photo-title' ) ) { handleFooterElementClick( e ); } } ); window.addEventListener( 'keydown', handleKeyboardEvent ); carousel.overlay.addEventListener( 'jp_carousel.afterOpen', function () { enableKeyboardNavigation(); // Don't show navigation if there's only one image. if ( carousel.slides.length <= 1 ) { return; } // Show dot pagination if slide count is <= 5, otherwise show n/total. if ( carousel.slides.length <= 5 ) { domUtil.show( carousel.info.querySelector( '.jp-swiper-pagination' ) ); } else { domUtil.show( carousel.info.querySelector( '.jp-carousel-pagination' ) ); } } ); carousel.overlay.addEventListener( 'jp_carousel.beforeClose', function () { disableKeyboardNavigation(); // Fixes some themes where closing carousel brings view back to top. document.documentElement.style.removeProperty( 'height' ); // If we disable the swiper (because there's only one image) // we have to re-enable it here again as Swiper doesn't, for some reason, // show the navigation buttons again after reinitialization. if ( swiper ) { swiper.enable(); } // Hide pagination. domUtil.hide( carousel.info.querySelector( '.jp-swiper-pagination' ) ); domUtil.hide( carousel.info.querySelector( '.jp-carousel-pagination' ) ); } ); carousel.overlay.addEventListener( 'jp_carousel.afterClose', function () { // don't force the browser back when the carousel closes. if ( window.history.pushState ) { history.pushState( '', document.title, window.location.pathname + window.location.search ); } else { window.location.href = ''; } lastKnownLocationHash = ''; carousel.isOpen = false; } ); // Prevent native browser zooming carousel.overlay.addEventListener( 'touchstart', function ( e ) { if ( e.touches.length > 1 ) { e.preventDefault(); } } ); } } function handleCommentLoginClick() { var slide = carousel.currentSlide; var attachmentId = slide ? slide.attrs.attachmentId : '0'; window.location.href = jetpackCarouselStrings.login_url + '%23jp-carousel-' + attachmentId; } function updatePostResults( msg, isSuccess ) { var results = carousel.overlay.querySelector( '#jp-carousel-comment-post-results' ); var elClass = 'jp-carousel-comment-post-' + ( isSuccess ? 'success' : 'error' ); results.innerHTML = '' + msg + ''; domUtil.hide( carousel.overlay.querySelector( '#jp-carousel-comment-form-spinner' ) ); carousel.overlay .querySelector( '#jp-carousel-comment-form' ) .classList.remove( 'jp-carousel-is-disabled' ); domUtil.show( results ); } function handleCommentFormClick( e ) { var target = e.target; var data = domUtil.getJSONAttribute( carousel.container, 'data-carousel-extra' ) || {}; var attachmentId = carousel.currentSlide.attrs.attachmentId; var wrapper = document.querySelector( '#jp-carousel-comment-form-submit-and-info-wrapper' ); var spinner = document.querySelector( '#jp-carousel-comment-form-spinner' ); // eslint-disable-next-line @wordpress/no-unused-vars-before-return var submit = document.querySelector( '#jp-carousel-comment-form-button-submit' ); var form = document.querySelector( '#jp-carousel-comment-form' ); if ( carousel.commentField && carousel.commentField.getAttribute( 'id' ) === target.getAttribute( 'id' ) ) { // For first page load disableKeyboardNavigation(); domUtil.show( wrapper ); } else if ( domUtil.matches( target, 'input[type="submit"]' ) ) { e.preventDefault(); e.stopPropagation(); domUtil.show( spinner ); form.classList.add( 'jp-carousel-is-disabled' ); var ajaxData = { action: 'post_attachment_comment', nonce: jetpackCarouselStrings.nonce, blog_id: data.blog_id, id: attachmentId, comment: carousel.commentField.value, }; if ( ! ajaxData.comment.length ) { updatePostResults( jetpackCarouselStrings.no_comment_text, false ); return; } if ( Number( jetpackCarouselStrings.is_logged_in ) !== 1 ) { ajaxData.email = carousel.emailField.value; ajaxData.author = carousel.authorField.value; ajaxData.url = carousel.urlField.value; if ( Number( jetpackCarouselStrings.require_name_email ) === 1 ) { if ( ! ajaxData.email.length || ! ajaxData.email.match( '@' ) ) { updatePostResults( jetpackCarouselStrings.no_comment_email, false ); return; } else if ( ! ajaxData.author.length ) { updatePostResults( jetpackCarouselStrings.no_comment_author, false ); return; } } } var xhr = new XMLHttpRequest(); xhr.open( 'POST', jetpackCarouselStrings.ajaxurl, true ); xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' ); xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' ); xhr.onreadystatechange = function () { if ( this.readyState === XMLHttpRequest.DONE && this.status >= 200 && this.status < 300 ) { var response; try { response = JSON.parse( this.response ); } catch { updatePostResults( jetpackCarouselStrings.comment_post_error, false ); return; } if ( response.comment_status === 'approved' ) { updatePostResults( jetpackCarouselStrings.comment_approved, true ); } else if ( response.comment_status === 'unapproved' ) { updatePostResults( jetpackCarouselStrings.comment_unapproved, true ); } else { // 'deleted', 'spam', false updatePostResults( jetpackCarouselStrings.comment_post_error, false ); } clearCommentTextAreaValue(); fetchComments( attachmentId ); submit.value = jetpackCarouselStrings.post_comment; domUtil.hide( spinner ); form.classList.remove( 'jp-carousel-is-disabled' ); } else { // TODO: Add error handling and display here updatePostResults( jetpackCarouselStrings.comment_post_error, false ); } }; var params = []; for ( var item in ajaxData ) { if ( item ) { // Encode each form element into a URI-compatible string. var encoded = encodeURIComponent( item ) + '=' + encodeURIComponent( ajaxData[ item ] ); // In x-www-form-urlencoded, spaces should be `+`, not `%20`. params.push( encoded.replace( /%20/g, '+' ) ); } } var encodedData = params.join( '&' ); xhr.send( encodedData ); } } /** * Handles clicks to icons and other action elements in the icon container. * @param {MouseEvent|TouchEvent|KeyBoardEvent} Event object. */ function handleFooterElementClick( e ) { e.preventDefault(); var target = e.target; var extraInfoContainer = carousel.info.querySelector( '.jp-carousel-info-extra' ); var photoMetaContainer = carousel.info.querySelector( '.jp-carousel-image-meta' ); var commentsContainer = carousel.info.querySelector( '.jp-carousel-comments-wrapper' ); var infoIcon = carousel.info.querySelector( '.jp-carousel-icon-info' ); var commentsIcon = carousel.info.querySelector( '.jp-carousel-icon-comments' ); function handleInfoToggle() { if ( commentsIcon ) { commentsIcon.classList.remove( 'jp-carousel-selected' ); } infoIcon.classList.toggle( 'jp-carousel-selected' ); if ( commentsContainer ) { commentsContainer.classList.remove( 'jp-carousel-show' ); } if ( photoMetaContainer ) { photoMetaContainer.classList.toggle( 'jp-carousel-show' ); if ( photoMetaContainer.classList.contains( 'jp-carousel-show' ) ) { extraInfoContainer.classList.add( 'jp-carousel-show' ); } else { extraInfoContainer.classList.remove( 'jp-carousel-show' ); } } } function handleCommentToggle() { if ( infoIcon ) { infoIcon.classList.remove( 'jp-carousel-selected' ); } commentsIcon.classList.toggle( 'jp-carousel-selected' ); if ( photoMetaContainer ) { photoMetaContainer.classList.remove( 'jp-carousel-show' ); } if ( commentsContainer ) { commentsContainer.classList.toggle( 'jp-carousel-show' ); if ( commentsContainer.classList.contains( 'jp-carousel-show' ) ) { extraInfoContainer.classList.add( 'jp-carousel-show' ); } else { extraInfoContainer.classList.remove( 'jp-carousel-show' ); } } } if ( domUtil.closest( target, '.jp-carousel-icon-info' ) || target.classList.contains( 'jp-carousel-photo-title' ) ) { if ( photoMetaContainer && photoMetaContainer.classList.contains( 'jp-carousel-show' ) ) { domUtil.scrollToElement( carousel.overlay, carousel.overlay, handleInfoToggle ); } else { handleInfoToggle(); domUtil.scrollToElement( carousel.info, carousel.overlay ); } } if ( domUtil.closest( target, '.jp-carousel-icon-comments' ) ) { if ( commentsContainer && commentsContainer.classList.contains( 'jp-carousel-show' ) ) { domUtil.scrollToElement( carousel.overlay, carousel.overlay, handleCommentToggle ); } else { handleCommentToggle(); domUtil.scrollToElement( carousel.info, carousel.overlay ); } } } function processSingleImageGallery() { var images = document.querySelectorAll( 'a img[data-attachment-id]' ); Array.prototype.forEach.call( images, function ( image ) { var link = image.parentElement; var container = link.parentElement; // Skip if image was already added to gallery by shortcode. if ( container.classList.contains( 'gallery-icon' ) ) { return; } // Skip if image is part of a gallery. if ( domUtil.closest( container, galleryItemSelector ) ) { return; } // Skip if the parent is not actually a link. if ( ! link.hasAttribute( 'href' ) ) { return; } var valid = false; // If link points to 'Media File' (ignoring GET parameters) and flag is set, allow it. if ( link.getAttribute( 'href' ).split( '?' )[ 0 ] === image.getAttribute( 'data-orig-file' ).split( '?' )[ 0 ] && Number( jetpackCarouselStrings.single_image_gallery_media_file ) === 1 ) { valid = true; } // If link points to 'Attachment Page', allow it. if ( link.getAttribute( 'href' ) === image.getAttribute( 'data-permalink' ) ) { valid = true; } // Links to 'Custom URL' or 'Media File' when flag is not set are not valid. if ( ! valid ) { return; } makeGalleryImageAccessible( image ); // Make this node a gallery recognizable by event listener above. link.classList.add( 'single-image-gallery' ); // blog_id is needed to allow posting comments to correct blog. link.setAttribute( 'data-carousel-extra', JSON.stringify( { blog_id: Number( jetpackCarouselStrings.blog_id ), } ) ); } ); } function testForData( el ) { return !! ( el && el.getAttribute( 'data-carousel-extra' ) ); } function openOrSelectSlide( gal, index ) { if ( ! carousel.isOpen ) { // The `open` method selects the correct slide during the initialization. loadSwiper( gal, { startIndex: index } ); } else { selectSlideAtIndex( index ); // We have to force swiper to slide to the index onHasChange. swiper.slideTo( index + 1 ); } } function selectSlideAtIndex( index ) { if ( ! index || index < 0 || index > carousel.slides.length ) { index = 0; } carousel.currentSlide = carousel.slides[ index ]; var current = carousel.currentSlide; var attachmentId = current.attrs.attachmentId; loadFullImage( carousel.slides[ index ] ); if ( Number( jetpackCarouselStrings.display_background_image ) === 1 && ! carousel.slides[ index ].backgroundImage ) { loadBackgroundImage( carousel.slides[ index ] ); } domUtil.hide( carousel.caption ); updateTitleCaptionAndDesc( { caption: current.attrs.caption, title: current.attrs.title, desc: current.attrs.desc, } ); var imageMeta = carousel.slides[ index ].attrs.imageMeta; updateExif( imageMeta ); updateFullSizeLink( current ); if ( Number( jetpackCarouselStrings.display_comments ) === 1 ) { testCommentsOpened( carousel.slides[ index ].attrs.commentsOpened ); fetchComments( attachmentId ); domUtil.hide( carousel.info.querySelector( '#jp-carousel-comment-post-results' ) ); } // Update pagination in footer. var pagination = carousel.info.querySelector( '.jp-carousel-pagination' ); if ( pagination && carousel.slides.length > 5 ) { var currentPage = index + 1; pagination.innerHTML = '' + currentPage + ' / ' + carousel.slides.length + ''; } // Record pageview in WP Stats, for each new image loaded full-screen. if ( jetpackCarouselStrings.stats ) { new Image().src = document.location.protocol + '//pixel.wp.com/g.gif?' + jetpackCarouselStrings.stats + '&post=' + encodeURIComponent( attachmentId ) + '&rand=' + Math.random(); } pageview( attachmentId ); window.location.hash = lastKnownLocationHash = '#jp-carousel-' + attachmentId; } function restoreScroll() { window.scrollTo( window.scrollX || window.pageXOffset || 0, scrollPos || 0 ); } function closeCarousel() { // Make sure to let the page scroll again. document.body.style.overflow = originalOverflow; document.documentElement.style.overflow = originalHOverflow; clearCommentTextAreaValue(); disableKeyboardNavigation(); domUtil.emitEvent( carousel.overlay, 'jp_carousel.beforeClose' ); restoreScroll(); swiper.destroy(); carousel.isOpen = false; // Clear slide data for DOM garbage collection. carousel.slides = []; carousel.currentSlide = undefined; carousel.gallery.innerHTML = ''; domUtil.fadeOut( carousel.overlay, function () { domUtil.emitEvent( carousel.overlay, 'jp_carousel.afterClose' ); } ); } function calculateMaxSlideDimensions() { return { width: window.innerWidth, height: window.innerHeight - 64, //subtract height of bottom info bar, }; } function selectBestImageUrl( args ) { if ( typeof args !== 'object' ) { args = {}; } if ( typeof args.origFile === 'undefined' ) { return ''; } if ( typeof args.origWidth === 'undefined' || typeof args.maxWidth === 'undefined' ) { return args.origFile; } if ( typeof args.mediumFile === 'undefined' || typeof args.largeFile === 'undefined' ) { return args.origFile; } // Check if the image is being served by Photon (using a regular expression on the hostname). var imageLinkParser = document.createElement( 'a' ); imageLinkParser.href = args.largeFile; var isPhotonUrl = /^i[0-2]\.wp\.com$/i.test( imageLinkParser.hostname ); var largeSizeParts = getImageSizeParts( args.largeFile, args.origWidth, isPhotonUrl ); var largeWidth = parseInt( largeSizeParts[ 0 ], 10 ); var largeHeight = parseInt( largeSizeParts[ 1 ], 10 ); args.origMaxWidth = args.maxWidth; args.origMaxHeight = args.maxHeight; // Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android phones = 1.5, etc) if ( typeof window.devicePixelRatio !== 'undefined' && window.devicePixelRatio > 1 ) { args.maxWidth = args.maxWidth * window.devicePixelRatio; args.maxHeight = args.maxHeight * window.devicePixelRatio; } if ( largeWidth >= args.maxWidth || largeHeight >= args.maxHeight ) { return args.largeFile; } var mediumSizeParts = getImageSizeParts( args.mediumFile, args.origWidth, isPhotonUrl ); var mediumWidth = parseInt( mediumSizeParts[ 0 ], 10 ); var mediumHeight = parseInt( mediumSizeParts[ 1 ], 10 ); if ( mediumWidth >= args.maxWidth || mediumHeight >= args.maxHeight ) { return args.mediumFile; } if ( isPhotonUrl ) { // args.origFile doesn't point to a Photon url, so in this case we use args.largeFile // to return the photon url of the original image. var largeFileIndex = args.largeFile.lastIndexOf( '?' ); var origPhotonUrl = args.largeFile; if ( largeFileIndex !== -1 ) { origPhotonUrl = args.largeFile.substring( 0, largeFileIndex ); // If we have a really large image load a smaller version // that is closer to the viewable size if ( args.origWidth > args.maxWidth || args.origHeight > args.maxHeight ) { // @2x the max sizes so we get a high enough resolution for zooming. args.origMaxWidth = args.maxWidth * 2; args.origMaxHeight = args.maxHeight * 2; origPhotonUrl += '?fit=' + args.origMaxWidth + '%2C' + args.origMaxHeight; } } return origPhotonUrl; } return args.origFile; } function getImageSizeParts( file, origWidth, isPhotonUrl ) { var size = isPhotonUrl ? file.replace( /.*=([\d]+%2C[\d]+).*$/, '$1' ) : file.replace( /.*-([\d]+x[\d]+)\..+$/, '$1' ); var sizeParts; if ( size !== file ) { sizeParts = isPhotonUrl ? size.split( '%2C' ) : size.split( 'x' ); } else { sizeParts = [ origWidth, 0 ]; } // If one of the dimensions is set to 9999, then the actual value of that dimension can't be retrieved from the url. // In that case, we set the value to 0. if ( sizeParts[ 0 ] === '9999' ) { sizeParts[ 0 ] = '0'; } if ( sizeParts[ 1 ] === '9999' ) { sizeParts[ 1 ] = '0'; } return sizeParts; } /** * Returns a number in a fraction format that represents the shutter speed. * @param Number speed * @return String */ function formatShutterSpeed( speed ) { var denominator; // round to one decimal if value > 1s by multiplying it by 10, rounding, then dividing by 10 again if ( speed >= 1 ) { return Math.round( speed * 10 ) / 10 + 's'; } // If the speed is less than one, we find the denominator by inverting // the number. Since cameras usually use rational numbers as shutter // speeds, we should get a nice round number. Or close to one in cases // like 1/30. So we round it. denominator = Math.round( 1 / speed ); return '1/' + denominator + 's'; } function parseTitleOrDesc( value ) { if ( ! value.match( ' ' ) && value.match( '_' ) ) { return ''; } return value; } function updateTitleCaptionAndDesc( data ) { var caption = ''; var title = ''; var desc = ''; var captionMainElement; var captionInfoExtraElement; var titleElement; var descriptionElement; captionMainElement = carousel.overlay.querySelector( '.jp-carousel-photo-caption' ); captionInfoExtraElement = carousel.overlay.querySelector( '.jp-carousel-caption' ); titleElement = carousel.overlay.querySelector( '.jp-carousel-photo-title' ); descriptionElement = carousel.overlay.querySelector( '.jp-carousel-photo-description' ); domUtil.hide( captionMainElement ); domUtil.hide( captionInfoExtraElement ); domUtil.hide( titleElement ); domUtil.hide( descriptionElement ); caption = parseTitleOrDesc( data.caption ) || ''; title = parseTitleOrDesc( data.title ) || ''; desc = parseTitleOrDesc( data.desc ) || ''; if ( caption || title || desc ) { if ( caption ) { captionMainElement.innerHTML = caption; captionInfoExtraElement.innerHTML = caption; domUtil.show( captionMainElement ); domUtil.show( captionInfoExtraElement ); } if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( title ) ) { title = ''; } if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( desc ) ) { desc = ''; } if ( domUtil.stripHTML( title ) === domUtil.stripHTML( desc ) ) { desc = ''; } if ( desc ) { descriptionElement.innerHTML = desc; domUtil.show( descriptionElement ); if ( ! title && ! caption ) { captionMainElement.innerHTML = domUtil.stripHTML( desc ); domUtil.show( captionMainElement ); } } if ( title ) { var plainTitle = domUtil.stripHTML( title ); titleElement.innerHTML = plainTitle; if ( ! caption ) { captionMainElement.innerHTML = plainTitle; captionInfoExtraElement.innerHTML = plainTitle; domUtil.show( captionMainElement ); } domUtil.show( titleElement ); } } } // updateExif updates the contents of the exif UL (.jp-carousel-image-exif) function updateExif( meta ) { if ( ! meta || Number( jetpackCarouselStrings.display_exif ) !== 1 ) { return false; } var ul = carousel.info.querySelector( '.jp-carousel-image-meta ul.jp-carousel-image-exif' ); var html = ''; for ( var key in meta ) { var val = meta[ key ]; var metaKeys = jetpackCarouselStrings.meta_data || []; if ( parseFloat( val ) === 0 || ! val.length || metaKeys.indexOf( key ) === -1 ) { continue; } switch ( key ) { case 'focal_length': val = val + 'mm'; break; case 'shutter_speed': val = formatShutterSpeed( val ); break; case 'aperture': val = 'f/' + val; break; } html += '