mirror of https://github.com/artf/grapesjs.git
nocodeframeworkdrag-and-dropsite-buildersite-generatortemplate-builderui-builderweb-builderweb-builder-frameworkwebsite-builderno-codepage-builder
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
725 lines
16 KiB
725 lines
16 KiB
define([
|
|
'backbone',
|
|
'backboneUndo',
|
|
'keymaster',
|
|
'AssetManager',
|
|
'StorageManager',
|
|
'ModalDialog',
|
|
'CodeManager',
|
|
'CssComposer',
|
|
'Commands',
|
|
'Canvas',
|
|
'RichTextEditor',
|
|
'DomComponents',
|
|
'ClassManager',
|
|
'StyleManager',
|
|
'Panels',
|
|
'Parser',
|
|
'Utils'],
|
|
function(
|
|
Backbone,
|
|
UndoManager,
|
|
Keymaster,
|
|
AssetManager,
|
|
StorageManager,
|
|
ModalDialog,
|
|
CodeManager,
|
|
CssComposer,
|
|
Commands,
|
|
Canvas,
|
|
RichTextEditor,
|
|
DomComponents,
|
|
ClassManager,
|
|
StyleManager,
|
|
Panels,
|
|
Parser,
|
|
Utils
|
|
){
|
|
return Backbone.Model.extend({
|
|
|
|
defaults: {
|
|
clipboard: null,
|
|
selectedComponent: null,
|
|
previousModel: null,
|
|
changesCount: 0,
|
|
},
|
|
|
|
initialize: function(c) {
|
|
this.config = c;
|
|
this.pfx = this.config.storagePrefix;
|
|
this.compName = this.pfx + 'components' + this.config.id;
|
|
this.rulesName = this.pfx + 'rules' + this.config.id;
|
|
this.set('Config', c);
|
|
|
|
if(c.el && c.fromElement)
|
|
this.config.components = c.el.innerHTML;
|
|
|
|
this.initParser();
|
|
this.initStorage();
|
|
this.initClassManager();
|
|
this.initModal();
|
|
this.initAssetManager();
|
|
this.initCodeManager();
|
|
this.initCommands();
|
|
this.initPanels();
|
|
this.initRichTextEditor();
|
|
this.initCssComposer();
|
|
this.initComponents();
|
|
this.initCanvas();
|
|
this.initUndoManager();
|
|
this.initUtils();
|
|
this.initStyleManager();
|
|
|
|
this.on('change:selectedComponent', this.componentSelected, this);
|
|
},
|
|
|
|
/**
|
|
* Initialize editor model and set editor instance
|
|
* @param {Editor} editor Editor instance
|
|
* @return {this}
|
|
*/
|
|
init: function(editor){
|
|
this.set('Editor', editor);
|
|
},
|
|
|
|
/**
|
|
* Initialize Parser
|
|
* */
|
|
initParser: function() {
|
|
this.Parser = new Parser();
|
|
this.set('parser', this.Parser);
|
|
},
|
|
|
|
/**
|
|
* Initialize Utils
|
|
* @private
|
|
* */
|
|
initUtils: function() {
|
|
this.Utils = new Utils();
|
|
this.set('Utils', this.Utils);
|
|
},
|
|
|
|
/**
|
|
* Initialize Style Manager
|
|
* @private
|
|
* */
|
|
initStyleManager: function(){
|
|
var cfg = this.config.styleManager,
|
|
pfx = cfg.stylePrefix || 'sm-';
|
|
cfg.pStylePrefix = this.config.stylePrefix;
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
cfg.target = this;
|
|
this.set('StyleManager', new StyleManager(cfg));
|
|
},
|
|
|
|
/**
|
|
* Initialize Css Composer
|
|
* @private
|
|
* */
|
|
initCssComposer: function() {
|
|
var elStyle = this.config.style || '';
|
|
var cfg = _.clone(this.config.cssComposer),
|
|
df = '';
|
|
pfx = cfg.stylePrefix || 'css-';
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
|
|
if(this.StorageManager.getConfig().autoload)
|
|
df = this.loadRules();
|
|
|
|
if(elStyle)
|
|
cfg.defaults = elStyle;
|
|
|
|
if(df)
|
|
cfg.defaults = df;
|
|
|
|
cfg.sm = this;
|
|
this.cssc = new CssComposer(cfg);
|
|
this.CssComposer = this.cssc;
|
|
this.set('CssComposer', this.cssc);
|
|
if(this.stm.isAutosave())
|
|
this.listenRules(this.cssc.getRules());
|
|
},
|
|
|
|
/**
|
|
* Listen for new rules
|
|
* @param {Object} collection
|
|
*/
|
|
listenRules: function(collection) {
|
|
this.stopListening(collection, 'add remove', this.listenRule);
|
|
this.listenTo(collection, 'add remove', this.listenRule);
|
|
collection.each(function(model){
|
|
this.listenRule(model);
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* Listen for rule changes
|
|
* @param {Object} model
|
|
*/
|
|
listenRule: function(model) {
|
|
this.stopListening(model, 'change:style', this.ruleUpdated);
|
|
this.listenTo(model, 'change:style', this.ruleUpdated);
|
|
},
|
|
|
|
/**
|
|
* Triggered when rule is updated
|
|
* @param {Object} model
|
|
* @param {Mixed} val Value
|
|
* @param {Object} opt Options
|
|
* */
|
|
ruleUpdated: function(model, val, opt) {
|
|
var count = this.get('changesCount') + 1,
|
|
avSt = opt ? opt.avoidStore : 0;
|
|
this.set('changesCount', count);
|
|
|
|
if(this.stm.isAutosave() && count < this.stm.getStepsBeforeSave())
|
|
return;
|
|
|
|
if(!avSt){
|
|
this.storeRules();
|
|
this.set('changesCount', 0);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initialize Class manager
|
|
* @private
|
|
* */
|
|
initClassManager: function() {
|
|
var cfg = this.config.classManager,
|
|
pfx = cfg.stylePrefix || 'clm-';
|
|
cfg.pStylePrefix = this.config.stylePrefix;
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
cfg.target = this;
|
|
this.clm = new ClassManager(cfg);
|
|
this.ClassManager = this.clm;
|
|
this.set('ClassManager', this.clm);
|
|
},
|
|
|
|
/**
|
|
* Initialize components
|
|
* @private
|
|
* */
|
|
initComponents: function() {
|
|
this.config.domComponents.components = this.config.components;
|
|
var cfg = this.config.domComponents,
|
|
comp = '',
|
|
cmpStylePfx = cfg.stylePrefix || 'comp-';
|
|
|
|
if(this.StorageManager.getConfig().autoload)
|
|
comp = this.loadComponents();
|
|
|
|
|
|
cfg.stylePrefix = this.config.stylePrefix + cmpStylePfx;
|
|
|
|
if(comp)
|
|
cfg.components = comp;
|
|
|
|
if(this.rte)
|
|
cfg.rte = this.rte;
|
|
|
|
if(this.modal)
|
|
cfg.modal = this.modal;
|
|
|
|
if(this.am)
|
|
cfg.am = this.am;
|
|
|
|
cfg.em = this;
|
|
|
|
this.cmp = new DomComponents(cfg);
|
|
this.Components = this.cmp;
|
|
|
|
if(this.stm.isAutosave()){
|
|
var md = this.cmp.getComponent();
|
|
this.updateComponents( md, null, { avoidStore : 1 });
|
|
|
|
// Call UndoManager here so it's possible to call it also for children inside
|
|
this.initUndoManager();
|
|
this.initChildrenComp(md);
|
|
}
|
|
|
|
this.set('Components', this.cmp);
|
|
},
|
|
|
|
/**
|
|
* Initialize canvas
|
|
* @private
|
|
* */
|
|
initCanvas: function() {
|
|
var cfg = this.config.canvas,
|
|
pfx = cfg.stylePrefix || 'cv-';
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
cfg.canvasId = this.config.idCanvas;
|
|
this.cv = new Canvas(this.config.canvas);
|
|
|
|
if(this.cmp)
|
|
this.cv.setWrapper(this.cmp);
|
|
this.Canvas = this.cv;
|
|
this.set('Canvas', this.cv);
|
|
},
|
|
|
|
/**
|
|
* Initialize rich text editor
|
|
* @private
|
|
* */
|
|
initRichTextEditor: function() {
|
|
var cfg = this.config.rte,
|
|
rteStylePfx = cfg.stylePrefix || 'rte-';
|
|
cfg.stylePrefix = this.config.stylePrefix + rteStylePfx;
|
|
this.rte = new RichTextEditor(cfg);
|
|
this.set('RichTextEditor', this.rte);
|
|
},
|
|
|
|
/**
|
|
* Initialize storage
|
|
* @private
|
|
* */
|
|
initStorage: function() {
|
|
this.stm = new StorageManager(this.config.storage || this.config.storageManager);
|
|
this.StorageManager = this.stm;
|
|
this.stm.loadDefaultProviders().setCurrent(this.stm.getConfig().type);
|
|
this.set('StorageManager', this.stm);
|
|
},
|
|
|
|
/**
|
|
* Initialize asset manager
|
|
* @private
|
|
* */
|
|
initAssetManager: function() {
|
|
var cfg = this.config.assetManager,
|
|
pfx = cfg.stylePrefix || 'am-';
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
|
|
if(this.stm)
|
|
cfg.stm = this.stm;
|
|
|
|
this.am = new AssetManager(cfg);
|
|
this.AssetManager = this.am;
|
|
this.set('AssetManager', this.am);
|
|
},
|
|
|
|
/**
|
|
* Initialize dialog
|
|
* @private
|
|
* */
|
|
initModal: function() {
|
|
var cfg = this.config.modal,
|
|
pfx = cfg.stylePrefix || 'mdl-';
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
this.modal = new ModalDialog(cfg);
|
|
this.modal.render().appendTo('body');
|
|
this.Dialog = this.modal;
|
|
this.set('Modal', this.modal);
|
|
},
|
|
|
|
/**
|
|
* Initialize Code Manager
|
|
* @private
|
|
* */
|
|
initCodeManager: function() {
|
|
var cfg = this.config.codeManager,
|
|
pfx = cfg.stylePrefix || 'cm-';
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
this.cm = new CodeManager(cfg);
|
|
this.CodeManager = this.cm;
|
|
this.cm.loadDefaultGenerators().loadDefaultViewers();
|
|
this.set('CodeManager', this.cm);
|
|
},
|
|
|
|
/**
|
|
* Initialize Commands
|
|
* @private
|
|
* */
|
|
initCommands: function() {
|
|
var cfg = this.config.commands,
|
|
pfx = cfg.stylePrefix || 'com-';
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
cfg.em = this;
|
|
cfg.canvasId = this.config.idCanvas;
|
|
cfg.wrapperId = this.config.idWrapper;
|
|
this.com = new Commands(cfg);
|
|
this.Commands = this.com;
|
|
this.com.loadDefaultCommands();
|
|
this.set('Commands', this.com);
|
|
},
|
|
|
|
/**
|
|
* Initialize Panels
|
|
* @private
|
|
* */
|
|
initPanels: function() {
|
|
var cfg = this.config.panels,
|
|
pfx = cfg.stylePrefix || 'pn-';
|
|
cfg.stylePrefix = this.config.stylePrefix + pfx;
|
|
cfg.em = this;
|
|
this.pn = new Panels(cfg);
|
|
//this.pn.addPanel({ id: 'views-container'});
|
|
this.Panels = this.pn;
|
|
this.set('Panels', this.pn);
|
|
},
|
|
|
|
/**
|
|
* Initialize Undo manager
|
|
* @private
|
|
* */
|
|
initUndoManager: function() {
|
|
if(this.um)
|
|
return;
|
|
if(this.cmp && this.config.undoManager){
|
|
var that = this;
|
|
this.um = new Backbone.UndoManager({
|
|
register: [this.cmp.getComponent().get('components')],
|
|
track: true
|
|
});
|
|
this.UndoManager = this.um;
|
|
this.set('UndoManager', this.um);
|
|
key('⌘+z, ctrl+z', function(){
|
|
that.um.undo();
|
|
});
|
|
key('⌘+shift+z, ctrl+shift+z', function(){
|
|
that.um.redo();
|
|
});
|
|
|
|
Backbone.UndoManager.removeUndoType("change");
|
|
var beforeCache;
|
|
Backbone.UndoManager.addUndoType("change:style", {
|
|
"on": function (model, value, opt) {
|
|
if(!beforeCache)
|
|
beforeCache = model.toJSON();
|
|
if (opt && opt.avoidStore) {
|
|
return;
|
|
} else {
|
|
var obj = {
|
|
"object": model,
|
|
"before": beforeCache,
|
|
"after": model.toJSON()
|
|
};
|
|
beforeCache = null;
|
|
return obj;
|
|
}
|
|
},
|
|
"undo": function (model, bf, af, opt) {
|
|
model.set(bf);
|
|
},
|
|
"redo": function (model, bf, af, opt) {
|
|
model.set(af);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Triggered when components are updated
|
|
* @param {Object} model
|
|
* @param {Mixed} val Value
|
|
* @param {Object} opt Options
|
|
* */
|
|
componentsUpdated: function(model, val, opt){
|
|
var updatedCount = this.get('changesCount') + 1,
|
|
avSt = opt ? opt.avoidStore : 0;
|
|
this.set('changesCount', updatedCount);
|
|
if(this.stm.isAutosave() && updatedCount < this.stm.getStepsBeforeSave()){
|
|
return;
|
|
}
|
|
|
|
if(!avSt){
|
|
this.storeComponents();
|
|
this.set('changesCount', 0);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback on component selection
|
|
* @param {Object} Model
|
|
* @param {Mixed} New value
|
|
* @param {Object} Options
|
|
*
|
|
* */
|
|
componentSelected: function(model, val, options){
|
|
if(!this.get('selectedComponent'))
|
|
this.trigger('deselect-comp');
|
|
else
|
|
this.trigger('select-comp',[model,val,options]);
|
|
},
|
|
|
|
/**
|
|
* Load components from storage
|
|
*
|
|
* @return {Object}
|
|
* */
|
|
loadComponents: function() {
|
|
var comps = '';
|
|
var result = this.getCacheLoad();
|
|
|
|
if(result.components){
|
|
try{
|
|
comps = JSON.parse(result.components);
|
|
}catch(err){}
|
|
}else if(result.html)
|
|
comps = result.html;
|
|
|
|
return comps;
|
|
},
|
|
|
|
/**
|
|
* Save components to storage
|
|
*
|
|
* @return void
|
|
* */
|
|
storeComponents: function() {
|
|
this.store();
|
|
},
|
|
|
|
/**
|
|
* Load rules from storage
|
|
* @return {Array<Object>}
|
|
* @private
|
|
* */
|
|
loadRules: function() {
|
|
var comps = '';
|
|
var result = this.getCacheLoad();
|
|
|
|
if(result.style){
|
|
try{
|
|
comps = JSON.parse(result.style);
|
|
}catch(err){}
|
|
}else if(result.css)
|
|
comps = this.Parser.parseCss(result.css);
|
|
|
|
return comps;
|
|
},
|
|
|
|
/**
|
|
* Save rules to storage
|
|
* @private
|
|
* */
|
|
storeRules: function() {
|
|
this.store();
|
|
},
|
|
|
|
/**
|
|
* Triggered when components are updated
|
|
* @param {Object} model
|
|
* @param {Mixed} val Value
|
|
* @param {Object} opt Options
|
|
*
|
|
* */
|
|
updateComponents: function(model, val, opt) {
|
|
var comps = model.get('components'),
|
|
classes = model.get('classes'),
|
|
avSt = opt ? opt.avoidStore : 0;
|
|
|
|
// Observe component with Undo Manager
|
|
if(this.um)
|
|
this.um.register(comps);
|
|
|
|
// Call stopListening for not creating nested listeners
|
|
this.stopListening(comps, 'add', this.updateComponents);
|
|
this.stopListening(comps, 'remove', this.rmComponents);
|
|
this.listenTo(comps, 'add', this.updateComponents);
|
|
this.listenTo(comps, 'remove', this.rmComponents);
|
|
|
|
this.stopListening(classes, 'add remove', this.componentsUpdated);
|
|
this.listenTo(classes, 'add remove', this.componentsUpdated);
|
|
|
|
var evn = 'change:style change:content';
|
|
this.stopListening(model, evn, this.componentsUpdated);
|
|
this.listenTo(model, evn, this.componentsUpdated);
|
|
|
|
if(!avSt)
|
|
this.componentsUpdated();
|
|
},
|
|
|
|
/**
|
|
* Init stuff like storage for already existing elements
|
|
* @param {Object} model
|
|
*/
|
|
initChildrenComp: function(model) {
|
|
var comps = model.get('components');
|
|
if(comps.length){
|
|
comps.each(function(md){
|
|
this.updateComponents(md, null, { avoidStore : 1 });
|
|
this.initChildrenComp(md);
|
|
if(this.um)
|
|
this.um.register(md);
|
|
}, this);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Triggered when some component is removed updated
|
|
* @param {Object} model
|
|
* @param {Mixed} val Value
|
|
* @param {Object} opt Options
|
|
*
|
|
* */
|
|
rmComponents: function(model, val, opt){
|
|
var avSt = opt ? opt.avoidStore : 0;
|
|
|
|
if(!avSt)
|
|
this.componentsUpdated();
|
|
},
|
|
|
|
/**
|
|
* Returns model of the selected component
|
|
* @return {Component|null}
|
|
*/
|
|
getSelected: function(){
|
|
return this.get('selectedComponent');
|
|
},
|
|
|
|
/**
|
|
* Set components inside editor's canvas. This method overrides actual components
|
|
* @param {Object|string} components HTML string or components model
|
|
* @return {this}
|
|
*/
|
|
setComponents: function(components){
|
|
return this.Components.setComponents(components);
|
|
},
|
|
|
|
/**
|
|
* Returns components model from the editor's canvas
|
|
* @return {Components}
|
|
*/
|
|
getComponents: function(){
|
|
var cmp = this.Components;
|
|
var cm = this.CodeManager;
|
|
|
|
if(!cmp || !cm)
|
|
return;
|
|
|
|
var wrp = cmp.getComponent();
|
|
return cm.getCode(wrp, 'json');
|
|
},
|
|
|
|
/**
|
|
* Set style inside editor's canvas. This method overrides actual style
|
|
* @param {Object|string} style CSS string or style model
|
|
* @return {this}
|
|
*/
|
|
setStyle: function(style){
|
|
var rules = this.CssComposer.getRules();
|
|
for(var i = 0, len = rules.length; i < len; i++)
|
|
rules.pop();
|
|
rules.add(style);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns rules/style model from the editor's canvas
|
|
* @return {Rules}
|
|
*/
|
|
getStyle: function(){
|
|
return this.CssComposer.getRules();
|
|
},
|
|
|
|
/**
|
|
* Returns HTML built inside canvas
|
|
* @return {string} HTML string
|
|
*/
|
|
getHtml: function(){
|
|
var cmp = this.Components;
|
|
var cm = this.CodeManager;
|
|
|
|
if(!cmp || !cm)
|
|
return;
|
|
|
|
var wrp = cmp.getComponent();
|
|
return cm.getCode(wrp, 'html');
|
|
},
|
|
|
|
/**
|
|
* Returns CSS built inside canvas
|
|
* @return {string} CSS string
|
|
*/
|
|
getCss: function(){
|
|
var cmp = this.Components;
|
|
var cm = this.CodeManager;
|
|
var cssc = this.CssComposer;
|
|
|
|
if(!cmp || !cm || !cssc)
|
|
return;
|
|
|
|
var wrp = cmp.getComponent();
|
|
return cm.getCode(wrp, 'css', cssc);
|
|
},
|
|
|
|
/**
|
|
* Store data to the current storage
|
|
* @return {Object} Stored data
|
|
*/
|
|
store: function(){
|
|
var sm = this.StorageManager;
|
|
|
|
if(!sm)
|
|
return;
|
|
|
|
var smc = sm.getConfig();
|
|
var store = {};
|
|
|
|
if(smc.storeHtml)
|
|
store.html = this.getHtml();
|
|
|
|
if(smc.storeComponents)
|
|
store.components = JSON.stringify(this.getComponents());
|
|
|
|
if(smc.storeCss)
|
|
store.css = this.getCss();
|
|
|
|
if(smc.storeStyles)
|
|
store.styles = JSON.stringify(this.getStyle());
|
|
|
|
sm.store(store);
|
|
return store;
|
|
},
|
|
|
|
/**
|
|
* Load data from the current storage
|
|
* @return {Object} Loaded data
|
|
*/
|
|
load: function(){
|
|
var result = this.getCacheLoad(1);
|
|
//this.setComponents(result.components || result.html);
|
|
//this.setStyle(result.styles || result.css);
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Returns cached load
|
|
* @param {Boolean} force Force to reload
|
|
* @return {Object}
|
|
* @private
|
|
*/
|
|
getCacheLoad: function(force){
|
|
var f = force ? 1 : 0;
|
|
|
|
if(this.cacheLoad && !f)
|
|
return this.cacheLoad;
|
|
|
|
var sm = this.StorageManager;
|
|
var load = [];
|
|
|
|
if(!sm)
|
|
return {};
|
|
|
|
var smc = sm.getConfig();
|
|
|
|
if(smc.storeHtml)
|
|
load.push('html');
|
|
|
|
if(smc.storeComponents)
|
|
load.push('components');
|
|
|
|
if(smc.storeCss)
|
|
load.push('css');
|
|
|
|
if(smc.storeStyles)
|
|
load.push('styles');
|
|
|
|
this.cacheLoad = sm.load(load);
|
|
|
|
return this.cacheLoad;
|
|
},
|
|
|
|
});
|
|
});
|
|
|