Browse Source

Merge remote-tracking branch 'upstream/dev' into dev

pull/636/head
gabri.gcl 9 years ago
parent
commit
2612f22210
  1. 3
      .npmignore
  2. 22
      README.md
  3. 2
      dist/css/grapes.min.css
  4. 1721
      dist/grapes.js
  5. 6
      dist/grapes.min.js
  6. 1422
      index.html
  7. 2
      package-lock.json
  8. 2
      package.json
  9. 4
      src/asset_manager/config/config.js
  10. 10
      src/canvas/view/FrameView.js
  11. 4
      src/code_manager/model/CodeMirrorEditor.js
  12. 8
      src/code_manager/model/CssGenerator.js
  13. 3
      src/commands/index.js
  14. 97
      src/commands/view/ExportTemplate.js
  15. 14
      src/commands/view/SelectComponent.js
  16. 70
      src/css_composer/index.js
  17. 2
      src/css_composer/view/CssRuleView.js
  18. 27
      src/css_composer/view/CssRulesView.js
  19. 9
      src/device_manager/index.js
  20. 3
      src/device_manager/model/Device.js
  21. 1
      src/dom_components/config/config.js
  22. 15
      src/dom_components/index.js
  23. 12
      src/dom_components/model/Component.js
  24. 84
      src/dom_components/model/ComponentTable.js
  25. 58
      src/dom_components/model/ComponentTableBody.js
  26. 25
      src/dom_components/model/ComponentTableCell.js
  27. 21
      src/dom_components/model/ComponentTableFoot.js
  28. 21
      src/dom_components/model/ComponentTableHead.js
  29. 37
      src/dom_components/model/ComponentTableRow.js
  30. 4
      src/dom_components/view/ComponentTableBodyView.js
  31. 4
      src/dom_components/view/ComponentTableFootView.js
  32. 4
      src/dom_components/view/ComponentTableHeadView.js
  33. 2
      src/dom_components/view/ComponentTextView.js
  34. 14
      src/domain_abstract/ui/InputColor.js
  35. 6
      src/editor/config/config.js
  36. 22
      src/editor/index.js
  37. 10
      src/editor/model/Editor.js
  38. 8
      src/panels/view/ButtonView.js
  39. 2
      src/parser/config/config.js
  40. 150
      src/parser/model/ParserHtml.js
  41. 40
      src/style_manager/index.js
  42. 6
      src/style_manager/view/PropertyView.js
  43. 1
      src/style_manager/view/SectorView.js
  44. 33
      src/styles/scss/_gjs_canvas.scss
  45. 20
      src/styles/scss/_gjs_devices.scss
  46. 70
      src/styles/scss/_gjs_modal.scss
  47. 3
      src/styles/scss/_gjs_variables.scss
  48. 100
      src/styles/scss/main.scss
  49. 4
      src/trait_manager/view/TraitView.js
  50. 47
      src/utils/Resizer.js
  51. 33
      src/utils/Sorter.js
  52. 14
      src/utils/mixins.js
  53. 18
      test/specs/css_composer/index.js
  54. 8
      test/specs/parser/model/ParserHtml.js

3
.npmignore

@ -1,3 +0,0 @@
test
index.html
webpack.config.js

22
README.md

