Browse Source

Merge branch 'webpack' into dev

pull/2/merge
Artur Arseniev 9 years ago
parent
commit
26b2e3e9e7
  1. 252
      Gruntfile.js
  2. 4
      LICENSE
  3. 65
      README.md
  4. 53
      bower.json
  5. 8
      dist/css/grapes.min.css
  6. 28
      dist/grapes.min.js
  7. 395
      index.html
  8. 49
      package.json
  9. 26
      src/asset_manager/config/config.js
  10. 239
      src/asset_manager/index.js
  11. 241
      src/asset_manager/main.js
  12. 49
      src/asset_manager/model/Asset.js
  13. 20
      src/asset_manager/model/AssetImage.js
  14. 133
      src/asset_manager/model/Assets.js
  15. 10
      src/asset_manager/template/assetImage.html
  16. 17
      src/asset_manager/template/assets.html
  17. 5
      src/asset_manager/template/fileUploader.html
  18. 181
      src/asset_manager/view/AssetImageView.js
  19. 22
      src/asset_manager/view/AssetView.js
  20. 282
      src/asset_manager/view/AssetsView.js
  21. 184
      src/asset_manager/view/FileUploader.js
  22. 8
      src/block_manager/config/config.js
  23. 118
      src/block_manager/index.js
  24. 122
      src/block_manager/main.js
  25. 18
      src/block_manager/model/Block.js
  26. 11
      src/block_manager/model/Blocks.js
  27. 82
      src/block_manager/view/BlockView.js
  28. 237
      src/block_manager/view/BlocksView.js
  29. 22
      src/canvas/config/config.js
  30. 325
      src/canvas/index.js
  31. 329
      src/canvas/main.js
  32. 27
      src/canvas/model/Canvas.js
  33. 20
      src/canvas/model/Frame.js
  34. 479
      src/canvas/view/CanvasView.js
  35. 102
      src/canvas/view/FrameView.js
  36. 12
      src/code_manager/config/config.js
  37. 221
      src/code_manager/index.js
  38. 227
      src/code_manager/main.js
  39. 85
      src/code_manager/model/CodeMirrorEditor.js
  40. 280
      src/code_manager/model/CssGenerator.js
  41. 32
      src/code_manager/model/HtmlGenerator.js
  42. 103
      src/code_manager/model/JsGenerator.js
  43. 64
      src/code_manager/model/JsonGenerator.js
  44. 4
      src/code_manager/template/editor.html
  45. 48
      src/code_manager/view/EditorView.js
  46. 32
      src/commands/config/config.js
  47. 236
      src/commands/index.js
  48. 238
      src/commands/main.js
  49. 20
      src/commands/model/Command.js
  50. 17
      src/commands/model/Commands.js
  51. 227
      src/commands/view/CommandAbstract.js
  52. 461
      src/commands/view/CreateComponent.js
  53. 143
      src/commands/view/DeleteComponent.js
  54. 128
      src/commands/view/ExportTemplate.js
  55. 157
      src/commands/view/Fullscreen.js
  56. 66
      src/commands/view/ImageComponent.js
  57. 140
      src/commands/view/InsertCustom.js
  58. 296
      src/commands/view/MoveComponent.js
  59. 35
      src/commands/view/OpenAssets.js
  60. 48
      src/commands/view/OpenBlocks.js
  61. 60
      src/commands/view/OpenLayers.js
  62. 140
      src/commands/view/OpenStyleManager.js
  63. 56
      src/commands/view/OpenTraitManager.js
  64. 121
      src/commands/view/Preview.js
  65. 52
      src/commands/view/Resize.js
  66. 997
      src/commands/view/SelectComponent.js
  67. 187
      src/commands/view/SelectPosition.js
  68. 296
      src/commands/view/ShowOffset.js
  69. 18
      src/commands/view/SwitchVisibility.js
  70. 60
      src/commands/view/TextComponent.js
  71. 59
      src/config/require-config.js
  72. 18
      src/css_composer/config/config.js
  73. 272
      src/css_composer/index.js
  74. 274
      src/css_composer/main.js
  75. 180
      src/css_composer/model/CssRule.js
  76. 48
      src/css_composer/model/CssRules.js
  77. 31
      src/css_composer/model/Selectors.js
  78. 135
      src/css_composer/view/CssRuleView.js
  79. 120
      src/css_composer/view/CssRulesView.js
  80. 369
      src/demo.js
  81. 10
      src/device_manager/config/config.js
  82. 111
      src/device_manager/index.js
  83. 115
      src/device_manager/main.js
  84. 18
      src/device_manager/model/Device.js
  85. 11
      src/device_manager/model/Devices.js
  86. 10
      src/device_manager/template/devices.html
  87. 152
      src/device_manager/view/DevicesView.js
  88. 48
      src/dom_components/config/config.js
  89. 395
      src/dom_components/index.js
  90. 398
      src/dom_components/main.js
  91. 717
      src/dom_components/model/Component.js
  92. 180
      src/dom_components/model/ComponentImage.js
  93. 68
      src/dom_components/model/ComponentLink.js
  94. 193
      src/dom_components/model/ComponentMap.js
  95. 40
      src/dom_components/model/ComponentScript.js
  96. 156
      src/dom_components/model/ComponentTable.js
  97. 56
      src/dom_components/model/ComponentTableCell.js
  98. 74
      src/dom_components/model/ComponentTableRow.js
  99. 16
      src/dom_components/model/ComponentText.js
  100. 42
      src/dom_components/model/ComponentTextNode.js

252
Gruntfile.js

@ -1,252 +0,0 @@
module.exports = function(grunt) {
var appPath = 'src',
buildPath = 'dist',
stylePath = 'styles',
configPath = 'config/require-config.js',
port = grunt.option('port') || 8000;
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-bowercopy');
grunt.loadNpmTasks('grunt-mocha');
grunt.loadNpmTasks('grunt-sass');
grunt.initConfig({
appDir: appPath,
builtDir: buildPath,
styleDir: stylePath,
pkg: grunt.file.readJSON("package.json"),
requirejs:{
compile:{
options: {
mainConfigFile: '<%= appDir %>/' + configPath,
appDir: '<%= appDir %>',
dir: '<%= builtDir %>',
baseUrl: './',
name: 'main',
include: ["./../node_modules/almond/almond"],
removeCombined: true,
findNestedDependencies: true,
keepBuildDir: true,
inlineText: true,
optimize: 'none',
wrap: {
start: "(function (root, factory) {"+
"if (typeof define === 'function' && define.amd)"+
"define([], factory);"+
"else if(typeof exports === 'object' && typeof module === 'object')"+
"module.exports = factory();"+
"else "+
"root.<%= pkg.name %> = root.GrapesJS = factory();"+
"}(this, function () {",
end: "return require('grapesjs/main'); }));"
},
paths: {
"jquery": "wrappers/jquery",
}
}
}
},
jshint: {
all: [
'Gruntfile.js',
'<%= appDir %>/**/*.js',
]
},
uglify: {
options: {
banner: '/*! <%= pkg.name %> - v<%= pkg.version %> */\n'
},
build:{
files: {
'<%= builtDir %>/grapes.min.js': ['<%= builtDir %>/main.js']
}
}
},
sass: {
dist: {
files: [{
expand: true,
cwd: '<%= styleDir %>/scss',
src: ['**/*.scss'],
dest: '<%= styleDir %>/css',
ext: '.css'
}]
}
},
cssmin: {
target: {
files: [{
expand: true,
flatten: true,
src: [
'<%= styleDir %>/css/main.css',
'node_modules/codemirror/lib/codemirror.css',
'node_modules/codemirror/theme/hopscotch.css'
],
dest: '<%= builtDir %>',
ext: '.min.css'
}]
}
},
concat: {
css: {
src: ['<%= builtDir %>/*.min.css'],
dest: '<%= builtDir %>/css/grapes.min.css'
}
},
mocha: {
test: {
src: ['test/index.html'],
options: { log: true, },
},
},
connect: {
server: {
options: {
port: port,
open: true
}
},
},
/*
bowercopy: {
options: {
srcPrefix: 'bower_components'
},
scripts: {
options: {
destPrefix: 'vendor'
},
files: {
'almond/almond.js' : 'almond/almond.js',
'jquery/jquery.js' : 'jquery/dist/jquery.min.js',
'underscore/underscore.js' : 'underscore/underscore-min.js',
'backbone/backbone.js' : 'backbone/backbone-min.js',
'backbone-undo/backbone-undo.js' : 'Backbone.Undo/Backbone.Undo.js',
'keymaster/keymaster.js' : 'keymaster/keymaster.js',
'require/require.js' : 'requirejs/require.js',
'require-text/text.js' : 'requirejs-text/text.js',
'spectrum/spectrum.js' : 'spectrum/spectrum.js',
'codemirror' : 'codemirror',
'codemirror-formatting' : 'codemirror-formatting/formatting.js',
'mocha' : 'mocha',
'chai' : 'chai/chai.js',
'sinon' : 'sinonjs/sinon.js',
},
}
},
*/
watch: {
script: {
files: [ '<%= appDir %>/**/*.js' ],
tasks: ['jshint']
},
css: {
files: '**/*.scss',
tasks: ['sass']
},
test: {
files: ['test/specs/**/*.js'],
tasks: ['mocha'],
//options: { livereload: true }, //default port 35729
}
},
clean: {
all: ["<%= builtDir %>/*", "!<%= builtDir %>/grapes.min.js", "!<%= builtDir %>/css"]
},
copy: {
fonts: {
cwd: '<%= styleDir %>/fonts',
src: '**/*',
dest: '<%= builtDir %>/fonts',
expand: true
}
}
});
/**
* Have to copy require configs cause r.js will try to load them from the path indicated inside
* main.js file. This is the only way I have found to do it
* */
grunt.registerTask('before-rjs', function() {
if(grunt.file.exists(buildPath))
grunt.file.delete(buildPath);
grunt.file.mkdir(buildPath);
grunt.file.copy(appPath + '/' + configPath, buildPath + '/' + appPath + '/' + configPath);
});
grunt.registerTask('webfont-custom', function() {
var dir = './styles/fonts/';
var destName = 'main-fonts';
var donePromise = this.async();
var svg2ttf = {
cmd: 'svg2ttf',
args: [dir + destName + '.svg', dir + destName + '.ttf'],
};
var ttf2woff = {
cmd: 'ttf2woff',
args: [dir + destName + '.ttf', dir + destName + '.woff'],
};
var ttf2woff2 = {
cmd: 'cat',
//args: [dir + destName + '.ttf', dir + destName + '.woff2'],
args: [dir + destName + '.ttf', '|', 'ttf2woff2', '>>', dir + destName + '.woff2'],
};
var ttf2eot = {
cmd: 'ttf2eot',
args: [dir + destName + '.ttf', dir + destName + '.eot'],
};
grunt.util.spawn(svg2ttf, function done(error, result, code) {
grunt.log.ok('.ttf file created');
grunt.util.spawn(ttf2woff, function done(error, result, code) {
grunt.log.ok('.woff file created');
grunt.util.spawn(ttf2eot, function done(error, result, code) {
grunt.log.ok('.eot file created');
donePromise();
/*
grunt.util.spawn(ttf2woff2, function done(error, result, code) {
grunt.log.ok('.woff2 file created');
donePromise();
});
*/
});
});
});
});
grunt.registerTask('dev', ['connect', 'watch']);
grunt.registerTask('test', ['jshint', 'mocha']);
grunt.registerTask('build:fonts', ['webfont-custom']);
grunt.registerTask('build', ['jshint', 'sass', 'before-rjs', 'requirejs', 'uglify', 'cssmin', 'concat', 'clean', 'copy']);
grunt.registerTask('default', ['dev']);
};

4
LICENSE

@ -1,4 +1,4 @@
Copyright (c) 2016, Artur Arseniev
Copyright (c) 2017, Artur Arseniev
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@ -22,4 +22,4 @@ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

65
README.md

