first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Close extends CommandBase {
validateArgs( args ) {
this.requireArgument( 'id', args );
}
async apply( args ) {
const { id, mode, onClose } = args,
document = elementor.documents.get( id );
// Already closed.
if ( 'closed' === document.editor.status ) {
return jQuery.Deferred().resolve();
}
// TODO: Move to an hook.
if ( ! mode && ( document.editor.isChanged || document.isDraft() ) ) {
const deferred = jQuery.Deferred();
this.getConfirmDialog( deferred ).show();
return deferred.promise();
}
switch ( mode ) {
case 'autosave':
await $e.run( 'document/save/auto' );
break;
case 'save':
await $e.run( 'document/save/update' );
break;
case 'discard':
await $e.run( 'document/save/discard', { document } );
break;
}
$e.internal( 'editor/documents/unload', { document } );
if ( onClose ) {
await onClose( document );
}
return jQuery.Deferred().resolve();
}
getConfirmDialog( deferred ) {
if ( this.confirmDialog ) {
return this.confirmDialog;
}
this.confirmDialog = elementorCommon.dialogsManager.createWidget( 'confirm', {
id: 'elementor-document-save-on-close',
headerMessage: __( 'Save Changes', 'elementor' ),
message: __( 'Would you like to save the changes you\'ve made?', 'elementor' ),
position: {
my: 'center center',
at: 'center center',
},
strings: {
confirm: __( 'Save', 'elementor' ),
cancel: __( 'Discard', 'elementor' ),
},
onHide: () => {
// If still not action chosen. use `defer` because onHide is called before onConfirm/onCancel.
_.defer( () => {
if ( ! this.args.mode ) {
deferred.reject( 'Close document has been canceled.' );
}
} );
},
onConfirm: () => {
this.args.mode = 'save';
// Re-run with same args.
$e.run( 'editor/documents/close', this.args )
.then( () => {
deferred.resolve();
} );
},
onCancel: () => {
this.args.mode = 'discard';
// Re-run with same args.
$e.run( 'editor/documents/close', this.args )
.then( () => {
deferred.resolve();
} );
},
} );
return this.confirmDialog;
}
}
export default Close;

View File

@@ -0,0 +1,6 @@
// Alphabetical order.
export { Close } from './close';
export { Open } from './open';
export { Preview } from './preview';
export { Switch } from './switch';

View File

@@ -0,0 +1,72 @@
import CommandInternalBaseBase from 'elementor-api/modules/command-internal-base';
export class AttachPreview extends CommandInternalBaseBase {
apply() {
const document = elementor.documents.getCurrent();
return $e.data.get( 'globals/index' )
.then( () => {
elementor.trigger( 'globals:loaded' );
return this.attachDocumentToPreview( document );
} )
.then( () => {
elementor.toggleDocumentCssFiles( document, false );
elementor.onEditModeSwitched();
elementor.checkPageStatus();
elementor.trigger( 'document:loaded', document );
return $e.internal( 'panel/open-default', {
refresh: true,
} );
} );
}
attachDocumentToPreview( document ) {
return new Promise( ( resolve, reject ) => {
// Not yet loaded.
if ( ! document ) {
return reject();
}
if ( ! document.config.elements ) {
return resolve();
}
document.$element = elementor.$previewContents.find( '.elementor-' + document.id );
if ( ! document.$element.length ) {
elementor.onPreviewElNotFound();
return reject();
}
document.$element.addClass( 'elementor-edit-area elementor-edit-mode' );
// If not the same document.
if ( document.id !== elementor.config.initial_document.id ) {
elementor.$previewElementorEl.addClass( 'elementor-embedded-editor' );
}
elementor.initElements();
elementor.initPreviewView( document );
document.container.view = elementor.getPreviewView();
document.container.model.attributes.elements = elementor.elements;
elementor.helpers.scrollToView( document.$element );
document.$element
.addClass( 'elementor-edit-area-active' )
.removeClass( 'elementor-editor-preview' );
resolve();
} );
}
}
export default AttachPreview;

View File

@@ -0,0 +1,5 @@
// Alphabetical order.
export { AttachPreview } from './attach-preview';
export { Load } from './load';
export { Unload } from './unload';

View File

@@ -0,0 +1,56 @@
import CommandInternalBase from 'elementor-api/modules/command-internal-base';
import Document from 'elementor-editor/document';
import Heartbeat from 'elementor-editor-utils/heartbeat';
export class Load extends CommandInternalBase {
validateArgs( args = {} ) {
this.requireArgument( 'config', args );
}
apply( args ) {
const { config } = args;
elementor.config.document = config;
elementor.setAjax();
elementor.addWidgetsCache( config.widgets );
elementor.templates.init();
const document = new Document( config );
elementor.documents.add( document );
// Must set current before create a container.
elementor.documents.setCurrent( document );
elementor.settings.page = new elementor.settings.modules.page( config.settings );
document.container = elementor.settings.page.getEditedView().getContainer();
// Reference container back to document.
document.container.document = document;
elementor.heartbeat = new Heartbeat( document );
const isOldPageVersion = elementor.config.document.version &&
elementor.helpers.compareVersions( elementor.config.document.version, '2.5.0', '<' );
if ( ! elementor.config.user.introduction.flexbox && isOldPageVersion ) {
elementor.showFlexBoxAttentionDialog();
}
if ( elementor.loaded ) {
// TODO: Find better solution - Fix issue when globals does not render after saving from kit.
// The issue is that the css-parser is depends upon cache and cache is not available during this time.
return $e.data.get( 'globals/index' ).then( () =>
$e.internal( 'editor/documents/attach-preview' )
);
}
return Promise.resolve( document );
}
}
export default Load;

View File

@@ -0,0 +1,44 @@
import CommandInternalBase from 'elementor-api/modules/command-internal-base';
import Document from 'elementor-editor/document';
export class Unload extends CommandInternalBase {
validateArgs( args = {} ) {
this.requireArgumentConstructor( 'document', Document, args );
}
apply( args ) {
const { document } = args;
if ( document.id !== elementor.config.document.id ) {
return;
}
elementor.elements = [];
elementor.saver.stopAutoSave( document );
elementor.channels.dataEditMode.trigger( 'switch', 'preview' );
if ( document.$element ) {
document.$element
.removeClass( 'elementor-edit-area-active elementor-edit-mode' )
.addClass( 'elementor-editor-preview' );
}
elementorCommon.elements.$body.removeClass( `elementor-editor-${ document.config.type }` );
elementor.settings.page.destroy();
elementor.heartbeat.destroy();
document.editor.status = 'closed';
elementor.config.document = {};
elementor.documents.unsetCurrent();
elementor.trigger( 'document:unloaded', document );
}
}
export default Unload;

View File

@@ -0,0 +1,38 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Open extends CommandBase {
validateArgs( args ) {
this.requireArgument( 'id', args );
}
apply( args ) {
const { id } = args,
currentDocument = elementor.documents.getCurrent();
// Already opened.
if ( currentDocument && id === currentDocument.id ) {
return jQuery.Deferred().resolve();
}
// TODO: move to $e.hooks.ui.
if ( elementor.loaded ) {
elementor.$previewContents.find( `.elementor-${ id }` ).addClass( 'loading' );
}
return elementor.documents.request( id )
.then( ( config ) => {
elementorCommon.elements.$body.addClass( `elementor-editor-${ config.type }` );
// Tell the editor to load the document.
return $e.internal( 'editor/documents/load', { config } );
} )
.always( () => {
// TODO: move to $e.hooks.ui.
if ( elementor.loaded ) {
elementor.$previewContents.find( `.elementor-${ id }` ).removeClass( 'loading' );
}
} );
}
}
export default Open;

View File

@@ -0,0 +1,26 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Preview extends CommandBase {
validateArgs( args ) {
this.requireArgument( 'id', args );
}
// TODO: Check if blocking is required.
async apply( args ) {
const { id } = args,
{ footerSaver } = $e.components.get( 'document/save' ),
document = elementor.documents.get( id );
if ( document.editor.isChanged ) {
// Force save even if it's saving now.
await $e.run( 'document/save/auto', {
force: true,
} );
}
// Open immediately in order to avoid popup blockers.
footerSaver.previewWindow = open( document.config.urls.wp_preview, `wp-preview-${ document.id }` );
}
}
export default Preview;

View File

@@ -0,0 +1,22 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Switch extends CommandBase {
validateArgs( args ) {
this.requireArgument( 'id', args );
}
apply( args ) {
const { id, mode, onClose } = args;
return $e.run( 'editor/documents/close', {
id: elementor.documents.getCurrentId(),
mode,
onClose,
} )
.then( () => {
return $e.run( 'editor/documents/open', { id } );
} );
}
}
export default Switch;

View File

@@ -0,0 +1,184 @@
import ComponentBase from 'elementor-api/modules/component-base';
import Document from './document';
import * as commands from './commands/';
import * as internalCommands from './commands/internal/';
export default class Component extends ComponentBase {
__construct( args = {} ) {
super.__construct( args );
/**
* All the documents.
*
* @type {Object.<Document>}
*/
this.documents = {};
/**
* Current document.
*
* @type {Document}
*/
this.currentDocument = null;
this.saveInitialDocumentToCache();
}
getNamespace() {
return 'editor/documents';
}
defaultCommands() {
return this.importCommands( commands );
}
defaultCommandsInternal() {
return this.importCommands( internalCommands );
}
/**
* Function add().
*
* Add's document to the manager.
*
* @param {Document} document
*
* @returns {Document}
*/
add( document ) {
const { id } = document;
// Save the document.
this.documents[ id ] = document;
return document;
}
/**
* Function addDocumentByConfig().
*
* Add document to manager by config.
*
* @param {{}} config
*
* @returns {Document}
*/
addDocumentByConfig( config ) {
return this.add( new Document( config ) );
}
/**
* Function get().
*
* Get document by id.
*
* @param {number} id
*
* @returns {Document|boolean}
*/
get( id ) {
if ( undefined !== this.documents[ id ] ) {
return this.documents[ id ];
}
return false;
}
/**
* Function getCurrent().
*
* Return's current document.
*
* @returns {Document}
*/
getCurrent() {
return this.currentDocument;
}
/**
* Function getCurrentId().
*
* Return's current document id.
*
* @returns {number}
*/
getCurrentId() {
return this.currentDocument.id;
}
/**
* Function setCurrent().
*
* set current document by document instance.
*
* @param {Document} document
*/
setCurrent( document ) {
if ( undefined === this.documents[ document.id ] ) {
throw Error( `The document with id: '${ document.id }' does not exist/loaded` );
}
if ( this.currentDocument ) {
this.currentDocument.editor.status = 'closed';
}
this.currentDocument = this.documents[ document.id ];
this.currentDocument.editor.status = 'open';
elementorCommon.ajax.addRequestConstant( 'editor_post_id', document.id );
}
isCurrent( id ) {
return parseInt( id ) === this.currentDocument.id;
}
unsetCurrent() {
this.currentDocument = null;
elementorCommon.ajax.addRequestConstant( 'editor_post_id', null );
}
request( id ) {
return elementorCommon.ajax.load( this.getRequestArgs( id ), true );
}
invalidateCache( id ) {
elementorCommon.ajax.invalidateCache( this.getRequestArgs( id ) );
}
getRequestArgs( id ) {
id = parseInt( id );
return {
action: 'get_document_config',
unique_id: `document-${ id }`,
data: { id },
success: ( config ) => config,
error: ( data ) => {
let message;
if ( _.isString( data ) ) {
message = data;
} else if ( data.statusText ) {
message = elementor.createAjaxErrorMessage( data );
if ( 0 === data.readyState ) {
message += ' ' + __( 'Cannot load editor', 'elementor' );
}
} else if ( data[ 0 ] && data[ 0 ].code ) {
message = __( 'Server Error', 'elementor' ) + ' ' + data[ 0 ].code;
}
alert( message );
},
};
}
/**
* Temp: Don't request initial document via ajax.
* Keep the event `elementor:init` before `preview:loaded`.
*/
saveInitialDocumentToCache() {
const document = elementor.config.initial_document;
elementorCommon.ajax.addRequestCache( this.getRequestArgs( document.id ), document );
}
}

View File

@@ -0,0 +1,288 @@
var TagPanelView = require( 'elementor-dynamic-tags/tag-panel-view' );
module.exports = Marionette.Behavior.extend( {
tagView: null,
listenerAttached: false,
initialize: function() {
if ( ! this.listenerAttached ) {
this.listenTo( this.view.options.container.settings, 'change:external:__dynamic__', this.onAfterExternalChange );
this.listenerAttached = true;
}
},
renderTools: function() {
// If the user has Elementor Pro and the current control has no dynamic tags available, don't generate the dynamic switcher.
// If the user has the core version only, we do display the dynamic switcher for the promotion.
if ( this.getOption( 'dynamicSettings' ).default || ( elementor.helpers.hasPro() && ! this.getOption( 'tags' ).length ) ) {
return;
}
const $dynamicSwitcher = jQuery( Marionette.Renderer.render( '#tmpl-elementor-control-dynamic-switcher' ) );
$dynamicSwitcher.on( 'click', ( event ) => this.onDynamicSwitcherClick( event ) );
this.$el.find( '.elementor-control-dynamic-switcher-wrapper' ).append( $dynamicSwitcher );
this.ui.dynamicSwitcher = $dynamicSwitcher;
if ( 'color' === this.view.model.get( 'type' ) ) {
if ( this.view.colorPicker ) {
this.moveDynamicSwitcherToColorPicker();
} else {
setTimeout( () => this.moveDynamicSwitcherToColorPicker() );
}
}
// Add a Tipsy Tooltip to the Dynamic Switcher
this.ui.dynamicSwitcher.tipsy( {
title() {
return this.getAttribute( 'data-tooltip' );
},
gravity: 's',
} );
},
moveDynamicSwitcherToColorPicker: function() {
const $colorPickerToolsContainer = this.view.colorPicker.$pickerToolsContainer;
this.ui.dynamicSwitcher.removeClass( 'elementor-control-unit-1' ).addClass( 'e-control-tool' );
$colorPickerToolsContainer.append( this.ui.dynamicSwitcher );
},
toggleDynamicClass: function() {
this.$el.toggleClass( 'elementor-control-dynamic-value', this.isDynamicMode() );
},
isDynamicMode: function() {
var dynamicSettings = this.view.container.settings.get( '__dynamic__' );
return ! ! ( dynamicSettings && dynamicSettings[ this.view.model.get( 'name' ) ] );
},
createTagsList: function() {
var tags = _.groupBy( this.getOption( 'tags' ), 'group' ),
groups = elementor.dynamicTags.getConfig( 'groups' ),
$tagsList = this.ui.tagsList = jQuery( '<div>', { class: 'elementor-tags-list' } ),
$tagsListInner = jQuery( '<div>', { class: 'elementor-tags-list__inner' } );
$tagsList.append( $tagsListInner );
jQuery.each( groups, function( groupName ) {
var groupTags = tags[ groupName ];
if ( ! groupTags ) {
return;
}
var group = this,
$groupTitle = jQuery( '<div>', { class: 'elementor-tags-list__group-title' } ).text( group.title );
$tagsListInner.append( $groupTitle );
groupTags.forEach( function( tag ) {
var $tag = jQuery( '<div>', { class: 'elementor-tags-list__item' } );
$tag.text( tag.title ).attr( 'data-tag-name', tag.name );
$tagsListInner.append( $tag );
} );
} );
// Create and inject pro dynamic teaser template if Pro is not installed
if ( ! elementor.helpers.hasPro() && Object.keys( tags ).length ) {
const proTeaser = Marionette.Renderer.render( '#tmpl-elementor-dynamic-tags-promo' );
$tagsListInner.append( proTeaser );
}
$tagsListInner.on( 'click', '.elementor-tags-list__item', this.onTagsListItemClick.bind( this ) );
elementorCommon.elements.$body.append( $tagsList );
},
getTagsList: function() {
if ( ! this.ui.tagsList ) {
this.createTagsList();
}
return this.ui.tagsList;
},
toggleTagsList: function() {
var $tagsList = this.getTagsList();
if ( $tagsList.is( ':visible' ) ) {
$tagsList.hide();
return;
}
const direction = elementorCommon.config.isRTL ? 'left' : 'right';
$tagsList.show().position( {
my: `${ direction } top`,
at: `${ direction } bottom+5`,
of: this.ui.dynamicSwitcher,
} );
},
setTagView: function( id, name, settings ) {
if ( this.tagView ) {
this.tagView.destroy();
}
const tagView = this.tagView = new TagPanelView( {
id: id,
name: name,
settings: settings,
controlName: this.view.model.get( 'name' ),
dynamicSettings: this.getOption( 'dynamicSettings' ),
} ),
elementContainer = this.view.options.container,
tagViewLabel = elementContainer.controls[ tagView.options.controlName ].label;
tagView.options.container = new elementorModules.editor.Container( {
type: 'dynamic',
id: id,
model: tagView.model,
settings: tagView.model,
view: tagView,
parent: elementContainer,
label: elementContainer.label + ' ' + tagViewLabel,
controls: tagView.model.options.controls,
renderer: elementContainer,
} );
tagView.render();
this.$el.find( '.elementor-control-tag-area' ).after( tagView.el );
this.listenTo( tagView, 'remove', this.onTagViewRemove.bind( this ) );
},
setDefaultTagView: function() {
var tagData = elementor.dynamicTags.tagTextToTagData( this.getDynamicValue() );
this.setTagView( tagData.id, tagData.name, tagData.settings );
},
tagViewToTagText: function() {
var tagView = this.tagView;
return elementor.dynamicTags.tagDataToTagText( tagView.getOption( 'id' ), tagView.getOption( 'name' ), tagView.model );
},
getDynamicValue: function() {
return this.view.container.dynamic.get( this.view.model.get( 'name' ) );
},
destroyTagView: function() {
if ( this.tagView ) {
this.tagView.destroy();
this.tagView = null;
}
},
showPromotion: function() {
let message = __( 'Create more personalized and dynamic sites by populating data from various sources with dozens of dynamic tags to choose from.', 'elementor' );
elementor.promotion.showDialog( {
headerMessage: __( 'Dynamic Content', 'elementor' ),
message: message,
top: '-10',
element: this.ui.dynamicSwitcher,
actionURL: elementor.config.dynamicPromotionURL,
} );
},
onRender: function() {
this.$el.addClass( 'elementor-control-dynamic' );
this.renderTools();
this.toggleDynamicClass();
if ( this.isDynamicMode() ) {
this.setDefaultTagView();
}
},
onDynamicSwitcherClick: function( event ) {
event.stopPropagation();
if ( this.getOption( 'tags' ).length ) {
this.toggleTagsList();
} else {
this.showPromotion();
}
},
onTagsListItemClick: function( event ) {
const $tag = jQuery( event.currentTarget );
this.setTagView( elementorCommon.helpers.getUniqueId(), $tag.data( 'tagName' ), {} );
// If an element has an active global value, disable it before applying the dynamic value.
if ( this.view.getGlobalKey() ) {
this.view.triggerMethod( 'unset:global:value' );
}
if ( this.isDynamicMode() ) {
$e.run( 'document/dynamic/settings', {
container: this.view.options.container,
settings: {
[ this.view.model.get( 'name' ) ]: this.tagViewToTagText(),
},
} );
} else {
$e.run( 'document/dynamic/enable', {
container: this.view.options.container,
settings: {
[ this.view.model.get( 'name' ) ]: this.tagViewToTagText(),
},
} );
}
this.toggleDynamicClass();
this.toggleTagsList();
if ( this.tagView.getTagConfig().settings_required ) {
this.tagView.showSettingsPopup();
}
},
onTagViewRemove: function() {
$e.run( 'document/dynamic/disable', {
container: this.view.options.container,
settings: {
// Set value for `undo` command.
[ this.view.model.get( 'name' ) ]: this.tagViewToTagText(),
},
} );
this.toggleDynamicClass();
},
onAfterExternalChange: function() {
this.destroyTagView();
if ( this.isDynamicMode() ) {
this.setDefaultTagView();
}
this.toggleDynamicClass();
},
onDestroy: function() {
this.destroyTagView();
if ( this.ui.tagsList ) {
this.ui.tagsList.remove();
}
},
} );

View File

@@ -0,0 +1,157 @@
module.exports = elementorModules.Module.extend( {
CACHE_KEY_NOT_FOUND_ERROR: 'Cache key not found',
tags: {
Base: require( 'elementor-dynamic-tags/tag' ),
},
cache: {},
cacheRequests: {},
cacheCallbacks: [],
addCacheRequest: function( tag ) {
this.cacheRequests[ this.createCacheKey( tag ) ] = true;
},
createCacheKey: function( tag ) {
return btoa( tag.getOption( 'name' ) ) + '-' + btoa( encodeURIComponent( JSON.stringify( tag.model ) ) );
},
loadTagDataFromCache: function( tag ) {
var cacheKey = this.createCacheKey( tag );
if ( undefined !== this.cache[ cacheKey ] ) {
return this.cache[ cacheKey ];
}
if ( ! this.cacheRequests[ cacheKey ] ) {
this.addCacheRequest( tag );
}
},
loadCacheRequests: function() {
var cache = this.cache,
cacheRequests = this.cacheRequests,
cacheCallbacks = this.cacheCallbacks;
this.cacheRequests = {};
this.cacheCallbacks = [];
elementorCommon.ajax.addRequest( 'render_tags', {
data: {
post_id: elementor.config.document.id,
tags: Object.keys( cacheRequests ),
},
success: function( data ) {
jQuery.extend( cache, data );
cacheCallbacks.forEach( function( callback ) {
callback();
} );
},
} );
},
refreshCacheFromServer: function( callback ) {
this.cacheCallbacks.push( callback );
this.loadCacheRequests();
},
getConfig: function( key ) {
return this.getItems( elementor.config.dynamicTags, key );
},
parseTagsText: function( text, settings, parseCallback ) {
var self = this;
if ( 'object' === settings.returnType ) {
return self.parseTagText( text, settings, parseCallback );
}
return text.replace( /\[elementor-tag[^\]]+]/g, function( tagText ) {
return self.parseTagText( tagText, settings, parseCallback );
} );
},
parseTagText: function( tagText, settings, parseCallback ) {
var tagData = this.tagTextToTagData( tagText );
if ( ! tagData ) {
if ( 'object' === settings.returnType ) {
return {};
}
return '';
}
return parseCallback( tagData.id, tagData.name, tagData.settings );
},
tagTextToTagData: function( tagText ) {
var tagIDMatch = tagText.match( /id="(.*?(?="))"/ ),
tagNameMatch = tagText.match( /name="(.*?(?="))"/ ),
tagSettingsMatch = tagText.match( /settings="(.*?(?="]))/ );
if ( ! tagIDMatch || ! tagNameMatch || ! tagSettingsMatch ) {
return false;
}
return {
id: tagIDMatch[ 1 ],
name: tagNameMatch[ 1 ],
settings: JSON.parse( decodeURIComponent( tagSettingsMatch[ 1 ] ) ),
};
},
createTag: function( tagID, tagName, tagSettings ) {
var tagConfig = this.getConfig( 'tags.' + tagName );
if ( ! tagConfig ) {
return;
}
var TagClass = this.tags[ tagName ] || this.tags.Base,
model = new elementorModules.editor.elements.models.BaseSettings( tagSettings, {
controls: tagConfig.controls,
} );
return new TagClass( { id: tagID, name: tagName, model: model } );
},
getTagDataContent: function( tagID, tagName, tagSettings ) {
var tag = this.createTag( tagID, tagName, tagSettings );
if ( ! tag ) {
return;
}
return tag.getContent();
},
tagDataToTagText: function( tagID, tagName, tagSettings ) {
tagSettings = encodeURIComponent( JSON.stringify( ( tagSettings && tagSettings.toJSON( { remove: [ 'default' ] } ) ) || {} ) );
return '[elementor-tag id="' + tagID + '" name="' + tagName + '" settings="' + tagSettings + '"]';
},
tagContainerToTagText: function( /**Container*/ container ) {
return elementor.dynamicTags.tagDataToTagText(
container.view.getOption( 'id' ),
container.view.getOption( 'name' ),
container.view.model
);
},
cleanCache: function() {
this.cache = {};
},
onInit: function() {
this.loadCacheRequests = _.debounce( this.loadCacheRequests, 300 );
},
} );

View File

@@ -0,0 +1,5 @@
module.exports = Marionette.ItemView.extend( {
className: 'elementor-tag-controls-stack-empty',
template: '#tmpl-elementor-tag-controls-stack-empty',
} );

View File

@@ -0,0 +1,37 @@
var EmptyView = require( 'elementor-dynamic-tags/tag-controls-stack-empty' );
module.exports = elementorModules.editor.views.ControlsStack.extend( {
activeTab: 'content',
template: _.noop,
emptyView: EmptyView,
isEmpty: function() {
// Ignore the section control
return this.collection.length < 2;
},
childViewOptions: function() {
return {
container: this.options.container,
};
},
getNamespaceArray: function() {
var currentPageView = elementor.getPanelView().getCurrentPageView(),
eventNamespace = currentPageView.getNamespaceArray();
eventNamespace.push( currentPageView.activeSection );
eventNamespace.push( this.getOption( 'controlName' ) );
eventNamespace.push( this.getOption( 'name' ) );
return eventNamespace;
},
onRenderTemplate: function() {
this.activateFirstSection();
},
} );

View File