@ -6,11 +6,6 @@
<p align="center"><img src="http://grapesjs.com/img/grapesjs-front-page-m.jpg" alt="GrapesJS" width="500" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/grapesjs-front-page-m.jpg" alt="GrapesJS" width="500" align="center"/></p>
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/nb9nPYf2XqCE1PbgcQj6gf4D/artf/grapesjs'>
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/nb9nPYf2XqCE1PbgcQj6gf4D/artf/grapesjs.svg' />
</a>
GrapesJS is a free and open source Web Builder Framework which helps you building HTML templates to be used inside sites, newsletters and mobile apps. GrapesJS is a free and open source Web Builder Framework which helps you building HTML templates to be used inside sites, newsletters and mobile apps.
Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of dynamic templates. To better understand this concept check the image below Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of dynamic templates. To better understand this concept check the image below
@ -191,6 +186,7 @@ $ npm test
* [grapesjs-navbar](https://github.com/artf/grapesjs-navbar) - Simple navbar component * [grapesjs-navbar](https://github.com/artf/grapesjs-navbar) - Simple navbar component
* [grapesjs-component-countdown](https://github.com/artf/grapesjs-component-countdown) - Simple countdown component * [grapesjs-component-countdown](https://github.com/artf/grapesjs-component-countdown) - Simple countdown component
* [grapesjs-style-gradient](https://github.com/artf/grapesjs-style-gradient) - Add a gradient type input * [grapesjs-style-gradient](https://github.com/artf/grapesjs-style-gradient) - Add a gradient type input
* [grapesjs-blocks-flexbox](https://github.com/artf/grapesjs-blocks-flexbox) - Add the flexbox block
### Presets ### Presets
* [grapesjs-preset-webpage](https://github.com/artf/grapesjs-preset-webpage) - Webpage Builder * [grapesjs-preset-webpage](https://github.com/artf/grapesjs-preset-webpage) - Webpage Builder
@ -210,15 +206,15 @@ If you like the project support it with a donation of your choice or become a ba
[![PayPalMe](http://grapesjs.com/img/ppme.png)](https://paypal.me/grapesjs) [![PayPalMe](http://grapesjs.com/img/ppme.png)](https://paypal.me/grapesjs)
<a href="https://opencollective.com/grapesjs/sponsors/0/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/0/avatar"></a> <a href="https://opencollective.com/grapesjs/sponsors/0/website"><img src="https://opencollective.com/grapesjs/sponsors/0/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/1/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/1/avatar"></a> <a href="https://opencollective.com/grapesjs/sponsors/1/website"><img src="https://opencollective.com/grapesjs/sponsors/1/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/2/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/2/avatar"></a> <a href="https://opencollective.com/grapesjs/sponsors/2/website"><img src="https://opencollective.com/grapesjs/sponsors/2/avatar"></a>
<a href="https://opencollective.com/grapesjs/sponsors/3/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/3/avatar"></a> <a href="https://opencollective.com/grapesjs/sponsors/3/website"><img src="https://opencollective.com/grapesjs/sponsors/3/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/0/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/0/avatar"></a> <a href="https://opencollective.com/grapesjs/backers/0/website"><img src="https://opencollective.com/grapesjs/backers/0/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/1/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/1/avatar"></a> <a href="https://opencollective.com/grapesjs/backers/1/website"><img src="https://opencollective.com/grapesjs/backers/1/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/2/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/2/avatar"></a> <a href="https://opencollective.com/grapesjs/backers/2/website"><img src="https://opencollective.com/grapesjs/backers/2/avatar"></a>
<a href="https://opencollective.com/grapesjs/backers/3/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/3/avatar"></a> <a href="https://opencollective.com/grapesjs/backers/3/website"><img src="https://opencollective.com/grapesjs/backers/3/avatar"></a>

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

1721
dist/grapes.js

File diff suppressed because it is too large

6
dist/grapes.min.js

File diff suppressed because one or more lines are too long

1422
index.html

File diff suppressed because it is too large

2
package-lock.json

@ -1,6 +1,6 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"version": "0.12.45", "version": "0.12.52",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

2
package.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Free and Open Source Web Builder Framework", "description": "Free and Open Source Web Builder Framework",
"version": "0.12.45", "version": "0.12.52",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",

4
src/asset_manager/config/config.js

@ -66,7 +66,9 @@ module.exports = {
// Enable an upload dropzone on the entire editor (not document) when dragging // Enable an upload dropzone on the entire editor (not document) when dragging
// files over it // files over it
dropzone: 1, // If active the dropzone disable/hide the upload dropzone in asset modal,
// otherwise you will get double drops (#507)
dropzone: 0,
// Open the asset manager once files are been dropped via the dropzone // Open the asset manager once files are been dropped via the dropzone
openAssetsOnDrop: 1, openAssetsOnDrop: 1,

10
src/canvas/view/FrameView.js

@ -16,17 +16,19 @@ module.exports = require('backbone').View.extend({
this.config = o.config || {}; this.config = o.config || {};
this.ppfx = this.config.pStylePrefix || ''; this.ppfx = this.config.pStylePrefix || '';
this.em = this.config.em; this.em = this.config.em;
this.listenTo(this.em, 'change:device', this.updateWidth); this.listenTo(this.em, 'change:device', this.updateDim);
}, },
/** /**
* Update width of the frame * Update dimensions of the frame
* @private * @private
*/ */
updateWidth(model) { updateDim(model) {
const em = this.em; const em = this.em;
const device = em.getDeviceModel(); const device = em.getDeviceModel();
this.el.style.width = device ? device.get('width') : ''; const style = this.el.style;
style.width = device ? device.get('width') : '';
style.height = device ? device.get('height') : '';
this.udpateOffset(); this.udpateOffset();
em.stopDefault({ preserveSelected: 1 }); em.stopDefault({ preserveSelected: 1 });
this.$el.on(motionsEv, this.udpateOffset); this.$el.on(motionsEv, this.udpateOffset);

4
src/code_manager/model/CodeMirrorEditor.js

@ -20,10 +20,8 @@ module.exports = Backbone.Model.extend({
this.editor = CodeMirror.fromTextArea(el, { this.editor = CodeMirror.fromTextArea(el, {
dragDrop: false, dragDrop: false,
lineWrapping: true, lineWrapping: true,
lineNumbers: this.get('lineNumbers'),
readOnly: this.get('readOnly'),
mode: this.get('codeName'), mode: this.get('codeName'),
theme: this.get('theme'), ...this.attributes,
}); });
return this; return this;

8
src/code_manager/model/CssGenerator.js

@ -1,5 +1,3 @@
import { isEmpty } from 'underscore';
module.exports = require('backbone').Model.extend({ module.exports = require('backbone').Model.extend({
initialize() { initialize() {
@ -16,7 +14,7 @@ module.exports = require('backbone').Model.extend({
let code = ''; let code = '';
const em = this.em; const em = this.em;
const avoidInline = em && em.getConfig('avoidInlineStyle'); const avoidInline = em && em.getConfig('avoidInlineStyle');
const style = model.get('style'); const style = model.styleToString();
const classes = model.get('classes'); const classes = model.get('classes');
const wrappesIsBody = opts.wrappesIsBody; const wrappesIsBody = opts.wrappesIsBody;
const isWrapper = model.get('wrapper'); const isWrapper = model.get('wrapper');
@ -25,10 +23,10 @@ module.exports = require('backbone').Model.extend({
// Let's know what classes I've found // Let's know what classes I've found
classes.each(model => this.compCls.push(model.getFullName())); classes.each(model => this.compCls.push(model.getFullName()));
if ((!isEmpty(style) && !avoidInline) || isWrapper) { if ((!avoidInline || isWrapper) && style) {
let selector = `#${model.getId()}`; let selector = `#${model.getId()}`;
selector = wrappesIsBody && isWrapper ? 'body' : selector; selector = wrappesIsBody && isWrapper ? 'body' : selector;
code = `${selector}{${model.styleToString()}}`; code = `${selector}{${style}}`;
} }
const components = model.components(); const components = model.components();

3
src/commands/index.js

@ -90,6 +90,7 @@ module.exports = () => {
this.add(obj.id, obj); this.add(obj.id, obj);
} }
const ViewCode = require('./view/ExportTemplate');
defaultCommands['select-comp'] = require('./view/SelectComponent'); defaultCommands['select-comp'] = require('./view/SelectComponent');
defaultCommands['create-comp'] = require('./view/CreateComponent'); defaultCommands['create-comp'] = require('./view/CreateComponent');
defaultCommands['delete-comp'] = require('./view/DeleteComponent'); defaultCommands['delete-comp'] = require('./view/DeleteComponent');
@ -97,7 +98,7 @@ module.exports = () => {
defaultCommands['move-comp'] = require('./view/MoveComponent'); defaultCommands['move-comp'] = require('./view/MoveComponent');
defaultCommands['text-comp'] = require('./view/TextComponent'); defaultCommands['text-comp'] = require('./view/TextComponent');
defaultCommands['insert-custom'] = require('./view/InsertCustom'); defaultCommands['insert-custom'] = require('./view/InsertCustom');
defaultCommands['export-template'] = require('./view/ExportTemplate'); defaultCommands['export-template'] = ViewCode;
defaultCommands['sw-visibility'] = require('./view/SwitchVisibility'); defaultCommands['sw-visibility'] = require('./view/SwitchVisibility');
defaultCommands['open-layers'] = require('./view/OpenLayers'); defaultCommands['open-layers'] = require('./view/OpenLayers');
defaultCommands['open-sm'] = require('./view/OpenStyleManager'); defaultCommands['open-sm'] = require('./view/OpenStyleManager');

97
src/commands/view/ExportTemplate.js

@ -2,73 +2,54 @@ const $ = Backbone.$;
module.exports = { module.exports = {
run(editor, sender) { run(editor, sender, opts = {}) {
this.sender = sender; sender && sender.set && sender.set('active', 0);
this.wrapper = editor.DomComponents.getWrapper(); const config = editor.getConfig();
this.components = editor.DomComponents.getComponents(); const modal = editor.Modal;
this.modal = editor.Modal || null; const pfx = config.stylePrefix;
this.cm = editor.CodeManager || null; this.cm = editor.CodeManager || null;
this.cssc = editor.CssComposer || null;
this.protCss = editor.Config.protectedCss;
this.pfx = editor.Config.stylePrefix || '';
this.enable();
},
/**
* Build editor
* @param {String} codeName
* @param {String} theme
* @param {String} label
*
* @return {Object} Editor
* @private
* */
buildEditor(codeName, theme, label) {
if(!this.codeMirror)
this.codeMirror = this.cm.getViewer('CodeMirror');
var $input = $('<textarea>'), if (!this.$editors) {
const oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML');
const oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS');
this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el;
const $editors = $(`<div class="${pfx}export-dl"></div>`);
$editors.append(oHtmlEd.$el).append(oCsslEd.$el);
this.$editors = $editors;
}
editor = this.codeMirror.clone().set({ modal.setTitle(config.textViewCode);
label, modal.setContent(this.$editors);
codeName, modal.open();
theme, this.htmlEditor.setContent(editor.getHtml());
input: $input[0], this.cssEditor.setContent(editor.getCss());
}), },
$editor = new this.cm.EditorView({ stop(editor) {
model: editor, const modal = editor.Modal;
config: this.cm.getConfig() modal && modal.close();
}).render().$el; },
editor.init( $input[0] ); buildEditor(codeName, theme, label) {
const input = document.createElement('textarea');
!this.codeMirror && (this.codeMirror = this.cm.getViewer('CodeMirror'));
return { el: editor, $el: $editor }; const el = this.codeMirror.clone().set({
}, label,
codeName,
theme,
input,
});
enable() { const $el = new this.cm.EditorView({
if(!this.$editors){ model: el,
var oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML'); config: this.cm.getConfig()
var oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS'); }).render().$el;
this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el;
this.$editors = $(`<div class="${this.pfx}export-dl">`);
this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el);
}
if(this.modal){ el.init(input);
this.modal.setTitle('Export template');
this.modal.setContent(this.$editors);
this.modal.open();
}
const em = this.em;
var addCss = this.protCss || '';
this.htmlEditor.setContent(em.getHtml());
this.cssEditor.setContent(em.getCss());
if(this.sender) return { el, $el };
this.sender.set('active',false);
}, },
stop() {}
}; };

14
src/commands/view/SelectComponent.js

@ -447,11 +447,8 @@ module.exports = {
} }
this.toolbar.reset(toolbar); this.toolbar.reset(toolbar);
var view = model.view; const view = model.view;
view && this.updateToolbarPos(view.el);
if(view) {
this.updateToolbarPos(view.el);
}
} else { } else {
toolbarStyle.display = 'none'; toolbarStyle.display = 'none';
} }
@ -466,6 +463,7 @@ module.exports = {
var unit = 'px'; var unit = 'px';
var toolbarEl = this.canvas.getToolbarEl(); var toolbarEl = this.canvas.getToolbarEl();
var toolbarStyle = toolbarEl.style; var toolbarStyle = toolbarEl.style;
toolbarStyle.display = 'block';
var pos = this.canvas.getTargetToElementDim(toolbarEl, el, { var pos = this.canvas.getTargetToElementDim(toolbarEl, el, {
elPos, elPos,
event: 'toolbarPosUpdate', event: 'toolbarPosUpdate',
@ -473,6 +471,7 @@ module.exports = {
var leftPos = pos.left + pos.elementWidth - pos.targetWidth; var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
toolbarStyle.top = pos.top + unit; toolbarStyle.top = pos.top + unit;
toolbarStyle.left = leftPos + unit; toolbarStyle.left = leftPos + unit;
toolbarStyle.display = '';
}, },
/** /**
@ -522,8 +521,9 @@ module.exports = {
* Update attached elements, eg. component toolbar * Update attached elements, eg. component toolbar
* @return {[type]} [description] * @return {[type]} [description]
*/ */
updateAttached() { updateAttached(updated) {
var model = this.em.get('selectedComponent'); const model = this.em.getSelected();
if (model) { if (model) {
var view = model.view; var view = model.view;
this.updateToolbarPos(view.el); this.updateToolbarPos(view.el);

70
src/css_composer/index.js

@ -1,14 +1,4 @@
/** /**
* * [add](#add)
* * [get](#get)
* * [set](#set)
* * [getAll](#getall)
* * [clear](#clear)
* * [load](#load)
* * [store](#store)
* * [setIdRule](#setidrule)
* * [getIdRule](#getidrule)
*
* This module contains and manage CSS rules for the template inside the canvas * This module contains and manage CSS rules for the template inside the canvas
* Before using the methods you should get first the module from the editor instance, in this way: * Before using the methods you should get first the module from the editor instance, in this way:
* *
@ -289,11 +279,17 @@ module.exports = () => {
/** /**
* Add/update a css rule with id selector * Add/update the CSS rule with id selector
* @param {string} name Id selector name, eg. 'my-id' * @param {string} name Id selector name, eg. 'my-id'
* @param {Object} style Style properties and values * @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options * @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} * @return {CssRule} The new/updated rule
* @example
* const rule = cc.setIdRule('myid', { color: 'red' });
* const ruleHover = cc.setIdRule('myid', { color: 'blue' }, { state: 'hover' });
* // This will add current CSS:
* // #myid { color: red }
* // #myid:hover { color: blue }
*/ */
setIdRule(name, style = {}, opts = {}) { setIdRule(name, style = {}, opts = {}) {
const state = opts.state || ''; const state = opts.state || '';
@ -307,10 +303,13 @@ module.exports = () => {
/** /**
* Get css rule by id selector * Get the CSS rule by id selector
* @param {string} name Id selector name, eg. 'my-id' * @param {string} name Id selector name, eg. 'my-id'
* @param {Object} [opts={}] Custom options * @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} * @return {CssRule}
* @example
* const rule = cc.getIdRule('myid');
* const ruleHover = cc.setIdRule('myid', { state: 'hover' });
*/ */
getIdRule(name, opts = {}) { getIdRule(name, opts = {}) {
const state = opts.state || ''; const state = opts.state || '';
@ -320,6 +319,47 @@ module.exports = () => {
}, },
/**
* Add/update the CSS rule with class selector
* @param {string} name Class selector name, eg. 'my-class'
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} The new/updated rule
* @example
* const rule = cc.setClassRule('myclass', { color: 'red' });
* const ruleHover = cc.setClassRule('myclass', { color: 'blue' }, { state: 'hover' });
* // This will add current CSS:
* // .myclass { color: red }
* // .myclass:hover { color: blue }
*/
setClassRule(name, style = {}, opts = {}) {
const state = opts.state || '';
const media = opts.mediaText || em.getCurrentMedia();
const sm = em.get('SelectorManager');
const selector = sm.add({ name, type: Selector.TYPE_CLASS });
const rule = this.add(selector, state, media);
rule.setStyle(style, opts);
return rule;
},
/**
* Get the CSS rule by class selector
* @param {string} name Class selector name, eg. 'my-class'
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @example
* const rule = cc.getClassRule('myclass');
* const ruleHover = cc.getClassRule('myclass', { state: 'hover' });
*/
getClassRule(name, opts = {}) {
const state = opts.state || '';
const media = opts.mediaText || em.getCurrentMedia();
const selector = em.get('SelectorManager').get(name, Selector.TYPE_CLASS);
return selector && this.get(selector, state, media);
},
/** /**
* Render the block of CSS rules * Render the block of CSS rules
* @return {HTMLElement} * @return {HTMLElement}

2
src/css_composer/view/CssRuleView.js

@ -1,8 +1,6 @@
module.exports = require('backbone').View.extend({ module.exports = require('backbone').View.extend({
tagName: 'style', tagName: 'style',
initialize(o = {}) { initialize(o = {}) {
this.config = o.config || {}; this.config = o.config || {};
const model = this.model; const model = this.model;

27
src/css_composer/view/CssRulesView.js

@ -4,11 +4,14 @@ var CssRuleView = require('./CssRuleView');
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
initialize(o) { initialize(o) {
this.config = o.config || {}; const config = o.config || {};
this.pfx = this.config.stylePrefix || ''; this.config = config;
this.em = config.em;
this.pfx = config.stylePrefix || '';
this.className = this.pfx + 'rules'; this.className = this.pfx + 'rules';
this.listenTo( this.collection, 'add', this.addTo ); const coll = this.collection;
this.listenTo( this.collection, 'reset', this.render ); this.listenTo(coll, 'add', this.addTo );
this.listenTo(coll, 'reset', this.render );
}, },
/** /**
@ -17,7 +20,6 @@ module.exports = Backbone.View.extend({
* @private * @private
* */ * */
addTo(model) { addTo(model) {
//console.log('Added');
this.addToCollection(model); this.addToCollection(model);
}, },
@ -47,15 +49,12 @@ module.exports = Backbone.View.extend({
}, },
render() { render() {
var fragment = document.createDocumentFragment(); const $el = this.$el;
this.$el.empty(); const frag = document.createDocumentFragment();
$el.empty();
this.collection.each(function(model){ this.collection.each(model => this.addToCollection(model, frag));
this.addToCollection(model, fragment); $el.append(frag);
}, this); $el.attr('class', this.className);
this.$el.append(fragment);
this.$el.attr('class', this.className);
return this; return this;
} }
}); });

9
src/device_manager/index.js

@ -1,8 +1,4 @@
/** /**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
*
* Before using methods you should get first the module from the editor instance, in this way: * Before using methods you should get first the module from the editor instance, in this way:
* *
* ```js * ```js
@ -41,6 +37,7 @@ module.exports = () => {
* } * }
* ... * ...
* @return {this} * @return {this}
* @private
*/ */
init(config) { init(config) {
c = config || {}; c = config || {};
@ -65,6 +62,10 @@ module.exports = () => {
* @return {Device} Added device * @return {Device} Added device
* @example * @example
* deviceManager.add('Tablet', '900px'); * deviceManager.add('Tablet', '900px');
* deviceManager.add('Tablet2', '900px', {
* height: '300px',
* widthMedia: '810px', // the width that will be used for the CSS media
* });
*/ */
add(name, width, opts) { add(name, width, opts) {
var obj = opts || {}; var obj = opts || {};

3
src/device_manager/model/Device.js

@ -10,6 +10,9 @@ module.exports = Backbone.Model.extend({
// Width to set for the editor iframe // Width to set for the editor iframe
width: '', width: '',
// Height to set for the editor iframe
height: '',
// The width which will be used in media queries, // The width which will be used in media queries,
// If empty the width will be used // If empty the width will be used
widthMedia: null, widthMedia: null,

1
src/dom_components/config/config.js

@ -7,7 +7,6 @@ module.exports = {
// Default wrapper configuration // Default wrapper configuration
wrapper: { wrapper: {
style: {margin: 0},
removable: false, removable: false,
copyable: false, copyable: false,
draggable: false, draggable: false,

15
src/dom_components/index.js

@ -57,6 +57,21 @@ module.exports = () => {
model: require('./model/ComponentTable'), model: require('./model/ComponentTable'),
view: require('./view/ComponentTableView'), view: require('./view/ComponentTableView'),
}, },
{
id: 'thead',
model: require('./model/ComponentTableHead'),
view: require('./view/ComponentTableHeadView'),
},
{
id: 'tbody',
model: require('./model/ComponentTableBody'),
view: require('./view/ComponentTableBodyView'),
},
{
id: 'tfoot',
model: require('./model/ComponentTableFoot'),
view: require('./view/ComponentTableFootView'),
},
{ {
id: 'map', id: 'map',
model: require('./model/ComponentMap'), model: require('./model/ComponentMap'),

12
src/dom_components/model/Component.js

@ -169,6 +169,18 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.init(); this.init();
}, },
/**
* Check component's type
* @param {string} type Component type
* @return {Boolean}
* @example
* model.is('image')
* // -> false
*/
is(type) {
return !!(this.get('type') == type);
},
/** /**
* Update attributes of the model * Update attributes of the model

84
src/dom_components/model/ComponentTable.js

@ -1,88 +1,28 @@
var Component = require('./Component'); const Component = require('./Component');
module.exports = Component.extend({ module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: { ...Component.prototype.defaults,
type: 'table', type: 'table',
tagName: 'table', tagName: 'table',
droppable: ['tr', 'tbody', 'thead', 'tfoot'], droppable: ['tbody', 'thead', 'tfoot'],
columns: 3, },
rows: 2,
/*
traits: [{
label: 'Columns',
name: 'columns',
changeProp: 1,
},{
label: 'Rows',
name: 'rows',
changeProp: 1,
}]
*/
}),
initialize(o, opt) { initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments); Component.prototype.initialize.apply(this, arguments);
var components = this.get('components'); const components = this.get('components');
var rows = this.get('rows'); !components.length && components.add({ type: 'tbody' });
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(model => {
if(model.get('type') != 'row'){
model.get('components').each(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(el) { isComponent(el) {
var result = ''; let result = '';
if(el.tagName == 'TABLE'){
result = {type: 'table'}; if (el.tagName == 'TABLE') {
result = { type: 'table' };
} }
return result; return result;
}, },

58
src/dom_components/model/ComponentTableBody.js

@ -0,0 +1,58 @@
const Component = require('./Component');
module.exports = Component.extend({
defaults: { ...Component.prototype.defaults,
type: 'tbody',
tagName: 'tbody',
draggable: ['table'],
droppable: ['tr'],
columns: 1,
rows: 1,
},
initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments);
const components = this.get('components');
let columns = this.get('columns');
let rows = this.get('rows');
// Init components if empty
if (!components.length) {
const rowsToAdd = [];
while (rows--) {
const columnsToAdd = [];
let clm = columns;
while (clm--) {
columnsToAdd.push({
type: 'cell',
classes: ['cell']
});
}
rowsToAdd.push({
type: 'row',
classes: ['row'],
components: columnsToAdd
});
}
components.add(rowsToAdd);
}
},
},{
isComponent(el) {
let result = '';
if (el.tagName == 'TBODY') {
result = { type: 'tbody' };
}
return result;
},
});

25
src/dom_components/model/ComponentTableCell.js

@ -1,32 +1,25 @@
var Component = require('./Component'); const Component = require('./Component');
module.exports = Component.extend({ module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: { ...Component.prototype.defaults,
type: 'cell', type: 'cell',
tagName: 'td', tagName: 'td',
draggable: ['tr'], 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(el) { isComponent(el) {
var result = ''; let result = '';
var tag = el.tagName; const tag = el.tagName;
if(tag == 'TD' || tag == 'TH'){
if (tag == 'TD' || tag == 'TH') {
result = { result = {
type: 'cell', type: 'cell',
tagName: tag.toLowerCase() tagName: tag.toLowerCase()
}; };
} }
return result; return result;
}, },

21
src/dom_components/model/ComponentTableFoot.js

@ -0,0 +1,21 @@
const ComponentTableBody = require('./ComponentTableBody');
module.exports = ComponentTableBody.extend({
defaults: { ...ComponentTableBody.prototype.defaults,
type: 'tfoot',
tagName: 'tfoot',
},
},{
isComponent(el) {
let result = '';
if (el.tagName == 'TFOOT') {
result = { type: 'tfoot' };
}
return result;
},
});

21
src/dom_components/model/ComponentTableHead.js

@ -0,0 +1,21 @@
const ComponentTableBody = require('./ComponentTableBody');
module.exports = ComponentTableBody.extend({
defaults: { ...ComponentTableBody.prototype.defaults,
type: 'thead',
tagName: 'thead'
},
}, {
isComponent(el) {
let result = '';
if (el.tagName == 'THEAD') {
result = { type: 'thead' };
}
return result;
},
});

37
src/dom_components/model/ComponentTableRow.js

@ -1,43 +1,32 @@
var Component = require('./Component'); const Component = require('./Component');
module.exports = Component.extend({ module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: { ...Component.prototype.defaults,
type: 'row', type: 'row',
tagName: 'tr', tagName: 'tr',
draggable: ['table', 'tbody', 'thead'], draggable: ['thead', 'tbody', 'tfoot'],
droppable: ['th', 'td'] droppable: ['th', 'td']
}), },
initialize(o, opt) { initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments); Component.prototype.initialize.apply(this, arguments);
// Clean the row from non cell components // Clean the row from non cell components
var cells = []; const cells = [];
var components = this.get('components'); const components = this.get('components');
components.each(model => { components.each(model => model.is('cell') && cells.push(model));
if(model.get('type') == 'cell'){
cells.push(model);
}
});
components.reset(cells); 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(el) { isComponent(el) {
var result = ''; let result = '';
if(el.tagName == 'TR'){
result = {type: 'row'}; if (el.tagName == 'TR') {
result = { type: 'row' };
} }
return result; return result;
}, },

4
src/dom_components/view/ComponentTableBodyView.js

@ -0,0 +1,4 @@
var Backbone = require('backbone');
var ComponentView = require('./ComponentView');
module.exports = ComponentView.extend({});

4
src/dom_components/view/ComponentTableFootView.js

@ -0,0 +1,4 @@
var Backbone = require('backbone');
var ComponentView = require('./ComponentView');
module.exports = ComponentView.extend({});

4
src/dom_components/view/ComponentTableHeadView.js

@ -0,0 +1,4 @@
var Backbone = require('backbone');
var ComponentView = require('./ComponentView');
module.exports = ComponentView.extend({});

2
src/dom_components/view/ComponentTextView.js

@ -82,7 +82,7 @@ module.exports = ComponentView.extend({
} }
// Avoid re-render on reset with silent option // Avoid re-render on reset with silent option
model.set('content', ''); model.set('content', '').trigger('change:content', model);
comps.add(content); comps.add(content);
comps.each(model => clean(model)); comps.each(model => clean(model));
comps.trigger('resetNavigator'); comps.trigger('resetNavigator');

14
src/domain_abstract/ui/InputColor.js

@ -65,7 +65,7 @@ module.exports = Input.extend({
} }
let changed = 0; let changed = 0;
let previousСolor; let previousColor;
this.$el.find(`[data-colorp-c]`).append(colorEl); this.$el.find(`[data-colorp-c]`).append(colorEl);
colorEl.spectrum({ colorEl.spectrum({
containerClassName: `${ppfx}one-bg ${ppfx}two-color`, containerClassName: `${ppfx}one-bg ${ppfx}two-color`,
@ -90,16 +90,16 @@ module.exports = Input.extend({
}, },
show(color) { show(color) {
changed = 0; changed = 0;
previousСolor = getColor(color); previousColor = getColor(color);
}, },
hide(color) { hide(color) {
if (!changed && previousСolor) { if (!changed && previousColor) {
if (self.noneColor) { if (self.noneColor) {
previousСolor = ''; previousColor = '';
} }
cpStyle.backgroundColor = previousСolor; cpStyle.backgroundColor = previousColor;
colorEl.spectrum('set', previousСolor); colorEl.spectrum('set', previousColor);
model.setValueFromInput(previousСolor, 0); model.setValueFromInput(previousColor, 0);
} }
} }
}); });

6
src/editor/config/config.js

@ -31,7 +31,7 @@ module.exports = {
width: '100%', width: '100%',
// CSS that could only be seen (for instance, inside the code viewer) // CSS that could only be seen (for instance, inside the code viewer)
protectedCss: '*{box-sizing: border-box;}', protectedCss: '* { box-sizing: border-box; } body {margin: 0;}',
// CSS for the iframe which containing the canvas, useful if you need to custom something inside // CSS for the iframe which containing the canvas, useful if you need to custom something inside
// (eg. the style of the selected component) // (eg. the style of the selected component)
@ -175,4 +175,8 @@ module.exports = {
//Configurations for Block Manager //Configurations for Block Manager
blockManager: {}, blockManager: {},
// Texts
textViewCode: 'Code',
}; };

22
src/editor/index.js

@ -7,16 +7,19 @@
* ``` * ```
* *
* # Available Events * # Available Events
*
* ## Components * ## Components
* * `component:add` - Triggered when a new component is added to the editor, the model is passed as an argument to the callback * * `component:add` - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
* * `component:update` - Triggered when a component is, generally, updated (moved, styled, etc.) * * `component:update` - Triggered when a component is updated (moved, styled, etc.), the model is passed as an argument to the callback
* * `component:update:{propertyName}` - Listen any property change * * `component:update:{propertyName}` - Listen any property change, the model is passed as an argument to the callback
* * `component:styleUpdate` - Triggered when the style of the component is updated * * `component:styleUpdate` - Triggered when the style of the component is updated, the model is passed as an argument to the callback
* * `component:styleUpdate:{propertyName}` - Listen for a specific style property change * * `component:styleUpdate:{propertyName}` - Listen for a specific style property change, the model is passed as an argument to the callback
* * `component:selected` - New component selected * * `component:selected` - New component selected, the selected model is passed as an argument to the callback
* ## Blocks * ## Blocks
* * `block:add` - New block added * * `block:add` - New block added
* * `block:remove` - Block removed * * `block:remove` - Block removed
* * `block:drag:start` - Started dragging new block, Event object is passed as an argument
* * `block:drag:stop` - Block dropped inside canvas, the new model is passed as an argument to the callback
* ## Assets * ## Assets
* * `asset:add` - New asset added * * `asset:add` - New asset added
* * `asset:remove` - Asset removed * * `asset:remove` - Asset removed
@ -244,16 +247,17 @@ module.exports = config => {
* Returns HTML built inside canvas * Returns HTML built inside canvas
* @return {string} HTML string * @return {string} HTML string
*/ */
getHtml() { getHtml(opts) {
return em.getHtml(); return em.getHtml(opts);
}, },
/** /**
* Returns CSS built inside canvas * Returns CSS built inside canvas
* @param {Object} [opts={}] Options
* @return {string} CSS string * @return {string} CSS string
*/ */
getCss() { getCss(opts) {
return em.getCss(); return em.getCss(opts);
}, },
/** /**

10
src/editor/model/Editor.js

@ -485,15 +485,17 @@ module.exports = Backbone.Model.extend({
/** /**
* Returns CSS built inside canvas * Returns CSS built inside canvas
* @param {Object} [opts={}] Options
* @return {string} CSS string * @return {string} CSS string
* @private * @private
*/ */
getCss() { getCss(opts = {}) {
const config = this.config; const config = this.config;
const wrappesIsBody = config.wrappesIsBody; const wrappesIsBody = config.wrappesIsBody;
var cssc = this.get('CssComposer'); const avoidProt = opts.avoidProtected;
var wrp = this.get('DomComponents').getComponent(); const cssc = this.get('CssComposer');
var protCss = config.protectedCss; const wrp = this.get('DomComponents').getComponent();
const protCss = !avoidProt ? config.protectedCss : '';
return protCss + this.get('CodeManager').getCode(wrp, 'css', { return protCss + this.get('CodeManager').getCode(wrp, 'css', {
cssc, wrappesIsBody cssc, wrappesIsBody

8
src/panels/view/ButtonView.js

@ -203,12 +203,12 @@ module.exports = Backbone.View.extend({
const model = this.model; const model = this.model;
const context = model.get('context'); const context = model.get('context');
const parent = this.parentM; const parent = this.parentM;
var command = null; let command = {};
var editor = this.em && this.em.get ? this.em.get('Editor') : null; var editor = this.em && this.em.get ? this.em.get('Editor') : null;
var commandName = model.get('command'); var commandName = model.get('command');
if (this.commands && isString(commandName)) { if (this.commands && isString(commandName)) {
command = this.commands.get(commandName); command = this.commands.get(commandName) || {};
} else if (isFunction(commandName)) { } else if (isFunction(commandName)) {
command = { run: commandName }; command = { run: commandName };
} else if (commandName !== null && isObject(commandName)) { } else if (commandName !== null && isObject(commandName)) {
@ -220,7 +220,7 @@ module.exports = Backbone.View.extend({
model.set('active', true, { silent: true }).trigger('checkActive'); model.set('active', true, { silent: true }).trigger('checkActive');
parent && parent.set('active', true, { silent: true }).trigger('checkActive'); parent && parent.set('active', true, { silent: true }).trigger('checkActive');
if (command && command.run) { if (command.run) {
command.run(editor, model, model.get('options')); command.run(editor, model, model.get('options'));
editor.trigger('run:' + commandName); editor.trigger('run:' + commandName);
} }
@ -232,7 +232,7 @@ module.exports = Backbone.View.extend({
model.collection.deactivateAll(context); model.collection.deactivateAll(context);
parent && parent.set('active', false, { silent: true }).trigger('checkActive'); parent && parent.set('active', false, { silent: true }).trigger('checkActive');
if (command && command.stop) { if (command.stop) {
command.stop(editor, model, model.get('options')); command.stop(editor, model, model.get('options'));
editor.trigger('stop:' + commandName); editor.trigger('stop:' + commandName);
} }

2
src/parser/config/config.js

@ -1,5 +1,5 @@
module.exports = { module.exports = {
textTags: ['br', 'b', 'i', 'u'], textTags: ['br', 'b', 'i', 'u', 'a', 'ul', 'ol'],
}; };

150
src/parser/model/ParserHtml.js

@ -53,144 +53,140 @@ module.exports = config => {
}, },
/** /**
* Fetch data from node * Get data from the node element
* @param {HTMLElement} el DOM * @param {HTMLElement} el DOM element to traverse
* @return {Array<Object>} * @return {Array<Object>}
*/ */
parseNode(el) { parseNode(el) {
var result = []; const result = [];
var nodes = el.childNodes; const nodes = el.childNodes;
for (var i = 0, len = nodes.length; i < len; i++) { for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i]; const node = nodes[i];
var model = {}; const attrs = node.attributes || [];
var attrs = node.attributes || []; const attrsLen = attrs.length;
var attrsLen = attrs.length; const nodePrev = result[result.length - 1];
var prevI = result.length - 1; const nodeChild = node.childNodes.length;
var prevSib = result[prevI]; const ct = this.compTypes;
var ct = this.compTypes; let model = {};
if(ct){ // Start with understanding what kind of component it is
var obj = ''; if (ct) {
/* let obj = '';
for (var cType in ct) {
var component = ct[cType].model; // Iterate over all available Component Types and
obj = component.isComponent(node); // the first with a valid result will be that component
if(obj) for (let it = 0; it < ct.length; it++) {
break; obj = ct[it].model.isComponent(node);
}*/ if (obj) break;
for (var it = 0; it < ct.length; it++) {
var component = ct[it].model;
obj = component.isComponent(node);
if(obj)
break;
} }
model = obj; model = obj;
} }
if(!model.tagName) // Set tag name if not yet done
if (!model.tagName) {
model.tagName = node.tagName ? node.tagName.toLowerCase() : ''; model.tagName = node.tagName ? node.tagName.toLowerCase() : '';
}
if(attrsLen) if (attrsLen) {
model.attributes = {}; model.attributes = {};
}
// Store attributes // Parse attributes
for (var j = 0; j < attrsLen; j++){ for (let j = 0; j < attrsLen; j++) {
var nodeName = attrs[j].nodeName; const nodeName = attrs[j].nodeName;
var nodeValue = attrs[j].nodeValue; let nodeValue = attrs[j].nodeValue;
//Isolate few attributes // Isolate attributes
if(nodeName == 'style') if (nodeName == 'style') {
model.style = this.parseStyle(nodeValue); model.style = this.parseStyle(nodeValue);
else if(nodeName == 'class') } else if (nodeName == 'class') {
model.classes = this.parseClass(nodeValue); model.classes = this.parseClass(nodeValue);
else if (nodeName == 'contenteditable') } else if (nodeName == 'contenteditable') {
continue; continue;
else if (nodeName.indexOf(modelAttrStart) === 0) { } else if (nodeName.indexOf(modelAttrStart) === 0) {
const modelAttr = nodeName.replace(modelAttrStart, ''); const modelAttr = nodeName.replace(modelAttrStart, '');
const valueLen = nodeValue.length; const valueLen = nodeValue.length;
const firstChar = nodeValue && nodeValue.substr(0, 1); const firstChar = nodeValue && nodeValue.substr(0, 1);
const lastChar = nodeValue && nodeValue.substr(valueLen - 1); const lastChar = nodeValue && nodeValue.substr(valueLen - 1);
nodeValue = nodeValue === 'true' ? true : nodeValue; nodeValue = nodeValue === 'true' ? true : nodeValue;
nodeValue = nodeValue === 'false' ? false : nodeValue; nodeValue = nodeValue === 'false' ? false : nodeValue;
// Try to json parse where is possible
// Try to parse JSON where it's possible
// I can get false positive here (eg. a selector '[data-attr]') // I can get false positive here (eg. a selector '[data-attr]')
// so put it under try/catch and let fail silently // so put it under try/catch and let fail silently
try { try {
nodeValue = (firstChar == '{' && lastChar == '}') || nodeValue = (firstChar == '{' && lastChar == '}') ||
(firstChar == '[' && lastChar == ']') ? JSON.parse(nodeValue) : nodeValue; (firstChar == '[' && lastChar == ']') ? JSON.parse(nodeValue) : nodeValue;
} catch (e) {} } catch (e) {}
model[modelAttr] = nodeValue; model[modelAttr] = nodeValue;
} else { } else {
model.attributes[nodeName] = nodeValue; model.attributes[nodeName] = nodeValue;
} }
} }
// Check for nested elements but avoid it if already provided
if (nodeChild && !model.components) {
// Avoid infinite nested text nodes
const firstChild = node.childNodes[0];
var nodeChild = node.childNodes.length; // If there is only one child and it's a TEXTNODE
// just make it content of the current node
// Check for nested elements and avoid them if an array if (nodeChild === 1 && firstChild.nodeType === 3) {
// was already given !model.type && (model.type = 'text');
if(nodeChild && !model.components){
// Avoid infinite text nodes nesting
var firstChild = node.childNodes[0];
if(nodeChild === 1 && firstChild.nodeType === 3){
if(!model.type){
model.type = 'text';
}
model.content = firstChild.nodeValue; model.content = firstChild.nodeValue;
}else{ } else {
var parsed = this.parseNode(node); model.components = this.parseNode(node);
// From: <div> <span>TEST</span> </div> <-- span is text type
// TO: <div> TEST </div> <-- div become text type
if(parsed.length == 1 && parsed[0].type == 'text' &&
parsed[0].tagName == TEXT_NODE){
model.type = 'text';
model.content = parsed[0].content;
}else
model.components = parsed;
} }
} }
// Check if it's a text node and if could be moved to the prevous model // Check if it's a text node and if could be moved to the prevous model
if(model.type == 'textnode'){ if (model.type == 'textnode') {
var prevIsText = prevSib && prevSib.type == 'textnode'; if (nodePrev && nodePrev.type == 'textnode') {
if(prevIsText){ nodePrev.content += model.content;
prevSib.content += model.content;
continue; continue;
} }
// Throw away empty nodes (keep spaces) // Throw away empty nodes (keep spaces)
var content = node.nodeValue; const content = node.nodeValue;
if(content != ' ' && !content.trim()){ if (content != ' ' && !content.trim()) {
continue; continue;
} }
} }
// If all children are texts and there is some textnode the parent should // If all children are texts and there is some textnode the parent should
// be text too otherwise I'm unable to edit texnodes // be text too otherwise I'm unable to edit texnodes
var comps = model.components; const comps = model.components;
if(!model.type && comps){ if (!model.type && comps) {
var allTxt = 1; let allTxt = 1;
var foundTextNode = 0; let foundTextNode = 0;
for(var ci = 0; ci < comps.length; ci++){
var comp = comps[ci]; for (let ci = 0; ci < comps.length; ci++) {
if(comp.type != 'text' && const comp = comps[ci];
comp.type != 'textnode' && const cType = comp.type;
c.textTags.indexOf(comp.tagName) < 0 ){
if (['text', 'textnode'].indexOf(cType) < 0 &&
c.textTags.indexOf(comp.tagName) < 0 ) {
allTxt = 0; allTxt = 0;
break; break;
} }
if(comp.type == 'textnode')
if (cType == 'textnode') {
foundTextNode = 1; foundTextNode = 1;
}
} }
if(allTxt && foundTextNode)
if (allTxt && foundTextNode) {
model.type = 'text'; model.type = 'text';
}
} }
// If tagName is still empty and is not a textnode, do not push it // If tagName is still empty and is not a textnode, do not push it
if(!model.tagName && model.type != 'textnode') if (!model.tagName && model.type != 'textnode') {
continue; continue;
}
result.push(model); result.push(model);
} }

40
src/style_manager/index.js

@ -1,18 +1,4 @@
/** /**
*
* * [addSector](#addsector)
* * [getSector](#getsector)
* * [getSectors](#getsectors)
* * [addProperty](#addproperty)
* * [getProperty](#getproperty)
* * [getProperties](#getproperties)
* * [getModelToStyle](#getmodeltostyle)
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
* * [createType](#createtype)
* * [render](#render)
*
* With Style Manager you basically build categories (called sectors) of CSS properties which could * With Style Manager you basically build categories (called sectors) of CSS properties which could
* be used to custom components and classes. * be used to custom components and classes.
* You can init the editor with all sectors and properties via configuration * You can init the editor with all sectors and properties via configuration
@ -146,6 +132,18 @@ module.exports = () => {
}, },
/**
* Remove a sector by id
* @param {string} id Sector id
* @return {Sector} Removed sector
* @example
* const removed = styleManager.removeSector('mySector');
*/
removeSector(id) {
return this.getSectors().remove(this.getSector(id));
},
/** /**
* Get all sectors * Get all sectors
* @return {Sectors} Collection of sectors * @return {Sectors} Collection of sectors
@ -222,6 +220,20 @@ module.exports = () => {
}, },
/**
* Remove a property from the sector
* @param {string} sectorId Sector id
* @param {string} name CSS property name, eg. 'min-height'
* @return {Property} Removed property
* @example
* const property = styleManager.removeProperty('mySector', 'min-height');
*/
removeProperty(sectorId, name) {
const props = this.getProperties(sectorId);
return props && props.remove(this.getProperty(sectorId, name));
},
/** /**
* Get properties of the sector * Get properties of the sector
* @param {string} sectorId Sector id * @param {string} sectorId Sector id

6
src/style_manager/view/PropertyView.js

@ -339,9 +339,9 @@ module.exports = Backbone.View.extend({
} }
if (em) { if (em) {
em.trigger('component:update', model); em.trigger('component:update', target);
em.trigger('component:styleUpdate', model); em.trigger('component:styleUpdate', target);
em.trigger('component:styleUpdate:' + model.get('property'), model); em.trigger('component:styleUpdate:' + model.get('property'), target);
} }
}, },

1
src/style_manager/view/SectorView.js

@ -23,6 +23,7 @@ module.exports = Backbone.View.extend({
const model = this.model; const model = this.model;
this.listenTo(model, 'change:open', this.updateOpen); this.listenTo(model, 'change:open', this.updateOpen);
this.listenTo(model, 'updateVisibility', this.updateVisibility); this.listenTo(model, 'updateVisibility', this.updateVisibility);
this.listenTo(model, 'destroy remove', this.remove);
}, },
/** /**

33
src/styles/scss/_gjs_canvas.scss

@ -1,23 +1,17 @@
$frameAnimation: 0.35s ease;
$canvasTop: 40px;
.#{$cv-prefix}canvas { .#{$cv-prefix}canvas {
background-color: rgba(0, 0, 0, 0.15); background-color: rgba(0, 0, 0, 0.15);
box-sizing: border-box; box-sizing: border-box;
position: absolute;
width: (100% - $leftWidth); width: (100% - $leftWidth);
height: 100%; height: calc(100% - $canvasTop);
bottom: 0; bottom: 0;
left: 0;
overflow: hidden; overflow: hidden;
padding-top: 40px;
z-index: 1; z-index: 1;
position: absolute;
> iframe { left: 0;
height: 100%; top: 40px;
outline: medium none;
width: 100%;
border: none;
margin: 0 auto;
display: block;
}
.#{$app-prefix}ghost { .#{$app-prefix}ghost {
display: none; display: none;
@ -69,7 +63,18 @@
} }
.#{$app-prefix}frame { .#{$app-prefix}frame {
transition: width 0.35s ease; outline: medium none;
height: 100%;
width: 100%;
border: none;
margin: auto;
display: block;
transition: width $frameAnimation, height $frameAnimation;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
} }
.#{$app-prefix}toolbar { .#{$app-prefix}toolbar {

20
src/styles/scss/_gjs_devices.scss

@ -0,0 +1,20 @@
.#{$app-prefix}devices-c {
display: flex;
align-items: center;
padding: 3px;
.#{$app-prefix}device-label {
flex-grow: 2;
text-align: left;
margin-right: 10px;
}
.#{$app-prefix}select {
flex-grow: 20;
}
.#{$app-prefix}add-trasp {
flex-grow: 1;
margin-left: 5px;
}
}

70
src/styles/scss/_gjs_modal.scss

@ -0,0 +1,70 @@
.#{$mdl-prefix} {
&backlayer {
@include opacity(0.5);
background-color: #000;
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
}
&container {
font-family: $mainFont;
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 100%;
height: 100%;
}
&dialog {
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.05);
animation: #{$app-prefix}slide-down 0.215s;
margin: 30px auto 0;
max-width: 850px;
width: 90%;
border-radius: 3px;
font-weight: lighter;
position: relative;
z-index: 2;
}
&title {
font-size: 1rem;
}
&btn-close {
@extend .btn-cl;
position: absolute;
right: 15px;
top: 5px;
}
&active .#{$mdl-prefix}dialog {
animation: #{$mdl-prefix}slide-down 0.216s;
}
&header,
&content {
padding: 10px 15px;
clear: both;
}
&header {
position: relative;
border-bottom: 1px solid $mainDkColor;
padding: 15px 15px 7px;
}
}
.#{$app-prefix}export-dl::after {
content: "";
clear: both;
display: block;
margin-bottom: 10px;
}

3
src/styles/scss/_gjs_variables.scss

@ -40,7 +40,6 @@ $mainDklColor: rgba(0, 0, 0, 0.1) !default;
$mainLhColor: rgba(255, 255, 255, 0.1) !default; /* #515151 */ $mainLhColor: rgba(255, 255, 255, 0.1) !default; /* #515151 */
$mainLhlColor: rgba(255, 255, 255, 0.7) !default; $mainLhlColor: rgba(255, 255, 255, 0.7) !default;
$fontColorDk: #777 !default; $fontColorDk: #777 !default;
$mainFont: Helvetica, sans-serif !default;
$colorBlue: #3b97e3 !default; $colorBlue: #3b97e3 !default;
$colorRed: #dd3636 !default; $colorRed: #dd3636 !default;
$colorYell: #ffca6f !default; $colorYell: #ffca6f !default;
@ -77,6 +76,8 @@ $uploadPadding: 150px 10px !default;
$animSpeed: 0.2s !default; $animSpeed: 0.2s !default;
/* Fonts */ /* Fonts */
$mainFont: Helvetica, sans-serif !default;
$fontPath: '../fonts' !default; $fontPath: '../fonts' !default;
$fontName: 'main-fonts' !default; $fontName: 'main-fonts' !default;
$fontSize: 0.7rem;
$fontV: 20 !default;//random(1000) $fontV: 20 !default;//random(1000)

100
src/styles/scss/main.scss

@ -214,12 +214,13 @@ $prefix: $app-prefix;
} }
.#{$app-prefix}editor { .#{$app-prefix}editor {
font-family: $mainFont; font-family: $mainFont;
//background-color: $mainColor; font-size: $fontSize;
position:relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
height: 100%; height: 100%;
} }
.#{$app-prefix}freezed, .#{$app-prefix}freezed,
.#{$nv-prefix}freezed { .#{$nv-prefix}freezed {
@include opacity(0.50); @include opacity(0.50);
@ -459,36 +460,7 @@ $prefix: $app-prefix;
@import "gjs_inputs"; @import "gjs_inputs";
/********* Device Manager **********/ @import "gjs_devices";
.#{$app-prefix}devices-c {
display: flex;
align-items: center;
padding: 3px;
/*
padding: 10px 5px;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
margin-bottom: 10px;
*/
.#{$app-prefix}device-label {
flex-grow: 2;
font-size: 0.7em;
text-align: left;
margin-right: 10px;
}
.#{$app-prefix}select{
flex-grow: 20;
}
.#{$app-prefix}add-trasp{
flex-grow: 1;
margin-left: 5px;
}
}
/********* END Device Manager **********/
/********* General **********/ /********* General **********/
@ -614,62 +586,7 @@ $prefix: $app-prefix;
/********* Modal dialog **********/ /********* Modal dialog **********/
.#{$mdl-prefix}backlayer { @import "gjs_modal";
background-color: #000;
position: absolute;
top: 0; left: 0;
z-index: 1;
width:100%; height:100%;
@include opacity(0.5);
}
.#{$mdl-prefix}container {
font-family: $mainFont;
position: absolute;
top: 0; left: 0;
z-index: 10;
width:100%; height:100%;
}
.#{$mdl-prefix}dialog {
text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.05);
animation: #{$app-prefix}slide-down 0.215s;
margin: 30px auto 0;
max-width: 850px;
width: 90%;
border-radius: 3px;
font-weight: lighter;
position:relative;
z-index: 2;
.#{$mdl-prefix}btn-close {
@extend .btn-cl;
position: absolute;
right: 15px; top: 5px;
}
}
.#{$mdl-prefix}active .#{$mdl-prefix}dialog {
animation: #{$mdl-prefix}slide-down .216s;
}
.#{$mdl-prefix}header, .#{$mdl-prefix}content{
padding:10px 15px;
clear: both;
}
.#{$mdl-prefix}header{
position: relative;
border-bottom: 1px solid $mainDkColor;
padding: 15px 15px 7px;
}
.#{$app-prefix}export-dl::after{
content: "";
clear: both;
display:block;
margin-bottom:10px;
}
/********* Assets Manager **********/ /********* Assets Manager **********/
@ -735,7 +652,6 @@ $prefix: $app-prefix;
} }
&##{$cm-prefix}css { &##{$cm-prefix}css {
padding-left: 10px; padding-left: 10px;
border-left: 1px solid $mainLhColor;
##{$cm-prefix}title { color: #ddca7e;} ##{$cm-prefix}title { color: #ddca7e;}
} }
##{$cm-prefix}title { ##{$cm-prefix}title {

4
src/trait_manager/view/TraitView.js

@ -6,6 +6,10 @@ module.exports = Backbone.View.extend({
'change': 'onChange' 'change': 'onChange'
}, },
attributes() {
return this.model.get('attributes');
},
initialize(o) { initialize(o) {
var md = this.model; var md = this.model;
this.config = o.config || {}; this.config = o.config || {};

47
src/utils/Resizer.js

@ -1,5 +1,5 @@
import { bindAll, defaults, isFunction } from 'underscore'; import { bindAll, defaults, isFunction } from 'underscore';
import { on, off } from 'utils/mixins'; import { on, off, normalizeFloat } from 'utils/mixins';
var defaultOpts = { var defaultOpts = {
// Function which returns custom X and Y coordinates of the mouse // Function which returns custom X and Y coordinates of the mouse
@ -13,9 +13,15 @@ var defaultOpts = {
onMove: null, onMove: null,
onEnd: null, onEnd: null,
// Resize unit step
step: 1,
// Minimum dimension // Minimum dimension
minDim: 32, minDim: 32,
// Maximum dimension
maxDim: '',
// Unit used for height resizing // Unit used for height resizing
unitHeight: 'px', unitHeight: 'px',
@ -269,7 +275,7 @@ class Resizer {
this.currentPos = currentPos; this.currentPos = currentPos;
this.delta = { this.delta = {
x: currentPos.x - this.startPos.x, x: currentPos.x - this.startPos.x,
y: currentPos.y - this.startPos.y y: currentPos.y - this.startPos.y,
}; };
this.keys = { this.keys = {
shift: e.shiftKey, shift: e.shiftKey,
@ -386,14 +392,21 @@ class Resizer {
* @return {Object} * @return {Object}
*/ */
calc(data) { calc(data) {
var opts = this.opts || {}; let value;
var startDim = this.startDim; const opts = this.opts || {};
var minDim = opts.minDim; const step = opts.step;
const startDim = this.startDim;
const minDim = opts.minDim;
const maxDim = opts.maxDim;
const deltaX = data.delta.x;
const deltaY = data.delta.y;
const startW = startDim.w;
const startH = startDim.h;
var box = { var box = {
t: 0, t: 0,
l: 0, l: 0,
w: startDim.w, w: startW,
h: startDim.h h: startH
}; };
if (!data) if (!data)
@ -401,16 +414,28 @@ class Resizer {
var attr = data.handlerAttr; var attr = data.handlerAttr;
if (~attr.indexOf('r')) { if (~attr.indexOf('r')) {
box.w = Math.max(minDim, startDim.w + data.delta.x); value = normalizeFloat(startW + deltaX * step, step);
value = Math.max(minDim, value);
maxDim && (value = Math.min(maxDim, value));
box.w = value;
} }
if (~attr.indexOf('b')) { if (~attr.indexOf('b')) {
box.h = Math.max(minDim, startDim.h + data.delta.y); value = normalizeFloat(startH + deltaY * step, step);
value = Math.max(minDim, value);
maxDim && (value = Math.min(maxDim, value));
box.h = value;
} }
if (~attr.indexOf('l')) { if (~attr.indexOf('l')) {
box.w = Math.max(minDim, startDim.w - data.delta.x); value = normalizeFloat(startW - deltaX * step, step);
value = Math.max(minDim, value);
maxDim && (value = Math.min(maxDim, value));
box.w = value;
} }
if (~attr.indexOf('t')) { if (~attr.indexOf('t')) {
box.h = Math.max(minDim, startDim.h - data.delta.y); value = normalizeFloat(startH - deltaY * step, step);
value = Math.max(minDim, value);
maxDim && (value = Math.min(maxDim, value));
box.h = value;
} }
// Enforce aspect ratio (unless shift key is being held) // Enforce aspect ratio (unless shift key is being held)

33
src/utils/Sorter.js

@ -148,32 +148,35 @@ module.exports = Backbone.View.extend({
* @param {Event} e * @param {Event} e
*/ */
moveDragHelper(e) { moveDragHelper(e) {
var doc = e.target.ownerDocument; const doc = e.target.ownerDocument;
if(!this.dragHelper || !doc) { if(!this.dragHelper || !doc) {
return; return;
} }
var addTop = 0; let posY = e.pageY;
var addLeft = 0; let posX = e.pageX;
var window = doc.defaultView || doc.parentWindow; let addTop = 0;
var frame = window.frameElement; let addLeft = 0;
var dragHelperStyle = this.dragHelper.style; const window = doc.defaultView || doc.parentWindow;
const frame = window.frameElement;
const dragHelperStyle = this.dragHelper.style;
// If frame is present that means mouse has moved over the editor's canvas, // If frame is present that means mouse has moved over the editor's canvas,
// which is rendered inside the iframe and the mouse move event comes from // which is rendered inside the iframe and the mouse move event comes from
// the iframe, not the parent window. Mouse position relative to the frame's // the iframe, not the parent window. Mouse position relative to the frame's
// parent window needs to account for the frame's position relative to the // parent window needs to account for the frame's position relative to the
// parent window. // parent window.
if(frame) { if (frame) {
// TODO: Cache ?!? const frameRect = frame.getBoundingClientRect();
var frameRect = frame.getBoundingClientRect(); addTop = frameRect.top + document.documentElement.scrollTop;
var ownerDocBodyRect = frame.ownerDocument.body.getBoundingClientRect(); addLeft = frameRect.left + document.documentElement.scrollLeft;
addTop = frameRect.top - ownerDocBodyRect.top - doc.body.scrollTop; posY = e.clientY;
addLeft = frameRect.left - ownerDocBodyRect.left - doc.body.scrollLeft; posX = e.clientX;
} }
dragHelperStyle.top = (e.pageY + addTop) + 'px';
dragHelperStyle.left = (e.pageX + addLeft) + 'px'; dragHelperStyle.top = (posY + addTop) + 'px';
dragHelperStyle.left = (posX + addLeft) + 'px';
}, },

14
src/utils/mixins.js

@ -78,6 +78,19 @@ const camelCase = value => {
return values[0].toLowerCase() + values.slice(1).map(upFirst); return values[0].toLowerCase() + values.slice(1).map(upFirst);
} }
const normalizeFloat = (value, step = 1, valueDef = 0) => {
let stepDecimals = 0;
if (isNaN(value)) return valueDef;
value = parseFloat(value);
if (Math.floor(value) !== value) {
const side = step.toString().split('.')[1];
stepDecimals = side ? side.length : 0;
}
return stepDecimals ? parseFloat(value.toFixed(stepDecimals)) : value;
}
export { export {
on, on,
@ -86,5 +99,6 @@ export {
matches, matches,
camelCase, camelCase,
shallowDiff, shallowDiff,
normalizeFloat,
getUnitFromValue getUnitFromValue
} }

18
test/specs/css_composer/index.js

@ -180,6 +180,24 @@ describe('Css Composer', () => {
expect(rule.selectorsToString()).toEqual(`#${name}:${state}`); expect(rule.selectorsToString()).toEqual(`#${name}:${state}`);
}); });
it('Create a rule with class selector by using setClassRule()', () => {
const name = 'test';
obj.setClassRule(name, {color: 'red'});
expect(obj.getAll().length).toEqual(1);
const rule = obj.getClassRule(name);
expect(rule.selectorsToString()).toEqual(`.${name}`);
expect(rule.styleToString()).toEqual(`color:red;`);
});
it('Create a rule with class selector and state by using setClassRule()', () => {
const name = 'test';
const state = 'hover';
obj.setClassRule(name, {color: 'red'}, {state});
expect(obj.getAll().length).toEqual(1);
const rule = obj.getClassRule(name, {state});
expect(rule.selectorsToString()).toEqual(`.${name}:${state}`);
});
}); });
Models.run(); Models.run();

8
test/specs/parser/model/ParserHtml.js

@ -283,15 +283,17 @@ module.exports = {
var str = '<div>content1 <div><span>nested</span></div> content2</div>'; var str = '<div>content1 <div><span>nested</span></div> content2</div>';
var result = { var result = {
tagName: 'div', tagName: 'div',
type: 'text',
components: [{ components: [{
tagName: '', tagName: '',
type: 'textnode', type: 'textnode',
content: 'content1 ', content: 'content1 ',
},{ },{
tagName: 'div', tagName: 'div',
type: 'text', components: [{
content: 'nested', tagName: 'span',
type: 'text',
content: 'nested',
}],
},{ },{
tagName: '', tagName: '',
type: 'textnode', type: 'textnode',

Loading…
Cancel
Save