<?xml version="1.0" encoding="UTF-8"?>
<javascript app="gallery">
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/browse" javascript_name="ips.browse.lightbox.js" javascript_type="controller" javascript_version="103009" javascript_position="1000200"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.browse.lightbox.js - Gallery browse list controller
 *
 * Author: Brandon Farber
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.browse.imageLightbox', {
		/**
		 * Initialize controller
		 *
		 * @returns {void}
		 */
		initialize: function () {
			this.on( 'click', '[data-imageLightbox]', this.launchLightbox );
			this.on( document, 'keydown', this.keyDown );

			// Primary event that watches for URL changes
			History.Adapter.bind( window, 'statechange', _.bind( this.stateChange, this ) );

			this.setup();
		},

		/**
		 * Setup controller instance
		 *
		 * @returns {void}
		 */
		setup: function () {
			if( !_.isUndefined( this.scope.attr('data-launchLightbox') ) ){
				this._launch( this.scope.attr('data-lightboxURL'), document.title );
			}
		},

		/**
		 * Monitor state change and close lightbox if we go back again
		 */
		closeLightboxNextStateChange: false,

		/**
		 * Handles URL state changes
		 *
		 * @returns {void}
		 */
		stateChange: function () {
			var state = History.getState();

			// Monitor for back button when we're on the 'first' image
			if( state.data.controller == 'gallery.front.browse.imageLightbox' && ( !_.isUndefined( state.data.initialLaunch ) && state.data.initialLaunch == true ) ) {
				this.closeLightboxNextStateChange = true;
			}

			if( state.data.controller != 'gallery.front.view.image' ){
				if( state.data.controller == 'gallery.front.browse.imageLightbox' && ( _.isUndefined( state.data.initialLaunch ) || state.data.initialLaunch != true ) ) {
					this.closeLightboxNextStateChange = false;
					Debug.log( state.data );
					this.closeLightbox();
				}
				return;
			}

			this.closeLightboxNextStateChange = false;

			// We are looking for next/prev clicks in the lightbox, so make sure we know which was done
			if( _.isUndefined( state.data.direction ) ){
				return;
			}

			// If the image ID we are loading is already on the page, we didn't switch to a different page yet
			if( $('[data-role="tableRows"] div[data-imageId="' + state.data.imageID + '"]' ).length ){
				return;
			}

			// If we are moving next and are on the last image in the listing, we need to paginate forward
			if( state.data.direction == 'next' ){
				$('#cLightbox').attr('data-originalUrl', $('[data-role="tablePagination"]').find('.ipsPagination_next:not(.ipsPagination_inactive) a').first().attr('href') );
				$('[data-role="tablePagination"]').find('.ipsPagination_next:not(.ipsPagination_inactive) a').first().click();
			}

			// If we are moving backwards and are on the first image in the listing, we need to paginate backward
			if( state.data.direction == 'prev' ){
				$('#cLightbox').attr('data-originalUrl', $('[data-role="tablePagination"]').find('.ipsPagination_prev:not(.ipsPagination_inactive) a').first().attr('href') );
				$('[data-role="tablePagination"]').find('.ipsPagination_prev:not(.ipsPagination_inactive) a').first().click();
			}
		},

		/**
		 * Event handler for launching the lightbox
		 * 
		 * @param	e	Event
		 * @return void
		 */
		launchLightbox: function (e) {
			e.preventDefault();

			// Get the image URL and set the lightbox param
			var url	= $( e.currentTarget ).attr('href');
			var title = $( e.currentTarget ).attr('title');
			this._launch( url, title );
		},

		/**
		 * Launch a lightbox
		 * 
		 * @param	e	Event
		 * @return void
		 */
		_launch: function(url, title) {
			if( url.indexOf( '?' ) == -1 ){
				var logUrl = ips.utils.url.removeParams( [ 'lightbox', 'browse' ], url ) + '?browse=1&lightbox=1';
				url = url + '?lightbox=1';
			} else {
				var logUrl = ips.utils.url.removeParams( [ 'lightbox', 'browse' ], url ) + '&browse=1&lightbox=1';
				url = url + '&lightbox=1';
			}

			// Now draw the general lightbox (if we haven't done so already)
			if( !$('#cLightbox').length ){
				var newWidget = ips.templates.render('gallery.lightbox.wrapper', { originalUrl: window.location.href, originaltitle: document.title });
				$('body').append( newWidget );

				$('#cLightbox').css({
					zIndex: ips.ui.zIndex()
				});

				$('.cLightboxClose').on( 'click', this.closeLightbox );
			} else if( !$('#cLightbox').is(':visible') ) {
				$('#cLightbox').show();
			}

			if( ips.utils.responsive.currentIs('phone') ){
				$( window ).scrollTop(0);
			}

			History.pushState( { controller: 'gallery.front.browse.imageLightbox', initialLaunch: true, lightbox: true, realUrl: logUrl }, title, ips.utils.url.removeParams( [ 'lightbox', 'browse' ], url ) );

			// And then load the page into the lightbox
			ips.getAjax()( url, {
				type: 'get',
				showLoading: true
			})
			.done( function(response) {
			   $('#cLightbox > .cLightboxBack').html( response );
			   $( document ).trigger('contentChange', [ $('#cLightbox') ] );
			})
			.fail( function () {
				window.location = url;
			});

			$('body').addClass('ipsNoScroll');
		},

		/**
		 * Handles the keyDown event for navigating photos
		 *
		 * @returns {void}
		 */
		keyDown: function (e) {
			// Ignore the keypress if we're in a form element
			if( $( e.target ).closest('input, textarea, .ipsComposeArea, .ipsComposeArea_editor').length ){
				return;
			}

			switch( e.keyCode ){
				case ips.ui.key.ESCAPE:
					this.closeLightbox();
				break;
			}
		},

		/**
		 * Close the lightbox
		 *
		 * @returns {void}
		 */
		 closeLightbox: function( e ) {
		 	// Hide the lightbox
			$('#cLightbox').fadeOut( 400, function(){ 
				// Empty the lightbox
				$('#cLightbox > .cLightboxBack').html('');
			});

			$('body').removeClass('ipsNoScroll');

			// Store a history entry
			History.pushState( { controller: 'gallery.front.browse.imageLightbox', bypassStateAdjustment: true }, $('#cLightbox').attr('data-originalTitle'), $('#cLightbox').attr('data-originalUrl') );
		 }
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/browse" javascript_name="ips.browse.list.js" javascript_type="controller" javascript_version="103009" javascript_position="1000200">/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.browse.list.js - Gallery browse list controller
 *
 * Author: Rikki Tissier
 */
;( function($, _, undefined){
	&quot;use strict&quot;;

	ips.controller.register('gallery.front.browse.list', {

		initialize: function () {
			this.on( 'change', '[data-role=&quot;moderation&quot;]', this.selectImage );
			this.on( 'tableRowsUpdated', this.rowsUpdated );
		},

		/**
		 * Refreshes the patchwork when table rows are updated
		 *
		 * @returns {void}
		 */
		rowsUpdated: function () {
			var patchwork = ips.ui.photoLayout.getObj( this.scope );

			if( patchwork ){
				patchwork.refresh();
			}
		},

		/**
		 * Toggles classes when the moderation checkbox is checked
		 *
		 * @param	{event} 	e 		Event object
		 * @returns {void}
		 */
		selectImage: function (e) {
			// e.stopPropagation();
			// Can't do that or the moderator floating menu never shows up

			var row = $( e.currentTarget ).closest('.cGalleryImageItem');
			row.toggleClass( 'cGalleryImageItem_selected', $( e.currentTarget ).is(':checked') );

			//return false;
		}
	});
}(jQuery, _));</file>
 <file javascript_app="gallery" javascript_location="admin" javascript_path="controllers/settings" javascript_name="ips.settings.settings.js" javascript_type="controller" javascript_version="103009" javascript_position="1000050">/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.settings.settings.js
 *
 * Author: Brandon Farber
 */
