//global sekPreviewLocalized var SekPreviewPrototype = SekPreviewPrototype || {}; ( function( api, $, _ ) { $.extend( SekPreviewPrototype, { cachedElements : { $body : $('body'), $window : $(window) }, initialize: function() { var self = this; // Set the skope_id try { this.skope_id = _.findWhere( _wpCustomizeSettings.czr_new_skopes, { skope : 'local' }).skope_id; } catch( _er_ ) { this.errare('Preview => error when storing the skope_id', _er_ ); return; } // Active UI this.scheduleHighlightActiveLevel(); // The loading icon when a level is refreshed self.setupLoader(); // DOM READY $( function() { self.setupSortable(); self.setupResizable(); self.setupUiHoverVisibility(); self.scheduleUiClickReactions(); self.schedulePanelMsgReactions(); }); // Make sure we don't force a minimum height to empty columns when a section has at least one module // => allow a better previewing experience and more realistic spacing adjustments // The css class .sek-has-modules is also printed server side // @see php SEK_Front_Render::render() self.cachedElements.$body.on('sek-columns-refreshed sek-modules-refreshed', function( evt, params ) { if ( !_.isUndefined( params ) && !_.isUndefined( params.in_sektion ) && $('[data-sek-id="' + params.in_sektion +'"]').length > 0 ) { var $updatedSektion = $('[data-sek-id="' + params.in_sektion +'"]'); $updatedSektion.toggleClass( 'sek-has-modules', $updatedSektion.find('[data-sek-level="module"]').length > 0 ); } }); // Deactivates the links self.deactivateLinks(); self.cachedElements.$body.on([ 'sek-modules-refreshed', 'sek-columns-refreshed', 'sek-section-added', 'sek-level-refreshed', 'sek-edit-module' ].join(' '), function( evt ) { self.deactivateLinks(evt); }); // Send the contextually active locations // 1) on init // 2) and when requested by the control panel // introduced for the level tree, https://github.com/presscustomizr/nimble-builder/issues/359 var sendActiveLocations = function() { var active_locs = [], active_locs_info = [];// <= introduced for better move up/down of sections https://github.com/presscustomizr/nimble-builder/issues/521 $('[data-sek-level="location"]').each( function() { active_locs.push( $(this).data('sek-id') ); active_locs_info.push({ id : $(this).data('sek-id'), is_global : true === $(this).data('sek-is-global-location'), is_header_footer : true === $(this).data('sek-is-header-location') || true === $(this).data('sek-is-footer-location'), // added for https://github.com/presscustomizr/nimble-builder-pro/issues/6 is_header : true === $(this).data('sek-is-header-location'), is_footer : true === $(this).data('sek-is-footer-location') }); }); api.preview.send('sek-active-locations-in-preview', { active_locations : active_locs, active_locs_info : active_locs_info } ); }; api.preview.bind('sek-request-active-locations', sendActiveLocations ); sendActiveLocations(); }, // Fired on initialize() // and on user generated events deactivateLinks : function( evt ) { evt = evt || {}; var _doSafe_ = function() { if ( "yes" === $(this).data('sek-unlinked') ) return; // Several cases : // 1- internal link ( <=> api.isLinkPreviewable(... ) = true ) : we allow navigation with shift + click // 2- extenal link => navigation is disabled. // 3- server disabled links, with href attribute set to "javascript:void(0)", this case is checked isJavascriptProtocol var isJavascriptProtocol = _.isString( $(this)[0].protocol ) && -1 !== $(this)[0].protocol.indexOf('javascript'); // the check on isJavascriptProtocol fixes issue https://github.com/presscustomizr/nimble-builder/issues/255 if ( ! isJavascriptProtocol && api.isLinkPreviewable( $(this)[0] ) ) { $(this).addClass('nimble-shift-clickable'); $(this).data('sek-unlinked', "yes").attr('data-nimble-href', $(this).attr('href') ).attr('href', 'javascript:void(0)'); // remove target="_blank" if enabled by user // @fixes issue https://github.com/presscustomizr/nimble-builder/issues/542 $(this).removeAttr('target'); $(this).on('mouseenter', function() { $(this).attr( 'title', sekPreviewLocalized.i18n['Shift-click to visit the link']); }); $(this).on('mouseleave', function() { $(this).removeAttr( 'title' ); }); $(this).on('click', function(evt) { if ( ! evt.shiftKey ) { return; } evt.preventDefault(); window.location.href = $(this).attr('data-nimble-href'); }); } else { $(this).addClass('nimble-unclickable'); $(this).data('sek-unlinked', "yes").attr('data-nimble-href', $(this).attr('href') ).attr('href', 'javascript:void(0)'); $(this).on('mouseenter', function() { $(this).attr( 'title', isJavascriptProtocol ? sekPreviewLocalized.i18n['Link deactivated while previewing'] : sekPreviewLocalized.i18n['External links are disabled when customizing']); }).on('mouseleave', function() { $(this).removeAttr( 'title' ); }); $(this).on('click', function(evt) { evt.preventDefault(); }); } }; this.cachedElements.$body.find('[data-sek-level="module"]').each( function() { $(this).find('a').each( function(){ try { _doSafe_.call( $(this) ); } catch(er) { api.errare( '::deactivateLinks => error ', er ); } }); }); }, // Hightlight the currently level in the preview, corresponding to the active ui in the panel // // When a new ui is generated, the activeLevelUI is set @see ::schedulePanelMsgReactions() // When the level options are modidied ( 'sek-refresh-stylesheet', 'sek-refresh-level' ), scheduleHighlightActiveLevel : function() { var self = this; // Stores the currently edited level // aka the one on which the user clicked to edit it this.activeLevelUI = new api.Value(''); this.activeLevelEl = new api.Value(null); this.activeUIChangedRecently = new api.Value( false ); this.activeLevelUI.bind( function( to, from ) { var $activeLevel = $('[data-sek-id="' + to +'"]'), $previousActiveLevel = $('[data-sek-id="' + from +'"]'); if ( $activeLevel.length > 0 ) { $activeLevel.addClass('sek-active-ui sek-highlight-active-ui'); // cache $activeLevel self.activeLevelEl( $activeLevel ); } if ( $previousActiveLevel.length > 0 ) { $previousActiveLevel.removeClass('sek-active-ui sek-highlight-active-ui'); } self.activeUIChangedRecently( Date.now() ); }); // MAY 2020 : added to focus on the edited element // updated in self.activeLevelUI() and self.schedulePanelMsgReactions() self.activeLevelEl.bind( function($el) { // scroll to focus on the active element // but only if element is offscreen, otherwise clicking on a section edit UI for example, will make it move to the top, which is annoying if ( _.isObject($el) && $el.length > 0 && !nb_.isInScreen( $el[0]) ) { // https://caniuse.com/#search=scrollIntoView try{ $el[0].scrollIntoView(); } catch(er) { self.errare('activeLevelEl error', er ); } } }); // apiParams : { // action : 'sek-refresh-level', // id : params.uiParams.id, // level : params.uiParams.level // }, // skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ), _.each( [ 'sek-refresh-stylesheet', 'sek-refresh-level' ], function( msgId ) { api.preview.bind( msgId, function( params ) { self.activeUIChangedRecently( Date.now() ); }); }); // This api.Value() is updated with Date.now() when something just happened and false after 3000 ms of inactivity // so we can always react to changes, and refresh the timeOut this.activeUIChangedRecently.bind( function( hasChangedRecently ) { var $newActiveLevel = $('[data-sek-id="' + self.activeLevelUI() +'"]'); // remove the highlight class if it was previously set to another level if ( $('.sek-highlight-active-ui').length ) { $('.sek-highlight-active-ui').removeClass('sek-highlight-active-ui'); } if ( $newActiveLevel.length > 0 ) { $newActiveLevel.toggleClass( 'sek-highlight-active-ui', false !== hasChangedRecently ); } clearTimeout( $.data( this, '_ui_change_timer_') ); $.data( this, '_ui_change_timer_', setTimeout(function() { self.activeUIChangedRecently( false ); }, 3000 ) ); }); } });//$.extend() })( wp.customize, jQuery, _ );//global sekPreviewLocalized var SekPreviewPrototype = SekPreviewPrototype || {}; ( function( api, $, _ ) { $.extend( SekPreviewPrototype, { // Fired on Dom Ready, in ::initialize() setupSortable: function() { var self = this; this.sortableDefaultParams = { //forcePlaceholderSize: true, //handle: '.fa-arrows-alt',<= set by level placeholder: "sortable-placeholder", over: function( event, ui ) {}, cursorAt: { top:0, left: 0 },//@fixes https://github.com/presscustomizr/nimble-builder/issues/114 tolerance: "pointer",//@fixes https://github.com/presscustomizr/nimble-builder/issues/114 }; // SEKTIONS // On dom ready $('[data-sek-level="location"]').each( function() { self.makeSektionsSortableInLocation( $(this).data('sek-id') ); }); // Schedule with delegation self.cachedElements.$body.on( 'sek-section-added sek-level-refreshed sek-location-refreshed', '[data-sek-level="location"]', function( evt, params ) { self.makeSektionsSortableInLocation( $(this).data('sek-id') ); }); // COLUMNS // On dom ready $('[data-sek-level="location"]').each( function() { $(this).find( '[data-sek-level="section"]' ).each( function() { self.makeColumnsSortableInSektion( $(this).data('sek-id') ); }); }); // Schedule with delegation self.cachedElements.$body.on( 'sek-columns-refreshed sek-section-added', '[data-sek-level="section"]', function( evt ) { self.makeColumnsSortableInSektion( $(this).data('sek-id') ); }); // this case occurs when moving a section from one location to another for example self.cachedElements.$body.on( 'sek-level-refreshed', '[data-sek-level="location"]', function( evt, params ) { $(this).find( '[data-sek-level="section"]' ).each( function() { self.makeColumnsSortableInSektion( $(this).data('sek-id') ); }); }); // MODULES // On dom ready $('[data-sek-level="location"]').each( function() { $(this).find( '[data-sek-level="column"]' ).each( function() { self.makeModulesSortableInColumn( $(this).data('sek-id') ); }); }); // Schedule with delegation self.cachedElements.$body.on( 'sek-modules-refreshed', '[data-sek-level="column"]', function() { self.makeModulesSortableInColumn( $(this).data('sek-id') ); }); self.cachedElements.$body.on( 'sek-columns-refreshed', '[data-sek-level="section"]', function() { $(this).find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ).each( function() { self.makeModulesSortableInColumn( $(this).data('sek-id') ); }); }); // this case occurs when moving a section from one location to another for example self.cachedElements.$body.on( 'sek-level-refreshed', '[data-sek-level="location"]', function( evt, params ) { $(this).find( '[data-sek-level="column"]' ).each( function() { self.makeModulesSortableInColumn( $(this).data('sek-id') ); }); }); self.cachedElements.$body.on( 'sek-section-added', '[data-sek-level="location"]', function( evt, params ) { $(this).find( '[data-sek-level="column"]' ).each( function() { self.makeModulesSortableInColumn( $(this).data('sek-id') ); }); }); // added to fix impossibility to move an already inserted module in a freshly added multicolumn section // @see https://github.com/presscustomizr/nimble-builder/issues/538 self.cachedElements.$body.on( 'sek-location-refreshed', '[data-sek-level="location"]', function( evt, params ) { $(this).find( '[data-sek-level="column"]' ).each( function() { self.makeModulesSortableInColumn( $(this).data('sek-id') ); }); }); // NESTED SEKTIONS // $('.sek-column-inner', '[data-sek-level="section"]').children( '[data-sek-level="section"]' ).each( function() { // defaults = $.extend( true, {}, self.sortableDefaultParams ); // $(this).sortable( _.extend( defaults, { // handle : '.sek-move-nested-section', // connectWith: ".sek-column-inner, [data-sek-level="location"]", // start: function( event, ui ) { // // store the startOrder // $('[data-sek-level="location"]').children( '[data-sek-level="section"]' ).each( function() { // startOrder.push( $(this).data('sek-id') ); // }); // //console.log('column moved from', from_sektion, ui ); // }, // stop : function( event, ui ) { // newOrder = []; // // Restrict to the direct children // $('[data-sek-level="location"]').children( '[data-sek-level="section"]' ).each( function() { // newOrder.push( $(this).data('sek-id') ); // }); // // api.preview.send( 'sek-move', { // // id : ui.item.data('sek-id'), // // level : 'section', // // newOrder : newOrder // // }); // } // })); // }); // // $('.sek-sektion-inner').each( function() { // $(this).sortable({ // connectWith: ".sek-sektion-inner" // }).disableSelection(); // }); return this; },//setupSortable() // Print all level ui // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/521 printAllLevelsUi : function() { var self = this; self.cachedElements.$body.find( '[data-sek-level]' ).each( function() { self.printLevelUI( $(this) ); $(this).find('.sek-dyn-ui-wrapper').stop( true, true ).show(); }); }, makeSektionsSortableInLocation : function( locationId ) { var self = this; var from_location, to_location, startOrder = [], newOrder = [], defaults; if ( true === $('[data-sek-id="' + locationId +'"]').data('sek-is-global-location') ) return; defaults = $.extend( true, {}, self.sortableDefaultParams ); $('[data-sek-id="' + locationId +'"]').sortable( _.extend( defaults, { items: '[data-sek-level="section"]', //handle : '.sek-move-section, .sek-section-dyn-ui > .sek-dyn-ui-location-type',//@fixes https://github.com/presscustomizr/nimble-builder/issues/153 handle : '.sek-move-section', connectWith : '[data-sek-is-global-location="false"]', placeholder: { element: function(currentItem) { return $('

