22526 lines
1.3 MiB
Plaintext
22526 lines
1.3 MiB
Plaintext
//global sektionsLocalizedData
|
|
//global serverControlParams
|
|
(function ( api, $ ) {
|
|
api.CZR_Helpers.getInputSubTemplate = function( template_name ) {
|
|
if ( $('#tmpl-nimble-subtemplate___' + template_name ).length > 0 ) {
|
|
return wp.template( 'nimble-subtemplate___' + template_name );
|
|
} else {
|
|
api.errare( 'problem in api.czr_sektions.getInputSubTemplate(), missing js template in the DOM for template_name : ' + template_name );
|
|
return null;
|
|
}
|
|
};
|
|
|
|
var getInputTemplate = function( input_type ) {
|
|
var template_name = input_type;
|
|
switch( input_type ) {
|
|
case 'czr_layouts' ://<= specific to the hueman theme
|
|
case 'select' ://<= used in the customizr and hueman theme
|
|
case 'simpleselect' ://<=used in Nimble Builder
|
|
case 'fa_icon_picker' :
|
|
case 'font_picker':
|
|
case 'animation_picker' ://<= oct 2020 added for https://github.com/presscustomizr/nimble-builder-pro/issues/73
|
|
template_name = 'simpleselect';
|
|
break;
|
|
|
|
case 'simpleselectWithDeviceSwitcher':
|
|
template_name = 'simpleselect_deviceswitcher';
|
|
break;
|
|
|
|
case 'multiselect':
|
|
case 'category_picker':
|
|
template_name = 'multiselect';
|
|
break;
|
|
|
|
case 'h_alignment' :
|
|
case 'horizAlignmentWithDeviceSwitcher' :
|
|
template_name = 'h_alignment';
|
|
break;
|
|
|
|
case 'h_text_alignment' :
|
|
case 'horizTextAlignmentWithDeviceSwitcher' :
|
|
template_name = 'h_text_alignment';
|
|
break;
|
|
|
|
case 'range_simple' :
|
|
case 'range_simple_device_switcher' :
|
|
template_name = 'range_simple';
|
|
break;
|
|
|
|
case 'number_simple' :
|
|
template_name = 'number_simple';
|
|
break;
|
|
|
|
case 'font_size' :
|
|
case 'line_height' :
|
|
case 'range_with_unit_picker' :
|
|
case 'range_with_unit_picker_device_switcher' :
|
|
template_name = 'range_with_unit_picker';
|
|
break;
|
|
|
|
case 'spacing' :
|
|
case 'spacingWithDeviceSwitcher' :
|
|
template_name = 'spacing';
|
|
break;
|
|
|
|
case 'upload' :
|
|
case 'upload_url' :
|
|
template_name = 'upload';
|
|
break;
|
|
|
|
case 'bg_position' :
|
|
case 'bgPositionWithDeviceSwitcher' :
|
|
template_name = 'bg_position';
|
|
break;
|
|
|
|
case 'verticalAlignWithDeviceSwitcher' :
|
|
template_name = 'v_alignment';
|
|
break;
|
|
}
|
|
if ( $('#tmpl-nimble-input___' + template_name ).length > 0 ) {
|
|
return wp.template( 'nimble-input___' + template_name );
|
|
} else {
|
|
api.errare( 'problem in getInputTemplate(), missing js template in the DOM for input_type : ' + input_type );
|
|
return null;
|
|
}
|
|
};
|
|
|
|
|
|
// Overrides FMK method
|
|
// @param args
|
|
// control_id: "__nimble__51b2f35191b3__main_settings"
|
|
// item_model: {id: "czr_heading_child_0", title: "", heading_text: "This is a heading.", heading_tag: "h1", h_alignment_css: {…}, …}
|
|
// module_id: "__nimble__51b2f35191b3__main_settings_czr_module"
|
|
// module_type: "czr_heading_child"
|
|
// tmpl: "item-inputs"
|
|
var originalMethod = api.CZR_Helpers.getModuleTmpl;
|
|
api.CZR_Helpers.getModuleTmpl = function( args ) {
|
|
args = _.extend( {
|
|
tmpl : '',
|
|
module_type: '',
|
|
module_id : '',
|
|
cache : true,//<= shall we cache the tmpl or not. Should be true in almost all cases.
|
|
nonce: api.settings.nonce.save//<= do we need to set a specific nonce to fetch the tmpls ?
|
|
}, args );
|
|
|
|
// target only Nimble modules
|
|
// a nimble module id looks like : "__nimble__00b8efefe207_czr_module"
|
|
if ( -1 === args.module_id.indexOf('__nimble__') ) {
|
|
return originalMethod( args );
|
|
}
|
|
|
|
var dfd = $.Deferred();
|
|
// are we good to go ?
|
|
if ( _.isEmpty( args.tmpl ) || _.isEmpty( args.module_type ) ) {
|
|
dfd.reject( 'api.CZR_Helpers.getModuleTmpl => missing tmpl or module_type param' );
|
|
}
|
|
|
|
if ( ! api.czr_sektions.isModuleRegistered( args.module_type ) ) {
|
|
dfd.reject( 'api.CZR_Helpers.getModuleTmpl => module type not registered' );
|
|
dfd.resolve();
|
|
return originalMethod( args );
|
|
}
|
|
|
|
/// TEMP !! ///
|
|
// dfd.resolve();
|
|
// return originalMethod( args );
|
|
if ( _.contains( [
|
|
// 'sek_content_type_switcher_module',
|
|
// 'sek_module_picker_module'
|
|
], args.module_type ) ) {
|
|
dfd.resolve();
|
|
return originalMethod( args );
|
|
}
|
|
/// TEMP !! ///
|
|
|
|
// Get the item input map
|
|
var item_input_tmpls = api.czr_sektions.getRegisteredModuleProperty( args.module_type, 'tmpl' );
|
|
var item_input_map = ( _.isObject( item_input_tmpls ) && item_input_tmpls[ args.tmpl ] ) ? item_input_tmpls[ args.tmpl ] : {};
|
|
if ( _.isEmpty( item_input_map ) || !_.isObject( item_input_map ) ) {
|
|
api.errare( 'getModuleTmpl => Error empty or invalid input map for module : ', args.module_type );
|
|
dfd.reject( 'getModuleTmpl => Error empty or invalid input map for module : ', args.module_type );
|
|
}
|
|
|
|
// Get the item value
|
|
var item_model = args.item_model,
|
|
default_item_model = $.extend( true, {}, api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( args.module_type ) ),
|
|
cloned_default = $.extend( true, {},default_item_model );
|
|
|
|
|
|
// normalizes the item_model with defaults
|
|
item_model = $.extend( cloned_default, item_model );
|
|
|
|
if ( _.isEmpty( item_model ) ) {
|
|
api.errare( 'getModuleTmpl => Error invalid item model for module : ', args.module_type );
|
|
dfd.reject( 'getModuleTmpl => Error invalid item model for module : ', args.module_type );
|
|
}
|
|
|
|
var input_html = '', input_type;
|
|
|
|
// Loop on the item input map and render the input
|
|
// the rendering uses a nested _ template mechanism
|
|
// see https://stackoverflow.com/questions/8938841/underscore-js-nested-templates#13649447
|
|
var renderInputCollection = function( inputCollection ) {
|
|
var _html = '';
|
|
_.each( inputCollection, function( input_data, input_id ){
|
|
input_type = input_data.input_type;
|
|
// render generic input wrapper
|
|
try { _html += wp.template( 'nimble-input-wrapper' )( {
|
|
input_type : input_type,
|
|
input_data : input_data,
|
|
input_id : input_id,
|
|
item_model : item_model,
|
|
input_tmpl : getInputTemplate( input_type ),
|
|
control_id : args.control_id //<= needed for some modules like tiny_mce_editor
|
|
}); } catch( er ) {
|
|
api.errare( 'getModuleTmpl => Error when parsing the nimble-input-wrapper template', er );
|
|
dfd.reject( 'getModuleTmpl => Error when parsing the nimble-input-wrapper template');
|
|
return false;
|
|
}
|
|
});
|
|
return _html;
|
|
};//renderInputCollection
|
|
|
|
|
|
// GENERATE MODULE HTML : two cases, with or without tabs
|
|
if ( item_input_map.tabs ) {
|
|
var _tabNavHtml = '', _tabContentHtml ='';
|
|
|
|
_.each( item_input_map.tabs, function( rawTabData, tabKey ) {
|
|
// normalizes
|
|
var tabData = $.extend( true, {}, rawTabData );
|
|
tabData = $.extend( { inputs : {}, title : '' }, tabData );
|
|
// generate tab nav html
|
|
var _attributes = !_.isEmpty( tabData.attributes ) ? tabData.attributes : '';
|
|
_tabNavHtml += '<li data-tab-id="section-topline-' + ( +tabKey+1 ) +'" '+ _attributes +'><a href="#" title="' + tabData.title + '"><span>' + tabData.title +'</span></a></li>';
|
|
// generate tab content html
|
|
var _inputCollectionHtml = renderInputCollection( tabData.inputs );
|
|
_tabContentHtml += '<section id="section-topline-' + ( +tabKey+1 ) +'">' + _inputCollectionHtml +'</section>';
|
|
});
|
|
|
|
|
|
// put it all together
|
|
input_html += [
|
|
'<div class="tabs tabs-style-topline">',
|
|
'<nav>',
|
|
'<ul>',
|
|
_tabNavHtml,
|
|
'</ul>',
|
|
'</nav>',
|
|
'<div class="content-wrap">',
|
|
_tabContentHtml,
|
|
'</div>',
|
|
'</div>',
|
|
].join('');
|
|
|
|
} else {
|
|
input_html = renderInputCollection(item_input_map);
|
|
}
|
|
|
|
|
|
|
|
// 1) Get the input map from the module registration params
|
|
// 2) Normalizes input data with defaults
|
|
// 3) loop on the input map
|
|
// 4) print the default input tmpl wrapper
|
|
|
|
return dfd.resolve( input_html ).promise();
|
|
};
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
cachedElements : {
|
|
$body : $('body'),
|
|
$window : $(window)
|
|
},
|
|
|
|
initialize: function() {
|
|
var self = this;
|
|
if ( _.isUndefined( window.sektionsLocalizedData ) ) {
|
|
throw new Error( 'CZRSeksPrototype => missing localized server params sektionsLocalizedData' );
|
|
}
|
|
// this class is skope dependant
|
|
if ( ! _.isFunction( api.czr_activeSkopes ) ) {
|
|
throw new Error( 'CZRSeksPrototype => api.czr_activeSkopes' );
|
|
}
|
|
// SECTIONS ID FOR LOCAL AND GLOBAL OPTIONS
|
|
self.SECTION_ID_FOR_GLOBAL_OPTIONS = '__globalOptionsSectionId';
|
|
self.SECTION_ID_FOR_LOCAL_OPTIONS = '__localOptionsSection';
|
|
|
|
// SECTION ID FOR THE CONTENT PICKER
|
|
self.SECTION_ID_FOR_CONTENT_PICKER = '__content_picker__';
|
|
|
|
// Max possible number of columns in a section
|
|
self.MAX_NUMBER_OF_COLUMNS = 12;
|
|
|
|
// _.debounce param when updating the UI setting
|
|
// prevent hammering server + fixes https://github.com/presscustomizr/nimble-builder/issues/244
|
|
self.SETTING_UPDATE_BUFFER = 100;
|
|
|
|
// introduced for https://github.com/presscustomizr/nimble-builder/issues/403
|
|
self.TINYMCE_EDITOR_HEIGHT = 100;
|
|
|
|
// Define a default value for the sektion setting value, used when no server value has been sent
|
|
// @see php function
|
|
// function sek_get_default_location_model() {
|
|
// $defaut_sektions_value = [ 'collection' => [], 'options' => [] ];
|
|
// foreach( sek_get_locations() as $location ) {
|
|
// $defaut_sektions_value['collection'][] = [
|
|
// 'id' => $location,
|
|
// 'level' => 'location',
|
|
// 'collection' => [],
|
|
// 'options' => []
|
|
// ];
|
|
// }
|
|
// return $defaut_sektions_value;
|
|
// }
|
|
self.defaultLocalSektionSettingValue = self.getDefaultSektionSettingValue( 'local' );
|
|
|
|
// Store the contextual setting prefix
|
|
self.localSectionsSettingId = new api.Value( {} );
|
|
|
|
// Keep track of the registered ui elements dynamically registered
|
|
// this collection is populated in ::register(), if the track param is true
|
|
// this is used to know what ui elements are currently being displayed
|
|
self.registered = new api.Value([]);
|
|
|
|
// June 2020 : added for https://github.com/presscustomizr/nimble-builder/issues/708
|
|
if ( wp.customize.apiIsReady ) {
|
|
self.doSektionThinksOnApiReady();
|
|
} else {
|
|
api.bind( 'ready', function() {
|
|
self.doSektionThinksOnApiReady();
|
|
});
|
|
}
|
|
|
|
|
|
// Add the skope id on save
|
|
// Uses a WP core hook to filter the query on a customize_save action
|
|
//
|
|
// This posted skope id is useful when we need to know the skope id during ajax.
|
|
// ( Note that with the nimble ajax action, the skope_id is always posted. Not in WP core ajax actions. )
|
|
// Example of use of $_POST['local_skope_id'] => @see sek_get_parent_level_model()
|
|
// Helps fixing : https://github.com/presscustomizr/nimble-builder/issues/242, for which sek_add_css_rules_for_spacing() couldn't be set for columns margins
|
|
api.bind( 'save-request-params', function( query ) {
|
|
$.extend( query, {
|
|
local_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),
|
|
group_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id', 'group' ),//<= feb 2021, added for #478
|
|
active_locations : api.czr_sektions.activeLocations(),
|
|
inherit_group_template : true
|
|
});
|
|
});
|
|
|
|
|
|
// TINY MCE EDITOR
|
|
var clearActiveWPEditorsInstances = function() {
|
|
if ( _.isArray( api.czrActiveWPEditors ) ) {
|
|
_.each( api.czrActiveWPEditors, function( _id ) {
|
|
wp.oldEditor.remove( _id );
|
|
});
|
|
api.czrActiveWPEditors = [];
|
|
}
|
|
};
|
|
// added for https://github.com/presscustomizr/nimble-builder/issues/403
|
|
// in fmk::setupTinyMceEditor => each id of newly instantiated editor is added to the [] api.czrActiveWPEditors
|
|
// We need to remove those instances when cleaning registered controls
|
|
api.bind( 'sek-before-clean-registered', clearActiveWPEditorsInstances );
|
|
|
|
// When using the text editor in the items of in a multi-item module
|
|
// We need to clear the editor instances each time all items are closed, before opening a new one
|
|
// 'czr-all-items-closed' is fired in CZRModuleMths.closeAllItems()
|
|
api.bind('czr-all-items-closed', clearActiveWPEditorsInstances );
|
|
},// initialize()
|
|
|
|
|
|
// @ API READY
|
|
// Fired at api.bind( 'ready', function() {})
|
|
doSektionThinksOnApiReady : function() {
|
|
var self = this;
|
|
// the main sektion panel
|
|
// the local and global options section
|
|
self.registerAndSetupDefaultPanelSectionOptions();
|
|
|
|
// Setup the collection settings => register the main settings for local and global skope and bind it
|
|
// schedule reaction to collection setting ids => the setup of the collection setting when the collection setting ids are set
|
|
//=> on skope change
|
|
//@see setContextualCollectionSettingIdWhenSkopeSet
|
|
//
|
|
// var _settingsToRegister_ = {
|
|
// 'local' : { collectionSettingId : self.localSectionsSettingId() },//<= "nimble___[skp__post_page_10]"
|
|
// 'global' : { collectionSettingId : self.getGlobalSectionsSettingId() }//<= "nimble___[skp__global]"
|
|
// };
|
|
self.localSectionsSettingId.callbacks.add( function( collectionSettingIds, previousCollectionSettingIds ) {
|
|
// register the collection setting id
|
|
// and schedule the reaction to different collection changes : refreshModules, ...
|
|
try { self.setupSettingsToBeSaved(); } catch( er ) {
|
|
api.errare( 'Error in self.localSectionsSettingId.callbacks => self.setupSettingsToBeSaved()' , er );
|
|
}
|
|
|
|
// Now that the local and global settings are registered, initialize the history log
|
|
self.initializeHistoryLogWhenSettingsRegistered();
|
|
|
|
// On init and when skope changes, request the contextually active locations
|
|
// We should not need this call, because the preview sends it on initialize
|
|
// But this is safer.
|
|
// The preview send back the list of active locations 'sek-active-locations-in-preview'
|
|
// introduced for the level tree, https://github.com/presscustomizr/nimble-builder/issues/359
|
|
api.previewer.send('sek-request-active-locations');
|
|
});
|
|
|
|
|
|
// POPULATE THE MAIN SETTING ID NOW
|
|
// + GENERATE UI FOR THE LOCAL SKOPE OPTIONS
|
|
// + GENERATE UI FOR THE GLOBAL OPTIONS
|
|
// + GENERATE UI FOR SITE TEMPLATES
|
|
var doSkopeDependantActions = function( newSkopes, previousSkopes ) {
|
|
self.setContextualCollectionSettingIdWhenSkopeSet( newSkopes, previousSkopes );
|
|
|
|
// Generate UI for the local skope options
|
|
api.section( self.SECTION_ID_FOR_LOCAL_OPTIONS, function( _section_ ) {
|
|
_section_.deferred.embedded.done( function() {
|
|
if( true === _section_.boundForLocalOptionGeneration )
|
|
return;
|
|
// Defer the UI generation when the section is expanded
|
|
_section_.boundForLocalOptionGeneration = true;
|
|
_section_.expanded.bind( function( expanded ) {
|
|
if ( true === expanded ) {
|
|
self.generateUI({ action : 'sek-generate-local-skope-options-ui'});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
// The UI of the global option must be generated only once.
|
|
// We don't want to re-generate on each skope change
|
|
// fixes https://github.com/presscustomizr/nimble-builder/issues/271
|
|
api.section( self.SECTION_ID_FOR_GLOBAL_OPTIONS, function( _section_ ) {
|
|
if ( true === _section_.nimbleGlobalOptionGenerated )
|
|
return;
|
|
self.generateUI({ action : 'sek-generate-global-options-ui'});
|
|
_section_.nimbleGlobalOptionGenerated = true;
|
|
// Make sure template gallery is closed when opening/closing global options panel
|
|
// see https://github.com/presscustomizr/nimble-builder/issues/840
|
|
_section_.expanded.bind( function() {
|
|
if ( !self.templateGalleryExpanded )
|
|
return;
|
|
self.templateGalleryExpanded(false);
|
|
});
|
|
});
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
// June 2020 property introduced for https://github.com/presscustomizr/nimble-builder-pro/issues/12
|
|
self.nb_is_ready = true;
|
|
|
|
// This event has been introduced when implementing https://github.com/presscustomizr/nimble-builder/issues/304
|
|
api.trigger('nimble-ready-for-current-skope');
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
};//doSkopeDependantActions()
|
|
|
|
// populate the setting ids now if skopes are set
|
|
if ( !_.isEmpty( api.czr_activeSkopes().local ) ) {
|
|
doSkopeDependantActions();
|
|
}
|
|
// ON SKOPE READY
|
|
// - Set the contextual setting prefix
|
|
// - Generate UI for Nimble local skope options
|
|
// - Generate the content picker
|
|
api.czr_activeSkopes.callbacks.add( function( newSkopes, previousSkopes ) {
|
|
doSkopeDependantActions( newSkopes, previousSkopes );
|
|
});
|
|
|
|
|
|
// Communicate with the preview
|
|
self.reactToPreviewMsg();
|
|
|
|
// Setup Dnd
|
|
self.setupDnd();
|
|
|
|
|
|
// setup the tinyMce editor used for the tiny_mce_editor input
|
|
// => one object listened to by each tiny_mce_editor input
|
|
self.setupTinyMceEditor();
|
|
|
|
// print json
|
|
self.schedulePrintSectionJson();
|
|
|
|
// Always set the previewed device back to desktop on ui change
|
|
// event 'sek-ui-removed' id triggered when cleaning the registered ui controls
|
|
// @see ::cleanRegisteredAndLargeSelectInput()
|
|
// July 2020 commented to fix https://github.com/presscustomizr/nimble-builder/issues/728
|
|
// self.bind( 'sek-ui-removed', function() {
|
|
// api.previewedDevice( 'desktop' );
|
|
// });
|
|
|
|
// Synchronize api.previewedDevice with the currently rendered ui
|
|
// ensure that the selected device tab of the spacing module is the one being previewed
|
|
// =>@see spacing module, in item constructor CZRSpacingItemMths
|
|
api.previewedDevice.bind( function( device ) {
|
|
var currentControls = _.filter( self.registered(), function( uiData ) {
|
|
return 'control' == uiData.what;
|
|
});
|
|
_.each( currentControls || [] , function( ctrlData ) {
|
|
api.control( ctrlData.id, function( _ctrl_ ) {
|
|
_ctrl_.container.find('[data-sek-device="' + device + '"]').each( function() {
|
|
$(this).trigger('click');
|
|
});
|
|
});
|
|
});
|
|
|
|
// Send the previewed device to the preview
|
|
//api.previewer.send( 'sek-preview-device-changed', { device : device });
|
|
});
|
|
|
|
// Schedule a reset
|
|
$('#customize-notifications-area').on( 'click', '[data-sek-reset="true"]', function() {
|
|
api.previewer.trigger('sek-reset-collection', { scope : 'local' } );
|
|
});
|
|
|
|
|
|
// CLEAN UI BEFORE REMOVAL
|
|
// 'sek-ui-pre-removal' is triggered in ::cleanRegisteredAndLargeSelectInput
|
|
// @params { what : control, id : '' }
|
|
self.bind( 'sek-ui-pre-removal', function( params ) {
|
|
// CLEAN DRAG N DROP
|
|
if ( 'control' == params.what && -1 < params.id.indexOf( 'draggable') ) {
|
|
api.control( params.id, function( _ctrl_ ) {
|
|
_ctrl_.container.find( '[draggable]' ).each( function() {
|
|
$(this).off( 'dragstart dragend' );
|
|
});
|
|
});
|
|
}
|
|
|
|
// CLEAN SELECT2
|
|
// => we need to destroy the czrSelect2 instance, otherwise it can stay open when switching to another ui.
|
|
if ( 'control' == params.what ) {
|
|
api.control( params.id, function( _ctrl_ ) {
|
|
_ctrl_.container.find( 'select' ).each( function() {
|
|
if ( ! _.isUndefined( $(this).data('czrSelect2') ) ) {
|
|
$(this).czrSelect2('destroy');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
|
|
// POPULATE THE REGISTERED COLLECTION
|
|
// 'czr-new-registered' is fired in api.CZR_Helpers.register()
|
|
api.bind( 'czr-new-registered', function( params ) {
|
|
//console.log( 'czr-new-registered => ', params );
|
|
// Check that we have an origin property and that make sure we populate only the registration emitted by 'nimble'
|
|
if ( _.isUndefined( params.origin ) ) {
|
|
throw new Error( 'czr-new-registered event => missing params.origin' );
|
|
}
|
|
if ( 'nimble' !== params.origin )
|
|
return;
|
|
|
|
// when no collection is provided, we use
|
|
if ( false !== params.track ) {
|
|
var currentlyRegistered = self.registered();
|
|
var newRegistered = $.extend( true, [], currentlyRegistered );
|
|
//Check for duplicates
|
|
var duplicateCandidate = _.findWhere( newRegistered, { id : params.id } );
|
|
if ( ! _.isEmpty( duplicateCandidate ) && _.isEqual( duplicateCandidate, params ) ) {
|
|
throw new Error( 'register => duplicated element in self.registered() collection ' + params.id );
|
|
}
|
|
newRegistered.push( params );
|
|
self.registered( newRegistered );
|
|
|
|
// say it
|
|
//this.trigger( [params.what, params.id , 'registered' ].join('__'), params );
|
|
}
|
|
});
|
|
|
|
|
|
// store active locations
|
|
self.activeLocations = new api.Value([]);// <= introduced for the level tree, https://github.com/presscustomizr/nimble-builder/issues/359
|
|
self.activeLocationsInfo = new api.Value([]);// <= introduced for better move up/down of sections https://github.com/presscustomizr/nimble-builder/issues/521
|
|
api.previewer.bind('sek-active-locations-in-preview', function( activelocs ){
|
|
self.activeLocations( ( _.isObject(activelocs) && _.isArray( activelocs.active_locations ) ) ? activelocs.active_locations : [] );
|
|
self.activeLocationsInfo( ( _.isObject(activelocs) && _.isArray( activelocs.active_locs_info ) ) ? activelocs.active_locs_info : [] );
|
|
// December 2020 => refresh local setting when an active location is available locally but not present in the local setting
|
|
// Fixes the problem of importing template from the gallery, with locations different than the current local page
|
|
// update : December 24th => deactivated because of https://github.com/presscustomizr/nimble-builder/issues/770
|
|
// if ( !_.isEmpty( api.dirtyValues() ) ) {
|
|
// try{ self.updateAPISetting({ action : 'sek-maybe-add-missing-locations'}); } catch(er) {
|
|
// api.errare( '::initialize => error with sek-maybe-add-missing-locations', er );
|
|
// }
|
|
// }
|
|
});
|
|
|
|
|
|
// TOP BAR
|
|
// Setup the topbar including do/undo action buttons
|
|
self.setupTopBar();//@see specific dev file
|
|
|
|
// SAVE SECTION UI
|
|
// June 2020 : for https://github.com/presscustomizr/nimble-builder/issues/520 and https://github.com/presscustomizr/nimble-builder/issues/713
|
|
self.setupSaveSectionUI();
|
|
|
|
// SAVE TEMPLATE UI
|
|
// April 2020 : introduced for https://github.com/presscustomizr/nimble-builder/issues/655
|
|
self.setupSaveTmplUI();
|
|
|
|
// SETUP DOUBLE CLICK INSERTION THINGS
|
|
// Stores the preview target for double click insertion
|
|
// implemented for https://github.com/presscustomizr/nimble-builder/issues/317
|
|
self.lastClickedTargetInPreview = new api.Value();
|
|
self.lastClickedTargetInPreview.bind( function( to, from ) {
|
|
// to and from are formed this way : { id : "__nimble__fb2ab3e47472" }
|
|
// @see 'sek-pick-content' event in ::reactToPreviewMsg()
|
|
|
|
// Send the level id of the current double-click insertion target
|
|
// => this will be used to style the level id container with a pulse animation
|
|
if ( _.isObject( to ) && to.id ) {
|
|
api.previewer.send( 'sek-set-double-click-target', to );
|
|
} else {
|
|
// Tell the preview to clean the target highlight effect
|
|
api.previewer.send( 'sek-reset-double-click-target' );
|
|
}
|
|
|
|
// reset after a delay
|
|
clearTimeout( self.cachedElements.$window.data('_preview_target_timer_') );
|
|
self.cachedElements.$window.data('_preview_target_timer_', setTimeout(function() {
|
|
// Reset the click target
|
|
self.lastClickedTargetInPreview( {} );
|
|
// Tell the preview to clean the target highlight effect
|
|
api.previewer.send( 'sek-reset-double-click-target' );
|
|
}, 20000 ) );
|
|
});
|
|
|
|
// React to the preview to clean any currently highlighted drop zone
|
|
// This event is triggered on all click in the preview iframe
|
|
// @see preview::scheduleUiClickReactions()
|
|
api.previewer.bind( 'sek-clean-target-drop-zone', function() {
|
|
// Reset the click target
|
|
self.lastClickedTargetInPreview({});
|
|
});
|
|
|
|
// Clean the current target when hitting escape
|
|
$(document).keydown(function( evt ) {
|
|
// ESCAPE key pressed
|
|
if ( evt && 27 === evt.keyCode ) {
|
|
self.lastClickedTargetInPreview({});
|
|
}
|
|
});
|
|
|
|
// PRINT A WARNING NOTICE FOR USERS OF CACHE PLUGIN
|
|
if ( sektionsLocalizedData.hasActiveCachePlugin ) {
|
|
_.delay( function() {
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'has-active-cache-plugin',
|
|
type : 'info',
|
|
duration : 20000,
|
|
message : [
|
|
'<span style="color:#0075a2">',
|
|
sektionsLocalizedData.i18n['You seem to be using a cache plugin.'],
|
|
( ! _.isString( sektionsLocalizedData.hasActiveCachePlugin ) || sektionsLocalizedData.hasActiveCachePlugin.length < 2 ) ? '' : '<strong> (' + sektionsLocalizedData.hasActiveCachePlugin + ')</strong><br/>',
|
|
' <strong>',
|
|
sektionsLocalizedData.i18n['It is recommended to disable your cache plugin when customizing your website.'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
}, 2000 );//delay()
|
|
}
|
|
|
|
// SCHEDULE AN AUTOFOCUS ON THE ITEM THAT HAS BEEN MODIFIED IN THE PREVIEW
|
|
// the 'multi-items-module-refreshed' event is sent on each preview update due to a Nimble change
|
|
// @see sendSuccessDataToPanel() in SekPreviewPrototype::schedulePanelMsgReactions
|
|
api.previewer.bind('multi-items-module-refreshed', function( params ) {
|
|
if ( _.isUndefined( params.apiParams.control_id ) )
|
|
return;
|
|
// the module_id param is added on control registration
|
|
// @see CZRSeksPrototype::generateUIforFrontModules
|
|
// we use it to identify that
|
|
api.control( params.apiParams.control_id, function( _control_ ) {
|
|
if ( _.isUndefined( _control_.params.sek_registration_params ) )
|
|
return;
|
|
if ( api.control( _control_.id ).params.sek_registration_params.module_id !== params.apiParams.id )
|
|
return;
|
|
_control_.czr_Module.each( function( _module_ ) {
|
|
_module_.czr_Item.each( function( _item_ ) {
|
|
if( 'expanded' === _item_.viewState() ) {
|
|
_item_.trigger('sek-request-item-focus-in-preview');
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});//api.previewer.bind()
|
|
|
|
// April 2020. For https://github.com/presscustomizr/nimble-builder/issues/651
|
|
self.setupTemplateGallery();
|
|
|
|
// March 2021. For #478. 'czr-new-skopes-synced' is sent by preview on each refresh
|
|
api.previewer.bind( 'czr-new-skopes-synced', function( skope_server_data ) {
|
|
//console.log('SKOPED SYNCED');
|
|
var localSektionsData = api.czr_skopeBase.getSkopeProperty( 'sektions', 'local');
|
|
if ( sektionsLocalizedData.isDevMode ) {
|
|
api.infoLog( '::czr-new-skopes-synced => SEKTIONS DATA ? ', localSektionsData );
|
|
}
|
|
if ( _.isEmpty( localSektionsData ) ) {
|
|
api.errare('::czr-new-skopes-synced => no sektionsData');
|
|
}
|
|
if ( _.isEmpty( localSektionsData.setting_id ) ) {
|
|
api.errare('::czr-new-skopes-synced => missing setting_id');
|
|
}
|
|
|
|
});
|
|
},//doSektionThinksOnApiReady
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fired at api "ready"
|
|
registerAndSetupDefaultPanelSectionOptions : function() {
|
|
var self = this;
|
|
|
|
// MAIN SEKTION PANEL
|
|
var SektionPanelConstructor = api.Panel.extend({
|
|
//attachEvents : function () {},
|
|
// Always make the panel active, event if we have no sections / control in it
|
|
isContextuallyActive : function () {
|
|
return this.active();
|
|
},
|
|
_toggleActive : function(){ return true; }
|
|
});
|
|
|
|
// Prepend the Nimble logo in the main panel title
|
|
// the panel.expanded() Value is not the right candidate to be observed because it gets changed on too many events, when generating the various UI.
|
|
api.panel( sektionsLocalizedData.sektionsPanelId, function( _mainPanel_ ) {
|
|
_mainPanel_.deferred.embedded.done( function() {
|
|
var $sidePanelTitleEl = _mainPanel_.container.first().find('h3.accordion-section-title'),
|
|
$topPanelTitleEl = _mainPanel_.container.first().find('.panel-meta .accordion-section-title'),
|
|
logoHtml = [
|
|
'<img class="sek-nimble-logo" alt="'+ _mainPanel_.params.title +'" src="',
|
|
sektionsLocalizedData.baseUrl,
|
|
'/assets/img/nimble/nimble_horizontal.svg?ver=' + sektionsLocalizedData.nimbleVersion,
|
|
'"/>',
|
|
].join('');
|
|
// Add Pro
|
|
if ( sektionsLocalizedData.isPro ) {
|
|
logoHtml += [
|
|
'<img class="sek-nimble-logo sek-pro-pastil" src="',
|
|
sektionsLocalizedData.baseUrl,
|
|
'/assets/czr/sek/img/pro_white.svg?ver=' + sektionsLocalizedData.nimbleVersion,
|
|
'"/>',
|
|
].join('');
|
|
}
|
|
|
|
if ( 0 < $sidePanelTitleEl.length ) {
|
|
// The default title looks like this : Nimble Builder <span class="screen-reader-text">Press return or enter to open this section</span>
|
|
// we want to style "Nimble Builder" only.
|
|
var $sidePanelTitleElSpan = $sidePanelTitleEl.find('span');
|
|
$sidePanelTitleEl
|
|
.addClass('sek-side-nimble-logo-wrapper')
|
|
.html( logoHtml )
|
|
.append( $sidePanelTitleElSpan );
|
|
}
|
|
|
|
// default looks like
|
|
// <span class="preview-notice">You are customizing <strong class="panel-title">Nimble Builder</strong></span>
|
|
// if ( 0 < $topPanelTitleEl.length ) {
|
|
// var $topPanelTitleElInner = $topPanelTitleEl.find('.panel-title');
|
|
// $topPanelTitleElInner.html( logoHtml );
|
|
// }
|
|
|
|
// NOV FEEDBACK UI DISABLED in favor of an admin notice
|
|
// if ( sektionsLocalizedData.eligibleForFeedbackNotification ) {
|
|
// _mainPanel_.expanded.bind( function( expanded ) {
|
|
// if ( expanded && _.isUndefined( self.feedbackUIVisible ) ) {
|
|
// // FEEDBACK UI
|
|
// self.setupFeedBackUI();
|
|
|
|
// // march 2020 : print confettis
|
|
// // confettis script is enqueued in the preview
|
|
// setTimeout( function() {
|
|
// api.previewer.send('sek-print-confettis', { duration : Date.now() + (1 * 2000) } );
|
|
// }, 1000 );
|
|
// }
|
|
// });
|
|
// }//if ( sektionsLocalizedData.eligibleForFeedbackNotification )
|
|
});
|
|
});
|
|
|
|
// The parent panel for all ui sections + global options section
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
what : 'panel',
|
|
id : sektionsLocalizedData.sektionsPanelId,//'__sektions__'
|
|
title: sektionsLocalizedData.i18n['Nimble Builder'],
|
|
priority : -1000,
|
|
constructWith : SektionPanelConstructor,
|
|
track : false,//don't register in the self.registered() => this will prevent this container to be removed when cleaning the registered
|
|
});
|
|
|
|
|
|
//GLOBAL OPTIONS SECTION
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
what : 'section',
|
|
id : self.SECTION_ID_FOR_GLOBAL_OPTIONS,
|
|
title: sektionsLocalizedData.i18n['Site wide options'],
|
|
panel : sektionsLocalizedData.sektionsPanelId,
|
|
priority : 20,
|
|
track : false,//don't register in the self.registered() => this will prevent this container to be removed when cleaning the registered
|
|
constructWith : api.Section.extend({
|
|
//attachEvents : function () {},
|
|
// Always make the section active, event if we have no control in it
|
|
isContextuallyActive : function () {
|
|
return this.active();
|
|
},
|
|
_toggleActive : function(){ return true; }
|
|
})
|
|
}).done( function() {
|
|
api.section( self.SECTION_ID_FOR_GLOBAL_OPTIONS, function( _section_ ) {
|
|
// Style the section title
|
|
var $sectionTitleEl = _section_.container.find('.accordion-section-title'),
|
|
$panelTitleEl = _section_.container.find('.customize-section-title h3');
|
|
|
|
// The default title looks like this : Title <span class="screen-reader-text">Press return or enter to open this section</span>
|
|
if ( 0 < $sectionTitleEl.length ) {
|
|
$sectionTitleEl.prepend( '<i class="fas fa-globe sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// The default title looks like this : <span class="customize-action">Customizing</span> Title
|
|
if ( 0 < $panelTitleEl.length ) {
|
|
$panelTitleEl.find('.customize-action').after( '<i class="fas fa-globe sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// Schedule the accordion behaviour
|
|
self.scheduleModuleAccordion.call( _section_ );
|
|
});
|
|
});
|
|
|
|
//LOCAL OPTIONS SECTION
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
what : 'section',
|
|
id : self.SECTION_ID_FOR_LOCAL_OPTIONS,//<= the section id doesn't need to be skope dependant. Only the control id is skope dependant.
|
|
title: sektionsLocalizedData.i18n['Current page options'],
|
|
panel : sektionsLocalizedData.sektionsPanelId,
|
|
priority : 10,
|
|
track : false,//don't register in the self.registered() => this will prevent this container to be removed when cleaning the registered
|
|
constructWith : api.Section.extend({
|
|
//attachEvents : function () {},
|
|
// Always make the section active, event if we have no control in it
|
|
isContextuallyActive : function() {
|
|
return this.active();
|
|
},
|
|
_toggleActive : function(){ return true; }
|
|
})
|
|
}).done( function() {
|
|
api.section( self.SECTION_ID_FOR_LOCAL_OPTIONS, function( _section_ ) {
|
|
// Style the section title
|
|
var $sectionTitleEl = _section_.container.find('.accordion-section-title'),
|
|
$panelTitleEl = _section_.container.find('.customize-section-title h3');
|
|
|
|
// The default title looks like this : Title <span class="screen-reader-text">Press return or enter to open this section</span>
|
|
if ( 0 < $sectionTitleEl.length ) {
|
|
$sectionTitleEl.prepend( '<i class="fas fa-map-marker-alt sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// The default title looks like this : <span class="customize-action">Customizing</span> Title
|
|
if ( 0 < $panelTitleEl.length ) {
|
|
$panelTitleEl.find('.customize-action').after( '<i class="fas fa-map-marker-alt sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// Schedule the accordion behaviour
|
|
self.scheduleModuleAccordion.call( _section_ );
|
|
});
|
|
});
|
|
|
|
|
|
// SITE WIDE GLOBAL OPTIONS SETTING
|
|
// Will Be updated in ::generateUIforGlobalOptions()
|
|
// has no control.
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
//level : params.level,
|
|
what : 'setting',
|
|
id : sektionsLocalizedData.optNameForGlobalOptions,
|
|
dirty : false,
|
|
value : sektionsLocalizedData.globalOptionDBValues,
|
|
transport : 'postMessage',//'refresh',//// ,
|
|
type : 'option'
|
|
});
|
|
|
|
// CONTENT PICKER SECTION
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
what : 'section',
|
|
id : self.SECTION_ID_FOR_CONTENT_PICKER,
|
|
title: sektionsLocalizedData.i18n['Content Picker'],
|
|
panel : sektionsLocalizedData.sektionsPanelId,
|
|
priority : 30,
|
|
track : false,//don't register in the self.registered() => this will prevent this container to be removed when cleaning the registered
|
|
constructWith : api.Section.extend({
|
|
//attachEvents : function () {},
|
|
// Always make the section active, event if we have no control in it
|
|
isContextuallyActive : function() {
|
|
return this.active();
|
|
},
|
|
_toggleActive : function(){ return true; }
|
|
})
|
|
}).done( function() {
|
|
// generate the UI for the content picker if not done yet
|
|
// defer this action when the section is instantiated AND the api.previewer is active, so we can trigger event on it
|
|
// => we also need the local skope to be set, that's why api.czr_initialSkopeCollectionPopulated is convenient because it ensures the api.previewer is ready and we have a local skope set.
|
|
// @see czr-skope-base.js
|
|
// @fixes https://github.com/presscustomizr/nimble-builder/issues/187
|
|
api.section( self.SECTION_ID_FOR_CONTENT_PICKER, function( _section_ ) {
|
|
if ( 'resolved' != api.czr_initialSkopeCollectionPopulated.state() ) {
|
|
api.czr_initialSkopeCollectionPopulated.done( function() {
|
|
api.previewer.trigger('sek-pick-content', { focus : false });
|
|
});
|
|
} else {
|
|
api.previewer.trigger('sek-pick-content', { focus : false });
|
|
}
|
|
});
|
|
});
|
|
},//registerAndSetupDefaultPanelSectionOptions()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//@return void()
|
|
// sektionsData is built server side :
|
|
//array(
|
|
// 'db_values' => sek_get_skoped_seks( $skope_id ),
|
|
// 'setting_id' => sek_get_seks_setting_id( $skope_id )//nimble___[skp__post_page_home]
|
|
// )
|
|
setContextualCollectionSettingIdWhenSkopeSet : function( newSkopes, previousSkopes ) {
|
|
var self = this;
|
|
previousSkopes = previousSkopes || {};
|
|
// Clear all previous sektions if the main panel is expanded and we're coming from a previousSkopes
|
|
if ( !_.isEmpty( previousSkopes.local ) && api.panel( sektionsLocalizedData.sektionsPanelId ).expanded() ) {
|
|
// We don't want to change focus to content picker when setting site templates ( which forces a preview refresh on home when being opened and modified )
|
|
if ( _.isUndefined(api._nimbleRefreshingPreviewHomeWhenSettingSiteTemplate) || !api._nimbleRefreshingPreviewHomeWhenSettingSiteTemplate ) {
|
|
api.previewer.trigger('sek-pick-content');
|
|
}
|
|
}
|
|
|
|
// set the localSectionsSettingId now, and update it on skope change
|
|
sektionsData = api.czr_skopeBase.getSkopeProperty( 'sektions', 'local');
|
|
if ( sektionsLocalizedData.isDevMode ) {
|
|
api.infoLog( '::setContextualCollectionSettingIdWhenSkopeSet => SEKTIONS DATA ? ', sektionsData );
|
|
}
|
|
if ( _.isEmpty( sektionsData ) ) {
|
|
api.errare('::setContextualCollectionSettingIdWhenSkopeSet() => no sektionsData');
|
|
}
|
|
if ( _.isEmpty( sektionsData.setting_id ) ) {
|
|
api.errare('::setContextualCollectionSettingIdWhenSkopeSet() => missing setting_id');
|
|
}
|
|
self.localSectionsSettingId( sektionsData.setting_id );
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// fired in ::initialize(), at api.bind( 'ready', function() {})
|
|
setupTopBar : function() {
|
|
var self = this;
|
|
self.topBarId = '#nimble-top-bar';
|
|
self.topBarVisible = new api.Value( false );
|
|
self.topBarVisible.bind( function( visible ){
|
|
if ( ! self.levelTreeExpanded() ) {
|
|
self.toggleTopBar( visible );
|
|
}
|
|
});
|
|
|
|
self.mouseMovedRecently = new api.Value( {} );
|
|
self.mouseMovedRecently.bind( function( position ) {
|
|
self.topBarVisible( ! _.isEmpty( position ) );
|
|
});
|
|
|
|
var trackMouseMovements = 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 ) );
|
|
};
|
|
self.cachedElements.$window.on( 'mousemove scroll,', _.throttle( trackMouseMovements , 50 ) );
|
|
api.previewer.bind('ready', function() {
|
|
$(api.previewer.targetWindow().document ).on( 'mousemove scroll,', _.throttle( trackMouseMovements , 50 ) );
|
|
});
|
|
|
|
// LEVEL TREE
|
|
self.setupLevelTree();
|
|
},
|
|
|
|
|
|
// @return void()
|
|
// self.topBarVisible.bind( function( visible ){
|
|
// self.toggleTopBar( visible );
|
|
// });
|
|
toggleTopBar : function( visible ) {
|
|
visible = _.isUndefined( visible ) ? true : visible;
|
|
var self = this,
|
|
_renderAndSetup = function() {
|
|
$.when( self.renderAndSetupTopBarTmpl({}) ).done( function( $_el ) {
|
|
self.topBarContainer = $_el;
|
|
//display
|
|
_.delay( function() {
|
|
self.cachedElements.$body.addClass('nimble-top-bar-visible');
|
|
}, 200 );
|
|
});
|
|
},
|
|
_hide = function() {
|
|
var dfd = $.Deferred();
|
|
self.cachedElements.$body.removeClass('nimble-top-bar-visible');
|
|
if ( self.topBarContainer && self.topBarContainer.length ) {
|
|
//remove Dom element after slide up
|
|
_.delay( function() {
|
|
//self.topBarContainer.remove();
|
|
dfd.resolve();
|
|
}, 300 );
|
|
} else {
|
|
dfd.resolve();
|
|
}
|
|
return dfd.promise();
|
|
};
|
|
|
|
if ( visible ) {
|
|
_renderAndSetup();
|
|
} else {
|
|
_hide().done( function() {
|
|
self.topBarVisible( false );//should be already false
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
//@param = { }
|
|
renderAndSetupTopBarTmpl : function( params ) {
|
|
var self = this,
|
|
_tmpl;
|
|
|
|
// CHECK IF ALREADY RENDERED
|
|
if ( $( self.topBarId ).length > 0 )
|
|
return $( self.topBarId );
|
|
|
|
// RENDER
|
|
try {
|
|
_tmpl = wp.template( 'nimble-top-bar' )( {} );
|
|
} catch( er ) {
|
|
api.errare( 'Error when parsing the the top note template', er );
|
|
return false;
|
|
}
|
|
$('#customize-preview').after( $( _tmpl ) );
|
|
$('#customize-preview').trigger('nimble-top-bar-rendered');
|
|
|
|
// UNDO / REDO ON CTRL + Z / CTRL + Y EVENTS
|
|
$(document).keydown( function( evt ) {
|
|
if ( evt.ctrlKey && _.contains( [89, 90], evt.keyCode ) ) {
|
|
try { self.navigateHistory( 90 === evt.keyCode ? 'undo' : 'redo'); } catch( er ) {
|
|
api.errare( 'Error when firing self.navigateHistory', er );
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
// CLICK EVENTS
|
|
// Attach click events
|
|
$('.sek-add-content', self.topBarId).on( 'click', function(evt) {
|
|
evt.preventDefault();
|
|
api.previewer.trigger( 'sek-pick-content', { content_type : 'module' });
|
|
});
|
|
$('.sek-level-tree', self.topBarId).on( 'click', function(evt) {
|
|
evt.preventDefault();
|
|
self.levelTreeExpanded(!self.levelTreeExpanded());
|
|
});
|
|
$('[data-nimble-history]', self.topBarId).on( 'click', function(evt) {
|
|
try { self.navigateHistory( $(this).data( 'nimble-history') ); } catch( er ) {
|
|
api.errare( 'Error when firing self.navigateHistory', er );
|
|
}
|
|
});
|
|
$('.sek-settings', self.topBarId).on( 'click', function(evt) {
|
|
// Focus on the Nimble panel
|
|
api.panel( sektionsLocalizedData.sektionsPanelId, function( _panel_ ) {
|
|
self.rootPanelFocus();
|
|
_panel_.focus();
|
|
});
|
|
// // Generate UI for the local skope options
|
|
// self.generateUI({ action : 'sek-generate-local-skope-options-ui'}).done( function() {
|
|
// api.control( self.getLocalSkopeOptionId(), function( _control_ ) {
|
|
// _control_.focus();
|
|
// });
|
|
// });
|
|
});
|
|
|
|
$('.sek-nimble-doc, .sek-notifications', self.topBarId).on( 'click', function(evt) {
|
|
evt.preventDefault();
|
|
if ( $(this).data('doc-href') ) {
|
|
window.open($(this).data('doc-href'), '_blank');
|
|
}
|
|
});
|
|
|
|
$('.sek-tmpl-saving', self.topBarId ).on( 'click', function(evt) {
|
|
// Focus on the Nimble panel
|
|
// api.panel( sektionsLocalizedData.sektionsPanelId, function( _panel_ ) {
|
|
// self.rootPanelFocus();
|
|
// _panel_.focus();
|
|
// });
|
|
evt.preventDefault();
|
|
self.tmplDialogVisible(!self.tmplDialogVisible());// self.tmplDialogVisible() is initialized false
|
|
});
|
|
|
|
$( self.topBarId ).on( 'click', '.sek-reset-local-sektions', function(evt) {
|
|
// Focus on the Nimble panel
|
|
// api.panel( sektionsLocalizedData.sektionsPanelId, function( _panel_ ) {
|
|
// self.rootPanelFocus();
|
|
// _panel_.focus();
|
|
// });
|
|
// api.control( self.getLocalSkopeOptionId() + '__local_reset', function( _control_ ) {
|
|
// _control_.focus();
|
|
// _control_.container.find('.customize-control-title').trigger('click');
|
|
// });
|
|
// evt.preventDefault();
|
|
// Focus on the Nimble panel
|
|
api.panel( sektionsLocalizedData.sektionsPanelId, function( _panel_ ) {
|
|
self.rootPanelFocus();
|
|
_panel_.focus();
|
|
api.section( self.SECTION_ID_FOR_LOCAL_OPTIONS, function( _section_ ) {
|
|
_section_.focus();
|
|
setTimeout( function() {
|
|
api.control( self.getLocalSkopeOptionId() + '__local_reset', function( _control_ ) {
|
|
_control_.focus();
|
|
_control_.container.find('.customize-control-title').trigger('click');
|
|
_control_.container.addClass('button-see-me');
|
|
_.delay( function() {
|
|
_control_.container.removeClass('button-see-me');
|
|
}, 800 );
|
|
});
|
|
}, 500 );
|
|
});
|
|
});
|
|
});
|
|
|
|
$( self.topBarId ).on( 'click', '.sek-goto-site-tmpl-options', function(evt) {
|
|
// evt.preventDefault();
|
|
// Focus on the Nimble panel
|
|
api.panel( sektionsLocalizedData.sektionsPanelId, function( _panel_ ) {
|
|
self.rootPanelFocus();
|
|
_panel_.focus();
|
|
api.section( self.SECTION_ID_FOR_GLOBAL_OPTIONS, function( _section_ ) {
|
|
_section_.focus();
|
|
setTimeout( function() {
|
|
api.control( sektionsLocalizedData.prefixForSettingsNotSaved + sektionsLocalizedData.optNameForGlobalOptions + '__site_templates', function( _control_ ) {
|
|
_control_.focus();
|
|
_control_.container.find('.customize-control-title').trigger('click');
|
|
});
|
|
}, 500 );
|
|
});
|
|
});
|
|
});
|
|
|
|
// NOTIFICATION WHEN USING CUSTOM TEMPLATE
|
|
// implemented for https://github.com/presscustomizr/nimble-builder/issues/304
|
|
var printSektionsSkopeStatus = function( args ) {
|
|
if ( $(self.topBarId).length < 1 || sektionsLocalizedData.isDebugMode )
|
|
return;
|
|
var _hasLocalNBCustomizations = false;
|
|
if ( args && args.on_init ) {
|
|
//console.log('ON INIT : ', api.czr_skopeBase.getSkopeProperty( 'skope_id', 'group' ) );
|
|
_hasLocalNBCustomizations = api.czr_skopeBase.getSkopeProperty( 'has_local_nimble_customizations', 'local' );//property added server side see php:add_sektion_values_to_skope_export()
|
|
} else if ( args && args.after_reset ) {
|
|
//console.log('AFTER RESET');
|
|
_hasLocalNBCustomizations = false;
|
|
} else {
|
|
_hasLocalNBCustomizations = self.hasLocalSettingBeenCustomized();
|
|
}
|
|
|
|
//console.log('GLOBAL OPTIONS ', api(sektionsLocalizedData.optNameForGlobalOptions)() );
|
|
var _groupSkope = self.getGroupSkopeForSiteTemplate(),
|
|
_hasSiteTemplateSet = false,
|
|
_inheritsSiteTemplate = false,
|
|
_globOptions = api(sektionsLocalizedData.optNameForGlobalOptions)();
|
|
|
|
var _is_inheritance_enabled_in_local_options = true,
|
|
currentSetValue = api( self.localSectionsSettingId() )(),
|
|
localOptions = currentSetValue.local_options;
|
|
|
|
if ( localOptions && _.isObject(localOptions) && localOptions.local_reset && !_.isUndefined( localOptions.local_reset.inherit_group_scope ) ) {
|
|
_is_inheritance_enabled_in_local_options = localOptions.local_reset.inherit_group_scope;
|
|
}
|
|
|
|
if ( _.isObject(_globOptions) && _globOptions.site_templates && _.isObject(_globOptions.site_templates ) ) {
|
|
_.each( _globOptions.site_templates, function( tmpl, siteTmplSkope ) {
|
|
// If no match found, keep sniffing
|
|
if ( !_hasSiteTemplateSet ) {
|
|
// How does this work ?
|
|
// the site_templates key are intended to match exactly the skope ids, as generated by NB skope system
|
|
// But there are exceptions for some skopes that have no "group skopes" and for which we've added the suffix "for_site_tmpl"
|
|
// ex : skp__404_for_site_tmpl, skp__date_for_site_tmpl, skp__search_for_site_tmpl @see php sek_get_module_params_for_sek_site_tmpl_pickers()
|
|
_hasSiteTemplateSet = _groupSkope === siteTmplSkope;
|
|
}
|
|
} );
|
|
}
|
|
|
|
_inheritsSiteTemplate = _hasSiteTemplateSet && !_hasLocalNBCustomizations && _is_inheritance_enabled_in_local_options;
|
|
var _msg = sektionsLocalizedData.i18n['This page is not customized with NB'];
|
|
if ( _inheritsSiteTemplate ) {
|
|
_msg = '<span class="sek-goto-site-tmpl-options">' + sektionsLocalizedData.i18n['This page inherits a NB site template'] + '</span>';
|
|
} else if ( _hasLocalNBCustomizations ) {
|
|
_msg = sektionsLocalizedData.i18n['This page is customized with NB'];
|
|
_msg += '<button type="button" class="far fa-trash-alt sek-reset-local-sektions" title="' + sektionsLocalizedData.i18n['Remove all sections and options of this page'] +'" data-nimble-state="enabled"><span class="screen-reader-text">' + sektionsLocalizedData.i18n['Remove all sections and options of this page'] +'</span></button>';
|
|
}
|
|
|
|
$(self.topBarId).find('.sek-notifications')
|
|
.html([
|
|
'<span class="fas fa-info-circle"></span>',
|
|
_msg
|
|
//sektionsLocalizedData.i18n['This page uses Nimble Builder template.']
|
|
].join(' '));
|
|
//.attr('data-doc-href', 'https://docs.presscustomizr.com/article/339-changing-the-page-template');
|
|
|
|
// if ( _.isObject( templateSettingValue ) && templateSettingValue.local_template && 'default' !== templateSettingValue.local_template ) {
|
|
// if ( _inheritsSiteTemplate ) {
|
|
// $(self.topBarId).find('.sek-notifications').addClass('is-linked').data('doc-href', 'https://docs.presscustomizr.com/article/428-how-to-use-site-templates-with-nimble-builder');
|
|
// } else {
|
|
// $(self.topBarId).find('.sek-notifications').removeClass('is-linked').data('doc-href','');
|
|
// }
|
|
// } else {
|
|
// $(self.topBarId).find('.sek-notifications').html('');
|
|
// }
|
|
};
|
|
|
|
api.bind('nimble-update-topbar-skope-status', printSektionsSkopeStatus );
|
|
|
|
var initOnSkopeReady = function() {
|
|
// Schedule notification rendering on init
|
|
// @see ::generateUIforLocalSkopeOptions()
|
|
api( self.localSectionsSettingId(), function( _localSectionsSetting_ ) {
|
|
// var localSectionsValue = _localSectionsSetting_(),
|
|
// initialLocalTemplateValue = ( _.isObject( localSectionsValue ) && localSectionsValue.local_options && localSectionsValue.local_options.template ) ? localSectionsValue.local_options.template : null;
|
|
// // on init
|
|
// printSektionsSkopeStatus( initialLocalTemplateValue );
|
|
printSektionsSkopeStatus({on_init : true});
|
|
});
|
|
|
|
// React to template changes
|
|
// @see ::generateUIforLocalSkopeOptions() for the declaration of self.getLocalSkopeOptionId() + '__template'
|
|
// api( self.getLocalSkopeOptionId() + '__template', function( _set_ ) {
|
|
// _set_.bind( function( to, from ) {
|
|
// printSektionsSkopeStatus( to );
|
|
// });
|
|
// });
|
|
};
|
|
|
|
// fire now
|
|
initOnSkopeReady();
|
|
// and on skope change, when user navigates through the previewed pages
|
|
// 'nimble-ready-for-current-skope' declared in ::initialize()
|
|
api.bind('nimble-ready-for-current-skope', function() {
|
|
initOnSkopeReady();
|
|
});
|
|
|
|
return $( self.topBarId );
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// Fired in ::initialize(), at api 'ready'
|
|
// March 2019 : history log tracks local and global section settings
|
|
// no tracking of the global option sektionsLocalizedData.optNameForGlobalOptions
|
|
initializeHistoryLogWhenSettingsRegistered : function() {
|
|
var self = this;
|
|
// This api.Value() is bound in ::setupTopBar
|
|
self.historyLog = new api.Value([{
|
|
status : 'current',
|
|
value : {
|
|
'local' : api( self.localSectionsSettingId() )(),//<= "nimble___[skp__post_page_10]"
|
|
'global' : api( self.getGlobalSectionsSettingId() )()
|
|
},
|
|
action : 'initial'
|
|
}]);
|
|
// LISTEN TO HISTORY LOG CHANGES AND UPDATE THE BUTTON STATE
|
|
self.historyLog.bind( function( newLog ) {
|
|
if ( _.isEmpty( newLog ) )
|
|
return;
|
|
|
|
var newCurrentKey = _.findKey( newLog, { status : 'current'} );
|
|
newCurrentKey = Number( newCurrentKey );
|
|
$( '#nimble-top-bar' ).find('[data-nimble-history]').each( function() {
|
|
if ( 'undo' === $(this).data('nimble-history') ) {
|
|
$(this).attr('data-nimble-state', 0 >= newCurrentKey ? 'disabled' : 'enabled');
|
|
} else {
|
|
$(this).attr('data-nimble-state', newLog.length <= ( newCurrentKey + 1 ) ? 'disabled' : 'enabled');
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
// React to a local or global setting change api( settingData.collectionSettingId )
|
|
// =>populates self.historyLog() observable value
|
|
// invoked in ::setupSettingsToBeSaved, if params.navigatingHistoryLogs !== true <=> not already navigating
|
|
trackHistoryLog : function( sektionSetInstance, params ) {
|
|
var self = this,
|
|
_isGlobal = sektionSetInstance.id === self.getGlobalSectionsSettingId();
|
|
|
|
// Safety checks
|
|
// trackHistoryLog must be invoked with a try catch statement
|
|
if ( !_.isObject( params ) || !_.isFunction( self.historyLog ) || !_.isArray( self.historyLog() ) ) {
|
|
api.errare( 'params, self.historyLog() ', params, self.historyLog() );
|
|
throw new Error('trackHistoryLog => invalid params or historyLog value');
|
|
}
|
|
|
|
// Always clean future values if the logs have been previously navigated back
|
|
var newHistoryLog = [],
|
|
historyLog = $.extend( true, [], self.historyLog() ),
|
|
sektionToRefresh;
|
|
|
|
if ( ! _.isEmpty( params.in_sektion ) ) {//<= module changed, column resized, removed...
|
|
sektionToRefresh = params.in_sektion;
|
|
} else if ( ! _.isEmpty( params.to_sektion ) ) {// column moved /
|
|
sektionToRefresh = params.to_sektion;
|
|
}
|
|
|
|
// Reset all status but 'future' to 'previous'
|
|
_.each( historyLog, function( log ) {
|
|
var newStatus = 'previous';
|
|
if ( 'future' == log.status )
|
|
return;
|
|
$.extend( log, { status : 'previous' } );
|
|
newHistoryLog.push( log );
|
|
});
|
|
newHistoryLog.push({
|
|
status : 'current',
|
|
value : _isGlobal ? { global : sektionSetInstance() } : { local : sektionSetInstance() },
|
|
action : _.isObject( params ) ? ( params.action || '' ) : '',
|
|
sektionToRefresh : sektionToRefresh
|
|
});
|
|
self.historyLog( newHistoryLog );
|
|
},
|
|
|
|
|
|
|
|
// @param direction = string 'undo', 'redo'
|
|
// @return void()
|
|
// Fired on click in the topbar or when hitting ctrl z / y
|
|
navigateHistory : function( direction ) {
|
|
var self = this,
|
|
historyLog = $.extend( true, [], self.historyLog() );
|
|
// log model
|
|
// {
|
|
// status : 'current', 'previous', 'future'
|
|
// value : {},
|
|
// action : 'sek-add-column'
|
|
// }
|
|
|
|
// UPDATE THE SETTING VALUE
|
|
var previous,
|
|
current,
|
|
future,
|
|
newHistoryLog = [],
|
|
newSettingValue,
|
|
previousSektionToRefresh,
|
|
currentSektionToRefresh;
|
|
|
|
_.each( historyLog, function( log ) {
|
|
if ( ! _.isEmpty( newSettingValue ) ) {
|
|
return;
|
|
}
|
|
switch( log.status ) {
|
|
case 'previous' :
|
|
previous = log;
|
|
break;
|
|
case 'current' :
|
|
current = log;
|
|
break;
|
|
case 'future' :
|
|
future = log;
|
|
break;
|
|
}
|
|
switch( direction ) {
|
|
case 'undo' :
|
|
// the last previous is our new setting value
|
|
if ( ! _.isEmpty( current ) && ! _.isEmpty( previous ) ) {
|
|
newSettingValue = previous.value;
|
|
previousSektionToRefresh = current.sektionToRefresh;
|
|
currentSektionToRefresh = previous.sektionToRefresh;
|
|
}
|
|
break;
|
|
case 'redo' :
|
|
// the first future is our new setting value
|
|
if ( ! _.isEmpty( future ) ) {
|
|
newSettingValue = future.value;
|
|
previousSektionToRefresh = current.sektionToRefresh;
|
|
currentSektionToRefresh = future.sektionToRefresh;
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
// set the new setting Value
|
|
if( !_.isUndefined( newSettingValue ) ) {
|
|
if ( !_.isEmpty( newSettingValue.local ) ) {
|
|
api( self.localSectionsSettingId() )( self.validateSettingValue( newSettingValue.local, 'local' ), { navigatingHistoryLogs : true } );
|
|
|
|
// Clean and regenerate the local option setting
|
|
// Note that we also do it after a local import, tmpl injection or local reset
|
|
//
|
|
// Settings are normally registered once and never cleaned, unlike controls.
|
|
// Updating the setting value will refresh the sections
|
|
// but the local options, persisted in separate settings, won't be updated if the settings are not cleaned
|
|
// Example of local setting id :
|
|
// __nimble__skp__post_page_2__localSkopeOptions__template
|
|
// or
|
|
// __nimble__skp__home__localSkopeOptions__custom_css
|
|
api.czr_sektions.generateUI({
|
|
action : 'sek-generate-local-skope-options-ui',
|
|
clean_settings_and_controls_first : true//<= see api.czr_sektions.generateUIforLocalSkopeOptions()
|
|
});
|
|
}
|
|
if ( !_.isEmpty( newSettingValue.global ) ) {
|
|
api( self.getGlobalSectionsSettingId() )( self.validateSettingValue( newSettingValue.global, 'global' ), { navigatingHistoryLogs : true } );
|
|
}
|
|
// If the information is available, refresh only the relevant sections
|
|
// otherwise fallback on a full refresh
|
|
var previewHasBeenRefreshed = false;
|
|
|
|
// if ( ! _.isEmpty( previousSektionToRefresh ) ) {
|
|
// api.previewer.trigger( 'sek-refresh-level', {
|
|
// level : 'section',
|
|
// id : previousSektionToRefresh
|
|
// });
|
|
// } else {
|
|
// api.previewer.refresh();
|
|
// previewHasBeenRefreshed = true;
|
|
// }
|
|
// if ( currentSektionToRefresh != previousSektionToRefresh ) {
|
|
// if ( ! _.isEmpty( currentSektionToRefresh ) ) {
|
|
// api.previewer.trigger( 'sek-refresh-level', {
|
|
// level : 'section',
|
|
// id : currentSektionToRefresh
|
|
// });
|
|
// } else if ( ! previewHasBeenRefreshed ) {
|
|
// api.previewer.refresh();
|
|
// }
|
|
// }
|
|
api.previewer.refresh();
|
|
|
|
// Always make sure that the ui gets refreshed
|
|
api.previewer.trigger( 'sek-pick-content', {});
|
|
|
|
// Clean registered control
|
|
self.cleanRegisteredAndLargeSelectInput();//<= normal cleaning
|
|
// Clean even the level settings
|
|
// => otherwise the level settings won't be synchronized when regenerating their ui.
|
|
self.cleanRegisteredLevelSettings();// setting cleaning
|
|
}
|
|
|
|
// UPDATE THE HISTORY LOG
|
|
var currentKey = _.findKey( historyLog, { status : 'current'} );
|
|
currentKey = Number( currentKey );
|
|
if ( ! _.isNumber( currentKey ) ) {
|
|
api.errare( 'Error when navigating the history log, the current key should be a number');
|
|
return;
|
|
}
|
|
|
|
_.each( historyLog, function( log, key ) {
|
|
newLog = $.extend( true, {}, log );
|
|
// cast keys to number so we can compare them
|
|
key = Number( key );
|
|
switch( direction ) {
|
|
case 'undo' :
|
|
if ( 0 < currentKey ) {
|
|
if ( key === ( currentKey - 1 ) ) {
|
|
newLog.status = 'current';
|
|
} else if ( key === currentKey ) {
|
|
newLog.status = 'future';
|
|
}
|
|
}
|
|
break;
|
|
case 'redo' :
|
|
if ( historyLog.length > ( currentKey + 1 ) ) {
|
|
if ( key === currentKey ) {
|
|
newLog.status = 'previous';
|
|
} else if ( key === ( currentKey + 1 ) ) {
|
|
newLog.status = 'current';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
newHistoryLog.push( newLog );
|
|
});
|
|
self.historyLog( newHistoryLog );
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// fired in ::setupTopBar(), at api.bind( 'ready', function() {})
|
|
setupLevelTree : function() {
|
|
var self = this, stringifiedLTVal;
|
|
self.levelTree = new api.Value([]);
|
|
self.levelTree.bind( function( val ) {
|
|
// Refresh when the collection is being modified from the tree
|
|
if ( self.levelTreeExpanded() ) {
|
|
self.renderOrRefreshTree();
|
|
}
|
|
});
|
|
|
|
// March 2021 => highlight level tree button in blue if NB levels already inserted.
|
|
var maybeHighlightCtrlButton = function( val ) {
|
|
try { stringifiedLTVal = JSON.stringify( val ); } catch(er) {
|
|
api.errorLog('::setupLevelTree => error when JSON.stringify Level Tree');
|
|
}
|
|
if ( !_.isString( stringifiedLTVal ) )
|
|
return;
|
|
// look for a NB level id starting looking like __nimble__986b1c3921fe
|
|
if ( -1 !== stringifiedLTVal.indexOf('__nimble__') ) {
|
|
$('.sek-level-tree button', self.topBarId).css('color', '#46d2ff' );
|
|
} else {
|
|
$('.sek-level-tree button', self.topBarId).css('color', '' );
|
|
}
|
|
};
|
|
self.levelTree.bind( _.debounce( function(val) {
|
|
maybeHighlightCtrlButton( val );
|
|
}, 1000 ));
|
|
// Initial Button state based on the current tree value
|
|
$('#customize-preview').one('nimble-top-bar-rendered', function() {
|
|
maybeHighlightCtrlButton( self.setLevelTreeValue() );
|
|
});
|
|
|
|
// SETUP AND REACT TO LEVEL TREE EXPANSION
|
|
self.levelTreeExpanded = new api.Value(false);
|
|
self.levelTreeExpanded.bind( function(expanded) {
|
|
self.cachedElements.$body.toggleClass( 'sek-level-tree-expanded', expanded );
|
|
if ( expanded ) {
|
|
// Close template gallery, template saver, section saver
|
|
self.templateGalleryExpanded(false);
|
|
self.tmplDialogVisible(false);
|
|
if ( self.saveSectionDialogVisible ) {
|
|
self.saveSectionDialogVisible(false);
|
|
}
|
|
|
|
// Set the level tree now
|
|
self.setLevelTreeValue();
|
|
|
|
// Make sure we the tree is set first
|
|
if ( _.isEmpty( self.levelTree() ) ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['No sections to navigate'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
// self disable
|
|
self.levelTreeExpanded(false);
|
|
return;
|
|
}
|
|
$('#customize-preview iframe').css('z-index', 1);
|
|
self.renderOrRefreshTree();
|
|
} else if ( $('#nimble-level-tree').length > 0 ) {
|
|
_.delay( function() {
|
|
$('#nimble-level-tree').remove();
|
|
$('#customize-preview iframe').css('z-index', '');
|
|
}, 300 );
|
|
}
|
|
});
|
|
|
|
// REFRESH THE TREE WHEN THE ACTIVE LOCATIONS CHANGE
|
|
// @see ::initialize to understand how active locations are updated
|
|
self.activeLocations.bind(function() {
|
|
if ( !_.isEmpty( self.levelTree() ) ) {
|
|
self.renderOrRefreshTree();
|
|
}
|
|
});
|
|
|
|
// API READY
|
|
api.previewer.bind('ready', function() {
|
|
// LEVEL TREE
|
|
// on each skope change
|
|
// - set the level tree
|
|
// - bind the local and global settings so that they refresh the level tree when changed
|
|
self.localSectionsSettingId.callbacks.add( function() {
|
|
self.levelTreeExpanded(false);
|
|
// Bind the global and local settings if not bound yet
|
|
_.each( [ self.getGlobalSectionsSettingId(), self.localSectionsSettingId(), sektionsLocalizedData.optNameForGlobalOptions ], function( setId ){
|
|
if ( api(setId)._isBoundForNimbleLevelTree )
|
|
return;
|
|
|
|
api(setId).bind( function(to) {
|
|
self.setLevelTreeValue();
|
|
});
|
|
api(setId)._isBoundForNimbleLevelTree = true;
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// SETUP CLICK EVENTS IN THE TREE
|
|
self.cachedElements.$body.on('click', '#nimble-level-tree [data-nimb-level]', function(evt) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
var $el = $(evt.target),
|
|
$closestLevel = $el.closest('[data-nimb-level]');
|
|
api.previewer.send('sek-animate-to-level', { id : $closestLevel.data('nimb-id') });
|
|
api.previewer.send('sek-clean-level-uis');
|
|
// Display the level ui in the preview
|
|
// and expand the level options in the customizer control panel
|
|
_.delay( function() {
|
|
api.previewer.send('sek-display-level-ui', { id : $closestLevel.data('nimb-id') });
|
|
|
|
var _id = $closestLevel.data('nimb-id'),
|
|
_level = $closestLevel.data('nimb-level');
|
|
|
|
if ( 'column' === _level || 'section' === _level ) {
|
|
api.previewer.trigger('sek-edit-options', { id : _id, level : _level });
|
|
} else if ( 'module' === _level ) {
|
|
api.previewer.trigger('sek-edit-module', { id : _id, level : _level });
|
|
}
|
|
}, 100 );
|
|
});
|
|
|
|
self.cachedElements.$body.on('click', '#nimble-level-tree .sek-remove-level', function(evt) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
var $el = $(evt.target).closest('[data-nimb-level]');
|
|
api.previewer.trigger('sek-remove', {
|
|
level : $el.data('nimb-level'),
|
|
id : $el.data('nimb-id'),
|
|
location : $el.closest('[data-nimb-level="location"]').data('nimb-id'),
|
|
in_sektion : $el.closest('[data-nimb-level="section"]').data('nimb-id'),
|
|
in_column : $el.closest('[data-nimb-level="column"]').data('nimb-id')
|
|
});
|
|
$el.fadeOut('slow');
|
|
// Refresh
|
|
self.renderOrRefreshTree();
|
|
});
|
|
|
|
// Collapse tree ( also possible by clicking on the tree icon in the top Nimble bar )
|
|
self.cachedElements.$body.on('click', '.sek-close-level-tree' , function(evt) {
|
|
evt.preventDefault();
|
|
self.levelTreeExpanded(false);
|
|
});
|
|
},
|
|
|
|
// This method updates the levelTree observable api.Value()
|
|
setLevelTreeValue : function() {
|
|
var self = this,
|
|
globalCollSetId = self.getGlobalSectionsSettingId(),
|
|
localCollSetId = self.localSectionsSettingId(),
|
|
globalOptionSetId = sektionsLocalizedData.optNameForGlobalOptions,
|
|
globalColSetValue, localColSetValue,
|
|
globalCollection, localCollection,
|
|
rawGlobalOptionsValue,
|
|
missingDependantSettingId = false;
|
|
|
|
// Check if all dependant settings are registered
|
|
// we won't go further if any of the 3 setting id's is not yet registered
|
|
_.each( [globalCollSetId, localCollSetId, globalOptionSetId ], function( setId ) {
|
|
if ( !api.has(setId) ) {
|
|
missingDependantSettingId = setId;
|
|
return;
|
|
}
|
|
});
|
|
|
|
if ( false !== missingDependantSettingId ) {
|
|
api.errare( '::setLevelTreeValue => a setting id is not registered ');
|
|
return;
|
|
}
|
|
|
|
// Normalizes the setting values
|
|
globalColSetValue = api(globalCollSetId)();
|
|
globalCollection = _.isObject( globalColSetValue ) ? $.extend( true, {}, globalColSetValue ) : {};
|
|
globalCollection = ! _.isEmpty( globalCollection.collection )? globalCollection.collection : [];
|
|
globalCollection = _.isArray( globalCollection ) ? globalCollection : [];
|
|
|
|
localColSetValue = api(localCollSetId)();
|
|
localColSetValue = _.isObject( localColSetValue ) ? localColSetValue : {};
|
|
localCollection = $.extend( true, {}, localColSetValue );
|
|
localCollection = ! _.isEmpty( localCollection.collection ) ? localCollection.collection : [];
|
|
localCollection = _.isArray( localCollection ) ? localCollection : [];
|
|
|
|
var raw_col = _.union( globalCollection, localCollection ),
|
|
local_header_footer_value,
|
|
global_header_footer_value,
|
|
has_local_header_footer = false,
|
|
has_global_header_footer = false;
|
|
|
|
rawGlobalOptionsValue = api( globalOptionSetId )();
|
|
rawGlobalOptionsValue = _.isObject( rawGlobalOptionsValue ) ? rawGlobalOptionsValue : {};
|
|
|
|
// HEADER-FOOTER => do we have a header-footer set, local or global ?
|
|
// LOCAL
|
|
if ( localColSetValue.local_options && localColSetValue.local_options.local_header_footer ) {
|
|
local_header_footer_value = localColSetValue.local_options.local_header_footer['header-footer'];
|
|
has_local_header_footer = 'nimble_local' === local_header_footer_value;
|
|
}
|
|
|
|
// GLOBAL
|
|
// there can be a global header footer if
|
|
// 1) local is not set to 'nimble_local' or 'theme'
|
|
// and
|
|
// 2) the global option is set to 'nimble_global'
|
|
//
|
|
// OR when
|
|
// 1) local is set to 'nimble_global'
|
|
if ( rawGlobalOptionsValue.global_header_footer && !has_local_header_footer && 'theme' !== local_header_footer_value) {
|
|
global_header_footer_value = rawGlobalOptionsValue.global_header_footer['header-footer'];
|
|
has_global_header_footer = 'nimble_global' === global_header_footer_value || 'nimble_global' === local_header_footer_value;
|
|
}
|
|
|
|
var filteredCollection = $.extend( true, [], raw_col ),
|
|
header_loc,
|
|
footer_loc;
|
|
|
|
filteredCollection = _.filter( filteredCollection, function( loc, key ) {
|
|
return !_.contains( ['nimble_global_header', 'nimble_global_footer', 'nimble_local_header', 'nimble_local_footer'], loc.id );
|
|
});
|
|
|
|
// RE-ORGANIZE LOCATIONS SO THAT WE HAVE
|
|
// - header
|
|
// - content loc #1
|
|
// - content loc #2
|
|
// - ...
|
|
// - footer
|
|
var wrapContentLocationWithHeaderFoooterLocations = function( scope ) {
|
|
header_loc = _.findWhere(raw_col, {id:'nimble_' + scope + '_header'});
|
|
footer_loc = _.findWhere(raw_col, {id:'nimble_' + scope + '_footer'});
|
|
filteredCollection.unshift(header_loc);
|
|
filteredCollection.push(footer_loc);
|
|
};
|
|
if ( has_local_header_footer ) {
|
|
wrapContentLocationWithHeaderFoooterLocations('local');
|
|
} else if ( has_global_header_footer ) {
|
|
wrapContentLocationWithHeaderFoooterLocations('global');
|
|
}
|
|
|
|
// RE-ORDER LOCATIONS IN THE SAME ORDER AS THEY ARE IN THE DOM
|
|
// @see ::initialize to understand how active locations are updated
|
|
var contextuallyActiveLocactions = self.activeLocations(),
|
|
orderedCollection = [],
|
|
candidate;
|
|
if ( !_.isEmpty(contextuallyActiveLocactions) ) {
|
|
_.each( contextuallyActiveLocactions, function( loc ) {
|
|
candidate = _.findWhere(filteredCollection, {id:loc});
|
|
if( !_.isUndefined(candidate) ) {
|
|
orderedCollection.push(candidate);
|
|
}
|
|
});
|
|
} else {
|
|
orderedCollection = filteredCollection;
|
|
}
|
|
|
|
// Store it now
|
|
self.levelTree( orderedCollection );
|
|
return orderedCollection;
|
|
},
|
|
|
|
|
|
// print the tree
|
|
renderOrRefreshTree : function() {
|
|
var self = this,
|
|
_tmpl;
|
|
if( $('#nimble-level-tree').length < 1 ) {
|
|
// RENDER
|
|
try {
|
|
_tmpl = wp.template( 'nimble-level-tree' )( {} );
|
|
} catch( er ) {
|
|
api.errare( 'Error when parsing the nimble-level-tree template', er );
|
|
return false;
|
|
}
|
|
$( '#customize-preview' ).after( $( _tmpl ) );
|
|
}
|
|
$('#nimble-level-tree').find('.sek-tree-wrap').html( self.getLevelTreeHtml() );
|
|
},
|
|
|
|
// recursive helper
|
|
// return an html string describing the contextually printed sections
|
|
getLevelTreeHtml : function( _col, level ) {
|
|
var self = this;
|
|
_col = _col || self.levelTree();
|
|
|
|
var levelType,
|
|
levelName,
|
|
_html,
|
|
skipLevel = false;
|
|
|
|
if ( !_.isArray( _col ) || _.isEmpty( _col ) ) {
|
|
api.errare('::buildLevelTree => invalid collection param', _col );
|
|
return _html;
|
|
}
|
|
var remove_icon_html = '<i class="material-icons sek-remove-level" title="'+ sektionsLocalizedData.i18n['Remove this element'] +'">delete_forever</i>';
|
|
_html = '<ul>';
|
|
_.each( _col, function( _level_param ) {
|
|
if ( _.isUndefined( _level_param.level ) ){
|
|
api.errare('::buildLevelTree => missing level property', _level_param );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( _level_param.id ) ){
|
|
api.errare('::buildLevelTree => missing id property', _level_param );
|
|
return;
|
|
}
|
|
|
|
// Set some vars now
|
|
levelType = _level_param.level;
|
|
levelName = levelType;
|
|
|
|
// if the level is a location, is this location contextually active ?
|
|
// @see ::initialize to understand how active locations are updated
|
|
if ( 'location' === levelType ) {
|
|
skipLevel = !_.contains( self.activeLocations(), _level_param.id );
|
|
}
|
|
|
|
if ( !skipLevel ) {
|
|
//try to get the i18n level name, fall back on the level type
|
|
if ( sektionsLocalizedData.i18n[levelType] ) {
|
|
levelName = sektionsLocalizedData.i18n[levelType];
|
|
}
|
|
if ( true === _level_param.is_nested ) {
|
|
levelName = sektionsLocalizedData.i18n['nested section'];
|
|
}
|
|
|
|
remove_icon_html = 'location' !== levelType ? remove_icon_html : '';
|
|
_html += '<li data-nimb-level="'+levelType+'" data-nimb-id="'+_level_param.id+'">';
|
|
|
|
_html += '<div class="sek-level-infos"><div class="sek-inner-level-infos">';
|
|
// add module type and icon
|
|
if ( 'module' === levelType ) {
|
|
_html += [
|
|
self.getTreeModuleIcon( _level_param.module_type ),
|
|
self.getTreeModuleTitle( _level_param.module_type )
|
|
].join(' ');
|
|
}
|
|
// add the rest of the html, common to all elements
|
|
_html += [
|
|
' ',
|
|
levelName,
|
|
'( id :',
|
|
_level_param.id,
|
|
')',
|
|
remove_icon_html
|
|
].join(' ');
|
|
_html += '</div></div>';
|
|
|
|
if ( _.isArray( _level_param.collection ) && ! _.isEmpty( _level_param.collection ) ) {
|
|
_html += self.getLevelTreeHtml( _level_param.collection, level );
|
|
}
|
|
_html += '</li>';
|
|
}//if ( !skipLevel )
|
|
});//_.each
|
|
|
|
_html += '</ul>';
|
|
|
|
return _html;
|
|
},
|
|
|
|
// the module icons can be
|
|
// an svg file like Nimble__divider_icon.svg => in this case we build and return the full url
|
|
// or a font_icon like '<i class="fab fa-wordpress-simple"></i>'
|
|
getTreeModuleIcon : function( modType ) {
|
|
var _icon = {}, icon_img_src;
|
|
_.each( sektionsLocalizedData.moduleCollection, function( modData ) {
|
|
if ( !_.isEmpty( _icon ) )
|
|
return;
|
|
if ( modType === modData['content-id'] ) {
|
|
if ( !_.isEmpty( modData.icon ) ) {
|
|
if ( 'http' === modData.icon.substring(0, 4) ) {
|
|
icon_img_src = modData.icon;
|
|
} else {
|
|
icon_img_src = sektionsLocalizedData.moduleIconPath + modData.icon;
|
|
}
|
|
_icon = {
|
|
svg : modData.icon ? icon_img_src : '',
|
|
font : modData.font_icon ? modData.font_icon : ''
|
|
};
|
|
}
|
|
}
|
|
});
|
|
if ( !_.isEmpty( _icon.svg ) ) {
|
|
return '<img class="sek-svg-mod-icon" src="' + _icon.svg + '"/>';
|
|
} else if ( !_.isEmpty( _icon.font ) ) {
|
|
return _icon.font;
|
|
}
|
|
},
|
|
|
|
getTreeModuleTitle : function( modType ) {
|
|
var _title = {};
|
|
_.each( sektionsLocalizedData.moduleCollection, function( modData ) {
|
|
if ( !_.isEmpty( _title ) )
|
|
return;
|
|
if ( modType === modData['content-id'] ) {
|
|
_title = modData.title;
|
|
}
|
|
});
|
|
return _title;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
//
|
|
//Note : idOfSectionToSave is set when user clicks on the save icon, and is reset when saving/updating closing save dialog
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// SAVE SECTION DIALOG BLOCK
|
|
// fired in ::initialize()
|
|
setupSaveSectionUI : function() {
|
|
var self = this;
|
|
|
|
// Declare api values and schedule reactions
|
|
self.saveSectionDialogVisible = new api.Value( false );// Hidden by default
|
|
|
|
// The observer for visibility
|
|
// connected to other observers
|
|
self.saveSectionDialogVisible.bind( function( visible ){
|
|
if ( visible ) {
|
|
// close template gallery
|
|
// close level tree
|
|
self.templateGalleryExpanded(false);
|
|
self.levelTreeExpanded(false);
|
|
if ( self.tmplDialogVisible ) {
|
|
self.tmplDialogVisible(false);
|
|
}
|
|
}
|
|
self.toggleSaveSectionUI(visible);
|
|
});
|
|
|
|
// Will store the collection of saved sections
|
|
self.allSavedSections = new api.Value('_not_populated_');
|
|
// When the collection is refreshed
|
|
// - populate select options
|
|
// - set the select value to default 'none'
|
|
self.allSavedSections.bind( function( sec_collection ) {
|
|
if ( !_.isObject(sec_collection) ) {
|
|
api.errare('error setupSaveSectionUI => section collection should be an object');
|
|
return;
|
|
}
|
|
sec_collection = _.isEmpty(sec_collection) ? {} : sec_collection;
|
|
self.refreshSectionPickerHtml( sec_collection );
|
|
});
|
|
|
|
self.saveSectionDialogMode = new api.Value('hidden');// 'save' default mode is set when dialog html is rendered
|
|
self.saveSectionDialogMode.bind( function(mode){
|
|
if ( !_.contains(['hidden', 'save', 'update', 'remove', 'edit' ], mode ) ) {
|
|
api.errare('error setupSaveSectionUI => unknown section dialog mode', mode );
|
|
mode = 'save';
|
|
}
|
|
|
|
// Set the button pressed state
|
|
var $secSaveDialogWrap = $('#nimble-top-section-save-ui'),
|
|
$titleInput = $secSaveDialogWrap.find('#sek-saved-section-title'),
|
|
$descInput = $secSaveDialogWrap.find('#sek-saved-section-description');
|
|
|
|
$secSaveDialogWrap.find('[data-section-mode-switcher]').attr('aria-pressed', false );
|
|
$secSaveDialogWrap.find('[data-section-mode-switcher="' + mode +'"]').attr('aria-pressed', true );
|
|
|
|
// update the current mode
|
|
$('#nimble-top-section-save-ui').attr('data-sek-section-dialog-mode', mode );
|
|
|
|
// make sure the remove dialog is hidden
|
|
$secSaveDialogWrap.removeClass('sek-removal-confirmation-opened');
|
|
var $selectEl;
|
|
// execute actions depending on the selected mode
|
|
switch( mode ) {
|
|
case 'save' :
|
|
// When selecting 'save', make sure the title and description input are cleaned
|
|
$titleInput.val('');
|
|
$descInput.val('');
|
|
break;
|
|
case 'update' :
|
|
case 'edit' :
|
|
$selectEl = $secSaveDialogWrap.find('.sek-saved-section-picker');
|
|
// Make sure the select value is always reset when switching mode
|
|
$selectEl.val('none').trigger('change');
|
|
|
|
self.setSavedSectionCollection().done( function( sec_collection ) {
|
|
// refresh section picker in case the user updated without changing anything
|
|
self.refreshSectionPickerHtml();
|
|
$selectEl.val( self.userSectionToEdit || 'none' ).trigger('change');
|
|
self.userSectionToEdit = null;
|
|
});
|
|
break;
|
|
case 'remove' :
|
|
console.log('sOOO ?', self.userSectionToRemove );
|
|
$selectEl = $secSaveDialogWrap.find('.sek-saved-section-picker');
|
|
// Make sure the select value is always reset when switching mode
|
|
$selectEl.val('none').trigger('change');
|
|
|
|
self.setSavedSectionCollection().done( function( sec_collection ) {
|
|
// refresh section picker in case the user updated without changing anything
|
|
self.refreshSectionPickerHtml();
|
|
$selectEl.val( self.userSectionToRemove || 'none' ).trigger('change');
|
|
self.userSectionToRemove = null;
|
|
});
|
|
break;
|
|
}//switch
|
|
|
|
// when user clicks on the remove icon of a section in the collection
|
|
// => hide save and update buttons
|
|
if ( 'remove' === mode && _.isEmpty( self.idOfSectionToSave ) ) {
|
|
$secSaveDialogWrap.addClass('sek-is-removal-only');
|
|
} else {
|
|
$secSaveDialogWrap.removeClass('sek-is-removal-only');
|
|
}
|
|
});//self.saveSectionDialogMode.bind()
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// RENDER DIALOG BOX AND SCHEDULE CLICK ACTIONS
|
|
refreshSectionPickerHtml : function( sec_collection ) {
|
|
sec_collection = sec_collection || this.allSavedSections();
|
|
|
|
var $secSaveDialogWrap = $('#nimble-top-section-save-ui'),
|
|
$selectEl = $secSaveDialogWrap.find('.sek-saved-section-picker');
|
|
// Make sure the select value is always reset when switching mode
|
|
$selectEl.val('none').trigger('change');
|
|
|
|
// empty all options but the default 'none' one
|
|
$selectEl.find('option').each( function() {
|
|
if ( 'none' !== $(this).attr('value') ) {
|
|
$(this).remove();
|
|
}
|
|
});
|
|
|
|
// Make sure we don't populate the collection twice ( if user clicks two times fast )
|
|
// if ( $secSaveDialogWrap.hasClass('sec-collection-populated') )
|
|
// return;
|
|
|
|
var _default_title = 'section title not set',
|
|
_title,
|
|
_last_modified_date,
|
|
_html = '';
|
|
_.each( sec_collection, function( _sec_data, _sec_post_name ) {
|
|
if ( !_.isObject(_sec_data) )
|
|
return;
|
|
_last_modified_date = _sec_data.last_modified_date ? _sec_data.last_modified_date : '';
|
|
_title = _sec_data.title ? _sec_data.title : _default_title;
|
|
_html +='<option value="' + _sec_post_name + '">' + [ _title, sektionsLocalizedData.i18n['Last modified'] + ' : ' + _last_modified_date ].join(' | ') + '</option>';
|
|
});
|
|
|
|
$selectEl.append(_html);
|
|
|
|
// flag so we know it's done
|
|
// => controls the CSS visibility of the select element
|
|
$secSaveDialogWrap.addClass('section-collection-populated');
|
|
},
|
|
|
|
|
|
//@param = { }
|
|
renderSectionSaveUI : function( params ) {
|
|
if ( $('#nimble-top-section-save-ui').length > 0 )
|
|
return $('#nimble-top-section-save-ui');
|
|
|
|
var self = this;
|
|
|
|
try {
|
|
_tmpl = wp.template( 'nimble-top-section-save-ui' )( {} );
|
|
} catch( er ) {
|
|
api.errare( 'Error when parsing nimble-top-section-save-ui template', er );
|
|
return false;
|
|
}
|
|
$('#customize-preview').after( $( _tmpl ) );
|
|
return $('#nimble-top-section-save-ui');
|
|
},
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// DOM EVENTS
|
|
// Fired once, on first rendering
|
|
maybeScheduleSectionSaveDOMEvents : function() {
|
|
var self = this, $secSaveDialogWrap = $('#nimble-top-section-save-ui');
|
|
if ( $secSaveDialogWrap.data('nimble-sec-save-dom-events-scheduled') )
|
|
return;
|
|
|
|
// ATTACH DOM EVENTS
|
|
// Dialog Mode Switcher
|
|
$secSaveDialogWrap
|
|
.on( 'click', '[data-section-mode-switcher]', function(evt) {
|
|
evt.preventDefault();
|
|
self.saveSectionDialogMode($(this).data('section-mode-switcher'));
|
|
})
|
|
|
|
// React to section select
|
|
// update title and description fields on section selection
|
|
.on( 'change', '.sek-saved-section-picker', function(evt){ self.reactOnSectionSelection(evt, $(this) ); })
|
|
|
|
// SAVE
|
|
.on( 'click', '.sek-do-save-section', function(evt){
|
|
$secSaveDialogWrap.addClass('nimble-section-processing-ajax');
|
|
self.saveOrUpdateSavedSection(evt).done( function( response ) {
|
|
$secSaveDialogWrap.removeClass('nimble-section-processing-ajax');
|
|
if ( response.success ) {
|
|
self.saveSectionDialogVisible( false );
|
|
self.setSavedSectionCollection( { refresh : true } );// <= true for refresh
|
|
}
|
|
});
|
|
})
|
|
|
|
// UPDATE
|
|
.on( 'click', '.sek-do-update-section', function(evt){
|
|
var $selectEl = $secSaveDialogWrap.find('.sek-saved-section-picker'),
|
|
sectionPostNameCandidateForUpdate = $selectEl.val();
|
|
// make sure we don't try to remove the default option
|
|
if ( 'none' === sectionPostNameCandidateForUpdate || _.isEmpty(sectionPostNameCandidateForUpdate) )
|
|
return;
|
|
|
|
$secSaveDialogWrap.addClass('nimble-section-processing-ajax');
|
|
self.saveOrUpdateSavedSection(evt, sectionPostNameCandidateForUpdate).done( function(response) {
|
|
$secSaveDialogWrap.removeClass('nimble-section-processing-ajax');
|
|
if ( response.success ) {
|
|
self.saveSectionDialogVisible( false );
|
|
self.setSavedSectionCollection( { refresh : true } )// <= true for refresh
|
|
.done( function( sec_collection ) {
|
|
// refresh section picker in case the user updated without changing anything
|
|
self.refreshSectionPickerHtml();
|
|
});
|
|
}
|
|
});
|
|
})
|
|
|
|
// REMOVE
|
|
// Reveal remove dialog
|
|
.on( 'click', '.sek-open-remove-confirmation', function(evt){
|
|
$secSaveDialogWrap.addClass('sek-removal-confirmation-opened');
|
|
})
|
|
|
|
// Do Remove
|
|
.on( 'click', '.sek-do-remove-section', function(evt){
|
|
var $selectEl = $secSaveDialogWrap.find('.sek-saved-section-picker'),
|
|
sectionPostNameCandidateForRemoval = $selectEl.val();
|
|
// make sure we don't try to remove the default option
|
|
if ( 'none' === sectionPostNameCandidateForRemoval || _.isEmpty(sectionPostNameCandidateForRemoval) )
|
|
return;
|
|
|
|
$secSaveDialogWrap.addClass('nimble-section-processing-ajax');
|
|
self.removeSavedSection(evt, sectionPostNameCandidateForRemoval).done( function(response) {
|
|
$secSaveDialogWrap.removeClass('nimble-section-processing-ajax');
|
|
$secSaveDialogWrap.removeClass('sek-removal-confirmation-opened');
|
|
if ( response.success ) {
|
|
self.setSavedSectionCollection( { refresh : true } );// <= true for refresh
|
|
}
|
|
});
|
|
})
|
|
|
|
// Cancel Remove
|
|
.on( 'click', '.sek-cancel-remove-section', function(evt){
|
|
$secSaveDialogWrap.removeClass('sek-removal-confirmation-opened');
|
|
});
|
|
|
|
|
|
// Switch to update mode
|
|
//$secSaveDialogWrap.on( 'click', '[data-section-mode-switcher="update"]', function(evt){ });
|
|
|
|
$('.sek-close-dialog', $secSaveDialogWrap ).on( 'click', function(evt) {
|
|
evt.preventDefault();
|
|
self.saveSectionDialogVisible(false);
|
|
});
|
|
|
|
// Say we're done with DOM event scheduling
|
|
$secSaveDialogWrap.data('nimble-sec-save-dom-events-scheduled', true );
|
|
},
|
|
|
|
|
|
|
|
// Is used in update and remove modes
|
|
reactOnSectionSelection : function(evt, $selectEl ){
|
|
var self = this,
|
|
$secSaveDialogWrap = $('#nimble-top-section-save-ui'),
|
|
_sectionPostName = $selectEl.val(),
|
|
$titleInput = $secSaveDialogWrap.find('#sek-saved-section-title'),
|
|
$descInput = $secSaveDialogWrap.find('#sek-saved-section-description'),
|
|
// The informative class control the visibility of the title and the description in CSS
|
|
_informativeClass = 'update' === self.saveSectionDialogMode() ? 'sek-section-update-selected' : 'sek-section-remove-selected';
|
|
|
|
if ( 'none' === _sectionPostName ) {
|
|
$titleInput.val('');
|
|
$descInput.val('');
|
|
$secSaveDialogWrap.removeClass(_informativeClass);
|
|
} else {
|
|
var _allSavedSections = self.allSavedSections();
|
|
var _selectedSection = _sectionPostName;
|
|
|
|
// normalize
|
|
_allSavedSections = ( _.isObject(_allSavedSections) && !_.isArray(_allSavedSections) ) ? _allSavedSections : {};
|
|
_allSavedSections[_sectionPostName] = $.extend( {
|
|
title : '',
|
|
description : '',
|
|
last_modified_date : ''
|
|
}, _allSavedSections[_sectionPostName] || {} );
|
|
|
|
$titleInput.val( _allSavedSections[_sectionPostName].title );
|
|
$descInput.val( _allSavedSections[_sectionPostName].description );
|
|
$secSaveDialogWrap.addClass(_informativeClass);
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// AJAX ACTIONS
|
|
// Fired on 'click' on .sek-do-save-section btn
|
|
// @param sectionPostNameCandidateForUpdate is only provided when saving
|
|
saveOrUpdateSavedSection : function(evt, sectionPostNameCandidateForUpdate ) {
|
|
var self = this,
|
|
_dfd_ = $.Deferred(),
|
|
_isEditSectionMode = 'edit' === self.saveSectionDialogMode();
|
|
|
|
// idOfSectionToSave is set when reacting to click action
|
|
// @see react to preview 'sek-toggle-save-section-ui'
|
|
if ( !_isEditSectionMode ) {
|
|
if ( !self.idOfSectionToSave || _.isEmpty( self.idOfSectionToSave ) ) {
|
|
api.errare('saveOrUpdateSavedSection => error => missing section id');
|
|
return _dfd_.resolve( {success:false});
|
|
}
|
|
}
|
|
evt.preventDefault();
|
|
var $_title = $('#sek-saved-section-title'),
|
|
section_title = $_title.val(),
|
|
section_description = $('#sek-saved-section-description').val(),
|
|
sectionModel;
|
|
|
|
// Only get the section model when not in edit section mode
|
|
if ( !_isEditSectionMode ) {
|
|
sectionModel = $.extend( true, {}, self.getLevelModel( self.idOfSectionToSave ) );
|
|
if ( 'no_match' == sectionModel ) {
|
|
api.errare('saveOrUpdateSavedSection => error => no section model with id ' + self.idOfSectionToSave );
|
|
return _dfd_.resolve( {success:false});
|
|
}
|
|
// Do some pre-processing before ajaxing
|
|
// Note : ids will be replaced server side
|
|
sectionModel = self.preProcessSection( sectionModel );
|
|
if ( !_.isObject( sectionModel ) ) {
|
|
api.errare('::saveOrUpdateSavedSection => error => invalid sectionModel');
|
|
return _dfd_.resolve( {success:false});
|
|
}
|
|
}
|
|
|
|
if ( _.isEmpty( section_title ) ) {
|
|
$_title.addClass('error');
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['A title is required'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
|
|
});
|
|
return _dfd_.resolve( {success:false});
|
|
}
|
|
|
|
$('#sek-saved-section-title').removeClass('error');
|
|
|
|
wp.ajax.post( 'sek_save_user_section', {
|
|
nonce: api.settings.nonce.save,
|
|
section_data: _isEditSectionMode ? '' : JSON.stringify( sectionModel ),
|
|
// the following will be saved in 'metas'
|
|
section_title: section_title,
|
|
section_description: section_description,
|
|
section_post_name: sectionPostNameCandidateForUpdate || '',// <= provided when updating a section
|
|
skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' ),
|
|
edit_metas_only: _isEditSectionMode ? 'yes' : 'no'//<= in this case we only update title and description. Not the template content
|
|
//active_locations : api.czr_sektions.activeLocations()
|
|
})
|
|
.done( function( response ) {
|
|
//console.log('SAVED POST ID', response );
|
|
_dfd_.resolve( {success:true});
|
|
|
|
// response is {section_post_id: 436}
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'success',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Template saved'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
})
|
|
.fail( function( er ) {
|
|
_dfd_.resolve( {success:false});
|
|
api.errorLog( 'ajax sek_save_section => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Error when processing template'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
})
|
|
.always( function() {
|
|
// reset the id of section to save
|
|
// => because we need to know when we are in 'remove' mode when user clicked on remove icon in the section collection, => which hides save and update buttons
|
|
self.idOfSectionToSave = null;
|
|
});
|
|
return _dfd_;
|
|
},//saveOrUpdateSavedSection
|
|
|
|
|
|
// @return a section model
|
|
// Note : ids are reset server side
|
|
// Example of section model before preprocessing
|
|
// {
|
|
// collection: [{…}]
|
|
// id: "" //<= reset server side
|
|
// level: "section"
|
|
// is_nested : false
|
|
// options: {bg: {…}}
|
|
// ver_ini: "1.1.8"
|
|
// }
|
|
preProcessSection : function( sectionModel ) {
|
|
if ( !_.isObject( sectionModel ) ) {
|
|
return null;
|
|
}
|
|
var preprocessedModel = $.extend( {}, true, sectionModel );
|
|
// Make sure a nested section is saved as normal
|
|
if ( _.has( preprocessedModel, 'is_nested') ) {
|
|
preprocessedModel = _.omit( preprocessedModel, 'is_nested' );
|
|
}
|
|
return preprocessedModel;
|
|
},
|
|
|
|
|
|
// Fired on 'click on .sek-do-remove-section btn
|
|
removeSavedSection : function(evt, sectionPostNameCandidateForRemoval ) {
|
|
var self = this, _dfd_ = $.Deferred();
|
|
evt.preventDefault();
|
|
wp.ajax.post( 'sek_remove_user_section', {
|
|
nonce: api.settings.nonce.save,
|
|
section_post_name: sectionPostNameCandidateForRemoval
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( response ) {
|
|
_dfd_.resolve( {success:true});
|
|
// response is {section_post_id: 436}
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'success',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Template removed'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
})
|
|
.fail( function( er ) {
|
|
_dfd_.resolve( {success:false});
|
|
api.errorLog( 'ajax sek_remove_section => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Error when processing templates'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
})
|
|
.always( function() {
|
|
// reset the id of section to save
|
|
// => because we need to know when we are in 'remove' mode when user clicked on remove icon in the section collection, => which hides save and update buttons
|
|
self.idOfSectionToSave = null;
|
|
});
|
|
|
|
return _dfd_;
|
|
},
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// REVEAL / HIDE DIALOG BOX
|
|
/// react on self.saveSectionDialogVisible.bind(...)
|
|
// @return void()
|
|
// self.saveSectionDialogVisible.bind( function( visible ){
|
|
// self.toggleSaveSectionUI( visible );
|
|
// });
|
|
toggleSaveSectionUI : function( visible ) {
|
|
visible = _.isUndefined( visible ) ? true : visible;
|
|
var self = this,
|
|
_renderAndSetup = function() {
|
|
$.when( self.renderSectionSaveUI({}) ).done( function( $_el ) {
|
|
self.maybeScheduleSectionSaveDOMEvents();//<= schedule on the first display only
|
|
self.saveUIContainer = $_el;
|
|
//display
|
|
_.delay( function() {
|
|
// set dialog mode now so we display the relevant fields on init
|
|
self.saveSectionDialogMode('save');// Default mode is save
|
|
self.cachedElements.$body.addClass('sek-save-section-ui-visible');
|
|
}, 200 );
|
|
// set section id input value
|
|
//$('#sek-saved-section-id').val( sectionId );
|
|
});
|
|
},
|
|
_hide = function() {
|
|
var dfd = $.Deferred();
|
|
self.cachedElements.$body.removeClass('sek-save-section-ui-visible');
|
|
if ( $( '#nimble-top-section-save-ui' ).length > 0 ) {
|
|
//remove Dom element after slide up
|
|
_.delay( function() {
|
|
// set dialog mode back to 'hidden' mode
|
|
self.saveSectionDialogMode = self.saveSectionDialogMode ? self.saveSectionDialogMode : new api.Value();
|
|
self.saveSectionDialogMode('hidden');
|
|
self.saveUIContainer.remove();
|
|
// reset the id of section to save
|
|
// => because we need to know when we are in 'remove' mode when user clicked on remove icon in the section collection, => which hides save and update buttons
|
|
self.idOfSectionToSave = null;
|
|
dfd.resolve();
|
|
}, 250 );
|
|
} else {
|
|
dfd.resolve();
|
|
}
|
|
return dfd.promise();
|
|
};
|
|
|
|
if ( visible ) {
|
|
_renderAndSetup();
|
|
} else {
|
|
_hide().done( function() {
|
|
self.saveSectionDialogVisible( false );//should be already false
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// TMPL COLLECTION
|
|
// @return $.promise
|
|
// @param params = {refresh : false};
|
|
setSavedSectionCollection : function( params ) {
|
|
var self = this, _dfd_ = $.Deferred();
|
|
|
|
// refresh is true on save, update, remove success
|
|
params = params || {refresh : false};
|
|
|
|
// If the collection is already set, return it.
|
|
// unless this is a "refresh" case
|
|
if ( !params.refresh && '_not_populated_' !== self.allSavedSections() ) {
|
|
return _dfd_.resolve( self.allSavedSections() );
|
|
}
|
|
|
|
var _promise;
|
|
// Prevent a double request while ajax request is being processed
|
|
if ( self.sectionCollectionPromise && 'pending' === self.sectionCollectionPromise.state() ) {
|
|
_promise = self.sectionCollectionPromise;
|
|
} else {
|
|
_promise = self.getSavedSectionCollection( params );
|
|
}
|
|
_promise.done( function( sec_collection ) {
|
|
self.allSavedSections( sec_collection );
|
|
_dfd_.resolve( sec_collection );
|
|
});
|
|
return _dfd_.promise();
|
|
},
|
|
|
|
// @return a promise
|
|
// @param params = {refresh : false};
|
|
// also used from the input Constructor of sek_my_sections_sec_picker_module
|
|
// @param params = {refresh : false};
|
|
getSavedSectionCollection : function( params ) {
|
|
var self = this;
|
|
// refresh is true on save, update, remove success
|
|
params = params || {refresh : false};
|
|
|
|
self.sectionCollectionPromise = $.Deferred();
|
|
if ( !params.refresh && '_not_populated_' !== self.allSavedSections() ) {
|
|
self.sectionCollectionPromise.resolve( self.allSavedSections() );
|
|
return self.sectionCollectionPromise;
|
|
}
|
|
wp.ajax.post( 'sek_get_all_saved_sections', {
|
|
nonce: api.settings.nonce.save
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( sec_collection ) {
|
|
if ( _.isObject(sec_collection) && !_.isArray( sec_collection ) ) {
|
|
self.sectionCollectionPromise.resolve( sec_collection );
|
|
} else {
|
|
self.sectionCollectionPromise.resolve( {} );
|
|
if ( !_.isEmpty( sec_collection ) ) {
|
|
api.errorLog('control::getSavedSectionCollection => collection is empty or invalid');
|
|
}
|
|
}
|
|
|
|
// response is {section_post_id: 436}
|
|
//self.saveSectionDialogVisible( false );
|
|
// api.previewer.trigger('sek-notify', {
|
|
// type : 'success',
|
|
// duration : 10000,
|
|
// message : [
|
|
// '<span style="font-size:0.95em">',
|
|
// '<strong>Your section has been saved.</strong>',
|
|
// '</span>'
|
|
// ].join('')
|
|
// });
|
|
})
|
|
.fail( function( er ) {
|
|
api.errorLog( 'ajax sek_get_all_saved_section => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Error when processing templates'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
self.sectionCollectionPromise.resolve({});
|
|
});
|
|
|
|
return self.sectionCollectionPromise;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
// introduced in april 2020 for https://github.com/presscustomizr/nimble-builder/issues/655
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// SAVE TMPL DIALOG BLOCK
|
|
// fired in ::initialize()
|
|
setupSaveTmplUI : function() {
|
|
var self = this;
|
|
|
|
// Declare api values and schedule reactions
|
|
self.tmplDialogVisible = new api.Value( false );// Hidden by default
|
|
|
|
if ( !sektionsLocalizedData.isTemplateSaveEnabled ) {
|
|
return;
|
|
}
|
|
self.tmplDialogVisible.bind( function( visible ){
|
|
if ( visible ) {
|
|
// close template gallery
|
|
// close level tree
|
|
self.templateGalleryExpanded(false);
|
|
self.levelTreeExpanded(false);
|
|
if ( self.saveSectionDialogVisible ) {
|
|
self.saveSectionDialogVisible(false);
|
|
}
|
|
}
|
|
self.toggleSaveTmplUI(visible);
|
|
});
|
|
|
|
// Will store the collection of saved templates
|
|
self.allSavedTemplates = new api.Value('_not_populated_');
|
|
// When the collection is refreshed
|
|
// - populate select options
|
|
// - set the select value to default 'none'
|
|
self.allSavedTemplates.bind( function( tmpl_collection ) {
|
|
if ( !_.isObject(tmpl_collection) ) {
|
|
api.errare('error setupSaveTmplUI => tmpl collection should be an object');
|
|
return;
|
|
}
|
|
tmpl_collection = _.isEmpty(tmpl_collection) ? {} : tmpl_collection;
|
|
self.refreshTmplPickerHtml( tmpl_collection );
|
|
});
|
|
|
|
// Will store the collection of saved templates
|
|
self.allApiTemplates = new api.Value('_not_populated_');
|
|
|
|
self.tmplDialogMode = new api.Value('hidden');// 'save' default mode is set when dialog html is rendered
|
|
self.tmplDialogMode.bind( function(mode){
|
|
if ( !_.contains(['hidden', 'save', 'update', 'remove', 'edit' ], mode ) ) {
|
|
api.errare('::setupSaveTmplUI => unknown tmpl dialog mode', mode );
|
|
mode = 'save';
|
|
}
|
|
|
|
// Set the button pressed state
|
|
var $tmplDialogWrapper = $('#nimble-top-tmpl-save-ui'),
|
|
$titleInput = $tmplDialogWrapper.find('#sek-saved-tmpl-title'),
|
|
$descInput = $tmplDialogWrapper.find('#sek-saved-tmpl-description');
|
|
|
|
$tmplDialogWrapper.find('[data-tmpl-mode-switcher]').attr('aria-pressed', false );
|
|
$tmplDialogWrapper.find('[data-tmpl-mode-switcher="' + mode +'"]').attr('aria-pressed', true );
|
|
|
|
// update the current mode
|
|
$('#nimble-top-tmpl-save-ui').attr('data-sek-tmpl-dialog-mode', mode );
|
|
|
|
// make sure the remove dialog is hidden
|
|
$tmplDialogWrapper.removeClass('sek-removal-confirmation-opened');
|
|
var $selectEl;
|
|
// execute actions depending on the selected mode
|
|
switch( mode ) {
|
|
case 'save' :
|
|
// When selecting 'save', make sure the title and description input are cleaned
|
|
$titleInput.val('');
|
|
$descInput.val('');
|
|
break;
|
|
case 'update' :
|
|
case 'remove' :
|
|
$selectEl = $tmplDialogWrapper.find('.sek-saved-tmpl-picker');
|
|
// Make sure the select value is always reset when switching mode
|
|
$selectEl.val('none').trigger('change');
|
|
|
|
self.setSavedTmplCollection().done( function( tmpl_collection ) {
|
|
// refresh tmpl picker in case the user updated without changing anything
|
|
self.refreshTmplPickerHtml();
|
|
$selectEl.val( self.tmplToRemove || 'none' ).trigger('change');
|
|
self.tmplToRemove = null;
|
|
});
|
|
break;
|
|
case 'edit' :
|
|
$selectEl = $tmplDialogWrapper.find('.sek-saved-tmpl-picker');
|
|
// Make sure the select value is always reset when switching mode
|
|
$selectEl.val('none').trigger('change');
|
|
|
|
self.setSavedTmplCollection().done( function( tmpl_collection ) {
|
|
// refresh tmpl picker in case the user updated without changing anything
|
|
self.refreshTmplPickerHtml();
|
|
$selectEl.val( self.tmplToEdit || 'none' ).trigger('change');
|
|
self.tmplToEdit = null;
|
|
});
|
|
break;
|
|
}//switch
|
|
});//self.tmplDialogMode.bind()
|
|
|
|
},
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// RENDER DIALOG BOX AND SCHEDULE CLICK ACTIONS
|
|
refreshTmplPickerHtml : function( tmpl_collection ) {
|
|
tmpl_collection = tmpl_collection || this.allSavedTemplates();
|
|
|
|
var $tmplDialogWrapper = $('#nimble-top-tmpl-save-ui'),
|
|
$selectEl = $tmplDialogWrapper.find('.sek-saved-tmpl-picker');
|
|
// Make sure the select value is always reset when switching mode
|
|
$selectEl.val('none').trigger('change');
|
|
|
|
// empty all options but the default 'none' one
|
|
$selectEl.find('option').each( function() {
|
|
if ( 'none' !== $(this).attr('value') ) {
|
|
$(this).remove();
|
|
}
|
|
});
|
|
|
|
// Make sure we don't populate the collection twice ( if user clicks two times fast )
|
|
// if ( $tmplDialogWrapper.hasClass('tmpl-collection-populated') )
|
|
// return;
|
|
|
|
var _default_title = 'template title not set',
|
|
_title,
|
|
_last_modified_date,
|
|
_html = '';
|
|
_.each( tmpl_collection, function( _tmpl_data, _tmpl_post_name ) {
|
|
if ( !_.isObject(_tmpl_data) )
|
|
return;
|
|
_last_modified_date = _tmpl_data.last_modified_date ? _tmpl_data.last_modified_date : '';
|
|
_title = _tmpl_data.title ? _tmpl_data.title : _default_title;
|
|
_html +='<option value="' + _tmpl_post_name + '">' + [ _title, sektionsLocalizedData.i18n['Last modified'] + ' : ' + _last_modified_date ].join(' | ') + '</option>';
|
|
});
|
|
|
|
$selectEl.append(_html);
|
|
|
|
// flag so we know it's done
|
|
// => controls the CSS visibility of the select element
|
|
$tmplDialogWrapper.addClass('tmpl-collection-populated');
|
|
},
|
|
|
|
|
|
//@param = { }
|
|
renderTmplUI : function( params ) {
|
|
if ( $('#nimble-top-tmpl-save-ui').length > 0 )
|
|
return $('#nimble-top-tmpl-save-ui');
|
|
|
|
var self = this;
|
|
|
|
try {
|
|
_tmpl = wp.template( 'nimble-top-tmpl-save-ui' )( {} );
|
|
} catch( er ) {
|
|
api.errare( 'Error when parsing nimble-top-tmpl-save-ui template', er );
|
|
return false;
|
|
}
|
|
$('#customize-preview').after( $( _tmpl ) );
|
|
return $('#nimble-top-tmpl-save-ui');
|
|
},
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// DOM EVENTS
|
|
// Fired once, on first rendering
|
|
scheduleTmplSaveDOMEvents : function() {
|
|
var self = this, $tmplDialogWrapper = $('#nimble-top-tmpl-save-ui');
|
|
if ( $tmplDialogWrapper.data('nimble-tmpl-dom-events-scheduled') )
|
|
return;
|
|
|
|
// ATTACH DOM EVENTS
|
|
// Dialog Mode Switcher
|
|
$tmplDialogWrapper.on( 'click', '[data-tmpl-mode-switcher]', function(evt) {
|
|
evt.preventDefault();
|
|
self.tmplDialogMode($(this).data('tmpl-mode-switcher'));
|
|
});
|
|
|
|
// React to template select
|
|
// update title and description fields on template selection
|
|
$tmplDialogWrapper.on( 'change', '.sek-saved-tmpl-picker', function(evt){ self.reactOnTemplateSelection(evt, $(this) ); });
|
|
|
|
// SAVE
|
|
$tmplDialogWrapper.on( 'click', '.sek-do-save-tmpl', function(evt){
|
|
$tmplDialogWrapper.addClass('nimble-tmpl-processing-ajax');
|
|
self.saveOrUpdateTemplate(evt).done( function( response ) {
|
|
$tmplDialogWrapper.removeClass('nimble-tmpl-processing-ajax');
|
|
if ( response.success ) {
|
|
self.tmplDialogVisible( false );
|
|
self.setSavedTmplCollection( { refresh : true } );// <= true for refresh
|
|
}
|
|
});
|
|
});
|
|
|
|
// UPDATE
|
|
$tmplDialogWrapper.on( 'click', '.sek-do-update-tmpl', function(evt){
|
|
var $selectEl = $tmplDialogWrapper.find('.sek-saved-tmpl-picker'),
|
|
tmplPostNameCandidateForUpdate = $selectEl.val();
|
|
// make sure we don't try to remove the default option
|
|
if ( 'none' === tmplPostNameCandidateForUpdate || _.isEmpty(tmplPostNameCandidateForUpdate) )
|
|
return;
|
|
|
|
$tmplDialogWrapper.addClass('nimble-tmpl-processing-ajax');
|
|
self.saveOrUpdateTemplate(evt, tmplPostNameCandidateForUpdate).done( function(response) {
|
|
$tmplDialogWrapper.removeClass('nimble-tmpl-processing-ajax');
|
|
if ( response.success ) {
|
|
self.tmplDialogVisible( false );
|
|
self.setSavedTmplCollection( { refresh : true } )// <= true for refresh
|
|
.done( function( tmpl_collection ) {
|
|
// refresh tmpl picker in case the user updated without changing anything
|
|
self.refreshTmplPickerHtml();
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// REMOVE
|
|
// Reveal remove dialog
|
|
$tmplDialogWrapper.on( 'click', '.sek-open-remove-confirmation', function(evt){
|
|
$tmplDialogWrapper.addClass('sek-removal-confirmation-opened');
|
|
});
|
|
|
|
// Do Remove
|
|
$tmplDialogWrapper.on( 'click', '.sek-do-remove-tmpl', function(evt){
|
|
var $selectEl = $tmplDialogWrapper.find('.sek-saved-tmpl-picker'),
|
|
tmplPostNameCandidateForRemoval = $selectEl.val();
|
|
// make sure we don't try to remove the default option
|
|
if ( 'none' === tmplPostNameCandidateForRemoval || _.isEmpty(tmplPostNameCandidateForRemoval) )
|
|
return;
|
|
|
|
$tmplDialogWrapper.addClass('nimble-tmpl-processing-ajax');
|
|
self.removeTemplate(evt, tmplPostNameCandidateForRemoval).done( function(response) {
|
|
$tmplDialogWrapper.removeClass('nimble-tmpl-processing-ajax');
|
|
$tmplDialogWrapper.removeClass('sek-removal-confirmation-opened');
|
|
if ( response.success ) {
|
|
self.setSavedTmplCollection( { refresh : true } );// <= true for refresh
|
|
}
|
|
});
|
|
});
|
|
|
|
// Cancel Remove
|
|
$tmplDialogWrapper.on( 'click', '.sek-cancel-remove-tmpl', function(evt){
|
|
$tmplDialogWrapper.removeClass('sek-removal-confirmation-opened');
|
|
});
|
|
|
|
|
|
// Switch to update mode
|
|
//$tmplDialogWrapper.on( 'click', '[data-tmpl-mode-switcher="update"]', function(evt){ });
|
|
|
|
$('.sek-close-dialog', $tmplDialogWrapper ).on( 'click', function(evt) {
|
|
evt.preventDefault();
|
|
self.tmplDialogVisible(false);
|
|
});
|
|
|
|
// Say we're done with DOM event scheduling
|
|
$tmplDialogWrapper.data('nimble-tmpl-dom-events-scheduled', true );
|
|
},
|
|
|
|
|
|
|
|
// Is used in update and remove modes
|
|
reactOnTemplateSelection : function(evt, $selectEl ){
|
|
var self = this,
|
|
$tmplDialogWrapper = $('#nimble-top-tmpl-save-ui'),
|
|
_tmplPostName = $selectEl.val(),
|
|
$titleInput = $tmplDialogWrapper.find('#sek-saved-tmpl-title'),
|
|
$descInput = $tmplDialogWrapper.find('#sek-saved-tmpl-description'),
|
|
// The informative class control the visibility of the title and the description in CSS
|
|
_informativeClass = 'update' === self.tmplDialogMode() ? 'sek-tmpl-update-selected' : 'sek-tmpl-remove-selected';
|
|
|
|
if ( 'none' === _tmplPostName ) {
|
|
$titleInput.val('');
|
|
$descInput.val('');
|
|
$tmplDialogWrapper.removeClass(_informativeClass);
|
|
} else {
|
|
var _allSavedTemplates = self.allSavedTemplates();
|
|
var _selectedTmpl = _tmplPostName;
|
|
|
|
// normalize
|
|
_allSavedTemplates = ( _.isObject(_allSavedTemplates) && !_.isArray(_allSavedTemplates) ) ? _allSavedTemplates : {};
|
|
_allSavedTemplates[_tmplPostName] = $.extend( {
|
|
title : '',
|
|
description : '',
|
|
last_modified_date : ''
|
|
}, _allSavedTemplates[_tmplPostName] || {} );
|
|
|
|
$titleInput.val( _allSavedTemplates[_tmplPostName].title );
|
|
$descInput.val( _allSavedTemplates[_tmplPostName].description );
|
|
$tmplDialogWrapper.addClass(_informativeClass);
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// AJAX ACTIONS
|
|
// Fired on 'click' on .sek-do-save-tmpl btn
|
|
saveOrUpdateTemplate : function(evt, tmplPostNameCandidateForUpdate ) {
|
|
var self = this, _dfd_ = $.Deferred();
|
|
evt.preventDefault();
|
|
var $_title = $('#sek-saved-tmpl-title'),
|
|
tmpl_title = $_title.val(),
|
|
tmpl_description = $('#sek-saved-tmpl-description').val(),
|
|
collectionSettingId = self.localSectionsSettingId(),
|
|
currentLocalSettingValue;
|
|
|
|
// Dec 2020 : remove locations not active on the page or empty before saving the tmpl
|
|
try { currentLocalSettingValue = self.preProcessTmpl( api( collectionSettingId )() ); } catch( er ) {
|
|
api.errorLog( 'error in ::saveOrUpdateTemplate', er );
|
|
_dfd_.resolve( {success:false});
|
|
}
|
|
|
|
if ( _.isEmpty( tmpl_title ) ) {
|
|
$_title.addClass('error');
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['A title is required'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
|
|
});
|
|
return _dfd_.resolve( {success:false});
|
|
}
|
|
|
|
$('#sek-saved-tmpl-title').removeClass('error');
|
|
|
|
wp.ajax.post( 'sek_save_user_template', {
|
|
nonce: api.settings.nonce.save,
|
|
tmpl_data: 'edit' === self.tmplDialogMode() ? '' : JSON.stringify( currentLocalSettingValue ),
|
|
// the following will be saved in 'metas'
|
|
tmpl_title: tmpl_title,
|
|
tmpl_description: tmpl_description,
|
|
tmpl_post_name: tmplPostNameCandidateForUpdate || '',// <= provided when updating a template
|
|
edit_metas_only: 'edit' === self.tmplDialogMode() ? 'yes' : 'no',//<= in this case we only update title and description. Not the template content
|
|
skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' ),
|
|
tmpl_locations : self.getActiveLocationsForTmpl( currentLocalSettingValue ),
|
|
tmpl_header_location : self.getHeaderOrFooterLocationIdForTmpl( 'header', currentLocalSettingValue ),
|
|
tmpl_footer_location : self.getHeaderOrFooterLocationIdForTmpl( 'footer', currentLocalSettingValue )
|
|
})
|
|
.done( function( response ) {
|
|
//console.log('SAVED POST ID', response );
|
|
_dfd_.resolve( {success:true});
|
|
// response is {tmpl_post_id: 436}
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'success',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Template saved'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
})
|
|
.fail( function( er ) {
|
|
_dfd_.resolve( {success:false});
|
|
api.errorLog( 'ajax sek_save_template => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Error when processing template'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
return _dfd_;
|
|
},//saveOrUpdateTemplate
|
|
|
|
// return an array of the location used for the template, to be used as template meta for a future injection
|
|
// we don't need to list all inactive locations of the current page in the template meta
|
|
getActiveLocationsForTmpl : function( tmpl_data ) {
|
|
if ( !_.isObject( tmpl_data ) ) {
|
|
throw new Error('preProcess Tmpl => error : tmpl_data must be an object');
|
|
}
|
|
var _tmpl_locations = [];
|
|
_.each( tmpl_data.collection, function( _loc_params ) {
|
|
if ( !_.isObject( _loc_params ) || !_loc_params.id || !_loc_params.level )
|
|
return;
|
|
|
|
if ( 'location' === _loc_params.level ) {
|
|
_tmpl_locations.push( _loc_params.id );
|
|
}
|
|
});
|
|
return _tmpl_locations;
|
|
},
|
|
|
|
getHeaderOrFooterLocationIdForTmpl : function( headerOrFooter, tmpl_data) {
|
|
var self = this;
|
|
if ( !_.isObject( tmpl_data ) ) {
|
|
throw new Error('preProcess Tmpl => error : tmpl_data must be an object');
|
|
}
|
|
var _loc = '';
|
|
_.each( tmpl_data.collection, function( _loc_params ) {
|
|
if ( !_.isObject( _loc_params ) || !_loc_params.id || !_loc_params.level )
|
|
return;
|
|
if ( ( 'header' === headerOrFooter && self.isHeaderLocation( _loc_params.id ) ) || ( 'footer' === headerOrFooter && self.isFooterLocation( _loc_params.id ) ) ) {
|
|
_loc = _loc_params.id;
|
|
}
|
|
});
|
|
return _loc;
|
|
},
|
|
|
|
// @return a tmpl model
|
|
// Note : ids are reset server side
|
|
// Example of tmpl model before preprocessing
|
|
// {
|
|
// collection: [{…}]
|
|
// id: "" //<= to remove
|
|
// level: "tmpl" // <= to remove
|
|
// options: {bg: {…}}
|
|
// ver_ini: "1.1.8"
|
|
// }
|
|
// Dec 2020 : remove locations not active on the page or empty before saving the tmpl
|
|
preProcessTmpl : function( tmpl_data ) {
|
|
var self = this, tmpl_processed, activeLocations;
|
|
if ( !_.isObject( tmpl_data ) ) {
|
|
throw new Error('preProcess Tmpl => error : tmpl_data must be an object');
|
|
}
|
|
tmpl_processed = $.extend( true, {}, tmpl_data );
|
|
tmpl_processed.collection = [];
|
|
activeLocations = self.activeLocations();
|
|
|
|
//console.log('TO DO => make sure template is ok to be saved', tmpl_data, self.activeLocations() );
|
|
_.each( tmpl_data.collection, function( _loc_params ) {
|
|
if ( !_.isObject( _loc_params ) || !_loc_params.id || !_loc_params.collection )
|
|
return;
|
|
|
|
if ( _.contains( activeLocations, _loc_params.id ) && !_.isEmpty(_loc_params.collection) ) {
|
|
tmpl_processed.collection.push( _loc_params );
|
|
}
|
|
});
|
|
return tmpl_processed;
|
|
},
|
|
|
|
// Fired on 'click on .sek-do-remove-tmpl btn
|
|
removeTemplate : function(evt, tmplPostNameCandidateForRemoval ) {
|
|
var self = this, _dfd_ = $.Deferred();
|
|
evt.preventDefault();
|
|
wp.ajax.post( 'sek_remove_user_template', {
|
|
nonce: api.settings.nonce.save,
|
|
tmpl_post_name: tmplPostNameCandidateForRemoval
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( response ) {
|
|
_dfd_.resolve( {success:true});
|
|
// response is {tmpl_post_id: 436}
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'success',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Template removed'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
})
|
|
.fail( function( er ) {
|
|
_dfd_.resolve( {success:false});
|
|
api.errorLog( 'ajax sek_remove_template => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Error when processing templates'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
|
|
return _dfd_;
|
|
},
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// REVEAL / HIDE DIALOG BOX
|
|
/// react on self.tmplDialogVisible.bind(...)
|
|
// @return void()
|
|
// self.tmplDialogVisible.bind( function( visible ){
|
|
// self.toggleSaveTmplUI( visible );
|
|
// });
|
|
toggleSaveTmplUI : function( visible ) {
|
|
visible = _.isUndefined( visible ) ? true : visible;
|
|
var self = this,
|
|
_renderAndSetup = function() {
|
|
$.when( self.renderTmplUI({}) ).done( function( $_el ) {
|
|
self.scheduleTmplSaveDOMEvents();
|
|
self.saveUIContainer = $_el;
|
|
//display
|
|
_.delay( function() {
|
|
// set dialog mode now so we display the relevant fields on init
|
|
self.tmplDialogMode('save');// Default mode is save
|
|
self.cachedElements.$body.addClass('sek-save-tmpl-ui-visible');
|
|
}, 200 );
|
|
// set tmpl id input value
|
|
//$('#sek-saved-tmpl-id').val( tmplId );
|
|
});
|
|
},
|
|
_hide = function() {
|
|
var dfd = $.Deferred();
|
|
self.cachedElements.$body.removeClass('sek-save-tmpl-ui-visible');
|
|
if ( $( '#nimble-top-tmpl-save-ui' ).length > 0 ) {
|
|
//remove Dom element after slide up
|
|
_.delay( function() {
|
|
// set dialog mode back to 'hidden' mode
|
|
self.tmplDialogMode = self.tmplDialogMode ? self.tmplDialogMode : new api.Value();
|
|
self.tmplDialogMode('hidden');
|
|
self.saveUIContainer.remove();
|
|
dfd.resolve();
|
|
}, 250 );
|
|
} else {
|
|
dfd.resolve();
|
|
}
|
|
return dfd.promise();
|
|
};
|
|
|
|
if ( visible ) {
|
|
_renderAndSetup();
|
|
} else {
|
|
_hide().done( function() {
|
|
self.tmplDialogVisible( false );//should be already false
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////
|
|
///// TMPL COLLECTION
|
|
// @return $.promise
|
|
setSavedTmplCollection : function( params ) {
|
|
var self = this, _dfd_ = $.Deferred();
|
|
|
|
// refresh is true on save, update, remove success
|
|
params = params || {refresh : false};
|
|
|
|
// If the collection is already set, return it.
|
|
// unless this is a "refresh" case
|
|
if ( !params.refresh && '_not_populated_' !== self.allSavedTemplates() ) {
|
|
return _dfd_.resolve( self.allSavedTemplates() );
|
|
}
|
|
|
|
var _promise;
|
|
// Prevent a double request while ajax request is being processed
|
|
if ( self.templateCollectionPromise && 'pending' === self.templateCollectionPromise.state() ) {
|
|
_promise = self.templateCollectionPromise;
|
|
} else {
|
|
_promise = self.getSavedTmplCollection();
|
|
}
|
|
_promise.done( function( tmpl_collection ) {
|
|
self.allSavedTemplates( tmpl_collection );
|
|
_dfd_.resolve( tmpl_collection );
|
|
});
|
|
return _dfd_.promise();
|
|
},
|
|
|
|
// @return a promise
|
|
getSavedTmplCollection : function() {
|
|
var self = this;
|
|
self.templateCollectionPromise = $.Deferred();
|
|
|
|
wp.ajax.post( 'sek_get_all_saved_tmpl', {
|
|
nonce: api.settings.nonce.save
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( tmpl_collection ) {
|
|
if ( _.isObject(tmpl_collection) && !_.isArray( tmpl_collection ) ) {
|
|
self.templateCollectionPromise.resolve( tmpl_collection );
|
|
//console.log('GET SAVED TMPL COLLECTION', tmpl_collection );
|
|
} else {
|
|
self.templateCollectionPromise.resolve( {} );
|
|
if ( !_.isEmpty( tmpl_collection ) ) {
|
|
api.errare('control::getSavedTmplCollection => error => tmpl collection is invalid', tmpl_collection);
|
|
}
|
|
}
|
|
})
|
|
.fail( function( er ) {
|
|
api.errorLog( 'ajax sek_get_all_saved_tmpl => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Error when processing templates'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
self.templateCollectionPromise.resolve({});
|
|
});
|
|
|
|
return self.templateCollectionPromise;
|
|
},
|
|
|
|
|
|
|
|
// API TMPL COLLECTION
|
|
// @return a promise
|
|
getApiTmplCollection : function() {
|
|
var self = this,
|
|
dfd = $.Deferred(),
|
|
_collection = {};
|
|
if ( '_not_populated_' !== self.allApiTemplates() ) {
|
|
dfd.resolve( self.allApiTemplates() );
|
|
} else {
|
|
if ( !sektionsLocalizedData.useAPItemplates ) {
|
|
self.allApiTemplates([]);
|
|
dfd.resolve([]);
|
|
} else {
|
|
wp.ajax.post( 'sek_get_all_api_tmpl', {
|
|
nonce: api.settings.nonce.save
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( tmpl_collection ) {
|
|
if ( _.isObject(tmpl_collection) && !_.isArray( tmpl_collection ) ) {
|
|
_collection = tmpl_collection;
|
|
//console.log('AJAX GET API TMPL COLLECTION DONE', tmpl_collection );
|
|
} else {
|
|
api.errare('control::getApiTmplCollection => error => tmpl collection is invalid', tmpl_collection);
|
|
}
|
|
self.allApiTemplates( _collection );
|
|
dfd.resolve( _collection );
|
|
})
|
|
.fail( function( er ) {
|
|
api.errorLog( 'ajax sek_get_all_api_tmpl => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n['Error when processing templates'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
dfd.resolve({});
|
|
});
|
|
}
|
|
}
|
|
return dfd;
|
|
},
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
// introduced in december 2020 for https://github.com/presscustomizr/nimble-builder/issues/655
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
////////////////////////////////////////////////////////
|
|
// INJECT TEMPLATE FROM GALLERY => FROM USER SAVED COLLECTION OR REMOTE API
|
|
////////////////////////////////////////////////////////
|
|
// @return promise
|
|
getTmplJsonFromUserTmpl : function( tmpl_name ) {
|
|
var self = this, _dfd_ = $.Deferred();
|
|
wp.ajax.post( 'sek_get_user_tmpl_json', {
|
|
nonce: api.settings.nonce.save,
|
|
tmpl_post_name: tmpl_name
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( response ) {
|
|
_dfd_.resolve( {success:true, tmpl_json:response });
|
|
})
|
|
.fail( function( er ) {
|
|
_dfd_.resolve( {success:false});
|
|
api.errorLog( 'ajax getTmplJsonFromUserTmpl => error', er );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>Error when fetching the template</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
|
|
return _dfd_;
|
|
},
|
|
|
|
getTmplJsonFromApi : function( tmpl_name, tmpl_is_pro ) {
|
|
var self = this, _dfd_ = $.Deferred();
|
|
wp.ajax.post( 'sek_get_api_tmpl_json', {
|
|
nonce: api.settings.nonce.save,
|
|
api_tmpl_name: tmpl_name,
|
|
api_tmpl_is_pro : tmpl_is_pro ? 'yes' : 'no'
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( response ) {
|
|
_dfd_.resolve( {success:true, tmpl_json:response });
|
|
})
|
|
.fail( function( _r_ ) {
|
|
_dfd_.resolve( {success:false});
|
|
api.errorLog( 'ajax getTmplJsonFromApiTmpl => error', _r_ );
|
|
var _msg = 'Error when fetching the template';
|
|
if ( _.isString( _r_ ) && !_.isEmpty( _r_ ) ) {
|
|
_msg = _r_;
|
|
}
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 60000,
|
|
is_pro_notif : true,
|
|
notif_id : 'pro_tmpl_error',
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>'+ _msg + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
return _dfd_;
|
|
},
|
|
|
|
|
|
|
|
|
|
// April 2020 : added for https://github.com/presscustomizr/nimble-builder/issues/651
|
|
// @param params {
|
|
// tmpl_name : string,
|
|
// tmpl_source : api_tmpl or user_tmpl,
|
|
// tmpl_inject_mode : 3 possible import modes : replace, before, after,
|
|
// tmpl_is_pro: false
|
|
// }
|
|
get_gallery_tmpl_json_and_inject : function( params ) {
|
|
var self = this;
|
|
params = $.extend( {
|
|
tmpl_name : '',
|
|
tmpl_source : 'user',
|
|
tmpl_inject_mode : 'replace',
|
|
tmpl_is_pro: false
|
|
}, params || {});
|
|
|
|
var tmpl_name = params.tmpl_name;
|
|
if ( _.isEmpty( tmpl_name ) || ! _.isString( tmpl_name ) ) {
|
|
api.errare('::tmpl inject => error => invalid template name');
|
|
}
|
|
|
|
var _promise;
|
|
if ( 'api_tmpl' === params.tmpl_source ) {
|
|
// doc : https://api.jquery.com/jQuery.getJSON/
|
|
_promise = self.getTmplJsonFromApi(tmpl_name, params.tmpl_is_pro);
|
|
} else {
|
|
_promise = self.getTmplJsonFromUserTmpl(tmpl_name);
|
|
}
|
|
|
|
// response object structure :
|
|
// {
|
|
// data : { nimble content },
|
|
// metas : {
|
|
// skope_id :
|
|
// version :
|
|
// tmpl_locations :
|
|
// date :
|
|
// theme :
|
|
// }
|
|
// }
|
|
_promise.done( function( response ) {
|
|
if ( response.success ) {
|
|
// api.infoLog('INJECT NIMBLE TEMPLATE', params );
|
|
self.inject_tmpl_from_gallery({
|
|
tmpl_name : tmpl_name,
|
|
template_data : response.tmpl_json,
|
|
tmpl_inject_mode : params.tmpl_inject_mode,
|
|
tmpl_is_pro: params.tmpl_is_pro
|
|
});
|
|
} else {
|
|
// Clean loader if failed response
|
|
api.previewer.send( 'sek-clean-loader', { cleanFullPageLoader : true });
|
|
}
|
|
}).fail( function() {
|
|
api.previewer.send( 'sek-clean-loader', { cleanFullPageLoader : true });
|
|
});
|
|
|
|
// After 30 s display a failure notification
|
|
// april 2020 : introduced for https://github.com/presscustomizr/nimble-builder/issues/663
|
|
_.delay( function() {
|
|
if ( 'pending' !== _promise.state() )
|
|
return;
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-too-long',
|
|
type : 'error',
|
|
duration : 20000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
'Template import failed',
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
}, 30000 );
|
|
|
|
return _promise;
|
|
},
|
|
|
|
// INJECT TEMPLATE FROM GALLERY
|
|
// => REMOTE API COLLECTION + USER COLLECTION
|
|
// @param params
|
|
// {
|
|
// tmpl_name : tmpl_name,
|
|
// template_data : response.tmpl_json,
|
|
// tmpl_inject_mode : 3 possible import modes : replace, before, after
|
|
// tmpl_is_pro : false
|
|
// }
|
|
inject_tmpl_from_gallery : function( params ) {
|
|
var self = this;
|
|
params = params || {};
|
|
// normalize params
|
|
params = $.extend({
|
|
tmpl_inject_mode : 'replace'
|
|
}, params );
|
|
|
|
var _scope = 'local';//<= when injecting a template not manually, scope is always local
|
|
|
|
// remote template inject case
|
|
if ( !params.template_data ) {
|
|
throw new Error( '::inject_tmpl => missing remote template data' );
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
/// 1) CHECK IF CONTENT IS WELL FORMED AND ELIGIBLE FOR API
|
|
/// 2) LET'S PROCESS THE SETTING ID'S
|
|
/// 3) ATTEMPT TO UPDATE THE SETTING API, LOCAL OR GLOBAL. ( always local for template inject )
|
|
if ( !api.czr_sektions.isImportedContentEligibleForAPI( {success:true, data:params.template_data}, params ) ) {
|
|
api.infoLog('::inject_tmpl problem => !api.czr_sektions.isImportedContentEligibleForAPI', params );
|
|
return;
|
|
}
|
|
|
|
params.template_data.data.collection = self.setIdsForImportedTmpl( params.template_data.data.collection );
|
|
// and try to update the api setting
|
|
api.czr_sektions.doUpdateApiSettingAfter_TmplGalleryImport( {success:true, data:params.template_data}, params );
|
|
},//inject_tmpl_from_gallery
|
|
|
|
|
|
// fired on ajaxrequest done
|
|
// At this stage, the server_resp data structure has been validated.
|
|
// We can try to the update the api setting
|
|
// @param params
|
|
// {
|
|
// tmpl_name : tmpl_name,
|
|
// template_data : response.tmpl_json,
|
|
// tmpl_inject_mode : 3 possible import modes : replace, before, after,
|
|
// tmpl_is_pro : false
|
|
// }
|
|
doUpdateApiSettingAfter_TmplGalleryImport : function( server_resp, params ){
|
|
params = params || {};
|
|
if ( !api.czr_sektions.isImportedContentEligibleForAPI( server_resp, params ) ) {
|
|
api.previewer.send( 'sek-clean-loader', { cleanFullPageLoader : true });
|
|
return;
|
|
}
|
|
|
|
var _scope = 'local';// <= always local when template gallery inject
|
|
|
|
//api.infoLog('TODO => verify metas => version, active locations, etc ... ');
|
|
|
|
// Update the setting api via the normalized method
|
|
// the scope will determine the setting id, local or global
|
|
api.czr_sektions.updateAPISetting({
|
|
action : 'sek-inject-tmpl-from-gallery',
|
|
scope : _scope,//'global' or 'local'<= will determine which setting will be updated,
|
|
// => self.getGlobalSectionsSettingId() or self.localSectionsSettingId()
|
|
injected_content : server_resp.data,
|
|
tmpl_inject_mode : params.tmpl_inject_mode
|
|
}).done( function() {
|
|
// Clean an regenerate the local option setting
|
|
// Settings are normally registered once and never cleaned, unlike controls.
|
|
// After the inject, updating the setting value will refresh the sections
|
|
// but the local options, persisted in separate settings, won't be updated if the settings are not cleaned
|
|
if ( 'local' === _scope ) {
|
|
var _doThingsAfterRefresh = function() {
|
|
// Removes and RE-register local settings and controls
|
|
api.czr_sektions.generateUI({
|
|
action : 'sek-generate-local-skope-options-ui',
|
|
clean_settings_and_controls_first : true//<= see api.czr_sektions.generateUIforLocalSkopeOptions()
|
|
});
|
|
api.previewer.unbind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
};
|
|
// 'czr-new-skopes-synced' is always sent on a previewer.refresh()
|
|
api.previewer.bind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
}
|
|
|
|
//_notify( sektionsLocalizedData.i18n['The revision has been successfully restored.'], 'success' );
|
|
api.previewer.refresh();
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-success',
|
|
type : 'success',
|
|
duration : 30000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['Template successfully imported'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
}).fail( function( response ) {
|
|
api.errare( '::doUpdateApiSettingAfter_TmplGalleryImport => error when firing ::updateAPISetting', response );
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-failed',
|
|
type : 'error',
|
|
duration : 30000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
[ sektionsLocalizedData.i18n['Import failed'], response ].join(' : '),
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
|
|
// Refresh the preview, so the markup is refreshed and the css stylesheet are generated
|
|
api.previewer.refresh();
|
|
}//doUpdateApiSettingAfter_TmplGalleryImport()
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// the input id determine if we fetch the revision history of the local or global setting
|
|
// @return a deferred promise
|
|
// @params object : { is_local:bool} <= 'local_revisions' === input.id
|
|
getRevisionHistory : function(params) {
|
|
return wp.ajax.post( 'sek_get_revision_history', {
|
|
nonce: api.settings.nonce.save,
|
|
skope_id : params.is_local ? api.czr_skopeBase.getSkopeProperty( 'skope_id' ) : sektionsLocalizedData.globalSkopeId
|
|
});
|
|
},
|
|
|
|
// @return void()
|
|
// Fetches the_content and try to set the setting value through normalized ::updateAPISetting method
|
|
// @params {
|
|
// is_local : bool//<= 'local_revisions' === input.id
|
|
// revision_post_id : int
|
|
// }
|
|
setSingleRevision : function(params) {
|
|
var self = this;
|
|
var _notify = function( message, type ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'restore-revision-error',
|
|
type : type || 'info',
|
|
duration : 10000,
|
|
message : [
|
|
'<span style="">',
|
|
'<strong>',
|
|
message || '',
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
};
|
|
wp.ajax.post( 'sek_get_single_revision', {
|
|
nonce: api.settings.nonce.save,
|
|
//skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),
|
|
revision_post_id : params.revision_post_id
|
|
}).done( function( revision_value ){
|
|
// If the setting value is unchanged, no need to go further
|
|
// is_local is decided with the input id => @see revision_history input type.
|
|
var setId = params.is_local ? self.localSectionsSettingId() : self.getGlobalSectionsSettingId();
|
|
if ( _.isEqual( api( setId )(), revision_value ) ) {
|
|
_notify( sektionsLocalizedData.i18n['This is the current version.'], 'info' );
|
|
return;
|
|
}
|
|
// api.infoLog( 'getSingleRevision response', revision_value );
|
|
// api.infoLog( 'Current val', api(self.localSectionsSettingId())() );
|
|
self.updateAPISetting({
|
|
action : 'sek-restore-revision',
|
|
is_global_location : !params.is_local,//<= will determine which setting will be updated,
|
|
// => self.getGlobalSectionsSettingId() or self.localSectionsSettingId()
|
|
revision_value : revision_value
|
|
}).done( function() {
|
|
//_notify( sektionsLocalizedData.i18n['The revision has been successfully restored.'], 'success' );
|
|
api.previewer.refresh();
|
|
}).fail( function( response ) {
|
|
api.errare( '::setSingleRevision error when firing ::updateAPISetting', response );
|
|
_notify( sektionsLocalizedData.i18n['The revision could not be restored.'], 'error' );
|
|
});
|
|
//api.previewer.refresh();
|
|
}).fail( function( response ) {
|
|
api.errare( '::setSingleRevision ajax error', response );
|
|
_notify( sektionsLocalizedData.i18n['The revision could not be restored.'], 'error' );
|
|
});
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );
|
|
//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// Fired on api 'ready', in reaction to ::setContextualCollectionSettingIdWhenSkopeSet => ::localSectionsSettingId
|
|
// 1) register the collection setting nimble___[{$skope_id}] ( ex : nimble___[skp__post_page_20] )
|
|
// 2) validate that the setting is well formed before being changed
|
|
// 3) schedule reactions on change ?
|
|
// @return void()
|
|
setupSettingsToBeSaved : function( params ) {
|
|
var self = this,
|
|
serverCollection;
|
|
|
|
params = params || { dirty : false, is_group_inheritance_enabled : true };
|
|
|
|
// maybe register the sektion_collection settings
|
|
var _settingsToRegister_ = {
|
|
'local' : { collectionSettingId : self.localSectionsSettingId() },//<= "nimble___[skp__post_page_10]"
|
|
'global' : { collectionSettingId : self.getGlobalSectionsSettingId() }//<= "nimble___[skp__global]"
|
|
};
|
|
|
|
_.each( _settingsToRegister_, function( settingData, localOrGlobal ) {
|
|
serverCollection = api.czr_skopeBase.getSkopeProperty( 'sektions', localOrGlobal ).db_values;
|
|
if ( _.isEmpty( settingData.collectionSettingId ) ) {
|
|
throw new Error( 'setupSettingsToBeSaved => the collectionSettingId is invalid' );
|
|
}
|
|
// if the collection setting is not registered yet
|
|
// => register it and bind it
|
|
// => ensure that it will be bound only once, because the setting are never unregistered
|
|
if ( ! api.has( settingData.collectionSettingId ) ) {
|
|
var inital_setting_value = _.isObject( serverCollection ) ? serverCollection : self.getDefaultSektionSettingValue( localOrGlobal );
|
|
if ( 'local' == localOrGlobal && !params.is_group_inheritance_enabled ) {
|
|
inital_setting_value.local_options.local_reset = _.isObject( inital_setting_value.local_options.local_reset ) ? inital_setting_value.local_options.local_reset : {};
|
|
inital_setting_value.local_options.local_reset.inherit_group_scope = false;
|
|
}
|
|
|
|
var __collectionSettingInstance__ = api.CZR_Helpers.register({
|
|
what : 'setting',
|
|
id : settingData.collectionSettingId,
|
|
value : self.validateSettingValue( inital_setting_value, localOrGlobal ),
|
|
transport : 'postMessage',//'refresh'
|
|
type : 'option',
|
|
track : false,//don't register in the self.registered()
|
|
origin : 'nimble',
|
|
dirty : params.dirty
|
|
});
|
|
|
|
//if ( sektionsLocalizedData.isDevMode ) {}
|
|
api( settingData.collectionSettingId, function( sektionSetInstance ) {
|
|
|
|
// Schedule reactions to a collection change
|
|
sektionSetInstance.bind( _.debounce( function( newSektionSettingValue, previousValue, params ) {
|
|
// api.infoLog( 'sektionSettingValue is updated',
|
|
// {
|
|
// newValue : newSektionSettingValue,
|
|
// previousValue : previousValue,
|
|
// params : params
|
|
// }
|
|
// );
|
|
|
|
// console.log('MAIN SETTING CHANGED', params );
|
|
//console.log('NEW MAIN SETTING VALUE', newSektionSettingValue );
|
|
|
|
|
|
// Track changes, if not already navigating the logs
|
|
if ( !_.isObject( params ) || true !== params.navigatingHistoryLogs ) {
|
|
try { self.trackHistoryLog( sektionSetInstance, params ); } catch(er) {
|
|
api.errare( 'setupSettingsToBeSaved => trackHistoryLog', er );
|
|
}
|
|
}
|
|
|
|
// April 2021 : for site templates
|
|
if ( 'local' === localOrGlobal ) {
|
|
api.trigger('nimble-update-topbar-skope-status');
|
|
}
|
|
|
|
}, 1000 ) );
|
|
});//api( settingData.collectionSettingId, function( sektionSetInstance ){}
|
|
}//if ( ! api.has( settingData.collectionSettingId ) ) {
|
|
});//_.each(
|
|
|
|
// global options for all collection setting of this skope_id
|
|
// loop_start, before_content, after_content, loop_end
|
|
|
|
// Global Options : section
|
|
// api.CZR_Helpers.register({
|
|
// what : 'section',
|
|
// id : sektionsLocalizedData.optPrefixForSektionGlobalOptsSetting,//'__sektions__'
|
|
// title: 'Global Options',
|
|
// priority : 1000,
|
|
// constructWith : SektionPanelConstructor,
|
|
// track : false//don't register in the self.registered()
|
|
// });
|
|
|
|
// // => register a control
|
|
// // Template
|
|
// api.CZR_Helpers.register({
|
|
// what : 'control',
|
|
// id : sektionsLocalizedData.sektionsPanelId,//'__sektions__'
|
|
// title: 'Main sektions panel',
|
|
// priority : 1000,
|
|
// constructWith : SektionPanelConstructor,
|
|
// track : false//don't register in the self.registered()
|
|
// });
|
|
},// SetupSettingsToBeSaved()
|
|
|
|
// Fired :
|
|
// 1) when instantiating the setting
|
|
// 2) on each setting change, as an override of api.Value::validate( to ) @see customize-base.js
|
|
// 3) directly when navigating the history log
|
|
// 4) when importing locally or globally
|
|
// @return {} or null if did not pass the checks
|
|
// @param scope = string, local or global
|
|
validateSettingValue : function( valCandidate, scope ) {
|
|
if ( ! _.isObject( valCandidate ) ) {
|
|
api.errare('::validateSettingValue => validation error => the setting should be an object', valCandidate );
|
|
return null;
|
|
}
|
|
if ( _.isEmpty( scope ) || !_.contains(['local', 'global'], scope ) ) {
|
|
api.errare( '::validateSettingValue => invalid scope provided.', scope );
|
|
return;
|
|
}
|
|
var parentLevel = {},
|
|
errorDetected = false,
|
|
levelIds = [],
|
|
authorized_local_option_groups = ['collection', 'local_options', 'fonts', '__inherits_group_skope_tmpl_when_exists__' ];
|
|
|
|
// walk the collections tree and verify it passes the various consistency checks
|
|
var _errorDetected_ = function( msg ) {
|
|
api.errare( msg , valCandidate );
|
|
if ( sektionsLocalizedData.isDevMode ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 60000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + msg + '</strong>',
|
|
'<br>',
|
|
sektionsLocalizedData.i18n['If this problem locks Nimble Builder, you can try resetting the sections of this page.'],
|
|
'<br>',
|
|
'<span style="text-align:center;display:block">',
|
|
'<button type="button" class="button" aria-label="' + sektionsLocalizedData.i18n.Reset + '" data-sek-reset="true">' + sektionsLocalizedData.i18n.Reset + '</button>',
|
|
'</span>',
|
|
'</span>'
|
|
].join('')
|
|
|
|
});
|
|
}
|
|
errorDetected = true;
|
|
};
|
|
var _checkWalker_ = function( level ) {
|
|
if ( errorDetected ) {
|
|
return;
|
|
}
|
|
if ( _.isUndefined( level ) && _.isEmpty( parentLevel ) ) {
|
|
// we are at the root level
|
|
level = $.extend( true, {}, valCandidate );
|
|
if ( _.isUndefined( level.id ) || _.isUndefined( level.level ) ) {
|
|
// - there should be no 'level' property or 'id'
|
|
// - there should be a collection of registered locations
|
|
// - there should be no parent level defined
|
|
if ( _.isUndefined( level.collection ) ) {
|
|
_errorDetected_( 'validation error => the root level is missing the collection of locations' );
|
|
return;
|
|
}
|
|
if ( ! _.isEmpty( level.level ) || ! _.isEmpty( level.id ) ) {
|
|
_errorDetected_( 'validation error => the root level should not have a "level" or an "id" property' );
|
|
return;
|
|
}
|
|
|
|
// the local setting is structured this way:
|
|
// {
|
|
// collection : [],
|
|
// local_options : {},
|
|
// fonts : []
|
|
// }
|
|
//
|
|
// global_options like sitewide header and footer are saved in a specific option => NIMBLE_OPT_NAME_FOR_GLOBAL_OPTIONS
|
|
// the global setting is structured this way:
|
|
// {
|
|
// collection : [],
|
|
// fonts : []
|
|
// }
|
|
// Make sure that there's no unauthorized option group at root level
|
|
_.each( level, function( _opts, _opt_group_name) {
|
|
switch( scope ) {
|
|
case 'local' :
|
|
if( !_.contains( authorized_local_option_groups , _opt_group_name ) ) {
|
|
_errorDetected_( 'validation error => unauthorized option group for local setting value => ' + _opt_group_name );
|
|
return;
|
|
}
|
|
break;
|
|
case 'global' :
|
|
if( !_.contains( ['collection', 'fonts' ] , _opt_group_name ) ) {
|
|
_errorDetected_( 'validation error => unauthorized option group for global setting value => ' + _opt_group_name );
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
|
|
// Walk the section collection
|
|
_.each( valCandidate.collection, function( _l_ ) {
|
|
// Set the parent level now
|
|
parentLevel = level;
|
|
// walk
|
|
_checkWalker_( _l_ );
|
|
});
|
|
}
|
|
} else {
|
|
// we have a level.
|
|
// - make sure we have at least the following properties : id, level
|
|
// LEVEL should be an object
|
|
if ( _.isUndefined( level ) || !_.isObject( level ) ) {
|
|
_errorDetected_('validation error => a level is invalid' );
|
|
return;
|
|
}
|
|
|
|
// ID
|
|
if ( _.isEmpty( level.id ) || ! _.isString( level.id )) {
|
|
_errorDetected_('validation error => a ' + level.level + ' level must have a valid id' );
|
|
return;
|
|
} else if ( _.contains( levelIds, level.id ) ) {
|
|
_errorDetected_('validation error => duplicated level id : ' + level.id );
|
|
return;
|
|
} else {
|
|
levelIds.push( level.id );
|
|
}
|
|
|
|
// OPTIONS
|
|
// if ( _.isEmpty( level.options ) || ! _.isObject( level.options )) {
|
|
// _errorDetected_('validation error => a ' + level.level + ' level must have a valid options property' );
|
|
// return;
|
|
// }
|
|
|
|
// LEVEL
|
|
if ( _.isEmpty( level.level ) || ! _.isString( level.level ) ) {
|
|
_errorDetected_('validation error => a ' + level.level + ' level must have a level property' );
|
|
return;
|
|
} else if ( ! _.contains( [ 'location', 'section', 'column', 'module' ], level.level ) ) {
|
|
_errorDetected_('validation error => the level "' + level.level + '" is not authorized' );
|
|
return;
|
|
}
|
|
|
|
// - Unless we are in a module, there should be a collection property
|
|
// - make sure a module doesn't have a collection property
|
|
if ( 'module' == level.level ) {
|
|
if ( ! _.isUndefined( level.collection ) ) {
|
|
_errorDetected_('validation error => a module can not have a collection property' );
|
|
return;
|
|
}
|
|
} else {
|
|
if ( _.isUndefined( level.collection ) ) {
|
|
_errorDetected_( 'validation error => missing collection property for level => ' + level.level + ' ' + level.id );
|
|
return;
|
|
}
|
|
}
|
|
|
|
// a level should always have a version "ver_ini" property
|
|
if ( _.isUndefined( level.ver_ini ) ) {
|
|
//_errorDetected_('validation error => a ' + level.level + ' should have a version property : "ver_ini"' );
|
|
//return;
|
|
api.errare( 'validateSettingValue() => validation error => a ' + level.level + ' should have a version property : "ver_ini"' );
|
|
}
|
|
|
|
// Specific checks by level type
|
|
switch ( level.level ) {
|
|
case 'location' :
|
|
if ( ! _.isEmpty( parentLevel.level ) ) {
|
|
_errorDetected_('validation error => the parent of location ' + level.id +' should have no level set' );
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'section' :
|
|
if ( level.is_nested && 'column' != parentLevel.level ) {
|
|
_errorDetected_('validation error => the nested section ' + level.id +' must be child of a column' );
|
|
return;
|
|
}
|
|
if ( ! level.is_nested && 'location' != parentLevel.level ) {
|
|
_errorDetected_('validation error => the section ' + level.id +' must be child of a location' );
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'column' :
|
|
if ( 'section' != parentLevel.level ) {
|
|
_errorDetected_('validation error => the column ' + level.id +' must be child of a section' );
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 'module' :
|
|
if ( 'column' != parentLevel.level ) {
|
|
_errorDetected_('validation error => the module ' + level.id +' must be child of a column' );
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// If we are not in a module, keep walking the collections
|
|
if ( 'module' != level.level ) {
|
|
_.each( level.collection, function( _l_ ) {
|
|
// Set the parent level now
|
|
parentLevel = $.extend( true, {}, level );
|
|
if ( ! _.isUndefined( _l_ ) ) {
|
|
// And walk sub levels
|
|
_checkWalker_( _l_ );
|
|
} else {
|
|
_errorDetected_('validation error => undefined level ' );
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
_checkWalker_();
|
|
|
|
if ( errorDetected ) {
|
|
api.infoLog('error in ::validateSettingValue', valCandidate );
|
|
return null;
|
|
}
|
|
//api.infoLog('in ::validateSettingValue', valCandidate );
|
|
// if null is returned, the setting value is not set @see customize-base.js
|
|
return valCandidate;
|
|
},//validateSettingValue
|
|
|
|
|
|
|
|
// triggered when clicking on [data-sek-reset="true"]
|
|
// click event is scheduled in ::initialize()
|
|
// Note : only the collection is set to self.getDefaultSektionSettingValue( 'local' )
|
|
// @see php function which defines the defaults sek_get_default_location_model()
|
|
resetCollectionSetting : function( scope, localOptions ) {
|
|
var self = this, newSettingValue;
|
|
if ( _.isEmpty( scope ) || !_.contains(['local', 'global'], scope ) ) {
|
|
throw new Error( 'resetCollectionSetting => invalid scope provided.', scope );
|
|
}
|
|
// INHERITANCE
|
|
// solves the problem of preventing group template inheritance after a local reset
|
|
var _is_inheritance_enabled_in_local_options = true;
|
|
if ( 'local' === scope ) {
|
|
if ( localOptions && _.isObject(localOptions) && localOptions.local_reset && !_.isUndefined( localOptions.local_reset.inherit_group_scope ) ) {
|
|
_is_inheritance_enabled_in_local_options = localOptions.local_reset.inherit_group_scope;
|
|
}
|
|
}
|
|
|
|
newSettingValue = $.extend( true, {}, self.getDefaultSektionSettingValue( scope ) );
|
|
|
|
if ( !_is_inheritance_enabled_in_local_options ) {
|
|
newSettingValue.local_options.local_reset = { inherit_group_scope : false };
|
|
}
|
|
// April 2021, for site templates #478 => the default local sektion model includes property __inherits_group_skope_tmpl_when_exists__, set to true
|
|
// => when reseting locally, if a group template is defined, it will be inherited
|
|
|
|
// How does it work?
|
|
// When a page has not been locally customized, property __inherits_group_skope_tmpl_when_exists__ is true ( @see sek_get_default_location_model() )
|
|
// As soon as the main local setting id is modified, __inherits_group_skope_tmpl_when_exists__ is set to false ( see js control::updateAPISetting )
|
|
// After a reset case, NB sets __inherits_group_skope_tmpl_when_exists__ back to true here
|
|
// Note : If this property is set to true => NB removes the local skope post in Nimble_Collection_Setting::update()
|
|
return newSettingValue;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// invoked on api('ready') from self::initialize()
|
|
// update the main setting OR generate a UI in the panel
|
|
// AND
|
|
// always send back a confirmation to the preview, so we can fire the ajax actions
|
|
// the message sent back is used in particular to
|
|
// - always pass the location_skope_id, which otherwise would be impossible to get in ajax
|
|
// - in a duplication case, to pass the the newly generated id of the cloned level
|
|
reactToPreviewMsg : function() {
|
|
var self = this,
|
|
apiParams = {},
|
|
uiParams = {},
|
|
sendToPreview = true, //<= the default behaviour is to send a message to the preview when the setting has been changed
|
|
msgCollection = {
|
|
// A section can be added in various scenarios :
|
|
// - when clicking on the ( + ) Insert content => @see preview::scheduleUiClickReactions() => addContentButton
|
|
// - when adding a nested section to a column
|
|
// - when dragging a module in a 'between-sections' or 'in-empty-location' drop zone
|
|
//
|
|
// Note : if the target location level already has section(s), then the section is appended in ajax, at the right place
|
|
// Note : if the target location is empty ( is_first_section is true ), nothing is send to the preview when updating the api setting, and we refresh the location level. => this makes sure that we removes the placeholder printed in the previously empty location
|
|
'sek-add-section' : {
|
|
callback : function( params ) {
|
|
// July 2020 => for #728
|
|
api.previewedDevice( 'desktop' );
|
|
|
|
sendToPreview = ! _.isUndefined( params.send_to_preview ) ? params.send_to_preview : true;//<= when the level is refreshed when complete, we don't need to send to preview.
|
|
uiParams = {};
|
|
apiParams = {
|
|
action : 'sek-add-section',
|
|
id : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid(),
|
|
location : params.location,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column,
|
|
is_nested : ! _.isEmpty( params.in_sektion ) && ! _.isEmpty( params.in_column ),
|
|
before_section : params.before_section,
|
|
after_section : params.after_section,
|
|
is_first_section : params.is_first_section
|
|
};
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
// When a section is created ( not duplicated )
|
|
if ( params.apiParams.is_first_section ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.location
|
|
});
|
|
}
|
|
api.previewer.trigger( 'sek-pick-content', {
|
|
// the "id" param is added to set the target for double click insertion
|
|
// implemented for https://github.com/presscustomizr/nimble-builder/issues/317
|
|
id : params.apiParams ? params.apiParams.id : '',
|
|
content_type : 'section'
|
|
});
|
|
api.previewer.send('sek-animate-to-level', { id : params.apiParams.id });
|
|
}
|
|
},
|
|
|
|
|
|
'sek-add-column' : {
|
|
callback : function( params ) {
|
|
sendToPreview = true;
|
|
uiParams = {};
|
|
apiParams = {
|
|
id : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid(),
|
|
action : 'sek-add-column',
|
|
in_sektion : params.in_sektion,
|
|
autofocus : params.autofocus
|
|
};
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
// When adding a section, a nested column is automatically added
|
|
// We want to focus on the module picker in this case, that's why the autofocus is set to false
|
|
// @see 'sek-add-section' action description
|
|
if ( false !== params.apiParams.autofocus ) {
|
|
api.previewer.trigger( 'sek-pick-content', {});
|
|
}
|
|
}
|
|
},
|
|
'sek-add-module' : {
|
|
callback :function( params ) {
|
|
sendToPreview = true;
|
|
uiParams = {};
|
|
apiParams = {
|
|
id : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid(),
|
|
action : 'sek-add-module',
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column,
|
|
module_type : params.content_id,
|
|
|
|
before_module_or_nested_section : params.before_module_or_nested_section,
|
|
after_module_or_nested_section : params.after_module_or_nested_section
|
|
};
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
api.previewer.trigger( 'sek-edit-module', {
|
|
id : params.apiParams.id,
|
|
level : 'module',
|
|
in_sektion : params.apiParams.in_sektion,
|
|
in_column : params.apiParams.in_column
|
|
});
|
|
// always update the root fonts property after a module addition
|
|
// because there might be a google font specified in the starting value
|
|
self.updateAPISetting({
|
|
action : 'sek-update-fonts',
|
|
is_global_location : self.isGlobalLocation( params.apiParams )
|
|
});
|
|
|
|
// Refresh the stylesheet to generate the css rules of the clone
|
|
// api.previewer.send( 'sek-refresh-stylesheet', {
|
|
// location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
// });
|
|
api.previewer.trigger('sek-refresh-stylesheet', {
|
|
id : params.apiParams.in_column,
|
|
location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' )//<= send skope id to the preview so we can use it when ajaxing
|
|
});
|
|
}
|
|
},
|
|
'sek-remove' : {
|
|
callback : function( params ) {
|
|
sendToPreview = true;
|
|
uiParams = {};
|
|
switch( params.level ) {
|
|
case 'section' :
|
|
var sektionToRemove = self.getLevelModel( params.id );
|
|
if ( 'no_match' === sektionToRemove ) {
|
|
api.errare( 'reactToPreviewMsg => sek-remove-section => no sektionToRemove matched' );
|
|
break;
|
|
}
|
|
apiParams = {
|
|
action : 'sek-remove-section',
|
|
id : params.id,
|
|
location : params.location,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column,
|
|
is_nested : sektionToRemove.is_nested
|
|
};
|
|
break;
|
|
case 'column' :
|
|
apiParams = {
|
|
action : 'sek-remove-column',
|
|
id : params.id,
|
|
in_sektion : params.in_sektion
|
|
};
|
|
break;
|
|
case 'module' :
|
|
apiParams = {
|
|
action : 'sek-remove-module',
|
|
id : params.id,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column
|
|
};
|
|
break;
|
|
default :
|
|
api.errare( '::reactToPreviewMsg => sek-remove => missing level ', params );
|
|
break;
|
|
}
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
api.previewer.trigger( 'sek-pick-content', {});
|
|
// always update the root fonts property after a removal
|
|
// because the removed level(s) might had registered fonts
|
|
self.updateAPISetting({
|
|
action : 'sek-update-fonts',
|
|
is_global_location : self.isGlobalLocation( params.apiParams )
|
|
});
|
|
|
|
// When the last section of a location gets removed, make sure we refresh the location level, to print the sek-empty-location-placeholder
|
|
if ( 'sek-remove-section' === params.apiParams.action ) {
|
|
var locationLevel = self.getLevelModel( params.apiParams.location );
|
|
if ( _.isEmpty( locationLevel.collection ) ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.location
|
|
});
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
'sek-move' : {
|
|
callback : function( params ) {
|
|
sendToPreview = true;
|
|
uiParams = {};
|
|
switch( params.level ) {
|
|
case 'section' :
|
|
apiParams = {
|
|
action : 'sek-move-section',
|
|
id : params.id,
|
|
is_nested : ! _.isEmpty( params.in_sektion ) && ! _.isEmpty( params.in_column ),
|
|
newOrder : params.newOrder,
|
|
from_location : params.from_location,
|
|
to_location : params.to_location
|
|
};
|
|
break;
|
|
case 'column' :
|
|
apiParams = {
|
|
action : 'sek-move-column',
|
|
id : params.id,
|
|
newOrder : params.newOrder,
|
|
from_sektion : params.from_sektion,
|
|
to_sektion : params.to_sektion,
|
|
};
|
|
break;
|
|
case 'module' :
|
|
apiParams = {
|
|
action : 'sek-move-module',
|
|
id : params.id,
|
|
newOrder : params.newOrder,
|
|
from_column : params.from_column,
|
|
to_column : params.to_column,
|
|
from_sektion : params.from_sektion,
|
|
to_sektion : params.to_sektion,
|
|
};
|
|
break;
|
|
}
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
switch( params.apiParams.action ) {
|
|
case 'sek-move-section' :
|
|
api.previewer.trigger('sek-edit-options', {
|
|
id : params.apiParams.id,
|
|
level : 'section',
|
|
in_sektion : params.apiParams.id
|
|
});
|
|
// refresh location levels if the source and target location are differents
|
|
if ( params.apiParams.from_location != params.apiParams.to_location ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.to_location
|
|
});
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.from_location
|
|
});
|
|
}
|
|
break;
|
|
case 'sek-move-column' :
|
|
api.previewer.trigger('sek-edit-options', {
|
|
id : params.apiParams.id,
|
|
level : 'column',
|
|
in_sektion : params.apiParams.in_sektion,
|
|
in_column : params.apiParams.in_column
|
|
});
|
|
break;
|
|
case 'sek-refresh-modules-in-column' :
|
|
api.previewer.trigger('sek-edit-module', {
|
|
id : params.apiParams.id,
|
|
level : 'module',
|
|
in_sektion : params.apiParams.in_sektion,
|
|
in_column : params.apiParams.in_column
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
},//sek-move
|
|
|
|
|
|
'sek-move-section-up' : {
|
|
callback : function( params ) {
|
|
sendToPreview = false;
|
|
uiParams = {};
|
|
apiParams = {
|
|
action : 'sek-move-section-up-down',
|
|
direction : 'up',
|
|
id : params.id,
|
|
is_nested : ! _.isEmpty( params.in_sektion ) && ! _.isEmpty( params.in_column ),
|
|
location : params.location,
|
|
in_column : params.in_column//<= will be used when moving a nested section
|
|
};
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.location,
|
|
|
|
// added for https://github.com/presscustomizr/nimble-builder/issues/471
|
|
original_action : 'sek-move-section-up',
|
|
moved_level_id : params.apiParams.id
|
|
});
|
|
|
|
// Introduced for https://github.com/presscustomizr/nimble-builder/issues/521
|
|
if ( params.apiParams.new_location ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.new_location,
|
|
|
|
// added for https://github.com/presscustomizr/nimble-builder/issues/471
|
|
original_action : 'sek-move-section-down',
|
|
moved_level_id : params.apiParams.id
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
'sek-move-section-down' : {
|
|
callback : function( params ) {
|
|
sendToPreview = false;
|
|
uiParams = {};
|
|
apiParams = {
|
|
action : 'sek-move-section-up-down',
|
|
direction : 'down',
|
|
id : params.id,
|
|
is_nested : ! _.isEmpty( params.in_sektion ) && ! _.isEmpty( params.in_column ),
|
|
location : params.location,
|
|
in_column : params.in_column//<= will be used when moving a nested section
|
|
};
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.location,
|
|
|
|
// added for https://github.com/presscustomizr/nimble-builder/issues/471
|
|
original_action : 'sek-move-section-down',
|
|
moved_level_id : params.apiParams.id
|
|
});
|
|
|
|
// Introduced for https://github.com/presscustomizr/nimble-builder/issues/521
|
|
if ( params.apiParams.new_location ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.new_location,
|
|
|
|
// added for https://github.com/presscustomizr/nimble-builder/issues/471
|
|
original_action : 'sek-move-section-down',
|
|
moved_level_id : params.apiParams.id
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// the level will be cloned and walked to replace all ids by new one
|
|
// then the level clone id will be send back to the preview for the ajax rendering ( this is done in updateAPISetting() promise() )
|
|
'sek-duplicate' : {
|
|
callback : function( params ) {
|
|
sendToPreview = true;
|
|
uiParams = {};
|
|
switch( params.level ) {
|
|
case 'section' :
|
|
apiParams = {
|
|
action : 'sek-duplicate-section',
|
|
id : params.id,
|
|
location : params.location,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column,
|
|
is_nested : ! _.isEmpty( params.in_sektion ) && ! _.isEmpty( params.in_column )
|
|
};
|
|
break;
|
|
case 'column' :
|
|
apiParams = {
|
|
action : 'sek-duplicate-column',
|
|
id : params.id,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column
|
|
};
|
|
break;
|
|
case 'module' :
|
|
apiParams = {
|
|
action : 'sek-duplicate-module',
|
|
id : params.id,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column
|
|
};
|
|
break;
|
|
}
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
var idForStyleSheetRefresh;
|
|
switch( params.apiParams.action ) {
|
|
case 'sek-duplicate-section' :
|
|
api.previewer.trigger('sek-edit-options', {
|
|
id : params.apiParams.id,
|
|
level : 'section',
|
|
in_sektion : params.apiParams.id
|
|
});
|
|
idForStyleSheetRefresh = params.apiParams.location;
|
|
|
|
//introduced for https://github.com/presscustomizr/nimble-builder/issues/617
|
|
if ( params.apiParams.is_nested ) {
|
|
api.previewer.refresh();
|
|
}
|
|
|
|
// Focus on the cloned level
|
|
api.previewer.send('sek-animate-to-level', { id : params.apiParams.id });
|
|
break;
|
|
case 'sek-duplicate-column' :
|
|
api.previewer.trigger('sek-edit-options', {
|
|
id : params.apiParams.id,
|
|
level : 'column',
|
|
in_sektion : params.apiParams.in_sektion,
|
|
in_column : params.apiParams.in_column
|
|
});
|
|
idForStyleSheetRefresh = params.apiParams.in_sektion;
|
|
break;
|
|
case 'sek-duplicate-module' :
|
|
api.previewer.trigger('sek-edit-module', {
|
|
id : params.apiParams.id,
|
|
level : 'module',
|
|
in_sektion : params.apiParams.in_sektion,
|
|
in_column : params.apiParams.in_column
|
|
});
|
|
idForStyleSheetRefresh = params.apiParams.in_column;
|
|
break;
|
|
}
|
|
// Refresh the stylesheet to generate the css rules of the clone
|
|
// api.previewer.send( 'sek-refresh-stylesheet', {
|
|
// location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
// });
|
|
api.previewer.trigger('sek-refresh-stylesheet', {
|
|
id : idForStyleSheetRefresh,
|
|
location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' )//<= send skope id to the preview so we can use it when ajaxing
|
|
});
|
|
|
|
}
|
|
},
|
|
'sek-resize-columns' : function( params ) {
|
|
sendToPreview = true;
|
|
uiParams = {};
|
|
apiParams = params;
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @params {
|
|
// drop_target_element : $(this),
|
|
// position : _position,
|
|
// before_section : $(this).data('sek-before-section'),
|
|
// after_section : $(this).data('sek-after-section'),
|
|
// content_type : event.originalEvent.dataTransfer.getData( "sek-content-type" ),
|
|
// content_id : event.originalEvent.dataTransfer.getData( "sek-content-id" )
|
|
// }
|
|
'sek-add-content-in-new-sektion' : {
|
|
callback : function( params ) {
|
|
sendToPreview = ! _.isUndefined( params.send_to_preview ) ? params.send_to_preview : true;//<= when the level is refreshed when complete, we don't need to send to preview.
|
|
uiParams = {};
|
|
apiParams = params;
|
|
apiParams.action = 'sek-add-content-in-new-sektion';
|
|
apiParams.id = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();//we set the id here because it will be needed when ajaxing
|
|
switch( params.content_type) {
|
|
// When a module is dropped in a section + column structure to be generated
|
|
case 'module' :
|
|
apiParams.droppedModuleId = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();//we set the id here because it will be needed when ajaxing
|
|
break;
|
|
|
|
// When a preset section is dropped
|
|
case 'preset_section' :
|
|
api.previewer.send( 'sek-maybe-print-loader', { loader_located_in_level_id : params.location });
|
|
api.previewer.send( 'sek-maybe-print-loader', { fullPageLoader : true });
|
|
break;
|
|
}
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
switch( params.apiParams.content_type) {
|
|
case 'module' :
|
|
api.previewer.trigger('sek-edit-module', {
|
|
level : 'module',
|
|
id : params.apiParams.droppedModuleId
|
|
});
|
|
break;
|
|
// Clean the full page loader if not autocleaned yet
|
|
case 'preset_section' :
|
|
api.previewer.send( 'sek-clean-loader', { cleanFullPageLoader : true });
|
|
break;
|
|
}
|
|
|
|
// Always update the root fonts property after a module addition
|
|
// => because there might be a google font specified in the starting value or in a preset section
|
|
self.updateAPISetting({
|
|
action : 'sek-update-fonts',
|
|
is_global_location : self.isGlobalLocation( params.apiParams )
|
|
});
|
|
|
|
// Refresh the stylesheet to generate the css rules of the clone
|
|
// api.previewer.send( 'sek-refresh-stylesheet', {
|
|
// location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
// });
|
|
|
|
// Use the location_skope_id provided if set, otherwise generate it
|
|
var location_skope_id = params.location_skope_id;
|
|
if ( _.isUndefined( location_skope_id ) ) {
|
|
location_skope_id = true === params.is_global_location ? sektionsLocalizedData.globalSkopeId : api.czr_skopeBase.getSkopeProperty( 'skope_id' );
|
|
}
|
|
api.previewer.trigger('sek-refresh-stylesheet', {
|
|
//id : params.apiParams.location,
|
|
location_skope_id : location_skope_id,//<= send skope id to the preview so we can use it when ajaxing
|
|
is_global_location : self.isGlobalLocation( params.apiParams )
|
|
});
|
|
|
|
// Refresh when a section is created ( not duplicated )
|
|
if ( params.apiParams.is_first_section ) {
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'location',
|
|
id : params.apiParams.location
|
|
});
|
|
}
|
|
|
|
// Remove the sektion_to_replace when dropping a preset_section in an empty section ( <= the one to replace )
|
|
if ( params.apiParams.sektion_to_replace ) {
|
|
api.previewer.trigger( 'sek-remove', {
|
|
id : params.apiParams.sektion_to_replace,
|
|
location : params.apiParams.location,
|
|
in_column : params.apiParams.in_column,//needed when removing a nested column
|
|
level : 'section'
|
|
});
|
|
}
|
|
|
|
// Refresh the stylesheet again after a delay
|
|
// For the moment, some styling, like fonts are not
|
|
// @todo fix => see why we need to do it.
|
|
// _.delay( function() {
|
|
// // Refresh the stylesheet to generate the css rules of the module
|
|
// api.previewer.send( 'sek-refresh-stylesheet', {
|
|
// location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
// });
|
|
// }, 1000 );
|
|
}
|
|
},//'sek-add-content-in-new-sektion'
|
|
|
|
|
|
// @params {
|
|
// drop_target_element : $(this),
|
|
// position : _position,
|
|
// before_section : $(this).data('sek-before-section'),
|
|
// after_section : $(this).data('sek-after-section'),
|
|
// content_type : event.originalEvent.dataTransfer.getData( "sek-content-type" ),
|
|
// content_id : event.originalEvent.dataTransfer.getData( "sek-content-id" )
|
|
// }
|
|
'sek-add-preset-section-in-new-nested-sektion' : {
|
|
callback : function( params ) {
|
|
sendToPreview = false;//<= when the level is refreshed when complete, we don't need to send to preview.
|
|
uiParams = {};
|
|
apiParams = params;
|
|
apiParams.action = 'sek-add-preset-section-in-new-nested-sektion';
|
|
api.previewer.send( 'sek-maybe-print-loader', { loader_located_in_level_id : params.location });
|
|
return self.updateAPISetting( apiParams );
|
|
},
|
|
complete : function( params ) {
|
|
// Always update the root fonts property after a module addition
|
|
// => because there might be a google font specified in the starting value or in a preset section
|
|
self.updateAPISetting({
|
|
action : 'sek-update-fonts',
|
|
is_global_location : self.isGlobalLocation( params.apiParams )
|
|
});
|
|
// Refresh the stylesheet to generate the css rules of the clone
|
|
// api.previewer.send( 'sek-refresh-stylesheet', {
|
|
// location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
// });
|
|
api.previewer.trigger('sek-refresh-stylesheet', {
|
|
id : params.apiParams.in_sektion,
|
|
location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' )//<= send skope id to the preview so we can use it when ajaxing
|
|
});
|
|
|
|
|
|
api.previewer.trigger( 'sek-refresh-level', {
|
|
level : 'section',
|
|
id : params.apiParams.in_sektion
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// GENERATE UI ELEMENTS
|
|
// June 2020 :
|
|
// When a user creates a new section, the content type switcher is set to section
|
|
// For all other cases, when user clicks on the + icon, the content type switcher is set to module
|
|
'sek-pick-content' : function( params ) {
|
|
params = _.isObject(params) ? params : {};
|
|
// Set the active content type here
|
|
// This is used in api.czrInputMap.content_type_switcher()
|
|
// Fixes issue https://github.com/presscustomizr/nimble-builder/issues/248
|
|
api.czr_sektions.currentContentPickerType = api.czr_sektions.currentContentPickerType || new api.Value();
|
|
|
|
// Set the last clicked target element id now => will be used for double click insertion of module / section
|
|
if ( _.isObject( params ) && params.id ) {
|
|
// self reset after a moment.
|
|
// @see CZRSeksPrototype::initialize
|
|
// implemented for https://github.com/presscustomizr/nimble-builder/issues/317
|
|
self.lastClickedTargetInPreview( { id : params.id } );
|
|
}
|
|
|
|
params = params || {};
|
|
sendToPreview = true;
|
|
apiParams = {};
|
|
uiParams = {
|
|
action : 'sek-generate-draggable-candidates-picker-ui',
|
|
content_type : params.content_type || 'module',
|
|
// <= the "was_triggered" param can be used to determine if we need to animate the picker control or not. @see ::generateUI() case 'sek-generate-draggable-candidates-picker-ui'
|
|
// true by default, because this is the most common scenario ( when adding a section, a column ... )
|
|
// but false when clicking on the + ui icon in the preview
|
|
was_triggered : _.has( params, 'was_triggered' ) ? params.was_triggered : true,
|
|
focus : _.has( params, 'focus' ) ? params.focus : true
|
|
};
|
|
return self.generateUI( uiParams );
|
|
},
|
|
|
|
'sek-edit-options' : function( params ) {
|
|
sendToPreview = true;
|
|
apiParams = {};
|
|
if ( _.isEmpty( params.id ) ) {
|
|
return $.Deferred( function() {
|
|
this.reject( 'missing id' );
|
|
});
|
|
}
|
|
uiParams = {
|
|
action : 'sek-generate-level-options-ui',
|
|
location : params.location,//<= added June 2020 for https://github.com/presscustomizr/nimble-builder-pro/issues/6
|
|
level : params.level,
|
|
id : params.id,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column,
|
|
options : params.options || []
|
|
};
|
|
return self.generateUI( uiParams );
|
|
},
|
|
'sek-edit-module' : function( params ) {
|
|
sendToPreview = true;
|
|
apiParams = {};
|
|
uiParams = {
|
|
action : 'sek-generate-module-ui',
|
|
level : params.level,
|
|
id : params.id,
|
|
in_sektion : params.in_sektion,
|
|
in_column : params.in_column,
|
|
options : params.options || []
|
|
};
|
|
return self.generateUI( uiParams );
|
|
},
|
|
|
|
|
|
// OTHER MESSAGE TYPES
|
|
// @params {
|
|
// type : info, error, success
|
|
// notif_id : '',
|
|
// is_pro_notif: '',
|
|
// message : '',
|
|
// duration : in ms,
|
|
// button_see_me : true
|
|
// }
|
|
'sek-notify' : function( params ) {
|
|
sendToPreview = false;
|
|
var notif_id = params.notif_id || 'sek-notify';
|
|
params.button_see_me = _.isUndefined(params.button_see_me) ? true : params.button_see_me;
|
|
|
|
// Make sure we clean the last printed notification
|
|
if ( self.lastNimbleNotificationId ) {
|
|
api.notifications.remove( self.lastNimbleNotificationId );
|
|
}
|
|
|
|
return $.Deferred(function() {
|
|
api.panel( sektionsLocalizedData.sektionsPanelId, function( __main_panel__ ) {
|
|
api.notifications.add( new api.Notification( notif_id, {
|
|
type: params.type || 'info',
|
|
message: params.message,
|
|
dismissible: true
|
|
}));
|
|
|
|
self.lastNimbleNotificationId = notif_id;
|
|
var _doThingsWhenRendered = function() {
|
|
if ( params.is_pro_notif ) {
|
|
api.notifications( notif_id ).container.css('background', '#ffff88');
|
|
}
|
|
if ( params.button_see_me ) {
|
|
api.notifications( notif_id ).container.addClass('button-see-me-twice');
|
|
_.delay( function() {
|
|
api.notifications.container.removeClass('button-see-me-twice');
|
|
}, 2000 );
|
|
}
|
|
api.notifications.unbind('rendered', _doThingsWhenRendered );
|
|
};
|
|
if ( api.notifications.has( notif_id ) ) {
|
|
api.notifications.bind('rendered', _doThingsWhenRendered );
|
|
}
|
|
// Removed if not dismissed after 5 seconds
|
|
_.delay( function() {
|
|
api.notifications.remove( notif_id );
|
|
}, params.duration || 5000 );
|
|
});
|
|
// always pass the local or global skope of the currently customized location id when resolving the promise.
|
|
// It will be send to the preview and used when ajaxing
|
|
this.resolve({
|
|
is_global_location : self.isGlobalLocation( params )
|
|
});
|
|
});
|
|
},
|
|
|
|
'sek-refresh-level' : function( params ) {
|
|
sendToPreview = true;
|
|
return $.Deferred(function(_dfd_) {
|
|
apiParams = {
|
|
action : 'sek-refresh-level',
|
|
level : params.level,
|
|
id : params.id,
|
|
|
|
// added for https://github.com/presscustomizr/nimble-builder/issues/471
|
|
original_action : params.original_action,
|
|
moved_level_id : params.moved_level_id
|
|
};
|
|
uiParams = {};
|
|
// always pass the local or global skope of the currently customized location id when resolving the promise.
|
|
// It will be send to the preview and used when ajaxing
|
|
_dfd_.resolve({
|
|
is_global_location : self.isGlobalLocation( params )
|
|
});
|
|
});
|
|
},
|
|
|
|
'sek-refresh-stylesheet' : function( params ) {
|
|
sendToPreview = true;
|
|
params = params || {};
|
|
return $.Deferred(function(_dfd_) {
|
|
apiParams = {id : params.id};
|
|
uiParams = {};
|
|
// always pass the local or global skope of the currently customized location id when resolving the promise.
|
|
// It will be send to the preview and used when ajaxing
|
|
_dfd_.resolve({
|
|
is_global_location : self.isGlobalLocation( params )
|
|
});
|
|
});
|
|
},
|
|
|
|
// Updated June 2020 for https://github.com/presscustomizr/nimble-builder/issues/520
|
|
'sek-toggle-save-section-ui' : function( params ) {
|
|
sendToPreview = false;
|
|
self.idOfSectionToSave = params.id;
|
|
self.saveSectionDialogVisible( true );
|
|
return $.Deferred(function(_dfd_) {
|
|
apiParams = {
|
|
// action : 'sek-refresh-level',
|
|
// level : params.level,
|
|
// id : params.id
|
|
};
|
|
uiParams = {};
|
|
// always pass the local or global skope of the currently customized location id when resolving the promise.
|
|
// It will be send to the preview and used when ajaxing
|
|
_dfd_.resolve({
|
|
is_global_location : self.isGlobalLocation( params )
|
|
});
|
|
});
|
|
},
|
|
|
|
|
|
// RESET
|
|
'sek-reset-collection' : {
|
|
callback : function( params ) {
|
|
sendToPreview = false;//<= when the level is refreshed when complete, we don't need to send to preview.
|
|
uiParams = {};
|
|
apiParams = params;
|
|
apiParams.action = 'sek-reset-collection';
|
|
apiParams.scope = params.scope;
|
|
var _dfd_ = self.updateAPISetting( apiParams )
|
|
.done( function( resp) {
|
|
api.previewer.refresh();
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'reset-success',
|
|
type : 'success',
|
|
duration : 8000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['Reset complete'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
if ( 'local' === params.scope ) {
|
|
var _doThingsAfterRefresh = function() {
|
|
// INHERITANCE
|
|
// solves the problem of preventing group template inheritance after a local reset
|
|
var _is_inheritance_enabled_in_local_options = true,
|
|
currentSetValue = api( self.localSectionsSettingId() )(),
|
|
localOptions = currentSetValue.local_options;
|
|
|
|
if ( localOptions && _.isObject(localOptions) && localOptions.local_reset && !_.isUndefined( localOptions.local_reset.inherit_group_scope ) ) {
|
|
_is_inheritance_enabled_in_local_options = localOptions.local_reset.inherit_group_scope;
|
|
}
|
|
//api.infoLog('RESET MAIN LOCAL SETTING ON NEW SKOPES SYNCED', self.localSectionsSettingId() );
|
|
// Keep only the settings for global option, local options, content picker
|
|
// Remove all the others
|
|
// ( local options are removed below )
|
|
self.cleanRegisteredLevelSettings();
|
|
|
|
// Removes the local sektions setting
|
|
api.remove( self.localSectionsSettingId() );
|
|
|
|
// RE-register the local sektions setting with values sent from the server
|
|
// If the local page inherits a group skope, those will be set as local
|
|
// To prevent saving server sets property __inherits_group_skope_tmpl_when_exists__ = true
|
|
// set the param { dirty : true } => because otherwise, if user saves right after a reset, local option won't be ::updated() server side.
|
|
// Which means that the page will keep its previous aspect
|
|
try { self.setupSettingsToBeSaved( { dirty : true, is_group_inheritance_enabled : _is_inheritance_enabled_in_local_options } ); } catch( er ) {
|
|
api.errare( 'Error in self.localSectionsSettingId.callbacks => self.setupSettingsToBeSaved()' , er );
|
|
}
|
|
|
|
api.trigger('nimble-update-topbar-skope-status', { after_reset : true } );
|
|
|
|
// Removes and RE-register local settings and controls
|
|
self.generateUI({
|
|
action : 'sek-generate-local-skope-options-ui',
|
|
clean_settings_and_controls_first : true//<= see self.generateUIforLocalSkopeOptions()
|
|
});
|
|
// 'czr-new-skopes-synced' is always sent on a previewer.refresh()
|
|
api.previewer.unbind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
};
|
|
api.previewer.bind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
}//if ( 'local' === params.scope ) {
|
|
})
|
|
.fail( function( response ) {
|
|
api.errare( 'reset_button input => error when firing ::updateAPISetting', response );
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'reset-failed',
|
|
type : 'error',
|
|
duration : 8000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['Reset failed'],
|
|
'<br/>',
|
|
'<i>' + response + '</i>',
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
|
|
return _dfd_;
|
|
},
|
|
complete : function( params ) {
|
|
// api.previewer.refresh();
|
|
// api.previewer.trigger('sek-notify', {
|
|
// notif_id : 'reset-success',
|
|
// type : 'success',
|
|
// duration : 8000,
|
|
// message : [
|
|
// '<span>',
|
|
// '<strong>',
|
|
// sektionsLocalizedData.i18n['Reset complete'],
|
|
// '</strong>',
|
|
// '</span>'
|
|
// ].join('')
|
|
// });
|
|
}
|
|
},
|
|
};//msgCollection
|
|
|
|
// Schedule the reactions
|
|
// May be send a message to the preview
|
|
_.each( msgCollection, function( callbackFn, msgId ) {
|
|
api.previewer.bind( msgId, function( params ) {
|
|
var _cb_;
|
|
if ( _.isFunction( callbackFn ) ) {
|
|
_cb_ = callbackFn;
|
|
} else if ( _.isFunction( callbackFn.callback ) ) {
|
|
_cb_ = callbackFn.callback;
|
|
} else {
|
|
api.errare( '::reactToPreviewMsg => invalid callback for action ' + msgId );
|
|
return;
|
|
}
|
|
|
|
// Close template gallery, template saver
|
|
// do nothing when we notify
|
|
if ( 'sek-notify' !== msgId ) {
|
|
self.templateGalleryExpanded(false);
|
|
self.tmplDialogVisible(false);
|
|
}
|
|
|
|
try { _cb_( params )
|
|
// the cloneId is passed when resolving the ::updateAPISetting() promise()
|
|
// they are needed on level duplication to get the newly generated level id.
|
|
.done( function( promiseParams ) {
|
|
promiseParams = promiseParams || {};
|
|
// Send to the preview
|
|
if ( sendToPreview ) {
|
|
var messageToSend = {
|
|
location_skope_id : true === promiseParams.is_global_location ? sektionsLocalizedData.globalSkopeId : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
local_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),
|
|
apiParams : apiParams,
|
|
uiParams : uiParams,
|
|
cloneId : ! _.isEmpty( promiseParams.cloneId ) ? promiseParams.cloneId : false
|
|
}, isError = false;
|
|
|
|
// when using api.previewer.send, the data are sent as a JSON ( see customize-base.js::send )
|
|
// If the object message to send has a circular reference, the JSON.stringify will break ( TypeError: Converting circular structure to JSON )
|
|
// fixes https://github.com/presscustomizr/nimble-builder/issues/848
|
|
try { JSON.stringify( messageToSend ); } catch( er ) {
|
|
api.errare( 'JSON.stringify problem when executing the callback of ' + msgId, messageToSend );
|
|
isError = true;
|
|
}
|
|
if ( ! isError ) {
|
|
api.previewer.send(
|
|
msgId,
|
|
messageToSend
|
|
);
|
|
}
|
|
} else {
|
|
// if nothing was sent to the preview, trigger the '*_done' action so we can execute the 'complete' callback
|
|
api.previewer.trigger( [ msgId, 'done' ].join('_'), { apiParams : apiParams, uiParams : uiParams } );
|
|
}
|
|
// say it
|
|
self.trigger( [ msgId, 'done' ].join('_'), params );
|
|
})
|
|
.fail( function( errorMsg ) {
|
|
api.errare( 'reactToPreviewMsg => problem or error when running action ' + msgId, errorMsg );
|
|
// api.panel( sektionsLocalizedData.sektionsPanelId, function( __main_panel__ ) {
|
|
// api.notifications.add( new api.Notification( 'sek-react-to-preview', {
|
|
// type: 'info',
|
|
// message: errorMsg,
|
|
// dismissible: true
|
|
// } ) );
|
|
|
|
// // Removed if not dismissed after 5 seconds
|
|
// _.delay( function() {
|
|
// api.notifications.remove( 'sek-react-to-preview' );
|
|
// }, 5000 );
|
|
// });
|
|
|
|
if ( !_.isEmpty( errorMsg ) && sektionsLocalizedData.isDevMode ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 30000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + errorMsg + '</strong>',
|
|
'<br>',
|
|
sektionsLocalizedData.i18n['If this problem locks Nimble Builder, you can try resetting the sections of this page.'],
|
|
'<br>',
|
|
'<span style="text-align:center;display:block">',
|
|
'<button type="button" class="button" aria-label="' + sektionsLocalizedData.i18n.Reset + '" data-sek-reset="true">' + sektionsLocalizedData.i18n.Reset + '</button>',
|
|
'</span>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
}//if ( sektionsLocalizedData.isDevMode ) {
|
|
}); } catch( _er_ ) {
|
|
api.errare( 'reactToPreviewMsg => error when receiving ' + msgId, _er_ );
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
// Schedule actions when callback done msg is sent by the preview
|
|
_.each( msgCollection, function( callbackFn, msgId ) {
|
|
api.previewer.bind( [ msgId, 'done' ].join('_'), function( params ) {
|
|
if ( _.isFunction( callbackFn.complete ) ) {
|
|
try { callbackFn.complete( params ); } catch( _er_ ) {
|
|
api.errare( 'reactToPreviewMsg done => error when receiving ' + [msgId, 'done'].join('_') , _er_ );
|
|
}
|
|
}
|
|
});
|
|
});
|
|
},//reactToPreview();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fired in initialized on api(ready)
|
|
schedulePrintSectionJson : function() {
|
|
var self = this;
|
|
var popupCenter = function ( content ) {
|
|
w = 400;
|
|
h = 300;
|
|
// Fixes dual-screen position Most browsers Firefox
|
|
var dualScreenLeft = ! _.isUndefined( window.screenLeft ) ? window.screenLeft : window.screenX;
|
|
var dualScreenTop = ! _.isUndefined( window.screenTop ) ? window.screenTop : window.screenY;
|
|
|
|
var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
|
|
var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
|
|
|
|
var left = ((width / 2) - (w / 2)) + dualScreenLeft;
|
|
var top = ((height / 2) - (h / 2)) + dualScreenTop;
|
|
var newWindow = window.open("about:blank", null, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
|
|
var doc = newWindow.document;
|
|
doc.open("text/html");
|
|
doc.write( content );
|
|
doc.close();
|
|
// Puts focus on the newWindow
|
|
if (window.focus) {
|
|
newWindow.focus();
|
|
}
|
|
};
|
|
|
|
api.previewer.bind( 'sek-to-json', function( params ) {
|
|
var sectionModel = $.extend( true, {}, self.getLevelModel( params.id ) );
|
|
console.log( JSON.stringify( self.cleanIds( sectionModel ) ) );
|
|
//popupCenter( JSON.stringify( cleanIds( sectionModel ) ) );
|
|
});
|
|
}//schedulePrintSectionJson
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// @params = {
|
|
// action : 'sek-generate-module-ui' / 'sek-generate-level-options-ui'
|
|
// level : params.level,
|
|
// id : params.id,
|
|
// in_sektion : params.in_sektion,
|
|
// in_column : params.in_column,
|
|
// options : params.options || []
|
|
// }
|
|
// @return promise()
|
|
generateUI : function( params ) {
|
|
var self = this,
|
|
dfd = $.Deferred();
|
|
|
|
if ( _.isEmpty( params.action ) ) {
|
|
dfd.reject( 'generateUI => missing action' );
|
|
}
|
|
|
|
// REGISTER SETTING AND CONTROL
|
|
switch ( params.action ) {
|
|
// FRONT AND LEVEL MODULES UI
|
|
// The registered elements are cleaned (self.cleanRegisteredAndLargeSelectInput()) in the callbacks,
|
|
// because we want to check if the requested UI is not the one already rendered, and fire a button-see-me animation if yes.
|
|
case 'sek-generate-module-ui' :
|
|
try{ dfd = self.generateUIforFrontModules( params, dfd ); } catch( er ) {
|
|
api.errare( '::generateUI() => error', er );
|
|
dfd = $.Deferred();
|
|
}
|
|
break;
|
|
|
|
case 'sek-generate-level-options-ui' :
|
|
try{ dfd = self.generateUIforLevelOptions( params, dfd ); } catch( er ) {
|
|
api.errare( '::generateUI() => error', er );
|
|
dfd = $.Deferred();
|
|
}
|
|
break;
|
|
|
|
// Possible content types :
|
|
// 1) module
|
|
// 2) preset_section
|
|
case 'sek-generate-draggable-candidates-picker-ui' :
|
|
// Clean previously generated UI elements
|
|
self.cleanRegisteredAndLargeSelectInput();
|
|
try{ dfd = self.generateUIforDraggableContent( params, dfd ); } catch( er ) {
|
|
api.errare( '::generateUI() => error', er );
|
|
dfd = $.Deferred();
|
|
}
|
|
// June 2020 Make sure the content picker is set to "section" when user creates a new section
|
|
api.czr_sektions.currentContentPickerType( params.content_type || 'module' );
|
|
break;
|
|
|
|
// Fired in ::initialize()
|
|
case 'sek-generate-local-skope-options-ui' :
|
|
// Clean previously generated UI elements
|
|
self.cleanRegisteredAndLargeSelectInput();
|
|
try{ dfd = self.generateUIforLocalSkopeOptions( params, dfd ); } catch( er ) {
|
|
api.errare( '::generateUI() => error', er );
|
|
dfd = $.Deferred();
|
|
}
|
|
break;
|
|
|
|
// Fired in ::initialize()
|
|
case 'sek-generate-global-options-ui' :
|
|
// Clean previously generated UI elements
|
|
self.cleanRegisteredAndLargeSelectInput();
|
|
try{ dfd = self.generateUIforGlobalOptions( params, dfd ); } catch( er ) {
|
|
api.errare( '::generateUI() => error', er );
|
|
dfd = $.Deferred();
|
|
}
|
|
break;
|
|
}//switch
|
|
|
|
return 'pending' == dfd.state() ? dfd.resolve().promise() : dfd.promise();//<= we might want to resolve on focus.completeCallback ?
|
|
},//generateUI()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @params = {
|
|
// uiParams : params,
|
|
// options_type : 'spacing',
|
|
// settingParams : {
|
|
// to : to,
|
|
// from : from,
|
|
// args : args
|
|
// }
|
|
// }
|
|
//
|
|
// @param settingParams.args = {
|
|
// inputRegistrationParams : {
|
|
// id :,
|
|
// type :
|
|
// refresh_markup : bool
|
|
// refresh_stylesheet : bool
|
|
// refresh_fonts : bool
|
|
// }
|
|
// input_changed : input_id
|
|
// input_transport : 'inherit'/'postMessage',
|
|
// module : { items : [...]}
|
|
// module_id :
|
|
// not_preview_sent : bool
|
|
//}
|
|
//
|
|
// Note 1 : this method must handle two types of modules :
|
|
// 1) mono item modules, for which the settingParams.to is an object, a single item object
|
|
// 2) multi-items modules, for which the settingParams.to is an array, a collection of item objects
|
|
// How do we know that we are a in single / multi item module ?
|
|
//
|
|
// Note 2 : we must also handle several scenarios of module value update :
|
|
// 1) mono-items and multi-items module => input change
|
|
// 2) crud multi item => item added or removed => in this case some args are not passed, like params.settingParams.args.inputRegistrationParams
|
|
updateAPISettingAndExecutePreviewActions : function( params ) {
|
|
if ( _.isEmpty( params.settingParams ) || !_.has( params.settingParams, 'to' ) ) {
|
|
api.errare( 'updateAPISettingAndExecutePreviewActions => missing params.settingParams.to. The api main setting can not be updated', params );
|
|
return;
|
|
}
|
|
var self = this;
|
|
|
|
// NORMALIZE THE VALUE WE WANT TO WRITE IN THE MAIN SETTING
|
|
// 1) We don't want to store the default title and id module properties
|
|
// 2) We don't want to write in db the properties that are set to their default values
|
|
var rawModuleValue = params.settingParams.to,
|
|
moduleValueCandidate,// {} or [] if mono item of multi-item module
|
|
parentModuleType = null,
|
|
isMultiItemModule = false;
|
|
|
|
if ( _.isEmpty( params.settingParams.args ) || !_.has( params.settingParams.args, 'moduleRegistrationParams' ) ) {
|
|
api.errare( 'updateAPISettingAndExecutePreviewActions => missing params.settingParams.args.moduleRegistrationParams The api main setting can not be updated', params );
|
|
return;
|
|
}
|
|
|
|
var _ctrl_ = params.settingParams.args.moduleRegistrationParams.control,
|
|
_module_id_ = params.settingParams.args.moduleRegistrationParams.id,
|
|
parentModuleInstance = _ctrl_.czr_Module( _module_id_ );
|
|
|
|
if ( !_.isEmpty( parentModuleInstance ) ) {
|
|
parentModuleType = parentModuleInstance.module_type;
|
|
isMultiItemModule = parentModuleInstance.isMultiItem();
|
|
} else {
|
|
api.errare( 'updateAPISettingAndExecutePreviewActions => missing parentModuleInstance', params );
|
|
}
|
|
|
|
|
|
|
|
// The new module value can be a single item object if monoitem module, or an array of item objects if multi-item crud
|
|
// Let's normalize it
|
|
if ( !isMultiItemModule && _.isObject( rawModuleValue ) ) {
|
|
moduleValueCandidate = self.normalizeAndSanitizeSingleItemInputValues( {
|
|
item_value : rawModuleValue,
|
|
parent_module_type : parentModuleType,
|
|
is_multi_items : false
|
|
});
|
|
} else {
|
|
moduleValueCandidate = [];
|
|
_.each( rawModuleValue, function( item ) {
|
|
moduleValueCandidate.push( self.normalizeAndSanitizeSingleItemInputValues( {
|
|
item_value :item,
|
|
parent_module_type : parentModuleType,
|
|
is_multi_items : true
|
|
}));
|
|
});
|
|
}
|
|
|
|
// WHAT TO REFRESH IN THE PREVIEW ? Markup, stylesheet, font ?
|
|
// The action to trigger is determined by the changed input
|
|
// For the options of a level, the default action is to refresh the stylesheet.
|
|
// But we might need to refresh the markup in some cases. Like for example when a css class is added. @see the boxed-wide layout example
|
|
if ( _.isEmpty( params.defaultPreviewAction ) ) {
|
|
api.errare( 'updateAPISettingAndExecutePreviewActions => missing defaultPreviewAction in passed params. No action can be triggered to the api.previewer.', params );
|
|
return;
|
|
}
|
|
// Set the default value
|
|
var refresh_stylesheet = 'refresh_stylesheet' === params.defaultPreviewAction,//<= default action for level options
|
|
refresh_markup = 'refresh_markup' === params.defaultPreviewAction,//<= default action for module options
|
|
refresh_fonts = 'refresh_fonts' === params.defaultPreviewAction,
|
|
refresh_preview = 'refresh_preview' === params.defaultPreviewAction,
|
|
refresh_css_via_post_message = false;//<= introduced for pro custom css
|
|
|
|
// Maybe set the input based value
|
|
var input_id = params.settingParams.args.input_changed;
|
|
var inputRegistrationParams;
|
|
|
|
// introduced when updating the new text editors
|
|
// https://github.com/presscustomizr/nimble-builder/issues/403
|
|
var refreshMarkupWhenNeededForInput = function() {
|
|
return inputRegistrationParams && _.isString( inputRegistrationParams.refresh_markup ) && 'true' !== inputRegistrationParams.refresh_markup && 'false' !== inputRegistrationParams.refresh_markup;
|
|
};
|
|
|
|
if ( !_.isUndefined( input_id ) ) {
|
|
inputRegistrationParams = self.getInputRegistrationParams( input_id, parentModuleType );
|
|
if ( !_.isUndefined( inputRegistrationParams.refresh_stylesheet ) ) {
|
|
refresh_stylesheet = Boolean( inputRegistrationParams.refresh_stylesheet );
|
|
}
|
|
if ( !_.isUndefined( inputRegistrationParams.refresh_markup ) ) {
|
|
if ( refreshMarkupWhenNeededForInput() ) {
|
|
refresh_markup = inputRegistrationParams.refresh_markup;
|
|
} else {
|
|
refresh_markup = Boolean( inputRegistrationParams.refresh_markup );
|
|
}
|
|
}
|
|
if ( !_.isUndefined( inputRegistrationParams.refresh_fonts ) ) {
|
|
refresh_fonts = Boolean( inputRegistrationParams.refresh_fonts );
|
|
}
|
|
if ( !_.isUndefined( inputRegistrationParams.refresh_preview ) ) {
|
|
refresh_preview = Boolean( inputRegistrationParams.refresh_preview );
|
|
}
|
|
if ( !_.isUndefined( inputRegistrationParams.refresh_css_via_post_message ) ) {
|
|
refresh_css_via_post_message = Boolean( inputRegistrationParams.refresh_css_via_post_message );
|
|
}
|
|
}
|
|
|
|
var _doUpdateWithRequestedAction = function() {
|
|
// GLOBAL OPTIONS CASE => SITE WIDE => WRITING IN A SPECIFIC OPTION, SEPARATE FROM THE SEKTION COLLECTION
|
|
if ( true === params.isGlobalOptions ) {
|
|
if ( _.isEmpty( params.options_type ) ) {
|
|
api.errare( 'updateAPISettingAndExecutePreviewActions => error when updating the global options => missing options_type');
|
|
return;
|
|
}
|
|
//api( sektionsLocalizedData.optNameForGlobalOptions )() is registered on ::initialize();
|
|
var rawGlobalOptions = api( sektionsLocalizedData.optNameForGlobalOptions )(),
|
|
clonedGlobalOptions = $.extend( true, {}, _.isObject( rawGlobalOptions ) ? rawGlobalOptions : {} ),
|
|
_valueCandidate = {};
|
|
|
|
// consider only the non empty settings for db
|
|
// booleans should bypass this check
|
|
_.each( moduleValueCandidate || {}, function( _val_, _key_ ) {
|
|
// Note : _.isEmpty( 5 ) returns true when checking an integer,
|
|
// that's why we need to cast the _val_ to a string when using _.isEmpty()
|
|
if ( !_.isBoolean( _val_ ) && _.isEmpty( _val_ + "" ) )
|
|
return;
|
|
_valueCandidate[ _key_ ] = _val_;
|
|
});
|
|
|
|
clonedGlobalOptions[ params.options_type ] = _valueCandidate;
|
|
|
|
// Set it
|
|
api( sektionsLocalizedData.optNameForGlobalOptions )( clonedGlobalOptions );
|
|
|
|
// REFRESH THE PREVIEW ?
|
|
if ( false !== refresh_preview ) {
|
|
api.previewer.refresh();
|
|
}
|
|
|
|
// Refresh the font list now, before ajax stylesheet update
|
|
// So that the .fonts collection is ready server side
|
|
if ( true === refresh_fonts ) {
|
|
var newFontFamily = params.settingParams.args.input_value;
|
|
if ( !_.isString( newFontFamily ) ) {
|
|
api.errare( 'updateAPISettingAndExecutePreviewActions => font-family must be a string', newFontFamily );
|
|
return;
|
|
}
|
|
|
|
// will add it only if gfont
|
|
self.updateGlobalGFonts( newFontFamily );
|
|
}
|
|
|
|
// REFRESH THE STYLESHEET ?
|
|
if ( true === refresh_stylesheet ) {
|
|
api.previewer.send( 'sek-refresh-stylesheet', {
|
|
local_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),
|
|
location_skope_id : sektionsLocalizedData.globalSkopeId
|
|
});
|
|
}
|
|
} else {
|
|
// LEVEL OPTION CASE => LOCAL
|
|
return self.updateAPISetting({
|
|
action : params.uiParams.action,// mandatory : 'sek-generate-level-options-ui', 'sek-generate-local-skope-options-ui',...
|
|
id : params.uiParams.id,
|
|
value : moduleValueCandidate,
|
|
in_column : params.uiParams.in_column,//not mandatory
|
|
in_sektion : params.uiParams.in_sektion,//not mandatory
|
|
|
|
// specific for level options and local skope options
|
|
options_type : params.options_type,// mandatory : 'layout', 'spacing', 'bg_border', 'height', ...
|
|
|
|
settingParams : params.settingParams
|
|
}).done( function( promiseParams ) {
|
|
// STYLESHEET => default action when modifying the level options
|
|
if ( true === refresh_stylesheet ) {
|
|
api.previewer.send( 'sek-refresh-stylesheet', {
|
|
location_skope_id : true === promiseParams.is_global_location ? sektionsLocalizedData.globalSkopeId : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
local_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
apiParams : {
|
|
action : 'sek-refresh-stylesheet',
|
|
id : params.uiParams.id,
|
|
level : params.uiParams.level
|
|
},
|
|
});
|
|
}
|
|
|
|
|
|
// MARKUP
|
|
// since https://github.com/presscustomizr/nimble-builder/issues/403, 2 cases :
|
|
// 1) update simply by postMessage, without ajax action <= refresh_markup is a string of selectors, and the content does not include content that needs server side parsing, like shortcode or template tages
|
|
// 2) otherwise => update the level with an ajax refresh action
|
|
|
|
var _changed_item_id;
|
|
if ( isMultiItemModule && params.settingParams.args.inputRegistrationParams && _.isFunction( params.settingParams.args.inputRegistrationParams.input_parent ) ) {
|
|
_changed_item_id = params.settingParams.args.inputRegistrationParams.input_parent.id;
|
|
}
|
|
|
|
var _sendRequestForAjaxMarkupRefresh = function() {
|
|
api.previewer.send( 'sek-refresh-level', {
|
|
location_skope_id : true === promiseParams.is_global_location ? sektionsLocalizedData.globalSkopeId : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
local_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
apiParams : {
|
|
action : 'sek-refresh-level',
|
|
id : params.uiParams.id,
|
|
level : params.uiParams.level,
|
|
changed_item_id : _changed_item_id,
|
|
control_id : _ctrl_.id,
|
|
is_multi_items : isMultiItemModule
|
|
},
|
|
skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
});
|
|
};
|
|
|
|
// NB ajaxily refreshes the markup
|
|
if ( true === refresh_markup ) {
|
|
_sendRequestForAjaxMarkupRefresh();
|
|
}
|
|
|
|
// Case when NB maybe refreshes the markup via postmessage
|
|
// Note : for multi-item modules, the changed item id is sent
|
|
if ( refreshMarkupWhenNeededForInput() ) {
|
|
var _html_content = params.settingParams.args.input_value;
|
|
if ( !_.isString( _html_content ) ) {
|
|
throw new Error( '::updateAPISettingAndExecutePreviewActions => _doUpdateWithRequestedAction => refreshMarkupWhenNeededForInput => html content is not a string.');
|
|
}
|
|
|
|
// Like shortcode tags, template tags, script tags
|
|
if ( !self.htmlIncludesElementsThatNeedAnAjaxRefresh( _html_content ) ) {
|
|
api.previewer.send( 'sek-update-html-in-selector', {
|
|
selector : inputRegistrationParams.refresh_markup,
|
|
changed_item_id : _changed_item_id,
|
|
is_multi_items : isMultiItemModule,
|
|
html : _html_content,
|
|
id : params.uiParams.id,
|
|
location_skope_id : true === promiseParams.is_global_location ? sektionsLocalizedData.globalSkopeId : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
local_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
apiParams : {
|
|
action : 'sek-update-html-in-selector',
|
|
id : params.uiParams.id,
|
|
level : params.uiParams.level
|
|
},
|
|
skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' )//<= send skope id to the preview so we can use it when ajaxing
|
|
});
|
|
} else {
|
|
_sendRequestForAjaxMarkupRefresh();
|
|
}
|
|
}
|
|
|
|
if ( true === refresh_css_via_post_message ) {
|
|
var _css_content = params.settingParams.args.input_value;
|
|
if ( !_.isString( _css_content ) ) {
|
|
throw new Error( '::updateAPISettingAndExecutePreviewActions => _doUpdateWithRequestedAction => refresh css with post message => css content is not a string.');
|
|
} else {
|
|
api.previewer.send( 'sek-update-css-with-postmessage', {
|
|
//selector : inputRegistrationParams.refresh_markup,
|
|
changed_item_id : _changed_item_id,
|
|
is_multi_items : isMultiItemModule,
|
|
css_content : _css_content,
|
|
id : params.uiParams.id,
|
|
location_skope_id : true === promiseParams.is_global_location ? sektionsLocalizedData.globalSkopeId : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
local_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
apiParams : {
|
|
action : 'sek-update-css-with-postmessage',
|
|
id : params.uiParams.id,
|
|
level : params.uiParams.level
|
|
},
|
|
skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
|
|
is_current_page_custom_css : 'local_custom_css' === input_id
|
|
});
|
|
}
|
|
}
|
|
|
|
// REFRESH THE PREVIEW ?
|
|
if ( true === refresh_preview ) {
|
|
api.previewer.refresh();
|
|
}
|
|
})
|
|
.fail( function( er ) {
|
|
api.errare( '::updateAPISettingAndExecutePreviewActions=> api setting not updated', er );
|
|
api.errare( '::updateAPISettingAndExecutePreviewActions=> api setting not updated => params ', params );
|
|
});//self.updateAPISetting()
|
|
}
|
|
};//_doUpdateWithRequestedAction
|
|
|
|
// if the changed input is a google font modifier ( <=> true === refresh_fonts )
|
|
// => we want to first refresh the google font collection, and then proceed the requested action
|
|
// this way we make sure that the customized value used when ajaxing will be taken into account when writing the google font http request link
|
|
if ( true === refresh_fonts ) {
|
|
var newFontFamily = params.settingParams.args.input_value;
|
|
if ( !_.isString( newFontFamily ) ) {
|
|
api.errare( 'updateAPISettingAndExecutePreviewActions => font-family must be a string', newFontFamily );
|
|
return;
|
|
}
|
|
|
|
// add it only if gfont
|
|
if ( true === params.isGlobalOptions ) {
|
|
_doUpdateWithRequestedAction( newFontFamily );
|
|
} else {
|
|
self.updateAPISetting({
|
|
action : 'sek-update-fonts',
|
|
font_family : newFontFamily,
|
|
is_global_location : self.isGlobalLocation( params.uiParams )
|
|
})
|
|
// we use always() instead of done here, because the api section setting might not be changed ( and therefore return a reject() promise ).
|
|
// => this can occur when a user is setting a google font already picked elsewhere
|
|
// @see case 'sek-update-fonts'
|
|
.always( function() {
|
|
_doUpdateWithRequestedAction().then( function() {
|
|
// always refresh again after
|
|
// Why ?
|
|
// Because the first refresh was done before actually setting the new font family, so based on a previous set of fonts
|
|
// which leads to have potentially an additional google fonts that we don't need after the first refresh
|
|
// that's why this second refresh is required. It wont trigger any preview ajax actions. Simply refresh the root fonts property of the main api setting.
|
|
self.updateAPISetting({
|
|
action : 'sek-update-fonts',
|
|
is_global_location : self.isGlobalLocation( params.uiParams )
|
|
});
|
|
});
|
|
});
|
|
}
|
|
} else {
|
|
_doUpdateWithRequestedAction();
|
|
}
|
|
},//updateAPISettingAndExecutePreviewActions
|
|
|
|
|
|
|
|
// IMPORTANT => Updates the setting for global options
|
|
updateGlobalGFonts : function( newFontFamily ) {
|
|
var self = this;
|
|
//api( sektionsLocalizedData.optNameForGlobalOptions )() is registered on ::initialize();
|
|
var rawGlobalOptions = api( sektionsLocalizedData.optNameForGlobalOptions )(),
|
|
clonedGlobalOptions = $.extend( true, {}, _.isObject( rawGlobalOptions ) ? rawGlobalOptions : {} );
|
|
|
|
// Get the gfonts from the level options and modules values
|
|
var currentGfonts = self.sniffGlobalGFonts( clonedGlobalOptions );
|
|
|
|
// add it only if gfont
|
|
if ( !_.isEmpty( newFontFamily ) && _.isString( newFontFamily ) ) {
|
|
if ( newFontFamily.indexOf('gfont') > -1 && !_.contains( currentGfonts, newFontFamily ) ) {
|
|
currentGfonts.push( newFontFamily );
|
|
}
|
|
}
|
|
// update the global gfonts collection
|
|
// this is then used server side in Sek_Dyn_CSS_Handler::sek_get_gfont_print_candidates to build the Google Fonts request
|
|
clonedGlobalOptions.fonts = currentGfonts;
|
|
|
|
// Set it
|
|
api( sektionsLocalizedData.optNameForGlobalOptions )( clonedGlobalOptions );
|
|
},
|
|
|
|
|
|
// Walk the global option and populate an array of google fonts
|
|
// To be a candidate for sniffing, an input font value font should start with [gfont]
|
|
// @return array
|
|
sniffGlobalGFonts : function( _data_ ) {
|
|
var self = this,
|
|
gfonts = [],
|
|
_snifff_ = function( _data_ ) {
|
|
_.each( _data_, function( levelData, _key_ ) {
|
|
// of course, don't sniff the already stored fonts
|
|
if ( 'fonts' === _key_ )
|
|
return;
|
|
// example of input_id candidate 'font_family_css'
|
|
if ( _.isString( _key_ ) && _key_.indexOf('font_family') > -1 ) {
|
|
if ( levelData.indexOf('gfont') > -1 && !_.contains( gfonts, levelData ) ) {
|
|
gfonts.push( levelData );
|
|
}
|
|
}
|
|
|
|
if ( _.isArray( levelData ) || _.isObject( levelData ) ) {
|
|
_snifff_( levelData );
|
|
}
|
|
});
|
|
};
|
|
if ( _.isArray( _data_ ) || _.isObject( _data_ ) ) {
|
|
_snifff_( _data_ );
|
|
}
|
|
return gfonts;
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// @return a normalized and sanitized item value
|
|
// What does this helper do ?
|
|
// 1) remove title and id properties for non multi-items modules, we don't need those properties in db
|
|
// 2) don't write if is equal to default
|
|
// @param params {
|
|
// item_value : rawModuleValue,
|
|
// parent_module_type : parentModuleType,
|
|
// is_multi_items : false
|
|
// }
|
|
normalizeAndSanitizeSingleItemInputValues : function( params ) {
|
|
var itemNormalized = {},
|
|
itemNormalizedAndSanitized = {},
|
|
inputDefaultValue = null,
|
|
inputType = null,
|
|
sanitizedVal,
|
|
self = this,
|
|
isEqualToDefault = function( _val, _default ) {
|
|
var equal = false;
|
|
if ( _.isBoolean( _val ) || _.isBoolean( _default ) ) {
|
|
equal = Boolean(_val) === Boolean(_default);
|
|
} else if ( _.isNumber( _val ) || _.isNumber( _default ) ) {
|
|
equal = Number( _val ) === Number( _default );
|
|
} else if ( _.isString( _val ) || _.isString( _default ) ) {
|
|
equal = _val+'' === _default+'';
|
|
} else if ( _.isObject( _val ) && _.isObject( _default ) ) {
|
|
equal = _.isEqual( _val,_default );
|
|
} else if ( _.isArray( _val ) && _.isArray( _default ) ) {
|
|
//@see https://stackoverflow.com/questions/39517316/check-for-equality-between-two-array
|
|
equal = JSON.stringify(_val.sort()) === JSON.stringify(_default.sort());
|
|
} else {
|
|
equal = _val === _default;
|
|
}
|
|
return equal;
|
|
};
|
|
|
|
// NORMALIZE
|
|
// title, id are always included in the defaultItemModel
|
|
// title and id are legacy entries that can be used in multi-items modules to identify and name the item
|
|
// we need the id to target each item when generating the CSS => @see https://github.com/presscustomizr/nimble-builder/issues/78
|
|
// For non multi-items modules, those properties don't need to be saved in database
|
|
// @see ::getDefaultItemModelFromRegisteredModuleData()
|
|
_.each( params.item_value, function( _val, input_id ) {
|
|
if ( 'title' === input_id )
|
|
return;
|
|
if ( !params.is_multi_items && 'id' === input_id )
|
|
return;
|
|
|
|
if ( null !== params.parent_module_type ) {
|
|
// Skip if the key is an "id" => specific to multi-item module, for which we have an id added in the js api and not registered in php.
|
|
if ( 'id' !== input_id ) {
|
|
inputDefaultValue = self.getInputDefaultValue( input_id, params.parent_module_type );
|
|
if ( 'no_default_value_specified' === inputDefaultValue ) {
|
|
api.infoLog( '::normalizeAndSanitizeSingleItemInputValues => missing default value for input ' + input_id + ' in module ' + params.parent_module_type );
|
|
}
|
|
}
|
|
}
|
|
if ( isEqualToDefault( _val, inputDefaultValue ) ) {
|
|
return;
|
|
// When the value is a string of an object, no need to write an empty value
|
|
} else if ( ( _.isString( _val ) || _.isObject( _val ) ) && _.isEmpty( _val ) ) {
|
|
return;
|
|
} else {
|
|
itemNormalized[ input_id ] = _val;
|
|
}
|
|
});
|
|
|
|
|
|
// SANITIZE
|
|
_.each( itemNormalized, function( _val, input_id ) {
|
|
// @see extend_api_base.js
|
|
// @see sektions::_7_0_sektions_add_inputs_to_api.js
|
|
switch( self.getInputType( input_id, params.parent_module_type ) ) {
|
|
case 'text' :
|
|
case 'textarea' :
|
|
case 'check' :
|
|
case 'gutencheck' :
|
|
case 'select' :
|
|
case 'radio' :
|
|
case 'number' :
|
|
case 'upload' :
|
|
case 'upload_url' :
|
|
case 'color' :
|
|
case 'wp_color_alpha' :
|
|
case 'wp_color' :
|
|
case 'content_picker' :
|
|
case 'detached_tinymce_editor' :
|
|
case 'nimble_tinymce_editor' :
|
|
case 'password' :
|
|
case 'range' :
|
|
case 'range_slider' :
|
|
case 'hidden' :
|
|
case 'h_alignment' :
|
|
case 'h_text_alignment' :
|
|
|
|
case 'spacing' :
|
|
case 'bg_position' :
|
|
case 'v_alignment' :
|
|
case 'font_size' :
|
|
case 'line_height' :
|
|
case 'font_picker' :
|
|
sanitizedVal = _val;
|
|
break;
|
|
default :
|
|
sanitizedVal = _val;
|
|
break;
|
|
}
|
|
|
|
itemNormalizedAndSanitized[ input_id ] = sanitizedVal;
|
|
});
|
|
return itemNormalizedAndSanitized;
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Is the UI currently displayed the one that is being requested ?
|
|
// If so, don't generate the ui again
|
|
// @return bool
|
|
isUIControlAlreadyRegistered : function( uiElementId ) {
|
|
var self = this,
|
|
uiCandidate = _.filter( self.registered(), function( registered ) {
|
|
return registered.id == uiElementId && 'control' === registered.what;
|
|
}),
|
|
controlIsAlreadyRegistered = false;
|
|
|
|
// If the control is not been tracked in our self.registered(), let's check if it is registered in the api
|
|
// Typically, the module / section picker will match that case, because we don't keep track of it ( so it's not cleaned )
|
|
if ( _.isEmpty( uiCandidate ) ) {
|
|
controlIsAlreadyRegistered = api.control.has( uiElementId );
|
|
} else {
|
|
controlIsAlreadyRegistered = true;
|
|
// we should have only one uiCandidate with this very id
|
|
if ( uiCandidate.length > 1 ) {
|
|
api.errare( 'isUIControlAlreadyRegistered => why is this control registered more than once ? => ' + uiElementId );
|
|
}
|
|
}
|
|
return controlIsAlreadyRegistered;
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
* Gets a list of unique shortcodes or shortcode-look-alikes in the content.
|
|
*
|
|
* @param {string} content The content we want to scan for shortcodes.
|
|
*/
|
|
htmlIncludesElementsThatNeedAnAjaxRefresh : function( content ) {
|
|
if ( !_.isString( content ) )
|
|
return false;
|
|
|
|
content = content.replace(/\s+/g,'');//<= remove all spaces so that we can detect template tags and shortcodes that have spaces inside curly braces or bracket, like {{ the_tags }}
|
|
var shortcodes = content.match( /\[+([\w_-])+/g ),
|
|
tmpl_tags = content.match( /\{\{+([\w_-])+/g ),
|
|
// script detection introduced for https://github.com/presscustomizr/nimble-builder/issues/710
|
|
script_tags = content.match( /<script[\s\S]*?>[\s\S]*?<\/script>/gi ),
|
|
shortcode_result = [],
|
|
tmpl_tag_result = [];
|
|
|
|
if ( shortcodes ) {
|
|
for ( var i = 0; i < shortcodes.length; i++ ) {
|
|
var _shortcode = shortcodes[ i ].replace( /^\[+/g, '' );
|
|
|
|
if ( shortcode_result.indexOf( _shortcode ) === -1 ) {
|
|
shortcode_result.push( _shortcode );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( tmpl_tags ) {
|
|
for ( var j = 0; j < tmpl_tags.length; j++ ) {
|
|
var _tag = tmpl_tags[ j ].replace( /^\[+/g, '' );
|
|
|
|
if ( tmpl_tag_result.indexOf( _tag ) === -1 ) {
|
|
tmpl_tag_result.push( _tag );
|
|
}
|
|
}
|
|
}
|
|
return !_.isEmpty( shortcode_result ) || !_.isEmpty( tmpl_tag_result ) || !_.isEmpty( script_tags );
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// @params = {
|
|
// action : 'sek-generate-module-ui' / 'sek-generate-level-options-ui'
|
|
// level : params.level,
|
|
// id : params.id,
|
|
// in_sektion : params.in_sektion,
|
|
// in_column : params.in_column,
|
|
// options : params.options || []
|
|
// }
|
|
// @dfd = $.Deferred()
|
|
// @return the state promise dfd
|
|
generateUIforDraggableContent : function( params, dfd ) {
|
|
var self = this;
|
|
// Prepare the module map to register
|
|
var registrationParams = {};
|
|
|
|
|
|
$.extend( registrationParams, {
|
|
// The content type switcher has a priority lower than the other so it's printed on top
|
|
// it's loaded last, because it needs to know the existence of all other
|
|
sek_content_type_switcher_module : {
|
|
settingControlId : sektionsLocalizedData.prefixForSettingsNotSaved + '_sek_content_type_switcher_ui',
|
|
module_type : 'sek_content_type_switcher_module',
|
|
controlLabel : self.getRegisteredModuleProperty( 'sek_content_type_switcher_module', 'name' ),//sektionsLocalizedData.i18n['Select a content type'],
|
|
priority : 10,
|
|
settingValue : { content_type : params.content_type }
|
|
//icon : '<i class="material-icons sek-level-option-icon">center_focus_weak</i>'
|
|
},
|
|
sek_module_picker_module : {
|
|
settingControlId : sektionsLocalizedData.prefixForSettingsNotSaved + '_sek_draggable_modules_ui',
|
|
module_type : 'sek_module_picker_module',
|
|
controlLabel : self.getRegisteredModuleProperty( 'sek_module_picker_module', 'name' ),//sektionsLocalizedData.i18n['Pick a module'],
|
|
content_type : 'module',
|
|
priority : 20,
|
|
icon : '<i class="fas fa-grip-vertical sek-level-option-icon"></i>'
|
|
},
|
|
|
|
// June 2020 for : https://github.com/presscustomizr/nimble-builder/issues/520
|
|
// and https://github.com/presscustomizr/nimble-builder/issues/713
|
|
sek_my_sections_sec_picker_module : {
|
|
settingControlId : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid() + '_sek_draggable_sections_ui',
|
|
module_type : 'sek_my_sections_sec_picker_module',
|
|
controlLabel : self.getRegisteredModuleProperty( 'sek_my_sections_sec_picker_module', 'name' ),//sektionsLocalizedData.i18n['My sections'],
|
|
content_type : 'section',
|
|
expandAndFocusOnInit : false,
|
|
priority : 10,
|
|
icon : '<i class="fas fa-grip-vertical sek-level-option-icon"></i>',
|
|
is_new : false
|
|
},
|
|
});//
|
|
|
|
|
|
|
|
// Register prebuild sections modules
|
|
// declared server side in inc/sektions/_front_dev_php/_constants_and_helper_functions/0_5_2_sektions_local_sektion_data.php
|
|
// to understand how those sections are registered server side, see @see https://github.com/presscustomizr/nimble-builder/issues/713
|
|
_.each([
|
|
'sek_intro_sec_picker_module',
|
|
'sek_features_sec_picker_module',
|
|
'sek_post_grids_sec_picker_module',
|
|
'sek_about_sec_picker_module',
|
|
'sek_contact_sec_picker_module',
|
|
'sek_team_sec_picker_module',
|
|
'sek_column_layouts_sec_picker_module',
|
|
'sek_header_sec_picker_module',
|
|
'sek_footer_sec_picker_module'
|
|
], function( mod_type, key ) {
|
|
registrationParams[mod_type] = {
|
|
settingControlId : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid() + '_sek_draggable_sections_ui',
|
|
module_type : mod_type,
|
|
controlLabel : self.getRegisteredModuleProperty( mod_type, 'name' ),
|
|
content_type : 'section',
|
|
expandAndFocusOnInit : 0 === key,//<= first section group is expanded on start
|
|
priority : 30,
|
|
icon : '<i class="fas fa-grip-vertical sek-level-option-icon"></i>',
|
|
is_new : 'sek_post_grids_sec_picker_module' === mod_type
|
|
};
|
|
});
|
|
|
|
|
|
//$.extend( registrationParams, { });
|
|
|
|
// Beta features to merge here ?
|
|
// if ( sektionsLocalizedData.areBetaFeaturesEnabled ) {
|
|
// $.extend( registrationParams, {});
|
|
// }
|
|
|
|
|
|
// BAIL WITH A SEE-ME ANIMATION IF THIS UI IS CURRENTLY BEING DISPLAYED
|
|
// Is the UI currently displayed the one that is being requested ?
|
|
// If so :
|
|
// 1) visually remind the user that a module should be dragged
|
|
// 2) pass the content_type param to display the requested content_type
|
|
var firstKey = _.keys( registrationParams )[0],
|
|
firstControlId = registrationParams[firstKey].settingControlId;
|
|
|
|
if ( self.isUIControlAlreadyRegistered( firstControlId ) ) {
|
|
api.control( firstControlId, function( _control_ ) {
|
|
_control_.focus({
|
|
completeCallback : function() {
|
|
var $container = _control_.container;
|
|
// @use button-see-mee css class declared in core in /wp-admin/css/customize-controls.css
|
|
if ( $container.hasClass( 'button-see-me') )
|
|
return;
|
|
$container.addClass('button-see-me');
|
|
_.delay( function() {
|
|
$container.removeClass('button-see-me');
|
|
}, 800 );
|
|
}
|
|
});
|
|
});
|
|
|
|
return dfd;
|
|
}//if
|
|
|
|
|
|
// @return void()
|
|
_do_register_ = function() {
|
|
_.each( registrationParams, function( optionData, optionType ){
|
|
if ( ! api.has( optionData.settingControlId ) ) {
|
|
// synchronize the module setting with the main collection setting
|
|
api( optionData.settingControlId, function( _setting_ ) {
|
|
_setting_.bind( function( to, from ) {
|
|
api.errare('generateUIforDraggableContent => the setting() should not changed');
|
|
});
|
|
});
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'setting',
|
|
id : optionData.settingControlId,
|
|
dirty : false,
|
|
value : optionData.settingValue || {},
|
|
transport : 'postMessage',// 'refresh',
|
|
type : '_nimble_ui_'//will be dynamically registered but not saved in db as option// columnData.settingType
|
|
});
|
|
}
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'control',
|
|
id : optionData.settingControlId,
|
|
label : optionData.controlLabel,
|
|
type : 'czr_module',//sekData.controlType,
|
|
module_type : optionData.module_type,
|
|
section : self.SECTION_ID_FOR_CONTENT_PICKER,
|
|
priority : optionData.priority || 10,
|
|
settings : { default : optionData.settingControlId },
|
|
track : false//don't register in the self.registered() => this will prevent this container to be removed when cleaning the registered
|
|
}).done( function() {
|
|
api.control( optionData.settingControlId, function( _control_ ) {
|
|
// set the control type property
|
|
_control_.content_type = optionData.content_type;//<= used to handle visibility when switching content type with the "content_type_switcher" control
|
|
|
|
// we set the focus to false when firing api.previewer.trigger( 'sek-pick-content', { focus : false }); in ::initialize()
|
|
if ( true === params.focus ) {
|
|
_control_.focus({
|
|
completeCallback : function() {}
|
|
});
|
|
}
|
|
|
|
var $title = _control_.container.find('label > .customize-control-title'),
|
|
_titleContent = $title.html();
|
|
|
|
// We wrap the original text content in this span.sek-ctrl-accordion-title in order to style it (underlined) independently ( without styling the icons next to it )
|
|
$title.html( ['<span class="sek-ctrl-accordion-title">', _titleContent, '</span>' ].join('') );
|
|
|
|
if ( optionData.is_new ) {
|
|
var _titleHtml = $title.html();
|
|
$title.html( _titleHtml + ' <span class="sek-new-label">New!</span>' );
|
|
}
|
|
// if this level has an icon, let's prepend it to the title
|
|
if ( ! _.isUndefined( optionData.icon ) ) {
|
|
$title.addClass('sek-flex-vertical-center').prepend( optionData.icon );
|
|
}
|
|
|
|
// ACCORDION
|
|
// Setup the accordion only for section content type
|
|
if ( 'section' === _control_.content_type ) {
|
|
// Hide the item wrapper
|
|
// @see css
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
// prepend the animated arrow
|
|
$title.prepend('<span class="sek-animated-arrow" data-name="icon-chevron-down"><span class="fa fa-chevron-down"></span></span>');
|
|
// setup the initial state + initial click
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
if ( true === optionData.expandAndFocusOnInit && "false" == _control_.container.attr('data-sek-expanded' ) ) {
|
|
//_control_.container.find('.czr-items-wrapper').show();
|
|
//$title.trigger('click');
|
|
_control_.container.addClass('sek-expand-on-init');
|
|
}
|
|
} else {
|
|
_control_.container.attr('data-sek-accordion', 'no');
|
|
}
|
|
|
|
});
|
|
});
|
|
});//_.each
|
|
|
|
api.trigger('nimble-modules-and-sections-controls-registered');
|
|
};//_do_register_
|
|
|
|
|
|
// the self.SECTION_ID_FOR_CONTENT_PICKER section is registered on initialize
|
|
// @fixes https://github.com/presscustomizr/nimble-builder/issues/187
|
|
api.section( self.SECTION_ID_FOR_CONTENT_PICKER, function( _section_ ) {
|
|
_do_register_();
|
|
|
|
// Style the section title
|
|
var $sectionTitleEl = _section_.container.find('.accordion-section-title'),
|
|
$panelTitleEl = _section_.container.find('.customize-section-title h3');
|
|
|
|
// The default title looks like this : Title <span class="screen-reader-text">Press return or enter to open this section</span>
|
|
if ( 0 < $sectionTitleEl.length && $sectionTitleEl.find('.sek-level-option-icon').length < 1 ) {
|
|
$sectionTitleEl.prepend( '<i class="fas fa-grip-vertical sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// The default title looks like this : <span class="customize-action">Customizing</span> Title
|
|
if ( 0 < $panelTitleEl.length && $panelTitleEl.find('.sek-level-option-icon').length < 1 ) {
|
|
$panelTitleEl.find('.customize-action').after( '<i class="fas fa-grip-vertical sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// Schedule the accordion behaviour
|
|
self.scheduleModuleAccordion.call( _section_, { expand_first_control : false } );
|
|
_section_.container.find('.customize-control.sek-expand-on-init').find('label > .customize-control-title').trigger('click');
|
|
// Fetch the presetSectionCollection from the server now, so we save a few milliseconds when injecting the first preset_section
|
|
// it populates api.nimble_ApiSections
|
|
//
|
|
// updated in v1.7.5, may 21st : performance improvements on customizer load
|
|
// inserting preset sections is not on all Nimble sessions => let's only fetch when user inserts the first section
|
|
// self._getApiSingleSectionData();
|
|
});
|
|
return dfd;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// @params = {
|
|
// action : 'sek-generate-module-ui' / 'sek-generate-level-options-ui'
|
|
// level : params.level,
|
|
// id : params.id,
|
|
// in_sektion : params.in_sektion,
|
|
// in_column : params.in_column,
|
|
// options : params.options || []
|
|
// }
|
|
// @dfd = $.Deferred()
|
|
// @return the state promise dfd
|
|
generateUIforFrontModules : function( params, dfd ) {
|
|
var self = this;
|
|
if ( _.isEmpty( params.id ) ) {
|
|
dfd.reject( 'generateUI => missing id' );
|
|
}
|
|
|
|
// For modules, we need to generate a UI for the module value
|
|
var moduleValue = self.getLevelProperty({
|
|
property : 'value',
|
|
id : params.id
|
|
});
|
|
|
|
var moduleType = self.getLevelProperty({
|
|
property : 'module_type',
|
|
id : params.id
|
|
});
|
|
|
|
var moduleName = self.getRegisteredModuleProperty( moduleType, 'name' );
|
|
|
|
if ( _.isEmpty( moduleType ) ) {
|
|
dfd.reject( 'generateUI => module => invalid module_type' );
|
|
}
|
|
|
|
// Prepare the module map to register
|
|
var modulesRegistrationParams = {};
|
|
|
|
if ( true === self.getRegisteredModuleProperty( moduleType, 'is_father' ) ) {
|
|
var _childModules_ = self.getRegisteredModuleProperty( moduleType, 'children' );
|
|
if ( _.isEmpty( _childModules_ ) ) {
|
|
throw new Error('::generateUIforFrontModules => a father module ' + moduleType + ' is missing children modules ');
|
|
} else {
|
|
_.each( _childModules_, function( mod_type, optionType ){
|
|
modulesRegistrationParams[ optionType ] = {
|
|
settingControlId : params.id + '__' + optionType,
|
|
module_type : mod_type,
|
|
controlLabel : self.getRegisteredModuleProperty( mod_type, 'name' )
|
|
//icon : '<i class="material-icons sek-level-option-icon">code</i>'
|
|
};
|
|
});
|
|
}
|
|
} else {
|
|
modulesRegistrationParams.__no_option_group_to_be_updated_by_children_modules__ = {
|
|
settingControlId : params.id,
|
|
module_type : moduleType,
|
|
controlLabel : moduleName
|
|
//icon : '<i class="material-icons sek-level-option-icon">code</i>'
|
|
};
|
|
}
|
|
|
|
// BAIL WITH A SEE-ME ANIMATION IF THIS UI IS CURRENTLY BEING DISPLAYED
|
|
// Is the UI currently displayed the one that is being requested ?
|
|
// Check if the first control of the list is already registered
|
|
// If so, visually remind the user and break;
|
|
var firstKey = _.keys( modulesRegistrationParams )[0],
|
|
firstControlId = modulesRegistrationParams[firstKey].settingControlId;
|
|
|
|
if ( self.isUIControlAlreadyRegistered( firstControlId ) ) {
|
|
api.control( firstControlId ).focus({
|
|
completeCallback : function() {
|
|
var $container = api.control( firstControlId ).container;
|
|
// @use button-see-mee css class declared in core in /wp-admin/css/customize-controls.css
|
|
if ( $container.hasClass( 'button-see-me') )
|
|
return;
|
|
$container.addClass('button-see-me');
|
|
_.delay( function() {
|
|
$container.removeClass('button-see-me');
|
|
}, 800 );
|
|
}
|
|
});
|
|
return dfd;
|
|
}//if
|
|
|
|
// Clean previously generated UI elements
|
|
self.cleanRegisteredAndLargeSelectInput();
|
|
|
|
_do_register_ = function() {
|
|
_.each( modulesRegistrationParams, function( optionData, optionType ){
|
|
// Make sure this setting is bound only once !
|
|
if ( ! api.has( optionData.settingControlId ) ) {
|
|
var doUpdate = function( to, from, args ) {
|
|
try { self.updateAPISettingAndExecutePreviewActions({
|
|
defaultPreviewAction : 'refresh_markup',
|
|
uiParams : _.extend( params, { action : 'sek-set-module-value' } ),
|
|
options_type : optionType,
|
|
settingParams : {
|
|
to : to,
|
|
from : from,
|
|
args : args
|
|
}
|
|
}); } catch( er ) {
|
|
api.errare( '::generateUIforFrontModules => Error in updateAPISettingAndExecutePreviewActions', er );
|
|
}
|
|
};
|
|
|
|
// Schedule the binding to synchronize the module setting with the main collection setting
|
|
// Note 1 : unlike control or sections, the setting are not getting cleaned up on each ui generation.
|
|
// They need to be kept in order to keep track of the changes in the customizer.
|
|
// => that's why we check if ! api.has( ... )
|
|
api( optionData.settingControlId, function( _setting_ ) {
|
|
_setting_.bind( _.debounce( doUpdate, self.SETTING_UPDATE_BUFFER ) );//_setting_.bind( _.debounce( function( to, from, args ) {}
|
|
});
|
|
|
|
var settingValueOnRegistration = $.extend( true, {}, moduleValue );
|
|
if ( '__no_option_group_to_be_updated_by_children_modules__' !== optionType ) {
|
|
settingValueOnRegistration = ( !_.isEmpty( settingValueOnRegistration ) && _.isObject( settingValueOnRegistration ) && _.isObject( settingValueOnRegistration[optionType] ) ) ? settingValueOnRegistration[optionType] : {};
|
|
}
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'setting',
|
|
id : optionData.settingControlId,
|
|
dirty : false,
|
|
value : settingValueOnRegistration,
|
|
transport : 'postMessage',// 'refresh',
|
|
type : '_nimble_ui_'//will be dynamically registered but not saved in db as option// columnData.settingType
|
|
});
|
|
}//if ( ! api.has( optionData.settingControlId ) )
|
|
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'control',
|
|
module_id : params.id,// <= the id of the corresponding module level as saved in DB. Can be needed, @see image slider module
|
|
id : optionData.settingControlId,
|
|
label : optionData.controlLabel,
|
|
//label : sektionsLocalizedData.i18n['Customize the options for module :'] + ' ' + optionData.controlLabel,
|
|
type : 'czr_module',//sekData.controlType,
|
|
module_type : optionData.module_type,
|
|
section : params.id,
|
|
priority : 20,
|
|
settings : { default : optionData.settingControlId }
|
|
}).done( function() {});
|
|
|
|
// Implement the animated arrow markup, and the initial state of the module visibility
|
|
api.control( optionData.settingControlId, function( _control_ ) {
|
|
api.control( optionData.settingControlId ).focus({
|
|
completeCallback : function() {}
|
|
});
|
|
|
|
// Hide the item wrapper
|
|
// @see css
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
|
|
var $title = _control_.container.find('label > .customize-control-title'),
|
|
// store the title text in a var + decode html entities ( added by WP )
|
|
// @see https://stackoverflow.com/questions/1147359/how-to-decode-html-entities-using-jquery
|
|
_titleContent = $("<div/>").html( $title.html() ).text();
|
|
|
|
$title.html( ['<span class="sek-ctrl-accordion-title">', _titleContent , '</span>' ].join('') );
|
|
// if this level has an icon, let's prepend it to the title
|
|
if ( ! _.isUndefined( optionData.icon ) ) {
|
|
$title.addClass('sek-flex-vertical-center').prepend( optionData.icon );
|
|
}
|
|
// prepend the animated arrow
|
|
$title.prepend('<span class="sek-animated-arrow" data-name="icon-chevron-down"><span class="fa fa-chevron-down"></span></span>');
|
|
// setup the initial state + initial click
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
});
|
|
});//each()
|
|
};//_do_register()
|
|
|
|
|
|
// Defer the registration when the parent section gets added to the api
|
|
api.section( params.id, function( _section_ ) {
|
|
api.section(params.id).focus();
|
|
// Generate the UI for module option switcher
|
|
// introduded in july 2019 for https://github.com/presscustomizr/nimble-builder/issues/135
|
|
self.generateModuleOptionSwitcherUI( params.id, params.action );
|
|
_do_register_();
|
|
|
|
// don't display the clickable section title in the nimble root panel
|
|
_section_.container.find('.accordion-section-title').first().hide();
|
|
|
|
// Style the section title
|
|
var $panelTitleEl = _section_.container.find('.customize-section-title h3');
|
|
|
|
// The default title looks like this : <span class="customize-action">Customizing</span> Title
|
|
if ( 0 < $panelTitleEl.length ) {
|
|
$panelTitleEl.find('.customize-action').after( '<i class="fas fa-pencil-alt sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// Schedule the accordion behaviour
|
|
self.scheduleModuleAccordion.call( _section_, { expand_first_control : true } );
|
|
});
|
|
|
|
// Register the module content section
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
what : 'section',
|
|
id : params.id,
|
|
title: sektionsLocalizedData.i18n['Content for'] + ' ' + moduleName,
|
|
panel : sektionsLocalizedData.sektionsPanelId,
|
|
priority : 1000,
|
|
//track : false//don't register in the self.registered()
|
|
//constructWith : MainSectionConstructor,
|
|
}).done( function() {});
|
|
|
|
return dfd;
|
|
},
|
|
|
|
// Generate the UI for module option switcher
|
|
// introduded in july 2019 for https://github.com/presscustomizr/nimble-builder/issues/135
|
|
// REGISTER MODULE OPTION SWITCHER SETTING AND CONTROL
|
|
generateModuleOptionSwitcherUI : function( module_id, ui_action ) {
|
|
var setCtrlId = module_id + '__' + 'option_switcher';
|
|
|
|
if ( ! api.has( setCtrlId ) ) {
|
|
// synchronize the module setting with the main collection setting
|
|
api( setCtrlId, function( _setting_ ) {
|
|
_setting_.bind( function( to, from ) {
|
|
api.errare('generateUIforDraggableContent => the setting() should not changed');
|
|
});
|
|
});
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : 'module',
|
|
what : 'setting',
|
|
id : setCtrlId,
|
|
dirty : false,
|
|
value : '',
|
|
transport : 'postMessage',// 'refresh',
|
|
type : '_nimble_ui_'//will be dynamically registered but not saved in db as option// columnData.settingType
|
|
});
|
|
}
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : 'module',
|
|
what : 'control',
|
|
module_id : module_id,// <= the id of the corresponding module level as saved in DB
|
|
id : setCtrlId,
|
|
label : '',
|
|
type : 'czr_module',//sekData.controlType,
|
|
module_type : 'sek_mod_option_switcher_module',
|
|
section : module_id,
|
|
priority : 10,
|
|
settings : { default : setCtrlId },
|
|
has_accordion : false,
|
|
ui_action : ui_action // 'sek-generate-module-ui' or 'sek-generate-level-options-ui' // <= will be used to determine which button is selected
|
|
}).done( function() {
|
|
api.control( setCtrlId, function( _control_ ) {
|
|
_control_.deferred.embedded.done( function() {
|
|
// Hide the control label
|
|
_control_.container.find('.customize-control-title').hide();
|
|
// don't setup the accordion
|
|
_control_.container.attr('data-sek-accordion', 'no');
|
|
});
|
|
});
|
|
});
|
|
}//generateModuleOptionSwitcherUI
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// @params = {
|
|
// action : 'sek-generate-module-ui' / 'sek-generate-level-options-ui'
|
|
// level : params.level,
|
|
// id : params.id,
|
|
// in_sektion : params.in_sektion,
|
|
// in_column : params.in_column,
|
|
// options : params.options || []
|
|
// }
|
|
// @dfd = $.Deferred()
|
|
// @return the state promise dfd
|
|
generateUIforLevelOptions : function( params, dfd ) {
|
|
var self = this;
|
|
// Get this level options
|
|
var levelOptionValues = self.getLevelProperty({
|
|
property : 'options',
|
|
id : params.id
|
|
});
|
|
|
|
levelOptionValues = _.isObject( levelOptionValues ) ? levelOptionValues : {};
|
|
|
|
// Prepare the module map to register
|
|
var modulesRegistrationParams = {};
|
|
|
|
// June 2020 : introduced for https://github.com/presscustomizr/nimble-builder-pro/issues/6
|
|
// so we can remotely register modules
|
|
api.trigger('nb_setup_level_ui_registration_params', {
|
|
params : params,
|
|
modulesRegistrationParams : modulesRegistrationParams
|
|
});
|
|
|
|
$.extend( modulesRegistrationParams, {
|
|
bg : {
|
|
settingControlId : params.id + '__bg_options',
|
|
module_type : 'sek_level_bg_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Background settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
expandAndFocusOnInit : true,
|
|
icon : '<i class="material-icons sek-level-option-icon">gradient</i>'//'<i class="material-icons sek-level-option-icon">brush</i>'
|
|
}
|
|
});
|
|
|
|
// implemented for https://github.com/presscustomizr/nimble-builder/issues/504
|
|
if ( 'section' === params.level ) {
|
|
$.extend( modulesRegistrationParams, {
|
|
level_text : {
|
|
settingControlId : params.id + '__text_options',
|
|
module_type : 'sek_level_text_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Text settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
expandAndFocusOnInit : true,
|
|
icon : '<i class="material-icons sek-level-option-icon">text_format</i>'//'<i class="material-icons sek-level-option-icon">brush</i>'
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
$.extend( modulesRegistrationParams, {
|
|
border : {
|
|
settingControlId : params.id + '__border_options',
|
|
module_type : 'sek_level_border_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Borders settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
//expandAndFocusOnInit : true,
|
|
icon : '<i class="material-icons sek-level-option-icon">rounded_corner</i>'//'<i class="material-icons sek-level-option-icon">brush</i>'
|
|
},
|
|
spacing : {
|
|
settingControlId : params.id + '__spacing_options',
|
|
module_type : 'column' === params.level ? 'sek_level_spacing_module_for_columns' : 'sek_level_spacing_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Padding and margin settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="material-icons sek-level-option-icon">center_focus_weak</i>'
|
|
},
|
|
anchor : {
|
|
settingControlId : params.id + '__anchor_options',
|
|
module_type : 'sek_level_anchor_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Custom anchor ( CSS ID ) and CSS classes for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="fas fa-anchor sek-level-option-icon"></i>'
|
|
},
|
|
visibility : {
|
|
settingControlId : params.id + '__visibility_options',
|
|
module_type : 'sek_level_visibility_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Device visibility settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="far fa-eye sek-level-option-icon"></i>'
|
|
},
|
|
height : {
|
|
settingControlId : params.id + '__height_options',
|
|
module_type : 'sek_level_height_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Height, vertical alignment, z-index for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="fas fa-ruler-vertical sek-level-option-icon"></i>'
|
|
},
|
|
});
|
|
|
|
if ( sektionsLocalizedData.isUpsellEnabled || sektionsLocalizedData.isPro ) {
|
|
$.extend( modulesRegistrationParams, {
|
|
animation : {
|
|
settingControlId : params.id + '__animate_options',
|
|
module_type : 'sek_level_animation_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Animation settings for the']+ ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="material-icons sek-level-option-icon">movie_filter</i>',
|
|
isPro : true
|
|
}
|
|
});
|
|
}
|
|
|
|
if ( 'section' === params.level ) {
|
|
$.extend( modulesRegistrationParams, {
|
|
width : {
|
|
settingControlId : params.id + '__width_options',
|
|
module_type : 'sek_level_width_section',
|
|
controlLabel : sektionsLocalizedData.i18n['Width settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="fas fa-ruler-horizontal sek-level-option-icon"></i>'
|
|
}
|
|
});
|
|
// Deactivated
|
|
// => replaced by sek_level_width_section
|
|
// $.extend( modulesRegistrationParams, {
|
|
// layout : {
|
|
// settingControlId : params.id + '__sectionLayout_options',
|
|
// module_type : 'sek_level_section_layout_module',
|
|
// controlLabel : sektionsLocalizedData.i18n['Layout settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
// icon : '<i class="material-icons sek-level-option-icon">crop_din</i>'
|
|
// }
|
|
// });
|
|
// Pro icon
|
|
$.extend( modulesRegistrationParams, {
|
|
breakpoint : {
|
|
settingControlId : params.id + '__breakpoint_options',
|
|
module_type : 'sek_level_breakpoint_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Responsive settings : breakpoint, column direction'],
|
|
icon : '<i class="material-icons sek-level-option-icon">devices</i>'
|
|
}
|
|
});
|
|
}
|
|
if ( 'column' === params.level ) {
|
|
$.extend( modulesRegistrationParams, {
|
|
width : {
|
|
settingControlId : params.id + '__width_options',
|
|
module_type : 'sek_level_width_column',
|
|
controlLabel : sektionsLocalizedData.i18n['Width settings for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="fas fa-ruler-horizontal sek-level-option-icon"></i>'
|
|
}
|
|
});
|
|
}
|
|
if ( 'module' === params.level ) {
|
|
$.extend( modulesRegistrationParams, {
|
|
width : {
|
|
settingControlId : params.id + '__width_options',
|
|
module_type : 'sek_level_width_module',
|
|
controlLabel : sektionsLocalizedData.i18n['Width and horizontal alignment for the'] + ' ' + sektionsLocalizedData.i18n[params.level],
|
|
icon : '<i class="fas fa-ruler-horizontal sek-level-option-icon"></i>'
|
|
}
|
|
});
|
|
}
|
|
if ( sektionsLocalizedData.isUpsellEnabled || sektionsLocalizedData.isPro ) {
|
|
$.extend( modulesRegistrationParams, {
|
|
level_cust_css : {
|
|
settingControlId : params.id + '__level_custom_css',
|
|
module_type : 'sek_level_cust_css_level',
|
|
controlLabel : sektionsLocalizedData.i18n['Custom CSS'],
|
|
icon : '<i class="material-icons sek-level-option-icon">code</i>',
|
|
isPro : true
|
|
}
|
|
});
|
|
}
|
|
|
|
// BAIL WITH A SEE-ME ANIMATION IF THIS UI IS CURRENTLY BEING DISPLAYED
|
|
// Is the UI currently displayed the one that is being requested ?
|
|
// Check if the first control of the list is already registered
|
|
// If so, visually remind the user and break;
|
|
var firstKey = _.keys( modulesRegistrationParams )[0],
|
|
firstControlId = modulesRegistrationParams[firstKey].settingControlId;
|
|
|
|
if ( self.isUIControlAlreadyRegistered( firstControlId ) ) {
|
|
api.control( firstControlId ).focus({
|
|
completeCallback : function() {
|
|
var $container = api.control( firstControlId ).container;
|
|
// @use button-see-mee css class declared in core in /wp-admin/css/customize-controls.css
|
|
if ( $container.hasClass( 'button-see-me') )
|
|
return;
|
|
$container.addClass('button-see-me');
|
|
_.delay( function() {
|
|
$container.removeClass('button-see-me');
|
|
}, 800 );
|
|
}
|
|
});
|
|
return dfd;
|
|
}//if
|
|
|
|
// Clean previously generated UI elements
|
|
self.cleanRegisteredAndLargeSelectInput();
|
|
|
|
|
|
// @return void()
|
|
_do_register_ = function() {
|
|
_.each( modulesRegistrationParams, function( optionData, optionType ){
|
|
// Is the UI currently displayed the one that is being requested ?
|
|
// If so, don't generate the ui again, simply focus on the section
|
|
if ( self.isUIControlAlreadyRegistered( optionData.settingControlId ) ) {
|
|
api.section( api.control( optionData.settingControlId ).section() ).expanded( true );
|
|
return;
|
|
}
|
|
if( ! api.has( optionData.settingControlId ) ) {
|
|
var doUpdate = function( to, from, args ) {
|
|
try { self.updateAPISettingAndExecutePreviewActions({
|
|
defaultPreviewAction : 'refresh_stylesheet',
|
|
uiParams : params,
|
|
options_type : optionType,// <= this is the options sub property where we will store this setting values. @see updateAPISetting case 'sek-generate-level-options-ui'
|
|
settingParams : {
|
|
to : to,
|
|
from : from,
|
|
args : args
|
|
}
|
|
}); } catch( er ) {
|
|
api.errare( '::_do_register_ => Error in updateAPISettingAndExecutePreviewActions', er );
|
|
}
|
|
};
|
|
|
|
// Schedule the binding to synchronize the options with the main collection setting
|
|
// Note 1 : unlike control or sections, the setting are not getting cleaned up on each ui generation.
|
|
// They need to be kept in order to keep track of the changes in the customizer.
|
|
// => that's why we check if ! api.has( ... )
|
|
api( optionData.settingControlId, function( _setting_ ) {
|
|
_setting_.bind( _.debounce( doUpdate, self.SETTING_UPDATE_BUFFER ) );//_setting_.bind( _.debounce( function( to, from, args ) {}
|
|
});//api( Id, function( _setting_ ) {})
|
|
|
|
// Let's add the starting values if provided when registrating the module
|
|
var initialModuleValues = levelOptionValues[ optionType ] || {};
|
|
var startingModuleValue = self.getModuleStartingValue( optionData.module_type );
|
|
if ( 'no_starting_value' !== startingModuleValue && _.isObject( startingModuleValue ) ) {
|
|
// make sure the starting values are deeped clone now, before being extended
|
|
var clonedStartingModuleValue = $.extend( true, {}, startingModuleValue );
|
|
initialModuleValues = $.extend( clonedStartingModuleValue, initialModuleValues );
|
|
}
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'setting',
|
|
id : optionData.settingControlId,
|
|
dirty : false,
|
|
value : initialModuleValues,
|
|
transport : 'postMessage',// 'refresh',
|
|
type : '_nimble_ui_'//will be dynamically registered but not saved in db as option //sekData.settingType
|
|
});
|
|
}//if( ! api.has( optionData.settingControlId ) ) {
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
level_id : params.id,
|
|
what : 'control',
|
|
id : optionData.settingControlId,
|
|
label : optionData.controlLabel,
|
|
type : 'czr_module',//sekData.controlType,
|
|
module_type : optionData.module_type,
|
|
section : params.id,
|
|
priority : 20,
|
|
settings : { default : optionData.settingControlId }
|
|
}).done( function() {});
|
|
|
|
// Implement the animated arrow markup, and the initial state of the module visibility
|
|
api.control( optionData.settingControlId, function( _control_ ) {
|
|
if ( true === optionData.expandAndFocusOnInit ) {
|
|
_control_.focus({
|
|
completeCallback : function() {}
|
|
});
|
|
}
|
|
|
|
// Hide the item wrapper
|
|
// @see css
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
|
|
var $title = _control_.container.find('label > .customize-control-title'),
|
|
_titleContent = $title.html();
|
|
// We wrap the original text content in this span.sek-ctrl-accordion-title in order to style it (underlined) independently ( without styling the icons next to it )
|
|
$title.html( ['<span class="sek-ctrl-accordion-title">', _titleContent, '</span>' ].join('') );
|
|
|
|
// if this level has an icon, let's prepend it to the title
|
|
if ( !_.isUndefined( optionData.icon ) ) {
|
|
$title.addClass('sek-flex-vertical-center').prepend( optionData.icon );
|
|
}
|
|
// prepend the animated arrow
|
|
$title.prepend('<span class="sek-animated-arrow" data-name="icon-chevron-down"><span class="fa fa-chevron-down"></span></span>');
|
|
|
|
// if this section is pro => add the icon
|
|
if ( optionData.isPro ) {
|
|
$title.append( [
|
|
'<img class="sek-pro-icon-next-title" src="',
|
|
sektionsLocalizedData.baseUrl,
|
|
'/assets/czr/sek/img/pro_orange.svg?ver=' + sektionsLocalizedData.nimbleVersion,
|
|
'"/>',
|
|
].join('') );
|
|
}
|
|
|
|
// setup the initial state + initial click
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
if ( true === optionData.expandAndFocusOnInit && "false" == _control_.container.attr('data-sek-expanded' ) ) {
|
|
$title.trigger('click');
|
|
}
|
|
});
|
|
});//_.each()
|
|
};//_do_register_()
|
|
|
|
// - Defer the registration when the parent section gets added to the api
|
|
// - Implement the module visibility
|
|
api.section( params.id, function( _section_ ) {
|
|
_do_register_();
|
|
// Generate the UI for module option switcher
|
|
// introduded in july 2019 for https://github.com/presscustomizr/nimble-builder/issues/135
|
|
if ( 'module' === params.level ) {
|
|
self.generateModuleOptionSwitcherUI( params.id, params.action );
|
|
}
|
|
|
|
// don't display the clickable section title in the nimble root panel
|
|
_section_.container.find('.accordion-section-title').first().hide();
|
|
|
|
// Style the section title
|
|
var $panelTitleEl = _section_.container.find('.customize-section-title h3');
|
|
|
|
// The default title looks like this : <span class="customize-action">Customizing</span> Title
|
|
if ( 0 < $panelTitleEl.length && $panelTitleEl.find('.sek-level-option-icon').length < 1 ) {
|
|
$panelTitleEl.find('.customize-action').after( '<i class="fas fa-sliders-h sek-level-option-icon"></i>' );
|
|
}
|
|
|
|
// Schedule the accordion behaviour
|
|
self.scheduleModuleAccordion.call( _section_, { expand_first_control : false } );
|
|
});
|
|
|
|
// Register the level settings section
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
what : 'section',
|
|
id : params.id,
|
|
title: sektionsLocalizedData.i18n['Settings for the'] + ' ' + params.level,
|
|
panel : sektionsLocalizedData.sektionsPanelId,
|
|
priority : 10,
|
|
//track : false//don't register in the self.registered()
|
|
//constructWith : MainSectionConstructor,
|
|
}).done( function() {});
|
|
|
|
return dfd;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
getLocalSkopeOptionId : function() {
|
|
var skope_id = api.czr_skopeBase.getSkopeProperty( 'skope_id' );
|
|
if ( _.isEmpty( skope_id ) ) {
|
|
api.errare( 'czr_sektions::getLocalSkopeOptionId => empty skope_id ');
|
|
return '';
|
|
}
|
|
return sektionsLocalizedData.prefixForSettingsNotSaved + skope_id + '__localSkopeOptions';
|
|
},
|
|
// @params = {
|
|
// action : 'sek-generate-module-ui' / 'sek-generate-level-options-ui'
|
|
// level : params.level,
|
|
// id : params.id,
|
|
// in_sektion : params.in_sektion,
|
|
// in_column : params.in_column,
|
|
// options : params.options || []
|
|
// }
|
|
// @dfd = $.Deferred()
|
|
// @return the state promise dfd
|
|
generateUIforLocalSkopeOptions : function( params, dfd ) {
|
|
var self = this,
|
|
_id_ = self.getLocalSkopeOptionId();
|
|
// Is the UI currently displayed the one that is being requested ?
|
|
// If so, visually remind the user that a module should be dragged
|
|
if ( self.isUIControlAlreadyRegistered( _id_ ) ) {
|
|
return dfd;
|
|
}
|
|
|
|
// Prepare the module map to register
|
|
self.localOptionsRegistrationParams = {};
|
|
if ( _.isUndefined( sektionsLocalizedData.localOptionsMap ) || ! _.isObject( sektionsLocalizedData.localOptionsMap ) ) {
|
|
api.errare( '::generateUIforGlobalOptions => missing or invalid localOptionsMap');
|
|
return dfd;
|
|
}
|
|
|
|
// remove settings when requested
|
|
// Happens when
|
|
// - importing a file
|
|
// - after a local reset
|
|
// - after a template injection
|
|
// - a history navigation action
|
|
if ( true === params.clean_settings_and_controls_first ) {
|
|
self.cleanRegisteredLocalOptionSettingsAndControls();
|
|
}
|
|
|
|
|
|
// Populate the registration params
|
|
_.each( sektionsLocalizedData.localOptionsMap, function( mod_type, opt_name ) {
|
|
switch( opt_name ) {
|
|
case 'template' :
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__template',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Page template'],
|
|
expandAndFocusOnInit : false,
|
|
icon : '<i class="material-icons sek-level-option-icon">check_box_outline_blank</i>'
|
|
};
|
|
break;
|
|
// Header and footer have been beta tested during 5 months and released in June 2019, in version 1.8.0
|
|
case 'local_header_footer':
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__local_header_footer',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Page header and footer'],
|
|
icon : '<i class="material-icons sek-level-option-icon">web</i>'
|
|
};
|
|
break;
|
|
case 'widths' :
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__widths',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Inner and outer widths'],
|
|
icon : '<i class="fas fa-ruler-horizontal sek-level-option-icon"></i>'
|
|
};
|
|
break;
|
|
case 'custom_css' :
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__custom_css',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Custom CSS'],
|
|
icon : '<i class="material-icons sek-level-option-icon">code</i>'
|
|
};
|
|
break;
|
|
case 'local_performances' :
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__local_performances',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Page speed optimizations'],
|
|
icon : '<i class="material-icons sek-level-option-icon">network_check</i>'
|
|
};
|
|
break;
|
|
case 'local_reset' :
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__local_reset',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Remove all sections and options of this page'],
|
|
icon : '<i class="material-icons sek-level-option-icon">delete</i>'
|
|
};
|
|
break;
|
|
case 'local_revisions' :
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__local_revisions',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Revision history of local sections'],
|
|
icon : '<i class="material-icons sek-level-option-icon">history</i>'
|
|
};
|
|
break;
|
|
case 'import_export' :
|
|
self.localOptionsRegistrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__local_imp_exp',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Export / Import'],
|
|
icon : '<i class="material-icons sek-level-option-icon">import_export</i>'
|
|
};
|
|
break;
|
|
default :
|
|
api.errare('::generateUIforLocalOptions => an option group could not be registered => ' + mod_type, opt_name );
|
|
break;
|
|
}//switch
|
|
});//_.each
|
|
|
|
// Get the current local options from the local setting value
|
|
// local setting value is structured this way :
|
|
// {
|
|
// collection : [],
|
|
// local_options : {},
|
|
// fonts : []
|
|
// }
|
|
// we only need the local_options here
|
|
var currentSetValue = api( self.localSectionsSettingId() )(),
|
|
currentAllLocalOptionsValue = $.extend( true, {}, _.isObject( currentSetValue.local_options ) ? currentSetValue.local_options : {} );
|
|
|
|
_do_register_ = function() {
|
|
_.each( self.localOptionsRegistrationParams, function( optionData, optionType ){
|
|
// Let's add the starting values if provided when registrating the module
|
|
var startingModuleValue = self.getModuleStartingValue( optionData.module_type ),
|
|
optionTypeValue = _.isObject( currentAllLocalOptionsValue[ optionType ] ) ? currentAllLocalOptionsValue[ optionType ]: {},
|
|
initialModuleValues = optionTypeValue;
|
|
|
|
// SETTING
|
|
if ( !api.has( optionData.settingControlId ) ) {
|
|
var doUpdate = function( to, from, args ) {
|
|
try { self.updateAPISettingAndExecutePreviewActions({
|
|
defaultPreviewAction : 'refresh_preview',
|
|
uiParams : params,
|
|
options_type : optionType,
|
|
settingParams : {
|
|
to : to,
|
|
from : from,
|
|
args : args
|
|
}
|
|
}); } catch( er ) {
|
|
api.errare( '::generateUIforLocalSkopeOptions => Error in updateAPISettingAndExecutePreviewActions', er );
|
|
}
|
|
};
|
|
|
|
// Schedule the binding to synchronize the options with the main collection setting
|
|
// Note 1 : unlike control or sections, the setting are not getting cleaned up on each ui generation.
|
|
// They need to be kept in order to keep track of the changes in the customizer.
|
|
// => that's why we check if ! api.has( ... )
|
|
api( optionData.settingControlId, function( _setting_ ) {
|
|
_setting_.bind( _.debounce( doUpdate, self.SETTING_UPDATE_BUFFER ) );//_setting_.bind( _.debounce( function( to, from, args ) {}
|
|
});//api( Id, function( _setting_ ) {})
|
|
|
|
|
|
|
|
if ( 'no_starting_value' !== startingModuleValue && _.isObject( startingModuleValue ) ) {
|
|
// make sure the starting values are deeped clone now, before being extended
|
|
var clonedStartingModuleValue = $.extend( true, {}, startingModuleValue );
|
|
initialModuleValues = $.extend( clonedStartingModuleValue, initialModuleValues );
|
|
}
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'setting',
|
|
id : optionData.settingControlId,
|
|
dirty : false,
|
|
value : initialModuleValues,
|
|
transport : 'postMessage',//'refresh',//// ,
|
|
type : '_nimble_ui_'//will be dynamically registered but not saved in db as option// columnData.settingType
|
|
});
|
|
}//if ( ! api.has( optionData.settingControlId ) )
|
|
|
|
|
|
// CONTROL
|
|
if ( !api.control.has( optionData.settingControlId ) ) {
|
|
api.CZR_Helpers.register({
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'control',
|
|
id : optionData.settingControlId,
|
|
label : optionData.controlLabel,
|
|
type : 'czr_module',//sekData.controlType,
|
|
module_type : optionData.module_type,
|
|
section : self.SECTION_ID_FOR_LOCAL_OPTIONS,
|
|
priority : 10,
|
|
settings : { default : optionData.settingControlId },
|
|
track : true//don't register in the self.registered() => this will prevent this container to be removed when cleaning the registered
|
|
}).done( function() {
|
|
|
|
// if ( true === optionData.expandAndFocusOnInit ) {
|
|
// api.control( optionData.settingControlId ).focus({
|
|
// completeCallback : function() {}
|
|
// });
|
|
// }
|
|
// Implement the animated arrow markup, and the initial state of the module visibility
|
|
api.control( optionData.settingControlId, function( _control_ ) {
|
|
// Hide the item wrapper
|
|
// @see css
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
var $title = _control_.container.find('label > .customize-control-title').first(),
|
|
_titleContent = $title.html();
|
|
// We wrap the original text content in this span.sek-ctrl-accordion-title in order to style it (underlined) independently ( without styling the icons next to it )
|
|
$title.html( ['<span class="sek-ctrl-accordion-title">', _titleContent, '</span>' ].join('') );
|
|
|
|
// if this level has an icon, let's prepend it to the title
|
|
if ( ! _.isUndefined( optionData.icon ) ) {
|
|
$title.addClass('sek-flex-vertical-center').prepend( optionData.icon );
|
|
}
|
|
// prepend the animated arrow
|
|
$title.prepend('<span class="sek-animated-arrow" data-name="icon-chevron-down"><span class="fa fa-chevron-down"></span></span>');
|
|
// setup the initial state + initial click
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
if ( true === optionData.expandAndFocusOnInit && "false" == _control_.container.attr('data-sek-expanded' ) ) {
|
|
$title.trigger('click');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});//_.each()
|
|
};//_do_register()
|
|
|
|
// The parent section has already been added in ::initialize()
|
|
_do_register_();
|
|
|
|
return dfd;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// @params = {
|
|
// action : 'sek-generate-module-ui' / 'sek-generate-level-options-ui'
|
|
// level : params.level,
|
|
// id : params.id,
|
|
// in_sektion : params.in_sektion,
|
|
// in_column : params.in_column,
|
|
// options : params.options || []
|
|
// }
|
|
// @dfd = $.Deferred()
|
|
// @return the state promise dfd
|
|
generateUIforGlobalOptions : function( params, dfd ) {
|
|
var self = this,
|
|
_id_ = sektionsLocalizedData.prefixForSettingsNotSaved + sektionsLocalizedData.optNameForGlobalOptions;
|
|
|
|
// Is the UI currently displayed the one that is being requested ?
|
|
// If so, visually remind the user that a module should be dragged
|
|
if ( self.isUIControlAlreadyRegistered( _id_ ) ) {
|
|
return dfd;
|
|
}
|
|
|
|
// Prepare the module map to register
|
|
var registrationParams = {};
|
|
if ( _.isUndefined( sektionsLocalizedData.globalOptionsMap ) || ! _.isObject( sektionsLocalizedData.globalOptionsMap ) ) {
|
|
api.errare( '::generateUIforGlobalOptions => missing or invalid globalOptionsMap');
|
|
return dfd;
|
|
}
|
|
|
|
// Populate the registration params
|
|
_.each( sektionsLocalizedData.globalOptionsMap, function( mod_type, opt_name ) {
|
|
switch( opt_name ) {
|
|
case 'site_templates' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__site_templates',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Site templates'],
|
|
icon : '<i class="material-icons sek-level-option-icon">devices</i>'
|
|
};
|
|
break;
|
|
// Header and footer have been beta tested during 5 months and released in June 2019, in version 1.8.0
|
|
case 'global_header_footer':
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__header_footer',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Site wide header and footer'],
|
|
icon : '<i class="material-icons sek-level-option-icon">web</i>'
|
|
};
|
|
break;
|
|
case 'global_text' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__global_text',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Global text options for Nimble sections'],
|
|
icon : '<i class="material-icons sek-level-option-icon">text_format</i>'
|
|
};
|
|
break;
|
|
case 'widths' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__widths',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Site wide inner and outer sections widths'],
|
|
icon : '<i class="fas fa-ruler-horizontal sek-level-option-icon"></i>'
|
|
};
|
|
break;
|
|
case 'breakpoint' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__breakpoint',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Site wide breakpoint for Nimble sections'],
|
|
expandAndFocusOnInit : false,
|
|
icon : '<i class="material-icons sek-level-option-icon">devices</i>'
|
|
};
|
|
break;
|
|
case 'performances' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__performances',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Site wide page speed optimizations'],
|
|
icon : '<i class="material-icons sek-level-option-icon">network_check</i>'
|
|
};
|
|
break;
|
|
case 'recaptcha' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__recaptcha',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Protect your contact forms with Google reCAPTCHA'],
|
|
icon : '<i class="material-icons sek-level-option-icon">security</i>'
|
|
};
|
|
break;
|
|
case 'global_revisions' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__global_revisions',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Revision history of global sections'],
|
|
icon : '<i class="material-icons sek-level-option-icon">history</i>'
|
|
};
|
|
break;
|
|
case 'global_imp_exp' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__global_imp_exp',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Export / Import global sections'],
|
|
icon : '<i class="material-icons sek-level-option-icon">import_export</i>'
|
|
};
|
|
break;
|
|
case 'global_reset' :
|
|
registrationParams[ opt_name ] = {
|
|
settingControlId : _id_ + '__global_reset',
|
|
module_type : mod_type,
|
|
controlLabel : sektionsLocalizedData.i18n['Remove the sections displayed in global locations'],
|
|
icon : '<i class="material-icons sek-level-option-icon">delete</i>'
|
|
};
|
|
break;
|
|
case 'beta_features' :
|
|
// may 2021 not rendered anymore
|
|
// registrationParams[ opt_name ] = {
|
|
// settingControlId : _id_ + '__beta_features',
|
|
// module_type : mod_type,
|
|
// controlLabel : sektionsLocalizedData.i18n['Beta features'],
|
|
// icon : '<i class="material-icons sek-level-option-icon">widgets</i>'
|
|
// };
|
|
break;
|
|
default :
|
|
api.errare('::generateUIforGlobalOptions => an option group could not be registered => ' + mod_type, opt_name );
|
|
break;
|
|
}//switch
|
|
});//_.each
|
|
|
|
// Let assign the global options to a var
|
|
var globalOptionDBValues = sektionsLocalizedData.globalOptionDBValues;
|
|
|
|
_do_register_ = function() {
|
|
_.each( registrationParams, function( optionData, optionType ){
|
|
if ( 'site_templates' === optionType ) {
|
|
var _doThingsAfterRefresh = function() {
|
|
// setTimeout( function() {
|
|
// api.control( optionData.settingControlId ).focus();
|
|
// }, 300 );
|
|
api.trigger('nimble-update-topbar-skope-status');
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 20000,
|
|
message : [
|
|
'<span style="">',
|
|
//'<strong>' + sektionsLocalizedData.i18n['Template saved'] + '</strong>',
|
|
sektionsLocalizedData.i18n['Refreshed to home page : site templates must be set when previewing home'],
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
api.previewer.unbind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
setTimeout( function() {
|
|
// This property is used to avoid the automatic focus on content picker when forcing preview on home while modifying site templates
|
|
api._nimbleRefreshingPreviewHomeWhenSettingSiteTemplate = false;
|
|
}, 1000);
|
|
};
|
|
}
|
|
|
|
if ( ! api.has( optionData.settingControlId ) ) {
|
|
var doUpdate = function( to, from, args ) {
|
|
try { self.updateAPISettingAndExecutePreviewActions({
|
|
isGlobalOptions : true,//<= indicates that we won't update the local skope setting id
|
|
defaultPreviewAction : 'refresh_preview',
|
|
uiParams : params,
|
|
options_type : optionType,
|
|
settingParams : {
|
|
to : to,
|
|
from : from,
|
|
args : args
|
|
}
|
|
}); } catch( er ) {
|
|
api.errare( '::generateUIforGlobalOptions => Error in updateAPISettingAndExecutePreviewActions', er );
|
|
}
|
|
};
|
|
|
|
// Schedule the binding to synchronize the options with the main collection setting
|
|
// Note 1 : unlike control or sections, the setting are not getting cleaned up on each ui generation.
|
|
// They need to be kept in order to keep track of the changes in the customizer.
|
|
// => that's why we check if ! api.has( ... )
|
|
api( optionData.settingControlId, function( _setting_ ) {
|
|
// SITE TEMPLATE STUFFS
|
|
// Added March 2021 for #478
|
|
// Force preview to home when modifying the site templates
|
|
if ( 'site_templates' === optionType ) {
|
|
_setting_.bind( function( to ) {
|
|
// This property is used to avoid the automatic focus on content picker when forcing preview on home while modifying site templates
|
|
api._nimbleRefreshingPreviewHomeWhenSettingSiteTemplate = true;//<= set to false in _doThingsAfterRefresh
|
|
api.previewer.bind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
api.previewer.previewUrl( api.settings.url.home );
|
|
api.trigger('nimble-update-topbar-skope-status');
|
|
});
|
|
}
|
|
|
|
_setting_.bind( _.debounce( doUpdate, self.SETTING_UPDATE_BUFFER ) );//_setting_.bind( _.debounce( function( to, from, args ) {}
|
|
});//api( Id, function( _setting_ ) {})
|
|
|
|
// Let's add the starting values if provided when registrating the module
|
|
var startingModuleValue = self.getModuleStartingValue( optionData.module_type ),
|
|
initialModuleValues = ( _.isObject( globalOptionDBValues ) && ! _.isEmpty( globalOptionDBValues[ optionType ] ) ) ? globalOptionDBValues[ optionType ] : {};
|
|
|
|
if ( 'no_starting_value' !== startingModuleValue && _.isObject( startingModuleValue ) ) {
|
|
// make sure the starting values are deeped clone now, before being extended
|
|
var clonedStartingModuleValue = $.extend( true, {}, startingModuleValue );
|
|
initialModuleValues = $.extend( clonedStartingModuleValue, initialModuleValues );
|
|
}
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'setting',
|
|
id : optionData.settingControlId,
|
|
dirty : false,
|
|
value : initialModuleValues,
|
|
transport : 'postMessage',//'refresh',//// ,
|
|
type : '_nimble_ui_'//will be dynamically registered but not saved in db as option// columnData.settingType
|
|
});
|
|
}
|
|
|
|
api.CZR_Helpers.register( {
|
|
origin : 'nimble',
|
|
level : params.level,
|
|
what : 'control',
|
|
id : optionData.settingControlId,
|
|
label : optionData.controlLabel,
|
|
type : 'czr_module',//sekData.controlType,
|
|
module_type : optionData.module_type,
|
|
section : self.SECTION_ID_FOR_GLOBAL_OPTIONS,//registered in ::initialize()
|
|
priority : 20,
|
|
settings : { default : optionData.settingControlId },
|
|
track : false//don't register in the self.registered() => this will prevent this container to be removed when cleaning the registered
|
|
}).done( function() {
|
|
// if ( true === optionData.expandAndFocusOnInit ) {
|
|
// api.control( optionData.settingControlId ).focus({
|
|
// completeCallback : function() {}
|
|
// });
|
|
// }
|
|
|
|
// Implement the animated arrow markup, and the initial state of the module visibility
|
|
api.control( optionData.settingControlId, function( _control_ ) {
|
|
// Hide the item wrapper
|
|
// @see css
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
var $title = _control_.container.find('label > .customize-control-title'),
|
|
_titleContent = $title.html();
|
|
// We wrap the original text content in this span.sek-ctrl-accordion-title in order to style it (underlined) independently ( without styling the icons next to it )
|
|
$title.html( [
|
|
'<span class="sek-ctrl-accordion-title">',
|
|
_titleContent,
|
|
//'site_templates' === optionType ? ' <span class="sek-new-label">New!</span>' : '',
|
|
'</span>'
|
|
].join('') );
|
|
|
|
// if this level has an icon, let's prepend it to the title
|
|
if ( ! _.isUndefined( optionData.icon ) ) {
|
|
$title.addClass('sek-flex-vertical-center').prepend( optionData.icon );
|
|
}
|
|
// prepend the animated arrow
|
|
$title.prepend('<span class="sek-animated-arrow" data-name="icon-chevron-down"><span class="fa fa-chevron-down"></span></span>');
|
|
// setup the initial state + initial click
|
|
_control_.container.attr('data-sek-expanded', "false" );
|
|
if ( true === optionData.expandAndFocusOnInit && "false" == _control_.container.attr('data-sek-expanded' ) ) {
|
|
$title.trigger('click');
|
|
}
|
|
|
|
if ( 'site_templates' === optionType ) {
|
|
_control_.container.one('click', '.customize-control-title', function() {
|
|
// This property is used to avoid the automatic focus on content picker when forcing preview on home while modifying site templates
|
|
api._nimbleRefreshingPreviewHomeWhenSettingSiteTemplate = true;//<= set to false in _doThingsAfterRefresh
|
|
api.previewer.bind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
api.previewer.previewUrl( api.settings.url.home );
|
|
api.trigger('nimble-update-topbar-skope-status');
|
|
});
|
|
}
|
|
|
|
});
|
|
});
|
|
});//_.each();
|
|
};//do register
|
|
|
|
// The parent section has already been added in ::initialize()
|
|
_do_register_();
|
|
|
|
return dfd;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData, serverControlParams
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// user action => this utility must be used to set the main setting value
|
|
// params = {
|
|
// action : 'sek-add-section', 'sek-add-column', 'sek-add-module',...
|
|
// in_sektion
|
|
// in_column
|
|
// }
|
|
updateAPISetting : function( params ) {
|
|
var self = this;
|
|
|
|
// Are we in global location ?
|
|
// Add the global information to the params
|
|
// => is used to determine the skope id when resolving the promise in reactToPreviewMsg
|
|
params = params || {};
|
|
params.is_global_location = 'global' === params.scope || self.isGlobalLocation( params );
|
|
|
|
var _collectionSettingId_ = params.is_global_location ? self.getGlobalSectionsSettingId() : self.localSectionsSettingId();
|
|
var currentSetValue = api( _collectionSettingId_ )();
|
|
|
|
// The following property is populated on each api setting update
|
|
// some properties are modified during the sub-callbacks of _updateSektionSettingInstanceWithAction
|
|
self.updAPISetParams = {
|
|
params : params,
|
|
promise : $.Deferred(),
|
|
newSetValue : _.isObject( currentSetValue ) ? $.extend( true, {}, currentSetValue ) : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ),
|
|
cloneId : '', // the cloneId is only needed in the duplication scenarii
|
|
sectionInjectPromise : '_not_injection_scenario_'//this property is turned into a $.Deferred() object in a scenario of section injection
|
|
};
|
|
|
|
// callback ran in api( _collectionSettingId_, function( sektionSetInstance ) {})
|
|
var _updateSektionSettingInstanceWithAction = function() {
|
|
// api( _collectionSettingId_)() = {
|
|
// collection : [
|
|
// 'loop_start' : { level : location, collection : [ 'sek124' : { collection : [], level : section, options : {} }], options : {}},
|
|
// 'loop_end' : { level : location, collection : [], options : {}}
|
|
// ...
|
|
// ],
|
|
// options : {}
|
|
//
|
|
// }
|
|
|
|
// make sure we have a collection array to populate
|
|
self.updAPISetParams.newSetValue.collection = _.isArray( self.updAPISetParams.newSetValue.collection ) ? self.updAPISetParams.newSetValue.collection : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ).collection;
|
|
|
|
switch( params.action ) {
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- LOCATION
|
|
//-------------------------------------------------------------------------------------------------
|
|
// December 2020 => this action is triggered in ::initialize self.activeLocations.bind()
|
|
// when injecting template from the gallery it may happen that the collection of location in the local setting value is not synchronized anymore with the actual active locations on the page
|
|
// update : December 24th => deactivated because of https://github.com/presscustomizr/nimble-builder/issues/770
|
|
case 'sek-maybe-add-missing-locations' :
|
|
var activeLocations = self.activeLocations(),
|
|
settingLocations = [],
|
|
locInSetting,
|
|
missingLoc,
|
|
newSettingCollection = [],
|
|
currentSettingCollection = $.extend( true, [], self.updAPISetParams.newSetValue.collection );
|
|
|
|
//console.log('SOO current Setting Collection', currentSettingCollection, activeLocations );
|
|
|
|
// loop on the active locations of the current page.
|
|
// if one is missing in the setting value, let's add it.
|
|
_.each( activeLocations, function( _loc_id ) {
|
|
locInSetting = _.findWhere( self.updAPISetParams.newSetValue.collection, { id : _loc_id } );
|
|
if ( _.isUndefined( locInSetting ) ) {
|
|
missingLoc = $.extend( true, {}, sektionsLocalizedData.defaultLocationModel );
|
|
missingLoc.id = _loc_id;
|
|
api.infoLog('=> need to add missing location to api setting value', missingLoc );
|
|
self.updAPISetParams.newSetValue.collection.push(missingLoc);
|
|
}
|
|
});
|
|
break;
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- SEKTION
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-add-section' :
|
|
self._updAPISet_sek_add_sektion();
|
|
break;
|
|
case 'sek-duplicate-section' :
|
|
self._updAPISet_sek_duplicate_sektion();
|
|
break;
|
|
case 'sek-remove-section' :
|
|
self._updAPISet_sek_remove_sektion();
|
|
break;
|
|
case 'sek-move-section' :
|
|
self._updAPISet_sek_move_sektion();
|
|
break;
|
|
// Fired on click on up / down arrows in the section ui menu
|
|
// This handles the nested sections case
|
|
case 'sek-move-section-up-down' :
|
|
self._updAPISet_sek_move_sektion_up_down();
|
|
break;
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- COLUMN
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-add-column' :
|
|
self._updAPISet_sek_add_column();
|
|
break;
|
|
case 'sek-remove-column' :
|
|
self._updAPISet_sek_remove_column();
|
|
break;
|
|
case 'sek-duplicate-column' :
|
|
self._updAPISet_sek_duplicate_column();
|
|
break;
|
|
// Note : the css rules are generated in Sek_Dyn_CSS_Builder::sek_add_rules_for_column_width
|
|
case 'sek-resize-columns' :
|
|
self._updAPISet_sek_resize_column();
|
|
break;
|
|
case 'sek-move-column' :
|
|
self._updAPISet_sek_move_column();
|
|
break;
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- MODULE
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-add-module' :
|
|
self._updAPISet_sek_add_module();
|
|
break;
|
|
case 'sek-duplicate-module' :
|
|
self._updAPISet_sek_duplicate_module();
|
|
break;
|
|
case 'sek-remove-module' :
|
|
self._updAPISet_sek_remove_module();
|
|
break;
|
|
case 'sek-move-module' :
|
|
self._updAPISet_sek_move_module();
|
|
break;
|
|
case 'sek-set-module-value' :
|
|
self._updAPISet_sek_set_module_value();
|
|
break;
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- CONTENT IN NEW SEKTION
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-add-content-in-new-sektion' :
|
|
self._updAPISet_sek_add_content_in_new_sektion();
|
|
break;
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- CONTENT IN NEW NESTED SEKTION
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-add-preset-section-in-new-nested-sektion' :
|
|
self._updAPISet_sek_add_preset_sektion_in_new_nested_sektion();
|
|
break;
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- FILE IMPORT
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-import-from-file' :
|
|
self._updAPISet_sek_import_from_file();
|
|
break;
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- INJECT TEMPLATE FROM GALLERY
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-inject-tmpl-from-gallery' :
|
|
self._updAPISet_sek_inject_tmpl_from_gallery();
|
|
break;
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- LEVEL OPTIONS
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-generate-level-options-ui' :
|
|
var _candidate_ = self.getLevelModel( params.id, self.updAPISetParams.newSetValue.collection ),
|
|
_valueCandidate = {};
|
|
|
|
if ( 'no_match'=== _candidate_ ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent sektion matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent sektion matched');
|
|
break;
|
|
}
|
|
// start from a deep cloned object
|
|
// important => fixes https://github.com/presscustomizr/nimble-builder/issues/455
|
|
var _new_options_values = $.extend( true, {}, _candidate_.options || {} );
|
|
|
|
// consider only the non empty settings for db
|
|
// booleans should bypass this check
|
|
_.each( params.value || {}, function( _val_, _key_ ) {
|
|
// Note : _.isEmpty( 5 ) returns true when checking an integer,
|
|
// that's why we need to cast the _val_ to a string when using _.isEmpty()
|
|
if ( ! _.isBoolean( _val_ ) && _.isEmpty( _val_ + "" ) )
|
|
return;
|
|
_valueCandidate[ _key_ ] = _val_;
|
|
});
|
|
|
|
if ( _.isEmpty( params.options_type ) ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => missing options_type');
|
|
}
|
|
|
|
_new_options_values[ params.options_type ] = _valueCandidate;
|
|
_candidate_.options = _new_options_values;
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- LOCAL SKOPE OPTIONS
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Note : this is saved in "local_options"
|
|
case 'sek-generate-local-skope-options-ui' :
|
|
_valueCandidate = {};
|
|
|
|
var _currentOptions = $.extend( true, {}, _.isObject( self.updAPISetParams.newSetValue.local_options ) ? self.updAPISetParams.newSetValue.local_options : {} );
|
|
// consider only the non empty settings for db
|
|
// booleans should bypass this check
|
|
_.each( params.value || {}, function( _val_, _key_ ) {
|
|
// Note : _.isEmpty( 5 ) returns true when checking an integer,
|
|
// that's why we need to cast the _val_ to a string when using _.isEmpty()
|
|
if ( ! _.isBoolean( _val_ ) && _.isEmpty( _val_ + "" ) )
|
|
return;
|
|
_valueCandidate[ _key_ ] = _val_;
|
|
});
|
|
if ( _.isEmpty( params.options_type ) || ! _.isString( params.options_type ) ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => missing options_type');
|
|
} else {
|
|
var newOptionsValues = {};
|
|
newOptionsValues[ params.options_type ] = _valueCandidate;
|
|
self.updAPISetParams.newSetValue.local_options = $.extend( _currentOptions, newOptionsValues );
|
|
}
|
|
break;
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- POPULATE GOOGLE FONTS
|
|
//-------------------------------------------------------------------------------------------------
|
|
//@params {
|
|
// action : 'sek-update-fonts',
|
|
// font_family : newFontFamily,
|
|
// }
|
|
case 'sek-update-fonts' :
|
|
// Get the gfonts from the level options and modules values
|
|
var currentGfonts = self.sniffGFonts( { is_global_location : ( params && true === params.is_global_location ) } );
|
|
|
|
// add it only if gfont
|
|
if ( ! _.isEmpty( params.font_family ) && _.isString( params.font_family ) ) {
|
|
if ( params.font_family.indexOf('gfont') > -1 && ! _.contains( currentGfonts, params.font_family ) ) {
|
|
currentGfonts.push( params.font_family );
|
|
}
|
|
}
|
|
|
|
// update the global gfonts collection
|
|
// this is then used server side in Sek_Dyn_CSS_Handler::sek_get_gfont_print_candidates to build the Google Fonts request
|
|
self.updAPISetParams.newSetValue.fonts = currentGfonts;
|
|
break;
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- RESTORE A REVISION
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-restore-revision' :
|
|
//api.infoLog( 'sek-restore-revision', params );
|
|
self.updAPISetParams.newSetValue = params.revision_value;
|
|
break;
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- RESET COLLECTION, LOCAL OR GLOBAL
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'sek-reset-collection' :
|
|
//api.infoLog( 'sek-import-from-file', params );
|
|
var _localOptions;
|
|
if ( 'local' === params.scope ) {
|
|
_localOptions = $.extend( true, {}, _.isObject( self.updAPISetParams.newSetValue.local_options ) ? self.updAPISetParams.newSetValue.local_options : {} );
|
|
}
|
|
try { self.updAPISetParams.newSetValue = api.czr_sektions.resetCollectionSetting( params.scope, _localOptions ); } catch( er ) {
|
|
api.errare( 'sek-reset-collection => error when firing resetCollectionSetting()', er );
|
|
}
|
|
break;
|
|
}// switch
|
|
|
|
|
|
|
|
// if we did not already rejected the request, let's check if the setting object has actually been modified
|
|
// at this point it should have been.
|
|
if ( 'pending' == self.updAPISetParams.promise.state() ) {
|
|
var mayBeUpdateSektionsSetting = function() {
|
|
|
|
// When a sektion setting is changed, "from" and "to" are passed to the .settingParams property
|
|
// settingParams : {
|
|
// to : to,
|
|
// from : from,
|
|
// args : args
|
|
// }
|
|
// @see for example ::generateUIforFrontModules or ::generateUIforLevelOptions
|
|
var isSettingValueChangeCase = params.settingParams && params.settingParams.from && params.settingParams.to;
|
|
|
|
|
|
// in a setting value change case, the from and to must be different
|
|
// implemented when fixing https://github.com/presscustomizr/nimble-builder/issues/455
|
|
if ( isSettingValueChangeCase && _.isEqual( params.settingParams.from, params.settingParams.to ) ) {
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => main sektion setting change => the new setting value is unchanged when firing action : ' + params.action );
|
|
} else if ( ! isSettingValueChangeCase && _.isEqual( currentSetValue, self.updAPISetParams.newSetValue ) ) {
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => the new setting value is unchanged when firing action : ' + params.action );
|
|
} else {
|
|
// method ::validateSettingValue() returns null if there is at least one validation error
|
|
var _settingValidationResult = self.validateSettingValue( self.updAPISetParams.newSetValue, params.is_global_location ? 'global' : 'local' );
|
|
if ( null !== _settingValidationResult && !_.isUndefined(_settingValidationResult) ) {
|
|
if ( !params.is_global_location ) {
|
|
// INHERITANCE
|
|
// solves the problem of preventing group template inheritance after a local reset
|
|
// on ::resetCollectionSetting(), the setting val is being modified to add this property local_reset.inherit_group_scope
|
|
var _is_inheritance_enabled_in_local_options = true, newSetVal = self.updAPISetParams.newSetValue;
|
|
if ( newSetVal.local_options && newSetVal.local_options.local_reset && !_.isUndefined( newSetVal.local_options.local_reset.inherit_group_scope ) ) {
|
|
_is_inheritance_enabled_in_local_options = newSetVal.local_options.local_reset.inherit_group_scope;
|
|
}
|
|
// Added March 2021 for #478
|
|
// When a page has not been locally customized, property __inherits_group_skope_tmpl_when_exists__ is true ( @see sek_get_default_location_model() )
|
|
// As soon as the main local setting id is modified, __inherits_group_skope_tmpl_when_exists__ is set to false ( see js control::updateAPISetting )
|
|
// After a reset case, NB sets __inherits_group_skope_tmpl_when_exists__ back to true ( see js control:: resetCollectionSetting )
|
|
// Note : If this property is set to true => NB removes the local skope post in Nimble_Collection_Setting::update()
|
|
self.updAPISetParams.newSetValue.__inherits_group_skope_tmpl_when_exists__ = 'sek-reset-collection' === params.action && _is_inheritance_enabled_in_local_options;
|
|
}
|
|
api( _collectionSettingId_ )( self.updAPISetParams.newSetValue, params );
|
|
|
|
// April 2021 : for site templates
|
|
if ( !params.is_global_location ) {
|
|
api.trigger('nimble-update-topbar-skope-status');
|
|
}
|
|
// Add the cloneId to the params when we resolve
|
|
// the cloneId is only needed in the duplication scenarii
|
|
params.cloneId = self.updAPISetParams.cloneId;
|
|
self.updAPISetParams.promise.resolve( params );
|
|
} else {
|
|
self.updAPISetParams.promise.reject( 'Validation problem for action ' + params.action );
|
|
}
|
|
//api.infoLog('COLLECTION SETTING UPDATED => ', _collectionSettingId_, api( _collectionSettingId_ )() );
|
|
}
|
|
};//mayBeUpdateSektionsSetting()
|
|
|
|
// For all scenarios except section injection, we can update the sektion setting now
|
|
// otherwise we need to wait for the injection to be processed asynchronously
|
|
// CRITICAL => self.updAPISetParams.promise has to be resolved / rejected
|
|
// otherwise this can lead to scenarios where a change is not taken into account in ::updateAPISettingAndExecutePreviewActions
|
|
// like in https://github.com/presscustomizr/nimble-builder/issues/373
|
|
if ( '_not_injection_scenario_' === self.updAPISetParams.sectionInjectPromise ) {
|
|
mayBeUpdateSektionsSetting();
|
|
// At this point the self.updAPISetParams.promise obj can't be in a 'pending' state
|
|
if ( 'pending' === self.updAPISetParams.promise.state() ) {
|
|
api.errare( '::updateAPISetting => The self.updAPISetParams.promise has not been resolved properly.');
|
|
}
|
|
} else {
|
|
self.updAPISetParams.sectionInjectPromise
|
|
.done( function() {
|
|
mayBeUpdateSektionsSetting();
|
|
// At this point the self.updAPISetParams.promise obj can't be in a 'pending' state
|
|
if ( 'pending' === self.updAPISetParams.promise.state() ) {
|
|
api.errare( '::updateAPISetting => The self.updAPISetParams.promise has not been resolved properly.');
|
|
}
|
|
})
|
|
.fail( function( _er_ ) {
|
|
api.errare( 'updateAPISetting => self.updAPISetParams.sectionInjectPromise failed', _er_ );
|
|
});
|
|
}
|
|
}
|
|
};//_updateSektionSettingInstanceWithAction()
|
|
|
|
|
|
// Update the sektion collection
|
|
api( _collectionSettingId_, function( sektionSetInstance ) {
|
|
_updateSektionSettingInstanceWithAction();
|
|
});
|
|
return self.updAPISetParams.promise.promise();
|
|
},//updateAPISetting
|
|
|
|
|
|
// used on :
|
|
// - add column
|
|
// - remove column
|
|
// - duplicate column
|
|
// - move column
|
|
// added in June 2019 for https://github.com/presscustomizr/nimble-builder/issues/279
|
|
resetColumnsWidthInSection : function( sektionCandidate ) {
|
|
// RESET ALL COLUMNS WIDTH
|
|
_.each( sektionCandidate.collection, function( colModel ) {
|
|
if ( colModel.options && colModel.options.width && colModel.options.width['custom-width'] ) {
|
|
colModel.options.width = _.omit( colModel.options.width, 'custom-width' );
|
|
}
|
|
colModel.width = '';// For backward compat since June 2019
|
|
});
|
|
},
|
|
|
|
|
|
// @return a promise()
|
|
// caches the api sections in api.nimble_ApiSections when api.section( '__content_picker__') is registered
|
|
// caches the user saved sections on the first drag and drop of a user-saved section
|
|
_getApiSingleSectionData : function( presetSectionId ) {
|
|
var dfd = $.Deferred(),
|
|
_ajaxRequest_;
|
|
|
|
// If already cached, resolve now
|
|
if ( ! _.isEmpty( api.nimble_ApiSections[presetSectionId] ) ) {
|
|
dfd.resolve( api.nimble_ApiSections[presetSectionId] );
|
|
} else {
|
|
if ( ! _.isUndefined( api.nimble_fetchingApiSection ) && 'pending' == api.nimble_fetchingApiSection.state() ) {
|
|
_ajaxRequest_ = api.nimble_fetchingApiSection;
|
|
} else {
|
|
_ajaxRequest_ = wp.ajax.post( 'sek_get_single_api_section_data', {
|
|
nonce: api.settings.nonce.save,
|
|
api_section_id : presetSectionId
|
|
});
|
|
api.nimble_fetchingApiSection = _ajaxRequest_;
|
|
}
|
|
_ajaxRequest_.done( function( _section_data_ ) {
|
|
//api.nimble_ApiSections = JSON.parse( _section_data_ );
|
|
api.nimble_ApiSections[presetSectionId] = _section_data_;
|
|
dfd.resolve( _section_data_ );
|
|
}).fail( function( _r_ ) {
|
|
api.errorLog( 'ajax sek_get_single_api_section_data => error', _r_ );
|
|
var _msg = 'Error when fetching the section';
|
|
if ( _.isString( _r_ ) && !_.isEmpty( _r_ ) ) {
|
|
_msg = _r_;
|
|
}
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 60000,
|
|
is_pro_notif : true,
|
|
notif_id : 'pro_section_error',
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>'+ _msg + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
}
|
|
return dfd.promise();
|
|
},
|
|
|
|
|
|
|
|
|
|
// First run : fetches the collection from the server
|
|
// Next runs : uses the cached collection
|
|
//
|
|
// @return a JSON parsed string,
|
|
// + guid() ids for each levels
|
|
// ready for insertion
|
|
//
|
|
// @sectionParams : {
|
|
// is_user_section : bool, //<= is this section a "saved" section ?
|
|
// presetSectionId : params.content_id,
|
|
// }
|
|
getPresetSectionCollectionData : function( sectionParams ) {
|
|
var self = this,
|
|
__dfd__ = $.Deferred();
|
|
if ( sectionParams.is_user_section ) {
|
|
wp.ajax.post( 'sek_get_user_section_json', {
|
|
nonce: api.settings.nonce.save,
|
|
section_post_name: sectionParams.presetSectionId
|
|
//skope_id: api.czr_skopeBase.getSkopeProperty( 'skope_id' )
|
|
})
|
|
.done( function( userSection ) {
|
|
// User section looks like
|
|
// {
|
|
// data : {}
|
|
// metas : {}
|
|
// section_post_name : ''
|
|
// }
|
|
if ( ! _.isObject( userSection ) || _.isEmpty( userSection ) || _.isUndefined( userSection.data ) ) {
|
|
api.errare( 'getPresetSectionCollectionData => preset section type not found or empty : ' + sectionParams.presetSectionId, userSection );
|
|
throw new Error( 'getPresetSectionCollectionData => preset section type not found or empty');
|
|
}
|
|
|
|
var userSectionCandidate = $.extend( {}, true, userSection.data );
|
|
|
|
// ID's
|
|
// the level's id have to be generated
|
|
// 1) root level
|
|
userSectionCandidate.id = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();
|
|
// 2) children levels
|
|
userSectionCandidate.collection = self.setPresetOrUserSectionIds( userSectionCandidate.collection );
|
|
|
|
// NIMBLE VERSION
|
|
// set the section version
|
|
userSectionCandidate.ver_ini = sektionsLocalizedData.nimbleVersion;
|
|
// the other level's version have to be added
|
|
userSectionCandidate.collection = self.setPresetSectionVersion( userSectionCandidate.collection );
|
|
|
|
// OK. time to resolve self.updAPISetParams.sectionInjectPromise.promise()
|
|
__dfd__.resolve( userSectionCandidate );
|
|
})
|
|
.fail( function( er ) {
|
|
__dfd__.reject( er );
|
|
});
|
|
} else {
|
|
api.nimble_ApiSections = api.nimble_ApiSections || {};
|
|
var _doResolveDfdWithData = function( _section_data_ ) {
|
|
//api.infoLog( 'API SECTION fetched', sectionParams.presetSectionId, api.nimble_ApiSections );
|
|
if ( _.isEmpty( _section_data_ ) || !_.isObject(_section_data_) ) {
|
|
throw new Error( 'getPresetSectionCollectionData => Invalid collection');
|
|
}
|
|
// if ( _.isEmpty( allPresets[ sectionParams.presetSectionId ] ) ) {
|
|
// throw new Error( 'getPresetSectionCollectionData => the preset section : "' + sectionParams.presetSectionId + '" has not been found in the collection');
|
|
// }
|
|
var presetCandidate = $.extend( true, {}, _section_data_ );
|
|
|
|
// Ensure we have a string that's JSON.parse-able
|
|
// if ( typeof presetCandidate !== 'string' || presetCandidate[0] !== '{' ) {
|
|
// throw new Error( 'getPresetSectionCollectionData => ' + sectionParams.presetSectionId + ' is not JSON.parse-able');
|
|
// }
|
|
// presetCandidate = JSON.parse( presetCandidate );
|
|
|
|
// ID's
|
|
// the level's id have to be generated
|
|
presetCandidate.collection = self.setPresetOrUserSectionIds( presetCandidate.collection );
|
|
|
|
// NIMBLE VERSION
|
|
// set the section version
|
|
presetCandidate.ver_ini = sektionsLocalizedData.nimbleVersion;
|
|
// the other level's version have to be added
|
|
presetCandidate.collection = self.setPresetSectionVersion( presetCandidate.collection );
|
|
__dfd__.resolve( presetCandidate );
|
|
};
|
|
var _collection;
|
|
switch( sectionParams.presetSectionId ) {
|
|
case 'two_columns' :
|
|
_collection = JSON.parse('{"collection":[{"id":"","level":"column","collection":[]},{"id":"","level":"column","collection":[]}]}');
|
|
_doResolveDfdWithData(_collection);
|
|
break;
|
|
case 'three_columns' :
|
|
_collection = JSON.parse('{"collection":[{"id":"","level":"column","collection":[]},{"id":"","level":"column","collection":[]},{"id":"","level":"column","collection":[]}]}');
|
|
_doResolveDfdWithData(_collection);
|
|
break;
|
|
case 'four_columns' :
|
|
_collection = JSON.parse('{"collection":[{"id":"","level":"column","collection":[]},{"id":"","level":"column","collection":[]},{"id":"","level":"column","collection":[]},{"id":"","level":"column","collection":[]}]}');
|
|
_doResolveDfdWithData(_collection);
|
|
break;
|
|
default :
|
|
self._getApiSingleSectionData( sectionParams.presetSectionId )
|
|
.fail( function( er ) {
|
|
__dfd__.reject( er );
|
|
})
|
|
.done( _doResolveDfdWithData );//_getApiSingleSectionData.done()
|
|
break;
|
|
}// Switch()
|
|
}
|
|
return __dfd__.promise();
|
|
},
|
|
|
|
|
|
// SECTION HELPERS
|
|
// Replaces __rep__me__ by an id looking like __nimble__1eda909f0cfe
|
|
setPresetOrUserSectionIds : function( collection ) {
|
|
var self = this;
|
|
// Only collection set as arrays hold columns or modules
|
|
if ( _.isArray( collection ) ) {
|
|
_.each( collection, function( levelData ) {
|
|
levelData.id = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();
|
|
if ( _.isArray( levelData.collection ) ) {
|
|
self.setPresetOrUserSectionIds( levelData.collection );
|
|
}
|
|
});
|
|
}
|
|
return collection;
|
|
},
|
|
|
|
setPresetSectionVersion : function( collection ) {
|
|
var self = this;
|
|
_.each( collection, function( levelData ) {
|
|
levelData.ver_ini = sektionsLocalizedData.nimbleVersion;
|
|
if ( _.isArray( levelData.collection ) ) {
|
|
self.setPresetSectionVersion( levelData.collection );
|
|
}
|
|
});
|
|
return collection;
|
|
},
|
|
|
|
|
|
|
|
// Walk the column collection of a preset section, and replace '__img_url__*' pattern by image ids that we get from ajax calls
|
|
// Is designed to handle multiple ajax calls in parallel if the preset_section includes several images
|
|
// @return a promise()
|
|
preparePresetSectionForInjection : function( columnCollection ) {
|
|
var self = this,
|
|
deferreds = {},
|
|
preparedSection = {},
|
|
_dfd_ = $.Deferred();
|
|
|
|
// items id of multi-items module must always be unique
|
|
// this recursive method sniff and does the job
|
|
self.maybeGenerateNewItemIdsForCrudModules( columnCollection );
|
|
|
|
// walk the column collection and populates the deferreds object recursively
|
|
var _sniffImg = function( data ) {
|
|
_.each( data, function( val, key ) {
|
|
if ( _.isObject( val ) || _.isArray( val ) ) {
|
|
_sniffImg( val );
|
|
} else if ( _.isString( val ) && -1 != val.indexOf( '__img_url__' ) ) {
|
|
// scenario when a section uses an image more than once.
|
|
// => we don't need to fire a new ajax request for an image already sniffed
|
|
if ( ! _.has( deferreds, val ) ) {
|
|
deferreds[ val ] = self.importAttachment( val.replace( '__img_url__', '' ) );
|
|
}
|
|
}
|
|
});
|
|
return deferreds;
|
|
};
|
|
|
|
// walk the column collection and populates the deferreds object recursively
|
|
// imdList is formed this way :
|
|
// __img_url__/assets/img/1.jpg : {id: 2547, url: "http://customizr-dev.test/wp-content/uploads/2018/09/nimble_asset_1.jpg"}
|
|
// __img_url__/assets/img/2.jpg : {id: 2548, url: "http://customizr-dev.test/wp-content/uploads/2018/09/nimble_asset_2.jpg"}
|
|
// __img_url__/assets/img/3.jpg : {id: 2549, url: "http://customizr-dev.test/wp-content/uploads/2018/09/nimble_asset_3.jpg"}
|
|
var _replaceImgPlaceholderById = function( data, imgList) {
|
|
_.each( data, function( val, key ) {
|
|
if ( _.isObject( val ) || _.isArray( val ) ) {
|
|
_replaceImgPlaceholderById( val, imgList );
|
|
} else if ( _.isString( val ) && -1 != val.indexOf( '__img_url__' ) && _.has( imgList, val ) && _.isObject( imgList[ val ] ) ) {
|
|
data[ key ] = imgList[ val ].id;
|
|
}
|
|
});
|
|
return columnCollection;
|
|
};
|
|
|
|
self.whenAllPromisesInParallel( _sniffImg( columnCollection ) )
|
|
.done( function( imgList ) {
|
|
var imgReadySection = _replaceImgPlaceholderById( columnCollection, imgList );
|
|
_dfd_.resolve( imgReadySection );
|
|
})
|
|
.fail( function( _er_ ){
|
|
_dfd_.reject( _er_ );
|
|
});
|
|
|
|
return _dfd_.promise();
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData, serverControlParams
|
|
//self.updAPISetParams = {
|
|
// params : params,
|
|
// promise : $.Deferred(),
|
|
// newSetValue : _.isObject( _currentSetValue ) ? $.extend( true, {}, _currentSetValue ) : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ),
|
|
// cloneId : '',
|
|
// sectionInjectPromise
|
|
// };
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- ADD SECTION
|
|
//-------------------------------------------------------------------------------------------------
|
|
_updAPISet_sek_add_sektion : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate,
|
|
parentSektionCandidate,
|
|
locationCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
//console.log('ADD SECTION ?', params, self.updAPISetParams );
|
|
// an id must be provided
|
|
if ( _.isEmpty( params.id ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing id' );
|
|
}
|
|
|
|
if ( _.isEmpty( params.location ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing location' );
|
|
}
|
|
// Is this a nested sektion ?
|
|
if ( true === params.is_nested ) {
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
// can we add this nested sektion ?
|
|
// if the parent sektion of the column has is_nested = true, then we can't
|
|
parentSektionCandidate = self.getLevelModel( params.in_sektion, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == parentSektionCandidate ) {
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no grand parent sektion found');
|
|
} else if ( true === parentSektionCandidate.is_nested ) {
|
|
self.updAPISetParams.promise.reject('');
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 30000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n[ "You've reached the maximum number of allowed nested sections." ] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
} else if ( 'no_match' == columnCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent column matched');
|
|
} else {
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
columnCandidate.collection.push({
|
|
id : params.id,
|
|
level : 'section',
|
|
collection : [{
|
|
id : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid(),
|
|
level : 'column',
|
|
collection : [],
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
}],
|
|
is_nested : true,
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
});
|
|
}
|
|
} else {
|
|
locationCandidate = self.getLevelModel( params.location, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == locationCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no location matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no location matched');
|
|
} else {
|
|
var position = 0;
|
|
locationCandidate.collection = _.isArray( locationCandidate.collection ) ? locationCandidate.collection : [];
|
|
_.each( locationCandidate.collection, function( secModel, index ) {
|
|
if ( params.before_section === secModel.id ) {
|
|
position = index;
|
|
}
|
|
if ( params.after_section === secModel.id ) {
|
|
position = index + 1;
|
|
}
|
|
});
|
|
|
|
// @see reactToCollectionSettingIdChange
|
|
locationCandidate.collection = _.isArray( locationCandidate.collection ) ? locationCandidate.collection : [];
|
|
// insert the section in the collection at the right place
|
|
locationCandidate.collection.splice( position, 0, {
|
|
id : params.id,
|
|
level : 'section',
|
|
collection : [{
|
|
id : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid(),
|
|
level : 'column',
|
|
collection : [],
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
}],
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
});
|
|
}
|
|
}
|
|
},//_updAPISet_sek_add_sektion
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- DUPLICATE SECTION
|
|
//-------------------------------------------------------------------------------------------------
|
|
_updAPISet_sek_duplicate_sektion : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate,
|
|
locationCandidate,
|
|
deepClonedSektion;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
// an id must be provided
|
|
if ( _.isEmpty( params.id ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing id' );
|
|
}
|
|
if ( _.isEmpty( params.location ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing location' );
|
|
}
|
|
try { deepClonedSektion = self.cloneLevel( params.id ); } catch( er ) {
|
|
api.errare( 'updateAPISetting => ' + params.action, er );
|
|
return;
|
|
}
|
|
|
|
// items id of multi-items module must always be unique
|
|
// this recursive method sniff and does the job
|
|
self.maybeGenerateNewItemIdsForCrudModules( deepClonedSektion );
|
|
|
|
var _position_ = self.getLevelPositionInCollection( params.id, self.updAPISetParams.newSetValue.collection );
|
|
// Is this a nested sektion ?
|
|
if ( true === params.is_nested ) {
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == columnCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent column matched');
|
|
return;
|
|
}
|
|
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
columnCandidate.collection.splice( parseInt( _position_ + 1, 10 ), 0, deepClonedSektion );
|
|
|
|
|
|
} else {
|
|
locationCandidate = self.getLevelModel( params.location, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == locationCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no location matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no location matched');
|
|
return;
|
|
}
|
|
locationCandidate.collection = _.isArray( locationCandidate.collection ) ? locationCandidate.collection : [];
|
|
// @see reactToCollectionSettingIdChange
|
|
locationCandidate.collection.splice( parseInt( _position_ + 1, 10 ), 0, deepClonedSektion );
|
|
|
|
}
|
|
self.updAPISetParams.cloneId = deepClonedSektion.id;//will be passed in resolve()
|
|
},//_updAPISet_sek_duplicate_sektion
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- REMOVE SECTION
|
|
//-------------------------------------------------------------------------------------------------
|
|
// in the case of a nested sektion, we have to remove it from a column
|
|
// otherwise from the root sektion collection
|
|
_updAPISet_sek_remove_sektion : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate,
|
|
locationCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
//api.infoLog('PARAMS IN sek-remove-sektion', params );
|
|
if ( true === params.is_nested ) {
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' != columnCandidate ) {
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
columnCandidate.collection = _.filter( columnCandidate.collection, function( col ) {
|
|
return col.id != params.id;
|
|
});
|
|
} else {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
}
|
|
} else {
|
|
locationCandidate = self.getLevelModel( params.location, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == locationCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no location matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no location matched');
|
|
return;
|
|
}
|
|
locationCandidate.collection = _.filter( locationCandidate.collection, function( sek ) {
|
|
return sek.id != params.id;
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- MOVE SECTION (DRAG IN THE PAGE INSIDE A LOCATION, OR CROSS LOCATIONS)
|
|
//-------------------------------------------------------------------------------------------------
|
|
_updAPISet_sek_move_sektion : function() {
|
|
var self = this,
|
|
params,
|
|
originalCollection,
|
|
reorderedCollection,
|
|
locationCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
//api.infoLog('PARAMS in sek-move-section', params );
|
|
var toLocationCandidate = self.getLevelModel( params.to_location, self.updAPISetParams.newSetValue.collection ),
|
|
movedSektionCandidate,
|
|
copyOfMovedSektionCandidate;
|
|
|
|
if ( _.isEmpty( toLocationCandidate ) || 'no_match' == toLocationCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing target location' );
|
|
}
|
|
|
|
// MOVED CROSS LOCATIONS
|
|
// - make a copy of the moved sektion
|
|
// - remove the moved sektion from the source location
|
|
if ( params.from_location != params.to_location ) {
|
|
// Remove the moved sektion from the source location
|
|
var fromLocationCandidate = self.getLevelModel( params.from_location, self.updAPISetParams.newSetValue.collection );
|
|
if ( _.isEmpty( fromLocationCandidate ) || 'no_match' == fromLocationCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing source location' );
|
|
}
|
|
|
|
fromLocationCandidate.collection = _.isArray( fromLocationCandidate.collection ) ? fromLocationCandidate.collection : [];
|
|
// Make a copy of the sektion candidate now, before removing it
|
|
movedSektionCandidate = self.getLevelModel( params.id, fromLocationCandidate.collection );
|
|
copyOfMovedSektionCandidate = $.extend( true, {}, movedSektionCandidate );
|
|
// remove the sektion from its previous sektion
|
|
fromLocationCandidate.collection = _.filter( fromLocationCandidate.collection, function( sektion ) {
|
|
return sektion.id != params.id;
|
|
});
|
|
}
|
|
|
|
// UPDATE THE TARGET LOCATION
|
|
toLocationCandidate.collection = _.isArray( toLocationCandidate.collection ) ? toLocationCandidate.collection : [];
|
|
originalCollection = $.extend( true, [], toLocationCandidate.collection );
|
|
reorderedCollection = [];
|
|
_.each( params.newOrder, function( _id_ ) {
|
|
// in the case of a cross location movement, we need to add the moved sektion to the target location
|
|
if ( params.from_location != params.to_location && _id_ == copyOfMovedSektionCandidate.id ) {
|
|
reorderedCollection.push( copyOfMovedSektionCandidate );
|
|
} else {
|
|
sektionCandidate = self.getLevelModel( _id_, originalCollection );
|
|
if ( _.isEmpty( sektionCandidate ) || 'no_match' == sektionCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing section candidate' );
|
|
}
|
|
reorderedCollection.push( sektionCandidate );
|
|
}
|
|
});
|
|
toLocationCandidate.collection = reorderedCollection;
|
|
},//_updAPISet_sek_move_sektion
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- MOVE SECTION UP DOWN
|
|
//-------------------------------------------------------------------------------------------------
|
|
_updAPISet_sek_move_sektion_up_down : function() {
|
|
var self = this,
|
|
params,
|
|
parentCandidate,
|
|
originalCollection,
|
|
reorderedCollection,
|
|
locationCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
parentCandidate = self.getLevelModel( params.is_nested ? params.in_column : params.location , self.updAPISetParams.newSetValue.collection );
|
|
if ( _.isEmpty( parentCandidate ) || 'no_match' == parentCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing target location' );
|
|
}
|
|
parentCandidate.collection = _.isArray( parentCandidate.collection ) ? parentCandidate.collection : [];
|
|
originalCollection = $.extend( true, [], parentCandidate.collection );
|
|
reorderedCollection = $.extend( true, [], parentCandidate.collection );
|
|
|
|
var _indexInOriginal = _.findIndex( originalCollection, function( _sec_ ) {
|
|
return _sec_.id === params.id;
|
|
});
|
|
// @see https://underscorejs.org/#findIndex
|
|
if ( -1 === _indexInOriginal ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => invalid index' );
|
|
}
|
|
|
|
// Swap up <=> down
|
|
var direction = params.direction || 'up',
|
|
isLastSectionInLocation = originalCollection.length === _indexInOriginal + 1,
|
|
isFirstSectionInLocation = 0 === _indexInOriginal,
|
|
_locInfo = self.activeLocationsInfo(),
|
|
_currentLocInfo = ! _.isArray( _locInfo ) ? {} : _.findWhere( _locInfo, { id : params.location } ),
|
|
isCurrentLocationGlobal = false,
|
|
isCurrentLocationHeaderOrFooter = false;
|
|
|
|
// Is current location global ?
|
|
isCurrentLocationGlobal = _.isObject( _currentLocInfo ) && _currentLocInfo.is_global;
|
|
|
|
// Is current location header footer ?
|
|
isCurrentLocationHeaderOrFooter = _.isObject( _currentLocInfo ) && _currentLocInfo.is_header_footer;
|
|
|
|
// When a section is last in location and there's another location below, let's move the section to this sibling location
|
|
// This is possible when :
|
|
// - when moved section is not nested
|
|
// - only in locations that are 'local', not header or footer
|
|
|
|
// Populate the eligible activeLocations in the page
|
|
var activeLocationsInPage = [];
|
|
// self.activeLocationsInfo() is set on ::initialized when send by the preview, and is structured the following way :
|
|
// [
|
|
// {
|
|
// id: "loop_start"
|
|
// is_global: false
|
|
// is_header_footer: false
|
|
// },
|
|
// {..},
|
|
// .
|
|
if ( _.isArray( _locInfo ) ) {
|
|
_.each( self.activeLocationsInfo(), function( _loc_ ) {
|
|
if ( ! _loc_.is_global && ! _loc_.is_header_footer ) {
|
|
activeLocationsInPage.push( _loc_.id );
|
|
}
|
|
});
|
|
}
|
|
|
|
// Set the index of the current location
|
|
var indexOfCurrentLocation = _.findIndex( activeLocationsInPage, function( _loc_id ) {
|
|
return _loc_id === params.location;
|
|
});
|
|
|
|
var isCurrentLocationEligibleForSectionMoveOutside = ! params.is_nested && ! isCurrentLocationGlobal && ! isCurrentLocationHeaderOrFooter,
|
|
isFirstLocationInPage = 0 === indexOfCurrentLocation,
|
|
isLastLocationInPage = activeLocationsInPage.length === indexOfCurrentLocation + 1,
|
|
newLocationId, newLocationCandidate;
|
|
|
|
if ( isCurrentLocationEligibleForSectionMoveOutside && isLastSectionInLocation && 'up' !== direction && ! isLastLocationInPage ) {
|
|
newLocationId = activeLocationsInPage[ indexOfCurrentLocation + 1 ];
|
|
newLocationCandidate = self.getLevelModel( newLocationId , self.updAPISetParams.newSetValue.collection );
|
|
|
|
// Add the section in first position of the section below
|
|
newLocationCandidate.collection.unshift( originalCollection[ _indexInOriginal ] );
|
|
// Removes the section in last position of the original section
|
|
parentCandidate.collection.pop();
|
|
// the new_location param will be used in the 'complete' callback of 'sek-move-section-down' / 'sek-move-section-up'
|
|
params.new_location = newLocationId;
|
|
|
|
} else if ( isCurrentLocationEligibleForSectionMoveOutside && isFirstSectionInLocation && 'up' === direction && ! isFirstLocationInPage ) {
|
|
newLocationId = activeLocationsInPage[ indexOfCurrentLocation - 1 ];
|
|
newLocationCandidate = self.getLevelModel( newLocationId , self.updAPISetParams.newSetValue.collection );
|
|
|
|
// Add the section in first position of the section below
|
|
newLocationCandidate.collection.push( originalCollection[ _indexInOriginal ] );
|
|
// Removes the section in last position of the original section
|
|
parentCandidate.collection.shift();
|
|
// the new_location param will be used in the 'complete' callback of 'sek-move-section-down' / 'sek-move-section-up'
|
|
params.new_location = newLocationId;
|
|
} else {
|
|
// prevent absurd movements of a section
|
|
// this should not happen because up / down arrows are not displayed when section is positionned top / bottom
|
|
// but safer to add it
|
|
if ( 'up' !== direction && originalCollection.length === _indexInOriginal + 1 ) {
|
|
//throw new Error( 'updateAPISetting => ' + params.action + ' => bottom reached' );
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 30000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n[ "The section cannot be moved lower." ] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
} else if ( 'up' === direction && 0 === _indexInOriginal ){
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 30000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n[ "The section cannot be moved higher." ] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
} else {
|
|
reorderedCollection[ _indexInOriginal ] = originalCollection[ 'up' === direction ? _indexInOriginal - 1 : _indexInOriginal + 1 ];
|
|
reorderedCollection[ 'up' === direction ? _indexInOriginal - 1 : _indexInOriginal + 1 ] = originalCollection[ _indexInOriginal ];
|
|
parentCandidate.collection = reorderedCollection;
|
|
}
|
|
}
|
|
}//_updAPISet_sek_move_sektion_up_down
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData, serverControlParams
|
|
//self.updAPISetParams = {
|
|
// params : params,
|
|
// promise : $.Deferred(),
|
|
// newSetValue : _.isObject( _currentSetValue ) ? $.extend( true, {}, _currentSetValue ) : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ),
|
|
// cloneId : '',
|
|
// sectionInjectPromise
|
|
// };
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// @params {
|
|
// drop_target_element : $(this),
|
|
// position : _position,// <= top or bottom
|
|
// before_section : $(this).data('sek-before-section'),
|
|
// after_section : $(this).data('sek-after-section'),
|
|
// content_type : event.originalEvent.dataTransfer.getData( "sek-content-type" ), //<= module or preset_section
|
|
// content_id : event.originalEvent.dataTransfer.getData( "sek-content-id" )
|
|
// }
|
|
_updAPISet_sek_add_content_in_new_sektion : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate,
|
|
locationCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
// get the position of the before or after section
|
|
var positionIndex = 0,
|
|
startingModuleValue;
|
|
|
|
locationCandidate = self.getLevelModel( params.location, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == locationCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no location matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no location matched');
|
|
return;
|
|
}
|
|
locationCandidate.collection = _.isArray( locationCandidate.collection ) ? locationCandidate.collection : [];
|
|
_.each( locationCandidate.collection, function( secModel, index ) {
|
|
if ( params.before_section === secModel.id ) {
|
|
positionIndex = index;
|
|
}
|
|
if ( params.after_section === secModel.id ) {
|
|
positionIndex = index + 1;
|
|
}
|
|
});
|
|
|
|
switch( params.content_type) {
|
|
// When a module is dropped in a section + column structure to be generated
|
|
case 'module' :
|
|
// Let's add the starting value if provided when registrating the module
|
|
// Note : params.content_id is the module_type
|
|
startingModuleValue = self.getModuleStartingValue( params.content_id );
|
|
|
|
// insert the section in the collection at the right place
|
|
locationCandidate.collection.splice( positionIndex, 0, {
|
|
id : params.id,
|
|
level : 'section',
|
|
collection : [
|
|
{
|
|
id : sektionsLocalizedData.prefixForSettingsNotSaved + self.guid(),
|
|
level : 'column',
|
|
collection : [
|
|
{
|
|
id : params.droppedModuleId,
|
|
level : 'module',
|
|
module_type : params.content_id,
|
|
value : 'no_starting_value' !== startingModuleValue ? startingModuleValue : null,
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
}
|
|
],
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
}
|
|
],
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
});
|
|
break;//case 'module' :
|
|
|
|
// When a preset section is dropped
|
|
case 'preset_section' :
|
|
// insert the section in the collection at the right place
|
|
self.updAPISetParams.sectionInjectPromise = $.Deferred();//defined at the beginning of the method
|
|
|
|
// we use a section index here to display the section in the same order as in the json
|
|
// introduced when implementing multi-section pre-build section
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/489
|
|
var _injectPresetSectionInLocationOrParentColumn = function( sectionReadyToInject, positionIndex ) {
|
|
// If the preset_section is inserted in a an empty nested section, add it at the right place in the parent column of the nested section.
|
|
// Otherwise, add the preset section at the right position in the parent location of the section.
|
|
var insertedInANestedSektion = false;
|
|
if ( ! _.isEmpty( params.sektion_to_replace ) ) {
|
|
var sektionToReplace = self.getLevelModel( params.sektion_to_replace, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' === sektionToReplace ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no sektionToReplace matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no sektionToReplace matched');
|
|
}
|
|
insertedInANestedSektion = true === sektionToReplace.is_nested;
|
|
}
|
|
|
|
|
|
// The following param "collection_of_preset_section_id" has been introduced when implementing support for multi-section pre-build sections
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/489
|
|
// It is sent to the preview with ::reactToPreviewMsg, see bottom of the method.
|
|
var injected_section_id = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();
|
|
params.collection_of_preset_section_id = params.collection_of_preset_section_id || [];
|
|
params.collection_of_preset_section_id.push( injected_section_id );
|
|
|
|
if ( ! insertedInANestedSektion ) {
|
|
locationCandidate.collection.splice( positionIndex, 0, {
|
|
id : injected_section_id,//params.id,//self.guid()
|
|
level : 'section',
|
|
collection : sectionReadyToInject.collection,
|
|
options : sectionReadyToInject.options || {},
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
});
|
|
} else {
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' === columnCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent column matched');
|
|
}
|
|
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
|
|
// get the position of the before or after module
|
|
_.each( columnCandidate.collection, function( moduleOrSectionModel, index ) {
|
|
if ( params.before_section === moduleOrSectionModel.id ) {
|
|
positionIndex = index;
|
|
}
|
|
if ( params.after_section === moduleOrSectionModel.id ) {
|
|
positionIndex = index + 1;
|
|
}
|
|
});
|
|
|
|
columnCandidate.collection.splice( positionIndex, 0, {
|
|
id : injected_section_id,
|
|
is_nested : true,
|
|
level : 'section',
|
|
collection : sectionReadyToInject.collection,
|
|
options : sectionReadyToInject.options || {},
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
});
|
|
}
|
|
};//_injectPresetSectionInLocationOrParentColumn
|
|
var _doWhenPresetSectionCollectionFetched = function( presetColumnOrSectionCollection ) {
|
|
self.preparePresetSectionForInjection( presetColumnOrSectionCollection )
|
|
.fail( function( _er_ ){
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => error when preparePresetSectionForInjection => ' + params.action + ' => ' + _er_ );
|
|
// Used when updating the setting
|
|
// @see end of this method
|
|
self.updAPISetParams.sectionInjectPromise.reject( _er_ );
|
|
})
|
|
.done( function( maybeMultiSectionReadyToInject ) {
|
|
var is_multi_section = 'section' === maybeMultiSectionReadyToInject.collection[0].level;
|
|
// support for pre-built multi-section has been introduced in july 2019
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/489
|
|
if ( is_multi_section ) {
|
|
// we use a section index here to display the section in the same order as in the json
|
|
var sectionIndex = 0;
|
|
_.each( maybeMultiSectionReadyToInject.collection, function( sectionReadyToInject ) {
|
|
_injectPresetSectionInLocationOrParentColumn( sectionReadyToInject, positionIndex );
|
|
positionIndex++;
|
|
});
|
|
} else {
|
|
_injectPresetSectionInLocationOrParentColumn( maybeMultiSectionReadyToInject, positionIndex );
|
|
}
|
|
|
|
// Used when updating the setting
|
|
// @see end of this method
|
|
self.updAPISetParams.sectionInjectPromise.resolve();
|
|
});// self.preparePresetSectionForInjection.done()
|
|
};//_doWhenPresetSectionCollectionFetched()
|
|
|
|
// Try to fetch the sections from the server
|
|
// if sucessfull, resolve self.updAPISetParams.sectionInjectPromise.promise()
|
|
self.getPresetSectionCollectionData({
|
|
is_user_section : params.is_user_section,
|
|
presetSectionId : params.content_id
|
|
})
|
|
.fail( function( _er_ ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => Error with self.getPresetSectionCollectionData()', _er_ );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => Error with self.getPresetSectionCollectionData()');
|
|
})
|
|
.done( function( presetColumnOrSectionCollection ) {
|
|
if ( ! _.isObject( presetColumnOrSectionCollection ) || _.isEmpty( presetColumnOrSectionCollection ) ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => preset section type not found or empty : ' + params.content_id, presetColumnOrSectionCollection );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => preset section type not found or empty');
|
|
}
|
|
// OK. time to resolve self.updAPISetParams.sectionInjectPromise.promise()
|
|
_doWhenPresetSectionCollectionFetched( presetColumnOrSectionCollection );
|
|
});//self.getPresetSectionCollectionData().done()
|
|
|
|
break;//case 'preset_section' :
|
|
}//switch( params.content_type)
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @params {
|
|
// drop_target_element : $(this),
|
|
// position : _position,// <= top or bottom
|
|
// before_section : $(this).data('sek-before-section'),
|
|
// after_section : $(this).data('sek-after-section'),
|
|
// content_type : event.originalEvent.dataTransfer.getData( "sek-content-type" ), //<= module or preset_section
|
|
// content_id : event.originalEvent.dataTransfer.getData( "sek-content-id" )
|
|
// }
|
|
_updAPISet_sek_add_preset_sektion_in_new_nested_sektion : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate,
|
|
parentSektionCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' === columnCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent column matched');
|
|
return;
|
|
}
|
|
|
|
var positionIndexInColumn = 0;
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
// get the position of the before or after module or nested section
|
|
_.each( columnCandidate.collection, function( moduleOrNestedSectionModel, index ) {
|
|
if ( params.before_module_or_nested_section === moduleOrNestedSectionModel.id ) {
|
|
positionIndexInColumn = index;
|
|
}
|
|
if ( params.after_module_or_nested_section === moduleOrNestedSectionModel.id ) {
|
|
positionIndexInColumn = index + 1;
|
|
}
|
|
});
|
|
|
|
// can we add this nested sektion ?
|
|
// if the parent sektion of the column has is_nested = true, then we can't
|
|
parentSektionCandidate = self.getLevelModel( params.in_sektion, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == parentSektionCandidate ) {
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no grand parent sektion found');
|
|
return;
|
|
}
|
|
if ( true === parentSektionCandidate.is_nested ) {
|
|
self.updAPISetParams.promise.reject('');
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 30000,
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>' + sektionsLocalizedData.i18n[ "You've reached the maximum number of allowed nested sections." ] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
return;
|
|
}
|
|
|
|
// insert the nested section in the collection at the right place
|
|
var presetColumnOrSectionCollection;
|
|
self.updAPISetParams.sectionInjectPromise = $.Deferred();
|
|
|
|
var _doWhenPrebuiltSectionCollectionFetched = function( presetColumnOrSectionCollection ) {
|
|
self.preparePresetSectionForInjection( presetColumnOrSectionCollection )
|
|
.fail( function( _er_ ){
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => error when preparePresetSectionForInjection => ' + params.action + ' => ' + _er_ );
|
|
// Used when updating the setting
|
|
// @see end of this method
|
|
self.updAPISetParams.sectionInjectPromise.reject( _er_ );
|
|
})
|
|
.done( function( maybeMultiSectionReadyToInject ) {
|
|
|
|
var _injectNestedSectionInParentColumn = function( sectionReadyToInject, positionIndexInColumn ) {
|
|
positionIndexInColumn = positionIndexInColumn || 0;
|
|
|
|
// The following param "collection_of_preset_section_id" has been introduced when implementing support for multi-section pre-build sections
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/489
|
|
// It is sent to the preview with ::reactToPreviewMsg, see bottom of the method.
|
|
var injected_section_id = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();
|
|
params.collection_of_preset_section_id = params.collection_of_preset_section_id || [];
|
|
params.collection_of_preset_section_id.push( injected_section_id );
|
|
|
|
columnCandidate.collection.splice( positionIndexInColumn, 0, {
|
|
id : injected_section_id,
|
|
level : 'section',
|
|
collection : sectionReadyToInject.collection,
|
|
options : sectionReadyToInject.options || {},
|
|
is_nested : true,
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
});
|
|
};
|
|
|
|
// support for pre-built multi-section has been introduced in july 2019
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/489
|
|
var is_multi_section = 'section' === maybeMultiSectionReadyToInject.collection[0].level;
|
|
if ( is_multi_section ) {
|
|
_.each( maybeMultiSectionReadyToInject.collection, function( sectionReadyToInject ) {
|
|
_injectNestedSectionInParentColumn( sectionReadyToInject, positionIndexInColumn );
|
|
positionIndexInColumn++;
|
|
});
|
|
} else {
|
|
_injectNestedSectionInParentColumn( maybeMultiSectionReadyToInject, positionIndexInColumn );
|
|
}
|
|
|
|
// Used when updating the setting
|
|
// @see end of this method
|
|
self.updAPISetParams.sectionInjectPromise.resolve();
|
|
});//self.preparePresetSectionForInjection.done()
|
|
};//_doWhenPrebuiltSectionCollectionFetched
|
|
|
|
|
|
// Try to fetch the sections from the server
|
|
// if sucessfull, resolve self.updAPISetParams.sectionInjectPromise.promise()
|
|
self.getPresetSectionCollectionData({
|
|
is_user_section : params.is_user_section,
|
|
presetSectionId : params.content_id
|
|
})
|
|
.fail( function() {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => Error with self.getPresetSectionCollectionData()', _er_ );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => Error with self.getPresetSectionCollectionData()');
|
|
})
|
|
.done( function( presetColumnOrSectionCollection ) {
|
|
if ( ! _.isObject( presetColumnOrSectionCollection ) || _.isEmpty( presetColumnOrSectionCollection ) ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => preset section type not found or empty : ' + params.content_id, presetColumnOrSectionCollection );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => preset section type not found or empty');
|
|
}
|
|
// OK. time to resolve self.updAPISetParams.sectionInjectPromise.promise()
|
|
_doWhenPrebuiltSectionCollectionFetched( presetColumnOrSectionCollection );
|
|
});//self.getPresetSectionCollectionData().done()
|
|
}
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData, serverControlParams
|
|
//self.updAPISetParams = {
|
|
// params : params,
|
|
// promise : $.Deferred(),
|
|
// newSetValue : _.isObject( _currentSetValue ) ? $.extend( true, {}, _currentSetValue ) : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ),
|
|
// cloneId : '',
|
|
// sectionInjectPromise
|
|
// };
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
_updAPISet_sek_add_column : function() {
|
|
var self = this,
|
|
params,
|
|
sektionCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
// an id must be provided
|
|
if ( _.isEmpty( params.id ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing id' );
|
|
}
|
|
sektionCandidate = self.getLevelModel( params.in_sektion, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == sektionCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent sektion matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent sektion matched');
|
|
return;
|
|
}
|
|
|
|
sektionCandidate.collection = _.isArray( sektionCandidate.collection ) ? sektionCandidate.collection : [];
|
|
// can we add another column ?
|
|
if ( ( self.MAX_NUMBER_OF_COLUMNS - 1 ) < _.size( sektionCandidate.collection ) ) {
|
|
self.updAPISetParams.promise.reject( sektionsLocalizedData.i18n["You've reached the maximum number of columns allowed in this section."]);
|
|
return;
|
|
}
|
|
|
|
// RESET ALL COLUMNS WIDTH
|
|
// _.each( sektionCandidate.collection, function( colModel ) {
|
|
// colModel.width = '';
|
|
// });
|
|
self.resetColumnsWidthInSection( sektionCandidate );
|
|
|
|
sektionCandidate.collection.push({
|
|
id : params.id,
|
|
level : 'column',
|
|
collection : [],
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
});
|
|
},
|
|
|
|
_updAPISet_sek_remove_column : function() {
|
|
var self = this,
|
|
params,
|
|
sektionCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
sektionCandidate = self.getLevelModel( params.in_sektion, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' != sektionCandidate ) {
|
|
// can we remove the column ?
|
|
if ( 1 === _.size( sektionCandidate.collection ) ) {
|
|
self.updAPISetParams.promise.reject( sektionsLocalizedData.i18n["A section must have at least one column."]);
|
|
return;
|
|
}
|
|
sektionCandidate.collection = _.isArray( sektionCandidate.collection ) ? sektionCandidate.collection : [];
|
|
sektionCandidate.collection = _.filter( sektionCandidate.collection, function( column ) {
|
|
return column.id != params.id;
|
|
});
|
|
// RESET ALL COLUMNS WIDTH
|
|
// _.each( sektionCandidate.collection, function( colModel ) {
|
|
// colModel.width = '';
|
|
// });
|
|
self.resetColumnsWidthInSection( sektionCandidate );
|
|
} else {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent sektion matched' );
|
|
}
|
|
},
|
|
|
|
_updAPISet_sek_duplicate_column : function() {
|
|
var self = this,
|
|
params,
|
|
sektionCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
// an id must be provided
|
|
if ( _.isEmpty( params.id ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing id' );
|
|
}
|
|
|
|
sektionCandidate = self.getLevelModel( params.in_sektion, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == sektionCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent sektion matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent sektion matched');
|
|
return;
|
|
}
|
|
|
|
sektionCandidate.collection = _.isArray( sektionCandidate.collection ) ? sektionCandidate.collection : [];
|
|
// can we add another column ?
|
|
if ( ( self.MAX_NUMBER_OF_COLUMNS - 1 ) < _.size( sektionCandidate.collection ) ) {
|
|
self.updAPISetParams.promise.reject( sektionsLocalizedData.i18n["You've reached the maximum number of columns allowed in this section."]);
|
|
return;
|
|
}
|
|
|
|
var deepClonedColumn;
|
|
try { deepClonedColumn = self.cloneLevel( params.id ); } catch( er ) {
|
|
api.errare( 'updateAPISetting => ' + params.action, er );
|
|
return;
|
|
}
|
|
|
|
// items id of multi-items module must always be unique
|
|
// this recursive method sniff and does the job
|
|
self.maybeGenerateNewItemIdsForCrudModules( deepClonedColumn );
|
|
|
|
var _position = self.getLevelPositionInCollection( params.id, self.updAPISetParams.newSetValue.collection );
|
|
self.updAPISetParams.cloneId = deepClonedColumn.id;//will be passed in resolve()
|
|
sektionCandidate.collection.splice( parseInt( _position + 1, 10 ), 0, deepClonedColumn );
|
|
// RESET ALL COLUMNS WIDTH
|
|
// _.each( sektionCandidate.collection, function( colModel ) {
|
|
// colModel.width = '';
|
|
// });
|
|
self.resetColumnsWidthInSection( sektionCandidate );
|
|
},
|
|
|
|
_updAPISet_sek_resize_column : function() {
|
|
var self = this,
|
|
params;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
if ( params.col_number < 2 )
|
|
return;
|
|
|
|
var resizedColumn = self.getLevelModel( params.resized_column, self.updAPISetParams.newSetValue.collection ),
|
|
sistercolumn = self.getLevelModel( params.sister_column, self.updAPISetParams.newSetValue.collection );
|
|
|
|
//api.infoLog( 'updateAPISetting => ' + params.action + ' => ', params );
|
|
|
|
// SET RESIZED COLUMN WIDTH
|
|
if ( 'no_match' == resizedColumn ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no resized column matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no resized column matched');
|
|
return;
|
|
}
|
|
|
|
var _getColumnWidth = function( _candidate_ ) {
|
|
var _width = '_not_set_';
|
|
var _options = _.isObject( _candidate_.options ) ? _candidate_.options : {};
|
|
if ( ! _.isEmpty( _options ) && _options.width && _options.width['custom-width'] ) {
|
|
_width = parseFloat( _options.width['custom-width'] * 1 );
|
|
}
|
|
return _width;
|
|
};
|
|
|
|
var _setColumnWidth = function( _candidate_, newWidthValue ) {
|
|
// start from a deep cloned object
|
|
// important => fixes https://github.com/presscustomizr/nimble-builder/issues/455
|
|
var _new_options_values = $.extend( true, {}, _candidate_.options || {} );
|
|
// april 2020 : due to a previous implementation of width option as an array, there could be a bug when trying to set the width
|
|
// because the check on _.isObject() was true for an array
|
|
// Fixes https://github.com/presscustomizr/nimble-builder/issues/620
|
|
_new_options_values.width = ( _.isObject( _new_options_values.width ) && _new_options_values.width['custom-width'] ) ? _new_options_values.width : {};
|
|
_new_options_values.width['custom-width'] = newWidthValue;
|
|
_candidate_.options = _new_options_values;
|
|
|
|
// Live update the input value ( when rendered )
|
|
self.cachedElements.$body.find('[data-sek-width-range-column-id="'+ _candidate_.id +'"]').val( newWidthValue ).trigger('input', { is_resize_column_trigger : true } );
|
|
return newWidthValue;
|
|
};
|
|
|
|
// DEPRECATED SINCE JUNE 2019 => resizedColumn.width = parseFloat( params.resizedColumnWidthInPercent );
|
|
var resizedColumnWidthInPercent = _setColumnWidth( resizedColumn, parseFloat( params.resizedColumnWidthInPercent ) );
|
|
// cast to number
|
|
resizedColumnWidthInPercent = parseFloat( resizedColumnWidthInPercent );
|
|
|
|
// SET OTHER COLUMNS WIDTH
|
|
var parentSektion = self.getLevelModel( params.in_sektion, self.updAPISetParams.newSetValue.collection );
|
|
var otherColumns = _.filter( parentSektion.collection, function( _col_ ) {
|
|
return _col_.id != resizedColumn.id && _col_.id != sistercolumn.id;
|
|
});
|
|
var otherColumnsWidth = parseFloat( resizedColumnWidthInPercent.toFixed(3) );
|
|
|
|
if ( ! _.isEmpty( otherColumns ) ) {
|
|
_.each( otherColumns, function( colModel ) {
|
|
currentColWidth = _getColumnWidth( colModel );
|
|
if ( '_not_set_' === currentColWidth || ! _.isNumber( currentColWidth * 1 ) || _.isEmpty( currentColWidth + '' ) || 1 > currentColWidth ) {
|
|
// DEPRECATED SINCE JUNE 2019 => colModel.width = parseFloat( ( 100 / params.col_number ).toFixed(3) );
|
|
currentColWidth = _setColumnWidth( colModel, parseFloat( ( 100 / params.col_number ).toFixed(3) ) );
|
|
}
|
|
|
|
// sum up all other column's width, excluding the resized and sister one.
|
|
otherColumnsWidth = parseFloat( ( otherColumnsWidth + currentColWidth ).toFixed(3) );
|
|
});
|
|
}
|
|
|
|
// SET SISTER COLUMN WIDTH
|
|
// sum up all other column's width, excluding the resized and sister one.
|
|
// api.infoLog( "resizedColumn.width", resizedColumn.width );
|
|
// api.infoLog( "otherColumns", otherColumns );
|
|
|
|
// then calculate the sistercolumn so we are sure that we feel the entire space of the sektion
|
|
// DEPRECATED SINCE JUNE 2019 => sistercolumn.width = parseFloat( ( 100 - otherColumnsWidth ).toFixed(3) );
|
|
_setColumnWidth( sistercolumn, parseFloat( ( 100 - otherColumnsWidth ).toFixed(3) ) );
|
|
// api.infoLog('otherColumnsWidth', otherColumnsWidth );
|
|
// api.infoLog("sistercolumn.width", sistercolumn.width );
|
|
// api.infoLog( "parseFloat( ( 100 - otherColumnsWidth ).toFixed(3) )" , parseFloat( ( 100 - otherColumnsWidth ).toFixed(3) ) );
|
|
//api.infoLog('COLLECTION AFTER UPDATE ', parentSektion.collection );
|
|
},
|
|
|
|
_updAPISet_sek_move_column : function() {
|
|
var self = this,
|
|
params,
|
|
sektionCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
var toSektionCandidate = self.getLevelModel( params.to_sektion, self.updAPISetParams.newSetValue.collection ),
|
|
movedColumnCandidate,
|
|
copyOfMovedColumnCandidate;
|
|
|
|
if ( _.isEmpty( toSektionCandidate ) || 'no_match' == toSektionCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing target sektion' );
|
|
}
|
|
|
|
if ( params.from_sektion != params.to_sektion ) {
|
|
// Remove the moved column from the source sektion
|
|
var fromSektionCandidate = self.getLevelModel( params.from_sektion, self.updAPISetParams.newSetValue.collection );
|
|
if ( _.isEmpty( fromSektionCandidate ) || 'no_match' == fromSektionCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing source column' );
|
|
}
|
|
|
|
fromSektionCandidate.collection = _.isArray( fromSektionCandidate.collection ) ? fromSektionCandidate.collection : [];
|
|
// Make a copy of the column candidate now, before removing it
|
|
movedColumnCandidate = self.getLevelModel( params.id, fromSektionCandidate.collection );
|
|
copyOfMovedColumnCandidate = $.extend( true, {}, movedColumnCandidate );
|
|
// remove the column from its previous sektion
|
|
fromSektionCandidate.collection = _.filter( fromSektionCandidate.collection, function( column ) {
|
|
return column.id != params.id;
|
|
});
|
|
// Reset the column's width in the target sektion
|
|
// _.each( fromSektionCandidate.collection, function( colModel ) {
|
|
// colModel.width = '';
|
|
// });
|
|
self.resetColumnsWidthInSection( fromSektionCandidate );
|
|
}
|
|
|
|
// update the target sektion
|
|
toSektionCandidate.collection = _.isArray( toSektionCandidate.collection ) ? toSektionCandidate.collection : [];
|
|
originalCollection = $.extend( true, [], toSektionCandidate.collection );
|
|
reorderedCollection = [];
|
|
_.each( params.newOrder, function( _id_ ) {
|
|
// in the case of a cross sektion movement, we need to add the moved column to the target sektion
|
|
if ( params.from_sektion != params.to_sektion && _id_ == copyOfMovedColumnCandidate.id ) {
|
|
reorderedCollection.push( copyOfMovedColumnCandidate );
|
|
} else {
|
|
columnCandidate = self.getLevelModel( _id_, originalCollection );
|
|
if ( _.isEmpty( columnCandidate ) || 'no_match' == columnCandidate ) {
|
|
throw new Error( 'updateAPISetting => moveColumn => missing columnCandidate' );
|
|
}
|
|
reorderedCollection.push( columnCandidate );
|
|
}
|
|
});
|
|
toSektionCandidate.collection = reorderedCollection;
|
|
|
|
// Reset the column's width in the target sektion
|
|
// _.each( toSektionCandidate.collection, function( colModel ) {
|
|
// colModel.width = '';
|
|
// });
|
|
self.resetColumnsWidthInSection( toSektionCandidate );
|
|
},
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData, serverControlParams
|
|
//self.updAPISetParams = {
|
|
// params : params,
|
|
// promise : $.Deferred(),
|
|
// newSetValue : _.isObject( _currentSetValue ) ? $.extend( true, {}, _currentSetValue ) : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ),
|
|
// cloneId : '',
|
|
// sectionInjectPromise
|
|
// };
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
_updAPISet_sek_add_module : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate,
|
|
startingModuleValue,// will be populated by the optional starting value specificied on module registration
|
|
position;//<= the position of the module or section or nested section in a column or location
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
// an id must be provided
|
|
if ( _.isEmpty( params.id ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing id' );
|
|
}
|
|
// a module_type must be provided
|
|
if ( _.isEmpty( params.module_type ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing module_type' );
|
|
}
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' === columnCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent column matched');
|
|
return;
|
|
}
|
|
|
|
position = 0;
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
// get the position of the before or after module or nested section
|
|
_.each( columnCandidate.collection, function( moduleOrNestedSectionModel, index ) {
|
|
if ( params.before_module_or_nested_section === moduleOrNestedSectionModel.id ) {
|
|
position = index;
|
|
}
|
|
if ( params.after_module_or_nested_section === moduleOrNestedSectionModel.id ) {
|
|
position = index + 1;
|
|
}
|
|
});
|
|
|
|
var _moduleParams = {
|
|
id : params.id,
|
|
level : 'module',
|
|
module_type : params.module_type,
|
|
ver_ini : sektionsLocalizedData.nimbleVersion
|
|
};
|
|
// Let's add the starting value if provided when registrating the module
|
|
startingModuleValue = self.getModuleStartingValue( params.module_type );
|
|
if ( 'no_starting_value' !== startingModuleValue ) {
|
|
_moduleParams.value = startingModuleValue;
|
|
}
|
|
|
|
columnCandidate.collection.splice( position, 0, _moduleParams );
|
|
},
|
|
|
|
|
|
_updAPISet_sek_duplicate_module : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
// an id must be provided
|
|
if ( _.isEmpty( params.id ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing id' );
|
|
}
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' == columnCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => no parent column matched');
|
|
return;
|
|
}
|
|
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
|
|
var deepClonedModule;
|
|
try { deepClonedModule = self.cloneLevel( params.id ); } catch( er ) {
|
|
api.errare( 'updateAPISetting => ' + params.action, er );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => error when cloning the level');
|
|
return;
|
|
}
|
|
// items id of multi-items module must always be unique
|
|
// this recursive method sniff and does the job
|
|
self.maybeGenerateNewItemIdsForCrudModules( deepClonedModule );
|
|
|
|
var insertInposition = self.getLevelPositionInCollection( params.id, self.updAPISetParams.newSetValue.collection );
|
|
self.updAPISetParams.cloneId = deepClonedModule.id;//will be passed in resolve()
|
|
columnCandidate.collection.splice( parseInt( insertInposition + 1, 10 ), 0, deepClonedModule );
|
|
},
|
|
|
|
_updAPISet_sek_remove_module : function() {
|
|
var self = this,
|
|
params,
|
|
columnCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
// an id must be provided
|
|
if ( _.isEmpty( params.id ) ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing id' );
|
|
}
|
|
columnCandidate = self.getLevelModel( params.in_column, self.updAPISetParams.newSetValue.collection );
|
|
if ( 'no_match' != columnCandidate ) {
|
|
columnCandidate.collection = _.isArray( columnCandidate.collection ) ? columnCandidate.collection : [];
|
|
columnCandidate.collection = _.filter( columnCandidate.collection, function( module ) {
|
|
return module.id != params.id;
|
|
});
|
|
|
|
} else {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no parent column matched' );
|
|
}
|
|
},
|
|
|
|
_updAPISet_sek_move_module : function() {
|
|
var self = this,
|
|
params;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
var toColumnCandidate,
|
|
movedModuleCandidate,
|
|
copyOfMovedModuleCandidate;
|
|
|
|
// loop on the sektions to find the toColumnCandidate
|
|
// _.each( self.updAPISetParams.newSetValue.collection, function( _sektion_ ) {
|
|
// _.each( _sektion_.collection, function( _column_ ) {
|
|
// if ( _column_.id == params.to_column ) {
|
|
// toColumnCandidate = _column_;
|
|
// }
|
|
// });
|
|
// });
|
|
toColumnCandidate = self.getLevelModel( params.to_column, self.updAPISetParams.newSetValue.collection );
|
|
|
|
if ( _.isEmpty( toColumnCandidate ) || 'no_match' == toColumnCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing target column' );
|
|
}
|
|
|
|
// If the module has been moved to another column
|
|
// => remove the moved module from the source column
|
|
if ( params.from_column != params.to_column ) {
|
|
var fromColumnCandidate;
|
|
fromColumnCandidate = self.getLevelModel( params.from_column, self.updAPISetParams.newSetValue.collection );
|
|
|
|
if ( _.isEmpty( fromColumnCandidate ) || 'no_match' == fromColumnCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing source column' );
|
|
}
|
|
|
|
fromColumnCandidate.collection = _.isArray( fromColumnCandidate.collection ) ? fromColumnCandidate.collection : [];
|
|
// Make a copy of the module candidate now, before removing it
|
|
movedModuleCandidate = self.getLevelModel( params.id, self.updAPISetParams.newSetValue.collection );
|
|
copyOfMovedModuleCandidate = $.extend( true, {}, movedModuleCandidate );
|
|
// remove the module from its previous column
|
|
fromColumnCandidate.collection = _.filter( fromColumnCandidate.collection, function( module ) {
|
|
return module.id != params.id;
|
|
});
|
|
}// if params.from_column != params.to_column
|
|
|
|
// update the target column
|
|
toColumnCandidate.collection = _.isArray( toColumnCandidate.collection ) ? toColumnCandidate.collection : [];
|
|
originalCollection = $.extend( true, [], toColumnCandidate.collection );
|
|
var reorderedCollection = [];
|
|
_.each( params.newOrder, function( _id_ ) {
|
|
if ( params.from_column != params.to_column && _id_ == copyOfMovedModuleCandidate.id ) {
|
|
reorderedCollection.push( copyOfMovedModuleCandidate );
|
|
} else {
|
|
moduleCandidate = self.getLevelModel( _id_, self.updAPISetParams.newSetValue.collection );
|
|
if ( _.isEmpty( moduleCandidate ) || 'no_match' == moduleCandidate ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => missing moduleCandidate' );
|
|
}
|
|
reorderedCollection.push( moduleCandidate );
|
|
}
|
|
});
|
|
// Check if we have duplicates ?
|
|
if ( reorderedCollection.length != _.uniq( reorderedCollection ).length ) {
|
|
throw new Error( 'updateAPISetting => ' + params.action + ' => there are duplicated modules in column : ' + toColumnCandidate.id );
|
|
} else {
|
|
toColumnCandidate.collection = reorderedCollection;
|
|
}
|
|
},
|
|
|
|
_updAPISet_sek_set_module_value : function() {
|
|
var self = this,
|
|
params,
|
|
moduleCandidate;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
moduleCandidate = self.getLevelModel( params.id, self.updAPISetParams.newSetValue.collection );
|
|
|
|
// Is this a multi-item module ?
|
|
// Fixes https://github.com/presscustomizr/nimble-builder/issues/616
|
|
var _ctrl_ = params.settingParams.args.moduleRegistrationParams.control,
|
|
_module_id_ = params.settingParams.args.moduleRegistrationParams.id,
|
|
parentModuleInstance = _ctrl_.czr_Module( _module_id_ );
|
|
|
|
if ( ! _.isEmpty( parentModuleInstance ) ) {
|
|
isMultiItemModule = parentModuleInstance.isMultiItem();
|
|
} else {
|
|
api.errare( 'updateAPISetting => missing parentModuleInstance', params );
|
|
}
|
|
|
|
// if multi-item module, the value is array of items, otherwise an object
|
|
var _modValueCandidate = isMultiItemModule ? [] : {};
|
|
// consider only the non empty settings for db
|
|
// booleans should bypass this check
|
|
_.each( params.value || (isMultiItemModule ? [] : {}), function( _val_, _key_ ) {
|
|
// Note : _.isEmpty( 5 ) returns true when checking an integer,
|
|
// that's why we need to cast the _val_ to a string when using _.isEmpty()
|
|
if ( ! _.isBoolean( _val_ ) && _.isEmpty( _val_ + "" ) )
|
|
return;
|
|
_modValueCandidate[ _key_ ] = _val_;
|
|
});
|
|
if ( 'no_match' == moduleCandidate ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => no module matched', params );
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => error no module matched');
|
|
return;
|
|
}
|
|
if ( _.isEmpty( params.options_type ) ) {
|
|
api.errare( 'updateAPISetting => ' + params.action + ' => missing options_type');
|
|
self.updAPISetParams.promise.reject( 'updateAPISetting => ' + params.action + ' => missing options_type');
|
|
return;
|
|
}
|
|
|
|
// Is this a father module ?
|
|
// If yes, the module value is structured by option group, each option group being updated by a child module
|
|
// If no, the default option type is : '__no_option_group_to_be_updated_by_children_modules__'
|
|
if ( '__no_option_group_to_be_updated_by_children_modules__' === params.options_type ) {
|
|
moduleCandidate.value = _modValueCandidate;
|
|
} else {
|
|
// start from a deep cloned object
|
|
// prevents issues like https://github.com/presscustomizr/nimble-builder/issues/455
|
|
var _new_module_values = $.extend( true, {}, _.isEmpty( moduleCandidate.value ) ? {} : moduleCandidate.value );
|
|
_new_module_values[ params.options_type ] = _modValueCandidate;
|
|
moduleCandidate.value = _new_module_values;
|
|
}
|
|
},
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData, serverControlParams
|
|
//self.updAPISetParams = {
|
|
// params : params,
|
|
// promise : $.Deferred(),
|
|
// newSetValue : _.isObject( _currentSetValue ) ? $.extend( true, {}, _currentSetValue ) : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ),
|
|
// cloneId : '',
|
|
// sectionInjectPromise
|
|
// };
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- FILE IMPORT
|
|
//-------------------------------------------------------------------------------------------------
|
|
_updAPISet_sek_import_from_file : function() {
|
|
var self = this,
|
|
params;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
//api.infoLog( 'sek-import-from-file', params );
|
|
|
|
if ( _.isUndefined( params.imported_content.data ) || _.isUndefined( params.imported_content.metas ) ) {
|
|
api.errare( 'updateAPISetting::sek-import-from-file => invalid imported content', imported_content );
|
|
return;
|
|
}
|
|
|
|
var importedCollection = _.isArray( params.imported_content.data.collection ) ? $.extend( true, [], params.imported_content.data.collection ) : [];
|
|
|
|
// ASSIGN MISSING LOCATIONS => IF IMPORTED LOCATIONS DON'T MATCH CURRENT PAGE LOCATIONS
|
|
// NB will import sections in the first active location of the page
|
|
// Important : header and footer must be excluded from active locations
|
|
//
|
|
// For example the current page has only the 'loop_start' location, whereas the imported content includes 3 locations :
|
|
// - after_header
|
|
// - loop_start
|
|
// - before_footer
|
|
// Among those 3 locations, 2 are not active in the page.
|
|
// We will merge all section collections from the 3 imported locations one new collection, that will be assigned to 'loop_start'
|
|
// Note that the active imported locations are ordered like they were on the page when exported.
|
|
//
|
|
// So :
|
|
// 1) identify the first active location of the page
|
|
// 2) populate a new collection of combined sections from all active imported locations.
|
|
// 3) updated the imported collection with this
|
|
if ( true === params.assign_missing_locations ) {
|
|
var importedActiveLocations = params.imported_content.metas.active_locations,
|
|
allActiveLocations = api.czr_sektions.activeLocations(),
|
|
currentActiveLocations = [];
|
|
|
|
// Set the current active locations excluding header and footer location
|
|
_.each( allActiveLocations, function( loc_id ) {
|
|
if( !self.isHeaderLocation( loc_id ) && !self.isFooterLocation( loc_id ) ) {
|
|
currentActiveLocations.push(loc_id);
|
|
}
|
|
});
|
|
|
|
// console.log('Current set value ?', api( _collectionSettingId_ )() );
|
|
// console.log('import params', params );
|
|
// console.log('importedCollection?', importedCollection );
|
|
// console.log('importedActiveLocations', importedActiveLocations );
|
|
|
|
// first active location of the current setting
|
|
var firstCurrentActiveLocationId = _.first( currentActiveLocations );
|
|
|
|
if ( !_.isEmpty( firstCurrentActiveLocationId ) && !_.isEmpty( importedActiveLocations ) && _.isArray( importedActiveLocations ) ) {
|
|
// importedActiveLocationsNotAvailableInCurrentActiveLocations
|
|
// Example :
|
|
// active location in the page : loop_start, loop_end
|
|
// active locations imported : after_header, loop_start, before_footer
|
|
// importedActiveLocationsNotAvailableInCurrentActiveLocations => after_header, before_footer
|
|
var importedActiveLocationsNotAvailableInCurrentActiveLocations = $(importedActiveLocations).not(currentActiveLocations).get(),
|
|
firstCurrentLocationData = self.getLevelModel( firstCurrentActiveLocationId, self.updAPISetParams.newSetValue.collection ),
|
|
importedTargetLocationData = self.getLevelModel( firstCurrentActiveLocationId, params.imported_content.data.collection ),
|
|
newCollectionForTargetLocation = [];// the collection that will hold the merge of all active imported collections
|
|
|
|
// normalize
|
|
// => make sure we have a collection array, even empty
|
|
firstCurrentLocationData.collection = _.isArray( firstCurrentLocationData.collection ) ? firstCurrentLocationData.collection : [];
|
|
importedTargetLocationData.collection = _.isArray( importedTargetLocationData.collection ) ? importedTargetLocationData.collection : [];
|
|
|
|
// loop on the active imported locations
|
|
// Example : ["__after_header", "__before_main_wrapper", "loop_start", "__before_footer"]
|
|
// and populate newCollectionForTargetLocation, with locations ordered as they were on export
|
|
// importedCollection is a clone
|
|
_.each( importedActiveLocations, function( impLocationId ){
|
|
var impLocationData = self.getLevelModel( impLocationId, importedCollection );
|
|
if ( _.isEmpty( impLocationData.collection ) )
|
|
return;
|
|
newCollectionForTargetLocation = _.union( newCollectionForTargetLocation, impLocationData.collection );
|
|
});//_.each( importedActiveLocations
|
|
|
|
// replace the previous collection of the target location, by the union of all collections.
|
|
// for example, if 'loop_start' is the target location, all sections will be added to it.
|
|
importedTargetLocationData.collection = newCollectionForTargetLocation;
|
|
|
|
// remove the missing locations from the imported collection
|
|
// importedActiveLocationsNotAvailableInCurrentActiveLocations
|
|
params.imported_content.data.collection = _.filter( params.imported_content.data.collection, function( _location ) {
|
|
return !_.contains( importedActiveLocationsNotAvailableInCurrentActiveLocations, _location.id );
|
|
});
|
|
}//if ( !_.isEmpty( firstCurrentActiveLocationId ) )
|
|
}//if ( true === params.assign_missing_locations )
|
|
|
|
|
|
// SHALL WE MERGE ?
|
|
// Sept 2019 note : for local import only. Not implemented for global https://github.com/presscustomizr/nimble-builder/issues/495
|
|
// loop on each location of the imported content
|
|
// if the current setting value has sections in a location, add them before the imported ones
|
|
// keep_existing_sections is a user check option
|
|
// @see PHP sek_get_module_params_for_sek_local_imp_exp()
|
|
if ( true === params.keep_existing_sections ) {
|
|
// note that importedCollection is a unlinked clone of params.imported_content.data.collection
|
|
// merge sections
|
|
_.each( importedCollection, function( imp_location_data ) {
|
|
var currentLocationData = self.getLevelModel( imp_location_data.id, self.updAPISetParams.newSetValue.collection );
|
|
if ( _.isEmpty( currentLocationData.collection ) )
|
|
return;
|
|
|
|
var importedLocationData = self.getLevelModel( imp_location_data.id, params.imported_content.data.collection );
|
|
importedLocationData.collection = _.union( currentLocationData.collection, importedLocationData.collection );
|
|
});
|
|
|
|
// merge fonts if needed
|
|
var currentFonts = self.updAPISetParams.newSetValue.fonts,
|
|
importedFonts = params.imported_content.data.fonts;
|
|
|
|
if ( currentFonts && !_.isEmpty( currentFonts ) && _.isArray( currentFonts ) ) {
|
|
importedFonts = _.isArray( importedFonts ) ? importedFonts : [];
|
|
// merge and remove duplicated fonts
|
|
params.imported_content.data.fonts = _.uniq( _.union( currentFonts, importedFonts ) );
|
|
}
|
|
}// if true === params.merge
|
|
|
|
self.updAPISetParams.newSetValue = params.imported_content.data;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData, serverControlParams
|
|
//self.updAPISetParams = {
|
|
// params : params,
|
|
// promise : $.Deferred(),
|
|
// newSetValue : _.isObject( _currentSetValue ) ? $.extend( true, {}, _currentSetValue ) : self.getDefaultSektionSettingValue( params.is_global_location ? 'global' : 'local' ),
|
|
// cloneId : '',
|
|
// sectionInjectPromise
|
|
// };
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- INJECT FROM TMPL GALLERY
|
|
//-------------------------------------------------------------------------------------------------
|
|
// self.updAPISetParams.params : {
|
|
// action: "sek-inject-tmpl-from-gallery"
|
|
// assign_missing_locations: undefined
|
|
// cloneId: ""
|
|
// injected_content: {data: {…}, metas: {…}, img_errors: Array(0)}
|
|
// is_global_location: false
|
|
// scope: "local"
|
|
// tmpl_inject_mode: "replace"
|
|
// }
|
|
_updAPISet_sek_inject_tmpl_from_gallery : function() {
|
|
var self = this,
|
|
params;
|
|
|
|
params = self.updAPISetParams.params;
|
|
|
|
//api.infoLog( 'api update params for sek-inject-tmpl-from-gallery', params );
|
|
|
|
// DO WE HAVE PROPER CONTENT DO INJECT ?
|
|
if ( _.isUndefined( params.injected_content.data ) || _.isUndefined( params.injected_content.metas ) ) {
|
|
api.errare( 'updateAPISetting::sek-inject-tmpl-from-gallery => invalid imported content', injected_content );
|
|
return;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- HELPERS
|
|
//-------------------------------------------------------------------------------------------------
|
|
var _allTmplLocationsExistLocally = function() {
|
|
var bool = true;
|
|
_.each( tmplLocations, function( loc_id ){
|
|
if (!bool)
|
|
return;
|
|
bool = _.contains(localLocations, loc_id);
|
|
});
|
|
return bool;
|
|
};
|
|
|
|
// @return bool
|
|
var _isTmplHeaderLocId = function( loc_id ) {
|
|
return params.injected_content && params.injected_content.metas && loc_id === params.injected_content.metas.tmpl_header_location;
|
|
};
|
|
|
|
// @return bool
|
|
var _isTmplFooterLocId = function( loc_id ) {
|
|
return params.injected_content && params.injected_content.metas && loc_id === params.injected_content.metas.tmpl_footer_location;
|
|
};
|
|
|
|
// The template has a header/footer if we find the header or the footer location
|
|
// AND
|
|
// if there's a local_header_footer property set in the local_options
|
|
var _hasTmplHeaderFooter = function() {
|
|
var hasHeaderFooterLoc = false;
|
|
_.each( tmplLocations, function( loc_id ){
|
|
if (hasHeaderFooterLoc)
|
|
return;
|
|
|
|
if ( _isTmplHeaderLocId( loc_id ) || _isTmplFooterLocId( loc_id ) ) {
|
|
hasHeaderFooterLoc = self.getLevelModel( loc_id, tmplCollection );
|
|
hasHeaderFooterLoc = 'no_match' != hasHeaderFooterLoc;
|
|
}
|
|
});
|
|
return hasHeaderFooterLoc && !_.isEmpty( tmplLocalOptions.local_header_footer );
|
|
};
|
|
|
|
var _tmplUsesNBtemplate = function() {
|
|
return tmplLocalOptions && tmplLocalOptions.template && 'nimble_template' === tmplLocalOptions.template.local_template;
|
|
};
|
|
|
|
var tmplCollection = _.isArray( params.injected_content.data.collection ) ? $.extend( true, [], params.injected_content.data.collection ) : [],
|
|
tmplLocations = params.injected_content.metas.tmpl_locations,
|
|
localLocations = [],
|
|
currentSettingCollection = self.updAPISetParams.newSetValue.collection;
|
|
|
|
|
|
// Set the current local locations, make sure we exclude all global locations
|
|
_.each( api.czr_sektions.activeLocations(), function( loc_id ) {
|
|
if( !self.isGlobalLocationId(loc_id) ) {
|
|
localLocations.push(loc_id);
|
|
}
|
|
});
|
|
|
|
// Imported Active Locations has to be an array not empty
|
|
if ( !_.isArray(tmplLocations) || _.isEmpty(tmplLocations) ) {
|
|
api.errare( 'updateAPISetting::sek-inject-tmpl-from-gallery => invalid imported template locations', params );
|
|
return;
|
|
}
|
|
|
|
// TEMPLATE LOCAL OPTIONS and FONTS
|
|
// Important :
|
|
// - Local options is structured as an object : { local_header_footer: {…}, widths: {…}} }. But when not populated, it can be an array []. So make sure the type if set as object before merging it with current page local options
|
|
// - Fonts is a collection described with an array
|
|
var tmplLocalOptions = params.injected_content.data.local_options;
|
|
tmplLocalOptions = $.extend( true, {}, _.isObject( tmplLocalOptions ) ? tmplLocalOptions : {} );
|
|
var tmplFonts = params.injected_content.data.fonts;
|
|
tmplFonts = _.isArray( tmplFonts ) ? $.extend( true, [], tmplFonts ) : [];
|
|
|
|
// Define variables uses for all cases
|
|
var newSetValueCollection = $.extend( true, [], currentSettingCollection ),// Create a deep copy of the current API collection
|
|
_allContentSectionsInTmpl = [],
|
|
targetLocationId = '__not_set__',
|
|
locModel,
|
|
targetLocationModel,
|
|
tmplLocCandidate, localLocCandidate;
|
|
|
|
// Gather all template content sections from potentially multiple locations in one collection
|
|
// => header and footer locations are excluded from this collection
|
|
// This collection is used :
|
|
// - in 'replace' mode when template locations don't exists in the local context
|
|
// - in 'before' and 'after' mode
|
|
// Note : if this collection is used the template header and footer ( if any ) have to be added separately
|
|
_.each( tmplCollection, function( loc_data ){
|
|
if ( _isTmplHeaderLocId( loc_data.id ) || _isTmplFooterLocId( loc_data.id ) )
|
|
return;
|
|
if( !_.isEmpty( loc_data.collection ) ) {
|
|
_allContentSectionsInTmpl = _.union( _allContentSectionsInTmpl, loc_data.collection );
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// console.log('_hasTmplHeaderFooter ?', _hasTmplHeaderFooter() );
|
|
|
|
// console.log('_allContentSectionsInTmpl ?', _allContentSectionsInTmpl);
|
|
// console.log('NEW SET VALUE COLLECTION? ', $.extend( true, [], newSetValueCollection ) );
|
|
// If the current page already has NB sections, the user can chose 3 options : REPLACE, BEFORE, AFTER.
|
|
// when the page has no NB sections, the default option is REPLACE
|
|
switch( params.tmpl_inject_mode ) {
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- REPLACE CASE ( default case )
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'replace' :
|
|
// api.infoLog('CURRENT SETTING VALUE ?', self.updAPISetParams.newSetValue );
|
|
// console.log('SO COLLECTION BEFORE ?', tmplCollection );
|
|
// return bool
|
|
|
|
// IF ALL TEMPLATE LOCATIONS EXIST IN CURRENT PAGE
|
|
// Loop on local locations, use template locations when exists, otherwise use local ones
|
|
if ( _allTmplLocationsExistLocally() ) {
|
|
// Replace locations from local collection that are provided by the tmpl, and not empty
|
|
// => if the header / footer template location is empty, keep the local one
|
|
// => the tmpl location will replace the local location in the collection
|
|
newSetValueCollection = [];
|
|
var resetLocalLocation, newLocalLocation;
|
|
_.each( currentSettingCollection, function( _localLocation ) {
|
|
tmplLocCandidate = _.findWhere(tmplCollection, { id : _localLocation.id }) || {};
|
|
if ( _.isEmpty( tmplLocCandidate.collection ) ) {
|
|
if ( self.isHeaderLocation( _localLocation.id ) || self.isFooterLocation( _localLocation.id ) ) {
|
|
newSetValueCollection.push( _localLocation );
|
|
} else {
|
|
// Reset previous local location to defaults
|
|
resetLocalLocation = { collection : [], options :[] };
|
|
newLocalLocation = $.extend( true, {}, _localLocation );
|
|
newLocalLocation = $.extend( newLocalLocation, resetLocalLocation );
|
|
newSetValueCollection.push( newLocalLocation );
|
|
}
|
|
} else {
|
|
newSetValueCollection.push( tmplLocCandidate );
|
|
}
|
|
});
|
|
// console.log('tmplCollection ??', tmplCollection );
|
|
// console.log('localLocations ??', localLocations);
|
|
} else {
|
|
// IF TEMPLATE LOCATIONS DO NOT MATCH THE ONES OF THE CURRENT PAGE => ASSIGN ALL TEMPLATE SECTIONS TO LOOP_START OR First local content location
|
|
if ( _tmplUsesNBtemplate() ) {
|
|
targetLocationId = 'loop_start';
|
|
} else {
|
|
if ( _.contains(localLocations, 'loop_start') ) {
|
|
targetLocationId = 'loop_start';
|
|
} else {
|
|
_.each( localLocations, function( loc_id ) {
|
|
if ( !self.isHeaderLocation( loc_id ) && !self.isFooterLocation( loc_id ) ) {
|
|
targetLocationId = loc_id;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
// At this point, we need a target location id
|
|
if ( '__not_set__' === targetLocationId ) {
|
|
api.errare( 'updateAPISetting::sek-inject-tmpl-from-gallery => target location id is empty' );
|
|
break;
|
|
}
|
|
|
|
// Get the current target location model
|
|
targetLocationModel = self.getLevelModel( targetLocationId, newSetValueCollection );
|
|
if ( 'no_match' === targetLocationModel ) {
|
|
api.errare('::_updAPISet_sek_inject_tmpl_from_gallery => error => target location id ' + targetLocationId );
|
|
break;
|
|
}
|
|
targetLocationModel = $.extend( true, {}, targetLocationModel );// <= create a deep copy
|
|
|
|
// Replace the target location collection with the template one
|
|
targetLocationModel.collection = _allContentSectionsInTmpl;
|
|
|
|
// remove all locations from future setting value
|
|
newSetValueCollection = [];
|
|
|
|
// If the template has a header/footer use it
|
|
// else, if a header footer is defined locally
|
|
if ( _hasTmplHeaderFooter() ) {
|
|
_.each( tmplLocations, function( loc_id ) {
|
|
if ( _isTmplHeaderLocId( loc_id ) || _isTmplFooterLocId( loc_id ) ) {
|
|
tmplLocCandidate = self.getLevelModel( loc_id, tmplCollection );
|
|
if ( 'no_match' === tmplLocCandidate ) {
|
|
api.errare('::_updAPISet_sek_inject_tmpl_from_gallery => error => location id ' + loc_id +' not found in template collection');
|
|
return;
|
|
} else {
|
|
newSetValueCollection.push( tmplLocCandidate );
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// Populate the local target location with the template section collection
|
|
// AND
|
|
// Re-populate the header and footer location, either with the local one, or the template one ( if any)
|
|
_.each( localLocations, function( loc_id ) {
|
|
if ( targetLocationId === loc_id ) {
|
|
newSetValueCollection.push( targetLocationModel );
|
|
}
|
|
localLocModel = self.getLevelModel( loc_id, currentSettingCollection );
|
|
if ( 'no_match' === localLocModel ) {
|
|
api.errare('::_updAPISet_sek_inject_tmpl_from_gallery => error => location id ' + loc_id +' not found in current setting collection');
|
|
return;
|
|
}
|
|
// re-add header and footer if _hasTmplHeaderFooter()
|
|
if ( !_hasTmplHeaderFooter() ) {
|
|
if ( self.isHeaderLocation( loc_id ) || self.isFooterLocation( loc_id ) ) {
|
|
newSetValueCollection.push( localLocModel );
|
|
}
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- INJECT BEFORE CASE
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'before' :
|
|
// For the before case, we are sure that hasCurrentPageNBSectionsNotHeaderFooter() is true
|
|
// so there's at least one location that has section(s)
|
|
// Find the first non header/footer location not empty
|
|
_.each( localLocations, function( loc_id ){
|
|
// stop if the location id has been found
|
|
if ( '__not_set__' != targetLocationId )
|
|
return;
|
|
|
|
locModel = self.getLevelModel( loc_id, newSetValueCollection );
|
|
if ( 'no_match' === locModel ) {
|
|
api.errare('::_updAPISet_sek_inject_tmpl_from_gallery => error => location id not found' + loc_id );
|
|
return;
|
|
}
|
|
if ( !self.isHeaderLocation( loc_id ) && !self.isFooterLocation( loc_id ) ) {
|
|
if ( !_.isEmpty( locModel.collection ) ) {
|
|
targetLocationId = loc_id;
|
|
targetLocationModel = locModel;
|
|
}
|
|
}
|
|
});
|
|
|
|
// At this point, we need a target location id
|
|
if ( '__not_set__' === targetLocationId ) {
|
|
api.errare( 'updateAPISetting::sek-inject-tmpl-from-gallery => target location id is empty' );
|
|
break;
|
|
}
|
|
|
|
// Get the current target location model
|
|
targetLocationModel = $.extend( true, {}, targetLocationModel );
|
|
|
|
// Adds the template sections BEFORE the existing sections of the target location
|
|
targetLocationModel.collection = _.union( _allContentSectionsInTmpl, targetLocationModel.collection );
|
|
|
|
// remove all locations from future setting value
|
|
newSetValueCollection = [];
|
|
|
|
// Re-populate the location models previously removed the updated target location model
|
|
_.each( localLocations, function( loc_id ){
|
|
if ( targetLocationId === loc_id ) {
|
|
newSetValueCollection.push( targetLocationModel );
|
|
} else {
|
|
if ( 'no_match' === locModel ) {
|
|
api.errare('::_updAPISet_sek_inject_tmpl_from_gallery => error => location id not found' + loc_id );
|
|
return;
|
|
}
|
|
newSetValueCollection.push( self.getLevelModel( loc_id, currentSettingCollection ) );
|
|
}
|
|
});
|
|
break;
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- INJECT AFTER CASE
|
|
//-------------------------------------------------------------------------------------------------
|
|
case 'after' :
|
|
// For the after case, we are sure that hasCurrentPageNBSectionsNotHeaderFooter() is true
|
|
// so there's at least one location that has section(s)
|
|
// Find the last non header/footer location not empty
|
|
_.each( localLocations.reverse(), function( loc_id ){
|
|
// stop if the location id has been found
|
|
if ( '__not_set__' != targetLocationId )
|
|
return;
|
|
|
|
locModel = self.getLevelModel( loc_id, newSetValueCollection );
|
|
if ( 'no_match' === locModel ) {
|
|
api.errare('::_updAPISet_sek_inject_tmpl_from_gallery => error => location id not found' + loc_id );
|
|
return;
|
|
}
|
|
if ( !self.isHeaderLocation( loc_id ) && !self.isFooterLocation( loc_id ) ) {
|
|
if ( !_.isEmpty( locModel.collection ) ) {
|
|
targetLocationId = loc_id;
|
|
targetLocationModel = locModel;
|
|
}
|
|
}
|
|
});
|
|
|
|
// At this point, we need a target location id
|
|
if ( '__not_set__' === targetLocationId ) {
|
|
api.errare( 'updateAPISetting::sek-inject-tmpl-from-gallery => target location id is empty' );
|
|
break;
|
|
}
|
|
|
|
// Get the current target location model
|
|
targetLocationModel = $.extend( true, {}, targetLocationModel );
|
|
|
|
// Adds the template sections AFTER the existing sections of the target location
|
|
targetLocationModel.collection = _.union( targetLocationModel.collection, _allContentSectionsInTmpl );
|
|
|
|
// remove all locations from future setting value
|
|
newSetValueCollection = [];
|
|
|
|
// Re-populate the location models previously removed the updated target location model
|
|
_.each( localLocations, function( loc_id ){
|
|
if ( targetLocationId === loc_id ) {
|
|
newSetValueCollection.push( targetLocationModel );
|
|
} else {
|
|
locModel = self.getLevelModel( loc_id, currentSettingCollection );
|
|
if ( 'no_match' === locModel ) {
|
|
api.errare('::_updAPISet_sek_inject_tmpl_from_gallery => error => loc id not found' + loc_id );
|
|
return;
|
|
}
|
|
newSetValueCollection.push( locModel );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
|
|
// update the API setting
|
|
// this is a candiate setting value, the new setting value will be validated in ::updateAPISetting => ::validateSettingValue()
|
|
self.updAPISetParams.newSetValue.collection = newSetValueCollection;
|
|
|
|
// LOCAL OPTIONS
|
|
// local_options states if the imported template uses nimble_template, or use custom_width, custom_css, performance, etc.. see the full list of local options in ::generateUIforLocalSkopeOptions
|
|
// Design decision : by default NB extends existing local options with the imported ones.
|
|
// import mode :
|
|
// 'replace' (default) => local options extended
|
|
// insert 'before' or 'after' => existing local options are preserved
|
|
//
|
|
// Scenario :
|
|
// 1) user has created NB sections on a single post and wants to insert a NB template before the existing sections ( 'before' inject_mode )
|
|
// => in this case, we need to keep the default theme template, local options must be the existing ones => no extension of local options.
|
|
//
|
|
// 2) the current page has no NB sections yet, import mode is 'replace' by default
|
|
// => it means that if the imported template uses NB template as canvas, it must be set in local options => extension of local options
|
|
if ( !_.isEmpty( tmplLocalOptions ) && 'replace' === params.tmpl_inject_mode ) {
|
|
var currentLocalOptions = self.updAPISetParams.newSetValue.local_options;
|
|
currentLocalOptions = $.extend( true, {}, _.isObject( currentLocalOptions ) ? currentLocalOptions : {} );
|
|
self.updAPISetParams.newSetValue.local_options = _.extend( currentLocalOptions, tmplLocalOptions );
|
|
}
|
|
|
|
// FONTS
|
|
// If there are imported fonts, we need to merge when import mode is not 'replace', otherwise we need to copy the imported font collection in .fonts property of the API setting.
|
|
if ( _.isArray( tmplFonts ) && !_.isEmpty( tmplFonts ) ) {
|
|
if ( 'replace' != params.tmpl_inject_mode ) {
|
|
var currentFonts = self.updAPISetParams.newSetValue.fonts;
|
|
currentFonts = $.extend( true, [], _.isArray( currentFonts ) ? currentFonts : [] );
|
|
// merge two collection of fonts without duplicates
|
|
self.updAPISetParams.newSetValue.fonts = _.uniq( _.union( tmplFonts, currentFonts ));
|
|
} else {
|
|
self.updAPISetParams.newSetValue.fonts = tmplFonts;
|
|
}
|
|
}
|
|
|
|
//api.infoLog('SETTING VALUE AFTER ?', self.updAPISetParams.newSetValue );
|
|
}//_updAPISet_sek_inject_tmpl_from_gallery
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
//@return void()
|
|
// clean registered controls, sections, panels
|
|
// only elements that have a true "track" param on registration are populated in the registered() collection
|
|
// if the _id_ param is not specified, all registered controls, sections and panels are removed.
|
|
//
|
|
// preserve the settings => because this is where the customizer changeset of values is persisted before publishing
|
|
// typically fired before updating the ui. @see ::generateUI()
|
|
//
|
|
// March 2021 => also clean large select options like fontPicker which generates thousands of lines and slow down the UI dramatically if kept
|
|
cleanRegisteredAndLargeSelectInput : function( _id_ ) {
|
|
var self = this,
|
|
registered = $.extend( true, [], self.registered() || [] );
|
|
|
|
// added for https://github.com/presscustomizr/nimble-builder/issues/403
|
|
// in order to remove all instantiations of WP editor
|
|
// @see ::initialize()
|
|
api.trigger('sek-before-clean-registered');
|
|
|
|
registered = _.filter( registered, function( _reg_ ) {
|
|
if ( 'setting' !== _reg_.what ) {
|
|
if ( api[ _reg_.what ].has( _reg_.id ) ) {
|
|
if ( ! _.isEmpty( _id_ ) && _reg_.id !== _id_ )
|
|
return;
|
|
// fire an event before removal, can be used to clean some jQuery plugin instance for example
|
|
if ( _.isFunction( api[ _reg_.what ]( _reg_.id ).trigger ) ) {//<= Section and Panel constructor are not extended with the Event class, that's why we check if this method exists
|
|
self.trigger( 'sek-ui-pre-removal', { what : _reg_.what, id : _reg_.id } );
|
|
}
|
|
$.when( api[ _reg_.what ]( _reg_.id ).container.remove() ).done( function() {
|
|
// remove control, section, panel
|
|
api[ _reg_.what ].remove( _reg_.id );
|
|
// useful event, used to destroy the $ drop plugin instance for the section / module picker
|
|
self.trigger( 'sek-ui-removed', { what : _reg_.what, id : _reg_.id } );
|
|
});
|
|
}
|
|
}
|
|
return _reg_.what === 'setting';
|
|
});
|
|
self.registered( registered );
|
|
|
|
// March 2021
|
|
// clean font picker markup, which generates thousands of select options lines and slow down the entire UI when kept
|
|
// This concerns the global options and local options for which controls are not cleaned like the one of the levels UI
|
|
self.cachedElements.$body.find('[data-input-type="font_picker"]').each( function() {
|
|
var currentInputVal = $(this).find('select[data-czrtype]').val();
|
|
// clean select 2 instance + all select options
|
|
if ( !_.isUndefined( $(this).find('select[data-czrtype]').data('czrSelect2') ) ) {
|
|
$(this).find('select[data-czrtype]').czrSelect2('destroy');
|
|
}
|
|
$(this).find('select[data-czrtype]').html('');
|
|
|
|
// append the current input val
|
|
$(this).find('select[data-czrtype]').html('').append( $('<option>', {
|
|
value : currentInputVal,
|
|
html: currentInputVal,
|
|
selected : "selected"
|
|
}));
|
|
|
|
$(this).find('select[data-czrtype]').data('selectOptionsSet', false );
|
|
});
|
|
|
|
},
|
|
|
|
|
|
// This action can be fired after an import, to update the local settings with the imported values
|
|
cleanRegisteredLocalOptionSettingsAndControls : function() {
|
|
var self = this,
|
|
localOptionPrefix = self.getLocalSkopeOptionId(),
|
|
registered = $.extend( true, [], self.registered() || [] );
|
|
|
|
registered = _.filter( registered, function( _reg_ ) {
|
|
// Remove the local setting
|
|
if ( _reg_.id && -1 !== _reg_.id.indexOf( localOptionPrefix ) && api.has( _reg_.id ) ) {
|
|
api.remove( _reg_.id );
|
|
}
|
|
// Remove the local control
|
|
if ( _reg_.id && -1 !== _reg_.id.indexOf( localOptionPrefix ) && api.control.has( _reg_.id ) ) {
|
|
$.when( api.control( _reg_.id ).container.remove() ).done( function() {
|
|
// remove control, section, panel
|
|
api.control.remove( _reg_.id );
|
|
});
|
|
}
|
|
// keep only the setting not local
|
|
return _reg_.id && -1 === _reg_.id.indexOf( localOptionPrefix );
|
|
});
|
|
self.registered( registered );
|
|
},
|
|
|
|
|
|
// Keep only the settings for global option, local options, content picker
|
|
// Remove all the other
|
|
// The level ( section, column module ) settings can be identified because they are registered with a level property
|
|
cleanRegisteredLevelSettings : function() {
|
|
var self = this,
|
|
registered = $.extend( true, [], self.registered() || [] );
|
|
|
|
registered = _.filter( registered, function( _reg_ ) {
|
|
// We check if the level property is empty
|
|
// if not empty, we can remove the setting from the api.
|
|
if ( ! _.isEmpty( _reg_.level ) && 'setting' === _reg_.what && api.has( _reg_.id ) ) {
|
|
// remove setting from the api
|
|
api.remove( _reg_.id );
|
|
}
|
|
// we keep only the setting with
|
|
// so we preserve the permanent options like global options, local options, content picker
|
|
return _.isEmpty( _reg_.level ) && 'setting' === _reg_.what ;
|
|
});
|
|
self.registered( registered );
|
|
}
|
|
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
// @eturn void()
|
|
rootPanelFocus : function() {
|
|
//close everything
|
|
if ( api.section.has( api.czr_activeSectionId() ) ) {
|
|
api.section( api.czr_activeSectionId() ).expanded( false );
|
|
} else {
|
|
api.section.each( function( _s ) {
|
|
_s.expanded( false );
|
|
});
|
|
}
|
|
api.panel.each( function( _p ) {
|
|
_p.expanded( false );
|
|
});
|
|
},
|
|
|
|
//@return a global unique identifier
|
|
guid : function() {
|
|
function s4() {
|
|
return Math.floor((1 + Math.random()) * 0x10000)
|
|
.toString(16)
|
|
.substring(1);
|
|
}
|
|
return s4() + s4() + s4();//s4() + s4() + s4() + s4() + s4() + s4();
|
|
},
|
|
|
|
//@return a string "nimble___[skp__global]"
|
|
getGlobalSectionsSettingId : function() {
|
|
return sektionsLocalizedData.settingIdForGlobalSections;
|
|
},
|
|
|
|
|
|
// @params = { id : '', level : '' }
|
|
// Recursively walk the level tree until a match is found
|
|
// @return the level model object
|
|
getParentSectionFromColumnId : function( id, collection ) {
|
|
var self = this, _section_model_ = 'no_match',
|
|
// @param id mandatory
|
|
// @param collection mandatory
|
|
// @param collectionSettingId optional
|
|
// @param localOrGlobal optional
|
|
_walk_ = function( id, collection, collectionSettingId, localOrGlobal ) {
|
|
// do we have a collection ?
|
|
// if not, let's use the root one
|
|
if ( _.isUndefined( collection ) ) {
|
|
var currentSektionSettingValue = api( collectionSettingId )();
|
|
var sektionSettingValue = _.isObject( currentSektionSettingValue ) ? $.extend( true, {}, currentSektionSettingValue ) : $.extend( true, {}, self.getDefaultSektionSettingValue( localOrGlobal ) );
|
|
collection = _.isArray( sektionSettingValue.collection ) ? sektionSettingValue.collection : [];
|
|
}
|
|
_.each( collection, function( levelData ) {
|
|
// did we found a match recursively ?
|
|
if ( 'no_match' != _section_model_ )
|
|
return;
|
|
|
|
var colCandidate;
|
|
if ( 'section' == levelData.level ) {
|
|
colCandidate = _.findWhere( levelData.collection, { id : id });
|
|
}
|
|
if ( ! _.isEmpty( colCandidate ) ) {
|
|
// we found our column in this section
|
|
_section_model_ = levelData;
|
|
} else {
|
|
if ( _.isArray( levelData.collection ) ) {
|
|
_walk_( id, levelData.collection, collectionSettingId, localOrGlobal );
|
|
}
|
|
}
|
|
});
|
|
return _section_model_;
|
|
};
|
|
|
|
// if a collection has been provided in the signature, let's walk it.
|
|
// Otherwise, let's walk the local and global ones until a match is found.
|
|
if ( ! _.isEmpty( collection ) ) {
|
|
_walk_( id, collection );
|
|
} else {
|
|
_.each( {
|
|
local : self.localSectionsSettingId(),
|
|
global : self.getGlobalSectionsSettingId()
|
|
}, function( collectionSettingId, localOrGlobal ) {
|
|
if ( 'no_match' === _section_model_ ) {
|
|
_walk_( id, collection, collectionSettingId, localOrGlobal );
|
|
}
|
|
});
|
|
}
|
|
|
|
return _section_model_;
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- LOCATION HELPERS
|
|
//-------------------------------------------------------------------------------------------------
|
|
// used in react to preview or update api settings
|
|
// @params is an object {
|
|
//
|
|
// }
|
|
isGlobalLocation : function( params ) {
|
|
var self = this, is_global_location = false;
|
|
params = params || {};
|
|
if ( _.has( params, 'is_global_location' ) ) {
|
|
is_global_location = params.is_global_location;
|
|
} else if ( _.has( params, 'scope' ) ) {
|
|
is_global_location = 'global' === params.scope;
|
|
} else if ( !_.isEmpty( params.location ) ) {
|
|
is_global_location = self.isChildOfAGlobalLocation( params.location );
|
|
} else if ( !_.isEmpty( params.in_sektion ) ) {
|
|
is_global_location = self.isChildOfAGlobalLocation( params.in_sektion );
|
|
} else if ( !_.isEmpty( params.id ) ) {
|
|
is_global_location = self.isChildOfAGlobalLocation( params.id );
|
|
}
|
|
return is_global_location;
|
|
},
|
|
|
|
// @params = { id : '', level : '' }
|
|
// Recursively walk the level tree until a match is found
|
|
// @return the level model object
|
|
isChildOfAGlobalLocation : function( id ) {
|
|
var self = this,
|
|
walkCollection = function( id, collection ) {
|
|
var _data_ = 'no_match';
|
|
// do we have a collection ?
|
|
// if not, let's use the root global one
|
|
if ( _.isUndefined( collection ) ) {
|
|
var currentSettingValue = api( self.getGlobalSectionsSettingId() )();
|
|
var sektionSettingValue = _.isObject( currentSettingValue ) ? $.extend( true, {}, currentSettingValue ) : self.getDefaultSektionSettingValue( 'global' );
|
|
collection = _.isArray( sektionSettingValue.collection ) ? sektionSettingValue.collection : [];
|
|
}
|
|
_.each( collection, function( levelData ) {
|
|
// did we found a match recursively ?
|
|
if ( 'no_match' != _data_ )
|
|
return;
|
|
if ( id === levelData.id ) {
|
|
_data_ = levelData;
|
|
} else {
|
|
if ( _.isArray( levelData.collection ) ) {
|
|
_data_ = walkCollection( id, levelData.collection );
|
|
}
|
|
}
|
|
});
|
|
return _data_;
|
|
};
|
|
return walkCollection( id ) !== 'no_match';
|
|
},
|
|
|
|
// @return bool
|
|
isGlobalLocationId : function( id ) {
|
|
var _locCollection = this.activeLocationsInfo(),
|
|
_currentLocInfo = !_.isArray( _locCollection ) ? {} : _.findWhere( _locCollection, { id : id } );
|
|
return _.isObject( _currentLocInfo ) && _currentLocInfo.is_global;
|
|
},
|
|
|
|
// @return bool
|
|
// June 2020 : introduced for https://github.com/presscustomizr/nimble-builder-pro/issues/6
|
|
isHeaderLocation : function( id ) {
|
|
var _locCollection = this.activeLocationsInfo(),
|
|
_currentLocInfo = !_.isArray( _locCollection ) ? {} : _.findWhere( _locCollection, { id : id } );
|
|
return _.isObject( _currentLocInfo ) && _currentLocInfo.is_header;
|
|
},
|
|
|
|
// @return bool
|
|
// June 2020 : introduced for https://github.com/presscustomizr/nimble-builder-pro/issues/6
|
|
isFooterLocation : function( id ) {
|
|
var _locCollection = this.activeLocationsInfo(),
|
|
_currentLocInfo = !_.isArray( _locCollection ) ? {} : _.findWhere( _locCollection, { id : id } );
|
|
return _.isObject( _currentLocInfo ) && _currentLocInfo.is_footer;
|
|
},
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- LEVEL HELPERS
|
|
//-------------------------------------------------------------------------------------------------
|
|
// @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',
|
|
// @param id mandatory
|
|
// @param collection mandatory
|
|
// @param collectionSettingId optional
|
|
// @param localOrGlobal optional
|
|
_walk_ = function( id, collection, collectionSettingId, localOrGlobal ) {
|
|
// do we have a collection ?
|
|
// if not, let's use the root one
|
|
if ( _.isUndefined( collection ) ) {
|
|
var currentSektionSettingValue = api( collectionSettingId )();
|
|
var sektionSettingValue = _.isObject( currentSektionSettingValue ) ? $.extend( true, {}, currentSektionSettingValue ) : $.extend( true, {}, self.getDefaultSektionSettingValue( localOrGlobal ) );
|
|
collection = _.isArray( sektionSettingValue.collection ) ? sektionSettingValue.collection : [];
|
|
}
|
|
_.each( collection, function( levelData ) {
|
|
// did we found a match recursively ?
|
|
if ( 'no_match' != _data_ )
|
|
return;
|
|
if ( id === levelData.id ) {
|
|
_data_ = levelData;
|
|
} else {
|
|
if ( _.isArray( levelData.collection ) ) {
|
|
_walk_( id, levelData.collection, collectionSettingId, localOrGlobal );
|
|
}
|
|
}
|
|
});
|
|
return _data_;
|
|
};
|
|
|
|
// if a collection has been provided in the signature, let's walk it.
|
|
// Otherwise, let's walk the local and global ones until a match is found.
|
|
if ( ! _.isEmpty( collection ) ) {
|
|
_walk_( id, collection );
|
|
} else {
|
|
_.each( {
|
|
local : self.localSectionsSettingId(),
|
|
global : self.getGlobalSectionsSettingId()
|
|
}, function( collectionSettingId, localOrGlobal ) {
|
|
if ( 'no_match' === _data_ ) {
|
|
_walk_( id, collection, collectionSettingId, localOrGlobal );
|
|
}
|
|
});
|
|
}
|
|
|
|
return _data_;
|
|
},
|
|
|
|
getLevelPositionInCollection : function( id, collection ) {
|
|
var self = this, _position_ = 'no_match',
|
|
// @param id mandatory
|
|
// @param collection mandatory
|
|
// @param collectionSettingId optional
|
|
// @param localOrGlobal optional
|
|
_walk_ = function( id, collection, collectionSettingId, localOrGlobal ) {
|
|
// do we have a collection ?
|
|
// if not, let's use the root one
|
|
if ( _.isUndefined( collection ) ) {
|
|
var currentSektionSettingValue = api( collectionSettingId )();
|
|
var sektionSettingValue = _.isObject( currentSektionSettingValue ) ? $.extend( true, {}, currentSektionSettingValue ) : $.extend( true, {}, self.getDefaultSektionSettingValue( localOrGlobal ) );
|
|
collection = _.isArray( sektionSettingValue.collection ) ? sektionSettingValue.collection : [];
|
|
}
|
|
_.each( collection, function( levelData, _key_ ) {
|
|
// did we find a match recursively ?
|
|
if ( 'no_match' != _position_ )
|
|
return;
|
|
if ( id === levelData.id ) {
|
|
_position_ = _key_;
|
|
} else {
|
|
if ( _.isArray( levelData.collection ) ) {
|
|
_walk_( id, levelData.collection, collectionSettingId, localOrGlobal );
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// if a collection has been provided in the signature, let's walk it.
|
|
// Otherwise, let's walk the local and global ones until a match is found.
|
|
if ( ! _.isEmpty( collection ) ) {
|
|
_walk_( id, collection );
|
|
} else {
|
|
_.each( {
|
|
local : self.localSectionsSettingId(),
|
|
global : self.getGlobalSectionsSettingId()
|
|
}, function( collectionSettingId, localOrGlobal ) {
|
|
if ( 'no_match' === _position_ ) {
|
|
_walk_( id, collectionSettingId, localOrGlobal, collection );
|
|
}
|
|
});
|
|
}
|
|
return _position_;
|
|
},
|
|
|
|
// @params = { property : 'options', id : }
|
|
// @return mixed type
|
|
getLevelProperty : function( params ) {
|
|
params = _.extend( {
|
|
id : '',
|
|
property : ''
|
|
}, params );
|
|
if ( _.isEmpty( params.id ) ) {
|
|
api.errare( 'getLevelProperty => invalid id provided' );
|
|
return;
|
|
}
|
|
var self = this,
|
|
modelCandidate = self.getLevelModel( params.id );
|
|
|
|
if ( 'no_match' == modelCandidate ) {
|
|
api.errare( 'getLevelProperty => no level model found for id : ' + params.id );
|
|
return;
|
|
}
|
|
if ( ! _.isObject( modelCandidate ) ) {
|
|
api.errare( 'getLevelProperty => invalid model for id : ' + params.id, modelCandidate );
|
|
return;
|
|
}
|
|
return modelCandidate[ params.property ];
|
|
},
|
|
|
|
// @return a detached clone of a given level model, with new unique ids
|
|
cloneLevel : function( levelId ) {
|
|
var self = this;
|
|
var levelModelCandidate = self.getLevelModel( levelId );
|
|
if ( 'no_match' == levelModelCandidate ) {
|
|
throw new Error( 'cloneLevel => no match for level id : ' + levelId );
|
|
}
|
|
var deepClonedLevel = $.extend( true, {}, levelModelCandidate );
|
|
// recursive
|
|
var newIdWalker = function( level_model ) {
|
|
if ( _.isEmpty( level_model.id ) ) {
|
|
throw new Error( 'cloneLevel => missing level id');
|
|
}
|
|
// No collection, we've reach the end of a branch
|
|
level_model.id = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();
|
|
if ( ! _.isEmpty( level_model.collection ) ) {
|
|
if ( ! _.isArray( level_model.collection ) ) {
|
|
throw new Error( 'cloneLevel => the collection must be an array for level id : ' + level_model.id );
|
|
}
|
|
_.each( level_model.collection, function( levelData ) {
|
|
levelData.id = sektionsLocalizedData.prefixForSettingsNotSaved + self.guid();
|
|
newIdWalker( levelData );
|
|
});
|
|
}
|
|
return level_model;
|
|
};
|
|
// recursively walk the provided level sub-tree until all collection ids are updated
|
|
return newIdWalker( deepClonedLevel );
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- REGISTRATION HELPERS
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Extract the default model values from the server localized registered module
|
|
// Invoked when registrating a module in api.czrModuleMap
|
|
// For example :
|
|
// czr_image_module : {
|
|
// mthds : ImageModuleConstructor,
|
|
// crud : false,
|
|
// name : 'Image',
|
|
// has_mod_opt : false,
|
|
// ready_on_section_expanded : true,
|
|
// defaultItemModel : _.extend(
|
|
// { id : '', title : '' },
|
|
// api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_image_module' )
|
|
// )
|
|
// },
|
|
// @return {}
|
|
getDefaultItemModelFromRegisteredModuleData : function( moduleType ) {
|
|
if ( ! this.isModuleRegistered( moduleType ) ) {
|
|
return {};
|
|
}
|
|
// This method should normally not be invoked for a father module type
|
|
if ( sektionsLocalizedData.registeredModules[moduleType].is_father ) {
|
|
api.errare( 'getDefaultItemModelFromRegisteredModuleData => Father modules should be treated specifically' );
|
|
return {};
|
|
}
|
|
var data = sektionsLocalizedData.registeredModules[ moduleType ].tmpl['item-inputs'],
|
|
// title, id are always included in the defaultItemModel
|
|
// title and id are legacy entries that can be used in multi-items modules to identify and name the item
|
|
// For non multi-items modules, those properties don't need to be saved in database
|
|
// @see : ::normalizeAndSanitizeSingleItemInputValues()
|
|
defaultItemModel = {
|
|
id : '',
|
|
title : ''
|
|
},
|
|
self = this;
|
|
|
|
_.each( data, function( _d_, _key_ ) {
|
|
switch ( _key_ ) {
|
|
case 'tabs' :
|
|
_.each( _d_ , function( _tabData_ ) {
|
|
_.each( _tabData_.inputs, function( _inputData_, _id_ ) {
|
|
defaultItemModel[ _id_ ] = _inputData_['default'] || '';
|
|
});
|
|
});
|
|
break;
|
|
default :
|
|
defaultItemModel[ _key_ ] = _d_['default'] || '';
|
|
break;
|
|
}
|
|
});
|
|
return defaultItemModel;
|
|
},
|
|
|
|
//@return mixed
|
|
getRegisteredModuleProperty : function( moduleType, property ) {
|
|
if ( ! this.isModuleRegistered( moduleType ) ) {
|
|
return 'not_set';
|
|
}
|
|
return sektionsLocalizedData.registeredModules[ moduleType ][ property ];
|
|
},
|
|
|
|
// @return boolean
|
|
isModuleRegistered : function( moduleType ) {
|
|
return sektionsLocalizedData.registeredModules && ! _.isUndefined( sektionsLocalizedData.registeredModules[ moduleType ] );
|
|
},
|
|
|
|
isMultiItemsModule : function(mod_type) {
|
|
return sektionsLocalizedData.registeredModules[ mod_type ] && true === sektionsLocalizedData.registeredModules[ mod_type ].is_crud;
|
|
},
|
|
|
|
|
|
// This method is used to re-generate new unique item ids for multi-items modules.
|
|
// This is needed when a module is duplicated ( or its parent column or section )
|
|
// This is also needed when a pre-build section is injected.
|
|
// @Introduced in July 2019 when coding the accordion module
|
|
//
|
|
// @param data is an object or an array
|
|
// we know we are in an item when :
|
|
// - the item belongs to an array ( the collection of items )
|
|
// - the item is described as an object
|
|
// - the item has an id
|
|
//
|
|
// // additional checkes
|
|
// - the item does not have a level property <= makes sure we don't regenerate ids of level
|
|
// - the id does not start with __nimble__ ( sektionsLocalizedData.prefixForSettingsNotSaved ), which is the characteristic of a level
|
|
maybeGenerateNewItemIdsForCrudModules : function( data ) {
|
|
var self = this;
|
|
if ( _.isArray( data ) || _.isObject( data ) ) {
|
|
_.each( data, function( value ) {
|
|
if ( _.isArray( data ) && _.isObject( value ) && value.id && !_.has( value, 'level') ) {
|
|
if ( -1 === value.id.indexOf(sektionsLocalizedData.prefixForSettingsNotSaved) ) {
|
|
value.id = self.guid();
|
|
}
|
|
} else {
|
|
if ( _.isArray( value ) || _.isObject( value ) ) {
|
|
self.maybeGenerateNewItemIdsForCrudModules( value );
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return data;
|
|
},
|
|
|
|
|
|
// Walk the main sektion setting and populate an array of google fonts
|
|
// This method is used when processing the 'sek-update-fonts' action to update the .fonts property
|
|
// To be a candidate for sniffing, an input font value should meet those criteria :
|
|
// 1) be the value of a '{...}_css' input id
|
|
// 2) this input must be a font modifier ( @see 'refresh_fonts' params set on parent module registration )
|
|
// 2) the font should start with [gfont]
|
|
// @param args { is_global_location : bool }
|
|
// @return array
|
|
sniffGFonts : function( args ) {
|
|
args = args || { is_global_location : false };
|
|
var self = this,
|
|
gfonts = [],
|
|
_snifff_ = function( collectionSettingId, localOrGlobal, level ) {
|
|
if ( _.isUndefined( level ) ) {
|
|
var currentSektionSettingValue = api( collectionSettingId )();
|
|
level = _.isObject( currentSektionSettingValue ) ? $.extend( true, {}, currentSektionSettingValue ) : $.extend( true, {}, self.getDefaultSektionSettingValue( localOrGlobal ) );
|
|
}
|
|
_.each( level, function( levelData, _key_ ) {
|
|
// example of input_id candidate 'font_family_css'
|
|
if ( _.isString( _key_ ) && '_css' === _key_.substr( _key_.length - 4 ) ) {
|
|
if ( true === self.inputIsAFontFamilyModifier( _key_ ) ) {
|
|
if ( levelData.indexOf('gfont') > -1 && ! _.contains( gfonts, levelData ) ) {
|
|
gfonts.push( levelData );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( _.isArray( levelData ) || _.isObject( levelData ) ) {
|
|
_snifff_( collectionSettingId, localOrGlobal, levelData );
|
|
}
|
|
});
|
|
};
|
|
if ( args.is_global_location ) {
|
|
_snifff_( self.getGlobalSectionsSettingId(), 'global' );
|
|
} else {
|
|
_snifff_( self.localSectionsSettingId(), 'local' );
|
|
}
|
|
|
|
return gfonts;
|
|
},
|
|
|
|
// return an array of all fonts currently used in local sections, global sections and global options
|
|
sniffAllFonts : function() {
|
|
var self = this,
|
|
Allfonts = [],
|
|
_snifff_ = function( collectionSettingId, localOrGlobal, level ) {
|
|
if ( _.isUndefined( level ) ) {
|
|
var currentSektionSettingValue = api( collectionSettingId )();
|
|
level = _.isObject( currentSektionSettingValue ) ? $.extend( true, {}, currentSektionSettingValue ) : $.extend( true, {}, self.getDefaultSektionSettingValue( localOrGlobal ) );
|
|
}
|
|
_.each( level, function( levelData, _key_ ) {
|
|
// example of input_id candidate 'font_family_css'
|
|
// if ( _.isString( _key_ ) && _.isString( levelData ) && ( levelData.indexOf('[gfont]') > -1 || levelData.indexOf('[cfont]') > -1 ) && ! _.contains( Allfonts, levelData ) ) {
|
|
// Allfonts.push( levelData );
|
|
// }
|
|
if ( _.isString( _key_ ) && _.isString( levelData ) && ( levelData.indexOf('[gfont]') > -1 || levelData.indexOf('[cfont]') > -1 ) ) {
|
|
Allfonts.push( levelData );
|
|
}
|
|
if ( _.isArray( levelData ) || _.isObject( levelData ) ) {
|
|
_snifff_( collectionSettingId, localOrGlobal, levelData );
|
|
}
|
|
});
|
|
};
|
|
|
|
// Loop and sniff local sections, global sections, global options
|
|
_.each( [
|
|
{ id : self.localSectionsSettingId(), scope : 'local' },
|
|
{ id : self.getGlobalSectionsSettingId(), scope : 'global' },
|
|
{ id : sektionsLocalizedData.optNameForGlobalOptions, scope : 'global' }
|
|
], function( args ) {
|
|
_snifff_( args.id, args.scope );
|
|
});
|
|
return Allfonts;
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// <RECURSIVE UTILITIES USING THE sektionsLocalizedData.registeredModules>
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Invoked when updating a setting value => in normalizeAndSanitizeSingleItemInputValues(), when doing updateAPISettingAndExecutePreviewActions()
|
|
// @return a mixed type default value
|
|
// @param input_id string
|
|
// @param module_type string
|
|
// @param level array || object
|
|
getInputDefaultValue : function( input_id, module_type, level ) {
|
|
var self = this;
|
|
|
|
// Do we have a cached default value ?
|
|
self.cachedDefaultInputValues = self.cachedDefaultInputValues || {};
|
|
self.cachedDefaultInputValues[ module_type ] = self.cachedDefaultInputValues[ module_type ] || {};
|
|
if ( _.has( self.cachedDefaultInputValues[ module_type ], input_id ) ) {
|
|
return self.cachedDefaultInputValues[ module_type ][ input_id ];
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules ) ) {
|
|
api.errare( 'getInputDefaultValue => missing sektionsLocalizedData.registeredModules' );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules[module_type] ) ) {
|
|
api.errare( 'getInputDefaultValue => missing ' + module_type + ' in sektionsLocalizedData.registeredModules' );
|
|
return;
|
|
}
|
|
// This method should normally not be invoked for a father module type
|
|
if ( sektionsLocalizedData.registeredModules[module_type].is_father ) {
|
|
api.errare( 'getInputDefaultValue => Father modules should be treated specifically' );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( level ) ) {
|
|
level = sektionsLocalizedData.registeredModules[ module_type ].tmpl;
|
|
}
|
|
var _defaultVal_ = 'no_default_value_specified';
|
|
_.each( level, function( levelData, _key_ ) {
|
|
// we found a match skip next levels
|
|
if ( 'no_default_value_specified' !== _defaultVal_ )
|
|
return;
|
|
if ( input_id === _key_ && ! _.isUndefined( levelData.default ) ) {
|
|
_defaultVal_ = levelData.default;
|
|
}
|
|
// if we have still no match, and the data are sniffable, let's go ahead recursively
|
|
if ( 'no_default_value_specified' === _defaultVal_ && ( _.isArray( levelData ) || _.isObject( levelData ) ) ) {
|
|
_defaultVal_ = self.getInputDefaultValue( input_id, module_type, levelData );
|
|
}
|
|
if ( 'no_default_value_specified' !== _defaultVal_ ) {
|
|
// cache it
|
|
self.cachedDefaultInputValues[ module_type ][ input_id ] = _defaultVal_;
|
|
}
|
|
});
|
|
return _defaultVal_;
|
|
},
|
|
|
|
|
|
|
|
// @return input_type string
|
|
// @param input_id string
|
|
// @param module_type string
|
|
// @param level array || object
|
|
getInputType : function( input_id, module_type, level ) {
|
|
var self = this;
|
|
|
|
// Do we have a cached default value ?
|
|
self.cachedInputTypes = self.cachedInputTypes || {};
|
|
self.cachedInputTypes[ module_type ] = self.cachedInputTypes[ module_type ] || {};
|
|
if ( _.has( self.cachedInputTypes[ module_type ], input_id ) ) {
|
|
return self.cachedInputTypes[ module_type ][ input_id ];
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules ) ) {
|
|
api.errare( 'getInputType => missing sektionsLocalizedData.registeredModules' );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules[module_type] ) ) {
|
|
api.errare( 'getInputType => missing ' + module_type + ' in sektionsLocalizedData.registeredModules' );
|
|
return;
|
|
}
|
|
if ( sektionsLocalizedData.registeredModules[module_type].is_father ) {
|
|
api.errare( 'getInputType => Father modules should be treated specifically' );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( level ) ) {
|
|
level = sektionsLocalizedData.registeredModules[ module_type ].tmpl;
|
|
}
|
|
var _inputType_ = 'no_input_type_specified';
|
|
_.each( level, function( levelData, _key_ ) {
|
|
// we found a match skip next levels
|
|
if ( 'no_input_type_specified' !== _inputType_ )
|
|
return;
|
|
if ( input_id === _key_ && ! _.isUndefined( levelData.input_type ) ) {
|
|
_inputType_ = levelData.input_type;
|
|
}
|
|
// if we have still no match, and the data are sniffable, let's go ahead recursively
|
|
if ( 'no_input_type_specified' === _inputType_ && ( _.isArray( levelData ) || _.isObject( levelData ) ) ) {
|
|
_inputType_ = self.getInputType( input_id, module_type, levelData );
|
|
}
|
|
if ( 'no_input_type_specified' !== _inputType_ ) {
|
|
// cache it
|
|
self.cachedInputTypes[ module_type ][ input_id ] = _inputType_;
|
|
}
|
|
});
|
|
return _inputType_;
|
|
},
|
|
|
|
|
|
// Invoked when :
|
|
// 1) updating a setting value, in ::updateAPISettingAndExecutePreviewActions()
|
|
// 2) we need to get a registration param like the default value for example, @see spacing input
|
|
// @return object of registration params
|
|
// @param input_id string
|
|
// @param module_type string
|
|
// @param level array || object
|
|
getInputRegistrationParams : function( input_id, module_type, level ) {
|
|
var self = this;
|
|
|
|
// Do we have a cached default value ?
|
|
self.cachedInputRegistrationParams = self.cachedInputRegistrationParams || {};
|
|
self.cachedInputRegistrationParams[ module_type ] = self.cachedInputRegistrationParams[ module_type ] || {};
|
|
if ( _.has( self.cachedInputRegistrationParams[ module_type ], input_id ) ) {
|
|
return self.cachedInputRegistrationParams[ module_type ][ input_id ];
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules ) ) {
|
|
api.errare( 'getInputRegistrationParams => missing sektionsLocalizedData.registeredModules' );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules[module_type] ) ) {
|
|
api.errare( 'getInputRegistrationParams => missing ' + module_type + ' in sektionsLocalizedData.registeredModules' );
|
|
return;
|
|
}
|
|
// This method should normally not be invoked for a father module type
|
|
if ( sektionsLocalizedData.registeredModules[module_type].is_father ) {
|
|
api.errare( 'getInputRegistrationParams => Father modules should be treated specifically' );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( level ) ) {
|
|
level = sektionsLocalizedData.registeredModules[ module_type ].tmpl;
|
|
}
|
|
var _params_ = {};
|
|
_.each( level, function( levelData, _key_ ) {
|
|
// we found a match skip next levels
|
|
if ( ! _.isEmpty( _params_ ) )
|
|
return;
|
|
if ( input_id === _key_ && ! _.isUndefined( levelData.input_type ) ) {
|
|
_params_ = levelData;
|
|
}
|
|
// if we have still no match, and the data are sniffable, let's go ahead recursively
|
|
if ( _.isEmpty( _params_ ) && ( _.isArray( levelData ) || _.isObject( levelData ) ) ) {
|
|
_params_ = self.getInputRegistrationParams( input_id, module_type, levelData );
|
|
}
|
|
if ( ! _.isEmpty( _params_ ) ) {
|
|
// cache it
|
|
self.cachedInputRegistrationParams[ module_type ][ input_id ] = _params_;
|
|
}
|
|
});
|
|
return _params_;
|
|
},
|
|
|
|
|
|
// @return bool
|
|
// @param input_id string
|
|
// @param module_type string
|
|
// @param level array || object
|
|
inputIsAFontFamilyModifier : function( input_id, level ) {
|
|
var self = this;
|
|
|
|
// Do we have a cached default value ?
|
|
self.cachedFontFamilyModifier = self.cachedFontFamilyModifier || {};
|
|
if ( _.has( self.cachedFontFamilyModifier, input_id ) ) {
|
|
return self.cachedFontFamilyModifier[ input_id ];
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules ) ) {
|
|
api.errare( 'inputIsAFontFamilyModifier => missing sektionsLocalizedData.registeredModules' );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( level ) ) {
|
|
level = sektionsLocalizedData.registeredModules;
|
|
}
|
|
var _bool_ = 'not_set';
|
|
_.each( level, function( levelData, _key_ ) {
|
|
// we found a match skip next levels
|
|
if ( 'not_set' !== _bool_ )
|
|
return;
|
|
if ( input_id === _key_ && ! _.isUndefined( levelData.input_type ) ) {
|
|
_bool_ = _.isUndefined( levelData.refresh_fonts ) ? false : levelData.refresh_fonts;
|
|
}
|
|
// if we have still no match, and the data are sniffable, let's go ahead recursively
|
|
if ( 'not_set' === _bool_ && ( _.isArray( levelData ) || _.isObject( levelData ) ) ) {
|
|
_bool_ = self.inputIsAFontFamilyModifier( input_id, levelData );
|
|
}
|
|
if ( 'not_set' !== _bool_ ) {
|
|
// cache it
|
|
self.cachedFontFamilyModifier[ input_id ] = _bool_;
|
|
}
|
|
});
|
|
return _bool_;
|
|
},
|
|
//-------------------------------------------------------------------------------------------------
|
|
// </RECURSIVE UTILITIES USING THE sektionsLocalizedData.registeredModules>
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @return the item(s) ( array of items if multi-item module ) that we should use when adding the module to the main setting
|
|
getModuleStartingValue : function( module_type ) {
|
|
var self = this;
|
|
if ( ! sektionsLocalizedData.registeredModules ) {
|
|
api.errare( 'getModuleStartingValue => missing sektionsLocalizedData.registeredModules' );
|
|
return 'no_starting_value';
|
|
}
|
|
if ( _.isUndefined( sektionsLocalizedData.registeredModules[ module_type ] ) ) {
|
|
api.errare( 'getModuleStartingValue => the module type ' + module_type + ' is not registered' );
|
|
return 'no_starting_value';
|
|
}
|
|
var getStartingValues = function(mod_type ) {
|
|
return ( sektionsLocalizedData.registeredModules[ mod_type ] && sektionsLocalizedData.registeredModules[ mod_type ].starting_value ) ? sektionsLocalizedData.registeredModules[ mod_type ].starting_value : {};
|
|
},
|
|
getChildModuleStartingValues = function(childModType, optGroupName, fatherStartingValues ) {
|
|
var rawStartValues,
|
|
readyStartValues;
|
|
|
|
rawStartValues = fatherStartingValues[optGroupName] ? fatherStartingValues[optGroupName] : {};
|
|
|
|
if ( self.isMultiItemsModule(childModType) && _.isArray( rawStartValues ) ) {
|
|
readyStartValues = buildMultiItemStartingValues( rawStartValues );
|
|
} else {
|
|
readyStartValues = rawStartValues;
|
|
}
|
|
return readyStartValues;
|
|
},
|
|
buildMultiItemStartingValues = function( rawStartValues ) {
|
|
// Exemple of the accordion module
|
|
// 'children' => array(
|
|
// 'accord_collec' => 'czr_accordion_collection_child',
|
|
// 'accord_opts' => 'czr_accordion_opts_child'
|
|
// ),
|
|
// => The multi-item module czr_accordion_collection_child will populate the 'accord_collec' option group
|
|
// We set a 3 items starting value
|
|
// And we need to generate unique id before injection in the API
|
|
// 'starting_value' => array(
|
|
// 'accord_collec' => array(
|
|
// array( 'text_content' => 'Lorem ipsum dolor sit amet' ),
|
|
// array( 'text_content' => 'Lorem ipsum dolor sit amet' ),
|
|
// array( 'text_content' => 'Lorem ipsum dolor sit amet' )
|
|
// ),
|
|
// 'accord_opts' => array()
|
|
// )
|
|
readyStartValues = [];
|
|
_.each( rawStartValues, function( item ) {
|
|
if ( ! _.isObject( item ) ) {
|
|
api.errare( 'getModuleStartingValue => multi-item module => items should be objects for module ' + mod_type );
|
|
return;
|
|
}
|
|
var clonedItem = $.extend( true, {}, item );
|
|
clonedItem.id = api.czr_sektions.guid();
|
|
readyStartValues.push( clonedItem );
|
|
});
|
|
return readyStartValues;
|
|
},
|
|
isFatherModule = function(mod_type) {
|
|
return sektionsLocalizedData.registeredModules[ mod_type ] && true === sektionsLocalizedData.registeredModules[ mod_type ].is_father;
|
|
},
|
|
getChildren = function(mod_type) {
|
|
return ( sektionsLocalizedData.registeredModules[ mod_type ] && sektionsLocalizedData.registeredModules[ mod_type ].children ) ? sektionsLocalizedData.registeredModules[ mod_type ].children : {};
|
|
};
|
|
|
|
var startingValueCandidate = {},
|
|
rawMaybeFatherModuleStartingValue = getStartingValues( module_type );
|
|
|
|
// Prepare starting value
|
|
// If a module is crud ( multi-items ), we need to generate a unique id for each item
|
|
// => implemented for https://github.com/presscustomizr/nimble-builder/issues/486
|
|
// If module_type is a father module, let's loop on the data structure
|
|
if ( isFatherModule( module_type ) ) {
|
|
// Structure :
|
|
// 'children' => array(
|
|
// 'accord_collec' => 'czr_accordion_collection_child',
|
|
// 'accord_opts' => 'czr_accordion_opts_child'
|
|
// ),
|
|
_.each( getChildren( module_type ), function( childModType, optGroupName ) {
|
|
var normalizedStartingValues = getChildModuleStartingValues( childModType, optGroupName, rawMaybeFatherModuleStartingValue );
|
|
if ( ! _.isEmpty( normalizedStartingValues ) ) {
|
|
startingValueCandidate[optGroupName] = normalizedStartingValues;
|
|
}
|
|
});
|
|
} else {
|
|
if ( self.isMultiItemsModule(module_type) && _.isArray( rawMaybeFatherModuleStartingValue ) ) {
|
|
startingValueCandidate = buildMultiItemStartingValues( rawMaybeFatherModuleStartingValue );
|
|
} else {
|
|
startingValueCandidate = rawMaybeFatherModuleStartingValue;
|
|
}
|
|
}
|
|
|
|
return _.isEmpty( startingValueCandidate ) ? 'no_starting_value' : startingValueCandidate;
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
* Following two functions taken from jQuery.tabbable 1.0
|
|
* see https://github.com/marklagendijk/jquery.tabbable/blob/master/jquery.tabbable.js
|
|
*
|
|
* Copyright 2013, Mark Lagendijk
|
|
* Released under the MIT license
|
|
*/
|
|
selectNextTabbableOrFocusable : function( selector ) {
|
|
var selectables = $( selector );
|
|
var current = $( ':focus' );
|
|
var nextIndex = 0;
|
|
if( current.length === 1 ) {
|
|
var currentIndex = selectables.index( current );
|
|
if( currentIndex + 1 < selectables.length ) {
|
|
nextIndex = currentIndex + 1;
|
|
}
|
|
}
|
|
|
|
selectables.eq( nextIndex ).focus();
|
|
},
|
|
|
|
selectPrevTabbableOrFocusable : function( selector ) {
|
|
var selectables = $( selector );
|
|
var current = $( ':focus' );
|
|
var prevIndex = selectables.length - 1;
|
|
if( current.length === 1 ) {
|
|
var currentIndex = selectables.index( current );
|
|
if( currentIndex > 0 ) {
|
|
prevIndex = currentIndex - 1;
|
|
}
|
|
}
|
|
|
|
selectables.eq( prevIndex ).focus();
|
|
},
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// GENERIC WAY TO SETUP SELECT INPUTS
|
|
//-------------------------------------------------------------------------------------------------
|
|
// used in the module input constructors
|
|
// "this" is the input
|
|
setupSelectInput : function( selectOptions ) {
|
|
var input = this,
|
|
item = input.input_parent,
|
|
module = input.module,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type );
|
|
// use the provided selectOptions if any
|
|
selectOptions = _.isEmpty( selectOptions ) ? inputRegistrationParams.choices : selectOptions;
|
|
|
|
// allow selectOptions to be filtrable remotely when the options are not passed on registration for example
|
|
// @see widget are module in initialize() for example
|
|
var filtrable = { params : selectOptions };
|
|
input.module.trigger( 'nimble-set-select-input-options', filtrable );
|
|
selectOptions = filtrable.params;
|
|
|
|
if ( _.isEmpty( selectOptions ) || ! _.isObject( selectOptions ) ) {
|
|
api.errare( 'api.czr_sektions.setupSelectInput => missing select options for input id => ' + input.id + ' in module ' + input.module.module_type );
|
|
return;
|
|
} else {
|
|
switch( input.type ) {
|
|
case 'simpleselect' :
|
|
//generates the options
|
|
_.each( selectOptions , function( title, value ) {
|
|
var _attributes = {
|
|
value : value,
|
|
html: title
|
|
};
|
|
if ( value == input() ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
} else if ( 'px' === value ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
}
|
|
$( 'select[data-czrtype]', input.container ).append( $('<option>', _attributes) );
|
|
});
|
|
$( 'select[data-czrtype]', input.container ).selecter();
|
|
break;
|
|
case 'multiselect' :
|
|
// when select is multiple, the value is an array
|
|
var input_value = input();
|
|
input_value = _.isString( input_value ) ? [ input_value ] : input_value;
|
|
input_value = !_.isArray( input_value ) ? [] : input_value;
|
|
|
|
//generates the options
|
|
_.each( selectOptions , function( title, value ) {
|
|
var _attributes = {
|
|
value : value,
|
|
html: title
|
|
};
|
|
if ( _.contains( input_value, value ) ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
}
|
|
$( 'select[data-czrtype]', input.container ).append( $('<option>', _attributes) );
|
|
});
|
|
// see how the tmpl is rendered server side in PHP with ::ac_set_input_tmpl_content()
|
|
$( 'select[data-czrtype]', input.container ).czrSelect2({
|
|
closeOnSelect: true,
|
|
templateSelection: function czrEscapeMarkup(obj) {
|
|
//trim dashes
|
|
return obj.text.replace(/\u2013|\u2014/g, "");
|
|
}
|
|
});
|
|
|
|
//handle case when all choices become unselected
|
|
$( 'select[data-czrtype]', input.container ).on('change', function(){
|
|
if ( 0 === $(this).find("option:selected").length ) {
|
|
input([]);
|
|
}
|
|
});
|
|
break;
|
|
default :
|
|
api.errare( '::setupSelectInput => invalid input type => ' + input.type );
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// PREPARE INPUT REGISTERED WITH has_device_switcher set to true
|
|
//-------------------------------------------------------------------------------------------------
|
|
// "this" is the input
|
|
maybeSetupDeviceSwitcherForInput : function() {
|
|
var input = this;
|
|
// If this has already been done, let's stop now
|
|
if ( input.previewedDevice )
|
|
return;
|
|
// render the device switcher before the input title
|
|
var deviceSwitcherHtml = [
|
|
'<span class="sek-input-device-switcher">',
|
|
'<i data-sek-device="desktop" class="sek-switcher preview-desktop active" title="'+ sektionsLocalizedData.i18n['Settings on desktops'] +'"></i>',
|
|
'<i data-sek-device="tablet" class="sek-switcher preview-tablet" title="'+ sektionsLocalizedData.i18n['Settings on tablets'] +'"></i>',
|
|
'<i data-sek-device="mobile" class="sek-switcher preview-mobile" title="'+ sektionsLocalizedData.i18n['Settings on mobiles'] +'"></i>',
|
|
'</span>'
|
|
].join(' ');
|
|
|
|
input.container.find('.customize-control-title').first().prepend( deviceSwitcherHtml );
|
|
input.previewedDevice = new api.Value( api.previewedDevice() );
|
|
|
|
|
|
syncWithPreviewedDevice = function( evt ) {
|
|
evt.stopPropagation();
|
|
input.container.find( '[data-sek-device]' ).removeClass('active');
|
|
$(this).addClass('active');
|
|
var device = 'desktop';
|
|
try { device = $(this).data('sek-device'); } catch( er ) {
|
|
api.errare( 'maybeSetupDeviceSwitcherForInput => error when binding sek-switcher', er );
|
|
}
|
|
try { api.previewedDevice( device ); } catch( er ) {
|
|
api.errare( 'maybeSetupDeviceSwitcherForInput => error when setting the previewed device', er );
|
|
}
|
|
input.previewedDevice( device );
|
|
var sectionIdToBeFocusedOn;
|
|
|
|
// focus on the parent section
|
|
// only when really clicked (not triggered)
|
|
// fixes https://github.com/presscustomizr/nimble-builder/issues/512
|
|
if ( _.isUndefined( evt.isTrigger ) && input.module && input.module.control && input.module.control.params.section ) {
|
|
sectionIdToBeFocusedOn = input.module.control.params.section;
|
|
_.delay( function() {
|
|
api.previewer.send('sek-animate-to-level', { id : sectionIdToBeFocusedOn });
|
|
}, 600 );
|
|
}
|
|
|
|
|
|
};
|
|
// react on device click
|
|
input.container.on( 'click', '[data-sek-device]', syncWithPreviewedDevice );
|
|
|
|
// initialize with the currently previewed device
|
|
var $currentDeviceIcon = input.container.find('[data-sek-device="' + api.previewedDevice() + '"]');
|
|
if ( $currentDeviceIcon.length > 0 ) {
|
|
$currentDeviceIcon.trigger('click');
|
|
}
|
|
},
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// GENERIC WAY TO SETUP ACCORDION BEHAVIOUR OF MODULES IN SECTIONS
|
|
//-------------------------------------------------------------------------------------------------
|
|
// "this" is the section
|
|
// in the content picker section, control's container have the attribute "data-sek-accordion" to selectively enable the accordion
|
|
// @see ::generateUIforDraggableContent()
|
|
// @params { expand_first_control : boolean }
|
|
scheduleModuleAccordion : function( params ) {
|
|
params = params || { expand_first_control : true };
|
|
var _section_ = this;
|
|
// Void if already done
|
|
if( true === _section_.container.data('sek-module-accordion-has-been-setup') )
|
|
return;
|
|
|
|
// Attach event on click
|
|
$( _section_.container ).on( 'click', '.customize-control label > .customize-control-title', function( evt ) {
|
|
//evt.preventDefault();
|
|
evt.stopPropagation();
|
|
|
|
// close various dialog UI
|
|
api.czr_sektions.levelTreeExpanded(false);
|
|
api.czr_sektions.templateGalleryExpanded( false );
|
|
api.czr_sektions.saveSectionDialogVisible(false);
|
|
api.czr_sektions.tmplDialogVisible(false);
|
|
api.czr_sektions.tmplInjectDialogVisible(false);
|
|
|
|
var $control = $(this).closest( '.customize-control');
|
|
|
|
if ( "no" === $control.attr( 'data-sek-accordion' ))
|
|
return;
|
|
|
|
_section_.container.find('.customize-control').not( $control ).each( function() {
|
|
if ( $(this).attr( 'data-sek-accordion' ) )
|
|
return;
|
|
$(this).attr('data-sek-expanded', "false" );
|
|
});
|
|
$control.attr('data-sek-expanded', "false" == $control.attr('data-sek-expanded') ? "true" : "false" );
|
|
|
|
// this event 'sek-accordion-expanded', is used to defer the instantiation of the code editor
|
|
// @see api.czrInputMap['code_editor']
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/176
|
|
$control.trigger( "true" == $control.attr('data-sek-expanded') ? 'sek-accordion-expanded' : 'sek-accordion-collapsed' );
|
|
if ( true === $control.data('module_ready_on_custom_control_event_is_setup' ) ) {
|
|
$control.trigger( 'sek-accordion-expanded' );
|
|
} else {
|
|
$control.one('module_ready_on_custom_control_event_is_setup', function() {
|
|
$control.trigger( 'sek-accordion-expanded' );
|
|
});
|
|
|
|
}
|
|
});
|
|
|
|
_section_.container.data('sek-module-accordion-has-been-setup', true );
|
|
|
|
// Expand the first module if requested
|
|
if ( params.expand_first_control ) {
|
|
// we want to exclude controls for which the accordion is not scheduled
|
|
// introduced when implementing the module option switcher in july 2019. @see https://github.com/presscustomizr/nimble-builder/issues/135
|
|
var _eligibleControls = _.filter( _section_.controls(), function( _ctrl_ ) {
|
|
if ( _ctrl_.params && _ctrl_.params.sek_registration_params ) {
|
|
return false !== _ctrl_.params.sek_registration_params.has_accordion;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
var firstControl = _.first( _eligibleControls );
|
|
|
|
if ( _.isObject( firstControl ) && ! _.isEmpty( firstControl.id ) ) {
|
|
api.control( firstControl.id, function( _ctrl_ ) {
|
|
// this event is triggered by the control fmk in module.isReady.done( function() {} )
|
|
// we need to defer the revealing of the module content when item collection is ready, otherwise it's too early.
|
|
// because the item collection can be ready after the module.isReady() has been resolved.
|
|
// see also https://github.com/presscustomizr/themes-customizer-fmk/commit/1f9fb0045d12dd3af9f4fdd880210dc3183fd63a
|
|
_ctrl_.container.one('items-collection-populated', function() {
|
|
$(this).find('label > .customize-control-title').trigger('click');
|
|
});
|
|
|
|
// remotely request a module.ready()
|
|
// see CZRModuleMths::initialize
|
|
// => then once module is ready and all items populated, the event 'items-collection-populated' is triggered on the control, and we can reveal the module content/.
|
|
|
|
// Nov 2020 => make sure the setup has been done on the control so that it reacts on a on custom event ( here 'sek-accordion-expanded' )
|
|
// see fmk CZRModuleMths::initialize
|
|
// event defined on module registration with api.czrModuleMap[ module.module_type ].ready_on_section_expanded
|
|
if ( true === _ctrl_.container.data('module_ready_on_custom_control_event_is_setup' ) ) {
|
|
_ctrl_.container.trigger( 'sek-accordion-expanded' );
|
|
} else {
|
|
_ctrl_.container.one('module_ready_on_custom_control_event_is_setup', function() {
|
|
_ctrl_.container.trigger( 'sek-accordion-expanded' );
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
// HELPERS USED WHEN UPLOADING IMAGES FROM PRESET SECTIONS
|
|
//-------------------------------------------------------------------------------------------------
|
|
isPromise : function (fn) {
|
|
return fn && typeof fn.then === 'function' && String( $.Deferred().then ) === String( fn.then );
|
|
},
|
|
|
|
// @param deferreds = { '__img_url__/assets/img/tests/1.jpg' : 'dfd1', '__img_url__/assets/img/tests/2.jpg' : dfd2, ..., '__img_url__/assets/img/tests/n.jpg' : dfdn }
|
|
whenAllPromisesInParallel : function ( deferreds ) {
|
|
var self = this,
|
|
mainDfd = $.Deferred(),
|
|
args = [],
|
|
_keys_ = _.keys( deferreds );
|
|
|
|
_.each( deferreds, function( mayBeDfd, _k_ ) {
|
|
args.push( $.Deferred( function( _dfd_ ) {
|
|
var dfdCandidate = self.isPromise( mayBeDfd ) ? mayBeDfd : $.Deferred();
|
|
dfdCandidate
|
|
.done( _dfd_.resolve )
|
|
.fail( function (err) { _dfd_.reject( err ); } );
|
|
}) );
|
|
});
|
|
$.when.apply( this, args )
|
|
.done( function () {
|
|
var resObj = {},
|
|
resArgs = Array.prototype.slice.call( arguments );
|
|
|
|
_.each( resArgs, function( v, i ) {
|
|
resObj[ _keys_[i] ] = v;
|
|
});
|
|
mainDfd.resolve( resObj );
|
|
})
|
|
.fail( mainDfd.reject );
|
|
|
|
return mainDfd;
|
|
},
|
|
|
|
// Run the deferred in sequence, only one asynchronous method at a time
|
|
// Was an experiment when implementing the img assets upload for preset sections
|
|
// Abandonned for whenAllPromisesInParallel
|
|
whenAllPromisesInSerie : function ( deferreds, ind, promiseMessages, mainDfd ) {
|
|
ind = ind || 0;
|
|
promiseMessages = promiseMessages || {};
|
|
mainDfd = mainDfd || $.Deferred();
|
|
var self = this;
|
|
if ( _.isArray( deferreds ) ) {
|
|
var mayBeDfd = deferreds[ind],
|
|
dfdCandidate = self.isPromise( mayBeDfd ) ? mayBeDfd : $.Deferred( function( _d_ ) { _d_.resolve(); } );
|
|
|
|
dfdCandidate.always( function( msg ) {
|
|
promiseMessages[ ind ] = msg;
|
|
if ( ( ind + 1 ) == deferreds.length ) {
|
|
mainDfd.resolve( promiseMessages );
|
|
} else {
|
|
if ( ind + 1 < deferreds.length ) {
|
|
self.whenAllPromisesInSerie( deferreds, ind + 1, promiseMessages, mainDfd );
|
|
}
|
|
}
|
|
});
|
|
}//if
|
|
return mainDfd;
|
|
},
|
|
|
|
|
|
// @param attachment_url = string : '/assets/img/41883.jpg'
|
|
// @return a promise
|
|
importAttachment : function( attachment_url ) {
|
|
// @see php wp_ajax_sek_import_attachment
|
|
return wp.ajax.post( 'sek_import_attachment', {
|
|
img_url : attachment_url,
|
|
nonce: api.settings.nonce.save//<= do we need to set a specific nonce to fetch the attachment
|
|
})
|
|
.fail( function( _er_ ) {
|
|
api.errare( 'sek_import_attachment ajax action failed for image ' + attachment_url, _er_ );
|
|
});
|
|
// .done( function( data) {
|
|
// api.infoLog('relpath and DATA ' + relpath , data );
|
|
// });
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// recursive helper
|
|
// used when saving a section
|
|
cleanIds : function( levelData ) {
|
|
levelData.id = "";
|
|
var self = this;
|
|
_.each( levelData.collection, function( levelData ) {
|
|
levelData.id = "";
|
|
if ( _.isArray( levelData.collection ) ) {
|
|
self.cleanIds( levelData );
|
|
}
|
|
});
|
|
return levelData;
|
|
},
|
|
|
|
// @return { collection[] ... }
|
|
getDefaultSektionSettingValue : function( localOrGlobal ) {
|
|
if ( _.isUndefined( localOrGlobal ) || !_.contains( [ 'local', 'global' ], localOrGlobal ) ) {
|
|
api.errare( 'getDefaultSektionSettingValue => the skope should be set to local or global');
|
|
}
|
|
return 'global' === localOrGlobal ? sektionsLocalizedData.defaultGlobalSektionSettingValue : sektionsLocalizedData.defaultLocalSektionSettingValue;
|
|
},
|
|
|
|
// @return void()
|
|
// input controller instance == this
|
|
scheduleVisibilityOfInputId : function( controlledInputId, visibilityCallBack ) {
|
|
var item = this.input_parent;
|
|
if ( !_.isFunction(visibilityCallBack) || _.isEmpty(controlledInputId) ) {
|
|
api.errare('::scheduleVisibilityOfInputId => error when firing for input id : ' + this.id );
|
|
return;
|
|
}
|
|
if ( !item.czr_Input.has( controlledInputId ) ) {
|
|
api.errare('::scheduleVisibilityOfInputId => missing input id : ' + controlledInputId );
|
|
return;
|
|
}
|
|
//Fire on init
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
//React on change
|
|
this.bind( function( to ) {
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
});
|
|
},
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- TEMPLATE HELPERS
|
|
// Dec 2020
|
|
//-------------------------------------------------------------------------------------------------
|
|
|
|
// @return boolean
|
|
// introduced when implementing #655
|
|
// When importing a template, if current page has NB sections, out of header and footer, display an import dialog, otherwise import now
|
|
hasCurrentPageNBSectionsNotHeaderFooter : function() {
|
|
var self = this,
|
|
_bool = false,
|
|
_collection,
|
|
localCollSetId = this.localSectionsSettingId(),
|
|
localColSetValue = api(localCollSetId)();
|
|
|
|
localColSetValue = _.isObject( localColSetValue ) ? localColSetValue : {};
|
|
_collection = $.extend( true, {}, localColSetValue );
|
|
_collection = ! _.isEmpty( _collection.collection ) ? _collection.collection : [];
|
|
_collection = _.isArray( _collection ) ? _collection : [];
|
|
_.each( _collection, function( loc_data ){
|
|
if ( _bool )
|
|
return;
|
|
if ( _.isObject(loc_data) && 'location' == loc_data.level && loc_data.collection ) {
|
|
// skip if the location is a header
|
|
if ( !self.isHeaderLocation( loc_data.id ) && !self.isFooterLocation( loc_data.id ) ) {
|
|
_bool = !_.isEmpty( loc_data.collection );
|
|
}
|
|
}
|
|
});
|
|
return _bool;
|
|
},
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- SITE TEMPLATE HELPERS
|
|
//-------------------------------------------------------------------------------------------------
|
|
// localSektionsInheritsGroupSkope : function() {
|
|
// var localCollSetId = this.localSectionsSettingId(),
|
|
// localColSetValue = api(localCollSetId)();
|
|
// return localColSetValue && localColSetValue.__inherits_group_skope_tmpl_when_exists__;
|
|
// },
|
|
|
|
// @return bool
|
|
// hasLocalSektions : function() {
|
|
// var self = this,
|
|
// _bool = false,
|
|
// _collection,
|
|
// localCollSetId = this.localSectionsSettingId(),
|
|
// localColSetValue = api(localCollSetId)();
|
|
|
|
// localColSetValue = _.isObject( localColSetValue ) ? localColSetValue : {};
|
|
// _collection = $.extend( true, {}, localColSetValue );
|
|
// _collection = ! _.isEmpty( _collection.collection ) ? _collection.collection : [];
|
|
// _collection = _.isArray( _collection ) ? _collection : [];
|
|
// _.each( _collection, function( loc_data ){
|
|
// if ( _bool )
|
|
// return;
|
|
// if ( _.isObject(loc_data) && 'location' == loc_data.level && loc_data.collection ) {
|
|
// _bool = !_.isEmpty( loc_data.collection );
|
|
// }
|
|
// });
|
|
// // on a reset, property __inherits_group_skope_tmpl_when_exists__ is set to true server side
|
|
// return _bool && !( localColSetValue && localColSetValue.__inherits_group_skope_tmpl_when_exists__ );
|
|
// },
|
|
|
|
// Added April 2021 for #478
|
|
// When a page has not been locally customized, property __inherits_group_skope_tmpl_when_exists__ is true ( @see sek_get_default_location_model() )
|
|
// As soon as the main local setting id is modified, __inherits_group_skope_tmpl_when_exists__ is set to false ( see js control::updateAPISetting )
|
|
// After a reset case, NB sets __inherits_group_skope_tmpl_when_exists__ back to true ( see js control:: resetCollectionSetting )
|
|
// Note : If this property is set to true => NB removes the local skope post in Nimble_Collection_Setting::update()
|
|
hasLocalSettingBeenCustomized : function() {
|
|
var self = this,
|
|
_bool = false,
|
|
_collection,
|
|
localCollSetId = this.localSectionsSettingId(),
|
|
localColSetValue = api(localCollSetId)();
|
|
|
|
localColSetValue = _.isObject( localColSetValue ) ? localColSetValue : {};
|
|
|
|
// on a reset, property __inherits_group_skope_tmpl_when_exists__ is set to true server side
|
|
return !( localColSetValue && localColSetValue.__inherits_group_skope_tmpl_when_exists__ );
|
|
},
|
|
|
|
// Js version of php helper sek_get_group_skope_for_site_tmpl()
|
|
// April 2021 for site template
|
|
// Why this method?
|
|
// the site_templates key are intended to match exactly the skope ids, as generated by NB skope system
|
|
// But there are exceptions for some skopes that have no "group skopes" and for which we've added the suffix "for_site_tmpl"
|
|
// So when registering site template global options the suffix '_for_site_tmpl' is added to :
|
|
// 'no group skope' scopes : 'skp__search_for_site_tmpl', 'skp__404_for_site_tmpl', 'skp__date_for_site_tmpl'
|
|
getGroupSkopeForSiteTemplate : function() {
|
|
var _groupSkope = api.czr_skopeBase.getSkopeProperty( 'skope_id', 'group' ),
|
|
_skopeId;
|
|
if ( '_skope_not_set_' === _groupSkope ) {
|
|
var _isNoGroupSkope = function( localskopeId ) {
|
|
if ( FlatSkopeLocalizedData && _.isArray( FlatSkopeLocalizedData.noGroupSkopeList ) ) {
|
|
localskopeId = _.isString( _skopeId ) ? _skopeId : '_skope_not_set_';
|
|
localskopeId = localskopeId.replace( 'skp__', '' );
|
|
return _.contains( FlatSkopeLocalizedData.noGroupSkopeList , localskopeId );
|
|
}
|
|
api.errare( ':getGroupSkopeForSiteTemplate => missing localized data FlatSkopeLocalizedData.noGroupSkopeList' );
|
|
return false;
|
|
};
|
|
|
|
_skopeId = api.czr_skopeBase.getSkopeProperty( 'skope_id', 'local' );
|
|
if ( _isNoGroupSkope( _skopeId ) ) {
|
|
_groupSkope = _skopeId + '_for_site_tmpl';
|
|
} else {
|
|
api.errare('::getGroupSkopeForSiteTemplate => group skope could not be set');
|
|
}
|
|
}
|
|
return _groupSkope;
|
|
},
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- VARIOUS
|
|
//-------------------------------------------------------------------------------------------------
|
|
isJsonString : function(str) {
|
|
try {
|
|
JSON.parse(str);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
/**
|
|
* @https://github.com/StackHive/DragDropInterface
|
|
* @https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
|
|
* @https://html.spec.whatwg.org/multipage/dnd.html#dnd
|
|
* @https://caniuse.com/#feat=dragndrop
|
|
*/
|
|
// EVENTS
|
|
|
|
// drag => handler : ondrag Fired when an element or text selection is being dragged.
|
|
// dragend => handler : ondragend Fired when a drag operation is being ended (for example, by releasing a mouse button or hitting the escape key). (See Finishing a Drag.)
|
|
// dragenter => handler : ondragenter Fired when a dragged element or text selection enters a valid drop target. (See Specifying Drop Targets.)
|
|
// dragexit => handler : ondragexit Fired when an element is no longer the drag operation's immediate selection target.
|
|
// dragleave => handler : ondragleave Fired when a dragged element or text selection leaves a valid drop target.
|
|
// dragover => handler : ondragover Fired when an element or text selection is being dragged over a valid drop target (every few hundred milliseconds).
|
|
// dragstart => handler : ondragstart Fired when the user starts dragging an element or text selection. (See Starting a Drag Operation.)
|
|
// drop => handler : ondrop Fired when an element or text selection is dropped on a valid drop target. (See Performing a Drop.)
|
|
|
|
// Drop targets can be rendered statically when the preview is rendered or dynamically on dragstart ( sent to preview with 'sek-drag-start')
|
|
// Typically, an empty column will be populated with a zek-drop-zone element statically in the preview.
|
|
// The other drop zones are rendered dynamically in ::schedulePanelMsgReactions case 'sek-drag-start'
|
|
//
|
|
// droppable targets are defined server side in sektionsLocalizedData.dropSelectors :
|
|
// '.sek-drop-zone' <= to pass the ::dnd_canDrop() test, a droppable target should have this css class
|
|
// 'body' <= body will not be eligible for drop, but setting the body as drop zone allows us to fire dragenter / dragover actions, like toggling the "approaching" or "close" css class to real drop zone
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- SETUP DnD
|
|
//-------------------------------------------------------------------------------------------------
|
|
//Fired in ::initialize()
|
|
// INSTANTIATE Dnd ZONES IF SUPPORTED BY THE BROWSER
|
|
// + SCHEDULE DROP ZONES RE-INSTANTIATION ON PREVIEW REFRESH
|
|
// + SCHEDULE API REACTION TO *drop event
|
|
// setup $.sekDrop for $( api.previewer.targetWindow().document ).find( '.sektion-wrapper')
|
|
setupDnd : function() {
|
|
var self = this;
|
|
// emitted by the module_picker or the section_picker module
|
|
// @params { type : 'section' || 'module', input_container : input.container }
|
|
self.bind( 'sek-refresh-dragzones', function( params ) {
|
|
// Detecting HTML5 Drag And Drop support in javascript
|
|
// https://stackoverflow.com/questions/2856262/detecting-html5-drag-and-drop-support-in-javascript#2856275
|
|
if ( true !== 'draggable' in document.createElement('span') ) {
|
|
api.panel( sektionsLocalizedData.sektionsPanelId, function( __main_panel__ ) {
|
|
api.notifications.add( new api.Notification( 'drag-drop-support', {
|
|
type: 'error',
|
|
message: sektionsLocalizedData.i18n['This browser does not support drag and drop. You might need to update your browser or use another one.'],
|
|
dismissible: true
|
|
} ) );
|
|
|
|
// Removed if not dismissed after 5 seconds
|
|
_.delay( function() {
|
|
api.notifications.remove( 'drag-drop-support' );
|
|
}, 10000 );
|
|
});
|
|
|
|
}
|
|
|
|
self.setupNimbleDragZones( params.input_container );//<= module or section picker
|
|
});
|
|
|
|
// on previewer refresh
|
|
api.previewer.bind( 'ready', function() {
|
|
try { self.setupNimbleDropZones();//<= module or section picker
|
|
} catch( er ) {
|
|
api.errare( '::setupDnd => error on self.setupNimbleDropZones()', er );
|
|
}
|
|
// if the module_picker or the section_picker is currently a registered ui control,
|
|
// => re-instantiate sekDrop on the new preview frame
|
|
// the registered() ui levels look like :
|
|
// [
|
|
// { what: "control", id: "__nimble___sek_draggable_sections_ui", label: "Section Picker", type: "czr_module", module_type: "sek_intro_sec_picker_module", …}
|
|
// { what: "setting", id: "__nimble___sek_draggable_sections_ui", dirty: false, value: "", transport: "postMessage", … }
|
|
// { what: "section", id: "__nimble___sek_draggable_sections_ui", title: "Section Picker", panel: "__sektions__", priority: 30}
|
|
// ]
|
|
if ( ! _.isUndefined( _.findWhere( self.registered(), { module_type : 'sek_intro_sec_picker_module' } ) ) ) {
|
|
self.rootPanelFocus();
|
|
} else if ( ! _.isUndefined( _.findWhere( self.registered(), { module_type : 'sek_module_picker_module' } ) ) ) {
|
|
self.rootPanelFocus();
|
|
}
|
|
});
|
|
|
|
// React to the *-droped event
|
|
self.reactToDrop();
|
|
},
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//--DRAG ZONES SETUP
|
|
//-------------------------------------------------------------------------------------------------
|
|
// fired in ::initialize, on 'sek-refresh-nimbleDragDropZones
|
|
// 'sek-refresh-nimbleDragDropZones' is emitted by the section and the module picker modules with param { type : 'section_picker' || 'module_picker'}
|
|
setupNimbleDragZones : function( $draggableWrapper ) {
|
|
var self = this;
|
|
//api.infoLog('instantiate', type );
|
|
// $(this) is the dragged element
|
|
var _onStart = function( evt ) {
|
|
// Reset the preview target
|
|
// implemented for double-click insertion https://github.com/presscustomizr/nimble-builder/issues/317
|
|
self.lastClickedTargetInPreview({});
|
|
|
|
evt.originalEvent.dataTransfer.setData( "sek-content-type", $(this).data('sek-content-type') );
|
|
evt.originalEvent.dataTransfer.setData( "sek-eligible-for-module-dropzones", $(this).data('sek-eligible-for-module-dropzones') ); //<= introduced for https://github.com/presscustomizr/nimble-builder/issues/540
|
|
evt.originalEvent.dataTransfer.setData( "sek-content-id", $(this).data('sek-content-id') );
|
|
evt.originalEvent.dataTransfer.setData( "sek-section-type", $(this).data('sek-section-type') );
|
|
evt.originalEvent.dataTransfer.setData( "sek-is-user-section", $(this).data('sek-is-user-section') );
|
|
|
|
// in addition to the dataTransfer storage, store the properties of the dragged object in a static property
|
|
// => we will need it for example to access the object property when checking if "can drop"
|
|
self.dndData = {
|
|
content_type : evt.originalEvent.dataTransfer.getData( "sek-content-type" ),
|
|
eligible_for_module_dropzones : "true" === evt.originalEvent.dataTransfer.getData( "sek-eligible-for-module-dropzones" ), //<= introduced for https://github.com/presscustomizr/nimble-builder/issues/540
|
|
content_id : evt.originalEvent.dataTransfer.getData( "sek-content-id" ),
|
|
section_type : evt.originalEvent.dataTransfer.getData( "sek-section-type" ),
|
|
// Saved sections
|
|
is_user_section : "true" === evt.originalEvent.dataTransfer.getData( "sek-is-user-section" )
|
|
};
|
|
|
|
// evt.originalEvent.dataTransfer.effectAllowed = "move";
|
|
// evt.originalEvent.dataTransfer.dropEffect = "move";
|
|
// Notify if not supported : https://caniuse.com/#feat=dragndrop
|
|
try {
|
|
evt.originalEvent.dataTransfer.setData( 'browserSupport', 'browserSupport' );
|
|
evt.originalEvent.dataTransfer.clearData( 'browserSupport' );
|
|
} catch ( er ) {
|
|
api.panel( sektionsLocalizedData.sektionsPanelId, function( __main_panel__ ) {
|
|
api.notifications.add( new api.Notification( 'drag-drop-support', {
|
|
type: 'error',
|
|
message: sektionsLocalizedData.i18n['This browser does not support drag and drop. You might need to update your browser or use another one.'],
|
|
dismissible: true
|
|
} ) );
|
|
|
|
// Removed if not dismissed after 5 seconds
|
|
_.delay( function() {
|
|
api.notifications.remove( 'drag-drop-support' );
|
|
}, 10000 );
|
|
});
|
|
}
|
|
$(this).addClass('sek-dragged');
|
|
self.cachedElements.$body.addClass('sek-dragging');
|
|
|
|
// Say it to the preview
|
|
// @see 'sek-drag-start' case in preview::schedulePanelMsgReactions()
|
|
api.previewer.send( 'sek-drag-start', {
|
|
content_type : self.dndData.content_type,
|
|
eligible_for_module_dropzones : self.dndData.eligible_for_module_dropzones//<= added for https://github.com/presscustomizr/nimble-builder/issues/540
|
|
});//fires the rendering of the dropzones
|
|
};
|
|
// $(this) is the dragged element
|
|
var _onEnd = function( evt ) {
|
|
self.cachedElements.$body.removeClass('sek-dragging');
|
|
$(this).removeClass('sek-dragged');
|
|
api.previewer.send( 'sek-drag-stop' );
|
|
};
|
|
// $(this) is the double clicked element
|
|
var _onDoubleClick = function( evt ) {
|
|
var _targetCandidate = self.lastClickedTargetInPreview();// { id : "__nimble__fb2ab3e47472" }
|
|
var $dropTarget;
|
|
if ( ! _.isEmpty( _targetCandidate ) && _targetCandidate.id ) {
|
|
$dropTarget = self.dnd_getDropZonesElements().find('[data-sek-id="' + _targetCandidate.id + '"]').find('.sek-module-drop-zone-for-first-module').first();
|
|
} else {
|
|
_doubleClickTargetMissingNotif();
|
|
}
|
|
|
|
if ( $dropTarget && $dropTarget.length > 0 ) {
|
|
// "Emulate" a drop action
|
|
// @see ::dnd_onDrop()
|
|
api.czr_sektions.trigger( 'sek-content-dropped', {
|
|
drop_target_element : $dropTarget,
|
|
location : $dropTarget.closest('[data-sek-level="location"]').data('sek-id'),
|
|
// when inserted between modules
|
|
before_module_or_nested_section : $dropTarget.data('drop-zone-before-module-or-nested-section'),
|
|
after_module_or_nested_section : $dropTarget.data('drop-zone-after-module-or-nested-section'),
|
|
|
|
// When inserted between sections
|
|
before_section : $dropTarget.data('drop-zone-before-section'),
|
|
after_section : $dropTarget.data('drop-zone-after-section'),
|
|
|
|
content_type : $(this).attr('data-sek-content-type'),
|
|
content_id : $(this).attr('data-sek-content-id'),
|
|
|
|
section_type : $(this).attr('data-sek-section-type'),
|
|
// Saved sections
|
|
is_user_section : "true" === $(this).attr('data-sek-is-user-section')
|
|
});
|
|
// And reset the preview target
|
|
self.lastClickedTargetInPreview({});
|
|
} else {
|
|
_doubleClickTargetMissingNotif();
|
|
api.errare( 'Double click insertion => the target zone was not found');
|
|
}
|
|
};//_onDoubleClick()
|
|
var _doubleClickTargetMissingNotif = function() {
|
|
api.notifications.add( new api.Notification( 'missing-injection-target', {
|
|
type: 'info',
|
|
message: sektionsLocalizedData.i18n['You first need to click on a target ( with a + icon ) in the preview.'],
|
|
dismissible: true
|
|
} ) );
|
|
// Removed if not dismissed after a moment
|
|
_.delay( function() {
|
|
api.notifications.remove( 'missing-injection-target' );
|
|
}, 30000 );
|
|
};
|
|
|
|
// Schedule
|
|
$draggableWrapper.find( '[draggable="true"]' ).each( function() {
|
|
$(this)
|
|
.on( 'dragstart', function( evt ) { _onStart.call( $(this), evt ); })
|
|
.on( 'dragend', function( evt ) { _onEnd.call( $(this), evt ); })
|
|
// double click insertion
|
|
// implemented for https://github.com/presscustomizr/nimble-builder/issues/317
|
|
.on( 'dblclick', function( evt ) { _onDoubleClick.call( $(this), evt ); });
|
|
});
|
|
|
|
// Upsell pro sections and modules
|
|
$draggableWrapper.find( '[draggable="false"][data-sek-is-pro-section="yes"], [draggable="false"][data-sek-is-pro-module="yes"]' ).each( function() {
|
|
$(this).on( 'mousedown', function( evt ) {
|
|
// Reset the preview target
|
|
// implemented for double-click insertion https://github.com/presscustomizr/nimble-builder/issues/317
|
|
self.lastClickedTargetInPreview({});
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 60000,
|
|
//is_pro_notif : true,
|
|
notif_id : 'go_pro',
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>'+ sektionsLocalizedData.i18n['Go pro link when click on pro tmpl or section'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
});
|
|
},//setupNimbleZones()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//--DRAG ZONES SETUP
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Scheduled on previewer('ready') each time the previewer is refreshed
|
|
setupNimbleDropZones : function() {
|
|
var self = this;
|
|
this.$dropZones = this.dnd_getDropZonesElements();
|
|
this.preDropElement = $( '<div>', {
|
|
class: sektionsLocalizedData.preDropElementClass,
|
|
html : ''//will be set dynamically
|
|
});
|
|
if ( this.$dropZones.length < 1 ) {
|
|
throw new Error( '::setupNimbleDropZones => invalid Dom element');
|
|
}
|
|
|
|
this.$dropZones.each( function() {
|
|
var $zone = $(this);
|
|
// Make sure we don't delegate an event twice for a given element
|
|
if ( true === $zone.data('zone-droppable-setup') )
|
|
return;
|
|
|
|
self.enterOverTimer = null;
|
|
// Delegated to allow reactions on future modules / sections
|
|
$zone
|
|
//.on( 'dragenter dragover', sektionsLocalizedData.dropSelectors, )
|
|
.on( 'dragenter dragover', sektionsLocalizedData.dropSelectors, function( evt ) {
|
|
//api.infoLog( self.enterOverTimer, self.dnd_canDrop( { targetEl : $(this), evt : evt } ) );
|
|
if ( _.isNull( self.enterOverTimer ) ) {
|
|
self.enterOverTimer = true;
|
|
_.delay(function() {
|
|
// If the mouse did not move, reset the time and do nothing
|
|
// this will prevent a drop zone to "dance", aka expand collapse, when stoping the mouse close to it
|
|
if ( self.currentMousePosition && ( ( self.currentMousePosition + '' ) == ( evt.clientY + '' + evt.clientX + '') ) ) {
|
|
self.enterOverTimer = null;
|
|
return;
|
|
}
|
|
self.currentMousePosition = evt.clientY + '' + evt.clientX + '';
|
|
self.dnd_toggleDragApproachClassesToDropZones( evt );
|
|
}, 100 );
|
|
}
|
|
|
|
if ( self.dnd_canDrop( { targetEl : $(this), evt : evt } ) ) {
|
|
evt.stopPropagation();
|
|
self.dnd_OnEnterOver( $(this), evt );
|
|
}
|
|
})
|
|
.on( 'dragleave drop', sektionsLocalizedData.dropSelectors, function( evt ) {
|
|
switch( evt.type ) {
|
|
case 'dragleave' :
|
|
if ( ! self.dnd_isOveringDropTarget( $(this), evt ) ) {
|
|
self.dnd_cleanOnLeaveDrop( $(this), evt );
|
|
}
|
|
break;
|
|
case 'drop' :
|
|
// Reset the this.$cachedDropZoneCandidates now
|
|
this.$cachedDropZoneCandidates = null;//has been declared on enter over
|
|
|
|
if ( self.dnd_canDrop( { targetEl : $(this), evt : evt } ) ) {
|
|
evt.preventDefault();//@see https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#drop
|
|
self.dnd_onDrop( $(this), evt );
|
|
self.dnd_cleanOnLeaveDrop( $(this), evt );
|
|
// this event will fire another cleaner
|
|
// also sent on dragend
|
|
api.previewer.send( 'sek-drag-stop' );
|
|
}
|
|
break;
|
|
}
|
|
})
|
|
.data( 'zone-droppable-setup', true );// flag the zone. Will be removed on 'destroy'
|
|
|
|
});//this.dropZones.each()
|
|
},//setupNimbleDropZones()
|
|
|
|
|
|
|
|
dnd_isInTarget : function( $el, evt ) {
|
|
var yPos = evt.clientY,
|
|
xPos = evt.clientX,
|
|
dzoneRect = $el[0].getBoundingClientRect(),
|
|
isInHorizontally = xPos <= dzoneRect.right && dzoneRect.left <= xPos,
|
|
isInVertically = yPos >= dzoneRect.top && dzoneRect.bottom >= yPos;
|
|
return isInVertically && isInHorizontally;
|
|
},
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- DnD Helpers
|
|
//-------------------------------------------------------------------------------------------------
|
|
// Fired on 'dragenter dragover'
|
|
// toggles the "approaching" and "close" css classes when conditions are met.
|
|
//
|
|
// Because this function can be potentially heavy if there are a lot of drop zones, this is fired with a timer
|
|
//
|
|
// Note : this is fired before checking if the target is eligible for drop. This way we can calculate an approach, as soon as we start hovering the 'body' ( which is part the drop selector list )
|
|
dnd_toggleDragApproachClassesToDropZones : function( evt ) {
|
|
|
|
var self = this,
|
|
getHypotenuse = function( a, b ) {
|
|
return(Math.sqrt((a * a) + (b * b)));
|
|
};
|
|
|
|
this.$dropZones = this.$dropZones || this.dnd_getDropZonesElements();
|
|
this.$cachedDropZoneCandidates = _.isEmpty( this.$cachedDropZoneCandidates ) ? this.$dropZones.find('.sek-drop-zone') : this.$cachedDropZoneCandidates;// Will be reset on drop
|
|
|
|
this.distanceTable = [];
|
|
|
|
this.$dropZones.find('.sek-drop-zone').each( function() {
|
|
var yPos = evt.clientY,
|
|
xPos = evt.clientX,
|
|
APPROACHING_DIST = 120,
|
|
CLOSE_DIST = 80,
|
|
VERY_CLOSE_DIST = 50;//60;
|
|
|
|
var dzoneRect = $(this)[0].getBoundingClientRect(),
|
|
mouseToYCenter = Math.abs( yPos - ( dzoneRect.bottom - ( dzoneRect.bottom - dzoneRect.top )/2 ) ),
|
|
mouseToTop = Math.abs( dzoneRect.top - yPos ),
|
|
mouseToXCenter = Math.abs( xPos - ( dzoneRect.right - ( dzoneRect.right - dzoneRect.left )/2 ) ),
|
|
mouseToRight = xPos - dzoneRect.right,
|
|
mouseToLeft = dzoneRect.left - xPos,
|
|
isVeryCloseVertically = mouseToYCenter < VERY_CLOSE_DIST,
|
|
isVeryCloseHorizontally = mouseToXCenter < VERY_CLOSE_DIST,
|
|
isCloseVertically = mouseToYCenter < CLOSE_DIST,
|
|
isCloseHorizontally = mouseToXCenter < CLOSE_DIST,
|
|
isApproachingVertically = mouseToYCenter < APPROACHING_DIST,
|
|
isApproachingHorizontally = mouseToXCenter < APPROACHING_DIST,
|
|
|
|
isInHorizontally = xPos <= dzoneRect.right && dzoneRect.left <= xPos,
|
|
isInVertically = yPos >= dzoneRect.top && dzoneRect.bottom >= yPos;
|
|
|
|
// var html = "isApproachingHorizontally : " + isApproachingHorizontally + ' | isCloseHorizontally : ' + isCloseHorizontally + ' | isInHorizontally : ' + isInHorizontally;
|
|
// html += ' | xPos : ' + xPos + ' | zoneRect.right : ' + dzoneRect.right;
|
|
// html += "isApproachingVertically : " + isApproachingVertically + ' | isCloseVertically : ' + isCloseVertically + ' | isInVertically : ' + isInVertically;
|
|
// html += ' | yPos : ' + yPos + ' | zoneRect.top : ' + dzoneRect.top;
|
|
// $(this).html( '<span style="font-size:10px">' + html + '</span>');
|
|
|
|
// var html = '';
|
|
// html += ' | mouseToBottom : ' + mouseToBottom + ' | mouseToTop : ' + mouseToTop;
|
|
// html += "isApproachingVertically : " + isApproachingVertically + ' | isCloseVertically : ' + isCloseVertically + ' | isInVertically : ' + isInVertically;
|
|
// $(this).html( '<span style="font-size:12px">' + html + '</span>');
|
|
|
|
// var html = ' | xPos : ' + xPos + ' | zoneRect.right : ' + dzoneRect.right + ' | zoneRect.left : ' + dzoneRect.left;
|
|
// html += "mouseToYCenter : " + mouseToYCenter + ' | mouseToXCenter : ' + mouseToXCenter;
|
|
// html += ' | yPos : ' + yPos + ' | zoneRect.top : ' + dzoneRect.top + ' | zoneRect.bottom : ' + dzoneRect.bottom;
|
|
// $(this).html( '<span style="font-size:10px">' + html + '</span>');
|
|
|
|
self.distanceTable.push({
|
|
el : $(this),
|
|
dist : ( isInVertically && isInHorizontally ) ? 0 : getHypotenuse( mouseToXCenter, mouseToYCenter )
|
|
});
|
|
|
|
|
|
//var html = '';
|
|
|
|
// if ( isInVertically && isInHorizontally ) {
|
|
// $(this).removeClass( 'sek-drag-is-approaching');
|
|
// $(this).removeClass( 'sek-drag-is-close' );
|
|
// $(this).removeClass( 'sek-drag-is-very-close');
|
|
// $(this).addClass( 'sek-drag-is-in');
|
|
// //html += 'is IN';
|
|
// }
|
|
// else if ( ( isCloseVertically || isInVertically ) && ( isCloseHorizontally || isInHorizontally ) ) {
|
|
// $(this).removeClass( 'sek-drag-is-approaching');
|
|
// $(this).addClass( 'sek-drag-is-close' );
|
|
// $(this).removeClass( 'sek-drag-is-very-close');
|
|
// $(this).removeClass( 'sek-drag-is-in');
|
|
// //html += 'is close';
|
|
// } else if ( ( isApproachingVertically || isInVertically ) && ( isApproachingHorizontally || isInHorizontally ) ) {
|
|
// $(this).addClass( 'sek-drag-is-approaching');
|
|
// $(this).removeClass( 'sek-drag-is-close' );
|
|
// $(this).removeClass( 'sek-drag-is-very-close');
|
|
// $(this).removeClass( 'sek-drag-is-in');
|
|
// //html += 'is approaching';
|
|
//
|
|
$(this).removeClass( 'sek-drag-is-in');
|
|
|
|
if ( ( isVeryCloseVertically || isInVertically ) && ( isVeryCloseHorizontally || isInHorizontally ) ) {
|
|
$(this).removeClass( 'sek-drag-is-approaching');
|
|
$(this).removeClass( 'sek-drag-is-close' );
|
|
$(this).addClass( 'sek-drag-is-very-close');
|
|
$(this).removeClass( 'sek-drag-is-in');
|
|
//html += 'is very close';
|
|
} else {
|
|
$(this).removeClass( 'sek-drag-is-approaching');
|
|
$(this).removeClass( 'sek-drag-is-close' );
|
|
$(this).removeClass( 'sek-drag-is-very-close');
|
|
$(this).removeClass( 'sek-drag-is-in');
|
|
}
|
|
|
|
|
|
//$(this).html( '<span style="font-size:10px">' + html + '</span>');
|
|
});//$('.sek-drop-zones').each()
|
|
|
|
|
|
var _lowerDist = _.min( _.pluck( self.distanceTable, 'dist') );
|
|
self.$dropTargetCandidate = null;
|
|
_.each( self.distanceTable, function( data ) {
|
|
if ( _.isNull( self.$dropTargetCandidate ) && _lowerDist === data.dist ) {
|
|
self.$dropTargetCandidate = data.el;
|
|
}
|
|
});
|
|
if ( self.$dropTargetCandidate && self.$dropTargetCandidate.length > 0 && self.dnd_isInTarget( self.$dropTargetCandidate, evt ) ) {
|
|
self.$dropTargetCandidate.addClass('sek-drag-is-in');
|
|
}
|
|
// Reset the timer
|
|
self.enterOverTimer = null;
|
|
},
|
|
|
|
// @return string
|
|
dnd_getPreDropElementContent : function( evt ) {
|
|
var $target = $( evt.currentTarget ),
|
|
html,
|
|
preDropContent;
|
|
|
|
switch( this.dndData.content_type ) {
|
|
case 'module' :
|
|
html = sektionsLocalizedData.i18n['Insert here'];
|
|
if ( $target.length > 0 ) {
|
|
if ( 'between-sections' === $target.data('sek-location') || 'in-empty-location' === $target.data('sek-location') ) {
|
|
html = sektionsLocalizedData.i18n['Insert in a new section'];
|
|
}
|
|
}
|
|
preDropContent = '<div class="sek-module-placeholder-content"><p>' + html + '</p></div>';
|
|
break;
|
|
|
|
case 'preset_section' :
|
|
html = sektionsLocalizedData.i18n['Insert a new section here'];
|
|
preDropContent = '<div class="sek-module-placeholder-content"><p>' + html + '</p></div>';
|
|
break;
|
|
|
|
default :
|
|
api.errare( '::dnd_getPreDropElementContent => invalid content type provided');
|
|
break;
|
|
}
|
|
return preDropContent;
|
|
},
|
|
|
|
// Scheduled on previewer('ready') each time the previewer is refreshed
|
|
dnd_getDropZonesElements : function() {
|
|
return $( api.previewer.targetWindow().document );
|
|
},
|
|
|
|
// @return boolean
|
|
// @paraps = { targetEl : $(this), evt : evt }
|
|
// Note : the class "sek-content-preset_section-drop-zone" is dynamically generated in preview::schedulePanelMsgReactions() sek-drag-start case
|
|
dnd_canDrop : function( params ) {
|
|
params = _.extend( { targetEl : {}, evt : {} }, params || {} );
|
|
var self = this, $dropTarget = params.targetEl;
|
|
|
|
if ( ! _.isObject( $dropTarget ) || 1 > $dropTarget.length )
|
|
return false;
|
|
|
|
// stop here if the drop target is not a child of a location
|
|
if ( $dropTarget.closest('[data-sek-level="location"]').length < 1 )
|
|
return false;
|
|
|
|
//console.log('params in control::dnd_canDrop', params, self.dndData );
|
|
|
|
var isSectionDropZone = $dropTarget.hasClass( 'sek-content-preset_section-drop-zone' ),
|
|
sectionHasNoModule = $dropTarget.hasClass( 'sek-module-drop-zone-for-first-module' ),
|
|
isHeaderLocation = true === $dropTarget.closest('[data-sek-level="location"]').data('sek-is-header-location'),
|
|
isFooterLocation = true === $dropTarget.closest('[data-sek-level="location"]').data('sek-is-footer-location'),
|
|
isContentSectionCandidate = 'preset_section' === self.dndData.content_type && 'content' === self.dndData.section_type,
|
|
msg;
|
|
|
|
var maybePrintErrorMessage = function( msg ) {
|
|
if ( $('.sek-no-drop-possible-message', $dropTarget ).length < 1 ) {
|
|
$dropTarget.append([
|
|
'<div class="sek-no-drop-possible-message">',
|
|
'<i class="material-icons">not_interested</i>',
|
|
msg,
|
|
'</div>'
|
|
].join(''));
|
|
}
|
|
};
|
|
|
|
if ( ! $dropTarget.hasClass('sek-drop-zone') ) {
|
|
return false;
|
|
}
|
|
// June 2020 : always allow user sections to be dropped in header and footer location
|
|
// while preset section must explicitely be 'content' section_type to be allowed
|
|
if ( !self.dndData.is_user_section ) {
|
|
if ( ( isHeaderLocation || isFooterLocation ) && isContentSectionCandidate ) {
|
|
msg = isHeaderLocation ? sektionsLocalizedData.i18n['Header location only accepts modules and pre-built header sections'] : sektionsLocalizedData.i18n['Footer location only accepts modules and pre-built footer sections'];
|
|
maybePrintErrorMessage( msg );
|
|
return false;
|
|
}
|
|
}
|
|
if ( isFooterLocation && 'preset_section' === self.dndData.content_type && 'header' === self.dndData.section_type ) {
|
|
msg = sektionsLocalizedData.i18n['You can\'t drop a header section in the footer location'];
|
|
maybePrintErrorMessage( msg );
|
|
return false;
|
|
}
|
|
|
|
if ( isHeaderLocation && 'preset_section' === self.dndData.content_type && 'footer' === self.dndData.section_type ) {
|
|
msg = sektionsLocalizedData.i18n['You can\'t drop a footer section in the header location'];
|
|
maybePrintErrorMessage( msg );
|
|
return false;
|
|
}
|
|
|
|
|
|
// case of multicolumn preset section dragged from the module list
|
|
// introduced for https://github.com/presscustomizr/nimble-builder/issues/540
|
|
if ( 'preset_section' === self.dndData.content_type && true === self.dndData.eligible_for_module_dropzones && ! isSectionDropZone ) {
|
|
return true;
|
|
}
|
|
|
|
return ( ( 'preset_section' === self.dndData.content_type && isSectionDropZone ) || ( 'module' === self.dndData.content_type && ! isSectionDropZone ) || ( 'preset_section' === self.dndData.content_type && sectionHasNoModule ) );
|
|
},// dnd_canDrop()
|
|
|
|
// @return void()
|
|
dnd_OnEnterOver : function( $dropTarget, evt ) {
|
|
evt.preventDefault();//@see :https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
|
|
// Bail here if we are in the currently drag entered element
|
|
if ( true !== $dropTarget.data( 'is-drag-entered' ) ) {
|
|
// Flag now
|
|
$dropTarget.data( 'is-drag-entered', true );
|
|
$dropTarget.addClass( 'sek-active-drop-zone' );
|
|
// Flag the dropEl parent element
|
|
this.$dropZones.addClass( 'sek-is-dragging' );
|
|
}
|
|
|
|
try { this.dnd_mayBePrintPreDropElement( $dropTarget, evt ); } catch( er ) {
|
|
api.errare('Error when trying to insert the preDrop content', er );
|
|
}
|
|
},
|
|
|
|
// @return void()
|
|
dnd_cleanOnLeaveDrop : function( $dropTarget, evt ) {
|
|
var self = this;
|
|
this.$dropZones = this.$dropZones || this.dnd_getDropZonesElements();
|
|
this.preDropElement.remove();
|
|
this.$dropZones.removeClass( 'sek-is-dragging' );
|
|
|
|
$( sektionsLocalizedData.dropSelectors, this.$dropZones ).each( function() {
|
|
self.dnd_cleanSingleDropTarget( $(this) );
|
|
});
|
|
},
|
|
|
|
// @return void()
|
|
dnd_cleanSingleDropTarget : function( $dropTarget ) {
|
|
if ( _.isEmpty( $dropTarget ) || $dropTarget.length < 1 )
|
|
return;
|
|
$dropTarget.data( 'is-drag-entered', false );
|
|
$dropTarget.data( 'preDrop-position', false );
|
|
$dropTarget.removeClass( 'sek-active-drop-zone' );
|
|
$dropTarget.find('.sek-drop-zone').removeClass('sek-drag-is-close');
|
|
$dropTarget.find('.sek-drop-zone').removeClass('sek-drag-is-approaching');
|
|
|
|
$dropTarget.removeClass('sek-feed-me-seymore');
|
|
|
|
$dropTarget.find('.sek-no-drop-possible-message').remove();
|
|
},
|
|
|
|
|
|
// @return string after or before
|
|
dnd_getPosition : function( $dropTarget, evt ) {
|
|
var targetRect = $dropTarget[0].getBoundingClientRect(),
|
|
targetHeight = targetRect.height;
|
|
|
|
// if the preDrop is already printed, we have to take it into account when calc. the target height
|
|
if ( 'before' === $dropTarget.data( 'preDrop-position' ) ) {
|
|
targetHeight = targetHeight + this.preDropElement.outerHeight();
|
|
} else if ( 'after' === $dropTarget.data( 'preDrop-position' ) ) {
|
|
targetHeight = targetHeight - this.preDropElement.outerHeight();
|
|
}
|
|
|
|
return evt.originalEvent.clientY - targetRect.top - ( targetHeight / 2 ) > 0 ? 'after' : 'before';
|
|
},
|
|
|
|
// @return void()
|
|
dnd_mayBePrintPreDropElement : function( $dropTarget, evt ) {
|
|
var self = this,
|
|
previousPosition = $dropTarget.data( 'preDrop-position' ),
|
|
newPosition = this.dnd_getPosition( $dropTarget, evt );
|
|
|
|
if ( previousPosition === newPosition )
|
|
return;
|
|
|
|
if ( true === self.isPrintingPreDrop ) {
|
|
return;
|
|
}
|
|
|
|
self.isPrintingPreDrop = true;
|
|
|
|
// make sure we clean the previous wrapper of the pre drop element
|
|
this.dnd_cleanSingleDropTarget( this.$currentPreDropTarget );
|
|
var inNewSection = 'between-sections' === $dropTarget.data('sek-location') || 'in-empty-location' === $dropTarget.data('sek-location');
|
|
$.when( self.preDropElement.remove() ).done( function(){
|
|
$dropTarget[ 'before' === newPosition ? 'prepend' : 'append' ]( self.preDropElement )
|
|
.find( '.' + sektionsLocalizedData.preDropElementClass ).html( self.dnd_getPreDropElementContent( evt ) );
|
|
// Flag the preDrop element with class to apply a specific style if inserted in a new sektion of in a column
|
|
$dropTarget.find( '.' + sektionsLocalizedData.preDropElementClass ).toggleClass('in-new-sektion', inNewSection );
|
|
$dropTarget.data( 'preDrop-position', newPosition );
|
|
|
|
$dropTarget.addClass('sek-feed-me-seymore');
|
|
|
|
self.isPrintingPreDrop = false;
|
|
self.$currentPreDropTarget = $dropTarget;
|
|
});
|
|
},
|
|
|
|
//@return void()
|
|
dnd_isOveringDropTarget : function( $dropTarget, evt ) {
|
|
var targetRect = $dropTarget[0].getBoundingClientRect(),
|
|
mouseX = evt.clientX,
|
|
mouseY = evt.clientY,
|
|
tLeft = targetRect.left,
|
|
tRight = targetRect.right,
|
|
tTop = targetRect.top,
|
|
tBottom = targetRect.bottom,
|
|
isXin = mouseX >= tLeft && ( tRight - tLeft ) >= ( mouseX - tLeft),
|
|
isYin = mouseY >= tTop && ( tBottom - tTop ) >= ( mouseY - tTop);
|
|
return isXin && isYin;
|
|
},
|
|
|
|
//@return void()
|
|
dnd_onDrop: function( $dropTarget, evt ) {
|
|
evt.stopPropagation();
|
|
var _position = 'after' === this.dnd_getPosition( $dropTarget, evt ) ? $dropTarget.index() + 1 : $dropTarget.index();
|
|
// api.infoLog('onDropping params', position, evt );
|
|
// api.infoLog('onDropping element => ', $dropTarget.data('drop-zone-before-section'), $dropTarget );
|
|
api.czr_sektions.trigger( 'sek-content-dropped', {
|
|
drop_target_element : $dropTarget,
|
|
location : $dropTarget.closest('[data-sek-level="location"]').data('sek-id'),
|
|
// when inserted between modules or nested sections
|
|
before_module_or_nested_section : $dropTarget.data('drop-zone-before-module-or-nested-section'),
|
|
after_module_or_nested_section : $dropTarget.data('drop-zone-after-module-or-nested-section'),
|
|
|
|
// When inserted between sections
|
|
before_section : $dropTarget.data('drop-zone-before-section'),
|
|
after_section : $dropTarget.data('drop-zone-after-section'),
|
|
|
|
content_type : evt.originalEvent.dataTransfer.getData( "sek-content-type" ),
|
|
eligible_for_module_dropzones : "true" === evt.originalEvent.dataTransfer.getData( "sek-eligible-for-module-dropzones" ), //<= introduced for https://github.com/presscustomizr/nimble-builder/issues/540
|
|
content_id : evt.originalEvent.dataTransfer.getData( "sek-content-id" ),
|
|
|
|
section_type : evt.originalEvent.dataTransfer.getData( "sek-section-type" ),
|
|
// Saved sections
|
|
is_user_section : "true" === evt.originalEvent.dataTransfer.getData( "sek-is-user-section" )
|
|
});
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------------------------------
|
|
//-- SCHEDULE REACTIONS TO 'sek-content-dropped'
|
|
//-------------------------------------------------------------------------------------------------
|
|
// invoked on api('ready') from self::initialize()
|
|
reactToDrop : function() {
|
|
var self = this;
|
|
// @param {
|
|
// drop_target_element : $(el) in which the content has been dropped
|
|
// position : 'bottom' or 'top' compared to the drop-zone
|
|
// before_section : $(this).data('drop-zone-before-section'),
|
|
// after_section : $(this).data('drop-zone-after-section'),
|
|
// content_type : evt.originalEvent.dataTransfer.getData( "sek-content-type" ),
|
|
// content_id : evt.originalEvent.dataTransfer.getData( "sek-content-id" ),
|
|
// section_type : evt.originalEvent.dataTransfer.getData( "sek-section-type" ),//<= content, header, footer
|
|
// is_user_section : true === evt.originalEvent.dataTransfer.getData( "sek-is-user-section" ),
|
|
// }
|
|
var _do_ = function( params ) {
|
|
if ( ! _.isObject( params ) ) {
|
|
throw new Error( 'Invalid params provided' );
|
|
}
|
|
if ( params.drop_target_element.length < 1 ) {
|
|
throw new Error( 'Invalid drop_target_element' );
|
|
}
|
|
|
|
var $dropTarget = params.drop_target_element;
|
|
|
|
// IMPORTANT : the dropcase var is declared with a default value
|
|
// then, depending on the content_type and the target location, it will be overriden, see below
|
|
var dropCase = 'content-in-column';
|
|
|
|
// If the data('sek-location') is available, let's use it
|
|
switch( $dropTarget.data('sek-location') ) {
|
|
case 'between-sections' :
|
|
dropCase = 'content-in-a-section-to-create';
|
|
break;
|
|
case 'in-empty-location' :
|
|
params.is_first_section = true;
|
|
params.send_to_preview = false;
|
|
dropCase = 'content-in-empty-location';
|
|
break;
|
|
case 'between-columns' :
|
|
dropCase = 'content-in-new-column';
|
|
break;
|
|
}
|
|
|
|
// case of a preset_section content_type being added to an existing but empty section
|
|
if ( 'preset_section' === params.content_type ) {
|
|
var $parentSektion;
|
|
if ( $dropTarget.hasClass( 'sek-module-drop-zone-for-first-module' ) ) {
|
|
$parentSektion = $dropTarget.closest('div[data-sek-level="section"]');
|
|
//calculate the number of column in this section, excluding the columns inside nested sections if any
|
|
var colNumber = $parentSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ).length;
|
|
// if the parent section has more than 1 column, we will need to inject the preset_section inside a nested_section
|
|
if ( colNumber > 1 ) {
|
|
dropCase = 'preset-section-in-a-nested-section-to-create';
|
|
params.is_nested = true;
|
|
params.in_column = $dropTarget.closest('[data-sek-level="column"]').data('sek-id');
|
|
params.in_sektion = $parentSektion.data('sek-id');
|
|
//params.after_section = params.sektion_to_replace;
|
|
} else {
|
|
params.sektion_to_replace = $parentSektion.data('sek-id');
|
|
params.after_section = params.sektion_to_replace;
|
|
// if the sektion to replace is nested, we will append the new sektion to the parent column of the nested section
|
|
params.in_column = $parentSektion.closest('[data-sek-level="column"]').data('sek-id');
|
|
dropCase = 'content-in-a-section-to-replace';
|
|
}
|
|
} else {
|
|
if ( 'between-sections' === $dropTarget.data('sek-location') ) {
|
|
dropCase = 'content-in-a-section-to-create';
|
|
}
|
|
// introduced for https://github.com/presscustomizr/nimble-builder/issues/540
|
|
// this is typically the case of a multi-columns "module" drop, which is actually a preset_section from Nimble standpoint
|
|
// @see 'between-modules-and-nested-sections' is addded by 'sek-drag-start' case in preview::schedulePanelMsgReactions()
|
|
if ( 'between-modules-and-nested-sections' === $dropTarget.data('sek-location') && params.eligible_for_module_dropzones ) {
|
|
dropCase = 'preset-section-eligible-for-module-dropzones-in-new-nested-sektion';
|
|
params.is_nested = true;
|
|
params.in_column = $dropTarget.closest('[data-sek-level="column"]').data('sek-id');
|
|
$parentSektion = $dropTarget.closest('div[data-sek-level="section"]');
|
|
params.in_sektion = $parentSektion.data('sek-id');
|
|
}
|
|
}
|
|
}//if ( 'preset_section' === params.content_type ) {
|
|
|
|
|
|
// Now the dropcase is setup, let's say it to the previewer
|
|
// see in control::reactToPreviewMsg() and then in control::updateAPISetting() how those actions are handled
|
|
var focusOnAddedContentEditor;
|
|
switch( dropCase ) {
|
|
case 'content-in-column' :
|
|
var $closestLevelWrapper = $dropTarget.closest('div[data-sek-level]');
|
|
if ( 1 > $closestLevelWrapper.length ) {
|
|
throw new Error( 'No valid level dom element found' );
|
|
}
|
|
var _level = $closestLevelWrapper.data( 'sek-level' ),
|
|
_id = $closestLevelWrapper.data('sek-id');
|
|
|
|
if ( _.isEmpty( _level ) || _.isEmpty( _id ) ) {
|
|
throw new Error( 'No valid level id found' );
|
|
}
|
|
|
|
api.previewer.trigger( 'sek-add-module', {
|
|
level : _level,
|
|
id : _id,
|
|
in_column : $dropTarget.closest('div[data-sek-level="column"]').data( 'sek-id'),
|
|
in_sektion : $dropTarget.closest('div[data-sek-level="section"]').data( 'sek-id'),
|
|
|
|
before_module_or_nested_section : params.before_module_or_nested_section,
|
|
after_module_or_nested_section : params.after_module_or_nested_section,
|
|
|
|
content_type : params.content_type,
|
|
content_id : params.content_id
|
|
});
|
|
break;
|
|
|
|
case 'content-in-a-section-to-create' :
|
|
// this case fixes https://github.com/presscustomizr/nimble-builder/issues/139
|
|
case 'content-in-a-section-to-replace' :
|
|
case 'content-in-empty-location' :
|
|
var _newParams = $.extend( true, {}, params );
|
|
api.previewer.trigger( 'sek-add-content-in-new-sektion', {
|
|
// level : _level,
|
|
// id : _id,
|
|
in_column : $dropTarget.closest('div[data-sek-level="column"]').data( 'sek-id'),
|
|
in_sektion : $dropTarget.closest('div[data-sek-level="section"]').data( 'sek-id'),
|
|
|
|
before_module_or_nested_section : _newParams.before_module_or_nested_section,
|
|
after_module_or_nested_section : _newParams.after_module_or_nested_section,
|
|
|
|
content_type : _newParams.content_type,
|
|
content_id : _newParams.content_id,
|
|
is_user_section : _newParams.is_user_section,
|
|
after_section : _newParams.after_section,
|
|
before_section : _newParams.before_section,
|
|
location : _newParams.location,
|
|
sektion_to_replace: _newParams.sektion_to_replace
|
|
} );
|
|
break;
|
|
|
|
case 'preset-section-in-a-nested-section-to-create' :
|
|
api.previewer.trigger( 'sek-add-preset-section-in-new-nested-sektion', params );
|
|
break;
|
|
|
|
// introduced for https://github.com/presscustomizr/nimble-builder/issues/540
|
|
// this is typically the case of a multi-columns "module" drop, which is actually a preset_section from Nimble standpoint
|
|
case 'preset-section-eligible-for-module-dropzones-in-new-nested-sektion' :
|
|
var newParams = $.extend( true, {}, params );
|
|
newParams = $.extend( newParams, {
|
|
before_module_or_nested_section : params.before_module_or_nested_section,
|
|
after_module_or_nested_section : params.after_module_or_nested_section
|
|
});
|
|
api.previewer.trigger( 'sek-add-preset-section-in-new-nested-sektion', newParams );
|
|
break;
|
|
|
|
default :
|
|
api.errare( 'sek control panel => ::reactToDrop => invalid drop case : ' + dropCase );
|
|
break;
|
|
// case 'content-in-new-column' :
|
|
|
|
// break;
|
|
}
|
|
};
|
|
|
|
// @see module picker or section picker modules
|
|
// api.czr_sektions.trigger( 'sek-content-dropped', {
|
|
// drop_target_element : $(this),
|
|
// position : _position,
|
|
// before_section : $(this).data('drop-zone-before-section'),
|
|
// after_section : $(this).data('drop-zone-after-section'),
|
|
// content_type : evt.originalEvent.dataTransfer.getData( "sek-content-type" ),
|
|
// content_id : evt.originalEvent.dataTransfer.getData( "sek-content-id" ),
|
|
// is_user_section : true === evt.originalEvent.dataTransfer.getData( "sek-is-user-section" ),
|
|
// });
|
|
this.bind( 'sek-content-dropped', function( params ) {
|
|
//api.infoLog('sek-content-dropped', params );
|
|
try { _do_( params ); } catch( er ) {
|
|
api.errare( 'error when reactToDrop', er );
|
|
}
|
|
});
|
|
}//reactToDrop
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
/* This code is inpired from the plugin customize-posts, GPLv2 or later licensed
|
|
Credits : xwp, westonruter, valendesigns, sayedwp, utkarshpatel.
|
|
Date of original code modification : July 2018
|
|
*/
|
|
// fired from ::initialize()
|
|
setupTinyMceEditor: function() {
|
|
var self = this;
|
|
// OBSERVABLE VALUES
|
|
api.sekEditorExpanded = new api.Value( false );
|
|
//api.sekEditorSynchronizedInput = new api.Value();
|
|
|
|
self.editorEventsListenerSetup = false;//this status will help us ensure that we bind the shared tinyMce instance only once
|
|
|
|
// Cache some dom elements
|
|
self.$editorPane = $( '#czr-customize-content_editor-pane' );
|
|
self.$editorDragbar = $( '#czr-customize-content_editor-dragbar' );
|
|
self.$preview = $( '#customize-preview' );
|
|
self.$collapseSidebar = $( '.collapse-sidebar' );
|
|
|
|
self.attachResizeEventsToEditor();
|
|
|
|
// Cache the instance and attach
|
|
var mayBeAwakeTinyMceEditor = function() {
|
|
api.sekTinyMceEditor = tinyMCE.get( sektionsLocalizedData.idOfDetachedTinyMceTextArea );
|
|
var _do = function() {
|
|
if ( false === self.editorEventsListenerSetup ) {
|
|
self.editorEventsListenerSetup = true;
|
|
self.trigger('sek-tiny-mce-editor-bound-and-instantiated');
|
|
}
|
|
};
|
|
if ( api.sekTinyMceEditor ) {
|
|
if ( api.sekTinyMceEditor.initialized ) {
|
|
_do();
|
|
} else {
|
|
api.sekTinyMceEditor.on( 'init',function() {
|
|
_do();
|
|
} );
|
|
}
|
|
}
|
|
};
|
|
|
|
// CASE 1)
|
|
// Toggle the editor visibility
|
|
// Change the button text
|
|
// set the clicked input id as the new one
|
|
$('#customize-theme-controls').on('click', '[data-czr-action="open-tinymce-editor"]', function() {
|
|
//console.log( '[data-czr-action="toggle-tinymce-editor"]', $(this) , api.sekEditorSynchronizedInput() );
|
|
// Get the control and the input id from the clicked element
|
|
// => then updated the synchronized input with them
|
|
var control_id = $(this).data('czr-control-id'),
|
|
input_id = $(this).data('czr-input-id');
|
|
if ( _.isEmpty( control_id ) || _.isEmpty( input_id ) ) {
|
|
api.errare('toggle-tinymce-editor => missing input or control id');
|
|
return;
|
|
}
|
|
// var currentEditorSyncData = $.extend( true, {}, api.sekEditorSynchronizedInput() ),
|
|
// newEditorSyncData = _.extend( currentEditorSyncData, {
|
|
// input_id : input_id,
|
|
// control_id : control_id
|
|
// });
|
|
//api.sekEditorSynchronizedInput( newEditorSyncData );
|
|
api.sekEditorExpanded( true );
|
|
//api.sekTinyMceEditor.focus();
|
|
});
|
|
|
|
|
|
|
|
// REACT TO EDITOR VISIBILITY
|
|
api.sekEditorExpanded.bind( function ( expanded, from, params ) {
|
|
try{ mayBeAwakeTinyMceEditor(); } catch(er) {
|
|
if ( window.console ) {
|
|
console.log('Error in mayBeAwakeTinyMceEditor ', er );
|
|
}
|
|
}
|
|
//api.infoLog('in api.sekEditorExpanded', expanded );
|
|
if ( expanded && api.sekTinyMceEditor ) {
|
|
api.sekTinyMceEditor.focus();
|
|
}
|
|
$(document.body).toggleClass( 'czr-customize-content_editor-pane-open', expanded);
|
|
|
|
/*
|
|
* Ensure only the latest input is bound
|
|
*/
|
|
// if ( api.sekTinyMceEditor.locker && api.sekTinyMceEditor.locker !== input ) {
|
|
// //api.sekEditorExpanded.set( false );
|
|
// api.sekTinyMceEditor.locker = null;
|
|
// } if ( ! api.sekTinyMceEditor.locker || api.sekTinyMceEditor.locker === input ) {
|
|
// $(document.body).toggleClass('czr-customize-content_editor-pane-open', expanded);
|
|
// api.sekTinyMceEditor.locker = input;
|
|
// }
|
|
|
|
self.cachedElements.$window[ expanded ? 'on' : 'off' ]('resize', function() {
|
|
if ( ! api.sekEditorExpanded() )
|
|
return;
|
|
_.delay( function() {
|
|
self.czrResizeEditor( window.innerHeight - self.$editorPane.height() );
|
|
}, 50 );
|
|
|
|
});
|
|
|
|
if ( expanded ) {
|
|
self.czrResizeEditor( window.innerHeight - self.$editorPane.height() );
|
|
// fix wrong height on init https://github.com/presscustomizr/nimble-builder/issues/409
|
|
// there's probably a smarter way to get the right height on init. But let's be lazy.
|
|
_.delay( function() {
|
|
self.cachedElements.$window.trigger('resize');
|
|
}, 100 );
|
|
} else {
|
|
//resize reset
|
|
//self.container.closest( 'ul.accordion-section-content' ).css( 'padding-bottom', '' );
|
|
self.$preview.css( 'bottom', '' );
|
|
self.$collapseSidebar.css( 'bottom', '' );
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
// COLLAPSING THE EDITOR
|
|
// or on click on the icon located on top of the editor
|
|
$('#czr-customize-content_editor-pane' ).on('click', '[data-czr-action="close-tinymce-editor"]', function() {
|
|
api.sekEditorExpanded( false );
|
|
});
|
|
|
|
// on click anywhere but on the 'Edit' ( 'open-tinymce-editor' action ) button
|
|
$('#customize-controls' ).on('click', function( evt ) {
|
|
if ( 'open-tinymce-editor' == $( evt.target ).data( 'czr-action') )
|
|
return;
|
|
api.sekEditorExpanded( false, { context : "clicked anywhere"} );
|
|
});
|
|
|
|
// Pressing the escape key collapses the editor
|
|
// both in the customizer panel and the editor frame
|
|
$(document).on( 'keydown', _.throttle( function( evt ) {
|
|
if ( 27 === evt.keyCode ) {
|
|
api.sekEditorExpanded( false );
|
|
}
|
|
}, 50 ));
|
|
|
|
self.bind('sek-tiny-mce-editor-bound-and-instantiated', function() {
|
|
var iframeDoc = $( api.sekTinyMceEditor.iframeElement ).contents().get(0);
|
|
$( iframeDoc ).on('keydown', _.throttle( function( evt ) {
|
|
if ( 27 === evt.keyCode ) {
|
|
api.sekEditorExpanded( false );
|
|
}
|
|
}, 50 ));
|
|
});
|
|
|
|
_.each( [
|
|
//'sek-click-on-inactive-zone', //<=commented to fix #856
|
|
'sek-add-section',
|
|
'sek-add-column',
|
|
'sek-add-module',
|
|
'sek-remove',
|
|
'sek-move',
|
|
'sek-duplicate',
|
|
'sek-resize-columns',
|
|
'sek-add-content-in-new-sektion',
|
|
'sek-pick-content',
|
|
'sek-edit-options',
|
|
'sek-edit-module',
|
|
'sek-notify'
|
|
], function( _evt_ ) {
|
|
if ( 'sek-edit-module' != _evt_ ) {
|
|
api.previewer.bind( _evt_, function() { api.sekEditorExpanded( false ); } );
|
|
} else {
|
|
api.previewer.bind( _evt_, function( params ) {
|
|
if ( params && params.module_type ) {
|
|
api.sekEditorExpanded( params.module_type === 'czr_tiny_mce_editor_module' );
|
|
}
|
|
});
|
|
}
|
|
});
|
|
},//setupTinyMceEditor
|
|
|
|
|
|
|
|
|
|
attachResizeEventsToEditor : function() {
|
|
var self = this;
|
|
// LISTEN TO USER DRAG ACTIONS => RESIZE EDITOR
|
|
// Note : attaching event to the dragbar element was broken => the mouseup event could not be triggered for some reason, probably because adding the class "czr-customize-content_editor-pane-resize", makes us lose access to the dragbar element
|
|
// => that's why we listen for the mouse events when they have bubbled up to the parent wrapper, and then check if the target is our candidate.
|
|
$('#czr-customize-content_editor-pane').on( 'mousedown mouseup', function( evt ) {
|
|
if ( 'mousedown' === evt.type && 'czr-customize-content_editor-dragbar' !== $(evt.target).attr('id') && ! $(evt.target).hasClass('czr-resize-handle') )
|
|
return;
|
|
if ( ! api.sekEditorExpanded() )
|
|
return;
|
|
switch( evt.type ) {
|
|
case 'mousedown' :
|
|
$( document ).on( 'mousemove.' + sektionsLocalizedData.idOfDetachedTinyMceTextArea, function( event ) {
|
|
event.preventDefault();
|
|
$( document.body ).addClass( 'czr-customize-content_editor-pane-resize' );
|
|
$( '#czr-customize-content_editor_ifr' ).css( 'pointer-events', 'none' );
|
|
self.czrResizeEditor( event.pageY );
|
|
});
|
|
break;
|
|
|
|
case 'mouseup' :
|
|
$( document ).off( 'mousemove.' + sektionsLocalizedData.idOfDetachedTinyMceTextArea );
|
|
$( document.body ).removeClass( 'czr-customize-content_editor-pane-resize' );
|
|
$( '#czr-customize-content_editor_ifr' ).css( 'pointer-events', '' );
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
|
|
|
|
czrResizeEditor : function( position ) {
|
|
var self = this,
|
|
//$sectionContent = input.container.closest( 'ul.accordion-section-content' ),
|
|
windowHeight = window.innerHeight,
|
|
windowWidth = window.innerWidth,
|
|
minScroll = 40,
|
|
maxScroll = 1,
|
|
mobileWidth = 782,
|
|
collapseMinSpacing = 56,
|
|
collapseBottomOutsideEditor = 8,
|
|
collapseBottomInsideEditor = 4,
|
|
args = {},
|
|
resizeHeight;
|
|
|
|
var $editorFrame = $( '#czr-customize-content_editor_ifr' ),
|
|
$mceTools = $( '#wp-czr-customize-content_editor-tools' ),
|
|
$mceToolbar = self.$editorPane.find( '.mce-toolbar-grp' ),
|
|
$mceStatusbar = self.$editorPane.find( '.mce-statusbar' );
|
|
|
|
|
|
if ( ! api.sekEditorExpanded() ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! _.isNaN( position ) ) {
|
|
resizeHeight = windowHeight - position;
|
|
}
|
|
|
|
args.height = resizeHeight;
|
|
args.components = $mceTools.outerHeight() + $mceToolbar.outerHeight() + $mceStatusbar.outerHeight();
|
|
|
|
if ( resizeHeight < minScroll ) {
|
|
args.height = minScroll;
|
|
}
|
|
|
|
if ( resizeHeight > windowHeight - maxScroll ) {
|
|
args.height = windowHeight - maxScroll;
|
|
}
|
|
|
|
if ( windowHeight < self.$editorPane.outerHeight() ) {
|
|
args.height = windowHeight;
|
|
}
|
|
|
|
self.$preview.css( 'bottom', args.height );
|
|
self.$editorPane.css( 'height', args.height );
|
|
$editorFrame.css( 'height', args.height - args.components );
|
|
|
|
// the code hereafter is not needed.
|
|
// don't remember why it was included from the beginning...
|
|
// self.$collapseSidebar.css(
|
|
// 'bottom',
|
|
// collapseMinSpacing > windowHeight - args.height ? $mceStatusbar.outerHeight() + collapseBottomInsideEditor : args.height + collapseBottomOutsideEditor
|
|
// );
|
|
|
|
//$sectionContent.css( 'padding-bottom', windowWidth <= mobileWidth ? args.height : '' );
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
////////////////////////////////////////////////////////
|
|
// EXPORT
|
|
////////////////////////////////////////////////////////
|
|
//@params { scope : 'local' or 'global' }
|
|
export_template : function( params ) {
|
|
params = params || {};
|
|
// normalize params
|
|
params = $.extend({
|
|
scope : 'local',
|
|
}, params );
|
|
|
|
var query = [],
|
|
query_params = {
|
|
sek_export_nonce : api.settings.nonce.save,
|
|
skope_id : 'local' === params.scope ? api.czr_skopeBase.getSkopeProperty( 'skope_id' ) : sektionsLocalizedData.globalSkopeId,
|
|
active_locations : api.czr_sektions.activeLocations()
|
|
};
|
|
_.each( query_params, function(v,k) {
|
|
query.push( encodeURIComponent(k) + '=' + encodeURIComponent(v) );
|
|
});
|
|
|
|
// The ajax action is used to make a pre-check
|
|
// the idea is to avoid a white screen when generating the download window afterwards
|
|
wp.ajax.post( 'sek_pre_export_checks', {
|
|
nonce: api.settings.nonce.save,
|
|
sek_export_nonce : api.settings.nonce.save,
|
|
skope_id : 'local' === params.scope ? api.czr_skopeBase.getSkopeProperty( 'skope_id' ) : sektionsLocalizedData.globalSkopeId,
|
|
active_locations : api.czr_sektions.activeLocations()
|
|
}).done( function() {
|
|
// disable the 'beforeunload' listeners generating popup window when the changeset is dirty
|
|
$(window).off( 'beforeunload' );
|
|
// Generate a download window
|
|
// @see add_action( 'customize_register', '\Nimble\sek_catch_export_action', PHP_INT_MAX );
|
|
window.location.href = [
|
|
sektionsLocalizedData.customizerURL,
|
|
'?',
|
|
query.join('&')
|
|
].join('');
|
|
// re-enable the listeners
|
|
$(window).on( 'beforeunload' );
|
|
}).fail( function( error_resp ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-failed',
|
|
type : 'error',
|
|
duration : 30000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
[ sektionsLocalizedData.i18n['Export failed'], encodeURIComponent( error_resp ) ].join(' '),
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
},//export_template
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
// IMPORT TEMPLATE FROM FILE
|
|
////////////////////////////////////////////////////////
|
|
// @params
|
|
// {
|
|
// is_file_import : true,
|
|
// pre_import_check : false,
|
|
// assign_missing_locations : false,
|
|
// input : <= input instance when import is manual
|
|
// file_input : $file_input
|
|
// }
|
|
import_template_from_file : function( params ) {
|
|
//console.log('import_template_from_file ??', params );
|
|
|
|
params = params || {};
|
|
// normalize params
|
|
params = $.extend({
|
|
is_file_import : true,
|
|
pre_import_check : false,
|
|
assign_missing_locations : false,
|
|
input : '',
|
|
file_input : ''
|
|
}, params );
|
|
|
|
// SETUP FOR MANUAL INPUT
|
|
var __request__,
|
|
_input = params.input,
|
|
_scope = 'local';//<= when importing a template not manually, scope is always local
|
|
|
|
// We must have a params.input when import is manual
|
|
if ( _.isEmpty( _input ) ) {
|
|
throw new Error( '::import_template => missing file_input param' );
|
|
}
|
|
|
|
// We must have a params.file_input when import is manual
|
|
if ( _.isEmpty( params.file_input ) ) {
|
|
throw new Error( '::import_template => missing file_input param' );
|
|
}
|
|
|
|
// Bail here if the file input is invalid
|
|
if ( params.file_input.length < 1 || _.isUndefined( params.file_input[0] ) || ! params.file_input[0].files || _.isEmpty( params.file_input.val() ) ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'missing-import-file',
|
|
type : 'info',
|
|
duration : 30000,
|
|
message : [
|
|
'<span style="color:#0075a2">',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['Missing file'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Set the scope in the case of a manual import
|
|
var inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( _input.id, _input.module.module_type );
|
|
_scope = inputRegistrationParams.scope;
|
|
|
|
// display the uploading message
|
|
_input.container.find('.sek-uploading').show();
|
|
|
|
// make sure a previous warning gets removed
|
|
api.notifications.remove( 'missing-import-file' );
|
|
api.notifications.remove( 'import-success' );
|
|
api.notifications.remove( 'import-failed' );
|
|
api.notifications.remove( 'img-import-errors');
|
|
|
|
|
|
//console.log('params.file_input[0].files[0] ??', params.file_input[0].files[0] );
|
|
var fd = new FormData();
|
|
fd.append( 'file_candidate', params.file_input[0].files[0] );
|
|
fd.append( 'action', 'sek_get_manually_imported_file_content' );
|
|
fd.append( 'nonce', api.settings.nonce.save );
|
|
|
|
// Make sure we have a correct scope provided
|
|
if ( !_.contains( ['local', 'global'], _scope ) ) {
|
|
api.errare('::import_template => invalid scope provided', _scope );
|
|
return;
|
|
}
|
|
fd.append( 'skope', _scope);
|
|
// When doing the pre_import_check, we inform the server about it
|
|
// so that the image sniff and upload is not processed at this stage.
|
|
if ( params.pre_import_check ) {
|
|
fd.append( 'pre_import_check', params.pre_import_check );
|
|
}
|
|
|
|
// april 2020 : introduced for https://github.com/presscustomizr/nimble-builder/issues/663
|
|
fd.append( 'import_img', _input.input_parent.czr_Input('import_img')() );
|
|
|
|
// fire an uploading message removed on .always()
|
|
_input.container.find('.sek-uploading').show();
|
|
|
|
__request__ = $.ajax({
|
|
url: wp.ajax.settings.url,
|
|
data: fd,
|
|
// Setting processData to false lets you prevent jQuery from automatically transforming the data into a query string. See the docs for more info. http://api.jquery.com/jQuery.ajax/
|
|
// Setting the contentType to false is imperative, since otherwise jQuery will set it incorrectly. https://stackoverflow.com/a/5976031/33080
|
|
processData: false,
|
|
contentType: false,
|
|
type: 'POST',
|
|
// success: function(data){
|
|
// alert(data);
|
|
// }
|
|
});
|
|
|
|
// When pre checking on manual mode, return a promise
|
|
if ( params.pre_import_check ) {
|
|
return $.Deferred( function() {
|
|
var dfd = this;
|
|
__request__
|
|
.done( function( server_resp ) {
|
|
//console.log('__request__ done in pre_import_check', server_resp );
|
|
if( !server_resp.success ) {
|
|
dfd.reject( server_resp );
|
|
}
|
|
if ( !api.czr_sektions.isImportedContentEligibleForAPI( server_resp, params ) ) {
|
|
dfd.reject( server_resp );
|
|
}
|
|
//console.log('ALORS DONE IN PRE IMPORT CHECK ?');
|
|
dfd.resolve( server_resp );
|
|
})
|
|
.fail( function( server_resp ) {
|
|
dfd.reject( server_resp );
|
|
});
|
|
// .always( function() {
|
|
// //input.container.find('.sek-uploading').hide();
|
|
// });
|
|
});
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////
|
|
/// NOW THAT WE HAVE OUR PROMISE
|
|
/// 1) CHECK IF CONTENT IS WELL FORMED AND ELIGIBLE FOR API
|
|
/// 2) LET'S PROCESS THE SETTING ID'S
|
|
/// 3) ATTEMPT TO UPDATE THE SETTING API, LOCAL OR GLOBAL. ( always local for template import )
|
|
|
|
// fire a previewer loader removed on .always()
|
|
api.previewer.send( 'sek-maybe-print-loader', { fullPageLoader : true, duration : 30000 });
|
|
|
|
// After 30 s display a failure notification
|
|
// april 2020 : introduced for https://github.com/presscustomizr/nimble-builder/issues/663
|
|
_.delay( function() {
|
|
if ( 'pending' !== __request__.state() )
|
|
return;
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-too-long',
|
|
type : 'error',
|
|
duration : 20000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['Import exceeds server response time, try to uncheck "import images" option.'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
}, 30000 );
|
|
|
|
|
|
// At this stage, we are not in a pre-check case
|
|
// the ajax request is processed and will upload images if needed
|
|
__request__
|
|
.done( function( server_resp ) {
|
|
//console.log('SERVER RESP 2 ?', server_resp );
|
|
if ( !api.czr_sektions.isImportedContentEligibleForAPI( server_resp, params ) ) {
|
|
api.infoLog('::import_template problem => !api.czr_sektions.isImportedContentEligibleForAPI', server_resp, params );
|
|
return;
|
|
}
|
|
//console.log('MANUAL IMPORT DATA', server_resp );
|
|
server_resp.data.data.collection = api.czr_sektions.setIdsForImportedTmpl( server_resp.data.data.collection );
|
|
// and try to update the api setting
|
|
api.czr_sektions.doUpdateApiSettingAfter_FileImport( server_resp, params );
|
|
})
|
|
.fail( function( response ) {
|
|
api.errare( '::import_template => ajax error', response );
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-failed',
|
|
type : 'error',
|
|
duration : 30000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['Import failed, file problem'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
})
|
|
.always( function() {
|
|
api.czr_sektions.doAlwaysAfterFileImportAndApiSettingUpdate( params );
|
|
});
|
|
},//import_template_from_file
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
// FILE IMPORT => PRE-IMPORT CHECKS
|
|
////////////////////////////////////////////////////////
|
|
// Compare current active locations with the imported ones
|
|
// if some imported locations are not rendered in the current context, reveal the import dialog
|
|
// before comparing locations, purge the collection of imported location from header and footer if any
|
|
// "nimble_local_header", "nimble_local_footer"
|
|
pre_checks_from_file_import : function( server_resp, params ) {
|
|
params = params || {};
|
|
// normalize params
|
|
params = $.extend({
|
|
is_file_import : true,
|
|
pre_import_check : false,
|
|
assign_missing_locations : false,
|
|
input : '',
|
|
file_input : ''
|
|
}, params );
|
|
|
|
// We must have a params.input when import is manual
|
|
if ( params.is_file_import && _.isEmpty( params.input ) ) {
|
|
throw new Error( '::pre_checks_from_file_import => missing file_input param' );
|
|
}
|
|
|
|
var currentActiveLocations = api.czr_sektions.activeLocations(),
|
|
importedActiveLocations = $.extend( true, [], _.isArray( server_resp.data.metas.active_locations ) ? server_resp.data.metas.active_locations : [] ),
|
|
input = params.input,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type );
|
|
|
|
// filter to remove local header and footer before comparison with current active locations
|
|
importedActiveLocations = _.filter( importedActiveLocations, function( locId ) {
|
|
return !_.contains( ['nimble_local_header', 'nimble_local_footer'], locId );
|
|
});
|
|
|
|
if ( _.isArray( importedActiveLocations ) && _.isArray( currentActiveLocations ) ) {
|
|
var importedActiveLocationsNotAvailableInCurrentActiveLocations = $(importedActiveLocations).not(currentActiveLocations).get();
|
|
|
|
if ( !_.isEmpty( importedActiveLocationsNotAvailableInCurrentActiveLocations ) ) {
|
|
if ( params.is_file_import ) {
|
|
input.container.find('button[data-czr-action="sek-pre-import"]').hide();
|
|
|
|
// Different messages for local and global
|
|
// since sept 2019 for https://github.com/presscustomizr/nimble-builder/issues/495
|
|
// @see tmpl-nimble-input___import_export input php template for messages
|
|
if ( 'local' === inputRegistrationParams.scope ) {
|
|
input.container.find('.czr-import-dialog.czr-local-import').slideToggle();
|
|
} else {
|
|
input.container.find('.czr-import-dialog.czr-global-import').slideToggle();
|
|
}
|
|
}
|
|
api.infoLog('sek-pre-import => imported locations missing in current page.', importedActiveLocationsNotAvailableInCurrentActiveLocations );
|
|
} else {
|
|
api.czr_sektions.import_template_from_file( params );
|
|
}
|
|
} else {
|
|
// if current and imported location are not arrays, there's a problem.
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-failed',
|
|
type : 'info',
|
|
duration : 30000,
|
|
message : [
|
|
'<span style="color:#0075a2">',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['Import failed'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
if ( params.is_file_import ) {
|
|
api.czr_sektions.doAlwaysAfterFileImportAndApiSettingUpdate( params );
|
|
}
|
|
}
|
|
},//pre_checks_from_file_import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Recursive helper to set ids in imported templates
|
|
// we have a server_resp well structured { success : true, data : { data : , metas, img_errors } }
|
|
// Let's set the unique level ids
|
|
setIdsForImportedTmpl : function( _data ) {
|
|
var self = this;
|
|
if ( _.isObject( _data ) || _.isArray( _data ) ) {
|
|
_.each( _data, function( _v, _k ) {
|
|
// go recursive ?
|
|
if ( _.isObject( _v ) || _.isArray( _v ) ) {
|
|
_data[_k] = self.setIdsForImportedTmpl( _v );
|
|
}
|
|
// double check on both the key and the value
|
|
// also re-generates new ids when the export has been done without replacing the ids by '__rep__me__'
|
|
if ( 'id' === _k && _.isString( _v ) && ( 0 === _v.indexOf( '__rep__me__' ) || 0 === _v.indexOf( '__nimble__' ) ) ) {
|
|
_data[_k] = sektionsLocalizedData.prefixForSettingsNotSaved + api.czr_sektions.guid();
|
|
}
|
|
});
|
|
}
|
|
return _data;
|
|
},
|
|
|
|
|
|
|
|
|
|
// @return a boolean
|
|
// server_resp : { success : true, data : {...} }
|
|
// check if :
|
|
// - server resp is a success
|
|
// - the server_response is well formed
|
|
isImportedContentEligibleForAPI : function( server_resp, params ) {
|
|
var status = true;
|
|
// If the setting value is unchanged, no need to go further
|
|
// is_local is decided with the input id => @see revision_history input type.
|
|
var unserialized_file_content = server_resp.data,
|
|
import_success = server_resp.success,
|
|
importErrorMsg = null;
|
|
|
|
// PHP generates the export like this:
|
|
// $export = array(
|
|
// 'data' => sek_get_skoped_seks( $_REQUEST['skope_id'] ),
|
|
// 'metas' => array(
|
|
// 'skope_id' => $_REQUEST['skope_id'],
|
|
// 'version' => NIMBLE_VERSION,
|
|
// // is sent as a string : "__after_header,__before_main_wrapper,loop_start,__before_footer"
|
|
// 'active_locations' => is_string( $_REQUEST['active_locations'] ) ? explode( ',', $_REQUEST['active_locations'] ) : array(),
|
|
// 'date' => date("Y-m-d")
|
|
// )
|
|
// );
|
|
// @see sek_maybe_export()
|
|
|
|
//api.infoLog('AJAX SUCCESS file_content ', server_resp, unserialized_file_content );
|
|
if ( !import_success ) {
|
|
importErrorMsg = [ sektionsLocalizedData.i18n['Import failed'], unserialized_file_content ].join(' : ');
|
|
}
|
|
|
|
if ( _.isNull( importErrorMsg ) && ! _.isObject( unserialized_file_content ) ) {
|
|
importErrorMsg = sektionsLocalizedData.i18n['Import failed, invalid file content'];
|
|
}
|
|
|
|
// Verify that we have the setting value and the import metas
|
|
var importSettingValue = unserialized_file_content.data,
|
|
importMetas = unserialized_file_content.metas,
|
|
imgImporErrors = unserialized_file_content.img_errors;
|
|
|
|
if ( _.isNull( importErrorMsg ) && ! _.isObject( importSettingValue ) ) {
|
|
importErrorMsg = sektionsLocalizedData.i18n['Import failed, invalid file content'];
|
|
}
|
|
|
|
if ( _.isNull( importErrorMsg ) && ! _.isObject( importMetas ) ) {
|
|
importErrorMsg = sektionsLocalizedData.i18n['Import failed, invalid file content'];
|
|
}
|
|
|
|
var currentSetId = api.czr_sektions.localSectionsSettingId();
|
|
|
|
// Manual import => set the relevant setting ID
|
|
if ( params.is_file_import ) {
|
|
var _input = params.input,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( _input.id, _input.module.module_type );
|
|
|
|
currentSetId = 'local' === inputRegistrationParams.scope ? currentSetId : api.czr_sektions.getGlobalSectionsSettingId();
|
|
}
|
|
|
|
|
|
if ( _.isNull( importErrorMsg ) && _.isEqual( api( currentSetId )(), importSettingValue ) ) {
|
|
api.infoLog('::isImportedContentEligibleForAPI => Setting unchanged');
|
|
status = false;
|
|
}
|
|
|
|
// bail here if we have an import error msg
|
|
if ( !_.isNull( importErrorMsg ) ) {
|
|
api.errare('::isImportedContentEligibleForAPI => invalid data sent from server', unserialized_file_content );
|
|
api.errare('::isImportedContentEligibleForAPI => importErrorMsg', importErrorMsg );
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-failed',
|
|
type : 'error',
|
|
duration : 30000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
importErrorMsg,
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
status = false;
|
|
}
|
|
|
|
// Img importation errors ?
|
|
if ( !_.isEmpty( imgImporErrors ) ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'img-import-errors',
|
|
type : 'info',
|
|
duration : 60000,
|
|
message : [
|
|
'<span style="color:#0075a2">',
|
|
[
|
|
'<strong>' + sektionsLocalizedData.i18n['Some image(s) could not be imported'] + '</strong><br/>',
|
|
'<span style="font-size:11px">' + imgImporErrors + '</span>'
|
|
].join(' : '),
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
}
|
|
return status;
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// fired on ajaxrequest done
|
|
// At this stage, the server_resp data structure has been validated.
|
|
// We can try to the update the api setting
|
|
doUpdateApiSettingAfter_FileImport : function( server_resp, params ){
|
|
//console.log('doUpdateApiSettingAfter_FileImport ???', params, server_resp );
|
|
params = params || {};
|
|
if ( !api.czr_sektions.isImportedContentEligibleForAPI( server_resp, params ) && params.is_file_import ) {
|
|
api.czr_sektions.doAlwaysAfterFileImportAndApiSettingUpdate( params );
|
|
return;
|
|
}
|
|
|
|
var _scope = 'local',
|
|
_keep_existing_sections = false;//<= only possibly true when importing manually
|
|
|
|
// Manual import => set the relevant scope
|
|
if ( params.is_file_import ) {
|
|
var _input = params.input,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( _input.id, _input.module.module_type );
|
|
|
|
_scope = inputRegistrationParams.scope;
|
|
_keep_existing_sections = 'local' === inputRegistrationParams.scope ? _input.input_parent.czr_Input('keep_existing_sections')() : false;
|
|
// api.infoLog('api.czr_sektions.localSectionsSettingId()?', api.czr_sektions.localSectionsSettingId());
|
|
// api.infoLog('inputRegistrationParams.scope ?', inputRegistrationParams.scope );
|
|
}
|
|
|
|
|
|
|
|
//api.infoLog('TODO => verify metas => version, active locations, etc ... ');
|
|
|
|
// Update the setting api via the normalized method
|
|
// the scope will determine the setting id, local or global
|
|
api.czr_sektions.updateAPISetting({
|
|
action : 'sek-import-from-file',
|
|
scope : _scope,//'global' or 'local'<= will determine which setting will be updated,
|
|
// => self.getGlobalSectionsSettingId() or self.localSectionsSettingId()
|
|
imported_content : server_resp.data,
|
|
assign_missing_locations : params.assign_missing_locations,
|
|
keep_existing_sections : _keep_existing_sections
|
|
}).done( function() {
|
|
// Clean an regenerate the local option setting
|
|
// Settings are normally registered once and never cleaned, unlike controls.
|
|
// After the import, updating the setting value will refresh the sections
|
|
// but the local options, persisted in separate settings, won't be updated if the settings are not cleaned
|
|
if ( 'local' === _scope ) {
|
|
var _doThingsAfterRefresh = function() {
|
|
// Removes and RE-register local settings and controls
|
|
api.czr_sektions.generateUI({
|
|
action : 'sek-generate-local-skope-options-ui',
|
|
clean_settings_and_controls_first : true//<= see api.czr_sektions.generateUIforLocalSkopeOptions()
|
|
});
|
|
api.previewer.unbind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
};
|
|
// 'czr-new-skopes-synced' is always sent on a previewer.refresh()
|
|
api.previewer.bind( 'czr-new-skopes-synced', _doThingsAfterRefresh );
|
|
}
|
|
|
|
//_notify( sektionsLocalizedData.i18n['The revision has been successfully restored.'], 'success' );
|
|
api.previewer.refresh();
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-success',
|
|
type : 'success',
|
|
duration : 30000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
sektionsLocalizedData.i18n['File successfully imported'],
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
}).fail( function( response ) {
|
|
api.errare( '::doUpdateApiSettingAfter_FileImport => error when firing ::updateAPISetting', response );
|
|
api.previewer.trigger('sek-notify', {
|
|
notif_id : 'import-failed',
|
|
type : 'error',
|
|
duration : 30000,
|
|
message : [
|
|
'<span>',
|
|
'<strong>',
|
|
[ sektionsLocalizedData.i18n['Import failed'], response ].join(' : '),
|
|
'</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
});
|
|
|
|
// Refresh the preview, so the markup is refreshed and the css stylesheet are generated
|
|
api.previewer.refresh();
|
|
},//doUpdateApiSettingAfter_FileImport()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fired when params.is_file_import only
|
|
doAlwaysAfterFileImportAndApiSettingUpdate : function( params ) {
|
|
api.previewer.send( 'sek-clean-loader', { cleanFullPageLoader : true });
|
|
|
|
params = params || {};
|
|
// normalize params
|
|
params = $.extend({
|
|
is_file_import : true,
|
|
pre_import_check : false,
|
|
assign_missing_locations : false,
|
|
input : '',
|
|
file_input : ''
|
|
}, params );
|
|
|
|
if ( !params.is_file_import )
|
|
return;
|
|
|
|
var input = params.input;
|
|
|
|
input.container.find('.sek-uploading').hide();
|
|
// Clean the file input val
|
|
params.file_input.val('').trigger('change');
|
|
// Close the import dialog
|
|
input.container.find('.czr-import-dialog').hide();
|
|
// display back the pre import button
|
|
input.container.find('button[data-czr-action="sek-pre-import"]').show();
|
|
}
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
$.extend( CZRSeksPrototype, {
|
|
////////////////////////////////////////////////////////
|
|
// TEMPLATE GALLERY
|
|
////////////////////////////////////////////////////////
|
|
// APRIL 2020 : for https://github.com/presscustomizr/nimble-builder/issues/651
|
|
setupTemplateGallery : function() {
|
|
var self = this;
|
|
self.templateGalleryExpanded = new api.Value(false);
|
|
self.tmplInjectDialogVisible = new api.Value(false);// Hidden by default
|
|
if ( !sektionsLocalizedData.isTemplateGalleryEnabled )
|
|
return;
|
|
|
|
self.tmplSearchFieldVisible = new api.Value(false);// Hidden by default
|
|
self.tmplSearchFieldVisible.bind( function( visible ) {
|
|
var $tmplSearchWrap = self.cachedElements.$body.find('.sek-tmpl-filter-wrapper');
|
|
if ( visible ) {
|
|
$tmplSearchWrap.fadeIn('fast');
|
|
} else {
|
|
$tmplSearchWrap.fadeOut('fast');
|
|
}
|
|
});
|
|
|
|
self.templateGalleryExpanded.bind( function( expanded ) {
|
|
self.cachedElements.$body.toggleClass( 'sek-template-gallery-expanded', expanded );
|
|
if ( expanded ) {
|
|
// close template saver
|
|
// close level tree
|
|
self.tmplDialogVisible(false);
|
|
self.levelTreeExpanded(false);
|
|
self.tmplInjectDialogVisible(false);
|
|
$('#customize-preview iframe').css('z-index', 1);
|
|
self.renderOrRefreshTempGallery( { tmpl_source:'api_tmpl' } );
|
|
} else {
|
|
$('#customize-preview iframe').css('z-index', '');
|
|
api.trigger('nb-template-gallery-closed');
|
|
// SITE TEMPLATE PICKING
|
|
// When closing template gallery, make sure NB reset the possible previous tmpl scope used in a site template picking scenario
|
|
self._site_tmpl_scope = null;
|
|
// If template gallery was closed during a site template picking scenario, make sure input state is reset
|
|
$('[data-input-type="site_tmpl_picker"]').removeClass('sek-site-tmpl-picking-active');
|
|
}
|
|
});
|
|
|
|
self.tmplInjectDialogVisible.bind( function( expanded ) {
|
|
self.cachedElements.$body.toggleClass( 'sek-tmpl-dialog-expanded', expanded );
|
|
if ( expanded ) {
|
|
// close template saver
|
|
// close level tree
|
|
self.tmplDialogVisible(false);
|
|
self.levelTreeExpanded(false);
|
|
$('#customize-preview iframe').css('z-index', 1);
|
|
}
|
|
});
|
|
|
|
// API READY
|
|
api.previewer.bind('ready', function() {
|
|
self.templateGalleryExpanded( false );
|
|
});
|
|
},
|
|
|
|
renderTmplGalleryUI : function() {
|
|
if ( $('#nimble-tmpl-gallery').length > 0 )
|
|
return $('#nimble-tmpl-gallery');
|
|
|
|
var self = this;
|
|
|
|
try {
|
|
_tmpl = wp.template( 'nimble-top-tmpl-gallery' )( {} );
|
|
} catch( er ) {
|
|
api.errare( 'Error when parsing nimble-top-tmpl-gallery template', er );
|
|
return false;
|
|
}
|
|
$('#customize-preview').after( $( _tmpl ) );
|
|
return $('#nimble-tmpl-gallery');
|
|
},
|
|
|
|
// print and schedule dom events
|
|
// @params : { tmpl_source:'api_tmpl'}
|
|
renderOrRefreshTempGallery : function( params ) {
|
|
params = $.extend( {tmpl_source:'api_tmpl'}, params || {} );
|
|
var self = this,
|
|
$tmplGalWrapper;
|
|
if( $('#nimble-tmpl-gallery').length < 1 ) {
|
|
$.when( self.renderTmplGalleryUI({}) ).done( function() {
|
|
self.setupTmplGalleryDOMEvents();
|
|
});
|
|
}
|
|
|
|
// Clean previous html
|
|
$('#nimble-tmpl-gallery').find('.sek-tmpl-gallery-inner').html('');
|
|
|
|
var _doPrintTmplGalleryHtml = function(params) {
|
|
return self.getTemplateGalleryHtml( params ).done( function( html ) {
|
|
$tmplGalWrapper = $('#nimble-tmpl-gallery');
|
|
$tmplGalWrapper.find('.sek-tmpl-gallery-inner').html( html );
|
|
$tmplGalWrapper.removeClass('sek-is-site-tmpl-mode');
|
|
// Site template picking mode => add a class in order to display only api site templates
|
|
if ( 'api_tmpl' === params.tmpl_source && self._site_tmpl_scope && !_.isEmpty( self._site_tmpl_scope ) ) {
|
|
$tmplGalWrapper.addClass('sek-is-site-tmpl-mode');
|
|
}
|
|
});
|
|
};
|
|
// Wait for the gallery to be fetched and rendered
|
|
_doPrintTmplGalleryHtml( params ).done( function( html ) {
|
|
if ( _.isEmpty( html ) && 'api_tmpl' === params.tmpl_source ) {
|
|
if ( typeof window.console.log == 'function' ) {
|
|
console.log('Nimble Builder API problem => could not fetch templates');
|
|
}
|
|
_doPrintTmplGalleryHtml( {tmpl_source:'user_tmpl' } );
|
|
} else {
|
|
$tmplGalWrapper = $('#nimble-tmpl-gallery');
|
|
$tmplGalWrapper.find('#sek-tmpl-source-switcher').show();
|
|
// Reset template source switcher buttons
|
|
$tmplGalWrapper.find('#sek-tmpl-source-switcher button').attr('aria-pressed', "false").removeClass('is-selected');
|
|
$tmplGalWrapper.find('[data-sek-tmpl-source="'+ params.tmpl_source +'"]').attr('aria-pressed', "true").addClass('is-selected');
|
|
}
|
|
});
|
|
},
|
|
|
|
// @return html
|
|
getTemplateGalleryHtml : function( params ) {
|
|
var self = this,
|
|
_html = '';
|
|
// var _templates = {
|
|
// temp_one : {
|
|
// thumb_url : 'https://nimblebuilder.com/wp-content/uploads/2020/04/2020-04-06_16-36-12.jpg',
|
|
// preview_url : ''
|
|
// },
|
|
// temp_two : {
|
|
// thumb_url : 'https://nimblebuilder.com/wp-content/uploads/2020/04/2020-04-06_16-36-12.jpg',
|
|
// preview_url : ''
|
|
// };
|
|
|
|
// _.each( _templates, function( _data, _temp_id ) {
|
|
// _html += '<div class="sek-tmpl-item" data-sek-tmpl-item-id="' + _temp_id + '">';
|
|
// _html += '<div class="sek-tmpl-thumb"><img src="'+ _data.thumb_url +'"/></div>';
|
|
// _html += '</div>';
|
|
// });
|
|
var _defaultThumbUrl = [ sektionsLocalizedData.baseUrl , '/assets/admin/img/wire_frame.png', '?ver=' , sektionsLocalizedData.nimbleVersion ].join(''),
|
|
_dfd_ = $.Deferred(),
|
|
_titleAttr,
|
|
_thumbUrl,
|
|
$cssLoader = $('#nimble-tmpl-gallery').find('.czr-css-loader');
|
|
|
|
$cssLoader.show();
|
|
|
|
var _doRender = function( tmpl_collection ) {
|
|
if ( _.isEmpty( tmpl_collection ) && 'user_tmpl' === params.tmpl_source ) {
|
|
var _placeholdImgUrl = [ sektionsLocalizedData.baseUrl , '/assets/admin/img/empty_tmpl_collection_notice.jpg', '?ver=' , sektionsLocalizedData.nimbleVersion ].join(''),
|
|
doc_url = 'https://docs.presscustomizr.com/article/426-how-to-save-and-reuse-templates-with-nimble-builder';
|
|
|
|
_html += '<div class="sek-tmpl-empty-collection">';
|
|
_html += '<p>' + sektionsLocalizedData.i18n['You did not save any templates yet.'] + '</p>';
|
|
_html += '<img src="'+ _placeholdImgUrl +'" />';
|
|
_html += '<br/><a href="'+ doc_url +'" target="_blank" rel="noreferrer nofollow">'+ doc_url +'</a>';
|
|
_html += '</div>';
|
|
} else {
|
|
_.each( tmpl_collection, function( _data, _temp_id ) {
|
|
if( !_.isEmpty( _data.description ) ) {
|
|
_titleAttr = [ _data.title, _data.last_modified_date, _data.description ].join(' | ');
|
|
} else {
|
|
_titleAttr = [ _data.title, _data.last_modified_date ].join(' | ');
|
|
}
|
|
|
|
_thumbUrl = !_.isEmpty( _data.thumb_url ) ? _data.thumb_url : _defaultThumbUrl;
|
|
|
|
_html += '<div class="sek-tmpl-item" data-sek-tmpl-item-id="' + _temp_id + '" data-sek-tmpl-item-source="'+ params.tmpl_source +'" data-sek-api-site-tmpl="' + (_data.is_site_tmpl ? "true" : "false") +'" data-sek-is-pro-tmpl="' + (_data.is_pro_tmpl ? "yes" : "no") + '">';
|
|
//_html += '<div class="sek-tmpl-thumb"><img src="'+ _thumbUrl +'"/></div>';
|
|
_html += '<div class="tmpl-top-title"><h3>' + _data.title + '</h3></div>';
|
|
_html += '<div class="tmpl-thumb-and-info-wrap">';
|
|
_html += '<div class="sek-tmpl-thumb" style="background-image:url('+ _thumbUrl +')"></div>';
|
|
_html += '<div class="sek-tmpl-info" title="'+ _titleAttr +'">';
|
|
// _html += '<h3 class="tmpl-title tmpl-api-hide">' + _data.title + '</h3>';
|
|
_html += '<p class="tmpl-desc tmpl-api-hide">' + _data.description + '</p>';
|
|
_html += '<p class="tmpl-date tmpl-api-hide"><i>' + [ sektionsLocalizedData.i18n['Last modified'], ' : ', _data.last_modified_date ].join(' ') + '</i></p>';
|
|
_html += '<i class="material-icons use-tmpl" title="'+ sektionsLocalizedData.i18n['Use this template'] +'">add_circle_outline</i>';
|
|
if ( 'user_tmpl' === params.tmpl_source ) {
|
|
_html += '<i class="material-icons edit-tmpl" title="'+ sektionsLocalizedData.i18n['Edit this template'] +'">edit</i>';
|
|
_html += '<i class="material-icons remove-tmpl" title="'+ sektionsLocalizedData.i18n['Remove this template'] +'">delete_forever</i>';
|
|
}
|
|
if ( _data.is_pro_tmpl ) {
|
|
_html += '<div class="sek-is-pro-template"><img src="' + sektionsLocalizedData.czrAssetsPath + 'sek/img/pro_orange.svg" alt="Pro feature"/></div>';
|
|
}
|
|
|
|
if ( 'api_tmpl' === params.tmpl_source ) {
|
|
if ( _data.demo_url && -1 != _data.demo_url.indexOf('http') ) {
|
|
_html += '<div class="sek-tmpl-demo-link tmpl-api-hide"><a href="' + _data.demo_url + '?utm_source=usersite&utm_medium=link&utm_campaign=tmpl_demos" target="_blank" rel="noopener noreferrer">' + sektionsLocalizedData.i18n['Live demo'] + ' <i class="fas fa-external-link-alt"></i></a></div>';
|
|
}
|
|
if ( _data.is_site_tmpl ) {
|
|
_html += '<div class="sek-is-site-template" title="Site templates include dynamic template tags.">Site Template</div>';
|
|
}
|
|
}
|
|
_html += '</div>';
|
|
_html += '</div>';
|
|
_html += '</div>';
|
|
});
|
|
if ( 'api_tmpl' === params.tmpl_source && !_.isEmpty(_html) ) {
|
|
_html += '<div class="sek-tmpl-coming-soon">';
|
|
_html += '<p>' + sektionsLocalizedData.i18n['🍥 More templates coming...'] + '</p>';
|
|
_html += '</div>';
|
|
}
|
|
}
|
|
|
|
|
|
if ( $cssLoader.length > 0 ) {
|
|
$cssLoader.hide({
|
|
duration : 100,
|
|
complete : function() {
|
|
//$(this).remove();
|
|
_dfd_.resolve( _html );
|
|
}
|
|
});
|
|
} else {
|
|
_dfd_.resolve( _html );
|
|
}
|
|
};//_doRender
|
|
|
|
var _tmpl_collection_promise = 'user_tmpl' === params.tmpl_source ? self.setSavedTmplCollection : self.getApiTmplCollection;
|
|
_tmpl_collection_promise.call(self)
|
|
.done( function(tmpl_collection) {
|
|
setTimeout( function() {
|
|
_doRender(tmpl_collection);
|
|
self.tmplSearchFieldVisible( !_.isEmpty( tmpl_collection ) );
|
|
}, 0 );
|
|
})
|
|
.fail( function() {
|
|
console.log('tmpl collection promise failed', params );
|
|
_dfd_.resolve('');
|
|
});
|
|
return _dfd_.promise();
|
|
},
|
|
|
|
|
|
|
|
// @return void()
|
|
setupTmplGalleryDOMEvents : function() {
|
|
var $galWrapper = $('#nimble-tmpl-gallery'),
|
|
self = this;
|
|
|
|
$galWrapper
|
|
// Schedule click event with delegation
|
|
// PICK A TEMPLATE
|
|
.on('click', '.sek-tmpl-item .use-tmpl', function( evt ) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
var _tmpl_id = $(this).closest('.sek-tmpl-item').data('sek-tmpl-item-id'),
|
|
_tmpl_source = $(this).closest('.sek-tmpl-item').data('sek-tmpl-item-source'),
|
|
_tmpl_title = $(this).closest('.sek-tmpl-item').find('.tmpl-top-title h3').html(),
|
|
_tmpl_is_pro = 'yes' === $(this).closest('.sek-tmpl-item').data('sek-is-pro-tmpl');
|
|
|
|
if ( _.isEmpty(_tmpl_id) ) {
|
|
api.errare('::setupTmplGalleryDOMEvents => error => invalid template id');
|
|
return;
|
|
}
|
|
|
|
if ( _tmpl_is_pro ) {
|
|
var _problemMsg;
|
|
if ( sektionsLocalizedData.isPro ) {
|
|
// Check if :
|
|
// 1) the license key has been entered
|
|
// 2) the status is 'valid'
|
|
if ( _.isEmpty( sektionsLocalizedData.pro_license_key ) ) {
|
|
_problemMsg = sektionsLocalizedData.i18n['Missing license key'];
|
|
} else if ( 'valid' !== sektionsLocalizedData.pro_license_status ) {
|
|
_problemMsg = sektionsLocalizedData.i18n['Pro license problem'];
|
|
}
|
|
// If we have a problem msg let's print it and bail now
|
|
if ( !_.isEmpty( _problemMsg ) ) {
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'error',
|
|
duration : 60000,
|
|
is_pro_notif : true,
|
|
notif_id : 'pro_tmpl_error',
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>'+ _problemMsg + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
api.previewer.trigger('sek-notify', {
|
|
type : 'info',
|
|
duration : 60000,
|
|
//is_pro_notif : true,
|
|
notif_id : 'go_pro',
|
|
message : [
|
|
'<span style="font-size:0.95em">',
|
|
'<strong>'+ sektionsLocalizedData.i18n['Go pro link when click on pro tmpl or section'] + '</strong>',
|
|
'</span>'
|
|
].join('')
|
|
});
|
|
return;
|
|
}
|
|
}//if tmpl is pro
|
|
|
|
// Site template mode ?
|
|
if ( self._site_tmpl_scope && !_.isEmpty( self._site_tmpl_scope ) ) {
|
|
var $siteTmplInput = $( '[data-czrtype="' + self._site_tmpl_scope +'"]' );
|
|
if ( $siteTmplInput.length > 0 ) {
|
|
if ( !_.contains(['user_tmpl', 'api_tmpl'], _tmpl_source ) ) {
|
|
api.errare('Error when picking site template => invalid tmpl source');
|
|
return;
|
|
}
|
|
|
|
$siteTmplInput.trigger('nb-set-site-tmpl', {
|
|
site_tmpl_id : _tmpl_id,
|
|
site_tmpl_source : _tmpl_source,
|
|
site_tmpl_title : _tmpl_title
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if current page has NB sections, display an import dialog, otherwise import now
|
|
if ( self.hasCurrentPageNBSectionsNotHeaderFooter() ) {
|
|
self._tmplNameWhileImportDialog = _tmpl_id;
|
|
self._tmplSourceWhileImportDialog = _tmpl_source;
|
|
self._tmplIsProWhileImportDialog = _tmpl_is_pro;
|
|
self.tmplInjectDialogVisible(true);
|
|
} else {
|
|
api.previewer.send( 'sek-maybe-print-loader', { fullPageLoader : true, duration : 30000 });
|
|
//api.czr_sektions.get_gallery_tmpl_json_and_inject( $(this).data('sek-tmpl-item-id') );
|
|
//api.czr_sektions.get_gallery_tmpl_json_and_inject( {tmpl_name : 'test_one', tmpl_source: 'api_tmpl'});// FOR TEST PURPOSES UNTIL THE COLLECTION IS SETUP
|
|
api.czr_sektions.get_gallery_tmpl_json_and_inject( {
|
|
tmpl_name : _tmpl_id,
|
|
tmpl_source: _tmpl_source,
|
|
tmpl_is_pro: _tmpl_is_pro
|
|
}).always( function() {
|
|
api.previewer.send( 'sek-clean-loader');
|
|
});
|
|
self.templateGalleryExpanded(false);
|
|
}
|
|
})
|
|
// PICK AN IMPORT MODE WHEN PAGE HAS SECTIONS ALREADY
|
|
.on('click', '.sek-tmpl-gal-inject-dialog .sek-ui-button', function( evt ) {
|
|
evt.preventDefault();
|
|
evt.stopPropagation();
|
|
var tmpl_inject_mode = $(this).data('sek-tmpl-inject-mode');
|
|
|
|
// Did user cancel tmpl injection ?
|
|
if ( 'cancel' === tmpl_inject_mode ) {
|
|
self.tmplInjectDialogVisible(false);
|
|
return;
|
|
}
|
|
|
|
// 3 possible import modes : replace, before, after
|
|
if ( !_.contains(['replace', 'before', 'after'], tmpl_inject_mode ) ) {
|
|
api.errare('::setupTmplGalleryDOMEvents => error => invalid import mode');
|
|
return;
|
|
}
|
|
api.previewer.send( 'sek-maybe-print-loader', { fullPageLoader : true, duration : 30000 });
|
|
api.czr_sektions.get_gallery_tmpl_json_and_inject({
|
|
tmpl_name : self._tmplNameWhileImportDialog,
|
|
tmpl_source: self._tmplSourceWhileImportDialog,
|
|
tmpl_is_pro: self._tmplIsProWhileImportDialog,
|
|
tmpl_inject_mode: tmpl_inject_mode
|
|
}).always( function() {
|
|
api.previewer.send( 'sek-clean-loader', { cleanFullPageLoader : true });
|
|
});
|
|
// api.czr_sektions.get_gallery_tmpl_json_and_inject({
|
|
// tmpl_name : 'test_one',
|
|
// tmpl_source: 'nimble_api',
|
|
// tmpl_inject_mode: tmpl_inject_mode
|
|
// });
|
|
self.templateGalleryExpanded(false);
|
|
})
|
|
// SEARCH ACTIONS
|
|
.on('propertychange change click keyup input paste', '.sek-filter-tmpl', _.debounce( function(evt) {
|
|
evt.preventDefault();
|
|
var _s = $(this).val();
|
|
var _reset = function() {
|
|
$galWrapper.removeClass('search-active');
|
|
$galWrapper.find('.sek-tmpl-item').each( function() {
|
|
$(this).removeClass('search-match');
|
|
});
|
|
};
|
|
if ( !_.isString(_s) ) {
|
|
_reset();
|
|
return;
|
|
}
|
|
_s = _s.trim().toLowerCase();
|
|
if ( _.isEmpty( _s.replace(/\s/g, '') ) ) {
|
|
_reset();
|
|
} else {
|
|
$galWrapper.addClass('search-active');
|
|
var title,desc,date,titleMatch, descMatch,dateMatch;
|
|
$galWrapper.find('.sek-tmpl-item').each( function() {
|
|
title = ( $(this).find('.tmpl-title').html() + '' ).toLowerCase();
|
|
desc = ( $(this).find('.tmpl-desc').html() + '' ).toLowerCase();
|
|
date = ( $(this).find('.tmpl-date').html() + '' ).toLowerCase();
|
|
titleMatch = -1 != title.indexOf(_s);
|
|
descMatch = -1 != desc.indexOf(_s);
|
|
dateMatch = -1 != date.indexOf(_s);
|
|
$(this).toggleClass( 'search-match', titleMatch || descMatch || dateMatch );
|
|
});
|
|
}
|
|
|
|
}, 100 ) )
|
|
// EDIT
|
|
.on( 'click', '.sek-tmpl-info .edit-tmpl', function(evt) {
|
|
evt.preventDefault();
|
|
var _focusOnEditCandidate = function( mode ) {
|
|
self.tmplDialogMode( 'edit' );
|
|
// self unbind
|
|
self.tmplDialogMode.unbind( _focusOnEditCandidate );
|
|
};
|
|
self.tmplToEdit = $(this).closest("[data-sek-tmpl-item-id]").data('sek-tmpl-item-id');
|
|
self.tmplDialogMode.bind( _focusOnEditCandidate );
|
|
self.tmplDialogVisible(true);
|
|
})
|
|
// REMOVE
|
|
.on( 'click', '.sek-tmpl-info .remove-tmpl', function(evt) {
|
|
evt.preventDefault();
|
|
var _focusOnRemoveCandidate = function( mode ) {
|
|
self.tmplDialogMode( 'remove' );
|
|
// self unbind
|
|
self.tmplDialogMode.unbind( _focusOnRemoveCandidate );
|
|
};
|
|
self.tmplToRemove = $(this).closest("[data-sek-tmpl-item-id]").data('sek-tmpl-item-id');
|
|
self.tmplDialogMode.bind( _focusOnRemoveCandidate );
|
|
self.tmplDialogVisible(true);
|
|
})
|
|
.on( 'click', '.sek-close-dialog', function(evt) {
|
|
evt.preventDefault();
|
|
self.templateGalleryExpanded( false );
|
|
})
|
|
.on( 'click', '#sek-tmpl-source-switcher button', function( evt ) {
|
|
evt.preventDefault();
|
|
$('#sek-tmpl-source-switcher button').removeClass('is-selected').attr('aria-pressed', "false");
|
|
$(this).addClass('is-selected').attr('aria-pressed', "true");
|
|
self.renderOrRefreshTempGallery( { tmpl_source: $(this).data('sek-tmpl-source') } );
|
|
});
|
|
},
|
|
});//$.extend()
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
var CZRSeksPrototype = CZRSeksPrototype || {};
|
|
(function ( api, $ ) {
|
|
// Skope
|
|
$.extend( CZRSeksPrototype, api.Events );
|
|
var CZR_SeksConstructor = api.Class.extend( CZRSeksPrototype );
|
|
|
|
// Schedule skope instantiation on api ready
|
|
// api.bind( 'ready' , function() {
|
|
// api.czr_skopeBase = new api.CZR_SeksConstructor();
|
|
// });
|
|
try { api.czr_sektions = new CZR_SeksConstructor(); } catch( er ) {
|
|
api.errare( 'api.czr_sektions => problem on instantiation', er );
|
|
}
|
|
})( wp.customize, jQuery );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
//input_type => callback fn to fire in the Input constructor on initialize
|
|
//the callback can receive specific params define in each module constructor
|
|
//For example, a content picker can be given params to display only taxonomies
|
|
$.extend( api.czrInputMap, {
|
|
content_type_switcher : function( input_options ) {
|
|
var input = this,
|
|
_section_,
|
|
initial_content_type,
|
|
nbApiInstance = api.czr_sektions;
|
|
|
|
if ( ! api.section.has( input.module.control.section() ) ) {
|
|
throw new Error( 'api.czrInputMap.content_type_switcher => section not registered' );
|
|
}
|
|
_section_ = api.section( input.module.control.section() );
|
|
|
|
var _do_ = function( contentType ) {
|
|
input.container.find( '[data-sek-content-type="' + ( contentType || 'module' ) + '"]').trigger('click');
|
|
_.each( _section_.controls(), function( _control_ ) {
|
|
if ( ! _.isUndefined( _control_.content_type ) ) {
|
|
_control_.active( contentType === _control_.content_type );
|
|
}
|
|
});
|
|
};
|
|
|
|
// Initialize
|
|
// Fixes issue https://github.com/presscustomizr/nimble-builder/issues/248
|
|
api.czr_sektions.currentContentPickerType = api.czr_sektions.currentContentPickerType || new api.Value();
|
|
// This event is emitted by ::generateUIforDraggableContent()
|
|
// this way we are sure that all controls for modules and sections are instantiated
|
|
// and we can use _section_.controls() to set the visibility of module / section controls when switching
|
|
api.bind('nimble-modules-and-sections-controls-registered', function() {
|
|
_do_( api.czr_sektions.currentContentPickerType() );
|
|
});
|
|
|
|
|
|
|
|
// Schedule a reaction to changes
|
|
api.czr_sektions.currentContentPickerType.bind( function( contentType ) {
|
|
_do_( contentType );
|
|
});
|
|
|
|
// initialize the content picker observer
|
|
api.czr_sektions.currentContentPickerType( input() );
|
|
|
|
// attach click event on data-sek-content-type buttons
|
|
input.container.on('click', '[data-sek-content-type]', function( evt ) {
|
|
evt.preventDefault();
|
|
var _contentType = $(this).data( 'sek-content-type');
|
|
// handle the aria-pressed state
|
|
input.container.find('[data-sek-content-type]').attr( 'aria-pressed', false );
|
|
|
|
// close other dialog
|
|
//nbApiInstance.templateGalleryExpanded(false);
|
|
nbApiInstance.levelTreeExpanded(false);
|
|
if ( nbApiInstance.tmplDialogVisible ) {
|
|
nbApiInstance.tmplDialogVisible(false);
|
|
}
|
|
if ( nbApiInstance.saveSectionDialogVisible ) {
|
|
nbApiInstance.saveSectionDialogVisible(false);
|
|
}
|
|
|
|
// April 2020 : template case added for https://github.com/presscustomizr/nimble-builder/issues/651
|
|
if ( 'template' === _contentType ) {
|
|
var _isExpanded = api.czr_sektions.templateGalleryExpanded();
|
|
$(this).attr( 'aria-pressed', !_isExpanded );
|
|
// When opening template gallery from the content type switcher, make sure NB reset the possible previous tmpl scope used in a site template picking scenario
|
|
self._site_tmpl_scope = null;
|
|
api.czr_sektions.templateGalleryExpanded(!_isExpanded);
|
|
} else {
|
|
// always close the template picker when selecting something else
|
|
api.czr_sektions.templateGalleryExpanded(false);
|
|
|
|
$(this).attr( 'aria-pressed', true );
|
|
|
|
// case for section and module content type
|
|
api.czr_sektions.currentContentPickerType( _contentType );
|
|
}
|
|
});
|
|
|
|
// Specific for templates
|
|
api.bind('nb-template-gallery-closed', function() {
|
|
input.container.find('[data-sek-content-type="template"]').attr( 'aria-pressed', false );
|
|
});
|
|
|
|
// initialize with module or section picker depending on the scenario :
|
|
// 1) new section created => section picker
|
|
// 2) all other cases => module picker
|
|
_do_( api.czr_sektions.currentContentPickerType() );
|
|
}
|
|
});
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
$.extend( api.czrInputMap, {
|
|
module_picker : function( input_options ) {
|
|
var input = this;
|
|
// Mouse effect with cursor: -webkit-grab; -webkit-grabbing;
|
|
// input.container.find('[draggable]').each( function() {
|
|
// $(this).on( 'mousedown mouseup', function( evt ) {
|
|
// switch( evt.type ) {
|
|
// case 'mousedown' :
|
|
// //$(this).addClass('sek-grabbing');
|
|
// break;
|
|
// case 'mouseup' :
|
|
// //$(this).removeClass('sek-grabbing');
|
|
// break;
|
|
// }
|
|
// });
|
|
// });
|
|
api.czr_sektions.trigger( 'sek-refresh-dragzones', { type : 'module', input_container : input.container } );
|
|
//console.log( this.id, input_options );
|
|
},
|
|
|
|
// June 2020 : input type used for both prebuilt and user sections
|
|
section_picker : function( input_options ) {
|
|
var input = this;
|
|
// Mouse effect with cursor: -webkit-grab; -webkit-grabbing;
|
|
// input.container.find('[draggable]').each( function() {
|
|
// $(this).on( 'mousedown mouseup', function( evt ) {
|
|
// switch( evt.type ) {
|
|
// case 'mousedown' :
|
|
// //$(this).addClass('sek-grabbing');
|
|
// break;
|
|
// case 'mouseup' :
|
|
// //$(this).removeClass('sek-grabbing');
|
|
// break;
|
|
// }
|
|
// });
|
|
// });
|
|
api.czr_sektions.trigger( 'sek-refresh-dragzones', { type : 'preset_section', input_container : input.container } );
|
|
}
|
|
});
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
$.extend( api.czrInputMap, {
|
|
module_option_switcher : function( input_options ) {
|
|
var input = this,
|
|
_section_,
|
|
initial_content_type;
|
|
|
|
if ( ! api.section.has( input.module.control.section() ) ) {
|
|
throw new Error( input.input_type + ' => section not registered' );
|
|
}
|
|
_section_ = api.section( input.module.control.section() );
|
|
|
|
var module_id = '',
|
|
requested_ui_action,
|
|
controlRegistrationParams = input.module.control.params.sek_registration_params;
|
|
|
|
if ( _.isUndefined( controlRegistrationParams ) ) {
|
|
throw new Error( input.input_type + ' => missing registration params' );
|
|
}
|
|
if ( controlRegistrationParams && controlRegistrationParams.module_id ) {
|
|
module_id = controlRegistrationParams.module_id;
|
|
requested_ui_action = controlRegistrationParams.ui_action;
|
|
}
|
|
if ( _.isEmpty( module_id ) ) {
|
|
throw new Error( input.input_type + ' => missing module id' );
|
|
}
|
|
|
|
// attach click event on data-sek-option-type buttons
|
|
input.container.on('click', '[data-sek-option-type]', function( evt ) {
|
|
evt.preventDefault();
|
|
// handle the is-selected css class toggling
|
|
input.container.find('[data-sek-option-type]').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
|
|
api.previewer.trigger( 'settings' === $(this).data( 'sek-option-type') ? 'sek-edit-options' : 'sek-edit-module',
|
|
{
|
|
id : module_id,
|
|
level : 'module'
|
|
}
|
|
);
|
|
});
|
|
|
|
// handle the is-selected css class toggling
|
|
var _requestedOptionType = 'sek-generate-level-options-ui' === requested_ui_action ? 'settings' : 'content';
|
|
|
|
input.container
|
|
.find('[data-sek-option-type]')
|
|
.removeClass('is-selected')
|
|
.attr( 'aria-pressed', false );
|
|
input.container
|
|
.find('[data-sek-option-type="'+ _requestedOptionType +'"]')
|
|
.addClass('is-selected')
|
|
.attr( 'aria-pressed', true );
|
|
}
|
|
});
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
|
|
// HELPERS USED IN ALL SPACING INPUT TYPES
|
|
// "this" is input
|
|
var validateUnit = function( unit ) {
|
|
if ( ! _.contains( ['px', 'em', '%'], unit ) ) {
|
|
api.errare( 'error : invalid unit for input ' + this.id, unit );
|
|
unit = 'px';
|
|
}
|
|
return unit;
|
|
},
|
|
stripUnit = function( value ) {
|
|
return _.isString( value ) ? value.replace(/px|em|%/g,'') : '';
|
|
},
|
|
unitButtonsSetup = function( $wrapper ) {
|
|
var input = this;
|
|
// Schedule unit changes on button click
|
|
// @param params can be { previewed_device_switched : true }
|
|
$wrapper.on( 'click', '.sek-ui-button', function( evt, params ) {
|
|
evt.preventDefault();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('.sek-ui-button').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
// set the current unit Value
|
|
input.css_unit( $(this).data('sek-unit'), params );
|
|
});
|
|
|
|
// add is-selected button on init to the relevant unit button
|
|
$wrapper.find( '.sek-ui-button[data-sek-unit="'+ ( input.initial_unit || 'px' ) +'"]').addClass('is-selected').attr( 'aria-pressed', true );
|
|
},
|
|
setupResetAction = function( $wrapper, defaultVal ) {
|
|
var input = this;
|
|
$wrapper.on( 'click', '.reset-spacing-wrap', function(evt) {
|
|
evt.preventDefault();
|
|
$wrapper.find('input[type="number"]').each( function() {
|
|
$(this).val('');
|
|
});
|
|
|
|
input( defaultVal );
|
|
// Reset unit to pixels
|
|
$('.sek-unit-wrapper', $wrapper ).find('[data-sek-unit="px"]').trigger('click');
|
|
});
|
|
};
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* SPACING CLASSIC
|
|
/* ------------------------------------------------------------------------- */
|
|
$.extend( api.czrInputMap, {
|
|
spacing : function( input_options ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-spacing-wrapper', input.container ),
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : [];
|
|
|
|
// Listen to user actions on the inputs and set the input value
|
|
$wrapper.on( 'input', 'input[type="number"]', function(evt) {
|
|
var _type_ = $(this).closest('[data-sek-spacing]').data('sek-spacing'),
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} ),
|
|
_rawVal = $(this).val();
|
|
|
|
// Validates
|
|
// @fixes https://github.com/presscustomizr/nimble-builder/issues/26
|
|
if ( ( _.isString( _rawVal ) && ! _.isEmpty( _rawVal ) ) || _.isNumber( _rawVal ) ) {
|
|
_newInputVal[ _type_ ] = _rawVal;
|
|
} else {
|
|
// this allow users to reset a given padding / margin instead of reseting them all at once with the "reset all spacing" option
|
|
_newInputVal = _.omit( _newInputVal, _type_ );
|
|
}
|
|
input( _newInputVal );
|
|
});
|
|
// Schedule a reset action
|
|
setupResetAction.call( input, $wrapper, defaultVal );
|
|
|
|
// Synchronize on init
|
|
if ( _.isObject( input() ) ) {
|
|
_.each( input(), function( _val_, _key_ ) {
|
|
$( '[data-sek-spacing="' + _key_ +'"]', $wrapper ).find( 'input[type="number"]' ).val( _val_ );
|
|
});
|
|
// loop on the unit buttons and check which one should be clicked
|
|
var unitToActivate = 'px';
|
|
$('.sek-unit-wrapper .sek-ui-button', input.container ).each( function() {
|
|
var unit = $(this).data('sek-unit');
|
|
// do we have a unit for the current device ?
|
|
if ( ! _.isEmpty( input() ) ) {
|
|
if ( ! _.isEmpty( input()[ 'unit' ] ) ) {
|
|
if ( unit === input()[ 'unit' ] ) {
|
|
unitToActivate = unit;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
$('.sek-unit-wrapper', input.container ).find('[data-sek-unit="' + validateUnit.call( input, unitToActivate ) + '"]').trigger('click');
|
|
}
|
|
|
|
// Set the initial unit
|
|
var initial_value = input();
|
|
input.initial_unit = 'px';
|
|
if ( ! _.isEmpty( initial_value ) ) {
|
|
input.initial_unit = _.isEmpty( initial_value['unit'] ) ? 'px' : initial_value['unit'];
|
|
}
|
|
|
|
// initialize the unit with the value provided in the dom
|
|
input.css_unit = new api.Value( validateUnit.call( input, input.initial_unit ) );
|
|
|
|
// React to a unit change
|
|
input.css_unit.bind( function( to ) {
|
|
to = _.isEmpty( to ) ? 'px' : to;
|
|
var _newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ 'unit' ] = to;
|
|
input( _newInputVal );
|
|
});
|
|
|
|
// Schedule unit changes on button click
|
|
// add is-selected button on init to the relevant unit button
|
|
unitButtonsSetup.call( input, $wrapper );
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* SPACING WITH DEVICE SWITCHER
|
|
/* ------------------------------------------------------------------------- */
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
spacingWithDeviceSwitcher : function( input_options ) {
|
|
// DEFINITIONS
|
|
var input = this,
|
|
$wrapper = $('.sek-spacing-wrapper', input.container ),
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
// Recursive helper
|
|
// return the value set for the currently previewed device if exists
|
|
// OR
|
|
// return the inherited value from the first parent device for which the value is set
|
|
// OR
|
|
// falls back on the module default
|
|
var getCurrentDeviceActualOrInheritedValue = function( inputValues, currentDevice ) {
|
|
var deviceHierarchy = [ 'mobile' , 'tablet', 'desktop' ];
|
|
if ( _.has( inputValues, currentDevice ) ) {
|
|
return inputValues[ currentDevice ];
|
|
} else {
|
|
var deviceIndex = _.findIndex( deviceHierarchy, function( _d_ ) { return currentDevice === _d_; });
|
|
if ( ! _.isEmpty( currentDevice ) && deviceIndex < deviceHierarchy.length ) {
|
|
return getCurrentDeviceActualOrInheritedValue( inputValues, deviceHierarchy[ deviceIndex + 1 ] );
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on previewed device changes
|
|
var syncWithPreviewedDevice = function( currentDevice ) {
|
|
var inputValues = $.extend( true, {}, _.isObject( input() ) ? input() : {} ),
|
|
clonedDefault = $.extend( true, {}, defaultVal );
|
|
inputValues = _.isObject( inputValues ) ? $.extend( clonedDefault, inputValues ) : clonedDefault;
|
|
var _currentDeviceValues = getCurrentDeviceActualOrInheritedValue( inputValues, currentDevice );
|
|
|
|
// loop on each sek spacing and check if we find a value to write for this device
|
|
$( '[data-sek-spacing]', $wrapper ).each( function() {
|
|
var spacingType = $(this).data('sek-spacing'),
|
|
_val_ = '';
|
|
// do we have a val for the current device ?
|
|
if ( ! _.isEmpty( _currentDeviceValues ) ) {
|
|
if ( ! _.isEmpty( _currentDeviceValues[ spacingType ] ) ) {
|
|
_val_ = _currentDeviceValues[ spacingType ];
|
|
}
|
|
}
|
|
$(this).find( 'input[type="number"]' ).val( _val_ );
|
|
});
|
|
|
|
// loop on the unit button and check which one should be clicked
|
|
var unitToActivate = 'px';
|
|
$( '.sek-unit-wrapper .sek-ui-button', input.container).each( function() {
|
|
var unit = $(this).data('sek-unit');
|
|
// do we have a unit for the current device ?
|
|
if ( ! _.isEmpty( _currentDeviceValues ) ) {
|
|
if ( ! _.isEmpty( _currentDeviceValues[ 'unit' ] ) ) {
|
|
if ( unit === _currentDeviceValues[ 'unit' ] ) {
|
|
unitToActivate = unit;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
$('.sek-unit-wrapper', input.container ).find('[data-sek-unit="' + validateUnit.call( input, unitToActivate ) + '"]').trigger('click', { previewed_device_switched : true });// We don't want to update the input();
|
|
};
|
|
|
|
|
|
|
|
|
|
// SETUP
|
|
api.czr_sektions.maybeSetupDeviceSwitcherForInput.call( input );
|
|
|
|
// Set the initial unit
|
|
var initial_value = input();
|
|
input.initial_unit = 'px';
|
|
if ( ! _.isEmpty( initial_value ) && ! _.isEmpty( initial_value[ input.previewedDevice() ] ) ) {
|
|
input.initial_unit = _.isEmpty( initial_value[ input.previewedDevice() ]['unit'] ) ? 'px' : initial_value[ input.previewedDevice() ]['unit'];
|
|
}
|
|
|
|
// initialize the unit with the value provided in the dom
|
|
input.css_unit = new api.Value( validateUnit.call( input, input.initial_unit ) );
|
|
|
|
|
|
|
|
|
|
// SCHEDULE REACTIONS
|
|
// Listen to user actions on the inputs and set the input value
|
|
$wrapper.on( 'input', 'input[type="number"]', function(evt) {
|
|
var changedSpacingType = $(this).closest('[data-sek-spacing]').data('sek-spacing'),
|
|
changedNumberInputVal = $(this).val(),
|
|
_newInputVal,
|
|
previewedDevice = api.previewedDevice() || 'desktop';
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ previewedDevice ] = $.extend( true, {}, _newInputVal[ previewedDevice ] || {} );
|
|
// Validates
|
|
// @fixes https://github.com/presscustomizr/nimble-builder/issues/26
|
|
if ( ( _.isString( changedNumberInputVal ) && ! _.isEmpty( changedNumberInputVal ) ) || _.isNumber( changedNumberInputVal ) ) {
|
|
_newInputVal[ previewedDevice ][ changedSpacingType ] = changedNumberInputVal;
|
|
} else {
|
|
// this allow users to reset a given padding / margin instead of reseting them all at once with the "reset all spacing" option
|
|
_newInputVal[ previewedDevice ] = _.omit( _newInputVal[ previewedDevice ], changedSpacingType );
|
|
}
|
|
|
|
// Make sure the current device unit is consistent with the input value
|
|
// fixes https://github.com/presscustomizr/nimble-builder/issues/667
|
|
if ( !_newInputVal[previewedDevice]['unit'] || input.css_unit() != _newInputVal[ previewedDevice ][ 'unit' ] ) {
|
|
_newInputVal[previewedDevice]['unit'] = input.css_unit();
|
|
}
|
|
|
|
input( _newInputVal );
|
|
});
|
|
|
|
// Schedule a reset action
|
|
setupResetAction.call( input, $wrapper, defaultVal );
|
|
|
|
// react to previewed device changes
|
|
// input.previewedDevice is updated in api.czr_sektions.maybeSetupDeviceSwitcherForInput()
|
|
input.previewedDevice.bind( function( currentDevice ) {
|
|
try { syncWithPreviewedDevice( currentDevice ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type spacingWithDeviceSwitcher for input id ' + input.id , er );
|
|
}
|
|
});
|
|
|
|
|
|
// React to a unit change
|
|
// Don't move when switching the device
|
|
input.css_unit.bind( function( to, from, params ) {
|
|
if ( _.isObject( params ) && true === params.previewed_device_switched )
|
|
return;
|
|
to = _.isEmpty( to ) ? 'px' : to;
|
|
var _newInputVal,
|
|
previewedDevice = input.previewedDevice() || 'desktop';
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ previewedDevice ] = $.extend( true, {}, _newInputVal[ previewedDevice ] || {} );
|
|
_newInputVal[ previewedDevice ][ 'unit' ] = to;
|
|
input( _newInputVal );
|
|
});
|
|
|
|
// Schedule unit changes on button click
|
|
// add is-selected button on init to the relevant unit button
|
|
unitButtonsSetup.call( input, $wrapper );
|
|
|
|
|
|
|
|
// INITIALIZES
|
|
try { syncWithPreviewedDevice( api.previewedDevice() ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type spacingWithDeviceSwitcher for input id ' + input.id , er );
|
|
}
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
/* ------------------------------------------------------------------------- *
|
|
* BG POSITION SIMPLE
|
|
/* ------------------------------------------------------------------------- */
|
|
bg_position : function( input_options ) {
|
|
var input = this;
|
|
// Listen to user actions on the inputs and set the input value
|
|
$('.sek-bg-pos-wrapper', input.container ).on( 'change', 'input[type="radio"]', function(evt) {
|
|
input( $(this).val() );
|
|
});
|
|
|
|
// Synchronize on init
|
|
if ( ! _.isEmpty( input() ) ) {
|
|
input.container.find('input[value="'+ input() +'"]').attr('checked', true).trigger('click');
|
|
}
|
|
},
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* BG POSITION WITH DEVICE SWITCHER
|
|
/* ------------------------------------------------------------------------- */
|
|
bgPositionWithDeviceSwitcher : function( input_options ) {
|
|
var input = this,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
// SETUP
|
|
api.czr_sektions.maybeSetupDeviceSwitcherForInput.call( input );
|
|
|
|
var getCurrentDeviceActualOrInheritedValue = function( inputValues, currentDevice ) {
|
|
var deviceHierarchy = [ 'mobile' , 'tablet', 'desktop' ];
|
|
if ( _.has( inputValues, currentDevice ) ) {
|
|
return inputValues[ currentDevice ];
|
|
} else {
|
|
var deviceIndex = _.findIndex( deviceHierarchy, function( _d_ ) { return currentDevice === _d_; });
|
|
if ( ! _.isEmpty( currentDevice ) && deviceIndex < deviceHierarchy.length ) {
|
|
return getCurrentDeviceActualOrInheritedValue( inputValues, deviceHierarchy[ deviceIndex + 1 ] );
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on previewed device changes
|
|
var syncWithPreviewedDevice = function( currentDevice ) {
|
|
var inputValues = $.extend( true, {}, _.isObject( input() ) ? input() : {} ),
|
|
clonedDefault = $.extend( true, {}, defaultVal );
|
|
inputValues = _.isObject( inputValues ) ? $.extend( clonedDefault, inputValues ) : clonedDefault;
|
|
var _currentDeviceValue = getCurrentDeviceActualOrInheritedValue( inputValues, currentDevice );
|
|
|
|
input.container.find('input[value="'+ _currentDeviceValue +'"]').attr('checked', true).trigger('click', { previewed_device_switched : true } );
|
|
};
|
|
|
|
|
|
|
|
// Listen to user actions on the inputs and set the input value
|
|
$('.sek-bg-pos-wrapper', input.container ).on( 'change', 'input[type="radio"]', function( evt ) {
|
|
var changedRadioVal = $(this).val(),
|
|
_newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ api.previewedDevice() || 'desktop' ] = changedRadioVal;
|
|
|
|
input( _newInputVal );
|
|
});
|
|
|
|
|
|
// react to previewed device changes
|
|
// input.previewedDevice is updated in api.czr_sektions.maybeSetupDeviceSwitcherForInput()
|
|
input.previewedDevice.bind( function( currentDevice ) {
|
|
try { syncWithPreviewedDevice( currentDevice ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type spacingWithDeviceSwitcher for input id ' + input.id , er );
|
|
}
|
|
});
|
|
|
|
// INITIALIZES
|
|
try { syncWithPreviewedDevice( api.previewedDevice() ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type bgPositionWithDeviceSwitcher for input id ' + input.id , er );
|
|
}
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// Generic method to instantiate the following input types :
|
|
// horizTextAlignmentWithDeviceSwitcher and horizAlignmentWithDeviceSwitcher => tmpl => 3_0_5_sek_input_tmpl_horizontal_alignment.php
|
|
// verticalAlignWithDeviceSwitcher => tmpl => 3_0_6_sek_input_tmpl_vertical_alignment.php
|
|
var x_or_y_AlignWithDeviceSwitcher = function( params ) {
|
|
var input = this,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {},
|
|
tmplSelector = 'verticalAlignWithDeviceSwitcher' === input.type ? '.sek-v-align-wrapper' : '.sek-h-align-wrapper',// <= because used by 2 different input tmpl
|
|
$wrapper = $( tmplSelector, input.container );
|
|
|
|
// SETUP
|
|
api.czr_sektions.maybeSetupDeviceSwitcherForInput.call( input );
|
|
|
|
var getCurrentDeviceActualOrInheritedValue = function( inputValues, currentDevice ) {
|
|
var deviceHierarchy = [ 'mobile' , 'tablet', 'desktop' ];
|
|
if ( _.has( inputValues, currentDevice ) ) {
|
|
return inputValues[ currentDevice ];
|
|
} else {
|
|
var deviceIndex = _.findIndex( deviceHierarchy, function( _d_ ) { return currentDevice === _d_; });
|
|
if ( ! _.isEmpty( currentDevice ) && deviceIndex < deviceHierarchy.length ) {
|
|
return getCurrentDeviceActualOrInheritedValue( inputValues, deviceHierarchy[ deviceIndex + 1 ] );
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on previewed device changes
|
|
var syncWithPreviewedDevice = function( currentDevice ) {
|
|
var inputValues = $.extend( true, {}, _.isObject( input() ) ? input() : {} ),
|
|
clonedDefault = $.extend( true, {}, defaultVal );
|
|
inputValues = _.isObject( inputValues ) ? $.extend( clonedDefault, inputValues ) : clonedDefault;
|
|
var _currentDeviceValue = getCurrentDeviceActualOrInheritedValue( inputValues, currentDevice );
|
|
|
|
//input.container.find('input[value="'+ _currentDeviceValue +'"]').attr('checked', true).trigger('click', { previewed_device_switched : true } );
|
|
$wrapper.find('.selected').removeClass('selected');
|
|
$wrapper.find( 'div[data-sek-align="' + _currentDeviceValue +'"]' ).addClass('selected');
|
|
};
|
|
|
|
// on click
|
|
$wrapper.on( 'click', '[data-sek-align]', function(evt) {
|
|
evt.preventDefault();
|
|
var _newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ api.previewedDevice() || 'desktop' ] = $(this).data('sek-align');
|
|
|
|
$wrapper.find('.selected').removeClass('selected');
|
|
$.when( $(this).addClass('selected') ).done( function() {
|
|
input( _newInputVal );
|
|
});
|
|
});
|
|
|
|
// react to previewed device changes
|
|
// input.previewedDevice is updated in api.czr_sektions.maybeSetupDeviceSwitcherForInput()
|
|
input.previewedDevice.bind( function( currentDevice ) {
|
|
try { syncWithPreviewedDevice( currentDevice ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type : ' + input.type + ' for input id ' + input.id , er );
|
|
}
|
|
});
|
|
|
|
// INITIALIZES
|
|
try { syncWithPreviewedDevice( api.previewedDevice() ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type : ' + input.type + ' for input id ' + input.id , er );
|
|
}
|
|
};
|
|
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
horizTextAlignmentWithDeviceSwitcher : x_or_y_AlignWithDeviceSwitcher,
|
|
horizAlignmentWithDeviceSwitcher : x_or_y_AlignWithDeviceSwitcher,
|
|
verticalAlignWithDeviceSwitcher : x_or_y_AlignWithDeviceSwitcher
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
// FONT PICKER
|
|
font_picker : function( input_options ) {
|
|
var input = this,
|
|
item = input.input_parent,
|
|
$fontSelectElement = $( 'select[data-czrtype="' + input.id + '"]', input.container );
|
|
|
|
var _getFontCollections = function() {
|
|
var dfd = $.Deferred();
|
|
if ( ! _.isEmpty( api.sek_fontCollections ) ) {
|
|
dfd.resolve( api.sek_fontCollections );
|
|
} else {
|
|
var _ajaxRequest_;
|
|
if ( ! _.isUndefined( api.sek_fetchingFontCollection ) && 'pending' == api.sek_fetchingFontCollection.state() ) {
|
|
_ajaxRequest_ = api.sek_fetchingFontCollection;
|
|
} else {
|
|
// This utility handles a cached version of the font_list once fetched the first time
|
|
// @see api.CZR_Helpers.czr_cachedTmpl
|
|
_ajaxRequest_ = api.CZR_Helpers.getModuleTmpl( {
|
|
tmpl : 'font_list',
|
|
module_type: 'font_picker_input',
|
|
module_id : input.module.id
|
|
} );
|
|
api.sek_fetchingFontCollection = _ajaxRequest_;
|
|
}
|
|
_ajaxRequest_.done( function( _serverTmpl_ ) {
|
|
// Ensure we have a string that's JSON.parse-able
|
|
if ( typeof _serverTmpl_ !== 'string' || _serverTmpl_[0] !== '{' ) {
|
|
throw new Error( 'font_picker => server list is not JSON.parse-able');
|
|
}
|
|
api.sek_fontCollections = JSON.parse( _serverTmpl_ );
|
|
dfd.resolve( api.sek_fontCollections );
|
|
}).fail( function( _r_ ) {
|
|
dfd.reject( _r_ );
|
|
});
|
|
|
|
}
|
|
return dfd.promise();
|
|
};
|
|
var _preprocessSelect2ForFontFamily = function() {
|
|
/*
|
|
* Override czrSelect2 Results Adapter in order to select on highlight
|
|
* deferred needed cause the selects needs to be instantiated when this override is complete
|
|
* selec2.amd.require is asynchronous
|
|
*/
|
|
var selectFocusResults = $.Deferred();
|
|
if ( 'undefined' !== typeof $.fn.czrSelect2 && 'undefined' !== typeof $.fn.czrSelect2.amd && 'function' === typeof $.fn.czrSelect2.amd.require ) {
|
|
$.fn.czrSelect2.amd.require(['czrSelect2/results', 'czrSelect2/utils'], function (Result, Utils) {
|
|
var ResultsAdapter = function($element, options, dataAdapter) {
|
|
ResultsAdapter.__super__.constructor.call(this, $element, options, dataAdapter);
|
|
};
|
|
Utils.Extend(ResultsAdapter, Result);
|
|
ResultsAdapter.prototype.bind = function (container, $container) {
|
|
var _self = this;
|
|
container.on('results:focus', function (params) {
|
|
if ( params.element.attr('aria-selected') != 'true') {
|
|
_self.trigger('select', {
|
|
data: params.data
|
|
});
|
|
}
|
|
});
|
|
ResultsAdapter.__super__.bind.call(this, container, $container);
|
|
};
|
|
selectFocusResults.resolve( ResultsAdapter );
|
|
});
|
|
}
|
|
else {
|
|
selectFocusResults.resolve( false );
|
|
}
|
|
|
|
return selectFocusResults.promise();
|
|
|
|
};//_preprocessSelect2ForFontFamily
|
|
|
|
// @return void();
|
|
// Instantiates a czrSelect2 select input
|
|
// http://ivaynberg.github.io/czrSelect2/#documentation
|
|
var _setupSelectForFontFamilySelector = function( customResultsAdapter, fontCollections ) {
|
|
var _model = item(),
|
|
_googleFontsFilteredBySubset = function() {
|
|
var subset = item.czr_Input('subset')(),
|
|
filtered = _.filter( fontCollections.gfonts, function( data ) {
|
|
return data.subsets && _.contains( data.subsets, subset );
|
|
});
|
|
|
|
if ( ! _.isUndefined( subset ) && ! _.isNull( subset ) && 'all-subsets' != subset ) {
|
|
return filtered;
|
|
} else {
|
|
return fontCollections.gfonts;
|
|
}
|
|
|
|
};
|
|
|
|
// generates the options
|
|
// @param type = cfont or gfont
|
|
var _generateFontOptions = function( fontList, type ) {
|
|
var _html_ = '';
|
|
_.each( fontList , function( font_data ) {
|
|
var _value = _.isString( font_data.name ) ? font_data.name : 'Undefined Font Family',
|
|
optionTitle = _value.replace(/[+|:]/g, ' ' ),
|
|
_maybeSetFontTypePrefix = function( val, type ) {
|
|
if ( _.isEmpty( type ) )
|
|
return val;
|
|
return _.isString( val ) ? [ '[', type, ']', val ].join('') : '';//<= Example : [gfont]Aclonica:regular
|
|
};
|
|
|
|
_value = _maybeSetFontTypePrefix( _value, type );
|
|
optionTitle = optionTitle.replace('[cfont]', '').replace('[gfont]', '');
|
|
if ( _value == input() ) {
|
|
_html_ += '<option selected="selected" value="' + _value + '">' + optionTitle + '</option>';
|
|
} else {
|
|
_html_ += '<option value="' + _value + '">' + optionTitle + '</option>';
|
|
}
|
|
});
|
|
return _html_;
|
|
};
|
|
|
|
//add the first option
|
|
if ( _.isNull( input() ) || _.isEmpty( input() ) ) {
|
|
$fontSelectElement.append( '<option value="none" selected="selected">' + sektionsLocalizedData.i18n['Select a font family'] + '</option>' );
|
|
} else {
|
|
$fontSelectElement.append( '<option value="none">' + sektionsLocalizedData.i18n['Select a font family'] + '</option>' );
|
|
}
|
|
|
|
// declare the font list collection : most used, cfont, gfont
|
|
var _fontCollection = [
|
|
{
|
|
title : sektionsLocalizedData.i18n['Web safe fonts'],
|
|
type : 'cfont',
|
|
list : fontCollections.cfonts
|
|
},
|
|
{
|
|
title : sektionsLocalizedData.i18n['Google fonts'],
|
|
type : 'gfont',
|
|
list : fontCollections.gfonts//_googleFontsFilteredBySubset()
|
|
}
|
|
];
|
|
|
|
// Server fonts are stored as an array of gfonts with duplicates no removed
|
|
// 0: "[gfont]Raleway:800"
|
|
// 1: "[gfont]Roboto:regular"
|
|
// 2: "[gfont]Montserrat:regular"
|
|
// 3: "[gfont]Exo+2:800italic"
|
|
// 4: "[gfont]Raleway:800"
|
|
// 5: "[gfont]Roboto:regular"
|
|
|
|
|
|
//
|
|
// SERVER FONTS is a merge of the uniq gfont array of all skopes. Because each skopes font are stored in the .fonts property of a section setting, each time a new font is used in the customizer.
|
|
// The resulting SERVER FONTS array can have duplicatesd google fonts, if two skopes use the same font for example.
|
|
//
|
|
// How do we increase the weight of locally used gfont for the currently customized skope ?
|
|
// => AllFontsInApi is a raw list of all fonts, web safe and google fonts, with duplicates not removed
|
|
// those fonts are the one of the current skope + global sections fonts + global options fonts
|
|
// Server and api fonts are merged
|
|
// Since duplicates are not removed from api fonts, a frequently used local font can be quickly positionned on top of the list.
|
|
var allFontsInApi = api.czr_sektions.sniffAllFonts();
|
|
var allServerSentFonts = sektionsLocalizedData.alreadyUsedFonts;
|
|
|
|
var _alreadyUsedFonts = [],
|
|
_allFonts = [];
|
|
|
|
if ( ! _.isEmpty( allServerSentFonts ) && _.isObject( allServerSentFonts ) ) {
|
|
_.each( allServerSentFonts, function( _font ){
|
|
_allFonts.push( _font );
|
|
});
|
|
}
|
|
|
|
if ( _.isArray( allFontsInApi ) ) {
|
|
_.each( allFontsInApi, function( _font ) {
|
|
_allFonts.push( _font );
|
|
});
|
|
}
|
|
|
|
if ( !_.isEmpty( _allFonts ) ) {
|
|
// order fonts by number of occurences
|
|
var _occurencesMap = {},
|
|
_fontsOrderedByOccurences = [];
|
|
// Creates the occurence map
|
|
_allFonts.forEach(function(i) { _occurencesMap[i] = (_occurencesMap[i]||0) + 1;});
|
|
|
|
// isolate only the occurence number in an array
|
|
var _occurences = _.sortBy(_occurencesMap, function(num){ return num; });
|
|
|
|
_.each( _occurences, function( nb ) {
|
|
_.each( _occurencesMap, function( nbOccurence, fontName ) {
|
|
if ( nb === nbOccurence && !_.contains( _fontsOrderedByOccurences, fontName ) ) {
|
|
// unshift because the occurencesMap is in ascending order, and we want the most used fonts at the beginning
|
|
_fontsOrderedByOccurences.unshift( fontName );
|
|
}
|
|
});
|
|
});
|
|
|
|
// normalizes the most used font collection, like other font collection [{name:'font1'}, {...}, ... ]
|
|
_.each( _fontsOrderedByOccurences, function( fontName ){
|
|
_alreadyUsedFonts.push({name : fontName });
|
|
});
|
|
_fontCollection.unshift( {
|
|
title : sektionsLocalizedData.i18n['Already used fonts'],
|
|
type : null,//already set for Most used fonts
|
|
list : _alreadyUsedFonts
|
|
});
|
|
}//if ( !_.isEmpty( _allFonts ) )
|
|
|
|
|
|
// generate the cfont and gfont html
|
|
_.each( _fontCollection, function( fontData ) {
|
|
var $optGroup = $('<optgroup>', { label : fontData.title , html : _generateFontOptions( fontData.list, fontData.type ) });
|
|
$fontSelectElement.append( $optGroup );
|
|
});
|
|
|
|
var _fonts_czrSelect2_params = {
|
|
//minimumResultsForSearch: -1, //no search box needed
|
|
//templateResult: paintFontOptionElement,
|
|
//templateSelection: paintFontOptionElement,
|
|
escapeMarkup: function(m) { return m; },
|
|
};
|
|
/*
|
|
* Maybe use custom adapter
|
|
*/
|
|
if ( customResultsAdapter ) {
|
|
$.extend( _fonts_czrSelect2_params, {
|
|
resultsAdapter: customResultsAdapter,
|
|
closeOnSelect: false,
|
|
} );
|
|
}
|
|
|
|
//http://ivaynberg.github.io/czrSelect2/#documentation
|
|
//FONTS
|
|
$fontSelectElement.czrSelect2( _fonts_czrSelect2_params );
|
|
$( '.czrSelect2-selection__rendered', input.container ).css( getInlineFontStyle( input() ) );
|
|
|
|
};//_setupSelectForFontFamilySelector
|
|
|
|
// @return {} used to set $.css()
|
|
// @param font {string}.
|
|
// Example : Aclonica:regular
|
|
// Example : Helvetica Neue, Helvetica, Arial, sans-serif
|
|
var getInlineFontStyle = function( _fontFamily_ ){
|
|
// the font is set to 'none' when "Select a font family" option is picked
|
|
if ( ! _.isString( _fontFamily_ ) || _.isEmpty( _fontFamily_ ) )
|
|
return {};
|
|
|
|
//always make sure we remove the prefix.
|
|
_fontFamily_ = _fontFamily_.replace('[gfont]', '').replace('[cfont]', '');
|
|
|
|
var module = this,
|
|
split = _fontFamily_.split(':'), font_family, font_weight, font_style;
|
|
|
|
font_family = getFontFamilyName( _fontFamily_ );
|
|
|
|
font_weight = split[1] ? split[1].replace( /[^0-9.]+/g , '') : 400; //removes all characters
|
|
font_weight = _.isNumber( font_weight ) ? font_weight : 400;
|
|
font_style = ( split[1] && -1 != split[1].indexOf('italic') ) ? 'italic' : '';
|
|
|
|
|
|
return {
|
|
'font-family' : 'none' == font_family ? 'inherit' : font_family.replace(/[+|:]/g, ' '),//removes special characters
|
|
'font-weight' : font_weight || 400,
|
|
'font-style' : font_style || 'normal'
|
|
};
|
|
};
|
|
|
|
// @return the font family name only from a pre Google formated
|
|
// Example : input is Inknut+Antiqua:regular
|
|
// Should return Inknut Antiqua
|
|
var getFontFamilyName = function( rawFontFamily ) {
|
|
if ( ! _.isString( rawFontFamily ) || _.isEmpty( rawFontFamily ) )
|
|
return rawFontFamily;
|
|
|
|
rawFontFamily = rawFontFamily.replace('[gfont]', '').replace('[cfont]', '');
|
|
var split = rawFontFamily.split(':');
|
|
return _.isString( split[0] ) ? split[0].replace(/[+|:]/g, ' ') : '';//replaces special characters ( + ) by space
|
|
};
|
|
|
|
// On load, simply print the current input value
|
|
// the full list of font ( several thousands !! ) will be rendered on click
|
|
// March 2021 => to avoid slowing down the UI, the font picker select options are cleaned in cleanRegisteredAndLargeSelectInput()
|
|
var inputVal = input();
|
|
$fontSelectElement.append( $('<option>', {
|
|
value : inputVal,
|
|
html: inputVal,
|
|
selected : "selected"
|
|
}));
|
|
|
|
// Generate options and open select2
|
|
input.container.on('click', function() {
|
|
if ( true === $fontSelectElement.data('selectOptionsSet') )
|
|
return;
|
|
|
|
$fontSelectElement.data('selectOptionsSet', true );
|
|
// reset previous default html
|
|
$fontSelectElement.html('');
|
|
|
|
$.when( _getFontCollections() ).done( function( fontCollections ) {
|
|
_preprocessSelect2ForFontFamily().done( function( customResultsAdapter ) {
|
|
_setupSelectForFontFamilySelector( customResultsAdapter, fontCollections );
|
|
if ( !_.isUndefined( input.container.find('select[data-czrtype]').data('czrSelect2') ) ) {
|
|
input.container.find('select[data-czrtype]').czrSelect2('open');
|
|
}
|
|
});
|
|
}).fail( function( _r_ ) {
|
|
api.errare( 'font_picker => fail response =>', _r_ );
|
|
});
|
|
});
|
|
}//font_picker()
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
// FONT AWESOME ICON PICKER
|
|
fa_icon_picker : function() {
|
|
var input = this,
|
|
_selected_found = false,
|
|
$selectElement = $( 'select[data-czrtype="' + input.id + '"]', input.container );
|
|
|
|
//generates the options
|
|
var _generateOptions = function( iconCollection ) {
|
|
_.each( iconCollection , function( iconClass ) {
|
|
var _attributes = {
|
|
value: iconClass,
|
|
//iconClass is in the form "fa(s|b|r) fa-{$name}" so the name starts at position 7
|
|
html: api.CZR_Helpers.capitalize( iconClass.substring( 7 ) )
|
|
};
|
|
|
|
if ( _attributes.value == input() ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
_selected_found = true;
|
|
}
|
|
$selectElement.append( $('<option>', _attributes) );
|
|
});
|
|
|
|
|
|
var addIcon = function ( state ) {
|
|
if (! state.id) { return state.text; }
|
|
|
|
//two spans here because we cannot wrap the text into the icon span as the solid FA5 font-weight is bold
|
|
var $state = $(
|
|
'<span class="' + state.element.value + '"></span><span class="social-name"> ' + state.text + '</span>'
|
|
);
|
|
return $state;
|
|
};
|
|
|
|
//blank option to allow placeholders
|
|
var $_placeholder;
|
|
if ( _selected_found ) {
|
|
$_placeholder = $('<option>');
|
|
} else {
|
|
$_placeholder = $('<option>', { selected: 'selected' } );
|
|
}
|
|
//Initialize czrSelect2
|
|
$selectElement
|
|
.prepend( $_placeholder )
|
|
.czrSelect2({
|
|
templateResult: addIcon,
|
|
templateSelection: addIcon,
|
|
placeholder: sektionsLocalizedData.i18n['Select an icon'],
|
|
allowClear: true
|
|
});
|
|
};//_generateOptions
|
|
|
|
|
|
var _getIconsCollections = function() {
|
|
return $.Deferred( function( _dfd_ ) {
|
|
if ( ! _.isEmpty( input.sek_faIconCollection ) ) {
|
|
_dfd_.resolve( input.sek_faIconCollection );
|
|
} else {
|
|
// This utility handles a cached version of the font_list once fetched the first time
|
|
// @see api.CZR_Helpers.czr_cachedTmpl
|
|
api.CZR_Helpers.getModuleTmpl( {
|
|
tmpl : 'icon_list',
|
|
module_type: 'fa_icon_picker_input',
|
|
module_id : input.module.id
|
|
} ).done( function( _serverTmpl_ ) {
|
|
// Ensure we have a string that's JSON.parse-able
|
|
if ( typeof _serverTmpl_ !== 'string' || _serverTmpl_[0] !== '[' ) {
|
|
throw new Error( 'fa_icon_picker => server list is not JSON.parse-able');
|
|
}
|
|
input.sek_faIconCollection = JSON.parse( _serverTmpl_ );
|
|
_dfd_.resolve( input.sek_faIconCollection );
|
|
}).fail( function( _r_ ) {
|
|
_dfd_.reject( _r_ );
|
|
});
|
|
}
|
|
//return dfd.promise();
|
|
});
|
|
};//_getIconsCollections
|
|
|
|
// do
|
|
var _do_ = function() {
|
|
if ( true === input.iconCollectionSet )
|
|
return;
|
|
$.when( _getIconsCollections() ).done( function( iconCollection ) {
|
|
_generateOptions( iconCollection );
|
|
if ( !_.isUndefined( input.container.find('select[data-czrtype]').data('czrSelect2') ) ) {
|
|
input.container.find('select[data-czrtype]').czrSelect2('open');
|
|
}
|
|
}).fail( function( _r_ ) {
|
|
api.errare( 'fa_icon_picker => fail response =>', _r_ );
|
|
});
|
|
input.iconCollectionSet = true;
|
|
};
|
|
|
|
// On load, simply print the current input value
|
|
// the huge full list of icons will be rendered on click
|
|
// March 2021 => to avoid slowing down the UI, the font picker select options are cleaned in cleanRegisteredAndLargeSelectInput()
|
|
var inputVal = input();
|
|
$selectElement.append( $('<option>', {
|
|
value : inputVal,
|
|
html: inputVal,
|
|
selected : "selected"
|
|
}));
|
|
|
|
// Generate options and open select2
|
|
input.container.on('click', _do_ );
|
|
|
|
// schedule the iconCollectionSet after a delay
|
|
//_.delay( function() { _do_( { open_on_init : false } );}, 1000 );
|
|
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
code_editor : function( input_options ) {
|
|
var input = this,
|
|
control = this.module.control,
|
|
item = input.input_parent(),
|
|
editorSettings = false,
|
|
$textarea = input.container.find( 'textarea' ),
|
|
$input_title = input.container.find( '.customize-control-title' ),
|
|
initial_content;
|
|
//editor_params = $textarea.data( 'editor-params' );
|
|
|
|
// // When using blocking notifications (type: error) the following block will append a checkbox to the
|
|
// // notification message block that once checked will allow to save and publish anyways
|
|
|
|
// // Note that rendering is debounced so the props will be used when rendering happens after add event.
|
|
// control.notifications.bind( 'add', function( notification ) {
|
|
// // Skip if control notification is not from setting csslint_error notification.
|
|
// if ( notification.code !== control.setting.id + ':' + input.id ) {
|
|
// return;
|
|
// }
|
|
|
|
// // Customize the template and behavior of csslint_error notifications.
|
|
// notification.templateId = 'customize-code-editor-lint-error-notification';
|
|
// notification.render = (function( render ) {
|
|
// return function() {
|
|
// var li = render.call( this );
|
|
// li.find( 'input[type=checkbox]' ).on( 'click', function() {
|
|
// control.setting.notifications.remove( input.id );
|
|
// } );
|
|
// return li;
|
|
// };
|
|
// })( notification.render );
|
|
// } );
|
|
var _getEditorParams = function() {
|
|
return $.Deferred( function( _dfd_ ) {
|
|
var code_type = _.isEmpty( $textarea.data('editor-code-type') ) ? 'text/html' : $textarea.data('editor-code-type');
|
|
if ( api.czr_sektions.code_editor_params && api.czr_sektions.code_editor_params[ code_type ] ) {
|
|
_dfd_.resolve( api.czr_sektions.code_editor_params[ code_type ] );
|
|
} else {
|
|
wp.ajax.post( 'sek_get_code_editor_params', {
|
|
nonce: api.settings.nonce.save,
|
|
code_type : code_type
|
|
}).done( function( code_editor_params ) {
|
|
if ( !_.isObject( code_editor_params ) ) {
|
|
api.errare( input.id + ' => error => invalid code editor params sent by server', code_editor_params );
|
|
}
|
|
api.czr_sektions.code_editor_params = {} || api.czr_sektions.code_editor_params;
|
|
api.czr_sektions.code_editor_params[ code_type ] = code_editor_params;
|
|
_dfd_.resolve( api.czr_sektions.code_editor_params[ code_type ] );
|
|
}).fail( function( _r_ ) {
|
|
_dfd_.reject( _r_ );
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
// do
|
|
var _fetchEditorParamsAndInstantiate = function( params ) {
|
|
if ( true === input.catCollectionSet )
|
|
return;
|
|
$.when( _getEditorParams() ).done( function( editorParams ) {
|
|
_generateOptionsAndInstantiateSelect2(editorParams);
|
|
if ( params && true === params.open_on_init ) {
|
|
// let's open select2 after a delay ( because there's no 'ready' event with select2 )
|
|
_.delay( function() {
|
|
try{ $selectEl.czrSelect2('open'); } catch(er) {}
|
|
}, 100 );
|
|
}
|
|
}).fail( function( _r_ ) {
|
|
api.errare( input.id + ' => fail response when _getEditorParams()', _r_ );
|
|
});
|
|
input.catCollectionSet = true;
|
|
};
|
|
|
|
|
|
input.isReady.done( function() {
|
|
var _doInstantiate = function( evt ) {
|
|
var input = this;
|
|
// Bail if we have an instance
|
|
if ( ! _.isEmpty( input.editor ) )
|
|
return;
|
|
// Bail if the control is not expanded yet
|
|
if ( _.isEmpty( input.module.control.container.attr('data-sek-expanded') ) || "false" == input.module.control.container.attr('data-sek-expanded') )
|
|
return;
|
|
|
|
setTimeout( function() {
|
|
if ( editorSettings ) {
|
|
try { initSyntaxHighlightingEditor( editorSettings ); } catch( er ) {
|
|
api.errare( 'error in sek_control => code_editor() input', er );
|
|
initPlainTextareaEditor();
|
|
}
|
|
} else {
|
|
initPlainTextareaEditor();
|
|
}
|
|
//focus the editor
|
|
$input_title.trigger('click');
|
|
}, 10 );
|
|
};
|
|
// Feb 2021 : modules using this input will now be saved as a json to fix emojis issues
|
|
// we've started to implement the json saved for the heading module, but all modules will progressively transition to this new format
|
|
// see fix for https://github.com/presscustomizr/nimble-builder/issues/544
|
|
// to ensure retrocompatibility with data previously not saved as json, we need to perform a json validity check
|
|
initial_content = input();
|
|
if ( api.czr_sektions.isJsonString(initial_content) ) {
|
|
initial_content = JSON.parse( initial_content );
|
|
}
|
|
|
|
// inject the content in the code editor now
|
|
// @fixes the problem of {{...}} syntax being parsed by _. templating system
|
|
$textarea.html( initial_content );
|
|
|
|
$.when( _getEditorParams() ).done( function( editorParams ) {
|
|
//$textarea.attr( 'data-editor-params', editorParams );
|
|
// Obtain editorSettings for instantiation.
|
|
if ( wp.codeEditor && ( _.isUndefined( editorParams ) || false !== editorParams ) ) {
|
|
// Obtain this input editor settings (we don't have defaults).
|
|
editorSettings = editorParams;
|
|
}
|
|
|
|
// Try to instantiate now
|
|
_doInstantiate.call(input);
|
|
|
|
// the input should be visible otherwise the code mirror initializes wrongly:
|
|
// e.g. bad ui (bad inline CSS maths), not visible content until click.
|
|
// When the code_editor input is rendered in an accordion control ( @see CZRSeksPrototype.scheduleModuleAccordion ), we need to defer the instantiation when the control has been expanded.
|
|
// fixes @see https://github.com/presscustomizr/nimble-builder/issues/176
|
|
input.module.control.container.first().one('sek-accordion-expanded', function() {
|
|
_doInstantiate.call( input );
|
|
});
|
|
}).fail( function(er) {
|
|
api.errare( input.id + ' => error when getting the editor params from server');
|
|
});
|
|
});
|
|
|
|
|
|
/**
|
|
* Initialize syntax-highlighting editor.
|
|
*/
|
|
var initSyntaxHighlightingEditor = function( codeEditorSettings ) {
|
|
var suspendEditorUpdate = false,
|
|
settings;
|
|
|
|
settings = _.extend( {}, codeEditorSettings, {
|
|
onTabNext: CZRSeksPrototype.selectNextTabbableOrFocusable( ':tabbable' ),
|
|
onTabPrevious: CZRSeksPrototype.selectPrevTabbableOrFocusable( ':tabbable' ),
|
|
onUpdateErrorNotice: onUpdateErrorNotice
|
|
});
|
|
|
|
input.editor = wp.codeEditor.initialize( $textarea, settings );
|
|
|
|
|
|
// Improve the editor accessibility.
|
|
$( input.editor.codemirror.display.lineDiv )
|
|
.attr({
|
|
role: 'textbox',
|
|
'aria-multiline': 'true',
|
|
'aria-label': $input_title.html(),
|
|
'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
|
|
});
|
|
|
|
// Focus the editor when clicking on its title.
|
|
$input_title.on( 'click', function( evt ) {
|
|
evt.stopPropagation();
|
|
input.editor.codemirror.focus();
|
|
});
|
|
|
|
|
|
/*
|
|
* When the CodeMirror instance changes, mirror to the textarea,
|
|
* where we have our "true" change event handler bound.
|
|
*/
|
|
input.editor.codemirror.on( 'change', function( codemirror ) {
|
|
suspendEditorUpdate = true;
|
|
$textarea.val( codemirror.getValue() ).trigger( 'change' );
|
|
suspendEditorUpdate = false;
|
|
});
|
|
|
|
// Feb 2021 : modules using this input will now be saved as a json to fix emojis issues
|
|
// we've started to implement the json saved for the heading module, but all modules will progressively transition to this new format
|
|
// see fix for https://github.com/presscustomizr/nimble-builder/issues/544
|
|
// to ensure retrocompatibility with data previously not saved as json, we need to perform a json validity check
|
|
initial_content = input();
|
|
if ( api.czr_sektions.isJsonString(initial_content) ) {
|
|
initial_content = JSON.parse( initial_content );
|
|
}
|
|
input.editor.codemirror.setValue( initial_content );
|
|
|
|
// Update CodeMirror when the setting is changed by another plugin.
|
|
/* TODO: check this */
|
|
input.bind( input.id + ':changed', function( value ) {
|
|
if ( ! suspendEditorUpdate ) {
|
|
input.editor.codemirror.setValue( value );
|
|
}
|
|
});
|
|
|
|
// Prevent collapsing section when hitting Esc to tab out of editor.
|
|
input.editor.codemirror.on( 'keydown', function onKeydown( codemirror, event ) {
|
|
var escKeyCode = 27;
|
|
if ( escKeyCode === event.keyCode ) {
|
|
event.stopPropagation();
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Initialize plain-textarea editor when syntax highlighting is disabled.
|
|
*/
|
|
var initPlainTextareaEditor = function() {
|
|
var textarea = $textarea[0];
|
|
input.editor = textarea;//assign the editor property
|
|
$textarea.on( 'blur', function onBlur() {
|
|
$textarea.data( 'next-tab-blurs', false );
|
|
} );
|
|
|
|
$textarea.on( 'keydown', function onKeydown( event ) {
|
|
var selectionStart, selectionEnd, value, tabKeyCode = 9, escKeyCode = 27;
|
|
|
|
if ( escKeyCode === event.keyCode ) {
|
|
if ( ! $textarea.data( 'next-tab-blurs' ) ) {
|
|
$textarea.data( 'next-tab-blurs', true );
|
|
event.stopPropagation(); // Prevent collapsing the section.
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Short-circuit if tab key is not being pressed or if a modifier key *is* being pressed.
|
|
if ( tabKeyCode !== event.keyCode || event.ctrlKey || event.altKey || event.shiftKey ) {
|
|
return;
|
|
}
|
|
|
|
// Prevent capturing Tab characters if Esc was pressed.
|
|
if ( $textarea.data( 'next-tab-blurs' ) ) {
|
|
return;
|
|
}
|
|
|
|
selectionStart = textarea.selectionStart;
|
|
selectionEnd = textarea.selectionEnd;
|
|
value = textarea.value;
|
|
|
|
if ( selectionStart >= 0 ) {
|
|
textarea.value = value.substring( 0, selectionStart ).concat( '\t', value.substring( selectionEnd ) );
|
|
$textarea.selectionStart = textarea.selectionEnd = selectionStart + 1;
|
|
}
|
|
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
});
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
* Update error notice.
|
|
*/
|
|
onUpdateErrorNotice = function( errorAnnotations ) {
|
|
var message;
|
|
|
|
control.setting.notifications.remove( input.id );
|
|
if ( 0 !== errorAnnotations.length ) {
|
|
if ( 1 === errorAnnotations.length ) {
|
|
message = sektionsLocalizedData.i18n.codeEditorSingular.replace( '%d', '1' ).replace( '%s', $input_title.html() );
|
|
} else {
|
|
message = sektionsLocalizedData.i18n.codeEditorPlural.replace( '%d', String( errorAnnotations.length ) ).replace( '%s', $input_title.html() );
|
|
}
|
|
control.setting.notifications.add( input.id, new api.Notification( input.id, {
|
|
message: message,
|
|
type: 'warning'
|
|
} ) );
|
|
}
|
|
}
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
range_simple : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-range-with-unit-picker-wrapper', input.container ),
|
|
$numberInput = $wrapper.find( 'input[type="number"]'),
|
|
$rangeInput = $wrapper.find( 'input[type="range"]');
|
|
|
|
// synchronizes range input and number input
|
|
// number is the master => sets the input() val
|
|
$rangeInput.on('input', function( evt ) {
|
|
$numberInput.val( $(this).val() ).trigger('input');
|
|
});
|
|
$numberInput.on('input', function( evt ) {
|
|
input( $(this).val() );
|
|
$rangeInput.val( $(this).val() );
|
|
});
|
|
// trigger a change on init to sync the range input
|
|
$rangeInput.val( $numberInput.val() || 0 );
|
|
},
|
|
|
|
number_simple : function( params ) {
|
|
var input = this,
|
|
$numberInput = input.container.find( 'input[type="number"]');
|
|
|
|
$numberInput.on('input', function( evt ) {
|
|
input( $(this).val() );
|
|
});
|
|
},
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
range_simple_device_switcher : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-range-with-unit-picker-wrapper', input.container ),
|
|
$numberInput = $wrapper.find( 'input[type="number"]'),
|
|
$rangeInput = $wrapper.find( 'input[type="range"]'),
|
|
// dev note : value.replace(/\D+/g, '') : ''; not working because remove "." which we might use for em for example
|
|
_extractNumericVal = function( _rawVal ) {
|
|
return ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) ? '16' : _rawVal.replace(/px|em|%/g,'');
|
|
},
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
// Recursive helper
|
|
// return the value set for the currently previewed device if exists
|
|
// OR
|
|
// return the inherited value from the first parent device for which the value is set
|
|
// OR
|
|
// falls back on the module default
|
|
var getCurrentDeviceActualOrInheritedValue = function( inputValues, currentDevice ) {
|
|
var deviceHierarchy = [ 'mobile' , 'tablet', 'desktop' ];
|
|
if ( _.has( inputValues, currentDevice ) ) {
|
|
return inputValues[ currentDevice ];
|
|
} else {
|
|
var deviceIndex = _.findIndex( deviceHierarchy, function( _d_ ) { return currentDevice === _d_; });
|
|
if ( ! _.isEmpty( currentDevice ) && deviceIndex < deviceHierarchy.length ) {
|
|
return getCurrentDeviceActualOrInheritedValue( inputValues, deviceHierarchy[ deviceIndex + 1 ] );
|
|
} else {
|
|
var clonedDefault = $.extend( true, { desktop : '' }, defaultVal );
|
|
return clonedDefault[ 'desktop' ];
|
|
}
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on previewed device changes
|
|
var syncWithPreviewedDevice = function( currentDevice ) {
|
|
// initialize the number input with the current input val
|
|
// for retro-compatibility, we must handle the case when the initial input val is a string instead of an array
|
|
// in this case, the string value is assigned to the desktop device.
|
|
var inputVal = input(), inputValues = {}, clonedDefault = $.extend( true, {}, defaultVal );
|
|
inputValues = clonedDefault;
|
|
if ( _.isObject( inputVal ) ) {
|
|
inputValues = $.extend( true, {}, inputVal );
|
|
} else if ( _.isString( inputVal ) && ! _.isEmpty( inputVal ) ) {
|
|
inputValues = { desktop : inputVal };
|
|
}
|
|
//inputValues = _.extend( inputValues, clonedDefault );
|
|
// do we have a val for the current device ?
|
|
var _rawVal = getCurrentDeviceActualOrInheritedValue( inputValues, currentDevice ),
|
|
_numberVal = _extractNumericVal( _rawVal );
|
|
|
|
// update the numeric val
|
|
$numberInput.val( _numberVal ).trigger('input', { previewed_device_switched : true });// We don't want to update the input()
|
|
};
|
|
|
|
// SETUP
|
|
// setup the device switcher
|
|
api.czr_sektions.maybeSetupDeviceSwitcherForInput.call( input );
|
|
|
|
// Append a reset button
|
|
// var resetButton = '<button type="button" class="button sek-reset-button sek-float-right">' + sektionsLocalizedData.i18n['Reset'] + '</button>';
|
|
// input.container.find('.customize-control-title').append( resetButton );
|
|
|
|
// SCHEDULE REACTIONS
|
|
// synchronizes range input and number input
|
|
// number is the master => sets the input() val
|
|
$rangeInput.on('input', function( evt ) {
|
|
$numberInput.val( $(this).val() ).trigger('input');
|
|
});
|
|
|
|
// Set the input val
|
|
$numberInput.on('input', function( evt, params ) {
|
|
var previewedDevice = api.previewedDevice() || 'desktop',
|
|
changedNumberInputVal = $(this).val(),
|
|
_newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ previewedDevice ] = $.extend( true, {}, _newInputVal[ previewedDevice ] || {} );
|
|
|
|
// Validates
|
|
if ( ( _.isString( changedNumberInputVal ) && ! _.isEmpty( changedNumberInputVal ) ) ) {
|
|
_newInputVal[ previewedDevice ]= changedNumberInputVal;
|
|
}
|
|
|
|
// update input if not previewed_device_switched
|
|
if ( _.isEmpty( params ) || ( _.isObject( params ) && true !== params.previewed_device_switched ) ) {
|
|
input( _newInputVal );
|
|
}
|
|
$rangeInput.val( $(this).val() );
|
|
});
|
|
|
|
// react to previewed device changes
|
|
// input.previewedDevice is updated in api.czr_sektions.maybeSetupDeviceSwitcherForInput()
|
|
input.previewedDevice.bind( function( currentDevice ) {
|
|
try { syncWithPreviewedDevice( currentDevice ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type ' + input.type + ' for input id ' + input.id , er );
|
|
}
|
|
});
|
|
|
|
// // Schedule the reset of the value for the currently previewed device
|
|
// input.container.on( 'click', '.sek-reset-button', function( evt ) {
|
|
// var _currentDevice = api.previewedDevice(),
|
|
// _newVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
// if ( !_.isEmpty( _newVal[ _currentDevice ] ) ) {
|
|
// _newVal = _.omit( _newVal, _currentDevice );
|
|
// input( _newVal );
|
|
// syncWithPreviewedDevice( api.previewedDevice() );
|
|
// }
|
|
// });
|
|
|
|
// trigger a change on init to sync the range input
|
|
$rangeInput.val( $numberInput.val() || 0 );
|
|
try { syncWithPreviewedDevice( api.previewedDevice() ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type ' + input.type + ' for input id ' + input.id , er );
|
|
}
|
|
},
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
range_with_unit_picker : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-range-with-unit-picker-wrapper', input.container ),
|
|
$numberInput = $wrapper.find( 'input[type="number"]'),
|
|
$rangeInput = $wrapper.find( 'input[type="range"]'),
|
|
initial_unit = $wrapper.find('input[data-czrtype]').data('sek-unit'),
|
|
validateUnit = function( unit ) {
|
|
if ( ! _.contains( ['px', 'em', '%'], unit ) ) {
|
|
api.errare( 'error : invalid unit for input ' + input.id, unit );
|
|
unit = 'px';
|
|
}
|
|
return unit;
|
|
};
|
|
// initialize the unit with the value provided in the dom
|
|
input.css_unit = new api.Value( _.isEmpty( initial_unit ) ? 'px' : validateUnit( initial_unit ) );
|
|
// React to a unit change => trigger a number input change
|
|
input.css_unit.bind( function( to ) {
|
|
to = _.isEmpty( to ) ? 'px' : to;
|
|
$wrapper.find( 'input[type="number"]').trigger('input');
|
|
});
|
|
|
|
// synchronizes range input and number input
|
|
// number is the master => sets the input() val
|
|
$rangeInput.on('input', function( evt ) {
|
|
$numberInput.val( $(this).val() ).trigger('input');
|
|
});
|
|
$numberInput.on('input', function( evt ) {
|
|
input( $(this).val() + validateUnit( input.css_unit() ) );
|
|
$rangeInput.val( $(this).val() );
|
|
});
|
|
// trigger a change on init to sync the range input
|
|
$rangeInput.val( $numberInput.val() || 0 );
|
|
|
|
// Schedule unit changes on button click
|
|
$wrapper.on( 'click', '.sek-ui-button', function(evt) {
|
|
evt.preventDefault();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('.sek-ui-button').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
// update the initial unit ( not mandatory)
|
|
$wrapper.find('input[data-czrtype]').data('sek-unit', $(this).data('sek-unit') );
|
|
// set the current unit Value
|
|
input.css_unit( $(this).data('sek-unit') );
|
|
});
|
|
|
|
// add is-selected button on init to the relevant unit button
|
|
$wrapper.find( '.sek-ui-button[data-sek-unit="'+ initial_unit +'"]').addClass('is-selected').attr( 'aria-pressed', true );
|
|
},
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
range_with_unit_picker_device_switcher : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-range-with-unit-picker-wrapper', input.container ),
|
|
$numberInput = $wrapper.find( 'input[type="number"]'),
|
|
$rangeInput = $wrapper.find( 'input[type="range"]'),
|
|
validateUnit = function( unit ) {
|
|
if ( ! _.contains( ['px', 'em', '%'], unit ) ) {
|
|
api.errare( 'range_with_unit_picker_device_switcher => error : invalid unit for input ' + input.id, unit );
|
|
unit = 'px';
|
|
}
|
|
return unit;
|
|
},
|
|
// dev note : value.replace(/\D+/g, '') : ''; not working because remove "." which we might use for em for example
|
|
_extractNumericVal = function( _rawVal ) {
|
|
return ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) ? '16' : _rawVal.replace(/px|em|%/g,'');
|
|
},
|
|
_extractUnit = function( _rawVal ) {
|
|
return ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) ? 'px' : _rawVal.replace(/[0-9]|\.|,/g, '');
|
|
},
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
var getInitialUnit = function() {
|
|
return $wrapper.find('input[data-czrtype]').data('sek-unit') || 'px';
|
|
};
|
|
|
|
// Recursive helper
|
|
// return the value set for the currently previewed device if exists
|
|
// OR
|
|
// return the inherited value from the first parent device for which the value is set
|
|
// OR
|
|
// falls back on the module default
|
|
var getCurrentDeviceActualOrInheritedValue = function( inputValues, currentDevice ) {
|
|
var deviceHierarchy = [ 'mobile' , 'tablet', 'desktop' ];
|
|
if ( _.has( inputValues, currentDevice ) ) {
|
|
return inputValues[ currentDevice ];
|
|
} else {
|
|
var deviceIndex = _.findIndex( deviceHierarchy, function( _d_ ) { return currentDevice === _d_; });
|
|
if ( ! _.isEmpty( currentDevice ) && deviceIndex < deviceHierarchy.length ) {
|
|
return getCurrentDeviceActualOrInheritedValue( inputValues, deviceHierarchy[ deviceIndex + 1 ] );
|
|
} else {
|
|
var clonedDefault = $.extend( true, { desktop : '' }, defaultVal );
|
|
return clonedDefault[ 'desktop' ];
|
|
}
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on previewed device changes
|
|
var syncWithPreviewedDevice = function( currentDevice ) {
|
|
// initialize the number input with the current input val
|
|
// for retro-compatibility, we must handle the case when the initial input val is a string instead of an array
|
|
// in this case, the string value is assigned to the desktop device.
|
|
var inputVal = input(), inputValues = {}, clonedDefault = $.extend( true, {}, defaultVal );
|
|
inputValues = clonedDefault;
|
|
if ( _.isObject( inputVal ) ) {
|
|
inputValues = $.extend( true, {}, inputVal );
|
|
} else if ( _.isString( inputVal ) && ! _.isEmpty( inputVal ) ) {
|
|
inputValues = { desktop : inputVal };
|
|
}
|
|
//inputValues = _.extend( inputValues, clonedDefault );
|
|
// do we have a val for the current device ?
|
|
var _rawVal = getCurrentDeviceActualOrInheritedValue( inputValues, currentDevice ),
|
|
_unit = _extractUnit( _rawVal ),
|
|
_numberVal = _extractNumericVal( _rawVal );
|
|
|
|
// update the unit
|
|
$('.sek-unit-wrapper', $wrapper).find('[data-sek-unit="' + _unit +'"]').trigger('click', { previewed_device_switched : true });// We don't want to update the input()
|
|
// add is-selected button on init to the relevant unit button
|
|
$wrapper.find( '.sek-ui-button[data-sek-unit="'+ _unit +'"]').addClass('is-selected').attr( 'aria-pressed', true );
|
|
|
|
// update the numeric val
|
|
$numberInput.val( _numberVal ).trigger('input', { previewed_device_switched : true });// We don't want to update the input()
|
|
};
|
|
|
|
|
|
|
|
// SETUP
|
|
// setup the device switcher
|
|
api.czr_sektions.maybeSetupDeviceSwitcherForInput.call( input );
|
|
|
|
// initialize the unit with the value provided in the dom
|
|
input.css_unit = new api.Value( _.isEmpty( getInitialUnit() ) ? 'px' : validateUnit( getInitialUnit() ) );
|
|
|
|
// Append a reset button
|
|
var resetButton = '<button type="button" class="button sek-reset-button sek-float-right">' + sektionsLocalizedData.i18n['Reset'] + '</button>';
|
|
input.container.find('.customize-control-title').append( resetButton );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SCHEDULE REACTIONS
|
|
// React to a unit change => trigger a number input change
|
|
// Don't move when switching the device
|
|
// @param params can be { previewed_device_switched : true }
|
|
input.css_unit.bind( function( to, from, params ) {
|
|
if ( _.isObject( params ) && true === params.previewed_device_switched )
|
|
return;
|
|
$numberInput.trigger('input');
|
|
});
|
|
|
|
// synchronizes range input and number input
|
|
// number is the master => sets the input() val
|
|
$rangeInput.on('input', function( evt ) {
|
|
$numberInput.val( $(this).val() ).trigger('input');
|
|
});
|
|
// Set the input val
|
|
$numberInput.on('input', function( evt, params ) {
|
|
var previewedDevice = api.previewedDevice() || 'desktop',
|
|
changedNumberInputVal = $(this).val() + validateUnit( input.css_unit() ),
|
|
_newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ previewedDevice ] = $.extend( true, {}, _newInputVal[ previewedDevice ] || {} );
|
|
|
|
// Validates
|
|
if ( ( _.isString( changedNumberInputVal ) && ! _.isEmpty( changedNumberInputVal ) ) ) {
|
|
_newInputVal[ previewedDevice ]= changedNumberInputVal;
|
|
}
|
|
|
|
// update input if not previewed_device_switched
|
|
if ( _.isEmpty( params ) || ( _.isObject( params ) && true !== params.previewed_device_switched ) ) {
|
|
input( _newInputVal );
|
|
}
|
|
$rangeInput.val( $(this).val() );
|
|
});
|
|
|
|
// Schedule unit changes on button click
|
|
$wrapper.on( 'click', '.sek-ui-button', function( evt, params ) {
|
|
//evt.preventDefault();
|
|
evt.stopPropagation();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('.sek-ui-button').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
// update the initial unit ( not mandatory)
|
|
$wrapper.find('input[data-czrtype]').data('sek-unit', $(this).data('sek-unit') );
|
|
// set the current unit Value
|
|
input.css_unit( $(this).data('sek-unit'), params );
|
|
});
|
|
|
|
// react to previewed device changes
|
|
// input.previewedDevice is updated in api.czr_sektions.maybeSetupDeviceSwitcherForInput()
|
|
input.previewedDevice.bind( function( currentDevice ) {
|
|
try { syncWithPreviewedDevice( currentDevice ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type range_with_unit_picker_device_switcher for input id ' + input.id , er );
|
|
}
|
|
});
|
|
|
|
// Schedule the reset of the value for the currently previewed device
|
|
input.container.on( 'click', '.sek-reset-button', function( evt ) {
|
|
var _currentDevice = api.previewedDevice(),
|
|
_newVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
if ( !_.isEmpty( _newVal[ _currentDevice ] ) ) {
|
|
_newVal = _.omit( _newVal, _currentDevice );
|
|
input( _newVal );
|
|
syncWithPreviewedDevice( api.previewedDevice() );
|
|
}
|
|
});
|
|
|
|
|
|
// INITIALIZES
|
|
// trigger a change on init to sync the range input
|
|
$rangeInput.val( $numberInput.val() || 0 );
|
|
try { syncWithPreviewedDevice( api.previewedDevice() ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type range_with_unit_picker_device_switcher for input id ' + input.id , er );
|
|
}
|
|
},
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
borders : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-borders', input.container ),
|
|
$numberInput = $wrapper.find( 'input[type="number"]'),
|
|
$rangeInput = $wrapper.find( 'input[type="range"]'),
|
|
$colorInput = $wrapper.find('.sek-alpha-color-input'),
|
|
validateUnit = function( unit ) {
|
|
if ( ! _.contains( ['px', 'em', '%'], unit ) ) {
|
|
api.errare( 'borders => error : invalid unit for input ' + input.id, unit );
|
|
unit = 'px';
|
|
}
|
|
return unit;
|
|
},
|
|
// dev note : value.replace(/\D+/g, '') : ''; not working because remove "." which we might use for em for example
|
|
_extractNumericVal = function( _rawVal ) {
|
|
return ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) ? '16' : _rawVal.replace(/px|em|%/g,'');
|
|
},
|
|
_extractUnit = function( _rawVal ) {
|
|
return ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) ? 'px' : _rawVal.replace(/[0-9]|\.|,/g, '');
|
|
},
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
input.cssBorderTypes = [ 'top', 'left', 'right', 'bottom' ];
|
|
|
|
// Return the unit of the _all_ border type
|
|
var getInitialUnit = function() {
|
|
var inputVal = input(), initial_unit = 'px';
|
|
if ( _.isObject( inputVal ) && _.has( inputVal, '_all_' ) && _.isObject( inputVal['_all_'] ) && ! _.isEmpty( inputVal['_all_'][ 'wght'] ) ) {
|
|
initial_unit = validateUnit( _extractUnit( inputVal['_all_'][ 'wght'] ) );
|
|
}
|
|
return initial_unit;
|
|
};
|
|
// Return the number value of the _all_ border type
|
|
var getInitialWeight = function() {
|
|
var inputVal = input(), initial_weight = 1;
|
|
if ( _.isObject( inputVal ) && _.has( inputVal, '_all_' ) && _.isObject( inputVal['_all_'] ) && ! _.isEmpty( inputVal['_all_'][ 'wght'] ) ) {
|
|
initial_weight = _extractNumericVal( inputVal['_all_'][ 'wght'] );
|
|
}
|
|
initial_weight = parseInt(initial_weight, 10);
|
|
if ( ! _.isNumber( initial_weight ) || initial_weight < 0 ) {
|
|
api.errare( 'Error in borders input type for module : ' + input.module.module_type + ' the initial border width is invalid : ' + initial_weight );
|
|
initial_weight = 1;
|
|
}
|
|
return initial_weight;
|
|
};
|
|
// Return the color of the _all_ border type
|
|
var getInitialColor = function() {
|
|
var inputVal = input(), initial_color = '#000000';
|
|
if ( _.isObject( inputVal ) && _.has( inputVal, '_all_' ) && _.isObject( inputVal['_all_'] ) && ! _.isEmpty( inputVal['_all_'][ 'col'] ) ) {
|
|
initial_color = inputVal['_all_'][ 'col'];
|
|
}
|
|
return initial_color;
|
|
};
|
|
|
|
// Recursive helper
|
|
// _all_ : { wght : 1px, col : #000000 }
|
|
// falls back on {}
|
|
var getCurrentBorderTypeOrAllValue = function( inputValues, borderType ) {
|
|
var clonedDefaults = $.extend( true, {}, defaultVal ), _all_Value;
|
|
if ( ! _.has( clonedDefaults, '_all_' ) ) {
|
|
throw new Error( "Error when firing getCurrentBorderTypeOrAllValue : the default value of the borders input must be php registered as an array formed : array( 'wght' => '1px', 'col' => '#000000' )");
|
|
}
|
|
|
|
_all_Value = ( _.isObject( inputValues ) && _.has( inputValues, '_all_' ) ) ? _.extend( clonedDefaults['_all_'], inputValues[ '_all_' ] ) : clonedDefaults['_all_'];
|
|
if ( _.has( inputValues, borderType ) && _.isObject( inputValues[ borderType ] ) ) {
|
|
return _.extend( _all_Value, inputValues[ borderType ] );
|
|
} else {
|
|
return clonedDefaults['_all_'];
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on border type change
|
|
var syncWithBorderType = function( borderType ) {
|
|
if ( ! _.contains( _.union( input.cssBorderTypes, [ '_all_' ] ) , borderType ) ) {
|
|
throw new Error( "Error in syncWithBorderType : the border type must be one of those values '_all_', 'top', 'left', 'right', 'bottom'" );
|
|
}
|
|
|
|
// initialize the number input with the current input val
|
|
// for retro-compatibility, we must handle the case when the initial input val is a string instead of an array
|
|
// in this case, the string value is assigned to the desktop device.
|
|
var inputVal = input(), inputValues = {}, clonedDefault = $.extend( true, {}, defaultVal );
|
|
if ( _.isObject( inputVal ) ) {
|
|
inputValues = $.extend( true, {}, inputVal );
|
|
} else if ( _.isString( inputVal ) ) {
|
|
inputValues = { _all_ : { wght : inputVal } };
|
|
}
|
|
inputValues = $.extend( clonedDefault, inputValues );
|
|
|
|
// do we have a val for the current border type ?
|
|
var _rawVal = getCurrentBorderTypeOrAllValue( inputValues, borderType ), _unit, _numberVal;
|
|
if ( _.isEmpty( _rawVal ) || ! _.isObject( _rawVal ) || _.isEmpty( _rawVal.wght ) || _.isEmpty( _rawVal.col ) ) {
|
|
throw new Error( "Error in syncWithBorderType : getCurrentBorderTypeOrAllValue must return an object formed : array( 'wght' => '1px', 'col' => '#000000' )");
|
|
}
|
|
|
|
_unit = _extractUnit( _rawVal.wght );
|
|
_numberVal = _extractNumericVal( _rawVal.wght );
|
|
|
|
// update the unit
|
|
$('.sek-unit-wrapper', $wrapper).find('[data-sek-unit="' + _unit +'"]').trigger('click', { border_type_switched : true });// We don't want to update the input()
|
|
// add is-selected button on init to the relevant unit button
|
|
$wrapper.find( '.sek-ui-button[data-sek-unit="'+ _unit +'"]').addClass('is-selected').attr( 'aria-pressed', true );
|
|
// update the numeric val
|
|
$numberInput.val( _numberVal ).trigger('input', { border_type_switched : true });// We don't want to update the input()
|
|
// update the color
|
|
// trigger the change between "border_type_switched" data flags, so we know the api setting don't have to be refreshed
|
|
// ( there's no easy other way to pass a param when triggering )
|
|
$colorInput.data('border_type_switched', true );
|
|
$colorInput.val( _rawVal.col ).trigger( 'change' );
|
|
$colorInput.data('border_type_switched', false );
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// SETUP
|
|
input.borderColor = new api.Value( _.isEmpty( getInitialColor() ) ? '#000000' : getInitialColor() );
|
|
// initialize the unit
|
|
input.css_unit = new api.Value( _.isEmpty( getInitialUnit() ) ? 'px' : validateUnit( getInitialUnit() ) );
|
|
// setup the border type switcher. Initialized with all.
|
|
input.borderType = new api.Value( '_all_');
|
|
// Setup the initial state of the number input
|
|
$numberInput.val( getInitialWeight() );
|
|
// Setup the color input
|
|
$colorInput.val( input.borderColor() );
|
|
$colorInput.wpColorPicker({
|
|
palettes: true,
|
|
//hide:false,
|
|
width: window.innerWidth >= 1440 ? 271 : 251,
|
|
change : function( evt, o ) {
|
|
//if the input val is not updated here, it's not detected right away.
|
|
//weird
|
|
//is there a "change complete" kind of event for iris ?
|
|
//$(this).val($(this).wpColorPicker('color'));
|
|
//input.container.find('[data-czrtype]').trigger('colorpickerchange');
|
|
|
|
//synchronizes with the original input
|
|
//OLD => $(this).val( $(this).wpColorPicker('color') ).trigger('colorpickerchange').trigger('change');
|
|
$(this).val( o.color.toString() ).trigger('colorpickerchange');
|
|
input.borderColor( o.color.toString(), { border_type_switched : true === $(this).data('border_type_switched') } );
|
|
//input.borderColor( o.color.toString() );
|
|
// if ( evt.originalEvent && evt.originalEvent.type && 'external' === evt.originalEvent.type ) {
|
|
// input.borderColor( o.color.toString(), { border_type_switched : true } );
|
|
// } else {
|
|
// input.borderColor( o.color.toString() );
|
|
// }
|
|
},
|
|
clear : function( e, o ) {
|
|
$(this).val('').trigger('colorpickerchange');
|
|
input.borderColor('');
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SCHEDULE REACTIONS
|
|
// React to a unit change => trigger a number input change
|
|
// Don't move when switching the border type or initializing unit
|
|
// @param params can be { border_type_switched : true }
|
|
input.css_unit.bind( function( to, from, params ) {
|
|
// don't update the main input when switching border types or initializing the unit value
|
|
if ( _.isObject( params ) && ( true === params.border_type_switched || true === params.initializing_the_unit ) )
|
|
return;
|
|
$numberInput.trigger('input', params);
|
|
});
|
|
|
|
// React to a color change => trigger a number input change
|
|
// Don't move when switching the border type or initializing the color
|
|
// @param params can be { border_type_switched : true }
|
|
input.borderColor.bind( function( to, from, params ) {
|
|
// don't update the main input when switching border types or initializing the unit value
|
|
if ( _.isObject( params ) && ( true === params.border_type_switched || true === params.initializing_the_color ) )
|
|
return;
|
|
$numberInput.trigger('input', params);
|
|
});
|
|
|
|
// react to border type changes
|
|
input.borderType.bind( function( borderType ) {
|
|
try { syncWithBorderType( borderType ); } catch( er ) {
|
|
api.errare('Error when firing syncWithBorderType for input type borders for module type ' + input.module.module_type , er );
|
|
}
|
|
});
|
|
|
|
// synchronizes range input and number input
|
|
// number is the master => sets the input() val
|
|
$rangeInput.on('input', function( evt ) {
|
|
$numberInput.val( $(this).val() ).trigger('input');
|
|
});
|
|
|
|
// Set the input val
|
|
$numberInput.on('input', function( evt, params ) {
|
|
var currentBorderType = input.borderType() || '_all_',
|
|
currentColor = input.borderColor(),
|
|
changedNumberInputVal = $(this).val() + validateUnit( input.css_unit() ),
|
|
clonedDefaults = $.extend( true, {}, defaultVal ),
|
|
_newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : clonedDefaults );
|
|
_newInputVal[ currentBorderType ] = $.extend( true, {}, _newInputVal[ currentBorderType ] || clonedDefaults[ currentBorderType ] );
|
|
|
|
// populate the border weight value
|
|
if ( ( _.isString( changedNumberInputVal ) && ! _.isEmpty( changedNumberInputVal ) ) ) {
|
|
_newInputVal[ currentBorderType ][ 'wght' ] = changedNumberInputVal;
|
|
}
|
|
// populate the color value
|
|
_newInputVal[ currentBorderType ][ 'col' ] = currentColor;
|
|
|
|
// update input if not border_type_switched
|
|
// if _all_ is changed, removed all other types
|
|
if ( _.isEmpty( params ) || ( _.isObject( params ) && true !== params.border_type_switched ) ) {
|
|
if ( '_all_' === currentBorderType ) {
|
|
_.each( input.cssBorderTypes, function( _type ) {
|
|
_newInputVal = _.omit( _newInputVal, _type );
|
|
});
|
|
}
|
|
input( _newInputVal );
|
|
}
|
|
// refresh the range slider
|
|
$rangeInput.val( $(this).val() );
|
|
});
|
|
|
|
|
|
// Schedule unit changes on button click
|
|
$wrapper.on( 'click', '[data-sek-unit]', function( evt, params ) {
|
|
evt.preventDefault();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('[data-sek-unit]').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
// update the initial unit ( not mandatory)
|
|
$wrapper.find('input[data-czrtype]').data('sek-unit', $(this).data('sek-unit') );
|
|
// set the current unit Value
|
|
input.css_unit( $(this).data('sek-unit'), params );
|
|
});
|
|
|
|
// Schedule border type changes on button click
|
|
$wrapper.on( 'click', '[data-sek-border-type]', function( evt, params ) {
|
|
evt.preventDefault();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('[data-sek-border-type]').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
var border = '_all_';
|
|
try { border = $(this).data('sek-border-type'); } catch( er ) {
|
|
api.errare( 'borders input type => error when attaching click event', er );
|
|
}
|
|
input.borderType( border, params );
|
|
});
|
|
|
|
// Schedule the reset of the value for the currently previewed device
|
|
input.container.on( 'click', '.sek-reset-button', function( evt ) {
|
|
var currentBorderType = input.borderType() || '_all_',
|
|
_newVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
if ( !_.isEmpty( _newVal[ currentBorderType ] ) ) {
|
|
_newVal = _.omit( _newVal, currentBorderType );
|
|
input( _newVal );
|
|
syncWithBorderType( currentBorderType );
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// INITIALIZES
|
|
// trigger a change on init to sync the range input
|
|
$rangeInput.val( $numberInput.val() || 0 );
|
|
try { syncWithBorderType( input.borderType() ); } catch( er ) {
|
|
api.errare('Error when firing syncWithBorderType for input type borders for module type ' + input.module.module_type , er );
|
|
}
|
|
// trigger a click on the initial unit
|
|
// => the initial unit could be set when fetching the server template but it's more convenient to handle it once the template is rendered
|
|
$( '[data-sek-unit="' + input.css_unit() + '"]', $wrapper ).trigger('click', { initializing_the_unit : true } );
|
|
},
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
border_radius : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-borders', input.container ),
|
|
$numberInput = $wrapper.find( 'input[type="number"]'),
|
|
$rangeInput = $wrapper.find( 'input[type="range"]'),
|
|
validateUnit = function( unit ) {
|
|
if ( ! _.contains( ['px', 'em', '%'], unit ) ) {
|
|
api.errare( 'border_radius => error : invalid unit for input ' + input.id, unit );
|
|
unit = 'px';
|
|
}
|
|
return unit;
|
|
},
|
|
// dev note : value.replace(/\D+/g, '') : ''; not working because remove "." which we might use for em for example
|
|
_extractNumericVal = function( _rawVal ) {
|
|
return ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) ? '16' : _rawVal.replace(/px|em|%/g,'');
|
|
},
|
|
_extractUnit = function( _rawVal ) {
|
|
return ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) ? 'px' : _rawVal.replace(/[0-9]|\.|,/g, '');
|
|
},
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
input.cssRadiusTypes = [ 'top_left','top_right','bottom_right','bottom_left' ];
|
|
|
|
// Return the unit of the _all_ border type
|
|
var getInitialUnit = function() {
|
|
var inputVal = input(), initial_unit = 'px';
|
|
if ( _.isObject( inputVal ) && _.has( inputVal, '_all_' ) ) {
|
|
initial_unit = validateUnit( _extractUnit( inputVal['_all_'] ) );
|
|
}
|
|
return initial_unit;
|
|
};
|
|
// Return the number value of the _all_ border type
|
|
var getInitialRadius = function() {
|
|
var inputVal = input(), initial_rad = 0;
|
|
if ( _.isObject( inputVal ) && _.has( inputVal, '_all_' ) ) {
|
|
initial_rad = _extractNumericVal( inputVal['_all_'] );
|
|
}
|
|
initial_rad = parseInt(initial_rad, 10);
|
|
if ( ! _.isNumber( initial_rad ) || initial_rad < 0 ) {
|
|
api.errare( 'Error in border_radius input type for module : ' + input.module.module_type + ' the initial radius is invalid : ' + initial_rad );
|
|
initial_rad = 0;
|
|
}
|
|
return initial_rad;
|
|
};
|
|
|
|
|
|
// Recursive helper
|
|
// _all_ : 3px
|
|
// falls back on {}
|
|
var getCurrentRadiusTypeOrAllValue = function( inputValues, radiusType ) {
|
|
var clonedDefaults = $.extend( true, {}, defaultVal ), _all_Value;
|
|
if ( ! _.has( clonedDefaults, '_all_' ) ) {
|
|
throw new Error( "Error when firing getCurrentRadiusTypeOrAllValue : the default value of the border_radius input must be php registered as an array");
|
|
}
|
|
|
|
_all_Value = ( _.isObject( inputValues ) && _.has( inputValues, '_all_' ) ) ? inputValues[ '_all_' ] : clonedDefaults['_all_'];
|
|
if ( _.has( inputValues, radiusType ) ) {
|
|
return inputValues[ radiusType ];
|
|
} else {
|
|
return _all_Value;
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on border radius change
|
|
var syncWithRadiusType = function( radiusType ) {
|
|
if ( ! _.contains( [ '_all_', 'top_left', 'top_right', 'bottom_right', 'bottom_left' ], radiusType ) ) {
|
|
throw new Error( "Error in syncWithRadiusType : the radius type must be one of those values '_all_', 'top_left', 'top_right', 'bottom_right', 'bottom_left', => radius type => " + radiusType );
|
|
}
|
|
|
|
// initialize the number input with the current input val
|
|
// for retro-compatibility, we must handle the case when the initial input val is a string instead of an array
|
|
// in this case, the string value is assigned to the desktop device.
|
|
var inputVal = input(), inputValues = {}, clonedDefault = $.extend( true, {}, defaultVal );
|
|
if ( _.isObject( inputVal ) ) {
|
|
inputValues = $.extend( true, {}, inputVal );
|
|
} else if ( _.isString( inputVal ) ) {
|
|
inputValues = { _all_ : '0px' };
|
|
}
|
|
inputValues = $.extend( clonedDefault, inputValues );
|
|
|
|
// do we have a val for the current type ?
|
|
var _rawVal = getCurrentRadiusTypeOrAllValue( inputValues, radiusType ), _unit, _numberVal;
|
|
if ( _.isEmpty( _rawVal ) || ! _.isString( _rawVal ) ) {
|
|
throw new Error( "Error in syncWithRadiusType : getCurrentRadiusTypeOrAllValue must return a string like 3em");
|
|
}
|
|
|
|
_unit = _extractUnit( _rawVal );
|
|
_numberVal = _extractNumericVal( _rawVal );
|
|
|
|
// update the unit
|
|
$('.sek-unit-wrapper', $wrapper).find('[data-sek-unit="' + _unit +'"]').trigger('click', { radius_type_switched : true });// We don't want to update the input()
|
|
// add is-selected button on init to the relevant unit button
|
|
$wrapper.find( '.sek-ui-button[data-sek-unit="'+ _unit +'"]').addClass('is-selected').attr( 'aria-pressed', true );
|
|
// update the numeric val
|
|
$numberInput.val( _numberVal ).trigger('input', { radius_type_switched : true });// We don't want to update the input()
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SETUP
|
|
// initialize the unit
|
|
input.css_unit = new api.Value( _.isEmpty( getInitialUnit() ) ? 'px' : validateUnit( getInitialUnit() ) );
|
|
// setup the border type switcher. Initialized with all.
|
|
input.radiusType = new api.Value('_all_');
|
|
// Setup the initial state of the number input
|
|
$numberInput.val( getInitialRadius() );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SCHEDULE REACTIONS
|
|
// React to a unit change => trigger a number input change
|
|
// Don't move when switching the border type or initializing unit
|
|
// @param params can be { radius_type_switched : true }
|
|
input.css_unit.bind( function( to, from, params ) {
|
|
// don't update the main input when switching border types or initializing the unit value
|
|
if ( _.isObject( params ) && ( true === params.radius_type_switched || true === params.initializing_the_unit ) )
|
|
return;
|
|
$numberInput.trigger('input', params);
|
|
});
|
|
|
|
// react to border type changes
|
|
input.radiusType.bind( function( radiusType ) {
|
|
try { syncWithRadiusType( radiusType ); } catch( er ) {
|
|
api.errare('Error when firing syncWithRadiusType for input type border_radius for module type ' + input.module.module_type , er );
|
|
}
|
|
});
|
|
|
|
// synchronizes range input and number input
|
|
// number is the master => sets the input() val
|
|
$rangeInput.on('input', function( evt ) {
|
|
$numberInput.val( $(this).val() ).trigger('input');
|
|
});
|
|
|
|
// Set the input val
|
|
$numberInput.on('input', function( evt, params ) {
|
|
var currentRadiusType = input.radiusType() || '_all_',
|
|
changedNumberInputVal = $(this).val() + validateUnit( input.css_unit() ),
|
|
clonedDefaults = $.extend( true, {}, defaultVal ),
|
|
_newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : clonedDefaults );
|
|
_newInputVal[ currentRadiusType ] = $.extend( true, {}, _newInputVal[ currentRadiusType ] || clonedDefaults[ currentRadiusType ] );
|
|
|
|
// populate the border weight value
|
|
if ( ( _.isString( changedNumberInputVal ) && ! _.isEmpty( changedNumberInputVal ) ) ) {
|
|
_newInputVal[ currentRadiusType ] = changedNumberInputVal;
|
|
}
|
|
// update input if not radius_type_switched
|
|
// if _all_ is changed, removed all other types
|
|
if ( _.isEmpty( params ) || ( _.isObject( params ) && true !== params.radius_type_switched ) ) {
|
|
if ( '_all_' === currentRadiusType ) {
|
|
_.each( input.cssRadiusTypes, function( _type ) {
|
|
_newInputVal = _.omit( _newInputVal, _type );
|
|
});
|
|
}
|
|
input( _newInputVal );
|
|
}
|
|
// refresh the range slider
|
|
$rangeInput.val( $(this).val() );
|
|
});
|
|
|
|
|
|
// Schedule unit changes on button click
|
|
$wrapper.on( 'click', '[data-sek-unit]', function( evt, params ) {
|
|
evt.preventDefault();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('[data-sek-unit]').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
// update the initial unit ( not mandatory)
|
|
$wrapper.find('input[data-czrtype]').data('sek-unit', $(this).data('sek-unit') );
|
|
// set the current unit Value
|
|
input.css_unit( $(this).data('sek-unit'), params );
|
|
});
|
|
|
|
// Schedule border type changes on button click
|
|
$wrapper.on( 'click', '[data-sek-radius-type]', function( evt, params ) {
|
|
evt.preventDefault();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('[data-sek-radius-type]').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
var border = '_all_';
|
|
try { border = $(this).data('sek-radius-type'); } catch( er ) {
|
|
api.errare( 'border_radius input type => error when attaching click event', er );
|
|
}
|
|
input.radiusType( border, params );
|
|
});
|
|
|
|
// Schedule the reset of the value for the currently previewed device
|
|
input.container.on( 'click', '.sek-reset-button', function( evt ) {
|
|
var currentRadiusType = input.radiusType() || '_all_',
|
|
_newVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
if ( !_.isEmpty( _newVal[ currentRadiusType ] ) ) {
|
|
_newVal = _.omit( _newVal, currentRadiusType );
|
|
input( _newVal );
|
|
syncWithRadiusType( currentRadiusType );
|
|
}
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// INITIALIZES
|
|
// trigger a change on init to sync the range input
|
|
$rangeInput.val( $numberInput.val() || 0 );
|
|
try { syncWithRadiusType( input.radiusType() ); } catch( er ) {
|
|
api.errare('Error when firing syncWithRadiusType for input type border_radius for module type ' + input.module.module_type , er );
|
|
}
|
|
// trigger a click on the initial unit
|
|
// => the initial unit could be set when fetching the server template but it's more convenient to handle it once the template is rendered
|
|
$( '[data-sek-unit="' + input.css_unit() + '"]', $wrapper ).trigger('click', { initializing_the_unit : true } );
|
|
},
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
buttons_choice : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-button-choice-wrapper', input.container ),
|
|
$mainInput = $wrapper.find( 'input[type="number"]'),
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
// SETUP
|
|
// Setup the initial state of the number input
|
|
$mainInput.val( input() );
|
|
|
|
// Schedule choice changes on button click
|
|
$wrapper.on( 'click', '[data-sek-choice]', function( evt, params ) {
|
|
evt.stopPropagation();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('[data-sek-choice]').removeClass('is-selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('is-selected').attr( 'aria-pressed', true );
|
|
var newChoice;
|
|
try { newChoice = $(this).data('sek-choice'); } catch( er ) {
|
|
api.errare( 'buttons_choice input type => error when attaching click event', er );
|
|
}
|
|
input( newChoice );
|
|
});
|
|
|
|
|
|
// INITIALIZES
|
|
// trigger a click on the initial unit
|
|
// => the initial unit could be set when fetching the server template but it's more convenient to handle it once the template is rendered
|
|
$( '[data-sek-choice="' + input() + '"]', $wrapper ).trigger('click', { initializing_the_unit : true } );
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
reset_button : function( params ) {
|
|
var input = this;
|
|
|
|
// Schedule choice changes on button click
|
|
input.container.on( 'click', '[data-sek-reset-scope]', function( evt, params ) {
|
|
evt.stopPropagation();
|
|
var scope = $(this).data( 'sek-reset-scope' );
|
|
|
|
if ( _.isEmpty( scope ) || !_.contains(['local', 'global'], scope ) ) {
|
|
api.errare( 'reset_button input => invalid scope provided.', scope );
|
|
return;
|
|
}
|
|
api.previewer.trigger('sek-reset-collection', { scope : scope } );
|
|
|
|
});//on('click')
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// the input id determine if we fetch the revision history of the local or global setting
|
|
$.extend( api.czrInputMap, {
|
|
revision_history : function( params ) {
|
|
var input = this;
|
|
_selected_found = false;
|
|
//generates the options
|
|
var _generateOptions = function( revisionHistory ) {
|
|
if ( input.container.find('.sek-revision-history').length > 0 )
|
|
return;
|
|
if ( _.isEmpty( revisionHistory ) ) {
|
|
input.container.append( [ '<i>', sektionsLocalizedData.i18n['No revision history available for the moment.'], '</i>' ].join('') );
|
|
return;
|
|
}
|
|
input.container.append( $('<select/>', {
|
|
class : 'sek-revision-history',
|
|
html : [ '<option value="_select_">', ' -', sektionsLocalizedData.i18n['Select'], '- ', '</option>'].join('')
|
|
}));
|
|
|
|
// The revisions are listed by ascending date
|
|
// => let's reverse the order so that we see the latest first
|
|
var optionsNodes = [];
|
|
_.each( revisionHistory , function( _date, _post_id ) {
|
|
var _attributes = {
|
|
value: _post_id,
|
|
html: _date
|
|
};
|
|
|
|
if ( _attributes.value == input() ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
_selected_found = true;
|
|
}
|
|
optionsNodes.unshift( $('<option>', _attributes) );
|
|
});
|
|
|
|
// Add the 'published' note to the first node
|
|
optionsNodes[0].html( [ optionsNodes[0].html(), sektionsLocalizedData.i18n['(currently published version)'] ].join(' ') );
|
|
_.each( optionsNodes, function( nod ) {
|
|
$( 'select.sek-revision-history', input.container ).append( nod );
|
|
});
|
|
|
|
// Initialize selecter
|
|
$( 'select.sek-revision-history', input.container ).selecter();
|
|
};//_generateOptions
|
|
|
|
|
|
var _getRevisionHistory = function() {
|
|
return $.Deferred( function( _dfd_ ) {
|
|
if ( ! _.isEmpty( input.sek_revisionHistory ) ) {
|
|
_dfd_.resolve( input.sek_revisionHistory );
|
|
} else {
|
|
// The revision history sent by the server is an object
|
|
// {
|
|
// post_id : date,
|
|
// post_id : date,
|
|
// ...
|
|
// }
|
|
api.czr_sektions.getRevisionHistory( { is_local : 'local_revisions' === input.id } ).done( function( revisionHistory ) {
|
|
// Ensure we have a string that's JSON.parse-able
|
|
if ( !_.isObject(revisionHistory) ) {
|
|
throw new Error( '_getRevisionHistory => server list is not a object');
|
|
}
|
|
input.sek_revisionHistory = revisionHistory;
|
|
_dfd_.resolve( input.sek_revisionHistory );
|
|
}).fail( function( _r_ ) {
|
|
_dfd_.reject( _r_ );
|
|
});
|
|
}
|
|
//return dfd.promise();
|
|
});
|
|
};//_getRevisionHistory
|
|
|
|
// do
|
|
var _do_ = function( params ) {
|
|
if ( true === input.revisionHistorySet )
|
|
return;
|
|
$.when( _getRevisionHistory() ).done( function( revisionHistory ) {
|
|
_generateOptions( revisionHistory );
|
|
if ( params && true === params.open_on_init ) {
|
|
// let's open select2 after a delay ( because there's no 'ready' event with select2 )
|
|
_.delay( function() {
|
|
try{ $( 'select[data-czrtype]', input.container ).czrSelect2('open'); }catch(er) {}
|
|
}, 100 );
|
|
}
|
|
}).fail( function( _r_ ) {
|
|
api.errare( '_getRevisionHistory => fail response =>', _r_ );
|
|
});
|
|
input.revisionHistorySet = true;
|
|
};
|
|
|
|
// Generate options and open select2
|
|
input.container.on('change', '.sek-revision-history', function() {
|
|
var _val = $(this).val();
|
|
if ( '_select_' !== _val ) {
|
|
api.czr_sektions.setSingleRevision( { revision_post_id : _val, is_local : 'local_revisions' === input.id } );
|
|
}
|
|
});
|
|
|
|
// schedule the revisionHistorySet after a delay
|
|
_.delay( function() { _do_( { open_on_init : false } );}, 1000 );
|
|
|
|
}//revision_history
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
nimble_tinymce_editor : function() {
|
|
var input = this,
|
|
$textarea = input.container.find('textarea').first(),
|
|
_id = $textarea.length > 0 ? $textarea.attr('id') : null,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
// see how those buttons can be set in php class _NIMBLE_Editors
|
|
// @see the global js var nimbleTinyMCEPreInit includes all params
|
|
defaultToolbarBtns = sektionsLocalizedData.defaultToolbarBtns,
|
|
//defaultQuickTagBtns = "strong,em,link,block,del,ins,img,ul,ol,li,code,more,close",
|
|
defaultQuickTagBtns = "strong,em,link,code";
|
|
|
|
if ( _.isNull( _id ) ) {
|
|
throw new Error( 'api.czrInputMap.nimble_tinymce_editor => missing textarea for module :' + input.module.id );
|
|
}
|
|
if ( !window.tinyMCE ) {
|
|
throw new Error( 'api.czrInputMap.nimble_tinymce_editor => tinyMCE not defined.');
|
|
}
|
|
if ( tinyMCE.get( _id ) ) {
|
|
throw new Error( 'api.czrInputMap.nimble_tinymce_editor => duplicate editor id.');
|
|
}
|
|
var getToolbarBtns = function() {
|
|
var toolBarBtn = defaultToolbarBtns.split(',');
|
|
if ( inputRegistrationParams.editor_params && _.isArray( inputRegistrationParams.editor_params.excludedBtns ) ) {
|
|
var excluded = inputRegistrationParams.editor_params.excludedBtns;
|
|
toolBarBtn = _.filter( toolBarBtn, function( _btn ) {
|
|
return !_.contains( excluded, _btn );
|
|
});
|
|
}
|
|
if ( inputRegistrationParams.editor_params && _.isString( inputRegistrationParams.editor_params.includedBtns ) ) {
|
|
var includedBtns = inputRegistrationParams.editor_params.includedBtns;
|
|
// 'basic_btns' or 'basic_btns_nolink'
|
|
if ( _.isEmpty( includedBtns ) || !_.isArray( sektionsLocalizedData[includedBtns] ) ) {
|
|
api.errare('nimble_tinymce_editor input => invalid set of buttons provided', includedBtns );
|
|
} else {
|
|
includedBtns = sektionsLocalizedData[includedBtns];
|
|
toolBarBtn = _.filter( toolBarBtn, function( _btn ) {
|
|
return _.contains( includedBtns, _btn );
|
|
});
|
|
}
|
|
}
|
|
return toolBarBtn.join(',');
|
|
};
|
|
var getEditorHeight = function() {
|
|
return ( inputRegistrationParams.editor_params && _.isNumber( inputRegistrationParams.editor_params.height ) ) ? inputRegistrationParams.editor_params.height : api.czr_sektions.TINYMCE_EDITOR_HEIGHT;
|
|
};
|
|
var isAutoPEnabled = function() {
|
|
// on registration, the autop can be specified
|
|
return inputRegistrationParams && inputRegistrationParams.editor_params && true === inputRegistrationParams.editor_params.autop;
|
|
};
|
|
// Set a height for the textarea before instanciation
|
|
//$textarea.css( { 'height' : getEditorHeight() } );
|
|
|
|
// the plugins like colorpicker have been loaded when instantiating the detached tinymce editor
|
|
// @see php class _NIMBLE_Editors
|
|
// if not specified, wp.editor falls back on the ones of wp.editor.getDefaultSettings()
|
|
// we can use them here without the need to specify them in the tinymce {} params
|
|
// @see the tinyMCE params with this global var : nimbleTinyMCEPreInit.mceInit["czr-customize-content_editor"]
|
|
//
|
|
// forced_root_block added to remove <p> tags automatically added
|
|
// @see https://stackoverflow.com/questions/20464028/how-to-remove-unwanted-p-tags-from-wordpress-editor-using-tinymce
|
|
var init_settings = {
|
|
//tinymce: nimbleTinyMCEPreInit.mceInit["czr-customize-content_editor"],
|
|
tinymce: {
|
|
//plugins:"charmap,colorpicker,hr,lists,media,paste,tabfocus,textcolor,wordpress,wpeditimage,wpemoji,wpgallery,wplink,wpdialogs,wptextpattern,wpview",
|
|
toolbar1:getToolbarBtns(),
|
|
//toolbar2:"",
|
|
content_css:( function() {
|
|
var stylesheets = [ sektionsLocalizedData.tinyMceNimbleEditorStylesheetUrl ];
|
|
if( !wp.oldEditor.getDefaultSetting )
|
|
return stylesheets;
|
|
var default_settings = wp.oldEditor.getDefaultSettings();
|
|
if ( default_settings && default_settings.tinymce && default_settings.tinymce.content_css ) {
|
|
stylesheets = _.union( default_settings.tinymce.content_css.split(','), stylesheets );
|
|
}
|
|
return stylesheets.join(',');
|
|
})(),
|
|
// https://www.tiny.cloud/docs/plugins/autoresize/
|
|
min_height :40,
|
|
height:getEditorHeight()
|
|
},
|
|
quicktags : {
|
|
buttons : defaultQuickTagBtns
|
|
},
|
|
mediaButtons: ( inputRegistrationParams.editor_params && false === inputRegistrationParams.editor_params.media_button ) ? false : true
|
|
};
|
|
|
|
// AUTOP
|
|
init_settings.tinymce.wpautop = isAutoPEnabled();
|
|
// forced_root_block is added to remove <p> tags automatically added
|
|
// @see https://stackoverflow.com/questions/20464028/how-to-remove-unwanted-p-tags-from-wordpress-editor-using-tinymce
|
|
if ( !isAutoPEnabled() ) {
|
|
init_settings.tinymce.forced_root_block = "";
|
|
}
|
|
|
|
// INITIALIZE
|
|
wp.oldEditor.initialize( _id, init_settings );
|
|
// Note that an easy way to instantiate a basic editor would be to use :
|
|
// wp.editor.initialize( _id, { tinymce : { forced_root_block : "", wpautop: false }, quicktags : true });
|
|
var _editor = tinyMCE.get( _id );
|
|
if ( ! _editor ) {
|
|
throw new Error( 'setupTinyMceEditor => missing editor instance for module :' + input.module.id );
|
|
}
|
|
|
|
// Store the id of each instantiated tinyMceEditor
|
|
// used in api.czrSektion::cleanRegisteredAndLargeSelectInput
|
|
api.czrActiveWPEditors = api.czrActiveWPEditors || [];
|
|
var currentEditors = $.extend( true, [], api.czrActiveWPEditors );
|
|
currentEditors.push(_id);
|
|
api.czrActiveWPEditors = currentEditors;
|
|
|
|
// Let's set the input() value when the editor is ready
|
|
// Because when we instantiate it, the textarea might not reflect the input value because too early
|
|
var initial_value, _doOnInit = function() {
|
|
// inject the content in the code editor now
|
|
// @fixes the problem of {{...}} syntax being parsed by _. templating system
|
|
|
|
// Feb 2021 : modules using this input will now be saved as a json to fix emojis issues
|
|
// we've started to implement the json saved for the heading module, but all modules will progressively transition to this new format
|
|
// see fix for https://github.com/presscustomizr/nimble-builder/issues/544
|
|
// to ensure retrocompatibility with data previously not saved as json, we need to perform a json validity check
|
|
initial_value = input();
|
|
if ( api.czr_sektions.isJsonString(initial_value) ) {
|
|
initial_value = JSON.parse( initial_value );
|
|
}
|
|
$textarea.html( initial_value );
|
|
_editor.setContent( initial_value );
|
|
//$('#wp-' + _editor.id + '-wrap' ).find('iframe').addClass('labite').css('height','50px');
|
|
};
|
|
if ( _editor.initialized ) {
|
|
_doOnInit();
|
|
} else {
|
|
_editor.on( 'init',_doOnInit );
|
|
}
|
|
|
|
// bind events
|
|
_editor.on( 'input change keyup', function( evt ) {
|
|
input( _editor.getContent() );
|
|
} );
|
|
},
|
|
|
|
|
|
|
|
|
|
// this input setup works in collaboration with ::setupTinyMceEditor()
|
|
// for api.sekEditorExpanded() and resizing of the editor.
|
|
detached_tinymce_editor : function() {
|
|
var input = this,
|
|
$textarea = $('textarea#' + sektionsLocalizedData.idOfDetachedTinyMceTextArea ), //$('textarea#czr-customize-content_editor'),
|
|
_id,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type );
|
|
|
|
if ( $textarea.length > 0 ) {
|
|
_id = $textarea.attr('id');
|
|
} else {
|
|
throw new Error( 'api.czrInputMap::detached_tinymce_editor => missing textarea element');
|
|
}
|
|
|
|
// if ( _.isNull( _id ) ) {
|
|
// throw new Error( 'setupDetachedTinyMceEditor => missing textarea for module :' + input.module.id );
|
|
// }
|
|
// See wp.editor.initialize() in wp-admin/js/editor.js for initialization options.
|
|
// **
|
|
// * Initialize TinyMCE and/or Quicktags. For use with wp_enqueue_editor() (PHP).
|
|
// *
|
|
// * Intended for use with an existing textarea that will become the Text editor tab.
|
|
// * The editor width will be the width of the textarea container, height will be adjustable.
|
|
// *
|
|
// * Settings for both TinyMCE and Quicktags can be passed on initialization, and are "filtered"
|
|
// * with custom jQuery events on the document element, wp-before-tinymce-init and wp-before-quicktags-init.
|
|
// *
|
|
// * @since 4.8.0
|
|
// *
|
|
// * @param {string} id The HTML id of the textarea that is used for the editor.
|
|
// * Has to be jQuery compliant. No brackets, special chars, etc.
|
|
// * @param {object} settings Example:
|
|
// * settings = {
|
|
// * // See https://www.tinymce.com/docs/configure/integration-and-setup/.
|
|
// * // Alternatively set to `true` to use the defaults.
|
|
// * tinymce: {
|
|
// * setup: function( editor ) {
|
|
// * console.log( 'Editor initialized', editor );
|
|
// * }
|
|
// * }
|
|
// *
|
|
// * // Alternatively set to `true` to use the defaults.
|
|
// * quicktags: {
|
|
// * buttons: 'strong,em,link'
|
|
// * }
|
|
// * }
|
|
// */
|
|
|
|
// Remove now
|
|
// the initial instance has been created with php inline js generated by sek_setup_nimble_editor()
|
|
// IMPORTANT !! => don't use wp.editor.remove() @see wp-admin/js/editor.js, because it can remove the stylesheet editor.css
|
|
// and break the style of all editors
|
|
if ( window.tinymce ) {
|
|
mceInstance = window.tinymce.get( _id );
|
|
if ( mceInstance ) {
|
|
// if ( ! mceInstance.isHidden() ) {
|
|
// mceInstance.save();
|
|
// }
|
|
mceInstance.remove();
|
|
}
|
|
}
|
|
// if ( window.quicktags ) {
|
|
// qtInstance = window.QTags.getInstance( id );
|
|
|
|
// if ( qtInstance ) {
|
|
// qtInstance.remove();
|
|
// }
|
|
// }
|
|
|
|
// Instantiate a new one
|
|
// see in wp-admin/js/editor.js
|
|
// the nimbleTinyMCEPreInit are set in php class _NIMBLE_Editors
|
|
if ( !window.nimbleTinyMCEPreInit || !window.nimbleTinyMCEPreInit.mceInit || !window.nimbleTinyMCEPreInit.mceInit[ _id ] ) {
|
|
throw new Error('setupDetachedTinyMceEditor => invalid nimbleTinyMCEPreInit global var');
|
|
}
|
|
|
|
var init_settings = nimbleTinyMCEPreInit.mceInit[ _id ];
|
|
|
|
// Add the nimble editor's stylesheet to the default's ones
|
|
init_settings.content_css = ( function() {
|
|
var stylesheets = [ sektionsLocalizedData.tinyMceNimbleEditorStylesheetUrl ];
|
|
if( !wp.oldEditor.getDefaultSetting )
|
|
return stylesheets;
|
|
var default_settings = wp.oldEditor.getDefaultSettings();
|
|
if ( default_settings && default_settings.tinymce && default_settings.tinymce.content_css ) {
|
|
stylesheets = _.union( default_settings.tinymce.content_css.split(','), stylesheets );
|
|
}
|
|
return stylesheets.join(',');
|
|
})();
|
|
|
|
// Handle wpautop param
|
|
var item = input.input_parent;
|
|
var isAutoPEnabled = function() {
|
|
var parent_item_val = item();
|
|
// 1) the module 'czr_tinymce_child' includes an autop option
|
|
// 2) on registration, the autop can be specified
|
|
if ( !_.isUndefined( parent_item_val.autop ) ) {
|
|
return parent_item_val.autop;
|
|
} else {
|
|
return inputRegistrationParams && inputRegistrationParams.editor_params && true === inputRegistrationParams.editor_params.autop;
|
|
}
|
|
};
|
|
|
|
init_settings.wpautop = isAutoPEnabled();
|
|
|
|
// forced_root_block is added to remove <p> tags automatically added
|
|
// @see https://stackoverflow.com/questions/20464028/how-to-remove-unwanted-p-tags-from-wordpress-editor-using-tinymce
|
|
if ( !isAutoPEnabled() ) {
|
|
init_settings.forced_root_block = "";
|
|
}
|
|
|
|
// TOOLBARS
|
|
init_settings.toolbar1 = sektionsLocalizedData.defaultToolbarBtns;
|
|
init_settings.toolbar2 = "";
|
|
|
|
if ( window.tinymce ) {
|
|
window.tinymce.init( init_settings );
|
|
window.QTags.getInstance( _id );
|
|
} else {
|
|
if ( window.console ) {
|
|
console.log('Error in ::detached_tinymce_editor => window.tinymce not defined ');
|
|
}
|
|
}
|
|
|
|
// wp.editor.initialize( _id, {
|
|
// //tinymce : true,
|
|
// tinymce: nimbleTinyMCEPreInit.mceInit[_id],
|
|
// quicktags : nimbleTinyMCEPreInit.qtInit[_id],
|
|
// mediaButtons: true
|
|
// });
|
|
|
|
var _editor;
|
|
if ( window.tinyMCE ) {
|
|
_editor = tinyMCE.get( _id );
|
|
//throw new Error( 'api.czrInputMap.detached_tinymce_editor => tinyMCE not defined.');
|
|
} else {
|
|
if ( window.console ) {
|
|
console.log('Error in ::detached_tinymce_editor => window.tinyMCE not defined ');
|
|
}
|
|
}
|
|
|
|
// Let's set the input() value when the editor is ready
|
|
// Because when we instantiate it, the textarea might not reflect the input value because too early
|
|
var initial_value, _doOnInit = function() {
|
|
// To ensure retro-compat with content created prior to Nimble v1.5.2, in which the editor has been updated
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/404
|
|
// we add the <p> tag on init, if autop option is checked
|
|
// December 2020 : when running wp.editor.autop( input() , line break not preserved when re-opening a text module UI,
|
|
// see https://github.com/presscustomizr/nimble-builder/issues/769
|
|
// var initial_value = ( !isAutoPEnabled() || ! _.isFunction( wp.editor.autop ) ) ? input() : wp.editor.autop( input() );
|
|
// console.log('INITIAL CONTENT', input(), wp.editor.autop( input() ) );
|
|
|
|
// Feb 2021 : modules using this input will now be saved as a json to fix emojis issues
|
|
// we've started to implement the json saved for the heading module, but all modules will progressively transition to this new format
|
|
// see fix for https://github.com/presscustomizr/nimble-builder/issues/544
|
|
// to ensure retrocompatibility with data previously not saved as json, we need to perform a json validity check
|
|
initial_value = input.input_value;
|
|
if ( api.czr_sektions.isJsonString(initial_value) ) {
|
|
initial_value = JSON.parse( initial_value );
|
|
}
|
|
|
|
_editor.setContent( initial_value );
|
|
api.sekEditorExpanded( true );
|
|
// trigger a resize to adjust height on init https://github.com/presscustomizr/nimble-builder/issues/409
|
|
$(window).trigger('resize');
|
|
};
|
|
|
|
// if we have an editor, let's go
|
|
if ( _editor ) {
|
|
if ( _editor.initialized ) {
|
|
_doOnInit();
|
|
} else {
|
|
_editor.on( 'init', _doOnInit );
|
|
}
|
|
|
|
// bind events
|
|
_editor.on( 'input change keyup keydown click SetContent BeforeSetContent', function( evt ) {
|
|
//$textarea.trigger( 'change', {current_input : input} );
|
|
input( isAutoPEnabled() ? _editor.getContent() : wp.oldEditor.removep( _editor.getContent() ) );
|
|
});
|
|
}
|
|
|
|
// store the current input now, so we'll always get the right one when textarea changes
|
|
api.sekCurrentDetachedTinyMceInput = input;
|
|
|
|
// TEXT EDITOR => This is the original textarea element => needs to be bound separatelyn because not considered part of the tinyMce editor.
|
|
// Bound only once
|
|
if ( !$textarea.data('czr-bound-for-detached-editor') ) {
|
|
$textarea.on( 'input', function( evt, params ) {
|
|
api.sekCurrentDetachedTinyMceInput( $(this).val() );
|
|
});
|
|
$textarea.data('czr-bound-for-detached-editor', true );
|
|
}
|
|
|
|
},//setupDetachedTinyMceEditor
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
import_export : function() {
|
|
var input = this,
|
|
$pre_import_button = input.container.find('button[data-czr-action="sek-pre-import"]'),
|
|
$file_input = input.container.find('input[name=sek-import-file]'),
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
currentScope = inputRegistrationParams.scope,
|
|
currentSetId = 'local' === inputRegistrationParams.scope ? api.czr_sektions.localSectionsSettingId() : api.czr_sektions.getGlobalSectionsSettingId();
|
|
|
|
if ( !_.contains(['local', 'global'], currentScope ) ) {
|
|
api.errare('api.czrInputMap.import_export => invalid currentScope', currentScope );
|
|
}
|
|
|
|
// Add event listener to set the button state
|
|
$file_input.on('change', function( evt ) {
|
|
$pre_import_button.toggleClass( 'disabled', _.isEmpty( $(this).val() ) );
|
|
});
|
|
|
|
// @return boolean
|
|
var customizeChangesetIncludesNimbleDirties = function() {
|
|
var hasNimbleDirties = false,
|
|
_dirties = wp.customize.dirtyValues();
|
|
|
|
if ( ! _.isEmpty( _dirties ) ) {
|
|
_.each( _dirties, function( _val, _setId ) {
|
|
if ( hasNimbleDirties )
|
|
return;
|
|
// we're after setting id like
|
|
// - nimble___[skp__post_post_1] <= local skope setting
|
|
// - __nimble__4234ae1dc0fa__font_settings <= level setting
|
|
// - nimble_global_opts <= global options
|
|
// - __nimble__skp__post_post_1__localSkopeOptions__template <= local option setting
|
|
hasNimbleDirties = -1 !== _setId.indexOf('nimble');
|
|
});
|
|
}
|
|
return hasNimbleDirties;
|
|
};
|
|
|
|
// Schedule action on button click
|
|
input.container.on( 'click', '[data-czr-action]', function( evt ) {
|
|
evt.stopPropagation();
|
|
var _action = $(this).data( 'czr-action' );
|
|
switch( _action ) {
|
|
case 'sek-export' :
|
|
// prevent exporting if the customize changeset is dirty
|
|
// => because the PHP sek_catch_export_action() doesn't have access to the customize changeset and needs the one persisted in DB
|
|
if ( customizeChangesetIncludesNimbleDirties() ) {
|
|
alert(sektionsLocalizedData.i18n['You need to publish before exporting.']);
|
|
break;
|
|
}
|
|
// Is there something to export ?
|
|
var currentVal = api( currentSetId )(),
|
|
hasNoSections = true;
|
|
_.each( currentVal.collection, function( locationData ){
|
|
if ( !hasNoSections )
|
|
return;
|
|
if ( !_.isEmpty( locationData.collection ) ) {
|
|
hasNoSections = false;
|
|
}
|
|
});
|
|
if ( hasNoSections ) {
|
|
alert(sektionsLocalizedData.i18n['Nothing to export.']);
|
|
break;
|
|
}
|
|
//_export( { scope : currentScope } );// local or global
|
|
api.czr_sektions.export_template( { scope : currentScope } );
|
|
break;//'sek-export'
|
|
|
|
case 'sek-pre-import' :
|
|
// Can we import ?
|
|
// => the current page must have at least one active location
|
|
if( _.isEmpty( api.czr_sektions.activeLocations() ) ) {
|
|
alert(sektionsLocalizedData.i18n['The current page has no available locations to import Nimble Builder sections.']);
|
|
break;
|
|
}
|
|
|
|
api.czr_sektions.import_template_from_file({
|
|
pre_import_check : true,
|
|
input : input,
|
|
file_input : $file_input
|
|
})
|
|
.done( function( server_resp ) {
|
|
api.czr_sektions.pre_checks_from_file_import( server_resp, {
|
|
pre_import_check : false,
|
|
input : input,
|
|
file_input : $file_input
|
|
});
|
|
})
|
|
.fail( function( error_resp ) {
|
|
api.errare( 'import_export_ input => pre_checks_from_file_import failed', error_resp );
|
|
api.czr_sektions.doAlwaysAfterFileImportAndApiSettingUpdate({
|
|
input : input,
|
|
file_input : $file_input
|
|
});
|
|
api.czr_sektions.import_template_from_file({
|
|
input : input,
|
|
file_input : $file_input
|
|
});
|
|
});
|
|
|
|
break;//'sek-import'
|
|
case 'sek-import-as-is' :
|
|
api.czr_sektions.import_template_from_file({
|
|
input : input,
|
|
file_input : $file_input
|
|
});
|
|
break;
|
|
case 'sek-import-assign' :
|
|
api.czr_sektions.import_template_from_file({
|
|
assign_missing_locations : true,
|
|
input : input,
|
|
file_input : $file_input
|
|
});
|
|
break;
|
|
case 'sek-cancel-import' :
|
|
api.czr_sektions.doAlwaysAfterFileImportAndApiSettingUpdate({
|
|
input : input,
|
|
file_input : $file_input
|
|
});
|
|
break;
|
|
}//switch
|
|
});//input.container.on( 'click' .. )
|
|
}//import_export()
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
simpleselect : function( selectOptions ) {
|
|
api.czr_sektions.setupSelectInput.call( this, selectOptions );
|
|
},
|
|
multiselect : function( selectOptions ) {
|
|
api.czr_sektions.setupSelectInput.call( this, selectOptions );
|
|
},
|
|
|
|
simpleselectWithDeviceSwitcher : function( selectOptions ) {
|
|
var input = this,
|
|
item = input.input_parent,
|
|
module = input.module,
|
|
inputRegistrationParams = api.czr_sektions.getInputRegistrationParams( input.id, input.module.module_type ),
|
|
$select = $( 'select', input.container ),
|
|
defaultVal = ( ! _.isEmpty( inputRegistrationParams ) && ! _.isEmpty( inputRegistrationParams.default ) ) ? inputRegistrationParams.default : {};
|
|
|
|
// use the provided selectOptions if any
|
|
selectOptions = _.isEmpty( selectOptions ) ? inputRegistrationParams.choices : selectOptions;
|
|
|
|
// allow selectOptions to be filtrable remotely when the options are not passed on registration for example
|
|
// @see widget are module in initialize() for example
|
|
var filtrable = { params : selectOptions };
|
|
input.module.trigger( 'nimble-set-select-input-options', filtrable );
|
|
selectOptions = filtrable.params;
|
|
|
|
if ( _.isEmpty( selectOptions ) || ! _.isObject( selectOptions ) ) {
|
|
api.errare( 'api.czr_sektions.setupSelectInput => missing select options for input id => ' + input.id + ' in module ' + input.module.module_type );
|
|
return;
|
|
}
|
|
|
|
//generates the options
|
|
_.each( selectOptions , function( title, value ) {
|
|
var _attributes = {
|
|
value : value,
|
|
html: title
|
|
};
|
|
if ( value == input() ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
} else if ( 'px' === value ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
}
|
|
$select.append( $('<option>', _attributes) );
|
|
});
|
|
|
|
// Recursive helper
|
|
// return the value set for the currently previewed device if exists
|
|
// OR
|
|
// return the inherited value from the first parent device for which the value is set
|
|
// OR
|
|
// falls back on the module default
|
|
var getCurrentDeviceActualOrInheritedValue = function( inputValues, currentDevice ) {
|
|
var deviceHierarchy = [ 'mobile' , 'tablet', 'desktop' ];
|
|
if ( _.has( inputValues, currentDevice ) ) {
|
|
return inputValues[ currentDevice ];
|
|
} else {
|
|
var deviceIndex = _.findIndex( deviceHierarchy, function( _d_ ) { return currentDevice === _d_; });
|
|
if ( ! _.isEmpty( currentDevice ) && deviceIndex < deviceHierarchy.length ) {
|
|
return getCurrentDeviceActualOrInheritedValue( inputValues, deviceHierarchy[ deviceIndex + 1 ] );
|
|
} else {
|
|
var clonedDefault = $.extend( true, { desktop : '' }, defaultVal );
|
|
return clonedDefault[ 'desktop' ];
|
|
}
|
|
}
|
|
};
|
|
|
|
// Synchronizes on init + refresh on previewed device changes
|
|
var syncWithPreviewedDevice = function( currentDevice ) {
|
|
// initialize the number input with the current input val
|
|
// for retro-compatibility, we must handle the case when the initial input val is a string instead of an array
|
|
// in this case, the string value is assigned to the desktop device.
|
|
var inputVal = input(), inputValues = {}, clonedDefault = $.extend( true, {}, defaultVal );
|
|
inputValues = clonedDefault;
|
|
if ( _.isObject( inputVal ) ) {
|
|
inputValues = $.extend( true, {}, inputVal );
|
|
} else if ( _.isString( inputVal ) && ! _.isEmpty( inputVal ) ) {
|
|
inputValues = { desktop : inputVal };
|
|
}
|
|
|
|
//inputValues = _.extend( inputValues, clonedDefault );
|
|
// do we have a val for the current device ?
|
|
var _rawVal = getCurrentDeviceActualOrInheritedValue( inputValues, currentDevice );
|
|
|
|
// update the select val
|
|
//$select.selecter('destroy');
|
|
$select.val( _rawVal ).trigger('change', { previewed_device_switched : true });// We don't want to update the input()
|
|
};
|
|
|
|
// SETUP
|
|
// setup the device switcher
|
|
api.czr_sektions.maybeSetupDeviceSwitcherForInput.call( input );
|
|
|
|
// Set the input val
|
|
$select.on('change', function( evt, params ) {
|
|
var previewedDevice = api.previewedDevice() || 'desktop',
|
|
changedSelectVal = $(this).val(),
|
|
_newInputVal;
|
|
|
|
_newInputVal = $.extend( true, {}, _.isObject( input() ) ? input() : {} );
|
|
_newInputVal[ previewedDevice ] = $.extend( true, {}, _newInputVal[ previewedDevice ] || {} );
|
|
|
|
// Validates
|
|
if ( ( _.isString( changedSelectVal ) && ! _.isEmpty( changedSelectVal ) ) ) {
|
|
_newInputVal[ previewedDevice ]= changedSelectVal;
|
|
}
|
|
|
|
// update input if not previewed_device_switched
|
|
if ( _.isEmpty( params ) || ( _.isObject( params ) && true !== params.previewed_device_switched ) ) {
|
|
input( _newInputVal );
|
|
}
|
|
});
|
|
|
|
// react to previewed device changes
|
|
// input.previewedDevice is updated in api.czr_sektions.maybeSetupDeviceSwitcherForInput()
|
|
input.previewedDevice.bind( function( currentDevice ) {
|
|
try { syncWithPreviewedDevice( currentDevice ); } catch( er ) {
|
|
api.errare('Error when firing syncWithPreviewedDevice for input type ' + input.type + ' for input id ' + input.id , er );
|
|
}
|
|
});
|
|
|
|
//$select.selecter();
|
|
}
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
category_picker : function( params ) {
|
|
var selectOptions,
|
|
input = this,
|
|
$selectEl = $( 'select[data-czrtype]', input.container );
|
|
|
|
var getInputValue = function() {
|
|
var inputValue = input();
|
|
// when select is multiple, the value is an array
|
|
inputValue = _.isString( inputValue ) ? [ inputValue ] : inputValue;
|
|
return !_.isArray( inputValue ) ? [] : inputValue;
|
|
};
|
|
|
|
|
|
var _getCategoryCollection = function() {
|
|
return $.Deferred( function( _dfd_ ) {
|
|
if ( ! _.isEmpty( api.czr_sektions.post_categories ) ) {
|
|
_dfd_.resolve( api.czr_sektions.post_categories );
|
|
} else {
|
|
wp.ajax.post( 'sek_get_post_categories', {
|
|
nonce: api.settings.nonce.save,
|
|
}).done( function( raw_cat_collection ) {
|
|
if ( !_.isArray( raw_cat_collection ) ) {
|
|
api.errare( input.id + ' => error => invalid category collection sent by server');
|
|
}
|
|
var catCollection = {};
|
|
// server sends
|
|
// [
|
|
// 0: {id: 2, slug:'my-category', name: "My category"}
|
|
// 1: {id: 11, slug:'my-category', name: "cat10"}
|
|
// ...
|
|
// ]
|
|
_.each( raw_cat_collection, function( cat_data ) {
|
|
if ( _.isEmpty( cat_data.slug ) || _.isEmpty( cat_data.name ) ) {
|
|
_dfd_.reject( 'missing slug or name for at least one category' );
|
|
} else {
|
|
catCollection[ cat_data.slug ] = cat_data.name;
|
|
}
|
|
|
|
});
|
|
api.czr_sektions.post_categories = catCollection;
|
|
_dfd_.resolve( api.czr_sektions.post_categories );
|
|
}).fail( function( _r_ ) {
|
|
_dfd_.reject( _r_ );
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
// do
|
|
var _fetchServerCatsAndInstantiateSelect2 = function( params ) {
|
|
if ( true === input.catCollectionSet )
|
|
return;
|
|
$.when( _getCategoryCollection() ).done( function( _catCollection ) {
|
|
_generateOptionsAndInstantiateSelect2(_catCollection);
|
|
if ( params && true === params.open_on_init ) {
|
|
// let's open select2 after a delay ( because there's no 'ready' event with select2 )
|
|
_.delay( function() {
|
|
try{ $selectEl.czrSelect2('open'); } catch(er) {}
|
|
}, 100 );
|
|
}
|
|
}).fail( function( _r_ ) {
|
|
api.errare( input.id + ' => fail response when _getCategoryCollection()', _r_ );
|
|
});
|
|
input.catCollectionSet = true;
|
|
};
|
|
|
|
var _generateOptionsAndInstantiateSelect2 = function( selectOptions ) {
|
|
//generates the options
|
|
_.each( selectOptions , function( title, value ) {
|
|
var _attributes = {
|
|
value : value,
|
|
html: title
|
|
};
|
|
if ( _.contains( getInputValue(), value ) ) {
|
|
$.extend( _attributes, { selected : "selected" } );
|
|
}
|
|
$selectEl.append( $('<option>', _attributes) );
|
|
});
|
|
// see how the tmpl is rendered server side in PHP with ::ac_set_input_tmpl_content()
|
|
$selectEl.czrSelect2({
|
|
closeOnSelect: true,
|
|
templateSelection: function czrEscapeMarkup(obj) {
|
|
//trim dashes
|
|
return obj.text.replace(/\u2013|\u2014/g, "");
|
|
}
|
|
});
|
|
|
|
//handle case when all choices become unselected
|
|
$selectEl.on('change', function(){
|
|
if ( 0 === $(this).find("option:selected").length ) {
|
|
input([]);
|
|
}
|
|
});
|
|
};// _generateOptionsAnd...()
|
|
// schedule the catCollectionSet after a delay
|
|
//_.delay( function() { _fetchServerCatsAndInstantiateSelect2( { open_on_init : false } );}, 1000 );
|
|
|
|
// on init, instantiate select2 with the input() values only
|
|
var selectOptionsOnInit = {};
|
|
_.each( getInputValue(), function( _val ) {
|
|
selectOptionsOnInit[ _val ] = ( _val + '' ).replace( /-/g, ' ');
|
|
});
|
|
_generateOptionsAndInstantiateSelect2( selectOptionsOnInit );
|
|
|
|
// re-generate select2 on click with the server collection
|
|
input.container.on('click', function() {
|
|
if ( true === input.catCollectionSet )
|
|
return;
|
|
// destroy the temporary instance
|
|
$selectEl.czrSelect2('destroy');
|
|
// destroy the temporary options
|
|
$.when( $selectEl.find('option').remove() ).done( function() {
|
|
_fetchServerCatsAndInstantiateSelect2( { open_on_init : true } );
|
|
});
|
|
});
|
|
|
|
}//category_picker()
|
|
});//$.extend( api.czrInputMap, {})
|
|
|
|
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
$.extend( api.czrInputMap, {
|
|
grid_layout : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-grid-layout-wrapper', input.container ),
|
|
$mainInput = $wrapper.find( 'input[type="hidden"]');
|
|
|
|
// SETUP
|
|
// Setup the initial state of the number input
|
|
$mainInput.val( input() );
|
|
|
|
// Schedule choice changes on button click
|
|
$wrapper.on( 'click', '[data-sek-grid-layout]', function( evt, params ) {
|
|
evt.stopPropagation();
|
|
// handle the is-selected css class toggling
|
|
$wrapper.find('[data-sek-grid-layout]').removeClass('selected').attr( 'aria-pressed', false );
|
|
$(this).addClass('selected').attr( 'aria-pressed', true );
|
|
var newChoice;
|
|
try { newChoice = $(this).data('sek-grid-layout'); } catch( er ) {
|
|
api.errare( input.type + ' => error when attaching click event', er );
|
|
}
|
|
input( newChoice );
|
|
});
|
|
|
|
|
|
// INITIALIZES
|
|
// trigger a click on the initial unit
|
|
$( '[data-sek-grid-layout="' + input() + '"]', $wrapper ).trigger('click');
|
|
}
|
|
});// $.extend( api.czrInputMap
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
refresh_preview_button : function( params ) {
|
|
var input = this;
|
|
|
|
// Schedule choice changes on button click
|
|
input.container.on( 'click', '.sek-refresh-button', function( evt, params ) {
|
|
evt.stopPropagation();
|
|
api.previewer.refresh();
|
|
});//on('click')
|
|
}
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData
|
|
( function ( api, $, _ ) {
|
|
// all available input type as a map
|
|
api.czrInputMap = api.czrInputMap || {};
|
|
|
|
// input_type => callback fn to fire in the Input constructor on initialize
|
|
// the callback can receive specific params define in each module constructor
|
|
// For example, a content picker can be given params to display only taxonomies
|
|
// the default input_event_map can also be overriden in this callback
|
|
$.extend( api.czrInputMap, {
|
|
site_tmpl_picker : function( params ) {
|
|
var input = this,
|
|
_html,
|
|
$hidInputEl = $( '[data-czrtype]', input.container ),
|
|
_defaultData = {
|
|
site_tmpl_id : '_no_site_tmpl_',
|
|
site_tmpl_source : 'user_tmpl',
|
|
site_tmpl_title : ''
|
|
},
|
|
siteTmplData,
|
|
siteTmplDataCandidate,
|
|
site_tmpl_id, site_tmpl_source, site_tmpl_title;
|
|
|
|
// When a user template is being modified or removed, NB refreshes the site template input
|
|
input.container.one('site-tmpl-input-rendered', function() {
|
|
api.czr_sektions.allSavedTemplates.bind( function( userTmplates ) {
|
|
var raw_input = input();
|
|
siteTmplDataCandidate = $.extend( true, {}, _.isObject(raw_input) ? raw_input : {} );
|
|
if ( !_.isObject( siteTmplDataCandidate ) || _.isArray( siteTmplDataCandidate ) ) {
|
|
siteTmplDataCandidate = $.extend( true, {}, _defaultData );
|
|
}
|
|
//siteTmplDataCandidate = $.extend( siteTmplDataCandidate, _defaultData );
|
|
site_tmpl_id = siteTmplDataCandidate.site_tmpl_id;
|
|
site_tmpl_source = siteTmplDataCandidate.site_tmpl_source;
|
|
site_tmpl_title = siteTmplDataCandidate.site_tmpl_title;
|
|
|
|
if ( _.isEmpty(site_tmpl_id) || !_.isString(site_tmpl_id) || !_.isObject(userTmplates) )
|
|
return;
|
|
// Stop here if the template is not a user_tmpl. ( we don't need to reset title if _no_site_tmpl_ and api_tmpl are not editable )
|
|
if ( "user_tmpl" != site_tmpl_source )
|
|
return;
|
|
|
|
if ( userTmplates[site_tmpl_id] ) {
|
|
try{ printCurrentTemplateName(); } catch(er) { api.errare('Error when printing template val', er ); }
|
|
} else {
|
|
// If the template has been removed, trigger a reset
|
|
$hidInputEl.trigger('nb-set-site-tmpl', _defaultData );
|
|
}
|
|
});
|
|
});
|
|
|
|
// printParams : { see_me : true }
|
|
var printCurrentTemplateName = function( printParams ) {
|
|
printParams = $.extend( { see_me : false }, printParams || {} );
|
|
|
|
var _doRender = function( site_tmpl_id, site_tmpl_title ) {
|
|
_html = '<span class="sek-current-site-tmpl">';
|
|
if ( '_no_site_tmpl_' === site_tmpl_id || _.isEmpty( site_tmpl_id ) ) {
|
|
_html += sektionsLocalizedData.i18n['No template set.'];
|
|
input.container.removeClass('sek-has-site-tmpl');
|
|
input.container.removeClass('sek-site-tmpl-not-found');
|
|
// Case of a user template not found. NOT POSSIBLE WHEN API TEMPLATE SOURCE
|
|
} else if ( '_tmpl_not_found_' === site_tmpl_id || _.isEmpty( site_tmpl_id ) ) {
|
|
_html += sektionsLocalizedData.i18n['Template not found : reset or pick another one.'];
|
|
input.container.removeClass('sek-has-site-tmpl');
|
|
input.container.addClass('sek-site-tmpl-not-found');
|
|
} else {
|
|
_html += sektionsLocalizedData.i18n['Active template : '] + ( _.isEmpty(site_tmpl_title) ? site_tmpl_id : site_tmpl_title );
|
|
input.container.addClass('sek-has-site-tmpl');
|
|
input.container.removeClass('sek-site-tmpl-not-found');
|
|
}
|
|
_html += '</span>';
|
|
input.container.find('.sek-current-site-tmpl').remove();
|
|
input.container.find('.czr-input').prepend(_html);
|
|
|
|
// Catch user's eye by animating the site template input
|
|
if ( printParams.see_me && '_no_site_tmpl_' != site_tmpl_id ) {
|
|
input.container.addClass('button-see-me');
|
|
_.delay( function() {
|
|
input.container.removeClass('button-see-me');
|
|
}, 800 );
|
|
}
|
|
|
|
input.container.trigger('site-tmpl-input-rendered');
|
|
};//_doRender
|
|
|
|
|
|
//{
|
|
// site_tmpl_id : _tmpl_id,
|
|
// site_tmpl_source : _tmpl_source,
|
|
// site_tmpl_title : _tmpl_title
|
|
// }
|
|
var raw_input = input();
|
|
siteTmplDataCandidate = $.extend( true, {}, _.isObject(raw_input) ? raw_input : {} );
|
|
if ( !_.isObject( siteTmplDataCandidate ) || _.isArray( siteTmplDataCandidate ) ) {
|
|
siteTmplDataCandidate = $.extend( true, {}, _defaultData );
|
|
}
|
|
//siteTmplDataCandidate = $.extend( siteTmplDataCandidate, _defaultData );
|
|
|
|
site_tmpl_id = siteTmplDataCandidate.site_tmpl_id;
|
|
site_tmpl_source = siteTmplDataCandidate.site_tmpl_source;
|
|
site_tmpl_title = siteTmplDataCandidate.site_tmpl_title;
|
|
|
|
//site_tmpl_id = input();
|
|
if ( !_.isString(site_tmpl_id) || _.isEmpty(site_tmpl_id) ) {
|
|
api.errare('printCurrentTemplateName : Error => site template must be a string');
|
|
site_tmpl_id = '_no_site_tmpl_';
|
|
}
|
|
// Get the title
|
|
if ( '_no_site_tmpl_' === site_tmpl_id ) {
|
|
_doRender(siteTmplDataCandidate.site_tmpl_id, site_tmpl_title);
|
|
} else {
|
|
_tmpl_collection_promise = 'user_tmpl' === site_tmpl_source ? api.czr_sektions.setSavedTmplCollection : api.czr_sektions.getApiTmplCollection;
|
|
_tmpl_collection_promise.call(api.czr_sektions)
|
|
.done( function(tmpl_collection) {
|
|
// if the tmpl_id is found in the collection, update the site_tmpl_title with its latest value
|
|
if ( _.isObject(tmpl_collection) && tmpl_collection[site_tmpl_id] && tmpl_collection[site_tmpl_id].title ) {
|
|
site_tmpl_title = tmpl_collection[site_tmpl_id].title;
|
|
} else if ( 'user_tmpl' === site_tmpl_source ) {
|
|
// If an api template is not found, NB doesn't print _tmpl_not_found_ associated message
|
|
// => because it may happen that the api is unreachable or that an api template previously selected has been removed.
|
|
//
|
|
// For user template source, if tmpl id was not found in the current collection, it's been probably previously removed
|
|
// so render as a '_tmpl_not_found_'
|
|
api.errare('::printCurrentTemplateName => site template not found in collection => previously removed => id : ' + site_tmpl_id + ' | source : ' + site_tmpl_source );
|
|
site_tmpl_id = '_tmpl_not_found_';
|
|
}
|
|
_doRender(site_tmpl_id, site_tmpl_title);
|
|
})
|
|
.fail( function() {
|
|
api.errare('printCurrentTemplateName error when getting collection promise failed', params );
|
|
_dfd_.resolve('');
|
|
});
|
|
}
|
|
};
|
|
|
|
// Schedule events
|
|
input.container.on( 'click', '[data-sek-group-scope]', function( evt, args ) {
|
|
evt.stopPropagation();
|
|
var scope = $(this).data( 'sek-group-scope' );
|
|
|
|
if ( _.isEmpty( scope ) ) {
|
|
api.errare( 'site_tmpl_picker input => invalid scope provided.', scope );
|
|
return;
|
|
}
|
|
// Close picking when re-click on the button while the template gallery is already displayed
|
|
if ( input.container.hasClass('sek-site-tmpl-picking-active') ) {
|
|
api.czr_sektions._site_tmpl_scope = null;
|
|
api.czr_sektions.templateGalleryExpanded( false );
|
|
$('[data-input-type="site_tmpl_picker"]').removeClass('sek-site-tmpl-picking-active');
|
|
} else {
|
|
api.czr_sektions._site_tmpl_scope = input.id;
|
|
api.czr_sektions.templateGalleryExpanded( true );
|
|
$('[data-input-type="site_tmpl_picker"]').removeClass('sek-site-tmpl-picking-active');
|
|
input.container.addClass('sek-site-tmpl-picking-active');
|
|
}
|
|
});//on('click')
|
|
input.container.on( 'click', '.sek-remove-site-tmpl', function( evt, args ) {
|
|
evt.stopPropagation();
|
|
$hidInputEl.trigger('nb-set-site-tmpl', _defaultData );
|
|
});//on('click')
|
|
|
|
// @args {
|
|
// site_tmpl_id : _tmpl_id,
|
|
// site_tmpl_source : _tmpl_source,
|
|
// site_tmpl_title : _tmpl_title
|
|
// }
|
|
$hidInputEl.on('nb-set-site-tmpl', function( evt, args ) {
|
|
if ( !_.isObject(args) ) {
|
|
api.errare('site_tmpl_picker => error => wrong args on tmpl pick', args );
|
|
return;
|
|
}
|
|
if ( _.isUndefined(args.site_tmpl_id) || _.isUndefined(args.site_tmpl_source) || _.isUndefined(args.site_tmpl_title) ) {
|
|
api.errare('site_tmpl_picker => error => invalid args passed on tmpl pick', args );
|
|
return;
|
|
}
|
|
// _defaultData = { site_tmpl_id : '_no_site_tmpl_', site_tmpl_source : 'user_tmpl' }
|
|
siteTmplData = $.extend( true, {}, _defaultData );
|
|
siteTmplData = $.extend( siteTmplData, args );
|
|
input( siteTmplData );
|
|
|
|
|
|
try{ printCurrentTemplateName({ see_me : true }); } catch(er) { api.errare('Error when printing template val', er ); }
|
|
|
|
// Close gallery unless a reset has been triggered ( If a template has been removed )
|
|
if ( '_no_site_tmpl_' !== siteTmplData.site_tmpl_id ) {
|
|
api.czr_sektions.templateGalleryExpanded( false );
|
|
$('[data-input-type="site_tmpl_picker"]').removeClass('sek-site-tmpl-picking-active');
|
|
}
|
|
|
|
});
|
|
|
|
try{ printCurrentTemplateName(); } catch(er) { api.errare('Error when printing template val', er ); }
|
|
}
|
|
|
|
});//$.extend( api.czrInputMap, {})
|
|
})( wp.customize, jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
/* ------------------------------------------------------------------------- *
|
|
* CONTENT TYPE SWITCHER
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_content_type_switcher_module : {
|
|
//mthds : SectionPickerModuleConstructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_content_type_switcher_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_content_type_switcher_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* MODULE PICKER MODULE
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_module_picker_module : {
|
|
//mthds : ModulePickerModuleConstructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_module_picker_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_module_picker_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* SECTION PICKER MODULES
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
// var section_modules = [
|
|
// 'sek_intro_sec_picker_module',
|
|
// 'sek_features_sec_picker_module',
|
|
// 'sek_contact_sec_picker_module',
|
|
// 'sek_column_layouts_sec_picker_module',
|
|
// 'sek_header_sec_picker_module',
|
|
// 'sek_footer_sec_picker_module'
|
|
// ];
|
|
|
|
var section_modules = sektionsLocalizedData.presetSectionsModules;
|
|
if ( ! _.isArray( section_modules ) || _.isEmpty( section_modules ) ) {
|
|
api.errare( 'api.czrModuleMap => error when adding section modules');
|
|
return;
|
|
}
|
|
|
|
_.each( section_modules, function( module_type ) {
|
|
api.czrModuleMap[ module_type ] = {
|
|
//mthds : SectionPickerModuleConstructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( module_type, 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( module_type )
|
|
)
|
|
};
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* MY SECTIONS MODULE
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR INPUT
|
|
module.inputConstructor = api.CZRInput.extend({
|
|
initialize : function( name, options ) {
|
|
var input = this;
|
|
api.CZRInput.prototype.initialize.call( input, name, options );
|
|
input.isReady.then( function() {
|
|
input.renderUserSavedSections();
|
|
input.attachDomEvents();
|
|
});
|
|
},
|
|
|
|
// Ajax fetch the user section collection
|
|
// or return the already cached collection
|
|
getUserSavedSections : function() {
|
|
var _dfd_ = $.Deferred();
|
|
if ( !_.isEmpty( api.czr_sektions.userSavedSections ) ) {
|
|
_dfd_.resolve( api.czr_sektions.userSavedSections );
|
|
} else {
|
|
api.czr_sektions.getSavedSectionCollection().done( function( sec_collection ) {
|
|
_dfd_.resolve( sec_collection );
|
|
});
|
|
}
|
|
return _dfd_.promise();
|
|
},
|
|
|
|
renderUserSavedSections : function() {
|
|
var input = this,
|
|
html = '',
|
|
$wrapper = input.container.find('.sek-content-type-wrapper'),
|
|
creation_date = '';
|
|
// https://stackoverflow.com/questions/3552461/how-to-format-a-javascript-date
|
|
// formatDate = function(date) {
|
|
// var monthNames = [
|
|
// "January", "February", "March",
|
|
// "April", "May", "June", "July",
|
|
// "August", "September", "October",
|
|
// "November", "December"
|
|
// ];
|
|
|
|
// var day = date.getDate(),
|
|
// monthIndex = date.getMonth(),
|
|
// year = date.getFullYear(),
|
|
// hours = date.getHours(),
|
|
// minutes = date.getMinutes(),
|
|
// seconds = date.getSeconds();
|
|
|
|
// return [
|
|
// day,
|
|
// monthNames[monthIndex],
|
|
// year
|
|
// //[hours,minutes,seconds].join(':')
|
|
// ].join(' ');
|
|
// };
|
|
|
|
var _refreshUserSectionView = function( sec_collection ) {
|
|
// clean
|
|
$wrapper.find('.sek-user-section-wrapper').remove();
|
|
|
|
// Write
|
|
if ( _.isEmpty( sec_collection ) ) {
|
|
var _placeholdImgUrl = [ sektionsLocalizedData.baseUrl , '/assets/admin/img/save_section_notice.png', '?ver=' , sektionsLocalizedData.nimbleVersion ].join(''),
|
|
doc_url = 'https://docs.presscustomizr.com/article/417-how-to-save-and-reuse-sections-with-nimble-builder';
|
|
html = [
|
|
'<div class="sek-user-section-wrapper">',
|
|
'<img src="'+ _placeholdImgUrl +'" />',
|
|
'<br/><a href="'+ doc_url +'" target="_blank" rel="noreferrer nofollow">'+ doc_url +'</a>',
|
|
'</div>'
|
|
].join('');
|
|
$wrapper.append( html );
|
|
input.module.container.find('.czr-item-content .customize-control-title').html(sektionsLocalizedData.i18n['You did not save any section yet.']);
|
|
} else {
|
|
var _thumbUrl = [ sektionsLocalizedData.baseUrl , '/assets/admin/img/nb_sec_pholder.png', '?ver=' , sektionsLocalizedData.nimbleVersion ].join(''),
|
|
styleAttr = 'background: url(' + _thumbUrl + ') 50% 50% / cover no-repeat;';
|
|
_.each( sec_collection, function( secData, secKey ) {
|
|
// try { creation_date = formatDate( new Date( secData.creation_date.replace( /-/g, '/' ) ) ); } catch( er ) {
|
|
// api.errare( '::renderUserSavedSections => formatDate => error', er );
|
|
// }
|
|
if( !_.isEmpty( secData.description ) ) {
|
|
_titleAttr = [ secData.title, secData.last_modified_date, secData.description ].join(' | ');
|
|
} else {
|
|
_titleAttr = [ secData.title, secData.last_modified_date ].join(' | ');
|
|
}
|
|
html = [
|
|
'<div class="sek-user-section-wrapper">',
|
|
'<div draggable="true" data-sek-is-user-section="true" data-sek-section-type="content" data-sek-content-type="preset_section" data-sek-content-id="' + secKey +'" style="" title="' + secData.title + '">',
|
|
'<div class="sek-sec-thumb" style="'+ styleAttr +'"></div>',//<img src="'+ _thumbUrl +'"/>
|
|
'<div class="sek-overlay"></div>',
|
|
'<div class="sek-sec-info" title="'+ _titleAttr +'">',
|
|
'<h3 class="sec-title">' + secData.title + '</h3>',
|
|
'<p class="sec-date"><i>' + [ sektionsLocalizedData.i18n['Last modified'], ' : ', secData.last_modified_date ].join(' ') + '</i></p>',
|
|
'<p class="sec-desc">' + secData.description + '</p>',
|
|
'<i class="material-icons edit-user-sec" title="'+ sektionsLocalizedData.i18n['Edit this template'] +'">edit</i>',
|
|
'<i class="material-icons remove-user-sec" title="'+ sektionsLocalizedData.i18n['Remove this template'] +'">delete_forever</i>',
|
|
//'<div class="sek-overlay"></div>',
|
|
//'<div class="sek-saved-section-description">' + secData.description + '</div>',
|
|
//! _.isEmpty( creation_date ) ? ( '<div class="sek-saved-section-date"><i class="far fa-calendar-alt"></i> Created : ' + creation_date + '</div>' ) : '',
|
|
'</div>',
|
|
'</div>',
|
|
'</div>'
|
|
].join('');
|
|
$wrapper.append( html );
|
|
});//_.each
|
|
}
|
|
// Remove the loader previously added
|
|
$wrapper.find('.czr-css-loader').remove();
|
|
|
|
// Make section draggable now
|
|
api.czr_sektions.trigger( 'sek-refresh-dragzones', { type : 'preset_section', input_container : input.container } );
|
|
};//_refreshUserSectionView
|
|
|
|
// on input instantiation, render the collection
|
|
// print a loader
|
|
$wrapper.append('<div class="czr-css-loader czr-mr-loader" style="display:block"><div></div><div></div><div></div></div>');
|
|
input.getUserSavedSections().done( function( sec_collection ) {
|
|
_refreshUserSectionView( sec_collection );
|
|
});
|
|
|
|
// when the collection is modified : save, update, remove actions => NB refreshes the collection
|
|
api.czr_sektions.allSavedSections.bind( function( sec_collection ) {
|
|
_refreshUserSectionView( sec_collection );
|
|
});
|
|
},//renderUserSavedSections
|
|
|
|
// with delegation
|
|
attachDomEvents : function() {
|
|
// Attach events
|
|
this.container
|
|
.on('click', '.sek-sec-info .remove-user-sec', function(evt) {
|
|
evt.preventDefault();
|
|
var self = api.czr_sektions;
|
|
self.saveSectionDialogVisible(false);
|
|
// Close section dialog if it was open
|
|
//self.saveSectionDialogMode( 'hidden' );
|
|
var _focusOnRemoveCandidate = function( mode ) {
|
|
self.saveSectionDialogMode( 'remove' );
|
|
// self unbind
|
|
self.saveSectionDialogMode.unbind( _focusOnRemoveCandidate );
|
|
};
|
|
self.userSectionToRemove = $(this).closest("[data-sek-content-id]").data('sek-content-id');
|
|
self.saveSectionDialogMode.bind( _focusOnRemoveCandidate );
|
|
self.saveSectionDialogVisible(true);
|
|
})
|
|
.on('click', '.sek-sec-info .edit-user-sec', function(evt) {
|
|
evt.preventDefault();
|
|
var self = api.czr_sektions;
|
|
self.saveSectionDialogVisible(false);
|
|
//self.saveSectionDialogMode( 'hidden' );
|
|
var _focusOnEditCandidate = function( mode ) {
|
|
self.saveSectionDialogMode( 'edit' );
|
|
// self unbind
|
|
self.saveSectionDialogMode.unbind( _focusOnEditCandidate );
|
|
};
|
|
self.userSectionToEdit = $(this).closest("[data-sek-content-id]").data('sek-content-id');
|
|
self.saveSectionDialogMode.bind( _focusOnEditCandidate );
|
|
self.saveSectionDialogVisible(true);
|
|
});
|
|
}
|
|
});//module.inputConstructor
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
// module.embedded.then( function() {
|
|
// console.log('MODULE READY=> lets dance', module.container, module.container.find('.sek-ctrl-accordion-title') );
|
|
// });
|
|
},//initialize
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_my_sections_sec_picker_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_my_sections_sec_picker_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_my_sections_sec_picker_module' )
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
/* ------------------------------------------------------------------------- *
|
|
* MODULE OPTION SWITCHER
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_mod_option_switcher_module : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_mod_option_switcher_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_mod_option_switcher_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_anchor_module : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_anchor_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_anchor_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
//console.log('INITIALIZING SEKTION OPTIONS', id, options );
|
|
var module = this;
|
|
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'bg-apply-overlay' :
|
|
_.each( [ 'bg-color-overlay', 'bg-opacity-overlay' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return api.CZR_Helpers.isChecked( input() );
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'bg-parallax' :
|
|
_.each( [ 'bg-parallax-force', 'bg-scale', 'bg-repeat'] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'bg-parallax-force' :
|
|
bool = api.CZR_Helpers.isChecked( input() );
|
|
break;
|
|
case 'bg-repeat' :
|
|
case 'bg-scale' :
|
|
bool = !api.CZR_Helpers.isChecked( input() );
|
|
break;
|
|
}
|
|
return bool;
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
// uncheck fixed background if needed
|
|
input.bind( function( to ) {
|
|
if ( api.CZR_Helpers.isChecked( input() ) ) {
|
|
if ( api.CZR_Helpers.isChecked( item.czr_Input('bg-attachment')()) ) {
|
|
item.czr_Input('bg-attachment').container.find('input[type=checkbox]').trigger('click');
|
|
}
|
|
}
|
|
});
|
|
break;
|
|
case 'bg-attachment' :
|
|
// uncheck parallax if needed
|
|
input.bind( function( to ) {
|
|
if ( api.CZR_Helpers.isChecked( input() ) ) {
|
|
if ( api.CZR_Helpers.isChecked( item.czr_Input('bg-parallax')()) ) {
|
|
item.czr_Input('bg-parallax').container.find('input[type=checkbox]').trigger('click');
|
|
}
|
|
}
|
|
});
|
|
break;
|
|
case 'bg-use-video' :
|
|
_.each( [ 'bg-video', 'bg-video-loop', 'bg-video-delay-start', 'bg-video-on-mobile', 'bg-video-start-time', 'bg-video-end-time' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return api.CZR_Helpers.isChecked( input() );
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});//item.czr_Input.each
|
|
|
|
// Video background should only be available for sections and columns
|
|
if ( module.control && module.control.params && module.control.params.sek_registration_params ) {
|
|
if ( ! _.contains( [ 'section', 'column' ], module.control.params.sek_registration_params.level ) ) {
|
|
_.each( [ 'bg-use-video', 'bg-video', 'bg-video-loop', 'bg-video-on-mobile', 'bg-video-start-time', 'bg-video-end-time' ], function( _inputId_ ) {
|
|
item.czr_Input( _inputId_ ).visible( false );
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_bg_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_bg_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_bg_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_text_module : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_text_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_text_module' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
//console.log('INITIALIZING SEKTION OPTIONS', id, options );
|
|
var module = this;
|
|
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'border-type' :
|
|
_.each( [ 'borders' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'none' !== input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_border_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_border_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_border_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use-custom-breakpoint' :
|
|
_.each( [ 'custom-breakpoint', 'apply-to-all' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_breakpoint_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_breakpoint_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_breakpoint_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'height-type' :
|
|
_.each( [ 'custom-height', 'overflow_hidden' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'custom' === input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_height_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_height_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_height_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_visibility_module : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_visibility_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_visibility_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'width-type' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'custom-width', function() {
|
|
return 'custom' === input();
|
|
});
|
|
// api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'h_alignment', function() {
|
|
// return 'custom' === input();
|
|
// });
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_width_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_width_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_width_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR INPUT
|
|
module.inputConstructor = api.CZRInput.extend( module.CZRInputConstructor || {} );
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
// Constructor for the input
|
|
CZRInputConstructor : {
|
|
// initialize : function( name, options ) {
|
|
// var input = this;
|
|
// // Expand the editor when ready
|
|
// if ( 'detached_tinymce_editor' == input.type ) {
|
|
// input.isReady.then( function() {
|
|
// input.container.find('[data-czr-action="open-tinymce-editor"]').trigger('click');
|
|
// });
|
|
// }
|
|
// api.CZRInput.prototype.initialize.call( input, name, options );
|
|
// },
|
|
|
|
// Overrides the default range_simple method for the column width module
|
|
range_simple : function( params ) {
|
|
var input = this,
|
|
$wrapper = $('.sek-range-with-unit-picker-wrapper', input.container ),
|
|
$numberInput = $wrapper.find( 'input[type="number"]'),
|
|
$rangeInput = $wrapper.find( 'input[type="range"]');
|
|
|
|
// Get the moduleRegistration Params
|
|
var moduleRegistrationParams;
|
|
try{ moduleRegistrationParams = input.module.control.params.sek_registration_params; } catch( er ) {
|
|
api.errare('Error when getting the module registration params', er );
|
|
return;
|
|
}
|
|
if ( _.isUndefined( moduleRegistrationParams.level_id ) ) {
|
|
api.errare('Error : missing column id', er );
|
|
return;
|
|
}
|
|
|
|
// Get the column id and model,
|
|
// the parent section model
|
|
// and calculate the number of columns in the parent section
|
|
input.columnId = moduleRegistrationParams.level_id;
|
|
input.columnModel = $.extend( true, {}, api.czr_sektions.getLevelModel( input.columnId ) );
|
|
input.parentSectionModel = api.czr_sektions.getParentSectionFromColumnId( input.columnId );
|
|
|
|
if ( 'no_match' == input.columnModel ) {
|
|
api.errare( 'sek_level_width_column module => invalid column model' );
|
|
return;
|
|
}
|
|
if ( 'no_match' == input.parentSectionModel ) {
|
|
api.errare( 'sek_level_width_column module => invalid parent section model' );
|
|
return;
|
|
}
|
|
|
|
// Calculate the column number in the parent section
|
|
input.colNb = _.size( input.parentSectionModel.collection );
|
|
|
|
// Add the column id identifier, so we can communicate with it and update its value when the column gets resized from user
|
|
// @see update api setting, 'sek-resize-columns' case
|
|
$numberInput.attr('data-sek-width-range-column-id', input.columnId );
|
|
|
|
// For single column section, we don't want to display this module
|
|
if ( 1 === input.colNb ) {
|
|
input.container.html( ['<p>', sektionsLocalizedData.i18n['This is a single-column section with a width of 100%. You can act on the internal width of the parent section, or adjust padding and margin.']].join('') );
|
|
} else {
|
|
input.container.show();
|
|
}
|
|
|
|
// Always get the value from the model instead of relying on the setting val.
|
|
// => because the column width value is not only set from the customizer input, but also from the preview when resizing manually, this is an exception
|
|
var currentColumnModelValue = api.czr_sektions.getLevelModel( input.columnId ),
|
|
currentColumnWidthValueFromModel = '_not_set_',
|
|
columnWidthInPercent;
|
|
|
|
if ( 'no_match' == currentColumnModelValue ) {
|
|
api.errare( 'sek_level_width_column module => invalid column model' );
|
|
return;
|
|
}
|
|
|
|
var hasCustomWidth = currentColumnModelValue.options && currentColumnModelValue.options.width && currentColumnModelValue.options.width['custom-width'] && _.isNumber( +currentColumnModelValue.options.width['custom-width'] );
|
|
|
|
if ( hasCustomWidth ) {
|
|
currentColumnWidthValueFromModel = currentColumnModelValue.options.width['custom-width'];
|
|
}
|
|
// For retrocompat, use the former width property when exists.
|
|
// Deprecated in June 2019. See https://github.com/presscustomizr/nimble-builder/issues/279
|
|
else if ( ! hasCustomWidth && currentColumnModelValue.width && _.isNumber( +currentColumnModelValue.width ) ) {
|
|
currentColumnWidthValueFromModel = currentColumnModelValue.width;
|
|
}
|
|
|
|
|
|
if ( '_not_set_' !== currentColumnWidthValueFromModel ) {
|
|
columnWidthInPercent = currentColumnWidthValueFromModel;
|
|
}
|
|
// The default width is "_not_set_"
|
|
// @see php sek_get_module_params_for_sek_level_width_column()
|
|
// If not set, calculate the column width in percent based on the number of columns of the parent section
|
|
else if ( '_not_set_' === input() ) {
|
|
//$rangeInput.val( $numberInput.val() || 0 );
|
|
columnWidthInPercent = Math.floor( 100/input.colNb );
|
|
} else {
|
|
columnWidthInPercent = input();
|
|
}
|
|
|
|
// Cast to a number
|
|
columnWidthInPercent = +parseFloat(columnWidthInPercent).toFixed(3)*1;
|
|
|
|
// Make sure we have a number between 0 and 100
|
|
if ( ! _.isNumber( columnWidthInPercent ) || 100 < columnWidthInPercent || 0 > columnWidthInPercent ) {
|
|
api.errare( 'Error => invalid column width', columnWidthInPercent );
|
|
columnWidthInPercent = 50;
|
|
}
|
|
|
|
|
|
// synchronizes range input and number input
|
|
// number is the master => sets the input() val
|
|
$rangeInput.on('input', function( evt, params ) {
|
|
$numberInput.val( $(this).val() ).trigger('input', params );
|
|
});
|
|
// debounced to avoid a intermediate state of visual disorder of the columns
|
|
$numberInput.on('input', _.debounce(function( evt, params ) {
|
|
$rangeInput.val( $(this).val() );
|
|
if ( params && params.is_init )
|
|
return;
|
|
input( +parseFloat( $(this).val() ).toFixed(3) );
|
|
}, 300 ) );
|
|
|
|
// say it to the api, so we can regenerate the columns width for all columns.
|
|
// consistently with the action triggered when resizing the column manually
|
|
|
|
// Make sure that we don't react to the event sent when resizing column in update api setting, case 'sek-resize-columns'
|
|
// where we do $('body').find('[data-sek-width-range-column-id="'+ _candidate_.id +'"]').val( newWidthValue ).trigger('input', { is_resize_column_trigger : true } );
|
|
// => otherwise it will create an infinite loop
|
|
//
|
|
// Debounce to avoid server hammering
|
|
$numberInput.on( 'input', _.debounce( function( evt, params ) {
|
|
if ( params && ( params.is_init || params.is_resize_column_trigger ) )
|
|
return;
|
|
input.sayItToApi( $(this).val() );
|
|
}, 300 ) );
|
|
// trigger a change on init to sync the range input
|
|
$rangeInput.val( columnWidthInPercent ).trigger('input', { is_init : true } );
|
|
},
|
|
|
|
|
|
sayItToApi : function( columnWidthInPercent, _val ) {
|
|
var input = this;
|
|
// Get the sister column id
|
|
// 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.
|
|
var indexOfResizedColumn = _.findIndex( input.parentSectionModel.collection, {id : input.columnId} ),
|
|
isLastColumn = indexOfResizedColumn + 1 == input.colNb,
|
|
sisterColumnIndex = isLastColumn ? indexOfResizedColumn - 1 : indexOfResizedColumn + 1,
|
|
sisterColumnModel = _.find( input.parentSectionModel.collection, function( _val, _key ) { return sisterColumnIndex === _key; });
|
|
|
|
if ( 'no_match' === sisterColumnModel ) {
|
|
api.errare( 'sek_level_width_column module => invalid sister column model' );
|
|
}
|
|
|
|
api.previewer.trigger( 'sek-resize-columns', {
|
|
action : 'sek-resize-columns',
|
|
level : 'column',
|
|
in_sektion : input.parentSectionModel.id,
|
|
id : input.columnId,
|
|
|
|
resized_column : input.columnId,
|
|
sister_column : sisterColumnModel.id ,
|
|
|
|
resizedColumnWidthInPercent : columnWidthInPercent,
|
|
|
|
col_number : input.colNb
|
|
});
|
|
}
|
|
|
|
},//CZRTextEditorInputMths
|
|
// CZRItemConstructor : {
|
|
// //overrides the parent ready
|
|
// ready : function() {
|
|
// var item = this;
|
|
// //wait for the input collection to be populated,
|
|
// //and then set the input visibility dependencies
|
|
// item.inputCollection.bind( function( col ) {
|
|
// if( _.isEmpty( col ) )
|
|
// return;
|
|
// try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
// api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
// }
|
|
// });//item.inputCollection.bind()
|
|
|
|
// //fire the parent
|
|
// api.CZRItem.prototype.ready.call( item );
|
|
// },
|
|
|
|
|
|
// //Fired when the input collection is populated
|
|
// //At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
// setInputVisibilityDeps : function() {
|
|
// var item = this,
|
|
// module = item.module;
|
|
|
|
// //Internal item dependencies
|
|
// item.czr_Input.each( function( input ) {
|
|
// switch( input.id ) {
|
|
// case 'width-type' :
|
|
// api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'custom-width', function() {
|
|
// return 'custom' === input();
|
|
// });
|
|
// api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'h_alignment', function() {
|
|
// return 'custom' === input();
|
|
// });
|
|
// break;
|
|
// }
|
|
// });
|
|
// }
|
|
// }//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_width_column : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_width_column', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_width_column' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use-custom-outer-width' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'outer-section-width', function() {
|
|
return input();
|
|
});
|
|
break;
|
|
case 'use-custom-inner-width' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'inner-section-width', function() {
|
|
return input();
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_width_section : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_width_section', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_width_section' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_spacing_module : {
|
|
mthds : '',
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_spacing_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_spacing_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_spacing_module_for_columns : {
|
|
mthds : '',
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_spacing_module_for_columns', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_spacing_module_for_columns' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_cust_css_level : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_cust_css_level', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_cust_css_level' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
|
|
// _isChecked : function( v ) {
|
|
// return 0 !== v && '0' !== v && false !== v && 'off' !== v;
|
|
// },
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
// Oct 2020 for https://github.com/presscustomizr/nimble-builder-pro/issues/23
|
|
api.trigger('nb_setup_visibility_deps_for_animation_module', {
|
|
item : item,
|
|
module : module
|
|
});
|
|
}
|
|
},//CZRItemConstructor
|
|
|
|
};//Constructor
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_level_animation_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_level_animation_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_level_animation_module' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_template : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_template', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_template' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use-custom-outer-width' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'outer-section-width', function() {
|
|
return input();
|
|
});
|
|
break;
|
|
case 'use-custom-inner-width' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'inner-section-width', function() {
|
|
return input();
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};//Constructor
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_widths : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_widths', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_widths' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_custom_css : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_custom_css', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_custom_css' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_reset : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_reset', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_reset' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_performances : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_performances', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_performances' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_header_footer : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_header_footer', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_header_footer' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_revisions : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_revisions', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_revisions' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_local_imp_exp : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_local_imp_exp', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_local_imp_exp' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'links_underline' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'links_underline_hover', function() {
|
|
return !input();
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_text : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_text', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_text' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use-custom-breakpoint' :
|
|
_.each( [ 'global-custom-breakpoint', 'apply-to-all' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_breakpoint : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_breakpoint', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_breakpoint' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use-custom-outer-width' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'outer-section-width', function() {
|
|
return input();
|
|
});
|
|
break;
|
|
case 'use-custom-inner-width' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'inner-section-width', function() {
|
|
return input();
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_widths : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_widths', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_widths' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_performances : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_performances', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_performances' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_header_footer : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_header_footer', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_header_footer' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
//run the parent initialize
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'enable' :
|
|
_.each( [ 'public_key', 'private_key', 'badge', 'show_failure_message', 'failure_message', 'score' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'failure_message' :
|
|
bool = input() && item.czr_Input('show_failure_message')();
|
|
break;
|
|
default :
|
|
bool = input();
|
|
break;
|
|
}
|
|
return bool;
|
|
}); } catch( er ) {
|
|
api.errare( module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'show_failure_message' :
|
|
_.each( [ 'failure_message' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input() && item.czr_Input('enable')();
|
|
}); } catch( er ) {
|
|
api.errare( module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_recaptcha : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_recaptcha', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_recaptcha' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_revisions : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_revisions', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_revisions' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_reset : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_reset', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_reset' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_imp_exp : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_imp_exp', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_imp_exp' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_global_beta_features : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_global_beta_features', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_global_beta_features' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
sek_site_tmpl_pickers : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'sek_site_tmpl_pickers', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : true,
|
|
defaultItemModel : _.extend(
|
|
{ id : '', title : '' },
|
|
api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'sek_site_tmpl_pickers' )
|
|
)
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );/* ------------------------------------------------------------------------- *
|
|
* IMAGE MAIN SETTINGS
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
//SET THE CONTENT PICKER DEFAULT OPTIONS
|
|
//@see ::setupContentPicker()
|
|
module.bind( 'set_default_content_picker_options', function( params ) {
|
|
params.defaultContentPickerOption.defaultOption = {
|
|
'title' : '<span style="font-weight:bold">' + sektionsLocalizedData.i18n['Set a custom url'] + '</span>',
|
|
'type' : '',
|
|
'type_label' : '',
|
|
'object' : '',
|
|
'id' : '_custom_',
|
|
'url' : ''
|
|
};
|
|
return params;
|
|
});
|
|
},//initialize
|
|
|
|
|
|
// _isChecked : function( v ) {
|
|
// return 0 !== v && '0' !== v && false !== v && 'off' !== v;
|
|
// },
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
// case 'img' :
|
|
// api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'img-size', function() {
|
|
// return ! _.isEmpty( input()+'' ) && _.isNumber( input() );
|
|
// });
|
|
// break;
|
|
case 'link-to' :
|
|
_.each( [ 'link-pick-url', 'link-custom-url', 'link-target' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'link-custom-url' :
|
|
bool = 'url' === input() && '_custom_' == item.czr_Input('link-pick-url')().id;
|
|
break;
|
|
case 'link-pick-url' :
|
|
bool = 'url' === input();
|
|
break;
|
|
case 'link-target' :
|
|
bool = ! _.contains( [ 'no-link', 'img-lightbox' ], input() );
|
|
break;
|
|
}
|
|
return bool;
|
|
}); } catch( er ) {
|
|
api.errare( 'Image module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'link-pick-url' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'link-custom-url', function() {
|
|
return '_custom_' == input().id && 'url' == item.czr_Input('link-to')();
|
|
});
|
|
break;
|
|
case 'border-type' :
|
|
_.each( [ 'borders' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'none' !== input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'use_custom_width' :
|
|
_.each( [ 'custom_width' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Image module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'use_custom_height' :
|
|
_.each( [ 'custom_height' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Image module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'use_custom_title_attr' :
|
|
_.each( [ 'heading_title' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Image module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
|
|
// Sept 2020 for https://github.com/presscustomizr/nimble-builder-pro/issues/23
|
|
api.trigger('nb_setup_visibility_deps_for_img_module', {
|
|
item : item,
|
|
module : module
|
|
});
|
|
}
|
|
},//CZRItemConstructor
|
|
|
|
};//Constructor
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_image_main_settings_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_image_main_settings_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_image_main_settings_child' )
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* IMAGE BORDERS AND BORDER RADIUS
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
|
|
// _isChecked : function( v ) {
|
|
// return 0 !== v && '0' !== v && false !== v && 'off' !== v;
|
|
// },
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
// input controller instance == this
|
|
var scheduleVisibilityOfInputId = function( controlledInputId, visibilityCallBack ) {
|
|
//Fire on init
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
//React on change
|
|
this.bind( function( to ) {
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
});
|
|
};
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'border-type' :
|
|
_.each( [ 'borders' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'none' !== input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
},//CZRItemConstructor
|
|
|
|
};//Constructor
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_image_borders_corners_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_image_borders_corners_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_image_borders_corners_child' )
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
//console.log('INITIALIZING IMAGE MODULE', id, options );
|
|
var module = this;
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR INPUT
|
|
module.inputConstructor = api.CZRInput.extend( module.CZRTextEditorInputMths || {} );
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
// module.itemConstructor = api.CZRItem.extend( module.CZRSocialsItem || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
CZRTextEditorInputMths : {
|
|
initialize : function( name, options ) {
|
|
var input = this;
|
|
// Expand the editor when ready
|
|
if ( 'detached_tinymce_editor' == input.type ) {
|
|
input.isReady.then( function() {
|
|
input.container.find('[data-czr-action="open-tinymce-editor"]').trigger('click');
|
|
});
|
|
}
|
|
api.CZRInput.prototype.initialize.call( input, name, options );
|
|
}
|
|
},//CZRTextEditorInputMths
|
|
|
|
// CZRSocialsItem : { },//CZRSocialsItem
|
|
};//Constructor
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_tinymce_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_tinymce_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_tinymce_child' )
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_simple_html_module : {
|
|
//mthds : SimpleHtmlModuleConstructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_simple_html_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_simple_html_module' )
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//ICON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
//console.log('INITIALIZING IMAGE MODULE', id, options );
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
//SET THE CONTENT PICKER DEFAULT OPTIONS
|
|
//@see ::setupContentPicker()
|
|
module.bind( 'set_default_content_picker_options', function( params ) {
|
|
params.defaultContentPickerOption.defaultOption = {
|
|
'title' : '<span style="font-weight:bold">' + sektionsLocalizedData.i18n['Set a custom url'] + '</span>',
|
|
'type' : '',
|
|
'type_label' : '',
|
|
'object' : '',
|
|
'id' : '_custom_',
|
|
'url' : ''
|
|
};
|
|
return params;
|
|
});
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
/* Helpers */
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'link-to' :
|
|
_.each( [ 'link-pick-url', 'link-custom-url', 'link-target' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'link-custom-url' :
|
|
bool = 'url' == input() && '_custom_' == item.czr_Input('link-pick-url')().id;
|
|
break;
|
|
default :
|
|
bool = 'url' == input();
|
|
break;
|
|
}
|
|
return bool;
|
|
}); } catch( er ) {
|
|
api.errare( module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'link-pick-url' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'link-custom-url', function() {
|
|
return '_custom_' == input().id && 'url' == item.czr_Input('link-to')();
|
|
});
|
|
break;
|
|
case 'use_custom_color_on_hover' :
|
|
_.each( [ 'color_hover' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
},//CZRItemConstructor
|
|
|
|
};//Constructor
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_icon_settings_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_icon_settings_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_icon_settings_child' )
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//BUTTON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
// EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
// input controller instance == this
|
|
var scheduleVisibilityOfInputId = function( controlledInputId, visibilityCallBack ) {
|
|
//Fire on init
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
//React on change
|
|
this.bind( function( to ) {
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
});
|
|
};
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'border-type' :
|
|
_.each( [ 'borders' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'none' !== input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}//CZRItemConstructor
|
|
};// Constructor
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_icon_spacing_border_child: {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_icon_spacing_border_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_icon_spacing_border_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );/* ------------------------------------------------------------------------- *
|
|
* HEADING MAIN CHILD
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//HEADING MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
//SET THE CONTENT PICKER DEFAULT OPTIONS
|
|
//@see ::setupContentPicker()
|
|
module.bind( 'set_default_content_picker_options', function( params ) {
|
|
params.defaultContentPickerOption.defaultOption = {
|
|
'title' : '<span style="font-weight:bold">' + sektionsLocalizedData.i18n['Set a custom url'] + '</span>',
|
|
'type' : '',
|
|
'type_label' : '',
|
|
'object' : '',
|
|
'id' : '_custom_',
|
|
'url' : ''
|
|
};
|
|
return params;
|
|
});
|
|
},//initialize
|
|
|
|
// _isChecked : function( v ) {
|
|
// return 0 !== v && '0' !== v && false !== v && 'off' !== v;
|
|
// },
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'link-to' :
|
|
_.each( [ 'link-pick-url', 'link-custom-url', 'link-target' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'link-custom-url' :
|
|
bool = input() && '_custom_' == item.czr_Input('link-pick-url')().id;
|
|
break;
|
|
case 'link-pick-url' :
|
|
bool = input();
|
|
break;
|
|
case 'link-target' :
|
|
bool = input();
|
|
break;
|
|
}
|
|
return bool;
|
|
}); } catch( er ) {
|
|
api.errare( 'Heading module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'link-pick-url' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'link-custom-url', function() {
|
|
return '_custom_' == input().id && true === item.czr_Input('link-to')();
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}//setInputVisibilityDeps
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_heading_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_heading_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_heading_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* HEADING SPACING
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_heading_spacing_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_heading_spacing_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_heading_spacing_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_divider_module : {
|
|
//mthds : DividerModuleConstructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_divider_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_divider_module' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_spacer_module : {
|
|
//mthds : ModuleConstructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_spacer_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_spacer_module' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_map_module : {
|
|
//mthds : ModuleConstructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_map_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_map_module' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
/* ------------------------------------------------------------------------- *
|
|
* QUOTE DESIGN
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//BUTTON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRButtonItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRButtonItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'quote_design' :
|
|
_.each( [ 'border_width_css', 'border_color_css' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'border-before' == input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Quote module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
_.each( [ 'icon_color_css', 'icon_size_css' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'quote-icon-before' == input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Quote module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_quote_design_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_quote_design_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_quote_design_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* QUOTE CONTENT
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_quote_quote_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_quote_quote_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_quote_quote_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* CITE CONTENT
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_quote_cite_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_quote_cite_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_quote_cite_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
/* ------------------------------------------------------------------------- *
|
|
* BUTTON CONTENT
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//BUTTON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
//SET THE CONTENT PICKER DEFAULT OPTIONS
|
|
//@see ::setupContentPicker()
|
|
module.bind( 'set_default_content_picker_options', function( params ) {
|
|
params.defaultContentPickerOption.defaultOption = {
|
|
'title' : '<span style="font-weight:bold">' + sektionsLocalizedData.i18n['Set a custom url'] + '</span>',
|
|
'type' : '',
|
|
'type_label' : '',
|
|
'object' : '',
|
|
'id' : '_custom_',
|
|
'url' : ''
|
|
};
|
|
return params;
|
|
});
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'link-to' :
|
|
_.each( [ 'link-pick-url', 'link-custom-url', 'link-target' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'link-custom-url' :
|
|
bool = 'url' == input() && '_custom_' == item.czr_Input('link-pick-url')().id;
|
|
break;
|
|
default :
|
|
bool = 'url' == input();
|
|
break;
|
|
}
|
|
return bool;
|
|
}); } catch( er ) {
|
|
api.errare( 'Button module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'link-pick-url' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'link-custom-url', function() {
|
|
return '_custom_' == input().id && 'url' == item.czr_Input('link-to')();
|
|
});
|
|
break;
|
|
case 'icon' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'icon-side', function() {
|
|
return !_.isEmpty( input() );
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_btn_content_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_btn_content_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_btn_content_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* BUTTON DESIGN
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//BUTTON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
// input controller instance == this
|
|
var scheduleVisibilityOfInputId = function( controlledInputId, visibilityCallBack ) {
|
|
//Fire on init
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
//React on change
|
|
this.bind( function( to ) {
|
|
item.czr_Input( controlledInputId ).visible( visibilityCallBack() );
|
|
});
|
|
};
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use_custom_bg_color_on_hover' :
|
|
_.each( [ 'bg_color_hover' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Button module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'border-type' :
|
|
_.each( [ 'borders' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'none' !== input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'use_box_shadow' :
|
|
_.each( [ 'push_effect' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Button module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'width-type' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'custom-width', function() {
|
|
return 'custom' === input();
|
|
});
|
|
_.each( [ 'custom-width', 'h_inner_align_css' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'custom' === input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Button module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'height-type' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'custom-height', function() {
|
|
return 'custom' === input();
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_btn_design_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_btn_design_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_btn_design_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use_current_query' :
|
|
_.each( [ 'replace_query', 'post_number', 'posts_per_page', 'include_sticky', 'categories', 'must_have_all_cats', 'order_by'] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
_replace_query = item.czr_Input('replace_query')(),
|
|
_display_pagination = item.czr_Input('display_pagination')();
|
|
|
|
switch( _inputId_ ) {
|
|
case 'replace_query' :
|
|
bool = input();
|
|
break;
|
|
case 'post_number' :
|
|
bool = ( !input() && !_display_pagination ) || ( input() && _replace_query && !_display_pagination );
|
|
break;
|
|
case 'posts_per_page' :
|
|
bool = ( !input() && _display_pagination ) || ( input() && _replace_query && _display_pagination );
|
|
break;
|
|
case 'include_sticky' :
|
|
case 'categories' :
|
|
case 'must_have_all_cats' :
|
|
case 'order_by' :
|
|
bool = !input() || ( input() && item.czr_Input('replace_query')() );
|
|
break;
|
|
}
|
|
return bool;
|
|
});
|
|
});
|
|
break;
|
|
case 'replace_query' :
|
|
_.each( [ 'post_number', 'posts_per_page', 'include_sticky', 'categories', 'must_have_all_cats', 'order_by'] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var _use_current_query = item.czr_Input('use_current_query')(),
|
|
_display_pagination = item.czr_Input('display_pagination')(),
|
|
bool = false;
|
|
|
|
switch( _inputId_ ) {
|
|
case 'post_number' :
|
|
bool = (!_use_current_query && !_display_pagination ) || ( input() && !_display_pagination );
|
|
break;
|
|
case 'posts_per_page' :
|
|
bool = (!_use_current_query && _display_pagination ) || ( input() && _display_pagination );
|
|
break;
|
|
case 'include_sticky' :
|
|
case 'categories' :
|
|
case 'must_have_all_cats' :
|
|
case 'order_by' :
|
|
bool = !_use_current_query || input();
|
|
break;
|
|
}
|
|
return bool;
|
|
});
|
|
});
|
|
break;
|
|
case 'layout' :
|
|
_.each( [ 'columns', 'img_column_width', 'has_tablet_breakpoint', 'has_mobile_breakpoint' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'columns' :
|
|
bool = 'grid' === input();
|
|
break;
|
|
case 'has_tablet_breakpoint' :
|
|
case 'has_mobile_breakpoint' :
|
|
case 'img_column_width' :
|
|
bool = 'list' === input();
|
|
break;
|
|
}
|
|
return bool;
|
|
});
|
|
});
|
|
break;
|
|
case 'categories' :
|
|
_.each( [ 'must_have_all_cats' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var input_val = input();
|
|
return _.isArray( input_val ) && input_val.length>1;
|
|
});
|
|
});
|
|
break;
|
|
case 'display_pagination' :
|
|
_.each( [ 'posts_per_page', 'post_number' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var _replace_query = item.czr_Input('replace_query')(),
|
|
_use_current_query = item.czr_Input('use_current_query')();
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'posts_per_page' :
|
|
bool = input() && !_use_current_query || (input() && _use_current_query && _replace_query);
|
|
break;
|
|
case 'post_number' :
|
|
bool = !input() && !_use_current_query || (!input() && _use_current_query && _replace_query);
|
|
break;
|
|
}
|
|
return bool;
|
|
});
|
|
});
|
|
break;
|
|
case 'custom_grid_spaces' :
|
|
_.each( [ 'column_gap', 'row_gap' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
});
|
|
});
|
|
break;
|
|
case 'show_excerpt' :
|
|
_.each( [ 'excerpt_length' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_post_grid_main_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_post_grid_main_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_post_grid_main_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'show_thumb' :
|
|
_.each( [ 'img_size', 'img_has_custom_height', 'img_height', 'border_radius_css', 'use_post_thumb_placeholder' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'img_height' :
|
|
bool = input() && item.czr_Input('img_has_custom_height')();
|
|
break;
|
|
default :
|
|
bool = input();
|
|
break;
|
|
}
|
|
return bool;
|
|
});
|
|
});
|
|
break;
|
|
case 'img_has_custom_height' :
|
|
_.each( [ 'img_height' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input() && item.czr_Input('show_thumb')();
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_post_grid_thumb_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_post_grid_thumb_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_post_grid_thumb_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_post_grid_metas_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_post_grid_metas_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_post_grid_metas_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_post_grid_fonts_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_post_grid_fonts_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_post_grid_fonts_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );/* ------------------------------------------------------------------------- *
|
|
* MENU CONTENT
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_menu_content_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_menu_content_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_menu_content_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* MENU OPTIONS FOR MOBILE DEVICES
|
|
/* ------------------------------------------------------------------------- */
|
|
//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_menu_mobile_options : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_menu_mobile_options', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_menu_mobile_options' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//BUTTON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'show_name_field' :
|
|
_.each( [ 'name_field_label', 'name_field_required' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( input.module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'show_subject_field' :
|
|
_.each( [ 'subject_field_label', 'subject_field_required' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( input.module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'show_message_field' :
|
|
_.each( [ 'message_field_label', 'message_field_required' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( input.module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'show_privacy_field' :
|
|
_.each( [ 'privacy_field_label', 'privacy_field_required' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( input.module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'link-pick-url' :
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'link-custom-url', function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( input.module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_simple_form_fields_child: {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_simple_form_fields_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_simple_form_fields_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//BUTTON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'border-type' :
|
|
_.each( [ 'borders' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'none' !== input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_simple_form_design_child: {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_simple_form_design_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_simple_form_design_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//BUTTON MODULE
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use_custom_bg_color_on_hover' :
|
|
_.each( [ 'bg_color_hover' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( input.module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'border-type' :
|
|
_.each( [ 'borders' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'none' !== input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'use_box_shadow' :
|
|
_.each( [ 'push_effect' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( input.module.module_type + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_simple_form_button_child: {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_simple_form_button_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_simple_form_button_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_simple_form_fonts_child: {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_simple_form_fonts_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_simple_form_fonts_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_simple_form_submission_child: {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_simple_form_submission_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_simple_form_submission_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_font_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_font_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_font_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
// fixes https://github.com/presscustomizr/nimble-builder/issues/426
|
|
// 'nimble-set-select-input-options' is triggered in api.czr_sektions.setupSelectInput
|
|
module.bind('nimble-set-select-input-options', function( filtrable ) {
|
|
filtrable.params = sektionsLocalizedData.registeredWidgetZones;
|
|
});
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
};
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_widget_area_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_widget_area_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_widget_area_module' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
//console.log('INITIALIZING FP MODULE', id, options );
|
|
var module = this;
|
|
|
|
|
|
module.crudModulePart = 'nimble-crud-module-part';
|
|
module.rudItemPart = 'nimble-rud-item-part';
|
|
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// module.isReady.then( function() {
|
|
// if ( _.isUndefined( module.preItem ) )
|
|
// return;
|
|
// //specific update for the item preModel on social-icon change
|
|
// module.preItem.bind( function( to, from ) {
|
|
// if ( ! _.has(to, 'icon') )
|
|
// return;
|
|
// if ( _.isEqual( to['icon'], from['icon'] ) )
|
|
// return;
|
|
// module.updateItemModel( module.preItem, true );
|
|
// });
|
|
// });
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
|
|
// overrides the default fmk method which generates a too long id for each item, like : "czr_social_icons_settings_child_2"
|
|
// this method generates a uniq GUID id for each item
|
|
generateItemId : function() {
|
|
return api.czr_sektions.guid();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
_makeItemsSortable : function(obj) {
|
|
if ( wp.media.isTouchDevice || ! $.fn.sortable )
|
|
return;
|
|
var module = this;
|
|
$( '.' + module.control.css_attr.items_wrapper, module.container ).sortable( {
|
|
handle: '.' + module.control.css_attr.item_sort_handle,
|
|
start: function() {},
|
|
update: function( event, ui ) {
|
|
var _sortedCollectionReact = function() {
|
|
if ( _.has(module, 'preItem') ) {
|
|
module.preItemExpanded.set(false);
|
|
}
|
|
|
|
module.closeAllItems().closeRemoveDialogs();
|
|
// var refreshPreview = function() {
|
|
// api.previewer.refresh();
|
|
// };
|
|
// //refreshes the preview frame :
|
|
// //1) only needed if transport is postMessage, because is triggered by wp otherwise
|
|
// //2) only needed when : add, remove, sort item(s).
|
|
// //var isItemUpdate = ( _.size(from) == _.size(to) ) && ! _.isEmpty( _.difference(from, to) );
|
|
// if ( 'postMessage' == api(module.control.id).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// refreshPreview = _.debounce( refreshPreview, 500 );//500ms are enough
|
|
// refreshPreview();
|
|
// }
|
|
|
|
module.trigger( 'item-collection-sorted' );
|
|
};
|
|
module._getSortedDOMItemCollection()
|
|
.done( function( _collection_ ) {
|
|
module.itemCollection.set( _collection_ );
|
|
})
|
|
.then( function() {
|
|
_sortedCollectionReact();
|
|
});
|
|
//refreshes the preview frame, only if the associated setting is a postMessage transport one, with no partial refresh
|
|
// if ( 'postMessage' == api( module.control.id ).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// _.delay( function() { api.previewer.refresh(); }, 100 );
|
|
// }
|
|
}//update
|
|
}
|
|
);
|
|
},//_makeItemsSortable
|
|
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
// //update the item model on social-icon change
|
|
// item.bind('icon:changed', function(){
|
|
// //item.module.updateItemModel( item );
|
|
// });
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
|
|
_buildTitle : function( title, icon, color ) {
|
|
var item = this,
|
|
module = item.module;
|
|
title = title || ( 'string' === typeof(icon) ? api.CZR_Helpers.capitalize( icon.replace( 'fa-', '') ) : '' );
|
|
title = api.CZR_Helpers.truncate(title, 20);
|
|
color = color || module.defaultSocialColor;
|
|
|
|
return '<div><span class="' + icon + '" style="color:' + color + '"></span> ' + title + '</div>';
|
|
},
|
|
|
|
//overrides the default parent method by a custom one
|
|
//at this stage, the model passed in the obj is up to date
|
|
writeItemViewTitle : function( model ) {
|
|
var item = this,
|
|
module = item.module,
|
|
_model = model || item(),
|
|
_title = ( _model['icon'] ? _model['icon'] : '' ).replace('fa-', '').replace('envelope', 'email').replace( 'far', '').replace( 'fab', '').replace( 'fas', '');
|
|
|
|
$( '.' + module.control.css_attr.item_title , item.container ).html(
|
|
item._buildTitle( _title, _model['icon'], _model['color_css'] )
|
|
);
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use_custom_color_on_hover' :
|
|
_.each( [ 'social_color_hover' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( 'Featured pages module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
|
|
// Overrides the default fmk method in order to disable the remove dialog box
|
|
toggleRemoveAlert : function() {
|
|
this.removeItem();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
//fired on click dom event
|
|
//for dynamic multi input modules
|
|
//@return void()
|
|
//@param params : { dom_el : {}, dom_event : {}, event : {}, model {} }
|
|
removeItem : function( params ) {
|
|
params = params || {};
|
|
var item = this,
|
|
module = this.module,
|
|
_new_collection = _.clone( module.itemCollection() );
|
|
|
|
//hook here
|
|
module.trigger('pre_item_dom_remove', item() );
|
|
|
|
//destroy the Item DOM el
|
|
item._destroyView();
|
|
|
|
//new collection
|
|
//say it
|
|
_new_collection = _.without( _new_collection, _.findWhere( _new_collection, {id: item.id }) );
|
|
module.itemCollection.set( _new_collection );
|
|
//hook here
|
|
module.trigger('pre_item_api_remove', item() );
|
|
|
|
var _item_ = $.extend( true, {}, item() );
|
|
|
|
// <REMOVE THE ITEM FROM THE COLLECTION>
|
|
module.czr_Item.remove( item.id );
|
|
// </REMOVE THE ITEM FROM THE COLLECTION>
|
|
|
|
//refresh the preview frame (only needed if transport is postMessage && has no partial refresh set )
|
|
//must be a dom event not triggered
|
|
//otherwise we are in the init collection case where the items are fetched and added from the setting in initialize
|
|
if ( 'postMessage' == api(module.control.id).transport && _.has( params, 'dom_event') && ! _.has( params.dom_event, 'isTrigger' ) && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// api.previewer.refresh().done( function() {
|
|
// _dfd_.resolve();
|
|
// });
|
|
// It would be better to wait for the refresh promise
|
|
// The following approach to bind and unbind when refreshing the preview is similar to the one coded in module::addItem()
|
|
var triggerEventWhenPreviewerReady = function() {
|
|
api.previewer.unbind( 'ready', triggerEventWhenPreviewerReady );
|
|
module.trigger( 'item-removed', _item_ );
|
|
};
|
|
api.previewer.bind( 'ready', triggerEventWhenPreviewerReady );
|
|
//api.previewer.refresh();
|
|
} else {
|
|
module.trigger( 'item-removed', _item_ );
|
|
module.control.trigger( 'item-removed', _item_ );
|
|
}
|
|
|
|
},
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_social_icons_settings_child : {
|
|
mthds : Constructor,
|
|
crud : true,//api.czr_sektions.getRegisteredModuleProperty( 'czr_social_icons_settings_child', 'is_crud' ),
|
|
hasPreItem : false,//a crud module has a pre item by default
|
|
refresh_on_add_item : false,// the preview is refreshed on item add
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_social_icons_settings_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_social_icons_settings_child' ),
|
|
items_are_clonable : true
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* SOCIAL ICONS OPTIONS
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_social_icons_style_child : {
|
|
//mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_social_icons_style_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_social_icons_style_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
|
|
module.crudModulePart = 'nimble-crud-module-part';
|
|
module.rudItemPart = 'nimble-rud-item-part';
|
|
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// module.isReady.then( function() {
|
|
// if ( _.isUndefined( module.preItem ) )
|
|
// return;
|
|
// //specific update for the item preModel on social-icon change
|
|
// module.preItem.bind( function( to, from ) {
|
|
// if ( ! _.has(to, 'icon') )
|
|
// return;
|
|
// if ( _.isEqual( to['icon'], from['icon'] ) )
|
|
// return;
|
|
// module.updateItemModel( module.preItem, true );
|
|
// });
|
|
// });
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
//SET THE CONTENT PICKER DEFAULT OPTIONS ( FOR PRO)
|
|
//@see ::setupContentPicker()
|
|
module.bind( 'set_default_content_picker_options', function( params ) {
|
|
params.defaultContentPickerOption.defaultOption = {
|
|
'title' : '<span style="font-weight:bold">' + sektionsLocalizedData.i18n['Set a custom url'] + '</span>',
|
|
'type' : '',
|
|
'type_label' : '',
|
|
'object' : '',
|
|
'id' : '_custom_',
|
|
'url' : ''
|
|
};
|
|
return params;
|
|
});
|
|
},//initialize
|
|
|
|
|
|
// overrides the default fmk method which generates a too long id for each item, like : "czr_img_slider_collection_child_2"
|
|
// this method generates a uniq GUID id for each item
|
|
generateItemId : function() {
|
|
return api.czr_sektions.guid();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
_makeItemsSortable : function(obj) {
|
|
if ( wp.media.isTouchDevice || ! $.fn.sortable )
|
|
return;
|
|
var module = this;
|
|
$( '.' + module.control.css_attr.items_wrapper, module.container ).sortable( {
|
|
handle: '.' + module.control.css_attr.item_sort_handle,
|
|
start: function() {},
|
|
update: function( event, ui ) {
|
|
var _sortedCollectionReact = function() {
|
|
if ( _.has(module, 'preItem') ) {
|
|
module.preItemExpanded.set(false);
|
|
}
|
|
|
|
module.closeAllItems().closeRemoveDialogs();
|
|
// var refreshPreview = function() {
|
|
// api.previewer.refresh();
|
|
// };
|
|
// //refreshes the preview frame :
|
|
// //1) only needed if transport is postMessage, because is triggered by wp otherwise
|
|
// //2) only needed when : add, remove, sort item(s).
|
|
// //var isItemUpdate = ( _.size(from) == _.size(to) ) && ! _.isEmpty( _.difference(from, to) );
|
|
// if ( 'postMessage' == api(module.control.id).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// refreshPreview = _.debounce( refreshPreview, 500 );//500ms are enough
|
|
// refreshPreview();
|
|
// }
|
|
|
|
module.trigger( 'item-collection-sorted' );
|
|
};
|
|
module._getSortedDOMItemCollection()
|
|
.done( function( _collection_ ) {
|
|
module.itemCollection.set( _collection_ );
|
|
})
|
|
.then( function() {
|
|
_sortedCollectionReact();
|
|
});
|
|
//refreshes the preview frame, only if the associated setting is a postMessage transport one, with no partial refresh
|
|
// if ( 'postMessage' == api( module.control.id ).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// _.delay( function() { api.previewer.refresh(); }, 100 );
|
|
// }
|
|
}//update
|
|
}
|
|
);
|
|
},//_makeItemsSortable
|
|
|
|
|
|
// Overrides core FMK method
|
|
// introduced in July 2019 to solve the problem of the default image for the items
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/479
|
|
getPreItem : function() {
|
|
var rawStartingValue = api.czr_sektions.getRegisteredModuleProperty( 'czr_img_slider_collection_child', 'starting_value' ),
|
|
preItemValue = $.extend( true, {}, this.preItem() );//create a new detached clones object
|
|
|
|
if ( _.isObject( rawStartingValue ) ) {
|
|
var startingValue = $.extend( true, {}, rawStartingValue );//create a new detached clones object
|
|
return $.extend( preItemValue, startingValue );
|
|
}
|
|
|
|
return this.preItem();
|
|
},
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
// //update the item model on social-icon change
|
|
// item.bind('icon:changed', function(){
|
|
// //item.module.updateItemModel( item );
|
|
// });
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
|
|
// FOCUS ON CURRENTLY EXPANDED / EDITED ITEM
|
|
var requestFocusToPreview = function() {
|
|
api.previewer.send( 'sek-item-focus', {
|
|
control_id : item.module.control.id,
|
|
item_id : item.id,
|
|
item_value : item()
|
|
});
|
|
};
|
|
// when the item get expanded
|
|
item.viewState.callbacks.add( function( to, from ) {
|
|
if ( 'expanded' === to ) {
|
|
requestFocusToPreview();
|
|
}
|
|
});
|
|
|
|
// when the item value is changed
|
|
item.callbacks.add( requestFocusToPreview );
|
|
|
|
// when the module requests a focus after a preview update
|
|
item.bind('sek-request-item-focus-in-preview', requestFocusToPreview );
|
|
},
|
|
|
|
|
|
|
|
//overrides the default parent method by a custom one
|
|
//at this stage, the model passed in the obj is up to date
|
|
writeItemViewTitle : function( model, data ) {
|
|
var item = this,
|
|
index = 1,
|
|
module = item.module,
|
|
_model = model || item(),
|
|
_title = '',
|
|
_slideBg = '',
|
|
_src = 'not_set',
|
|
_areDataSet = ! _.isUndefined( data ) && _.isObject( data );
|
|
|
|
//When shall we update the item title ?
|
|
//=> when the slide title or the thumbnail have been updated
|
|
//=> on module model initialized
|
|
if ( _areDataSet && data.input_changed && ! _.contains( [ 'img', 'text_content' ], data.input_changed ) )
|
|
return;
|
|
|
|
//set title with index
|
|
if ( ! _.isEmpty( _model.title ) ) {
|
|
_title = _model.title;
|
|
} else {
|
|
//find the current item index in the collection
|
|
var _index = _.findIndex( module.itemCollection(), function( _itm ) {
|
|
return _itm.id === item.id;
|
|
});
|
|
_index = _.isUndefined( _index ) ? index : _index + 1;
|
|
}
|
|
|
|
//if the slide title is set, use it
|
|
_title = api.CZR_Helpers.truncate( _title, 15 );
|
|
|
|
if ( _model['img'] ) {
|
|
_slideBg = _model['img'];
|
|
if ( _.isString( _model['img'] ) ) {
|
|
// if the img is already an url, typically the default image
|
|
if ( -1 !== _model['img'].indexOf( 'http' ) ) {
|
|
_slideBg = _model['img'];
|
|
// else, cast to an int
|
|
} else {
|
|
_slideBg = parseInt( _model['img'], 10 );
|
|
}
|
|
}
|
|
}
|
|
|
|
var _getThumbSrc = function() {
|
|
return $.Deferred( function() {
|
|
var dfd = this;
|
|
if ( _.isUndefined( _slideBg ) || _.isEmpty( '' + _slideBg ) ) { //<= always cast to a string when using _.isEmpty
|
|
dfd.resolve( '' );
|
|
}
|
|
//try to set the default src
|
|
else if ( _.isString( _slideBg ) && -1 !== _slideBg.indexOf( 'http' ) ) {
|
|
dfd.resolve( _slideBg );
|
|
} else {
|
|
wp.media.attachment( _slideBg ).fetch()
|
|
.always( function() {
|
|
var attachment = this;
|
|
if ( _.isObject( attachment ) && _.has( attachment, 'attributes' ) && _.has( attachment.attributes, 'sizes' ) ) {
|
|
var _sizes = attachment.get('sizes');
|
|
if ( _sizes && _.isObject( _sizes ) ) {
|
|
// loop on the various possible image sizes, starting with thumbnail, the smallest.
|
|
// as soon as an available size is found, use it as src
|
|
_.each( ['thumbnail', 'medium', 'large', 'full' ], function( _val, _k ) {
|
|
if ( 'not_set' === _src && _sizes[_val] && _.isObject( _sizes[_val] ) && _sizes[_val].url ) {
|
|
_src = _sizes[_val].url;
|
|
}
|
|
});
|
|
}
|
|
dfd.resolve( _src );
|
|
}
|
|
});
|
|
}
|
|
}).promise();
|
|
};
|
|
|
|
|
|
var $slideTitleEl = $( '.' + module.control.css_attr.item_title , item.container ).find('.sek-slide-title'),
|
|
$slideThumbEl = $( '.' + module.control.css_attr.item_title , item.container ).find( '.sek-slide-thumb');
|
|
|
|
//TITLE
|
|
//always write the title
|
|
var _text = _model['text_content'] ? _model['text_content'] : '';
|
|
// Strip all html tags and keep only first characters
|
|
_text = $("<div>").html(_text).text();
|
|
_text = _text.substring(0,60);
|
|
if ( 1 > $slideTitleEl.length ) {
|
|
//remove the default item title
|
|
$( '.' + module.control.css_attr.item_title , item.container ).html( '' );
|
|
//write the new one
|
|
$( '.' + module.control.css_attr.item_title , item.container ).append( $( '<div/>',
|
|
{
|
|
class : 'sek-slide-title',
|
|
html : _text
|
|
}
|
|
) );
|
|
} else {
|
|
$slideTitleEl.html( _text );
|
|
}
|
|
|
|
//THUMB
|
|
//When shall we append the item thumb ?
|
|
//=>IF the sek-slide-thumb element is not set
|
|
//=>OR in the case where data have been provided and the input_changed is 'img'
|
|
//=>OR if no data is provided ( we are in the initialize phase )
|
|
var _isBgChange = _areDataSet && data.input_changed && 'img' === data.input_changed;
|
|
|
|
var _getThumbHtml = function( src ) {
|
|
return ( _.isEmpty( '' + src ) || 'not_set' === src ) ? '' : '<img src="' + src + '" width="32" alt="' + _title + '" />';
|
|
};
|
|
|
|
$( '.' + module.control.css_attr.item_title, item.container ).css('padding', '0 4px');
|
|
|
|
|
|
if ( 1 > $slideThumbEl.length ) {
|
|
_getThumbSrc().done( function( src ) {
|
|
$( '.' + module.control.css_attr.item_title, item.container ).prepend( $('<div/>',
|
|
{
|
|
class : 'sek-slide-thumb',
|
|
html : _getThumbHtml( src )
|
|
}
|
|
));
|
|
});
|
|
} else if ( _isBgChange || ! _areDataSet ) {
|
|
_getThumbSrc().done( function( src ) {
|
|
$slideThumbEl.html( _getThumbHtml( src ) );
|
|
});
|
|
}
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
// link-to and pick url are FOR PRO
|
|
case 'link-to' :
|
|
_.each( [ 'link-pick-url', 'link-custom-url', 'link-target' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'link-custom-url' :
|
|
bool = 'url' === input() && '_custom_' == item.czr_Input('link-pick-url')().id;
|
|
break;
|
|
case 'link-pick-url' :
|
|
bool = 'url' === input();
|
|
break;
|
|
case 'link-target' :
|
|
bool = ! _.contains( [ 'no-link'], input() );
|
|
break;
|
|
}
|
|
return bool;
|
|
}); } catch( er ) {
|
|
api.errare( 'Image module => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'link-pick-url' :
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, 'link-custom-url', function() {
|
|
return '_custom_' == input().id && 'url' == item.czr_Input('link-to')();
|
|
});
|
|
break;
|
|
|
|
case 'apply-overlay' :
|
|
_.each( [ 'color-overlay', 'opacity-overlay' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return api.CZR_Helpers.isChecked( input() );
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'enable_text' :
|
|
_.each( [ 'text_content', 'font_family_css', 'font_size_css', 'line_height_css', 'color_css', 'h_alignment_css', 'v_alignment', 'spacing_css'] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return api.CZR_Helpers.isChecked( input() );
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'apply_overlay' :
|
|
_.each( [ 'color-overlay', 'opacity-overlay' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return api.CZR_Helpers.isChecked( input() );
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
|
|
// Overrides the default fmk method in order to disable the remove dialog box
|
|
toggleRemoveAlert : function() {
|
|
this.removeItem();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
//fired on click dom event
|
|
//for dynamic multi input modules
|
|
//@return void()
|
|
//@param params : { dom_el : {}, dom_event : {}, event : {}, model {} }
|
|
removeItem : function( params ) {
|
|
params = params || {};
|
|
var item = this,
|
|
module = this.module,
|
|
_new_collection = _.clone( module.itemCollection() );
|
|
|
|
//hook here
|
|
module.trigger('pre_item_dom_remove', item() );
|
|
|
|
//destroy the Item DOM el
|
|
item._destroyView();
|
|
|
|
//new collection
|
|
//say it
|
|
_new_collection = _.without( _new_collection, _.findWhere( _new_collection, {id: item.id }) );
|
|
module.itemCollection.set( _new_collection );
|
|
//hook here
|
|
module.trigger('pre_item_api_remove', item() );
|
|
|
|
var _item_ = $.extend( true, {}, item() );
|
|
|
|
// <REMOVE THE ITEM FROM THE COLLECTION>
|
|
module.czr_Item.remove( item.id );
|
|
// </REMOVE THE ITEM FROM THE COLLECTION>
|
|
|
|
//refresh the preview frame (only needed if transport is postMessage && has no partial refresh set )
|
|
//must be a dom event not triggered
|
|
//otherwise we are in the init collection case where the items are fetched and added from the setting in initialize
|
|
if ( 'postMessage' == api(module.control.id).transport && _.has( params, 'dom_event') && ! _.has( params.dom_event, 'isTrigger' ) && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// api.previewer.refresh().done( function() {
|
|
// _dfd_.resolve();
|
|
// });
|
|
// It would be better to wait for the refresh promise
|
|
// The following approach to bind and unbind when refreshing the preview is similar to the one coded in module::addItem()
|
|
var triggerEventWhenPreviewerReady = function() {
|
|
api.previewer.unbind( 'ready', triggerEventWhenPreviewerReady );
|
|
module.trigger( 'item-removed', _item_ );
|
|
};
|
|
api.previewer.bind( 'ready', triggerEventWhenPreviewerReady );
|
|
//api.previewer.refresh();
|
|
} else {
|
|
module.trigger( 'item-removed', _item_ );
|
|
module.control.trigger( 'item-removed', _item_ );
|
|
}
|
|
|
|
},
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_img_slider_collection_child : {
|
|
mthds : Constructor,
|
|
crud : true,//api.czr_sektions.getRegisteredModuleProperty( 'czr_img_slider_collection_child', 'is_crud' ),
|
|
hasPreItem : false,//a crud module has a pre item by default
|
|
refresh_on_add_item : false,// the preview is refreshed on item add
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_img_slider_collection_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_img_slider_collection_child' ),
|
|
items_are_clonable : true
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* SLIDER OPTIONS
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
|
|
module.crudModulePart = 'nimble-crud-module-part';
|
|
module.rudItemPart = 'nimble-rud-item-part';
|
|
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// module.isReady.then( function() {
|
|
// if ( _.isUndefined( module.preItem ) )
|
|
// return;
|
|
// //specific update for the item preModel on social-icon change
|
|
// module.preItem.bind( function( to, from ) {
|
|
// if ( ! _.has(to, 'icon') )
|
|
// return;
|
|
// if ( _.isEqual( to['icon'], from['icon'] ) )
|
|
// return;
|
|
// module.updateItemModel( module.preItem, true );
|
|
// });
|
|
// });
|
|
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'height-type' :
|
|
_.each( [ 'custom-height' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return 'custom' === input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
case 'autoplay' :
|
|
_.each( [ 'autoplay_delay', 'pause_on_hover' ] , function(_inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return api.CZR_Helpers.isChecked( input() );
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_img_slider_opts_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_img_slider_opts_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_img_slider_opts_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
|
|
module.crudModulePart = 'nimble-crud-module-part';
|
|
module.rudItemPart = 'nimble-rud-item-part';
|
|
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
// module.isReady.then( function() {
|
|
// if ( _.isUndefined( module.preItem ) )
|
|
// return;
|
|
// //specific update for the item preModel on social-icon change
|
|
// module.preItem.bind( function( to, from ) {
|
|
// if ( ! _.has(to, 'icon') )
|
|
// return;
|
|
// if ( _.isEqual( to['icon'], from['icon'] ) )
|
|
// return;
|
|
// module.updateItemModel( module.preItem, true );
|
|
// });
|
|
// });
|
|
},//initialize
|
|
|
|
|
|
// overrides the default fmk method which generates a too long id for each item, like : "czr_accordion_collection_child_2"
|
|
// this method generates a uniq GUID id for each item
|
|
generateItemId : function() {
|
|
return api.czr_sektions.guid();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
_makeItemsSortable : function(obj) {
|
|
if ( wp.media.isTouchDevice || ! $.fn.sortable )
|
|
return;
|
|
var module = this;
|
|
$( '.' + module.control.css_attr.items_wrapper, module.container ).sortable( {
|
|
handle: '.' + module.control.css_attr.item_sort_handle,
|
|
start: function() {},
|
|
update: function( event, ui ) {
|
|
var _sortedCollectionReact = function() {
|
|
if ( _.has(module, 'preItem') ) {
|
|
module.preItemExpanded.set(false);
|
|
}
|
|
|
|
module.closeAllItems().closeRemoveDialogs();
|
|
// var refreshPreview = function() {
|
|
// api.previewer.refresh();
|
|
// };
|
|
// //refreshes the preview frame :
|
|
// //1) only needed if transport is postMessage, because is triggered by wp otherwise
|
|
// //2) only needed when : add, remove, sort item(s).
|
|
// //var isItemUpdate = ( _.size(from) == _.size(to) ) && ! _.isEmpty( _.difference(from, to) );
|
|
// if ( 'postMessage' == api(module.control.id).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// refreshPreview = _.debounce( refreshPreview, 500 );//500ms are enough
|
|
// refreshPreview();
|
|
// }
|
|
|
|
module.trigger( 'item-collection-sorted' );
|
|
};
|
|
module._getSortedDOMItemCollection()
|
|
.done( function( _collection_ ) {
|
|
module.itemCollection.set( _collection_ );
|
|
})
|
|
.then( function() {
|
|
_sortedCollectionReact();
|
|
});
|
|
//refreshes the preview frame, only if the associated setting is a postMessage transport one, with no partial refresh
|
|
// if ( 'postMessage' == api( module.control.id ).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// _.delay( function() { api.previewer.refresh(); }, 100 );
|
|
// }
|
|
}//update
|
|
}
|
|
);
|
|
},//_makeItemsSortable
|
|
|
|
|
|
// Overrides core FMK method
|
|
// introduced in July 2019 to solve the problem of the default image for the items
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/479
|
|
getPreItem : function() {
|
|
var rawStartingValue = api.czr_sektions.getRegisteredModuleProperty( 'czr_accordion_collection_child', 'starting_value' ),
|
|
preItemValue = $.extend( true, {}, this.preItem() );//create a new detached clones object
|
|
|
|
if ( _.isObject( rawStartingValue ) ) {
|
|
var startingValue = $.extend( true, {}, rawStartingValue );//create a new detached clones object
|
|
return $.extend( preItemValue, startingValue );
|
|
}
|
|
|
|
return this.preItem();
|
|
},
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
// item.inputCollection.bind( function( col ) {
|
|
// if( _.isEmpty( col ) )
|
|
// return;
|
|
// try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
// api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
// }
|
|
// });//item.inputCollection.bind()
|
|
|
|
// //update the item model on social-icon change
|
|
// item.bind('icon:changed', function(){
|
|
// //item.module.updateItemModel( item );
|
|
// });
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
|
|
// FOCUS ON CURRENTLY EXPANDED / EDITED ITEM
|
|
var requestFocusToPreview = function() {
|
|
api.previewer.send( 'sek-item-focus', {
|
|
control_id : item.module.control.id,
|
|
item_id : item.id,
|
|
item_value : item()
|
|
});
|
|
};
|
|
// when the item get expanded
|
|
item.viewState.callbacks.add( function( to, from ) {
|
|
if ( 'expanded' === to ) {
|
|
requestFocusToPreview();
|
|
}
|
|
});
|
|
|
|
// when the item value is changed
|
|
item.callbacks.add( requestFocusToPreview );
|
|
|
|
// when the module requests a focus after a preview update
|
|
item.bind('sek-request-item-focus-in-preview', requestFocusToPreview );
|
|
|
|
// rewriteItemTitle when item are sorted, so that the placeholder title ( based in item DOM index ) get refreshed
|
|
item.module.bind('item-collection-sorted', function() {
|
|
item.writeItemViewTitle( item(), { input_changed : 'title_text'} );
|
|
});
|
|
},
|
|
|
|
|
|
|
|
//overrides the default parent method by a custom one
|
|
//at this stage, the model passed in the obj is up to date
|
|
writeItemViewTitle : function( model, data ) {
|
|
var item = this,
|
|
index = 1,
|
|
module = item.module,
|
|
_model = model || item(),
|
|
_title = '',
|
|
_areDataSet = ! _.isUndefined( data ) && _.isObject( data );
|
|
|
|
//When shall we update the item title ?
|
|
//=> when the title or the thumbnail have been updated
|
|
//=> on module model initialized
|
|
if ( _areDataSet && data.input_changed && ! _.contains( [ 'title_text' ], data.input_changed ) )
|
|
return;
|
|
|
|
//set title with index
|
|
if ( ! _.isEmpty( _model.title ) ) {
|
|
_title = _model.title;
|
|
} else {
|
|
//find the current item index in the collection
|
|
var _index = _.findIndex( module.itemCollection(), function( _itm ) {
|
|
return _itm.id === item.id;
|
|
});
|
|
_index = _.isUndefined( _index ) ? index : _index + 1;
|
|
}
|
|
|
|
//if the title is set, use it
|
|
_title = api.CZR_Helpers.truncate( _title, 25 );
|
|
|
|
var $itemTitleEl = $( '.' + module.control.css_attr.item_title , item.container ).find('.sek-accord-title');
|
|
|
|
//TITLE
|
|
//always write the title
|
|
var _text = _model['title_text'] ? _model['title_text'] : '';
|
|
// Strip all html tags and keep only first characters
|
|
_text = $("<div>").html(_text).text();
|
|
|
|
// placeholder text, consistent with the php one in tmpl/accordion_tmpl.php
|
|
var item_index = item.module.container.find( '.czr-items-wrapper > li').index( item.container );
|
|
_text = _.isEmpty( _text ) ? sektionsLocalizedData.i18n['Accordion title'] + ' #' + ( +item_index+1 ) : _text;
|
|
|
|
_text = _text.substring(0,60);
|
|
if ( 1 > $itemTitleEl.length ) {
|
|
//remove the default item title
|
|
$( '.' + module.control.css_attr.item_title , item.container ).html( '' );
|
|
//write the new one
|
|
$( '.' + module.control.css_attr.item_title , item.container ).append( $( '<div/>',
|
|
{
|
|
class : 'sek-accord-title',
|
|
html : _text
|
|
}
|
|
) );
|
|
} else {
|
|
$itemTitleEl.html( _text );
|
|
}
|
|
},//writeItemViewTitle
|
|
|
|
|
|
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
//setInputVisibilityDeps : function() {},
|
|
|
|
// Overrides the default fmk method in order to disable the remove dialog box
|
|
toggleRemoveAlert : function() {
|
|
this.removeItem();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
//fired on click dom event
|
|
//for dynamic multi input modules
|
|
//@return void()
|
|
//@param params : { dom_el : {}, dom_event : {}, event : {}, model {} }
|
|
removeItem : function( params ) {
|
|
params = params || {};
|
|
var item = this,
|
|
module = this.module,
|
|
_new_collection = _.clone( module.itemCollection() );
|
|
|
|
//hook here
|
|
module.trigger('pre_item_dom_remove', item() );
|
|
|
|
//destroy the Item DOM el
|
|
item._destroyView();
|
|
|
|
//new collection
|
|
//say it
|
|
_new_collection = _.without( _new_collection, _.findWhere( _new_collection, {id: item.id }) );
|
|
module.itemCollection.set( _new_collection );
|
|
//hook here
|
|
module.trigger('pre_item_api_remove', item() );
|
|
|
|
var _item_ = $.extend( true, {}, item() );
|
|
|
|
// <REMOVE THE ITEM FROM THE COLLECTION>
|
|
module.czr_Item.remove( item.id );
|
|
// </REMOVE THE ITEM FROM THE COLLECTION>
|
|
|
|
//refresh the preview frame (only needed if transport is postMessage && has no partial refresh set )
|
|
//must be a dom event not triggered
|
|
//otherwise we are in the init collection case where the items are fetched and added from the setting in initialize
|
|
if ( 'postMessage' == api(module.control.id).transport && _.has( params, 'dom_event') && ! _.has( params.dom_event, 'isTrigger' ) && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// api.previewer.refresh().done( function() {
|
|
// _dfd_.resolve();
|
|
// });
|
|
// It would be better to wait for the refresh promise
|
|
// The following approach to bind and unbind when refreshing the preview is similar to the one coded in module::addItem()
|
|
var triggerEventWhenPreviewerReady = function() {
|
|
api.previewer.unbind( 'ready', triggerEventWhenPreviewerReady );
|
|
module.trigger( 'item-removed', _item_ );
|
|
};
|
|
api.previewer.bind( 'ready', triggerEventWhenPreviewerReady );
|
|
//api.previewer.refresh();
|
|
} else {
|
|
module.trigger( 'item-removed', _item_ );
|
|
module.control.trigger( 'item-removed', _item_ );
|
|
}
|
|
|
|
},
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_accordion_collection_child : {
|
|
mthds : Constructor,
|
|
crud : true,//api.czr_sektions.getRegisteredModuleProperty( 'czr_accordion_collection_child', 'is_crud' ),
|
|
hasPreItem : false,//a crud module has a pre item by default
|
|
refresh_on_add_item : false,// the preview is refreshed on item add
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_accordion_collection_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_accordion_collection_child' ),
|
|
items_are_clonable : true
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* ACCORDION OPTIONS
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
|
|
module.crudModulePart = 'nimble-crud-module-part';
|
|
module.rudItemPart = 'nimble-rud-item-part';
|
|
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// module.isReady.then( function() {
|
|
// if ( _.isUndefined( module.preItem ) )
|
|
// return;
|
|
// //specific update for the item preModel on social-icon change
|
|
// module.preItem.bind( function( to, from ) {
|
|
// if ( ! _.has(to, 'icon') )
|
|
// return;
|
|
// if ( _.isEqual( to['icon'], from['icon'] ) )
|
|
// return;
|
|
// module.updateItemModel( module.preItem, true );
|
|
// });
|
|
// });
|
|
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
// item.inputCollection.bind( function( col ) {
|
|
// if( _.isEmpty( col ) )
|
|
// return;
|
|
// try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
// api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
// }
|
|
// });//item.inputCollection.bind()
|
|
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
//setInputVisibilityDeps : function() {},
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_accordion_opts_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_accordion_opts_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_accordion_opts_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
//EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRButtonItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
},//initialize
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRButtonItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'use_flex' :
|
|
_.each( [ 'h_alignment_css' ] , function( _inputId_ ) {
|
|
try { api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
return input();
|
|
}); } catch( er ) {
|
|
api.errare( module.id + ' => error in setInputVisibilityDeps', er );
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_shortcode_module : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_shortcode_module', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_shortcode_module' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ );//global sektionsLocalizedData, serverControlParams
|
|
//extends api.CZRDynModule
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
|
|
module.crudModulePart = 'nimble-crud-module-part';
|
|
module.rudItemPart = 'nimble-rud-item-part';
|
|
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
|
|
// module.isReady.then( function() {
|
|
// if ( _.isUndefined( module.preItem ) )
|
|
// return;
|
|
// //specific update for the item preModel on social-icon change
|
|
// module.preItem.bind( function( to, from ) {
|
|
// if ( ! _.has(to, 'icon') )
|
|
// return;
|
|
// if ( _.isEqual( to['icon'], from['icon'] ) )
|
|
// return;
|
|
// module.updateItemModel( module.preItem, true );
|
|
// });
|
|
// });
|
|
},//initialize
|
|
|
|
|
|
// overrides the default fmk method which generates a too long id for each item, like : "czr_gallery_collection_child_2"
|
|
// this method generates a uniq GUID id for each item
|
|
generateItemId : function() {
|
|
return api.czr_sektions.guid();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
_makeItemsSortable : function(obj) {
|
|
if ( wp.media.isTouchDevice || ! $.fn.sortable )
|
|
return;
|
|
var module = this;
|
|
$( '.' + module.control.css_attr.items_wrapper, module.container ).sortable( {
|
|
handle: '.' + module.control.css_attr.item_sort_handle,
|
|
start: function() {},
|
|
update: function( event, ui ) {
|
|
var _sortedCollectionReact = function() {
|
|
if ( _.has(module, 'preItem') ) {
|
|
module.preItemExpanded.set(false);
|
|
}
|
|
|
|
module.closeAllItems().closeRemoveDialogs();
|
|
// var refreshPreview = function() {
|
|
// api.previewer.refresh();
|
|
// };
|
|
// //refreshes the preview frame :
|
|
// //1) only needed if transport is postMessage, because is triggered by wp otherwise
|
|
// //2) only needed when : add, remove, sort item(s).
|
|
// //var isItemUpdate = ( _.size(from) == _.size(to) ) && ! _.isEmpty( _.difference(from, to) );
|
|
// if ( 'postMessage' == api(module.control.id).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// refreshPreview = _.debounce( refreshPreview, 500 );//500ms are enough
|
|
// refreshPreview();
|
|
// }
|
|
|
|
module.trigger( 'item-collection-sorted' );
|
|
};
|
|
module._getSortedDOMItemCollection()
|
|
.done( function( _collection_ ) {
|
|
module.itemCollection.set( _collection_ );
|
|
})
|
|
.then( function() {
|
|
_sortedCollectionReact();
|
|
});
|
|
//refreshes the preview frame, only if the associated setting is a postMessage transport one, with no partial refresh
|
|
// if ( 'postMessage' == api( module.control.id ).transport && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// _.delay( function() { api.previewer.refresh(); }, 100 );
|
|
// }
|
|
}//update
|
|
}
|
|
);
|
|
},//_makeItemsSortable
|
|
|
|
|
|
// Overrides core FMK method
|
|
// introduced in July 2019 to solve the problem of the default image for the items
|
|
// @see https://github.com/presscustomizr/nimble-builder/issues/479
|
|
getPreItem : function() {
|
|
var rawStartingValue = api.czr_sektions.getRegisteredModuleProperty( 'czr_gallery_collection_child', 'starting_value' ),
|
|
preItemValue = $.extend( true, {}, this.preItem() );//create a new detached clones object
|
|
|
|
if ( _.isObject( rawStartingValue ) ) {
|
|
var startingValue = $.extend( true, {}, rawStartingValue );//create a new detached clones object
|
|
return $.extend( preItemValue, startingValue );
|
|
}
|
|
|
|
return this.preItem();
|
|
},
|
|
|
|
//////////////////////////////////////////////////////////
|
|
/// ITEM CONSTRUCTOR
|
|
//////////////////////////////////////////
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
// //wait for the input collection to be populated,
|
|
// //and then set the input visibility dependencies
|
|
// item.inputCollection.bind( function( col ) {
|
|
// if( _.isEmpty( col ) )
|
|
// return;
|
|
// try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
// api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
// }
|
|
// });//item.inputCollection.bind()
|
|
|
|
// //update the item model on social-icon change
|
|
// item.bind('icon:changed', function(){
|
|
// //item.module.updateItemModel( item );
|
|
// });
|
|
//fire the parent
|
|
api.CZRItem.prototype.ready.call( item );
|
|
|
|
// FOCUS ON CURRENTLY EXPANDED / EDITED ITEM
|
|
var requestFocusToPreview = function() {
|
|
api.previewer.send( 'sek-item-focus', {
|
|
control_id : item.module.control.id,
|
|
item_id : item.id,
|
|
item_value : item()
|
|
});
|
|
};
|
|
// when the item get expanded
|
|
item.viewState.callbacks.add( function( to, from ) {
|
|
if ( 'expanded' === to ) {
|
|
requestFocusToPreview();
|
|
}
|
|
});
|
|
|
|
// when the item value is changed
|
|
item.callbacks.add( requestFocusToPreview );
|
|
|
|
// when the module requests a focus after a preview update
|
|
item.bind('sek-request-item-focus-in-preview', requestFocusToPreview );
|
|
|
|
// // rewriteItemTitle when item are sorted, so that the placeholder title ( based in item DOM index ) get refreshed
|
|
// item.module.bind('item-collection-sorted', function() {
|
|
// item.writeItemViewTitle( item(), { input_changed : 'title_text'} );
|
|
// });
|
|
},
|
|
|
|
|
|
|
|
//overrides the default parent method by a custom one
|
|
//at this stage, the model passed in the obj is up to date
|
|
writeItemViewTitle : function( model, data ) {
|
|
var item = this,
|
|
index = 1,
|
|
module = item.module,
|
|
_model = model || item(),
|
|
_title = '',
|
|
_slideBg = '',
|
|
_src = 'not_set',
|
|
_areDataSet = ! _.isUndefined( data ) && _.isObject( data );
|
|
|
|
//When shall we update the item title ?
|
|
//=> when the slide title or the thumbnail have been updated
|
|
//=> on module model initialized
|
|
if ( _areDataSet && data.input_changed && ! _.contains( [ 'img' ], data.input_changed ) )
|
|
return;
|
|
|
|
//set title with index
|
|
if ( ! _.isEmpty( _model.title ) ) {
|
|
_title = _model.title;
|
|
} else {
|
|
//find the current item index in the collection
|
|
var _index = _.findIndex( module.itemCollection(), function( _itm ) {
|
|
return _itm.id === item.id;
|
|
});
|
|
_index = _.isUndefined( _index ) ? index : _index + 1;
|
|
}
|
|
|
|
//if the slide title is set, use it
|
|
_title = api.CZR_Helpers.truncate( _title, 25 );
|
|
|
|
if ( _model['img'] ) {
|
|
_slideBg = _model['img'];
|
|
if ( _.isString( _model['img'] ) ) {
|
|
// if the img is already an url, typically the default image
|
|
if ( -1 !== _model['img'].indexOf( 'http' ) ) {
|
|
_slideBg = _model['img'];
|
|
// else, cast to an int
|
|
} else {
|
|
_slideBg = parseInt( _model['img'], 10 );
|
|
}
|
|
}
|
|
}
|
|
|
|
var _getThumbSrc = function() {
|
|
return $.Deferred( function() {
|
|
var dfd = this;
|
|
if ( _.isUndefined( _slideBg ) || _.isEmpty( '' + _slideBg ) ) { //<= always cast to a string when using _.isEmpty
|
|
dfd.resolve( '' );
|
|
}
|
|
//try to set the default src
|
|
else if ( _.isString( _slideBg ) && -1 !== _slideBg.indexOf( 'http' ) ) {
|
|
dfd.resolve( _slideBg );
|
|
} else {
|
|
wp.media.attachment( _slideBg ).fetch()
|
|
.always( function() {
|
|
var attachment = this;
|
|
if ( _.isObject( attachment ) && _.has( attachment, 'attributes' ) && _.has( attachment.attributes, 'sizes' ) ) {
|
|
var _sizes = attachment.get('sizes');
|
|
if ( _sizes && _.isObject( _sizes ) ) {
|
|
// loop on the various possible image sizes, starting with thumbnail, the smallest.
|
|
// as soon as an available size is found, use it as src
|
|
_.each( ['thumbnail', 'medium', 'large', 'full' ], function( _val, _k ) {
|
|
if ( 'not_set' === _src && _sizes[_val] && _.isObject( _sizes[_val] ) && _sizes[_val].url ) {
|
|
_src = _sizes[_val].url;
|
|
}
|
|
});
|
|
}
|
|
dfd.resolve( _src );
|
|
}
|
|
});
|
|
}
|
|
}).promise();
|
|
};
|
|
|
|
var //$itemTitleEl = $( '.' + module.control.css_attr.item_title , item.container ).find('.sek-accord-title'),
|
|
$itemThumbEl = $( '.' + module.control.css_attr.item_title , item.container ).find( '.sek-slide-thumb');
|
|
|
|
//THUMB
|
|
//When shall we append the item thumb ?
|
|
//=>IF the sek-slide-thumb element is not set
|
|
//=>OR in the case where data have been provided and the input_changed is 'img'
|
|
//=>OR if no data is provided ( we are in the initialize phase )
|
|
var _isBgChange = _areDataSet && data.input_changed && 'img' === data.input_changed;
|
|
|
|
var _getThumbHtml = function( src ) {
|
|
return ( _.isEmpty( '' + src ) || 'not_set' === src ) ? '' : '<img src="' + src + '" width="32" alt="' + _title + '" />';
|
|
};
|
|
|
|
$( '.' + module.control.css_attr.item_title, item.container ).css('padding', '0 4px');
|
|
|
|
|
|
if ( 1 > $itemThumbEl.length ) {
|
|
_getThumbSrc().done( function( src ) {
|
|
$( '.' + module.control.css_attr.item_title, item.container ).prepend( $('<div/>',
|
|
{
|
|
class : 'sek-slide-thumb',
|
|
html : _getThumbHtml( src )
|
|
}
|
|
));
|
|
});
|
|
} else if ( _isBgChange || ! _areDataSet ) {
|
|
_getThumbSrc().done( function( src ) {
|
|
$itemThumbEl.html( _getThumbHtml( src ) );
|
|
});
|
|
}
|
|
|
|
//TITLE
|
|
// //always write the title
|
|
// var _text = _model['title_text'] ? _model['title_text'] : '';
|
|
// // Strip all html tags and keep only first characters
|
|
// _text = $("<div>").html(_text).text();
|
|
|
|
// // placeholder text, consistent with the php one in tmpl/gallery_tmpl.php
|
|
// var item_index = item.module.container.find( '.czr-items-wrapper > li').index( item.container );
|
|
// _text = _.isEmpty( _text ) ? sektionsLocalizedData.i18n['Accordion title'] + ' #' + ( +item_index+1 ) : _text;
|
|
|
|
// _text = _text.substring(0,60);
|
|
// if ( 1 > $itemTitleEl.length ) {
|
|
// //remove the default item title
|
|
// $( '.' + module.control.css_attr.item_title , item.container ).html( '' );
|
|
// //write the new one
|
|
// $( '.' + module.control.css_attr.item_title , item.container ).append( $( '<div/>',
|
|
// {
|
|
// class : 'sek-accord-title',
|
|
// html : _text
|
|
// }
|
|
// ) );
|
|
// } else {
|
|
// $itemTitleEl.html( _text );
|
|
// }
|
|
},//writeItemViewTitle
|
|
|
|
// Overrides the default fmk method in order to disable the remove dialog box
|
|
toggleRemoveAlert : function() {
|
|
this.removeItem();
|
|
},
|
|
|
|
// Overrides the default fmk method, to disable the default preview refresh
|
|
//fired on click dom event
|
|
//for dynamic multi input modules
|
|
//@return void()
|
|
//@param params : { dom_el : {}, dom_event : {}, event : {}, model {} }
|
|
removeItem : function( params ) {
|
|
params = params || {};
|
|
var item = this,
|
|
module = this.module,
|
|
_new_collection = _.clone( module.itemCollection() );
|
|
|
|
//hook here
|
|
module.trigger('pre_item_dom_remove', item() );
|
|
|
|
//destroy the Item DOM el
|
|
item._destroyView();
|
|
|
|
//new collection
|
|
//say it
|
|
_new_collection = _.without( _new_collection, _.findWhere( _new_collection, {id: item.id }) );
|
|
module.itemCollection.set( _new_collection );
|
|
//hook here
|
|
module.trigger('pre_item_api_remove', item() );
|
|
|
|
var _item_ = $.extend( true, {}, item() );
|
|
|
|
// <REMOVE THE ITEM FROM THE COLLECTION>
|
|
module.czr_Item.remove( item.id );
|
|
// </REMOVE THE ITEM FROM THE COLLECTION>
|
|
|
|
//refresh the preview frame (only needed if transport is postMessage && has no partial refresh set )
|
|
//must be a dom event not triggered
|
|
//otherwise we are in the init collection case where the items are fetched and added from the setting in initialize
|
|
if ( 'postMessage' == api(module.control.id).transport && _.has( params, 'dom_event') && ! _.has( params.dom_event, 'isTrigger' ) && ! api.CZR_Helpers.hasPartRefresh( module.control.id ) ) {
|
|
// api.previewer.refresh().done( function() {
|
|
// _dfd_.resolve();
|
|
// });
|
|
// It would be better to wait for the refresh promise
|
|
// The following approach to bind and unbind when refreshing the preview is similar to the one coded in module::addItem()
|
|
var triggerEventWhenPreviewerReady = function() {
|
|
api.previewer.unbind( 'ready', triggerEventWhenPreviewerReady );
|
|
module.trigger( 'item-removed', _item_ );
|
|
};
|
|
api.previewer.bind( 'ready', triggerEventWhenPreviewerReady );
|
|
//api.previewer.refresh();
|
|
} else {
|
|
module.trigger( 'item-removed', _item_ );
|
|
module.control.trigger( 'item-removed', _item_ );
|
|
}
|
|
}
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_gallery_collection_child : {
|
|
mthds : Constructor,
|
|
crud : true,//api.czr_sektions.getRegisteredModuleProperty( 'czr_gallery_collection_child', 'is_crud' ),
|
|
hasPreItem : false,//a crud module has a pre item by default
|
|
refresh_on_add_item : false,// the preview is refreshed on item add
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_gallery_collection_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_gallery_collection_child' ),
|
|
items_are_clonable : true
|
|
},
|
|
});
|
|
})( wp.customize , jQuery, _ );
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------- *
|
|
* GALLERY OPTIONS
|
|
/* ------------------------------------------------------------------------- */
|
|
( function ( api, $, _ ) {
|
|
var Constructor = {
|
|
initialize: function( id, options ) {
|
|
var module = this;
|
|
|
|
|
|
module.crudModulePart = 'nimble-crud-module-part';
|
|
module.rudItemPart = 'nimble-rud-item-part';
|
|
|
|
// //EXTEND THE DEFAULT CONSTRUCTORS FOR MONOMODEL
|
|
module.itemConstructor = api.CZRItem.extend( module.CZRItemConstructor || {} );
|
|
|
|
// module.isReady.then( function() {
|
|
// if ( _.isUndefined( module.preItem ) )
|
|
// return;
|
|
// //specific update for the item preModel on social-icon change
|
|
// module.preItem.bind( function( to, from ) {
|
|
// if ( ! _.has(to, 'icon') )
|
|
// return;
|
|
// if ( _.isEqual( to['icon'], from['icon'] ) )
|
|
// return;
|
|
// module.updateItemModel( module.preItem, true );
|
|
// });
|
|
// });
|
|
//SET THE CONTENT PICKER DEFAULT OPTIONS
|
|
//@see ::setupContentPicker()
|
|
// module.bind( 'set_default_content_picker_options', function( params ) {
|
|
// params.defaultContentPickerOption.defaultOption = {
|
|
// 'title' : '<span style="font-weight:bold">' + sektionsLocalizedData.i18n['Set a custom url'] + '</span>',
|
|
// 'type' : '',
|
|
// 'type_label' : '',
|
|
// 'object' : '',
|
|
// 'id' : '_custom_',
|
|
// 'url' : ''
|
|
// };
|
|
// return params;
|
|
// });
|
|
|
|
// run the parent initialize
|
|
// Note : must be always invoked always after the input / item class extension
|
|
// Otherwise the constructor might be extended too early and not taken into account. @see https://github.com/presscustomizr/nimble-builder/issues/37
|
|
api.CZRDynModule.prototype.initialize.call( module, id, options );
|
|
},//initialize
|
|
|
|
CZRItemConstructor : {
|
|
//overrides the parent ready
|
|
ready : function() {
|
|
var item = this;
|
|
//wait for the input collection to be populated,
|
|
//and then set the input visibility dependencies
|
|
item.inputCollection.bind( function( col ) {
|
|
if( _.isEmpty( col ) )
|
|
return;
|
|
try { item.setInputVisibilityDeps(); } catch( er ) {
|
|
api.errorLog( 'item.setInputVisibilityDeps() : ' + er );
|
|
}
|
|
});//item.inputCollection.bind()
|
|
|
|
api.CZRItem.prototype.ready.call( item );
|
|
},
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
|
|
|
|
|
|
//Fired when the input collection is populated
|
|
//At this point, the inputs are all ready (input.isReady.state() === 'resolved') and we can use their visible Value ( set to true by default )
|
|
setInputVisibilityDeps : function() {
|
|
var item = this,
|
|
module = item.module;
|
|
|
|
//Internal item dependencies
|
|
item.czr_Input.each( function( input ) {
|
|
switch( input.id ) {
|
|
case 'link-to' :
|
|
_.each( [ 'link-target' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'link-target' :
|
|
bool = ! _.contains( [ 'no-link', 'img-lightbox' ], input() );
|
|
break;
|
|
}
|
|
return bool;
|
|
});
|
|
});
|
|
break;
|
|
case 'custom-rows-columns' :
|
|
_.each( [ 'column_width', 'raw_height' ] , function( _inputId_ ) {
|
|
api.czr_sektions.scheduleVisibilityOfInputId.call( input, _inputId_, function() {
|
|
var _is_masonry_on = item.czr_Input.has('masonry_on') && item.czr_Input('masonry_on')(),
|
|
_is_auto_fill_on = item.czr_Input.has('auto_fill') && item.czr_Input('auto_fill')();
|
|
|
|
var bool = false;
|
|
switch( _inputId_ ) {
|
|
case 'column_width' :
|
|
bool = input() && !_is_masonry_on && !_is_auto_fill_on;
|
|
break;
|
|
case 'raw_height' :
|
|
bool = input() && !_is_masonry_on;
|
|
break;
|
|
}
|
|
return bool;
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
},//CZRItemConstructor
|
|
};//Constructor
|
|
//provides a description of each module
|
|
//=> will determine :
|
|
//1) how to initialize the module model. If not crud, then the initial item(s) model shall be provided
|
|
//2) which js template(s) to use : if crud, the module template shall include the add new and pre-item elements.
|
|
// , if crud, the item shall be removable
|
|
//3) how to render : if multi item, the item content is rendered when user click on edit button.
|
|
// If not multi item, the single item content is rendered as soon as the item wrapper is rendered.
|
|
//4) some DOM behaviour. For example, a multi item shall be sortable.
|
|
api.czrModuleMap = api.czrModuleMap || {};
|
|
$.extend( api.czrModuleMap, {
|
|
czr_gallery_opts_child : {
|
|
mthds : Constructor,
|
|
crud : false,
|
|
name : api.czr_sektions.getRegisteredModuleProperty( 'czr_gallery_opts_child', 'name' ),
|
|
has_mod_opt : false,
|
|
ready_on_section_expanded : false,
|
|
ready_on_control_event : 'sek-accordion-expanded',// triggered in ::scheduleModuleAccordion()
|
|
defaultItemModel : api.czr_sektions.getDefaultItemModelFromRegisteredModuleData( 'czr_gallery_opts_child' )
|
|
}
|
|
});
|
|
})( wp.customize , jQuery, _ ); |