PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.1.3
Jetpack – WP Security, Backup, Speed, & Growth v12.1.3
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 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / carousel / jetpack-carousel.js
jetpack / modules / carousel Last commit date
jetpack-carousel-rtl.css 3 years ago jetpack-carousel.css 3 years ago jetpack-carousel.js 3 years ago jetpack-carousel.php 3 years ago readme.md 5 years ago swiper-bundle.css 5 years ago swiper-bundle.js 4 years ago
jetpack-carousel.js
1729 lines
1 /* global wpcom, jetpackCarouselStrings, DocumentTouch */
2
3 ( function () {
4 'use strict';
5 var swiper;
6 /////////////////////////////////////
7 // Utility functions
8 /////////////////////////////////////
9 var util = ( function () {
10 var noop = function () {};
11
12 function texturize( text ) {
13 // Ensure we get a string.
14 text = text + '';
15 text = text.replace( /'/g, '’' ).replace( /'/g, '’' );
16 text = text
17 .replace( /"/g, '”' )
18 .replace( /"/g, '”' )
19 .replace( /"/g, '”' )
20 .replace( /[\u201D]/g, '”' );
21 // Untexturize allowed HTML tags params double-quotes.
22 text = text.replace( /([\w]+)=&#[\d]+;(.+?)&#[\d]+;/g, '$1="$2"' );
23 return text.trim();
24 }
25
26 function applyReplacements( text, replacements ) {
27 if ( ! text ) {
28 return;
29 }
30 if ( ! replacements ) {
31 return text;
32 }
33 return text.replace( /{(\d+)}/g, function ( match, number ) {
34 return typeof replacements[ number ] !== 'undefined' ? replacements[ number ] : match;
35 } );
36 }
37
38 function getBackgroundImage( imgEl ) {
39 var canvas = document.createElement( 'canvas' ),
40 context = canvas.getContext && canvas.getContext( '2d' );
41
42 if ( ! imgEl ) {
43 return;
44 }
45
46 context.filter = 'blur(20px) ';
47 context.drawImage( imgEl, 0, 0 );
48 var url = canvas.toDataURL( 'image/png' );
49 canvas = null;
50
51 return url;
52 }
53
54 return {
55 noop: noop,
56 texturize: texturize,
57 applyReplacements: applyReplacements,
58 getBackgroundImage: getBackgroundImage,
59 };
60 } )();
61
62 /////////////////////////////////////
63 // DOM-related utility functions
64 /////////////////////////////////////
65 var domUtil = ( function () {
66 // Helper matches function (not a polyfill), compatible with IE 11.
67 function matches( el, sel ) {
68 if ( Element.prototype.matches ) {
69 return el.matches( sel );
70 }
71
72 if ( Element.prototype.msMatchesSelector ) {
73 return el.msMatchesSelector( sel );
74 }
75 }
76
77 // Helper closest parent node function (not a polyfill) based on
78 // https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
79 function closest( el, sel ) {
80 if ( el.closest ) {
81 return el.closest( sel );
82 }
83
84 var current = el;
85
86 do {
87 if ( matches( current, sel ) ) {
88 return current;
89 }
90 current = current.parentElement || current.parentNode;
91 } while ( current !== null && current.nodeType === 1 );
92
93 return null;
94 }
95
96 function hide( el ) {
97 if ( el ) {
98 el.style.display = 'none';
99 }
100 }
101
102 function show( el ) {
103 if ( el ) {
104 // Everything we show and hide in Carousel is currently a block,
105 // so we can make this really straightforward.
106 el.style.display = 'block';
107 }
108 }
109
110 function fade( el, start, end, callback ) {
111 if ( ! el ) {
112 return callback();
113 }
114
115 // Prepare for transition.
116 // Ensure the item is in the render tree, in its initial state.
117 el.style.removeProperty( 'display' );
118 el.style.opacity = start;
119 el.style.transition = 'opacity 0.2s';
120 el.style.pointerEvents = 'none';
121
122 var finished = function ( e ) {
123 if ( e.target === el && e.propertyName === 'opacity' ) {
124 el.style.removeProperty( 'transition' );
125 el.style.removeProperty( 'opacity' );
126 el.style.removeProperty( 'pointer-events' );
127 el.removeEventListener( 'transitionend', finished );
128 el.removeEventListener( 'transitioncancel', finished );
129 callback();
130 }
131 };
132
133 requestAnimationFrame( function () {
134 // Double rAF for browser compatibility.
135 requestAnimationFrame( function () {
136 el.addEventListener( 'transitionend', finished );
137 el.addEventListener( 'transitioncancel', finished );
138 // Trigger transition.
139 el.style.opacity = end;
140 } );
141 } );
142 }
143
144 function fadeIn( el, callback ) {
145 callback = callback || util.noop;
146 fade( el, '0', '1', callback );
147 }
148
149 function fadeOut( el, callback ) {
150 callback = callback || util.noop;
151 fade( el, '1', '0', function () {
152 if ( el ) {
153 el.style.display = 'none';
154 }
155 callback();
156 } );
157 }
158
159 function emitEvent( el, type, detail ) {
160 var e;
161 try {
162 e = new CustomEvent( type, {
163 bubbles: true,
164 cancelable: true,
165 detail: detail || null,
166 } );
167 } catch ( err ) {
168 e = document.createEvent( 'CustomEvent' );
169 e.initCustomEvent( type, true, true, detail || null );
170 }
171 el.dispatchEvent( e );
172 }
173
174 // From: https://easings.net/#easeInOutQuad
175 function easeInOutQuad( num ) {
176 return num < 0.5 ? 2 * num * num : 1 - Math.pow( -2 * num + 2, 2 ) / 2;
177 }
178
179 function getFooterClearance( container ) {
180 var footer = container.querySelector( '.jp-carousel-info-footer' );
181 var infoArea = container.querySelector( '.jp-carousel-info-extra' );
182 var contentArea = container.querySelector( '.jp-carousel-info-content-wrapper' );
183
184 if ( footer && infoArea && contentArea ) {
185 var styles = window.getComputedStyle( infoArea );
186 var padding = parseInt( styles.paddingTop, 10 ) + parseInt( styles.paddingBottom, 10 );
187 padding = isNaN( padding ) ? 0 : padding;
188 return contentArea.offsetHeight + footer.offsetHeight + padding;
189 }
190 return 0;
191 }
192
193 function isTouch() {
194 return (
195 'ontouchstart' in window || ( window.DocumentTouch && document instanceof DocumentTouch )
196 );
197 }
198
199 function scrollToElement( el, container, callback ) {
200 if ( ! el || ! container ) {
201 if ( callback ) {
202 return callback();
203 }
204 return;
205 }
206
207 // For iOS Safari compatibility, use JS to set the minimum height.
208 var infoArea = container.querySelector( '.jp-carousel-info-extra' );
209 if ( infoArea ) {
210 // 64px is the same height as `.jp-carousel-info-footer` in the CSS.
211 infoArea.style.minHeight = window.innerHeight - 64 + 'px';
212 }
213
214 var isScrolling = true;
215 var startTime = Date.now();
216 var duration = 300;
217 var originalPosition = container.scrollTop;
218 var targetPosition = Math.max(
219 0,
220 el.offsetTop - Math.max( 0, window.innerHeight - getFooterClearance( container ) )
221 );
222 var distance = targetPosition - container.scrollTop;
223 distance = Math.min( distance, container.scrollHeight - window.innerHeight );
224
225 function stopScroll() {
226 isScrolling = false;
227 }
228
229 function runScroll() {
230 var now = Date.now();
231 var progress = easeInOutQuad( ( now - startTime ) / duration );
232
233 progress = progress > 1 ? 1 : progress;
234 var newVal = progress * distance;
235 container.scrollTop = originalPosition + newVal;
236
237 if ( now <= startTime + duration && isScrolling ) {
238 return requestAnimationFrame( runScroll );
239 }
240 if ( callback ) {
241 callback();
242 }
243 if ( infoArea ) {
244 infoArea.style.minHeight = '';
245 }
246 isScrolling = false;
247 container.removeEventListener( 'wheel', stopScroll );
248 }
249
250 // Allow scroll to be cancelled by user interaction.
251 container.addEventListener( 'wheel', stopScroll );
252 runScroll();
253 }
254
255 function getJSONAttribute( el, attr ) {
256 if ( ! el || ! el.hasAttribute( attr ) ) {
257 return undefined;
258 }
259
260 try {
261 return JSON.parse( el.getAttribute( attr ) );
262 } catch ( e ) {
263 return undefined;
264 }
265 }
266
267 function convertToPlainText( html ) {
268 var dummy = document.createElement( 'div' );
269 dummy.textContent = html;
270 return dummy.innerHTML;
271 }
272
273 function stripHTML( text ) {
274 return text.replace( /<[^>]*>?/gm, '' );
275 }
276
277 return {
278 closest: closest,
279 matches: matches,
280 hide: hide,
281 show: show,
282 fadeIn: fadeIn,
283 fadeOut: fadeOut,
284 scrollToElement: scrollToElement,
285 getJSONAttribute: getJSONAttribute,
286 convertToPlainText: convertToPlainText,
287 stripHTML: stripHTML,
288 emitEvent: emitEvent,
289 isTouch: isTouch,
290 };
291 } )();
292
293 /////////////////////////////////////
294 // Carousel implementation
295 /////////////////////////////////////
296 function init() {
297 var commentInterval;
298 var screenPadding;
299 var originalOverflow;
300 var originalHOverflow;
301 var scrollPos;
302
303 var lastKnownLocationHash = '';
304 var isUserTyping = false;
305
306 var gallerySelector =
307 'div.gallery, div.tiled-gallery, ul.wp-block-gallery, ul.blocks-gallery-grid, ' +
308 'figure.wp-block-gallery.has-nested-images, div.wp-block-jetpack-tiled-gallery, a.single-image-gallery';
309
310 // Selector for items within a gallery or tiled gallery.
311 var galleryItemSelector =
312 '.gallery-item, .tiled-gallery-item, .blocks-gallery-item, ' + ' .tiled-gallery__item';
313
314 // Selector for all items including single images.
315 var itemSelector = galleryItemSelector + ', .wp-block-image';
316
317 var carousel = {};
318
319 var stat =
320 typeof wpcom !== 'undefined' && wpcom.carousel && wpcom.carousel.stat
321 ? wpcom.carousel.stat
322 : util.noop;
323
324 var pageview =
325 typeof wpcom !== 'undefined' && wpcom.carousel && wpcom.carousel.pageview
326 ? wpcom.carousel.pageview
327 : util.noop;
328
329 function handleKeyboardEvent( e ) {
330 if ( ! isUserTyping ) {
331 switch ( e.which ) {
332 case 38: // up
333 e.preventDefault();
334 carousel.overlay.scrollTop -= 100;
335 break;
336 case 40: // down
337 e.preventDefault();
338 carousel.overlay.scrollTop += 100;
339 break;
340 case 39: // right
341 e.preventDefault();
342 swiper.slideNext();
343 break;
344 case 37: // left
345 case 8: // backspace
346 e.preventDefault();
347 swiper.slidePrev();
348 break;
349 case 27: // escape
350 e.preventDefault();
351 closeCarousel();
352 break;
353 default:
354 break;
355 }
356 }
357 }
358
359 function disableKeyboardNavigation() {
360 isUserTyping = true;
361 }
362
363 function enableKeyboardNavigation() {
364 isUserTyping = false;
365 }
366
367 function calculatePadding() {
368 var baseScreenPadding = 110;
369 screenPadding = baseScreenPadding;
370
371 if ( window.innerWidth <= 760 ) {
372 screenPadding = Math.round( ( window.innerWidth / 760 ) * baseScreenPadding );
373
374 if ( screenPadding < 40 && domUtil.isTouch() ) {
375 screenPadding = 0;
376 }
377 }
378 }
379
380 function initializeCarousel() {
381 if ( ! carousel.overlay ) {
382 carousel.overlay = document.querySelector( '.jp-carousel-overlay' );
383 carousel.container = carousel.overlay.querySelector( '.jp-carousel-wrap' );
384 carousel.gallery = carousel.container.querySelector( '.jp-carousel' );
385 carousel.info = carousel.overlay.querySelector( '.jp-carousel-info' );
386 carousel.caption = carousel.info.querySelector( '.jp-carousel-caption' );
387 carousel.commentField = carousel.overlay.querySelector(
388 '#jp-carousel-comment-form-comment-field'
389 );
390 carousel.emailField = carousel.overlay.querySelector(
391 '#jp-carousel-comment-form-email-field'
392 );
393 carousel.authorField = carousel.overlay.querySelector(
394 '#jp-carousel-comment-form-author-field'
395 );
396 carousel.urlField = carousel.overlay.querySelector( '#jp-carousel-comment-form-url-field' );
397
398 calculatePadding();
399
400 [
401 carousel.commentField,
402 carousel.emailField,
403 carousel.authorField,
404 carousel.urlField,
405 ].forEach( function ( field ) {
406 if ( field ) {
407 field.addEventListener( 'focus', disableKeyboardNavigation );
408 field.addEventListener( 'blur', enableKeyboardNavigation );
409 }
410 } );
411
412 carousel.overlay.addEventListener( 'click', function ( e ) {
413 var target = e.target;
414 var isTargetCloseHint = !! domUtil.closest( target, '.jp-carousel-close-hint' );
415 var isSmallScreen = !! window.matchMedia( '(max-device-width: 760px)' ).matches;
416 if ( target === carousel.overlay ) {
417 if ( isSmallScreen ) {
418 return;
419 } else {
420 closeCarousel();
421 }
422 } else if ( isTargetCloseHint ) {
423 closeCarousel();
424 } else if ( target.classList.contains( 'jp-carousel-image-download' ) ) {
425 stat( 'download_original_click' );
426 } else if ( target.classList.contains( 'jp-carousel-comment-login' ) ) {
427 handleCommentLoginClick( e );
428 } else if ( domUtil.closest( target, '#jp-carousel-comment-form-container' ) ) {
429 handleCommentFormClick( e );
430 } else if (
431 domUtil.closest( target, '.jp-carousel-photo-icons-container' ) ||
432 target.classList.contains( 'jp-carousel-photo-title' )
433 ) {
434 handleFooterElementClick( e );
435 } else if ( ! domUtil.closest( target, '.jp-carousel-info' ) ) {
436 return;
437 }
438 } );
439
440 window.addEventListener( 'keydown', handleKeyboardEvent );
441
442 carousel.overlay.addEventListener( 'jp_carousel.afterOpen', function () {
443 enableKeyboardNavigation();
444
445 // Don't show navigation if there's only one image.
446 if ( carousel.slides.length <= 1 ) {
447 return;
448 }
449 // Show dot pagination if slide count is <= 5, otherwise show n/total.
450 if ( carousel.slides.length <= 5 ) {
451 domUtil.show( carousel.info.querySelector( '.jp-swiper-pagination' ) );
452 } else {
453 domUtil.show( carousel.info.querySelector( '.jp-carousel-pagination' ) );
454 }
455 } );
456
457 carousel.overlay.addEventListener( 'jp_carousel.beforeClose', function () {
458 disableKeyboardNavigation();
459
460 // Fixes some themes where closing carousel brings view back to top.
461 document.documentElement.style.removeProperty( 'height' );
462
463 // If we disable the swiper (because there's only one image)
464 // we have to re-enable it here again as Swiper doesn't, for some reason,
465 // show the navigation buttons again after reinitialization.
466 if ( swiper ) {
467 swiper.enable();
468 }
469
470 // Hide pagination.
471 domUtil.hide( carousel.info.querySelector( '.jp-swiper-pagination' ) );
472 domUtil.hide( carousel.info.querySelector( '.jp-carousel-pagination' ) );
473 } );
474
475 carousel.overlay.addEventListener( 'jp_carousel.afterClose', function () {
476 // don't force the browser back when the carousel closes.
477 if ( window.history.pushState ) {
478 history.pushState(
479 '',
480 document.title,
481 window.location.pathname + window.location.search
482 );
483 } else {
484 window.location.href = '';
485 }
486 lastKnownLocationHash = '';
487 carousel.isOpen = false;
488 } );
489
490 // Prevent native browser zooming
491 carousel.overlay.addEventListener( 'touchstart', function ( e ) {
492 if ( e.touches.length > 1 ) {
493 e.preventDefault();
494 }
495 } );
496 }
497 }
498
499 function handleCommentLoginClick() {
500 var slide = carousel.currentSlide;
501 var attachmentId = slide ? slide.attrs.attachmentId : '0';
502
503 window.location.href = jetpackCarouselStrings.login_url + '%23jp-carousel-' + attachmentId;
504 }
505
506 function updatePostResults( msg, isSuccess ) {
507 var results = carousel.overlay.querySelector( '#jp-carousel-comment-post-results' );
508 var elClass = 'jp-carousel-comment-post-' + ( isSuccess ? 'success' : 'error' );
509 results.innerHTML = '<span class="' + elClass + '">' + msg + '</span>';
510 domUtil.hide( carousel.overlay.querySelector( '#jp-carousel-comment-form-spinner' ) );
511 carousel.overlay
512 .querySelector( '#jp-carousel-comment-form' )
513 .classList.remove( 'jp-carousel-is-disabled' );
514 domUtil.show( results );
515 }
516
517 function handleCommentFormClick( e ) {
518 var target = e.target;
519 var data = domUtil.getJSONAttribute( carousel.container, 'data-carousel-extra' ) || {};
520 var attachmentId = carousel.currentSlide.attrs.attachmentId;
521
522 var wrapper = document.querySelector( '#jp-carousel-comment-form-submit-and-info-wrapper' );
523 var spinner = document.querySelector( '#jp-carousel-comment-form-spinner' );
524 var submit = document.querySelector( '#jp-carousel-comment-form-button-submit' );
525 var form = document.querySelector( '#jp-carousel-comment-form' );
526
527 if (
528 carousel.commentField &&
529 carousel.commentField.getAttribute( 'id' ) === target.getAttribute( 'id' )
530 ) {
531 // For first page load
532 disableKeyboardNavigation();
533 domUtil.show( wrapper );
534 } else if ( domUtil.matches( target, 'input[type="submit"]' ) ) {
535 e.preventDefault();
536 e.stopPropagation();
537
538 domUtil.show( spinner );
539 form.classList.add( 'jp-carousel-is-disabled' );
540
541 var ajaxData = {
542 action: 'post_attachment_comment',
543 nonce: jetpackCarouselStrings.nonce,
544 blog_id: data.blog_id,
545 id: attachmentId,
546 comment: carousel.commentField.value,
547 };
548
549 if ( ! ajaxData.comment.length ) {
550 updatePostResults( jetpackCarouselStrings.no_comment_text, false );
551 return;
552 }
553
554 if ( Number( jetpackCarouselStrings.is_logged_in ) !== 1 ) {
555 ajaxData.email = carousel.emailField.value;
556 ajaxData.author = carousel.authorField.value;
557 ajaxData.url = carousel.urlField.value;
558
559 if ( Number( jetpackCarouselStrings.require_name_email ) === 1 ) {
560 if ( ! ajaxData.email.length || ! ajaxData.email.match( '@' ) ) {
561 updatePostResults( jetpackCarouselStrings.no_comment_email, false );
562 return;
563 } else if ( ! ajaxData.author.length ) {
564 updatePostResults( jetpackCarouselStrings.no_comment_author, false );
565 return;
566 }
567 }
568 }
569
570 var xhr = new XMLHttpRequest();
571 xhr.open( 'POST', jetpackCarouselStrings.ajaxurl, true );
572 xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
573 xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
574
575 xhr.onreadystatechange = function () {
576 if (
577 this.readyState === XMLHttpRequest.DONE &&
578 this.status >= 200 &&
579 this.status < 300
580 ) {
581 var response;
582 try {
583 response = JSON.parse( this.response );
584 } catch ( error ) {
585 updatePostResults( jetpackCarouselStrings.comment_post_error, false );
586 return;
587 }
588 if ( response.comment_status === 'approved' ) {
589 updatePostResults( jetpackCarouselStrings.comment_approved, true );
590 } else if ( response.comment_status === 'unapproved' ) {
591 updatePostResults( jetpackCarouselStrings.comment_unapproved, true );
592 } else {
593 // 'deleted', 'spam', false
594 updatePostResults( jetpackCarouselStrings.comment_post_error, false );
595 }
596 clearCommentTextAreaValue();
597 fetchComments( attachmentId );
598 submit.value = jetpackCarouselStrings.post_comment;
599 domUtil.hide( spinner );
600 form.classList.remove( 'jp-carousel-is-disabled' );
601 } else {
602 // TODO: Add error handling and display here
603 updatePostResults( jetpackCarouselStrings.comment_post_error, false );
604 }
605 };
606
607 var params = [];
608 for ( var item in ajaxData ) {
609 if ( item ) {
610 // Encode each form element into a URI-compatible string.
611 var encoded = encodeURIComponent( item ) + '=' + encodeURIComponent( ajaxData[ item ] );
612 // In x-www-form-urlencoded, spaces should be `+`, not `%20`.
613 params.push( encoded.replace( /%20/g, '+' ) );
614 }
615 }
616 var encodedData = params.join( '&' );
617
618 xhr.send( encodedData );
619 }
620 }
621
622 /**
623 * Handles clicks to icons and other action elements in the icon container.
624 * @param {MouseEvent|TouchEvent|KeyBoardEvent} Event object.
625 */
626 function handleFooterElementClick( e ) {
627 e.preventDefault();
628
629 var target = e.target;
630 var extraInfoContainer = carousel.info.querySelector( '.jp-carousel-info-extra' );
631 var photoMetaContainer = carousel.info.querySelector( '.jp-carousel-image-meta' );
632 var commentsContainer = carousel.info.querySelector( '.jp-carousel-comments-wrapper' );
633 var infoIcon = carousel.info.querySelector( '.jp-carousel-icon-info' );
634 var commentsIcon = carousel.info.querySelector( '.jp-carousel-icon-comments' );
635
636 function handleInfoToggle() {
637 if ( commentsIcon ) {
638 commentsIcon.classList.remove( 'jp-carousel-selected' );
639 }
640 infoIcon.classList.toggle( 'jp-carousel-selected' );
641
642 if ( commentsContainer ) {
643 commentsContainer.classList.remove( 'jp-carousel-show' );
644 }
645 if ( photoMetaContainer ) {
646 photoMetaContainer.classList.toggle( 'jp-carousel-show' );
647 if ( photoMetaContainer.classList.contains( 'jp-carousel-show' ) ) {
648 extraInfoContainer.classList.add( 'jp-carousel-show' );
649 } else {
650 extraInfoContainer.classList.remove( 'jp-carousel-show' );
651 }
652 }
653 }
654
655 function handleCommentToggle() {
656 if ( infoIcon ) {
657 infoIcon.classList.remove( 'jp-carousel-selected' );
658 }
659 commentsIcon.classList.toggle( 'jp-carousel-selected' );
660
661 if ( photoMetaContainer ) {
662 photoMetaContainer.classList.remove( 'jp-carousel-show' );
663 }
664 if ( commentsContainer ) {
665 commentsContainer.classList.toggle( 'jp-carousel-show' );
666 if ( commentsContainer.classList.contains( 'jp-carousel-show' ) ) {
667 extraInfoContainer.classList.add( 'jp-carousel-show' );
668 } else {
669 extraInfoContainer.classList.remove( 'jp-carousel-show' );
670 }
671 }
672 }
673
674 if (
675 domUtil.closest( target, '.jp-carousel-icon-info' ) ||
676 target.classList.contains( 'jp-carousel-photo-title' )
677 ) {
678 if ( photoMetaContainer && photoMetaContainer.classList.contains( 'jp-carousel-show' ) ) {
679 domUtil.scrollToElement( carousel.overlay, carousel.overlay, handleInfoToggle );
680 } else {
681 handleInfoToggle();
682 domUtil.scrollToElement( carousel.info, carousel.overlay );
683 }
684 }
685
686 if ( domUtil.closest( target, '.jp-carousel-icon-comments' ) ) {
687 if ( commentsContainer && commentsContainer.classList.contains( 'jp-carousel-show' ) ) {
688 domUtil.scrollToElement( carousel.overlay, carousel.overlay, handleCommentToggle );
689 } else {
690 handleCommentToggle();
691 domUtil.scrollToElement( carousel.info, carousel.overlay );
692 }
693 }
694 }
695
696 function processSingleImageGallery() {
697 var images = document.querySelectorAll( 'a img[data-attachment-id]' );
698 Array.prototype.forEach.call( images, function ( image ) {
699 var link = image.parentElement;
700 var container = link.parentElement;
701
702 // Skip if image was already added to gallery by shortcode.
703 if ( container.classList.contains( 'gallery-icon' ) ) {
704 return;
705 }
706
707 // Skip if image is part of a gallery.
708 if ( domUtil.closest( container, galleryItemSelector ) ) {
709 return;
710 }
711
712 // Skip if the parent is not actually a link.
713 if ( ! link.hasAttribute( 'href' ) ) {
714 return;
715 }
716
717 var valid = false;
718
719 // If link points to 'Media File' (ignoring GET parameters) and flag is set, allow it.
720 if (
721 link.getAttribute( 'href' ).split( '?' )[ 0 ] ===
722 image.getAttribute( 'data-orig-file' ).split( '?' )[ 0 ] &&
723 Number( jetpackCarouselStrings.single_image_gallery_media_file ) === 1
724 ) {
725 valid = true;
726 }
727
728 // If link points to 'Attachment Page', allow it.
729 if ( link.getAttribute( 'href' ) === image.getAttribute( 'data-permalink' ) ) {
730 valid = true;
731 }
732
733 // Links to 'Custom URL' or 'Media File' when flag is not set are not valid.
734 if ( ! valid ) {
735 return;
736 }
737
738 // Make this node a gallery recognizable by event listener above.
739 link.classList.add( 'single-image-gallery' );
740 // blog_id is needed to allow posting comments to correct blog.
741 link.setAttribute(
742 'data-carousel-extra',
743 JSON.stringify( {
744 blog_id: Number( jetpackCarouselStrings.blog_id ),
745 } )
746 );
747 } );
748 }
749
750 function testForData( el ) {
751 return !! ( el && el.getAttribute( 'data-carousel-extra' ) );
752 }
753
754 function openOrSelectSlide( gal, index ) {
755 if ( ! carousel.isOpen ) {
756 // The `open` method selects the correct slide during the initialization.
757 loadSwiper( gal, { startIndex: index } );
758 } else {
759 selectSlideAtIndex( index );
760 // We have to force swiper to slide to the index onHasChange.
761 swiper.slideTo( index + 1 );
762 }
763 }
764
765 function selectSlideAtIndex( index ) {
766 if ( ! index || index < 0 || index > carousel.slides.length ) {
767 index = 0;
768 }
769 carousel.currentSlide = carousel.slides[ index ];
770
771 var current = carousel.currentSlide;
772 var attachmentId = current.attrs.attachmentId;
773 var infoIcon = carousel.info.querySelector( '.jp-carousel-icon-info' );
774 var commentsIcon = carousel.info.querySelector( '.jp-carousel-icon-comments' );
775
776 // If the comment/info section is toggled open, it's kept open, but scroll to top of the next slide.
777 if (
778 ( infoIcon && infoIcon.classList.contains( 'jp-carousel-selected' ) ) ||
779 ( commentsIcon && commentsIcon.classList.contains( 'jp-carousel-selected' ) )
780 ) {
781 if ( carousel.overlay.scrollTop !== 0 ) {
782 domUtil.scrollToElement( carousel.overlay, carousel.overlay );
783 }
784 }
785
786 loadFullImage( carousel.slides[ index ] );
787
788 if (
789 Number( jetpackCarouselStrings.display_background_image ) === 1 &&
790 ! carousel.slides[ index ].backgroundImage
791 ) {
792 loadBackgroundImage( carousel.slides[ index ] );
793 }
794
795 domUtil.hide( carousel.caption );
796 updateTitleCaptionAndDesc( {
797 caption: current.attrs.caption,
798 title: current.attrs.title,
799 desc: current.attrs.desc,
800 } );
801
802 var imageMeta = carousel.slides[ index ].attrs.imageMeta;
803 updateExif( imageMeta );
804 updateFullSizeLink( current );
805
806 if ( Number( jetpackCarouselStrings.display_comments ) === 1 ) {
807 testCommentsOpened( carousel.slides[ index ].attrs.commentsOpened );
808 fetchComments( attachmentId );
809 domUtil.hide( carousel.info.querySelector( '#jp-carousel-comment-post-results' ) );
810 }
811
812 // Update pagination in footer.
813 var pagination = carousel.info.querySelector( '.jp-carousel-pagination' );
814 if ( pagination && carousel.slides.length > 5 ) {
815 var currentPage = index + 1;
816 pagination.innerHTML = '<span>' + currentPage + ' / ' + carousel.slides.length + '</span>';
817 }
818
819 // Record pageview in WP Stats, for each new image loaded full-screen.
820 if ( jetpackCarouselStrings.stats ) {
821 new Image().src =
822 document.location.protocol +
823 '//pixel.wp.com/g.gif?' +
824 jetpackCarouselStrings.stats +
825 '&post=' +
826 encodeURIComponent( attachmentId ) +
827 '&rand=' +
828 Math.random();
829 }
830
831 pageview( attachmentId );
832
833 window.location.hash = lastKnownLocationHash = '#jp-carousel-' + attachmentId;
834 }
835
836 function restoreScroll() {
837 window.scrollTo( window.scrollX || window.pageXOffset || 0, scrollPos || 0 );
838 }
839
840 function closeCarousel() {
841 // Make sure to let the page scroll again.
842 document.body.style.overflow = originalOverflow;
843 document.documentElement.style.overflow = originalHOverflow;
844 clearCommentTextAreaValue();
845
846 disableKeyboardNavigation();
847
848 domUtil.emitEvent( carousel.overlay, 'jp_carousel.beforeClose' );
849 restoreScroll();
850 swiper.destroy();
851 carousel.isOpen = false;
852 // Clear slide data for DOM garbage collection.
853 carousel.slides = [];
854 carousel.currentSlide = undefined;
855 carousel.gallery.innerHTML = '';
856
857 domUtil.fadeOut( carousel.overlay, function () {
858 domUtil.emitEvent( carousel.overlay, 'jp_carousel.afterClose' );
859 } );
860 }
861
862 function calculateMaxSlideDimensions() {
863 return {
864 width: window.innerWidth,
865 height: window.innerHeight - 64, //subtract height of bottom info bar,
866 };
867 }
868
869 function selectBestImageUrl( args ) {
870 if ( typeof args !== 'object' ) {
871 args = {};
872 }
873
874 if ( typeof args.origFile === 'undefined' ) {
875 return '';
876 }
877
878 if ( typeof args.origWidth === 'undefined' || typeof args.maxWidth === 'undefined' ) {
879 return args.origFile;
880 }
881
882 if ( typeof args.mediumFile === 'undefined' || typeof args.largeFile === 'undefined' ) {
883 return args.origFile;
884 }
885
886 // Check if the image is being served by Photon (using a regular expression on the hostname).
887
888 var imageLinkParser = document.createElement( 'a' );
889 imageLinkParser.href = args.largeFile;
890
891 var isPhotonUrl = /^i[0-2]\.wp\.com$/i.test( imageLinkParser.hostname );
892
893 var mediumSizeParts = getImageSizeParts( args.mediumFile, args.origWidth, isPhotonUrl );
894 var largeSizeParts = getImageSizeParts( args.largeFile, args.origWidth, isPhotonUrl );
895
896 var largeWidth = parseInt( largeSizeParts[ 0 ], 10 );
897 var largeHeight = parseInt( largeSizeParts[ 1 ], 10 );
898 var mediumWidth = parseInt( mediumSizeParts[ 0 ], 10 );
899 var mediumHeight = parseInt( mediumSizeParts[ 1 ], 10 );
900
901 args.origMaxWidth = args.maxWidth;
902 args.origMaxHeight = args.maxHeight;
903
904 // Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android phones = 1.5, etc)
905 if ( typeof window.devicePixelRatio !== 'undefined' && window.devicePixelRatio > 1 ) {
906 args.maxWidth = args.maxWidth * window.devicePixelRatio;
907 args.maxHeight = args.maxHeight * window.devicePixelRatio;
908 }
909
910 if ( largeWidth >= args.maxWidth || largeHeight >= args.maxHeight ) {
911 return args.largeFile;
912 }
913
914 if ( mediumWidth >= args.maxWidth || mediumHeight >= args.maxHeight ) {
915 return args.mediumFile;
916 }
917
918 if ( isPhotonUrl ) {
919 // args.origFile doesn't point to a Photon url, so in this case we use args.largeFile
920 // to return the photon url of the original image.
921 var largeFileIndex = args.largeFile.lastIndexOf( '?' );
922 var origPhotonUrl = args.largeFile;
923 if ( largeFileIndex !== -1 ) {
924 origPhotonUrl = args.largeFile.substring( 0, largeFileIndex );
925 // If we have a really large image load a smaller version
926 // that is closer to the viewable size
927 if ( args.origWidth > args.maxWidth || args.origHeight > args.maxHeight ) {
928 // @2x the max sizes so we get a high enough resolution for zooming.
929 args.origMaxWidth = args.maxWidth * 2;
930 args.origMaxHeight = args.maxHeight * 2;
931 origPhotonUrl += '?fit=' + args.origMaxWidth + '%2C' + args.origMaxHeight;
932 }
933 }
934 return origPhotonUrl;
935 }
936
937 return args.origFile;
938 }
939
940 function getImageSizeParts( file, origWidth, isPhotonUrl ) {
941 var size = isPhotonUrl
942 ? file.replace( /.*=([\d]+%2C[\d]+).*$/, '$1' )
943 : file.replace( /.*-([\d]+x[\d]+)\..+$/, '$1' );
944
945 var sizeParts =
946 size !== file
947 ? isPhotonUrl
948 ? size.split( '%2C' )
949 : size.split( 'x' )
950 : [ origWidth, 0 ];
951
952 // If one of the dimensions is set to 9999, then the actual value of that dimension can't be retrieved from the url.
953 // In that case, we set the value to 0.
954 if ( sizeParts[ 0 ] === '9999' ) {
955 sizeParts[ 0 ] = '0';
956 }
957
958 if ( sizeParts[ 1 ] === '9999' ) {
959 sizeParts[ 1 ] = '0';
960 }
961
962 return sizeParts;
963 }
964
965 /**
966 * Returns a number in a fraction format that represents the shutter speed.
967 * @param Number speed
968 * @return String
969 */
970 function formatShutterSpeed( speed ) {
971 var denominator;
972
973 // round to one decimal if value > 1s by multiplying it by 10, rounding, then dividing by 10 again
974 if ( speed >= 1 ) {
975 return Math.round( speed * 10 ) / 10 + 's';
976 }
977
978 // If the speed is less than one, we find the denominator by inverting
979 // the number. Since cameras usually use rational numbers as shutter
980 // speeds, we should get a nice round number. Or close to one in cases
981 // like 1/30. So we round it.
982 denominator = Math.round( 1 / speed );
983
984 return '1/' + denominator + 's';
985 }
986
987 function parseTitleOrDesc( value ) {
988 if ( ! value.match( ' ' ) && value.match( '_' ) ) {
989 return '';
990 }
991
992 return value;
993 }
994
995 function updateTitleCaptionAndDesc( data ) {
996 var caption = '';
997 var title = '';
998 var desc = '';
999 var captionMainElement;
1000 var captionInfoExtraElement;
1001 var titleElement;
1002 var descriptionElement;
1003
1004 captionMainElement = carousel.overlay.querySelector( '.jp-carousel-photo-caption' );
1005 captionInfoExtraElement = carousel.overlay.querySelector( '.jp-carousel-caption' );
1006
1007 titleElement = carousel.overlay.querySelector( '.jp-carousel-photo-title' );
1008 descriptionElement = carousel.overlay.querySelector( '.jp-carousel-photo-description' );
1009
1010 domUtil.hide( captionMainElement );
1011 domUtil.hide( captionInfoExtraElement );
1012 domUtil.hide( titleElement );
1013 domUtil.hide( descriptionElement );
1014
1015 caption = parseTitleOrDesc( data.caption ) || '';
1016 title = parseTitleOrDesc( data.title ) || '';
1017 desc = parseTitleOrDesc( data.desc ) || '';
1018
1019 if ( caption || title || desc ) {
1020 if ( caption ) {
1021 captionMainElement.innerHTML = caption;
1022 captionInfoExtraElement.innerHTML = caption;
1023
1024 domUtil.show( captionMainElement );
1025 domUtil.show( captionInfoExtraElement );
1026 }
1027
1028 if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( title ) ) {
1029 title = '';
1030 }
1031
1032 if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( desc ) ) {
1033 desc = '';
1034 }
1035
1036 if ( domUtil.stripHTML( title ) === domUtil.stripHTML( desc ) ) {
1037 desc = '';
1038 }
1039
1040 if ( desc ) {
1041 descriptionElement.innerHTML = desc;
1042 domUtil.show( descriptionElement );
1043
1044 if ( ! title && ! caption ) {
1045 captionMainElement.innerHTML = domUtil.stripHTML( desc );
1046 domUtil.show( captionMainElement );
1047 }
1048 }
1049
1050 if ( title ) {
1051 var plainTitle = domUtil.stripHTML( title );
1052 titleElement.innerHTML = plainTitle;
1053
1054 if ( ! caption ) {
1055 captionMainElement.innerHTML = plainTitle;
1056 captionInfoExtraElement.innerHTML = plainTitle;
1057
1058 domUtil.show( captionMainElement );
1059 }
1060
1061 domUtil.show( titleElement );
1062 }
1063 }
1064 }
1065
1066 // updateExif updates the contents of the exif UL (.jp-carousel-image-exif)
1067 function updateExif( meta ) {
1068 if ( ! meta || Number( jetpackCarouselStrings.display_exif ) !== 1 ) {
1069 return false;
1070 }
1071
1072 var ul = carousel.info.querySelector( '.jp-carousel-image-meta ul.jp-carousel-image-exif' );
1073 var html = '';
1074
1075 for ( var key in meta ) {
1076 var val = meta[ key ];
1077 var metaKeys = jetpackCarouselStrings.meta_data || [];
1078
1079 if ( parseFloat( val ) === 0 || ! val.length || metaKeys.indexOf( key ) === -1 ) {
1080 continue;
1081 }
1082
1083 switch ( key ) {
1084 case 'focal_length':
1085 val = val + 'mm';
1086 break;
1087 case 'shutter_speed':
1088 val = formatShutterSpeed( val );
1089 break;
1090 case 'aperture':
1091 val = 'f/' + val;
1092 break;
1093 }
1094
1095 html += '<li><h5>' + jetpackCarouselStrings[ key ] + '</h5>' + val + '</li>';
1096 }
1097
1098 ul.innerHTML = html;
1099 ul.style.removeProperty( 'display' );
1100 }
1101
1102 // Update the contents of the jp-carousel-image-download link
1103 function updateFullSizeLink( currentSlide ) {
1104 if ( ! currentSlide ) {
1105 return false;
1106 }
1107 var original;
1108 var origSize = [ currentSlide.attrs.origWidth, currentSlide.attrs.origHeight ];
1109 var imageLinkParser = document.createElement( 'a' );
1110
1111 imageLinkParser.href = currentSlide.attrs.src.replace( /\?.+$/, '' );
1112
1113 // Is this a Photon URL?
1114 if ( imageLinkParser.hostname.match( /^i[\d]{1}\.wp\.com$/i ) !== null ) {
1115 original = imageLinkParser.href;
1116 } else {
1117 original = currentSlide.attrs.origFile.replace( /\?.+$/, '' );
1118 }
1119
1120 var downloadText = carousel.info.querySelector( '.jp-carousel-download-text' );
1121 var permalink = carousel.info.querySelector( '.jp-carousel-image-download' );
1122
1123 downloadText.innerHTML = util.applyReplacements(
1124 jetpackCarouselStrings.download_original,
1125 origSize
1126 );
1127 permalink.setAttribute( 'href', original );
1128 permalink.style.removeProperty( 'display' );
1129 }
1130
1131 function testCommentsOpened( opened ) {
1132 var commentForm = carousel.container.querySelector( '.jp-carousel-comment-form-container' );
1133 var isOpened = parseInt( opened, 10 ) === 1;
1134
1135 if ( isOpened ) {
1136 domUtil.fadeIn( commentForm );
1137 } else {
1138 domUtil.fadeOut( commentForm );
1139 }
1140 }
1141
1142 function fetchComments( attachmentId, offset ) {
1143 var shouldClear = offset === undefined;
1144 var commentsIndicator = carousel.info.querySelector(
1145 '.jp-carousel-icon-comments .jp-carousel-has-comments-indicator'
1146 );
1147
1148 commentsIndicator.classList.remove( 'jp-carousel-show' );
1149
1150 clearInterval( commentInterval );
1151
1152 if ( ! attachmentId ) {
1153 return;
1154 }
1155
1156 if ( ! offset || offset < 1 ) {
1157 offset = 0;
1158 }
1159
1160 var comments = carousel.info.querySelector( '.jp-carousel-comments' );
1161 var commentsLoading = carousel.info.querySelector( '#jp-carousel-comments-loading' );
1162 domUtil.show( commentsLoading );
1163
1164 if ( shouldClear ) {
1165 domUtil.hide( comments );
1166 comments.innerHTML = '';
1167 }
1168
1169 var xhr = new XMLHttpRequest();
1170 var url =
1171 jetpackCarouselStrings.ajaxurl +
1172 '?action=get_attachment_comments' +
1173 '&nonce=' +
1174 jetpackCarouselStrings.nonce +
1175 '&id=' +
1176 attachmentId +
1177 '&offset=' +
1178 offset;
1179 xhr.open( 'GET', url );
1180 xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
1181
1182 var onError = function () {
1183 domUtil.fadeIn( comments );
1184 domUtil.fadeOut( commentsLoading );
1185 };
1186
1187 xhr.onload = function () {
1188 // Ignore the results if they arrive late and we're now on a different slide.
1189 if (
1190 ! carousel.currentSlide ||
1191 carousel.currentSlide.attrs.attachmentId !== attachmentId
1192 ) {
1193 return;
1194 }
1195
1196 var isSuccess = xhr.status >= 200 && xhr.status < 300;
1197 var data;
1198 try {
1199 data = JSON.parse( xhr.responseText );
1200 } catch ( e ) {
1201 // Do nothing.
1202 }
1203
1204 if ( ! isSuccess || ! data || ! Array.isArray( data ) ) {
1205 return onError();
1206 }
1207
1208 if ( shouldClear ) {
1209 comments.innerHTML = '';
1210 }
1211
1212 for ( var i = 0; i < data.length; i++ ) {
1213 var entry = data[ i ];
1214 var comment = document.createElement( 'div' );
1215 comment.classList.add( 'jp-carousel-comment' );
1216 comment.setAttribute( 'id', 'jp-carousel-comment-' + entry.id );
1217 comment.innerHTML =
1218 '<div class="comment-gravatar">' +
1219 entry.gravatar_markup +
1220 '</div>' +
1221 '<div class="comment-content">' +
1222 '<div class="comment-author">' +
1223 entry.author_markup +
1224 '</div>' +
1225 '<div class="comment-date">' +
1226 entry.date_gmt +
1227 '</div>' +
1228 entry.content +
1229 '</div>';
1230 comments.appendChild( comment );
1231
1232 // Set the interval to check for a new page of comments.
1233 clearInterval( commentInterval );
1234 commentInterval = setInterval( function () {
1235 if ( carousel.container.scrollTop + 150 > window.innerHeight ) {
1236 fetchComments( attachmentId, offset + 10 );
1237 clearInterval( commentInterval );
1238 }
1239 }, 300 );
1240 }
1241
1242 if ( data.length > 0 ) {
1243 domUtil.show( comments );
1244 commentsIndicator.innerText = data.length;
1245 commentsIndicator.classList.add( 'jp-carousel-show' );
1246 }
1247
1248 domUtil.hide( commentsLoading );
1249 };
1250
1251 xhr.onerror = onError;
1252
1253 xhr.send();
1254 }
1255
1256 function loadFullImage( slide ) {
1257 var el = slide.el;
1258 var attrs = slide.attrs;
1259 var image = el.querySelector( 'img' );
1260
1261 if ( ! image.hasAttribute( 'data-loaded' ) ) {
1262 var hasPreview = !! attrs.previewImage;
1263 var thumbSize = attrs.thumbSize;
1264
1265 if ( ! hasPreview || ( thumbSize && el.offsetWidth > thumbSize.width ) ) {
1266 image.src = attrs.src;
1267 } else {
1268 image.src = attrs.previewImage;
1269 }
1270
1271 image.setAttribute( 'itemprop', 'image' );
1272 image.setAttribute( 'data-loaded', 1 );
1273 }
1274 }
1275
1276 function loadBackgroundImage( slide ) {
1277 var currentSlide = slide.el;
1278
1279 if ( swiper && swiper.slides ) {
1280 currentSlide = swiper.slides[ swiper.activeIndex ];
1281 }
1282
1283 var image = slide.attrs.originalElement;
1284 var isLoaded = image.complete && image.naturalHeight !== 0;
1285
1286 if ( isLoaded ) {
1287 applyBackgroundImage( slide, currentSlide, image );
1288 return;
1289 }
1290
1291 image.onload = function () {
1292 applyBackgroundImage( slide, currentSlide, image );
1293 };
1294 }
1295
1296 function applyBackgroundImage( slide, currentSlide, image ) {
1297 var url = util.getBackgroundImage( image );
1298 slide.backgroundImage = url;
1299 currentSlide.style.backgroundImage = 'url(' + url + ')';
1300 currentSlide.style.backgroundSize = 'cover';
1301 }
1302
1303 function clearCommentTextAreaValue() {
1304 if ( carousel.commentField ) {
1305 carousel.commentField.value = '';
1306 }
1307 }
1308
1309 function getOriginalDimensions( el ) {
1310 var size = el.getAttribute( 'data-orig-size' ) || '';
1311
1312 if ( size ) {
1313 var parts = size.split( ',' );
1314 return { width: parseInt( parts[ 0 ], 10 ), height: parseInt( parts[ 1 ], 10 ) };
1315 } else {
1316 return {
1317 width:
1318 el.getAttribute( 'data-original-width' ) || el.getAttribute( 'width' ) || undefined,
1319 height:
1320 el.getAttribute( 'data-original-height' ) || el.getAttribute( 'height' ) || undefined,
1321 };
1322 }
1323 }
1324
1325 function initCarouselSlides( items, startIndex ) {
1326 carousel.slides = [];
1327
1328 var max = calculateMaxSlideDimensions();
1329
1330 // If the startIndex is not 0 then preload the clicked image first.
1331 if ( startIndex !== 0 ) {
1332 var img = new Image();
1333 img.src = items[ startIndex ].getAttribute( 'data-gallery-src' );
1334 }
1335
1336 var useInPageThumbnails = !! domUtil.closest( items[ 0 ], '.tiled-gallery.type-rectangular' );
1337
1338 // create the 'slide'
1339 Array.prototype.forEach.call( items, function ( item, i ) {
1340 var permalinkEl = domUtil.closest( item, 'a' );
1341 var origFile = item.getAttribute( 'data-orig-file' ) || item.getAttribute( 'src-orig' );
1342 var attrID =
1343 item.getAttribute( 'data-attachment-id' ) || item.getAttribute( 'data-id' ) || '0';
1344 var caption = document.querySelector(
1345 'img[data-attachment-id="' + attrID + '"] + figcaption'
1346 );
1347
1348 if ( caption ) {
1349 caption = caption.innerHTML;
1350 } else {
1351 caption = item.getAttribute( 'data-image-caption' );
1352 }
1353
1354 var attrs = {
1355 originalElement: item,
1356 attachmentId: attrID,
1357 commentsOpened: item.getAttribute( 'data-comments-opened' ) || '0',
1358 imageMeta: domUtil.getJSONAttribute( item, 'data-image-meta' ) || {},
1359 title: item.getAttribute( 'data-image-title' ) || '',
1360 desc: item.getAttribute( 'data-image-description' ) || '',
1361 mediumFile: item.getAttribute( 'data-medium-file' ) || '',
1362 largeFile: item.getAttribute( 'data-large-file' ) || '',
1363 origFile: origFile || '',
1364 thumbSize: { width: item.naturalWidth, height: item.naturalHeight },
1365 caption: caption || '',
1366 permalink: permalinkEl && permalinkEl.getAttribute( 'href' ),
1367 src: origFile || item.getAttribute( 'src' ) || '',
1368 };
1369
1370 var tiledGalleryItem = domUtil.closest( item, '.tiled-gallery-item' );
1371 var tiledCaptionEl =
1372 tiledGalleryItem && tiledGalleryItem.querySelector( '.tiled-gallery-caption' );
1373 var tiledCaption = tiledCaptionEl && tiledCaptionEl.innerHTML;
1374 if ( tiledCaption ) {
1375 attrs.caption = tiledCaption;
1376 }
1377
1378 var origDimensions = getOriginalDimensions( item );
1379
1380 attrs.origWidth = origDimensions.width || attrs.thumbSize.width;
1381 attrs.origHeight = origDimensions.height || attrs.thumbSize.height;
1382
1383 if ( typeof wpcom !== 'undefined' && wpcom.carousel && wpcom.carousel.generateImgSrc ) {
1384 attrs.src = wpcom.carousel.generateImgSrc( item, max );
1385 } else {
1386 attrs.src = selectBestImageUrl( {
1387 origFile: attrs.src,
1388 origWidth: attrs.origWidth,
1389 origHeight: attrs.origHeight,
1390 maxWidth: max.width,
1391 maxHeight: max.height,
1392 mediumFile: attrs.mediumFile,
1393 largeFile: attrs.largeFile,
1394 } );
1395 }
1396
1397 // Set the final src.
1398 item.setAttribute( 'data-gallery-src', attrs.src );
1399
1400 if ( attrs.attachmentId !== '0' ) {
1401 attrs.title = util.texturize( attrs.title );
1402 attrs.desc = util.texturize( attrs.desc );
1403 attrs.caption = util.texturize( attrs.caption );
1404
1405 // Initially, the image is a 1x1 transparent gif.
1406 // The preview is shown as a background image on the slide itself.
1407 var image = new Image();
1408 image.src = attrs.src;
1409
1410 var slideEl = document.createElement( 'div' );
1411 slideEl.classList.add( 'swiper-slide' );
1412 slideEl.setAttribute( 'itemprop', 'associatedMedia' );
1413 slideEl.setAttribute( 'itemscope', '' );
1414 slideEl.setAttribute( 'itemtype', 'https://schema.org/ImageObject' );
1415 var zoomEl = document.createElement( 'div' );
1416 zoomEl.classList.add( 'swiper-zoom-container' );
1417
1418 carousel.gallery.appendChild( slideEl );
1419
1420 slideEl.appendChild( zoomEl );
1421 zoomEl.appendChild( image );
1422 slideEl.setAttribute( 'data-attachment-id', attrs.attachmentId );
1423 slideEl.setAttribute( 'data-permalink', attrs.permalink );
1424 slideEl.setAttribute( 'data-orig-file', attrs.origFile );
1425
1426 if ( useInPageThumbnails ) {
1427 // Use the image already loaded in the gallery as a preview.
1428 attrs.previewImage = attrs.src;
1429 }
1430
1431 var slide = { el: slideEl, attrs: attrs, index: i };
1432 carousel.slides.push( slide );
1433 }
1434 } );
1435 }
1436
1437 function loadSwiper( gallery, options ) {
1438 if ( ! window.Swiper670 ) {
1439 var loader = document.querySelector( '#jp-carousel-loading-overlay' );
1440 domUtil.show( loader );
1441 var jsScript = document.createElement( 'script' );
1442 jsScript.id = 'jetpack-carousel-swiper-js';
1443 jsScript.src = window.jetpackSwiperLibraryPath.url;
1444 jsScript.async = true;
1445 jsScript.onload = function () {
1446 domUtil.hide( loader );
1447 openCarousel( gallery, options );
1448 };
1449 jsScript.onerror = function () {
1450 domUtil.hide( loader );
1451 };
1452 document.head.appendChild( jsScript );
1453 return;
1454 }
1455 openCarousel( gallery, options );
1456 }
1457
1458 function openCarousel( gallery, options ) {
1459 var settings = {
1460 imgSelector:
1461 '.gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id], img[data-attachment-id], img[data-id]',
1462 startIndex: 0,
1463 };
1464
1465 var data = domUtil.getJSONAttribute( gallery, 'data-carousel-extra' );
1466 var tapTimeout;
1467
1468 if ( ! data ) {
1469 return; // don't run if the default gallery functions weren't used
1470 }
1471
1472 initializeCarousel();
1473
1474 if ( carousel.isOpen ) {
1475 return; // don't open if already opened
1476 }
1477 carousel.isOpen = true;
1478
1479 // make sure to stop the page from scrolling behind the carousel overlay, so we don't trigger
1480 // infiniscroll for it when enabled (Reader, theme infiniscroll, etc).
1481 originalOverflow = getComputedStyle( document.body ).overflow;
1482 document.body.style.overflow = 'hidden';
1483 // prevent html from overflowing on some of the new themes.
1484 originalHOverflow = getComputedStyle( document.documentElement ).overflow;
1485 document.documentElement.style.overflow = 'hidden';
1486 scrollPos = window.scrollY || window.pageYOffset || 0;
1487
1488 carousel.container.setAttribute( 'data-carousel-extra', JSON.stringify( data ) );
1489 stat( [ 'open', 'view_image' ] );
1490
1491 // If options exist, lets merge them
1492 // with our default settings
1493 for ( var option in options || {} ) {
1494 settings[ option ] = options[ option ];
1495 }
1496
1497 if ( settings.startIndex === -1 ) {
1498 settings.startIndex = 0; // -1 returned if can't find index, so start from beginning
1499 }
1500
1501 domUtil.emitEvent( carousel.overlay, 'jp_carousel.beforeOpen' );
1502 carousel.gallery.innerHTML = '';
1503
1504 // Need to set the overlay manually to block or swiper does't initialise properly.
1505 carousel.overlay.style.opacity = 1;
1506 carousel.overlay.style.display = 'block';
1507
1508 initCarouselSlides( gallery.querySelectorAll( settings.imgSelector ), settings.startIndex );
1509
1510 swiper = new window.Swiper670( '.jp-carousel-swiper-container', {
1511 centeredSlides: true,
1512 zoom: true,
1513 loop: carousel.slides.length > 1,
1514 // Turn off interactions and hide navigation arrows if there is only one slide.
1515 enabled: carousel.slides.length > 1,
1516 pagination: {
1517 el: '.jp-swiper-pagination',
1518 clickable: true,
1519 },
1520 navigation: {
1521 nextEl: '.jp-swiper-button-next',
1522 prevEl: '.jp-swiper-button-prev',
1523 },
1524 initialSlide: settings.startIndex,
1525 on: {
1526 init: function () {
1527 selectSlideAtIndex( settings.startIndex );
1528 },
1529 },
1530 preventClicks: false,
1531 preventClicksPropagation: false,
1532 preventInteractionOnTransition: ! domUtil.isTouch(),
1533 threshold: 5,
1534 } );
1535
1536 swiper.on( 'slideChange', function ( swiper ) {
1537 var index;
1538 // Swiper indexes slides from 1, plus when looping to left last slide ends up
1539 // as 0 and looping to right first slide as total slides + 1. These are adjusted
1540 // here to match index of carousel.slides.
1541 if ( swiper.activeIndex === 0 ) {
1542 index = carousel.slides.length - 1;
1543 } else if ( swiper.activeIndex === carousel.slides.length + 1 ) {
1544 index = 0;
1545 } else {
1546 index = swiper.activeIndex - 1;
1547 }
1548 selectSlideAtIndex( index );
1549
1550 carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
1551 } );
1552
1553 swiper.on( 'zoomChange', function ( swiper, scale ) {
1554 if ( scale > 1 ) {
1555 carousel.overlay.classList.add( 'jp-carousel-hide-controls' );
1556 }
1557
1558 if ( scale === 1 ) {
1559 carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
1560 }
1561 } );
1562
1563 swiper.on( 'doubleTap', function ( swiper ) {
1564 clearTimeout( tapTimeout );
1565 if ( swiper.zoom.scale === 1 ) {
1566 var zoomTimeout = setTimeout( function () {
1567 carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
1568 clearTimeout( zoomTimeout );
1569 }, 150 );
1570 }
1571 } );
1572
1573 swiper.on( 'tap', function () {
1574 if ( swiper.zoom.scale > 1 ) {
1575 tapTimeout = setTimeout( function () {
1576 carousel.overlay.classList.toggle( 'jp-carousel-hide-controls' );
1577 }, 150 );
1578 }
1579 } );
1580
1581 domUtil.fadeIn( carousel.overlay, function () {
1582 domUtil.emitEvent( carousel.overlay, 'jp_carousel.afterOpen' );
1583 } );
1584 }
1585
1586 // Register the event listener for starting the gallery
1587 document.body.addEventListener( 'click', function ( e ) {
1588 var isCompatible =
1589 window.CSS && window.CSS.supports && window.CSS.supports( 'display', 'grid' );
1590
1591 // IE11 support is being dropped in August 2021. The new swiper.js libray is not IE11 compat
1592 // so just default to opening individual image attachment/media pages for IE.
1593 if ( ! isCompatible ) {
1594 return;
1595 }
1596
1597 var target = e.target;
1598 var gallery = domUtil.closest( target, gallerySelector );
1599
1600 if ( gallery ) {
1601 if ( ! testForData( gallery ) ) {
1602 return;
1603 }
1604
1605 var parent = target.parentElement;
1606 var grandparent = parent.parentElement;
1607
1608 // If Gallery is made up of individual Image blocks check for custom link before
1609 // loading carousel. The custom link may be the parent or could be a descendant
1610 // of the parent if the image has rounded corners.
1611 var parentHref = null;
1612 if ( grandparent && grandparent.classList.contains( 'wp-block-image' ) ) {
1613 parentHref = parent.getAttribute( 'href' );
1614 } else if (
1615 parent &&
1616 parent.classList.contains( 'wp-block-image' ) &&
1617 parent.querySelector( ':scope > a' )
1618 ) {
1619 parentHref = parent.querySelector( ':scope > a' ).getAttribute( 'href' );
1620 }
1621
1622 // If the link does not point to the attachment or media file then assume Image has
1623 // a custom link so don't load the carousel.
1624 if (
1625 parentHref &&
1626 parentHref.split( '?' )[ 0 ] !==
1627 target.getAttribute( 'data-orig-file' ).split( '?' )[ 0 ] &&
1628 parentHref !== target.getAttribute( 'data-permalink' )
1629 ) {
1630 return;
1631 }
1632
1633 // Do not open the modal if we are looking at a gallery caption from before WP5, which may contain a link.
1634 if ( parent.classList.contains( 'gallery-caption' ) ) {
1635 return;
1636 }
1637
1638 // Do not open the modal if we are looking at a caption of a gallery block, which may contain a link.
1639 if ( domUtil.matches( parent, 'figcaption' ) ) {
1640 return;
1641 }
1642
1643 // Set height to auto.
1644 // Fix some themes where closing carousel brings view back to top.
1645 document.documentElement.style.height = 'auto';
1646
1647 e.preventDefault();
1648
1649 // Stopping propagation in case there are parent elements
1650 // with .gallery or .tiled-gallery class
1651 e.stopPropagation();
1652
1653 var item = domUtil.closest( target, itemSelector );
1654 var index = Array.prototype.indexOf.call( gallery.querySelectorAll( itemSelector ), item );
1655 loadSwiper( gallery, { startIndex: index } );
1656 }
1657 } );
1658
1659 // Handle lightbox (single image gallery) for images linking to 'Attachment Page'.
1660 if ( Number( jetpackCarouselStrings.single_image_gallery ) === 1 ) {
1661 processSingleImageGallery();
1662 document.body.addEventListener( 'is.post-load', function () {
1663 processSingleImageGallery();
1664 } );
1665 }
1666
1667 // Makes carousel work on page load and when back button leads to same URL with carousel hash
1668 // (i.e. no actual document.ready trigger).
1669 window.addEventListener( 'hashchange', function () {
1670 var hashRegExp = /jp-carousel-(\d+)/;
1671
1672 if ( ! window.location.hash || ! hashRegExp.test( window.location.hash ) ) {
1673 if ( carousel.isOpen ) {
1674 closeCarousel();
1675 }
1676
1677 return;
1678 }
1679
1680 if ( window.location.hash === lastKnownLocationHash && carousel.isOpen ) {
1681 return;
1682 }
1683
1684 if ( window.location.hash && carousel.gallery && ! carousel.isOpen && history.back ) {
1685 history.back();
1686 return;
1687 }
1688
1689 lastKnownLocationHash = window.location.hash;
1690 var matchList = window.location.hash.match( hashRegExp );
1691 var attachmentId = parseInt( matchList[ 1 ], 10 );
1692 var galleries = document.querySelectorAll( gallerySelector );
1693
1694 // Find the first thumbnail that matches the attachment ID in the location
1695 // hash, then open the gallery that contains it.
1696 for ( var i = 0; i < galleries.length; i++ ) {
1697 var gallery = galleries[ i ];
1698 var selected;
1699
1700 var images = gallery.querySelectorAll( 'img' );
1701 for ( var j = 0; j < images.length; j++ ) {
1702 if (
1703 parseInt( images[ j ].getAttribute( 'data-attachment-id' ), 10 ) === attachmentId ||
1704 parseInt( images[ j ].getAttribute( 'data-id' ), 10 ) === attachmentId
1705 ) {
1706 selected = j;
1707 break;
1708 }
1709 }
1710
1711 if ( selected !== undefined ) {
1712 openOrSelectSlide( gallery, selected );
1713 break;
1714 }
1715 }
1716 } );
1717
1718 if ( window.location.hash ) {
1719 domUtil.emitEvent( window, 'hashchange' );
1720 }
1721 }
1722
1723 if ( document.readyState !== 'loading' ) {
1724 init();
1725 } else {
1726 document.addEventListener( 'DOMContentLoaded', init );
1727 }
1728 } )();
1729