Browse Source

Merge pull request #415 from artf/dev

Merge dev branch
pull/432/head
Artur Arseniev 9 years ago
committed by GitHub
parent
commit
8c9ffa958a
  1. 10
      .editorconfig
  2. 4
      .gitignore
  3. 3
      .npmignore
  4. 26
      README.md
  5. 2
      dist/css/grapes.min.css
  6. 6
      dist/grapes.min.js
  7. 15
      index.html
  8. 22
      package.json
  9. 3
      src/asset_manager/view/AssetsView.js
  10. 2
      src/asset_manager/view/FileUploader.js
  11. 115
      src/block_manager/index.js
  12. 2
      src/block_manager/view/BlockView.js
  13. 12
      src/block_manager/view/BlocksView.js
  14. 19
      src/canvas/index.js
  15. 64
      src/canvas/view/CanvasView.js
  16. 2
      src/code_manager/view/EditorView.js
  17. 15
      src/commands/index.js
  18. 188
      src/commands/view/CommandAbstract.js
  19. 4
      src/commands/view/CreateComponent.js
  20. 4
      src/commands/view/DeleteComponent.js
  21. 4
      src/commands/view/ExportTemplate.js
  22. 16
      src/commands/view/MoveComponent.js
  23. 28
      src/commands/view/OpenBlocks.js
  24. 3
      src/commands/view/OpenLayers.js
  25. 17
      src/commands/view/OpenStyleManager.js
  26. 4
      src/commands/view/OpenTraitManager.js
  27. 53
      src/commands/view/SelectComponent.js
  28. 9
      src/commands/view/SelectParent.js
  29. 2
      src/commands/view/SelectPosition.js
  30. 24
      src/commands/view/ShowOffset.js
  31. 1
      src/dom_components/index.js
  32. 6
      src/dom_components/model/Component.js
  33. 112
      src/dom_components/view/ComponentTextView.js
  34. 137
      src/dom_components/view/ComponentView.js
  35. 61
      src/dom_components/view/ComponentsView.js
  36. 11
      src/dom_components/view/ToolbarButtonView.js
  37. 65
      src/domain_abstract/ui/Input.js
  38. 118
      src/domain_abstract/ui/InputColor.js
  39. 119
      src/domain_abstract/ui/InputNumber.js
  40. 10
      src/editor/index.js
  41. 83
      src/editor/model/Editor.js
  42. 2
      src/editor/view/EditorView.js
  43. 13
      src/grapesjs/index.js
  44. 15
      src/modal_dialog/view/ModalView.js
  45. 126
      src/navigator/view/ItemView.js
  46. 22
      src/navigator/view/ItemsView.js
  47. 2
      src/panels/view/ButtonView.js
  48. 35
      src/rich_text_editor/config/config.js
  49. 345
      src/rich_text_editor/index.js
  50. 21
      src/rich_text_editor/model/CommandButton.js
  51. 6
      src/rich_text_editor/model/CommandButtons.js
  52. 245
      src/rich_text_editor/model/RichTextEditor.js
  53. 42
      src/rich_text_editor/view/CommandButtonSelectView.js
  54. 16
      src/rich_text_editor/view/CommandButtonView.js
  55. 76
      src/rich_text_editor/view/CommandButtonsView.js
  56. 231
      src/rich_text_editor/view/TextEditorView.js
  57. 2
      src/selector_manager/model/Selector.js
  58. 28
      src/selector_manager/view/ClassTagView.js
  59. 4
      src/selector_manager/view/ClassTagsView.js
  60. 5
      src/storage_manager/config/config.js
  61. 3
      src/storage_manager/index.js
  62. 165
      src/storage_manager/model/RemoteStorage.js
  63. 44
      src/style_manager/index.js
  64. 25
      src/style_manager/model/Layer.js
  65. 99
      src/style_manager/model/Layers.js
  66. 43
      src/style_manager/model/Properties.js
  67. 3
      src/style_manager/model/PropertyComposite.js
  68. 2
      src/style_manager/model/PropertyFactory.js
  69. 18
      src/style_manager/model/PropertyInteger.js
  70. 9
      src/style_manager/model/PropertySlider.js
  71. 12
      src/style_manager/model/PropertyStack.js
  72. 5
      src/style_manager/model/Sectors.js
  73. 242
      src/style_manager/view/LayerView.js
  74. 23
      src/style_manager/view/LayersView.js
  75. 21
      src/style_manager/view/PropertiesView.js
  76. 34
      src/style_manager/view/PropertyColorView.js
  77. 39
      src/style_manager/view/PropertyCompositeView.js
  78. 25
      src/style_manager/view/PropertyFileView.js
  79. 45
      src/style_manager/view/PropertyIntegerView.js
  80. 91
      src/style_manager/view/PropertyRadioView.js
  81. 45
      src/style_manager/view/PropertySelectView.js
  82. 59
      src/style_manager/view/PropertySliderView.js
  83. 331
      src/style_manager/view/PropertyStackView.js
  84. 155
      src/style_manager/view/PropertyView.js
  85. 24
      src/style_manager/view/SectorView.js
  86. 7
      src/style_manager/view/SectorsView.js
  87. 100
      src/styles/scss/_gjs_inputs.scss
  88. 41
      src/styles/scss/_gjs_rte.scss
  89. 54
      src/styles/scss/_gjs_style_manager.scss
  90. 102
      src/styles/scss/_gjs_variables.scss
  91. 91
      src/styles/scss/main.scss
  92. 4
      src/trait_manager/view/TraitSelectView.js
  93. 43
      src/trait_manager/view/TraitView.js
  94. 2321
      src/utils/ColorPicker.js
  95. 2
      src/utils/Dragger.js
  96. 2
      src/utils/Resizer.js
  97. 2
      src/utils/Sorter.js
  98. 309
      src/utils/extender.js
  99. 19
      src/utils/mixins.js
  100. 21
      test/helper.js

10
.editorconfig

@ -0,0 +1,10 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.js]
charset = utf-8
indent_style = space
indent_size = 2

4
.gitignore

@ -2,7 +2,8 @@
.settings/ .settings/
.sass-cache/ .sass-cache/
.project .project
npm-debug.log .idea
npm-debug.log*
style/.sass-cache/ style/.sass-cache/
img/ img/
@ -13,3 +14,4 @@ vendor/
coverage/ coverage/
node_modules/ node_modules/
bower_components/ bower_components/
grapesjs-*.tgz

3
.npmignore

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

26
README.md

