Browse Source

Merge branch 'webpack' into dev

pull/72/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. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, 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 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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 (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 Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
## Features ## 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) * Default built-in commands (basically for creating and managing different components)
## Installation ## 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. For development purpose you should follow instructions below.
## Development ## 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 ```sh
$ npm install -g grunt-cli
$ git clone https://github.com/artf/grapesjs.git $ git clone https://github.com/artf/grapesjs.git
$ cd grapesjs $ cd grapesjs
$ npm i
``` ```
Install all necessary dependencies Start the dev server
```sh ```sh
$ npm install $ npm start
``` ```
Build GrapesJS Build before the commit. This will also increase the patch level version of the package
```sh ```sh
$ npm run build $ 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 ## 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 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] Check the getting started guide here: [wiki]
## API ## API
API References (draft) could be found here: [API-Reference] API References could be found here: [API-Reference]
## Testing ## Testing
@ -142,16 +155,8 @@ API References (draft) could be found here: [API-Reference]
$ npm test $ 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 ## Sponsors
@ -161,6 +166,9 @@ The project is sponsored by
[![Sendloop](http://grapesjs.com/img/sendloop-logo-l.png)](https://sendloop.com) [![Sendloop](http://grapesjs.com/img/sendloop-logo-l.png)](https://sendloop.com)
## Support ## Support
If you like the project support it with a donation of your choice. 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) [![PayPalMe](http://grapesjs.com/img/ppme.png)](https://paypal.me/grapesjs)
## License ## License
BSD 3-clause 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> [wiki]: <https://github.com/artf/grapesjs/wiki>
[API-Reference]: <https://github.com/artf/grapesjs/wiki/API-Reference> [API-Reference]: <https://github.com/artf/grapesjs/wiki/API-Reference>
[CMS]: <https://it.wikipedia.org/wiki/Content_management_system> [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> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>GrapesJS</title> <title>GrapesJS</title>
<link rel="stylesheet" href="styles/css/main.css"> <link rel="stylesheet" href="dist/css/grapes.min.css">
<link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="node_modules/codemirror/theme/hopscotch.css"> <script src="dist/grapes.min.js"></script>
</head> </head>
<style> <style>
body, html{ height: 100%; margin: 0;} body, html{ height: 100%; margin: 0;}
@ -15,27 +16,6 @@
<div id="gjs" style="height:0px; overflow:hidden"> <div id="gjs" style="height:0px; overflow:hidden">
<header class="header-banner"> <header class="header-banner">
<div class="container-width"> <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-container">
<div class="logo">GrapesJS</div> <div class="logo">GrapesJS</div>
</div> </div>
@ -812,6 +792,371 @@
</style> </style>
</div> </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> </body>
</html> </html>

49
package.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Open source Web Template Editor", "description": "Free and Open Source Web Builder Framework",
"version": "0.5.41", "version": "0.7.6",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",
@ -11,45 +11,34 @@
"url": "https://github.com/artf/grapesjs.git" "url": "https://github.com/artf/grapesjs.git"
}, },
"dependencies": { "dependencies": {
"almond": "^0.3.3",
"backbone": "^1.3.3", "backbone": "^1.3.3",
"backbone-undo": "^0.2.5", "backbone-undo": "^0.2.5",
"bower": "^1.7.2",
"codemirror": "^5.21.0", "codemirror": "^5.21.0",
"codemirror-formatting": "^1.0.0", "codemirror-formatting": "^1.0.0",
"font-awesome": "^4.7.0",
"jquery": "^3.1.1", "jquery": "^3.1.1",
"keymaster": "^1.6.2", "keymaster": "^1.6.2",
"spectrum-colorpicker": "^1.8.0", "spectrum-colorpicker": "^1.8.0",
"underscore": "^1.8.3" "underscore": "^1.8.3"
}, },
"devDependencies": { "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", "chai": "^3.5.0",
"documentation": "^4.0.0-beta2", "documentation": "^4.0.0-beta2",
"grunt": "^0.4.5", "expect": "^1.20.2",
"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",
"istanbul": "^0.4.2", "istanbul": "^0.4.2",
"jsdom": "^11.0.0",
"mocha": "^3.1.2", "mocha": "^3.1.2",
"node-sass": "^3.4.2", "node-sass": "^3.4.2",
"requirejs": "^2.3.2",
"requirejs-text": "^2.0.12",
"sinon": "^1.17.6", "sinon": "^1.17.6",
"svg2ttf": "^4.0.1", "svg2ttf": "^4.0.1",
"ttf2eot": "^2.0.0", "ttf2eot": "^2.0.0",
"ttf2woff": "^2.0.1", "ttf2woff": "^2.0.1",
"ttf2woff2": "^2.0.3" "ttf2woff2": "^2.0.3",
"webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5"
}, },
"keywords": [ "keywords": [
"wte", "wte",
@ -65,10 +54,18 @@
"template", "template",
"editor" "editor"
], ],
"babel": {
"presets": [
"es2015"
]
},
"scripts": { "scripts": {
"build": "./node_modules/.bin/grunt build", "lint": "eslint src",
"build:fonts": "./node_modules/.bin/grunt build:fonts", "build": "WEBPACK_ENV=prod && npm run lint && npm run test && npm run v:patch && webpack && npm run build:css",
"test": "./node_modules/.bin/grunt test", "build:css": "node-sass src/styles/scss/main.scss dist/css/grapes.min.css --output-style compressed",
"start": "./node_modules/.bin/grunt dev" "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 () { module.exports = {
return { // Default assets
// Default assets assets: [],
assets: [],
// Style prefix // Style prefix
stylePrefix: 'am-', stylePrefix: 'am-',
// Url where uploads will be send, set false to disable upload // Url where uploads will be send, set false to disable upload
upload: 'http://localhost/assets/upload', upload: 'http://localhost/assets/upload',
// Text on upload input // Text on upload input
uploadText: 'Drop files here or click to upload', uploadText: 'Drop files here or click to upload',
// Label for the add button // Label for the add button
addBtnText: 'Add image', 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'], var Backbone = require('backbone');
function (Backbone) {
return Backbone.Model.extend({
idAttribute: 'src', module.exports = Backbone.Model.extend({
defaults: { idAttribute: 'src',
type: '',
src: '',
},
/** defaults: {
* Get filename of the asset type: '',
* @return {string} src: '',
* @private },
* */
getFilename: function(){
return this.get('src').split('/').pop();
},
/** /**
* Get extension of the asset * Get filename of the asset
* @return {string} * @return {string}
* @private * @private
* */ * */
getExtension: function(){ getFilename() {
return this.getFilename().split('.').pop(); 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'], var Backbone = require('backbone');
function (Backbone, Asset) { var Asset = require('./Asset');
return Asset.extend({
defaults: _.extend({}, Asset.prototype.defaults, { module.exports = Asset.extend({
type: 'image',
unitDim: 'px', defaults: _.extend({}, Asset.prototype.defaults, {
height: 0, type: 'image',
width: 0, unitDim: 'px',
}), height: 0,
width: 0,
}),
});
}); });

133
src/asset_manager/model/Assets.js

@ -1,67 +1,68 @@
define(['backbone', './Asset', './AssetImage'], var Backbone = require('backbone');
function (Backbone, Asset, AssetImage) { var Asset = require('./Asset');
return Backbone.Collection.extend({ var AssetImage = require('./AssetImage');
model: AssetImage, module.exports = Backbone.Collection.extend({
initialize: function(models, opt){ model: AssetImage,
this.model = function(attrs, options) { initialize(models, opt) {
var model;
switch(attrs.type){ this.model = (attrs, options) => {
default: var model;
model = new AssetImage(attrs, options); switch(attrs.type){
} default:
return model; 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 * Add new image asset to the collection
* @return {this} * @param {string} url URL of the image
* @private * @param {Object} opts Options
*/ * @return {this}
addImg: function(url, opts){ * @private
this.add({ */
type: 'image', addImg(url, opts) {
src: url, this.add({
}, opts); type: 'image',
return this; src: url,
}, }, opts);
return this;
/** },
* Prevent inserting assets with the same 'src'
* Seems like idAttribute is not working with dynamic model assignament /**
* @private * Prevent inserting assets with the same 'src'
*/ * Seems like idAttribute is not working with dynamic model assignament
add: function(models, opt) { * @private
var mods = []; */
models = models instanceof Array ? models : [models]; add(models, opt) {
var mods = [];
for (var i = 0, len = models.length; i < len; i++) { models = models instanceof Array ? models : [models];
var model = models[i];
for (var i = 0, len = models.length; i < len; i++) {
if(typeof model === 'string') var model = models[i];
model = {src: model, type: 'image'};
if(typeof model === 'string')
if(!model || !model.src) model = {src: model, type: 'image'};
continue;
if(!model || !model.src)
var found = this.where({src: model.src}); continue;
if(!found.length) var found = this.where({src: model.src});
mods.push(model);
} if(!found.length)
mods.push(model);
if(mods.length == 1) }
mods = mods[0];
if(mods.length == 1)
return Backbone.Collection.prototype.add.apply(this, [mods, opt]); 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'], var AssetView = require('./AssetView');
function (AssetView, assetTemplate) { var assetTemplate = `
return AssetView.extend({ <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:{ module.exports = AssetView.extend({
'click': 'handleClick',
'dblclick': 'handleDblClick',
},
template: _.template(assetTemplate), events:{
'click': 'handleClick',
'dblclick': 'handleDblClick',
},
initialize: function(o) { template: _.template(assetTemplate),
AssetView.prototype.initialize.apply(this, arguments);
this.className += ' ' + this.pfx + 'asset-image';
this.events['click #' + this.pfx + 'close'] = 'removeItem';
this.delegateEvents();
},
/** initialize(o) {
* Trigger when the asset is clicked AssetView.prototype.initialize.apply(this, arguments);
* @private this.className += ' ' + this.pfx + 'asset-image';
* */ this.events['click #' + this.pfx + 'close'] = 'removeItem';
handleClick: function() { this.delegateEvents();
var onClick = this.config.onClick; },
var model = this.model;
model.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'highlight');
if (typeof onClick === 'function') { /**
onClick(model); * Trigger when the asset is clicked
} else { * @private
this.updateTarget(model.get('src')); * */
} handleClick() {
}, var onClick = this.config.onClick;
var model = this.model;
model.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'highlight');
/** if (typeof onClick === 'function') {
* Trigger when the asset is double clicked onClick(model);
* @private } else {
* */ this.updateTarget(model.get('src'));
handleDblClick: function() { }
var onDblClick = this.config.onDblClick; },
var model = this.model;
if (typeof onDblClick === 'function') { /**
onDblClick(model); * Trigger when the asset is double clicked
} else { * @private
this.updateTarget(model.get('src')); * */
} handleDblClick() {
var onDblClick = this.config.onDblClick;
var model = this.model;
var onSelect = model.collection.onSelect; if (typeof onDblClick === 'function') {
if(typeof onSelect == 'function'){ onDblClick(model);
onSelect(this.model); } else {
} this.updateTarget(model.get('src'));
}, }
/** var onSelect = model.collection.onSelect;
* Update target if exists if(typeof onSelect == 'function'){
* @param {String} v Value onSelect(this.model);
* @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 );
}
},
/** /**
* Remove asset from collection * Update target if exists
* @private * @param {String} v Value
* */ * @private
removeItem: function(e){ * */
e.stopPropagation(); updateTarget(v) {
this.model.collection.remove(this.model); 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'), * Remove asset from collection
dim = this.model.get('width') && this.model.get('height') ? * @private
this.model.get('width')+' x '+this.model.get('height') : ''; * */
name = name ? name : this.model.get('src').split("/").pop(); removeItem(e) {
name = name && name.length > 30 ? name.substring(0, 30)+'...' : name; e.stopPropagation();
dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : ''; this.model.collection.remove(this.model);
this.$el.html( this.template({ },
name: name,
src: this.model.get('src'), render() {
dim: dim, var name = this.model.get('name'),
pfx: this.pfx, dim = this.model.get('width') && this.model.get('height') ?
ppfx: this.ppfx this.model.get('width')+' x '+this.model.get('height') : '';
})); name = name ? name : this.model.get('src').split("/").pop();
this.$el.attr('class', this.className); name = name && name.length > 30 ? name.substring(0, 30)+'...' : name;
return this; 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) { var Backbone = require('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);
},
});
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'], var Backbone = require('backbone');
function (Backbone, AssetView, AssetImageView, FileUploader, assetsTemplate) { var AssetView = require('./AssetView');
return Backbone.View.extend({ var AssetImageView = require('./AssetImageView');
var FileUploader = require('./FileUploader');
template: _.template(assetsTemplate), var assetsTemplate = `
<div class="<%= pfx %>assets-cont">
initialize: function(o) { <div class="<%= pfx %>assets-header">
this.options = o; <form class="<%= pfx %>add-asset">
this.config = o.config; <div class="<%= ppfx %>field <%= pfx %>add-field">
this.pfx = this.config.stylePrefix || ''; <input placeholder="http://path/to/the/image.jpg"/>
this.ppfx = this.config.pStylePrefix || ''; </div>
this.listenTo( this.collection, 'add', this.addToAsset ); <button class="<%= ppfx %>btn-prim"><%= btnText %></button>
this.listenTo( this.collection, 'deselectAll', this.deselectAll ); <div style="clear:both"></div>
this.className = this.pfx + 'assets'; </form>
<div class="<%= pfx %>dips" style="display:none">
this.events = {}; <button class="fa fa-th <%= ppfx %>btnt"></button>
this.events.submit = 'addFromStr'; <button class="fa fa-th-list <%= ppfx %>btnt"></button>
this.delegateEvents(); </div>
}, </div>
<div class="<%= pfx %>assets"></div>
/** <div style="clear:both"></div>
* Add new asset to the collection via string </div>
* @param {Event} e Event object
* @return {this} `;
* @private
*/ module.exports = Backbone.View.extend({
addFromStr: function(e){
e.preventDefault(); template: _.template(assetsTemplate),
var input = this.getInputUrl(); initialize(o) {
this.options = o;
var url = input.value.trim(); this.config = o.config;
this.pfx = this.config.stylePrefix || '';
if(!url) this.ppfx = this.config.pStylePrefix || '';
return; this.listenTo( this.collection, 'add', this.addToAsset );
this.listenTo( this.collection, 'deselectAll', this.deselectAll );
this.collection.addImg(url, {at: 0}); this.className = this.pfx + 'assets';
this.getAssetsEl().scrollTop = 0; this.events = {};
input.value = ''; this.events.submit = 'addFromStr';
return this; this.delegateEvents();
}, },
/** /**
* Returns assets element * Add new asset to the collection via string
* @return {HTMLElement} * @param {Event} e Event object
* @private * @return {this}
*/ * @private
getAssetsEl: function(){ */
//if(!this.assets) // Not able to cache as after the rerender it losses the ref addFromStr(e) {
this.assets = this.el.querySelector('.' + this.pfx + 'assets'); e.preventDefault();
return this.assets;
}, var input = this.getInputUrl();
/** var url = input.value.trim();
* Returns input url element
* @return {HTMLElement} if(!url)
* @private return;
*/
getInputUrl: function(){ this.collection.addImg(url, {at: 0});
if(!this.inputUrl || !this.inputUrl.value)
this.inputUrl = this.el.querySelector('.'+this.pfx+'add-asset input'); this.getAssetsEl().scrollTop = 0;
return this.inputUrl; input.value = '';
}, return this;
},
/**
* Add asset to collection /**
* @private * Returns assets element
* */ * @return {HTMLElement}
addToAsset: function(model){ * @private
this.addAsset(model); */
}, getAssetsEl() {
//if(!this.assets) // Not able to cache as after the rerender it losses the ref
/** this.assets = this.el.querySelector('.' + this.pfx + 'assets');
* Add new asset to collection return this.assets;
* @param Object Model },
* @param Object Fragment collection
* @return Object Object created /**
* @private * Returns input url element
* */ * @return {HTMLElement}
addAsset: function(model, fragmentEl){ * @private
var fragment = fragmentEl || null; */
var viewObject = AssetView; getInputUrl() {
if(!this.inputUrl || !this.inputUrl.value)
if(model.get('type').indexOf("image") > -1) this.inputUrl = this.el.querySelector('.'+this.pfx+'add-asset input');
viewObject = AssetImageView; return this.inputUrl;
},
var view = new viewObject({
model : model, /**
config : this.config, * Add asset to collection
}); * @private
var rendered = view.render().el; * */
addToAsset(model) {
if(fragment){ this.addAsset(model);
fragment.appendChild( rendered ); },
}else{
var assetsEl = this.getAssetsEl(); /**
if(assetsEl) * Add new asset to collection
assetsEl.insertBefore(rendered, assetsEl.firstChild); * @param Object Model
} * @param Object Fragment collection
* @return Object Object created
return rendered; * @private
}, * */
addAsset(model, fragmentEl) {
/** var fragment = fragmentEl || null;
* Deselect all assets var viewObject = AssetView;
* @private
* */ if(model.get('type').indexOf("image") > -1)
deselectAll: function(){ viewObject = AssetImageView;
this.$el.find('.' + this.pfx + 'highlight').removeClass(this.pfx + 'highlight');
}, var view = new viewObject({
model,
render: function() { config : this.config,
var fragment = document.createDocumentFragment(); });
this.$el.empty(); var rendered = view.render().el;
this.collection.each(function(model){ if(fragment){
this.addAsset(model, fragment); fragment.appendChild( rendered );
},this); }else{
var assetsEl = this.getAssetsEl();
this.$el.html(this.template({ if(assetsEl)
pfx: this.pfx, assetsEl.insertBefore(rendered, assetsEl.firstChild);
ppfx: this.ppfx, }
btnText: this.config.addBtnText,
})); return rendered;
},
this.$el.find('.'+this.pfx + 'assets').append(fragment);
return this; /**
} * 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'], var Backbone = require('backbone');
function (Backbone, fileUploaderTemplate) { var fileUploaderTemplate = `
return Backbone.View.extend({ <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) { events: {},
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();
},
/** initialize(o) {
* Upload files this.options = o || {};
* @param {Object} e Event this.config = o.config || {};
* @private this.pfx = this.config.stylePrefix || '';
* */ this.target = this.collection || {};
uploadFile : function(e){ this.uploadId = this.pfx + 'uploadFile';
var files = e.dataTransfer ? e.dataTransfer.files : e.target.files, this.disabled = !this.config.upload;
formData = new FormData(); this.events['change #' + this.uploadId] = 'uploadFile';
for (var i = 0; i < files.length; i++) { this.delegateEvents();
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
});
},
/** /**
* Make input file droppable * Upload files
* @private * @param {Object} e Event
* */ * @private
initDrop: function(){ * */
var that = this; uploadFile(e) {
if(!this.uploadForm){ var files = e.dataTransfer ? e.dataTransfer.files : e.target.files,
this.uploadForm = this.$el.find('form').get(0); formData = new FormData();
if( 'draggable' in this.uploadForm ){ for (var i = 0; i < files.length; i++) {
var uploadFile = this.uploadFile; formData.append('files[]', files[i]);
this.uploadForm.ondragover = function(){ }
this.className = that.pfx + 'hover'; var target = this.target;
return false; $.ajax({
}; url : this.config.upload,
this.uploadForm.ondragleave = function(){ type : 'POST',
this.className = ''; data : formData,
return false; beforeSend : this.config.beforeSend,
}; complete : this.config.onComplete,
this.uploadForm.ondrop = function(e){ xhrFields : {
this.className = ''; onprogress(e) {
e.preventDefault(); if (e.lengthComputable) {
that.uploadFile(e); /*var result = e.loaded / e.total * 100 + '%';*/
return; }
}; },
} 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({ * Make input file droppable
title: this.config.uploadText, * @private
uploadId: this.uploadId, * */
disabled: this.disabled, initDrop() {
pfx: this.pfx var that = this;
}) ); if(!this.uploadForm){
this.initDrop(); this.uploadForm = this.$el.find('form').get(0);
this.$el.attr('class', this.pfx + 'file-uploader'); if( 'draggable' in this.uploadForm ){
return this; 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 () { module.exports = {
return {
'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'], var Backbone = require('backbone');
function(Backbone){
return Backbone.Model.extend({ module.exports = Backbone.Model.extend({
defaults :{ defaults :{
label: '', label: '',
content: '', content: '',
attributes: {}, attributes: {},
}, },
}); });
});

11
src/block_manager/model/Blocks.js

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

82
src/block_manager/view/BlockView.js

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

237
src/block_manager/view/BlocksView.js

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

22
src/canvas/config/config.js

@ -1,16 +1,14 @@
define(function () { module.exports = {
return {
stylePrefix: 'cv-', stylePrefix: 'cv-',
// Coming soon // Coming soon
rulers: false, rulers: false,
/* /*
* append scripts in head of iframe before renderBody content * append scripts in head of iframe before renderBody content
* need to manually maintain the same scripts in cms's render template * need to manually maintain the same scripts in cms's render template
*/ */
scripts: [] 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'], var Backbone = require('backbone');
function(Backbone, Frame){ var Frame = require('./Frame');
return Backbone.Model.extend({ module.exports = Backbone.Model.extend({
defaults :{ defaults :{
frame: '', frame: '',
wrapper: '', wrapper: '',
rulers: false, rulers: false,
}, },
initialize: function(config) { initialize(config) {
var conf = this.conf || {}; var conf = this.conf || {};
this.set('frame', new Frame(conf.frame)); this.set('frame', new Frame(conf.frame));
}, },
}); });
});

20
src/canvas/model/Frame.js

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

479
src/canvas/view/CanvasView.js

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

102
src/canvas/view/FrameView.js

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

12
src/code_manager/config/config.js

@ -1,8 +1,6 @@
define(function () { module.exports = {
return { // Style prefix
// Style prefix stylePrefix: 'cm-',
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', var Backbone = require('backbone');
'codemirror/lib/codemirror', var CodeMirror = require('codemirror/lib/codemirror');
'codemirror/mode/htmlmixed/htmlmixed', var htmlMode = require('codemirror/mode/htmlmixed/htmlmixed');
'codemirror/mode/css/css', var cssMode = require('codemirror/mode/css/css');
'formatting' var formatting = require('codemirror-formatting');
],
function(Backbone, CodeMirror, htmlMode, cssMode, formatting) {
/**
* @class CodeViewer
* */
return Backbone.Model.extend({
defaults: { module.exports = Backbone.Model.extend({
input : '',
label : '',
codeName : '',
theme : '',
readOnly : true,
lineNumbers : true,
},
/** @inheritdoc */ defaults: {
init: function(el) input : '',
{ label : '',
this.editor = CodeMirror.fromTextArea(el, { codeName : '',
dragDrop: false, theme : '',
lineWrapping: true, readOnly : true,
lineNumbers: this.get('lineNumbers'), lineNumbers : true,
readOnly: this.get('readOnly'), },
mode: this.get('codeName'),
theme: this.get('theme'),
});
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 */ return this;
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);
}
},
}); /** @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'], var Backbone = require('backbone');
function (Backbone) {
/** module.exports = Backbone.Model.extend({
* @class CssGenerator
* */ initialize() {
return Backbone.Model.extend({ this.compCls = [];
},
initialize: function() {
this.compCls = []; /**
}, * Get CSS from component
* @param {Model} model
/** * @return {String}
* Get CSS from component */
* @param {Model} model buildFromModel(model) {
* @return {String} var code = '';
*/ var style = model.get('style');
buildFromModel: function (model) { var classes = model.get('classes');
var code = '';
var style = model.get('style'); // Let's know what classes I've found
var classes = model.get('classes'); if(classes) {
classes.each(function(model){
// Let's know what classes I've found this.compCls.push(model.get('name'));
if(classes) { }, this);
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 += this.buildFromRule(rule);
code += '#' + model.cid + '{'; }, this);
for(var prop in style){
if(style.hasOwnProperty(prop))
code += prop + ':' + style[prop] + ';';
}
code += '}';
}
return code; // Get media rules
}, for (var ruleW in mediaRules) {
var meRules = mediaRules[ruleW];
/** var ruleC = '';
* Get CSS from components for(var i = 0, len = meRules.length; i < len; i++){
* @param {Model} model ruleC += this.buildFromRule(meRules[i]);
* @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 + '}';
}
}
if (ruleC) {
code += '@media ' + ruleW + '{' + ruleC + '}';
} }
return code; }
},
}
/** return code;
* Get CSS from the rule model },
* @param {Model} rule
* @return {string} CSS string /**
*/ * Get CSS from the rule model
buildFromRule: function(rule) { * @param {Model} rule
var result = ''; * @return {string} CSS string
var selectorsAdd = rule.get('selectorsAdd'); */
var selectors = rule.get('selectors'); buildFromRule(rule) {
var ruleStyle = rule.get('style'); var result = '';
var state = rule.get('state'); var selectorsAdd = rule.get('selectorsAdd');
var strSel = ''; var selectors = rule.get('selectors');
var found = 0; var ruleStyle = rule.get('style');
var compCls = this.compCls; var state = rule.get('state');
var strSel = '';
// Get string of selectors var found = 0;
selectors.each(function(selector){ var compCls = this.compCls;
strSel += '.' + selector.get('name');
if(compCls.indexOf(selector.get('name')) > -1) // Get string of selectors
found = 1; selectors.each(selector => {
}); strSel += '.' + selector.get('name');
if(compCls.indexOf(selector.get('name')) > -1)
// With 'found' will skip rules which selectors are not found in found = 1;
// canvas components. });
if ((strSel && found) || selectorsAdd) {
strSel += state ? ':' + state : ''; // With 'found' will skip rules which selectors are not found in
strSel += selectorsAdd ? (strSel ? ', ' : '') + selectorsAdd : ''; // canvas components.
var strStyle = ''; if ((strSel && found) || selectorsAdd) {
strSel += state ? ':' + state : '';
// Get string of style properties strSel += selectorsAdd ? (strSel ? ', ' : '') + selectorsAdd : '';
if(ruleStyle && Object.keys(ruleStyle).length !== 0){ var strStyle = '';
for(var prop2 in ruleStyle){
if(ruleStyle.hasOwnProperty(prop2)) // Get string of style properties
strStyle += prop2 + ':' + ruleStyle[prop2] + ';'; 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 result; if(strStyle)
}, result += strSel + '{' + strStyle + '}';
}
return result;
},
});
}); });

32
src/code_manager/model/HtmlGenerator.js

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

64
src/code_manager/model/JsonGenerator.js

@ -1,40 +1,36 @@
define(['backbone'], var Backbone = require('backbone');
function (Backbone) {
/**
* @class JsonGenerator
* */
return Backbone.Model.extend({
/** @inheritdoc */ module.exports = Backbone.Model.extend({
build: function(model) {
var json = model.toJSON();
this.beforeEach(json);
_.each(json,function(v, attr){ /** @inheritdoc */
var obj = json[attr]; build(model) {
if(obj instanceof Backbone.Model){ var json = model.toJSON();
json[attr] = this.build(obj); this.beforeEach(json);
}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);
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);
/** return json;
* Execute on each object },
* @param {Object} obj
*/ /**
beforeEach: function(obj) { * Execute on each object
delete obj.status; * @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'], var Backbone = require('backbone');
function (Backbone, vTemplate) {
/** module.exports = Backbone.View.extend({
* @class EditorView
* */ template: _.template(`
return Backbone.View.extend({ <div class="<%= pfx %>editor" id="<%= pfx %><%= codeName %>">
<div id="<%= pfx %>title"><%= label %></div>
template: _.template(vTemplate), <div id="<%= pfx %>code"></div>
</div>`),
initialize: function(o){
this.config = o.config || {}; initialize(o) {
this.pfx = this.config.stylePrefix; this.config = o.config || {};
}, this.pfx = this.config.stylePrefix;
},
render : function(){
var obj = this.model.toJSON(); render() {
obj.pfx = this.pfx; var obj = this.model.toJSON();
this.$el.html( this.template(obj) ); obj.pfx = this.pfx;
this.$el.attr('class', this.pfx + 'editor-c'); this.$el.html( this.template(obj) );
this.$el.find('#'+this.pfx+'code').html(this.model.get('input')); this.$el.attr('class', this.pfx + 'editor-c');
return this; 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 () { module.exports = {
return {
ESCAPE_KEY : 27, ESCAPE_KEY: 27,
stylePrefix : 'com-', stylePrefix: 'com-',
defaults : [], defaults: [],
// Editor model // Editor model
em : null, em: null,
// If true center new first-level components // If true center new first-level components
firstCentered : true, firstCentered: true,
// If true the new component will created with 'height', else 'min-height' // If true the new component will created with 'height', else 'min-height'
newFixedH : false, newFixedH: false,
// Minimum height (in px) of new component // Minimum height (in px) of new component
minComponentH : 50, minComponentH: 50,
// Minimum width (in px) of component on creation // Minimum width (in px) of component on creation
minComponentW : 50, 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'], var Backbone = require('backbone');
function (Backbone) {
/** module.exports = Backbone.Model.extend({
* @class Command
* */ defaults :{
return Backbone.Model.extend({ id: '',
}
defaults :{
id : '',
}
});
}); });

17
src/commands/model/Commands.js

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

227
src/commands/view/CommandAbstract.js

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

461
src/commands/view/CreateComponent.js

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

143
src/commands/view/DeleteComponent.js

@ -1,88 +1,79 @@
define(['backbone', './SelectComponent'], var Backbone = require('backbone');
function(Backbone, SelectComponent) { var SelectComponent = require('./SelectComponent');
/**
* @class DeleteComponent
* @private
* */
return _.extend({},SelectComponent,{
init: function(o){ module.exports = _.extend({},SelectComponent,{
_.bindAll(this, 'startDelete', 'stopDelete', 'onDelete');
this.hoverClass = this.pfx + 'hover-delete';
this.badgeClass = this.pfx + 'badge-red';
},
enable: function() init(o) {
{ _.bindAll(this, 'startDelete', 'stopDelete', 'onDelete');
var that = this; this.hoverClass = this.pfx + 'hover-delete';
this.$el.find('*') this.badgeClass = this.pfx + 'badge-red';
.mouseover(this.startDelete) },
.mouseout(this.stopDelete)
.click(this.onDelete);
},
/** enable() {
* Start command var that = this;
* @param {Object} e this.$el.find('*')
* @private .mouseover(this.startDelete)
*/ .mouseout(this.stopDelete)
startDelete: function(e) .click(this.onDelete);
{ },
e.stopPropagation();
var $this = $(e.target);
// Show badge if possible /**
if($this.data('model').get('removable')){ * Start command
$this.addClass(this.hoverClass); * @param {Object} e
this.attachBadge($this.get(0)); * @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) * Stop command
this.badge.css({ left: -1000, top:-1000 }); * @param {Object} e
}, * @private
*/
stopDelete(e) {
e.stopPropagation();
var $this = $(e.target);
$this.removeClass(this.hoverClass);
/** // Hide badge if possible
* Delete command if(this.badge)
* @param {Object} e this.badge.css({ left: -1000, top:-1000 });
* @private },
*/
onDelete: function(e)
{
e.stopPropagation();
var $this = $(e.target);
// Do nothing in case can't remove /**
if(!$this.data('model').get('removable')) * Delete command
return; * @param {Object} e
* @private
*/
onDelete(e) {
e.stopPropagation();
var $this = $(e.target);
$this.data('model').destroy(); // Do nothing in case can't remove
this.removeBadge(); if(!$this.data('model').get('removable'))
this.clean(); return;
},
/** $this.data('model').destroy();
* Updates badge label this.removeBadge();
* @param {Object} model this.clean();
* @private },
* */
updateBadgeLabel: function (model)
{
this.badge.html( 'Remove ' + model.getName() );
},
}); /**
}); * 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() { module.exports = {
/**
* @class ExportTemplate
* @private
* */
return {
run: function(editor, sender) { run(editor, sender) {
this.sender = sender; this.sender = sender;
this.wrapper = editor.DomComponents.getWrapper(); this.wrapper = editor.DomComponents.getWrapper();
this.components = editor.DomComponents.getComponents(); this.components = editor.DomComponents.getComponents();
this.modal = editor.Modal || null; this.modal = editor.Modal || null;
this.cm = editor.CodeManager || null; this.cm = editor.CodeManager || null;
this.cssc = editor.CssComposer || null; this.cssc = editor.CssComposer || null;
this.protCss = editor.Config.protectedCss; this.protCss = editor.Config.protectedCss;
this.pfx = editor.Config.stylePrefix || ''; this.pfx = editor.Config.stylePrefix || '';
this.enable(); this.enable();
}, },
/** /**
* Build editor * Build editor
* @param {String} codeName * @param {String} codeName
* @param {String} theme * @param {String} theme
* @param {String} label * @param {String} label
* *
* @return {Object} Editor * @return {Object} Editor
* @private * @private
* */ * */
buildEditor: function(codeName, theme, label) { buildEditor(codeName, theme, label) {
if(!this.codeMirror) if(!this.codeMirror)
this.codeMirror = this.cm.getViewer('CodeMirror'); this.codeMirror = this.cm.getViewer('CodeMirror');
var $input = $('<textarea>'), var $input = $('<textarea>'),
editor = this.codeMirror.clone().set({ editor = this.codeMirror.clone().set({
label : label, label,
codeName : codeName, codeName,
theme : theme, theme,
input : $input[0], input : $input[0],
}), }),
$editor = new this.cm.EditorView({ $editor = new this.cm.EditorView({
model : editor, model : editor,
config : this.cm.getConfig() config : this.cm.getConfig()
}).render().$el; }).render().$el;
editor.init( $input[0] ); editor.init( $input[0] );
return { el: editor, $el: $editor }; return { el: editor, $el: $editor };
}, },
enable: function() { enable() {
if(!this.$editors){ if(!this.$editors){
var oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML'), var oHtmlEd = this.buildEditor('htmlmixed', 'hopscotch', 'HTML'),
oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS'); oCsslEd = this.buildEditor('css', 'hopscotch', 'CSS');
this.htmlEditor = oHtmlEd.el; this.htmlEditor = oHtmlEd.el;
this.cssEditor = oCsslEd.el; this.cssEditor = oCsslEd.el;
this.$editors = $('<div>', {class: this.pfx + 'export-dl'}); this.$editors = $('<div>', {class: this.pfx + 'export-dl'});
this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el); this.$editors.append(oHtmlEd.$el).append(oCsslEd.$el);
} }
if(this.modal){ if(this.modal){
this.modal.setTitle('Export template'); this.modal.setTitle('Export template');
this.modal.setContent(this.$editors); this.modal.setContent(this.$editors);
this.modal.open(); this.modal.open();
} }
var addCss = this.protCss || ''; var addCss = this.protCss || '';
//this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc)); //this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc));
this.htmlEditor.setContent(this.em.getHtml()); this.htmlEditor.setContent(this.em.getHtml());
this.cssEditor.setContent(addCss + this.cm.getCode(this.wrapper, 'css', this.cssc)); this.cssEditor.setContent(addCss + this.cm.getCode(this.wrapper, 'css', this.cssc));
if(this.sender) if(this.sender)
this.sender.set('active',false); 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 { /**
/** * Enable fullscreen mode and return browser prefix
* Check if fullscreen mode is enabled * @param {HTMLElement} el
* @return {Boolean} * @return {string}
*/ */
isEnabled: function(){ enable(el) {
var d = document; var pfx = '';
if(d.fullscreenElement || d.webkitFullscreenElement || d.mozFullScreenElement) if (el.requestFullscreen)
return 1; el.requestFullscreen();
else else if (el.webkitRequestFullscreen) {
return 0; 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 * Disable fullscreen mode
* @param {HTMLElement} el */
* @return {string} disable() {
*/ var d = document;
enable: function(el){ if (d.exitFullscreen)
var pfx = ''; d.exitFullscreen();
if (el.requestFullscreen) else if (d.webkitExitFullscreen)
el.requestFullscreen(); d.webkitExitFullscreen();
else if (el.webkitRequestFullscreen) { else if (d.mozCancelFullScreen)
pfx = 'webkit'; d.mozCancelFullScreen();
el.webkitRequestFullscreen(); else if (d.msExitFullscreen)
}else if (el.mozRequestFullScreen) { d.msExitFullscreen();
pfx = 'moz'; },
el.mozRequestFullScreen();
}else if (el.msRequestFullscreen)
el.msRequestFullscreen();
else
console.warn('Fullscreen not supported');
return pfx;
},
/** /**
* Disable fullscreen mode * Triggered when the state of the fullscreen is changed. Inside detects if
*/ * it's enabled
disable: function(){ * @param {strinf} pfx Browser prefix
var d = document; * @param {Event} e
if (d.exitFullscreen) */
d.exitFullscreen(); fsChanged(pfx, e) {
else if (d.webkitExitFullscreen) var d = document;
d.webkitExitFullscreen(); var ev = (pfx || '') + 'fullscreenchange';
else if (d.mozCancelFullScreen) if(!this.isEnabled()){
d.mozCancelFullScreen(); this.stop(null, this.sender);
else if (d.msExitFullscreen) document.removeEventListener(ev, this.fsChanged);
d.msExitFullscreen(); }
}, },
/** run(editor, sender) {
* Triggered when the state of the fullscreen is changed. Inside detects if this.sender = sender;
* it's enabled var pfx = this.enable(editor.getContainer());
* @param {strinf} pfx Browser prefix this.fsChanged = this.fsChanged.bind(this, pfx);
* @param {Event} e document.addEventListener(pfx + 'fullscreenchange', this.fsChanged);
*/ if(editor)
fsChanged: function(pfx, e){ editor.trigger('change:canvasOffset');
var d = document; },
var ev = (pfx || '') + 'fullscreenchange';
if(!this.isEnabled()){
this.stop(null, this.sender);
document.removeEventListener(ev, this.fsChanged);
}
},
run: function(editor, sender){ stop(editor, sender) {
this.sender = sender; if(sender && sender.set)
var pfx = this.enable(editor.getContainer()); sender.set('active', false);
this.fsChanged = this.fsChanged.bind(this, pfx); this.disable();
document.addEventListener(pfx + 'fullscreenchange', this.fsChanged); if(editor)
if(editor) editor.trigger('change:canvasOffset');
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'], var Backbone = require('backbone');
function(Backbone, InsertCustom) { var InsertCustom = require('./InsertCustom');
/**
* @class ImageComponent
* @private
* */
return _.extend({}, InsertCustom, {
/** module.exports = _.extend({}, 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';
}
},
/** /**
* Trigger after insert * Trigger before insert
* @param {Object} model Model created after insert * @param {Object} object
* @private * @private
* */ *
afterInsert: function(model){ * */
model.trigger('dblclick'); beforeInsert(object) {
if(this.sender) object.type = 'image';
this.sender.set('active', false); 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'], var Backbone = require('backbone');
function(Backbone, CreateComponent) { var CreateComponent = require('./CreateComponent');
/**
* @class InsertCustom
* @private
* */
return _.extend({}, CreateComponent, {
init: function(){ module.exports = _.extend({}, CreateComponent, {
CreateComponent.init.apply(this, arguments);
_.bindAll(this, 'insertComponent');
this.allowDraw = 0;
},
/** init(...args) {
* Run method CreateComponent.init.apply(this, args);
* @private _.bindAll(this, 'insertComponent');
* */ this.allowDraw = 0;
run: function(em, sender, options) { },
this.em = em;
this.sender = sender;
this.opt = options || {};
this.$wr = this.$wrapper;
this.enable();
},
enable: function(){ /**
CreateComponent.enable.apply(this, arguments); * Run method
this.$wr.on('click', this.insertComponent); * @private
}, * */
run(em, sender, options) {
this.em = em;
this.sender = sender;
this.opt = options || {};
this.$wr = this.$wrapper;
this.enable();
},
/** enable(...args) {
* Start insert event CreateComponent.enable.apply(this, args);
* @private this.$wr.on('click', this.insertComponent);
* */ },
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});
if(this.opt.terminateAfterInsert && this.sender) /**
this.sender.set('active', false); * Start insert event
else * @private
this.enable(); * */
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) if(this.opt.terminateAfterInsert && this.sender)
return; this.sender.set('active', false);
else
this.enable();
if(this.em) if(!model)
this.em.editor.initChildrenComp(model); return;
this.afterInsert(model, this); if(this.em)
}, this.em.editor.initChildrenComp(model);
/** this.afterInsert(model, this);
* Trigger before insert },
* @param {Object} obj
* @private
* */
beforeInsert: function(obj){},
/** /**
* Trigger after insert * Trigger before insert
* @param {Object} model Model created after insert * @param {Object} obj
* @private * @private
* */ * */
afterInsert: function(model){}, beforeInsert(obj) {},
/** /**
* Create different object, based on content, to insert inside canvas * Trigger after insert
* * @param {Object} model Model created after insert
* @return {Object} * @private
* @private * */
* */ afterInsert(model) {},
buildContent: function(){
return this.opt.content || {}; /**
}, * 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'], var Backbone = require('backbone');
function(Backbone, SelectComponent, SelectPosition) { var SelectComponent = require('./SelectComponent');
var SelectPosition = require('./SelectPosition');
return _.extend({}, SelectPosition, SelectComponent, {
module.exports = _.extend({}, SelectPosition, SelectComponent, {
init: function(o){
SelectComponent.init.apply(this, arguments); init(o) {
_.bindAll(this, 'initSorter','rollback', 'onEndMove'); SelectComponent.init.apply(this, arguments);
this.opt = o; _.bindAll(this, 'initSorter','rollback', 'onEndMove');
this.hoverClass = this.ppfx + 'highlighter-warning'; this.opt = o;
this.badgeClass = this.ppfx + 'badge-warning'; this.hoverClass = this.ppfx + 'highlighter-warning';
this.noSelClass = this.ppfx + 'no-select'; this.badgeClass = this.ppfx + 'badge-warning';
}, this.noSelClass = this.ppfx + 'no-select';
},
enable: function() {
SelectComponent.enable.apply(this, arguments); enable(...args) {
this.getBadgeEl().addClass(this.badgeClass); SelectComponent.enable.apply(this, args);
this.getHighlighterEl().addClass(this.hoverClass); this.getBadgeEl().addClass(this.badgeClass);
var wp = this.$wrapper; this.getHighlighterEl().addClass(this.hoverClass);
wp.css('cursor','move'); var wp = this.$wrapper;
wp.on('mousedown', this.initSorter); wp.css('cursor','move');
wp.on('mousedown', this.initSorter);
// Avoid strange moving behavior
wp.addClass(this.noSelClass); // Avoid strange moving behavior
}, wp.addClass(this.noSelClass);
},
/**
* Overwrite for doing nothing /**
* @private * Overwrite for doing nothing
*/ * @private
toggleClipboard: function(){}, */
toggleClipboard() {},
/**
* Delegate sorting /**
* @param {Event} e * Delegate sorting
* @private * @param {Event} e
* */ * @private
initSorter: function(e){ * */
var el = $(e.target).data('model'); initSorter(e) {
var drag = el.get('draggable'); var el = $(e.target).data('model');
if(!drag) var drag = el.get('draggable');
return; if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null; // Avoid badge showing on move
this.startSelectPosition(e.target, this.frameEl.contentDocument); this.cacheEl = null;
this.sorter.draggable = drag; this.startSelectPosition(e.target, this.frameEl.contentDocument);
this.sorter.onEndMove = this.onEndMove.bind(this); this.sorter.draggable = drag;
this.stopSelectComponent(); this.sorter.onEndMove = this.onEndMove.bind(this);
this.$wrapper.off('mousedown', this.initSorter); this.stopSelectComponent();
this.getContentWindow().on('keydown', this.rollback); this.$wrapper.off('mousedown', this.initSorter);
}, this.getContentWindow().on('keydown', this.rollback);
},
/**
* Init sorter from model /**
* @param {Object} model * Init sorter from model
* @private * @param {Object} model
*/ * @private
initSorterFromModel: function(model) { */
var drag = model.get('draggable'); initSorterFromModel(model) {
if(!drag) var drag = model.get('draggable');
return; if(!drag)
// Avoid badge showing on move return;
this.cacheEl = null; // Avoid badge showing on move
var el = model.view.el; this.cacheEl = null;
this.startSelectPosition(el, this.frameEl.contentDocument); var el = model.view.el;
this.sorter.draggable = drag; this.startSelectPosition(el, this.frameEl.contentDocument);
this.sorter.onEndMove = this.onEndMoveFromModel.bind(this); this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMoveFromModel.bind(this);
/*
this.sorter.setDragHelper(el); /*
var dragHelper = this.sorter.dragHelper; this.sorter.setDragHelper(el);
dragHelper.className = this.ppfx + 'drag-helper'; var dragHelper = this.sorter.dragHelper;
dragHelper.innerHTML = ''; dragHelper.className = this.ppfx + 'drag-helper';
dragHelper.backgroundColor = 'white'; dragHelper.innerHTML = '';
*/ dragHelper.backgroundColor = 'white';
*/
this.stopSelectComponent();
this.getContentWindow().on('keydown', this.rollback); this.stopSelectComponent();
}, this.getContentWindow().on('keydown', this.rollback);
},
onEndMoveFromModel: function() {
this.getContentWindow().off('keydown', this.rollback); onEndMoveFromModel() {
}, this.getContentWindow().off('keydown', this.rollback);
},
/**
* Callback after sorting /**
* @private * Callback after sorting
*/ * @private
onEndMove: function(){ */
this.enable(); onEndMove() {
this.getContentWindow().off('keydown', this.rollback); this.enable();
}, this.getContentWindow().off('keydown', this.rollback);
},
/**
* Say what to do after the component was selected (selectComponent) /**
* @param {Event} e * Say what to do after the component was selected (selectComponent)
* @param {Object} Selected element * @param {Event} e
* @private * @param {Object} Selected element
* */ * @private
onSelect: function(e,el){}, * */
onSelect(e, el) {},
/**
* Used to bring the previous situation before start moving the component /**
* @param {Event} e * Used to bring the previous situation before start moving the component
* @param {Boolean} Indicates if rollback in anycase * @param {Event} e
* @private * @param {Boolean} Indicates if rollback in anycase
* */ * @private
rollback: function(e, force){ * */
var key = e.which || e.keyCode; rollback(e, force) {
if(key == this.opt.ESCAPE_KEY || force){ var key = e.which || e.keyCode;
this.sorter.moved = false; if(key == this.opt.ESCAPE_KEY || force){
this.sorter.endMove(); this.sorter.moved = false;
} this.sorter.endMove();
return; }
}, return;
},
/**
* Returns badge element /**
* @return {HTMLElement} * Returns badge element
* @private * @return {HTMLElement}
*/ * @private
getBadgeEl: function(){ */
if(!this.$badge) getBadgeEl() {
this.$badge = $(this.getBadge()); if(!this.$badge)
return this.$badge; this.$badge = $(this.getBadge());
}, return this.$badge;
},
/**
* Returns highlighter element /**
* @return {HTMLElement} * Returns highlighter element
* @private * @return {HTMLElement}
*/ * @private
getHighlighterEl: function(){ */
if(!this.$hl) getHighlighterEl() {
this.$hl = $(this.canvas.getHighlighter()); if(!this.$hl)
return this.$hl; this.$hl = $(this.canvas.getHighlighter());
}, return this.$hl;
},
stop: function(){
SelectComponent.stop.apply(this, arguments); stop(...args) {
this.getBadgeEl().removeClass(this.badgeClass); SelectComponent.stop.apply(this, args);
this.getHighlighterEl().removeClass(this.hoverClass); this.getBadgeEl().removeClass(this.badgeClass);
var wp = this.$wrapper; this.getHighlighterEl().removeClass(this.hoverClass);
wp.css('cursor', '').unbind().removeClass(this.noSelClass); 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) { assetManager.onClick(opt.onClick);
var opt = opts || {}; assetManager.onDblClick(opt.onDblClick);
var config = editor.getConfig();
var modal = editor.Modal;
var assetManager = editor.AssetManager;
assetManager.onClick(opt.onClick); // old API
assetManager.onDblClick(opt.onDblClick); assetManager.setTarget(opt.target);
assetManager.onSelect(opt.onSelect);
// old API modal.setTitle(opt.modalTitle || 'Select image');
assetManager.setTarget(opt.target); modal.setContent(assetManager.render());
assetManager.onSelect(opt.onSelect); 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) { this.blocks.style.display = 'block';
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'; stop() {
}, if(this.blocks)
this.blocks.style.display = 'none';
stop: function() { }
if(this.blocks) };
this.blocks.style.display = 'none';
}
};
});

60
src/commands/view/OpenLayers.js

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

140
src/commands/view/OpenStyleManager.js

@ -1,82 +1,78 @@
define(['StyleManager'], function(StyleManager) { var StyleManager = require('style_manager');
/**
* @class OpenStyleManager
* @private
* */
return {
run: function(em, sender) { module.exports = {
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);
// Device Manager run(em, sender) {
var dvm = em.DeviceManager; this.sender = sender;
if(dvm && config.showDevices){ if(!this.$cn){
var devicePanel = panels.addPanel({ id: 'devices-c'}); var config = em.getConfig(),
devicePanel.set('appendContent', dvm.render()).trigger('change:appendContent'); panels = em.Panels;
} // Main container
this.$cn = $('<div/>');
// Secondary container
this.$cn2 = $('<div/>');
this.$cn.append(this.$cn2);
// Class Manager container // Device Manager
var clm = em.SelectorManager; var dvm = em.DeviceManager;
if(clm) if(dvm && config.showDevices){
this.$cn2.append(clm.render([])); var devicePanel = panels.addPanel({ id: 'devices-c'});
devicePanel.set('appendContent', dvm.render()).trigger('change:appendContent');
}
this.$cn2.append(em.StyleManager.render()); // Class Manager container
var smConfig = em.StyleManager.getConfig(); var clm = em.SelectorManager;
// Create header if(clm)
this.$header = $('<div>', { this.$cn2.append(clm.render([]));
class: smConfig.stylePrefix + 'header',
text: smConfig.textNoElement,
});
//this.$cn = this.$cn.add(this.$header);
this.$cn.append(this.$header);
// Create panel if not exists this.$cn2.append(em.StyleManager.render());
if(!panels.getPanel('views-container')) var smConfig = em.StyleManager.getConfig();
this.panel = panels.addPanel({ id: 'views-container'}); // Create header
else this.$header = $('<div>', {
this.panel = panels.getPanel('views-container'); 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 // Create panel if not exists
this.panel.set('appendContent', this.$cn).trigger('change:appendContent'); if(!panels.getPanel('views-container'))
this.panel = panels.addPanel({ id: 'views-container'});
else
this.panel = panels.getPanel('views-container');
this.target = em.editor; // Add all containers to the panel
this.listenTo( this.target ,'change:selectedComponent', this.toggleSm); this.panel.set('appendContent', this.$cn).trigger('change:appendContent');
}
this.toggleSm();
},
/** this.target = em.editor;
* Toggle Style Manager visibility this.listenTo( this.target ,'change:selectedComponent', this.toggleSm);
* @private }
*/ this.toggleSm();
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();
}
},
stop: function() { /**
// Hide secondary container if exists * Toggle Style Manager visibility
if(this.$cn2) * @private
this.$cn2.hide(); */
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 stop() {
if(this.$header) // Hide secondary container if exists
this.$header.hide(); 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) { this.obj.style.display = 'block';
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'; stop() {
}, if(this.obj)
this.obj.style.display = 'none';
stop: function() { }
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){ tglPointers(editor, v) {
if(!this.panels) var elP = editor.Canvas.getBody().querySelectorAll('.' + this.ppfx + 'no-pointer');
this.panels = editor.Panels.getPanelsEl(); _.each(elP, item => {
return this.panels; item.style.pointerEvents = v ? '' : 'all';
}, });
},
tglPointers: function(editor, v) { run(editor, sender) {
var elP = editor.Canvas.getBody().querySelectorAll('.' + this.ppfx + 'no-pointer'); if(sender && sender.set)
_.each(elP, function(item){ sender.set('active', false);
item.style.pointerEvents = v ? '' : 'all'; 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) editor.Canvas.getBody().querySelectorAll('.' + pfx + 'no-pointer').forEach(function(){
sender.set('active', false); this.style.pointerEvents = 'all';
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);
/* panels.style.display = 'none';
editor.Canvas.getBody().querySelectorAll('.' + pfx + 'no-pointer').forEach(function(){ var canvasS = canvas.style;
this.style.pointerEvents = 'all'; 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'; stop(editor, sender) {
var canvasS = canvas.style; var panels = this.getPanels(editor);
canvasS.width = '100%'; editor.runCommand('sw-visibility');
canvasS.height = '100%'; editor.getModel().runDefault();
canvasS.top = '0'; panels.style.display = 'block';
canvasS.left = '0'; var canvas = editor.Canvas.getElement();
canvasS.padding = '0'; canvas.setAttribute('style', '');
canvasS.margin = '0'; if(this.helper) {
editor.trigger('change:canvasOffset'); this.helper.style.display = 'none';
}, }
editor.trigger('change:canvasOffset');
stop: function(editor, sender) { this.tglPointers(editor, 1);
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() { module.exports = {
return {
run: function(editor, sender, opts) { run(editor, sender, opts) {
var el = (opts && opts.el) || ''; var el = (opts && opts.el) || '';
var canvas = editor.Canvas; var canvas = editor.Canvas;
var canvasResizer = this.canvasResizer; var canvasResizer = this.canvasResizer;
var options = opts.options || {}; var options = opts.options || {};
// Create the resizer for the canvas if not yet created // Create the resizer for the canvas if not yet created
if(!canvasResizer) { if(!canvasResizer) {
var canvasView = canvas.getCanvasView(); var canvasView = canvas.getCanvasView();
options.ratioDefault = 1; options.ratioDefault = 1;
options.appendTo = canvas.getResizerEl(); options.appendTo = canvas.getResizerEl();
options.prefix = editor.getConfig().stylePrefix; options.prefix = editor.getConfig().stylePrefix;
options.posFetcher = canvasView.getElementPos.bind(canvasView); options.posFetcher = canvasView.getElementPos.bind(canvasView);
options.mousePosFetcher = canvas.getMouseRelativePos; options.mousePosFetcher = canvas.getMouseRelativePos;
this.canvasResizer = editor.Utils.Resizer.init(options); this.canvasResizer = editor.Utils.Resizer.init(options);
canvasResizer = this.canvasResizer; canvasResizer = this.canvasResizer;
} }
canvasResizer.setOptions(options); canvasResizer.setOptions(options);
canvasResizer.focus(el); canvasResizer.focus(el);
}, },
stop: function() { stop() {
if(this.canvasResizer) if(this.canvasResizer)
this.canvasResizer.blur(); 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 * Get frame position
* @param {HTMLElement} trg * @return {Object}
* @private * @private
* */ */
startSelectPosition: function(trg, doc) { getOffsetDim() {
this.isPointed = false; var frameOff = this.offset(this.canvas.getFrameEl());
var utils = this.editorModel.get('Utils'); var canvasOff = this.offset(this.canvas.getElement());
if(utils && !this.sorter) var top = frameOff.top - canvasOff.top;
this.sorter = new utils.Sorter({ var left = frameOff.left - canvasOff.left;
container: this.getCanvasBody(), return { top, left };
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 * Stop select position event
* @return {Object} * @private
* @private * */
*/ stopSelectPosition() {
getOffsetDim: function() { this.posTargetCollection = null;
var frameOff = this.offset(this.canvas.getFrameEl()); this.posIndex = this.posMethod=='after' && this.cDim.length!==0 ? this.posIndex + 1 : this.posIndex; //Normalize
var canvasOff = this.offset(this.canvas.getElement()); if(this.sorter){
var top = frameOff.top - canvasOff.top; this.sorter.moved = 0;
var left = frameOff.left - canvasOff.left; this.sorter.endMove();
return { top: top, left: left }; }
}, 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 * Enabel select position
* @private * @private
* */ */
stopSelectPosition: function() { enable() {
this.posTargetCollection = null; this.startSelectPosition();
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 * Check if the pointer is near to the float component
* @private * @param {number} index
*/ * @param {string} method
enable: function() { * @param {Array<Array>} dims
this.startSelectPosition(); * @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() { stop() {
this.enable(); this.stopSelectPosition();
}, this.$wrapper.css('cursor','');
this.$wrapper.unbind();
stop: function() { }
this.stopSelectPosition(); };
this.$wrapper.css('cursor','');
this.$wrapper.unbind();
}
};
});

296
src/commands/view/ShowOffset.js

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

18
src/commands/view/SwitchVisibility.js

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

60
src/commands/view/TextComponent.js

@ -1,35 +1,31 @@
define(['backbone', './CreateComponent'], var Backbone = require('backbone');
function(Backbone, CreateComponent) { var CreateComponent = require('./CreateComponent');
/**
* @class TextComponent
* @private
* */
return _.extend({}, CreateComponent, {
/** module.exports = _.extend({}, 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';
},
/** /**
* This event is triggered at the end of a draw operation * This event is triggered at the beginning of a draw operation
* @param {Object} model Component model created * @param {Object} component Object component before creation
* @private * @private
* */ * */
afterDraw: function(model){ beforeDraw(component) {
if(!model || !model.set) component.type = 'text';
return; if(!component.style)
model.trigger('focus'); component.style = {};
if(this.sender) component.style.padding = '10px';
this.sender.set('active', false); },
},
}); /**
}); * 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 () { module.exports = {
return {
// Style prefix // Style prefix
stylePrefix: 'css-', stylePrefix: 'css-',
// Custom CSS string to render on top // Custom CSS string to render on top
'staticRules': '', 'staticRules': '',
// Default CSS style // Default CSS style
rules: [], 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'], var Backbone = require('backbone');
function (Backbone, Selectors) { var Selectors = require('./Selectors');
return Backbone.Model.extend({
module.exports = Backbone.Model.extend({
defaults: {
// Css selectors defaults: {
selectors: {}, // Css selectors
selectors: {},
// Additional string css selectors
selectorsAdd: '', // Additional string css selectors
selectorsAdd: '',
// Css properties style
style: {}, // Css properties style
style: {},
// On which device width this rule should be rendered, eg. @media (max-width: 1000px)
mediaText: '', // On which device width this rule should be rendered, eg. @media (max-width: 1000px)
mediaText: '',
// State of the rule, eg: hover | pressed | focused
state: '', // State of the rule, eg: hover | pressed | focused
state: '',
// Indicates if the rule is stylable
stylable: true, // Indicates if the rule is stylable
}, stylable: true,
},
initialize: function(c, opt) {
this.config = c || {}; initialize(c, opt) {
this.sm = opt ? opt.sm || {} : {}; this.config = c || {};
this.slct = this.config.selectors || []; this.sm = opt ? opt.sm || {} : {};
this.slct = this.config.selectors || [];
if(this.sm.get){
var slct = []; if(this.sm.get){
for(var i = 0; i < this.slct.length; i++) var slct = [];
slct.push(this.sm.get('SelectorManager').add(this.slct[i].name || this.slct[i])); for(var i = 0; i < this.slct.length; i++)
this.slct = slct; slct.push(this.sm.get('SelectorManager').add(this.slct[i].name || this.slct[i]));
} this.slct = slct;
}
this.set('selectors', new Selectors(this.slct));
}, this.set('selectors', new Selectors(this.slct));
},
/**
* Compare the actual model with parameters /**
* @param {Object} selectors Collection of selectors * Compare the actual model with parameters
* @param {String} state Css rule state * @param {Object} selectors Collection of selectors
* @param {String} width For which device this style is oriented * @param {String} state Css rule state
* @param {Object} ruleProps Other rule props * @param {String} width For which device this style is oriented
* @return {Boolean} * @param {Object} ruleProps Other rule props
* @private * @return {Boolean}
*/ * @private
compare: function(selectors, state, width, ruleProps){ */
var otherRule = ruleProps || {}; compare(selectors, state, width, ruleProps) {
var st = state || ''; var otherRule = ruleProps || {};
var wd = width || ''; var st = state || '';
var selectorsAdd = otherRule.selectorsAdd || ''; var wd = width || '';
var cId = 'cid'; var selectorsAdd = otherRule.selectorsAdd || '';
//var a1 = _.pluck(selectors.models || selectors, cId); var cId = 'cid';
//var a2 = _.pluck(this.get('selectors').models, cId); //var a1 = _.pluck(selectors.models || selectors, cId);
if(!(selectors instanceof Array) && !selectors.models) //var a2 = _.pluck(this.get('selectors').models, cId);
selectors = [selectors]; if(!(selectors instanceof Array) && !selectors.models)
var a1 = _.map((selectors.models || selectors), function(model) { selectors = [selectors];
return model.get('name'); var a1 = _.map((selectors.models || selectors), model => model.get('name'));
}); var a2 = _.map(this.get('selectors').models, model => model.get('name'));
var a2 = _.map(this.get('selectors').models, function(model) { var f = false;
return model.get('name');
}); if(a1.length !== a2.length)
var f = false; return f;
if(a1.length !== a2.length) for (var i = 0; i < a1.length; i++) {
return f; var re = 0;
for (var j = 0; j < a2.length; j++) {
for (var i = 0; i < a1.length; i++) { if (a1[i] === a2[j])
var re = 0; re = 1;
for (var j = 0; j < a2.length; j++) { }
if (a1[i] === a2[j]) if(re === 0)
re = 1; return f;
} }
if(re === 0)
return f; if(this.get('state') !== st)
} return f;
if(this.get('state') !== st) if(this.get('mediaText') !== wd)
return f; return f;
if(this.get('mediaText') !== wd) if(this.get('selectorsAdd') !== selectorsAdd)
return f; return f;
if(this.get('selectorsAdd') !== selectorsAdd) return true;
return f; },
return true;
},
});
}); });

48
src/css_composer/model/CssRules.js

@ -1,34 +1,34 @@
define(['backbone','./CssRule'], var Backbone = require('backbone');
function (Backbone, CssRule) { var CssRule = require('./CssRule');
return Backbone.Collection.extend({
initialize: function(models, opt){ module.exports = Backbone.Collection.extend({
// Inject editor initialize(models, opt) {
if(opt && opt.sm)
this.editor = opt.sm;
this.model = function(attrs, options) { // Inject editor
var model; if(opt && opt.sm)
this.editor = opt.sm;
if(!options.sm && opt && opt.sm) this.model = (attrs, options) => {
options.sm = opt.sm; var model;
switch(1){ if(!options.sm && opt && opt.sm)
default: options.sm = opt.sm;
model = new CssRule(attrs, options);
}
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); add(models, opt) {
return Backbone.Collection.prototype.add.apply(this, [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'], var Backbone = require('backbone');
function (Backbone, require) {
return Backbone.Collection.extend({
initialize: function(models, opt){ module.exports = Backbone.Collection.extend({
this.model = function(attrs, opts) { initialize(models, opt) {
var model;
switch(1){ this.model = function(attrs, opts) {
var model;
default: switch(1){
if(!this.ClassTag)
this.ClassTag = require("SelectorManager/model/Selector");
model = new this.ClassTag(attrs, opts);
} 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'], var Backbone = require('backbone');
function (Backbone) {
return Backbone.View.extend({
tagName: 'style', module.exports = Backbone.View.extend({
initialize: function(o) { tagName: 'style',
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);
},
/** initialize(o) {
* Triggered when some selector is changed this.config = o.config || {};
* @private this.listenTo(this.model, 'change:style', this.render);
*/ this.listenTo(this.model, 'change:state', this.render);
selChanged: function(){ this.listenTo(this.model, 'destroy remove', this.remove);
this.selStr = this.renderSelectors(); this.listenTo(this.model, 'change:mediaText', this.render);
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 * Returns string of selectors
* @return {String} * @return {String}
* @private * @private
*/ */
renderSelectors: function() { renderSelectors() {
var sel = []; var sel = [];
var model = this.model; var model = this.model;
var add = model.get('selectorsAdd'); var add = model.get('selectorsAdd');
model.get('selectors').each(function(m){ model.get('selectors').each(m => {
sel.push('.' + m.get('name')); sel.push('.' + m.get('name'));
}); });
var sels = sel.join(''); var sels = sel.join('');
return sels + (sels && add ? ', ' : '') + add; return sels + (sels && add ? ', ' : '') + add;
}, },
/** /**
* Returns string of properties * Returns string of properties
* @return {String} * @return {String}
* @private * @private
*/ */
renderProperties: function(){ renderProperties() {
var sel = [], var sel = [],
props = this.model.get('style'); props = this.model.get('style');
for (var prop in props){ for (var prop in props){
sel.push(prop + ':' + props[prop] + ';'); sel.push(prop + ':' + props[prop] + ';');
} }
return sel.join(''); return sel.join('');
}, },
render : function(){ render() {
var block = '', var block = '',
selStr = ''; selStr = '',
o = ''; o = '';
if(!this.selStr) if(!this.selStr)
this.selStr = this.renderSelectors(); this.selStr = this.renderSelectors();
var prpStr = this.renderProperties(); var prpStr = this.renderProperties();
var stateStr = this.model.get('state'); var stateStr = this.model.get('state');
var mediaText = this.model.get('mediaText'); var mediaText = this.model.get('mediaText');
if(this.selStr){ if(this.selStr){
stateStr = stateStr ? ':' + stateStr : ''; stateStr = stateStr ? ':' + stateStr : '';
block = prpStr !== '' ? '{' + prpStr + '}' : ''; block = prpStr !== '' ? '{' + prpStr + '}' : '';
} }
o = this.selStr && block ? this.selStr + stateStr + block : ''; o = this.selStr && block ? this.selStr + stateStr + block : '';
if(mediaText && o) if(mediaText && o)
o = '@media ' + mediaText + '{' + o + '}'; o = '@media ' + mediaText + '{' + o + '}';
this.$el.html(o); this.$el.html(o);
return this; return this;
}, },
});
}); });

120
src/css_composer/view/CssRulesView.js

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

11
src/device_manager/model/Devices.js

@ -1,9 +1,6 @@
define(['backbone','./Device'], var Backbone = require('backbone');
function (Backbone, Device) { var Device = require('./Device');
return Backbone.Collection.extend({ module.exports = Backbone.Collection.extend({
model: Device,
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'], var Backbone = require('backbone');
function(Backbone, devicesTemplate) {
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: { events: {
'change': 'updateDevice' 'change': 'updateDevice'
}, },
initialize: function(o) { initialize(o) {
this.config = o.config || {}; this.config = o.config || {};
this.em = this.config.em; this.em = this.config.em;
this.ppfx = this.config.pStylePrefix || ''; this.ppfx = this.config.pStylePrefix || '';
this.events['click .' + this.ppfx + 'add-trasp'] = this.startAdd; this.events['click .' + this.ppfx + 'add-trasp'] = this.startAdd;
this.listenTo(this.em, 'change:device', this.updateSelect); this.listenTo(this.em, 'change:device', this.updateSelect);
this.delegateEvents(); this.delegateEvents();
}, },
/** /**
* Start adding new device * Start adding new device
* @return {[type]} [description] * @return {[type]} [description]
* @private * @private
*/ */
startAdd: function(){}, startAdd() {},
/** /**
* Update device of the editor * Update device of the editor
* @private * @private
*/ */
updateDevice: function(){ updateDevice() {
var em = this.em; var em = this.em;
if(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;
var devEl = this.devicesEl; var devEl = this.devicesEl;
if(em && em.getDeviceModel && devEl){ var val = devEl ? devEl.val() : '';
var device = em.getDeviceModel(); em.set('device', val);
var name = device ? device.get('name') : ''; }
devEl.val(name); },
}
}, /**
* 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 devices options
* @return {string} String of options * @return {string} String of options
* @private * @private
*/ */
getOptions: function(){ getOptions() {
var result = ''; var result = '';
this.collection.each(function(device){ this.collection.each(device => {
var name = device.get('name'); var name = device.get('name');
result += '<option value="' + name+ '">' + name + '</option>'; result += '<option value="' + name+ '">' + name + '</option>';
}); });
return result; return result;
}, },
render: function() { render() {
var pfx = this.ppfx; var pfx = this.ppfx;
this.$el.html(this.template({ this.$el.html(this.template({
ppfx: pfx, ppfx: pfx,
deviceLabel: this.config.deviceLabel deviceLabel: this.config.deviceLabel
})); }));
this.devicesEl = this.$el.find('.' + pfx + 'devices'); this.devicesEl = this.$el.find('.' + pfx + 'devices');
this.devicesEl.append(this.getOptions()); this.devicesEl.append(this.getOptions());
this.el.className = pfx + 'devices-c'; this.el.className = pfx + 'devices-c';
return this; return this;
}, },
}); });
});

48
src/dom_components/config/config.js

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

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'], var Backbone = require('backbone');
function (Backbone, Components, Selectors, Traits) { 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: { defaults: {
// HTML tag of the component // HTML tag of the component
tagName: 'div', tagName: 'div',
// Component type, eg. 'text', 'image', 'video', etc. // Component type, eg. 'text', 'image', 'video', etc.
type: '', type: '',
// True if the component is removable from the canvas // True if the component is removable from the canvas
removable: true, removable: true,
// Indicates if it's possible to drag the component inside other // Indicates if it's possible to drag the component inside other
// Tip: Indicate an array of selectors where it could be dropped inside // Tip: Indicate an array of selectors where it could be dropped inside
draggable: true, draggable: true,
// Indicates if it's possible to drop other components inside // Indicates if it's possible to drop other components inside
// Tip: Indicate an array of selectors which could be dropped inside // Tip: Indicate an array of selectors which could be dropped inside
droppable: true, droppable: true,
// Set false if don't want to see the badge (with the name) over the component // Set false if don't want to see the badge (with the name) over the component
badgable: true, badgable: true,
// True if it's possible to style it // True if it's possible to style it
// Tip: Indicate an array of CSS properties which is possible to style // Tip: Indicate an array of CSS properties which is possible to style
stylable: true, stylable: true,
// Highlightable with 'dotted' style if true // Highlightable with 'dotted' style if true
highlightable: true, highlightable: true,
// True if it's possible to clone the component // True if it's possible to clone the component
copyable: true, copyable: true,
// Indicates if it's possible to resize the component (at the moment implemented only on Image Components) // Indicates if it's possible to resize the component (at the moment implemented only on Image Components)
resizable: false, resizable: false,
// Allow to edit the content of the component (used on Text components) // Allow to edit the content of the component (used on Text components)
editable: false, editable: false,
// Hide the component inside Layers // Hide the component inside Layers
hiddenLayer: false, hiddenLayer: false,
// This property is used by the HTML exporter as void elements do not // This property is used by the HTML exporter as void elements do not
// have closing tag, eg. <br/>, <hr/>, etc. // have closing tag, eg. <br/>, <hr/>, etc.
void: false, void: false,
// Indicates if the component is in some CSS state like ':hover', ':active', etc. // Indicates if the component is in some CSS state like ':hover', ':active', etc.
state: '', state: '',
// State, eg. 'selected' // State, eg. 'selected'
status: '', status: '',
// Content of the component (not escaped) which will be appended before children rendering // Content of the component (not escaped) which will be appended before children rendering
content: '', content: '',
// Component related style // Component related style
style: {}, style: {},
// Key-value object of the component's attributes // Key-value object of the component's attributes
attributes: '', attributes: '',
// Array of classes // Array of classes
classes: '', classes: '',
// Component's javascript // Component's javascript
script: '', script: '',
// Traits // Traits
traits: ['id', 'title'], traits: ['id', 'title'],
/** /**
* Set an array of items to show up inside the toolbar (eg. move, clone, delete) * Set an array of items to show up inside the toolbar (eg. move, clone, delete)
* when the component is selected * when the component is selected
* toolbar: [{ * toolbar: [{
* attributes: {class: 'fa fa-arrows'}, * attributes: {class: 'fa fa-arrows'},
* command: 'tlb-move', * command: 'tlb-move',
* },{ * },{
* attributes: {class: 'fa fa-clone'}, * attributes: {class: 'fa fa-clone'},
* command: 'tlb-clone', * command: 'tlb-clone',
* }] * }]
*/ */
toolbar: null, toolbar: null,
// TODO // TODO
previousModel: '', previousModel: '',
mirror: '', mirror: '',
}, },
initialize: function(o, opt) { initialize(o, opt) {
// Check void elements // Check void elements
if(opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0) if(opt && opt.config && opt.config.voidElements.indexOf(this.get('tagName')) >= 0)
this.set('void', true); this.set('void', true);
this.opt = opt; this.opt = opt;
this.sm = opt ? opt.sm || {} : {}; this.sm = opt ? opt.sm || {} : {};
this.config = o || {}; this.config = o || {};
this.defaultC = this.config.components || []; this.defaultC = this.config.components || [];
this.defaultCl = this.normalizeClasses(this.get('classes') || this.config.classes || []); this.defaultCl = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.components = new Components(this.defaultC, opt); this.components = new Components(this.defaultC, opt);
this.components.parent = this; this.components.parent = this;
this.listenTo(this, 'change:script', this.scriptUpdated); this.listenTo(this, 'change:script', this.scriptUpdated);
this.set('attributes', this.get('attributes') || {}); this.set('attributes', this.get('attributes') || {});
this.set('components', this.components); this.set('components', this.components);
this.set('classes', new Selectors(this.defaultCl)); this.set('classes', new Selectors(this.defaultCl));
var traits = new Traits(); var traits = new Traits();
traits.setTarget(this); traits.setTarget(this);
traits.add(this.get('traits')); traits.add(this.get('traits'));
this.set('traits', traits); this.set('traits', traits);
this.initToolbar(); this.initToolbar();
// Normalize few properties from strings to arrays // Normalize few properties from strings to arrays
var toNormalize = ['stylable']; var toNormalize = ['stylable'];
toNormalize.forEach(function(name) { toNormalize.forEach(function(name) {
var value = this.get(name); var value = this.get(name);
if (typeof value == 'string') { if (typeof value == 'string') {
var newValue = value.split(',').map(function(prop) { var newValue = value.split(',').map(prop => prop.trim());
return prop.trim(); this.set(name, newValue);
}); }
this.set(name, newValue); }, this);
}
}, this); this.init();
},
this.init();
}, /**
* Initialize callback
/** */
* Initialize callback init() {},
*/
init: function () {}, /**
* Script updated
/** */
* Script updated scriptUpdated() {
*/ this.set('scriptUpdated', 1);
scriptUpdated: function() { },
this.set('scriptUpdated', 1);
}, /**
* Init toolbar
/** */
* Init toolbar initToolbar() {
*/ var model = this;
initToolbar: function () { if(!model.get('toolbar')) {
var model = this; var tb = [];
if(!model.get('toolbar')) { if(model.get('draggable')) {
var tb = []; tb.push({
if(model.get('draggable')) { attributes: {class: 'fa fa-arrows'},
tb.push({ command: 'tlb-move',
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();
}); });
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 = ''; if(model.get('removable')) {
attr.view = ''; tb.push({
attributes: {class: 'fa fa-trash-o'},
if(reset){ command: 'tlb-delete',
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');
}); });
strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : ''; }
model.set('toolbar', tb);
// 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 + '" '; /**
* 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) { // If style is not empty I need an ID attached to the component
code += m.toHTML(); // TODO: need to refactor in case of 'ID Trait'
}); if(!_.isEmpty(m.get('style')))
attrId = ' id="' + m.cid + '" ';
if(!sTag) code += '<' + tag + strCls + attrId + strAttr + (sTag ? '/' : '') + '>' + m.get('content');
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;
}
},{ m.get('components').each(m => {
code += m.toHTML();
});
/** if(!sTag)
* Detect if the passed element is a valid component. code += '</'+tag+'>';
* In case the element is valid an object abstracted
* from the element will be returned return code;
* @param {HTMLElement} },
* @return {Object}
* @private /**
*/ * Returns object of attributes for HTML
isComponent: function(el) { * @return {Object}
return {tagName: el.tagName ? el.tagName.toLowerCase() : ''}; * @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'], var Component = require('./Component');
function (Component) {
return Component.extend({ module.exports = Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: _.extend({}, Component.prototype.defaults, {
type: 'image', type: 'image',
tagName: 'img', tagName: 'img',
src: '', src: '',
void: 1, void: 1,
droppable: false, droppable: false,
resizable: true, resizable: true,
traits: ['alt'] traits: ['alt']
}), }),
initialize: function(o, opt) { initialize(o, opt) {
Component.prototype.initialize.apply(this, arguments); Component.prototype.initialize.apply(this, arguments);
var attr = this.get('attributes'); var attr = this.get('attributes');
if(attr.src) if(attr.src)
this.set('src', attr.src); this.set('src', attr.src);
}, },
initToolbar: function() { initToolbar(...args) {
Component.prototype.initToolbar.apply(this, arguments); Component.prototype.initToolbar.apply(this, args);
if (this.sm && this.sm.get) { if (this.sm && this.sm.get) {
var cmd = this.sm.get('Commands'); var cmd = this.sm.get('Commands');
var cmdName = 'image-editor'; var cmdName = 'image-editor';
// Add Image Editor button only if the default command exists // Add Image Editor button only if the default command exists
if (cmd.has(cmdName)) { if (cmd.has(cmdName)) {
var tb = this.get('toolbar'); var tb = this.get('toolbar');
tb.push({ tb.push({
attributes: {class: 'fa fa-pencil'}, attributes: {class: 'fa fa-pencil'},
command: cmdName, command: cmdName,
}); });
this.set('toolbar', tb); this.set('toolbar', tb);
} }
} }
}, },
/** /**
* Returns object of attributes for HTML * Returns object of attributes for HTML
* @return {Object} * @return {Object}
* @private * @private
*/ */
getAttrToHTML: function() { getAttrToHTML(...args) {
var attr = Component.prototype.getAttrToHTML.apply(this, arguments); var attr = Component.prototype.getAttrToHTML.apply(this, args);
delete attr.onmousedown; delete attr.onmousedown;
var src = this.get('src'); var src = this.get('src');
if(src) if(src)
attr.src = src; attr.src = src;
return attr; return attr;
}, },
/** /**
* Parse uri * Parse uri
* @param {string} uri * @param {string} uri
* @return {object} * @return {object}
* @private * @private
*/ */
parseUri: function(uri) { parseUri(uri) {
var el = document.createElement('a'); var el = document.createElement('a');
el.href = uri; el.href = uri;
var query = {}; var query = {};
var qrs = el.search.substring(1).split('&'); var qrs = el.search.substring(1).split('&');
for (var i = 0; i < qrs.length; i++) { for (var i = 0; i < qrs.length; i++) {
var pair = qrs[i].split('='); var pair = qrs[i].split('=');
var name = decodeURIComponent(pair[0]); var name = decodeURIComponent(pair[0]);
if(name) if(name)
query[name] = decodeURIComponent(pair[1]); query[name] = decodeURIComponent(pair[1]);
} }
return { return {
hostname: el.hostname, hostname: el.hostname,
pathname: el.pathname, pathname: el.pathname,
protocol: el.protocol, protocol: el.protocol,
search: el.search, search: el.search,
hash: el.hash, hash: el.hash,
port: el.port, port: el.port,
query: query, query,
}; };
}, },
},{ },{
/** /**
* Detect if the passed element is a valid component. * Detect if the passed element is a valid component.
* In case the element is valid an object abstracted * In case the element is valid an object abstracted
* from the element will be returned * from the element will be returned
* @param {HTMLElement} * @param {HTMLElement}
* @return {Object} * @return {Object}
* @private * @private
*/ */
isComponent: function(el) { isComponent(el) {
var result = ''; var result = '';
if(el.tagName == 'IMG'){ if(el.tagName == 'IMG'){
result = {type: 'image'}; result = {type: 'image'};
} }
return result; return result;
}, },
});
}); });

68
src/dom_components/model/ComponentLink.js

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

193
src/dom_components/model/ComponentMap.js

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

40
src/dom_components/model/ComponentScript.js

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

156
src/dom_components/model/ComponentTable.js

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

56
src/dom_components/model/ComponentTableCell.js

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

74
src/dom_components/model/ComponentTableRow.js

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

16
src/dom_components/model/ComponentText.js

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

42
src/dom_components/model/ComponentTextNode.js

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

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

Loading…
Cancel
Save