@ -19,6 +19,9 @@ Webpage Demo - http://grapesjs.com/demo.html
Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
## Features
@ -42,45 +45,46 @@ Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
* Default built-in commands (basically for creating and managing different components)
## Installation
You can get GrapesJS with `npm install grapesjs` or via `git clone https://github.com/artf/grapesjs.git`.
* `npm i grapesjs` / `yarn add grapesjs`
* `git clone https://github.com/artf/grapesjs.git`
For development purpose you should follow instructions below.
## Development
GrapesJS uses [RequireJS](http://requirejs.org/) to organize its files inside `src` folder and [Grunt](http://gruntjs.com/) for build them to `dist`
GrapesJS uses [Webpack2](https://github.com/webpack/webpack) as a module bundler and [Babel](https://github.com/babel/babel) as a compiler.
Clone the repository and enter inside the folder
Clone the repository and install all the necessary dependencies
```sh
$ npm install -g grunt-cli
$ git clone https://github.com/artf/grapesjs.git
$ cd grapesjs
$ npm i
```
Install all necessary dependencies
Start the dev server
```sh
$ npm install
$ npm start
```
Build GrapesJS
Build before the commit. This will also increase the patch level version of the package
```sh
$ npm run build
```
Launch server, which also gonna watch some files, and try out the demo on `localhost:8000`
```sh
$ npm start
```
Tests are already available inside browser on `localhost:8000/test`
If [Grunt](http://gruntjs.com/) is already installed globally you could change the port by using `grunt dev --port 9000`
## Usage
@ -126,14 +130,23 @@ You could also grab the content directly from the element with `fromElement` pro
For more practical example I suggest to look up the code inside this demo: http://grapesjs.com/demo.html
## Configuration
## Documentation
Check the getting started guide here: [wiki]
## API
API References (draft) could be found here: [API-Reference]
API References could be found here: [API-Reference]
## Testing
@ -142,16 +155,8 @@ API References (draft) could be found here: [API-Reference]
$ npm test
```
## Acknowledgements
GrapesJS is built on top of this amazing open source projects:
* [Backbone] - gives Backbone to web applications
* [Backbone.Undo] - a simple Backbone undo-manager
* [Keymaster] - keyboard shortcuts
* [CodeMirror] - versatile text editor
* [Spectrum] - no hassle colorpicker
* [FontAwesome] - the iconic font and CSS framework
## Sponsors
@ -161,6 +166,9 @@ 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.
@ -168,17 +176,14 @@ If you like the project support it with a donation of your choice.
[![PayPalMe](http://grapesjs.com/img/ppme.png)](https://paypal.me/grapesjs)
## License
BSD 3-clause
[Backbone]: <http://backbonejs.org/>
[Backbone.Undo]: <http://backbone.undojs.com/>
[Keymaster]: <https://github.com/madrobby/keymaster>
[CodeMirror]: <http://codemirror.net/>
[Spectrum]: <https://github.com/bgrins/spectrum>
[FontAwesome]: <https://fortawesome.github.io/Font-Awesome/>
[wiki]: <https://github.com/artf/grapesjs/wiki>
[API-Reference]: <https://github.com/artf/grapesjs/wiki/API-Reference>
[CMS]: <https://it.wikipedia.org/wiki/Content_management_system>

53
bower.json

@ -1,53 +0,0 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.5.41",
"author": "Artur Arseniev",
"homepage": "http://grapesjs.com",
"main": [
"dist/grapes.min.js",
"dist/grapes.min.css"
],
"keywords": [
"grapes",
"wte",
"web template editor",
"site builder",
"newsletter builder",
"wysiwyg",
"template",
"editor"
],
"license": "BSD-3-Clause",
"ignore": [
"**/.*",
"styles",
"src",
"test",
"Gruntfile.js",
"index.html",
"README.md",
"package.json"
],
"dependencies": {
"jquery": "~2.2.0"
},
"devDependencies": {
"mocha": "~2.3.4",
"chai": "~3.4.2",
"sinonjs": "~1.17.1",
"backbone": "~1.2.3",
"Backbone.Undo": "~0.2.5",
"keymaster": "~1.6.3",
"requirejs": "~2.1.22",
"requirejs-text": "~2.0.14",
"spectrum": "~1.8.0",
"underscore": "~1.8.3",
"codemirror": "~5.10.0",
"codemirror-formatting": "*",
"almond": "~0.3.1"
},
"resolutions": {
"backbone": "~1.2.3"
}
}

8
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

28
dist/grapes.min.js

File diff suppressed because one or more lines are too long

395
index.html

@ -3,9 +3,10 @@
<head>
<meta charset="utf-8">
<title>GrapesJS</title>
<link rel="stylesheet" href="styles/css/main.css">
<link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="node_modules/codemirror/theme/hopscotch.css">
<link rel="stylesheet" href="dist/css/grapes.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="dist/grapes.min.js"></script>
</head>
<style>
body, html{ height: 100%; margin: 0;}
@ -15,27 +16,6 @@
<div id="gjs" style="height:0px; overflow:hidden">
<header class="header-banner">
<div class="container-width">
<!--
<table>
<thead>
<tr> <th>Header1</th> <th>Header2</th> <th>Header3</th> </tr>
</thead>
<tbody>
<tr> <td>Row11</td> <td>Row12</td> <td>Row13</td> </tr>
<tr> <td>Row21</td> <td>Row22</td> <td>Row23</td> </tr>
<tr> <td>Row31</td> <td>Row32</td> <td>Row33</td> </tr>
</tbody>
</table>
<br/>
<br/>
<table>
<tr> <td>Row11</td> <td>Row12</td> <td>Row13</td> </tr>
<tr> <td>Row21</td> <td>Row22</td> <td>Row23</td> </tr>
<tr> <td>Row31</td> <td>Row32</td> <td><div>Div Content Row33</div></td> </tr>
</table>
<iframe class="iframe" src="http://player.vimeo.com/video/2?&controls=0&loop=1&color=ff0000"></iframe>
<iframe class="iframe" src="https://maps.google.com/maps?&q=London, UK&z=11&t=q&output=embed"></iframe>
-->
<div class="logo-container">
<div class="logo">GrapesJS</div>
</div>
@ -812,6 +792,371 @@
</style>
</div>
<script data-main="src/demo" src="node_modules/requirejs/require.js"></script>
<script type="text/javascript">
var editor = grapesjs.init(
{
allowScripts: 1,
showOffsets: 1,
autorender: 0,
noticeOnUnload: 0,
container : '#gjs',
height: '100%',
fromElement: true,
clearOnRender: 0,
storageManager:{
autoload: 0,
storeComponents: 1,
storeStyles: 1,
},
/*
components: [{
//script: 'var el = this; setInterval(function(){el.style.marginLeft = Math.random() * 50 +"px";}, 1000)',
script: 'loadScript = function(){console.log("loaded INSIDE", $);}',
style: {
background: 'red',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
script: 'this.innerHTML= "test1";',
style: {
background: 'blue',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
script: 'this.innerHTML= "test2";',
style: {
background: 'green',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
style: {
background: 'yellow',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
type: 'text',
style:{
width:'100px',
height:'100px',
margin: '50px auto',
},
traits: ['title'],
components: [{
type: 'textnode',
content: 'text node row',
},{
type: 'textnode',
content: ', another text node',
},{
type: 'link',
content: 'someLink',
},{
type: 'textnode',
content: " More text node --- ",
}],
}],
*/
commands: {
defaults : [{
id: 'open-github',
run: function(editor, sender){
sender.set('active',false);
window.open('https://github.com/artf/grapesjs','_blank');
}
},{
id: 'undo',
run: function(editor, sender){
sender.set('active',false);
editor.UndoManager.undo(true);
}
},{
id: 'redo',
run: function(editor, sender){
sender.set('active',false);
editor.UndoManager.redo(true);
}
},{
id: 'clean-all',
run: function(editor, sender){
sender.set('active',false);
if(confirm('Are you sure to clean the canvas?')){
var comps = editor.DomComponents.clear();
}
}
}],
},
assetManager: {
storageType : '',
storeOnChange : true,
storeAfterUpload : true,
assets : [
{ type: 'image', src : 'http://placehold.it/350x250/78c5d6/fff/image1.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/459ba8/fff/image2.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/79c267/fff/image3.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/c5d647/fff/image4.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/f28c33/fff/image5.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/e868a2/fff/image6.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/cc4360/fff/image7.jpg', height:350, width:250},
{ type: 'image', src : './img/work-desk.jpg', date: '2015-02-01',height:1080, width:1728},
{ type: 'image', src : './img/phone-app.png', date: '2015-02-01',height:650, width:320},
{ type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728},
]
},
styleManager : {
sectors: [{
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'],
},{
name: 'Dimension',
open: false,
buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
},{
name: 'Typography',
open: false,
buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-align', 'text-shadow'],
properties: [{
property: 'text-align',
list : [
{value: 'left', className: 'fa fa-align-left'},
{value: 'center', className: 'fa fa-align-center' },
{value: 'right', className: 'fa fa-align-right'},
{value: 'justify', className: 'fa fa-align-justify'}
],
}]
},{
name: 'Decorations',
open: false,
buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'],
},{
name: 'Extra',
open: false,
buildProps: ['transition', 'perspective', 'transform'],
},{
name: 'Flex',
open: false,
properties: [{
name : 'Flex Container',
property : 'display',
type : 'select',
defaults : 'block',
list : [{
value : 'block',
name : 'Disable',
},{
value : 'flex',
name : 'Enable',
}],
},{
name: 'Flex Parent',
property: 'label-parent-flex',
},{
name : 'Direction',
property : 'flex-direction',
type : 'radio',
defaults : 'row',
list : [{
value : 'row',
name : 'Row',
className : 'icons-flex icon-dir-row',
title : 'Row',
},{
value : 'row-reverse',
name : 'Row reverse',
className : 'icons-flex icon-dir-row-rev',
title : 'Row reverse',
},{
value : 'column',
name : 'Column',
title : 'Column',
className : 'icons-flex icon-dir-col',
},{
value : 'column-reverse',
name : 'Column reverse',
title : 'Column reverse',
className : 'icons-flex icon-dir-col-rev',
}],
},{
name : 'Wrap',
property : 'flex-wrap',
type : 'radio',
defaults : 'nowrap',
list : [{
value : 'nowrap',
title : 'Single line',
},{
value : 'wrap',
title : 'Multiple lines',
},{
value : 'wrap-reverse',
title : 'Multiple lines reverse',
}],
},{
name : 'Justify',
property : 'justify-content',
type : 'radio',
defaults : 'flex-start',
list : [{
value : 'flex-start',
className : 'icons-flex icon-just-start',
title : 'Start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-just-end',
},{
value : 'space-between',
title : 'Space between',
className : 'icons-flex icon-just-sp-bet',
},{
value : 'space-around',
title : 'Space around',
className : 'icons-flex icon-just-sp-ar',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-just-sp-cent',
}],
},{
name : 'Align',
property : 'align-items',
type : 'radio',
defaults : 'center',
list : [{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
},{
name: 'Flex Children',
property: 'label-parent-flex',
},{
name: 'Order',
property: 'order',
type: 'integer',
defaults : 0,
min: 0
},{
name : 'Flex',
property : 'flex',
type : 'composite',
properties : [{
name: 'Grow',
property: 'flex-grow',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Shrink',
property: 'flex-shrink',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Basis',
property: 'flex-basis',
type: 'integer',
units: ['px','%',''],
unit: '',
defaults : 'auto',
}],
},{
name : 'Align',
property : 'align-self',
type : 'radio',
defaults : 'auto',
list : [{
value : 'auto',
name : 'Auto',
},{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
}]
}
],
},
});
window.editor = editor;
var pnm = editor.Panels;
pnm.addButton('options', [{
id: 'undo',
className: 'fa fa-undo icon-undo',
command: function(editor, sender) {
sender.set('active', 0);
editor.UndoManager.undo(1);
},
attributes: { title: 'Undo (CTRL/CMD + Z)'}
},{
id: 'redo',
className: 'fa fa-repeat icon-redo',
command: function(editor, sender) {
sender.set('active', 0);
editor.UndoManager.redo(1);
},
attributes: { title: 'Redo (CTRL/CMD + SHIFT + Z)' }
},{
id: 'clean-all',
className: 'fa fa-trash icon-blank',
command: function(editor, sender) {
if(sender) sender.set('active', false);
if(confirm('Are you sure to clean the canvas?')) {
var comps = editor.DomComponents.clear();
localStorage.clear();
}
},
attributes: { title: 'Empty canvas' }
}]);
editor.render();
</script>
</body>
</html>

49
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.5.41",
"description": "Free and Open Source Web Builder Framework",
"version": "0.7.6",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
@ -11,45 +11,34 @@
"url": "https://github.com/artf/grapesjs.git"
},
"dependencies": {
"almond": "^0.3.3",
"backbone": "^1.3.3",
"backbone-undo": "^0.2.5",
"bower": "^1.7.2",
"codemirror": "^5.21.0",
"codemirror-formatting": "^1.0.0",
"font-awesome": "^4.7.0",
"jquery": "^3.1.1",
"keymaster": "^1.6.2",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.8.3"
},
"devDependencies": {
"bower": "^1.7.2",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-preset-es2015": "^6.24.1",
"chai": "^3.5.0",
"documentation": "^4.0.0-beta2",
"grunt": "^0.4.5",
"grunt-bowercopy": "^1.2.4",
"grunt-cli": "^0.1.13",
"grunt-contrib-clean": "^0.7.0",
"grunt-contrib-concat": "^0.5.1",
"grunt-contrib-connect": "^0.11.2",
"grunt-contrib-copy": "^0.8.2",
"grunt-contrib-cssmin": "^0.14.0",
"grunt-contrib-jshint": "^0.12.0",
"grunt-contrib-requirejs": "^0.4.4",
"grunt-contrib-uglify": "^0.11.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-mocha": "^0.4.15",
"grunt-sass": "^1.1.0",
"expect": "^1.20.2",
"istanbul": "^0.4.2",
"jsdom": "^11.0.0",
"mocha": "^3.1.2",
"node-sass": "^3.4.2",
"requirejs": "^2.3.2",
"requirejs-text": "^2.0.12",
"sinon": "^1.17.6",
"svg2ttf": "^4.0.1",
"ttf2eot": "^2.0.0",
"ttf2woff": "^2.0.1",
"ttf2woff2": "^2.0.3"
"ttf2woff2": "^2.0.3",
"webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5"
},
"keywords": [
"wte",
@ -65,10 +54,18 @@
"template",
"editor"
],
"babel": {
"presets": [
"es2015"
]
},
"scripts": {
"build": "./node_modules/.bin/grunt build",
"build:fonts": "./node_modules/.bin/grunt build:fonts",
"test": "./node_modules/.bin/grunt test",
"start": "./node_modules/.bin/grunt dev"
"lint": "eslint src",
"build": "WEBPACK_ENV=prod && npm run lint && npm run test && npm run v:patch && 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": "WEBPACK_ENV=dev webpack-dev-server --progress --colors & npm run build:css -- -w",
"test": "NODE_PATH=./src mocha --compilers js:babel-core/register --require test/helper.js --recursive test/main.js",
"test:dev": "npm test -- -R min -w"
}
}

26
src/asset_manager/config/config.js

@ -1,18 +1,16 @@
define(function () {
return {
// Default assets
assets: [],
module.exports = {
// Default assets
assets: [],
// Style prefix
stylePrefix: 'am-',
// Style prefix
stylePrefix: 'am-',
// Url where uploads will be send, set false to disable upload
upload: 'http://localhost/assets/upload',
// Url where uploads will be send, set false to disable upload
upload: 'http://localhost/assets/upload',
// Text on upload input
uploadText: 'Drop files here or click to upload',
// Text on upload input
uploadText: 'Drop files here or click to upload',
// Label for the add button
addBtnText: 'Add image',
};
});
// Label for the add button
addBtnText: 'Add image',
};

239
src/asset_manager/index.js

@ -0,0 +1,239 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [remove](#remove)
* * [store](#store)
* * [load](#load)
* * [onClick](#onClick)
* * [onDblClick](#onDblClick)
*
* Before using this methods you should get first the module from the editor instance, in this way:
*
* ```js
* var assetManager = editor.AssetManager;
* ```
*
* @module AssetManager
* @param {Object} config Configurations
* @param {Array<Object>} [config.assets=[]] Default assets
* @param {String} [config.uploadText='Drop files here or click to upload'] Upload text
* @param {String} [config.addBtnText='Add image'] Text for the add button
* @param {String} [config.upload=''] Where to send upload data. Expects as return a JSON with asset/s object
* as: {data: [{src:'...'}, {src:'...'}]}
* @return {this}
* @example
* ...
* {
* assets: [
* {src:'path/to/image.png'},
* ...
* ],
* upload: 'http://dropbox/path', // Set to false to disable it
* uploadText: 'Drop files here or click to upload',
* }
*/
module.exports = () => {
var c = {},
Assets = require('./model/Assets'),
AssetsView = require('./view/AssetsView'),
FileUpload = require('./view/FileUploader'),
assets, am, fu;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'AssetManager',
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey: 'assets',
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init(config) {
c = config || {};
var defaults = require('./config/config');
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
assets = new Assets(c.assets);
var obj = {
collection: assets,
config: c,
};
am = new AssetsView(obj);
fu = new FileUpload(obj);
return this;
},
/**
* Add new asset/s to the collection. URLs are supposed to be unique
* @param {string|Object|Array<string>|Array<Object>} asset URL strings or an objects representing the resource.
* @return {Model}
* @example
* // In case of strings, would be interpreted as images
* assetManager.add('http://img.jpg');
* assetManager.add(['http://img.jpg', './path/to/img.png']);
*
* // Using objects you could indicate the type and other meta informations
* assetManager.add({
* src: 'http://img.jpg',
* //type: 'image', //image is default
* height: 300,
* width: 200,
* });
* assetManager.add([{
* src: 'http://img.jpg',
* },{
* src: './path/to/img.png',
* }]);
*/
add(asset) {
return assets.add(asset);
},
/**
* Returns the asset by URL
* @param {string} src URL of the asset
* @return {Object} Object representing the asset
* @example
* var asset = assetManager.get('http://img.jpg');
*/
get(src) {
return assets.where({src})[0];
},
/**
* Return all assets
* @return {Collection}
*/
getAll() {
return assets;
},
/**
* Remove the asset by its URL
* @param {string} src URL of the asset
* @return {this}
* @example
* assetManager.remove('http://img.jpg');
*/
remove(src) {
var asset = this.get(src);
this.getAll().remove(asset);
return this;
},
/**
* Store assets data to the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
* @example
* var assets = assetManager.store();
*/
store(noStore) {
var obj = {};
var assets = JSON.stringify(this.getAll().toJSON());
obj[this.storageKey] = assets;
if(!noStore && c.stm)
c.stm.store(obj);
return obj;
},
/**
* Load data from the passed object, if the object is empty will try to fetch them
* autonomously from the storage manager.
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded assets
* @example
* var assets = assetManager.load();
* // The format below will be used by the editor model
* // to load automatically all the stuff
* var assets = assetManager.load({
* assets: [...]
* });
*
*/
load(data) {
var d = data || '';
var name = this.storageKey;
if(!d && c.stm)
d = c.stm.load(name);
var assets = [];
try{
assets = JSON.parse(d[name]);
}catch(err){}
this.getAll().add(assets);
return assets;
},
/**
* Render assets
* @param {Boolean} f Force to render, otherwise cached version will be returned
* @return {HTMLElement}
* @private
*/
render(f) {
if(!this.rendered || f)
this.rendered = am.render().$el.add(fu.render().$el);
return this.rendered;
},
//-------
/**
* Set new target
* @param {Object} m Model
* @private
* */
setTarget(m) {
am.collection.target = m;
},
/**
* Set callback after asset was selected
* @param {Object} f Callback function
* @private
* */
onSelect(f) {
am.collection.onSelect = f;
},
/**
* Set callback to fire when the asset is clicked
* @param {function} func
*/
onClick(func) {
c.onClick = func;
},
/**
* Set callback to fire when the asset is double clicked
* @param {function} func
*/
onDblClick(func) {
c.onDblClick = func;
},
};
};

241
src/asset_manager/main.js

@ -1,241 +0,0 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [remove](#remove)
* * [store](#store)
* * [load](#load)
* * [onClick](#onClick)
* * [onDblClick](#onDblClick)
*
* Before using this methods you should get first the module from the editor instance, in this way:
*
* ```js
* var assetManager = editor.AssetManager;
* ```
*
* @module AssetManager
* @param {Object} config Configurations
* @param {Array<Object>} [config.assets=[]] Default assets
* @param {String} [config.uploadText='Drop files here or click to upload'] Upload text
* @param {String} [config.addBtnText='Add image'] Text for the add button
* @param {String} [config.upload=''] Where to send upload data. Expects as return a JSON with asset/s object
* as: {data: [{src:'...'}, {src:'...'}]}
* @return {this}
* @example
* ...
* {
* assets: [
* {src:'path/to/image.png'},
* ...
* ],
* upload: 'http://dropbox/path', // Set to false to disable it
* uploadText: 'Drop files here or click to upload',
* }
*/
define(function(require) {
return function() {
var c = {},
Assets = require('./model/Assets'),
AssetsView = require('./view/AssetsView'),
FileUpload = require('./view/FileUploader'),
assets, am, fu;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'AssetManager',
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey: 'assets',
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init: function(config){
c = config || {};
var defaults = require('./config/config');
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
assets = new Assets(c.assets);
var obj = {
collection: assets,
config: c,
};
am = new AssetsView(obj);
fu = new FileUpload(obj);
return this;
},
/**
* Add new asset/s to the collection. URLs are supposed to be unique
* @param {string|Object|Array<string>|Array<Object>} asset URL strings or an objects representing the resource.
* @return {Model}
* @example
* // In case of strings, would be interpreted as images
* assetManager.add('http://img.jpg');
* assetManager.add(['http://img.jpg', './path/to/img.png']);
*
* // Using objects you could indicate the type and other meta informations
* assetManager.add({
* src: 'http://img.jpg',
* //type: 'image', //image is default
* height: 300,
* width: 200,
* });
* assetManager.add([{
* src: 'http://img.jpg',
* },{
* src: './path/to/img.png',
* }]);
*/
add: function(asset){
return assets.add(asset);
},
/**
* Returns the asset by URL
* @param {string} src URL of the asset
* @return {Object} Object representing the asset
* @example
* var asset = assetManager.get('http://img.jpg');
*/
get: function(src){
return assets.where({src: src})[0];
},
/**
* Return all assets
* @return {Collection}
*/
getAll: function(){
return assets;
},
/**
* Remove the asset by its URL
* @param {string} src URL of the asset
* @return {this}
* @example
* assetManager.remove('http://img.jpg');
*/
remove: function(src){
var asset = this.get(src);
this.getAll().remove(asset);
return this;
},
/**
* Store assets data to the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
* @example
* var assets = assetManager.store();
*/
store: function(noStore){
var obj = {};
var assets = JSON.stringify(this.getAll().toJSON());
obj[this.storageKey] = assets;
if(!noStore && c.stm)
c.stm.store(obj);
return obj;
},
/**
* Load data from the passed object, if the object is empty will try to fetch them
* autonomously from the storage manager.
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded assets
* @example
* var assets = assetManager.load();
* // The format below will be used by the editor model
* // to load automatically all the stuff
* var assets = assetManager.load({
* assets: [...]
* });
*
*/
load: function(data){
var d = data || '';
var name = this.storageKey;
if(!d && c.stm)
d = c.stm.load(name);
var assets = [];
try{
assets = JSON.parse(d[name]);
}catch(err){}
this.getAll().add(assets);
return assets;
},
/**
* Render assets
* @param {Boolean} f Force to render, otherwise cached version will be returned
* @return {HTMLElement}
* @private
*/
render: function(f){
if(!this.rendered || f)
this.rendered = am.render().$el.add(fu.render().$el);
return this.rendered;
},
//-------
/**
* Set new target
* @param {Object} m Model
* @private
* */
setTarget: function(m){
am.collection.target = m;
},
/**
* Set callback after asset was selected
* @param {Object} f Callback function
* @private
* */
onSelect: function(f){
am.collection.onSelect = f;
},
/**
* Set callback to fire when the asset is clicked
* @param {function} func
*/
onClick: function(func) {
c.onClick = func;
},
/**
* Set callback to fire when the asset is double clicked
* @param {function} func
*/
onDblClick: function(func) {
c.onDblClick = func;
},
};
};
});

49
src/asset_manager/model/Asset.js

@ -1,31 +1,30 @@
define(['backbone'],
function (Backbone) {
return Backbone.Model.extend({
var Backbone = require('backbone');
idAttribute: 'src',
module.exports = Backbone.Model.extend({
defaults: {
type: '',
src: '',
},
idAttribute: 'src',
/**
* Get filename of the asset
* @return {string}
* @private
* */
getFilename: function(){
return this.get('src').split('/').pop();
},
defaults: {
type: '',
src: '',
},
/**
* Get extension of the asset
* @return {string}
* @private
* */
getExtension: function(){
return this.getFilename().split('.').pop();
},
/**
* Get filename of the asset
* @return {string}
* @private
* */
getFilename() {
return this.get('src').split('/').pop();
},
/**
* Get extension of the asset
* @return {string}
* @private
* */
getExtension() {
return this.getFilename().split('.').pop();
},
});
});

20
src/asset_manager/model/AssetImage.js

@ -1,13 +1,13 @@
define(['backbone', './Asset'],
function (Backbone, Asset) {
return Asset.extend({
var Backbone = require('backbone');
var Asset = require('./Asset');
defaults: _.extend({}, Asset.prototype.defaults, {
type: 'image',
unitDim: 'px',
height: 0,
width: 0,
}),
module.exports = Asset.extend({
defaults: _.extend({}, Asset.prototype.defaults, {
type: 'image',
unitDim: 'px',
height: 0,
width: 0,
}),
});
});

133
src/asset_manager/model/Assets.js

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

10
src/asset_manager/template/assetImage.html

@ -1,10 +0,0 @@
<div id="<%= pfx %>preview-cont">
<div id="<%= pfx %>preview" style="background-image: url(<%= src %>);"></div>
<div id="<%= pfx %>preview-bg" class="<%= ppfx %>checker-bg"></div>
</div>
<div id="<%= pfx %>meta">
<div id="<%= pfx %>name"><%= name %></div>
<div id="<%= pfx %>dimensions"><%= dim %></div>
</div>
<div id="<%= pfx %>close">&Cross;</div>
<div style="clear:both"></div>

17
src/asset_manager/template/assets.html

@ -1,17 +0,0 @@
<div class="<%= pfx %>assets-cont">
<div class="<%= pfx %>assets-header">
<form class="<%= pfx %>add-asset">
<div class="<%= ppfx %>field <%= pfx %>add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="<%= ppfx %>btn-prim"><%= btnText %></button>
<div style="clear:both"></div>
</form>
<div class="<%= pfx %>dips" style="display:none">
<button class="fa fa-th <%= ppfx %>btnt"></button>
<button class="fa fa-th-list <%= ppfx %>btnt"></button>
</div>
</div>
<div class="<%= pfx %>assets"></div>
<div style="clear:both"></div>
</div>

5
src/asset_manager/template/fileUploader.html

@ -1,5 +0,0 @@
<form>
<div id="<%= pfx %>title"><%= title %></div>
<input type="file" id="<%= uploadId %>" name="file" accept="image/*" <%= disabled ? 'disabled' : '' %> multiple/>
<div style="clear:both;"></div>
</form>

181
src/asset_manager/view/AssetImageView.js

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

22
src/asset_manager/view/AssetView.js

@ -1,14 +1,12 @@
define(['backbone'], function (Backbone) {
return Backbone.View.extend({
initialize: function(o) {
this.options = o;
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.className = this.pfx + 'asset';
this.listenTo( this.model, 'destroy remove', this.remove);
},
});
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
initialize(o) {
this.options = o;
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.className = this.pfx + 'asset';
this.listenTo( this.model, 'destroy remove', this.remove);
},
});

282
src/asset_manager/view/AssetsView.js

@ -1,131 +1,153 @@
define(['backbone', './AssetView', './AssetImageView', './FileUploader', 'text!./../template/assets.html'],
function (Backbone, AssetView, AssetImageView, FileUploader, assetsTemplate) {
return Backbone.View.extend({
template: _.template(assetsTemplate),
initialize: function(o) {
this.options = o;
this.config = o.config;
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.listenTo( this.collection, 'add', this.addToAsset );
this.listenTo( this.collection, 'deselectAll', this.deselectAll );
this.className = this.pfx + 'assets';
this.events = {};
this.events.submit = 'addFromStr';
this.delegateEvents();
},
/**
* Add new asset to the collection via string
* @param {Event} e Event object
* @return {this}
* @private
*/
addFromStr: function(e){
e.preventDefault();
var input = this.getInputUrl();
var url = input.value.trim();
if(!url)
return;
this.collection.addImg(url, {at: 0});
this.getAssetsEl().scrollTop = 0;
input.value = '';
return this;
},
/**
* Returns assets element
* @return {HTMLElement}
* @private
*/
getAssetsEl: function(){
//if(!this.assets) // Not able to cache as after the rerender it losses the ref
this.assets = this.el.querySelector('.' + this.pfx + 'assets');
return this.assets;
},
/**
* Returns input url element
* @return {HTMLElement}
* @private
*/
getInputUrl: function(){
if(!this.inputUrl || !this.inputUrl.value)
this.inputUrl = this.el.querySelector('.'+this.pfx+'add-asset input');
return this.inputUrl;
},
/**
* Add asset to collection
* @private
* */
addToAsset: function(model){
this.addAsset(model);
},
/**
* Add new asset to collection
* @param Object Model
* @param Object Fragment collection
* @return Object Object created
* @private
* */
addAsset: function(model, fragmentEl){
var fragment = fragmentEl || null;
var viewObject = AssetView;
if(model.get('type').indexOf("image") > -1)
viewObject = AssetImageView;
var view = new viewObject({
model : model,
config : this.config,
});
var rendered = view.render().el;
if(fragment){
fragment.appendChild( rendered );
}else{
var assetsEl = this.getAssetsEl();
if(assetsEl)
assetsEl.insertBefore(rendered, assetsEl.firstChild);
}
return rendered;
},
/**
* Deselect all assets
* @private
* */
deselectAll: function(){
this.$el.find('.' + this.pfx + 'highlight').removeClass(this.pfx + 'highlight');
},
render: function() {
var fragment = document.createDocumentFragment();
this.$el.empty();
this.collection.each(function(model){
this.addAsset(model, fragment);
},this);
this.$el.html(this.template({
pfx: this.pfx,
ppfx: this.ppfx,
btnText: this.config.addBtnText,
}));
this.$el.find('.'+this.pfx + 'assets').append(fragment);
return this;
}
});
var Backbone = require('backbone');
var AssetView = require('./AssetView');
var AssetImageView = require('./AssetImageView');
var FileUploader = require('./FileUploader');
var assetsTemplate = `
<div class="<%= pfx %>assets-cont">
<div class="<%= pfx %>assets-header">
<form class="<%= pfx %>add-asset">
<div class="<%= ppfx %>field <%= pfx %>add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="<%= ppfx %>btn-prim"><%= btnText %></button>
<div style="clear:both"></div>
</form>
<div class="<%= pfx %>dips" style="display:none">
<button class="fa fa-th <%= ppfx %>btnt"></button>
<button class="fa fa-th-list <%= ppfx %>btnt"></button>
</div>
</div>
<div class="<%= pfx %>assets"></div>
<div style="clear:both"></div>
</div>
`;
module.exports = Backbone.View.extend({
template: _.template(assetsTemplate),
initialize(o) {
this.options = o;
this.config = o.config;
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.listenTo( this.collection, 'add', this.addToAsset );
this.listenTo( this.collection, 'deselectAll', this.deselectAll );
this.className = this.pfx + 'assets';
this.events = {};
this.events.submit = 'addFromStr';
this.delegateEvents();
},
/**
* Add new asset to the collection via string
* @param {Event} e Event object
* @return {this}
* @private
*/
addFromStr(e) {
e.preventDefault();
var input = this.getInputUrl();
var url = input.value.trim();
if(!url)
return;
this.collection.addImg(url, {at: 0});
this.getAssetsEl().scrollTop = 0;
input.value = '';
return this;
},
/**
* Returns assets element
* @return {HTMLElement}
* @private
*/
getAssetsEl() {
//if(!this.assets) // Not able to cache as after the rerender it losses the ref
this.assets = this.el.querySelector('.' + this.pfx + 'assets');
return this.assets;
},
/**
* Returns input url element
* @return {HTMLElement}
* @private
*/
getInputUrl() {
if(!this.inputUrl || !this.inputUrl.value)
this.inputUrl = this.el.querySelector('.'+this.pfx+'add-asset input');
return this.inputUrl;
},
/**
* Add asset to collection
* @private
* */
addToAsset(model) {
this.addAsset(model);
},
/**
* Add new asset to collection
* @param Object Model
* @param Object Fragment collection
* @return Object Object created
* @private
* */
addAsset(model, fragmentEl) {
var fragment = fragmentEl || null;
var viewObject = AssetView;
if(model.get('type').indexOf("image") > -1)
viewObject = AssetImageView;
var view = new viewObject({
model,
config : this.config,
});
var rendered = view.render().el;
if(fragment){
fragment.appendChild( rendered );
}else{
var assetsEl = this.getAssetsEl();
if(assetsEl)
assetsEl.insertBefore(rendered, assetsEl.firstChild);
}
return rendered;
},
/**
* Deselect all assets
* @private
* */
deselectAll() {
this.$el.find('.' + this.pfx + 'highlight').removeClass(this.pfx + 'highlight');
},
render() {
var fragment = document.createDocumentFragment();
this.$el.empty();
this.collection.each(function(model){
this.addAsset(model, fragment);
},this);
this.$el.html(this.template({
pfx: this.pfx,
ppfx: this.ppfx,
btnText: this.config.addBtnText,
}));
this.$el.find('.'+this.pfx + 'assets').append(fragment);
return this;
}
});

184
src/asset_manager/view/FileUploader.js

@ -1,97 +1,103 @@
define(['backbone', 'text!./../template/fileUploader.html'],
function (Backbone, fileUploaderTemplate) {
return Backbone.View.extend({
var Backbone = require('backbone');
var fileUploaderTemplate = `
<form>
<div id="<%= pfx %>title"><%= title %></div>
<input type="file" id="<%= uploadId %>" name="file" accept="image/*" <%= disabled ? 'disabled' : '' %> multiple/>
<div style="clear:both;"></div>
</form>
`;
template: _.template(fileUploaderTemplate),
module.exports = Backbone.View.extend({
events: {},
template: _.template(fileUploaderTemplate),
initialize: function(o) {
this.options = o || {};
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.target = this.collection || {};
this.uploadId = this.pfx + 'uploadFile';
this.disabled = !this.config.upload;
this.events['change #' + this.uploadId] = 'uploadFile';
this.delegateEvents();
},
events: {},
/**
* Upload files
* @param {Object} e Event
* @private
* */
uploadFile : function(e){
var files = e.dataTransfer ? e.dataTransfer.files : e.target.files,
formData = new FormData();
for (var i = 0; i < files.length; i++) {
formData.append('files[]', files[i]);
}
var target = this.target;
$.ajax({
url : this.config.upload,
type : 'POST',
data : formData,
beforeSend : this.config.beforeSend,
complete : this.config.onComplete,
xhrFields : {
onprogress: function (e) {
if (e.lengthComputable) {
/*var result = e.loaded / e.total * 100 + '%';*/
}
},
onload: function (e) {
//progress.value = 100;
}
},
cache: false, contentType: false, processData: false
}).done(function(data){
target.add(data.data);
}).always(function(){
//turnOff loading
});
},
initialize(o) {
this.options = o || {};
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.target = this.collection || {};
this.uploadId = this.pfx + 'uploadFile';
this.disabled = !this.config.upload;
this.events['change #' + this.uploadId] = 'uploadFile';
this.delegateEvents();
},
/**
* Make input file droppable
* @private
* */
initDrop: function(){
var that = this;
if(!this.uploadForm){
this.uploadForm = this.$el.find('form').get(0);
if( 'draggable' in this.uploadForm ){
var uploadFile = this.uploadFile;
this.uploadForm.ondragover = function(){
this.className = that.pfx + 'hover';
return false;
};
this.uploadForm.ondragleave = function(){
this.className = '';
return false;
};
this.uploadForm.ondrop = function(e){
this.className = '';
e.preventDefault();
that.uploadFile(e);
return;
};
}
}
},
/**
* Upload files
* @param {Object} e Event
* @private
* */
uploadFile(e) {
var files = e.dataTransfer ? e.dataTransfer.files : e.target.files,
formData = new FormData();
for (var i = 0; i < files.length; i++) {
formData.append('files[]', files[i]);
}
var target = this.target;
$.ajax({
url : this.config.upload,
type : 'POST',
data : formData,
beforeSend : this.config.beforeSend,
complete : this.config.onComplete,
xhrFields : {
onprogress(e) {
if (e.lengthComputable) {
/*var result = e.loaded / e.total * 100 + '%';*/
}
},
onload(e) {
//progress.value = 100;
}
},
cache: false, contentType: false, processData: false
}).done(data => {
target.add(data.data);
}).always(() => {
//turnOff loading
});
},
render : function(){
this.$el.html( this.template({
title: this.config.uploadText,
uploadId: this.uploadId,
disabled: this.disabled,
pfx: this.pfx
}) );
this.initDrop();
this.$el.attr('class', this.pfx + 'file-uploader');
return this;
},
/**
* Make input file droppable
* @private
* */
initDrop() {
var that = this;
if(!this.uploadForm){
this.uploadForm = this.$el.find('form').get(0);
if( 'draggable' in this.uploadForm ){
var uploadFile = this.uploadFile;
this.uploadForm.ondragover = function(){
this.className = that.pfx + 'hover';
return false;
};
this.uploadForm.ondragleave = function(){
this.className = '';
return false;
};
this.uploadForm.ondrop = function(e){
this.className = '';
e.preventDefault();
that.uploadFile(e);
return;
};
}
}
},
render() {
this.$el.html( this.template({
title: this.config.uploadText,
uploadId: this.uploadId,
disabled: this.disabled,
pfx: this.pfx
}) );
this.initDrop();
this.$el.attr('class', this.pfx + 'file-uploader');
return this;
},
});
});

8
src/block_manager/config/config.js

@ -1,7 +1,5 @@
define(function () {
return {
module.exports = {
'blocks': [],
'blocks': [],
};
});
};

118
src/block_manager/index.js

@ -0,0 +1,118 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [render](#render)
*
* Block manager helps managing various, draggable, piece of contents that could be easily reused inside templates.
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var blockManager = editor.BlockManager;
* ```
*
* @module BlockManager
* @param {Object} config Configurations
* @param {Array<Object>} [config.blocks=[]] Default blocks
* @example
* ...
* {
* blocks: [
* {id:'h1-block' label: 'Heading', content:'<h1>...</h1>'},
* ...
* ],
* }
* ...
*/
module.exports = () => {
var c = {},
defaults = require('./config/config'),
Blocks = require('./model/Blocks'),
BlocksView = require('./view/BlocksView');
var blocks, view;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'BlockManager',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @return {this}
* @private
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
blocks = new Blocks(c.blocks);
view = new BlocksView({ collection: blocks }, c);
return this;
},
/**
* Add new block to the collection.
* @param {string} id Block id
* @param {Object} opts Options
* @param {string} opts.label Name of the block
* @param {string} opts.content HTML content
* @param {Object} [opts.attributes={}] Block attributes
* @return {Block} Added block
* @example
* blockManager.add('h1-block', {
* label: 'Heading',
* content: '<h1>Put your title here</h1>',
* attributes: {
* title: 'Insert h1 block'
* }
* });
*/
add(id, opts) {
var obj = opts || {};
obj.id = id;
return blocks.add(obj);
},
/**
* Return block by id
* @param {string} id Block id
* @example
* var block = blockManager.get('h1-block');
* console.log(JSON.stringify(block));
* // {label: 'Heading', content: '<h1>Put your ...', ...}
*/
get(id) {
return blocks.get(id);
},
/**
* Return all blocks
* @return {Collection}
* @example
* var blocks = blockManager.getAll();
* console.log(JSON.stringify(blocks));
* // [{label: 'Heading', content: '<h1>Put your ...'}, ...]
*/
getAll() {
return blocks;
},
/**
* Render blocks
* @return {HTMLElement}
*/
render() {
return view.render().el;
},
};
};

122
src/block_manager/main.js

@ -1,122 +0,0 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [render](#render)
*
* Block manager helps managing various, draggable, piece of contents that could be easily reused inside templates.
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var blockManager = editor.BlockManager;
* ```
*
* @module BlockManager
* @param {Object} config Configurations
* @param {Array<Object>} [config.blocks=[]] Default blocks
* @example
* ...
* {
* blocks: [
* {id:'h1-block' label: 'Heading', content:'<h1>...</h1>'},
* ...
* ],
* }
* ...
*/
define(function(require) {
return function() {
var c = {},
defaults = require('./config/config'),
Blocks = require('./model/Blocks'),
BlocksView = require('./view/BlocksView');
var blocks, view;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'BlockManager',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @return {this}
* @private
*/
init: function(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
blocks = new Blocks(c.blocks);
view = new BlocksView({ collection: blocks }, c);
return this;
},
/**
* Add new block to the collection.
* @param {string} id Block id
* @param {Object} opts Options
* @param {string} opts.label Name of the block
* @param {string} opts.content HTML content
* @param {Object} [opts.attributes={}] Block attributes
* @return {Block} Added block
* @example
* blockManager.add('h1-block', {
* label: 'Heading',
* content: '<h1>Put your title here</h1>',
* attributes: {
* title: 'Insert h1 block'
* }
* });
*/
add: function(id, opts){
var obj = opts || {};
obj.id = id;
return blocks.add(obj);
},
/**
* Return block by id
* @param {string} id Block id
* @example
* var block = blockManager.get('h1-block');
* console.log(JSON.stringify(block));
* // {label: 'Heading', content: '<h1>Put your ...', ...}
*/
get: function(id){
return blocks.get(id);
},
/**
* Return all blocks
* @return {Collection}
* @example
* var blocks = blockManager.getAll();
* console.log(JSON.stringify(blocks));
* // [{label: 'Heading', content: '<h1>Put your ...'}, ...]
*/
getAll: function(){
return blocks;
},
/**
* Render blocks
* @return {HTMLElement}
*/
render: function(){
return view.render().el;
},
};
};
});

18
src/block_manager/model/Block.js

@ -1,13 +1,11 @@
define(['backbone'],
function(Backbone){
var Backbone = require('backbone');
return Backbone.Model.extend({
module.exports = Backbone.Model.extend({
defaults :{
label: '',
content: '',
attributes: {},
},
defaults :{
label: '',
content: '',
attributes: {},
},
});
});
});

11
src/block_manager/model/Blocks.js

@ -1,9 +1,6 @@
define(['backbone','./Block'],
function (Backbone, Block) {
var Backbone = require('backbone');
var Block = require('./Block');
return Backbone.Collection.extend({
model: Block,
});
module.exports = Backbone.Collection.extend({
model: Block,
});

82
src/block_manager/view/BlockView.js

@ -1,50 +1,48 @@
define(['backbone'],
function(Backbone) {
var Backbone = require('backbone');
return Backbone.View.extend({
module.exports = Backbone.View.extend({
events: {
mousedown: 'onDrag'
},
events: {
mousedown: 'onDrag'
},
initialize: function(o, config) {
_.bindAll(this, 'onDrop');
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
this.listenTo(this.model, 'destroy', this.remove);
this.doc = $(document);
},
initialize(o, config) {
_.bindAll(this, 'onDrop');
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
this.listenTo(this.model, 'destroy', this.remove);
this.doc = $(document);
},
/**
* Start block dragging
* @private
*/
onDrag: function(e) {
if(!this.config.getSorter)
return;
this.config.em.refreshCanvas();
var sorter = this.config.getSorter();
sorter.setDragHelper(this.el, e);
sorter.startSort(this.el);
sorter.setDropContent(this.model.get('content'));
this.doc.on('mouseup', this.onDrop);
},
/**
* Start block dragging
* @private
*/
onDrag(e) {
if(!this.config.getSorter)
return;
this.config.em.refreshCanvas();
var sorter = this.config.getSorter();
sorter.setDragHelper(this.el, e);
sorter.startSort(this.el);
sorter.setDropContent(this.model.get('content'));
this.doc.on('mouseup', this.onDrop);
},
/**
* Drop block
* @private
*/
onDrop: function() {
this.doc.off('mouseup', this.onDrop);
this.config.getSorter().endMove();
},
/**
* Drop block
* @private
*/
onDrop() {
this.doc.off('mouseup', this.onDrop);
this.config.getSorter().endMove();
},
render: function() {
var className = this.ppfx + 'block';
this.$el.addClass(className);
this.el.innerHTML = '<div class="' + className + '-label">' + this.model.get('label') + '</div>';
return this;
},
render() {
var className = this.ppfx + 'block';
this.$el.addClass(className);
this.el.innerHTML = '<div class="' + className + '-label">' + this.model.get('label') + '</div>';
return this;
},
});
});

237
src/block_manager/view/BlocksView.js

@ -1,121 +1,120 @@
define(['backbone', './BlockView'],
function(Backbone, BlockView) {
return Backbone.View.extend({
initialize: function(opts, config) {
_.bindAll(this, 'getSorter', 'onDrag', 'onDrop');
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
this.listenTo(this.collection, 'add', this.addTo);
this.em = this.config.em;
this.tac = 'test-tac';
this.grabbingCls = this.ppfx + 'grabbing';
if(this.em){
this.config.getSorter = this.getSorter;
this.canvas = this.em.get('Canvas');
var Backbone = require('backbone');
var BlockView = require('./BlockView');
module.exports = Backbone.View.extend({
initialize(opts, config) {
_.bindAll(this, 'getSorter', 'onDrag', 'onDrop');
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
this.listenTo(this.collection, 'add', this.addTo);
this.em = this.config.em;
this.tac = 'test-tac';
this.grabbingCls = this.ppfx + 'grabbing';
if(this.em){
this.config.getSorter = this.getSorter;
this.canvas = this.em.get('Canvas');
}
},
/**
* Get sorter
* @private
*/
getSorter() {
if(!this.em)
return;
if(!this.sorter){
var utils = this.em.get('Utils');
var canvas = this.canvas;
this.sorter = new utils.Sorter({
container: canvas.getBody(),
placer: canvas.getPlacerEl(),
containerSel: '*',
itemSel: '*',
pfx: this.ppfx,
onStart: this.onDrag,
onEndMove: this.onDrop,
document: canvas.getFrameEl().contentDocument,
direction: 'a',
wmargin: 1,
nested: 1,
em: this.em,
canvasRelative: 1,
});
}
return this.sorter;
},
/**
* Callback when block is on drag
* @private
*/
onDrag() {
this.em.stopDefault();
},
/**
* Callback when block is dropped
* @private
*/
onDrop(model) {
this.em.runDefault();
if (model && model.get) {
if(model.get('activeOnRender')) {
model.trigger('active');
model.set('activeOnRender', 0);
}
},
/**
* Get sorter
* @private
*/
getSorter: function(){
if(!this.em)
return;
if(!this.sorter){
var utils = this.em.get('Utils');
var canvas = this.canvas;
this.sorter = new utils.Sorter({
container: canvas.getBody(),
placer: canvas.getPlacerEl(),
containerSel: '*',
itemSel: '*',
pfx: this.ppfx,
onStart: this.onDrag,
onEndMove: this.onDrop,
document: canvas.getFrameEl().contentDocument,
direction: 'a',
wmargin: 1,
nested: 1,
em: this.em,
canvasRelative: 1,
});
}
return this.sorter;
},
/**
* Callback when block is on drag
* @private
*/
onDrag: function(){
this.em.stopDefault();
},
/**
* Callback when block is dropped
* @private
*/
onDrop: function(model){
this.em.runDefault();
if (model && model.get) {
if(model.get('activeOnRender')) {
model.trigger('active');
model.set('activeOnRender', 0);
}
// Register all its components (eg. for the Undo Manager)
this.em.initChildrenComp(model);
}
},
/**
* Add new model to the collection
* @param {Model} model
* @private
* */
addTo: function(model){
this.add(model);
},
/**
* Render new model inside the view
* @param {Model} model
* @param {Object} fragment Fragment collection
* @private
* */
add: function(model, fragment){
var frag = fragment || null;
var view = new BlockView({
model: model,
attributes: model.get('attributes'),
}, this.config);
var rendered = view.render().el;
if(frag)
frag.appendChild(rendered);
else
this.$el.append(rendered);
},
render: function() {
var frag = document.createDocumentFragment();
this.$el.empty();
this.collection.each(function(model){
this.add(model, frag);
}, this);
this.$el.append(frag);
this.$el.addClass(this.ppfx + 'blocks-c');
return this;
},
});
// Register all its components (eg. for the Undo Manager)
this.em.initChildrenComp(model);
}
},
/**
* 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 view = new BlockView({
model,
attributes: model.get('attributes'),
}, 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.addClass(this.ppfx + 'blocks-c');
return this;
},
});

22
src/canvas/config/config.js

@ -1,16 +1,14 @@
define(function () {
return {
module.exports = {
stylePrefix: 'cv-',
stylePrefix: 'cv-',
// Coming soon
rulers: false,
// Coming soon
rulers: false,
/*
* append scripts in head of iframe before renderBody content
* need to manually maintain the same scripts in cms's render template
*/
scripts: []
/*
* append scripts in head of iframe before renderBody content
* need to manually maintain the same scripts in cms's render template
*/
scripts: []
};
});
};

325
src/canvas/index.js

@ -0,0 +1,325 @@
module.exports = () => {
var c = {},
defaults = require('./config/config'),
Canvas = require('./model/Canvas'),
CanvasView = require('./view/CanvasView');
var canvas;
return {
/**
* Used inside RTE
* @private
*/
getCanvasView() {
return CanvasView;
},
/**
* Name of the module
* @type {String}
* @private
*/
name: 'Canvas',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
canvas = new Canvas(config);
CanvasView = new CanvasView({
model: canvas,
config: c,
});
var cm = c.em.get('DomComponents');
if(cm)
this.setWrapper(cm);
return this;
},
/**
* Add wrapper
* @param {Object} wrp Wrapper
*
* */
setWrapper(wrp) {
canvas.set('wrapper', wrp);
},
/**
* Returns canvas element
* @return {HTMLElement}
*/
getElement() {
return CanvasView.el;
},
/**
* Returns frame element of the canvas
* @return {HTMLElement}
*/
getFrameEl() {
return CanvasView.frame.el;
},
/**
* Returns body element of the frame
* @return {HTMLElement}
*/
getBody() {
return CanvasView.frame.el.contentDocument.body;
},
/**
* Returns body wrapper element of the frame
* @return {HTMLElement}
*/
getWrapperEl() {
return this.getBody().querySelector('#wrapper');
},
/**
* Returns element containing canvas tools
* @return {HTMLElement}
*/
getToolsEl() {
return CanvasView.toolsEl;
},
/**
* Returns highlighter element
* @return {HTMLElement}
*/
getHighlighter() {
return CanvasView.hlEl;
},
/**
* Returns badge element
* @return {HTMLElement}
*/
getBadgeEl() {
return CanvasView.badgeEl;
},
/**
* Returns placer element
* @return {HTMLElement}
*/
getPlacerEl() {
return CanvasView.placerEl;
},
/**
* Returns ghost element
* @return {HTMLElement}
* @private
*/
getGhostEl() {
return CanvasView.ghostEl;
},
/**
* Returns toolbar element
* @return {HTMLElement}
*/
getToolbarEl() {
return CanvasView.toolbarEl;
},
/**
* Returns resizer element
* @return {HTMLElement}
*/
getResizerEl() {
return CanvasView.resizerEl;
},
/**
* Returns offset viewer element
* @return {HTMLElement}
*/
getOffsetViewerEl() {
return CanvasView.offsetEl;
},
/**
* Returns fixed offset viewer element
* @return {HTMLElement}
*/
getFixedOffsetViewerEl() {
return CanvasView.fixedOffsetEl;
},
/**
* Render canvas
* */
render() {
return CanvasView.render().el;
},
/**
* Get frame position
* @return {Object}
* @private
*/
getOffset() {
var frameOff = this.offset(this.getFrameEl());
var canvasOff = this.offset(this.getElement());
return {
top: frameOff.top - canvasOff.top,
left: frameOff.left - canvasOff.left
};
},
/**
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
* @private
*/
offset(el) {
var rect = el.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
},
/**
* Get element position relative to the canvas
* @param {HTMLElement} el
* @return {Object}
*/
getElementPos(el) {
return CanvasView.getElementPos(el);
},
/**
* This method comes handy when you need to attach something like toolbars
* to elements inside the canvas, dealing with all relative position,
* offsets, etc. and returning as result the object with positions which are
* viewable by the user (when the canvas is scrolled the top edge of the element
* is not viewable by the user anymore so the new top edge is the one of the canvas)
*
* The target should be visible before being passed here as invisible elements
* return empty string as width
* @param {HTMLElement} target The target in this case could be the toolbar
* @param {HTMLElement} element The element on which I'd attach the toolbar
* @param {Object} options Custom options
* @param {Boolean} options.toRight Set to true if you want the toolbar attached to the right
* @return {Object}
*/
getTargetToElementDim(target, element, options) {
var opts = options || {};
var canvasPos = CanvasView.getPosition();
var pos = opts.elPos || CanvasView.getElementPos(element);
var toRight = options.toRight || 0;
var targetHeight = opts.targetHeight || target.offsetHeight;
var targetWidth = opts.targetWidth || target.offsetWidth;
var eventToTrigger = opts.event || null;
var elTop = pos.top - targetHeight;
var elLeft = pos.left;
elLeft += toRight ? pos.width : 0;
elLeft = toRight ? (elLeft - targetWidth) : elLeft;
var leftPos = elLeft < canvasPos.left ? canvasPos.left : elLeft;
var topPos = elTop < canvasPos.top ? canvasPos.top : elTop;
topPos = topPos > (pos.top + pos.height) ? (pos.top + pos.height) : topPos;
var result = {
top: topPos,
left: leftPos,
elementTop: pos.top,
elementLeft: pos.left,
elementWidth: pos.width,
elementHeight: pos.height,
targetWidth: target.offsetWidth,
targetHeight: target.offsetHeight,
canvasTop: canvasPos.top,
canvasLeft: canvasPos.left,
};
// In this way I can catch data and also change the position strategy
if(eventToTrigger && c.em) {
c.em.trigger(eventToTrigger, result);
}
return result;
},
/**
* Instead of simply returning e.clientX and e.clientY this function
* calculates also the offset based on the canvas. This is helpful when you
* need to get X and Y position while moving between the editor area and
* canvas area, which is in the iframe
* @param {Event} e
* @return {Object}
*/
getMouseRelativePos(e, options) {
var opts = options || {};
var addTop = 0;
var addLeft = 0;
var subWinOffset = opts.subWinOffset;
var doc = e.target.ownerDocument;
var win = doc.defaultView || doc.parentWindow;
var frame = win.frameElement;
var yOffset = subWinOffset ? win.pageYOffset : 0;
var xOffset = subWinOffset ? win.pageXOffset : 0;
if (frame) {
var frameRect = frame.getBoundingClientRect();
addTop = frameRect.top || 0;
addLeft = frameRect.left || 0;
}
return {
y: e.clientY + addTop - yOffset,
x: e.clientX + addLeft - xOffset,
};
},
/**
* X and Y mouse position relative to the canvas
* @param {Event} e
* @return {Object}
*/
getMouseRelativeCanvas(e, options) {
var opts = options || {};
var frame = this.getFrameEl();
var body = this.getBody();
var addTop = frame.offsetTop || 0;
var addLeft = frame.offsetLeft || 0;
var yOffset = body.scrollTop || 0;
var xOffset = body.scrollLeft || 0;
return {
y: e.clientY + addTop + yOffset,
x: e.clientX + addLeft + xOffset,
};
},
/**
* Returns wrapper element
* @return {HTMLElement}
* ????
*/
getFrameWrapperEl() {
return CanvasView.frame.getWrapper();
},
};
};

329
src/canvas/main.js

@ -1,329 +0,0 @@
define(function(require) {
return function() {
var c = {},
defaults = require('./config/config'),
Canvas = require('./model/Canvas'),
CanvasView = require('./view/CanvasView');
var canvas;
return {
/**
* Used inside RTE
* @private
*/
getCanvasView: function() {
return CanvasView;
},
/**
* Name of the module
* @type {String}
* @private
*/
name: 'Canvas',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
*/
init: function(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
canvas = new Canvas(config);
CanvasView = new CanvasView({
model: canvas,
config: c,
});
var cm = c.em.get('DomComponents');
if(cm)
this.setWrapper(cm);
return this;
},
/**
* Add wrapper
* @param {Object} wrp Wrapper
*
* */
setWrapper: function(wrp) {
canvas.set('wrapper', wrp);
},
/**
* Returns canvas element
* @return {HTMLElement}
*/
getElement: function(){
return CanvasView.el;
},
/**
* Returns frame element of the canvas
* @return {HTMLElement}
*/
getFrameEl: function(){
return CanvasView.frame.el;
},
/**
* Returns body element of the frame
* @return {HTMLElement}
*/
getBody: function(){
return CanvasView.frame.el.contentDocument.body;
},
/**
* Returns body wrapper element of the frame
* @return {HTMLElement}
*/
getWrapperEl: function(){
return this.getBody().querySelector('#wrapper');
},
/**
* Returns element containing canvas tools
* @return {HTMLElement}
*/
getToolsEl: function(){
return CanvasView.toolsEl;
},
/**
* Returns highlighter element
* @return {HTMLElement}
*/
getHighlighter: function(){
return CanvasView.hlEl;
},
/**
* Returns badge element
* @return {HTMLElement}
*/
getBadgeEl: function(){
return CanvasView.badgeEl;
},
/**
* Returns placer element
* @return {HTMLElement}
*/
getPlacerEl: function(){
return CanvasView.placerEl;
},
/**
* Returns ghost element
* @return {HTMLElement}
* @private
*/
getGhostEl: function(){
return CanvasView.ghostEl;
},
/**
* Returns toolbar element
* @return {HTMLElement}
*/
getToolbarEl: function() {
return CanvasView.toolbarEl;
},
/**
* Returns resizer element
* @return {HTMLElement}
*/
getResizerEl: function() {
return CanvasView.resizerEl;
},
/**
* Returns offset viewer element
* @return {HTMLElement}
*/
getOffsetViewerEl: function() {
return CanvasView.offsetEl;
},
/**
* Returns fixed offset viewer element
* @return {HTMLElement}
*/
getFixedOffsetViewerEl: function() {
return CanvasView.fixedOffsetEl;
},
/**
* Render canvas
* */
render: function() {
return CanvasView.render().el;
},
/**
* Get frame position
* @return {Object}
* @private
*/
getOffset: function() {
var frameOff = this.offset(this.getFrameEl());
var canvasOff = this.offset(this.getElement());
return {
top: frameOff.top - canvasOff.top,
left: frameOff.left - canvasOff.left
};
},
/**
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
* @private
*/
offset: function(el){
var rect = el.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
},
/**
* Get element position relative to the canvas
* @param {HTMLElement} el
* @return {Object}
*/
getElementPos: function(el) {
return CanvasView.getElementPos(el);
},
/**
* This method comes handy when you need to attach something like toolbars
* to elements inside the canvas, dealing with all relative position,
* offsets, etc. and returning as result the object with positions which are
* viewable by the user (when the canvas is scrolled the top edge of the element
* is not viewable by the user anymore so the new top edge is the one of the canvas)
*
* The target should be visible before being passed here as invisible elements
* return empty string as width
* @param {HTMLElement} target The target in this case could be the toolbar
* @param {HTMLElement} element The element on which I'd attach the toolbar
* @param {Object} options Custom options
* @param {Boolean} options.toRight Set to true if you want the toolbar attached to the right
* @return {Object}
*/
getTargetToElementDim: function (target, element, options) {
var opts = options || {};
var canvasPos = CanvasView.getPosition();
var pos = opts.elPos || CanvasView.getElementPos(element);
var toRight = options.toRight || 0;
var targetHeight = opts.targetHeight || target.offsetHeight;
var targetWidth = opts.targetWidth || target.offsetWidth;
var eventToTrigger = opts.event || null;
var elTop = pos.top - targetHeight;
var elLeft = pos.left;
elLeft += toRight ? pos.width : 0;
elLeft = toRight ? (elLeft - targetWidth) : elLeft;
var leftPos = elLeft < canvasPos.left ? canvasPos.left : elLeft;
var topPos = elTop < canvasPos.top ? canvasPos.top : elTop;
topPos = topPos > (pos.top + pos.height) ? (pos.top + pos.height) : topPos;
var result = {
top: topPos,
left: leftPos,
elementTop: pos.top,
elementLeft: pos.left,
elementWidth: pos.width,
elementHeight: pos.height,
targetWidth: target.offsetWidth,
targetHeight: target.offsetHeight,
canvasTop: canvasPos.top,
canvasLeft: canvasPos.left,
};
// In this way I can catch data and also change the position strategy
if(eventToTrigger && c.em) {
c.em.trigger(eventToTrigger, result);
}
return result;
},
/**
* Instead of simply returning e.clientX and e.clientY this function
* calculates also the offset based on the canvas. This is helpful when you
* need to get X and Y position while moving between the editor area and
* canvas area, which is in the iframe
* @param {Event} e
* @return {Object}
*/
getMouseRelativePos: function (e, options) {
var opts = options || {};
var addTop = 0;
var addLeft = 0;
var subWinOffset = opts.subWinOffset;
var doc = e.target.ownerDocument;
var win = doc.defaultView || doc.parentWindow;
var frame = win.frameElement;
var yOffset = subWinOffset ? win.pageYOffset : 0;
var xOffset = subWinOffset ? win.pageXOffset : 0;
if (frame) {
var frameRect = frame.getBoundingClientRect();
addTop = frameRect.top || 0;
addLeft = frameRect.left || 0;
}
return {
y: e.clientY + addTop - yOffset,
x: e.clientX + addLeft - xOffset,
};
},
/**
* X and Y mouse position relative to the canvas
* @param {Event} e
* @return {Object}
*/
getMouseRelativeCanvas: function (e, options) {
var opts = options || {};
var frame = this.getFrameEl();
var body = this.getBody();
var addTop = frame.offsetTop || 0;
var addLeft = frame.offsetLeft || 0;
var yOffset = body.scrollTop || 0;
var xOffset = body.scrollLeft || 0;
return {
y: e.clientY + addTop + yOffset,
x: e.clientX + addLeft + xOffset,
};
},
/**
* Returns wrapper element
* @return {HTMLElement}
* ????
*/
getFrameWrapperEl: function(){
return CanvasView.frame.getWrapper();
},
};
};
});

27
src/canvas/model/Canvas.js

@ -1,18 +1,17 @@
define(['backbone', './Frame'],
function(Backbone, Frame){
var Backbone = require('backbone');
var Frame = require('./Frame');
return Backbone.Model.extend({
module.exports = Backbone.Model.extend({
defaults :{
frame: '',
wrapper: '',
rulers: false,
},
defaults :{
frame: '',
wrapper: '',
rulers: false,
},
initialize: function(config) {
var conf = this.conf || {};
this.set('frame', new Frame(conf.frame));
},
initialize(config) {
var conf = this.conf || {};
this.set('frame', new Frame(conf.frame));
},
});
});
});

20
src/canvas/model/Frame.js

@ -1,14 +1,12 @@
define(['backbone'],
function(Backbone){
var Backbone = require('backbone');
return Backbone.Model.extend({
module.exports = Backbone.Model.extend({
defaults :{
wrapper: '',
width: '',
height: '',
attributes: {},
},
defaults :{
wrapper: '',
width: '',
height: '',
attributes: {},
},
});
});
});

479
src/canvas/view/CanvasView.js

@ -1,269 +1,266 @@
define(['backbone','./FrameView'],
function(Backbone, FrameView) {
/**
* @class CanvasView
* */
return Backbone.View.extend({
var Backbone = require('backbone');
var FrameView = require('./FrameView');
initialize: function(o) {
_.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff');
this.config = o.config || {};
this.em = this.config.em || {};
this.ppfx = this.config.pStylePrefix || '';
this.className = this.config.stylePrefix + 'canvas';
this.listenTo(this.em, 'change:canvasOffset', this.clearOff);
this.frame = new FrameView({
model: this.model.get('frame'),
config: this.config
});
},
module.exports = Backbone.View.extend({
/**
* Update tools position
* @private
*/
onFrameScroll: function(){
var u = 'px';
var body = this.frame.el.contentDocument.body;
this.toolsEl.style.top = '-' + body.scrollTop + u;
this.toolsEl.style.left = '-' + body.scrollLeft + u;
this.em.trigger('canvasScroll');
},
initialize(o) {
_.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff');
this.config = o.config || {};
this.em = this.config.em || {};
this.ppfx = this.config.pStylePrefix || '';
this.className = this.config.stylePrefix + 'canvas';
this.listenTo(this.em, 'change:canvasOffset', this.clearOff);
this.frame = new FrameView({
model: this.model.get('frame'),
config: this.config
});
},
/**
* Insert scripts into head, it will call renderBody after all scripts loaded or failed
* @private
*/
renderScripts: function () {
var frame = this.frame;
var that = this;
/**
* Update tools position
* @private
*/
onFrameScroll() {
var u = 'px';
var body = this.frame.el.contentDocument.body;
this.toolsEl.style.top = '-' + body.scrollTop + u;
this.toolsEl.style.left = '-' + body.scrollLeft + u;
this.em.trigger('canvasScroll');
},
frame.el.onload = function () {
var scripts = that.config.scripts.slice(0), // clone
counter = 0;
/**
* Insert scripts into head, it will call renderBody after all scripts loaded or failed
* @private
*/
renderScripts() {
var frame = this.frame;
var that = this;
function appendScript(scripts) {
if (scripts.length > 0) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scripts.shift();
script.onerror = script.onload = appendScript.bind(null, scripts);
frame.el.contentDocument.head.appendChild(script);
} else {
that.renderBody();
}
frame.el.onload = () => {
var scripts = that.config.scripts.slice(0), // clone
counter = 0;
function appendScript(scripts) {
if (scripts.length > 0) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = scripts.shift();
script.onerror = script.onload = appendScript.bind(null, scripts);
frame.el.contentDocument.head.appendChild(script);
} else {
that.renderBody();
}
appendScript(scripts);
};
},
}
appendScript(scripts);
};
},
/**
* Render inside frame's body
* @private
*/
renderBody: function() {
var wrap = this.model.get('frame').get('wrapper');
var em = this.config.em;
if(wrap) {
var ppfx = this.ppfx;
var body = this.frame.$el.contents().find('body');
var cssc = em.get('CssComposer');
var conf = em.get('Config');
body.append(wrap.render()).append(cssc.render());
var protCss = conf.protectedCss;
/**
* Render inside frame's body
* @private
*/
renderBody() {
var wrap = this.model.get('frame').get('wrapper');
var em = this.config.em;
if(wrap) {
var ppfx = this.ppfx;
var body = this.frame.$el.contents().find('body');
var cssc = em.get('CssComposer');
var conf = em.get('Config');
body.append(wrap.render()).append(cssc.render());
var protCss = conf.protectedCss;
// I need all this styles to make the editor work properly
var frameCss = '* {box-sizing: border-box;} body{margin:0;height:auto;background-color:#fff} #wrapper{min-height:100%; overflow:auto}' +
'.' + ppfx + 'dashed :not([contenteditable]) > *[data-highlightable]{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' +
'.' + ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' +
'.' + ppfx + 'no-select{user-select: none; -webkit-user-select:none; -moz-user-select: none}'+
'.' + ppfx + 'freezed{opacity: 0.5; pointer-events: none}' +
'.' + ppfx + 'no-pointer{pointer-events: none}' +
'.' + ppfx + 'plh-image{background:#f5f5f5; border:none; height:50px; width:50px; display:block; outline:3px solid #ffca6f; cursor:pointer}' +
'.' + ppfx + 'grabbing{cursor: grabbing; cursor: -webkit-grabbing}' +
'* ::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1)}' +
'* ::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.2)}' +
'* ::-webkit-scrollbar {width: 10px}' +
(conf.canvasCss || '');
frameCss += protCss || '';
body.append('<style>' + frameCss + '</style>');
body.append(this.getJsContainer());
em.trigger('loaded');
this.frame.el.contentWindow.onscroll = this.onFrameScroll;
this.frame.udpateOffset();
// I need all this styles to make the editor work properly
var frameCss = '* {box-sizing: border-box;} body{margin:0;height:auto;background-color:#fff} #wrapper{min-height:100%; overflow:auto}' +
'.' + ppfx + 'dashed :not([contenteditable]) > *[data-highlightable]{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' +
'.' + ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' +
'.' + ppfx + 'no-select{user-select: none; -webkit-user-select:none; -moz-user-select: none}'+
'.' + ppfx + 'freezed{opacity: 0.5; pointer-events: none}' +
'.' + ppfx + 'no-pointer{pointer-events: none}' +
'.' + ppfx + 'plh-image{background:#f5f5f5; border:none; height:50px; width:50px; display:block; outline:3px solid #ffca6f; cursor:pointer}' +
'.' + ppfx + 'grabbing{cursor: grabbing; cursor: -webkit-grabbing}' +
'* ::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1)}' +
'* ::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.2)}' +
'* ::-webkit-scrollbar {width: 10px}' +
(conf.canvasCss || '');
frameCss += protCss || '';
body.append('<style>' + frameCss + '</style>');
body.append(this.getJsContainer());
em.trigger('loaded');
this.frame.el.contentWindow.onscroll = this.onFrameScroll;
this.frame.udpateOffset();
// When the iframe is focused the event dispatcher is not the same so
// I need to delegate all events to the parent document
var doc = document;
var fdoc = this.frame.el.contentDocument;
fdoc.addEventListener('keydown', function(e){
doc.dispatchEvent(new KeyboardEvent(e.type, e));
});
fdoc.addEventListener('keyup', function(e){
doc.dispatchEvent(new KeyboardEvent(e.type, e));
});
}
},
// When the iframe is focused the event dispatcher is not the same so
// I need to delegate all events to the parent document
var doc = document;
var fdoc = this.frame.el.contentDocument;
fdoc.addEventListener('keydown', e => {
doc.dispatchEvent(new KeyboardEvent(e.type, e));
});
fdoc.addEventListener('keyup', e => {
doc.dispatchEvent(new KeyboardEvent(e.type, e));
});
}
},
/**
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
*/
offset: function(el){
var rect = el.getBoundingClientRect();
var docBody = el.ownerDocument.body;
return {
top: rect.top + docBody.scrollTop,
left: rect.left + docBody.scrollLeft
};
},
/**
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
*/
offset(el) {
var rect = el.getBoundingClientRect();
var docBody = el.ownerDocument.body;
return {
top: rect.top + docBody.scrollTop,
left: rect.left + docBody.scrollLeft
};
},
/**
* Cleare cached offsets
* @private
*/
clearOff: function(){
this.frmOff = null;
this.cvsOff = null;
},
/**
* Cleare cached offsets
* @private
*/
clearOff() {
this.frmOff = null;
this.cvsOff = null;
},
/**
* Return frame offset
* @return {Object}
* @private
*/
getFrameOffset: function () {
if(!this.frmOff)
this.frmOff = this.offset(this.frame.el);
return this.frmOff;
},
/**
* Return frame offset
* @return {Object}
* @private
*/
getFrameOffset() {
if(!this.frmOff)
this.frmOff = this.offset(this.frame.el);
return this.frmOff;
},
/**
* Return canvas offset
* @return {Object}
* @private
*/
getCanvasOffset: function () {
if(!this.cvsOff)
this.cvsOff = this.offset(this.el);
return this.cvsOff;
},
/**
* Return canvas offset
* @return {Object}
* @private
*/
getCanvasOffset() {
if(!this.cvsOff)
this.cvsOff = this.offset(this.el);
return this.cvsOff;
},
/**
* Returns element's data info
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementPos: function(el) {
var frmOff = this.getFrameOffset();
var cvsOff = this.getCanvasOffset();
var eo = this.offset(el);
var top = eo.top + frmOff.top - cvsOff.top;
var left = eo.left + frmOff.left - cvsOff.left;
return {
top: top,
left: left,
height: el.offsetHeight,
width: el.offsetWidth
};
},
/**
* Returns element's data info
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementPos(el) {
var frmOff = this.getFrameOffset();
var cvsOff = this.getCanvasOffset();
var eo = this.offset(el);
var top = eo.top + frmOff.top - cvsOff.top;
var left = eo.left + frmOff.left - cvsOff.left;
return {
top,
left,
height: el.offsetHeight,
width: el.offsetWidth
};
},
/**
* Returns position data of the canvas element
* @return {Object} obj Position object
* @private
*/
getPosition: function() {
var bEl = this.frame.el.contentDocument.body;
var fo = this.getFrameOffset();
var co = this.getCanvasOffset();
return {
top: fo.top + bEl.scrollTop - co.top,
left: fo.left + bEl.scrollLeft - co.left
};
},
/**
* Returns position data of the canvas element
* @return {Object} obj Position object
* @private
*/
getPosition() {
var bEl = this.frame.el.contentDocument.body;
var fo = this.getFrameOffset();
var co = this.getCanvasOffset();
return {
top: fo.top + bEl.scrollTop - co.top,
left: fo.left + bEl.scrollLeft - co.left
};
},
/**
* Update javascript of a specific component passed by its View
* @param {View} view Component's View
* @private
*/
updateScript: function(view) {
if(!view.scriptContainer) {
view.scriptContainer = $('<div>');
this.getJsContainer().append(view.scriptContainer.get(0));
}
/**
* Update javascript of a specific component passed by its View
* @param {View} view Component's View
* @private
*/
updateScript(view) {
if(!view.scriptContainer) {
view.scriptContainer = $('<div>');
this.getJsContainer().append(view.scriptContainer.get(0));
}
var id = view.model.cid;
var script = view.model.get('script');
var scrStr = 'function(){' + script + '}';
scrStr = typeof script == 'function' ? script.toString() : scrStr;
var id = view.model.cid;
var script = view.model.get('script');
var scrStr = 'function(){' + script + '}';
scrStr = typeof script == 'function' ? script.toString() : scrStr;
view.el.id = id;
view.scriptContainer.html('');
view.el.id = id;
view.scriptContainer.html('');
view.scriptContainer.append('<script>' +
'var item = document.getElementById("'+id+'");' +
'(' + scrStr + '.bind(item))()</script>');
},
view.scriptContainer.append('<script>' +
'var item = document.getElementById("'+id+'");' +
'(' + scrStr + '.bind(item))()</script>');
},
/**
* Get javascript container
* @private
*/
getJsContainer: function () {
if (!this.jsContainer) {
this.jsContainer = $('<div>', {class: this.ppfx + 'js-cont'}).get(0);
}
return this.jsContainer;
},
/**
* Get javascript container
* @private
*/
getJsContainer() {
if (!this.jsContainer) {
this.jsContainer = $('<div>', {class: this.ppfx + 'js-cont'}).get(0);
}
return this.jsContainer;
},
render: function() {
this.wrapper = this.model.get('wrapper');
render() {
this.wrapper = this.model.get('wrapper');
if(this.wrapper && typeof this.wrapper.render == 'function'){
this.model.get('frame').set('wrapper', this.wrapper);
this.$el.append(this.frame.render().el);
var frame = this.frame;
if (this.config.scripts.length === 0) {
frame.el.onload = this.renderBody;
} else {
this.renderScripts(); // will call renderBody later
}
if(this.wrapper && typeof this.wrapper.render == 'function'){
this.model.get('frame').set('wrapper', this.wrapper);
this.$el.append(this.frame.render().el);
var frame = this.frame;
if (this.config.scripts.length === 0) {
frame.el.onload = this.renderBody;
} else {
this.renderScripts(); // will call renderBody later
}
var ppfx = this.ppfx;
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');
}
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());
if(rte)
toolsEl.appendChild(rte.render());
this.toolsEl = toolsEl;
this.$el.attr({class: this.className});
return this;
},
this.toolsEl = toolsEl;
this.$el.attr({class: this.className});
return this;
},
});
});

102
src/canvas/view/FrameView.js

@ -1,55 +1,51 @@
define(['backbone'],
function(Backbone) {
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
tagName: 'iframe',
attributes: {
src: 'about:blank',
allowfullscreen: 'allowfullscreen'
},
initialize(o) {
_.bindAll(this, 'udpateOffset');
this.config = o.config || {};
this.ppfx = this.config.pStylePrefix || '';
this.em = this.config.em;
this.motionsEv = 'transitionend oTransitionEnd transitionend webkitTransitionEnd';
this.listenTo(this.em, 'change:device', this.updateWidth);
},
/**
* @class CanvasView
* */
return Backbone.View.extend({
tagName: 'iframe',
attributes: {
src: 'about:blank',
allowfullscreen: 'allowfullscreen'
},
initialize: function(o) {
_.bindAll(this, 'udpateOffset');
this.config = o.config || {};
this.ppfx = this.config.pStylePrefix || '';
this.em = this.config.em;
this.motionsEv = 'transitionend oTransitionEnd transitionend webkitTransitionEnd';
this.listenTo(this.em, 'change:device', this.updateWidth);
},
/**
* Update width of the frame
* @private
*/
updateWidth: function(model){
var device = this.em.getDeviceModel();
this.el.style.width = device ? device.get('width') : '';
this.udpateOffset();
this.$el.on(this.motionsEv, this.udpateOffset);
},
udpateOffset: function(){
var offset = this.em.get('Canvas').getOffset();
this.em.set('canvasOffset', offset);
this.$el.off(this.motionsEv, this.udpateOffset);
},
getBody: function(){
this.$el.contents().find('body');
},
getWrapper: function(){
return this.$el.contents().find('body > div');
},
render: function() {
this.$el.attr({class: this.ppfx + 'frame'});
return this;
},
});
* Update width of the frame
* @private
*/
updateWidth(model) {
var device = this.em.getDeviceModel();
this.el.style.width = device ? device.get('width') : '';
this.udpateOffset();
this.$el.on(this.motionsEv, this.udpateOffset);
},
udpateOffset() {
var offset = this.em.get('Canvas').getOffset();
this.em.set('canvasOffset', offset);
this.$el.off(this.motionsEv, this.udpateOffset);
},
getBody() {
this.$el.contents().find('body');
},
getWrapper() {
return this.$el.contents().find('body > div');
},
render() {
this.$el.attr({class: this.ppfx + 'frame'});
return this;
},
});

12
src/code_manager/config/config.js

@ -1,8 +1,6 @@
define(function () {
return {
// Style prefix
stylePrefix: 'cm-',
module.exports = {
// Style prefix
stylePrefix: 'cm-',
inlineCss: false,
};
});
inlineCss: false,
};

221
src/code_manager/index.js

@ -0,0 +1,221 @@
/**
* - [addGenerator](#addgenerator)
* - [getGenerator](#getgenerator)
* - [getGenerators](#getgenerators)
* - [addViewer](#addviewer)
* - [getViewer](#getviewer)
* - [getViewers](#getviewers)
* - [updateViewer](#updateviewer)
* - [getCode](#getcode)
*
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var codeManager = editor.CodeManager;
* ```
*
* @module CodeManager
*/
module.exports = () => {
var c = {},
defaults = require('./config/config'),
gHtml = require('./model/HtmlGenerator'),
gCss = require('./model/CssGenerator'),
gJson = require('./model/JsonGenerator'),
gJs = require('./model/JsGenerator'),
eCM = require('./model/CodeMirrorEditor'),
editorView = require('./view/EditorView');
var generators = {},
defGenerators = {},
viewers = {},
defViewers = {};
return {
getConfig() {
return c;
},
config: c,
EditorView: editorView,
/**
* Name of the module
* @type {String}
* @private
*/
name: 'CodeManager',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
defGenerators.html = new gHtml();
defGenerators.css = new gCss();
defGenerators.json = new gJson();
defGenerators.js = new gJs();
defViewers.CodeMirror = new eCM();
return this;
},
/**
* Callback on load
*/
onLoad() {
this.loadDefaultGenerators().loadDefaultViewers();
},
/**
* Add new code generator to the collection
* @param {string} id Code generator ID
* @param {Object} generator Code generator wrapper
* @param {Function} generator.build Function that builds the code
* @return {this}
* @example
* codeManager.addGenerator('html7',{
* build: function(model){
* return 'myCode';
* }
* });
* */
addGenerator(id, generator) {
generators[id] = generator;
return this;
},
/**
* Get code generator by id
* @param {string} id Code generator ID
* @return {Object|null}
* @example
* var generator = codeManager.getGenerator('html7');
* generator.build = function(model){
* //extend
* };
* */
getGenerator(id) {
return generators[id] || null;
},
/**
* Returns all code generators
* @return {Array<Object>}
* */
getGenerators() {
return generators;
},
/**
* Add new code viewer
* @param {string} id Code viewer ID
* @param {Object} viewer Code viewer wrapper
* @param {Function} viewer.init Set element on which viewer will be displayed
* @param {Function} viewer.setContent Set content to the viewer
* @return {this}
* @example
* codeManager.addViewer('ace',{
* init: function(el){
* var ace = require('ace-editor');
* this.editor = ace.edit(el.id);
* },
* setContent: function(code){
* this.editor.setValue(code);
* }
* });
* */
addViewer(id, viewer) {
viewers[id] = viewer;
return this;
},
/**
* Get code viewer by id
* @param {string} id Code viewer ID
* @return {Object|null}
* @example
* var viewer = codeManager.getViewer('ace');
* */
getViewer(id) {
return viewers[id] || null;
},
/**
* Returns all code viewers
* @return {Array<Object>}
* */
getViewers() {
return viewers;
},
/**
* Update code viewer content
* @param {Object} viewer Viewer instance
* @param {string} code Code string
* @example
* var AceViewer = codeManager.getViewer('ace');
* // ...
* var viewer = AceViewer.init(el);
* // ...
* codeManager.updateViewer(AceViewer, 'code');
* */
updateViewer(viewer, code) {
viewer.setContent(code);
},
/**
* Get code from model
* @param {Object} model Any kind of model that will be passed to the build method of generator
* @param {string} genId Code generator id
* @param {Object} [opt] Options
* @return {string}
* @example
* var codeStr = codeManager.getCode(model, 'html');
* */
getCode(model, genId, opt) {
var generator = this.getGenerator(genId);
return generator ? generator.build(model, opt) : '';
},
/**
* Load default code generators
* @return {this}
* @private
* */
loadDefaultGenerators() {
for (var id in defGenerators)
this.addGenerator(id, defGenerators[id]);
return this;
},
/**
* Load default code viewers
* @return {this}
* @private
* */
loadDefaultViewers() {
for (var id in defViewers)
this.addViewer(id, defViewers[id]);
return this;
},
};
};

227
src/code_manager/main.js

@ -1,227 +0,0 @@
/**
* - [addGenerator](#addgenerator)
* - [getGenerator](#getgenerator)
* - [getGenerators](#getgenerators)
* - [addViewer](#addviewer)
* - [getViewer](#getviewer)
* - [getViewers](#getviewers)
* - [updateViewer](#updateviewer)
* - [getCode](#getcode)
*
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var codeManager = editor.CodeManager;
* ```
*
* @module CodeManager
*/
define(function(require) {
var CodeManager = function() {
var c = {},
defaults = require('./config/config'),
gHtml = require('./model/HtmlGenerator'),
gCss = require('./model/CssGenerator'),
gJson = require('./model/JsonGenerator'),
gJs = require('./model/JsGenerator'),
eCM = require('./model/CodeMirrorEditor'),
editorView = require('./view/EditorView');
var generators = {},
defGenerators = {},
viewers = {},
defViewers = {};
return {
getConfig: function() {
return c;
},
config: c,
EditorView: editorView,
/**
* Name of the module
* @type {String}
* @private
*/
name: 'CodeManager',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
*/
init: function(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
defGenerators.html = new gHtml();
defGenerators.css = new gCss();
defGenerators.json = new gJson();
defGenerators.js = new gJs();
defViewers.CodeMirror = new eCM();
return this;
},
/**
* Callback on load
*/
onLoad: function(){
this.loadDefaultGenerators().loadDefaultViewers();
},
/**
* Add new code generator to the collection
* @param {string} id Code generator ID
* @param {Object} generator Code generator wrapper
* @param {Function} generator.build Function that builds the code
* @return {this}
* @example
* codeManager.addGenerator('html7',{
* build: function(model){
* return 'myCode';
* }
* });
* */
addGenerator: function(id, generator) {
generators[id] = generator;
return this;
},
/**
* Get code generator by id
* @param {string} id Code generator ID
* @return {Object|null}
* @example
* var generator = codeManager.getGenerator('html7');
* generator.build = function(model){
* //extend
* };
* */
getGenerator: function(id) {
return generators[id] || null;
},
/**
* Returns all code generators
* @return {Array<Object>}
* */
getGenerators: function() {
return generators;
},
/**
* Add new code viewer
* @param {string} id Code viewer ID
* @param {Object} viewer Code viewer wrapper
* @param {Function} viewer.init Set element on which viewer will be displayed
* @param {Function} viewer.setContent Set content to the viewer
* @return {this}
* @example
* codeManager.addViewer('ace',{
* init: function(el){
* var ace = require('ace-editor');
* this.editor = ace.edit(el.id);
* },
* setContent: function(code){
* this.editor.setValue(code);
* }
* });
* */
addViewer: function(id, viewer) {
viewers[id] = viewer;
return this;
},
/**
* Get code viewer by id
* @param {string} id Code viewer ID
* @return {Object|null}
* @example
* var viewer = codeManager.getViewer('ace');
* */
getViewer: function(id) {
return viewers[id] || null;
},
/**
* Returns all code viewers
* @return {Array<Object>}
* */
getViewers: function() {
return viewers;
},
/**
* Update code viewer content
* @param {Object} viewer Viewer instance
* @param {string} code Code string
* @example
* var AceViewer = codeManager.getViewer('ace');
* // ...
* var viewer = AceViewer.init(el);
* // ...
* codeManager.updateViewer(AceViewer, 'code');
* */
updateViewer: function(viewer, code) {
viewer.setContent(code);
},
/**
* Get code from model
* @param {Object} model Any kind of model that will be passed to the build method of generator
* @param {string} genId Code generator id
* @param {Object} [opt] Options
* @return {string}
* @example
* var codeStr = codeManager.getCode(model, 'html');
* */
getCode: function(model, genId, opt) {
var generator = this.getGenerator(genId);
return generator ? generator.build(model, opt) : '';
},
/**
* Load default code generators
* @return {this}
* @private
* */
loadDefaultGenerators: function() {
for (var id in defGenerators)
this.addGenerator(id, defGenerators[id]);
return this;
},
/**
* Load default code viewers
* @return {this}
* @private
* */
loadDefaultViewers: function() {
for (var id in defViewers)
this.addViewer(id, defViewers[id]);
return this;
},
};
};
return CodeManager;
});

85
src/code_manager/model/CodeMirrorEditor.js

@ -1,51 +1,44 @@
define(['backbone',
'codemirror/lib/codemirror',
'codemirror/mode/htmlmixed/htmlmixed',
'codemirror/mode/css/css',
'formatting'
],
function(Backbone, CodeMirror, htmlMode, cssMode, formatting) {
/**
* @class CodeViewer
* */
return Backbone.Model.extend({
var Backbone = require('backbone');
var CodeMirror = require('codemirror/lib/codemirror');
var htmlMode = require('codemirror/mode/htmlmixed/htmlmixed');
var cssMode = require('codemirror/mode/css/css');
var formatting = require('codemirror-formatting');
defaults: {
input : '',
label : '',
codeName : '',
theme : '',
readOnly : true,
lineNumbers : true,
},
module.exports = Backbone.Model.extend({
/** @inheritdoc */
init: function(el)
{
this.editor = CodeMirror.fromTextArea(el, {
dragDrop: false,
lineWrapping: true,
lineNumbers: this.get('lineNumbers'),
readOnly: this.get('readOnly'),
mode: this.get('codeName'),
theme: this.get('theme'),
});
defaults: {
input : '',
label : '',
codeName : '',
theme : '',
readOnly : true,
lineNumbers : true,
},
return this;
},
/** @inheritdoc */
init(el) {
this.editor = CodeMirror.fromTextArea(el, {
dragDrop: false,
lineWrapping: true,
lineNumbers: this.get('lineNumbers'),
readOnly: this.get('readOnly'),
mode: this.get('codeName'),
theme: this.get('theme'),
});
/** @inheritdoc */
setContent : function(v)
{
if(!this.editor)
return;
this.editor.setValue(v);
if(this.editor.autoFormatRange){
CodeMirror.commands.selectAll(this.editor);
this.editor.autoFormatRange(this.editor.getCursor(true), this.editor.getCursor(false) );
CodeMirror.commands.goDocStart(this.editor);
}
},
return this;
},
});
});
/** @inheritdoc */
setContent(v) {
if(!this.editor)
return;
this.editor.setValue(v);
if(this.editor.autoFormatRange){
CodeMirror.commands.selectAll(this.editor);
this.editor.autoFormatRange(this.editor.getCursor(true), this.editor.getCursor(false) );
CodeMirror.commands.goDocStart(this.editor);
}
},
});

280
src/code_manager/model/CssGenerator.js

@ -1,151 +1,147 @@
define(['backbone'],
function (Backbone) {
/**
* @class CssGenerator
* */
return Backbone.Model.extend({
initialize: function() {
this.compCls = [];
},
/**
* Get CSS from component
* @param {Model} model
* @return {String}
*/
buildFromModel: function (model) {
var code = '';
var style = model.get('style');
var classes = model.get('classes');
// Let's know what classes I've found
if(classes) {
classes.each(function(model){
this.compCls.push(model.get('name'));
}, this);
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
initialize() {
this.compCls = [];
},
/**
* Get CSS from component
* @param {Model} model
* @return {String}
*/
buildFromModel(model) {
var code = '';
var style = model.get('style');
var classes = model.get('classes');
// Let's know what classes I've found
if(classes) {
classes.each(function(model){
this.compCls.push(model.get('name'));
}, this);
}
if(style && Object.keys(style).length !== 0) {
code += '#' + model.cid + '{';
for(var prop in style){
if(style.hasOwnProperty(prop))
code += prop + ':' + style[prop] + ';';
}
code += '}';
}
return code;
},
/**
* Get CSS from components
* @param {Model} model
* @return {String}
*/
buildFromComp(model) {
var coll = model.get('components') || model,
code = '';
coll.each(function(m) {
var cln = m.get('components');
code += this.buildFromModel(m);
if(cln.length){
code += this.buildFromComp(cln);
}
}, this);
return code;
},
/** @inheritdoc */
build(model, cssc) {
this.compCls = [];
var code = this.buildFromModel(model);
code += this.buildFromComp(model);
var compCls = this.compCls;
if(cssc){
var rules = cssc.getAll();
var mediaRules = {};
rules.each(function(rule) {
var width = rule.get('mediaText');
// If width setted will render it later
if(width){
var mRule = mediaRules[width];
if(mRule)
mRule.push(rule);
else
mediaRules[width] = [rule];
return;
}
if(style && Object.keys(style).length !== 0) {
code += '#' + model.cid + '{';
for(var prop in style){
if(style.hasOwnProperty(prop))
code += prop + ':' + style[prop] + ';';
}
code += '}';
}
code += this.buildFromRule(rule);
}, this);
return code;
},
/**
* Get CSS from components
* @param {Model} model
* @return {String}
*/
buildFromComp: function(model) {
var coll = model.get('components') || model,
code = '';
coll.each(function(m) {
var cln = m.get('components');
code += this.buildFromModel(m);
if(cln.length){
code += this.buildFromComp(cln);
}
}, this);
return code;
},
/** @inheritdoc */
build: function(model, cssc) {
this.compCls = [];
var code = this.buildFromModel(model);
code += this.buildFromComp(model);
var compCls = this.compCls;
if(cssc){
var rules = cssc.getAll();
var mediaRules = {};
rules.each(function(rule) {
var width = rule.get('mediaText');
// If width setted will render it later
if(width){
var mRule = mediaRules[width];
if(mRule)
mRule.push(rule);
else
mediaRules[width] = [rule];
return;
}
code += this.buildFromRule(rule);
}, this);
// Get media rules
for (var ruleW in mediaRules) {
var meRules = mediaRules[ruleW];
var ruleC = '';
for(var i = 0, len = meRules.length; i < len; i++){
ruleC += this.buildFromRule(meRules[i]);
}
if (ruleC) {
code += '@media ' + ruleW + '{' + ruleC + '}';
}
}
// Get media rules
for (var ruleW in mediaRules) {
var meRules = mediaRules[ruleW];
var ruleC = '';
for(var i = 0, len = meRules.length; i < len; i++){
ruleC += this.buildFromRule(meRules[i]);
}
if (ruleC) {
code += '@media ' + ruleW + '{' + ruleC + '}';
}
return code;
},
/**
* Get CSS from the rule model
* @param {Model} rule
* @return {string} CSS string
*/
buildFromRule: function(rule) {
var result = '';
var selectorsAdd = rule.get('selectorsAdd');
var selectors = rule.get('selectors');
var ruleStyle = rule.get('style');
var state = rule.get('state');
var strSel = '';
var found = 0;
var compCls = this.compCls;
// Get string of selectors
selectors.each(function(selector){
strSel += '.' + selector.get('name');
if(compCls.indexOf(selector.get('name')) > -1)
found = 1;
});
// With 'found' will skip rules which selectors are not found in
// canvas components.
if ((strSel && found) || selectorsAdd) {
strSel += state ? ':' + state : '';
strSel += selectorsAdd ? (strSel ? ', ' : '') + selectorsAdd : '';
var strStyle = '';
// Get string of style properties
if(ruleStyle && Object.keys(ruleStyle).length !== 0){
for(var prop2 in ruleStyle){
if(ruleStyle.hasOwnProperty(prop2))
strStyle += prop2 + ':' + ruleStyle[prop2] + ';';
}
}
if(strStyle)
result += strSel + '{' + strStyle + '}';
}
}
return code;
},
/**
* Get CSS from the rule model
* @param {Model} rule
* @return {string} CSS string
*/
buildFromRule(rule) {
var result = '';
var selectorsAdd = rule.get('selectorsAdd');
var selectors = rule.get('selectors');
var ruleStyle = rule.get('style');
var state = rule.get('state');
var strSel = '';
var found = 0;
var compCls = this.compCls;
// Get string of selectors
selectors.each(selector => {
strSel += '.' + selector.get('name');
if(compCls.indexOf(selector.get('name')) > -1)
found = 1;
});
// With 'found' will skip rules which selectors are not found in
// canvas components.
if ((strSel && found) || selectorsAdd) {
strSel += state ? ':' + state : '';
strSel += selectorsAdd ? (strSel ? ', ' : '') + selectorsAdd : '';
var strStyle = '';
// Get string of style properties
if(ruleStyle && Object.keys(ruleStyle).length !== 0){
for(var prop2 in ruleStyle){
if(ruleStyle.hasOwnProperty(prop2))
strStyle += prop2 + ':' + ruleStyle[prop2] + ';';
}
}
return result;
},
if(strStyle)
result += strSel + '{' + strStyle + '}';
}
return result;
},
});
});

32
src/code_manager/model/HtmlGenerator.js

@ -1,23 +1,19 @@
define(['backbone'],
function (Backbone) {
/**
* @class HtmlGenerator
* */
return Backbone.Model.extend({
var Backbone = require('backbone');
/** @inheritdoc */
build: function(model, cssc){
var coll = model.get('components') || model,
code = '';
module.exports = Backbone.Model.extend({
coll.each(function(m){
code += m.toHTML({
cssc: cssc
});
}, this);
/** @inheritdoc */
build(model, cssc) {
var coll = model.get('components') || model,
code = '';
return code;
},
coll.each(m => {
code += m.toHTML({
cssc
});
}, this);
return code;
},
});
});

103
src/code_manager/model/JsGenerator.js

@ -1,62 +1,61 @@
define(['backbone'], function (Backbone) {
return Backbone.Model.extend({
mapModel: function (model) {
var code = '';
var script = model.get('script');
var type = model.get('type');
var comps = model.get('components');
var id = model.cid;
if (script) {
// If the component has scripts we need to expose his ID
var attr = model.get('attributes');
attr = _.extend({}, attr, {id: id});
model.set('attributes', attr);
var scrStr = 'function(){' + script + '}';
scrStr = typeof script == 'function' ? script.toString() : scrStr;
// If the script was updated, I'll put its code in a separate container
if (model.get('scriptUpdated')) {
this.mapJs[type+'-'+id] = {ids: [id], code: scrStr};
} else {
var mapType = this.mapJs[type];
if(mapType) {
mapType.ids.push(id);
} else {
this.mapJs[type] = {ids: [id], code: scrStr};
}
}
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
mapModel(model) {
var code = '';
var script = model.get('script');
var type = model.get('type');
var comps = model.get('components');
var id = model.cid;
if (script) {
// If the component has scripts we need to expose his ID
var attr = model.get('attributes');
attr = _.extend({}, attr, {id});
model.set('attributes', attr);
var scrStr = 'function(){' + script + '}';
scrStr = typeof script == 'function' ? script.toString() : scrStr;
// If the script was updated, I'll put its code in a separate container
if (model.get('scriptUpdated')) {
this.mapJs[type+'-'+id] = {ids: [id], code: scrStr};
} else {
var mapType = this.mapJs[type];
if(mapType) {
mapType.ids.push(id);
} else {
this.mapJs[type] = {ids: [id], code: scrStr};
}
}
}
comps.each(function(model) {
code += this.mapModel(model);
}, this);
comps.each(function(model) {
code += this.mapModel(model);
}, this);
return code;
},
return code;
},
build: function(model) {
this.mapJs = {};
this.mapModel(model);
build(model) {
this.mapJs = {};
this.mapModel(model);
var code = '';
var code = '';
for(var type in this.mapJs) {
var mapType = this.mapJs[type];
var ids = '#' + mapType.ids.join(', #');
code += 'var items = document.querySelectorAll("'+ids+'");' +
'for (var i = 0, len = items.length; i < len; i++) {'+
'(' + mapType.code + '.bind(items[i]))();' +
'}';
}
for(var type in this.mapJs) {
var mapType = this.mapJs[type];
var ids = '#' + mapType.ids.join(', #');
code += 'var items = document.querySelectorAll("'+ids+'");' +
'for (var i = 0, len = items.length; i < len; i++) {'+
'(' + mapType.code + '.bind(items[i]))();' +
'}';
}
return code;
},
return code;
},
});
});

64
src/code_manager/model/JsonGenerator.js

@ -1,40 +1,36 @@
define(['backbone'],
function (Backbone) {
/**
* @class JsonGenerator
* */
return Backbone.Model.extend({
var Backbone = require('backbone');
/** @inheritdoc */
build: function(model) {
var json = model.toJSON();
this.beforeEach(json);
module.exports = Backbone.Model.extend({
_.each(json,function(v, attr){
var obj = json[attr];
if(obj instanceof Backbone.Model){
json[attr] = this.build(obj);
}else if(obj instanceof Backbone.Collection){
var coll = obj;
json[attr] = [];
if(coll.length){
coll.each(function (el, index) {
json[attr][index] = this.build(el);
}, this);
}
}
}, this);
/** @inheritdoc */
build(model) {
var json = model.toJSON();
this.beforeEach(json);
return json;
},
_.each(json,function(v, attr){
var obj = json[attr];
if(obj instanceof Backbone.Model){
json[attr] = this.build(obj);
}else if(obj instanceof Backbone.Collection){
var coll = obj;
json[attr] = [];
if(coll.length){
coll.each(function (el, index) {
json[attr][index] = this.build(el);
}, this);
}
}
}, this);
/**
* Execute on each object
* @param {Object} obj
*/
beforeEach: function(obj) {
delete obj.status;
}
return json;
},
/**
* Execute on each object
* @param {Object} obj
*/
beforeEach(obj) {
delete obj.status;
}
});
});

4
src/code_manager/template/editor.html

@ -1,4 +0,0 @@
<div class="<%= pfx %>editor" id="<%= pfx %><%= codeName %>">
<div id="<%= pfx %>title"><%= label %></div>
<div id="<%= pfx %>code"></div>
</div>

48
src/code_manager/view/EditorView.js

@ -1,25 +1,25 @@
define(['backbone', 'text!./../template/editor.html'],
function (Backbone, vTemplate) {
/**
* @class EditorView
* */
return Backbone.View.extend({
template: _.template(vTemplate),
initialize: function(o){
this.config = o.config || {};
this.pfx = this.config.stylePrefix;
},
render : function(){
var obj = this.model.toJSON();
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'));
return this;
},
});
var Backbone = require('backbone');
module.exports = Backbone.View.extend({
template: _.template(`
<div class="<%= pfx %>editor" id="<%= pfx %><%= codeName %>">
<div id="<%= pfx %>title"><%= label %></div>
<div id="<%= pfx %>code"></div>
</div>`),
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix;
},
render() {
var obj = this.model.toJSON();
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'));
return this;
},
});

32
src/commands/config/config.js

@ -1,25 +1,23 @@
define(function () {
return {
module.exports = {
ESCAPE_KEY : 27,
ESCAPE_KEY: 27,
stylePrefix : 'com-',
stylePrefix: 'com-',
defaults : [],
defaults: [],
// Editor model
em : null,
// Editor model
em: null,
// If true center new first-level components
firstCentered : true,
// If true center new first-level components
firstCentered: true,
// If true the new component will created with 'height', else 'min-height'
newFixedH : false,
// If true the new component will created with 'height', else 'min-height'
newFixedH: false,
// Minimum height (in px) of new component
minComponentH : 50,
// Minimum height (in px) of new component
minComponentH: 50,
// Minimum width (in px) of component on creation
minComponentW : 50,
};
});
// Minimum width (in px) of component on creation
minComponentW: 50,
};

236
src/commands/index.js

@ -0,0 +1,236 @@
/**
*
* * [add](#add)
* * [get](#get)
* * [has](#has)
*
* You can init the editor with all necessary commands via configuration
*
* ```js
* var editor = grapesjs.init({
* ...
* commands: {...} // Check below for the properties
* ...
* });
* ```
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var commands = editor.Commands;
* ```
*
* @module Commands
* @param {Object} config Configurations
* @param {Array<Object>} [config.defaults=[]] Array of possible commands
* @example
* ...
* commands: {
* defaults: [{
* id: 'helloWorld',
* run: function(editor, sender){
* alert('Hello world!');
* },
* stop: function(editor, sender){
* alert('Stop!');
* },
* }],
* },
* ...
*/
module.exports = () => {
var c = {},
commands = {},
defaultCommands = {},
defaults = require('./config/config'),
AbsCommands = require('./view/CommandAbstract');
// Need it here as it would be used below
var add = function(id, obj){
delete obj.initialize;
commands[id] = AbsCommands.extend(obj);
return this;
};
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'Commands',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
// Load commands passed via configuration
for( var k in c.defaults) {
var obj = c.defaults[k];
if(obj.id)
this.add(obj.id, obj);
}
defaultCommands['select-comp'] = require('./view/SelectComponent');
defaultCommands['create-comp'] = require('./view/CreateComponent');
defaultCommands['delete-comp'] = require('./view/DeleteComponent');
defaultCommands['image-comp'] = require('./view/ImageComponent');
defaultCommands['move-comp'] = require('./view/MoveComponent');
defaultCommands['text-comp'] = require('./view/TextComponent');
defaultCommands['insert-custom'] = require('./view/InsertCustom');
defaultCommands['export-template'] = require('./view/ExportTemplate');
defaultCommands['sw-visibility'] = require('./view/SwitchVisibility');
defaultCommands['open-layers'] = require('./view/OpenLayers');
defaultCommands['open-sm'] = require('./view/OpenStyleManager');
defaultCommands['open-tm'] = require('./view/OpenTraitManager');
defaultCommands['open-blocks'] = require('./view/OpenBlocks');
defaultCommands['open-assets'] = require('./view/OpenAssets');
defaultCommands['show-offset'] = require('./view/ShowOffset');
defaultCommands.fullscreen = require('./view/Fullscreen');
defaultCommands.preview = require('./view/Preview');
defaultCommands.resize = require('./view/Resize');
defaultCommands['tlb-delete'] = {
run(ed) {
var sel = ed.getSelected();
if(!sel || !sel.get('removable')) {
console.warn('The element is not removable');
return;
}
sel.set('status', '');
sel.destroy();
ed.trigger('component:update', sel);
ed.editor.set('selectedComponent', null);
},
};
defaultCommands['tlb-clone'] = {
run(ed) {
var sel = ed.getSelected();
if(!sel || !sel.get('copyable')) {
console.warn('The element is not clonable');
return;
}
var collection = sel.collection;
var index = collection.indexOf(sel);
collection.add(sel.clone(), {at: index + 1});
ed.trigger('component:update', sel);
},
};
defaultCommands['tlb-move'] = {
run(ed) {
var sel = ed.getSelected();
if(!sel || !sel.get('draggable')) {
console.warn('The element is not draggable');
return;
}
var toolbarEl = ed.Canvas.getToolbarEl();
var cmdMove = ed.Commands.get('move-comp');
cmdMove.onEndMoveFromModel = () => {
ed.editor.runDefault();
ed.editor.set('selectedComponent', sel);
ed.trigger('component:update', sel);
};
ed.editor.stopDefault();
cmdMove.initSorterFromModel(sel);
sel.set('status', 'selected');
toolbarEl.style.display = 'none';
},
};
if(c.em)
c.model = c.em.get('Canvas');
return this;
},
/**
* On load callback
* @private
*/
onLoad() {
this.loadDefaultCommands();
},
/**
* Add new command to the collection
* @param {string} id Command's ID
* @param {Object} command Object representing you command. Methods `run` and `stop` are required
* @return {this}
* @example
* commands.add('myCommand', {
* run: function(editor, sender){
* alert('Hello world!');
* },
* stop: function(editor, sender){
* },
* });
* */
add,
/**
* Get command by ID
* @param {string} id Command's ID
* @return {Object} Object representing the command
* @example
* var myCommand = commands.get('myCommand');
* myCommand.run();
* */
get(id) {
var el = commands[id];
if(typeof el == 'function'){
el = new el(c);
commands[id] = el;
}
return el;
},
/**
* Check if command exists
* @param {string} id Command's ID
* @return {Boolean}
* */
has(id) {
return !!commands[id];
},
/**
* Load default commands
* @return {this}
* @private
* */
loadDefaultCommands() {
for (var id in defaultCommands) {
this.add(id, defaultCommands[id]);
}
return this;
},
};
};

238
src/commands/main.js

@ -1,238 +0,0 @@
/**
*
* * [add](#add)
* * [get](#get)
* * [has](#has)
*
* You can init the editor with all necessary commands via configuration
*
* ```js
* var editor = grapesjs.init({
* ...
* commands: {...} // Check below for the properties
* ...
* });
* ```
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var commands = editor.Commands;
* ```
*
* @module Commands
* @param {Object} config Configurations
* @param {Array<Object>} [config.defaults=[]] Array of possible commands
* @example
* ...
* commands: {
* defaults: [{
* id: 'helloWorld',
* run: function(editor, sender){
* alert('Hello world!');
* },
* stop: function(editor, sender){
* alert('Stop!');
* },
* }],
* },
* ...
*/
define(function(require) {
return function() {
var c = {},
commands = {},
defaultCommands = {},
defaults = require('./config/config'),
AbsCommands = require('./view/CommandAbstract');
// Need it here as it would be used below
var add = function(id, obj){
delete obj.initialize;
commands[id] = AbsCommands.extend(obj);
return this;
};
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'Commands',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
init: function(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
// Load commands passed via configuration
for( var k in c.defaults) {
var obj = c.defaults[k];
if(obj.id)
this.add(obj.id, obj);
}
defaultCommands['select-comp'] = require('./view/SelectComponent');
defaultCommands['create-comp'] = require('./view/CreateComponent');
defaultCommands['delete-comp'] = require('./view/DeleteComponent');
defaultCommands['image-comp'] = require('./view/ImageComponent');
defaultCommands['move-comp'] = require('./view/MoveComponent');
defaultCommands['text-comp'] = require('./view/TextComponent');
defaultCommands['insert-custom'] = require('./view/InsertCustom');
defaultCommands['export-template'] = require('./view/ExportTemplate');
defaultCommands['sw-visibility'] = require('./view/SwitchVisibility');
defaultCommands['open-layers'] = require('./view/OpenLayers');
defaultCommands['open-sm'] = require('./view/OpenStyleManager');
defaultCommands['open-tm'] = require('./view/OpenTraitManager');
defaultCommands['open-blocks'] = require('./view/OpenBlocks');
defaultCommands['open-assets'] = require('./view/OpenAssets');
defaultCommands['show-offset'] = require('./view/ShowOffset');
defaultCommands.fullscreen = require('./view/Fullscreen');
defaultCommands.preview = require('./view/Preview');
defaultCommands.resize = require('./view/Resize');
defaultCommands['tlb-delete'] = {
run: function(ed) {
var sel = ed.getSelected();
if(!sel || !sel.get('removable')) {
console.warn('The element is not removable');
return;
}
sel.set('status', '');
sel.destroy();
ed.trigger('component:update', sel);
ed.editor.set('selectedComponent', null);
},
};
defaultCommands['tlb-clone'] = {
run: function(ed) {
var sel = ed.getSelected();
if(!sel || !sel.get('copyable')) {
console.warn('The element is not clonable');
return;
}
var collection = sel.collection;
var index = collection.indexOf(sel);
collection.add(sel.clone(), {at: index + 1});
ed.trigger('component:update', sel);
},
};
defaultCommands['tlb-move'] = {
run: function(ed){
var sel = ed.getSelected();
if(!sel || !sel.get('draggable')) {
console.warn('The element is not draggable');
return;
}
var toolbarEl = ed.Canvas.getToolbarEl();
var cmdMove = ed.Commands.get('move-comp');
cmdMove.onEndMoveFromModel = function() {
ed.editor.runDefault();
ed.editor.set('selectedComponent', sel);
ed.trigger('component:update', sel);
};
ed.editor.stopDefault();
cmdMove.initSorterFromModel(sel);
sel.set('status', 'selected');
toolbarEl.style.display = 'none';
},
};
if(c.em)
c.model = c.em.get('Canvas');
return this;
},
/**
* On load callback
* @private
*/
onLoad: function() {
this.loadDefaultCommands();
},
/**
* Add new command to the collection
* @param {string} id Command's ID
* @param {Object} command Object representing you command. Methods `run` and `stop` are required
* @return {this}
* @example
* commands.add('myCommand', {
* run: function(editor, sender){
* alert('Hello world!');
* },
* stop: function(editor, sender){
* },
* });
* */
add: add,
/**
* Get command by ID
* @param {string} id Command's ID
* @return {Object} Object representing the command
* @example
* var myCommand = commands.get('myCommand');
* myCommand.run();
* */
get: function(id) {
var el = commands[id];
if(typeof el == 'function'){
el = new el(c);
commands[id] = el;
}
return el;
},
/**
* Check if command exists
* @param {string} id Command's ID
* @return {Boolean}
* */
has: function(id) {
return !!commands[id];
},
/**
* Load default commands
* @return {this}
* @private
* */
loadDefaultCommands: function(){
for (var id in defaultCommands) {
this.add(id, defaultCommands[id]);
}
return this;
},
};
};
});

20
src/commands/model/Command.js

@ -1,13 +1,9 @@
define([ 'backbone'],
function (Backbone) {
/**
* @class Command
* */
return Backbone.Model.extend({
defaults :{
id : '',
}
});
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
defaults :{
id: '',
}
});

17
src/commands/model/Commands.js

@ -1,11 +1,8 @@
define([ 'backbone','./Command'],
function (Backbone, Command) {
/**
* @class Commands
* */
return Backbone.Collection.extend({
model: Command,
});
var Backbone = require('backbone');
var Command = require('./Command');
module.exports = Backbone.Collection.extend({
model: Command,
});

227
src/commands/view/CommandAbstract.js

@ -1,116 +1,111 @@
define(['backbone'],
function(Backbone) {
/**
* @class CommandAbstract
* @private
* */
return Backbone.View.extend({
/**
* Initialize method that can't be removed
* @param {Object} o Options
* @private
* */
initialize: function(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: function(e){},
/**
* Returns canval element
* @return {HTMLElement}
*/
getCanvas: function(){
return this.canvas.getElement();
},
/**
* Get canvas body element
* @return {HTMLElement}
*/
getCanvasBody: function(){
return this.canvas.getBody();
},
/**
* Get canvas wrapper element
* @return {HTMLElement}
*/
getCanvasWrapper: function(){
return this.canvas.getWrapperEl();
},
/**
* Get canvas wrapper element
* @return {HTMLElement}
*/
getCanvasTools: function(){
return this.canvas.getToolsEl();
},
/**
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
*/
offset: function(el){
var rect = el.getBoundingClientRect();
return {
top: rect.top + el.ownerDocument.body.scrollTop,
left: rect.left + el.ownerDocument.body.scrollLeft
};
},
/**
* Callback triggered after initialize
* @param {Object} o Options
* @private
* */
init: function(o){},
/**
* Method that run command
* @param {Object} em Editor model
* @param {Object} sender Button sender
* @private
* */
run: function(em, sender) {},
/**
* Method that stop command
* @param {Object} em Editor model
* @param {Object} sender Button sender
* @private
* */
stop: function(em, sender) {},
});
});
var Backbone = require('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();
},
/**
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
*/
offset(el) {
var rect = el.getBoundingClientRect();
return {
top: rect.top + el.ownerDocument.body.scrollTop,
left: rect.left + el.ownerDocument.body.scrollLeft
};
},
/**
* 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) {},
});

461
src/commands/view/CreateComponent.js

@ -1,231 +1,230 @@
define(['backbone','./SelectPosition'],
function(Backbone, SelectPosition) {
return _.extend({}, SelectPosition, {
init: function(opt) {
_.bindAll(this,'startDraw','draw','endDraw','rollback');
this.config = opt || {};
this.hType = this.config.newFixedH ? 'height' : 'min-height';
this.allowDraw = 1;
},
/**
* Start with enabling to select position and listening to start drawning
* @private
* */
enable: function() {
SelectPosition.enable.apply(this, arguments);
this.$wr.css('cursor','crosshair');
if(this.allowDraw)
this.$wr.on('mousedown', this.startDraw);
this.ghost = this.canvas.getGhostEl();
},
/**
* Start drawing component
* @param {Object} e Event
* @private
* */
startDraw : function(e) {
e.preventDefault();
this.stopSelectPosition();
this.ghost.style.display = 'block';
this.frameOff = this.getOffsetDim();
this.startPos = {
top : e.pageY + this.frameOff.top,
left: e.pageX + this.frameOff.left
};
this.isDragged = false;
this.tempComponent = {style: {}};
this.beforeDraw(this.tempComponent);
this.updateSize(this.startPos.top, this.startPos.left, 0, 0);
this.toggleEvents(1);
},
/**
* Enable/Disable events
* @param {Boolean} enable
*/
toggleEvents: function(enable) {
var method = enable ? 'on' : 'off';
this.$wr[method]('mousemove', this.draw);
this.$wr[method]('mouseup', this.endDraw);
this.$canvas[method]('mousemove', this.draw);
$(document)[method]('mouseup', this.endDraw);
$(document)[method]('keypress', this.rollback);
},
/**
* While drawing the component
* @param {Object} e Event
* @private
* */
draw: function(e) {
this.isDragged = true;
this.updateComponentSize(e);
},
/**
* End drawing component
* @param {Object} e Event
* @private
* */
endDraw : function(e) {
this.toggleEvents();
var model = {};
// Only if the mouse was moved
if(this.isDragged){
this.updateComponentSize(e);
this.setRequirements(this.tempComponent);
var lp = this.sorter.lastPos;
model = this.create(this.sorter.target, this.tempComponent, lp.index, lp.method);
this.sorter.prevTarget = null;
}
this.ghost.style.display = 'none';
this.startSelectPosition();
this.afterDraw(model);
},
/**
* Create new component inside the target
* @param {Object} target Tha target collection
* @param {Object} component New component to create
* @param {number} index Index inside the collection, 0 if no children inside
* @param {string} method Before or after of the children
* @param {Object} opts Options
*/
create: function(target, component, index, method, opts) {
index = method === 'after' ? index + 1 : index;
var opt = opts || {};
var $trg = $(target);
var trgModel = $trg.data('model');
var trgCollection = $trg.data('collection');
var droppable = trgModel ? trgModel.get('droppable') : 1;
opt.at = index;
if(trgCollection && droppable)
return trgCollection.add(component, opt);
else
console.warn("Invalid target position");
},
/**
* Check and set basic requirements for the component
* @param {Object} component New component to be created
* @return {Object} Component updated
* @private
* */
setRequirements: function(component) {
var c = this.config;
var compStl = component.style;
// Check min width
if(compStl.width.replace(/\D/g,'') < c.minComponentW)
compStl.width = c.minComponentW +'px';
// Check min height
if(compStl[this.hType].replace(/\D/g,'') < c.minComponentH)
compStl[this.hType] = c.minComponentH +'px';
// Set overflow in case of fixed height
if(c.newFixedH)
compStl.overflow = 'auto';
if(!this.absoluteMode){
delete compStl.left;
delete compStl.top;
}else
compStl.position = 'absolute';
var lp = this.sorter.lastPos;
if(this.nearFloat(lp.index, lp.method, this.sorter.lastDims))
compStl.float = 'left';
if(this.config.firstCentered &&
this.getCanvasWrapper() == this.sorter.target){
compStl.margin = '0 auto';
}
return component;
},
/**
* Update new component size while drawing
* @param {Object} e Event
* @private
* */
updateComponentSize : function (e) {
var y = e.pageY + this.frameOff.top;
var x = e.pageX + this.frameOff.left;
var start = this.startPos;
var top = start.top;
var left = start.left;
var height = y - top;
var width = x - left;
if (x < left) {
left = x;
width = start.left - x;
}
if (y < top) {
top = y;
height = start.top - y;
}
this.updateSize(top, left, width, height);
},
/**
* Update size
* @private
*/
updateSize: function(top, left, width, height){
var u = 'px';
var ghStl = this.ghost.style;
var compStl = this.tempComponent.style;
ghStl.top = compStl.top = top + u;
ghStl.left = compStl.left = left + u;
ghStl.width = compStl.width = width + u;
ghStl[this.hType] = compStl[this.hType] = height + u;
},
/**
* Used to bring the previous situation before event started
* @param {Object} e Event
* @param {Boolean} forse Indicates if rollback in anycase
* @private
* */
rollback: function(e, force) {
var key = e.which || e.keyCode;
if(key == this.config.ESCAPE_KEY || force){
this.isDragged = false;
this.endDraw();
}
return;
},
/**
* This event is triggered at the beginning of a draw operation
* @param {Object} component Object component before creation
* @private
* */
beforeDraw: function(component){
component.editable = false;//set this component editable
},
/**
* This event is triggered at the end of a draw operation
* @param {Object} model Component model created
* @private
* */
afterDraw: function(model){},
run: function(editor, sender, opts){
this.editor = editor;
this.sender = sender;
this.$wr = this.$wrapper;
this.enable();
},
stop: function(){
this.stopSelectPosition();
this.$wrapper.css('cursor','');
this.$wrapper.unbind();
}
});
});
var Backbone = require('backbone');
var SelectPosition = require('./SelectPosition');
module.exports = _.extend({}, SelectPosition, {
init(opt) {
_.bindAll(this,'startDraw','draw','endDraw','rollback');
this.config = opt || {};
this.hType = this.config.newFixedH ? 'height' : 'min-height';
this.allowDraw = 1;
},
/**
* Start with enabling to select position and listening to start drawning
* @private
* */
enable(...args) {
SelectPosition.enable.apply(this, args);
this.$wr.css('cursor','crosshair');
if(this.allowDraw)
this.$wr.on('mousedown', this.startDraw);
this.ghost = this.canvas.getGhostEl();
},
/**
* Start drawing component
* @param {Object} e Event
* @private
* */
startDraw(e) {
e.preventDefault();
this.stopSelectPosition();
this.ghost.style.display = 'block';
this.frameOff = this.getOffsetDim();
this.startPos = {
top : e.pageY + this.frameOff.top,
left: e.pageX + this.frameOff.left
};
this.isDragged = false;
this.tempComponent = {style: {}};
this.beforeDraw(this.tempComponent);
this.updateSize(this.startPos.top, this.startPos.left, 0, 0);
this.toggleEvents(1);
},
/**
* Enable/Disable events
* @param {Boolean} enable
*/
toggleEvents(enable) {
var method = enable ? 'on' : 'off';
this.$wr[method]('mousemove', this.draw);
this.$wr[method]('mouseup', this.endDraw);
this.$canvas[method]('mousemove', this.draw);
$(document)[method]('mouseup', this.endDraw);
$(document)[method]('keypress', this.rollback);
},
/**
* While drawing the component
* @param {Object} e Event
* @private
* */
draw(e) {
this.isDragged = true;
this.updateComponentSize(e);
},
/**
* End drawing component
* @param {Object} e Event
* @private
* */
endDraw(e) {
this.toggleEvents();
var model = {};
// Only if the mouse was moved
if(this.isDragged){
this.updateComponentSize(e);
this.setRequirements(this.tempComponent);
var lp = this.sorter.lastPos;
model = this.create(this.sorter.target, this.tempComponent, lp.index, lp.method);
this.sorter.prevTarget = null;
}
this.ghost.style.display = 'none';
this.startSelectPosition();
this.afterDraw(model);
},
/**
* Create new component inside the target
* @param {Object} target Tha target collection
* @param {Object} component New component to create
* @param {number} index Index inside the collection, 0 if no children inside
* @param {string} method Before or after of the children
* @param {Object} opts Options
*/
create(target, component, index, method, opts) {
index = method === 'after' ? index + 1 : index;
var opt = opts || {};
var $trg = $(target);
var trgModel = $trg.data('model');
var trgCollection = $trg.data('collection');
var droppable = trgModel ? trgModel.get('droppable') : 1;
opt.at = index;
if(trgCollection && droppable)
return trgCollection.add(component, opt);
else
console.warn("Invalid target position");
},
/**
* Check and set basic requirements for the component
* @param {Object} component New component to be created
* @return {Object} Component updated
* @private
* */
setRequirements(component) {
var c = this.config;
var compStl = component.style;
// Check min width
if(compStl.width.replace(/\D/g,'') < c.minComponentW)
compStl.width = c.minComponentW +'px';
// Check min height
if(compStl[this.hType].replace(/\D/g,'') < c.minComponentH)
compStl[this.hType] = c.minComponentH +'px';
// Set overflow in case of fixed height
if(c.newFixedH)
compStl.overflow = 'auto';
if(!this.absoluteMode){
delete compStl.left;
delete compStl.top;
}else
compStl.position = 'absolute';
var lp = this.sorter.lastPos;
if(this.nearFloat(lp.index, lp.method, this.sorter.lastDims))
compStl.float = 'left';
if(this.config.firstCentered &&
this.getCanvasWrapper() == this.sorter.target){
compStl.margin = '0 auto';
}
return component;
},
/**
* Update new component size while drawing
* @param {Object} e Event
* @private
* */
updateComponentSize(e) {
var y = e.pageY + this.frameOff.top;
var x = e.pageX + this.frameOff.left;
var start = this.startPos;
var top = start.top;
var left = start.left;
var height = y - top;
var width = x - left;
if (x < left) {
left = x;
width = start.left - x;
}
if (y < top) {
top = y;
height = start.top - y;
}
this.updateSize(top, left, width, height);
},
/**
* Update size
* @private
*/
updateSize(top, left, width, height) {
var u = 'px';
var ghStl = this.ghost.style;
var compStl = this.tempComponent.style;
ghStl.top = compStl.top = top + u;
ghStl.left = compStl.left = left + u;
ghStl.width = compStl.width = width + u;
ghStl[this.hType] = compStl[this.hType] = height + u;
},
/**
* Used to bring the previous situation before event started
* @param {Object} e Event
* @param {Boolean} forse Indicates if rollback in anycase
* @private
* */
rollback(e, force) {
var key = e.which || e.keyCode;
if(key == this.config.ESCAPE_KEY || force){
this.isDragged = false;
this.endDraw();
}
return;
},
/**
* This event is triggered at the beginning of a draw operation
* @param {Object} component Object component before creation
* @private
* */
beforeDraw(component) {
component.editable = false;//set this component editable
},
/**
* This event is triggered at the end of a draw operation
* @param {Object} model Component model created
* @private
* */
afterDraw(model) {},
run(editor, sender, opts) {
this.editor = editor;
this.sender = sender;
this.$wr = this.$wrapper;
this.enable();
},
stop() {
this.stopSelectPosition();
this.$wrapper.css('cursor','');
this.$wrapper.unbind();
}
});

143
src/commands/view/DeleteComponent.js

@ -1,88 +1,79 @@
define(['backbone', './SelectComponent'],
function(Backbone, SelectComponent) {
/**
* @class DeleteComponent
* @private
* */
return _.extend({},SelectComponent,{
var Backbone = require('backbone');
var SelectComponent = require('./SelectComponent');
init: function(o){
_.bindAll(this, 'startDelete', 'stopDelete', 'onDelete');
this.hoverClass = this.pfx + 'hover-delete';
this.badgeClass = this.pfx + 'badge-red';
},
module.exports = _.extend({},SelectComponent,{
enable: function()
{
var that = this;
this.$el.find('*')
.mouseover(this.startDelete)
.mouseout(this.stopDelete)
.click(this.onDelete);
},
init(o) {
_.bindAll(this, 'startDelete', 'stopDelete', 'onDelete');
this.hoverClass = this.pfx + 'hover-delete';
this.badgeClass = this.pfx + 'badge-red';
},
/**
* Start command
* @param {Object} e
* @private
*/
startDelete: function(e)
{
e.stopPropagation();
var $this = $(e.target);
enable() {
var that = this;
this.$el.find('*')
.mouseover(this.startDelete)
.mouseout(this.stopDelete)
.click(this.onDelete);
},
// Show badge if possible
if($this.data('model').get('removable')){
$this.addClass(this.hoverClass);
this.attachBadge($this.get(0));
}
/**
* Start command
* @param {Object} e
* @private
*/
startDelete(e) {
e.stopPropagation();
var $this = $(e.target);
},
// Show badge if possible
if($this.data('model').get('removable')){
$this.addClass(this.hoverClass);
this.attachBadge($this.get(0));
}
/**
* Stop command
* @param {Object} e
* @private
*/
stopDelete: function(e)
{
e.stopPropagation();
var $this = $(e.target);
$this.removeClass(this.hoverClass);
},
// Hide badge if possible
if(this.badge)
this.badge.css({ left: -1000, top:-1000 });
},
/**
* Stop command
* @param {Object} e
* @private
*/
stopDelete(e) {
e.stopPropagation();
var $this = $(e.target);
$this.removeClass(this.hoverClass);
/**
* Delete command
* @param {Object} e
* @private
*/
onDelete: function(e)
{
e.stopPropagation();
var $this = $(e.target);
// Hide badge if possible
if(this.badge)
this.badge.css({ left: -1000, top:-1000 });
},
// Do nothing in case can't remove
if(!$this.data('model').get('removable'))
return;
/**
* Delete command
* @param {Object} e
* @private
*/
onDelete(e) {
e.stopPropagation();
var $this = $(e.target);
$this.data('model').destroy();
this.removeBadge();
this.clean();
},
// Do nothing in case can't remove
if(!$this.data('model').get('removable'))
return;
/**
* Updates badge label
* @param {Object} model
* @private
* */
updateBadgeLabel: function (model)
{
this.badge.html( 'Remove ' + model.getName() );
},
$this.data('model').destroy();
this.removeBadge();
this.clean();
},
});
});
/**
* Updates badge label
* @param {Object} model
* @private
* */
updateBadgeLabel(model) {
this.badge.html( 'Remove ' + model.getName() );
},
});

128
src/commands/view/ExportTemplate.js

@ -1,78 +1,72 @@
define(function() {
/**
* @class ExportTemplate
* @private
* */
return {
module.exports = {
run: function(editor, sender) {
this.sender = sender;
this.wrapper = editor.DomComponents.getWrapper();
this.components = editor.DomComponents.getComponents();
this.modal = editor.Modal || null;
this.cm = editor.CodeManager || null;
this.cssc = editor.CssComposer || null;
this.protCss = editor.Config.protectedCss;
this.pfx = editor.Config.stylePrefix || '';
this.enable();
},
run(editor, sender) {
this.sender = sender;
this.wrapper = editor.DomComponents.getWrapper();
this.components = editor.DomComponents.getComponents();
this.modal = editor.Modal || null;
this.cm = editor.CodeManager || null;
this.cssc = editor.CssComposer || null;
this.protCss = editor.Config.protectedCss;
this.pfx = editor.Config.stylePrefix || '';
this.enable();
},
/**
* Build editor
* @param {String} codeName
* @param {String} theme
* @param {String} label
*
* @return {Object} Editor
* @private
* */
buildEditor: function(codeName, theme, label) {
if(!this.codeMirror)
this.codeMirror = this.cm.getViewer('CodeMirror');
/**
* Build editor
* @param {String} codeName
* @param {String} theme
* @param {String} label
*
* @return {Object} Editor
* @private
* */
buildEditor(codeName, theme, label) {
if(!this.codeMirror)
this.codeMirror = this.cm.getViewer('CodeMirror');
var $input = $('<textarea>'),
var $input = $('<textarea>'),
editor = this.codeMirror.clone().set({
label : label,
codeName : codeName,
theme : theme,
input : $input[0],
}),
editor = this.codeMirror.clone().set({
label,
codeName,
theme,
input : $input[0],
}),
$editor = new this.cm.EditorView({
model : editor,
config : this.cm.getConfig()
}).render().$el;
$editor = new this.cm.EditorView({
model : editor,
config : this.cm.getConfig()
}).render().$el;
editor.init( $input[0] );
editor.init( $input[0] );
return { el: editor, $el: $editor };
},
return { el: editor, $el: $editor };
},
enable: function() {
if(!this.$editors){
var oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML'),
oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS');
this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el;
this.$editors = $('<div>', {class: this.pfx + 'export-dl'});
this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el);
}
enable() {
if(!this.$editors){
var oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML'),
oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS');
this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el;
this.$editors = $('<div>', {class: this.pfx + 'export-dl'});
this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el);
}
if(this.modal){
this.modal.setTitle('Export template');
this.modal.setContent(this.$editors);
this.modal.open();
}
var addCss = this.protCss || '';
//this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc));
this.htmlEditor.setContent(this.em.getHtml());
this.cssEditor.setContent(addCss + this.cm.getCode(this.wrapper, 'css', this.cssc));
if(this.modal){
this.modal.setTitle('Export template');
this.modal.setContent(this.$editors);
this.modal.open();
}
var addCss = this.protCss || '';
//this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc));
this.htmlEditor.setContent(this.em.getHtml());
this.cssEditor.setContent(addCss + this.cm.getCode(this.wrapper, 'css', this.cssc));
if(this.sender)
this.sender.set('active',false);
},
if(this.sender)
this.sender.set('active',false);
},
stop: function(){}
};
});
stop() {}
};

157
src/commands/view/Fullscreen.js

@ -1,86 +1,83 @@
define(function() {
module.exports = {
/**
* Check if fullscreen mode is enabled
* @return {Boolean}
*/
isEnabled() {
var d = document;
if(d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement)
return 1;
else
return 0;
},
return {
/**
* Check if fullscreen mode is enabled
* @return {Boolean}
*/
isEnabled: function(){
var d = document;
if(d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement)
return 1;
else
return 0;
},
/**
* Enable fullscreen mode and return browser prefix
* @param {HTMLElement} el
* @return {string}
*/
enable(el) {
var pfx = '';
if (el.requestFullscreen)
el.requestFullscreen();
else if (el.webkitRequestFullscreen) {
pfx = 'webkit';
el.webkitRequestFullscreen();
}else if (el.mozRequestFullScreen) {
pfx = 'moz';
el.mozRequestFullScreen();
}else if (el.msRequestFullscreen)
el.msRequestFullscreen();
else
console.warn('Fullscreen not supported');
return pfx;
},
/**
* Enable fullscreen mode and return browser prefix
* @param {HTMLElement} el
* @return {string}
*/
enable: function(el){
var pfx = '';
if (el.requestFullscreen)
el.requestFullscreen();
else if (el.webkitRequestFullscreen) {
pfx = 'webkit';
el.webkitRequestFullscreen();
}else if (el.mozRequestFullScreen) {
pfx = 'moz';
el.mozRequestFullScreen();
}else if (el.msRequestFullscreen)
el.msRequestFullscreen();
else
console.warn('Fullscreen not supported');
return pfx;
},
/**
* Disable fullscreen mode
*/
disable() {
var d = document;
if (d.exitFullscreen)
d.exitFullscreen();
else if (d.webkitExitFullscreen)
d.webkitExitFullscreen();
else if (d.mozCancelFullScreen)
d.mozCancelFullScreen();
else if (d.msExitFullscreen)
d.msExitFullscreen();
},
/**
* Disable fullscreen mode
*/
disable: function(){
var d = document;
if (d.exitFullscreen)
d.exitFullscreen();
else if (d.webkitExitFullscreen)
d.webkitExitFullscreen();
else if (d.mozCancelFullScreen)
d.mozCancelFullScreen();
else if (d.msExitFullscreen)
d.msExitFullscreen();
},
/**
* Triggered when the state of the fullscreen is changed. Inside detects if
* it's enabled
* @param {strinf} pfx Browser prefix
* @param {Event} e
*/
fsChanged(pfx, e) {
var d = document;
var ev = (pfx || '') + 'fullscreenchange';
if(!this.isEnabled()){
this.stop(null, this.sender);
document.removeEventListener(ev, this.fsChanged);
}
},
/**
* Triggered when the state of the fullscreen is changed. Inside detects if
* it's enabled
* @param {strinf} pfx Browser prefix
* @param {Event} e
*/
fsChanged: function(pfx, e){
var d = document;
var ev = (pfx || '') + 'fullscreenchange';
if(!this.isEnabled()){
this.stop(null, this.sender);
document.removeEventListener(ev, this.fsChanged);
}
},
run(editor, sender) {
this.sender = sender;
var pfx = this.enable(editor.getContainer());
this.fsChanged = this.fsChanged.bind(this, pfx);
document.addEventListener(pfx + 'fullscreenchange', this.fsChanged);
if(editor)
editor.trigger('change:canvasOffset');
},
run: function(editor, sender){
this.sender = sender;
var pfx = this.enable(editor.getContainer());
this.fsChanged = this.fsChanged.bind(this, pfx);
document.addEventListener(pfx + 'fullscreenchange', this.fsChanged);
if(editor)
editor.trigger('change:canvasOffset');
},
stop(editor, sender) {
if(sender && sender.set)
sender.set('active', false);
this.disable();
if(editor)
editor.trigger('change:canvasOffset');
}
stop: function(editor, sender){
if(sender && sender.set)
sender.set('active', false);
this.disable();
if(editor)
editor.trigger('change:canvasOffset');
}
};
});
};

66
src/commands/view/ImageComponent.js

@ -1,39 +1,35 @@
define(['backbone', './InsertCustom'],
function(Backbone, InsertCustom) {
/**
* @class ImageComponent
* @private
* */
return _.extend({}, InsertCustom, {
var Backbone = require('backbone');
var InsertCustom = require('./InsertCustom');
/**
* Trigger before insert
* @param {Object} object
* @private
*
* */
beforeInsert: function(object){
object.type = 'image';
object.style = {};
object.attributes = {};
object.attributes.onmousedown = 'return false';
if (this.config.firstCentered &&
this.getCanvasWrapper() == this.sorter.target ) {
object.style.margin = '0 auto';
}
},
module.exports = _.extend({}, InsertCustom, {
/**
* Trigger after insert
* @param {Object} model Model created after insert
* @private
* */
afterInsert: function(model){
model.trigger('dblclick');
if(this.sender)
this.sender.set('active', false);
},
/**
* Trigger before insert
* @param {Object} object
* @private
*
* */
beforeInsert(object) {
object.type = 'image';
object.style = {};
object.attributes = {};
object.attributes.onmousedown = 'return false';
if (this.config.firstCentered &&
this.getCanvasWrapper() == this.sorter.target ) {
object.style.margin = '0 auto';
}
},
/**
* Trigger after insert
* @param {Object} model Model created after insert
* @private
* */
afterInsert(model) {
model.trigger('dblclick');
if(this.sender)
this.sender.set('active', false);
},
});
});
});

140
src/commands/view/InsertCustom.js

@ -1,83 +1,79 @@
define(['backbone', './CreateComponent'],
function(Backbone, CreateComponent) {
/**
* @class InsertCustom
* @private
* */
return _.extend({}, CreateComponent, {
var Backbone = require('backbone');
var CreateComponent = require('./CreateComponent');
init: function(){
CreateComponent.init.apply(this, arguments);
_.bindAll(this, 'insertComponent');
this.allowDraw = 0;
},
module.exports = _.extend({}, CreateComponent, {
/**
* Run method
* @private
* */
run: function(em, sender, options) {
this.em = em;
this.sender = sender;
this.opt = options || {};
this.$wr = this.$wrapper;
this.enable();
},
init(...args) {
CreateComponent.init.apply(this, args);
_.bindAll(this, 'insertComponent');
this.allowDraw = 0;
},
enable: function(){
CreateComponent.enable.apply(this, arguments);
this.$wr.on('click', this.insertComponent);
},
/**
* Run method
* @private
* */
run(em, sender, options) {
this.em = em;
this.sender = sender;
this.opt = options || {};
this.$wr = this.$wrapper;
this.enable();
},
/**
* Start insert event
* @private
* */
insertComponent: function(){
this.$wr.off('click', this.insertComponent);
this.stopSelectPosition();
var object = this.buildContent();
this.beforeInsert(object);
var index = this.sorter.lastPos.index;
// By default, collections do not trigger add event, so silent is used
var model = this.create(this.sorter.target, object, index, null, {silent: false});
enable(...args) {
CreateComponent.enable.apply(this, args);
this.$wr.on('click', this.insertComponent);
},
if(this.opt.terminateAfterInsert && this.sender)
this.sender.set('active', false);
else
this.enable();
/**
* Start insert event
* @private
* */
insertComponent() {
this.$wr.off('click', this.insertComponent);
this.stopSelectPosition();
var object = this.buildContent();
this.beforeInsert(object);
var index = this.sorter.lastPos.index;
// By default, collections do not trigger add event, so silent is used
var model = this.create(this.sorter.target, object, index, null, {silent: false});
if(!model)
return;
if(this.opt.terminateAfterInsert && this.sender)
this.sender.set('active', false);
else
this.enable();
if(this.em)
this.em.editor.initChildrenComp(model);
if(!model)
return;
this.afterInsert(model, this);
},
if(this.em)
this.em.editor.initChildrenComp(model);
/**
* Trigger before insert
* @param {Object} obj
* @private
* */
beforeInsert: function(obj){},
this.afterInsert(model, this);
},
/**
* Trigger after insert
* @param {Object} model Model created after insert
* @private
* */
afterInsert: function(model){},
/**
* Trigger before insert
* @param {Object} obj
* @private
* */
beforeInsert(obj) {},
/**
* Create different object, based on content, to insert inside canvas
*
* @return {Object}
* @private
* */
buildContent: function(){
return this.opt.content || {};
},
});
});
/**
* Trigger after insert
* @param {Object} model Model created after insert
* @private
* */
afterInsert(model) {},
/**
* Create different object, based on content, to insert inside canvas
*
* @return {Object}
* @private
* */
buildContent() {
return this.opt.content || {};
},
});

296
src/commands/view/MoveComponent.js

@ -1,148 +1,148 @@
define(['backbone', './SelectComponent','./SelectPosition'],
function(Backbone, SelectComponent, SelectPosition) {
return _.extend({}, SelectPosition, SelectComponent, {
init: function(o){
SelectComponent.init.apply(this, arguments);
_.bindAll(this, 'initSorter','rollback', 'onEndMove');
this.opt = o;
this.hoverClass = this.ppfx + 'highlighter-warning';
this.badgeClass = this.ppfx + 'badge-warning';
this.noSelClass = this.ppfx + 'no-select';
},
enable: function() {
SelectComponent.enable.apply(this, arguments);
this.getBadgeEl().addClass(this.badgeClass);
this.getHighlighterEl().addClass(this.hoverClass);
var wp = this.$wrapper;
wp.css('cursor','move');
wp.on('mousedown', this.initSorter);
// Avoid strange moving behavior
wp.addClass(this.noSelClass);
},
/**
* Overwrite for doing nothing
* @private
*/
toggleClipboard: function(){},
/**
* Delegate sorting
* @param {Event} e
* @private
* */
initSorter: function(e){
var el = $(e.target).data('model');
var drag = el.get('draggable');
if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null;
this.startSelectPosition(e.target, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMove.bind(this);
this.stopSelectComponent();
this.$wrapper.off('mousedown', this.initSorter);
this.getContentWindow().on('keydown', this.rollback);
},
/**
* Init sorter from model
* @param {Object} model
* @private
*/
initSorterFromModel: function(model) {
var drag = model.get('draggable');
if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null;
var el = model.view.el;
this.startSelectPosition(el, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMoveFromModel.bind(this);
/*
this.sorter.setDragHelper(el);
var dragHelper = this.sorter.dragHelper;
dragHelper.className = this.ppfx + 'drag-helper';
dragHelper.innerHTML = '';
dragHelper.backgroundColor = 'white';
*/
this.stopSelectComponent();
this.getContentWindow().on('keydown', this.rollback);
},
onEndMoveFromModel: function() {
this.getContentWindow().off('keydown', this.rollback);
},
/**
* Callback after sorting
* @private
*/
onEndMove: function(){
this.enable();
this.getContentWindow().off('keydown', this.rollback);
},
/**
* Say what to do after the component was selected (selectComponent)
* @param {Event} e
* @param {Object} Selected element
* @private
* */
onSelect: function(e,el){},
/**
* Used to bring the previous situation before start moving the component
* @param {Event} e
* @param {Boolean} Indicates if rollback in anycase
* @private
* */
rollback: function(e, force){
var key = e.which || e.keyCode;
if(key == this.opt.ESCAPE_KEY || force){
this.sorter.moved = false;
this.sorter.endMove();
}
return;
},
/**
* Returns badge element
* @return {HTMLElement}
* @private
*/
getBadgeEl: function(){
if(!this.$badge)
this.$badge = $(this.getBadge());
return this.$badge;
},
/**
* Returns highlighter element
* @return {HTMLElement}
* @private
*/
getHighlighterEl: function(){
if(!this.$hl)
this.$hl = $(this.canvas.getHighlighter());
return this.$hl;
},
stop: function(){
SelectComponent.stop.apply(this, arguments);
this.getBadgeEl().removeClass(this.badgeClass);
this.getHighlighterEl().removeClass(this.hoverClass);
var wp = this.$wrapper;
wp.css('cursor', '').unbind().removeClass(this.noSelClass);
}
});
});
var Backbone = require('backbone');
var SelectComponent = require('./SelectComponent');
var SelectPosition = require('./SelectPosition');
module.exports = _.extend({}, SelectPosition, SelectComponent, {
init(o) {
SelectComponent.init.apply(this, arguments);
_.bindAll(this, 'initSorter','rollback', 'onEndMove');
this.opt = o;
this.hoverClass = this.ppfx + 'highlighter-warning';
this.badgeClass = this.ppfx + 'badge-warning';
this.noSelClass = this.ppfx + 'no-select';
},
enable(...args) {
SelectComponent.enable.apply(this, args);
this.getBadgeEl().addClass(this.badgeClass);
this.getHighlighterEl().addClass(this.hoverClass);
var wp = this.$wrapper;
wp.css('cursor','move');
wp.on('mousedown', this.initSorter);
// Avoid strange moving behavior
wp.addClass(this.noSelClass);
},
/**
* Overwrite for doing nothing
* @private
*/
toggleClipboard() {},
/**
* Delegate sorting
* @param {Event} e
* @private
* */
initSorter(e) {
var el = $(e.target).data('model');
var drag = el.get('draggable');
if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null;
this.startSelectPosition(e.target, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMove.bind(this);
this.stopSelectComponent();
this.$wrapper.off('mousedown', this.initSorter);
this.getContentWindow().on('keydown', this.rollback);
},
/**
* Init sorter from model
* @param {Object} model
* @private
*/
initSorterFromModel(model) {
var drag = model.get('draggable');
if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null;
var el = model.view.el;
this.startSelectPosition(el, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMoveFromModel.bind(this);
/*
this.sorter.setDragHelper(el);
var dragHelper = this.sorter.dragHelper;
dragHelper.className = this.ppfx + 'drag-helper';
dragHelper.innerHTML = '';
dragHelper.backgroundColor = 'white';
*/
this.stopSelectComponent();
this.getContentWindow().on('keydown', this.rollback);
},
onEndMoveFromModel() {
this.getContentWindow().off('keydown', this.rollback);
},
/**
* Callback after sorting
* @private
*/
onEndMove() {
this.enable();
this.getContentWindow().off('keydown', this.rollback);
},
/**
* Say what to do after the component was selected (selectComponent)
* @param {Event} e
* @param {Object} Selected element
* @private
* */
onSelect(e, el) {},
/**
* Used to bring the previous situation before start moving the component
* @param {Event} e
* @param {Boolean} Indicates if rollback in anycase
* @private
* */
rollback(e, force) {
var key = e.which || e.keyCode;
if(key == this.opt.ESCAPE_KEY || force){
this.sorter.moved = false;
this.sorter.endMove();
}
return;
},
/**
* Returns badge element
* @return {HTMLElement}
* @private
*/
getBadgeEl() {
if(!this.$badge)
this.$badge = $(this.getBadge());
return this.$badge;
},
/**
* Returns highlighter element
* @return {HTMLElement}
* @private
*/
getHighlighterEl() {
if(!this.$hl)
this.$hl = $(this.canvas.getHighlighter());
return this.$hl;
},
stop(...args) {
SelectComponent.stop.apply(this, args);
this.getBadgeEl().removeClass(this.badgeClass);
this.getHighlighterEl().removeClass(this.hoverClass);
var wp = this.$wrapper;
wp.css('cursor', '').unbind().removeClass(this.noSelClass);
}
});

35
src/commands/view/OpenAssets.js

@ -1,24 +1,21 @@
define(function() {
module.exports = {
return {
run(editor, sender, opts) {
var opt = opts || {};
var config = editor.getConfig();
var modal = editor.Modal;
var assetManager = editor.AssetManager;
run: function(editor, sender, opts) {
var opt = opts || {};
var config = editor.getConfig();
var modal = editor.Modal;
var assetManager = editor.AssetManager;
assetManager.onClick(opt.onClick);
assetManager.onDblClick(opt.onDblClick);
assetManager.onClick(opt.onClick);
assetManager.onDblClick(opt.onDblClick);
// old API
assetManager.setTarget(opt.target);
assetManager.onSelect(opt.onSelect);
// old API
assetManager.setTarget(opt.target);
assetManager.onSelect(opt.onSelect);
modal.setTitle(opt.modalTitle || 'Select image');
modal.setContent(assetManager.render());
modal.open();
},
modal.setTitle(opt.modalTitle || 'Select image');
modal.setContent(assetManager.render());
modal.open();
},
};
});
};

48
src/commands/view/OpenBlocks.js

@ -1,28 +1,26 @@
define(function() {
module.exports = {
return {
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');
}
run: function(editor, sender) {
var config = editor.Config;
var pfx = config.stylePrefix;
var bm = editor.BlockManager;
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');
}
this.blocks.style.display = 'block';
},
this.blocks.style.display = 'block';
},
stop: function() {
if(this.blocks)
this.blocks.style.display = 'none';
}
};
});
stop() {
if(this.blocks)
this.blocks.style.display = 'none';
}
};

60
src/commands/view/OpenLayers.js

@ -1,38 +1,34 @@
define(['Navigator'], function(Layers) {
/**
* @class OpenStyleManager
* @private
* */
return {
var Layers = require('navigator');
run: function(em, sender) {
if(!this.$layers) {
var collection = em.DomComponents.getComponent().get('components'),
config = em.getConfig(),
panels = em.Panels,
lyStylePfx = config.layers.stylePrefix || 'nv-';
module.exports = {
config.layers.stylePrefix = config.stylePrefix + lyStylePfx;
config.layers.pStylePrefix = config.stylePrefix;
config.layers.em = em.editor;
config.layers.opened = em.editor.get('opened');
var layers = new Layers(collection, config.layers);
this.$layers = layers.render();
run(em, sender) {
if(!this.$layers) {
var collection = em.DomComponents.getComponent().get('components'),
config = em.getConfig(),
panels = em.Panels,
lyStylePfx = config.layers.stylePrefix || 'nv-';
// Check if panel exists otherwise crate it
if(!panels.getPanel('views-container'))
this.panel = panels.addPanel({id: 'views-container'});
else
this.panel = panels.getPanel('views-container');
config.layers.stylePrefix = config.stylePrefix + lyStylePfx;
config.layers.pStylePrefix = config.stylePrefix;
config.layers.em = em.editor;
config.layers.opened = em.editor.get('opened');
var layers = new Layers(collection, config.layers);
this.$layers = layers.render();
this.panel.set('appendContent', this.$layers).trigger('change:appendContent');
}
this.$layers.show();
},
// Check if panel exists otherwise crate it
if(!panels.getPanel('views-container'))
this.panel = panels.addPanel({id: 'views-container'});
else
this.panel = panels.getPanel('views-container');
stop: function() {
if(this.$layers)
this.$layers.hide();
this.panel.set('appendContent', this.$layers).trigger('change:appendContent');
}
};
});
this.$layers.show();
},
stop() {
if(this.$layers)
this.$layers.hide();
}
};

140
src/commands/view/OpenStyleManager.js

@ -1,82 +1,78 @@
define(['StyleManager'], function(StyleManager) {
/**
* @class OpenStyleManager
* @private
* */
return {
var StyleManager = require('style_manager');
run: function(em, sender) {
this.sender = sender;
if(!this.$cn){
var config = em.getConfig(),
panels = em.Panels;
// Main container
this.$cn = $('<div/>');
// Secondary container
this.$cn2 = $('<div/>');
this.$cn.append(this.$cn2);
module.exports = {
// Device Manager
var dvm = em.DeviceManager;
if(dvm && config.showDevices){
var devicePanel = panels.addPanel({ id: 'devices-c'});
devicePanel.set('appendContent', dvm.render()).trigger('change:appendContent');
}
run(em, sender) {
this.sender = sender;
if(!this.$cn){
var config = em.getConfig(),
panels = em.Panels;
// Main container
this.$cn = $('<div/>');
// Secondary container
this.$cn2 = $('<div/>');
this.$cn.append(this.$cn2);
// Class Manager container
var clm = em.SelectorManager;
if(clm)
this.$cn2.append(clm.render([]));
// Device Manager
var dvm = em.DeviceManager;
if(dvm && config.showDevices){
var devicePanel = panels.addPanel({ id: 'devices-c'});
devicePanel.set('appendContent', dvm.render()).trigger('change:appendContent');
}
this.$cn2.append(em.StyleManager.render());
var smConfig = em.StyleManager.getConfig();
// Create header
this.$header = $('<div>', {
class: smConfig.stylePrefix + 'header',
text: smConfig.textNoElement,
});
//this.$cn = this.$cn.add(this.$header);
this.$cn.append(this.$header);
// Class Manager container
var clm = em.SelectorManager;
if(clm)
this.$cn2.append(clm.render([]));
// Create panel if not exists
if(!panels.getPanel('views-container'))
this.panel = panels.addPanel({ id: 'views-container'});
else
this.panel = panels.getPanel('views-container');
this.$cn2.append(em.StyleManager.render());
var smConfig = em.StyleManager.getConfig();
// Create header
this.$header = $('<div>', {
class: smConfig.stylePrefix + 'header',
text: smConfig.textNoElement,
});
//this.$cn = this.$cn.add(this.$header);
this.$cn.append(this.$header);
// Add all containers to the panel
this.panel.set('appendContent', this.$cn).trigger('change:appendContent');
// Create panel if not exists
if(!panels.getPanel('views-container'))
this.panel = panels.addPanel({ id: 'views-container'});
else
this.panel = panels.getPanel('views-container');
this.target = em.editor;
this.listenTo( this.target ,'change:selectedComponent', this.toggleSm);
}
this.toggleSm();
},
// Add all containers to the panel
this.panel.set('appendContent', this.$cn).trigger('change:appendContent');
/**
* Toggle Style Manager visibility
* @private
*/
toggleSm: function() {
if(!this.sender.get('active'))
return;
if(this.target.get('selectedComponent')){
this.$cn2.show();
this.$header.hide();
}else{
this.$cn2.hide();
this.$header.show();
}
},
this.target = em.editor;
this.listenTo( this.target ,'change:selectedComponent', this.toggleSm);
}
this.toggleSm();
},
stop: function() {
// Hide secondary container if exists
if(this.$cn2)
this.$cn2.hide();
/**
* Toggle Style Manager visibility
* @private
*/
toggleSm() {
if(!this.sender.get('active'))
return;
if(this.target.get('selectedComponent')){
this.$cn2.show();
this.$header.hide();
}else{
this.$cn2.hide();
this.$header.show();
}
},
// Hide header container if exists
if(this.$header)
this.$header.hide();
}
};
});
stop() {
// Hide secondary container if exists
if(this.$cn2)
this.$cn2.hide();
// Hide header container if exists
if(this.$header)
this.$header.hide();
}
};

56
src/commands/view/OpenTraitManager.js

@ -1,32 +1,30 @@
define(function() {
module.exports = {
return {
run(editor, sender) {
var config = editor.Config;
var pfx = config.stylePrefix;
var tm = editor.TraitManager;
var panelC;
if(!this.obj){
var tmView = tm.getTraitsViewer();
var confTm = tm.getConfig();
this.obj = $('<div/>')
.append('<div class="'+pfx+'traits-label">' + confTm.labelContainer + '</div>')
.get(0);
this.obj.appendChild(tmView.render().el);
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.obj).trigger('change:appendContent');
}
run: function(editor, sender) {
var config = editor.Config;
var pfx = config.stylePrefix;
var tm = editor.TraitManager;
if(!this.obj){
var tmView = tm.getTraitsViewer();
var confTm = tm.getConfig();
this.obj = $('<div/>')
.append('<div class="'+pfx+'traits-label">' + confTm.labelContainer + '</div>')
.get(0);
this.obj.appendChild(tmView.render().el);
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.obj).trigger('change:appendContent');
}
this.obj.style.display = 'block';
},
this.obj.style.display = 'block';
},
stop: function() {
if(this.obj)
this.obj.style.display = 'none';
}
};
});
stop() {
if(this.obj)
this.obj.style.display = 'none';
}
};

121
src/commands/view/Preview.js

@ -1,68 +1,65 @@
define(function() {
module.exports = {
return {
getPanels(editor) {
if(!this.panels)
this.panels = editor.Panels.getPanelsEl();
return this.panels;
},
getPanels: function(editor){
if(!this.panels)
this.panels = editor.Panels.getPanelsEl();
return this.panels;
},
tglPointers(editor, v) {
var elP = editor.Canvas.getBody().querySelectorAll('.' + this.ppfx + 'no-pointer');
_.each(elP, item => {
item.style.pointerEvents = v ? '' : 'all';
});
},
tglPointers: function(editor, v) {
var elP = editor.Canvas.getBody().querySelectorAll('.' + this.ppfx + 'no-pointer');
_.each(elP, function(item){
item.style.pointerEvents = v ? '' : 'all';
});
},
run(editor, sender) {
if(sender && sender.set)
sender.set('active', false);
editor.stopCommand('sw-visibility');
var that = this;
var panels = this.getPanels(editor);
var canvas = editor.Canvas.getElement();
var editorEl = editor.getEl();
var pfx = editor.Config.stylePrefix;
if(!this.helper) {
this.helper = document.createElement('span');
this.helper.className = pfx + 'off-prv fa fa-eye-slash';
editorEl.appendChild(this.helper);
this.helper.onclick = () => {
that.stop(editor);
};
}
this.helper.style.display = 'inline-block';
this.tglPointers(editor);
run: function(editor, sender) {
if(sender && sender.set)
sender.set('active', false);
editor.stopCommand('sw-visibility');
var that = this;
var panels = this.getPanels(editor);
var canvas = editor.Canvas.getElement();
var editorEl = editor.getEl();
var pfx = editor.Config.stylePrefix;
if(!this.helper) {
this.helper = document.createElement('span');
this.helper.className = pfx + 'off-prv fa fa-eye-slash';
editorEl.appendChild(this.helper);
this.helper.onclick = function(){
that.stop(editor);
};
}
this.helper.style.display = 'inline-block';
this.tglPointers(editor);
/*
editor.Canvas.getBody().querySelectorAll('.' + pfx + 'no-pointer').forEach(function(){
this.style.pointerEvents = 'all';
});*/
/*
editor.Canvas.getBody().querySelectorAll('.' + pfx + 'no-pointer').forEach(function(){
this.style.pointerEvents = 'all';
});*/
panels.style.display = 'none';
var canvasS = canvas.style;
canvasS.width = '100%';
canvasS.height = '100%';
canvasS.top = '0';
canvasS.left = '0';
canvasS.padding = '0';
canvasS.margin = '0';
editor.trigger('change:canvasOffset');
},
panels.style.display = 'none';
var canvasS = canvas.style;
canvasS.width = '100%';
canvasS.height = '100%';
canvasS.top = '0';
canvasS.left = '0';
canvasS.padding = '0';
canvasS.margin = '0';
editor.trigger('change:canvasOffset');
},
stop: function(editor, sender) {
var panels = this.getPanels(editor);
editor.runCommand('sw-visibility');
editor.getModel().runDefault();
panels.style.display = 'block';
var canvas = editor.Canvas.getElement();
canvas.setAttribute('style', '');
if(this.helper) {
this.helper.style.display = 'none';
}
editor.trigger('change:canvasOffset');
this.tglPointers(editor, 1);
}
};
});
stop(editor, sender) {
var panels = this.getPanels(editor);
editor.runCommand('sw-visibility');
editor.getModel().runDefault();
panels.style.display = 'block';
var canvas = editor.Canvas.getElement();
canvas.setAttribute('style', '');
if(this.helper) {
this.helper.style.display = 'none';
}
editor.trigger('change:canvasOffset');
this.tglPointers(editor, 1);
}
};

52
src/commands/view/Resize.js

@ -1,32 +1,30 @@
define(function() {
return {
module.exports = {
run: function(editor, sender, opts) {
var el = (opts && opts.el) || '';
var canvas = editor.Canvas;
var canvasResizer = this.canvasResizer;
var options = opts.options || {};
run(editor, sender, opts) {
var el = (opts && opts.el) || '';
var canvas = editor.Canvas;
var canvasResizer = this.canvasResizer;
var options = opts.options || {};
// Create the resizer for the canvas if not yet created
if(!canvasResizer) {
var canvasView = canvas.getCanvasView();
options.ratioDefault = 1;
options.appendTo = canvas.getResizerEl();
options.prefix = editor.getConfig().stylePrefix;
options.posFetcher = canvasView.getElementPos.bind(canvasView);
options.mousePosFetcher = canvas.getMouseRelativePos;
this.canvasResizer = editor.Utils.Resizer.init(options);
canvasResizer = this.canvasResizer;
}
// Create the resizer for the canvas if not yet created
if(!canvasResizer) {
var canvasView = canvas.getCanvasView();
options.ratioDefault = 1;
options.appendTo = canvas.getResizerEl();
options.prefix = editor.getConfig().stylePrefix;
options.posFetcher = canvasView.getElementPos.bind(canvasView);
options.mousePosFetcher = canvas.getMouseRelativePos;
this.canvasResizer = editor.Utils.Resizer.init(options);
canvasResizer = this.canvasResizer;
}
canvasResizer.setOptions(options);
canvasResizer.focus(el);
},
canvasResizer.setOptions(options);
canvasResizer.focus(el);
},
stop: function() {
if(this.canvasResizer)
this.canvasResizer.blur();
},
stop() {
if(this.canvasResizer)
this.canvasResizer.blur();
},
};
});
};

997
src/commands/view/SelectComponent.js

File diff suppressed because it is too large

187
src/commands/view/SelectPosition.js

@ -1,103 +1,100 @@
define(function() {
module.exports = {
return {
/**
* Start select position event
* @param {HTMLElement} trg
* @private
* */
startSelectPosition(trg, doc) {
this.isPointed = false;
var utils = this.editorModel.get('Utils');
if(utils && !this.sorter)
this.sorter = new utils.Sorter({
container: this.getCanvasBody(),
placer: this.canvas.getPlacerEl(),
containerSel: '*',
itemSel: '*',
pfx: this.ppfx,
direction: 'a',
document: doc,
wmargin: 1,
nested: 1,
em: this.editorModel,
canvasRelative: 1,
});
this.sorter.startSort(trg);
},
/**
* Start select position event
* @param {HTMLElement} trg
* @private
* */
startSelectPosition: function(trg, doc) {
this.isPointed = false;
var utils = this.editorModel.get('Utils');
if(utils && !this.sorter)
this.sorter = new utils.Sorter({
container: this.getCanvasBody(),
placer: this.canvas.getPlacerEl(),
containerSel: '*',
itemSel: '*',
pfx: this.ppfx,
direction: 'a',
document: doc,
wmargin: 1,
nested: 1,
em: this.editorModel,
canvasRelative: 1,
});
this.sorter.startSort(trg);
},
/**
* Get frame position
* @return {Object}
* @private
*/
getOffsetDim() {
var frameOff = this.offset(this.canvas.getFrameEl());
var canvasOff = this.offset(this.canvas.getElement());
var top = frameOff.top - canvasOff.top;
var left = frameOff.left - canvasOff.left;
return { top, left };
},
/**
* Get frame position
* @return {Object}
* @private
*/
getOffsetDim: function() {
var frameOff = this.offset(this.canvas.getFrameEl());
var canvasOff = this.offset(this.canvas.getElement());
var top = frameOff.top - canvasOff.top;
var left = frameOff.left - canvasOff.left;
return { top: top, left: left };
},
/**
* Stop select position event
* @private
* */
stopSelectPosition() {
this.posTargetCollection = null;
this.posIndex = this.posMethod=='after' && this.cDim.length!==0 ? this.posIndex + 1 : this.posIndex; //Normalize
if(this.sorter){
this.sorter.moved = 0;
this.sorter.endMove();
}
if(this.cDim){
this.posIsLastEl = this.cDim.length!==0 && this.posMethod=='after' && this.posIndex==this.cDim.length;
this.posTargetEl = (this.cDim.length===0 ? $(this.outsideElem) :
(!this.posIsLastEl && this.cDim[this.posIndex] ? $(this.cDim[this.posIndex][5]).parent() : $(this.outsideElem) ));
this.posTargetModel = this.posTargetEl.data("model");
this.posTargetCollection = this.posTargetEl.data("model-comp");
}
},
/**
* Stop select position event
* @private
* */
stopSelectPosition: function() {
this.posTargetCollection = null;
this.posIndex = this.posMethod=='after' && this.cDim.length!==0 ? this.posIndex + 1 : this.posIndex; //Normalize
if(this.sorter){
this.sorter.moved = 0;
this.sorter.endMove();
}
if(this.cDim){
this.posIsLastEl = this.cDim.length!==0 && this.posMethod=='after' && this.posIndex==this.cDim.length;
this.posTargetEl = (this.cDim.length===0 ? $(this.outsideElem) :
(!this.posIsLastEl && this.cDim[this.posIndex] ? $(this.cDim[this.posIndex][5]).parent() : $(this.outsideElem) ));
this.posTargetModel = this.posTargetEl.data("model");
this.posTargetCollection = this.posTargetEl.data("model-comp");
}
},
/**
* Enabel select position
* @private
*/
enable() {
this.startSelectPosition();
},
/**
* Enabel select position
* @private
*/
enable: function() {
this.startSelectPosition();
},
/**
* Check if the pointer is near to the float component
* @param {number} index
* @param {string} method
* @param {Array<Array>} dims
* @return {Boolean}
* @private
* */
nearFloat(index, method, dims) {
var i = index || 0;
var m = method || 'before';
var len = dims.length;
var isLast = len !== 0 && m == 'after' && i == len;
if(len !== 0 && (
(!isLast && !dims[i][4]) ||
(dims[i-1] && !dims[i-1][4]) ||
(isLast && !dims[i-1][4]) ) )
return 1;
return 0;
},
/**
* Check if the pointer is near to the float component
* @param {number} index
* @param {string} method
* @param {Array<Array>} dims
* @return {Boolean}
* @private
* */
nearFloat: function(index, method, dims) {
var i = index || 0;
var m = method || 'before';
var len = dims.length;
var isLast = len !== 0 && m == 'after' && i == len;
if(len !== 0 && (
(!isLast && !dims[i][4]) ||
(dims[i-1] && !dims[i-1][4]) ||
(isLast && !dims[i-1][4]) ) )
return 1;
return 0;
},
run() {
this.enable();
},
run: function() {
this.enable();
},
stop: function() {
this.stopSelectPosition();
this.$wrapper.css('cursor','');
this.$wrapper.unbind();
}
};
});
stop() {
this.stopSelectPosition();
this.$wrapper.css('cursor','');
this.$wrapper.unbind();
}
};

296
src/commands/view/ShowOffset.js

@ -1,149 +1,147 @@
define(function() {
return {
getOffsetMethod: function(state) {
var method = state || '';
return 'get' + method + 'OffsetViewerEl';
},
run: function(editor, sender, opts) {
var opt = opts || {};
var state = opt.state || '';
var config = editor.getConfig();
if (!config.showOffsets ||
(!config.showOffsetsSelected && state == 'Fixed') ) {
return;
}
var canvas = editor.Canvas;
var el = opt.el || '';
var pos = opt.elPos || canvas.getElementPos(el);
var style = window.getComputedStyle(el);
var ppfx = this.ppfx;
var stateVar = state + 'State';
var method = this.getOffsetMethod(state);
var offsetViewer = canvas[method]();
offsetViewer.style.display = 'block';
var marginT = this['marginT' + state];
var marginB = this['marginB' + state];
var marginL = this['marginL' + state];
var marginR = this['marginR' + state];
var padT = this['padT' + state];
var padB = this['padB' + state];
var padL = this['padL' + state];
var padR = this['padR' + state];
if(!this[stateVar]) {
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 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);
this['marginT' + state] = marginT;
this['marginB' + state] = marginB;
this['marginL' + state] = marginL;
this['marginR' + state] = marginR;
this['padT' + state] = padT;
this['padB' + state] = padB;
this['padL' + state] = padL;
this['padR' + state] = padR;
marginV.appendChild(marginT);
marginV.appendChild(marginB);
marginV.appendChild(marginL);
marginV.appendChild(marginR);
paddingV.appendChild(padT);
paddingV.appendChild(padB);
paddingV.appendChild(padL);
paddingV.appendChild(padR);
offsetViewer.appendChild(marginV);
offsetViewer.appendChild(paddingV);
this[stateVar] = '1';
}
var unit = 'px';
var marginLeftSt = style.marginLeft.replace(unit, '');
var marginTopSt = parseInt(style.marginTop.replace(unit, ''));
var marginBottomSt = parseInt(style.marginBottom.replace(unit, ''));
var mtStyle = marginT.style;
var mbStyle = marginB.style;
var mlStyle = marginL.style;
var mrStyle = marginR.style;
var ptStyle = padT.style;
var pbStyle = padB.style;
var plStyle = padL.style;
var prStyle = padR.style;
var posLeft = parseInt(pos.left);
// Margin style
mtStyle.height = style.marginTop;
mtStyle.width = style.width;
mtStyle.top = pos.top - style.marginTop.replace(unit, '') + unit;
mtStyle.left = posLeft + unit;
mbStyle.height = style.marginBottom;
mbStyle.width = style.width;
mbStyle.top = pos.top + pos.height + unit;
mbStyle.left = posLeft + unit;
var marginSideH = pos.height + marginTopSt + marginBottomSt + unit;
var marginSideT = pos.top - marginTopSt + unit;
mlStyle.height = marginSideH;
mlStyle.width = style.marginLeft;
mlStyle.top = marginSideT;
mlStyle.left = posLeft - marginLeftSt + unit;
mrStyle.height = marginSideH;
mrStyle.width = style.marginRight;
mrStyle.top = marginSideT;
mrStyle.left = posLeft + pos.width + unit;
// Padding style
var padTop = parseInt(style.paddingTop.replace(unit, ''));
ptStyle.height = style.paddingTop;
ptStyle.width = style.width;
ptStyle.top = pos.top + unit;
ptStyle.left = posLeft + unit;
var padBot = parseInt(style.paddingBottom.replace(unit, ''));
pbStyle.height = style.paddingBottom;
pbStyle.width = style.width;
pbStyle.top = pos.top + pos.height - padBot + unit;
pbStyle.left = posLeft + unit;
var padSideH = (pos.height - padBot - padTop) + unit;
var padSideT = pos.top + padTop + unit;
plStyle.height = padSideH;
plStyle.width = style.paddingLeft;
plStyle.top = padSideT;
plStyle.left = pos.left + unit;
var padRight = parseInt(style.paddingRight.replace(unit, ''));
prStyle.height = padSideH;
prStyle.width = style.paddingRight;
prStyle.top = padSideT;
prStyle.left = pos.left + pos.width - padRight + unit;
},
stop: function(editor, sender, opts) {
var opt = opts || {};
var state = opt.state || '';
var method = this.getOffsetMethod(state);
var canvas = editor.Canvas;
var offsetViewer = canvas[method]();
offsetViewer.style.display = 'none';
},
};
});
module.exports = {
getOffsetMethod(state) {
var method = state || '';
return 'get' + method + 'OffsetViewerEl';
},
run(editor, sender, opts) {
var opt = opts || {};
var state = opt.state || '';
var config = editor.getConfig();
if (!config.showOffsets ||
(!config.showOffsetsSelected && state == 'Fixed') ) {
return;
}
var canvas = editor.Canvas;
var el = opt.el || '';
var pos = opt.elPos || canvas.getElementPos(el);
var style = window.getComputedStyle(el);
var ppfx = this.ppfx;
var stateVar = state + 'State';
var method = this.getOffsetMethod(state);
var offsetViewer = canvas[method]();
offsetViewer.style.display = 'block';
var marginT = this['marginT' + state];
var marginB = this['marginB' + state];
var marginL = this['marginL' + state];
var marginR = this['marginR' + state];
var padT = this['padT' + state];
var padB = this['padB' + state];
var padL = this['padL' + state];
var padR = this['padR' + state];
if(!this[stateVar]) {
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 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);
this['marginT' + state] = marginT;
this['marginB' + state] = marginB;
this['marginL' + state] = marginL;
this['marginR' + state] = marginR;
this['padT' + state] = padT;
this['padB' + state] = padB;
this['padL' + state] = padL;
this['padR' + state] = padR;
marginV.appendChild(marginT);
marginV.appendChild(marginB);
marginV.appendChild(marginL);
marginV.appendChild(marginR);
paddingV.appendChild(padT);
paddingV.appendChild(padB);
paddingV.appendChild(padL);
paddingV.appendChild(padR);
offsetViewer.appendChild(marginV);
offsetViewer.appendChild(paddingV);
this[stateVar] = '1';
}
var unit = 'px';
var marginLeftSt = style.marginLeft.replace(unit, '');
var marginTopSt = parseInt(style.marginTop.replace(unit, ''));
var marginBottomSt = parseInt(style.marginBottom.replace(unit, ''));
var mtStyle = marginT.style;
var mbStyle = marginB.style;
var mlStyle = marginL.style;
var mrStyle = marginR.style;
var ptStyle = padT.style;
var pbStyle = padB.style;
var plStyle = padL.style;
var prStyle = padR.style;
var posLeft = parseInt(pos.left);
// Margin style
mtStyle.height = style.marginTop;
mtStyle.width = style.width;
mtStyle.top = pos.top - style.marginTop.replace(unit, '') + unit;
mtStyle.left = posLeft + unit;
mbStyle.height = style.marginBottom;
mbStyle.width = style.width;
mbStyle.top = pos.top + pos.height + unit;
mbStyle.left = posLeft + unit;
var marginSideH = pos.height + marginTopSt + marginBottomSt + unit;
var marginSideT = pos.top - marginTopSt + unit;
mlStyle.height = marginSideH;
mlStyle.width = style.marginLeft;
mlStyle.top = marginSideT;
mlStyle.left = posLeft - marginLeftSt + unit;
mrStyle.height = marginSideH;
mrStyle.width = style.marginRight;
mrStyle.top = marginSideT;
mrStyle.left = posLeft + pos.width + unit;
// Padding style
var padTop = parseInt(style.paddingTop.replace(unit, ''));
ptStyle.height = style.paddingTop;
ptStyle.width = style.width;
ptStyle.top = pos.top + unit;
ptStyle.left = posLeft + unit;
var padBot = parseInt(style.paddingBottom.replace(unit, ''));
pbStyle.height = style.paddingBottom;
pbStyle.width = style.width;
pbStyle.top = pos.top + pos.height - padBot + unit;
pbStyle.left = posLeft + unit;
var padSideH = (pos.height - padBot - padTop) + unit;
var padSideT = pos.top + padTop + unit;
plStyle.height = padSideH;
plStyle.width = style.paddingLeft;
plStyle.top = padSideT;
plStyle.left = pos.left + unit;
var padRight = parseInt(style.paddingRight.replace(unit, ''));
prStyle.height = padSideH;
prStyle.width = style.paddingRight;
prStyle.top = padSideT;
prStyle.left = pos.left + pos.width - padRight + unit;
},
stop(editor, sender, opts) {
var opt = opts || {};
var state = opt.state || '';
var method = this.getOffsetMethod(state);
var canvas = editor.Canvas;
var offsetViewer = canvas[method]();
offsetViewer.style.display = 'none';
},
};

18
src/commands/view/SwitchVisibility.js

@ -1,13 +1,11 @@
define(function() {
return {
module.exports = {
run: function(ed) {
ed.Canvas.getBody().className = this.ppfx + 'dashed';
},
run(ed) {
ed.Canvas.getBody().className = this.ppfx + 'dashed';
},
stop: function(ed) {
ed.Canvas.getBody().className = "";
}
stop(ed) {
ed.Canvas.getBody().className = "";
}
};
});
};

60
src/commands/view/TextComponent.js

@ -1,35 +1,31 @@
define(['backbone', './CreateComponent'],
function(Backbone, CreateComponent) {
/**
* @class TextComponent
* @private
* */
return _.extend({}, CreateComponent, {
var Backbone = require('backbone');
var CreateComponent = require('./CreateComponent');
/**
* This event is triggered at the beginning of a draw operation
* @param {Object} component Object component before creation
* @private
* */
beforeDraw: function(component){
component.type = 'text';
if(!component.style)
component.style = {};
component.style.padding = '10px';
},
module.exports = _.extend({}, CreateComponent, {
/**
* This event is triggered at the end of a draw operation
* @param {Object} model Component model created
* @private
* */
afterDraw: function(model){
if(!model || !model.set)
return;
model.trigger('focus');
if(this.sender)
this.sender.set('active', false);
},
/**
* This event is triggered at the beginning of a draw operation
* @param {Object} component Object component before creation
* @private
* */
beforeDraw(component) {
component.type = 'text';
if(!component.style)
component.style = {};
component.style.padding = '10px';
},
});
});
/**
* This event is triggered at the end of a draw operation
* @param {Object} model Component model created
* @private
* */
afterDraw(model) {
if(!model || !model.set)
return;
model.trigger('focus');
if(this.sender)
this.sender.set('active', false);
},
});

59
src/config/require-config.js

@ -1,59 +0,0 @@
require.config({
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: [ 'underscore', 'jquery' ],
exports: 'Backbone'
},
rte: {
deps: [ 'jquery' ],
exports: 'rte'
},
backboneUndo: {
deps: ['backbone'],
exports: 'backboneUndo'
},
keymaster: {
exports: 'keymaster'
},
},
paths: {
jquery: '../node_modules/jquery/dist/jquery',
underscore: '../node_modules/underscore/underscore',
backbone: '../node_modules/backbone/backbone',
backboneUndo: '../node_modules/backbone-undo/Backbone.Undo',
keymaster: '../node_modules/keymaster/keymaster',
text: '../node_modules/requirejs-text/text',
Spectrum: '../node_modules/spectrum-colorpicker/spectrum',
codemirror: '../node_modules/codemirror',
formatting: '../node_modules/codemirror-formatting/formatting',
},
packages : [
{ name: 'GrapesJS', location: 'grapesjs' },
{ name: 'Abstract', location: 'domain_abstract' },
{ name: 'Editor', location: 'editor', },
{ name: 'AssetManager', location: 'asset_manager', },
{ name: 'BlockManager', location: 'block_manager', },
{ name: 'TraitManager', location: 'trait_manager', },
{ name: 'StyleManager', location: 'style_manager', },
{ name: 'DeviceManager', location: 'device_manager', },
{ name: 'StorageManager', location: 'storage_manager', },
{ name: 'PluginManager', location: 'plugin_manager', },
{ name: 'Navigator', location: 'navigator', },
{ name: 'DomComponents', location: 'dom_components', },
{ name: 'RichTextEditor', location: 'rich_text_editor', },
{ name: 'SelectorManager', location: 'selector_manager', },
{ name: 'ModalDialog', location: 'modal_dialog', },
{ name: 'CodeManager', location: 'code_manager', },
{ name: 'CssComposer', location: 'css_composer', },
{ name: 'Commands', location: 'commands', },
{ name: 'Canvas', location: 'canvas', },
{ name: 'Panels', location: 'panels', },
{ name: 'Parser', location: 'parser', },
{ name: 'Utils', location: 'utils', }
]
});