@@ -0,0 +1,143 @@
var TagControlsStack = require( 'elementor-dynamic-tags/tag-controls-stack' );
module.exports = Marionette.ItemView.extend( {
className: 'elementor-dynamic-cover elementor-input-style',
tagControlsStack: null,
templateHelpers: function() {
var helpers = {};
if ( this.model ) {
helpers.controls = this.model.options.controls;
}
return helpers;
},
ui: {
remove: '.elementor-dynamic-cover__remove',
},
events: function() {
var events = {
'click @ui.remove': 'onRemoveClick',
};
if ( this.hasSettings() ) {
events.click = 'onClick';
}
return events;
},
getTemplate: function() {
var config = this.getTagConfig(),
templateFunction = Marionette.TemplateCache.get( '#tmpl-elementor-control-dynamic-cover' ),
renderedTemplate = Marionette.Renderer.render( templateFunction, {
hasSettings: this.hasSettings(),
isRemovable: ! this.getOption( 'dynamicSettings' ).default,
title: config.title,
content: config.panel_template,
} );
return Marionette.TemplateCache.prototype.compileTemplate( renderedTemplate.trim() );
},
getTagConfig: function() {
return elementor.dynamicTags.getConfig( 'tags.' + this.getOption( 'name' ) );
},
initSettingsPopup: function() {
var settingsPopupOptions = {
className: 'elementor-tag-settings-popup',
position: {
my: 'left top+5',
at: 'left bottom',
of: this.$el,
autoRefresh: true,
},
hide: {
ignore: '.select2-container',
},
};
var settingsPopup = elementorCommon.dialogsManager.createWidget( 'buttons', settingsPopupOptions );
this.getSettingsPopup = function() {
return settingsPopup;
};
},
hasSettings: function() {
return !! Object.values( this.getTagConfig().controls ).length;
},
showSettingsPopup: function() {
if ( ! this.tagControlsStack ) {
this.initTagControlsStack();
}
var settingsPopup = this.getSettingsPopup();
if ( settingsPopup.isVisible() ) {
return;
}
settingsPopup.show();
},
initTagControlsStack: function() {
this.tagControlsStack = new TagControlsStack( {
model: this.model,
controls: this.model.controls,
name: this.options.name,
controlName: this.options.controlName,
container: this.options.container,
el: this.getSettingsPopup().getElements( 'message' )[ 0 ],
} );
this.tagControlsStack.render();
},
initModel: function() {
this.model = new elementorModules.editor.elements.models.BaseSettings( this.getOption( 'settings' ), {
controls: this.getTagConfig().controls,
} );
},
initialize: function() {
// The `model` should always be available.
this.initModel();
if ( ! this.hasSettings() ) {
return;
}
this.initSettingsPopup();
this.listenTo( this.model, 'change', this.render );
},
onClick: function() {
this.showSettingsPopup();
},
onRemoveClick: function( event ) {
event.stopPropagation();
this.destroy();
this.trigger( 'remove' );
},
onDestroy: function() {
if ( this.hasSettings() ) {
this.getSettingsPopup().destroy();
}
if ( this.tagControlsStack ) {
this.tagControlsStack.destroy();
}
},
} );

View File

@@ -0,0 +1,69 @@
module.exports = Marionette.ItemView.extend( {
hasTemplate: true,
tagName: 'span',
className: function() {
return 'elementor-tag';
},
getTemplate: function() {
if ( ! this.hasTemplate ) {
return false;
}
return Marionette.TemplateCache.get( '#tmpl-elementor-tag-' + this.getOption( 'name' ) + '-content' );
},
initialize: function() {
try {
this.getTemplate();
} catch ( e ) {
this.hasTemplate = false;
}
},
getConfig: function( key ) {
var config = elementor.dynamicTags.getConfig( 'tags.' + this.getOption( 'name' ) );
if ( key ) {
return config[ key ];
}
return config;
},
getContent: function() {
var contentType = this.getConfig( 'content_type' ),
data;
if ( ! this.hasTemplate ) {
data = elementor.dynamicTags.loadTagDataFromCache( this );
if ( undefined === data ) {
throw new Error( elementor.dynamicTags.CACHE_KEY_NOT_FOUND_ERROR );
}
}
if ( 'ui' === contentType ) {
this.render();
if ( this.hasTemplate ) {
return this.el.outerHTML;
}
if ( this.getConfig( 'wrapped_tag' ) ) {
data = jQuery( data ).html();
}
this.$el.html( data );
}
return data;
},
onRender: function() {
this.el.id = 'elementor-tag-' + this.getOption( 'id' );
},
} );

View File

@@ -0,0 +1,21 @@
import ComponentModalBase from 'elementor-api/modules/component-modal-base';
import ModalLayout from './modal-layout';
export default class Component extends ComponentModalBase {
getNamespace() {
return 'shortcuts';
}
defaultShortcuts() {
return {
'': {
keys: 'ctrl+?, shift+?',
exclude: [ 'input' ],
},
};
}
getModalLayout() {
return ModalLayout;
}
}

View File

@@ -0,0 +1,7 @@
import Component from './component';
export default class extends elementorModules.Module {
onInit() {
$e.components.register( new Component( { manager: this } ) );
}
}

View File

@@ -0,0 +1,17 @@
import environment from 'elementor-common/utils/environment';
export default class extends Marionette.LayoutView {
id() {
return 'elementor-hotkeys';
}
templateHelpers() {
return {
environment: environment,
};
}
getTemplate() {
return '#tmpl-elementor-hotkeys';
}
}

View File

@@ -0,0 +1,27 @@
import ModalContent from './modal-content';
export default class extends elementorModules.common.views.modal.Layout {
getModalOptions() {
return {
id: 'elementor-hotkeys__modal',
};
}
getLogoOptions() {
return {
title: __( 'Keyboard Shortcuts', 'elementor' ),
};
}
initialize( ...args ) {
super.initialize( ...args );
this.showLogo();
this.showContentView();
}
showContentView() {
this.modalContent.show( new ModalContent() );
}
}

View File

@@ -0,0 +1,94 @@
export default class {
loaded = {};
notifyCallback = null;
fetchIcons = ( library ) => {
fetch( library.fetchJson, { mode: 'cors' } )
.then( ( res ) => {
return res.json();
} )
.then( ( json ) => {
library.icons = json.icons;
return this.normalizeIconList( library );
} );
};
normalizeIconList( library ) {
const icons = {};
let name;
jQuery.each( library.icons, ( index, icon ) => {
name = icon;
if ( 'object' === typeof name ) {
name = Object.entries( name )[ 0 ][ 0 ];
}
if ( ! name ) {
return;
}
icons[ name ] = {
prefix: library.prefix,
selector: library.prefix + name.trim( ':' ),
name: elementorCommon.helpers.upperCaseWords( name )
.trim( ':' )
.split( '-' )
.join( ' ' ),
filter: name.trim( ':' ),
displayPrefix: library.displayPrefix || library.prefix.replace( '-', '' ),
};
} );
if ( Object.keys( icons ).length ) {
library.icons = icons;
this.loaded[ library.name ] = true;
elementor.iconManager.store.save( library );
this.runCallback( library );
}
}
runCallback = ( library ) => {
if ( 'function' !== typeof this.notifyCallback ) {
return library;
}
return this.notifyCallback( library );
};
initIconType = ( libraryConfig, callback ) => {
this.notifyCallback = callback;
const store = elementor.iconManager.store;
if ( this.loaded[ libraryConfig.name ] ) {
libraryConfig.icons = store.getIcons( libraryConfig );
return this.runCallback( libraryConfig );
}
// Enqueue CSS
if ( libraryConfig.enqueue ) {
libraryConfig.enqueue.forEach( ( assetURL ) => {
elementor.helpers.enqueueEditorStylesheet( assetURL );
} );
}
if ( libraryConfig.url ) {
elementor.helpers.enqueueEditorStylesheet( libraryConfig.url );
}
//already saved an stored
if ( store.isValid( libraryConfig ) ) {
const data = store.get( store.getKey( libraryConfig ) );
return this.normalizeIconList( data );
}
// comes with icons
if ( libraryConfig.icons && libraryConfig.icons.length ) {
return this.normalizeIconList( libraryConfig );
}
// Get icons from via ajax
if ( libraryConfig.fetchJson ) {
return this.fetchIcons( libraryConfig );
}
// @todo: error handling
};
}

View File

@@ -0,0 +1,36 @@
const Store = class {
static getKey( library ) {
const name = ( library.name ) ? library.name : library;
return `elementor_${ name }_icons`;
}
save( library ) {
elementorCommon.storage.set( Store.getKey( library ), library );
}
getIcons( library ) {
const data = this.get( Store.getKey( library ) );
if ( data && data.icons ) {
return data.icons;
}
return false;
}
get( key ) {
return elementorCommon.storage.get( key );
}
isValid( library ) {
const saved = this.get( Store.getKey( library ) );
if ( ! saved ) {
return false;
}
if ( saved.ver !== library.ver ) {
// @todo: delete from localStorage if version is invalid
return false;
}
return ( saved.icons && saved.icons.length );
}
};
export default Store;

View File

@@ -0,0 +1,162 @@
import PropTypes from 'prop-types';
import React, { Fragment, Component } from 'react';
class LazyIconList extends Component {
state = {
itemSize: {
width: 0,
height: 0,
},
wrapperSize: {
width: 0,
height: 0,
},
firstRowInView: 0,
};
selectors = {
item: '.elementor-icons-manager__tab__item',
wrapper: 'elementor-icons-manager__tab__wrapper',
};
attachScrollListener = () => {
const element = document.getElementById( this.selectors.wrapper );
if ( element ) {
element.addEventListener( 'scroll', this.handleScroll );
}
};
maybeMeasureItem = () => {
if ( this.state.itemSize.width ) {
return;
}
// CSS Item Padding
const itemPadding = 20,
wrapper = document.getElementById( this.selectors.wrapper ),
testElement = document.querySelector( this.selectors.item );
if ( ! testElement ) {
return;
}
const newState = {
itemSize: {
width: testElement.offsetWidth + itemPadding,
height: testElement.offsetHeight + itemPadding,
},
wrapperSize: {
width: wrapper.offsetWidth,
height: wrapper.clientHeight,
},
};
return this.setState( newState, () => {
this.maybeScrollToSelected();
} );
};
maybeScrollToSelected = () => {
if ( ! this.hasSelected() ) {
return;
}
const { selectedIndex } = this.props,
{ wrapperSize, itemSize } = this.state,
itemsInRow = Math.floor( wrapperSize.width / itemSize.width ),
selectedItemRow = Math.ceil( selectedIndex / itemsInRow ) - 1,
scrollTop = selectedItemRow * itemSize.height;
setTimeout( () => {
this.props.parentRef.current.scrollTo( {
top: scrollTop,
left: 0,
behavior: 'auto',
} );
}, 0 );
};
componentDidMount() {
this.attachScrollListener();
this.maybeMeasureItem();
}
componentWillUnmount() {
this.clearDebounceScrollCallback();
const element = document.getElementById( this.selectors.wrapper );
if ( element ) {
element.removeEventListener( 'scroll', this.handleScroll );
}
}
handleScroll = () => {
this.clearDebounceScrollCallback();
this._debounce = setTimeout( () => {
const element = document.getElementById( this.selectors.wrapper );
const { itemSize } = this.state;
this.setState( {
firstRowInView: Math.floor( element.scrollTop / itemSize.height ),
} );
}, 10 );
};
clearDebounceScrollCallback() {
clearTimeout( this._debounce );
}
renderFirstElementForMeasurement() {
return <div id={ 'elementor-icons-manager__tab__content' }>
{ this.props.items[ 0 ] }
</div>;
}
hasSelected() {
return -1 !== this.props.selectedIndex;
}
render = () => {
const { itemSize, wrapperSize } = this.state;
let { firstRowInView } = this.state;
if ( ! itemSize.width ) {
return this.renderFirstElementForMeasurement();
}
const { items } = this.props,
itemsInRow = Math.floor( wrapperSize.width / itemSize.width ),
totalRows = Math.ceil( items.length / itemsInRow ),
spareRows = 4;
let rowsInView = Math.ceil( wrapperSize.height / itemSize.height ) + spareRows;
if ( rowsInView > totalRows ) {
rowsInView = totalRows;
}
// Prevent scroll overflow
if ( firstRowInView > ( totalRows - rowsInView ) ) {
firstRowInView = totalRows - rowsInView;
}
const tailRows = ( totalRows - firstRowInView ) - rowsInView,
firstItemIndexInWindow = firstRowInView * itemsInRow,
lastItemIndexInWindow = ( ( firstRowInView + rowsInView ) * itemsInRow ) - 1,
itemsInView = items.slice( firstItemIndexInWindow, lastItemIndexInWindow + 1 ),
offsetStyle = {
height: `${ firstRowInView * itemSize.height }px`,
},
tailStyle = {
height: `${ tailRows * itemSize.height }px`,
};
return <Fragment>
<div className={ 'elementor-icons-manager__tab__content__offset'} style={ offsetStyle }></div>
<div id={ 'elementor-icons-manager__tab__content' }>
{ itemsInView }
</div>
<div className={ 'elementor-icons-manager__tab__content__tail'} style={ tailStyle }></div>
</Fragment>;
};
}
export default LazyIconList;
LazyIconList.propTypes = {
items: PropTypes.array,
selectedIndex: PropTypes.number,
parentRef: PropTypes.any,
};

View File

@@ -0,0 +1,308 @@
import PropTypes from 'prop-types';
import {
Component,
Fragment,
createRef,
} from 'react';
import { render } from 'react-dom';
import Tab from './tab';
import IconsGoPro from './icons-go-pro';
class IconsManager extends Component {
scrollViewRef = createRef();
state = {
activeTab: this.props.activeTab,
selected: {
library: '',
value: '',
},
iconTabs: elementor.config.icons.libraries,
loaded: this.props.loaded,
filter: '',
};
cache = {};
loadAllTabs = () => {
const { loaded } = this.state;
const { icons } = this.props;
icons.forEach( ( tabSettings ) => {
if ( loaded[ tabSettings.name ] ) {
return;
}
if ( -1 < [ 'all', 'recommended' ].indexOf( tabSettings.name ) ) {
return;
}
elementor.iconManager.library.initIconType( { ... tabSettings }, ( library ) => {
this.cache[ library.name ] = library;
loaded[ tabSettings.name ] = true;
} );
} );
loaded.all = true;
loaded.recommended = true;
this.setState( {
loaded: loaded,
} );
};
getActiveTab = () => {
let { activeTab } = this.state;
const { loaded } = this.state,
{ icons } = this.props;
if ( ! activeTab ) {
if ( this.props.activeTab ) {
activeTab = this.props.activeTab;
}
}
if ( 'GoPro' === activeTab ) {
return activeTab;
}
if ( ! loaded[ activeTab ] ) {
return false;
}
const tabSettings = {
... icons.filter( ( tab ) => tab.name === activeTab )[ 0 ],
};
if ( loaded[ activeTab ] ) {
return { ... tabSettings };
}
if ( 'all' === tabSettings.name && ! loaded.all ) {
return this.loadAllTabs();
}
elementor.iconManager.library.initIconType( { ... tabSettings }, ( library ) => {
this.cache[ library.name ] = library;
this.updateLoaded( library.name );
} );
return false;
};
updateLoaded( libraryName ) {
const { loaded } = this.state;
loaded[ libraryName ] = true;
this.setState( { loaded: loaded } );
}
isNativeTab( tab ) {
return ( 'all' === tab.name || 'recommended' === tab.name || 'fa-' === tab.name.substr( 0, 3 ) ) && tab.native;
}
getIconTabsLinks = ( native = true ) => {
return this.props.icons.map( ( tab ) => {
if ( native ^ this.isNativeTab( tab ) ) {
return '';
}
const isCurrentTab = tab.name === this.state.activeTab;
let className = 'elementor-icons-manager__tab-link';
if ( isCurrentTab ) {
className += ' elementor-active';
}
return (
<div
className={ className }
key={ tab.name }
onClick={ () => {
if ( isCurrentTab ) {
return;
}
this.setState( { activeTab: tab.name } );
} }>
<i className={ tab.labelIcon }></i>
{ tab.label }
</div>
);
} );
};
getActiveTabIcons = ( activeTab ) => {
if ( activeTab.name ) {
return this.getActiveTabIcons( activeTab.name );
}
if ( this.cache[ activeTab ] ) {
return this.cache[ activeTab ].icons;
}
if ( 'recommended' === activeTab ) {
return this.state.iconTabs[ 0 ].icons;
}
if ( 'all' === activeTab ) {
return this.getAllIcons();
}
if ( ! this.state.loaded[ activeTab ] ) {
const librarySettings = this.props.icons.filter( ( library ) => activeTab === library.name );
return elementor.iconManager.library.initIconType( { ... librarySettings[ 0 ] }, ( library ) => {
this.cache[ library.name ] = library;
this.updateLoaded( library.name );
} );
}
return elementor.iconManager.store.getIcons( activeTab );
};
getAllIcons = () => {
if ( this.cache.all ) {
return this.cache.all.icons;
}
const icons = {};
this.props.icons.forEach( ( tabSettings ) => {
if ( 'all' === tabSettings.name || 'recommended' === tabSettings.name ) {
return;
}
icons[ tabSettings.name ] = this.getActiveTabIcons( tabSettings.name );
} );
this.cache.all = { icons: icons };
return icons;
};
handleSearch = ( event ) => {
let filter = event.target.value;
if ( filter && '' !== filter ) {
filter = filter.toLocaleLowerCase();
if ( this.state.filter === filter ) {
return;
}
} else {
filter = '';
}
this.setState( { filter: filter } );
};
setSelected = ( selected ) => {
elementor.iconManager.setSettings( 'selectedIcon', selected );
this.setState( { selected: selected } );
};
getSelected = () => {
let { selected } = this.state;
if ( '' === selected.value && this.props.selected && this.props.selected.value ) {
selected = { value: this.props.selected.value, library: this.props.selected.library };
}
return selected;
};
getUploadCustomButton() {
let onClick = () => {
if ( 'GoPro' === this.state.activeTab ) {
return;
}
this.setState( { activeTab: 'GoPro' } );
};
if ( this.props.customIconsURL ) {
onClick = () => {
window.open( this.props.customIconsURL, '_blank' );
};
}
return (
<div id="elementor-icons-manager__upload">
<div id="elementor-icons-manager__upload__title">{ __( 'My Libraries', 'elementor' ) }</div>
<button id="elementor-icons-manager__upload__button" className="elementor-button elementor-button-default" onClick={ onClick }>{ __( 'Upload', 'elementor' ) }</button>
</div>
);
}
getSearchHTML() {
return (
<div id="elementor-icons-manager__search">
<input placeholder={ 'Filter by name...' } onInput={ this.handleSearch }/>
<i className={ 'eicon-search' }></i>
</div>
);
}
render = () => {
const activeTab = this.getActiveTab(),
activeTabName = ( activeTab.name ) ? activeTab.name : activeTab,
{ showSearch = true } = this.props,
{ filter } = this.state,
selected = this.getSelected();
if ( 'GoPro' !== activeTab ) {
if ( ! activeTabName || ! this.state.loaded[ activeTabName ] ) {
return 'Loading';
}
if ( activeTab ) {
activeTab.icons = this.getActiveTabIcons( activeTab );
}
}
return (
<Fragment>
<div id="elementor-icons-manager__sidebar" className={ 'elementor-templates-modal__sidebar' }>
<div id="elementor-icons-manager__tab-links">
{ this.getIconTabsLinks() }
{ this.getUploadCustomButton() }
{ this.getIconTabsLinks( false ) }
</div>
</div>
<div id="elementor-icons-manager__main" className={ 'elementor-templates-modal__content' }>
{ 'GoPro' === activeTabName ? <IconsGoPro /> :
<Fragment>
{ showSearch ? this.getSearchHTML() : '' }
<div id="elementor-icons-manager__tab__wrapper" ref={ this.scrollViewRef }>
<div id="elementor-icons-manager__tab__title">{ activeTab.label }</div>
<div id="elementor-icons-manager__tab__content_wrapper">
<input type="hidden" name="icon_value" id="icon_value" value={ selected.value }/>
<input type="hidden" name="icon_type" id="icon_type" value={ selected.library }/>
{ this.state.loaded[ activeTab.name ] ? (
<Tab
setSelected={ this.setSelected }
selected={ selected }
filter={ filter }
key={ activeTab.name }
parentRef={ this.scrollViewRef }
{ ... activeTab } />
) : (
'Loading'
) }
</div>
</div>
</Fragment>
}
</div>
</Fragment>
);
};
}
export default IconsManager;
const renderIconManager = function( props ) {
const containerElement = document.querySelector( '#elementor-icons-manager-modal .dialog-content' );
return render( <IconsManager
{ ... props }
containerElement={ containerElement } />,
containerElement
);
};
export { renderIconManager };
IconsManager.propTypes = {
activeTab: PropTypes.any,
customIconsURL: PropTypes.string,
icons: PropTypes.any,
loaded: PropTypes.any,
modalView: PropTypes.any,
recommended: PropTypes.oneOfType( [ PropTypes.bool, PropTypes.object ] ),
selected: PropTypes.any,
showSearch: PropTypes.bool,
};

View File

@@ -0,0 +1,29 @@
import PropTypes from 'prop-types';
import { Component } from 'react';
export default class Icon extends Component {
setSelected = () => {
this.props.setSelectedHandler( { value: this.props.data.displayPrefix + ' ' + this.props.data.selector, library: this.props.library } );
};
render = () => {
return (
<div className={ this.props.containerClass } key={ this.props.keyID } onClick={ this.setSelected } filter={ this.props.data.filter }>
<div className="elementor-icons-manager__tab__item__content">
<i className={ 'elementor-icons-manager__tab__item__icon ' + this.props.className }>
</i>
<div className={ 'elementor-icons-manager__tab__item__name' } title={ this.props.data.name }>{ this.props.data.name }</div>
</div>
</div>
);
};
}
Icon.propTypes = {
className: PropTypes.string,
containerClass: PropTypes.string,
data: PropTypes.object,
keyID: PropTypes.string,
library: PropTypes.string,
selector: PropTypes.string,
setSelectedHandler: PropTypes.func,
};

View File

@@ -0,0 +1,21 @@
import { Component } from 'react';
class IconsGoPro extends Component {
render = () => {
return (
<div id="elementor-icons-manager__promotion">
<i id="elementor-icons-manager__promotion__icon" className="eicon-nerd"></i>
<div id="elementor-icons-manager__promotion__text">{ __( 'Become a Pro user to upload unlimited font icon folders to your website.', 'elementor' ) }</div>
<a
href={ elementor.config.icons.goProURL }
id="elementor-icons-manager__promotion__link"
className="elementor-button elementor-button-default elementor-button-go-pro"
target="_blank"
rel="noopener noreferrer"
>{ __( 'Go Pro', 'elementor' ) }</a>
</div>
);
};
}
export default IconsGoPro;

View File

@@ -0,0 +1,132 @@
import PropTypes from 'prop-types';
import LazyIconList from './icon-list';
import { Component } from 'react';
import Icon from './icon';
class Tab extends Component {
componentDidMount = () => {
if ( this.props.selected && this.props.selected.value ) {
setTimeout( () => {
const element = document.querySelector( '.elementor-selected' );
if ( element ) {
element.scrollIntoView( false );
}
}, 0 );
}
};
getIconsOfType( type, icons ) {
const { selected, filter } = this.props;
return Object.entries( icons ).map( ( icon ) => {
const iconData = icon[ 1 ],
iconName = icon[ 0 ],
className = iconData.displayPrefix + ' ' + iconData.selector;
let containerClass = 'elementor-icons-manager__tab__item';
if ( selected.value === className ) {
containerClass += ' elementor-selected';
}
const key = containerClass + type + '-' + iconName + filter;
return (
<Icon
key={ key }
library={ type }
keyID={ iconName }
containerClass={ containerClass }
className={ className }
setSelectedHandler={ this.props.setSelected }
data={ iconData }
/>
);
} );
}
handleFullIconList = () => {
let fullIconList = [];
Object.entries( this.props.icons ).forEach( ( library ) => {
if ( 'recommended' !== library[ 0 ] ) {
fullIconList = [ ... fullIconList, ... this.getIconsOfType( library[ 0 ], library[ 1 ] ) ];
}
} );
return fullIconList.sort( ( a, b ) => a.filter === b.filter ? 0 : +( a.filter > b.filter ) || -1 );
};
getLibrary = ( libraryName ) => {
const icons = elementor.config.icons.libraries.filter( ( library ) => {
return libraryName === library.name;
} );
return icons;
};
handleRecommendedList = () => {
let recommendedIconList = [];
Object.entries( this.props.icons ).forEach( ( library ) => {
const iconLibrary = this.getLibrary( library[ 0 ] ),
iconsOfType = iconLibrary[ 0 ].icons,
recommendedIconsOfType = {};
library[ 1 ].forEach( ( iconName ) => {
if ( iconsOfType[ iconName ] ) {
recommendedIconsOfType[ iconName ] = iconsOfType[ iconName ];
}
} );
recommendedIconList = [ ... recommendedIconList, ...this.getIconsOfType( library[ 0 ], recommendedIconsOfType ) ];
} );
return recommendedIconList;
};
getIconsComponentList = () => {
let iconsToShow = [];
const { name, icons, filter } = this.props;
switch ( name ) {
case 'all':
iconsToShow = this.handleFullIconList();
break;
case 'recommended':
iconsToShow = this.handleRecommendedList();
break;
default:
iconsToShow = this.getIconsOfType( name, icons );
break;
}
if ( filter ) {
iconsToShow = Object.values( iconsToShow ).filter( ( icon ) => {
return icon.props.data.name.toLowerCase().indexOf( filter ) > -1;
} );
}
return iconsToShow;
};
render = () => {
const icons = this.getIconsComponentList();
let selectedIndex = -1;
for ( const [ index, icon ] of icons.entries() ) {
if ( icon.props.containerClass.includes( 'elementor-selected' ) ) {
selectedIndex = index;
break;
}
}
return (
<LazyIconList
selectedIndex={ selectedIndex }
items={ icons }
parentRef={ this.props.parentRef }
/>
);
};
}
Tab.propTypes = {
data: PropTypes.any,
filter: PropTypes.any,
icons: PropTypes.object,
name: PropTypes.string,
selected: PropTypes.object,
setSelected: PropTypes.func,
parentRef: PropTypes.any,
};
export default Tab;

View File

