Browse Source

Merge pull request #600 from artf/dev

Merge from dev
pull/883/head
Artur Arseniev 9 years ago
committed by GitHub
parent
commit
4d876a17e3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .npmignore
  2. 23
      README.md
  3. 2
      dist/css/grapes.min.css
  4. 46749
      dist/grapes.js
  5. 6
      dist/grapes.min.js
  6. 11
      package-lock.json
  7. 14
      package.json
  8. 4
      src/asset_manager/config/config.js
  9. 4
      src/asset_manager/model/AssetImage.js
  10. 4
      src/asset_manager/view/AssetImageView.js
  11. 7
      src/block_manager/view/BlockView.js
  12. 1
      src/canvas/view/CanvasView.js
  13. 23
      src/canvas/view/FrameView.js
  14. 1
      src/code_manager/index.js
  15. 4
      src/code_manager/model/CodeMirrorEditor.js
  16. 150
      src/code_manager/model/CssGenerator.js
  17. 28
      src/commands/index.js
  18. 97
      src/commands/view/ExportTemplate.js
  19. 2
      src/commands/view/Preview.js
  20. 42
      src/commands/view/SelectComponent.js
  21. 110
      src/css_composer/index.js
  22. 76
      src/css_composer/model/CssRule.js
  23. 24
      src/css_composer/model/Selectors.js
  24. 76
      src/css_composer/view/CssRuleView.js
  25. 27
      src/css_composer/view/CssRulesView.js
  26. 1
      src/dom_components/config/config.js
  27. 18
      src/dom_components/index.js
  28. 168
      src/dom_components/model/Component.js
  29. 5
      src/dom_components/model/ComponentImage.js
  30. 26
      src/dom_components/model/Components.js
  31. 31
      src/dom_components/view/ComponentImageView.js
  32. 2
      src/dom_components/view/ComponentTextView.js
  33. 65
      src/dom_components/view/ComponentView.js
  34. 9
      src/dom_components/view/ComponentsView.js
  35. 46
      src/domain_abstract/model/Styleable.js
  36. 17
      src/domain_abstract/ui/InputColor.js
  37. 12
      src/editor/config/config.js
  38. 80
      src/editor/index.js
  39. 97
      src/editor/model/Editor.js
  40. 2
      src/editor/view/EditorView.js
  41. 135
      src/keymaps/index.js
  42. 54
      src/modal_dialog/view/ModalView.js
  43. 82
      src/navigator/view/ItemView.js
  44. 42
      src/panels/index.js
  45. 65
      src/panels/view/ButtonView.js
  46. 18
      src/panels/view/PanelView.js
  47. 2
      src/parser/config/config.js
  48. 150
      src/parser/model/ParserHtml.js
  49. 2
      src/rich_text_editor/index.js
  50. 11
      src/selector_manager/index.js
  51. 18
      src/selector_manager/model/Selector.js
  52. 6
      src/selector_manager/model/Selectors.js
  53. 9
      src/selector_manager/view/ClassTagView.js
  54. 36
      src/selector_manager/view/ClassTagsView.js
  55. 2
      src/storage_manager/config/config.js
  56. 23
      src/storage_manager/index.js
  57. 20
      src/style_manager/index.js
  58. 4
      src/style_manager/model/Layers.js
  59. 2
      src/style_manager/model/Properties.js
  60. 7
      src/style_manager/model/Property.js
  61. 4
      src/style_manager/model/PropertyComposite.js
  62. 4
      src/style_manager/model/PropertyRadio.js
  63. 4
      src/style_manager/model/PropertyStack.js
  64. 24
      src/style_manager/model/Sector.js
  65. 1
      src/style_manager/view/LayerView.js
  66. 2
      src/style_manager/view/PropertyColorView.js
  67. 2
      src/style_manager/view/PropertyStackView.js
  68. 53
      src/style_manager/view/PropertyView.js
  69. 96
      src/style_manager/view/SectorsView.js
  70. 4
      src/styles/scss/_gjs_blocks.scss
  71. 20
      src/styles/scss/_gjs_devices.scss
  72. 7
      src/styles/scss/_gjs_inputs.scss
  73. 4
      src/styles/scss/_gjs_layers.scss
  74. 70
      src/styles/scss/_gjs_modal.scss
  75. 3
      src/styles/scss/_gjs_rte.scss
  76. 2
      src/styles/scss/_gjs_style_manager.scss
  77. 15
      src/styles/scss/_gjs_variables.scss
  78. 268
      src/styles/scss/main.scss
  79. 47
      src/utils/Resizer.js
  80. 128
      src/utils/Sorter.js
  81. 63
      src/utils/mixins.js
  82. 1
      test/main.js
  83. 2
      test/specs/asset_manager/view/AssetImageView.js
  84. 57
      test/specs/code_manager/model/CodeModels.js
  85. 5
      test/specs/css_composer/e2e/CssComposer.js
  86. 56
      test/specs/css_composer/index.js
  87. 24
      test/specs/css_composer/model/CssModels.js
  88. 20
      test/specs/css_composer/view/CssRuleView.js
  89. 4
      test/specs/dom_components/index.js
  90. 11
      test/specs/dom_components/model/Component.js
  91. 8
      test/specs/dom_components/view/ComponentV.js
  92. 34
      test/specs/grapesjs/index.js
  93. 60
      test/specs/keymaps/index.js
  94. 54
      test/specs/panels/index.js
  95. 4
      test/specs/panels/view/ButtonView.js
  96. 3
      test/specs/panels/view/PanelView.js
  97. 8
      test/specs/parser/model/ParserHtml.js
  98. 2
      test/specs/selector_manager/index.js
  99. 21
      test/specs/selector_manager/model/SelectorModels.js
  100. 13
      test/specs/selector_manager/view/ClassTagsView.js

3
.npmignore

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

