Browse Source

Merge pull request #312 from artf/dev

Merge from dev
pull/432/head
Artur Arseniev 9 years ago
committed by GitHub
parent
commit
e613f3e991
  1. 5
      .eslintrc
  2. 8
      README.md
  3. 2
      dist/css/grapes.min.css
  4. 6
      dist/grapes.min.js
  5. 24
      index.html
  6. 23
      package.json
  7. 51
      src/asset_manager/config/config.js
  8. 193
      src/asset_manager/index.js
  9. 4
      src/asset_manager/model/Asset.js
  10. 13
      src/asset_manager/model/AssetImage.js
  11. 82
      src/asset_manager/model/Assets.js
  12. 109
      src/asset_manager/view/AssetImageView.js
  13. 61
      src/asset_manager/view/AssetView.js
  14. 179
      src/asset_manager/view/AssetsView.js
  15. 239
      src/asset_manager/view/FileUploader.js
  16. 21
      src/block_manager/view/BlockView.js
  17. 7
      src/block_manager/view/BlocksView.js
  18. 9
      src/canvas/index.js
  19. 117
      src/canvas/view/CanvasView.js
  20. 12
      src/code_manager/index.js
  21. 15
      src/code_manager/model/CssGenerator.js
  22. 25
      src/code_manager/model/HtmlGenerator.js
  23. 6
      src/commands/index.js
  24. 28
      src/commands/view/ExportTemplate.js
  25. 29
      src/commands/view/OpenAssets.js
  26. 26
      src/commands/view/OpenLayers.js
  27. 10
      src/commands/view/Resize.js
  28. 136
      src/commands/view/SelectComponent.js
  29. 29
      src/css_composer/index.js
  30. 23
      src/css_composer/model/CssRule.js
  31. 25
      src/dom_components/config/config.js
  32. 148
      src/dom_components/index.js
  33. 88
      src/dom_components/model/Component.js
  34. 5
      src/dom_components/model/ComponentImage.js
  35. 2
      src/dom_components/model/ComponentScript.js
  36. 5
      src/dom_components/model/Components.js
  37. 8
      src/dom_components/view/ComponentTextView.js
  38. 84
      src/dom_components/view/ComponentView.js
  39. 6
      src/dom_components/view/ComponentsView.js
  40. 4
      src/domain_abstract/model/Styleable.js
  41. 132
      src/domain_abstract/model/TypeableCollection.js
  42. 4
      src/domain_abstract/ui/InputNumber.js
  43. 19
      src/editor/config/config.js
  44. 73
      src/editor/index.js
  45. 163
      src/editor/model/Editor.js
  46. 62
      src/editor/view/EditorView.js
  47. 55
      src/grapesjs/index.js
  48. 10
      src/modal_dialog/index.js
  49. 5
      src/modal_dialog/view/ModalView.js
  50. 12
      src/navigator/config/config.js
  51. 87
      src/navigator/index.js
  52. 70
      src/navigator/view/ItemView.js
  53. 24
      src/navigator/view/ItemsView.js
  54. 9
      src/parser/model/ParserCss.js
  55. 10
      src/plugin_manager/index.js
  56. 7
      src/selector_manager/config/config.js
  57. 45
      src/selector_manager/index.js
  58. 52
      src/selector_manager/model/Selector.js
  59. 9
      src/selector_manager/model/Selectors.js
  60. 5
      src/selector_manager/view/ClassTagView.js
  61. 69
      src/selector_manager/view/ClassTagsView.js
  62. 10
      src/storage_manager/config/config.js
  63. 38
      src/storage_manager/index.js
  64. 21
      src/style_manager/index.js
  65. 81
      src/style_manager/model/Properties.js
  66. 82
      src/style_manager/model/Property.js
  67. 55
      src/style_manager/model/PropertyComposite.js
  68. 18
      src/style_manager/model/PropertyInteger.js
  69. 10
      src/style_manager/model/PropertyRadio.js
  70. 30
      src/style_manager/model/PropertyStack.js
  71. 37
      src/style_manager/view/PropertiesView.js
  72. 5
      src/style_manager/view/PropertyColorView.js
  73. 77
      src/style_manager/view/PropertyCompositeView.js
  74. 74
      src/style_manager/view/PropertyFileView.js
  75. 12
      src/style_manager/view/PropertyIntegerView.js
  76. 20
      src/style_manager/view/PropertyRadioView.js
  77. 24
      src/style_manager/view/PropertySelectView.js
  78. 122
      src/style_manager/view/PropertyStackView.js
  79. 305
      src/style_manager/view/PropertyView.js
  80. 32
      src/style_manager/view/SectorsView.js
  81. 143
      src/styles/scss/_gjs_assets.scss
  82. 2
      src/styles/scss/_gjs_canvas.scss
  83. 75
      src/styles/scss/_gjs_inputs.scss
  84. 144
      src/styles/scss/_gjs_layers.scss
  85. 7
      src/styles/scss/_gjs_style_manager.scss
  86. 78
      src/styles/scss/_gjs_variables.scss
  87. 219
      src/styles/scss/main.scss
  88. 17
      src/trait_manager/model/Trait.js
  89. 112
      src/utils/Resizer.js
  90. 6
      src/utils/Sorter.js
  91. 29
      src/utils/fetch.js
  92. 8
      test/specs/asset_manager/index.js
  93. 25
      test/specs/asset_manager/model/Assets.js
  94. 9
      test/specs/asset_manager/view/AssetImageView.js
  95. 17
      test/specs/asset_manager/view/AssetsView.js
  96. 11
      test/specs/asset_manager/view/FileUploader.js
  97. 22
      test/specs/code_manager/model/CodeModels.js
  98. 14
      test/specs/css_composer/e2e/CssComposer.js
  99. 14
      test/specs/dom_components/index.js
  100. 4
      test/specs/dom_components/model/Component.js

5
.eslintrc