@@ -0,0 +1,160 @@
import ModalLayout from './modal-layout';
import { renderIconManager } from './components/icon-manager';
import IconLibrary from './classes/icon-library';
import Store from './classes/store';
import { unmountComponentAtNode } from 'react-dom';
export default class extends elementorModules.Module {
onInit() {
// Init icon library helper
this.library = new IconLibrary();
// Init Icon library Storage helper
this.store = new Store();
// Fetch fa4 to fa5 migration data
elementor.helpers.fetchFa4ToFa5Mapping();
this.cache = {};
}
getLayout() {
if ( ! this.layout ) {
this.layout = new ModalLayout();
const layoutModal = this.layout.getModal();
layoutModal.addButton( {
name: 'insert_icon',
text: __( 'Insert', 'elementor' ),
classes: 'elementor-button elementor-button-success',
callback: () => {
this.updateControlValue();
this.unMountIconManager();
},
} );
layoutModal
.on( 'show', this.onPickerShow.bind( this ) )
.on( 'hide', this.unMountIconManager );
}
return this.layout;
}
getDefaultSettings() {
return {
selectedIcon: {},
};
}
unMountIconManager() {
const containerElement = document.querySelector( '#elementor-icons-manager-modal .dialog-content' );
unmountComponentAtNode( containerElement );
}
loadIconLibraries() {
if ( ! this.cache.loaded ) {
elementor.config.icons.libraries.forEach( ( library ) => {
if ( 'all' === library.name ) {
return;
}
elementor.iconManager.library.initIconType( library );
} );
this.cache.loaded = true;
}
}
onPickerShow() {
const controlView = this.getSettings( 'controlView' ),
loaded = {
GoPro: true,
},
iconManagerConfig = {
recommended: controlView.model.get( 'recommended' ) || false,
};
let selected = controlView.getControlValue(),
icons = elementor.config.icons.libraries;
if ( ! selected.library || ! selected.value ) {
selected = {
value: '',
library: '',
};
}
iconManagerConfig.selected = selected;
this.setSettings( 'selectedIcon', selected );
if ( iconManagerConfig.recommended ) {
let hasRecommended = false;
icons.forEach( ( library, index ) => {
if ( 'recommended' === library.name ) {
hasRecommended = true;
icons[ index ].icons = iconManagerConfig.recommended;
}
} );
if ( ! hasRecommended ) {
icons.unshift( {
name: 'recommended',
label: 'Recommended',
icons: iconManagerConfig.recommended,
labelIcon: 'eicon-star-o',
native: true,
} );
}
} else {
icons = icons.filter( ( library ) => {
return 'recommended' !== library.name;
} );
}
icons.forEach( ( tab, index ) => {
if ( -1 === [ 'all', 'recommended' ].indexOf( tab.name ) ) {
elementor.iconManager.library.initIconType( tab, ( lib ) => {
icons[ index ] = lib;
} );
}
loaded[ tab.name ] = true;
} );
iconManagerConfig.loaded = loaded;
iconManagerConfig.icons = icons;
// Set active tab
let activeTab = selected.library || icons[ 0 ].name;
if ( 'svg' === selected.library ) {
activeTab = icons[ 0 ].name;
}
// selected Library exists
if ( ! Object.keys( icons ).some( ( library ) => library === activeTab ) ) {
activeTab = icons[ 0 ].name;
}
// Show recommended tab if selected from it
if ( iconManagerConfig.recommended && '' !== selected.library && '' !== selected.value && iconManagerConfig.recommended.hasOwnProperty( selected.library ) ) {
const iconLibrary = icons.filter( ( library ) => selected.library === library.name );
const selectedIconName = selected.value.replace( iconLibrary[ 0 ].displayPrefix + ' ' + iconLibrary[ 0 ].prefix, '' );
if ( iconManagerConfig.recommended[ selected.library ].some( ( icon ) => -1 < icon.indexOf( selectedIconName ) ) ) {
activeTab = icons[ 0 ].name;
}
}
iconManagerConfig.customIconsURL = elementor.config.customIconsURL;
iconManagerConfig.activeTab = activeTab;
return renderIconManager( iconManagerConfig );
}
updateControlValue() {
const settings = this.getSettings();
settings.controlView.setValue( settings.selectedIcon );
settings.controlView.applySavedValue();
}
show( options ) {
this.setSettings( 'controlView', options.view );
this.getLayout().showModal( options );
}
}

View File

@@ -0,0 +1,21 @@
import BaseModalLayout from 'elementor-common/views/modal/layout';
export default class extends BaseModalLayout {
getModalOptions() {
return {
id: 'elementor-icons-manager-modal',
};
}
getLogoOptions() {
return {
title: __( 'Icon Library', 'elementor' ),
};
}
initialize( ...args ) {
super.initialize( ...args );
this.showLogo();
}
}

View File

@@ -0,0 +1,229 @@
var ControlsCSSParser = require( 'elementor-editor-utils/controls-css-parser' );
module.exports = elementorModules.ViewModule.extend( {
model: null,
hasChange: false,
changeCallbacks: {},
addChangeCallback: function( attribute, callback ) {
this.changeCallbacks[ attribute ] = callback;
},
bindEvents: function() {
elementor.on( 'document:loaded', this.onElementorDocumentLoaded );
this.model.on( 'change', this.onModelChange );
},
unbindEvents: function() {
elementor.off( 'document:loaded', this.onElementorDocumentLoaded );
},
addPanelPage: function() {
var name = this.getSettings( 'name' );
elementor.getPanelView().addPage( name + '_settings', {
view: elementor.settings.panelPages[ name ] || elementor.settings.panelPages.base,
title: this.getSettings( 'panelPage.title' ),
options: {
editedView: this.getEditedView(),
model: this.model,
controls: this.model.controls,
name: name,
},
} );
},
getContainerId() {
return this.getSettings( 'name' ) + '_settings';
},
// Emulate an element view/model structure with the parts needed for a container.
getEditedView() {
const id = this.getContainerId(),
editModel = new Backbone.Model( {
id,
elType: id,
settings: this.model,
} );
const container = new elementorModules.editor.Container( {
type: id,
id: editModel.id,
model: editModel,
settings: editModel.get( 'settings' ),
view: false,
label: this.getSettings( 'panelPage' ).title,
controls: this.model.controls,
document: this.getDocument(),
renderer: false,
} );
return {
getContainer: () => container,
getEditModel: () => editModel,
model: editModel,
};
},
getDocument() {
return false;
},
updateStylesheet: function( keepOldEntries ) {
var controlsCSS = this.getControlsCSS();
if ( ! keepOldEntries ) {
controlsCSS.stylesheet.empty();
}
this.model.handleRepeaterData( this.model.attributes );
controlsCSS.addStyleRules( this.model.getStyleControls(), this.model.attributes, this.model.controls, [ /{{WRAPPER}}/g ], [ this.getSettings( 'cssWrapperSelector' ) ] );
controlsCSS.addStyleToDocument( {
// Ensures we don't override default global style
at: 'before',
of: '#elementor-style-e-global-style',
} );
},
initModel: function() {
this.model = new elementorModules.editor.elements.models.BaseSettings( this.getSettings( 'settings' ), {
controls: this.getSettings( 'controls' ),
} );
},
getStyleId: function() {
return this.getSettings( 'name' );
},
initControlsCSSParser: function() {
var controlsCSS;
this.destroyControlsCSS = function() {
controlsCSS.removeStyleFromDocument();
};
this.getControlsCSS = function() {
if ( ! controlsCSS ) {
controlsCSS = new ControlsCSSParser( {
id: this.getStyleId(),
settingsModel: this.model,
context: this.getEditedView(),
} );
}
return controlsCSS;
};
},
getDataToSave: function( data ) {
return data;
},
save: function( callback ) {
var self = this;
if ( ! self.hasChange ) {
return;
}
var settings = this.model.toJSON( { remove: [ 'default' ] } ),
data = this.getDataToSave( {
data: settings,
} );
if ( ! elementorCommonConfig.isTesting ) {
NProgress.start();
}
elementorCommon.ajax.addRequest( 'save_' + this.getSettings( 'name' ) + '_settings', {
data: data,
success: function() {
if ( ! elementorCommonConfig.isTesting ) {
NProgress.done();
}
self.setSettings( 'settings', settings );
self.hasChange = false;
if ( callback ) {
callback.apply( self, arguments );
}
},
error: function() {
alert( 'An error occurred' );
},
} );
},
onInit: function() {
this.initModel();
this.initControlsCSSParser();
this.addPanelMenuItem();
this.debounceSave = _.debounce( this.save, 3000 );
elementorModules.ViewModule.prototype.onInit.apply( this, arguments );
},
/**
* BC for custom settings without a JS component.
*/
addPanelMenuItem: function() {
const menuSettings = this.getSettings( 'panelPage.menu' );
if ( ! menuSettings ) {
return;
}
const namespace = 'panel/' + this.getSettings( 'name' ) + '-settings',
menuItemOptions = {
icon: menuSettings.icon,
title: this.getSettings( 'panelPage.title' ),
type: 'page',
pageName: this.getSettings( 'name' ) + '_settings',
callback: () => $e.route( `${ namespace }/settings` ),
};
$e.bc.ensureTab( namespace, 'settings', menuItemOptions.pageName );
elementor.modules.layouts.panel.pages.menu.Menu.addItem( menuItemOptions, 'settings', menuSettings.beforeItem );
},
onModelChange: function( model ) {
var self = this;
self.hasChange = true;
this.getControlsCSS().stylesheet.empty();
_.each( model.changed, function( value, key ) {
if ( self.changeCallbacks[ key ] ) {
self.changeCallbacks[ key ].call( self, value );
}
} );
self.updateStylesheet( true );
self.debounceSave();
},
onElementorDocumentLoaded: function() {
this.updateStylesheet();
this.addPanelPage();
},
destroy: function() {
this.unbindEvents();
this.model.destroy();
},
} );

View File

@@ -0,0 +1,19 @@
module.exports = elementorModules.editor.views.ControlsStack.extend( {
id: function() {
return 'elementor-panel-' + this.getOption( 'name' ) + '-settings';
},
getTemplate: function() {
return '#tmpl-elementor-panel-' + this.getOption( 'name' ) + '-settings';
},
childViewContainer: function() {
return '#elementor-panel-' + this.getOption( 'name' ) + '-settings-controls';
},
childViewOptions: function() {
return {
container: this.getOption( 'editedView' ).getContainer(),
};
},
} );

View File

@@ -0,0 +1,66 @@
import BaseManager from '../base/manager';
export default class extends BaseManager {
getDefaultSettings() {
return {
darkModeLinkID: 'elementor-editor-dark-mode-css',
};
}
constructor( ...args ) {
super( ...args );
this.changeCallbacks = {
ui_theme: this.onUIThemeChanged,
panel_width: this.onPanelWidthChanged,
edit_buttons: this.onEditButtonsChanged,
};
}
createDarkModeStylesheetLink() {
const darkModeLinkID = this.getSettings( 'darkModeLinkID' );
let $darkModeLink = jQuery( '#' + darkModeLinkID );
if ( ! $darkModeLink.length ) {
$darkModeLink = jQuery( '<link>', {
id: darkModeLinkID,
rel: 'stylesheet',
href: elementor.config.ui.darkModeStylesheetURL,
} );
}
this.$link = $darkModeLink;
}
getDarkModeStylesheetLink() {
if ( ! this.$link ) {
this.createDarkModeStylesheetLink();
}
return this.$link;
}
onUIThemeChanged( newValue ) {
const $link = this.getDarkModeStylesheetLink();
if ( 'light' === newValue ) {
$link.remove();
return;
}
$link.attr( 'media', 'auto' === newValue ? '(prefers-color-scheme: dark)' : '' ).appendTo( elementorCommon.elements.$body );
}
onPanelWidthChanged( newValue ) {
elementor.panel.saveSize( { width: newValue.size + newValue.unit } );
elementor.panel.setSize();
}
onEditButtonsChanged() {
// Let the button change before the high-performance action of rendering the entire page
setTimeout( () => elementor.getPreviewView()._renderChildren(), 300 );
}
}

View File

@@ -0,0 +1,23 @@
import ComponentBase from 'elementor-api/modules/component-base';
export default class Component extends ComponentBase {
getNamespace() {
return 'panel/page-settings';
}
defaultTabs() {
return {
settings: { title: __( 'Settings', 'elementor' ) },
style: { title: __( 'Style', 'elementor' ) },
advanced: { title: __( 'Advanced', 'elementor' ) },
};
}
renderTab( tab ) {
elementor.getPanelView().setPage( 'page_settings' ).activateTab( tab );
}
getTabsWrapperSelector() {
return '.elementor-panel-navigation';
}
}

View File

@@ -0,0 +1,68 @@
import Component from './component';
var BaseSettings = require( 'elementor-editor/components/settings/base/manager' );
module.exports = BaseSettings.extend( {
getStyleId: function() {
return this.getSettings( 'name' ) + '-' + elementor.documents.getCurrent().id;
},
onInit: function() {
BaseSettings.prototype.onInit.apply( this );
$e.components.register( new Component( { manager: this } ) );
},
save: function() {},
getDataToSave: function( data ) {
data.id = elementor.config.document.id;
return data;
},
// Emulate an element view/model structure with the parts needed for a container.
getEditedView() {
if ( this.editedView ) {
return this.editedView;
}
const id = this.getContainerId(),
editModel = new Backbone.Model( {
id,
elType: id,
settings: this.model,
elements: elementor.elements,
} );
const container = new elementorModules.editor.Container( {
type: id,
id: editModel.id,
model: editModel,
settings: editModel.get( 'settings' ),
label: elementor.config.document.panel.title,
controls: this.model.controls,
children: elementor.elements,
// Emulate a view that can render the style.
renderer: {
view: {
lookup: () => container,
renderOnChange: () => this.updateStylesheet(),
renderUI: () => this.updateStylesheet(),
},
},
} );
this.editedView = {
getContainer: () => container,
getEditModel: () => editModel,
model: editModel,
};
return this.editedView;
},
getContainerId() {
return 'document';
},
} );

View File

@@ -0,0 +1,27 @@
import EditorPreferences from './editor-preferences/manager';
module.exports = elementorModules.Module.extend( {
modules: {
base: require( 'elementor-editor/components/settings/base/manager' ),
page: require( 'elementor-editor/components/settings/page/manager' ),
editorPreferences: EditorPreferences,
},
panelPages: {
base: require( 'elementor-editor/components/settings/base/panel' ),
},
onInit: function() {
this.initSettings();
},
initSettings: function() {
var self = this;
_.each( elementor.config.settings, function( config, name ) {
var Manager = self.modules[ name ] || self.modules.base;
self[ name ] = new Manager( config );
} );
},
} );

View File

@@ -0,0 +1,26 @@
var InsertTemplateHandler;
InsertTemplateHandler = Marionette.Behavior.extend( {
ui: {
insertButton: '.elementor-template-library-template-insert',
},
events: {
'click @ui.insertButton': 'onInsertButtonClick',
},
onInsertButtonClick: function() {
const args = {
model: this.view.model,
};
if ( 'remote' === args.model.get( 'source' ) && ! elementor.config.library_connect.is_connected ) {
$e.route( 'library/connect', args );
return;
}
$e.run( 'library/insert-template', args );
},
} );
module.exports = InsertTemplateHandler;

View File

@@ -0,0 +1,8 @@
var TemplateLibraryTemplateModel = require( 'elementor-templates/models/template' ),
TemplateLibraryCollection;
TemplateLibraryCollection = Backbone.Collection.extend( {
model: TemplateLibraryTemplateModel,
} );
module.exports = TemplateLibraryCollection;

View File

@@ -0,0 +1,2 @@
export { InsertTemplate } from './insert-template';
export { Open } from './open';

View File

@@ -0,0 +1,9 @@
import CommandBase from 'elementor-api/modules/command-base';
export class InsertTemplate extends CommandBase {
apply( args ) {
return this.component.insertTemplate( args );
}
}
export default InsertTemplate;

View File

@@ -0,0 +1,9 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Open extends CommandBase {
apply( args ) {
return this.component.show( args );
}
}
export default Open;

View File

@@ -0,0 +1,248 @@
import ComponentModalBase from 'elementor-api/modules/component-modal-base';
import * as commands from './commands/';
const TemplateLibraryLayoutView = require( 'elementor-templates/views/library-layout' );
export default class Component extends ComponentModalBase {
__construct( args ) {
super.__construct( args );
// When switching documents update defaultTabs.
elementor.on( 'document:loaded', this.onDocumentLoaded.bind( this ) );
}
getNamespace() {
return 'library';
}
defaultTabs() {
return {
'templates/blocks': {
title: __( 'Blocks', 'elementor' ),
getFilter: () => ( {
source: 'remote',
type: 'block',
subtype: elementor.config.document.remoteLibrary.category,
} ),
},
'templates/pages': {
title: __( 'Pages', 'elementor' ),
filter: {
source: 'remote',
type: 'page',
},
},
'templates/my-templates': {
title: __( 'My Templates', 'elementor' ),
filter: {
source: 'local',
},
},
};
}
defaultRoutes() {
return {
import: () => {
this.manager.layout.showImportView();
},
'save-template': ( args ) => {
this.manager.layout.showSaveTemplateView( args.model );
},
preview: ( args ) => {
this.manager.layout.showPreviewView( args.model );
},
connect: ( args ) => {
args.texts = {
title: __( 'Connect to Template Library', 'elementor' ),
message: __( 'Access this template and our entire library by creating a free personal account', 'elementor' ),
button: __( 'Get Started', 'elementor' ),
};
this.manager.layout.showConnectView( args );
},
};
}
defaultCommands() {
const modalCommands = super.defaultCommands();
return {
... modalCommands,
... this.importCommands( commands ),
};
}
defaultShortcuts() {
return {
open: {
keys: 'ctrl+shift+l',
},
};
}
onDocumentLoaded( document ) {
this.setDefaultRoute( document.config.remoteLibrary.default_route );
this.maybeOpenLibrary();
}
renderTab( tab ) {
const currentTab = this.tabs[ tab ],
filter = currentTab.getFilter ? currentTab.getFilter() : currentTab.filter;
this.manager.setScreen( filter );
}
activateTab( tab ) {
$e.routes.saveState( 'library' );
super.activateTab( tab );
}
open() {
super.open();
if ( ! this.manager.layout ) {
this.manager.layout = this.layout;
}
this.manager.layout.setHeaderDefaultParts();
return true;
}
close() {
if ( ! super.close() ) {
return false;
}
this.manager.modalConfig = {};
return true;
}
show( args ) {
this.manager.modalConfig = args;
if ( args.toDefault || ! $e.routes.restoreState( 'library' ) ) {
$e.route( this.getDefaultRoute() );
}
}
// TODO: Move function to 'insert-template' command.
insertTemplate( args ) {
const autoImportSettings = elementor.config.document.remoteLibrary.autoImportSettings,
model = args.model;
let { withPageSettings = null } = args;
if ( autoImportSettings ) {
withPageSettings = true;
}
if ( null === withPageSettings && model.get( 'hasPageSettings' ) ) {
const insertTemplateHandler = this.getImportSettingsDialog();
insertTemplateHandler.showImportDialog( model );
return;
}
this.manager.layout.showLoadingView();
this.manager.requestTemplateContent( model.get( 'source' ), model.get( 'template_id' ), {
data: {
with_page_settings: withPageSettings,
},
success: ( data ) => {
// Clone the `modalConfig.importOptions` because it deleted during the closing.
const importOptions = jQuery.extend( {}, this.manager.modalConfig.importOptions );
importOptions.withPageSettings = withPageSettings;
// Hide for next open.
this.manager.layout.hideLoadingView();
this.manager.layout.hideModal();
$e.run( 'document/elements/import', {
model,
data,
options: importOptions,
} );
},
error: ( data ) => {
this.manager.showErrorDialog( data );
},
complete: () => {
this.manager.layout.hideLoadingView();
},
} );
}
getImportSettingsDialog() {
// Moved from ./behaviors/insert-template.js
const InsertTemplateHandler = {
dialog: null,
showImportDialog: function( model ) {
var dialog = InsertTemplateHandler.getDialog();
dialog.onConfirm = function() {
$e.run( 'library/insert-template', {
model,
withPageSettings: true,
} );
};
dialog.onCancel = function() {
$e.run( 'library/insert-template', {
model,
withPageSettings: false,
} );
};
dialog.show();
},
initDialog: function() {
InsertTemplateHandler.dialog = elementorCommon.dialogsManager.createWidget( 'confirm', {
id: 'elementor-insert-template-settings-dialog',
headerMessage: __( 'Import Document Settings', 'elementor' ),
message: __( 'Do you want to also import the document settings of the template?', 'elementor' ) + '<br>' + __( 'Attention: Importing may override previous settings.', 'elementor' ),
strings: {
confirm: __( 'Yes', 'elementor' ),
cancel: __( 'No', 'elementor' ),
},
} );
},
getDialog: function() {
if ( ! InsertTemplateHandler.dialog ) {
InsertTemplateHandler.initDialog();
}
return InsertTemplateHandler.dialog;
},
};
return InsertTemplateHandler;
}
getTabsWrapperSelector() {
return '#elementor-template-library-header-menu';
}
getModalLayout() {
return TemplateLibraryLayoutView;
}
maybeOpenLibrary() {
if ( '#library' === location.hash ) {
$e.run( 'library/open' );
location.hash = '';
}
}
}

View File

@@ -0,0 +1,366 @@
import Component from './component';
var TemplateLibraryCollection = require( 'elementor-templates/collections/templates' ),
TemplateLibraryManager;
TemplateLibraryManager = function() {
this.modalConfig = {};
const self = this,
templateTypes = {};
let deleteDialog,
errorDialog,
templatesCollection,
config = {},
filterTerms = {};
const registerDefaultTemplateTypes = function() {
var data = {
saveDialog: {
description: __( 'Your designs will be available for export and reuse on any page or website', 'elementor' ),
},
ajaxParams: {
success: function( successData ) {
$e.route( 'library/templates/my-templates', {
onBefore: () => {
if ( templatesCollection ) {
const itemExist = templatesCollection.findWhere( {
template_id: successData.template_id,
} );
if ( ! itemExist ) {
templatesCollection.add( successData );
}
}
},
} );
},
error: function( errorData ) {
self.showErrorDialog( errorData );
},
},
};
const translationMap = {
page: __( 'Page', 'elementor' ),
section: __( 'Section', 'elementor' ),
[ elementor.config.document.type ]: elementor.config.document.panel.title,
};
jQuery.each( translationMap, function( type, title ) {
var safeData = jQuery.extend( true, {}, data, {
saveDialog: {
/* translators: %s: Template type. */
title: sprintf( __( 'Save Your %s to Library', 'elementor' ), title ),
},
} );
self.registerTemplateType( type, safeData );
} );
};
const registerDefaultFilterTerms = function() {
filterTerms = {
text: {
callback: function( value ) {
value = value.toLowerCase();
if ( this.get( 'title' ).toLowerCase().indexOf( value ) >= 0 ) {
return true;
}
return _.any( this.get( 'tags' ), function( tag ) {
return tag.toLowerCase().indexOf( value ) >= 0;
} );
},
},
type: {},
subtype: {},
favorite: {},
};
};
this.init = function() {
registerDefaultTemplateTypes();
registerDefaultFilterTerms();
this.component = $e.components.register( new Component( { manager: this } ) );
elementor.addBackgroundClickListener( 'libraryToggleMore', {
element: '.elementor-template-library-template-more',
} );
};
this.getTemplateTypes = function( type ) {
if ( type ) {
return templateTypes[ type ];
}
return templateTypes;
};
this.registerTemplateType = function( type, data ) {
templateTypes[ type ] = data;
};
this.deleteTemplate = function( templateModel, options ) {
var dialog = self.getDeleteDialog();
dialog.onConfirm = function() {
if ( options.onConfirm ) {
options.onConfirm();
}
elementorCommon.ajax.addRequest( 'delete_template', {
data: {
source: templateModel.get( 'source' ),
template_id: templateModel.get( 'template_id' ),
},
success: function( response ) {
templatesCollection.remove( templateModel, { silent: true } );
if ( options.onSuccess ) {
options.onSuccess( response );
}
},
} );
};
dialog.show();
};
this.importTemplate = function( model, args = {} ) {
elementorCommon.helpers.softDeprecated( 'importTemplate', '2.8.0',
"$e.run( 'library/insert-template' )" );
args.model = model;
$e.run( 'library/insert-template', args );
};
this.saveTemplate = function( type, data ) {
var templateType = templateTypes[ type ];
_.extend( data, {
source: 'local',
type: type,
} );
if ( templateType.prepareSavedData ) {
data = templateType.prepareSavedData( data );
}
data.content = JSON.stringify( data.content );
var ajaxParams = { data: data };
if ( templateType.ajaxParams ) {
_.extend( ajaxParams, templateType.ajaxParams );
}
elementorCommon.ajax.addRequest( 'save_template', ajaxParams );
};
this.requestTemplateContent = function( source, id, ajaxOptions ) {
var options = {
unique_id: id,
data: {
source: source,
edit_mode: true,
display: true,
template_id: id,
},
};
if ( ajaxOptions ) {
jQuery.extend( true, options, ajaxOptions );
}
return elementorCommon.ajax.addRequest( 'get_template_data', options );
};
this.markAsFavorite = function( templateModel, favorite ) {
var options = {
data: {
source: templateModel.get( 'source' ),
template_id: templateModel.get( 'template_id' ),
favorite: favorite,
},
};
return elementorCommon.ajax.addRequest( 'mark_template_as_favorite', options );
};
this.getDeleteDialog = function() {
if ( ! deleteDialog ) {
deleteDialog = elementorCommon.dialogsManager.createWidget( 'confirm', {
id: 'elementor-template-library-delete-dialog',
headerMessage: __( 'Delete Template', 'elementor' ),
message: __( 'Are you sure you want to delete this template?', 'elementor' ),
strings: {
confirm: __( 'Delete', 'elementor' ),
},
} );
}
return deleteDialog;
};
this.getErrorDialog = function() {
if ( ! errorDialog ) {
errorDialog = elementorCommon.dialogsManager.createWidget( 'alert', {
id: 'elementor-template-library-error-dialog',
headerMessage: __( 'An error occurred', 'elementor' ),
} );
}
return errorDialog;
};
this.getTemplatesCollection = function() {
return templatesCollection;
};
this.getConfig = function( item ) {
if ( item ) {
return config[ item ] ? config[ item ] : {};
}
return config;
};
this.requestLibraryData = function( options ) {
if ( templatesCollection && ! options.forceUpdate ) {
if ( options.onUpdate ) {
options.onUpdate();
}
return;
}
if ( options.onBeforeUpdate ) {
options.onBeforeUpdate();
}
var ajaxOptions = {
data: {},
success: function( data ) {
templatesCollection = new TemplateLibraryCollection( data.templates );
if ( data.config ) {
config = data.config;
}
if ( options.onUpdate ) {
options.onUpdate();
}
},
};
if ( options.forceSync ) {
ajaxOptions.data.sync = true;
}
elementorCommon.ajax.addRequest( 'get_library_data', ajaxOptions );
};
this.getFilter = function( name ) {
return elementor.channels.templates.request( 'filter:' + name );
};
this.setFilter = function( name, value, silent ) {
elementor.channels.templates.reply( 'filter:' + name, value );
if ( ! silent ) {
elementor.channels.templates.trigger( 'filter:change' );
}
};
this.getFilterTerms = function( termName ) {
if ( termName ) {
return filterTerms[ termName ];
}
return filterTerms;
};
this.setScreen = function( args ) {
elementor.channels.templates.stopReplying();
self.setFilter( 'source', args.source, true );
self.setFilter( 'type', args.type, true );
self.setFilter( 'subtype', args.subtype, true );
self.showTemplates();
};
this.loadTemplates = function( onUpdate ) {
self.requestLibraryData( {
onBeforeUpdate: self.layout.showLoadingView.bind( self.layout ),
onUpdate: function() {
self.layout.hideLoadingView();
if ( onUpdate ) {
onUpdate();
}
},
} );
};
this.showTemplates = function() {
// The tabs should exist in DOM on loading.
self.layout.setHeaderDefaultParts();
self.loadTemplates( function() {
var templatesToShow = self.filterTemplates();
self.layout.showTemplatesView( new TemplateLibraryCollection( templatesToShow ) );
} );
};
this.filterTemplates = function() {
const activeSource = self.getFilter( 'source' );
return templatesCollection.filter( function( model ) {
if ( activeSource !== model.get( 'source' ) ) {
return false;
}
var typeInfo = templateTypes[ model.get( 'type' ) ];
return ! typeInfo || false !== typeInfo.showInLibrary;
} );
};
this.showErrorDialog = function( errorMessage ) {
if ( 'object' === typeof errorMessage ) {
var message = '';
_.each( errorMessage, function( error ) {
if ( ! error.message ) {
return;
}
message += '<div>' + error.message + '.</div>';
} );
errorMessage = message;
} else if ( errorMessage ) {
errorMessage += '.';
}
if ( errorMessage ) {
errorMessage = __( 'The following error(s) occurred while processing the request:', 'elementor' ) +
'<div id="elementor-template-library-error-info">' + errorMessage + '</div>';
} else {
errorMessage = __( 'Please try again.', 'elementor' );
}
self.getErrorDialog()
.setMessage( errorMessage )
.show();
};
};
module.exports = new TemplateLibraryManager();