23
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
@ -190,8 +185,10 @@ $ npm test
* [grapesjs-plugin-forms](https://github.com/artf/grapesjs-plugin-forms) - Set of form components and blocks
* [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
### Presets
* [grapesjs-preset-webpage](https://github.com/artf/grapesjs-preset-webpage) - Webpage Builder
* [grapesjs-preset-newsletter](https://github.com/artf/grapesjs-preset-newsletter) - Newsletter Builder
* [grapesjs-mjml](https://github.com/artf/grapesjs-mjml) - Newsletter Builder with MJML components
@ -208,15 +205,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

46749
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

11
package-lock.json

@ -1,6 +1,6 @@
{
"name": "grapesjs",
"version": "0.12.25",
"version": "0.12.50",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -7920,9 +7920,9 @@
}
},
"node-sass": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.6.0.tgz",
"integrity": "sha512-rh0CvkxpYdQdbWx4EQfunmG0+99BVyVwQHlFE+yUzc6lteF5K3WUcJ0bdmv9E9CqQA1RfuMyvmpDP99cmBObow==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz",
"integrity": "sha1-0JydEXlkEjnRuX/8YjH9zsU+FWg=",
"dev": true,
"requires": {
"async-foreach": "0.1.3",
@ -8704,7 +8704,8 @@
"promise-polyfill": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.0.2.tgz",
"integrity": "sha1-2chtPcTcLfkBboiUbe/Wm0m0EWI="
"integrity": "sha1-2chtPcTcLfkBboiUbe/Wm0m0EWI=",
"dev": true
},
"promised-io": {
"version": "0.3.5",

14
package.json

@ -1,11 +1,11 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.12.25",
"version": "0.12.50",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
"main": "dist/grapes.min.js",
"main": "dist/grapes.js",
"repository": {
"type": "git",
"url": "https://github.com/artf/grapesjs.git"
@ -18,7 +18,6 @@
"codemirror-formatting": "^1.0.0",
"font-awesome": "^4.7.0",
"keymaster": "^1.6.2",
"promise-polyfill": "^6.0.2",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.8.3"
},
@ -26,6 +25,7 @@
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"promise-polyfill": "^6.0.2",
"babel-preset-env": "^1.6.1",
"chai": "^4.1.2",
"cross-env": "^5.0.5",
@ -74,11 +74,13 @@
},
"scripts": {
"lint": "eslint src",
"build": "cross-env WEBPACK_ENV=prod && npm run lint && npm run test && npm run v:patch && webpack && npm run build:css",
"build-n": "cross-env WEBPACK_ENV=prod && npm run lint && npm run test && webpack && npm run build:css",
"check": "npm run lint && npm run test",
"build": "npm run check && npm run v:patch && npm run build-dev && webpack --env=prod",
"build-n": "npm run check && npm run build:css && webpack --env=prod",
"build-dev": "webpack --env=dev && npm run build:css",
"build:css": "node-sass src/styles/scss/main.scss dist/css/grapes.min.css --output-style compressed",
"v:patch": "npm version --no-git-tag-version patch",
"start": "cross-env WEBPACK_ENV=dev webpack-dev-server --progress --colors & npm run build:css -- -w",
"start": "webpack-dev-server --open --progress --colors & npm run build:css -- -w",
"test": "cross-env NODE_PATH=./src mocha --compilers js:babel-core/register --require test/helper.js --timeout 10000 --recursive test/main.js",
"test:dev": "npm test -- -R min -w"
}

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,

4
src/asset_manager/model/AssetImage.js

@ -2,11 +2,11 @@ const Asset = require('./Asset');
module.exports = Asset.extend({
defaults: Object.assign({}, Asset.prototype.defaults, {
defaults: { ...Asset.prototype.defaults,
type: 'image',
unitDim: 'px',
height: 0,
width: 0,
}),
},
});

4
src/asset_manager/view/AssetImageView.js

@ -1,9 +1,9 @@
module.exports = require('./AssetView').extend({
events: {
'click [data-toggle=asset-remove]': 'onRemove',
click: 'onClick',
dblclick: 'onDblClick',
'click [data-toggle=asset-remove]': 'onRemove',
},
getPreview() {
@ -79,7 +79,7 @@ module.exports = require('./AssetView').extend({
* @private
* */
onRemove(e) {
e.stopPropagation();
e.stopImmediatePropagation();
this.model.collection.remove(this.model);
}
});

7
src/block_manager/view/BlockView.js

@ -30,8 +30,8 @@ module.exports = Backbone.View.extend({
this.config.em.refreshCanvas();
var sorter = this.config.getSorter();
sorter.setDragHelper(this.el, e);
sorter.startSort(this.el);
sorter.setDropContent(this.model.get('content'));
sorter.startSort(this.el);
on(document, 'mouseup', this.endDrag);
},
@ -53,8 +53,9 @@ module.exports = Backbone.View.extend({
render() {
const el = this.el;
const className = `${this.ppfx}block`;
el.className += ` ${className}`;
const pfx = this.ppfx;
const className = `${pfx}block`;
el.className += ` ${className} ${pfx}one-bg ${pfx}four-color-h`;
el.innerHTML = `<div class="${className}-label">${this.model.get('label')}</div>`;
return this;
},

1
src/canvas/view/CanvasView.js

@ -5,6 +5,7 @@ module.exports = Backbone.View.extend({
initialize(o) {
_.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff');
window.onscroll = this.clearOff
this.config = o.config || {};
this.em = this.config.em || {};
this.ppfx = this.config.pStylePrefix || '';

23
src/canvas/view/FrameView.js

@ -1,6 +1,8 @@
var Backbone = require('backbone');
import { bindAll } from 'underscore'
module.exports = Backbone.View.extend({
const motionsEv = 'transitionend oTransitionEnd transitionend webkitTransitionEnd';
module.exports = require('backbone').View.extend({
tagName: 'iframe',
@ -10,11 +12,10 @@ module.exports = Backbone.View.extend({
},
initialize(o) {
_.bindAll(this, 'udpateOffset');
bindAll(this, 'udpateOffset');
this.config = o.config || {};
this.ppfx = this.config.pStylePrefix || '';
this.em = this.config.em;
this.motionsEv = 'transitionend oTransitionEnd transitionend webkitTransitionEnd';
this.listenTo(this.em, 'change:device', this.updateWidth);
},
@ -23,16 +24,20 @@ module.exports = Backbone.View.extend({
* @private
*/
updateWidth(model) {
var device = this.em.getDeviceModel();
const em = this.em;
const device = em.getDeviceModel();
this.el.style.width = device ? device.get('width') : '';
this.udpateOffset();
this.$el.on(this.motionsEv, this.udpateOffset);
em.stopDefault({ preserveSelected: 1 });
this.$el.on(motionsEv, this.udpateOffset);
},
udpateOffset() {
var offset = this.em.get('Canvas').getOffset();
this.em.set('canvasOffset', offset);
this.$el.off(this.motionsEv, this.udpateOffset);
const em = this.em;
const offset = em.get('Canvas').getOffset();
em.set('canvasOffset', offset);
em.runDefault({ preserveSelected: 1 });
this.$el.off(motionsEv, this.udpateOffset);
},
getBody() {

1
src/code_manager/index.js

@ -182,6 +182,7 @@ module.exports = () => {
* var codeStr = codeManager.getCode(model, 'html');
* */
getCode(model, genId, opt = {}) {
opt.em = c.em;
var generator = this.getGenerator(genId);
return generator ? generator.build(model, opt) : '';
},

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;

150
src/code_manager/model/CssGenerator.js

@ -1,107 +1,81 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
module.exports = require('backbone').Model.extend({
initialize() {
this.compCls = [];
this.ids = [];
},
/**
* Get CSS from component
* Get CSS from a component
* @param {Model} model
* @return {String}
*/
buildFromModel(model, opts = {}) {
var code = '';
var style = model.get('style');
var classes = model.get('classes');
let code = '';
const em = this.em;
const avoidInline = em && em.getConfig('avoidInlineStyle');
const style = model.styleToString();
const classes = model.get('classes');
const wrappesIsBody = opts.wrappesIsBody;
const isWrapper = model.get('wrapper');
this.ids.push(`#${model.getId()}`);
// Let's know what classes I've found
if(classes) {
classes.each(function(model){
this.compCls.push(model.get('name'));
}, this);
}
classes.each(model => this.compCls.push(model.getFullName()));
if(style && Object.keys(style).length !== 0) {
if ((!avoidInline || isWrapper) && style) {
let selector = `#${model.getId()}`;
selector = wrappesIsBody && model.get('wrapper') ?
'body' : selector;
code += `${selector}{`;
for(var prop in style){
if(style.hasOwnProperty(prop))
code += prop + ':' + style[prop] + ';';
}
code += '}';
selector = wrappesIsBody && isWrapper ? 'body' : selector;
code = `${selector}{${style}}`;
}
const components = model.components();
components.each(model => code += this.buildFromModel(model, opts));
return code;
},
/**
* Get CSS from components
* @param {Model} model
* @return {String}
*/
buildFromComp(model) {
var coll = model.get('components') || model,
code = '';
coll.each(function(m) {
var cln = m.get('components');
code += this.buildFromModel(m);
if(cln.length){
code += this.buildFromComp(cln);
}
}, this);
return code;
},
/** @inheritdoc */
build(model, opts = {}) {
const cssc = opts.cssc;
this.em = opts.em || '';
this.compCls = [];
this.ids = [];
var code = this.buildFromModel(model, opts);
code += this.buildFromComp(model);
var compCls = this.compCls;
if (cssc) {
var rules = cssc.getAll();
var mediaRules = {};
rules.each(function(rule) {
var width = rule.get('mediaText');
// If width setted will render it later
if(width){
var mRule = mediaRules[width];
if(mRule)
mRule.push(rule);
else
mediaRules[width] = [rule];
const rules = cssc.getAll();
const mediaRules = {};
rules.each(rule => {
const media = rule.get('mediaText');
// If media is setted, I'll render it later
if (media) {
const mRules = mediaRules[media];
if (mRules) {
mRules.push(rule);
} else {
mediaRules[media] = [rule];
}
return;
}
code += this.buildFromRule(rule);
}, this);
});
// Get media rules
for (var ruleW in mediaRules) {
var meRules = mediaRules[ruleW];
var ruleC = '';
for(var i = 0, len = meRules.length; i < len; i++){
ruleC += this.buildFromRule(meRules[i]);
}
for (let media in mediaRules) {
let rulesStr = '';
const mRules = mediaRules[media];
mRules.forEach(rule => rulesStr += this.buildFromRule(rule));
if (ruleC) {
code += '@media ' + ruleW + '{' + ruleC + '}';
if (rulesStr) {
code += `@media ${media}{${rulesStr}}`;
}
}
}
return code;
},
@ -111,39 +85,25 @@ module.exports = Backbone.Model.extend({
* @return {string} CSS string
*/
buildFromRule(rule) {
var result = '';
var selectorsAdd = rule.get('selectorsAdd');
var selectors = rule.get('selectors');
var ruleStyle = rule.get('style');
var state = rule.get('state');
var strSel = '';
var found = 0;
var compCls = this.compCls;
// Get string of selectors
selectors.each(selector => {
strSel += '.' + selector.get('name');
if(compCls.indexOf(selector.get('name')) > -1)
let result = '';
const selectorStr = rule.selectorsToString();
const selectorStrNoAdd = rule.selectorsToString({skipAdd: 1});
let found;
// This will not render a rule if there is no its component
rule.get('selectors').each(selector => {
const name = selector.getFullName();
if (this.compCls.indexOf(name) >= 0 || this.ids.indexOf(name) >= 0) {
found = 1;
}
});
// With 'found' will skip rules which selectors are not found in
// canvas components.
if ((strSel && found) || selectorsAdd) {
strSel += state ? ':' + state : '';
strSel += selectorsAdd ? (strSel ? ', ' : '') + selectorsAdd : '';
var strStyle = '';
// Get string of style properties
if(ruleStyle && Object.keys(ruleStyle).length !== 0){
for(var prop2 in ruleStyle){
if(ruleStyle.hasOwnProperty(prop2))
strStyle += prop2 + ':' + ruleStyle[prop2] + ';';
}
}
if ((selectorStrNoAdd && found) || rule.get('selectorsAdd')) {
const style = rule.styleToString();
if(strStyle)
result += strSel + '{' + strStyle + '}';
if (style) {
result += `${selectorStr}{${style}}`;
}
}
return result;

28
src/commands/index.js

@ -38,6 +38,7 @@
* },
* ...
*/
import { isFunction } from 'underscore';
module.exports = () => {
var c = {},
@ -47,7 +48,11 @@ module.exports = () => {
AbsCommands = require('./view/CommandAbstract');
// Need it here as it would be used below
var add = function(id, obj){
var add = function(id, obj) {
if (isFunction(obj)) {
obj = { run: obj };
}
delete obj.initialize;
commands[id] = AbsCommands.extend(obj);
return this;
@ -85,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');
@ -92,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');
@ -193,6 +199,14 @@ module.exports = () => {
},
};
// Core commands
defaultCommands['core:undo'] = e => e.UndoManager.undo(1);
defaultCommands['core:redo'] = e => e.UndoManager.redo(1);
defaultCommands['core:canvas-clear'] = e => {
e.DomComponents.clear();
e.CssComposer.clear();
};
if(c.em)
c.model = c.em.get('Canvas');
@ -204,16 +218,20 @@ module.exports = () => {
/**
* Add new command to the collection
* @param {string} id Command's ID
* @param {Object} command Object representing you command. Methods `run` and `stop` are required
* @param {Object|Function} command Object representing your command,
* By passing just a function it's intended as a stateless command
* (just like passing an object with only `run` method).
* @return {this}
* @example
* commands.add('myCommand', {
* run: function(editor, sender){
* run(editor, sender) {
* alert('Hello world!');
* },
* stop: function(editor, sender){
* stop(editor, sender) {
* },
* });
* // As a function
* commands.add('myCommand2', editor => { ... });
* */
add,

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() {}
};

2
src/commands/view/Preview.js

@ -28,7 +28,7 @@ module.exports = {
this.helper.className = pfx + 'off-prv fa fa-eye-slash';
editorEl.appendChild(this.helper);
this.helper.onclick = () => {
that.stop(editor);
editor.stopCommand('preview');
};
}
this.helper.style.display = 'inline-block';

42
src/commands/view/SelectComponent.js

@ -274,7 +274,7 @@ module.exports = {
var $el = $(el);
var model = $el.data('model');
if(!model || (model && model.get('status') == 'selected')) {
if(!model || !model.get("hoverable") || model.get('status') == 'selected') {
return;
}
@ -392,7 +392,7 @@ module.exports = {
modelToStyle.setStyle(style, {avoidStore: 1});
const updateEvent = `update:component:style`;
em && em.trigger(`${updateEvent}:height ${updateEvent}:width`);
em && em.trigger(`${updateEvent}:${keyHeight} ${updateEvent}:${keyWidth}`);
if (store) {
modelToStyle.trigger('change:style', modelToStyle, style, {});
@ -401,7 +401,7 @@ module.exports = {
};
if (typeof resizable == 'object') {
options = Object.assign(options, resizable);
options = { ...options, ...resizable };
}
editor.runCommand('resize', {el, options});
@ -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);
@ -555,11 +555,10 @@ module.exports = {
* @private
*/
cleanPrevious(model) {
if(model)
model.set({
status: '',
state: '',
});
model && model.set({
status: '',
state: '',
});
},
/**
@ -573,20 +572,21 @@ module.exports = {
run(editor) {
this.editor = editor && editor.get('Editor');
this.enable();
this.onSelect();
},
stop() {
stop(editor, sender, opts = {}) {
const em = this.em;
this.stopSelectComponent();
this.cleanPrevious(this.em.get('selectedComponent'));
!opts.preserveSelected && em.setSelected(null);
this.clean();
this.em.set('selectedComponent', null);
this.toggleClipboard();
this.hideBadge();
this.hideFixedElementOffset();
this.canvas.getToolbarEl().style.display = 'none';
this.em.off('component:update', this.updateAttached, this);
this.em.off('change:canvasOffset', this.updateAttached, this);
this.em.off('change:selectedComponent', this.updateToolbar, this);
em.off('component:update', this.updateAttached, this);
em.off('change:canvasOffset', this.updateAttached, this);
em.off('change:selectedComponent', this.updateToolbar, this);
}
};

110
src/css_composer/index.js

@ -1,9 +1,15 @@
/**
* * [add](#add)
* * [get](#get)
* * [set](#set)
* * [getAll](#getall)
* * [clear](#clear)
* * [load](#load)
* * [store](#store)
* * [setIdRule](#setidrule)
* * [getIdRule](#getidrule)
* * [setClassRule](#setclassrule)
* * [getClassRule](#getclassrule)
*
* 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:
@ -23,12 +29,14 @@
*/
module.exports = () => {
let em;
var c = {},
defaults = require('./config/config'),
CssRule = require('./model/CssRule'),
CssRules = require('./model/CssRules'),
Selectors = require('./model/Selectors'),
CssRulesView = require('./view/CssRulesView');
const Selectors = require('selector_manager/model/Selectors');
const Selector = require('selector_manager/model/Selector');
var rules, rulesView;
@ -78,6 +86,7 @@ module.exports = () => {
c.rules = elStyle || c.rules;
c.sm = c.em;
em = c.em;
rules = new CssRules([], c);
rulesView = new CssRulesView({
collection: rules,
@ -225,6 +234,17 @@ module.exports = () => {
return rules;
},
/**
* Remove all rules
* @return {this}
*/
clear() {
this.getAll().reset();
return this;
},
/**
* Add a raw collection of rule objects
* This method overrides styles, in case, of already defined rule
@ -259,8 +279,7 @@ module.exports = () => {
const style = rule.style || {};
if (updateStyle) {
let styleUpdate = opts.extend ?
Object.assign({}, model.get('style'), style) : style;
let styleUpdate = opts.extend ? { ...model.get('style'), ...style } : style;
model.set('style', styleUpdate);
}
@ -270,6 +289,89 @@ module.exports = () => {
return result;
},
/**
* 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, 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 || '';
const media = opts.mediaText || em.getCurrentMedia();
const sm = em.get('SelectorManager');
const selector = sm.add({ name, type: Selector.TYPE_ID });
const rule = this.add(selector, state, media);
rule.setStyle(style, opts);
return rule;
},
/**
* Get the CSS rule by id selector
* @param {string} name Id selector name, eg. 'my-id'
* @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 || '';
const media = opts.mediaText || em.getCurrentMedia();
const selector = em.get('SelectorManager').get(name, Selector.TYPE_ID);
return selector && this.get(selector, state, media);
},
/**
* 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}
@ -277,7 +379,7 @@ module.exports = () => {
*/
render() {
return rulesView.render().el;
}
},
};
};

76
src/css_composer/model/CssRule.js

@ -1,7 +1,7 @@
import Styleable from 'domain_abstract/model/Styleable';
var Backbone = require('backbone');
var Selectors = require('./Selectors');
var Selectors = require('selector_manager/model/Selectors');
module.exports = Backbone.Model.extend(Styleable).extend({
@ -23,26 +23,72 @@ module.exports = Backbone.Model.extend(Styleable).extend({
// Indicates if the rule is stylable
stylable: true,
// If true, sets '!important' on all properties
// You can use an array to specify properties to set important
// Used in view
important: 0,
},
initialize(c, opt = {}) {
this.config = c || {};
const em = opt.em;
let selectors = this.config.selectors || [];
this.em = em;
if (em) {
const sm = em.get('SelectorManager');
const slct = [];
selectors.forEach((selector) => {
slct.push(sm.add(selector));
});
selectors = slct;
}
this.config = c || {};
const em = opt.em;
let selectors = this.config.selectors || [];
this.em = em;
if (em) {
const sm = em.get('SelectorManager');
const slct = [];
selectors.forEach((selector) => {
slct.push(sm.add(selector));
});
selectors = slct;
}
this.set('selectors', new Selectors(selectors));
},
/**
* Return selectors fo the rule as a string
* @return {string}
*/
selectorsToString(opts = {}) {
const result = [];
const state = this.get('state');
const addSelector = this.get('selectorsAdd');
const selectors = this.get('selectors').getFullString();
const stateStr = state ? `:${state}` : '';
selectors && result.push(`${selectors}${stateStr}`);
addSelector && !opts.skipAdd && result.push(addSelector);
return result.join(', ');
},
/**
* Returns CSS string of the rule
* @param {Object} [opts={}] Options
* @return {string}
*/
toCSS(opts = {}) {
let result = '';
const media = this.get('mediaText');
const style = this.styleToString(opts);
const selectors = this.selectorsToString();
if (selectors && style) {
result = `${selectors}{${style}}`;
}
this.set('selectors', new Selectors(selectors));
if (media && result) {
result = `@media ${media}{${result}}`;
}
return result;
},
/**
* Compare the actual model with parameters
* @param {Object} selectors Collection of selectors

24
src/css_composer/model/Selectors.js

@ -1,24 +0,0 @@
var Backbone = require('backbone');
module.exports = Backbone.Collection.extend({
initialize(models, opt) {
this.model = function(attrs, opts) {
var model;
switch(1){
default:
if(!this.ClassTag)
this.ClassTag = require("selector_manager/model/Selector");
model = new this.ClassTag(attrs, opts);
}
return model;
};
},
});

76
src/css_composer/view/CssRuleView.js

@ -1,76 +1,20 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
module.exports = require('backbone').View.extend({
tagName: 'style',
initialize(o) {
initialize(o = {}) {
this.config = o.config || {};
this.listenTo(this.model, 'change:style', this.render);
this.listenTo(this.model, 'change:state', this.render);
this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:mediaText', this.render);
this.listenTo(this.model.get('selectors'), 'change', this.selChanged);
const model = this.model;
const toTrack = 'change:style change:state change:mediaText';
this.listenTo(model, toTrack, this.render);
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model.get('selectors'), 'change', this.render);
},
/**
* Triggered when some selector is changed
* @private
*/
selChanged() {
this.selStr = this.renderSelectors();
this.render();
},
/**
* Returns string of selectors
* @return {String}
* @private
*/
renderSelectors() {
var sel = [];
var model = this.model;
var add = model.get('selectorsAdd');
model.get('selectors').each(m => {
sel.push('.' + m.get('name'));
});
var sels = sel.join('');
return sels + (sels && add ? ', ' : '') + add;
},
/**
* Returns string of properties
* @return {String}
* @private
*/
renderProperties() {
var sel = [],
props = this.model.get('style');
for (var prop in props){
sel.push(prop + ':' + props[prop] + ';');
}
return sel.join('');
},
render() {
var block = '',
selStr = '',
o = '';
if(!this.selStr)
this.selStr = this.renderSelectors();
var prpStr = this.renderProperties();
var stateStr = this.model.get('state');
var mediaText = this.model.get('mediaText');
if(this.selStr){
stateStr = stateStr ? ':' + stateStr : '';
block = prpStr !== '' ? '{' + prpStr + '}' : '';
}
o = this.selStr && block ? this.selStr + stateStr + block : '';
if(mediaText && o)
o = '@media ' + mediaText + '{' + o + '}';
this.$el.html(o);
const model = this.model;
const important = model.get('important');
this.el.innerHTML = this.model.toCSS({important});
return this;
},

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;
}
});

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,

18
src/dom_components/index.js

@ -33,10 +33,12 @@
* ...
*/
module.exports = () => {
var c = {},
defaults = require('./config/config'),
Component = require('./model/Component'),
ComponentView = require('./view/ComponentView');
var c = {};
const defaults = require('./config/config');
const Component = require('./model/Component');
const ComponentView = require('./view/ComponentView');
const Components = require('./model/Components');
const ComponentsView = require('./view/ComponentsView');
var component, componentView;
var componentTypes = [
@ -104,6 +106,10 @@ module.exports = () => {
return {
Components,
ComponentsView,
componentTypes,
/**
@ -170,13 +176,13 @@ module.exports = () => {
// Build wrapper
let components = c.components;
let wrapper = Object.assign({}, c.wrapper);
let wrapper = { ...c.wrapper };
wrapper['custom-name'] = c.wrapperName;
wrapper.wrapper = 1;
// Components might be a wrapper
if (components && components.constructor === Object && components.wrapper) {
wrapper = Object.assign({}, components);
wrapper = { ...components };
components = components.components || [];
wrapper.components = [];

168
src/dom_components/model/Component.js

@ -1,10 +1,11 @@
import { isUndefined, isArray } from 'underscore';
import { isUndefined, isArray, isEmpty, has, clone, isString, keys } from 'underscore';
import { shallowDiff } from 'utils/mixins';
import Styleable from 'domain_abstract/model/Styleable';
var Backbone = require('backbone');
var Components = require('./Components');
var Selectors = require('selector_manager/model/Selectors');
var Traits = require('trait_manager/model/Traits');
const Backbone = require('backbone');
const Components = require('./Components');
const Selectors = require('selector_manager/model/Selectors');
const Traits = require('trait_manager/model/Traits');
const escapeRegExp = (str) => {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
@ -34,9 +35,17 @@ module.exports = Backbone.Model.extend(Styleable).extend({
badgable: true,
// True if it's possible to style it
// Tip: Indicate an array of CSS properties which is possible to style
// Tip:
// Indicate an array of CSS properties which is possible to style, eg. ['color', 'width']
// All other properties will be hidden from the style manager
stylable: true,
// Indicate an array of style properties to show up which has been marked as `toRequire`
'stylable-require': '',
// Indicate an array of style properties which should be hidden from the style manager
unstylable: '',
// Highlightable with 'dotted' style if true
highlightable: true,
@ -53,6 +62,12 @@ module.exports = Backbone.Model.extend(Styleable).extend({
// Hide the component inside Layers
layerable: true,
// Allow component to be selected when clicked
selectable: true,
// Shows a highlight outline when hovering on the element if true
hoverable: true,
// This property is used by the HTML exporter as void elements do not
// have closing tag, eg. <br/>, <hr/>, etc.
void: false,
@ -113,7 +128,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
initialize(props = {}, opt = {}) {
const em = opt.sm || opt.em || {};
const em = opt.sm || opt.em || '';
// Propagate properties from parent if indicated
const parent = this.parent();
@ -142,7 +157,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.opt = opt;
this.sm = em;
this.em = em;
this.config = props;
this.config = opt.config || {};
this.set('attributes', this.get('attributes') || {});
this.listenTo(this, 'change:script', this.scriptUpdated);
this.listenTo(this, 'change:traits', this.traitsUpdated);
@ -150,18 +165,6 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.initClasses();
this.initComponents();
this.initToolbar();
// Normalize few properties from strings to arrays
var toNormalize = ['stylable'];
toNormalize.forEach(function(name) {
var value = this.get(name);
if (typeof value == 'string') {
var newValue = value.split(',').map(prop => prop.trim());
this.set(name, newValue);
}
}, this);
this.set('status', '');
this.init();
},
@ -190,12 +193,61 @@ module.exports = Backbone.Model.extend(Styleable).extend({
},
getStyle() {
const em = this.em;
if (em && em.getConfig('avoidInlineStyle')) {
const state = this.get('state');
const cc = em.get('CssComposer');
const rule = cc.getIdRule(this.getId(), { state });
this.rule = rule;
if (rule) {
return rule.getStyle();
}
}
return Styleable.getStyle.call(this);
},
setStyle(prop = {}, opts = {}) {
const em = this.em;
if (em && em.getConfig('avoidInlineStyle')) {
prop = isString(prop) ? this.parseStyle(prop) : prop;
const state = this.get('state');
const cc = em.get('CssComposer');
const propOrig = this.getStyle();
this.rule = cc.setIdRule(this.getId(), prop, { ...opts, state });
const diff = shallowDiff(propOrig, prop);
keys(diff).forEach(pr => this.trigger(`change:style:${pr}`));
} else {
prop = Styleable.setStyle.apply(this, arguments);
}
return prop;
},
/**
* Return attributes
* @return {Object}
*/
getAttributes() {
return this.get('attributes');
const classes = [];
const attributes = this.get('attributes') || {};
// Add classes
this.get('classes').each(cls => classes.push(cls.get('name')));
classes.length && (attributes.class = classes.join(' '));
// If style is not empty I need an ID attached to the component
if (!isEmpty(this.getStyle()) && !has(attributes, 'id')) {
attributes.id = this.getId();
}
return attributes;
},
@ -232,7 +284,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
initClasses() {
const classes = this.normalizeClasses(this.get('classes') || this.config.classes || []);
const classes = this.normalizeClasses(this.get('classes') || []);
this.set('classes', new Selectors(classes));
return this;
},
@ -321,7 +373,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
*/
traitsUpdated() {
let found = 0;
const attrs = Object.assign({}, this.get('attributes'));
const attrs = { ...this.get('attributes') };
const traits = this.get('traits');
if (!(traits instanceof Traits)) {
@ -434,21 +486,21 @@ module.exports = Backbone.Model.extend(Styleable).extend({
* @private
*/
clone(reset) {
var attr = _.clone(this.attributes),
comp = this.get('components'),
traits = this.get('traits'),
cls = this.get('classes');
const em = this.em;
const style = this.getStyle();
const attr = clone(this.attributes);
delete attr.attributes.id;
attr.components = [];
attr.classes = [];
attr.traits = [];
comp.each((md, i) => {
this.get('components').each((md, i) => {
attr.components[i] = md.clone(1);
});
traits.each((md, i) => {
this.get('traits').each((md, i) => {
attr.traits[i] = md.clone();
});
cls.each((md, i) => {
this.get('classes').each((md, i) => {
attr.classes[i] = md.get('name');
});
@ -459,6 +511,10 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.opt.collection = null;
}
if (em && em.getConfig('avoidInlineStyle') && !isEmpty(style)) {
attr.style = style;
}
return new this.constructor(attr, this.opt);
},
@ -494,43 +550,25 @@ module.exports = Backbone.Model.extend(Styleable).extend({
* @private
*/
toHTML(opts) {
var code = '';
var m = this;
var tag = m.get('tagName');
var idFound = 0;
var sTag = m.get('void');
var attrId = '';
var strAttr = '';
var attr = this.getAttrToHTML();
for (var prop in attr) {
if (prop == 'id') {
idFound = 1;
const model = this;
const attrs = [];
const classes = [];
const tag = model.get('tagName');
const sTag = model.get('void');
const attributes = this.getAttrToHTML();
for (let attr in attributes) {
const value = attributes[attr];
if (!isUndefined(value)) {
attrs.push(`${attr}="${value}"`);
}
var val = attr[prop];
strAttr += typeof val !== undefined && val !== '' ?
' ' + prop + '="' + val + '"' : '';
}
// Build the string of classes
var strCls = '';
m.get('classes').each(m => {
strCls += ' ' + m.get('name');
});
strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : '';
// If style is not empty I need an ID attached to the component
if(!_.isEmpty(m.get('style')) && !idFound)
attrId = ' id="' + m.getId() + '" ';
code += '<' + tag + strCls + attrId + strAttr + (sTag ? '/' : '') + '>' + m.get('content');
m.get('components').each(m => {
code += m.toHTML();
});
if(!sTag)
code += '</'+tag+'>';
let attrString = attrs.length ? ` ${attrs.join(' ')}` : '';
let code = `<${tag}${attrString}${sTag ? '/' : ''}>${model.get('content')}`;
model.get('components').each(comp => code += comp.toHTML());
!sTag && (code += `</${tag}>`);
return code;
},
@ -542,7 +580,7 @@ module.exports = Backbone.Model.extend(Styleable).extend({
* @private
*/
getAttrToHTML() {
var attr = this.get('attributes') || {};
var attr = this.getAttributes();
delete attr.style;
return attr;
},

5
src/dom_components/model/ComponentImage.js

@ -2,16 +2,17 @@ var Component = require('./Component');
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
defaults: { ...Component.prototype.defaults,
type: 'image',
tagName: 'img',
src: '',
void: 1,
droppable: 0,
editable: 1,
highlightable: 0,
resizable: 1,
traits: ['alt']
}),
},
initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments);

26
src/dom_components/model/Components.js

@ -1,4 +1,6 @@
var Backbone = require('backbone');
import { isEmpty } from 'underscore';
const Backbone = require('backbone');
module.exports = Backbone.Collection.extend({
@ -67,16 +69,18 @@ module.exports = Backbone.Collection.extend({
},
onAdd(model, c, opts) {
var style = model.get('style');
var em = this.editor;
if (!_.isEmpty(style) && em && em.get && em.get('Config').forceClass) {
var cssC = this.editor.get('CssComposer');
var newClass = this.editor.get('SelectorManager').add(model.cid);
model.set({style:{}});
model.get('classes').add(newClass);
var rule = cssC.add(newClass);
rule.set('style', style);
const em = this.editor;
const style = model.get('style');
const avoidInline = em && em.getConfig('avoidInlineStyle');
if (!isEmpty(style) && !avoidInline &&
em && em.get && em.get('Config').forceClass) {
var cssC = this.editor.get('CssComposer');
var newClass = this.editor.get('SelectorManager').add(model.cid);
model.set({style:{}});
model.get('classes').add(newClass);
var rule = cssC.add(newClass);
rule.set('style', style);
}
},

31
src/dom_components/view/ComponentImageView.js

@ -11,31 +11,29 @@ module.exports = ComponentView.extend({
},
initialize(o) {
const model = this.model;
ComponentView.prototype.initialize.apply(this, arguments);
this.listenTo(this.model, 'change:src', this.updateSrc);
this.listenTo(this.model, 'dblclick active', this.openModal);
this.classEmpty = this.ppfx + 'plh-image';
if(this.config.modal)
this.modal = this.config.modal;
if(this.config.am)
this.am = this.config.am;
this.listenTo(model, 'change:src', this.updateSrc);
this.listenTo(model, 'dblclick active', this.openModal);
this.classEmpty = `${this.ppfx}plh-image`;
const config = this.config;
config.modal && (this.modal = config.modal);
config.am && (this.am = config.am);
},
/**
* Update src attribute
* @private
* */
updateSrc() {
var src = this.model.get("src");
this.$el.attr('src', src);
if(!src)
this.$el.addClass(this.classEmpty);
else
this.$el.removeClass(this.classEmpty);
const src = this.model.get("src");
const el = this.$el;
el.attr('src', src);
el[src ? 'removeClass' : 'addClass'](this.classEmpty);
},
/**
* Open dialog for image changing
* @param {Object} e Event
@ -45,7 +43,7 @@ module.exports = ComponentView.extend({
var em = this.opts.config.em;
var editor = em ? em.get('Editor') : '';
if(editor) {
if(editor && this.model.get('editable')) {
editor.runCommand('open-assets', {
target: this.model,
onSelect() {
@ -56,6 +54,7 @@ module.exports = ComponentView.extend({
}
},
render() {
this.updateAttributes();
this.updateClasses();

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');

65
src/dom_components/view/ComponentView.js

@ -1,3 +1,5 @@
import { isArray } from 'underscore';
const ComponentsView = require('./ComponentsView');
module.exports = Backbone.View.extend({
@ -10,13 +12,14 @@ module.exports = Backbone.View.extend({
return this.model.get('tagName');
},
initialize(opt) {
initialize(opt = {}) {
const model = this.model;
this.opts = opt || {};
this.config = this.opts.config || {};
this.em = this.config.em || '';
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
const config = opt.config || {};
this.opts = opt;
this.config = config;
this.em = config.em || '';
this.pfx = config.stylePrefix || '';
this.ppfx = config.pStylePrefix || '';
this.attr = model.get('attributes');
this.classe = this.attr.class || [];
const $el = this.$el;
@ -64,6 +67,7 @@ module.exports = Backbone.View.extend({
}
},
/**
* Import, if possible, classes inside main container
* @private
@ -78,6 +82,7 @@ module.exports = Backbone.View.extend({
}
},
/**
* Fires on state update. If the state is not empty will add a helper class
* @param {Event} e
@ -94,6 +99,7 @@ module.exports = Backbone.View.extend({
}
},
/**
* Update item on status change
* @param {Event} e
@ -131,6 +137,7 @@ module.exports = Backbone.View.extend({
}
},
/**
* Update highlight attribute
* @private
@ -140,14 +147,24 @@ module.exports = Backbone.View.extend({
this.setAttribute('data-highlightable', hl ? 1 : '');
},
/**
* Update style attribute
* @private
* */
updateStyle() {
this.setAttribute('style', this.getStyleString());
const em = this.em;
const model = this.model;
if (em && em.get('avoidInlineStyle')) {
this.el.id = model.getId();
model.setStyle(model.getStyle());
} else {
this.setAttribute('style', model.styleToString());
}
},
/**
* Update classe attribute
* @private
@ -180,10 +197,13 @@ module.exports = Backbone.View.extend({
getClasses() {
var attr = this.model.get("attributes"),
classes = attr['class'] || [];
if(classes.length){
return classes.join(" ");
}else
classes = isArray(classes) ? classes : [classes];
if (classes.length) {
return classes.join(' ');
} else {
return null;
}
},
/**
@ -214,31 +234,6 @@ module.exports = Backbone.View.extend({
this.getChildrenContainer().innerHTML = this.model.get('content');
},
/**
* Return style string
* @return {string}
* @private
* */
getStyleString() {
var style = '';
this.style = this.model.get('style');
for(var key in this.style) {
if(this.style.hasOwnProperty(key))
style += key + ':' + this.style[key] + ';';
}
return style;
},
/**
* Reply to event call
* @param object Event that generated the request
* @private
* */
eventCall(event) {
event.viewResponse = this;
},
/**
* Prevent default helper
* @param {Event} e

9
src/dom_components/view/ComponentsView.js

@ -18,13 +18,12 @@ module.exports = Backbone.View.extend({
* @private
* */
addTo(model) {
var i = this.collection.indexOf(model);
const em = this.config.em;
const i = this.collection.indexOf(model);
this.addToCollection(model, null, i);
var em = this.config.em;
if(em) {
// OLD
em.trigger('add:component', model);
if (em && !model.opt.temporary) {
em.trigger('add:component', model); // @deprecated
em.trigger('component:add', model);
}
},

46
src/domain_abstract/model/Styleable.js

@ -1,9 +1,12 @@
import { isString } from 'underscore';
import { isString, isArray, keys } from 'underscore';
import { shallowDiff } from 'utils/mixins';
import ParserHtml from 'parser/model/ParserHtml';
const parseStyle = ParserHtml().parseStyle;
export default {
parseStyle,
/**
* To trigger the style change event on models I have to
* pass a new object instance
@ -11,34 +14,40 @@ export default {
* @return {Object}
*/
extendStyle(prop) {
return Object.assign({}, this.getStyle(), prop);
return { ...this.getStyle(), ...prop};
},
/**
* Get style object
* @return {Object}
*/
getStyle() {
return Object.assign({}, this.get('style'));
return { ...this.get('style') };
},
/**
* Set new style object
* @param {Object|string} prop
* @param {Object} opts
* @return {Object} Applied properties
*/
setStyle(prop = {}, opts = {}) {
if (isString(prop)) {
prop = parseStyle(prop);
}
this.set('style', Object.assign({}, prop), opts);
const propOrig = this.getStyle();
const propNew = { ...prop };
this.set('style', propNew, opts);
const diff = shallowDiff(propOrig, propNew);
keys(diff).forEach(pr => this.trigger(`change:style:${pr}`));
for (let pr in prop) {
this.trigger(`change:style:${pr}`);
}
return propNew;
},
/**
* Add style property
* @param {Object|string} prop
@ -60,6 +69,7 @@ export default {
this.setStyle(prop, opts);
},
/**
* Remove style property
* @param {string} prop
@ -68,5 +78,25 @@ export default {
let style = this.getStyle();
delete style[prop];
this.setStyle(style);
}
},
/**
* Returns string of style properties
* @param {Object} [opts={}] Options
* @return {String}
*/
styleToString(opts = {}) {
const result = [];
const style = this.getStyle();
for (let prop in style) {
const imp = opts.important;
const important = isArray(imp) ? imp.indexOf(prop) >= 0 : imp;
const value = `${style[prop]}${important ? ' !important' : ''}`;
result.push(`${prop}:${value};`);
}
return result.join('');
},
}

17
src/domain_abstract/ui/InputColor.js

@ -53,6 +53,7 @@ module.exports = Input.extend({
getColorEl() {
if (!this.colorEl) {
const self = this;
const ppfx = this.ppfx;
var model = this.model;
var colorEl = $(`<div class="${this.ppfx}field-color-picker"></div>`);
@ -64,10 +65,10 @@ 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`,
appendTo: elToAppend || 'body',
maxSelectionSize: 8,
showPalette: true,
@ -89,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);
}
}
});

12
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)
@ -71,6 +71,12 @@ module.exports = {
// The wrapper, if visible, will be shown as a `<body>`
wrappesIsBody: 1,
// Usually when you update the `style` of the component this changes the
// element's `style` attribute. Unfortunately, inline styling doesn't allow
// use of media queries (@media) or even pseudo selectors (eg. :hover).
// When `avoidInlineStyle` is true all styles are inserted inside the css rule
avoidInlineStyle: 0,
// Dom element
el: '',
@ -169,4 +175,8 @@ module.exports = {
//Configurations for Block Manager
blockManager: {},
// Texts
textViewCode: 'Code',
};

80
src/editor/index.js

@ -1,29 +1,4 @@
/**
*
* * [getConfig](#getconfig)
* * [getHtml](#gethtml)
* * [getCss](#getcss)
* * [getJs](#getjs)
* * [getComponents](#getcomponents)
* * [setComponents](#setcomponents)
* * [addComponents](#addcomponents)
* * [getStyle](#getstyle)
* * [setStyle](#setstyle)
* * [getSelected](#getselected)
* * [getSelectedToStyle](#getselectedtostyle)
* * [setDevice](#setdevice)
* * [getDevice](#getdevice)
* * [runCommand](#runcommand)
* * [stopCommand](#stopcommand)
* * [store](#store)
* * [load](#load)
* * [getContainer](#getcontainer)
* * [refresh](#refresh)
* * [on](#on)
* * [off](#off)
* * [trigger](#trigger)
* * [render](#render)
*
* Editor class contains the top level API which you'll probably use to custom the editor or extend it with plugins.
* You get the Editor instance on init method
*
@ -31,37 +6,53 @@
* var editor = grapesjs.init({...});
* ```
*
* **Available Events**
* # 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
* * `asset:upload:start` - Before the upload is started
* * `asset:upload:end` - After the upload is ended
* * `asset:upload:error` - On any error in upload, passes the error as an argument
* * `asset:upload:response` - On upload response, passes the result as an argument
* ## Keymaps
* * `keymap:add` - New keymap added. The new keyamp object is passed as an argument
* * `keymap:remove` - Keymap removed. The removed keyamp object is passed as an argument
* * `keymap:emit` - Some keymap emitted, in arguments you get keymapId, shortcutUsed, Event
* * `keymap:emit:{keymapId}` - `keymapId` emitted, in arguments you get keymapId, shortcutUsed, Event
* ## Style Manager
* * `styleManager:change` - Triggered on style property change from new selected component, the view of the property is passed as an argument to the callback
* * `styleManager:change:{propertyName}` - As above but for a specific style property
* ## Storages
* * `storage:start` - Before the storage request is started
* * `storage:load` - Triggered when something was loaded from the storage, loaded object passed as an argumnet
* * `storage:store` - Triggered when something is stored to the storage, stored object passed as an argumnet
* * `storage:end` - After the storage request is ended
* * `storage:error` - On any error on storage request, passes the error as an argument
* ## Selectors
* * `selector:add` - Triggers when a new selector/class is created
* ## RTE
* * `rte:enable` - RTE enabled. The view, on which RTE is enabled, is passed as an argument
* * `rte:disable` - RTE disabled. The view, on which RTE is disabled, is passed as an argument
* * `canvasScroll` - Triggered when the canvas is scrolled
* ## Commands
* * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview'))
* * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview'))
* ## General
* * `canvasScroll` - Triggered when the canvas is scrolle
* * `load` - When the editor is loaded
*
* @module Editor
* @param {Object} config Configurations
* @param {string} config.container='' Selector for the editor container, eg. '#myEditor'
* @param {string|Array<Object>} [config.components=''] HTML string or object of components
@ -172,6 +163,12 @@ module.exports = config => {
*/
Commands: em.get('Commands'),
/**
* @property {Keymaps}
* @private
*/
Keymaps: em.get('Keymaps'),
/**
* @property {Modal}
* @private
@ -238,26 +235,29 @@ module.exports = config => {
/**
* Returns configuration object
* @return {Object}
* @param {string} [prop] Property name
* @return {any} Returns the configuration object or
* the value of the specified property
*/
getConfig() {
return c;
getConfig(prop) {
return em.getConfig(prop);
},
/**
* 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);
},
/**

97
src/editor/model/Editor.js

@ -2,6 +2,7 @@ import { isUndefined, defaults } from 'underscore';
const deps = [
require('utils'),
require('keymaps'),
require('storage_manager'),
require('device_manager'),
require('parser'),
@ -47,7 +48,7 @@ module.exports = Backbone.Model.extend({
device: '',
},
initialize(c) {
initialize(c = {}) {
this.config = c;
this.set('Config', c);
this.set('modules', []);
@ -66,6 +67,19 @@ module.exports = Backbone.Model.extend({
this.on('change:changesCount', this.updateChanges, this);
},
/**
* Get configurations
* @param {string} [prop] Property name
* @return {any} Returns the configuration object or
* the value of the specified property
*/
getConfig(prop) {
const config = this.config;
return isUndefined(prop) ? config : config[prop];
},
/**
* Should be called after all modules and plugins are loaded
* @param {Function} clb
@ -130,30 +144,30 @@ module.exports = Backbone.Model.extend({
*/
loadModule(moduleName) {
var c = this.config;
var M = new moduleName();
var name = M.name.charAt(0).toLowerCase() + M.name.slice(1);
var cfg = c[name] || c[M.name] || {};
var Mod = new moduleName();
var name = Mod.name.charAt(0).toLowerCase() + Mod.name.slice(1);
var cfg = c[name] || c[Mod.name] || {};
cfg.pStylePrefix = c.pStylePrefix || '';
// Check if module is storable
var sm = this.get('StorageManager');
if(M.storageKey && M.store && M.load && sm){
if(Mod.storageKey && Mod.store && Mod.load && sm){
cfg.stm = sm;
var storables = this.get('storables');
storables.push(M);
storables.push(Mod);
this.set('storables', storables);
}
cfg.em = this;
M.init(Object.create(cfg));
Mod.init({ ...cfg });
// Bind the module to the editor model if public
if(!M.private)
this.set(M.name, M);
if(!Mod.private)
this.set(Mod.name, Mod);
if(M.onLoad)
this.get('toLoad').push(M);
if(Mod.onLoad)
this.get('toLoad').push(Mod);
this.get('modules').push(M);
this.get('modules').push(Mod);
return this;
},
@ -167,6 +181,12 @@ module.exports = Backbone.Model.extend({
this.set('Editor', editor);
},
getEditor() {
return this.get('Editor');
},
/**
* Listen for new rules
* @param {Object} collection
@ -261,9 +281,9 @@ module.exports = Backbone.Model.extend({
return;
} else {
var obj = {
"object": model,
"before": beforeCache,
"after": model.toJSON()
object: model,
before: beforeCache,
after: model.toJSON()
};
beforeCache = null;
return obj;
@ -283,7 +303,9 @@ module.exports = Backbone.Model.extend({
UndoManager.removeUndoType("change");
UndoManager.addUndoType("change:style", customUndoType);
UndoManager.addUndoType("change:attributes", customUndoType);
UndoManager.addUndoType("change:content", customUndoType);
UndoManager.addUndoType("change:src", customUndoType);
}
},
@ -328,7 +350,7 @@ module.exports = Backbone.Model.extend({
this.stopListening(classes, 'add remove', this.handleUpdates);
this.listenTo(classes, 'add remove', this.handleUpdates);
var evn = 'change:style change:content change:attributes';
var evn = 'change:style change:content change:attributes change:src';
this.stopListening(model, evn, this.handleUpdates);
this.listenTo(model, evn, this.handleUpdates);
@ -383,10 +405,14 @@ module.exports = Backbone.Model.extend({
setSelected(el, opts = {}) {
let model = el;
if (el instanceof HTMLElement) {
if (el instanceof window.HTMLElement) {
model = $(el).data('model');
}
if (model && !model.get("selectable")) {
return;
}
this.set('selectedComponent', model, opts);
},
@ -459,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
@ -569,26 +597,28 @@ module.exports = Backbone.Model.extend({
/**
* Run default command if setted
* @param {Object} [opts={}] Options
* @private
*/
runDefault() {
runDefault(opts = {}) {
var command = this.get('Commands').get(this.config.defaultCommand);
if(!command || this.defaultRunning)
return;
command.stop(this, this);
command.run(this, this);
command.stop(this, this, opts);
command.run(this, this, opts);
this.defaultRunning = 1;
},
/**
* Stop default command
* @param {Object} [opts={}] Options
* @private
*/
stopDefault() {
stopDefault(opts = {}) {
var command = this.get('Commands').get(this.config.defaultCommand);
if(!command)
return;
command.stop(this, this);
command.stop(this, this, opts);
this.defaultRunning = 0;
},
@ -611,6 +641,21 @@ module.exports = Backbone.Model.extend({
w.getSelection().removeAllRanges();
},
/**
* Get the current media text
* @return {string}
*/
getCurrentMedia() {
const config = this.config;
const device = this.getDeviceModel();
const condition = config.mediaCondition;
const preview = config.devicePreviewMode;
const width = device && device.get('widthMedia');
return device && width && !preview ? `(${condition}: ${width})` : '';
},
/**
* Set/get data from the HTMLElement
* @param {HTMLElement} el

2
src/editor/view/EditorView.js

@ -31,7 +31,7 @@ module.exports = Backbone.View.extend({
el.append(model.get('Canvas').render());
el.append(this.pn.render());
el.attr('class', `${pfx}editor`);
el.attr('class', `${pfx}editor ${pfx}one-bg ${pfx}two-color`);
contEl.addClass(`${pfx}editor-cont`).empty().append(el);
return this;

135
src/keymaps/index.js

@ -0,0 +1,135 @@
/**
* This module allows to create shortcuts for functions and commands (via command id)
*
* You can access the module in this way
* ```js
* const keymaps = editor.Keymaps;
* ```
*
*/
import { isString } from 'underscore';
const keymaster = require('keymaster');
module.exports = () => {
let config;
const configDef = {};
const keymaps = {};
return {
keymaster,
name: 'Keymaps',
/**
* Get module configurations
* @return {Object} Configuration object
*/
getConfig() {
return config;
},
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init(opts = {}) {
config = { ...opts, ...configDef };
this.em = config.em;
return this;
},
/**
* Add new keymap
* @param {string} id Keymap id
* @param {string} keys Keymap keys, eg. `ctrl+a`, `⌘+z, ctrl+z`
* @param {Function|string} handler Keymap handler, might be a function
* @return {Object} Added keymap
* or just a command id as a string
* @example
* // 'ns' is just a custom namespace
* keymaps.add('ns:my-keymap', '⌘+j, ⌘+u, ctrl+j, alt+u', editor => {
* console.log('do stuff');
* });
* // or
* keymaps.add('ns:my-keymap', '⌘+s, ctrl+s', 'some-gjs-command');
*
* // listen to events
* editor.on('keymap:emit', (id, shortcut, e) => {
* // ...
* })
*/
add(id, keys, handler) {
const em = this.em;
const cmd = em.get('Commands');
const editor = em.getEditor();
const keymap = { id, keys, handler };
const pk = keymaps[id];
pk && this.remove(id);
keymaps[id] = keymap;
keymaster(keys, (e, h) => {
// It's safer putting handlers resolution inside the callback
handler = isString(handler) ? cmd.get(handler) : handler;
typeof handler == 'object' ? handler.run(editor) : handler(editor);
const args = [id, h.shortcut, e];
em.trigger('keymap:emit', ...args);
em.trigger(`keymap:emit:${id}`, ...args);
});
em.trigger('keymap:add', keymap);
return keymap;
},
/**
* Get the keymap by id
* @param {string} id Keymap id
* @return {Object} Keymap object
* @example
* keymaps.get('ns:my-keymap');
* // -> {keys, handler};
*/
get(id) {
return keymaps[id];
},
/**
* Get all keymaps
* @return {Object}
* @example
* keymaps.getAll();
* // -> {id1: {}, id2: {}};
*/
getAll() {
return keymaps;
},
/**
* Remove the keymap by id
* @param {string} id Keymap id
* @return {Object} Removed keymap
* @example
* keymaps.remove('ns:my-keymap');
* // -> {keys, handler};
*/
remove(id) {
const em = this.em;
const keymap = this.get(id);
if (keymap) {
delete keymaps[id];
keymaster.unbind(keymap.keys);
em && em.trigger('keymap:remove', keymap);
return keymap;
}
},
};
};

54
src/modal_dialog/view/ModalView.js

@ -1,33 +1,35 @@
var Backbone = require('backbone');
module.exports = require('backbone').View.extend({
module.exports = Backbone.View.extend({
template: _.template(`
<div class="<%= pfx %>dialog">
<div class="<%= pfx %>header">
<div class="<%= pfx %>title"><%= title %></div>
<div class="<%= pfx %>btn-close">&Cross;</div>
template({pfx, ppfx, content, title}) {
return `<div class="${pfx}dialog ${ppfx}one-bg">
<div class="${pfx}header">
<div class="${pfx}title">${title}</div>
<div class="${pfx}btn-close">&Cross;</div>
</div>
<div class="<%= pfx %>content">
<div id="<%= pfx %>c"> <%= content %> </div>
<div class="${pfx}content">
<div id="${pfx}c">${content}</div>
<div style="clear:both"></div>
</div>
</div>
<div class="<%= pfx %>backlayer"></div>
<div class="<%= pfx %>collector" style="display: none"></div>`),
<div class="${pfx}backlayer"></div>
<div class="${pfx}collector" style="display: none"></div>`;
},
events: {},
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.listenTo(this.model, 'change:open', this.updateOpen);
this.listenTo(this.model, 'change:title', this.updateTitle);
this.listenTo(this.model, 'change:content', this.updateContent);
this.events['click .'+this.pfx+'btn-close'] = 'hide';
if(this.config.backdrop)
this.events['click .'+this.pfx+'backlayer'] = 'hide';
const model = this.model;
const config = o.config || {};
const pfx = config.stylePrefix || '';
const bkd = config.backdrop;
this.config = config;
this.pfx = pfx;
this.ppfx = config.pStylePrefix || '';
this.listenTo(model, 'change:open', this.updateOpen);
this.listenTo(model, 'change:title', this.updateTitle);
this.listenTo(model, 'change:content', this.updateContent);
this.events[`click .${pfx}btn-close`] = 'hide';
bkd && (this.events[`click .${pfx}backlayer`] = 'hide');
this.delegateEvents();
},
@ -116,10 +118,14 @@ module.exports = Backbone.View.extend({
},
render() {
var obj = this.model.toJSON();
const el = this.$el;
const pfx = this.pfx;
const ppfx = this.ppfx;
const obj = this.model.toJSON();
obj.pfx = this.pfx;
this.$el.html( this.template(obj) );
this.$el.attr('class', this.pfx + 'container');
obj.ppfx = this.ppfx;
el.html(this.template(obj));
el.attr('class', `${pfx}container`);
this.updateOpen();
return this;
},

82
src/navigator/view/ItemView.js

@ -9,7 +9,7 @@ module.exports = Backbone.View.extend({
'click [data-toggle-visible]': 'toggleVisibility',
'click [data-toggle-select]': 'handleSelect',
'click [data-toggle-open]': 'toggleOpening',
'click [data-toggle-edit]': 'handleEdit',
'dblclick input': 'handleEdit',
'focusout input': 'handleEditEnd',
},
@ -18,10 +18,9 @@ module.exports = Backbone.View.extend({
<i id="<%= prefix %>btn-eye" class="btn fa fa-eye <%= (visible ? '' : 'fa-eye-slash') %>" data-toggle-visible></i>
<% } %>
<div class="<%= prefix %>title-c">
<div class="<%= prefix %>title <%= addClass %>" style="padding-left: <%= 42 + level * 10 %>px" data-toggle-select>
<div class="<%= prefix %>title-c <%= ppfx %>one-bg">
<div class="<%= prefix %>title <%= addClass %>" style="padding-left: <%= 30 + level * 10 %>px" data-toggle-select>
<div class="<%= prefix %>title-inn">
<i class="fa fa-pencil <%= editBtnCls %>" data-toggle-edit></i>
<i id="<%= prefix %>caret" class="fa fa-chevron-right <%= caretCls %>" data-toggle-open></i>
<%= icon %>
<input class="<%= ppfx %>no-app <%= inputNameCls %>" value="<%= title %>" readonly>
@ -54,6 +53,7 @@ module.exports = Backbone.View.extend({
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:status', this.updateStatus);
this.listenTo(model, 'change:open', this.updateOpening);
this.listenTo(model, 'change:style:display', this.updateVisibility);
this.className = `${pfx}item no-select`;
this.editBtnCls = `${pfx}nav-item-edit`;
this.inputNameCls = `${ppfx}nav-comp-name`;
@ -63,6 +63,50 @@ module.exports = Backbone.View.extend({
this.$el.data('collection', components);
},
getVisibilityEl () {
if (!this.eyeEl) {
this.eyeEl = this.$el.children(`#${this.pfx}btn-eye`);
}
return this.eyeEl;
},
updateVisibility() {
const pfx = this.pfx;
const model = this.model;
const hClass = `${pfx}hide`;
const hideIcon = 'fa-eye-slash';
const hidden = model.getStyle().display == 'none';
const method = hidden ? 'addClass' : 'removeClass';
this.$el[method](hClass);
this.getVisibilityEl()[method](hideIcon);
},
/**
* Toggle visibility
* @param Event
*
* @return void
* */
toggleVisibility(e) {
e && e.stopPropagation();
const model = this.model;
const style = model.getStyle();
const hidden = style.display == 'none';
if (hidden) {
delete style.display;
} else {
style.display = 'none';
}
model.setStyle(style);
},
/**
* Handle the edit of the component name
*/
@ -73,6 +117,7 @@ module.exports = Backbone.View.extend({
inputName.focus();
},
/**
* Handle with the end of editing of the component name
*/
@ -178,32 +223,6 @@ module.exports = Backbone.View.extend({
ComponentView.prototype.updateStatus.apply(this, arguments);
},
/**
* Toggle visibility
* @param Event
*
* @return void
* */
toggleVisibility(e) {
e.stopPropagation();
const pfx = this.pfx;
if(!this.$eye)
this.$eye = this.$el.children(`#${pfx}btn-eye`);
var cCss = _.clone(this.model.get('style')),
hClass = this.pfx + 'hide';
if(this.isVisible()){
this.$el.addClass(hClass);
this.$eye.addClass('fa-eye-slash');
cCss.display = 'none';
}else{
this.$el.removeClass(hClass);
this.$eye.removeClass('fa-eye-slash');
delete cCss.display;
}
this.model.set('style', cCss);
},
/**
* Check if component is visible
@ -279,7 +298,7 @@ module.exports = Backbone.View.extend({
const el = this.$el;
const level = this.level + 1;
el.html( this.template({
el.html(this.template({
title: model.getName(),
icon: model.getIcon(),
addClass: (count ? '' : pfx+'no-chld'),
@ -317,6 +336,7 @@ module.exports = Backbone.View.extend({
el.attr('class', _.result(this, 'className'));
this.updateOpening();
this.updateStatus();
this.updateVisibility();
return this;
},

42
src/panels/index.js

@ -2,6 +2,7 @@
*
* * [addPanel](#addpanel)
* * [addButton](#addbutton)
* * [removeButton](#removebutton)
* * [getButton](#getbutton)
* * [getPanel](#getpanel)
* * [getPanels](#getpanels)
@ -115,6 +116,24 @@ module.exports = () => {
addPanel(panel) {
return panels.add(panel);
},
/**
* Remove a panel from the collection
* @param {Object|Panel|String} panel Object with right properties or an instance of Panel or Painel id
* @return {Panel} Removed panel. Useful in case passed argument was an Object
* @example
* const newPanel = panelManager.removePanel({
* id: 'myNewPanel',
* visible : true,
* buttons : [...],
* });
*
* const newPanel = panelManager.removePanel('myNewPanel');
*
*/
removePanel(panel) {
return panels.remove(panel);
},
/**
* Get panel by ID
@ -162,6 +181,29 @@ module.exports = () => {
var pn = this.getPanel(panelId);
return pn ? pn.get('buttons').add(button) : null;
},
/**
* Remove button from the panel
* @param {string} panelId Panel's ID
* @param {Object|Button|String} button Button object or instance of Button or button id
* @return {Button|null} Removed button.
* @example
* const removedButton = panelManager.removeButton('myNewPanel',{
* id: 'myNewButton',
* className: 'someClass',
* command: 'someCommand',
* attributes: { title: 'Some title'},
* active: false,
* });
*
* // It's also possible to use the button id
* const removedButton = panelManager.removeButton('myNewPanel','myNewButton');
*
*/
removeButton(panelId, button) {
var pn = this.getPanel(panelId);
return pn && pn.get('buttons').remove(button);
},
/**
* Get button from the panel

65
src/panels/view/ButtonView.js

@ -1,3 +1,4 @@
import { isString, isObject, isFunction } from 'underscore';
const $ = Backbone.$;
module.exports = Backbone.View.extend({
@ -9,14 +10,16 @@ module.exports = Backbone.View.extend({
var cls = this.model.get('className');
this.config = o.config || {};
this.em = this.config.em || {};
this.pfx = this.config.stylePrefix || '';
const pfx = this.config.stylePrefix || '';
const ppfx = this.config.pStylePrefix || '';
this.pfx = pfx;
this.ppfx = this.config.pStylePrefix || '';
this.id = this.pfx + this.model.get('id');
this.activeCls = this.pfx + 'active';
this.disableCls = this.pfx + 'active';
this.btnsVisCls = this.pfx + 'visible';
this.id = pfx + this.model.get('id');
this.activeCls = `${pfx}active ${ppfx}four-color`;
this.disableCls = pfx + 'active';
this.btnsVisCls = pfx + 'visible';
this.parentM = o.parentM || null;
this.className = this.pfx + 'btn' + (cls ? ' ' + cls : '');
this.className = pfx + 'btn' + (cls ? ' ' + cls : '');
this.listenTo(this.model, 'change:active updateActive', this.updateActive);
this.listenTo(this.model, 'checkActive', this.checkActive);
this.listenTo(this.model, 'change:bntsVis', this.updateBtnsVis);
@ -26,7 +29,7 @@ module.exports = Backbone.View.extend({
if(this.model.get('buttons').length){
this.$el.on('mousedown', this.startTimer);
this.$el.append($('<div>',{class: this.pfx + 'arrow-rd'}));
this.$el.append($('<div>',{class: pfx + 'arrow-rd'}));
}
if(this.em && this.em.get)
@ -197,40 +200,40 @@ module.exports = Backbone.View.extend({
* @return void
* */
updateActive() {
const model = this.model;
const context = model.get('context');
const parent = this.parentM;
var command = null;
var editor = this.em && this.em.get ? this.em.get('Editor') : null;
var commandName = this.model.get('command');
var commandName = model.get('command');
if (this.commands && typeof commandName === 'string') {
if (this.commands && isString(commandName)) {
command = this.commands.get(commandName);
} else if (commandName !== null && typeof commandName === 'object') {
} else if (isFunction(commandName)) {
command = { run: commandName };
} else if (commandName !== null && isObject(commandName)) {
command = commandName;
} else if (typeof commandName === 'function') {
command = {run: commandName};
}
if(this.model.get('active')){
if (model.get('active')) {
model.collection.deactivateAll(context);
model.set('active', true, { silent: true }).trigger('checkActive');
parent && parent.set('active', true, { silent: true }).trigger('checkActive');
this.model.collection.deactivateAll(this.model.get('context'));
this.model.set('active', true, { silent: true }).trigger('checkActive');
if(this.parentM)
this.parentM.set('active', true, { silent: true }).trigger('checkActive');
if(command && command.run){
command.run(editor, this.model, this.model.get('options'));
if (command && command.run) {
command.run(editor, model, model.get('options'));
editor.trigger('run:' + commandName);
}
}else{
this.$el.removeClass(this.activeCls);
this.model.collection.deactivateAll(this.model.get('context'));
if(this.parentM)
this.parentM.set('active', false, { silent: true }).trigger('checkActive');
// Disable button if there is no stop method
!command.stop && model.set('active', false);
} else {
this.$el.removeClass(this.activeCls);
model.collection.deactivateAll(context);
parent && parent.set('active', false, { silent: true }).trigger('checkActive');
if(command && command.stop){
command.stop(editor, this.model, this.model.get('options'));
if (command && command.stop) {
command.stop(editor, model, model.get('options'));
editor.trigger('stop:' + commandName);
}
}
@ -242,7 +245,7 @@ module.exports = Backbone.View.extend({
} else {
this.$el.removeClass(this.disableCls);
}
},
/**
@ -266,7 +269,7 @@ module.exports = Backbone.View.extend({
clicked(e) {
if(this.model.get('bntsVis') )
return;
if(this.model.get('disable') )
return;

18
src/panels/view/PanelView.js

@ -4,8 +4,10 @@ var ButtonsView = require('./ButtonsView');
module.exports = Backbone.View.extend({
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
const config = o.config || {};
this.config = config;
this.pfx = config.stylePrefix || '';
this.ppfx = config.pStylePrefix || '';
this.buttons = this.model.get('buttons');
this.className = this.pfx + 'panel';
this.id = this.pfx + this.model.get('id');
@ -76,20 +78,20 @@ module.exports = Backbone.View.extend({
},
render() {
this.$el.attr('class', this.className);
if(this.id)
this.$el.attr('id', this.id);
const el = this.$el;
const pfx = this.ppfx;
el.attr('class', `${this.className} ${pfx}one-bg`);
this.id && el.attr('id', this.id);
if (this.buttons.length) {
var buttons = new ButtonsView({
collection: this.buttons,
config: this.config,
});
this.$el.append(buttons.render().el);
el.append(buttons.render().el);
}
this.$el.append(this.model.get('content'));
el.append(this.model.get('content'));
return this;
},

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);
}

2
src/rich_text_editor/index.js

@ -63,7 +63,7 @@ module.exports = () => {
this.pfx = config.stylePrefix;
actions = config.actions || [];
toolbar = document.createElement('div');
toolbar.className = `${ppfx}rte-toolbar`;
toolbar.className = `${ppfx}rte-toolbar ${ppfx}one-bg`;
globalRte = this.initRte(document.createElement('div'));
//Avoid closing on toolbar clicking

11
src/selector_manager/index.js

@ -61,6 +61,10 @@ module.exports = config => {
return {
Selector,
Selectors,
/**
* Name of the module
* @type {String}
@ -138,7 +142,7 @@ module.exports = config => {
}
const cname = opts.name;
const selector = cname ? this.get(cname) : selectors.where(opts)[0];
const selector = cname ? this.get(cname, opts.type) : selectors.where(opts)[0];
if (!selector) {
return selectors.add(opts);
@ -172,12 +176,13 @@ module.exports = config => {
/**
* Get the selector by its name
* @param {String} name Selector name
* @param {String} tyoe Selector type
* @return {Model|null}
* @example
* var selector = selectorManager.get('selectorName');
* */
get(name) {
return selectors.where({name})[0];
get(name, type = Selector.TYPE_CLASS) {
return selectors.where({name, type})[0];
},
/**

18
src/selector_manager/model/Selector.js

@ -1,15 +1,19 @@
var Backbone = require('backbone');
const TYPE_CLASS = 1;
const TYPE_ID = 2;
const Selector = Backbone.Model.extend({
idAttribute: 'name',
defaults: {
name: '',
label: '',
// Type of the selector
type: 'class',
type: TYPE_CLASS,
// If not active it's not selectable by the style manager (uncheckboxed)
active: true,
@ -18,7 +22,7 @@ const Selector = Backbone.Model.extend({
// Will be rendered only in export code
private: false,
// If true, can't be removed by the user, from the attacched element
// If true, can't be removed from the attacched element
protected: false,
},
@ -43,10 +47,10 @@ const Selector = Backbone.Model.extend({
let init = '';
switch (this.get('type')) {
case 'class':
case TYPE_CLASS:
init = '.';
break;
case 'id':
case TYPE_ID:
init = '#';
break;
}
@ -55,6 +59,12 @@ const Selector = Backbone.Model.extend({
}
}, {
// All type selectors: https://developer.mozilla.org/it/docs/Web/CSS/CSS_Selectors
// Here I define only what I need
TYPE_CLASS,
TYPE_ID,
/**
* Escape string
* @param {string} name

6
src/selector_manager/model/Selectors.js

@ -11,5 +11,11 @@ module.exports = Backbone.Collection.extend({
getValid() {
return _.filter(this.models, item => !item.get('private'));
},
getFullString() {
const result = [];
this.each(selector => result.push(selector.getFullName()));
return result.join('').trim();
}
});

9
src/selector_manager/view/ClassTagView.js

@ -16,7 +16,6 @@ module.exports = Backbone.View.extend({
this.ppfx = this.config.pStylePrefix || '';
this.inputProp = 'readonly';
this.target = this.config.em;
this.className = this.pfx + 'tag';
this.closeId = this.pfx + 'close';
this.chkId = this.pfx + 'checkbox';
this.labelId = this.pfx + 'tag-label';
@ -124,13 +123,15 @@ module.exports = Backbone.View.extend({
render() {
const pfx = this.pfx;
const ppfx = this.ppfx;
this.$el.html( this.template({
label: this.model.get('label'),
pfx: this.pfx,
ppfx: this.ppfx,
pfx,
ppfx,
inputProp: this.inputProp,
}));
this.$el.attr('class', this.className);
this.$el.attr('class', `${pfx}tag ${ppfx}three-bg`);
this.updateStatus();
this.updateInputLabel();
return this;

36
src/selector_manager/view/ClassTagsView.js

@ -33,7 +33,7 @@ module.exports = Backbone.View.extend({
events: {},
initialize(o) {
initialize(o = {}) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
@ -48,7 +48,8 @@ module.exports = Backbone.View.extend({
this.events['keyup #' + this.newInputId] = 'onInputKeyUp';
this.events['change #' + this.stateInputId] = 'stateChanged';
this.target = this.config.em;
this.target = this.config.em;
this.em = this.target;
this.listenTo(this.target ,'change:selectedComponent',this.componentChanged);
this.listenTo(this.target, 'targetClassUpdated', this.updateSelector);
@ -148,38 +149,39 @@ module.exports = Backbone.View.extend({
* @private
*/
updateStateVis() {
if(this.collection.length)
const em = this.em;
const avoidInline = em && em.getConfig('avoidInlineStyle');
if(this.collection.length || avoidInline)
this.getStatesC().css('display','block');
else
this.getStatesC().css('display','none');
this.updateSelector();
},
/**
* Udpate selector helper
* @return {this}
* @private
*/
updateSelector() {
const selected = this.target.get('selectedComponent');
const selected = this.target.getSelected();
this.compTarget = selected;
if(!selected || !selected.get)
return;
var result = '';
this.collection.each(model => {
if(model.get('active'))
result += '.' + model.get('name');
});
var state = selected.get('state');
result = state ? result + ':' + state : result;
result = result || selected.getName();
var el = this.el.querySelector('#' + this.pfx + 'sel');
if (el) {
el.innerHTML = result;
if (!selected || !selected.get) {
return;
}
const state = selected.get('state');
let result = this.collection.getFullString();
result = result || `#${selected.getId()}`;
result += state ? `:${state}` : '';
const el = this.el.querySelector('#' + this.pfx + 'sel');
el && (el.innerHTML = result);
},
/**
* Triggered when the select with states is changed
* @param {Object} e

2
src/storage_manager/config/config.js

@ -24,7 +24,7 @@ module.exports = {
//Enable/Disable saving HTML template
storeHtml: 1,
//Enable/Disable saving HTML template
//Enable/Disable saving CSS template
storeCss: 1,
// ONLY FOR LOCAL STORAGE

23
src/storage_manager/index.js

@ -1,24 +1,9 @@
/**
* - [isAutosave](#isautosave)
* - [setAutosave](#setautosave)
* - [getStepsBeforeSave](#getstepsbeforesave)
* - [setStepsBeforeSave](#setstepsbeforesave)
* - [getStorages](#getstorages)
* - [getCurrent](#getcurrent)
* - [setCurrent](#setcurrent)
* - [add](#add)
* - [get](#get)
* - [store](#store)
* - [load](#load)
*
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var storageManager = editor.StorageManager;
* ```
*
* @module StorageManager
*/
module.exports = () => {
var c = {},
@ -47,6 +32,7 @@ module.exports = () => {
* @param {number} [config.stepsBeforeSave=1] If autosave enabled, indicates how many steps/changes are necessary
* before autosave is triggered
* @param {string} [config.type='local'] Default storage type. Available: 'local' | 'remote' | ''(do not store)
* @private
* @example
* ...
* {
@ -115,17 +101,18 @@ module.exports = () => {
* @return {this}
* @example
* storageManager.add('local2', {
* load: function(keys){
* load: function(keys, clb) {
* var res = {};
* for (var i = 0, len = keys.length; i < len; i++){
* var v = localStorage.getItem(keys[i]);
* if(v) res[keys[i]] = v;
* }
* return res;
* clb(res); // might be called inside some async method
* },
* store: function(data){
* store: function(data, clb) {
* for(var key in data)
* localStorage.setItem(key, data[key]);
* clb(); // might be called inside some async method
* }
* });
* */

20
src/style_manager/index.js

@ -249,16 +249,16 @@ module.exports = () => {
* @return {Model}
*/
getModelToStyle(model) {
var classes = model.get('classes');
if(c.em && classes && classes.length) {
var previewMode = c.em.get('Config').devicePreviewMode;
var device = c.em.getDeviceModel();
var state = !previewMode ? model.get('state') : '';
var deviceW = device && !previewMode ? device.get('width') : '';
var cssC = c.em.get('CssComposer');
var valid = classes.getStyleable();
var CssRule = cssC.get(valid, state, deviceW);
const classes = model.get('classes');
const em = c.em;
if (em && classes && classes.length) {
const conf = em.get('Config');
const state = !conf.devicePreviewMode ? model.get('state') : '';
const deviceW = em.getCurrentMedia();
const cssC = em.get('CssComposer');
const valid = classes.getStyleable();
const CssRule = cssC.get(valid, state, deviceW);
if(CssRule && valid.length) {
return CssRule;

4
src/style_manager/model/Layers.js

@ -66,7 +66,7 @@ module.exports = Backbone.Collection.extend({
values.forEach((value, i) => {
value = propModel.parseValue(value.trim()).value;
const layer = layers[i];
const propertyObj = Object.assign({}, propModel.attributes, {value});
const propertyObj = { ...propModel.attributes, ...{ value } };
if (layer) {
layer.properties.push(propertyObj);
@ -85,7 +85,7 @@ module.exports = Backbone.Collection.extend({
const propertyName = propModel.get('property');
if (layerProprs.indexOf(propertyName) < 0) {
layer.properties.push(Object.assign({}, propModel.attributes))
layer.properties.push({ ...propModel.attributes })
}
})
});

2
src/style_manager/model/Properties.js

@ -107,7 +107,7 @@ module.exports = require('backbone').Collection.extend(TypeableCollection).exten
const values = value.split(' ');
values.forEach((value, i) => {
const property = this.at(i);
properties.push(Object.assign({}, property.attributes, {value}));
properties.push({ ...property.attributes, ...{ value } });
});
return properties;
},

7
src/style_manager/model/Property.js

@ -12,6 +12,13 @@ module.exports = require('backbone').Model.extend({
status: '',
visible: true,
fixedValues: ['initial', 'inherit'],
// If true, will be hidden by default and will show up only for targets
// which require this property (via `stylable-require`)
// Use case:
// you can add all SVG CSS properties with toRequire as true
// and then require them on SVG Components
toRequire: 0,
},

4
src/style_manager/model/PropertyComposite.js

@ -2,7 +2,7 @@ const Property = require('./Property');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
defaults: { ...Property.prototype.defaults,
// 'background' is a good example where to make a difference
// between detached and not
//
@ -19,7 +19,7 @@ module.exports = Property.extend({
// Separator between properties
separator: ' ',
}),
},
init() {

4
src/style_manager/model/PropertyRadio.js

@ -2,9 +2,9 @@ const Property = require('./Property');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
defaults: { ...Property.prototype.defaults,
// Array of options, eg. [{name: 'Label ', value: '100'}]
options: [],
}),
},
});

4
src/style_manager/model/PropertyStack.js

@ -3,13 +3,13 @@ const Layers = require('./Layers');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
defaults: { ...Property.prototype.defaults,
// Array of layers (which contain properties)
layers: [],
// Layer preview
preview: 0,
}),
},
init() {
Property.prototype.init.apply(this, arguments);

24
src/style_manager/model/Sector.js

@ -1,6 +1,8 @@
var Backbone = require('backbone');
var Properties = require('./Properties');
var PropertyFactory = require('./PropertyFactory');
import { extend } from 'underscore';
const Backbone = require('backbone');
const Properties = require('./Properties');
const PropertyFactory = require('./PropertyFactory');
module.exports = Backbone.Model.extend({
@ -17,10 +19,7 @@ module.exports = Backbone.Model.extend({
var o = opts || {};
var props = [];
var builded = this.buildProperties(o.buildProps);
if (!this.get('id')) {
this.set('id', this.get('name'))
}
!this.get('id') && this.set('id', this.get('name'));
if(!builded)
props = this.get('properties');
@ -46,19 +45,20 @@ module.exports = Backbone.Model.extend({
var ext = this.get('extendBuilded');
var isolated = [];
for (var i = 0, len = mProps.length; i < len; i++){
for (var i = 0, len = mProps.length; i < len; i++) {
var mProp = mProps[i];
var found = 0;
for(var j = 0; j < pLen; j++){
for (var j = 0; j < pLen; j++) {
var prop = props[j];
if(mProp.property == prop.property){
if (mProp.property == prop.property ||
mProp.id == prop.property) {
// Check for nested properties
var mPProps = mProp.properties;
if(mPProps && mPProps.length){
if (mPProps && mPProps.length) {
mProp.properties = this.extendProperties(prop.properties, mPProps, 1);
}
props[j] = ext ? _.extend(prop, mProp) : mProp;
props[j] = ext ? extend(prop, mProp) : mProp;
isolated[j] = props[j];
found = 1;
continue;

1
src/style_manager/view/LayerView.js

@ -156,6 +156,7 @@ module.exports = Backbone.View.extend({
const properties = new PropertiesView({
collection: model.get('properties'),
config: this.config,
target: propsConfig.target,
customValue: propsConfig.customValue,
propTarget: propsConfig.propTarget,
onChange: propsConfig.onChange,

2
src/style_manager/view/PropertyColorView.js

@ -3,7 +3,7 @@ const InputColor = require('domain_abstract/ui/InputColor');
module.exports = require('./PropertyIntegerView').extend({
setValue(value, opts = {}) {
opts = Object.assign({}, opts, {silent: 1});
opts = { ...opts, silent: 1 };
this.inputInst.setValue(value, opts);
},

2
src/style_manager/view/PropertyStackView.js

@ -135,6 +135,7 @@ module.exports = PropertyCompositeView.extend({
const fieldEl = this.el.querySelector('[data-layers-wrapper]');
const PropertiesView = require('./PropertiesView');
const propsConfig = {
target: this.target,
propTarget: this.propTarget,
// Things to do when a single sub-property is changed
@ -160,6 +161,7 @@ module.exports = PropertyCompositeView.extend({
// Will use it to propogate changes
new PropertiesView({
target: this.target,
collection: this.model.get('properties'),
stackModel: model,
config: this.config,

53
src/style_manager/view/PropertyView.js

@ -1,4 +1,4 @@
import { bindAll } from 'underscore';
import { bindAll, isArray } from 'underscore';
import { camelCase } from 'utils/mixins';
module.exports = Backbone.View.extend({
@ -87,7 +87,7 @@ module.exports = Backbone.View.extend({
const pfx = this.pfx;
const ppfx = this.ppfx;
const config = this.config;
const updatedCls = `${ppfx}color-hl`;
const updatedCls = `${ppfx}four-color`;
const computedCls = `${ppfx}color-warn`;
const labelEl = this.$el.children(`.${pfx}label`);
const clearStyle = this.getClearEl().style;
@ -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);
}
},
@ -374,12 +374,34 @@ module.exports = Backbone.View.extend({
* The target could be the Component as the CSS Rule
* @return {Boolean}
*/
isTargetStylable() {
var stylable = this.getTarget().get('stylable');
isTargetStylable(target) {
if (this.model.get('id') == 'flex-width') {
//debugger;
}
const trg = target || this.getTarget();
const model = this.model;
const property = model.get('property');
const toRequire = model.get('toRequire');
const unstylable = trg.get('unstylable');
const stylableReq = trg.get('stylable-require');
let stylable = trg.get('stylable');
// Stylable could also be an array indicating with which property
// the target could be styled
if(stylable instanceof Array)
stylable = _.indexOf(stylable, this.property) >= 0;
if (isArray(stylable)) {
stylable = stylable.indexOf(property) >= 0;
}
// Check if the property was signed as unstylable
if (isArray(unstylable)) {
stylable = unstylable.indexOf(property) < 0;
}
// Check if the property is available only if requested
if (toRequire) {
stylable = (stylableReq && stylableReq.indexOf(property) >= 0) || !target;
}
return stylable;
},
@ -389,21 +411,14 @@ module.exports = Backbone.View.extend({
* @return {Boolean}
*/
isComponentStylable() {
var em = this.em;
var component = em && em.get('selectedComponent');
const em = this.em;
const component = em && em.get('selectedComponent');
if (!component) {
return true;
}
var stylable = component.get('stylable');
// Stylable could also be an array indicating with which property
// the target could be styled
if(stylable instanceof Array){
stylable = _.indexOf(stylable, this.property) >= 0;
}
return stylable;
return this.isTargetStylable(component);
},
/**

96
src/style_manager/view/SectorsView.js

@ -9,7 +9,7 @@ module.exports = Backbone.View.extend({
this.pfx = this.config.stylePrefix || '';
this.target = o.target || {};
// The taget that will emit events for properties
// The target that will emit events for properties
const target = {};
extend(target, Backbone.Events);
const body = document.body;
@ -18,10 +18,10 @@ module.exports = Backbone.View.extend({
target.computedDefault = { ...window.getComputedStyle(dummy) };
body.removeChild(dummy);
this.propTarget = target;
this.listenTo( this.collection, 'add', this.addTo);
this.listenTo( this.collection, 'reset', this.render);
this.listenTo( this.target, 'change:selectedComponent targetClassAdded targetClassRemoved targetClassUpdated ' +
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.render);
this.listenTo(this.target, 'change:selectedComponent targetClassAdded targetClassRemoved targetClassUpdated ' +
'targetStateUpdated targetStyleUpdated change:device', this.targetUpdated);
},
@ -42,33 +42,63 @@ module.exports = Backbone.View.extend({
*/
targetUpdated() {
var em = this.target;
var el = em.get('selectedComponent');
let model = em.getSelected();
const um = em.get('UndoManager');
const cc = em.get('CssComposer');
const avoidInline = em.getConfig('avoidInlineStyle');
if(!el)
if (!model) {
return;
}
const id = model.getId();
const config = em.get('Config');
var previewMode = config.devicePreviewMode;
var classes = el.get('classes');
var classes = model.get('classes');
var pt = this.propTarget;
var device = em.getDeviceModel();
var state = !previewMode ? el.get('state') : '';
var widthMedia = device && device.get('widthMedia');
const state = !config.devicePreviewMode ? model.get('state') : '';
const opts = { state };
var stateStr = state ? `:${state}` : null;
var view = el.view;
var mediaText = device && !previewMode && widthMedia ?
`(${config.mediaCondition}: ${widthMedia})` : '';
var view = model.view;
const media = em.getCurrentMedia();
pt.helper = null;
if (view) {
pt.computed = window.getComputedStyle(view.el, stateStr);
pt.computed = window.getComputedStyle(view.el, state ? `:${state}` : null);
}
const appendStateRule = (style = {}) => {
const sm = em.get('SelectorManager');
const helperClass = sm.add('hc-state');
let helperRule = cc.get([helperClass]);
if (!helperRule) {
helperRule = cc.add([helperClass]);
} else {
// I will make it last again, otherwise it could be overridden
const rules = cc.getAll();
rules.remove(helperRule);
rules.add(helperRule);
}
helperRule.set('important', 1);
helperRule.setStyle(style);
pt.helper = helperRule;
};
// If true the model will be always a rule
if (avoidInline) {
const ruleId = cc.getIdRule(id, opts);
if (!ruleId) {
model = cc.setIdRule(id, {}, opts);
} else {
model = ruleId;
}
}
if (classes.length) {
var cssC = em.get('CssComposer');
var valid = classes.getStyleable();
var iContainer = cssC.get(valid, state, mediaText);
var iContainer = cc.get(valid, state, media);
if (!iContainer && valid.length) {
// I stop undo manager here as after adding the CSSRule (generally after
@ -76,42 +106,34 @@ module.exports = Backbone.View.extend({
// the collection, therefore updating it in style manager will not affect it
// #268
um.stopTracking();
iContainer = cssC.add(valid, state, mediaText);
iContainer.set('style', el.get('style'));
el.set('style', {});
iContainer = cc.add(valid, state, media);
iContainer.setStyle(model.getStyle());
model.setStyle({});
um.startTracking();
}
if (!iContainer) {
// In this case it's just a Component without any valid selector
pt.model = el;
pt.model = model;
pt.trigger('update');
return;
}
// If the state is not empty, there should be a helper rule in play
// The helper rule will get the same style of the iContainer
if (state) {
var clm = em.get('SelectorManager');
var helperClass = clm.add('hc-state');
var helperRule = cssC.get([helperClass]);
if(!helperRule)
helperRule = cssC.add([helperClass]);
else{
// I will make it last again, otherwise it could be overridden
cssC.getAll().remove(helperRule);
cssC.getAll().add(helperRule);
}
helperRule.set('style', iContainer.get('style'));
pt.helper = helperRule;
}
state && appendStateRule(iContainer.getStyle());
pt.model = iContainer;
pt.trigger('update');
return;
}
pt.model = el;
if (state) {
const ruleState = cc.getIdRule(id, opts);
state && appendStateRule(ruleState && ruleState.getStyle());
}
pt.model = model;
pt.trigger('update');
},

4
src/styles/scss/_gjs_blocks.scss

@ -23,8 +23,6 @@
.#{$app-prefix}block {
@include user-select(none);
@extend .#{$app-prefix}bg-main;
width: 45%;
padding: 1em;
box-sizing: border-box;
@ -52,7 +50,7 @@
}
.#{$app-prefix}block-svg-path {
@extend .#{$app-prefix}color-main;
fill: currentColor;
}
.#{$app-prefix}block.fa {

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;
}
}

7
src/styles/scss/_gjs_inputs.scss

@ -83,20 +83,20 @@
.#{$app-prefix}select option,
.#{$clm-prefix}select option,
.#{$sm-prefix}select option,
.#{$app-prefix}fields option,
.#{$sm-prefix}unit option {
background-color: $mainColor;
color: $fontColor;
}
.#{$app-prefix}field {
background-color: $mainDkColor;
background-color: rgba(0, 0, 0, 0.2);
border: none;
box-shadow: none;
border-radius: 2px;
box-sizing: border-box;
padding: 0;
position: relative;
color: $inputFontColor;
textarea {
resize: vertical;
@ -319,8 +319,7 @@
}
.#{$app-prefix}btn-prim {
@extend .#{$app-prefix}color-main;
color: inherit;
background-color: $mainLhColor;
border-radius: 2px;
padding: 3px 6px;

4
src/styles/scss/_gjs_layers.scss

@ -62,10 +62,6 @@
}
}
.#{$nv-prefix}item .#{$nv-prefix}title-c {
@extend .#{$app-prefix}bg-main;
}
.#{$nv-prefix}title {
@extend .#{$app-prefix}category-title;

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_rte.scss

@ -1,6 +1,5 @@
.#{$rte-prefix} {
&toolbar {
@extend .#{$app-prefix}bg-main;
@extend .#{$app-prefix}no-user-select;
border: 1px solid $mainDkColor;
@ -14,8 +13,6 @@
}
&action {
@extend .#{$app-prefix}color-main;
display: flex;
align-items: center;
justify-content: center;

2
src/styles/scss/_gjs_style_manager.scss

@ -167,8 +167,6 @@
}
&.#{$sm-prefix}composite {
@extend .#{$app-prefix}color-main;
border-radius: 2px;
}

15
src/styles/scss/_gjs_variables.scss

@ -14,9 +14,15 @@ $cv-prefix: $app-prefix + 'cv-' !default;
$clm-prefix: $app-prefix + 'clm-' !default;
$trt-prefix: $app-prefix + 'trt-' !default;
/* Colors / Theme */
/*
New Pattern Color System
With this should be easier to overwrite colors
not only in SCSS but even CSS
*/
$primaryColor: #444 !default;
$secondaryColor: #ddd !default;
$tertiaryColor: #804f7b !default;
$quaternaryColor: #d278c9 !default;
/* Dark theme */
$mainColor: #444 !default; /* Light: #573454 Dark: #3b2639 -moz-linear-gradient(top, #fca99b 0%, #6e2842 100%) */
@ -34,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;
@ -71,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)

268
src/styles/scss/main.scss

@ -48,6 +48,26 @@
transform: $v;
}
$prefix: $app-prefix;
.#{$prefix} {
@each $cnum, $ccol in (one, $primaryColor), (two, $secondaryColor), (three, $tertiaryColor), (four, $quaternaryColor) {
&#{$cnum} {
&-bg {
background-color: $ccol;
}
&-color {
color: $ccol;
&-h:hover {
color: $ccol;
}
}
}
}
}
.#{$app-prefix}bg {
&-main {
@ -193,13 +213,14 @@
@include user-select(none);
}
.#{$app-prefix}editor{
font-family: $mainFont;
background-color: $mainColor;
position:relative;
box-sizing: border-box;
height: 100%;
.#{$app-prefix}editor {
font-family: $mainFont;
font-size: $fontSize;
position: relative;
box-sizing: border-box;
height: 100%;
}
.#{$app-prefix}freezed,
.#{$nv-prefix}freezed {
@include opacity(0.50);
@ -219,7 +240,6 @@
display: flex;
justify-content: start;
padding: 5px 10px;
font-size: 0.75em;
font-weight: lighter;
.#{$app-prefix}label {
@ -313,8 +333,8 @@
/********* PANELS **********/
.#{$pn-prefix}panel {
@extend .#{$app-prefix}bg-main;
@extend .#{$app-prefix}color-main;
//@extend .#{$app-prefix}bg-main;
//@extend .#{$app-prefix}color-main;
display: inline-block;
padding: 5px;
position: absolute;
@ -362,14 +382,13 @@
}
}
.#{$pn-prefix}buttons{
.#{$pn-prefix}buttons {
align-items: center;
display: flex;
justify-content: center;
justify-content: space-between;
}
.#{$pn-prefix}btn, .#{$app-prefix}btnt{
@extend .#{$app-prefix}color-main;
.#{$pn-prefix}btn, .#{$app-prefix}btnt {
box-sizing: border-box;
height: 30px; width: 30px;
line-height: 21px;
@ -382,8 +401,7 @@
padding: 5px;
position: relative;
&.#{$pn-prefix}active{
@extend .#{$app-prefix}color-active;
&.#{$pn-prefix}active {
background-color: rgba(0, 0, 0, 0.15);
box-shadow: 0 0 3px rgba(0, 0, 0, 0.25) inset;
}
@ -442,36 +460,7 @@
@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 **********/
@ -559,14 +548,10 @@
}
.#{$clm-prefix}tag {
background-color: $tagBg;
border: 1px solid darken($tagBg, 12%);
box-shadow: 1px 1px lighten($tagBg, 9%) inset;
text-shadow: 1px 1px darken($tagBg, 7%);
display: inline-block;
border-radius:3px;
margin: 0 3px 3px 0;
padding: 4px;
padding: 5px;
cursor: default;
}
##{$clm-prefix}close {
@ -601,65 +586,7 @@
/********* 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 {
@extend .#{$app-prefix}bg-main;
@extend .#{$app-prefix}color-main;
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;
}
.#{$mdl-prefix}content{
border-top: 1px solid $mainLhColor;
}
.#{$app-prefix}export-dl::after{
content: "";
clear: both;
display:block;
margin-bottom:10px;
}
@import "gjs_modal";
/********* Assets Manager **********/
@ -725,7 +652,6 @@
}
&##{$cm-prefix}css {
padding-left: 10px;
border-left: 1px solid $mainLhColor;
##{$cm-prefix}title { color: #ddca7e;}
}
##{$cm-prefix}title {
@ -742,70 +668,70 @@
/********* Spectrum **********/
.sp-hue, .sp-slider{ cursor: row-resize;}
.sp-color, .sp-dragger{ cursor: crosshair;}
.sp-alpha-inner, .sp-alpha-handle{cursor: col-resize;}
.sp-hue{ left: 90%; }
.sp-color{right: 15%;}
.sp-container {
@extend .#{$app-prefix}bg-main;
border: 1px solid $mainDkColor;
box-shadow: 0 0 7px $mainDkColor;
border-radius: 3px;
}
.sp-picker-container{border:none;}
.colpick_dark .colpick_color { outline: 1px solid $mainDkColor;}
.sp-cancel, .sp-cancel:hover {
bottom: -8px;
color: #777 !important;
font-size: 25px;
left: 0;
position: absolute;
text-decoration:none;
}
.sp-alpha-handle {
background-color: #ccc;
border: 1px solid #555;
width: 4px;
}
.sp-color, .sp-hue { border: 1px solid #333333;}
.sp-slider {
background-color: #ccc;
border: 1px solid #555;
height: 3px;
left: -4px;
width: 22px;
}
.sp-dragger{background:transparent; box-shadow: 0 0 0 1px #111;}
.sp-button-container{
float: none;
width: 100%;
position: relative;
text-align: right;
}
.sp-container button, .sp-container button:hover, .sp-container button:active{
background: $mainDkColor;
border-color: $mainDkColor;
color: $fontColor;
text-shadow: none;
box-shadow: none;
padding: 3px 5px;
}
.sp-palette-container{
border: none;
float:none;
margin:0;
padding:5px 10px 0;
}
.sp-palette .sp-thumb-el, .sp-palette .sp-thumb-el:hover{
border: 1px solid rgba(0, 0, 0, 0.9);
}
.#{$app-prefix}editor-cont {
.sp-hue, .sp-slider{ cursor: row-resize;}
.sp-color, .sp-dragger{ cursor: crosshair;}
.sp-alpha-inner, .sp-alpha-handle{cursor: col-resize;}
.sp-hue{ left: 90%; }
.sp-color{right: 15%;}
.sp-container {
border: 1px solid $mainDkColor;
box-shadow: 0 0 7px $mainDkColor;
border-radius: 3px;
}
.sp-picker-container{border:none;}
.colpick_dark .colpick_color { outline: 1px solid $mainDkColor;}
.sp-cancel, .sp-cancel:hover {
bottom: -8px;
color: #777 !important;
font-size: 25px;
left: 0;
position: absolute;
text-decoration:none;
}
.sp-alpha-handle {
background-color: #ccc;
border: 1px solid #555;
width: 4px;
}
.sp-color, .sp-hue { border: 1px solid #333333;}
.sp-slider {
background-color: #ccc;
border: 1px solid #555;
height: 3px;
left: -4px;
width: 22px;
}
.sp-dragger{background:transparent; box-shadow: 0 0 0 1px #111;}
.sp-button-container{
float: none;
width: 100%;
position: relative;
text-align: right;
}
.sp-container button, .sp-container button:hover, .sp-container button:active{
background: $mainDkColor;
border-color: $mainDkColor;
color: $fontColor;
text-shadow: none;
box-shadow: none;
padding: 3px 5px;
}
.sp-palette-container{
border: none;
float:none;
margin:0;
padding:5px 10px 0;
}
.sp-palette .sp-thumb-el, .sp-palette .sp-thumb-el:hover{
border: 1px solid rgba(0, 0, 0, 0.9);
}
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
border-color: rgba(0, 0, 0, 0.9);
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
border-color: rgba(0, 0, 0, 0.9);
}
}
.#{$app-prefix}hidden {
display: none;
}

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)

128
src/utils/Sorter.js

@ -1,4 +1,5 @@
import {on, off} from 'utils/mixins';
import { isString } from 'underscore';
import { on, off, matches } from 'utils/mixins';
const $ = Backbone.$;
module.exports = Backbone.View.extend({
@ -114,23 +115,24 @@ module.exports = Backbone.View.extend({
* @param {Event} event
*/
setDragHelper(el, event) {
var ev = event || '';
var clonedEl = el.cloneNode(1);
const ev = event || '';
const clonedEl = el.cloneNode(1);
const rect = el.getBoundingClientRect();
const computed = getComputedStyle(el);
let style = '';
// Attach style
var style = '';
var o = getComputedStyle(el);
for(var i = 0; i < o.length; i++) {
style += o[i] + ':' + o.getPropertyValue(o[i])+';';
for (var i = 0; i < computed.length; i++) {
const prop = computed[i];
style += `${prop}:${computed.getPropertyValue(prop)};`;
}
clonedEl.setAttribute('style', style);
clonedEl.className += ' ' + this.pfx + 'bdrag';
document.body.appendChild(clonedEl);
clonedEl.className += ` ${this.pfx}bdrag`;
clonedEl.setAttribute('style', style);
this.dragHelper = clonedEl;
if(ev) {
this.moveDragHelper(ev);
}
clonedEl.style.width = `${rect.width}px`;
clonedEl.style.height = `${rect.height}px`;
ev && this.moveDragHelper(ev);
// Listen mouse move events
if(this.em) {
@ -142,29 +144,39 @@ module.exports = Backbone.View.extend({
},
/**
* //TODO Refactor, use canvas.getMouseRelativePos to get mouse's X and Y
* Update the position of the helper
* @param {Event} e
*/
moveDragHelper(e) {
var doc = e.target.ownerDocument;
const doc = e.target.ownerDocument;
if(!this.dragHelper || !doc) {
return;
}
var win = doc.defaultView || doc.parentWindow;
var addTop = 0;
var addLeft = 0;
var frame = win.frameElement;
if(frame) {
var frameRect = frame.getBoundingClientRect(); // maybe to cache ?!?
addTop = frameRect.top || 0;
addLeft = frameRect.left || 0;
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) {
const frameRect = frame.getBoundingClientRect();
addTop = frameRect.top + document.documentElement.scrollTop;
addLeft = frameRect.left + document.documentElement.scrollLeft;
posY = e.clientY;
posX = e.clientX;
}
var hStyle = this.dragHelper.style;
hStyle.left = (e.pageX - win.pageXOffset + addLeft) + 'px';
hStyle.top = (e.pageY - win.pageYOffset + addTop) + 'px';
dragHelperStyle.top = (posY + addTop) + 'px';
dragHelperStyle.left = (posX + addLeft) + 'px';
},
@ -175,13 +187,7 @@ module.exports = Backbone.View.extend({
* @return {Boolean}
*/
matches(el, selector, useBody) {
var startEl = el.parentNode || document.body;
//startEl = useBody ? startEl.ownerDocument.body : startEl;
var els = startEl.querySelectorAll(selector);
var i = 0;
while (els[i] && els[i] !== el)
++i;
return !!els[i];
return matches.call(el, selector);
},
/**
@ -242,6 +248,7 @@ module.exports = Backbone.View.extend({
const container = this.getContainerEl();
const docs = this.getDocuments();
const onStart = this.onStart;
let srcModel;
let plh = this.plh;
this.dropModel = null;
this.moved = 0;
@ -262,7 +269,7 @@ module.exports = Backbone.View.extend({
}
if (src) {
const srcModel = this.getSourceModel(src);
srcModel = this.getSourceModel(src);
srcModel && srcModel.set && srcModel.set('status', 'freezed');
}
@ -274,6 +281,8 @@ module.exports = Backbone.View.extend({
// Avoid strange effects on dragging
em && em.clearSelection();
this.toggleSortCursor(1);
em && em.trigger('sorter:drag:start', src, srcModel);
},
/**
@ -285,6 +294,7 @@ module.exports = Backbone.View.extend({
return $(elem).data('model');
},
/**
* Get the model of the current source element (element to drag)
* @return {Model}
@ -310,6 +320,7 @@ module.exports = Backbone.View.extend({
}
},
/**
* Highlight target
* @param {Model|null} model
@ -335,11 +346,13 @@ module.exports = Backbone.View.extend({
* @param {Event} e
* */
onMove(e) {
const em = this.em;
this.moved = 1;
// Turn placeholder visibile
var plh = this.plh;
var dsp = plh.style.display;
if(!dsp || dsp === 'none')
if (!dsp || dsp === 'none')
plh.style.display = 'block';
// Cache all necessary positions
@ -349,8 +362,8 @@ module.exports = Backbone.View.extend({
var rY = (e.pageY - this.elT) + this.el.scrollTop;
var rX = (e.pageX - this.elL) + this.el.scrollLeft;
if (this.canvasRelative && this.em) {
var mousePos = this.em.get('Canvas').getMouseRelativeCanvas(e);
if (this.canvasRelative && em) {
var mousePos = em.get('Canvas').getMouseRelativeCanvas(e);
rX = mousePos.x;
rY = mousePos.y;
}
@ -359,9 +372,10 @@ module.exports = Backbone.View.extend({
this.rY = rY;
this.eventMove = e;
//var target = this.getTargetFromEl(e.target);
var dims = this.dimsFromTarget(e.target, rX, rY);
let targetModel = this.getTargetModel(this.target);
//var targetNew = this.getTargetFromEl(e.target);
const dims = this.dimsFromTarget(e.target, rX, rY);
const target = this.target;
const targetModel = this.getTargetModel(target);
this.selectTargetModel(targetModel);
this.lastDims = dims;
@ -387,6 +401,15 @@ module.exports = Backbone.View.extend({
if(typeof this.onMoveClb === 'function')
this.onMoveClb(e);
em && em.trigger('sorter:drag', {
target,
targetModel,
dims,
pos,
x: rX,
y: rY,
});
},
/**
@ -475,14 +498,14 @@ module.exports = Backbone.View.extend({
droppable = droppable instanceof Backbone.Collection ? 1 : droppable;
droppable = droppable instanceof Array ? droppable.join(', ') : droppable;
result.dropInfo = droppable;
droppable = typeof droppable === 'string' ? this.matches(src, droppable) : droppable;
droppable = isString(droppable) ? this.matches(src, droppable) : droppable;
result.droppable = droppable;
// check if the source is draggable in target
let draggable = srcModel.get('draggable');
draggable = draggable instanceof Array ? draggable.join(', ') : draggable;
result.dragInfo = draggable;
draggable = typeof draggable === 'string' ? this.matches(trg, draggable) : draggable;
draggable = isString(draggable) ? this.matches(trg, draggable) : draggable;
result.draggable = draggable;
if (!droppable || !draggable) {
@ -500,6 +523,7 @@ module.exports = Backbone.View.extend({
* @return {Array<Array>}
*/
dimsFromTarget(target, rX, rY) {
const em = this.em;
var dims = [];
if (!target) {
@ -531,6 +555,8 @@ module.exports = Backbone.View.extend({
// Check if the source is valid with the target
let validResult = this.validTarget(target);
em && em.trigger('sorter:drag:validation', validResult);
if (!validResult.valid && this.targetP) {
return this.dimsFromTarget(this.targetP, rX, rY);
}
@ -575,11 +601,13 @@ module.exports = Backbone.View.extend({
let target = el;
let targetParent;
let targetPrev = this.targetPrev;
const em = this.em;
const containerSel = this.containerSel;
const itemSel = this.itemSel;
// Select the first valuable target
if (!this.matches(target, `${this.itemSel}, ${containerSel}`)) {
target = this.closest(target, this.itemSel);
if (!this.matches(target, `${itemSel}, ${containerSel}`)) {
target = this.closest(target, itemSel);
}
// If draggable is an array the target will be one of those
@ -599,7 +627,10 @@ module.exports = Backbone.View.extend({
// If the current target is not valid (src/trg reasons) try with
// the parent one (if exists)
if (!this.validTarget(target).valid && targetParent) {
const validResult = this.validTarget(target);
em && em.trigger('sorter:drag:validation', validResult);
if (!validResult.valid && targetParent) {
return this.getTargetFromEl(targetParent);
}
@ -910,7 +941,7 @@ module.exports = Backbone.View.extend({
* */
move(dst, src, pos) {
var em = this.em;
em && em.trigger('component:dragEnd:before', dst, src, pos);
em && em.trigger('component:dragEnd:before', dst, src, pos); // @depricated
var warns = [];
var index = pos.index;
var modelToDrop, modelTemp, created;
@ -966,7 +997,8 @@ module.exports = Backbone.View.extend({
console.warn('Invalid target position: ' + warns.join(', '));
}
em && em.trigger('component:dragEnd', targetCollection, modelToDrop, warns);
em && em.trigger('component:dragEnd', targetCollection, modelToDrop, warns); // @depricated
em && em.trigger('sorter:drag:end', targetCollection, modelToDrop, warns);
return created;
},

63
src/utils/mixins.js

@ -1,3 +1,50 @@
import { omit, keys, isUndefined } from 'underscore';
const elProt = window.Element.prototype;
const matches = elProt.matches || elProt.webkitMatchesSelector || elProt.mozMatchesSelector || elProt.msMatchesSelector;
/**
* Returns shallow diff between 2 objects
* @param {Object} objOrig
* @param {Objec} objNew
* @return {Object}
* @example
* var a = {foo: 'bar', baz: 1, faz: 'sop'};
* var b = {foo: 'bar', baz: 2, bar: ''};
* shallowDiff(a, b);
* // -> {baz: 2, faz: null, bar: ''};
*/
const shallowDiff = (objOrig, objNew) => {
const result = {};
const keysNew = keys(objNew);
for (let prop in objOrig) {
if (objOrig.hasOwnProperty(prop)) {
const origValue = objOrig[prop];
const newValue = objNew[prop];
if (keysNew.indexOf(prop) >= 0) {
if (origValue !== newValue) {
result[prop] = newValue;
}
} else {
result[prop] = null;
}
}
}
for (let prop in objNew) {
if (objNew.hasOwnProperty(prop)) {
if (isUndefined(objOrig[prop])) {
result[prop] = objNew[prop];
}
}
}
return result;
};
const on = (el, ev, fn) => {
ev = ev.split(/\s+/);
el = el instanceof Array ? el : [el];
@ -31,11 +78,27 @@ 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,
off,
upFirst,
matches,
camelCase,
shallowDiff,
normalizeFloat,
getUnitFromValue
}

1
test/main.js

@ -9,6 +9,7 @@ describe('Main', () => {
});
const path = './specs/';
require(`${path}keymaps`);
require(`${path}asset_manager`);
require(`${path}block_manager`);
require(`${path}code_manager`);

2
test/specs/asset_manager/view/AssetImageView.js

@ -66,7 +66,7 @@ module.exports = {
it('Could be removed', function() {
var spy = sinon.spy();
obj.model.on("remove", spy);
obj.onRemove({stopPropagation() {}});
obj.onRemove({stopImmediatePropagation() {}});
expect(spy.called).toEqual(true);
});

57
test/specs/code_manager/model/CodeModels.js

@ -2,18 +2,24 @@ const CssGenerator = require('code_manager/model/CssGenerator');
const HtmlGenerator = require('code_manager/model/HtmlGenerator');
const DomComponents = require('dom_components');
const Component = require('dom_components/model/Component');
const Editor = require('editor/model/Editor');
const CssComposer = require('css_composer');
module.exports = {
run() {
let comp;
let dcomp;
let obj;
let em;
let cc;
describe('HtmlGenerator', () => {
beforeEach(() => {
em = new Editor();
obj = new HtmlGenerator();
var dcomp = new DomComponents();
dcomp = new DomComponents();
comp = new Component({}, {
em,
componentTypes: dcomp.componentTypes,
});
});
@ -54,16 +60,19 @@ module.exports = {
['class1', 'class2'].forEach(item => {
m1.get('classes').add({name: item});
});
expect(obj.build(comp)).toEqual('<article class="class1 class2" data-test1="value1" data-test2="value2"></article>');
expect(obj.build(comp)).toEqual('<article data-test1="value1" data-test2="value2" class="class1 class2"></article>');
});
});
describe('CssGenerator', () => {
var newCssComp = () => new CssComposer().init();
beforeEach(() => {
em = new Editor({});
cc = em.get('CssComposer');
obj = new CssGenerator();
var dcomp = new DomComponents();
dcomp = new DomComponents();
comp = new Component({}, {
em,
componentTypes: dcomp.componentTypes,
});
});
@ -213,6 +222,48 @@ module.exports = {
comp.get('components').remove(m1);
expect(obj.build(comp, {cssc})).toEqual('');
});
it('Render correctly a rule without avoidInlineStyle option', () => {
comp.setStyle({color: 'red'});
const id = comp.getId();
const result = `#${id}{color:red;}`;
expect(obj.build(comp, {cssc: cc})).toEqual(result);
});
it('Render correctly a rule with avoidInlineStyle option', () => {
em.getConfig().avoidInlineStyle = 1;
comp = new Component({}, {
em,
componentTypes: dcomp.componentTypes,
});
comp.setStyle({color: 'red'});
const id = comp.getId();
const result = `#${id}{color:red;}`;
expect(obj.build(comp, {cssc: cc, em})).toEqual(result);
});
it('Render correctly a rule with avoidInlineStyle and state', () => {
em.getConfig().avoidInlineStyle = 1;
const state = 'hover';
comp.config.avoidInlineStyle = 1;
comp.set('state', state);
comp.setStyle({color: 'red'});
const id = comp.getId();
const result = `#${id}:${state}{color:red;}`;
expect(obj.build(comp, {cssc: cc, em})).toEqual(result);
});
it('Render correctly a rule with avoidInlineStyle and w/o state', () => {
em.getConfig().avoidInlineStyle = 1;
const state = 'hover';
comp.config.avoidInlineStyle = 1;
comp.setStyle({color: 'blue'});
comp.set('state', state);
comp.setStyle({color: 'red'});
const id = comp.getId();
const result = `#${id}{color:blue;}#${id}:${state}{color:red;}`;
expect(obj.build(comp, {cssc: cc, em})).toEqual(result);
});
})
}
};

5
test/specs/css_composer/e2e/CssComposer.js

@ -130,10 +130,11 @@ module.exports = {
active: true,
label: 'test1',
name: 'test1',
type: 'class',
type: clsm.Selector.TYPE_CLASS,
private: false,
protected: false,
}],
important: 0,
selectorsAdd: '',
state: '',
stylable: true,
@ -172,6 +173,7 @@ module.exports = {
rule1Out = JSON.parse(JSON.stringify(rule1Out));
rule2Out = JSON.parse(JSON.stringify(rule2Out));
var rule1Result = {
important: 0,
mediaText: '',
selectors: [],
selectorsAdd: '*',
@ -183,6 +185,7 @@ module.exports = {
}
};
var rule2Result = {
important: 0,
mediaText: '',
selectors: [],
selectorsAdd: 'p',

56
test/specs/css_composer/index.js

@ -1,16 +1,17 @@
var Models = require('./model/CssModels');
var CssRuleView = require('./view/CssRuleView');
var CssRulesView = require('./view/CssRulesView');
var CssComposer = require('css_composer');
var e2e = require('./e2e/CssComposer');
var utils = require('./../test_utils.js');
const Models = require('./model/CssModels');
const CssRuleView = require('./view/CssRuleView');
const CssRulesView = require('./view/CssRulesView');
const CssComposer = require('css_composer');
const e2e = require('./e2e/CssComposer');
const utils = require('./../test_utils.js');
const Editor = require('editor/model/Editor');
describe('Css Composer', () => {
describe('Main', () => {
var obj;
var em;
var config;
var storagMock = utils.storageMock();
var editorModel = {
@ -33,7 +34,8 @@ describe('Css Composer', () => {
beforeEach(() => {
config = {};
em = new Editor({});
config = {em};
obj = new CssComposer().init(config);
});
@ -158,6 +160,44 @@ describe('Css Composer', () => {
expect(obj.render()).toExist();
});
it('Create a rule with id selector by using setIdRule()', () => {
const name = 'test';
obj.setIdRule(name, {color: 'red'});
expect(obj.getAll().length).toEqual(1);
const rule = obj.getIdRule(name);
expect(rule.selectorsToString()).toEqual(`#${name}`);
expect(rule.styleToString()).toEqual(`color:red;`);
expect(rule.styleToString({important: 1})).toEqual(`color:red !important;`);
expect(rule.styleToString({important: ['color']})).toEqual(`color:red !important;`);
});
it('Create a rule with id selector and state by using setIdRule()', () => {
const name = 'test';
const state = 'hover';
obj.setIdRule(name, {color: 'red'}, {state});
expect(obj.getAll().length).toEqual(1);
const rule = obj.getIdRule(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();

24
test/specs/css_composer/model/CssModels.js

@ -56,6 +56,30 @@ module.exports = {
expect(obj.compare([s2, s1], 'hover', '1000')).toEqual(true);
});
it('toCSS returns empty if there is no style', () => {
var s1 = obj.get('selectors').add({ name: 'test1' });
expect(obj.toCSS()).toEqual('');
});
it('toCSS returns empty if there is no selectors', () => {
obj.setStyle({color: 'red'});
expect(obj.toCSS()).toEqual('');
});
it('toCSS returns simple CSS', () => {
obj.get('selectors').add({ name: 'test1' });
obj.setStyle({color: 'red'});
expect(obj.toCSS()).toEqual(`.test1{color:red;}`);
});
it('toCSS wraps correctly inside media rule', () => {
const media = '(max-width: 768px)';
obj.set('mediaText', media);
obj.get('selectors').add({ name: 'test1' });
obj.setStyle({color: 'red'});
expect(obj.toCSS()).toEqual(`@media ${media}{.test1{color:red;}}`);
});
});
describe('CssRules', () => {

20
test/specs/css_composer/view/CssRuleView.js

@ -26,26 +26,6 @@ module.exports = {
expect(CssRuleView).toExist();
});
it('Correct behaviour of renderSelectors with single selector', () => {
obj.model.get('selectors').add({name: 'test'});
expect(obj.renderSelectors()).toEqual('.test');
});
it('Correct behaviour of renderSelectors with multiple selectors', () => {
obj.model.get('selectors').add([{name: 'test2'}, {name: 'test1'}]);
expect(obj.renderSelectors()).toEqual('.test2.test1');
});
it('Correct behaviour of renderProperties with single property', () => {
obj.model.set('style', {'prop': 'value'});
expect(obj.renderProperties()).toEqual('prop:value;');
});
it('Correct behaviour of renderProperties with multiple properties', () => {
obj.model.set('style', {'prop2': 'value2', 'prop3': 'value3'});
expect(obj.renderProperties()).toEqual('prop2:value2;prop3:value3;');
});
it('Empty style inside', function() {
expect(fixtures.innerHTML).toEqual('<style></style>');
});

4
test/specs/dom_components/index.js

@ -5,12 +5,14 @@ const ComponentView = require('./view/ComponentV');
const ComponentsView = require('./view/ComponentsView');
const ComponentTextView = require('./view/ComponentTextView');
const ComponentImageView = require('./view/ComponentImageView');
const Editor = require('editor/model/Editor');
const utils = require('./../test_utils.js');
describe('DOM Components', () => {
describe('Main', () => {
var em;
var obj;
var config;
var storagMock = utils.storageMock();
@ -39,7 +41,9 @@ describe('DOM Components', () => {
beforeEach(() => {
em = new Editor();
config = {
em,
storeWrapper: 1,
};
obj = new DomComponents().init(config);

11
test/specs/dom_components/model/Component.js

@ -82,6 +82,16 @@ module.exports = {
expect(obj.toHTML()).toEqual('<article data-test1="value1" data-test2="value2"></article>');
});
it('Component toHTML with value-less attribute', () => {
obj = new Component({
tagName: 'div',
attributes: {
'data-is-a-test': ''
}
});
expect(obj.toHTML()).toEqual('<div data-is-a-test=""></div>');
});
it('Component toHTML with classes', () => {
obj = new Component({
tagName: 'article'
@ -169,6 +179,7 @@ module.exports = {
});
expect(obj.getAttributes()).toEqual({
id: 'test',
class: 'class1 class2',
'data-test': 'value',
});
expect(obj.get('classes').length).toEqual(2);

8
test/specs/dom_components/view/ComponentV.js

@ -80,14 +80,6 @@ module.exports = {
expect(view.el.getAttribute('style')).toEqual(null);
});
it('Get style string', () => {
model.set('style', {
color: 'red',
float: 'left'
});
expect(view.getStyleString()).toEqual('color:red;float:left;');
});
it('Add class', () => {
model.get('classes').add({name: 'test'});
expect(view.el.getAttribute('class')).toEqual('test');

34
test/specs/grapesjs/index.js

@ -16,11 +16,12 @@ describe('GrapesJS', () => {
var storage;
var storageId = 'testStorage';
var storageMock = {
store(data) {
store(data, clb) {
storage = data;
clb();
},
load(keys) {
return storage;
load(keys, clb) {
return clb(storage);
},
};
@ -36,7 +37,8 @@ describe('GrapesJS', () => {
container: '#' + editorName,
storageManager: {
autoload: 0,
type:'none'
autosave: 0,
type: ''
},
}
obj = grapesjs;
@ -47,11 +49,6 @@ describe('GrapesJS', () => {
fixtures = document.body.querySelector('#fixtures');
});
afterEach(() => {
config = {};
obj = null;
});
it('Main object should be loaded', () => {
expect(obj).toExist();
});
@ -76,7 +73,7 @@ describe('GrapesJS', () => {
it('New editor is empty', () => {
var editor = obj.init(config);
var html = editor.getHtml();
var css = editor.getCss();
//var css = editor.getCss();
var protCss = editor.getConfig().protectedCss;
expect((html ? html : '')).toNotExist();
//expect((css ? css : '')).toEqual(protCss);
@ -154,7 +151,7 @@ describe('GrapesJS', () => {
expect(styles.at(1).get('selectors').at(0).get('name')).toEqual('test5');
});
it('Adds new storage as plugin and store data there', () => {
it.skip('Adds new storage as plugin and store data there', done => {
var pluginName = storageId + '-plugin';
obj.plugins.add(pluginName, edt => {
edt.StorageManager.add(storageId, storageMock);
@ -163,9 +160,11 @@ describe('GrapesJS', () => {
config.plugins = [pluginName];
var editor = obj.init(config);
editor.setComponents(htmlString);
editor.store();
editor.load((data) => {
expect(data.html).toEqual(htmlString);
editor.store(() => {
editor.load((data) => {
expect(data.html).toEqual(htmlString);
done();
});
});
});
@ -182,8 +181,7 @@ describe('GrapesJS', () => {
expect(editor.customValue).toEqual('TEST');
});
// Problems with iframe loading
it.skip('Execute custom command', () => {
it('Execute custom command', () => {
var editor = obj.init(config);
editor.testVal = '';
editor.setComponents(htmlString);
@ -196,7 +194,7 @@ describe('GrapesJS', () => {
expect(editor.testVal).toEqual(htmlString + '5');
});
it.skip('Stop custom command', () => {
it('Stop custom command', () => {
var editor = obj.init(config);
editor.testVal = '';
editor.setComponents(htmlString);
@ -231,7 +229,7 @@ describe('GrapesJS', () => {
});
// Problems with iframe loading
it.skip('Init new editor with custom plugin overrides default commands', () => {
it('Init new editor with custom plugin overrides default commands', () => {
var editor,
pluginName = 'test-plugin-opts';

60
test/specs/keymaps/index.js

@ -0,0 +1,60 @@
const Editor = require('editor/model/Editor');
const Keymaps = require('keymaps');
describe('Keymaps', () => {
describe('Main', () => {
let em;
let obj;
beforeEach(() => {
em = new Editor();
obj = new Keymaps().init({em});
});
it('Object exists', () => {
expect(obj).toExist();
});
it('No keymaps inside', () => {
var coll = obj.getAll();
expect(coll).toEqual({});
});
it('Add new keymap', () => {
const id = 'test';
const keys = 'ctrl+a';
const handler = () => {};
const model = obj.add(id, 'ctrl+a', handler);
expect(obj.get(id)).toEqual({id, keys, handler});
});
it('Add keymap event triggers', () => {
let called = 0;
em.on('keymap:add', () => called = 1);
const model = obj.add('tes', 'ctrl+a');
expect(called).toEqual(1);
});
it('Remove keymap', () => {
const id = 'test';
const keys = 'ctrl+a';
const handler = () => {};
const model = obj.add(id, 'ctrl+a', handler);
const removed = obj.remove(id);
expect(obj.get(id)).toEqual(undefined);
expect(obj.getAll()).toEqual({});
expect(removed).toEqual({id, keys, handler});
});
it('Remove keymap event triggers', () => {
let called = 0;
em.on('keymap:remove', () => called = 1);
const model = obj.add('tes', 'ctrl+a');
const removed = obj.remove('tes');
expect(called).toEqual(1);
});
});
});

54
test/specs/panels/index.js

@ -102,6 +102,60 @@ describe('Panels', () => {
expect(spy.called).toEqual(true);
});
it("Can't remove button to non existent panel", () => {
expect(obj.removeButton('test', {id:'btn'})).toEqual(null);
});
describe('Removes button', () => {
it("Remove button correctly with object", () => {
var panel = obj.addPanel({id: 'test'});
var btn = obj.addButton('test', {id:'btn'});
expect(panel.get('buttons').length).toEqual(1);
expect(panel.get('buttons').at(0).get('id')).toEqual('btn');
expect(obj.removeButton('test', {id:'btn'})).toEqual(btn);
expect(panel.get('buttons').length).toEqual(0);
});
it("Remove button correctly with sting", () => {
var panel = obj.addPanel({id: 'test'});
var btn = obj.addButton('test', {id:'btn'});
expect(panel.get('buttons').length).toEqual(1);
expect(panel.get('buttons').at(0).get('id')).toEqual('btn');
expect(obj.removeButton('test', 'btn')).toEqual(btn);
expect(panel.get('buttons').length).toEqual(0);
});
});
describe('Removes Panel', () => {
it("Removes panel correctly via object", () => {
var panel = obj.addPanel({id: 'test'});
expect(panel.get('id')).toEqual('test');
obj.removePanel({id: 'test'});
expect(panel.get('id')).toEqual('test');
});
it("Removes panel correctly via Panel instance", () => {
var oPanel = new obj.Panel({id: 'test'});
var panel = obj.addPanel(oPanel);
expect(panel).toEqual(oPanel);
expect(panel.get('id')).toEqual('test');
obj.removePanel(oPanel);
expect(obj.getPanels.length).toEqual(0);
});
it("Removes panel correctly via id", () => {
var oPanel = new obj.Panel({id: 'test'});
var panel = obj.addPanel(oPanel);
expect(panel).toEqual(oPanel);
expect(panel.get('id')).toEqual('test');
obj.removePanel('test');
expect(obj.getPanels.length).toEqual(0);
});
});
});
Models.run();

4
test/specs/panels/view/ButtonView.js

@ -44,7 +44,7 @@ module.exports = {
it('Check enable active', () => {
model.set('active', true, {silent: true});
view.checkActive();
expect(view.el.getAttribute('class')).toEqual(btnClass + ' active');
expect(view.el.getAttribute('class')).toContain(btnClass + ' active');
});
it('Check disable active', () => {
@ -60,7 +60,7 @@ module.exports = {
view.updateDisable();
expect(view.el.getAttribute('class')).toEqual(btnClass + ' active');
});
it('Enable the disabled button', () => {
model.set('disable', true, {silent: true});
view.updateDisable();

3
test/specs/panels/view/PanelView.js

@ -25,7 +25,8 @@ module.exports = {
});
it('Panel empty', () => {
expect(fixtures.innerHTML).toEqual('<div class="panel"></div>');
fixtures.firstChild.className = "";
expect(fixtures.innerHTML).toEqual('<div class=""></div>');
});
it('Append content', () => {

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',

2
test/specs/selector_manager/index.js

@ -44,7 +44,7 @@ describe('SelectorManager', () => {
it('Default new selector is a class type', () => {
obj.add('test');
expect(obj.get('test').get('type')).toEqual('class');
expect(obj.get('test').get('type')).toEqual(obj.Selector.TYPE_CLASS);
});
it('Check name property', () => {

21
test/specs/selector_manager/model/SelectorModels.js

@ -37,7 +37,13 @@ module.exports = {
});
describe('Selectors', () => {
var obj;
beforeEach(() => {
obj = new Selectors();
});
it('Creates collection item correctly', () => {
var c = new Selectors();
@ -45,6 +51,21 @@ module.exports = {
expect(m instanceof Selector).toEqual(true);
});
it('getFullString with single class', () => {
obj.add({name: 'test'});
expect(obj.getFullString()).toEqual('.test');
});
it('getFullString with multiple classes', () => {
obj.add([{name: 'test'}, {name: 'test2'}]);
expect(obj.getFullString()).toEqual('.test.test2');
});
it('getFullString with mixed selectors', () => {
obj.add([{name: 'test'}, {name: 'test2', type: Selector.TYPE_ID}]);
expect(obj.getFullString()).toEqual('.test#test2');
});
});
}
};

13
test/specs/selector_manager/view/ClassTagsView.js

@ -1,5 +1,6 @@
const ClassTagsView = require('selector_manager/view/ClassTagsView');
const Selectors = require('selector_manager/model/Selectors');
const Editor = require('editor/model/Editor');
module.exports = {
run() {
@ -11,6 +12,7 @@ module.exports = {
var testLabel;
var coll;
var target;
var em;
before(() => {
document.body.innerHTML = '<div id="fixtures"></div>';
@ -23,10 +25,8 @@ module.exports = {
});
beforeEach(function () {
target = { get() {} };
target = new Editor();
coll = new Selectors();
_.extend(target, Backbone.Events);
view = new ClassTagsView({
config : { em: target },
collection: coll
@ -74,16 +74,13 @@ module.exports = {
expect(this.input.css('display')).toNotEqual('none');
});
it.skip('Stop tag creation', function() {
it('Stop tag creation', function() {
this.btnAdd.trigger('click');
this.input.val('test')
this.input.trigger('blur');
//(this.btnAdd.css('display') !== 'none').should.equal(true);
//(this.input.css('display') == 'none').should.equal(true);
//this.input.val().should.equal('');
expect(this.btnAdd.css('display')).toNotEqual('none');
expect(this.input.css('display')).toEqual('none');
expect(this.input.val()).toEqual('');
expect(this.input.val()).toEqual(null);
});
it.skip('Check keyup of ESC on input', function() {

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save