@ -4,7 +4,10 @@
"node": true
},
"parserOptions": {
"sourceType": "module"
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"strict": 0,

8
README.md

@ -109,7 +109,7 @@ $ npm run build
JQuery is the only hard dependency so you have to include it before using GrapesJS
```html
<script src="http://code.jquery.com/jquery-2.2.0.min.js"></script>
<script src="//code.jquery.com/jquery-3.2.1.min.js"></script>
```
After that include scripts from GrapesJS with all your configurations
@ -186,12 +186,18 @@ $ npm test
* [grapesjs-blocks-basic](https://github.com/artf/grapesjs-blocks-basic) - Basic set of blocks
* [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
### Presets
* [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
Find out more about plugins here: [Creating plugins](https://github.com/artf/grapesjs/wiki/Creating-plugins)
## Sponsors

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

6
dist/grapes.min.js

File diff suppressed because one or more lines are too long

24
index.html

@ -831,11 +831,7 @@
fromElement: true,
clearOnRender: 0,
storageManager:{
autoload: 0,
storeComponents: 1,
storeStyles: 1,
},
storageManager: {autoload: 0},
commands: {
defaults: [{
@ -868,10 +864,11 @@
},
assetManager: {
storageType : '',
storeOnChange : true,
storeAfterUpload : true,
assets : [
upload: 'https://test.page',
params: {
_token: 'pCYrSwjuiV0t5NVtZpQDY41Gn5lNUwo3it1FIkAj',
},
assets: [
{ type: 'image', src : 'http://placehold.it/350x250/78c5d6/fff/image1.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/459ba8/fff/image2.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/79c267/fff/image3.jpg', height:350, width:250},
@ -1267,8 +1264,10 @@
command: function(editor, sender) {
if(sender) sender.set('active', false);
if(confirm('Are you sure to clean the canvas?')) {
var comps = editor.DomComponents.clear();
localStorage.clear();
editor.DomComponents.clear();
setTimeout(function() {
localStorage.clear();
}, 0);
}
},
attributes: { title: 'Empty canvas' }
@ -1312,6 +1311,7 @@
});
*/
// Store and load events
editor.on('storage:load', function(e) {
console.log('LOAD ', e);
@ -1326,7 +1326,7 @@
let computedValue = view.getComputedValue();
let defaultValue = view.getDefaultValue();
//console.log('Style of ', model.get('property'), 'Target: ', targetValue, 'Computed:', computedValue, 'Default:', defaultValue);
})
});
editor.render();
</script>

23
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.9.12",
"version": "0.10.6",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
@ -18,6 +18,7 @@
"font-awesome": "^4.7.0",
"jquery": "^3.1.1",
"keymaster": "^1.6.2",
"promise-polyfill": "^6.0.2",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.8.3"
},
@ -25,18 +26,19 @@
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-preset-es2015": "^6.24.1",
"chai": "^3.5.0",
"cross-env": "^5.0.4",
"documentation": "^4.0.0-beta2",
"babel-preset-stage-3": "^6.24.1",
"chai": "^4.1.2",
"cross-env": "^5.0.5",
"documentation": "^5.3.0",
"eslint": "^4.1.1",
"expect": "^1.20.2",
"istanbul": "^0.4.2",
"jsdom": "^11.1.0",
"jsdom": "^11.2.0",
"mocha": "^3.1.2",
"node-sass": "^3.4.2",
"sinon": "^1.17.6",
"webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5"
"node-sass": "^4.5.3",
"sinon": "^3.2.1",
"webpack": "^3.5.5",
"webpack-dev-server": "^2.7.1"
},
"keywords": [
"wte",
@ -54,7 +56,8 @@
],
"babel": {
"presets": [
"es2015"
"es2015",
"stage-3"
]
},
"scripts": {

51
src/asset_manager/config/config.js

@ -1,12 +1,46 @@
module.exports = {
// Default assets
// eg. [
// 'https://...image1.png',
// 'https://...image2.png',
// {type: 'image', src: 'https://...image3.png', someOtherCustomProp: 1},
// ..
// ]
assets: [],
// Content to add where there is no assets to show
// eg. 'No <b>assets</b> here, drag to upload'
noAssets: '',
// Style prefix
stylePrefix: 'am-',
// Url where uploads will be send, set false to disable upload
upload: 'http://localhost/assets/upload',
// Upload endpoint, set `false` to disable upload
// upload: 'https://endpoint/upload/assets',
// upload: false,
upload: 0,
// The name used in POST to pass uploaded files
uploadName: 'files',
// Custom headers to pass with the upload request
headers: {},
// Custom parameters to pass with the upload request, eg. csrf token
params: {},
// If true, tries to add automatically uploaded assets.
// To make it work the server should respond with a JSON containing assets
// in a data key, eg:
// {
// data: [
// 'https://.../image.png',
// ...
// {src: 'https://.../image2.png'},
// ...
// ]
// }
autoAdd: 1,
// Text on upload input
uploadText: 'Drop files here or click to upload',
@ -16,12 +50,20 @@ module.exports = {
// Custom uploadFile function
// @example
// uploadFile: function(e) {
// uploadFile: (e) => {
// var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
// // ...send somewhere
// }
uploadFile: '',
// Handle the image url submit from the built-in 'Add image' form
// @example
// handleAdd: (textFromInput) => {
// // some check...
// editor.AssetManager.add(textFromInput);
// }
handleAdd: '',
// Enable an upload dropzone on the entire editor (not document) when dragging
// files over it
dropzone: 1,
@ -31,4 +73,7 @@ module.exports = {
// Any dropzone content to append inside dropzone element
dropzoneContent: '',
// Default title for the asset manager modal
modalTitle: 'Select Image',
};

193
src/asset_manager/index.js

@ -2,11 +2,15 @@
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [getAllVisible](#getallvisible)
* * [remove](#remove)
* * [getContainer](#getcontainer)
* * [getAssetsEl](#getassetsel)
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
* * [store](#store)
* * [load](#load)
* * [onClick](#onClick)
* * [onDblClick](#onDblClick)
*
* Before using this methods you should get first the module from the editor instance, in this way:
*
@ -35,11 +39,12 @@
*/
module.exports = () => {
var c = {},
Assets = require('./model/Assets'),
AssetsView = require('./view/AssetsView'),
FileUpload = require('./view/FileUploader'),
assets, am, fu;
let c = {};
const defaults = require('./config/config');
const Assets = require('./model/Assets');
const AssetsView = require('./view/AssetsView');
const FileUpload = require('./view/FileUploader');
let assets, am, fu;
return {
@ -57,6 +62,10 @@ module.exports = () => {
*/
storageKey: 'assets',
getConfig() {
return c;
},
/**
* Initialize module
* @param {Object} config Configurations
@ -64,30 +73,49 @@ module.exports = () => {
*/
init(config) {
c = config || {};
var defaults = require('./config/config');
for (var name in defaults) {
for (let name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
const ppfx = c.pStylePrefix;
const em = c.em;
if (ppfx) {
c.stylePrefix = ppfx + c.stylePrefix;
}
assets = new Assets(c.assets);
var obj = {
collection: assets,
// Global assets collection
assets = new Assets([]);
const obj = {
// Collection visible in asset manager
collection: new Assets([]),
globalCollection: assets,
config: c,
};
am = new AssetsView(obj);
fu = new FileUpload(obj);
obj.fu = fu;
am = new AssetsView(obj);
// Setup the sync between the global and public collections
assets.listenTo(assets, 'add', (model) => {
this.getAllVisible().add(model);
em && em.trigger('asset:add', model);
});
assets.listenTo(assets, 'remove', (model) => {
this.getAllVisible().remove(model);
em && em.trigger('asset:remove', model);
});
return this;
},
/**
* Add new asset/s to the collection. URLs are supposed to be unique
* @param {string|Object|Array<string>|Array<Object>} asset URL strings or an objects representing the resource.
* @param {Object} [opts] Options
* @return {Model}
* @example
* // In case of strings, would be interpreted as images
@ -107,8 +135,14 @@ module.exports = () => {
* src: './path/to/img.png',
* }]);
*/
add(asset) {
return assets.add(asset);
add(asset, opts = {}) {
// Put the model at the beginning
if (typeof opts.at == 'undefined') {
opts.at = 0;
}
return assets.add(asset, opts);
},
/**
@ -123,13 +157,21 @@ module.exports = () => {
},
/**
* Return all assets
* Return the global collection, containing all the assets
* @return {Collection}
*/
getAll() {
return assets;
},
/**
* Return the visible collection, which containes assets actually rendered
* @return {Collection}
*/
getAllVisible() {
return am.collection;
},
/**
* Remove the asset by its URL
* @param {string} src URL of the asset
@ -160,30 +202,23 @@ module.exports = () => {
},
/**
* Load data from the passed object, if the object is empty will try to fetch them
* autonomously from the storage manager.
* The fetched data will be added to the collection
* Load data from the passed object.
* The fetched data will be added to the collection.
* @param {Object} data Object of data to load
* @return {Object} Loaded assets
* @example
* var assets = assetManager.load();
* // The format below will be used by the editor model
* // to load automatically all the stuff
* var assets = assetManager.load({
* assets: [...]
* });
* })
*
*/
load(data) {
var d = data || '';
var name = this.storageKey;
if(!d && c.stm)
d = c.stm.load(name);
var assets = d[name] || [];
load(data = {}) {
const name = this.storageKey;
let assets = data[name] || [];
if (typeof assets == 'string') {
try {
assets = JSON.parse(d[name]);
assets = JSON.parse(data[name]);
} catch(err) {}
}
@ -194,24 +229,100 @@ module.exports = () => {
return assets;
},
/**
* Return the Asset Manager Container
* @return {HTMLElement}
*/
getContainer() {
return am.el;
},
/**
* Get assets element container
* @return {HTMLElement}
*/
getAssetsEl() {
return am.el.querySelector('[data-el=assets]');
},
/**
* Render assets
* @param {Boolean} f Force to render, otherwise cached version will be returned
* @param {array} assets Assets to render, without the argument will render
* all global assets
* @return {HTMLElement}
* @private
* @example
* // Render all assets
* assetManager.render();
*
* // Render some of the assets
* const assets = assetManager.getAll();
* assetManager.render(assets.filter(
* asset => asset.get('category') == 'cats'
* ));
*/
render(f) {
if(!this.rendered || f)
this.rendered = am.render().$el.add(fu.render().$el);
return this.rendered;
render(assets) {
const toRender = assets || this.getAll().models;
if (!am.rendered) {
am.render();
}
am.collection.reset(toRender);
return this.getContainer();
},
postRender(editorView) {
fu.initDropzone(editorView);
/**
* Add new type
* @param {string} id Type ID
* @param {Object} definition Definition of the type. Each definition contains
* `model` (business logic), `view` (presentation logic)
* and `isType` function which recognize the type of the
* passed entity
* addType('my-type', {
* model: {},
* view: {},
* isType: (value) => {},
* })
*/
addType(id, definition) {
this.getAll().addType(id, definition);
},
/**
* Get type
* @param {string} id Type ID
* @return {Object} Type definition
*/
getType(id) {
return this.getAll().getType(id);
},
/**
* Get types
* @return {Array}
*/
getTypes() {
return this.getAll().getTypes();
},
//-------
AssetsView() {
return am;
},
FileUploader() {
return fu;
},
onLoad() {
this.getAll().reset(c.assets);
},
postRender(editorView) {
c.dropzone && fu.initDropzone(editorView);
},
/**
* Set new target
* @param {Object} m Model
@ -233,6 +344,7 @@ module.exports = () => {
/**
* Set callback to fire when the asset is clicked
* @param {function} func
* @private
*/
onClick(func) {
c.onClick = func;
@ -241,6 +353,7 @@ module.exports = () => {
/**
* Set callback to fire when the asset is double clicked
* @param {function} func
* @private
*/
onDblClick(func) {
c.onDblClick = func;

4
src/asset_manager/model/Asset.js

@ -1,6 +1,4 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
module.exports = require('backbone').Model.extend({
idAttribute: 'src',

13
src/asset_manager/model/AssetImage.js

@ -1,13 +1,12 @@
var Backbone = require('backbone');
var Asset = require('./Asset');
const Asset = require('./Asset');
module.exports = Asset.extend({
defaults: _.extend({}, Asset.prototype.defaults, {
type: 'image',
unitDim: 'px',
height: 0,
width: 0,
defaults: Object.assign({}, Asset.prototype.defaults, {
type: 'image',
unitDim: 'px',
height: 0,
width: 0,
}),
});

82
src/asset_manager/model/Assets.js

@ -1,68 +1,18 @@
var Backbone = require('backbone');
var Asset = require('./Asset');
var AssetImage = require('./AssetImage');
module.exports = Backbone.Collection.extend({
model: AssetImage,
initialize(models, opt) {
this.model = (attrs, options) => {
var model;
switch(attrs.type){
default:
model = new AssetImage(attrs, options);
import TypeableCollection from 'domain_abstract/model/TypeableCollection';
module.exports = require('backbone').Collection.extend(TypeableCollection).extend({
types: [{
id: 'image',
model: require('./AssetImage'),
view: require('./../view/AssetImageView'),
isType(value) {
if (typeof value == 'string') {
return {
type: 'image',
src: value,
}
}
return value;
}
return model;
};
},
/**
* Add new image asset to the collection
* @param {string} url URL of the image
* @param {Object} opts Options
* @return {this}
* @private
*/
addImg(url, opts) {
this.add({
type: 'image',
src: url,
}, opts);
return this;
},
/**
* Prevent inserting assets with the same 'src'
* Seems like idAttribute is not working with dynamic model assignament
* @private
*/
add(models, opt) {
var mods = [];
models = models instanceof Array ? models : [models];
for (var i = 0, len = models.length; i < len; i++) {
var model = models[i];
if(typeof model === 'string')
model = {src: model, type: 'image'};
if(!model || !model.src)
continue;
var found = this.where({src: model.src});
if(!found.length)
mods.push(model);
}
if(mods.length == 1)
mods = mods[0];
return Backbone.Collection.prototype.add.apply(this, [mods, opt]);
},
}]
});

109
src/asset_manager/view/AssetImageView.js

@ -1,108 +1,85 @@
var AssetView = require('./AssetView');
var assetTemplate = `
<div id="<%= pfx %>preview-cont">
<div id="<%= pfx %>preview" style="background-image: url(<%= src %>);"></div>
<div id="<%= pfx %>preview-bg" class="<%= ppfx %>checker-bg"></div>
</div>
<div id="<%= pfx %>meta">
<div id="<%= pfx %>name"><%= name %></div>
<div id="<%= pfx %>dimensions"><%= dim %></div>
</div>
<div id="<%= pfx %>close">&Cross;</div>
<div style="clear:both"></div>
`;
module.exports = require('./AssetView').extend({
module.exports = AssetView.extend({
events: {
click: 'onClick',
dblclick: 'onDblClick',
'click [data-toggle=asset-remove]': 'onRemove',
},
events:{
'click': 'handleClick',
'dblclick': 'handleDblClick',
getPreview() {
const pfx = this.pfx;
const src = this.model.get('src');
return `
<div class="${pfx}preview" style="background-image: url(${src});"></div>
<div class="${pfx}preview-bg ${this.ppfx}checker-bg"></div>
`;
},
template: _.template(assetTemplate),
getInfo() {
const pfx = this.pfx;
const model = this.model;
let name = model.get('name');
let width = model.get('width');
let height = model.get('height');
let unit = model.get('unitDim');
let dim = width && height ? `${width}x${height}${unit}` : '';
name = name || model.getFilename();
return `
<div class="${pfx}name">${name}</div>
<div class="${pfx}dimensions">${dim}</div>
`;
},
initialize(o) {
AssetView.prototype.initialize.apply(this, arguments);
this.className += ' ' + this.pfx + 'asset-image';
this.events['click #' + this.pfx + 'close'] = 'removeItem';
this.delegateEvents();
init(o) {
const pfx = this.pfx;
this.className += ` ${pfx}asset-image`;
},
/**
* Trigger when the asset is clicked
* Triggered when the asset is clicked
* @private
* */
handleClick() {
onClick() {
var onClick = this.config.onClick;
var model = this.model;
model.collection.trigger('deselectAll');
this.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'highlight');
if (typeof onClick === 'function') {
onClick(model);
} else {
this.updateTarget(model.get('src'));
this.updateTarget(this.collection.target);
}
},
/**
* Trigger when the asset is double clicked
* Triggered when the asset is double clicked
* @private
* */
handleDblClick() {
onDblClick() {
const em = this.em;
var onDblClick = this.config.onDblClick;
var model = this.model;
if (typeof onDblClick === 'function') {
onDblClick(model);
} else {
this.updateTarget(model.get('src'));
this.updateTarget(this.collection.target);
em && em.get('Modal').close();
}
var onSelect = model.collection.onSelect;
if(typeof onSelect == 'function'){
var onSelect = this.collection.onSelect;
if (typeof onSelect == 'function') {
onSelect(this.model);
}
},
/**
* Update target if exists
* @param {String} v Value
* @private
* */
updateTarget(v) {
var target = this.model.collection.target;
if(target && target.set) {
var attr = _.clone( target.get('attributes') );
target.set('attributes', attr );
target.set('src', v );
}
},
/**
* Remove asset from collection
* @private
* */
removeItem(e) {
onRemove(e) {
e.stopPropagation();
this.model.collection.remove(this.model);
},
render() {
var name = this.model.get('name'),
dim = this.model.get('width') && this.model.get('height') ?
this.model.get('width')+' x '+this.model.get('height') : '';
name = name ? name : this.model.get('src').split("/").pop();
name = name && name.length > 30 ? name.substring(0, 30)+'...' : name;
dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : '';
this.$el.html( this.template({
name,
src: this.model.get('src'),
dim,
pfx: this.pfx,
ppfx: this.ppfx
}));
this.$el.attr('class', this.className);
return this;
},
}
});

61
src/asset_manager/view/AssetView.js

@ -1,12 +1,59 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
initialize(o) {
initialize(o = {}) {
this.options = o;
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.collection = o.collection;
const config = o.config || {};
this.config = config;
this.pfx = config.stylePrefix || '';
this.ppfx = config.pStylePrefix || '';
this.em = config.em;
this.className = this.pfx + 'asset';
this.listenTo( this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'destroy remove', this.remove);
this.model.view = this;
const init = this.init && this.init.bind(this);
init && init(o);
},
template() {
const pfx = this.pfx;
return `
<div class="${pfx}preview-cont">
${this.getPreview()}
</div>
<div class="${pfx}meta">
${this.getInfo()}
</div>
<div class="${pfx}close" data-toggle="asset-remove">
&Cross;
</div>
`;
},
/**
* Update target if exists
* @param {Model} target
* @private
* */
updateTarget(target) {
if (target && target.set) {
target.set('attributes', _.clone(target.get('attributes')));
target.set('src', this.model.get('src'));
}
},
getPreview() {
return '';
},
getInfo() {
return '';
},
render() {
const el = this.el;
el.innerHTML = this.template(this, this.model);
el.className = this.className;
return this;
},
});

179
src/asset_manager/view/AssetsView.js

@ -1,45 +1,47 @@
var Backbone = require('backbone');
var AssetView = require('./AssetView');
var AssetImageView = require('./AssetImageView');
var FileUploader = require('./FileUploader');
var assetsTemplate = `
<div class="<%= pfx %>assets-cont">
<div class="<%= pfx %>assets-header">
<form class="<%= pfx %>add-asset">
<div class="<%= ppfx %>field <%= pfx %>add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="<%= ppfx %>btn-prim"><%= btnText %></button>
<div style="clear:both"></div>
</form>
<div class="<%= pfx %>dips" style="display:none">
<button class="fa fa-th <%= ppfx %>btnt"></button>
<button class="fa fa-th-list <%= ppfx %>btnt"></button>
</div>
</div>
<div class="<%= pfx %>assets"></div>
<div style="clear:both"></div>
</div>
`;
module.exports = Backbone.View.extend({
template: _.template(assetsTemplate),
events: {
submit: 'handleSubmit',
},
template(view) {
const pfx = view.pfx;
const ppfx = view.ppfx;
return `
<div class="${pfx}assets-cont">
<div class="${pfx}assets-header">
<form class="${pfx}add-asset">
<div class="${ppfx}field ${pfx}add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="${ppfx}btn-prim">${view.config.addBtnText}</button>
<div style="clear:both"></div>
</form>
<div class="${pfx}dips" style="display:none">
<button class="fa fa-th <%${ppfx}btnt"></button>
<button class="fa fa-th-list <%${ppfx}btnt"></button>
</div>
</div>
<div class="${pfx}assets" data-el="assets"></div>
<div style="clear:both"></div>
</div>
`;
},
initialize(o) {
this.options = o;
this.config = o.config;
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.listenTo(this.collection, 'add', this.addToAsset );
this.listenTo(this.collection, 'deselectAll', this.deselectAll);
this.listenTo(this.collection, 'reset', this.render);
this.className = this.pfx + 'assets';
this.events = {};
this.events.submit = 'addFromStr';
this.delegateEvents();
const coll = this.collection;
this.listenTo(coll, 'reset', this.renderAssets);
this.listenTo(coll, 'add', this.addToAsset);
this.listenTo(coll, 'remove', this.removedAsset);
this.listenTo(coll, 'deselectAll', this.deselectAll);
},
/**
@ -48,21 +50,24 @@ module.exports = Backbone.View.extend({
* @return {this}
* @private
*/
addFromStr(e) {
handleSubmit(e) {
e.preventDefault();
const input = this.getAddInput();
const url = input.value.trim();
const handleAdd = this.config.handleAdd;
var input = this.getInputUrl();
var url = input.value.trim();
if(!url)
if (!url) {
return;
}
this.collection.addImg(url, {at: 0});
this.getAssetsEl().scrollTop = 0;
input.value = '';
return this;
this.getAssetsEl().scrollTop = 0;
if (handleAdd) {
handleAdd(url);
} else {
this.options.globalCollection.add(url, {at: 0});
}
},
/**
@ -72,8 +77,7 @@ module.exports = Backbone.View.extend({
*/
getAssetsEl() {
//if(!this.assets) // Not able to cache as after the rerender it losses the ref
this.assets = this.el.querySelector('.' + this.pfx + 'assets');
return this.assets;
return this.el.querySelector(`.${this.pfx}assets`);
},
/**
@ -81,17 +85,31 @@ module.exports = Backbone.View.extend({
* @return {HTMLElement}
* @private
*/
getInputUrl() {
getAddInput() {
if(!this.inputUrl || !this.inputUrl.value)
this.inputUrl = this.el.querySelector('.'+this.pfx+'add-asset input');
this.inputUrl = this.el.querySelector(`.${this.pfx}add-asset input`);
return this.inputUrl;
},
/**
* Triggered when an asset is removed
* @param {Asset} model Removed asset
* @private
*/
removedAsset(model) {
if (!this.collection.length) {
this.toggleNoAssets();
}
},
/**
* Add asset to collection
* @private
* */
addToAsset(model) {
if (this.collection.length == 1) {
this.toggleNoAssets(1);
}
this.addAsset(model);
},
@ -102,53 +120,68 @@ module.exports = Backbone.View.extend({
* @return Object Object created
* @private
* */
addAsset(model, fragmentEl) {
var fragment = fragmentEl || null;
var viewObject = AssetView;
if(model.get('type').indexOf("image") > -1)
viewObject = AssetImageView;
var view = new viewObject({
addAsset(model, fragmentEl = null) {
const fragment = fragmentEl;
const collection = this.collection;
const config = this.config;
const rendered = new model.typeView({
model,
config : this.config,
});
var rendered = view.render().el;
collection,
config,
}).render().el;
if(fragment){
if (fragment) {
fragment.appendChild( rendered );
}else{
var assetsEl = this.getAssetsEl();
if(assetsEl)
} else {
const assetsEl = this.getAssetsEl();
if (assetsEl) {
assetsEl.insertBefore(rendered, assetsEl.firstChild);
}
}
return rendered;
},
/**
* Checks if to show noAssets
* @param {Boolean} hide
* @private
*/
toggleNoAssets(hide) {
const assetsEl = this.$el.find(`.${this.pfx}assets`);
if (hide) {
assetsEl.empty();
} else {
assetsEl.append(this.config.noAssets);
}
},
/**
* Deselect all assets
* @private
* */
deselectAll() {
this.$el.find('.' + this.pfx + 'highlight').removeClass(this.pfx + 'highlight');
const pfx = this.pfx;
this.$el.find(`.${pfx}highlight`).removeClass(`${pfx}highlight`);
},
renderAssets() {
const fragment = document.createDocumentFragment();
const assets = this.$el.find(`.${this.pfx}assets`);
assets.empty();
this.toggleNoAssets(this.collection.length);
this.collection.each((model) => this.addAsset(model, fragment));
assets.append(fragment);
},
render() {
var fragment = document.createDocumentFragment();
const fuRendered = this.options.fu.render().el;
this.$el.empty();
this.collection.each(function(model){
this.addAsset(model, fragment);
},this);
this.$el.html(this.template({
pfx: this.pfx,
ppfx: this.ppfx,
btnText: this.config.addBtnText,
}));
this.$el.find('.'+this.pfx + 'assets').append(fragment);
this.$el.append(fuRendered).append(this.template(this));
this.el.className = `${this.ppfx}asset-manager`;
this.renderAssets();
this.rendered = 1;
return this;
}
});

239
src/asset_manager/view/FileUploader.js

@ -1,15 +1,14 @@
var Backbone = require('backbone');
var fileUploaderTemplate = `
<form>
<div id="<%= pfx %>title"><%= title %></div>
<input type="file" id="<%= uploadId %>" name="file" accept="image/*" <%= disabled ? 'disabled' : '' %> multiple/>
<div style="clear:both;"></div>
</form>
`;
import fetch from 'utils/fetch';
module.exports = Backbone.View.extend({
template: _.template(fileUploaderTemplate),
template: _.template(`
<form>
<div id="<%= pfx %>title"><%= title %></div>
<input type="file" id="<%= uploadId %>" name="file" accept="image/*" <%= disabled ? 'disabled' : '' %> multiple/>
<div style="clear:both;"></div>
</form>
`),
events: {},
@ -19,53 +18,114 @@ module.exports = Backbone.View.extend({
this.config = c;
this.pfx = c.stylePrefix || '';
this.ppfx = c.pStylePrefix || '';
this.target = this.collection || {};
this.target = this.options.globalCollection || {};
this.uploadId = this.pfx + 'uploadFile';
this.disabled = !c.upload;
this.disabled = c.disableUpload !== undefined ? c.disableUpload : !c.upload && !c.embedAsBase64;
this.events['change #' + this.uploadId] = 'uploadFile';
let uploadFile = c.uploadFile;
if (uploadFile) {
this.uploadFile = uploadFile.bind(this);
} else if (c.embedAsBase64) {
this.uploadFile = this.constructor.embedAsBase64;
}
this.delegateEvents();
},
/**
* Triggered before the upload is started
* @private
*/
onUploadStart() {
const em = this.config.em;
em && em.trigger('asset:upload:start');
},
/**
* Triggered after the upload is ended
* @param {Object|string} res End result
* @private
*/
onUploadEnd(res) {
const em = this.config.em;
em && em.trigger('asset:upload:end', res);
},
/**
* Triggered on upload error
* @param {Object} err Error
* @private
*/
onUploadError(err) {
const em = this.config.em;
console.error(err);
this.onUploadEnd(err);
em && em.trigger('asset:upload:error', err);
},
/**
* Triggered on upload response
* @param {string} text Response text
* @private
*/
onUploadResponse(text) {
const em = this.config.em;
const config = this.config;
const target = this.target;
const json = typeof text === 'text' ? JSON.parse(text) : text;
em && em.trigger('asset:upload:response', json);
if (config.autoAdd && target) {
target.add(json.data, {at: 0});
}
this.onUploadEnd(text);
},
/**
* Upload files
* @param {Object} e Event
* @return {Promise}
* @private
* */
uploadFile(e) {
var files = e.dataTransfer ? e.dataTransfer.files : e.target.files,
formData = new FormData();
for (var i = 0; i < files.length; i++) {
formData.append('files[]', files[i]);
}
const files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
const body = new FormData();
const config = this.config;
const params = config.params;
for (let i = 0; i < files.length; i++) {
body.append(`${config.uploadName}[]`, files[i]);
}
for (let param in params) {
body.append(param, params[param]);
}
var target = this.target;
$.ajax({
url : this.config.upload,
type : 'POST',
data : formData,
beforeSend : this.config.beforeSend,
complete : this.config.onComplete,
xhrFields : {
onprogress(e) {
if (e.lengthComputable) {
/*var result = e.loaded / e.total * 100 + '%';*/
}
},
onload(e) {
//progress.value = 100;
}
},
cache: false, contentType: false, processData: false
}).done(data => {
target.add(data.data);
}).always(() => {
//turnOff loading
});
const url = config.upload;
const headers = config.headers;
const reqHead = 'X-Requested-With';
if (typeof headers[reqHead] == 'undefined') {
headers[reqHead] = 'XMLHttpRequest';
}
if (url) {
this.onUploadStart();
return fetch(url, {
method: 'post',
credentials: 'include',
headers,
body,
}).then(res => (res.status/200|0) == 1 ?
res.text() : res.text().then((text) =>
Promise.reject(text)
))
.then((text) => this.onUploadResponse(text))
.catch(err => this.onUploadError(err));
}
},
/**
@ -101,8 +161,9 @@ module.exports = Backbone.View.extend({
const c = this.config;
const em = ev.model;
const edEl = ev.el;
const editor = em && em.get('Editor');
const frameEl = ev.model.get('Canvas').getBody();
const editor = em.get('Editor');
const container = em.get('Config').el;
const frameEl = em.get('Canvas').getBody();
const ppfx = this.ppfx;
const updatedCls = `${ppfx}dropzone-active`;
const dropzoneCls = `${ppfx}dropzone`;
@ -124,11 +185,18 @@ module.exports = Backbone.View.extend({
const onDrop = (e) => {
cleanEditorElCls();
e.preventDefault();
e.stopPropagation();
this.uploadFile(e);
if (c.openAssetsOnDrop && editor) {
const target = editor.getSelected();
editor.runCommand('open-assets', {target});
editor.runCommand('open-assets', {
target,
onSelect() {
editor.Modal.close();
editor.AssetManager.setTarget(null);
}
});
}
return false;
@ -137,7 +205,7 @@ module.exports = Backbone.View.extend({
ev.$el.append(`<div class="${dropzoneCls}">${c.dropzoneContent}</div>`);
cleanEditorElCls();
if (c.dropzone && 'draggable' in edEl) {
if ('draggable' in edEl) {
[edEl, frameEl].forEach((item) => {
item.ondragover = onDragOver;
item.ondragleave = onDragLeave;
@ -158,4 +226,91 @@ module.exports = Backbone.View.extend({
return this;
},
}, {
embedAsBase64: function (e) {
// List files dropped
const files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
const response = { data: [] };
// Unlikely, widely supported now
if (!FileReader) {
this.onUploadError(new Error('Unsupported platform, FileReader is not defined'));
return;
}
const promises = [];
const mimeTypeMatcher = /^(.+)\/(.+)$/;
for (const file of files) {
// For each file a reader (to read the base64 URL)
// and a promise (to track and merge results and errors)
const promise = new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('load', (event) => {
let type;
const name = file.name;
// Try to find the MIME type of the file.
const match = mimeTypeMatcher.exec(file.type);
if (match) {
type = match[1]; // The first part in the MIME, "image" in image/png
} else {
type = file.type;
}
// If it's an image, try to find its size
if (type === 'image') {
const data = {
src: reader.result,
name,
type,
height: 0,
width: 0
};
const image = new Image();
image.addEventListener('error', (error) => {
reject(error);
});
image.addEventListener('load', () => {
data.height = image.height;
data.width = image.width;
resolve(data);
});
image.src = data.src;
} else if (type) {
// Not an image, but has a type
resolve({
src: reader.result,
name,
type
});
} else {
// No type found, resolve with the URL only
resolve(reader.result);
}
});
reader.addEventListener('error', (error) => {
reject(error);
});
reader.addEventListener('abort', (error) => {
reject('Aborted');
});
reader.readAsDataURL(file);
});
promises.push(promise);
}
Promise
.all(promises)
.then((data) => {
response.data = data;
this.onUploadResponse(response);
}, (error) => {
this.onUploadError(error);
});
}
});

21
src/block_manager/view/BlockView.js

@ -3,11 +3,11 @@ var Backbone = require('backbone');
module.exports = Backbone.View.extend({
events: {
mousedown: 'onDrag'
mousedown: 'startDrag'
},
initialize(o, config) {
_.bindAll(this, 'onDrop');
_.bindAll(this, 'endDrag');
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
this.listenTo(this.model, 'destroy remove', this.remove);
@ -18,7 +18,7 @@ module.exports = Backbone.View.extend({
* Start block dragging
* @private
*/
onDrag(e) {
startDrag(e) {
//Right or middel click
if (e.button !== 0) {
return;
@ -33,16 +33,23 @@ module.exports = Backbone.View.extend({
sorter.setDragHelper(this.el, e);
sorter.startSort(this.el);
sorter.setDropContent(this.model.get('content'));
this.doc.on('mouseup', this.onDrop);
this.doc.on('mouseup', this.endDrag);
},
/**
* Drop block
* @private
*/
onDrop() {
this.doc.off('mouseup', this.onDrop);
this.config.getSorter().endMove();
endDrag(e) {
this.doc.off('mouseup', this.endDrag);
const sorter = this.config.getSorter();
// After dropping the block in the canvas the mouseup event is not yet
// triggerd on 'this.doc' and so clicking outside, the sorter, tries to move
// things (throws false positives). As this method just need to drop away
// the block helper I use the trick of 'moved = 0' to void those errors.
sorter.moved = 0;
sorter.endMove();
},
render() {

7
src/block_manager/view/BlocksView.js

@ -73,7 +73,8 @@ module.exports = Backbone.View.extend({
* @private
*/
onDrop(model) {
this.em.runDefault();
const em = this.em;
em.runDefault();
if (model && model.get) {
if(model.get('activeOnRender')) {
@ -82,9 +83,9 @@ module.exports = Backbone.View.extend({
}
// Register all its components (eg. for the Undo Manager)
this.em.initChildrenComp(model);
em.initChildrenComp(model);
em.trigger('block:drag:stop', model);
}
this.em.trigger('block:drag:stop', model);
},
/**

9
src/canvas/index.js

@ -337,6 +337,15 @@ module.exports = () => {
};
},
/**
* Detects if some input is focused (input elements, text components, etc.)
* Used internally, for example, to avoid undo/redo in text editing mode
* @return {Boolean}
*/
isInputFocused() {
return this.getFrameEl().contentDocument.activeElement.tagName !== 'BODY';
},
/**
* Start autoscroll
*/

117
src/canvas/view/CanvasView.js

@ -64,7 +64,8 @@ module.exports = Backbone.View.extend({
var em = this.config.em;
if(wrap) {
var ppfx = this.ppfx;
var body = this.frame.$el.contents().find('body');
//var body = this.frame.$el.contents().find('body');
var body = $(this.frame.el.contentWindow.document.body);
var cssc = em.get('CssComposer');
var conf = em.get('Config');
var confCanvas = this.config;
@ -75,7 +76,6 @@ module.exports = Backbone.View.extend({
externalStyles += `<link rel="stylesheet" href="${style}"/>`;
});
// rgb(255, 202, 111)
const colorWarn = '#ffca6f';
let baseCss = `
@ -85,42 +85,86 @@ module.exports = Backbone.View.extend({
html, body, #wrapper {
min-height: 100%;
}
html {
height: 100%;
}
body {
margin: 0;
height: auto;
height: 100%;
background-color: #fff
}
#wrapper {
overflow: auto
}
`;
// Remove `html { height: 100%;}` from the baseCss as it gives jumpings
// effects (on ENTER) with RTE like CKEditor (maybe some bug there?!?)
// With `body {height: auto;}` jumps in CKEditor are removed but in
// Firefox is impossible to drag stuff in empty canvas, so bring back
// `body {height: 100%;}`.
// For the moment I give the priority to Firefox as it might be
// CKEditor's issue
// I need all this styles to make the editor work properly
var frameCss = `
${baseCss}
.${ppfx}dashed :not([contenteditable]) > *[data-highlightable] {
outline: 1px dashed rgba(170,170,170,0.7);
outline-offset: -2px
}
let layoutCss = `
.${ppfx}comp-selected{
.${ppfx}comp-selected {
outline: 3px solid #3b97e3 !important
}
.${ppfx}comp-selected-parent{
.${ppfx}comp-selected-parent {
outline: 2px solid ${colorWarn} !important
}
`;
// I need all this styles to make the editor work properly
var frameCss = baseCss +
'.' + ppfx + 'dashed :not([contenteditable]) > *[data-highlightable]{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' +
layoutCss +
'.' + ppfx + 'no-select{user-select: none; -webkit-user-select:none; -moz-user-select: none}'+
'.' + ppfx + 'freezed{opacity: 0.5; pointer-events: none}' +
'.' + ppfx + 'no-pointer{pointer-events: none}' +
'.' + ppfx + 'plh-image{background:#f5f5f5; border:none; height:50px; width:50px; display:block; outline:3px solid #ffca6f; cursor:pointer}' +
'.' + ppfx + 'grabbing{cursor: grabbing; cursor: -webkit-grabbing}' +
'* ::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1)}' +
'* ::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.2)}' +
'* ::-webkit-scrollbar {width: 10px}' +
(conf.canvasCss || '');
frameCss += protCss || '';
.${ppfx}no-select {
user-select: none;
-webkit-user-select:none;
-moz-user-select: none;
}
.${ppfx}freezed {
opacity: 0.5;
pointer-events: none;
}
.${ppfx}no-pointer {
pointer-events: none;
}
.${ppfx}plh-image {
background: #f5f5f5;
border: none;
height: 50px;
width: 50px;
display: block;
outline: 3px solid #ffca6f;
cursor: pointer;
outline-offset: -2px
}
.${ppfx}grabbing {
cursor: grabbing;
cursor: -webkit-grabbing;
}
* ::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1)
}
* ::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2)
}
* ::-webkit-scrollbar {
width: 10px
}
${conf.canvasCss || ''}
${protCss || ''}
`;
if (externalStyles) {
body.append(externalStyles);
@ -135,13 +179,30 @@ module.exports = Backbone.View.extend({
// When the iframe is focused the event dispatcher is not the same so
// I need to delegate all events to the parent document
var doc = document;
var fdoc = this.frame.el.contentDocument;
const doc = document;
const fdoc = this.frame.el.contentDocument;
// Unfortunately just creating `KeyboardEvent(e.type, e)` is not enough,
// the keyCode/which will be always `0`. Even if it's an old/deprecated
// property keymaster (and many others) still use it... using `defineProperty`
// hack seems the only way
const createCustomEvent = (e) => {
var oEvent = new KeyboardEvent(e.type, e);
oEvent.keyCodeVal = e.keyCode;
['keyCode', 'which'].forEach(prop => {
Object.defineProperty(oEvent, prop, {
get() {
return this.keyCodeVal;
}
});
});
return oEvent;
}
fdoc.addEventListener('keydown', e => {
doc.dispatchEvent(new KeyboardEvent(e.type, e));
doc.dispatchEvent(createCustomEvent(e));
});
fdoc.addEventListener('keyup', e => {
doc.dispatchEvent(new KeyboardEvent(e.type, e));
doc.dispatchEvent(createCustomEvent(e));
});
}
},

12
src/code_manager/index.js

@ -69,16 +69,10 @@ module.exports = () => {
defGenerators.css = new gCss();
defGenerators.json = new gJson();
defGenerators.js = new gJs();
defViewers.CodeMirror = new eCM();
return this;
},
/**
* Callback on load
*/
onLoad() {
this.loadDefaultGenerators().loadDefaultViewers();
return this;
},
/**
@ -187,7 +181,7 @@ module.exports = () => {
* @example
* var codeStr = codeManager.getCode(model, 'html');
* */
getCode(model, genId, opt) {
getCode(model, genId, opt = {}) {
var generator = this.getGenerator(genId);
return generator ? generator.build(model, opt) : '';
},

15
src/code_manager/model/CssGenerator.js

@ -11,10 +11,11 @@ module.exports = Backbone.Model.extend({
* @param {Model} model
* @return {String}
*/
buildFromModel(model) {
buildFromModel(model, opts = {}) {
var code = '';
var style = model.get('style');
var classes = model.get('classes');
const wrappesIsBody = opts.wrappesIsBody;
// Let's know what classes I've found
if(classes) {
@ -24,7 +25,10 @@ module.exports = Backbone.Model.extend({
}
if(style && Object.keys(style).length !== 0) {
code += '#' + model.getId() + '{';
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] + ';';
@ -58,13 +62,14 @@ module.exports = Backbone.Model.extend({
},
/** @inheritdoc */
build(model, cssc) {
build(model, opts = {}) {
const cssc = opts.cssc;
this.compCls = [];
var code = this.buildFromModel(model);
var code = this.buildFromModel(model, opts);
code += this.buildFromComp(model);
var compCls = this.compCls;
if(cssc){
if (cssc) {
var rules = cssc.getAll();
var mediaRules = {};
rules.each(function(rule) {

25
src/code_manager/model/HtmlGenerator.js

@ -2,18 +2,23 @@ var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
/** @inheritdoc */
build(model, cssc) {
var coll = model.get('components') || model,
code = '';
build(model, opts = {}) {
const models = model.get('components');
coll.each(m => {
code += m.toHTML({
cssc
});
}, this);
if (opts.exportWrapper) {
return opts.wrappesIsBody ?
`<body>${this.buildModels(models)}</body>` : model.toHTML();
}
return code;
return this.buildModels(models);
},
buildModels(models) {
let code = '';
models.each(model => {
code += model.toHTML();
});
return code;
}
});

6
src/commands/index.js

@ -114,10 +114,10 @@ module.exports = () => {
return;
}
sel.set('status', '');
ed.select(null);
sel.destroy();
ed.trigger('component:update', sel);
ed.editor.set('selectedComponent', null);
ed.trigger('change:canvasOffset');
//ed.refresh();//change:canvasOffset
},
};

28
src/commands/view/ExportTemplate.js

@ -25,18 +25,18 @@ module.exports = {
if(!this.codeMirror)
this.codeMirror = this.cm.getViewer('CodeMirror');
var $input = $('<textarea>'),
var $input = $('<textarea>'),
editor = this.codeMirror.clone().set({
editor = this.codeMirror.clone().set({
label,
codeName,
theme,
input : $input[0],
input: $input[0],
}),
$editor = new this.cm.EditorView({
model : editor,
config : this.cm.getConfig()
$editor = new this.cm.EditorView({
model: editor,
config: this.cm.getConfig()
}).render().$el;
editor.init( $input[0] );
@ -46,11 +46,11 @@ module.exports = {
enable() {
if(!this.$editors){
var oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML'),
oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS');
this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el;
this.$editors = $('<div>', {class: this.pfx + 'export-dl'});
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);
}
@ -59,10 +59,10 @@ module.exports = {
this.modal.setContent(this.$editors);
this.modal.open();
}
const em = this.em;
var addCss = this.protCss || '';
//this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc));
this.htmlEditor.setContent(this.em.getHtml());
this.cssEditor.setContent(addCss + this.cm.getCode(this.wrapper, 'css', this.cssc));
this.htmlEditor.setContent(em.getHtml());
this.cssEditor.setContent(em.getCss());
if(this.sender)
this.sender.set('active',false);

29
src/commands/view/OpenAssets.js

@ -1,20 +1,25 @@
module.exports = {
run(editor, sender, opts) {
var opt = opts || {};
var config = editor.getConfig();
var modal = editor.Modal;
var assetManager = editor.AssetManager;
run(editor, sender, opts = {}) {
const modal = editor.Modal;
const am = editor.AssetManager;
const config = am.getConfig();
const title = opts.modalTitle || config.modalTitle || '';
assetManager.onClick(opt.onClick);
assetManager.onDblClick(opt.onDblClick);
am.setTarget(opts.target);
am.onClick(opts.onClick);
am.onDblClick(opts.onDblClick);
am.onSelect(opts.onSelect);
// old API
assetManager.setTarget(opt.target);
assetManager.onSelect(opt.onSelect);
if (!this.rendered) {
am.render(am.getAll().filter(
asset => asset.get('type') == 'image'
));
this.rendered = 1;
}
modal.setTitle(opt.modalTitle || 'Select image');
modal.setContent(assetManager.render());
modal.setTitle(title);
modal.setContent(am.getContainer());
modal.open();
},

26
src/commands/view/OpenLayers.js

@ -3,18 +3,17 @@ var Layers = require('navigator');
module.exports = {
run(em, sender) {
if(!this.$layers) {
var collection = em.DomComponents.getComponent().get('components'),
config = em.getConfig(),
panels = em.Panels,
lyStylePfx = config.layers.stylePrefix || 'nv-';
if (!this.toAppend) {
var collection = em.DomComponents.getComponent().get('components');
var config = em.getConfig();
var pfx = config.stylePrefix;
var panels = em.Panels;
var lyStylePfx = config.layers.stylePrefix || 'nv-';
config.layers.stylePrefix = config.stylePrefix + lyStylePfx;
config.layers.pStylePrefix = config.stylePrefix;
config.layers.em = em.editor;
config.layers.opened = em.editor.get('opened');
var layers = new Layers(collection, config.layers);
this.$layers = layers.render();
// Check if panel exists otherwise crate it
if(!panels.getPanel('views-container'))
@ -22,13 +21,18 @@ module.exports = {
else
this.panel = panels.getPanel('views-container');
this.panel.set('appendContent', this.$layers).trigger('change:appendContent');
const toAppend = $(`<div class="${pfx}layers"></div>`);
this.panel.set('appendContent', toAppend).trigger('change:appendContent');
config.layers.sortContainer = toAppend.get(0);
const layers = new Layers().init(collection, config.layers);
this.$layers = layers.render();
toAppend.append(this.$layers);
this.toAppend = toAppend;
}
this.$layers.show();
this.toAppend.show();
},
stop() {
if(this.$layers)
this.$layers.hide();
this.toAppend && this.toAppend.hide();
}
};

10
src/commands/view/Resize.js

@ -7,14 +7,14 @@ module.exports = {
var canvasResizer = this.canvasResizer;
var options = opt.options || {};
var canvasView = canvas.getCanvasView();
options.ratioDefault = 1;
options.appendTo = canvas.getResizerEl();
options.prefix = editor.getConfig().stylePrefix;
options.posFetcher = canvasView.getElementPos.bind(canvasView);
options.mousePosFetcher = canvas.getMouseRelativePos;
// Create the resizer for the canvas if not yet created
if(!canvasResizer || opt.forceNew) {
options.ratioDefault = 1;
options.appendTo = canvas.getResizerEl();
options.prefix = editor.getConfig().stylePrefix;
options.posFetcher = canvasView.getElementPos.bind(canvasView);
options.mousePosFetcher = canvas.getMouseRelativePos;
this.canvasResizer = editor.Utils.Resizer.init(options);
canvasResizer = this.canvasResizer;
}

136
src/commands/view/SelectComponent.js

@ -1,6 +1,7 @@
var ToolbarView = require('dom_components/view/ToolbarView');
var Toolbar = require('dom_components/model/Toolbar');
var key = require('keymaster');
let showOffsets;
module.exports = {
@ -16,6 +17,7 @@ module.exports = {
this.startSelectComponent();
this.toggleClipboard(config.copyPaste);
var em = this.config.em;
showOffsets = 1;
em.on('component:update', this.updateAttached, this);
em.on('change:canvasOffset', this.updateAttached, this);
@ -174,9 +176,12 @@ module.exports = {
showElementOffset(el, pos) {
var $el = $(el);
var model = $el.data('model');
if(model && model.get('status') == 'selected'){
if ( (model && model.get('status') == 'selected') ||
!showOffsets){
return;
}
this.editor.runCommand('show-offset', {
el,
elPos: pos,
@ -299,33 +304,96 @@ module.exports = {
* */
onSelect(e, el) {
e.stopPropagation();
var md = this.editorModel.get('selectedComponent');
this.cleanPrevious(md);
var $el = $(el);
var nMd = $el.data('model');
if(nMd) {
var em = this.em;
var mirror = nMd.get('mirror');
nMd = mirror ? mirror : nMd;
// Close all opened components inside Navigator
var opened = em.get('opened');
for (var cid in opened){
var m = opened[cid];
m.set('open', 0);
}
var parent = nMd.collection ? nMd.collection.parent : null;
while(parent) {
parent.set('open', 1);
opened[parent.cid] = parent;
parent = parent.collection ? parent.collection.parent : null;
}
var model = $(el).data('model');
this.editorModel.set('selectedComponent', nMd);
nMd.set('status','selected');
if (model) {
this.editor.select(model);
this.showFixedElementOffset(el);
this.hideElementOffset();
this.hideHighlighter();
this.initResize(el);
}
},
/**
* Init resizer on the element if possible
* @param {HTMLElement} el
* @private
*/
initResize(el) {
var em = this.em;
var editor = em ? em.get('Editor') : '';
var config = em ? em.get('Config') : '';
var pfx = config.stylePrefix || '';
var attrName = 'data-' + pfx + 'handler';
var resizeClass = pfx + 'resizing';
var model = em.get('selectedComponent');
var resizable = model.get('resizable');
var options = {};
var modelToStyle;
var toggleBodyClass = (method, e, opts) => {
var handlerAttr = e.target.getAttribute(attrName);
var resizeHndClass = pfx + 'resizing-' + handlerAttr;
var classToAdd = resizeClass;// + ' ' +resizeHndClass;
if (opts.docs) {
opts.docs.find('body')[method](classToAdd);
}
};
if (editor && resizable) {
options = {
onStart(e, opts) {
toggleBodyClass('addClass', e, opts);
modelToStyle = em.get('StyleManager').getModelToStyle(model);
showOffsets = 0;
},
// Update all positioned elements (eg. component toolbar)
onMove() {
editor.trigger('change:canvasOffset');
},
onEnd(e, opts) {
toggleBodyClass('removeClass', e, opts);
editor.trigger('change:canvasOffset');
showOffsets = 1;
},
updateTarget(el, rect, options = {}) {
if (!modelToStyle) {
return;
}
const {store, selectedHandler} = options;
const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler) >= 0;
const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler) >= 0;
const unit = 'px';
const style = modelToStyle.getStyle();
if (!onlyHeight) {
style.width = rect.w + unit;
}
if (!onlyWidth) {
style.height = rect.h + unit;
}
modelToStyle.setStyle(style, {avoidStore: 1});
em.trigger('targetStyleUpdated');
if (store) {
modelToStyle.trigger('change:style', modelToStyle, style, {});
}
}
};
if (typeof resizable == 'object') {
options = Object.assign(options, resizable);
}
editor.runCommand('resize', {el, options});
// On undo/redo the resizer rect is not updating, need somehow to call
// this.updateRect on undo/redo action
}
},
@ -336,17 +404,24 @@ module.exports = {
updateToolbar(mod) {
var em = this.config.em;
var model = mod == em ? em.get('selectedComponent') : mod;
if(!model){
var toolbarEl = this.canvas.getToolbarEl();
var toolbarStyle = toolbarEl.style;
if (!model) {
// By putting `toolbarStyle.display = 'none'` will cause kind
// of freezed effect with component selection (probably by iframe
// switching)
toolbarStyle.opacity = 0;
return;
}
var toolbar = model.get('toolbar');
var ppfx = this.ppfx;
var showToolbar = em.get('Config').showToolbar;
var toolbarEl = this.canvas.getToolbarEl();
var toolbarStyle = toolbarEl.style;
if (showToolbar && toolbar && toolbar.length) {
toolbarStyle.display = 'flex';
toolbarStyle.opacity = '';
toolbarStyle.display = '';
if(!this.toolbar) {
toolbarEl.innerHTML = '';
this.toolbar = new Toolbar(toolbar);
@ -483,9 +558,8 @@ module.exports = {
return this.contWindow;
},
run(em) {
if(em && em.get)
this.editor = em.get('Editor');
run(editor) {
this.editor = editor && editor.get('Editor');
this.enable();
},

29
src/css_composer/index.js

@ -79,7 +79,6 @@ module.exports = () => {
c.sm = c.em; // TODO Refactor
rules = new CssRules([], c);
rules.add(c.rules);
rulesView = new CssRulesView({
collection: rules,
@ -93,8 +92,16 @@ module.exports = () => {
* @private
*/
onLoad() {
if(c.stm && c.stm.isAutosave())
c.em.listenRules(this.getAll());
rules.add(c.rules);
},
/**
* Do stuff after load
* @param {Editor} em
* @private
*/
postLoad(em) {
em.listenRules(this.getAll());
},
/**
@ -106,19 +113,25 @@ module.exports = () => {
*/
load(data) {
var d = data || '';
if(!d && c.stm)
if (!d && c.stm) {
d = c.em.getCacheLoad();
var obj = '';
}
var obj = d.styles || '';
if(d.styles) {
try{
try {
obj = JSON.parse(d.styles);
}catch(err){}
} catch (err) {}
} else if (d.css) {
obj = c.em.get('Parser').parseCss(d.css);
}
if(obj)
if (obj) {
rules.reset(obj);
}
return obj;
},

23
src/css_composer/model/CssRule.js

@ -26,18 +26,21 @@ module.exports = Backbone.Model.extend(Styleable).extend({
},
initialize(c, opt) {
this.config = c || {};
this.sm = opt ? opt.sm || {} : {};
this.slct = this.config.selectors || [];
if(this.sm.get){
var slct = [];
for(var i = 0; i < this.slct.length; i++)
slct.push(this.sm.get('SelectorManager').add(this.slct[i].name || this.slct[i]));
this.slct = slct;
this.config = c || {};
const em = opt && opt.sm;
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(this.slct));
this.set('selectors', new Selectors(selectors));
},
/**

25
src/dom_components/config/config.js

@ -3,28 +3,37 @@ module.exports = {
wrapperId: 'wrapper',
wrapperName: 'Body',
// Default wrapper configuration
wrapper: {
//classes: ['body'],
style: {margin: 0},
removable: false,
copyable: false,
stylable: ['background','background-color','background-image', 'background-repeat','background-attachment','background-position'],
draggable: false,
badgable: false,
components: [],
traits: [],
stylable: ['background','background-color','background-image',
'background-repeat','background-attachment','background-position',
'background-size'],
},
// Could be used for default components
components: [],
rte: {},
// Class for new image component
imageCompClass : 'fa fa-picture-o',
imageCompClass: 'fa fa-picture-o',
// Open assets manager on create of image component
oAssetsOnCreate : true,
oAssetsOnCreate: true,
// TODO to remove
// Editor should also store the wrapper informations, but as this change might
// break stuff I set ii as an opt-in option, for now.
storeWrapper: 0,
// List of void elements
voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'],
voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img',
'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source',
'track', 'wbr'],
};

148
src/dom_components/index.js

@ -34,13 +34,12 @@
*/
module.exports = () => {
var c = {},
componentTypes = {},
defaults = require('./config/config'),
Component = require('./model/Component'),
ComponentView = require('./view/ComponentView');
var component, componentView;
var defaultTypes = [
var componentTypes = [
{
id: 'cell',
model: require('./model/ComponentTableCell'),
@ -105,7 +104,7 @@ module.exports = () => {
return {
componentTypes: defaultTypes,
componentTypes,
/**
* Name of the module
@ -146,8 +145,11 @@ module.exports = () => {
*/
init(config) {
c = config || {};
if(c.em)
c.components = c.em.config.components || c.components;
const em = c.em;
if (em) {
c.components = em.config.components || c.components;
}
for (var name in defaults) {
if (!(name in c))
@ -159,29 +161,43 @@ module.exports = () => {
c.stylePrefix = ppfx + c.stylePrefix;
// Load dependencies
if(c.em){
c.rte = c.em.get('rte') || '';
c.modal = c.em.get('Modal') || '';
c.am = c.em.get('AssetManager') || '';
c.em.get('Parser').compTypes = defaultTypes;
if (em) {
c.rte = em.get('rte') || '';
c.modal = em.get('Modal') || '';
c.am = em.get('AssetManager') || '';
em.get('Parser').compTypes = componentTypes;
em.on('change:selectedComponent', this.componentChanged, this);
}
// Build wrapper
let components = c.components;
let wrapper = Object.assign({}, 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);
components = components.components || [];
wrapper.components = [];
// Have to put back the real object of components
if (em) {
em.config.components = components;
c.components = components;
}
}
component = new Component(c.wrapper, {
sm: c.em,
component = new Component(wrapper, {
sm: em,
config: c,
defaultTypes,
componentTypes,
});
component.set({ attributes: {id: 'wrapper'}});
if(c.em && !c.em.config.loadCompsOnRender) {
component.get('components').add(c.components);
}
componentView = new ComponentView({
model: component,
config: c,
defaultTypes,
componentTypes,
});
return this;
@ -192,10 +208,16 @@ module.exports = () => {
* @private
*/
onLoad() {
if(c.stm && c.stm.isAutosave()){
c.em.initUndoManager();
c.em.initChildrenComp(this.getWrapper());
}
this.getComponents().reset(c.components);
},
/**
* Do stuff after load
* @param {Editor} em
* @private
*/
postLoad(em) {
em.initChildrenComp(this.getWrapper());
},
/**
@ -205,25 +227,37 @@ module.exports = () => {
* @param {Object} data Object of data to load
* @return {Object} Loaded data
*/
load(data) {
var d = data || '';
if(!d && c.stm)
d = c.em.getCacheLoad();
var obj = '';
if(d.components){
try{
obj = JSON.parse(d.components);
}catch(err){}
}else if(d.html)
obj = d.html;
if (obj && obj.length) {
load(data = '') {
let result = '';
if (!data && c.stm) {
data = c.em.getCacheLoad();
}
if (data.components) {
try {
result = JSON.parse(data.components);
} catch (err) {}
} else if (data.html) {
result = data.html;
}
const isObj = result && result.constructor === Object;
if ((result && result.length) || isObj) {
this.clear();
this.getComponents().reset();
this.getComponents().add(obj);
// If the result is an object I consider it the wrapper
if (isObj) {
this.getWrapper().set(result)
.initComponents().initClasses().loadTraits();
} else {
this.getComponents().add(result);
}
}
return obj;
return result;
},
/**
@ -232,14 +266,22 @@ module.exports = () => {
* @return {Object} Data to store
*/
store(noStore) {
if(!c.stm)
if(!c.stm) {
return;
}
var obj = {};
var keys = this.storageKey();
if(keys.indexOf('html') >= 0)
if (keys.indexOf('html') >= 0) {
obj.html = c.em.getHtml();
if(keys.indexOf('components') >= 0)
obj.components = JSON.stringify(c.em.getComponents());
}
if (keys.indexOf('components') >= 0) {
const toStore = c.storeWrapper ?
this.getWrapper() : this.getComponents();
obj.components = JSON.stringify(toStore);
}
if (!noStore) {
c.stm.store(obj);
@ -379,7 +421,7 @@ module.exports = () => {
compType.view = methods.view;
} else {
methods.id = type;
defaultTypes.unshift(methods);
componentTypes.unshift(methods);
}
},
@ -389,7 +431,7 @@ module.exports = () => {
* @private
*/
getType(type) {
var df = defaultTypes;
var df = componentTypes;
for (var it = 0; it < df.length; it++) {
var dfId = df[it].id;
@ -400,5 +442,25 @@ module.exports = () => {
return;
},
/**
* Triggered when the selected component is changed
* @private
*/
componentChanged() {
const em = c.em;
const model = em.get('selectedComponent');
const previousModel = em.previous('selectedComponent');
// Deselect the previous component
if (previousModel) {
previousModel.set({
status: '',
state: '',
});
}
model && model.set('status','selected');
}
};
};

88
src/dom_components/model/Component.js

@ -43,13 +43,14 @@ module.exports = Backbone.Model.extend(Styleable).extend({
copyable: true,
// Indicates if it's possible to resize the component (at the moment implemented only on Image Components)
// It's also possible to pass an object as options for the Resizer
resizable: false,
// Allow to edit the content of the component (used on Text components)
editable: false,
// Hide the component inside Layers
hiddenLayer: false,
layerable: true,
// This property is used by the HTML exporter as void elements do not
// have closing tag, eg. <br/>, <hr/>, etc.
@ -94,32 +95,26 @@ module.exports = Backbone.Model.extend(Styleable).extend({
* }]
*/
toolbar: null,
// TODO
previousModel: '',
mirror: '',
},
initialize(o, opt) {
initialize(props = {}, opt = {}) {
const em = opt.sm || {};
// Check void elements
if(opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0)
this.set('void', true);
if(opt && opt.config &&
opt.config.voidElements.indexOf(this.get('tagName')) >= 0) {
this.set('void', true);
}
this.opt = opt;
this.sm = opt ? opt.sm || {} : {};
this.config = o || {};
this.defaultC = this.config.components || [];
this.defaultCl = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.components = new Components(this.defaultC, opt);
this.components.parent = this;
this.listenTo(this, 'change:script', this.scriptUpdated);
this.sm = em;
this.config = props;
this.set('attributes', this.get('attributes') || {});
this.set('components', this.components);
this.set('classes', new Selectors(this.defaultCl));
var traits = new Traits();
traits.setTarget(this);
traits.add(this.get('traits'));
this.set('traits', traits);
this.listenTo(this, 'change:script', this.scriptUpdated);
this.listenTo(this, 'change:traits', this.traitsUpdated);
this.loadTraits();
this.initClasses();
this.initComponents();
this.initToolbar();
// Normalize few properties from strings to arrays
@ -137,6 +132,19 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.init();
},
initClasses() {
const classes = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.set('classes', new Selectors(classes));
return this;
},
initComponents() {
let comps = new Components(this.get('components'), this.opt);
comps.parent = this;
this.set('components', comps);
return this;
},
/**
* Initialize callback
*/
@ -149,6 +157,32 @@ module.exports = Backbone.Model.extend(Styleable).extend({
this.set('scriptUpdated', 1);
},
/**
* Once traits are updated I have to populates model's attributes
*/
traitsUpdated() {
let found = 0;
const attrs = Object.assign({}, this.get('attributes'));
const traits = this.get('traits');
if (!(traits instanceof Traits)) {
this.loadTraits();
return;
}
traits.each((trait) => {
found = 1;
if (!trait.get('changeProp')) {
const value = trait.getInitValue();
if (value) {
attrs[trait.get('name')] = value;
}
}
});
found && this.set('attributes', attrs);
},
/**
* Init toolbar
*/
@ -183,11 +217,17 @@ module.exports = Backbone.Model.extend(Styleable).extend({
* @param {Array} traits
* @private
*/
loadTraits(traits) {
loadTraits(traits, opts = {}) {
var trt = new Traits();
trt.setTarget(this);
trt.add(traits);
this.set('traits', trt);
traits = traits || this.get('traits');
if (traits.length) {
trt.add(traits);
}
this.set('traits', trt, opts);
return this;
},
/**

5
src/dom_components/model/ComponentImage.js

@ -7,8 +7,9 @@ module.exports = Component.extend({
tagName: 'img',
src: '',
void: 1,
droppable: false,
resizable: true,
droppable: 0,
highlightable: 0,
resizable: 1,
traits: ['alt']
}),

2
src/dom_components/model/ComponentScript.js

@ -6,7 +6,7 @@ module.exports = Component.extend({
type: 'script',
droppable: false,
draggable: false,
hiddenLayer: true,
layerable: false,
}),
}, {

5
src/dom_components/model/Components.js

@ -21,13 +21,10 @@ module.exports = Backbone.Collection.extend({
if(opt && opt.config)
options.config = opt.config;
if(opt && opt.defaultTypes)
options.defaultTypes = opt.defaultTypes;
if(opt && opt.componentTypes)
options.componentTypes = opt.componentTypes;
var df = opt.defaultTypes;
var df = opt.componentTypes;
for (var it = 0; it < df.length; it++) {
var dfId = df[it].id;

8
src/dom_components/view/ComponentTextView.js

@ -11,7 +11,9 @@ module.exports = ComponentView.extend({
initialize(o) {
ComponentView.prototype.initialize.apply(this, arguments);
_.bindAll(this,'disableEditing');
this.listenTo(this.model, 'focus active', this.enableEditing);
const model = this.model;
this.listenTo(model, 'focus active', this.enableEditing);
this.listenTo(model, 'change:content', this.updateContent);
this.rte = this.config.rte || '';
this.activeRte = null;
this.em = this.config.em;
@ -29,7 +31,7 @@ module.exports = ComponentView.extend({
this.activeRte = this.rte.attach(this, this.activeRte);
this.rte.focus(this, this.activeRte);
} catch (err) {
console.error(err);
console.error(err);
}
}
this.toggleEvents(1);
@ -51,6 +53,8 @@ module.exports = ComponentView.extend({
console.error(err);
}
var el = this.getChildrenContainer();
// Avoid double content by removing its children components
model.get('components').reset();
model.set('content', el.innerHTML);
}

84
src/dom_components/view/ComponentView.js

@ -3,10 +3,6 @@ var ComponentsView = require('./ComponentsView');
module.exports = Backbone.View.extend({
events: {
'click': 'initResize',
},
className() {
return this.getClasses();
},
@ -156,8 +152,9 @@ module.exports = Backbone.View.extend({
var attributes = {},
attr = model.get("attributes");
for(var key in attr) {
if(attr.hasOwnProperty(key))
if (key && attr.hasOwnProperty(key)) {
attributes[key] = attr[key];
}
}
// Update src
@ -183,6 +180,14 @@ module.exports = Backbone.View.extend({
this.$el.attr('style', this.getStyleString());
},
/**
* Update component content
* @private
* */
updateContent() {
this.getChildrenContainer().innerHTML = this.model.get('content');
},
/**
* Return style string
* @return {string}
@ -229,71 +234,6 @@ module.exports = Backbone.View.extend({
event.viewResponse = this;
},
/**
* Init component for resizing
*/
initResize() {
var em = this.opts.config.em;
var editor = em ? em.get('Editor') : '';
var config = em ? em.get('Config') : '';
var pfx = config.stylePrefix || '';
var attrName = 'data-' + pfx + 'handler';
var resizeClass = pfx + 'resizing';
var model = this.model;
var modelToStyle;
var toggleBodyClass = (method, e, opts) => {
var handlerAttr = e.target.getAttribute(attrName);
var resizeHndClass = pfx + 'resizing-' + handlerAttr;
var classToAdd = resizeClass;// + ' ' +resizeHndClass;
if (opts.docs) {
opts.docs.find('body')[method](classToAdd);
}
};
if(editor && this.model.get('resizable')) {
editor.runCommand('resize', {
el: this.el,
options: {
onStart(e, opts) {
toggleBodyClass('addClass', e, opts);
modelToStyle = em.get('StyleManager').getModelToStyle(model);
},
// Update all positioned elements (eg. component toolbar)
onMove() {
editor.trigger('change:canvasOffset');
},
onEnd(e, opts) {
toggleBodyClass('removeClass', e, opts);
editor.trigger('change:canvasOffset');
},
updateTarget(el, rect, store) {
if (!modelToStyle) {
return;
}
var unit = 'px';
var style = _.clone(modelToStyle.get('style'));
var width = rect.w + (store ? 1 : 0);
style.width = width + unit;
style.height = rect.h + unit;
modelToStyle.set('style', style, {avoidStore: 1});
em.trigger('targetStyleUpdated');
// This trick will trigger the Undo Manager. To trigger "change:style"
// on the Model you need to provide a new object and after that
// Undo Manager will trigger only if values are different (this is why
// above I've added + 1 to width if store required)
if(store) {
var style3 = _.clone(style);
style3.width = (width - 1) + unit;
modelToStyle.set('style', style3);
}
}
}
});
}
},
/**
* Prevent default helper
* @param {Event} e
@ -363,7 +303,6 @@ module.exports = Backbone.View.extend({
var view = new ComponentsView({
collection: this.model.get('components'),
config: this.config,
defaultTypes: this.opts.defaultTypes,
componentTypes: this.opts.componentTypes,
});
@ -401,8 +340,7 @@ module.exports = Backbone.View.extend({
render() {
this.renderAttributes();
var model = this.model;
var container = this.getChildrenContainer();
container.innerHTML = model.get('content');
this.updateContent();
this.renderChildren();
this.updateScript();
return this;

6
src/dom_components/view/ComponentsView.js

@ -44,8 +44,7 @@ module.exports = Backbone.View.extend({
viewObject = this.compView;
//console.log('Add to collection', model, 'Index',i);
var dt = this.opts.defaultTypes;
var ct = this.opts.componentTypes;
var dt = this.opts.componentTypes;
var type = model.get('type');
@ -61,8 +60,7 @@ module.exports = Backbone.View.extend({
var view = new viewObject({
model,
config: this.config,
defaultTypes: dt,
componentTypes: ct,
componentTypes: dt,
});
var rendered = view.render().el;
if(view.model.get('type') == 'textnode')

4
src/domain_abstract/model/Styleable.js

@ -15,7 +15,7 @@ export default {
* @return {Object}
*/
getStyle() {
return this.get('style');
return Object.assign({}, this.get('style'));
},
/**
@ -53,7 +53,7 @@ export default {
* @param {string} prop
*/
removeStyle(prop) {
let style = Object.assign({}, this.getStyle());
let style = this.getStyle();
delete style[prop];
this.setStyle(style);
}

132
src/domain_abstract/model/TypeableCollection.js

@ -0,0 +1,132 @@
const Model = Backbone.Model;
const View = Backbone.View;
export default {
types: [],
initialize(models, opts) {
this.model = (attrs = {}, options = {}) => {
let Model, View, type;
if (attrs && attrs.type) {
const baseType = this.getBaseType();
type = this.getType(attrs.type);
Model = type ? type.model : baseType.model;
View = type ? type.view : baseType.view;
} else {
const typeFound = this.recognizeType(attrs);
type = typeFound.type;
Model = type.model;
View = type.view;
attrs = typeFound.attributes;
}
const model = new Model(attrs, options);
model.typeView = View;
return model;
};
const init = this.init && this.init.bind(this);
init && init();
},
/**
* Recognize type by any value
* @param {mixed} value
* @return {Object} Found type
*/
recognizeType(value) {
const types = this.getTypes();
for (let i = 0; i < types.length; i++) {
const type = types[i];
let typeFound = type.isType(value);
typeFound = typeof typeFound == 'boolean' && typeFound ?
{type: type.id} : typeFound;
if (typeFound) {
return {
type,
attributes: typeFound,
};
}
}
// If, for any reason, the type is not found it'll return the base one
return {
type: this.getBaseType(),
attributes: value,
}
},
/**
* Returns the base type (last object in the stack)
* @return {Object}
*/
getBaseType() {
const types = this.getTypes();
return types[types.length - 1];
},
/**
* Get types
* @return {Array}
*/
getTypes() {
return this.types;
},
/**
* Get type
* @param {string} id Type ID
* @return {Object} Type definition
*/
getType(id) {
const types = this.getTypes();
for (let i = 0; i < types.length; i++) {
const type = types[i];
if (type.id === id) {
return type;
}
}
},
/**
* Add new type
* @param {string} id Type ID
* @param {Object} definition Definition of the type. Each definition contains
* `model` (business logic), `view` (presentation logic)
* and `isType` function which recognize the type of the
* passed entity
* addType('my-type', {
* model: {},
* view: {},
* isType: (value) => {},
* })
*/
addType(id, definition) {
const type = this.getType(id);
const baseType = this.getBaseType();
const ModelInst = type ? type.model : baseType.model;
const ViewInst = type ? type.view : baseType.view;
let {model, view, isType} = definition;
model = model instanceof Model ? model : ModelInst.extend(model || {});
view = view instanceof View ? view : ViewInst.extend(view || {});
if (type) {
type.model = model;
type.view = view;
type.isType = isType || type.isType;
} else {
definition.id = id;
definition.model = model;
definition.view = view;
definition.isType = isType || function(value) {
if (value && value.type == id) {
return true;
}
};
this.getTypes().unshift(definition);
}
}
}

4
src/domain_abstract/ui/InputNumber.js

@ -19,7 +19,7 @@ module.exports = Backbone.View.extend({
var contClass = opt.contClass || (ppfx + 'field');
this.ppfx = ppfx;
this.docEl = $(document);
this.inputCls = ppfx + 'input-number';
this.inputCls = ppfx + 'field-number';
this.unitCls = ppfx + 'input-unit';
this.contClass = contClass;
this.events['click .' + ppfx + 'field-arrow-u'] = 'upArrowClick';
@ -139,6 +139,7 @@ module.exports = Backbone.View.extend({
value = isNaN(value) ? 1 : parseInt(value, 10) + 1;
var valid = this.validateInputValue(value);
this.model.set('value', valid.value);
this.elementUpdated();
},
/**
@ -149,6 +150,7 @@ module.exports = Backbone.View.extend({
value = isNaN(value) ? 0 : parseInt(value, 10) - 1;
var valid = this.validateInputValue(value);
this.model.set('value', valid.value);
this.elementUpdated();
},
/**

19
src/editor/config/config.js

@ -20,12 +20,6 @@ module.exports = {
// Show paddings and margins on selected component
showOffsetsSelected: false,
// Clear the canvas when editor.render() is called
clearOnRender: false,
// Return JS of components inside HTML from 'editor.getHtml()'
jsInHtml: true,
// On creation of a new Component (via object), if the 'style' attribute is not
// empty, all those roles will be moved in its new class
forceClass: true,
@ -37,7 +31,7 @@ module.exports = {
width: '100%',
// CSS that could only be seen (for instance, inside the code viewer)
protectedCss: '*{box-sizing: border-box;} body{margin: 0;}',
protectedCss: '*{box-sizing: border-box;}',
// CSS for the iframe which containing the canvas, useful if you need to custom something inside
// (eg. the style of the selected component)
@ -68,9 +62,14 @@ module.exports = {
// Ending tag for variable inside scripts in Components
tagVarEnd: ' ]}',
// This option makes available custom component types also for loaded
// elements inside canvas
loadCompsOnRender: 1,
// Return JS of components inside HTML from 'editor.getHtml()'
jsInHtml: true,
// Show the wrapper component in the final code, eg. in editor.getHtml()
exportWrapper: 0,
// The wrapper, if visible, will be shown as a `<body>`
wrappesIsBody: 1,
// Dom element
el: '',

73
src/editor/index.js

@ -31,20 +31,27 @@
* var editor = grapesjs.init({...});
* ```
*
* **Available events**
* 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
* 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
* 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
* canvasScroll - Triggered when the canvas is scrolled
* 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'))
* load - When the editor is loaded
* **Available Events**
* * `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
* * `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
* * `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
* * `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
* * `selector:add` - Triggers when a new selector/class is created
* * `canvasScroll` - Triggered when the canvas is scrolled
* * `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'))
* * `load` - When the editor is loaded
*
* @module Editor
* @param {Object} config Configurations
@ -103,16 +110,19 @@ module.exports = config => {
/**
* @property {DomComponents}
* @private
*/
DomComponents: em.get('DomComponents'),
/**
* @property {CssComposer}
* @private
*/
CssComposer: em.get('CssComposer'),
/**
* @property {StorageManager}
* @private
*/
StorageManager: em.get('StorageManager'),
@ -123,71 +133,85 @@ module.exports = config => {
/**
* @property {BlockManager}
* @private
*/
BlockManager: em.get('BlockManager'),
/**
* @property {TraitManager}
* @private
*/
TraitManager: em.get('TraitManager'),
/**
* @property {SelectorManager}
* @private
*/
SelectorManager: em.get('SelectorManager'),
/**
* @property {CodeManager}
* @private
*/
CodeManager: em.get('CodeManager'),
/**
* @property {Commands}
* @private
*/
Commands: em.get('Commands'),
/**
* @property {Modal}
* @private
*/
Modal: em.get('Modal'),
/**
* @property {Panels}
* @private
*/
Panels: em.get('Panels'),
/**
* @property {StyleManager}
* @private
*/
StyleManager: em.get('StyleManager'),
/**
* @property {Canvas}
* @private
*/
Canvas: em.get('Canvas'),
/**
* @property {UndoManager}
* @private
*/
UndoManager: em.get('UndoManager'),
/**
* @property {DeviceManager}
* @private
*/
DeviceManager: em.get('DeviceManager'),
/**
* @property {RichTextEditor}
* @private
*/
RichTextEditor: em.get('rte'),
/**
* @property {Utils}
* @private
*/
Utils: em.get('Utils'),
/**
* @property {Utils}
* @private
*/
Config: em.get('Config'),
@ -329,15 +353,32 @@ module.exports = config => {
}
},
/**
* Select a component
* @param {Component|HTMLElement} el Component to select
* @return {this}
* @example
* // Select dropped block
* editor.on('block:drag:stop', function(model) {
* editor.select(model);
* });
*/
select(el) {
em.setSelected(el);
return this;
},
/**
* Set device to the editor. If the device exists it will
* change the canvas to the proper width
* @param {string} name Name of the device
* @return {this}
* @example
* editor.setDevice('Tablet');
*/
setDevice(name) {
return em.set('device', name);
em.set('device', name);
return this;
},
/**
@ -519,7 +560,7 @@ module.exports = config => {
// Do post render stuff after the iframe is loaded otherwise it'll
// be empty during tests
em.on('loaded', () => {
em.get('modules').forEach((module) => {
em.get('modules').forEach(module => {
module.postRender && module.postRender(editorView);
});
});

163
src/editor/model/Editor.js

@ -23,6 +23,10 @@ var UndoManager = require('backbone-undo');
var key = require('keymaster');
var timedInterval;
if (!Backbone.$) {
Backbone.$ = $;
}
module.exports = Backbone.Model.extend({
defaults: {
@ -30,7 +34,7 @@ module.exports = Backbone.Model.extend({
designerMode: false,
selectedComponent: null,
previousModel: null,
changesCount: 0,
changesCount: 0,
storables: [],
modules: [],
toLoad: [],
@ -51,12 +55,6 @@ module.exports = Backbone.Model.extend({
this.loadModule(name);
}, this);
// Call modules with onLoad callback
this.get('toLoad').forEach(M => {
M.onLoad();
});
this.loadOnStart();
this.initUndoManager();
this.on('change:selectedComponent', this.componentSelected, this);
@ -64,20 +62,43 @@ module.exports = Backbone.Model.extend({
},
/**
* Load on start if it was requested
* Should be called after all modules and plugins are loaded
* @param {Function} clb
* @private
*/
loadOnStart() {
loadOnStart(clb = null) {
const sm = this.get('StorageManager');
// Generally, with `onLoad`, the module will try to load the data from
// its configurations
this.get('toLoad').forEach(module => {
module.onLoad();
});
// Stuff to do post load (eg. init undo manager for loaded components)
const postLoad = () => {
// I've initialized undo manager in initialize() because otherwise the
// editor will unable to fetch the instance via 'editor.UndoManager' but
// I need to cleare the stack now as it was dirtied by 'onLoad' method
this.um.clear();
this.initUndoManager();
this.get('modules').forEach(module =>
module.postLoad && module.postLoad(this)
);
clb && clb();
};
if (sm && sm.getConfig().autoload) {
this.load();
this.load(postLoad);
} else {
postLoad();
}
},
/**
* Set the alert before unload in case it's requested
* and there are unsaved changes
* @private
*/
updateBeforeUnload() {
var changes = this.get('changesCount');
@ -93,6 +114,7 @@ module.exports = Backbone.Model.extend({
* Load generic module
* @param {String} moduleName Module name
* @return {this}
* @private
*/
loadModule(moduleName) {
var c = this.config;
@ -152,28 +174,27 @@ module.exports = Backbone.Model.extend({
* @private
*/
listenRule(model) {
this.stopListening(model, 'change:style', this.componentsUpdated);
this.listenTo(model, 'change:style', this.componentsUpdated);
this.stopListening(model, 'change:style', this.handleUpdates);
this.listenTo(model, 'change:style', this.handleUpdates);
},
/**
* Triggered when something in components is changed
* @param {Object} model
* @param {Mixed} val Value
* @param {Object} opt Options
* This method handles updates on the editor and tries to store them
* if requested and if the changesCount is exceeded
* @param {Object} model
* @param {any} val Value
* @param {Object} opt Options
* @private
* */
componentsUpdated(model, val, opt) {
var temp = opt ? opt.temporary : 0;
if (temp) {
//component has been added temporarily - do not update storage or record changes
handleUpdates(model, val, opt = {}) {
// Component has been added temporarily - do not update storage or record changes
if (opt.temporary) {
return;
}
timedInterval && clearInterval(timedInterval);
timedInterval = setTimeout(() => {
var count = this.get('changesCount') + 1;
var avoidStore = opt ? opt.avoidStore : 0;
var stm = this.get('StorageManager');
this.set('changesCount', count);
@ -181,7 +202,7 @@ module.exports = Backbone.Model.extend({
return;
}
if (!avoidStore) {
if (!opt.avoidStore) {
this.store();
}
}, 0);
@ -192,10 +213,14 @@ module.exports = Backbone.Model.extend({
* @private
* */
initUndoManager() {
if(this.um)
const canvas = this.get('Canvas');
if (this.um) {
return;
}
var cmp = this.get('DomComponents');
if(cmp && this.config.undoManager){
if(cmp && this.config.undoManager) {
var that = this;
this.um = new UndoManager({
register: [cmp.getComponents(), this.get('CssComposer').getAll()],
@ -203,19 +228,27 @@ module.exports = Backbone.Model.extend({
});
this.UndoManager = this.um;
this.set('UndoManager', this.um);
key('⌘+z, ctrl+z', () => {
if (canvas.isInputFocused()) {
return;
}
that.um.undo(true);
that.trigger('component:update');
});
key('⌘+shift+z, ctrl+shift+z', () => {
if (canvas.isInputFocused()) {
return;
}
that.um.redo(true);
that.trigger('component:update');
});
UndoManager.removeUndoType("change");
var beforeCache;
UndoManager.addUndoType("change:style", {
"on": function (model, value, opts) {
const customUndoType = {
on: function (model, value, opts) {
var opt = opts || {};
if(!beforeCache){
beforeCache = model.previousAttributes();
@ -232,17 +265,21 @@ module.exports = Backbone.Model.extend({
return obj;
}
},
"undo": function (model, bf, af, opt) {
undo: function (model, bf, af, opt) {
model.set(bf);
// Update also inputs inside Style Manager
that.trigger('change:selectedComponent');
},
"redo": function (model, bf, af, opt) {
redo: function (model, bf, af, opt) {
model.set(af);
// Update also inputs inside Style Manager
that.trigger('change:selectedComponent');
}
});
};
UndoManager.removeUndoType("change");
UndoManager.addUndoType("change:style", customUndoType);
UndoManager.addUndoType("change:content", customUndoType);
}
},
@ -282,15 +319,15 @@ module.exports = Backbone.Model.extend({
this.listenTo(comps, 'add', this.updateComponents);
this.listenTo(comps, 'remove', this.rmComponents);
this.stopListening(classes, 'add remove', this.componentsUpdated);
this.listenTo(classes, 'add remove', this.componentsUpdated);
this.stopListening(classes, 'add remove', this.handleUpdates);
this.listenTo(classes, 'add remove', this.handleUpdates);
var evn = 'change:style change:content change:attributes';
this.stopListening(model, evn, this.componentsUpdated);
this.listenTo(model, evn, this.componentsUpdated);
this.stopListening(model, evn, this.handleUpdates);
this.listenTo(model, evn, this.handleUpdates);
if(!avSt)
this.componentsUpdated(model, val, opt);
this.handleUpdates(model, val, opt);
},
/**
@ -319,7 +356,7 @@ module.exports = Backbone.Model.extend({
var avSt = opt ? opt.avoidStore : 0;
if(!avSt)
this.componentsUpdated(model, val, opt);
this.handleUpdates(model, val, opt);
},
/**
@ -331,6 +368,22 @@ module.exports = Backbone.Model.extend({
return this.get('selectedComponent');
},
/**
* Select a component
* @param {Component|HTMLElement} el Component to select
* @param {Object} opts Options, optional
* @private
*/
setSelected(el, opts = {}) {
let model = el;
if (el instanceof HTMLElement) {
model = $(el).data('model');
}
this.set('selectedComponent', model, opts);
},
/**
* Set components inside editor's canvas. This method overrides actual components
* @param {Object|string} components HTML string or components model
@ -386,10 +439,15 @@ module.exports = Backbone.Model.extend({
* @private
*/
getHtml() {
var js = this.config.jsInHtml ? this.getJs() : '';
const config = this.config;
const exportWrapper = config.exportWrapper;
const wrappesIsBody = config.wrappesIsBody;
const js = config.jsInHtml ? this.getJs() : '';
var wrp = this.get('DomComponents').getComponent();
var html = this.get('CodeManager').getCode(wrp, 'html');
html += js ? '<script>'+ js +'</script>' : '';
var html = this.get('CodeManager').getCode(wrp, 'html', {
exportWrapper, wrappesIsBody
});
html += js ? `<script>${js}</script>` : '';
return html;
},
@ -399,11 +457,15 @@ module.exports = Backbone.Model.extend({
* @private
*/
getCss() {
const config = this.config;
const wrappesIsBody = config.wrappesIsBody;
var cssc = this.get('CssComposer');
var wrp = this.get('DomComponents').getComponent();
var protCss = this.config.protectedCss;
var protCss = config.protectedCss;
return protCss + this.get('CodeManager').getCode(wrp, 'css', cssc);
return protCss + this.get('CodeManager').getCode(wrp, 'css', {
cssc, wrappesIsBody
});
},
/**
@ -447,15 +509,13 @@ module.exports = Backbone.Model.extend({
/**
* Load data from the current storage
* @param {Function} clb Callback function
* @return {Object} Loaded data
* @private
*/
load(clb) {
var result = this.getCacheLoad(1, clb);
this.get('storables').forEach(m => {
m.load(result);
load(clb = null) {
this.getCacheLoad(1, (res) => {
this.get('storables').forEach(module => module.load(res));
clb && clb(res);
});
return result;
},
/**
@ -484,16 +544,17 @@ module.exports = Backbone.Model.extend({
});
});
this.cacheLoad = sm.load(load, (loaded) => {
clb && clb(loaded);
this.trigger('storage:load', loaded);
sm.load(load, res => {
this.cacheLoad = res;
clb && clb(res);
this.trigger('storage:load', res);
});
return this.cacheLoad;
},
/**
* Returns device model by name
* @return {Device|null}
* @private
*/
getDeviceModel() {
var name = this.get('device');

62
src/editor/view/EditorView.js

@ -1,55 +1,35 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
initialize() {
this.model.view = this;
this.pn = this.model.get('Panels');
this.conf = this.model.config;
this.className = this.conf.stylePrefix + 'editor';
this.model.on('loaded', function(){
const model = this.model;
model.view = this;
this.conf = model.config;
this.pn = model.get('Panels');
model.on('loaded', () => {
this.pn.active();
this.model.runDefault();
this.model.trigger('load');
}, this);
model.runDefault();
setTimeout(() => model.trigger('load'), 0);
});
},
render() {
var model = this.model;
var um = model.get('UndoManager');
var dComps = model.get('DomComponents');
var config = model.get('Config');
if(config.loadCompsOnRender) {
if (config.clearOnRender) {
dComps.clear();
}
dComps.getComponents().reset(config.components);
model.loadOnStart();
um.clear();
// This will init loaded components
dComps.onLoad();
}
var conf = this.conf;
var contEl = $(conf.el || ('body ' + conf.container));
this.$el.empty();
if(conf.width)
const model = this.model;
const el = this.$el;
const conf = this.conf;
const contEl = $(conf.el || `body ${conf.container}`);
const pfx = conf.stylePrefix;
el.empty();
if (conf.width)
contEl.css('width', conf.width);
if(conf.height)
if (conf.height)
contEl.css('height', conf.height);
// Canvas
this.$el.append(model.get('Canvas').render());
// Panels
this.$el.append(this.pn.render());
this.$el.attr('class', this.className);
contEl.addClass(conf.stylePrefix + 'editor-cont');
contEl.html(this.$el);
el.append(model.get('Canvas').render());
el.append(this.pn.render());
el.attr('class', `${pfx}editor`);
contEl.addClass(`${pfx}editor-cont`).empty().append(el);
return this;
}

55
src/grapesjs/index.js

@ -1,11 +1,11 @@
module.exports = (config => {
var c = config || {},
defaults = require('./config/config'),
Editor = require('editor'),
PluginManager = require('plugin_manager');
import { isUndefined, defaults } from 'underscore';
var plugins = new PluginManager();
var editors = [];
module.exports = (() => {
const defaultConfig = require('./config/config');
const Editor = require('editor');
const PluginManager = require('plugin_manager');
const plugins = new PluginManager();
const editors = [];
return {
@ -16,7 +16,7 @@
/**
* Initializes an editor based on passed options
* @param {Object} config Configuration object
* @param {string} config.container Selector which indicates where render the editor
* @param {string|HTMLElement} config.container Selector which indicates where render the editor
* @param {Object|string} config.components='' HTML string or Component model in JSON format
* @param {Object|string} config.style='' CSS string or CSS model in JSON format
* @param {Boolean} [config.fromElement=false] If true, will fetch HTML and CSS from selected container
@ -31,42 +31,39 @@
* style: '.hello{color: red}',
* })
*/
init(config) {
var c = config || {};
var els = c.container;
init(config = {}) {
const els = config.container;
// Make a missing $ more verbose
if (typeof $ == 'undefined') {
if (isUndefined($)) {
throw 'jQuery not found';
}
// Set default options
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
if(!els)
if (!els) {
throw new Error("'container' is required");
}
c.el = document.querySelector(els);
var editor = new Editor(c).init();
// Execute plugins
var plugs = plugins.getAll();
defaults(config, defaultConfig);
config.el = els instanceof window.HTMLElement ? els : document.querySelector(els);
const editor = new Editor(config).init();
c.plugins.forEach((pluginId) => {
let plugin = plugins.get(pluginId);
// Load plugins
config.plugins.forEach(pluginId => {
const plugin = plugins.get(pluginId);
if (plugin) {
plugin(editor, c.pluginsOpts[pluginId] || {});
plugin(editor, config.pluginsOpts[pluginId] || {});
} else {
console.warn(`Plugin ${pluginId} not found`);
}
});
if(c.autorender)
editor.render();
// Execute `onLoad` on modules once all plugins are initialized.
// A plugin might have extended/added some custom type so this
// is a good point to load stuff like components, css rules, etc.
editor.getModel().loadOnStart();
config.autorender && editor.render();
editors.push(editor);
return editor;

10
src/modal_dialog/index.js

@ -52,14 +52,14 @@ module.exports = () => {
config: c,
});
if(c.em)
c.em.on('loaded', function(){
this.render().appendTo(c.em.config.el || 'body');
}, this);
return this;
},
postRender(editorView) {
// c.em.config.el || 'body'
this.render().appendTo(editorView.el);
},
/**
* Open the modal window
* @return {this}

5
src/modal_dialog/view/ModalView.js

@ -89,10 +89,7 @@ module.exports = Backbone.View.extend({
* @private
* */
updateOpen() {
if(this.model.get('open'))
this.$el.show();
else
this.$el.hide();
this.el.style.display = this.model.get('open') ? '' : 'none';
},
/**

12
src/navigator/config/config.js

@ -1,9 +1,15 @@
module.exports = {
stylePrefix: 'nv-',
// Enable/Disable globally the possibility to sort layers
sortable: 1,
// Enable/Disable globally the possibility to hide layers
hidable: 1,
// Hide textnodes
hideTextnode: 1,
containerId: 'navigator',
itemClass: 'item',
itemsClass: 'items',
// Indicates if the wrapper is visible in layers
showWrapper: 1,
};

87
src/navigator/index.js

@ -1,27 +1,72 @@
function Navigator(collection, c) {
var config = c,
defaults = require('./config/config'),
ItemsView = require('./view/ItemsView');
// Set default options
for (var name in defaults) {
if (!(name in config))
config[name] = defaults[name];
}
module.exports = () => {
let itemsView;
let config = {};
const defaults = require('./config/config');
const ItemView = require('./view/ItemView');
const ItemsView = require('./view/ItemsView');
return {
init(collection, opts) {
config = opts || config;
const em = config.em;
// Set default options
for (var name in defaults) {
if (!(name in config))
config[name] = defaults[name];
}
let View = ItemsView;
const level = 0;
const opened = opts.opened || {};
const options = {
level,
config,
opened
}
var obj = {
collection,
config,
opened: c.opened || {}
};
// Show wrapper if requested
if (config.showWrapper && collection.parent) {
View = ItemView;
options.model = collection.parent;
} else {
options.collection = collection
}
itemsView = new View(options);
em && em.on('change:selectedComponent', this.componentChanged);
this.componentChanged();
return this;
},
this.ItemsView = new ItemsView(obj);
}
/**
* Triggered when the selected component is changed
* @private
*/
componentChanged(e, md, opts = {}) {
if (opts.fromLayers) {
return;
}
const em = config.em;
const opened = em.get('opened');
const model = em.get('selectedComponent');
let parent = model && model.collection ? model.collection.parent : null;
for (let cid in opened) {
opened[cid].set('open', 0);
}
while (parent) {
parent.set('open', 1);
opened[parent.cid] = parent;
parent = parent.collection ? parent.collection.parent : null;
}
},
Navigator.prototype = {
render() {
return this.ItemsView.render().$el;
return itemsView.render().$el;
},
}
};
module.exports = Navigator;

70
src/navigator/view/ItemView.js

@ -6,36 +6,39 @@ module.exports = Backbone.View.extend({
template: _.template(`
<% if (hidable) { %>
<i id="<%= prefix %>btn-eye" class="btn fa fa-eye <%= (visible ? '' : 'fa-eye-slash') %>"></i>
<i id="<%= prefix %>btn-eye" class="btn fa fa-eye <%= (visible ? '' : 'fa-eye-slash') %>"></i>
<% } %>
<div class="<%= prefix %>title-c">
<div class="<%= prefix %>title <%= addClass %>">
<i id="<%= prefix %>caret" class="fa fa-chevron-right <%= caretCls %>"></i>
<i class="fa fa-pencil <%= editBtnCls %>"></i>
<%= icon %>
<input class="<%= ppfx %>no-app <%= inputNameCls %>" value="<%= title %>" readonly>
<div class="<%= prefix %>title <%= addClass %>" style="padding-left: <%= 42 + level * 10 %>px">
<div class="<%= prefix %>title-inn">
<i class="fa fa-pencil <%= editBtnCls %>"></i>
<i id="<%= prefix %>caret" class="fa fa-chevron-right <%= caretCls %>"></i>
<%= icon %>
<input class="<%= ppfx %>no-app <%= inputNameCls %>" value="<%= title %>" readonly>
</div>
</div>
</div>
<div id="<%= prefix %>counter"><%= (count ? count : '') %></div>
<div id="<%= prefix %>move">
<i class="fa fa-arrows"></i>
<i class="fa fa-arrows"></i>
</div>
<div class="<%= prefix %>children"></div>`),
initialize(o) {
initialize(o = {}) {
this.opt = o;
this.level = o.level;
this.config = o.config;
this.em = o.config.em;
this.ppfx = this.em.get('Config').stylePrefix;
this.sorter = o.sorter || {};
this.sorter = o.sorter || '';
this.pfx = this.config.stylePrefix;
if(typeof this.model.get('open') == 'undefined')
this.model.set('open',false);
this.listenTo(this.model.components, 'remove add change reset', this.checkChildren);
this.listenTo(this.model.get('components'), 'remove add change reset', this.checkChildren);
this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model, 'change:open', this.updateOpening);
@ -101,11 +104,11 @@ module.exports = Backbone.View.extend({
var model = this.model;
if(model.get('open')){
this.$el.addClass("open");
this.$caret.addClass('fa-chevron-down');
this.getCaret().addClass('fa-chevron-down');
opened[model.cid] = model;
}else{
this.$el.removeClass("open");
this.$caret.removeClass('fa-chevron-down');
this.getCaret().removeClass('fa-chevron-down');
delete opened[model.cid];
}
},
@ -119,7 +122,7 @@ module.exports = Backbone.View.extend({
toggleOpening(e) {
e.stopPropagation();
if(!this.model.components.length)
if(!this.model.get('components').length)
return;
this.model.set('open', !this.model.get('open') );
@ -127,20 +130,10 @@ module.exports = Backbone.View.extend({
/**
* Handle component selection
* @return {[type]} [description]
*/
handleSelect(e) {
e.stopPropagation();
var em = this.em;
if(em){
var model = em.get('selectedComponent');
if(model){
model.set('status', '');
}
this.model.set('status', 'selected');
em.set('selectedComponent', this.model);
}
this.em && this.em.setSelected(this.model, {fromLayers: 1});
},
/**
@ -148,9 +141,14 @@ module.exports = Backbone.View.extend({
* @param Event
* */
startSort(e) {
if (this.sorter) {
this.sorter.startSort(e.target);
e.stopPropagation();
//Right or middel click
if (e.button !== 0) {
return;
}
this.sorter && this.sorter.startSort(e.target);
},
/**
@ -244,7 +242,7 @@ module.exports = Backbone.View.extend({
*/
countChildren(model) {
var count = 0;
model.components.each(function(m){
model.get('components').each(function(m){
var isCountable = this.opt.isCountable;
var hide = this.config.hideTextnode;
if(isCountable && !isCountable(m, hide))
@ -254,11 +252,20 @@ module.exports = Backbone.View.extend({
return count;
},
getCaret() {
if (!this.caret) {
const pfx = this.pfx;
this.caret = this.$el.find(`> .${pfx}title-c > .${pfx}title > .${pfx}title-inn > #${pfx}caret`);
}
return this.caret;
},
render() {
let model = this.model;
var pfx = this.pfx;
var vis = this.isVisible();
var count = this.countChildren(model);
const level = this.level + 1;
this.$el.html( this.template({
title: model.getName(),
@ -271,20 +278,21 @@ module.exports = Backbone.View.extend({
visible: vis,
hidable: this.config.hidable,
prefix: pfx,
ppfx: this.ppfx
ppfx: this.ppfx,
level
}));
if(typeof ItemsView == 'undefined')
ItemsView = require('./ItemsView');
this.$components = new ItemsView({
collection : model.components,
collection: model.get('components'),
config: this.config,
sorter: this.sorter,
opened: this.opt.opened,
parent: model
parent: model,
level
}).render().$el;
this.$el.find('.'+ pfx +'children').html(this.$components);
this.$caret = this.$el.find('> .' + pfx + 'title-c > .' + pfx + 'title > #' + pfx + 'caret');
if(!model.get('draggable') || !this.config.sortable){
this.$el.find('> #' + pfx + 'move').detach();
}

24
src/navigator/view/ItemsView.js

@ -3,26 +3,29 @@ var ItemView = require('./ItemView');
module.exports = Backbone.View.extend({
initialize(o) {
initialize(o = {}) {
this.opt = o;
this.config = o.config;
const config = o.config || {};
this.level = o.level;
this.config = config;
this.preview = o.preview;
this.ppfx = o.config.pStylePrefix || '';
this.pfx = o.config.stylePrefix || '';
this.ppfx = config.pStylePrefix || '';
this.pfx = config.stylePrefix || '';
this.parent = o.parent;
this.listenTo(this.collection, 'add', this.addTo);
this.listenTo(this.collection, 'reset resetNavigator', this.render);
this.className = this.pfx + 'items';
if(this.config.sortable && !this.opt.sorter){
if (config.sortable && !this.opt.sorter) {
var pfx = this.pfx;
var utils = this.config.em.get('Utils');
var utils = config.em.get('Utils');
this.opt.sorter = new utils.Sorter({
container: this.el,
container: config.sortContainer || this.el,
containerSel: '.' + pfx + 'items',
itemSel: '.' + pfx + 'item',
ppfx: this.ppfx,
ignoreViewChildren: 1,
avoidSelectOnEnd: 1,
pfx,
nested: 1
});
@ -30,9 +33,6 @@ module.exports = Backbone.View.extend({
this.sorter = this.opt.sorter || '';
if(!this.parent)
this.className += ' ' + this.pfx + this.config.containerId;
// For the sorter
this.$el.data('collection', this.collection);
@ -61,10 +61,12 @@ module.exports = Backbone.View.extend({
* @return Object Object created
* */
addToCollection(model, fragmentEl, index) {
const level = this.level;
var fragment = fragmentEl || null;
var viewObject = ItemView;
var view = new viewObject({
level,
model,
config: this.config,
sorter: this.sorter,
@ -106,7 +108,7 @@ module.exports = Backbone.View.extend({
var type = model.get('type');
var tag = model.get('tagName');
if( ((type == 'textnode' || tag == 'br') && hide) ||
model.get('hiddenLayer')) {
!model.get('layerable')) {
return false;
}
return true;

9
src/parser/model/ParserCss.js

@ -72,11 +72,12 @@ module.exports = config => ({
// Create style object from the big one
var stl = node.style;
var style = {};
for (var j = 0, len2 = stl.length; j < len2; j++) {
var propName = stl[j];
var important = stl.getPropertyPriority(propName);
style[propName] = stl[propName] +
(important ? ' !' + important : '');
const propName = stl[j];
const propValue = stl.getPropertyValue(propName);
const important = stl.getPropertyPriority(propName);
style[propName] = `${propValue}${important ? ` !${important}` : ''}`
}
var lastRule = '';

10
src/plugin_manager/index.js

@ -17,7 +17,7 @@ module.exports = config => {
* Add new plugin. Plugins could not be overwritten
* @param {string} id Plugin ID
* @param {Function} plugin Function which contains all plugin logic
* @return {this}
* @return {Function} The plugin function
* @example
* PluginManager.add('some-plugin', function(editor){
* editor.Commands.add('new-command', {
@ -28,10 +28,12 @@ module.exports = config => {
* });
*/
add(id, plugin) {
if(plugins[id])
return this;
if (plugins[id]) {
return plugins[id];
}
plugins[id] = plugin;
return this;
return plugin;
},
/**

7
src/selector_manager/config/config.js

@ -3,20 +3,21 @@ module.exports = {
// Style prefix
stylePrefix: 'clm-',
// Default classes
// Default selectors
selectors: [],
// Label for classes
// Label for selectors
label: 'Classes',
// Label for states
statesLabel: '- State -',
selectedLabel: 'Selected',
// States
states: [
{ name: 'hover', label: 'Hover' },
{ name: 'active', label: 'Click' },
{ name: 'nth-of-type(2n)', label: 'Even/Odd' }
],
};

45
src/selector_manager/index.js

@ -55,6 +55,7 @@
module.exports = config => {
var c = config || {},
defaults = require('./config/config'),
Selector = require('./model/Selector'),
Selectors = require('./model/Selectors'),
ClassTagsView = require('./view/ClassTagsView');
var selectors, selectorTags;
@ -82,23 +83,28 @@ module.exports = config => {
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
const em = c.em;
const ppfx = c.pStylePrefix;
if (ppfx) {
c.stylePrefix = ppfx + c.stylePrefix;
}
selectors = new Selectors(c.selectors, {
em: c.em,
config: c,
});
selectorTags = new ClassTagsView({
collection: selectors,
collection: new Selectors([], {em,config: c}),
config: c,
});
// Global selectors container
selectors = new Selectors(c.selectors);
selectors.on('add', (model) =>
em.trigger('selector:add', model));
return this;
},
/**
* Add the new selector to collection if it's not already exists. Class type is a default one
* Add a new selector to collection if it's not already exists. Class type is a default one
* @param {String} name Selector name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
@ -112,10 +118,25 @@ module.exports = config => {
* label: 'selectorName'
* });
* */
add(name, opts) {
var obj = opts || {};
obj.name = name.name || name;
return selectors.add(obj);
add(name, opts = {}) {
if (typeof name == 'object') {
opts = name;
} else {
opts.name = name;
}
if (opts.label && !opts.name) {
opts.name = Selector.escapeName(opts.label);
}
const cname = opts.name;
const selector = cname ? this.get(cname) : selectors.where(opts)[0];
if (!selector) {
return selectors.add(opts);
}
return selector;
},
/**

52
src/selector_manager/model/Selector.js

@ -1,23 +1,60 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
const Selector = Backbone.Model.extend({
idAttribute: 'name',
defaults: {
name: '',
label: '',
// Type of the selector
type: 'class',
// If not active it's not selectable by the style manager (uncheckboxed)
active: true,
// Can't be seen by the style manager, therefore even by the user
// Will be rendered only in export code
private: false,
// If true, can't be removed by the user, from the attacched element
protected: false,
},
initialize() {
this.set('name', this.escapeName(this.get('name')));
var label = this.get('label').trim();
if(!label)
this.set('label', this.get('name'));
const name = this.get('name');
const label = this.get('label');
if (!name) {
this.set('name', label);
} else if (!label) {
this.set('label', name);
}
this.set('name', Selector.escapeName(this.get('name')));
},
/**
* Get full selector name
* @return {string}
*/
getFullName() {
let init = '';
switch (this.get('type')) {
case 'class':
init = '.';
break;
case 'id':
init = '#';
break;
}
return init + this.get('name');
}
}, {
/**
* Escape string
* @param {string} name
@ -25,7 +62,8 @@ module.exports = Backbone.Model.extend({
* @private
*/
escapeName(name) {
return name.replace(/([^a-z0-9\w]+)/gi, '-');
return `${name}`.trim().replace(/([^a-z0-9\w]+)/gi, '-');
},
});
module.exports = Selector;

9
src/selector_manager/model/Selectors.js

@ -3,4 +3,13 @@ var Selector = require('./Selector');
module.exports = Backbone.Collection.extend({
model: Selector,
getStyleable() {
return _.filter(this.models, item =>
item.get('active') && !item.get('private'));
},
getValid() {
return _.filter(this.models, item => !item.get('private'));
}
});

5
src/selector_manager/view/ClassTagView.js

@ -1,4 +1,5 @@
var Backbone = require('backbone');
const Selector = require('./../model/Selector');
module.exports = Backbone.View.extend({
template: _.template(`
@ -46,7 +47,7 @@ module.exports = Backbone.View.extend({
*/
endEditTag() {
var value = this.$labelInput.val();
var next = this.model.escapeName(value);
var next = Selector.escapeName(value);
if(this.target){
var clsm = this.target.get('SelectorManager');
@ -129,8 +130,8 @@ module.exports = Backbone.View.extend({
ppfx: this.ppfx,
inputProp: this.inputProp,
}));
this.updateStatus();
this.$el.attr('class', this.className);
this.updateStatus();
this.updateInputLabel();
return this;
},

69
src/selector_manager/view/ClassTagsView.js

@ -26,7 +26,7 @@ module.exports = Backbone.View.extend({
<span id="<%= pfx %>add-tag" class="fa fa-plus"></span>
</div>
<div id="<%= pfx %>sel-help">
<div id="<%= pfx %>label">Selected</div>
<div id="<%= pfx %>label"><%= selectedLabel %></div>
<div id="<%= pfx %>sel"></div>
<div style="clear:both"></div>
</div>`),
@ -130,10 +130,15 @@ module.exports = Backbone.View.extend({
*/
componentChanged(e) {
this.compTarget = this.target.get('selectedComponent');
if(this.compTarget)
this.getStates().val(this.compTarget.get('state'));
var models = this.compTarget ? this.compTarget.get('classes').models : [];
this.collection.reset(models);
const target = this.compTarget;
let validSelectors = [];
if (target) {
this.getStates().val(target.get('state'));
validSelectors = target.get('classes').getValid();
}
this.collection.reset(validSelectors);
this.updateStateVis();
},
@ -156,20 +161,23 @@ module.exports = Backbone.View.extend({
* @private
*/
updateSelector() {
this.compTarget = this.target.get('selectedComponent');
if(!this.compTarget || !this.compTarget.get)
const selected = this.target.get('selectedComponent');
this.compTarget = selected;
if(!selected || !selected.get)
return;
var result = '';
var models = this.compTarget.get('classes');
models.each(model => {
this.collection.each(model => {
if(model.get('active'))
result += '.' + model.get('name');
});
var state = this.compTarget.get('state');
var state = selected.get('state');
result = state ? result + ':' + state : result;
result = result || selected.getName();
var el = this.el.querySelector('#' + this.pfx + 'sel');
if(el)
if (el) {
el.innerHTML = result;
}
},
/**
@ -191,23 +199,28 @@ module.exports = Backbone.View.extend({
* @param {Object} e
* @private
*/
addNewTag(name) {
if(!name)
addNewTag(label) {
const target = this.target;
const component = this.compTarget;
if (!label.trim()) {
return;
}
if(this.target){
var cm = this.target.get('SelectorManager');
var model = cm.add(name);
if (target) {
const sm = target.get('SelectorManager');
var model = sm.add({label});
if(this.compTarget){
var targetCls = this.compTarget.get('classes');
var lenB = targetCls.length;
targetCls.add(model);
var lenA = targetCls.length;
if (component) {
var compCls = component.get('classes');
var lenB = compCls.length;
compCls.add(model);
var lenA = compCls.length;
this.collection.add(model);
if(lenA > lenB)
this.target.trigger('targetClassAdded');
if (lenA > lenB) {
target.trigger('targetClassAdded');
}
this.updateStateVis();
}
@ -292,11 +305,13 @@ module.exports = Backbone.View.extend({
},
render() {
this.$el.html( this.template({
label: this.config.label,
statesLabel: this.config.statesLabel,
const config = this.config;
this.$el.html(this.template({
selectedLabel: config.selectedLabel,
statesLabel: config.statesLabel,
label: config.label,
pfx: this.pfx,
ppfx: this.ppfx
ppfx: this.ppfx,
}));
this.$input = this.$el.find('input#' + this.newInputId);
this.$addBtn = this.$el.find('#' + this.addBtnId);

10
src/storage_manager/config/config.js

@ -16,20 +16,20 @@ module.exports = {
stepsBeforeSave: 1,
//Enable/Disable components model (JSON format)
storeComponents: false,
storeComponents: 1,
//Enable/Disable styles model (JSON format)
storeStyles: false,
storeStyles: 1,
//Enable/Disable saving HTML template
storeHtml: true,
storeHtml: 1,
//Enable/Disable saving HTML template
storeCss: true,
storeCss: 1,
// ONLY FOR LOCAL STORAGE
// If enabled, checks if browser supports Local Storage
checkLocal: true,
checkLocal: 1,
// ONLY FOR REMOTE STORAGE
// Custom params that should be passed with each store/load request

38
src/storage_manager/index.js

@ -65,15 +65,8 @@ module.exports = () => {
defaultStorages.remote = new RemoteStorage(c);
defaultStorages.local = new LocalStorage(c);
c.currentStorage = c.type;
return this;
},
/**
* Callback executed after the module is loaded
* @private
*/
onLoad() {
this.loadDefaultProviders().setCurrent(c.type);
return this;
},
/**
@ -197,12 +190,13 @@ module.exports = () => {
* Load resource from the current storage by keys
* @param {string|Array<string>} keys Keys to load
* @param {Function} clb Callback function
* @return {Object|null} Loaded resources
* @example
* var data = storageManager.load(['item1', 'item2']);
* // data -> {item1: value1, item2: value2}
* var data2 = storageManager.load('item1');
* // data2 -> {item1: value1}
* storageManager.load(['item1', 'item2'], res => {
* // res -> {item1: value1, item2: value2}
* });
* storageManager.load('item1', res => {
* // res -> {item1: value1}
* });
* */
load(keys, clb) {
var st = this.get(this.getCurrent());
@ -215,16 +209,16 @@ module.exports = () => {
for (var i = 0, len = keys.length; i < len; i++)
keysF.push(c.id + keys[i]);
var loaded = st ? st.load(keysF, clb) : {};
// Restore keys name
for (var itemKey in loaded){
var reg = new RegExp('^' + c.id + '');
var itemKeyR = itemKey.replace(reg, '');
result[itemKeyR] = loaded[itemKey];
}
st && st.load(keysF, res => {
// Restore keys name
for (var itemKey in res) {
var reg = new RegExp('^' + c.id + '');
var itemKeyR = itemKey.replace(reg, '');
result[itemKeyR] = res[itemKey];
}
return result;
clb && clb(result);
});
},
/**

21
src/style_manager/index.js

@ -1,12 +1,13 @@
/**
*
* - [addSector](#addsector)
* - [getSector](#getsector)
* - [getSectors](#getsectors)
* - [addProperty](#addproperty)
* - [getProperty](#getproperty)
* - [getProperties](#getproperties)
* - [render](#render)
* * [addSector](#addsector)
* * [getSector](#getsector)
* * [getSectors](#getsectors)
* * [addProperty](#addproperty)
* * [getProperty](#getproperty)
* * [getProperties](#getproperties)
* * [getModelToStyle](#getmodeltostyle)
* * [render](#render)
*
* With Style Manager you basically build categories (called sectors) of CSS properties which could
* be used to custom components and classes.
@ -240,12 +241,10 @@ module.exports = () => {
var state = !previewMode ? model.get('state') : '';
var deviceW = device && !previewMode ? device.get('width') : '';
var cssC = c.em.get('CssComposer');
var valid = _.filter(classes.models, item => item.get('active'));
var valid = classes.getStyleable();
var CssRule = cssC.get(valid, state, deviceW);
if(CssRule) {
if(CssRule && valid.length) {
return CssRule;
}
}

81
src/style_manager/model/Properties.js

@ -1,6 +1,79 @@
var Backbone = require('backbone');
var Property = require('./Property');
import TypeableCollection from 'domain_abstract/model/TypeableCollection';
const Property = require('./Property');
module.exports = Backbone.Collection.extend({
model: Property,
module.exports = require('backbone').Collection.extend(TypeableCollection).extend({
types: [
{
id: 'stack',
model: require('./PropertyStack'),
view: require('./../view/PropertyStackView'),
isType(value) {
if (value && value.type == 'stack') {
return value;
}
}
},{
id: 'composite',
model: require('./PropertyComposite'),
view: require('./../view/PropertyCompositeView'),
isType(value) {
if (value && value.type == 'composite') {
return value;
}
}
},{
id: 'file',
model: Property,
view: require('./../view/PropertyFileView'),
isType(value) {
if (value && value.type == 'file') {
return value;
}
}
},{
id: 'color',
model: Property,
view: require('./../view/PropertyColorView'),
isType(value) {
if (value && value.type == 'color') {
return value;
}
}
},{
id: 'select',
model: require('./PropertyRadio'),
view: require('./../view/PropertySelectView'),
isType(value) {
if (value && value.type == 'select') {
return value;
}
}
},{
id: 'radio',
model: require('./PropertyRadio'),
view: require('./../view/PropertyRadioView'),
isType(value) {
if (value && value.type == 'radio') {
return value;
}
}
},{
id: 'integer',
model: require('./PropertyInteger'),
view: require('./../view/PropertyIntegerView'),
isType(value) {
if (value && value.type == 'integer') {
return value;
}
}
},{
id: 'base',
model: Property,
view: require('./../view/PropertyView'),
isType(value) {
value.type = 'base';
return value;
}
}
]
});

82
src/style_manager/model/Property.js

@ -1,70 +1,82 @@
var Backbone = require('backbone');
var Layers = require('./Layers');
module.exports = Backbone.Model.extend({
module.exports = require('backbone').Model.extend({
defaults: {
name: '',
property: '',
type: '',
units: [],
unit: '',
defaults: '',
info: '',
value: '',
icon: '',
preview: false,
detached: false,
visible: true,
functionName: '',
status: '',
properties: [],
layers: [],
list: [],
visible: true,
fixedValues: ['initial', 'inherit'],
},
initialize(opt) {
var o = opt || {};
var type = this.get('type');
var name = this.get('name');
var prop = this.get('property');
var props = this.get('properties');
if(!name)
if (!name) {
this.set('name', prop.charAt(0).toUpperCase() + prop.slice(1).replace(/-/g,' '));
}
if(props.length){
var Properties = require('./Properties');
this.set('properties', new Properties(props));
const init = this.init && this.init.bind(this);
init && init();
},
/**
* Parse a raw value, generally fetched from the target, for this property
* @param {string} value
* @return {string}
*/
parseValue(value) {
if (!this.get('functionName')) {
return value;
}
switch(type){
case 'stack':
this.set('layers', new Layers());
break;
const args = [];
let valueStr = value + '';
let start = valueStr.indexOf('(') + 1;
let end = valueStr.lastIndexOf(')');
args.push(start);
// Will try even if the last closing parentheses is not found
if (end >= 0) {
args.push(end);
}
return String.prototype.substring.apply(valueStr, args);
},
/**
* Get the default value
* @return {string}
* @private
*/
getDefaultValue() {
return this.get('defaults');
},
/**
* Return value
* @return {string} Value
* Get a complete value of the property.
* This probably will replace the getValue when all
* properties models will be splitted
* @param {string} val Custom value to replace the one on the model
* @return {string}
* @private
*/
getValue() {
var result = '';
var type = this.get('type');
getFullValue(val) {
const fn = this.get('functionName');
let value = val || this.get('value');
switch(type){
case 'integer':
result = this.get('value') + this.get('unit');
break;
default:
result = this.get('value');
break;
if (fn) {
value = `${fn}(${value})`;
}
return result;
return value;
},
});

55
src/style_manager/model/PropertyComposite.js

@ -0,0 +1,55 @@
const Property = require('./Property');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
// 'background' is a good example where to make a difference
// between detached and not
//
// - NOT detached (default)
// background: url(..) no-repeat center ...;
// - Detached
// background-image: url();
// background-repeat: repeat;
// ...
detached: 0,
// Array of sub properties
properties: [],
}),
init() {
const properties = this.get('properties') || [];
const Properties = require('./Properties');
this.set('properties', new Properties(properties));
},
/**
* Returns default value
* @param {Boolean} defaultProps Force to get defaults from properties
* @return {string}
*/
getDefaultValue(defaultProps) {
let value = this.get('defaults');
if (value && !defaultProps) {
return value;
}
value = '';
const properties = this.get('properties');
properties.each((prop, index) => value += `${prop.getDefaultValue()} `);
return value.trim();
},
getFullValue() {
if (this.get('detached')) {
return '';
}
let result = '';
this.get('properties').each(prop => result += `${prop.getFullValue()} `);
return result.trim();
},
});

18
src/style_manager/model/PropertyInteger.js

@ -0,0 +1,18 @@
const Property = require('./Property');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
// Array of units, eg. ['px', '%']
units: [],
// Selected unit, eg. 'px'
unit: '',
}),
getFullValue() {
let value = this.get('value') + this.get('unit');
return Property.prototype.getFullValue.apply(this, [value]);
},
});

10
src/style_manager/model/PropertyRadio.js

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

30
src/style_manager/model/PropertyStack.js

@ -0,0 +1,30 @@
const Property = require('./PropertyComposite');
const Layers = require('./Layers');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
// Array of layers (which contain properties)
layers: [],
// Layer preview
preview: 0,
}),
init() {
Property.prototype.init.apply(this, arguments);
const layers = this.get('layers');
this.set('layers', new Layers(layers));
},
getFullValue() {
if (this.get('detached')) {
return '';
}
const layers = this.get('layers');
let val = layers.length ? layers.pluck('value').join(', ') : '';
return val.trim();
},
});

37
src/style_manager/view/PropertiesView.js

@ -11,39 +11,20 @@ var PropertyStackView = require('./PropertyStackView');
module.exports = Backbone.View.extend({
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.target = o.target || {};
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.target = o.target || {};
this.propTarget = o.propTarget || {};
this.onChange = o.onChange || {};
this.onInputRender = o.onInputRender || {};
this.customValue = o.customValue || {};
this.onChange = o.onChange;
this.onInputRender = o.onInputRender || {};
this.customValue = o.customValue || {};
},
render() {
var fragment = document.createDocumentFragment();
this.collection.each(function(model){
var objView = PropertyView;
switch(model.get('type')){
case 'integer':
objView = PropertyIntegerView; break;
case 'radio':
objView = PropertyRadioView; break;
case 'select':
objView = PropertySelectView; break;
case 'color':
objView = PropertyColorView; break;
case 'file':
objView = PropertyFileView; break;
case 'composite':
objView = PropertyCompositeView;break;
case 'stack':
objView = PropertyStackView; break;
}
var view = new objView({
this.collection.each((model) => {
var view = new model.typeView({
model,
name: model.get('name'),
id: this.pfx + model.get('property'),
@ -59,7 +40,7 @@ module.exports = Backbone.View.extend({
}
fragment.appendChild(view.render().el);
},this);
});
this.$el.append(fragment);
this.$el.append($('<div>', {class: "clear"}));

5
src/style_manager/view/PropertyColorView.js

@ -4,7 +4,10 @@ var InputColor = require('domain_abstract/ui/InputColor');
module.exports = PropertyView.extend({
renderTemplate() {},
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
this.className += ` ${this.pfx}file`;
},
renderInput() {
if (!this.input) {

77
src/style_manager/view/PropertyCompositeView.js

@ -3,15 +3,19 @@ var PropertyView = require('./PropertyView');
module.exports = PropertyView.extend({
template: _.template(`
<div class="<%= pfx %>field <%= pfx %>composite">
<span id='<%= pfx %>input-holder'></span>
</div>
<div style="clear:both"></div>`),
templateField() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${pfx}field ${pfx}composite">
<span id="${pfx}input-holder"></span>
</div>
<div style="clear:both"></div>
`;
},
initialize(o) {
PropertyView.prototype.initialize.apply(this, arguments);
_.bindAll(this, 'build');
this.config = o.config || {};
this.className = this.className + ' '+ this.pfx +'composite';
},
@ -65,6 +69,7 @@ module.exports = PropertyView.extend({
*/
getPropsConfig(opts) {
var that = this;
const model = this.model;
var result = {
config: this.config,
@ -73,8 +78,7 @@ module.exports = PropertyView.extend({
propTarget: this.propTarget,
// On any change made to children I need to update composite value
onChange(el, view, opts) {
var result = that.build();
that.model.set('value', result, opts);
model.set('value', model.getFullValue(), opts);
},
// Each child property will receive a full composite string, eg. '0px 0px 10px 0px'
// I need to extract from that string the corresponding one to that property.
@ -84,24 +88,13 @@ module.exports = PropertyView.extend({
};
// If detached let follow its standard flow
if(this.model.get('detached'))
if (model.get('detached')) {
delete result.onChange;
}
return result;
},
/**
* Get default value of the property
* @return {string}
* */
getDefaultValue() {
var str = '';
this.props.each((prop, index) => {
str += prop.get('defaults') + prop.get('unit') + ' ';
});
return this.model.get('defaults') || str.replace(/ +$/,'');
},
/**
* Extract string from composite value
* @param {number} index Index
@ -109,38 +102,20 @@ module.exports = PropertyView.extend({
* @return {string}
* */
valueOnIndex(index, view) {
var result = null;
var a = this.getComponentValue().split(' ');
if(a.length && a[index]){
result = a[index];
if(view && view.model && view.model.get('functionName')){
var v = this.fetchFromFunction(result);
if(v)
result = v;
}
let value;
const targetValue = this.getTargetValue({ignoreDefault: 1});
// If the target value of the composite is not empty I'll fetch
// the corresponding value from the requested index, otherwise try
// to get the value of the sub-property
if (targetValue) {
const values = targetValue.split(' ');
value = view ? view.model.parseValue(values[index]) : values[index];
} else {
value = view.getTargetValue({ignoreCustomValue: 1});
}
return result;
},
/**
* Build composite value
* @param {Object} selectedEl Selected element
* @param {Object} propertyView Property view
* @param {Object} opts Options
* @return {string}
* */
build(selectedEl, propertyView, opts) {
var result = '';
this.model.get('properties').each(prop => {
var v = prop.getValue();
var func = prop.get('functionName');
if(func)
v = func + '(' + v + ')';
result += v + ' ';
});
return result.replace(/ +$/,'');
return value;
},
});

74
src/style_manager/view/PropertyFileView.js

@ -2,26 +2,36 @@ var Backbone = require('backbone');
var PropertyView = require('./PropertyView');
module.exports = PropertyView.extend({
template: _.template(`<div class="<%= pfx %>field <%= pfx %>file">
<div id='<%= pfx %>input-holder'>
<div class="<%= pfx %>btn-c">
<button class="<%= pfx %>btn" id="<%= pfx %>images" type="button"><%= assets %></button>
templateField() {
const pfx = this.pfx;
const ppfx = this.ppfx;
const assetsLabel = this.config.assetsLabel || 'Images';
return `
<div class="${pfx}field ${pfx}file">
<div id='${pfx}input-holder'>
<div class="${pfx}btn-c">
<button class="${pfx}btn" id="${pfx}images" type="button">
${assetsLabel}
</button>
</div>
<div style="clear:both;"></div>
</div>
<div id="${pfx}preview-box">
<div id="${pfx}preview-file"></div>
<div id="${pfx}close">&Cross;</div>
</div>
<div style="clear:both;"></div>
</div>
<div id="<%= pfx %>preview-box">
<div id="<%= pfx %>preview-file"></div>
<div id="<%= pfx %>close">&Cross;</div>
</div>
</div>
<div style="clear:both"></div>`),
<div style="clear:both"></div>
`;
},
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
this.assets = this.target.get('assets');
this.modal = this.target.get('Modal');
this.am = this.target.get('AssetManager');
this.className = this.className + ' '+ this.pfx +'file';
this.assets = this.target.get('assets');
this.modal = this.target.get('Modal');
this.am = this.target.get('AssetManager');
this.className = this.className + ' '+ this.pfx +'file';
this.events['click #'+this.pfx+'close'] = 'removeFile';
this.events['click #'+this.pfx+'images'] = 'openAssetManager';
this.delegateEvents();
@ -30,7 +40,7 @@ module.exports = PropertyView.extend({
/** @inheritdoc */
renderInput() {
if (!this.$input) {
this.$input = $('<input>', {placeholder: this.defaultValue, type: 'text' });
this.$input = $('<input>', {placeholder: this.model.getDefaultValue(), type: 'text' });
}
if (!this.$preview) {
@ -57,12 +67,8 @@ module.exports = PropertyView.extend({
* @return void
* */
setPreviewView(v) {
if(!this.$previewBox)
return;
if(v)
this.$previewBox.addClass(this.pfx + 'show');
else
this.$previewBox.removeClass(this.pfx + 'show');
const pv = this.$previewBox;
pv && pv[v ? 'addClass' : 'removeClass'](`${this.pfx}show`);
},
/**
@ -72,7 +78,7 @@ module.exports = PropertyView.extend({
* @return void
* */
spreadUrl(url) {
this.setValue(url);
this.model.set('value', url);
this.setPreviewView(1);
},
@ -80,19 +86,10 @@ module.exports = PropertyView.extend({
* Shows file preview
* @param string Value
* */
setPreview(url) {
if(this.$preview)
this.$preview.css('background-image', "url(" + url + ")");
},
/** @inheritdoc */
renderTemplate() {
this.$el.append( this.template({
upload : 'Upload',
assets : 'Images',
pfx : this.pfx
}));
setPreview(value) {
const preview = this.$preview;
value = value && value.indexOf('url(') < 0 ? `url(${value})` : value;
preview && preview.css('background-image', value);
},
/** @inheritdoc */
@ -107,7 +104,7 @@ module.exports = PropertyView.extend({
* @return void
* */
removeFile(...args) {
this.model.set('value',this.defaultValue);
this.model.set('value', this.model.getDefaultValue());
PropertyView.prototype.cleanValue.apply(this, args);
this.setPreviewView(0);
},
@ -125,14 +122,13 @@ module.exports = PropertyView.extend({
if(editor) {
this.modal.setTitle('Select image');
this.modal.setContent(this.am.render());
this.modal.setContent(this.am.getContainer());
this.am.setTarget(null);
editor.runCommand('open-assets', {
target: this.model,
onSelect(target) {
that.modal.close();
that.spreadUrl(target.get('src'));
that.valueChanged(e);
}
});
}

12
src/style_manager/view/PropertyIntegerView.js

@ -1,4 +1,3 @@
var Backbone = require('backbone');
var PropertyView = require('./PropertyView');
var InputNumber = require('domain_abstract/ui/InputNumber');
@ -10,15 +9,6 @@ module.exports = PropertyView.extend({
this.listenTo(this.model, 'el:change', this.elementUpdated);
},
/**
* Returns value from inputs
* @return {string}
*/
getValueForTarget() {
var model = this.model;
return model.get('value') + model.get('unit');
},
renderInput() {
if (!this.input) {
var inputNumber = new InputNumber({
@ -33,8 +23,6 @@ module.exports = PropertyView.extend({
this.setValue(this.componentValue);
},
renderTemplate() {},
setValue(value) {
this.input.setValue(value, {silent: 1});
},

20
src/style_manager/view/PropertyRadioView.js

@ -3,15 +3,21 @@ var PropertyView = require('./PropertyView');
module.exports = PropertyView.extend({
template: _.template(`
<div class="<%= ppfx %>field <%= ppfx %>field-radio">
<span id='<%= pfx %>input-holder'></span>
</div>
<div style="clear:both"></div>`),
templateField() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${ppfx}field ${ppfx}field-radio">
<span id="${pfx}input-holder"></span>
</div>
<div style="clear:both"></div>
`;
},
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
this.list = this.model.get('list') || [];
const model = this.model;
this.list = model.get('list') || model.get('options') || [];
this.className = this.className + ' '+ this.pfx +'list';
},
@ -54,7 +60,7 @@ module.exports = PropertyView.extend({
/** @inheritdoc */
setValue(value) {
var v = this.model.get('value') || this.defaultValue;
var v = this.model.get('value') || this.model.getDefaultValue();
if(value)
v = value;

24
src/style_manager/view/PropertySelectView.js

@ -3,18 +3,24 @@ var PropertyView = require('./PropertyView');
module.exports = PropertyView.extend({
template: _.template(`
<div class="<%= ppfx %>field <%= ppfx %>select">
<span id='<%= pfx %>input-holder'></span>
<div class="<%= ppfx %>sel-arrow">
<div class="<%= ppfx %>d-s-arrow"></div>
</div>
</div>
<div style="clear:both"></div>`),
templateField() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${ppfx}field ${ppfx}select">
<span id="${pfx}input-holder"></span>
<div class="${ppfx}sel-arrow">
<div class="${ppfx}d-s-arrow"></div>
</div>
</div>
<div style="clear:both"></div>
`;
},
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
this.list = this.model.get('list') || [];
const model = this.model;
this.list = model.get('list') || model.get('options') || [];
},
/** @inheritdoc */

122
src/style_manager/view/PropertyStackView.js

@ -5,20 +5,27 @@ var LayersView = require('./LayersView');
module.exports = PropertyCompositeView.extend({
template: _.template(`
<div class="<%= pfx %>field <%= pfx %>stack">
<button type="button" id='<%= pfx %>add'>+</button>
<span id='<%= pfx %>input-holder'></span>
</div>
<div style="clear:both"></div>`),
templateField() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${pfx}field ${pfx}stack">
<button type="button" id="${pfx}add">+</button>
<span id="${pfx}input-holder"></span>
</div>
<div style="clear:both"></div>
`;
},
initialize(o) {
PropertyCompositeView.prototype.initialize.apply(this, arguments);
this.model.set('stackIndex', null);
this.className = this.pfx + 'property '+ this.pfx +'stack';
this.events['click #'+this.pfx+'add'] = 'addLayer';
this.listenTo( this.model ,'change:stackIndex', this.indexChanged);
this.listenTo( this.model ,'updateValue', this.valueUpdated);
const model = this.model;
const pfx = this.pfx;
model.set('stackIndex', null);
this.className = `${pfx}property ${pfx}stack`;
this.events[`click #${pfx}add`] = 'addLayer';
this.listenTo(model, 'change:stackIndex', this.indexChanged);
this.listenTo(model, 'updateValue', this.valueUpdated);
this.delegateEvents();
},
@ -57,9 +64,7 @@ module.exports = PropertyCompositeView.extend({
var model = this.model;
var layer = this.getLayers().at(model.get('stackIndex'));
layer.set('props', this.$props);
model.get('properties').each(prop => {
prop.trigger('targetUpdated');
});
model.get('properties').each(prop => prop.trigger('targetUpdated'));
},
/**
@ -72,26 +77,30 @@ module.exports = PropertyCompositeView.extend({
/** @inheritDoc */
getPropsConfig(opts) {
var that = this;
const model = this.model;
const detached = model.get('detached');
var result = PropertyCompositeView.prototype.getPropsConfig.apply(this, arguments);
result.onChange = (el, view, opt) => {
var model = view.model;
var result = that.build();
const subModel = view.model;
const subProperty = subModel.get('property');
this.build();
if(that.model.get('detached')){
if (detached) {
var propVal = '';
var index = model.collection.indexOf(model);
var index = subModel.collection.indexOf(subModel);
that.getLayers().each(layer => {
var val = layer.get('values')[model.get('property')];
if(val)
this.getLayers().each(layer => {
var val = layer.get('values')[subProperty];
if (val) {
propVal += (propVal ? ',' : '') + val;
}
});
view.updateTargetStyle(propVal, null, opt);
}else
that.model.set('value', result, opt);
} else {
model.set('value', model.getFullValue(), opt);
}
};
return result;
@ -105,16 +114,17 @@ module.exports = PropertyCompositeView.extend({
* @private
* */
valueOnIndex(index, propView) {
var result = null;
var layerIndex = this.model.get('stackIndex');
let result;
const model = this.model;
const layerIndex = model.get('stackIndex');
// If detached the value in this case is stacked, eg. substack-prop: 1px, 2px, 3px...
if (this.model.get('detached')) {
if (model.get('detached')) {
var targetValue = propView.getTargetValue({ignoreCustomValue: 1});
var valist = (targetValue + '').split(',');
result = valist[layerIndex];
result = result ? result.trim() : propView.getDefaultValue();
result = propView.tryFetchFromFunction(result);
result = propView.model.parseValue(result);
} else {
var aStack = this.getStackValues();
var strVar = aStack[layerIndex];
@ -134,42 +144,37 @@ module.exports = PropertyCompositeView.extend({
* @private
* */
build(...args) {
var stackIndex = this.model.get('stackIndex');
if(stackIndex === null)
return;
var result = PropertyCompositeView.prototype.build.apply(this, args);
var model = this.getLayers().at(stackIndex);
if(!model)
let value = '';
let values = {};
const model = this.model;
const stackIndex = model.get('stackIndex');
const properties = model.get('properties');
if (stackIndex === null) {
return;
}
// Store properties values inside layer, in this way it's more reliable
// to fetch them later
var valObj = {};
this.model.get('properties').each(prop => {
var v = prop.getValue(),
func = prop.get('functionName');
if(func)
v = func + '(' + v + ')';
valObj[prop.get('property')] = v;
// to fetch them later
properties.each(prop => {
const propValue = prop.getFullValue();
values[prop.get('property')] = propValue;
value += `${propValue} `;
});
model.set('values', valObj);
model.set('value', result);
return this.createValue();
const layerModel = this.getLayers().at(stackIndex);
layerModel && layerModel.set({values, value});
},
/**
* Add layer
* @param Event
*
* @return Object
* Add new layer
* */
addLayer(e) {
if(this.getTarget()){
var layers = this.getLayers();
var layer = layers.add({ name : 'test' });
var index = layers.indexOf(layer);
layer.set('value', this.getDefaultValue());
addLayer() {
if (this.getTarget()) {
const layers = this.getLayers();
const layer = layers.add({name: 'New'});
const index = layers.indexOf(layer);
layer.set('value', this.model.getDefaultValue(1));
// In detached mode valueUpdated will add new 'layer value'
// to all subprops
@ -177,7 +182,6 @@ module.exports = PropertyCompositeView.extend({
// This will set subprops with a new default values
this.model.set('stackIndex', index);
return layer;
}
},
@ -278,7 +282,6 @@ module.exports = PropertyCompositeView.extend({
fieldName = 'values';
a = this.getLayersFromTarget();
} else {
//var v = this.getComponentValue();
var v = this.getTargetValue();
var vDef = this.getDefaultValue();
v = v == vDef ? '' : v;
@ -315,8 +318,9 @@ module.exports = PropertyCompositeView.extend({
},
render() {
this.renderLabel();
this.renderField();
const el = this.el;
el.innerHTML = this.template(this.model);
this.renderInput();
this.renderLayers();
this.$el.attr('class', this.className);
this.updateStatus();

305
src/style_manager/view/PropertyView.js

@ -1,19 +1,33 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
template: _.template(`
<div class="<%= ppfx %>field">
<span id='<%= pfx %>input-holder'></span>
</div>
<div style="clear:both"></div>`),
templateLabel: _.template(`
<div class="<%= pfx %>label">
<span class="<%= pfx %>icon <%= icon %>" title="<%= info %>">
<%= label %>
</span>
<b class="<%= pfx %>clear">&Cross;</b>
</div>`),
template(model) {
const pfx = this.pfx;
const name = model.get('name');
const icon = model.get('icon');
const info = model.get('info');
return `
<div class="${pfx}label">
<span class="${pfx}icon ${icon}" title="${info}">
${name}
</span>
<b class="${pfx}clear">&Cross;</b>
</div>
${this.templateField()}
`;
},
templateField() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${ppfx}field">
<span id="${pfx}input-holder"></span>
</div>
<div style="clear:both"></div>
`;
},
events: {
'change': 'valueUpdated'
@ -26,26 +40,27 @@ module.exports = Backbone.View.extend({
this.ppfx = this.config.pStylePrefix || '';
this.target = o.target || {};
this.propTarget = o.propTarget || {};
this.onChange = o.onChange || {};
this.onChange = o.onChange;
this.onInputRender = o.onInputRender || {};
this.customValue = o.customValue || {};
this.defaultValue = this.model.get('defaults');
this.property = this.model.get('property');
const model = this.model;
this.property = model.get('property');
this.input = this.$input = null;
const pfx = this.pfx;
this.className = pfx + 'property';
this.inputHolderId = '#' + pfx + 'input-holder';
this.sector = this.model.collection && this.model.collection.sector;
this.sector = model.collection && model.collection.sector;
if(!this.model.get('value'))
this.model.set('value', this.model.get('defaults'));
if (!model.get('value')) {
model.set('value', model.getDefaultValue());
}
this.listenTo(this.propTarget, 'update', this.targetUpdated);
this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:value', this.valueChanged);
this.listenTo(this.model, 'targetUpdated', this.targetUpdated);
this.listenTo(this.model, 'change:visible', this.updateVisibility);
this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:value', this.valueChanged);
this.listenTo(model, 'targetUpdated', this.targetUpdated);
this.listenTo(model, 'change:visible', this.updateVisibility);
this.listenTo(model, 'change:status', this.updateStatus);
this.events[`click .${pfx}clear`] = 'clear';
this.delegateEvents();
},
@ -168,22 +183,14 @@ module.exports = Backbone.View.extend({
status = '';
}
//value = this.tryFetchFromFunction(value);
model.set('value', value, {silent: 1});
this.setValue(value, 1);
this.model.set('status', status);
model.set('status', status);
if (em) {
em.trigger('styleManager:change', this);
em.trigger(`styleManager:change:${model.get('property')}`, this);
}
/*
if(this.getTarget()) {
if(!this.sameValue()){
this.renderInputRequest();
}
}*/
},
checkVisibility() {
@ -207,54 +214,7 @@ module.exports = Backbone.View.extend({
},
/**
* Checks if the value from selected component is the
* same of the value of the model
*
* @return {Boolean}
* */
sameValue() {
return this.getComponentValue() == this.getValueForTarget();
},
/**
* Get the value from the selected component of this property
* @return {String}
* */
getComponentValue() {
var propModel = this.model;
var target = this.getTargetModel();
if(!target)
return;
var targetProp = target.get('style')[this.property];
if(targetProp)
this.componentValue = targetProp;
else
this.componentValue = this.defaultValue + (this.unit || ''); // todo model
// Check if wrap inside function is required
if (propModel.get('functionName')) {
var v = this.fetchFromFunction(this.componentValue);
if(v)
this.componentValue = v;
}
// This allow to ovveride the normal flow of selecting component value,
// useful in composite properties
if(this.customValue && typeof this.customValue === "function"){
var index = propModel.collection.indexOf(propModel);
var t = this.customValue(this, index);
if(t)
this.componentValue = t;
}
return this.componentValue;
},
/**
* Refactor of getComponentValue
* Get the value of this property from the target (eg, Component, CSSRule)
* @param {Object} [opts] Options
* @param {Boolean} [options.fetchFromFunction]
* @param {Boolean} [options.ignoreDefault]
@ -273,6 +233,11 @@ module.exports = Backbone.View.extend({
result = target.getStyle()[model.get('property')];
// TODO when stack type asks the sub-property (in valueOnIndex method)
// to provide its target value and its detached, I should avoid parsing
// (at least is wrong applying 'functionName' cleaning)
result = model.parseValue(result);
if (!result && !opts.ignoreDefault) {
result = this.getDefaultValue();
}
@ -295,7 +260,7 @@ module.exports = Backbone.View.extend({
* @private
*/
getDefaultValue() {
return this.model.get('defaults');
return this.model.getDefaultValue();
},
/**
@ -310,35 +275,6 @@ module.exports = Backbone.View.extend({
return computed && valid.indexOf(property) >= 0 && computed[property];
},
/**
* Fetch the string from function type value
* @param {String} v Function type value
*
* @return {String}
* */
fetchFromFunction(v) {
return v.substring(v.indexOf("(") + 1, v.lastIndexOf(")"));
},
tryFetchFromFunction(value) {
if (!this.model.get('functionName')) {
return value;
}
var valueStr = value + '';
var start = valueStr.indexOf("(") + 1;
var end = valueStr.lastIndexOf(")");
return valueStr.substring(start, end);
},
/**
* Returns value from inputs
* @return {string}
*/
getValueForTarget() {
return this.model.get('value');
},
/**
* Returns value from input
* @return {string}
@ -348,42 +284,32 @@ module.exports = Backbone.View.extend({
},
/**
* Property was changed, so I need to update the component too
* Triggers when the 'value' of the model changes, so I have to update
* the target model
* @param {Object} e Events
* @param {Mixed} val Value
* @param {Object} opt Options
* */
valueChanged(e, val, opt) {
var mVal = this.getValueForTarget();
var em = this.config.em;
var model = this.model;
if(this.$input)
this.setValue(mVal);
if(!this.getTarget())
return;
const em = this.config.em;
const model = this.model;
const value = model.getFullValue();
const target = this.getTarget();
const onChange = this.onChange;
this.setValue(value);
// Check if component is allowed to be styled
if (!this.isTargetStylable() || !this.isComponentStylable()) {
if (!target || !this.isTargetStylable() || !this.isComponentStylable()) {
return;
}
var value = this.getValueForTarget();
var func = model.get('functionName');
if(func)
value = func + '(' + value + ')';
var target = this.getTarget();
var onChange = this.onChange;
if(onChange && typeof onChange === "function"){
if (onChange) {
onChange(target, this, opt);
}else
} else {
this.updateTargetStyle(value, null, opt);
}
if(em){
if (em) {
em.trigger('component:update', model);
em.trigger('component:styleUpdate', model);
em.trigger('component:styleUpdate:' + model.get('property'), model);
@ -392,27 +318,26 @@ module.exports = Backbone.View.extend({
/**
* Update target style
* @param {string} propertyValue
* @param {string} propertyName
* @param {string} value
* @param {string} name
* @param {Object} opts
*/
updateTargetStyle(propertyValue, propertyName, opts) {
var propName = propertyName || this.property;
var value = propertyValue || '';
var avSt = opts ? opts.avoidStore : 0;
var target = this.getTarget();
var targetStyle = _.clone(target.get('style'));
if(value)
targetStyle[propName] = value;
else
delete targetStyle[propName];
target.set('style', targetStyle, { avoidStore : avSt});
// Helper exists when is active a State in Style Manager
let helper = this.getHelperModel();
helper && helper.setStyle(targetStyle, {avoidStore: avSt});
updateTargetStyle(value, name = '', opts = {}) {
const property = name || this.model.get('property');
const target = this.getTarget();
const style = target.getStyle();
if (value) {
style[property] = value;
} else {
delete style[property];
}
target.setStyle(style, opts);
// Helper is used by `states` like ':hover' to show its preview
const helper = this.getHelperModel();
helper && helper.setStyle(style, opts);
},
/**
@ -453,20 +378,23 @@ module.exports = Backbone.View.extend({
},
/**
* Set value to the input
* @param {String} value
* @param {Boolean} force
* Set the value to property input
* @param {String} value
* @param {Boolean} force
* @private
* */
setValue(value, force) {
var f = force === 0 ? 0 : 1;
var def = this.model.get('defaults');
var v = this.model.get('value') || def;
if(value || f){
const model = this.model;
const f = force === 0 ? 0 : 1;
const def = model.getDefaultValue();
let v = model.get('value') || def;
if (value || f) {
v = value;
}
if(this.$input)
this.$input.val(v);
this.model.set({value: v}, {silent: true});
const input = this.$input;
input && input.val(v);
},
updateVisibility() {
@ -482,46 +410,13 @@ module.exports = Backbone.View.extend({
this.model.set('visible', 0);
},
renderLabel() {
let model = this.model;
this.$el.html(this.templateLabel({
pfx: this.pfx,
ppfx: this.ppfx,
icon: model.get('icon'),
info: model.get('info'),
label: model.get('name'),
}));
},
/**
* Render field property
* */
renderField() {
this.renderTemplate();
this.renderInput();
delete this.componentValue;
},
/**
* Render loaded template
* */
renderTemplate() {
this.$el.append( this.template({
pfx : this.pfx,
ppfx : this.ppfx,
icon : this.model.get('icon'),
info : this.model.get('info'),
label : this.model.get('name'),
}));
},
/**
* Renders input, to override
* */
renderInput() {
if(!this.$input){
this.$input = $('<input>', {
placeholder: this.model.get('defaults'),
placeholder: this.model.getDefaultValue(),
type: 'text'
});
this.$el.find(this.inputHolderId).html(this.$input);
@ -529,13 +424,6 @@ module.exports = Backbone.View.extend({
this.setValue(this.componentValue, 0);
},
/**
* Request to render input of the property
* */
renderInputRequest() {
this.renderInput();
},
/**
* Clean input
* */
@ -544,9 +432,10 @@ module.exports = Backbone.View.extend({
},
render() {
this.renderLabel();
this.renderField();
this.$el.attr('class', this.className);
const el = this.el;
el.innerHTML = this.template(this.model);
this.renderInput();
el.className = this.className;
this.updateStatus();
return this;
},

32
src/style_manager/view/SectorsView.js

@ -35,6 +35,7 @@ module.exports = Backbone.View.extend({
targetUpdated() {
var em = this.target;
var el = em.get('selectedComponent');
const um = em.get('UndoManager');
if(!el)
return;
@ -46,36 +47,43 @@ module.exports = Backbone.View.extend({
var device = em.getDeviceModel();
var state = !previewMode ? el.get('state') : '';
var widthMedia = device && device.get('widthMedia');
var mediaText = device && !previewMode && widthMedia ?
`(${config.mediaCondition}: ${widthMedia})` : '';
var stateStr = state ? `:${state}` : null;
var view = el.view;
var mediaText = device && !previewMode && widthMedia ?
`(${config.mediaCondition}: ${widthMedia})` : '';
pt.helper = null;
if (view) {
pt.computed = window.getComputedStyle(view.el, stateStr);
}
if(classes.length){
if (classes.length) {
var cssC = em.get('CssComposer');
var valid = _.filter(classes.models, item => item.get('active'));
var valid = classes.getStyleable();
var iContainer = cssC.get(valid, state, mediaText);
if(!iContainer){
if (!iContainer && valid.length) {
// I stop undo manager here as after adding the CSSRule (generally after
// selecting the component) and calling undo() it will remove the rule from
// the collection, therefore updating it in style manager will not affect it
// #268
um.stopTracking();
iContainer = cssC.add(valid, state, mediaText);
// Get styles from the component
iContainer.set('style', el.get('style'));
//cssC.addRule(iContainer);
el.set('style', {});
}else{
// Ensure to clean element
//if(classes.length == 1)
//el.set('style', {});
um.startTracking();
}
if (!iContainer) {
// In this case it's just a Component without any valid selector
pt.model = el;
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){
if (state) {
var clm = em.get('SelectorManager');
var helperClass = clm.add('hc-state');
var helperRule = cssC.get([helperClass]);

143
src/styles/scss/_gjs_assets.scss

@ -4,7 +4,7 @@
position: absolute;
top: 0;
left: 0;
z-index: 5;
z-index: 11;
width: 100%;
height: 100%;
transition: opacity 0.25s;
@ -22,6 +22,10 @@
height: 290px;
overflow: auto;
clear: both;
display: flex;
flex-wrap: wrap;
align-items: flex-start;
align-content: flex-start;
}
.#{$am-prefix}assets-header {
@ -33,85 +37,94 @@
width: 70%;
float: left;
}
button{
button {
width: 25%;
float: right;
}
}
.#{$am-prefix}add-field input {
padding: 6px;
.#{$am-prefix}preview-cont {
position: relative;
height: 70px;
width: 30%;
background-color: $mainColor;
border-radius: 2px;
float: left;
overflow: hidden;
}
.#{$am-prefix}assets-cont {
background-color: $mainDklColor;
border-radius: 3px;
box-sizing: border-box;
padding: 10px;
width: 45%;
float:right;
height: 325px;
overflow: hidden;
.#{$am-prefix}preview {
position: absolute;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
height: 100%;
width: 100%;
z-index: 1;
}
##{$am-prefix}preview-cont{
position: relative;
height: 70px; width: 30%;
background-color: $mainColor;
border-radius: 2px;
float: left;
overflow: hidden;
}
.#{$am-prefix}preview-bg {
@include opacity(0.5);
##{$am-prefix}preview{
position: absolute;
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
height: 100%;
width: 100%;
z-index: 1;
}
position: absolute;
height: 100%;
width: 100%;
z-index: 0;
}
##{$am-prefix}preview-bg{
position: absolute;
height: 100%;
width: 100%;
@include opacity(0.5);
z-index: 0;
}
.#{$am-prefix}dimensions {
@include opacity(0.5);
font-size: 10px;
}
.#{$am-prefix}meta {
width: 70%;
float: left;
font-size: 12px;
padding: 5px 0 0 5px;
box-sizing: border-box;
.#{$am-prefix}highlight {
background-color: $mainLhColor;
> div {
margin-bottom: 5px;
}
}
.#{$am-prefix}asset {
border-bottom: 1px solid darken($mainDkColor, 3%);
padding: 5px;
cursor:pointer;
position: relative;
.#{$am-prefix}close {
@extend .btn-cl;
&:hover ##{$am-prefix}close { display: block;}
cursor: pointer;
position: absolute;
right: 5px;
top: 0;
display: none;
}
}
##{$am-prefix}close {
@extend .btn-cl;
position: absolute;
right: 5px;
top: 0;
display: none;
}
.#{$am-prefix}asset {
border-bottom: 1px solid darken($mainDkColor, 3%);
padding: 5px;
cursor: pointer;
position: relative;
box-sizing: border-box;
width: 100%;
##{$am-prefix}meta {
width: 70%;
float: left;
font-size: 12px;
padding: 5px 0 0 5px;
box-sizing: border-box;
> div { margin-bottom: 5px;}
##{$am-prefix}dimensions {
font-size: 10px;
@include opacity(0.5);
}
&:hover .#{$am-prefix}close {
display: block;
}
}
.#{$am-prefix}highlight {
background-color: $mainLhColor;
}
.#{$am-prefix}assets-cont {
background-color: $mainDklColor;
border-radius: 3px;
box-sizing: border-box;
padding: 10px;
width: 45%;
float: right;
height: 325px;
overflow: hidden;
}

2
src/styles/scss/_gjs_canvas.scss

@ -117,8 +117,6 @@
background-color: navy;
}
$hndlMargin: -5px;
.#{$app-prefix}resizer-h {
pointer-events: all;
position: absolute;

75
src/styles/scss/_gjs_inputs.scss

@ -1,9 +1,4 @@
/********* Input style **********/
$inputFontColor: $mainLhlColor; /* #d5d5d5 */
$arrowColor: $mainLhlColor; /* b1b1b1 */
$darkTextShadow: $mainDkColor; /* #252525 */
$darkBorder: rgba(0, 0, 0, 0.15); /* 303030 */
$colorpSize: 22px;
@mixin rangeThumbStyle() {
height: 10px;
@ -27,25 +22,26 @@ $colorpSize: 22px;
.#{$app-prefix}field {
background-color: $mainDkColor;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 1px 1px 0 $mainLhColor;
border: none;
box-shadow: none;
border-radius: 2px;
box-sizing: border-box;
padding: 0;
position: relative;
color: $inputFontColor;
input,
select,
textarea {
@include appearance(none);
color: $inputFontColor;
color: inherit;
border: none;
background-color: transparent;
box-sizing: border-box;
width: 100%;
position: relative;
padding: 3px 4px 4px;
padding: $inputPadding;
z-index: 1;
}
@ -53,11 +49,6 @@ $colorpSize: 22px;
resize: vertical;
}
select {
height: 20px;
padding-right: 12px;
}
option {
padding: 3px 0;
}
@ -72,7 +63,10 @@ $colorpSize: 22px;
}
.#{$app-prefix}d-s-arrow {
bottom: 7px;
bottom: 0;
top: 0;
margin: auto;
right: $inputPadding;
border-top: 4px solid $arrowColor;
position: absolute;
height: 0;
@ -81,6 +75,22 @@ $colorpSize: 22px;
border-right: 4px solid transparent;
cursor: pointer;
}
.#{$app-prefix}field-number {
padding-right: 30px;
}
&-arrows {
position: absolute;
cursor: ns-resize;
margin: auto;
height: 20px;
width: 9px;
z-index: 10;
bottom: 0;
right: $inputPadding - 2px;
top: 0;
}
}
.#{$app-prefix}field-color {
@ -174,17 +184,15 @@ $colorpSize: 22px;
.#{$app-prefix}radio-item-label {
cursor: pointer;
display: block;
padding: 5px;
padding: $inputPadding;
}
.#{$app-prefix}field-units {
position: absolute;
right: 0;
margin: auto;
right: 10px;
bottom: 0;
top: 0;
select {
padding: 0 12px 0 0;
}
}
.#{$app-prefix}field-unit {
@ -196,16 +204,6 @@ $colorpSize: 22px;
cursor: pointer;
}
.#{$app-prefix}field-arrows {
z-index: 10;
cursor: ns-resize;
height: 100%;
position: absolute;
right: 0;
top: 0;
width: 9px;
}
.#{$app-prefix}field-arrow-u,
.#{$app-prefix}field-arrow-d {
position: absolute;
@ -270,6 +268,21 @@ $colorpSize: 22px;
}
}
.#{$app-prefix}btn-prim {
@extend .#{$app-prefix}color-main;
background-color: $mainLhColor;
border-radius: 2px;
padding: 3px 6px;
padding: $inputPadding;
cursor: pointer;
border: none;
&:active {
background-color: $mainLhColor;
}
}
.#{$app-prefix}chk-icon {
@include transform(rotate(45deg));

144
src/styles/scss/_gjs_layers.scss

@ -0,0 +1,144 @@
.#{$nv-prefix}selected-parent {
border: 1px solid $colorYell;
}
.#{$nv-prefix}opac50{
@include opacity(0.50);
}
.#{$app-prefix}layers {
position:relative;
height: 100%;
##{$nv-prefix}placeholder{
width: 100%;
position: absolute;
##{$nv-prefix}plh-int{
height: 100%;
padding: 1px;
&.#{$nv-prefix}insert{
background-color: $colorGreen;
}
}
}
.#{$nv-prefix}item {
font-weight: lighter;
text-align: left;
position: relative;
background-color: rgba(0, 0, 0, 0.1);
}
.#{$nv-prefix}item.#{$nv-prefix}hide {
@include opacity(0.55);
}
.#{$nv-prefix}item ##{$nv-prefix}counter {
font-size: 10px;
position: absolute;
right: 27px;
top: 9px;
}
.#{$nv-prefix}item ##{$nv-prefix}btn-eye{
@extend .btn;
height: auto !important; width: auto !important;
font-size: 13px;
left: 0; top: 0;
padding: 7px 5px 7px 10px;
position: absolute;
cursor:pointer;
z-index: 1;
}
}
.#{$nv-prefix}item ##{$nv-prefix}caret {
font-size: 7px;
width: 8px;
padding: 2px;
cursor: pointer;
position: absolute;
left: -9px;
top: 6px;
@include opacity(0.7);
&:hover {
@include opacity(1);
}
}
.#{$nv-prefix}item .#{$nv-prefix}title-c {
@extend .#{$app-prefix}bg-main;
}
.#{$nv-prefix}title {
@extend .#{$app-prefix}category-title;
padding: 3px 10px 5px 30px;
display: flex;
align-items: center;
}
.#{$nv-prefix}title-inn {
position: relative;
}
.#{$nv-prefix}item .#{$nv-prefix}children .#{$nv-prefix}title{
border-left: 1px solid lighten($mainDkColor,2%);
}
.#{$nv-prefix}item > .#{$nv-prefix}children {
display: none;
}
.#{$nv-prefix}item.open > .#{$nv-prefix}children {
display: block;
}
.#{$nv-prefix}item > .#{$nv-prefix}no-chld > ##{$nv-prefix}caret::before{
content:'';
}
.#{$nv-prefix}no-chld > .#{$nv-prefix}title-inn > ##{$nv-prefix}caret {
display:none;
}
.#{$nv-prefix}item > ##{$nv-prefix}move {
position: absolute;
cursor: move;
font-size: 12px;
right: 0; top: 0;
padding: 7px 10px 7px 5px;
}
/*
.#{$nv-prefix}item{
&.#{$nv-prefix}selected{
border: 2px solid $colorBlue;
}
}
*/
.#{$nv-prefix}selected .#{$nv-prefix}title {
background-color: rgba(255,255,255,0.1);
}
.#{$nv-prefix}nav-item-edit {
visibility: hidden;
padding: 5px;
font-size: 9px;
position: absolute;
left: -27px;
top: 1px;
@include opacity(0.7);
&:hover {
@include opacity(1);
}
}
.#{$nv-prefix}title-c:hover {
.#{$nv-prefix}nav-item-edit {
visibility: visible;
cursor: pointer;
}
}
.#{$app-prefix}nav-comp-name {
padding: 5px;
box-sizing: content-box;
@extend .#{$app-prefix}no-user-select;
}

7
src/styles/scss/_gjs_style_manager.scss

@ -246,7 +246,8 @@
&.#{$sm-prefix}composite,
&.#{$sm-prefix}file,
&.#{$sm-prefix}list,
&.#{$sm-prefix}stack {
&.#{$sm-prefix}stack,
&.#{$sm-prefix}color {
width: 100%;
}
@ -306,14 +307,14 @@
.#{$sm-prefix}layers {
margin-top: 5px;
padding: 1px 3px;
min-height: 30px;
}
.#{$sm-prefix}layer {
background-color: rgba(255, 255, 255, 0.055);
border-radius: 2px;
box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2), 1px 1px 0 rgba(255, 255, 255, 0.055) inset;
margin: 2px;
margin: 2px 0;
padding: 7px;
position: relative;
cursor: pointer;

78
src/styles/scss/_gjs_variables.scss

@ -0,0 +1,78 @@
/* Class names prefixes */
$app-prefix: 'gjs-' !default;
$nv-prefix: $app-prefix + 'nv-' !default;
$rte-prefix: $app-prefix + 'rte-' !default;
$comp-prefix: $app-prefix + 'comp-' !default;
$mdl-prefix: $app-prefix + 'mdl-' !default;
$am-prefix: $app-prefix + 'am-' !default;
$cm-prefix: $app-prefix + 'cm-' !default;
$pn-prefix: $app-prefix + 'pn-' !default;
$com-prefix: $app-prefix + 'com-' !default;
$sm-prefix: $app-prefix + 'sm-' !default;
$cv-prefix: $app-prefix + 'cv-' !default;
$clm-prefix: $app-prefix + 'clm-' !default;
$trt-prefix: $app-prefix + 'trt-' !default;
/* Colors / Theme */
/* Dark theme */
$mainColor: #444 !default; /* Light: #573454 Dark: #3b2639 -moz-linear-gradient(top, #fca99b 0%, #6e2842 100%) */
$fontColor: #ddd !default; /* l: #d8d7db */
$fontColorActive: #f8f8f8 !default;
/* Light theme
$mainColor: #fff;
$fontColor: #9299a3;
$fontColorActive: #4f8ef7;
*/
$mainDkColor: rgba(0, 0, 0, 0.3) !default;/* darken($mainColor, 4%) - #383838 */
$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;
$colorGreen: #62c462 !default;
$tagBg: #804f7b !default;
$secColor: $tagBg !default;
$imageCompDim: 50px !default;
$leftWidth: 15% !default;
/* Color Helpers */
$colorHighlight: #71b7f1 !default;
$colorWarn: #ffca6f !default;
/* Canvas */
$hndlMargin: -5px !default;
/* Components / Inputs */
$lightBorder: rgba(255, 255, 255, 0.05) !default;
$inputFontColor: $mainLhlColor !default; /* #d5d5d5 */
$arrowColor: $mainLhlColor !default; /* b1b1b1 */
$darkTextShadow: $mainDkColor !default; /* #252525 */
$darkBorder: rgba(0, 0, 0, 0.15) !default; /* 303030 */
$colorpSize: 22px !default;
$inputPadding: 5px !default; // Has to be a single value
/* Class manager */
$addBtnBg: lighten($mainDkColor, 10%) !default;
$paddElClm: 5px 6px !default;
/* File uploader */
$uploadPadding: 150px 10px !default;
/* Commands */
$animSpeed: 0.2s !default;
/* Fonts */
$fontPath: '../fonts' !default;
$fontName: 'main-fonts' !default;
$fontV: 20 !default;//random(1000)

219
src/styles/scss/main.scss

@ -4,49 +4,9 @@
@import "node_modules/codemirror/lib/codemirror";
@import "node_modules/codemirror/theme/hopscotch";
$app-prefix: 'gjs-';
$nv-prefix: $app-prefix + 'nv-';
$rte-prefix: $app-prefix + 'rte-';
$comp-prefix: $app-prefix + 'comp-';
$mdl-prefix: $app-prefix + 'mdl-';
$am-prefix: $app-prefix + 'am-';
$cm-prefix: $app-prefix + 'cm-';
$pn-prefix: $app-prefix + 'pn-';
$com-prefix: $app-prefix + 'com-';
$sm-prefix: $app-prefix + 'sm-';
$cv-prefix: $app-prefix + 'cv-';
$clm-prefix: $app-prefix + 'clm-';
$trt-prefix: $app-prefix + 'trt-';
/* Dark theme */
$mainColor: #444; /* Light: #573454 Dark: #3b2639 -moz-linear-gradient(top, #fca99b 0%, #6e2842 100%) */
$fontColor: #ddd; /* l: #d8d7db */
$fontColorActive: #f8f8f8;
/* Light theme
$mainColor: #fff;
$fontColor: #9299a3;
$fontColorActive: #4f8ef7;
*/
$mainDkColor: rgba(0, 0, 0, 0.3);/* darken($mainColor, 4%) - #383838 */
$mainDklColor: rgba(0, 0, 0, 0.1);
$mainLhColor: rgba(255, 255, 255, 0.1); /* #515151 */
$mainLhlColor: rgba(255, 255, 255, 0.7);
$fontColorDk: #777;
$mainFont: Helvetica, sans-serif;
$colorBlue: #3b97e3;
$colorRed: #dd3636;
$colorYell: #ffca6f;
$colorGreen: #62c462;
$tagBg: #804f7b;
$secColor: $tagBg;
$imageCompDim: 50px;
$leftWidth: 15%;
$fontPath: '../fonts';
$fontName: 'main-fonts';
$fontV: 20;//random(1000)
@import "gjs_variables.scss";
@font-face {
font-family: 'font3336';
@ -88,9 +48,6 @@ $fontV: 20;//random(1000)
transform: $v;
}
/* Color Helpers */
$colorHighlight: #71b7f1;
$colorWarn: #ffca6f;
.#{$app-prefix}bg {
&-main {
@ -248,30 +205,6 @@ $colorWarn: #ffca6f;
@include opacity(0.50);
pointer-events: none;
}
.#{$app-prefix}btn-prim{
@extend .#{$app-prefix}color-main;
background-color: $mainLhColor;
border-radius: 2px;
padding: 3px 6px;
cursor: pointer;
padding: 0.5em;
border: none;
}
.#{$app-prefix}btn-prim:active{
background-color: $mainLhColor;
}
.#{$app-prefix}input{
background-color: $mainDkColor;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 2px;
box-shadow: 1px 1px 0 $mainLhColor;
box-sizing: border-box;
color: $fontColor;
padding: 0.5em 1em;
}
.#{$app-prefix}select {
@extend .#{$app-prefix}input;
}
div.#{$app-prefix}select {
padding: 0;
@ -419,11 +352,12 @@ ol.example li.placeholder:before {position: absolute;}
.#{$app-prefix}placeholder,
.#{$nv-prefix}placeholder {
/*border-width: 3px !important;*/
border-style: solid !important;
border-color: $colorGreen;
outline: none;
box-sizing: border-box;
transition: top $animSpeed, left $animSpeed,
width $animSpeed, height $animSpeed;
}
.#{$app-prefix}placeholder-int,
@ -543,139 +477,6 @@ ol.example li.placeholder:before {position: absolute;}
}
}
/************* Navigator *************/
.#{$nv-prefix}selected-parent {
border: 1px solid $colorYell;
}
.#{$nv-prefix}opac50{
@include opacity(0.50);
}
.#{$nv-prefix}navigator{
position:relative;
height: 100%;
##{$nv-prefix}placeholder{
width: 100%;
position: absolute;
##{$nv-prefix}plh-int{
height: 100%;
padding: 1px;
&.#{$nv-prefix}insert{
background-color: $colorGreen;
}
}
}
.#{$nv-prefix}item {
font-weight: lighter;
text-align: left;
position: relative;
background-color: rgba(0, 0, 0, 0.1);
}
.#{$nv-prefix}item.#{$nv-prefix}hide {
@include opacity(0.55);
}
.#{$nv-prefix}item ##{$nv-prefix}counter {
font-size: 10px;
position: absolute;
right: 27px;
top: 9px;
}
.#{$nv-prefix}item ##{$nv-prefix}btn-eye{
@extend .btn;
height: auto !important; width: auto !important;
font-size: 13px;
left: 0; top: 0;
padding: 7px 5px 7px 10px;
position: absolute;
cursor:pointer;
z-index: 1;
}
}
.#{$nv-prefix}item ##{$nv-prefix}caret {
font-size: 7px;
width: 8px;
padding: 5px;
cursor: pointer;
@include opacity(0.7);
&:hover {
@include opacity(1);
}
}
.#{$nv-prefix}item .#{$nv-prefix}title-c {
@extend .#{$app-prefix}bg-main;
}
.#{$nv-prefix}title {
background-color: $mainDklColor;
font-size: 11px;
letter-spacing: 1px;
padding: 3px 10px 5px 30px;
border-bottom: 1px solid $mainDkColor;
border-top: 1px solid $mainLhColor;
display: flex;
align-items: center;
}
.#{$nv-prefix}item .#{$nv-prefix}children .#{$nv-prefix}title{
border-left: 1px solid lighten($mainDkColor,2%);
}
.#{$nv-prefix}item > .#{$nv-prefix}children {
margin-left: 7px;
display: none;
}
.#{$nv-prefix}item.open > .#{$nv-prefix}children { display: block; }
.#{$nv-prefix}item > .#{$nv-prefix}no-chld > ##{$nv-prefix}caret::before{ content:''; }
.#{$nv-prefix}no-chld > ##{$nv-prefix}caret{ display:none; }
.#{$nv-prefix}item > ##{$nv-prefix}move {
position: absolute;
cursor: move;
font-size: 12px;
right: 0; top: 0;
padding: 7px 10px 7px 5px;
}
/*
.#{$nv-prefix}item{
&.#{$nv-prefix}selected{
border: 2px solid $colorBlue;
}
}
*/
.#{$nv-prefix}selected .#{$nv-prefix}title {
background-color: rgba(255,255,255,0.1);
}
.#{$nv-prefix}nav-item-edit {
visibility: hidden;
padding: 5px;
font-size: 10px;
@include opacity(0.7);
&:hover {
@include opacity(1);
}
}
.#{$nv-prefix}title-c:hover {
.#{$nv-prefix}nav-item-edit {
visibility: visible;
cursor: pointer;
}
}
.#{$app-prefix}nav-comp-name {
padding: 5px;
box-sizing: content-box;
@extend .#{$app-prefix}no-user-select;
}
/************* END Navigator *************/
/* pa-refresh pa-rocket pa-trash pa-columns pa-rotate-left/right */
.btn.expand{ background-image: none;}
@ -685,7 +486,8 @@ ol.example li.placeholder:before {position: absolute;}
display: block;
background-color: #f5f5f5;
color: $fontColorDk;
height: $imageCompDim; width: $imageCompDim;
height: $imageCompDim;
width: $imageCompDim;
line-height: $imageCompDim;
outline: 3px solid $colorYell;
outline-offset: -3px;
@ -698,7 +500,6 @@ ol.example li.placeholder:before {position: absolute;}
}
}
$lightBorder: rgba(255, 255, 255, 0.05);
@import "gjs_inputs";
@ -762,11 +563,11 @@ $lightBorder: rgba(255, 255, 255, 0.05);
@import "gjs_blocks";
/************* Navigator *************/
@import "gjs_layers";
/********* Class manager **********/
$addBtnBg: lighten($mainDkColor, 10%);
$paddElClm: 5px 6px;
.#{$clm-prefix}field{
@extend .#{$sm-prefix}field
@ -927,8 +728,6 @@ $paddElClm: 5px 6px;
/********* File uploader **********/
$uploadPadding: 150px 10px;
.#{$am-prefix}file-uploader {
width: 55%;
float:left;

17
src/trait_manager/model/Trait.js

@ -23,4 +23,21 @@ module.exports = Backbone.Model.extend({
}
},
/**
* Get the initial value of the trait
* @return {string}
*/
getInitValue() {
const target = this.target;
const name = this.get('name');
let value;
if (target) {
const attrs = target.get('attributes');
value = this.get('changeProp') ? target.get(name) : attrs[name];
}
return value || this.get('value') || this.get('default');
}
});

112
src/utils/Resizer.js

@ -9,14 +9,15 @@ var defaults = {
onStart: null,
onMove: null,
onEnd: null,
tl: 1,
tc: 1,
tr: 1,
cl: 1,
cr: 1,
bl: 1,
bc: 1,
br: 1,
// Handlers
tl: 1, // Top left
tc: 1, // Top center
tr: 1, // Top right
cl: 1, // Center left
cr: 1, // Center right
bl: 1, // Bottom left
bc: 1, // Bottom center
br: 1, // Bottom right
};
var createHandler = (name, opts) => {
@ -45,17 +46,46 @@ class Resizer {
* @param {Object} options
*/
constructor(opts = {}) {
var pfx = opts.prefix || '';
var appendTo = opts.appendTo || document.body;
this.setOptions(opts);
return this;
}
/**
* Setup options
* @param {Object} options
*/
setOptions(options = {}) {
// Setup default options
for (var name in defaults) {
if (!(name in opts))
opts[name] = defaults[name];
if (!(name in options))
options[name] = defaults[name];
}
var container = document.createElement('div');
container.className = pfx + 'resizer-c';
appendTo.appendChild(container);
this.opts = options;
this.setup();
}
/**
* Setup resizer
*/
setup() {
const opts = this.opts;
const pfx = opts.prefix || '';
const appendTo = opts.appendTo || document.body;
let container;
// Create container if not yet exist
if (!this.container) {
container = document.createElement('div');
container.className = pfx + 'resizer-c';
appendTo.appendChild(container);
this.container = container;
}
container = this.container;
while (container.firstChild) {
container.removeChild(container.firstChild);
}
// Create handlers
var handlers = {
@ -69,14 +99,14 @@ class Resizer {
br: opts.br ? createHandler('br', opts) : '',
};
for (var n in handlers) {
if(handlers[n])
container.appendChild(handlers[n]);
for (let n in handlers) {
const handler = handlers[n];
if (handler) {
container.appendChild(handler);
}
}
this.container = container;
this.handlers = handlers;
this.opts = opts;
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.move = this.move.bind(this);
@ -87,22 +117,6 @@ class Resizer {
this.onStart = opts.onStart;
this.onMove = opts.onMove;
this.onEnd = opts.onEnd;
return this;
}
/**
* Update options
* @param {Object} options
*/
setOptions(options) {
var opts = options || {};
for (var opt in opts) {
if(opt in defaults) {
this[opt] = opts[opt];
}
}
}
/**
@ -279,7 +293,7 @@ class Resizer {
this.updateRect(1);
// Stop callback
if(typeof this.onEnd === 'function') {
if (typeof this.onEnd === 'function') {
this.onEnd(e, {docs: doc});
}
}
@ -291,10 +305,14 @@ class Resizer {
var elStyle = this.el.style;
var conStyle = this.container.style;
var rect = this.rectDim;
const selectedHandler = this.getSelectedHandler();
// Use custom updating strategy if requested
if (typeof this.updateTarget === 'function') {
this.updateTarget(this.el, rect, store);
this.updateTarget(this.el, rect, {
store,
selectedHandler
});
} else {
elStyle.width = rect.w + 'px';
elStyle.height = rect.h + 'px';
@ -310,6 +328,22 @@ class Resizer {
conStyle.height = rectEl.height + unit;
}
/**
* Get selected handler name
* @return {string}
*/
getSelectedHandler() {
var handlers = this.handlers;
if (!this.selectedHandler) {
return;
}
for (let n in handlers) {
if (handlers[n] === this.selectedHandler) return n;
}
}
/**
* Handle ESC key
* @param {Event} e
@ -329,8 +363,10 @@ class Resizer {
handleMouseDown(e) {
var el = e.target;
if (this.isHandler(el)) {
this.selectedHandler = el;
this.start(e);
}else if(el !== this.el){
this.selectedHandler = '';
this.blur();
}
}

6
src/utils/Sorter.js

@ -39,6 +39,7 @@ module.exports = Backbone.View.extend({
this.em = o.em || '';
this.dragHelper = null;
this.canvasRelative = o.canvasRelative || 0;
this.selectOnEnd = !o.avoidSelectOnEnd;
if(this.em && this.em.on){
this.em.on('change:canvasOffset', this.udpateOffset);
@ -766,11 +767,14 @@ module.exports = Backbone.View.extend({
if (srcModel && srcModel.set) {
srcModel.set('status', '');
srcModel.set('status', 'selected');
//this.selectOnEnd && srcModel.set('status', 'selected');
}
}
if(this.moved)
if (this.moved) {
created = this.move(this.target, src, this.lastPos);
}
if(this.plh)
this.plh.style.display = 'none';

29
src/utils/fetch.js

@ -0,0 +1,29 @@
import Promise from 'promise-polyfill';
window.Promise = window.Promise || Promise;
export default typeof fetch == 'function' ? fetch.bind() : (url, options) => {
return new Promise((res, rej) => {
const req = new XMLHttpRequest();
req.open(options.method || 'get', url);
req.withCredentials = options.credentials == 'include';
for (let k in options.headers || {}) {
req.setRequestHeader(k, options.headers[k]);
}
req.onload = e => res({
status: req.status,
statusText: req.statusText,
text: () => Promise.resolve(req.responseText)
});
req.onerror = rej;
// Actually, fetch doesn't support onProgress feature
if (req.upload && options.onProgress) {
req.upload.onprogress = options.onProgress;
}
req.send(options.body);
});
}

8
test/specs/asset_manager/index.js

@ -72,12 +72,6 @@ describe('Asset Manager', () => {
expect(asset2.get('src')).toEqual(imgObj.src + '2');
});
it('Src is unique', () => {
obj.add(imgObj);
obj.add(imgObj);
expect(obj.getAll().length).toEqual(1);
});
it('Remove asset', () => {
obj.add(imgObj);
obj.remove(imgObj.src);
@ -112,7 +106,7 @@ describe('Asset Manager', () => {
obj.add(imgObj);
obj.store();
obj.remove(imgObj.src);
obj.load();
obj.load({assets: storage['gjs-assets']});
var asset = obj.get(imgObj.src);
expect(asset.get('width')).toEqual(imgObj.width);
expect(asset.get('height')).toEqual(imgObj.height);

25
test/specs/asset_manager/model/Assets.js

@ -21,31 +21,6 @@ module.exports = {
it('Collection is empty', () => {
expect(obj.length).toEqual(0);
});
it("Can't insert assets without src", () => {
obj.add({});
expect(obj.length).toEqual(0);
obj.add([{},{},{}]);
expect(obj.length).toEqual(0);
});
it("Insert assets only with src", () => {
obj.add([{},{src:'test'},{}]);
expect(obj.length).toEqual(1);
});
it('addImg creates new asset', () => {
obj.addImg('/img/path');
expect(obj.length).toEqual(1);
});
it('addImg asset is correct', () => {
obj.addImg('/img/path');
var asset = obj.at(0);
expect(asset.get('type')).toEqual('image');
expect(asset.get('src')).toEqual('/img/path');
});
});
}
};

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

@ -16,6 +16,7 @@ module.exports = {
var coll = new Assets();
var model = coll.add({ type:'image', src: '/test' });
this.view = new AssetImageView({
collection: new Assets(),
config : {},
model
});
@ -39,17 +40,17 @@ module.exports = {
it('Has preview box', function() {
var $asset = this.view.$el;
expect($asset.find('#preview').length).toEqual(1);
expect($asset.find('.preview').length).toEqual(1);
});
it('Has meta box', function() {
var $asset = this.view.$el;
expect($asset.find('#meta').length).toEqual(1);
expect($asset.find('.meta').length).toEqual(1);
});
it('Has close button', function() {
var $asset = this.view.$el;
expect($asset.find('#close').length).toEqual(1);
expect($asset.find('[data-toggle=asset-remove]').length).toEqual(1);
});
});
@ -72,7 +73,7 @@ module.exports = {
it('Could be removed', function() {
var spy = sinon.spy();
this.view.model.on("remove", spy);
this.view.$el.find('#close').trigger('click');
this.view.$el.find('[data-toggle=asset-remove]').trigger('click');
expect(spy.called).toEqual(true);
});

17
test/specs/asset_manager/view/AssetsView.js

@ -1,4 +1,5 @@
var AssetsView = require('asset_manager/view/AssetsView');
var FileUploader = require('asset_manager/view/FileUploader');
var Assets = require('asset_manager/model/Assets');
module.exports = {
@ -14,10 +15,12 @@ module.exports = {
});
beforeEach(function () {
this.coll = new Assets([]);
this.coll = new Assets([]);
this.view = new AssetsView({
config : {},
collection: this.coll
config: {},
collection: this.coll,
globalCollection: new Assets([]),
fu: new FileUploader({})
});
obj = this.view;
this.$fixture.empty().appendTo(this.$fixtures);
@ -77,15 +80,15 @@ module.exports = {
});
it("Returns not empty url input", () => {
expect(obj.getInputUrl()).toExist();
expect(obj.getAddInput()).toExist();
});
it("Add image asset from input string", () => {
obj.getInputUrl().value = "test";
obj.addFromStr({
obj.getAddInput().value = "test";
obj.handleSubmit({
preventDefault() {}
});
var asset = obj.collection.at(0);
var asset = obj.options.globalCollection.at(0);
expect(asset.get('src')).toEqual('test');
});

11
test/specs/asset_manager/view/FileUploader.js

@ -1,5 +1,6 @@
var FileUploader = require('asset_manager/view/FileUploader');
module.exports = {
run() {
@ -65,10 +66,20 @@ module.exports = {
it('Could be disabled', () => {
var view = new FileUploader({ config : {
disableUpload: true,
upload: 'something'
} });
view.render();
expect(view.$el.find('input[type=file]').prop('disabled')).toEqual(true);
});
it('Handles embedAsBase64 parameter', () => {
var view = new FileUploader({ config : {
embedAsBase64: true
} });
view.render();
expect(view.$el.find('input[type=file]').prop('disabled')).toEqual(false);
expect(view.uploadFile).toEqual(FileUploader.embedAsBase64);
});
});

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

@ -14,7 +14,7 @@ module.exports = {
obj = new HtmlGenerator();
var dcomp = new DomComponents();
comp = new Component({}, {
defaultTypes: dcomp.componentTypes,
componentTypes: dcomp.componentTypes,
});
});
@ -64,7 +64,7 @@ module.exports = {
obj = new CssGenerator();
var dcomp = new DomComponents();
comp = new Component({}, {
defaultTypes: dcomp.componentTypes,
componentTypes: dcomp.componentTypes,
});
});
@ -100,7 +100,7 @@ module.exports = {
var rule = cssc.add(cls1);
rule.set('style',{'prop1':'value1', 'prop2':'value2'});
expect(obj.build(comp, cssc)).toEqual('.class1{prop1:value1;prop2:value2;}');
expect(obj.build(comp, {cssc})).toEqual('.class1{prop1:value1;prop2:value2;}');
});
it('Build correctly component styled with class and state', () => {
@ -112,7 +112,7 @@ module.exports = {
rule.set('style',{'prop1':'value1', 'prop2':'value2'});
rule.set('state', 'hover');
expect(obj.build(comp, cssc)).toEqual('.class1:hover{prop1:value1;prop2:value2;}');
expect(obj.build(comp, {cssc})).toEqual('.class1:hover{prop1:value1;prop2:value2;}');
});
@ -125,7 +125,7 @@ module.exports = {
var rule = cssc.add([cls1, cls2]);
rule.set('style',{'prop1':'value1', 'prop2':'value2'});
expect(obj.build(comp, cssc)).toEqual('.class1.class2{prop1:value1;prop2:value2;}');
expect(obj.build(comp, {cssc})).toEqual('.class1.class2{prop1:value1;prop2:value2;}');
});
it('Build rules with mixed classes', () => {
@ -138,7 +138,7 @@ module.exports = {
rule.set('style',{'prop1':'value1', 'prop2':'value2'});
rule.set('selectorsAdd', '.class1 .class2, div > .class4');
expect(obj.build(comp, cssc)).toEqual('.class1.class2, .class1 .class2, div > .class4{prop1:value1;prop2:value2;}');
expect(obj.build(comp, {cssc})).toEqual('.class1.class2, .class1 .class2, div > .class4{prop1:value1;prop2:value2;}');
});
it('Build rules with only not class based selectors', () => {
@ -147,7 +147,7 @@ module.exports = {
rule.set('style',{'prop1':'value1', 'prop2':'value2'});
rule.set('selectorsAdd', '.class1 .class2, div > .class4');
expect(obj.build(comp, cssc)).toEqual('.class1 .class2, div > .class4{prop1:value1;prop2:value2;}');
expect(obj.build(comp, {cssc})).toEqual('.class1 .class2, div > .class4{prop1:value1;prop2:value2;}');
});
it('Build correctly with class styled out', () => {
@ -161,7 +161,7 @@ module.exports = {
var rule2 = cssc.add(cls2);
rule2.set('style',{'prop2':'value2'});
expect(obj.build(comp, cssc)).toEqual('.class1.class2{prop1:value1;}.class2{prop2:value2;}');
expect(obj.build(comp, {cssc})).toEqual('.class1.class2{prop1:value1;}.class2{prop2:value2;}');
});
it('Rule with media query', () => {
@ -174,7 +174,7 @@ module.exports = {
rule.set('style',{'prop1':'value1'});
rule.set('mediaText', '(max-width: 999px)');
expect(obj.build(comp, cssc)).toEqual('@media (max-width: 999px){.class1.class2{prop1:value1;}}');
expect(obj.build(comp, {cssc})).toEqual('@media (max-width: 999px){.class1.class2{prop1:value1;}}');
});
it('Rules mixed with media queries', () => {
@ -197,7 +197,7 @@ module.exports = {
var rule5 = cssc.add(cls1, '', '(max-width: 100px)');
rule5.set('style',{'prop5':'value5'});
expect(obj.build(comp, cssc)).toEqual('.class1.class2{prop1:value1;}.class2{prop2:value2;}'+
expect(obj.build(comp, {cssc})).toEqual('.class1.class2{prop1:value1;}.class2{prop2:value2;}'+
'@media (max-width: 999px){.class1{prop3:value3;}.class2{prop4:value4;}}'+
'@media (max-width: 100px){.class1{prop5:value5;}}');
});
@ -211,7 +211,7 @@ module.exports = {
rule.set('style',{'prop1':'value1', 'prop2':'value2'});
comp.get('components').remove(m1);
expect(obj.build(comp, cssc)).toEqual('');
expect(obj.build(comp, {cssc})).toEqual('');
});
})
}

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

@ -54,9 +54,9 @@ module.exports = {
it('Rules are correctly imported from default property', () => {
var gj = grapesjs.init({
stylePrefix: '',
storageManager: { autoload: 0, type:'none' },
assetManager: { storageType: 'none', },
cssComposer: { rules: rulesSet},
storageManager: {autoload: 0, type:'none' },
assetManager: {storageType: 'none', },
cssComposer: {rules: rulesSet},
container: 'csscomposer-fixture',
});
var cssc = gj.editor.get('CssComposer');
@ -68,8 +68,10 @@ module.exports = {
it('New rule adds correctly the class inside selector manager', () => {
var rules = cssc.getAll();
rules.add({ selectors: [{name: 'test1'}] });
expect(clsm.getAll().at(0).get('name')).toEqual('test1');
rules.add({ selectors: [{name: 'test1', private: true}] });
var rule = clsm.getAll().at(0);
expect(rule.get('name')).toEqual('test1');
expect(rule.get('private')).toEqual(true);
});
it('New rules are correctly imported inside selector manager', () => {
@ -128,6 +130,8 @@ module.exports = {
label: 'test1',
name: 'test1',
type: 'class',
private: false,
protected: false,
}],
selectorsAdd: '',
state: '',

14
test/specs/dom_components/index.js

@ -1,4 +1,5 @@
const DomComponents = require('dom_components');
const Components = require('dom_components/model/Components');
const ComponentModels = require('./model/Component');
const ComponentView = require('./view/ComponentV');
const ComponentsView = require('./view/ComponentsView');
@ -38,7 +39,9 @@ describe('DOM Components', () => {
beforeEach(() => {
config = {};
config = {
storeWrapper: 1,
};
obj = new DomComponents().init(config);
});
@ -69,18 +72,21 @@ describe('DOM Components', () => {
it('Store data', () => {
setSmConfig();
setEm();
//obj.getWrapper().get('components').add({});
var expected = {
html: 'testHtml',
components: '{"test":1}',
components: JSON.stringify(obj.getWrapper()),
};
expect(obj.store(1)).toEqual(expected);
});
it('Store and load data', () => {
it.skip('Store and load data', () => {
setSmConfig();
setEm();
const comps = new Components({}, {});
obj.getWrapper().set('components', comps);
obj.store();
expect(obj.load()).toEqual({test: 1});
expect(obj.load()).toEqual([{test: 1}]);
});
it('Wrapper exists', () => {

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

@ -19,7 +19,7 @@ module.exports = {
obj = new Component();
dcomp = new DomComponents();
compOpts = {
defaultTypes: dcomp.componentTypes,
componentTypes: dcomp.componentTypes,
};
});
@ -254,7 +254,7 @@ module.exports = {
beforeEach(() => {
dcomp = new DomComponents();
compOpts = {
defaultTypes: dcomp.componentTypes,
componentTypes: dcomp.componentTypes,
}
});

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

Loading…
Cancel
Save