PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 7.2.2
Jetpack – WP Security, Backup, Speed, & Growth v7.2.2
16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 All 503 releases
jetpack / modules / carousel / jetpack-carousel.js

jetpack-carousel.js in Jetpack – WP Security, Backup, Speed, & Growth 7.2.2, at modules/carousel/jetpack-carousel.js

1,610 lines 52.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* jshint sub: true, onevar: false, multistr: true, devel: true, smarttabs: true */
2 /* global jetpackCarouselStrings, DocumentTouch */
3
4
5 jQuery(document).ready(function($) {
6
7 // gallery faded layer and container elements
8 var overlay, comments, gallery, container, nextButton, previousButton, info, transitionBegin,
9 caption, resizeTimeout, photo_info, close_hint, commentInterval, lastSelectedSlide,
10 screenPadding = 110, originalOverflow = $('body').css('overflow'), originalHOverflow = $('html').css('overflow'), proportion = 85,
11 last_known_location_hash = '', imageMeta, titleAndDescription, commentForm, leftColWrapper, scrollPos;
12
13 if ( window.innerWidth <= 760 ) {
14 screenPadding = Math.round( ( window.innerWidth / 760 ) * 110 );
15
16 if ( screenPadding < 40 && ( ( 'ontouchstart' in window ) || window.DocumentTouch && document instanceof DocumentTouch ) ) {
17 screenPadding = 0;
18 }
19 }
20
21 // Adding a polyfill for browsers that do not have Date.now
22 if ( 'undefined' === typeof Date.now ) {
23 Date.now = function now() {
24 return new Date().getTime();
25 };
26 }
27
28 var keyListener = function(e){
29 switch(e.which){
30 case 38: // up
31 e.preventDefault();
32 container.scrollTop(container.scrollTop() - 100);
33 break;
34 case 40: // down
35 e.preventDefault();
36 container.scrollTop(container.scrollTop() + 100);
37 break;
38 case 39: // right
39 e.preventDefault();
40 gallery.jp_carousel('next');
41 break;
42 case 37: // left
43 case 8: // backspace
44 e.preventDefault();
45 gallery.jp_carousel('previous');
46 break;
47 case 27: // escape
48 e.preventDefault();
49 container.jp_carousel('close');
50 break;
51 default:
52 // making jslint happy
53 break;
54 }
55 };
56
57 var resizeListener = function(/*e*/){
58 clearTimeout(resizeTimeout);
59 resizeTimeout = setTimeout(function(){
60 gallery
61 .jp_carousel('slides')
62 .jp_carousel('fitSlide', true);
63 gallery.jp_carousel('updateSlidePositions', true);
64 gallery.jp_carousel('fitMeta', true);
65 }, 200);
66 };
67
68 var prepareGallery = function( /*dataCarouselExtra*/ ){
69 if (!overlay) {
70 overlay = $('<div></div>')
71 .addClass('jp-carousel-overlay')
72 .css({
73 'position' : 'fixed',
74 'top' : 0,
75 'right' : 0,
76 'bottom' : 0,
77 'left' : 0
78 });
79
80 var buttons = '<a class="jp-carousel-commentlink" href="#">' + jetpackCarouselStrings.comment + '</a>';
81 if ( 1 === Number( jetpackCarouselStrings.is_logged_in ) ) {
82 }
83
84 buttons = $('<div class="jp-carousel-buttons">' + buttons + '</div>');
85
86 caption = $('<h2 itemprop="caption description"></h2>');
87 photo_info = $('<div class="jp-carousel-photo-info"></div>').append(caption);
88
89 imageMeta = $('<div></div>')
90 .addClass('jp-carousel-image-meta')
91 .css({
92 'float' : 'right',
93 'margin-top' : '20px',
94 'width' : '250px'
95 });
96
97 imageMeta
98 .append( buttons )
99 .append( '<ul class=\'jp-carousel-image-exif\' style=\'display:none;\'></ul>' )
100 .append( '<a class=\'jp-carousel-image-download\' style=\'display:none;\'></a>' )
101 .append( '<div class=\'jp-carousel-image-map\' style=\'display:none;\'></div>' );
102
103 titleAndDescription = $('<div></div>')
104 .addClass('jp-carousel-titleanddesc')
105 .css({
106 'width' : '100%',
107 'margin-top' : imageMeta.css('margin-top')
108 });
109
110 var commentFormMarkup = '<div id="jp-carousel-comment-form-container">';
111
112 if ( jetpackCarouselStrings.local_comments_commenting_as && jetpackCarouselStrings.local_comments_commenting_as.length ) {
113 // Comments not enabled, fallback to local comments
114
115 if ( 1 !== Number( jetpackCarouselStrings.is_logged_in ) && 1 === Number( jetpackCarouselStrings.comment_registration ) ) {
116 commentFormMarkup += '<div id="jp-carousel-comment-form-commenting-as">' + jetpackCarouselStrings.local_comments_commenting_as + '</div>';
117 } else {
118 commentFormMarkup += '<form id="jp-carousel-comment-form">';
119 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>';
120 commentFormMarkup += '<div id="jp-carousel-comment-form-submit-and-info-wrapper">';
121 commentFormMarkup += '<div id="jp-carousel-comment-form-commenting-as">' + jetpackCarouselStrings.local_comments_commenting_as + '</div>';
122 commentFormMarkup += '<input type="submit" name="submit" class="jp-carousel-comment-form-button" id="jp-carousel-comment-form-button-submit" value="'+jetpackCarouselStrings.post_comment+'" />';
123 commentFormMarkup += '<span id="jp-carousel-comment-form-spinner">&nbsp;</span>';
124 commentFormMarkup += '<div id="jp-carousel-comment-post-results"></div>';
125 commentFormMarkup += '</div>';
126 commentFormMarkup += '</form>';
127 }
128 }
129 commentFormMarkup += '</div>';
130
131 commentForm = $(commentFormMarkup)
132 .css({
133 'width' : '100%',
134 'margin-top' : '20px',
135 'color' : '#999'
136 });
137
138 comments = $('<div></div>')
139 .addClass('jp-carousel-comments')
140 .css({
141 'width' : '100%',
142 'bottom' : '10px',
143 'margin-top' : '20px'
144 });
145
146 var commentsLoading = $('<div id="jp-carousel-comments-loading"><span>'+jetpackCarouselStrings.loading_comments+'</span></div>')
147 .css({
148 'width' : '100%',
149 'bottom' : '10px',
150 'margin-top' : '20px'
151 });
152
153 var leftWidth = ( $(window).width() - ( screenPadding * 2 ) ) - (imageMeta.width() + 40);
154 leftWidth += 'px';
155
156 leftColWrapper = $('<div></div>')
157 .addClass('jp-carousel-left-column-wrapper')
158 .css({
159 'width' : Math.floor( leftWidth )
160 })
161 .append(titleAndDescription)
162 .append(commentForm)
163 .append(comments)
164 .append(commentsLoading);
165
166 var fadeaway = $('<div></div>')
167 .addClass('jp-carousel-fadeaway');
168
169 info = $('<div></div>')
170 .addClass('jp-carousel-info')
171 .css({
172 'top' : Math.floor( ($(window).height() / 100) * proportion ),
173 'left' : screenPadding,
174 'right' : screenPadding
175 })
176 .append(photo_info)
177 .append(imageMeta);
178
179 if ( window.innerWidth <= 760 ) {
180 photo_info.remove().insertAfter( titleAndDescription );
181 info.prepend( leftColWrapper );
182 }
183 else {
184 info.append( leftColWrapper );
185 }
186
187 var targetBottomPos = ( $(window).height() - parseInt( info.css('top'), 10 ) ) + 'px';
188
189 nextButton = $('<div><span></span></div>')
190 .addClass('jp-carousel-next-button')
191 .css({
192 'right' : '15px'
193 })
194 .hide();
195
196 previousButton = $('<div><span></span></div>')
197 .addClass('jp-carousel-previous-button')
198 .css({
199 'left' : 0
200 })
201 .hide();
202
203 nextButton.add( previousButton ).css( {
204 'position' : 'fixed',
205 'top' : '40px',
206 'bottom' : targetBottomPos,
207 'width' : screenPadding
208 } );
209
210 gallery = $('<div></div>')
211 .addClass('jp-carousel')
212 .css({
213 'position' : 'absolute',
214 'top' : 0,
215 'bottom' : targetBottomPos,
216 'left' : 0,
217 'right' : 0
218 });
219
220 close_hint = $('<div class="jp-carousel-close-hint"><span>&times;</span></div>')
221 .css({
222 position : 'fixed'
223 });
224
225 container = $('<div></div>')
226 .addClass('jp-carousel-wrap')
227 .addClass( 'jp-carousel-transitions' );
228 if ( 'white' === jetpackCarouselStrings.background_color ) {
229 container.addClass('jp-carousel-light');
230 }
231
232 container.attr('itemscope', '');
233
234 container.attr('itemtype', 'https://schema.org/ImageGallery');
235
236 container.css({
237 'position' : 'fixed',
238 'top' : 0,
239 'right' : 0,
240 'bottom' : 0,
241 'left' : 0,
242 'z-index' : 2147483647,
243 'overflow-x' : 'hidden',
244 'overflow-y' : 'auto',
245 'direction' : 'ltr'
246 })
247 .hide()
248 .append(overlay)
249 .append(gallery)
250 .append(fadeaway)
251 .append(info)
252 .append(nextButton)
253 .append(previousButton)
254 .append(close_hint)
255 .appendTo($('body'))
256 .click(function(e){
257 var target = $(e.target), wrap = target.parents('div.jp-carousel-wrap'), data = wrap.data('carousel-extra'),
258 slide = wrap.find('div.selected'), attachment_id = slide.data('attachment-id');
259 data = data || [];
260
261 if ( target.is(gallery) || target.parents().add(target).is(close_hint) ) {
262 container.jp_carousel('close');
263 } else if ( target.hasClass('jp-carousel-commentlink') ) {
264 e.preventDefault();
265 e.stopPropagation();
266 $(window).unbind('keydown', keyListener);
267 container.animate({scrollTop: parseInt(info.position()['top'], 10)}, 'fast');
268 $('#jp-carousel-comment-form-submit-and-info-wrapper').slideDown('fast');
269 $('#jp-carousel-comment-form-comment-field').focus();
270 } else if ( target.hasClass('jp-carousel-comment-login') ) {
271 var url = jetpackCarouselStrings.login_url + '%23jp-carousel-' + attachment_id;
272
273 window.location.href = url;
274 } else if ( target.parents('#jp-carousel-comment-form-container').length ) {
275 var textarea = $('#jp-carousel-comment-form-comment-field')
276 .blur(function(){
277 $(window).bind('keydown', keyListener);
278 })
279 .focus(function(){
280 $(window).unbind('keydown', keyListener);
281 });
282
283 var emailField = $('#jp-carousel-comment-form-email-field')
284 .blur(function(){
285 $(window).bind('keydown', keyListener);
286 })
287 .focus(function(){
288 $(window).unbind('keydown', keyListener);
289 });
290
291 var authorField = $('#jp-carousel-comment-form-author-field')
292 .blur(function(){
293 $(window).bind('keydown', keyListener);
294 })
295 .focus(function(){
296 $(window).unbind('keydown', keyListener);
297 });
298
299 var urlField = $('#jp-carousel-comment-form-url-field')
300 .blur(function(){
301 $(window).bind('keydown', keyListener);
302 })
303 .focus(function(){
304 $(window).unbind('keydown', keyListener);
305 });
306
307 if ( textarea && textarea.attr('id') === target.attr('id')) {
308 // For first page load
309 $(window).unbind('keydown', keyListener);
310 $('#jp-carousel-comment-form-submit-and-info-wrapper').slideDown('fast');
311 } else if ( target.is( 'input[type="submit"]' ) ) {
312 e.preventDefault();
313 e.stopPropagation();
314
315 $('#jp-carousel-comment-form-spinner').spin('small', 'white');
316
317 var ajaxData = {
318 action: 'post_attachment_comment',
319 nonce: jetpackCarouselStrings.nonce,
320 blog_id: data['blog_id'],
321 id: attachment_id,
322 comment: textarea.val()
323 };
324
325 if ( ! ajaxData['comment'].length ) {
326 gallery.jp_carousel('postCommentError', {'field': 'jp-carousel-comment-form-comment-field', 'error': jetpackCarouselStrings.no_comment_text});
327 return;
328 }
329
330 if ( 1 !== Number( jetpackCarouselStrings.is_logged_in ) ) {
331 ajaxData['email'] = emailField.val();
332 ajaxData['author'] = authorField.val();
333 ajaxData['url'] = urlField.val();
334
335 if ( 1 === Number( jetpackCarouselStrings.require_name_email ) ) {
336 if ( ! ajaxData['email'].length || ! ajaxData['email'].match('@') ) {
337 gallery.jp_carousel('postCommentError', {'field': 'jp-carousel-comment-form-email-field', 'error': jetpackCarouselStrings.no_comment_email});
338 return;
339 } else if ( ! ajaxData['author'].length ) {
340 gallery.jp_carousel('postCommentError', {'field': 'jp-carousel-comment-form-author-field', 'error': jetpackCarouselStrings.no_comment_author});
341 return;
342 }
343 }
344 }
345
346 $.ajax({
347 type: 'POST',
348 url: jetpackCarouselStrings.ajaxurl,
349 data: ajaxData,
350 dataType: 'json',
351 success: function(response/*, status, xhr*/) {
352 if ( 'approved' === response.comment_status ) {
353 $('#jp-carousel-comment-post-results').slideUp('fast').html('<span class="jp-carousel-comment-post-success">' + jetpackCarouselStrings.comment_approved + '</span>').slideDown('fast');
354 } else if ( 'unapproved' === response.comment_status ) {
355 $('#jp-carousel-comment-post-results').slideUp('fast').html('<span class="jp-carousel-comment-post-success">' + jetpackCarouselStrings.comment_unapproved + '</span>').slideDown('fast');
356 } else {
357 // 'deleted', 'spam', false
358 $('#jp-carousel-comment-post-results').slideUp('fast').html('<span class="jp-carousel-comment-post-error">' + jetpackCarouselStrings.comment_post_error + '</span>').slideDown('fast');
359 }
360 gallery.jp_carousel('clearCommentTextAreaValue');
361 gallery.jp_carousel('getComments', {attachment_id: attachment_id, offset: 0, clear: true});
362 $('#jp-carousel-comment-form-button-submit').val(jetpackCarouselStrings.post_comment);
363 $('#jp-carousel-comment-form-spinner').spin(false);
364 },
365 error: function(/*xhr, status, error*/) {
366 // TODO: Add error handling and display here
367 gallery.jp_carousel('postCommentError', {'field': 'jp-carousel-comment-form-comment-field', 'error': jetpackCarouselStrings.comment_post_error});
368 return;
369 }
370 });
371 }
372 } else if ( ! target.parents( '.jp-carousel-info' ).length ) {
373 container.jp_carousel('next');
374 }
375 })
376 .bind('jp_carousel.afterOpen', function(){
377 $(window).bind('keydown', keyListener);
378 $(window).bind('resize', resizeListener);
379 gallery.opened = true;
380
381 resizeListener();
382 })
383 .bind('jp_carousel.beforeClose', function(){
384 var scroll = $(window).scrollTop();
385
386 $(window).unbind('keydown', keyListener);
387 $(window).unbind('resize', resizeListener);
388 $(window).scrollTop(scroll);
389 $( '.jp-carousel-previous-button' ).hide();
390 $( '.jp-carousel-next-button' ).hide();
391 })
392 .bind('jp_carousel.afterClose', function(){
393 if ( window.location.hash && history.back ) {
394 history.back();
395 }
396 last_known_location_hash = '';
397 gallery.opened = false;
398 })
399 .on( 'transitionend.jp-carousel ', '.jp-carousel-slide', function ( e ) {
400 // If the movement transitions take more than twice the allotted time, disable them.
401 // There is some wiggle room in the 2x, since some of that time is taken up in
402 // JavaScript, setting up the transition and calling the events.
403 if ( 'transform' === e.originalEvent.propertyName ) {
404 var transitionMultiplier = ( ( Date.now() - transitionBegin ) / 1000 ) / e.originalEvent.elapsedTime;
405
406 container.off( 'transitionend.jp-carousel' );
407
408 if ( transitionMultiplier >= 2 ) {
409 $( '.jp-carousel-transitions' ).removeClass( 'jp-carousel-transitions' );
410 }
411 }
412 } );
413
414 $( '.jp-carousel-wrap' ).touchwipe( {
415 wipeLeft : function ( e ) {
416 e.preventDefault();
417 gallery.jp_carousel( 'next' );
418 },
419 wipeRight : function ( e ) {
420 e.preventDefault();
421 gallery.jp_carousel( 'previous' );
422 },
423 preventDefaultEvents : false
424 } );
425
426 nextButton.add(previousButton).click(function(e){
427 e.preventDefault();
428 e.stopPropagation();
429 if ( nextButton.is(this) ) {
430 gallery.jp_carousel('next');
431 } else {
432 gallery.jp_carousel('previous');
433 }
434 });
435 }
436 };
437
438 var processSingleImageGallery = function() {
439 // process links that contain img tag with attribute data-attachment-id
440 $( 'a img[data-attachment-id]' ).each(function() {
441 var container = $( this ).parent();
442
443 // skip if image was already added to gallery by shortcode
444 if( container.parent( '.gallery-icon' ).length ) {
445 return;
446 }
447
448 // skip if the container is not a link
449 if ( 'undefined' === typeof( $( container ).attr( 'href' ) ) ) {
450 return;
451 }
452
453 var valid = false;
454
455 // if link points to 'Media File' (ignoring GET parameters) and flag is set allow it
456 if ( $( container ).attr( 'href' ).split( '?' )[0] === $( this ).attr( 'data-orig-file' ).split( '?' )[0] &&
457 1 === Number( jetpackCarouselStrings.single_image_gallery_media_file )
458 ) {
459 valid = true;
460 }
461
462 // if link points to 'Attachment Page' allow it
463 if( $( container ).attr( 'href' ) === $( this ).attr( 'data-permalink' ) ) {
464 valid = true;
465 }
466
467 // links to 'Custom URL' or 'Media File' when flag not set are not valid
468 if( ! valid ) {
469 return;
470 }
471
472 // make this node a gallery recognizable by event listener above
473 $( container ).addClass( 'single-image-gallery' ) ;
474 // blog_id is needed to allow posting comments to correct blog
475 $( container ).data( 'carousel-extra', { blog_id: Number( jetpackCarouselStrings.blog_id ) } );
476 });
477 };
478
479 var methods = {
480 testForData: function(gallery) {
481 gallery = $( gallery ); // make sure we have it as a jQuery object.
482 return !( ! gallery.length || ! gallery.data( 'carousel-extra' ) );
483 },
484
485 testIfOpened: function() {
486 return !!( 'undefined' !== typeof(gallery) && 'undefined' !== typeof(gallery.opened) && gallery.opened );
487 },
488
489 openOrSelectSlide: function( index ) {
490 // The `open` method triggers an asynchronous effect, so we will get an
491 // error if we try to use `open` then `selectSlideAtIndex` immediately
492 // after it. We can only use `selectSlideAtIndex` if the carousel is
493 // already open.
494 if ( ! $( this ).jp_carousel( 'testIfOpened' ) ) {
495 // The `open` method selects the correct slide during the
496 // initialization.
497 $( this ).jp_carousel( 'open', { start_index: index } );
498 } else {
499 gallery.jp_carousel( 'selectSlideAtIndex', index );
500 }
501 },
502
503 open: function(options) {
504 var settings = {
505 'items_selector' : '.gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id], img[data-attachment-id]',
506 'start_index': 0
507 },
508 data = $(this).data('carousel-extra');
509
510 if ( !data ) {
511 return; // don't run if the default gallery functions weren't used
512 }
513
514 prepareGallery( data );
515
516 if ( gallery.jp_carousel( 'testIfOpened' ) ) {
517 return; // don't open if already opened
518 }
519
520 // make sure to stop the page from scrolling behind the carousel overlay, so we don't trigger
521 // infiniscroll for it when enabled (Reader, theme infiniscroll, etc).
522 originalOverflow = $('body').css('overflow');
523 $('body').css('overflow', 'hidden');
524 // prevent html from overflowing on some of the new themes.
525 originalHOverflow = $('html').css('overflow');
526 $('html').css('overflow', 'hidden');
527 scrollPos = $( window ).scrollTop();
528
529 container.data('carousel-extra', data);
530
531 return this.each(function() {
532 // If options exist, lets merge them
533 // with our default settings
534 var $this = $(this);
535
536 if ( options ) {
537 $.extend( settings, options );
538 }
539 if ( -1 === settings.start_index ) {
540 settings.start_index = 0; //-1 returned if can't find index, so start from beginning
541 }
542
543 container.trigger('jp_carousel.beforeOpen').fadeIn('fast',function(){
544 container.trigger('jp_carousel.afterOpen');
545 gallery
546 .jp_carousel('initSlides', $this.find(settings.items_selector), settings.start_index)
547 .jp_carousel('selectSlideAtIndex', settings.start_index);
548 });
549 gallery.html('');
550 });
551 },
552
553 selectSlideAtIndex : function(index){
554 var slides = this.jp_carousel('slides'), selected = slides.eq(index);
555
556 if ( 0 === selected.length ) {
557 selected = slides.eq(0);
558 }
559
560 gallery.jp_carousel('selectSlide', selected, false);
561 return this;
562 },
563
564 close : function(){
565 // make sure to let the page scroll again
566 $('body').css('overflow', originalOverflow);
567 $('html').css('overflow', originalHOverflow);
568 this.jp_carousel( 'clearCommentTextAreaValue' );
569 return container
570 .trigger('jp_carousel.beforeClose')
571 .fadeOut('fast', function(){
572 container.trigger('jp_carousel.afterClose');
573 $( window ).scrollTop( scrollPos );
574 });
575
576 },
577
578 next : function() {
579 this.jp_carousel( 'previousOrNext', 'nextSlide' );
580 },
581
582 previous : function() {
583 this.jp_carousel( 'previousOrNext', 'prevSlide' );
584 },
585
586 previousOrNext : function ( slideSelectionMethodName ) {
587 if ( ! this.jp_carousel( 'hasMultipleImages' ) ) {
588 return false;
589 }
590
591 var slide = gallery.jp_carousel( slideSelectionMethodName );
592
593 if ( slide ) {
594 container.animate( { scrollTop: 0 }, 'fast' );
595 this.jp_carousel( 'clearCommentTextAreaValue' );
596 this.jp_carousel( 'selectSlide', slide );
597 }
598 },
599
600
601
602 selectedSlide : function(){
603 return this.find('.selected');
604 },
605
606 setSlidePosition : function(x) {
607 transitionBegin = Date.now();
608
609 return this.css({
610 '-webkit-transform':'translate3d(' + x + 'px,0,0)',
611 '-moz-transform':'translate3d(' + x + 'px,0,0)',
612 '-ms-transform':'translate(' + x + 'px,0)',
613 '-o-transform':'translate(' + x + 'px,0)',
614 'transform':'translate3d(' + x + 'px,0,0)'
615 });
616 },
617
618 updateSlidePositions : function(animate) {
619 var current = this.jp_carousel( 'selectedSlide' ),
620 galleryWidth = gallery.width(),
621 currentWidth = current.width(),
622 previous = gallery.jp_carousel( 'prevSlide' ),
623 next = gallery.jp_carousel( 'nextSlide' ),
624 previousPrevious = previous.prev(),
625 nextNext = next.next(),
626 left = Math.floor( ( galleryWidth - currentWidth ) * 0.5 );
627
628 current.jp_carousel( 'setSlidePosition', left ).show();
629
630 // minimum width
631 gallery.jp_carousel( 'fitInfo', animate );
632
633 // prep the slides
634 var direction = lastSelectedSlide.is( current.prevAll() ) ? 1 : -1;
635
636 // Since we preload the `previousPrevious` and `nextNext` slides, we need
637 // to make sure they technically visible in the DOM, but invisible to the
638 // user. To hide them from the user, we position them outside the edges
639 // of the window.
640 //
641 // This section of code only applies when there are more than three
642 // slides. Otherwise, the `previousPrevious` and `nextNext` slides will
643 // overlap with the `previous` and `next` slides which must be visible
644 // regardless.
645 if ( 1 === direction ) {
646 if ( ! nextNext.is( previous ) ) {
647 nextNext.jp_carousel( 'setSlidePosition', galleryWidth + next.width() ).show();
648 }
649
650 if ( ! previousPrevious.is( next ) ) {
651 previousPrevious.jp_carousel( 'setSlidePosition', -previousPrevious.width() - currentWidth ).show();
652 }
653 } else {
654 if ( ! nextNext.is( previous ) ) {
655 nextNext.jp_carousel( 'setSlidePosition', galleryWidth + currentWidth ).show();
656 }
657 }
658
659 previous.jp_carousel( 'setSlidePosition', Math.floor( -previous.width() + ( screenPadding * 0.75 ) ) ).show();
660 next.jp_carousel( 'setSlidePosition', Math.ceil( galleryWidth - ( screenPadding * 0.75 ) ) ).show();
661 },
662
663 selectSlide : function(slide, animate){
664 lastSelectedSlide = this.find( '.selected' ).removeClass( 'selected' );
665
666 var slides = gallery.jp_carousel( 'slides' ).css({ 'position': 'fixed' }),
667 current = $( slide ).addClass( 'selected' ).css({ 'position': 'relative' }),
668 attachmentId = current.data( 'attachment-id' ),
669 previous = gallery.jp_carousel( 'prevSlide' ),
670 next = gallery.jp_carousel( 'nextSlide' ),
671 previousPrevious = previous.prev(),
672 nextNext = next.next(),
673 animated,
674 captionHtml;
675
676 // center the main image
677 gallery.jp_carousel( 'loadFullImage', current );
678
679 caption.hide();
680
681 if ( next.length === 0 && slides.length <= 2 ) {
682 $( '.jp-carousel-next-button' ).hide();
683 } else {
684 $( '.jp-carousel-next-button' ).show();
685 }
686
687 if ( previous.length === 0 && slides.length <= 2 ) {
688 $( '.jp-carousel-previous-button' ).hide();
689 } else {
690 $( '.jp-carousel-previous-button' ).show();
691 }
692
693 animated = current
694 .add( previous )
695 .add( previousPrevious )
696 .add( next )
697 .add( nextNext )
698 .jp_carousel( 'loadSlide' );
699
700 // slide the whole view to the x we want
701 slides.not( animated ).hide();
702
703 gallery.jp_carousel( 'updateSlidePositions', animate );
704
705 container.trigger( 'jp_carousel.selectSlide', [current] );
706
707 gallery.jp_carousel( 'getTitleDesc', {
708 title: current.data( 'title' ),
709 desc: current.data( 'desc' )
710 });
711
712 var imageMeta = current.data( 'image-meta' );
713 gallery.jp_carousel( 'updateExif', imageMeta );
714 gallery.jp_carousel( 'updateFullSizeLink', current );
715 gallery.jp_carousel( 'updateMap', imageMeta );
716 gallery.jp_carousel( 'testCommentsOpened', current.data( 'comments-opened' ) );
717 gallery.jp_carousel( 'getComments', {
718 'attachment_id': attachmentId,
719 'offset': 0,
720 'clear': true
721 });
722 $( '#jp-carousel-comment-post-results' ).slideUp();
723
724 // $('<div />').text(sometext).html() is a trick to go to HTML to plain
725 // text (including HTML entities decode, etc)
726 if ( current.data( 'caption' ) ) {
727 captionHtml = $( '<div />' ).text( current.data( 'caption' ) ).html();
728
729 if ( captionHtml === $( '<div />' ).text( current.data( 'title' ) ).html() ) {
730 $( '.jp-carousel-titleanddesc-title' ).fadeOut( 'fast' ).empty();
731 }
732
733 if ( captionHtml === $( '<div />' ).text( current.data( 'desc' ) ).html() ) {
734 $( '.jp-carousel-titleanddesc-desc' ).fadeOut( 'fast' ).empty();
735 }
736
737 caption.html( current.data( 'caption' ) ).fadeIn( 'slow' );
738 } else {
739 caption.fadeOut( 'fast' ).empty();
740 }
741
742 // Record pageview in WP Stats, for each new image loaded full-screen.
743 if ( jetpackCarouselStrings.stats ) {
744 new Image().src = document.location.protocol +
745 '//pixel.wp.com/g.gif?' +
746 jetpackCarouselStrings.stats +
747 '&post=' + encodeURIComponent( attachmentId ) +
748 '&rand=' + Math.random();
749 }
750
751
752 // Load the images for the next and previous slides.
753 $( next ).add( previous ).each( function() {
754 gallery.jp_carousel( 'loadFullImage', $( this ) );
755 });
756
757 window.location.hash = last_known_location_hash = '#jp-carousel-' + attachmentId;
758 },
759
760 slides : function(){
761 return this.find('.jp-carousel-slide');
762 },
763
764 slideDimensions : function(){
765 return {
766 width: $(window).width() - (screenPadding * 2),
767 height: Math.floor( $(window).height() / 100 * proportion - 60 )
768 };
769 },
770
771 loadSlide : function() {
772 return this.each(function(){
773 var slide = $(this);
774 slide.find('img')
775 .one('load', function(){
776 // set the width/height of the image if it's too big
777 slide
778 .jp_carousel('fitSlide',false);
779 });
780 });
781 },
782
783 bestFit : function(){
784 var max = gallery.jp_carousel('slideDimensions'),
785 orig = this.jp_carousel('originalDimensions'),
786 orig_ratio = orig.width / orig.height,
787 w_ratio = 1,
788 h_ratio = 1,
789 width, height;
790
791 if ( orig.width > max.width ) {
792 w_ratio = max.width / orig.width;
793 }
794 if ( orig.height > max.height ) {
795 h_ratio = max.height / orig.height;
796 }
797
798 if ( w_ratio < h_ratio ) {
799 width = max.width;
800 height = Math.floor( width / orig_ratio );
801 } else if ( h_ratio < w_ratio ) {
802 height = max.height;
803 width = Math.floor( height * orig_ratio );
804 } else {
805 width = orig.width;
806 height = orig.height;
807 }
808
809 return {
810 width: width,
811 height: height
812 };
813 },
814
815 fitInfo : function(/*animated*/){
816 var current = this.jp_carousel('selectedSlide'),
817 size = current.jp_carousel('bestFit');
818
819 photo_info.css({
820 'left' : Math.floor( (info.width() - size.width) * 0.5 ),
821 'width' : Math.floor( size.width )
822 });
823
824 return this;
825 },
826
827 fitMeta : function(animated){
828 var newInfoTop = { top: Math.floor( $(window).height() / 100 * proportion + 5 ) + 'px' };
829 var newLeftWidth = { width: ( info.width() - (imageMeta.width() + 80) ) + 'px' };
830
831 if (animated) {
832 info.animate(newInfoTop);
833 leftColWrapper.animate(newLeftWidth);
834 } else {
835 info.animate(newInfoTop);
836 leftColWrapper.css(newLeftWidth);
837 }
838 },
839
840 fitSlide : function(/*animated*/){
841 return this.each(function(){
842 var $this = $(this),
843 dimensions = $this.jp_carousel('bestFit'),
844 method = 'css',
845 max = gallery.jp_carousel('slideDimensions');
846
847 dimensions.left = 0;
848 dimensions.top = Math.floor( (max.height - dimensions.height) * 0.5 ) + 40;
849 $this[method](dimensions);
850 });
851 },
852
853 texturize : function(text) {
854 text = '' + text; // make sure we get a string. Title "1" came in as int 1, for example, which did not support .replace().
855 text = text.replace(/'/g, '&#8217;').replace(/&#039;/g, '&#8217;').replace(/[\u2019]/g, '&#8217;');
856 text = text.replace(/"/g, '&#8221;').replace(/&#034;/g, '&#8221;').replace(/&quot;/g, '&#8221;').replace(/[\u201D]/g, '&#8221;');
857 text = text.replace(/([\w]+)=&#[\d]+;(.+?)&#[\d]+;/g, '$1="$2"'); // untexturize allowed HTML tags params double-quotes
858 return $.trim(text);
859 },
860
861 initSlides : function(items, start_index){
862 if ( items.length < 2 ) {
863 $( '.jp-carousel-next-button, .jp-carousel-previous-button' ).hide();
864 } else {
865 $( '.jp-carousel-next-button, .jp-carousel-previous-button' ).show();
866 }
867
868 // Calculate the new src.
869 items.each(function(/*i*/){
870 var src_item = $(this),
871 orig_size = src_item.data('orig-size') || '',
872 max = gallery.jp_carousel('slideDimensions'),
873 parts = orig_size.split(','),
874 medium_file = src_item.data('medium-file') || '',
875 large_file = src_item.data('large-file') || '',
876 src;
877 orig_size = {width: parseInt(parts[0], 10), height: parseInt(parts[1], 10)};
878
879 src = src_item.data('orig-file');
880
881 src = gallery.jp_carousel('selectBestImageSize', {
882 orig_file : src,
883 orig_width : orig_size.width,
884 orig_height : orig_size.height,
885 max_width : max.width,
886 max_height : max.height,
887 medium_file : medium_file,
888 large_file : large_file
889 });
890
891 // Set the final src
892 $(this).data( 'gallery-src', src );
893 });
894
895 // If the start_index is not 0 then preload the clicked image first.
896 if ( 0 !== start_index ) {
897 $('<img/>')[0].src = $(items[start_index]).data('gallery-src');
898 }
899
900 var useInPageThumbnails = items.first().closest( '.tiled-gallery.type-rectangular' ).length > 0;
901
902 // create the 'slide'
903 items.each(function(i){
904 var src_item = $(this),
905 attachment_id = src_item.data('attachment-id') || 0,
906 comments_opened = src_item.data('comments-opened') || 0,
907 image_meta = src_item.data('image-meta') || {},
908 orig_size = src_item.data('orig-size') || '',
909 thumb_size = { width : src_item[0].naturalWidth, height : src_item[0].naturalHeight },
910 title = src_item.data('image-title') || '',
911 description = src_item.data('image-description') || '',
912 caption = src_item.parents('.gallery-item').find('.gallery-caption').html() || '',
913 src = src_item.data('gallery-src') || '',
914 medium_file = src_item.data('medium-file') || '',
915 large_file = src_item.data('large-file') || '',
916 orig_file = src_item.data('orig-file') || '';
917
918 var tiledCaption = src_item.parents('div.tiled-gallery-item').find('div.tiled-gallery-caption').html();
919 if ( tiledCaption ) {
920 caption = tiledCaption;
921 }
922
923 if ( attachment_id && orig_size.length ) {
924 title = gallery.jp_carousel('texturize', title);
925 description = gallery.jp_carousel('texturize', description);
926 caption = gallery.jp_carousel('texturize', caption);
927
928 // Initially, the image is a 1x1 transparent gif. The preview is shown as a background image on the slide itself.
929 var image = $( '<img/>' )
930 .attr( 'src', 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' )
931 .css( 'width', '100%' )
932 .css( 'height', '100%' );
933
934 var slide = $('<div class="jp-carousel-slide" itemprop="associatedMedia" itemscope itemtype="https://schema.org/ImageObject"></div>')
935 .hide()
936 .css({
937 //'position' : 'fixed',
938 'left' : i < start_index ? -1000 : gallery.width()
939 })
940 .append( image )
941 .appendTo(gallery)
942 .data('src', src )
943 .data('title', title)
944 .data('desc', description)
945 .data('caption', caption)
946 .data('attachment-id', attachment_id)
947 .data('permalink', src_item.parents('a').attr('href'))
948 .data('orig-size', orig_size)
949 .data('comments-opened', comments_opened)
950 .data('image-meta', image_meta)
951 .data('medium-file', medium_file)
952 .data('large-file', large_file)
953 .data('orig-file', orig_file)
954 .data('thumb-size', thumb_size)
955 ;
956
957 if ( useInPageThumbnails ) {
958 // Use the image already loaded in the gallery as a preview.
959 slide
960 .data( 'preview-image', src_item.attr( 'src' ) )
961 .css( {
962 'background-image' : 'url("' + src_item.attr( 'src' ) + '")',
963 'background-size' : '100% 100%',
964 'background-position' : 'center center'
965 } );
966 }
967
968 slide.jp_carousel( 'fitSlide', false );
969 }
970 });
971 return this;
972 },
973
974 selectBestImageSize: function(args) {
975 if ( 'object' !== typeof args ) {
976 args = {};
977 }
978
979 if ( 'undefined' === typeof args.orig_file ) {
980 return '';
981 }
982
983 if ( 'undefined' === typeof args.orig_width || 'undefined' === typeof args.max_width ) {
984 return args.orig_file;
985 }
986
987 if ( 'undefined' === typeof args.medium_file || 'undefined' === typeof args.large_file ) {
988 return args.orig_file;
989 }
990
991 // Check if the image is being served by Photon (using a regular expression on the hostname).
992
993 var imageLinkParser = document.createElement( 'a' );
994 imageLinkParser.href = args.large_file;
995
996 var isPhotonUrl = /^i[0-2].wp.com$/i.test( imageLinkParser.hostname );
997
998 var medium_size_parts = gallery.jp_carousel( 'getImageSizeParts', args.medium_file, args.orig_width, isPhotonUrl );
999 var large_size_parts = gallery.jp_carousel( 'getImageSizeParts', args.large_file, args.orig_width, isPhotonUrl );
1000
1001 var large_width = parseInt( large_size_parts[0], 10 ),
1002 large_height = parseInt( large_size_parts[1], 10 ),
1003 medium_width = parseInt( medium_size_parts[0], 10 ),
1004 medium_height = parseInt( medium_size_parts[1], 10 );
1005
1006 // Assign max width and height.
1007 args.orig_max_width = args.max_width;
1008 args.orig_max_height = args.max_height;
1009
1010 // Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android phones = 1.5, etc)
1011 if ( 'undefined' !== typeof window.devicePixelRatio && window.devicePixelRatio > 1 ) {
1012 args.max_width = args.max_width * window.devicePixelRatio;
1013 args.max_height = args.max_height * window.devicePixelRatio;
1014 }
1015
1016 if ( large_width >= args.max_width || large_height >= args.max_height ) {
1017 return args.large_file;
1018 }
1019
1020 if ( medium_width >= args.max_width || medium_height >= args.max_height ) {
1021 return args.medium_file;
1022 }
1023
1024 if ( isPhotonUrl ) {
1025 // args.orig_file doesn't point to a Photon url, so in this case we use args.large_file
1026 // to return the photon url of the original image.
1027 var largeFileIndex = args.large_file.lastIndexOf( '?' );
1028 var origPhotonUrl = args.large_file;
1029 if ( -1 !== largeFileIndex ) {
1030 origPhotonUrl = args.large_file.substring( 0, largeFileIndex );
1031 // If we have a really large image load a smaller version
1032 // that is closer to the viewable size
1033 if ( args.orig_width > args.max_width || args.orig_height > args.max_height ) {
1034 origPhotonUrl += '?fit=' + args.orig_max_width + '%2C' + args.orig_max_height;
1035 }
1036 }
1037 return origPhotonUrl;
1038 }
1039
1040 return args.orig_file;
1041 },
1042
1043 getImageSizeParts: function( file, orig_width, isPhotonUrl ) {
1044 var size = isPhotonUrl ?
1045 file.replace( /.*=([\d]+%2C[\d]+).*$/, '$1' ) :
1046 file.replace( /.*-([\d]+x[\d]+)\..+$/, '$1' );
1047
1048 var size_parts = ( size !== file ) ?
1049 ( isPhotonUrl ? size.split( '%2C' ) : size.split( 'x' ) ) :
1050 [ orig_width, 0 ];
1051
1052 // If one of the dimensions is set to 9999, then the actual value of that dimension can't be retrieved from the url.
1053 // In that case, we set the value to 0.
1054 if ( '9999' === size_parts[0] ) {
1055 size_parts[0] = '0';
1056 }
1057
1058 if ( '9999' === size_parts[1] ) {
1059 size_parts[1] = '0';
1060 }
1061
1062 return size_parts;
1063 },
1064
1065
1066 originalDimensions: function() {
1067 var splitted = $(this).data('orig-size').split(',');
1068 return {width: parseInt(splitted[0], 10), height: parseInt(splitted[1], 10)};
1069 },
1070
1071 format: function( args ) {
1072 if ( 'object' !== typeof args ) {
1073 args = {};
1074 }
1075 if ( ! args.text || 'undefined' === typeof args.text ) {
1076 return;
1077 }
1078 if ( ! args.replacements || 'undefined' === typeof args.replacements ) {
1079 return args.text;
1080 }
1081 return args.text.replace(/{(\d+)}/g, function( match, number ) {
1082 return typeof args.replacements[number] !== 'undefined' ? args.replacements[number] : match;
1083 });
1084 },
1085
1086 /**
1087 * Returns a number in a fraction format that represents the shutter speed.
1088 * @param Number speed
1089 * @return String
1090 */
1091 shutterSpeed: function( speed ) {
1092 var denominator;
1093
1094 // round to one decimal if value > 1s by multiplying it by 10, rounding, then dividing by 10 again
1095 if ( speed >= 1 ) {
1096 return Math.round( speed * 10 ) / 10 + 's';
1097 }
1098
1099 // If the speed is less than one, we find the denominator by inverting
1100 // the number. Since cameras usually use rational numbers as shutter
1101 // speeds, we should get a nice round number. Or close to one in cases
1102 // like 1/30. So we round it.
1103 denominator = Math.round( 1 / speed );
1104
1105 return '1/' + denominator + 's';
1106 },
1107
1108 parseTitleDesc: function( value ) {
1109 if ( !value.match(' ') && value.match('_') ) {
1110 return '';
1111 }
1112 // Prefix list originally based on http://commons.wikimedia.org/wiki/MediaWiki:Filename-prefix-blacklist
1113 $([
1114 'CIMG', // Casio
1115 'DSC_', // Nikon
1116 'DSCF', // Fuji
1117 'DSCN', // Nikon
1118 'DUW', // some mobile phones
1119 'GEDC', // GE
1120 'IMG', // generic
1121 'JD', // Jenoptik
1122 'MGP', // Pentax
1123 'PICT', // misc.
1124 'Imagen', // misc.
1125 'Foto', // misc.
1126 'DSC', // misc.
1127 'Scan', // Scanners
1128 'SANY', // Sanyo
1129 'SAM', // Samsung
1130 'Screen Shot [0-9]+' // Mac screenshots
1131 ])
1132 .each(function(key, val){
1133 var regex = new RegExp('^' + val);
1134 if ( regex.test(value) ) {
1135 value = '';
1136 return;
1137 }
1138 });
1139 return value;
1140 },
1141
1142 getTitleDesc: function( data ) {
1143 var title ='', desc = '', markup = '', target;
1144
1145 target = $( 'div.jp-carousel-titleanddesc', 'div.jp-carousel-wrap' );
1146 target.hide();
1147
1148 title = gallery.jp_carousel('parseTitleDesc', data.title) || '';
1149 desc = gallery.jp_carousel('parseTitleDesc', data.desc) || '';
1150
1151 if ( title.length || desc.length ) {
1152 // Convert from HTML to plain text (including HTML entities decode, etc)
1153 if ( $('<div />').html( title ).text() === $('<div />').html( desc ).text() ) {
1154 title = '';
1155 }
1156
1157 markup = ( title.length ) ? '<div class="jp-carousel-titleanddesc-title">' + title + '</div>' : '';
1158 markup += ( desc.length ) ? '<div class="jp-carousel-titleanddesc-desc">' + desc + '</div>' : '';
1159
1160 target.html( markup ).fadeIn('slow');
1161 }
1162
1163 $( 'div#jp-carousel-comment-form-container' ).css('margin-top', '20px');
1164 $( 'div#jp-carousel-comments-loading' ).css('margin-top', '20px');
1165 },
1166
1167 // updateExif updates the contents of the exif UL (.jp-carousel-image-exif)
1168 updateExif: function( meta ) {
1169 if ( !meta || 1 !== Number( jetpackCarouselStrings.display_exif ) ) {
1170 return false;
1171 }
1172
1173 var $ul = $( '<ul class=\'jp-carousel-image-exif\'></ul>' );
1174
1175 $.each( meta, function( key, val ) {
1176 if ( 0 === parseFloat(val) || !val.length || -1 === $.inArray( key, $.makeArray( jetpackCarouselStrings.meta_data ) ) ) {
1177 return;
1178 }
1179
1180 switch( key ) {
1181 case 'focal_length':
1182 val = val + 'mm';
1183 break;
1184 case 'shutter_speed':
1185 val = gallery.jp_carousel('shutterSpeed', val);
1186 break;
1187 case 'aperture':
1188 val = 'f/' + val;
1189 break;
1190 }
1191
1192 $ul.append( '<li><h5>' + jetpackCarouselStrings[key] + '</h5>' + val + '</li>' );
1193 });
1194
1195 // Update (replace) the content of the ul
1196 $( 'div.jp-carousel-image-meta ul.jp-carousel-image-exif' ).replaceWith( $ul );
1197 },
1198
1199 // updateFullSizeLink updates the contents of the jp-carousel-image-download link
1200 updateFullSizeLink: function(current) {
1201 if(!current || !current.data) {
1202 return false;
1203 }
1204 var original,
1205 origSize = current.data('orig-size').split(',' ),
1206 imageLinkParser = document.createElement( 'a' );
1207
1208 imageLinkParser.href = current.data( 'src' ).replace( /\?.+$/, '' );
1209
1210 // Is this a Photon URL?
1211 if ( imageLinkParser.hostname.match( /^i[\d]{1}.wp.com$/i ) !== null ) {
1212 original = imageLinkParser.href;
1213 } else {
1214 original = current.data('orig-file').replace(/\?.+$/, '');
1215 }
1216
1217 var permalink = $( '<a>'+gallery.jp_carousel('format', {'text': jetpackCarouselStrings.download_original, 'replacements': origSize})+'</a>' )
1218 .addClass( 'jp-carousel-image-download' )
1219 .attr( 'href', original )
1220 .attr( 'target', '_blank' );
1221
1222 // Update (replace) the content of the anchor
1223 $( 'div.jp-carousel-image-meta a.jp-carousel-image-download' ).replaceWith( permalink );
1224 },
1225
1226 updateMap: function( meta ) {
1227 if ( !meta.latitude || !meta.longitude || 1 !== Number( jetpackCarouselStrings.display_geo ) ) {
1228 return;
1229 }
1230
1231 var latitude = meta.latitude,
1232 longitude = meta.longitude,
1233 $metabox = $( 'div.jp-carousel-image-meta', 'div.jp-carousel-wrap' ),
1234 $mapbox = $( '<div></div>' ),
1235 style = '&scale=2&style=feature:all|element:all|invert_lightness:true|hue:0x0077FF|saturation:-50|lightness:-5|gamma:0.91';
1236
1237 $mapbox
1238 .addClass( 'jp-carousel-image-map' )
1239 .html( '<img width="154" height="154" src="https://maps.googleapis.com/maps/api/staticmap?\
1240 center=' + latitude + ',' + longitude + '&\
1241 zoom=8&\
1242 size=154x154&\
1243 sensor=false&\
1244 markers=size:medium%7Ccolor:blue%7C' + latitude + ',' + longitude + style +'" class="gmap-main" />\
1245 \
1246 <div class="gmap-topright"><div class="imgclip"><img width="175" height="154" src="https://maps.googleapis.com/maps/api/staticmap?\
1247 center=' + latitude + ',' + longitude + '&\
1248 zoom=3&\
1249 size=175x154&\
1250 sensor=false&\
1251 markers=size:small%7Ccolor:blue%7C' + latitude + ',' + longitude + style + '"c /></div></div>\
1252 \
1253 ' )
1254 .prependTo( $metabox );
1255 },
1256
1257 testCommentsOpened: function( opened ) {
1258 if ( 1 === parseInt( opened, 10 ) ) {
1259 $('.jp-carousel-buttons').fadeIn('fast');
1260 commentForm.fadeIn('fast');
1261 } else {
1262 $('.jp-carousel-buttons').fadeOut('fast');
1263 commentForm.fadeOut('fast');
1264 }
1265 },
1266
1267 getComments: function( args ) {
1268 clearInterval( commentInterval );
1269
1270 if ( 'object' !== typeof args ) {
1271 return;
1272 }
1273
1274 if ( 'undefined' === typeof args.attachment_id || ! args.attachment_id ) {
1275 return;
1276 }
1277
1278 if ( ! args.offset || 'undefined' === typeof args.offset || args.offset < 1 ) {
1279 args.offset = 0;
1280 }
1281
1282 var comments = $('.jp-carousel-comments'),
1283 commentsLoading = $('#jp-carousel-comments-loading').show();
1284
1285 if ( args.clear ) {
1286 comments.hide().empty();
1287 }
1288
1289 $.ajax({
1290 type: 'GET',
1291 url: jetpackCarouselStrings.ajaxurl,
1292 dataType: 'json',
1293 data: {
1294 action: 'get_attachment_comments',
1295 nonce: jetpackCarouselStrings.nonce,
1296 id: args.attachment_id,
1297 offset: args.offset
1298 },
1299 success: function(data/*, status, xhr*/) {
1300 if ( args.clear ) {
1301 comments.fadeOut('fast').empty();
1302 }
1303
1304 $( data ).each(function(){
1305 var comment = $('<div></div>')
1306 .addClass('jp-carousel-comment')
1307 .attr('id', 'jp-carousel-comment-' + this['id'])
1308 .html(
1309 '<div class="comment-gravatar">' +
1310 this['gravatar_markup'] +
1311 '</div>' +
1312 '<div class="comment-author">' +
1313 this['author_markup'] +
1314 '</div>' +
1315 '<div class="comment-date">' +
1316 this['date_gmt'] +
1317 '</div>' +
1318 '<div class="comment-content">' +
1319 this['content'] +
1320 '</div>'
1321 );
1322 comments.append(comment);
1323
1324 // Set the interval to check for a new page of comments.
1325 clearInterval( commentInterval );
1326 commentInterval = setInterval( function() {
1327 if ( ( $('.jp-carousel-overlay').height() - 150 ) < $('.jp-carousel-wrap').scrollTop() + $(window).height() ) {
1328 gallery.jp_carousel('getComments',{ attachment_id: args.attachment_id, offset: args.offset + 10, clear: false });
1329 clearInterval( commentInterval );
1330 }
1331 }, 300 );
1332 });
1333
1334 // Verify (late) that the user didn't repeatldy click the arrows really fast, in which case the requested
1335 // attachment id might no longer match the current attachment id by the time we get the data back or a now
1336 // registered infiniscroll event kicks in, so we don't ever display comments for the wrong image by mistake.
1337 var current = $('.jp-carousel div.selected');
1338 if ( current && current.data && current.data('attachment-id') != args.attachment_id ) { // jshint ignore:line
1339 comments.fadeOut('fast');
1340 comments.empty();
1341 return;
1342 }
1343
1344 // Increase the height of the background, semi-transparent overlay to match the new length of the comments list.
1345 $('.jp-carousel-overlay').height( $(window).height() + titleAndDescription.height() + commentForm.height() + ( (comments.height() > 0) ? comments.height() : imageMeta.height() ) + 200 );
1346
1347 comments.show();
1348 commentsLoading.hide();
1349 },
1350 error: function(xhr, status, error) {
1351 // TODO: proper error handling
1352 console.log( 'Comment get fail...', xhr, status, error );
1353 comments.fadeIn('fast');
1354 commentsLoading.fadeOut('fast');
1355 }
1356 });
1357 },
1358
1359 postCommentError: function(args) {
1360 if ( 'object' !== typeof args ) {
1361 args = {};
1362 }
1363 if ( ! args.field || 'undefined' === typeof args.field || ! args.error || 'undefined' === typeof args.error ) {
1364 return;
1365 }
1366 $('#jp-carousel-comment-post-results').slideUp('fast').html('<span class="jp-carousel-comment-post-error">'+args.error+'</span>').slideDown('fast');
1367 $('#jp-carousel-comment-form-spinner').spin(false);
1368 },
1369
1370 setCommentIframeSrc: function(attachment_id) {
1371 var iframe = $('#jp-carousel-comment-iframe');
1372 // Set the proper irame src for the current attachment id
1373 if (iframe && iframe.length) {
1374 iframe.attr('src', iframe.attr('src').replace(/(postid=)\d+/, '$1'+attachment_id) );
1375 iframe.attr('src', iframe.attr('src').replace(/(%23.+)?$/, '%23jp-carousel-'+attachment_id) );
1376 }
1377 },
1378
1379 clearCommentTextAreaValue: function() {
1380 var commentTextArea = $('#jp-carousel-comment-form-comment-field');
1381 if ( commentTextArea ) {
1382 commentTextArea.val('');
1383 }
1384 },
1385
1386 nextSlide : function () {
1387 var slides = this.jp_carousel( 'slides' );
1388 var selected = this.jp_carousel( 'selectedSlide' );
1389
1390 if ( selected.length === 0 || ( slides.length > 2 && selected.is( slides.last() ) ) ) {
1391 return slides.first();
1392 }
1393
1394 return selected.next();
1395 },
1396
1397 prevSlide : function () {
1398 var slides = this.jp_carousel( 'slides' );
1399 var selected = this.jp_carousel( 'selectedSlide' );
1400
1401 if ( selected.length === 0 || ( slides.length > 2 && selected.is( slides.first() ) ) ) {
1402 return slides.last();
1403 }
1404
1405 return selected.prev();
1406 },
1407
1408 loadFullImage : function ( slide ) {
1409 var image = slide.find( 'img:first' );
1410
1411 if ( ! image.data( 'loaded' ) ) {
1412 // If the width of the slide is smaller than the width of the "thumbnail" we're already using,
1413 // don't load the full image.
1414
1415 image.on( 'load.jetpack', function () {
1416 image.off( 'load.jetpack' );
1417 $( this ).closest( '.jp-carousel-slide' ).css( 'background-image', '' );
1418 } );
1419
1420 if ( ! slide.data( 'preview-image' ) || ( slide.data( 'thumb-size' ) && slide.width() > slide.data( 'thumb-size' ).width ) ) {
1421 image.attr( 'src', image.closest( '.jp-carousel-slide' ).data( 'src' ) ).attr('itemprop', 'image');
1422 } else {
1423 image.attr( 'src', slide.data( 'preview-image' ) ).attr('itemprop', 'image');
1424 }
1425
1426 image.data( 'loaded', 1 );
1427 }
1428 },
1429
1430 hasMultipleImages : function () {
1431 return gallery.jp_carousel('slides').length > 1;
1432 }
1433 };
1434
1435 $.fn.jp_carousel = function(method){
1436 // ask for the HTML of the gallery
1437 // Method calling logic
1438 if ( methods[method] ) {
1439 return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
1440 } else if ( typeof method === 'object' || ! method ) {
1441 return methods.open.apply( this, arguments );
1442 } else {
1443 $.error( 'Method ' + method + ' does not exist on jQuery.jp_carousel' );
1444 }
1445
1446 };
1447
1448 // register the event listener for starting the gallery
1449 $(document.body).on('click.jp-carousel', 'div.gallery, div.tiled-gallery, ul.wp-block-gallery, div.wp-block-jetpack-tiled-gallery, a.single-image-gallery', function( e ) {
1450 if ( ! $(this).jp_carousel( 'testForData', e.currentTarget ) ) {
1451 return;
1452 }
1453
1454 // Do not open the modal if we are looking at a gallery caption from before WP5, which may contain a link.
1455 if ( $(e.target).parent().hasClass('gallery-caption') ) {
1456 return;
1457 }
1458
1459 // Do not open the modal if we are looking at a caption of a gallery block, which may contain a link.
1460 if ( $(e.target).parent().is('figcaption') ) {
1461 return;
1462 }
1463
1464 e.preventDefault();
1465
1466 // Stopping propagation in case there are parent elements
1467 // with .gallery or .tiled-gallery class
1468 e.stopPropagation();
1469 $(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'))});
1470 });
1471
1472 // handle lightbox (single image gallery) for images linking to 'Attachment Page'
1473 if ( 1 === Number( jetpackCarouselStrings.single_image_gallery ) ) {
1474 processSingleImageGallery();
1475 $( document.body ).on( 'post-load', function() {
1476 processSingleImageGallery();
1477 } );
1478 }
1479
1480 // Makes carousel work on page load and when back button leads to same URL with carousel hash (ie: no actual document.ready trigger)
1481 $( window ).on( 'hashchange.jp-carousel', function () {
1482
1483 var hashRegExp = /jp-carousel-(\d+)/,
1484 matches, attachmentId, galleries, selectedThumbnail;
1485
1486 if ( ! window.location.hash || ! hashRegExp.test( window.location.hash ) ) {
1487 if ( gallery && gallery.opened ) {
1488 container.jp_carousel( 'close' );
1489 }
1490
1491 return;
1492 }
1493
1494 if ( ( window.location.hash === last_known_location_hash ) && gallery.opened ) {
1495 return;
1496 }
1497
1498 if ( window.location.hash && gallery && !gallery.opened && history.back) {
1499 history.back();
1500 return;
1501 }
1502
1503 last_known_location_hash = window.location.hash;
1504 matches = window.location.hash.match( hashRegExp );
1505 attachmentId = parseInt( matches[1], 10 );
1506 galleries = $( 'div.gallery, div.tiled-gallery, a.single-image-gallery, ul.wp-block-gallery, div.wp-block-jetpack-tiled-gallery' );
1507
1508 // Find the first thumbnail that matches the attachment ID in the location
1509 // hash, then open the gallery that contains it.
1510 galleries.each( function( _, galleryEl ) {
1511 $( galleryEl ).find('img').each( function( imageIndex, imageEl ) {
1512 if ( $( imageEl ).data( 'attachment-id' ) === parseInt( attachmentId, 10 ) ) {
1513 selectedThumbnail = { index: imageIndex, gallery: galleryEl };
1514 return false;
1515 }
1516 });
1517
1518 if ( selectedThumbnail ) {
1519 $( selectedThumbnail.gallery )
1520 .jp_carousel( 'openOrSelectSlide', selectedThumbnail.index );
1521 return false;
1522 }
1523 });
1524 });
1525
1526 if ( window.location.hash ) {
1527 $( window ).trigger( 'hashchange' );
1528 }
1529 });
1530
1531 /**
1532 * jQuery Plugin to obtain touch gestures from iPhone, iPod Touch and iPad, should also work with Android mobile phones (not tested yet!)
1533 * Common usage: wipe images (left and right to show the previous or next image)
1534 *
1535 * @author Andreas Waltl, netCU Internetagentur (http://www.netcu.de)
1536 * Version 1.1.1, modified to pass the touchmove event to the callbacks.
1537 */
1538 (function($) {
1539 $.fn.touchwipe = function(settings) {
1540 var config = {
1541 min_move_x: 20,
1542 min_move_y: 20,
1543 wipeLeft: function(/*e*/) { },
1544 wipeRight: function(/*e*/) { },
1545 wipeUp: function(/*e*/) { },
1546 wipeDown: function(/*e*/) { },
1547 preventDefaultEvents: true
1548 };
1549
1550 if (settings) {
1551 $.extend(config, settings);
1552 }
1553
1554 this.each(function() {
1555 var startX;
1556 var startY;
1557 var isMoving = false;
1558
1559 function cancelTouch() {
1560 this.removeEventListener('touchmove', onTouchMove);
1561 startX = null;
1562 isMoving = false;
1563 }
1564
1565 function onTouchMove(e) {
1566 if(config.preventDefaultEvents) {
1567 e.preventDefault();
1568 }
1569 if(isMoving) {
1570 var x = e.touches[0].pageX;
1571 var y = e.touches[0].pageY;
1572 var dx = startX - x;
1573 var dy = startY - y;
1574 if(Math.abs(dx) >= config.min_move_x) {
1575 cancelTouch();
1576 if(dx > 0) {
1577 config.wipeLeft(e);
1578 } else {
1579 config.wipeRight(e);
1580 }
1581 }
1582 else if(Math.abs(dy) >= config.min_move_y) {
1583 cancelTouch();
1584 if(dy > 0) {
1585 config.wipeDown(e);
1586 } else {
1587 config.wipeUp(e);
1588 }
1589 }
1590 }
1591 }
1592
1593 function onTouchStart(e)
1594 {
1595 if (e.touches.length === 1) {
1596 startX = e.touches[0].pageX;
1597 startY = e.touches[0].pageY;
1598 isMoving = true;
1599 this.addEventListener('touchmove', onTouchMove, false);
1600 }
1601 }
1602 if ('ontouchstart' in document.documentElement) {
1603 this.addEventListener('touchstart', onTouchStart, false);
1604 }
1605 });
1606
1607 return this;
1608 };
1609 })(jQuery);
1610