View File

@@ -0,0 +1,14 @@
module.exports = Backbone.Model.extend( {
defaults: {
template_id: 0,
title: '',
source: '',
type: '',
subtype: '',
author: '',
thumbnail: '',
url: '',
export_link: '',
tags: [],
},
} );

View File

@@ -0,0 +1,106 @@
var TemplateLibraryHeaderActionsView = require( 'elementor-templates/views/parts/header-parts/actions' ),
TemplateLibraryHeaderMenuView = require( 'elementor-templates/views/parts/header-parts/menu' ),
TemplateLibraryHeaderPreviewView = require( 'elementor-templates/views/parts/header-parts/preview' ),
TemplateLibraryHeaderBackView = require( 'elementor-templates/views/parts/header-parts/back' ),
TemplateLibraryCollectionView = require( 'elementor-templates/views/parts/templates' ),
TemplateLibrarySaveTemplateView = require( 'elementor-templates/views/parts/save-template' ),
TemplateLibraryImportView = require( 'elementor-templates/views/parts/import' ),
TemplateLibraryConnectView = require( 'elementor-templates/views/parts/connect' ),
TemplateLibraryPreviewView = require( 'elementor-templates/views/parts/preview' );
module.exports = elementorModules.common.views.modal.Layout.extend( {
getModalOptions: function() {
return {
id: 'elementor-template-library-modal',
};
},
getLogoOptions: function() {
return {
title: __( 'Library', 'elementor' ),
click: function() {
$e.run( 'library/open', { toDefault: true } );
},
};
},
getTemplateActionButton: function( templateData ) {
const subscriptionPlans = elementor.config.library_connect.subscription_plans,
baseAccessLevel = elementor.config.library_connect.base_access_level;
let viewId = '#tmpl-elementor-template-library-' + ( baseAccessLevel !== templateData.accessLevel ? 'upgrade-plan-button' : 'insert-button' );
viewId = elementor.hooks.applyFilters( 'elementor/editor/template-library/template/action-button', viewId, templateData );
const template = Marionette.TemplateCache.get( viewId );
// In case the access level of the template is not one of the defined.
// it will find the next access level that was defined.
// Example: access_level = 15, and access_level 15 is not exists in the plans the button will be "Go Expert" which is 20
const closestAccessLevel = Object.keys( subscriptionPlans )
.sort()
.find( ( accessLevel ) => {
return accessLevel >= templateData.accessLevel;
} );
const subscriptionPlan = subscriptionPlans[ closestAccessLevel ];
return Marionette.Renderer.render( template, {
promotionText: `Go ${ subscriptionPlan.label }`,
promotionLink: subscriptionPlan.promotion_url,
} );
},
setHeaderDefaultParts: function() {
var headerView = this.getHeaderView();
headerView.tools.show( new TemplateLibraryHeaderActionsView() );
headerView.menuArea.show( new TemplateLibraryHeaderMenuView() );
this.showLogo();
},
showTemplatesView: function( templatesCollection ) {
this.modalContent.show( new TemplateLibraryCollectionView( {
collection: templatesCollection,
} ) );
},
showImportView: function() {
const headerView = this.getHeaderView();
headerView.menuArea.reset();
this.modalContent.show( new TemplateLibraryImportView() );
headerView.logoArea.show( new TemplateLibraryHeaderBackView() );
},
showConnectView: function( args ) {
this.getHeaderView().menuArea.reset();
this.modalContent.show( new TemplateLibraryConnectView( args ) );
},
showSaveTemplateView: function( elementModel ) {
this.getHeaderView().menuArea.reset();
this.modalContent.show( new TemplateLibrarySaveTemplateView( { model: elementModel } ) );
},
showPreviewView: function( templateModel ) {
this.modalContent.show( new TemplateLibraryPreviewView( {
url: templateModel.get( 'url' ),
} ) );
var headerView = this.getHeaderView();
headerView.menuArea.reset();
headerView.tools.show( new TemplateLibraryHeaderPreviewView( {
model: templateModel,
} ) );
headerView.logoArea.show( new TemplateLibraryHeaderBackView() );
},
} );

View File

@@ -0,0 +1,37 @@
module.exports = Marionette.ItemView.extend( {
template: '#tmpl-elementor-template-library-connect',
id: 'elementor-template-library-connect',
ui: {
connect: '#elementor-template-library-connect__button',
thumbnails: '#elementor-template-library-connect-thumbnails',
},
templateHelpers: function() {
return this.getOption( 'texts' );
},
onRender: function() {
this.ui.connect.elementorConnect( {
success: () => {
elementor.config.library_connect.is_connected = true;
// If is connecting during insert template.
if ( this.getOption( 'model' ) ) {
$e.run( 'library/insert-template', {
model: this.getOption( 'model' ),
} );
} else {
$e.run( 'library/close' );
elementor.notifications.showToast( {
message: __( 'Connected successfully', 'elementor' ),
} );
}
},
error: () => {
elementor.config.library_connect.is_connected = false;
},
} );
},
} );

View File

@@ -0,0 +1,41 @@
module.exports = Marionette.ItemView.extend( {
template: '#tmpl-elementor-template-library-header-actions',
id: 'elementor-template-library-header-actions',
ui: {
import: '#elementor-template-library-header-import i',
sync: '#elementor-template-library-header-sync i',
save: '#elementor-template-library-header-save i',
},
events: {
'click @ui.import': 'onImportClick',
'click @ui.sync': 'onSyncClick',
'click @ui.save': 'onSaveClick',
},
onImportClick: function() {
$e.route( 'library/import' );
},
onSyncClick: function() {
var self = this;
self.ui.sync.addClass( 'eicon-animation-spin' );
elementor.templates.requestLibraryData( {
onUpdate: function() {
self.ui.sync.removeClass( 'eicon-animation-spin' );
$e.routes.refreshContainer( 'library' );
},
forceUpdate: true,
forceSync: true,
} );
},
onSaveClick: function() {
$e.route( 'library/save-template' );
},
} );

View File

@@ -0,0 +1,13 @@
module.exports = Marionette.ItemView.extend( {
template: '#tmpl-elementor-template-library-header-back',
id: 'elementor-template-library-header-preview-back',
events: {
click: 'onClick',
},
onClick: function() {
$e.routes.restoreState( 'library' );
},
} );

View File

@@ -0,0 +1,11 @@
module.exports = Marionette.ItemView.extend( {
template: '#tmpl-elementor-template-library-header-menu',
id: 'elementor-template-library-header-menu',
templateHelpers: function() {
return {
tabs: $e.components.get( 'library' ).getTabs(),
};
},
} );

View File

@@ -0,0 +1,13 @@
var TemplateLibraryInsertTemplateBehavior = require( 'elementor-templates/behaviors/insert-template' );
module.exports = Marionette.ItemView.extend( {
template: '#tmpl-elementor-template-library-header-preview',
id: 'elementor-template-library-header-preview',
behaviors: {
insertTemplate: {
behaviorClass: TemplateLibraryInsertTemplateBehavior,
},
},
} );

View File

@@ -0,0 +1,104 @@
var TemplateLibraryImportView;
TemplateLibraryImportView = Marionette.ItemView.extend( {
template: '#tmpl-elementor-template-library-import',
id: 'elementor-template-library-import',
ui: {
uploadForm: '#elementor-template-library-import-form',
fileInput: '#elementor-template-library-import-form-input',
},
events: {
'change @ui.fileInput': 'onFileInputChange',
},
droppedFiles: null,
submitForm: function() {
let file;
if ( this.droppedFiles ) {
file = this.droppedFiles[ 0 ];
this.droppedFiles = null;
} else {
file = this.ui.fileInput[ 0 ].files[ 0 ];
this.ui.uploadForm[ 0 ].reset();
}
const fileReader = new FileReader();
fileReader.onload = ( event ) => this.importTemplate( file.name, event.target.result.replace( /^[^,]+,/, '' ) );
fileReader.readAsDataURL( file );
},
importTemplate: function( fileName, fileData ) {
const layout = elementor.templates.layout;
const options = {
data: {
fileName: fileName,
fileData: fileData,
},
success: ( successData ) => {
elementor.templates.getTemplatesCollection().add( successData );
$e.route( 'library/templates/my-templates' );
},
error: ( errorData ) => {
elementor.templates.showErrorDialog( errorData );
layout.showImportView();
},
complete: () => {
layout.hideLoadingView();
},
};
elementorCommon.ajax.addRequest( 'import_template', options );
layout.showLoadingView();
},
onRender: function() {
this.ui.uploadForm.on( {
'drag dragstart dragend dragover dragenter dragleave drop': this.onFormActions.bind( this ),
dragenter: this.onFormDragEnter.bind( this ),
'dragleave drop': this.onFormDragLeave.bind( this ),
drop: this.onFormDrop.bind( this ),
} );
},
onFormActions: function( event ) {
event.preventDefault();
event.stopPropagation();
},
onFormDragEnter: function() {
this.ui.uploadForm.addClass( 'elementor-drag-over' );
},
onFormDragLeave: function( event ) {
if ( jQuery( event.relatedTarget ).closest( this.ui.uploadForm ).length ) {
return;
}
this.ui.uploadForm.removeClass( 'elementor-drag-over' );
},
onFormDrop: function( event ) {
this.droppedFiles = event.originalEvent.dataTransfer.files;
this.submitForm();
},
onFileInputChange: function() {
this.submitForm();
},
} );
module.exports = TemplateLibraryImportView;

View File

@@ -0,0 +1,17 @@
var TemplateLibraryPreviewView;
TemplateLibraryPreviewView = Marionette.ItemView.extend( {
template: '#tmpl-elementor-template-library-preview',
id: 'elementor-template-library-preview',
ui: {
iframe: '> iframe',
},
onRender: function() {
this.ui.iframe.attr( 'src', this.getOption( 'url' ) );
},
} );
module.exports = TemplateLibraryPreviewView;

View File

@@ -0,0 +1,52 @@
var TemplateLibrarySaveTemplateView;
TemplateLibrarySaveTemplateView = Marionette.ItemView.extend( {
id: 'elementor-template-library-save-template',
template: '#tmpl-elementor-template-library-save-template',
ui: {
form: '#elementor-template-library-save-template-form',
submitButton: '#elementor-template-library-save-template-submit',
},
events: {
'submit @ui.form': 'onFormSubmit',
},
getSaveType: function() {
let type;
if ( this.model ) {
type = this.model.get( 'elType' );
} else if ( elementor.config.document.library && elementor.config.document.library.save_as_same_type ) {
type = elementor.config.document.type;
} else {
type = 'page';
}
return type;
},
templateHelpers: function() {
var saveType = this.getSaveType(),
templateType = elementor.templates.getTemplateTypes( saveType );
return templateType.saveDialog;
},
onFormSubmit: function( event ) {
event.preventDefault();
var formData = this.ui.form.elementorSerializeObject(),
saveType = this.getSaveType(),
JSONParams = { remove: [ 'default' ] };
formData.content = this.model ? [ this.model.toJSON( JSONParams ) ] : elementor.elements.toJSON( JSONParams );
this.ui.submitButton.addClass( 'elementor-button-state' );
elementor.templates.saveTemplate( saveType, formData );
},
} );
module.exports = TemplateLibrarySaveTemplateView;

View File

@@ -0,0 +1,49 @@
var TemplateLibraryTemplatesEmptyView;
TemplateLibraryTemplatesEmptyView = Marionette.ItemView.extend( {
id: 'elementor-template-library-templates-empty',
template: '#tmpl-elementor-template-library-templates-empty',
ui: {
title: '.elementor-template-library-blank-title',
message: '.elementor-template-library-blank-message',
},
modesStrings: {
empty: {
title: __( 'Havent Saved Templates Yet?', 'elementor' ),
message: __( 'This is where your templates should be. Design it. Save it. Reuse it.', 'elementor' ),
},
noResults: {
title: __( 'No Results Found', 'elementor' ),
message: __( 'Please make sure your search is spelled correctly or try a different words.', 'elementor' ),
},
noFavorites: {
title: __( 'No Favorite Templates', 'elementor' ),
message: __( 'You can mark any pre-designed template as a favorite.', 'elementor' ),
},
},
getCurrentMode: function() {
if ( elementor.templates.getFilter( 'text' ) ) {
return 'noResults';
}
if ( elementor.templates.getFilter( 'favorite' ) ) {
return 'noFavorites';
}
return 'empty';
},
onRender: function() {
var modeStrings = this.modesStrings[ this.getCurrentMode() ];
this.ui.title.html( modeStrings.title );
this.ui.message.html( modeStrings.message );
},
} );
module.exports = TemplateLibraryTemplatesEmptyView;

View File

@@ -0,0 +1,240 @@
var TemplateLibraryTemplateLocalView = require( 'elementor-templates/views/template/local' ),
TemplateLibraryTemplateRemoteView = require( 'elementor-templates/views/template/remote' ),
TemplateLibraryCollectionView;
import Select2 from 'elementor-editor-utils/select2.js';
TemplateLibraryCollectionView = Marionette.CompositeView.extend( {
template: '#tmpl-elementor-template-library-templates',
id: 'elementor-template-library-templates',
childViewContainer: '#elementor-template-library-templates-container',
reorderOnSort: true,
emptyView: function() {
var EmptyView = require( 'elementor-templates/views/parts/templates-empty' );
return new EmptyView();
},
ui: {
textFilter: '#elementor-template-library-filter-text',
selectFilter: '.elementor-template-library-filter-select',
myFavoritesFilter: '#elementor-template-library-filter-my-favorites',
orderInputs: '.elementor-template-library-order-input',
orderLabels: 'label.elementor-template-library-order-label',
},
events: {
'input @ui.textFilter': 'onTextFilterInput',
'change @ui.selectFilter': 'onSelectFilterChange',
'change @ui.myFavoritesFilter': 'onMyFavoritesFilterChange',
'mousedown @ui.orderLabels': 'onOrderLabelsClick',
},
comparators: {
title: function( model ) {
return model.get( 'title' ).toLowerCase();
},
popularityIndex: function( model ) {
var popularityIndex = model.get( 'popularityIndex' );
if ( ! popularityIndex ) {
popularityIndex = model.get( 'date' );
}
return -popularityIndex;
},
trendIndex: function( model ) {
var trendIndex = model.get( 'trendIndex' );
if ( ! trendIndex ) {
trendIndex = model.get( 'date' );
}
return -trendIndex;
},
},
getChildView: function( childModel ) {
if ( 'remote' === childModel.get( 'source' ) ) {
return TemplateLibraryTemplateRemoteView;
}
return TemplateLibraryTemplateLocalView;
},
initialize: function() {
this.listenTo( elementor.channels.templates, 'filter:change', this._renderChildren );
},
filter: function( childModel ) {
var filterTerms = elementor.templates.getFilterTerms(),
passingFilter = true;
jQuery.each( filterTerms, function( filterTermName ) {
var filterValue = elementor.templates.getFilter( filterTermName );
if ( ! filterValue ) {
return;
}
if ( this.callback ) {
var callbackResult = this.callback.call( childModel, filterValue );
if ( ! callbackResult ) {
passingFilter = false;
}
return callbackResult;
}
var filterResult = filterValue === childModel.get( filterTermName );
if ( ! filterResult ) {
passingFilter = false;
}
return filterResult;
} );
return passingFilter;
},
order: function( by, reverseOrder ) {
var comparator = this.comparators[ by ] || by;
if ( reverseOrder ) {
comparator = this.reverseOrder( comparator );
}
this.collection.comparator = comparator;
this.collection.sort();
},
reverseOrder: function( comparator ) {
if ( 'function' !== typeof comparator ) {
var comparatorValue = comparator;
comparator = function( model ) {
return model.get( comparatorValue );
};
}
return function( left, right ) {
var l = comparator( left ),
r = comparator( right );
if ( undefined === l ) {
return -1;
}
if ( undefined === r ) {
return 1;
}
if ( l < r ) {
return 1;
}
if ( l > r ) {
return -1;
}
return 0;
};
},
addSourceData: function() {
var isEmpty = this.children.isEmpty();
this.$el.attr( 'data-template-source', isEmpty ? 'empty' : elementor.templates.getFilter( 'source' ) );
},
setFiltersUI: function() {
if ( ! this.select2Instance ) {
const $filters = this.$( this.ui.selectFilter ),
select2Options = {
placeholder: __( 'Category', 'elementor' ),
allowClear: true,
width: 150,
dropdownParent: this.$el,
};
this.select2Instance = new Select2( {
$element: $filters,
options: select2Options,
} );
}
},
setMasonrySkin: function() {
var masonry = new elementorModules.utils.Masonry( {
container: this.$childViewContainer,
items: this.$childViewContainer.children(),
} );
this.$childViewContainer.imagesLoaded( masonry.run.bind( masonry ) );
},
toggleFilterClass: function() {
this.$el.toggleClass( 'elementor-templates-filter-active', ! ! ( elementor.templates.getFilter( 'text' ) || elementor.templates.getFilter( 'favorite' ) ) );
},
isPageOrLandingPageTemplates: function() {
const templatesType = elementor.templates.getFilter( 'type' );
return 'page' === templatesType || 'lp' === templatesType;
},
onRender() {
if ( 'remote' === elementor.templates.getFilter( 'source' ) && 'page' !== elementor.templates.getFilter( 'type' ) ) {
this.setFiltersUI();
}
},
onRenderCollection: function() {
this.addSourceData();
this.toggleFilterClass();
if ( 'remote' === elementor.templates.getFilter( 'source' ) && ! this.isPageOrLandingPageTemplates() ) {
this.setMasonrySkin();
}
},
onBeforeRenderEmpty: function() {
this.addSourceData();
},
onTextFilterInput: function() {
elementor.templates.setFilter( 'text', this.ui.textFilter.val() );
},
onSelectFilterChange: function( event ) {
var $select = jQuery( event.currentTarget ),
filterName = $select.data( 'elementor-filter' );
elementor.templates.setFilter( filterName, $select.val() );
},
onMyFavoritesFilterChange: function() {
elementor.templates.setFilter( 'favorite', this.ui.myFavoritesFilter[ 0 ].checked );
},
onOrderLabelsClick: function( event ) {
var $clickedInput = jQuery( event.currentTarget.control ),
toggle;
if ( ! $clickedInput[ 0 ].checked ) {
toggle = 'asc' !== $clickedInput.data( 'default-ordering-direction' );
}
$clickedInput.toggleClass( 'elementor-template-library-order-reverse', toggle );
this.order( $clickedInput.val(), $clickedInput.hasClass( 'elementor-template-library-order-reverse' ) );
},
} );
module.exports = TemplateLibraryCollectionView;

View File

@@ -0,0 +1,53 @@
var TemplateLibraryInsertTemplateBehavior = require( 'elementor-templates/behaviors/insert-template' ),
TemplateLibraryTemplateView;
TemplateLibraryTemplateView = Marionette.ItemView.extend( {
className: function() {
var classes = 'elementor-template-library-template',
source = this.model.get( 'source' );
classes += ' elementor-template-library-template-' + source;
if ( 'remote' === source ) {
classes += ' elementor-template-library-template-' + this.model.get( 'type' );
}
if ( elementor.config.library_connect.base_access_level !== this.model.get( 'accessLevel' ) ) {
classes += ' elementor-template-library-pro-template';
}
return classes;
},
attributes: function() {
const subscriptionPlan = elementor.config.library_connect.subscription_plans[ this.model.get( 'accessLevel' ) ];
if ( ! subscriptionPlan ) {
return {};
}
return {
style: `--elementor-template-library-subscription-plan-label: "${ subscriptionPlan.label }";--elementor-template-library-subscription-plan-color: ${ subscriptionPlan.color };`,
};
},
ui: function() {
return {
previewButton: '.elementor-template-library-template-preview',
};
},
events: function() {
return {
'click @ui.previewButton': 'onPreviewButtonClick',
};
},
behaviors: {
insertTemplate: {
behaviorClass: TemplateLibraryInsertTemplateBehavior,
},
},
} );
module.exports = TemplateLibraryTemplateView;

View File

@@ -0,0 +1,45 @@
var TemplateLibraryTemplateView = require( 'elementor-templates/views/template/base' ),
TemplateLibraryTemplateLocalView;
TemplateLibraryTemplateLocalView = TemplateLibraryTemplateView.extend( {
template: '#tmpl-elementor-template-library-template-local',
ui: function() {
return _.extend( TemplateLibraryTemplateView.prototype.ui.apply( this, arguments ), {
deleteButton: '.elementor-template-library-template-delete',
morePopup: '.elementor-template-library-template-more',
toggleMore: '.elementor-template-library-template-more-toggle',
toggleMoreIcon: '.elementor-template-library-template-more-toggle i',
} );
},
events: function() {
return _.extend( TemplateLibraryTemplateView.prototype.events.apply( this, arguments ), {
'click @ui.deleteButton': 'onDeleteButtonClick',
'click @ui.toggleMore': 'onToggleMoreClick',
} );
},
onDeleteButtonClick: function() {
var toggleMoreIcon = this.ui.toggleMoreIcon;
elementor.templates.deleteTemplate( this.model, {
onConfirm: function() {
toggleMoreIcon.removeClass( 'eicon-ellipsis-h' ).addClass( 'eicon-loading eicon-animation-spin' );
},
onSuccess: function() {
elementor.templates.showTemplates();
},
} );
},
onToggleMoreClick: function() {
this.ui.morePopup.show();
},
onPreviewButtonClick: function() {
open( this.model.get( 'url' ), '_blank' );
},
} );
module.exports = TemplateLibraryTemplateLocalView;

View File

@@ -0,0 +1,36 @@
var TemplateLibraryTemplateView = require( 'elementor-templates/views/template/base' ),
TemplateLibraryTemplateRemoteView;
TemplateLibraryTemplateRemoteView = TemplateLibraryTemplateView.extend( {
template: '#tmpl-elementor-template-library-template-remote',
ui: function() {
return jQuery.extend( TemplateLibraryTemplateView.prototype.ui.apply( this, arguments ), {
favoriteCheckbox: '.elementor-template-library-template-favorite-input',
} );
},
events: function() {
return jQuery.extend( TemplateLibraryTemplateView.prototype.events.apply( this, arguments ), {
'change @ui.favoriteCheckbox': 'onFavoriteCheckboxChange',
} );
},
onPreviewButtonClick: function() {
$e.route( 'library/preview', { model: this.model } );
},
onFavoriteCheckboxChange: function() {
var isFavorite = this.ui.favoriteCheckbox[ 0 ].checked;
this.model.set( 'favorite', isFavorite );
elementor.templates.markAsFavorite( this.model, isFavorite );
if ( ! isFavorite && elementor.templates.getFilter( 'favorite' ) ) {
elementor.channels.templates.trigger( 'filter:change' );
}
},
} );
module.exports = TemplateLibraryTemplateRemoteView;

