File: /var/www/html/triad-infosec/wp-content/plugins/fusion-builder/front-end/views/view-history.js
/* global FusionPageBuilderApp, FusionApp, fusionBuilderText, FusionEvents */
var FusionPageBuilder = FusionPageBuilder || {};
( function() {
// Builder Builder History
FusionPageBuilder.BuilderHistory = window.wp.Backbone.View.extend( {
template: FusionPageBuilder.template( jQuery( '#fusion-builder-front-end-history' ).html() ),
className: 'fusion-builder-history-list submenu-trigger-target',
tagName: 'ul',
/**
* Init.
*
* @since 2.0.0
* @param {Object} data - The data.
* @return {void}
*/
initialize: function() {
var data = FusionApp.data;
this.fusionCommands = new Array( '[]' );
this.fusionCommandsStates = new Array( '[]' ); // History states
this.maxSteps = 25; // Maximum steps allowed/saved
this.currStep = 1; // Current Index of step
this.allElements = data.postDetails.post_content;
this.fusionHistoryState = '';
this.tracking = 'on';
this.trackingPaused = 'off';
this.unsavedStep = 1; // Unsaved steps.
// Set initial history step
this.fusionCommands[ this.currStep ] = { allElements: data.postDetails.post_content };
this.fusionCommandsStates[ this.currStep ] = fusionBuilderText.empty;
this.listenTo( FusionEvents, 'fusion-history-pause-tracking', this.pauseTracking );
this.listenTo( FusionEvents, 'fusion-history-resume-tracking', this.resumeTracking );
this.listenTo( FusionEvents, 'fusion-history-save-step', this.saveHistoryStep );
this.listenTo( FusionEvents, 'fusion-history-turn-on-tracking', this.turnOnTracking );
this.listenTo( FusionEvents, 'fusion-history-turn-off-tracking', this.turnOffTracking );
this.listenTo( FusionEvents, 'fusion-history-go-to-step', this.historyStep );
this.listenTo( FusionEvents, 'fusion-history-clear', this.clearEditor );
this.listenTo( FusionEvents, 'fusion-history-capture-editor', this.captureEditor );
this.listenTo( FusionEvents, 'fusion-history-undo', this.doUndo );
this.listenTo( FusionEvents, 'fusion-history-redo', this.doRedo );
this.listenTo( FusionEvents, 'fusion-builder-reset', this.resetStates );
this.listenTo( FusionEvents, 'fusion-element-removed', this.resetStates );
},
resetStates: function( cid ) {
var self = this;
if ( 'object' === typeof this.fusionCommands ) {
_.each( this.fusionCommands, function( state, index ) {
if ( 'undefined' === typeof cid || ! cid || ( 'param' === state.type && 'undefined' !== typeof state.cid && cid === state.cid ) ) {
self.fusionCommands[ index ] = { allElements: state.allElements };
}
} );
}
},
/**
* Renders the view.
*
* @since 2.0.0
* @return {Object} this
*/
render: function() {
var self = this;
this.$el.html( this.template( { steps: this.fusionCommandsStates, currentStep: this.currStep } ) );
this.$el.attr( 'aria-expanded', false );
this.$el.find( 'li' ).on( 'click', function( event ) {
if ( event ) {
event.preventDefault();
}
self.historyStep( event );
} );
this.updateUI();
return this;
},
/**
* Saves a step in the history.
*
* @since 2.0.0
* @param {string} text - The text to be displayed in the history log.
* @return {void}
*/
saveHistoryStep: function( text, state ) {
this.fusionHistoryState = text;
this.turnOnTracking();
this.captureEditor( state );
this.turnOffTracking();
},
/**
* Captures the editor (used in front-end.js)
*
* @since 2.0.0
* @return {void}
*/
captureEditor: function( state ) {
if ( 'object' !== typeof state ) {
state = {};
}
if ( 'undefined' === typeof FusionPageBuilderApp ) {
return;
}
FusionPageBuilderApp.builderToShortcodes();
if ( this.isTrackingOn() && ! this.isTrackingPaused() ) {
// If reached limit
if ( this.currStep == this.maxSteps ) {
// Remove first index
this.fusionCommands.shift();
this.fusionCommandsStates.shift();
} else {
// Else increment index
this.currStep += 1;
this.unsavedStep += 1;
}
// If we are not at the end of the states, we need to wipe those ahead.
if ( this.currStep !== this.fusionCommands.length ) {
this.fusionCommandsStates.length = this.currStep;
this.fusionCommands.length = this.currStep;
}
// Get content
this.allElements = FusionApp.data.postDetails.post_content;
// Add all elements as fallback method.
state.allElements = this.allElements;
// Add editor data to Array
this.fusionCommands[ this.currStep ] = state;
// Add history state
this.fusionCommandsStates[ this.currStep ] = this.fusionHistoryState;
FusionApp.contentChange( 'page', 'builder-content' );
// Update buttons
this.fusionHistoryState = '';
this.render();
}
},
/**
* Turn history tracking ON.
*
* @since 2.0.0
* @return {void}
*/
turnOnTracking: function() {
this.tracking = 'on';
},
/**
* Turn history tracking OFF.
*
* @since 2.0.0
* @return {void}
*/
turnOffTracking: function() {
this.tracking = 'off';
},
/**
* Turn history tracking ON.
*
* @since 2.0.0
* @return {void}
*/
pauseTracking: function() {
this.trackingPaused = 'on';
},
/**
* Turn history tracking OFF.
*
* @since 2.0.0
* @return {void}
*/
resumeTracking: function() {
this.trackingPaused = 'off';
},
canApplyStep: function( historyStep ) {
if ( 'object' !== typeof historyStep || 'undefined' === typeof historyStep.type ) {
return false;
}
if ( 'param' === historyStep.type || 'price-param' === historyStep.type || 'pricefooter-param' === historyStep.type || 'pricefeatures-param' === historyStep.type ) {
return true;
}
return false;
},
canApplySteps: function( stepIndex ) {
var self = this,
redo = stepIndex < this.currStep ? false : true,
steps = [],
canApply = true;
if ( ! redo ) {
steps = this.fusionCommands.slice( stepIndex + 1, this.currStep + 1 );
} else {
steps = this.fusionCommands.slice( this.currStep + 1, stepIndex + 1 );
}
_.each( steps, function( step ) {
if ( ! self.canApplyStep( step ) ) {
canApply = false;
}
} );
return canApply;
},
applySteps: function( stepIndex ) {
var self = this,
redo = stepIndex < this.currStep ? false : true,
steps = [];
if ( ! redo ) {
steps = this.fusionCommands.slice( stepIndex + 1, this.currStep + 1 ).reverse();
} else {
steps = this.fusionCommands.slice( this.currStep + 1, stepIndex + 1 );
}
_.each( steps, function( step ) {
self.applyStep( step, redo );
} );
},
applyStep: function( historyStep, redo ) {
var elementView,
params, // eslint-disable-line no-unused-vars
columnView;
redo = 'undefined' === typeof redo ? false : redo;
switch ( historyStep.type ) {
case 'param':
elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
if ( elementView ) {
params = elementView.model.get( 'params' ); // eslint-disable-line no-unused-vars
// If undo, set new value to step so redo can use it.
if ( ! redo ) {
elementView.historyUpdateParam( historyStep.param, historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.oldValue );
} else {
elementView.historyUpdateParam( historyStep.param, historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.newValue );
}
}
break;
case 'price-param':
elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
if ( elementView ) {
// If undo, set new value to step so redo can use it.
if ( ! redo ) {
elementView.updatePricingTablePrice( historyStep.param, historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.oldValue );
} else {
elementView.updatePricingTablePrice( historyStep.param, historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.newValue );
}
}
break;
case 'pricefooter-param':
elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
if ( elementView ) {
// If undo, set new value to step so redo can use it.
if ( ! redo ) {
elementView.updatePricingTableFooter( historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'footer_content', historyStep.oldValue );
} else {
elementView.updatePricingTableFooter( historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'footer_content', historyStep.newValue );
}
}
break;
case 'pricefeatures-param':
elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
if ( elementView ) {
// If undo, set new value to step so redo can use it.
if ( ! redo ) {
elementView.updatePricingTableFeatures( historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.oldValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'feature_rows', historyStep.oldValue );
} else {
elementView.updatePricingTableFeatures( historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.newValue );
FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'feature_rows', historyStep.newValue );
}
}
break;
case 'add-element':
if ( redo ) {
FusionPageBuilderApp.collection.add( historyStep.model );
} else {
elementView = window.FusionPageBuilderViewManager.getView( historyStep.model.cid );
if ( elementView ) {
elementView.removeElement();
}
}
break;
case 'remove-element':
if ( redo ) {
elementView = window.FusionPageBuilderViewManager.getView( historyStep.model.cid );
if ( elementView ) {
elementView.removeElement();
}
} else {
FusionPageBuilderApp.collection.add( historyStep.model );
}
break;
case 'move-element':
elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
// Need to ignore itself.
elementView.$el.addClass( 'ignore-me' );
if ( redo ) {
columnView = window.FusionPageBuilderViewManager.getView( historyStep.newParent );
if ( elementView && columnView ) {
columnView.$el.find( '.fusion-builder-column-content' ).first().find( '> span, > div' ).not( '.ignore-me' ).eq( ( historyStep.newIndex - 1 ) ).after( elementView.$el );
FusionPageBuilderApp.onDropCollectionUpdate( elementView.model, historyStep.newIndex, historyStep.newParent );
}
} else {
columnView = window.FusionPageBuilderViewManager.getView( historyStep.oldParent );
if ( elementView && columnView ) {
columnView.$el.find( '.fusion-builder-column-content' ).first().find( '> span, > div' ).not( '.ignore-me' ).eq( ( historyStep.oldIndex - 1 ) ).after( elementView.$el );
FusionPageBuilderApp.onDropCollectionUpdate( elementView.model, historyStep.oldIndex, historyStep.oldParent );
}
}
elementView.$el.removeClass( 'ignore-me' );
break;
}
},
updateActiveStyling: function() {
FusionApp.builderToolbarView.$el.find( '.fusion-builder-history-list li' ).removeClass( 'fusion-history-active-state' );
FusionApp.builderToolbarView.$el.find( '.fusion-builder-history-list' ).find( '[data-state-id="' + this.currStep + '"]' ).addClass( 'fusion-history-active-state' );
},
fullContentReplace: function( data ) {
this.resetStates();
FusionPageBuilderApp.clearBuilderLayout();
FusionPageBuilderApp.$el.find( '.fusion_builder_container' ).remove();
// Try to make the shortcode if the content does not contain them.
if ( ! FusionApp.data.is_fusion_element || 'mega_menus' === FusionApp.data.fusion_element_type ) {
data = FusionPageBuilderApp.validateContent( data );
}
// Reset models with new elements
FusionPageBuilderApp.createBuilderLayout( data );
},
/**
* Undo last step in history.
* Saves the undone step so that we may redo later if needed.
*
* @since 2.0.0
* @param {Object} event - The event.
* @return {void}
*/
doUndo: function( event ) {
var undoData,
historyStep = {};
if ( event ) {
event.preventDefault();
}
// Turn off tracking first, so these actions are not captured
if ( this.hasUndo() ) { // If no data or end of stack and nothing to undo
// Close opened nested cols to make sure UI works after history change.
this.closeNestedCols();
this.turnOffTracking();
// Data to undo
historyStep = this.fusionCommands[ this.currStep ];
if ( this.canApplyStep( historyStep ) ) {
this.applyStep( historyStep, false );
this.currStep -= 1;
} else {
this.currStep -= 1;
historyStep = this.fusionCommands[ this.currStep ];
undoData = 'object' === typeof historyStep ? historyStep.allElements : false;
if ( undoData && '[]' !== undoData ) {
this.fullContentReplace( undoData );
}
}
this.updateActiveStyling();
}
},
/**
* Redo last step.
*
* @since 2.0.0
* @param {Object} event - The event.
* @return {void}
*/
doRedo: function( event ) {
var redoData;
if ( event ) {
event.preventDefault();
}
if ( this.hasRedo() ) { // If not at end and nothing to redo
// Close opened nested cols to make sure UI works after history change.
this.closeNestedCols();
// Turn off tracking, so these actions are not tracked
this.turnOffTracking();
// Move index
this.currStep += 1;
window.historyStep = this.fusionCommands[ this.currStep ];
redoData = 'object' === typeof window.historyStep ? window.historyStep.allElements : false;
if ( this.canApplyStep( window.historyStep ) ) {
this.applyStep( window.historyStep, true );
} else if ( redoData && '[]' !== redoData ) {
this.fullContentReplace( redoData );
}
this.updateActiveStyling();
}
},
/**
* Go to a step.
*
* @since 2.0.0
* @param {string|number} step - The step.
* @param {Object} event - The event.
* @return {void}
*/
historyStep: function( event ) {
var step,
stepData;
if ( event ) {
event.preventDefault();
}
// Close opened nested cols to make sure UI works after history change.
this.closeNestedCols();
step = jQuery( event.currentTarget ).data( 'state-id' );
// Turn off tracking, so these actions are not tracked
this.turnOffTracking();
if ( this.canApplySteps( step ) ) {
this.applySteps( step );
this.currStep = step;
} else {
this.currStep = step;
stepData = 'object' === typeof this.fusionCommands[ this.currStep ] ? this.fusionCommands[ this.currStep ].allElements : false;
if ( stepData && '[]' !== stepData ) {
this.fullContentReplace( stepData );
}
}
this.updateActiveStyling();
},
/**
* Are we currently tracking history?
*
* @since 2.0.0
* @return {boolean}
*/
isTrackingOn: function() {
return 'on' === this.tracking;
},
/**
* Is tracking paused currently?
*
* @since 2.0.0
* @return {boolean}
*/
isTrackingPaused: function() {
return 'on' === this.trackingPaused;
},
/**
* Log commands in the console as JSON.
*
* @since 2.0.0
* @return {void}
*/
logStacks: function() {
console.log( JSON.parse( this.fusionCommands ) );
},
/**
* Clear the editor.
*
* @since 2.0.0
* @return {void}
*/
clearEditor: function() {
this.fusionCommands = new Array( '[]' );
this.fusionCommandsStates = new Array( '[]' );
this.currStep = 1;
this.unsavedStep = 1;
this.fusionHistoryState = '';
this.fusionCommands[ this.currStep ] = { allElements: FusionApp.data.postDetails.post_content };
this.fusionCommandsStates[ this.currStep ] = fusionBuilderText.empty;
this.render();
},
/**
* Do we have an undo? Checks if the current step is the 1st one.
*
* @since 2.0.0
* @return {boolean}
*/
hasUndo: function() {
return 1 !== this.currStep;
},
/**
* Do we have a redo? Checks if a step greater than current one exists.
*
* @since 2.0.0
* @return {boolean}
*/
hasRedo: function() {
return this.currStep < ( this.fusionCommands.length - 1 );
},
/**
* Get the array of steps/fusionCommands.
*
* @since 2.0.0
* @return {Array}
*/
getCommands: function() {
return this.fusionCommands;
},
/**
* Update the undo/redo/history buttons.
*
* @since 2.0.0
* @return {void}
*/
updateUI: function() {
if ( 1 < this.unsavedStep ) {
FusionApp.builderToolbarView.$el.find( '#fusion-builder-toolbar-history-menu' ).attr( 'data-has-unsaved', true );
} else {
FusionApp.builderToolbarView.$el.find( '#fusion-builder-toolbar-history-menu' ).attr( 'data-has-unsaved', false );
}
this.updateActiveStyling();
},
/**
* Close nested cols.
*
* @since 2.2
* @return {void}
*/
closeNestedCols: function() {
var activeNestedCols = FusionPageBuilderApp.$el.find( '.fusion-nested-columns.editing' );
if ( activeNestedCols.length ) {
activeNestedCols.find( '.fusion-builder-cancel-row' ).trigger( 'click' );
}
}
} );
}( jQuery ) );