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>
<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.
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-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-blocks-flexbox](https://github.com/artf/grapesjs-blocks-flexbox) - Add the flexbox block
### Presets
* [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)
<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/1/website" target="_blank"><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/3/website" target="_blank"><img src="https://opencollective.com/grapesjs/sponsors/3/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"><img src="https://opencollective.com/grapesjs/sponsors/1/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"><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/1/website" target="_blank"><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/3/website" target="_blank"><img src="https://opencollective.com/grapesjs/backers/3/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"><img src="https://opencollective.com/grapesjs/backers/1/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"><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",
"version": "0.12.45",
"version": "0.12.52",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

2
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.12.45",
"version": "0.12.52",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"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
// 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
openAssetsOnDrop: 1,

10
src/canvas/view/FrameView.js

@ -16,17 +16,19 @@ module.exports = require('backbone').View.extend({
this.config = o.config || {};
this.ppfx = this.config.pStylePrefix || '';
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
*/
updateWidth(model) {
updateDim(model) {
const em = this.em;
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();
em.stopDefault({ preserveSelected: 1 });
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, {
dragDrop: false,
lineWrapping: true,
lineNumbers: this.get('lineNumbers'),
readOnly: this.get('readOnly'),
mode: this.get('codeName'),
theme: this.get('theme'),
...this.attributes,
});
return this;

8
src/code_manager/model/CssGenerator.js

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

3
src/commands/index.js

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

97
src/commands/view/ExportTemplate.js

@ -2,73 +2,54 @@ const $ = Backbone.$;
module.exports = {
run(editor, sender) {
this.sender = sender;
this.wrapper = editor.DomComponents.getWrapper();
this.components = editor.DomComponents.getComponents();
this.modal = editor.Modal || null;
run(editor, sender, opts = {}) {
sender && sender.set && sender.set('active', 0);
const config = editor.getConfig();
const modal = editor.Modal;
const pfx = config.stylePrefix;
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({
label,
codeName,
theme,
input: $input[0],
}),
modal.setTitle(config.textViewCode);
modal.setContent(this.$editors);
modal.open();
this.htmlEditor.setContent(editor.getHtml());
this.cssEditor.setContent(editor.getCss());
},
$editor = new this.cm.EditorView({
model: editor,
config: this.cm.getConfig()
}).render().$el;
stop(editor) {
const modal = editor.Modal;
modal && modal.close();
},
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() {
if(!this.$editors){
var oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML');
var oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS');
this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el;
this.$editors = $(`<div class="${this.pfx}export-dl">`);
this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el);
}
const $el = new this.cm.EditorView({
model: el,
config: this.cm.getConfig()
}).render().$el;
if(this.modal){
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());
el.init(input);
if(this.sender)
this.sender.set('active',false);
return { el, $el };
},
stop() {}
};

14
src/commands/view/SelectComponent.js

@ -447,11 +447,8 @@ module.exports = {
}
this.toolbar.reset(toolbar);
var view = model.view;
if(view) {
this.updateToolbarPos(view.el);
}
const view = model.view;
view && this.updateToolbarPos(view.el);
} else {
toolbarStyle.display = 'none';
}
@ -466,6 +463,7 @@ module.exports = {
var unit = 'px';
var toolbarEl = this.canvas.getToolbarEl();
var toolbarStyle = toolbarEl.style;
toolbarStyle.display = 'block';
var pos = this.canvas.getTargetToElementDim(toolbarEl, el, {
elPos,
event: 'toolbarPosUpdate',
@ -473,6 +471,7 @@ module.exports = {
var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
toolbarStyle.top = pos.top + unit;
toolbarStyle.left = leftPos + unit;
toolbarStyle.display = '';
},
/**
@ -522,8 +521,9 @@ module.exports = {
* Update attached elements, eg. component toolbar
* @return {[type]} [description]
*/
updateAttached() {
var model = this.em.get('selectedComponent');
updateAttached(updated) {
const model = this.em.getSelected();
if (model) {
var view = model.view;
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
* 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 {Object} style Style properties and values
* @param {Object} [opts={}] Custom options
* @return {CssRule}
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @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 = {}) {
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 {Object} [opts={}] Custom options
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @example
* const rule = cc.getIdRule('myid');
* const ruleHover = cc.setIdRule('myid', { state: 'hover' });
*/
getIdRule(name, opts = {}) {
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
* @return {HTMLElement}

2
src/css_composer/view/CssRuleView.js

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

27
src/css_composer/view/CssRulesView.js

@ -4,11 +4,14 @@ var CssRuleView = require('./CssRuleView');
module.exports = Backbone.View.extend({
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
const config = o.config || {};
this.config = config;
this.em = config.em;
this.pfx = config.stylePrefix || '';
this.className = this.pfx + 'rules';
this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render );
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo );
this.listenTo(coll, 'reset', this.render );
},
/**
@ -17,7 +20,6 @@ module.exports = Backbone.View.extend({
* @private
* */
addTo(model) {
//console.log('Added');
this.addToCollection(model);
},
@ -47,15 +49,12 @@ module.exports = Backbone.View.extend({
},
render() {
var fragment = document.createDocumentFragment();
this.$el.empty();
this.collection.each(function(model){
this.addToCollection(model, fragment);
}, this);
this.$el.append(fragment);
this.$el.attr('class', this.className);
const $el = this.$el;
const frag = document.createDocumentFragment();
$el.empty();
this.collection.each(model => this.addToCollection(model, frag));
$el.append(frag);
$el.attr('class', this.className);
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:
*
* ```js
@ -41,6 +37,7 @@ module.exports = () => {
* }
* ...
* @return {this}
* @private
*/
init(config) {
c = config || {};
@ -65,6 +62,10 @@ module.exports = () => {
* @return {Device} Added device
* @example
* 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) {
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: '',
// Height to set for the editor iframe
height: '',
// The width which will be used in media queries,
// If empty the width will be used
widthMedia: null,

1
src/dom_components/config/config.js

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

15
src/dom_components/index.js

@ -57,6 +57,21 @@ module.exports = () => {
model: require('./model/ComponentTable'),
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',
model: require('./model/ComponentMap'),

12
src/dom_components/model/Component.js

@ -169,6 +169,18 @@ module.exports = Backbone.Model.extend(Styleable).extend({
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

84
src/dom_components/model/ComponentTable.js

@ -1,88 +1,28 @@
var Component = require('./Component');
const Component = require('./Component');
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
defaults: { ...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,
}]
*/
}),
droppable: ['tbody', 'thead', 'tfoot'],
},
initialize(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(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);
const components = this.get('components');
!components.length && components.add({ type: 'tbody' });
},
},{
}, {
/**
* 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) {
var result = '';
if(el.tagName == 'TABLE'){
result = {type: 'table'};
let result = '';
if (el.tagName == 'TABLE') {
result = { type: 'table' };
}
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({
defaults: _.extend({}, Component.prototype.defaults, {
defaults: { ...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(el) {
var result = '';
var tag = el.tagName;
if(tag == 'TD' || tag == 'TH'){
let result = '';
const tag = el.tagName;
if (tag == 'TD' || tag == 'TH') {
result = {
type: 'cell',
tagName: tag.toLowerCase()
};
}
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({
defaults: _.extend({}, Component.prototype.defaults, {
defaults: { ...Component.prototype.defaults,
type: 'row',
tagName: 'tr',
draggable: ['table', 'tbody', 'thead'],
draggable: ['thead', 'tbody', 'tfoot'],
droppable: ['th', 'td']
}),
},
initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments);
// Clean the row from non cell components
var cells = [];
var components = this.get('components');
components.each(model => {
if(model.get('type') == 'cell'){
cells.push(model);
}
});
const cells = [];
const components = this.get('components');
components.each(model => model.is('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(el) {
var result = '';
if(el.tagName == 'TR'){
result = {type: 'row'};
let result = '';
if (el.tagName == 'TR') {
result = { type: 'row' };
}
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
model.set('content', '');
model.set('content', '').trigger('change:content', model);
comps.add(content);
comps.each(model => clean(model));
comps.trigger('resetNavigator');

14
src/domain_abstract/ui/InputColor.js

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

6
src/editor/config/config.js

@ -31,7 +31,7 @@ module.exports = {
width: '100%',
// 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
// (eg. the style of the selected component)
@ -175,4 +175,8 @@ module.exports = {
//Configurations for Block Manager
blockManager: {},
// Texts
textViewCode: 'Code',
};

22
src/editor/index.js

@ -7,16 +7,19 @@
* ```
*
* # Available Events
*
* ## Components
* * `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:{propertyName}` - Listen any property change
* * `component:styleUpdate` - Triggered when the style of the component is updated
* * `component:styleUpdate:{propertyName}` - Listen for a specific style property change
* * `component:selected` - New component selected
* * `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, the model is passed as an argument to the callback
* * `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, the model is passed as an argument to the callback
* * `component:selected` - New component selected, the selected model is passed as an argument to the callback
* ## Blocks
* * `block:add` - New block added
* * `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
* * `asset:add` - New asset added
* * `asset:remove` - Asset removed
@ -244,16 +247,17 @@ module.exports = config => {
* Returns HTML built inside canvas
* @return {string} HTML string
*/
getHtml() {
return em.getHtml();
getHtml(opts) {
return em.getHtml(opts);
},
/**
* Returns CSS built inside canvas
* @param {Object} [opts={}] Options
* @return {string} CSS string
*/
getCss() {
return em.getCss();
getCss(opts) {
return em.getCss(opts);
},
/**

10
src/editor/model/Editor.js

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

8
src/panels/view/ButtonView.js

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

2
src/parser/config/config.js

@ -1,5 +1,5 @@
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
* @param {HTMLElement} el DOM
* Get data from the node element
* @param {HTMLElement} el DOM element to traverse
* @return {Array<Object>}
*/
parseNode(el) {
var result = [];
var nodes = el.childNodes;
const result = [];
const nodes = el.childNodes;
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var model = {};
var attrs = node.attributes || [];
var attrsLen = attrs.length;
var prevI = result.length - 1;
var prevSib = result[prevI];
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;
}*/
for (var it = 0; it < ct.length; it++) {
var component = ct[it].model;
obj = component.isComponent(node);
if(obj)
break;
const node = nodes[i];
const attrs = node.attributes || [];
const attrsLen = attrs.length;
const nodePrev = result[result.length - 1];
const nodeChild = node.childNodes.length;
const ct = this.compTypes;
let model = {};
// Start with understanding what kind of component it is
if (ct) {
let obj = '';
// Iterate over all available Component Types and
// the first with a valid result will be that component
for (let it = 0; it < ct.length; it++) {
obj = ct[it].model.isComponent(node);
if (obj) break;
}
model = obj;
}
if(!model.tagName)
// Set tag name if not yet done
if (!model.tagName) {
model.tagName = node.tagName ? node.tagName.toLowerCase() : '';
}
if(attrsLen)
if (attrsLen) {
model.attributes = {};
}
// Store attributes
for (var j = 0; j < attrsLen; j++){
var nodeName = attrs[j].nodeName;
var nodeValue = attrs[j].nodeValue;
// Parse attributes
for (let j = 0; j < attrsLen; j++) {
const nodeName = attrs[j].nodeName;
let nodeValue = attrs[j].nodeValue;
//Isolate few attributes
if(nodeName == 'style')
// Isolate attributes
if (nodeName == 'style') {
model.style = this.parseStyle(nodeValue);
else if(nodeName == 'class')
} else if (nodeName == 'class') {
model.classes = this.parseClass(nodeValue);
else if (nodeName == 'contenteditable')
} else if (nodeName == 'contenteditable') {
continue;
else if (nodeName.indexOf(modelAttrStart) === 0) {
} else if (nodeName.indexOf(modelAttrStart) === 0) {
const modelAttr = nodeName.replace(modelAttrStart, '');
const valueLen = nodeValue.length;
const firstChar = nodeValue && nodeValue.substr(0, 1);
const lastChar = nodeValue && nodeValue.substr(valueLen - 1);
nodeValue = nodeValue === 'true' ? true : 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]')
// so put it under try/catch and let fail silently
try {
nodeValue = (firstChar == '{' && lastChar == '}') ||
(firstChar == '[' && lastChar == ']') ? JSON.parse(nodeValue) : nodeValue;
} catch (e) {}
model[modelAttr] = nodeValue;
} else {
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;
// Check for nested elements and avoid them if an array
// was already given
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';
}
// If there is only one child and it's a TEXTNODE
// just make it content of the current node
if (nodeChild === 1 && firstChild.nodeType === 3) {
!model.type && (model.type = 'text');
model.content = firstChild.nodeValue;
}else{
var parsed = 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;
} else {
model.components = this.parseNode(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 += model.content;
if (model.type == 'textnode') {
if (nodePrev && nodePrev.type == 'textnode') {
nodePrev.content += model.content;
continue;
}
// Throw away empty nodes (keep spaces)
var content = node.nodeValue;
if(content != ' ' && !content.trim()){
const content = node.nodeValue;
if (content != ' ' && !content.trim()) {
continue;
}
}
// 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 ){
const comps = model.components;
if (!model.type && comps) {
let allTxt = 1;
let foundTextNode = 0;
for (let ci = 0; ci < comps.length; ci++) {
const comp = comps[ci];
const cType = comp.type;
if (['text', 'textnode'].indexOf(cType) < 0 &&
c.textTags.indexOf(comp.tagName) < 0 ) {
allTxt = 0;
break;
}
if(comp.type == 'textnode')
if (cType == 'textnode') {
foundTextNode = 1;
}
}
if(allTxt && foundTextNode)
if (allTxt && foundTextNode) {
model.type = 'text';
}
}
// 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;
}
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
* be used to custom components and classes.
* 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
* @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
* @param {string} sectorId Sector id

6
src/style_manager/view/PropertyView.js

@ -339,9 +339,9 @@ module.exports = Backbone.View.extend({
}
if (em) {
em.trigger('component:update', model);
em.trigger('component:styleUpdate', model);
em.trigger('component:styleUpdate:' + model.get('property'), model);
em.trigger('component:update', target);
em.trigger('component:styleUpdate', target);
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;
this.listenTo(model, 'change:open', this.updateOpen);
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 {
background-color: rgba(0, 0, 0, 0.15);
box-sizing: border-box;
position: absolute;
width: (100% - $leftWidth);
height: 100%;
height: calc(100% - $canvasTop);
bottom: 0;
left: 0;
overflow: hidden;
padding-top: 40px;
z-index: 1;
> iframe {
height: 100%;
outline: medium none;
width: 100%;
border: none;
margin: 0 auto;
display: block;
}
position: absolute;
left: 0;
top: 40px;
.#{$app-prefix}ghost {
display: none;
@ -69,7 +63,18 @@
}
.#{$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 {

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

100
src/styles/scss/main.scss

@ -214,12 +214,13 @@ $prefix: $app-prefix;
}
.#{$app-prefix}editor {
font-family: $mainFont;
//background-color: $mainColor;
position:relative;
box-sizing: border-box;
height: 100%;
font-family: $mainFont;
font-size: $fontSize;
position: relative;
box-sizing: border-box;
height: 100%;
}
.#{$app-prefix}freezed,
.#{$nv-prefix}freezed {
@include opacity(0.50);
@ -459,36 +460,7 @@ $prefix: $app-prefix;
@import "gjs_inputs";
/********* Device Manager **********/
.#{$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 **********/
@import "gjs_devices";
/********* General **********/
@ -614,62 +586,7 @@ $prefix: $app-prefix;
/********* Modal dialog **********/
.#{$mdl-prefix}backlayer {
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;
}
@import "gjs_modal";
/********* Assets Manager **********/
@ -735,7 +652,6 @@ $prefix: $app-prefix;
}
&##{$cm-prefix}css {
padding-left: 10px;
border-left: 1px solid $mainLhColor;
##{$cm-prefix}title { color: #ddca7e;}
}
##{$cm-prefix}title {

4
src/trait_manager/view/TraitView.js

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

47
src/utils/Resizer.js

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

33
src/utils/Sorter.js

@ -148,32 +148,35 @@ module.exports = Backbone.View.extend({
* @param {Event} e
*/
moveDragHelper(e) {
var doc = e.target.ownerDocument;
const doc = e.target.ownerDocument;
if(!this.dragHelper || !doc) {
return;
}
var addTop = 0;
var addLeft = 0;
var window = doc.defaultView || doc.parentWindow;
var frame = window.frameElement;
var dragHelperStyle = this.dragHelper.style;
let posY = e.pageY;
let posX = e.pageX;
let addTop = 0;
let addLeft = 0;
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,
// 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
// parent window needs to account for the frame's position relative to the
// parent window.
if(frame) {
// TODO: Cache ?!?
var frameRect = frame.getBoundingClientRect();
var ownerDocBodyRect = frame.ownerDocument.body.getBoundingClientRect();
addTop = frameRect.top - ownerDocBodyRect.top - doc.body.scrollTop;
addLeft = frameRect.left - ownerDocBodyRect.left - doc.body.scrollLeft;
}
dragHelperStyle.top = (e.pageY + addTop) + 'px';
dragHelperStyle.left = (e.pageX + addLeft) + 'px';
if (frame) {
const frameRect = frame.getBoundingClientRect();
addTop = frameRect.top + document.documentElement.scrollTop;
addLeft = frameRect.left + document.documentElement.scrollLeft;
posY = e.clientY;
posX = e.clientX;
}
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);
}
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 {
on,
@ -86,5 +99,6 @@ export {
matches,
camelCase,
shallowDiff,
normalizeFloat,
getUnitFromValue
}

18
test/specs/css_composer/index.js

@ -180,6 +180,24 @@ describe('Css Composer', () => {
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();

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 result = {
tagName: 'div',
type: 'text',
components: [{
tagName: '',
type: 'textnode',
content: 'content1 ',
},{
tagName: 'div',
type: 'text',
content: 'nested',
components: [{
tagName: 'span',
type: 'text',
content: 'nested',
}],
},{
tagName: '',
type: 'textnode',

Loading…
Cancel
Save