: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
((api,topWindowDoc,_CLICK_)=>{
api.undoManager = class {
constructor(btnUndo,btnRedo,compactBtn){
const toolbarEl=api.ToolBar.el,
isEmpty=this.constructor.get(0)===undefined;
this.compactBtn = compactBtn;
if(isEmpty && toolbarEl.contains(btnUndo)){
toolbarEl.tfClass('menu_undo')[0].tfOn(_CLICK_,e=>{
if(e.target!==this.compactBtn){
const target=e.target.closest('.undo_redo');
if(target!==null && !target.classList.contains('disabled')){
this.constructor.doChange(target.classList.contains('undo'));
for(let items=[btnUndo,btnRedo],i=items.length-1;i>-1;--i){
items[i].tfOn(_CLICK_,e=>{
this.constructor.doChange(e.target.classList.contains('undo'));
if (isEmpty && !Themify.isTouch && !themifyBuilder.disableShortcuts) {
topWindowDoc.tfOn('keydown auxclick',e=>{
this.constructor.keypres(e);
doc.tfOn('keydown auxclick',e=>{
this.constructor.keypres(e);
this.constructor.#items.push(this);
return this.index < (this.stack.length - 1);
for(let items=this.constructor.#items,i=items.length-1;i>-1;--i){
this.stack=this.state=this.btnUndo=this.btnRedo=this.compactBtn=null;
this.constructor.updateUndoBtns();
index??= this.#items.length-1;
return this.#items[index];
static setActive(undoItem){
for(let items=this.#items,len=items.length-1,i=len;i>-1;--i){
[items[i], items[len]] = [items[len], items[i]];
const _this = this.get();
if(this.has(type)===true){
console.warn('UndoManager:'+type+' is already started');
_this.state.set(type,this.getCurrentState(type,cid));
const _this = this.get();
if(this.has(type)===false){
console.warn('UndoManager:'+type+' isn`t started');
Themify.trigger('tb_undo_add',type);
const diff=this.getDiff(type,this.getState(type),this.getCurrentState(type,_this.#cid));
if(Object.keys(diff).length>0){
_this.state.delete(type);
_this.#type=_this.#cid=null;
static getCurrentState(type){
result={builder:api.Helper.cloneObject(api.Builder.get().toJSON(false))},
breakpoints=api.breakpointsReverse;
for(let i=breakpoints.length-1;i>-1;--i){
rules=ThemifyStyles.getSheet(bp).cssRules,
gsRules=ThemifyStyles.getSheet(bp,true).cssRules;
styles[bp]={st:{},gs:{}};
for(let j=rules.length-1;j>-1;--j){
styles[bp].st[rules[j].selectorText]=rules[j].style.cssText;
for(let j=gsRules.length-1;j>-1;--j){
styles[bp].gs[gsRules[j].selectorText]=gsRules[j].style.cssText;
return this.get().state.get(type);
return !!this.get().state.has(type);
const _this = this.get();
_this.state.delete(type);
return this.get().hasRedo();
return this.get().hasUndo();
const _this = this.get();
_this.btnUndo.classList.add('disabled');
_this.btnRedo.classList.add('disabled');
_this.compactBtn?.classList.add('disabled');
const _this = this.get();
const _this = this.get();
api.ModulePageBreak.countModules();
static updateUndoBtns() {
const _this = this.get();
if(_this.isDisabled!==true){
const undo = _this.hasUndo(),
_this.btnUndo.classList.toggle('disabled', !undo);
_this.btnRedo.classList.toggle('disabled', !redo);
_this.compactBtn?.classList.toggle('disabled', !(undo || redo));
const _this = this.get();
const _this = this.get();
_this.stack.splice(_this.index + 1, _this.stack.length - _this.index);
_this.index = _this.stack.length - 1;
Themify.trigger('add_undo');
api.Builder.get().isSaved=false;
static async doChange(is_undo) {
const _this = this.get();
if (_this.isWorking === false && _this.isDisabled===false) {
await this.changes(is_undo);
static getDiff(type,oldState,newState){
const oldBuilder=oldState.builder,
newBuilder=newState.builder,
for(let i=0;i<oldBuilder.length;++i){
if(newBuilder[i]?.element_id===id){
if(api.Helper.compareObject(oldB,newB)){
builderChanges.set(id,{old:oldB,new:newB});
for(let j=newBuilder.length-1;j>-1;--j){
if(newBuilder[j].element_id===id){
if(type==='delete' && api.Helper.compareObject(oldB,newBuilder[j])){
builderChanges.set(id,{old:oldB,new:newBuilder[j]});
else if(type==='move' && newBuilder.length===oldBuilder.length && !builderChanges.has('sort')){//row position changed
for(let i=0;i<oldBuilder.length;++i){
oldSort.push(oldBuilder[i].element_id);
for(let i=0;i<newBuilder.length;++i){
newSort.push(newBuilder[i].element_id);
builderChanges.set('sort',{old:oldSort,new:newSort});
if(!found){//row not found it's deleted
builderChanges.set(id,{old:oldB,index:i});
if(newBuilder.length>oldBuilder.length){//check new rows
for(let i=0;i<newBuilder.length;++i){
builderChanges.set(id,{new:newB,index:i});
let oldStyles=oldState.style,
currentStyles=newState.style,
cssText=cssText.split('; ');
for(let i=cssText.length-1;i>-1;--i){
let index = cssText[i].indexOf(':'),
prop = cssText[i].substring(0, index);
res[prop]=cssText[i].substring(index + 1).trim();
let len=res[prop].length;
if (res[prop][len - 1] === ';') {
res[prop] = res[prop].slice(0, -1);
else if(res[prop][len - 1]==='"' && res[prop][len - 2]===';'){
res[prop]=res[prop].substring(0, index)+res[prop].substring(index+1);
diffStyles=(oldStyles,newStyles)=>{
let diff={old:{},new:{}};
for(let sel in oldStyles){//check changes
if(newStyles[sel]!==undefined){
if(newStyles[sel]!==oldStyles[sel]){
let oldCss=parseCssText(oldStyles[sel]),
newCss= parseCssText(newStyles[sel]);
for(let prop in oldCss){//check props changes
if(newCss[prop]!==oldCss[prop]){
let oldV=oldCss[prop].trim(),
newV=newCss[prop]?.trim()??'';
diff.old[sel][prop]=oldV;
diff.new[sel][prop]=newV;
for(let prop in newCss){//new props
if(oldCss[prop]===undefined){
diff.new[sel][prop]=newCss[prop].trim();
diff.old[sel]=parseCssText(oldStyles[sel]);
for(let sel in newStyles){//new selectors
if(oldStyles[sel]===undefined){
diff.new[sel]=parseCssText(newStyles[sel]);
if(Object.keys(diff.old).length===0){
if(Object.keys(diff.new).length===0){
for(let bp in oldStyles){
if(currentStyles[bp]!==undefined){
let stChanges=diffStyles(oldStyles[bp].st,currentStyles[bp].st),
gsChanges=diffStyles(oldStyles[bp].gs,currentStyles[bp].gs);
if(Object.keys(stChanges).length>0){
stylesChanges[bp]={st:stChanges};
if(Object.keys(gsChanges).length>0){
stylesChanges[bp]={gs:gsChanges};
for(let bp in currentStyles){//new breakpoints
if(oldStyles[bp]===undefined){
let stChanges=diffStyles({},currentStyles[bp].st),
gsChanges=diffStyles({},currentStyles[bp].gs);
if(Object.keys(stChanges).length>0){
stylesChanges[bp]={st:stChanges};
if(Object.keys(gsChanges).length>0){
stylesChanges[bp]={gs:gsChanges};
newState=currentStyles=oldStyles=null;
if(Object.keys(stylesChanges).length>0){
data.styles=stylesChanges;
if(builderChanges.size>0){
data.html=builderChanges;
if(data.html || data.styles){
const _this = this.get();
if (_this.isWorking === false && _this.isDisabled===false && (e.button===3 || e.button===4 || true === e.ctrlKey || true === e.metaKey)){
const activeTag = doc.activeElement.tagName,
topActiveTag = topWindowDoc.activeElement.tagName,
if (activeTag !== 'INPUT' && activeTag !== 'TEXTAREA' && topActiveTag !== 'INPUT' && topActiveTag !== 'TEXTAREA' && !api.LightBox.el.contains(e.target)) {
if ('KeyY' === key || e.button===4 || ('KeyZ' === key && true === e.shiftKey)) {// Redo
else if (('KeyZ' === key || e.button===3) && this.hasUndo()) { // UNDO
static async changes(is_undo) {
api.ActionBar.clearClicked();
if (api.activeModel !== null && (!api.isVisual || (!doc.activeElement.isContentEditable && api.activeModel.el.contains(doc.activeElement)))) {
await api.LightBox.save();
return this.changes(is_undo);
const _this = this.get(),
index = is_undo===true ? 0 : 1,
stack = _this.stack[_this.index + index];
if (stack !== undefined) {
const type=is_undo===true?'old':'new';
await this.domChanges(stack.html,type,stack.type);
this.styleChanges(stack.styles,type,!stack.html);
static styleChanges(styles,mode,runJs){
for(let k in styles[bp]){
let sheet=ThemifyStyles.getSheet(bp,k==='gs'),
for(let sel in styles[bp][k][mode]){
let vals=styles[bp][k][mode][sel],
index=api.Utils.findCssRule(rules, sel);
if(index === false || rules[index]===undefined){
cssText+=prop + ':' + vals[prop] + ';';
sheet.insertRule(sel + '{' + cssText + ';}', rules.length);
let val=vals[prop].trim(),
priority = val !== '' && val.includes('!important')? 'important' : '';
val = val.replace('!important', '').trim();
rules[index].style.setProperty(prop, val,priority);
else if(index !== false && rules[index]!==undefined){
for(let sel of selectors){
let item=doc.querySelector(sel);
static async domChanges(changes,mode,type){
let builder=api.Builder.get().el,
sort=changes.get('sort')?.[mode],
componentType=model?.type,
for (let i = items.length - 1; i > -1; --i) {
mod_settings=item.mod_settings;
if(item.element_id===cid){
ThemifyConstructor.setStylingValues(api.activeBreakPoint);//save current breakpoint style tab
let settings = {...api.Helper.cloneObject(ThemifyConstructor.values),...api.Forms.serialize('tb_options_setting', true)};
if (componentType !== 'column') {
settings={...settings,...api.Forms.serialize('tb_options_animation', true),...api.Forms.serialize('tb_options_visibility', true)};
if(componentType==='module'){
item.mod_settings=settings;
if(componentType!=='column'){
rowSizes={...item.sizes};
if ((item.cols?.length>0 && loop(item.cols)) || (item.modules?.length>0 && loop(item.modules))) {
let nestedBuilder=mod_settings.content_accordion || mod_settings.tab_content_tab;
for(let j=nestedBuilder.length-1;j>-1;--j){
if(nestedBuilder[j].builder_content){
loop(nestedBuilder[j].builder_content);