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/
.sass-cache/
.project
npm-debug.log
.idea
npm-debug.log*
style/.sass-cache/
img/
@ -13,3 +14,4 @@ vendor/
coverage/
node_modules/
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)
[![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>
<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.
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)
* [Testing](#testing)
* [Plugins](#plugins)
* [Sponsors](#sponsors)
* [Support](#support)
* [License](#license)
@ -106,13 +111,6 @@ $ npm run build
## 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
<link rel="stylesheet" href="path/to/grapes.min.css">
<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
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">
<title>GrapesJS</title>
<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>
</head>
@ -1037,7 +1036,7 @@
sectors: [{
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'],
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom']
},{
name: 'Dimension',
open: false,
@ -1062,7 +1061,15 @@
},{
name: 'Extra',
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',
open: false,
@ -1324,7 +1331,7 @@
var model = view.model;
let targetValue = view.getTargetValue({ignoreDefault: 1});
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);
});

22
package.json

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

3
src/asset_manager/view/AssetsView.js

@ -153,7 +153,8 @@ module.exports = Backbone.View.extend({
if (hide) {
assetsEl.empty();
} 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 config = this.config;
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);
if (config.autoAdd && target) {

115
src/block_manager/index.js

@ -2,7 +2,9 @@
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [getAllVisible](#getallvisible)
* * [getCategories](#getcategories)
* * [getContainer](#getcontainer)
* * [render](#render)
*
* 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'),
BlockCategories = require('./model/Categories'),
BlocksView = require('./view/BlocksView');
var blocks, view;
var blocks, blocksVisible, blocksView;
var categories = [];
return {
@ -52,19 +54,58 @@ module.exports = () => {
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
const em = c.em;
for (let name in defaults) {
if (!(name in c)) {
c[name] = defaults[name];
}
}
blocks = new Blocks(c.blocks);
// Global blocks collection
blocks = new Blocks([]);
blocksVisible = new Blocks([]);
categories = new BlockCategories(),
view = new BlocksView({
collection: blocks,
blocksView = new BlocksView({
// Visible collection
collection: blocksVisible,
categories,
}, 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;
},
/**
* 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.
* @param {string} id Block id
@ -98,7 +139,7 @@ module.exports = () => {
* Return the block by id
* @param {string} id Block id
* @example
* var block = blockManager.get('h1-block');
* const block = blockManager.get('h1-block');
* console.log(JSON.stringify(block));
* // {label: 'Heading', content: '<h1>Put your ...', ...}
*/
@ -110,7 +151,7 @@ module.exports = () => {
* Return all blocks
* @return {Collection}
* @example
* var blocks = blockManager.getAll();
* const blocks = blockManager.getAll();
* console.log(JSON.stringify(blocks));
* // [{label: 'Heading', content: '<h1>Put your ...'}, ...]
*/
@ -118,9 +159,26 @@ module.exports = () => {
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.
* 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}
*/
getCategories() {
@ -128,20 +186,43 @@ module.exports = () => {
},
/**
* Render blocks
* Return the Blocks container element
* @return {HTMLElement}
*/
render() {
return view.render().el;
getContainer() {
return blocksView.el;
},
/**
* Remove block by id
* @param {string} id Block id
* @return {Block} Removed block
* Render blocks
* @param {Array} blocks Blocks to render, without the argument will render
* 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) {
return blocks.remove(id);
render(blocks) {
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({

12
src/block_manager/view/BlocksView.js

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

19
src/canvas/index.js

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

64
src/canvas/view/CanvasView.js

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

2
src/code_manager/view/EditorView.js

@ -18,7 +18,7 @@ module.exports = Backbone.View.extend({
obj.pfx = this.pfx;
this.$el.html( this.template(obj) );
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;
},

15
src/commands/index.js

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

188
src/commands/view/CommandAbstract.js

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

4
src/commands/view/CreateComponent.js

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

4
src/commands/view/DeleteComponent.js

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

4
src/commands/view/ExportTemplate.js

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

16
src/commands/view/MoveComponent.js

@ -1,6 +1,8 @@
var Backbone = require('backbone');
var SelectComponent = require('./SelectComponent');
var SelectPosition = require('./SelectPosition');
import {on, off} from 'utils/mixins'
const SelectComponent = require('./SelectComponent');
const SelectPosition = require('./SelectPosition');
const $ = Backbone.$;
module.exports = _.extend({}, SelectPosition, SelectComponent, {
@ -49,7 +51,7 @@ module.exports = _.extend({}, SelectPosition, SelectComponent, {
this.sorter.onEndMove = this.onEndMove.bind(this);
this.stopSelectComponent();
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.getContentWindow().on('keydown', this.rollback);
on(this.getContentWindow(), 'keydown', this.rollback);
},
onEndMoveFromModel() {
this.getContentWindow().off('keydown', this.rollback);
off(this.getContentWindow(), 'keydown', this.rollback);
},
/**
@ -90,7 +92,7 @@ module.exports = _.extend({}, SelectPosition, SelectComponent, {
*/
onEndMove() {
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 = {
run(editor, sender) {
var config = editor.Config;
var pfx = config.stylePrefix;
var bm = editor.BlockManager;
var panelC;
if(!this.blocks){
this.blocks = $('<div/>').get(0);
this.blocks.appendChild(bm.render());
var panels = editor.Panels;
if(!panels.getPanel('views-container'))
panelC = panels.addPanel({id: 'views-container'});
else
panelC = panels.getPanel('views-container');
panelC.set('appendContent', this.blocks).trigger('change:appendContent');
const bm = editor.BlockManager;
const pn = editor.Panels;
if (!this.blocks) {
bm.render();
const id = 'views-container';
const blocks = document.createElement('div');
const panels = pn.getPanel(id) || pn.addPanel({id});
blocks.appendChild(bm.getContainer());
panels.set('appendContent', blocks).trigger('change:appendContent');
this.blocks = blocks;
}
this.blocks.style.display = 'block';
},
stop() {
if(this.blocks)
this.blocks.style.display = 'none';
const blocks = this.blocks;
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 = {

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

4
src/commands/view/OpenTraitManager.js

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

53
src/commands/view/SelectComponent.js

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

24
src/commands/view/ShowOffset.js

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

1
src/dom_components/index.js

@ -162,7 +162,6 @@ module.exports = () => {
// Load dependencies
if (em) {
c.rte = em.get('rte') || '';
c.modal = em.get('Modal') || '';
c.am = em.get('AssetManager') || '';
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;
if(!model.get('toolbar')) {
var tb = [];
if(model.collection) {
tb.push({
attributes: {class: 'fa fa-arrow-up'},
command: 'select-parent',
});
}
if(model.get('draggable')) {
tb.push({
attributes: {class: 'fa fa-arrows'},

112
src/dom_components/view/ComponentTextView.js

@ -1,67 +1,96 @@
var Backbone = require('backbone');
var ComponentView = require('./ComponentView');
import {on, off} from 'utils/mixins'
const ComponentView = require('./ComponentView');
module.exports = ComponentView.extend({
events: {
'dblclick': 'enableEditing',
'change': 'parseRender',
},
initialize(o) {
ComponentView.prototype.initialize.apply(this, arguments);
_.bindAll(this,'disableEditing');
this.disableEditing = this.disableEditing.bind(this);
const model = this.model;
const em = this.em;
this.listenTo(model, 'focus active', this.enableEditing);
this.listenTo(model, 'change:content', this.updateContent);
this.rte = this.config.rte || '';
this.activeRte = null;
this.em = this.config.em;
this.rte = em && em.get('RichTextEditor');
},
/**
* Enable the component to be editable
* @param {Event} e
* Enable element content editing
* @private
* */
enableEditing(e) {
var editable = this.model.get('editable');
if(this.rte && editable) {
enableEditing() {
if (this.rteEnabled) {
return;
}
const editable = this.model.get('editable');
const rte = this.rte;
if (rte && editable) {
try {
this.activeRte = this.rte.attach(this, this.activeRte);
this.rte.focus(this, this.activeRte);
this.activeRte = rte.enable(this, this.activeRte);
} catch (err) {
console.error(err);
}
}
this.rteEnabled = 1;
this.toggleEvents(1);
},
/**
* Disable this component to be editable
* @param {Event}
* Disable element content editing
* @private
* */
disableEditing(e) {
var model = this.model;
var editable = model.get('editable');
disableEditing() {
const model = this.model;
const editable = model.get('editable');
const rte = this.rte;
if(this.rte && editable) {
if (rte && editable) {
try {
this.rte.detach(this, this.activeRte);
rte.disable(this, this.activeRte);
} catch (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) {
this.parseRender();
const content = this.getChildrenContainer().innerHTML;
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();
},
@ -74,37 +103,18 @@ module.exports = ComponentView.extend({
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
* @param {Boolean} enable
*/
toggleEvents(enable) {
var method = enable ? 'on' : 'off';
const mixins = {on, off};
// The ownerDocument is from the frame
var elDocs = [this.el.ownerDocument, document, this.rte];
$(elDocs).off('mousedown', this.disableEditing);
$(elDocs)[method]('mousedown', this.disableEditing);
var elDocs = [this.el.ownerDocument, document];
mixins.off(elDocs, 'mousedown', this.disableEditing);
mixins[method](elDocs, 'mousedown', this.disableEditing);
// Avoid closing edit mode on component click
this.$el.off('mousedown', this.disablePropagation);

137
src/dom_components/view/ComponentView.js

@ -1,5 +1,4 @@
var Backbone = require('backbone');
var ComponentsView = require('./ComponentsView');
const ComponentsView = require('./ComponentsView');
module.exports = Backbone.View.extend({
@ -12,33 +11,38 @@ module.exports = Backbone.View.extend({
},
initialize(opt) {
var model = this.model;
const model = this.model;
this.opts = opt || {};
this.config = this.opts.config || {};
this.em = this.config.em || '';
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.components = model.get('components');
this.attr = model.get("attributes");
this.attr = model.get('attributes');
this.classe = this.attr.class || [];
const $el = this.$el;
const classes = model.get('classes');
this.listenTo(model, 'destroy remove', this.remove);
this.listenTo(model, 'change:style', this.updateStyle);
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:state', this.updateState);
this.listenTo(model, 'change:script', this.render);
this.listenTo(model, 'change', this.handleChange);
this.listenTo(model.get('classes'), 'add remove change', this.updateClasses);
this.$el.data('model', model);
this.listenTo(classes, 'add remove change', this.updateClasses);
$el.data('model', model);
$el.data('collection', model.get('components'));
model.view = this;
this.$el.data("collection", this.components);
if(model.get('classes').length)
this.importClasses();
classes.length && this.importClasses();
this.init();
},
remove() {
Backbone.View.prototype.remove.apply(this);
const children = this.childrenView;
children && children.stopListening();
},
/**
* 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.
* This method is called before initialize
@ -148,36 +191,19 @@ module.exports = Backbone.View.extend({
* @private
* */
updateAttributes() {
var model = this.model;
var attributes = {},
attr = model.get("attributes");
for(var key in attr) {
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;
const model = this.model;
const attrs = {}
const attr = model.get('attributes');
const src = model.get('src');
this.$el.attr(attributes);
},
for (let key in attr) {
attrs[key] = attr[key];
}
/**
* Update style attribute
* @private
* */
updateStyle() {
this.$el.attr('style', this.getStyleString());
src && (attrs.src = src);
this.$el.attr(attrs);
this.updateHighlight();
this.updateStyle();
},
/**
@ -204,27 +230,6 @@ module.exports = Backbone.View.extend({
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
* @param object Event that generated the request
@ -300,15 +305,16 @@ module.exports = Backbone.View.extend({
* @private
*/
renderChildren() {
var view = new ComponentsView({
const container = this.getChildrenContainer();
const view = new ComponentsView({
collection: this.model.get('components'),
config: this.config,
componentTypes: this.opts.componentTypes,
});
var container = this.getChildrenContainer();
var childNodes = view.render($(container)).el.childNodes;
childNodes = Array.prototype.slice.call(childNodes);
view.render(container);
this.childrenView = view;
const childNodes = Array.prototype.slice.call(view.el.childNodes);
for (var i = 0, len = childNodes.length ; i < len; i++) {
container.appendChild(childNodes.shift());
@ -339,7 +345,6 @@ module.exports = Backbone.View.extend({
render() {
this.renderAttributes();
var model = this.model;
this.updateContent();
this.renderChildren();
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({
initialize(o) {
this.opts = o || {};
this.config = o.config || {};
this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render );
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset', this.resetChildren);
},
/**
@ -17,7 +18,7 @@ module.exports = Backbone.View.extend({
* @private
* */
addTo(model) {
var i = this.collection.indexOf(model);
var i = this.collection.indexOf(model);
this.addToCollection(model, null, i);
var em = this.config.em;
@ -42,7 +43,6 @@ module.exports = Backbone.View.extend({
this.compView = require('./ComponentView');
var fragment = fragmentEl || null,
viewObject = this.compView;
//console.log('Add to collection', model, 'Index',i);
var dt = this.opts.componentTypes;
@ -66,44 +66,47 @@ module.exports = Backbone.View.extend({
if(view.model.get('type') == 'textnode')
rendered = document.createTextNode(view.model.get('content'));
if(fragment){
if (fragment) {
fragment.appendChild(rendered);
}else{
var p = this.$parent;
var pc = p.children;
if(typeof index != 'undefined'){
var method = 'before';
} else {
const parent = this.parentEl;
const children = parent.childNodes;
if (!isUndefined(index)) {
const lastIndex = children.length == index;
// If the added model is the last of collection
// need to change the logic of append
if(pc && p.children().length == index){
if (lastIndex) {
index--;
method = 'after';
}
// In case the added is new in the collection index will be -1
if(index < 0) {
p.append(rendered);
}else {
if(pc) {
p.children().eq(index)[method](rendered);
}
if (lastIndex || !children.length) {
parent.appendChild(rendered);
} else {
parent.insertBefore(rendered, children[index]);
}
}else{
p.append(rendered);
} else {
parent.appendChild(rendered);
}
}
return rendered;
},
render($p) {
var fragment = document.createDocumentFragment();
this.$parent = $p || this.$el;
this.$el.empty();
this.collection.each(function(model){
this.addToCollection(model, fragment);
},this);
this.$el.append(fragment);
resetChildren() {
this.parentEl.innerHTML = '';
this.collection.each(model => this.addToCollection(model));
},
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;
}

11
src/dom_components/view/ToolbarButtonView.js

@ -14,15 +14,18 @@ module.exports = Backbone.View.extend({
},
handleClick(event) {
var opts = {event};
var command = this.model.get('command');
event.preventDefault();
event.stopPropagation();
const opts = {event};
const command = this.model.get('command');
const editor = this.editor;
if (typeof command === 'function') {
command(this.editor, null, opts);
command(editor, null, opts);
}
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({
@ -6,24 +6,35 @@ module.exports = Backbone.View.extend({
'change': 'handleChange',
},
template: _.template(`<span class='<%= holderClass %>'></span>`),
template() {
const holderClass = this.holderClass;
return `<span class="${holderClass}"></span>`;
},
initialize(opts) {
var opt = opts || {};
var ppfx = opt.ppfx || '';
this.target = opt.target || {};
initialize(opts = {}) {
const ppfx = opts.ppfx || '';
this.target = opts.target || {};
this.inputClass = ppfx + 'field';
this.inputHolderClass = ppfx + 'input-holder';
this.holderClass = `${ppfx}input-holder`;
this.ppfx = ppfx;
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
*/
handleChange(e) {
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 {Object} opts
*/
setValue(value, opts) {
var opt = opts || {};
var model = this.model;
model.set({
value: value || model.get('defaults')
}, opt);
// Generally I get silent when I need to reflect data to view without
// reupdating the target
if(opt.silent) {
this.handleModelChange();
}
setValue(value, opts = {}) {
const model = this.model;
let val = value || model.get('defaults');
const input = this.getInputEl();
input && (input.value = val);
},
/**
* Updates the view when the model is changed
* */
handleModelChange() {
this.getInputEl().value = this.model.get('value');
handleModelChange(model, value, opts) {
this.setValue(value, opts);
},
/**
@ -58,23 +62,20 @@ module.exports = Backbone.View.extend({
*/
getInputEl() {
if(!this.inputEl) {
this.inputEl = $('<input>', {
type: 'text',
class: this.inputCls,
placeholder: this.model.get('defaults')
});
const plh = this.model.get('defaults');
const cls = this.inputCls;
this.inputEl = $(`<input type="text" class="${cls}" placeholder="${plh}">`);
}
return this.inputEl.get(0);
},
render() {
var el = this.$el;
const el = this.$el;
const ppfx = this.ppfx;
const holderClass = this.holderClass;
el.addClass(this.inputClass);
el.html(this.template({
holderClass: this.inputHolderClass,
ppfx: this.ppfx
}));
el.find('.'+ this.inputHolderClass).html(this.getInputEl());
el.html(this.template());
el.find(`.${holderClass}`).append(this.getInputEl());
return this;
}

118
src/domain_abstract/ui/InputColor.js

@ -1,40 +1,55 @@
var Backbone = require('backbone');
var Input = require('./Input');
var Spectrum = require('spectrum-colorpicker');
const Input = require('./Input');
//require('spectrum-colorpicker');
require('utils/ColorPicker');
const $ = Backbone.$;
module.exports = Input.extend({
template: _.template(`
<div class='<%= ppfx %>input-holder'></div>
<div class="<%= ppfx %>field-colorp">
<div class="<%= ppfx %>field-colorp-c">
<div class="<%= ppfx %>checker-bg"></div>
</div>
</div>`),
template() {
const ppfx = this.ppfx;
return `
<div class="${ppfx}input-holder"></div>
<div class="${ppfx}field-colorp">
<div class="${ppfx}field-colorp-c">
<div class="${ppfx}checker-bg"></div>
</div>
</div>
`;
},
initialize(opts) {
Input.prototype.initialize.apply(this, arguments);
var ppfx = this.ppfx;
this.colorCls = ppfx + 'field-color-picker';
this.inputClass = ppfx + 'field ' + ppfx + 'field-color';
this.colorHolderClass = ppfx + 'field-colorp-c';
const ppfx = this.ppfx;
this.colorCls = `${ppfx}field-color-picker`;
this.inputClass = `${ppfx}field ${ppfx}field-color`;
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
* */
handleModelChange(...args) {
Input.prototype.handleModelChange.apply(this, args);
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;
handleModelChange(model, value, opts) {
this.setValue(value, opts);
},
/**
@ -42,16 +57,22 @@ module.exports = Input.extend({
* @return {HTMLElement}
*/
getColorEl() {
if(!this.colorEl) {
if (!this.colorEl) {
const self = this;
var model = this.model;
var colorEl = $('<div>', {class: this.colorCls});
var colorEl = $(`<div class="${this.colorCls}"></div>`);
var cpStyle = colorEl.get(0).style;
var elToAppend = this.target && this.target.config ? this.target.config.el : '';
if (typeof colorEl.spectrum == 'undefined') {
throw 'Spectrum missing, probably you load jQuery twice';
const getColor = color => {
let cl = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
return cl.replace(/ /g, '');
}
let changed = 0;
let previousСolor;
this.$el.find(`.${this.colorHolderClass}`).append(colorEl);
colorEl.spectrum({
appendTo: elToAppend || 'body',
maxSelectionSize: 8,
@ -61,24 +82,43 @@ module.exports = Input.extend({
cancelText: '⨯',
palette: [],
move(color) {
var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
cpStyle.backgroundColor = c;
const cl = getColor(color);
cpStyle.backgroundColor = cl;
model.set('value', cl, {avoidStore: 1});
},
change(color) {
var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
c = c.replace(/ /g,'');
cpStyle.backgroundColor = c;
model.set('value', c);
changed = 1;
const cl = getColor(color);
cpStyle.backgroundColor = cl;
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;
}
return this.colorEl;
},
render(...args) {
Input.prototype.render.apply(this, args);
this.$el.find('.' + this.colorHolderClass).html(this.getColorEl());
render() {
Input.prototype.render.call(this);
// This will make the color input available on render
this.getColorEl();
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(`
<span class='<%= ppfx %>input-holder'></span>
@ -16,18 +17,18 @@ module.exports = Backbone.View.extend({
_.bindAll(this, 'moveIncrement', 'upIncrement');
var opt = opts || {};
var ppfx = opt.ppfx || '';
var contClass = opt.contClass || (ppfx + 'field');
var contClass = opt.contClass || (`${ppfx}field ${ppfx}field-integer`);
this.ppfx = ppfx;
this.docEl = $(document);
this.doc = document;
this.inputCls = ppfx + 'field-number';
this.unitCls = ppfx + 'input-unit';
this.contClass = contClass;
this.events['click .' + ppfx + 'field-arrow-u'] = 'upArrowClick';
this.events['click .' + ppfx + 'field-arrow-d'] = 'downArrowClick';
this.events['mousedown .' + ppfx + 'field-arrows'] = 'downIncrement';
this.events['change .' + this.inputCls] = 'handleChange';
this.events['change .' + this.unitCls] = 'handleUnitChange';
this.events = {};
this.events[`click .${ppfx}field-arrow-u`] = 'upArrowClick';
this.events[`click .${ppfx}field-arrow-d`] = 'downArrowClick';
this.events[`mousedown .${ppfx}field-arrows`] = 'downIncrement';
this.events[`change .${this.inputCls}`] = 'handleChange';
this.events[`change .${this.unitCls}`] = 'handleUnitChange';
this.listenTo(this.model, 'change:unit change:value', this.handleModelChange);
this.delegateEvents();
},
@ -100,12 +101,10 @@ module.exports = Backbone.View.extend({
* @return {HTMLElement}
*/
getInputEl() {
if(!this.inputEl) {
this.inputEl = $('<input>', {
type: 'text',
class: this.inputCls,
placeholder: this.model.get('defaults')
});
if (!this.inputEl) {
const cls = this.inputCls;
const plh = this.model.get('defaults');
this.inputEl = $(`<input type="text" class="${cls}" placeholder="${plh}">`);
}
return this.inputEl.get(0);
},
@ -125,20 +124,24 @@ module.exports = Backbone.View.extend({
unitStr += '<option ' + selected + ' >' + unit + '</option>';
});
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
* */
upArrowClick() {
var value = this.model.get('value');
value = isNaN(value) ? 1 : parseInt(value, 10) + 1;
const model = this.model;
const step = model.get('step');
let value = model.get('value');
value = this.normalizeValue(value + step);
var valid = this.validateInputValue(value);
this.model.set('value', valid.value);
model.set('value', valid.value);
this.elementUpdated();
},
@ -146,10 +149,12 @@ module.exports = Backbone.View.extend({
* Invoked when the down arrow is clicked
* */
downArrowClick() {
var value = this.model.get('value');
value = isNaN(value) ? 0 : parseInt(value, 10) - 1;
const model = this.model;
const step = model.get('step');
let value = model.get('value');
value = this.normalizeValue(value - step);
var valid = this.validateInputValue(value);
this.model.set('value', valid.value);
model.set('value', valid.value);
this.elementUpdated();
},
@ -163,10 +168,10 @@ module.exports = Backbone.View.extend({
e.preventDefault();
this.moved = 0;
var value = this.model.get('value');
value = isNaN(value) ? 0 : parseInt(value, 10);
var current = {y: e.pageY, val: value };
this.docEl.mouseup(current, this.upIncrement);
this.docEl.mousemove(current, this.moveIncrement);
value = this.normalizeValue(value);
this.current = {y: e.pageY, val: value};
on(this.doc, 'mousemove', this.moveIncrement);
on(this.doc, 'mouseup', this.upIncrement);
},
/** While the increment is clicked, moving the mouse will update input value
@ -176,30 +181,50 @@ module.exports = Backbone.View.extend({
* */
moveIncrement(ev) {
this.moved = 1;
var pos = parseInt(ev.data.val - ev.pageY + ev.data.y, 10);
this.prValue = this.validateInputValue(pos).value;//Math.max(this.min, Math.min(this.max, pos) );
this.model.set('value', this.prValue, {avoidStore: 1});
const model = this.model;
const step = model.get('step');
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;
},
/**
* Stop moveIncrement method
* @param Object
*
* @return void
* */
upIncrement(e) {
this.docEl.off('mouseup', this.upIncrement);
this.docEl.off('mousemove', this.moveIncrement);
upIncrement() {
const model = this.model;
const step = model.get('step');
off(this.doc, 'mouseup', this.upIncrement);
off(this.doc, 'mousemove', this.moveIncrement);
if(this.prValue && this.moved) {
var value = this.prValue - 1;
this.model.set('value', value, {avoidStore: 1})
.set('value', value + 1);
var value = this.prValue - step;
model.set('value', value, {avoidStore: 1})
.set('value', value + step);
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
* @param {String} value Raw value
@ -253,11 +278,13 @@ module.exports = Backbone.View.extend({
},
render() {
var ppfx = this.ppfx;
this.$el.html(this.template({ppfx}));
this.$el.find('.'+ ppfx +'input-holder').html(this.getInputEl());
this.$el.find('.' + ppfx + 'field-units').html(this.getUnitEl());
this.$el.addClass(this.contClass);
const ppfx = this.ppfx;
const el = this.$el;
el.html(this.template({ppfx}));
el.find(`.${ppfx}input-holder`).append(this.getInputEl());
const unit = this.getUnitEl();
unit && el.find(`.${ppfx}field-units`).get(0).appendChild(unit);
el.addClass(this.contClass);
return this;
}

10
src/editor/index.js

@ -37,6 +37,9 @@
* * `component:update:{propertyName}` - Listen any property change
* * `component:styleUpdate` - Triggered when the style of the component is updated
* * `component:styleUpdate:{propertyName}` - Listen for a specific style property change
* * `component:selected` - New component selected
* * `block:add` - New block added
* * `block:remove` - Block removed
* * `asset:add` - New asset added
* * `asset:remove` - Asset removed
* * `asset:upload:start` - Before the upload is started
@ -45,9 +48,14 @@
* * `asset:upload:response` - On upload response, passes the result as an argument
* * `styleManager:change` - Triggered on style property change from new selected component, the view of the property is passed as an argument to the callback
* * `styleManager:change:{propertyName}` - As above but for a specific style property
* * `storage:start` - Before the storage request is started
* * `storage:load` - Triggered when something was loaded from the storage, loaded object passed as an argumnet
* * `storage:store` - Triggered when something is stored to the storage, stored object passed as an argumnet
* * `storage:end` - After the storage request is ended
* * `storage:error` - On any error on storage request, passes the error as an argument
* * `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
* * `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'))
@ -201,7 +209,7 @@ module.exports = config => {
* @property {RichTextEditor}
* @private
*/
RichTextEditor: em.get('rte'),
RichTextEditor: em.get('RichTextEditor'),
/**
* @property {Utils}

83
src/editor/model/Editor.js

@ -1,31 +1,36 @@
var deps = [
require('utils'),
require('storage_manager'),
require('device_manager'),
require('parser'),
require('selector_manager'),
require('modal_dialog'),
require('code_manager'),
require('panels'),
require('rich_text_editor'),
require('style_manager'),
require('asset_manager'),
require('css_composer'),
require('dom_components'),
require('canvas'),
require('commands'),
require('block_manager'),
require('trait_manager'),
import { isUndefined, defaults } from 'underscore';
const deps = [
require('utils'),
require('storage_manager'),
require('device_manager'),
require('parser'),
require('selector_manager'),
require('modal_dialog'),
require('code_manager'),
require('panels'),
require('rich_text_editor'),
require('style_manager'),
require('asset_manager'),
require('css_composer'),
require('dom_components'),
require('canvas'),
require('commands'),
require('block_manager'),
require('trait_manager'),
];
var Backbone = require('backbone');
var UndoManager = require('backbone-undo');
var key = require('keymaster');
var timedInterval;
const Backbone = require('backbone');
const UndoManager = require('backbone-undo');
const key = require('keymaster');
let timedInterval;
if (!Backbone.$) {
Backbone.$ = $;
}
require('utils/extender')({
Backbone: Backbone,
$: Backbone.$
});
const $ = Backbone.$;
module.exports = Backbone.Model.extend({
@ -291,10 +296,12 @@ module.exports = Backbone.Model.extend({
* @private
* */
componentSelected(model, val, options) {
if(!this.get('selectedComponent'))
if (!this.get('selectedComponent')) {
this.trigger('deselect-comp');
else
} else {
this.trigger('select-comp',[model,val,options]);
this.trigger('component:selected', arguments);
}
},
/**
@ -605,4 +612,26 @@ module.exports = Backbone.Model.extend({
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({
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 = (() => {
const defaultConfig = require('./config/config');
@ -9,10 +10,15 @@ module.exports = (() => {
return {
$,
editors,
plugins,
// Will be replaced on build
version: '<# VERSION #>',
/**
* Initializes an editor based on passed options
* @param {Object} config Configuration object
@ -34,11 +40,6 @@ module.exports = (() => {
init(config = {}) {
const els = config.container;
// Make a missing $ more verbose
if (isUndefined($)) {
throw 'jQuery not found';
}
if (!els) {
throw new Error("'container' is required");
}

15
src/modal_dialog/view/ModalView.js

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

22
src/navigator/view/ItemsView.js

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

2
src/panels/view/ButtonView.js

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

35
src/rich_text_editor/config/config.js

@ -1,40 +1,11 @@
module.exports = {
stylePrefix : 'rte-',
toolbarId : 'toolbar',
// If true, moves the toolbar below the element when the top canvas
// edge is reached
adjustToolbar: 1,
// Default toolbar commands
commands : [{
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'}
]
}*/],
// Default RTE actions
actions: ['bold', 'italic', 'underline', 'strikethrough', 'link'],
};

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.
* 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')
* and leave this task to the Style Manager.
@ -16,19 +11,15 @@
* ```js
* 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
*/
import RichTextEditor from './model/RichTextEditor';
import {on, off} from 'utils/mixins'
module.exports = () => {
var c = {},
defaults = require('./config/config'),
rte = require('./view/TextEditorView'),
CommandButtons = require('./model/CommandButtons'),
CommandButtonsView = require('./view/CommandButtonsView');
var tlbPfx, toolbar, commands;
var mainSelf;
let config = {};
const defaults = require('./config/config');
let toolbar, actions, lastEl, globalRte;
return {
@ -39,77 +30,182 @@ module.exports = () => {
* @type {String}
* @private
*/
name: 'rte',
name: 'RichTextEditor',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @param {Object} opts Options
* @private
*/
init(config) {
mainSelf = this;
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
init(opts = {}) {
config = opts;
for (let name in defaults) {
if (!(name in config)) {
config[name] = defaults[name];
}
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
const ppfx = config.pStylePrefix;
tlbPfx = c.stylePrefix;
commands = new CommandButtons(c.commands);
toolbar = new CommandButtonsView({
collection: commands,
config: c,
});
if (ppfx) {
config.stylePrefix = ppfx + config.stylePrefix;
}
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;
},
/**
* Add a new command to the toolbar
* @param {string} command Command name
* @param {Object} opts Command options
* @return {Model} Added command
* Post render callback
* @param {View} ev
* @private
*/
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
* var cm = rte.add('bold', {
* title: 'Make bold',
* class: 'fa fa-bold',
* rte.add('bold', {
* icon: '<b>B</b>',
* attributes: {title: 'Bold',}
* result: rte => rte.exec('bold')
* });
* // With arguments
* var cm = rte.add('fontSize', {
* title: 'Font size',
* options: [
* {name: 'Big', value: 5},
* {name: 'Normal', value: 3},
* {name: 'Small', value: 1}
* ]
* rte.add('link', {
* icon: document.getElementById('t'),
* attributes: {title: 'Link',}
* // Example on it's easy to wrap a selected content
* result: rte => rte.insertHTML(`<a href="#">${rte.selection()}</a>`)
* });
* // 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) {
var obj = opts || {};
obj.command = command;
return commands.add(obj);
add(name, action = {}) {
action.name = name;
globalRte.addAction(action, {sync: 1});
},
/**
* Get the command by its name
* @param {string} command Command name
* @return {Model}
* Get the action by its name
* @param {string} name Action name
* @return {Object}
* @example
* var cm = rte.get('fontSize');
* const action = rte.get('bold');
* // {name: 'bold', ...}
*/
get(command) {
return commands.where({command})[0];
get(name) {
let result;
globalRte.getActions().forEach(action => {
if (action.name == name) {
result = action;
}
});
return result;
},
/**
* Returns the collection of commands
* @return {Collection}
* Get all actions
* @return {Array}
*/
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
*/
udpatePosition() {
var u = 'px';
var canvas = c.em.get('Canvas');
var pos = canvas.getTargetToElementDim(toolbar.el, this.lastEl, {
const un = 'px';
const canvas = config.em.get('Canvas');
const pos = canvas.getTargetToElementDim(toolbar, lastEl, {
event: 'rteToolbarPosUpdate',
});
if (c.adjustToolbar) {
if (config.adjustToolbar) {
// Move the toolbar down when the top canvas edge is reached
if (pos.top <= pos.canvasTop) {
pos.top = pos.elementTop + pos.elementHeight;
}
}
var toolbarStyle = toolbar.el.style;
toolbarStyle.top = pos.top + u;
toolbarStyle.left = pos.left + u;
const toolbarStyle = toolbar.style;
toolbarStyle.top = pos.top + un;
toolbarStyle.left = pos.left + un;
},
/**
* Bind rich text editor to the element
* @param {View} view
* Enable rich text editor on the element
* @param {View} view Component view
* @param {Object} rte The instance of already defined RTE
* @private
* */
attach(view, rte) {
// lastEl will be used to place the RTE toolbar
this.lastEl = view.el;
var el = view.getChildrenContainer();
var customRte = this.customRte;
enable(view, rte) {
lastEl = view.el;
const em = config.em;
const el = view.getChildrenContainer();
const customRte = this.customRte;
// If a custom RTE is defined
if (customRte) {
rte = customRte.enable(el, rte);
} else {
$(el).wysiwyg({}).focus();
}
toolbar.style.display = '';
rte = customRte ? customRte.enable(el, rte) : this.initRte(el).enable();
this.show();
if(c.em) {
if (em) {
setTimeout(this.udpatePosition.bind(this), 0);
c.em.off('change:canvasOffset', this.udpatePosition, this);
c.em.on('change:canvasOffset', this.udpatePosition, this);
// Update position on scrolling
c.em.off('canvasScroll', this.udpatePosition, this);
c.em.on('canvasScroll', this.udpatePosition, this);
const event = 'change:canvasOffset canvasScroll';
em.off(event, this.udpatePosition, this);
em.on(event, this.udpatePosition, this);
em.trigger('rte:enable', view, rte);
}
//Avoid closing edit mode clicking on toolbar
toolbar.$el.on('mousedown', this.disableProp);
return rte;
},
@ -176,78 +263,22 @@ module.exports = () => {
* @param {Object} rte The instance of already defined RTE
* @private
* */
detach(view, rte) {
var customRte = this.customRte;
disable(view, rte) {
const em = config.em;
const customRte = this.customRte;
const style = toolbar.style;
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.focus)
customRte.focus(el, rte);
customRte.disable(el, rte);
} 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();
},
/**
* Return toolbar element
* @return {HTMLElement}
* @private
*/
getToolbarEl() {
return toolbar.el;
style.display = 'none';
style.top = 0;
style.left = 0;
em && em.trigger('rte:disable', view, rte);
},
/**
* 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
*/
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 id="<%= pfx %>close">&Cross;</span>`),
events: {},
initialize(o) {
this.config = o.config || {};
this.coll = o.coll || null;
@ -22,6 +20,7 @@ module.exports = Backbone.View.extend({
this.closeId = this.pfx + 'close';
this.chkId = this.pfx + 'checkbox';
this.labelId = this.pfx + 'tag-label';
this.events = {};
this.events['click #' + this.closeId ] = 'removeTag';
this.events['click #' + this.chkId ] = 'changeStatus';
this.events['dblclick #' + this.labelId ] = 'startEditTag';
@ -77,17 +76,15 @@ module.exports = Backbone.View.extend({
* @private
*/
removeTag(e) {
var comp = this.target.get('selectedComponent');
if(comp)
comp.get('classes').remove(this.model);
if(this.coll){
this.coll.remove(this.model);
this.target.trigger('targetClassRemoved');
}
this.remove();
const em = this.target;
const model = this.model;
const coll = this.coll;
const el = this.el;
const sel = em && em.get('selectedComponent');
sel && sel.get & sel.get('classes').remove(model);
coll && coll.remove(model);
setTimeout(() => this.remove(), 0);
em && em.trigger('targetClassRemoved');
},
/**
@ -115,8 +112,11 @@ module.exports = Backbone.View.extend({
* @private
*/
updateInputLabel() {
if(!this.$labelInput)
if(!this.$labelInput) {
this.$labelInput = this.$el.find('input');
}
this.$labelInput.prop(this.inputProp, true);
var size = this.$labelInput.val().length - 1;
size = size < 1 ? 1 : size;
this.$labelInput.attr('size', size);

4
src/selector_manager/view/ClassTagsView.js

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

5
src/storage_manager/config/config.js

@ -32,9 +32,12 @@ module.exports = {
checkLocal: 1,
// 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: {},
// Custom headers for the remote storage request
headers: {},
// Endpoint where to save all stuff
urlStore: '',

3
src/storage_manager/index.js

@ -57,7 +57,8 @@ module.exports = () => {
*/
init(config) {
c = config || {};
for (var name in defaults){
for (var name in defaults) {
if (!(name in c))
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: {
urlStore: '',
@ -12,67 +15,121 @@ module.exports = Backbone.Model.extend({
},
/**
* Triggered before the request is started
* @private
*/
store(data, clb) {
var fd = {},
params = this.get('params');
for(var k in data)
fd[k] = data[k];
for(var key in params)
fd[key] = params[key];
let req = $.ajax({
url: this.get('urlStore'),
beforeSend: this.get('beforeSend'),
complete: this.get('onComplete'),
method: 'POST',
dataType: 'json',
contentType: this.get('contentTypeJson') ? 'application/json; charset=utf-8': 'x-www-form-urlencoded',
data: this.get('contentTypeJson') ? JSON.stringify(fd): fd,
});
// Assign always callback when possible
req && req.always && req.always(() => {
if (typeof clb == 'function') {
clb();
}
});
onStart() {
const em = this.get('em');
const before = this.get('beforeSend');
before && before();
em && em.trigger('storage:start');
},
/**
* Triggered on request error
* @param {Object} err Error
* @private
*/
onError(err) {
const em = this.get('em');
console.error(err);
em && em.trigger('storage:error', err);
this.onEnd(err);
},
/**
* Triggered after the request is ended
* @param {Object|string} res End result
* @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) {
var result = {},
fd = {},
params = this.get('params');
for(var key in params)
fd[key] = params[key];
fd.keys = keys;
let req = $.ajax({
url: this.get('urlLoad'),
beforeSend: this.get('beforeSend'),
complete: this.get('onComplete'),
data: fd,
async: false,
method: 'GET',
}).done(d => {
result = d;
});
// Assign always callback when possible
req && req.always && req.always((res) => {
if (typeof clb == 'function') {
clb(res);
this.request(this.get('urlLoad'), {body: {keys}}, clb);
},
/**
* Execute remote request
* @param {string} url Url
* @param {Object} [opts={}] Options
* @param {[type]} [clb=null] Callback
* @private
*/
request(url, opts = {}, clb = null) {
const typeJson = this.get('contentTypeJson');
const headers = this.get('headers') || {};
const params = this.get('params');
const reqHead = 'X-Requested-With';
const typeHead = 'Content-Type';
const bodyObj = opts.body || {};
let body;
for (let param in params) {
bodyObj[param] = params[param];
}
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)
* * [getProperties](#getproperties)
* * [getModelToStyle](#getmodeltostyle)
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
* * [render](#render)
*
* With Style Manager you basically build categories (called sectors) of CSS properties which could
@ -52,7 +55,9 @@ module.exports = () => {
var c = {},
defaults = require('./config/config'),
Sectors = require('./model/Sectors'),
Properties = require('./model/Properties'),
SectorsView = require('./view/SectorsView');
let properties;
var sectors, SectView;
return {
@ -88,6 +93,7 @@ module.exports = () => {
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
properties = new Properties();
sectors = new Sectors(c.sectors);
SectView = new SectorsView({
collection: sectors,
@ -252,6 +258,44 @@ module.exports = () => {
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
* @return {HTMLElement}

25
src/style_manager/model/Layer.js

@ -1,17 +1,20 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
defaults: {
index: '',
value: '',
values: {},
active: true,
active: false,
preview: false,
properties: [],
},
initialize() {
const Properties = require('./Properties');
const properties = this.get('properties');
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
// 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');
var Layer = require('./Layer');
const Layer = require('./Layer');
module.exports = Backbone.Collection.extend({
@ -18,6 +17,102 @@ module.exports = Backbone.Collection.extend({
onReset() {
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;
}
}
},{
id: 'slider',
model: require('./PropertySlider'),
view: require('./../view/PropertySliderView'),
isType(value) {
if (value && value.type == 'slider') {
return value;
}
}
},{
id: 'integer',
model: require('./PropertyInteger'),
@ -75,5 +84,37 @@ module.exports = require('backbone').Collection.extend(TypeableCollection).exten
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 = '';
this.get('properties').each(prop => result += `${prop.getFullValue()} `);
return result.trim();
return this.get('properties').getFullValue();
},
});

2
src/style_manager/model/PropertyFactory.js

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

18
src/style_manager/model/PropertyInteger.js

@ -8,8 +8,26 @@ module.exports = Property.extend({
// Selected unit, eg. 'px'
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() {
let value = this.get('value') + this.get('unit');
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() {
Property.prototype.init.apply(this, arguments);
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() {
if (this.get('detached')) {
return '';
}
const layers = this.get('layers');
let val = layers.length ? layers.pluck('value').join(', ') : '';
return val.trim();
return this.get('detached') ? '' : this.get('layers').getFullValue();
},
});

5
src/style_manager/model/Sectors.js

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

242
src/style_manager/view/LayerView.js

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

23
src/style_manager/view/LayersView.js

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

21
src/style_manager/view/PropertiesView.js

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

34
src/style_manager/view/PropertyColorView.js

@ -1,31 +1,27 @@
var Backbone = require('backbone');
var PropertyView = require('./PropertyView');
var InputColor = require('domain_abstract/ui/InputColor');
const InputColor = require('domain_abstract/ui/InputColor');
module.exports = PropertyView.extend({
module.exports = require('./PropertyIntegerView').extend({
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
this.className += ` ${this.pfx}file`;
setValue(value, opts = {}) {
opts = Object.assign({}, opts, {silent: 1});
this.inputInst.setValue(value, opts);
},
renderInput() {
onRender() {
if (!this.input) {
var inputColor = new InputColor({
const ppfx = this.ppfx;
const inputColor = new InputColor({
target: this.target,
model: this.model,
ppfx: this.ppfx
ppfx
});
this.input = inputColor.render();
this.$el.append(this.input.$el);
this.$input = this.input.inputEl;
this.$color = this.input.colorEl;
const input = inputColor.render();
this.el.querySelector(`.${ppfx}fields`).appendChild(input.el);
this.$input = input.inputEl;
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');
var PropertyView = require('./PropertyView');
const PropertyView = require('./PropertyView');
const $ = Backbone.$;
module.exports = PropertyView.extend({
templateField() {
templateInput() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${pfx}field ${pfx}composite">
<span id="${pfx}input-holder"></span>
</div>
<div style="clear:both"></div>
`;
},
initialize(o) {
PropertyView.prototype.initialize.apply(this, arguments);
this.config = o.config || {};
this.className = this.className + ' '+ this.pfx +'composite';
},
/**
* Fired when the input value is updated
*/
valueUpdated(...args) {
inputValueChanged(...args) {
if(!this.model.get('detached'))
PropertyView.prototype.valueUpdated.apply(this, args);
PropertyView.prototype.inputValueChanged.apply(this, args);
},
/**
* Renders input
* */
renderInput() {
onRender() {
var model = this.model;
var props = model.get('properties') || [];
var self = this;
if (props.length) {
if(!this.$input)
this.$input = $('<input>', {value: 0, type: 'hidden' });
if (!this.$input) {
this.$input = $('<input type="hidden" value="0">');
this.input = this.$input.get(0);
}
if (!this.props) {
this.props = model.get('properties');
@ -57,7 +48,7 @@ module.exports = PropertyView.extend({
var PropertiesView = require('./PropertiesView');
var propsView = new PropertiesView(this.getPropsConfig());
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
if (targetValue) {
const values = targetValue.split(' ');
value = view ? view.model.parseValue(values[index]) : values[index];
value = values[index];
} else {
value = view.getTargetValue({ignoreCustomValue: 1});
value = view && view.getTargetValue({ignoreCustomValue: 1, ignoreDefault: 1});
}
if (view) {
value = view.model.parseValue(value);
}
return value;

25
src/style_manager/view/PropertyFileView.js

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

45
src/style_manager/view/PropertyIntegerView.js

@ -1,30 +1,39 @@
var PropertyView = require('./PropertyView');
var InputNumber = require('domain_abstract/ui/InputNumber');
const InputNumber = require('domain_abstract/ui/InputNumber');
const $ = Backbone.$;
module.exports = PropertyView.extend({
module.exports = require('./PropertyView').extend({
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
this.listenTo(this.model, 'change:unit', this.valueChanged);
this.listenTo(this.model, 'el:change', this.elementUpdated);
templateInput() {
return '';
},
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) {
var inputNumber = new InputNumber({
const inputNumber = new InputNumber({
model: this.model,
ppfx: this.ppfx
});
this.input = inputNumber.render();
this.$el.append(this.input.$el);
this.$input = this.input.inputEl;
this.$unit = this.input.unitEl;
const input = inputNumber.render();
const fields = this.el.querySelector(`.${ppfx}fields`);
fields.appendChild(input.el);
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');
var PropertyView = require('./PropertyView');
module.exports = require('./PropertyView').extend({
module.exports = PropertyView.extend({
templateField() {
templateInput() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${ppfx}field ${ppfx}field-radio">
<span id="${pfx}input-holder"></span>
</div>
<div style="clear:both"></div>
`;
},
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
onRender() {
const pfx = this.pfx;
const ppfx = this.ppfx;
const itemCls = `${ppfx}radio-item-label`;
const model = this.model;
this.list = model.get('list') || model.get('options') || [];
this.className = this.className + ' '+ this.pfx +'list';
},
const prop = model.get('property');
const options = model.get('list') || model.get('options') || [];
/** @inheritdoc */
renderInput() {
var pfx = this.pfx;
var ppfx = this.ppfx;
var itemCls = ppfx + 'radio-item-label';
var prop = this.property;
if (!this.input) {
if(options && options.length) {
let inputStr = '';
if(!this.$input) {
if(this.list && this.list.length) {
var input = '';
_.each(this.list, el => {
var cl = el.className ? el.className + ' ' + pfx + 'icon ' + itemCls : '',
id = prop + '-' + el.value,
labelTxt = el.name ? el.name : el.value;
var titleAttr = el.title ? 'title="' + el.title + '"': '';
input += '<div class="' + ppfx + 'radio-item">'+
'<input class="'+pfx+'radio" type="radio" id="'+ id +'" name="'+prop+'" value="'+el.value+'" />'+
'<label class="'+(cl ? cl : itemCls)+'" ' + titleAttr + ' for="'+ id +'">' + (cl ? '' : labelTxt) + '</label></div>';
options.forEach(el => {
let cl = el.className ? `${el.className} ${pfx}icon ${itemCls}` : '';
let id = `${prop}-${el.value}`;
let labelTxt = el.name || el.value;
let titleAttr = el.title ? `title="${el.title}"` : '';
inputStr += `
<div class="${ppfx}radio-item">
<input type="radio" class="${pfx}radio" id="${id}" name="${prop}" value="${el.value}"/>
<label class="${cl || itemCls}" ${titleAttr} for="${id}">${cl ? '' : labelTxt}</label>
</div>
`;
});
this.input = input;
this.$inputEl = $(this.input);
this.$el.find('#'+ pfx +'input-holder').html(this.$inputEl);
this.$input = this.$inputEl.find('input[name="'+this.property+'"]');
const inputHld = this.el.querySelector(`#${pfx}input-holder`);
inputHld.innerHTML = `<div>${inputStr}</div>`;
this.input = inputHld.firstChild;
}
}
this.setValue(this.componentValue);
},
/**
* Returns value from input
* @return {string}
*/
getInputValue() {
return this.$input ? this.$el.find('input:checked').val() : '';
const inputChk = this.getCheckedEl();
return inputChk ? inputChk.value : '';
},
/** @inheritdoc */
setValue(value) {
var v = this.model.get('value') || this.model.getDefaultValue();
if(value)
v = value;
getCheckedEl() {
const input = this.getInputEl();
return input ? input.querySelector('input:checked') : '';
},
if(this.$input)
this.$input.filter('[value="'+v+'"]').prop('checked', true);
setValue(value) {
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');
var PropertyView = require('./PropertyView');
const $ = Backbone.$;
module.exports = PropertyView.extend({
module.exports = require('./PropertyView').extend({
templateField() {
templateInput() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
@ -13,37 +12,29 @@ module.exports = PropertyView.extend({
<div class="${ppfx}d-s-arrow"></div>
</div>
</div>
<div style="clear:both"></div>
`;
},
initialize(options) {
PropertyView.prototype.initialize.apply(this, arguments);
onRender() {
var pfx = this.pfx;
const model = this.model;
this.list = model.get('list') || model.get('options') || [];
},
const options = model.get('list') || model.get('options') || [];
/** @inheritdoc */
renderInput() {
var pfx = this.pfx;
if(!this.$input){
var input = '<select>';
if (!this.input) {
let optionsStr = '';
if (this.list && this.list.length) {
_.each(this.list, el => {
var name = el.name ? el.name : el.value;
var style = el.style ? el.style.replace(/"/g,'&quot;') : '';
var styleAttr = style ? 'style="' + style + '"' : '';
input += '<option value="'+el.value.replace(/"/g,'&quot;')+'" ' + styleAttr + '>'+name+'</option>';
});
}
options.forEach(option => {
let name = option.name || option.value;
let style = option.style ? option.style.replace(/"/g,'&quot;') : '';
let styleAttr = style ? `style="${style}"` : '';
let value = option.value.replace(/"/g,'&quot;');
optionsStr += `<option value="${value}" ${styleAttr}>${name}</option>`;
});
input += '</select>';
this.input = input;
this.$input = $(this.input);
this.$el.find('#'+ pfx +'input-holder').html(this.$input);
const inputH = this.el.querySelector(`#${pfx}input-holder`);
inputH.innerHTML = `<select>${optionsStr}</select>`;
this.input = inputH.firstChild;
}
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');
var PropertyCompositeView = require('./PropertyCompositeView');
var Layers = require('./../model/Layers');
var LayersView = require('./LayersView');
const PropertyCompositeView = require('./PropertyCompositeView');
const LayersView = require('./LayersView');
module.exports = PropertyCompositeView.extend({
templateField() {
templateInput() {
const pfx = this.pfx;
const ppfx = this.ppfx;
return `
<div class="${pfx}field ${pfx}stack">
<button type="button" id="${pfx}add">+</button>
<span id="${pfx}input-holder"></span>
<button type="button" id="${pfx}add" data-add-layer>+</button>
<div data-layers-wrapper></div>
</div>
<div style="clear:both"></div>
`;
},
initialize(o) {
PropertyCompositeView.prototype.initialize.apply(this, arguments);
init() {
const model = this.model;
const pfx = this.pfx;
model.set('stackIndex', null);
this.className = `${pfx}property ${pfx}stack`;
this.events[`click #${pfx}add`] = 'addLayer';
this.events[`click [data-add-layer]`] = 'addLayer';
this.listenTo(model, 'change:stackIndex', this.indexChanged);
this.listenTo(model, 'updateValue', this.valueUpdated);
this.listenTo(model, 'updateValue', this.inputValueChanged);
this.delegateEvents();
},
/**
* Fired when the target is updated.
* 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) {
if (!this.model.get('detached')) {
@ -61,270 +56,120 @@ module.exports = PropertyCompositeView.extend({
* @return {Object}
* */
indexChanged(e) {
var model = this.model;
var layer = this.getLayers().at(model.get('stackIndex'));
layer.set('props', this.$props);
model.get('properties').each(prop => prop.trigger('targetUpdated'));
},
/**
* Get array of values from layers
* @return Array
* */
getStackValues() {
return this.getLayers().pluck('value');
const model = this.model;
this.getLayers().active(model.get('stackIndex'));
},
/** @inheritDoc */
getPropsConfig(opts) {
addLayer() {
const model = this.model;
const detached = model.get('detached');
var result = PropertyCompositeView.prototype.getPropsConfig.apply(this, arguments);
result.onChange = (el, view, opt) => {
const subModel = view.model;
const subProperty = subModel.get('property');
this.build();
if (detached) {
var propVal = '';
var index = subModel.collection.indexOf(subModel);
const layers = this.getLayers();
const properties = model.get('properties').deepClone();
properties.each(property => property.set('value', ''));
const layer = layers.add({properties});
this.getLayers().each(layer => {
var val = layer.get('values')[subProperty];
if (val) {
propVal += (propVal ? ',' : '') + val;
}
});
// In detached mode inputValueChanged will add new 'layer value'
// to all subprops
this.inputValueChanged();
view.updateTargetStyle(propVal, null, opt);
} else {
model.set('value', model.getFullValue(), opt);
}
};
return result;
// This will set subprops with a new default values
model.set('stackIndex', layers.indexOf(layer));
},
/**
* Extract string from the composite value of the target
* @param {integer} index Property index
* @param {View} propView Property view
* @return string
* @private
* */
valueOnIndex(index, propView) {
let result;
inputValueChanged() {
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 (model.get('detached')) {
var targetValue = propView.getTargetValue({ignoreCustomValue: 1});
var valist = (targetValue + '').split(',');
result = valist[layerIndex];
result = result ? result.trim() : propView.getDefaultValue();
result = propView.model.parseValue(result);
// If not detached I'll just put all the values from layers to property
// eg. background: layer1Value, layer2Value, layer3Value, ...
if (!model.get('detached')) {
model.set('value', this.getLayerValues());
} else {
var aStack = this.getStackValues();
var strVar = aStack[layerIndex];
if(!strVar)
return;
var a = strVar.split(' ');
if(a.length && a[index]){
result = a[index];
}
model.get('properties').each(prop => prop.trigger('change:value'))
}
return result;
},
/**
* Build composite value
* There is no need to handle input update by the property itself,
* this will be done by layers
* @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() {
var model = this.model;
if (!model.get('detached')) {
model.set('value', this.createValue());
} else {
model.get('properties').each(prop => {
prop.trigger('change:value');
});
}
},
setValue() {},
/**
* Create value by layers
* @return string
* */
createValue() {
return this.getStackValues().join(', ');
},
/**
* 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;
getLayerValues() {
return this.getLayers().getFullValue();
},
/**
* Refresh layers
* */
refreshLayers() {
var n = [];
var a = [];
var fieldName = 'value';
var detached = this.model.get('detached');
let layersObj = [];
const model = this.model;
const layers = this.getLayers();
const detached = model.get('detached');
// With detached layers values will be assigned to their properties
if (detached) {
fieldName = 'values';
a = this.getLayersFromTarget();
const target = this.getTarget();
const style = target ? target.getStyle() : {};
layersObj = layers.getLayersFromStyle(style);
} else {
var v = this.getTargetValue();
var vDef = this.getDefaultValue();
v = v == vDef ? '' : v;
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(', ');
}
let value = this.getTargetValue();
value = value == model.getDefaultValue() ? '' : value;
layersObj = layers.getLayersFromValue(value);
}
_.each(a, e => {
var o = {};
o[fieldName] = e;
n.push(o);
},this);
this.$props.detach();
var layers = this.getLayers();
layers.reset();
layers.add(n);
// Avoid updating with detached as it will cause issues on next change
if (!detached) {
this.valueUpdated();
}
this.model.set({stackIndex: null}, {silent: true});
layers.add(layersObj);
model.set({stackIndex: null}, {silent: true});
},
render() {
const el = this.el;
el.innerHTML = this.template(this.model);
this.renderInput();
this.renderLayers();
this.$el.attr('class', this.className);
this.updateStatus();
return this;
onRender() {
const self = this;
const model = this.model;
const fieldEl = this.el.querySelector('[data-layers-wrapper]');
const PropertiesView = require('./PropertiesView');
const propsConfig = {
propTarget: this.propTarget,
// 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({
template(model) {
const pfx = this.pfx;
const name = model.get('name');
const icon = model.get('icon');
const info = model.get('info');
return `
<div class="${pfx}label">
<span class="${pfx}icon ${icon}" title="${info}">
${name}
</span>
<b class="${pfx}clear">&Cross;</b>
${this.templateLabel(model)}
</div>
<div class="${this.ppfx}fields">
${this.templateInput(model)}
</div>
${this.templateField()}
`;
},
templateField() {
templateLabel(model) {
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 `
<div class="${ppfx}field">
<span id="${pfx}input-holder"></span>
<div class="${this.ppfx}field">
<input placeholder="${model.getDefaultValue()}"/>
</div>
<div style="clear:both"></div>
`;
},
events: {
'change': 'valueUpdated'
'change': 'inputValueChanged'
},
initialize(o) {
initialize(o = {}) {
this.config = o.config || {};
this.em = this.config.em;
this.pfx = this.config.stylePrefix || '';
@ -45,9 +48,8 @@ module.exports = Backbone.View.extend({
this.customValue = o.customValue || {};
const model = this.model;
this.property = model.get('property');
this.input = this.$input = null;
this.input = null;
const pfx = this.pfx;
this.className = pfx + 'property';
this.inputHolderId = '#' + pfx + 'input-holder';
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(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, 'change:visible', this.updateVisibility);
this.listenTo(model, 'change:status', this.updateStatus);
this.events[`click .${pfx}clear`] = 'clear';
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() {
const status = this.model.get('status');
const pfx = this.pfx;
@ -72,7 +82,7 @@ module.exports = Backbone.View.extend({
const config = this.config;
const updatedCls = `${ppfx}color-hl`;
const computedCls = `${ppfx}color-warn`;
const labelEl = this.$el.find(`> .${pfx}label`);
const labelEl = this.$el.children(`.${pfx}label`);
const clearStyle = this.getClearEl().style;
labelEl.removeClass(`${updatedCls} ${computedCls}`);
clearStyle.display = 'none';
@ -92,7 +102,7 @@ module.exports = Backbone.View.extend({
},
/**
* Clear the property
* Clear the property from the target
*/
clear() {
const target = this.getTargetModel();
@ -110,11 +120,10 @@ module.exports = Backbone.View.extend({
/**
* Returns selected target which should have 'style' property
* @deprecated
* @return {Model|null}
*/
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.elementUpdated();
},
@ -156,14 +166,14 @@ module.exports = Backbone.View.extend({
return;
}
const config = this.config;
const em = config.em;
const model = this.model;
let value = '';
let status = '';
let targetValue = this.getTargetValue({ignoreDefault: 1});
let defaultValue = this.getDefaultValue();
let defaultValue = model.getDefaultValue();
let computedValue = this.getComputedValue();
const config = this.config;
const em = config.em;
const model = this.model;
if (targetValue) {
value = targetValue;
@ -184,7 +194,7 @@ module.exports = Backbone.View.extend({
}
model.set('value', value, {silent: 1});
this.setValue(value, 1);
this.setValue(value, {targetUpdate: 1});
model.set('status', status);
if (em) {
@ -239,7 +249,7 @@ module.exports = Backbone.View.extend({
result = model.parseValue(result);
if (!result && !opts.ignoreDefault) {
result = this.getDefaultValue();
result = model.getDefaultValue();
}
if (typeof customFetchValue == 'function' && !opts.ignoreCustomValue) {
@ -254,15 +264,6 @@ module.exports = Backbone.View.extend({
return result;
},
/**
* Returns default value
* @return {String}
* @private
*/
getDefaultValue() {
return this.model.getDefaultValue();
},
/**
* Returns computed value
* @return {String}
@ -280,23 +281,24 @@ module.exports = Backbone.View.extend({
* @return {string}
*/
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
* the target model
* @param {Object} e Events
* @param {Mixed} val Value
* @param {Object} opt Options
* Triggers when the `value` of the model changes, so the target and
* the input element should be updated
* @param {Object} e Event
* @param {Mixed} val Value
* @param {Object} opt Options
* */
valueChanged(e, val, opt) {
modelValueChanged(e, val, opt) {
const em = this.config.em;
const model = this.model;
const value = model.getFullValue();
const target = this.getTarget();
const onChange = this.onChange;
this.setValue(value);
this.setRawValue(value);
// Check if component is allowed to be styled
if (!target || !this.isTargetStylable() || !this.isComponentStylable()) {
@ -377,24 +379,37 @@ module.exports = Backbone.View.extend({
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
* @param {String} value
* @param {Boolean} force
* @private
* */
setValue(value, force) {
setValue(value, opts = {}) {
const model = this.model;
const f = force === 0 ? 0 : 1;
const def = model.getDefaultValue();
let v = model.get('value') || def;
let val = value || model.get('value') || model.getDefaultValue();
const input = this.getInputEl();
input && (input.value = val);
},
if (value || f) {
v = value;
getInputEl() {
if (!this.input) {
this.input = this.el.querySelector('input');
}
const input = this.$input;
input && input.val(v);
return this.input;
},
updateVisibility() {
@ -410,20 +425,6 @@ module.exports = Backbone.View.extend({
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
* */
@ -432,12 +433,16 @@ module.exports = Backbone.View.extend({
},
render() {
const pfx = this.pfx;
const model = this.model;
const el = this.el;
el.innerHTML = this.template(this.model);
this.renderInput();
el.className = this.className;
el.innerHTML = this.template(model);
el.className = `${pfx}property ${pfx}${model.get('type')}`;
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({
template: _.template(`
<div class="<%= pfx %>title">
<div class="<%= pfx %>title" data-sector-title>
<i id="<%= pfx %>caret" class="fa"></i>
<%= label %>
</div>`),
events:{},
events:{
'click [data-sector-title]': 'toggle'
},
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.target = o.target || {};
this.propTarget = o.propTarget || {};
this.open = this.model.get('open');
this.caretR = 'fa-caret-right';
this.caretD = 'fa-caret-down';
this.listenTo(this.model, 'change:open', this.updateOpen);
this.listenTo(this.model, 'updateVisibility', this.updateVisibility);
this.events['click .' + this.pfx + 'title'] = 'toggle';
this.delegateEvents();
const model = this.model;
this.listenTo(model, 'change:open', this.updateOpen);
this.listenTo(model, 'updateVisibility', this.updateVisibility);
},
/**
@ -53,7 +53,7 @@ module.exports = Backbone.View.extend({
* */
show() {
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);
},
@ -62,14 +62,18 @@ module.exports = Backbone.View.extend({
* */
hide() {
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);
},
getPropertiesEl() {
return this.$el.find(`.${this.pfx}properties`).get(0);
},
/**
* Toggle visibility
* */
toggle() {
toggle(e) {
var v = this.model.get('open') ? 0 : 1;
this.model.set('open', v);
},

7
src/style_manager/view/SectorsView.js

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

100
src/styles/scss/_gjs_inputs.scss

@ -11,13 +11,55 @@
@mixin rangeTrackStyle() {
background-color: $mainDkColor;
border: 1px solid $darkBorder;
border-radius: 1px;
height: 2px;
margin-top: 3px;
height: 3px;
}
.#{$app-prefix}label {
line-height: 18px;
.#{$app-prefix} {
&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 {
@ -43,6 +85,10 @@
position: relative;
padding: $inputPadding;
z-index: 1;
&:focus {
outline: none;
}
}
textarea {
@ -238,33 +284,39 @@
input {
margin: 0;
}
height: 100%;
input::-moz-range-thumb {
@include rangeThumbStyle();
}
&:focus {
outline: none;
}
input::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -5px;
&::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -4px;
@include rangeThumbStyle();
}
@include rangeThumbStyle();
}
input::-ms-thumb {
@include rangeThumbStyle();
}
&::-moz-range-thumb {
@include rangeThumbStyle();
}
input::-moz-range-track {
@include rangeTrackStyle();
}
&::-ms-thumb {
@include rangeThumbStyle();
}
input::-webkit-slider-runnable-track {
@include rangeTrackStyle();
}
/* -moz-range-progress */
&::-moz-range-track {
@include rangeTrackStyle();
}
&::-webkit-slider-runnable-track {
@include rangeTrackStyle();
}
input::-ms-track {
@include rangeTrackStyle();
&::-ms-track {
@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;
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 {
@ -22,12 +42,6 @@
}
}
.#{$sm-prefix}header {
font-size: 11px;
font-weight: lighter;
padding: 10px;
}
.#{$sm-prefix}sector {
clear: both;
font-weight: lighter;
@ -46,10 +60,6 @@
@extend .#{$app-prefix}category-title;
}
.#{$sm-prefix}label {
margin: 5px 5px 2px 0;
}
/* ------------------Field-------------------- */
.#{$sm-prefix}field {
@ -229,13 +239,16 @@
}
}
.#{$sm-prefix}properties {
font-size: 11px;
padding: 10px 5px;
}
/* ------------------Property-------------------- */
.#{$sm-prefix} {
&slider {
.#{$app-prefix}field-integer {
flex: 1 1 65px;
}
}
}
.#{$sm-prefix}property {
box-sizing: border-box;
float: left;
@ -246,7 +259,9 @@
&.#{$sm-prefix}composite,
&.#{$sm-prefix}file,
&.#{$sm-prefix}list,
&.#{$sm-prefix}radio,
&.#{$sm-prefix}stack,
&.#{$sm-prefix}slider,
&.#{$sm-prefix}color {
width: 100%;
}
@ -273,7 +288,6 @@
box-sizing: border-box;
float: left;
width: 100%;
padding: 0 5px;
}
&.#{$sm-prefix}file ##{$sm-prefix}preview-box {
@ -285,12 +299,13 @@
&.#{$sm-prefix}show {
border: 1px solid darken($lightBorder, 1%);
padding: 3px 5px;
padding: 3px 20px;
}
##{$sm-prefix}close {
@extend .#{$sm-prefix}close-btn;
top: -5px;
display: block;
}
}
@ -377,7 +392,7 @@
/* ------------------END Property-------------------- */
.#{$sm-prefix}stack .#{$sm-prefix}properties {
padding-top: 5px;
padding: 5px 0 0;
}
.#{$sm-prefix}stack ##{$sm-prefix}add {
@ -386,11 +401,12 @@
background: none;
border: none;
cursor: pointer;
outline: none;
font-size: 22px;
line-height: 10px;
position: absolute;
right: 0;
top: -20px;
top: -17px;
opacity: 0.75;
&:hover {

102
src/styles/scss/_gjs_variables.scss

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

91
src/styles/scss/main.scss

@ -206,36 +206,6 @@
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 ****************/
.#{$app-prefix}traits-label {
border-bottom: 1px solid $mainDkColor;
@ -266,41 +236,10 @@ div.#{$app-prefix}select {
@import "gjs_canvas";
/************* RTE ****************/
#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**********/
/********* COMMANDS **********/
.no-dots, .ui-resizable-handle{ border: none !important; margin:0 !important; outline: none !important; }
/********* COMMANDS **********/
.#{$com-prefix}dashed *{
outline: 1px dashed #888;
outline-offset: -2px;
@ -799,33 +738,7 @@ ol.example li.placeholder:before {position: absolute;}
/*************RTE****************/
##{$rte-prefix}toolbar {
@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;
}
}
@import 'gjs_rte';
/********* Spectrum **********/

4
src/trait_manager/view/TraitSelectView.js

@ -1,5 +1,5 @@
var Backbone = require('backbone');
var TraitView = require('./TraitView');
const TraitView = require('./TraitView');
const $ = Backbone.$;
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({
@ -81,21 +81,28 @@ module.exports = Backbone.View.extend({
var md = this.model;
var trg = this.target;
var name = md.get('name');
var opts = {
placeholder: md.get('placeholder') || md.get('default'),
type: md.get('type') || 'text'
};
if(md.get('changeProp')){
opts.value = trg.get(name);
}else{
var attrs = trg.get('attributes');
opts.value = md.get('value') || attrs[name];
const plh = md.get('placeholder') || md.get('default') || '';
const type = md.get('type') || 'text';
const attrs = trg.get('attributes');
const min = md.get('min');
const max = md.get('max');
const value = md.get('changeProp') ?
trg.get(name) : md.get('value') || attrs[name];
const input = $(`<input type="${type}" placeholder="${plh}">`);
if (value) {
input.prop('value', value);
}
if (min) {
input.prop('min', min);
}
if(md.get('min'))
opts.min = md.get('min');
if(md.get('max'))
opts.max = md.get('max');
this.$input = $('<input>', opts);
if (max) {
input.prop('max', max);
}
this.$input = input;
}
return this.$input.get(0);
},
@ -123,8 +130,10 @@ module.exports = Backbone.View.extend({
renderField() {
if(!this.$input){
this.$el.append(this.tmpl);
var el = this.getInputEl();
this.$el.find('.' + this.inputhClass).prepend(el);
const el = this.getInputEl();
// 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 w = win || window;
var rect = el.getBoundingClientRect();

2
src/utils/Resizer.js

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

2
src/utils/Sorter.js

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

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

Loading…
Cancel
Save