18
src/css_composer/config/config.js

@ -1,14 +1,12 @@
define(function () {
return {
module.exports = {
// Style prefix
stylePrefix: 'css-',
// Style prefix
stylePrefix: 'css-',
// Custom CSS string to render on top
'staticRules': '',
// Custom CSS string to render on top
'staticRules': '',
// Default CSS style
rules: [],
// Default CSS style
rules: [],
};
});
};

272
src/css_composer/index.js

@ -0,0 +1,272 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [load](#load)
* * [store](#store)
*
* This module contains and manage CSS rules for the template inside the canvas
* Before using the methods you should get first the module from the editor instance, in this way:
*
* ```js
* var cssComposer = editor.CssComposer;
* ```
*
* @module CssComposer
* @param {Object} config Configurations
* @param {string|Array<Object>} [config.rules=[]] CSS string or an array of rule objects
* @example
* ...
* CssComposer: {
* rules: '.myClass{ color: red}',
* }
*/
module.exports = () => {
var c = {},
defaults = require('./config/config'),
CssRule = require('./model/CssRule'),
CssRules = require('./model/CssRules'),
Selectors = require('./model/Selectors'),
CssRulesView = require('./view/CssRulesView');
var rules, rulesView;
return {
Selectors,
/**
* Name of the module
* @type {String}
* @private
*/
name: 'CssComposer',
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey() {
var keys = [];
var smc = (c.stm && c.stm.getConfig()) || {};
if(smc.storeCss)
keys.push('css');
if(smc.storeStyles)
keys.push('styles');
return keys;
},
/**
* Initializes module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
var elStyle = (c.em && c.em.config.style) || '';
c.rules = elStyle || c.rules;
c.sm = c.em; // TODO Refactor
rules = new CssRules([], c);
rules.add(c.rules);
rulesView = new CssRulesView({
collection: rules,
config: c,
});
return this;
},
/**
* On load callback
* @private
*/
onLoad() {
if(c.stm && c.stm.getConfig().autoload)
this.load();
if(c.stm && c.stm.isAutosave())
c.em.listenRules(this.getAll());
},
/**
* Load data from the passed object, if the object is empty will try to fetch them
* autonomously from the storage manager.
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded rules
*/
load(data) {
var d = data || '';
if(!d && c.stm)
d = c.em.getCacheLoad();
var obj = '';
if(d.styles) {
try{
obj = JSON.parse(d.styles);
}catch(err){}
} else if (d.css) {
obj = c.em.get('Parser').parseCss(d.css);
}
if(obj)
rules.reset(obj);
return obj;
},
/**
* Store data to the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
*/
store(noStore) {
if(!c.stm)
return;
var obj = {};
var keys = this.storageKey();
if(keys.indexOf('css') >= 0)
obj.css = c.em.getCss();
if(keys.indexOf('styles') >= 0)
obj.styles = JSON.stringify(rules);
if(!noStore)
c.stm.store(obj);
return obj;
},
/**
* Add new rule to the collection, if not yet exists with the same selectors
* @param {Array<Selector>} selectors Array of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} opts Other options for the rule
* @return {Model}
* @example
* var sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2');
* var rule = cssComposer.add([sel1, sel2], 'hover');
* rule.set('style', {
* width: '100px',
* color: '#fff',
* });
* */
add(selectors, state, width, opts) {
var s = state || '';
var w = width || '';
var opt = opts || {};
var rule = this.get(selectors, s, w, opt);
if(rule)
return rule;
else {
opt.state = s;
opt.mediaText = w;
opt.selectors = '';
rule = new CssRule(opt);
rule.get('selectors').add(selectors);
rules.add(rule);
return rule;
}
},
/**
* Get the rule
* @param {Array<Selector>} selectors Array of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} ruleProps Other rule props
* @return {Model|null}
* @example
* var sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2');
* var rule = cssComposer.get([sel1, sel2], 'hover');
* // Update the style
* rule.set('style', {
* width: '300px',
* color: '#000',
* });
* */
get(selectors, state, width, ruleProps) {
var rule = null;
rules.each(m => {
if(rule)
return;
if(m.compare(selectors, state, width, ruleProps))
rule = m;
});
return rule;
},
/**
* Get the collection of rules
* @return {Collection}
* */
getAll() {
return rules;
},
/**
* Add a raw collection of rule objects
* This method overrides styles, in case, of already defined rule
* @param {Array<Object>} data Array of rule objects, eg . [{selectors: ['class1'], style: {....}}, ..]
* @param {Object} opts Options
* @return {Array<Model>}
* @private
*/
addCollection(data, opts) {
var opt = opts || {};
var result = [];
var d = data instanceof Array ? data : [data];
for (var i = 0, l = d.length; i < l; i++) {
var rule = d[i] || {};
if(!rule.selectors)
continue;
var sm = c.em && c.em.get('SelectorManager');
if(!sm)
console.warn('Selector Manager not found');
var sl = rule.selectors;
var sels = sl instanceof Array ? sl : [sl];
var newSels = [];
for (var j = 0, le = sels.length; j < le; j++) {
var selec = sm.add(sels[j]);
newSels.push(selec);
}
var model = this.add(newSels, rule.state, rule.mediaText, rule);
if (opt.extend) {
var newStyle = _.extend({}, model.get('style'), rule.style || {});
model.set('style', newStyle);
} else {
model.set('style', rule.style || {});
}
result.push(model);
}
return result;
},
/**
* Render the block of CSS rules
* @return {HTMLElement}
* @private
*/
render() {
return rulesView.render().el;
}
};
};

274
src/css_composer/main.js

@ -1,274 +0,0 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
* * [load](#load)
* * [store](#store)
*
* This module contains and manage CSS rules for the template inside the canvas
* Before using the methods you should get first the module from the editor instance, in this way:
*
* ```js
* var cssComposer = editor.CssComposer;
* ```
*
* @module CssComposer
* @param {Object} config Configurations
* @param {string|Array<Object>} [config.rules=[]] CSS string or an array of rule objects
* @example
* ...
* CssComposer: {
* rules: '.myClass{ color: red}',
* }
*/
define(function(require) {
return function() {
var c = {},
defaults = require('./config/config'),
CssRule = require('./model/CssRule'),
CssRules = require('./model/CssRules'),
Selectors = require('./model/Selectors'),
CssRulesView = require('./view/CssRulesView');
var rules, rulesView;
return {
Selectors: Selectors,
/**
* Name of the module
* @type {String}
* @private
*/
name: 'CssComposer',
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey: function(){
var keys = [];
var smc = (c.stm && c.stm.getConfig()) || {};
if(smc.storeCss)
keys.push('css');
if(smc.storeStyles)
keys.push('styles');
return keys;
},
/**
* Initializes module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
init: function(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
var elStyle = (c.em && c.em.config.style) || '';
c.rules = elStyle || c.rules;
c.sm = c.em; // TODO Refactor
rules = new CssRules([], c);
rules.add(c.rules);
rulesView = new CssRulesView({
collection: rules,
config: c,
});
return this;
},
/**
* On load callback
* @private
*/
onLoad: function(){
if(c.stm && c.stm.getConfig().autoload)
this.load();
if(c.stm && c.stm.isAutosave())
c.em.listenRules(this.getAll());
},
/**
* Load data from the passed object, if the object is empty will try to fetch them
* autonomously from the storage manager.
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded rules
*/
load: function(data) {
var d = data || '';
if(!d && c.stm)
d = c.em.getCacheLoad();
var obj = '';
if(d.styles) {
try{
obj = JSON.parse(d.styles);
}catch(err){}
} else if (d.css) {
obj = c.em.get('Parser').parseCss(d.css);
}
if(obj)
rules.reset(obj);
return obj;
},
/**
* Store data to the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
*/
store: function(noStore){
if(!c.stm)
return;
var obj = {};
var keys = this.storageKey();
if(keys.indexOf('css') >= 0)
obj.css = c.em.getCss();
if(keys.indexOf('styles') >= 0)
obj.styles = JSON.stringify(rules);
if(!noStore)
c.stm.store(obj);
return obj;
},
/**
* Add new rule to the collection, if not yet exists with the same selectors
* @param {Array<Selector>} selectors Array of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} opts Other options for the rule
* @return {Model}
* @example
* var sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2');
* var rule = cssComposer.add([sel1, sel2], 'hover');
* rule.set('style', {
* width: '100px',
* color: '#fff',
* });
* */
add: function(selectors, state, width, opts) {
var s = state || '';
var w = width || '';
var opt = opts || {};
var rule = this.get(selectors, s, w, opt);
if(rule)
return rule;
else {
opt.state = s;
opt.mediaText = w;
opt.selectors = '';
rule = new CssRule(opt);
rule.get('selectors').add(selectors);
rules.add(rule);
return rule;
}
},
/**
* Get the rule
* @param {Array<Selector>} selectors Array of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} ruleProps Other rule props
* @return {Model|null}
* @example
* var sm = editor.SelectorManager;
* var sel1 = sm.add('myClass1');
* var sel2 = sm.add('myClass2');
* var rule = cssComposer.get([sel1, sel2], 'hover');
* // Update the style
* rule.set('style', {
* width: '300px',
* color: '#000',
* });
* */
get: function(selectors, state, width, ruleProps) {
var rule = null;
rules.each(function(m) {
if(rule)
return;
if(m.compare(selectors, state, width, ruleProps))
rule = m;
});
return rule;
},
/**
* Get the collection of rules
* @return {Collection}
* */
getAll: function() {
return rules;
},
/**
* Add a raw collection of rule objects
* This method overrides styles, in case, of already defined rule
* @param {Array<Object>} data Array of rule objects, eg . [{selectors: ['class1'], style: {....}}, ..]
* @param {Object} opts Options
* @return {Array<Model>}
* @private
*/
addCollection: function(data, opts) {
var opt = opts || {};
var result = [];
var d = data instanceof Array ? data : [data];
for (var i = 0, l = d.length; i < l; i++) {
var rule = d[i] || {};
if(!rule.selectors)
continue;
var sm = c.em && c.em.get('SelectorManager');
if(!sm)
console.warn('Selector Manager not found');
var sl = rule.selectors;
var sels = sl instanceof Array ? sl : [sl];
var newSels = [];
for (var j = 0, le = sels.length; j < le; j++) {
var selec = sm.add(sels[j]);
newSels.push(selec);
}
var model = this.add(newSels, rule.state, rule.mediaText, rule);
if (opt.extend) {
var newStyle = _.extend({}, model.get('style'), rule.style || {});
model.set('style', newStyle);
} else {
model.set('style', rule.style || {});
}
result.push(model);
}
return result;
},
/**
* Render the block of CSS rules
* @return {HTMLElement}
* @private
*/
render: function() {
return rulesView.render().el;
}
};
};
});

180
src/css_composer/model/CssRule.js

@ -1,93 +1,89 @@
define(['backbone', './Selectors'],
function (Backbone, Selectors) {
return Backbone.Model.extend({
defaults: {
// Css selectors
selectors: {},
// Additional string css selectors
selectorsAdd: '',
// Css properties style
style: {},
// On which device width this rule should be rendered, eg. @media (max-width: 1000px)
mediaText: '',
// State of the rule, eg: hover | pressed | focused
state: '',
// Indicates if the rule is stylable
stylable: true,
},
initialize: function(c, opt) {
this.config = c || {};
this.sm = opt ? opt.sm || {} : {};
this.slct = this.config.selectors || [];
if(this.sm.get){
var slct = [];
for(var i = 0; i < this.slct.length; i++)
slct.push(this.sm.get('SelectorManager').add(this.slct[i].name || this.slct[i]));
this.slct = slct;
}
this.set('selectors', new Selectors(this.slct));
},
/**
* Compare the actual model with parameters
* @param {Object} selectors Collection of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} ruleProps Other rule props
* @return {Boolean}
* @private
*/
compare: function(selectors, state, width, ruleProps){
var otherRule = ruleProps || {};
var st = state || '';
var wd = width || '';
var selectorsAdd = otherRule.selectorsAdd || '';
var cId = 'cid';
//var a1 = _.pluck(selectors.models || selectors, cId);
//var a2 = _.pluck(this.get('selectors').models, cId);
if(!(selectors instanceof Array) && !selectors.models)
selectors = [selectors];
var a1 = _.map((selectors.models || selectors), function(model) {
return model.get('name');
});
var a2 = _.map(this.get('selectors').models, function(model) {
return model.get('name');
});
var f = false;
if(a1.length !== a2.length)
return f;
for (var i = 0; i < a1.length; i++) {
var re = 0;
for (var j = 0; j < a2.length; j++) {
if (a1[i] === a2[j])
re = 1;
}
if(re === 0)
return f;
}
if(this.get('state') !== st)
return f;
if(this.get('mediaText') !== wd)
return f;
if(this.get('selectorsAdd') !== selectorsAdd)
return f;
return true;
},
});
var Backbone = require('backbone');
var Selectors = require('./Selectors');
module.exports = Backbone.Model.extend({
defaults: {
// Css selectors
selectors: {},
// Additional string css selectors
selectorsAdd: '',
// Css properties style
style: {},
// On which device width this rule should be rendered, eg. @media (max-width: 1000px)
mediaText: '',
// State of the rule, eg: hover | pressed | focused
state: '',
// Indicates if the rule is stylable
stylable: true,
},
initialize(c, opt) {
this.config = c || {};
this.sm = opt ? opt.sm || {} : {};
this.slct = this.config.selectors || [];
if(this.sm.get){
var slct = [];
for(var i = 0; i < this.slct.length; i++)
slct.push(this.sm.get('SelectorManager').add(this.slct[i].name || this.slct[i]));
this.slct = slct;
}
this.set('selectors', new Selectors(this.slct));
},
/**
* Compare the actual model with parameters
* @param {Object} selectors Collection of selectors
* @param {String} state Css rule state
* @param {String} width For which device this style is oriented
* @param {Object} ruleProps Other rule props
* @return {Boolean}
* @private
*/
compare(selectors, state, width, ruleProps) {
var otherRule = ruleProps || {};
var st = state || '';
var wd = width || '';
var selectorsAdd = otherRule.selectorsAdd || '';
var cId = 'cid';
//var a1 = _.pluck(selectors.models || selectors, cId);
//var a2 = _.pluck(this.get('selectors').models, cId);
if(!(selectors instanceof Array) && !selectors.models)
selectors = [selectors];
var a1 = _.map((selectors.models || selectors), model => model.get('name'));
var a2 = _.map(this.get('selectors').models, model => model.get('name'));
var f = false;
if(a1.length !== a2.length)
return f;
for (var i = 0; i < a1.length; i++) {
var re = 0;
for (var j = 0; j < a2.length; j++) {
if (a1[i] === a2[j])
re = 1;
}
if(re === 0)
return f;
}
if(this.get('state') !== st)
return f;
if(this.get('mediaText') !== wd)
return f;
if(this.get('selectorsAdd') !== selectorsAdd)
return f;
return true;
},
});

48
src/css_composer/model/CssRules.js

@ -1,34 +1,34 @@
define(['backbone','./CssRule'],
function (Backbone, CssRule) {
return Backbone.Collection.extend({
var Backbone = require('backbone');
var CssRule = require('./CssRule');
initialize: function(models, opt){
module.exports = Backbone.Collection.extend({
// Inject editor
if(opt && opt.sm)
this.editor = opt.sm;
initialize(models, opt) {
this.model = function(attrs, options) {
var model;
// Inject editor
if(opt && opt.sm)
this.editor = opt.sm;
if(!options.sm && opt && opt.sm)
options.sm = opt.sm;
this.model = (attrs, options) => {
var model;
switch(1){
default:
model = new CssRule(attrs, options);
}
if(!options.sm && opt && opt.sm)
options.sm = opt.sm;
return model;
};
switch(1){
default:
model = new CssRule(attrs, options);
}
},
return model;
};
add: function(models, opt){
if(typeof models === 'string')
models = this.editor.get('Parser').parseCss(models);
return Backbone.Collection.prototype.add.apply(this, [models, opt]);
},
},
add(models, opt) {
if(typeof models === 'string')
models = this.editor.get('Parser').parseCss(models);
return Backbone.Collection.prototype.add.apply(this, [models, opt]);
},
});
});

31
src/css_composer/model/Selectors.js

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

135
src/css_composer/view/CssRuleView.js

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

120
src/css_composer/view/CssRulesView.js

@ -1,61 +1,61 @@
define(['backbone','./CssRuleView'],
function (Backbone, CssRuleView) {
return Backbone.View.extend({
initialize: function(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.className = this.pfx + 'rules';
this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render );
},
/**
* Add to collection
* @param {Object} model
* @private
* */
addTo: function(model){
//console.log('Added');
this.addToCollection(model);
},
/**
* Add new object to collection
* @param {Object} model
* @param {Object} fragmentEl
* @return {Object}
* @private
* */
addToCollection: function(model, fragmentEl){
var fragment = fragmentEl || null;
var viewObject = CssRuleView;
var view = new viewObject({
model: model,
config: this.config,
});
var rendered = view.render().el;
if(fragment)
fragment.appendChild( rendered );
else
this.$el.append(rendered);
return rendered;
},
render: function() {
var fragment = document.createDocumentFragment();
this.$el.empty();
this.collection.each(function(model){
this.addToCollection(model, fragment);
}, this);
this.$el.append(fragment);
this.$el.attr('class', this.className);
return this;
}
});
var Backbone = require('backbone');
var CssRuleView = require('./CssRuleView');
module.exports = Backbone.View.extend({
initialize(o) {
this.config = o.config || {};
this.pfx = this.config.stylePrefix || '';
this.className = this.pfx + 'rules';
this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render );
},
/**
* Add to collection
* @param {Object} model
* @private
* */
addTo(model) {
//console.log('Added');
this.addToCollection(model);
},
/**
* Add new object to collection
* @param {Object} model
* @param {Object} fragmentEl
* @return {Object}
* @private
* */
addToCollection(model, fragmentEl) {
var fragment = fragmentEl || null;
var viewObject = CssRuleView;
var view = new viewObject({
model,
config: this.config,
});
var rendered = view.render().el;
if(fragment)
fragment.appendChild( rendered );
else
this.$el.append(rendered);
return rendered;
},
render() {
var fragment = document.createDocumentFragment();
this.$el.empty();
this.collection.each(function(model){
this.addToCollection(model, fragment);
}, this);
this.$el.append(fragment);
this.$el.attr('class', this.className);
return this;
}
});

369
src/demo.js

@ -1,369 +0,0 @@
require(['config/require-config'], function() {
require(['grapesjs/main'],function (grapesjs){
var editor = grapesjs.init(
{
allowScripts: 1,
showOffsets: 1,
autorender: 0,
noticeOnUnload: 0,
container : '#gjs',
height: '100%',
fromElement: true,
clearOnRender: 0,
storageManager:{
autoload: 0,
storeComponents: 1,
storeStyles: 1,
},
/*
components: [{
//script: 'var el = this; setInterval(function(){el.style.marginLeft = Math.random() * 50 +"px";}, 1000)',
script: 'loadScript = function(){console.log("loaded INSIDE", $);}',
style: {
background: 'red',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
script: 'this.innerHTML= "test1";',
style: {
background: 'blue',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
script: 'this.innerHTML= "test2";',
style: {
background: 'green',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
style: {
background: 'yellow',
width:'500px',
height:'100px',
margin: '50px auto',
}
},{
type: 'text',
style:{
width:'100px',
height:'100px',
margin: '50px auto',
},
traits: ['title'],
components: [{
type: 'textnode',
content: 'text node row',
},{
type: 'textnode',
content: ', another text node',
},{
type: 'link',
content: 'someLink',
},{
type: 'textnode',
content: " More text node --- ",
}],
}],
*/
commands: {
defaults : [{
id: 'open-github',
run: function(editor, sender){
sender.set('active',false);
window.open('https://github.com/artf/grapesjs','_blank');
}
},{
id: 'undo',
run: function(editor, sender){
sender.set('active',false);
editor.UndoManager.undo(true);
}
},{
id: 'redo',
run: function(editor, sender){
sender.set('active',false);
editor.UndoManager.redo(true);
}
},{
id: 'clean-all',
run: function(editor, sender){
sender.set('active',false);
if(confirm('Are you sure to clean the canvas?')){
var comps = editor.DomComponents.clear();
}
}
}],
},
assetManager: {
storageType : '',
storeOnChange : true,
storeAfterUpload : true,
assets : [
{ type: 'image', src : 'http://placehold.it/350x250/78c5d6/fff/image1.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/459ba8/fff/image2.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/79c267/fff/image3.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/c5d647/fff/image4.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/f28c33/fff/image5.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/e868a2/fff/image6.jpg', height:350, width:250},
{ type: 'image', src : 'http://placehold.it/350x250/cc4360/fff/image7.jpg', height:350, width:250},
{ type: 'image', src : './img/work-desk.jpg', date: '2015-02-01',height:1080, width:1728},
{ type: 'image', src : './img/phone-app.png', date: '2015-02-01',height:650, width:320},
{ type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728},
]
},
styleManager : {
sectors: [{
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom'],
},{
name: 'Dimension',
open: false,
buildProps: ['width', 'height', 'max-width', 'min-height', 'margin', 'padding'],
},{
name: 'Typography',
open: false,
buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing', 'color', 'line-height', 'text-align', 'text-shadow'],
properties: [{
property: 'text-align',
list : [
{value: 'left', className: 'fa fa-align-left'},
{value: 'center', className: 'fa fa-align-center' },
{value: 'right', className: 'fa fa-align-right'},
{value: 'justify', className: 'fa fa-align-justify'}
],
}]
},{
name: 'Decorations',
open: false,
buildProps: ['border-radius-c', 'background-color', 'border-radius', 'border', 'box-shadow', 'background'],
},{
name: 'Extra',
open: false,
buildProps: ['transition', 'perspective', 'transform'],
},{
name: 'Flex',
open: false,
properties: [{
name : 'Flex Container',
property : 'display',
type : 'select',
defaults : 'block',
list : [{
value : 'block',
name : 'Disable',
},{
value : 'flex',
name : 'Enable',
}],
},{
name: 'Flex Parent',
property: 'label-parent-flex',
},{
name : 'Direction',
property : 'flex-direction',
type : 'radio',
defaults : 'row',
list : [{
value : 'row',
name : 'Row',
className : 'icons-flex icon-dir-row',
title : 'Row',
},{
value : 'row-reverse',
name : 'Row reverse',
className : 'icons-flex icon-dir-row-rev',
title : 'Row reverse',
},{
value : 'column',
name : 'Column',
title : 'Column',
className : 'icons-flex icon-dir-col',
},{
value : 'column-reverse',
name : 'Column reverse',
title : 'Column reverse',
className : 'icons-flex icon-dir-col-rev',
}],
},{
name : 'Wrap',
property : 'flex-wrap',
type : 'radio',
defaults : 'nowrap',
list : [{
value : 'nowrap',
title : 'Single line',
},{
value : 'wrap',
title : 'Multiple lines',
},{
value : 'wrap-reverse',
title : 'Multiple lines reverse',
}],
},{
name : 'Justify',
property : 'justify-content',
type : 'radio',
defaults : 'flex-start',
list : [{
value : 'flex-start',
className : 'icons-flex icon-just-start',
title : 'Start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-just-end',
},{
value : 'space-between',
title : 'Space between',
className : 'icons-flex icon-just-sp-bet',
},{
value : 'space-around',
title : 'Space around',
className : 'icons-flex icon-just-sp-ar',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-just-sp-cent',
}],
},{
name : 'Align',
property : 'align-items',
type : 'radio',
defaults : 'center',
list : [{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
},{
name: 'Flex Children',
property: 'label-parent-flex',
},{
name: 'Order',
property: 'order',
type: 'integer',
defaults : 0,
min: 0
},{
name : 'Flex',
property : 'flex',
type : 'composite',
properties : [{
name: 'Grow',
property: 'flex-grow',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Shrink',
property: 'flex-shrink',
type: 'integer',
defaults : 0,
min: 0
},{
name: 'Basis',
property: 'flex-basis',
type: 'integer',
units: ['px','%',''],
unit: '',
defaults : 'auto',
}],
},{
name : 'Align',
property : 'align-self',
type : 'radio',
defaults : 'auto',
list : [{
value : 'auto',
name : 'Auto',
},{
value : 'flex-start',
title : 'Start',
className : 'icons-flex icon-al-start',
},{
value : 'flex-end',
title : 'End',
className : 'icons-flex icon-al-end',
},{
value : 'stretch',
title : 'Stretch',
className : 'icons-flex icon-al-str',
},{
value : 'center',
title : 'Center',
className : 'icons-flex icon-al-center',
}],
}]
}
],
},
});
window.editor = editor;
var pnm = editor.Panels;
pnm.addButton('options', [{
id: 'undo',
className: 'fa fa-undo icon-undo',
command: function(editor, sender) {
sender.set('active', 0);
editor.UndoManager.undo(1);
},
attributes: { title: 'Undo (CTRL/CMD + Z)'}
},{
id: 'redo',
className: 'fa fa-repeat icon-redo',
command: function(editor, sender) {
sender.set('active', 0);
editor.UndoManager.redo(1);
},
attributes: { title: 'Redo (CTRL/CMD + SHIFT + Z)' }
},{
id: 'clean-all',
className: 'fa fa-trash icon-blank',
command: function(editor, sender) {
if(sender) sender.set('active', false);
if(confirm('Are you sure to clean the canvas?')) {
var comps = editor.DomComponents.clear();
localStorage.clear();
}
},
attributes: { title: 'Empty canvas' }
}]);
editor.render();
});
});

10
src/device_manager/config/config.js

@ -1,9 +1,7 @@
define(function () {
return {
module.exports = {
devices: [],
devices: [],
deviceLabel: 'Device',
deviceLabel: 'Device',
};
});
};

111
src/device_manager/index.js

@ -0,0 +1,111 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var deviceManager = editor.DeviceManager;
* ```
*
* @module DeviceManager
*/
module.exports = () => {
var c = {},
defaults = require('./config/config'),
Devices = require('./model/Devices'),
DevicesView = require('./view/DevicesView');
var devices, view;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'DeviceManager',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @param {Array<Object>} [config.devices=[]] Default devices
* @example
* ...
* {
* devices: [
* {name: 'Desktop', width: ''}
* {name: 'Tablet', width: '991px'}
* ],
* }
* ...
* @return {this}
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
devices = new Devices(c.devices);
view = new DevicesView({
collection: devices,
config: c
});
return this;
},
/**
* Add new device to the collection. URLs are supposed to be unique
* @param {string} name Device name
* @param {string} width Width of the device
* @param {Object} opts Custom options
* @return {Device} Added device
* @example
* deviceManager.add('Tablet', '900px');
*/
add(name, width, opts) {
var obj = opts || {};
obj.name = name;
obj.width = width;
return devices.add(obj);
},
/**
* Return device by name
* @param {string} name Name of the device
* @example
* var device = deviceManager.get('Tablet');
* console.log(JSON.stringify(device));
* // {name: 'Tablet', width: '900px'}
*/
get(name) {
return devices.get(name);
},
/**
* Return all devices
* @return {Collection}
* @example
* var devices = deviceManager.getAll();
* console.log(JSON.stringify(devices));
* // [{name: 'Desktop', width: ''}, ...]
*/
getAll() {
return devices;
},
/**
* Render devices
* @return {string} HTML string
* @private
*/
render() {
return view.render().el;
},
};
};

115
src/device_manager/main.js

@ -1,115 +0,0 @@
/**
* * [add](#add)
* * [get](#get)
* * [getAll](#getall)
*
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var deviceManager = editor.DeviceManager;
* ```
*
* @module DeviceManager
*/
define(function(require) {
return function() {
var c = {},
defaults = require('./config/config'),
Devices = require('./model/Devices'),
DevicesView = require('./view/DevicesView');
var devices, view;
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'DeviceManager',
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @param {Array<Object>} [config.devices=[]] Default devices
* @example
* ...
* {
* devices: [
* {name: 'Desktop', width: ''}
* {name: 'Tablet', width: '991px'}
* ],
* }
* ...
* @return {this}
*/
init: function(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
devices = new Devices(c.devices);
view = new DevicesView({
collection: devices,
config: c
});
return this;
},
/**
* Add new device to the collection. URLs are supposed to be unique
* @param {string} name Device name
* @param {string} width Width of the device
* @param {Object} opts Custom options
* @return {Device} Added device
* @example
* deviceManager.add('Tablet', '900px');
*/
add: function(name, width, opts){
var obj = opts || {};
obj.name = name;
obj.width = width;
return devices.add(obj);
},
/**
* Return device by name
* @param {string} name Name of the device
* @example
* var device = deviceManager.get('Tablet');
* console.log(JSON.stringify(device));
* // {name: 'Tablet', width: '900px'}
*/
get: function(name){
return devices.get(name);
},
/**
* Return all devices
* @return {Collection}
* @example
* var devices = deviceManager.getAll();
* console.log(JSON.stringify(devices));
* // [{name: 'Desktop', width: ''}, ...]
*/
getAll: function(){
return devices;
},
/**
* Render devices
* @return {string} HTML string
* @private
*/
render: function(){
return view.render().el;
},
};
};
});

18
src/device_manager/model/Device.js

@ -1,14 +1,12 @@
define(['backbone'],
function(Backbone){
var Backbone = require('backbone');
return Backbone.Model.extend({
module.exports = Backbone.Model.extend({
idAttribute: 'name',
idAttribute: 'name',
defaults :{
name: '',
width: '',
},
defaults :{
name: '',
width: '',
},
});
});
});

11
src/device_manager/model/Devices.js

@ -1,9 +1,6 @@
define(['backbone','./Device'],
function (Backbone, Device) {
var Backbone = require('backbone');
var Device = require('./Device');
return Backbone.Collection.extend({
model: Device,
});
module.exports = Backbone.Collection.extend({
model: Device,
});

10
src/device_manager/template/devices.html

@ -1,10 +0,0 @@
<div class="<%= ppfx %>device-label"><%= deviceLabel %></div>
<div class="<%= ppfx %>field <%= ppfx %>select">
<span id="<%= ppfx %>input-holder">
<select class="<%= ppfx %>devices"></select>
</span>
<div class="<%= ppfx %>sel-arrow">
<div class="<%= ppfx %>d-s-arrow"></div>
</div>
</div>
<button style="display:none" class="<%= ppfx %>add-trasp">+</button>

152
src/device_manager/view/DevicesView.js

@ -1,82 +1,90 @@
define(['backbone', 'text!./../template/devices.html'],
function(Backbone, devicesTemplate) {
var Backbone = require('backbone');
return Backbone.View.extend({
module.exports = Backbone.View.extend({
template: _.template(devicesTemplate),
template: _.template(`
<div class="<%= ppfx %>device-label"><%= deviceLabel %></div>
<div class="<%= ppfx %>field <%= ppfx %>select">
<span id="<%= ppfx %>input-holder">
<select class="<%= ppfx %>devices"></select>
</span>
<div class="<%= ppfx %>sel-arrow">
<div class="<%= ppfx %>d-s-arrow"></div>
</div>
</div>
<button style="display:none" class="<%= ppfx %>add-trasp">+</button>`),
events: {
'change': 'updateDevice'
},
events: {
'change': 'updateDevice'
},
initialize: function(o) {
this.config = o.config || {};
this.em = this.config.em;
this.ppfx = this.config.pStylePrefix || '';
this.events['click .' + this.ppfx + 'add-trasp'] = this.startAdd;
this.listenTo(this.em, 'change:device', this.updateSelect);
this.delegateEvents();
},
initialize(o) {
this.config = o.config || {};
this.em = this.config.em;
this.ppfx = this.config.pStylePrefix || '';
this.events['click .' + this.ppfx + 'add-trasp'] = this.startAdd;
this.listenTo(this.em, 'change:device', this.updateSelect);
this.delegateEvents();
},
/**
* Start adding new device
* @return {[type]} [description]
* @private
*/
startAdd: function(){},
/**
* Start adding new device
* @return {[type]} [description]
* @private
*/
startAdd() {},
/**
* Update device of the editor
* @private
*/
updateDevice: function(){
var em = this.em;
if(em){
var devEl = this.devicesEl;
var val = devEl ? devEl.val() : '';
em.set('device', val);
}
},
/**
* Update select value on device update
* @private
*/
updateSelect: function(){
var em = this.em;
/**
* Update device of the editor
* @private
*/
updateDevice() {
var em = this.em;
if(em){
var devEl = this.devicesEl;
if(em && em.getDeviceModel && devEl){
var device = em.getDeviceModel();
var name = device ? device.get('name') : '';
devEl.val(name);
}
},
var val = devEl ? devEl.val() : '';
em.set('device', val);
}
},
/**
* Update select value on device update
* @private
*/
updateSelect() {
var em = this.em;
var devEl = this.devicesEl;
if(em && em.getDeviceModel && devEl){
var device = em.getDeviceModel();
var name = device ? device.get('name') : '';
devEl.val(name);
}
},
/**
* Return devices options
* @return {string} String of options
* @private
*/
getOptions: function(){
var result = '';
this.collection.each(function(device){
var name = device.get('name');
result += '<option value="' + name+ '">' + name + '</option>';
});
return result;
},
/**
* Return devices options
* @return {string} String of options
* @private
*/
getOptions() {
var result = '';
this.collection.each(device => {
var name = device.get('name');
result += '<option value="' + name+ '">' + name + '</option>';
});
return result;
},
render: function() {
var pfx = this.ppfx;
this.$el.html(this.template({
ppfx: pfx,
deviceLabel: this.config.deviceLabel
}));
this.devicesEl = this.$el.find('.' + pfx + 'devices');
this.devicesEl.append(this.getOptions());
this.el.className = pfx + 'devices-c';
return this;
},
render() {
var pfx = this.ppfx;
this.$el.html(this.template({
ppfx: pfx,
deviceLabel: this.config.deviceLabel
}));
this.devicesEl = this.$el.find('.' + pfx + 'devices');
this.devicesEl.append(this.getOptions());
this.el.className = pfx + 'devices-c';
return this;
},
});
});
});

48
src/dom_components/config/config.js

@ -1,32 +1,30 @@
define(function () {
return {
stylePrefix: 'comp-',
module.exports = {
stylePrefix: 'comp-',
wrapperId: 'wrapper',
wrapperId: 'wrapper',
// Default wrapper configuration
wrapper: {
//classes: ['body'],
removable: false,
copyable: false,
stylable: ['background','background-color','background-image', 'background-repeat','background-attachment','background-position'],
draggable: false,
badgable: false,
components: [],
},
// Default wrapper configuration
wrapper: {
//classes: ['body'],
removable: false,
copyable: false,
stylable: ['background','background-color','background-image', 'background-repeat','background-attachment','background-position'],
draggable: false,
badgable: false,
components: [],
},
// Could be used for default components
components: [],
// Could be used for default components
components: [],
rte: {},
rte: {},
// Class for new image component
imageCompClass : 'fa fa-picture-o',
// Class for new image component
imageCompClass : 'fa fa-picture-o',
// Open assets manager on create of image component
oAssetsOnCreate : true,
// Open assets manager on create of image component
oAssetsOnCreate : true,
// List of void elements
voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'],
};
});
// List of void elements
voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'],
};

395
src/dom_components/index.js

@ -0,0 +1,395 @@
/**
*
* * [getWrapper](#getwrapper)
* * [getComponents](#getcomponents)
* * [addComponent](#addcomponent)
* * [clear](#clear)
* * [load](#load)
* * [store](#store)
* * [render](#render)
*
* With this module is possible to manage components inside the canvas.
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var domComponents = editor.DomComponents;
* ```
*
* @module DomComponents
* @param {Object} config Configurations
* @param {string|Array<Object>} [config.components=[]] HTML string or an array of possible components
* @example
* ...
* domComponents: {
* components: '<div>Hello world!</div>',
* }
* // Or
* domComponents: {
* components: [
* { tagName: 'span', style: {color: 'red'}, content: 'Hello'},
* { style: {width: '100px', content: 'world!'}}
* ],
* }
* ...
*/
module.exports = () => {
var c = {},
componentTypes = {},
defaults = require('./config/config'),
Component = require('./model/Component'),
ComponentView = require('./view/ComponentView');
var component, componentView;
var defaultTypes = [
{
id: 'cell',
model: require('./model/ComponentTableCell'),
view: require('./view/ComponentTableCellView'),
},
{
id: 'row',
model: require('./model/ComponentTableRow'),
view: require('./view/ComponentTableRowView'),
},
{
id: 'table',
model: require('./model/ComponentTable'),
view: require('./view/ComponentTableView'),
},
{
id: 'map',
model: require('./model/ComponentMap'),
view: require('./view/ComponentMapView'),
},
{
id: 'link',
model: require('./model/ComponentLink'),
view: require('./view/ComponentLinkView'),
},
{
id: 'video',
model: require('./model/ComponentVideo'),
view: require('./view/ComponentVideoView'),
},
{
id: 'image',
model: require('./model/ComponentImage'),
view: require('./view/ComponentImageView'),
},
{
id: 'script',
model: require('./model/ComponentScript'),
view: require('./view/ComponentScriptView'),
},
{
id: 'textnode',
model: require('./model/ComponentTextNode'),
view: require('./view/ComponentTextNodeView'),
},
{
id: 'text',
model: require('./model/ComponentText'),
view: require('./view/ComponentTextView'),
},
{
id: 'default',
model: Component,
view: ComponentView,
},
];
return {
componentTypes: defaultTypes,
/**
* Name of the module
* @type {String}
* @private
*/
name: 'DomComponents',
/**
* Returns config
* @return {Object} Config object
* @private
*/
getConfig() {
return c;
},
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey() {
var keys = [];
var smc = (c.stm && c.stm.getConfig()) || {};
if(smc.storeHtml)
keys.push('html');
if(smc.storeComponents)
keys.push('components');
return keys;
},
/**
* Initialize module. Called on a new instance of the editor with configurations passed
* inside 'domComponents' field
* @param {Object} config Configurations
* @private
*/
init(config) {
c = config || {};
if(c.em)
c.components = c.em.config.components || c.components;
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
// Load dependencies
if(c.em){
c.rte = c.em.get('rte') || '';
c.modal = c.em.get('Modal') || '';
c.am = c.em.get('AssetManager') || '';
c.em.get('Parser').compTypes = defaultTypes;
}
component = new Component(c.wrapper, {
sm: c.em,
config: c,
defaultTypes,
componentTypes,
});
component.set({ attributes: {id: 'wrapper'}});
if(c.em && !c.em.config.loadCompsOnRender) {
component.get('components').add(c.components);
}
componentView = new ComponentView({
model: component,
config: c,
defaultTypes,
componentTypes,
});
return this;
},
/**
* On load callback
* @private
*/
onLoad() {
if(c.stm && c.stm.getConfig().autoload)
this.load();
if(c.stm && c.stm.isAutosave()){
c.em.initUndoManager();
c.em.initChildrenComp(this.getWrapper());
}
},
/**
* Load components from the passed object, if the object is empty will try to fetch them
* autonomously from the selected storage
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded data
*/
load(data) {
var d = data || '';
if(!d && c.stm)
d = c.em.getCacheLoad();
var obj = '';
if(d.components){
try{
obj = JSON.parse(d.components);
}catch(err){}
}else if(d.html)
obj = d.html;
if(obj)
this.getComponents().reset(obj);
return obj;
},
/**
* Store components on the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
*/
store(noStore) {
if(!c.stm)
return;
var obj = {};
var keys = this.storageKey();
if(keys.indexOf('html') >= 0)
obj.html = c.em.getHtml();
if(keys.indexOf('components') >= 0)
obj.components = JSON.stringify(c.em.getComponents());
if(!noStore)
c.stm.store(obj);
return obj;
},
/**
* Returns privately the main wrapper
* @return {Object}
* @private
*/
getComponent() {
return component;
},
/**
* Returns root component inside the canvas. Something like <body> inside HTML page
* The wrapper doesn't differ from the original Component Model
* @return {Component} Root Component
* @example
* // Change background of the wrapper and set some attribute
* var wrapper = domComponents.getWrapper();
* wrapper.set('style', {'background-color': 'red'});
* wrapper.set('attributes', {'title': 'Hello!'});
*/
getWrapper() {
return this.getComponent();
},
/**
* Returns wrapper's children collection. Once you have the collection you can
* add other Components(Models) inside. Each component can have several nested
* components inside and you can nest them as more as you wish.
* @return {Components} Collection of components
* @example
* // Let's add some component
* var wrapperChildren = domComponents.getComponents();
* var comp1 = wrapperChildren.add({
* style: { 'background-color': 'red'}
* });
* var comp2 = wrapperChildren.add({
* tagName: 'span',
* attributes: { title: 'Hello!'}
* });
* // Now let's add an other one inside first component
* // First we have to get the collection inside. Each
* // component has 'components' property
* var comp1Children = comp1.get('components');
* // Procede as before. You could also add multiple objects
* comp1Children.add([
* { style: { 'background-color': 'blue'}},
* { style: { height: '100px', width: '100px'}}
* ]);
* // Remove comp2
* wrapperChildren.remove(comp2);
*/
getComponents() {
return this.getWrapper().get('components');
},
/**
* Add new components to the wrapper's children. It's the same
* as 'domComponents.getComponents().add(...)'
* @param {Object|Component|Array<Object>} component Component/s to add
* @param {string} [component.tagName='div'] Tag name
* @param {string} [component.type=''] Type of the component. Available: ''(default), 'text', 'image'
* @param {boolean} [component.removable=true] If component is removable
* @param {boolean} [component.draggable=true] If is possible to move the component around the structure
* @param {boolean} [component.droppable=true] If is possible to drop inside other components
* @param {boolean} [component.badgable=true] If the badge is visible when the component is selected
* @param {boolean} [component.stylable=true] If is possible to style component
* @param {boolean} [component.copyable=true] If is possible to copy&paste the component
* @param {string} [component.content=''] String inside component
* @param {Object} [component.style={}] Style object
* @param {Object} [component.attributes={}] Attribute object
* @return {Component|Array<Component>} Component/s added
* @example
* // Example of a new component with some extra property
* var comp1 = domComponents.addComponent({
* tagName: 'div',
* removable: true, // Can't remove it
* draggable: true, // Can't move it
* copyable: true, // Disable copy/past
* content: 'Content text', // Text inside component
* style: { color: 'red'},
* attributes: { title: 'here' }
* });
*/
addComponent(component) {
return this.getComponents().add(component);
},
/**
* Render and returns wrapper element with all components inside.
* Once the wrapper is rendered, and it's what happens when you init the editor,
* the all new components will be added automatically and property changes are all
* updated immediately
* @return {HTMLElement}
*/
render() {
return componentView.render().el;
},
/**
* Remove all components
* @return {this}
*/
clear() {
var c = this.getComponents();
for(var i = 0, len = c.length; i < len; i++)
c.pop();
return this;
},
/**
* Set components
* @param {Object|string} components HTML string or components model
* @return {this}
* @private
*/
setComponents(components) {
this.clear().addComponent(components);
},
/**
* Add new component type
* @param {string} type
* @param {Object} methods
* @private
*/
addType(type, methods) {
var compType = this.getType(type);
if(compType) {
compType.model = methods.model;
compType.view = methods.view;
} else {
methods.id = type;
defaultTypes.unshift(methods);
}
},
/**
* Get component type
* @param {string} type
* @private
*/
getType(type) {
var df = defaultTypes;
for (var it = 0; it < df.length; it++) {
var dfId = df[it].id;
if(dfId == type) {
return df[it];
}
}
return;
},
};
};

398
src/dom_components/main.js

@ -1,398 +0,0 @@
/**
*
* * [getWrapper](#getwrapper)
* * [getComponents](#getcomponents)
* * [addComponent](#addcomponent)
* * [clear](#clear)
* * [load](#load)
* * [store](#store)
* * [render](#render)
*
* With this module is possible to manage components inside the canvas.
* Before using methods you should get first the module from the editor instance, in this way:
*
* ```js
* var domComponents = editor.DomComponents;
* ```
*
* @module DomComponents
* @param {Object} config Configurations
* @param {string|Array<Object>} [config.components=[]] HTML string or an array of possible components
* @example
* ...
* domComponents: {
* components: '<div>Hello world!</div>',
* }
* // Or
* domComponents: {
* components: [
* { tagName: 'span', style: {color: 'red'}, content: 'Hello'},
* { style: {width: '100px', content: 'world!'}}
* ],
* }
* ...
*/
define(function(require) {
return function (){
var c = {},
componentTypes = {},
defaults = require('./config/config'),
Component = require('./model/Component'),
ComponentView = require('./view/ComponentView');
var component, componentView;
var defaultTypes = [
{
id: 'cell',
model: require('./model/ComponentTableCell'),
view: require('./view/ComponentTableCellView'),
},
{
id: 'row',
model: require('./model/ComponentTableRow'),
view: require('./view/ComponentTableRowView'),
},
{
id: 'table',
model: require('./model/ComponentTable'),
view: require('./view/ComponentTableView'),
},
{
id: 'map',
model: require('./model/ComponentMap'),
view: require('./view/ComponentMapView'),
},
{
id: 'link',
model: require('./model/ComponentLink'),
view: require('./view/ComponentLinkView'),
},
{
id: 'video',
model: require('./model/ComponentVideo'),
view: require('./view/ComponentVideoView'),
},
{
id: 'image',
model: require('./model/ComponentImage'),
view: require('./view/ComponentImageView'),
},
{
id: 'script',
model: require('./model/ComponentScript'),
view: require('./view/ComponentScriptView'),
},
{
id: 'textnode',
model: require('./model/ComponentTextNode'),
view: require('./view/ComponentTextNodeView'),
},
{
id: 'text',
model: require('./model/ComponentText'),
view: require('./view/ComponentTextView'),
},
{
id: 'default',
model: Component,
view: ComponentView,
},
];
return {
componentTypes: defaultTypes,
/**
* Name of the module
* @type {String}
* @private
*/
name: 'DomComponents',
/**
* Returns config
* @return {Object} Config object
* @private
*/
getConfig: function () {
return c;
},
/**
* Mandatory for the storage manager
* @type {String}
* @private
*/
storageKey: function(){
var keys = [];
var smc = (c.stm && c.stm.getConfig()) || {};
if(smc.storeHtml)
keys.push('html');
if(smc.storeComponents)
keys.push('components');
return keys;
},
/**
* Initialize module. Called on a new instance of the editor with configurations passed
* inside 'domComponents' field
* @param {Object} config Configurations
* @private
*/
init: function(config) {
c = config || {};
if(c.em)
c.components = c.em.config.components || c.components;
for (var name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
var ppfx = c.pStylePrefix;
if(ppfx)
c.stylePrefix = ppfx + c.stylePrefix;
// Load dependencies
if(c.em){
c.rte = c.em.get('rte') || '';
c.modal = c.em.get('Modal') || '';
c.am = c.em.get('AssetManager') || '';
c.em.get('Parser').compTypes = defaultTypes;
}
component = new Component(c.wrapper, {
sm: c.em,
config: c,
defaultTypes: defaultTypes,
componentTypes: componentTypes,
});
component.set({ attributes: {id: 'wrapper'}});
if(c.em && !c.em.config.loadCompsOnRender) {
component.get('components').add(c.components);
}
componentView = new ComponentView({
model: component,
config: c,
defaultTypes: defaultTypes,
componentTypes: componentTypes,
});
return this;
},
/**
* On load callback
* @private
*/
onLoad: function(){
if(c.stm && c.stm.getConfig().autoload)
this.load();
if(c.stm && c.stm.isAutosave()){
c.em.initUndoManager();
c.em.initChildrenComp(this.getWrapper());
}
},
/**
* Load components from the passed object, if the object is empty will try to fetch them
* autonomously from the selected storage
* The fetched data will be added to the collection
* @param {Object} data Object of data to load
* @return {Object} Loaded data
*/
load: function(data){
var d = data || '';
if(!d && c.stm)
d = c.em.getCacheLoad();
var obj = '';
if(d.components){
try{
obj = JSON.parse(d.components);
}catch(err){}
}else if(d.html)
obj = d.html;
if(obj)
this.getComponents().reset(obj);
return obj;
},
/**
* Store components on the selected storage
* @param {Boolean} noStore If true, won't store
* @return {Object} Data to store
*/
store: function(noStore){
if(!c.stm)
return;
var obj = {};
var keys = this.storageKey();
if(keys.indexOf('html') >= 0)
obj.html = c.em.getHtml();
if(keys.indexOf('components') >= 0)
obj.components = JSON.stringify(c.em.getComponents());
if(!noStore)
c.stm.store(obj);
return obj;
},
/**
* Returns privately the main wrapper
* @return {Object}
* @private
*/
getComponent : function(){
return component;
},
/**
* Returns root component inside the canvas. Something like <body> inside HTML page
* The wrapper doesn't differ from the original Component Model
* @return {Component} Root Component
* @example
* // Change background of the wrapper and set some attribute
* var wrapper = domComponents.getWrapper();
* wrapper.set('style', {'background-color': 'red'});
* wrapper.set('attributes', {'title': 'Hello!'});
*/
getWrapper: function(){
return this.getComponent();
},
/**
* Returns wrapper's children collection. Once you have the collection you can
* add other Components(Models) inside. Each component can have several nested
* components inside and you can nest them as more as you wish.
* @return {Components} Collection of components
* @example
* // Let's add some component
* var wrapperChildren = domComponents.getComponents();
* var comp1 = wrapperChildren.add({
* style: { 'background-color': 'red'}
* });
* var comp2 = wrapperChildren.add({
* tagName: 'span',
* attributes: { title: 'Hello!'}
* });
* // Now let's add an other one inside first component
* // First we have to get the collection inside. Each
* // component has 'components' property
* var comp1Children = comp1.get('components');
* // Procede as before. You could also add multiple objects
* comp1Children.add([
* { style: { 'background-color': 'blue'}},
* { style: { height: '100px', width: '100px'}}
* ]);
* // Remove comp2
* wrapperChildren.remove(comp2);
*/
getComponents: function(){
return this.getWrapper().get('components');
},
/**
* Add new components to the wrapper's children. It's the same
* as 'domComponents.getComponents().add(...)'
* @param {Object|Component|Array<Object>} component Component/s to add
* @param {string} [component.tagName='div'] Tag name
* @param {string} [component.type=''] Type of the component. Available: ''(default), 'text', 'image'
* @param {boolean} [component.removable=true] If component is removable
* @param {boolean} [component.draggable=true] If is possible to move the component around the structure
* @param {boolean} [component.droppable=true] If is possible to drop inside other components
* @param {boolean} [component.badgable=true] If the badge is visible when the component is selected
* @param {boolean} [component.stylable=true] If is possible to style component
* @param {boolean} [component.copyable=true] If is possible to copy&paste the component
* @param {string} [component.content=''] String inside component
* @param {Object} [component.style={}] Style object
* @param {Object} [component.attributes={}] Attribute object
* @return {Component|Array<Component>} Component/s added
* @example
* // Example of a new component with some extra property
* var comp1 = domComponents.addComponent({
* tagName: 'div',
* removable: true, // Can't remove it
* draggable: true, // Can't move it
* copyable: true, // Disable copy/past
* content: 'Content text', // Text inside component
* style: { color: 'red'},
* attributes: { title: 'here' }
* });
*/
addComponent: function(component){
return this.getComponents().add(component);
},
/**
* Render and returns wrapper element with all components inside.
* Once the wrapper is rendered, and it's what happens when you init the editor,
* the all new components will be added automatically and property changes are all
* updated immediately
* @return {HTMLElement}
*/
render: function(){
return componentView.render().el;
},
/**
* Remove all components
* @return {this}
*/
clear: function(){
var c = this.getComponents();
for(var i = 0, len = c.length; i < len; i++)
c.pop();
return this;
},
/**
* Set components
* @param {Object|string} components HTML string or components model
* @return {this}
* @private
*/
setComponents: function(components){
this.clear().addComponent(components);
},
/**
* Add new component type
* @param {string} type
* @param {Object} methods
* @private
*/
addType: function(type, methods) {
var compType = this.getType(type);
if(compType) {
compType.model = methods.model;
compType.view = methods.view;
} else {
methods.id = type;
defaultTypes.unshift(methods);
}
},
/**
* Get component type
* @param {string} type
* @private
*/
getType: function(type) {
var df = defaultTypes;
for (var it = 0; it < df.length; it++) {
var dfId = df[it].id;
if(dfId == type) {
return df[it];
}
}
return;
},
};
};
});

717
src/dom_components/model/Component.js

@ -1,371 +1,370 @@
define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitManager/model/Traits'],
function (Backbone, Components, Selectors, Traits) {
var Backbone = require('backbone');
var Components = require('./Components');
var Selectors = require('selector_manager/model/Selectors');
var Traits = require('trait_manager/model/Traits');
return Backbone.Model.extend({
module.exports = Backbone.Model.extend({
defaults: {
// HTML tag of the component
tagName: 'div',
// Component type, eg. 'text', 'image', 'video', etc.
type: '',
// True if the component is removable from the canvas
removable: true,
// Indicates if it's possible to drag the component inside other
// Tip: Indicate an array of selectors where it could be dropped inside
draggable: true,
// Indicates if it's possible to drop other components inside
// Tip: Indicate an array of selectors which could be dropped inside
droppable: true,
// Set false if don't want to see the badge (with the name) over the component
badgable: true,
// True if it's possible to style it
// Tip: Indicate an array of CSS properties which is possible to style
stylable: true,
// Highlightable with 'dotted' style if true
highlightable: true,
// True if it's possible to clone the component
copyable: true,
// Indicates if it's possible to resize the component (at the moment implemented only on Image Components)
resizable: false,
// Allow to edit the content of the component (used on Text components)
editable: false,
// Hide the component inside Layers
hiddenLayer: false,
// This property is used by the HTML exporter as void elements do not
// have closing tag, eg. <br/>, <hr/>, etc.
void: false,
// Indicates if the component is in some CSS state like ':hover', ':active', etc.
state: '',
// State, eg. 'selected'
status: '',
// Content of the component (not escaped) which will be appended before children rendering
content: '',
// Component related style
style: {},
// Key-value object of the component's attributes
attributes: '',
// Array of classes
classes: '',
// Component's javascript
script: '',
// Traits
traits: ['id', 'title'],
/**
* Set an array of items to show up inside the toolbar (eg. move, clone, delete)
* when the component is selected
* toolbar: [{
* attributes: {class: 'fa fa-arrows'},
* command: 'tlb-move',
* },{
* attributes: {class: 'fa fa-clone'},
* command: 'tlb-clone',
* }]
*/
toolbar: null,
// TODO
previousModel: '',
mirror: '',
},
initialize: function(o, opt) {
// Check void elements
if(opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0)
this.set('void', true);
this.opt = opt;
this.sm = opt ? opt.sm || {} : {};
this.config = o || {};
this.defaultC = this.config.components || [];
this.defaultCl = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.components = new Components(this.defaultC, opt);
this.components.parent = this;
this.listenTo(this, 'change:script', this.scriptUpdated);
this.set('attributes', this.get('attributes') || {});
this.set('components', this.components);
this.set('classes', new Selectors(this.defaultCl));
var traits = new Traits();
traits.setTarget(this);
traits.add(this.get('traits'));
this.set('traits', traits);
this.initToolbar();
// Normalize few properties from strings to arrays
var toNormalize = ['stylable'];
toNormalize.forEach(function(name) {
var value = this.get(name);
if (typeof value == 'string') {
var newValue = value.split(',').map(function(prop) {
return prop.trim();
});
this.set(name, newValue);
}
}, this);
this.init();
},
/**
* Initialize callback
*/
init: function () {},
/**
* Script updated
*/
scriptUpdated: function() {
this.set('scriptUpdated', 1);
},
/**
* Init toolbar
*/
initToolbar: function () {
var model = this;
if(!model.get('toolbar')) {
var tb = [];
if(model.get('draggable')) {
tb.push({
attributes: {class: 'fa fa-arrows'},
command: 'tlb-move',
});
}
if(model.get('copyable')) {
tb.push({
attributes: {class: 'fa fa-clone'},
command: 'tlb-clone',
});
}
if(model.get('removable')) {
tb.push({
attributes: {class: 'fa fa-trash-o'},
command: 'tlb-delete',
});
}
model.set('toolbar', tb);
}
},
/**
* Load traits
* @param {Array} traits
* @private
*/
loadTraits: function(traits) {
var trt = new Traits();
trt.setTarget(this);
trt.add(traits);
this.set('traits', trt);
},
/**
* Normalize input classes from array to array of objects
* @param {Array} arr
* @return {Array}
* @private
*/
normalizeClasses: function(arr) {
var res = [];
if(!this.sm.get)
return;
var clm = this.sm.get('SelectorManager');
if(!clm)
return;
arr.forEach(function(val){
var name = '';
if(typeof val === 'string')
name = val;
else
name = val.name;
var model = clm.add(name);
res.push(model);
});
return res;
},
/**
* Override original clone method
* @private
*/
clone: function(reset) {
var attr = _.clone(this.attributes),
comp = this.get('components'),
traits = this.get('traits'),
cls = this.get('classes');
attr.components = [];
attr.classes = [];
attr.traits = [];
comp.each(function(md,i) {
attr.components[i] = md.clone(1);
});
traits.each(function(md, i) {
attr.traits[i] = md.clone();
defaults: {
// HTML tag of the component
tagName: 'div',
// Component type, eg. 'text', 'image', 'video', etc.
type: '',
// True if the component is removable from the canvas
removable: true,
// Indicates if it's possible to drag the component inside other
// Tip: Indicate an array of selectors where it could be dropped inside
draggable: true,
// Indicates if it's possible to drop other components inside
// Tip: Indicate an array of selectors which could be dropped inside
droppable: true,
// Set false if don't want to see the badge (with the name) over the component
badgable: true,
// True if it's possible to style it
// Tip: Indicate an array of CSS properties which is possible to style
stylable: true,
// Highlightable with 'dotted' style if true
highlightable: true,
// True if it's possible to clone the component
copyable: true,
// Indicates if it's possible to resize the component (at the moment implemented only on Image Components)
resizable: false,
// Allow to edit the content of the component (used on Text components)
editable: false,
// Hide the component inside Layers
hiddenLayer: false,
// This property is used by the HTML exporter as void elements do not
// have closing tag, eg. <br/>, <hr/>, etc.
void: false,
// Indicates if the component is in some CSS state like ':hover', ':active', etc.
state: '',
// State, eg. 'selected'
status: '',
// Content of the component (not escaped) which will be appended before children rendering
content: '',
// Component related style
style: {},
// Key-value object of the component's attributes
attributes: '',
// Array of classes
classes: '',
// Component's javascript
script: '',
// Traits
traits: ['id', 'title'],
/**
* Set an array of items to show up inside the toolbar (eg. move, clone, delete)
* when the component is selected
* toolbar: [{
* attributes: {class: 'fa fa-arrows'},
* command: 'tlb-move',
* },{
* attributes: {class: 'fa fa-clone'},
* command: 'tlb-clone',
* }]
*/
toolbar: null,
// TODO
previousModel: '',
mirror: '',
},
initialize(o, opt) {
// Check void elements
if(opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0)
this.set('void', true);
this.opt = opt;
this.sm = opt ? opt.sm || {} : {};
this.config = o || {};
this.defaultC = this.config.components || [];
this.defaultCl = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.components = new Components(this.defaultC, opt);
this.components.parent = this;
this.listenTo(this, 'change:script', this.scriptUpdated);
this.set('attributes', this.get('attributes') || {});
this.set('components', this.components);
this.set('classes', new Selectors(this.defaultCl));
var traits = new Traits();
traits.setTarget(this);
traits.add(this.get('traits'));
this.set('traits', traits);
this.initToolbar();
// Normalize few properties from strings to arrays
var toNormalize = ['stylable'];
toNormalize.forEach(function(name) {
var value = this.get(name);
if (typeof value == 'string') {
var newValue = value.split(',').map(prop => prop.trim());
this.set(name, newValue);
}
}, this);
this.init();
},
/**
* Initialize callback
*/
init() {},
/**
* Script updated
*/
scriptUpdated() {
this.set('scriptUpdated', 1);
},
/**
* Init toolbar
*/
initToolbar() {
var model = this;
if(!model.get('toolbar')) {
var tb = [];
if(model.get('draggable')) {
tb.push({
attributes: {class: 'fa fa-arrows'},
command: 'tlb-move',
});
cls.each(function(md,i) {
attr.classes[i] = md.get('name');
}
if(model.get('copyable')) {
tb.push({
attributes: {class: 'fa fa-clone'},
command: 'tlb-clone',
});
attr.status = '';
attr.view = '';
if(reset){
this.opt.collection = null;
}
return new this.constructor(attr, this.opt);
},
/**
* Get name of the component
* @return {string}
* @private
* */
getName: function() {
if(!this.name){
var id = this.cid.replace(/\D/g,''),
type = this.get('type');
var tag = this.get('tagName');
tag = tag == 'div' ? 'box' : tag;
tag = type ? type : tag;
this.name = tag.charAt(0).toUpperCase() + tag.slice(1);
}
return this.name;
},
/**
* Return HTML string of the component
* @param {Object} opts Options
* @return {string} HTML string
* @private
*/
toHTML: function(opts) {
var code = '';
var m = this;
var tag = m.get('tagName'),
sTag = m.get('void'),
attrId = '';
// Build the string of attributes
var strAttr = '';
var attr = this.getAttrToHTML();
for(var prop in attr){
var val = attr[prop];
strAttr += typeof val !== undefined && val !== '' ?
' ' + prop + '="' + val + '"' : '';
}
// Build the string of classes
var strCls = '';
m.get('classes').each(function(m){
strCls += ' ' + m.get('name');
}
if(model.get('removable')) {
tb.push({
attributes: {class: 'fa fa-trash-o'},
command: 'tlb-delete',
});
strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : '';
// If style is not empty I need an ID attached to the component
// TODO: need to refactor in case of 'ID Trait'
if(!_.isEmpty(m.get('style')))
attrId = ' id="' + m.cid + '" ';
}
model.set('toolbar', tb);
}
},
/**
* Load traits
* @param {Array} traits
* @private
*/
loadTraits(traits) {
var trt = new Traits();
trt.setTarget(this);
trt.add(traits);
this.set('traits', trt);
},
/**
* Normalize input classes from array to array of objects
* @param {Array} arr
* @return {Array}
* @private
*/
normalizeClasses(arr) {
var res = [];
if(!this.sm.get)
return;
var clm = this.sm.get('SelectorManager');
if(!clm)
return;
arr.forEach(val => {
var name = '';
if(typeof val === 'string')
name = val;
else
name = val.name;
var model = clm.add(name);
res.push(model);
});
return res;
},
/**
* Override original clone method
* @private
*/
clone(reset) {
var attr = _.clone(this.attributes),
comp = this.get('components'),
traits = this.get('traits'),
cls = this.get('classes');
attr.components = [];
attr.classes = [];
attr.traits = [];
comp.each((md, i) => {
attr.components[i] = md.clone(1);
});
traits.each((md, i) => {
attr.traits[i] = md.clone();
});
cls.each((md, i) => {
attr.classes[i] = md.get('name');
});
code += '<' + tag + strCls + attrId + strAttr + (sTag ? '/' : '') + '>' + m.get('content');
attr.status = '';
attr.view = '';
if(reset){
this.opt.collection = null;
}
return new this.constructor(attr, this.opt);
},
/**
* Get name of the component
* @return {string}
* @private
* */
getName() {
if(!this.name){
var id = this.cid.replace(/\D/g,''),
type = this.get('type');
var tag = this.get('tagName');
tag = tag == 'div' ? 'box' : tag;
tag = type ? type : tag;
this.name = tag.charAt(0).toUpperCase() + tag.slice(1);
}
return this.name;
},
/**
* Return HTML string of the component
* @param {Object} opts Options
* @return {string} HTML string
* @private
*/
toHTML(opts) {
var code = '';
var m = this;
var tag = m.get('tagName'),
sTag = m.get('void'),
attrId = '';
// Build the string of attributes
var strAttr = '';
var attr = this.getAttrToHTML();
for(var prop in attr){
var val = attr[prop];
strAttr += typeof val !== undefined && val !== '' ?
' ' + prop + '="' + val + '"' : '';
}
// Build the string of classes
var strCls = '';
m.get('classes').each(m => {
strCls += ' ' + m.get('name');
});
strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : '';
m.get('components').each(function(m) {
code += m.toHTML();
});
// If style is not empty I need an ID attached to the component
// TODO: need to refactor in case of 'ID Trait'
if(!_.isEmpty(m.get('style')))
attrId = ' id="' + m.cid + '" ';
if(!sTag)
code += '</'+tag+'>';
return code;
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML: function() {
var attr = this.get('attributes') || {};
delete attr.style;
return attr;
},
/**
* Return a shallow copy of the model's attributes for JSON
* stringification.
* @return {Object}
* @private
*/
toJSON: function() {
var obj = Backbone.Model.prototype.toJSON.apply(this, arguments);
var scriptStr = this.getScriptString();
if (scriptStr) {
obj.script = scriptStr;
}
return obj;
},
/**
* Return script in string format, cleans 'function() {..' from scripts
* if it's a function
* @param {string|Function} script
* @return {string}
* @private
*/
getScriptString: function (script) {
var scr = script || this.get('script');
// Need to cast script functions to string
if (typeof scr == 'function') {
var scrStr = scr.toString().trim();
scrStr = scrStr.replace(/^function\s?\(\)\s?\{/, '');
scrStr = scrStr.replace(/\}$/, '');
scr = scrStr;
}
return scr;
}
code += '<' + tag + strCls + attrId + strAttr + (sTag ? '/' : '') + '>' + m.get('content');
},{
m.get('components').each(m => {
code += m.toHTML();
});
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
return {tagName: el.tagName ? el.tagName.toLowerCase() : ''};
},
if(!sTag)
code += '</'+tag+'>';
return code;
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML() {
var attr = this.get('attributes') || {};
delete attr.style;
return attr;
},
/**
* Return a shallow copy of the model's attributes for JSON
* stringification.
* @return {Object}
* @private
*/
toJSON(...args) {
var obj = Backbone.Model.prototype.toJSON.apply(this, args);
var scriptStr = this.getScriptString();
if (scriptStr) {
obj.script = scriptStr;
}
return obj;
},
/**
* Return script in string format, cleans 'function() {..' from scripts
* if it's a function
* @param {string|Function} script
* @return {string}
* @private
*/
getScriptString(script) {
var scr = script || this.get('script');
// Need to cast script functions to string
if (typeof scr == 'function') {
var scrStr = scr.toString().trim();
scrStr = scrStr.replace(/^function\s?\(\)\s?\{/, '');
scrStr = scrStr.replace(/\}$/, '');
scr = scrStr;
}
return scr;
}
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
return {tagName: el.tagName ? el.tagName.toLowerCase() : ''};
},
});
});

180
src/dom_components/model/ComponentImage.js

@ -1,103 +1,101 @@
define(['./Component'],
function (Component) {
var Component = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'image',
tagName: 'img',
src: '',
void: 1,
droppable: false,
resizable: true,
traits: ['alt']
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'image',
tagName: 'img',
src: '',
void: 1,
droppable: false,
resizable: true,
traits: ['alt']
}),
initialize: function(o, opt) {
Component.prototype.initialize.apply(this, arguments);
var attr = this.get('attributes');
if(attr.src)
this.set('src', attr.src);
},
initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments);
var attr = this.get('attributes');
if(attr.src)
this.set('src', attr.src);
},
initToolbar: function() {
Component.prototype.initToolbar.apply(this, arguments);
initToolbar(...args) {
Component.prototype.initToolbar.apply(this, args);
if (this.sm && this.sm.get) {
var cmd = this.sm.get('Commands');
var cmdName = 'image-editor';
if (this.sm && this.sm.get) {
var cmd = this.sm.get('Commands');
var cmdName = 'image-editor';
// Add Image Editor button only if the default command exists
if (cmd.has(cmdName)) {
var tb = this.get('toolbar');
tb.push({
attributes: {class: 'fa fa-pencil'},
command: cmdName,
});
this.set('toolbar', tb);
}
}
},
// Add Image Editor button only if the default command exists
if (cmd.has(cmdName)) {
var tb = this.get('toolbar');
tb.push({
attributes: {class: 'fa fa-pencil'},
command: cmdName,
});
this.set('toolbar', tb);
}
}
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML: function() {
var attr = Component.prototype.getAttrToHTML.apply(this, arguments);
delete attr.onmousedown;
var src = this.get('src');
if(src)
attr.src = src;
return attr;
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML(...args) {
var attr = Component.prototype.getAttrToHTML.apply(this, args);
delete attr.onmousedown;
var src = this.get('src');
if(src)
attr.src = src;
return attr;
},
/**
* Parse uri
* @param {string} uri
* @return {object}
* @private
*/
parseUri: function(uri) {
var el = document.createElement('a');
el.href = uri;
var query = {};
var qrs = el.search.substring(1).split('&');
for (var i = 0; i < qrs.length; i++) {
var pair = qrs[i].split('=');
var name = decodeURIComponent(pair[0]);
if(name)
query[name] = decodeURIComponent(pair[1]);
}
return {
hostname: el.hostname,
pathname: el.pathname,
protocol: el.protocol,
search: el.search,
hash: el.hash,
port: el.port,
query: query,
};
},
/**
* Parse uri
* @param {string} uri
* @return {object}
* @private
*/
parseUri(uri) {
var el = document.createElement('a');
el.href = uri;
var query = {};
var qrs = el.search.substring(1).split('&');
for (var i = 0; i < qrs.length; i++) {
var pair = qrs[i].split('=');
var name = decodeURIComponent(pair[0]);
if(name)
query[name] = decodeURIComponent(pair[1]);
}
return {
hostname: el.hostname,
pathname: el.pathname,
protocol: el.protocol,
search: el.search,
hash: el.hash,
port: el.port,
query,
};
},
},{
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
if(el.tagName == 'IMG'){
result = {type: 'image'};
}
return result;
},
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
if(el.tagName == 'IMG'){
result = {type: 'image'};
}
return result;
},
});
});

68
src/dom_components/model/ComponentLink.js

@ -1,42 +1,40 @@
define(['./ComponentText'],
function (Component) {
var Component = require('./ComponentText');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'link',
tagName: 'a',
traits: ['title', 'href', 'target'],
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'link',
tagName: 'a',
traits: ['title', 'href', 'target'],
}),
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML: function() {
var attr = Component.prototype.getAttrToHTML.apply(this, arguments);
delete attr.onmousedown;
return attr;
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML(...args) {
var attr = Component.prototype.getAttrToHTML.apply(this, args);
delete attr.onmousedown;
return attr;
},
},{
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
if(el.tagName == 'A'){
result = {type: 'link'};
}
return result;
},
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
if(el.tagName == 'A'){
result = {type: 'link'};
}
return result;
},
});
});

193
src/dom_components/model/ComponentMap.js

@ -1,108 +1,107 @@
define(['./ComponentImage', './Component'],
function (Component, OComponent) {
var Component = require('./ComponentImage');
var OComponent = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'map',
void: 0,
mapUrl: 'https://maps.google.com/maps',
tagName: 'iframe',
mapType: 'q',
address: '',
zoom: '1',
attributes: {frameborder: 0},
toolbar: OComponent.prototype.defaults.toolbar,
traits: [{
label: 'Address',
name: 'address',
placeholder: 'eg. London, UK',
changeProp: 1,
},{
type: 'select',
label: 'Map type',
name: 'mapType',
changeProp: 1,
options: [
{value: 'q', name: 'Roadmap'},
{value: 'w', name: 'Satellite'}
]
},{
label: 'Zoom',
name: 'zoom',
type: 'range',
min: '1',
max: '20',
changeProp: 1,
}],
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'map',
void: 0,
mapUrl: 'https://maps.google.com/maps',
tagName: 'iframe',
mapType: 'q',
address: '',
zoom: '1',
attributes: {frameborder: 0},
toolbar: OComponent.prototype.defaults.toolbar,
traits: [{
label: 'Address',
name: 'address',
placeholder: 'eg. London, UK',
changeProp: 1,
},{
type: 'select',
label: 'Map type',
name: 'mapType',
changeProp: 1,
options: [
{value: 'q', name: 'Roadmap'},
{value: 'w', name: 'Satellite'}
]
},{
label: 'Zoom',
name: 'zoom',
type: 'range',
min: '1',
max: '20',
changeProp: 1,
}],
}),
initialize: function(o, opt) {
if(this.get('src'))
this.parseFromSrc();
else
this.updateSrc();
Component.prototype.initialize.apply(this, arguments);
this.listenTo(this, 'change:address change:zoom change:mapType', this.updateSrc);
},
initialize(o, opt) {
if(this.get('src'))
this.parseFromSrc();
else
this.updateSrc();
Component.prototype.initialize.apply(this, arguments);
this.listenTo(this, 'change:address change:zoom change:mapType', this.updateSrc);
},
updateSrc: function() {
this.set('src', this.getMapUrl());
},
updateSrc() {
this.set('src', this.getMapUrl());
},
/**
* Returns url of the map
* @return {string}
* @private
*/
getMapUrl: function() {
var md = this;
var addr = md.get('address');
var zoom = md.get('zoom');
var type = md.get('mapType');
var size = '';
addr = addr ? '&q=' + addr : '';
zoom = zoom ? '&z=' + zoom : '';
type = type ? '&t=' + type : '';
var result = md.get('mapUrl')+'?' + addr + zoom + type;
result += '&output=embed';
return result;
},
/**
* Returns url of the map
* @return {string}
* @private
*/
getMapUrl() {
var md = this;
var addr = md.get('address');
var zoom = md.get('zoom');
var type = md.get('mapType');
var size = '';
addr = addr ? '&q=' + addr : '';
zoom = zoom ? '&z=' + zoom : '';
type = type ? '&t=' + type : '';
var result = md.get('mapUrl')+'?' + addr + zoom + type;
result += '&output=embed';
return result;
},
/**
* Set attributes by src string
* @private
*/
parseFromSrc: function() {
var uri = this.parseUri(this.get('src'));
var qr = uri.query;
if(qr.q)
this.set('address', qr.q);
if(qr.z)
this.set('zoom', qr.z);
if(qr.t)
this.set('mapType', qr.t);
},
/**
* Set attributes by src string
* @private
*/
parseFromSrc() {
var uri = this.parseUri(this.get('src'));
var qr = uri.query;
if(qr.q)
this.set('address', qr.q);
if(qr.z)
this.set('zoom', qr.z);
if(qr.t)
this.set('mapType', qr.t);
},
},{
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
if(el.tagName == 'IFRAME' &&
/maps\.google\.com/.test(el.src) ){
result = {type: 'map', src: el.src};
}
return result;
},
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
if(el.tagName == 'IFRAME' &&
/maps\.google\.com/.test(el.src) ){
result = {type: 'map', src: el.src};
}
return result;
},
});
});

40
src/dom_components/model/ComponentScript.js

@ -1,29 +1,27 @@
define(['./Component'],
function (Component) {
var Component = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'script',
droppable: false,
draggable: false,
hiddenLayer: true,
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'script',
droppable: false,
draggable: false,
hiddenLayer: true,
}),
}, {
}, {
isComponent: function(el) {
if (el.tagName == 'SCRIPT') {
var result = {type: 'script'};
isComponent(el) {
if (el.tagName == 'SCRIPT') {
var result = {type: 'script'};
if (el.src) {
result.src = el.src;
result.onload = el.onload;
}
if (el.src) {
result.src = el.src;
result.onload = el.onload;
}
return result;
}
},
return result;
}
},
});
});

