PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 12.5.2
Jetpack – WP Security, Backup, Speed, & Growth v12.5.2
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
1717 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
774 loadFullImage( carousel.slides[ index ] );
775
776 if (
777 Number( jetpackCarouselStrings.display_background_image ) === 1 &&
778 ! carousel.slides[ index ].backgroundImage
779 ) {
780 loadBackgroundImage( carousel.slides[ index ] );
781 }
782
783 domUtil.hide( carousel.caption );
784 updateTitleCaptionAndDesc( {
785 caption: current.attrs.caption,
786 title: current.attrs.title,
787 desc: current.attrs.desc,
788 } );
789
790 var imageMeta = carousel.slides[ index ].attrs.imageMeta;
791 updateExif( imageMeta );
792 updateFullSizeLink( current );
793
794 if ( Number( jetpackCarouselStrings.display_comments ) === 1 ) {
795 testCommentsOpened( carousel.slides[ index ].attrs.commentsOpened );
796 fetchComments( attachmentId );
797 domUtil.hide( carousel.info.querySelector( '#jp-carousel-comment-post-results' ) );
798 }
799
800 // Update pagination in footer.
801 var pagination = carousel.info.querySelector( '.jp-carousel-pagination' );
802 if ( pagination && carousel.slides.length > 5 ) {
803 var currentPage = index + 1;
804 pagination.innerHTML = '<span>' + currentPage + ' / ' + carousel.slides.length + '</span>';
805 }
806
807 // Record pageview in WP Stats, for each new image loaded full-screen.
808 if ( jetpackCarouselStrings.stats ) {
809 new Image().src =
810 document.location.protocol +
811 '//pixel.wp.com/g.gif?' +
812 jetpackCarouselStrings.stats +
813 '&post=' +
814 encodeURIComponent( attachmentId ) +
815 '&rand=' +
816 Math.random();
817 }
818
819 pageview( attachmentId );
820
821 window.location.hash = lastKnownLocationHash = '#jp-carousel-' + attachmentId;
822 }
823
824 function restoreScroll() {
825 window.scrollTo( window.scrollX || window.pageXOffset || 0, scrollPos || 0 );
826 }
827
828 function closeCarousel() {
829 // Make sure to let the page scroll again.
830 document.body.style.overflow = originalOverflow;
831 document.documentElement.style.overflow = originalHOverflow;
832 clearCommentTextAreaValue();
833
834 disableKeyboardNavigation();
835
836 domUtil.emitEvent( carousel.overlay, 'jp_carousel.beforeClose' );
837 restoreScroll();
838 swiper.destroy();
839 carousel.isOpen = false;
840 // Clear slide data for DOM garbage collection.
841 carousel.slides = [];
842 carousel.currentSlide = undefined;
843 carousel.gallery.innerHTML = '';
844
845 domUtil.fadeOut( carousel.overlay, function () {
846 domUtil.emitEvent( carousel.overlay, 'jp_carousel.afterClose' );
847 } );
848 }
849
850 function calculateMaxSlideDimensions() {
851 return {
852 width: window.innerWidth,
853 height: window.innerHeight - 64, //subtract height of bottom info bar,
854 };
855 }
856
857 function selectBestImageUrl( args ) {
858 if ( typeof args !== 'object' ) {
859 args = {};
860 }
861
862 if ( typeof args.origFile === 'undefined' ) {
863 return '';
864 }
865
866 if ( typeof args.origWidth === 'undefined' || typeof args.maxWidth === 'undefined' ) {
867 return args.origFile;
868 }
869
870 if ( typeof args.mediumFile === 'undefined' || typeof args.largeFile === 'undefined' ) {
871 return args.origFile;
872 }
873
874 // Check if the image is being served by Photon (using a regular expression on the hostname).
875
876 var imageLinkParser = document.createElement( 'a' );
877 imageLinkParser.href = args.largeFile;
878
879 var isPhotonUrl = /^i[0-2]\.wp\.com$/i.test( imageLinkParser.hostname );
880
881 var mediumSizeParts = getImageSizeParts( args.mediumFile, args.origWidth, isPhotonUrl );
882 var largeSizeParts = getImageSizeParts( args.largeFile, args.origWidth, isPhotonUrl );
883
884 var largeWidth = parseInt( largeSizeParts[ 0 ], 10 );
885 var largeHeight = parseInt( largeSizeParts[ 1 ], 10 );
886 var mediumWidth = parseInt( mediumSizeParts[ 0 ], 10 );
887 var mediumHeight = parseInt( mediumSizeParts[ 1 ], 10 );
888
889 args.origMaxWidth = args.maxWidth;
890 args.origMaxHeight = args.maxHeight;
891
892 // Give devices with a higher devicePixelRatio higher-res images (Retina display = 2, Android phones = 1.5, etc)
893 if ( typeof window.devicePixelRatio !== 'undefined' && window.devicePixelRatio > 1 ) {
894 args.maxWidth = args.maxWidth * window.devicePixelRatio;
895 args.maxHeight = args.maxHeight * window.devicePixelRatio;
896 }
897
898 if ( largeWidth >= args.maxWidth || largeHeight >= args.maxHeight ) {
899 return args.largeFile;
900 }
901
902 if ( mediumWidth >= args.maxWidth || mediumHeight >= args.maxHeight ) {
903 return args.mediumFile;
904 }
905
906 if ( isPhotonUrl ) {
907 // args.origFile doesn't point to a Photon url, so in this case we use args.largeFile
908 // to return the photon url of the original image.
909 var largeFileIndex = args.largeFile.lastIndexOf( '?' );
910 var origPhotonUrl = args.largeFile;
911 if ( largeFileIndex !== -1 ) {
912 origPhotonUrl = args.largeFile.substring( 0, largeFileIndex );
913 // If we have a really large image load a smaller version
914 // that is closer to the viewable size
915 if ( args.origWidth > args.maxWidth || args.origHeight > args.maxHeight ) {
916 // @2x the max sizes so we get a high enough resolution for zooming.
917 args.origMaxWidth = args.maxWidth * 2;
918 args.origMaxHeight = args.maxHeight * 2;
919 origPhotonUrl += '?fit=' + args.origMaxWidth + '%2C' + args.origMaxHeight;
920 }
921 }
922 return origPhotonUrl;
923 }
924
925 return args.origFile;
926 }
927
928 function getImageSizeParts( file, origWidth, isPhotonUrl ) {
929 var size = isPhotonUrl
930 ? file.replace( /.*=([\d]+%2C[\d]+).*$/, '$1' )
931 : file.replace( /.*-([\d]+x[\d]+)\..+$/, '$1' );
932
933 var sizeParts =
934 size !== file
935 ? isPhotonUrl
936 ? size.split( '%2C' )
937 : size.split( 'x' )
938 : [ origWidth, 0 ];
939
940 // If one of the dimensions is set to 9999, then the actual value of that dimension can't be retrieved from the url.
941 // In that case, we set the value to 0.
942 if ( sizeParts[ 0 ] === '9999' ) {
943 sizeParts[ 0 ] = '0';
944 }
945
946 if ( sizeParts[ 1 ] === '9999' ) {
947 sizeParts[ 1 ] = '0';
948 }
949
950 return sizeParts;
951 }
952
953 /**
954 * Returns a number in a fraction format that represents the shutter speed.
955 * @param Number speed
956 * @return String
957 */
958 function formatShutterSpeed( speed ) {
959 var denominator;
960
961 // round to one decimal if value > 1s by multiplying it by 10, rounding, then dividing by 10 again
962 if ( speed >= 1 ) {
963 return Math.round( speed * 10 ) / 10 + 's';
964 }
965
966 // If the speed is less than one, we find the denominator by inverting
967 // the number. Since cameras usually use rational numbers as shutter
968 // speeds, we should get a nice round number. Or close to one in cases
969 // like 1/30. So we round it.
970 denominator = Math.round( 1 / speed );
971
972 return '1/' + denominator + 's';
973 }
974
975 function parseTitleOrDesc( value ) {
976 if ( ! value.match( ' ' ) && value.match( '_' ) ) {
977 return '';
978 }
979
980 return value;
981 }
982
983 function updateTitleCaptionAndDesc( data ) {
984 var caption = '';
985 var title = '';
986 var desc = '';
987 var captionMainElement;
988 var captionInfoExtraElement;
989 var titleElement;
990 var descriptionElement;
991
992 captionMainElement = carousel.overlay.querySelector( '.jp-carousel-photo-caption' );
993 captionInfoExtraElement = carousel.overlay.querySelector( '.jp-carousel-caption' );
994
995 titleElement = carousel.overlay.querySelector( '.jp-carousel-photo-title' );
996 descriptionElement = carousel.overlay.querySelector( '.jp-carousel-photo-description' );
997
998 domUtil.hide( captionMainElement );
999 domUtil.hide( captionInfoExtraElement );
1000 domUtil.hide( titleElement );
1001 domUtil.hide( descriptionElement );
1002
1003 caption = parseTitleOrDesc( data.caption ) || '';
1004 title = parseTitleOrDesc( data.title ) || '';
1005 desc = parseTitleOrDesc( data.desc ) || '';
1006
1007 if ( caption || title || desc ) {
1008 if ( caption ) {
1009 captionMainElement.innerHTML = caption;
1010 captionInfoExtraElement.innerHTML = caption;
1011
1012 domUtil.show( captionMainElement );
1013 domUtil.show( captionInfoExtraElement );
1014 }
1015
1016 if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( title ) ) {
1017 title = '';
1018 }
1019
1020 if ( domUtil.stripHTML( caption ) === domUtil.stripHTML( desc ) ) {
1021 desc = '';
1022 }
1023
1024 if ( domUtil.stripHTML( title ) === domUtil.stripHTML( desc ) ) {
1025 desc = '';
1026 }
1027
1028 if ( desc ) {
1029 descriptionElement.innerHTML = desc;
1030 domUtil.show( descriptionElement );
1031
1032 if ( ! title && ! caption ) {
1033 captionMainElement.innerHTML = domUtil.stripHTML( desc );
1034 domUtil.show( captionMainElement );
1035 }
1036 }
1037
1038 if ( title ) {
1039 var plainTitle = domUtil.stripHTML( title );
1040 titleElement.innerHTML = plainTitle;
1041
1042 if ( ! caption ) {
1043 captionMainElement.innerHTML = plainTitle;
1044 captionInfoExtraElement.innerHTML = plainTitle;
1045
1046 domUtil.show( captionMainElement );
1047 }
1048
1049 domUtil.show( titleElement );
1050 }
1051 }
1052 }
1053
1054 // updateExif updates the contents of the exif UL (.jp-carousel-image-exif)
1055 function updateExif( meta ) {
1056 if ( ! meta || Number( jetpackCarouselStrings.display_exif ) !== 1 ) {
1057 return false;
1058 }
1059
1060 var ul = carousel.info.querySelector( '.jp-carousel-image-meta ul.jp-carousel-image-exif' );
1061 var html = '';
1062
1063 for ( var key in meta ) {
1064 var val = meta[ key ];
1065 var metaKeys = jetpackCarouselStrings.meta_data || [];
1066
1067 if ( parseFloat( val ) === 0 || ! val.length || metaKeys.indexOf( key ) === -1 ) {
1068 continue;
1069 }
1070
1071 switch ( key ) {
1072 case 'focal_length':
1073 val = val + 'mm';
1074 break;
1075 case 'shutter_speed':
1076 val = formatShutterSpeed( val );
1077 break;
1078 case 'aperture':
1079 val = 'f/' + val;
1080 break;
1081 }
1082
1083 html += '<li><h5>' + jetpackCarouselStrings[ key ] + '</h5>' + val + '</li>';
1084 }
1085
1086 ul.innerHTML = html;
1087 ul.style.removeProperty( 'display' );
1088 }
1089
1090 // Update the contents of the jp-carousel-image-download link
1091 function updateFullSizeLink( currentSlide ) {
1092 if ( ! currentSlide ) {
1093 return false;
1094 }
1095 var original;
1096 var origSize = [ currentSlide.attrs.origWidth, currentSlide.attrs.origHeight ];
1097 var imageLinkParser = document.createElement( 'a' );
1098
1099 imageLinkParser.href = currentSlide.attrs.src.replace( /\?.+$/, '' );
1100
1101 // Is this a Photon URL?
1102 if ( imageLinkParser.hostname.match( /^i[\d]{1}\.wp\.com$/i ) !== null ) {
1103 original = imageLinkParser.href;
1104 } else {
1105 original = currentSlide.attrs.origFile.replace( /\?.+$/, '' );
1106 }
1107
1108 var downloadText = carousel.info.querySelector( '.jp-carousel-download-text' );
1109 var permalink = carousel.info.querySelector( '.jp-carousel-image-download' );
1110
1111 downloadText.innerHTML = util.applyReplacements(
1112 jetpackCarouselStrings.download_original,
1113 origSize
1114 );
1115 permalink.setAttribute( 'href', original );
1116 permalink.style.removeProperty( 'display' );
1117 }
1118
1119 function testCommentsOpened( opened ) {
1120 var commentForm = carousel.container.querySelector( '.jp-carousel-comment-form-container' );
1121 var isOpened = parseInt( opened, 10 ) === 1;
1122
1123 if ( isOpened ) {
1124 domUtil.fadeIn( commentForm );
1125 } else {
1126 domUtil.fadeOut( commentForm );
1127 }
1128 }
1129
1130 function fetchComments( attachmentId, offset ) {
1131 var shouldClear = offset === undefined;
1132 var commentsIndicator = carousel.info.querySelector(
1133 '.jp-carousel-icon-comments .jp-carousel-has-comments-indicator'
1134 );
1135
1136 commentsIndicator.classList.remove( 'jp-carousel-show' );
1137
1138 clearInterval( commentInterval );
1139
1140 if ( ! attachmentId ) {
1141 return;
1142 }
1143
1144 if ( ! offset || offset < 1 ) {
1145 offset = 0;
1146 }
1147
1148 var comments = carousel.info.querySelector( '.jp-carousel-comments' );
1149 var commentsLoading = carousel.info.querySelector( '#jp-carousel-comments-loading' );
1150 domUtil.show( commentsLoading );
1151
1152 if ( shouldClear ) {
1153 domUtil.hide( comments );
1154 comments.innerHTML = '';
1155 }
1156
1157 var xhr = new XMLHttpRequest();
1158 var url =
1159 jetpackCarouselStrings.ajaxurl +
1160 '?action=get_attachment_comments' +
1161 '&nonce=' +
1162 jetpackCarouselStrings.nonce +
1163 '&id=' +
1164 attachmentId +
1165 '&offset=' +
1166 offset;
1167 xhr.open( 'GET', url );
1168 xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
1169
1170 var onError = function () {
1171 domUtil.fadeIn( comments );
1172 domUtil.fadeOut( commentsLoading );
1173 };
1174
1175 xhr.onload = function () {
1176 // Ignore the results if they arrive late and we're now on a different slide.
1177 if (
1178 ! carousel.currentSlide ||
1179 carousel.currentSlide.attrs.attachmentId !== attachmentId
1180 ) {
1181 return;
1182 }
1183
1184 var isSuccess = xhr.status >= 200 && xhr.status < 300;
1185 var data;
1186 try {
1187 data = JSON.parse( xhr.responseText );
1188 } catch ( e ) {
1189 // Do nothing.
1190 }
1191
1192 if ( ! isSuccess || ! data || ! Array.isArray( data ) ) {
1193 return onError();
1194 }
1195
1196 if ( shouldClear ) {
1197 comments.innerHTML = '';
1198 }
1199
1200 for ( var i = 0; i < data.length; i++ ) {
1201 var entry = data[ i ];
1202 var comment = document.createElement( 'div' );
1203 comment.classList.add( 'jp-carousel-comment' );
1204 comment.setAttribute( 'id', 'jp-carousel-comment-' + entry.id );
1205 comment.innerHTML =
1206 '<div class="comment-gravatar">' +
1207 entry.gravatar_markup +
1208 '</div>' +
1209 '<div class="comment-content">' +
1210 '<div class="comment-author">' +
1211 entry.author_markup +
1212 '</div>' +
1213 '<div class="comment-date">' +
1214 entry.date_gmt +
1215 '</div>' +
1216 entry.content +
1217 '</div>';
1218 comments.appendChild( comment );
1219
1220 // Set the interval to check for a new page of comments.
1221 clearInterval( commentInterval );
1222 commentInterval = setInterval( function () {
1223 if ( carousel.container.scrollTop + 150 > window.innerHeight ) {
1224 fetchComments( attachmentId, offset + 10 );
1225 clearInterval( commentInterval );
1226 }
1227 }, 300 );
1228 }
1229
1230 if ( data.length > 0 ) {
1231 domUtil.show( comments );
1232 commentsIndicator.innerText = data.length;
1233 commentsIndicator.classList.add( 'jp-carousel-show' );
1234 }
1235
1236 domUtil.hide( commentsLoading );
1237 };
1238
1239 xhr.onerror = onError;
1240
1241 xhr.send();
1242 }
1243
1244 function loadFullImage( slide ) {
1245 var el = slide.el;
1246 var attrs = slide.attrs;
1247 var image = el.querySelector( 'img' );
1248
1249 if ( ! image.hasAttribute( 'data-loaded' ) ) {
1250 var hasPreview = !! attrs.previewImage;
1251 var thumbSize = attrs.thumbSize;
1252
1253 if ( ! hasPreview || ( thumbSize && el.offsetWidth > thumbSize.width ) ) {
1254 image.src = attrs.src;
1255 } else {
1256 image.src = attrs.previewImage;
1257 }
1258
1259 image.setAttribute( 'itemprop', 'image' );
1260 image.setAttribute( 'data-loaded', 1 );
1261 }
1262 }
1263
1264 function loadBackgroundImage( slide ) {
1265 var currentSlide = slide.el;
1266
1267 if ( swiper && swiper.slides ) {
1268 currentSlide = swiper.slides[ swiper.activeIndex ];
1269 }
1270
1271 var image = slide.attrs.originalElement;
1272 var isLoaded = image.complete && image.naturalHeight !== 0;
1273
1274 if ( isLoaded ) {
1275 applyBackgroundImage( slide, currentSlide, image );
1276 return;
1277 }
1278
1279 image.onload = function () {
1280 applyBackgroundImage( slide, currentSlide, image );
1281 };
1282 }
1283
1284 function applyBackgroundImage( slide, currentSlide, image ) {
1285 var url = util.getBackgroundImage( image );
1286 slide.backgroundImage = url;
1287 currentSlide.style.backgroundImage = 'url(' + url + ')';
1288 currentSlide.style.backgroundSize = 'cover';
1289 }
1290
1291 function clearCommentTextAreaValue() {
1292 if ( carousel.commentField ) {
1293 carousel.commentField.value = '';
1294 }
1295 }
1296
1297 function getOriginalDimensions( el ) {
1298 var size = el.getAttribute( 'data-orig-size' ) || '';
1299
1300 if ( size ) {
1301 var parts = size.split( ',' );
1302 return { width: parseInt( parts[ 0 ], 10 ), height: parseInt( parts[ 1 ], 10 ) };
1303 } else {
1304 return {
1305 width:
1306 el.getAttribute( 'data-original-width' ) || el.getAttribute( 'width' ) || undefined,
1307 height:
1308 el.getAttribute( 'data-original-height' ) || el.getAttribute( 'height' ) || undefined,
1309 };
1310 }
1311 }
1312
1313 function initCarouselSlides( items, startIndex ) {
1314 carousel.slides = [];
1315
1316 var max = calculateMaxSlideDimensions();
1317
1318 // If the startIndex is not 0 then preload the clicked image first.
1319 if ( startIndex !== 0 ) {
1320 var img = new Image();
1321 img.src = items[ startIndex ].getAttribute( 'data-gallery-src' );
1322 }
1323
1324 var useInPageThumbnails = !! domUtil.closest( items[ 0 ], '.tiled-gallery.type-rectangular' );
1325
1326 // create the 'slide'
1327 Array.prototype.forEach.call( items, function ( item, i ) {
1328 var permalinkEl = domUtil.closest( item, 'a' );
1329 var origFile = item.getAttribute( 'data-orig-file' ) || item.getAttribute( 'src-orig' );
1330 var attrID =
1331 item.getAttribute( 'data-attachment-id' ) || item.getAttribute( 'data-id' ) || '0';
1332 var caption = document.querySelector(
1333 'img[data-attachment-id="' + attrID + '"] + figcaption'
1334 );
1335
1336 if ( caption ) {
1337 caption = caption.innerHTML;
1338 } else {
1339 caption = item.getAttribute( 'data-image-caption' );
1340 }
1341
1342 var attrs = {
1343 originalElement: item,
1344 attachmentId: attrID,
1345 commentsOpened: item.getAttribute( 'data-comments-opened' ) || '0',
1346 imageMeta: domUtil.getJSONAttribute( item, 'data-image-meta' ) || {},
1347 title: item.getAttribute( 'data-image-title' ) || '',
1348 desc: item.getAttribute( 'data-image-description' ) || '',
1349 mediumFile: item.getAttribute( 'data-medium-file' ) || '',
1350 largeFile: item.getAttribute( 'data-large-file' ) || '',
1351 origFile: origFile || '',
1352 thumbSize: { width: item.naturalWidth, height: item.naturalHeight },
1353 caption: caption || '',
1354 permalink: permalinkEl && permalinkEl.getAttribute( 'href' ),
1355 src: origFile || item.getAttribute( 'src' ) || '',
1356 };
1357
1358 var tiledGalleryItem = domUtil.closest( item, '.tiled-gallery-item' );
1359 var tiledCaptionEl =
1360 tiledGalleryItem && tiledGalleryItem.querySelector( '.tiled-gallery-caption' );
1361 var tiledCaption = tiledCaptionEl && tiledCaptionEl.innerHTML;
1362 if ( tiledCaption ) {
1363 attrs.caption = tiledCaption;
1364 }
1365
1366 var origDimensions = getOriginalDimensions( item );
1367
1368 attrs.origWidth = origDimensions.width || attrs.thumbSize.width;
1369 attrs.origHeight = origDimensions.height || attrs.thumbSize.height;
1370
1371 if ( typeof wpcom !== 'undefined' && wpcom.carousel && wpcom.carousel.generateImgSrc ) {
1372 attrs.src = wpcom.carousel.generateImgSrc( item, max );
1373 } else {
1374 attrs.src = selectBestImageUrl( {
1375 origFile: attrs.src,
1376 origWidth: attrs.origWidth,
1377 origHeight: attrs.origHeight,
1378 maxWidth: max.width,
1379 maxHeight: max.height,
1380 mediumFile: attrs.mediumFile,
1381 largeFile: attrs.largeFile,
1382 } );
1383 }
1384
1385 // Set the final src.
1386 item.setAttribute( 'data-gallery-src', attrs.src );
1387
1388 if ( attrs.attachmentId !== '0' ) {
1389 attrs.title = util.texturize( attrs.title );
1390 attrs.desc = util.texturize( attrs.desc );
1391 attrs.caption = util.texturize( attrs.caption );
1392
1393 // Initially, the image is a 1x1 transparent gif.
1394 // The preview is shown as a background image on the slide itself.
1395 var image = new Image();
1396 image.src = attrs.src;
1397
1398 var slideEl = document.createElement( 'div' );
1399 slideEl.classList.add( 'swiper-slide' );
1400 slideEl.setAttribute( 'itemprop', 'associatedMedia' );
1401 slideEl.setAttribute( 'itemscope', '' );
1402 slideEl.setAttribute( 'itemtype', 'https://schema.org/ImageObject' );
1403 var zoomEl = document.createElement( 'div' );
1404 zoomEl.classList.add( 'swiper-zoom-container' );
1405
1406 carousel.gallery.appendChild( slideEl );
1407
1408 slideEl.appendChild( zoomEl );
1409 zoomEl.appendChild( image );
1410 slideEl.setAttribute( 'data-attachment-id', attrs.attachmentId );
1411 slideEl.setAttribute( 'data-permalink', attrs.permalink );
1412 slideEl.setAttribute( 'data-orig-file', attrs.origFile );
1413
1414 if ( useInPageThumbnails ) {
1415 // Use the image already loaded in the gallery as a preview.
1416 attrs.previewImage = attrs.src;
1417 }
1418
1419 var slide = { el: slideEl, attrs: attrs, index: i };
1420 carousel.slides.push( slide );
1421 }
1422 } );
1423 }
1424
1425 function loadSwiper( gallery, options ) {
1426 if ( ! window.Swiper670 ) {
1427 var loader = document.querySelector( '#jp-carousel-loading-overlay' );
1428 domUtil.show( loader );
1429 var jsScript = document.createElement( 'script' );
1430 jsScript.id = 'jetpack-carousel-swiper-js';
1431 jsScript.src = window.jetpackSwiperLibraryPath.url;
1432 jsScript.async = true;
1433 jsScript.onload = function () {
1434 domUtil.hide( loader );
1435 openCarousel( gallery, options );
1436 };
1437 jsScript.onerror = function () {
1438 domUtil.hide( loader );
1439 };
1440 document.head.appendChild( jsScript );
1441 return;
1442 }
1443 openCarousel( gallery, options );
1444 }
1445
1446 function openCarousel( gallery, options ) {
1447 var settings = {
1448 imgSelector:
1449 '.gallery-item [data-attachment-id], .tiled-gallery-item [data-attachment-id], img[data-attachment-id], img[data-id]',
1450 startIndex: 0,
1451 };
1452
1453 var data = domUtil.getJSONAttribute( gallery, 'data-carousel-extra' );
1454 var tapTimeout;
1455
1456 if ( ! data ) {
1457 return; // don't run if the default gallery functions weren't used
1458 }
1459
1460 initializeCarousel();
1461
1462 if ( carousel.isOpen ) {
1463 return; // don't open if already opened
1464 }
1465 carousel.isOpen = true;
1466
1467 // make sure to stop the page from scrolling behind the carousel overlay, so we don't trigger
1468 // infiniscroll for it when enabled (Reader, theme infiniscroll, etc).
1469 originalOverflow = getComputedStyle( document.body ).overflow;
1470 document.body.style.overflow = 'hidden';
1471 // prevent html from overflowing on some of the new themes.
1472 originalHOverflow = getComputedStyle( document.documentElement ).overflow;
1473 document.documentElement.style.overflow = 'hidden';
1474 scrollPos = window.scrollY || window.pageYOffset || 0;
1475
1476 carousel.container.setAttribute( 'data-carousel-extra', JSON.stringify( data ) );
1477 stat( [ 'open', 'view_image' ] );
1478
1479 // If options exist, lets merge them
1480 // with our default settings
1481 for ( var option in options || {} ) {
1482 settings[ option ] = options[ option ];
1483 }
1484
1485 if ( settings.startIndex === -1 ) {
1486 settings.startIndex = 0; // -1 returned if can't find index, so start from beginning
1487 }
1488
1489 domUtil.emitEvent( carousel.overlay, 'jp_carousel.beforeOpen' );
1490 carousel.gallery.innerHTML = '';
1491
1492 // Need to set the overlay manually to block or swiper does't initialise properly.
1493 carousel.overlay.style.opacity = 1;
1494 carousel.overlay.style.display = 'block';
1495
1496 initCarouselSlides( gallery.querySelectorAll( settings.imgSelector ), settings.startIndex );
1497
1498 swiper = new window.Swiper670( '.jp-carousel-swiper-container', {
1499 centeredSlides: true,
1500 zoom: true,
1501 loop: carousel.slides.length > 1,
1502 // Turn off interactions and hide navigation arrows if there is only one slide.
1503 enabled: carousel.slides.length > 1,
1504 pagination: {
1505 el: '.jp-swiper-pagination',
1506 clickable: true,
1507 },
1508 navigation: {
1509 nextEl: '.jp-swiper-button-next',
1510 prevEl: '.jp-swiper-button-prev',
1511 },
1512 initialSlide: settings.startIndex,
1513 on: {
1514 init: function () {
1515 selectSlideAtIndex( settings.startIndex );
1516 },
1517 },
1518 preventClicks: false,
1519 preventClicksPropagation: false,
1520 preventInteractionOnTransition: ! domUtil.isTouch(),
1521 threshold: 5,
1522 } );
1523
1524 swiper.on( 'slideChange', function ( swiper ) {
1525 var index;
1526 // Swiper indexes slides from 1, plus when looping to left last slide ends up
1527 // as 0 and looping to right first slide as total slides + 1. These are adjusted
1528 // here to match index of carousel.slides.
1529 if ( swiper.activeIndex === 0 ) {
1530 index = carousel.slides.length - 1;
1531 } else if ( swiper.activeIndex === carousel.slides.length + 1 ) {
1532 index = 0;
1533 } else {
1534 index = swiper.activeIndex - 1;
1535 }
1536 selectSlideAtIndex( index );
1537
1538 carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
1539 } );
1540
1541 swiper.on( 'zoomChange', function ( swiper, scale ) {
1542 if ( scale > 1 ) {
1543 carousel.overlay.classList.add( 'jp-carousel-hide-controls' );
1544 }
1545
1546 if ( scale === 1 ) {
1547 carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
1548 }
1549 } );
1550
1551 swiper.on( 'doubleTap', function ( swiper ) {
1552 clearTimeout( tapTimeout );
1553 if ( swiper.zoom.scale === 1 ) {
1554 var zoomTimeout = setTimeout( function () {
1555 carousel.overlay.classList.remove( 'jp-carousel-hide-controls' );
1556 clearTimeout( zoomTimeout );
1557 }, 150 );
1558 }
1559 } );
1560
1561 swiper.on( 'tap', function () {
1562 if ( swiper.zoom.scale > 1 ) {
1563 tapTimeout = setTimeout( function () {
1564 carousel.overlay.classList.toggle( 'jp-carousel-hide-controls' );
1565 }, 150 );
1566 }
1567 } );
1568
1569 domUtil.fadeIn( carousel.overlay, function () {
1570 domUtil.emitEvent( carousel.overlay, 'jp_carousel.afterOpen' );
1571 } );
1572 }
1573
1574 // Register the event listener for starting the gallery
1575 document.body.addEventListener( 'click', function ( e ) {
1576 var isCompatible =
1577 window.CSS && window.CSS.supports && window.CSS.supports( 'display', 'grid' );
1578
1579 // IE11 support is being dropped in August 2021. The new swiper.js libray is not IE11 compat
1580 // so just default to opening individual image attachment/media pages for IE.
1581 if ( ! isCompatible ) {
1582 return;
1583 }
1584
1585 var target = e.target;
1586 var gallery = domUtil.closest( target, gallerySelector );
1587
1588 if ( gallery ) {
1589 if ( ! testForData( gallery ) ) {
1590 return;
1591 }
1592
1593 var parent = target.parentElement;
1594 var grandparent = parent.parentElement;
1595
1596 // If Gallery is made up of individual Image blocks check for custom link before
1597 // loading carousel. The custom link may be the parent or could be a descendant
1598 // of the parent if the image has rounded corners.
1599 var parentHref = null;
1600 if ( grandparent && grandparent.classList.contains( 'wp-block-image' ) ) {
1601 parentHref = parent.getAttribute( 'href' );
1602 } else if (
1603 parent &&
1604 parent.classList.contains( 'wp-block-image' ) &&
1605 parent.querySelector( ':scope > a' )
1606 ) {
1607 parentHref = parent.querySelector( ':scope > a' ).getAttribute( 'href' );
1608 }
1609
1610 // If the link does not point to the attachment or media file then assume Image has
1611 // a custom link so don't load the carousel.
1612 if (
1613 parentHref &&
1614 parentHref.split( '?' )[ 0 ] !==
1615 target.getAttribute( 'data-orig-file' ).split( '?' )[ 0 ] &&
1616 parentHref !== target.getAttribute( 'data-permalink' )
1617 ) {
1618 return;
1619 }
1620
1621 // Do not open the modal if we are looking at a gallery caption from before WP5, which may contain a link.
1622 if ( parent.classList.contains( 'gallery-caption' ) ) {
1623 return;
1624 }
1625
1626 // Do not open the modal if we are looking at a caption of a gallery block, which may contain a link.
1627 if ( domUtil.matches( parent, 'figcaption' ) ) {
1628 return;
1629 }
1630
1631 // Set height to auto.
1632 // Fix some themes where closing carousel brings view back to top.
1633 document.documentElement.style.height = 'auto';
1634
1635 e.preventDefault();
1636
1637 // Stopping propagation in case there are parent elements
1638 // with .gallery or .tiled-gallery class
1639 e.stopPropagation();
1640
1641 var item = domUtil.closest( target, itemSelector );
1642 var index = Array.prototype.indexOf.call( gallery.querySelectorAll( itemSelector ), item );
1643 loadSwiper( gallery, { startIndex: index } );
1644 }
1645 } );
1646
1647 // Handle lightbox (single image gallery) for images linking to 'Attachment Page'.
1648 if ( Number( jetpackCarouselStrings.single_image_gallery ) === 1 ) {
1649 processSingleImageGallery();
1650 document.body.addEventListener( 'is.post-load', function () {
1651 processSingleImageGallery();
1652 } );
1653 }
1654
1655 // Makes carousel work on page load and when back button leads to same URL with carousel hash
1656 // (i.e. no actual document.ready trigger).
1657 window.addEventListener( 'hashchange', function () {
1658 var hashRegExp = /jp-carousel-(\d+)/;
1659
1660 if ( ! window.location.hash || ! hashRegExp.test( window.location.hash ) ) {
1661 if ( carousel.isOpen ) {
1662 closeCarousel();
1663 }
1664
1665 return;
1666 }
1667
1668 if ( window.location.hash === lastKnownLocationHash && carousel.isOpen ) {
1669 return;
1670 }
1671
1672 if ( window.location.hash && carousel.gallery && ! carousel.isOpen && history.back ) {
1673 history.back();
1674 return;
1675 }
1676
1677 lastKnownLocationHash = window.location.hash;
1678 var matchList = window.location.hash.match( hashRegExp );
1679 var attachmentId = parseInt( matchList[ 1 ], 10 );
1680 var galleries = document.querySelectorAll( gallerySelector );
1681
1682 // Find the first thumbnail that matches the attachment ID in the location
1683 // hash, then open the gallery that contains it.
1684 for ( var i = 0; i < galleries.length; i++ ) {
1685 var gallery = galleries[ i ];
1686 var selected;
1687
1688 var images = gallery.querySelectorAll( 'img' );
1689 for ( var j = 0; j < images.length; j++ ) {
1690 if (
1691 parseInt( images[ j ].getAttribute( 'data-attachment-id' ), 10 ) === attachmentId ||
1692 parseInt( images[ j ].getAttribute( 'data-id' ), 10 ) === attachmentId
1693 ) {
1694 selected = j;
1695 break;
1696 }
1697 }
1698
1699 if ( selected !== undefined ) {
1700 openOrSelectSlide( gallery, selected );
1701 break;
1702 }
1703 }
1704 } );
1705
1706 if ( window.location.hash ) {
1707 domUtil.emitEvent( window, 'hashchange' );
1708 }
1709 }
1710
1711 if ( document.readyState !== 'loading' ) {
1712 init();
1713 } else {
1714 document.addEventListener( 'DOMContentLoaded', init );
1715 }
1716 } )();
1717