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,192 @@
.elementor-panel {
#elementor-panel-header {
#elementor-panel-header-kit-close,
#elementor-panel-header-kit-back {
display: none;
}
}
}
.elementor-editor-kit {
.elementor-panel {
#elementor-panel-header {
background-color: $editor-kit-accent;
#elementor-panel-header-kit-close,
#elementor-panel-header-kit-back {
display: table-cell;
}
@at-root body:not(.e-routes-has-history) #elementor-panel-header-kit-back {
pointer-events: none;
opacity: 0.4;
&:hover {
.elementor-icon {
cursor: inherit;
color: inherit;
}
}
}
}
.elementor-panel-navigation {
.elementor-panel-navigation-tab{
&.elementor-active{
border-bottom-color: $editor-kit-accent;
}
}
}
#elementor-panel-page-settings {
.elementor-panel-navigation {
display: none;
}
}
#elementor-panel-saver-button-publish,
#elementor-panel-saver-button-save-options {
&.elementor-button.elementor-button-success:not(.elementor-disabled) {
background-color: $editor-kit-accent;
}
}
#elementor-panel-page-menu {
padding: 25px 15px 0;
}
}
#elementor-panel-header-add-button,
#elementor-panel-header-menu-button,
#elementor-panel-footer-sub-menu-item-save-template,
#elementor-panel-footer-navigator {
display: none;
}
}
.elementor-control {
&-type-global-style-repeater {
.elementor-repeater {
&-fields {
margin-bottom: 15px;
position: relative;
}
&-row-controls {
display: flex;
align-items: center;
}
&-row-tool {
cursor: pointer;
color: $editor-lightest;
padding: 5px;
}
}
.elementor-control {
padding: 0;
&-title {
flex-grow: 1;
input {
max-width: $control-unit * 5;
&:not(:focus) {
background: none;
border: none;
}
}
}
&-input-wrapper {
display: flex;
align-items: center;
// Fix HEX values flickering when moving the mouse over them.
min-width: $control-unit * 3;
&:hover {
.e-global-colors__color-value {
display: none;
}
}
&:not(:hover) {
.elementor-repeater-tool-remove,
.elementor-repeater-tool-remove--disabled {
display: none;
}
}
}
}
// Hard selector to override default margin
.elementor-control-content {
> .elementor-control-field {
> .elementor-control-input-wrapper {
margin-top: 0;
}
}
}
.elementor-controls-popover {
align-self: end;
margin-top: 35px;
top: 0;
width: 100%;
&:before {
@include end(5px);
}
.elementor-control {
padding: 0 20px 15px;
&-title {
flex-grow: initial;
}
}
}
.elementor-button-wrapper {
text-align: center;
border-top: 1px solid $editor-background;
max-width: 260px;
padding-top: 15px;
}
.pickr,
.elementor-control-popover-toggle-toggle-label {
@include margin-start(5px);
}
}
&-system_colors,
&-system_typography {
padding-bottom: 0;
}
}
.e-global-colors__color-value {
color: $editor-lightest;
font-size: 10px;
padding: 0 5px;
text-align: $end;
@include ellipsis;
}

View File

@@ -0,0 +1,48 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Back extends CommandBase {
confirmDialog = null;
apply() {
const panelHistory = $e.routes.getHistory( 'panel' );
// Don't go back if no where.
if ( 1 === panelHistory.length ) {
this.getCloseConfirmDialog( event ).show();
return;
}
return $e.routes.back( 'panel' );
}
getCloseConfirmDialog( event ) {
if ( ! this.confirmDialog ) {
const modalOptions = {
id: 'elementor-kit-warn-on-close',
headerMessage: __( 'Exit', 'elementor' ),
message: __( 'Would you like to exit?', 'elementor' ),
position: {
my: 'center center',
at: 'center center',
},
strings: {
confirm: __( 'Exit', 'elementor' ),
cancel: __( 'Cancel', 'elementor' ),
},
onConfirm: () => {
$e.run( 'panel/global/close' );
},
};
this.confirmDialog = elementorCommon.dialogsManager.createWidget( 'confirm', modalOptions );
}
this.confirmDialog.setSettings( 'hide', {
onEscKeyPress: ! event,
} );
return this.confirmDialog;
}
}
export default Back;

View File

@@ -0,0 +1,34 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Close extends CommandBase {
apply( args ) {
const { mode } = args;
// The kit is opened directly.
if ( elementor.config.initial_document.id === parseInt( elementor.config.kit_id ) ) {
return $e.run( 'panel/global/exit' );
}
$e.internal( 'panel/state-loading' );
return $e.run( 'editor/documents/switch', {
mode,
id: elementor.config.initial_document.id,
onClose: ( document ) => {
if ( document.isDraft() ) {
// Restore published style.
elementor.toggleDocumentCssFiles( document, true );
elementor.settings.page.destroyControlsCSS();
}
$e.components.get( 'panel/global' ).close();
$e.routes.clearHistory( this.component.getRootContainer() );
// The kit shouldn't be cached for next open. (it may be changed via create colors/typography).
elementor.documents.invalidateCache( elementor.config.kit_id );
},
} ).finally( () => $e.internal( 'panel/state-ready' ) );
}
}
export default Close;

View File

@@ -0,0 +1,14 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Exit extends CommandBase {
apply() {
return $e.run( 'editor/documents/close', {
id: elementor.config.kit_id,
onClose: ( document ) => {
location = document.config.urls.exit_to_dashboard;
},
} );
}
}
export default Exit;

View File

@@ -0,0 +1,6 @@
// Alphabetical order.
export { Back } from './back';
export { Close } from './close';
export { Exit } from './exit';
export { Open } from './open';

View File

@@ -0,0 +1,30 @@
import CommandBase from 'elementor-api/modules/command-base';
export class Open extends CommandBase {
static getInfo() {
return {
isSafe: true,
};
}
apply() {
const kit = elementor.documents.get( elementor.config.kit_id );
if ( kit && 'open' === kit.editor.status ) {
return jQuery.Deferred().resolve();
}
$e.routes.clearHistory( this.component.getRootContainer() );
this.component.toggleHistoryClass();
$e.internal( 'panel/state-loading' );
return $e.run( 'editor/documents/switch', {
id: elementor.config.kit_id,
mode: 'autosave',
} ).finally( () => $e.internal( 'panel/state-ready' ) );
}
}
export default Open;

View File

@@ -0,0 +1,63 @@
import * as hooks from './hooks';
import * as commands from './commands/';
import Repeater from './repeater';
export default class extends $e.modules.ComponentBase {
pages = {};
__construct( args ) {
super.__construct( args );
elementor.on( 'panel:init', () => {
args.manager.addPanelPages();
args.manager.addPanelMenuItem();
} );
elementor.hooks.addFilter( 'panel/header/behaviors', args.manager.addHeaderBehavior );
elementor.addControlView( 'global-style-repeater', Repeater );
}
getNamespace() {
return 'panel/global';
}
defaultRoutes() {
return {
menu: () => {
elementor.getPanelView().setPage( 'kit_menu' );
},
};
}
defaultCommands() {
return this.importCommands( commands );
}
defaultShortcuts() {
return {
open: {
keys: 'ctrl+k',
dependency: () => {
return 'kit' !== elementor.documents.getCurrent().config.type;
},
},
back: {
keys: 'esc',
scopes: [ 'panel' ],
dependency: () => {
return elementor.documents.isCurrent( elementor.config.kit_id ) && ! jQuery( '.dialog-widget:visible' ).length;
},
},
};
}
defaultHooks() {
return this.importHooks( hooks );
}
renderTab( tab ) {
elementor.getPanelView().setPage( 'kit_settings' ).content.currentView.activateTab( tab );
}
}

View File