156
src/dom_components/model/ComponentTable.js

@ -1,91 +1,89 @@
define(['./Component'],
function (Component) {
var Component = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'table',
tagName: 'table',
droppable: ['tr', 'tbody', 'thead', 'tfoot'],
columns: 3,
rows: 2,
/*
traits: [{
label: 'Columns',
name: 'columns',
changeProp: 1,
},{
label: 'Rows',
name: 'rows',
changeProp: 1,
}]
*/
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'table',
tagName: 'table',
droppable: ['tr', 'tbody', 'thead', 'tfoot'],
columns: 3,
rows: 2,
/*
traits: [{
label: 'Columns',
name: 'columns',
changeProp: 1,
},{
label: 'Rows',
name: 'rows',
changeProp: 1,
}]
*/
}),
initialize: function(o, opt) {
Component.prototype.initialize.apply(this, arguments);
var components = this.get('components');
var rows = this.get('rows');
var columns = this.get('columns');
initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments);
var components = this.get('components');
var rows = this.get('rows');
var columns = this.get('columns');
// Init components if empty
if(!components.length){
var rowsToAdd = [];
// Init components if empty
if(!components.length){
var rowsToAdd = [];
while(rows--){
var columnsToAdd = [];
var clm = columns;
while(rows--){
var columnsToAdd = [];
var clm = columns;
while (clm--) {
columnsToAdd.push({
type: 'cell',
classes: ['cell']
});
}
while (clm--) {
columnsToAdd.push({
type: 'cell',
classes: ['cell']
});
}
rowsToAdd.push({
type: 'row',
classes: ['row'],
components: columnsToAdd
});
}
components.add(rowsToAdd);
}
rowsToAdd.push({
type: 'row',
classes: ['row'],
components: columnsToAdd
});
}
components.add(rowsToAdd);
}
// Clean table from non rows
var rowsColl = [];
components.each(function(model){
if(model.get('type') != 'row'){
model.get('components').each(function(row) {
if(row.get('type') == 'row'){
row.collection = components;
rowsColl.push(row);
}
});
}else{
rowsColl.push(model);
}
});
components.reset(rowsColl);
},
// Clean table from non rows
var rowsColl = [];
components.each(model => {
if(model.get('type') != 'row'){
model.get('components').each(row => {
if(row.get('type') == 'row'){
row.collection = components;
rowsColl.push(row);
}
});
}else{
rowsColl.push(model);
}
});
components.reset(rowsColl);
},
},{
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
if(el.tagName == 'TABLE'){
result = {type: 'table'};
}
return result;
},
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
if(el.tagName == 'TABLE'){
result = {type: 'table'};
}
return result;
},
});
});