View File

@@ -0,0 +1,42 @@
module.exports = elementorModules.Module.extend( {
errors: [],
__construct: function( settings ) {
var customValidationMethod = settings.customValidationMethod;
if ( customValidationMethod ) {
this.validationMethod = customValidationMethod;
}
},
getDefaultSettings: function() {
return {
validationTerms: {},
};
},
isValid: function() {
var validationErrors = this.validationMethod.apply( this, arguments );
if ( validationErrors.length ) {
this.errors = validationErrors;
return false;
}
return true;
},
validationMethod: function( newValue ) {
var validationTerms = this.getSettings( 'validationTerms' ),
errors = [];
if ( validationTerms.required ) {
if ( ! ( '' + newValue ).length ) {
errors.push( 'Required value is empty' );
}
}
return errors;
},
} );

View File

@@ -0,0 +1,20 @@
var Validator = require( 'elementor-validator/base' );
module.exports = Validator.extend( {
validationMethod: function( newValue ) {
var validationTerms = this.getSettings( 'validationTerms' ),
errors = [];
if ( _.isFinite( newValue ) ) {
if ( undefined !== validationTerms.min && newValue < validationTerms.min ) {
errors.push( 'Value is less than minimum' );
}
if ( undefined !== validationTerms.max && newValue > validationTerms.max ) {
errors.push( 'Value is greater than maximum' );
}
}
return errors;
},
} );

View File

@@ -0,0 +1,340 @@
import ArgsObject from '../../modules/imports/args-object';
import Panel from './panel';
/**
* TODO: ViewsOptions
* @typedef {(Marionette.View|Marionette.CompositeView|BaseElementView|SectionView|BaseContainer)} ViewsOptions
*/
export default class Container extends ArgsObject {
/**
* Container type.
*
* @type {string}
*/
type;
/**
* Container id.
*
* @type {string}
*/
id;
/**
* Document Object.
*
* @type {{}}
*/
document;
/**
* Container model.
*
* @type {Backbone.Model}
*/
model;
/**
* Container settings.
*
* @type {Backbone.Model}
*/
settings;
/**
* Container view.
*
* @type {ViewsOptions}
*/
view;
/**
* Container parent.
*
* @type {Container}
*/
parent;
/**
* Container children(s).
*
* @type {Array}
*/
children = [];
/**
* Container dynamic.
*
* @type {Backbone.Model}
*/
dynamic;
/**
* Container globals.
*
* @type {Backbone.Model}
*/
globals;
/**
* Container label.
*
* @type {string}
*/
label;
/**
* Container controls.
*
* @type {{}}
*/
controls = {};
/**
* Repeaters containers
*
* @type {{}}
*/
repeaters = {};
/**
* Container renderer (The one who render).
*
* @type {Container}
*/
renderer;
/**
* Container panel.
*
* @type {Panel}
*/
panel;
/**
* Function constructor().
*
* Create container.
*
* @param {{}} args
*
* @throws {Error}
*/
constructor( args ) {
super( args );
// Validate args.
this.validateArgs( args );
args = Object.entries( args );
// If empty.
if ( 0 === args.length ) {
throw Error( 'Container cannot be empty.' );
}
// Set properties, if not defined - keep the defaults.
args.forEach( ( [ key, value ] ) => {
this[ key ] = 'undefined' === typeof value ? this[ key ] : value;
} );
if ( 'undefined' === typeof this.renderer ) {
this.renderer = this;
}
if ( ! this.document ) {
this.document = elementor.documents.getCurrent();
}
this.dynamic = new Backbone.Model( this.settings.get( '__dynamic__' ) );
this.globals = new Backbone.Model( this.settings.get( '__globals__' ) );
this.panel = new Panel( this );
this.handleRepeaterChildren();
}
validateArgs( args ) {
this.requireArgumentType( 'type', 'string', args );
this.requireArgumentType( 'id', 'string', args );
this.requireArgumentInstance( 'settings', Backbone.Model, args );
this.requireArgumentInstance( 'model', Backbone.Model, args );
}
/**
* Function getGroupRelatedControls().
*
* Example:
* Settings = { typography_typography: 'whatever', button_text_color: 'whatever' };
* Result { control_name: controlValue, ... - and so on };
* `Object.keys( Result ) = [ 'typography_typography', 'typography_font_family', 'typography_font_size', 'typography_font_size_tablet', 'typography_font_size_mobile', 'typography_font_weight', 'typography_text_transform', 'typography_font_style', 'typography_text_decoration', 'typography_line_height', 'typography_line_height_tablet', 'typography_line_height_mobile', 'typography_letter_spacing', 'typography_letter_spacing_tablet', 'typography_letter_spacing_mobile', 'button_text_color' ]`.
*
* @param {{}} settings
*
* @return {{}}
*/
getGroupRelatedControls( settings ) {
const result = {};
Object.keys( settings ).forEach( ( settingKey ) => {
Object.values( this.controls ).forEach( ( control ) => {
if ( settingKey === control.name ) {
result[ control.name ] = control;
} else if ( this.controls[ settingKey ]?.groupPrefix ) {
const { groupPrefix } = this.controls[ settingKey ];
if ( control.name.toString().startsWith( groupPrefix ) ) {
result[ control.name ] = control;
}
}
} );
} );
return result;
}
handleRepeaterChildren() {
Object.values( this.controls ).forEach( ( control ) => {
if ( ! control.is_repeater ) {
return;
}
const model = new Backbone.Model( {
name: control.name,
} );
this.repeaters[ control.name ] = new elementorModules.editor.Container( {
// TODO: replace to `repeater`, and the item should by `repeater-item`.
type: 'repeater-control',
id: control.name,
model,
settings: model,
view: this.view,
parent: this,
label: control.label || control.name,
controls: {},
renderer: this.renderer,
} );
this.settings.get( control.name ).forEach( ( rowModel, index ) => {
this.addRepeaterItem( control.name, rowModel, index );
} );
} );
// Backwards Compatibility: if there is only one repeater (type=repeater), set it's children as current children.
// Since 3.0.0.
if ( [ 'widget', 'document' ].includes( this.type ) ) {
const repeaters = Object.values( this.controls ).filter( ( control ) => 'repeater' === control.type );
if ( 1 === repeaters.length ) {
Object.defineProperty( this, 'children', {
get() {
elementorCommon.helpers.softDeprecated( 'children', '3.0.0', 'container.repeaters[ repeaterName ].children' );
return this.repeaters[ repeaters[ 0 ].name ].children;
},
} );
}
}
}
addRepeaterItem( repeaterName, rowSettingsModel, index ) {
let rowId = rowSettingsModel.get( '_id' );
// TODO: Temp backwards compatibility. since 2.8.0.
if ( ! rowId ) {
rowId = 'bc-' + elementorCommon.helpers.getUniqueId();
rowSettingsModel.set( '_id', rowId );
}
this.repeaters[ repeaterName ].children.splice( index, 0, new elementorModules.editor.Container( {
type: 'repeater',
id: rowSettingsModel.get( '_id' ),
model: new Backbone.Model( {
name: repeaterName,
} ),
settings: rowSettingsModel,
view: this.view,
parent: this.repeaters[ repeaterName ],
label: this.label + ' ' + __( 'Item', 'elementor' ),
controls: rowSettingsModel.options.controls,
renderer: this.renderer,
} ) );
}
/**
* Function lookup().
*
* If the view were destroyed, try to find it again if it exists.
*
* TODO: Refactor.
*
* @returns {Container}
*/
lookup() {
let result = this;
if ( ! this.renderer ) {
return this;
}
if ( this !== this.renderer && this.renderer.view?.isDisconnected && this.renderer.view.isDisconnected() ) {
this.renderer = this.renderer.lookup();
}
if ( undefined === this.view || ! this.view.lookup || ! this.view.isDisconnected() ) {
// Hack For repeater item the result is the parent container.
if ( 'repeater' === this.type ) {
this.settings = this.parent.parent.settings.get( this.model.get( 'name' ) ).findWhere( { _id: this.id } );
}
return result;
}
const lookup = this.view.lookup();
if ( lookup ) {
result = lookup.getContainer();
// Hack For repeater item the result is the parent container.
if ( 'repeater' === this.type ) {
this.settings = result.settings.get( this.model.get( 'name' ) ).findWhere( { _id: this.id } );
return this;
}
}
return result;
}
/**
* Function render().
*
* Call view render.
*
* Run's `this.renderer.view.renderOnChange( this.settings ) `.
* When `this.renderer` exist.
*
*/
render() {
if ( ! this.renderer ) {
return;
}
this.renderer.view.renderOnChange( this.settings );
}
renderUI() {
if ( ! this.renderer ) {
return;
}
this.renderer.view.renderUI();
}
isEditable() {
return 'edit' === elementor.channels.dataEditMode.request( 'activeMode' ) && 'open' === this.document.editor.status;
}
isDesignable() {
return elementor.userCan( 'design' ) && this.isEditable();
}
}

View File

@@ -0,0 +1,44 @@
export default class Panel {
/**
* Function constructor().
*
* Create constructor panel.
*
* @param {Container} container
*/
constructor( container ) {
this.container = container;
}
/**
* Function refresh().
*
* Refresh the panel.
*/
refresh() {
if ( $e.routes.isPartOf( 'panel/editor' ) ) {
$e.routes.refreshContainer( 'panel' );
}
}
/**
* Function closeEditor().
*
* Route to `panel/elements/categories`
*/
closeEditor() {
$e.route( 'panel/elements/categories' );
}
getControlView( name ) {
const editor = elementor.getPanelView().getCurrentPageView();
return editor.children.findByModelCid( this.getControlModel( name ).cid );
}
getControlModel( name ) {
const editor = elementor.getPanelView().getCurrentPageView();
return editor.collection.findWhere( { name: name } );
}
}

View File

@@ -0,0 +1,350 @@
var ControlBaseView = require( 'elementor-controls/base' ),
TagsBehavior = require( 'elementor-dynamic-tags/control-behavior' ),
Validator = require( 'elementor-validator/base' ),
ControlBaseDataView;
ControlBaseDataView = ControlBaseView.extend( {
ui: function() {
var ui = ControlBaseView.prototype.ui.apply( this, arguments );
_.extend( ui, {
input: 'input[data-setting][type!="checkbox"][type!="radio"]',
checkbox: 'input[data-setting][type="checkbox"]',
radio: 'input[data-setting][type="radio"]',
select: 'select[data-setting]',
textarea: 'textarea[data-setting]',
responsiveSwitchers: '.elementor-responsive-switcher',
contentEditable: '[contenteditable="true"]',
} );
return ui;
},
templateHelpers: function() {
var controlData = ControlBaseView.prototype.templateHelpers.apply( this, arguments );
controlData.data.controlValue = this.getControlValue();
return controlData;
},
events: function() {
return {
'input @ui.input': 'onBaseInputTextChange',
'change @ui.checkbox': 'onBaseInputChange',
'change @ui.radio': 'onBaseInputChange',
'input @ui.textarea': 'onBaseInputTextChange',
'change @ui.select': 'onBaseInputChange',
'input @ui.contentEditable': 'onBaseInputTextChange',
'click @ui.responsiveSwitchers': 'onResponsiveSwitchersClick',
};
},
behaviors: function() {
const behaviors = ControlBaseView.prototype.behaviors.apply( this, arguments ),
dynamicSettings = this.options.model.get( 'dynamic' );
if ( dynamicSettings && dynamicSettings.active ) {
const tags = _.filter( elementor.dynamicTags.getConfig( 'tags' ), function( tag ) {
return tag.editable && _.intersection( tag.categories, dynamicSettings.categories ).length;
} );
if ( tags.length || elementor.config.user.is_administrator ) {
behaviors.tags = {
behaviorClass: TagsBehavior,
tags: tags,
dynamicSettings: dynamicSettings,
};
}
}
return behaviors;
},
initialize: function() {
ControlBaseView.prototype.initialize.apply( this, arguments );
this.registerValidators();
// TODO: this.elementSettingsModel is deprecated since 2.8.0.
const settings = this.container ? this.container.settings : this.elementSettingsModel;
this.listenTo( settings, 'change:external:' + this.model.get( 'name' ), this.onAfterExternalChange );
},
getControlValue: function() {
return this.container.settings.get( this.model.get( 'name' ) );
},
getGlobalKey: function() {
return this.container.globals.get( this.model.get( 'name' ) );
},
getGlobalValue: function() {
return this.globalValue;
},
getGlobalDefault: function() {
const controlGlobalArgs = this.model.get( 'global' );
if ( controlGlobalArgs?.default ) {
// If the control is a color/typography control and default colors/typography are disabled, don't return the global value.
if ( ! elementor.config.globals.defaults_enabled[ this.getGlobalMeta().controlType ] ) {
return '';
}
const { command, args } = $e.data.commandExtractArgs( controlGlobalArgs.default ),
result = $e.data.getCache( $e.components.get( 'globals' ), command, args.query );
return result?.value;
}
// No global default.
return '';
},
getCurrentValue: function() {
if ( this.getGlobalKey() && ! this.globalValue ) {
return '';
}
if ( this.globalValue ) {
return this.globalValue;
}
const controlValue = this.getControlValue();
if ( controlValue ) {
return controlValue;
}
return this.getGlobalDefault();
},
isGlobalActive: function() {
return this.options.model.get( 'global' )?.active;
},
setValue: function( value ) {
this.setSettingsModel( value );
},
setSettingsModel: function( value ) {
const key = this.model.get( 'name' );
$e.run( 'document/elements/settings', {
container: this.options.container,
settings: {
[ key ]: value,
},
} );
this.triggerMethod( 'settings:change' );
},
applySavedValue: function() {
this.setInputValue( '[data-setting="' + this.model.get( 'name' ) + '"]', this.getControlValue() );
},
getEditSettings: function( setting ) {
var settings = this.getOption( 'elementEditSettings' ).toJSON();
if ( setting ) {
return settings[ setting ];
}
return settings;
},
setEditSetting: function( settingKey, settingValue ) {
const settings = this.getOption( 'elementEditSettings' ) || this.getOption( 'container' ).settings;
settings.set( settingKey, settingValue );
},
getInputValue: function( input ) {
var $input = this.$( input );
if ( $input.is( '[contenteditable="true"]' ) ) {
return $input.html();
}
var inputValue = $input.val(),
inputType = $input.attr( 'type' );
if ( -1 !== [ 'radio', 'checkbox' ].indexOf( inputType ) ) {
return $input.prop( 'checked' ) ? inputValue : '';
}
if ( 'number' === inputType && _.isFinite( inputValue ) ) {
return +inputValue;
}
// Temp fix for jQuery (< 3.0) that return null instead of empty array
if ( 'SELECT' === input.tagName && $input.prop( 'multiple' ) && null === inputValue ) {
inputValue = [];
}
return inputValue;
},
setInputValue: function( input, value ) {
var $input = this.$( input ),
inputType = $input.attr( 'type' );
if ( 'checkbox' === inputType ) {
$input.prop( 'checked', !! value );
} else if ( 'radio' === inputType ) {
$input.filter( '[value="' + value + '"]' ).prop( 'checked', true );
} else {
$input.val( value );
}
},
addValidator: function( validator ) {
this.validators.push( validator );
},
registerValidators: function() {
this.validators = [];
var validationTerms = {};
if ( this.model.get( 'required' ) ) {
validationTerms.required = true;
}
if ( ! jQuery.isEmptyObject( validationTerms ) ) {
this.addValidator( new Validator( {
validationTerms: validationTerms,
} ) );
}
},
onRender: function() {
ControlBaseView.prototype.onRender.apply( this, arguments );
if ( this.model.get( 'responsive' ) ) {
this.renderResponsiveSwitchers();
}
this.applySavedValue();
this.triggerMethod( 'ready' );
this.toggleControlVisibility();
this.addTooltip();
},
onBaseInputTextChange: function( event ) {
this.onBaseInputChange( event );
},
onBaseInputChange: function( event ) {
clearTimeout( this.correctionTimeout );
var input = event.currentTarget,
value = this.getInputValue( input ),
validators = this.validators.slice( 0 ),
settingsValidators = this.container.settings.validators[ this.model.get( 'name' ) ];
if ( settingsValidators ) {
validators = validators.concat( settingsValidators );
}
if ( validators ) {
var oldValue = this.getControlValue( input.dataset.setting );
var isValidValue = validators.every( function( validator ) {
return validator.isValid( value, oldValue );
} );
if ( ! isValidValue ) {
this.correctionTimeout = setTimeout( this.setInputValue.bind( this, input, oldValue ), 1200 );
return;
}
}
this.updateElementModel( value, input );
this.triggerMethod( 'input:change', event );
},
onResponsiveSwitchersClick: function( event ) {
const $switcher = jQuery( event.currentTarget ),
device = $switcher.data( 'device' ),
$switchersWrapper = this.ui.responsiveSwitchersWrapper,
selectedOption = $switcher.index();
$switchersWrapper.toggleClass( 'elementor-responsive-switchers-open' );
$switchersWrapper[ 0 ].style.setProperty( '--selected-option', selectedOption );
this.triggerMethod( 'responsive:switcher:click', device );
elementor.changeDeviceMode( device );
},
renderResponsiveSwitchers: function() {
var templateHtml = Marionette.Renderer.render( '#tmpl-elementor-control-responsive-switchers', this.model.attributes );
this.ui.controlTitle.after( templateHtml );
this.ui.responsiveSwitchersWrapper = this.$el.find( '.elementor-control-responsive-switchers' );
},
onAfterExternalChange: function() {
this.hideTooltip();
this.applySavedValue();
},
addTooltip: function() {
this.ui.tooltipTargets = this.$el.find( '.tooltip-target' );
if ( ! this.ui.tooltipTargets.length ) {
return;
}
// Create tooltip on controls
this.ui.tooltipTargets.tipsy( {
gravity: function() {
// `n` for down, `s` for up
var gravity = jQuery( this ).data( 'tooltip-pos' );
if ( undefined !== gravity ) {
return gravity;
}
return 's';
},
title: function() {
return this.getAttribute( 'data-tooltip' );
},
} );
},
hideTooltip: function() {
if ( this.ui.tooltipTargets.length ) {
this.ui.tooltipTargets.tipsy( 'hide' );
}
},
updateElementModel: function( value ) {
this.setValue( value );
},
}, {
// Static methods
getStyleValue: function( placeholder, controlValue, controlData ) {
if ( 'DEFAULT' === placeholder ) {
return controlData.default;
}
return controlValue;
},
onPasteStyle: function() {
return true;
},
} );
module.exports = ControlBaseDataView;

View File

@@ -0,0 +1,70 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlBaseMultipleItemView;
ControlBaseMultipleItemView = ControlBaseDataView.extend( {
applySavedValue: function() {
var values = this.getControlValue(),
$inputs = this.$( '[data-setting]' ),
self = this;
_.each( values, function( value, key ) {
var $input = $inputs.filter( function() {
return key === this.dataset.setting;
} );
self.setInputValue( $input, value );
} );
},
getControlValue: function( key ) {
var values = this.container.settings.get( this.model.get( 'name' ) );
if ( ! jQuery.isPlainObject( values ) ) {
return {};
}
if ( key ) {
var value = values[ key ];
if ( undefined === value ) {
value = '';
}
return value;
}
return elementorCommon.helpers.cloneObject( values );
},
setValue: function( key, value ) {
var values = this.getControlValue();
if ( 'object' === typeof key ) {
_.each( key, function( internalValue, internalKey ) {
values[ internalKey ] = internalValue;
} );
} else {
values[ key ] = value;
}
this.setSettingsModel( values );
},
updateElementModel: function( value, input ) {
var key = input.dataset.setting;
this.setValue( key, value );
},
}, {
// Static methods
getStyleValue: function( placeholder, controlValue ) {
if ( ! _.isObject( controlValue ) ) {
return ''; // invalid
}
return controlValue[ placeholder.toLowerCase() ];
},
} );
module.exports = ControlBaseMultipleItemView;

View File

@@ -0,0 +1,21 @@
var ControlBaseMultipleItemView = require( 'elementor-controls/base-multiple' ),
ControlBaseUnitsItemView;
ControlBaseUnitsItemView = ControlBaseMultipleItemView.extend( {
getCurrentRange: function() {
return this.getUnitRange( this.getControlValue( 'unit' ) );
},
getUnitRange: function( unit ) {
var ranges = this.model.get( 'range' );
if ( ! ranges || ! ranges[ unit ] ) {
return false;
}
return ranges[ unit ];
},
} );
module.exports = ControlBaseUnitsItemView;

View File

@@ -0,0 +1,136 @@
var ControlBaseView;
ControlBaseView = Marionette.CompositeView.extend( {
ui: function() {
return {
controlTitle: '.elementor-control-title',
};
},
behaviors: function() {
var behaviors = {};
return elementor.hooks.applyFilters( 'controls/base/behaviors', behaviors, this );
},
getBehavior: function( name ) {
return this._behaviors[ Object.keys( this.behaviors() ).indexOf( name ) ];
},
className: function() {
// TODO: Any better classes for that?
var classes = 'elementor-control elementor-control-' + this.model.get( 'name' ) + ' elementor-control-type-' + this.model.get( 'type' ),
modelClasses = this.model.get( 'classes' ),
responsive = this.model.get( 'responsive' );
if ( ! _.isEmpty( modelClasses ) ) {
classes += ' ' + modelClasses;
}
if ( ! _.isEmpty( responsive ) ) {
classes += ' elementor-control-responsive-' + responsive.max;
}
return classes;
},
templateHelpers: function() {
var controlData = {
_cid: this.model.cid,
};
return {
view: this,
data: _.extend( {}, this.model.toJSON(), controlData ),
};
},
getTemplate: function() {
return Marionette.TemplateCache.get( '#tmpl-elementor-control-' + this.model.get( 'type' ) + '-content' );
},
initialize: function( options ) {
const label = this.model.get( 'label' );
// TODO: Temp backwards compatibility. since 2.8.0.
Object.defineProperty( this, 'container', {
get() {
if ( ! options.container ) {
const settingsModel = options.elementSettingsModel,
view = $e.components.get( 'document' ).utils.findViewById( settingsModel.id );
// Element control.
if ( view && view.getContainer ) {
options.container = view.getContainer();
} else {
if ( ! settingsModel.id ) {
settingsModel.id = 'bc-' + elementorCommon.helpers.getUniqueId();
}
// Document/General/Other control.
options.container = new elementorModules.editor.Container( {
type: 'bc-container',
id: settingsModel.id,
model: settingsModel,
settings: settingsModel,
label,
view: false,
renderer: false,
controls: settingsModel.options.controls,
} );
}
}
return options.container;
},
} );
// Use `defineProperty` because `get elementSettingsModel()` fails during the `Marionette.CompositeView.extend`.
Object.defineProperty( this, 'elementSettingsModel', {
get() {
elementorCommon.helpers.softDeprecated( 'elementSettingsModel', '2.8.0', 'container.settings' );
return options.container ? options.container.settings : options.elementSettingsModel;
},
} );
var controlType = this.model.get( 'type' ),
controlSettings = jQuery.extend( true, {}, elementor.config.controls[ controlType ], this.model.attributes );
this.model.set( controlSettings );
// TODO: this.elementSettingsModel is deprecated since 2.8.0.
const settings = this.container ? this.container.settings : this.elementSettingsModel;
this.listenTo( settings, 'change', this.toggleControlVisibility );
},
toggleControlVisibility: function() {
// TODO: this.elementSettingsModel is deprecated since 2.8.0.
const settings = this.container ? this.container.settings : this.elementSettingsModel;
var isVisible = elementor.helpers.isActiveControl( this.model, settings.attributes );
this.$el.toggleClass( 'elementor-hidden-control', ! isVisible );
elementor.getPanelView().updateScrollbar();
},
onRender: function() {
var layoutType = this.model.get( 'label_block' ) ? 'block' : 'inline',
showLabel = this.model.get( 'show_label' ),
elClasses = 'elementor-label-' + layoutType;
elClasses += ' elementor-control-separator-' + this.model.get( 'separator' );
if ( ! showLabel ) {
elClasses += ' elementor-control-hidden-label';
}
this.$el.addClass( elClasses );
this.toggleControlVisibility();
},
} );
module.exports = ControlBaseView;

View File

@@ -0,0 +1,77 @@
var ControlMultipleBaseItemView = require( 'elementor-controls/base-multiple' ),
ControlBoxShadowItemView;
import ColorPicker from '../utils/color-picker';
ControlBoxShadowItemView = ControlMultipleBaseItemView.extend( {
ui: function() {
var ui = ControlMultipleBaseItemView.prototype.ui.apply( this, arguments );
ui.sliders = '.elementor-slider';
ui.colorPickerPlaceholder = '.elementor-color-picker-placeholder';
return ui;
},
initSliders: function() {
const value = this.getControlValue();
this.ui.sliders.each( ( index, slider ) => {
const $input = jQuery( slider ).next( '.elementor-slider-input' ).find( 'input' );
const sliderInstance = noUiSlider.create( slider, {
start: [ value[ slider.dataset.input ] ],
step: 1,
range: {
min: +$input.attr( 'min' ),
max: +$input.attr( 'max' ),
},
format: {
to: ( sliderValue ) => +sliderValue.toFixed( 1 ),
from: ( sliderValue ) => +sliderValue,
},
} );
sliderInstance.on( 'slide', ( values ) => {
const type = sliderInstance.target.dataset.input;
$input.val( values[ 0 ] );
this.setValue( type, values[ 0 ] );
} );
} );
},
initColors: function() {
this.colorPicker = new ColorPicker( {
picker: {
el: this.ui.colorPickerPlaceholder[ 0 ],
default: this.getControlValue( 'color' ),
},
onChange: () => {
this.setValue( 'color', this.colorPicker.getColor() );
},
onClear: () => {
this.setValue( 'color', '' );
},
} );
},
onInputChange: function( event ) {
var type = event.currentTarget.dataset.setting,
$slider = this.ui.sliders.filter( '[data-input="' + type + '"]' );
$slider[ 0 ].noUiSlider.set( this.getControlValue( type ) );
},
onReady: function() {
this.initSliders();
this.initColors();
},
onBeforeDestroy: function() {
this.colorPicker.destroy();
},
} );
module.exports = ControlBoxShadowItemView;