@@ -0,0 +1,488 @@
export default class GlobalControlSelect extends Marionette.Behavior {
getClassNames() {
return {
previewItemsContainer: 'e-global__preview-items-container',
previewItem: 'e-global__preview-item',
selectedPreviewItem: 'e-global__preview-item--selected',
manageButton: 'e-global__manage-button',
popover: 'e-global__popover',
popoverToggle: 'e-global__popover-toggle',
popoverToggleActive: 'e-global__popover-toggle--active',
controlGlobal: 'e-control-global',
globalPopoverContainer: 'e-global__popover-container',
globalPopoverTitle: 'e-global__popover-title',
globalPopoverTitleText: 'e-global__popover-title-text',
globalPopoverInfo: 'e-global__popover-info',
globalPopoverInfoTooltip: 'e-global__popover-info-tooltip',
confirmAddNewGlobal: 'e-global__confirm-add',
confirmMessageText: '.e-global__confirm-message-text',
};
}
// This method exists because the UI elements are printed after controls are already rendered.
registerUiElements() {
const popoverWidget = this.popover.getElements( 'widget' );
this.ui.manageGlobalsButton = popoverWidget.find( `.${ this.getClassNames().manageButton }` );
}
registerPreviewElements() {
const popoverWidget = this.popover.getElements( 'widget' ),
classes = this.getClassNames();
this.ui.globalPreviewItems = popoverWidget.find( `.${ classes.previewItem }` );
}
// This method exists because the UI elements are printed after controls are already rendered.
registerEvents() {
this.ui.globalPopoverToggle.on( 'click', ( event ) => this.toggleGlobalPopover( event ) );
this.ui.manageGlobalsButton.on( 'click', () => {
const { route } = this.view.getGlobalMeta(),
args = {
route: $e.routes.getHistory( 'panel' ).reverse()[ 0 ].route,
container: this.view.options.container,
};
$e.run( 'panel/global/open', args ).then( () => $e.route( route ) );
this.popover.hide();
} );
}
addPreviewItemsClickListener() {
this.ui.$globalPreviewItemsContainer.on( 'click', `.${ this.getClassNames().previewItem }`, ( event ) => this.applySavedGlobalValue( event.currentTarget.dataset.globalId ) );
}
fetchGlobalValue() {
return $e.data.get( this.view.getGlobalKey() )
.then( ( globalData ) => {
this.view.globalValue = globalData.data.value;
this.onValueTypeChange();
elementor.kitManager.renderGlobalVariables();
this.view.applySavedValue();
return globalData.data;
} ).catch( ( e ) => {
// TODO: Need to be replaced by "e instanceof NotFoundError"
if ( 404 !== e?.data?.status ) {
return Promise.reject( e );
}
this.disableGlobalValue( false );
} );
}
setCurrentActivePreviewItem() {
const selectedClass = this.getClassNames().selectedPreviewItem,
defaultGlobalsAreEnabled = elementor.config.globals.defaults_enabled[ this.view.getGlobalMeta().controlType ];
if ( this.activePreviewItem ) {
this.resetActivePreviewItem();
}
// If there is an active global on the control, get it.
let globalKey = this.view.getGlobalKey();
// If the control has no active global and no active custom value, check if there is a default global and use it.
if ( ! globalKey && ! this.view.getControlValue() && defaultGlobalsAreEnabled ) {
globalKey = this.view.model.get( 'global' )?.default;
}
if ( ! globalKey ) {
// If there is no active global or global default, reset the active preview item.
this.activePreviewItem = null;
return;
}
// Extract the Global's ID from the Global key
const { args } = $e.data.commandExtractArgs( globalKey ),
globalId = args.query.id;
// Get the active global's corresponding preview item in the Global Select Popover
const $item = this.ui.globalPreviewItems.filter( `[data-global-id="${ globalId }"]` );
if ( ! $item ) {
return;
}
this.activePreviewItem = $item;
this.activePreviewItem.addClass( selectedClass );
}
resetActivePreviewItem() {
if ( this.activePreviewItem ) {
this.activePreviewItem.removeClass( this.getClassNames().selectedPreviewItem );
}
this.activePreviewItem = null;
}
applySavedGlobalValue( globalId ) {
this.setGlobalValue( globalId );
this.fetchGlobalValue();
this.popover.hide();
}
// Update the behavior's components.
onValueTypeChange() {
this.updateCurrentGlobalName();
}
updateCurrentGlobalName( value ) {
const classes = this.getClassNames();
let globalTooltipText = '';
if ( value ) {
globalTooltipText = value;
} else {
value = this.view.getControlValue();
let globalValue = this.view.getGlobalKey();
if ( ! globalValue && ! value && elementor.config.globals.defaults_enabled[ this.view.getGlobalMeta().controlType ] ) {
globalValue = this.view.model.get( 'global' )?.default;
}
if ( globalValue ) {
// If there is a global value saved, get the global's name and display it.
$e.data.get( globalValue )
.then( ( result ) => {
let text = '';
if ( result.data.title ) {
text = result.data.title;
} else {
text = __( 'Default', 'elementor' );
}
this.updateCurrentGlobalName( text );
} );
this.ui.globalPopoverToggle.addClass( classes.popoverToggleActive );
return;
} else if ( value ) {
// If there is a value and it is not a global, set the text to custom.
globalTooltipText = __( 'Custom', 'elementor' );
} else {
// If there is no value, set the text as default.
globalTooltipText = __( 'Default', 'elementor' );
}
// If there is no value, remove the 'active' class from the Global Toggle button.
this.ui.globalPopoverToggle.removeClass( classes.popoverToggleActive );
}
// This is used in the Global Toggle Button's tooltip.
this.globalName = globalTooltipText;
}
// The Global Control elements are initialized onRender and not with initialize() because their position depends
// on elements that are not yet rendered when initialize() is called.
onRender() {
this.printGlobalToggleButton();
this.initGlobalPopover();
if ( this.view.getGlobalKey() ) {
// This setTimeout is here to overcome an issue with a requestAnimationFrame that runs in the Pickr library.
setTimeout( () => this.fetchGlobalValue(), 50 );
} else {
this.onValueTypeChange();
}
this.$el.addClass( this.getClassNames().controlGlobal );
}
toggleGlobalPopover() {
if ( this.popover.isVisible() ) {
this.popover.hide();
} else {
if ( this.ui.$globalPreviewItemsContainer ) {
// This element is not defined when the controls popover is first loaded.
this.ui.$globalPreviewItemsContainer.remove();
}
this.view.getGlobalsList()
.then(
( globalsList ) => {
// We just deleted the existing list of global preview items, so we need to rebuild it
// with the updated list of globals, register the elements and re-add the on click listeners.
this.addGlobalsListToPopover( globalsList );
this.registerPreviewElements();
this.addPreviewItemsClickListener();
this.popover.show();
this.setCurrentActivePreviewItem();
} );
}
}
buildGlobalPopover() {
const classes = this.getClassNames(),
$popover = jQuery( '<div>', { class: classes.globalPopoverContainer } ),
$popoverTitle = jQuery( '<div>', { class: classes.globalPopoverTitle } )
.html( '<div class="' + classes.globalPopoverInfo + '"><i class="eicon-info-circle"></i></div><span class="' + classes.globalPopoverTitleText + '">' + this.getOption( 'popoverTitle' ) + '</span>' ),
$manageGlobalsLink = jQuery( '<div>', { class: classes.manageButton } )
.html( '<i class="eicon-cog"></i>' );
$popoverTitle.append( $manageGlobalsLink );
$popover.append( $popoverTitle );
this.manageButtonTooltipText = this.getOption( 'manageButtonText' );
$manageGlobalsLink.tipsy( {
title: () => {
return this.manageButtonTooltipText;
},
offset: 3,
gravity: () => 's',
} );
return $popover;
}
printGlobalToggleButton() {
const $globalToggleButton = jQuery( '<div>', { class: this.getClassNames().popoverToggle + ' elementor-control-unit-1' } ),
$globalPopoverToggleIcon = jQuery( '<i>', { class: 'eicon-globe' } ),
$globalsLoadingSpinner = jQuery( '<span>', { class: 'elementor-control-spinner' } )
.html( '<i class="eicon-spinner eicon-animation-spin"></i></span>' );
$globalToggleButton.append( $globalPopoverToggleIcon );
this.$el.find( '.elementor-control-input-wrapper' ).prepend( $globalToggleButton );
this.ui.globalPopoverToggle = $globalToggleButton;
this.ui.globalPopoverToggleIcon = $globalPopoverToggleIcon;
this.ui.$globalsLoadingSpinner = $globalsLoadingSpinner;
// Add tooltip to the Global Popover toggle button, displaying the current Global Name / 'Default' / 'Custom'.
this.ui.globalPopoverToggleIcon.tipsy( {
title: () => {
return this.globalName;
},
offset: 7,
gravity: () => 's',
} );
$globalToggleButton.before( $globalsLoadingSpinner );
this.ui.$globalsLoadingSpinner.hide();
}
initGlobalPopover() {
this.popover = elementorCommon.dialogsManager.createWidget( 'simple', {
className: this.getClassNames().popover,
message: this.buildGlobalPopover(),
effects: {
show: 'show',
hide: 'hide',
},
hide: {
onOutsideClick: false,
},
position: {
my: `right top`,
at: `right bottom+5`,
of: this.ui.globalPopoverToggle,
collision: 'fit flip',
autoRefresh: true,
},
} );
// Add Popover elements to the this.ui object and register click events.
this.registerUiElementsAndEvents();
this.createGlobalInfoTooltip();
}
addGlobalsListToPopover( globalsList ) {
const $globalPreviewItemsContainer = jQuery( '<div>', { class: 'e-global__preview-items-container' } );
this.view.buildGlobalsList( globalsList, $globalPreviewItemsContainer );
this.popover.getElements( 'widget' ).find( `.${ this.getClassNames().globalPopoverTitle }` ).after( $globalPreviewItemsContainer );
// The populated list is nested under the previews container element.
this.ui.$globalPreviewItemsContainer = $globalPreviewItemsContainer;
}
registerUiElementsAndEvents() {
// Instead of ui()
this.registerUiElements();
// Instead of events()
this.registerEvents();
}
// This method is not called directly, but triggered by Marionette's .triggerMethod(),
// in the onAddGlobalButtonClick() method in the color and typography global controls.
onAddGlobalToList( $confirmMessage ) {
const classes = this.getClassNames();
this.confirmNewGlobalModal = elementorCommon.dialogsManager.createWidget( 'confirm', {
className: classes.confirmAddNewGlobal,
headerMessage: this.getOption( 'newGlobalConfirmTitle' ),
message: $confirmMessage,
strings: {
confirm: __( 'Create', 'elementor' ),
cancel: __( 'Cancel', 'elementor' ),
},
hide: {
onBackgroundClick: false,
},
onConfirm: () => this.onConfirmNewGlobal(),
onShow: () => {
// Put focus on the naming input.
const modalWidget = this.confirmNewGlobalModal.getElements( 'widget' );
this.ui.globalNameInput = modalWidget.find( 'input' ).focus();
this.ui.confirmMessageText = modalWidget.find( classes.confirmMessageText );
this.ui.globalNameInput.on( 'input', () => this.onAddGlobalConfirmInputChange() );
},
} );
this.confirmNewGlobalModal.show();
}
onAddGlobalConfirmInputChange() {
if ( ! this.view.globalsList ) {
return;
}
let messageContent;
for ( const globalValue of Object.values( this.view.globalsList ) ) {
if ( this.ui.globalNameInput.val() === globalValue.title ) {
messageContent = this.view.getNameAlreadyExistsMessage();
break;
} else {
messageContent = this.view.getConfirmTextMessage();
}
}
this.ui.confirmMessageText.html( messageContent );
}
onConfirmNewGlobal() {
const globalMeta = this.view.getGlobalMeta();
globalMeta.title = this.ui.globalNameInput.val();
this.createNewGlobal( globalMeta );
}
createNewGlobal( globalMeta ) {
this.ui.$globalsLoadingSpinner.show();
$e.run( globalMeta.commandName + '/create', {
container: this.view.container,
setting: globalMeta.key, // group control name
title: globalMeta.title,
} )
.then( ( result ) => {
this.applySavedGlobalValue( result.data.id );
this.ui.$globalsLoadingSpinner.hide();
} );
}
setGlobalValue( globalId ) {
let command = '';
const settings = {};
if ( this.view.getGlobalKey() ) {
// If a global setting is already active, switch them without disabling globals.
command = 'document/globals/settings';
} else {
// If the active setting is NOT a global, enable globals and apply the selected global.
command = 'document/globals/enable';
}
// colors / typography
settings[ this.view.model.get( 'name' ) ] = this.view.getGlobalCommand() + '?id=' + globalId;
// Trigger async render.
$e.run( command, {
container: this.view.options.container,
settings: settings,
} );
}
// The unset method is triggered from the controls via triggerMethod.
onUnsetGlobalValue() {
this.disableGlobalValue();
}
onUnlinkGlobalDefault() {
const globalMeta = this.view.getGlobalMeta();
$e.run( 'document/globals/unlink', {
container: this.view.container,
globalValue: this.view.model.get( 'global' ).default,
setting: globalMeta.key,
options: { external: true },
} )
.then( () => {
this.onValueTypeChange();
this.view.globalValue = null;
this.resetActivePreviewItem();
} );
}
createGlobalInfoTooltip() {
const classes = this.getClassNames(),
$infoIcon = this.popover.getElements( 'widget' ).find( `.${ classes.globalPopoverInfo }` );
this.globalInfoTooltip = elementorCommon.dialogsManager.createWidget( 'simple', {
className: classes.globalPopoverInfoTooltip,
message: this.getOption( 'tooltipText' ),
effects: {
show: 'show',
hide: 'hide',
},
position: {
my: `left bottom`,
at: `left top+9`,
of: this.popover.getElements( 'widget' ),
autoRefresh: true,
},
} );
$infoIcon.on( {
mouseenter: () => this.globalInfoTooltip.show(),
mouseleave: () => this.globalInfoTooltip.hide(),
} );
}
disableGlobalValue( restore = true ) {
const globalMeta = this.view.getGlobalMeta();
return $e.run( 'document/globals/disable', {
container: this.view.container,
settings: { [ globalMeta.key ]: '' },
options: { restore },
} )
.then( () => {
this.onValueTypeChange();
this.view.globalValue = null;
this.resetActivePreviewItem();
} );
}
}

View File

@@ -0,0 +1,41 @@
export class BaseGlobalsUpdate extends $e.modules.hookData.After {
getContainerType() {
return 'document';
}
getConditions() {
return $e.routes.isPartOf( 'panel/global' );
}
getRepeaterName() {
elementorModules.ForceMethodImplementation();
}
applyModel( model, id, value ) {
elementorModules.ForceMethodImplementation();
}
apply( args, result ) {
const { containers = [ args.container ] } = args,
model = Object.assign( {}, result.data );
const id = model.id,
value = model.value;
delete model.id;
delete model.value;
model._id = id;
this.applyModel( model, value );
containers.forEach( ( container ) => {
$e.run( 'document/repeater/insert', {
container,
model,
name: this.getRepeaterName(),
} );
} );
}
}
export default BaseGlobalsUpdate;

View File

@@ -0,0 +1,20 @@
import BaseGlobalsUpdate from '../base-globals-update';
export class KitGlobalsUpdateColors extends BaseGlobalsUpdate {
getCommand() {
return 'globals/colors/create';
}
getId() {
return 'globals-update-colors-/globals/colors/create';
}
getRepeaterName() {
return 'custom_colors';
}
applyModel( model, value ) {
model.color = value;
}
}
export default KitGlobalsUpdateColors;

View File

@@ -0,0 +1,20 @@
import BaseGlobalsUpdate from '../base-globals-update';
export class KitGlobalsUpdateTypography extends BaseGlobalsUpdate {
getCommand() {
return 'globals/typography/create';
}
getId() {
return 'globals-update-typography-/globals/typography/create';
}
getRepeaterName() {
return 'custom_typography';
}
applyModel( model, value ) {
Object.assign( model, value );
}
}
export default KitGlobalsUpdateTypography;

View File

@@ -0,0 +1,13 @@
export { KitGlobalsUpdateColors } from './data/globals/colors/globals-update-colors';
export { KitGlobalsUpdateTypography } from './data/globals/typography/globals-update-typography';
export { KitDeleteGlobalsCache } from './ui/document/save/save/delete-globals-cache';
export { KitAfterSave } from './ui/document/save/save/after';
export { KitUpdateBreakpointsPreview } from './ui/document/elements/settings/update-breakpoints-preview';
export { KitUpdateLightboxPreview } from './ui/document/elements/settings/update-lightbox-preview';
export { KitUpdateStretchContainer } from './ui/document/elements/settings/update-stretch-container';
export { KitSaveRouteHistory } from './ui/panel/global/open/save-route-history';
export { KitRemoveEditorActiveCSSDocumentsOpen } from './ui/editor/documents/open/remove-editor-active-css';
export { KitRemoveEditorActiveCSSPanelOpen } from './ui/panel/open/remove-editor-active-css';
export { KitBackToRouteHistory } from './ui/panel/global/close/back-to-route-history';
export { KitRemovePreviewDeletedVariables } from './ui/document/repeater/remove/remove-preview-deleted-variables';
export { KitAddMenuItems } from './ui/editor/documents/load/add-menu-items';

View File

@@ -0,0 +1,50 @@
export class KitUpdateBreakpointsPreview extends $e.modules.hookUI.After {
getCommand() {
return 'document/elements/settings';
}
getId() {
return 'kit-update-breakpoints-preview';
}
getContainerType() {
return 'document';
}
getConditions() {
return 'kit' === elementor.documents.getCurrent().config.type;
}
apply( args ) {
const { settings } = args;
// If the 'active_breakpoints' control was changed, we need to make sure that all of the breakpoints in the new
// setting are now set to active.
if ( settings.active_breakpoints ) {
// Clear the active breakpoints object before repopulating it, to make sure unselected breakpoints are removed.
elementorFrontend.config.responsive.activeBreakpoints = {};
settings.active_breakpoints.forEach( ( breakpointName ) => {
breakpointName = breakpointName.replace( 'viewport_', '' );
// Set its state to enabled.
elementorFrontend.config.responsive.breakpoints[ breakpointName ].is_enabled = true;
// Add/re-add the breakpoint to the emptied activeBreakpoints object.
elementorFrontend.config.responsive.activeBreakpoints[ breakpointName ] = elementorFrontend.config.responsive.breakpoints[ breakpointName ];
} );
// If this is the modified setting, no need to do further checks.
return;
}
// If a breakpoint value was updated, update the value in the config.
Object.entries( settings ).forEach( ( [ key, value ] ) => {
if ( key.startsWith( 'viewport_' ) ) {
const keyWithoutPrefix = key.replace( 'viewport_', '' );
// Update both the config for all breakpoints and the one for active breakpoints.
elementorFrontend.config.responsive.breakpoints[ keyWithoutPrefix ].value = value;
}
} );
elementor.updatePreviewResizeOptions( true );
}
}

View File

@@ -0,0 +1,32 @@
/**
* On change kit lightbox settings - update the lightbox preview config.
*/
export class KitUpdateLightboxPreview extends $e.modules.hookUI.After {
getCommand() {
return 'document/elements/settings';
}
getId() {
return 'kit-update-lightbox-preview';
}
getContainerType() {
return 'document';
}
getConditions() {
return 'kit' === elementor.documents.getCurrent().config.type;
}
apply( args ) {
const { settings } = args;
Object.entries( settings ).forEach( ( [ key, value ] ) => {
if ( -1 !== key.indexOf( 'lightbox' ) ) {
elementorFrontend.config.kit[ key ] = value;
}
} );
}
}
export default KitUpdateLightboxPreview;

View File

