summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/modules/carousel/jetpack-carousel.js')
-rw-r--r--plugins/jetpack/modules/carousel/jetpack-carousel.js2964
1 files changed, 1415 insertions, 1549 deletions
diff --git a/plugins/jetpack/modules/carousel/jetpack-carousel.js b/plugins/jetpack/modules/carousel/jetpack-carousel.js
index 2a5ad2d6..c9d295b8 100644
--- a/plugins/jetpack/modules/carousel/jetpack-carousel.js
+++ b/plugins/jetpack/modules/carousel/jetpack-carousel.js
@@ -1,1257 +1,973 @@
-/* global jetpackCarouselStrings, DocumentTouch */
-
-jQuery( document ).ready( function( $ ) {
- // gallery faded layer and container elements
- var overlay,
- comments,
- gallery,
- container,
- nextButton,
- previousButton,
- info,
- transitionBegin,
- caption,
- resizeTimeout,
- photo_info,
- close_hint,
- commentInterval,
- lastSelectedSlide,
- screenPadding = 110,
- originalOverflow = $( 'body' ).css( 'overflow' ),
- originalHOverflow = $( 'html' ).css( 'overflow' ),
- proportion = 85,
- last_known_location_hash = '',
- imageMeta,
- titleAndDescription,
- commentForm,
- leftColWrapper,
- scrollPos;
-
- if ( window.innerWidth <= 760 ) {
- screenPadding = Math.round( ( window.innerWidth / 760 ) * 110 );
-
- if (
- screenPadding < 40 &&
- ( 'ontouchstart' in window || ( window.DocumentTouch && document instanceof DocumentTouch ) )
- ) {
- screenPadding = 0;
+/* global wpcom, jetpackCarouselStrings, DocumentTouch */
+
+( 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, '&#8217;' ).replace( /&#039;/g, '&#8217;' );
+ text = text
+ .replace( /"/g, '&#8221;' )
+ .replace( /&#034;/g, '&#8221;' )
+ .replace( /&quot;/g, '&#8221;' )
+ .replace( /[\u201D]/g, '&#8221;' );
+ // Untexturize allowed HTML tags params double-quotes.
+ text = text.replace( /([\w]+)=&#[\d]+;(.+?)&#[\d]+;/g, '$1="$2"' );
+ return text.trim();
}
- }
-
- // Adding a polyfill for browsers that do not have Date.now
- if ( 'undefined' === typeof Date.now ) {
- Date.now = function now() {
- return new Date().getTime();
- };
- }
- var keyListener = function( e ) {
- switch ( e.which ) {
- case 38: // up
- e.preventDefault();
- container.scrollTop( container.scrollTop() - 100 );
- break;
- case 40: // down
- e.preventDefault();
- container.scrollTop( container.scrollTop() + 100 );
- break;
- case 39: // right
- e.preventDefault();
- gallery.jp_carousel( 'next' );
- break;
- case 37: // left
- case 8: // backspace
- e.preventDefault();
- gallery.jp_carousel( 'previous' );
- break;
- case 27: // escape
- e.preventDefault();
- container.jp_carousel( 'close' );
- break;
- default:
- // making jslint happy
- break;
+ 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;
+ } );
}
- };
-
- var resizeListener = function(/*e*/) {
- clearTimeout( resizeTimeout );
- resizeTimeout = setTimeout( function() {
- gallery.jp_carousel( 'slides' ).jp_carousel( 'fitSlide', true );
- gallery.jp_carousel( 'updateSlidePositions', true );
- gallery.jp_carousel( 'fitMeta', true );
- }, 200 );
- };
-
- var prepareGallery = function(/*dataCarouselExtra*/) {
- if ( ! overlay ) {
- overlay = $( '<div></div>' )
- .addClass( 'jp-carousel-overlay' )
- .css( {
- position: 'fixed',
- top: 0,
- right: 0,
- bottom: 0,
- left: 0,
- } );
- var buttons =
- '<a class="jp-carousel-commentlink" href="#">' + jetpackCarouselStrings.comment + '</a>';
- if ( 1 === Number( jetpackCarouselStrings.is_logged_in ) ) {
+ function getBackgroundImage( imgEl ) {
+ var canvas = document.createElement( 'canvas' ),
+ context = canvas.getContext && canvas.getContext( '2d' );
+
+ if ( ! imgEl ) {
+ return;
}
- buttons = $( '<div class="jp-carousel-buttons">' + buttons + '</div>' );
+ context.filter = 'blur(20px) ';
+ context.drawImage( imgEl, 0, 0 );
+ var url = canvas.toDataURL( 'image/png' );
+ canvas = null;
- caption = $( '<h2 itemprop="caption description"></h2>' );
- photo_info = $( '<div class="jp-carousel-photo-info"></div>' ).append( caption );
+ return url;
+ }
- imageMeta = $( '<div></div>' )
- .addClass( 'jp-carousel-image-meta' )
- .css( {
- float: 'right',
- 'margin-top': '20px',
- width: '250px',
- } );
+ return {
+ noop: noop,
+ texturize: texturize,
+ applyReplacements: applyReplacements,
+ getBackgroundImage: getBackgroundImage,
+ };
+ } )();
- imageMeta
- .append( buttons )
- .append( "<ul class='jp-carousel-image-exif' style='display:none;'></ul>" )
- .append( "<a class='jp-carousel-image-download' style='display:none;'></a>" )
- .append( "<div class='jp-carousel-image-map' style='display:none;'></div>" );
-
- titleAndDescription = $( '<div></div>' )
- .addClass( 'jp-carousel-titleanddesc' )
- .css( {
- width: '100%',
- 'margin-top': imageMeta.css( 'margin-top' ),
- } );
+ /////////////////////////////////////
+ // 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 );
+ }
- var commentFormMarkup = '<div id="jp-carousel-comment-form-container">';
+ if ( Element.prototype.msMatchesSelector ) {
+ return el.msMatchesSelector( sel );
+ }
+ }
- if (
- jetpackCarouselStrings.local_comments_commenting_as &&
- jetpackCarouselStrings.local_comments_commenting_as.length
- ) {
- // Comments not enabled, fallback to local comments
+ // 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 );
+ }
- if (
- 1 !== Number( jetpackCarouselStrings.is_logged_in ) &&
- 1 === Number( jetpackCarouselStrings.comment_registration )
- ) {
- commentFormMarkup +=
- '<div id="jp-carousel-comment-form-commenting-as">' +
- jetpackCarouselStrings.local_comments_commenting_as +
- '</div>';
- } else {
- commentFormMarkup += '<form id="jp-carousel-comment-form">';
- commentFormMarkup +=
- '<textarea name="comment" class="jp-carousel-comment-form-field jp-carousel-comment-form-textarea" id="jp-carousel-comment-form-comment-field" placeholder="' +
- jetpackCarouselStrings.write_comment +
- '"></textarea>';
- commentFormMarkup += '<div id="jp-carousel-comment-form-submit-and-info-wrapper">';
- commentFormMarkup +=
- '<div id="jp-carousel-comment-form-commenting-as">' +
- jetpackCarouselStrings.local_comments_commenting_as +
- '</div>';
- commentFormMarkup +=
- '<input type="submit" name="submit" class="jp-carousel-comment-form-button" id="jp-carousel-comment-form-button-submit" value="' +
- jetpackCarouselStrings.post_comment +
- '" />';
- commentFormMarkup += '<span id="jp-carousel-comment-form-spinner">&nbsp;</span>';
- commentFormMarkup += '<div id="jp-carousel-comment-post-results"></div>';
- commentFormMarkup += '</div>';
- commentFormMarkup += '</form>';
+ var current = el;
+
+ do {
+ if ( matches( current, sel ) ) {
+ return current;
}
- }
- commentFormMarkup += '</div>';
+ current = current.parentElement || current.parentNode;
+ } while ( current !== null && current.nodeType === 1 );
- commentForm = $( commentFormMarkup ).css( {
- width: '100%',
- 'margin-top': '20px',
- color: '#999',
- } );
+ return null;
+ }
- comments = $( '<div></div>' )
- .addClass( 'jp-carousel-comments' )
- .css( {
- width: '100%',
- bottom: '10px',
- 'margin-top': '20px',
- } );
+ function hide( el ) {
+ if ( el ) {
+ el.style.display = 'none';
+ }
+ }
- var commentsLoading = $(
- '<div id="jp-carousel-comments-loading"><span>' +
- jetpackCarouselStrings.loading_comments +
- '</span></div>'
- ).css( {
- width: '100%',
- bottom: '10px',
- 'margin-top': '20px',
- } );
+ 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';
+ }
+ }
- var leftWidth = $( window ).width() - screenPadding * 2 - ( imageMeta.width() + 40 );
- leftWidth += 'px';
-
- leftColWrapper = $( '<div></div>' )
- .addClass( 'jp-carousel-left-column-wrapper' )
- .css( {
- width: Math.floor( leftWidth ),
- } )
- .append( titleAndDescription )
- .append( commentForm )
- .append( comments )
- .append( commentsLoading );
-
- var fadeaway = $( '<div></div>' ).addClass( 'jp-carousel-fadeaway' );
-
- info = $( '<div></div>' )
- .addClass( 'jp-carousel-info' )
- .css( {
- top: Math.floor( ( $( window ).height() / 100 ) * proportion ),
- left: screenPadding,
- right: screenPadding,
- } )
- .append( photo_info )
- .append( imageMeta );
+ 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.transition = 'opacity 0.2s';
+ el.style.pointerEvents = 'none';
+
+ var finished = function ( e ) {
+ if ( e.target === el && e.propertyName === 'opacity' ) {
+ el.style.removeProperty( 'transition' );
+ el.style.removeProperty( 'opacity' );
+ el.style.removeProperty( 'pointer-events' );
+ el.removeEventListener( 'transitionend', finished );
+ el.removeEventListener( 'transitioncancel', finished );
+ callback();
+ }
+ };
- if ( window.innerWidth <= 760 ) {
- photo_info.remove().insertAfter( titleAndDescription );
- info.prepend( leftColWrapper );
- } else {
- info.append( leftColWrapper );
- }
-
- var targetBottomPos = $( window ).height() - parseInt( info.css( 'top' ), 10 ) + 'px';
-
- nextButton = $( '<div><span></span></div>' )
- .addClass( 'jp-carousel-next-button' )
- .css( {
- right: '15px',
- } )
- .hide();
-
- previousButton = $( '<div><span></span></div>' )
- .addClass( 'jp-carousel-previous-button' )
- .css( {
- left: 0,
- } )
- .hide();
-
- nextButton.add( previousButton ).css( {
- position: 'fixed',
- top: '40px',
- bottom: targetBottomPos,
- width: screenPadding,
+ requestAnimationFrame( function () {
+ // Double rAF for browser compatibility.
+ requestAnimationFrame( function () {
+ el.addEventListener( 'transitionend', finished );
+ el.addEventListener( 'transitioncancel', finished );
+ // Trigger transition.
+ el.style.opacity = end;
+ } );
} );
+ }
- gallery = $( '<div></div>' )
- .addClass( 'jp-carousel' )
- .css( {
- position: 'absolute',
- top: 0,
- bottom: targetBottomPos,
- left: 0,
- right: 0,
- } );
+ function fadeIn( el, callback ) {
+ callback = callback || util.noop;
+ fade( el, '0', '1', callback );
+ }
- close_hint = $( '<div class="jp-carousel-close-hint"><span>&times;</span></div>' ).css( {
- position: 'fixed',
+ function fadeOut( el, callback ) {
+ callback = callback || util.noop;
+ fade( el, '1', '0', function () {
+ if ( el ) {
+ el.style.display = 'none';
+ }
+ callback();
} );
+ }
- container = $( '<div></div>' )
- .addClass( 'jp-carousel-wrap' )
- .addClass( 'jp-carousel-transitions' );
- if ( 'white' === jetpackCarouselStrings.background_color ) {
- container.addClass( 'jp-carousel-light' );
- }
-
- container.attr( 'itemscope', '' );
-
- container.attr( 'itemtype', 'https://schema.org/ImageGallery' );
-
- container
- .css( {
- position: 'fixed',
- top: 0,
- right: 0,
- bottom: 0,
- left: 0,
- 'z-index': 2147483647,
- 'overflow-x': 'hidden',
- 'overflow-y': 'auto',
- direction: 'ltr',
- } )
- .hide()
- .append( overlay )
- .append( gallery )
- .append( fadeaway )
- .append( info )
- .append( nextButton )
- .append( previousButton )
- .append( close_hint )
- .appendTo( $( 'body' ) )
- .click( function( e ) {
- var target = $( e.target ),
- wrap = target.parents( 'div.jp-carousel-wrap' ),
- data = wrap.data( 'carousel-extra' ),
- slide = wrap.find( 'div.selected' ),
- attachment_id = slide.data( 'attachment-id' );
- data = data || [];
-
- if (
- target.is( gallery ) ||
- target
- .parents()
- .add( target )
- .is( close_hint )
- ) {
- container.jp_carousel( 'close' );
- } else if ( target.hasClass( 'jp-carousel-commentlink' ) ) {
- e.preventDefault();
- e.stopPropagation();
- $( window ).unbind( 'keydown', keyListener );
- container.animate( { scrollTop: parseInt( info.position()[ 'top' ], 10 ) }, 'fast' );
- $( '#jp-carousel-comment-form-submit-and-info-wrapper' ).slideDown( 'fast' );
- $( '#jp-carousel-comment-form-comment-field' ).focus();
- } else if ( target.hasClass( 'jp-carousel-comment-login' ) ) {
- var url = jetpackCarouselStrings.login_url + '%23jp-carousel-' + attachment_id;
-
- window.location.href = url;
- } else if ( target.parents( '#jp-carousel-comment-form-container' ).length ) {
- var textarea = $( '#jp-carousel-comment-form-comment-field' )
- .blur( function() {
- $( window ).bind( 'keydown', keyListener );
- } )
- .focus( function() {
- $( window ).unbind( 'keydown', keyListener );
- } );
-
- var emailField = $( '#jp-carousel-comment-form-email-field' )
- .blur( function() {
- $( window ).bind( 'keydown', keyListener );
- } )
- .focus( function() {
- $( window ).unbind( 'keydown', keyListener );
- } );
-
- var authorField = $( '#jp-carousel-comment-form-author-field' )
- .blur( function() {
- $( window ).bind( 'keydown', keyListener );
- } )
- .focus( function() {
- $( window ).unbind( 'keydown', keyListener );
- } );
-
- var urlField = $( '#jp-carousel-comment-form-url-field' )
- .blur( function() {
- $( window ).bind( 'keydown', keyListener );
- } )
- .focus( function() {
- $( window ).unbind( 'keydown', keyListener );
- } );
-
- if ( textarea && textarea.attr( 'id' ) === target.attr( 'id' ) ) {
- // For first page load
- $( window ).unbind( 'keydown', keyListener );
- $( '#jp-carousel-comment-form-submit-and-info-wrapper' ).slideDown( 'fast' );
- } else if ( target.is( 'input[type="submit"]' ) ) {
- e.preventDefault();
- e.stopPropagation();
-
- $( '#jp-carousel-comment-form-spinner' ).spin( 'small', 'white' );
-
- var ajaxData = {
- action: 'post_attachment_comment',
- nonce: jetpackCarouselStrings.nonce,
- blog_id: data[ 'blog_id' ],
- id: attachment_id,
- comment: textarea.val(),
- };
-
- if ( ! ajaxData[ 'comment' ].length ) {
- gallery.jp_carousel( 'postCommentError', {
- field: 'jp-carousel-comment-form-comment-field',
- error: jetpackCarouselStrings.no_comment_text,
- } );
- return;
- }
-
- if ( 1 !== Number( jetpackCarouselStrings.is_logged_in ) ) {
- ajaxData[ 'email' ] = emailField.val();
- ajaxData[ 'author' ] = authorField.val();
- ajaxData[ 'url' ] = urlField.val();
-
- if ( 1 === Number( jetpackCarouselStrings.require_name_email ) ) {
- if ( ! ajaxData[ 'email' ].length || ! ajaxData[ 'email' ].match( '@' ) ) {
- gallery.jp_carousel( 'postCommentError', {
- field: 'jp-carousel-comment-form-email-field',
- error: jetpackCarouselStrings.no_comment_email,
- } );
- return;
- } else if ( ! ajaxData[ 'author' ].length ) {
- gallery.jp_carousel( 'postCommentError', {
- field: 'jp-carousel-comment-form-author-field',
- error: jetpackCarouselStrings.no_comment_author,
- } );
- return;
- }
- }
- }
-
- $.ajax( {
- type: 'POST',
- url: jetpackCarouselStrings.ajaxurl,
- data: ajaxData,
- dataType: 'json',
- success: function( response /*, status, xhr*/ ) {
- if ( 'approved' === response.comment_status ) {
- $( '#jp-carousel-comment-post-results' )
- .slideUp( 'fast' )
- .html(
- '<span class="jp-carousel-comment-post-success">' +
- jetpackCarouselStrings.comment_approved +
- '</span>'
- )
- .slideDown( 'fast' );
- } else if ( 'unapproved' === response.comment_status ) {
- $( '#jp-carousel-comment-post-results' )
- .slideUp( 'fast' )
- .html(
- '<span class="jp-carousel-comment-post-success">' +
- jetpackCarouselStrings.comment_unapproved +
- '</span>'
- )
- .slideDown( 'fast' );
- } else {
- // 'deleted', 'spam', false
- $( '#jp-carousel-comment-post-results' )
- .slideUp( 'fast' )
- .html(
- '<span class="jp-carousel-comment-post-error">' +
- jetpackCarouselStrings.comment_post_error +
- '</span>'
- )
- .slideDown( 'fast' );
- }
- gallery.jp_carousel( 'clearCommentTextAreaValue' );
- gallery.jp_carousel( 'getComments', {
- attachment_id: attachment_id,
- offset: 0,
- clear: true,
- } );
- $( '#jp-carousel-comment-form-button-submit' ).val(
- jetpackCarouselStrings.post_comment
- );
- $( '#jp-carousel-comment-form-spinner' ).spin( false );
- },
- error: function(/*xhr, status, error*/) {
- // TODO: Add error handling and display here
- gallery.jp_carousel( 'postCommentError', {
- field: 'jp-carousel-comment-form-comment-field',
- error: jetpackCarouselStrings.comment_post_error,
- } );
- return;
- },
- } );
- }
- } else if ( ! target.parents( '.jp-carousel-info' ).length ) {
- container.jp_carousel( 'next' );
- }
- } )
- .bind( 'jp_carousel.afterOpen', function() {
- $( window ).bind( 'keydown', keyListener );
- $( window ).bind( 'resize', resizeListener );
- gallery.opened = true;
-
- resizeListener();
- } )
- .bind( 'jp_carousel.beforeClose', function() {
- var scroll = $( window ).scrollTop();
-
- $( window ).unbind( 'keydown', keyListener );
- $( window ).unbind( 'resize', resizeListener );
- $( window ).scrollTop( scroll );
- $( '.jp-carousel-previous-button' ).hide();
- $( '.jp-carousel-next-button' ).hide();
- // Set height to original value
- // Fix some themes where closing carousel brings view back to top
- $( 'html' ).css( 'height', '' );
- } )
- .bind( 'jp_carousel.afterClose', function() {
- if ( window.location.hash && history.back ) {
- history.back();
- }
- last_known_location_hash = '';
- gallery.opened = false;
- } )
- .on( 'transitionend.jp-carousel ', '.jp-carousel-slide', function( e ) {
- // If the movement transitions take more than twice the allotted time, disable them.
- // There is some wiggle room in the 2x, since some of that time is taken up in
- // JavaScript, setting up the transition and calling the events.
- if ( 'transform' === e.originalEvent.propertyName ) {
- var transitionMultiplier =
- ( Date.now() - transitionBegin ) / 1000 / e.originalEvent.elapsedTime;
-
- container.off( 'transitionend.jp-carousel' );
-
- if ( transitionMultiplier >= 2 ) {
- $( '.jp-carousel-transitions' ).removeClass( 'jp-carousel-transitions' );
- }
- }
+ function emitEvent( el, type, detail ) {
+ var e;
+ try {
+ e = new CustomEvent( type, {
+ bubbles: true,
+ cancelable: true,
+ detail: detail || null,
} );
+ } catch ( err ) {
+ e = document.createEvent( 'CustomEvent' );
+ e.initCustomEvent( type, true, true, detail || null );
+ }
+ el.dispatchEvent( e );
+ }
- $( '.jp-carousel-wrap' ).touchwipe( {
- wipeLeft: function( e ) {
- e.preventDefault();
- gallery.jp_carousel( 'next' );
- },
- wipeRight: function( e ) {
- e.preventDefault();
- gallery.jp_carousel( 'previous' );
- },
- preventDefaultEvents: false,
- } );
+ // From: https://easings.net/#easeInOutQuad
+ function easeInOutQuad( num ) {
+ return num < 0.5 ? 2 * num * num : 1 - Math.pow( -2 * num + 2, 2 ) / 2;
+ }
- nextButton.add( previousButton ).click( function( e ) {
- e.preventDefault();
- e.stopPropagation();
- if ( nextButton.is( this ) ) {
- gallery.jp_carousel( 'next' );
- } else {
- gallery.jp_carousel( 'previous' );
- }
- } );
+ 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;
}
- };
- var processSingleImageGallery = function() {
- // process links that contain img tag with attribute data-attachment-id
- $( 'a img[data-attachment-id]' ).each( function() {
- var container = $( this ).parent();
+ function isTouch() {
+ return (
+ 'ontouchstart' in window || ( window.DocumentTouch && document instanceof DocumentTouch )
+ );
+ }
- // skip if image was already added to gallery by shortcode
- if ( container.parent( '.gallery-icon' ).length ) {
+ function scrollToElement( el, container, callback ) {
+ if ( ! el || ! container ) {
+ if ( callback ) {
+ return callback();
+ }
return;
}
- // skip if the container is not a link
- if ( 'undefined' === typeof $( container ).attr( 'href' ) ) {
- 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 valid = false;
+ 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 );
- // if link points to 'Media File' (ignoring GET parameters) and flag is set allow it
- if (
- $( container )
- .attr( 'href' )
- .split( '?' )[ 0 ] ===
- $( this )
- .attr( 'data-orig-file' )
- .split( '?' )[ 0 ] &&
- 1 === Number( jetpackCarouselStrings.single_image_gallery_media_file )
- ) {
- valid = true;
+ function stopScroll() {
+ isScrolling = false;
}
- // if link points to 'Attachment Page' allow it
- if ( $( container ).attr( 'href' ) === $( this ).attr( 'data-permalink' ) ) {
- valid = true;
+ 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 );
}
- // links to 'Custom URL' or 'Media File' when flag not set are not valid
- if ( ! valid ) {
- return;
+ // Allow scroll to be cancelled by user interaction.
+ container.addEventListener( 'wheel', stopScroll );
+ runScroll();
+ }
+
+ function getJSONAttribute( el, attr ) {
+ if ( ! el || ! el.hasAttribute( attr ) ) {
+ return undefined;
}
- // make this node a gallery recognizable by event listener above
- $( container ).addClass( 'single-image-gallery' );
- // blog_id is needed to allow posting comments to correct blog
- $( container ).data( 'carousel-extra', {
- blog_id: Number( jetpackCarouselStrings.blog_id ),
- } );
- } );
- };
-
- var methods = {
- testForData: function( gallery ) {
- gallery = $( gallery ); // make sure we have it as a jQuery object.
- return ! ( ! gallery.length || ! gallery.data( 'carousel-extra' ) );
- },
-
- testIfOpened: function() {
- return !! (
- 'undefined' !== typeof gallery &&
- 'undefined' !== typeof gallery.opened &&
- gallery.opened
- );
- },
-
- openOrSelectSlide: function( index ) {
- // The `open` method triggers an asynchronous effect, so we will get an
- // error if we try to use `open` then `selectSlideAtIndex` immediately
- // after it. We can only use `selectSlideAtIndex` if the carousel is
- // already open.
- if ( ! $( this ).jp_carousel( 'testIfOpened' ) ) {
- // The `open` method selects the correct slide during the
- // initialization.
- $( this ).jp_carousel( 'open', { start_index: index } );
- } else {
- gallery.jp_carousel( 'selectSlideAtIndex', index );
+ try {
+ return JSON.parse( el.getAttribute( attr ) );
+ } catch ( e ) {
+ return undefined;
}
- },
+ }
- open: function( options ) {
- var settings = {
- items_selector:
- '.gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id], img[data-attachment-id]',
- start_index: 0,
- },
- data = $( this ).data( 'carousel-extra' );
+ function convertToPlainText( html ) {
+ var dummy = document.createElement( 'div' );
+ dummy.textContent = html;
+ return dummy.innerHTML;
+ }
- if ( ! data ) {
- return; // don't run if the default gallery functions weren't used
+ 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;
+ }
}
+ }
- prepareGallery( data );
+ function disableKeyboardNavigation() {
+ isUserTyping = true;
+ }
- if ( gallery.jp_carousel( 'testIfOpened' ) ) {
- return; // don't open if already opened
+ 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;
+ }
}
+ }
- // make sure to stop the page from scrolling behind the carousel overlay, so we don't trigger
- // infiniscroll for it when enabled (Reader, theme infiniscroll, etc).
- originalOverflow = $( 'body' ).css( 'overflow' );
- $( 'body' ).css( 'overflow', 'hidden' );
- // prevent html from overflowing on some of the new themes.
- originalHOverflow = $( 'html' ).css( 'overflow' );
- $( 'html' ).css( 'overflow', 'hidden' );
- scrollPos = $( window ).scrollTop();
+ 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 );
+ }
+ } );
- container.data( 'carousel-extra', data );
+ 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 ) {
+ return;
+ } else {
+ 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 );
+ } else if ( ! domUtil.closest( target, '.jp-carousel-info' ) ) {
+ return;
+ }
+ } );
- return this.each( function() {
- // If options exist, lets merge them
- // with our default settings
- var $this = $( this );
+ window.addEventListener( 'keydown', handleKeyboardEvent );
- if ( options ) {
- $.extend( settings, options );
- }
- if ( -1 === settings.start_index ) {
- settings.start_index = 0; //-1 returned if can't find index, so start from beginning
- }
+ carousel.overlay.addEventListener( 'jp_carousel.afterOpen', function () {
+ enableKeyboardNavigation();
- container.trigger( 'jp_carousel.beforeOpen' ).fadeIn( 'fast', function() {
- container.trigger( 'jp_carousel.afterOpen' );
- gallery
- .jp_carousel(
- 'initSlides',
- $this.find( settings.items_selector ),
- settings.start_index
- )
- .jp_carousel( 'selectSlideAtIndex', settings.start_index );
+ // 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' ) );
} );
- gallery.html( '' );
- } );
- },
- selectSlideAtIndex: function( index ) {
- var slides = this.jp_carousel( 'slides' ),
- selected = slides.eq( index );
+ 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;
+ } );
- if ( 0 === selected.length ) {
- selected = slides.eq( 0 );
+ // Prevent native browser zooming
+ carousel.overlay.addEventListener( 'touchstart', function ( e ) {
+ if ( e.touches.length > 1 ) {
+ e.preventDefault();
+ }
+ } );
}
+ }
- gallery.jp_carousel( 'selectSlide', selected, false );
- return this;
- },
+ function handleCommentLoginClick() {
+ var slide = carousel.currentSlide;
+ var attachmentId = slide ? slide.attrs.attachmentId : '0';
- close: function() {
- // make sure to let the page scroll again
- $( 'body' ).css( 'overflow', originalOverflow );
- $( 'html' ).css( 'overflow', originalHOverflow );
- this.jp_carousel( 'clearCommentTextAreaValue' );
- return container.trigger( 'jp_carousel.beforeClose' ).fadeOut( 'fast', function() {
- container.trigger( 'jp_carousel.afterClose' );
- $( window ).scrollTop( scrollPos );
- } );
- },
+ window.location.href = jetpackCarouselStrings.login_url + '%23jp-carousel-' + attachmentId;
+ }
- next: function() {
- this.jp_carousel( 'previousOrNext', 'nextSlide' );
- },
+ 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 = '<span class="' + elClass + '">' + msg + '</span>';
+ 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 );
+ }
- previous: function() {
- this.jp_carousel( 'previousOrNext', 'prevSlide' );
- },
+ function handleCommentFormClick( e ) {
+ var target = e.target;
+ var data = domUtil.getJSONAttribute( carousel.container, 'data-carousel-extra' ) || {};
+ var attachmentId = carousel.currentSlide.attrs.attachmentId;
- previousOrNext: function( slideSelectionMethodName ) {
- if ( ! this.jp_carousel( 'hasMultipleImages' ) ) {
- return false;
- }
+ var wrapper = document.querySelector( '#jp-carousel-comment-form-submit-and-info-wrapper' );
+ var spinner = document.querySelector( '#jp-carousel-comment-form-spinner' );
+ var submit = document.querySelector( '#jp-carousel-comment-form-button-submit' );
+ var form = document.querySelector( '#jp-carousel-comment-form' );
- var slide = gallery.jp_carousel( slideSelectionMethodName );
+ 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' );
- if ( slide ) {
- container.animate( { scrollTop: 0 }, 'fast' );
- this.jp_carousel( 'clearCommentTextAreaValue' );
- this.jp_carousel( 'selectSlide', slide );
+ 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 ( error ) {
+ 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 );
}
- },
+ }
- selectedSlide: function() {
- return this.find( '.selected' );
- },
+ /**
+ * Handles clicks to icons and other action elements in the icon container.
+ * @param {MouseEvent|TouchEvent|KeyBoardEvent} Event object.
+ */
+ function handleFooterElementClick( e ) {
+ e.preventDefault();
- setSlidePosition: function( x ) {
- transitionBegin = Date.now();
+ 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' );
- return this.css( {
- '-webkit-transform': 'translate3d(' + x + 'px,0,0)',
- '-moz-transform': 'translate3d(' + x + 'px,0,0)',
- '-ms-transform': 'translate(' + x + 'px,0)',
- '-o-transform': 'translate(' + x + 'px,0)',
- transform: 'translate3d(' + x + 'px,0,0)',
- } );
- },
-
- updateSlidePositions: function( animate ) {
- var current = this.jp_carousel( 'selectedSlide' ),
- galleryWidth = gallery.width(),
- currentWidth = current.width(),
- previous = gallery.jp_carousel( 'prevSlide' ),
- next = gallery.jp_carousel( 'nextSlide' ),
- previousPrevious = previous.prev(),
- nextNext = next.next(),
- left = Math.floor( ( galleryWidth - currentWidth ) * 0.5 );
-
- current.jp_carousel( 'setSlidePosition', left ).show();
-
- // minimum width
- gallery.jp_carousel( 'fitInfo', animate );
-
- // prep the slides
- var direction = lastSelectedSlide.is( current.prevAll() ) ? 1 : -1;
-
- // Since we preload the `previousPrevious` and `nextNext` slides, we need
- // to make sure they technically visible in the DOM, but invisible to the
- // user. To hide them from the user, we position them outside the edges
- // of the window.
- //
- // This section of code only applies when there are more than three
- // slides. Otherwise, the `previousPrevious` and `nextNext` slides will
- // overlap with the `previous` and `next` slides which must be visible
- // regardless.
- if ( 1 === direction ) {
- if ( ! nextNext.is( previous ) ) {
- nextNext.jp_carousel( 'setSlidePosition', galleryWidth + next.width() ).show();
+ function handleInfoToggle() {
+ if ( commentsIcon ) {
+ commentsIcon.classList.remove( 'jp-carousel-selected' );
}
+ infoIcon.classList.toggle( 'jp-carousel-selected' );
- if ( ! previousPrevious.is( next ) ) {
- previousPrevious
- .jp_carousel( 'setSlidePosition', -previousPrevious.width() - currentWidth )
- .show();
+ if ( commentsContainer ) {
+ commentsContainer.classList.remove( 'jp-carousel-show' );
}
- } else {
- if ( ! nextNext.is( previous ) ) {
- nextNext.jp_carousel( 'setSlidePosition', galleryWidth + currentWidth ).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' );
+ }
}
}
- previous
- .jp_carousel( 'setSlidePosition', Math.floor( -previous.width() + screenPadding * 0.75 ) )
- .show();
- next
- .jp_carousel( 'setSlidePosition', Math.ceil( galleryWidth - screenPadding * 0.75 ) )
- .show();
- },
-
- selectSlide: function( slide, animate ) {
- lastSelectedSlide = this.find( '.selected' ).removeClass( 'selected' );
-
- var slides = gallery.jp_carousel( 'slides' ).css( { position: 'fixed' } ),
- current = $( slide )
- .addClass( 'selected' )
- .css( { position: 'relative' } ),
- attachmentId = current.data( 'attachment-id' ),
- previous = gallery.jp_carousel( 'prevSlide' ),
- next = gallery.jp_carousel( 'nextSlide' ),
- previousPrevious = previous.prev(),
- nextNext = next.next(),
- animated,
- captionHtml;
-
- // center the main image
- gallery.jp_carousel( 'loadFullImage', current );
-
- caption.hide();
-
- if ( next.length === 0 && slides.length <= 2 ) {
- $( '.jp-carousel-next-button' ).hide();
- } else {
- $( '.jp-carousel-next-button' ).show();
- }
+ function handleCommentToggle() {
+ if ( infoIcon ) {
+ infoIcon.classList.remove( 'jp-carousel-selected' );
+ }
+ commentsIcon.classList.toggle( 'jp-carousel-selected' );
- if ( previous.length === 0 && slides.length <= 2 ) {
- $( '.jp-carousel-previous-button' ).hide();
- } else {
- $( '.jp-carousel-previous-button' ).show();
+ 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' );
+ }
+ }
}
- animated = current
- .add( previous )
- .add( previousPrevious )
- .add( next )
- .add( nextNext )
- .jp_carousel( 'loadSlide' );
+ 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 );
+ }
+ }
- // slide the whole view to the x we want
- slides.not( animated ).hide();
+ 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 );
+ }
+ }
+ }
- gallery.jp_carousel( 'updateSlidePositions', animate );
+ 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;
- container.trigger( 'jp_carousel.selectSlide', [ current ] );
+ // Skip if image was already added to gallery by shortcode.
+ if ( container.classList.contains( 'gallery-icon' ) ) {
+ return;
+ }
- gallery.jp_carousel( 'getTitleDesc', {
- title: current.data( 'title' ),
- desc: current.data( 'desc' ),
- } );
+ // Skip if image is part of a gallery.
+ if ( domUtil.closest( container, galleryItemSelector ) ) {
+ return;
+ }
- var imageMeta = current.data( 'image-meta' );
- gallery.jp_carousel( 'updateExif', imageMeta );
- gallery.jp_carousel( 'updateFullSizeLink', current );
- gallery.jp_carousel( 'updateMap', imageMeta );
- gallery.jp_carousel( 'testCommentsOpened', current.data( 'comments-opened' ) );
- gallery.jp_carousel( 'getComments', {
- attachment_id: attachmentId,
- offset: 0,
- clear: true,
- } );
- $( '#jp-carousel-comment-post-results' ).slideUp();
+ // Skip if the parent is not actually a link.
+ if ( ! link.hasAttribute( 'href' ) ) {
+ return;
+ }
- // $('<div />').text(sometext).html() is a trick to go to HTML to plain
- // text (including HTML entities decode, etc)
- if ( current.data( 'caption' ) ) {
- captionHtml = $( '<div />' )
- .text( current.data( 'caption' ) )
- .html();
+ var valid = false;
+ // If link points to 'Media File' (ignoring GET parameters) and flag is set, allow it.
if (
- captionHtml ===
- $( '<div />' )
- .text( current.data( 'title' ) )
- .html()
+ link.getAttribute( 'href' ).split( '?' )[ 0 ] ===
+ image.getAttribute( 'data-orig-file' ).split( '?' )[ 0 ] &&
+ Number( jetpackCarouselStrings.single_image_gallery_media_file ) === 1
) {
- $( '.jp-carousel-titleanddesc-title' )
- .fadeOut( 'fast' )
- .empty();
+ valid = true;
}
- if (
- captionHtml ===
- $( '<div />' )
- .text( current.data( 'desc' ) )
- .html()
- ) {
- $( '.jp-carousel-titleanddesc-desc' )
- .fadeOut( 'fast' )
- .empty();
+ // If link points to 'Attachment Page', allow it.
+ if ( link.getAttribute( 'href' ) === image.getAttribute( 'data-permalink' ) ) {
+ valid = true;
}
- caption.html( current.data( 'caption' ) ).fadeIn( 'slow' );
- } else {
- caption.fadeOut( 'fast' ).empty();
- }
-
- // 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();
- }
-
- // Load the images for the next and previous slides.
- $( next )
- .add( previous )
- .each( function() {
- gallery.jp_carousel( 'loadFullImage', $( this ) );
- } );
+ // Links to 'Custom URL' or 'Media File' when flag is not set are not valid.
+ if ( ! valid ) {
+ return;
+ }
- window.location.hash = last_known_location_hash = '#jp-carousel-' + attachmentId;
- },
+ // 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 ),
+ } )
+ );
+ } );
+ }
- slides: function() {
- return this.find( '.jp-carousel-slide' );
- },
+ function testForData( el ) {
+ return !! ( el && el.getAttribute( 'data-carousel-extra' ) );
+ }
- slideDimensions: function() {
- return {
- width: $( window ).width() - screenPadding * 2,
- height: Math.floor( ( $( window ).height() / 100 ) * proportion - 60 ),
- };
- },
-
- loadSlide: function() {
- return this.each( function() {
- var slide = $( this );
- slide.find( 'img' ).one( 'load', function() {
- // set the width/height of the image if it's too big
- slide.jp_carousel( 'fitSlide', false );
- } );
- } );
- },
-
- bestFit: function() {
- var max = gallery.jp_carousel( 'slideDimensions' ),
- orig = this.jp_carousel( 'originalDimensions' ),
- orig_ratio = orig.width / orig.height,
- w_ratio = 1,
- h_ratio = 1,
- width,
- height;
-
- if ( orig.width > max.width ) {
- w_ratio = max.width / orig.width;
- }
- if ( orig.height > max.height ) {
- h_ratio = max.height / orig.height;
- }
-
- if ( w_ratio < h_ratio ) {
- width = max.width;
- height = Math.floor( width / orig_ratio );
- } else if ( h_ratio < w_ratio ) {
- height = max.height;
- width = Math.floor( height * orig_ratio );
+ function openOrSelectSlide( gal, index ) {
+ if ( ! carousel.isOpen ) {
+ // The `open` method selects the correct slide during the initialization.
+ loadSwiper( gal, { startIndex: index } );
} else {
- width = orig.width;
- height = orig.height;
+ selectSlideAtIndex( index );
+ // We have to force swiper to slide to the index onHasChange.
+ swiper.slideTo( index + 1 );
}
+ }
- return {
- width: width,
- height: height,
- };
- },
-
- fitInfo: function(/*animated*/) {
- var current = this.jp_carousel( 'selectedSlide' ),
- size = current.jp_carousel( 'bestFit' );
+ function selectSlideAtIndex( index ) {
+ if ( ! index || index < 0 || index > carousel.slides.length ) {
+ index = 0;
+ }
+ carousel.currentSlide = carousel.slides[ index ];
- photo_info.css( {
- left: Math.floor( ( info.width() - size.width ) * 0.5 ),
- width: Math.floor( size.width ),
- } );
+ var current = carousel.currentSlide;
+ var attachmentId = current.attrs.attachmentId;
+ var infoIcon = carousel.info.querySelector( '.jp-carousel-icon-info' );
+ var commentsIcon = carousel.info.querySelector( '.jp-carousel-icon-comments' );
- return this;
- },
+ // If the comment/info section is toggled open, it's kept open, but scroll to top of the next slide.
+ if (
+ ( infoIcon && infoIcon.classList.contains( 'jp-carousel-selected' ) ) ||
+ ( commentsIcon && commentsIcon.classList.contains( 'jp-carousel-selected' ) )
+ ) {
+ if ( carousel.overlay.scrollTop !== 0 ) {
+ domUtil.scrollToElement( carousel.overlay, carousel.overlay );
+ }
+ }
- fitMeta: function( animated ) {
- var newInfoTop = {
- top: Math.floor( ( $( window ).height() / 100 ) * proportion + 5 ) + 'px',
- };
- var newLeftWidth = { width: info.width() - ( imageMeta.width() + 80 ) + 'px' };
+ loadFullImage( carousel.slides[ index ] );
- if ( animated ) {
- info.animate( newInfoTop );
- leftColWrapper.animate( newLeftWidth );
- } else {
- info.animate( newInfoTop );
- leftColWrapper.css( newLeftWidth );
+ if (
+ Number( jetpackCarouselStrings.display_background_image ) === 1 &&
+ ! carousel.slides[ index ].backgroundImage
+ ) {
+ loadBackgroundImage( carousel.slides[ index ] );
}
- },
-
- fitSlide: function(/*animated*/) {
- return this.each( function() {
- var $this = $( this ),
- dimensions = $this.jp_carousel( 'bestFit' ),
- method = 'css',
- max = gallery.jp_carousel( 'slideDimensions' );
- dimensions.left = 0;
- dimensions.top = Math.floor( ( max.height - dimensions.height ) * 0.5 ) + 40;
- $this[ method ]( dimensions );
+ domUtil.hide( carousel.caption );
+ updateTitleCaptionAndDesc( {
+ caption: current.attrs.caption,
+ title: current.attrs.title,
+ desc: current.attrs.desc,
} );
- },
- texturize: function( text ) {
- text = '' + text; // make sure we get a string. Title "1" came in as int 1, for example, which did not support .replace().
- text = text
- .replace( /'/g, '&#8217;' )
- .replace( /&#039;/g, '&#8217;' )
- .replace( /[\u2019]/g, '&#8217;' );
- text = text
- .replace( /"/g, '&#8221;' )
- .replace( /&#034;/g, '&#8221;' )
- .replace( /&quot;/g, '&#8221;' )
- .replace( /[\u201D]/g, '&#8221;' );
- text = text.replace( /([\w]+)=&#[\d]+;(.+?)&#[\d]+;/g, '$1="$2"' ); // untexturize allowed HTML tags params double-quotes
- return $.trim( text );
- },
+ var imageMeta = carousel.slides[ index ].attrs.imageMeta;
+ updateExif( imageMeta );
+ updateFullSizeLink( current );
- initSlides: function( items, start_index ) {
- if ( items.length < 2 ) {
- $( '.jp-carousel-next-button, .jp-carousel-previous-button' ).hide();
- } else {
- $( '.jp-carousel-next-button, .jp-carousel-previous-button' ).show();
- }
-
- // Calculate the new src.
- items.each( function(/*i*/) {
- var src_item = $( this ),
- orig_size = src_item.data( 'orig-size' ) || '',
- max = gallery.jp_carousel( 'slideDimensions' ),
- parts = orig_size.split( ',' ),
- medium_file = src_item.data( 'medium-file' ) || '',
- large_file = src_item.data( 'large-file' ) || '',
- src;
- orig_size = { width: parseInt( parts[ 0 ], 10 ), height: parseInt( parts[ 1 ], 10 ) };
-
- src = src_item.data( 'orig-file' );
-
- src = gallery.jp_carousel( 'selectBestImageSize', {
- orig_file: src,
- orig_width: orig_size.width,
- orig_height: orig_size.height,
- max_width: max.width,
- max_height: max.height,
- medium_file: medium_file,
- large_file: large_file,
- } );
+ if ( Number( jetpackCarouselStrings.display_comments ) === 1 ) {
+ testCommentsOpened( carousel.slides[ index ].attrs.commentsOpened );
+ fetchComments( attachmentId );
+ domUtil.hide( carousel.info.querySelector( '#jp-carousel-comment-post-results' ) );
+ }
- // Set the final src
- $( this ).data( 'gallery-src', src );
- } );
+ // Update pagination in footer.
+ var pagination = carousel.info.querySelector( '.jp-carousel-pagination' );
+ if ( pagination && carousel.slides.length > 5 ) {
+ var currentPage = index + 1;
+ pagination.innerHTML = '<span>' + currentPage + ' / ' + carousel.slides.length + '</span>';
+ }
- // If the start_index is not 0 then preload the clicked image first.
- if ( 0 !== start_index ) {
- $( '<img/>' )[ 0 ].src = $( items[ start_index ] ).data( 'gallery-src' );
+ // 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();
}
- var useInPageThumbnails =
- items.first().closest( '.tiled-gallery.type-rectangular' ).length > 0;
+ pageview( attachmentId );
- // create the 'slide'
- items.each( function( i ) {
- var src_item = $( this ),
- attachment_id = src_item.data( 'attachment-id' ) || 0,
- comments_opened = src_item.data( 'comments-opened' ) || 0,
- image_meta = src_item.data( 'image-meta' ) || {},
- orig_size = src_item.data( 'orig-size' ) || '',
- thumb_size = { width: src_item[ 0 ].naturalWidth, height: src_item[ 0 ].naturalHeight },
- title = src_item.data( 'image-title' ) || '',
- description = src_item.data( 'image-description' ) || '',
- caption =
- src_item
- .parents( '.gallery-item' )
- .find( '.gallery-caption' )
- .html() || '',
- src = src_item.data( 'gallery-src' ) || '',
- medium_file = src_item.data( 'medium-file' ) || '',
- large_file = src_item.data( 'large-file' ) || '',
- orig_file = src_item.data( 'orig-file' ) || '';
-
- var tiledCaption = src_item
- .parents( 'div.tiled-gallery-item' )
- .find( 'div.tiled-gallery-caption' )
- .html();
- if ( tiledCaption ) {
- caption = tiledCaption;
- }
+ window.location.hash = lastKnownLocationHash = '#jp-carousel-' + attachmentId;
+ }
- if ( attachment_id && orig_size.length ) {
- title = gallery.jp_carousel( 'texturize', title );
- description = gallery.jp_carousel( 'texturize', description );
- caption = gallery.jp_carousel( 'texturize', caption );
-
- // Initially, the image is a 1x1 transparent gif. The preview is shown as a background image on the slide itself.
- var image = $( '<img/>' )
- .attr(
- 'src',
- 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
- )
- .css( 'width', '100%' )
- .css( 'height', '100%' );
-
- var slide = $(
- '<div class="jp-carousel-slide" itemprop="associatedMedia" itemscope itemtype="https://schema.org/ImageObject"></div>'
- )
- .hide()
- .css( {
- //'position' : 'fixed',
- left: i < start_index ? -1000 : gallery.width(),
- } )
- .append( image )
- .appendTo( gallery )
- .data( 'src', src )
- .data( 'title', title )
- .data( 'desc', description )
- .data( 'caption', caption )
- .data( 'attachment-id', attachment_id )
- .data( 'permalink', src_item.parents( 'a' ).attr( 'href' ) )
- .data( 'orig-size', orig_size )
- .data( 'comments-opened', comments_opened )
- .data( 'image-meta', image_meta )
- .data( 'medium-file', medium_file )
- .data( 'large-file', large_file )
- .data( 'orig-file', orig_file )
- .data( 'thumb-size', thumb_size );
- if ( useInPageThumbnails ) {
- // Use the image already loaded in the gallery as a preview.
- slide.data( 'preview-image', src_item.attr( 'src' ) ).css( {
- 'background-image': 'url("' + src_item.attr( 'src' ) + '")',
- 'background-size': '100% 100%',
- 'background-position': 'center center',
- } );
- }
+ function restoreScroll() {
+ window.scrollTo( window.scrollX || window.pageXOffset || 0, scrollPos || 0 );
+ }
- slide.jp_carousel( 'fitSlide', false );
- }
+ 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' );
} );
- return this;
- },
+ }
+
+ function calculateMaxSlideDimensions() {
+ return {
+ width: window.innerWidth,
+ height: window.innerHeight - 64, //subtract height of bottom info bar,
+ };
+ }
- selectBestImageSize: function( args ) {
- if ( 'object' !== typeof args ) {
+ function selectBestImageUrl( args ) {
+ if ( typeof args !== 'object' ) {
args = {};
}
- if ( 'undefined' === typeof args.orig_file ) {
+ if ( typeof args.origFile === 'undefined' ) {
return '';
}
- if ( 'undefined' === typeof args.orig_width || 'undefined' === typeof args.max_width ) {
- return args.orig_file;
+ if ( typeof args.origWidth === 'undefined' || typeof args.maxWidth === 'undefined' ) {
+ return args.origFile;
}
- if ( 'undefined' === typeof args.medium_file || 'undefined' === typeof args.large_file ) {
- return args.orig_file;
+ 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.large_file;
+ imageLinkParser.href = args.largeFile;
- var isPhotonUrl = /^i[0-2].wp.com$/i.test( imageLinkParser.hostname );
+ var isPhotonUrl = /^i[0-2]\.wp\.com$/i.test( imageLinkParser.hostname );
- var medium_size_parts = gallery.jp_carousel(
- 'getImageSizeParts',
- args.medium_file,
- args.orig_width,
- isPhotonUrl
- );
- var large_size_parts = gallery.jp_carousel(
- 'getImageSizeParts',
- args.large_file,
- args.orig_width,
- isPhotonUrl
- );
+ var mediumSizeParts = getImageSizeParts( args.mediumFile, args.origWidth, isPhotonUrl );
+ var largeSizeParts = getImageSizeParts( args.largeFile, args.origWidth, isPhotonUrl );
- var large_width = parseInt( large_size_parts[ 0 ], 10 ),
- large_height = parseInt( large_size_parts[ 1 ], 10 ),
- medium_width = parseInt( medium_size_parts[ 0 ], 10 ),
- medium_height = parseInt( medium_size_parts[ 1 ], 10 );
+ var largeWidth = parseInt( largeSizeParts[ 0 ], 10 );
+ var largeHeight = parseInt( largeSizeParts[ 1 ], 10 );
+ var mediumWidth = parseInt( mediumSizeParts[ 0 ], 10 );
+ var mediumHeight = parseInt( mediumSizeParts[ 1 ], 10 );
- // Assign max width and height.
- args.orig_max_width = args.max_width;
- args.orig_max_height = args.max_height;
+ 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 ( 'undefined' !== typeof window.devicePixelRatio && window.devicePixelRatio > 1 ) {
- args.max_width = args.max_width * window.devicePixelRatio;
- args.max_height = args.max_height * window.devicePixelRatio;
+ if ( typeof window.devicePixelRatio !== 'undefined' && window.devicePixelRatio > 1 ) {
+ args.maxWidth = args.maxWidth * window.devicePixelRatio;
+ args.maxHeight = args.maxHeight * window.devicePixelRatio;
}
- if ( large_width >= args.max_width || large_height >= args.max_height ) {
- return args.large_file;
+ if ( largeWidth >= args.maxWidth || largeHeight >= args.maxHeight ) {
+ return args.largeFile;
}
- if ( medium_width >= args.max_width || medium_height >= args.max_height ) {
- return args.medium_file;
+ if ( mediumWidth >= args.maxWidth || mediumHeight >= args.maxHeight ) {
+ return args.mediumFile;
}
if ( isPhotonUrl ) {
- // args.orig_file doesn't point to a Photon url, so in this case we use args.large_file
+ // 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.large_file.lastIndexOf( '?' );
- var origPhotonUrl = args.large_file;
- if ( -1 !== largeFileIndex ) {
- origPhotonUrl = args.large_file.substring( 0, largeFileIndex );
+ 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.orig_width > args.max_width || args.orig_height > args.max_height ) {
- origPhotonUrl += '?fit=' + args.orig_max_width + '%2C' + args.orig_max_height;
+ 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.orig_file;
- },
+ return args.origFile;
+ }
- getImageSizeParts: function( file, orig_width, isPhotonUrl ) {
+ function getImageSizeParts( file, origWidth, isPhotonUrl ) {
var size = isPhotonUrl
? file.replace( /.*=([\d]+%2C[\d]+).*$/, '$1' )
: file.replace( /.*-([\d]+x[\d]+)\..+$/, '$1' );
- var size_parts =
+ var sizeParts =
size !== file
? isPhotonUrl
? size.split( '%2C' )
: size.split( 'x' )
- : [ orig_width, 0 ];
+ : [ 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 ( '9999' === size_parts[ 0 ] ) {
- size_parts[ 0 ] = '0';
+ if ( sizeParts[ 0 ] === '9999' ) {
+ sizeParts[ 0 ] = '0';
}
- if ( '9999' === size_parts[ 1 ] ) {
- size_parts[ 1 ] = '0';
+ if ( sizeParts[ 1 ] === '9999' ) {
+ sizeParts[ 1 ] = '0';
}
- return size_parts;
- },
-
- originalDimensions: function() {
- var splitted = $( this )
- .data( 'orig-size' )
- .split( ',' );
- return { width: parseInt( splitted[ 0 ], 10 ), height: parseInt( splitted[ 1 ], 10 ) };
- },
-
- format: function( args ) {
- if ( 'object' !== typeof args ) {
- args = {};
- }
- if ( ! args.text || 'undefined' === typeof args.text ) {
- return;
- }
- if ( ! args.replacements || 'undefined' === typeof args.replacements ) {
- return args.text;
- }
- return args.text.replace( /{(\d+)}/g, function( match, number ) {
- return typeof args.replacements[ number ] !== 'undefined'
- ? args.replacements[ number ]
- : match;
- } );
- },
+ return sizeParts;
+ }
/**
* Returns a number in a fraction format that represents the shutter speed.
* @param Number speed
* @return String
*/
- shutterSpeed: function( speed ) {
+ function formatShutterSpeed( speed ) {
var denominator;
// round to one decimal if value > 1s by multiplying it by 10, rounding, then dividing by 10 again
@@ -1266,70 +982,102 @@ jQuery( document ).ready( function( $ ) {
denominator = Math.round( 1 / speed );
return '1/' + denominator + 's';
- },
+ }
- parseTitleDesc: function( value ) {
+ function parseTitleOrDesc( value ) {
if ( ! value.match( ' ' ) && value.match( '_' ) ) {
return '';
}
return value;
- },
+ }
- getTitleDesc: function( data ) {
- var title = '',
- desc = '',
- markup = '',
- target;
+ 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 );
+ }
- target = $( 'div.jp-carousel-titleanddesc', 'div.jp-carousel-wrap' );
- target.hide();
+ if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( title ) ) {
+ title = '';
+ }
- title = gallery.jp_carousel( 'parseTitleDesc', data.title ) || '';
- desc = gallery.jp_carousel( 'parseTitleDesc', data.desc ) || '';
+ if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( desc ) ) {
+ desc = '';
+ }
- if ( title.length || desc.length ) {
- // Convert from HTML to plain text (including HTML entities decode, etc)
- if (
- $( '<div />' )
- .html( title )
- .text() ===
- $( '<div />' )
- .html( desc )
- .text()
- ) {
- title = '';
+ if ( domUtil.stripHTML( title ) === domUtil.stripHTML( desc ) ) {
+ desc = '';
}
- markup = title.length
- ? '<div class="jp-carousel-titleanddesc-title">' + title + '</div>'
- : '';
- markup += desc.length
- ? '<div class="jp-carousel-titleanddesc-desc">' + desc + '</div>'
- : '';
+ if ( desc ) {
+ descriptionElement.innerHTML = desc;
+ domUtil.show( descriptionElement );
- target.html( markup ).fadeIn( 'slow' );
- }
+ 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 );
+ }
- $( 'div#jp-carousel-comment-form-container' ).css( 'margin-top', '20px' );
- $( 'div#jp-carousel-comments-loading' ).css( 'margin-top', '20px' );
- },
+ domUtil.show( titleElement );
+ }
+ }
+ }
// updateExif updates the contents of the exif UL (.jp-carousel-image-exif)
- updateExif: function( meta ) {
- if ( ! meta || 1 !== Number( jetpackCarouselStrings.display_exif ) ) {
+ function updateExif( meta ) {
+ if ( ! meta || Number( jetpackCarouselStrings.display_exif ) !== 1 ) {
return false;
}
- var $ul = $( "<ul class='jp-carousel-image-exif'></ul>" );
+ var ul = carousel.info.querySelector( '.jp-carousel-image-meta ul.jp-carousel-image-exif' );
+ var html = '';
- $.each( meta, function( key, val ) {
- if (
- 0 === parseFloat( val ) ||
- ! val.length ||
- -1 === $.inArray( key, $.makeArray( jetpackCarouselStrings.meta_data ) )
- ) {
- return;
+ 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 ) {
@@ -1337,517 +1085,635 @@ jQuery( document ).ready( function( $ ) {
val = val + 'mm';
break;
case 'shutter_speed':
- val = gallery.jp_carousel( 'shutterSpeed', val );
+ val = formatShutterSpeed( val );
break;
case 'aperture':
val = 'f/' + val;
break;
}
- $ul.append( '<li><h5>' + jetpackCarouselStrings[ key ] + '</h5>' + val + '</li>' );
- } );
+ html += '<li><h5>' + jetpackCarouselStrings[ key ] + '</h5>' + val + '</li>';
+ }
- // Update (replace) the content of the ul
- $( 'div.jp-carousel-image-meta ul.jp-carousel-image-exif' ).replaceWith( $ul );
- },
+ ul.innerHTML = html;
+ ul.style.removeProperty( 'display' );
+ }
- // updateFullSizeLink updates the contents of the jp-carousel-image-download link
- updateFullSizeLink: function( current ) {
- if ( ! current || ! current.data ) {
+ // Update the contents of the jp-carousel-image-download link
+ function updateFullSizeLink( currentSlide ) {
+ if ( ! currentSlide ) {
return false;
}
- var original,
- origSize = current.data( 'orig-size' ).split( ',' ),
- imageLinkParser = document.createElement( 'a' );
+ var original;
+ var origSize = [ currentSlide.attrs.origWidth, currentSlide.attrs.origHeight ];
+ var imageLinkParser = document.createElement( 'a' );
- imageLinkParser.href = current.data( 'src' ).replace( /\?.+$/, '' );
+ imageLinkParser.href = currentSlide.attrs.src.replace( /\?.+$/, '' );
// Is this a Photon URL?
- if ( imageLinkParser.hostname.match( /^i[\d]{1}.wp.com$/i ) !== null ) {
+ if ( imageLinkParser.hostname.match( /^i[\d]{1}\.wp\.com$/i ) !== null ) {
original = imageLinkParser.href;
} else {
- original = current.data( 'orig-file' ).replace( /\?.+$/, '' );
- }
-
- var permalink = $(
- '<a>' +
- gallery.jp_carousel( 'format', {
- text: jetpackCarouselStrings.download_original,
- replacements: origSize,
- } ) +
- '</a>'
- )
- .addClass( 'jp-carousel-image-download' )
- .attr( 'href', original )
- .attr( 'target', '_blank' );
-
- // Update (replace) the content of the anchor
- $( 'div.jp-carousel-image-meta a.jp-carousel-image-download' ).replaceWith( permalink );
- },
-
- updateMap: function( meta ) {
- if (
- ! meta.latitude ||
- ! meta.longitude ||
- 1 !== Number( jetpackCarouselStrings.display_geo )
- ) {
- return;
+ original = currentSlide.attrs.origFile.replace( /\?.+$/, '' );
}
- var latitude = meta.latitude,
- longitude = meta.longitude,
- $metabox = $( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ),
- $mapbox = $( '<div></div>' ),
- style =
- '&scale=2&style=feature:all|element:all|invert_lightness:true|hue:0x0077FF|saturation:-50|lightness:-5|gamma:0.91';
-
- $mapbox
- .addClass( 'jp-carousel-image-map' )
- .html(
- '<img width="154" height="154" src="https://maps.googleapis.com/maps/api/staticmap?\
- center=' +
- latitude +
- ',' +
- longitude +
- '&\
- zoom=8&\
- size=154x154&\
- sensor=false&\
- markers=size:medium%7Ccolor:blue%7C' +
- latitude +
- ',' +
- longitude +
- style +
- '" class="gmap-main" />\
- \
- <div class="gmap-topright"><div class="imgclip"><img width="175" height="154" src="https://maps.googleapis.com/maps/api/staticmap?\
- center=' +
- latitude +
- ',' +
- longitude +
- '&\
- zoom=3&\
- size=175x154&\
- sensor=false&\
- markers=size:small%7Ccolor:blue%7C' +
- latitude +
- ',' +
- longitude +
- style +
- '"c /></div></div>\
- \
- '
- )
- .prependTo( $metabox );
- },
-
- testCommentsOpened: function( opened ) {
- if ( 1 === parseInt( opened, 10 ) ) {
- $( '.jp-carousel-buttons' ).fadeIn( 'fast' );
- commentForm.fadeIn( 'fast' );
+ var downloadText = carousel.info.querySelector( '.jp-carousel-download-text' );
+ var permalink = carousel.info.querySelector( '.jp-carousel-image-download' );
+
+ downloadText.innerHTML = util.applyReplacements(
+ jetpackCarouselStrings.download_original,
+ origSize
+ );
+ permalink.setAttribute( 'href', original );
+ permalink.style.removeProperty( 'display' );
+ }
+
+ function testCommentsOpened( opened ) {
+ var commentForm = carousel.container.querySelector( '.jp-carousel-comment-form-container' );
+ var isOpened = parseInt( opened, 10 ) === 1;
+
+ if ( isOpened ) {
+ domUtil.fadeIn( commentForm );
} else {
- $( '.jp-carousel-buttons' ).fadeOut( 'fast' );
- commentForm.fadeOut( 'fast' );
+ domUtil.fadeOut( commentForm );
}
- },
+ }
+
+ function fetchComments( attachmentId, offset ) {
+ var shouldClear = offset === undefined;
+ var commentsIndicator = carousel.info.querySelector(
+ '.jp-carousel-icon-comments .jp-carousel-has-comments-indicator'
+ );
+
+ commentsIndicator.classList.remove( 'jp-carousel-show' );
- getComments: function( args ) {
clearInterval( commentInterval );
- if ( 'object' !== typeof args ) {
+ if ( ! attachmentId ) {
return;
}
- if ( 'undefined' === typeof args.attachment_id || ! args.attachment_id ) {
- return;
+ if ( ! offset || offset < 1 ) {
+ offset = 0;
}
- if ( ! args.offset || 'undefined' === typeof args.offset || args.offset < 1 ) {
- args.offset = 0;
+ var comments = carousel.info.querySelector( '.jp-carousel-comments' );
+ var commentsLoading = carousel.info.querySelector( '#jp-carousel-comments-loading' );
+ domUtil.show( commentsLoading );
+
+ if ( shouldClear ) {
+ domUtil.hide( comments );
+ comments.innerHTML = '';
}
- var comments = $( '.jp-carousel-comments' ),
- commentsLoading = $( '#jp-carousel-comments-loading' ).show();
+ var xhr = new XMLHttpRequest();
+ var url =
+ jetpackCarouselStrings.ajaxurl +
+ '?action=get_attachment_comments' +
+ '&nonce=' +
+ jetpackCarouselStrings.nonce +
+ '&id=' +
+ attachmentId +
+ '&offset=' +
+ offset;
+ xhr.open( 'GET', url );
+ xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
- if ( args.clear ) {
- comments.hide().empty();
- }
+ var onError = function () {
+ domUtil.fadeIn( comments );
+ domUtil.fadeOut( commentsLoading );
+ };
- $.ajax( {
- type: 'GET',
- url: jetpackCarouselStrings.ajaxurl,
- dataType: 'json',
- data: {
- action: 'get_attachment_comments',
- nonce: jetpackCarouselStrings.nonce,
- id: args.attachment_id,
- offset: args.offset,
- },
- success: function( data /*, status, xhr*/ ) {
- if ( args.clear ) {
- comments.fadeOut( 'fast' ).empty();
- }
+ xhr.onload = function () {
+ // Ignore the results if they arrive late and we're now on a different slide.
+ if (
+ ! carousel.currentSlide ||
+ carousel.currentSlide.attrs.attachmentId !== attachmentId
+ ) {
+ return;
+ }
- $( data ).each( function() {
- var comment = $( '<div></div>' )
- .addClass( 'jp-carousel-comment' )
- .attr( 'id', 'jp-carousel-comment-' + this[ 'id' ] )
- .html(
- '<div class="comment-gravatar">' +
- this[ 'gravatar_markup' ] +
- '</div>' +
- '<div class="comment-author">' +
- this[ 'author_markup' ] +
- '</div>' +
- '<div class="comment-date">' +
- this[ 'date_gmt' ] +
- '</div>' +
- '<div class="comment-content">' +
- this[ 'content' ] +
- '</div>'
- );
- comments.append( comment );
-
- // Set the interval to check for a new page of comments.
- clearInterval( commentInterval );
- commentInterval = setInterval( function() {
- if (
- $( '.jp-carousel-overlay' ).height() - 150 <
- $( '.jp-carousel-wrap' ).scrollTop() + $( window ).height()
- ) {
- gallery.jp_carousel( 'getComments', {
- attachment_id: args.attachment_id,
- offset: args.offset + 10,
- clear: false,
- } );
- clearInterval( commentInterval );
- }
- }, 300 );
- } );
+ var isSuccess = xhr.status >= 200 && xhr.status < 300;
+ var data;
+ try {
+ data = JSON.parse( xhr.responseText );
+ } catch ( e ) {
+ // Do nothing.
+ }
- // Verify (late) that the user didn't repeatldy click the arrows really fast, in which case the requested
- // attachment id might no longer match the current attachment id by the time we get the data back or a now
- // registered infiniscroll event kicks in, so we don't ever display comments for the wrong image by mistake.
- var current = $( '.jp-carousel div.selected' );
- if ( current && current.data && current.data( 'attachment-id' ) != args.attachment_id ) {
- comments.fadeOut( 'fast' );
- comments.empty();
- return;
- }
+ if ( ! isSuccess || ! data || ! Array.isArray( data ) ) {
+ return onError();
+ }
- // Increase the height of the background, semi-transparent overlay to match the new length of the comments list.
- $( '.jp-carousel-overlay' ).height(
- $( window ).height() +
- titleAndDescription.height() +
- commentForm.height() +
- ( comments.height() > 0 ? comments.height() : imageMeta.height() ) +
- 200
- );
-
- comments.show();
- commentsLoading.hide();
- },
- error: function( xhr, status, error ) {
- // TODO: proper error handling
- console.log( 'Comment get fail...', xhr, status, error );
- comments.fadeIn( 'fast' );
- commentsLoading.fadeOut( 'fast' );
- },
- } );
- },
+ if ( shouldClear ) {
+ comments.innerHTML = '';
+ }
- postCommentError: function( args ) {
- if ( 'object' !== typeof args ) {
- args = {};
+ for ( var i = 0; i < data.length; i++ ) {
+ var entry = data[ i ];
+ var comment = document.createElement( 'div' );
+ comment.classList.add( 'jp-carousel-comment' );
+ comment.setAttribute( 'id', 'jp-carousel-comment-' + entry.id );
+ comment.innerHTML =
+ '<div class="comment-gravatar">' +
+ entry.gravatar_markup +
+ '</div>' +
+ '<div class="comment-content">' +
+ '<div class="comment-author">' +
+ entry.author_markup +
+ '</div>' +
+ '<div class="comment-date">' +
+ entry.date_gmt +
+ '</div>' +
+ entry.content +
+ '</div>';
+ comments.appendChild( comment );
+
+ // Set the interval to check for a new page of comments.
+ clearInterval( commentInterval );
+ commentInterval = setInterval( function () {
+ if ( carousel.container.scrollTop + 150 > window.innerHeight ) {
+ fetchComments( attachmentId, offset + 10 );
+ clearInterval( commentInterval );
+ }
+ }, 300 );
+ }
+
+ if ( data.length > 0 ) {
+ domUtil.show( comments );
+ commentsIndicator.innerText = data.length;
+ commentsIndicator.classList.add( 'jp-carousel-show' );
+ }
+
+ domUtil.hide( commentsLoading );
+ };
+
+ xhr.onerror = onError;
+
+ xhr.send();
+ }
+
+ function loadFullImage( slide ) {
+ var el = slide.el;
+ var attrs = slide.attrs;
+ var image = el.querySelector( 'img' );
+
+ if ( ! image.hasAttribute( 'data-loaded' ) ) {
+ var hasPreview = !! attrs.previewImage;
+ var thumbSize = attrs.thumbSize;
+
+ if ( ! hasPreview || ( thumbSize && el.offsetWidth > thumbSize.width ) ) {
+ image.src = attrs.src;
+ } else {
+ image.src = attrs.previewImage;
+ }
+
+ image.setAttribute( 'itemprop', 'image' );
+ image.setAttribute( 'data-loaded', 1 );
}
- if (
- ! args.field ||
- 'undefined' === typeof args.field ||
- ! args.error ||
- 'undefined' === typeof args.error
- ) {
- return;
+ }
+
+ function loadBackgroundImage( slide ) {
+ var currentSlide = slide.el;
+
+ if ( swiper && swiper.slides ) {
+ currentSlide = swiper.slides[ swiper.activeIndex ];
}
- $( '#jp-carousel-comment-post-results' )
- .slideUp( 'fast' )
- .html( '<span class="jp-carousel-comment-post-error">' + args.error + '</span>' )
- .slideDown( 'fast' );
- $( '#jp-carousel-comment-form-spinner' ).spin( false );
- },
-
- setCommentIframeSrc: function( attachment_id ) {
- var iframe = $( '#jp-carousel-comment-iframe' );
- // Set the proper irame src for the current attachment id
- if ( iframe && iframe.length ) {
- iframe.attr( 'src', iframe.attr( 'src' ).replace( /(postid=)\d+/, '$1' + attachment_id ) );
- iframe.attr(
- 'src',
- iframe.attr( 'src' ).replace( /(%23.+)?$/, '%23jp-carousel-' + attachment_id )
- );
+
+ var image = slide.attrs.originalElement;
+ var isLoaded = image.complete && image.naturalHeight !== 0;
+
+ if ( isLoaded ) {
+ applyBackgroundImage( slide, currentSlide, image );
+ return;
}
- },
- clearCommentTextAreaValue: function() {
- var commentTextArea = $( '#jp-carousel-comment-form-comment-field' );
- if ( commentTextArea ) {
- commentTextArea.val( '' );
+ image.onload = function () {
+ applyBackgroundImage( slide, currentSlide, image );
+ };
+ }
+
+ function applyBackgroundImage( slide, currentSlide, image ) {
+ var url = util.getBackgroundImage( image );
+ slide.backgroundImage = url;
+ currentSlide.style.backgroundImage = 'url(' + url + ')';
+ currentSlide.style.backgroundSize = 'cover';
+ }
+
+ function clearCommentTextAreaValue() {
+ if ( carousel.commentField ) {
+ carousel.commentField.value = '';
}
- },
+ }
- nextSlide: function() {
- var slides = this.jp_carousel( 'slides' );
- var selected = this.jp_carousel( 'selectedSlide' );
+ function getOriginalDimensions( el ) {
+ var size = el.getAttribute( 'data-orig-size' ) || '';
- if ( selected.length === 0 || ( slides.length > 2 && selected.is( slides.last() ) ) ) {
- return slides.first();
+ if ( size ) {
+ var parts = size.split( ',' );
+ return { width: parseInt( parts[ 0 ], 10 ), height: parseInt( parts[ 1 ], 10 ) };
+ } else {
+ return {
+ width:
+ el.getAttribute( 'data-original-width' ) || el.getAttribute( 'width' ) || undefined,
+ height:
+ el.getAttribute( 'data-original-height' ) || el.getAttribute( 'height' ) || undefined,
+ };
}
+ }
- return selected.next();
- },
+ function initCarouselSlides( items, startIndex ) {
+ carousel.slides = [];
- prevSlide: function() {
- var slides = this.jp_carousel( 'slides' );
- var selected = this.jp_carousel( 'selectedSlide' );
+ var max = calculateMaxSlideDimensions();
- if ( selected.length === 0 || ( slides.length > 2 && selected.is( slides.first() ) ) ) {
- return slides.last();
+ // If the startIndex is not 0 then preload the clicked image first.
+ if ( startIndex !== 0 ) {
+ var img = new Image();
+ img.src = items[ startIndex ].getAttribute( 'data-gallery-src' );
}
- return selected.prev();
- },
+ var useInPageThumbnails = !! domUtil.closest( items[ 0 ], '.tiled-gallery.type-rectangular' );
- loadFullImage: function( slide ) {
- var image = slide.find( 'img:first' );
+ // create the 'slide'
+ Array.prototype.forEach.call( items, function ( item, i ) {
+ var permalinkEl = domUtil.closest( item, 'a' );
+ var origFile = item.getAttribute( 'data-orig-file' ) || item.getAttribute( 'src-orig' );
+ var attrID =
+ item.getAttribute( 'data-attachment-id' ) || item.getAttribute( 'data-id' ) || '0';
+ var caption = document.querySelector(
+ 'img[data-attachment-id="' + attrID + '"] + figcaption'
+ );
- if ( ! image.data( 'loaded' ) ) {
- // If the width of the slide is smaller than the width of the "thumbnail" we're already using,
- // don't load the full image.
+ if ( caption ) {
+ caption = caption.innerHTML;
+ } else {
+ caption = item.getAttribute( 'data-image-caption' );
+ }
- image.on( 'load.jetpack', function() {
- image.off( 'load.jetpack' );
- $( this )
- .closest( '.jp-carousel-slide' )
- .css( 'background-image', '' );
- } );
+ var attrs = {
+ originalElement: item,
+ attachmentId: attrID,
+ commentsOpened: item.getAttribute( 'data-comments-opened' ) || '0',
+ imageMeta: domUtil.getJSONAttribute( item, 'data-image-meta' ) || {},
+ title: item.getAttribute( 'data-image-title' ) || '',
+ desc: item.getAttribute( 'data-image-description' ) || '',
+ mediumFile: item.getAttribute( 'data-medium-file' ) || '',
+ largeFile: item.getAttribute( 'data-large-file' ) || '',
+ origFile: origFile || '',
+ thumbSize: { width: item.naturalWidth, height: item.naturalHeight },
+ caption: caption || '',
+ permalink: permalinkEl && permalinkEl.getAttribute( 'href' ),
+ src: origFile || item.getAttribute( 'src' ) || '',
+ };
+
+ var tiledGalleryItem = domUtil.closest( item, '.tiled-gallery-item' );
+ var tiledCaptionEl =
+ tiledGalleryItem && tiledGalleryItem.querySelector( '.tiled-gallery-caption' );
+ var tiledCaption = tiledCaptionEl && tiledCaptionEl.innerHTML;
+ if ( tiledCaption ) {
+ attrs.caption = tiledCaption;
+ }
- if (
- ! slide.data( 'preview-image' ) ||
- ( slide.data( 'thumb-size' ) && slide.width() > slide.data( 'thumb-size' ).width )
- ) {
- image
- .attr( 'src', image.closest( '.jp-carousel-slide' ).data( 'src' ) )
- .attr( 'itemprop', 'image' );
+ var origDimensions = getOriginalDimensions( item );
+
+ attrs.origWidth = origDimensions.width || attrs.thumbSize.width;
+ attrs.origHeight = origDimensions.height || attrs.thumbSize.height;
+
+ if ( typeof wpcom !== 'undefined' && wpcom.carousel && wpcom.carousel.generateImgSrc ) {
+ attrs.src = wpcom.carousel.generateImgSrc( item, max );
} else {
- image.attr( 'src', slide.data( 'preview-image' ) ).attr( 'itemprop', 'image' );
+ attrs.src = selectBestImageUrl( {
+ origFile: attrs.src,
+ origWidth: attrs.origWidth,
+ origHeight: attrs.origHeight,
+ maxWidth: max.width,
+ maxHeight: max.height,
+ mediumFile: attrs.mediumFile,
+ largeFile: attrs.largeFile,
+ } );
}
- image.data( 'loaded', 1 );
- }
- },
+ // Set the final src.
+ item.setAttribute( 'data-gallery-src', attrs.src );
- hasMultipleImages: function() {
- return gallery.jp_carousel( 'slides' ).length > 1;
- },
- };
+ if ( attrs.attachmentId !== '0' ) {
+ attrs.title = util.texturize( attrs.title );
+ attrs.desc = util.texturize( attrs.desc );
+ attrs.caption = util.texturize( attrs.caption );
- $.fn.jp_carousel = function( method ) {
- // ask for the HTML of the gallery
- // Method calling logic
- if ( methods[ method ] ) {
- return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
- } else if ( typeof method === 'object' || ! method ) {
- return methods.open.apply( this, arguments );
- } else {
- $.error( 'Method ' + method + ' does not exist on jQuery.jp_carousel' );
+ // Initially, the image is a 1x1 transparent gif.
+ // The preview is shown as a background image on the slide itself.
+ var image = new Image();
+ image.src = attrs.src;
+
+ var slideEl = document.createElement( 'div' );
+ slideEl.classList.add( 'swiper-slide' );
+ slideEl.setAttribute( 'itemprop', 'associatedMedia' );
+ slideEl.setAttribute( 'itemscope', '' );
+ slideEl.setAttribute( 'itemtype', 'https://schema.org/ImageObject' );
+ var zoomEl = document.createElement( 'div' );
+ zoomEl.classList.add( 'swiper-zoom-container' );
+
+ carousel.gallery.appendChild( slideEl );
+
+ slideEl.appendChild( zoomEl );
+ zoomEl.appendChild( image );
+ slideEl.setAttribute( 'data-attachment-id', attrs.attachmentId );
+ slideEl.setAttribute( 'data-permalink', attrs.permalink );
+ slideEl.setAttribute( 'data-orig-file', attrs.origFile );
+
+ if ( useInPageThumbnails ) {
+ // Use the image already loaded in the gallery as a preview.
+ attrs.previewImage = attrs.src;
+ }
+
+ var slide = { el: slideEl, attrs: attrs, index: i };
+ carousel.slides.push( slide );
+ }
+ } );
}
- };
-
- // register the event listener for starting the gallery
- $( document.body ).on(
- 'click.jp-carousel',
- 'div.gallery, div.tiled-gallery, ul.wp-block-gallery, ul.blocks-gallery-grid, div.wp-block-jetpack-tiled-gallery, a.single-image-gallery',
- function( e ) {
- if ( ! $( this ).jp_carousel( 'testForData', e.currentTarget ) ) {
+
+ function loadSwiper( gallery, options ) {
+ if ( ! window.Swiper670 ) {
+ var loader = document.querySelector( '#jp-carousel-loading-overlay' );
+ domUtil.show( loader );
+ var jsScript = document.createElement( 'script' );
+ jsScript.id = 'jetpack-carousel-swiper-js';
+ jsScript.src = window.jetpackSwiperLibraryPath.url;
+ jsScript.async = true;
+ jsScript.onload = function () {
+ domUtil.hide( loader );
+ openCarousel( gallery, options );
+ };
+ jsScript.onerror = function () {
+ domUtil.hide( loader );
+ };
+ document.head.appendChild( jsScript );
return;
}
+ openCarousel( gallery, options );
+ }
- // Do not open the modal if we are looking at a gallery caption from before WP5, which may contain a link.
- if (
- $( e.target )
- .parent()
- .hasClass( 'gallery-caption' )
- ) {
- return;
+ function openCarousel( gallery, options ) {
+ var settings = {
+ imgSelector:
+ '.gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id], img[data-attachment-id], img[data-id]',
+ startIndex: 0,
+ };
+
+ var data = domUtil.getJSONAttribute( gallery, 'data-carousel-extra' );
+ var tapTimeout;
+
+ if ( ! data ) {
+ return; // don't run if the default gallery functions weren't used
}
- // Do not open the modal if we are looking at a caption of a gallery block, which may contain a link.
- if (
- $( e.target )
- .parent()
- .is( 'figcaption' )
- ) {
- return;
+ initializeCarousel();
+
+ if ( carousel.isOpen ) {
+ return; // don't open if already opened
}
+ carousel.isOpen = true;
- // Set height to auto
- // Fix some themes where closing carousel brings view back to top
- $( 'html' ).css( 'height', 'auto' );
+ // make sure to stop the page from scrolling behind the carousel overlay, so we don't trigger
+ // infiniscroll for it when enabled (Reader, theme infiniscroll, etc).
+ originalOverflow = getComputedStyle( document.body ).overflow;
+ document.body.style.overflow = 'hidden';
+ // prevent html from overflowing on some of the new themes.
+ originalHOverflow = getComputedStyle( document.documentElement ).overflow;
+ document.documentElement.style.overflow = 'hidden';
+ scrollPos = window.scrollY || window.pageYOffset || 0;
- e.preventDefault();
+ carousel.container.setAttribute( 'data-carousel-extra', JSON.stringify( data ) );
+ stat( [ 'open', 'view_image' ] );
+
+ // If options exist, lets merge them
+ // with our default settings
+ for ( var option in options || {} ) {
+ settings[ option ] = options[ option ];
+ }
+
+ if ( settings.startIndex === -1 ) {
+ settings.startIndex = 0; // -1 returned if can't find index, so start from beginning
+ }
- // Stopping propagation in case there are parent elements
- // with .gallery or .tiled-gallery class
- e.stopPropagation();
- $( this ).jp_carousel( 'open', {
- start_index: $( this )
- .find( '.gallery-item, .tiled-gallery-item, .blocks-gallery-item, .tiled-gallery__item' )
- .index(
- $( e.target ).parents(
- '.gallery-item, .tiled-gallery-item, .blocks-gallery-item, .tiled-gallery__item'
- )
- ),
+ domUtil.emitEvent( carousel.overlay, 'jp_carousel.beforeOpen' );
+ carousel.gallery.innerHTML = '';
+
+ // Need to set the overlay manually to block or swiper does't initialise properly.
+ carousel.overlay.style.opacity = 1;
+ carousel.overlay.style.display = 'block';
+
+ initCarouselSlides( gallery.querySelectorAll( settings.imgSelector ), settings.startIndex );
+
+ swiper = new window.Swiper670( '.jp-carousel-swiper-container', {
+ centeredSlides: true,
+ zoom: true,
+ loop: carousel.slides.length > 1,
+ // Turn off interactions and hide navigation arrows if there is only one slide.
+ enabled: carousel.slides.length > 1,
+ pagination: {
+ el: '.jp-swiper-pagination',
+ clickable: true,
+ },
+ navigation: {
+ nextEl: '.jp-swiper-button-next',
+ prevEl: '.jp-swiper-button-prev',
+ },
+ initialSlide: settings.startIndex,
+ on: {
+ init: function () {
+ selectSlideAtIndex( settings.startIndex );
+ },
+ },
+ preventClicks: false,
+ preventClicksPropagation: false,
+ preventInteractionOnTransition: ! domUtil.isTouch(),
+ threshold: 5,
} );
- }
- );
- // handle lightbox (single image gallery) for images linking to 'Attachment Page'
- if ( 1 === Number( jetpackCarouselStrings.single_image_gallery ) ) {
- processSingleImageGallery();
- $( document.body ).on( 'post-load', function() {
- processSingleImageGallery();
- } );
- }
+ swiper.on( 'slideChange', function ( swiper ) {
+ var index;
+ // Swiper indexes slides from 1, plus when looping to left last slide ends up
+ // as 0 and looping to right first slide as total slides + 1. These are adjusted
+ // here to match index of carousel.slides.
+ if ( swiper.activeIndex === 0 ) {
+ index = carousel.slides.length - 1;
+ } else if ( swiper.activeIndex === carousel.slides.length + 1 ) {
+ index = 0;
+ } else {
+ index = swiper.activeIndex - 1;
+ }
+ selectSlideAtIndex( index );
- // Makes carousel work on page load and when back button leads to same URL with carousel hash (ie: no actual document.ready trigger)
- $( window ).on( 'hashchange.jp-carousel', function() {
- var hashRegExp = /jp-carousel-(\d+)/,
- matches,
- attachmentId,
- galleries,
- selectedThumbnail;
+ carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
+ } );
- if ( ! window.location.hash || ! hashRegExp.test( window.location.hash ) ) {
- if ( gallery && gallery.opened ) {
- container.jp_carousel( 'close' );
- }
+ swiper.on( 'zoomChange', function ( swiper, scale ) {
+ if ( scale > 1 ) {
+ carousel.overlay.classList.add( 'jp-carousel-hide-controls' );
+ }
- return;
- }
+ if ( scale === 1 ) {
+ carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
+ }
+ } );
- if ( window.location.hash === last_known_location_hash && gallery.opened ) {
- return;
- }
+ swiper.on( 'doubleTap', function ( swiper ) {
+ clearTimeout( tapTimeout );
+ if ( swiper.zoom.scale === 1 ) {
+ var zoomTimeout = setTimeout( function () {
+ carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
+ clearTimeout( zoomTimeout );
+ }, 150 );
+ }
+ } );
- if ( window.location.hash && gallery && ! gallery.opened && history.back ) {
- history.back();
- return;
+ swiper.on( 'tap', function () {
+ if ( swiper.zoom.scale > 1 ) {
+ tapTimeout = setTimeout( function () {
+ carousel.overlay.classList.toggle( 'jp-carousel-hide-controls' );
+ }, 150 );
+ }
+ } );
+
+ domUtil.fadeIn( carousel.overlay, function () {
+ domUtil.emitEvent( carousel.overlay, 'jp_carousel.afterOpen' );
+ } );
}
- last_known_location_hash = window.location.hash;
- matches = window.location.hash.match( hashRegExp );
- attachmentId = parseInt( matches[ 1 ], 10 );
- galleries = $(
- 'div.gallery, div.tiled-gallery, a.single-image-gallery, ul.wp-block-gallery, div.wp-block-jetpack-tiled-gallery'
- );
-
- // Find the first thumbnail that matches the attachment ID in the location
- // hash, then open the gallery that contains it.
- galleries.each( function( _, galleryEl ) {
- $( galleryEl )
- .find( 'img' )
- .each( function( imageIndex, imageEl ) {
- if ( $( imageEl ).data( 'attachment-id' ) === parseInt( attachmentId, 10 ) ) {
- selectedThumbnail = { index: imageIndex, gallery: galleryEl };
- return false;
+ // Register the event listener for starting the gallery
+ document.body.addEventListener( 'click', function ( e ) {
+ var isCompatible =
+ window.CSS && window.CSS.supports && window.CSS.supports( 'display', 'grid' );
+
+ // IE11 support is being dropped in August 2021. The new swiper.js libray is not IE11 compat
+ // so just default to opening individual image attachment/media pages for IE.
+ if ( ! isCompatible ) {
+ return;
+ }
+
+ var target = e.target;
+ var gallery = domUtil.closest( target, gallerySelector );
+
+ if ( gallery ) {
+ if ( ! testForData( gallery ) ) {
+ return;
+ }
+
+ var parent = target.parentElement;
+ var grandparent = parent.parentElement;
+
+ // If Gallery is made up of individual Image blocks check for custom link before
+ // loading carousel.
+ if ( grandparent && grandparent.classList.contains( 'wp-block-image' ) ) {
+ var parentHref = parent.getAttribute( 'href' );
+
+ // If the link does not point to the attachment or media file then assume Image has
+ // a custom link so don't load the carousel.
+ if (
+ parentHref.split( '?' )[ 0 ] !==
+ target.getAttribute( 'data-orig-file' ).split( '?' )[ 0 ] &&
+ parentHref !== target.getAttribute( 'data-permalink' )
+ ) {
+ return;
}
- } );
+ }
- if ( selectedThumbnail ) {
- $( selectedThumbnail.gallery ).jp_carousel( 'openOrSelectSlide', selectedThumbnail.index );
- return false;
+ // Do not open the modal if we are looking at a gallery caption from before WP5, which may contain a link.
+ if ( parent.classList.contains( 'gallery-caption' ) ) {
+ return;
+ }
+
+ // Do not open the modal if we are looking at a caption of a gallery block, which may contain a link.
+ if ( domUtil.matches( parent, 'figcaption' ) ) {
+ return;
+ }
+
+ // Set height to auto.
+ // Fix some themes where closing carousel brings view back to top.
+ document.documentElement.style.height = 'auto';
+
+ e.preventDefault();
+
+ // Stopping propagation in case there are parent elements
+ // with .gallery or .tiled-gallery class
+ e.stopPropagation();
+
+ var item = domUtil.closest( target, itemSelector );
+ var index = Array.prototype.indexOf.call( gallery.querySelectorAll( itemSelector ), item );
+ loadSwiper( gallery, { startIndex: index } );
}
} );
- } );
- if ( window.location.hash ) {
- $( window ).trigger( 'hashchange' );
- }
-} );
-
-/**
- * jQuery Plugin to obtain touch gestures from iPhone, iPod Touch and iPad, should also work with Android mobile phones (not tested yet!)
- * Common usage: wipe images (left and right to show the previous or next image)
- *
- * @author Andreas Waltl, netCU Internetagentur (http://www.netcu.de)
- * Version 1.1.1, modified to pass the touchmove event to the callbacks.
- */
-( function( $ ) {
- $.fn.touchwipe = function( settings ) {
- var config = {
- min_move_x: 20,
- min_move_y: 20,
- wipeLeft: function(/*e*/) {},
- wipeRight: function(/*e*/) {},
- wipeUp: function(/*e*/) {},
- wipeDown: function(/*e*/) {},
- preventDefaultEvents: true,
- };
-
- if ( settings ) {
- $.extend( config, settings );
+ // Handle lightbox (single image gallery) for images linking to 'Attachment Page'.
+ if ( Number( jetpackCarouselStrings.single_image_gallery ) === 1 ) {
+ processSingleImageGallery();
+ document.body.addEventListener( 'is.post-load', function () {
+ processSingleImageGallery();
+ } );
}
- this.each( function() {
- var startX;
- var startY;
- var isMoving = false;
+ // Makes carousel work on page load and when back button leads to same URL with carousel hash
+ // (i.e. no actual document.ready trigger).
+ window.addEventListener( 'hashchange', function () {
+ var hashRegExp = /jp-carousel-(\d+)/;
- function cancelTouch() {
- this.removeEventListener( 'touchmove', onTouchMove );
- startX = null;
- isMoving = false;
+ if ( ! window.location.hash || ! hashRegExp.test( window.location.hash ) ) {
+ if ( carousel.isOpen ) {
+ closeCarousel();
+ }
+
+ return;
}
- function onTouchMove( e ) {
- if ( config.preventDefaultEvents ) {
- e.preventDefault();
- }
- if ( isMoving ) {
- var x = e.touches[ 0 ].pageX;
- var y = e.touches[ 0 ].pageY;
- var dx = startX - x;
- var dy = startY - y;
- if ( Math.abs( dx ) >= config.min_move_x ) {
- cancelTouch();
- if ( dx > 0 ) {
- config.wipeLeft( e );
- } else {
- config.wipeRight( e );
- }
- } else if ( Math.abs( dy ) >= config.min_move_y ) {
- cancelTouch();
- if ( dy > 0 ) {
- config.wipeDown( e );
- } else {
- config.wipeUp( e );
- }
+ if ( window.location.hash === lastKnownLocationHash && carousel.isOpen ) {
+ return;
+ }
+
+ if ( window.location.hash && carousel.gallery && ! carousel.isOpen && history.back ) {
+ history.back();
+ return;
+ }
+
+ lastKnownLocationHash = window.location.hash;
+ var matchList = window.location.hash.match( hashRegExp );
+ var attachmentId = parseInt( matchList[ 1 ], 10 );
+ var galleries = document.querySelectorAll( gallerySelector );
+
+ // Find the first thumbnail that matches the attachment ID in the location
+ // hash, then open the gallery that contains it.
+ for ( var i = 0; i < galleries.length; i++ ) {
+ var gallery = galleries[ i ];
+ var selected;
+
+ var images = gallery.querySelectorAll( 'img' );
+ for ( var j = 0; j < images.length; j++ ) {
+ if (
+ parseInt( images[ j ].getAttribute( 'data-attachment-id' ), 10 ) === attachmentId ||
+ parseInt( images[ j ].getAttribute( 'data-id' ), 10 ) === attachmentId
+ ) {
+ selected = j;
+ break;
}
}
- }
- function onTouchStart( e ) {
- if ( e.touches.length === 1 ) {
- startX = e.touches[ 0 ].pageX;
- startY = e.touches[ 0 ].pageY;
- isMoving = true;
- this.addEventListener( 'touchmove', onTouchMove, false );
+ if ( selected !== undefined ) {
+ openOrSelectSlide( gallery, selected );
+ break;
}
}
- if ( 'ontouchstart' in document.documentElement ) {
- this.addEventListener( 'touchstart', onTouchStart, false );
- }
} );
- return this;
- };
-} )( jQuery );
+ if ( window.location.hash ) {
+ domUtil.emitEvent( window, 'hashchange' );
+ }
+ }
+
+ if ( document.readyState !== 'loading' ) {
+ init();
+ } else {
+ document.addEventListener( 'DOMContentLoaded', init );
+ }
+} )();