View File

@@ -0,0 +1,22 @@
var ControlBaseView = require( 'elementor-controls/base' );
module.exports = ControlBaseView.extend( {
ui: function() {
var ui = ControlBaseView.prototype.ui.apply( this, arguments );
ui.button = 'button';
return ui;
},
events: {
'click @ui.button': 'onButtonClick',
},
onButtonClick: function() {
var eventName = this.model.get( 'event' );
elementor.channels.editor.trigger( eventName, this );
},
} );

View File

@@ -0,0 +1,56 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlChooseItemView;
ControlChooseItemView = ControlBaseDataView.extend( {
ui: function() {
var ui = ControlBaseDataView.prototype.ui.apply( this, arguments );
ui.inputs = '[type="radio"]';
return ui;
},
events: function() {
return _.extend( ControlBaseDataView.prototype.events.apply( this, arguments ), {
'mousedown label': 'onMouseDownLabel',
'click @ui.inputs': 'onClickInput',
'change @ui.inputs': 'onBaseInputChange',
} );
},
applySavedValue: function() {
const currentValue = this.getControlValue();
if ( currentValue ) {
this.ui.inputs.filter( '[value="' + currentValue + '"]' ).prop( 'checked', true );
} else {
this.ui.inputs.filter( ':checked' ).prop( 'checked', false );
}
},
onMouseDownLabel: function( event ) {
var $clickedLabel = this.$( event.currentTarget ),
$selectedInput = this.$( '#' + $clickedLabel.attr( 'for' ) );
$selectedInput.data( 'checked', $selectedInput.prop( 'checked' ) );
},
onClickInput: function( event ) {
if ( ! this.model.get( 'toggle' ) ) {
return;
}
var $selectedInput = this.$( event.currentTarget );
if ( $selectedInput.data( 'checked' ) ) {
$selectedInput.prop( 'checked', false ).trigger( 'change' );
}
},
}, {
onPasteStyle: function( control, clipboardValue ) {
return '' === clipboardValue || undefined !== control.options[ clipboardValue ];
},
} );
module.exports = ControlChooseItemView;

View File

@@ -0,0 +1,105 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlCodeEditorItemView;
ControlCodeEditorItemView = ControlBaseDataView.extend( {
ui: function() {
var ui = ControlBaseDataView.prototype.ui.apply( this, arguments );
ui.editor = '.elementor-code-editor';
return ui;
},
onReady: function() {
var self = this;
if ( 'undefined' === typeof ace ) {
return;
}
const langTools = ace.require( 'ace/ext/language_tools' ),
uiTheme = elementor.settings.editorPreferences.model.get( 'ui_theme' ),
userPrefersDark = matchMedia( '(prefers-color-scheme: dark)' ).matches;
self.editor = ace.edit( this.ui.editor[ 0 ] );
jQuery( self.editor.container ).addClass( 'elementor-input-style elementor-code-editor' );
self.editor.setOptions( {
mode: 'ace/mode/' + self.model.attributes.language,
minLines: 10,
maxLines: Infinity,
showGutter: true,
useWorker: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
} );
if ( 'dark' === uiTheme || ( 'auto' === uiTheme && userPrefersDark ) ) {
self.editor.setTheme( 'ace/theme/merbivore_soft' );
}
self.editor.getSession().setUseWrapMode( true );
elementor.panel.$el.on( 'resize.aceEditor', self.onResize.bind( this ) );
if ( 'css' === self.model.attributes.language ) {
var selectorCompleter = {
getCompletions: function( editor, session, pos, prefix, callback ) {
var list = [],
token = session.getTokenAt( pos.row, pos.column );
if ( 0 < prefix.length && 'selector'.match( prefix ) && 'constant' === token.type ) {
list = [ {
name: 'selector',
value: 'selector',
score: 1,
meta: 'Elementor',
} ];
}
callback( null, list );
},
};
langTools.addCompleter( selectorCompleter );
}
self.editor.setValue( self.getControlValue(), -1 ); // -1 = move cursor to the start
self.editor.on( 'change', function() {
self.setValue( self.editor.getValue() );
} );
if ( 'html' === self.model.attributes.language ) {
// Remove the `doctype` annotation
var session = self.editor.getSession();
session.on( 'changeAnnotation', function() {
var annotations = session.getAnnotations() || [],
annotationsLength = annotations.length,
index = annotations.length;
while ( index-- ) {
if ( /doctype first\. Expected/.test( annotations[ index ].text ) ) {
annotations.splice( index, 1 );
}
}
if ( annotationsLength > annotations.length ) {
session.setAnnotations( annotations );
}
} );
}
},
onResize: function() {
this.editor.resize();
},
onDestroy: function() {
elementor.panel.$el.off( 'resize.aceEditor' );
},
} );
module.exports = ControlCodeEditorItemView;

View File

@@ -0,0 +1,231 @@
import ControlBaseDataView from './base-data';
import ColorPicker from '../utils/color-picker';
export default class extends ControlBaseDataView {
ui() {
const ui = super.ui();
ui.pickerContainer = '.elementor-color-picker-placeholder';
return ui;
}
applySavedValue() {
// Gets the current OR default value of the control.
const currentValue = this.getCurrentValue();
if ( this.colorPicker ) {
// When there is a global set on the control but there is no value/it doesn't exist, don't show a value.
if ( currentValue ) {
// Set the picker color without triggering the 'onChange' event.
const parsedColor = this.colorPicker.picker._parseLocalColor( currentValue );
this.colorPicker.picker.setHSVA( ...parsedColor.values, false );
} else {
this.colorPicker.picker._clearColor( true );
}
} else {
this.initPicker();
}
this.$el.toggleClass( 'e-control-color--no-value', ! currentValue );
}
initPicker() {
const options = {
picker: {
el: this.ui.pickerContainer[ 0 ],
default: this.getCurrentValue(),
components: {
opacity: this.model.get( 'alpha' ),
},
defaultRepresentation: 'HEX',
},
// Don't create the add button in the Global Settings color pickers.
addButton: this.model.get( 'global' )?.active,
onChange: () => this.onPickerChange(),
onClear: () => this.onPickerClear(),
onAddButtonClick: () => this.onAddGlobalButtonClick(),
};
this.colorPicker = new ColorPicker( options );
this.$pickerButton = jQuery( this.colorPicker.picker.getRoot().button );
this.addTipsyToPickerButton();
this.$pickerButton.on( 'click', () => this.onPickerButtonClick() );
jQuery( this.colorPicker.picker.getRoot().root ).addClass( 'elementor-control-unit-1 elementor-control-tag-area' );
}
addTipsyToPickerButton() {
this.$pickerButton.tipsy( {
title: () => {
let currentValue = this.getCurrentValue();
// If there is a global enabled for the control, but the global has no value.
if ( this.getGlobalKey() && ! currentValue ) {
currentValue = `${ __( 'Invalid Global Color', 'elementor' ) }`;
}
return currentValue || '';
},
offset: 4,
gravity: () => 's',
} );
}
getGlobalMeta() {
return {
commandName: this.getGlobalCommand(),
key: this.model.get( 'name' ),
controlType: 'colors',
route: 'panel/global/global-colors',
};
}
getNameAlreadyExistsMessage() {
return '<i class="eicon-info-circle"></i> ' + __( 'Please note that the same exact color already exists in your Global Colors list. Are you sure you want to create it?', 'elementor' );
}
getConfirmTextMessage() {
return __( 'Are you sure you want to create a new Global Color?', 'elementor' );
}
getAddGlobalConfirmMessage( globalColors ) {
const colorTitle = __( 'New Global Color', 'elementor' ),
currentValue = this.getCurrentValue(),
$message = jQuery( '<div>', { class: 'e-global__confirm-message' } ),
$messageText = jQuery( '<div>', { class: 'e-global__confirm-message-text' } ),
$inputWrapper = jQuery( '<div>', { class: 'e-global__confirm-input-wrapper' } ),
$colorPreview = this.createColorPreviewBox( currentValue ),
$input = jQuery( '<input>', { type: 'text', name: 'global-name', placeholder: colorTitle } )
.val( colorTitle );
let messageContent;
// Check if the color already exists in the global colors, and display an appropriate message.
for ( const globalColor of Object.values( globalColors ) ) {
if ( currentValue === globalColor.value ) {
messageContent = this.getNameAlreadyExistsMessage();
break;
} else if ( colorTitle === globalColor.title ) {
messageContent = this.getConfirmTextMessage();
break;
} else {
messageContent = __( 'Are you sure you want to create a new Global Color?', 'elementor' );
}
}
$messageText.html( messageContent );
$inputWrapper.append( $colorPreview, $input );
$message.append( $messageText, $inputWrapper );
return $message;
}
getGlobalCommand() {
return 'globals/colors';
}
// The globalData parameter is received from the Data API.
createGlobalItemMarkup( globalData ) {
const $color = jQuery( '<div>', { class: 'e-global__preview-item e-global__color', 'data-global-id': globalData.id } ),
$colorPreview = this.createColorPreviewBox( globalData.value ),
$colorTitle = jQuery( '<span>', { class: 'e-global__color-title' } )
.html( globalData.title ),
$colorHex = jQuery( '<span>', { class: 'e-global__color-hex' } )
.html( globalData.value );
$color.append( $colorPreview, $colorTitle, $colorHex );
return $color;
}
createColorPreviewBox( color ) {
const $colorPreviewContainer = jQuery( '<div>', { class: 'e-global__color-preview-container' } ),
$colorPreviewColor = jQuery( '<div>', { class: 'e-global__color-preview-color', style: 'background-color: ' + color } ),
$colorPreviewBg = jQuery( '<div>', { class: 'e-global__color-preview-transparent-bg' } );
$colorPreviewContainer.append( $colorPreviewBg, $colorPreviewColor );
return $colorPreviewContainer;
}
async getGlobalsList() {
const result = await $e.data.get( this.getGlobalCommand() );
return result.data;
}
// Create the markup for the colors in the global select dropdown.
buildGlobalsList( globalColors, $globalPreviewItemsContainer ) {
Object.values( globalColors ).forEach( ( color ) => {
if ( ! color.value ) {
return;
}
const $color = this.createGlobalItemMarkup( color );
$globalPreviewItemsContainer.append( $color );
} );
}
onPickerChange() {
this.setValue( this.colorPicker.picker.getColor().toHEXA().toString() );
if ( ! this.isCustom ) {
this.triggerMethod( 'value:type:change' );
this.colorPicker.toggleClearButtonState( true );
if ( this.$el.hasClass( 'e-control-color--no-value' ) ) {
this.$el.removeClass( 'e-control-color--no-value' );
}
this.isCustom = true;
}
}
onPickerClear() {
this.isCustom = false;
// Empty the value saved in the control.
this.setValue( '' );
// Adjust the Global select box text according to the cleared value.
this.triggerMethod( 'value:type:change' );
this.applySavedValue();
this.colorPicker.toggleClearButtonState( false );
}
onPickerButtonClick() {
if ( this.getGlobalKey() ) {
this.triggerMethod( 'unset:global:value' );
} else if ( this.isGlobalActive() && ! this.getControlValue() && this.getGlobalDefault() ) {
this.triggerMethod( 'unlink:global:default' );
}
// If there is a value in the control, set the clear button to active, if not, deactivate it.
this.colorPicker.toggleClearButtonState( !! this.getCurrentValue() );
}
onAddGlobalButtonClick() {
this.getGlobalsList().then( ( globalsList ) => {
this.globalsList = globalsList;
this.triggerMethod( 'add:global:to:list', this.getAddGlobalConfirmMessage( globalsList ) );
} );
}
onBeforeDestroy() {
if ( this.colorPicker ) {
this.colorPicker.destroy();
}
}
}

View File

@@ -0,0 +1,16 @@
const ControlBaseDataView = require( 'elementor-controls/base-data' );
export default class extends ControlBaseDataView {
onReady() {
const options = _.extend( {
enableTime: true,
minuteIncrement: 1,
}, this.model.get( 'picker_options' ) );
this.ui.input.flatpickr( options );
}
onBeforeDestroy() {
this.ui.input.flatpickr().destroy();
}
}

View File

@@ -0,0 +1,166 @@
var ControlBaseUnitsItemView = require( 'elementor-controls/base-units' ),
ControlDimensionsItemView;
ControlDimensionsItemView = ControlBaseUnitsItemView.extend( {
ui: function() {
var ui = ControlBaseUnitsItemView.prototype.ui.apply( this, arguments );
ui.controls = '.elementor-control-dimension > input:enabled';
ui.link = 'button.elementor-link-dimensions';
return ui;
},
events: function() {
return _.extend( ControlBaseUnitsItemView.prototype.events.apply( this, arguments ), {
'click @ui.link': 'onLinkDimensionsClicked',
} );
},
defaultDimensionValue: 0,
initialize: function() {
ControlBaseUnitsItemView.prototype.initialize.apply( this, arguments );
// TODO: Need to be in helpers, and not in variable
this.model.set( 'allowed_dimensions', this.filterDimensions( this.model.get( 'allowed_dimensions' ) ) );
},
getPossibleDimensions: function() {
return [
'top',
'right',
'bottom',
'left',
];
},
filterDimensions: function( filter ) {
filter = filter || 'all';
var dimensions = this.getPossibleDimensions();
if ( 'all' === filter ) {
return dimensions;
}
if ( ! _.isArray( filter ) ) {
if ( 'horizontal' === filter ) {
filter = [ 'right', 'left' ];
} else if ( 'vertical' === filter ) {
filter = [ 'top', 'bottom' ];
}
}
return filter;
},
onReady: function() {
var self = this,
currentValue = self.getControlValue();
if ( ! self.isLinkedDimensions() ) {
self.ui.link.addClass( 'unlinked' );
self.ui.controls.each( function( index, element ) {
var value = currentValue[ element.dataset.setting ];
if ( _.isEmpty( value ) ) {
value = self.defaultDimensionValue;
}
self.$( element ).val( value );
} );
}
self.fillEmptyDimensions();
},
updateDimensionsValue: function() {
var currentValue = {},
dimensions = this.getPossibleDimensions(),
$controls = this.ui.controls,
defaultDimensionValue = this.defaultDimensionValue;
dimensions.forEach( function( dimension ) {
var $element = $controls.filter( '[data-setting="' + dimension + '"]' );
currentValue[ dimension ] = $element.length ? $element.val() : defaultDimensionValue;
} );
this.setValue( currentValue );
},
fillEmptyDimensions: function() {
var dimensions = this.getPossibleDimensions(),
allowedDimensions = this.model.get( 'allowed_dimensions' ),
$controls = this.ui.controls,
defaultDimensionValue = this.defaultDimensionValue;
if ( this.isLinkedDimensions() ) {
return;
}
dimensions.forEach( function( dimension ) {
var $element = $controls.filter( '[data-setting="' + dimension + '"]' ),
isAllowedDimension = -1 !== _.indexOf( allowedDimensions, dimension );
if ( isAllowedDimension && $element.length && _.isEmpty( $element.val() ) ) {
$element.val( defaultDimensionValue );
}
} );
},
updateDimensions: function() {
this.fillEmptyDimensions();
this.updateDimensionsValue();
},
resetDimensions: function() {
this.ui.controls.val( '' );
this.updateDimensionsValue();
},
onInputChange: function( event ) {
var inputSetting = event.target.dataset.setting;
if ( 'unit' === inputSetting ) {
this.resetDimensions();
}
if ( ! _.contains( this.getPossibleDimensions(), inputSetting ) ) {
return;
}
if ( this.isLinkedDimensions() ) {
var $thisControl = this.$( event.target );
this.ui.controls.val( $thisControl.val() );
}
this.updateDimensions();
},
onLinkDimensionsClicked: function( event ) {
event.preventDefault();
event.stopPropagation();
this.ui.link.toggleClass( 'unlinked' );
this.setValue( 'isLinked', ! this.ui.link.hasClass( 'unlinked' ) );
if ( this.isLinkedDimensions() ) {
// Set all controls value from the first control.
this.ui.controls.val( this.ui.controls.eq( 0 ).val() );
}
this.updateDimensions();
},
isLinkedDimensions: function() {
return this.getControlValue( 'isLinked' );
},
} );
module.exports = ControlDimensionsItemView;

View File

@@ -0,0 +1,114 @@
const ControlSelect2View = require( 'elementor-controls/select2' );
module.exports = ControlSelect2View.extend( {
$previewContainer: null,
getSelect2Options() {
return {
dir: elementorCommon.config.isRTL ? 'rtl' : 'ltr',
templateSelection: this.fontPreviewTemplate,
templateResult: this.fontPreviewTemplate,
};
},
onReady() {
const self = this;
this.ui.select.select2( this.getSelect2Options() );
this.ui.select.on( 'select2:open', function() {
self.$previewContainer = jQuery( '.select2-results__options[role="tree"]:visible' );
// load initial?
setTimeout( function() {
self.enqueueFontsInView();
}, 100 );
// On search
jQuery( 'input.select2-search__field:visible' ).on( 'keyup', function() {
self.typeStopDetection.action.apply( self );
} );
// On scroll
self.$previewContainer.on( 'scroll', function() {
self.scrollStopDetection.onScroll.apply( self );
} );
} );
},
typeStopDetection: {
idle: 350,
timeOut: null,
action() {
const parent = this,
self = this.typeStopDetection;
clearTimeout( self.timeOut );
self.timeOut = setTimeout( function() {
parent.enqueueFontsInView();
}, self.idle );
},
},
scrollStopDetection: {
idle: 350,
timeOut: null,
onScroll() {
const parent = this,
self = this.scrollStopDetection;
clearTimeout( self.timeOut );
self.timeOut = setTimeout( function() {
parent.enqueueFontsInView();
}, self.idle );
},
},
enqueueFontsInView() {
const self = this,
containerOffset = this.$previewContainer.offset(),
top = containerOffset.top,
bottom = top + this.$previewContainer.innerHeight(),
fontsInView = [];
this.$previewContainer.children().find( 'li:visible' ).each( function( index, font ) {
const $font = jQuery( font ),
offset = $font.offset();
if ( offset && offset.top > top && offset.top < bottom ) {
fontsInView.push( $font );
}
} );
fontsInView.forEach( function( font ) {
const fontFamily = jQuery( font ).find( 'span' ).html();
elementor.helpers.enqueueFont( fontFamily, 'editor' );
} );
},
fontPreviewTemplate( state ) {
if ( ! state.id ) {
return state.text;
}
return jQuery( '<span>', {
text: state.text,
css: {
'font-family': state.element.value.toString(),
},
} );
},
templateHelpers() {
var helpers = ControlSelect2View.prototype.templateHelpers.apply( this, arguments ),
fonts = this.model.get( 'options' );
helpers.getFontsByGroups = function( groups ) {
var filteredFonts = {};
_.each( fonts, function( fontType, fontName ) {
if ( ( _.isArray( groups ) && _.contains( groups, fontType ) ) || fontType === groups ) {
filteredFonts[ fontName ] = fontName;
}
} );
return filteredFonts;
};
return helpers;
},
} );

View File

@@ -0,0 +1,189 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlMediaItemView;
ControlMediaItemView = ControlBaseDataView.extend( {
ui: function() {
var ui = ControlBaseDataView.prototype.ui.apply( this, arguments );
ui.addImages = '.elementor-control-gallery-add';
ui.clearGallery = '.elementor-control-gallery-clear';
ui.galleryThumbnails = '.elementor-control-gallery-thumbnails';
ui.status = '.elementor-control-gallery-status-title';
return ui;
},
events: function() {
return _.extend( ControlBaseDataView.prototype.events.apply( this, arguments ), {
'click @ui.addImages': 'onAddImagesClick',
'click @ui.clearGallery': 'onClearGalleryClick',
'click @ui.galleryThumbnails': 'onGalleryThumbnailsClick',
} );
},
onReady: function() {
this.initRemoveDialog();
},
applySavedValue: function() {
var images = this.getControlValue(),
imagesCount = images.length,
hasImages = !! imagesCount;
this.$el
.toggleClass( 'elementor-gallery-has-images', hasImages )
.toggleClass( 'elementor-gallery-empty', ! hasImages );
var $galleryThumbnails = this.ui.galleryThumbnails;
$galleryThumbnails.empty();
this.ui.status.text( hasImages ? sprintf( '%s Images Selected', imagesCount ) : __( 'No Images Selected', 'elementor' ) );
if ( ! hasImages ) {
return;
}
this.getControlValue().forEach( function( image ) {
var $thumbnail = jQuery( '<div>', { class: 'elementor-control-gallery-thumbnail' } );
$thumbnail.css( 'background-image', 'url(' + image.url + ')' );
$galleryThumbnails.append( $thumbnail );
} );
},
hasImages: function() {
return !! this.getControlValue().length;
},
openFrame: function( action ) {
this.initFrame( action );
this.frame.open();
},
initFrame: function( action ) {
var frameStates = {
create: 'gallery',
add: 'gallery-library',
edit: 'gallery-edit',
};
var options = {
frame: 'post',
multiple: true,
state: frameStates[ action ],
button: {
text: __( 'Insert Media', 'elementor' ),
},
};
if ( this.hasImages() ) {
options.selection = this.fetchSelection();
}
this.frame = wp.media( options );
// When a file is selected, run a callback.
this.frame.on( {
update: this.select,
'menu:render:default': this.menuRender,
'content:render:browse': this.gallerySettings,
}, this );
},
menuRender: function( view ) {
view.unset( 'insert' );
view.unset( 'featured-image' );
},
gallerySettings: function( browser ) {
browser.sidebar.on( 'ready', function() {
browser.sidebar.unset( 'gallery' );
} );
},
fetchSelection: function() {
var attachments = wp.media.query( {
orderby: 'post__in',
order: 'ASC',
type: 'image',
perPage: -1,
post__in: _.pluck( this.getControlValue(), 'id' ),
} );
return new wp.media.model.Selection( attachments.models, {
props: attachments.props.toJSON(),
multiple: true,
} );
},
/**
* Callback handler for when an attachment is selected in the media modal.
* Gets the selected image information, and sets it within the control.
*/
select: function( selection ) {
var images = [];
selection.each( function( image ) {
images.push( {
id: image.get( 'id' ),
url: image.get( 'url' ),
} );
} );
this.setValue( images );
this.applySavedValue();
},
onBeforeDestroy: function() {
if ( this.frame ) {
this.frame.off();
}
this.$el.remove();
},
resetGallery: function() {
this.setValue( [] );
this.applySavedValue();
},
initRemoveDialog: function() {
var removeDialog;
this.getRemoveDialog = function() {
if ( ! removeDialog ) {
removeDialog = elementorCommon.dialogsManager.createWidget( 'confirm', {
message: __( 'Are you sure you want to reset this gallery?', 'elementor' ),
headerMessage: __( 'Reset Gallery', 'elementor' ),
strings: {
confirm: __( 'Delete', 'elementor' ),
cancel: __( 'Cancel', 'elementor' ),
},
defaultOption: 'confirm',
onConfirm: this.resetGallery.bind( this ),
} );
}
return removeDialog;
};
},
onAddImagesClick: function() {
this.openFrame( this.hasImages() ? 'add' : 'create' );
},
onClearGalleryClick: function() {
this.getRemoveDialog().show();
},
onGalleryThumbnailsClick: function() {
this.openFrame( 'edit' );
},
} );
module.exports = ControlMediaItemView;

View File

@@ -0,0 +1,3 @@
import ControlBaseDataView from './base-data';
module.exports = ControlBaseDataView.extend( {}, { onPasteStyle: () => false } );

View File

@@ -0,0 +1,54 @@
var ControlSelect2View = require( 'elementor-controls/select2' ),
ControlIconView;
ControlIconView = ControlSelect2View.extend( {
initialize: function() {
ControlSelect2View.prototype.initialize.apply( this, arguments );
this.filterIcons();
},
filterIcons: function() {
var icons = this.model.get( 'options' ),
include = this.model.get( 'include' ),
exclude = this.model.get( 'exclude' );
if ( include ) {
var filteredIcons = {};
_.each( include, function( iconKey ) {
filteredIcons[ iconKey ] = icons[ iconKey ];
} );
this.model.set( 'options', filteredIcons );
return;
}
if ( exclude ) {
_.each( exclude, function( iconKey ) {
delete icons[ iconKey ];
} );
}
},
iconsList: function( icon ) {
if ( ! icon.id ) {
return icon.text;
}
return jQuery(
'<span><i class="' + icon.id + '"></i> ' + icon.text + '</span>'
);
},
getSelect2Options: function() {
return {
allowClear: true,
templateResult: this.iconsList.bind( this ),
templateSelection: this.iconsList.bind( this ),
};
},
} );
module.exports = ControlIconView;

View File

