.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 and update the state history * * @returns {void} */ closeLightbox: function( e ) { this.hideLightbox(); // Store a history entry History.pushState( { controller: 'gallery.front.browse.imageLightbox', bypassStateAdjustment: true }, $('#cLightbox').attr('data-originalTitle'), $('#cLightbox').attr('data-originalUrl') ); }, /** * Hides the lightbox * * @returns {void} */ hideLightbox: function() { // Hide the lightbox $('#cLightbox').fadeOut( 400 ); $('body').removeClass('ipsNoScroll'); // If it's a video, pause it to stop the sound if( $('#cLightbox video').length ) { _.each( $('#cLightbox video'), function( elem ) { elem.pause(); } ); } } }); }(jQuery, _));]]> /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.browse.list.js - Gallery browse list controller * * Author: Rikki Tissier */ ;( function($, _, undefined){ "use strict"; ips.controller.register('gallery.front.browse.list', { initialize: function () { this.on( 'change', '[data-role="moderation"]', 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, _)); /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.settings.settings.js * * Author: Brandon Farber */ ;( function($, _, undefined){ "use strict"; 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, _)); 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, _));]]> 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, _));]]> ' + htmlToInsert.replace( /name="image_tags_DEFAULT"/g, 'name="image_tags_DEFAULT" data-ipsAutocomplete ').replace( /_DEFAULT/g, '_' + imageId ) + ""; // 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("") .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(); }, /** * IE11 doesn't support submit buttons outside of a
like HTML5 does. * So, if we click the button, are on IE11, and not inside a form, manually trigger a submit. * * @param {event} e Event object * @returns {void} */ maybeSubmitForm: function (e) { var isIE11 = !(window.ActiveXObject) && "ActiveXObject" in window; if( !$( e.currentTarget ).closest('form').length && $( e.currentTarget ).is('[form]') && isIE11 ){ var form = $('#' + $( e.currentTarget ).attr('form')); if( form.length ){ form.submit(); } } }, /** * 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"); } } if( ips.getSetting('memberID') ) { 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'); if( dialogElem.length ) { 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 ] ); }); } else { if( callback ){ callback(); } $( 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, _));]]> 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(); var urlToRequest = url; } else { // We need to add preload=1 to the if( url.match(/\?/) ) { if( url.slice(-1) != '?' ){ var urlToRequest = url + '&preload=1'; } } else { var urlToRequest = url + '?preload=1'; } } // 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 ] ); self._markImageRead( url ); return; } var ajax = ips.getAjax(); if( cacheOnly ){ this._preloadAjax[ direction ] = ajax; } else { this._ajaxObj = ajax; } ajax( urlToRequest, { 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; } } }); }, /** * Mark an image read. When we fetch for caching we don't want the image marked as read, but when we move forward/back * to view the image in the lightbox, it should be marked at that point. * * @param {string} url URL of new image to fetch * @returns {void} */ _markImageRead: function (url) { var self = this; if( url.match(/\?/) ) { if( url.slice(-1) != '?' ){ url = url + '&do=markread'; } } else { url = url + '?do=markread'; } ips.getAjax()( url, { dataType: 'json' } ) .done( function (response) { Debug.log( "Marked read" ); }); }, /** * 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( / div textarea').focus(); 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 ,,, */ _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 the note block is now out of view, flip it to the other side var parent = this.scope.closest('.cGalleryViewImage'); var notePos = this.scope.position(); var posLeft = this.scope.find('.cGalleryNote_note').css('left'); var posRight = this.scope.find('.cGalleryNote_note').css('right'); // If the text box is on the right side, and is now past the boundary of the image, flip it to the left side if( parseInt( posLeft ) > 0 ) { if( notePos['left'] + parseInt( this.scope.width() ) + parseInt( this.scope.find('.cGalleryNote_note').width() ) > parent.width() ) { // But don't bother doing it if it will just be off the other side now if( notePos['left'] - parseInt( this.scope.find('.cGalleryNote_note').width() ) > 0 ) { this.scope.find('.cGalleryNote_note').css( 'left', posRight ); this.scope.find('.cGalleryNote_note').css( 'right', posLeft ); } } } else { if( notePos['left'] - parseInt( this.scope.find('.cGalleryNote_note').width() ) < 0 ) { // Don't flip if it will be off the other side now if( notePos['left'] + parseInt( this.scope.width() ) + parseInt( this.scope.find('.cGalleryNote_note').width() ) < parent.width() ) { this.scope.find('.cGalleryNote_note').css( 'left', posRight ); this.scope.find('.cGalleryNote_note').css( 'right', posLeft ); } } } // 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, _));]]> \ {{/showThumb}}\ {{^showThumb}}\ \ {{/showThumb}}\ \ {{#showThumb}}\ {{#lazyLoad}}\ {{image.caption}}\ {{/lazyLoad}}\ {{^lazyLoad}}\ {{image.caption}}\ {{/lazyLoad}}\ {{/showThumb}}\
\ \
\ {{#lang}}by{{/lang}} {{image.author.name}}\ {{#lang}}in{{/lang}} {{image.container}}\
\
\
    \ {{#image.unread}}\
  • \ \
  • \ {{/image.unread}}\ {{#image.hasState}}\
  • \ {{#image.state.hidden}}\ \ {{/image.state.hidden}}\ {{#image.state.pending}}\ \ {{/image.state.pending}}\ {{#image.state.pinned}}\ \ {{/image.state.pinned}}\ {{#image.state.featured}}\ \ {{/image.state.featured}}\
  • \ {{/image.hasState}}\ {{#image.allowComments}}\
  • {{image.comments}}
  • \ {{/image.allowComments}}\
\
\
\ "); ips.templates.set('gallery.patchwork.tableItem', " \ {{#showThumb}}\ \ "); ips.templates.set('gallery.submit.imageItemWrapper', " \
{{{content}}}
\ ");]]>
\
\ \ \ "); ips.templates.set('gallery.notes.delete', " \ ×\ "); ips.templates.set('gallery.notes.edit', " \ \ \ ");]]>
templates common/ips.loader.js common/ui common/utils common controllers underscore jquery mustache jstz IntersectionObserver Debug.js app.js jquery.js jquery-migrate.js jquery.history.js jquery.transform.js linkify.min.js linkify-jquery.min.js