;( function($, _, undefined){
	&quot;use strict&quot;;

	ips.controller.register('gallery.admin.settings.settings', {
		alertOpen: false,

		initialize: function () {
			if( $('input[name=rebuildWatermarkScreenshots]').val() == 0 )
			{
				this.on( 'uploadComplete', '[data-ipsUploader]', this.promptRebuildPreference );
				this.on( 'fileDeleted', this.promptRebuildPreference );
				this.on( 'change', '#gallery_watermark_images input, #form_gallery_large_dims input, #form_gallery_small_dims input, #form_gallery_use_square_thumbnails input', this.promptRebuildPreference );
			}
		},

		promptRebuildPreference: function (e) {

			if( this.alertOpen )
			{
				return;
			}

			this.alertOpen = true;

			/* Show Rebuild Prompt */
			ips.ui.alert.show({
				type: 'confirm',
				message: ips.getString('rebuildGalleryThumbnails'),
				subText: ips.getString('rebuildGalleryThumbnailsBlurb'),
				icon: 'question',
				buttons: {
					ok: ips.getString('rebuildGalleryThumbnailsYes'),
					cancel: ips.getString('rebuildGalleryThumbnailsNo')
				},
				callbacks: {
					ok: function(){
						$('input[name=rebuildWatermarkScreenshots]').val( 1 );
						this.alertOpen = false;
					},
					cancel: function(){
						$('input[name=rebuildWatermarkScreenshots]').val( 0 );
						this.alertOpen = false;
					}
				}
			});
		}

	});
}(jQuery, _));</file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/submit" javascript_name="ips.submit.chooseCategory.js" javascript_type="controller" javascript_version="103009" javascript_position="1000100"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.submit.chooseCategory.js - AJAX to show album options after selecting category
 *
 * Author: Mark Wade
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.submit.chooseCategory', {

		_chosen: false,
		_resizeTimer: null,

		initialize: function () {
			this.on( 'nodeItemSelected', '[data-name="image_category"]', this.chooseCategory );
			this.on( 'nodeSelectedChanged', '[data-name="image_category"]', this.chooseCategoryInitially );
			this.on( 'click', '[data-action="continueNoAlbum"]', this.continueNoAlbum );
			this.on( 'click', '[data-type]:not([data-disabled])', this.chooseAlbumType );

			this.setup();
		},
		
		setup: function () {
			// Set the dialog title depending on what's being shown
			if( this.scope.find('[data-role="categoryForm"]').length ){
				this.trigger('gallery.updateTitle', { title: ips.getString('chooseCategory') });
			} else {
				this.trigger('gallery.updateTitle', { title: ips.getString('chooseAlbum') });
			}
		},

		chooseAlbumType: function (e) {
			e.preventDefault();

			var target = $( e.currentTarget );

			switch( target.attr('data-type') ){
				case 'category':
					target.next('form').submit();
				break;
				case 'createAlbum':
					this.trigger('gallery.updateTitle', { title: ips.getString('createAlbum') });
					this._resizeFormDiv( this.scope.find('[data-role="createAlbumForm"]') );
				break;
				case 'existingAlbum':
					this.trigger('gallery.updateTitle', { title: ips.getString('existingAlbum') });
					this._resizeFormDiv( this.scope.find('[data-role="existingAlbumForm"]') );
				break;
			}
		},

		/**
		 * Controller destroy handler
		 *
		 * @returns {void}
		 */
		destroy: function () {
			if( this._resizeTimer ){
				clearInterval( this._resizeTimer );
			}
		},

		/**
		 * Resize the dialog to fit the form being shown inside it
		 *
		 * @param	{element} 	form 		The form being shown
		 * @returns {void}
		 */
		_resizeFormDiv: function (form) {
			this.scope.find('[data-role="chooseAlbumType"]').hide();
			var self = this;

			var resize = function (animate) {
				var height = form.innerHeight() + 130;
				var submitHeight = form.find('.cGalleryDialog_submitBar').height();
				
				if( animate ){
					self.scope.closest('.cGalleryDialog').animate({
						minHeight: ( height + submitHeight ) + 'px'
					});
				} else {
					self.scope.closest('.cGalleryDialog').css({
						minHeight: ( height + submitHeight ) + 'px'
					});
				}
			}

			form.show().css({
				opacity: 0.001
			});

			if( this.scope.closest('.ipsDialog').length ){
				resize(true);
			}

			form.animate({
				opacity: 1
			}, function () {
				if( self.scope.closest('.ipsDialog').length ){
					self._resizeTimer = setInterval( function () {
						resize(false);
					}, 500);
				}
			});
		},

		/**
		 * Responds to the initial event put out by the select tree when it selects the default value
		 *
		 * @param	{event} 	e 		Event object
		 * @param	{object} 	data 	Event data object
		 * @returns {void}
		 */
		chooseCategoryInitially: function (e, data) {
			if( this._chosen ){
				return;
			}

			if( !_.isArray( data.selectedItems ) ){
				return;
			}

			var id = data.selectedItems[0];

			if( !_.isUndefined( id ) ){
				this._chosen = true;
				this.showAlbumOptions( id );
			}
		},
		
		/**
		 * Choose Category
		 *
		 * @param	{event} 	e 	Event object
		 * @returns {void}
		 */
		chooseCategory: function (e, data) {
			if( this._chosen ){
				return;
			}
			
			this._chosen = true;
			this.showAlbumOptions(data.id);
		},

		/**
		 * Continue the wizard without doing an album
		 *
		 * @param	{event} 	e 	Event object
		 * @returns {void}
		 */
		continueNoAlbum: function (e) {
			e.preventDefault();
			$( e.currentTarget ).closest('form').submit();
		},
		
		/**
		 * Trigger Category Selection
		 *
		 * @param	{int} 	id	Selected ID
		 * @returns {void}
		 */
		showAlbumOptions: function (id) {
			var outerWrapper = this.scope.closest('.ipsDialog_content');
			var self = this;

			outerWrapper.addClass('ipsLoading');
			this.scope.hide();
			
			// Fire AJAX
			ips.getAjax()( ips.getSetting('baseURL') + 'index.php?app=gallery&module=gallery&controller=submit&noWrapper=1&category=' + id + '&album=' + this.scope.attr('data-preselected-album') )
				.done( function (response) {
					if( response ){
						self.trigger( 'gallery.submit.response', {
							response: response 
						});						
					} else {
						self.scope.find('[data-role="continueCategory"]').show();
					}
				})
				.fail(function(err){
					self.scope.find('[data-role="continueCategory"]').show();
				})
				.always( function() {
					outerWrapper.removeClass('ipsLoading');
					self.scope.show();
				});
		},
		
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/submit" javascript_name="ips.submit.existingAlbums.js" javascript_type="controller" javascript_version="103009" javascript_position="1000100"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.submit.existingAlbums.js - Allows user to select an existing gallery album
 *
 * Author: Rikki Tissier
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.submit.existingAlbums', {
		/**
		 * Initialization method
		 *
		 * @returns {void}
		 */
		initialize: function () {
			this.on( 'click', '#elGallerySubmit_albumChooser > li', this.clickAlbum );
			this._checkSelected();
		},

		/**
		 * Event handler for clicking an album entry
		 *
		 * @param	{event} 	e 	Event object
		 * @returns {void}
		 */
		clickAlbum: function (e) {
			$( e.currentTarget ).find('input[type="radio"]').prop( 'checked', true );
			this._checkSelected();
		},

		/**
		 * Checks whether any radios are selected, and enables/disables the submit button as needed
		 *
		 * @returns {void}
		 */
		_checkSelected: function () {
			if( this.scope.find('input[name="existing_album"]:checked').length ){
				this.scope.find('button[type="submit"]').prop( 'disabled', false );
			} else {
				this.scope.find('button[type="submit"]').prop( 'disabled', true );
			}
		}
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/submit" javascript_name="ips.submit.uploadImages.js" javascript_type="controller" javascript_version="103009" javascript_position="1000100"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.submit.uploadImages.js - Image upload step
 *
 * Author: Rikki Tissier
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.submit.uploadImages', {

		initialize: function () {
			// Handle clicks on 'upload' field
			var self = this;
			this.scope.find('.ipsAttachment_dropZone').on( 'click', function(e){
				// This is here to prevent the file dialog opening twice due to the click triggered below (which is inside ipsAttachment_dropZone)
				e.stopPropagation();

				if( !$( e.target ).is('input') )
				{
					self.scope.find('input[type="file"]').trigger('click');
				}
			} );

			this.on( 'fileAdded', '[data-ipsUploader]', this.filesAdded );
			this.on( 'uploadComplete', '[data-ipsUploader]', this.uploadComplete );
			this.on( 'fileDeleted', '[data-ipsUploader]', this.fileRemoved );
			this.setup();
		},

		/**
		 * Setup method
		 *
		 * @returns {void}
		 */
		setup: function () {
			// Disable the submit button if we have no files
			if( !this.scope.find('[data-role="fileList"] [data-role="file"]').length ) {
				this.scope.find('[data-role="submitForm"]').prop( 'disabled', true );
			}

			// Make sure bottom submit bar is showing
			$('.cGallerySubmit_bottomBar').removeClass('ipsHide');

			//  We want to move the allowed types of files span
			$('[data-role="allowedTypes"]').html( this.scope.find('span.ipsType_light.ipsType_small').html() );
			this.scope.find('span.ipsType_light.ipsType_small').remove();

			//  And change the uploader message
			this.scope.find('.ipsAttachment_supportDrag').html( ips.getString('uploader_add_images') );
		},

		/**
		 * Responds to event from the uploader
		 *
		 * @param	{event} 	e 		Event object
		 * @param	{object} 	data 	Data object from uploader
		 * @returns {void}
		 */
		fileRemoved: function (e, data) {
			if( data.fileElem.attr('data-fileid').indexOf('o_') != -1 )
			{
				var imageId = $('input[name="images_existing\\[' + data.fileElem.attr('data-fileid') + '\\]"').val();
			}
			else
			{
				var imageId = data.fileElem.attr('data-fileid');
			}

			// If we've already built the image form, remove it
			if( $('#image_details_' + imageId ).length )
			{
				$('#image_details_' + imageId ).remove();
			}
		},

		/**
		 * Uploader has told us all uploads are complete
		 *
		 * @param	{event} 	e 		Event object
		 * @param	{objct} 	data 	Data object from uploader
		 * @returns {void}
		 */
		uploadComplete: function (e, data) {
			if( data.success > 0 ){
				this.trigger('gallery.activateSubmitButton');
			}

			if( data.error > 0 ){
				this.trigger('gallery.uploadErrors');
			}

			if( !this.sortableInitialized ){
				// And allow images to be reordered
				this.scope.find('[data-role="fileList"] > .cGallerySubmit_fileList').sortable({
					forcePlaceholderSize: true
				});

				this.sortableInitialized = true;
			}
		},

		/**
		 * Track whether we've initialized the sortable
		 */
		sortableInitialized: false,

		/**
		 * Responds to event from the uploader
		 *
		 * @param	{event} 	e 		Event object
		 * @param	{object} 	data 	Data object from uploader
		 * @returns {void}
		 */
		filesAdded: function (e, data) {
			this.trigger('gallery.disableSubmitButton');

			$('[data-role="addFiles"]').removeClass( 'ipsHide' );
			$('[data-role="imageDetails"]').removeClass('ipsHide');

			// Only add the uploadStep class if we aren't on mobile
			if( !ips.utils.responsive.enabled() || !ips.utils.responsive.currentIs('phone') ){
				this.scope.closest('.cGalleryDialog').addClass('cGalleryDialog_uploadStep');
			}

			$( window ).trigger('resize');
		}
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/submit" javascript_name="ips.submit.wrapper.js" javascript_type="controller" javascript_version="103009" javascript_position="1000100"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.submit.main.js - Main gallery submit dialog controller
 *
 * Author: Rikki Tissier
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.submit.wrapper', {

		_expanded: false,
		_currentErrors: {},

		/**
		 * Initialize the controller
		 *
		 * @returns {void}
		 */
		initialize: function () {
			// Intercept form submissions
			this.on( 'submit', 'form', this.submitForm );

			// If another controller tells us to do something, do it
			this.on( 'gallery.submit.response', this._updateWrapper );

			// Handle uploader "clicks"
			this.on( 'click', '[data-role="addFiles"]', this.dropzoneClick );
			this.on( 'click', '[data-action="closeDialog"]', this.confirmClose );

			// Handle image details
			this.on( 'click', '[data-role="file"][data-fileid]', this.setUpImageDetailsForm );
			this.on( 'click', '[data-role="imageDescriptionUseEditor"]', this.setUpImageDescriptionRich );
			this.on( 'click', '[data-role="imageDescriptionUseTextarea"]', this.setUpImageDescriptionPlain );
			this.on( 'click', '[data-role="addCopyrightCredit"]', this.showCopyrightCredit );
			this.on( 'click', '[data-role="saveDetails"]', this.saveDetails );
			this.on( 'click', '[data-role="saveInfo"]', this.saveInfo );

			// Handle events from uploader
			this.on( 'gallery.activateSubmitButton', this.activateSubmitButton );
			this.on( 'gallery.disableSubmitButton', this.disableSubmitButton );
			this.on( 'gallery.uploadErrors', this.uploadErrors );
			this.on( 'gallery.enlargeUploader', this.enlargeUploader );
			this.on( 'gallery.updateTitle', this.updateTitle );

			this.setup();
		},

		/**
		 * Setup method
		 *
		 * @returns {void}
		 */
		setup: function () {
			// On initial load, destroy the ckeditor object as it will be recreated for each individual form
			ips.ui.editor.destruct( this.scope.find('[data-ipseditor]') );

			if( this.scope.find('.cGalleryDialog_imageForm').is(':visible') ){
				this._enlargeUploadStep();
			}
		},

		/**
		 * Event handler, allows other controllers to update the title
		 *
		 * @param 	{string} 	url 	URL to call
		 * @param 	{object} 	data	Data to pass to ajax handler
		 * @returns {void}
		 */
		updateTitle: function (e, data) {
			if( data.title ){
				this._updateTitle( data.title );
			}
		},

		/**
		 * Actually updates the title of the dialog
		 *
		 * @param 	{string} 	url 	URL to call
		 * @param 	{object} 	data	Data to pass to ajax handler
		 * @returns {void}
		 */
		_updateTitle: function (title) {
			this.scope.find('[data-role="dialogTitle"]').text( title );
		},

		/**
		 * Event handler for when another controller needs to enlarge the uploader
		 *
		 * @param 	{string} 	url 	URL to call
		 * @param 	{object} 	data	Data to pass to ajax handler
		 * @returns {void}
		 */
		enlargeUploader: function (e, data) {
			this._enlargeUploadStep( data.callback || $.noop );
		},

		/**
		 * Enable the submit button
		 *
		 * @returns {void}
		 */
		activateSubmitButton: function () {
			this.scope.find('[data-role="submitForm"]').prop( 'disabled', false );
		},

		/**
		 * Disable the submit button
		 *
		 * @returns {void}
		 */
		disableSubmitButton: function () {
			this.scope.find('[data-role="submitForm"]').prop( 'disabled', true );
		},

		/**
		 * Uploader encounted errors; show message
		 *
		 * @returns {void}
		 */
		uploadErrors: function () {
			this.scope.find('[data-role="imageErrors"]').show();
		},

		/**
		 * Handles a click on the dropzone, to trigger the Add Files dialog
		 *
		 * @returns {void}
		 */
		dropzoneClick: function() {
			this.scope.find('.ipsAttachment_dropZone').trigger('click');
		},

		/**
		 * Handles a click on the dropzone, to trigger the Add Files dialog
		 *
		 * @returns {void}
		 */
		showCopyrightCredit: function (e) {
			e.preventDefault();
			var link = $( e.currentTarget );
			link.hide().next().slideDown();
		},

		/**
		 * Closes the copyright/credit menu
		 *
		 * @returns {void}
		 */
		saveInfo: function (e) {
			e.preventDefault();
			$(e.currentTarget).trigger('closeMenu');
		},

		/**
		 * Mobile-specific functionality for 'save' button
		 *
		 * @returns {void}
		 */
		saveDetails: function (e) {
			if( e ){
				e.preventDefault();
			}

			this._markActiveImageAsSaved();

			// Show and then hide a 'saved' message
			$(e.currentTarget).next('[data-role="savedMessage"]').fadeIn();
			setTimeout( function () {
				$(e.currentTarget).next('[data-role="savedMessage"]').fadeOut();
			}, 2000 );

			// Hide any error messages
			if( e ){
				$( e.currentTarget ).closest('.cGallerySubmit_details').find('[data-errorField]').hide();
			}

			// Mobile-only behavior
			if( ips.utils.responsive.enabled() && ips.utils.responsive.currentIs('phone') ){
				// Fade out details form
				this._toggleDetailsPanelMobile(false);
				// Remove active selection styles
				this.scope.find('.cGallerySubmit_activeFile').removeClass('cGallerySubmit_activeFile');
			}
		},

		/**
		 * Confirm the user wants to close the dialog
		 *
		 * @returns {void}
		 */
		confirmClose: function (e) {
			// If there are no images uploaded, let's not bother with a confirmation
			if( !this.scope.find('[data-fileid]').length )
			{
				this.trigger('closeDialog');
				return;
			}

			if( e ){
				e.preventDefault();
			}

			var self = this;

			ips.ui.alert.show( {
				type: 'confirm',
				icon: 'question',
				message: ips.getString('confirmSubmitClose'),
				callbacks: {
					ok: function () {
						self.trigger('closeDialog');
					}
				}
			});
		},

		/**
		 * Marks the currently-active image as 'saved'
		 *
		 * @returns {void}
		 */
		_markActiveImageAsSaved: function () {
			this.scope.find('.cGallerySubmit_activeFile').removeClass('cGallerySubmit_imageError').addClass('cGallerySubmit_imageSaved');
		},

		/**
		 * Set up image details form
		 *
		 * @returns {void}
		 */
		setUpImageDetailsForm: function (e) {

			// Remove selection from all other files
			this.scope.find('.cGallerySubmit_activeFile').removeClass('cGallerySubmit_activeFile');
			$( e.currentTarget ).addClass('cGallerySubmit_activeFile');

			// Get the image ID first
			if( $( e.currentTarget ).attr('data-fileid').indexOf('o_') != -1 )
			{
				var imageId = $('input[name="images_existing\\[' + $( e.currentTarget ).attr('data-fileid') + '\\]"').val();
			}
			else
			{
				var imageId = $( e.currentTarget ).attr('data-fileid');
			}

			var imagePreview = $( e.currentTarget ).find('.ipsImage').attr('src');
			var detailsPanel = this.scope.find('[data-role="imageDetails"]');

			// Hide our existing form, if any
			detailsPanel.find('.cGallerySubmit_details, [data-role="submitHelp"]').hide();

			// If we've already built the image form, just show it
			if( $('#image_details_' + imageId ).length ){
				$('#image_details_' + imageId + ' .cGallerySubmit_details' ).show();

				if( ips.utils.responsive.currentIs('phone') ){
					this._toggleDetailsPanelMobile(true);
				}
			} else {
				// Otherwise, clone our existing form to use
				$('[data-role="defaultImageDetailsForm"]').find('#cke_image_description_DEFAULT').remove();
				var htmlToInsert = $('[data-role="defaultImageDetailsForm"]').html();
				htmlToInsert	 = '<div id="image_details_' + imageId + '">' + htmlToInsert.replace( /name="image_tags_DEFAULT"/g, 'name="image_tags_DEFAULT" data-ipsAutocomplete ').replace( /_DEFAULT/g, '_' + imageId ) + "</div>";

				// Now insert this form
				detailsPanel.find('> form').prepend( htmlToInsert );

				var imageForm = $('#image_details_' + imageId );

				// Set the image caption
				var filename = $( e.currentTarget ).find('[data-role="title"]').text();
				var filenameWithoutExt = filename.replace(/\.[^/.]+$/, '');
				$('#elInput_image_title_' + imageId ).val( filenameWithoutExt );

				// Fix yes/no fields as they will reinitialize and break
				detailsPanel.find('> form #image_details_' + imageId ).find('.ipsToggle').remove();

				if( !_.isUndefined( imagePreview ) ){
					imageForm
						.find('.cGallerySubmit_preview')
							.removeClass('ipsBox_transparent')
							.removeClass('ipsNoThumb')
							.removeClass('ipsNoThumb_video')
							.html("<img src='" + imagePreview + "' class='ipsImage' />")
							.show();

				}
				else {
					imageForm
						.find('.cGallerySubmit_preview')
							.addClass('ipsBox_transparent')
							.addClass('ipsNoThumb')
							.addClass('ipsNoThumb_video')
							.html("")
							.show();
				}

				// If this is a movie, show the thumbnail uploader
				if( !$( e.currentTarget ).attr('data-thumbnailurl') ){
					imageForm.find('.cGalleryThumbField').removeClass('ipsHide');
				} else {

					// Otherwise if it's an image, find out if we have GPS info and need to let them toggle map on/off
					ips.getAjax()( ips.getSetting('baseURL') + 'index.php?app=gallery&module=gallery&controller=submit&do=checkGps&imageId=' + imageId, {
						type: 'get',
						bypassRedirect: true
					})
						.done( function (response, status, jqXHR) {
							// Just find the internal content
							if( parseInt( response.hasGeo ) ){
								imageForm.find('.cGalleryMapField').removeClass('ipsHide');
							}
						});
				}

				// Show the details panel if we're on mobile
				if( ips.utils.responsive.currentIs('phone') ){
					this._toggleDetailsPanelMobile(true);
				}

				// And then emit contentChange event to trigger javascript controllers (e.g. tags)
				$( document ).trigger( 'contentChange', [ $('[data-role="imageDetails"] > form') ] );

				// Check for current errors
				if( !_.isUndefined( this._currentErrors[ imageId ] ) ){
					this._updateDetailsWithErrors( imageId, this._currentErrors[ imageId ] );
				}
			}
		},

		/**
		 * Toggle details panel on mobile
		 *
		 * @returns {void}
		 */
		_toggleDetailsPanelMobile: function (show) {
			var detailsPanel = this.scope.find('[data-role="imageDetails"]');

			if( !show ){
				detailsPanel
					.animate({
						opacity: 0,
						top: '400px'
					}, 400, function () {
						detailsPanel.hide()
					});
			} else if( !detailsPanel.is(':visible') ) {
				detailsPanel
					.show()
					.css({
						opacity: 0,
						top: '400px'
					})
					.animate({
						opacity: 1,
						top: '0px'
					}, 400 );
			}
		},

		/**
		 * Set up description events
		 *
		 * @returns {void}
		 */
		setUpImageDescriptionRich: function(e) {
			$( e.currentTarget ).closest('[data-role="imageDescriptionTextarea"]')
				.addClass('ipsHide')
			.prev('[data-role="imageDescriptionEditor"]')
				.removeClass('ipsHide');

			e.preventDefault();
		},

		/**
		 * Set up description events
		 *
		 * @returns {void}
		 */
		setUpImageDescriptionPlain: function (e) {
			$( e.currentTarget ).closest('[data-role="imageDescriptionEditor"]')
				.addClass('ipsHide')
			.next('[data-role="imageDescriptionTextarea"]')
				.removeClass('ipsHide');

			e.preventDefault();			
		},

		/**
		 * Event handler for submitting forms inside the wizard
		 *
		 * @param 	{event} 	e 		Event object
		 * @returns {void}
		 */
		submitForm: function (e) {
			e.preventDefault();
			
			var form = $( e.currentTarget );
			var url = form.attr('action');

			// If we are submitting images, get the info we need
			if( form.attr('id') == 'elGallerySubmit' ){
				// Sync editor to its textarea
				this.scope.find('[data-ipseditor]').each( function(){
					try {
						var editorObj = ips.ui.editor.getObj( $( this ) );
						var editorInstance = editorObj.getInstance();

						$( this ).find('textarea[data-role="contentEditor"]').val( editorInstance.getData() );
					} catch (err) {
						Debug.error("Couldn't update textarea from editor");
					}
				});

				// Get credit, copyright and auto-follow fields and add to our form
				form.find('textarea[name="credit_all"]').val( $('#elTextarea_image_credit_info').val() );
				form.find('textarea[name="copyright_all"]').val( $('#elInput_image_copyright').val() );

				if( $('#elInput_image_tags_wrapper').length ){
					try {
						var tags = ips.ui.autocomplete.getObj( this.scope.find('#elInput_image_tags') ).getTokens();
						form.find('textarea[name="tags_all"]').val( tags.join("\n") );
						form.find('textarea[name="prefix_all"]').val( $('[name="image_tags_prefix"]').val() );
					} catch (err) {
						Debug.error("Couldn't update tags");
					}
				}

				form.find('input[name="images_autofollow_all"]').val( $('#check_image_auto_follow_wrapper').hasClass('ipsToggle_on') ? 1 : 0 );

				// Get the order of the images and store this in the submission
				var imageOrder = [];

				form.find('[data-role="file"]').each( function(){
					if( $(this).attr('data-fileid').indexOf('o_') != -1 )
					{
						imageOrder.push( $('input[name="images_existing\\[' + $(this).attr('data-fileid') + '\\]"').val() );
					}
					else
					{
						imageOrder.push( $(this).attr('data-fileid') );
					}
				});

				form.find('textarea[name="images_order"]').val( JSON.stringify( imageOrder ) );

				// And get the individual image details and store in the submission
				form.find('textarea[name="images_info"]').val( JSON.stringify( $('#form_imageDetails').serializeArray() ) );

				// Hide/disable some stuff
				this.scope.find('[data-role="imageDetails"]').addClass('ipsHide');
				this.scope.find('#elGallerySubmit_toolBar').hide();
				this.scope.find('[data-role="submitForm"]').prop('disabled', true);
				this.scope.find('.cGalleryDialog').removeClass('cGalleryDialog_uploadStep');
			}

			// And don't bubble up
			e.stopPropagation();

			this._changeContents( url, form.serialize() );
		},

		/**
		 * Updates the wizard contents from a URL response
		 *
		 * @param 	{string} 	url 	URL to call
		 * @param 	{object} 	data	Data to pass to ajax handler
		 * @returns {void}
		 */
		_changeContents: function (url, data) {
			if( _.isUndefined( data ) ){
				data = {};
			}

			var self = this;
			var loadingElement = this.scope.closest('.ipsDialog_content');

			this.scope.find('.cGalleryDialog_container, .cGalleryDialog_imageForm').hide();

			this.cleanContents();
			loadingElement.addClass('ipsLoading');

			ips.getAjax()( url + '&noWrapper=1', {
				data: data,
				type: 'post',
				bypassRedirect: true
			})
				.done( function (response, status, jqXHR) {
					// Just find the internal content
					self._updateContents( response );
				})
				.always( function() {
					loadingElement.removeClass('ipsLoading');
				});
		},

		/**
		 * Event listener for passing through AJAX responses
		 *
		 * @param	{object}	response	AJAX response object
		 * @returns {void}
		 */
		 _updateWrapper: function( e, data ) {
		 	this._updateContents( data.response );
		 },

		/**
		 * Updates the wizard contents
		 *
		 * @param 	{object}	response	Response object
		 * @returns {void}
		 */		
		_updateContents: function ( response ) {
			var wrapper = $('[data-role="submitWrapper"]');
			var container = wrapper.find('[data-role="container"]');

			if( response.container ) {
				container.html( response.container );
				container.show();
			} else {
				container.hide();
			}

			if( response.containerInfo ) {
				wrapper.find('[data-role="containerInfo"]').html( response.containerInfo );
			}

			if( response.images ) {
				this._updateTitle( ips.getString('addImages') );
				// Animate the dialog expanding for the upload step
				if( this.scope.closest('.cGalleryDialog_outer').length && !this.scope.find('[data-role="imagesForm"]').is(':visible') && !this._expanded ){
					this._enlargeUploadStep( function () {
						wrapper.find('[data-role="imageForm"]').html( response.images );
					});
				} else {
					wrapper.find('[data-role="images"]').show();
					wrapper.find('[data-role="imageForm"]').html( response.images );
				}
			}

			if( response.imageTags && wrapper.find('.cGalleryTagsField').hasClass('ipsHide') ){
				wrapper.find('.cGalleryTagsField').removeClass('ipsHide');
				wrapper.find('.cGalleryTagsField .ipsFieldRow_content').append( response.imageTags );
			}

			if( response.tagsField && wrapper.find('.cGalleryTagsButton').hasClass('ipsHide') )	{
				wrapper.find('.cGalleryTagsButton').removeClass('ipsHide');
				wrapper.find('[data-role="globalTagsField"]').append( response.tagsField );
			}

			$( document ).trigger( 'contentChange', [ wrapper ] );

			if( !_.isUndefined( response.imageErrors ) && _.size( response.imageErrors ) > 0 ){
				this._handleUploaderErrors( response.imageErrors );
			}
		},

		/**
		 * Handles a submission error
		 *
		 * @param 	{object}	errors		Object containing errors
		 * @returns {void}
		 */
		_handleUploaderErrors: function (errors) {
			var self = this;
			this.scope.find('[data-role="imageDetails"]').removeClass('ipsHide');
			this.scope.find('#elGallerySubmit_toolBar').show();
			this.scope.find('[data-role="submitForm"]').prop('disabled', false);
			this.scope.find('.cGalleryDialog').addClass('cGalleryDialog_uploadStep');

			this._currentErrors = errors;

			var errorCount = _.size( errors );
			var errorIDs = _.keys( errors );
			var errorFileIDs = _.map( errorIDs, function (id) {
				if( self.scope.find('input[type="hidden"][value="' + id + '"]').attr('name') )
				{
					return '#' + self.scope.find('input[type="hidden"][value="' + id + '"]').attr('name').replace(/images_existing\[/g, '').replace(/\]/g, '');
				}
			});
			var errorFileThumbs = this.scope.find( errorFileIDs.join(',') );

			// Mark all thumbs as 'done'
			this.scope.find('.cGallerySubmit_fileList [data-role="file"]').addClass('cGallerySubmit_imageSaved');

			// Add error class to all the errored ones
			errorFileThumbs.addClass('cGallerySubmit_imageError').removeClass('cGallerySubmit_imageSaved');

			_.each( errorIDs, function (id) {
				if( self.scope.find('#image_details_' + id).length ){
					self._updateDetailsWithErrors( id, errors[ id ] );
				}
			});

			// Show an error
			if( !_.isUndefined( errors[0] ) && !_.isUndefined( errors[0]['images'] ) )
			{
				ips.ui.alert.show( {
					type: 'alert',
					icon: 'warn',
					message:  errors[0]['images'],
				});
			}
			else
			{
				ips.ui.alert.show( {
					type: 'alert',
					icon: 'warn',
					message:  ips.pluralize( ips.getString('imageUploadErrors'), errorCount ),
					subText: ips.pluralize( ips.getString('imageUploadErrorsDesc'), errorCount )
				});
			}
		},

		/**
		 * Updates the details panel for a file with the provided errors
		 *
		 * @param 	{object}	errors		Object containing errors
		 * @returns {void}
		 */
		_updateDetailsWithErrors: function (fileID, errors) {
			var panel = this.scope.find('#image_details_' + fileID);

			_.each( errors, function (error, field) {
				panel.find('[data-errorField="' + field + '"]').text( error ).show();

				if( field == 'image_tags' || field == 'image_credit' || field == 'image_copyright' ){
					panel.find('[data-errorField="' + field + '"]').closest('.ipsFieldRow').find('[data-role="addCopyrightCredit"]').click();
				}
			});
		},

		/**
		 * Expands the dialog for the upload step
		 *
		 * @param 	{object}	response	Response object
		 * @returns {void}
		 */	
		_enlargeUploadStep: function (callback) {
			var wrapper = $('[data-role="submitWrapper"]');
			var dialogElem = this.scope.closest('.cGalleryDialog_outer > div');
			var dialogElemPos = ips.utils.position.getElemPosition( dialogElem );
			var viewportSize = { width: $( window ).width(), height: $( window ).height() };
			var left = ( viewportSize.width - dialogElem.width() ) / 2;

			// Set the size of the dialog div, and then animate expanding to fullscreen size
			this.scope.closest('.cGalleryDialog_outer > div').css({
				width: 'auto',
				maxWidth: '100%',
				position: 'fixed',
				margin: 0,
				top: dialogElemPos.absPos.top + 'px',
				left: left + 'px',
				right: viewportSize.width - ( left + dialogElem.width() ) + 'px'
			}).animate({
				left: '10px',
				right: '10px',
				bottom: '10px',
				top: '10px',
			}, function () {

				// Now fade in the wrapper
				wrapper.find('[data-role="images"]').css({
					opacity: 0.0001,
				}).show();

				if( callback ){
					callback();
				}

				wrapper.find('[data-role="images"]').animate({
					opacity: 1
				});

				$( document ).trigger( 'contentChange', [ wrapper ] );
			});

			// Positioning needed for upload step
			this.scope.find('.cGalleryDialog').css({ minHeight: 0, position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 });
			this.scope.closest('.ipsDialog_content').css({ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 });

			if( !wrapper.find('.cGallerySubmit_bottomBar').hasClass('ipsHide') ){
				wrapper.find('.cGallerySubmit_bottomBar').removeClass('ipsHide').fadeIn();
			}

			this._expanded = true;
		}
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/view" javascript_name="ips.view.image.js" javascript_type="controller" javascript_version="103009" javascript_position="1000150"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.view.image.js - Image controller
 *
 * Author: Rikki Tissier
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.view.image', {

		_sizeBuffer: 20,
		_ajaxObj: null,
		_scrolling: false,
		_rtl: false,
		_curWidth: 0,
		_curHeight: 0,
		_windowDims: null,
		_inLightbox: false,
		_preloadAjax: { next: null, prev: null },

		initialize: function () {
			this.on( 'click', '[data-action="nextImage"]', this.nextImage );
			this.on( 'click', '[data-action="prevImage"]', this.prevImage );
			this.on( 'menuOpened', this.menuOpened );
			this.on( 'menuClosed', this.menuClosed );
			this.on( document, 'keydown', this.keyDown );
			this.on( window, 'resize', _.debounce( _.bind( this.windowResize, this ), 250 ) );

			this.on( 'click', '[data-role="toggleFullscreen"]', this.toggleFullscreen );

			// AJAX it up in HERE
			this.on( 'click', '[data-action="setAsCover"]', this.setAsCover );
			this.on( 'click', '[data-action="setAsProfile"]', this.setAsProfile );
			this.on( 'click', '[data-action="rotateImage"]', this.rotateImage );

			// Primary event that watches for URL changes
			History.Adapter.bind( window, 'statechange', _.bind( this.stateChange, this ) );

			this.setup();
		},

		/**
		 * Setup method
		 *
		 * @returns {void}
		 */
		setup: function () {
			if( $('html').attr('dir') == 'rtl' ){
				this._rtl = true;
			}

			this._windowDims = {
				width: $( window ).width(),
				height: $( window ).height()
			};

			this._setUpSizing(false);
			this._setUpLightboxEvents();

			if( this.scope.closest('#cLightbox').length ){
				this._inLightbox = true;
			}

			this._checkForPreload();

			// Add swipe left/right support
			delete Hammer.defaults.cssProps.userSelect; // see https://github.com/hammerjs/hammer.js/issues/81
			var mc = new Hammer( this.scope.find('.elGalleryHeader').get(0) ); // Can't touch this
			mc.on( 'swipeleft', function( e ) {
				$( document ).trigger('lightboxNextImage');
			} );
			mc.on( 'swiperight', function( e ) {
				$( document ).trigger('lightboxPrevImage');
			} );

			// Toggle document scrolling when dialogs open/close
			var self = this;
			$(document).on( 'hideDialog', function() {
				if( self.scope.closest('#cLightbox').length )
				{
					$('body').addClass('ipsNoScroll');
				}
			});

			$(document).on( 'openDialog', function() {
				if( self.scope.closest('#cLightbox').length )
				{
					$('body').removeClass('ipsNoScroll');
				}
			});
		},

		/**
		 * Adds a classname to wrapper when a menu opens
		 *
		 * @returns {void}
		 */
		menuOpened: function () {
			this.scope.find('.elGalleryImage').addClass('cGalleryImageHover');
		},

		/**
		 * Removes classname to wrapper when a menu opens
		 *
		 * @returns {void}
		 */
		menuClosed: function (e) {
			this.scope.find('.elGalleryImage').removeClass('cGalleryImageHover');
		},

		/**
		 * Handles URL state changes
		 *
		 * @returns {void}
		 */
		 _setUpLightboxEvents: function() {
		 	var self = this;

		 	// When a lightbox image is shown, see if there are any next/previous and emit the appropriate events
		 	$( document ).on( 'lightboxImageShown', function(){
		 		// See if we have a previous image
		 		if( self.scope.find('[data-action="prevImage"]').length ) {
		 			$( document ).trigger('lightboxEnable_prev');
		 		} else {
		 			$( document ).trigger('lightboxDisable_prev');
		 		}

		 		// See if we have a next image
		 		if( self.scope.find('[data-action="nextImage"]').length ) {
		 			$( document ).trigger('lightboxEnable_next');
		 		} else {
		 			$( document ).trigger('lightboxDisable_next');
		 		}
		 	});

		 	// When you click next image in the lightbox, trigger our normal next image routine which will later emit an event to update the lightbox
		 	$( document ).on( 'lightboxNextImage', function(e, data){
		 		self.scope.find('[data-action="nextImage"]').click();
		 	});

		 	$( document ).on( 'lightboxPrevImage', function(e, data){
		 		self.scope.find('[data-action="prevImage"]').click();
		 	});
		 },

		/**
		 * Handles URL state changes
		 *
		 * @returns {void}
		 */
		stateChange: function () {
			var state = History.getState();

			if( state.data.controller != 'gallery.front.view.image' ){
				if( state.data.controller != 'gallery.front.browse.imageLightbox' && ( _.isUndefined( state.data.initialLaunch ) || !state.data.initialLaunch ) ){
					return;
				}
			}

			// Only handle this if the event comes from the lightbox and we're inside the lightbox (or vice versa)
			if( _.isUndefined( state.data.lightbox ) || state.data.lightbox !== this._inLightbox ){
				return;
			}

			// Track page view
			ips.utils.analytics.trackPageView( state.data.realUrl );
			
			// Scroll to the image but only if we're not in the lightbox
			if( !$('#cLightbox').length || !$('#cLightbox').is(':visible') ) {
				this._scrollPage();
			}

			this._loadURL( state.data.realUrl, false );
		},

		/**
		 * Scrolls the page to the image
		 *
		 * @returns {void}
		 */
		_scrollPage: function () {
			if( this._scrolling ){
				return;
			}

			var self = this;

			// Get top postition of table
			var elemPosition = ips.utils.position.getElemPosition( this.scope );
			var viewportHeight = $( window ).height();
			var docScrollTop = $( document ).scrollTop();

			// If it isn't on screen, scroll to it
			if( ( elemPosition.absPos.top - docScrollTop < 0 ) || elemPosition.absPos.top > viewportHeight + docScrollTop ){
				this._scrolling = true;

				$('html, body').animate( { scrollTop: elemPosition.absPos.top + 'px' }, function () {
					self._scrolling = false;
				} );	
			}			
		},
		
		/**
		 * Handles the keyDown event for navigating photos
		 *
		 * @returns {void}
		 */
		keyDown: function (e) {

			// Ignore the keypress if we're in a form element
			if( $( e.target ).closest('input, textarea, .ipsComposeArea, .ipsComposeArea_editor').length ){
				return;
			}

			switch( e.keyCode ){
				case ips.ui.key.LEFT:
					this.scope.find('[data-action="prevImage"]').click();
				break;
				case ips.ui.key.RIGHT:
					this.scope.find('[data-action="nextImage"]').click();
				break;
			}
		},

		/**
		 * Navigates the page to the next image
		 *
		 * @param 	{event}		e 	Event object
		 * @returns {void}
		 */
		nextImage: function (e) {
			e.preventDefault();

			var url = $( e.currentTarget ).attr('href');
			var id = $( e.currentTarget ).attr('data-imageID');
			var title = $( e.currentTarget ).attr('title');

			History.pushState( {
				controller: 'gallery.front.view.image',
				imageID: id,
				realUrl: url,
				direction: 'next',
				lightbox: this._inLightbox
			}, title, ips.utils.url.removeParams( [ 'lightbox', 'browse' ], url ) );
		},

		/**
		 * Navigates the page to the previous image
		 *
		 * @param 	{event}		e 	Event object
		 * @returns {void}
		 */
		prevImage: function (e) {
			e.preventDefault();

			var url = $( e.currentTarget ).attr('href');
			var id = $( e.currentTarget ).attr('data-imageID');
			var title = $( e.currentTarget ).attr('title');

			History.pushState( {
				controller: 'gallery.front.view.image',
				imageID: id,
				realUrl: url,
				direction: 'prev',
				lightbox: this._inLightbox
			}, title, ips.utils.url.removeParams( [ 'lightbox', 'browse' ], url ) );
		},

		/**
		 * Sets the current image as the user's profile picture
		 *
		 * @param 	{event}		e 	Event object
		 * @returns {void}
		 */
		setAsProfile: function (e) {
			e.preventDefault();

			var url = $( e.currentTarget ).attr('href');

			ips.ui.alert.show( {
				type: 'confirm',
				icon: 'question',
				message: ips.getString('set_as_photo_confirm'),
				callbacks: {
					ok: function () {
						ips.getAjax()( url, {
							showLoading: true
						} )
							.done( function (response) {
								ips.ui.flashMsg.show( response.message );
							})
							.fail( function () {
								window.location = url;
							});
					}
				}
			});
		},

		/**
		 * Sets the image as a cover photo
		 *
		 * @param	{event} 	e 		Event object
		 * @returns {void}
		 */
		setAsCover: function (e) {
			e.preventDefault();

			var url = $( e.currentTarget ).attr('href');

			ips.getAjax()( url, {
				showLoading: true
			} )
				.done( function (response) {
					ips.ui.flashMsg.show( response.message );
				})
				.fail( function () {
					window.location = url;
				});
		},

		/**
		 * Rotates the image
		 *
		 * @param	{event} 	e 		Event object
		 * @returns {void}
		 */
		rotateImage: function (e) {
			e.preventDefault();

			var url = $( e.currentTarget ).attr('href');
			var self = this;

			ips.getAjax()( url, {
				showLoading: true
			} )
				.done( function (response) {
					self.scope.find('[data-role="theImage"]')[0].src = response.src;
					self.scope.find('[data-role="theImage"]').css( { 'width': response.width + 'px', 'height': response.height + 'px' } );
					self.scope.find('[data-role="theImage"]').closest('.cGalleryViewImage').css( { 'width': response.width + 'px', 'height': response.height + 'px' } );
					ips.ui.flashMsg.show( response.message );
				})
				.fail( function () {
					window.location = url;
				});
		},

		/**
		 * Event handler for window resizing
		 *
		 * @returns {void}
		 */
		windowResize: function (e) {
			if( $( window ).width() !== this._windowDims.width || $( window ).height() !== this._windowDims.height ){
				this._setUpSizing(true);

				this._windowDims = {
					width: $( window ).width(),
					height: $( window ).height()
				};
			}
		},

		_cachedUrls: {},

		/**
		 * Loads the specified URL to fetch a new image
		 *
		 * @param 	{string}	url 	URL of new image to fetch
		 * @returns {void}
		 */
		_loadURL: function (url, cacheOnly, direction) {
			var self = this;

			// If we've cached this URL already, bail now
			if( cacheOnly && !_.isUndefined( this._cachedUrls[ url ] ) ){
				return;
			}

			if( !cacheOnly ){
				this.cleanContents();
				this._setImageLoading();
			}

			// If this is a regular load, set to loading
			if( _.isUndefined( cacheOnly ) || !cacheOnly ){
				if( this._ajaxObj && _.isFunction( this._ajaxObj.abort ) ){
					this._ajaxObj.abort();
				}
			} else {
				if( this._preloadAjax[ direction ] && _.isFunction( this._preloadAjax[ direction ].abort ) ){
					this._preloadAjax[ direction ].abort();
				}
			}

			// Already have cached data? Just show it.
			if( self._cachedUrls[ url ] ){
				self._updateImage( self._cachedUrls[ url ] );
				return;
			}

			var ajax = ips.getAjax();

			if( cacheOnly ){
				this._preloadAjax[ direction ] = ajax;
			} else {
				this._ajaxObj = ajax;
			}

			ajax( url, {
				dataType: 'json'
			} )
				.done( function (response) {
					// Cache for next time
					self._cachedUrls[ url ] = response;

					if( !cacheOnly ){
						self._updateImage( response );
					}
				})
				.fail( function( jqXHR, textStatus, errorThrown ) {
					if( Debug.isEnabled() ){
						Debug.error( errorThrown );
					} else {
						if( !cacheOnly ){
							window.location = url;
						}
					}
				});
		},

		/**
		 * Handles a response from the server with new image info
		 *
		 * @param 	{object}	response 	Server response json
		 * @returns {void}
		 */
		_updateImage: function (response) {
			this.scope.find('[data-role="imageInfo"]')
				.closest('.cGalleryLightbox_info')
					.show()
				.end()
					.html( response.info );
			this.scope.find('[data-role="imageFrame"]').replaceWith( response.image );
			
			if( response.comments ){
				this.scope.find('[data-role="imageComments"]').html( response.comments );	
			} else {
				this.scope.find('[data-role="imageComments"]').html( '' );
			}

			// Update breadcrumb
			$('nav.ipsBreadcrumb [data-role="breadcrumbList"] > li:last-child').html( response.title );

			// Reinit each area
			$( document ).trigger( 'contentChange', [ this.scope ] );

			// Trigger event for lightbox handler
			$( document )
				.trigger( 'imageUpdated', [ {
					closeLightbox: ( response.image.match( /<video /ig ) || response.image.match( /<embed /ig ) ) ? true : false,
					updateImage: {
						imageElem: null,
						largeImage: ( response.image.match( /<video /ig ) || response.image.match( /<embed /ig ) ) ? null : this.scope.find('[data-role="theImage"]')[0].src,
						commentsURL: null,
						meta: null
					}
				} ] );

	 		// See if we have a previous image
	 		if( this.scope.find('[data-action="prevImage"]').length ){
	 			$( document ).trigger('lightboxEnable_prev');
	 		} else {
	 			$( document ).trigger('lightboxDisable_prev');
	 		}

	 		// See if we have a next image
	 		if( this.scope.find('[data-action="nextImage"]').length ) {
	 			$( document ).trigger('lightboxEnable_next');
	 		} else {
	 			$( document ).trigger('lightboxDisable_next');
	 		}

	 		this._checkForPreload()
			this._setUpSizing(false);
		},

		/**
		 * Checks the current image to see if we can preload a prev/next image
		 *
		 * @param 	{boolean}		loading 	Are we loading?
		 * @returns {void}
		 */
		_checkForPreload: function () {
			// See if we can cache the next/prev image
	 		if( this.scope.find('[data-action="nextImage"]').length ){
	 			Debug.log("Caching next image");
	 			this._loadURL( this.scope.find('[data-action="nextImage"]').attr('href'), true, 'next' );
	 		}
	 		if( this.scope.find('[data-action="prevImage"]').length ){
	 			Debug.log("Caching prev image");
	 			this._loadURL( this.scope.find('[data-action="prevImage"]').attr('href'), true, 'prev' );
	 		}
		},

		/**
		 * Sets various page elements to a loading state while new data is loaded
		 *
		 * @param 	{boolean}		loading 	Are we loading?
		 * @returns {void}
		 */
		_setImageLoading: function (loading) {
			var description = this.scope.find('[data-role="imageDescription"]');
			var stats = this.scope.find('[data-role="imageStats"]');
			var image = this.scope.find('[data-role="imageFrame"]');

			description
				.css({
					height: description.outerHeight() + 'px'
				})
				.html('')
				.addClass('ipsLoading');

			stats
				.css({
					height: stats.outerHeight() + 'px'
				})
				.html('')
				.addClass('ipsLoading');

			image
				/*.css({
					height: image.outerHeight() + 'px'
				})*/
				.html('')
				.addClass('ipsLoading');

			this.scope.find('[data-role="imageInfo"]').closest('.cGalleryLightbox_info').hide();

			// Trigger event for lightbox handler
			$( document ).trigger( 'imageLoading', [] );
		},

		/**
		 * Determine whether we're viewing on mobile or desktop
		 *
		 * @param 	{boolean}	forceResize 	Images smaller than previous won't shrink lightbox; setting this to true overrides that behavior
		 * @returns {void}
		 */		
		_setUpSizing: function (forceResize) {
			if( ips.utils.responsive.currentIs('phone') ){
				this._setUpSizingMobile();
			} else {
				this._setUpSizingDesktop(forceResize);
			}
		},

		/**
		 * Handles resizing image elements for mobile view
		 *
		 * @returns {void}
		 */		
		_setUpSizingMobile: function (forceResize) {
			var isLightbox = this.scope.is('[data-role="lightbox"]');
			var frame = this.scope.find('[data-role="imageFrame"]');
			var frameHeight = $( window ).height() - 80;
			var imageData = frame.attr('data-imageSizes');

			if( isLightbox ){
				$( window ).scrollTop(0);
				frame.css({ height: frameHeight + 'px' });

				var maxHeight = frameHeight;
				var maxWidth = $( window ).width();
			} else {
				var maxHeight = frameHeight;
				var maxWidth = frame.width();
			}

			var ratio = 1;

			if( imageData ){
				imageData = $.parseJSON( imageData );
				ratio = imageData['large'][ 0 ] / imageData['large'][ 1 ];

				var marginTop = 0;
				var imageSize = {
					width: imageData['large'][0],
					height: imageData['large'][1]
				};

				if( imageSize['width'] > maxWidth ){
					imageSize['width'] = maxWidth;
					imageSize['height'] = Math.round( imageSize['width'] / ratio );
				}

				if( imageSize['height'] > maxHeight ){
					imageSize['height'] = maxHeight;
					imageSize['width'] = Math.round( imageSize['height'] * ratio );
				}

				this.scope
					.find('[data-role="notesWrapper"], [data-role="theImage"]')
					.css({
						width: imageSize['width'] + 'px',
						height: imageSize['height'] + 'px',
					})
					.show();
			}
		},

		/**
		 * Handles sizing elements as required
		 *
		 * @param 	{boolean}	forceResize 	Images smaller than previous won't shrink lightbox; setting this to true overrides that behavior
		 * @returns {void}
		 */
		_setUpSizingDesktop: function (forceResize) {
			var isLightbox = this.scope.is('[data-role="lightbox"]');
			var frame = this.scope.find('[data-role="imageFrame"]');
			var imageSizer = this.scope.find('[data-role="imageSizer"]');
			var infoPanel = this.scope.find('[data-role="imageInfo"]');
			var infoPanelWidth = infoPanel.width();
			var imageData = frame.attr('data-imageSizes');

			if( isLightbox ){
				var maxHeight = $( window ).height() - (this._sizeBuffer * 2);
				var maxWidth = $( window ).width() - (this._sizeBuffer * 2) - infoPanelWidth;

				frame.css({
					height: 'auto'
				});
			} else {
				var maxHeight = frame.height();
				var maxWidth = frame.width();
			}
			
			var ratio = 1;		

			if( maxHeight < 400 ){
				maxHeight = 400;
			}
console.dir(imageData);
			if( imageData ){
				imageData = $.parseJSON( imageData );
				ratio = imageData['large'][ 0 ] / imageData['large'][ 1 ];

				var marginTop = 0;
				var imageSize = {
					width: imageData['large'][0],
					height: imageData['large'][1]
				};
console.dir(imageSize);
				if( imageSize['width'] > maxWidth ){
					imageSize['width'] = maxWidth;
					imageSize['height'] = Math.round( imageSize['width'] / ratio );
				}

				if( imageSize['height'] > maxHeight ){
					imageSize['height'] = maxHeight;
					imageSize['width'] = Math.round( imageSize['height'] * ratio );
				}

				this.scope
					.find('[data-role="notesWrapper"], [data-role="theImage"]')
					.css({
						width: imageSize['width'] + 'px',
						height: imageSize['height'] + 'px',
					})
					.show();

				
				// ========
				// This code handled resizing the image frame to fit the photo.
				// However, in testing it's easier to use the lightbox when it is full-size, due to the number of UI controls
				// we display in the sidebar. Commenting this block out for now, but leaving for reference.
				// ========
				// Now size the container if needed. If this image is smaller than the last one, we DON'T shrink the lightbox
				// However if the image is larger than the previous, we do enlarge it.
				// We also make sure the panel is never smaller than 500x500.
				/*var minimumAllowedWidth = 500 + infoPanelWidth;
				if( forceResize || ( imageSize['width'] > this._curWidth && ( imageSize['width'] + infoPanelWidth ) >= minimumAllowedWidth ) ){
					imageSizer.css({ width: imageSize['width'] + infoPanelWidth + 'px' });
					this._curWidth = imageSize['width'];
				} else if ( ( imageSize['width'] + infoPanelWidth ) < minimumAllowedWidth && !this._curWidth ){
					imageSizer.css({ width: minimumAllowedWidth + 'px' });
					this._curWidth = 500;
				}

				// Height is simpler because we don't have to account for the info panel width here.
				if( forceResize || ( imageSize['height'] > this._curHeight && imageSize['height'] >= 500 ) ){
					imageSizer.css({ height: imageSize['height'] + 'px' });
					this._curHeight = imageSize['height'];
				} else if ( imageSize['height'] < 500 && !this._curHeight ){
					imageSizer.css({ height: '500px' });
					this._curHeight = 500;
				}*/
			}
		},

		/**
		 * Toggle viewing full image or viewing fancy lightbox
		 *
		 * @returns {void}
		 */
		 toggleFullscreen: function( e ) {
		 	e.preventDefault();
		 	
		 	if( $('#cLightbox').is('[data-fullScreen]' ) )
		 	{
		 		$('#cLightbox').removeAttr('data-fullScreen');
		 	}
		 	else
		 	{
		 		$('#cLightbox').attr( 'data-fullScreen', "true" );
		 	}
		 }
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/view" javascript_name="ips.view.note.js" javascript_type="controller" javascript_version="103009" javascript_position="1000150"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.view.note.js - Note controller
 *
 * Author: Rikki Tissier
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.view.note', {

		_editing: false,
		_editable: false,
		_draggingNotEditing: false,
		_hoverTimerOn: null,
		_hoverTimerOff: null,
		_note: '',

		initialize: function () {
			this.on( 'click', '.cGalleryNote_border', this.startEditing );
			this.on( 'click', '[data-action="save"]', this.saveNote );
			this.on( 'click', '[data-action="cancel"]', this.cancelNote );
			this.on( 'click', '[data-action="delete"]', this.deleteNote );
			this.on( 'mousedown', '.cGalleryNote_note', this.mouseDown );
			this.on( 'mouseenter', this.mouseEnter );
			this.on( 'mouseleave', this.mouseLeave );
			this.setup();
		},

		/**
		 * Setup method, builds the note, makes it editable and positions it
		 *
		 * @returns {void}
		 */
		setup: function () {
			var self = this;

			if( !_.isUndefined( this.scope.attr('data-editable') ) ){
				this._editable = true;
			}

			this._note = this.scope.attr('data-note');
			this._baseURL = ips.getSetting('baseURL') + 'index.php?app=gallery&module=gallery&controller=notes&imageId=' + this.scope.closest('.cGalleryViewImage').attr('data-imageID');

			this._buildNote();
			this._setUpEditable();
			this._initialPosition();

			// If this is a new note, trigger a click on it to put it into editing mode
			if( this.scope.attr('data-noteID') == 'new' ){
				this.scope.find('.cGalleryNote_border').click();
			}
		},

		/**
		 * Event handler for saving changes to note text
		 *
		 * @param 	{event}		e 	Event object
		 * @returns {void}
		 */
		saveNote: function (e) {
			e.preventDefault();
			var self = this;
			var note = this.scope.find('.cGalleryNote_note textarea').val();
			var savePosition = false;

			this.scope.draggable('enable');

			if( !$.trim( note ) ){
				return;
			}
			
			// If this is a new note, we'll save the position too.
			if( this.scope.attr('data-noteID') == 'new' ){
				savePosition = true;
			}

			this._saveNote( note, savePosition )
				.done( function () {
					self._note = note;
					self._stopEditing();
				});
		},

		/**
		 * Event handler for cancelling changes to note text
		 *
		 * @param 	{event}		e 	Event object
		 * @returns {void}
		 */
		cancelNote: function (e) {
			// If this is a new note 'cancel' should actually delete
			if( this.scope.attr('data-noteID') == 'new' ){
				this.deleteNote( e );
				return;
			}

			e.preventDefault();
			this.scope.draggable('enable');
			this._stopEditing();
		},

		/**
		 * Event handler for deleting a note. Confirms with user, then triggers ajax request to remove this note
		 *
		 * @param 	{event}		e 	Event
		 * @returns {void}
		 */
		deleteNote: function (e) {
			e.preventDefault();
			var self = this;

			ips.ui.alert.show( {
				type: 'confirm',
				icon: 'question',
				message: ips.getString('delete_note_confirm'),
				callbacks: {
					ok: function () {
						self._doDeleteNote();
					}
				}
			});
		},

		/**
		 * Mouse enter event; shows the note text after a short delay
		 *
		 * @returns {void}
		 */
		mouseEnter: function () {
			var self = this;

			if( this._hoverTimerOn ){
				clearTimeout( this._hoverTimerOn );
			}

			if( !this._editing ){
				this._hoverTimerOn = setTimeout( function () {
					if( !self.scope.find('.cGalleryNote_note').is(':visible') ){
						ips.utils.anim.go( 'fadeIn fast', self.scope.find('.cGalleryNote_note') );	
					}				
				});
			}
		},

		/**
		 * Mouse leave event; hides the note text after a short delay
		 *
		 * @returns {void}
		 */
		mouseLeave: function () {
			var self = this;

			if( this._hoverTimerOff ){
				clearTimeout( this._hoverTimerOff );
			}

			if( !this._editing ){
				this._hoverTimerOff = setTimeout( function () {
					if( self.scope.find('.cGalleryNote_note').is(':visible') ){
						ips.utils.anim.go( 'fadeOut fast', self.scope.find('.cGalleryNote_note') );	
					}				
				});
			}
		},

		/**
		 * Event handler for mousing down on the note edit area (textarea and buttons);
		 * This is necessary because on mobile, the draggable widget interferes with the controls
		 * and makes them unclickable. Instead what we do is disable the draggable onmouseodown so that
		 * clicks are registered, and then our save/cancel handlers will renable it.
		 *
		 * @returns {void}
		 */
		mouseDown: function () {
			this.scope.draggable('disable');
		},

		/**
		 * Triggered when the user clicks on the note. Puts the note into editing state,
		 * and shows a little form to allow the text to be edited
		 *
		 * @returns {void}
		 */
		startEditing: function () {
			if( !this._editable || this._draggingNotEditing ){
				return;
			}

			this._editing = true;

			this.scope
				.addClass('cGalleryNote_editing')
				.append( ips.templates.render('gallery.notes.delete') )
				.find('.cGalleryNote_note > div')
					.html( ips.templates.render('gallery.notes.edit', {
						note: this._note
					}))
					.find('textarea')
						.focus();
		},

		/**
		 * Deletes the note
		 *
		 * @returns {void}
		 */
		_doDeleteNote: function () {
			var url = this._baseURL;
			var self = this;

			if( this.scope.attr('data-noteID') == 'new' )
			{
				ips.utils.anim.go( 'fadeOutDown', self.scope )
					.done( function () {
						self.scope.remove();
					});
				return;
			}

			ips.getAjax()( url + '&delete=1&id=' + this.scope.attr('data-noteID') )
				.done( function () {
					ips.utils.anim.go( 'fadeOutDown', self.scope )
						.done( function () {
							self.scope.remove();
						});
				})
		},

		/**
		 * Saves the note
		 *
		 * @param 	{string}		noteContent 	If provided, the updated note text to be saved
		 * @param 	{boolean} 		savePosition	If true, will update the position info for the note
		 * @returns {promise}
		 */
		_saveNote: function (noteContent, savePosition) {
			var deferred = $.Deferred();
			var self = this;
			var url = this._baseURL;
			var position = '';
			var note = '';

			if( this.scope.attr('data-noteID') == 'new' ){
				url += '&add=1';
			} else {
				url += '&edit=1&id=' + this.scope.attr('data-noteID');
			}

			if( savePosition ){
				position = this._getPosition();
			}

			if( noteContent ){
				note = noteContent;
			}

			if( this.scope.find('[data-action="save"]').length && note ){
				this.scope.find('[data-action="save"]').prop('disabled', true).text( ips.getString('saving_note') );
			}

			// Send request
			ips.getAjax()( url, {
				data: {
					note: note,
					position: position
				}
			})
				.done( function (response) {
					if( self.scope.find('[data-action="save"]').length && note ){
						self.scope.find('[data-action="save"]').prop( 'disabled', false ).text( 'Save' );
					}

					// If this was a new note and the server returned an ID, update our attribute
					if( _.isObject( response ) && response.id ){
						self.scope.attr( 'data-noteID', response.id );
					}

					deferred.resolve();
				})
				.fail( function () {
					deferred.reject();
				});

			return deferred.promise();
		},

		/**
		 * Gets the position and dims of the note, in percentage values (relative to the image) 
		 *
		 * @returns {string}  In format <left>,<top>,<width>,<height>
		 */
		_getPosition: function () {
			var position = [];
			var parent = this.scope.closest('.cGalleryViewImage');
			var notePos = this.scope.position();

			// Left
			position[0] = ( notePos['left'] / parent.width() ) * 100;
			// Top
			position[1] = ( notePos['top'] / parent.height() ) * 100;
			// Width
			position[2] = ( this.scope.width() / parent.width() ) * 100;
			// Height
			position[3] = ( this.scope.height() / parent.height() ) * 100;

			return position.join(',');
		},

		/**
		 * Takes note out of editing state
		 *
		 * @returns {void}
		 */
		_stopEditing: function () {
			this._editing = false;
			this._draggingNotEditing = false;
			this.scope
				.removeClass('cGalleryNote_editing')
				.find('.cGalleryNote_note > div')
					.text( this._note )
				.end()
				.find('.cGalleryNote_delete')
					.remove();
		},

		/**
		 * Adds the note text to the note
		 *
		 * @returns {void}
		 */
		_buildNote: function () {
			this.scope.find('.cGalleryNote_note > div').text( this._note );
		},

		/**
		 * When the note is editable, loads jQuery UI and sets up resizable/draggable
		 *
		 * @returns {void}
		 */
		_setUpEditable: function () {
			if( !this._editable ){
				return;
			}

			var self = this;

			ips.loader.get( ['core/interface/jquery/jquery-ui.js'] ).then( function () {
				self.scope.resizable({
					containment: self.scope.closest('.cGalleryViewImage'),
					handles: 'se',
					stop: self._updatePosition.bind( self )
				});

				self.scope.draggable({
					containment: self.scope.closest('.cGalleryViewImage'),
					start: self._startDragging.bind( self ),
					stop: self._updatePosition.bind( self )
				});

				// A workaround for an issue in resizable, where the container will jump because it uses percentage
				// sizing, but resizable uses absolute sizing.
				self.scope.find('.ui-resizable-handle').on('mouseover', function () {
					self.scope.closest('.cGalleryViewImage').css( {
						height: self.scope.closest('.cGalleryViewImage').height() + 'px'
					});
				});
			});
		},

		/**
		 * Event handler for start event on Draggable. If we aren't already editing, set a flag so that
		 * when we stop dragging, the click doens't incorrectly put note into editing mode
		 *
		 * @returns {void}
		 */
		_startDragging: function () {
			if( !this._editing ){
				this._draggingNotEditing = true;
			}
		},

		/**
		 * Saves the current position of the note. Called when resizable or draggable stop
		 *
		 * @returns {void}
		 */
		_updatePosition: function () {
			var self = this;

			// If this is a new note, we don't want to update the position remotely yet.
			// We'll only do that once the note text is saved for the first time.
			if( this.scope.attr('data-noteID') == 'new' ){
				return;
			}

			this._saveNote( false, true )
				.done( function () {

					// If we were editing before updating pos/dims, we don't want to run the stop method 
					// otherwise changes to the note text will be lost.
					if( !self._editing ){
						self._stopEditing();	
					}					
				});
		},

		/**
		 * Positions the note based on the attributes on the scope element
		 *
		 * @returns {void}
		 */
		_initialPosition: function () {
			var left = this.scope.attr('data-posLeft');
			var top = this.scope.attr('data-posTop');
			var width = this.scope.attr('data-dimWidth');
			var height = this.scope.attr('data-dimHeight');

			// Position the note
			this.scope.css({
				left: left + '%',
				top: top + '%',
				width: width + '%',
				height: height + '%'
			});
		}
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="controllers/view" javascript_name="ips.view.notes.js" javascript_type="controller" javascript_version="103009" javascript_position="1000150"><![CDATA[/**
 * Invision Community
 * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com
 *
 * ips.view.notes.js - Gallery notes controller
 *
 * Author: Rikki Tissier
 */
;( function($, _, undefined){
	"use strict";

	ips.controller.register('gallery.front.view.notes', {

		_inAddingState: false,

		initialize: function () {
			this.on( document, 'click', '[data-action="addNote"]', this.startAddNote );
			this.setup();
		},

		/**
		 * Setup method
		 *
		 * @returns {void}
		 */
		setup: function () {
			var notes;

			try {
				notes = $.parseJSON( this.scope.attr('data-notesData') );
			} catch (err) {}

			if( notes && notes.length ){
				this._buildNotes( notes );
			}
		},

		/**
		 * Adds a new note to the image
		 *
		 * @param 	{event}		e 	Event object
		 * @returns {void}
		 */
		startAddNote: function (e) {
			e.preventDefault();

			this.scope.append( ips.templates.render( 'gallery.notes.wrapper', {
				id: 'new',
				left: 50,
				top: 50,
				width: ( 100 / this.scope.width() ) * 100,
				height: ( 100 / this.scope.height() ) * 100,
				editable: true
			}));

			$( document ).trigger( 'contentChange', [ this.scope ] );
		},

		/**
		 * Builds any existing notes from data attached to our scope element
		 *
		 * @param 	{array}		notes 	Array of note data to build from 
		 * @returns {void}
		 */
		_buildNotes: function (notes) {
			if( notes.length ){
				for( var i = 0; i < notes.length; i++ ){
					this.scope.append( ips.templates.render( 'gallery.notes.wrapper', {
						id: notes[ i ].ID,
						left: notes[ i ].LEFT,
						top: notes[ i ].TOP,
						width: notes[ i ].WIDTH,
						height: notes[ i ].HEIGHT,
						note: notes[ i ].NOTE,
						editable: !_.isUndefined( this.scope.attr('data-editable') ) ? true : false
					}));
				}

				$( document ).trigger( 'contentChange', [ this.scope ] );
			}
		}
	});
}(jQuery, _));]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="templates" javascript_name="ips.templates.browse.js" javascript_type="template" javascript_version="103009" javascript_position="1000050"><![CDATA[ips.templates.set('gallery.patchwork.indexItem', " \
	{{#showThumb}}\
		<span class='cGalleryPatchwork_item' style='width: {{dims.width}}px; height: {{dims.height}}px; margin: {{dims.margin}}px {{dims.marginRight}}px {{dims.margin}}px {{dims.marginLeft}}px'>\
	{{/showThumb}}\
	{{^showThumb}}\
		<span class='cGalleryPatchwork_item ipsNoThumb ipsNoThumb_video' style='width: {{dims.width}}px; height: {{dims.height}}px; margin: {{dims.margin}}px {{dims.marginRight}}px {{dims.margin}}px {{dims.marginLeft}}px'>\
	{{/showThumb}}\
			<a data-imageLightbox title='{{image.caption}}' href='{{image.url}}'>\
				{{#showThumb}}<img src='{{image.src}}' alt='{{image.caption}}' class='cGalleryPatchwork_image'>{{/showThumb}}\
				<div class='ipsPhotoPanel ipsPhotoPanel_mini'>\
					<img src='{{image.author.photo}}' class='ipsUserPhoto ipsUserPhoto_mini'>\
					<div>\
						<span class='ipsType_normal ipsTruncate ipsTruncate_line'>{{#lang}}by{{/lang}} {{image.author.name}}</span>\
						<span class='ipsType_small ipsTruncate ipsTruncate_line'>{{#lang}}in{{/lang}} {{image.container}}</span>\
					</div>\
				</div>\
				{{#image.allowComments}}\
					<span class='cGalleryPatchwork_comments' data-commentCount='{{image.comments}}'><i class='fa fa-comment'></i> {{image.comments}}</span>\
				{{/image.allowComments}}\
			</a>\
		</span>\
");

ips.templates.set('gallery.patchwork.tableItem', " \
	{{#showThumb}}\
		<div data-imageID='{{image.id}}' class='cGalleryPatchwork_item' style='width: {{dims.width}}px; height: {{dims.height}}px; margin: {{dims.margin}}px {{dims.marginRight}}px {{dims.margin}}px {{dims.marginLeft}}px'>\
	{{/showThumb}}\
	{{^showThumb}}\
		<div data-imageID='{{image.id}}' class='cGalleryPatchwork_item ipsNoThumb ipsNoThumb_video' style='width: {{dims.width}}px; height: {{dims.height}}px; margin: {{dims.margin}}px {{dims.marginRight}}px {{dims.margin}}px {{dims.marginLeft}}px'>\
	{{/showThumb}}\
		<a data-imageLightbox title='{{image.caption}}' href='{{image.url}}'>\
			{{#showThumb}}<img src='{{image.src}}' alt='{{image.caption}}' class='cGalleryPatchwork_image'>{{/showThumb}}\
			<div class='ipsPhotoPanel ipsPhotoPanel_mini'>\
				<img src='{{image.author.photo}}' class='ipsUserPhoto ipsUserPhoto_mini'>\
				<div>\
					<span class='ipsType_normal ipsTruncate ipsTruncate_line'>{{image.caption}}</span>\
					<span class='ipsType_small ipsTruncate ipsTruncate_line'>{{#lang}}by{{/lang}} {{image.author.name}}</span>\
				</div>\
			</div>\
			<ul class='ipsList_inline cGalleryPatchwork_stats'>\
				{{#image.unread}}\
					<li class='ipsPos_left'>\
						<span class='ipsItemStatus ipsItemStatus_small' data-ipsTooltip title='{{image.unread}}'><i class='fa fa-circle'></i></span>\
					</li>\
				{{/image.unread}}\
				{{#image.hasState}}\
					<li class='ipsPos_left'>\
						{{#image.state.hidden}}\
							<span class='ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_warning' data-ipsTooltip title='{{#lang}}hidden{{/lang}}'><i class='fa fa-eye-slash'></i></span>\
						{{/image.state.hidden}}\
						{{#image.state.pending}}\
							<span class='ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_warning' data-ipsTooltip title='{{#lang}}pending{{/lang}}'><i class='fa fa-warning'></i></span>\
						{{/image.state.pending}}\
						{{#image.state.pinned}}\
							<span class='ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_positive' data-ipsTooltip title='{{#lang}}pinned{{/lang}}'><i class='fa fa-thumb-tack'></i></span>\
						{{/image.state.pinned}}\
						{{#image.state.featured}}\
							<span class='ipsBadge ipsBadge_icon ipsBadge_small ipsBadge_positive' data-ipsTooltip title='{{#lang}}featured{{/lang}}'><i class='fa fa-star'></i></span>\
						{{/image.state.featured}}\
					</li>\
				{{/image.hasState}}\
				{{#image.allowComments}}\
					<li class='ipsPos_right' data-commentCount='{{image.comments}}'><i class='fa fa-comment'></i> {{image.comments}}</li>\
				{{/image.allowComments}}\
			</ul>\
		</a>\
		{{#image.modActions}}\
			<input type='checkbox' data-role='moderation' name='moderate[{{image.id}}]' data-actions='{{image.modActions}}' data-state='{{image.modStates}}'>\
		{{/image.modActions}}\
	</div>\
");

ips.templates.set('gallery.lightbox.wrapper', " \
	<div id='cLightbox' class='ipsModal' data-originalUrl='{{originalUrl}}' data-originalTitle='{{originalTitle}}'>\
		<span class='cLightboxClose'>&times;</span>\
		<div class='cLightboxBack'></div>\
	</div>\
");]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="templates" javascript_name="ips.templates.submit.js" javascript_type="template" javascript_version="103009" javascript_position="1000050"><![CDATA[ips.templates.set('gallery.submit.imageItem', " \
	<div class='ipsAttach ipsImageAttach ipsPad_half ipsAreaBackground_light {{#done}}ipsAttach_done{{/done}}' id='{{id}}' data-role='file' data-fileid='{{id}}' data-fullsizeurl='{{imagesrc}}' data-thumbnailurl='{{thumbnail}}' data-isImage='1'>\
		<ul class='ipsList_inline ipsImageAttach_controls'>\
			<li data-role='insert' {{#insertable}}style='display: none'{{/insertable}}><a href='#' data-action='insertFile' class='ipsAttach_selection' data-ipsTooltip title='{{#lang}}insertIntoPost{{/lang}}'><i class='fa fa-plus'></i></a></li>\
			</li>\
			<li class='ipsPos_right' {{#newUpload}}style='display: none'{{/newUpload}} data-role='deleteFileWrapper'>\
				<input type='hidden' name='{{field_name}}_keep[{{id}}]' value='1'>\
				<a href='#' data-role='deleteFile' class='ipsButton ipsButton_verySmall ipsButton_light' data-ipsTooltip title='{{#lang}}attachRemove{{/lang}}'><i class='fa fa-trash-o'></i></a>\
			</li>\
		</ul>\
		<div class='ipsImageAttach_thumb ipsType_center' data-role='preview' data-grid-ratio='65' data-action='insertFile' {{#thumb}}style='background-image: url( {{thumbnail}} )'{{/thumb}}>\
			{{#status}}\
				<span class='ipsImageAttach_status ipsType_light' data-role='status'>{{{status}}}</span>\
				<span class='ipsAttachment_progress'><span data-role='progressbar'></span></span>\
			{{/status}}\
			{{#thumb}}\
				{{{thumb}}}\
			{{/thumb}}\
		</div>\
		<h2 class='ipsType_reset ipsAttach_title ipsType_medium ipsTruncate ipsTruncate_line cGalleryImageAttach_info' data-role='title'>{{title}}</h2>\
		<p class='ipsType_light cGalleryImageAttach_info'>{{size}} {{#statusText}}&middot; <span data-role='status'>{{statusText}}</span>{{/statusText}}</p>\
	</div>\
");

ips.templates.set('gallery.submit.imageItemWrapper', " \
	<div class='cGallerySubmit_fileList'>{{{content}}}</div>\
");]]></file>
 <file javascript_app="gallery" javascript_location="front" javascript_path="templates" javascript_name="ips.templates.view.js" javascript_type="template" javascript_version="103009" javascript_position="1000050"><![CDATA[ips.templates.set('gallery.notes.wrapper', " \
<div class='cGalleryNote' data-controller='gallery.front.view.note' data-noteID='{{id}}' data-note=\"{{note}}\" {{#editable}}data-editable{{/editable}} data-posLeft='{{left}}' data-posTop='{{top}}' data-dimWidth='{{width}}' data-dimHeight='{{height}}'>\
	<div class='cGalleryNote_border'></div>\
	<div class='cGalleryNote_note' style='display: none'>\
		<div>{{note}}</div>\
	</div>\
</div>\
");

ips.templates.set('gallery.notes.delete', " \
	<a href='#' data-action='delete' class='cGalleryNote_delete' data-ipsTooltip title='{{#lang}}delete_note{{/lang}}'>&times;</a>\
");

ips.templates.set('gallery.notes.edit', " \
	<textarea>{{note}}</textarea>\
	<ul class='ipsList_inline'>\
		<li><button data-action='save' class='ipsButton ipsButton_light ipsButton_verySmall'>{{#lang}}save_note{{/lang}}</button></li>\
		<li><a href='#' data-action='cancel'>{{#lang}}cancel_note{{/lang}}</a></li>\
	</ul>\
");]]></file>
 <order app="global" path="/dev/js//framework/">templates
common/ips.loader.js
common/ui
common/utils
common
controllers</order>
 <order app="global" path="/dev/js//library/">underscore
jquery
mustache
jstz
Debug.js
app.js</order>
 <order app="global" path="/dev/js//library//jquery">jquery.js
jquery.history.js
jquery.transform.js</order>
 <order app="global" path="/dev/js//library//linkify">linkify.min.js
linkify-jquery.min.js</order>
</javascript>
