UNPKG

22.6 kBJavaScriptView Raw
1/**!
2 * @preserve nanogallery2 - GOOGLE PHOTOS data provider
3 * Homepage: http://nanogallery2.nanostudio.org
4 * Sources: https://github.com/nanostudio-org/nanogallery2
5 *
6 * License: GPLv3 and commercial licence
7 *
8*/
9
10// ###################################################
11// ##### nanogallery2 - module for GOOGLE PHOTOS #####
12// ##### requires nanogp #####
13// ###################################################
14
15
16(function (factory) {
17 "use strict";
18 if (typeof define === 'function' && define.amd) {
19 // AMD. Register as an anonymous module.
20 define(['jquery', 'nanogallery2'], factory);
21 } else if (typeof exports === 'object' && typeof require === 'function') {
22 // Browserify
23 factory(require(['jquery', 'nanogallery2']));
24 } else {
25 // Browser globals
26 factory(jQuery);
27 }
28}(function ($) {
29// ;(function ($) {
30
31 jQuery.nanogallery2.data_google2 = function (instance, fnName){
32 var G=instance; // current nanogallery2 instance
33
34 // ### Picasa/Google+
35 // square format : 32, 48, 64, 72, 104, 144, 150, 160 (cropped)
36 // details: https://developers.google.com/picasa-web/docs/2.0/reference
37 Google = {
38 url: function() {
39 // return ( G.O.picasaUseUrlCrossDomain ? 'https://photos.googleapis.com/data/feed/api/' : 'https://picasaweb.google.com/data/feed/api/');
40 return ( 'https://photos.googleapis.com/data/feed/api/' );
41 },
42 thumbSize: 64,
43 thumbAvailableSizes : new Array(32, 48, 64, 72, 94, 104, 110, 128, 144, 150, 160, 200, 220, 288, 320, 400, 512, 576, 640, 720, 800, 912, 1024, 1152, 1280, 1440, 1600),
44 thumbAvailableSizesCropped : ' 32 48 64 72 104 144 150 160 '
45 };
46
47
48 /** @function AlbumGetContent */
49 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
50
51
52 var url= Google.url() + 'user/'+G.O.userID;
53 var kind= 'image';
54 var albumIdx=NGY2Item.GetIdx(G, albumID);
55
56 var maxResults='';
57 if( G.galleryMaxItems.Get() > 0 ) {
58 maxResults='&max-results='+G.galleryMaxItems.Get();
59 }
60
61 var gat=''; // global authorization (using the Builder)
62 if( typeof ngy2_pwa_at !== 'undefined' ) {
63 gat=ngy2_pwa_at;
64 }
65
66 if( albumID == 0 ) {
67 // if( G.I[albumIdx].GetID() == 0 ) {
68 // retrieve the list of albums
69 if( gat != '' ) {
70 // in builder
71 url += '?alt=json&v=3&kind=album&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime()) + '&access_token=' + gat;
72 }
73 else {
74 if( G.O.google2URL == undefined || G.O.google2URL == '' ) {
75 // old Picasa access method (for content before 09/02/2017)
76 url += '?alt=json&v=3&kind=album&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime());
77 }
78 else {
79 // nanogp
80 url=G.O.google2URL + '?nguserid='+G.O.userID+'&alt=json&v=3&kind=album&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime());
81 }
82 }
83 kind='album';
84 }
85 else {
86 // retrieve the content of one album (=photos)
87 var auth='';
88 if( G.I[albumIdx].authkey != '' ) {
89 // private album
90 auth=G.I[albumIdx].authkey;
91 }
92 if( gat != '' ) {
93 // in builder
94 url += '/albumid/'+albumID+'?alt=json&kind=photo&thumbsize='+G.picasa.thumbSizes+maxResults+auth+'&imgmax=d&access_token=' + gat;
95 }
96 else {
97 if( G.O.google2URL == undefined || G.O.google2URL == '' ) {
98 // old Picasa access method (for content before 09/02/2017)
99 url += '/albumid/'+albumID+'?alt=json&v=3&kind=photo&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime());
100 }
101 else {
102 // nanogp
103 url=G.O.google2URL + '?nguserid='+G.O.userID+'&ngalbumid='+albumID+'&alt=json&v=3&kind=photo&thumbsize='+G.picasa.thumbSizes+maxResults+auth+'&imgmax=d';
104 }
105 }
106 }
107
108 if( G.O.debugMode ) { console.log('Google Photos URL: ' + url); }
109
110 PreloaderDisplay(true);
111 jQuery.ajaxSetup({ cache: false });
112 jQuery.support.cors = true;
113 try {
114
115 var tId = setTimeout( function() {
116 // workaround to handle JSONP (cross-domain) errors
117 PreloaderDisplay(false);
118 NanoAlert('Could not retrieve AJAX data...');
119 }, 60000 );
120
121 var GI_getJSONfinished = function(data){
122 clearTimeout(tId);
123 PreloaderDisplay(false);
124 GoogleParseData( albumIdx, kind, data );
125 AlbumPostProcess(albumID);
126 if( fnToCall !== null && fnToCall !== undefined) {
127 fnToCall( fnParam1, fnParam2, null );
128 }
129 };
130
131 var gi_data_loaded = null;
132 // load more than 1000 data (contributor: Giovanni Chiodi)
133 var GI_loadJSON = function(url,start_index){
134 // console.log(url + '&start-index=' + start_index + '&callback=?');
135 jQuery.getJSON( url + '&start-index=' + start_index + '&callback=?', function(data) {
136
137 if( data.nano_status == 'error' ) {
138 clearTimeout(tId);
139 PreloaderDisplay(false);
140 NanoAlert(G, "Could not retrieve Google data. Error: " + data.nano_message);
141 return;
142 }
143
144 if (gi_data_loaded===null) {
145 gi_data_loaded = data;
146 }
147 else {
148 gi_data_loaded.feed.entry=gi_data_loaded.feed.entry.concat(data.feed.entry);
149 }
150
151 var cnt=data.feed.openSearch$startIndex.$t+data.feed.openSearch$itemsPerPage.$t;
152 var numItems=0;
153 if( kind == 'image' ) {
154 // retrieve the number of images from one album
155 if( data.feed.gphoto$numphotos === undefined ) {
156 numItems=data.feed.openSearch$totalResults.$t;
157 }
158 else {
159 numItems=data.feed.gphoto$numphotos.$t;
160 }
161 }
162 else {
163 // retrieve the number of images from a list of albums
164 numItems=data.feed.openSearch$totalResults.$t;
165 }
166
167 // if (data.feed.openSearch$startIndex.$t+data.feed.openSearch$itemsPerPage.$t>=data.feed.openSearch$totalResults.$t){
168 if( cnt >= numItems || cnt >= G.galleryMaxItems.Get() ) {
169 //ok finito
170 GI_getJSONfinished(gi_data_loaded);
171 }
172 else {
173 //ce ne sono ancora da caricare
174 //altra chiamata per il rimanente
175 GI_loadJSON(url, cnt);
176 }
177 })
178 .fail( function(jqxhr, textStatus, error) {
179 clearTimeout(tId);
180 PreloaderDisplay(false);
181
182 var k=''
183 for(var key in jqxhr) {
184 k+= key + '=' + jqxhr[key] +'<br>';
185 }
186 var err = textStatus + ', ' + error + ' ' + k + '<br><br>URL:'+url;
187 NanoAlert(G, "Could not retrieve Google data. Error: " + err);
188
189 });
190
191 };
192
193 GI_loadJSON(url,1);
194 }
195 catch(e) {
196 NanoAlert(G, "Could not retrieve Google data. Error: " + e);
197 }
198 }
199
200
201 // -----------
202 // Retrieve items from a Google Photos (ex Picasa) data stream
203 // items can be images or albums
204 function GoogleParseData(albumIdx, kind, data) {
205
206 if( G.O.debugMode ) {
207 console.log('Google Photos data:');
208 console.dir(data);
209 }
210
211 var albumID=G.I[albumIdx].GetID();
212
213 if( G.I[albumIdx].title == '' ) {
214 // set title of the album (=> root level not loaded at this time)
215 G.I[albumIdx].title=data.feed.title.$t;
216 }
217
218 // iterate and parse each item
219 jQuery.each(data.feed.entry, function(i,data){
220
221 // Get the title
222 var imgUrl=data.media$group.media$content[0].url;
223 var itemTitle = data.title.$t;
224
225
226 // Get the description
227 var filename='';
228 var itemDescription = data.media$group.media$description.$t;
229 if( kind == 'image') {
230 // if image, the title contains the image filename -> replace with content of description
231 filename=itemTitle;
232 if( itemDescription != '' ) {
233 itemTitle=itemDescription;
234 itemDescription='';
235 }
236 if( G.O.thumbnailLabel.get('title') != '' ) {
237 // use filename for the title (extract from URL)
238 itemTitle=GetImageTitleFromURL(unescape(unescape(unescape(unescape(imgUrl)))));
239 }
240 }
241
242 var itemID = data.gphoto$id.$t;
243 if( !(kind == 'album' && !FilterAlbumName(itemTitle, itemID)) ) {
244
245 // create ngy2 item
246 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, kind, '' );
247
248 // set the image src
249 var src = '';
250 if( kind == 'image' ) {
251 src = imgUrl;
252 if( !G.O.viewerZoom && G.O.viewerZoom != undefined ) {
253 var s = imgUrl.substring(0, imgUrl.lastIndexOf('/'));
254 s = s.substring(0, s.lastIndexOf('/')) + '/';
255 if( window.screen.width > window.screen.height ) {
256 src=s + 'w' + window.screen.width + '/' + filename;
257 }
258 else {
259 src = s + 'h' + window.screen.height + '/' + filename;
260 }
261 }
262 // image's URL
263 newItem.setMediaURL( src, 'img');
264
265 // image size
266 if( data.gphoto$width !== undefined ) {
267 newItem.imageWidth=parseInt(data.gphoto$width.$t);
268 }
269 if( data.gphoto$height !== undefined ) {
270 newItem.imageHeight=parseInt(data.gphoto$height.$t);
271 }
272
273 if( data.media$group != null && data.media$group.media$credit != null && data.media$group.media$credit.length > 0 ) {
274 newItem.author=data.media$group.media$credit[0].$t;
275 }
276
277
278 // exif data
279 if( data.exif$tags !== undefined ) {
280 if( data.exif$tags.exif$exposure != undefined ) {
281 newItem.exif.exposure = data.exif$tags.exif$exposure.$t;
282 }
283 if( data.exif$tags.exif$flash != undefined ) {
284 if( data.exif$tags.exif$flash.$t == 'true' ) {
285 newItem.exif.flash = 'flash';
286 }
287 }
288 if( data.exif$tags.exif$focallength != undefined ) {
289 newItem.exif.focallength = data.exif$tags.exif$focallength.$t;
290 }
291 if( data.exif$tags.exif$fstop != undefined ) {
292 newItem.exif.fstop = data.exif$tags.exif$fstop.$t;
293 }
294 if( data.exif$tags.exif$iso != undefined ) {
295 newItem.exif.iso = data.exif$tags.exif$iso.$t;
296 }
297 if( data.exif$tags.exif$model != undefined ) {
298 newItem.exif.model = data.exif$tags.exif$model.$t;
299 }
300
301 // geo location
302 if( data.gphoto$location != undefined ) {
303 newItem.exif.location = data.gphoto$location;
304 }
305 }
306 }
307 else {
308 newItem.author = data.author[0].name.$t;
309 newItem.numberItems = data.gphoto$numphotos.$t;
310 }
311
312 // set the URL of the thumbnails images
313 newItem.thumbs=GoogleThumbSetSizes('l1', 0, newItem.thumbs, data, kind );
314 newItem.thumbs=GoogleThumbSetSizes('lN', 5, newItem.thumbs, data, kind );
315
316 // post-process callback
317 var fu = G.O.fnProcessData;
318 if( fu !== null ) {
319 typeof fu == 'function' ? fu(newItem, 'google2', data) : window[fu](newItem, 'google2', data);
320 }
321
322 }
323 });
324
325 G.I[albumIdx].contentIsLoaded = true; // album's content is ready
326 }
327
328
329
330 /** @function GetHiddenAlbums */
331 var GetHiddenAlbums = function( hiddenAlbums, callback ){
332 var lstAlbums = [].concat( hiddenAlbums );
333 for( var i = 0; i < lstAlbums.length; i++ ) {
334 AlbumAuthkeyGetInfoQueue(lstAlbums[i], callback);
335 }
336 // dequeue sequentially
337 jQuery(document).dequeue('GoogleAlbumWithAuthkey');
338 }
339
340 // Google+ - retrieves private album
341 // The first image is used as the cover image (=album thumbnail)
342 function AlbumAuthkeyGetInfoQueue( albumIDwithAuthkey, callback ) {
343 jQuery(document).queue('GoogleAlbumWithAuthkey', function() {
344
345 var p = albumIDwithAuthkey.indexOf('&authkey=');
346 if( p == -1 ) {
347 p = albumIDwithAuthkey.indexOf('?authkey=');
348 }
349 var albumID = albumIDwithAuthkey.substring(0,p);
350
351 var opt = albumIDwithAuthkey.substring(p);
352 if( opt.indexOf('Gv1sRg') == -1 ) {
353 opt = '&authkey=Gv1sRg'+opt.substring(9);
354 }
355 var url = Google.url() + 'user/'+G.O.userID+'/albumid/'+albumID+'?alt=json&kind=photo'+opt+'&max-results=1&thumbsize='+G.picasa.thumbSizes+'&imgmax=d';
356
357 PreloaderDisplay(true);
358
359 jQuery.ajaxSetup({ cache: false });
360 jQuery.support.cors = true;
361
362 var tId = setTimeout( function() {
363 // workaround to handle JSONP (cross-domain) errors
364 PreloaderDisplay(false);
365 NanoAlert(G, 'Could not retrieve AJAX data...');
366 }, 60000 );
367 jQuery.getJSON(url, function(data, status, xhr) {
368 clearTimeout(tId);
369 PreloaderDisplay(false);
370
371 var albumTitle = data.feed.title.$t;
372 var source = data.feed.entry[0];
373
374 var newItem = NGY2Item.New( G, albumTitle, '', albumID, '0', 'album', '' );
375
376 newItem.authkey = opt;
377
378 //Get and set the URLs of the thumbnail
379 newItem.thumbs = GoogleThumbSetSizes('l1', 0, newItem.thumbs, source, 'album' );
380 newItem.thumbs = GoogleThumbSetSizes('lN', 5, newItem.thumbs, source, 'album' );
381
382 if( typeof G.O.fnProcessData == 'function' ) {
383 G.O.fnProcessData(newItem, 'google', source);
384 }
385// G.I[1].contentIsLoaded=true;
386 newItem.numberItems = data.feed.gphoto$numphotos.$t;
387
388 // dequeue to process the next google+/picasa private album
389 if( jQuery(document).queue('GoogleAlbumWithAuthkey').length > 0 ) {
390 jQuery(document).dequeue('GoogleAlbumWithAuthkey');
391 }
392 else {
393 callback();
394 }
395
396 })
397 .fail( function(jqxhr, textStatus, error) {
398 clearTimeout(tId);
399 PreloaderDisplay(false);
400 NanoAlert(G, "Could not retrieve ajax data (google): " + textStatus + ', ' + error);
401 jQuery(document).dequeue('GoogleAlbumWithAuthkey');
402 });
403 });
404
405 }
406
407 // -----------
408 // Set thumbnail sizes (width and height) and URLs (for all resolutions (xs, sm, me, la, xl) and levels (l1, lN)
409 function GoogleThumbSetSizes(level, startI, tn, data, kind ) {
410 var sizes=['xs','sm','me','la','xl'];
411
412 for(var i=0; i<sizes.length; i++ ) {
413 tn.url[level][sizes[i]]=data.media$group.media$thumbnail[startI+i].url;
414 if( kind == 'image' ) {
415 tn.width[level][sizes[i]]=data.media$group.media$thumbnail[startI+i].width;
416 tn.height[level][sizes[i]]=data.media$group.media$thumbnail[startI+i].height;
417
418 var gw=data.media$group.media$thumbnail[startI+i].width;
419 var gh=data.media$group.media$thumbnail[startI+i].height;
420 if( G.tn.settings.width[level][sizes[i]] == 'auto' ) {
421 if( gh < G.tn.settings.height[level][sizes[i]] ) {
422 // calculate new h/w and change URL
423 var ratio1=gw/gh;
424 tn.width[level][sizes[i]]=gw*ratio1;
425 tn.height[level][sizes[i]]=gh*ratio1;
426 var url=tn.url[level][sizes[i]].substring(0, tn.url[level][sizes[i]].lastIndexOf('/'));
427 url=url.substring(0, url.lastIndexOf('/')) + '/';
428 tn.url[level][sizes[i]]=url+'h'+G.tn.settings.height[level][sizes[i]]+'/';
429 }
430 }
431 if( G.tn.settings.height[level][sizes[i]] == 'auto' ) {
432 if( gw < G.tn.settings.width[level][sizes[i]] ) {
433 // calculate new h/w and change URL
434 var ratio2=gh/gw;
435 tn.height[level][sizes[i]]=gh*ratio2;
436 tn.width[level][sizes[i]]=gw*ratio2;
437 var url=tn.url[level][sizes[i]].substring(0, tn.url[level][sizes[i]].lastIndexOf('/'));
438 url=url.substring(0, url.lastIndexOf('/')) + '/';
439 tn.url[level][sizes[i]]=url+'w'+G.tn.settings.width[level][sizes[i]]+'/';
440 }
441 }
442 }
443 else {
444 // albums
445 // the Google API returns incorrect height/width values
446 if( G.tn.settings.width[level][sizes[i]] != 'auto' ) {
447// tn.width[level][sizes[i]]=data.media$group.media$thumbnail[startI+i].width;
448 }
449 else {
450 var url=tn.url[level][sizes[i]].substring(0, tn.url[level][sizes[i]].lastIndexOf('/'));
451 url=url.substring(0, url.lastIndexOf('/')) + '/';
452 tn.url[level][sizes[i]]=url+'h'+G.tn.settings.height[level][sizes[i]]+'/';
453 }
454
455 if( G.tn.settings.height[level][sizes[i]] != 'auto' ) {
456// tn.height[level][sizes[i]]=data.media$group.media$thumbnail[startI+i].height;
457 }
458 else {
459 var url=tn.url[level][sizes[i]].substring(0, tn.url[level][sizes[i]].lastIndexOf('/'));
460 url=url.substring(0, url.lastIndexOf('/')) + '/';
461 tn.url[level][sizes[i]]=url+'w'+G.tn.settings.width[level][sizes[i]]+'/';
462 }
463 }
464 }
465 return tn;
466 }
467
468
469 // -----------
470 // Initialize thumbnail sizes
471 function Init() {
472 G.picasa = {
473 // cache value in instance to avoid regeneration on each need
474 thumbSizes:''
475 };
476
477 var sfL1=1;
478 if( G.tn.opt.l1.crop === true ) {
479 sfL1=G.O.thumbnailCropScaleFactor;
480 }
481 var sfLN=1;
482 if( G.tn.opt.lN.crop === true ) {
483 sfLN=G.O.thumbnailCropScaleFactor;
484 }
485
486 var st=G.tn.settings;
487 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.l1.xs*sfL1*st.mosaic.l1Factor.w.xs, st.height.l1.xs*sfL1*st.mosaic.l1Factor.h.xs, st.width.l1.xsc, st.height.l1.xsc );
488 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.l1.sm*sfL1*st.mosaic.l1Factor.w.sm, st.height.l1.sm*sfL1*st.mosaic.l1Factor.h.sm, st.width.l1.smc, st.height.l1.smc );
489 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.l1.me*sfL1*st.mosaic.l1Factor.w.me, st.height.l1.me*sfL1*st.mosaic.l1Factor.h.me, st.width.l1.mec, st.height.l1.mec );
490 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.l1.la*sfL1*st.mosaic.l1Factor.w.la, st.height.l1.la*sfL1*st.mosaic.l1Factor.h.la, st.width.l1.lac, st.height.l1.lac );
491 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.l1.xl*sfL1*st.mosaic.l1Factor.w.xl, st.height.l1.xl*sfL1*st.mosaic.l1Factor.h.xl, st.width.l1.xlc, st.height.l1.xlc );
492 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.lN.xs*sfLN*st.mosaic.lNFactor.w.xs, st.height.lN.xs*sfLN*st.mosaic.lNFactor.h.xs, st.width.lN.xsc, st.height.lN.xsc );
493 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.lN.sm*sfLN*st.mosaic.lNFactor.w.sm, st.height.lN.sm*sfLN*st.mosaic.lNFactor.h.sm, st.width.lN.smc, st.height.lN.smc );
494 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.lN.me*sfLN*st.mosaic.lNFactor.w.me, st.height.lN.me*sfLN*st.mosaic.lNFactor.h.me, st.width.lN.mec, st.height.lN.mec );
495 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.lN.la*sfLN*st.mosaic.lNFactor.w.la, st.height.lN.la*sfLN*st.mosaic.lNFactor.h.la, st.width.lN.lac, st.height.lN.lac );
496 G.picasa.thumbSizes=GoogleAddOneThumbSize(G.picasa.thumbSizes, st.width.lN.xl*sfLN*st.mosaic.lNFactor.w.xl, st.height.lN.xl*sfLN*st.mosaic.lNFactor.h.xl, st.width.lN.xlc, st.height.lN.xlc );
497 }
498
499 function GoogleAddOneThumbSize(thumbSizes, v1, v2, c1, c2 ) {
500
501 var v = Math.ceil( v2 * G.tn.scale ) + c2;
502 // if( v1 == 'auto' ) {
503 if( isNaN(v1) ) {
504 v = Math.ceil( v2 * G.tn.scale ) + c2;
505 }
506 // else if( v2 == 'auto' ) {
507 else if( isNaN(v2) ) {
508 v = Math.ceil( v1 * G.tn.scale ) + c1;
509 }
510 else if( v1 > v2 ) {
511 v = Math.ceil( v1 * G.tn.scale ) + c1;
512 }
513
514 if( thumbSizes.length > 0 ) {
515 thumbSizes += ',';
516 }
517 thumbSizes += v;
518 return thumbSizes;
519 }
520
521
522 // shortcuts to NGY2Tools functions (with context)
523 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
524 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
525 var NanoAlert = NGY2Tools.NanoAlert;
526 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
527 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
528 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
529
530 switch( fnName ){
531 case 'GetHiddenAlbums':
532 var hiddenAlbums = arguments[2],
533 callback1 = arguments[3];
534 GetHiddenAlbums(hiddenAlbums, callback1);
535 break;
536 case 'AlbumGetContent':
537 var albumID = arguments[2],
538 callback2 = arguments[3],
539 cbParam1 = arguments[4],
540 cbParam2 = arguments[5];
541 AlbumGetContent(albumID, callback2, cbParam1, cbParam2);
542 break;
543 case 'Init':
544 Init();
545 break;
546 case '':
547 break;
548 }
549
550 };
551
552// END GOOGLE DATA SOURCE FOR NANOGALLERY2
553// }( jQuery ));
554}));
555
556
557
558
\No newline at end of file