@@ -0,0 +1,351 @@
import FilesUploadHandler from '../utils/files-upload-handler';
const ControlMultipleBaseItemView = require( 'elementor-controls/base-multiple' );
class ControlIconsView extends ControlMultipleBaseItemView {
constructor( ...args ) {
super( ...args );
this.cache = {
loaded: false,
dialog: false,
enableClicked: false,
fa4Mapping: false,
migratedFlag: {},
};
this.dataKeys = {
migratedKey: '__fa4_migrated',
fa4MigrationFlag: 'fa4compatibility',
};
}
enqueueIconFonts( iconType ) {
const iconSetting = elementor.helpers.getIconLibrarySettings( iconType );
if ( false === iconSetting || ! this.isMigrationAllowed() ) {
return;
}
if ( iconSetting.enqueue ) {
iconSetting.enqueue.forEach( ( assetURL ) => {
elementor.helpers.enqueueEditorStylesheet( assetURL );
elementor.helpers.enqueuePreviewStylesheet( assetURL );
} );
}
if ( iconSetting.url ) {
elementor.helpers.enqueueEditorStylesheet( iconSetting.url );
elementor.helpers.enqueuePreviewStylesheet( iconSetting.url );
}
}
ui() {
const ui = super.ui(),
skin = this.model.get( 'skin' );
ui.controlMedia = '.elementor-control-media';
ui.svgUploader = 'media' === skin ? '.elementor-control-svg-uploader' : '.elementor-control-icons--inline__svg';
ui.iconPickers = 'media' === skin ? '.elementor-control-icon-picker, .elementor-control-media__preview, .elementor-control-media-upload-button' : '.elementor-control-icons--inline__icon';
ui.deleteButton = 'media' === skin ? '.elementor-control-media__remove' : '.elementor-control-icons--inline__none';
ui.previewPlaceholder = '.elementor-control-media__preview';
ui.previewContainer = '.elementor-control-preview-area';
ui.inlineIconContainer = '.elementor-control-inline-icon';
ui.inlineDisplayedIcon = '.elementor-control-icons--inline__displayed-icon';
ui.radioInputs = '[type="radio"]';
return ui;
}
events() {
return jQuery.extend( ControlMultipleBaseItemView.prototype.events.apply( this, arguments ), {
'click @ui.iconPickers': 'openPicker',
'click @ui.svgUploader': 'openFrame',
'click @ui.radioInputs': 'onClickInput',
'click @ui.deleteButton': 'deleteIcon',
} );
}
getControlValue() {
const value = super.getControlValue(),
model = this.model,
valueToMigrate = this.getValueToMigrate();
if ( ! this.isMigrationAllowed() ) {
return valueToMigrate;
}
// Bail if no migration flag or no value to migrate
if ( ! valueToMigrate ) {
return value;
}
const didMigration = this.elementSettingsModel.get( this.dataKeys.migratedKey ),
controlName = model.get( 'name' );
// Check if migration had been done and is stored locally
if ( this.cache.migratedFlag[ controlName ] ) {
return this.cache.migratedFlag[ controlName ];
}
// Check if already migrated
if ( didMigration && didMigration[ controlName ] ) {
return value;
}
// Do migration
return this.migrateFa4toFa5( valueToMigrate );
}
migrateFa4toFa5( fa4Value ) {
const fa5Value = elementor.helpers.mapFa4ToFa5( fa4Value );
this.cache.migratedFlag[ this.model.get( 'name' ) ] = fa5Value;
this.enqueueIconFonts( fa5Value.library );
return fa5Value;
}
setControlAsMigrated( controlName ) {
const didMigration = this.elementSettingsModel.get( this.dataKeys.migratedKey ) || {};
didMigration[ controlName ] = true;
this.elementSettingsModel.set( this.dataKeys.migratedKey, didMigration, { silent: true } );
}
isMigrationAllowed() {
return ! elementor.config[ 'icons_update_needed' ];
}
getValueToMigrate() {
const controlToMigrate = this.model.get( this.dataKeys.fa4MigrationFlag );
if ( ! controlToMigrate ) {
return false;
}
// Check if there is a value to migrate
const valueToMigrate = this.container.settings.get( controlToMigrate );
if ( valueToMigrate ) {
return valueToMigrate;
}
return false;
}
onReady() {
// is migration allowed from fa4
if ( ! this.isMigrationAllowed() ) {
const migrationPopupTrigger = 'media' === this.model.get( 'skin' ) ? this.ui.previewContainer[ 0 ] : this.ui.inlineIconContainer[ 0 ];
migrationPopupTrigger.addEventListener( 'click', ( event ) => {
// Prevent default to prevent marking the inline icons as selected on click when migration is not allowed
event.preventDefault();
event.stopPropagation();
const onConfirm = () => {
window.location.href = elementor.config.tools_page_link + '&redirect_to=' + encodeURIComponent( document.location.href ) + '#tab-fontawesome4_migration';
};
const enableMigrationDialog = elementor.helpers.getSimpleDialog(
'elementor-enable-fa5-dialog',
__( 'Elementor\'s New Icon Library', 'elementor' ),
__( 'Elementor v2.6 includes an upgrade from Font Awesome 4 to 5. In order to continue using icons, be sure to click "Upgrade".', 'elementor' ) + ' <a href="https://go.elementor.com/fontawesome-migration/" target="_blank">' + __( 'Learn More', 'elementor' ) + '</a>',
__( 'Update', 'elementor' ),
onConfirm
);
enableMigrationDialog.show();
return false;
}, true );
}
const controlName = this.model.get( 'name' );
if ( this.cache.migratedFlag[ controlName ] ) {
this.setControlAsMigrated( controlName );
setTimeout( () => {
this.setValue( this.cache.migratedFlag[ controlName ] );
}, 10 );
}
}
onRender() {
super.onRender();
if ( this.isMigrationAllowed() ) {
elementor.iconManager.loadIconLibraries();
}
}
initFrame() {
// Set current doc id to attach uploaded images.
wp.media.view.settings.post.id = elementor.config.document.id;
this.frame = wp.media( {
button: {
text: __( 'Insert Media', 'elementor' ),
},
library: { type: [ 'image/svg+xml' ] },
states: [
new wp.media.controller.Library( {
title: __( 'Insert Media', 'elementor' ),
library: wp.media.query( { type: [ 'image/svg+xml' ] } ),
multiple: false,
date: false,
} ),
],
} );
const handleSelect = () => this.selectSvg();
// When a file is selected, run a callback.
this.frame.on( 'insert select', handleSelect );
this.setUploadMimeType( this.frame, 'svg' );
}
setUploadMimeType( frame, ext ) {
// Set svg as only allowed upload extensions
const oldExtensions = _wpPluploadSettings.defaults.filters.mime_types[ 0 ].extensions;
frame.on( 'ready', () => {
_wpPluploadSettings.defaults.filters.mime_types[ 0 ].extensions = ext;
} );
this.frame.on( 'close', () => {
// restore allowed upload extensions
_wpPluploadSettings.defaults.filters.mime_types[ 0 ].extensions = oldExtensions;
} );
}
/**
* Callback handler for when an attachment is selected in the media modal.
* Gets the selected image information, and sets it within the control.
*/
selectSvg() {
this.trigger( 'before:select' );
// Get the attachment from the modal frame.
const attachment = this.frame.state().get( 'selection' ).first().toJSON();
if ( attachment.url ) {
this.setValue( {
value: {
url: attachment.url,
id: attachment.id,
},
library: 'svg',
} );
this.applySavedValue();
}
this.trigger( 'after:select' );
}
openFrame() {
if ( ! FilesUploadHandler.isUploadEnabled( 'svg' ) ) {
FilesUploadHandler.getUnfilteredFilesNotEnabledDialog( () => this.openFrame() ).show();
return false;
}
if ( ! this.frame ) {
this.initFrame();
}
this.frame.open();
// Set params to trigger sanitizer
FilesUploadHandler.setUploadTypeCaller( this.frame );
const selectedId = this.getControlValue( 'id' );
if ( ! selectedId ) {
return;
}
const selection = this.frame.state().get( 'selection' );
selection.add( wp.media.attachment( selectedId ) );
}
openPicker() {
elementor.iconManager.show( { view: this } );
}
applySavedValue() {
const controlValue = this.getControlValue(),
skin = this.model.get( 'skin' ),
iconContainer = 'inline' === skin ? this.ui.inlineDisplayedIcon : this.ui.previewPlaceholder,
defaultIcon = this.model.get( 'default' );
let iconValue = controlValue.value,
iconType = controlValue.library;
if ( ! this.isMigrationAllowed() && ! iconValue && this.getValueToMigrate() ) {
iconValue = this.getControlValue();
iconType = '';
}
if ( 'media' === skin ) {
this.ui.controlMedia.toggleClass( 'elementor-media-empty', ! iconValue );
} else {
this.markChecked( iconType );
}
if ( ! iconValue ) {
if ( 'inline' === skin ) {
this.setDefaultIconLibraryLabel( defaultIcon, iconContainer );
return;
}
this.ui.previewPlaceholder.html( '' );
return;
}
if ( 'svg' === iconType && 'inline' !== skin ) {
return elementor.helpers.fetchInlineSvg( iconValue.url, ( data ) => {
this.ui.previewPlaceholder.html( data );
} );
}
if ( 'media' === skin || 'svg' !== iconType ) {
const previewHTML = '<i class="' + iconValue + '"></i>';
iconContainer.html( previewHTML );
}
this.enqueueIconFonts( iconType );
}
setDefaultIconLibraryLabel( defaultIcon, iconContainer ) {
// Check if the control has a default icon
if ( '' !== defaultIcon.value && 'svg' !== defaultIcon.library ) {
// If the default icon is not an SVG, set the icon-library label's icon to the default icon
iconContainer.html( '<i class="' + defaultIcon.value + '"></i>' );
} else {
// If (1) the control does NOT have a default icon,
// OR (2) the control DOES have a default icon BUT the default icon is an SVG,
// set the default icon-library label's icon to a simple circle
iconContainer.html( '<i class="eicon-circle"></i>' );
}
}
markChecked( iconType ) {
this.ui.radioInputs.filter( ':checked' ).prop( 'checked', false );
if ( ! iconType ) {
return this.ui.radioInputs.filter( '[value="none"]' ).prop( 'checked', true );
}
if ( 'svg' !== iconType ) {
iconType = 'icon';
}
this.ui.radioInputs.filter( '[value="' + iconType + '"]' ).prop( 'checked', true );
}
onClickInput() {
this.markChecked( this.getControlValue().library );
}
deleteIcon( event ) {
event.stopPropagation();
this.setValue( {
value: '',
library: '',
} );
this.applySavedValue();
}
onBeforeDestroy() {
this.$el.remove();
}
}
module.exports = ControlIconsView;

View File

@@ -0,0 +1,41 @@
var ControlMultipleBaseItemView = require( 'elementor-controls/base-multiple' ),
ControlImageDimensionsItemView;
ControlImageDimensionsItemView = ControlMultipleBaseItemView.extend( {
ui: function() {
return {
inputWidth: 'input[data-setting="width"]',
inputHeight: 'input[data-setting="height"]',
btnApply: 'button.elementor-image-dimensions-apply-button',
};
},
// Override the base events
events: function() {
return {
'click @ui.btnApply': 'onApplyClicked',
'keyup @ui.inputWidth': 'onDimensionKeyUp',
'keyup @ui.inputHeight': 'onDimensionKeyUp',
};
},
onDimensionKeyUp: function( event ) {
const ENTER_KEY = 13;
if ( ENTER_KEY === event.keyCode ) {
this.onApplyClicked( event );
}
},
onApplyClicked: function( event ) {
event.preventDefault();
this.setValue( {
width: this.ui.inputWidth.val(),
height: this.ui.inputHeight.val(),
} );
},
} );
module.exports = ControlImageDimensionsItemView;

View File

@@ -0,0 +1,179 @@
import FilesUploadHandler from '../utils/files-upload-handler';
var ControlMultipleBaseItemView = require( 'elementor-controls/base-multiple' ),
ControlMediaItemView;
ControlMediaItemView = ControlMultipleBaseItemView.extend( {
ui: function() {
var ui = ControlMultipleBaseItemView.prototype.ui.apply( this, arguments );
ui.controlMedia = '.elementor-control-media';
ui.mediaImage = '.elementor-control-media__preview';
ui.mediaVideo = '.elementor-control-media-video';
ui.frameOpeners = '.elementor-control-preview-area';
ui.removeButton = '.elementor-control-media__remove';
ui.fileName = '.elementor-control-media__file__content__info__name';
return ui;
},
events: function() {
return _.extend( ControlMultipleBaseItemView.prototype.events.apply( this, arguments ), {
'click @ui.frameOpeners': 'openFrame',
'click @ui.removeButton': 'deleteImage',
} );
},
getMediaType: function() {
// `get( 'media_type' )` is for BC.
return this.mediaType || this.model.get( 'media_type' ) || this.model.get( 'media_types' )[ 0 ];
},
/**
* Get library type for `wp.media` using a given media type.
*
* @param {String} mediaType - The media type to get the library for.
* @returns {String}
*/
getLibraryType: function( mediaType ) {
if ( ! mediaType ) {
mediaType = this.getMediaType();
}
return ( 'svg' === mediaType ) ? 'image/svg+xml' : mediaType;
},
applySavedValue: function() {
const url = this.getControlValue( 'url' ),
mediaType = this.getMediaType();
if ( [ 'image', 'svg' ].includes( mediaType ) ) {
this.ui.mediaImage.css( 'background-image', url ? 'url(' + url + ')' : '' );
} else if ( 'video' === mediaType ) {
this.ui.mediaVideo.attr( 'src', url );
} else {
const fileName = url ? url.split( '/' ).pop() : '';
this.ui.fileName.text( fileName );
}
this.ui.controlMedia.toggleClass( 'elementor-media-empty', ! url );
},
openFrame: function( e ) {
const mediaType = e?.target?.dataset?.mediaType || this.getMediaType();
this.mediaType = mediaType;
if ( ! mediaType ) {
return;
}
if ( ! FilesUploadHandler.isUploadEnabled( mediaType ) ) {
FilesUploadHandler.getUnfilteredFilesNotEnabledDialog( () => this.openFrame( e ) ).show();
return false;
}
// If there is no frame, or the current initialized frame contains a different library than
// the `data-media-type` of the clicked button, (re)initialize the frame.
if ( ! this.frame || this.getLibraryType( mediaType ) !== this.currentLibraryType ) {
this.initFrame();
}
this.frame.open();
// Set params to trigger sanitizer
FilesUploadHandler.setUploadTypeCaller( this.frame );
const selectedId = this.getControlValue( 'id' );
if ( ! selectedId ) {
return;
}
this.frame.state().get( 'selection' ).add( wp.media.attachment( selectedId ) );
},
deleteImage: function( event ) {
event.stopPropagation();
this.setValue( {
url: '',
id: '',
} );
this.applySavedValue();
},
/**
* Create a media modal select frame, and store it so the instance can be reused when needed.
*/
initFrame: function() {
const mediaType = this.getMediaType();
this.currentLibraryType = this.getLibraryType( mediaType );
// Set current doc id to attach uploaded images.
wp.media.view.settings.post.id = elementor.config.document.id;
this.frame = wp.media( {
button: {
text: __( 'Insert Media', 'elementor' ),
},
states: [
new wp.media.controller.Library( {
title: __( 'Insert Media', 'elementor' ),
library: wp.media.query( { type: this.currentLibraryType } ),
multiple: false,
date: false,
} ),
],
} );
// When a file is selected, run a callback.
this.frame.on( 'insert select', this.select.bind( this ) );
if ( elementor.config.filesUpload.unfilteredFiles ) {
this.setUploadMimeType( this.frame, mediaType );
}
},
setUploadMimeType( frame, ext ) {
// Add unfiltered files to the allowed upload extensions
const oldExtensions = _wpPluploadSettings.defaults.filters.mime_types[ 0 ].extensions;
frame.on( 'ready', () => {
_wpPluploadSettings.defaults.filters.mime_types[ 0 ].extensions = ( 'application/json' === ext ) ? 'json' : oldExtensions + ',svg';
} );
this.frame.on( 'close', () => {
// Restore allowed upload extensions
_wpPluploadSettings.defaults.filters.mime_types[ 0 ].extensions = oldExtensions;
} );
},
/**
* Callback handler for when an attachment is selected in the media modal.
* Gets the selected image information, and sets it within the control.
*/
select: function() {
this.trigger( 'before:select' );
// Get the attachment from the modal frame.
var attachment = this.frame.state().get( 'selection' ).first().toJSON();
if ( attachment.url ) {
this.setValue( {
url: attachment.url,
id: attachment.id,
} );
this.applySavedValue();
}
this.trigger( 'after:select' );
},
onBeforeDestroy: function() {
this.$el.remove();
},
} );
module.exports = ControlMediaItemView;

View File

@@ -0,0 +1,29 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
NumberValidator = require( 'elementor-validator/number' ),
ControlNumberItemView;
ControlNumberItemView = ControlBaseDataView.extend( {
registerValidators: function() {
ControlBaseDataView.prototype.registerValidators.apply( this, arguments );
var validationTerms = {},
model = this.model;
[ 'min', 'max' ].forEach( function( term ) {
var termValue = model.get( term );
if ( _.isFinite( termValue ) ) {
validationTerms[ term ] = termValue;
}
} );
if ( ! jQuery.isEmptyObject( validationTerms ) ) {
this.addValidator( new NumberValidator( {
validationTerms: validationTerms,
} ) );
}
},
} );
module.exports = ControlNumberItemView;

View File

@@ -0,0 +1,30 @@
var ControlMultipleBaseItemView = require( 'elementor-controls/base-multiple' ),
ControlOrderItemView;
ControlOrderItemView = ControlMultipleBaseItemView.extend( {
ui: function() {
var ui = ControlMultipleBaseItemView.prototype.ui.apply( this, arguments );
ui.reverseOrderLabel = '.elementor-control-order-label';
return ui;
},
changeLabelTitle: function() {
var reverseOrder = this.getControlValue( 'reverse_order' );
this.ui.reverseOrderLabel.attr( 'title', reverseOrder ? __( 'Ascending order', 'elementor' ) : __( 'Descending order', 'elementor' ) );
},
onRender: function() {
ControlMultipleBaseItemView.prototype.onRender.apply( this, arguments );
this.changeLabelTitle();
},
onInputChange: function() {
this.changeLabelTitle();
},
} );
module.exports = ControlOrderItemView;

View File

@@ -0,0 +1,149 @@
const ControlChooseView = require( 'elementor-controls/choose' );
export default class ControlPopoverStarterView extends ControlChooseView {
ui() {
const ui = ControlChooseView.prototype.ui.apply( this, arguments );
ui.popoverToggle = '.elementor-control-popover-toggle-toggle';
ui.resetInput = '.elementor-control-popover-toggle-reset';
return ui;
}
events() {
return _.extend( ControlChooseView.prototype.events.apply( this, arguments ), {
'click @ui.popoverToggle': 'onPopoverToggleClick',
'click @ui.resetInput': 'onResetInputClick',
} );
}
onResetInputClick() {
const globalData = this.model.get( 'global' );
if ( globalData?.active ) {
this.triggerMethod( 'value:type:change' );
}
}
onInputChange( event ) {
if ( event.currentTarget !== this.ui.popoverToggle[ 0 ] ) {
return;
}
// If the control has a global value, unset the global.
if ( this.getGlobalKey() ) {
this.triggerMethod( 'unset:global:value' );
} else if ( this.isGlobalActive() ) {
this.triggerMethod( 'value:type:change' );
}
}
onPopoverToggleClick() {
if ( this.isGlobalActive() && ! this.getControlValue() && ! this.getGlobalKey() && this.getGlobalDefault() ) {
this.triggerMethod( 'unlink:global:default' );
}
this.$el.next( '.elementor-controls-popover' ).toggle();
}
getGlobalCommand() {
return 'globals/typography';
}
buildPreviewItemCSS( globalValue ) {
const cssObject = {};
Object.entries( globalValue ).forEach( ( [ property, value ] ) => {
// If a control value is empty, ignore it.
if ( ! value || '' === value.size ) {
return;
}
if ( property.startsWith( 'typography_' ) ) {
property = property.replace( 'typography_', '' );
}
if ( 'font_family' === property ) {
elementor.helpers.enqueueFont( value, 'editor' );
}
if ( 'font_size' === property ) {
// Set max size for Typography previews in the select popover so it isn't too big.
if ( value.size > 40 ) {
value.size = 40;
}
cssObject.fontSize = value.size + value.unit;
} else {
// Convert the snake case property names into camel case to match their corresponding CSS property names.
if ( property.includes( '_' ) ) {
property = property.replace( /([_][a-z])/g, ( result ) => result.toUpperCase().replace( '_', '' ) );
}
cssObject[ property ] = value;
}
} );
return cssObject;
}
createGlobalItemMarkup( globalData ) {
const $typographyPreview = jQuery( '<div>', { class: 'e-global__preview-item e-global__typography', 'data-global-id': globalData.id } );
$typographyPreview
.html( globalData.title )
.css( this.buildPreviewItemCSS( globalData.value ) );
return $typographyPreview;
}
getGlobalMeta() {
return {
commandName: this.getGlobalCommand(),
key: this.model.get( 'name' ),
title: __( 'New Typography Setting', 'elementor' ),
controlType: 'typography',
route: 'panel/global/global-typography',
};
}
getAddGlobalConfirmMessage() {
const globalData = this.getGlobalMeta(),
$message = jQuery( '<div>', { class: 'e-global__confirm-message' } ),
$messageText = jQuery( '<div>' )
.html( __( 'Are you sure you want to create a new Global Font setting?', 'elementor' ) ),
$inputWrapper = jQuery( '<div>', { class: 'e-global__confirm-input-wrapper' } ),
$input = jQuery( '<input>', { type: 'text', name: 'global-name', placeholder: globalData.title } )
.val( globalData.title );
$inputWrapper.append( $input );
$message.append( $messageText, $inputWrapper );
return $message;
}
async getGlobalsList() {
const result = await $e.data.get( this.getGlobalCommand() );
return result.data;
}
buildGlobalsList( globalTypographies, $globalPreviewItemsContainer ) {
Object.values( globalTypographies ).forEach( ( typography ) => {
// Only build markup if the typography is valid.
if ( typography ) {
const $typographyPreview = this.createGlobalItemMarkup( typography );
$globalPreviewItemsContainer.append( $typographyPreview );
}
} );
}
onAddGlobalButtonClick() {
this.triggerMethod( 'add:global:to:list', this.getAddGlobalConfirmMessage() );
}
}
ControlPopoverStarterView.onPasteStyle = ( control, clipboardValue ) => {
return ! clipboardValue || clipboardValue === control.return_value;
};

View File

@@ -0,0 +1,101 @@
import ControlsStack from 'elementor-views/controls-stack';
module.exports = Marionette.CompositeView.extend( {
template: Marionette.TemplateCache.get( '#tmpl-elementor-repeater-row' ),
className: 'elementor-repeater-fields',
ui: {
duplicateButton: '.elementor-repeater-tool-duplicate',
editButton: '.elementor-repeater-tool-edit',
removeButton: '.elementor-repeater-tool-remove',
itemTitle: '.elementor-repeater-row-item-title',
},
behaviors: {
HandleInnerTabs: {
behaviorClass: require( 'elementor-behaviors/inner-tabs' ),
},
},
triggers: {
'click @ui.removeButton': 'click:remove',
'click @ui.duplicateButton': 'click:duplicate',
'click @ui.itemTitle': 'click:edit',
},
modelEvents: {
change: 'onModelChange',
},
templateHelpers: function() {
return {
itemIndex: this.getOption( 'itemIndex' ),
itemActions: this.getOption( 'itemActions' ),
};
},
childViewContainer: '.elementor-repeater-row-controls',
getChildView: function( item ) {
var controlType = item.get( 'type' );
return elementor.getControlView( controlType );
},
childViewOptions: function() {
return {
container: this.options.container,
};
},
updateIndex: function( newIndex ) {
this.itemIndex = newIndex;
},
setTitle: function() {
const titleField = this.getOption( 'titleField' );
let title = '';
if ( titleField ) {
title = Marionette.TemplateCache.prototype.compileTemplate( titleField )( this.model.parseDynamicSettings() );
}
if ( ! title ) {
/* translators: %s: Item Index (number). */
title = sprintf( __( 'Item #%s', 'elementor' ), this.getOption( 'itemIndex' ) );
}
this.ui.itemTitle.html( title );
},
toggleSort( enable ) {
this.$el.toggleClass( 'elementor-repeater-row--disable-sort', ! enable );
},
initialize: function( options ) {
this.itemIndex = 0;
// Collection for Controls list
this.collection = new Backbone.Collection( _.values( elementor.mergeControlsSettings( options.controlFields ) ) );
},
onRender: function() {
this.setTitle();
ControlsStack.handlePopovers( this );
},
onModelChange: function() {
if ( this.getOption( 'titleField' ) ) {
this.setTitle();
}
},
onChildviewResponsiveSwitcherClick: function( childView, device ) {
if ( 'desktop' === device ) {
elementor.getPanelView().getCurrentPageView().$el.toggleClass( 'elementor-responsive-switchers-open' );
}
},
} );

View File