@ -1,9 +1,15 @@
# [GrapesJS](http://grapesjs.com) # [GrapesJS](http://grapesjs.com)
[![Build Status](https://travis-ci.org/artf/grapesjs.svg?branch=master)](https://travis-ci.org/artf/grapesjs) [![Build Status](https://travis-ci.org/artf/grapesjs.svg?branch=master)](https://travis-ci.org/artf/grapesjs)
[![Chat](https://img.shields.io/badge/chat-discord-7289da.svg)](https://discord.gg/QAbgGXq)
<p align="center"><img src="http://grapesjs.com/img/grapesjs-front-page-m.jpg" alt="GrapesJS" width="500" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/grapesjs-front-page-m.jpg" alt="GrapesJS" width="500" align="center"/></p>
<br/>
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/nb9nPYf2XqCE1PbgcQj6gf4D/artf/grapesjs'>
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/nb9nPYf2XqCE1PbgcQj6gf4D/artf/grapesjs.svg' />
</a>
GrapesJS is a free and open source Web Builder Framework which helps you building HTML templates to be used inside sites, newsletters and mobile apps. GrapesJS is a free and open source Web Builder Framework which helps you building HTML templates to be used inside sites, newsletters and mobile apps.
Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of dynamic templates. To better understand this concept check the image below Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of dynamic templates. To better understand this concept check the image below
@ -32,7 +38,6 @@ Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
* [API](#api) * [API](#api)
* [Testing](#testing) * [Testing](#testing)
* [Plugins](#plugins) * [Plugins](#plugins)
* [Sponsors](#sponsors)
* [Support](#support) * [Support](#support)
* [License](#license) * [License](#license)
@ -106,13 +111,6 @@ $ npm run build
## Usage ## Usage
JQuery is the only hard dependency so you have to include it before using GrapesJS
```html
<script src="//code.jquery.com/jquery-3.2.1.min.js"></script>
```
After that include scripts from GrapesJS with all your configurations
```html ```html
<link rel="stylesheet" href="path/to/grapes.min.css"> <link rel="stylesheet" href="path/to/grapes.min.css">
<script src="path/to/grapes.min.js"></script> <script src="path/to/grapes.min.js"></script>
@ -199,16 +197,6 @@ Find out more about plugins here: [Creating plugins](https://github.com/artf/gra
## Sponsors
The project is sponsored by
[![Sendloop](http://grapesjs.com/img/sendloop-logo-l.png)](https://sendloop.com)
## Support ## Support
If you like the project support it with a donation of your choice or become a backer/sponsor via [Open Collective](https://opencollective.com/grapesjs) If you like the project support it with a donation of your choice or become a backer/sponsor via [Open Collective](https://opencollective.com/grapesjs)

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

15
index.html

@ -4,7 +4,6 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>GrapesJS</title> <title>GrapesJS</title>
<link rel="stylesheet" href="dist/css/grapes.min.css"> <link rel="stylesheet" href="dist/css/grapes.min.css">
<script src="node_modules/jquery/dist/jquery.min.js"></script>
<script src="dist/grapes.min.js"></script> <script src="dist/grapes.min.js"></script>
</head> </head>
@ -1037,7 +1036,7 @@
sectors: [{ sectors: [{
name: 'General', name: 'General',
open: false, open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'], buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom']
},{ },{
name: 'Dimension', name: 'Dimension',
open: false, open: false,
@ -1062,7 +1061,15 @@
},{ },{
name: 'Extra', name: 'Extra',
open: false, open: false,
buildProps: ['transition', 'perspective', 'transform'], buildProps: ['opacity', 'transition', 'perspective', 'transform'],
properties: [{
type: 'slider',
property: 'opacity',
defaults: 1,
step: 0.01,
max: 1,
min:0,
}]
},{ },{
name: 'Flex', name: 'Flex',
open: false, open: false,
@ -1324,7 +1331,7 @@
var model = view.model; var model = view.model;
let targetValue = view.getTargetValue({ignoreDefault: 1}); let targetValue = view.getTargetValue({ignoreDefault: 1});
let computedValue = view.getComputedValue(); let computedValue = view.getComputedValue();
let defaultValue = view.getDefaultValue(); let defaultValue = view.model.getDefaultValue();
//console.log('Style of ', model.get('property'), 'Target: ', targetValue, 'Computed:', computedValue, 'Default:', defaultValue); //console.log('Style of ', model.get('property'), 'Target: ', targetValue, 'Computed:', computedValue, 'Default:', defaultValue);
}); });

22
package.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Free and Open Source Web Builder Framework", "description": "Free and Open Source Web Builder Framework",
"version": "0.10.6", "version": "0.12.8",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",
@ -13,10 +13,10 @@
"dependencies": { "dependencies": {
"backbone": "^1.3.3", "backbone": "^1.3.3",
"backbone-undo": "^0.2.5", "backbone-undo": "^0.2.5",
"cash-dom": "^1.3.5",
"codemirror": "^5.21.0", "codemirror": "^5.21.0",
"codemirror-formatting": "^1.0.0", "codemirror-formatting": "^1.0.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"jquery": "^3.1.1",
"keymaster": "^1.6.2", "keymaster": "^1.6.2",
"promise-polyfill": "^6.0.2", "promise-polyfill": "^6.0.2",
"spectrum-colorpicker": "^1.8.0", "spectrum-colorpicker": "^1.8.0",
@ -37,22 +37,21 @@
"mocha": "^3.1.2", "mocha": "^3.1.2",
"node-sass": "^4.5.3", "node-sass": "^4.5.3",
"sinon": "^3.2.1", "sinon": "^3.2.1",
"string-replace-loader": "^1.3.0",
"webpack": "^3.5.5", "webpack": "^3.5.5",
"webpack-dev-server": "^2.7.1" "webpack-dev-server": "^2.7.1",
"whatwg-fetch": "^2.0.3"
}, },
"keywords": [ "keywords": [
"wte",
"grapes", "grapes",
"grapesjs", "grapesjs",
"web template editor",
"web site builder",
"web template builder",
"html website builder",
"site builder",
"newsletter builder",
"wysiwyg", "wysiwyg",
"web",
"template", "template",
"editor" "editor",
"newsletter",
"site",
"builder"
], ],
"babel": { "babel": {
"presets": [ "presets": [
@ -63,6 +62,7 @@
"scripts": { "scripts": {
"lint": "eslint src", "lint": "eslint src",
"build": "cross-env WEBPACK_ENV=prod && npm run lint && npm run test && npm run v:patch && webpack && npm run build:css", "build": "cross-env WEBPACK_ENV=prod && npm run lint && npm run test && npm run v:patch && webpack && npm run build:css",
"build-n": "cross-env WEBPACK_ENV=prod && npm run lint && npm run test && webpack && npm run build:css",
"build:css": "node-sass src/styles/scss/main.scss dist/css/grapes.min.css --output-style compressed", "build:css": "node-sass src/styles/scss/main.scss dist/css/grapes.min.css --output-style compressed",
"v:patch": "npm version --no-git-tag-version patch", "v:patch": "npm version --no-git-tag-version patch",
"start": "cross-env WEBPACK_ENV=dev webpack-dev-server --progress --colors & npm run build:css -- -w", "start": "cross-env WEBPACK_ENV=dev webpack-dev-server --progress --colors & npm run build:css -- -w",

3
src/asset_manager/view/AssetsView.js

@ -153,7 +153,8 @@ module.exports = Backbone.View.extend({
if (hide) { if (hide) {
assetsEl.empty(); assetsEl.empty();
} else { } else {
assetsEl.append(this.config.noAssets); const noAssets = this.config.noAssets;
noAssets && assetsEl.append(noAssets);
} }
}, },

2
src/asset_manager/view/FileUploader.js

@ -73,7 +73,7 @@ module.exports = Backbone.View.extend({
const em = this.config.em; const em = this.config.em;
const config = this.config; const config = this.config;
const target = this.target; const target = this.target;
const json = typeof text === 'text' ? JSON.parse(text) : text; const json = typeof text === 'string' ? JSON.parse(text) : text;
em && em.trigger('asset:upload:response', json); em && em.trigger('asset:upload:response', json);
if (config.autoAdd && target) { if (config.autoAdd && target) {

115
src/block_manager/index.js

@ -2,7 +2,9 @@
* * [add](#add) * * [add](#add)
* * [get](#get) * * [get](#get)
* * [getAll](#getall) * * [getAll](#getall)
* * [getAllVisible](#getallvisible)
* * [getCategories](#getcategories) * * [getCategories](#getcategories)
* * [getContainer](#getcontainer)
* * [render](#render) * * [render](#render)
* *
* Block manager helps managing various, draggable, piece of contents that could be easily reused inside templates. * Block manager helps managing various, draggable, piece of contents that could be easily reused inside templates.
@ -32,7 +34,7 @@ module.exports = () => {
Blocks = require('./model/Blocks'), Blocks = require('./model/Blocks'),
BlockCategories = require('./model/Categories'), BlockCategories = require('./model/Categories'),
BlocksView = require('./view/BlocksView'); BlocksView = require('./view/BlocksView');
var blocks, view; var blocks, blocksVisible, blocksView;
var categories = []; var categories = [];
return { return {
@ -52,19 +54,58 @@ module.exports = () => {
*/ */
init(config) { init(config) {
c = config || {}; c = config || {};
for (var name in defaults) { const em = c.em;
if (!(name in c))
for (let name in defaults) {
if (!(name in c)) {
c[name] = defaults[name]; c[name] = defaults[name];
}
} }
blocks = new Blocks(c.blocks);
// Global blocks collection
blocks = new Blocks([]);
blocksVisible = new Blocks([]);
categories = new BlockCategories(), categories = new BlockCategories(),
view = new BlocksView({ blocksView = new BlocksView({
collection: blocks, // Visible collection
collection: blocksVisible,
categories, categories,
}, c); }, c);
// Setup the sync between the global and public collections
blocks.listenTo(blocks, 'add', model => {
blocksVisible.add(model);
em && em.trigger('block:add', model);
});
blocks.listenTo(blocks, 'remove', model => {
blocksVisible.remove(model);
em && em.trigger('block:remove', model);
});
blocks.listenTo(blocks, 'reset', coll => {
blocksVisible.reset(coll.models);
});
return this; return this;
}, },
/**
* Get configuration object
* @return {Object}
*/
getConfig() {
return c;
},
/**
* Load default blocks if the collection is empty
*/
onLoad() {
const blocks = this.getAll();
!blocks.length && blocks.reset(c.blocks);
},
/** /**
* Add new block to the collection. * Add new block to the collection.
* @param {string} id Block id * @param {string} id Block id
@ -98,7 +139,7 @@ module.exports = () => {
* Return the block by id * Return the block by id
* @param {string} id Block id * @param {string} id Block id
* @example * @example
* var block = blockManager.get('h1-block'); * const block = blockManager.get('h1-block');
* console.log(JSON.stringify(block)); * console.log(JSON.stringify(block));
* // {label: 'Heading', content: '<h1>Put your ...', ...} * // {label: 'Heading', content: '<h1>Put your ...', ...}
*/ */
@ -110,7 +151,7 @@ module.exports = () => {
* Return all blocks * Return all blocks
* @return {Collection} * @return {Collection}
* @example * @example
* var blocks = blockManager.getAll(); * const blocks = blockManager.getAll();
* console.log(JSON.stringify(blocks)); * console.log(JSON.stringify(blocks));
* // [{label: 'Heading', content: '<h1>Put your ...'}, ...] * // [{label: 'Heading', content: '<h1>Put your ...'}, ...]
*/ */
@ -118,9 +159,26 @@ module.exports = () => {
return blocks; return blocks;
}, },
/**
* Return the visible collection, which containes blocks actually rendered
* @return {Collection}
*/
getAllVisible() {
return blocksVisible;
},
/**
* Remove a block by id
* @param {string} id Block id
* @return {Block} Removed block
*/
remove(id) {
return blocks.remove(id);
},
/** /**
* Get all available categories. * Get all available categories.
* Is possible to add categories only with blocks via 'add()' method * It's possible to add categories only within blocks via 'add()' method
* @return {Array|Collection} * @return {Array|Collection}
*/ */
getCategories() { getCategories() {
@ -128,20 +186,43 @@ module.exports = () => {
}, },
/** /**
* Render blocks * Return the Blocks container element
* @return {HTMLElement} * @return {HTMLElement}
*/ */
render() { getContainer() {
return view.render().el; return blocksView.el;
}, },
/** /**
* Remove block by id * Render blocks
* @param {string} id Block id * @param {Array} blocks Blocks to render, without the argument will render
* @return {Block} Removed block * all global blocks
* @example
* // Render all blocks (inside the global collection)
* blockManager.render();
*
* // Render new set of blocks
* const blocks = blockManager.getAll();
* blockManager.render(blocks.filter(
* block => block.get('category') == 'sections'
* ));
* // Or a new set from an array
* blockManager.render([
* {label: 'Label text', content: '<div>Content</div>'}
* ]);
*
* // Back to blocks from the global collection
* blockManager.render();
*/ */
remove(id) { render(blocks) {
return blocks.remove(id); const toRender = blocks || this.getAll().models;
if (!blocksView.rendered) {
blocksView.render();
blocksView.rendered = 1;
}
blocksView.collection.reset(toRender);
}, },
}; };

2
src/block_manager/view/BlockView.js

@ -1,4 +1,4 @@
var Backbone = require('backbone'); const $ = Backbone.$;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({

12
src/block_manager/view/BlocksView.js

@ -14,7 +14,9 @@ module.exports = Backbone.View.extend({
this.noCatClass = `${ppfx}blocks-no-cat`; this.noCatClass = `${ppfx}blocks-no-cat`;
this.blockContClass = `${ppfx}blocks-c`; this.blockContClass = `${ppfx}blocks-c`;
this.catsClass = `${ppfx}block-categories`; this.catsClass = `${ppfx}block-categories`;
this.listenTo(this.collection, 'add', this.addTo); const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.render);
this.em = this.config.em; this.em = this.config.em;
this.tac = 'test-tac'; this.tac = 'test-tac';
this.grabbingCls = this.ppfx + 'grabbing'; this.grabbingCls = this.ppfx + 'grabbing';
@ -167,8 +169,7 @@ module.exports = Backbone.View.extend({
}, },
render() { render() {
var ppfx = this.ppfx; const frag = document.createDocumentFragment();
var frag = document.createDocumentFragment();
this.catsEl = null; this.catsEl = null;
this.blocksEl = null; this.blocksEl = null;
this.renderedCategories = []; this.renderedCategories = [];
@ -179,10 +180,7 @@ module.exports = Backbone.View.extend({
</div> </div>
`; `;
this.collection.each(function(model){ this.collection.each(model => this.add(model, frag));
this.add(model, frag);
}, this);
this.append(frag); this.append(frag);
this.$el.addClass(this.blockContClass + 's') this.$el.addClass(this.blockContClass + 's')
return this; return this;

19
src/canvas/index.js

@ -1,3 +1,5 @@
import {on, off} from 'utils/mixins'
module.exports = () => { module.exports = () => {
var c = {}, var c = {},
defaults = require('./config/config'), defaults = require('./config/config'),
@ -104,7 +106,7 @@ module.exports = () => {
}, },
/** /**
* Returns element containing canvas tools * Returns element containing all canvas tools
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getToolsEl() { getToolsEl() {
@ -353,8 +355,8 @@ module.exports = () => {
this.dragging = 1; this.dragging = 1;
let toListen = this.getScrollListeners(); let toListen = this.getScrollListeners();
frameRect = CanvasView.getFrameOffset(1); frameRect = CanvasView.getFrameOffset(1);
toListen.on('mousemove', this.autoscroll); on(toListen, 'mousemove', this.autoscroll);
toListen.on('mouseup', this.stopAutoscroll); on(toListen, 'mouseup', this.stopAutoscroll);
}, },
autoscroll(e) { autoscroll(e) {
@ -386,17 +388,12 @@ module.exports = () => {
stopAutoscroll() { stopAutoscroll() {
this.dragging = 0; this.dragging = 0;
let toListen = this.getScrollListeners(); let toListen = this.getScrollListeners();
toListen.off('mousemove', this.autoscroll); off(toListen, 'mousemove', this.autoscroll);
toListen.off('mouseup', this.stopAutoscroll); off(toListen, 'mouseup', this.stopAutoscroll);
}, },
getScrollListeners() { getScrollListeners() {
if (!this.scrollListeners) { return [this.getFrameEl().contentWindow, this.getElement()];
this.scrollListeners =
$(this.getFrameEl().contentWindow, this.getElement());
}
return this.scrollListeners;
}, },
/** /**

64
src/canvas/view/CanvasView.js

@ -1,5 +1,5 @@
var Backbone = require('backbone'); const FrameView = require('./FrameView');
var FrameView = require('./FrameView'); const $ = Backbone.$;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
@ -311,13 +311,14 @@ module.exports = Backbone.View.extend({
view.scriptContainer.html(''); view.scriptContainer.html('');
// In editor, I make use of setTimeout as during the append process of elements // In editor, I make use of setTimeout as during the append process of elements
// those will not be available immediatly, therefore 'item' variable // those will not be available immediatly, therefore 'item' variable
view.scriptContainer.append(`<script> const script = document.createElement('script');
script.innerText = `
setTimeout(function() { setTimeout(function() {
var item = document.getElementById('${id}'); var item = document.getElementById('${id}');
if (!item) return; if (!item) return;
(function(){${model.getScriptString()}}.bind(item))() (function(){${model.getScriptString()}}.bind(item))()
}, 1); }, 1);`;
</script>`); view.scriptContainer.get(0).appendChild(script);
}, },
/** /**
@ -326,7 +327,7 @@ module.exports = Backbone.View.extend({
*/ */
getJsContainer() { getJsContainer() {
if (!this.jsContainer) { if (!this.jsContainer) {
this.jsContainer = $('<div>', {class: this.ppfx + 'js-cont'}).get(0); this.jsContainer = $(`<div class="${this.ppfx}js-cont">`).get(0);
} }
return this.jsContainer; return this.jsContainer;
}, },
@ -346,33 +347,32 @@ module.exports = Backbone.View.extend({
} }
} }
var ppfx = this.ppfx; var ppfx = this.ppfx;
var toolsEl = $('<div>', { id: ppfx + 'tools' }).get(0); this.$el.append(`
this.hlEl = $('<div>', { class: ppfx + 'highlighter' }).get(0); <div id="${ppfx}tools" style="pointer-events:none">
this.badgeEl = $('<div>', {class: ppfx + 'badge'}).get(0); <div class="${ppfx}highlighter"></div>
this.placerEl = $('<div>', {class: ppfx + 'placeholder'}).get(0); <div class="${ppfx}badge"></div>
this.placerIntEl = $('<div>', {class: ppfx + 'placeholder-int'}).get(0); <div class="${ppfx}placeholder">
this.ghostEl = $('<div>', {class: ppfx + 'ghost'}).get(0); <div class="${ppfx}placeholder-int"></div>
this.toolbarEl = $('<div>', {class: ppfx + 'toolbar'}).get(0); </div>
this.resizerEl = $('<div>', {class: ppfx + 'resizer'}).get(0); <div class="${ppfx}ghost"></div>
this.offsetEl = $('<div>', {class: ppfx + 'offset-v'}).get(0); <div class="${ppfx}toolbar" style="pointer-events:all"></div>
this.fixedOffsetEl = $('<div>', {class: ppfx + 'offset-fixed-v'}).get(0); <div class="${ppfx}resizer"></div>
this.placerEl.appendChild(this.placerIntEl); <div class="${ppfx}offset-v"></div>
toolsEl.appendChild(this.hlEl); <div class="${ppfx}offset-fixed-v"></div>
toolsEl.appendChild(this.badgeEl); </div>
toolsEl.appendChild(this.placerEl); `);
toolsEl.appendChild(this.ghostEl); const el = this.el;
toolsEl.appendChild(this.toolbarEl); const toolsEl = el.querySelector(`#${ppfx}tools`);
toolsEl.appendChild(this.resizerEl); this.hlEl = el.querySelector(`.${ppfx}highlighter`);
toolsEl.appendChild(this.offsetEl); this.badgeEl = el.querySelector(`.${ppfx}badge`);
toolsEl.appendChild(this.fixedOffsetEl); this.placerEl = el.querySelector(`.${ppfx}placeholder`);
this.$el.append(toolsEl); this.ghostEl = el.querySelector(`.${ppfx}ghost`);
var rte = this.em.get('rte'); this.toolbarEl = el.querySelector(`.${ppfx}toolbar`);
this.resizerEl = el.querySelector(`.${ppfx}resizer`);
if(rte) this.offsetEl = el.querySelector(`.${ppfx}offset-v`);
toolsEl.appendChild(rte.render()); this.fixedOffsetEl = el.querySelector(`.${ppfx}offset-fixed-v`);
this.toolsEl = toolsEl; this.toolsEl = toolsEl;
this.$el.attr({class: this.className}); this.el.className = this.className;
return this; return this;
}, },

2
src/code_manager/view/EditorView.js

@ -18,7 +18,7 @@ module.exports = Backbone.View.extend({
obj.pfx = this.pfx; obj.pfx = this.pfx;
this.$el.html( this.template(obj) ); this.$el.html( this.template(obj) );
this.$el.attr('class', this.pfx + 'editor-c'); this.$el.attr('class', this.pfx + 'editor-c');
this.$el.find('#'+this.pfx+'code').html(this.model.get('input')); this.$el.find('#'+this.pfx+'code').append(this.model.get('input'));
return this; return this;
}, },

15
src/commands/index.js

@ -100,6 +100,7 @@ module.exports = () => {
defaultCommands['open-blocks'] = require('./view/OpenBlocks'); defaultCommands['open-blocks'] = require('./view/OpenBlocks');
defaultCommands['open-assets'] = require('./view/OpenAssets'); defaultCommands['open-assets'] = require('./view/OpenAssets');
defaultCommands['show-offset'] = require('./view/ShowOffset'); defaultCommands['show-offset'] = require('./view/ShowOffset');
defaultCommands['select-parent'] = require('./view/SelectParent');
defaultCommands.fullscreen = require('./view/Fullscreen'); defaultCommands.fullscreen = require('./view/Fullscreen');
defaultCommands.preview = require('./view/Preview'); defaultCommands.preview = require('./view/Preview');
defaultCommands.resize = require('./view/Resize'); defaultCommands.resize = require('./view/Resize');
@ -116,8 +117,6 @@ module.exports = () => {
ed.select(null); ed.select(null);
sel.destroy(); sel.destroy();
ed.trigger('change:canvasOffset');
//ed.refresh();//change:canvasOffset
}, },
}; };
@ -197,15 +196,9 @@ module.exports = () => {
if(c.em) if(c.em)
c.model = c.em.get('Canvas'); c.model = c.em.get('Canvas');
return this; this.loadDefaultCommands()
},
/** return this;
* On load callback
* @private
*/
onLoad() {
this.loadDefaultCommands();
}, },
/** /**
@ -259,7 +252,7 @@ module.exports = () => {
* */ * */
loadDefaultCommands() { loadDefaultCommands() {
for (var id in defaultCommands) { for (var id in defaultCommands) {
this.add(id, defaultCommands[id]); this.add(id, defaultCommands[id]);
} }
return this; return this;

188
src/commands/view/CommandAbstract.js

@ -1,78 +1,78 @@
var Backbone = require('backbone'); const $ = Backbone.$
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
/** /**
* Initialize method that can't be removed * Initialize method that can't be removed
* @param {Object} o Options * @param {Object} o Options
* @private * @private
* */ * */
initialize(o) { initialize(o) {
this.config = o || {}; this.config = o || {};
this.editorModel = this.em = this.config.em || {}; this.editorModel = this.em = this.config.em || {};
this.pfx = this.config.stylePrefix; this.pfx = this.config.stylePrefix;
this.ppfx = this.config.pStylePrefix; this.ppfx = this.config.pStylePrefix;
this.hoverClass = this.pfx + 'hover'; this.hoverClass = this.pfx + 'hover';
this.badgeClass = this.pfx + 'badge'; this.badgeClass = this.pfx + 'badge';
this.plhClass = this.pfx + 'placeholder'; this.plhClass = this.pfx + 'placeholder';
this.freezClass = this.ppfx + 'freezed'; this.freezClass = this.ppfx + 'freezed';
this.canvas = this.em.get && this.em.get('Canvas'); this.canvas = this.em.get && this.em.get('Canvas');
if(this.em.get) if(this.em.get)
this.setElement(this.getCanvas()); this.setElement(this.getCanvas());
if(this.canvas){ if(this.canvas){
this.$canvas = this.$el; this.$canvas = this.$el;
this.$wrapper = $(this.getCanvasWrapper()); this.$wrapper = $(this.getCanvasWrapper());
this.frameEl = this.canvas.getFrameEl(); this.frameEl = this.canvas.getFrameEl();
this.canvasTool = this.getCanvasTools(); this.canvasTool = this.getCanvasTools();
this.bodyEl = this.getCanvasBody(); this.bodyEl = this.getCanvasBody();
} }
this.init(this.config); this.init(this.config);
}, },
/** /**
* On frame scroll callback * On frame scroll callback
* @param {[type]} e [description] * @param {[type]} e [description]
* @return {[type]} [description] * @return {[type]} [description]
*/ */
onFrameScroll(e) {}, onFrameScroll(e) {},
/** /**
* Returns canval element * Returns canval element
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getCanvas() { getCanvas() {
return this.canvas.getElement(); return this.canvas.getElement();
}, },
/** /**
* Get canvas body element * Get canvas body element
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getCanvasBody() { getCanvasBody() {
return this.canvas.getBody(); return this.canvas.getBody();
}, },
/** /**
* Get canvas wrapper element * Get canvas wrapper element
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getCanvasWrapper() { getCanvasWrapper() {
return this.canvas.getWrapperEl(); return this.canvas.getWrapperEl();
}, },
/** /**
* Get canvas wrapper element * Get canvas wrapper element
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getCanvasTools() { getCanvasTools() {
return this.canvas.getToolsEl(); return this.canvas.getToolsEl();
}, },
/** /**
* Get the offset of the element * Get the offset of the element
* @param {HTMLElement} el * @param {HTMLElement} el
* @return {Object} * @return {Object}
@ -85,27 +85,27 @@ module.exports = Backbone.View.extend({
}; };
}, },
/** /**
* Callback triggered after initialize * Callback triggered after initialize
* @param {Object} o Options * @param {Object} o Options
* @private * @private
* */ * */
init(o) {}, init(o) {},
/** /**
* Method that run command * Method that run command
* @param {Object} em Editor model * @param {Object} em Editor model
* @param {Object} sender Button sender * @param {Object} sender Button sender
* @private * @private
* */ * */
run(em, sender) {}, run(em, sender) {},
/** /**
* Method that stop command * Method that stop command
* @param {Object} em Editor model * @param {Object} em Editor model
* @param {Object} sender Button sender * @param {Object} sender Button sender
* @private * @private
* */ * */
stop(em, sender) {}, stop(em, sender) {},
}); });

4
src/commands/view/CreateComponent.js

@ -1,5 +1,5 @@
var Backbone = require('backbone'); const SelectPosition = require('./SelectPosition');
var SelectPosition = require('./SelectPosition'); const $ = Backbone.$;
module.exports = _.extend({}, SelectPosition, { module.exports = _.extend({}, SelectPosition, {

4
src/commands/view/DeleteComponent.js

@ -1,5 +1,5 @@
var Backbone = require('backbone'); const SelectComponent = require('./SelectComponent');
var SelectComponent = require('./SelectComponent'); const $ = Backbone.$;
module.exports = _.extend({},SelectComponent,{ module.exports = _.extend({},SelectComponent,{

4
src/commands/view/ExportTemplate.js

@ -1,3 +1,5 @@
const $ = Backbone.$;
module.exports = { module.exports = {
run(editor, sender) { run(editor, sender) {
@ -50,7 +52,7 @@ module.exports = {
var oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS'); var oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS');
this.htmlEditor = oHtmlEd.el; this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el; this.cssEditor = oCsslEd.el;
this.$editors = $('<div>', {class: this.pfx + 'export-dl'}); this.$editors = $(`<div class="${this.pfx}export-dl">`);
this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el); this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el);
} }

16
src/commands/view/MoveComponent.js

@ -1,6 +1,8 @@
var Backbone = require('backbone'); import {on, off} from 'utils/mixins'
var SelectComponent = require('./SelectComponent');
var SelectPosition = require('./SelectPosition'); const SelectComponent = require('./SelectComponent');
const SelectPosition = require('./SelectPosition');
const $ = Backbone.$;
module.exports = _.extend({}, SelectPosition, SelectComponent, { module.exports = _.extend({}, SelectPosition, SelectComponent, {
@ -49,7 +51,7 @@ module.exports = _.extend({}, SelectPosition, SelectComponent, {
this.sorter.onEndMove = this.onEndMove.bind(this); this.sorter.onEndMove = this.onEndMove.bind(this);
this.stopSelectComponent(); this.stopSelectComponent();
this.$wrapper.off('mousedown', this.initSorter); this.$wrapper.off('mousedown', this.initSorter);
this.getContentWindow().on('keydown', this.rollback); on(this.getContentWindow(), 'keydown', this.rollback);
}, },
/** /**
@ -77,11 +79,11 @@ module.exports = _.extend({}, SelectPosition, SelectComponent, {
*/ */
this.stopSelectComponent(); this.stopSelectComponent();
this.getContentWindow().on('keydown', this.rollback); on(this.getContentWindow(), 'keydown', this.rollback);
}, },
onEndMoveFromModel() { onEndMoveFromModel() {
this.getContentWindow().off('keydown', this.rollback); off(this.getContentWindow(), 'keydown', this.rollback);
}, },
/** /**
@ -90,7 +92,7 @@ module.exports = _.extend({}, SelectPosition, SelectComponent, {
*/ */
onEndMove() { onEndMove() {
this.enable(); this.enable();
this.getContentWindow().off('keydown', this.rollback); off(this.getContentWindow(), 'keydown', this.rollback);
}, },
/** /**

28
src/commands/view/OpenBlocks.js

@ -1,26 +1,24 @@
module.exports = { module.exports = {
run(editor, sender) { run(editor, sender) {
var config = editor.Config; const bm = editor.BlockManager;
var pfx = config.stylePrefix; const pn = editor.Panels;
var bm = editor.BlockManager;
var panelC; if (!this.blocks) {
if(!this.blocks){ bm.render();
this.blocks = $('<div/>').get(0); const id = 'views-container';
this.blocks.appendChild(bm.render()); const blocks = document.createElement('div');
var panels = editor.Panels; const panels = pn.getPanel(id) || pn.addPanel({id});
if(!panels.getPanel('views-container')) blocks.appendChild(bm.getContainer());
panelC = panels.addPanel({id: 'views-container'}); panels.set('appendContent', blocks).trigger('change:appendContent');
else this.blocks = blocks;
panelC = panels.getPanel('views-container');
panelC.set('appendContent', this.blocks).trigger('change:appendContent');
} }
this.blocks.style.display = 'block'; this.blocks.style.display = 'block';
}, },
stop() { stop() {
if(this.blocks) const blocks = this.blocks;
this.blocks.style.display = 'none'; blocks && (blocks.style.display = 'none');
} }
}; };

3
src/commands/view/OpenLayers.js

@ -1,4 +1,5 @@
var Layers = require('navigator'); const Layers = require('navigator');
const $ = Backbone.$;
module.exports = { module.exports = {

17
src/commands/view/OpenStyleManager.js

@ -1,16 +1,18 @@
var StyleManager = require('style_manager'); const StyleManager = require('style_manager');
const Backbone = require('backbone');
const $ = Backbone.$;
module.exports = { module.exports = {
run(em, sender) { run(em, sender) {
this.sender = sender; this.sender = sender;
if(!this.$cn){ if (!this.$cn) {
var config = em.getConfig(), var config = em.getConfig(),
panels = em.Panels; panels = em.Panels;
// Main container // Main container
this.$cn = $('<div/>'); this.$cn = $('<div></div>');
// Secondary container // Secondary container
this.$cn2 = $('<div/>'); this.$cn2 = $('<div></div>');
this.$cn.append(this.$cn2); this.$cn.append(this.$cn2);
// Device Manager // Device Manager
@ -27,12 +29,9 @@ module.exports = {
this.$cn2.append(em.StyleManager.render()); this.$cn2.append(em.StyleManager.render());
var smConfig = em.StyleManager.getConfig(); var smConfig = em.StyleManager.getConfig();
const pfx = smConfig.stylePrefix;
// Create header // Create header
this.$header = $('<div>', { this.$header = $(`<div class="${pfx}header">${smConfig.textNoElement}</div>`);
class: smConfig.stylePrefix + 'header',
text: smConfig.textNoElement,
});
//this.$cn = this.$cn.add(this.$header);
this.$cn.append(this.$header); this.$cn.append(this.$header);
// Create panel if not exists // Create panel if not exists

4
src/commands/view/OpenTraitManager.js

@ -1,3 +1,5 @@
const $ = Backbone.$;
module.exports = { module.exports = {
run(editor, sender) { run(editor, sender) {
@ -8,7 +10,7 @@ module.exports = {
if(!this.obj){ if(!this.obj){
var tmView = tm.getTraitsViewer(); var tmView = tm.getTraitsViewer();
var confTm = tm.getConfig(); var confTm = tm.getConfig();
this.obj = $('<div/>') this.obj = $('<div></div>')
.append('<div class="'+pfx+'traits-label">' + confTm.labelContainer + '</div>') .append('<div class="'+pfx+'traits-label">' + confTm.labelContainer + '</div>')
.get(0); .get(0);
this.obj.appendChild(tmView.render().el); this.obj.appendChild(tmView.render().el);

53
src/commands/view/SelectComponent.js

@ -1,7 +1,11 @@
var ToolbarView = require('dom_components/view/ToolbarView'); import {on, off} from 'utils/mixins'
var Toolbar = require('dom_components/model/Toolbar');
var key = require('keymaster'); const ToolbarView = require('dom_components/view/ToolbarView');
const Toolbar = require('dom_components/model/Toolbar');
const key = require('keymaster');
const Backbone = require('backbone');
let showOffsets; let showOffsets;
const $ = Backbone.$;
module.exports = { module.exports = {
@ -65,16 +69,6 @@ module.exports = {
} }
}, },
/**
* Returns canavs body el
*/
getCanvasBodyEl() {
if(!this.$bodyEl) {
this.$bodyEl = $(this.getCanvasBody());
}
return this.$bodyEl;
},
/** /**
* Start select component event * Start select component event
* @private * @private
@ -96,16 +90,15 @@ module.exports = {
* @private * @private
* */ * */
toggleSelectComponent(enable) { toggleSelectComponent(enable) {
var el = '*'; const method = enable ? 'on' : 'off';
var method = enable ? 'on' : 'off'; const methods = {on, off};
this.getCanvasBodyEl() const body = this.getCanvasBody();
[method]('mouseover', el, this.onHover) const win = this.getContentWindow();
[method]('mouseout', el, this.onOut) methods[method](body, 'mouseover', this.onHover);
[method]('click', el, this.onClick); methods[method](body, 'mouseout', this.onOut);
methods[method](body, 'click', this.onClick);
var cw = this.getContentWindow(); methods[method](win, 'scroll', this.onFrameScroll);
cw[method]('scroll', this.onFrameScroll); methods[method](win, 'keydown', this.onKeyPress);
cw[method]('keydown', this.onKeyPress);
}, },
/** /**
@ -144,7 +137,7 @@ module.exports = {
var trg = e.target; var trg = e.target;
// Adjust tools scroll top // Adjust tools scroll top
if(!this.adjScroll){ if (!this.adjScroll) {
this.adjScroll = 1; this.adjScroll = 1;
this.onFrameScroll(e); this.onFrameScroll(e);
this.updateAttached(); this.updateAttached();
@ -282,6 +275,7 @@ module.exports = {
updateHighlighter(el, pos) { updateHighlighter(el, pos) {
var $el = $(el); var $el = $(el);
var model = $el.data('model'); var model = $el.data('model');
if(!model || (model && model.get('status') == 'selected')) { if(!model || (model && model.get('status') == 'selected')) {
return; return;
} }
@ -333,12 +327,7 @@ module.exports = {
var modelToStyle; var modelToStyle;
var toggleBodyClass = (method, e, opts) => { var toggleBodyClass = (method, e, opts) => {
var handlerAttr = e.target.getAttribute(attrName); opts.docs && opts.docs.find('body')[method](resizeClass);
var resizeHndClass = pfx + 'resizing-' + handlerAttr;
var classToAdd = resizeClass;// + ' ' +resizeHndClass;
if (opts.docs) {
opts.docs.find('body')[method](classToAdd);
}
}; };
@ -553,9 +542,7 @@ module.exports = {
* @private * @private
*/ */
getContentWindow() { getContentWindow() {
if(!this.contWindow) return this.frameEl.contentWindow;
this.contWindow = $(this.frameEl.contentWindow);
return this.contWindow;
}, },
run(editor) { run(editor) {

9
src/commands/view/SelectParent.js

@ -0,0 +1,9 @@
module.exports = {
run(editor) {
const comp = editor.getSelected();
const coll = comp && comp.collection;
coll && coll.parent && editor.select(coll.parent);
}
};

2
src/commands/view/SelectPosition.js

@ -1,3 +1,5 @@
const $ = Backbone.$;
module.exports = { module.exports = {
/** /**

24
src/commands/view/ShowOffset.js

@ -1,3 +1,5 @@
const $ = Backbone.$;
module.exports = { module.exports = {
getOffsetMethod(state) { getOffsetMethod(state) {
@ -38,18 +40,20 @@ module.exports = {
var stateLow = state.toLowerCase(); var stateLow = state.toLowerCase();
var marginName = stateLow + 'margin-v'; var marginName = stateLow + 'margin-v';
var paddingName = stateLow + 'padding-v'; var paddingName = stateLow + 'padding-v';
var marginV = $('<div>', {class: ppfx + marginName}).get(0); var marginV = $(`<div class="${ppfx}marginName">`).get(0);
var paddingV = $('<div>', {class: ppfx + paddingName}).get(0); var paddingV = $(`<div class="${ppfx}paddingName">`).get(0);
var marginEls = ppfx + marginName + '-el'; var marginEls = ppfx + marginName + '-el';
var paddingEls = ppfx + paddingName + '-el'; var paddingEls = ppfx + paddingName + '-el';
marginT = $('<div>', {class: ppfx + marginName + '-top ' + marginEls}).get(0); const fullMargName = `${marginEls} ${ppfx + marginName}`;
marginB = $('<div>', {class: ppfx + marginName + '-bottom ' + marginEls}).get(0); const fullPadName = `${paddingEls} ${ppfx + paddingName}`;
marginL = $('<div>', {class: ppfx + marginName + '-left ' + marginEls}).get(0); marginT = $(`<div class="${fullMargName}-top"></div>`).get(0);
marginR = $('<div>', {class: ppfx + marginName + '-right ' + marginEls}).get(0); marginB = $(`<div class="${fullMargName}-bottom"></div>`).get(0);
padT = $('<div>', {class: ppfx + paddingName + '-top ' + paddingEls}).get(0); marginL = $(`<div class="${fullMargName}-left"></div>`).get(0);
padB = $('<div>', {class: ppfx + paddingName + '-bottom ' + paddingEls}).get(0); marginR = $(`<div class="${fullMargName}-right"></div>`).get(0);
padL = $('<div>', {class: ppfx + paddingName + '-left ' + paddingEls}).get(0); padT = $(`<div class="${fullPadName}-top"></div>`).get(0);
padR = $('<div>', {class: ppfx + paddingName + '-right ' + paddingEls}).get(0); padB = $(`<div class="${fullPadName}-bottom"></div>`).get(0);
padL = $(`<div class="${fullPadName}-left"></div>`).get(0);
padR = $(`<div class="${fullPadName}-right"></div>`).get(0);
this['marginT' + state] = marginT; this['marginT' + state] = marginT;
this['marginB' + state] = marginB; this['marginB' + state] = marginB;
this['marginL' + state] = marginL; this['marginL' + state] = marginL;

1
src/dom_components/index.js

@ -162,7 +162,6 @@ module.exports = () => {
// Load dependencies // Load dependencies
if (em) { if (em) {
c.rte = em.get('rte') || '';
c.modal = em.get('Modal') || ''; c.modal = em.get('Modal') || '';
c.am = em.get('AssetManager') || ''; c.am = em.get('AssetManager') || '';
em.get('Parser').compTypes = componentTypes; em.get('Parser').compTypes = componentTypes;

6
src/dom_components/model/Component.js

@ -190,6 +190,12 @@ module.exports = Backbone.Model.extend(Styleable).extend({
var model = this; var model = this;
if(!model.get('toolbar')) { if(!model.get('toolbar')) {
var tb = []; var tb = [];
if(model.collection) {
tb.push({
attributes: {class: 'fa fa-arrow-up'},
command: 'select-parent',
});
}
if(model.get('draggable')) { if(model.get('draggable')) {
tb.push({ tb.push({
attributes: {class: 'fa fa-arrows'}, attributes: {class: 'fa fa-arrows'},

112
src/dom_components/view/ComponentTextView.js

@ -1,67 +1,96 @@
var Backbone = require('backbone'); import {on, off} from 'utils/mixins'
var ComponentView = require('./ComponentView');
const ComponentView = require('./ComponentView');
module.exports = ComponentView.extend({ module.exports = ComponentView.extend({
events: { events: {
'dblclick': 'enableEditing', 'dblclick': 'enableEditing',
'change': 'parseRender',
}, },
initialize(o) { initialize(o) {
ComponentView.prototype.initialize.apply(this, arguments); ComponentView.prototype.initialize.apply(this, arguments);
_.bindAll(this,'disableEditing'); this.disableEditing = this.disableEditing.bind(this);
const model = this.model; const model = this.model;
const em = this.em;
this.listenTo(model, 'focus active', this.enableEditing); this.listenTo(model, 'focus active', this.enableEditing);
this.listenTo(model, 'change:content', this.updateContent); this.listenTo(model, 'change:content', this.updateContent);
this.rte = this.config.rte || ''; this.rte = em && em.get('RichTextEditor');
this.activeRte = null;
this.em = this.config.em;
}, },
/** /**
* Enable the component to be editable * Enable element content editing
* @param {Event} e
* @private * @private
* */ * */
enableEditing(e) { enableEditing() {
var editable = this.model.get('editable'); if (this.rteEnabled) {
if(this.rte && editable) { return;
}
const editable = this.model.get('editable');
const rte = this.rte;
if (rte && editable) {
try { try {
this.activeRte = this.rte.attach(this, this.activeRte); this.activeRte = rte.enable(this, this.activeRte);
this.rte.focus(this, this.activeRte);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
} }
this.rteEnabled = 1;
this.toggleEvents(1); this.toggleEvents(1);
}, },
/** /**
* Disable this component to be editable * Disable element content editing
* @param {Event}
* @private * @private
* */ * */
disableEditing(e) { disableEditing() {
var model = this.model; const model = this.model;
var editable = model.get('editable'); const editable = model.get('editable');
const rte = this.rte;
if(this.rte && editable) { if (rte && editable) {
try { try {
this.rte.detach(this, this.activeRte); rte.disable(this, this.activeRte);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
var el = this.getChildrenContainer();
// Avoid double content by removing its children components
model.get('components').reset();
model.set('content', el.innerHTML);
}
if(!this.rte.customRte && editable) { const content = this.getChildrenContainer().innerHTML;
this.parseRender(); const comps = model.get('components');
comps.length && comps.reset();
// If there is a custom RTE the content is just baked staticly
// inside 'content'
if (rte.customRte) {
// Avoid double content by removing its children components
// and force to trigger change
model.set('content', '')
.set('content', content);
} else {
const clean = model => {
model.set({
editable: 0,
highlightable: 0,
removable: 0,
draggable: 0,
copyable: 0,
toolbar: '',
})
model.get('components').each(model => clean(model));
}
// Avoid re-render on reset with silent option
model.set('content', '');
comps.add(content);
comps.each(model => clean(model));
comps.trigger('resetNavigator');
}
} }
this.rteEnabled = 0;
this.toggleEvents(); this.toggleEvents();
}, },
@ -74,37 +103,18 @@ module.exports = ComponentView.extend({
e.stopPropagation(); e.stopPropagation();
}, },
/**
* Parse content and re-render it
* @private
*/
parseRender() {
var el = this.getChildrenContainer();
var comps = this.model.get('components');
var opts = {silent: true};
// Avoid re-render on reset with silent option
comps.reset(null, opts);
comps.add(el.innerHTML, opts);
this.model.set('content', '');
this.render();
// As the reset was in silent mode I need to notify
// the navigator about the change
comps.trigger('resetNavigator');
},
/** /**
* Enable/Disable events * Enable/Disable events
* @param {Boolean} enable * @param {Boolean} enable
*/ */
toggleEvents(enable) { toggleEvents(enable) {
var method = enable ? 'on' : 'off'; var method = enable ? 'on' : 'off';
const mixins = {on, off};
// The ownerDocument is from the frame // The ownerDocument is from the frame
var elDocs = [this.el.ownerDocument, document, this.rte]; var elDocs = [this.el.ownerDocument, document];
$(elDocs).off('mousedown', this.disableEditing); mixins.off(elDocs, 'mousedown', this.disableEditing);
$(elDocs)[method]('mousedown', this.disableEditing); mixins[method](elDocs, 'mousedown', this.disableEditing);
// Avoid closing edit mode on component click // Avoid closing edit mode on component click
this.$el.off('mousedown', this.disablePropagation); this.$el.off('mousedown', this.disablePropagation);

137
src/dom_components/view/ComponentView.js

@ -1,5 +1,4 @@
var Backbone = require('backbone'); const ComponentsView = require('./ComponentsView');
var ComponentsView = require('./ComponentsView');
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
@ -12,33 +11,38 @@ module.exports = Backbone.View.extend({
}, },
initialize(opt) { initialize(opt) {
var model = this.model; const model = this.model;
this.opts = opt || {}; this.opts = opt || {};
this.config = this.opts.config || {}; this.config = this.opts.config || {};
this.em = this.config.em || ''; this.em = this.config.em || '';
this.pfx = this.config.stylePrefix || ''; this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || ''; this.ppfx = this.config.pStylePrefix || '';
this.components = model.get('components'); this.attr = model.get('attributes');
this.attr = model.get("attributes");
this.classe = this.attr.class || []; this.classe = this.attr.class || [];
const $el = this.$el;
const classes = model.get('classes');
this.listenTo(model, 'destroy remove', this.remove); this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:style', this.updateStyle); this.listenTo(model, 'change:style', this.updateStyle);
this.listenTo(model, 'change:attributes', this.updateAttributes); this.listenTo(model, 'change:attributes', this.updateAttributes);
this.listenTo(model, 'change:highlightable', this.updateHighlight);
this.listenTo(model, 'change:status', this.updateStatus); this.listenTo(model, 'change:status', this.updateStatus);
this.listenTo(model, 'change:state', this.updateState); this.listenTo(model, 'change:state', this.updateState);
this.listenTo(model, 'change:script', this.render); this.listenTo(model, 'change:script', this.render);
this.listenTo(model, 'change', this.handleChange); this.listenTo(model, 'change', this.handleChange);
this.listenTo(model.get('classes'), 'add remove change', this.updateClasses); this.listenTo(classes, 'add remove change', this.updateClasses);
this.$el.data('model', model); $el.data('model', model);
$el.data('collection', model.get('components'));
model.view = this; model.view = this;
this.$el.data("collection", this.components); classes.length && this.importClasses();
if(model.get('classes').length)
this.importClasses();
this.init(); this.init();
}, },
remove() {
Backbone.View.prototype.remove.apply(this);
const children = this.childrenView;
children && children.stopListening();
},
/** /**
* Initialize callback * Initialize callback
*/ */
@ -127,6 +131,45 @@ module.exports = Backbone.View.extend({
} }
}, },
/**
* Update highlight attribute
* @private
* */
updateHighlight() {
const hl = this.model.get('highlightable');
this.setAttribute('data-highlightable', hl ? 1 : '');
},
/**
* Update style attribute
* @private
* */
updateStyle() {
this.setAttribute('style', this.getStyleString());
},
/**
* Update classe attribute
* @private
* */
updateClasses() {
const str = this.model.get('classes').pluck('name').join(' ');
this.setAttribute('class', str);
// Regenerate status class
this.updateStatus();
},
/**
* Update single attribute
* @param {[type]} name [description]
* @param {[type]} value [description]
*/
setAttribute(name, value) {
const el = this.$el;
value ? el.attr(name, value) : el.removeAttr(name);
},
/** /**
* Get classes from attributes. * Get classes from attributes.
* This method is called before initialize * This method is called before initialize
@ -148,36 +191,19 @@ module.exports = Backbone.View.extend({
* @private * @private
* */ * */
updateAttributes() { updateAttributes() {
var model = this.model; const model = this.model;
var attributes = {}, const attrs = {}
attr = model.get("attributes"); const attr = model.get('attributes');
for(var key in attr) { const src = model.get('src');
if (key && attr.hasOwnProperty(key)) {
attributes[key] = attr[key];
}
}
// Update src
if(model.get('src'))
attributes.src = model.get('src');
if(model.get('highlightable'))
attributes['data-highlightable'] = 1;
var styleStr = this.getStyleString();
if(styleStr)
attributes.style = styleStr;
this.$el.attr(attributes); for (let key in attr) {
}, attrs[key] = attr[key];
}
/** src && (attrs.src = src);
* Update style attribute this.$el.attr(attrs);
* @private this.updateHighlight();
* */ this.updateStyle();
updateStyle() {
this.$el.attr('style', this.getStyleString());
}, },
/** /**
@ -204,27 +230,6 @@ module.exports = Backbone.View.extend({
return style; return style;
}, },
/**
* Update classe attribute
* @private
* */
updateClasses() {
var str = '';
this.model.get('classes').each(model => {
str += model.get('name') + ' ';
});
str = str.trim();
if(str)
this.$el.attr('class', str);
else
this.$el.removeAttr('class');
// Regenerate status class
this.updateStatus();
},
/** /**
* Reply to event call * Reply to event call
* @param object Event that generated the request * @param object Event that generated the request
@ -300,15 +305,16 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
renderChildren() { renderChildren() {
var view = new ComponentsView({ const container = this.getChildrenContainer();
const view = new ComponentsView({
collection: this.model.get('components'), collection: this.model.get('components'),
config: this.config, config: this.config,
componentTypes: this.opts.componentTypes, componentTypes: this.opts.componentTypes,
}); });
var container = this.getChildrenContainer(); view.render(container);
var childNodes = view.render($(container)).el.childNodes; this.childrenView = view;
childNodes = Array.prototype.slice.call(childNodes); const childNodes = Array.prototype.slice.call(view.el.childNodes);
for (var i = 0, len = childNodes.length ; i < len; i++) { for (var i = 0, len = childNodes.length ; i < len; i++) {
container.appendChild(childNodes.shift()); container.appendChild(childNodes.shift());
@ -339,7 +345,6 @@ module.exports = Backbone.View.extend({
render() { render() {
this.renderAttributes(); this.renderAttributes();
var model = this.model;
this.updateContent(); this.updateContent();
this.renderChildren(); this.renderChildren();
this.updateScript(); this.updateScript();

61
src/dom_components/view/ComponentsView.js

@ -1,12 +1,13 @@
var Backbone = require('backbone'); import { isUndefined } from 'underscore'
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
initialize(o) { initialize(o) {
this.opts = o || {}; this.opts = o || {};
this.config = o.config || {}; this.config = o.config || {};
this.listenTo( this.collection, 'add', this.addTo ); const coll = this.collection;
this.listenTo( this.collection, 'reset', this.render ); this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.resetChildren);
}, },
/** /**
@ -17,7 +18,7 @@ module.exports = Backbone.View.extend({
* @private * @private
* */ * */
addTo(model) { addTo(model) {
var i = this.collection.indexOf(model); var i = this.collection.indexOf(model);
this.addToCollection(model, null, i); this.addToCollection(model, null, i);
var em = this.config.em; var em = this.config.em;
@ -42,7 +43,6 @@ module.exports = Backbone.View.extend({
this.compView = require('./ComponentView'); this.compView = require('./ComponentView');
var fragment = fragmentEl || null, var fragment = fragmentEl || null,
viewObject = this.compView; viewObject = this.compView;
//console.log('Add to collection', model, 'Index',i);
var dt = this.opts.componentTypes; var dt = this.opts.componentTypes;
@ -66,44 +66,47 @@ module.exports = Backbone.View.extend({
if(view.model.get('type') == 'textnode') if(view.model.get('type') == 'textnode')
rendered = document.createTextNode(view.model.get('content')); rendered = document.createTextNode(view.model.get('content'));
if(fragment){ if (fragment) {
fragment.appendChild(rendered); fragment.appendChild(rendered);
}else{ } else {
var p = this.$parent; const parent = this.parentEl;
var pc = p.children; const children = parent.childNodes;
if(typeof index != 'undefined'){
var method = 'before'; if (!isUndefined(index)) {
const lastIndex = children.length == index;
// If the added model is the last of collection // If the added model is the last of collection
// need to change the logic of append // need to change the logic of append
if(pc && p.children().length == index){ if (lastIndex) {
index--; index--;
method = 'after';
} }
// In case the added is new in the collection index will be -1 // In case the added is new in the collection index will be -1
if(index < 0) { if (lastIndex || !children.length) {
p.append(rendered); parent.appendChild(rendered);
}else { } else {
if(pc) { parent.insertBefore(rendered, children[index]);
p.children().eq(index)[method](rendered);
}
} }
}else{ } else {
p.append(rendered); parent.appendChild(rendered);
} }
} }
return rendered; return rendered;
}, },
render($p) { resetChildren() {
var fragment = document.createDocumentFragment(); this.parentEl.innerHTML = '';
this.$parent = $p || this.$el; this.collection.each(model => this.addToCollection(model));
this.$el.empty(); },
this.collection.each(function(model){
this.addToCollection(model, fragment);
},this);
this.$el.append(fragment);
render(parent) {
const el = this.el;
const frag = document.createDocumentFragment();
this.parentEl = parent || this.el;
this.collection.each(model => this.addToCollection(model, frag));
el.innerHTML = '';
el.appendChild(frag);
return this; return this;
} }

11
src/dom_components/view/ToolbarButtonView.js

@ -14,15 +14,18 @@ module.exports = Backbone.View.extend({
}, },
handleClick(event) { handleClick(event) {
var opts = {event}; event.preventDefault();
var command = this.model.get('command'); event.stopPropagation();
const opts = {event};
const command = this.model.get('command');
const editor = this.editor;
if (typeof command === 'function') { if (typeof command === 'function') {
command(this.editor, null, opts); command(editor, null, opts);
} }
if (typeof command === 'string') { if (typeof command === 'string') {
this.editor.runCommand(command, opts); editor.runCommand(command, opts);
} }
}, },

65
src/domain_abstract/ui/Input.js

@ -1,4 +1,4 @@
var Backbone = require('backbone'); const $ = Backbone.$;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
@ -6,24 +6,35 @@ module.exports = Backbone.View.extend({
'change': 'handleChange', 'change': 'handleChange',
}, },
template: _.template(`<span class='<%= holderClass %>'></span>`), template() {
const holderClass = this.holderClass;
return `<span class="${holderClass}"></span>`;
},
initialize(opts) { initialize(opts = {}) {
var opt = opts || {}; const ppfx = opts.ppfx || '';
var ppfx = opt.ppfx || ''; this.target = opts.target || {};
this.target = opt.target || {};
this.inputClass = ppfx + 'field'; this.inputClass = ppfx + 'field';
this.inputHolderClass = ppfx + 'input-holder'; this.inputHolderClass = ppfx + 'input-holder';
this.holderClass = `${ppfx}input-holder`;
this.ppfx = ppfx; this.ppfx = ppfx;
this.listenTo(this.model, 'change:value', this.handleModelChange); this.listenTo(this.model, 'change:value', this.handleModelChange);
}, },
/**
* Fired when the element of the property is updated
*/
elementUpdated() {
this.model.trigger('el:change');
},
/** /**
* Handled when the view is changed * Handled when the view is changed
*/ */
handleChange(e) { handleChange(e) {
e.stopPropagation(); e.stopPropagation();
this.setValue(this.getInputEl().value); this.model.set('value', this.getInputEl().value);
this.elementUpdated();
}, },
/** /**
@ -31,25 +42,18 @@ module.exports = Backbone.View.extend({
* @param {string} value * @param {string} value
* @param {Object} opts * @param {Object} opts
*/ */
setValue(value, opts) { setValue(value, opts = {}) {
var opt = opts || {}; const model = this.model;
var model = this.model; let val = value || model.get('defaults');
model.set({ const input = this.getInputEl();
value: value || model.get('defaults') input && (input.value = val);
}, opt);
// Generally I get silent when I need to reflect data to view without
// reupdating the target
if(opt.silent) {
this.handleModelChange();
}
}, },
/** /**
* Updates the view when the model is changed * Updates the view when the model is changed
* */ * */
handleModelChange() { handleModelChange(model, value, opts) {
this.getInputEl().value = this.model.get('value'); this.setValue(value, opts);
}, },
/** /**
@ -58,23 +62,20 @@ module.exports = Backbone.View.extend({
*/ */
getInputEl() { getInputEl() {
if(!this.inputEl) { if(!this.inputEl) {
this.inputEl = $('<input>', { const plh = this.model.get('defaults');
type: 'text', const cls = this.inputCls;
class: this.inputCls, this.inputEl = $(`<input type="text" class="${cls}" placeholder="${plh}">`);
placeholder: this.model.get('defaults')
});
} }
return this.inputEl.get(0); return this.inputEl.get(0);
}, },
render() { render() {
var el = this.$el; const el = this.$el;
const ppfx = this.ppfx;
const holderClass = this.holderClass;
el.addClass(this.inputClass); el.addClass(this.inputClass);
el.html(this.template({ el.html(this.template());
holderClass: this.inputHolderClass, el.find(`.${holderClass}`).append(this.getInputEl());
ppfx: this.ppfx
}));
el.find('.'+ this.inputHolderClass).html(this.getInputEl());
return this; return this;
} }

118
src/domain_abstract/ui/InputColor.js

@ -1,40 +1,55 @@
var Backbone = require('backbone'); const Input = require('./Input');
var Input = require('./Input'); //require('spectrum-colorpicker');
var Spectrum = require('spectrum-colorpicker'); require('utils/ColorPicker');
const $ = Backbone.$;
module.exports = Input.extend({ module.exports = Input.extend({
template: _.template(` template() {
<div class='<%= ppfx %>input-holder'></div> const ppfx = this.ppfx;
<div class="<%= ppfx %>field-colorp"> return `
<div class="<%= ppfx %>field-colorp-c"> <div class="${ppfx}input-holder"></div>
<div class="<%= ppfx %>checker-bg"></div> <div class="${ppfx}field-colorp">
</div> <div class="${ppfx}field-colorp-c">
</div>`), <div class="${ppfx}checker-bg"></div>
</div>
</div>
`;
},
initialize(opts) { initialize(opts) {
Input.prototype.initialize.apply(this, arguments); Input.prototype.initialize.apply(this, arguments);
var ppfx = this.ppfx; const ppfx = this.ppfx;
this.colorCls = ppfx + 'field-color-picker'; this.colorCls = `${ppfx}field-color-picker`;
this.inputClass = ppfx + 'field ' + ppfx + 'field-color'; this.inputClass = `${ppfx}field ${ppfx}field-color`;
this.colorHolderClass = ppfx + 'field-colorp-c'; this.colorHolderClass = `${ppfx}field-colorp-c`;
},
this.listenTo(this.model, 'change:value', this.handleModelChange); /**
* Set value to the model
* @param {string} val
* @param {Object} opts
*/
setValue(val, opts = {}) {
const model = this.model;
const value = val || model.get('defaults');
const inputEl = this.getInputEl();
const colorEl = this.getColorEl();
const valueClr = value != 'none' ? value : '';
inputEl.value = value;
colorEl.get(0).style.backgroundColor = valueClr;
if (opts.targetUpdate) {
colorEl.spectrum('set', valueClr);
this.noneColor = value == 'none';
}
}, },
/** /**
* Updates the view when the model is changed * Updates the view when the model is changed
* */ * */
handleModelChange(...args) { handleModelChange(model, value, opts) {
Input.prototype.handleModelChange.apply(this, args); this.setValue(value, opts);
var value = this.model.get('value');
var colorEl = this.getColorEl();
// If no color selected I will set white for the picker
value = value === 'none' ? '#fff' : value;
colorEl.spectrum('set', value);
colorEl.get(0).style.backgroundColor = value;
}, },
/** /**
@ -42,16 +57,22 @@ module.exports = Input.extend({
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getColorEl() { getColorEl() {
if(!this.colorEl) { if (!this.colorEl) {
const self = this;
var model = this.model; var model = this.model;
var colorEl = $('<div>', {class: this.colorCls});
var colorEl = $(`<div class="${this.colorCls}"></div>`);
var cpStyle = colorEl.get(0).style; var cpStyle = colorEl.get(0).style;
var elToAppend = this.target && this.target.config ? this.target.config.el : ''; var elToAppend = this.target && this.target.config ? this.target.config.el : '';
const getColor = color => {
if (typeof colorEl.spectrum == 'undefined') { let cl = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
throw 'Spectrum missing, probably you load jQuery twice'; return cl.replace(/ /g, '');
} }
let changed = 0;
let previousСolor;
this.$el.find(`.${this.colorHolderClass}`).append(colorEl);
colorEl.spectrum({ colorEl.spectrum({
appendTo: elToAppend || 'body', appendTo: elToAppend || 'body',
maxSelectionSize: 8, maxSelectionSize: 8,
@ -61,24 +82,43 @@ module.exports = Input.extend({
cancelText: '⨯', cancelText: '⨯',
palette: [], palette: [],
move(color) { move(color) {
var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString(); const cl = getColor(color);
cpStyle.backgroundColor = c; cpStyle.backgroundColor = cl;
model.set('value', cl, {avoidStore: 1});
}, },
change(color) { change(color) {
var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString(); changed = 1;
c = c.replace(/ /g,''); const cl = getColor(color);
cpStyle.backgroundColor = c; cpStyle.backgroundColor = cl;
model.set('value', c); model.set('value', '', {avoidStore: 1});
model.set('value', cl);
self.noneColor = 0;
},
show(color) {
changed = 0;
previousСolor = getColor(color);
},
hide(color) {
if (!changed && previousСolor) {
if (self.noneColor) {
previousСolor = '';
}
cpStyle.backgroundColor = previousСolor;
colorEl.spectrum('set', previousСolor);
model.set('value', previousСolor, {avoidStore: 1});
}
} }
}); });
this.colorEl = colorEl; this.colorEl = colorEl;
} }
return this.colorEl; return this.colorEl;
}, },
render(...args) { render() {
Input.prototype.render.apply(this, args); Input.prototype.render.call(this);
this.$el.find('.' + this.colorHolderClass).html(this.getColorEl()); // This will make the color input available on render
this.getColorEl();
return this; return this;
} }

119
src/domain_abstract/ui/InputNumber.js

@ -1,8 +1,9 @@
var Backbone = require('backbone'); import {on, off} from 'utils/mixins'
module.exports = Backbone.View.extend({ const Backbone = require('backbone');
const $ = Backbone.$;
events: {}, module.exports = Backbone.View.extend({
template: _.template(` template: _.template(`
<span class='<%= ppfx %>input-holder'></span> <span class='<%= ppfx %>input-holder'></span>
@ -16,18 +17,18 @@ module.exports = Backbone.View.extend({
_.bindAll(this, 'moveIncrement', 'upIncrement'); _.bindAll(this, 'moveIncrement', 'upIncrement');
var opt = opts || {}; var opt = opts || {};
var ppfx = opt.ppfx || ''; var ppfx = opt.ppfx || '';
var contClass = opt.contClass || (ppfx + 'field'); var contClass = opt.contClass || (`${ppfx}field ${ppfx}field-integer`);
this.ppfx = ppfx; this.ppfx = ppfx;
this.docEl = $(document); this.doc = document;
this.inputCls = ppfx + 'field-number'; this.inputCls = ppfx + 'field-number';
this.unitCls = ppfx + 'input-unit'; this.unitCls = ppfx + 'input-unit';
this.contClass = contClass; this.contClass = contClass;
this.events['click .' + ppfx + 'field-arrow-u'] = 'upArrowClick'; this.events = {};
this.events['click .' + ppfx + 'field-arrow-d'] = 'downArrowClick'; this.events[`click .${ppfx}field-arrow-u`] = 'upArrowClick';
this.events['mousedown .' + ppfx + 'field-arrows'] = 'downIncrement'; this.events[`click .${ppfx}field-arrow-d`] = 'downArrowClick';
this.events['change .' + this.inputCls] = 'handleChange'; this.events[`mousedown .${ppfx}field-arrows`] = 'downIncrement';
this.events['change .' + this.unitCls] = 'handleUnitChange'; this.events[`change .${this.inputCls}`] = 'handleChange';
this.events[`change .${this.unitCls}`] = 'handleUnitChange';
this.listenTo(this.model, 'change:unit change:value', this.handleModelChange); this.listenTo(this.model, 'change:unit change:value', this.handleModelChange);
this.delegateEvents(); this.delegateEvents();
}, },
@ -100,12 +101,10 @@ module.exports = Backbone.View.extend({
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getInputEl() { getInputEl() {
if(!this.inputEl) { if (!this.inputEl) {
this.inputEl = $('<input>', { const cls = this.inputCls;
type: 'text', const plh = this.model.get('defaults');
class: this.inputCls, this.inputEl = $(`<input type="text" class="${cls}" placeholder="${plh}">`);
placeholder: this.model.get('defaults')
});
} }
return this.inputEl.get(0); return this.inputEl.get(0);
}, },
@ -125,20 +124,24 @@ module.exports = Backbone.View.extend({
unitStr += '<option ' + selected + ' >' + unit + '</option>'; unitStr += '<option ' + selected + ' >' + unit + '</option>';
}); });
unitStr += '</select>'; unitStr += '</select>';
this.unitEl = $(unitStr); const temp = document.createElement('div');
temp.innerHTML = unitStr;
this.unitEl = temp.firstChild;
} }
} }
return this.unitEl && this.unitEl.get(0); return this.unitEl;
}, },
/** /**
* Invoked when the up arrow is clicked * Invoked when the up arrow is clicked
* */ * */
upArrowClick() { upArrowClick() {
var value = this.model.get('value'); const model = this.model;
value = isNaN(value) ? 1 : parseInt(value, 10) + 1; const step = model.get('step');
let value = model.get('value');
value = this.normalizeValue(value + step);
var valid = this.validateInputValue(value); var valid = this.validateInputValue(value);
this.model.set('value', valid.value); model.set('value', valid.value);
this.elementUpdated(); this.elementUpdated();
}, },
@ -146,10 +149,12 @@ module.exports = Backbone.View.extend({
* Invoked when the down arrow is clicked * Invoked when the down arrow is clicked
* */ * */
downArrowClick() { downArrowClick() {
var value = this.model.get('value'); const model = this.model;
value = isNaN(value) ? 0 : parseInt(value, 10) - 1; const step = model.get('step');
let value = model.get('value');
value = this.normalizeValue(value - step);
var valid = this.validateInputValue(value); var valid = this.validateInputValue(value);
this.model.set('value', valid.value); model.set('value', valid.value);
this.elementUpdated(); this.elementUpdated();
}, },
@ -163,10 +168,10 @@ module.exports = Backbone.View.extend({
e.preventDefault(); e.preventDefault();
this.moved = 0; this.moved = 0;
var value = this.model.get('value'); var value = this.model.get('value');
value = isNaN(value) ? 0 : parseInt(value, 10); value = this.normalizeValue(value);
var current = {y: e.pageY, val: value }; this.current = {y: e.pageY, val: value};
this.docEl.mouseup(current, this.upIncrement); on(this.doc, 'mousemove', this.moveIncrement);
this.docEl.mousemove(current, this.moveIncrement); on(this.doc, 'mouseup', this.upIncrement);
}, },
/** While the increment is clicked, moving the mouse will update input value /** While the increment is clicked, moving the mouse will update input value
@ -176,30 +181,50 @@ module.exports = Backbone.View.extend({
* */ * */
moveIncrement(ev) { moveIncrement(ev) {
this.moved = 1; this.moved = 1;
var pos = parseInt(ev.data.val - ev.pageY + ev.data.y, 10); const model = this.model;
this.prValue = this.validateInputValue(pos).value;//Math.max(this.min, Math.min(this.max, pos) ); const step = model.get('step');
this.model.set('value', this.prValue, {avoidStore: 1}); const data = this.current;
var pos = this.normalizeValue(data.val + (data.y - ev.pageY) * step);
this.prValue = this.validateInputValue(pos).value;
model.set('value', this.prValue, {avoidStore: 1});
return false; return false;
}, },
/** /**
* Stop moveIncrement method * Stop moveIncrement method
* @param Object
*
* @return void
* */ * */
upIncrement(e) { upIncrement() {
this.docEl.off('mouseup', this.upIncrement); const model = this.model;
this.docEl.off('mousemove', this.moveIncrement); const step = model.get('step');
off(this.doc, 'mouseup', this.upIncrement);
off(this.doc, 'mousemove', this.moveIncrement);
if(this.prValue && this.moved) { if(this.prValue && this.moved) {
var value = this.prValue - 1; var value = this.prValue - step;
this.model.set('value', value, {avoidStore: 1}) model.set('value', value, {avoidStore: 1})
.set('value', value + 1); .set('value', value + step);
this.elementUpdated(); this.elementUpdated();
} }
}, },
normalizeValue(value, defValue = 0) {
const model = this.model;
const step = model.get('step');
let stepDecimals = 0;
if (isNaN(value)) {
return defValue;
}
value = parseFloat(value);
if (Math.floor(value) !== value) {
stepDecimals = step.toString().split('.')[1].length || 0;
}
return stepDecimals ? parseFloat(value.toFixed(stepDecimals)) : value;
},
/** /**
* Validate input value * Validate input value
* @param {String} value Raw value * @param {String} value Raw value
@ -253,11 +278,13 @@ module.exports = Backbone.View.extend({
}, },
render() { render() {
var ppfx = this.ppfx; const ppfx = this.ppfx;
this.$el.html(this.template({ppfx})); const el = this.$el;
this.$el.find('.'+ ppfx +'input-holder').html(this.getInputEl()); el.html(this.template({ppfx}));
this.$el.find('.' + ppfx + 'field-units').html(this.getUnitEl()); el.find(`.${ppfx}input-holder`).append(this.getInputEl());
this.$el.addClass(this.contClass); const unit = this.getUnitEl();
unit && el.find(`.${ppfx}field-units`).get(0).appendChild(unit);
el.addClass(this.contClass);
return this; return this;
} }

10
src/editor/index.js

@ -37,6 +37,9 @@
* * `component:update:{propertyName}` - Listen any property change * * `component:update:{propertyName}` - Listen any property change
* * `component:styleUpdate` - Triggered when the style of the component is updated * * `component:styleUpdate` - Triggered when the style of the component is updated
* * `component:styleUpdate:{propertyName}` - Listen for a specific style property change * * `component:styleUpdate:{propertyName}` - Listen for a specific style property change
* * `component:selected` - New component selected
* * `block:add` - New block added
* * `block:remove` - Block removed
* * `asset:add` - New asset added * * `asset:add` - New asset added
* * `asset:remove` - Asset removed * * `asset:remove` - Asset removed
* * `asset:upload:start` - Before the upload is started * * `asset:upload:start` - Before the upload is started
@ -45,9 +48,14 @@
* * `asset:upload:response` - On upload response, passes the result 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` - 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 * * `styleManager:change:{propertyName}` - As above but for a specific style property
* * `storage:start` - Before the storage request is started
* * `storage:load` - Triggered when something was loaded from the storage, loaded object passed as an argumnet * * `storage:load` - Triggered when something was loaded from the storage, loaded object passed as an argumnet
* * `storage:store` - Triggered when something is stored to the storage, stored object passed as an argumnet * * `storage:store` - Triggered when something is stored to the storage, stored object passed as an argumnet
* * `storage:end` - After the storage request is ended
* * `storage:error` - On any error on storage request, passes the error as an argument
* * `selector:add` - Triggers when a new selector/class is created * * `selector:add` - Triggers when a new selector/class is created
* * `rte:enable` - RTE enabled. The view, on which RTE is enabled, is passed as an argument
* * `rte:disable` - RTE disabled. The view, on which RTE is disabled, is passed as an argument
* * `canvasScroll` - Triggered when the canvas is scrolled * * `canvasScroll` - Triggered when the canvas is scrolled
* * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview')) * * `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')) * * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview'))
@ -201,7 +209,7 @@ module.exports = config => {
* @property {RichTextEditor} * @property {RichTextEditor}
* @private * @private
*/ */
RichTextEditor: em.get('rte'), RichTextEditor: em.get('RichTextEditor'),
/** /**
* @property {Utils} * @property {Utils}

83
src/editor/model/Editor.js

@ -1,31 +1,36 @@
var deps = [ import { isUndefined, defaults } from 'underscore';
require('utils'),
require('storage_manager'), const deps = [
require('device_manager'), require('utils'),
require('parser'), require('storage_manager'),
require('selector_manager'), require('device_manager'),
require('modal_dialog'), require('parser'),
require('code_manager'), require('selector_manager'),
require('panels'), require('modal_dialog'),
require('rich_text_editor'), require('code_manager'),
require('style_manager'), require('panels'),
require('asset_manager'), require('rich_text_editor'),
require('css_composer'), require('style_manager'),
require('dom_components'), require('asset_manager'),
require('canvas'), require('css_composer'),
require('commands'), require('dom_components'),
require('block_manager'), require('canvas'),
require('trait_manager'), require('commands'),
require('block_manager'),
require('trait_manager'),
]; ];
var Backbone = require('backbone'); const Backbone = require('backbone');
var UndoManager = require('backbone-undo'); const UndoManager = require('backbone-undo');
var key = require('keymaster'); const key = require('keymaster');
var timedInterval; let timedInterval;
if (!Backbone.$) { require('utils/extender')({
Backbone.$ = $; Backbone: Backbone,
} $: Backbone.$
});
const $ = Backbone.$;
module.exports = Backbone.Model.extend({ module.exports = Backbone.Model.extend({
@ -291,10 +296,12 @@ module.exports = Backbone.Model.extend({
* @private * @private
* */ * */
componentSelected(model, val, options) { componentSelected(model, val, options) {
if(!this.get('selectedComponent')) if (!this.get('selectedComponent')) {
this.trigger('deselect-comp'); this.trigger('deselect-comp');
else } else {
this.trigger('select-comp',[model,val,options]); this.trigger('select-comp',[model,val,options]);
this.trigger('component:selected', arguments);
}
}, },
/** /**
@ -605,4 +612,26 @@ module.exports = Backbone.Model.extend({
w.getSelection().removeAllRanges(); w.getSelection().removeAllRanges();
}, },
/**
* Set/get data from the HTMLElement
* @param {HTMLElement} el
* @param {string} name Data name
* @param {any} value Date value
* @return {any}
* @private
*/
data(el, name, value) {
const varName = '_gjs-data';
if (!el[varName]) {
el[varName] = {};
}
if (isUndefined(value)) {
return el[varName][name];
} else {
el[varName][name] = value;
}
}
}); });

2
src/editor/view/EditorView.js

@ -1,3 +1,5 @@
const $ = Backbone.$;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
initialize() { initialize() {

13
src/grapesjs/index.js

@ -1,4 +1,5 @@
import { isUndefined, defaults } from 'underscore'; import $ from 'cash-dom';
import { defaults } from 'underscore';
module.exports = (() => { module.exports = (() => {
const defaultConfig = require('./config/config'); const defaultConfig = require('./config/config');
@ -9,10 +10,15 @@ module.exports = (() => {
return { return {
$,
editors, editors,
plugins, plugins,
// Will be replaced on build
version: '<# VERSION #>',
/** /**
* Initializes an editor based on passed options * Initializes an editor based on passed options
* @param {Object} config Configuration object * @param {Object} config Configuration object
@ -34,11 +40,6 @@ module.exports = (() => {
init(config = {}) { init(config = {}) {
const els = config.container; const els = config.container;
// Make a missing $ more verbose
if (isUndefined($)) {
throw 'jQuery not found';
}
if (!els) { if (!els) {
throw new Error("'container' is required"); throw new Error("'container' is required");
} }

15
src/modal_dialog/view/ModalView.js

@ -48,8 +48,12 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
getContent() { getContent() {
if(!this.$content) const pfx = this.pfx;
this.$content = this.$el.find('.'+this.pfx+'content #'+this.pfx+'c');
if (!this.$content) {
this.$content = this.$el.find(`.${pfx}content #${pfx}c`);
}
return this.$content; return this.$content;
}, },
@ -70,8 +74,11 @@ module.exports = Backbone.View.extend({
* */ * */
updateContent() { updateContent() {
var content = this.getContent(); var content = this.getContent();
this.getCollector().append(content.children()); const children = content.children();
content.html(this.model.get('content')); const coll = this.getCollector();
const body = this.model.get('content');
children.length && coll.append(children);
content.empty().append(body);
}, },
/** /**

126
src/navigator/view/ItemView.js

@ -4,16 +4,25 @@ var ItemsView;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
events: {
'mousedown [data-toggle-move]': 'startSort',
'click [data-toggle-visible]': 'toggleVisibility',
'click [data-toggle-select]': 'handleSelect',
'click [data-toggle-open]': 'toggleOpening',
'click [data-toggle-edit]': 'handleEdit',
'focusout input': 'handleEditEnd',
},
template: _.template(` template: _.template(`
<% if (hidable) { %> <% 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') %>" data-toggle-visible></i>
<% } %> <% } %>
<div class="<%= prefix %>title-c"> <div class="<%= prefix %>title-c">
<div class="<%= prefix %>title <%= addClass %>" style="padding-left: <%= 42 + level * 10 %>px"> <div class="<%= prefix %>title <%= addClass %>" style="padding-left: <%= 42 + level * 10 %>px" data-toggle-select>
<div class="<%= prefix %>title-inn"> <div class="<%= prefix %>title-inn">
<i class="fa fa-pencil <%= editBtnCls %>"></i> <i class="fa fa-pencil <%= editBtnCls %>" data-toggle-edit></i>
<i id="<%= prefix %>caret" class="fa fa-chevron-right <%= caretCls %>"></i> <i id="<%= prefix %>caret" class="fa fa-chevron-right <%= caretCls %>" data-toggle-open></i>
<%= icon %> <%= icon %>
<input class="<%= ppfx %>no-app <%= inputNameCls %>" value="<%= title %>" readonly> <input class="<%= ppfx %>no-app <%= inputNameCls %>" value="<%= title %>" readonly>
</div> </div>
@ -22,7 +31,7 @@ module.exports = Backbone.View.extend({
<div id="<%= prefix %>counter"><%= (count ? count : '') %></div> <div id="<%= prefix %>counter"><%= (count ? count : '') %></div>
<div id="<%= prefix %>move"> <div id="<%= prefix %>move" data-toggle-move>
<i class="fa fa-arrows"></i> <i class="fa fa-arrows"></i>
</div> </div>
@ -36,31 +45,22 @@ module.exports = Backbone.View.extend({
this.ppfx = this.em.get('Config').stylePrefix; this.ppfx = this.em.get('Config').stylePrefix;
this.sorter = o.sorter || ''; this.sorter = o.sorter || '';
this.pfx = this.config.stylePrefix; this.pfx = this.config.stylePrefix;
if(typeof this.model.get('open') == 'undefined') const pfx = this.pfx;
this.model.set('open',false); const ppfx = this.ppfx;
this.listenTo(this.model.get('components'), 'remove add change reset', this.checkChildren); const model = this.model;
this.listenTo(this.model, 'destroy remove', this.remove); const components = model.get('components');
this.listenTo(this.model, 'change:status', this.updateStatus); model.set('open', false);
this.listenTo(this.model, 'change:open', this.updateOpening); this.listenTo(components, 'remove add change reset', this.checkChildren);
this.className = this.pfx + 'item no-select'; this.listenTo(model, 'destroy remove', this.remove);
this.editBtnCls = this.pfx + 'nav-item-edit'; this.listenTo(model, 'change:status', this.updateStatus);
this.inputNameCls = this.ppfx + 'nav-comp-name'; this.listenTo(model, 'change:open', this.updateOpening);
this.caretCls = this.ppfx + 'nav-item-caret'; this.className = `${pfx}item no-select`;
this.titleCls = this.pfx + 'title'; this.editBtnCls = `${pfx}nav-item-edit`;
this.events = {}; this.inputNameCls = `${ppfx}nav-comp-name`;
this.events['click > #'+this.pfx+'btn-eye'] = 'toggleVisibility'; this.caretCls = `${ppfx}nav-item-caret`;
this.events['click .' + this.caretCls] = 'toggleOpening'; this.titleCls = `${pfx}title`;
this.events['click .' + this.titleCls] = 'handleSelect'; this.$el.data('model', model);
this.events['click .' + this.editBtnCls] = 'handleEdit'; this.$el.data('collection', components);
this.events['blur .' + this.inputNameCls] = 'handleEditEnd';
this.$el.data('model', this.model);
this.$el.data('collection', this.model.get('components'));
if(o.config.sortable)
this.events['mousedown > #'+this.pfx+'move'] = 'startSort';
this.delegateEvents();
}, },
/** /**
@ -102,13 +102,15 @@ module.exports = Backbone.View.extend({
updateOpening() { updateOpening() {
var opened = this.opt.opened || {}; var opened = this.opt.opened || {};
var model = this.model; var model = this.model;
if(model.get('open')){ const chvDown = 'fa-chevron-down';
this.$el.addClass("open");
this.getCaret().addClass('fa-chevron-down'); if (model.get('open')) {
this.$el.addClass('open');
this.getCaret().addClass(chvDown);
opened[model.cid] = model; opened[model.cid] = model;
}else{ } else {
this.$el.removeClass("open"); this.$el.removeClass("open");
this.getCaret().removeClass('fa-chevron-down'); this.getCaret().removeClass(chvDown);
delete opened[model.cid]; delete opened[model.cid];
} }
}, },
@ -183,8 +185,11 @@ module.exports = Backbone.View.extend({
* @return void * @return void
* */ * */
toggleVisibility(e) { toggleVisibility(e) {
e.stopPropagation();
const pfx = this.pfx;
if(!this.$eye) if(!this.$eye)
this.$eye = this.$el.find('> #'+this.pfx+'btn-eye'); this.$eye = this.$el.children(`#${pfx}btn-eye`);
var cCss = _.clone(this.model.get('style')), var cCss = _.clone(this.model.get('style')),
hClass = this.pfx + 'hide'; hClass = this.pfx + 'hide';
@ -219,18 +224,23 @@ module.exports = Backbone.View.extend({
* @return void * @return void
* */ * */
checkChildren() { checkChildren() {
var c = this.countChildren(this.model), const model = this.model;
pfx = this.pfx, const c = this.countChildren(model);
tC = '> .' + pfx + 'title-c > .' + pfx + 'title'; const pfx = this.pfx;
if(!this.$counter) const noChildCls = `${pfx}no-chld`;
this.$counter = this.$el.find('> #' + pfx + 'counter'); const title = this.$el.children(`.${pfx}title-c`).children(`.${pfx}title`);
if(c){ //tC = `> .${pfx}title-c > .${pfx}title`;
this.$el.find(tC).removeClass(pfx + 'no-chld'); if (!this.$counter) {
this.$counter = this.$el.children(`#${pfx}counter`);
}
if (c) {
title.removeClass(noChildCls);
this.$counter.html(c); this.$counter.html(c);
}else{ } else {
this.$el.find(tC).addClass(pfx + 'no-chld'); title.addClass(noChildCls);
this.$counter.empty(); this.$counter.empty();
this.model.set('open',0); model.set('open', 0);
} }
}, },
@ -255,8 +265,9 @@ module.exports = Backbone.View.extend({
getCaret() { getCaret() {
if (!this.caret) { if (!this.caret) {
const pfx = this.pfx; const pfx = this.pfx;
this.caret = this.$el.find(`> .${pfx}title-c > .${pfx}title > .${pfx}title-inn > #${pfx}caret`); this.caret = this.$el.children(`.${pfx}title-c`).find(`#${pfx}caret`);
} }
return this.caret; return this.caret;
}, },
@ -265,9 +276,10 @@ module.exports = Backbone.View.extend({
var pfx = this.pfx; var pfx = this.pfx;
var vis = this.isVisible(); var vis = this.isVisible();
var count = this.countChildren(model); var count = this.countChildren(model);
const el = this.$el;
const level = this.level + 1; const level = this.level + 1;
this.$el.html( this.template({ el.html( this.template({
title: model.getName(), title: model.getName(),
icon: model.getIcon(), icon: model.getIcon(),
addClass: (count ? '' : pfx+'no-chld'), addClass: (count ? '' : pfx+'no-chld'),
@ -282,9 +294,11 @@ module.exports = Backbone.View.extend({
level level
})); }));
if(typeof ItemsView == 'undefined') if (typeof ItemsView == 'undefined') {
ItemsView = require('./ItemsView'); ItemsView = require('./ItemsView');
this.$components = new ItemsView({ }
const children = new ItemsView({
collection: model.get('components'), collection: model.get('components'),
config: this.config, config: this.config,
sorter: this.sorter, sorter: this.sorter,
@ -292,13 +306,15 @@ module.exports = Backbone.View.extend({
parent: model, parent: model,
level level
}).render().$el; }).render().$el;
this.$el.find('.'+ pfx +'children').html(this.$components); el.find(`.${pfx}children`).append(children);
if(!model.get('draggable') || !this.config.sortable){
this.$el.find('> #' + pfx + 'move').detach(); if(!model.get('draggable') || !this.config.sortable) {
el.children(`#${pfx}move`).remove();
} }
if(!vis) if(!vis)
this.className += ' ' + pfx + 'hide'; this.className += ' ' + pfx + 'hide';
this.$el.attr('class', _.result(this, 'className')); el.attr('class', _.result(this, 'className'));
this.updateOpening(); this.updateOpening();
this.updateStatus(); this.updateStatus();
return this; return this;

22
src/navigator/view/ItemsView.js

@ -14,7 +14,7 @@ module.exports = Backbone.View.extend({
this.parent = o.parent; this.parent = o.parent;
this.listenTo(this.collection, 'add', this.addTo); this.listenTo(this.collection, 'add', this.addTo);
this.listenTo(this.collection, 'reset resetNavigator', this.render); this.listenTo(this.collection, 'reset resetNavigator', this.render);
this.className = this.pfx + 'items'; this.className = this.pfx + 'items';
if (config.sortable && !this.opt.sorter) { if (config.sortable && !this.opt.sorter) {
var pfx = this.pfx; var pfx = this.pfx;
@ -65,6 +65,10 @@ module.exports = Backbone.View.extend({
var fragment = fragmentEl || null; var fragment = fragmentEl || null;
var viewObject = ItemView; var viewObject = ItemView;
if(!this.isCountable(model, this.config.hideTextnode)) {
return;
}
var view = new viewObject({ var view = new viewObject({
level, level,
model, model,
@ -115,17 +119,11 @@ module.exports = Backbone.View.extend({
}, },
render() { render() {
var fragment = document.createDocumentFragment(); const frag = document.createDocumentFragment();
this.$el.empty(); this.el.innerHTML = '';
this.collection.each(model => this.addToCollection(model, frag));
this.collection.each(function(model) { this.el.appendChild(frag);
if(!this.isCountable(model, this.config.hideTextnode)) this.$el.attr('class', this.className);
return;
this.addToCollection(model, fragment);
}, this);
this.$el.append(fragment);
this.$el.attr('class', _.result(this, 'className'));
return this; return this;
} }
}); });

2
src/panels/view/ButtonView.js

@ -1,4 +1,4 @@
var Backbone = require('backbone'); const $ = Backbone.$;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({

35
src/rich_text_editor/config/config.js

@ -1,40 +1,11 @@
module.exports = { module.exports = {
stylePrefix : 'rte-', stylePrefix : 'rte-',
toolbarId : 'toolbar',
// If true, moves the toolbar below the element when the top canvas // If true, moves the toolbar below the element when the top canvas
// edge is reached // edge is reached
adjustToolbar: 1, adjustToolbar: 1,
// Default toolbar commands // Default RTE actions
commands : [{ actions: ['bold', 'italic', 'underline', 'strikethrough', 'link'],
command: 'bold',
title: 'Bold',
class: 'fa fa-bold',
},{
command: 'italic',
title: 'Italic',
class: 'fa fa-italic',
},{
command: 'underline',
title: 'Underline',
class: 'fa fa-underline',
},{
command: 'strikethrough',
title: 'Strikethrough',
class: 'fa fa-strikethrough',
group: 'format'
},{
command: 'insertHTML',
title: 'Link',
class: 'fa fa-link',
args: '<a class="link" href="">${content}</a>',
}/*,{
command: 'fontSize',
options: [
{name: 'Huge', value: '7'},
{name: 'Normal', value: '5'},
{value: '1'}
]
}*/],
}; };

345
src/rich_text_editor/index.js

@ -1,12 +1,7 @@
/** /**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [remove](#remove)
*
* This module allows to customize the toolbar of the Rich Text Editor and use commands from the HTML Editing APIs. * This module allows to customize the toolbar of the Rich Text Editor and use commands from the HTML Editing APIs.
* For more info about HTML Editing APIs check here: * For more info about HTML Editing APIs check here:
* https://developer.mozilla.org/it/docs/Web/API/Document/execCommand * https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
* *
* It's highly recommended to keep this toolbar as small as possible, especially from styling commands (eg. 'fontSize') * It's highly recommended to keep this toolbar as small as possible, especially from styling commands (eg. 'fontSize')
* and leave this task to the Style Manager. * and leave this task to the Style Manager.
@ -16,19 +11,15 @@
* ```js * ```js
* var rte = editor.RichTextEditor; * var rte = editor.RichTextEditor;
* ``` * ```
* Complete list of commands
* https://developer.mozilla.org/it/docs/Web/API/Document/execCommand
* http://www.quirksmode.org/dom/execCommand.html
* @module RichTextEditor * @module RichTextEditor
*/ */
import RichTextEditor from './model/RichTextEditor';
import {on, off} from 'utils/mixins'
module.exports = () => { module.exports = () => {
var c = {}, let config = {};
defaults = require('./config/config'), const defaults = require('./config/config');
rte = require('./view/TextEditorView'), let toolbar, actions, lastEl, globalRte;
CommandButtons = require('./model/CommandButtons'),
CommandButtonsView = require('./view/CommandButtonsView');
var tlbPfx, toolbar, commands;
var mainSelf;
return { return {
@ -39,77 +30,182 @@ module.exports = () => {
* @type {String} * @type {String}
* @private * @private
*/ */
name: 'rte', name: 'RichTextEditor',
/** /**
* Initialize module. Automatically called with a new instance of the editor * Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations * @param {Object} opts Options
* @private * @private
*/ */
init(config) { init(opts = {}) {
mainSelf = this; config = opts;
c = config || {};
for (var name in defaults) { for (let name in defaults) {
if (!(name in c)) if (!(name in config)) {
c[name] = defaults[name]; config[name] = defaults[name];
}
} }
var ppfx = c.pStylePrefix; const ppfx = config.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
tlbPfx = c.stylePrefix; if (ppfx) {
commands = new CommandButtons(c.commands); config.stylePrefix = ppfx + config.stylePrefix;
toolbar = new CommandButtonsView({ }
collection: commands,
config: c, this.pfx = config.stylePrefix;
}); actions = config.actions || [];
toolbar = document.createElement('div');
toolbar.className = `${ppfx}rte-toolbar`;
globalRte = this.initRte(document.createElement('div'));
//Avoid closing on toolbar clicking
on(toolbar, 'mousedown', e => e.stopPropagation());
return this; return this;
}, },
/** /**
* Add a new command to the toolbar * Post render callback
* @param {string} command Command name * @param {View} ev
* @param {Object} opts Command options * @private
* @return {Model} Added command */
postRender(ev) {
const canvas = ev.model.get('Canvas');
toolbar.style.pointerEvents = 'all';
canvas.getToolsEl().appendChild(toolbar);
},
/**
* Init the built-in RTE
* @param {HTMLElement} el
* @return {RichTextEditor}
* @private
*/
initRte(el) {
const pfx = this.pfx;
const actionbarContainer = toolbar;
const actionbar = this.actionbar;
const actions = this.actions || config.actions;
const classes = {
actionbar: `${pfx}actionbar`,
button: `${pfx}action`,
active: `${pfx}active`,
};
const rte = new RichTextEditor({
el,
classes,
actions,
actionbar,
actionbarContainer,
});
globalRte && globalRte.setEl(el);
if (rte.actionbar) {
this.actionbar = rte.actionbar;
}
if (rte.actions) {
this.actions = rte.actions;
}
return rte;
},
/**
* Add a new action to the built-in RTE toolbar
* @param {string} name Action name
* @param {Object} action Action options
* @example * @example
* var cm = rte.add('bold', { * rte.add('bold', {
* title: 'Make bold', * icon: '<b>B</b>',
* class: 'fa fa-bold', * attributes: {title: 'Bold',}
* result: rte => rte.exec('bold')
* }); * });
* // With arguments * rte.add('link', {
* var cm = rte.add('fontSize', { * icon: document.getElementById('t'),
* title: 'Font size', * attributes: {title: 'Link',}
* options: [ * // Example on it's easy to wrap a selected content
* {name: 'Big', value: 5}, * result: rte => rte.insertHTML(`<a href="#">${rte.selection()}</a>`)
* {name: 'Normal', value: 3},
* {name: 'Small', value: 1}
* ]
* }); * });
* // An example with fontSize
* rte.add('fontSize', {
* icon: `<select class="gjs-field">
* <option>1</option>
* <option>4</option>
* <option>7</option>
* </select>`,
* // Bind the 'result' on 'change' listener
* event: 'change',
* result: (rte, action) => rte.exec('fontSize', action.btn.firstChild.value),
* // Callback on any input change (mousedown, keydown, etc..)
* update: (rte, action) => {
* const value = rte.doc.queryCommandValue(action.name);
* if (value != 'false') { // value is a string
* action.btn.firstChild.value = value;
* }
* }
* })
*/ */
add(command, opts) { add(name, action = {}) {
var obj = opts || {}; action.name = name;
obj.command = command; globalRte.addAction(action, {sync: 1});
return commands.add(obj);
}, },
/** /**
* Get the command by its name * Get the action by its name
* @param {string} command Command name * @param {string} name Action name
* @return {Model} * @return {Object}
* @example * @example
* var cm = rte.get('fontSize'); * const action = rte.get('bold');
* // {name: 'bold', ...}
*/ */
get(command) { get(name) {
return commands.where({command})[0]; let result;
globalRte.getActions().forEach(action => {
if (action.name == name) {
result = action;
}
});
return result;
}, },
/** /**
* Returns the collection of commands * Get all actions
* @return {Collection} * @return {Array}
*/ */
getAll() { getAll() {
return commands; return globalRte.getActions();
},
/**
* Remove the action from the toolbar
* @param {string} name
* @return {Object} Removed action
* @example
* const action = rte.remove('bold');
* // {name: 'bold', ...}
*/
remove(name) {
const actions = this.getAll();
const action = this.get(name);
if (action) {
const btn = action.btn;
const index = actions.indexOf(action);
btn.parentNode.removeChild(btn);
actions.splice(index, 1);
}
return action;
},
/**
* Get the toolbar element
* @return {HTMLElement}
*/
getToolbarEl() {
return toolbar;
}, },
/** /**
@ -117,56 +213,47 @@ module.exports = () => {
* @private * @private
*/ */
udpatePosition() { udpatePosition() {
var u = 'px'; const un = 'px';
var canvas = c.em.get('Canvas'); const canvas = config.em.get('Canvas');
var pos = canvas.getTargetToElementDim(toolbar.el, this.lastEl, { const pos = canvas.getTargetToElementDim(toolbar, lastEl, {
event: 'rteToolbarPosUpdate', event: 'rteToolbarPosUpdate',
}); });
if (c.adjustToolbar) { if (config.adjustToolbar) {
// Move the toolbar down when the top canvas edge is reached // Move the toolbar down when the top canvas edge is reached
if (pos.top <= pos.canvasTop) { if (pos.top <= pos.canvasTop) {
pos.top = pos.elementTop + pos.elementHeight; pos.top = pos.elementTop + pos.elementHeight;
} }
} }
var toolbarStyle = toolbar.el.style; const toolbarStyle = toolbar.style;
toolbarStyle.top = pos.top + u; toolbarStyle.top = pos.top + un;
toolbarStyle.left = pos.left + u; toolbarStyle.left = pos.left + un;
}, },
/** /**
* Bind rich text editor to the element * Enable rich text editor on the element
* @param {View} view * @param {View} view Component view
* @param {Object} rte The instance of already defined RTE * @param {Object} rte The instance of already defined RTE
* @private * @private
* */ * */
attach(view, rte) { enable(view, rte) {
// lastEl will be used to place the RTE toolbar lastEl = view.el;
this.lastEl = view.el; const em = config.em;
var el = view.getChildrenContainer(); const el = view.getChildrenContainer();
var customRte = this.customRte; const customRte = this.customRte;
// If a custom RTE is defined toolbar.style.display = '';
if (customRte) { rte = customRte ? customRte.enable(el, rte) : this.initRte(el).enable();
rte = customRte.enable(el, rte);
} else {
$(el).wysiwyg({}).focus();
}
this.show(); if (em) {
if(c.em) {
setTimeout(this.udpatePosition.bind(this), 0); setTimeout(this.udpatePosition.bind(this), 0);
c.em.off('change:canvasOffset', this.udpatePosition, this); const event = 'change:canvasOffset canvasScroll';
c.em.on('change:canvasOffset', this.udpatePosition, this); em.off(event, this.udpatePosition, this);
// Update position on scrolling em.on(event, this.udpatePosition, this);
c.em.off('canvasScroll', this.udpatePosition, this); em.trigger('rte:enable', view, rte);
c.em.on('canvasScroll', this.udpatePosition, this);
} }
//Avoid closing edit mode clicking on toolbar
toolbar.$el.on('mousedown', this.disableProp);
return rte; return rte;
}, },
@ -176,78 +263,22 @@ module.exports = () => {
* @param {Object} rte The instance of already defined RTE * @param {Object} rte The instance of already defined RTE
* @private * @private
* */ * */
detach(view, rte) { disable(view, rte) {
var customRte = this.customRte; const em = config.em;
const customRte = this.customRte;
const style = toolbar.style;
var el = view.getChildrenContainer(); var el = view.getChildrenContainer();
if (customRte) {
view.model.set('content', el.innerHTML);
customRte.disable(el, rte);
} else {
$(el).wysiwyg('destroy');
}
this.hide();
toolbar.$el.off('mousedown', this.disableProp);
},
/**
* Unbind rich text editor from the element
* @param {View} view
* @param {Object} rte The instance of already defined RTE
* @private
* */
focus(view, rte) {
var customRte = this.customRte;
var el = view.getChildrenContainer();
if (customRte) { if (customRte) {
if(customRte.focus) customRte.disable(el, rte);
customRte.focus(el, rte);
} else { } else {
this.attach(view); rte.disable();
} }
},
/**
* Show the toolbar
* @private
* */
show() {
var toolbarStyle = toolbar.el.style;
toolbarStyle.display = "block";
},
/**
* Hide the toolbar
* @private
* */
hide() {
toolbar.el.style.display = "none";
},
/**
* Isolate the disable propagation method
* @private
* */
disableProp(e) {
e.stopPropagation();
},
/** style.display = 'none';
* Return toolbar element style.top = 0;
* @return {HTMLElement} style.left = 0;
* @private em && em.trigger('rte:disable', view, rte);
*/
getToolbarEl() {
return toolbar.el;
}, },
/**
* Render toolbar
* @return {HTMLElement}
* @private
*/
render() {
return toolbar.render().el;
}
}; };
}; };

21
src/rich_text_editor/model/CommandButton.js

@ -1,21 +0,0 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
idAttribute: 'command',
defaults: {
command: '',
type: '',
title: '',
class: '',
options: [],
},
initialize() {
var opts = this.get('options');
if(opts.length)
this.set('type', 'select');
},
});

6
src/rich_text_editor/model/CommandButtons.js

@ -1,6 +0,0 @@
var Backbone = require('backbone');
var CommandButton = require('./CommandButton');
module.exports = Backbone.Collection.extend({
model: CommandButton,
});

245
src/rich_text_editor/model/RichTextEditor.js

@ -0,0 +1,245 @@
// The initial version of this RTE was borrowed from https://github.com/jaredreich/pell
// and adapted to the GrapesJS's need
import {on, off} from 'utils/mixins'
const RTE_KEY = '_rte';
const defActions = {
bold: {
name: 'bold',
icon: '<b>B</b>',
attributes: {title: 'Bold'},
result: (rte) => rte.exec('bold')
},
italic: {
name: 'italic',
icon: '<i>I</i>',
attributes: {title: 'Italic'},
result: (rte) => rte.exec('italic')
},
underline: {
name: 'underline',
icon: '<u>U</u>',
attributes: {title: 'Underline'},
result: (rte) => rte.exec('underline')
},
strikethrough: {
name: 'strikethrough',
icon: '<strike>S</strike>',
attributes: {title: 'Strike-through'},
result: (rte) => rte.exec('strikeThrough')
},
link: {
icon: `<span style="transform:rotate(45deg)">&supdsub;</span>`,
name: 'link',
attributes: {
style: 'font-size:1.4rem;padding:0 4px 2px;',
title: 'Link',
},
result: (rte) => rte.insertHTML(`<a class="link" href="">${rte.selection()}</a>`)
}
}
export default class RichTextEditor {
constructor(settings = {}) {
const el = settings.el;
if (el[RTE_KEY]) {
return el[RTE_KEY];
}
el[RTE_KEY] = this;
this.setEl(el);
this.updateActiveActions = this.updateActiveActions.bind(this);
const settAct = settings.actions || [];
settAct.forEach((action, i) => {
if (typeof action === 'string') {
action = defActions[action];
} else if (defActions[action.name]) {
action = {...defActions[action.name], ...action};
}
settAct[i] = action;
});
const actions = settAct.length ? settAct :
Object.keys(defActions).map(action => defActions[action])
settings.classes = { ...{
actionbar: 'actionbar',
button: 'action',
active: 'active',
}, ...settings.classes};
const classes = settings.classes;
let actionbar = settings.actionbar;
this.actionbar = actionbar;
this.settings = settings;
this.classes = classes;
this.actions = actions;
if (!actionbar) {
const actionbarCont = settings.actionbarContainer;
actionbar = document.createElement('div');
actionbar.className = classes.actionbar;
actionbarCont.appendChild(actionbar);
this.actionbar = actionbar;
actions.forEach(action => this.addAction(action))
}
settings.styleWithCSS && this.exec('styleWithCSS');
this.syncActions();
return this;
}
setEl(el) {
this.el = el;
this.doc = el.ownerDocument;
}
updateActiveActions() {
this.getActions().forEach(action => {
const btn = action.btn;
const update = action.update;
const active = this.classes.active;
const name = action.name;
const doc = this.doc;
btn.className = btn.className.replace(active, '').trim();
// doc.queryCommandValue(name) != 'false'
if (doc.queryCommandState(name)) {
btn.className += ` ${active}`;
}
update && update(this, action);
})
}
enable() {
if (this.enabled) {
return this;
}
this.actionbarEl().style.display = '';
this.el.contentEditable = true;
on(this.el, 'mouseup keyup', this.updateActiveActions)
this.syncActions();
this.updateActiveActions();
this.el.focus();
this.enabled = 1;
return this;
}
disable() {
this.actionbarEl().style.display = 'none';
this.el.contentEditable = false;
off(this.el, 'mouseup keyup', this.updateActiveActions);
this.enabled = 0;
return this;
}
/**
* Sync actions with the current RTE
*/
syncActions() {
this.getActions().forEach(action => {
const event = action.event || 'click';
action.btn[`on${event}`] = e => {
action.result(this, action);
this.updateActiveActions();
};
})
}
/**
* Add new action to the actionbar
* @param {Object} action
* @param {Object} [opts={}]
*/
addAction(action, opts = {}) {
const sync = opts.sync;
const btn = document.createElement('span');
const icon = action.icon;
const attr = action.attributes || {};
btn.className = this.classes.button;
action.btn = btn;
for (let key in attr) {
btn.setAttribute(key, attr[key]);
}
if (typeof icon == 'string') {
btn.innerHTML = icon;
} else {
btn.appendChild(icon);
}
this.actionbarEl().appendChild(btn);
if (sync) {
this.actions.push(action);
this.syncActions();
}
}
/**
* Get the array of current actions
* @return {Array}
*/
getActions() {
return this.actions;
}
/**
* Returns the Selection instance
* @return {Selection}
*/
selection() {
return this.doc.getSelection()
}
/**
* Execute the command
* @param {string} command Command name
* @param {any} [value=null Command's arguments
*/
exec(command, value = null) {
this.doc.execCommand(command, false, value);
}
/**
* Get the actionbar element
* @return {HTMLElement}
*/
actionbarEl() {
return this.actionbar;
}
/**
* Set custom HTML to the selection, useful as the default 'insertHTML' command
* doesn't work in the same way on all browsers
* @param {string} value HTML string
*/
insertHTML(value) {
let lastNode;
const doc = this.doc;
const sel = doc.getSelection();
if (sel && sel.rangeCount) {
const node = doc.createElement('div');
const range = sel.getRangeAt(0);
range.deleteContents();
node.innerHTML = value;
Array.prototype.slice.call(node.childNodes).forEach(nd => {
range.insertNode(nd);
lastNode = nd;
})
sel.removeAllRanges();
sel.addRange(range);
this.el.focus();
}
}
}

42
src/rich_text_editor/view/CommandButtonSelectView.js

@ -1,42 +0,0 @@
var Backbone = require('backbone');
var CommandButtonView = require('./CommandButtonView');
module.exports = CommandButtonView.extend({
initialize(o, config) {
CommandButtonView.prototype.initialize.apply(this, arguments);
},
getInput() {
var m = this.model;
if(!this.input){
var cmd = m.get('command');
var input = '<select data-edit="' + cmd +'">';
var opts = m.get('options');
var label = m.get('title') || m.get('command');
input += '<option>' + label + '</option>';
for(var i = 0, len = opts.length; i < len; i++){
var opt = opts[i];
var value = opt.value;
var name = opt.name || value;
input += '<option value="' + value + '">' + name + '</option>';
}
input += '</select>';
this.input = $(input);
}
return this.input;
},
getInputCont() {
var input = this.getInput();
var pfx = this.ppfx;
var cont = $('<div class="'+pfx+'field '+pfx+'select"><div class="'+pfx+'sel-arrow"><div class="'+pfx+'d-s-arrow"></div></div></div>');
return cont.append(input);
},
render(...args) {
CommandButtonView.prototype.render.apply(this, args);
this.$el.html(this.getInputCont());
return this;
}
});

16
src/rich_text_editor/view/CommandButtonView.js

@ -1,16 +0,0 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
tagName: 'a',
initialize(o, config) {
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
this.className = this.config.stylePrefix + 'btn ' + this.model.get('class');
},
render() {
this.$el.addClass(this.className);
return this;
}
});

76
src/rich_text_editor/view/CommandButtonsView.js

@ -1,76 +0,0 @@
var Backbone = require('backbone');
var CommandButtonView = require('./CommandButtonView');
var CommandButtonSelectView = require('./CommandButtonSelectView');
module.exports = Backbone.View.extend({
attributes : {
'data-role': 'editor-toolbar',
},
initialize(o) {
this.config = o.config || {};
var pfx = this.config.stylePrefix || '';
this.id = pfx + this.config.toolbarId;
this.listenTo(this.collection, 'add', this.addTo);
this.$el.data('helper', 1);
},
/**
* Add new model to the collection
* @param {Model} model
* @private
* */
addTo(model) {
this.add(model);
},
/**
* Render new model inside the view
* @param {Model} model
* @param {Object} fragment Fragment collection
* @private
* */
add(model, fragment) {
var frag = fragment || null;
var viewObj = CommandButtonView;
switch (model.get('type')) {
case 'select':
viewObj = CommandButtonSelectView;
break;
}
var args = model.get('args');
var attrs = {
'title': model.get('title'),
'data-edit': model.get('command'),
};
if(args)
attrs['data-args'] = args;
var view = new viewObj({
model,
attributes: attrs,
}, this.config);
var rendered = view.render().el;
if(frag)
frag.appendChild(rendered);
else
this.$el.append(rendered);
},
render() {
var frag = document.createDocumentFragment();
this.$el.empty();
this.collection.each(function(model){
this.add(model, frag);
}, this);
this.$el.append(frag);
this.$el.attr('id', this.id );
return this;
}
});

231
src/rich_text_editor/view/TextEditorView.js

@ -1,231 +0,0 @@
var readFileIntoDataUrl = fileInfo => {
var loader = $.Deferred(),
fReader = new FileReader();
fReader.onload = e => {
loader.resolve(e.target.result);
};
fReader.onerror = loader.reject;
fReader.onprogress = loader.notify;
fReader.readAsDataURL(fileInfo);
return loader.promise();
};
$.fn.cleanHtml = function () {
var html = $(this).html();
return html && html.replace(/(<br>|\s|<div><br><\/div>|&nbsp;)*$/, '');
};
$.fn.wysiwyg = function (userOptions) {
var editor = this,
selectedRange,
options,
toolbarBtnSelector,
updateToolbar = () => {
var actCls = options.activeToolbarClass;
if (actCls) {
$(options.toolbarSelector).find(toolbarBtnSelector).each(function () {
var el = $(this);
var command = el.data(options.commandRole);
var doc = editor.get(0).ownerDocument;
if (doc.queryCommandState(command)) {
el.addClass(actCls);
} else {
el.removeClass(actCls);
}
});
}
},
execCommand = (commandWithArgs, valueArg) => {
var commandArr = commandWithArgs.split(' '),
command = commandArr.shift(),
args = commandArr.join(' ') + (valueArg || '');
//document.execCommand("insertHTML", false, "<span class='own-class'>"+ document.getSelection()+"</span>");
editor.get(0).ownerDocument.execCommand("styleWithCSS", false, true);
editor.get(0).ownerDocument.execCommand(command, 0, args);
updateToolbar();
editor.trigger('change');
},
/*
bindHotkeys = function (hotKeys) {
$.each(hotKeys, function (hotkey, command) {
editor.keydown(hotkey, function (e) {
if (editor.attr('contenteditable') && editor.is(':visible')) {
e.preventDefault();
e.stopPropagation();
execCommand(command);
}
}).keyup(hotkey, function (e) {
if (editor.attr('contenteditable') && editor.is(':visible')) {
e.preventDefault();
e.stopPropagation();
}
});
});
},
*/
getCurrentRange = () => {
var sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
return sel.getRangeAt(0);
}
},
saveSelection = () => {
selectedRange = getCurrentRange();
},
restoreSelection = () => {
var selection = window.getSelection();
if (selectedRange) {
try {
selection.removeAllRanges();
} catch (ex) {
document.body.createTextRange().select();
document.selection.empty();
}
selection.addRange(selectedRange);
}
},
insertFiles = files => {
editor.focus();
$.each(files, (idx, fileInfo) => {
if (/^image\//.test(fileInfo.type)) {
$.when(readFileIntoDataUrl(fileInfo)).done(dataUrl => {
execCommand('insertimage', dataUrl);
}).fail(e => {
options.fileUploadError("file-reader", e);
});
} else {
options.fileUploadError("unsupported-file-type", fileInfo.type);
}
});
},
markSelection = (input, color) => {
restoreSelection();
if (document.queryCommandSupported('hiliteColor')) {
document.execCommand('hiliteColor', 0, color || 'transparent');
}
saveSelection();
input.data(options.selectionMarker, color);
},
bindToolbar = (toolbar, options) => {
toolbar.find(toolbarBtnSelector).unbind().click(function () {
restoreSelection();
//editor.focus(); // cause defocus on selects
var doc = editor.get(0).ownerDocument;
var el = $(this);
var comm = el.data(options.commandRole);
var args = el.data('args');
if(args){
args = args.replace('${content}', doc.getSelection());
execCommand(comm, args);
}else{
doc.execCommand(comm);
}
saveSelection();
});
toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
var dName = '[data-' + options.commandRole + ']';
toolbar.find('select'+dName).on('webkitspeechchange change', function(){
var newValue = this.value;
restoreSelection();
if (newValue) {
editor.focus();
execCommand($(this).data(options.commandRole), newValue);
}
saveSelection();
});
toolbar.find('input[type=text]'+dName,', select'+dName).on('webkitspeechchange change', function () {
var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
this.value = '';
restoreSelection();
if (newValue) {
editor.focus();
execCommand($(this).data(options.commandRole), newValue);
}
saveSelection();
}).on('focus', function () {
var input = $(this);
if (!input.data(options.selectionMarker)) {
markSelection(input, options.selectionColor);
input.focus();
}
}).on('blur', function () {
var input = $(this);
if (input.data(options.selectionMarker)) {
markSelection(input, false);
}
});
toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
restoreSelection();
if (this.type === 'file' && this.files && this.files.length > 0) {
insertFiles(this.files);
}
saveSelection();
this.value = '';
});
},
initFileDrops = () => {
editor.on('dragenter dragover', false)
.on('drop', e => {
var dataTransfer = e.originalEvent.dataTransfer;
e.stopPropagation();
e.preventDefault();
if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
insertFiles(dataTransfer.files);
}
});
};
/** Disable the editor
* @date 2015-03-19 */
if(typeof userOptions=='string' && userOptions=='destroy'){
editor.attr('contenteditable', false).unbind('mouseup keyup mouseout dragenter dragover');
$(window).unbind('touchend');
return this;
}
options = $.extend({}, $.fn.wysiwyg.defaults, userOptions);
var dName = '[data-' + options.commandRole + ']';
toolbarBtnSelector = 'a'+dName+',button'+dName+',input[type=button]'+dName+', select'+dName;
//bindHotkeys(options.hotKeys);
if (options.dragAndDropImages) {
initFileDrops();
}
bindToolbar($(options.toolbarSelector), options);
editor.attr('contenteditable', true).on('mouseup keyup mouseout', () => {
saveSelection();
updateToolbar();
});
$(window).bind('touchend', e => {
var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
currentRange = getCurrentRange(),
clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
if (!clear || isInside) {
saveSelection();
updateToolbar();
}
});
return this;
};
$.fn.wysiwyg.defaults = {
/*
hotKeys: {
'ctrl+b meta+b': 'bold',
'ctrl+i meta+i': 'italic',
'ctrl+u meta+u': 'underline',
'ctrl+z meta+z': 'undo',
'ctrl+y meta+y meta+shift+z': 'redo',
'ctrl+l meta+l': 'justifyleft',
'ctrl+r meta+r': 'justifyright',
'ctrl+e meta+e': 'justifycenter',
'ctrl+j meta+j': 'justifyfull',
'shift+tab': 'outdent',
'tab': 'indent'
},
*/
toolbarSelector: '[data-role=editor-toolbar]',
commandRole: 'edit',
activeToolbarClass: 'btn-info',
selectionMarker: 'edit-focus-marker',
selectionColor: 'darkgrey',
dragAndDropImages: true,
fileUploadError(reason, detail) { console.log("File upload error", reason, detail); }
};
module.exports = $;

2
src/selector_manager/model/Selector.js

@ -62,7 +62,7 @@ const Selector = Backbone.Model.extend({
* @private * @private
*/ */
escapeName(name) { escapeName(name) {
return `${name}`.trim().replace(/([^a-z0-9\w]+)/gi, '-'); return `${name}`.trim().replace(/([^a-z0-9\w-]+)/gi, '-');
}, },
}); });

28
src/selector_manager/view/ClassTagView.js

@ -9,8 +9,6 @@ module.exports = Backbone.View.extend({
</span> </span>
<span id="<%= pfx %>close">&Cross;</span>`), <span id="<%= pfx %>close">&Cross;</span>`),
events: {},
initialize(o) { initialize(o) {
this.config = o.config || {}; this.config = o.config || {};
this.coll = o.coll || null; this.coll = o.coll || null;
@ -22,6 +20,7 @@ module.exports = Backbone.View.extend({
this.closeId = this.pfx + 'close'; this.closeId = this.pfx + 'close';
this.chkId = this.pfx + 'checkbox'; this.chkId = this.pfx + 'checkbox';
this.labelId = this.pfx + 'tag-label'; this.labelId = this.pfx + 'tag-label';
this.events = {};
this.events['click #' + this.closeId ] = 'removeTag'; this.events['click #' + this.closeId ] = 'removeTag';
this.events['click #' + this.chkId ] = 'changeStatus'; this.events['click #' + this.chkId ] = 'changeStatus';
this.events['dblclick #' + this.labelId ] = 'startEditTag'; this.events['dblclick #' + this.labelId ] = 'startEditTag';
@ -77,17 +76,15 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
removeTag(e) { removeTag(e) {
var comp = this.target.get('selectedComponent'); const em = this.target;
const model = this.model;
if(comp) const coll = this.coll;
comp.get('classes').remove(this.model); const el = this.el;
const sel = em && em.get('selectedComponent');
if(this.coll){ sel && sel.get & sel.get('classes').remove(model);
this.coll.remove(this.model); coll && coll.remove(model);
this.target.trigger('targetClassRemoved'); setTimeout(() => this.remove(), 0);
} em && em.trigger('targetClassRemoved');
this.remove();
}, },
/** /**
@ -115,8 +112,11 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
updateInputLabel() { updateInputLabel() {
if(!this.$labelInput) if(!this.$labelInput) {
this.$labelInput = this.$el.find('input'); this.$labelInput = this.$el.find('input');
}
this.$labelInput.prop(this.inputProp, true);
var size = this.$labelInput.val().length - 1; var size = this.$labelInput.val().length - 1;
size = size < 1 ? 1 : size; size = size < 1 ? 1 : size;
this.$labelInput.attr('size', size); this.$labelInput.attr('size', size);

4
src/selector_manager/view/ClassTagsView.js

@ -97,7 +97,7 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
startNewTag(e) { startNewTag(e) {
this.$addBtn.hide(); this.$addBtn.get(0).style.display = 'none';
this.$input.show().focus(); this.$input.show().focus();
}, },
@ -107,7 +107,7 @@ module.exports = Backbone.View.extend({
* @private * @private
*/ */
endNewTag(e) { endNewTag(e) {
this.$addBtn.show(); this.$addBtn.get(0).style.display = '';
this.$input.hide().val(''); this.$input.hide().val('');
}, },

5
src/storage_manager/config/config.js

@ -32,9 +32,12 @@ module.exports = {
checkLocal: 1, checkLocal: 1,
// ONLY FOR REMOTE STORAGE // ONLY FOR REMOTE STORAGE
// Custom params that should be passed with each store/load request // Custom parameters to pass with the remote storage request, eg. csrf token
params: {}, params: {},
// Custom headers for the remote storage request
headers: {},
// Endpoint where to save all stuff // Endpoint where to save all stuff
urlStore: '', urlStore: '',

3
src/storage_manager/index.js

@ -57,7 +57,8 @@ module.exports = () => {
*/ */
init(config) { init(config) {
c = config || {}; c = config || {};
for (var name in defaults){
for (var name in defaults) {
if (!(name in c)) if (!(name in c))
c[name] = defaults[name]; c[name] = defaults[name];
} }

165
src/storage_manager/model/RemoteStorage.js

@ -1,6 +1,9 @@
var Backbone = require('backbone'); import fetch from 'utils/fetch';
import { isUndefined } from 'underscore';
module.exports = Backbone.Model.extend({ module.exports = require('backbone').Model.extend({
fetch,
defaults: { defaults: {
urlStore: '', urlStore: '',
@ -12,67 +15,121 @@ module.exports = Backbone.Model.extend({
}, },
/** /**
* Triggered before the request is started
* @private * @private
*/ */
store(data, clb) { onStart() {
var fd = {}, const em = this.get('em');
params = this.get('params'); const before = this.get('beforeSend');
before && before();
for(var k in data) em && em.trigger('storage:start');
fd[k] = data[k]; },
for(var key in params) /**
fd[key] = params[key]; * Triggered on request error
* @param {Object} err Error
let req = $.ajax({ * @private
url: this.get('urlStore'), */
beforeSend: this.get('beforeSend'), onError(err) {
complete: this.get('onComplete'), const em = this.get('em');
method: 'POST', console.error(err);
dataType: 'json', em && em.trigger('storage:error', err);
contentType: this.get('contentTypeJson') ? 'application/json; charset=utf-8': 'x-www-form-urlencoded', this.onEnd(err);
data: this.get('contentTypeJson') ? JSON.stringify(fd): fd,
});
// Assign always callback when possible
req && req.always && req.always(() => {
if (typeof clb == 'function') {
clb();
}
});
}, },
/** /**
* Triggered after the request is ended
* @param {Object|string} res End result
* @private * @private
*/ */
onEnd(res) {
const em = this.get('em');
em && em.trigger('storage:end', res);
},
/**
* Triggered on request response
* @param {string} text Response text
* @private
*/
onResponse(text, clb) {
const em = this.get('em');
const complete = this.get('onComplete');
const typeJson = this.get('contentTypeJson');
const res = typeJson && typeof text === 'string' ? JSON.parse(text): text;
complete && complete(res);
clb && clb(res);
em && em.trigger('storage:response', res);
this.onEnd(text);
},
store(data, clb) {
const body = {};
for (let key in data) {
body[key] = data[key];
}
this.request(this.get('urlStore'), {body}, clb);
},
load(keys, clb) { load(keys, clb) {
var result = {}, this.request(this.get('urlLoad'), {body: {keys}}, clb);
fd = {}, },
params = this.get('params');
/**
for(var key in params) * Execute remote request
fd[key] = params[key]; * @param {string} url Url
* @param {Object} [opts={}] Options
fd.keys = keys; * @param {[type]} [clb=null] Callback
* @private
let req = $.ajax({ */
url: this.get('urlLoad'), request(url, opts = {}, clb = null) {
beforeSend: this.get('beforeSend'), const typeJson = this.get('contentTypeJson');
complete: this.get('onComplete'), const headers = this.get('headers') || {};
data: fd, const params = this.get('params');
async: false, const reqHead = 'X-Requested-With';
method: 'GET', const typeHead = 'Content-Type';
}).done(d => { const bodyObj = opts.body || {};
result = d; let body;
});
for (let param in params) {
// Assign always callback when possible bodyObj[param] = params[param];
req && req.always && req.always((res) => { }
if (typeof clb == 'function') {
clb(res); if (isUndefined(headers[reqHead])) {
headers[reqHead] = 'XMLHttpRequest';
}
// With `fetch`, have to send FormData without any 'Content-Type'
// https://stackoverflow.com/questions/39280438/fetch-missing-boundary-in-multipart-form-data-post
if (isUndefined(headers[typeHead]) && typeJson) {
headers[typeHead] = 'application/json; charset=utf-8';
}
if (typeJson) {
body = JSON.stringify(bodyObj);
} else {
body = new FormData();
for (let bodyKey in bodyObj) {
body.append(bodyKey, bodyObj[bodyKey]);
} }
}); }
return result;
this.onStart();
this.fetch(url, {
method: opts.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.onResponse(text, clb))
.catch(err => this.onError(err));
}, },
}); });

44
src/style_manager/index.js

@ -7,6 +7,9 @@
* * [getProperty](#getproperty) * * [getProperty](#getproperty)
* * [getProperties](#getproperties) * * [getProperties](#getproperties)
* * [getModelToStyle](#getmodeltostyle) * * [getModelToStyle](#getmodeltostyle)
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
* * [render](#render) * * [render](#render)
* *
* With Style Manager you basically build categories (called sectors) of CSS properties which could * With Style Manager you basically build categories (called sectors) of CSS properties which could
@ -52,7 +55,9 @@ module.exports = () => {
var c = {}, var c = {},
defaults = require('./config/config'), defaults = require('./config/config'),
Sectors = require('./model/Sectors'), Sectors = require('./model/Sectors'),
Properties = require('./model/Properties'),
SectorsView = require('./view/SectorsView'); SectorsView = require('./view/SectorsView');
let properties;
var sectors, SectView; var sectors, SectView;
return { return {
@ -88,6 +93,7 @@ module.exports = () => {
if(ppfx) if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix; c.stylePrefix = ppfx + c.stylePrefix;
properties = new Properties();
sectors = new Sectors(c.sectors); sectors = new Sectors(c.sectors);
SectView = new SectorsView({ SectView = new SectorsView({
collection: sectors, collection: sectors,
@ -252,6 +258,44 @@ module.exports = () => {
return model; return model;
}, },
/**
* Add new property 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) => {
* if (value && value.type == 'my-type') {
* return value;
* }
* },
* })
*/
addType(id, definition) {
properties.addType(id, definition);
},
/**
* Get type
* @param {string} id Type ID
* @return {Object} Type definition
*/
getType(id) {
return properties.getType(id);
},
/**
* Get all types
* @return {Array}
*/
getTypes() {
return properties.getTypes();
},
/** /**
* Render sectors and properties * Render sectors and properties
* @return {HTMLElement} * @return {HTMLElement}

25
src/style_manager/model/Layer.js

@ -1,17 +1,20 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({ module.exports = Backbone.Model.extend({
defaults: { defaults: {
index: '', index: '',
value: '', value: '',
values: {}, values: {},
active: true, active: false,
preview: false, preview: false,
properties: [],
}, },
initialize() { initialize() {
const Properties = require('./Properties');
const properties = this.get('properties');
var value = this.get('value'); var value = this.get('value');
this.set('properties', properties instanceof Properties ?
properties : new Properties(properties));
// If there is no value I'll try to get it from values // If there is no value I'll try to get it from values
// I need value setted to make preview working // I need value setted to make preview working
@ -27,4 +30,20 @@ module.exports = Backbone.Model.extend({
} }
}, },
getPropertyValue(property) {
let result = '';
this.get('properties').each(prop => {
if (prop.get('property') == property) {
result = prop.getFullValue();
}
});
return result;
},
getFullValue() {
let result = [];
this.get('properties').each(prop => result.push(prop.getFullValue()));
return result.join(' ');
}
}); });

99
src/style_manager/model/Layers.js

@ -1,5 +1,4 @@
var Backbone = require('backbone'); const Layer = require('./Layer');
var Layer = require('./Layer');
module.exports = Backbone.Collection.extend({ module.exports = Backbone.Collection.extend({
@ -18,6 +17,102 @@ module.exports = Backbone.Collection.extend({
onReset() { onReset() {
this.idx = 1; this.idx = 1;
},
/**
* Get layers from a value string (for not detached properties),
* example of input:
* `layer1Value, layer2Value, layer3Value, ...`
* @param {string} value
* @return {Array}
* @private
*/
getLayersFromValue(value) {
const layers = [];
// Remove spaces inside functions, eg:
// From: 1px 1px rgba(2px, 2px, 2px), 2px 2px rgba(3px, 3px, 3px)
// To: 1px 1px rgba(2px,2px,2px), 2px 2px rgba(3px,3px,3px)
value.replace(/\(([\w\s,.]*)\)/g, match => {
var cleaned = match.replace(/,\s*/g, ',');
value = value.replace(match, cleaned);
});
const layerValues = value ? value.split(', ') : [];
layerValues.forEach(layerValue => {
layers.push({properties: this.properties.parseValue(layerValue)});
});
return layers;
},
/**
* Get layers from a style object (for detached properties),
* example of input:
* {
* subPropname1: sub-propvalue11, sub-propvalue12, sub-propvalue13, ...
* subPropname2: sub-propvalue21, sub-propvalue22, sub-propvalue23, ...
* subPropname3: sub-propvalue31, sub-propvalue32, sub-propvalue33, ...
* }
* @param {Object} styleObj
* @return {Array}
* @private
*/
getLayersFromStyle(styleObj) {
const layers = [];
const properties = this.properties;
const propNames = properties.pluck('property');
properties.each(propModel => {
const style = styleObj[propModel.get('property')];
const values = style ? style.split(', ') : [];
values.forEach((value, i) => {
value = propModel.parseValue(value.trim());
const layer = layers[i];
const propertyObj = Object.assign({}, propModel.attributes, {value});
if (layer) {
layer.properties.push(propertyObj);
} else {
layers[i] = {
properties: [propertyObj]
};
}
});
});
// Now whit all layers in, will check missing properties
layers.forEach(layer => {
const layerProprs = layer.properties.map(prop => prop.property);
properties.each(propModel => {
const propertyName = propModel.get('property');
if (layerProprs.indexOf(propertyName) < 0) {
layer.properties.push(Object.assign({}, propModel.attributes))
}
})
});
return layers;
},
active(index) {
this.each(layer => layer.set('active', 0));
const layer = this.at(index);
layer && layer.set('active', 1);
},
getFullValue() {
let result = [];
this.each(layer => result.push(layer.getFullValue()));
return result.join(', ');
},
getPropertyValues(property) {
const result = [];
this.each(layer => {
const value = layer.getPropertyValue(property);
value && result.push(value);
});
return result.join(', ');
} }
}); });

43
src/style_manager/model/Properties.js

@ -57,6 +57,15 @@ module.exports = require('backbone').Collection.extend(TypeableCollection).exten
return value; return value;
} }
} }
},{
id: 'slider',
model: require('./PropertySlider'),
view: require('./../view/PropertySliderView'),
isType(value) {
if (value && value.type == 'slider') {
return value;
}
}
},{ },{
id: 'integer', id: 'integer',
model: require('./PropertyInteger'), model: require('./PropertyInteger'),
@ -75,5 +84,37 @@ module.exports = require('backbone').Collection.extend(TypeableCollection).exten
return value; return value;
} }
} }
] ],
deepClone() {
const collection = this.clone();
collection.reset(collection.map(model => {
const cloned = model.clone();
cloned.typeView = model.typeView;
return cloned;
}));
return collection;
},
/**
* Parse a value and return an array splitted by properties
* @param {string} value
* @return {Array}
* @return
*/
parseValue(value) {
const properties = [];
const values = value.split(' ');
values.forEach((value, i) => {
const property = this.at(i);
properties.push(Object.assign({}, property.attributes, {value}));
});
return properties;
},
getFullValue() {
let result = '';
this.each(model => result += `${model.getFullValue()} `);
return result.trim();
}
}); });

3
src/style_manager/model/PropertyComposite.js

@ -48,8 +48,7 @@ module.exports = Property.extend({
} }
let result = ''; let result = '';
this.get('properties').each(prop => result += `${prop.getFullValue()} `); return this.get('properties').getFullValue();
return result.trim();
}, },
}); });

2
src/style_manager/model/PropertyFactory.js

@ -121,7 +121,7 @@ module.exports = () => ({
obj.defaults = 1; obj.defaults = 1;
break; break;
case 'box-shadow-blur': case 'box-shadow-blur':
obj.defaults = 5; obj.defaults = '5px';
break; break;
case 'min-height': case 'min-width': case 'max-height': case 'max-width': case 'min-height': case 'min-width': case 'max-height': case 'max-width':
case 'width': case 'height': case 'width': case 'height':

18
src/style_manager/model/PropertyInteger.js

@ -8,8 +8,26 @@ module.exports = Property.extend({
// Selected unit, eg. 'px' // Selected unit, eg. 'px'
unit: '', unit: '',
// Integer value steps
step: 1,
// Minimum value
min: '',
// Maximum value
max: '',
}), }),
init() {
const unit = this.get('unit');
const units = this.get('units');
if (units.length && !unit) {
this.set('unit', units[0]);
}
},
getFullValue() { getFullValue() {
let value = this.get('value') + this.get('unit'); let value = this.get('value') + this.get('unit');
return Property.prototype.getFullValue.apply(this, [value]); return Property.prototype.getFullValue.apply(this, [value]);

9
src/style_manager/model/PropertySlider.js

@ -0,0 +1,9 @@
const Property = require('./PropertyInteger');
module.exports = Property.extend({
defaults: Object.assign({}, Property.prototype.defaults, {
showInput: 1,
}),
});

12
src/style_manager/model/PropertyStack.js

@ -14,17 +14,13 @@ module.exports = Property.extend({
init() { init() {
Property.prototype.init.apply(this, arguments); Property.prototype.init.apply(this, arguments);
const layers = this.get('layers'); const layers = this.get('layers');
this.set('layers', new Layers(layers)); const layersColl = new Layers(layers);
layersColl.properties = this.get('properties');
this.set('layers', layersColl);
}, },
getFullValue() { getFullValue() {
if (this.get('detached')) { return this.get('detached') ? '' : this.get('layers').getFullValue();
return '';
}
const layers = this.get('layers');
let val = layers.length ? layers.pluck('value').join(', ') : '';
return val.trim();
}, },
}); });

5
src/style_manager/model/Sectors.js

@ -1,6 +1,5 @@
var Backbone = require('backbone'); const Sector = require('./Sector');
var Sector = require('./Sector');
module.exports = Backbone.Collection.extend({ module.exports = require('backbone').Collection.extend({
model: Sector, model: Sector,
}); });

242
src/style_manager/view/LayerView.js

@ -1,35 +1,42 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
events:{ events: {
'click': 'updateIndex', click: 'active',
'click [data-close-layer]': 'remove',
'mousedown [data-move-layer]': 'initSorter'
},
template(model) {
const pfx = this.pfx;
const label = `Layer ${model.get('index')}`;
return `
<div id="${pfx}move" data-move-layer>
<i class="fa fa-arrows"></i>
</div>
<div id="${pfx}label">${label}</div>
<div id="${pfx}preview-box">
<div id="${pfx}preview" data-preview></div>
</div>
<div id="${pfx}close-layer" class="${pfx}btn-close" data-close-layer>
&Cross;
</div>
<div id="${pfx}inputs" data-properties></div>
<div style="clear:both"></div>
`
}, },
template: _.template(` initialize(o = {}) {
<div id="<%= pfx %>move">
<i class="fa fa-arrows"></i>
</div>
<div id="<%= pfx %>label"><%= label %></div>
<div id="<%= pfx %>preview-box">
<div id="<%= pfx %>preview"></div>
</div>
<div id="<%= pfx %>close-layer" class="<%= pfx %>btn-close">&Cross;</div>
<div id="<%= pfx %>inputs"></div>
<div style="clear:both"></div>`),
initialize(o) {
let model = this.model; let model = this.model;
this.stackModel = o.stackModel || {}; this.stackModel = o.stackModel || {};
this.config = o.config || {}; this.config = o.config || {};
this.pfx = this.config.stylePrefix || ''; this.pfx = this.config.stylePrefix || '';
this.className = this.pfx + 'layer';
this.sorter = o.sorter || null; this.sorter = o.sorter || null;
this.propsConfig = o.propsConfig || {};
this.customPreview = o.onPreview;
this.listenTo(model, 'destroy remove', this.remove); this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:value', this.valueChanged); this.listenTo(model, 'change:active', this.updateVisibility);
this.listenTo(model, 'change:props', this.showProps); this.listenTo(model.get('properties'), 'change', this.updatePreview);
this.events['click #' + this.pfx + 'close-layer'] = 'remove';
this.events['mousedown > #' + this.pfx + 'move'] = 'initSorter';
if (!model.get('preview')) { if (!model.get('preview')) {
this.$el.addClass(this.pfx + 'no-preview'); this.$el.addClass(this.pfx + 'no-preview');
@ -39,7 +46,6 @@ module.exports = Backbone.View.extend({
model.view = this; model.view = this;
model.set({droppable: 0, draggable: 1}); model.set({droppable: 0, draggable: 1});
this.$el.data('model', model); this.$el.data('model', model);
this.delegateEvents();
}, },
/** /**
@ -51,36 +57,24 @@ module.exports = Backbone.View.extend({
this.sorter.startSort(this.el); this.sorter.startSort(this.el);
}, },
/**
* Returns properties
* @return {Collection|null}
*/
getProps() {
if(this.stackModel.get)
return this.stackModel.get('properties');
else
return null;
},
/** remove(e) {
* Emitted when the value is changed if(e && e.stopPropagation)
*/ e.stopPropagation();
valueChanged() {
var preview = this.model.get('preview'); const model = this.model;
const collection = model.collection;
if(!preview) const stackModel = this.stackModel;
return;
Backbone.View.prototype.remove.apply(this, arguments);
if(!this.$preview)
this.$preview = this.$el.find('#' + this.pfx + 'preview'); if (collection.contains(model)) {
collection.remove(model);
var prw = ''; }
var props = this.getProps();
var previewEl = this.$preview; if (stackModel && stackModel.set) {
if (typeof preview === 'function') { stackModel.set({stackIndex: null}, {silent: true});
preview(props, previewEl); stackModel.trigger('updateValue');
} else {
this.onPreview(props, previewEl);
} }
}, },
@ -89,102 +83,88 @@ module.exports = Backbone.View.extend({
* @param {Collection} props * @param {Collection} props
* @param {Element} $el * @param {Element} $el
*/ */
onPreview(props, $el) { onPreview(value) {
var aV = this.model.get('value').split(' '); const values = value.split(' ');
var lim = 3; const lim = 3;
var nV = ''; const result = [];
props.each((p, index) => { this.model.get('properties').each((prop, index) => {
var v = aV[index] || ''; var value = values[index] || '';
if(v){
if(p.get('type') == 'integer'){ if (value) {
var vI = parseInt(v, 10), if (prop.get('type') == 'integer') {
u = v.replace(vI,''); let valueInt = parseInt(value, 10);
vI = !isNaN(vI) ? vI : 0; let unit = value.replace(valueInt,'');
if(vI > lim) valueInt = !isNaN(valueInt) ? valueInt : 0;
vI = lim; valueInt = valueInt > lim ? lim : valueInt;
if(vI < -lim) valueInt = valueInt < -lim ? -lim : valueInt;
vI = -lim; value = valueInt + unit;
v = vI + u;
} }
} }
nV += v + ' ';
result.push(value);
}); });
if(this.stackModel.get){ return result.join(' ');
var property = this.stackModel.get('property');
if(property)
this.$preview.get(0).style[property] = nV;
}
}, },
/** updatePreview() {
* Show inputs on this layer const stackModel = this.stackModel;
* */ const customPreview = this.customPreview;
showProps() { const previewEl = this.getPreviewEl();
this.$props = this.model.get('props'); const value = this.model.getFullValue();
this.$el.find('#' + this.pfx + 'inputs').html(this.$props.show()); const preview = customPreview ? customPreview(value) : this.onPreview(value);
this.model.set({props: null }, {silent: true });
},
/** @inheritdoc */
remove(e) {
// Prevent from revoming all events on props
if(this.$props)
this.$props.detach();
if(e && e.stopPropagation)
e.stopPropagation();
Backbone.View.prototype.remove.apply(this, arguments);
//--- if (preview && stackModel && previewEl) {
if(this.model.collection.contains(this.model)) previewEl.style[stackModel.get('property')] = preview;
this.model.collection.remove(this.model);
if(this.stackModel && this.stackModel.set){
this.stackModel.set({stackIndex: null}, {silent: true});
this.stackModel.trigger('updateValue');
} }
}, },
/** getPropertiesWrapper() {
* Update index if (!this.propsWrapEl) {
* @param Event this.propsWrapEl = this.el.querySelector('[data-properties]');
* }
* @return void return this.propsWrapEl;
* */
updateIndex(e) {
var i = this.getIndex();
this.stackModel.set('stackIndex', i);
if(this.model.collection)
this.model.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'active');
}, },
/** getPreviewEl() {
* Fetch model index if (!this.previewEl) {
* @return {number} Index this.previewEl = this.el.querySelector('[data-preview]');
*/
getIndex() {
var index = 0;
var model = this.model;
if (model.collection) {
index = model.collection.indexOf(model);
} }
return this.previewEl;
},
active() {
const model = this.model;
const collection = model.collection;
collection.active(collection.indexOf(model));
},
return index; updateVisibility() {
const pfx = this.pfx;
const wrapEl = this.getPropertiesWrapper();
const active = this.model.get('active');
wrapEl.style.display = active ? '' : 'none';
this.$el[active ? 'addClass' : 'removeClass'](`${pfx}active`);
}, },
render() { render() {
this.$el.html( this.template({ const PropertiesView = require('./PropertiesView');
label: 'Layer ' + this.model.get('index'), const propsConfig = this.propsConfig;
pfx: this.pfx, const className = `${this.pfx}layer`;
})); const model = this.model;
this.$el.attr('class', this.className); const el = this.el;
this.valueChanged(); const properties = new PropertiesView({
collection: model.get('properties'),
config: this.config,
customValue: propsConfig.customValue,
propTarget: propsConfig.propTarget,
onChange: propsConfig.onChange,
}).render().el;
el.innerHTML = this.template(model);
el.className = className;
this.getPropertiesWrapper().appendChild(properties);
this.updateVisibility();
this.updatePreview();
return this; return this;
}, },

23
src/style_manager/view/LayersView.js

@ -9,6 +9,7 @@ module.exports = Backbone.View.extend({
this.preview = o.preview; this.preview = o.preview;
this.pfx = this.config.stylePrefix || ''; this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || ''; this.ppfx = this.config.pStylePrefix || '';
this.propsConfig = o.propsConfig;
let pfx = this.pfx; let pfx = this.pfx;
let ppfx = this.ppfx; let ppfx = this.ppfx;
let collection = this.collection; let collection = this.collection;
@ -55,23 +56,27 @@ module.exports = Backbone.View.extend({
* */ * */
addToCollection(model, fragmentEl, index) { addToCollection(model, fragmentEl, index) {
var fragment = fragmentEl || null; var fragment = fragmentEl || null;
var viewObject = LayerView; const stackModel = this.stackModel;
const config = this.config;
const sorter = this.sorter;
const propsConfig = this.propsConfig;
if(typeof this.preview !== 'undefined'){ if(typeof this.preview !== 'undefined'){
model.set('preview', this.preview); model.set('preview', this.preview);
} }
var view = new viewObject({ var view = new LayerView({
model, model,
stackModel: this.stackModel, config,
config: this.config, sorter,
sorter: this.sorter stackModel,
propsConfig
}); });
var rendered = view.render().el; var rendered = view.render().el;
if(fragment){ if (fragment) {
fragment.appendChild( rendered ); fragment.appendChild(rendered);
}else{ } else {
if(typeof index != 'undefined'){ if(typeof index != 'undefined'){
var method = 'before'; var method = 'before';
// If the added model is the last of collection // If the added model is the last of collection

21
src/style_manager/view/PropertiesView.js

@ -1,12 +1,11 @@
var Backbone = require('backbone'); const PropertyView = require('./PropertyView');
var PropertyView = require('./PropertyView'); const PropertyIntegerView = require('./PropertyIntegerView');
var PropertyIntegerView = require('./PropertyIntegerView'); const PropertyRadioView = require('./PropertyRadioView');
var PropertyRadioView = require('./PropertyRadioView'); const PropertySelectView = require('./PropertySelectView');
var PropertySelectView = require('./PropertySelectView'); const PropertyColorView = require('./PropertyColorView');
var PropertyColorView = require('./PropertyColorView'); const PropertyFileView = require('./PropertyFileView');
var PropertyFileView = require('./PropertyFileView'); const PropertyCompositeView = require('./PropertyCompositeView');
var PropertyCompositeView = require('./PropertyCompositeView'); const PropertyStackView = require('./PropertyStackView');
var PropertyStackView = require('./PropertyStackView');
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
@ -39,11 +38,11 @@ module.exports = Backbone.View.extend({
view.customValue = this.customValue; view.customValue = this.customValue;
} }
fragment.appendChild(view.render().el); view.render();
fragment.appendChild(view.el);
}); });
this.$el.append(fragment); this.$el.append(fragment);
this.$el.append($('<div>', {class: "clear"}));
this.$el.attr('class', this.pfx + 'properties'); this.$el.attr('class', this.pfx + 'properties');
return this; return this;
} }

34
src/style_manager/view/PropertyColorView.js

@ -1,31 +1,27 @@
var Backbone = require('backbone'); const InputColor = require('domain_abstract/ui/InputColor');
var PropertyView = require('./PropertyView');
var InputColor = require('domain_abstract/ui/InputColor');
module.exports = PropertyView.extend({ module.exports = require('./PropertyIntegerView').extend({
initialize(options) { setValue(value, opts = {}) {
PropertyView.prototype.initialize.apply(this, arguments); opts = Object.assign({}, opts, {silent: 1});
this.className += ` ${this.pfx}file`; this.inputInst.setValue(value, opts);
}, },
renderInput() { onRender() {
if (!this.input) { if (!this.input) {
var inputColor = new InputColor({ const ppfx = this.ppfx;
const inputColor = new InputColor({
target: this.target, target: this.target,
model: this.model, model: this.model,
ppfx: this.ppfx ppfx
}); });
this.input = inputColor.render(); const input = inputColor.render();
this.$el.append(this.input.$el); this.el.querySelector(`.${ppfx}fields`).appendChild(input.el);
this.$input = this.input.inputEl; this.$input = input.inputEl;
this.$color = this.input.colorEl; this.$color = input.colorEl;
this.input = this.$input.get(0);
this.inputInst = input;
} }
this.setValue(this.componentValue);
},
setValue(value) {
this.input.setValue(value, {silent: 1});
}, },
}); });

39
src/style_manager/view/PropertyCompositeView.js

@ -1,44 +1,35 @@
var Backbone = require('backbone'); const PropertyView = require('./PropertyView');
var PropertyView = require('./PropertyView'); const $ = Backbone.$;
module.exports = PropertyView.extend({ module.exports = PropertyView.extend({
templateField() { templateInput() {
const pfx = this.pfx; const pfx = this.pfx;
const ppfx = this.ppfx;
return ` return `
<div class="${pfx}field ${pfx}composite"> <div class="${pfx}field ${pfx}composite">
<span id="${pfx}input-holder"></span> <span id="${pfx}input-holder"></span>
</div> </div>
<div style="clear:both"></div>
`; `;
}, },
initialize(o) { inputValueChanged(...args) {
PropertyView.prototype.initialize.apply(this, arguments);
this.config = o.config || {};
this.className = this.className + ' '+ this.pfx +'composite';
},
/**
* Fired when the input value is updated
*/
valueUpdated(...args) {
if(!this.model.get('detached')) if(!this.model.get('detached'))
PropertyView.prototype.valueUpdated.apply(this, args); PropertyView.prototype.inputValueChanged.apply(this, args);
}, },
/** /**
* Renders input * Renders input
* */ * */
renderInput() { onRender() {
var model = this.model; var model = this.model;
var props = model.get('properties') || []; var props = model.get('properties') || [];
var self = this; var self = this;
if (props.length) { if (props.length) {
if(!this.$input) if (!this.$input) {
this.$input = $('<input>', {value: 0, type: 'hidden' }); this.$input = $('<input type="hidden" value="0">');
this.input = this.$input.get(0);
}
if (!this.props) { if (!this.props) {
this.props = model.get('properties'); this.props = model.get('properties');
@ -57,7 +48,7 @@ module.exports = PropertyView.extend({
var PropertiesView = require('./PropertiesView'); var PropertiesView = require('./PropertiesView');
var propsView = new PropertiesView(this.getPropsConfig()); var propsView = new PropertiesView(this.getPropsConfig());
this.$props = propsView.render().$el; this.$props = propsView.render().$el;
this.$el.find('#'+ this.pfx +'input-holder').html(this.$props); this.$el.find(`#${this.pfx}input-holder`).append(this.$props);
} }
} }
}, },
@ -110,9 +101,13 @@ module.exports = PropertyView.extend({
// to get the value of the sub-property // to get the value of the sub-property
if (targetValue) { if (targetValue) {
const values = targetValue.split(' '); const values = targetValue.split(' ');
value = view ? view.model.parseValue(values[index]) : values[index]; value = values[index];
} else { } else {
value = view.getTargetValue({ignoreCustomValue: 1}); value = view && view.getTargetValue({ignoreCustomValue: 1, ignoreDefault: 1});
}
if (view) {
value = view.model.parseValue(value);
} }
return value; return value;

25
src/style_manager/view/PropertyFileView.js

@ -1,9 +1,9 @@
var Backbone = require('backbone'); const PropertyView = require('./PropertyView');
var PropertyView = require('./PropertyView'); const $ = Backbone.$;
module.exports = PropertyView.extend({ module.exports = PropertyView.extend({
templateField() { templateInput() {
const pfx = this.pfx; const pfx = this.pfx;
const ppfx = this.ppfx; const ppfx = this.ppfx;
const assetsLabel = this.config.assetsLabel || 'Images'; const assetsLabel = this.config.assetsLabel || 'Images';
@ -22,25 +22,22 @@ module.exports = PropertyView.extend({
<div id="${pfx}close">&Cross;</div> <div id="${pfx}close">&Cross;</div>
</div> </div>
</div> </div>
<div style="clear:both"></div>
`; `;
}, },
initialize(options) { init() {
PropertyView.prototype.initialize.apply(this, arguments); const em = this.em;
this.assets = this.target.get('assets'); this.modal = em.get('Modal');
this.modal = this.target.get('Modal'); this.am = em.get('AssetManager');
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+'close'] = 'removeFile';
this.events['click #'+this.pfx+'images'] = 'openAssetManager'; this.events['click #'+this.pfx+'images'] = 'openAssetManager';
this.delegateEvents(); this.delegateEvents();
}, },
/** @inheritdoc */ onRender() {
renderInput() {
if (!this.$input) { if (!this.$input) {
this.$input = $('<input>', {placeholder: this.model.getDefaultValue(), type: 'text' }); const plh = this.model.getDefaultValue();
this.$input = $(`<input placeholder="${plh}">`);
} }
if (!this.$preview) { if (!this.$preview) {
@ -56,7 +53,7 @@ module.exports = PropertyView.extend({
setValue(value, f) { setValue(value, f) {
PropertyView.prototype.setValue.apply(this, arguments); PropertyView.prototype.setValue.apply(this, arguments);
this.setPreviewView(value && value != this.getDefaultValue()); this.setPreviewView(value && value != this.model.getDefaultValue());
this.setPreview(value); this.setPreview(value);
}, },

45
src/style_manager/view/PropertyIntegerView.js

@ -1,30 +1,39 @@
var PropertyView = require('./PropertyView'); const InputNumber = require('domain_abstract/ui/InputNumber');
var InputNumber = require('domain_abstract/ui/InputNumber'); const $ = Backbone.$;
module.exports = PropertyView.extend({ module.exports = require('./PropertyView').extend({
initialize(options) { templateInput() {
PropertyView.prototype.initialize.apply(this, arguments); return '';
this.listenTo(this.model, 'change:unit', this.valueChanged);
this.listenTo(this.model, 'el:change', this.elementUpdated);
}, },
renderInput() { init() {
const model = this.model;
this.listenTo(model, 'change:unit', this.modelValueChanged);
this.listenTo(model, 'el:change', this.elementUpdated);
},
setValue(value) {
this.inputInst.setValue(value, {silent: 1});
},
onRender() {
const ppfx = this.ppfx;
if (!this.input) { if (!this.input) {
var inputNumber = new InputNumber({ const inputNumber = new InputNumber({
model: this.model, model: this.model,
ppfx: this.ppfx ppfx: this.ppfx
}); });
this.input = inputNumber.render(); const input = inputNumber.render();
this.$el.append(this.input.$el); const fields = this.el.querySelector(`.${ppfx}fields`);
this.$input = this.input.inputEl; fields.appendChild(input.el);
this.$unit = this.input.unitEl; this.$input = input.inputEl;
this.unit = input.unitEl;
this.$unit = $(this.unit);
this.input = this.$input.get(0);
this.inputInst = input;
} }
this.setValue(this.componentValue);
},
setValue(value) {
this.input.setValue(value, {silent: 1});
}, },
}); });

91
src/style_manager/view/PropertyRadioView.js

@ -1,74 +1,69 @@
var Backbone = require('backbone'); module.exports = require('./PropertyView').extend({
var PropertyView = require('./PropertyView');
module.exports = PropertyView.extend({ templateInput() {
templateField() {
const pfx = this.pfx; const pfx = this.pfx;
const ppfx = this.ppfx; const ppfx = this.ppfx;
return ` return `
<div class="${ppfx}field ${ppfx}field-radio"> <div class="${ppfx}field ${ppfx}field-radio">
<span id="${pfx}input-holder"></span> <span id="${pfx}input-holder"></span>
</div> </div>
<div style="clear:both"></div>
`; `;
}, },
initialize(options) { onRender() {
PropertyView.prototype.initialize.apply(this, arguments); const pfx = this.pfx;
const ppfx = this.ppfx;
const itemCls = `${ppfx}radio-item-label`;
const model = this.model; const model = this.model;
this.list = model.get('list') || model.get('options') || []; const prop = model.get('property');
this.className = this.className + ' '+ this.pfx +'list'; const options = model.get('list') || model.get('options') || [];
},
/** @inheritdoc */ if (!this.input) {
renderInput() { if(options && options.length) {
var pfx = this.pfx; let inputStr = '';
var ppfx = this.ppfx;
var itemCls = ppfx + 'radio-item-label';
var prop = this.property;
if(!this.$input) { options.forEach(el => {
if(this.list && this.list.length) { let cl = el.className ? `${el.className} ${pfx}icon ${itemCls}` : '';
var input = ''; let id = `${prop}-${el.value}`;
_.each(this.list, el => { let labelTxt = el.name || el.value;
var cl = el.className ? el.className + ' ' + pfx + 'icon ' + itemCls : '', let titleAttr = el.title ? `title="${el.title}"` : '';
id = prop + '-' + el.value, inputStr += `
labelTxt = el.name ? el.name : el.value; <div class="${ppfx}radio-item">
var titleAttr = el.title ? 'title="' + el.title + '"': ''; <input type="radio" class="${pfx}radio" id="${id}" name="${prop}" value="${el.value}"/>
input += '<div class="' + ppfx + 'radio-item">'+ <label class="${cl || itemCls}" ${titleAttr} for="${id}">${cl ? '' : labelTxt}</label>
'<input class="'+pfx+'radio" type="radio" id="'+ id +'" name="'+prop+'" value="'+el.value+'" />'+ </div>
'<label class="'+(cl ? cl : itemCls)+'" ' + titleAttr + ' for="'+ id +'">' + (cl ? '' : labelTxt) + '</label></div>'; `;
}); });
this.input = input;
this.$inputEl = $(this.input); const inputHld = this.el.querySelector(`#${pfx}input-holder`);
this.$el.find('#'+ pfx +'input-holder').html(this.$inputEl); inputHld.innerHTML = `<div>${inputStr}</div>`;
this.$input = this.$inputEl.find('input[name="'+this.property+'"]'); this.input = inputHld.firstChild;
} }
} }
this.setValue(this.componentValue);
}, },
/**
* Returns value from input
* @return {string}
*/
getInputValue() { getInputValue() {
return this.$input ? this.$el.find('input:checked').val() : ''; const inputChk = this.getCheckedEl();
return inputChk ? inputChk.value : '';
}, },
/** @inheritdoc */ getCheckedEl() {
setValue(value) { const input = this.getInputEl();
var v = this.model.get('value') || this.model.getDefaultValue(); return input ? input.querySelector('input:checked') : '';
},
if(value)
v = value;
if(this.$input) setValue(value) {
this.$input.filter('[value="'+v+'"]').prop('checked', true); const model = this.model;
let val = value || model.get('value') || model.getDefaultValue();
const input = this.getInputEl();
const inputIn = input ? input.querySelector(`[value="${val}"]`) : '';
this.model.set({value: v},{silent: true}); if (inputIn) {
inputIn.checked = true;
} else {
const inputChk = this.getCheckedEl();
inputChk && (inputChk.checked = false);
}
}, },
}); });

45
src/style_manager/view/PropertySelectView.js

@ -1,9 +1,8 @@
var Backbone = require('backbone'); const $ = Backbone.$;
var PropertyView = require('./PropertyView');
module.exports = PropertyView.extend({ module.exports = require('./PropertyView').extend({
templateField() { templateInput() {
const pfx = this.pfx; const pfx = this.pfx;
const ppfx = this.ppfx; const ppfx = this.ppfx;
return ` return `
@ -13,37 +12,29 @@ module.exports = PropertyView.extend({
<div class="${ppfx}d-s-arrow"></div> <div class="${ppfx}d-s-arrow"></div>
</div> </div>
</div> </div>
<div style="clear:both"></div>
`; `;
}, },
initialize(options) { onRender() {
PropertyView.prototype.initialize.apply(this, arguments); var pfx = this.pfx;
const model = this.model; const model = this.model;
this.list = model.get('list') || model.get('options') || []; const options = model.get('list') || model.get('options') || [];
},
/** @inheritdoc */ if (!this.input) {
renderInput() { let optionsStr = '';
var pfx = this.pfx;
if(!this.$input){
var input = '<select>';
if (this.list && this.list.length) { options.forEach(option => {
_.each(this.list, el => { let name = option.name || option.value;
var name = el.name ? el.name : el.value; let style = option.style ? option.style.replace(/"/g,'&quot;') : '';
var style = el.style ? el.style.replace(/"/g,'&quot;') : ''; let styleAttr = style ? `style="${style}"` : '';
var styleAttr = style ? 'style="' + style + '"' : ''; let value = option.value.replace(/"/g,'&quot;');
input += '<option value="'+el.value.replace(/"/g,'&quot;')+'" ' + styleAttr + '>'+name+'</option>'; optionsStr += `<option value="${value}" ${styleAttr}>${name}</option>`;
}); });
}
input += '</select>'; const inputH = this.el.querySelector(`#${pfx}input-holder`);
this.input = input; inputH.innerHTML = `<select>${optionsStr}</select>`;
this.$input = $(this.input); this.input = inputH.firstChild;
this.$el.find('#'+ pfx +'input-holder').html(this.$input);
} }
this.setValue(this.componentValue, 0);
}, },
}); });

59
src/style_manager/view/PropertySliderView.js

@ -0,0 +1,59 @@
const InputNumber = require('domain_abstract/ui/InputNumber');
const Property = require('./PropertyIntegerView');
module.exports = Property.extend({
events: {
'change': 'inputValueChanged',
'input': 'inputValueChangedSoft',
},
templateInput(model) {
const ppfx = this.ppfx;
return `
<div class="${ppfx}field ${ppfx}field-range">
<input type="range"
min="${model.get('min')}"
max="${model.get('max')}"
step="${model.get('step')}"/>
</div>
`;
},
getSliderEl() {
if (!this.slider) {
this.slider = this.el.querySelector('input[type=range]');
}
return this.slider;
},
inputValueChanged() {
const model = this.model;
const step = model.get('step');
this.getInputEl().value = this.getSliderEl().value;
const value = this.getInputValue() - step;
model.set('value', value, {avoidStore: 1}).set('value', value + step);
this.elementUpdated();
},
inputValueChangedSoft() {
this.getInputEl().value = this.getSliderEl().value;
this.model.set('value', this.getInputValue(), {avoidStore: 1});
this.elementUpdated();
},
setValue(value) {
this.getSliderEl().value = value;
this.inputInst.setValue(value, {silent: 1});
},
onRender() {
Property.prototype.onRender.apply(this, arguments);
const model = this.model;
if (!model.get('showInput')) {
this.inputInst.el.style.display = 'none';
}
}
});

331
src/style_manager/view/PropertyStackView.js

@ -1,38 +1,33 @@
var Backbone = require('backbone'); const PropertyCompositeView = require('./PropertyCompositeView');
var PropertyCompositeView = require('./PropertyCompositeView'); const LayersView = require('./LayersView');
var Layers = require('./../model/Layers');
var LayersView = require('./LayersView');
module.exports = PropertyCompositeView.extend({ module.exports = PropertyCompositeView.extend({
templateField() { templateInput() {
const pfx = this.pfx; const pfx = this.pfx;
const ppfx = this.ppfx; const ppfx = this.ppfx;
return ` return `
<div class="${pfx}field ${pfx}stack"> <div class="${pfx}field ${pfx}stack">
<button type="button" id="${pfx}add">+</button> <button type="button" id="${pfx}add" data-add-layer>+</button>
<span id="${pfx}input-holder"></span> <div data-layers-wrapper></div>
</div> </div>
<div style="clear:both"></div>
`; `;
}, },
initialize(o) { init() {
PropertyCompositeView.prototype.initialize.apply(this, arguments);
const model = this.model; const model = this.model;
const pfx = this.pfx; const pfx = this.pfx;
model.set('stackIndex', null); model.set('stackIndex', null);
this.className = `${pfx}property ${pfx}stack`; this.events[`click [data-add-layer]`] = 'addLayer';
this.events[`click #${pfx}add`] = 'addLayer';
this.listenTo(model, 'change:stackIndex', this.indexChanged); this.listenTo(model, 'change:stackIndex', this.indexChanged);
this.listenTo(model, 'updateValue', this.valueUpdated); this.listenTo(model, 'updateValue', this.inputValueChanged);
this.delegateEvents(); this.delegateEvents();
}, },
/** /**
* Fired when the target is updated. * Fired when the target is updated.
* With detached mode the component will be always empty as its value * With detached mode the component will be always empty as its value
* so we gonna check all props and fine if there is some differences. * so we gonna check all props and find if it has any difference
* */ * */
targetUpdated(...args) { targetUpdated(...args) {
if (!this.model.get('detached')) { if (!this.model.get('detached')) {
@ -61,270 +56,120 @@ module.exports = PropertyCompositeView.extend({
* @return {Object} * @return {Object}
* */ * */
indexChanged(e) { indexChanged(e) {
var model = this.model; const model = this.model;
var layer = this.getLayers().at(model.get('stackIndex')); this.getLayers().active(model.get('stackIndex'));
layer.set('props', this.$props);
model.get('properties').each(prop => prop.trigger('targetUpdated'));
},
/**
* Get array of values from layers
* @return Array
* */
getStackValues() {
return this.getLayers().pluck('value');
}, },
/** @inheritDoc */ addLayer() {
getPropsConfig(opts) {
const model = this.model; const model = this.model;
const detached = model.get('detached'); const layers = this.getLayers();
var result = PropertyCompositeView.prototype.getPropsConfig.apply(this, arguments); const properties = model.get('properties').deepClone();
properties.each(property => property.set('value', ''));
result.onChange = (el, view, opt) => { const layer = layers.add({properties});
const subModel = view.model;
const subProperty = subModel.get('property');
this.build();
if (detached) {
var propVal = '';
var index = subModel.collection.indexOf(subModel);
this.getLayers().each(layer => { // In detached mode inputValueChanged will add new 'layer value'
var val = layer.get('values')[subProperty]; // to all subprops
if (val) { this.inputValueChanged();
propVal += (propVal ? ',' : '') + val;
}
});
view.updateTargetStyle(propVal, null, opt); // This will set subprops with a new default values
} else { model.set('stackIndex', layers.indexOf(layer));
model.set('value', model.getFullValue(), opt);
}
};
return result;
}, },
/**
* Extract string from the composite value of the target inputValueChanged() {
* @param {integer} index Property index
* @param {View} propView Property view
* @return string
* @private
* */
valueOnIndex(index, propView) {
let result;
const model = this.model; const model = this.model;
const layerIndex = model.get('stackIndex'); this.elementUpdated();
// If detached the value in this case is stacked, eg. substack-prop: 1px, 2px, 3px... // If not detached I'll just put all the values from layers to property
if (model.get('detached')) { // eg. background: layer1Value, layer2Value, layer3Value, ...
var targetValue = propView.getTargetValue({ignoreCustomValue: 1}); if (!model.get('detached')) {
var valist = (targetValue + '').split(','); model.set('value', this.getLayerValues());
result = valist[layerIndex];
result = result ? result.trim() : propView.getDefaultValue();
result = propView.model.parseValue(result);
} else { } else {
var aStack = this.getStackValues(); model.get('properties').each(prop => prop.trigger('change:value'))
var strVar = aStack[layerIndex];
if(!strVar)
return;
var a = strVar.split(' ');
if(a.length && a[index]){
result = a[index];
}
} }
return result;
}, },
/** /**
* Build composite value * There is no need to handle input update by the property itself,
* this will be done by layers
* @private * @private
* */
build(...args) {
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
properties.each(prop => {
const propValue = prop.getFullValue();
values[prop.get('property')] = propValue;
value += `${propValue} `;
});
const layerModel = this.getLayers().at(stackIndex);
layerModel && layerModel.set({values, value});
},
/**
* Add new layer
* */
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
this.valueUpdated();
// This will set subprops with a new default values
this.model.set('stackIndex', index);
}
},
/**
* Fired when the input value is updated
*/ */
valueUpdated() { setValue() {},
var model = this.model;
if (!model.get('detached')) {
model.set('value', this.createValue());
} else {
model.get('properties').each(prop => {
prop.trigger('change:value');
});
}
},
/** /**
* Create value by layers * Create value by layers
* @return string * @return string
* */ * */
createValue() { getLayerValues() {
return this.getStackValues().join(', '); return this.getLayers().getFullValue();
},
/**
* Render layers
* @return self
* */
renderLayers() {
if(!this.$field)
this.$field = this.$el.find('> .' + this.pfx + 'field');
if(!this.$layers)
this.$layers = new LayersView({
collection: this.getLayers(),
stackModel: this.model,
preview: this.model.get('preview'),
config: this.config
});
this.$field.append(this.$layers.render().el);
this.$props.hide();
return this;
},
/** @inheritdoc */
renderInput(...args) {
PropertyCompositeView.prototype.renderInput.apply(this, args);
this.refreshLayers();
},
/**
* Returns array suitale for layers from target style
* Only for detached stacks
* @return {Array<string>}
*/
getLayersFromTarget() {
var arr = [];
var target = this.getTarget();
if(!target)
return arr;
var trgStyle = target.get('style');
this.model.get('properties').each(prop => {
var style = trgStyle[prop.get('property')];
if (style) {
var list = style.split(',');
for(var i = 0, len = list.length; i < len; i++){
var val = list[i].trim();
if(arr[i]){
arr[i][prop.get('property')] = val;
}else{
var vals = {};
vals[prop.get('property')] = val;
arr[i] = vals;
}
}
}
});
return arr;
}, },
/** /**
* Refresh layers * Refresh layers
* */ * */
refreshLayers() { refreshLayers() {
var n = []; let layersObj = [];
var a = []; const model = this.model;
var fieldName = 'value'; const layers = this.getLayers();
var detached = this.model.get('detached'); const detached = model.get('detached');
// With detached layers values will be assigned to their properties
if (detached) { if (detached) {
fieldName = 'values'; const target = this.getTarget();
a = this.getLayersFromTarget(); const style = target ? target.getStyle() : {};
layersObj = layers.getLayersFromStyle(style);
} else { } else {
var v = this.getTargetValue(); let value = this.getTargetValue();
var vDef = this.getDefaultValue(); value = value == model.getDefaultValue() ? '' : value;
v = v == vDef ? '' : v; layersObj = layers.getLayersFromValue(value);
if (v) {
// Remove spaces inside functions:
// eg:
// From: 1px 1px rgba(2px, 2px, 2px), 2px 2px rgba(3px, 3px, 3px)
// To: 1px 1px rgba(2px,2px,2px), 2px 2px rgba(3px,3px,3px)
v.replace(/\(([\w\s,.]*)\)/g, match => {
var cleaned = match.replace(/,\s*/g, ',');
v = v.replace(match, cleaned);
});
a = v.split(', ');
}
} }
_.each(a, e => {
var o = {};
o[fieldName] = e;
n.push(o);
},this);
this.$props.detach();
var layers = this.getLayers();
layers.reset(); layers.reset();
layers.add(n); layers.add(layersObj);
model.set({stackIndex: null}, {silent: true});
// Avoid updating with detached as it will cause issues on next change
if (!detached) {
this.valueUpdated();
}
this.model.set({stackIndex: null}, {silent: true});
}, },
render() { onRender() {
const el = this.el; const self = this;
el.innerHTML = this.template(this.model); const model = this.model;
this.renderInput(); const fieldEl = this.el.querySelector('[data-layers-wrapper]');
this.renderLayers(); const PropertiesView = require('./PropertiesView');
this.$el.attr('class', this.className); const propsConfig = {
this.updateStatus(); propTarget: this.propTarget,
return this;
// Things to do when a single sub-property is changed
onChange(el, view, opt) {
const subModel = view.model;
if (model.get('detached')) {
const subProp = subModel.get('property');
const values = self.getLayers().getPropertyValues(subProp);
view.updateTargetStyle(values, null, opt);
} else {
model.set('value', model.getFullValue(), opt);
}
},
};
const layers = new LayersView({
collection: this.getLayers(),
stackModel: model,
preview: model.get('preview'),
config: this.config,
propsConfig,
}).render().el;
// Will use it to propogate changes
new PropertiesView({
collection: this.model.get('properties'),
stackModel: model,
config: this.config,
onChange: propsConfig.onChange,
propTarget: propsConfig.propTarget,
customValue: propsConfig.customValue,
}).render();
//model.get('properties')
fieldEl.appendChild(layers);
}, },
}); });

155
src/style_manager/view/PropertyView.js

@ -1,39 +1,42 @@
var Backbone = require('backbone');
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
template(model) { template(model) {
const pfx = this.pfx; const pfx = this.pfx;
const name = model.get('name');
const icon = model.get('icon');
const info = model.get('info');
return ` return `
<div class="${pfx}label"> <div class="${pfx}label">
<span class="${pfx}icon ${icon}" title="${info}"> ${this.templateLabel(model)}
${name} </div>
</span> <div class="${this.ppfx}fields">
<b class="${pfx}clear">&Cross;</b> ${this.templateInput(model)}
</div> </div>
${this.templateField()}
`; `;
}, },
templateField() { templateLabel(model) {
const pfx = this.pfx; const pfx = this.pfx;
const ppfx = this.ppfx; const icon = model.get('icon');
const info = model.get('info');
return `
<span class="${pfx}icon ${icon}" title="${info}">
${model.get('name')}
</span>
<b class="${pfx}clear">&Cross;</b>
`;
},
templateInput(model) {
return ` return `
<div class="${ppfx}field"> <div class="${this.ppfx}field">
<span id="${pfx}input-holder"></span> <input placeholder="${model.getDefaultValue()}"/>
</div> </div>
<div style="clear:both"></div>
`; `;
}, },
events: { events: {
'change': 'valueUpdated' 'change': 'inputValueChanged'
}, },
initialize(o) { initialize(o = {}) {
this.config = o.config || {}; this.config = o.config || {};
this.em = this.config.em; this.em = this.config.em;
this.pfx = this.config.stylePrefix || ''; this.pfx = this.config.stylePrefix || '';
@ -45,9 +48,8 @@ module.exports = Backbone.View.extend({
this.customValue = o.customValue || {}; this.customValue = o.customValue || {};
const model = this.model; const model = this.model;
this.property = model.get('property'); this.property = model.get('property');
this.input = this.$input = null; this.input = null;
const pfx = this.pfx; const pfx = this.pfx;
this.className = pfx + 'property';
this.inputHolderId = '#' + pfx + 'input-holder'; this.inputHolderId = '#' + pfx + 'input-holder';
this.sector = model.collection && model.collection.sector; this.sector = model.collection && model.collection.sector;
@ -57,14 +59,22 @@ module.exports = Backbone.View.extend({
this.listenTo(this.propTarget, 'update', this.targetUpdated); this.listenTo(this.propTarget, 'update', this.targetUpdated);
this.listenTo(model, 'destroy remove', this.remove); this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:value', this.valueChanged); this.listenTo(model, 'change:value', this.modelValueChanged);
this.listenTo(model, 'targetUpdated', this.targetUpdated); this.listenTo(model, 'targetUpdated', this.targetUpdated);
this.listenTo(model, 'change:visible', this.updateVisibility); this.listenTo(model, 'change:visible', this.updateVisibility);
this.listenTo(model, 'change:status', this.updateStatus); this.listenTo(model, 'change:status', this.updateStatus);
this.events[`click .${pfx}clear`] = 'clear'; this.events[`click .${pfx}clear`] = 'clear';
this.delegateEvents(); this.delegateEvents();
const init = this.init && this.init.bind(this);
init && init();
}, },
/**
* Triggers when the status changes. The status indicates if the value of
* the proprerty is changed or inherited
* @private
*/
updateStatus() { updateStatus() {
const status = this.model.get('status'); const status = this.model.get('status');
const pfx = this.pfx; const pfx = this.pfx;
@ -72,7 +82,7 @@ module.exports = Backbone.View.extend({
const config = this.config; const config = this.config;
const updatedCls = `${ppfx}color-hl`; const updatedCls = `${ppfx}color-hl`;
const computedCls = `${ppfx}color-warn`; const computedCls = `${ppfx}color-warn`;
const labelEl = this.$el.find(`> .${pfx}label`); const labelEl = this.$el.children(`.${pfx}label`);
const clearStyle = this.getClearEl().style; const clearStyle = this.getClearEl().style;
labelEl.removeClass(`${updatedCls} ${computedCls}`); labelEl.removeClass(`${updatedCls} ${computedCls}`);
clearStyle.display = 'none'; clearStyle.display = 'none';
@ -92,7 +102,7 @@ module.exports = Backbone.View.extend({
}, },
/** /**
* Clear the property * Clear the property from the target
*/ */
clear() { clear() {
const target = this.getTargetModel(); const target = this.getTargetModel();
@ -110,11 +120,10 @@ module.exports = Backbone.View.extend({
/** /**
* Returns selected target which should have 'style' property * Returns selected target which should have 'style' property
* @deprecated
* @return {Model|null} * @return {Model|null}
*/ */
getTarget() { getTarget() {
return this.propTarget && this.propTarget.model; return this.getTargetModel();
}, },
/** /**
@ -134,9 +143,10 @@ module.exports = Backbone.View.extend({
}, },
/** /**
* Fired when the input value is updated * Triggers when the value of element input/s is changed, so have to update
* the value of the model which will propogate those changes to the target
*/ */
valueUpdated() { inputValueChanged() {
this.model.set('value', this.getInputValue()); this.model.set('value', this.getInputValue());
this.elementUpdated(); this.elementUpdated();
}, },
@ -156,14 +166,14 @@ module.exports = Backbone.View.extend({
return; return;
} }
const config = this.config;
const em = config.em;
const model = this.model;
let value = ''; let value = '';
let status = ''; let status = '';
let targetValue = this.getTargetValue({ignoreDefault: 1}); let targetValue = this.getTargetValue({ignoreDefault: 1});
let defaultValue = this.getDefaultValue(); let defaultValue = model.getDefaultValue();
let computedValue = this.getComputedValue(); let computedValue = this.getComputedValue();
const config = this.config;
const em = config.em;
const model = this.model;
if (targetValue) { if (targetValue) {
value = targetValue; value = targetValue;
@ -184,7 +194,7 @@ module.exports = Backbone.View.extend({
} }
model.set('value', value, {silent: 1}); model.set('value', value, {silent: 1});
this.setValue(value, 1); this.setValue(value, {targetUpdate: 1});
model.set('status', status); model.set('status', status);
if (em) { if (em) {
@ -239,7 +249,7 @@ module.exports = Backbone.View.extend({
result = model.parseValue(result); result = model.parseValue(result);
if (!result && !opts.ignoreDefault) { if (!result && !opts.ignoreDefault) {
result = this.getDefaultValue(); result = model.getDefaultValue();
} }
if (typeof customFetchValue == 'function' && !opts.ignoreCustomValue) { if (typeof customFetchValue == 'function' && !opts.ignoreCustomValue) {
@ -254,15 +264,6 @@ module.exports = Backbone.View.extend({
return result; return result;
}, },
/**
* Returns default value
* @return {String}
* @private
*/
getDefaultValue() {
return this.model.getDefaultValue();
},
/** /**
* Returns computed value * Returns computed value
* @return {String} * @return {String}
@ -280,23 +281,24 @@ module.exports = Backbone.View.extend({
* @return {string} * @return {string}
*/ */
getInputValue() { getInputValue() {
return this.$input ? this.$input.val() : ''; const input = this.getInputEl();
return input ? input.value : '';
}, },
/** /**
* Triggers when the 'value' of the model changes, so I have to update * Triggers when the `value` of the model changes, so the target and
* the target model * the input element should be updated
* @param {Object} e Events * @param {Object} e Event
* @param {Mixed} val Value * @param {Mixed} val Value
* @param {Object} opt Options * @param {Object} opt Options
* */ * */
valueChanged(e, val, opt) { modelValueChanged(e, val, opt) {
const em = this.config.em; const em = this.config.em;
const model = this.model; const model = this.model;
const value = model.getFullValue(); const value = model.getFullValue();
const target = this.getTarget(); const target = this.getTarget();
const onChange = this.onChange; const onChange = this.onChange;
this.setValue(value); this.setRawValue(value);
// Check if component is allowed to be styled // Check if component is allowed to be styled
if (!target || !this.isTargetStylable() || !this.isComponentStylable()) { if (!target || !this.isTargetStylable() || !this.isComponentStylable()) {
@ -377,24 +379,37 @@ module.exports = Backbone.View.extend({
return stylable; return stylable;
}, },
/**
* Passed a raw value you have to update the input element, generally
* is the value fetched from targets, so you can receive values with
* functions, units, etc. (eg. `rotateY(45deg)`)
* get also
* @param {string} value
* @private
*/
setRawValue(value) {
this.setValue(this.model.parseValue(value));
},
/** /**
* Set the value to property input * Set the value to property input
* @param {String} value * @param {String} value
* @param {Boolean} force * @param {Boolean} force
* @private * @private
* */ * */
setValue(value, force) { setValue(value, opts = {}) {
const model = this.model; const model = this.model;
const f = force === 0 ? 0 : 1; let val = value || model.get('value') || model.getDefaultValue();
const def = model.getDefaultValue(); const input = this.getInputEl();
let v = model.get('value') || def; input && (input.value = val);
},
if (value || f) { getInputEl() {
v = value; if (!this.input) {
this.input = this.el.querySelector('input');
} }
const input = this.$input; return this.input;
input && input.val(v);
}, },
updateVisibility() { updateVisibility() {
@ -410,20 +425,6 @@ module.exports = Backbone.View.extend({
this.model.set('visible', 0); this.model.set('visible', 0);
}, },
/**
* Renders input, to override
* */
renderInput() {
if(!this.$input){
this.$input = $('<input>', {
placeholder: this.model.getDefaultValue(),
type: 'text'
});
this.$el.find(this.inputHolderId).html(this.$input);
}
this.setValue(this.componentValue, 0);
},
/** /**
* Clean input * Clean input
* */ * */
@ -432,12 +433,16 @@ module.exports = Backbone.View.extend({
}, },
render() { render() {
const pfx = this.pfx;
const model = this.model;
const el = this.el; const el = this.el;
el.innerHTML = this.template(this.model); el.innerHTML = this.template(model);
this.renderInput(); el.className = `${pfx}property ${pfx}${model.get('type')}`;
el.className = this.className;
this.updateStatus(); this.updateStatus();
return this;
const onRender = this.onRender && this.onRender.bind(this);
onRender && onRender();
this.setValue(model.get('value'), {targetUpdate: 1});
}, },
}); });

24
src/style_manager/view/SectorView.js

@ -4,25 +4,25 @@ var PropertiesView = require('./PropertiesView');
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
template: _.template(` template: _.template(`
<div class="<%= pfx %>title"> <div class="<%= pfx %>title" data-sector-title>
<i id="<%= pfx %>caret" class="fa"></i> <i id="<%= pfx %>caret" class="fa"></i>
<%= label %> <%= label %>
</div>`), </div>`),
events:{}, events:{
'click [data-sector-title]': 'toggle'
},
initialize(o) { initialize(o) {
this.config = o.config || {}; this.config = o.config || {};
this.pfx = this.config.stylePrefix || ''; this.pfx = this.config.stylePrefix || '';
this.target = o.target || {}; this.target = o.target || {};
this.propTarget = o.propTarget || {}; this.propTarget = o.propTarget || {};
this.open = this.model.get('open');
this.caretR = 'fa-caret-right'; this.caretR = 'fa-caret-right';
this.caretD = 'fa-caret-down'; this.caretD = 'fa-caret-down';
this.listenTo(this.model, 'change:open', this.updateOpen); const model = this.model;
this.listenTo(this.model, 'updateVisibility', this.updateVisibility); this.listenTo(model, 'change:open', this.updateOpen);
this.events['click .' + this.pfx + 'title'] = 'toggle'; this.listenTo(model, 'updateVisibility', this.updateVisibility);
this.delegateEvents();
}, },
/** /**
@ -53,7 +53,7 @@ module.exports = Backbone.View.extend({
* */ * */
show() { show() {
this.$el.addClass(this.pfx + "open"); this.$el.addClass(this.pfx + "open");
this.$el.find('.' + this.pfx + 'properties').show(); this.getPropertiesEl().style.display = '';
this.$caret.removeClass(this.caretR).addClass(this.caretD); this.$caret.removeClass(this.caretR).addClass(this.caretD);
}, },
@ -62,14 +62,18 @@ module.exports = Backbone.View.extend({
* */ * */
hide() { hide() {
this.$el.removeClass(this.pfx + "open"); this.$el.removeClass(this.pfx + "open");
this.$el.find('.' + this.pfx + 'properties').hide(); this.getPropertiesEl().style.display = 'none';
this.$caret.removeClass(this.caretD).addClass(this.caretR); this.$caret.removeClass(this.caretD).addClass(this.caretR);
}, },
getPropertiesEl() {
return this.$el.find(`.${this.pfx}properties`).get(0);
},
/** /**
* Toggle visibility * Toggle visibility
* */ * */
toggle() { toggle(e) {
var v = this.model.get('open') ? 0 : 1; var v = this.model.get('open') ? 0 : 1;
this.model.set('open', v); this.model.set('open', v);
}, },

7
src/style_manager/view/SectorsView.js

@ -1,5 +1,4 @@
var Backbone = require('backbone'); const SectorView = require('./SectorView');
var SectorView = require('./SectorView');
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
@ -117,9 +116,7 @@ module.exports = Backbone.View.extend({
* */ * */
addToCollection(model, fragmentEl) { addToCollection(model, fragmentEl) {
var fragment = fragmentEl || null; var fragment = fragmentEl || null;
var viewObject = SectorView; var view = new SectorView({
var view = new viewObject({
model, model,
id: this.pfx + model.get('name').replace(' ','_').toLowerCase(), id: this.pfx + model.get('name').replace(' ','_').toLowerCase(),
name: model.get('name'), name: model.get('name'),

100
src/styles/scss/_gjs_inputs.scss

@ -11,13 +11,55 @@
@mixin rangeTrackStyle() { @mixin rangeTrackStyle() {
background-color: $mainDkColor; background-color: $mainDkColor;
border: 1px solid $darkBorder;
border-radius: 1px; border-radius: 1px;
height: 2px; margin-top: 3px;
height: 3px;
} }
.#{$app-prefix}label { .#{$app-prefix} {
line-height: 18px; &label {
line-height: 18px;
}
&fields {
display: flex;
}
&select {
padding: 0;
width: 100%;
select {
padding-right: 10px;
}
}
&select:-moz-focusring,
&select select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 $mainLhlColor;
}
&input:focus,
&button:focus,
&btn-prim:focus,
&select:focus,
&select select:focus {
outline: none;
}
&field {
&-range {
flex: 9 1 auto;
}
}
}
.#{$app-prefix}select option,
.#{$clm-prefix}select option,
.#{$sm-prefix}select option,
.#{$sm-prefix}unit option {
@extend .#{$app-prefix}bg-main;
} }
.#{$app-prefix}field { .#{$app-prefix}field {
@ -43,6 +85,10 @@
position: relative; position: relative;
padding: $inputPadding; padding: $inputPadding;
z-index: 1; z-index: 1;
&:focus {
outline: none;
}
} }
textarea { textarea {
@ -238,33 +284,39 @@
input { input {
margin: 0; margin: 0;
} height: 100%;
input::-moz-range-thumb { &:focus {
@include rangeThumbStyle(); outline: none;
} }
input::-webkit-slider-thumb { &::-webkit-slider-thumb {
-webkit-appearance: none; -webkit-appearance: none;
margin-top: -5px; margin-top: -4px;
@include rangeThumbStyle(); @include rangeThumbStyle();
} }
input::-ms-thumb { &::-moz-range-thumb {
@include rangeThumbStyle(); @include rangeThumbStyle();
} }
input::-moz-range-track { &::-ms-thumb {
@include rangeTrackStyle(); @include rangeThumbStyle();
} }
input::-webkit-slider-runnable-track { /* -moz-range-progress */
@include rangeTrackStyle(); &::-moz-range-track {
} @include rangeTrackStyle();
}
&::-webkit-slider-runnable-track {
@include rangeTrackStyle();
}
input::-ms-track { &::-ms-track {
@include rangeTrackStyle(); @include rangeTrackStyle();
}
} }
} }

41
src/styles/scss/_gjs_rte.scss

@ -0,0 +1,41 @@
.#{$rte-prefix} {
&toolbar {
@extend .#{$app-prefix}bg-main;
@extend .#{$app-prefix}no-user-select;
border: 1px solid $mainDkColor;
position: absolute;
border-radius: 3px;
z-index: 10;
}
&actionbar {
display: flex;
}
&action {
@extend .#{$app-prefix}color-main;
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
min-width: 25px;
border-right: 1px solid $mainDkColor;
text-align: center;
cursor: pointer;
outline: none;
&:last-child {
border-right: none;
}
&:hover {
background-color: $mainLhColor;
}
}
&active {
background-color: $mainDkColor;
}
}

54
src/styles/scss/_gjs_style_manager.scss

@ -5,6 +5,26 @@
line-height: 0; line-height: 0;
cursor: pointer; cursor: pointer;
} }
&header {
font-size: 11px;
font-weight: lighter;
padding: 10px;
}
&properties {
font-size: 11px;
padding: 10px 5px;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
box-sizing: border-box;
width: 100%;
}
&label {
margin: 5px 5px 3px 0;
}
} }
.#{$sm-prefix}close-btn { .#{$sm-prefix}close-btn {
@ -22,12 +42,6 @@
} }
} }
.#{$sm-prefix}header {
font-size: 11px;
font-weight: lighter;
padding: 10px;
}
.#{$sm-prefix}sector { .#{$sm-prefix}sector {
clear: both; clear: both;
font-weight: lighter; font-weight: lighter;
@ -46,10 +60,6 @@
@extend .#{$app-prefix}category-title; @extend .#{$app-prefix}category-title;
} }
.#{$sm-prefix}label {
margin: 5px 5px 2px 0;
}
/* ------------------Field-------------------- */ /* ------------------Field-------------------- */
.#{$sm-prefix}field { .#{$sm-prefix}field {
@ -229,13 +239,16 @@
} }
} }
.#{$sm-prefix}properties {
font-size: 11px;
padding: 10px 5px;
}
/* ------------------Property-------------------- */ /* ------------------Property-------------------- */
.#{$sm-prefix} {
&slider {
.#{$app-prefix}field-integer {
flex: 1 1 65px;
}
}
}
.#{$sm-prefix}property { .#{$sm-prefix}property {
box-sizing: border-box; box-sizing: border-box;
float: left; float: left;
@ -246,7 +259,9 @@
&.#{$sm-prefix}composite, &.#{$sm-prefix}composite,
&.#{$sm-prefix}file, &.#{$sm-prefix}file,
&.#{$sm-prefix}list, &.#{$sm-prefix}list,
&.#{$sm-prefix}radio,
&.#{$sm-prefix}stack, &.#{$sm-prefix}stack,
&.#{$sm-prefix}slider,
&.#{$sm-prefix}color { &.#{$sm-prefix}color {
width: 100%; width: 100%;
} }
@ -273,7 +288,6 @@
box-sizing: border-box; box-sizing: border-box;
float: left; float: left;
width: 100%; width: 100%;
padding: 0 5px;
} }
&.#{$sm-prefix}file ##{$sm-prefix}preview-box { &.#{$sm-prefix}file ##{$sm-prefix}preview-box {
@ -285,12 +299,13 @@
&.#{$sm-prefix}show { &.#{$sm-prefix}show {
border: 1px solid darken($lightBorder, 1%); border: 1px solid darken($lightBorder, 1%);
padding: 3px 5px; padding: 3px 20px;
} }
##{$sm-prefix}close { ##{$sm-prefix}close {
@extend .#{$sm-prefix}close-btn; @extend .#{$sm-prefix}close-btn;
top: -5px;
display: block; display: block;
} }
} }
@ -377,7 +392,7 @@
/* ------------------END Property-------------------- */ /* ------------------END Property-------------------- */
.#{$sm-prefix}stack .#{$sm-prefix}properties { .#{$sm-prefix}stack .#{$sm-prefix}properties {
padding-top: 5px; padding: 5px 0 0;
} }
.#{$sm-prefix}stack ##{$sm-prefix}add { .#{$sm-prefix}stack ##{$sm-prefix}add {
@ -386,11 +401,12 @@
background: none; background: none;
border: none; border: none;
cursor: pointer; cursor: pointer;
outline: none;
font-size: 22px; font-size: 22px;
line-height: 10px; line-height: 10px;
position: absolute; position: absolute;
right: 0; right: 0;
top: -20px; top: -17px;
opacity: 0.75; opacity: 0.75;
&:hover { &:hover {

102
src/styles/scss/_gjs_variables.scss

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

91
src/styles/scss/main.scss

@ -206,36 +206,6 @@
pointer-events: none; pointer-events: none;
} }
div.#{$app-prefix}select {
padding: 0;
}
.#{$app-prefix}select select {
padding-right: 10px;
}
.#{$app-prefix}select:-moz-focusring,
.#{$app-prefix}select select:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 $mainLhlColor;
}
.#{$app-prefix}input:focus,
.#{$app-prefix}button:focus,
.#{$app-prefix}btn-prim:focus,
.#{$app-prefix}select:focus,
.#{$app-prefix}select select:focus {
outline: none;
}
.#{$app-prefix}select option,
.#{$clm-prefix}select option,
.#{$sm-prefix}select option,
.#{$sm-prefix}unit option {
@extend .#{$app-prefix}bg-main;
//background-color: $mainDkColor;
}
/************* TRAITS ****************/ /************* TRAITS ****************/
.#{$app-prefix}traits-label { .#{$app-prefix}traits-label {
border-bottom: 1px solid $mainDkColor; border-bottom: 1px solid $mainDkColor;
@ -266,41 +236,10 @@ div.#{$app-prefix}select {
@import "gjs_canvas"; @import "gjs_canvas";
/************* RTE ****************/ /********* COMMANDS **********/
#commands.panel {
min-width: 35px;
height: 100%;
z-index:3;
}
#options.panel{ z-index:4; bottom: 0;}
#views.panel {
width: 16.5%;
font-weight: lighter;
color: $fontColor;
right:0; top:0;
z-index: 3;
height: 100%;
padding:0;
}
#views.panel .c{height:100%}
#commands.panel, #options.panel {width: 3.5%; left:0;}
#options .c { display: table; margin: 0 auto; }
/*********TEST**********/
body.dragging, body.dragging * { cursor: move !important;}
.dragged {
position: absolute;
@include opacity(0.50);
z-index: 2000;}
ol.example li.placeholder {position: relative;}
ol.example li.placeholder:before {position: absolute;}
/*********END-TEST**********/
.no-dots, .ui-resizable-handle{ border: none !important; margin:0 !important; outline: none !important; } .no-dots, .ui-resizable-handle{ border: none !important; margin:0 !important; outline: none !important; }
/********* COMMANDS **********/
.#{$com-prefix}dashed *{ .#{$com-prefix}dashed *{
outline: 1px dashed #888; outline: 1px dashed #888;
outline-offset: -2px; outline-offset: -2px;
@ -799,33 +738,7 @@ ol.example li.placeholder:before {position: absolute;}
/*************RTE****************/ /*************RTE****************/
##{$rte-prefix}toolbar { @import 'gjs_rte';
@extend .#{$app-prefix}bg-main;
display: none;
border: 1px solid $mainDkColor;
position: absolute;
border-radius: 3px;
z-index: 10;
.#{$rte-prefix}btn {
@extend .#{$app-prefix}color-main;
display: inline-block;
padding: 5px;
min-width: 25px;
border-right: 1px solid $mainDkColor;
text-align: center;
cursor: pointer;
outline: none;
&:last-child{ border-right:none; }
&.btn-info{ background-color: $mainDkColor;}
&:hover { background-color: $mainLhColor;}
}
select{
height: 16px;
}
}
/********* Spectrum **********/ /********* Spectrum **********/

4
src/trait_manager/view/TraitSelectView.js

@ -1,5 +1,5 @@
var Backbone = require('backbone'); const TraitView = require('./TraitView');
var TraitView = require('./TraitView'); const $ = Backbone.$;
module.exports = TraitView.extend({ module.exports = TraitView.extend({

43
src/trait_manager/view/TraitView.js

@ -1,4 +1,4 @@
var Backbone = require('backbone'); const $ = Backbone.$;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({
@ -81,21 +81,28 @@ module.exports = Backbone.View.extend({
var md = this.model; var md = this.model;
var trg = this.target; var trg = this.target;
var name = md.get('name'); var name = md.get('name');
var opts = { const plh = md.get('placeholder') || md.get('default') || '';
placeholder: md.get('placeholder') || md.get('default'), const type = md.get('type') || 'text';
type: md.get('type') || 'text' const attrs = trg.get('attributes');
}; const min = md.get('min');
if(md.get('changeProp')){ const max = md.get('max');
opts.value = trg.get(name); const value = md.get('changeProp') ?
}else{ trg.get(name) : md.get('value') || attrs[name];
var attrs = trg.get('attributes'); const input = $(`<input type="${type}" placeholder="${plh}">`);
opts.value = md.get('value') || attrs[name];
if (value) {
input.prop('value', value);
}
if (min) {
input.prop('min', min);
} }
if(md.get('min'))
opts.min = md.get('min'); if (max) {
if(md.get('max')) input.prop('max', max);
opts.max = md.get('max'); }
this.$input = $('<input>', opts);
this.$input = input;
} }
return this.$input.get(0); return this.$input.get(0);
}, },
@ -123,8 +130,10 @@ module.exports = Backbone.View.extend({
renderField() { renderField() {
if(!this.$input){ if(!this.$input){
this.$el.append(this.tmpl); this.$el.append(this.tmpl);
var el = this.getInputEl(); const el = this.getInputEl();
this.$el.find('.' + this.inputhClass).prepend(el); // I use prepand expecially for checkbox traits
const inputWrap = this.el.querySelector(`.${this.inputhClass}`);
inputWrap.insertBefore(el, inputWrap.childNodes[0]);
} }
}, },

2321
src/utils/ColorPicker.js

File diff suppressed because it is too large

2
src/utils/Dragger.js

@ -1,3 +1,5 @@
const $ = Backbone.$;
var getBoundingRect = (el, win) => { var getBoundingRect = (el, win) => {
var w = win || window; var w = win || window;
var rect = el.getBoundingClientRect(); var rect = el.getBoundingClientRect();

2
src/utils/Resizer.js

@ -1,3 +1,5 @@
const $ = Backbone.$;
var defaults = { var defaults = {
// Function which returns custom X and Y coordinates of the mouse // Function which returns custom X and Y coordinates of the mouse
mousePosFetcher: null, mousePosFetcher: null,

2
src/utils/Sorter.js

@ -1,4 +1,4 @@
var Backbone = require('backbone'); const $ = Backbone.$;
module.exports = Backbone.View.extend({ module.exports = Backbone.View.extend({

309
src/utils/extender.js

@ -0,0 +1,309 @@
module.exports = ({$, Backbone}) => {
if (Backbone) {
const ViewProt = Backbone.View.prototype;
const eventNsMap = {};
ViewProt.eventNsMap = eventNsMap;
ViewProt.delegate = function(eventName, selector, listener) {
const vid = '.delegateEvents' + this.cid;
this.$el.on(eventName, selector, listener);
//return this;
let eventMap = eventNsMap[vid];
if (!eventMap) {
eventMap = [];
eventNsMap[vid] = eventMap;
}
eventMap.push({eventName, selector, listener});
return this;
};
ViewProt.undelegateEvents = function() {
const vid = '.delegateEvents' + this.cid;
if (this.$el) {
//this.$el.off(); return this;
let eventMap = eventNsMap[vid];
if (eventMap) {
eventMap.forEach(({eventName, selector, listener}) => {
this.$el.off(eventName);
});
}
}
return this;
};
ViewProt.undelegate = function(ev, sel, list) {
const vid = '.delegateEvents' + this.cid;
//this.$el.off(ev, sel, list); return this;
let eventMap = eventNsMap[vid];
if (eventMap) {
eventMap.forEach(({eventName, selector, listener}) => {
if (eventName == ev && selector == sel) {
this.$el.off(eventName);
}
});
}
return this;
};
}
if ($) {
const fn = $.fn;
const splitNamespace = function(name) {
const namespaceArray = name.split('.')
return ( name.indexOf('.') !== 0 ? [namespaceArray[0], namespaceArray.slice(1)] : [null, namespaceArray] );
}
/*
const CashEvent = function(node, eventName, namespaces, delegate, originalCallback, runOnce) {
const eventCache = getData(node,'_cashEvents') || setData(node, '_cashEvents', {});
const remove = function(c, namespace){
if ( c && originalCallback !== c ) { return; }
if ( namespace && this.namespaces.indexOf(namespace) < 0 ) { return; }
node.removeEventListener(eventName, callback);
};
const callback = function(e) {
var t = this;
if (delegate) {
t = e.target;
while (t && !matches(t, delegate)) {
if (t === this) {
return (t = false);
}
t = t.parentNode;
}
}
if (t) {
originalCallback.call(t, e, e.data);
if ( runOnce ) { remove(); }
}
};
this.remove = remove;
this.namespaces = namespaces;
node.addEventListener(eventName, callback);
eventCache[eventName] = eventCache[eventName] || [];
eventCache[eventName].push(this);
return this;
}
*/
const on = $.prototype.on;
const off = $.prototype.off;
const trigger = $.prototype.trigger;
const offset = $.prototype.offset;
const getEvents = (eventName) => eventName.split(/[,\s]+/g);
const getNamespaces = (eventName) => eventName.split('.');
fn.on = function(eventName, delegate, callback, runOnce) {
if (typeof eventName == 'string') {
const events = getEvents(eventName);
if (events.length == 1) {
eventName = events[0];
let namespaces = getNamespaces(eventName);
if (eventName.indexOf('.') !== 0) {
eventName = namespaces[0];
}
namespaces = namespaces.slice(1);
if (namespaces.length) {
//console.log('Found event with namespaces', namespaces, eventName, delegate, this);
const cashNs = this.data('_cashNs') || [];
// cashNs[namespace]
this.data('_cashNs', namespaces); // for each ns need to store '.store' => eventName, delegate, callback
}
return on.call(this, eventName, delegate, callback, runOnce);
} else {
events.forEach((eventName) =>
this.on(eventName, delegate, callback, runOnce));
return this;
}
} else {
return on.call(this, eventName, delegate, callback, runOnce)
}
}
fn.off = function(eventName, callback) {
if (typeof eventName == 'string') {
const events = getEvents(eventName);
if (events.length == 1) {
eventName = events[0];
let namespaces = getNamespaces(eventName);
if (eventName.indexOf('.') !== 0) {
eventName = namespaces[0];
}
namespaces = namespaces.slice(1);
if (namespaces.length) {
// Have to off only with the same namespace
}
return off.call(this, eventName, callback);
} else {
events.forEach((eventName) => this.off(eventName, callback));
return this;
}
} else {
return off.call(this, eventName, callback);
}
}
fn.trigger = function(eventName, data) {
if (eventName instanceof $.Event) {
return this.trigger(eventName.type, data);
}
if (typeof eventName == 'string') {
const events = getEvents(eventName);
if (events.length == 1) {
eventName = events[0];
let namespaces = getNamespaces(eventName);
if (eventName.indexOf('.') !== 0) {
eventName = namespaces[0];
}
namespaces = namespaces.slice(1);
if (namespaces.length) {
// have to trigger with same namespaces and eventName
}
return trigger.call(this, eventName, data);
} else {
events.forEach((eventName) => this.trigger(eventName, data));
return this;
}
} else {
return trigger.call(this, eventName, data);
}
}
fn.hide = function() {
return this.css('display', 'none');
}
fn.show = function() {
return this.css('display', 'block');
}
fn.focus = function() {
const el = this.get(0);
el && el.focus();
return this;
}
fn.remove = function () {
return this.each(node => {
return node.parentNode && node.parentNode.removeChild(node);
});
},
// For spectrum compatibility
fn.bind = function(ev, h) {
return this.on(ev, h);
}
fn.unbind = function(ev, h) {
return this.off(ev, h);
}
fn.click = function(h) {
return h ? this.on('click', h) : this.trigger('click');
}
fn.change = function(h) {
return h ? this.on('change', h) : this.trigger('change');
}
fn.keydown = function(h) {
return h ? this.on('keydown', h) : this.trigger('keydown');
}
fn.delegate = function(selector, events, data, handler) {
if (!handler) {
handler = data;
}
return this.on(events, selector, function(e) {
e.data = data;
handler(e);
});
}
fn.scrollLeft = function() {
let el = this.get(0);
el = el.nodeType == 9 ? el.defaultView : el;
let win = el instanceof Window ? el : null;
return win ? win.pageXOffset : el.scrollLeft || 0;
}
fn.scrollTop = function() {
let el = this.get(0);
el = el.nodeType == 9 ? el.defaultView : el;
let win = el instanceof Window ? el : null;
return win ? win.pageYOffset : el.scrollTop || 0;
}
fn.offset = function(coords) {
let top, left;
if (coords) {
top = coords.top;
left = coords.left;
}
if (typeof top != 'undefined') {
this.css('top', `${top}px`);
}
if (typeof left != 'undefined') {
this.css('left', `${left}px`);
}
return offset.call(this);
};
$.map = function(items, clb) {
const ar = [];
for (var i = 0; i < items.length; i++) {
ar.push(clb(items[i], i));
}
return ar;
}
$.inArray = function(val, arr) {
return arr.indexOf(val);
}
$.Event = function(src, props) {
if (!(this instanceof $.Event) ) {
return new $.Event(src, props);
}
this.type = src;
this.isDefaultPrevented = () => false;
}
}
}

19
src/utils/mixins.js

@ -0,0 +1,19 @@
const on = (el, ev, fn) => {
ev = ev.split(/\s+/);
el = el instanceof Array ? el : [el];
for (let i = 0; i < ev.length; ++i) {
el.forEach(elem => elem.addEventListener(ev[i], fn));
}
}
const off = (el, ev, fn) => {
ev = ev.split(/\s+/);
el = el instanceof Array ? el : [el];
for (let i = 0; i < ev.length; ++i) {
el.forEach(elem => elem.removeEventListener(ev[i], fn));
}
}
export {on, off}

21
test/helper.js

@ -1,24 +1,17 @@
import _ from 'underscore'; import _ from 'underscore';
import expect from 'expect'; import expect from 'expect';
import sinon from 'sinon'; import sinon from 'sinon';
import Backbone from 'backbone';
import grapesjs from './../src';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import jquery from 'jquery';
const dom = new JSDOM('<!doctype html><html><body></body></html>'); const dom = new JSDOM('<!doctype html><html><body></body></html>');
const window = dom.window; const window = dom.window;
const $ = jquery(window);
//https://www.npmjs.com/package/proxyquire // Fix for the require of jquery
// Fix for the spectrum lib
var Module = require('module'); var Module = require('module');
var originalRequire = Module.prototype.require; var originalRequire = Module.prototype.require;
Module.prototype.require = function(name) { Module.prototype.require = function(name) {
if (name == 'jquery') { if (name == 'jquery') {
return $; return originalRequire.call(this, 'cash-dom');
} }
return originalRequire.apply(this, arguments); return originalRequire.apply(this, arguments);
}; };
@ -37,16 +30,16 @@ var localStorage = {
global.window = window; global.window = window;
global.document = window.document; global.document = window.document;
global.$ = $; global.FormData = window.FormData;
global._ = _; global._ = _;
global.expect = expect; global.expect = expect;
global.sinon = sinon; global.sinon = sinon;
global.grapesjs = grapesjs; global.grapesjs = require('./../src');
global.Backbone = Backbone; global.Backbone = require('backbone');
global.localStorage = localStorage; global.localStorage = localStorage;
global.SVGElement = global.Element; global.SVGElement = global.Element;
window.$ = $; window.$ = Backbone.$;
Backbone.$ = $; global.navigator = {userAgent: 'node.js'};
Object.keys(window).forEach((key) => { Object.keys(window).forEach((key) => {
if (!(key in global)) { if (!(key in global)) {

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

Loading…
Cancel
Save