@@ -0,0 +1,33 @@
/**
* On change kit stretch container settings - update the preview stretched sections.
*/
export class KitUpdateStretchContainer extends $e.modules.hookUI.After {
getCommand() {
return 'document/elements/settings';
}
getId() {
return 'kit-update-stretch-container';
}
getContainerType() {
return 'document';
}
getConditions() {
return 'kit' === elementor.documents.getCurrent().config.type;
}
apply( args ) {
const { settings } = args;
Object.entries( settings ).forEach( ( [ key, value ] ) => {
if ( 'stretched_section_container' === key ) {
elementorFrontend.config.kit[ key ] = value;
elementor.channels.editor.trigger( 'kit:change:stretchContainer' );
}
} );
}
}
export default KitUpdateStretchContainer;

View File

@@ -0,0 +1,78 @@
/**
* On delete a design system item - the used variables on the preview frame are
* invalid and cause the elements to get the user-agent default style instead of
* inherit higher CSS rules.
*
* The hook finds and removes all deleted item variables from the preview inline styles.
*/
export class KitRemovePreviewDeletedVariables extends $e.modules.hookUI.Before {
controls = [ 'custom_colors', 'custom_typography' ];
getCommand() {
return 'document/repeater/remove';
}
getId() {
return 'kit-remove-preview-deleted-variables';
}
getContainerType() {
return 'document';
}
getConditions( args ) {
return this.controls.includes( args.name ) && 'kit' === elementor.documents.getCurrent().config.type;
}
apply( args ) {
// Store on component in order to use it in undo hook.
this.component = $e.components.get( 'panel/global' );
this.component.tempStyle = this.component.tempStyle || {};
const { containers = [ args.container ] } = args,
kitCSSId = `elementor-style-page-${ elementor.config.kit_id }`;
containers.forEach( ( container ) => {
const item = container.repeaters[ args.name ].children[ args.index ],
stylesheets = Object.values( elementor.$previewContents[ 0 ].styleSheets )
.filter( ( stylesheet ) => {
// Don't touch the kit CSS itself.
return kitCSSId !== stylesheet.ownerNode.id && stylesheet.ownerNode.innerHTML.includes( item.id );
} );
stylesheets.forEach( ( stylesheet ) => {
this.component.tempStyle[ item.id ] = this.extractVariables( stylesheet.cssRules, item.id );
} );
} );
}
extractVariables( cssRules, id ) {
const variablesRules = {};
Object.values( cssRules ).forEach( ( rule ) => {
if ( ! rule.style ) {
// TODO Handle CSSMediaRule.
return;
}
variablesRules[ rule.selectorText ] = {};
// Get the original properties for undo.
for ( let i = 0; i < rule.style.length; i++ ) {
const property = rule.style[ i ],
value = rule.style[ property ];
if ( value.includes( id ) ) {
variablesRules[ rule.selectorText ][ property ] = value;
}
}
// Delete in a separated loop, because it changes the `style.length`.
Object.keys( variablesRules[ rule.selectorText ] ).forEach( ( property ) => {
rule.style[ property ] = '';
} );
} );
return variablesRules;
}
}

View File

@@ -0,0 +1,35 @@
import After from 'elementor-api/modules/hooks/data/after';
export class KitAfterSave extends After {
getCommand() {
return 'document/save/save';
}
getConditions( args ) {
const { status, document = elementor.documents.getCurrent() } = args;
return 'publish' === status && 'kit' === document.config.type;
}
getId() {
return 'kit-footer-saver-after-save';
}
apply( args ) {
if ( 'publish' === args.status ) {
elementor.notifications.showToast( {
message: __( 'Your changes have been updated.', 'elementor' ),
buttons: [
{
name: 'back_to_editor',
text: __( 'Back to Editor', 'elementor' ),
callback() {
$e.run( 'panel/global/close' );
},
},
],
} );
}
}
}
export default KitAfterSave;

View File

@@ -0,0 +1,23 @@
import After from 'elementor-api/modules/hooks/data/after';
export class KitDeleteGlobalsCache extends After {
getCommand() {
return 'document/save/save';
}
getConditions( args ) {
const { status, document = elementor.documents.getCurrent() } = args;
return 'publish' === status && 'kit' === document.config.type;
}
getId() {
return 'document/save/save::update-globals-cache';
}
apply() {
// After kit updates - remove globals from cache and force re-request from server.
$e.components.get( 'globals' ).refreshGlobalData();
}
}
export default KitDeleteGlobalsCache;

View File

@@ -0,0 +1,23 @@
export class KitAddMenuItems extends $e.modules.hookUI.Before {
getCommand() {
return 'editor/documents/attach-preview';
}
getId() {
return 'kit-add-menu-item';
}
getConditions() {
return 'kit' === elementor.documents.getCurrent().config.type && ! Object.keys( $e.components.get( 'panel/global' ).getTabs() ).length;
}
apply() {
const document = elementor.documents.getCurrent();
Object.entries( document.config.tabs ).forEach( ( [ tabId, tabConfig ] ) => {
$e.components.get( 'panel/global' ).addTab( tabId, tabConfig );
} );
}
}
export default KitAddMenuItems;

View File

@@ -0,0 +1,22 @@
export class KitRemoveEditorActiveCSSDocumentsOpen extends $e.modules.hookUI.After {
getCommand() {
return 'editor/documents/open';
}
getId() {
return 'kit-remove-editor-active-css--editor/documents/open';
}
getConditions() {
return 'kit' === elementor.documents.getCurrent().config.type;
}
apply() {
// TODO: Remove - Temporary fix to avoid conflict with `elementor.exitPreviewMode()`.
setTimeout( () => {
elementorFrontend.elements.$body.removeClass( 'elementor-editor-active' );
} );
}
}
export default KitRemoveEditorActiveCSSDocumentsOpen;

View File

@@ -0,0 +1,7 @@
export default class BaseOpenClose extends $e.modules.hookUI.After {
initialize() {
elementor.on( 'preview:loaded', () => {
this.component = $e.components.get( 'panel/global' );
} );
}
}

View File

@@ -0,0 +1,43 @@
import BaseOpenClose from '../base/base-open-close';
export class KitBackToRouteHistory extends BaseOpenClose {
getCommand() {
return 'panel/global/close';
}
getId() {
return 'back-to-route-history-/panel/global/close';
}
getConditions() {
return this.component.routeHistory;
}
apply() {
const historyBeforeOpen = this.component.routeHistory;
delete this.component.routeHistory;
/**
* TODO: Find better solution.
* Since cache deleted after leaving globals.
* Cover issue: When back to route, it back to style, it causes the UI ask for styles separately and since,
* Cache deleted, it asks the remote ( $e.data ) for specific colors/typography endpoints and causes a delay in global select box.
* To handle the the issue, request globals manually, then back to route.
*/
if ( historyBeforeOpen.container ) {
$e.data.get( 'globals/index' ).then( () => {
// Since the container comes from history, its not connected element.
historyBeforeOpen.container = historyBeforeOpen.container.lookup();
historyBeforeOpen.container.model.trigger( 'request:edit', { scrollIntoView: true } );
$e.route( historyBeforeOpen.route, {
model: historyBeforeOpen.container.model,
view: historyBeforeOpen.container.view,
} );
} );
}
}
}
export default KitBackToRouteHistory;

View File

@@ -0,0 +1,21 @@
import BaseOpenClose from '../base/base-open-close';
export class KitSaveRouteHistory extends BaseOpenClose {
getCommand() {
return 'panel/global/open';
}
getId() {
return 'save-route-history--/panel/global/open';
}
getConditions( args = {}, result ) {
return args.route;
}
apply( args ) {
this.component.routeHistory = args;
}
}
export default KitSaveRouteHistory;

View File

@@ -0,0 +1,22 @@
export class KitRemoveEditorActiveCSSPanelOpen extends $e.modules.hookUI.After {
getCommand() {
return 'panel/open';
}
getId() {
return 'kit-remove-editor-active-css--/panel/open';
}
getConditions() {
return 'kit' === elementor.documents.getCurrent().config.type;
}
apply() {
// TODO: Remove - Temporary fix to avoid conflict with `elementor.exitPreviewMode()`.
setTimeout( () => {
elementorFrontend.elements.$body.removeClass( 'elementor-editor-active' );
} );
}
}
export default KitRemoveEditorActiveCSSPanelOpen;

View File

@@ -0,0 +1,243 @@
import Component from './component';
import PanelView from './panel';
import PanelMenuView from './panel-menu';
import PanelHeaderBehavior from './panel-header-behavior';
import GlobalControlSelect from './globals/global-select-behavior';
import ControlsCSSParser from 'elementor-assets-js/editor/utils/controls-css-parser';
export default class Manager extends elementorModules.editor.utils.Module {
loadingTriggers = {
preview: false,
globals: false,
};
/**
* @type {ControlsCSSParser}
*/
variablesCSS = null;
initialize() {
elementor.on( 'preview:loaded', () => {
this.loadingTriggers.preview = true;
this.renderGlobalsDefaultCSS();
} );
elementor.on( 'document:loaded', () => {
this.renderGlobalVariables();
} );
elementor.once( 'globals:loaded', () => {
this.loadingTriggers.globals = true;
this.renderGlobalsDefaultCSS();
} );
elementor.hooks.addFilter( 'controls/base/behaviors', this.addGlobalsBehavior );
if ( ! elementor.config.user.can_edit_kit ) {
return;
}
$e.components.register( new Component( { manager: this } ) );
}
addPanelPages() {
elementor.getPanelView().addPage( 'kit_settings', {
view: PanelView,
title: __( 'Site Settings', 'elementor' ),
} );
elementor.getPanelView().addPage( 'kit_menu', {
view: PanelMenuView,
title: __( 'Site Settings', 'elementor' ),
} );
}
addPanelMenuItem() {
const menu = elementor.modules.layouts.panel.pages.menu.Menu;
menu.addItem( {
name: 'global-settings',
icon: 'eicon-global-settings',
title: __( 'Site Settings', 'elementor' ),
type: 'page',
callback: () => {
$e.run( 'panel/global/open', {
route: $e.routes.getHistory( 'panel' ).reverse()[ 0 ].route,
} );
},
}, 'style', 'editor-preferences' );
menu.addItem( {
name: 'site-editor',
icon: 'eicon-theme-builder',
title: __( 'Theme Builder', 'elementor' ),
type: 'page',
callback: () => $e.run( 'app/open' ),
}, 'style', 'editor-preferences' );
}
addHeaderBehavior( behaviors ) {
behaviors.kit = {
behaviorClass: PanelHeaderBehavior,
};
return behaviors;
}
addGlobalsBehavior( behaviors, view ) {
// The view can be a UI control which does not have this method.
if ( ! view.isGlobalActive ) {
return;
}
const isGlobalActive = view.isGlobalActive();
if ( 'color' === view.options.model.get( 'type' ) && isGlobalActive ) {
behaviors.globals = {
behaviorClass: GlobalControlSelect,
popoverTitle: __( 'Global Colors', 'elementor' ),
manageButtonText: __( 'Manage Global Colors', 'elementor' ),
tooltipText: __( 'Global Colors help you work smarter. Save a color, and use it anywhere throughout your site. Access and edit your global colors by clicking the Manage button.', 'elementor' ),
newGlobalConfirmTitle: __( 'Create New Global Color', 'elementor' ),
};
}
if ( 'popover_toggle' === view.options.model.get( 'type' ) && 'typography' === view.options.model.get( 'groupType' ) && isGlobalActive ) {
behaviors.globals = {
behaviorClass: GlobalControlSelect,
popoverTitle: __( 'Global Fonts', 'elementor' ),
manageButtonText: __( 'Manage Global Fonts', 'elementor' ),
tooltipText: __( 'Global Fonts help you work smarter. Save a Typography, and use it anywhere throughout your site. Access and edit your Global Fonts by clicking the Manage button.', 'elementor' ),
newGlobalConfirmTitle: __( 'Create New Global Font', 'elementor' ),
};
}
return behaviors;
}
/**
* In case there is a new global color/typography convert current globals to CSS variables.
*/
renderGlobalVariables() {
if ( ! this.variablesCSS ) {
this.variablesCSS = new ControlsCSSParser( {
id: 'e-kit-variables',
settingsModel: new elementorModules.editor.elements.models.BaseSettings( {}, {} ),
} );
}
// The kit document has its own CSS.
if ( 'kit' === elementor.documents.getCurrent().config.type ) {
this.variablesCSS.removeStyleFromDocument();
return;
}
$e.data.get( 'globals/index' ).then( ( { data } ) => {
if ( data.colors ) {
Object.values( data.colors ).forEach( ( item ) => {
const controls = elementor.config.kit_config.design_system_controls.colors,
values = {
_id: item.id,
color: item.value,
};
this.variablesCSS.addStyleRules( controls, values, controls, [ '{{WRAPPER}}' ], [ 'body' ] );
} );
}
if ( data.typography ) {
Object.values( data.typography ).forEach( ( item ) => {
const controls = elementor.config.kit_config.design_system_controls.typography,
values = {
_id: item.id,
...item.value,
};
// Enqueue fonts.
if ( item.value.typography_font_family ) {
elementor.helpers.enqueueFont( item.value.typography_font_family );
}
this.variablesCSS.addStyleRules( controls, values, controls, [ '{{WRAPPER}}' ], [ 'body' ] );
} );
}
this.variablesCSS.addStyleToDocument();
} );
}
// Use the Controls CSS Parser to add the global defaults CSS to the page.
renderGlobalsDefaultCSS() {
if ( ! this.loadingTriggers.preview || ! this.loadingTriggers.globals ) {
return;
}
const cssParser = new ControlsCSSParser( {
id: 'e-global-style',
} ),
defaultColorsEnabled = elementor.config.globals.defaults_enabled.colors,
defaultTypographyEnabled = elementor.config.globals.defaults_enabled.typography;
// If both default colors and typography are disabled, there is no need to render schemes and default global css.
if ( ! defaultColorsEnabled && ! defaultTypographyEnabled ) {
return;
}
Object.values( elementor.widgetsCache ).forEach( ( widget ) => {
if ( ! widget.controls ) {
return;
}
const globalControls = [],
globalValues = {};
Object.values( widget.controls ).forEach( ( control ) => {
const isColorControl = 'color' === control.type,
isTypographyControl = 'typography' === control.groupType;
if ( ( isColorControl && ! defaultColorsEnabled ) || ( isTypographyControl && ! defaultTypographyEnabled ) ) {
return;
}
let globalControl = control;
if ( control.groupType ) {
globalControl = widget.controls[ control.groupPrefix + control.groupType ];
}
if ( control.global?.default ) {
globalValues[ control.name ] = globalControl.global.default;
}
if ( globalControl.global?.default ) {
globalControls.push( control );
}
} );
globalControls.forEach( ( control ) => {
cssParser.addControlStyleRules(
control,
widget.controls, // values
widget.controls, // controls
[ '{{WRAPPER}}' ],
[ '.elementor-widget-' + widget.widget_type ],
globalValues
);
} );
} );
cssParser.addStyleToDocument();
}
onInit() {
super.onInit();
elementorCommon.elements.$window.on( 'elementor:loaded', () => {
if ( elementor.config.initial_document.panel.support_kit ) {
this.initialize();
}
} );
}
}