@@ -0,0 +1,270 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
RepeaterRowView = require( 'elementor-controls/repeater-row' ),
ControlRepeaterItemView;
ControlRepeaterItemView = ControlBaseDataView.extend( {
ui: {
btnAddRow: '.elementor-repeater-add',
fieldContainer: '.elementor-repeater-fields-wrapper',
},
events: function() {
return {
'click @ui.btnAddRow': 'onButtonAddRowClick',
'sortstart @ui.fieldContainer': 'onSortStart',
'sortupdate @ui.fieldContainer': 'onSortUpdate',
'sortstop @ui.fieldContainer': 'onSortStop',
};
},
childView: RepeaterRowView,
childViewContainer: '.elementor-repeater-fields-wrapper',
templateHelpers: function() {
return {
itemActions: this.model.get( 'item_actions' ),
data: _.extend( {}, this.model.toJSON(), { controlValue: [] } ),
};
},
childViewOptions: function( rowModel, index ) {
const elementContainer = this.getOption( 'container' );
return {
container: elementContainer.repeaters[ this.model.get( 'name' ) ].children[ index ],
controlFields: this.model.get( 'fields' ),
titleField: this.model.get( 'title_field' ),
itemActions: this.model.get( 'item_actions' ),
};
},
createItemModel: function( attrs, options, controlView ) {
options.controls = controlView.model.get( 'fields' );
return new elementorModules.editor.elements.models.BaseSettings( attrs, options );
},
fillCollection: function() {
// TODO: elementSettingsModel is deprecated since 2.8.0.
const settings = this.container ? this.container.settings : this.elementSettingsModel;
var controlName = this.model.get( 'name' );
this.collection = settings.get( controlName );
// Hack for history redo/undo
if ( ! ( this.collection instanceof Backbone.Collection ) ) {
this.collection = new Backbone.Collection( this.collection, {
// Use `partial` to supply the `this` as an argument, but not as context
// the `_` is a place holder for original arguments: `attrs` & `options`
model: _.partial( this.createItemModel, _, _, this ),
} );
// Set the value silent
settings.set( controlName, this.collection, { silent: true } );
}
},
initialize: function() {
ControlBaseDataView.prototype.initialize.apply( this, arguments );
this.fillCollection();
this.listenTo( this.collection, 'reset', this.resetContainer.bind( this ) );
this.listenTo( this.collection, 'add', this.updateContainer.bind( this ) );
},
editRow: function( rowView ) {
if ( this.currentEditableChild ) {
var currentEditable = this.currentEditableChild.getChildViewContainer( this.currentEditableChild );
currentEditable.removeClass( 'editable' );
// If the repeater contains TinyMCE editors, fire the `hide` trigger to hide floated toolbars
currentEditable.find( '.elementor-wp-editor' ).each( function() {
tinymce.get( this.id ).fire( 'hide' );
} );
}
if ( this.currentEditableChild === rowView ) {
delete this.currentEditableChild;
return;
}
rowView.getChildViewContainer( rowView ).addClass( 'editable' );
this.currentEditableChild = rowView;
this.updateActiveRow();
},
toggleMinRowsClass: function() {
if ( ! this.model.get( 'prevent_empty' ) ) {
return;
}
this.$el.toggleClass( 'elementor-repeater-has-minimum-rows', 1 >= this.collection.length );
},
updateActiveRow: function() {
var activeItemIndex = 1;
if ( this.currentEditableChild ) {
activeItemIndex = this.currentEditableChild.itemIndex;
}
this.setEditSetting( 'activeItemIndex', activeItemIndex );
},
updateChildIndexes: function() {
var collection = this.collection;
this.children.each( function( view ) {
view.updateIndex( collection.indexOf( view.model ) + 1 );
view.setTitle();
} );
},
onRender: function() {
ControlBaseDataView.prototype.onRender.apply( this, arguments );
if ( this.model.get( 'item_actions' ).sort ) {
this.ui.fieldContainer.sortable( {
axis: 'y',
handle: '.elementor-repeater-row-tools',
items: ' > :not(.elementor-repeater-row--disable-sort)',
} );
}
this.toggleMinRowsClass();
},
onSortStart: function( event, ui ) {
ui.item.data( 'oldIndex', ui.item.index() );
},
onSortStop: function( event, ui ) {
// Reload TinyMCE editors (if exist), it's a bug that TinyMCE content is missing after stop dragging
var self = this,
sortedIndex = ui.item.index();
if ( -1 === sortedIndex ) {
return;
}
var sortedRowView = self.children.findByIndex( ui.item.index() ),
rowControls = sortedRowView.children._views;
jQuery.each( rowControls, function() {
if ( 'wysiwyg' === this.model.get( 'type' ) ) {
sortedRowView.render();
delete self.currentEditableChild;
return false;
}
} );
},
onSortUpdate: function( event, ui ) {
const oldIndex = ui.item.data( 'oldIndex' ),
newIndex = ui.item.index();
$e.run( 'document/repeater/move', {
container: this.options.container,
name: this.model.get( 'name' ),
sourceIndex: oldIndex,
targetIndex: newIndex,
} );
},
onAddChild: function() {
this.updateChildIndexes();
this.updateActiveRow();
},
// BC since 3.0.0, ensure a new child is appear in container children.
updateContainer( model ) {
const container = this.options.container.repeaters[ this.model.get( 'name' ) ],
isInChildren = container.children.filter( ( child ) => {
return child.id === model.get( '_id' );
} );
if ( ! isInChildren.length ) {
elementorCommon.helpers.softDeprecated( 'Don\'t add models directly to the repeater.', '3.0.0', '$e.run( \'document/repeater/insert\' )' );
this.options.container.addRepeaterItem( this.model.get( 'name' ), model, model.collection.indexOf( model ) );
}
},
// BC since 3.0.0, ensure a container children are reset on collection reset.
resetContainer: function() {
elementorCommon.helpers.softDeprecated( 'Don\'t reset repeater collection directly.', '3.0.0', '$e.run( \'document/repeater/remove\' )' );
this.options.container.repeaters[ this.model.get( 'name' ) ].children = [];
},
getDefaults: function() {
const defaults = {};
// Get default fields.
_.each( this.model.get( 'fields' ), ( field ) => {
defaults[ field.name ] = field.default;
} );
return defaults;
},
onButtonAddRowClick: function() {
const newModel = $e.run( 'document/repeater/insert', {
container: this.options.container,
name: this.model.get( 'name' ),
model: this.getDefaults(),
} );
const newChild = this.children.findByModel( newModel );
this.editRow( newChild );
this.toggleMinRowsClass();
},
onChildviewClickRemove: function( childView ) {
if ( childView === this.currentEditableChild ) {
delete this.currentEditableChild;
}
$e.run( 'document/repeater/remove', {
container: this.options.container,
name: this.model.get( 'name' ),
index: childView._index,
} );
this.updateActiveRow();
this.updateChildIndexes();
this.toggleMinRowsClass();
},
onChildviewClickDuplicate: function( childView ) {
$e.run( 'document/repeater/duplicate', {
container: this.options.container,
name: this.model.get( 'name' ),
index: childView._index,
} );
this.toggleMinRowsClass();
},
onChildviewClickEdit: function( childView ) {
this.editRow( childView );
},
onAfterExternalChange: function() {
// Update the collection with current value
this.fillCollection();
ControlBaseDataView.prototype.onAfterExternalChange.apply( this, arguments );
},
} );
module.exports = ControlRepeaterItemView;

View File

@@ -0,0 +1,18 @@
var ControlBaseView = require( 'elementor-controls/base' ),
ControlSectionItemView;
ControlSectionItemView = ControlBaseView.extend( {
ui: function() {
var ui = ControlBaseView.prototype.ui.apply( this, arguments );
ui.heading = '.elementor-panel-heading';
return ui;
},
triggers: {
click: 'control:section:clicked',
},
} );
module.exports = ControlSectionItemView;

View File

@@ -0,0 +1,17 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlSelectItemView;
ControlSelectItemView = ControlBaseDataView.extend( {}, {
onPasteStyle: function( control, clipboardValue ) {
if ( control.groups ) {
return control.groups.some( function( group ) {
return ControlSelectItemView.onPasteStyle( group, clipboardValue );
} );
}
return undefined !== control.options[ clipboardValue ];
},
} );
module.exports = ControlSelectItemView;

View File

@@ -0,0 +1,82 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlSelect2ItemView;
import Select2 from 'elementor-editor-utils/select2.js';
ControlSelect2ItemView = ControlBaseDataView.extend( {
getSelect2Placeholder: function() {
return this.ui.select.children( 'option:first[value=""]' ).text();
},
getSelect2DefaultOptions: function() {
const defaultOptions = {
allowClear: true,
placeholder: this.getSelect2Placeholder(),
dir: elementorCommon.config.isRTL ? 'rtl' : 'ltr',
},
lockedOptions = this.model.get( 'lockedOptions' );
// If any non-deletable options are passed, remove the 'x' element from the template for selected items.
if ( lockedOptions ) {
defaultOptions.templateSelection = ( data, container ) => {
if ( lockedOptions.includes( data.id ) ) {
jQuery( container )
.addClass( 'e-non-deletable' )
.find( '.select2-selection__choice__remove' ).remove();
}
return data.text;
};
}
return defaultOptions;
},
getSelect2Options: function() {
return jQuery.extend( this.getSelect2DefaultOptions(), this.model.get( 'select2options' ) );
},
applySavedValue: function() {
ControlBaseDataView.prototype.applySavedValue.apply( this, arguments );
const elementSelect2Data = this.ui.select.data( 'select2' );
// Checking if the element itself was initiated with select2 functionality in case of multiple renders.
if ( ! elementSelect2Data ) {
this.select2Instance = new Select2( {
$element: this.ui.select,
options: this.getSelect2Options(),
} );
this.handleLockedOptions();
} else {
this.ui.select.trigger( 'change' );
}
},
handleLockedOptions() {
const lockedOptions = this.model.get( 'lockedOptions' );
if ( lockedOptions ) {
this.ui.select.on( 'select2:unselecting', ( event ) => {
if ( lockedOptions.includes( event.params.args.data.id ) ) {
event.preventDefault();
}
} );
}
},
onReady: function() {
elementorCommon.helpers.softDeprecated( 'onReady', '3.0.0' );
},
onBeforeDestroy: function() {
// We always destroy the select2 instance because there are cases where the DOM element's data cache
// itself has been destroyed but the select2 instance on it still exists
this.select2Instance.destroy();
this.$el.remove();
},
} );
module.exports = ControlSelect2ItemView;

View File

@@ -0,0 +1,147 @@
var ControlBaseUnitsItemView = require( 'elementor-controls/base-units' ),
ControlSliderItemView;
ControlSliderItemView = ControlBaseUnitsItemView.extend( {
ui: function() {
var ui = ControlBaseUnitsItemView.prototype.ui.apply( this, arguments );
ui.slider = '.elementor-slider';
return ui;
},
templateHelpers: function() {
const templateHelpers = ControlBaseUnitsItemView.prototype.templateHelpers.apply( this, arguments );
templateHelpers.isMultiple = this.isMultiple();
return templateHelpers;
},
isMultiple: function() {
const sizes = this.getControlValue( 'sizes' );
return ! jQuery.isEmptyObject( sizes );
},
initSlider: function() {
// Slider does not exist in tests.
if ( ! this.ui.slider[ 0 ] ) {
return;
}
this.destroySlider();
const isMultiple = this.isMultiple(),
unitRange = elementorCommon.helpers.cloneObject( this.getCurrentRange() ),
step = unitRange.step;
let sizes = this.getSize();
if ( isMultiple ) {
sizes = Object.values( sizes );
} else {
sizes = [ sizes ];
this.ui.input.attr( unitRange );
}
delete unitRange.step;
let tooltips;
const self = this;
if ( isMultiple ) {
tooltips = [];
sizes.forEach( () => tooltips.push( {
to: ( value ) => value + self.getControlValue( 'unit' ),
} ) );
}
const sliderInstance = noUiSlider.create( this.ui.slider[ 0 ], {
start: sizes,
range: unitRange,
step: step,
tooltips: tooltips,
connect: isMultiple,
format: {
to: ( value ) => Math.round( value * 1000 ) / 1000,
from: ( value ) => +value,
},
} );
sliderInstance.on( 'slide', this.onSlideChange.bind( this ) );
},
applySavedValue: function() {
ControlBaseUnitsItemView.prototype.applySavedValue.apply( this, arguments );
// Slider does not exist in tests.
if ( this.ui.slider[ 0 ] && this.ui.slider[ 0 ].noUiSlider ) {
this.ui.slider[ 0 ].noUiSlider.set( this.getSize() );
}
},
getSize: function() {
return this.getControlValue( this.isMultiple() ? 'sizes' : 'size' );
},
resetSize: function() {
if ( this.isMultiple() ) {
this.setValue( 'sizes', {} );
} else {
this.setValue( 'size', '' );
}
this.initSlider();
},
destroySlider: function() {
// Slider does not exist in tests.
if ( this.ui.slider[ 0 ] && this.ui.slider[ 0 ].noUiSlider ) {
this.ui.slider[ 0 ].noUiSlider.destroy();
}
},
onReady: function() {
if ( this.isMultiple() ) {
this.$el.addClass( 'elementor-control-type-slider--multiple elementor-control-type-slider--handles-' + this.model.get( 'handles' ) );
}
this.initSlider();
},
onSlideChange: function( values, index ) {
if ( this.isMultiple() ) {
const sizes = elementorCommon.helpers.cloneObject( this.getSize() ),
key = Object.keys( sizes )[ index ];
sizes[ key ] = values[ index ];
this.setValue( 'sizes', sizes );
} else {
this.setValue( 'size', values[ 0 ] );
this.ui.input.val( values[ 0 ] );
}
},
onInputChange: function( event ) {
var dataChanged = event.currentTarget.dataset.setting;
if ( 'size' === dataChanged ) {
this.ui.slider[ 0 ].noUiSlider.set( this.getSize() );
} else if ( 'unit' === dataChanged ) {
this.resetSize();
}
},
onBeforeDestroy: function() {
this.destroySlider();
this.$el.remove();
},
} );
module.exports = ControlSliderItemView;

View File

@@ -0,0 +1,44 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlStructureItemView;
ControlStructureItemView = ControlBaseDataView.extend( {
ui: function() {
var ui = ControlBaseDataView.prototype.ui.apply( this, arguments );
ui.resetStructure = '.elementor-control-structure-reset';
return ui;
},
events: function() {
return _.extend( ControlBaseDataView.prototype.events.apply( this, arguments ), {
'click @ui.resetStructure': 'onResetStructureClick',
} );
},
templateHelpers: function() {
var helpers = ControlBaseDataView.prototype.templateHelpers.apply( this, arguments );
helpers.getMorePresets = this.getMorePresets.bind( this );
return helpers;
},
getCurrentEditedSection: function() {
var editor = elementor.getPanelView().getCurrentPageView();
return editor.getOption( 'editedElementView' );
},
getMorePresets: function() {
var parsedStructure = elementor.presetsFactory.getParsedStructure( this.getControlValue() );
return elementor.presetsFactory.getPresets( parsedStructure.columnsCount );
},
onResetStructureClick: function() {
this.getCurrentEditedSection().resetColumnsCustomSize();
},
} );
module.exports = ControlStructureItemView;

View File

@@ -0,0 +1,13 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' );
module.exports = ControlBaseDataView.extend( {
setInputValue: function( input, value ) {
this.$( input ).prop( 'checked', this.model.get( 'return_value' ) === value );
},
}, {
onPasteStyle: function( control, clipboardValue ) {
return ! clipboardValue || clipboardValue === control.return_value;
},
} );

View File

@@ -0,0 +1,13 @@
var ControlBaseView = require( 'elementor-controls/base' ),
ControlTabItemView;
ControlTabItemView = ControlBaseView.extend( {
triggers: {
click: {
event: 'control:tab:clicked',
stopPropagation: false,
},
},
} );
module.exports = ControlTabItemView;

View File

@@ -0,0 +1,113 @@
const BaseMultiple = require( 'elementor-controls/base-multiple' );
class URL extends BaseMultiple {
ui() {
const ui = super.ui();
ui.mainInput = '.elementor-input';
ui.moreOptionsToggle = '.elementor-control-url-more';
ui.moreOptions = '.elementor-control-url-more-options';
return ui;
}
events() {
const events = super.events();
events[ 'click @ui.moreOptionsToggle' ] = 'onMoreOptionsToggleClick';
return events;
}
autoComplete() {
const $mainInput = this.ui.mainInput,
positionBase = elementorCommon.config.isRTL ? 'right' : 'left';
let last, cache;
// Based on /wp-includes/js/tinymce/plugins/wplink/plugin.js.
$mainInput.autocomplete( {
source: ( request, response ) => {
if ( ! this.options.model.attributes.autocomplete ) {
return;
}
if ( last === request.term ) {
response( cache );
return;
}
if ( /^https?:/.test( request.term ) || request.term.indexOf( '.' ) !== -1 ) {
return response();
}
// Show Spinner.
$mainInput.prev().show();
jQuery.post( window.ajaxurl, {
editor: 'elementor',
action: 'wp-link-ajax',
page: 1,
search: request.term,
_ajax_linking_nonce: jQuery( '#_ajax_linking_nonce' ).val(),
}, ( data ) => {
cache = data;
response( data );
}, 'json' )
.always( () => $mainInput.prev().hide() );
last = request.term;
},
focus: ( event ) => {
/*
* Don't empty the URL input field, when using the arrow keys to
* highlight items. See api.jqueryui.com/autocomplete/#event-focus
*/
event.preventDefault();
},
select: ( event, ui ) => {
$mainInput.val( ui.item.permalink );
this.setValue( 'url', ui.item.permalink );
return false;
},
open: ( event ) => {
jQuery( event.target ).data( 'uiAutocomplete' ).menu.activeMenu.addClass( 'elementor-autocomplete-menu' );
},
minLength: 2,
position: {
my: positionBase + ' top+2',
at: positionBase + ' bottom',
},
} );
// The `_renderItem` cannot be override via the arguments.
$mainInput.autocomplete( 'instance' )._renderItem = ( ul, item ) => {
const fallbackTitle = window.wpLinkL10n ? window.wpLinkL10n.noTitle : '',
title = item.title ? item.title : fallbackTitle;
return jQuery( '<li role="option" id="mce-wp-autocomplete-' + item.ID + '">' )
.append( '<span>' + title + '</span>&nbsp;<span class="elementor-autocomplete-item-info">' + item.info + '</span>' )
.appendTo( ul );
};
}
onReady() {
this.autoComplete();
}
onMoreOptionsToggleClick() {
this.ui.moreOptions.slideToggle();
}
onBeforeDestroy() {
if ( this.ui.mainInput.data( 'autocomplete' ) ) {
this.ui.mainInput.autocomplete( 'destroy' );
}
this.$el.remove();
}
}
module.exports = URL;

View File

@@ -0,0 +1,59 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlWPWidgetItemView;
ControlWPWidgetItemView = ControlBaseDataView.extend( {
ui: function() {
var ui = ControlBaseDataView.prototype.ui.apply( this, arguments );
ui.form = 'form';
ui.loading = '.wp-widget-form-loading';
return ui;
},
events: function() {
return {
'keyup @ui.form :input': 'onFormChanged',
'change @ui.form :input': 'onFormChanged',
};
},
onFormChanged: function() {
var idBase = 'widget-' + this.model.get( 'id_base' ),
settings = this.ui.form.elementorSerializeObject()[ idBase ].REPLACE_TO_ID;
this.setValue( settings );
},
onReady: function() {
var self = this;
elementorCommon.ajax.addRequest( 'editor_get_wp_widget_form', {
data: {
// Fake Widget ID
id: self.model.cid,
widget_type: self.model.get( 'widget' ),
data: self.container.settings.toJSON(),
},
success: function( data ) {
self.ui.form.html( data );
// WP >= 4.8
if ( wp.textWidgets ) {
self.ui.form.addClass( 'open' );
var event = new jQuery.Event( 'widget-added' );
wp.textWidgets.handleWidgetAdded( event, self.ui.form );
wp.mediaWidgets.handleWidgetAdded( event, self.ui.form );
// WP >= 4.9
if ( wp.customHtmlWidgets ) {
wp.customHtmlWidgets.handleWidgetAdded( event, self.ui.form );
}
}
elementor.hooks.doAction( 'panel/widgets/' + self.model.get( 'widget' ) + '/controls/wp_widget/loaded', self );
},
} );
},
} );
module.exports = ControlWPWidgetItemView;

View File

@@ -0,0 +1,183 @@
var ControlBaseDataView = require( 'elementor-controls/base-data' ),
ControlWysiwygItemView;
ControlWysiwygItemView = ControlBaseDataView.extend( {
editor: null,
ui: function() {
var ui = ControlBaseDataView.prototype.ui.apply( this, arguments );
jQuery.extend( ui, {
inputWrapper: '.elementor-control-input-wrapper',
} );
return ui;
},
events: function() {
return _.extend( ControlBaseDataView.prototype.events.apply( this, arguments ), {
'keyup textarea.elementor-wp-editor': 'onBaseInputChange',
} );
},
// List of buttons to move {buttonToMove: afterButton}
buttons: {
addToBasic: {
underline: 'italic',
},
addToAdvanced: {},
moveToAdvanced: {
blockquote: 'removeformat',
alignleft: 'blockquote',
aligncenter: 'alignleft',
alignright: 'aligncenter',
},
moveToBasic: {},
removeFromBasic: [ 'unlink', 'wp_more' ],
removeFromAdvanced: [],
},
initialize: function() {
ControlBaseDataView.prototype.initialize.apply( this, arguments );
var self = this;
self.editorID = 'elementorwpeditor' + self.cid;
// Wait a cycle before initializing the editors.
_.defer( function() {
if ( self.isDestroyed ) {
return;
}
// Initialize QuickTags, and set as the default mode.
quicktags( {
buttons: 'strong,em,del,link,img,close',
id: self.editorID,
} );
if ( elementor.config.rich_editing_enabled ) {
switchEditors.go( self.editorID, 'tmce' );
}
delete QTags.instances[ 0 ];
} );
if ( ! elementor.config.rich_editing_enabled ) {
self.$el.addClass( 'elementor-rich-editing-disabled' );
return;
}
var editorConfig = {
id: self.editorID,
selector: '#' + self.editorID,
setup: function( editor ) {
self.editor = editor;
},
};
tinyMCEPreInit.mceInit[ self.editorID ] = _.extend( _.clone( tinyMCEPreInit.mceInit.elementorwpeditor ), editorConfig );
if ( ! elementor.config.tinymceHasCustomConfig ) {
self.rearrangeButtons();
}
},
applySavedValue: function() {
if ( ! this.editor ) {
return;
}
var controlValue = this.getControlValue();
this.editor.setContent( controlValue );
// Update also the plain textarea
jQuery( '#' + this.editorID ).val( controlValue );
},
saveEditor: function() {
this.setValue( this.editor.getContent() );
},
moveButtons: function( buttonsToMove, from, to ) {
if ( ! to ) {
to = from;
from = null;
}
_.each( buttonsToMove, function( afterButton, button ) {
var afterButtonIndex = to.indexOf( afterButton );
if ( from ) {
var buttonIndex = from.indexOf( button );
if ( -1 === buttonIndex ) {
throw new ReferenceError( 'Trying to move non-existing button `' + button + '`' );
}
from.splice( buttonIndex, 1 );
}
if ( -1 === afterButtonIndex ) {
throw new ReferenceError( 'Trying to move button after non-existing button `' + afterButton + '`' );
}
to.splice( afterButtonIndex + 1, 0, button );
} );
},
rearrangeButtons: function() {
var editorProps = tinyMCEPreInit.mceInit[ this.editorID ],
editorBasicToolbarButtons = editorProps.toolbar1.split( ',' ),
editorAdvancedToolbarButtons = editorProps.toolbar2.split( ',' );
editorBasicToolbarButtons = _.difference( editorBasicToolbarButtons, this.buttons.removeFromBasic );
editorAdvancedToolbarButtons = _.difference( editorAdvancedToolbarButtons, this.buttons.removeFromAdvanced );
this.moveButtons( this.buttons.moveToBasic, editorAdvancedToolbarButtons, editorBasicToolbarButtons );
this.moveButtons( this.buttons.moveToAdvanced, editorBasicToolbarButtons, editorAdvancedToolbarButtons );
this.moveButtons( this.buttons.addToBasic, editorBasicToolbarButtons );
this.moveButtons( this.buttons.addToAdvanced, editorAdvancedToolbarButtons );
editorProps.toolbar1 = editorBasicToolbarButtons.join( ',' );
editorProps.toolbar2 = editorAdvancedToolbarButtons.join( ',' );
},
onReady: function() {
const $editor = jQuery( elementor.config.wp_editor.replace( /elementorwpeditor/g, this.editorID ).replace( '%%EDITORCONTENT%%', this.getControlValue() ) );
$editor.find( `.wp-editor-tabs` ).addClass( 'elementor-control-dynamic-switcher-wrapper' );
this.ui.inputWrapper.html( $editor );
setTimeout( () => {
if ( ! this.isDestroyed && this.editor ) {
this.editor.on( 'keyup change undo redo', this.saveEditor.bind( this ) );
}
}, 100 );
},
onBeforeDestroy: function() {
// Remove TinyMCE and QuickTags instances
delete QTags.instances[ this.editorID ];
if ( ! elementor.config.rich_editing_enabled ) {
return;
}
tinymce.EditorManager.execCommand( 'mceRemoveEditor', true, this.editorID );
// Cleanup PreInit data
delete tinyMCEPreInit.mceInit[ this.editorID ];
delete tinyMCEPreInit.qtInit[ this.editorID ];
},
} );
module.exports = ControlWysiwygItemView;

View File

@@ -0,0 +1,9 @@
import CommandBase from 'elementor-api/modules/command-base';
export default class CreateBase extends CommandBase {
validateArgs( args = {} ) {
this.requireContainer( args );
this.requireArgumentType( 'setting', 'string', args );
this.requireArgumentType( 'title', 'string', args );
}
}

View File

@@ -0,0 +1,24 @@
import CreateBase from 'elementor-editor/data/globals/base/create-base';
export class Create extends CreateBase {
apply( args = {} ) {
const { container, setting, title } = args,
controls = container.controls;
let result = false;
if ( ! controls[ setting ] ) {
throw new Error( `Invalid setting: control '${ setting }', not found.` );
}
// `args.id` used by tests.
const id = args.id || elementorCommon.helpers.getUniqueId();
result = $e.data.create( `globals/colors?id=${ id }`, {
title,
value: container.settings.get( setting ),
} );
return result;
}
}

View File

@@ -0,0 +1 @@
export { Create } from './create';

View File

@@ -0,0 +1,12 @@
import ComponentBase from 'elementor-api/modules/component-base';
import * as commands from './commands/';
export default class Component extends ComponentBase {
getNamespace() {
return 'globals/colors';
}
defaultCommands() {
return this.importCommands( commands );
}
}

View File

@@ -0,0 +1,9 @@
import CommandData from 'elementor-api/modules/command-data';
export class Colors extends CommandData {
static getEndpointFormat() {
return 'globals/colors/{id}';
}
}
export default Colors;

View File

@@ -0,0 +1,13 @@
import CommandData from 'elementor-api/modules/command-data';
// Alphabetical order
export { Colors } from './colors';
export { Typography } from './typography';
// TODO: Remove - Move to into base, Possible to handle using ComponentData.
export class Index extends CommandData {
static getEndpointFormat() {
// Format 'globals/index' to 'globals'.
return 'globals';
}
}

Some files were not shown because too many files have changed in this diff Show More