jetpack-carousel.js 52 KB

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