View File

@@ -0,0 +1,22 @@
export default class extends elementorModules.editor.views.ControlsStack {
id() {
return 'elementor-kit-panel-content';
}
getTemplate() {
return '#tmpl-elementor-kit-panel-content';
}
childViewContainer() {
return '#elementor-kit-panel-content-controls';
}
childViewOptions() {
const container = this.getOption( 'container' );
return {
elementSettingsModel: container.settings,
container,
};
}
}

View File

@@ -0,0 +1,33 @@
import {
buttonBack,
buttonClose,
} from './panel-header-buttons';
export default class extends Marionette.Behavior {
ui() {
return {
buttonClose: '#elementor-panel-header-kit-close',
buttonBack: '#elementor-panel-header-kit-back',
};
}
events() {
return {
'click @ui.buttonClose': 'onClickClose',
'click @ui.buttonBack': 'onClickBack',
};
}
onBeforeShow() {
this.$el.prepend( elementor.compileTemplate( buttonBack, { Back: __( 'Back', 'elementor' ) } ) );
this.$el.append( elementor.compileTemplate( buttonClose, { Close: __( 'Close', 'elementor' ) } ) );
}
onClickClose() {
$e.run( 'panel/global/close' );
}
onClickBack() {
$e.run( 'panel/global/back' );
}
}

View File

@@ -0,0 +1,17 @@
const arrowIconClass = 'eicon-chevron-' + ( elementorCommon.config.isRTL ? 'right' : 'left' );
const buttonBack = `
<div id="elementor-panel-header-kit-back" class="elementor-header-button">
<i class="elementor-icon ${ arrowIconClass } tooltip-target" aria-hidden="true" data-tooltip="{{ Back }}"></i>
<span class="elementor-screen-only">{{ Back }}</span>
</div>
`;
const buttonClose = `
<div id="elementor-panel-header-kit-close" class="elementor-header-button">
<i class="elementor-icon eicon-close tooltip-target" aria-hidden="true" data-tooltip="{{ Close }}"></i>
<span class="elementor-screen-only">{{ Close }}</span>
</div>
`;
export { buttonBack, buttonClose };

View File

@@ -0,0 +1,63 @@
import MenuPageView from 'elementor-panel/pages/menu/base';
export default class PanelMenu extends MenuPageView {
initialize() {
this.collection = PanelMenu.getGroups();
}
}
PanelMenu.groups = null;
PanelMenu.createGroupItems = ( groupName ) => {
const tabs = $e.components.get( 'panel/global' ).getTabs(),
groupTabs = Object.entries( tabs ).filter( ( [ tabId, tabConfig ] ) => groupName === tabConfig.group );
return groupTabs.map( ( [ tabId, tabConfig ] ) => {
return {
name: tabId,
icon: tabConfig.icon,
title: tabConfig.title,
callback: () => $e.route( 'panel/global/' + tabId ),
};
} );
};
PanelMenu.initGroups = () => {
const settingsItems = PanelMenu.createGroupItems( 'settings' ),
additionalSettingsProps = {
name: 'settings-additional-settings',
icon: 'eicon-tools',
title: __( 'Additional Settings', 'elementor' ),
type: 'link',
link: elementor.config.admin_settings_url,
newTab: true,
};
settingsItems.push( additionalSettingsProps );
PanelMenu.groups = new Backbone.Collection( [
{
name: 'design_system',
title: __( 'Design System', 'elementor' ),
items: PanelMenu.createGroupItems( 'global' ),
},
{
name: 'theme_style',
title: __( 'Theme Style', 'elementor' ),
items: PanelMenu.createGroupItems( 'theme-style' ),
},
{
name: 'settings',
title: __( 'Settings', 'elementor' ),
items: settingsItems,
},
] );
};
PanelMenu.getGroups = () => {
if ( ! PanelMenu.groups ) {
PanelMenu.initGroups();
}
return PanelMenu.groups;
};

View File

@@ -0,0 +1,23 @@
import PanelContent from './panel-content';
module.exports = Marionette.LayoutView.extend( {
id: 'elementor-kit-panel',
template: '#tmpl-elementor-kit-panel',
regions: {
content: '#elementor-kit__panel-content__wrapper',
},
onBeforeShow() {
const container = elementor.documents.getCurrent().container,
options = {
container,
model: container.model,
controls: container.settings.controls,
name: 'kit',
};
this.showChildView( 'content', new PanelContent( options ) );
},
} );

View File

@@ -0,0 +1,114 @@
import RepeaterRow from '../../../../assets/dev/js/editor/controls/repeater-row';
export default class extends RepeaterRow {
getTemplate() {
return '#tmpl-elementor-global-style-repeater-row';
}
events() {
return {
'click @ui.removeButton': 'onRemoveButtonClick',
};
}
updateColorValue() {
this.$colorValue.text( this.model.get( 'color' ) );
}
getDisabledRemoveButtons() {
if ( ! this.ui.disabledRemoveButtons ) {
this.ui.disabledRemoveButtons = this.$el.find( '.elementor-repeater-tool-remove--disabled' );
}
return this.ui.disabledRemoveButtons;
}
getRemoveButton() {
return this.ui.removeButton.add( this.getDisabledRemoveButtons() );
}
triggers() {
return {};
}
onChildviewRender( childView ) {
const isColor = 'color' === childView.model.get( 'type' ),
isPopoverToggle = 'popover_toggle' === childView.model.get( 'type' );
let globalType = '',
globalTypeTranslated = '';
if ( isColor ) {
this.$colorValue = jQuery( '<div>', { class: 'e-global-colors__color-value elementor-control-unit-3' } );
childView.$el
.find( '.elementor-control-input-wrapper' )
.prepend( this.getRemoveButton(), this.$colorValue );
globalType = 'color';
globalTypeTranslated = __( 'Color', 'elementor' );
this.updateColorValue();
}
if ( isPopoverToggle ) {
childView.$el
.find( '.elementor-control-input-wrapper' )
.append( this.getRemoveButton() );
globalType = 'font';
globalTypeTranslated = __( 'Font', 'elementor' );
}
if ( isColor || isPopoverToggle ) {
const removeButtons = this.getDisabledRemoveButtons();
this.ui.removeButton.data( 'e-global-type', globalType );
this.ui.removeButton.tipsy( {
/* translators: %s: Font/Color. */
title: () => sprintf( __( 'Delete Global %s', 'elementor' ), globalTypeTranslated ),
gravity: () => 's',
} );
removeButtons.tipsy( {
/* translators: %s: Font/Color. */
title: () => sprintf( __( 'System %s can\'t be deleted', 'elementor' ), globalTypeTranslated ),
gravity: () => 's',
} );
}
}
onModelChange( model ) {
if ( undefined !== model.changed.color ) {
this.updateColorValue();
}
}
onRemoveButtonClick() {
const globalType = this.ui.removeButton.data( 'e-global-type' ),
globalTypeTranslatedCapitalized = 'font' === globalType ? __( 'Font', 'elementor' ) : __( 'Color', 'elementor' ),
globalTypeTranslatedLowercase = 'font' === globalType ? __( 'font', 'elementor' ) : __( 'color', 'elementor' ),
/* translators: First %s: Font/Color. Second %s: typography/color */
translatedMessage = sprintf( __( 'You\'re about to delete a Global %s. Note that if it\'s being used anywhere on your site, it will inherit a default %s.', 'elementor' ), globalTypeTranslatedCapitalized, globalTypeTranslatedLowercase );
this.confirmDeleteModal = elementorCommon.dialogsManager.createWidget( 'confirm', {
className: 'e-global__confirm-delete',
/* translators: %s: Font/Color. */
headerMessage: sprintf( __( 'Delete Global %s', 'elementor' ), globalTypeTranslatedCapitalized ),
message: '<i class="eicon-info-circle"></i> ' + translatedMessage,
strings: {
confirm: __( 'Delete', 'elementor' ),
cancel: __( 'Cancel', 'elementor' ),
},
hide: {
onBackgroundClick: false,
},
onConfirm: () => {
this.trigger( 'click:remove' );
},
} );
this.confirmDeleteModal.show();
}
}

View File

