Files
automedsys_blog/wp-content/plugins/nimble-builder/assets/czr/sek/js/ccat-sek-preview.js
T
CHIEFSOFT\ameye b114fdf4fa first commit
2023-12-28 16:20:07 -05:00

2948 lines
180 KiB
JavaScript

//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
// // });
// }
// }));
// });
// <SORTABLE>
// $('.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 $('<div class="sortable-placeholder"><div class="sek-module-placeholder-content"><p>' + sekPreviewLocalized.i18n['Insert here'] + '</p></div></div>')[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('<i class="fas fa-arrows-alt-h"></i>');
}
}, 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
// <script type="text/html" id="sek-tmpl-add-content-button">
// <div class="sek-add-content-button <# if ( data.is_last ) { #>is_last<# } #>">
// <div class="sek-add-content-button-wrapper">
// <button data-sek-click-on="add-content" data-sek-add="section" class="sek-add-content-btn" style="--sek-add-content-btn-width:60px;">
// <span title="<?php _e('Add Content', 'text_doma' ); ?>" class="sek-click-on-button-icon fas fa-plus-circle sek-click-on"></span><span class="action-button-text"><?php _e('Add Content', 'text_doma' ); ?></span>
// </button>
// </div>
// </div>
// </script>
// 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( '<span style="font-size:12px">' + html + '</span>');
$(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 = '<div class="sek-css-loader sek-mr-loader" style="display:none"><div></div><div></div><div></div></div>';
// 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 = $('<div>', { 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 = $('<div>', { 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 = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.id + '"></span>',
$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(
'<div class="sek-content-' + params.content_type + '-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-sections" data-drop-zone-before-section="' + sectionId +'"></div>'
);
}
// After the last one
if ( ! isEmptySection && i == $('.sektion-wrapper').children('[data-sek-level="section"]').length ) {
$(this).after(
'<div class="sek-content-' + params.content_type + '-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-sections" data-drop-zone-after-section="' + sectionId +'"></div>'
);
}
i++;
previousSectionIsEmpty = isEmptySection;
});
// Append the drop zone in empty locations
$('.sek-empty-location-placeholder').each( function() {
$.when( $(this).append(
'<div class="sek-content-' + params.content_type + '-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="in-empty-location"></div>'
));
});
// 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(
'<div class="sek-content-module-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-modules-and-nested-sections" data-drop-zone-before-module-or-nested-section="' + $(this).data('sek-id') +'"></div>'
);
}
// 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(
'<div class="sek-content-module-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-modules-and-nested-sections" data-drop-zone-after-module-or-nested-section="' + $(this).data('sek-id') +'"></div>'
);
}
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( $('<style/>' , {
id : custom_css_sel,
}) );
$custom_css_el = $('#' + custom_css_sel );
}
// Apply the same treatment made server side in sek_add_css_rules_for_level_custom_css()
var _exploded_rules,
_rules_with_level_specificity = [],
_specific_rules = '',
_rule_selectors,
_rule_selectors_without_space,
_comma_exploded_selectors;
// 1) FIRST => Explode by } and add level specificity
_exploded_rules = params.css_content.split('}');
_.each( _exploded_rules, function( _rule ){
// remove all line breaks
_rule = _rule.replace(/\n|\r/g, "" );
if ( _.isEmpty(_rule) || -1 === _rule.indexOf('{') )
return;
_rule = _rule.replace(/.nimble-level/g, '' );
_rule_selectors = _rule.substr(0, _rule.indexOf('{'));
_rule_selectors_without_space = _rule_selectors.replace(/ /g,'');
_rule = _rule.replace(_rule_selectors, '' );
// If no selectors specified, simply use the level selector
if ( _.isEmpty( _rule_selectors_without_space ) ) {
_rules_with_level_specificity.push(_level_selector + _rule + '}css_delimiter');
} else {
// => handle selectors separated by commas, in this case, the previous treatment has only added specificity to the first selector of the list
_comma_exploded_selectors = _rule_selectors.split(',');
_.each( _comma_exploded_selectors, function( _sel ){
if ( _.isEmpty(_sel) )
return;
_rules_with_level_specificity.push(_level_selector + ' ' + _sel + _rule + '}css_delimiter');
});
}
});
if ( !_.isEmpty(_rules_with_level_specificity) ) {
_specific_rules = _rules_with_level_specificity.join('css_delimiter');
_specific_rules = _specific_rules.replace(/css_delimiter/g, '' );
}
// If there are no rules to write, refresh the stylesheet ajaxily ( because it might contain previous CSS rules that should be cleaned up )
// + remove the custom CSS style element
if ( !_.isEmpty( _specific_rules.replace(/ /g, '' ) ) ) {
$custom_css_el.html( _specific_rules );
} else {
$custom_css_el.remove();
}
params = $.extend({}, true, params );
params.dont_print_loader = true;
// Let's refresh the stylesheet ajaxily in the background to make sure that previously saved rules do not override new ones
api.preview.trigger('sek-refresh-stylesheet-in-the-background-for-custom-css', params);
},
// march 2020 : print confettis when displaying the review request
'sek-print-confettis' : function( params ) {
if (!window.confetti || !window.requestAnimationFrame)
return;
params = params || {};
var end = params.duration || Date.now() + (1 * 1000);
var colors = ['#f18700', '#684F2F', '#eea236'];
(function frame() {
confetti({
particleCount: 2,
angle: 60,
spread: 55,
origin: {
x: 0,
y: 0.8
},
colors: colors
});
confetti({
particleCount: 4,
angle: 120,
spread: 55,
origin: {
x: 1,
y: 0.8
},
colors: colors
});
if (Date.now() < end) {
window.requestAnimationFrame(frame);
}
}());
}
};//msgCollection
var $_activeElement; // <= will be used to cache self.activeLevelEl()
var _apiPreviewCallback = function( params, callbackFn, msgId ) {
params = _.extend( {
location_skope_id : '',
apiParams : {},
uiParams : {}
}, params || {} );
// If the ajax response is an array formed this way ( @see sek-refresh-level case ) :
// @see SEK_Front_Ajax::sek_get_level_content_for_injection
// _ajaxResponse_ = array(
// 'contents' => $html,
// 'setting_validities' => $exported_setting_validities
// );
// Then we send an additional setting-validity message to the control panel
// This is the same mechanism used by WP to handle the setting validity of the partial refresh
var sendSuccessDataToPanel = function( _ajaxResponse_ ) {
// always send back the {msgId}_done message, so the control panel can fire the "complete" callback.
// @see api.czr_sektions::reactToPreviewMsg
api.preview.send( [ msgId, 'done'].join('_'), params );
// For multi-items module, when the level is refreshed, we want to focus on the changed_item
// @see CZRSeksPrototype::doSektionThinksOnApiReady
if ( params.apiParams.is_multi_items && params.apiParams.action === 'sek-refresh-level' ) {
api.preview.send( 'multi-items-module-refreshed', params );
}
if ( _.isUndefined( _ajaxResponse_ ) )
return;
if ( _ajaxResponse_.data && _ajaxResponse_.data.setting_validities ) {
api.preview.send( 'selective-refresh-setting-validities', _ajaxResponse_.data.setting_validities );
}
};
// the action being processed is added as a css class to the body of the preview
// it's used to enable/disable specific css properties during the action
// for example, we don't want css transitions while duplicating or removing a column
self.cachedElements.$body.addClass( msgId );
try {
$.when( _.isFunction( callbackFn ) ? callbackFn( params ) : self[callbackFn].call( self, params ) )
.done( function( _ajaxResponse_ ) {
sendSuccessDataToPanel( _ajaxResponse_ );
})
.fail( function() {
api.preview.send( 'sek-notify', { type : 'error', duration : 10000, message : sekPreviewLocalized.i18n['Something went wrong, please refresh this page.'] });
})
.always( function( _ajaxResponse_ ) {
self.cachedElements.$body.removeClass( msgId );
})
.then( function() {
api.preview.trigger( 'control-panel-requested-action-done', { action : msgId, args : params } );
// Focus on the edited level
self.mayBeAnimateToEditedLevel( params );
});
} catch( _er_ ) {
self.errare( 'reactToPanelMsg => Error when firing the callback of ' + msgId , _er_ );
self.cachedElements.$body.removeClass( msgId );
}
// set the activeElement if needed/possible
if ( _.isObject( params ) && params.apiParams && params.apiParams.id ) {
$_activeElement = self.activeLevelEl();
if ( !$_activeElement || !_.isObject($_activeElement) || $_activeElement.length < 1 || self.activeLevelUI() !== params.apiParams.id ) {
self.activeLevelEl( $('[data-sek-id="' + params.apiParams.id + '"]' ) );
$_activeElement = self.activeLevelEl();
}
}
// Focus on the edited level
self.mayBeAnimateToEditedLevel( params );
};//_apiPreviewCallback
// Bind api preview
_.each( msgCollection, function( callbackFn, msgId ) {
if ( 'sek-refresh-stylesheet' === msgId ) {
api.preview.bind( msgId, function( params ) {
_apiPreviewCallback( params, callbackFn, msgId );
});// api.preview.bind( msgId, function( params ) {
// September 2020 : added a debounced callback when generating stylesheet in order to prevent
// css value may not be taken into account when typed fast, for example an height in pixels
// https://github.com/presscustomizr/nimble-builder/issues/742
api.preview.bind( msgId, _.debounce( function( params ) {
_apiPreviewCallback( params, callbackFn, msgId );
if ( params && params.apiParams && params.apiParams.id ) {
api.preview.trigger('sek-animate-to-level', { id : params.apiParams.id });
}
}, 1000 ));// api.preview.bind( msgId, function( params ) {
} else if ( 'sek-refresh-stylesheet-in-the-background-for-custom-css' === msgId ) {
api.preview.bind( msgId, _.debounce( function( params ) {
_apiPreviewCallback( params, callbackFn, msgId );
if ( params && params.apiParams && params.apiParams.id ) {
api.preview.trigger('sek-animate-to-level', { id : params.apiParams.id });
}
}, 500 ));// api.preview.bind( msgId, function( params ) {
} else {
api.preview.bind( msgId, function( params ) {
_apiPreviewCallback( params, callbackFn, msgId );
});// api.preview.bind( msgId, function( params ) {
}
});
},//schedulePanelMsgReactions()
mayBeAnimateToEditedLevel : function( params ) {
var self = this;
// MAY 2020 : focus on the edited element
if ( _.isObject( params ) && params.apiParams && params.apiParams.id ) {
$elToFocusOn = $('[data-sek-id="' + params.apiParams.id + '"]' );
// if user scrolled while editing an element, let's focus again
if ( 0 < $elToFocusOn.length && !nb_.isInScreen( $elToFocusOn[0]) ) {
$elToFocusOn[0].scrollIntoView({
behavior: 'auto',
block: 'center',
inline: 'center'
});
}
}
}
});//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
$.extend( SekPreviewPrototype, {
// this method is used when creating or duplicating a sektion
// @return a promise()
ajaxAddSektion : function( params ) {
var self = this;
// will be cleaned on ajax.done()
// @see ::scheduleTheLoaderCleaning
self.mayBePrintLoader({
loader_located_in_level_id : params.apiParams.location
});
return self.doAjax({
action : 'sek_get_content',
id : params.apiParams.id,
in_sektion : params.apiParams.in_sektion,
in_column : params.apiParams.in_column,
location_skope_id : params.location_skope_id,
local_skope_id : params.local_skope_id,
sek_action : params.apiParams.action,
is_nested : params.apiParams.is_nested,
// The following params have been introduced when implementing support for multi-section pre-build sections
// @see https://github.com/presscustomizr/nimble-builder/issues/489
content_type : ( params.apiParams && params.apiParams.content_type ) ? params.apiParams.content_type : null,
collection_of_preset_section_id : ( params.apiParams && params.apiParams.collection_of_preset_section_id ) ? params.apiParams.collection_of_preset_section_id : []
}).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::ajaxAddSektion => ajax_response.data.contents is undefined ', _r_ );
self.errare( 'params ?', params );
}
// Embed
// is it a nested sektion ?
var $parentColumn;
if ( params.apiParams.is_nested ) {
$parentColumn = $( '.sektion-wrapper').find( 'div[data-sek-id="' + params.apiParams.in_column + '"]' );
if ( 1 > $parentColumn.length ) {
self.errare( 'preview => reactToPanelMsg => sek-add-column => no DOM node for parent column => ', params.apiParams.in_column );
}
var placeholderHtml = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.in_column + '"></span>';
$parentColumn.before( placeholderHtml );
// remove and re-render the entire column
$parentColumn.remove();
$( '.sektion-wrapper').find( '.sek-placeholder' ).after( html_content );
$( '.sektion-wrapper').find( '.sek-placeholder' ).remove();
} else {
// DUPLICATE CASE
// Insert the clone section right after its cloned sister
if ( 'sek-duplicate-section' == params.apiParams.action && ! _.isEmpty( params.cloneId ) ) {
$( '.sektion-wrapper').find( 'div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).after( html_content );
}
// GENERATED WHEN ADDING A MODULE
else {
// When a section has been created by adding a module ( @see sek-add-content-in-new-sektion )
// we need to append it to a specific location
// otherwise, we append it at the end of the section collection
$beforeCandidate = $( '.sektion-wrapper[data-sek-id="' + params.apiParams.location + '"]').find( 'div[data-sek-id="' + params.apiParams.before_section + '"]' );
$afterCandidate = $( '.sektion-wrapper[data-sek-id="' + params.apiParams.location + '"]').find( 'div[data-sek-id="' + params.apiParams.after_section + '"]' );
if ( ! _.isEmpty( params.apiParams.before_section ) && $beforeCandidate.length > 0 ) {
$beforeCandidate.before( html_content );
} else if ( ! _.isEmpty( params.apiParams.after_section ) && $afterCandidate.length > 0 ) {
$afterCandidate.after( html_content );
} else {
$( '[data-sek-id="' + params.apiParams.location + '"]').append( html_content );
}
}
}
// When a section is duplicated, fire sortable for the inner-column modules
if ( 'sek-duplicate-section' == params.apiParams.action ) {
// re-instantiate sortable in the refreshed columns of the section
// + make columns resizable
$( 'div[data-sek-id="' + params.cloneId + '"]', '.sektion-wrapper').each( function() {
$(this).trigger('sek-columns-refreshed');
});
}
// refresh sortable for the inner column if nested sektion case
if ( params.apiParams.is_nested ) {
self.makeModulesSortableInColumn( params.apiParams.in_column );
$( '.sektion-wrapper').find( 'div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).each( function() {
self.maybeMakeColumnResizableInSektion.call( this );
});
}
// say it to the parent sektion
//=> will be listened to clean the loader overlay just in time
if ( params.cloneId ) {
$( 'div[data-sek-id="' + params.cloneId + '"]' ).trigger('sek-section-added', params );
}
// @todo is the params.apiParams.id correct ? Does it actually exists in the DOM?
$( 'div[data-sek-id="' + params.apiParams.id + '"]' ).trigger('sek-section-added', params );
// added to fix resizable not instantiated when adding column modules
// @see https://github.com/presscustomizr/nimble-builder/issues/523
$( 'div[data-sek-id="' + params.apiParams.location + '"]' ).trigger('sek-location-refreshed', params );
}).fail( function( _r_ ) {
self.errare( 'ERROR in sek_get_html_for_injection ? ' , _r_ );
$( '[data-sek-id="' + params.apiParams.id + '"]' )
.trigger( 'sek-ajax-error' );
});
}//ajaxAddSektion()
});//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
$.extend( SekPreviewPrototype, {
// refresh column is used to
// 1) Add a new column
// 2) re-render the column collection in a sektion
ajaxRefreshColumns : function( params ) {
var self = this;
// will be cleaned on 'sek-columns-refreshed'
self.mayBePrintLoader({
loader_located_in_level_id : params.apiParams.in_sektion
});
return self.doAjax( {
action : 'sek_get_content',
id : params.apiParams.id,
in_sektion : params.apiParams.in_sektion,
location_skope_id : params.location_skope_id,
local_skope_id : params.local_skope_id,
sek_action : params.apiParams.action// sek-add-column || sek-remove-column
}).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::ajaxRefreshColumns => ajax_response.data.contents is undefined ', _r_ );
self.errare( 'params ?', params );
}
var $parentSektion = $( 'div[data-sek-id="' + params.apiParams.in_sektion + '"]' );
if ( 1 > $parentSektion.length ) {
self.errare( 'reactToPanelMsg => ' + params.apiParams.action + ' => no DOM node for parent sektion => ', params.apiParams.in_sektion );
}
var placeholderHtml = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.in_sektion + '"></span>';
$parentSektion.before( placeholderHtml );
// remove and re-render the entire sektion
$parentSektion.remove();
$( '.sektion-wrapper').find( '.sek-placeholder' ).after( html_content );
$( '.sektion-wrapper').find( '.sek-placeholder' ).remove();
// re-generate the stylesheet => this will take into account the reset width of each column
api.preview.trigger( 'sek-refresh-stylesheet', params );
// say it to the parent sektion
//=> will be listened to by the column to re-instantiate sortable, resizable
//=> also listened to clean the loader overalay in time
$('div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).trigger('sek-columns-refreshed', { in_sektion : params.apiParams.in_sektion } );
}).fail( function( _r_ ) {
self.errare( 'ERROR reactToPanelMsg => sek-add-column => ' , _r_ );
$( '[data-sek-id="' + params.apiParams.id + '"]' )
.trigger( 'sek-ajax-error' );
});
},//ajaxRefreshColumns()
ajaxResizeColumns : function( params ) {
var self = this;
// will be cleaned on 'sek-module-refreshed'
self.mayBePrintLoader({
loader_located_in_level_id : params.apiParams.in_sektion
});
return self.doAjax( {
action : 'sek_get_content',
resized_column : params.apiParams.resized_column,
sister_column : params.apiParams.sister_column,
location_skope_id : params.location_skope_id,
local_skope_id : params.local_skope_id,
sek_action : 'sek-resize-columns'
}).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::ajaxResizeColumns => ajax_response.data.contents is undefined ', _r_ );
self.errare( 'params ?', params );
}
//self.errare('sek-preview => resize-column ajax response => ', html_content );
// Reset the automatic default resizable inline styling
$( '[data-sek-id="' + params.apiParams.resized_column + '"]' ).css({
width : '',
height: ''
});
//Append
self.appendDynStyleSheet( params.location_skope_id, html_content );
// say it
// listened to clean the loader just in time
$('div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).trigger('sek-columns-refreshed');
}).fail( function( _r_ ) {
self.errare( 'ERROR reactToPanelMsg => sek-resize-columns => ' , _r_ );
$( '[data-sek-id="' + params.apiParams.in_sektion + '"]' )
.trigger( 'sek-ajax-error' );
});
}
});//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
$.extend( SekPreviewPrototype, {
// refresh module is used to
// 1) Add a new module
// 2) re-render the module collection in a column, typically after a sortable move, or a module removal
ajaxRefreshModulesAndNestedSections : function( params ) {
var self = this;
// will be cleaned on 'sek-module-refreshed'
self.mayBePrintLoader({
loader_located_in_level_id : params.apiParams.in_column
});
return self.doAjax( {
action : 'sek_get_content',
id : params.apiParams.id,
in_sektion : params.apiParams.in_sektion,
in_column : params.apiParams.in_column,
location_skope_id : params.location_skope_id,
local_skope_id : params.local_skope_id,
sek_action : params.apiParams.action, // can be sek-add-module / refresh-modules-in-column
is_nested : params.apiParams.is_nested
}).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::ajaxRefreshModulesAndNestedSections => ajax_response.data.contents is undefined ', _r_ );
self.errare( 'params ?', params );
}
var $parentColumn = $('[data-sek-id="' + params.apiParams.in_column + '"]' );
if ( 1 > $parentColumn.length ) {
self.errare( 'reactToPanelMsg => ajaxRefreshModulesAndNestedSections => no DOM node for parent column => ', params.apiParams.in_column );
}
var placeholderHtml = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.in_column + '"></span>';
$parentColumn.before( placeholderHtml );
// remove and re-render the entire column
$parentColumn.remove();
$( '[data-sek-placeholder-for="' + params.apiParams.in_column + '"]' ).after( html_content );
$( '[data-sek-placeholder-for="' + params.apiParams.in_column + '"]' ).remove();
// say it to the column
//=> will be listened to by the column to re-instantiate sortable, resizable and fittext
$( '[data-sek-id="' + params.apiParams.in_column + '"]' ).trigger('sek-modules-refreshed', { in_column : params.apiParams.in_column, in_sektion : params.apiParams.in_sektion });
}).fail( function( _r_ ) {
self.errare( 'ERROR reactToPanelMsg => sek-add-module => ' , _r_ );
$( '[data-sek-id="' + params.apiParams.id + '"]' )
.trigger( 'sek-ajax-error' );
});
}//ajaxRefreshModulesAndNestedSections()
});//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
$.extend( SekPreviewPrototype, {
ajaxRefreshStylesheet : function( params ) {
var self = this;
// will be cleaned on 'sek-module-refreshed'
if ( true !== params.dont_print_loader ) {
self.mayBePrintLoader({
loader_located_in_level_id : params.apiParams.id
});
}
return self.doAjax( {
action : 'sek_get_content',
location_skope_id : params.location_skope_id,
local_skope_id : params.local_skope_id,
sek_action : 'sek-refresh-stylesheet'
}).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;
}
self.appendDynStyleSheet( params.location_skope_id, html_content );
//=> 'sek-level-refreshed' is listened to clean the loader overlay in time
$( '[data-sek-id="' + params.apiParams.id + '"]' )
.trigger( 'sek-stylesheet-refreshed', { level : params.apiParams.level, id : params.apiParams.id } );
}).fail( function( _r_ ) {
self.errare('sek-refresh-stylesheet fail !');
$( '[data-sek-id="' + params.apiParams.id + '"]' )
.trigger( 'sek-ajax-error' );
});
},
appendDynStyleSheet : function( location_skope_id, styleMarkup ) {
var _stylesheet_id_ = '#sek-' + location_skope_id,//@see php Sek_Dyn_CSS_Handler
_gfonts_id_ = '#' + sekPreviewLocalized.googleFontsStyleId,//@see php Sek_Dyn_CSS_Handler
_global_option_inline_style_id_ = '#' + sekPreviewLocalized.globalOptionsStyleId;
// Remove a dynamic inline stylesheet if already printed
if ( 0 < $('head').find( _stylesheet_id_ ).length ) {
$('head').find( _stylesheet_id_ ).remove();
}
if ( 0 < $('head').find( _gfonts_id_ ).length ) {
$('head').find( _gfonts_id_ ).remove();
}
if ( 0 < $('head').find( _global_option_inline_style_id_ ).length ) {
$('head').find( _global_option_inline_style_id_ ).remove();
}
if ( !_.isEmpty( styleMarkup ) ) {
$('head').append( styleMarkup );
}
// Has it be printed ?
// if we have something to print ( styleMarkup not empty ), there should be a dom element
if ( !_.isEmpty( styleMarkup ) && 1 > $('head').find( _stylesheet_id_ ).length && 1 > $('head').find( _gfonts_id_ ).length && 1 > $('head').find( _global_option_inline_style_id_ ).length ) {
this.errare( 'sek-preview => problem when printing the dynamic inline style for : '+ _stylesheet_id_, styleMarkup );
} else {
$('head').find( _stylesheet_id_ ).attr('sek-data-origin', 'customizer' );
}
}//appendDynStyleSheet()
});//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
$.extend( SekPreviewPrototype, {
// inspired from wp.template in wp-includes/js/wp-util.js
parseTemplate : _.memoize(function ( id ) {
var self = this;
var compiled,
//
// Underscore's default ERB-style templates are incompatible with PHP
// when asp_tags is enabled, so WordPress uses Mustache-inspired templating syntax.
//
// @see trac ticket #22344.
//
options = {
evaluate: /<#([\s\S]+?)#>/g,
interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
escape: /\{\{([^\}]+?)\}\}(?!\})/g,
variable: 'data'
};
return function ( data ) {
if ( $( id ).length < 1 ) {
self.errare( 'preview => parseTemplate => the requested tmpl does not exist =>' + id );
return '';
}
try { compiled = compiled || _.template( $( id ).html(), options );} catch( _er_ ) {
self.errare( 'preview => parseTemplate => problem when parsing tmpl =>' + id, _er_ );
}
return compiled( data );
};
}),
//@return [] for console method
//@bgCol @textCol are hex colors
//@arguments : the original console arguments
_prettyPrintLog : function( args ) {
var _defaults = {
bgCol : '#5ed1f5',
textCol : '#000',
consoleArguments : []
};
args = _.extend( _defaults, args );
var _toArr = Array.from( args.consoleArguments ),
_truncate = function( string ){
if ( ! _.isString( string ) )
return '';
return string.length > 300 ? string.substr( 0, 299 ) + '...' : string;
};
//if the array to print is not composed exclusively of strings, then let's stringify it
//else join(' ')
if ( ! _.isEmpty( _.filter( _toArr, function( it ) { return ! _.isString( it ); } ) ) ) {
_toArr = JSON.stringify( _toArr.join(' ') );
} else {
_toArr = _toArr.join(' ');
}
return [
'%c ' + _truncate( _toArr ),
[ 'background:' + args.bgCol, 'color:' + args.textCol, 'display: block;' ].join(';')
];
},
_wrapLogInsideTags : function( title, msg, bgColor ) {
//fix for IE, because console is only defined when in F12 debugging mode in IE
if ( ( _.isUndefined( console ) && typeof window.console.log != 'function' ) )
return;
if ( sekPreviewLocalized.isDevMode ) {
if ( _.isUndefined( msg ) ) {
console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ '<' + title + '>' ] } ) );
} else {
console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ '<' + title + '>' ] } ) );
console.log( msg );
console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ '</' + title + '>' ] } ) );
}
} else {
console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ title ] } ) );
}
},
errare : function( title, msg ) { this._wrapLogInsideTags( title, msg, '#ffd5a0' ); },
infoLog : function( title, msg ) { this._wrapLogInsideTags( title, msg, '#5ed1f5' ); },
//encapsulates a WordPress ajax request in a normalize method
//@param queryParams = {}
//
//Important : we MUST use $.ajax() because when previewing, the ajax requests params are amended with a preFilter ( see customize-preview.js, $.ajaxPrefilter( prefilterAjax ) )in order to include params
//in particular the 'customized' dirty values, that NB absolutely needs to dynamically register settings that have not yet been instantiated by WP_Customize_Manager
// see WP Core => class WP_Customize_Manager, add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 );
// see NB => class SEK_CZR_Dyn_Register
// see NB => Nimble_Collection_Setting::filter_previewed_sek_get_skoped_seks => this is how we can get the sektions collection while customizing, see sek_get_skoped_seks()
doAjax : function( queryParams ) {
var self = this;
//do we have a queryParams ?
queryParams = queryParams || ( _.isObject( queryParams ) ? queryParams : {} );
var ajaxUrl = queryParams.ajaxUrl || sekPreviewLocalized.ajaxUrl,//the ajaxUrl can be specified when invoking doAjax
nonce = sekPreviewLocalized.frontNonce,//{ 'id' => 'HuFrontNonce', 'handle' => wp_create_nonce( 'hu-front-nonce' ) },
dfd = $.Deferred(),
_query_ = _.extend( {
action : '',
withNonce : false
},
queryParams
);
// Check if the ajax url passes WP core customize-preview test
// Nov 2020 : added when fixing WPML compat https://github.com/presscustomizr/nimble-builder/issues/753
var urlParser = document.createElement( 'a' );
urlParser.href = ajaxUrl;
// Abort if the request is not for this site.
if ( ! api.isLinkPreviewable( urlParser, { allowAdminAjax: true } ) ) {
self.errare( 'self.doAjax => error => !api.isLinkPreviewable for action ' + _query_.action, urlParser );
return dfd.resolve().promise();
}
// HTTP ajaxurl when site is HTTPS causes Access-Control-Allow-Origin failure in Desktop and iOS Safari
if ( "https:" == document.location.protocol ) {
ajaxUrl = ajaxUrl.replace( "http://", "https://" );
}
//check if we're good
if ( _.isEmpty( _query_.action ) || ! _.isString( _query_.action ) ) {
self.errare( 'self.doAjax : unproper action provided' );
return dfd.resolve().promise();
}
//setup nonce
//Note : the nonce might be checked server side ( not in all cases, only when writing in db ) with check_ajax_referer( 'hu-front-nonce', 'HuFrontNonce' )
_query_[ nonce.id ] = nonce.handle;
if ( ! _.isObject( nonce ) || _.isUndefined( nonce.id ) || _.isUndefined( nonce.handle ) ) {
self.errare( 'self.doAjax : unproper nonce' );
return dfd.resolve().promise();
}
// introduced for https://github.com/presscustomizr/nimble-builder/issues/494
// september 2019
// this guid is used to differentiate dynamically rendered content from static content that may include a Nimble generated HTML structure
// an attribute "data-sek-preview-level-guid" is added to each rendered level when customizing or ajaxing
// otherwise the preview UI can be broken
_query_[ 'preview-level-guid' ] = sekPreviewLocalized.previewLevelGuid;
// introduced in october 2019 for https://github.com/presscustomizr/nimble-builder/issues/401
// Those query params are used for template tags related to the WP Query.
// Like "{{the_title}}" => in this case, using "get_the_title()" as callback when ajaxing with Nimble will return nothing. We need get_the_title( $post_id )
// That's why those static query params are written in the preview frame, and used as ajax params that we can access server side via php $_POST.
_query_.czr_query_params = JSON.stringify( _.isObject( _wpCustomizeSettings.czr_query_params ) ? _wpCustomizeSettings.czr_query_params : [] );
// note that the $_POST['customized'] param is set by core WP customizer-preview.js with $.ajaxPrefilter
$.post( ajaxUrl, _query_ )
.done( function( _r ) {
// Check if the user is logged out.
if ( '0' === _r || '-1' === _r || false === _r.success ) {
self.errare( 'self.doAjax : done ajax error for action : ' + _query_.action , _r );
dfd.reject( _r );
}
dfd.resolve( _r );
})
.fail( function( _r ) {
self.errare( 'self.doAjax : failed ajax error for : ' + _query_.action, _r );
dfd.reject( _r );
});
//.always( function( _r ) { dfd.resolve( _r ); });
return dfd.promise();
},//doAjax
// @return boolean
isModuleRegistered : function( moduleType ) {
return sekPreviewLocalized.registeredModules && ! _.isUndefined( sekPreviewLocalized.registeredModules[ moduleType ] );
},
//@return mixed
getRegisteredModuleProperty : function( moduleType, property ) {
if ( ! this.isModuleRegistered( moduleType ) ) {
return 'not_set';
}
return sekPreviewLocalized.registeredModules[ moduleType ][ property ];
},
// @params = { id : '', level : '' }
// Recursively walk the level tree until a match is found
// @return the level model object
getLevelModel : function( id, collection ) {
var self = this, _data_ = 'no_match';
// do we have a collection ?
// if not, let's use the root one
if ( _.isUndefined( collection ) ) {
self.errare( 'getLevelModel => a collection must be provided' );
}
_.each( collection, function( levelData ) {
// did we have a match recursively ?
if ( 'no_match' != _data_ )
return;
if ( id === levelData.id ) {
_data_ = levelData;
} else {
if ( _.isArray( levelData.collection ) ) {
_data_ = self.getLevelModel( id, levelData.collection );
}
}
});
return _data_;
},
});//$.extend()
})( wp.customize, jQuery, _ );//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
(function( api, $, _ ) {
$.extend( SekPreviewPrototype, api.Events );
var SekPreviewConstructor = api.Class.extend( SekPreviewPrototype );
api.bind( 'preview-ready', function(){
api.preview.bind( 'active', function() {
try { api.sekPreview = new SekPreviewConstructor(); } catch( _er_ ) {
SekPreviewPrototype.errare( 'SekPreviewConstructor => problem on instantiation', _er_ );
}
});
});
})( wp.customize, jQuery, _ );