56
src/dom_components/model/ComponentTableCell.js

@ -1,35 +1,33 @@
define(['./Component'],
function (Component) {
var Component = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'cell',
tagName: 'td',
draggable: ['tr'],
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'cell',
tagName: 'td',
draggable: ['tr'],
}),
},{
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
var tag = el.tagName;
if(tag == 'TD' || tag == 'TH'){
result = {
type: 'cell',
tagName: tag.toLowerCase()
};
}
return result;
},
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
var tag = el.tagName;
if(tag == 'TD' || tag == 'TH'){
result = {
type: 'cell',
tagName: tag.toLowerCase()
};
}
return result;
},
});
});

74
src/dom_components/model/ComponentTableRow.js

@ -1,46 +1,44 @@
define(['./Component'],
function (Component) {
var Component = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'row',
tagName: 'tr',
draggable: ['table', 'tbody', 'thead'],
droppable: ['th', 'td']
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'row',
tagName: 'tr',
draggable: ['table', 'tbody', 'thead'],
droppable: ['th', 'td']
}),
initialize: function(o, opt) {
Component.prototype.initialize.apply(this, arguments);
initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments);
// Clean the row from non cell components
var cells = [];
var components = this.get('components');
components.each(function(model){
if(model.get('type') == 'cell'){
cells.push(model);
}
});
components.reset(cells);
}
// Clean the row from non cell components
var cells = [];
var components = this.get('components');
components.each(model => {
if(model.get('type') == 'cell'){
cells.push(model);
}
});
components.reset(cells);
}
},{
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
if(el.tagName == 'TR'){
result = {type: 'row'};
}
return result;
},
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent(el) {
var result = '';
if(el.tagName == 'TR'){
result = {type: 'row'};
}
return result;
},
});
});

16
src/dom_components/model/ComponentText.js

@ -1,13 +1,11 @@
define(['./Component'],
function (Component) {
var Component = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'text',
droppable: false,
editable: true,
}),
defaults: _.extend({}, Component.prototype.defaults, {
type: 'text',
droppable: false,
editable: true,
}),
});
});

42
src/dom_components/model/ComponentTextNode.js

@ -1,29 +1,27 @@
define(['./Component'],
function (Component) {
var Component = require('./Component');
return Component.extend({
module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
droppable: false,
editable: true,
}),
defaults: _.extend({}, Component.prototype.defaults, {
droppable: false,
editable: true,
}),
toHTML: function() {
return this.get('content');
},
toHTML() {
return this.get('content');
},
}, {
}, {
isComponent: function(el) {
var result = '';
if(el.nodeType === 3){
result = {
type: 'textnode',
content: el.textContent
};
}
return result;
},
isComponent(el) {
var result = '';
if(el.nodeType === 3){
result = {
type: 'textnode',
content: el.textContent
};
}
return result;
},
});
});

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

Loading…
Cancel
Save