@@ -0,0 +1,26 @@
import Repeater from '../../../../assets/dev/js/editor/controls/repeater';
import RepeaterRow from './repeater-row';
export default class extends Repeater {
constructor( ...args ) {
super( ...args );
this.childView = RepeaterRow;
}
templateHelpers() {
const templateHelpers = super.templateHelpers();
templateHelpers.addButtonText = 'custom_colors' === this.model.get( 'name' ) ? __( 'Add Color', 'elementor' ) : __( 'Add Style', 'elementor' );
return templateHelpers;
}
getDefaults() {
const defaults = super.getDefaults();
defaults.title = `${ __( 'New Item', 'elementor' ) } #${ this.children.length + 1 }`;
return defaults;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Elementor\Core\Kits\Controls;
use Elementor\Control_Repeater;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Repeater extends Control_Repeater {
const CONTROL_TYPE = 'global-style-repeater';
/**
* Get control type.
*
* Retrieve the control type, in this case `global-style-repeater`.
*
* @since 3.0.0
* @access public
*
* @return string Control type.
*/
public function get_type() {
return self::CONTROL_TYPE;
}
/**
* Get repeater control default settings.
*
* Retrieve the default settings of the repeater control. Used to return the
* default settings while initializing the repeater control.
*
* @since 3.0.0
* @access protected
*
* @return array Control default settings.
*/
protected function get_default_settings() {
$settings = parent::get_default_settings();
$settings['item_actions']['duplicate'] = false;
$settings['item_actions']['sort'] = false;
return $settings;
}
/**
* Render repeater control output in the editor.
*
* Used to generate the control HTML in the editor using Underscore JS
* template. The variables for the class are available using `data` JS
* object.
*
* @since 3.0.0
* @access public
*/
public function content_template() {
?>
<div class="elementor-repeater-fields-wrapper"></div>
<# if ( itemActions.add ) { #>
<div class="elementor-button-wrapper">
<button class="elementor-button elementor-button-default elementor-repeater-add" type="button">
<i class="eicon-plus" aria-hidden="true"></i><span class="elementor-repeater__add-button__text">{{{ addButtonText }}}</span>
</button>
</div>
<# } #>
<?php
}
}

View File

@@ -0,0 +1,209 @@
<?php
namespace Elementor\Core\Kits\Documents;
use Elementor\Core\DocumentTypes\PageBase;
use Elementor\Core\Files\CSS\Post as Post_CSS;
use Elementor\Core\Kits\Documents\Tabs;
use Elementor\Core\Settings\Manager as SettingsManager;
use Elementor\Core\Settings\Page\Manager as PageManager;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Kit extends PageBase {
/**
* @var Tabs\Tab_Base[]
*/
private $tabs;
public function __construct( array $data = [] ) {
parent::__construct( $data );
$this->register_tabs();
}
public static function get_properties() {
$properties = parent::get_properties();
$properties['has_elements'] = false;
$properties['show_in_finder'] = false;
$properties['show_on_admin_bar'] = false;
$properties['edit_capability'] = 'edit_theme_options';
$properties['support_kit'] = true;
return $properties;
}
public function get_name() {
return 'kit';
}
public static function get_title() {
return __( 'Kit', 'elementor' );
}
/**
* @return Tabs\Tab_Base[]
*/
public function get_tabs() {
return $this->tabs;
}
protected function get_have_a_look_url() {
return '';
}
public static function get_editor_panel_config() {
$config = parent::get_editor_panel_config();
$config['default_route'] = 'panel/global/menu';
$config['needHelpUrl'] = 'https://go.elementor.com/global-settings';
return $config;
}
public function get_css_wrapper_selector() {
return '.elementor-kit-' . $this->get_main_id();
}
public function save( $data ) {
foreach ( $this->tabs as $tab ) {
$data = $tab->before_save( $data );
}
$saved = parent::save( $data );
if ( ! $saved ) {
return false;
}
// Should set is_saving to true, to avoid infinite loop when updating
// settings like: 'site_name" or "site_description".
$this->set_is_saving( true );
foreach ( $this->tabs as $tab ) {
$tab->on_save( $data );
}
$this->set_is_saving( false );
// When deleting a global color or typo, the css variable still exists in the frontend
// but without any value and it makes the element to be un styled even if there is a default style for the base element,
// for that reason this method removes css files of the entire site.
Plugin::instance()->files_manager->clear_cache();
return $saved;
}
/**
* Register a kit settings menu.
*
* @param $id
* @param $class
*/
public function register_tab( $id, $class ) {
$this->tabs[ $id ] = new $class( $this );
}
/**
* @inheritDoc
*/
protected function get_initial_config() {
$config = parent::get_initial_config();
foreach ( $this->tabs as $id => $tab ) {
$config['tabs'][ $id ] = [
'title' => $tab->get_title(),
'icon' => $tab->get_icon(),
'group' => $tab->get_group(),
'helpUrl' => $tab->get_help_url(),
'additionalContent' => $tab->get_additional_tab_content(),
];
}
return $config;
}
/**
* @since 3.1.0
* @access protected
*/
protected function register_controls() {
$this->register_document_controls();
foreach ( $this->tabs as $tab ) {
$tab->register_controls();
}
}
protected function get_post_statuses() {
return [
'draft' => sprintf( '%s (%s)', __( 'Disabled', 'elementor' ), __( 'Draft', 'elementor' ) ),
'publish' => __( 'Published', 'elementor' ),
];
}
public function add_repeater_row( $control_id, $item ) {
$meta_key = PageManager::META_KEY;
$document_settings = $this->get_meta( $meta_key );
if ( ! $document_settings ) {
$document_settings = [];
}
if ( ! isset( $document_settings[ $control_id ] ) ) {
$document_settings[ $control_id ] = [];
}
$document_settings[ $control_id ][] = $item;
$page_settings_manager = SettingsManager::get_settings_managers( 'page' );
$page_settings_manager->save_settings( $document_settings, $this->get_id() );
/** @var Kit $autosave **/
$autosave = $this->get_autosave();
if ( $autosave ) {
$autosave->add_repeater_row( $control_id, $item );
}
// Remove Post CSS.
$post_css = Post_CSS::create( $this->post->ID );
$post_css->delete();
// Refresh Cache.
Plugin::$instance->documents->get( $this->post->ID, false );
$post_css = Post_CSS::create( $this->post->ID );
$post_css->enqueue();
}
/**
* Register default tabs (menu pages) for site settings.
*/
private function register_tabs() {
$tabs = [
'global-colors' => Tabs\Global_Colors::class,
'global-typography' => Tabs\Global_Typography::class,
'theme-style-typography' => Tabs\Theme_Style_Typography::class,
'theme-style-buttons' => Tabs\Theme_Style_Buttons::class,
'theme-style-images' => Tabs\Theme_Style_Images::class,
'theme-style-form-fields' => Tabs\Theme_Style_Form_Fields::class,
'settings-site-identity' => Tabs\Settings_Site_Identity::class,
'settings-background' => Tabs\Settings_Background::class,
'settings-layout' => Tabs\Settings_Layout::class,
'settings-lightbox' => Tabs\Settings_Lightbox::class,
'settings-custom-css' => Tabs\Settings_Custom_CSS::class,
];
foreach ( $tabs as $id => $class ) {
$this->register_tab( $id, $class );
}
do_action( 'elementor/kit/register_tabs', $this );
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater;
use Elementor\Repeater;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Global_Colors extends Tab_Base {
const COLOR_PRIMARY = 'globals/colors?id=primary';
const COLOR_SECONDARY = 'globals/colors?id=secondary';
const COLOR_TEXT = 'globals/colors?id=text';
const COLOR_ACCENT = 'globals/colors?id=accent';
public function get_id() {
return 'global-colors';
}
public function get_title() {
return __( 'Global Colors', 'elementor' );
}
public function get_group() {
return 'global';
}
public function get_icon() {
return 'eicon-global-colors';
}
public function get_help_url() {
return 'https://go.elementor.com/global-colors';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_global_colors',
[
'label' => __( 'Global Colors', 'elementor' ),
'tab' => $this->get_id(),
]
);
$repeater = new Repeater();
$repeater->add_control(
'title',
[
'type' => Controls_Manager::TEXT,
'label_block' => true,
'required' => true,
]
);
// Color Value
$repeater->add_control(
'color',
[
'type' => Controls_Manager::COLOR,
'label_block' => true,
'dynamic' => [],
'selectors' => [
'{{WRAPPER}}' => '--e-global-color-{{_id.VALUE}}: {{VALUE}}',
],
'global' => [
'active' => false,
],
]
);
$default_colors = [
[
'_id' => 'primary',
'title' => __( 'Primary', 'elementor' ),
'color' => '#6EC1E4',
],
[
'_id' => 'secondary',
'title' => __( 'Secondary', 'elementor' ),
'color' => '#54595F',
],
[
'_id' => 'text',
'title' => __( 'Text', 'elementor' ),
'color' => '#7A7A7A',
],
[
'_id' => 'accent',
'title' => __( 'Accent', 'elementor' ),
'color' => '#61CE70',
],
];
$this->add_control(
'system_colors',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
'default' => $default_colors,
'item_actions' => [
'add' => false,
'remove' => false,
],
]
);
$this->add_control(
'custom_colors',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
]
);
$this->end_controls_section();
}
}

View File

@@ -0,0 +1,186 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Controls\Repeater as Global_Style_Repeater;
use Elementor\Group_Control_Typography;
use Elementor\Repeater;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Global_Typography extends Tab_Base {
const TYPOGRAPHY_PRIMARY = 'globals/typography?id=primary';
const TYPOGRAPHY_SECONDARY = 'globals/typography?id=secondary';
const TYPOGRAPHY_TEXT = 'globals/typography?id=text';
const TYPOGRAPHY_ACCENT = 'globals/typography?id=accent';
const TYPOGRAPHY_NAME = 'typography';
const TYPOGRAPHY_GROUP_PREFIX = self::TYPOGRAPHY_NAME . '_';
public function get_id() {
return 'global-typography';
}
public function get_title() {
return __( 'Global Fonts', 'elementor' );
}
public function get_group() {
return 'global';
}
public function get_icon() {
return 'eicon-t-letter';
}
public function get_help_url() {
return 'https://go.elementor.com/global-fonts';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_text_style',
[
'label' => __( 'Global Fonts', 'elementor' ),
'tab' => $this->get_id(),
]
);
$repeater = new Repeater();
$repeater->add_control(
'title',
[
'type' => Controls_Manager::TEXT,
'label_block' => true,
'required' => true,
]
);
$repeater->add_group_control(
Group_Control_Typography::get_type(),
[
'name' => self::TYPOGRAPHY_NAME,
'label' => '',
'global' => [
'active' => false,
],
'fields_options' => [
'font_family' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-family: "{{VALUE}}"',
],
],
'font_size' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-size: {{SIZE}}{{UNIT}}',
],
],
'font_weight' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-weight: {{VALUE}}',
],
],
'text_transform' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-transform: {{VALUE}}',
],
],
'font_style' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-font-style: {{VALUE}}',
],
],
'text_decoration' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-text-decoration: {{VALUE}}',
],
],
'line_height' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-line-height: {{SIZE}}{{UNIT}}',
],
],
'letter_spacing' => [
'selectors' => [
'{{SELECTOR}}' => '--e-global-typography-{{external._id.VALUE}}-letter-spacing: {{SIZE}}{{UNIT}}',
],
],
],
]
);
$typography_key = self::TYPOGRAPHY_GROUP_PREFIX . 'typography';
$font_family_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_family';
$font_weight_key = self::TYPOGRAPHY_GROUP_PREFIX . 'font_weight';
$default_typography = [
[
'_id' => 'primary',
'title' => __( 'Primary', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto',
$font_weight_key => '600',
],
[
'_id' => 'secondary',
'title' => __( 'Secondary', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto Slab',
$font_weight_key => '400',
],
[
'_id' => 'text',
'title' => __( 'Text', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto',
$font_weight_key => '400',
],
[
'_id' => 'accent',
'title' => __( 'Accent', 'elementor' ),
$typography_key => 'custom',
$font_family_key => 'Roboto',
$font_weight_key => '500',
],
];
$this->add_control(
'system_typography',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
'default' => $default_typography,
'item_actions' => [
'add' => false,
'remove' => false,
],
]
);
$this->add_control(
'custom_typography',
[
'type' => Global_Style_Repeater::CONTROL_TYPE,
'fields' => $repeater->get_controls(),
]
);
$this->add_control(
'default_generic_fonts',
[
'label' => __( 'Fallback Font Family', 'elementor' ),
'type' => Controls_Manager::TEXT,
'default' => 'Sans-serif',
'description' => __( 'The list of fonts used if the chosen font is not available.', 'elementor' ),
'label_block' => true,
'separator' => 'before',
]
);
$this->end_controls_section();
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Background;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Settings_Background extends Tab_Base {
public function get_id() {
return 'settings-background';
}
public function get_title() {
return __( 'Background', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-background';
}
public function get_help_url() {
return 'https://go.elementor.com/global-background';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_background',
[
'label' => $this->get_title(),
'tab' => $this->get_id(),
]
);
$this->add_group_control(
Group_Control_Background::get_type(),
[
'name' => 'body_background',
'types' => [ 'classic', 'gradient' ],
'selector' => '{{WRAPPER}}',
'fields_options' => [
'background' => [
'frontend_available' => true,
],
'color' => [
'dynamic' => [],
],
'color_b' => [
'dynamic' => [],
],
],
]
);
$this->add_control(
'mobile_browser_background',
[
'label' => __( 'Mobile Browser Background', 'elementor' ),
'type' => Controls_Manager::COLOR,
'description' => __( 'The `theme-color` meta tag will only be available in supported browsers and devices.', 'elementor' ),
'separator' => 'before',
]
);
$this->end_controls_section();
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Settings_Custom_CSS extends Tab_Base {
public function get_id() {
return 'settings-custom-css';
}
public function get_title() {
return __( 'Custom CSS', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-custom-css';
}
public function get_help_url() {
return 'https://go.elementor.com/global-custom-css';
}
protected function register_tab_controls() {
Plugin::$instance->controls_manager->add_custom_css_controls( $this->parent, $this->get_id() );
}
}

View File

@@ -0,0 +1,305 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Core\Breakpoints\Breakpoint;
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
use Elementor\Plugin;
use Elementor\Controls_Manager;
use Elementor\Core\Base\Document;
use Elementor\Modules\PageTemplates\Module as PageTemplatesModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Settings_Layout extends Tab_Base {
const ACTIVE_BREAKPOINTS_CONTROL_ID = 'active_breakpoints';
public function get_id() {
return 'settings-layout';
}
public function get_title() {
return __( 'Layout', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-layout-settings';
}
public function get_help_url() {
return 'https://go.elementor.com/global-layout';
}
protected function register_tab_controls() {
$breakpoints_default_config = Breakpoints_Manager::get_default_config();
$breakpoint_key_mobile = Breakpoints_Manager::BREAKPOINT_KEY_MOBILE;
$breakpoint_key_tablet = Breakpoints_Manager::BREAKPOINT_KEY_TABLET;
$this->start_controls_section(
'section_' . $this->get_id(),
[
'label' => __( 'Layout Settings', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_responsive_control(
'container_width',
[
'label' => __( 'Content Width', 'elementor' ) . ' (px)',
'type' => Controls_Manager::SLIDER,
'default' => [
'size' => '1140',
],
'tablet_default' => [
'size' => $breakpoints_default_config[ $breakpoint_key_tablet ]['default_value'],
],
'mobile_default' => [
'size' => $breakpoints_default_config[ $breakpoint_key_mobile ]['default_value'],
],
'range' => [
'px' => [
'min' => 300,
'max' => 1500,
'step' => 10,
],
],
'description' => __( 'Sets the default width of the content area (Default: 1140)', 'elementor' ),
'selectors' => [
'.elementor-section.elementor-section-boxed > .elementor-container' => 'max-width: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'space_between_widgets',
[
'label' => __( 'Widgets Space', 'elementor' ) . ' (px)',
'type' => Controls_Manager::SLIDER,
'default' => [
'size' => 20,
],
'range' => [
'px' => [
'min' => 0,
'max' => 40,
],
],
'placeholder' => '20',
'description' => __( 'Sets the default space between widgets (Default: 20)', 'elementor' ),
'selectors' => [
'.elementor-widget:not(:last-child)' => 'margin-bottom: {{SIZE}}{{UNIT}}',
],
]
);
$this->add_control(
'page_title_selector',
[
'label' => __( 'Page Title Selector', 'elementor' ),
'type' => Controls_Manager::TEXT,
'default' => 'h1.entry-title',
'placeholder' => 'h1.entry-title',
'description' => __( 'Elementor lets you hide the page title. This works for themes that have "h1.entry-title" selector. If your theme\'s selector is different, please enter it above.', 'elementor' ),
'label_block' => true,
'selectors' => [
// Hack to convert the value into a CSS selector.
'' => '}{{VALUE}}{display: var(--page-title-display)',
],
]
);
$this->add_control(
'stretched_section_container',
[
'label' => __( 'Stretched Section Fit To', 'elementor' ),
'type' => Controls_Manager::TEXT,
'placeholder' => 'body',
'description' => __( 'Enter parent element selector to which stretched sections will fit to (e.g. #primary / .wrapper / main etc). Leave blank to fit to page width.', 'elementor' ),
'label_block' => true,
'frontend_available' => true,
]
);
/**
* @var PageTemplatesModule $page_templates_module
*/
$page_templates_module = Plugin::$instance->modules_manager->get_modules( 'page-templates' );
$page_templates = $page_templates_module->add_page_templates( [], null, null );
// Removes the Theme option from the templates because 'default' is already handled.
unset( $page_templates[ PageTemplatesModule::TEMPLATE_THEME ] );
$page_template_control_options = [
'label' => __( 'Default Page Layout', 'elementor' ),
'options' => [
// This is here because the "Theme" string is different than the default option's string.
'default' => __( 'Theme', 'elementor' ),
] + $page_templates,
];
$page_templates_module->add_template_controls( $this->parent, 'default_page_template', $page_template_control_options );
$this->end_controls_section();
$this->start_controls_section(
'section_breakpoints',
[
'label' => __( 'Breakpoints', 'elementor' ),
'tab' => $this->get_id(),
]
);
$prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX;
$options = [];
foreach ( $breakpoints_default_config as $breakpoint_key => $breakpoint ) {
$options[ $prefix . $breakpoint_key ] = $breakpoint['label'];
}
$this->add_control(
self::ACTIVE_BREAKPOINTS_CONTROL_ID,
[
'label' => __( 'Active Breakpoints', 'elementor' ),
'type' => Controls_Manager::HIDDEN,
'description' => __( 'Mobile and Tablet options cannot be deleted.', 'elementor' ),
'options' => $options,
'default' => [
$prefix . $breakpoint_key_mobile,
$prefix . $breakpoint_key_tablet,
],
'select2options' => [
'allowClear' => false,
],
'lockedOptions' => [
$prefix . $breakpoint_key_mobile,
$prefix . $breakpoint_key_tablet,
],
'label_block' => true,
'render_type' => 'none',
'frontend_available' => true,
'multiple' => true,
]
);
$this->add_breakpoints_controls();
// Include the old mobile and tablet breakpoint controls as hidden for backwards compatibility.
$this->add_control( 'viewport_md', [ 'type' => Controls_Manager::HIDDEN ] );
$this->add_control( 'viewport_lg', [ 'type' => Controls_Manager::HIDDEN ] );
$this->end_controls_section();
}
/**
* Before Save
*
* Runs Before the Kit document is saved.
*
* For backwards compatibility, when the mobile and tablet breakpoints are updated, we also update the
* old breakpoint settings ('viewport_md', 'viewport_lg' ) with the saved values + 1px. The reason 1px
* is added is because the old breakpoints system was min-width based, and the new system introduced in
* Elementor v3.2.0 is max-width based.
*
* @since 3.2.0
*
* @param array $data
* @return array $data
*/
public function before_save( array $data ) {
// When creating a default kit, $data['settings'] is empty and should remain empty, so settings.
if ( empty( $data['settings'] ) ) {
return $data;
}
$prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX;
$mobile_breakpoint_key = $prefix . Breakpoints_Manager::BREAKPOINT_KEY_MOBILE;
$tablet_breakpoint_key = $prefix . Breakpoints_Manager::BREAKPOINT_KEY_TABLET;
$default_breakpoint_config = Breakpoints_Manager::get_default_config();
// Update the old mobile breakpoint. If the setting is empty, use the default value.
$data['settings'][ $prefix . 'md' ] = empty( $data['settings'][ $mobile_breakpoint_key ] )
? $default_breakpoint_config[ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE ]['default_value'] + 1
: $data['settings'][ $mobile_breakpoint_key ] + 1;
// Update the old tablet breakpoint. If the setting is empty, use the default value.
$data['settings'][ $prefix . 'lg' ] = empty( $data['settings'][ $tablet_breakpoint_key ] )
? $default_breakpoint_config[ Breakpoints_Manager::BREAKPOINT_KEY_TABLET ]['default_value'] + 1
: $data['settings'][ $tablet_breakpoint_key ] + 1;
return $data;
}
public function on_save( $data ) {
if ( ! isset( $data['settings'] ) || ( isset( $data['settings']['post_status'] ) && Document::STATUS_PUBLISH !== $data['settings']['post_status'] ) ) {
return;
}
$should_compile_css = false;
$breakpoints_default_config = Breakpoints_Manager::get_default_config();
foreach ( $breakpoints_default_config as $breakpoint_key => $default_config ) {
$breakpoint_setting_key = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX . $breakpoint_key;
if ( isset( $data['settings'][ $breakpoint_setting_key ] ) ) {
$should_compile_css = true;
}
}
if ( $should_compile_css ) {
Breakpoints_Manager::compile_stylesheet_templates();
}
}
private function add_breakpoints_controls() {
$default_breakpoints_config = Breakpoints_Manager::get_default_config();
$prefix = Breakpoints_Manager::BREAKPOINT_SETTING_PREFIX;
// Add a control for each of the **default** breakpoints.
foreach ( $default_breakpoints_config as $breakpoint_key => $default_breakpoint_config ) {
$this->add_control(
'breakpoint_' . $breakpoint_key . '_heading',
[
'label' => $default_breakpoint_config['label'],
'type' => Controls_Manager::HEADING,
'conditions' => [
'terms' => [
[
'name' => 'active_breakpoints',
'operator' => 'contains',
'value' => $prefix . $breakpoint_key,
],
],
],
]
);
$control_config = [
'label' => __( 'Breakpoint', 'elementor' ) . ' (px)',
'type' => Controls_Manager::NUMBER,
'placeholder' => $default_breakpoint_config['default_value'],
'frontend_available' => true,
'conditions' => [
'terms' => [
[
'name' => 'active_breakpoints',
'operator' => 'contains',
'value' => $prefix . $breakpoint_key,
],
],
],
];
// Add the breakpoint Control itself.
$this->add_control( $prefix . $breakpoint_key, $control_config );
}
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Settings_Lightbox extends Tab_Base {
public function get_id() {
return 'settings-lightbox';
}
public function get_title() {
return __( 'Lightbox', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-lightbox-expand';
}
public function get_help_url() {
return 'https://go.elementor.com/global-lightbox';
}
protected function register_tab_controls() {
$this->start_controls_section(
'section_' . $this->get_id(),
[
'label' => $this->get_title(),
'tab' => $this->get_id(),
]
);
$this->add_control(
'global_image_lightbox',
[
'label' => __( 'Image Lightbox', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'description' => __( 'Open all image links in a lightbox popup window. The lightbox will automatically work on any link that leads to an image file.', 'elementor' ),
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_counter',
[
'label' => __( 'Counter', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_fullscreen',
[
'label' => __( 'Fullscreen', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_zoom',
[
'label' => __( 'Zoom', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_enable_share',
[
'label' => __( 'Share', 'elementor' ),
'type' => Controls_Manager::SWITCHER,
'default' => 'yes',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_title_src',
[
'label' => __( 'Title', 'elementor' ),
'type' => Controls_Manager::SELECT,
'options' => [
'' => __( 'None', 'elementor' ),
'title' => __( 'Title', 'elementor' ),
'caption' => __( 'Caption', 'elementor' ),
'alt' => __( 'Alt', 'elementor' ),
'description' => __( 'Description', 'elementor' ),
],
'default' => 'title',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_description_src',
[
'label' => __( 'Description', 'elementor' ),
'type' => Controls_Manager::SELECT,
'options' => [
'' => __( 'None', 'elementor' ),
'title' => __( 'Title', 'elementor' ),
'caption' => __( 'Caption', 'elementor' ),
'alt' => __( 'Alt', 'elementor' ),
'description' => __( 'Description', 'elementor' ),
],
'default' => 'description',
'frontend_available' => true,
]
);
$this->add_control(
'lightbox_color',
[
'label' => __( 'Background Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => 'background-color: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_ui_color',
[
'label' => __( 'UI Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => '--lightbox-ui-color: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_ui_color_hover',
[
'label' => __( 'UI Hover Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => '--lightbox-ui-color-hover: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_text_color',
[
'label' => __( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'selectors' => [
'.elementor-lightbox' => '--lightbox-text-color: {{VALUE}}',
],
]
);
$this->add_control(
'lightbox_icons_size',
[
'label' => __( 'Toolbar Icons Size', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'selectors' => [
'.elementor-lightbox' => '--lightbox-header-icons-size: {{SIZE}}{{UNIT}}',
],
'separator' => 'before',
]
);
$this->add_control(
'lightbox_slider_icons_size',
[
'label' => __( 'Navigation Icons Size', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'selectors' => [
'.elementor-lightbox' => '--lightbox-navigation-icons-size: {{SIZE}}{{UNIT}}',
],
'separator' => 'before',
]
);
$this->end_controls_section();
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Core\Files\Assets\Files_Upload_Handler;
use Elementor\Core\Base\Document;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Settings_Site_Identity extends Tab_Base {
public function get_id() {
return 'settings-site-identity';
}
public function get_title() {
return __( 'Site Identity', 'elementor' );
}
public function get_group() {
return 'settings';
}
public function get_icon() {
return 'eicon-site-identity';
}
public function get_help_url() {
return 'https://go.elementor.com/global-site-identity';
}
protected function register_tab_controls() {
$custom_logo_id = get_theme_mod( 'custom_logo' );
$custom_logo_src = wp_get_attachment_image_src( $custom_logo_id, 'full' );
$site_icon_id = get_option( 'site_icon' );
$site_icon_src = wp_get_attachment_image_src( $site_icon_id, 'full' );
// If CANNOT upload svg normally, it will add a custom inline option to force svg upload if requested. (in logo and favicon)
$should_include_svg_inline_option = ! Files_Upload_Handler::is_enabled();
$this->start_controls_section(
'section_' . $this->get_id(),
[
'label' => $this->get_title(),
'tab' => $this->get_id(),
]
);
$this->add_control(
$this->get_id() . '_refresh_notice',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => __( 'Changes will be reflected in the preview only after the page reloads.', 'elementor' ),
'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
]
);
$this->add_control(
'site_name',
[
'label' => __( 'Site Name', 'elementor' ),
'default' => get_option( 'blogname' ),
'placeholder' => __( 'Choose name', 'elementor' ),
'label_block' => true,
'export' => false,
]
);
$this->add_control(
'site_description',
[
'label' => __( 'Site Description', 'elementor' ),
'default' => get_option( 'blogdescription' ),
'placeholder' => __( 'Choose description', 'elementor' ),
'label_block' => true,
'export' => false,
]
);
$this->add_control(
'site_logo',
[
'label' => __( 'Site Logo', 'elementor' ),
'type' => Controls_Manager::MEDIA,
'should_include_svg_inline_option' => $should_include_svg_inline_option,
'default' => [
'id' => $custom_logo_id,
'url' => $custom_logo_src ? $custom_logo_src[0] : '',
],
'description' => __( 'Suggested image dimensions: 350 × 100 pixels.', 'elementor' ),
'export' => false,
]
);
$this->add_control(
'site_favicon',
[
'label' => __( 'Site Favicon', 'elementor' ),
'type' => Controls_Manager::MEDIA,
'should_include_svg_inline_option' => $should_include_svg_inline_option,
'default' => [
'id' => $site_icon_id,
'url' => $site_icon_src ? $site_icon_src[0] : '',
],
'description' => __( 'Suggested favicon dimensions: 512 × 512 pixels.', 'elementor' ),
'export' => false,
]
);
$this->end_controls_section();
}
public function on_save( $data ) {
if (
! isset( $data['settings']['post_status'] ) ||
Document::STATUS_PUBLISH !== $data['settings']['post_status'] ||
// Should check for the current action to avoid infinite loop
// when updating options like: "blogname" and "blogdescription".
strpos( current_action(), 'update_option_' ) === 0
) {
return;
}
if ( isset( $data['settings']['site_name'] ) ) {
update_option( 'blogname', $data['settings']['site_name'] );
}
if ( isset( $data['settings']['site_description'] ) ) {
update_option( 'blogdescription', $data['settings']['site_description'] );
}
if ( isset( $data['settings']['site_logo'] ) ) {
set_theme_mod( 'custom_logo', $data['settings']['site_logo']['id'] );
}
if ( isset( $data['settings']['site_favicon'] ) ) {
update_option( 'site_icon', $data['settings']['site_favicon']['id'] );
}
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Core\Kits\Documents\Kit;
use Elementor\Core\Kits\Manager;
use Elementor\Plugin;
use Elementor\Settings;
use Elementor\Sub_Controls_Stack;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Tab_Base extends Sub_Controls_Stack {
/**
* @var Kit
*/
protected $parent;
abstract protected function register_tab_controls();
public function get_group() {
return 'settings';
}
public function get_icon() {
return '';
}
public function get_help_url() {
return '';
}
public function get_additional_tab_content() {
return '';
}
public function register_controls() {
$this->register_tab();
$this->register_tab_controls();
}
public function on_save( $data ) {}
/**
* Before Save
*
* Allows for modifying the kit data before it is saved to the database.
*
* @param array $data
* @return array
*/
public function before_save( array $data ) {
return $data;
}
protected function register_tab() {
Controls_Manager::add_tab( $this->get_id(), $this->get_title() );
}
protected function add_default_globals_notice() {
// Get the current section config (array - section id and tab) to use for creating a unique control ID and name
$current_section = $this->parent->get_current_section();
/** @var Manager $module */
$kits_manager = Plugin::$instance->kits_manager;
if ( $kits_manager->is_custom_colors_enabled() || $kits_manager->is_custom_typography_enabled() ) {
$this->add_control(
$current_section['section'] . '_schemes_notice',
[
'name' => $current_section['section'] . '_schemes_notice',
'type' => Controls_Manager::RAW_HTML,
'raw' => sprintf( __( 'In order for Theme Style to affect all relevant Elementor elements, please disable Default Colors and Fonts from the <a href="%s" target="_blank">Settings Page</a>.', 'elementor' ), Settings::get_url() ),
'content_classes' => 'elementor-panel-alert elementor-panel-alert-warning',
'render_type' => 'ui',
]
);
}
}
}

View File

@@ -0,0 +1,237 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Text_Shadow;
use Elementor\Group_Control_Typography;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Theme_Style_Buttons extends Tab_Base {
public function get_id() {
return 'theme-style-buttons';
}
public function get_title() {
return __( 'Buttons', 'elementor' );
}
public function get_group() {
return 'theme-style';
}
public function get_icon() {
return 'eicon-button';
}
public function get_help_url() {
return 'https://go.elementor.com/global-theme-style-buttons';
}
protected function register_tab_controls() {
$button_selectors = [
'{{WRAPPER}} button',
'{{WRAPPER}} input[type="button"]',
'{{WRAPPER}} input[type="submit"]',
'{{WRAPPER}} .elementor-button',
];
$button_hover_selectors = [
'{{WRAPPER}} button:hover',
'{{WRAPPER}} button:focus',
'{{WRAPPER}} input[type="button"]:hover',
'{{WRAPPER}} input[type="button"]:focus',
'{{WRAPPER}} input[type="submit"]:hover',
'{{WRAPPER}} input[type="submit"]:focus',
'{{WRAPPER}} .elementor-button:hover',
'{{WRAPPER}} .elementor-button:focus',
];
$button_selector = implode( ',', $button_selectors );
$button_hover_selector = implode( ',', $button_hover_selectors );
$this->start_controls_section(
'section_buttons',
[
'label' => __( 'Buttons', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_default_globals_notice();
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'label' => __( 'Typography', 'elementor' ),
'name' => 'button_typography',
'selector' => $button_selector,
]
);
$this->add_group_control(
Group_Control_Text_Shadow::get_type(),
[
'name' => 'button_text_shadow',
'selector' => $button_selector,
]
);
$this->start_controls_tabs( 'tabs_button_style' );
$this->start_controls_tab(
'tab_button_normal',
[
'label' => __( 'Normal', 'elementor' ),
]
);
$this->add_control(
'button_text_color',
[
'label' => __( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$button_selector => 'color: {{VALUE}};',
],
]
);
$this->add_control(
'button_background_color',
[
'label' => __( 'Background Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$button_selector => 'background-color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'button_box_shadow',
'selector' => $button_selector,
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'button_border',
'selector' => $button_selector,
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_control(
'button_border_radius',
[
'label' => __( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%' ],
'selectors' => [
$button_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->end_controls_tab();
$this->start_controls_tab(
'tab_button_hover',
[
'label' => __( 'Hover', 'elementor' ),
]
);
$this->add_control(
'button_hover_text_color',
[
'label' => __( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$button_hover_selector => 'color: {{VALUE}};',
],
]
);
$this->add_control(
'button_hover_background_color',
[
'label' => __( 'Background Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$button_hover_selector => 'background-color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'button_hover_box_shadow',
'selector' => $button_hover_selector,
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'button_hover_border',
'selector' => $button_hover_selector,
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_control(
'button_hover_border_radius',
[
'label' => __( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%' ],
'selectors' => [
$button_hover_selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
$this->add_responsive_control(
'button_padding',
[
'label' => __( 'Padding', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', 'em', '%' ],
'selectors' => [
$button_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
'separator' => 'before',
]
);
$this->end_controls_section();
}
}

View File

@@ -0,0 +1,231 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Typography;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Theme_Style_Form_Fields extends Tab_Base {
public function get_id() {
return 'theme-style-form-fields';
}
public function get_title() {
return __( 'Form Fields', 'elementor' );
}
public function get_group() {
return 'theme-style';
}
public function get_icon() {
return 'eicon-form-horizontal';
}
public function get_help_url() {
return 'https://go.elementor.com/global-theme-style-form-fields';
}
protected function register_tab_controls() {
$label_selectors = [
'{{WRAPPER}} label',
];
$input_selectors = [
'{{WRAPPER}} input:not([type="button"]):not([type="submit"])',
'{{WRAPPER}} textarea',
'{{WRAPPER}} .elementor-field-textual',
];
$input_focus_selectors = [
'{{WRAPPER}} input:focus:not([type="button"]):not([type="submit"])',
'{{WRAPPER}} textarea:focus',
'{{WRAPPER}} .elementor-field-textual:focus',
];
$label_selector = implode( ',', $label_selectors );
$input_selector = implode( ',', $input_selectors );
$input_focus_selector = implode( ',', $input_focus_selectors );
$this->start_controls_section(
'section_form_fields',
[
'label' => __( 'Form Fields', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_default_globals_notice();
$this->add_control(
'form_label_heading',
[
'type' => Controls_Manager::HEADING,
'label' => __( 'Label', 'elementor' ),
]
);
$this->add_control(
'form_label_color',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$label_selector => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'label' => __( 'Typography', 'elementor' ),
'name' => 'form_label_typography',
'selector' => $label_selector,
]
);
$this->add_control(
'form_field_heading',
[
'type' => Controls_Manager::HEADING,
'label' => __( 'Field', 'elementor' ),
'separator' => 'before',
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'label' => __( 'Typography', 'elementor' ),
'name' => 'form_field_typography',
'selector' => $input_selector,
]
);
$this->start_controls_tabs( 'tabs_form_field_style' );
$this->start_controls_tab(
'tab_form_field_normal',
[
'label' => __( 'Normal', 'elementor' ),
]
);
$this->add_form_field_state_tab_controls( 'form_field', $input_selector );
$this->end_controls_tab();
$this->start_controls_tab(
'tab_form_field_focus',
[
'label' => __( 'Focus', 'elementor' ),
]
);
$this->add_form_field_state_tab_controls( 'form_field_focus', $input_focus_selector );
$this->add_control(
'form_field_focus_transition_duration',
[
'label' => __( 'Transition Duration', 'elementor' ) . ' (ms)',
'type' => Controls_Manager::SLIDER,
'selectors' => [
$input_selector => 'transition: {{SIZE}}ms',
],
'range' => [
'px' => [
'min' => 0,
'max' => 3000,
],
],
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
$this->add_responsive_control(
'form_field_padding',
[
'label' => __( 'Padding', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', 'em', '%' ],
'selectors' => [
$input_selector => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
'separator' => 'before',
]
);
$this->end_controls_section();
}
private function add_form_field_state_tab_controls( $prefix, $selector ) {
$this->add_control(
$prefix . '_text_color',
[
'label' => __( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$selector => 'color: {{VALUE}};',
],
]
);
$this->add_control(
$prefix . '_background_color',
[
'label' => __( 'Background Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$selector => 'background-color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => $prefix . '_box_shadow',
'selector' => $selector,
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => $prefix . '_border',
'selector' => $selector,
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_control(
$prefix . '_border_radius',
[
'label' => __( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%' ],
'selectors' => [
$selector => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Css_Filter;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Theme_Style_Images extends Tab_Base {
public function get_id() {
return 'theme-style-images';
}
public function get_title() {
return __( 'Images', 'elementor' );
}
public function get_group() {
return 'theme-style';
}
public function get_icon() {
return 'eicon-image';
}
public function get_help_url() {
return 'https://go.elementor.com/global-theme-style-images';
}
protected function register_tab_controls() {
$image_selectors = [
'{{WRAPPER}} img',
];
$image_hover_selectors = [
'{{WRAPPER}} img:hover',
];
$image_selectors = implode( ',', $image_selectors );
$image_hover_selectors = implode( ',', $image_hover_selectors );
$this->start_controls_section(
'section_images',
[
'label' => __( 'Images', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_default_globals_notice();
$this->start_controls_tabs( 'tabs_image_style' );
$this->start_controls_tab(
'tab_image_normal',
[
'label' => __( 'Normal', 'elementor' ),
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'image_border',
'selector' => $image_selectors,
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_responsive_control(
'image_border_radius',
[
'label' => __( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%' ],
'selectors' => [
$image_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_control(
'image_opacity',
[
'label' => __( 'Opacity', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'range' => [
'px' => [
'max' => 1,
'min' => 0.10,
'step' => 0.01,
],
],
'selectors' => [
$image_selectors => 'opacity: {{SIZE}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'image_box_shadow',
'exclude' => [
'box_shadow_position',
],
'selector' => $image_selectors,
]
);
$this->add_group_control(
Group_Control_Css_Filter::get_type(),
[
'name' => 'image_css_filters',
'selector' => '{{WRAPPER}} img',
]
);
$this->end_controls_tab();
$this->start_controls_tab(
'tab_image_hover',
[
'label' => __( 'Hover', 'elementor' ),
]
);
$this->add_group_control(
Group_Control_Border::get_type(),
[
'name' => 'image_hover_border',
'selector' => '{{WRAPPER}} img:hover',
'fields_options' => [
'color' => [
'dynamic' => [],
],
],
]
);
$this->add_responsive_control(
'image_hover_border_radius',
[
'label' => __( 'Border Radius', 'elementor' ),
'type' => Controls_Manager::DIMENSIONS,
'size_units' => [ 'px', '%' ],
'selectors' => [
$image_hover_selectors => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
],
]
);
$this->add_control(
'image_hover_opacity',
[
'label' => __( 'Opacity', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'range' => [
'px' => [
'max' => 1,
'min' => 0.10,
'step' => 0.01,
],
],
'selectors' => [
$image_hover_selectors => 'opacity: {{SIZE}};',
],
]
);
$this->add_group_control(
Group_Control_Box_Shadow::get_type(),
[
'name' => 'image_hover_box_shadow',
'exclude' => [
'box_shadow_position',
],
'selector' => $image_hover_selectors,
]
);
$this->add_group_control(
Group_Control_Css_Filter::get_type(),
[
'name' => 'image_hover_css_filters',
'selector' => $image_hover_selectors,
]
);
$this->add_control(
'image_hover_transition',
[
'label' => __( 'Transition Duration', 'elementor' ) . ' (s)',
'type' => Controls_Manager::SLIDER,
'range' => [
'px' => [
'max' => 3,
'step' => 0.1,
],
],
'selectors' => [
$image_selectors => 'transition-duration: {{SIZE}}s',
],
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
$this->end_controls_section();
}
}

View File

@@ -0,0 +1,227 @@
<?php
namespace Elementor\Core\Kits\Documents\Tabs;
use Elementor\Controls_Manager;
use Elementor\Group_Control_Typography;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Theme_Style_Typography extends Tab_Base {
public function get_id() {
return 'theme-style-typography';
}
public function get_title() {
return __( 'Typography', 'elementor' );
}
public function get_group() {
return 'theme-style';
}
public function get_icon() {
return 'eicon-typography-1';
}
public function get_help_url() {
return 'https://go.elementor.com/global-theme-style-typography';
}
public function register_tab_controls() {
$this->start_controls_section(
'section_typography',
[
'label' => __( 'Typography', 'elementor' ),
'tab' => $this->get_id(),
]
);
$this->add_default_globals_notice();
$this->add_control(
'body_heading',
[
'type' => Controls_Manager::HEADING,
'label' => __( 'Body', 'elementor' ),
]
);
$this->add_control(
'body_color',
[
'label' => __( 'Text Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
'{{WRAPPER}}' => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'label' => __( 'Typography', 'elementor' ),
'name' => 'body_typography',
'selector' => '{{WRAPPER}}',
]
);
$this->add_responsive_control(
'paragraph_spacing',
[
'label' => __( 'Paragraph Spacing', 'elementor' ),
'type' => Controls_Manager::SLIDER,
'selectors' => [
'{{WRAPPER}} p' => 'margin-bottom: {{SIZE}}{{UNIT}}',
],
'range' => [
'px' => [
'min' => 0,
'max' => 100,
],
'em' => [
'min' => 0.1,
'max' => 20,
],
'vh' => [
'min' => 0,
'max' => 100,
],
],
'size_units' => [ 'px', 'em', 'vh' ],
]
);
//Link Selectors
$link_selectors = [
'{{WRAPPER}} a',
];
$link_hover_selectors = [
'{{WRAPPER}} a:hover',
];
$link_selectors = implode( ',', $link_selectors );
$link_hover_selectors = implode( ',', $link_hover_selectors );
$this->add_control(
'link_heading',
[
'type' => Controls_Manager::HEADING,
'label' => __( 'Link', 'elementor' ),
'separator' => 'before',
]
);
$this->start_controls_tabs( 'tabs_link_style' );
$this->start_controls_tab(
'tab_link_normal',
[
'label' => __( 'Normal', 'elementor' ),
]
);
$this->add_control(
'link_normal_color',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$link_selectors => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'label' => __( 'Typography', 'elementor' ),
'name' => 'link_normal_typography',
'selector' => $link_selectors,
]
);
$this->end_controls_tab();
$this->start_controls_tab(
'tab_link_hover',
[
'label' => __( 'Hover', 'elementor' ),
]
);
$this->add_control(
'link_hover_color',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$link_hover_selectors => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'label' => __( 'Typography', 'elementor' ),
'name' => 'link_hover_typography',
'selector' => $link_hover_selectors,
]
);
$this->end_controls_tab();
$this->end_controls_tabs();
// Headings.
$this->add_element_controls( __( 'H1', 'elementor' ), 'h1', '{{WRAPPER}} h1' );
$this->add_element_controls( __( 'H2', 'elementor' ), 'h2', '{{WRAPPER}} h2' );
$this->add_element_controls( __( 'H3', 'elementor' ), 'h3', '{{WRAPPER}} h3' );
$this->add_element_controls( __( 'H4', 'elementor' ), 'h4', '{{WRAPPER}} h4' );
$this->add_element_controls( __( 'H5', 'elementor' ), 'h5', '{{WRAPPER}} h5' );
$this->add_element_controls( __( 'H6', 'elementor' ), 'h6', '{{WRAPPER}} h6' );
$this->end_controls_section();
}
private function add_element_controls( $label, $prefix, $selector ) {
$this->add_control(
$prefix . '_heading',
[
'type' => Controls_Manager::HEADING,
'label' => $label,
'separator' => 'before',
]
);
$this->add_control(
$prefix . '_color',
[
'label' => __( 'Color', 'elementor' ),
'type' => Controls_Manager::COLOR,
'dynamic' => [],
'selectors' => [
$selector => 'color: {{VALUE}};',
],
]
);
$this->add_group_control(
Group_Control_Typography::get_type(),
[
'label' => __( 'Typography', 'elementor' ),
'name' => $prefix . '_typography',
'selector' => $selector,
]
);
}
}

View File

@@ -0,0 +1,358 @@
<?php
namespace Elementor\Core\Kits;
use Elementor\Core\Kits\Controls\Repeater;
use Elementor\Core\Kits\Documents\Tabs\Global_Colors;
use Elementor\Core\Kits\Documents\Tabs\Global_Typography;
use Elementor\Plugin;
use Elementor\Core\Files\CSS\Post as Post_CSS;
use Elementor\Core\Files\CSS\Post_Preview as Post_Preview;
use Elementor\Core\Documents_Manager;
use Elementor\Core\Kits\Documents\Kit;
use Elementor\TemplateLibrary\Source_Local;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Manager {
const OPTION_ACTIVE = 'elementor_active_kit';
const E_HASH_COMMAND_OPEN_SITE_SETTINGS = 'e:run:panel/global/open';
public function get_active_id() {
$id = get_option( self::OPTION_ACTIVE );
$kit_document = Plugin::$instance->documents->get( $id );
if ( ! $kit_document || ! $kit_document instanceof Kit || 'trash' === $kit_document->get_main_post()->post_status ) {
$id = $this->create_default();
update_option( self::OPTION_ACTIVE, $id );
}
return $id;
}
public function get_active_kit() {
$id = $this->get_active_id();
return Plugin::$instance->documents->get( $id );
}
public function get_active_kit_for_frontend() {
$id = $this->get_active_id();
return Plugin::$instance->documents->get_doc_for_frontend( $id );
}
/**
* Checks if specific post is a kit.
*
* @param $post_id
*
* @return bool
*/
public function is_kit( $post_id ) {
$document = Plugin::$instance->documents->get( $post_id );
return $document && $document instanceof Kit && ! $document->is_revision();
}
/**
* Init kit controls.
*
* A temp solution in order to avoid init kit group control from within another group control.
*
* After moving the `default_font` to the kit, the Typography group control cause initialize the kit controls at: https://github.com/elementor/elementor/blob/e6e1db9eddef7e3c1a5b2ba0c2338e2af2a3bfe3/includes/controls/groups/typography.php#L91
* and because the group control is a singleton, its args are changed to the last kit group control.
*/
public function init_kit_controls() {
$this->get_active_kit_for_frontend()->get_settings();
}
public function get_current_settings( $setting = null ) {
$kit = $this->get_active_kit_for_frontend();
if ( ! $kit ) {
return '';
}
return $kit->get_settings( $setting );
}
public function create( array $kit_data = [], array $kit_meta_data = [] ) {
$default_kit_data = [
'post_status' => 'publish',
];
$kit_data = array_merge( $default_kit_data, $kit_data );
$kit_data['post_type'] = Source_Local::CPT;
$kit = Plugin::$instance->documents->create( 'kit', $kit_data, $kit_meta_data );
return $kit->get_id();
}
private function create_default() {
return $this->create( [ 'post_title' => __( 'Default Kit', 'elementor' ) ] );
}
/**
* @param Documents_Manager $documents_manager
*/
public function register_document( $documents_manager ) {
$documents_manager->register_document_type( 'kit', Kit::get_class_full_name() );
}
public function localize_settings( $settings ) {
$kit = $this->get_active_kit();
$kit_controls = $kit->get_controls();
$design_system_controls = [
'colors' => $kit_controls['system_colors']['fields'],
'typography' => $kit_controls['system_typography']['fields'],
];
$settings = array_replace_recursive( $settings, [
'kit_id' => $kit->get_main_id(),
'kit_config' => [
'typography_prefix' => Global_Typography::TYPOGRAPHY_GROUP_PREFIX,
'design_system_controls' => $design_system_controls,
],
'user' => [
'can_edit_kit' => $kit->is_editable_by_current_user(),
],
] );
return $settings;
}
public function preview_enqueue_styles() {
$kit = $this->get_kit_for_frontend();
if ( $kit ) {
// On preview, the global style is not enqueued.
$this->frontend_before_enqueue_styles();
Plugin::$instance->frontend->print_fonts_links();
}
}
public function frontend_before_enqueue_styles() {
$kit = $this->get_kit_for_frontend();
if ( $kit ) {
if ( $kit->is_autosave() ) {
$css_file = Post_Preview::create( $kit->get_id() );
} else {
$css_file = Post_CSS::create( $kit->get_id() );
}
$css_file->enqueue();
}
}
public function render_panel_html() {
require __DIR__ . '/views/panel.php';
}
public function get_kit_for_frontend() {
$kit = false;
$active_kit = $this->get_active_kit();
$is_kit_preview = is_preview() && isset( $_GET['preview_id'] ) && $active_kit->get_main_id() === (int) $_GET['preview_id'];
if ( $is_kit_preview ) {
$kit = Plugin::$instance->documents->get_doc_or_auto_save( $active_kit->get_main_id(), get_current_user_id() );
} elseif ( 'publish' === $active_kit->get_main_post()->post_status ) {
$kit = $active_kit;
}
return $kit;
}
public function update_kit_settings_based_on_option( $key, $value ) {
/** @var Kit $active_kit */
$active_kit = $this->get_active_kit();
if ( $active_kit->is_saving() ) {
return;
}
$active_kit->update_settings( [ $key => $value ] );
}
/**
* Map Scheme To Global
*
* Convert a given scheme value to its corresponding default global value
*
* @param string $type 'color'/'typography'
* @param $value
* @return mixed
*/
private function map_scheme_to_global( $type, $value ) {
$schemes_to_globals_map = [
'color' => [
'1' => Global_Colors::COLOR_PRIMARY,
'2' => Global_Colors::COLOR_SECONDARY,
'3' => Global_Colors::COLOR_TEXT,
'4' => Global_Colors::COLOR_ACCENT,
],
'typography' => [
'1' => Global_Typography::TYPOGRAPHY_PRIMARY,
'2' => Global_Typography::TYPOGRAPHY_SECONDARY,
'3' => Global_Typography::TYPOGRAPHY_TEXT,
'4' => Global_Typography::TYPOGRAPHY_ACCENT,
],
];
return $schemes_to_globals_map[ $type ][ $value ];
}
/**
* Convert Scheme to Default Global
*
* If a control has a scheme property, convert it to a default Global.
*
* @param $scheme - Control scheme property
* @return array - Control/group control args
* @since 3.0.0
* @access public
*/
public function convert_scheme_to_global( $scheme ) {
if ( isset( $scheme['type'] ) && isset( $scheme['value'] ) ) {
//_deprecated_argument( $args['scheme'], '3.0.0', 'Schemes are now deprecated - use $args[\'global\'] instead.' );
return $this->map_scheme_to_global( $scheme['type'], $scheme['value'] );
}
// Typography control 'scheme' properties usually only include the string with the typography value ('1'-'4').
return $this->map_scheme_to_global( 'typography', $scheme );
}
public function register_controls() {
$controls_manager = Plugin::$instance->controls_manager;
$controls_manager->register_control( Repeater::CONTROL_TYPE, new Repeater() );
}
public function is_custom_colors_enabled() {
return ! get_option( 'elementor_disable_color_schemes' );
}
public function is_custom_typography_enabled() {
return ! get_option( 'elementor_disable_typography_schemes' );
}
/**
* Add kit wrapper body class.
*
* It should be added even for non Elementor pages,
* in order to support embedded templates.
*/
private function add_body_class() {
$kit = $this->get_kit_for_frontend();
if ( $kit ) {
Plugin::$instance->frontend->add_body_class( 'elementor-kit-' . $kit->get_main_id() );
}
}
/**
* Send a confirm message before move a kit to trash, or if delete permanently not for trash.
*
* @param $post_id
* @param false $is_permanently_delete
*/
private function before_delete_kit( $post_id, $is_permanently_delete = false ) {
$document = Plugin::$instance->documents->get( $post_id );
if (
! $document ||
! $this->is_kit( $post_id ) ||
isset( $_GET['force_delete_kit'] ) || // phpcs:ignore -- nonce validation is not require here.
( $is_permanently_delete && $document->is_trash() )
) {
return;
}
ob_start();
require __DIR__ . '/views/trash-kit-confirmation.php';
$confirmation_content = ob_get_clean();
wp_die(
new \WP_Error( 'cant_delete_kit', $confirmation_content )
);
}
/**
* Add 'Edit with elementor -> Site Settings' in admin bar.
*
* @param [] $admin_bar_config
*
* @return array $admin_bar_config
*/
private function add_menu_in_admin_bar( $admin_bar_config ) {
$document = Plugin::$instance->documents->get( get_the_ID() );
if ( ! $document || ! $document->is_built_with_elementor() ) {
$recent_edited_post = Utils::get_recently_edited_posts_query( [
'posts_per_page' => 1,
] );
if ( $recent_edited_post->post_count ) {
$posts = $recent_edited_post->get_posts();
$document = Plugin::$instance->documents->get( reset( $posts )->ID );
}
}
if ( $document ) {
$admin_bar_config['elementor_edit_page']['children'][] = [
'id' => 'elementor_site_settings',
'title' => __( 'Site Settings', 'elementor' ),
'sub_title' => __( 'Site', 'elementor' ),
'href' => $document->get_edit_url() . '#' . self::E_HASH_COMMAND_OPEN_SITE_SETTINGS,
'class' => 'elementor-site-settings',
'parent_class' => 'elementor-second-section',
];
}
return $admin_bar_config;
}
public function __construct() {
add_action( 'elementor/documents/register', [ $this, 'register_document' ] );
add_filter( 'elementor/editor/localize_settings', [ $this, 'localize_settings' ] );
add_filter( 'elementor/editor/footer', [ $this, 'render_panel_html' ] );
add_action( 'elementor/frontend/after_enqueue_styles', [ $this, 'frontend_before_enqueue_styles' ], 0 );
add_action( 'elementor/preview/enqueue_styles', [ $this, 'preview_enqueue_styles' ], 0 );
add_action( 'elementor/controls/controls_registered', [ $this, 'register_controls' ] );
add_action( 'wp_trash_post', function ( $post_id ) {
$this->before_delete_kit( $post_id );
} );
add_action( 'before_delete_post', function ( $post_id ) {
$this->before_delete_kit( $post_id, true );
} );
add_action( 'update_option_blogname', function ( $old_value, $value ) {
$this->update_kit_settings_based_on_option( 'site_name', $value );
}, 10, 2 );
add_action( 'update_option_blogdescription', function ( $old_value, $value ) {
$this->update_kit_settings_based_on_option( 'site_description', $value );
}, 10, 2 );
add_action( 'wp_head', function() {
$this->add_body_class();
} );
add_filter( 'elementor/frontend/admin_bar/settings', function ( $admin_bar_config ) {
return $this->add_menu_in_admin_bar( $admin_bar_config );
}, 9 /* Before site-editor (theme-builder) */ );
}
}

View File

@@ -0,0 +1,42 @@
<script type="text/template" id="tmpl-elementor-kit-panel">
<main id="elementor-kit__panel-content__wrapper" class="elementor-panel-content-wrapper"></main>
</script>
<script type="text/template" id="tmpl-elementor-kit-panel-content">
<div id="elementor-kit-panel-content-controls"></div>
<#
const tabConfig = $e.components.get( 'panel/global' ).getActiveTabConfig();
if ( tabConfig.helpUrl ) { #>
<div id="elementor-panel__editor__help">
<a id="elementor-panel__editor__help__link" href="{{ tabConfig.helpUrl }}" target="_blank">
<?php echo __( 'Need Help', 'elementor' ); ?>
<i class="eicon-help-o"></i>
</a>
</div>
<#
}
if ( tabConfig.additionalContent ) {
#> {{{ tabConfig.additionalContent }}} <#
}
#>
</script>
<script type="text/template" id="tmpl-elementor-global-style-repeater-row">
<# let removeClass = 'remove',
removeIcon = 'eicon-trash-o';
if ( ! itemActions.remove ) {
removeClass += '--disabled';
removeIcon = 'eicon-disable-trash-o'
}
#>
<div class="elementor-repeater-row-tool elementor-repeater-tool-{{{ removeClass }}}">
<i class="{{{ removeIcon }}}" aria-hidden="true"></i>
<# if ( itemActions.remove ) { #>
<span class="elementor-screen-only"><?php echo __( 'Remove', 'elementor' ); ?></span>
<# } #>
</div>
<div class="elementor-repeater-row-controls"></div>
</script>

View File

@@ -0,0 +1,61 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* @var int $post_id
* @var boolean $is_permanently_delete
*/
$config_url = add_query_arg(
[ 'force_delete_kit' => '1' ],
get_delete_post_link( $post_id, '', $is_permanently_delete )
);
?>
<h4>
<?php echo __( 'Are you sure you want to delete your Site Settings?', 'elementor' ); ?>
</h4>
<p>
<?php echo __( 'By removing this template you will delete your entire Site Settings. If this template is deleted, all associated settings: Global Colors & Fonts, Theme Style, Layout, Background, and Lightbox settings will be removed from your existing site. This action can not be undone.', 'elementor' ); ?>
</p>
<br/>
<a class="btn btn-danger" href="<?php echo $config_url; ?>">
<?php echo __( 'Delete', 'elementor' ); ?>
</a>
<a class="btn btn-primary" href="javascript:history.back()">
<?php echo __( 'Keep my settings', 'elementor' ); ?>
</a>
<style>
/* In WordPress "die" screen there is very basic style, so the current css is required for basic button styles. */
.btn {
text-decoration: none;
padding: 9px 20px;
font-weight: 500;
border-radius: 3px;
}
.btn-danger {
display: inline-block;
color: #a00;
}
.btn-danger:hover, .btn-danger:focus, .btn-danger:active {
color: #dc3232;
}
.btn-primary {
color: #fff;
background-color: #007cba;
margin: 0 10px;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary:active {
background-color: #0071a1;
color: #fff;
}
</style>