', {class: ppfx + 'toolbar'}).get(0);
+ this.placerEl.appendChild(this.placerIntEl);
+ this.toolsEl.appendChild(this.hlEl);
+ this.toolsEl.appendChild(this.badgeEl);
+ this.toolsEl.appendChild(this.placerEl);
+ this.toolsEl.appendChild(this.ghostEl);
+ this.toolsEl.appendChild(this.toolbarEl);
this.$el.append(this.toolsEl);
var rte = this.em.get('rte');
diff --git a/src/canvas/view/FrameView.js b/src/canvas/view/FrameView.js
index 7e9a9acb4..3ab671a7f 100644
--- a/src/canvas/view/FrameView.js
+++ b/src/canvas/view/FrameView.js
@@ -8,7 +8,8 @@ function(Backbone) {
tagName: 'iframe',
attributes: {
- src: 'about:blank'
+ src: 'about:blank',
+ allowfullscreen: 'allowfullscreen'
},
initialize: function(o) {
@@ -51,4 +52,4 @@ function(Backbone) {
},
});
-});
\ No newline at end of file
+});
diff --git a/src/code_manager/config/config.js b/src/code_manager/config/config.js
index 9af9e059a..c17dc46da 100644
--- a/src/code_manager/config/config.js
+++ b/src/code_manager/config/config.js
@@ -1,6 +1,8 @@
define(function () {
return {
// Style prefix
- stylePrefix : 'cm-',
+ stylePrefix: 'cm-',
+
+ inlineCss: false,
};
-});
\ No newline at end of file
+});
diff --git a/src/code_manager/model/CodeMirrorEditor.js b/src/code_manager/model/CodeMirrorEditor.js
index 3ea2fda9c..2ac5c9d33 100644
--- a/src/code_manager/model/CodeMirrorEditor.js
+++ b/src/code_manager/model/CodeMirrorEditor.js
@@ -23,11 +23,12 @@ define(['backbone',
init: function(el)
{
this.editor = CodeMirror.fromTextArea(el, {
- dragDrop : false,
- lineNumbers : this.get('lineNumbers'),
- readOnly : this.get('readOnly'),
- mode : this.get('codeName'),
- theme : this.get('theme'),
+ dragDrop: false,
+ lineWrapping: true,
+ lineNumbers: this.get('lineNumbers'),
+ readOnly: this.get('readOnly'),
+ mode: this.get('codeName'),
+ theme: this.get('theme'),
});
return this;
@@ -47,4 +48,4 @@ define(['backbone',
},
});
- });
\ No newline at end of file
+ });
diff --git a/src/code_manager/model/HtmlGenerator.js b/src/code_manager/model/HtmlGenerator.js
index 888e219d9..45fc56dd0 100644
--- a/src/code_manager/model/HtmlGenerator.js
+++ b/src/code_manager/model/HtmlGenerator.js
@@ -6,47 +6,14 @@ define(['backbone'],
return Backbone.Model.extend({
/** @inheritdoc */
- build: function(model){
- var coll = model.get('components') || model,
- code = '';
+ build: function(model, cssc){
+ var coll = model.get('components') || model,
+ code = '';
coll.each(function(m){
- var tag = m.get('tagName'), // Tag name
- attr = '', // Attributes string
- attrId = '',
- strCls = '',
- cln = m.get('components'), // Children
- attrs = m.get('attributes'),
- sTag = m.get('void'),
- classes = m.get('classes');
- _.each(attrs,function(value, prop){
- if(prop == 'onmousedown')
- return;
- attr += value && prop!='style' ? ' ' + prop + '="' + value + '"' : '';
+ code += m.toHTML({
+ cssc: cssc
});
-
- if(m.get('type') == 'image'){
- tag = 'img';
- sTag = 1;
- attr += ' src="' + m.get('src') + '"';
- }
-
- if(!_.isEmpty(m.get('style')))
- attrId = ' id="' + m.cid + '" ';
-
- classes.each(function(m){
- strCls += ' ' + m.get('name');
- });
-
- strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : '';
- code += '<' + tag + strCls + attrId + attr + (sTag ? '/' : '') + '>' + m.get('content');
-
- if(cln.length)
- code += this.build(cln);
-
- if(!sTag)
- code += ''+tag+'>';
-
}, this);
return code;
diff --git a/src/code_manager/model/JsonGenerator.js b/src/code_manager/model/JsonGenerator.js
index d048fbae8..a1825ffcc 100644
--- a/src/code_manager/model/JsonGenerator.js
+++ b/src/code_manager/model/JsonGenerator.js
@@ -6,12 +6,10 @@ define(['backbone'],
return Backbone.Model.extend({
/** @inheritdoc */
- build: function(model)
- {
+ build: function(model) {
var json = model.toJSON();
this.beforeEach(json);
- // Avoid jshint 'loopfunc' error
_.each(json,function(v, attr){
var obj = json[attr];
if(obj instanceof Backbone.Model){
@@ -25,7 +23,6 @@ define(['backbone'],
}, this);
}
}
-
}, this);
return json;
diff --git a/src/commands/main.js b/src/commands/main.js
index 0f1febadc..f6b0a9bb9 100644
--- a/src/commands/main.js
+++ b/src/commands/main.js
@@ -98,6 +98,7 @@ define(function(require) {
defaultCommands['open-sm'] = require('./view/OpenStyleManager');
defaultCommands['open-tm'] = require('./view/OpenTraitManager');
defaultCommands['open-blocks'] = require('./view/OpenBlocks');
+ defaultCommands['open-assets'] = require('./view/OpenAssets');
defaultCommands.fullscreen = require('./view/Fullscreen');
defaultCommands.preview = require('./view/Preview');
//this.defaultCommands['resize-comp'] = require('./view/ResizeComponent');
diff --git a/src/commands/view/ExportTemplate.js b/src/commands/view/ExportTemplate.js
index 824621782..96df728b8 100644
--- a/src/commands/view/ExportTemplate.js
+++ b/src/commands/view/ExportTemplate.js
@@ -64,8 +64,8 @@ define(function() {
this.modal.open();
}
var addCss = this.protCss || '';
- this.htmlEditor.setContent( this.cm.getCode(this.components, 'html') );
- this.cssEditor.setContent( addCss + this.cm.getCode(this.components, 'css', this.cssc));
+ this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc));
+ this.cssEditor.setContent(addCss + this.cm.getCode(this.components, 'css', this.cssc));
if(this.sender)
this.sender.set('active',false);
@@ -73,4 +73,4 @@ define(function() {
stop: function(){}
};
- });
\ No newline at end of file
+ });
diff --git a/src/commands/view/MoveComponent.js b/src/commands/view/MoveComponent.js
index f32f231ec..db5ca9551 100644
--- a/src/commands/view/MoveComponent.js
+++ b/src/commands/view/MoveComponent.js
@@ -12,7 +12,7 @@ define(['backbone', './SelectComponent','./SelectPosition'],
this.noSelClass = this.ppfx + 'no-select';
},
- enable: function(){
+ enable: function() {
SelectComponent.enable.apply(this, arguments);
this.getBadgeEl().addClass(this.badgeClass);
this.getHighlighterEl().addClass(this.hoverClass);
@@ -37,17 +37,43 @@ define(['backbone', './SelectComponent','./SelectPosition'],
* */
initSorter: function(e){
var el = $(e.target).data('model');
- if(!el.get('draggable'))
+ var drag = el.get('draggable');
+ if(!drag)
return;
+
// Avoid badge showing on move
this.cacheEl = null;
this.startSelectPosition(e.target, this.frameEl.contentDocument);
+ this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMove.bind(this);
this.stopSelectComponent();
this.$wrapper.off('mousedown', this.initSorter);
this.getContentWindow().on('keydown', this.rollback);
},
+ /**
+ * Init sorter from model
+ * @param {Object} model
+ * @private
+ */
+ initSorterFromModel: function(model) {
+ var drag = model.get('draggable');
+ if(!drag)
+ return;
+ // Avoid badge showing on move
+ this.cacheEl = null;
+ var el = model.get('view').el;
+ this.startSelectPosition(el, this.frameEl.contentDocument);
+ this.sorter.draggable = drag;
+ this.sorter.onEndMove = this.onEndMoveFromModel.bind(this);
+ this.stopSelectComponent();
+ this.getContentWindow().on('keydown', this.rollback);
+ },
+
+ onEndMoveFromModel: function() {
+ this.getContentWindow().off('keydown', this.rollback);
+ },
+
/**
* Callback after sorting
* @private
@@ -110,4 +136,4 @@ define(['backbone', './SelectComponent','./SelectPosition'],
wp.css('cursor', '').unbind().removeClass(this.noSelClass);
}
});
- });
\ No newline at end of file
+ });
diff --git a/src/commands/view/OpenAssets.js b/src/commands/view/OpenAssets.js
new file mode 100644
index 000000000..115b3169c
--- /dev/null
+++ b/src/commands/view/OpenAssets.js
@@ -0,0 +1,25 @@
+define(function() {
+ return {
+
+ run: function(editor, sender, opts) {
+ var opt = opts || {};
+ var config = editor.getConfig();
+ var modal = editor.Modal;
+ var assetManager = editor.AssetManager;
+ assetManager.onClick(opt.onClick);
+ assetManager.onDblClick(opt.onDblClick);
+
+ // old API
+ assetManager.setTarget(opt.target);
+ assetManager.onSelect(opt.onSelect);
+
+ if (modal) {
+ modal.setTitle(opt.modalTitle || 'Select image');
+ modal.setContent('');
+ modal.setContent(assetManager.render(1));
+ modal.open();
+ }
+ },
+
+ };
+});
diff --git a/src/commands/view/OpenLayers.js b/src/commands/view/OpenLayers.js
index f2779ddb0..71f0709c6 100644
--- a/src/commands/view/OpenLayers.js
+++ b/src/commands/view/OpenLayers.js
@@ -15,6 +15,7 @@ define(['Navigator'], function(Layers) {
config.layers.stylePrefix = config.stylePrefix + lyStylePfx;
config.layers.em = em.editor;
+ config.layers.opened = em.editor.get('opened');
var layers = new Layers(collection, config.layers);
this.$layers = layers.render();
@@ -35,4 +36,4 @@ define(['Navigator'], function(Layers) {
this.$layers.hide();
}
};
- });
\ No newline at end of file
+ });
diff --git a/src/commands/view/Preview.js b/src/commands/view/Preview.js
index 3fd62cf68..aa4a3d4a6 100644
--- a/src/commands/view/Preview.js
+++ b/src/commands/view/Preview.js
@@ -8,9 +8,16 @@ define(function() {
return this.panels;
},
+ tglPointers: function(editor, v) {
+ var elP = editor.Canvas.getBody().querySelectorAll('.' + this.ppfx + 'no-pointer');
+ _.each(elP, function(item){
+ item.style.pointerEvents = v ? '' : 'all';
+ });
+ },
+
run: function(editor, sender) {
- if(sender)
- sender.set('active',false);
+ if(sender && sender.set)
+ sender.set('active', false);
editor.stopCommand('sw-visibility');
var that = this;
var panels = this.getPanels(editor);
@@ -26,6 +33,12 @@ define(function() {
};
}
this.helper.style.display = 'inline-block';
+ this.tglPointers(editor);
+
+ /*
+ editor.Canvas.getBody().querySelectorAll('.' + pfx + 'no-pointer').forEach(function(){
+ this.style.pointerEvents = 'all';
+ });*/
panels.style.display = 'none';
var canvasS = canvas.style;
@@ -49,6 +62,7 @@ define(function() {
this.helper.style.display = 'none';
}
editor.trigger('change:canvasOffset');
+ this.tglPointers(editor, 1);
}
};
- });
\ No newline at end of file
+ });
diff --git a/src/commands/view/SelectComponent.js b/src/commands/view/SelectComponent.js
index b07d9171f..c1ecc9047 100644
--- a/src/commands/view/SelectComponent.js
+++ b/src/commands/view/SelectComponent.js
@@ -1,8 +1,11 @@
-define(function() {
+define(function(require) {
/**
* @class SelectComponent
* @private
* */
+ var ToolbarView = require('DomComponents/view/ToolbarView');
+ var Toolbar = require('DomComponents/model/Toolbar');
+
return {
init: function(o){
@@ -16,6 +19,7 @@ define(function() {
var config = this.config.em.get('Config');
this.startSelectComponent();
this.toggleClipboard(config.copyPaste);
+ this.config.em.on('change:canvasOffset', this.onFrameScroll);
},
/**
@@ -96,17 +100,20 @@ define(function() {
var key = e.which || e.keyCode;
var comp = this.editorModel.get('selectedComponent');
var focused = this.frameEl.contentDocument.activeElement.tagName !== 'BODY';
+
+ // On CANC (46) or Backspace (8)
if(key == 8 || key == 46) {
if(!focused)
e.preventDefault();
- if(comp && !focused){
+ if(comp && !focused) {
if(!comp.get('removable'))
- return;
+ return;
comp.set('status','');
comp.destroy();
this.hideBadge();
this.clean();
- this.editorModel.set('selectedComponent',null);
+ this.hideHighlighter();
+ this.editorModel.set('selectedComponent', null);
}
}
},
@@ -126,6 +133,7 @@ define(function() {
}
var pos = this.getElementPos(trg);
this.updateBadge(trg, pos);
+ // Not mirrored
this.updateHighlighter(trg, pos);
},
@@ -137,8 +145,14 @@ define(function() {
onOut: function(e) {
e.stopPropagation();
this.hideBadge();
- if(this.hl)
- this.hl.css({ left: -10000, top:-10000 });
+ this.hideHighlighter();
+ },
+
+ /**
+ * Hide Highlighter element
+ */
+ hideHighlighter: function () {
+ this.canvas.getHighlighter().style.display = 'none';
},
/**
@@ -188,15 +202,15 @@ define(function() {
* @param {Object} pos Position object
* @private
*/
- updateHighlighter: function(el, pos){
- if(!this.hl)
- this.hl = $(this.canvas.getHighlighter());
- this.hl.css({
- left: pos.left,
- top: pos.top,
- height: pos.height,
- width: pos.width
- });
+ updateHighlighter: function(el, pos) {
+ var hlEl = this.canvas.getHighlighter();
+ var hlStyle = hlEl.style;
+ var unit = 'px';
+ hlStyle.left = pos.left + unit;
+ hlStyle.top = pos.top + unit;
+ hlStyle.height = pos.height + unit;
+ hlStyle.width = pos.width + unit;
+ hlStyle.display = 'block';
},
/**
@@ -212,11 +226,92 @@ define(function() {
var $el = $(el);
var nMd = $el.data('model');
if(nMd){
+ var mirror = nMd.get('mirror');
+ nMd = mirror ? mirror : nMd;
+
+ // Close all opened components inside Navigator
+ var opened = this.em.get('opened');
+ for (var cid in opened){
+ var m = opened[cid];
+ m.set('open', 0);
+ }
+ var parent = nMd.collection ? nMd.collection.parent : null;
+ while(parent) {
+ parent.set('open', 1);
+ opened[parent.cid] = parent;
+ parent = parent.collection ? parent.collection.parent : null;
+ }
+
this.editorModel.set('selectedComponent', nMd);
nMd.set('status','selected');
+ this.updateToolbar(nMd);
}
},
+ /**
+ * Update toolbar if the component has one
+ * @param {Object} model
+ */
+ updateToolbar: function(model) {
+ var toolbar = model.get('toolbar');
+ var ppfx = this.ppfx;
+ var showToolbar = this.config.em.get('Config').showToolbar;
+
+ if (showToolbar && toolbar && toolbar.length) {
+
+ if(!this.toolbar) {
+ var toolbarEl = this.canvas.getToolbarEl();
+ toolbarEl.innerHTML = '';
+ this.toolbar = new Toolbar(toolbar);
+ var toolbarView = new ToolbarView({
+ collection: this.toolbar,
+ editor: this.editor
+ });
+ toolbarEl.appendChild(toolbarView.render().el);
+ }
+
+ this.toolbar.reset(toolbar);
+
+ var view = model.get('view');
+ if(view) {
+ this.updateToolbarPos(view.el);
+ }
+ }
+ },
+
+ /**
+ * Update toolbar positions
+ * @param {HTMLElement} el
+ * @param {Object} pos
+ */
+ updateToolbarPos: function(el, elPos) {
+ var toolbarEl = this.canvas.getToolbarEl();
+ var canvasPos = this.getCanvasPosition();
+ var pos = elPos || this.getElementPos(el);
+ var toolbarStyle = toolbarEl.style;
+ var unit = 'px';
+ toolbarStyle.display = 'flex';
+ var elTop = pos.top - toolbarEl.offsetHeight;
+ var elLeft = pos.left + pos.width - toolbarEl.offsetWidth;
+ var leftPos = elLeft < canvasPos.left ? canvasPos.left : elLeft;
+
+ // This will make the toolbar follow the window up
+ // and down while scrolling
+ var topPos = elTop < canvasPos.top ? canvasPos.top : elTop;
+ // This will stop the toolbar when the end of the element is reached
+ topPos = topPos > (pos.top + pos.height) ? (pos.top + pos.height) : topPos;
+ toolbarStyle.left = elLeft + unit;
+ toolbarStyle.top = topPos + unit;
+ },
+
+ /**
+ * Return canvas dimensions and positions
+ * @return {Object}
+ */
+ getCanvasPosition: function () {
+ return this.canvas.getCanvasView().getPosition();
+ },
+
/**
* Removes all highlighting effects on components
* @private
@@ -241,8 +336,16 @@ define(function() {
*/
onFrameScroll: function(e){
var el = this.cacheEl;
- if(el)
- this.updateBadge(el, this.getElementPos(el));
+ if(el){
+ var elPos = this.getElementPos(el);
+ this.updateBadge(el, elPos);
+ var model = this.editorModel.get('selectedComponent');
+
+ if (model) {
+ var view = model.get('view');
+ this.updateToolbarPos(view.el);
+ }
+ }
},
/**
@@ -286,7 +389,9 @@ define(function() {
return this.contWindow;
},
- run: function(em, sender) {
+ run: function(em) {
+ if(em && em.get)
+ this.editor = em.get('Editor');
this.enable();
},
@@ -297,6 +402,8 @@ define(function() {
this.em.set('selectedComponent', null);
this.toggleClipboard();
this.hideBadge();
+ this.canvas.getToolbarEl().style.display = 'none';
+ this.config.em.off('change:canvasOffset', this.onFrameScroll);
}
};
});
diff --git a/src/config/require-config.js b/src/config/require-config.js
index 55ae4d0c5..82054f079 100644
--- a/src/config/require-config.js
+++ b/src/config/require-config.js
@@ -21,15 +21,15 @@ require.config({
},
paths: {
- jquery: '../vendor/jquery/jquery',
- underscore: '../vendor/underscore/underscore',
- backbone: '../vendor/backbone/backbone',
- backboneUndo: '../vendor/backbone-undo/backbone-undo',
- keymaster: '../vendor/keymaster/keymaster',
- text: '../vendor/require-text/text',
- Spectrum: '../vendor/spectrum/spectrum',
- codemirror: '../vendor/codemirror',
- formatting: '../vendor/codemirror-formatting/formatting',
+ jquery: '../node_modules/jquery/dist/jquery',
+ underscore: '../node_modules/underscore/underscore',
+ backbone: '../node_modules/backbone/backbone',
+ backboneUndo: '../node_modules/backbone-undo/Backbone.Undo',
+ keymaster: '../node_modules/keymaster/keymaster',
+ text: '../node_modules/requirejs-text/text',
+ Spectrum: '../node_modules/spectrum-colorpicker/spectrum',
+ codemirror: '../node_modules/codemirror',
+ formatting: '../node_modules/codemirror-formatting/formatting',
},
packages : [
diff --git a/src/css_composer/main.js b/src/css_composer/main.js
index ba76f518d..3e97ad010 100644
--- a/src/css_composer/main.js
+++ b/src/css_composer/main.js
@@ -236,7 +236,7 @@ define(function(require) {
var selec = sm.add(sels[j]);
newSels.push(selec);
}
- var model = this.add(newSels, rule.state, rule.width);
+ var model = this.add(newSels, rule.state, rule.maxWidth);
model.set('style', rule.style || {});
result.push(model);
}
diff --git a/src/demo.js b/src/demo.js
index 09d076be6..fb31fda65 100644
--- a/src/demo.js
+++ b/src/demo.js
@@ -11,25 +11,31 @@ require(['config/require-config'], function() {
fromElement: true,
/*
components: [{
+ type: 'text',
style:{
width:'100px',
- height:'100px'
+ height:'100px',
+ margin: '50px auto',
},
- traits: ['title']
- },{
- style:{
- width:'150px',
- height:'100px'
- },
- traits: [{name:'title', value: "myTitleTest"}]
- },{
- type: 'image'
- },{
- type: 'link',
- content: 'mylink',
- }],
- */
- storageManager:{ autoload: 0},
+ traits: ['title'],
+ components: [{
+ type: 'textnode',
+ content: 'text node row',
+ },{
+ type: 'textnode',
+ content: ', another text node',
+ },{
+ type: 'link',
+ content: 'someLink',
+ },{
+ type: 'textnode',
+ content: " More text node --- ",
+ }],
+ }],*/
+
+ storageManager:{
+ autoload: 0,
+ },
commands: {
defaults : [{
id: 'open-github',
@@ -59,7 +65,7 @@ require(['config/require-config'], function() {
}
}],
},
-/*
+
assetManager: {
storageType : '',
storeOnChange : true,
@@ -77,7 +83,7 @@ require(['config/require-config'], function() {
{ type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728},
]
},
-*/
+
styleManager : {
sectors: [{
@@ -319,7 +325,43 @@ require(['config/require-config'], function() {
);
+
window.editor = editor;
+ /*
+ // Test toolbar commands
+ var cmd = editor.Commands;
+ cmd.add('tlb-delete', {
+ run: function(ed){
+ var sel = ed.getSelected();
+ if(!sel)
+ return;
+ sel.destroy();
+ ed.Canvas.getToolbarEl().style.display = 'none';
+ },
+ });
+
+ cmd.add('tlb-clone', {
+ run: function(ed){
+ var sel = ed.getSelected();
+ var collection = sel.collection;
+ var index = collection.indexOf(sel);
+ collection.add(sel.clone(), {at: index + 1});
+ },
+ });
+
+ cmd.add('tlb-move', {
+ run: function(ed){
+ var sel = ed.getSelected();
+ ed.editor.stopDefault();
+ var cmdMove = cmd.get('move-comp');
+ cmdMove.onEndMoveFromModel = function() {
+ ed.editor.runDefault();
+ };
+ cmdMove.initSorterFromModel(sel);
+ ed.Canvas.getToolbarEl().style.display = 'none';
+ },
+ });
+ */
});
});
diff --git a/src/dom_components/main.js b/src/dom_components/main.js
index c0aa7a864..d1009c502 100644
--- a/src/dom_components/main.js
+++ b/src/dom_components/main.js
@@ -36,19 +36,58 @@ define(function(require) {
return function (){
var c = {},
- defaults = require('./config/config'),
- Component = require('./model/Component'),
- ComponentText = require('./model/ComponentText'),
- ComponentImage = require('./model/ComponentImage'),
- ComponentLink = require('./model/ComponentLink'),
- ComponentView = require('./view/ComponentView'),
- ComponentImageView = require('./view/ComponentImageView'),
- ComponentTextView = require('./view/ComponentTextView');
- ComponentLinkView = require('./view/ComponentLinkView');
+ componentTypes = {},
+ defaults = require('./config/config'),
+ Component = require('./model/Component'),
+ ComponentView = require('./view/ComponentView');
var component, componentView;
+ var defaultTypes = {
+ 'cell': {
+ model: require('./model/ComponentTableCell'),
+ view: require('./view/ComponentTableCellView'),
+ },
+ 'row': {
+ model: require('./model/ComponentTableRow'),
+ view: require('./view/ComponentTableRowView'),
+ },
+ 'table': {
+ model: require('./model/ComponentTable'),
+ view: require('./view/ComponentTableView'),
+ },
+ 'map': {
+ model: require('./model/ComponentMap'),
+ view: require('./view/ComponentMapView'),
+ },
+ 'link': {
+ model: require('./model/ComponentLink'),
+ view: require('./view/ComponentLinkView'),
+ },
+ 'video': {
+ model: require('./model/ComponentVideo'),
+ view: require('./view/ComponentVideoView'),
+ },
+ 'image': {
+ model: require('./model/ComponentImage'),
+ view: require('./view/ComponentImageView'),
+ },
+ 'textnode': {
+ model: require('./model/ComponentTextNode'),
+ view: require('./view/ComponentTextNodeView'),
+ },
+ 'text': {
+ model: require('./model/ComponentText'),
+ view: require('./view/ComponentTextView'),
+ },
+ 'default': {
+ model: Component,
+ view: ComponentView,
+ },
+ };
return {
+ componentTypes: defaultTypes,
+
/**
* Name of the module
* @type {String}
@@ -96,14 +135,22 @@ define(function(require) {
c.rte = c.em.get('rte') || '';
c.modal = c.em.get('Modal') || '';
c.am = c.em.get('AssetManager') || '';
+ c.em.get('Parser').compTypes = defaultTypes;
}
- component = new Component(c.wrapper, { sm: c.em, config: c });
+ component = new Component(c.wrapper, {
+ sm: c.em,
+ config: c,
+ defaultTypes: defaultTypes,
+ componentTypes: componentTypes,
+ });
component.set({ attributes: {id: 'wrapper'}});
component.get('components').add(c.components);
componentView = new ComponentView({
model: component,
config: c,
+ defaultTypes: defaultTypes,
+ componentTypes: componentTypes,
});
return this;
},
@@ -283,6 +330,15 @@ define(function(require) {
this.clear().addComponent(components);
},
+ /**
+ * Add new component type
+ * @private
+ */
+ addComponentType: function(type, methods) {
+ componentTypes[type] = methods;
+ return this;
+ }
+
};
};
});
diff --git a/src/dom_components/model/Component.js b/src/dom_components/model/Component.js
index 1b1109265..d6f8bbe4e 100644
--- a/src/dom_components/model/Component.js
+++ b/src/dom_components/model/Component.js
@@ -7,12 +7,31 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
tagName: 'div',
type: '',
editable: false,
+
+ // True if the component is removable from the canvas
removable: true,
+
+ // Indicates if it's possible to drag the component inside other
+ // Tip: Indicate an array of selectors where it could be dropped inside
draggable: true,
+
+ // Indicates if it's possible to drop other components inside
+ // Tip: Indicate an array of selectors which could be dropped inside
droppable: true,
+
+ // Set false if don't want to see the badge (with the name) over the component
badgable: true,
+
+ // True if it's possible to style it
+ // Tip: Indicate an array of css properties is possible to style
stylable: true,
+
+ // True if it's possible to clone the component
copyable: true,
+
+ // TODO
+ mirror: '',
+
void: false,
state: '',
status: '',
@@ -20,7 +39,18 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
content: '',
style: {},
attributes: {},
+ classes: '',
traits: ['id', 'title'],
+ toolbar: [{
+ attributes: {class: 'fa fa-arrows'},
+ command: 'tlb-move',
+ },{
+ attributes: {class: 'fa fa-clone'},
+ command: 'tlb-clone',
+ },{
+ attributes: {class: 'fa fa-trash-o'},
+ command: 'tlb-delete',
+ }],
},
initialize: function(o, opt) {
@@ -31,8 +61,9 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
this.sm = opt ? opt.sm || {} : {};
this.config = o || {};
this.defaultC = this.config.components || [];
- this.defaultCl = this.normalizeClasses(this.config.classes || []);
+ this.defaultCl = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.components = new Components(this.defaultC, opt);
+ this.components.parent = this;
this.set('components', this.components);
this.set('classes', new Selectors(this.defaultCl));
var traits = new Traits();
@@ -41,6 +72,18 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
this.set('traits', traits);
},
+ /**
+ * Load traits
+ * @param {Array} traits
+ * @private
+ */
+ loadTraits: function(traits) {
+ var trt = new Traits();
+ trt.setTarget(this);
+ trt.add(traits);
+ this.set('traits', trt);
+ },
+
/**
* Normalize input classes from array to array of objects
* @param {Array} arr
@@ -99,13 +142,13 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
});
}
attr.status = '';
+ attr.view = '';
return new this.constructor(attr, {sm: this.sm});
},
/**
* Get name of the component
- *
- * @return {String}
+ * @return {string}
* @private
* */
getName: function() {
@@ -115,10 +158,79 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
var tag = this.get('tagName');
tag = tag == 'div' ? 'box' : tag;
tag = type ? type : tag;
- this.name = tag.charAt(0).toUpperCase() + tag.slice(1) + ' ' + id;
+ this.name = tag.charAt(0).toUpperCase() + tag.slice(1);
}
return this.name;
},
+ /**
+ * Return HTML string of the component
+ * @param {Object} opts Options
+ * @return {string} HTML string
+ * @private
+ */
+ toHTML: function(opts) {
+ var code = '';
+ var m = this;
+ var tag = m.get('tagName'),
+ sTag = m.get('void'),
+ attrId = '';
+ // Build the string of attributes
+ var strAttr = '';
+ var attr = this.getAttrToHTML();
+ for(var prop in attr){
+ var val = attr[prop];
+ strAttr += typeof val !== undefined && val !== '' ?
+ ' ' + prop + '="' + val + '"' : '';
+ }
+ // Build the string of classes
+ var strCls = '';
+ m.get('classes').each(function(m){
+ strCls += ' ' + m.get('name');
+ });
+ strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : '';
+
+ // If style is not empty I need an ID attached to the component
+ // TODO: need to refactor in case of 'ID Trait'
+ if(!_.isEmpty(m.get('style')))
+ attrId = ' id="' + m.cid + '" ';
+
+ code += '<' + tag + strCls + attrId + strAttr + (sTag ? '/' : '') + '>' + m.get('content');
+
+ m.get('components').each(function(m) {
+ code += m.toHTML();
+ });
+
+ if(!sTag)
+ code += ''+tag+'>';
+
+ return code;
+ },
+
+ /**
+ * Returns object of attributes for HTML
+ * @return {Object}
+ * @private
+ */
+ getAttrToHTML: function() {
+ var attr = this.get('attributes') || {};
+ delete attr.style;
+ return attr;
+ }
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ return {tagName: el.tagName ? el.tagName.toLowerCase() : ''};
+ },
+
});
});
diff --git a/src/dom_components/model/ComponentImage.js b/src/dom_components/model/ComponentImage.js
index f2cb65ffe..e5bd69c37 100644
--- a/src/dom_components/model/ComponentImage.js
+++ b/src/dom_components/model/ComponentImage.js
@@ -4,10 +4,93 @@ define(['./Component'],
return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
- src: '',
- droppable: false,
- traits: ['alt'],
+ type: 'image',
+ tagName: 'img',
+ src: '',
+ void: 1,
+ droppable: false,
+ traits: ['alt'],
+ toolbar: [{
+ attributes: {class: 'fa fa-arrows'},
+ command: 'tlb-move',
+ },{
+ attributes: {class: 'fa fa-clone'},
+ command: 'tlb-clone',
+ },{
+ attributes: {class: 'fa fa-pencil'},
+ command: 'tlb-edit',
+ },{
+ attributes: {class: 'fa fa-trash-o'},
+ command: 'tlb-delete',
+ }],
}),
+ initialize: function(o, opt) {
+ Component.prototype.initialize.apply(this, arguments);
+ var attr = this.get('attributes');
+ if(attr.src)
+ this.set('src', attr.src);
+ },
+
+ /**
+ * Returns object of attributes for HTML
+ * @return {Object}
+ * @private
+ */
+ getAttrToHTML: function() {
+ var attr = Component.prototype.getAttrToHTML.apply(this, arguments);
+ delete attr.onmousedown;
+ var src = this.get('src');
+ if(src)
+ attr.src = src;
+ return attr;
+ },
+
+ /**
+ * Parse uri
+ * @param {string} uri
+ * @return {object}
+ * @private
+ */
+ parseUri: function(uri) {
+ var el = document.createElement('a');
+ el.href = uri;
+ var query = {};
+ var qrs = el.search.substring(1).split('&');
+ for (var i = 0; i < qrs.length; i++) {
+ var pair = qrs[i].split('=');
+ var name = decodeURIComponent(pair[0]);
+ if(name)
+ query[name] = decodeURIComponent(pair[1]);
+ }
+ return {
+ hostname: el.hostname,
+ pathname: el.pathname,
+ protocol: el.protocol,
+ search: el.search,
+ hash: el.hash,
+ port: el.port,
+ query: query,
+ };
+ },
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ var result = '';
+ if(el.tagName == 'IMG'){
+ result = {type: 'image'};
+ }
+ return result;
+ },
+
});
});
diff --git a/src/dom_components/model/ComponentLink.js b/src/dom_components/model/ComponentLink.js
index 89247f5bb..e5e5756f6 100644
--- a/src/dom_components/model/ComponentLink.js
+++ b/src/dom_components/model/ComponentLink.js
@@ -1,13 +1,42 @@
-define(['./Component'],
+define(['./ComponentText'],
function (Component) {
return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
+ type: 'link',
tagName: 'a',
- droppable: false,
traits: ['title', 'href', 'target'],
}),
+ /**
+ * Returns object of attributes for HTML
+ * @return {Object}
+ * @private
+ */
+ getAttrToHTML: function() {
+ var attr = Component.prototype.getAttrToHTML.apply(this, arguments);
+ delete attr.onmousedown;
+ return attr;
+ },
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ var result = '';
+ if(el.tagName == 'A'){
+ result = {type: 'link'};
+ }
+ return result;
+ },
+
});
});
diff --git a/src/dom_components/model/ComponentMap.js b/src/dom_components/model/ComponentMap.js
new file mode 100644
index 000000000..e188705fe
--- /dev/null
+++ b/src/dom_components/model/ComponentMap.js
@@ -0,0 +1,107 @@
+define(['./ComponentImage'],
+ function (Component) {
+
+ return Component.extend({
+
+ defaults: _.extend({}, Component.prototype.defaults, {
+ type: 'map',
+ void: 0,
+ mapUrl: 'https://maps.google.com/maps',
+ tagName: 'iframe',
+ mapType: 'q',
+ address: '',
+ zoom: '1',
+ attributes: {frameborder: 0},
+ traits: [{
+ label: 'Address',
+ name: 'address',
+ placeholder: 'eg. London, UK',
+ changeProp: 1,
+ },{
+ type: 'select',
+ label: 'Map type',
+ name: 'mapType',
+ changeProp: 1,
+ options: [
+ {value: 'q', name: 'Roadmap'},
+ {value: 'w', name: 'Satellite'}
+ ]
+ },{
+ label: 'Zoom',
+ name: 'zoom',
+ type: 'range',
+ min: '1',
+ max: '20',
+ changeProp: 1,
+ }],
+ }),
+
+
+ initialize: function(o, opt) {
+ if(this.get('src'))
+ this.parseFromSrc();
+ else
+ this.updateSrc();
+ Component.prototype.initialize.apply(this, arguments);
+ this.listenTo(this, 'change:address change:zoom change:mapType', this.updateSrc);
+ },
+
+ updateSrc: function() {
+ this.set('src', this.getMapUrl());
+ },
+
+ /**
+ * Returns url of the map
+ * @return {string}
+ * @private
+ */
+ getMapUrl: function() {
+ var md = this;
+ var addr = md.get('address');
+ var zoom = md.get('zoom');
+ var type = md.get('mapType');
+ var size = '';
+ addr = addr ? '&q=' + addr : '';
+ zoom = zoom ? '&z=' + zoom : '';
+ type = type ? '&t=' + type : '';
+ var result = md.get('mapUrl')+'?' + addr + zoom + type;
+ result += '&output=embed';
+ return result;
+ },
+
+ /**
+ * Set attributes by src string
+ * @private
+ */
+ parseFromSrc: function() {
+ var uri = this.parseUri(this.get('src'));
+ var qr = uri.query;
+ if(qr.q)
+ this.set('address', qr.q);
+ if(qr.z)
+ this.set('zoom', qr.z);
+ if(qr.t)
+ this.set('mapType', qr.t);
+ },
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ var result = '';
+ if(el.tagName == 'IFRAME' &&
+ /maps\.google\.com/.test(el.src) ){
+ result = {type: 'map', src: el.src};
+ }
+ return result;
+ },
+
+ });
+});
diff --git a/src/dom_components/model/ComponentTable.js b/src/dom_components/model/ComponentTable.js
new file mode 100644
index 000000000..d36c2dfda
--- /dev/null
+++ b/src/dom_components/model/ComponentTable.js
@@ -0,0 +1,91 @@
+define(['./Component'],
+ function (Component) {
+
+ return Component.extend({
+
+ defaults: _.extend({}, Component.prototype.defaults, {
+ type: 'table',
+ tagName: 'table',
+ droppable: ['tr', 'tbody', 'thead', 'tfoot'],
+ columns: 3,
+ rows: 2,
+ /*
+ traits: [{
+ label: 'Columns',
+ name: 'columns',
+ changeProp: 1,
+ },{
+ label: 'Rows',
+ name: 'rows',
+ changeProp: 1,
+ }]
+ */
+ }),
+
+ initialize: function(o, opt) {
+ Component.prototype.initialize.apply(this, arguments);
+ var components = this.get('components');
+ var rows = this.get('rows');
+ var columns = this.get('columns');
+
+ // Init components if empty
+ if(!components.length){
+ var rowsToAdd = [];
+
+ while(rows--){
+ var columnsToAdd = [];
+ var clm = columns;
+
+ while (clm--) {
+ columnsToAdd.push({
+ type: 'cell',
+ classes: ['cell']
+ });
+ }
+
+ rowsToAdd.push({
+ type: 'row',
+ classes: ['row'],
+ components: columnsToAdd
+ });
+ }
+ components.add(rowsToAdd);
+ }
+
+ // Clean table from non rows
+ var rowsColl = [];
+ components.each(function(model){
+ if(model.get('type') != 'row'){
+ model.get('components').each(function(row) {
+ if(row.get('type') == 'row'){
+ row.collection = components;
+ rowsColl.push(row);
+ }
+ });
+ }else{
+ rowsColl.push(model);
+ }
+ });
+ components.reset(rowsColl);
+ },
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ var result = '';
+ if(el.tagName == 'TABLE'){
+ result = {type: 'table'};
+ }
+ return result;
+ },
+
+ });
+});
diff --git a/src/dom_components/model/ComponentTableCell.js b/src/dom_components/model/ComponentTableCell.js
new file mode 100644
index 000000000..ae961ccc9
--- /dev/null
+++ b/src/dom_components/model/ComponentTableCell.js
@@ -0,0 +1,35 @@
+define(['./Component'],
+ function (Component) {
+
+ return Component.extend({
+
+ defaults: _.extend({}, Component.prototype.defaults, {
+ type: 'cell',
+ tagName: 'td',
+ draggable: ['tr'],
+ }),
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ var result = '';
+ var tag = el.tagName;
+ if(tag == 'TD' || tag == 'TH'){
+ result = {
+ type: 'cell',
+ tagName: tag.toLowerCase()
+ };
+ }
+ return result;
+ },
+
+ });
+});
diff --git a/src/dom_components/model/ComponentTableRow.js b/src/dom_components/model/ComponentTableRow.js
new file mode 100644
index 000000000..52789705d
--- /dev/null
+++ b/src/dom_components/model/ComponentTableRow.js
@@ -0,0 +1,46 @@
+define(['./Component'],
+ function (Component) {
+
+ return Component.extend({
+
+ defaults: _.extend({}, Component.prototype.defaults, {
+ type: 'row',
+ tagName: 'tr',
+ draggable: ['table', 'tbody', 'thead'],
+ droppable: ['th', 'td']
+ }),
+
+ initialize: function(o, opt) {
+ Component.prototype.initialize.apply(this, arguments);
+
+ // Clean the row from non cell components
+ var cells = [];
+ var components = this.get('components');
+ components.each(function(model){
+ if(model.get('type') == 'cell'){
+ cells.push(model);
+ }
+ });
+ components.reset(cells);
+ }
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ var result = '';
+ if(el.tagName == 'TR'){
+ result = {type: 'row'};
+ }
+ return result;
+ },
+
+ });
+});
diff --git a/src/dom_components/model/ComponentText.js b/src/dom_components/model/ComponentText.js
index 4fc08a037..b34f10291 100644
--- a/src/dom_components/model/ComponentText.js
+++ b/src/dom_components/model/ComponentText.js
@@ -4,8 +4,7 @@ define(['./Component'],
return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
- content : '',
- droppable : false,
+ droppable: false,
}),
});
diff --git a/src/dom_components/model/ComponentTextNode.js b/src/dom_components/model/ComponentTextNode.js
new file mode 100644
index 000000000..a27cd2432
--- /dev/null
+++ b/src/dom_components/model/ComponentTextNode.js
@@ -0,0 +1,28 @@
+define(['./Component'],
+ function (Component) {
+
+ return Component.extend({
+
+ defaults: _.extend({}, Component.prototype.defaults, {
+ droppable: false,
+ }),
+
+ toHTML: function() {
+ return this.get('content');
+ },
+
+ }, {
+
+ isComponent: function(el) {
+ var result = '';
+ if(el.nodeType === 3){
+ result = {
+ type: 'textnode',
+ content: el.textContent
+ };
+ }
+ return result;
+ },
+
+ });
+});
diff --git a/src/dom_components/model/ComponentVideo.js b/src/dom_components/model/ComponentVideo.js
new file mode 100644
index 000000000..8ebbb5511
--- /dev/null
+++ b/src/dom_components/model/ComponentVideo.js
@@ -0,0 +1,316 @@
+define(['./ComponentImage'],
+ function (Component) {
+
+ var yt = 'yt';
+ var vi = 'vi';
+
+ return Component.extend({
+
+ defaults: _.extend({}, Component.prototype.defaults, {
+ type: 'video',
+ tagName: 'video',
+ videoId: '',
+ void: 0,
+ provider: '', // on change of provider, traits are switched
+ ytUrl: 'http://www.youtube.com/embed/',
+ viUrl: 'http://player.vimeo.com/video/',
+ loop: 0,
+ muted: 0,
+ autoplay: 0,
+ controls: 1,
+ color: '',
+ sources: [],
+ attributes:{allowfullscreen:'allowfullscreen'}
+ }),
+
+ initialize: function(o, opt) {
+ var traits = [];
+ var prov = this.get('provider');
+ switch (prov) {
+ case yt:
+ traits = this.getYoutubeTraits();
+ break;
+ case vi:
+ traits = this.getVimeoTraits();
+ break;
+ default:
+ traits = this.getSourceTraits();
+ }
+ if(this.get('src'))
+ this.parseFromSrc();
+ this.set('traits', traits);
+ Component.prototype.initialize.apply(this, arguments);
+ this.listenTo(this, 'change:provider', this.updateTraits);
+ this.listenTo(this, 'change:videoId', this.updateSrc);
+ },
+
+ /**
+ * Set attributes by src string
+ */
+ parseFromSrc: function() {
+ var prov = this.get('provider');
+ var uri = this.parseUri(this.get('src'));
+ var qr = uri.query;
+ switch (prov) {
+ case yt: case vi:
+ var videoId = uri.pathname.split('/').pop();
+ this.set('videoId', videoId);
+ if(qr.autoplay)
+ this.set('autoplay', 1);
+ if(qr.loop)
+ this.set('loop', 1);
+ if(parseInt(qr.controls) === 0)
+ this.set('controls', 0);
+ if(qr.color)
+ this.set('color', qr.color);
+ break;
+ default:
+ }
+ },
+
+ /**
+ * Update src on change of video ID
+ * @private
+ */
+ updateSrc: function() {
+ var prov = this.get('provider');
+ switch (prov) {
+ case yt:
+ this.set('src',this.getYoutubeSrc());
+ break;
+ case vi:
+ this.set('src',this.getVimeoSrc());
+ break;
+ }
+ },
+
+ /**
+ * Returns object of attributes for HTML
+ * @return {Object}
+ * @private
+ */
+ getAttrToHTML: function() {
+ var attr = Component.prototype.getAttrToHTML.apply(this, arguments);
+ var prov = this.get('provider');
+ switch (prov) {
+ case yt: case vi:
+ break;
+ default:
+ if(this.get('loop'))
+ attr.loop = 'loop';
+ if(this.get('autoplay'))
+ attr.autoplay = 'autoplay';
+ if(this.get('controls'))
+ attr.controls = 'controls';
+ }
+ return attr;
+ },
+
+ /**
+ * Update traits by provider
+ * @private
+ */
+ updateTraits: function() {
+ var prov = this.get('provider');
+ var traits = this.getSourceTraits();
+ switch (prov) {
+ case yt:
+ this.set('tagName', 'iframe');
+ traits = this.getYoutubeTraits();
+ break;
+ case vi:
+ this.set('tagName', 'iframe');
+ traits = this.getVimeoTraits();
+ break;
+ default:
+ this.set('tagName', 'video');
+ }
+ this.loadTraits(traits);
+ this.sm.trigger('change:selectedComponent');
+ },
+
+ // Listen provider change and switch traits, in TraitView listen traits change
+
+ /**
+ * Return the provider trait
+ * @return {Object}
+ * @private
+ */
+ getProviderTrait: function() {
+ return {
+ type: 'select',
+ label: 'Provider',
+ name: 'provider',
+ changeProp: 1,
+ value: this.get('provider'),
+ options: [
+ {value: 'so', name: 'HTML5 Source'},
+ {value: yt, name: 'Youtube'},
+ {value: vi, name: 'Vimeo'}
+ ]
+ };
+ },
+
+ /**
+ * Return traits for the source provider
+ * @return {Array
}
+ * @private
+ */
+ getSourceTraits: function() {
+ return [
+ this.getProviderTrait(), {
+ label: 'Source',
+ name: 'src',
+ placeholder: 'eg. ./media/video.mp4',
+ changeProp: 1,
+ },
+ this.getAutoplayTrait(),
+ this.getLoopTrait(),
+ this.getControlsTrait()];
+ },
+ /**
+ * Return traits for the source provider
+ * @return {Array}
+ * @private
+ */
+ getYoutubeTraits: function() {
+ return [
+ this.getProviderTrait(), {
+ label: 'Video ID',
+ name: 'videoId',
+ placeholder: 'eg. jNQXAC9IVRw',
+ changeProp: 1,
+ },
+ this.getAutoplayTrait(),
+ this.getLoopTrait(),
+ this.getControlsTrait()
+ ];
+ },
+
+ /**
+ * Return traits for the source provider
+ * @return {Array}
+ * @private
+ */
+ getVimeoTraits: function() {
+ return [
+ this.getProviderTrait(), {
+ label: 'Video ID',
+ name: 'videoId',
+ placeholder: 'eg. 123456789',
+ changeProp: 1,
+ },{
+ label: 'Color',
+ name: 'color',
+ placeholder: 'eg. FF0000',
+ changeProp: 1,
+ },
+ this.getAutoplayTrait(),
+ this.getLoopTrait(),
+ this.getControlsTrait()];
+ },
+
+ /**
+ * Return object trait
+ * @return {Object}
+ * @private
+ */
+ getAutoplayTrait: function(){
+ return {
+ type: 'checkbox',
+ label: 'Autoplay',
+ name: 'autoplay',
+ changeProp: 1,
+ };
+ },
+
+ /**
+ * Return object trait
+ * @return {Object}
+ * @private
+ */
+ getLoopTrait: function(){
+ return {
+ type: 'checkbox',
+ label: 'Loop',
+ name: 'loop',
+ changeProp: 1,
+ };
+ },
+
+ /**
+ * Return object trait
+ * @return {Object}
+ * @private
+ */
+ getControlsTrait: function(){
+ return {
+ type: 'checkbox',
+ label: 'Controls',
+ name: 'controls',
+ changeProp: 1,
+ };
+ },
+
+
+ /**
+ * Returns url to youtube video
+ * @return {string}
+ * @private
+ */
+ getYoutubeSrc: function() {
+ var url = this.get('ytUrl');
+ url += this.get('videoId') + '?';
+ url += this.get('autoplay') ? '&autoplay=1' : '';
+ url += !this.get('controls') ? '&controls=0' : '';
+ url += this.get('loop') ? '&loop=1' : '';
+ return url;
+ },
+
+ /**
+ * Returns url to vimeo video
+ * @return {string}
+ * @private
+ */
+ getVimeoSrc: function() {
+ var url = this.get('viUrl');
+ url += this.get('videoId') + '?';
+ url += this.get('autoplay') ? '&autoplay=1' : '';
+ url += this.get('loop') ? '&loop=1' : '';
+ url += !this.get('controls') ? '&title=0&portrait=0&badge=0' : '';
+ url += this.get('color') ? '&color=' + this.get('color') : '';
+ return url;
+ },
+
+ },{
+
+ /**
+ * Detect if the passed element is a valid component.
+ * In case the element is valid an object abstracted
+ * from the element will be returned
+ * @param {HTMLElement}
+ * @return {Object}
+ * @private
+ */
+ isComponent: function(el) {
+ var result = '';
+ var isYtProv = /youtube\.com\/embed/.test(el.src);
+ var isViProv = /player\.vimeo\.com\/video/.test(el.src);
+ var isExtProv = isYtProv || isViProv;
+ if(el.tagName == 'VIDEO' ||
+ (el.tagName == 'IFRAME' && isExtProv) ){
+ result = {type: 'video'};
+ if(el.src)
+ result.src = el.src;
+ if(isExtProv){
+ if(isYtProv)
+ result.provider = yt;
+ else if(isViProv)
+ result.provider = vi;
+ }
+ }
+ return result;
+ },
+
+ });
+});
diff --git a/src/dom_components/model/Components.js b/src/dom_components/model/Components.js
index 7e21ed205..a12ef5961 100644
--- a/src/dom_components/model/Components.js
+++ b/src/dom_components/model/Components.js
@@ -22,34 +22,22 @@ define([ 'backbone', 'require'],
if(opt && opt.config)
options.config = opt.config;
- switch(attrs.type){
-
- case 'text':
- if(!this.mComponentText)
- this.mComponentText = require("./ComponentText");
- model = new this.mComponentText(attrs, options);
- break;
-
- case 'link':
- if(!this.mComponentLink)
- this.mComponentLink = require("./ComponentLink");
- model = new this.mComponentLink(attrs, options);
- break;
-
- case 'image':
- if(!this.mComponentImage)
- this.mComponentImage = require("./ComponentImage");
- model = new this.mComponentImage(attrs, options);
- break;
-
- default:
- if(!this.mComponent)
- this.mComponent = require("./Component");
- model = new this.mComponent(attrs, options);
+ if(opt && opt.defaultTypes)
+ options.defaultTypes = opt.defaultTypes;
+ if(opt && opt.componentTypes)
+ options.componentTypes = opt.componentTypes;
+
+ var df = opt.defaultTypes;
+ var cf = opt.componentTypes;
+
+ if(df[attrs.type]){
+ model = df[attrs.type].model;
+ }else{
+ model = df.default.model;
}
- return model;
+ return new model(attrs, options);
};
},
diff --git a/src/dom_components/model/Toolbar.js b/src/dom_components/model/Toolbar.js
new file mode 100644
index 000000000..7d66f8f46
--- /dev/null
+++ b/src/dom_components/model/Toolbar.js
@@ -0,0 +1,6 @@
+define(['backbone','./ToolbarButton'],
+ function (Backbone, ToolbarButton) {
+
+ return Backbone.Collection.extend({model: ToolbarButton});
+
+});
diff --git a/src/dom_components/model/ToolbarButton.js b/src/dom_components/model/ToolbarButton.js
new file mode 100644
index 000000000..5df5bbea8
--- /dev/null
+++ b/src/dom_components/model/ToolbarButton.js
@@ -0,0 +1,13 @@
+define(['backbone'],
+ function(Backbone) {
+
+ return Backbone.Model.extend({
+
+ defaults: {
+ command: '',
+ attributes: {},
+ },
+
+
+ });
+ });
diff --git a/src/dom_components/view/ComponentImageView.js b/src/dom_components/view/ComponentImageView.js
index a22cba174..01882bb8a 100644
--- a/src/dom_components/view/ComponentImageView.js
+++ b/src/dom_components/view/ComponentImageView.js
@@ -3,16 +3,16 @@ define(['backbone', './ComponentView'],
return ComponentView.extend({
- tagName : 'img',
+ tagName: 'img',
- events : {
- 'dblclick' : 'openModal',
+ events: {
+ 'dblclick': 'openModal',
},
initialize: function(o){
ComponentView.prototype.initialize.apply(this, arguments);
- this.listenTo( this.model, 'change:src', this.updateSrc);
- this.listenTo( this.model, 'dblclick active', this.openModal);
+ this.listenTo(this.model, 'change:src', this.updateSrc);
+ this.listenTo(this.model, 'dblclick active', this.openModal);
this.classEmpty = this.ppfx + 'plh-image';
if(this.config.modal)
@@ -40,16 +40,17 @@ define(['backbone', './ComponentView'],
* @param {Object} e Event
* @private
* */
- openModal: function(e){
- var that = this;
- if(this.modal && this.am){
- this.modal.setTitle('Select image');
- this.modal.setContent(this.am.render(1));
- this.am.setTarget(this.model);
- this.modal.open();
- this.am.onSelect(function(){
- that.modal.close();
- that.am.setTarget(null);
+ openModal: function(e) {
+ var em = this.opts.config.em;
+ var editor = em ? em.get('Editor') : '';
+
+ if(editor) {
+ editor.runCommand('open-assets', {
+ target: this.model,
+ onSelect: function() {
+ editor.Modal.close();
+ editor.AssetManager.setTarget(null);
+ }
});
}
},
@@ -58,8 +59,9 @@ define(['backbone', './ComponentView'],
this.updateAttributes();
this.updateClasses();
+ var actCls = this.$el.attr('class') || '';
if(!this.model.get('src'))
- this.$el.attr('class', this.classEmpty);
+ this.$el.attr('class', (actCls + ' ' + this.classEmpty).trim());
// Avoid strange behaviours while try to drag
this.$el.attr('onmousedown', 'return false');
diff --git a/src/dom_components/view/ComponentLinkView.js b/src/dom_components/view/ComponentLinkView.js
index e5d607431..15bbb3770 100644
--- a/src/dom_components/view/ComponentLinkView.js
+++ b/src/dom_components/view/ComponentLinkView.js
@@ -1,15 +1,24 @@
-define(['backbone', './ComponentView'],
+define(['backbone', './ComponentTextView'],
function (Backbone, ComponentView) {
return ComponentView.extend({
events: {
'click': 'onClick',
+ 'dblclick': 'enableEditing',
},
onClick: function(e) {
e.preventDefault();
},
+ render: function() {
+ ComponentView.prototype.render.apply(this, arguments);
+
+ // Avoid strange behaviours while try to drag
+ //this.$el.attr('onmousedown', 'return false');
+ return this;
+ },
+
});
});
diff --git a/src/dom_components/view/ComponentMapView.js b/src/dom_components/view/ComponentMapView.js
new file mode 100644
index 000000000..8f7f53fb7
--- /dev/null
+++ b/src/dom_components/view/ComponentMapView.js
@@ -0,0 +1,44 @@
+define(['backbone', './ComponentImageView'],
+ function (Backbone, ComponentView) {
+
+ return ComponentView.extend({
+
+ tagName: 'div',
+
+ events: {},
+
+ initialize: function(o){
+ ComponentView.prototype.initialize.apply(this, arguments);
+ this.classEmpty = this.ppfx + 'plh-map';
+ },
+
+ /**
+ * Update the map on the canvas
+ * @private
+ */
+ updateSrc: function() {
+ this.getIframe().src = this.model.get('src');
+ },
+
+ getIframe: function() {
+ if(!this.iframe){
+ var ifrm = document.createElement("iframe");
+ ifrm.src = this.model.get('src');
+ ifrm.frameBorder = 0;
+ ifrm.style.height = '100%';
+ ifrm.style.width = '100%';
+ ifrm.className = this.ppfx + 'no-pointer';
+ this.iframe = ifrm;
+ }
+ return this.iframe;
+ },
+
+ render: function() {
+ ComponentView.prototype.render.apply(this, arguments);
+ this.updateClasses();
+ this.el.appendChild(this.getIframe());
+ return this;
+ },
+
+ });
+});
diff --git a/src/dom_components/view/ComponentTableCellView.js b/src/dom_components/view/ComponentTableCellView.js
new file mode 100644
index 000000000..5626864bd
--- /dev/null
+++ b/src/dom_components/view/ComponentTableCellView.js
@@ -0,0 +1,6 @@
+define(['backbone', './ComponentView'],
+ function (Backbone, ComponentView) {
+
+ return ComponentView.extend({
+ });
+});
diff --git a/src/dom_components/view/ComponentTableRowView.js b/src/dom_components/view/ComponentTableRowView.js
new file mode 100644
index 000000000..5626864bd
--- /dev/null
+++ b/src/dom_components/view/ComponentTableRowView.js
@@ -0,0 +1,6 @@
+define(['backbone', './ComponentView'],
+ function (Backbone, ComponentView) {
+
+ return ComponentView.extend({
+ });
+});
diff --git a/src/dom_components/view/ComponentTableView.js b/src/dom_components/view/ComponentTableView.js
new file mode 100644
index 000000000..75c7d9296
--- /dev/null
+++ b/src/dom_components/view/ComponentTableView.js
@@ -0,0 +1,9 @@
+define(['backbone', './ComponentView'],
+ function (Backbone, ComponentView) {
+
+ return ComponentView.extend({
+
+ events: {},
+
+ });
+});
diff --git a/src/dom_components/view/ComponentTextNodeView.js b/src/dom_components/view/ComponentTextNodeView.js
new file mode 100644
index 000000000..29abacbe2
--- /dev/null
+++ b/src/dom_components/view/ComponentTextNodeView.js
@@ -0,0 +1,5 @@
+define(['backbone'],
+ function (Backbone) {
+
+ return Backbone.View.extend({});
+});
diff --git a/src/dom_components/view/ComponentTextView.js b/src/dom_components/view/ComponentTextView.js
index 929e1b70f..017218cf0 100644
--- a/src/dom_components/view/ComponentTextView.js
+++ b/src/dom_components/view/ComponentTextView.js
@@ -4,7 +4,8 @@ define(['backbone', './ComponentView'],
return ComponentView.extend({
events: {
- 'dblclick' : 'enableEditing',
+ 'dblclick': 'enableEditing',
+ 'change': 'parseRender',
},
initialize: function(o){
@@ -14,6 +15,25 @@ define(['backbone', './ComponentView'],
this.rte = this.config.rte || '';
},
+ /**
+ * Parse content and re-render it
+ * @private
+ */
+ parseRender: function(){
+ var comps = this.model.get('components');
+ var opts = {silent: true};
+
+ // Avoid re-render on reset with silent option
+ comps.reset(null, opts);
+ comps.add(this.$el.html(), opts);
+ this.model.set('content', '');
+ this.render();
+
+ // As the reset was in silent mode I need to notify
+ // the navigator about the change
+ comps.trigger('resetNavigator');
+ },
+
/**
* Enable the component to be editable
* @param {Event} e
@@ -34,7 +54,7 @@ define(['backbone', './ComponentView'],
if(this.rte)
this.rte.detach(this);
this.toggleEvents();
- this.updateContents();
+ this.parseRender();
},
/**
@@ -46,32 +66,22 @@ define(['backbone', './ComponentView'],
e.stopPropagation();
},
- /**
- * Update contents of the element
- * @private
- **/
- updateContents: function(){
- this.model.set('content', this.el.innerHTML);
- },
-
/**
* Enable/Disable events
* @param {Boolean} enable
*/
toggleEvents: function(enable) {
var method = enable ? 'on' : 'off';
+
// The ownerDocument is from the frame
var elDocs = [this.el.ownerDocument, document, this.rte];
+ $(elDocs).off('mousedown', this.disableEditing);
$(elDocs)[method]('mousedown', this.disableEditing);
+
// Avoid closing edit mode on component click
+ this.$el.off('mousedown', this.disablePropagation);
this.$el[method]('mousedown', this.disablePropagation);
},
- render: function() {
- this.updateAttributes();
- this.updateClasses();
- this.el.innerHTML = this.model.get('content');
- return this;
- },
});
});
diff --git a/src/dom_components/view/ComponentVideoView.js b/src/dom_components/view/ComponentVideoView.js
new file mode 100644
index 000000000..7141b326f
--- /dev/null
+++ b/src/dom_components/view/ComponentVideoView.js
@@ -0,0 +1,119 @@
+define(['backbone', './ComponentImageView'],
+ function (Backbone, ComponentView) {
+
+ return ComponentView.extend({
+
+ tagName: 'div',
+
+ events: {},
+
+ initialize: function(o){
+ ComponentView.prototype.initialize.apply(this, arguments);
+ this.listenTo(this.model, 'change:loop change:autoplay change:controls change:color', this.updateVideo);
+ this.listenTo(this.model, 'change:provider', this.updateProvider);
+ },
+
+ /**
+ * Rerender on update of the provider
+ * @private
+ */
+ updateProvider: function() {
+ var prov = this.model.get('provider');
+ this.el.innerHTML = '';
+ this.el.appendChild(this.renderByProvider(prov));
+ },
+
+ /**
+ * Update the source of the video
+ * @private
+ */
+ updateSrc: function() {
+ var prov = this.model.get('provider');
+ var src = this.model.get('src');
+ switch (prov) {
+ case 'yt':
+ src = this.model.getYoutubeSrc();
+ break;
+ case 'vi':
+ src = this.model.getVimeoSrc();
+ break;
+ }
+ this.videoEl.src = src;
+ },
+
+ /**
+ * Update video parameters
+ * @private
+ */
+ updateVideo: function() {
+ var prov = this.model.get('provider');
+ var videoEl = this.videoEl;
+ var md = this.model;
+ switch (prov) {
+ case 'yt': case 'vi':
+ this.model.trigger('change:videoId');
+ break;
+ default:
+ videoEl.loop = md.get('loop');
+ videoEl.autoplay = md.get('autoplay');
+ videoEl.controls = md.get('controls');
+ }
+ },
+
+ renderByProvider: function(prov) {
+ var videoEl;
+ switch (prov) {
+ case 'yt':
+ videoEl = this.renderYoutube();
+ break;
+ case 'vi':
+ videoEl = this.renderVimeo();
+ break;
+ default:
+ videoEl = this.renderSource();
+ }
+ this.videoEl = videoEl;
+ return videoEl;
+ },
+
+ renderSource: function() {
+ var el = document.createElement('video');
+ el.src = this.model.get('src');
+ this.initVideoEl(el);
+ return el;
+ },
+
+ renderYoutube: function() {
+ var el = document.createElement('iframe');
+ el.src = this.model.getYoutubeSrc();
+ el.frameBorder = 0;
+ el.setAttribute('allowfullscreen', true);
+ this.initVideoEl(el);
+ return el;
+ },
+
+ renderVimeo: function() {
+ var el = document.createElement('iframe');
+ el.src = this.model.getVimeoSrc();
+ el.frameBorder = 0;
+ el.setAttribute('allowfullscreen', true);
+ this.initVideoEl(el);
+ return el;
+ },
+
+ initVideoEl: function(el){
+ el.className = this.ppfx + 'no-pointer';
+ el.style.height = '100%';
+ el.style.width = '100%';
+ },
+
+ render: function() {
+ ComponentView.prototype.render.apply(this, arguments);
+ this.updateClasses();
+ var prov = this.model.get('provider');
+ this.el.appendChild(this.renderByProvider(prov));
+ return this;
+ },
+
+ });
+});
diff --git a/src/dom_components/view/ComponentView.js b/src/dom_components/view/ComponentView.js
index 61ab586e7..c8d5444fb 100644
--- a/src/dom_components/view/ComponentView.js
+++ b/src/dom_components/view/ComponentView.js
@@ -12,19 +12,21 @@ define(['backbone', './ComponentsView'],
},
initialize: function(opt){
+ this.opts = opt || {};
this.config = opt.config || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.components = this.model.get('components');
this.attr = this.model.get("attributes");
this.classe = this.attr.class || [];
- this.listenTo(this.model, 'destroy remove', this.remove);
- this.listenTo(this.model, 'change:style', this.updateStyle);
+ this.listenTo(this.model, 'destroy remove', this.remove);
+ this.listenTo(this.model, 'change:style', this.updateStyle);
this.listenTo(this.model, 'change:attributes', this.updateAttributes);
- this.listenTo(this.model, 'change:status', this.updateStatus);
- this.listenTo(this.model, 'change:state', this.updateState);
+ this.listenTo(this.model, 'change:status', this.updateStatus);
+ this.listenTo(this.model, 'change:state', this.updateState);
this.listenTo(this.model.get('classes'), 'add remove change', this.updateClasses);
- this.$el.data("model", this.model);
+ this.$el.data('model', this.model);
+ this.model.set('view', this);
this.$el.data("collection", this.components);
if(this.model.get('classes').length)
@@ -178,8 +180,10 @@ define(['backbone', './ComponentsView'],
this.updateClasses();
this.$el.html(this.model.get('content'));
var view = new ComponentsView({
- collection: this.components,
+ collection: this.model.get('components'),
config: this.config,
+ defaultTypes: this.opts.defaultTypes,
+ componentTypes: this.opts.componentTypes,
});
// With childNodes lets avoid wrapping 'div'
diff --git a/src/dom_components/view/ComponentsView.js b/src/dom_components/view/ComponentsView.js
index fd748aee4..04386785f 100644
--- a/src/dom_components/view/ComponentsView.js
+++ b/src/dom_components/view/ComponentsView.js
@@ -4,7 +4,8 @@ function(Backbone, require) {
return Backbone.View.extend({
initialize: function(o) {
- this.config = o.config;
+ this.opts = o || {};
+ this.config = o.config;
this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render );
},
@@ -34,31 +35,24 @@ function(Backbone, require) {
if(!this.compView)
this.compView = require('./ComponentView');
var fragment = fragmentEl || null,
- viewObject = this.compView;
+ viewObject = this.compView;
+ //console.log('Add to collection', model, 'Index',i);
- switch(model.get('type')){
- case 'text':
- if(!this.compViewText)
- this.compViewText = require('./ComponentTextView');
- viewObject = this.compViewText;
- break;
- case 'image':
- if(!this.compViewImage)
- this.compViewImage = require('./ComponentImageView');
- viewObject = this.compViewImage;
- break;
- case 'link':
- if(!this.compViewLink)
- this.compViewLink = require('./ComponentLinkView');
- viewObject = this.compViewLink;
- break;
- }
+ var dt = this.opts.defaultTypes;
+ var ct = this.opts.componentTypes;
+
+ var type = model.get('type');
+ viewObject = dt[type] ? dt[type].view : dt.default.view;
- var view = new viewObject({
- model : model,
- config : this.config,
+ var view = new viewObject({
+ model: model,
+ config: this.config,
+ defaultTypes: dt,
+ componentTypes: ct,
});
var rendered = view.render().el;
+ if(view.model.get('type') == 'textnode')
+ rendered = document.createTextNode(view.model.get('content'));
if(fragment){
fragment.appendChild(rendered);
diff --git a/src/dom_components/view/ToolbarButtonView.js b/src/dom_components/view/ToolbarButtonView.js
new file mode 100644
index 000000000..c9ca1fdad
--- /dev/null
+++ b/src/dom_components/view/ToolbarButtonView.js
@@ -0,0 +1,37 @@
+define(['backbone'],
+ function (Backbone) {
+
+ return Backbone.View.extend({
+
+ events: {
+ 'mousedown': 'handleClick',
+ },
+
+ attributes: function () {
+ return this.model.get('attributes');
+ },
+
+ initialize: function(opts) {
+ this.editor = opts.config.editor;
+ },
+
+ handleClick: function() {
+ var command = this.model.get('command');
+
+ if (typeof command === 'function') {
+ command(this.editor);
+ }
+
+ if (typeof command === 'string') {
+ this.editor.runCommand(command);
+ }
+ },
+
+ render: function () {
+ var config = this.editor.getConfig();
+ this.el.className += ' ' + config.stylePrefix + 'toolbar-item';
+ return this;
+ },
+
+ });
+});
diff --git a/src/dom_components/view/ToolbarView.js b/src/dom_components/view/ToolbarView.js
new file mode 100644
index 000000000..c80ab4ab0
--- /dev/null
+++ b/src/dom_components/view/ToolbarView.js
@@ -0,0 +1,15 @@
+define(['backbone', 'Abstract/view/DomainViews', './ToolbarButtonView'],
+ function (Backbone, DomainViews, ToolbarButtonView) {
+
+ return DomainViews.extend({
+
+ itemView: ToolbarButtonView,
+
+ initialize: function(opts) {
+ this.config = {editor: opts.editor || ''};
+ this.listenTo(this.collection, 'reset', this.render);
+ },
+
+ });
+
+});
diff --git a/src/domain_abstract/ui/Input.js b/src/domain_abstract/ui/Input.js
new file mode 100644
index 000000000..70fd04fc6
--- /dev/null
+++ b/src/domain_abstract/ui/Input.js
@@ -0,0 +1,83 @@
+define(['backbone', 'text!./templates/input.html'],
+ function (Backbone, inputTemplate) {
+
+ return Backbone.View.extend({
+
+ events: {
+ 'change': 'handleChange',
+ },
+
+ template: _.template(inputTemplate),
+
+ initialize: function(opts) {
+ var opt = opts || {};
+ var ppfx = opt.ppfx || '';
+ this.target = opt.target || {};
+ this.inputClass = ppfx + 'field';
+ this.inputHolderClass = ppfx + 'input-holder';
+ this.ppfx = ppfx;
+ this.listenTo(this.model, 'change:value', this.handleModelChange);
+ },
+
+ /**
+ * Handled when the view is changed
+ */
+ handleChange: function (e) {
+ e.stopPropagation();
+ this.setValue(this.getInputEl().value);
+ },
+
+ /**
+ * Set value to the model
+ * @param {string} value
+ * @param {Object} opts
+ */
+ setValue: function(value, opts) {
+ var opt = opts || {};
+ var model = this.model;
+ model.set({
+ value: value || model.get('defaults')
+ }, opt);
+
+ // Generally I get silent when I need to reflect data to view without
+ // reupdating the target
+ if(opt.silent) {
+ this.handleModelChange();
+ }
+ },
+
+ /**
+ * Updates the view when the model is changed
+ * */
+ handleModelChange: function() {
+ this.getInputEl().value = this.model.get('value');
+ },
+
+ /**
+ * Get the input element
+ * @return {HTMLElement}
+ */
+ getInputEl: function() {
+ if(!this.inputEl) {
+ this.inputEl = $(' ', {
+ type: 'text',
+ class: this.inputCls,
+ placeholder: this.model.get('defaults')
+ });
+ }
+ return this.inputEl.get(0);
+ },
+
+ render: function() {
+ var el = this.$el;
+ el.addClass(this.inputClass);
+ el.html(this.template({
+ holderClass: this.inputHolderClass,
+ ppfx: this.ppfx
+ }));
+ el.find('.'+ this.inputHolderClass).html(this.getInputEl());
+ return this;
+ }
+
+ });
+});
diff --git a/src/domain_abstract/ui/InputColor.js b/src/domain_abstract/ui/InputColor.js
new file mode 100644
index 000000000..0e3306f8c
--- /dev/null
+++ b/src/domain_abstract/ui/InputColor.js
@@ -0,0 +1,74 @@
+define(['backbone', './Input', 'Spectrum', 'text!./templates/inputColor.html'],
+ function (Backbone, Input, Spectrum, inputTemplate) {
+
+ return Input.extend({
+
+ template: _.template(inputTemplate),
+
+ initialize: function(opts) {
+ Input.prototype.initialize.apply(this, arguments);
+ var ppfx = this.ppfx;
+ this.colorCls = ppfx + 'field-color-picker';
+ this.inputClass = ppfx + 'field ' + ppfx + 'field-color';
+ this.colorHolderClass = ppfx + 'field-colorp-c';
+
+ this.listenTo(this.model, 'change:value', this.handleModelChange);
+ },
+
+ /**
+ * Updates the view when the model is changed
+ * */
+ handleModelChange: function() {
+ Input.prototype.handleModelChange.apply(this, arguments);
+
+ var value = this.model.get('value');
+ var colorEl = this.getColorEl();
+
+ // If no color selected I will set white for the picker
+ value = value === 'none' ? '#fff' : value;
+ colorEl.spectrum('set', value);
+ colorEl.get(0).style.backgroundColor = value;
+ },
+
+ /**
+ * Get the color input element
+ * @return {HTMLElement}
+ */
+ getColorEl: function() {
+ if(!this.colorEl) {
+ var model = this.model;
+ var colorEl = $('', {class: this.colorCls});
+ var cpStyle = colorEl.get(0).style;
+ var elToAppend = this.target && this.target.config ? this.target.config.el : '';
+ colorEl.spectrum({
+ appendTo: elToAppend || 'body',
+ maxSelectionSize: 8,
+ showPalette: true,
+ showAlpha: true,
+ chooseText: 'Ok',
+ cancelText: '⨯',
+ palette: [],
+ move: function(color) {
+ var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
+ cpStyle.backgroundColor = c;
+ },
+ change: function(color) {
+ var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
+ c = c.replace(/ /g,'');
+ cpStyle.backgroundColor = c;
+ model.set('value', c);
+ }
+ });
+ this.colorEl = colorEl;
+ }
+ return this.colorEl;
+ },
+
+ render: function() {
+ Input.prototype.render.apply(this, arguments);
+ this.$el.find('.' + this.colorHolderClass).html(this.getColorEl());
+ return this;
+ }
+
+ });
+});
diff --git a/src/domain_abstract/ui/InputNumber.js b/src/domain_abstract/ui/InputNumber.js
new file mode 100644
index 000000000..2e7276b95
--- /dev/null
+++ b/src/domain_abstract/ui/InputNumber.js
@@ -0,0 +1,242 @@
+define(['backbone', 'text!./templates/inputNumber.html'],
+ function (Backbone, inputTemplate) {
+
+ return Backbone.View.extend({
+
+ events: {},
+
+ template: _.template(inputTemplate),
+
+ initialize: function(opts) {
+ _.bindAll(this, 'moveIncrement', 'upIncrement');
+ var ppfx = opts.ppfx || '';
+ this.ppfx = ppfx;
+ this.docEl = $(document);
+ this.inputCls = ppfx + 'input-number';
+ this.unitCls = ppfx + 'input-unit';
+ this.events['click .' + ppfx + 'field-arrow-u'] = 'upArrowClick';
+ this.events['click .' + ppfx + 'field-arrow-d'] = 'downArrowClick';
+ this.events['mousedown .' + ppfx + 'field-arrows'] = 'downIncrement';
+ this.events['change .' + this.inputCls] = 'handleChange';
+ this.events['change .' + this.unitCls] = 'handleUnitChange';
+
+ this.listenTo(this.model, 'change:unit change:value', this.handleModelChange);
+ this.delegateEvents();
+ },
+
+ /**
+ * Set value to the model
+ * @param {string} value
+ * @param {Object} opts
+ */
+ setValue: function(value, opts) {
+ var opt = opts || {};
+ var valid = this.validateInputValue(value, {deepCheck: 1});
+ var validObj = {value: valid.value};
+
+ // If found some unit value
+ if(valid.unit || valid.force) {
+ validObj.unit = valid.unit;
+ }
+
+ this.model.set(validObj, opt);
+
+ // Generally I get silent when I need to reflect data to view without
+ // reupdating the target
+ if(opt.silent) {
+ this.handleModelChange();
+ }
+ },
+
+ /**
+ * Handled when the view is changed
+ */
+ handleChange: function (e) {
+ e.stopPropagation();
+ this.setValue(this.getInputEl().value);
+ },
+
+ /**
+ * Handled when the view is changed
+ */
+ handleUnitChange: function (e) {
+ e.stopPropagation();
+ var value = this.getUnitEl().value;
+ this.model.set('unit', value);
+ },
+
+ /**
+ * Updates the view when the model is changed
+ * */
+ handleModelChange: function() {
+ var m = this.model;
+ this.getInputEl().value = m.get('value');
+ var unit = this.getUnitEl();
+
+ if (unit) {
+ unit.value = m.get('unit');
+ }
+ },
+
+ /**
+ * Get the input element
+ * @return {HTMLElement}
+ */
+ getInputEl: function() {
+ if(!this.inputEl) {
+ this.inputEl = $('
', {
+ type: 'text',
+ class: this.inputCls,
+ placeholder: this.model.get('defaults')
+ });
+ }
+ return this.inputEl.get(0);
+ },
+
+ /**
+ * Get the unit element
+ * @return {HTMLElement}
+ */
+ getUnitEl: function() {
+ if(!this.unitEl) {
+ var model = this.model;
+ var units = model.get('units') || [];
+ if(units.length){
+ var unitStr = '
';
+ _.each(units, function(unit){
+ var selected = unit == model.get('unit') ? 'selected': '';
+ unitStr += '' + unit + ' ';
+ });
+ unitStr += ' ';
+ this.unitEl = $(unitStr);
+ }
+ }
+ return this.unitEl && this.unitEl.get(0);
+ },
+
+ /**
+ * Invoked when the up arrow is clicked
+ * */
+ upArrowClick: function() {
+ var value = this.model.get('value');
+ value = isNaN(value) ? 1 : parseInt(value, 10) + 1;
+ var valid = this.validateInputValue(value);
+ this.model.set('value', valid.value);
+ },
+
+ /**
+ * Invoked when the down arrow is clicked
+ * */
+ downArrowClick: function(){
+ var value = this.model.get('value');
+ value = isNaN(value) ? 0 : parseInt(value, 10) - 1;
+ var valid = this.validateInputValue(value);
+ this.model.set('value', valid.value);
+ },
+
+ /**
+ * Change easily integer input value with click&drag method
+ * @param Event
+ *
+ * @return void
+ * */
+ downIncrement: function(e) {
+ e.preventDefault();
+ this.moved = 0;
+ var value = this.model.get('value');
+ value = isNaN(value) ? 0 : parseInt(value, 10);
+ var current = {y: e.pageY, val: value };
+ this.docEl.mouseup(current, this.upIncrement);
+ this.docEl.mousemove(current, this.moveIncrement);
+ },
+
+ /** While the increment is clicked, moving the mouse will update input value
+ * @param Object
+ *
+ * @return bool
+ * */
+ moveIncrement: function (ev) {
+ this.moved = 1;
+ var pos = parseInt(ev.data.val - ev.pageY + ev.data.y, 10);
+ this.prValue = this.validateInputValue(pos).value;//Math.max(this.min, Math.min(this.max, pos) );
+ this.model.set('value', this.prValue, {avoidStore: 1});
+ return false;
+ },
+
+ /**
+ * Stop moveIncrement method
+ * @param Object
+ *
+ * @return void
+ * */
+ upIncrement: function (e) {
+ this.docEl.off('mouseup', this.upIncrement);
+ this.docEl.off('mousemove', this.moveIncrement);
+
+ if(this.prValue && this.moved)
+ this.model.set('value', this.prValue - 1, {silent:1}).set('value', this.prValue + 1);
+ },
+
+ /**
+ * Validate input value
+ * @param {String} value Raw value
+ * @param {Object} opts Options
+ * @return {Object} Validated string
+ */
+ validateInputValue: function(value, opts) {
+ var force = 0;
+ var opt = opts || {};
+ var model = this.model;
+ var val = value || model.get('defaults');
+ var units = model.get('units') || [];
+ var unit = model.get('unit') || (units.length && units[0]) || '';
+ var max = model.get('max');
+ var min = model.get('min');
+
+ if(opt.deepCheck) {
+ var fixed = model.get('fixedValues') || [];
+
+ if (val) {
+ // If the value is one of the fixed values I leave it as it is
+ var regFixed = new RegExp('^' + fixed.join('|'), 'g');
+ if (fixed.length && regFixed.test(val)) {
+ val = val.match(regFixed)[0];
+ unit = '';
+ force = 1;
+ } else {
+ var valCopy = val + '';
+ val += ''; // Make it suitable for replace
+ val = parseFloat(val.replace(',', '.'));
+ val = !isNaN(val) ? val : model.get('defaults');
+ var uN = valCopy.replace(val, '');
+ // Check if exists as unit
+ if(_.indexOf(units, uN) >= 0)
+ unit = uN;
+ }
+ }
+ }
+
+ if(typeof max !== 'undefined')
+ val = val > max ? max : val;
+
+ if(typeof min !== 'undefined')
+ val = val < min ? min : val;
+
+ return {
+ force: force,
+ value: val,
+ unit: unit
+ };
+ },
+
+ render: function() {
+ var ppfx = this.ppfx;
+ this.$el.html(this.template({ppfx: ppfx}));
+ this.$el.find('.'+ ppfx +'input-holder').html(this.getInputEl());
+ this.$el.find('.' + ppfx + 'field-units').html(this.getUnitEl());
+ this.$el.addClass(ppfx + 'field');
+ return this;
+ }
+
+ });
+});
diff --git a/src/domain_abstract/ui/templates/input.html b/src/domain_abstract/ui/templates/input.html
new file mode 100644
index 000000000..03f3bd7d6
--- /dev/null
+++ b/src/domain_abstract/ui/templates/input.html
@@ -0,0 +1 @@
+
diff --git a/src/domain_abstract/ui/templates/inputColor.html b/src/domain_abstract/ui/templates/inputColor.html
new file mode 100644
index 000000000..7886de64f
--- /dev/null
+++ b/src/domain_abstract/ui/templates/inputColor.html
@@ -0,0 +1,6 @@
+
+
diff --git a/src/domain_abstract/ui/templates/inputNumber.html b/src/domain_abstract/ui/templates/inputNumber.html
new file mode 100644
index 000000000..405f3406c
--- /dev/null
+++ b/src/domain_abstract/ui/templates/inputNumber.html
@@ -0,0 +1,6 @@
+
+
+
diff --git a/src/domain_abstract/view/DomainViews.js b/src/domain_abstract/view/DomainViews.js
index 4ae5051f6..7c9032575 100644
--- a/src/domain_abstract/view/DomainViews.js
+++ b/src/domain_abstract/view/DomainViews.js
@@ -3,6 +3,7 @@ function(Backbone) {
return Backbone.View.extend({
+ // Default view
itemView: '',
// Defines the View per type
@@ -33,8 +34,8 @@ function(Backbone) {
add: function(model, fragment){
var frag = fragment || null;
var itemView = this.itemView;
- if(this.itemsView){
- var typeField = model.get(this.itemType);
+ var typeField = model.get(this.itemType);
+ if(this.itemsView && this.itemsView[typeField]){
itemView = this.itemsView[typeField];
}
var view = new itemView({
diff --git a/src/editor/config/config.js b/src/editor/config/config.js
index c1806cdc1..b4fc4db7e 100644
--- a/src/editor/config/config.js
+++ b/src/editor/config/config.js
@@ -26,9 +26,18 @@ define(function () {
// Default command
defaultCommand: 'select-comp',
+ // Show a toolbar when the component is selected
+ showToolbar: 0,
+
+ // Allow script tag importing
+ allowScripts: 0,
+
// If true render a select of available devices
showDevices: 1,
+ // When enabled, on device change media rules won't be created
+ devicePreviewMode: 0,
+
// Dom element
el: '',
@@ -38,9 +47,6 @@ define(function () {
//Configurations for Canvas
canvas: {},
- //Configurations for Style Manager
- styleManager: {},
-
//Configurations for Layers
layers: {},
@@ -88,6 +94,42 @@ define(function () {
}],
},
+ //Configurations for Style Manager
+ styleManager: {
+
+ sectors: [{
+ name: 'General',
+ open: false,
+ buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'],
+ },{
+ name: 'Dimension',
+ open: false,
+ buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
+ },{
+ name: 'Typography',
+ open: false,
+ buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-align', 'text-shadow'],
+ properties: [{
+ property: 'text-align',
+ list : [
+ {value: 'left', className: 'fa fa-align-left'},
+ {value: 'center', className: 'fa fa-align-center' },
+ {value: 'right', className: 'fa fa-align-right'},
+ {value: 'justify', className: 'fa fa-align-justify'}
+ ],
+ }]
+ },{
+ name: 'Decorations',
+ open: false,
+ buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'],
+ },{
+ name: 'Extra',
+ open: false,
+ buildProps: ['transition', 'perspective', 'transform'],
+ }],
+
+ },
+
//Configurations for Block Manager
blockManager: {
'blocks': [{
@@ -170,13 +212,56 @@ define(function () {
id: 'image',
label: 'Image',
attributes: {class:'gjs-fonts gjs-f-image'},
- content: { type:'image', activeOnRender: 1},
+ content: {
+ style: {color: 'black'},
+ type:'image',
+ activeOnRender: 1
+ },
},{
id: 'quo',
label: 'Quote',
content: '
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit ',
- attributes: {class:'gjs-fonts gjs-f-quo'}
- }],
+ attributes: {class:'fa fa-quote-right'}
+ },{
+ id: 'link',
+ label: 'Link',
+ attributes: {class:'fa fa-link'},
+ content: {
+ type:'link',
+ content:'Link',
+ style:{color: '#d983a6'}
+ },
+ },{
+ id: 'map',
+ label: 'Map',
+ attributes: {class:'fa fa-map-o'},
+ content: {
+ type: 'map',
+ style: {height: '350px'}
+ },
+ },{
+ id: 'video',
+ label: 'Video',
+ attributes: {class:'fa fa-youtube-play'},
+ content: {
+ type: 'video',
+ src: 'img/video2.webm',
+ style: {
+ height: '350px',
+ width: '615px',
+ }
+ },
+ }/*,{
+ id: 'table',
+ label: 'Table',
+ attributes: {class:'fa fa-table'},
+ content: {
+ type: 'table',
+ columns: 3,
+ rows: 5,
+ style: {height: '150px', width: '100%'}
+ },
+ }*/],
},
};
diff --git a/src/editor/main.js b/src/editor/main.js
index f13ae3d94..fd0746395 100644
--- a/src/editor/main.js
+++ b/src/editor/main.js
@@ -297,32 +297,38 @@ define(function (require){
* Execute command
* @param {string} id Command ID
* @param {Object} options Custom options
+ * @return {*} The return is defined by the command
* @example
* editor.runCommand('myCommand', {someValue: 1});
*/
runCommand: function(id, options) {
+ var result;
var command = em.get('Commands').get(id);
if(command){
- command.run(this, this, options);
+ result = command.run(this, this, options);
this.trigger('run:' + id);
}
+ return result;
},
/**
* Stop the command if stop method was provided
* @param {string} id Command ID
* @param {Object} options Custom options
+ * @return {*} The return is defined by the command
* @example
* editor.stopCommand('myCommand', {someValue: 1});
*/
stopCommand: function(id, options) {
+ var result;
var command = em.get('Commands').get(id);
if(command){
- command.stop(this, this, options);
+ result = command.stop(this, this, options);
this.trigger('stop:' + id);
}
+ return result;
},
/**
diff --git a/src/editor/model/Editor.js b/src/editor/model/Editor.js
index 883ea5e07..fcf1b7ed5 100644
--- a/src/editor/model/Editor.js
+++ b/src/editor/model/Editor.js
@@ -15,6 +15,7 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
changesCount: 0,
storables: [],
toLoad: [],
+ opened: {},
device: '',
},
diff --git a/src/grapesjs/main.js b/src/grapesjs/main.js
index 91c20a8dc..bec9ae1e4 100644
--- a/src/grapesjs/main.js
+++ b/src/grapesjs/main.js
@@ -12,6 +12,8 @@ define(function (require) {
return {
+ editors: editors,
+
plugins: plugins,
/**
@@ -76,4 +78,4 @@ define(function (require) {
}();
-});
\ No newline at end of file
+});
diff --git a/src/navigator/config/config.js b/src/navigator/config/config.js
index 42de4103f..a1cda5a2c 100644
--- a/src/navigator/config/config.js
+++ b/src/navigator/config/config.js
@@ -1,10 +1,11 @@
define(function () {
return {
- stylePrefix : 'nv-',
- sortable : true,
- hidable : true,
- containerId : 'navigator',
- itemClass : 'item',
- itemsClass : 'items',
+ stylePrefix: 'nv-',
+ sortable: 1,
+ hidable: 1,
+ hideTextnode: 1,
+ containerId: 'navigator',
+ itemClass: 'item',
+ itemsClass: 'items',
};
-});
\ No newline at end of file
+});
diff --git a/src/navigator/main.js b/src/navigator/main.js
index 267bee87c..1e1bde861 100644
--- a/src/navigator/main.js
+++ b/src/navigator/main.js
@@ -1,33 +1,29 @@
define(function(require) {
- /**
- * @class Navigator
- * @param {Object} Collection
- * @param {Object} Configurations
- * */
- function Navigator(collection, c)
- {
- var config = c,
- defaults = require('./config/config'),
- ItemsView = require('./view/ItemsView');
- // Set default options
+ function Navigator(collection, c) {
+ var config = c,
+ defaults = require('./config/config'),
+ ItemsView = require('./view/ItemsView');
+
+ // Set default options
for (var name in defaults) {
if (!(name in config))
config[name] = defaults[name];
}
- var obj = {
- collection : collection,
- config : config,
+ var obj = {
+ collection: collection,
+ config: config,
+ opened: c.opened || {}
};
// Check if sort is required
if(config.sortable){
- var ItemSort = require('./view/ItemSort');
- obj.sorter = new ItemSort({config : config});
+ var ItemSort = require('./view/ItemSort');
+ obj.sorter = new ItemSort({config : config});
}
- this.ItemsView = new ItemsView(obj);
+ this.ItemsView = new ItemsView(obj);
}
Navigator.prototype = {
@@ -37,4 +33,4 @@ define(function(require) {
};
return Navigator;
-});
\ No newline at end of file
+});
diff --git a/src/navigator/view/ItemView.js b/src/navigator/view/ItemView.js
index cbc1db82e..efc9c17a7 100644
--- a/src/navigator/view/ItemView.js
+++ b/src/navigator/view/ItemView.js
@@ -9,11 +9,11 @@ define(['backbone', 'text!./../template/item.html','require'],
template: _.template(ItemTemplate),
initialize: function(o){
- this.opt = o;
- this.config = o.config;
- this.em = o.config.em;
- this.sorter = o.sorter || {};
- this.pfx = this.config.stylePrefix;
+ this.opt = o;
+ this.config = o.config;
+ this.em = o.config.em;
+ this.sorter = o.sorter || {};
+ this.pfx = this.config.stylePrefix;
if(typeof this.model.get('open') == 'undefined')
this.model.set('open',false);
this.listenTo(this.model.components, 'remove add change reset', this.checkChildren);
@@ -40,12 +40,16 @@ define(['backbone', 'text!./../template/item.html','require'],
* @return void
* */
updateOpening: function (){
- if(this.model.get('open')){
+ var opened = this.opt.opened || {};
+ var model = this.model;
+ if(model.get('open')){
this.$el.addClass("open");
this.$caret.addClass('fa-chevron-down');
+ opened[model.cid] = model;
}else{
this.$el.removeClass("open");
this.$caret.removeClass('fa-chevron-down');
+ delete opened[model.cid];
}
},
@@ -161,7 +165,7 @@ define(['backbone', 'text!./../template/item.html','require'],
* @return void
* */
checkChildren: function(){
- var c = this.model.components.length,
+ var c = this.countChildren(this.model),
pfx = this.pfx,
tC = '> .' + pfx + 'title-c > .' + pfx + 'title';
if(!this.$counter)
@@ -176,24 +180,45 @@ define(['backbone', 'text!./../template/item.html','require'],
}
},
- render : function(){
- var pfx = this.pfx,
- vis = this.isVisible();
+ /**
+ * Count children inside model
+ * @param {Object} model
+ * @return {number}
+ * @private
+ */
+ countChildren: function(model){
+ var count = 0;
+ model.components.each(function(m){
+ var isCountable = this.opt.isCountable;
+ var hide = this.config.hideTextnode;
+ if(isCountable && !isCountable(m, hide))
+ return;
+ count++;
+ }, this);
+ return count;
+ },
+
+ render: function(){
+ var pfx = this.pfx;
+ var vis = this.isVisible();
+ var count = this.countChildren(this.model);
+
this.$el.html( this.template({
- title : this.model.getName(),
- addClass : (this.model.components.length ? '' : pfx+'no-chld'),
- count : this.model.components.length,
- visible : vis,
- hidable : this.config.hidable,
- prefix : pfx
+ title: this.model.getName(),
+ addClass: (count ? '' : pfx+'no-chld'),
+ count: count,
+ visible: vis,
+ hidable: this.config.hidable,
+ prefix: pfx
}));
if(typeof ItemsView == 'undefined')
ItemsView = require('./ItemsView');
- this.$components = new ItemsView({
+ this.$components = new ItemsView({
collection : this.model.components,
- config : this.config,
- sorter : this.sorter,
- parent : this.model
+ config: this.config,
+ sorter: this.sorter,
+ opened: this.opt.opened,
+ parent: this.model
}).render().$el;
this.$el.find('.'+ pfx +'children').html(this.$components);
this.$caret = this.$el.find('> .' + pfx + 'title-c > .' + pfx + 'title > #' + pfx + 'caret');
diff --git a/src/navigator/view/ItemsView.js b/src/navigator/view/ItemsView.js
index 09a15c7ce..51e8b391e 100644
--- a/src/navigator/view/ItemsView.js
+++ b/src/navigator/view/ItemsView.js
@@ -6,14 +6,14 @@ define(['backbone','./ItemView'],
return Backbone.View.extend({
initialize: function(o) {
- this.opt = o;
- this.config = o.config;
- this.preview = o.preview;
- this.sorter = o.sorter || {};
- this.pfx = o.config.stylePrefix;
- this.parent = o.parent;
- this.listenTo( this.collection, 'add', this.addTo );
- this.listenTo( this.collection, 'reset', this.render );
+ this.opt = o;
+ this.config = o.config;
+ this.preview = o.preview;
+ this.sorter = o.sorter || {};
+ this.pfx = o.config.stylePrefix;
+ this.parent = o.parent;
+ this.listenTo(this.collection, 'add', this.addTo);
+ this.listenTo(this.collection, 'reset resetNavigator', this.render);
this.className = this.pfx + 'items';
if(!this.parent)
@@ -43,10 +43,12 @@ define(['backbone','./ItemView'],
var fragment = fragmentEl || null;
var viewObject = ItemView;
- var view = new viewObject({
- model : model,
- config : this.config,
- sorter : this.sorter,
+ var view = new viewObject({
+ model: model,
+ config: this.config,
+ sorter: this.sorter,
+ isCountable: this.isCountable,
+ opened: this.opt.opened,
});
var rendered = view.render().el;
@@ -73,13 +75,29 @@ define(['backbone','./ItemView'],
return rendered;
},
+ /**
+ * Check if the model could be count by the navigator
+ * @param {Object} model
+ * @return {Boolean}
+ * @private
+ */
+ isCountable: function(model, hide) {
+ var type = model.get('type');
+ var tag = model.get('tagName');
+ if((type == 'textnode' || tag == 'br') && hide)
+ return false;
+ return true;
+ },
+
render: function() {
var fragment = document.createDocumentFragment();
this.$el.empty();
- this.collection.each(function(model){
+ this.collection.each(function(model) {
+ if(!this.isCountable(model, this.config.hideTextnode))
+ return;
this.addToCollection(model, fragment);
- },this);
+ }, this);
this.$el.append(fragment);
this.$el.attr('class', _.result(this, 'className'));
diff --git a/src/panels/main.js b/src/panels/main.js
index a9fb66f18..8aa8ad2f8 100644
--- a/src/panels/main.js
+++ b/src/panels/main.js
@@ -143,6 +143,22 @@ define(function(require) {
* attributes: { title: 'Some title'},
* active: false,
* });
+ * // It's also possible to pass the command as an object
+ * // with .run and .stop methods
+ * ...
+ * command: {
+ * run: function(editor) {
+ * ...
+ * },
+ * stop: function(editor) {
+ * ...
+ * }
+ * },
+ * // Or simply like a function which will be evaluated as a single .run command
+ * ...
+ * command: function(editor) {
+ * ...
+ * }
*/
addButton: function(panelId, button){
var pn = this.getPanel(panelId);
@@ -191,4 +207,4 @@ define(function(require) {
};
};
-});
\ No newline at end of file
+});
diff --git a/src/panels/view/ButtonView.js b/src/panels/view/ButtonView.js
index 2a363c4c2..eaecd02e1 100644
--- a/src/panels/view/ButtonView.js
+++ b/src/panels/view/ButtonView.js
@@ -209,8 +209,14 @@ function(Backbone, require) {
var command = null;
var editor = this.em && this.em.get ? this.em.get('Editor') : null;
var commandName = this.model.get('command');
- if(this.commands)
+
+ if (this.commands && typeof commandName === 'string') {
command = this.commands.get(commandName);
+ } else if (commandName !== null && typeof commandName === 'object') {
+ command = commandName;
+ } else if (typeof commandName === 'function') {
+ command = {run: commandName};
+ }
if(this.model.get('active')){
@@ -220,7 +226,7 @@ function(Backbone, require) {
if(this.parentM)
this.parentM.set('active', true, { silent: true }).trigger('checkActive');
- if(command){
+ if(command && command.run){
command.run(editor, this.model, this.model.get('options'));
editor.trigger('run:' + commandName);
}
@@ -232,7 +238,7 @@ function(Backbone, require) {
if(this.parentM)
this.parentM.set('active', false, { silent: true }).trigger('checkActive');
- if(command){
+ if(command && command.stop){
command.stop(editor, this.model, this.model.get('options'));
editor.trigger('stop:' + commandName);
}
@@ -315,4 +321,4 @@ function(Backbone, require) {
},
});
-});
\ No newline at end of file
+});
diff --git a/src/parser/config/config.js b/src/parser/config/config.js
index 6f7697c02..980cba95d 100644
--- a/src/parser/config/config.js
+++ b/src/parser/config/config.js
@@ -4,4 +4,4 @@ define(function () {
textTags: ['br', 'b', 'i', 'u'],
};
-});
\ No newline at end of file
+});
diff --git a/src/parser/main.js b/src/parser/main.js
index 42ef7e7f2..a410d9738 100644
--- a/src/parser/main.js
+++ b/src/parser/main.js
@@ -9,6 +9,8 @@ define(function(require) {
return {
+ compTypes: '',
+
/**
* Name of the module
* @type {String}
@@ -48,6 +50,7 @@ define(function(require) {
* @return {Object}
*/
parseHtml: function(str){
+ pHtml.compTypes = this.compTypes;
return pHtml.parse(str, pCss);
},
@@ -60,4 +63,4 @@ define(function(require) {
return Parser;
-});
\ No newline at end of file
+});
diff --git a/src/parser/model/ParserCss.js b/src/parser/model/ParserCss.js
index 836b8ca85..d48f28d7e 100644
--- a/src/parser/model/ParserCss.js
+++ b/src/parser/model/ParserCss.js
@@ -119,4 +119,4 @@ define(function(require) {
};
-});
\ No newline at end of file
+});
diff --git a/src/parser/model/ParserHtml.js b/src/parser/model/ParserHtml.js
index 8167701bb..3c9b9958e 100644
--- a/src/parser/model/ParserHtml.js
+++ b/src/parser/model/ParserHtml.js
@@ -7,6 +7,8 @@ define(function(require) {
return {
+ compTypes: '',
+
/**
* Parse style string to object
* @param {string} str
@@ -43,7 +45,8 @@ define(function(require) {
var cls = str.split(' ');
for (var i = 0, len = cls.length; i < len; i++) {
var cl = cls[i].trim();
- if(!cl)
+ var reg = new RegExp('^' + c.pStylePrefix);
+ if(!cl || reg.test(cl))
continue;
result.push(cl);
}
@@ -66,7 +69,21 @@ define(function(require) {
var attrsLen = attrs.length;
var prevI = result.length - 1;
var prevSib = result[prevI];
- model.tagName = node.tagName ? node.tagName.toLowerCase() : '';
+ var ct = this.compTypes;
+
+ if(ct){
+ var obj = '';
+ for (var cType in ct) {
+ var component = ct[cType].model;
+ obj = component.isComponent(node);
+ if(obj)
+ break;
+ }
+ model = obj;
+ }
+
+ if(!model.tagName)
+ model.tagName = node.tagName ? node.tagName.toLowerCase() : '';
if(attrsLen)
model.attributes = {};
@@ -76,15 +93,14 @@ define(function(require) {
var nodeName = attrs[j].nodeName;
var nodeValue = attrs[j].nodeValue;
- //Isolate style, class and src attributes
+ //Isolate few attributes
if(nodeName == 'style')
model.style = this.parseStyle(nodeValue);
else if(nodeName == 'class')
model.classes = this.parseClass(nodeValue);
- else if(nodeName == 'src' && model.tagName == 'img'){
- model.type = 'image';
- model.src = nodeValue;
- }else
+ else if (nodeName == 'contenteditable')
+ continue;
+ else
model.attributes[nodeName] = nodeValue;
}
@@ -94,15 +110,16 @@ define(function(require) {
// Avoid infinite text nodes nesting
var firstChild = node.childNodes[0];
if(nodeChild === 1 && firstChild.nodeType === 3){
- model.type = 'text';
+ if(!model.type){
+ model.type = 'text';
+ }
model.content = firstChild.nodeValue;
}else{
var parsed = this.parseNode(node);
// From:
TEST
<-- span is text type
// TO:
TEST
<-- div become text type
- // With 'nodeChild > 1' I know that nodes were merged
if(parsed.length == 1 && parsed[0].type == 'text' &&
- nodeChild > 1 && parsed[0].tagName == TEXT_NODE){
+ parsed[0].tagName == TEXT_NODE){
model.type = 'text';
model.content = parsed[0].content;
}else
@@ -110,38 +127,43 @@ define(function(require) {
}
}
- var prevIsText = prevSib && prevSib.type == 'text' && prevSib.tagName == TEXT_NODE;
- // Find text nodes
- if(!model.tagName && node.nodeType === 3){
- // Pass content to the previous model if it's a text node
+ // Check if it's a text node and if could be moved to the prevous model
+ if(model.type == 'textnode'){
+ var prevIsText = prevSib && prevSib.type == 'textnode';
if(prevIsText){
- prevSib.content += node.nodeValue;
+ prevSib.content += model.content;
continue;
}
- // Make it text node only the content is not empty
- if(node.nodeValue.trim()){
- model.type = 'text';
- model.tagName = TEXT_NODE;
- model.content = node.nodeValue;
+ // Throw away empty nodes (keep spaces)
+ var content = node.nodeValue;
+ if(content != ' ' && !content.trim()){
+ continue;
}
}
- // Check if it's a text node and if it could be moved to the prevous model
- if(c.textTags.indexOf(model.tagName) >= 0){
- if(prevIsText){
- prevSib.content += node.outerHTML;
- continue;
- }else{
- model = {
- type: 'text',
- tagName: TEXT_NODE,
- content: node.outerHTML,
- };
+ // If all children are texts and there is some textnode the parent should
+ // be text too otherwise I'm unable to edit texnodes
+ var comps = model.components;
+ if(!model.type && comps){
+ var allTxt = 1;
+ var foundTextNode = 0;
+ for(var ci = 0; ci < comps.length; ci++){
+ var comp = comps[ci];
+ if(comp.type != 'text' &&
+ comp.type != 'textnode' &&
+ c.textTags.indexOf(comp.tagName) < 0 ){
+ allTxt = 0;
+ break;
+ }
+ if(comp.type == 'textnode')
+ foundTextNode = 1;
}
+ if(allTxt && foundTextNode)
+ model.type = 'text';
}
- // If tagName is still empty do not push it
- if(!model.tagName)
+ // If tagName is still empty and is not a textnode, do not push it
+ if(!model.tagName && model.type != 'textnode')
continue;
result.push(model);
@@ -157,6 +179,7 @@ define(function(require) {
* @return {Object}
*/
parse: function(str, parserCss){
+ var config = (c.em && c.em.get('Config')) || {};
var res = { html: '', css: ''};
var el = document.createElement('div');
el.innerHTML = str;
@@ -164,8 +187,10 @@ define(function(require) {
var i = scripts.length;
// Remove all scripts
- while (i--)
- scripts[i].parentNode.removeChild(scripts[i]);
+ if(!config.allowScripts){
+ while (i--)
+ scripts[i].parentNode.removeChild(scripts[i]);
+ }
// Detach style tags and parse them
if(parserCss){
@@ -197,4 +222,4 @@ define(function(require) {
};
-});
\ No newline at end of file
+});
diff --git a/src/rich_text_editor/config/config.js b/src/rich_text_editor/config/config.js
index 8bf8a5821..b4b552da9 100644
--- a/src/rich_text_editor/config/config.js
+++ b/src/rich_text_editor/config/config.js
@@ -4,24 +4,29 @@ define(function () {
toolbarId : 'toolbar',
containerId : 'wrapper',
commands : [{
- command: 'bold',
- title: 'Bold',
- class: 'fa fa-bold',
+ command: 'bold',
+ title: 'Bold',
+ class: 'fa fa-bold',
},{
- command: 'italic',
- title: 'Italic',
- class: 'fa fa-italic',
+ command: 'italic',
+ title: 'Italic',
+ class: 'fa fa-italic',
},{
- command: 'underline',
- title: 'Underline',
- class: 'fa fa-underline',
+ command: 'underline',
+ title: 'Underline',
+ class: 'fa fa-underline',
},{
- command: 'strikethrough',
- title: 'Strikethrough',
- class: 'fa fa-strikethrough',
- group: 'format'
+ command: 'strikethrough',
+ title: 'Strikethrough',
+ class: 'fa fa-strikethrough',
+ group: 'format'
+ },{
+ command: 'insertHTML',
+ title: 'Link',
+ class: 'fa fa-link',
+ args: '
${content} ',
}/*,{
- command: 'fontSize',
+ command: 'fontSize',
options: [
{name: 'Huge', value: '7'},
{name: 'Normal', value: '5'},
@@ -29,4 +34,4 @@ define(function () {
]
}*/],
};
-});
\ No newline at end of file
+});
diff --git a/src/rich_text_editor/view/CommandButtonsView.js b/src/rich_text_editor/view/CommandButtonsView.js
index a42f381c1..9c992ac3d 100644
--- a/src/rich_text_editor/view/CommandButtonsView.js
+++ b/src/rich_text_editor/view/CommandButtonsView.js
@@ -38,13 +38,16 @@ define(['backbone','./CommandButtonView', './CommandButtonSelectView'],
viewObj = CommandButtonSelectView;
break;
}
-
+ var args = model.get('args');
+ var attrs = {
+ 'title': model.get('title'),
+ 'data-edit': model.get('command'),
+ };
+ if(args)
+ attrs['data-args'] = args;
var view = new viewObj({
model: model,
- attributes: {
- 'title': model.get('title'),
- 'data-edit': model.get('command'),
- },
+ attributes: attrs,
}, this.config);
var rendered = view.render().el;
diff --git a/src/rich_text_editor/view/TextEditorView.js b/src/rich_text_editor/view/TextEditorView.js
index 50bfa6461..58b79a2dd 100644
--- a/src/rich_text_editor/view/TextEditorView.js
+++ b/src/rich_text_editor/view/TextEditorView.js
@@ -45,6 +45,7 @@ define(['jquery'],
editor.get(0).ownerDocument.execCommand("styleWithCSS", false, true);
editor.get(0).ownerDocument.execCommand(command, 0, args);
updateToolbar();
+ editor.trigger('change');
},
/*
bindHotkeys = function (hotKeys) {
@@ -112,7 +113,16 @@ define(['jquery'],
toolbar.find(toolbarBtnSelector).unbind().click(function () {
restoreSelection();
//editor.focus(); // cause defocus on selects
- editor.get(0).ownerDocument.execCommand($(this).data(options.commandRole));
+ var doc = editor.get(0).ownerDocument;
+ var el = $(this);
+ var comm = el.data(options.commandRole);
+ var args = el.data('args');
+ if(args){
+ args = args.replace('${content}', doc.getSelection());
+ execCommand(comm, args);
+ }else{
+ doc.execCommand(comm);
+ }
saveSelection();
});
toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
@@ -124,11 +134,9 @@ define(['jquery'],
editor.focus();
execCommand($(this).data(options.commandRole), newValue);
}
- console.log('change isolated2 ', newValue);
saveSelection();
});
toolbar.find('input[type=text]'+dName,', select'+dName).on('webkitspeechchange change', function () {
- console.log('on changed ', newValue);
var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
this.value = '';
restoreSelection();
@@ -138,10 +146,8 @@ define(['jquery'],
}
saveSelection();
}).on('focus', function () {
- console.log('on focus ');
var input = $(this);
if (!input.data(options.selectionMarker)) {
- console.log('i have no ', options.selectionMarker);
markSelection(input, options.selectionColor);
input.focus();
}
@@ -228,4 +234,3 @@ define(['jquery'],
return $;
});
-
diff --git a/src/selector_manager/template/classTags.html b/src/selector_manager/template/classTags.html
index 32e0c7bb0..7905f16d2 100644
--- a/src/selector_manager/template/classTags.html
+++ b/src/selector_manager/template/classTags.html
@@ -2,20 +2,20 @@
<%= label %>
-
-
+
diff --git a/src/selector_manager/view/ClassTagView.js b/src/selector_manager/view/ClassTagView.js
index fe204f50e..830682e37 100644
--- a/src/selector_manager/view/ClassTagView.js
+++ b/src/selector_manager/view/ClassTagView.js
@@ -109,7 +109,7 @@ define(['backbone', 'text!./../template/classTag.html'],
updateInputLabel: function(){
if(!this.$labelInput)
this.$labelInput = this.$el.find('input');
- var size = this.$labelInput.val().length - 2;
+ var size = this.$labelInput.val().length - 1;
size = size < 1 ? 1 : size;
this.$labelInput.attr('size', size);
},
diff --git a/src/style_manager/config/config.js b/src/style_manager/config/config.js
index fb9458bde..95aa96aa4 100644
--- a/src/style_manager/config/config.js
+++ b/src/style_manager/config/config.js
@@ -1,55 +1,13 @@
define(function () {
- return {
- stylePrefix: 'sm-',
+ return {
- // Default sectors, which could include also properties
- //
- // Example:
- // sectors: [{
- // name: 'Some sector name',
- // properties:[{
- // name : 'Width',
- // property : 'width',
- // type : 'integer',
- // units : ['px','%'],
- // defaults : 'auto',
- // min : 0,
- // }],
- // }]
- sectors: [{
- name: 'General',
- open: false,
- buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'],
- },{
- name: 'Dimension',
- open: false,
- buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
- },{
- name: 'Typography',
- open: false,
- buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-align', 'text-shadow'],
- properties: [{
- property: 'text-align',
- list : [
- {value: 'left', className: 'fa fa-align-left'},
- {value: 'center', className: 'fa fa-align-center' },
- {value: 'right', className: 'fa fa-align-right'},
- {value: 'justify', className: 'fa fa-align-justify'}
- ],
- }]
- },{
- name: 'Decorations',
- open: false,
- buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'],
- },{
- name: 'Extra',
- open: false,
- buildProps: ['transition', 'perspective', 'transform'],
- }],
+ stylePrefix: 'sm-',
- // Text to show in case no element selected
- textNoElement: 'Select an element before using Style Manager',
+ sectors: [],
+ // Text to show in case no element selected
+ textNoElement: 'Select an element before using Style Manager',
+
};
-});
\ No newline at end of file
+});
diff --git a/src/style_manager/model/Property.js b/src/style_manager/model/Property.js
index 161d0c094..355d13859 100644
--- a/src/style_manager/model/Property.js
+++ b/src/style_manager/model/Property.js
@@ -4,7 +4,7 @@ define(['backbone', './Layers', 'require'],
return Backbone.Model.extend({
defaults: {
- name : '',
+ name: '',
property: '',
type: '',
units: [],
@@ -15,10 +15,11 @@ define(['backbone', './Layers', 'require'],
icon: '',
preview: false,
detached: false,
- functionName: '',
+ functionName: '',
properties: [],
layers: [],
list: [],
+ fixedValues: ['initial', 'inherit'],
},
initialize: function(opt) {
@@ -48,7 +49,7 @@ define(['backbone', './Layers', 'require'],
* @return {string} Value
* @private
*/
- getValue: function(){
+ getValue: function() {
var result = '';
var type = this.get('type');
diff --git a/src/style_manager/model/PropertyFactory.js b/src/style_manager/model/PropertyFactory.js
index 378eaa4d9..5b295e5bf 100644
--- a/src/style_manager/model/PropertyFactory.js
+++ b/src/style_manager/model/PropertyFactory.js
@@ -11,6 +11,7 @@ define(['backbone'],
*/
build: function(props){
var objs = [];
+ var dftFixedValues = ['initial', 'inherit'];
if(typeof props === 'string')
props = [props];
@@ -27,6 +28,16 @@ define(['backbone'],
break;
}
+ // Fixed values
+ switch(prop){
+ case 'margin-top': case 'margin-right': case 'margin-bottom': case 'margin-left':
+ case 'padding-top': case 'padding-right': case 'padding-bottom': case 'padding-left':
+ case 'width': case 'max-width': case 'min-width':
+ case 'height': case 'max-height': case 'min-height':
+ obj.fixedValues = ['initial', 'inherit', 'auto'];
+ break;
+ }
+
// Type
switch(prop){
case 'float': case 'position':
@@ -448,4 +459,4 @@ define(['backbone'],
};
};
-});
\ No newline at end of file
+});
diff --git a/src/style_manager/templates/propertyColor.html b/src/style_manager/templates/propertyColor.html
index af08e44e0..bccdeae08 100644
--- a/src/style_manager/templates/propertyColor.html
+++ b/src/style_manager/templates/propertyColor.html
@@ -1,7 +1,9 @@
-
-
-
-
+
-
\ No newline at end of file
+
diff --git a/src/style_manager/templates/propertyInput.html b/src/style_manager/templates/propertyInput.html
index b07ba910f..7277ecdaf 100644
--- a/src/style_manager/templates/propertyInput.html
+++ b/src/style_manager/templates/propertyInput.html
@@ -1,4 +1,4 @@
-