[data-role="containerItems"]' ); var self = this; if( !itemContainer.html().trim().length ){ ips.getAjax()( ips.getSetting( 'baseURL' ) + '?app=cms&module=pages&controller=ajax&do=loadItems',{ data: { navType: $( self.scope ).attr( 'data-navType' ), navId: $( self.scope ).attr( 'data-navId' ), container: $( details ).data( 'container' ) } }).done( function(response){ $( itemContainer ).html( response ); }); } } }); }(jQuery, _));]]> /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.databases.download.js - Present an alert to the user asking them if they want to build the app first * * Author: Rikki Tissier (Good bits), Matt Mecham (bad bits) */ ;( function($, _, undefined){ "use strict"; ips.controller.register('cms.admin.databases.download', { initialize: function () { this.on( 'click', this.launchAlert ); }, /** * Displays a dialog to the user with 'build and download' and 'download' options * * @param {event} e Event object * @returns {void} */ launchAlert: function (e) { var self = this; e.preventDefault(); ips.ui.alert.show({ type: 'confirm', message: ips.getString('cms_download_db_explain'), icon: 'fa-solid fa-download', buttons: { ok: ips.getString('cms_download_db'), cancel: ips.getString('cancel') }, callbacks: { ok: function () { window.location = self.scope.attr('data-downloadURL'); }, } }); } }); }(jQuery, _)); ').attr('type', 'text/css').appendTo('head'); var stylesheet = "\ body {\ font-family: " + data.font + "; \ color: " + data.text + ";\ }\ \ a {\ color: " + data.link + ";\ }\ "; elem.text( stylesheet ); }, /** * Event handler for clicking a link; we'll send it to the parent to handle * * @param {event} e Event object * @returns {void} */ clickLink: function (e) { var link = $( e.currentTarget ); var href = link.attr('href'); if( href.startsWith('#') ){ return; } e.preventDefault(); this._send( 'goToLink', { link: href } ); }, /** * Shortcut for calculating and sending the height to the parent * * @returns {void} */ _sendHeight: function () { var currentSize = $('body').outerHeight(); this._send( 'iframeSize', { size: currentSize } ); }, /** * Sends a message to the parent * * @param {string} method Method name to call on the parent * @param {object} data Data object to send * @returns {void} */ _send: function (method, data) { var output = JSON.stringify( _.extend( data || {}, { method: method, widgetID: this._widgetID, blockID: this._blockID })); window.top.postMessage( output, '*' ); } }); }(jQuery, _));]]> this.checkChange(), 50)); this.currentValue = fieldDefaultValue.value || ''; document.querySelectorAll('#field_default_update_existing').ipsHide(); } this.on('change', 'input[data-control="color"]', this.changeBadgeColor); this.on('change', 'input[type="checkbox"][name*="color"]', this.changeBadgeColor); this.elem.querySelector('form').addEventListener('ips:calculateToggles', e => { const form = e.target; if (!(form instanceof HTMLFormElement)) { return; } const typeSelector = form.querySelector('select[name="field_type"]'); const showMapActive = typeSelector?.value?.toLowerCase() === 'address'; const mapEl = form.querySelectorAll('#field_show_map_listing,#field_show_map_display'); if (mapEl && !showMapActive) { mapEl.forEach(el => e.detail.togglesOff.add(el)); } // for these, several of the listing/display format options don't apply if (typeSelector?.value?.match(/^(youtube|spotify|soundcloud)$/i)) { form .querySelectorAll("#field_display_listing_json_badge, #field_display_listing_badge_bgcolor, #field_display_listing_badge_color, #field_display_display_json_badge, #field_display_display_badge_bgcolor, #field_display_display_badge_color, #field_display_display_json_where") .forEach(el => { e.detail.togglesOff.add(el); e.detail.togglesOn.delete(el); }); } else { form .querySelectorAll("#media_display_listing_method, #media_display_display_method") .forEach(el => { e.detail.togglesOff.add(el); e.detail.togglesOn.delete(el); }); } // field_truncate is an exception where there are two togglers; in this case we just target the "Show in listing template" one const fieldTruncate = form.querySelector('#field_truncate'); if (fieldTruncate) { if (form.querySelector('#check_field_display_listing') && !form.querySelector('#check_field_display_listing').checked) { e.detail.togglesOff.add(fieldTruncate); e.detail.togglesOn.delete(fieldTruncate); } } }) }, /** * checkChange handler * * @returns {void} */ checkChange() { if (document.querySelector('#elTextarea_field_default_value').value !== this.currentValue) { document.querySelector('#field_default_update_existing').ipsShow(); } else { document.querySelector('#field_default_update_existing').ipsHide(); } }, changeBadgeColor(e){ const colorPicker = e.target?.closest?.('.ipsFieldRow[id]').getAttribute('id'); const badge = `#elField_field_display_${colorPicker?.includes('listing') ? 'listing' : 'display'}_json_badge_badge_label`; const color = (!colorPicker || document.querySelector(`#${colorPicker}_none`)?.checked) ? '' : document.querySelector(`input[name="${colorPicker}"]`)?.value; document .querySelectorAll(`${badge} span`) .forEach( span => span.style[colorPicker?.includes('bgcolor') ? 'background' : 'color'] = color ); } }); }(jQuery, _));]]> a', this.clickMediaFolder ); this.on( 'click', '[data-action="deleteSelected"]', this.deleteSelected ); this.on( 'keydown', '[data-role="mediaSearch"]', this.searchKeyPress ); this.on( 'submitDialog', '[data-role="uploadButton"]', this.dialogSubmitted ); this.on( 'submitDialog', '[data-role="replaceFile"]', this.replaceDialogSubmitted ); this.on( 'click', '[data-role="replaceFile"]', this.uploadNewFile ); this.on( window, 'resize', this.resizePanels ); this.setup(); }, /** * Setup method * * @returns {void} */ setup: function () { this._fileListing = this.scope.find('[data-role="fileListing"]'); this._searchResults = this.scope.find('[data-role="searchResults"]'); this._sidebar = this.scope.find('[data-role="mediaSidebar"]'); this._folderListing = this.scope.find('[data-role="folderList"]'); this._uploadURL = this.scope.find('[data-role="uploadButton"]').attr('href'); this._newFolderURL = this.scope.find('[data-role="folderButton"]').attr('href'); this._deleteFolderURL = this.scope.find('[data-action="deleteFolder"]').find('a').attr('href'); this.scope.find('[data-role="uploadButton"]').attr('href', this._uploadURL + '&media_parent=0' ); this.scope.find('[data-role="folderButton"]').attr('href', this._newFolderURL + '&media_parent=0' ); this.scope.find('[data-role="fileListing"]').click(); this.resizePanels(); }, /** * Resize panels * * @returns {void} */ resizePanels: function () { var windowHeight = $( window ).height(); this.scope.find('#elMedia_sidebar, #elMedia_fileList, #elMedia_searchResults').each( function () { var top = $( this ).offset().top; $( this ).css({ height: ( windowHeight - top ) + 'px' }); }); }, /** * Dialog submit handler from uploading process * The dialog response will include an array of media items in this folder. We'll use * this data to rebuild the file listing, thereby showing the new files immediately. * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ dialogSubmitted: function (e, data) { var newHTML = []; var folderID = data.response.folderID; _.each( data.response.rows, function (file, key) { newHTML.push( file ); }); this._cachedFolders[ folderID ] = newHTML; this._buildFileListing( folderID, newHTML ); ips.ui.flashMsg.show( ips.pluralize( ips.getString('mediaUploadedCount'), data.response.count ) ); }, /** * Dialog submit handler from uploading process * The dialog response will include an array of media items in this folder. We'll use * this data to rebuild the file listing, thereby showing the new files immediately. * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ replaceDialogSubmitted: function (e, data) { var newHTML = []; var folderID = data.response.folderID; _.each( data.response.rows, function (file, key) { newHTML.push( file ); }); this._cachedFolders[ folderID ] = newHTML; this._buildFileListing( folderID, newHTML ); delete this._cachedFileInfo[ data.response.fileID ]; ips.ui.flashMsg.show( ips.getString('mediaUploadedReplace') ); this._fileListing.find('[data-fileid="' + data.response.fileID + '"]').click(); }, /** * keypress event handler for the search text box * * @param {event} e Event object * @returns {void} */ searchKeyPress: function (e) { clearTimeout( this._searchTimer ); this._searchTimer = setTimeout( _.bind( this._doSearch, this ), 500 ); }, /** * Event handler for clicking a folder in the list * * @param {event} e Event object * @returns {void} */ clickMediaFolder: function (e) { e.preventDefault(); var self = this; var allRows = this._folderListing.find('[data-role="mediaFolder"]'); var row = $( e.currentTarget ).closest('[data-role="mediaFolder"]'); var rowURL = row.find('> a').attr('href'); var rowID = row.attr('data-folderID'); this._resetSearch(); if( row.hasClass('ipsTreeList_activeBranch') ){ row .removeClass('ipsTreeList_activeBranch') .addClass('ipsTreeList_inactiveBranch'); } else { row .removeClass('ipsTreeList_inactiveBranch') .addClass('ipsTreeList_activeBranch'); } allRows.removeClass('ipsTreeList_activeNode'); row.addClass('ipsTreeList_activeNode'); // Update the upload/new folder buttons this.scope.find('[data-role="uploadButton"]').attr('href', this._uploadURL + '&media_parent=' + rowID ); this.scope.find('[data-role="folderButton"]').attr('href', this._newFolderURL + '&media_parent=' + rowID ); this.scope.find('[data-role="folderButton"]').attr('href', this._newFolderURL + '&media_parent=' + rowID ); this.scope.find('[data-action="deleteFolder"]').find('a').attr('href', this._deleteFolderURL + '&id=' + rowID ); if ( rowID > 0 ) { this.scope.find('[data-action="deleteFolder"]').removeClass('ipsHide'); } else { this.scope.find('[data-action="deleteFolder"]').addClass('ipsHide'); } // Load subfolders this._loadFolders( rowID, rowURL ); // And folder contents this._loadFiles( rowID, rowURL ); this._unselectAll(); this._updatePreview(); this._checkDeleteButton(); }, /** * Prompts and deletes selected files * * @param {event} e Event object * @returns {void} */ deleteSelected: function (e) { var self = this; var selected = this._getSelected().closest('[data-role="mediaItem"]'); var selectedIDs = []; $.each( selected, function () { selectedIDs.push( $( this ).attr('data-fileid') ); }); // Confirm with user ips.ui.alert.show({ type: 'confirm', message: ips.pluralize( ips.getString('mediaConfirmDelete'), selected.length ), icon: 'warn', callbacks: { ok: function () { selected.find('.cMedia_itemSelected').removeClass('cMedia_itemSelected') // Hide and remove them ips.utils.anim.go( 'fadeOutDown', selected ) .done( function () { selected.remove(); self._updatePreview(); self._checkDeleteButton(); }); // Delete them on the server ips.getAjax()( '?app=cms&module=pages&controller=media&do=deleteByFileIds', { data: { fileIds: selectedIDs } }) .fail( function () { ips.ui.alert.show({ type: 'alert', message: ips.pluralize( ips.getString('mediaErrorDeleting'), selected.length ) }); }) } } }); }, /** * Event handler for clicking in the whitepace of the file listing * Unselects any selected files * * @param {event} e Event object * @returns {void} */ clickWrapper: function (e) { if( !$( e.target ).closest('[data-role="mediaItem"]').length ) { this._unselectAll(); this._updatePreview(); this._checkDeleteButton(); } }, /** * Event handler for clicking a file in the file listing * Modifier keys change how we handle the selection in order to mimic file managers * * @param {event} e Event object * @returns {void} */ clickItem: function (e) { var item = $( e.currentTarget ).find('> .cMedia_item'); var metaPressed = e.metaKey; var shiftPressed = e.shiftKey; if( !metaPressed && !shiftPressed ){ // If no special keys are pressed, unhighlight all and highlight this one this._unselectAll(); item.addClass('cMedia_itemSelected'); this._switchToTab('overview'); } else if( metaPressed ) { // If meta key is pressed, toggle the state of the clicked image item.toggleClass( 'cMedia_itemSelected', ( !item.hasClass('cMedia_itemSelected') ) ); } else if( shiftPressed ) { // If shift is pressed, select the whole run // Find the first selected file var all = this._fileListing.find('.cMedia_item'); var indexOfFirst = all.index( all.filter('.cMedia_itemSelected').first() ); var indexOfClicked = all.index( item ); var itemsToSelect = null; // Unselect all to start with this._unselectAll(); // Select the run if( indexOfFirst == indexOfClicked ){ itemsToSelect = item; } else if( indexOfFirst < indexOfClicked ){ itemsToSelect = all.slice( indexOfFirst, indexOfClicked ).addBack( item ); } else if( indexOfFirst > indexOfClicked ){ itemsToSelect = all.slice( indexOfClicked, indexOfFirst + 1 ); } itemsToSelect.addClass('cMedia_itemSelected'); } this._updatePreview(); this._checkDeleteButton(); }, /** * Show the dialog to replace the current file we are viewing * * @returns {void} */ uploadNewFile: function( e ) { if( e ){ e.preventDefault(); } var itemId = this._getSelected().closest('[data-role="mediaItem"]').attr('data-fileid'); $( e.currentTarget ).ipsDialog( { remoteSubmit: true, forceReload: true, url: $( e.currentTarget ).attr('data-baseUrl') + itemId, destructOnClose : true, title: ips.getString('replaceMediaFile') }); }, /** * Checks whether any files are selected and displays the delete button if so * * @returns {void} */ _checkDeleteButton: function () { var selected = this._getSelected().closest('[data-role="mediaItem"]'); if( selected.length == 0 ){ this.scope.find('[data-action="deleteSelected"]').addClass('ipsHide'); } else { this.scope.find('[data-action="deleteSelected"]').removeClass('ipsHide'); } }, /** * Loads sub-folders of the given folder id * * @param {number} rowID Folder row ID to load * @param {string} url Url for this folder * @returns {void} */ _loadFolders: function (rowID, url) { var self = this; var row = this._folderListing.find('[data-folderID="' + rowID + '"]'); if( this._folderAjax && _.isFunction( this._folderAjax.abort ) ){ this._folderAjax.abort(); } // If this row is loaded, we can just show it if( row.attr('data-loaded') ){ return; } this._folderAjax = ips.getAjax()( url, { data: { get: 'folders' } } ) .done( function (response) { var subFolder = row.find('ul'); var newHTML = []; _.each( response, function (folder, key) { newHTML.push( folder ); }); subFolder.html( newHTML.join('') ); row.attr('data-loaded', true); }); }, /** * Loads files inside the given folder ID * * @param {number} folderID Folder row ID to load * @param {string} url Url for this folder * @returns {void} */ _loadFiles: function (folderID, url) { var self = this; if( this._filesAjax && _.isFunction( this._filesAjax.abort ) ){ this._filesAjax.abort(); } // Are we already viewing this folder? if( this._fileListing.attr('data-showing') == folderID ){ return; } // Do we have a cache already? if( !_.isUndefined( this._cachedFolders[ folderID ] ) ){ this._buildFileListing( folderID, this._cachedFolders[ folderID ] ); return; } // No cache, so load the file listing this._fileListing.addClass('ipsLoading').html(''); ips.getAjax()( url, { data: { get: 'files' } } ) .done( function (response) { var newHTML = []; _.each( response, function (file, key) { newHTML.push( file ); }); self._cachedFolders[ folderID ] = newHTML; self._buildFileListing( folderID, newHTML ); }) .always( function () { self._fileListing.removeClass('ipsLoading'); }); }, /** * Builds the file listing for the given folder ID from the given data * * @param {number} folderID Folder row ID to build * @param {array} data Array of file item HTML fragments * @returns {void} */ _buildFileListing: function (folderID, data) { var output; if( !data.length ){ output = ips.templates.render('templates.media.noItems'); } else { output = ips.templates.render( 'templates.media.grid', { contents: data.join('') }); } this._fileListing.attr( 'data-showing', folderID ).html( output ); $( document ).trigger( 'contentChange', [ this._fileListing ] ); }, /** * Updates the preview panel in the sidebar by finding the selected item and extracting * relevant information about it to display * * @returns {void} */ _updatePreview: function () { // How many selected? var self = this; var selected = this._getSelected().closest('[data-role="mediaItem"]'); if( selected.length == 0 || selected.length > 1 ){ // Build the string var language = ( selected.length == 0 ) ? ips.getString('mediaNoneSelected') : ips.pluralize( ips.getString('mediaMultipleSelected'), selected.length ); // Insert it this._sidebar.find('[data-role="multipleItemsMessage"]').html( language ); // Show the message box this._sidebar.find('[data-role="itemInformation"]').hide().end().find('[data-role="multipleItems"]').show(); this._sidebar.find('[data-role="replaceFile"]').hide(); } else { // Build the file info var info = { itemFilename: selected.attr('data-filename'), itemUploaded: selected.attr('data-uploaded'), itemTag: '{media="' + selected.attr('data-fileid') + '"}', itemUrl: selected.attr('data-url'), itemID: selected.attr('data-fileid'), itemIsImage: selected.attr('data-fileType') === 'image', itemFilesize: null, itemDimensions: null }; // Dimensions & filesize var cache = this._cachedFileInfo[ info.itemID ]; if( !_.isUndefined( cache ) ){ info.itemFilesize = cache['itemFilesize']; info.itemDimensions = cache['itemDimensions']; } else { // Do ajax this._infoAjax = ips.getAjax()( '?app=cms&module=pages&controller=media&do=getFileInfo&id=' + info.itemID ) .done( function (response) { self._cachedFileInfo[ info.itemID ] = { itemFilesize: response.fileSize, itemDimensions: response.dimensions }; self._sidebar.find('[data-role="itemFilesize"]').text( response.fileSize ); self._sidebar.find('[data-role="itemDimensions"]').text( response.dimensions ); }); } // Build thumbnail if( info.itemIsImage ){ info.itemPreview = $('').attr( 'src', selected.attr('data-url') ).attr('data-ipsLightbox', true ); } else { info.itemPreview = ''; } // Update the easy info _.each( info, function (value, key) { var elem = self._sidebar.find('[data-role="' + key + '"]'); if( elem.is('input') ){ elem.val( value ); } else if( key == 'itemPreview' ) { elem.html( value ); } else if ( key == 'itemUrl' ) { elem.html( $('').attr('href', value).text( value ).attr('target', '_blank') ); } else if( value === null ){ elem.html( $('').addClass('i-color_soft').text('Loading...') ); } else { elem.text( value ); } }); // If this is an image, show the dimensions row this._sidebar.find('[data-role="itemDimensionsRow"]').toggle( info.itemIsImage ); // Pre-select the tag box this._sidebar.find('[data-role="itemTag"]').get(0).select(); // Hide the 'multiple items selected' div and show our info div this._sidebar.find('[data-role="itemInformation"]').show().end().find('[data-role="multipleItems"]').hide(); this._sidebar.find('[data-role="replaceFile"]').show(); $( document ).trigger( 'contentChange', [ this._sidebar ] ); } }, /** * Performs a tree search and displays results * * @returns {void} */ _doSearch: function () { if( this._searchAjax && _.isFunction( this._searchAjax.abort ) ){ this._searchAjax.abort(); } var self = this; var value = this.scope.find('[data-role="mediaSearch"]').val(); if( !_.isEmpty( value ) ){ this._searchResults.show().addClass('ipsLoading'); this._fileListing.hide(); this._folderListing.addClass('cMedia_treeDisabled'); this._searchAjax = ips.getAjax()( '?app=cms&module=pages&controller=media&do=search', { data: { input: value } }) .done( function (response) { var newHTML = []; var output = ''; _.each( response, function (file, key) { newHTML.push( file ); }); if( !newHTML.length ){ output = ips.templates.render('templates.media.noSearchResults'); } else { output = ips.templates.render( 'templates.media.grid', { contents: newHTML.join('') }); } self._searchResults.removeClass('ipsLoading').html( output ); $( document ).trigger( 'contentChange', [ self._searchResults ] ); self._unselectAll(); self._updatePreview(); self._checkDeleteButton(); }) } else { this._searchResults.hide().html(''); this._fileListing.show(); this._folderListing.removeClass('cMedia_treeDisabled'); this._updatePreview(); this._checkDeleteButton(); } }, /** * Returns selected files, from the file listing or search results panels depending * on which is active * * @returns {object} jQuery collection of selected files */ _getSelected: function () { if( this._fileListing.is(':visible') ){ return this._fileListing.find('.cMedia_itemSelected'); } else { return this._searchResults.find('.cMedia_itemSelected'); } }, /** * Resets the search box, hiding the results in the process * * @returns {void} */ _resetSearch: function () { this.scope.find('[data-role="mediaSearch"]').val(''); this._searchResults.hide(); this._fileListing.show(); this._unselectAll(); this._updatePreview(); this._checkDeleteButton(); }, /** * Unselects all files * * @returns {void} */ _unselectAll: function () { this._fileListing.find('.cMedia_item').removeClass('cMedia_itemSelected'); this._searchResults.find('.cMedia_item').removeClass('cMedia_itemSelected'); }, /** * Switches the sidebar to the given tab * * @param {string} tab Tab to switch to * @returns {void} */ _switchToTab: function (tab) { if( tab == 'overview' ){ this.scope.find('#ipsTabs_mediaSidebar_overview').click(); } } }); }(jQuery, _));]]> ", " data-inheritStyle='true'>") ); } else { textbox.val( textbox.val().replace(" data-inheritStyle='true'>", ">") ); } } }); }(jQuery, _));]]> /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.databases.revisions.js * * Author: Brandon Farber */ ;( function($, _, undefined){ "use strict"; ips.controller.register('cms.front.records.revisions', { initialize: function () { var self = this; ips.loader.getStatic( ['applications/core/interface/static/codemirror/diff_match_patch.js','applications/core/interface/static/codemirror/codemirror.js'] ).then( function () { self._initCodeMirror(); }); }, /** * Initializes CodeMirror on a textarea with the provided key * * @returns {void} */ _initCodeMirror: function () { var self = this; var count = 0; _.each( self.scope.find("[data-key]"), function(elem){ if( $(elem).attr('data-method') == 'merge' ) { CodeMirror.MergeView( document.getElementById( $(elem).identify().attr('id') ), { value: self.scope.find( '[data-current="' + $(elem).attr('data-key') + '"]' ).val(), origLeft: self.scope.find( '[data-original="' + $(elem).attr('data-key') + '"]' ).val(), lineWrapping: true, lineNumbers: false, mode: 'htmlmixed', revertButtons: false } ); } else if( _.isUndefined( $(elem).attr('data-complete') ) ) { var diff = new diff_match_patch(); var differences = diff.diff_main( self.scope.find( '[data-original="' + $(elem).attr('data-key') + '"]' ).html(), self.scope.find( '[data-current="' + $(elem).attr('data-key') + '"]' ).html() ); var currentDiff = diff.diff_prettyHtml(differences); self.scope.find( '[data-current="' + $(elem).attr('data-key') + '"]' ).html( currentDiff ); $(elem).attr( 'data-complete', 1 ); } }); } }); }(jQuery, _)); /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.templates.addForm.js - Controller for the 'add' form in the template editor * * Author: Rikki Tissier */ ;( function($, _, undefined){ "use strict"; ips.controller.register('core.admin.templates.addForm', { initialize: function () { this.on( 'submit', 'form.ipsForm', this.submitForm ); this.on( document, 'fileListRefreshed.templates', this.closeDialog ); }, submitForm: function (e) { e.preventDefault(); var self = this; if( !$( e.currentTarget ).attr('data-bypassValidation') ){ // The form hasn't been validated by the genericDialog controller yet, so bail for now return; } // Gather form values and send them ips.getAjax()( $( e.currentTarget ).attr('action'), { dataType: 'json', data: $( e.currentTarget ).serialize(), type: 'post' }) .done( function (response) { self.trigger( 'addedFile.templates', { type: self.scope.attr('data-type'), fileID: response.id, name: response.name }); }); }, closeDialog: function (e, data) { this.trigger('closeDialog'); } }); }(jQuery, _)); /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.templates.conflict.js - Templates: Parent controller for the template conflict manager * * Author: Matt Mecham */ ;( function($, _, undefined){ "use strict"; ips.controller.register('core.admin.templates.conflict', { initialize: function () { this.on( 'click', '.ipsButton', this.makeSelection ); this.setup(); }, setup: function () { $('span[data-conflict-name] input[type=radio]').hide(); }, /** * "Use this version" button is clicked * * @param {event} e Event object * @returns {void} */ makeSelection: function (e) { var span = $( e.target ).closest('span'); var radio = $( span ).find('input[type=radio]'); var name = $( span ).attr('data-conflict-name'); var id = $( radio ).attr('name').replace( /conflict_/, '' ); // Button disabled if ( span.hasClass('ipsButton--disabled') ){ return false; } // Undo selection else if ( span.hasClass('ipsButton--secondary') ){ radio.removeAttr('checked'); span.removeClass('ipsButton--secondary') .addClass('ipsButton--primary') .find('strong') .html( ips.getString('sc_use_this_version') ); $('input[type=radio][name=conflict_' + id + ']').closest('span.ipsButton[data-conflict-name=' + ( name == 'new' ? 'old' : 'new' ) +']').removeClass('ipsButton--disabled'); $('th span[data-conflict-id=' + id + '][data-conflict-name=' + name + ']').removeClass('ipsBadge ipsBadge--positive'); ips.utils.anim.go( 'blindDown', this.scope.find('div[data-conflict-id=' + id + ']') ); } // Make selection else { radio.attr('checked', 'checked'); span.removeClass('ipsButton--primary') .addClass('ipsButton--secondary') .find('strong') .html( ips.getString('sc_remove_selection') ); $('input[type=radio][name=conflict_' + id + ']').closest('span.ipsButton[data-conflict-name=' + ( name == 'new' ? 'old' : 'new' ) +']').addClass('ipsButton--disabled'); $('th span[data-conflict-id=' + id + '][data-conflict-name=' + name + ']').addClass('ipsBadge ipsBadge--positive'); ips.utils.anim.go( 'fadeOut', this.scope.find('div[data-conflict-id=' + id + ']') ); } } }); }(jQuery, _)); ') .attr( 'type', 'hidden' ) .attr( 'name', original.attr('name') ) .attr( 'value', original.attr('value') ) .attr( 'data-role', 'variables' ) ) original.remove(); } ips.loader.getStatic( ['applications/core/interface/static/codemirror/diff_match_patch.js','applications/core/interface/static/codemirror/codemirror.js'] ).then( function () { self._initCodeMirror( $( item ).attr('data-fileid'), $( item ).attr('data-type') ); }); }); }, /** * Event handler for the editor preference menu * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ menuSelected: function (e, data) { if( data.originalEvent ){ data.originalEvent.preventDefault(); } if( data.triggerID == 'elTemplateEditor_preferences' ){ if ( data.selectedItemID == 'diff' ) { this.toggleDiff(); } else if( data.selectedItemID == 'wrap' ){ this._changeEditorPreference( !ips.utils.db.get('templateEditor', 'wrap'), 'wrap', 'lineWrapping' ); } else { this._changeEditorPreference( !ips.utils.db.get('templateEditor', 'lines'), 'lines', 'lineNumbers' ); } } }, /** * Method that updates an editor preference * * @param {object} data Event data object from this.menuSelected * @returns {void} */ _changeEditorPreference: function (toValue, type, cmName) { // Set the menu appropriately if( toValue ){ $('#elTemplateEditor_preferences_menu') .find('[data-ipsMenuValue="' + type + '"]').addClass('ipsMenu_itemChecked'); } else { $('#elTemplateEditor_preferences_menu') .find('[data-ipsMenuValue="' + type + '"]').removeClass('ipsMenu_itemChecked'); } // Update controller variable this._editorPreferences[ type ] = toValue; // Update local DB ips.utils.db.set( 'templateEditor', type, toValue ); // Update all CM instances for( var i in this._cmInstances ){ if ( this._cmInstances[ i ] instanceof CodeMirror.MergeView ) { this._cmInstances[ i ].edit.setOption( cmName, toValue ); this._cmInstances[ i ].left.orig.setOption( cmName, toValue ); } else { this._cmInstances[ i ].setOption( cmName, toValue ); } } }, /** * A dialog has been opened * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ openedDialog: function (e, data) { if( data.elemID == 'elTemplateEditor_variables' || data.elemID == 'elTemplateEditor_attributes' ){ this._insertVariablesIntoDialog( data ); } }, /** * Inserts the current variables value into the dialog * * @param {object} data Event data object from the dialog * @returns {void} */ _insertVariablesIntoDialog: function (data) { // First get the variables var active = this._getActiveTab(); if( !active.tabPanel ){ return; } var variablesValue = active.tabPanel.find('[data-role="variables"]').val().trim(); var descriptionValue = active.tabPanel.find('[data-role="description"]').val().trim(); var titleValue = active.tabPanel.find('[data-role="title"]').val().trim(); var groupValue = active.tabPanel.find('[data-role="group"]').val().trim(); data.dialog .find('[data-role="variables"]') .val( variablesValue ) .end() .find('[data-role="description"]') .val( descriptionValue ) .end() .find('[name="_variables_fileid"]') .val( active.tabPanel.attr('data-fileid') ) .end() .find('[name="_variables_location"]') .val( active.tabPanel.attr('data-location') ) .end() .find('[data-role="title"]') .val( titleValue ); /* Show the correct select box */ data.dialog.find('select[data-container-type]').addClass('ipsHide'); data.dialog.find('select[data-container-type=' + active.tabPanel.attr('data-location') + ']').removeClass('ipsHide'); data.dialog.find('select[data-container-type=' + active.tabPanel.attr('data-location') + ']').val( groupValue ); /* Reset */ data.dialog.find('#elTemplateEditor_container_title').show(); data.dialog.find('[data-role="container"]').show(); data.dialog.find('[data-role="variables"]').show(); data.dialog.find('#elTemplateEditor_attributes_title').show(); if ( active.tabPanel.attr('data-type') != 'template' ) { data.dialog.find('#elTemplateEditor_group_title').hide(); data.dialog.find('[data-role="variables"]').hide(); data.dialog.find('#elTemplateEditor_attributes_title').hide(); } else if ( active.tabPanel.attr('data-location') == 'block') { data.dialog.find('#elTemplateEditor_group_title').hide(); data.dialog.find('[data-role="group"]').hide(); } else if ( active.tabPanel.attr('data-location') == 'database' ) { /* Can't move */ data.dialog.find('#elTemplateEditor_group_title').hide(); data.dialog.find('[data-role="group"]').hide(); /* Can't rename */ data.dialog.find('#elTemplateEditor_title_title').hide(); data.dialog.find('[data-role="title"]').hide(); } else if ( active.tabPanel.attr('data-location') == 'page' ) { /* Hide params */ data.dialog.find('[data-role="variables"]').show(); data.dialog.find('#elTemplateEditor_attributes_title').show(); data.dialog.find('select[data-container-type=' + active.tabPanel.attr('data-location') + ']').show(); } }, /** * Updates the value of the variables field in the tab with the given file ID * * @param {event} e Event object * @param {event} e Event data object from templates.variablesDialog * @returns {void} */ updateVariables: function (e, data) { // Find the panel with the correct fileID, and update it's variables value this._tabContent .find('[data-fileid="' + data.fileID + '"]') .find('[data-role="variables"]') .val( data.value ) .end() .find('[data-role="title"]') .val( data.title ) .end() .find('[data-role="description"]') .val( data.description ) .end() .find('[data-role="group"]') .val( data.group ); this._updateTabLabel( data.fileID, data.title ); }, /** * Toggle diff * * @returns {void} */ toggleDiff: function () { var self = this; var active = this._getActiveTab(); var panel = active.tabPanel; var key = panel.attr('data-fileid'); // Toggle off if ( self._cmInstances[ key ] instanceof CodeMirror.MergeView ) { self.scope.find( '#editor_' + key ).val( this._cmInstances[ key ].edit.doc.getValue() ); panel.find('.CodeMirror-merge,.cTemplateMergeHeaders').remove(); self._initCodeMirror( key, panel.attr('data-type') ); } // Toggle on else { // Get the contents from the current CodeMirror instance and remove it self._cmInstances[ key ].save(); panel.find('.CodeMirror').remove(); panel.addClass('ipsLoading'); // Fire AJAX to get the original content ips.getAjax()( this._normalURL + '&do=diffTemplate', { dataType: 'json', data: this._getParametersFromPanel( panel ) }) .done( function (response) { // Initiate the merge view self._cmInstances[ key ] = CodeMirror.MergeView( document.getElementById( panel.attr('id') ), { value: self.scope.find( '#editor_' + key ).val(), origLeft: response, lineWrapping: self._editorPreferences['wrap'], lineNumbers: self._editorPreferences['lines'], mode: ( panel.attr('data-type') == 'templates' ? 'htmlmixed' : 'css' ), } ); panel.removeClass('ipsLoading'); // Add headers var headers = $( ips.templates.render( 'templates.editor.pagesDiffHeaders' ) ); panel.prepend( headers ); // Set size /* var height = self._getTabContentHeight() - panel.find('.cTemplateMergeHeaders').outerHeight(); self._cmInstances[ key ].edit.setSize( null, height ); self._cmInstances[ key ].left.orig.setSize( null, height ); $( self._cmInstances[ key ].left.gap ).css( 'height', height ); */ // Add change handler self._cmInstances[ key ].edit.on( 'change', function (doc, cm) { self._setChanged( true, key ); }); }); } }, /** * Saves the contents of the editor * * @param {event} e Event object * @returns {void} */ save: function (e) { e.preventDefault(); var self = this; var active = this._getActiveTab(); var panel = active.tabPanel; var key = panel.attr('data-fileid'); if( !active.tab || !active.tabPanel ){ Debug.warn('No active tab or tab panel'); return; } var save = this._getParametersFromPanel( panel ); // We call .save() on the CodeMirror instance, which will cause it to update the // contents of the original textbox, unless we're in merge view, when we have to // do that ourselves if ( this._cmInstances[ key ] instanceof CodeMirror.MergeView ) { self.scope.find( '#editor_' + key ).val( this._cmInstances[ key ].edit.doc.getValue() ); } else { this._cmInstances[ key ].save(); } // Add it to the save object save[ 'editor_' + key ] = this.scope.find( '#editor_' + key ).val(); // Name _.extend( save, { 't_name': panel.find('[data-role="title"]').val() }); // Description _.extend( save, { 't_description': panel.find('[data-role="description"]').val() }); // Group _.extend( save, { 't_group': panel.find('input[data-role="group"]').val() }); // If this is a template, add its variables if( panel.attr('data-location') == 'block' || panel.attr('data-location') == 'database' || panel.attr('data-location') == 'page' ){ _.extend( save, { 't_variables': panel.find('[data-role="variables"]').val() }); } // Show loading this.trigger( 'savingFile.templates' ); // Send it ips.getAjax()( this._normalURL + '&do=save', { dataType: 'json', data: save, type: 'post' }) .done( function (response) { if ( response.msg ) { ips.ui.alert.show( { type: 'alert', message: response.msg, icon: 'warn' }); } else { // Let everyone know self.trigger( 'savedFile.templates', { key: key, oldID: parseInt( panel.attr('data-itemID') ), newID: parseInt( response.template_id ), newTitle: response.template_title, oldContainer: parseInt( panel.attr('data-container') ), newContainer: parseInt( response.template_container ), status: ( response.template_user_added == 1 ) ? 'custom' : 'changed' }); // Remove the unsaved status from the tab self._setChanged( false, key ); // Update the toolbar self._updateToolbar( active.tab ); // Update the tab name self._updateTabLabel( key, response.template_title ); } }) .fail( function () { ips.ui.alert.show( { type: 'alert', message: ips.getString('saveThemeError'), icon: 'warn' }); }) .always( function () { self.trigger( 'saveFileFinished.templates' ); }); }, /** * Reverts or deletes a file * If the bypass parameter is false, this method will confirm the action with the user first * * @param {event} e Event object * @param {boolean} bypass Bypass the user confirmation? * @returns {void} */ revert: function (e, bypass) { e.preventDefault(); var self = this; var active = this._getActiveTab(); var panel = active.tabPanel; var key = panel.attr('data-fileid'); var message = ( $( e.currentTarget ).attr('data-actionType') == 'revert' ) ? ips.getString('skin_revert_confirm') : ips.getString('skin_delete_confirm'); if( bypass !== true ){ ips.ui.alert.show({ type: 'confirm', message: message, icon: 'warn', callbacks: { ok: function () { self.revert( e, true ); } } }); return; } var save = this._getParametersFromPanel( panel ); // Send it ips.getAjax()( this._normalURL + '&do=delete&wasConfirmed=1', { dataType: 'json', data: save, type: 'post' }) .done( function (response) { if( response.template_id ){ self._revertedFile( response, key, active ); } else { self._deletedFile( key, active ); } }); }, /** * Handles updating the editor when a file is reverted * * @param {object} response JSON response object from ajax request * @param {string} key Key of the file that's been reverted * @param {object} active Object containing keys 'tab' and 'tabPanel' referencing active items * @returns {void} */ _revertedFile: function (response, key, active) { // Let the document know this.trigger( 'revertedFile.templates', { key: key, oldID: parseInt( active.tabPanel.attr('data-itemID') ), newID: parseInt( response.template_id ), status: response.InheritedValue }); // Update the raw textarea $( '#editor_' + key ).val( response.template_content ); // Update codemirror this._cmInstances[ key ].setValue( response.template_content ); // Remove the unsaved status from the tab this._setChanged( false, key ); // Update the toolbar this._updateToolbar( active.tab ); }, /** * Handles updating the editor when a file is deleted * * @param {string} key Key of the file that's been reverted * @param {object} active Object containing keys 'tab' and 'tabPanel' referencing active items * @returns {void} */ _deletedFile: function (key, active) { this.trigger( 'deletedFile.templates', { key: key, fileID: active.tabPanel.attr('data-itemID'), location: active.tabPanel.attr('data-location'), type: active.tabPanel.attr('data-type') }); // Find close link in the tab active.tab.find('[data-action="closeTab"]').click(); }, /** * Returns an object of parameters used by the ajax requests * * @param {element} panel The panel being used as the source * @returns {object} */ _getParametersFromPanel: function (panel) { return { t_type: panel.attr('data-type'), t_location: panel.attr('data-location'), t_item_id: panel.attr('data-itemID'), t_container: panel.find('input[data-role=container]').val(), t_group: panel.attr('data-group'), t_name: panel.attr('data-name'), t_key: panel.attr('data-fileid') }; }, /** * A file has been saved or reverted * Updates the ID of any element with the old ID, and changes the state * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ updateFile: function (e, data) { this.scope .find('[data-itemID="' + data.oldID + '"]') .attr( 'data-itemID', data.newID ) .attr( 'data-container', data.newContainer ) .attr( 'data-inherited-value', data.status ); }, /** * Event handler for clicking a close tab button * * @param {event} e Event object * @returns {void} */ closeTab: function (e) { var tab = $( e.currentTarget ).closest('.ipsTabs__tab'); this._doCloseTab( tab ); }, /** * Handles closing a tab. * We first check if the tab is in an 'unsaved' state, and if so, prompt the user to confirm losing changes. * We then destroy the codemirror instance, remove the tab and panel, and switch to another open tab. * * @param {element} tab The tab to be closed * @param {boolean} bypass Whether to bypass the unsaved check * @returns {void} */ _doCloseTab: function (tab, bypass) { var self = this; var tabParent = tab.closest('[data-fileid]'); var key = tabParent.attr('data-fileid'); var allTabs = this._tabBar.find('.ipsTabs__tab').closest('[data-fileid]'); var newTab = null; // Check if there's unsaved content if( tabParent.attr('data-state') == 'unsaved' && bypass != true ){ ips.ui.alert.show({ type: 'confirm', message: ips.getString('themeUnsavedContent'), icon: 'warn', callbacks: { ok: function () { self._doCloseTab( tab, true ); } } }); return; } // Is this tab active? var active = (tab.attr('aria-selected') === 'true'); // Let the document know what we're up to this.trigger( 'closedTab.templates', { fileID: key }); // Remove the codemirrrrr element & instance delete( this._cmInstances[ key ] ); // Find the next or prev tab, if this tab is active, and switch to it if( active && allTabs.length > 1 ){ if( allTabs.first().attr('data-fileid') == tabParent.attr('data-fileid') ){ newTab = tabParent.next(); } else { newTab = tabParent.prev(); } } if( newTab ){ newTab.click(); } // Close the tab ips.utils.anim.go('fadeOutDown fast', tabParent) .done( function () { tabParent.remove(); self._recalculatePanelWrapper(); }); // Remove the panel this._tabContent.find('[data-fileid="' + key + '"]').remove(); }, /** * Tab widget has indicated that the user has changed tab * If there's a file ID, trigger a new event with it, to enable the file listing to highlight it * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ changedTab: function (e, data) { var tab = data.tab; if( !_.isUndefined( tab.closest('[data-fileid]').attr('data-fileid') ) ){ this.trigger( 'fileSelected.templates', { fileID: tab.closest('[data-fileid]').attr('data-fileid') }); } this._updateToolbar( tab ); }, /** * Updates the toolbar buttons * * @param {element} tab The current tab * @returns {void} */ _updateToolbar: function (tab) { var tabParent = tab.closest('[data-fileid]').attr('data-fileid'); var tabPanel = this._tabContent.find('[data-fileid="' + tabParent + '"]'); var status = tabPanel.attr('data-inherited-value'); var type = tabPanel.attr('data-type'); var revert = this.scope.find('[data-action="revert"]'); var key = tabPanel.attr('data-fileid'); switch( status ){ case 'original': case 'inherit': revert .addClass('ipsButton--disabled') break; case 'custom': revert .html( ips.getString('skin_delete') ) .removeClass('ipsButton--disabled') .attr('data-actionType', 'delete') .show(); break; case 'changed': revert .html( ips.getString('skin_delete') ) .removeClass('ipsButton--disabled') .attr('data-actionType', 'delete') .show(); break; } $('#elTemplateEditor_variables').show(); $('#elTemplateEditor_attributes').hide(); if ( this._cmInstances[ key ] instanceof CodeMirror.MergeView ) { $('[data-ipsmenuvalue="diff"]').addClass('ipsMenu_itemChecked'); } else { $('[data-ipsmenuvalue="diff"]').removeClass('ipsMenu_itemChecked'); } }, /** * Reponds to the openFile event, to open a file * Either switch to it if already open, or hand off to _buildTab to load it * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ openFile: function (e, data) { // Is this file already open? if( !this._tabBar.find('[data-fileid="' + data.meta.key + '"]').length ){ this._buildTab( data.meta ); } else { this._tabBar.find('[data-fileid="' + data.meta.key + '"]').click(); } }, /** * Builds a tab for the file with the given metadata * * @param {object} meta Object of file metadata * @returns {void} */ _buildTab: function (meta) { var self = this; this._tabContent.attr('data-haseditor', 'true'); this.scope.find('#elTemplateEditor_panels_empty').hide(); // Build the actual tab this._tabBar.append( ips.templates.render('templates.editor.newTab', { title: meta.title, fileid: meta.key, location: meta.location, group: meta.group, container: meta.container, id: 'tab_' + meta.key })); // Build the content container this._tabContent.append( ips.templates.render('templates.editor.tabPanel', { fileid: meta.key, name: meta.name, type: meta.type, location: meta.location, container: meta.container, group: meta.group, id: meta.id, inherited: meta.inherited })); // We may need to rejig the tab pane wrap to account for wrapped tabs, // so do that now both tab and panel have been added this._recalculatePanelWrapper(); // Toggle the new tab this._tabBar.find('[data-fileid="' + meta.key + '"]').click(); // Manually set the content area to loading since we aren't using ui.tabbar's load methods this._tabContent.addClass('ipsLoading ipsTabs_loadingContent'); // Load the content ips.getAjax()( this._ajaxURL + '&do=loadTemplate&show=json', { dataType: 'json', data: { 't_container': meta.container, 't_group': meta.group, 't_name': meta.name, 't_key': meta.key, 't_location': meta.location, 't_type': meta.type } }) .done( function (response) { self._postProcessNewTab( response, meta ); }) .always( function () { self._tabContent.removeClass('ipsLoading ipsTabs_loadingContent'); }); }, /** * Update the tab label * * @param {string} key File key (block__foo) * @param {string} title Label Title (Foo) * @return {void} */ _updateTabLabel: function( key, title ) { var span = this._tabBar.find('[data-fileid="' + key + '"] span').clone(); this._tabBar.find('[data-fileid="' + key + '"]').html( title ).append( span ); }, /** * Once tab content has been returned by ajax, this method builds the content of a tab, * and initializes codemirrior for syntax highlighting * * @param {object} response Response JSON object from ajax request * @param {object} meta Object of meta data for the tab being created * @returns {void} */ _postProcessNewTab: function (response, meta) { var content = ips.templates.render('templates.editor.tabContent', { fileid: meta.key, content: response.template_content, variables: response.template_params, description: response.template_desc, title: response.template_title, container: response.template_container, group: response.template_group }); this._tabContent.find('[data-fileid="' + meta.key + '"]').html( content ); this._initCodeMirror( meta.key, meta.type ); }, /** * Initializes CodeMirror on a textarea with the provided key * * @param {string} key Key of the textarea to be turned into codemirrior * @returns {void} */ _initCodeMirror: function (key, type) { var self = this; this._cmInstances[ key ] = CodeMirror.fromTextArea( document.getElementById('editor_' + key ), { mode: (type == 'template' ? 'htmlmixed' : 'css'), lineWrapping: this._editorPreferences['wrap'], lineNumbers: this._editorPreferences['lines'] } ); //this._cmInstances[ key ].setSize( null, this._getTabContentHeight() ); this._cmInstances[ key ].on( 'change', function (doc, cm) { self._setChanged( true, key ); }); }, /** * Sets a tab to 'unsaved' state * * @returns {void} */ _setChanged: function (state, key) { if( state == true ){ // Update 'x' in tab to an unsaved version, then set state on the tab this._tabBar .find('[data-fileid="' + key + '"]') .attr('data-state', 'unsaved') .find('[data-action="closeTab"]') .html( ips.templates.render('templates.editor.unsaved') ); } else { this._tabBar .find('[data-fileid="' + key + '"]') .attr('data-state', 'saved') .find('[data-action="closeTab"]') .html( ips.templates.render('templates.editor.saved') ); } }, /** * Calculates whether the tab bar has wrapped, and if so, resizes the panel wrapper and updates * CodeMirror instances with the new height * * @returns {void} */ _recalculatePanelWrapper: function () { /* // Get height of the tab bar var tabHeight = this._tabBar.outerHeight(); // Set the top value of the panel this._tabContent.css( { top: tabHeight + 'px' } ); // Get the height of it var contentHeight = this._getTabContentHeight(); if ( this._tabContent.find('.cTemplateMergeHeaders').length ) { // We are viewing a diff, so we need to account for this bar too contentHeight -= this._tabContent.find('.cTemplateMergeHeaders').outerHeight(); } // Find all codemirror instances and resize those this._tabContent.find('.CodeMirror').css( { height: contentHeight + 'px' } ); // .. and any merge gap bars so they don't overrun this._tabContent.find('.CodeMirror-merge-gap').css( { height: contentHeight + 'px' } ); this._currentHeight = tabHeight; */ }, /** * Returns references to both the active tab and the active tab panel * * @returns {object} Contains keys 'tab' and 'tabPanel', which are jQuery objects */ _getActiveTab: function () { var toReturn = { tab: null, tabPanel: null }; var tab = this._tabBar.find('[aria-selected="true"]').first(); if( !tab.length ){ return toReturn; } // Get the associated panel toReturn = { tab: tab, tabPanel: this._tabContent.find('[data-fileid="' + tab.attr('data-fileid') + '"]') }; return toReturn; }, /** * Returns the current height of the tab panel wrapper * * @returns {number} */ _getTabContentHeight: function () { var tabContentTop = this._tabContent.offset().top; var windowHeight = $( window ).height(); var infoHeight = $('#elTemplateEditor_info').outerHeight(); this._panelHeight = windowHeight - tabContentTop - infoHeight; return this._panelHeight; } }); }(jQuery, _));]]> a').attr('data-key'); // If the panel isn't actually open, we'll just show it if( !panel.length ){ this._tabBar.find('[data-type="' + type + '"]').click(); } else { var open = this._getOpenNodes( panel ); // Now fetch the new list var url = this._tabBar.find('[data-type]').attr('data-tabURL'); ips.getAjax()( url ) .done( function (response) { panel.html( response ); // Now reopen all the nodes self._openNodes( open, panel, activeItem ); if( data.fileID ){ // Click it panel.find('[data-itemid="' + data.fileID + '"]').click(); } // Let everyone know self.trigger('fileListRefreshed.templates'); }); } }, /** * Opens the nodes provided in the toOpen param * * @param {object} toOpen Object of nodes to open, containing three keys: apps, locations, groups * @returns {void} */ _openNodes: function (toOpen, panel, activeItem) { var selector = []; // Get the locations if( toOpen.locations.length ){ for( var i = 0; i < toOpen.locations.length; i++ ){ selector.push('[data-location="' + toOpen.locations[i] + '"]'); } } // Get groups if( toOpen.groups.length ){ for( var i = 0; i < toOpen.groups.length; i++ ){ var str = '[data-location="' + toOpen.groups[i][0] + '"] '; str += '[data-group="' + toOpen.groups[i][1] + '"]'; selector.push( str ); } } // Now close all branches, then reopen the ones matching our selector panel .find('.cTemplateList_activeBranch') .removeClass('cTemplateList_activeBranch') .addClass('cTemplateList_inactiveBranch') .end() .find( selector.join(',') ) .removeClass('cTemplateList_inactiveBranch') .addClass('cTemplateList_activeBranch'); // Anything to make active? if( activeItem ){ panel .find('[data-key="' + activeItem + '"]') .click(); } }, /** * Returns an object containing the open nodes in the provided panel * * @param {element} panel Panel element to fetch from * @returns {object} Three array keys: apps, locations, groups */ _getOpenNodes: function (panel) { var locations = []; var groups = []; // Fetch all open nodes panel.find('.cTemplateList_activeBranch').each( function (i, item) { var el = $( item ); if( el.attr('data-location') ){ locations.push( el.attr('data-location') ); } if( el.attr('data-group') ){ groups.push( [ el.closest('[data-location]').attr('data-location'), el.attr('data-group') ]); } }); return { locations: locations, groups: groups }; }, /** * The editor controller has indicated that a file tab has been selected * We respond to this event by highlighting the file in the list * * @param {event} e Event object * @returns {void} */ selectFile: function (e, data) { if( data.fileID ){ this._makeActive( data.fileID ); } }, /** * Event handler for clicking a file node in the listing. * Gather metadata from the file, then trigger an event so that the editor controller * can load it. * * @param {event} e Event object * @returns {void} */ openFile: function (e) { e.preventDefault(); var elem = $( e.currentTarget ); // Get meta data for this file var meta = { name: elem.attr('data-name'), key: elem.attr('data-key'), type: elem.closest('[data-type]').attr('data-type'), title: elem.text(), group: elem.closest('[data-group]').attr('data-group'), location: elem.closest('[data-location]').attr('data-location'), id: elem.closest('[data-itemID]').attr('data-itemID'), inherited: elem.closest('[data-inherited-value]').attr('data-inherited-value') }; Debug.log( meta ); this.trigger( 'openFile.templates', { meta: meta }); }, /** * Updates the ID of any element with the old ID * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ updateItemMeta: function (e, data) { if( data.oldID != data.newID ){ this.scope .find('[data-itemID="' + data.oldID + '"]') .attr( 'data-itemID', data.newID ); } /* Update name */ this.scope .find('[data-itemID="' + data.newID + '"]') .attr( 'data-name', data.newTitle ) .html( data.newTitle ); /* Update container */ if ( data.oldContainer != data.newContainer ) { data.fileID = data.newID; // Let everyone know this.trigger( 'modifiedFile.templates', data ); } }, /** * A file's status has changed, so we update it with the new status * * @param {event} e Event object * @param {object} data Event data object * @returns {void} */ fileChangedStatus: function (e, data) { this.scope .find('[data-key="' + data.key + '"]') .attr( 'data-inherited-value', data.status ); }, /** * Finds the provided fileID in the list, highlights it and opens all branches to it * * @param {string} fileID fileID of node to higlight * @returns {void} */ _makeActive: function (fileID) { // Find the file entry var file = this.scope.find('[data-key="' + fileID +'"]'); // Make all others inactive this.scope.find('[data-key]').parent().removeClass('cTemplateList_activeNode'); // Make this one active file.parent().addClass('cTemplateList_activeNode'); // Get all parent nodes, and show them file.parents('li[data-group], li[data-location]').each( function (idx, parent) { if( $( parent ).hasClass('cTemplateList_inactiveBranch') ){ $( parent ) .removeClass('cTemplateList_inactiveBranch') .addClass('cTemplateList_activeBranch') .find('> ul') .show(); } }); }, /** * Returns the currently-selected type being shown (templates or css) * * @returns {string} */ _currentType: function () { if( this._tabBar.find('[data-type="template"]').attr('aria-selected') === "true" ){ return 'template'; } if( this._tabBar.find('[data-type="js"]').attr('aria-selected') === "true" ){ return 'js'; } return 'css'; } }); }(jQuery, _));]]> /** * Invision Community * (c) Invision Power Services, Inc. - https://www.invisioncommunity.com * * ips.templates.variablesDialog.js - Controller for the variables dialog * * Author: Rikki Tissier */ ;( function($, _, undefined){ "use strict"; ips.controller.register('cms.admin.templates.variablesDialog', { initialize: function () { this.on( 'click', 'input[type="submit"]', this.submitChange ); }, /** * Event handler called when the submit button within the dialog is clicked * Fires an event that the editor controller can respond to * * @param {event} e Event object * @returns {void} */ submitChange: function (e) { var key = this.scope.find('[name="_variables_fileid"]').val(); $('[data-fileid="' + key + '"]') .attr('data-state', 'unsaved') .find('[data-action="closeTab"]') .html( ips.templates.render('templates.editor.unsaved') ); this.trigger( 'variablesUpdated.templates', { fileID: this.scope.find('[name="_variables_fileid"]').val(), value: this.scope.find('[data-role="variables"]').val(), title: this.scope.find('[data-role="title"]').val(), description: this.scope.find('[data-role="description"]').val(), container: this.scope.find('[data-role="container"]').val(), group: this.scope.find('[data-role="group"]:not(.ipsHide)').val() }); this.trigger( 'closeDialog' ); } }); }(jQuery, _)); \ {{{contents}}}\ \ "); ips.templates.set( 'templates.media.noItems', "\
\ {{#lang}}mediaEmptyFolder{{/lang}}\
\ "); ips.templates.set( 'templates.media.noSearchResults', "\
\ {{#lang}}mediaNoResults{{/lang}}\
\ ");]]>
{{title}}
\ "); ips.templates.set('templates.editor.tabPanel', " \ \ "); ips.templates.set('templates.editor.tabContent', " \ \ \ \ \ \ "); ips.templates.set('templates.editor.unsaved', " \ \ "); ips.templates.set('templates.editor.saved', " \ \ "); ips.templates.set('templates.editor.pagesDiffHeaders', " \
\
\
{{#lang}}theme_diff_original_header{{/lang}} {{#lang}}theme_diff_original_desc{{/lang}}
\
\
\
{{#lang}}theme_diff_custom_header{{/lang}} {{#lang}}theme_diff_custom_desc{{/lang}}
\
\
\ ");]]> templates common/ips.loader.js common/ui common/utils common controllers underscore jquery mustache Debug.js app.js jquery.js jquery-migrate.js jquery.transform.js linkify.min.js linkify-jquery.min.js