' + sekPreviewLocalized.i18n['Insert here'] + '

')[0]; }, update: function(container, p) { return; } }, start: function( event, ui ) { self.cachedElements.$body.addClass('sek-moving-section'); self.isDraggingElement = true; $sourceLocation = ui.item.closest('[data-sek-level="location"]'); from_location = $sourceLocation.data('sek-id'); // Print all level ui // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/521 self.printAllLevelsUi(); // store the startOrder // $sourceLocation.children( '[data-sek-level="section"]' ).each( function() { // startOrder.push( $(this).data('sek-id') ); // }); }, stop : function( event, ui ) { self.cachedElements.$body.removeClass('sek-moving-section'); self.isDraggingElement = false; newOrder = []; $targetLocation = ui.item.closest('[data-sek-level="location"]'); to_location = $targetLocation.data('sek-id'); // Restrict to the direct children $targetLocation.children( '[data-sek-level="section"]' ).each( function() { newOrder.push( $(this).data('sek-id') ); }); api.preview.send( 'sek-move', { id : ui.item.data('sek-id'), level : 'section', newOrder : newOrder, from_location : from_location, to_location : to_location }); }, over : function( event, ui ) { ui.placeholder.addClass('sek-sortable-section-over'); }, out : function( event, ui ) { ui.placeholder.removeClass('sek-sortable-section-over'); } })); },//makeSektionsSortableInLocation // Instantiate sortable for a given column Id // Columns are a little more complex because we want to emulate the future layouts when moving a column from section to section // for that, we need to compute the number of columns and play with css classes. // During this process, we use two $.data to store informations : // 'sek-is-sender' => tells us if this is the sektion from which we started to drag a column // '_sortable_columns_css_classes_' => stores the current and future css classes makeColumnsSortableInSektion : function( sektionId ) { var self = this, defaults = $.extend( true, {}, self.sortableDefaultParams ), $fromLocation, is_global_from_location, $toLocation, is_global_to_location, startOrder = [], newOrder = [], $sortableCandidate = $( '[data-sek-id="' + sektionId + '"]').find('.sek-sektion-inner').first(), getCurrentAndNextColNumberClasses = function( args ) { args = $.extend( { forTarget : true }, args || {} ); if ( ! _.isEmpty( $(this).data('_sortable_columns_css_classes_' ) ) ) return $(this).data('_sortable_columns_css_classes_' ); var $targetSektion = $(this).closest('[data-sek-level="section"]'), $columnsInTargetSektion = $targetSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ), currentColnumber = $columnsInTargetSektion.length, currentColCSSSuffix = Math.floor( 100/currentColnumber ); // the future columns number is different for the source and target sektions. var nextColNumber; if ( true === args.forTarget ) { nextColNumber = 12 < ( currentColnumber + 1 ) ? 12 : currentColnumber + 1; } else { nextColNumber = 1 > ( currentColnumber - 1 ) ? 1 : currentColnumber -1; } // this css suffix is consistent with the one written server side // @see SEK_Front_Render::render() case 'column' var nextColCSSSuffix = Math.floor( 100/nextColNumber ), current_columns_css_class = 'sek-col-' + currentColCSSSuffix, next_columns_css_class = 'sek-col-' + nextColCSSSuffix, _classes_ = { current : current_columns_css_class , next : next_columns_css_class }; $(this).data('_sortable_columns_css_classes_', _classes_ ); return _classes_; }, cleanOnStop = function() { $( '[data-sek-level="section"]').find('.sek-sektion-inner').each( function() { $(this).data( 'sek-is-sender', null ).data('_sortable_columns_css_classes_', null ); }); }; // if ( $sortableCandidate.children('[data-sek-level="column"]').length > 11 ) { // self.errare('12 COLUMNS'); // return; // } $sortableCandidate.sortable( _.extend( defaults, { //handle : '.sek-move-column, .sek-column-dyn-ui > .sek-dyn-ui-location-type',//@fixes https://github.com/presscustomizr/nimble-builder/issues/153 handle : '.sek-move-column', connectWith: ".sek-sektion-inner", over : function( event, ui ) { var $targetSektion = $(this).closest('[data-sek-level="section"]'), $columnsInTargetSektion = $targetSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ), _classes_; if ( true !== $(this).data('sek-is-sender' ) ) { _classes_ = getCurrentAndNextColNumberClasses.call( $(this) ); if ( ! _.isEmpty( _classes_ ) ) { $columnsInTargetSektion.each( function() { $(this).removeClass( _classes_.current ).addClass( _classes_.next ); }); } } else { _classes_ = getCurrentAndNextColNumberClasses.call( $(this), { forTarget : false } ); if ( ! _.isEmpty( _classes_ ) ) { $columnsInTargetSektion.each( function() { $(this).addClass( _classes_.current ).removeClass( _classes_.next ); }); } } }, out : function( event, ui ) { var $outedSektion = $(this).closest('[data-sek-level="section"]'), $columnsInOutedSektion = $outedSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ), _classes_; if ( true !== $(this).data('sek-is-sender' ) ) { _classes_ = getCurrentAndNextColNumberClasses.call( $(this) ); if ( ! _.isEmpty( _classes_ ) ) { $columnsInOutedSektion.each( function() { $(this).addClass( _classes_.current ).removeClass( _classes_.next ); }); } } else { _classes_ = getCurrentAndNextColNumberClasses.call( $(this), { forTarget : false } ); if ( ! _.isEmpty( _classes_ ) ) { $columnsInOutedSektion.each( function() { $(this).removeClass( _classes_.current ).addClass( _classes_.next ); }); } } }, remove : function( event, ui ) { $toLocation = ui.item.closest('[data-sek-level="location"]'); to_location = $toLocation.data( 'sek-id'); is_global_to_location = true === $toLocation.data('sek-is-global-location'); var _isCrossSkope = is_global_from_location !== is_global_to_location, _isCrossLocation = to_location != from_location, _isGlobalToGlobal = true === is_global_from_location && true === is_global_to_location; // Not possible to drag from a local location to a global // Not possible to drag from a global header to a global footer if ( _isCrossSkope || ( _isGlobalToGlobal && _isCrossLocation ) ) { api.preview.send( 'sek-notify', { message : sekPreviewLocalized.i18n["Moving elements between global and local sections is not allowed."] }); return false; } $targetSektionCandidate = ui.item.closest('[data-sek-level="section"]'); if ( $targetSektionCandidate.length > 0 && $targetSektionCandidate.find('.sek-sektion-inner').first().children('[data-sek-level="column"]').length > 12 ) { api.preview.send( 'sek-notify', { message : sekPreviewLocalized.i18n["You've reached the maximum number of columns allowed in this section."] }); return false; } else { return true; } }, start: function( event, ui ) { // Store the following for the "remove" callback $fromLocation = ui.item.closest('[data-sek-level="location"]'); from_location = $fromLocation.data( 'sek-id'); is_global_from_location = true === $fromLocation.data('sek-is-global-location'); // Always reset startOrder and newOrder startOrder = []; newOrder = []; //$('.sek-column-inner').css( {'min-height' : '20px'}); // Set source from_sektion = ui.item.closest('[data-sek-level="section"]').data( 'sek-id'); // store the startOrder ui.item.closest('[data-sek-level="section"]').find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ).each( function() { startOrder.push( $(this).data('sek-id') ); }); $(this).data('sek-is-sender', true ); if ( _.isEmpty( startOrder ) ) { self.errare( 'column sortable => startOrder should not be empty' ); return; } // Print all level ui // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/553 self.printAllLevelsUi(); }, stop : function( event, ui ) { // set destination $targetSektion = ui.item.closest('[data-sek-level="section"]'); to_sektion = $targetSektion.data( 'sek-id'); $targetSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ).each( function() { newOrder.push( $(this).data('sek-id') ); }); var $stopSektion = $(this).closest('[data-sek-level="section"]'), $columnsInstopSektion = $stopSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ), _classes_; if ( true !== $(this).data('sek-is-sender' ) ) { _classes_ = getCurrentAndNextColNumberClasses.call( $(this) ); if ( ! _.isEmpty( _classes_ ) ) { $columnsInstopSektion.each( function() { $(this).removeClass( _classes_.current ).addClass( _classes_.next ); }); } } else { _classes_ = getCurrentAndNextColNumberClasses.call( $(this), { forTarget : false } ); if ( ! _.isEmpty( _classes_ ) ) { $columnsInstopSektion.each( function() { $(this).addClass( _classes_.current ).removeClass( _classes_.next ); }); } } cleanOnStop(); if ( _.isEmpty( newOrder ) ) { self.errare( 'column sortable => newOrder should not be empty' ); return; } // don't send anything if the source and target columns are the same, and the order is unchanged if ( _.isEqual( newOrder, startOrder ) && to_sektion === from_sektion ) { if ( sekPreviewLocalized.isDevMode ) { self.errare( 'preview => makeModulesSortableInColumn => start and stop positions are identical' ); } return; } api.preview.send( 'sek-move', { id : ui.item.data('sek-id'), level : 'column', newOrder : newOrder, from_sektion : from_sektion, to_sektion : to_sektion }); // inform the parent section that it's been refreshed //=> will be listened to by columns to re-instantiate sortable, resizable $('div[data-sek-id="' + from_sektion + '"]' ).trigger('sek-level-refreshed'); if ( from_sektion !== to_sektion ) { $('div[data-sek-id="' + to_sektion + '"]' ).trigger('sek-level-refreshed'); } } })); },//self.makeColumnsSortableInSektion // Instantiate sortable for a given column Id makeModulesSortableInColumn : function( columnId ) { var from_sektion, to_sektion, from_column, to_column, startOrder = [], newOrder = [], $targetSektion, $targetColumn, defaults, $fromLocation, is_global_from_location, $toLocation, is_global_to_location; var self = this; defaults = $.extend( true, {}, self.sortableDefaultParams ); // Restrict to the .sek-column-inner for this very column id with first() $( '[data-sek-id="' + columnId + '"]').find('.sek-column-inner').first().sortable( _.extend( defaults, { //handle : '.sek-move-module, .sek-module-dyn-ui > .sek-dyn-ui-location-type .sek-dyn-ui-level-type',//@fixes https://github.com/presscustomizr/nimble-builder/issues/153 handle : '.sek-move-module', connectWith: ".sek-column-inner", over : function( event, ui ) { // Hide the module placeholder while overing, when the column is empty // @see css rule .sek-sortable-overing > .sek-no-modules-column { display: none; } $('[data-sek-level="location"]').find('.sek-sortable-overing').each( function() { $(this).removeClass('sek-sortable-overing'); }); $( event.target ).addClass('sek-sortable-overing'); }, remove : function( event, ui ) { $toLocation = ui.item.closest('[data-sek-level="location"]'); to_location = $toLocation.data( 'sek-id'); is_global_to_location = true === $toLocation.data('sek-is-global-location'); var _isCrossSkope = is_global_from_location !== is_global_to_location, _isCrossLocation = to_location != from_location, _isGlobalToGlobal = true === is_global_from_location && true === is_global_to_location; // Not possible to drag from a local location to a global // Not possible to drag from a global header to a global footer if ( _isCrossSkope || ( _isGlobalToGlobal && _isCrossLocation ) ) { api.preview.send( 'sek-notify', { message : sekPreviewLocalized.i18n["Moving elements between global and local sections is not allowed."] }); return false; } else { return true; } }, start: function( event, ui ) { // Store the following for the "remove" callback $fromLocation = ui.item.closest('[data-sek-level="location"]'); from_location = $fromLocation.data( 'sek-id'); is_global_from_location = true === $fromLocation.data('sek-is-global-location'); // Always reset startOrder and newOrder startOrder = []; newOrder = []; self.cachedElements.$body.addClass( 'sek-dragging-element' ); //$('.sek-column-inner').css( {'min-height' : '20px'}); // Set source from_column = ui.item.closest('[data-sek-level="column"]').data( 'sek-id'); from_sektion = ui.item.closest('[data-sek-level="section"]').data( 'sek-id'); // store the startOrder ui.item.closest('[data-sek-level="column"]').find('.sek-column-inner').first().children( '[data-sek-level="module"]' ).each( function() { startOrder.push( $(this).data('sek-id') ); }); if ( _.isEmpty( startOrder ) ) { self.errare( 'makeModulesSortableInColumn => startOrder should not be empty' ); return; } // Print all level ui // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/521 self.printAllLevelsUi(); }, stop : function( event, ui ) { // set destination $targetColumn = ui.item.closest('[data-sek-level="column"]'); to_column = $targetColumn.data( 'sek-id'); $targetColumn.find('.sek-column-inner').first().children( '[data-sek-id]' ).each( function() { newOrder.push( $(this).data('sek-id') ); }); if ( _.isEmpty( newOrder ) ) { self.errare( 'makeModulesSortableInColumn => newOrder should not be empty' ); return; } // don't send anything if the source and target columns are the same, and the order is unchanged if ( _.isEqual( newOrder, startOrder ) && to_column === from_column ) { self.errare( 'preview => makeModulesSortableInColumn => start and stop positions are identical' ); return; } api.preview.send( 'sek-move', { id : ui.item.data('sek-id'), level : 'module', newOrder : newOrder, from_column : from_column, to_column : to_column, from_sektion : from_sektion, to_sektion : ui.item.closest('[data-sek-level="section"]').data( 'sek-id') }); // Clean some css classes self.cachedElements.$body.removeClass( 'sek-dragging-element' ); } })); $( '[data-sek-id="' + columnId + '"]').addClass('sek-module-sortable-setup'); },//makeModulesSortableInColumn });//$.extend() })( wp.customize, jQuery, _ );//global sekPreviewLocalized var SekPreviewPrototype = SekPreviewPrototype || {}; ( function( api, $, _ ) { $.extend( SekPreviewPrototype, { // Fired on Dom Ready, in ::initialize() setupResizable : function() { var self = this; $('.sektion-wrapper').find( 'div[data-sek-level="section"]' ).each( function() { self.maybeMakeColumnResizableInSektion.call( this ); }); // Delegate instantiation when a level markup is refreshed // Let the event bubble up to the location, and then visit all children section to maybe re-instantiate resizable // @fixes https://github.com/presscustomizr/nimble-builder/issues/165 self.cachedElements.$body.on( 'sek-level-refreshed sek-modules-refreshed sek-columns-refreshed sek-section-added sek-location-refreshed', '[data-sek-level="location"]', function( evt ) { $(this).find('[data-sek-level="section"]').each( function() { self.maybeMakeColumnResizableInSektion.call( this ); }); } ); return this; },//setupResizable() // this is the parent section jQuery object maybeMakeColumnResizableInSektion : function() { var self = this, $parentSektion, parentSektionId, parentSektionWidth, $resizedColumn, resizedColumnWidthInPercent, //calculate the number of column in this section, excluding the columns inside nested sections if any colNumber, $sisterColumn, isLastColumn; // We won't fire resizable for single column sektions var $directColumnChildren = $(this).find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ); if ( 2 > $directColumnChildren.length ) return; var $lastCol = $(this).find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).last(); $directColumnChildren.not($lastCol).each( function() { $(this).resizable({ // handles: { 'e': '.ui-resizable-e', 'w': '.ui-resizable-w' }, resize : function( event, ui ) { $('.sektion-wrapper').data('sek-resizing-columns', true ); }, start : function( event, ui ) { $parentSektion = ui.element.closest('div[data-sek-level="section"]'); parentSektionId = $parentSektion.data('sek-id'); parentSektionWidth = $parentSektion.find('.sek-sektion-inner')[0].getBoundingClientRect().width; //calculate the number of column in this section, excluding the columns inside nested sections if any colNumber = $parentSektion.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length; // Resizable should not have been instantiated anyway if ( 2 > colNumber ) return; $resizedColumn = ui.element.closest('div[data-sek-level="column"]'); if ( 1 > $resizedColumn.length ) { throw new Error( 'ERROR => resizable => No valid level dom element found' ); } isLastColumn = $resizedColumn.index() + 1 == colNumber; // Assomption : RTL user. LTR should be implemented also. // If parent section has at least 2 columns, the sister column is the one on the right if not in last position. On the left if last. $sisterColumn = isLastColumn ? $resizedColumn.prev() : $resizedColumn.next(); // Implement a global state value() $('.sektion-wrapper').data('sek-resizing-columns', true ); // auto set to false after a moment. _.delay( function() { $('.sektion-wrapper').data('sek-resizing-columns', false ); }, 3000 ); }, stop : function( event, ui ) { // console.log('ON RESIZE STOP', event, ui, $resizedColumn ); // console.log('colNumber', colNumber, '[data-sek-id="' + parentSektionId +'"] > .sek-sektion-inner' ); // Skip the case when there's only one column // Resizable should not have been instantiated anyway if ( 2 > colNumber ) return; if ( 1 > $resizedColumn.length ) { throw new Error( 'ERROR => resizable => No valid level dom element found' ); } // Reset the automatic inline style $resizedColumn.css({ width : '', height: '' }); resizedColumnWidthInPercent = ( ( parseFloat( ui.size.width ) / parseFloat( parentSektionWidth ) ) * 100 ).toFixed(3); api.preview.send( 'sek-resize-columns', { action : 'sek-resize-columns', level : $resizedColumn.data( 'sek-level' ), in_sektion : $parentSektion.data('sek-id'), id : $resizedColumn.data('sek-id'), resized_column : $resizedColumn.data('sek-id'), sister_column : $sisterColumn.data('sek-id'), resizedColumnWidthInPercent : resizedColumnWidthInPercent, col_number : colNumber }); $('.sektion-wrapper').data('sek-resizing-columns', false ); }, helper: "ui-resizable-helper", handles :'e' });//$(this).resizable({}) // Add a resizable icon in the handle // revealed on section hovering @see sek-preview.css var $column = $(this); _.delay( function() { var $resizableHandle = $column.find('.ui-resizable-handle'); if ( $resizableHandle.find('.fa-arrows-alt-h').length < 1 ) { $column.find('.ui-resizable-handle').append(''); } }, 500 ); });//$directColumnChildren.each() } });//$.extend() })( wp.customize, jQuery, _ ); //global sekPreviewLocalized var SekPreviewPrototype = SekPreviewPrototype || {}; ( function( api, $, _ ) { $.extend( SekPreviewPrototype, { printLevelUI : function( $el ) { var self = this; var tmpl, level, params, $levelEl; if ( _.isUndefined( $el ) || $el.length < 1 ) { self.errare('sekPreview::printeLevelUI => invalid level element => ', $el ); } level = $el.data('sek-level'); // we don't print a ui for locations if ( 'location' == level ) return; $levelEl = $el; // stop here if the .sek-dyn-ui-wrapper is already printed for this level AND is not being faded out. // if ( $levelEl.children('.sek-dyn-ui-wrapper').length > 0 && true !== $levelEl.data( 'UIisFadingOut' ) ) // return; if ( $levelEl.children('.sek-dyn-ui-wrapper').length > 0 ) return; var levelRect = $levelEl[0].getBoundingClientRect(), levelType = $levelEl.data('sek-level'); // Adapt the size of the UI icons and text in narrow containers $levelEl.toggleClass( 'sek-shrink-my-ui', levelRect.width && levelRect.width < ( 'section' === levelType ? 350 : ( 'column' === levelType ? 300 : 200 ) ) ); params = { id : $levelEl.data('sek-id'), level : levelType }; switch ( level ) { case 'section' : //$el = $('.sektion-wrapper').find('[data-sek-id="' + id + '"]'); // Let's prepare the is_last and is_first params that we are going to send to the js template // => this will determine which up / down arrow to display in the UI menu for moving a section var $parentLocation = $levelEl.closest('div[data-sek-level="location"]'), $parentColumn = $levelEl.closest('div[data-sek-level="column"]'), $sectionCollection, _is_last_section, _is_first_section, _is_nested = true === $levelEl.data('sek-is-nested'); // information about first and last section is used when rendering the up / down moving arrows if ( _is_nested ) { if ( $parentColumn.length > 0 ) { $sectionCollection = $parentColumn.find('.sek-column-inner').first().children( 'div[data-sek-level]' ); _is_last_section = $sectionCollection.length == $levelEl.index() + 1; _is_first_section = 0 === $levelEl.index(); } } else { if ( $parentLocation.length > 0 ) { $sectionCollection = $parentLocation.children( 'div[data-sek-level="section"]' ); _is_last_section = $sectionCollection.length == $levelEl.index() + 1; _is_first_section = 0 === $levelEl.index(); } } params = _.extend( params, { is_nested : _is_nested, can_have_more_columns : $levelEl.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length < 12, is_global_location : true === $parentLocation.data('sek-is-global-location'), is_last_section_in_parent : _is_last_section, is_first_section_in_parent : _is_first_section, is_header_location : true === $parentLocation.data('sek-is-header-location'), is_footer_location : true === $parentLocation.data('sek-is-footer-location') }); break; case 'column' : var $parent_sektion = $levelEl.closest('div[data-sek-level="section"]'); params = _.extend( params, { parent_can_have_more_columns : $parent_sektion.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length < 12, parent_is_single_column : $parent_sektion.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length < 2, parent_is_last_allowed_nested : true === $parent_sektion.data('sek-is-nested'), has_nested_section : $levelEl.find('[data-sek-is-nested="true"]').length > 0 }); break; case 'module' : var module_name = self.getRegisteredModuleProperty( $levelEl.data('sek-module-type'), 'name' ); params = _.extend( params, { module_name : 'not_set' != module_name ? module_name : '' }); break; } // don't display the column and module ui when resizing columns if ( true === $('.sektion-wrapper').data('sek-resizing-columns') && _.contains( ['column', 'module'], level ) ) { return; } tmpl = self.parseTemplate( '#sek-dyn-ui-tmpl-' + level ); $.when( $levelEl.prepend( tmpl( params ) ) ).done( function() { $levelEl.find('.sek-dyn-ui-wrapper').stop( true, true ).fadeIn( { duration : 150, complete : function() {} } ); }); },//printLevelUI() // Fired on Dom Ready, in ::initialize() setupUiHoverVisibility : function() { var self = this; var tmpl, level, params, $levelEl; var removeLevelUI = function() { $levelEl = $(this); if ( $levelEl.children('.sek-dyn-ui-wrapper').length < 1 ) return; // when PHP constant NIMBLE_IS_PREVIEW_UI_DEBUG_MODE is true, the levels UI in the preview are not being auto removed, so we can inspect the markup and CSS if ( sekPreviewLocalized.isPreviewUIDebugMode || self.isDraggingElement ) return; //stores the status of 200 ms fading out. => will let us know if we can print again when moving the mouse fast back and forth between two levels. $levelEl.data( 'UIisFadingOut', true );//<= we need to store a fadingOut status to not miss a re-print in case of a fast moving mouse $levelEl.children('.sek-dyn-ui-wrapper').stop( true, true ).fadeOut( { duration : 150, complete : function() { $(this).remove(); $levelEl.data( 'UIisFadingOut', false ); } }); };//removeLevelUI var removeAddContentButtons = function() { self.cachedElements.$body.stop( true, true ).find('.sek-add-content-button').each( function() { $(this).fadeOut( { duration : 200, complete : function() { $(this).remove(); } }); }); }; // clean add content buttons on section insertion // solves the problem of button not being rendered in some case // @see https://github.com/presscustomizr/nimble-builder/issues/545 self.cachedElements.$body.on( 'sek-location-refreshed sek-section-added sek-level-refreshed', '[data-sek-level="location"]', function( evt, params ) { removeAddContentButtons(); }); // UI MENU // React to click and mouse actions. Uses delegation. // + schedule auto collapse after n seconds of ui inactivity var autoCollapser = function() { var $menu = $(this); clearTimeout( $menu.data('_toggle_ui_menu_') ); $menu.data( '_toggle_ui_menu_', setTimeout(function() { setClassesAndVisibilities.call( $menu ); }, 10000 ) ); }, setClassesAndVisibilities = function( expand ) { var $menu = $(this), $levelTypeAndMenuWrapper = $(this).closest('.sek-dyn-ui-location-type'), $dynUiWrapper = $menu.closest( '.sek-dyn-ui-wrapper').find('.sek-dyn-ui-inner'); if ( true === expand ) { $menu.removeClass('sek-collapsed'); $dynUiWrapper.addClass('sek-is-expanded'); $levelTypeAndMenuWrapper.hide(); } else { $menu.addClass('sek-collapsed'); $dynUiWrapper.removeClass('sek-is-expanded'); $levelTypeAndMenuWrapper.show(); } }; self.cachedElements.$body.on( 'click', '.sek-dyn-ui-location-inner', function( evt ) { var $menu = $(this).find('.sek-dyn-ui-hamb-menu-wrapper'), $parentSection = $(this).closest('[data-sek-level="section"]'); // Close all other expanded ui menu of the column $parentSection.find('.sek-dyn-ui-hamb-menu-wrapper').each( function() { setClassesAndVisibilities.call( $(this) ); }); // expand the ui menu of the clicked level setClassesAndVisibilities.call( $menu, true ); // schedule autocollapsing autoCollapser.call( $menu ); }); // maintain expanded as long as it's being hovered self.cachedElements.$body.on( 'mouseenter mouseover mouseleave', '.sek-dyn-ui-wrapper', _.throttle( function( evt ) { var $menu = $(this).find('.sek-dyn-ui-hamb-menu-wrapper'); if ( _.isUndefined( $menu.data('_toggle_ui_menu_') ) || $menu.hasClass('sek-collapsed') ) return; if ( $menu.length > 0 ) { autoCollapser.call( $menu ); } }, 50 ) ); // minimize on click // solves the problem of a level ui on top of another one // @ee https://github.com/presscustomizr/nimble-builder/issues/138 self.cachedElements.$body.on( 'click', '.sek-minimize-ui', function( evt ) { $(this).closest('.sek-dyn-ui-location-type').slideToggle('fast'); }); // Ui for the WP content. // Generated when is_singular() only // @see SEK_Front::render() var $wpContentEl; self.cachedElements.$body.on( 'mouseenter', '.sek-wp-content-wrapper', function( evt ) { $wpContentEl = $(this); // stop here if the .sek-dyn-ui-wrapper is already printed for this level AND is not being faded out. if ( $wpContentEl.children('.sek-dyn-ui-wrapper').length > 0 && true !== $wpContentEl.data( 'UIisFadingOut' ) ) return; tmpl = self.parseTemplate( '#sek-dyn-ui-tmpl-wp-content'); $.when( $wpContentEl.prepend( tmpl( {} ) ) ).done( function() { $wpContentEl.find('.sek-dyn-ui-wrapper').stop( true, true ).fadeIn( { duration : 150, complete : function() {} } ); }); }).on( 'mouseleave', '.sek-wp-content-wrapper', function( evt ) { $(this).data( 'UIisFadingOut', true );//<= we need to store a fadingOut status to not miss a re-print in case of a fast moving mouse $wpContentEl = $(this); $(this).children('.sek-dyn-ui-wrapper').stop( true, true ).fadeOut( { duration : 150, complete : function() { $(this).remove(); $wpContentEl.data( 'UIisFadingOut', false ); } }); }); // ADD SECTION BUTTONS // Add content button between sections // // fired on mousemove and scroll, every 50ms var _printAddContentButtons = function() { var _location, _is_global_location; self.cachedElements.$body.find( 'div[data-sek-level="location"]' ).each( function() { $sectionCollection = $(this).children( 'div[data-sek-level="section"]' ); tmpl = self.parseTemplate( '#sek-tmpl-add-content-button' ); var $btn_el; _location = $(this).data('sek-id'); _is_global_location = true === $(this).data('sek-is-global-location'); // nested sections are not included $sectionCollection.each( function() { if ( $(this).find('.sek-add-content-button').length > 0 ) return; $.when( $(this).prepend( tmpl({ location : _location, is_global_location : _is_global_location }) ) ).done( function() { $btn_el = $(this).find('.sek-add-content-button'); if ( $(this).data('sek-id') ) { $btn_el.attr('data-sek-before-section', $(this).data('sek-id') );//Will be used to insert the section at the right place } $btn_el.fadeIn( 300 ); }); //console.log('$sectionCollection.length', $sectionCollection.length, $(this).index() + 1 ); //if is last section, append also //console.log('IS LAST ? => ', $sectionCollection.length, $(this).index() ); if ( $sectionCollection.length == $(this).index() + 1 ) { $.when( $(this).append( tmpl({ is_last : true, location : _location, is_global_location : _is_global_location }) ) ).done( function() { $btn_el = $(this).find('.sek-add-content-button').last(); if ( $(this).data('sek-id') ) { $btn_el.attr('data-sek-after-section', $(this).data('sek-id') );//Will be used to insert the section at the right place } $btn_el.fadeIn( 300 ); }); } });//$sectionCollection.each( function() ) });//$( 'div[data-sek-level="location"]' ).each( function() {}) // .sek-empty-location-placeholder container is printed when the location has no section yet in its collection $('.sek-empty-location-placeholder').each( function() { if ( $(this).find('.sek-add-content-button').length > 0 ) return; _location = $(this).closest( 'div[data-sek-level="location"]' ).data('sek-id'); _is_global_location = true === $(this).closest( 'div[data-sek-level="location"]' ).data('sek-is-global-location'); $.when( $(this).append( tmpl({ location : _location, is_global_location : _is_global_location } ) ) ).done( function() { $btn_el = $(this).find('.sek-add-content-button'); $btn_el.attr('data-sek-is-first-section', true ); $btn_el.fadeIn( 300 ); }); }); };//_printAddContentButtons // fired on mousemove and scroll, every 50ms var _sniffAndRevealButtons = function( position ) { self.cachedElements.$body.find('.sek-add-content-button').each( function() { var btnWrapperRect = $(this)[0].getBoundingClientRect(), yPos = position.y, xPos = position.x, isCloseThreshold = 40, mouseToBottom = Math.abs( yPos - btnWrapperRect.bottom ), mouseToTop = Math.abs( btnWrapperRect.top - yPos ), mouseToRight = xPos - btnWrapperRect.right, mouseToLeft = btnWrapperRect.left - xPos, isCloseVertically = ( mouseToBottom < isCloseThreshold ) || ( mouseToTop < isCloseThreshold ), isCloseHorizontally = ( mouseToRight > 0 && mouseToRight < isCloseThreshold ) || ( mouseToLeft > 0 && mouseToLeft < isCloseThreshold ), isInHorizontally = xPos <= btnWrapperRect.right && btnWrapperRect.left <= xPos, isInVertically = yPos >= btnWrapperRect.top && btnWrapperRect.bottom >= yPos; // var html = ''; // html += ' | mouseToBottom : ' + mouseToBottom + ' | mouseToTop : ' + mouseToTop; // html += ' isCloseVertically : ' + isCloseVertically + ' | isInVertically : ' + isInVertically; // $(this).html( '' + html + ''); $(this).toggleClass( 'sek-mouse-is-close', ( isCloseVertically || isInVertically ) && ( isCloseHorizontally || isInHorizontally ) ); }); }; // Print / remove ui according to the mouse position // The mouse position is provided by self.mouseMovedRecently() // If the ui is expanded, remove after a delay to let user access all ui buttons, even those outside the $level. // => the ui can be "outside" ( <=> out vertically and horizontally ) when columns are narrow. var _sniffLevelsAndPrintUI = function( position, $candidateForRemoval ) { var collectionOfLevelsToWalk = [], sniffCase; if ( _.isUndefined( $candidateForRemoval ) || $candidateForRemoval.length < 1 ) { // data-sek-preview-level-guid has been introduced in https://github.com/presscustomizr/nimble-builder/issues/494 // to fix a wrong UI generation leading to user unable to edit content self.cachedElements.$body.find('[data-sek-level][data-sek-preview-level-guid="' + sekPreviewLocalized.previewLevelGuid +'"]').each( function() { collectionOfLevelsToWalk.push( $(this) ); }); sniffCase = 'printOrScheduleRemoval'; } else { collectionOfLevelsToWalk.push( $candidateForRemoval ); sniffCase = 'mayBeRemove'; } _.each( collectionOfLevelsToWalk, function( $levelToWalk ) { var levelWrapperRect = $levelToWalk[0].getBoundingClientRect(), isInHorizontally = position.x <= levelWrapperRect.right && levelWrapperRect.left <= position.x, isInVertically = position.y >= levelWrapperRect.top && levelWrapperRect.bottom >= position.y, $levelEl = $levelToWalk; switch( sniffCase ) { case 'mayBeRemove' : $levelEl.data('sek-ui-removal-scheduled', false ); if ( ! isInHorizontally || ! isInVertically ) { removeLevelUI.call( $levelEl ); } break; case 'printOrScheduleRemoval' : if ( isInHorizontally && isInVertically ) { $levelEl.data('sek-ui-removal-scheduled', false ); self.printLevelUI( $levelEl ); } else { if ( true !== $levelEl.data('sek-ui-removal-scheduled') ) { $levelEl.data('sek-ui-removal-scheduled', true ); if ( $levelEl.children('.sek-dyn-ui-wrapper').find('.sek-is-expanded').length < 1 ) { _sniffLevelsAndPrintUI( position, $levelEl ); } else { _.delay( function() { if ( true === $levelEl.data('sek-ui-removal-scheduled') ) { // using the latest self.mouseMovedRecently(), instead of the initial "position" param, makes sure we don't miss the latest mouse movements _sniffLevelsAndPrintUI( self.mouseMovedRecently(), $levelEl ); } }, 3500 ); } } } break; } });//collectionOfLevelsToWalk.each }; // SCHEDULE // - the printing / removal of the add content button // - the printing of the level's UI self.mouseMovedRecently = new api.Value( {} ); self.mouseMovedRecently.bind( function( position ) { if ( ! _.isEmpty( position) ) { // print the buttons ( display:none) _printAddContentButtons(); // sniff sections around pointer and reveal add content button for the collection of candidates _sniffAndRevealButtons( position ); // sniff levels and print UI _sniffLevelsAndPrintUI( position ); } else { // when PHP constant NIMBLE_IS_PREVIEW_UI_DEBUG_MODE is true, the levels UI in the preview are not being auto removed, so we can inspect the markup and CSS if ( ! sekPreviewLocalized.isPreviewUIDebugMode ) { // Mouse didn't move recently? // => remove all UIs // 1) add content buttons removeAddContentButtons(); // 2) level UI's self.cachedElements.$body.stop( true, true ).find('[data-sek-level]').each( function() { // preserve if the ui menu is expanded, otherwise remove if ( $(this).children('.sek-dyn-ui-wrapper').find('.sek-is-expanded').length < 1 ) { removeLevelUI.call( $(this) ); } }); } } }); // @return void() var resetMouseMoveTrack = function() { clearTimeout( self.cachedElements.$window.data('_scroll_move_timer_') ); self.mouseMovedRecently.set({}); }; self.cachedElements.$window.on( 'mousemove scroll', _.throttle( function( evt ) { self.mouseMovedRecently( { x : evt.clientX, y : evt.clientY } ); clearTimeout( self.cachedElements.$window.data('_scroll_move_timer_') ); self.cachedElements.$window.data('_scroll_move_timer_', setTimeout(function() { self.mouseMovedRecently.set({}); }, 4000 ) ); }, 50 ) ); // Always reset the move timer and the mouseMove Value when // - a dragging action is started // - a section is added <= fixes the addition of multiple "Add Section" button in the same location api.preview.bind( 'sek-drag-start', function() { resetMouseMoveTrack(); }); self.cachedElements.$body.on( 'sek-section-added', '[data-sek-level="location"]', function( evt, params ) { resetMouseMoveTrack(); }); return this; }//setupUiHoverVisibility });//$.extend() })( wp.customize, jQuery, _ ); //global sekPreviewLocalized var SekPreviewPrototype = SekPreviewPrototype || {}; ( function( api, $, _ ) { $.extend( SekPreviewPrototype, { //Fired on Dom ready in initialize() scheduleUiClickReactions : function() { var self = this; var _reactOnClick = function( evt ) { // First clean any currently highlighted target drop zone // implemented for double-click insertion https://github.com/presscustomizr/nimble-builder/issues/317 api.preview.send( 'sek-clean-target-drop-zone' ); var clickedOn = 'inactiveZone', $el = $(evt.target), $hookLocation = $el.closest('[data-sek-level="location"][data-sek-preview-level-guid="' + sekPreviewLocalized.previewLevelGuid +'"]'), $closestLevelWrapper = $el.closest('[data-sek-preview-level-guid="' + sekPreviewLocalized.previewLevelGuid +'"]'), $closestActionIcon = $el.closest('[data-sek-click-on]'), _action, _location_id = $hookLocation.data('sek-id'), _level = $closestLevelWrapper.data('sek-level'), _id = $closestLevelWrapper.data('sek-id'); if ( 'add-content' == $el.data('sek-click-on') || ( $el.closest('[data-sek-click-on]').length > 0 && 'add-content' == $el.closest('[data-sek-click-on]').data('sek-click-on') ) ) { clickedOn = 'addContentButton'; } else if ( ! _.isEmpty( $el.data( 'sek-click-on' ) ) || $closestActionIcon.length > 0 ) { clickedOn = 'UIIcon'; } else if ( 'module' == $closestLevelWrapper.data('sek-level') ) { clickedOn = 'moduleWrapper'; } else if ( 'column' == $closestLevelWrapper.data('sek-level') && true === $closestLevelWrapper.data('sek-no-modules') ) { clickedOn = 'noModulesColumn'; } else if ( $el.hasClass('sek-to-json') ) { clickedOn = 'sekToJson'; } else if ( 'column' == $closestLevelWrapper.data('sek-level') ) { clickedOn = 'columnOutsideModules'; } else if ( 'section' == $closestLevelWrapper.data('sek-level') ) { clickedOn = 'sectionOutsideColumns'; } else if ( ! _.isEmpty( $el.data( 'sek-add' ) ) ) { clickedOn = 'addSektion'; } else if ( $el.hasClass('sek-wp-content-wrapper') || $el.hasClass( 'sek-wp-content-dyn-ui') ) { clickedOn = 'wpContent'; } else if ( $el.hasClass('sek-edit-wp-content') ) { clickedOn = 'editWpContent'; } else { clickedOn = 'inactiveZone'; } if ( $hookLocation.length > 0 && _.isEmpty( _location_id ) ) { self.errare( '::scheduleUiClickReactions => error location id can not be empty' ); } //console.log('ALORS CLICKED ?', clickedOn, $el ); switch( clickedOn ) { case 'addContentButton' : var is_first_section = true === $el.closest('[data-sek-is-first-section]').data('sek-is-first-section'); api.preview.send( 'sek-add-section', { location : _location_id, level : 'section', before_section : $el.closest('[data-sek-before-section]').data('sek-before-section'), after_section : $el.closest('[data-sek-after-section]').data('sek-after-section'), is_first_section : is_first_section, send_to_preview : ! is_first_section }); break; case 'UIIcon' : if ( 1 > $closestLevelWrapper.length ) { throw new Error( 'ERROR => sek-front-preview => No valid level dom element found' ); } _action = $el.closest('[data-sek-click-on]').data('sek-click-on'); if ( _.isEmpty( _action ) ) { throw new Error( 'Invalid action' ); } if ( _.isEmpty( _level ) || _.isEmpty( _id ) ) { throw new Error( 'ERROR => sek-front-preview => No valid level id found' ); } self._send_( $el, { action : _action, location : _location_id, level : _level, id : _id, was_triggered : false //<= indicates that the user clicked. }); break; case 'moduleWrapper' : // stop here if the ui icons block was clicked if ( $el.parent('.sek-dyn-ui-icons').length > 0 ) return; self._send_( $el, { action : 'edit-module', level : _level, id : _id }); break; case 'noModulesColumn' : // stop here if the ui icons block was clicked if ( $el.parent('.sek-dyn-ui-icons').length > 0 ) return; self._send_( $el, { action : 'edit-options', location : _location_id, level : _level, id : _id }); //self._send_( $el, { action : 'pick-content', content_type : 'module', level : _level , id : _id } ); break; case 'columnOutsideModules' : case 'sectionOutsideColumns' : self._send_( $el, { action : 'edit-options', location : _location_id, level : _level, id : _id }); break; case 'addSektion' : api.preview.send( 'sek-add-section', { location : _location_id, level : $el.data('sek-add') }); break; case 'sekToJson' : api.preview.send( 'sek-to-json', { id : _id } ); break; case 'wpContent' : api.preview.send( 'sek-notify', { type : 'info', duration : 8000, message : sekPreviewLocalized.i18n['This content has been created with the WordPress editor.'] }); break; case 'editWpContent' : // note : the edit url is printed as a data attribute to prevent being automatically parsed by wp when customizing and turned into a changeset url var edit_url = $el.closest('[data-sek-wp-edit-link]').data('sek-wp-edit-link'); if ( ! _.isEmpty( edit_url ) ) { window.open( edit_url,'_blank' ); } break; case 'inactiveZone' : api.preview.send( 'sek-click-on-inactive-zone');//<= for example, collapses the tinyMce editor if expanded //self._send_( $el, { action : 'pick-content' } ); break; } };//_reactOnClick() self.cachedElements.$body.on('click',_reactOnClick );//$('body').on('click', function( evt ) {} },//scheduleUserReactions() _send_ : function( $el, params ) { //console.log('IN _send_', $el, params ); var clonedParams = $.extend( true, {}, params ), syncedTinyMceInputId = '', $moduleWrapper = $el.closest('div[data-sek-level="module"]'), _module_type_ = 'module' === params.level ? $moduleWrapper.data( 'sek-module-type') : ''; if ( 'module' === params.level ) { if ( 'czr_tiny_mce_editor_module' === _module_type_ ) { syncedTinyMceInputId = $moduleWrapper.find('div[data-sek-input-id]').length > 0 ? $moduleWrapper.find('div[data-sek-input-id]').data('sek-input-id') : ''; } } api.preview.send( 'sek-' + params.action, _.extend( { location : params.location, level : params.level, id : params.id, content_type : $el.data( 'sek-content-type'), module_type : _module_type_, in_column : $el.closest('div[data-sek-level="column"]').length > 0 ? $el.closest('div[data-sek-level="column"]').data( 'sek-id') : '', in_sektion : $el.closest('div[data-sek-level="section"]').length > 0 ? $el.closest('div[data-sek-level="section"]').data( 'sek-id') : '', clicked_input_type : $el.closest('div[data-sek-input-type]').length > 0 ? $el.closest('div[data-sek-input-type]').data('sek-input-type') : '', clicked_input_id : $el.closest('div[data-sek-input-id]').length > 0 ? $el.closest('div[data-sek-input-id]').data('sek-input-id') : '', was_triggered : params.was_triggered, // the syncedTinyMceInputId is used in the control::setupTinyMceEditor() // Fixes : https://github.com/presscustomizr/nimble-builder/issues/251 syncedTinyMceInputId : syncedTinyMceInputId }, clonedParams ) ); } });//$.extend() })( wp.customize, jQuery, _ ); //global sekPreviewLocalized var SekPreviewPrototype = SekPreviewPrototype || {}; ( function( api, $, _ ) { $.extend( SekPreviewPrototype, { //Fired in ::initialize() setupLoader : function() { var self = this; // Cache our loader this._css_loader_html = ''; // Loader Cleaning <= the element printed when refreshing a level // @see ::mayBePrintLoader self.cachedElements.$body.on([ 'sek-modules-refreshed', 'sek-columns-refreshed', 'sek-section-added', 'sek-level-refreshed', 'sek-stylesheet-refreshed', 'sek-ajax-error' ].join(' '), function( evt ) { self.cleanLoader(); }); }, // @return void() // Insert a clone ( same dimensions ) div element of a level currently being refreshed, including a centered loading animation // + adds a .sek-refreshing css class to the element being refreshed // // Invoked when // - user click on an icon action in the preview that trigger a partial reflush of the DOM. For example, adding a column, duplicating a module, etc. // - a module / section is dropped in the preview // - a module is being edited // - a column is resized // - on 'sek-maybe-print-loader' // @params { // element : $(), // action : '', // level, // loader_located_in_level_id, // fullPageLoader: false, // duration:4000 // } mayBePrintLoader : function( params ) { params = _.isObject( params ) ? params : {}; var self = this, levelIdForTheLoader = params.loader_located_in_level_id; if ( ! _.isEmpty( levelIdForTheLoader ) ) { var $levelElementForTheLoader = $('[data-sek-id="' + levelIdForTheLoader +'"]'); if ( $levelElementForTheLoader.length > 0 && 1 > $('.sek-level-clone ').length ) { $levelClone = $('
', { class : 'sek-level-clone' }); // blur all children levels $levelElementForTheLoader.find('[data-sek-level]').each( function() { $(this).addClass('sek-refreshing'); }); // print the absolute positionned clone on top $levelElementForTheLoader.prepend( $levelClone ); $levelClone.css({ width : $levelElementForTheLoader.outerWidth() +'px', height : $levelElementForTheLoader.outerHeight() + 'px' }).append( self._css_loader_html ).find('.sek-css-loader').fadeIn( 'fast' ); // Start the countdown for auto-cleaning clearTimeout( $.data( this, '_nimble_loader_active_timer_') ); $.data( this, '_nimble_loader_active_timer_', setTimeout(function() { self.cleanLoader(); }, params.duration || 4000 ) ); } } if ( true === params.fullPageLoader ) { var $loaderWrapper = $('
', { id : 'nimble-full-page-loader-wrapper', class: 'white-loader'} ); self.cachedElements.$body.append($loaderWrapper); $loaderWrapper.fadeIn('fast').append( self._css_loader_html ).find('.sek-css-loader').fadeIn( 'fast' ); // Blur locations $('[data-sek-level="location"]').each( function() { $(this).addClass('sek-blur'); }); // Start the countdown for auto-cleaning clearTimeout( $.data( this, '_nimble_full_page_loader_active_timer_') ); $.data( this, '_nimble_full_page_loader_active_timer_', setTimeout(function() { self.cleanLoader( { cleanFullPageLoader : true }); }, params.duration || 6000 ) ); } }, // scheduled in ::initialize(), on 'sek-modules-refreshed sek-columns-refreshed sek-section-added sek-refresh-level' // invoked in ::mayBePrintLoader() in an auto-clean scenario // or on wp.customize.send('sek-clean-loader', { cleanFullPageLoader : true }) // { // cleanFullPageLoader : true // } cleanLoader : function( params ) { var self = this; $('.sek-level-clone').remove(); $('[data-sek-level]').each( function() { $(this).removeClass('sek-refreshing'); }); params = params || {}; if ( true === params.cleanFullPageLoader ) { // Unblur locations $('[data-sek-level="location"]').each( function() { $(this).removeClass('sek-blur'); }); $('#nimble-full-page-loader-wrapper').remove(); } } });//$.extend() })( wp.customize, jQuery, _ ); //global sekPreviewLocalized var SekPreviewPrototype = SekPreviewPrototype || {}; ( function( api, $, _ ) { $.extend( SekPreviewPrototype, { // Fired in ::initialize() schedulePanelMsgReactions : function() { var self = this, apiParams = {}, uiParams = {}, msgCollection = { // DOM MODIFICATION CASES 'sek-add-section' : 'ajaxAddSektion', 'sek-add-content-in-new-sektion' : 'ajaxAddSektion', 'sek-add-content-in-new-nested-sektion' : 'ajaxAddSektion', 'sek-add-column' : 'ajaxRefreshColumns', 'sek-add-module' : 'ajaxRefreshModulesAndNestedSections', 'sek-refresh-stylesheet-in-the-background-for-custom-css' : 'ajaxRefreshStylesheet', 'sek-refresh-stylesheet' : 'ajaxRefreshStylesheet', 'sek-resize-columns' : 'ajaxResizeColumns', 'sek-maybe-print-loader' : function( params ) { try { self.mayBePrintLoader( params ); } catch( er ) { api.errare( 'sek-print-loader => error', er ); } }, 'sek-clean-loader' : function( params ) { try { self.cleanLoader( params ); } catch( er ) { api.errare( 'sek-clean-loader => error', er ); } }, 'sek-remove' : function( params ) { var removeCandidateId = params.apiParams.id, $candidateEl = $('div[data-sek-id="' + removeCandidateId + '"]' ), dfd; switch ( params.apiParams.action ) { case 'sek-remove-section' : // will be cleaned on ajax.done() // @see ::scheduleTheLoaderCleaning self.mayBePrintLoader({ loader_located_in_level_id : params.apiParams.location }); if ( true === params.apiParams.is_nested ) { dfd = self.ajaxRefreshModulesAndNestedSections( params ); } else { if ( _.isEmpty( removeCandidateId ) || 1 > $candidateEl.length ) { self.errare( 'reactToPanelMsg => sek-remove => invalid candidate id => ', removeCandidateId ); } self.cachedElements.$body.find( $candidateEl ).remove(); // say it // listened to clean the loader just in time $('[data-sek-id="' + params.apiParams.location + '"]').trigger( 'sek-level-refreshed'); } //self.ajaxRefreshModulesAndNestedSections( params ); break; case 'sek-remove-column' : dfd = self.ajaxRefreshColumns( params ); break; case 'sek-remove-module' : dfd = self.ajaxRefreshModulesAndNestedSections( params ); break; default : break; } // We should always return a promise return _.isEmpty( dfd ) ? $.Deferred( function() { this.resolve(); } ) : dfd; }, 'sek-duplicate' : function( params ) { var dfd; switch ( params.apiParams.action ) { case 'sek-duplicate-section' : // replace the original id by the new cloneId registered in the main setting, and sent by the panel params.apiParams.id = params.cloneId; dfd = self.ajaxAddSektion( params ); break; case 'sek-duplicate-column' : // replace the original id by the new cloneId registered in the main setting, and sent by the panel params.apiParams.id = params.cloneId; dfd = self.ajaxRefreshColumns( params ); break; case 'sek-duplicate-module' : // replace the original id by the new cloneId registered in the main setting, and sent by the panel params.apiParams.id = params.cloneId; dfd = self.ajaxRefreshModulesAndNestedSections( params ); break; } return dfd; }, // Re-print a level // Can be invoked when setting the section layout option boxed / wide, when we need to add a css class server side // @params { // action : 'sek-refresh-level', // level : params.level, // id : params.id // } 'sek-refresh-level' : function( params ) { // will be cleaned on 'sek-module-refreshed' self.mayBePrintLoader({ loader_located_in_level_id : params.apiParams.id }); return self.doAjax({ location_skope_id : params.location_skope_id, local_skope_id : params.local_skope_id, action : 'sek_get_content', id : params.apiParams.id, level : params.apiParams.level, sek_action : params.apiParams.action }).fail( function( _r_ ) { self.errare( 'ERROR reactToPanelMsg => sek-refresh-level => ' , _r_ ); $( '[data-sek-id="' + params.apiParams.id + '"]' ).trigger( 'sek-ajax-error' ); }).done( function( _r_ ) { var html_content = ''; //@see php SEK_Front_Ajax::sek_get_level_content_for_injection if ( _r_.data && _r_.data.contents ) { html_content = _r_.data.contents; } else { self.errare( 'SekPreviewPrototype::sek-refresh-level => ajax_response.data.contents is undefined ', _r_ ); self.errare( 'params ?', params ); } // _r_ is an array // @see SEK_Front_Ajax::sek_get_level_content_for_injection // _r_ = array( // 'contents' => $html, // 'setting_validities' => $exported_setting_validities // ); var placeholderHtml = '', $currentLevelEl = $( 'div[data-sek-id="' + params.apiParams.id + '"]' ); if ( $currentLevelEl.length < 1 ) { self.errare( 'reactToPanelMsg => sek-refresh-level ajax done => the level to refresh is not rendered in the page', _r_ ); return; } $currentLevelEl.before( placeholderHtml ); var $placeHolder = $( '[data-sek-placeholder-for="' + params.apiParams.id + '"]' ); $currentLevelEl.remove(); if ( _.isUndefined( html_content ) ) { self.errare( 'reactToPanelMsg => sek-refresh-level ajax done => missing html_content', _r_ ); } else { $placeHolder.after( html_content ); } if ( $placeHolder.length > 0 ) { $placeHolder.remove(); } //=> 'sek-level-refreshed' is listened to ( for example ) clean the loader overlay in time $( '[data-sek-id="' + params.apiParams.id + '"]' ).trigger( 'sek-level-refreshed', { level : params.apiParams.level, id : params.apiParams.id } ); // When completing actions 'sek-move-section-down' && 'sek-move-section-up', a 'sek-refresh-level' is triggered. // We pass the moved_level_id so we can focus on it after it's been re-located in the DOM // implemented for https://github.com/presscustomizr/nimble-builder/issues/471 if ( params.apiParams.moved_level_id ) { api.preview.trigger( 'sek-animate-to-level', { id : params.apiParams.moved_level_id } ); } }); }, // EDITING MODULE AND OPTIONS 'sek-move' : function( params ) { switch ( params.apiParams.action ) { // case 'sek-move-section' : // //always re-render the source sektion and target sektion if different // //=> this will ensure a reset of the column's widths // if ( params.apiParams.from_location != params.apiParams.to_location ) { // var paramsForSourceSektion = $.extend( true, {}, params ); // var paramsForTargetSektion = $.extend( true, {}, params ); // // SOURCE SEKTION // // if the source sektion has been emptied, let's populate it with a new column // if ( $('[data-sek-id="' + params.apiParams.from_sektion +'"]', '.sektion-wrapper').find('div[data-sek-level="column"]').length < 1 ) { // api.preview.send( 'sek-add-column', { // in_sektion : params.apiParams.from_sektion, // autofocus:false//<= because we want to focus on the column that has been moved away from the section // }); // } else { // paramsForSourceSektion.apiParams = _.extend( paramsForSourceSektion.apiParams, { // in_sektion : params.apiParams.from_sektion, // action : 'sek-refresh-columns-in-sektion' // }); // self.ajaxRefreshColumns( paramsForSourceSektion ); // } // // TARGET SEKTION // paramsForTargetSektion.apiParams = _.extend( paramsForTargetSektion.apiParams, { // in_sektion : params.apiParams.to_sektion, // action : 'sek-refresh-columns-in-sektion' // }); // self.ajaxRefreshColumns( paramsForTargetSektion ); // } // break; case 'sek-move-column' : //always re-render the source sektion and target sektion if different //=> this will ensure a reset of the column's widths if ( params.apiParams.from_sektion != params.apiParams.to_sektion ) { var paramsForSourceSektion = $.extend( true, {}, params ); var paramsForTargetSektion = $.extend( true, {}, params ); // SOURCE SEKTION // if the source sektion has been emptied, let's populate it with a new column if ( $('[data-sek-id="' + params.apiParams.from_sektion +'"]', '.sektion-wrapper').find('div[data-sek-level="column"]').length < 1 ) { api.preview.send( 'sek-add-column', { in_sektion : params.apiParams.from_sektion, autofocus:false//<= because we want to focus on the column that has been moved away from the section }); } else { paramsForSourceSektion.apiParams = _.extend( paramsForSourceSektion.apiParams, { in_sektion : params.apiParams.from_sektion, action : 'sek-refresh-columns-in-sektion' }); self.ajaxRefreshColumns( paramsForSourceSektion ); } // TARGET SEKTION paramsForTargetSektion.apiParams = _.extend( paramsForTargetSektion.apiParams, { in_sektion : params.apiParams.to_sektion, action : 'sek-refresh-columns-in-sektion' }); self.ajaxRefreshColumns( paramsForTargetSektion ); } break; case 'sek-move-module' : var paramsForSourceColumn = $.extend( true, {}, params ), paramsForTargetColumn = $.extend( true, {}, params ); // SOURCE COLUMN //always re-render the source column if different than the target column //=> this will ensure that we have the drop-zone placeholder printed for a no-module column //+ will refresh the sortable() if ( paramsForSourceColumn.apiParams.from_column != paramsForSourceColumn.apiParams.to_column ) { paramsForSourceColumn.apiParams = _.extend( paramsForSourceColumn.apiParams, { in_column : paramsForSourceColumn.apiParams.from_column, in_sektion : paramsForSourceColumn.apiParams.from_sektion, action : 'sek-refresh-modules-in-column' }); self.ajaxRefreshModulesAndNestedSections( paramsForSourceColumn ); } // TARGET COLUMN params.apiParams = _.extend( paramsForTargetColumn.apiParams, { in_column : paramsForTargetColumn.apiParams.to_column, in_sektion : paramsForTargetColumn.apiParams.to_sektion, action : 'sek-refresh-modules-in-column' }); self.ajaxRefreshModulesAndNestedSections( paramsForTargetColumn ); // Re-instantiate sortable for the target column $('[data-sek-id="' + params.apiParams.to_column +'"]', '.sektion-wrapper').find('.sek-column-inner').sortable( "refresh" ); break; } }, // GENERATE UI ELEMENTS // when the options ui has been generated in the panel for a level, we receive back this msg // 'sek-generate-level-options-ui' : function( params ) { // api.infoLog('PANEL REACT? ', 'sek-generate-level-options-ui', params ); // }, 'sek-edit-options' : function( params ) { // ::activeLevelUI is declared in ::initialized() self.activeLevelUI( params.uiParams.id ); }, 'sek-edit-module' : function( params ) { // ::activeLevelUI is declared in ::initialized() self.activeLevelUI( params.uiParams.id ); }, // @params = { // location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing // apiParams : apiParams, // uiParams : uiParams // } // uiParams = { // action : 'sek-edit-module', // level : params.level, // id : params.id, // in_sektion : params.in_sektion, // in_column : params.in_column, // options : params.options || [] // }; // // when the module ui has been generated in the panel, we receive back this msg //'sek-generate-module-ui' : function( params ) {}, //@params { // content_type : module || preset_section, // eligible_for_module_dropzones : boolean //<= typically useful for multicolumn "modules" that are in reality preset_section @see https://github.com/presscustomizr/nimble-builder/issues/540 // } 'sek-drag-start' : function( params ) { // append the drop zones between sections var i = 1, previousSectionIsEmpty = false; $('[data-sek-level="location"]').children('[data-sek-level="section"]').each( function() { var sectionId = $(this).data('sek-id'), columnNb = $(this).find('[data-sek-level="column"]').length, moduleNb = $(this).find('[data-sek-level="module"]').length, isEmptySection = columnNb < 2 && moduleNb < 1, canPrintBefore = ! previousSectionIsEmpty && ! isEmptySection; // Print a dropzone before if the previous section and current section are not empty. if ( canPrintBefore && $('[data-drop-zone-before-section="' + sectionId +'"]').length < 1 ) { $(this).before( '
' ); } // After the last one if ( ! isEmptySection && i == $('.sektion-wrapper').children('[data-sek-level="section"]').length ) { $(this).after( '
' ); } i++; previousSectionIsEmpty = isEmptySection; }); // Append the drop zone in empty locations $('.sek-empty-location-placeholder').each( function() { $.when( $(this).append( '
' )); }); // Append a drop zone between modules and nested sections in columns // preset_sections like multicolumn structure are part of the module list // they fall under the second part of the conditional statement below // introduced for https://github.com/presscustomizr/nimble-builder/issues/540 if ( 'module' == params.content_type || ( 'preset_section' == params.content_type && true === params.eligible_for_module_dropzones ) ) { $('[data-sek-level="column"]').each( function() { // Our candidates are the modules and nested section which are direct children of this column // We don't want to include the modules inserted in the columns of a nested section. var $modules_and_nested_sections = $(this).children('.sek-column-inner').children( '[data-sek-level="module"]' ); var $nested_sections = $(this).children('.sek-column-inner').children( '[data-sek-is-nested="true"]' ); $modules_and_nested_sections = $modules_and_nested_sections.add( $nested_sections ); var j = 1; $modules_and_nested_sections.each( function() { // Always before if ( $('[data-drop-zone-before-module-or-nested-section="' + $(this).data('sek-id') +'"]').length < 1 ) { $(this).before( '
' ); } // After the last one if ( j == $modules_and_nested_sections.length && $('[data-drop-zone-after-module-or-nested-section="' + $(this).data('sek-id') +'"]').length < 1 ) { $(this).after( '
' ); } j++; }); }); } // toggle a parent css classes controlling some css rules @see preview.css self.cachedElements.$body.addClass('sek-dragging'); // Reveal all dynamic dropzones after a delay _.delay( function() { $('.sek-dynamic-drop-zone').css({ opacity : 1 }); }, 100 ); }, // is sent on dragend and drop 'sek-drag-stop' : function( params ) { self.cachedElements.$body.removeClass('sek-dragging'); // Clean any remaining placeholder $('.sortable-placeholder').remove(); // Remove the drop zone dynamically add on sek-drag-start $('.sek-dynamic-drop-zone').remove(); }, // FOCUS // Sent from the panel when duplicating a section level for example // focus on a level 'sek-animate-to-level' : function( params ) { var $elToFocusOn = $('[data-sek-id="' + params.id + '"]' ); if ( 0 < $elToFocusOn.length && !nb_.isInScreen( $elToFocusOn[0]) ) { $elToFocusOn[0].scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' }); } // if ( $elToFocusOn.length > 0 ) { // console.log( 'EL IN WINDOW ?', nb_.elOrFirstVisibleParentIsInWindow( $elToFocusOn ), $elToFocusOn ); // $elToFocusOn[0].scrollIntoView(); // $('html, body').animate({ // scrollTop : $elToFocusOn.offset().top - 100 // }, 200 ); // } }, // LEVEL UI's 'sek-clean-level-uis' : function( params ) { $('.sek-dyn-ui-wrapper').each( function() { $(this).remove(); }); }, // triggered when navigating the level tree 'sek-display-level-ui' : function( params ) { var $elToFocusOn = $('[data-sek-id="' + params.id + '"]' ); if ( $elToFocusOn.length > 0 ) { //$elToFocusOn.trigger('click'); //<= the click is not needed anymore since June 2019, we trigger the generation of the level options on 'click' in the level tree self.printLevelUI($elToFocusOn); } }, // DOUBLE CLICK INSERTION => HIGHLIGHTED TARGET // implemented for double-click insertion // https://github.com/presscustomizr/nimble-builder/issues/317 'sek-set-double-click-target' : function( params ) { // First clean any other highlighted target $('.sek-target-for-double-click-insertion').removeClass('sek-target-for-double-click-insertion'); if ( _.isObject( params ) && params.id ) { var $elToHighlight = $('[data-sek-id="' + params.id + '"]' ); if( 1 === $elToHighlight.length ) { $elToHighlight.addClass('sek-target-for-double-click-insertion'); } } }, 'sek-reset-double-click-target' : function( params ) { $('.sek-target-for-double-click-insertion').removeClass('sek-target-for-double-click-insertion'); }, // introduced for https://github.com/presscustomizr/nimble-builder/issues/403 // this is fired for module with postMessage refresh, like text editor // @see control::refreshMarkupWhenNeededForInput() // July 2019 => since the new UI rendering with JS template ( https://github.com/presscustomizr/nimble-builder/issues/465 ), this action is fired too early when inserting a new module with postMessage refresh // resulting in the target element not being rendered on first call 'sek-update-html-in-selector' : function( params ) { var $level_el = $('[data-sek-id="' + params.id + '"]' ), $target_el; // for multi-item modules, the changed item id is passed if ( !_.isEmpty( params.changed_item_id ) ) { // if a selector is provided in param 'refresh_markup' if ( params.selector ) { $target_el = $( '[data-sek-item-id="' + params.changed_item_id + '"] ' + params.selector, $level_el); } else { $target_el = $( '[data-sek-item-id="' + params.changed_item_id + '"]', $level_el); } } else { $target_el = $(params.selector, $level_el); } if ( $level_el.length > 0 && $target_el.length > 0 ) { $target_el.html( params.html ); } else { self.errare( 'reactToPanelMsg => sek-update-html-in-selector => missing level or target dom element', params ); } }, // introduced for CUSTOM CSS see https://github.com/presscustomizr/nimble-builder-pro/issues/201 // fired when refresh_css_via_post_message = true in input registration params 'sek-update-css-with-postmessage' : function( params ) { //console.log('ALORS PARAMS ?', params ); if ( _.isUndefined(params.css_content) || !_.isString(params.css_content) ) { self.errare( 'error => sek-update-css-with-postmessage => css content is not a string' ); return; } // Comments removal // see https://stackoverflow.com/questions/5989315/regex-for-match-replacing-javascript-comments-both-multiline-and-inline params.css_content = params.css_content.replace(/\/\*.+?\*\/|\/\/.*(?=[\n\r])/g, ''); var custom_css_sel, $custom_css_el, _level_selector; if ( params.is_current_page_custom_css ) { custom_css_sel = 'nb-custom-css-for-local-page'; _level_selector = ''; } else { custom_css_sel = 'nb-custom-css-for-level' + params.id; _level_selector = 'body .sektion-wrapper [data-sek-id="' + params.id +'"]'; } $custom_css_el = $('#' + custom_css_sel ); if ( $custom_css_el.length < 1 ) { $('head').append( $('