Browse Source

Merge branch 'dev'

pull/311/head
Artur Arseniev 10 years ago
parent
commit
bc6796234f
  1. 20
      Gruntfile.js
  2. 34
      README.md
  3. 2
      bower.json
  4. 6
      dist/css/grapes.min.css
  5. BIN
      dist/fonts/main-fonts.eot
  6. 174
      dist/fonts/main-fonts.svg
  7. BIN
      dist/fonts/main-fonts.ttf
  8. BIN
      dist/fonts/main-fonts.woff
  9. 33
      dist/grapes.min.js
  10. 21
      index.html
  11. 22
      package.json
  12. 7
      src/asset_manager/config/config.js
  13. 21
      src/asset_manager/main.js
  14. 10
      src/asset_manager/template/assets.html
  15. 64
      src/asset_manager/view/AssetImageView.js
  16. 3
      src/asset_manager/view/AssetsView.js
  17. 8
      src/asset_manager/view/FileUploader.js
  18. 14
      src/block_manager/view/BlockView.js
  19. 45
      src/block_manager/view/BlocksView.js
  20. 88
      src/canvas/main.js
  21. 109
      src/canvas/view/CanvasView.js
  22. 5
      src/canvas/view/FrameView.js
  23. 6
      src/code_manager/config/config.js
  24. 13
      src/code_manager/model/CodeMirrorEditor.js
  25. 62
      src/code_manager/model/CssGenerator.js
  26. 43
      src/code_manager/model/HtmlGenerator.js
  27. 5
      src/code_manager/model/JsonGenerator.js
  28. 61
      src/commands/main.js
  29. 7
      src/commands/view/ExportTemplate.js
  30. 32
      src/commands/view/MoveComponent.js
  31. 25
      src/commands/view/OpenAssets.js
  32. 3
      src/commands/view/OpenLayers.js
  33. 20
      src/commands/view/Preview.js
  34. 151
      src/commands/view/SelectComponent.js
  35. 18
      src/config/require-config.js
  36. 2
      src/css_composer/main.js
  37. 45
      src/demo.js
  38. 19
      src/dom_components/config/config.js
  39. 77
      src/dom_components/main.js
  40. 145
      src/dom_components/model/Component.js
  41. 89
      src/dom_components/model/ComponentImage.js
  42. 33
      src/dom_components/model/ComponentLink.js
  43. 108
      src/dom_components/model/ComponentMap.js
  44. 91
      src/dom_components/model/ComponentTable.js
  45. 35
      src/dom_components/model/ComponentTableCell.js
  46. 46
      src/dom_components/model/ComponentTableRow.js
  47. 3
      src/dom_components/model/ComponentText.js
  48. 28
      src/dom_components/model/ComponentTextNode.js
  49. 317
      src/dom_components/model/ComponentVideo.js
  50. 38
      src/dom_components/model/Components.js
  51. 6
      src/dom_components/model/Toolbar.js
  52. 13
      src/dom_components/model/ToolbarButton.js
  53. 34
      src/dom_components/view/ComponentImageView.js
  54. 17
      src/dom_components/view/ComponentLinkView.js
  55. 44
      src/dom_components/view/ComponentMapView.js
  56. 6
      src/dom_components/view/ComponentTableCellView.js
  57. 6
      src/dom_components/view/ComponentTableRowView.js
  58. 9
      src/dom_components/view/ComponentTableView.js
  59. 5
      src/dom_components/view/ComponentTextNodeView.js
  60. 55
      src/dom_components/view/ComponentTextView.js
  61. 119
      src/dom_components/view/ComponentVideoView.js
  62. 25
      src/dom_components/view/ComponentView.js
  63. 44
      src/dom_components/view/ComponentsView.js
  64. 37
      src/dom_components/view/ToolbarButtonView.js
  65. 15
      src/dom_components/view/ToolbarView.js
  66. 83
      src/domain_abstract/ui/Input.js
  67. 74
      src/domain_abstract/ui/InputColor.js
  68. 242
      src/domain_abstract/ui/InputNumber.js
  69. 1
      src/domain_abstract/ui/templates/input.html
  70. 6
      src/domain_abstract/ui/templates/inputColor.html
  71. 6
      src/domain_abstract/ui/templates/inputNumber.html
  72. 5
      src/domain_abstract/view/DomainViews.js
  73. 105
      src/editor/config/config.js
  74. 67
      src/editor/main.js
  75. 28
      src/editor/model/Editor.js
  76. 12
      src/editor/view/EditorView.js
  77. 4
      src/grapesjs/main.js
  78. 15
      src/navigator/config/config.js
  79. 34
      src/navigator/main.js
  80. 5
      src/navigator/template/item.html
  81. 330
      src/navigator/view/ItemSort.js
  82. 147
      src/navigator/view/ItemView.js
  83. 62
      src/navigator/view/ItemsView.js
  84. 18
      src/panels/main.js
  85. 14
      src/panels/view/ButtonView.js
  86. 2
      src/parser/config/config.js
  87. 5
      src/parser/main.js
  88. 2
      src/parser/model/ParserCss.js
  89. 108
      src/parser/model/ParserHtml.js
  90. 35
      src/rich_text_editor/config/config.js
  91. 85
      src/rich_text_editor/main.js
  92. 13
      src/rich_text_editor/view/CommandButtonsView.js
  93. 17
      src/rich_text_editor/view/TextEditorView.js
  94. 10
      src/selector_manager/template/classTags.html
  95. 2
      src/selector_manager/view/ClassTagView.js
  96. 56
      src/style_manager/config/config.js
  97. 7
      src/style_manager/model/Property.js
  98. 13
      src/style_manager/model/PropertyFactory.js
  99. 12
      src/style_manager/templates/propertyColor.html
  100. 4
      src/style_manager/templates/propertyInput.html

20
Gruntfile.js

@ -33,7 +33,7 @@ module.exports = function(grunt) {
dir: '<%= builtDir %>', dir: '<%= builtDir %>',
baseUrl: './', baseUrl: './',
name: 'main', name: 'main',
include: ["./../vendor/almond/almond"], include: ["./../node_modules/almond/almond"],
removeCombined: true, removeCombined: true,
findNestedDependencies: true, findNestedDependencies: true,
keepBuildDir: true, keepBuildDir: true,
@ -44,7 +44,7 @@ module.exports = function(grunt) {
"if (typeof define === 'function' && define.amd)"+ "if (typeof define === 'function' && define.amd)"+
"define([], factory);"+ "define([], factory);"+
"else if(typeof exports === 'object' && typeof module === 'object')"+ "else if(typeof exports === 'object' && typeof module === 'object')"+
"module.exports = mod();"+ "module.exports = factory();"+
"else "+ "else "+
"root.<%= pkg.name %> = root.GrapesJS = factory();"+ "root.<%= pkg.name %> = root.GrapesJS = factory();"+
"}(this, function () {", "}(this, function () {",
@ -96,8 +96,8 @@ module.exports = function(grunt) {
flatten: true, flatten: true,
src: [ src: [
'<%= styleDir %>/css/main.css', '<%= styleDir %>/css/main.css',
'vendor/codemirror/lib/codemirror.css', 'node_modules/codemirror/lib/codemirror.css',
'vendor/codemirror/theme/hopscotch.css' 'node_modules/codemirror/theme/hopscotch.css'
], ],
dest: '<%= builtDir %>', dest: '<%= builtDir %>',
ext: '.min.css' ext: '.min.css'
@ -127,7 +127,7 @@ module.exports = function(grunt) {
} }
}, },
}, },
/*
bowercopy: { bowercopy: {
options: { options: {
srcPrefix: 'bower_components' srcPrefix: 'bower_components'
@ -154,7 +154,7 @@ module.exports = function(grunt) {
}, },
} }
}, },
*/
watch: { watch: {
script: { script: {
files: [ '<%= appDir %>/**/*.js' ], files: [ '<%= appDir %>/**/*.js' ],
@ -239,16 +239,14 @@ module.exports = function(grunt) {
}); });
grunt.registerTask('bower', ['bowercopy']); grunt.registerTask('dev', ['connect', 'watch']);
grunt.registerTask('dev', ['bowercopy', 'connect', 'watch']);
grunt.registerTask('test', ['jshint', 'mocha']); grunt.registerTask('test', ['jshint', 'mocha']);
grunt.registerTask('build:fonts', ['webfont-custom']); grunt.registerTask('build:fonts', ['webfont-custom']);
grunt.registerTask('build', ['bowercopy', 'jshint', 'sass', 'before-rjs', 'requirejs', 'uglify', 'cssmin', 'concat', 'clean', 'copy']); grunt.registerTask('build', ['jshint', 'sass', 'before-rjs', 'requirejs', 'uglify', 'cssmin', 'concat', 'clean', 'copy']);
grunt.registerTask('default', ['dev']); grunt.registerTask('default', ['dev']);
}; };

34
README.md

@ -2,10 +2,10 @@
[![Build Status](https://travis-ci.org/artf/grapesjs.svg?branch=master)](https://travis-ci.org/artf/grapesjs) [![Build Status](https://travis-ci.org/artf/grapesjs.svg?branch=master)](https://travis-ci.org/artf/grapesjs)
<p align="center"><img src="http://grapesjs.com/img/grapesjs-demo-template2.jpg" alt="GrapesJS" width="500" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/grapesjs-front-page-m.jpg" alt="GrapesJS" width="500" align="center"/></p>
<br/> <br/>
GrapesJS is a free and open source Web Template Builder which helps you building HTML templates to be used inside sites, newsletters and mobile apps. GrapesJS is a free and open source Web Builder Framework which helps you building HTML templates to be used inside sites, newsletters and mobile apps.
Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of dynamic templates. To better understand this concept check the image below Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of dynamic templates. To better understand this concept check the image below
<br/> <br/>
@ -14,22 +14,28 @@ Mainly GrapesJS was designed to be used inside a [CMS] to speed up a creation of
Generally any 'template system', that you'd find in various applications like CMS, is composed by the **structure** (HTML), **style** (CSS) and **variables**, which are then replaced with other templates and contents on server-side and rendered on client. Generally any 'template system', that you'd find in various applications like CMS, is composed by the **structure** (HTML), **style** (CSS) and **variables**, which are then replaced with other templates and contents on server-side and rendered on client.
This demo shows an example of what is possible to achieve: http://grapesjs.com/demo.html This demos show examples of what is possible to achieve:
Webpage Demo - http://grapesjs.com/demo.html
Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
## Features ## Features
* Blocks
<p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-blocks-prp.jpg" alt="GrapesJS - Block Manager" height="400" align="center"/></p>
* Style Manager, for component styling<br/> * Style Manager, for component styling<br/>
<p align="center"><img src="http://grapesjs.com/img/grapesjs-style-manager.png" alt="GrapesJS - Style Manager" height="400" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-style-2.jpg" alt="GrapesJS - Style Manager" height="400" align="center"/></p>
* Layer Manager, that comes handy with nested elements<br/> * Layer Manager, that comes handy with nested elements<br/>
<p align="center"><img src="http://grapesjs.com/img/grapesjs-layer-manager.png" alt="GrapesJS - Layer Manager" height="300" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-layers-2.jpg" alt="GrapesJS - Layer Manager" height="400" align="center"/></p>
* Code Viewer <br/> * Code Viewer <br/>
<p align="center"><img src="http://grapesjs.com/img/grapesjs-code-viewer.png" alt="GrapesJS - Code Viewer" height="250" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-code.jpg" alt="GrapesJS - Code Viewer" height="300" align="center"/></p>
* Asset Manager, for uploading and managing images<br/> * Asset Manager, for uploading and managing images<br/>
<p align="center"><img src="http://grapesjs.com/img/grapesjs-asset-manager.png" alt="GrapesJS - Asset Manager" height="250" align="center"/></p> <p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-assets-1.jpg" alt="GrapesJS - Asset Manager" height="250" align="center"/></p>
* Local and remote storage * Local and remote storage
@ -135,13 +141,6 @@ API References (draft) could be found here: [API-Reference]
$ npm test $ npm test
``` ```
## TODOs before beta release
* **Component traits** - traits are some some kind of `attributes` of the component. For example, the `Link`
will get `href` for the url/resource address and it's exactly as we see `<a>`, but in case of `Map` component
I'd attach `address`, `city` and `zoom` to manage marker position.
## Acknowledgements ## Acknowledgements
GrapesJS is built on top of this amazing open source projects: GrapesJS is built on top of this amazing open source projects:
@ -154,6 +153,13 @@ GrapesJS is built on top of this amazing open source projects:
* [FontAwesome] - the iconic font and CSS framework * [FontAwesome] - the iconic font and CSS framework
## Sponsors
The project is sponsored by
[![Sendloop](http://grapesjs.com/img/sendloop-logo-l.png)](https://sendloop.com)
## Support ## Support
If you like the project support it with a donation of your choice. If you like the project support it with a donation of your choice.

2
bower.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Open source Web Template Editor", "description": "Open source Web Template Editor",
"version": "0.3.20", "version": "0.4.5",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",
"main": [ "main": [

6
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

BIN
dist/fonts/main-fonts.eot

Binary file not shown.

174
dist/fonts/main-fonts.svg

@ -18,6 +18,20 @@
sodipodi:docname="main-fonts.svg"> sodipodi:docname="main-fonts.svg">
<defs <defs
id="defs4"> id="defs4">
<marker
inkscape:stockid="Arrow1Lstart"
orient="auto"
refY="0.0"
refX="0.0"
id="Arrow1Lstart"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path4431"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<inkscape:perspective <inkscape:perspective
sodipodi:type="inkscape:persp3d" sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 500 : 1" inkscape:vp_x="0 : 500 : 1"
@ -91,6 +105,51 @@
id="glyph5248" id="glyph5248"
unicode="p" unicode="p"
d="m 205.0332,800 0,-10 0,-252.15234 122.83594,0 c 0.19856,-1.5552 0.35938,-3.0785 0.35938,-4.44336 l 0,-0.99024 0.19336,-0.96875 c 0.56358,-2.8203 0.91406,-6.65611 0.91406,-11.36719 0,-49.69706 -10.37622,-93.07559 -30.95703,-130.7207 l -0.0352,-0.0644 -0.0352,-0.0645 C 278.74416,352.1644 251.28807,331.76304 213.41602,325.56055 l -8.38282,-1.37305 0,-124.1875 11.19532,1.34961 c 82.40701,9.92779 145.45236,49.82688 186.08398,118.11523 l 0.0117,0.0215 0.0137,0.0215 c 40.26558,68.43351 60.20313,151.05581 60.20313,247.22266 l 0,233.26953 -257.50782,0 z m 332.42578,0 0,-10 0,-252.15234 122.83399,0 c 0.19946,-1.55621 0.36133,-3.07993 0.36133,-4.44336 l 0,-0.99024 0.19336,-0.96875 c 0.56358,-2.82026 0.91406,-6.6561 0.91406,-11.36719 0,-49.69706 -10.37622,-93.07559 -30.95703,-130.7207 l -0.0352,-0.0644 -0.0352,-0.0645 C 611.16993,352.1644 583.71386,331.76304 545.8418,325.56055 l -8.38282,-1.37305 0,-124.1875 11.19532,1.34961 c 82.40702,9.92779 145.45236,49.82688 186.08398,118.11523 l 0.0117,0.0215 0.0137,0.0215 c 40.26567,68.43338 60.20313,151.05581 60.20313,247.22266 l 0,233.26953 -257.50782,0 z m -312.42578,-20 217.50782,0 0,-213.26953 c 0,-93.39687 -19.27335,-172.19948 -57.42774,-237.05664 C 349.1695,269.27492 296.65772,234.14551 225.0332,222.82031 l 0,84.86719 c 39.43166,8.90932 70.49983,33.48573 90.92578,72.14453 22.30915,40.83549 33.37696,87.77857 33.37696,140.24609 0,5.3977 -0.38074,10.20735 -1.20899,14.62305 -0.1288,5.15097 -0.94385,10.4327 -2.50195,15.89063 l -2.07031,7.25586 -118.52149,0 0,222.15234 z m 332.42578,0 217.50782,0 0,-213.26953 c 0,-93.39687 -19.27334,-172.19948 -57.42774,-237.05664 -35.94378,-60.39891 -88.45555,-95.52832 -160.08008,-106.85352 l 0,84.86719 c 39.43166,8.90932 70.49982,33.48573 90.92579,72.14453 22.30914,40.83549 33.37695,87.77857 33.37695,140.24609 0,5.40154 -0.3814,10.21455 -1.21094,14.63282 -0.13032,5.15253 -0.94794,10.43031 -2.5039,15.88086 l -2.07032,7.25586 -118.51758,0 0,222.15234 z" /> d="m 205.0332,800 0,-10 0,-252.15234 122.83594,0 c 0.19856,-1.5552 0.35938,-3.0785 0.35938,-4.44336 l 0,-0.99024 0.19336,-0.96875 c 0.56358,-2.8203 0.91406,-6.65611 0.91406,-11.36719 0,-49.69706 -10.37622,-93.07559 -30.95703,-130.7207 l -0.0352,-0.0644 -0.0352,-0.0645 C 278.74416,352.1644 251.28807,331.76304 213.41602,325.56055 l -8.38282,-1.37305 0,-124.1875 11.19532,1.34961 c 82.40701,9.92779 145.45236,49.82688 186.08398,118.11523 l 0.0117,0.0215 0.0137,0.0215 c 40.26558,68.43351 60.20313,151.05581 60.20313,247.22266 l 0,233.26953 -257.50782,0 z m 332.42578,0 0,-10 0,-252.15234 122.83399,0 c 0.19946,-1.55621 0.36133,-3.07993 0.36133,-4.44336 l 0,-0.99024 0.19336,-0.96875 c 0.56358,-2.82026 0.91406,-6.6561 0.91406,-11.36719 0,-49.69706 -10.37622,-93.07559 -30.95703,-130.7207 l -0.0352,-0.0644 -0.0352,-0.0645 C 611.16993,352.1644 583.71386,331.76304 545.8418,325.56055 l -8.38282,-1.37305 0,-124.1875 11.19532,1.34961 c 82.40702,9.92779 145.45236,49.82688 186.08398,118.11523 l 0.0117,0.0215 0.0137,0.0215 c 40.26567,68.43338 60.20313,151.05581 60.20313,247.22266 l 0,233.26953 -257.50782,0 z m -312.42578,-20 217.50782,0 0,-213.26953 c 0,-93.39687 -19.27335,-172.19948 -57.42774,-237.05664 C 349.1695,269.27492 296.65772,234.14551 225.0332,222.82031 l 0,84.86719 c 39.43166,8.90932 70.49983,33.48573 90.92578,72.14453 22.30915,40.83549 33.37696,87.77857 33.37696,140.24609 0,5.3977 -0.38074,10.20735 -1.20899,14.62305 -0.1288,5.15097 -0.94385,10.4327 -2.50195,15.89063 l -2.07031,7.25586 -118.52149,0 0,222.15234 z m 332.42578,0 217.50782,0 0,-213.26953 c 0,-93.39687 -19.27334,-172.19948 -57.42774,-237.05664 -35.94378,-60.39891 -88.45555,-95.52832 -160.08008,-106.85352 l 0,84.86719 c 39.43166,8.90932 70.49982,33.48573 90.92579,72.14453 22.30914,40.83549 33.37695,87.77857 33.37695,140.24609 0,5.40154 -0.3814,10.21455 -1.21094,14.63282 -0.13032,5.15253 -0.94794,10.43031 -2.5039,15.88086 l -2.07032,7.25586 -118.51758,0 0,222.15234 z" />
<glyph
glyph-name="button"
id="glyph4390"
unicode="m"
d="M 122.0625,762.63867 C 101.77302,762.63867 85,746.0808 85,725.75391 l 0,-556.23243 c 0,-20.32692 16.77277,-36.88281 37.0625,-36.88281 l 755.875,0 c 20.28976,0 37.0625,16.55589 37.0625,36.88281 l 0,556.23243 c 0,20.32692 -16.77285,36.88476 -37.0625,36.88476 l -755.875,0 z m 12.44922,-49.35547 730.97656,0 0,-531.29101 -730.97656,0 0,531.29101 z" />
<glyph
glyph-name="trp-sect"
id="glyph5546"
unicode="E"
d="M 114.31055,767.6387 C 100.08103,767.6387 88,755.5578 88,741.3281 l 0,-587.3808 c 0,-14.2297 12.08103,-26.3086 26.31055,-26.3086 l 187.3789,0 c 14.22952,0 26.31055,12.0789 26.31055,26.3086 l 0,587.3808 c 0,14.2297 -12.08103,26.3106 -26.31055,26.3106 l -187.3789,0 z m 292,0 C 392.08103,767.6387 380,755.5578 380,741.3281 l 0,-587.3808 c 0,-14.2297 12.08103,-26.3086 26.31055,-26.3086 l 187.3789,0 c 14.22952,0 26.31055,12.0789 26.31055,26.3086 l 0,587.3808 c 0,14.2297 -12.08103,26.3106 -26.31055,26.3106 l -187.3789,0 z m 292,0 C 684.08103,767.6387 672,755.5578 672,741.3281 l 0,-587.3808 c 0,-14.2297 12.08103,-26.3086 26.31055,-26.3086 l 187.3789,0 c 14.22952,0 26.31055,12.0789 26.31055,26.3086 l 0,587.3808 c 0,14.2297 -12.08103,26.3106 -26.31055,26.3106 l -187.3789,0 z m -570.31055,-40 160,0 0,-560 -160,0 0,560 z m 292,0 160,0 0,-560 -160,0 0,560 z m 292,0 160,0 0,-560 -160,0 0,560 z" />
<glyph
glyph-name="o-sect"
id="glyph5726"
unicode="Q"
d="M 122.0625,762.63773 C 101.77302,762.63773 85,746.07983 85,725.75293 l 0,-556.2324 c 0,-20.3269 16.77277,-36.8828 37.0625,-36.8828 l 755.875,0 c 20.28976,0 37.0625,16.5559 37.0625,36.8828 l 0,556.2324 c 0,20.327 -16.77285,36.8848 -37.0625,36.8848 l -755.875,0 z m 12.44922,-49.3555 730.97656,0 0,-531.291 -730.97656,0 0,531.291 z" />
<glyph
glyph-name="dbl-sect"
id="glyph6326"
unicode="W"
d="m 111.69531,767.63867 c -14.229645,0 -26.310544,-12.08092 -26.310544,-26.31055 l 0,-587.38085 c 0,-14.22961 12.080937,-26.3086 26.310544,-26.3086 l 325.50586,0 c 14.22963,0 26.31055,12.07895 26.31055,26.3086 l 0,587.38085 c 0,14.22967 -12.08088,26.31055 -26.31055,26.31055 l -325.50586,0 z m 451.10352,0 c -14.22965,0 -26.31055,-12.08092 -26.31055,-26.31055 l 0,-587.38085 c 0,-14.22961 12.08094,-26.3086 26.31055,-26.3086 l 325.50586,0 c 14.22961,0 26.31054,12.07899 26.31054,26.3086 l 0,587.38085 c 0,14.22963 -12.0809,26.31055 -26.31054,26.31055 l -325.50586,0 z m -437.41406,-40 298.12695,0 0,-560 -298.12695,0 0,560 z m 451.10351,0 298.12695,0 0,-560 -298.12695,0 0,560 z" />
<glyph
glyph-name="37-rect"
id="glyph6441"
unicode="R"
d="m 117.06641,762.66507 c -14.22964,0 -26.310551,-12.0809 -26.310551,-26.31054 l 0,-577.43555 c 0,-14.22964 12.080911,-26.30859 26.310551,-26.30859 l 246.16992,0 c 14.22964,0 26.31053,12.07895 26.31055,26.30859 l 0,577.43555 c 0,14.22964 -12.08091,26.31054 -26.31055,26.31054 l -246.16992,0 z m 378.48828,0 c -14.22964,0 -26.31055,-12.0809 -26.31055,-26.31054 l 0,-577.43555 c 0,-14.22964 12.08091,-26.30859 26.31055,-26.30859 l 387.3789,0 c 14.22964,0 26.31055,12.07895 26.31055,26.30859 l 0,577.43555 c 0,14.22964 -12.08091,26.31054 -26.31055,26.31054 l -387.3789,0 z m -364.79883,-40 218.79102,0 0,-550.05468 -218.79102,0 0,550.05468 z m 378.48828,0 360,0 0,-550.05468 -360,0 0,550.05468 z" />
<glyph
glyph-name="hero-sect"
id="glyph6480"
unicode="T"
d="m 409.31437,299.23917 c -3.4816,0 -6.2851,-2.8032 -6.2851,-6.2852 l 0,-37.4297 c 0,-3.482 2.8035,-6.2851 6.2851,-6.2851 l 187.4297,0 c 3.4816,0 6.2852,2.8031 6.2852,6.2851 l 0,37.4297 c 0,3.482 -2.8036,6.2852 -6.2852,6.2852 z m -159.07796,200 c -3.4816,0 -6.2851,-2.8032 -6.2851,-6.2852 l 0,-135.4297 c 0,-3.482 2.8035,-6.2851 6.2851,-6.2851 l 491.4297,0 c 3.4816,0 6.2852,2.8031 6.2852,6.2851 l 0,135.4297 c 0,3.482 -2.8036,6.2852 -6.2852,6.2852 z m 200,150 c -3.4816,0 -6.2851,-2.8032 -6.2851,-6.2852 l 0,-37.4297 c 0,-3.482 2.8035,-6.2851 6.2851,-6.2851 l 337.4297,0 c 3.4816,0 6.2852,2.8031 6.2852,6.2851 l 0,37.4297 c 0,3.482 -2.8036,6.2852 -6.2852,6.2852 z m -247.9999,0 c -3.4816,0 -6.2852,-2.8032 -6.2852,-6.2852 l 0,-37.4297 c 0,-3.482 2.8036,-6.2851 6.2852,-6.2851 l 87.4296,0 c 3.4816,0 6.2852,2.8031 6.2852,6.2851 l 0,37.4297 c 0,3.482 -2.8036,6.2852 -6.2852,6.2852 z M 122.0625,762.63773 c -20.28948,0 -37.062501,-16.5579 -37.062501,-36.8848 l 0,-556.2324 c 0,-20.3269 16.772771,-36.8828 37.062501,-36.8828 l 755.875,0 c 20.28976,0 37.0625,16.5559 37.0625,36.8828 l 0,556.2324 c 0,20.327 -16.77285,36.8848 -37.0625,36.8848 l -755.875,0 z m 12.44922,-49.3555 730.97656,0 0,-531.291 -730.97656,0 0,531.291 z" />
<glyph
glyph-name="image-s"
id="glyph6495"
unicode="I"
d="m 411.48046,643.83499 -235.3789,-426.32224 259.2559,0 -0.5489,-0.9922 366.1914,0 -183.0957,331.11718 -76.7148,-138.73438 -129.709,234.93164 z m 318.56846,27.37625 a 67.5,67.5 0 0 1 -67.5,-67.5 67.5,67.5 0 0 1 67.5,-67.5 67.5,67.5 0 0 1 67.5,67.5 67.5,67.5 0 0 1 -67.5,67.5 z M 122.0625,762.63773 C 101.77302,762.63773 85,746.07983 85,725.75293 l 0,-556.2324 c 0,-20.3269 16.77277,-36.8828 37.0625,-36.8828 l 755.875,0 c 20.28976,0 37.0625,16.5559 37.0625,36.8828 l 0,556.2324 c 0,20.327 -16.77285,36.8848 -37.0625,36.8848 l -755.875,0 z m 12.44922,-49.3555 730.97656,0 0,-531.291 -730.97656,0 0,531.291 z" />
<glyph
glyph-name="button"
id="glyph6529"
d="m 246.7734,467.09478 c -3.4816,0 -6.2832,-1.0322 -6.2832,-2.3125 l 0,-34.2891 c 0,-1.2803 2.8016,-2.3125 6.2832,-2.3125 l 506.4512,0 c 3.4816,0 6.2852,1.0322 6.2852,2.3125 l 0,34.2891 c 0,1.2803 -2.8036,2.3125 -6.2852,2.3125 l -506.4512,0 z M 111.49023,619.35843 c -14.229587,0 -26.310541,-12.07899 -26.310541,-26.30859 l 0,-290.82227 c 0,-14.2296 12.080954,-26.31054 26.310541,-26.31054 l 777.01954,0 c 14.2296,0 26.31054,12.08094 26.31054,26.31054 l 0,290.82227 c 0,14.2296 -12.08094,26.30859 -26.31054,26.30859 z m 13.68946,-40 749.64062,0 0,-263.4414 -749.64062,0 z"
unicode="B" />
<glyph
glyph-name="divider"
id="glyph6571"
unicode="D"
d="m 233.7889,467.0941 c -3.4816,0 -6.2832,-1.0309 -6.2832,-2.3112 l 0,-34.2904 c 0,-1.2803 2.8016,-2.3112 6.2832,-2.3112 l 506.4512,0 c 3.4816,0 6.2852,1.0309 6.2852,2.3112 l 0,34.2904 c 0,1.2803 -2.8036,2.3112 -6.2852,2.3112 z M 111.6094,376.1484 c -14.2295,0 -26.3086,-12.079 -26.3086,-26.3086 l 0,-187.5371 c 0,-14.2296 12.0787,-26.3105 26.3086,-26.3105 l 776.7812,0 c 14.2299,0 26.3086,12.0809 26.3086,26.3105 l 0,187.5371 c 0,14.2296 -12.0791,26.3086 -26.3086,26.3086 l -776.7812,0 z m 13.6914,-40 749.3984,0 0,-160.1562 -749.3984,0 0,160.1562 z m -13.6914,423.1348 c -14.2295,0 -26.3086,-12.0809 -26.3086,-26.3105 l 0,-187.5372 c 0,-14.2298 12.0787,-26.3085 26.3086,-26.3085 l 776.7812,0 c 14.2299,0 26.3086,12.0787 26.3086,26.3085 l 0,187.5372 c 0,14.2296 -12.0791,26.3105 -26.3086,26.3105 l -776.7812,0 z m 13.6914,-40 749.3984,0 0,-160.1562 -749.3984,0 0,160.1562 z" />
</font> </font>
</defs> </defs>
<sodipodi:namedview <sodipodi:namedview
@ -100,21 +159,21 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="0.1767767" inkscape:zoom="0.22415285"
inkscape:cx="334.86414" inkscape:cx="631.04328"
inkscape:cy="521.53582" inkscape:cy="-1694.6037"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="true" showgrid="true"
units="px" units="px"
inkscape:window-width="1440" inkscape:window-width="1440"
inkscape:window-height="800" inkscape:window-height="800"
inkscape:window-x="268" inkscape:window-x="0"
inkscape:window-y="79" inkscape:window-y="1"
inkscape:window-maximized="0" inkscape:window-maximized="1"
guidetolerance="10" guidetolerance="10"
objecttolerance="10" objecttolerance="10"
gridtolerance="10" gridtolerance="2"
inkscape:snap-bbox="false" inkscape:snap-bbox="false"
inkscape:bbox-nodes="false" inkscape:bbox-nodes="false"
inkscape:bbox-paths="false" inkscape:bbox-paths="false"
@ -143,7 +202,7 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:title></dc:title>
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
@ -154,18 +213,18 @@
transform="translate(0,-52.36227)"> transform="translate(0,-52.36227)">
<path <path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -737.93827,1092.3623 c -12.0536,0 -22.0617,9.9274 -22.0617,21.8839 l 0,556.2324 c 0,11.9564 10.0081,21.8837 22.0617,21.8837 l 755.876498,0 c 12.0536,0 22.0618,-9.9275 22.0618,-21.8837 l 0,-556.2324 c 0,-11.9565 -10.0081,-21.8839 -22.0618,-21.8839 l -755.876498,0 z m 0,19.3548 755.876498,0 c 1.5814,0 2.5496,0.9604 2.5496,2.5291 l 0,556.2324 c 0,1.5685 -0.9682,2.5289 -2.5496,2.5289 l -755.876498,0 c -1.5813,0 -2.5495,-0.9604 -2.5495,-2.5289 l 0,-556.2324 c 0,-1.5687 0.9682,-2.5291 2.5495,-2.5291 z" d="m -603.49035,1669.397 c -12.0536,0 -22.0617,-9.9275 -22.0617,-21.884 l 0,-556.2324 c 0,-11.9564 10.0081,-21.8836 22.0617,-21.8836 l 755.8765,0 c 12.0536,0 22.0618,9.9274 22.0618,21.8836 l 0,556.2324 c 0,11.9565 -10.0081,21.884 -22.0618,21.884 l -755.8765,0 z m 0,-19.3549 755.8765,0 c 1.5814,0 2.5496,-0.9604 2.5496,-2.5291 l 0,-556.2324 c 0,-1.5685 -0.9682,-2.5289 -2.5496,-2.5289 l -755.8765,0 c -1.5813,0 -2.5495,0.9604 -2.5495,2.5289 l 0,556.2324 c 0,1.5687 0.9682,2.5291 2.5495,2.5291 z"
id="rect4221" id="rect4221"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
<path <path
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
d="m 1545.9833,300.36231 c -11.9982,0 -21.9589,9.85244 -21.9589,21.72025 l 0,555.59715 c 0,11.86783 9.9607,21.71632 21.9589,21.71632 l 336.0577,0 c 11.9983,0 21.959,-9.84849 21.959,-21.71632 l 0,-555.59714 c 0,-11.86781 -9.9608,-21.72025 -21.959,-21.72025 l -336.0577,0 z m 0,19.05571 336.0577,0 c 1.6598,0 2.6977,1.02288 2.6977,2.66454 l 0,555.59715 c 0,1.64155 -1.038,2.66453 -2.6977,2.66453 l -336.0577,0 c -1.6597,0 -2.6976,-1.02299 -2.6976,-2.66453 l 0,-555.59714 c 0,-1.64166 1.0379,-2.66454 2.6976,-2.66454 z m -420.0244,-18.08953 c -11.9982,0 -21.9589,9.85245 -21.9589,21.72025 l 0,555.59716 c 0,11.86782 9.9607,21.71633 21.9589,21.71633 l 336.0577,0 c 11.9982,0 21.959,-9.84851 21.959,-21.71633 l 0,-555.59715 c 0,-11.8678 -9.9608,-21.72025 -21.959,-21.72025 l -336.0577,0 z m 0,19.05571 336.0577,0 c 1.6597,0 2.6976,1.02288 2.6976,2.66454 l 0,555.59716 c 0,1.64154 -1.0379,2.66454 -2.6976,2.66454 l -336.0577,0 c -1.6597,0 -2.6976,-1.023 -2.6976,-2.66454 l 0,-555.59715 c 0,-1.64168 1.0379,-2.66454 2.6976,-2.66454 z" d="m 1520.0244,382.34038 c -11.9982,0 -21.9589,9.85244 -21.9589,21.72025 l 0,555.59715 c 0,11.86783 9.9607,21.71632 21.9589,21.71632 l 336.0577,0 c 11.9983,0 21.959,-9.84849 21.959,-21.71632 l 0,-555.59714 c 0,-11.86781 -9.9608,-21.72025 -21.959,-21.72025 l -336.0577,0 z m 0,19.05571 336.0577,0 c 1.6598,0 2.6977,1.02288 2.6977,2.66454 l 0,555.59715 c 0,1.64155 -1.038,2.66453 -2.6977,2.66453 l -336.0577,0 c -1.6597,0 -2.6976,-1.02299 -2.6976,-2.66453 l 0,-555.59714 c 0,-1.64166 1.0379,-2.66454 2.6976,-2.66454 z M 1100,383.30656 c -11.9982,0 -21.9589,9.85245 -21.9589,21.72025 l 0,555.59716 c 0,11.86782 9.9607,21.71633 21.9589,21.71633 l 336.0577,0 c 11.9982,0 21.959,-9.84851 21.959,-21.71633 l 0,-555.59715 c 0,-11.8678 -9.9608,-21.72025 -21.959,-21.72025 l -336.0577,0 z m 0,19.05571 336.0577,0 c 1.6597,0 2.6976,1.02288 2.6976,2.66454 l 0,555.59716 c 0,1.64154 -1.0379,2.66454 -2.6976,2.66454 l -336.0577,0 c -1.6597,0 -2.6976,-1.023 -2.6976,-2.66454 l 0,-555.59715 c 0,-1.64168 1.0379,-2.66454 2.6976,-2.66454 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.86022854;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:9.86022854;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect4238" /> id="rect4238" />
<path <path
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="rect4298" id="rect4298"
d="m 1114.1233,990.36225 c -12.0863,0 -22.1233,8.54906 -22.1233,18.84765 l 0,562.3015 c 0,10.2985 10.0371,18.8508 22.1233,18.8508 l 186.6098,0 c 12.0863,0 22.1234,-8.5523 22.1234,-18.8508 l 0,-562.3015 c 0,-10.29859 -10.0371,-18.84765 -22.1234,-18.84765 l -186.6098,0 z m 284.5698,0 c -12.0862,0 -22.1195,8.54906 -22.1195,18.84765 l 0,562.3015 c 0,10.2985 10.0333,18.8508 22.1195,18.8508 l 186.6137,0 c 12.0863,0 22.1195,-8.5523 22.1195,-18.8508 l 0,-562.3015 c 0,-10.29859 -10.0332,-18.84765 -22.1195,-18.84765 l -186.6137,0 z m 284.5736,0 c -12.0862,0 -22.1233,8.54906 -22.1233,18.84765 l 0,562.3015 c 0,10.2985 10.0371,18.8508 22.1233,18.8508 l 186.6138,0 c 12.0862,0 22.1195,-8.5523 22.1195,-18.8508 l 0,-562.3015 c 0,-10.29859 -10.0333,-18.84765 -22.1195,-18.84765 l -186.6138,0 z m -569.1434,16.66665 186.6098,0 c 1.5884,0 2.5635,0.8276 2.5635,2.181 l 0,562.3015 c 0,1.3533 -0.975,2.1843 -2.5635,2.1843 l -186.6098,0 c -1.5884,0 -2.5634,-0.831 -2.5634,-2.1843 l 0,-562.3015 c 0,-1.3534 0.975,-2.181 2.5634,-2.181 z m 284.5698,0 186.6137,0 c 1.5884,0 2.5596,0.8276 2.5596,2.181 l 0,562.3015 c 0,1.3533 -0.9712,2.1843 -2.5596,2.1843 l -186.6137,0 c -1.5884,0 -2.5596,-0.831 -2.5596,-2.1843 l 0,-562.3015 c 0,-1.3534 0.9713,-2.181 2.5596,-2.181 z m 284.5736,0 186.6138,0 c 1.5884,0 2.5595,0.8276 2.5595,2.181 l 0,562.3015 c 0,1.3533 -0.9711,2.1843 -2.5595,2.1843 l -186.6138,0 c -1.5883,0 -2.5634,-0.831 -2.5634,-2.1843 l 0,-562.3015 c 0,-1.3534 0.9751,-2.181 2.5634,-2.181 z" d="m 1102.8566,1186.3623 c -12.0863,0 -22.1233,8.549 -22.1233,18.8476 l 0,562.3015 c 0,10.2985 10.0371,18.8508 22.1233,18.8508 l 186.6098,0 c 12.0863,0 22.1234,-8.5523 22.1234,-18.8508 l 0,-562.3015 c 0,-10.2986 -10.0371,-18.8476 -22.1234,-18.8476 l -186.6098,0 z m 284.5698,0 c -12.0862,0 -22.1195,8.549 -22.1195,18.8476 l 0,562.3015 c 0,10.2985 10.0333,18.8508 22.1195,18.8508 l 186.6137,0 c 12.0863,0 22.1195,-8.5523 22.1195,-18.8508 l 0,-562.3015 c 0,-10.2986 -10.0332,-18.8476 -22.1195,-18.8476 l -186.6137,0 z m 284.5736,0 c -12.0862,0 -22.1233,8.549 -22.1233,18.8476 l 0,562.3015 c 0,10.2985 10.0371,18.8508 22.1233,18.8508 l 186.6138,0 c 12.0862,0 22.1195,-8.5523 22.1195,-18.8508 l 0,-562.3015 c 0,-10.2986 -10.0333,-18.8476 -22.1195,-18.8476 l -186.6138,0 z m -569.1434,16.6666 186.6098,0 c 1.5884,0 2.5635,0.8276 2.5635,2.181 l 0,562.3015 c 0,1.3533 -0.975,2.1843 -2.5635,2.1843 l -186.6098,0 c -1.5884,0 -2.5634,-0.831 -2.5634,-2.1843 l 0,-562.3015 c 0,-1.3534 0.975,-2.181 2.5634,-2.181 z m 284.5698,0 186.6137,0 c 1.5884,0 2.5596,0.8276 2.5596,2.181 l 0,562.3015 c 0,1.3533 -0.9712,2.1843 -2.5596,2.1843 l -186.6137,0 c -1.5884,0 -2.5596,-0.831 -2.5596,-2.1843 l 0,-562.3015 c 0,-1.3534 0.9713,-2.181 2.5596,-2.181 z m 284.5736,0 186.6138,0 c 1.5884,0 2.5595,0.8276 2.5595,2.181 l 0,562.3015 c 0,1.3533 -0.9711,2.1843 -2.5595,2.1843 l -186.6138,0 c -1.5883,0 -2.5634,-0.831 -2.5634,-2.1843 l 0,-562.3015 c 0,-1.3534 0.9751,-2.181 2.5634,-2.181 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path <path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
@ -240,7 +299,96 @@
x="-450" x="-450"
y="-1300" /></flowRegion><flowPara y="-1300" /></flowRegion><flowPara
style="font-size:125px" style="font-size:125px"
id="flowPara4175">Borders: 20px</flowPara><flowPara id="flowPara4175">Borders: 30px</flowPara><flowPara
style="font-size:125px" style="font-size:125px"
id="flowPara4179">Canvas: 1000x1000px</flowPara></flowRoot> </g> id="flowPara4179">Canvas: 1000x1000px</flowPara></flowRoot> <g
id="Page-1"
transform="matrix(13.647338,0,0,13.647338,1240.5799,-447.63773)"
style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1">
<g
transform="translate(-1,-1)"
id="Group"
style="fill:#4ff3ad">
<g
transform="translate(1,1)"
id="blogfeed_standard_module">
<g
id="g4357">
<path
id="Shape"
d="M 30,2 30,12 2,12 2,2 30,2 30,2 Z M 30,0 2,0 C 0.8,0 0,0.8 0,2 l 0,10 c 0,1.2 0.8,2 2,2 l 28,0 c 1.2,0 2,-0.8 2,-2 L 32,2 C 32,0.8 31.2,0 30,0 l 0,0 0,0 z"
inkscape:connector-curvature="0" />
<path
id="path4360"
d="M 11,10 5,10 C 4.4,10 4,9.6 4,9 L 4,5 C 4,4.4 4.4,4 5,4 l 6,0 c 0.6,0 1,0.4 1,1 l 0,4 c 0,0.6 -0.4,1 -1,1 l 0,0 z"
inkscape:connector-curvature="0" />
<path
id="path4362"
d="M 27,6 15,6 C 14.4,6 14,5.6 14,5 l 0,0 c 0,-0.6 0.4,-1 1,-1 l 12,0 c 0.6,0 1,0.4 1,1 l 0,0 c 0,0.6 -0.4,1 -1,1 l 0,0 z"
inkscape:connector-curvature="0" />
<path
id="path4364"
d="M 27,10 15,10 C 14.4,10 14,9.6 14,9 l 0,0 c 0,-0.6 0.4,-1 1,-1 l 12,0 c 0.6,0 1,0.4 1,1 l 0,0 c 0,0.6 -0.4,1 -1,1 l 0,0 z"
inkscape:connector-curvature="0" />
<path
id="path4366"
d="m 30,20 0,10 -28,0 0,-10 28,0 0,0 z m 0,-2 -28,0 c -1.2,0 -2,0.8 -2,2 l 0,10 c 0,1.2 0.8,2 2,2 l 28,0 c 1.2,0 2,-0.8 2,-2 l 0,-10 c 0,-1.2 -0.8,-2 -2,-2 l 0,0 0,0 z"
inkscape:connector-curvature="0" />
<path
id="path4368"
d="M 11,28 5,28 C 4.4,28 4,27.6 4,27 l 0,-4 c 0,-0.6 0.4,-1 1,-1 l 6,0 c 0.6,0 1,0.4 1,1 l 0,4 c 0,0.6 -0.4,1 -1,1 l 0,0 z"
inkscape:connector-curvature="0" />
<path
id="path4370"
d="m 27,24 -12,0 c -0.6,0 -1,-0.4 -1,-1 l 0,0 c 0,-0.6 0.4,-1 1,-1 l 12,0 c 0.6,0 1,0.4 1,1 l 0,0 c 0,0.6 -0.4,1 -1,1 l 0,0 z"
inkscape:connector-curvature="0" />
<path
id="path4372"
d="m 27,28 -12,0 c -0.6,0 -1,-0.4 -1,-1 l 0,0 c 0,-0.6 0.4,-1 1,-1 l 12,0 c 0.6,0 1,0.4 1,1 l 0,0 c 0,0.6 -0.4,1 -1,1 l 0,0 z"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</g>
<path
inkscape:connector-curvature="0"
id="path5548"
d="m -893.42578,3403.0068 c -20.28948,0 -37.0625,16.5579 -37.0625,36.8848 l 0,556.2324 c 0,20.3269 16.77277,36.8828 37.0625,36.8828 l 755.875,0 c 20.28976,0 37.0625,-16.5559 37.0625,-36.8828 l 0,-556.2324 c 0,-20.327 -16.77285,-36.8848 -37.0625,-36.8848 l -755.875,0 z m 12.44922,49.3555 730.97656,0 0,531.291 -730.97656,0 0,-531.291 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:30;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
inkscape:connector-curvature="0"
d="m 1050,1952.3623 c -14.2296,0 -26.3106,12.0809 -26.3106,26.3106 l 0,587.3808 c 0,14.2297 12.081,26.3086 26.3106,26.3086 l 187.3789,0 c 14.2295,0 26.3105,-12.0789 26.3105,-26.3086 l 0,-587.3808 c 0,-14.2297 -12.081,-26.3106 -26.3105,-26.3106 l -187.3789,0 z m 292,0 c -14.2296,0 -26.3106,12.0809 -26.3106,26.3106 l 0,587.3808 c 0,14.2297 12.081,26.3086 26.3106,26.3086 l 187.3789,0 c 14.2295,0 26.3105,-12.0789 26.3105,-26.3086 l 0,-587.3808 c 0,-14.2297 -12.081,-26.3106 -26.3105,-26.3106 l -187.3789,0 z m 292,0 c -14.2296,0 -26.3106,12.0809 -26.3106,26.3106 l 0,587.3808 c 0,14.2297 12.081,26.3086 26.3106,26.3086 l 187.3789,0 c 14.2295,0 26.3105,-12.0789 26.3105,-26.3086 l 0,-587.3808 c 0,-14.2297 -12.081,-26.3106 -26.3105,-26.3106 l -187.3789,0 z m -570.3106,40 160,0 0,560 -160,0 0,-560 z m 292,0 160,0 0,560 -160,0 0,-560 z m 292,0 160,0 0,560 -160,0 0,-560 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:40;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
id="rect5705" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:39.99999619;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -979.55318,1952.3623 c -14.22964,0 -26.31052,12.0809 -26.31052,26.3105 l 0,587.3809 c 0,14.2296 12.08091,26.3086 26.31052,26.3086 l 325.50586,0 c 14.22963,0 26.31055,-12.079 26.31055,-26.3086 l 0,-587.3809 c 0,-14.2296 -12.08088,-26.3105 -26.31055,-26.3105 l -325.50586,0 z m 451.10352,0 c -14.22965,0 -26.31055,12.0809 -26.31055,26.3105 l 0,587.3809 c 0,14.2296 12.08094,26.3086 26.31055,26.3086 l 325.50586,0 c 14.22961,0 26.31054,-12.079 26.31054,-26.3086 l 0,-587.3809 c 0,-14.2296 -12.0809,-26.3105 -26.31054,-26.3105 l -325.50586,0 z m -437.41406,40 298.12695,0 0,560 -298.12695,0 0,-560 z m 451.10351,0 298.12695,0 0,560 -298.12695,0 0,-560 z"
id="rect6320"
inkscape:connector-curvature="0" />
<path
id="rect6433"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:40;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 17.51953,2691.0178 c -14.22964,0 -26.310551,12.0809 -26.310551,26.3105 l 0,577.4356 c 0,14.2296 12.080911,26.3085 26.310551,26.3085 l 246.16992,0 c 14.22964,0 26.31053,-12.0789 26.31055,-26.3085 l 0,-577.4356 c 0,-14.2296 -12.08091,-26.3105 -26.31055,-26.3105 l -246.16992,0 z m 378.48828,0 c -14.22964,0 -26.31055,12.0809 -26.31055,26.3105 l 0,577.4356 c 0,14.2296 12.08091,26.3085 26.31055,26.3085 l 387.3789,0 c 14.22964,0 26.31055,-12.0789 26.31055,-26.3085 l 0,-577.4356 c 0,-14.2296 -12.08091,-26.3105 -26.31055,-26.3105 l -387.3789,0 z m -364.79883,40 218.79102,0 0,550.0546 -218.79102,0 0,-550.0546 z m 378.48828,0 360,0 0,550.0546 -360,0 0,-550.0546 z"
inkscape:connector-curvature="0" />
<path
id="path6453"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m -695.53876,3135.1144 c -3.4816,0 -6.2851,2.8032 -6.2851,6.2852 l 0,37.4297 c 0,3.482 2.8035,6.2851 6.2851,6.2851 l 187.4297,0 c 3.4816,0 6.2852,-2.8031 6.2852,-6.2851 l 0,-37.4297 c 0,-3.482 -2.8036,-6.2852 -6.2852,-6.2852 z m -159.07796,-200 c -3.4816,0 -6.2851,2.8032 -6.2851,6.2852 l 0,135.4297 c 0,3.482 2.8035,6.2851 6.2851,6.2851 l 491.4297,0 c 3.4816,0 6.2852,-2.8031 6.2852,-6.2851 l 0,-135.4297 c 0,-3.482 -2.8036,-6.2852 -6.2852,-6.2852 z m 200,-150 c -3.4816,0 -6.2851,2.8032 -6.2851,6.2852 l 0,37.4297 c 0,3.482 2.8035,6.2851 6.2851,6.2851 l 337.4297,0 c 3.4816,0 6.2852,-2.8031 6.2852,-6.2851 l 0,-37.4297 c 0,-3.482 -2.8036,-6.2852 -6.2852,-6.2852 z m -247.9999,0 c -3.4816,0 -6.2852,2.8032 -6.2852,6.2852 l 0,37.4297 c 0,3.482 2.8036,6.2851 6.2852,6.2851 l 87.4296,0 c 3.4816,0 6.2852,-2.8031 6.2852,-6.2851 l 0,-37.4297 c 0,-3.482 -2.8036,-6.2852 -6.2852,-6.2852 z m -80.17401,-113.3986 c -20.28957,0 -37.06257,16.5579 -37.06257,36.8848 l 0,556.2324 c 0,20.3269 16.7728,36.8828 37.06257,36.8828 l 755.875,0 c 20.28976,0 37.0625,-16.5559 37.0625,-36.8828 l 0,-556.2324 c 0,-20.327 -16.77285,-36.8848 -37.0625,-36.8848 l -755.875,0 z m 12.44922,49.3555 730.97656,0 0,531.291 -730.97656,0 0,-531.291 z"
inkscape:connector-curvature="0" />
<path
id="path6484"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 1295.9922,2821.8095 -235.3789,426.3223 259.2559,0 -0.5489,0.9922 366.1914,0 -183.0957,-331.1172 -76.7148,138.7344 -129.709,-234.9317 z m 318.5684,-27.3762 a 67.5,67.5 0 0 0 -67.5,67.5 67.5,67.5 0 0 0 67.5,67.5 67.5,67.5 0 0 0 67.5,-67.5 67.5,67.5 0 0 0 -67.5,-67.5 z m -607.9864,-91.4265 c -20.28946,0 -37.06248,16.5579 -37.06248,36.8848 l 0,556.2324 c 0,20.3269 16.77277,36.8828 37.06248,36.8828 l 755.875,0 c 20.2898,0 37.0625,-16.5559 37.0625,-36.8828 l 0,-556.2324 c 0,-20.327 -16.7728,-36.8848 -37.0625,-36.8848 l -755.875,0 z m 12.4492,49.3555 730.9766,0 0,531.291 -730.9766,0 0,-531.291 z"
inkscape:connector-curvature="0" />
<path
id="path6634"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 135.2832,3704.6259 c -3.4816,0 -6.2832,1.0322 -6.2832,2.3125 l 0,34.2891 c 0,1.2803 2.8016,2.3125 6.2832,2.3125 l 506.4512,0 c 3.4816,0 6.2852,-1.0322 6.2852,-2.3125 l 0,-34.2891 c 0,-1.2803 -2.8036,-2.3125 -6.2852,-2.3125 l -506.4512,0 z M 0,3552.3623 c -14.2296,0 -26.3105,12.079 -26.3105,26.3086 l 0,290.8222 c 0,14.2296 12.0809,26.3106 26.3105,26.3106 l 777.0196,0 c 14.2296,0 26.3105,-12.081 26.3105,-26.3106 l 0,-290.8222 c 0,-14.2296 -12.0809,-26.3086 -26.3105,-26.3086 z m 13.6895,40 749.6406,0 0,263.4414 -749.6406,0 z"
inkscape:connector-curvature="0" />
<path
id="path6563"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 158.4881,2244.3952 c -3.4816,0 -6.2832,1.0309 -6.2832,2.3112 l 0,34.2904 c 0,1.2803 2.8016,2.3112 6.2832,2.3112 l 506.4512,0 c 3.4816,0 6.2852,-1.0309 6.2852,-2.3112 l 0,-34.2904 c 0,-1.2803 -2.8036,-2.3112 -6.2852,-2.3112 z m -122.1795,90.9457 c -14.2295,0 -26.3086,12.079 -26.3086,26.3086 l 0,187.5371 c 0,14.2296 12.0787,26.3105 26.3086,26.3105 l 776.7812,0 c 14.2299,0 26.3086,-12.0809 26.3086,-26.3105 l 0,-187.5371 c 0,-14.2296 -12.0791,-26.3086 -26.3086,-26.3086 l -776.7812,0 z m 13.6914,40 749.3984,0 0,160.1562 -749.3984,0 0,-160.1562 z M 36.3086,1952.2061 C 22.0791,1952.2061 10,1964.287 10,1978.5166 l 0,187.5372 c 0,14.2298 12.0787,26.3085 26.3086,26.3085 l 776.7812,0 c 14.2299,0 26.3086,-12.0787 26.3086,-26.3085 l 0,-187.5372 c 0,-14.2296 -12.0791,-26.3105 -26.3086,-26.3105 l -776.7812,0 z m 13.6914,40 749.3984,0 0,160.1562 -749.3984,0 0,-160.1562 z"
inkscape:connector-curvature="0" />
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 63 KiB

BIN
dist/fonts/main-fonts.ttf

Binary file not shown.

BIN
dist/fonts/main-fonts.woff

Binary file not shown.

33
dist/grapes.min.js

File diff suppressed because one or more lines are too long

21
index.html

@ -4,8 +4,9 @@
<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="styles/css/main.css">
<link rel="stylesheet" href="vendor/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="vendor/codemirror/theme/hopscotch.css"> <link rel="stylesheet" href="node_modules/codemirror/theme/hopscotch.css">
<script src="private/ckeditor/ckeditor.js"></script>
</head> </head>
<style> <style>
body, html{ height: 100%; margin: 0;} body, html{ height: 100%; margin: 0;}
@ -26,6 +27,15 @@
<tr> <td>Row31</td> <td>Row32</td> <td>Row33</td> </tr> <tr> <td>Row31</td> <td>Row32</td> <td>Row33</td> </tr>
</tbody> </tbody>
</table> </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>
@ -780,6 +790,11 @@
margin: 20px 30px; margin: 20px 30px;
} }
.iframe {
height: 300px;
width: 500px;
}
@media (max-width: 768px){ @media (max-width: 768px){
.foot-form-cont{ .foot-form-cont{
width:400px; width:400px;
@ -797,6 +812,6 @@
</style> </style>
</div> </div>
<script data-main="src/demo" src="vendor/require/require.js"></script> <script data-main="src/demo" src="node_modules/requirejs/require.js"></script>
</body> </body>
</html> </html>

22
package.json

@ -1,7 +1,7 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Open source Web Template Editor", "description": "Open source Web Template Editor",
"version": "0.3.20", "version": "0.4.5",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",
@ -11,9 +11,20 @@
"url": "https://github.com/artf/grapesjs.git" "url": "https://github.com/artf/grapesjs.git"
}, },
"dependencies": { "dependencies": {
"bower": "^1.7.2" "almond": "^0.3.3",
"backbone": "^1.3.3",
"backbone-undo": "^0.2.5",
"bower": "^1.7.2",
"codemirror": "^5.21.0",
"codemirror-formatting": "^1.0.0",
"jquery": "^3.1.1",
"keymaster": "^1.6.2",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.8.3"
}, },
"devDependencies": { "devDependencies": {
"bower": "^1.7.2",
"chai": "^3.5.0",
"documentation": "^4.0.0-beta2", "documentation": "^4.0.0-beta2",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-bowercopy": "^1.2.4", "grunt-bowercopy": "^1.2.4",
@ -30,8 +41,11 @@
"grunt-mocha": "^0.4.15", "grunt-mocha": "^0.4.15",
"grunt-sass": "^1.1.0", "grunt-sass": "^1.1.0",
"istanbul": "^0.4.2", "istanbul": "^0.4.2",
"bower": "^1.7.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",
"svg2ttf": "^4.0.1", "svg2ttf": "^4.0.1",
"ttf2eot": "^2.0.0", "ttf2eot": "^2.0.0",
"ttf2woff": "^2.0.1", "ttf2woff": "^2.0.1",
@ -44,6 +58,7 @@
"web template editor", "web template editor",
"web site builder", "web site builder",
"web template builder", "web template builder",
"html website builder",
"site builder", "site builder",
"newsletter builder", "newsletter builder",
"wysiwyg", "wysiwyg",
@ -51,7 +66,6 @@
"editor" "editor"
], ],
"scripts": { "scripts": {
"postinstall": "./node_modules/.bin/bower install --config.interactive=false",
"build": "./node_modules/.bin/grunt build", "build": "./node_modules/.bin/grunt build",
"build:fonts": "./node_modules/.bin/grunt build:fonts", "build:fonts": "./node_modules/.bin/grunt build:fonts",
"test": "./node_modules/.bin/grunt test", "test": "./node_modules/.bin/grunt test",

7
src/asset_manager/config/config.js

@ -1,17 +1,18 @@
define(function () { define(function () {
return { return {
// Default assets // Default assets
assets: [], assets: [],
// Style prefix // Style prefix
stylePrefix: 'am-', stylePrefix: 'am-',
// Url where uploads will be send // 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
addBtnText: 'Add image',
}; };
}); });

21
src/asset_manager/main.js

@ -5,6 +5,8 @@
* * [remove](#remove) * * [remove](#remove)
* * [store](#store) * * [store](#store)
* * [load](#load) * * [load](#load)
* * [onClick](#onClick)
* * [onDblClick](#onDblClick)
* *
* Before using this methods you should get first the module from the editor instance, in this way: * Before using this methods you should get first the module from the editor instance, in this way:
* *
@ -16,6 +18,7 @@
* @param {Object} config Configurations * @param {Object} config Configurations
* @param {Array<Object>} [config.assets=[]] Default assets * @param {Array<Object>} [config.assets=[]] Default assets
* @param {String} [config.uploadText='Drop files here or click to upload'] Upload text * @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 * @param {String} [config.upload=''] Where to send upload data. Expects as return a JSON with asset/s object
* as: {data: [{src:'...'}, {src:'...'}]} * as: {data: [{src:'...'}, {src:'...'}]}
* @return {this} * @return {this}
@ -217,6 +220,22 @@ define(function(require) {
am.collection.onSelect = 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;
},
}; };
}; };
}); });

10
src/asset_manager/template/assets.html

@ -1,8 +1,10 @@
<div class="<%= pfx %>assets-cont"> <div class="<%= pfx %>assets-cont">
<div class="<%= pfx %>assets-header"> <div class="<%= pfx %>assets-header">
<form id="login-form" class="<%= pfx %>add-asset"> <form class="<%= pfx %>add-asset">
<input class="<%= ppfx %>input" placeholder="http://path/to/the/image.jpg" /> <div class="<%= ppfx %>field <%= pfx %>add-field">
<button class="<%= ppfx %>btn-prim">Add image</button> <input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="<%= ppfx %>btn-prim"><%= btnText %></button>
<div style="clear:both"></div> <div style="clear:both"></div>
</form> </form>
<div class="<%= pfx %>dips" style="display:none"> <div class="<%= pfx %>dips" style="display:none">
@ -12,4 +14,4 @@
</div> </div>
<div class="<%= pfx %>assets"></div> <div class="<%= pfx %>assets"></div>
<div style="clear:both"></div> <div style="clear:both"></div>
</div> </div>

64
src/asset_manager/view/AssetImageView.js

@ -3,8 +3,8 @@ define(['./AssetView','text!./../template/assetImage.html'],
return AssetView.extend({ return AssetView.extend({
events:{ events:{
'click' : 'selected', 'click': 'handleClick',
'dblclick' : 'chosen', 'dblclick': 'handleDblClick',
}, },
template: _.template(assetTemplate), template: _.template(assetTemplate),
@ -17,25 +17,39 @@ define(['./AssetView','text!./../template/assetImage.html'],
}, },
/** /**
* Trigger when asset is been selected * Trigger when the asset is clicked
* @private * @private
* */ * */
selected: function(){ handleClick: function() {
this.model.collection.trigger('deselectAll'); var onClick = this.config.onClick;
var model = this.model;
model.collection.trigger('deselectAll');
this.$el.addClass(this.pfx + 'highlight'); this.$el.addClass(this.pfx + 'highlight');
this.updateTarget(this.model.get('src')); if (typeof onClick === 'function') {
onClick(model);
} else {
this.updateTarget(model.get('src'));
}
}, },
/** /**
* Trigger when asset is been chosen (double clicked) * Trigger when the asset is double clicked
* @private * @private
* */ * */
chosen: function(){ handleDblClick: function() {
this.updateTarget(this.model.get('src')); var onDblClick = this.config.onDblClick;
var f = this.model.collection.onSelect; var model = this.model;
if(f && typeof f == 'function'){
f(this.model); if (typeof onDblClick === 'function') {
onDblClick(model);
} else {
this.updateTarget(model.get('src'));
}
var onSelect = model.collection.onSelect;
if(typeof onSelect == 'function'){
onSelect(this.model);
} }
}, },
@ -45,9 +59,9 @@ define(['./AssetView','text!./../template/assetImage.html'],
* @private * @private
* */ * */
updateTarget: function(v){ updateTarget: function(v){
var target = this.model.collection.target; var target = this.model.collection.target;
if(target && target.set){ if(target && target.set) {
var attr = _.clone( target.get('attributes') ); var attr = _.clone( target.get('attributes') );
target.set('attributes', attr ); target.set('attributes', attr );
target.set('src', v ); target.set('src', v );
} }
@ -63,18 +77,18 @@ define(['./AssetView','text!./../template/assetImage.html'],
}, },
render : function(){ render : function(){
var name = this.model.get('name'), var name = this.model.get('name'),
dim = this.model.get('width') && this.model.get('height') ? dim = this.model.get('width') && this.model.get('height') ?
this.model.get('width')+' x '+this.model.get('height') : ''; this.model.get('width')+' x '+this.model.get('height') : '';
name = name ? name : this.model.get('src').split("/").pop(); name = name ? name : this.model.get('src').split("/").pop();
name = name && name.length > 30 ? name.substring(0, 30)+'...' : name; name = name && name.length > 30 ? name.substring(0, 30)+'...' : name;
dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : ''; dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : '';
this.$el.html( this.template({ this.$el.html( this.template({
name: name, name: name,
src: this.model.get('src'), src: this.model.get('src'),
dim: dim, dim: dim,
pfx: this.pfx, pfx: this.pfx,
ppfx: this.ppfx ppfx: this.ppfx
})); }));
this.$el.attr('class', this.className); this.$el.attr('class', this.className);
return this; return this;

3
src/asset_manager/view/AssetsView.js

@ -119,8 +119,9 @@ define(['backbone', './AssetView', './AssetImageView', './FileUploader', 'text!.
},this); },this);
this.$el.html(this.template({ this.$el.html(this.template({
pfx: this.pfx, pfx: this.pfx,
ppfx: this.ppfx, ppfx: this.ppfx,
btnText: this.config.addBtnText,
})); }));
this.$el.find('.'+this.pfx + 'assets').append(fragment); this.$el.find('.'+this.pfx + 'assets').append(fragment);

8
src/asset_manager/view/FileUploader.js

@ -83,10 +83,10 @@ define(['backbone', 'text!./../template/fileUploader.html'],
render : function(){ render : function(){
this.$el.html( this.template({ this.$el.html( this.template({
title : this.config.uploadText, title: this.config.uploadText,
uploadId : this.uploadId, uploadId: this.uploadId,
disabled : this.disabled, disabled: this.disabled,
pfx : this.pfx pfx: this.pfx
}) ); }) );
this.initDrop(); this.initDrop();
this.$el.attr('class', this.pfx + 'file-uploader'); this.$el.attr('class', this.pfx + 'file-uploader');

14
src/block_manager/view/BlockView.js

@ -19,11 +19,12 @@ function(Backbone) {
* Start block dragging * Start block dragging
* @private * @private
*/ */
onDrag: function(){ onDrag: function(e) {
if(!this.config.getSorter) if(!this.config.getSorter)
return; return;
this.config.em.refreshCanvas();
var sorter = this.config.getSorter(); var sorter = this.config.getSorter();
//this.config.dragHelper(this.el.cloneNode(1)); 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);
@ -33,16 +34,17 @@ function(Backbone) {
* Drop block * Drop block
* @private * @private
*/ */
onDrop: function(){ onDrop: function() {
this.doc.off('mouseup', this.onDrop); this.doc.off('mouseup', this.onDrop);
this.config.getSorter().endMove(); this.config.getSorter().endMove();
}, },
render: function() { render: function() {
this.el.innerHTML = this.model.get('label'); var className = this.ppfx + 'block';
this.$el.addClass(this.ppfx + 'block'); this.$el.addClass(className);
this.el.innerHTML = '<div class="' + className + '-label">' + this.model.get('label') + '</div>';
return this; return this;
}, },
}); });
}); });

45
src/block_manager/view/BlocksView.js

@ -4,16 +4,16 @@ function(Backbone, BlockView) {
return Backbone.View.extend({ return Backbone.View.extend({
initialize: function(opts, config) { initialize: function(opts, config) {
_.bindAll(this, 'getSorter', 'onDrag', 'onDrop', 'dragHelper', 'moveHelper'); _.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';
if(this.em){ if(this.em){
this.config.getSorter = this.getSorter; this.config.getSorter = this.getSorter;
this.config.dragHelper = this.dragHelper;
this.canvas = this.em.get('Canvas'); this.canvas = this.em.get('Canvas');
} }
}, },
@ -45,37 +45,17 @@ function(Backbone, BlockView) {
} }
return this.sorter; return this.sorter;
}, },
/*
updateOffset: function(){
if(!this.sorter)
return;
var sorter = this.sorter;
var offDim = this.canvas.getOffset();
sorter.offTop = offDim.top;
sorter.offLeft = offDim.left;
},
*/
/** /**
* Callback when block is on drag * Callback when block is on drag
* @private * @private
*/ */
onDrag: function(){ onDrag: function(){
this.em.stopDefault(); this.em.stopDefault();
this.em.get('Canvas').getBody().style.cursor = 'grabbing'; //this.em.get('Canvas').getBody().style.cursor = 'grabbing';
document.body.style.cursor = 'grabbing'; //document.body.style.cursor = 'grabbing';
}, this.em.get('Canvas').getBody().className += ' ' + this.grabbingCls;
document.body.className += ' ' + this.grabbingCls;
dragHelper: function(el){
el.className += ' ' + this.ppfx + 'bdrag';
this.helper = el;
document.body.appendChild(el);
$(this.em.get('Canvas').getBody()).on('mousemove', this.moveHelper);
$(document).on('mousemove', this.moveHelper);
},
moveHelper: function(e){
this.helper.style.left = e.pageX + 'px';
this.helper.style.top = e.pageY + 'px';
}, },
/** /**
@ -84,9 +64,12 @@ function(Backbone, BlockView) {
*/ */
onDrop: function(model){ onDrop: function(model){
this.em.runDefault(); this.em.runDefault();
this.em.get('Canvas').getBody().style.cursor = ''; var bodyCanvas = this.em.get('Canvas').getBody();
document.body.style.cursor = ''; var body = document.body;
if(model && model.get('activeOnRender')){ bodyCanvas.className = bodyCanvas.className.replace(this.grabbingCls, '');
body.className = body.className.replace(this.grabbingCls, '');
if (model && model.get && model.get('activeOnRender')) {
model.trigger('active'); model.trigger('active');
model.set('activeOnRender', 0); model.set('activeOnRender', 0);
} }
@ -137,4 +120,4 @@ function(Backbone, BlockView) {
}, },
}); });
}); });

88
src/canvas/main.js

@ -136,6 +136,14 @@ define(function(require) {
return CanvasView.ghostEl; return CanvasView.ghostEl;
}, },
/**
* Returns toolbar element
* @return {HTMLElement}
*/
getToolbarEl: function(){
return CanvasView.toolbarEl;
},
/** /**
* Render canvas * Render canvas
* */ * */
@ -158,18 +166,72 @@ define(function(require) {
}, },
/** /**
* Get the offset of the element * Get the offset of the element
* @param {HTMLElement} el * @param {HTMLElement} el
* @return {Object} * @return {Object}
* @private * @private
*/ */
offset: function(el){ offset: function(el){
var rect = el.getBoundingClientRect(); var rect = el.getBoundingClientRect();
return { return {
top: rect.top + document.body.scrollTop, top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft left: rect.left + document.body.scrollLeft
}; };
}, },
/**
* 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 = 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;
},
/** /**
* Returns wrapper element * Returns wrapper element
@ -182,4 +244,4 @@ define(function(require) {
}; };
}; };
}); });

109
src/canvas/view/CanvasView.js

@ -27,6 +27,7 @@ function(Backbone, FrameView) {
var body = this.frame.el.contentDocument.body; var body = this.frame.el.contentDocument.body;
this.toolsEl.style.top = '-' + body.scrollTop + u; this.toolsEl.style.top = '-' + body.scrollTop + u;
this.toolsEl.style.left = '-' + body.scrollLeft + u; this.toolsEl.style.left = '-' + body.scrollLeft + u;
this.em.trigger('canvasScroll');
}, },
/** /**
@ -36,16 +37,23 @@ function(Backbone, FrameView) {
renderBody: function(){ renderBody: function(){
var wrap = this.model.get('frame').get('wrapper'); var wrap = this.model.get('frame').get('wrapper');
if(wrap){ if(wrap){
var ppfx = this.ppfx;
var body = this.frame.$el.contents().find('body'); var body = this.frame.$el.contents().find('body');
var cssc = this.config.em.get('CssComposer'); var cssc = this.config.em.get('CssComposer');
var conf = this.config.em.get('Config'); var conf = this.config.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;
var frameCss = '.' + this.ppfx + 'dashed *{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' + var frameCss = '.' + ppfx + 'dashed :not([contenteditable]) > *{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' +
'.' + this.ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' + '.' + ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' +
'.' + this.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}'+
'.' + this.ppfx + 'freezed{opacity: 0.5; pointer-events: none}' + '.' + ppfx + 'freezed{opacity: 0.5; pointer-events: none}' +
'.' + this.ppfx + 'plh-image{background:#f5f5f5; border:none; height:50px; width:50px; display:block; outline:3px solid #ffca6f; cursor:pointer}'; '.' + ppfx + 'no-pointer{pointer-events: none}' +
'.' + ppfx + 'plh-image{background:#f5f5f5; border:none; height:50px; width:50px; display:block; outline:3px solid #ffca6f; cursor:pointer}' +
'.' + ppfx + 'grabbing{cursor: grabbing; cursor: -webkit-grabbing}' +
'* ::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1)}' +
'* ::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.2)}' +
'* ::-webkit-scrollbar {width: 10px}' +
(conf.canvasCss || '');
if(protCss) if(protCss)
body.append('<style>' + frameCss + protCss + '</style>'); body.append('<style>' + frameCss + protCss + '</style>');
this.config.em.trigger('loaded'); this.config.em.trigger('loaded');
@ -88,27 +96,47 @@ function(Backbone, FrameView) {
this.cvsOff = null; this.cvsOff = null;
}, },
/** /**
* Returns element's data info * Return frame offset
* @param {HTMLElement} el * @return {Object}
* @return {Object} * @private
* @private */
*/ getFrameOffset: function () {
getElementPos: function(el){ if(!this.frmOff)
if(!this.frmOff) this.frmOff = this.offset(this.frame.el);
this.frmOff = this.offset(this.frame.el); return this.frmOff;
if(!this.cvsOff) },
this.cvsOff = this.offset(this.el);
var eo = this.offset(el); /**
var top = eo.top + this.frmOff.top - this.cvsOff.top; * Return canvas offset
var left = eo.left + this.frmOff.left - this.cvsOff.left; * @return {Object}
return { * @private
top: top, */
left: left, getCanvasOffset: function () {
height: el.offsetHeight, if(!this.cvsOff)
width: el.offsetWidth this.cvsOff = this.offset(this.el);
}; return this.cvsOff;
}, },
/**
* Returns element's data info
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementPos: function(el) {
var frmOff = this.getFrameOffset();
var cvsOff = this.getCanvasOffset();
var eo = this.offset(el);
var top = eo.top + frmOff.top - cvsOff.top;
var left = eo.left + frmOff.left - cvsOff.left;
return {
top: top,
left: left,
height: el.offsetHeight,
width: el.offsetWidth
};
},
/** /**
* Returns position data of the canvas element * Returns position data of the canvas element
@ -116,8 +144,8 @@ function(Backbone, FrameView) {
*/ */
getPosition: function() { getPosition: function() {
var bEl = this.frame.el.contentDocument.body; var bEl = this.frame.el.contentDocument.body;
var fo = this.frmOff; var fo = this.getFrameOffset();
var co = this.cvsOff; 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
@ -134,17 +162,20 @@ function(Backbone, FrameView) {
var frame = this.frame; var frame = this.frame;
frame.el.onload = this.renderBody; frame.el.onload = this.renderBody;
} }
this.toolsEl = $('<div>', { id: this.ppfx + 'tools' }).get(0); var ppfx = this.ppfx;
this.hlEl = $('<div>', { class: this.ppfx + 'highlighter' }).get(0); this.toolsEl = $('<div>', { id: ppfx + 'tools' }).get(0);
this.badgeEl = $('<div>', {class: this.ppfx + 'badge'}).get(0); this.hlEl = $('<div>', { class: ppfx + 'highlighter' }).get(0);
this.placerEl = $('<div>', {class: this.ppfx + 'placeholder'}).get(0); this.badgeEl = $('<div>', {class: ppfx + 'badge'}).get(0);
this.placerIntEl = $('<div>', {class: this.ppfx + 'placeholder-int'}).get(0); this.placerEl = $('<div>', {class: ppfx + 'placeholder'}).get(0);
this.ghostEl = $('<div>', {class: this.ppfx + 'ghost'}).get(0); this.placerIntEl = $('<div>', {class: ppfx + 'placeholder-int'}).get(0);
this.placerEl.appendChild(this.placerIntEl); this.ghostEl = $('<div>', {class: ppfx + 'ghost'}).get(0);
this.toolsEl.appendChild(this.hlEl); this.toolbarEl = $('<div>', {class: ppfx + 'toolbar'}).get(0);
this.toolsEl.appendChild(this.badgeEl); this.placerEl.appendChild(this.placerIntEl);
this.toolsEl.appendChild(this.placerEl); this.toolsEl.appendChild(this.hlEl);
this.toolsEl.appendChild(this.ghostEl); this.toolsEl.appendChild(this.badgeEl);
this.toolsEl.appendChild(this.placerEl);
this.toolsEl.appendChild(this.ghostEl);
this.toolsEl.appendChild(this.toolbarEl);
this.$el.append(this.toolsEl); this.$el.append(this.toolsEl);
var rte = this.em.get('rte'); var rte = this.em.get('rte');

5
src/canvas/view/FrameView.js

@ -8,7 +8,8 @@ function(Backbone) {
tagName: 'iframe', tagName: 'iframe',
attributes: { attributes: {
src: 'about:blank' src: 'about:blank',
allowfullscreen: 'allowfullscreen'
}, },
initialize: function(o) { initialize: function(o) {
@ -51,4 +52,4 @@ function(Backbone) {
}, },
}); });
}); });

6
src/code_manager/config/config.js

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

13
src/code_manager/model/CodeMirrorEditor.js

@ -23,11 +23,12 @@ define(['backbone',
init: function(el) init: function(el)
{ {
this.editor = CodeMirror.fromTextArea(el, { this.editor = CodeMirror.fromTextArea(el, {
dragDrop : false, dragDrop: false,
lineNumbers : this.get('lineNumbers'), lineWrapping: true,
readOnly : this.get('readOnly'), lineNumbers: this.get('lineNumbers'),
mode : this.get('codeName'), readOnly: this.get('readOnly'),
theme : this.get('theme'), mode: this.get('codeName'),
theme: this.get('theme'),
}); });
return this; return this;
@ -47,4 +48,4 @@ define(['backbone',
}, },
}); });
}); });

62
src/code_manager/model/CssGenerator.js

@ -5,38 +5,55 @@ define(['backbone'],
* */ * */
return Backbone.Model.extend({ return Backbone.Model.extend({
initialize: function(){ initialize: function() {
this.compCls = []; this.compCls = [];
}, },
/** /**
* Get CSS from components * Get CSS from component
* @param {Model} model * @param {Model} model
* @return {String} * @return {String}
*/ */
buildFromComp: function(model) { buildFromModel: function (model) {
var coll = model.get('components') || model, var code = '';
code = ''; var style = model.get('style');
var classes = model.get('classes');
coll.each(function(m){
var css = m.get('style'), // Let's know what classes I've found
cls = m.get('classes'), if(classes) {
cln = m.get('components'); classes.each(function(model){
cls.each(function(m){ this.compCls.push(model.get('name'));
this.compCls.push(m.get('name'));
}, this); }, this);
}
if(css && Object.keys(css).length !== 0){ if(style && Object.keys(style).length !== 0) {
code += '#' + m.cid + '{'; code += '#' + model.cid + '{';
for(var prop in css){ for(var prop in style){
if(css.hasOwnProperty(prop)) if(style.hasOwnProperty(prop))
code += prop + ':' + css[prop] + ';'; code += prop + ':' + style[prop] + ';';
}
code += '}';
} }
code += '}';
}
if(cln.length) return code;
code += this.buildFromComp(cln); },
/**
* Get CSS from components
* @param {Model} model
* @return {String}
*/
buildFromComp: function(model) {
var coll = model.get('components') || model,
code = '';
coll.each(function(m) {
var cln = m.get('components');
code += this.buildFromModel(m);
if(cln.length){
code += this.buildFromComp(cln);
}
}, this); }, this);
@ -46,7 +63,8 @@ define(['backbone'],
/** @inheritdoc */ /** @inheritdoc */
build: function(model, cssc) { build: function(model, cssc) {
this.compCls = []; this.compCls = [];
var code = this.buildFromComp(model); var code = this.buildFromModel(model);
code += this.buildFromComp(model);
var compCls = this.compCls; var compCls = this.compCls;
if(cssc){ if(cssc){

43
src/code_manager/model/HtmlGenerator.js

@ -6,47 +6,14 @@ define(['backbone'],
return Backbone.Model.extend({ return Backbone.Model.extend({
/** @inheritdoc */ /** @inheritdoc */
build: function(model){ build: function(model, cssc){
var coll = model.get('components') || model, var coll = model.get('components') || model,
code = ''; code = '';
coll.each(function(m){ coll.each(function(m){
var tag = m.get('tagName'), // Tag name code += m.toHTML({
attr = '', // Attributes string cssc: cssc
attrId = '',
strCls = '',
cln = m.get('components'), // Children
attrs = m.get('attributes'),
sTag = m.get('void'),
classes = m.get('classes');
_.each(attrs,function(value, prop){
if(prop == 'onmousedown')
return;
attr += value && prop!='style' ? ' ' + prop + '="' + value + '"' : '';
}); });
if(m.get('type') == 'image'){
tag = 'img';
sTag = 1;
attr += ' src="' + m.get('src') + '"';
}
if(!_.isEmpty(m.get('style')))
attrId = ' id="' + m.cid + '" ';
classes.each(function(m){
strCls += ' ' + m.get('name');
});
strCls = strCls !== '' ? ' class="' + strCls.trim() + '"' : '';
code += '<' + tag + strCls + attrId + attr + (sTag ? '/' : '') + '>' + m.get('content');
if(cln.length)
code += this.build(cln);
if(!sTag)
code += '</'+tag+'>';
}, this); }, this);
return code; return code;

5
src/code_manager/model/JsonGenerator.js

@ -6,12 +6,10 @@ define(['backbone'],
return Backbone.Model.extend({ return Backbone.Model.extend({
/** @inheritdoc */ /** @inheritdoc */
build: function(model) build: function(model) {
{
var json = model.toJSON(); var json = model.toJSON();
this.beforeEach(json); this.beforeEach(json);
// Avoid jshint 'loopfunc' error
_.each(json,function(v, attr){ _.each(json,function(v, attr){
var obj = json[attr]; var obj = json[attr];
if(obj instanceof Backbone.Model){ if(obj instanceof Backbone.Model){
@ -25,7 +23,6 @@ define(['backbone'],
}, this); }, this);
} }
} }
}, this); }, this);
return json; return json;

61
src/commands/main.js

@ -98,9 +98,68 @@ define(function(require) {
defaultCommands['open-sm'] = require('./view/OpenStyleManager'); defaultCommands['open-sm'] = require('./view/OpenStyleManager');
defaultCommands['open-tm'] = require('./view/OpenTraitManager'); defaultCommands['open-tm'] = require('./view/OpenTraitManager');
defaultCommands['open-blocks'] = require('./view/OpenBlocks'); defaultCommands['open-blocks'] = require('./view/OpenBlocks');
defaultCommands['open-assets'] = require('./view/OpenAssets');
defaultCommands.fullscreen = require('./view/Fullscreen'); defaultCommands.fullscreen = require('./view/Fullscreen');
defaultCommands.preview = require('./view/Preview'); defaultCommands.preview = require('./view/Preview');
//this.defaultCommands['resize-comp'] = require('./view/ResizeComponent');
defaultCommands['tlb-delete'] = {
run: function(ed){
var sel = ed.getSelected();
if(!sel || !sel.get('removable')) {
console.warn('The element is not removable');
return;
}
//sel.destroy();
sel.collection.remove(sel);
ed.Canvas.getToolbarEl().style.display = 'none';
ed.editor.runDefault();
},
};
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});
},
};
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 toolbarDisplay = toolbarEl.style.display;
var cmdMove = ed.Commands.get('move-comp');
cmdMove.onEndMoveFromModel = function() {
ed.editor.runDefault();
ed.editor.set('selectedComponent', sel);
//toolbarEl.style.display = toolbarDisplay;
//ed.trigger('canvasScroll'); <- Need first this on SelectComponent
};
ed.editor.stopDefault();
cmdMove.initSorterFromModel(sel);
sel.set('status', 'selected');
toolbarEl.style.display = 'none';
},
};
if(c.em) if(c.em)
c.model = c.em.get('Canvas'); c.model = c.em.get('Canvas');

7
src/commands/view/ExportTemplate.js

@ -7,6 +7,7 @@ define(function() {
run: function(editor, sender) { run: function(editor, sender) {
this.sender = sender; this.sender = sender;
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;
@ -64,8 +65,8 @@ define(function() {
this.modal.open(); this.modal.open();
} }
var addCss = this.protCss || ''; var addCss = this.protCss || '';
this.htmlEditor.setContent( this.cm.getCode(this.components, 'html') ); this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc));
this.cssEditor.setContent( addCss + this.cm.getCode(this.components, '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);
@ -73,4 +74,4 @@ define(function() {
stop: function(){} stop: function(){}
}; };
}); });

32
src/commands/view/MoveComponent.js

@ -12,7 +12,7 @@ define(['backbone', './SelectComponent','./SelectPosition'],
this.noSelClass = this.ppfx + 'no-select'; this.noSelClass = this.ppfx + 'no-select';
}, },
enable: function(){ enable: function() {
SelectComponent.enable.apply(this, arguments); SelectComponent.enable.apply(this, arguments);
this.getBadgeEl().addClass(this.badgeClass); this.getBadgeEl().addClass(this.badgeClass);
this.getHighlighterEl().addClass(this.hoverClass); this.getHighlighterEl().addClass(this.hoverClass);
@ -37,17 +37,43 @@ define(['backbone', './SelectComponent','./SelectPosition'],
* */ * */
initSorter: function(e){ initSorter: function(e){
var el = $(e.target).data('model'); var el = $(e.target).data('model');
if(!el.get('draggable')) var drag = el.get('draggable');
if(!drag)
return; return;
// Avoid badge showing on move // Avoid badge showing on move
this.cacheEl = null; this.cacheEl = null;
this.startSelectPosition(e.target, this.frameEl.contentDocument); this.startSelectPosition(e.target, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMove.bind(this); this.sorter.onEndMove = this.onEndMove.bind(this);
this.stopSelectComponent(); this.stopSelectComponent();
this.$wrapper.off('mousedown', this.initSorter); this.$wrapper.off('mousedown', this.initSorter);
this.getContentWindow().on('keydown', this.rollback); this.getContentWindow().on('keydown', this.rollback);
}, },
/**
* Init sorter from model
* @param {Object} model
* @private
*/
initSorterFromModel: function(model) {
var drag = model.get('draggable');
if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null;
var el = model.view.el;
this.startSelectPosition(el, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMoveFromModel.bind(this);
this.stopSelectComponent();
this.getContentWindow().on('keydown', this.rollback);
},
onEndMoveFromModel: function() {
this.getContentWindow().off('keydown', this.rollback);
},
/** /**
* Callback after sorting * Callback after sorting
* @private * @private
@ -110,4 +136,4 @@ define(['backbone', './SelectComponent','./SelectPosition'],
wp.css('cursor', '').unbind().removeClass(this.noSelClass); wp.css('cursor', '').unbind().removeClass(this.noSelClass);
} }
}); });
}); });

25
src/commands/view/OpenAssets.js

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

3
src/commands/view/OpenLayers.js

@ -15,6 +15,7 @@ define(['Navigator'], function(Layers) {
config.layers.stylePrefix = config.stylePrefix + lyStylePfx; config.layers.stylePrefix = config.stylePrefix + lyStylePfx;
config.layers.em = em.editor; config.layers.em = em.editor;
config.layers.opened = em.editor.get('opened');
var layers = new Layers(collection, config.layers); var layers = new Layers(collection, config.layers);
this.$layers = layers.render(); this.$layers = layers.render();
@ -35,4 +36,4 @@ define(['Navigator'], function(Layers) {
this.$layers.hide(); this.$layers.hide();
} }
}; };
}); });

20
src/commands/view/Preview.js

@ -8,9 +8,16 @@ define(function() {
return this.panels; return this.panels;
}, },
tglPointers: function(editor, v) {
var elP = editor.Canvas.getBody().querySelectorAll('.' + this.ppfx + 'no-pointer');
_.each(elP, function(item){
item.style.pointerEvents = v ? '' : 'all';
});
},
run: function(editor, sender) { run: function(editor, sender) {
if(sender) if(sender && sender.set)
sender.set('active',false); sender.set('active', false);
editor.stopCommand('sw-visibility'); editor.stopCommand('sw-visibility');
var that = this; var that = this;
var panels = this.getPanels(editor); var panels = this.getPanels(editor);
@ -26,6 +33,12 @@ define(function() {
}; };
} }
this.helper.style.display = 'inline-block'; this.helper.style.display = 'inline-block';
this.tglPointers(editor);
/*
editor.Canvas.getBody().querySelectorAll('.' + pfx + 'no-pointer').forEach(function(){
this.style.pointerEvents = 'all';
});*/
panels.style.display = 'none'; panels.style.display = 'none';
var canvasS = canvas.style; var canvasS = canvas.style;
@ -49,6 +62,7 @@ define(function() {
this.helper.style.display = 'none'; this.helper.style.display = 'none';
} }
editor.trigger('change:canvasOffset'); editor.trigger('change:canvasOffset');
this.tglPointers(editor, 1);
} }
}; };
}); });

151
src/commands/view/SelectComponent.js

@ -1,8 +1,11 @@
define(function() { define(function(require) {
/** /**
* @class SelectComponent * @class SelectComponent
* @private * @private
* */ * */
var ToolbarView = require('DomComponents/view/ToolbarView');
var Toolbar = require('DomComponents/model/Toolbar');
return { return {
init: function(o){ init: function(o){
@ -16,6 +19,9 @@ define(function() {
var config = this.config.em.get('Config'); var config = this.config.em.get('Config');
this.startSelectComponent(); this.startSelectComponent();
this.toggleClipboard(config.copyPaste); this.toggleClipboard(config.copyPaste);
var em = this.config.em;
em.on('change:canvasOffset', this.onFrameScroll);
em.on('change:selectedComponent', this.updateToolbar, this);
}, },
/** /**
@ -96,17 +102,20 @@ define(function() {
var key = e.which || e.keyCode; var key = e.which || e.keyCode;
var comp = this.editorModel.get('selectedComponent'); var comp = this.editorModel.get('selectedComponent');
var focused = this.frameEl.contentDocument.activeElement.tagName !== 'BODY'; var focused = this.frameEl.contentDocument.activeElement.tagName !== 'BODY';
// On CANC (46) or Backspace (8)
if(key == 8 || key == 46) { if(key == 8 || key == 46) {
if(!focused) if(!focused)
e.preventDefault(); e.preventDefault();
if(comp && !focused){ if(comp && !focused) {
if(!comp.get('removable')) if(!comp.get('removable'))
return; return;
comp.set('status',''); comp.set('status','');
comp.destroy(); comp.destroy();
this.hideBadge(); this.hideBadge();
this.clean(); this.clean();
this.editorModel.set('selectedComponent',null); this.hideHighlighter();
this.editorModel.set('selectedComponent', null);
} }
} }
}, },
@ -126,6 +135,7 @@ define(function() {
} }
var pos = this.getElementPos(trg); var pos = this.getElementPos(trg);
this.updateBadge(trg, pos); this.updateBadge(trg, pos);
// Not mirrored
this.updateHighlighter(trg, pos); this.updateHighlighter(trg, pos);
}, },
@ -137,8 +147,14 @@ define(function() {
onOut: function(e) { onOut: function(e) {
e.stopPropagation(); e.stopPropagation();
this.hideBadge(); this.hideBadge();
if(this.hl) this.hideHighlighter();
this.hl.css({ left: -10000, top:-10000 }); },
/**
* Hide Highlighter element
*/
hideHighlighter: function () {
this.canvas.getHighlighter().style.display = 'none';
}, },
/** /**
@ -188,15 +204,15 @@ define(function() {
* @param {Object} pos Position object * @param {Object} pos Position object
* @private * @private
*/ */
updateHighlighter: function(el, pos){ updateHighlighter: function(el, pos) {
if(!this.hl) var hlEl = this.canvas.getHighlighter();
this.hl = $(this.canvas.getHighlighter()); var hlStyle = hlEl.style;
this.hl.css({ var unit = 'px';
left: pos.left, hlStyle.left = pos.left + unit;
top: pos.top, hlStyle.top = pos.top + unit;
height: pos.height, hlStyle.height = pos.height + unit;
width: pos.width hlStyle.width = pos.width + unit;
}); hlStyle.display = 'block';
}, },
/** /**
@ -212,11 +228,97 @@ define(function() {
var $el = $(el); var $el = $(el);
var nMd = $el.data('model'); var nMd = $el.data('model');
if(nMd){ if(nMd){
var mirror = nMd.get('mirror');
nMd = mirror ? mirror : nMd;
// Close all opened components inside Navigator
var opened = this.em.get('opened');
for (var cid in opened){
var m = opened[cid];
m.set('open', 0);
}
var parent = nMd.collection ? nMd.collection.parent : null;
while(parent) {
parent.set('open', 1);
opened[parent.cid] = parent;
parent = parent.collection ? parent.collection.parent : null;
}
this.editorModel.set('selectedComponent', nMd); this.editorModel.set('selectedComponent', nMd);
nMd.set('status','selected'); nMd.set('status','selected');
//this.updateToolbar(nMd);
} }
}, },
/**
* Update toolbar if the component has one
* @param {Object} mod
*/
updateToolbar: function(mod) {
var em = this.config.em;
var model = mod == em ? em.get('selectedComponent') : mod;
if(!model){
return;
}
var toolbar = model.get('toolbar');
var ppfx = this.ppfx;
var showToolbar = em.get('Config').showToolbar;
if (showToolbar && toolbar && toolbar.length) {
if(!this.toolbar) {
var toolbarEl = this.canvas.getToolbarEl();
toolbarEl.innerHTML = '';
this.toolbar = new Toolbar(toolbar);
var toolbarView = new ToolbarView({
collection: this.toolbar,
editor: this.editor
});
toolbarEl.appendChild(toolbarView.render().el);
}
this.toolbar.reset(toolbar);
var view = model.view;
if(view) {
this.updateToolbarPos(view.el);
}
}
},
/**
* Update toolbar positions
* @param {HTMLElement} el
* @param {Object} pos
*/
updateToolbarPos: function(el, elPos) {
var toolbarEl = this.canvas.getToolbarEl();
var canvasPos = this.getCanvasPosition();
var pos = elPos || this.getElementPos(el);
var toolbarStyle = toolbarEl.style;
var unit = 'px';
toolbarStyle.display = 'flex';
var elTop = pos.top - toolbarEl.offsetHeight;
var elLeft = pos.left + pos.width - toolbarEl.offsetWidth;
var leftPos = elLeft < canvasPos.left ? canvasPos.left : elLeft;
// This will make the toolbar follow the window up
// and down while scrolling
var topPos = elTop < canvasPos.top ? canvasPos.top : elTop;
// This will stop the toolbar when the end of the element is reached
topPos = topPos > (pos.top + pos.height) ? (pos.top + pos.height) : topPos;
toolbarStyle.top = topPos + unit;
toolbarStyle.left = leftPos + unit;
},
/**
* Return canvas dimensions and positions
* @return {Object}
*/
getCanvasPosition: function () {
return this.canvas.getCanvasView().getPosition();
},
/** /**
* Removes all highlighting effects on components * Removes all highlighting effects on components
* @private * @private
@ -241,8 +343,16 @@ define(function() {
*/ */
onFrameScroll: function(e){ onFrameScroll: function(e){
var el = this.cacheEl; var el = this.cacheEl;
if(el) if(el){
this.updateBadge(el, this.getElementPos(el)); var elPos = this.getElementPos(el);
this.updateBadge(el, elPos);
var model = this.editorModel.get('selectedComponent');
if (model) {
var view = model.view;
this.updateToolbarPos(view.el);
}
}
}, },
/** /**
@ -286,7 +396,9 @@ define(function() {
return this.contWindow; return this.contWindow;
}, },
run: function(em, sender) { run: function(em) {
if(em && em.get)
this.editor = em.get('Editor');
this.enable(); this.enable();
}, },
@ -297,6 +409,9 @@ define(function() {
this.em.set('selectedComponent', null); this.em.set('selectedComponent', null);
this.toggleClipboard(); this.toggleClipboard();
this.hideBadge(); this.hideBadge();
this.canvas.getToolbarEl().style.display = 'none';
this.em.off('change:canvasOffset', this.onFrameScroll);
this.em.off('change:selectedComponent', this.updateToolbar, this);
} }
}; };
}); });

18
src/config/require-config.js

@ -21,15 +21,15 @@ require.config({
}, },
paths: { paths: {
jquery: '../vendor/jquery/jquery', jquery: '../node_modules/jquery/dist/jquery',
underscore: '../vendor/underscore/underscore', underscore: '../node_modules/underscore/underscore',
backbone: '../vendor/backbone/backbone', backbone: '../node_modules/backbone/backbone',
backboneUndo: '../vendor/backbone-undo/backbone-undo', backboneUndo: '../node_modules/backbone-undo/Backbone.Undo',
keymaster: '../vendor/keymaster/keymaster', keymaster: '../node_modules/keymaster/keymaster',
text: '../vendor/require-text/text', text: '../node_modules/requirejs-text/text',
Spectrum: '../vendor/spectrum/spectrum', Spectrum: '../node_modules/spectrum-colorpicker/spectrum',
codemirror: '../vendor/codemirror', codemirror: '../node_modules/codemirror',
formatting: '../vendor/codemirror-formatting/formatting', formatting: '../node_modules/codemirror-formatting/formatting',
}, },
packages : [ packages : [

2
src/css_composer/main.js

@ -236,7 +236,7 @@ define(function(require) {
var selec = sm.add(sels[j]); var selec = sm.add(sels[j]);
newSels.push(selec); newSels.push(selec);
} }
var model = this.add(newSels, rule.state, rule.width); var model = this.add(newSels, rule.state, rule.maxWidth);
model.set('style', rule.style || {}); model.set('style', rule.style || {});
result.push(model); result.push(model);
} }

45
src/demo.js

@ -11,25 +11,33 @@ require(['config/require-config'], function() {
fromElement: true, fromElement: true,
/* /*
components: [{ components: [{
type: 'text',
style:{ style:{
width:'100px', width:'100px',
height:'100px' height:'100px',
margin: '50px auto',
}, },
traits: ['title'] traits: ['title'],
},{ components: [{
style:{ type: 'textnode',
width:'150px', content: 'text node row',
height:'100px' },{
}, type: 'textnode',
traits: [{name:'title', value: "myTitleTest"}] content: ', another text node',
},{ },{
type: 'image' type: 'link',
},{ content: 'someLink',
type: 'link', },{
content: 'mylink', type: 'textnode',
}], content: " More text node --- ",
*/ }],
storageManager:{ autoload: 0}, }],*/
storageManager:{
autoload: 0,
storeComponents: 1,
storeStyles: 1,
},
commands: { commands: {
defaults : [{ defaults : [{
id: 'open-github', id: 'open-github',
@ -59,7 +67,7 @@ require(['config/require-config'], function() {
} }
}], }],
}, },
/*
assetManager: { assetManager: {
storageType : '', storageType : '',
storeOnChange : true, storeOnChange : true,
@ -77,7 +85,7 @@ require(['config/require-config'], function() {
{ type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728}, { type: 'image', src : './img/bg-gr-v.png', date: '2015-02-01',height:1, width:1728},
] ]
}, },
*/
styleManager : { styleManager : {
sectors: [{ sectors: [{
@ -319,6 +327,7 @@ require(['config/require-config'], function() {
); );
window.editor = editor; window.editor = editor;
}); });

19
src/dom_components/config/config.js

@ -1,16 +1,17 @@
define(function () { define(function () {
return { return {
stylePrefix : 'comp-', stylePrefix: 'comp-',
wrapperId : 'wrapper', wrapperId: 'wrapper',
// Default wrapper configuration // Default wrapper configuration
wrapper : { wrapper: {
removable : false, //classes: ['body'],
copyable : false, removable: false,
stylable : ['background','background-color','background-image', 'background-repeat','background-attachment','background-position'], copyable: false,
draggable : false, stylable: ['background','background-color','background-image', 'background-repeat','background-attachment','background-position'],
badgable : false, draggable: false,
badgable: false,
components: [], components: [],
}, },
@ -28,4 +29,4 @@ define(function () {
// 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'],
}; };
}); });

77
src/dom_components/main.js

@ -36,19 +36,59 @@ define(function(require) {
return function (){ return function (){
var c = {}, var c = {},
defaults = require('./config/config'), componentTypes = {},
Component = require('./model/Component'), defaults = require('./config/config'),
ComponentText = require('./model/ComponentText'), Component = require('./model/Component'),
ComponentImage = require('./model/ComponentImage'), ComponentView = require('./view/ComponentView');
ComponentLink = require('./model/ComponentLink'),
ComponentView = require('./view/ComponentView'),
ComponentImageView = require('./view/ComponentImageView'),
ComponentTextView = require('./view/ComponentTextView');
ComponentLinkView = require('./view/ComponentLinkView');
var component, componentView; var component, componentView;
var defaultTypes = {
'cell': {
model: require('./model/ComponentTableCell'),
view: require('./view/ComponentTableCellView'),
},
'row': {
model: require('./model/ComponentTableRow'),
view: require('./view/ComponentTableRowView'),
},
'table': {
model: require('./model/ComponentTable'),
view: require('./view/ComponentTableView'),
},
'map': {
model: require('./model/ComponentMap'),
view: require('./view/ComponentMapView'),
},
'link': {
model: require('./model/ComponentLink'),
view: require('./view/ComponentLinkView'),
},
'video': {
model: require('./model/ComponentVideo'),
view: require('./view/ComponentVideoView'),
},
'image': {
model: require('./model/ComponentImage'),
view: require('./view/ComponentImageView'),
},
'textnode': {
model: require('./model/ComponentTextNode'),
view: require('./view/ComponentTextNodeView'),
},
'text': {
model: require('./model/ComponentText'),
view: require('./view/ComponentTextView'),
},
'default': {
model: Component,
view: ComponentView,
},
};
return { return {
componentTypes: defaultTypes,
/** /**
* Name of the module * Name of the module
* @type {String} * @type {String}
@ -96,14 +136,22 @@ define(function(require) {
c.rte = c.em.get('rte') || ''; c.rte = c.em.get('rte') || '';
c.modal = c.em.get('Modal') || ''; c.modal = c.em.get('Modal') || '';
c.am = c.em.get('AssetManager') || ''; c.am = c.em.get('AssetManager') || '';
c.em.get('Parser').compTypes = defaultTypes;
} }
component = new Component(c.wrapper, { sm: c.em, config: c }); component = new Component(c.wrapper, {
sm: c.em,
config: c,
defaultTypes: defaultTypes,
componentTypes: componentTypes,
});
component.set({ attributes: {id: 'wrapper'}}); component.set({ attributes: {id: 'wrapper'}});
component.get('components').add(c.components); component.get('components').add(c.components);
componentView = new ComponentView({ componentView = new ComponentView({
model: component, model: component,
config: c, config: c,
defaultTypes: defaultTypes,
componentTypes: componentTypes,
}); });
return this; return this;
}, },
@ -283,6 +331,15 @@ define(function(require) {
this.clear().addComponent(components); this.clear().addComponent(components);
}, },
/**
* Add new component type
* @private
*/
addComponentType: function(type, methods) {
componentTypes[type] = methods;
return this;
}
}; };
}; };
}); });

145
src/dom_components/model/Component.js

@ -7,12 +7,31 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
tagName: 'div', tagName: 'div',
type: '', type: '',
editable: false, editable: false,
// True if the component is removable from the canvas
removable: true, removable: true,
// Indicates if it's possible to drag the component inside other
// Tip: Indicate an array of selectors where it could be dropped inside
draggable: true, draggable: true,
// Indicates if it's possible to drop other components inside
// Tip: Indicate an array of selectors which could be dropped inside
droppable: true, droppable: true,
// 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
// Tip: Indicate an array of css properties is possible to style
stylable: true, stylable: true,
// True if it's possible to clone the component
copyable: true, copyable: true,
// TODO
mirror: '',
void: false, void: false,
state: '', state: '',
status: '', status: '',
@ -20,7 +39,9 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
content: '', content: '',
style: {}, style: {},
attributes: {}, attributes: {},
classes: '',
traits: ['id', 'title'], traits: ['id', 'title'],
toolbar: null,
}, },
initialize: function(o, opt) { initialize: function(o, opt) {
@ -28,17 +49,61 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
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.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.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.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();
},
/**
* Init toolbar
*/
initToolbar: function () {
var model = this;
if(!model.get('toolbar')) {
var tb = [];
if(model.get('draggable')) {
tb.push({
attributes: {class: 'fa fa-arrows'},
command: 'tlb-move',
});
}
if(model.get('copyable')) {
tb.push({
attributes: {class: 'fa fa-clone'},
command: 'tlb-clone',
});
}
if(model.get('removable')) {
tb.push({
attributes: {class: 'fa fa-trash-o'},
command: 'tlb-delete',
});
}
model.set('toolbar', tb);
}
},
/**
* Load traits
* @param {Array} traits
* @private
*/
loadTraits: function(traits) {
var trt = new Traits();
trt.setTarget(this);
trt.add(traits);
this.set('traits', trt);
}, },
/** /**
@ -99,13 +164,14 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
}); });
} }
attr.status = ''; attr.status = '';
return new this.constructor(attr, {sm: this.sm}); attr.view = '';
return new this.constructor(attr, this.opt);
}, },
/** /**
* Get name of the component * Get name of the component
* * @return {string}
* @return {String}
* @private * @private
* */ * */
getName: function() { getName: function() {
@ -115,10 +181,79 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
var tag = this.get('tagName'); var tag = this.get('tagName');
tag = tag == 'div' ? 'box' : tag; tag = tag == 'div' ? 'box' : tag;
tag = type ? type : tag; tag = type ? type : tag;
this.name = tag.charAt(0).toUpperCase() + tag.slice(1) + ' ' + id; this.name = tag.charAt(0).toUpperCase() + tag.slice(1);
} }
return this.name; 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() + '"' : '';
// 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 + '" ';
code += '<' + tag + strCls + attrId + strAttr + (sTag ? '/' : '') + '>' + m.get('content');
m.get('components').each(function(m) {
code += m.toHTML();
});
if(!sTag)
code += '</'+tag+'>';
return code;
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML: function() {
var attr = this.get('attributes') || {};
delete attr.style;
return attr;
},
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
return {tagName: el.tagName ? el.tagName.toLowerCase() : ''};
},
}); });
}); });

89
src/dom_components/model/ComponentImage.js

@ -4,10 +4,93 @@ define(['./Component'],
return Component.extend({ return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: _.extend({}, Component.prototype.defaults, {
src: '', type: 'image',
droppable: false, tagName: 'img',
traits: ['alt'], src: '',
void: 1,
droppable: false,
traits: ['alt'],
toolbar: [{
attributes: {class: 'fa fa-arrows'},
command: 'tlb-move',
},{
attributes: {class: 'fa fa-clone'},
command: 'tlb-clone',
},{
attributes: {class: 'fa fa-pencil'},
command: 'tlb-edit',
},{
attributes: {class: 'fa fa-trash-o'},
command: 'tlb-delete',
}],
}), }),
initialize: function(o, opt) {
Component.prototype.initialize.apply(this, arguments);
var attr = this.get('attributes');
if(attr.src)
this.set('src', attr.src);
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML: function() {
var attr = Component.prototype.getAttrToHTML.apply(this, arguments);
delete attr.onmousedown;
var src = this.get('src');
if(src)
attr.src = src;
return attr;
},
/**
* Parse uri
* @param {string} uri
* @return {object}
* @private
*/
parseUri: function(uri) {
var el = document.createElement('a');
el.href = uri;
var query = {};
var qrs = el.search.substring(1).split('&');
for (var i = 0; i < qrs.length; i++) {
var pair = qrs[i].split('=');
var name = decodeURIComponent(pair[0]);
if(name)
query[name] = decodeURIComponent(pair[1]);
}
return {
hostname: el.hostname,
pathname: el.pathname,
protocol: el.protocol,
search: el.search,
hash: el.hash,
port: el.port,
query: query,
};
},
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
if(el.tagName == 'IMG'){
result = {type: 'image'};
}
return result;
},
}); });
}); });

33
src/dom_components/model/ComponentLink.js

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

108
src/dom_components/model/ComponentMap.js

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

91
src/dom_components/model/ComponentTable.js

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

35
src/dom_components/model/ComponentTableCell.js

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

46
src/dom_components/model/ComponentTableRow.js

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

3
src/dom_components/model/ComponentText.js

@ -4,8 +4,7 @@ define(['./Component'],
return Component.extend({ return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, { defaults: _.extend({}, Component.prototype.defaults, {
content : '', droppable: false,
droppable : false,
}), }),
}); });

28
src/dom_components/model/ComponentTextNode.js

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

317
src/dom_components/model/ComponentVideo.js

@ -0,0 +1,317 @@
define(['./ComponentImage', './Component'],
function (Component, OComponent) {
var yt = 'yt';
var vi = 'vi';
return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'video',
tagName: 'video',
videoId: '',
void: 0,
provider: '', // on change of provider, traits are switched
ytUrl: 'https://www.youtube.com/embed/',
viUrl: 'https://player.vimeo.com/video/',
loop: 0,
muted: 0,
autoplay: 0,
controls: 1,
color: '',
sources: [],
attributes:{allowfullscreen:'allowfullscreen'},
toolbar: OComponent.prototype.defaults.toolbar,
}),
initialize: function(o, opt) {
var traits = [];
var prov = this.get('provider');
switch (prov) {
case yt:
traits = this.getYoutubeTraits();
break;
case vi:
traits = this.getVimeoTraits();
break;
default:
traits = this.getSourceTraits();
}
if(this.get('src'))
this.parseFromSrc();
this.set('traits', traits);
Component.prototype.initialize.apply(this, arguments);
this.listenTo(this, 'change:provider', this.updateTraits);
this.listenTo(this, 'change:videoId', this.updateSrc);
},
/**
* Set attributes by src string
*/
parseFromSrc: function() {
var prov = this.get('provider');
var uri = this.parseUri(this.get('src'));
var qr = uri.query;
switch (prov) {
case yt: case vi:
var videoId = uri.pathname.split('/').pop();
this.set('videoId', videoId);
if(qr.autoplay)
this.set('autoplay', 1);
if(qr.loop)
this.set('loop', 1);
if(parseInt(qr.controls) === 0)
this.set('controls', 0);
if(qr.color)
this.set('color', qr.color);
break;
default:
}
},
/**
* Update src on change of video ID
* @private
*/
updateSrc: function() {
var prov = this.get('provider');
switch (prov) {
case yt:
this.set('src',this.getYoutubeSrc());
break;
case vi:
this.set('src',this.getVimeoSrc());
break;
}
},
/**
* Returns object of attributes for HTML
* @return {Object}
* @private
*/
getAttrToHTML: function() {
var attr = Component.prototype.getAttrToHTML.apply(this, arguments);
var prov = this.get('provider');
switch (prov) {
case yt: case vi:
break;
default:
if(this.get('loop'))
attr.loop = 'loop';
if(this.get('autoplay'))
attr.autoplay = 'autoplay';
if(this.get('controls'))
attr.controls = 'controls';
}
return attr;
},
/**
* Update traits by provider
* @private
*/
updateTraits: function() {
var prov = this.get('provider');
var traits = this.getSourceTraits();
switch (prov) {
case yt:
this.set('tagName', 'iframe');
traits = this.getYoutubeTraits();
break;
case vi:
this.set('tagName', 'iframe');
traits = this.getVimeoTraits();
break;
default:
this.set('tagName', 'video');
}
this.loadTraits(traits);
this.sm.trigger('change:selectedComponent');
},
// Listen provider change and switch traits, in TraitView listen traits change
/**
* Return the provider trait
* @return {Object}
* @private
*/
getProviderTrait: function() {
return {
type: 'select',
label: 'Provider',
name: 'provider',
changeProp: 1,
value: this.get('provider'),
options: [
{value: 'so', name: 'HTML5 Source'},
{value: yt, name: 'Youtube'},
{value: vi, name: 'Vimeo'}
]
};
},
/**
* Return traits for the source provider
* @return {Array<Object>}
* @private
*/
getSourceTraits: function() {
return [
this.getProviderTrait(), {
label: 'Source',
name: 'src',
placeholder: 'eg. ./media/video.mp4',
changeProp: 1,
},
this.getAutoplayTrait(),
this.getLoopTrait(),
this.getControlsTrait()];
},
/**
* Return traits for the source provider
* @return {Array<Object>}
* @private
*/
getYoutubeTraits: function() {
return [
this.getProviderTrait(), {
label: 'Video ID',
name: 'videoId',
placeholder: 'eg. jNQXAC9IVRw',
changeProp: 1,
},
this.getAutoplayTrait(),
this.getLoopTrait(),
this.getControlsTrait()
];
},
/**
* Return traits for the source provider
* @return {Array<Object>}
* @private
*/
getVimeoTraits: function() {
return [
this.getProviderTrait(), {
label: 'Video ID',
name: 'videoId',
placeholder: 'eg. 123456789',
changeProp: 1,
},{
label: 'Color',
name: 'color',
placeholder: 'eg. FF0000',
changeProp: 1,
},
this.getAutoplayTrait(),
this.getLoopTrait(),
this.getControlsTrait()];
},
/**
* Return object trait
* @return {Object}
* @private
*/
getAutoplayTrait: function(){
return {
type: 'checkbox',
label: 'Autoplay',
name: 'autoplay',
changeProp: 1,
};
},
/**
* Return object trait
* @return {Object}
* @private
*/
getLoopTrait: function(){
return {
type: 'checkbox',
label: 'Loop',
name: 'loop',
changeProp: 1,
};
},
/**
* Return object trait
* @return {Object}
* @private
*/
getControlsTrait: function(){
return {
type: 'checkbox',
label: 'Controls',
name: 'controls',
changeProp: 1,
};
},
/**
* Returns url to youtube video
* @return {string}
* @private
*/
getYoutubeSrc: function() {
var url = this.get('ytUrl');
url += this.get('videoId') + '?';
url += this.get('autoplay') ? '&autoplay=1' : '';
url += !this.get('controls') ? '&controls=0' : '';
url += this.get('loop') ? '&loop=1' : '';
return url;
},
/**
* Returns url to vimeo video
* @return {string}
* @private
*/
getVimeoSrc: function() {
var url = this.get('viUrl');
url += this.get('videoId') + '?';
url += this.get('autoplay') ? '&autoplay=1' : '';
url += this.get('loop') ? '&loop=1' : '';
url += !this.get('controls') ? '&title=0&portrait=0&badge=0' : '';
url += this.get('color') ? '&color=' + this.get('color') : '';
return url;
},
},{
/**
* Detect if the passed element is a valid component.
* In case the element is valid an object abstracted
* from the element will be returned
* @param {HTMLElement}
* @return {Object}
* @private
*/
isComponent: function(el) {
var result = '';
var isYtProv = /youtube\.com\/embed/.test(el.src);
var isViProv = /player\.vimeo\.com\/video/.test(el.src);
var isExtProv = isYtProv || isViProv;
if(el.tagName == 'VIDEO' ||
(el.tagName == 'IFRAME' && isExtProv) ){
result = {type: 'video'};
if(el.src)
result.src = el.src;
if(isExtProv){
if(isYtProv)
result.provider = yt;
else if(isViProv)
result.provider = vi;
}
}
return result;
},
});
});

38
src/dom_components/model/Components.js

@ -22,34 +22,22 @@ define([ 'backbone', 'require'],
if(opt && opt.config) if(opt && opt.config)
options.config = opt.config; options.config = opt.config;
switch(attrs.type){ if(opt && opt.defaultTypes)
options.defaultTypes = opt.defaultTypes;
case 'text':
if(!this.mComponentText)
this.mComponentText = require("./ComponentText");
model = new this.mComponentText(attrs, options);
break;
case 'link':
if(!this.mComponentLink)
this.mComponentLink = require("./ComponentLink");
model = new this.mComponentLink(attrs, options);
break;
case 'image':
if(!this.mComponentImage)
this.mComponentImage = require("./ComponentImage");
model = new this.mComponentImage(attrs, options);
break;
default:
if(!this.mComponent)
this.mComponent = require("./Component");
model = new this.mComponent(attrs, options);
if(opt && opt.componentTypes)
options.componentTypes = opt.componentTypes;
var df = opt.defaultTypes;
var cf = opt.componentTypes;
if(df[attrs.type]){
model = df[attrs.type].model;
}else{
model = df.default.model;
} }
return model; return new model(attrs, options);
}; };
}, },

6
src/dom_components/model/Toolbar.js

@ -0,0 +1,6 @@
define(['backbone','./ToolbarButton'],
function (Backbone, ToolbarButton) {
return Backbone.Collection.extend({model: ToolbarButton});
});

13
src/dom_components/model/ToolbarButton.js

@ -0,0 +1,13 @@
define(['backbone'],
function(Backbone) {
return Backbone.Model.extend({
defaults: {
command: '',
attributes: {},
},
});
});

34
src/dom_components/view/ComponentImageView.js

@ -3,16 +3,16 @@ define(['backbone', './ComponentView'],
return ComponentView.extend({ return ComponentView.extend({
tagName : 'img', tagName: 'img',
events : { events: {
'dblclick' : 'openModal', 'dblclick': 'openModal',
}, },
initialize: function(o){ initialize: function(o){
ComponentView.prototype.initialize.apply(this, arguments); ComponentView.prototype.initialize.apply(this, arguments);
this.listenTo( this.model, 'change:src', this.updateSrc); this.listenTo(this.model, 'change:src', this.updateSrc);
this.listenTo( this.model, 'dblclick active', this.openModal); this.listenTo(this.model, 'dblclick active', this.openModal);
this.classEmpty = this.ppfx + 'plh-image'; this.classEmpty = this.ppfx + 'plh-image';
if(this.config.modal) if(this.config.modal)
@ -40,16 +40,17 @@ define(['backbone', './ComponentView'],
* @param {Object} e Event * @param {Object} e Event
* @private * @private
* */ * */
openModal: function(e){ openModal: function(e) {
var that = this; var em = this.opts.config.em;
if(this.modal && this.am){ var editor = em ? em.get('Editor') : '';
this.modal.setTitle('Select image');
this.modal.setContent(this.am.render(1)); if(editor) {
this.am.setTarget(this.model); editor.runCommand('open-assets', {
this.modal.open(); target: this.model,
this.am.onSelect(function(){ onSelect: function() {
that.modal.close(); editor.Modal.close();
that.am.setTarget(null); editor.AssetManager.setTarget(null);
}
}); });
} }
}, },
@ -58,8 +59,9 @@ define(['backbone', './ComponentView'],
this.updateAttributes(); this.updateAttributes();
this.updateClasses(); this.updateClasses();
var actCls = this.$el.attr('class') || '';
if(!this.model.get('src')) if(!this.model.get('src'))
this.$el.attr('class', this.classEmpty); this.$el.attr('class', (actCls + ' ' + this.classEmpty).trim());
// Avoid strange behaviours while try to drag // Avoid strange behaviours while try to drag
this.$el.attr('onmousedown', 'return false'); this.$el.attr('onmousedown', 'return false');

17
src/dom_components/view/ComponentLinkView.js

@ -1,15 +1,22 @@
define(['backbone', './ComponentView'], /*jshint scripturl:true*/
define(['backbone', './ComponentTextView'],
function (Backbone, ComponentView) { function (Backbone, ComponentView) {
return ComponentView.extend({ return ComponentView.extend({
events: { events: {
'click': 'onClick', 'dblclick': 'enableEditing',
}, },
onClick: function(e) { render: function() {
e.preventDefault(); ComponentView.prototype.render.apply(this, arguments);
},
// I need capturing instead of bubbling as bubbled clicks from other
// children will execute the link event
this.el.addEventListener('click', this.prevDef, true);
return this;
},
}); });
}); });

44
src/dom_components/view/ComponentMapView.js

@ -0,0 +1,44 @@
define(['backbone', './ComponentImageView'],
function (Backbone, ComponentView) {
return ComponentView.extend({
tagName: 'div',
events: {},
initialize: function(o){
ComponentView.prototype.initialize.apply(this, arguments);
this.classEmpty = this.ppfx + 'plh-map';
},
/**
* Update the map on the canvas
* @private
*/
updateSrc: function() {
this.getIframe().src = this.model.get('src');
},
getIframe: function() {
if(!this.iframe){
var ifrm = document.createElement("iframe");
ifrm.src = this.model.get('src');
ifrm.frameBorder = 0;
ifrm.style.height = '100%';
ifrm.style.width = '100%';
ifrm.className = this.ppfx + 'no-pointer';
this.iframe = ifrm;
}
return this.iframe;
},
render: function() {
ComponentView.prototype.render.apply(this, arguments);
this.updateClasses();
this.el.appendChild(this.getIframe());
return this;
},
});
});

6
src/dom_components/view/ComponentTableCellView.js

@ -0,0 +1,6 @@
define(['backbone', './ComponentView'],
function (Backbone, ComponentView) {
return ComponentView.extend({
});
});

6
src/dom_components/view/ComponentTableRowView.js

@ -0,0 +1,6 @@
define(['backbone', './ComponentView'],
function (Backbone, ComponentView) {
return ComponentView.extend({
});
});

9
src/dom_components/view/ComponentTableView.js

@ -0,0 +1,9 @@
define(['backbone', './ComponentView'],
function (Backbone, ComponentView) {
return ComponentView.extend({
events: {},
});
});

5
src/dom_components/view/ComponentTextNodeView.js

@ -0,0 +1,5 @@
define(['backbone'],
function (Backbone) {
return Backbone.View.extend({});
});

55
src/dom_components/view/ComponentTextView.js

@ -4,14 +4,17 @@ define(['backbone', './ComponentView'],
return ComponentView.extend({ return ComponentView.extend({
events: { events: {
'dblclick' : 'enableEditing', 'dblclick': 'enableEditing',
'change': 'parseRender',
}, },
initialize: function(o){ initialize: function(o) {
ComponentView.prototype.initialize.apply(this, arguments); ComponentView.prototype.initialize.apply(this, arguments);
_.bindAll(this,'disableEditing'); _.bindAll(this,'disableEditing');
this.listenTo(this.model, 'focus active', this.enableEditing); this.listenTo(this.model, 'focus active', this.enableEditing);
this.rte = this.config.rte || ''; this.rte = this.config.rte || '';
this.activeRte = null;
this.em = this.config.em;
}, },
/** /**
@ -19,9 +22,11 @@ define(['backbone', './ComponentView'],
* @param {Event} e * @param {Event} e
* @private * @private
* */ * */
enableEditing: function(e){ enableEditing: function(e) {
if(this.rte) if(this.rte) {
this.rte.attach(this); this.activeRte = this.rte.attach(this, this.activeRte);
this.rte.focus(this, this.activeRte);
}
this.toggleEvents(1); this.toggleEvents(1);
}, },
@ -30,11 +35,14 @@ define(['backbone', './ComponentView'],
* @param {Event} * @param {Event}
* @private * @private
* */ * */
disableEditing: function(e){ disableEditing: function(e) {
if(this.rte) if(this.rte) {
this.rte.detach(this); this.rte.detach(this, this.activeRte);
}
if(!this.rte.customRte) {
this.parseRender();
}
this.toggleEvents(); this.toggleEvents();
this.updateContents();
}, },
/** /**
@ -47,11 +55,22 @@ define(['backbone', './ComponentView'],
}, },
/** /**
* Update contents of the element * Parse content and re-render it
* @private * @private
**/ */
updateContents: function(){ parseRender: function(){
this.model.set('content', this.el.innerHTML); var comps = this.model.get('components');
var opts = {silent: true};
// Avoid re-render on reset with silent option
comps.reset(null, opts);
comps.add(this.$el.html(), opts);
this.model.set('content', '');
this.render();
// As the reset was in silent mode I need to notify
// the navigator about the change
comps.trigger('resetNavigator');
}, },
/** /**
@ -60,18 +79,16 @@ define(['backbone', './ComponentView'],
*/ */
toggleEvents: function(enable) { toggleEvents: function(enable) {
var method = enable ? 'on' : 'off'; var method = enable ? 'on' : 'off';
// The ownerDocument is from the frame // The ownerDocument is from the frame
var elDocs = [this.el.ownerDocument, document, this.rte]; var elDocs = [this.el.ownerDocument, document, this.rte];
$(elDocs).off('mousedown', this.disableEditing);
$(elDocs)[method]('mousedown', this.disableEditing); $(elDocs)[method]('mousedown', this.disableEditing);
// Avoid closing edit mode on component click // Avoid closing edit mode on component click
this.$el.off('mousedown', this.disablePropagation);
this.$el[method]('mousedown', this.disablePropagation); this.$el[method]('mousedown', this.disablePropagation);
}, },
render: function() {
this.updateAttributes();
this.updateClasses();
this.el.innerHTML = this.model.get('content');
return this;
},
}); });
}); });

119
src/dom_components/view/ComponentVideoView.js

@ -0,0 +1,119 @@
define(['backbone', './ComponentImageView'],
function (Backbone, ComponentView) {
return ComponentView.extend({
tagName: 'div',
events: {},
initialize: function(o){
ComponentView.prototype.initialize.apply(this, arguments);
this.listenTo(this.model, 'change:loop change:autoplay change:controls change:color', this.updateVideo);
this.listenTo(this.model, 'change:provider', this.updateProvider);
},
/**
* Rerender on update of the provider
* @private
*/
updateProvider: function() {
var prov = this.model.get('provider');
this.el.innerHTML = '';
this.el.appendChild(this.renderByProvider(prov));
},
/**
* Update the source of the video
* @private
*/
updateSrc: function() {
var prov = this.model.get('provider');
var src = this.model.get('src');
switch (prov) {
case 'yt':
src = this.model.getYoutubeSrc();
break;
case 'vi':
src = this.model.getVimeoSrc();
break;
}
this.videoEl.src = src;
},
/**
* Update video parameters
* @private
*/
updateVideo: function() {
var prov = this.model.get('provider');
var videoEl = this.videoEl;
var md = this.model;
switch (prov) {
case 'yt': case 'vi':
this.model.trigger('change:videoId');
break;
default:
videoEl.loop = md.get('loop');
videoEl.autoplay = md.get('autoplay');
videoEl.controls = md.get('controls');
}
},
renderByProvider: function(prov) {
var videoEl;
switch (prov) {
case 'yt':
videoEl = this.renderYoutube();
break;
case 'vi':
videoEl = this.renderVimeo();
break;
default:
videoEl = this.renderSource();
}
this.videoEl = videoEl;
return videoEl;
},
renderSource: function() {
var el = document.createElement('video');
el.src = this.model.get('src');
this.initVideoEl(el);
return el;
},
renderYoutube: function() {
var el = document.createElement('iframe');
el.src = this.model.getYoutubeSrc();
el.frameBorder = 0;
el.setAttribute('allowfullscreen', true);
this.initVideoEl(el);
return el;
},
renderVimeo: function() {
var el = document.createElement('iframe');
el.src = this.model.getVimeoSrc();
el.frameBorder = 0;
el.setAttribute('allowfullscreen', true);
this.initVideoEl(el);
return el;
},
initVideoEl: function(el){
el.className = this.ppfx + 'no-pointer';
el.style.height = '100%';
el.style.width = '100%';
},
render: function() {
ComponentView.prototype.render.apply(this, arguments);
this.updateClasses();
var prov = this.model.get('provider');
this.el.appendChild(this.renderByProvider(prov));
return this;
},
});
});

25
src/dom_components/view/ComponentView.js

@ -12,19 +12,21 @@ define(['backbone', './ComponentsView'],
}, },
initialize: function(opt){ initialize: function(opt){
this.opts = opt || {};
this.config = opt.config || {}; this.config = opt.config || {};
this.pfx = this.config.stylePrefix || ''; this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || ''; this.ppfx = this.config.pStylePrefix || '';
this.components = this.model.get('components'); this.components = this.model.get('components');
this.attr = this.model.get("attributes"); this.attr = this.model.get("attributes");
this.classe = this.attr.class || []; this.classe = this.attr.class || [];
this.listenTo(this.model, 'destroy remove', this.remove); this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:style', this.updateStyle); this.listenTo(this.model, 'change:style', this.updateStyle);
this.listenTo(this.model, 'change:attributes', this.updateAttributes); this.listenTo(this.model, 'change:attributes', this.updateAttributes);
this.listenTo(this.model, 'change:status', this.updateStatus); this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model, 'change:state', this.updateState); this.listenTo(this.model, 'change:state', this.updateState);
this.listenTo(this.model.get('classes'), 'add remove change', this.updateClasses); this.listenTo(this.model.get('classes'), 'add remove change', this.updateClasses);
this.$el.data("model", this.model); this.$el.data('model', this.model);
this.model.view = this;
this.$el.data("collection", this.components); this.$el.data("collection", this.components);
if(this.model.get('classes').length) if(this.model.get('classes').length)
@ -173,13 +175,24 @@ define(['backbone', './ComponentsView'],
event.viewResponse = this; event.viewResponse = this;
}, },
/**
* Prevent default helper
* @param {Event} e
* @private
*/
prevDef: function (e) {
e.preventDefault();
},
render: function() { render: function() {
this.updateAttributes(); this.updateAttributes();
this.updateClasses(); this.updateClasses();
this.$el.html(this.model.get('content')); this.$el.html(this.model.get('content'));
var view = new ComponentsView({ var view = new ComponentsView({
collection: this.components, collection: this.model.get('components'),
config: this.config, config: this.config,
defaultTypes: this.opts.defaultTypes,
componentTypes: this.opts.componentTypes,
}); });
// With childNodes lets avoid wrapping 'div' // With childNodes lets avoid wrapping 'div'

44
src/dom_components/view/ComponentsView.js

@ -4,7 +4,8 @@ function(Backbone, require) {
return Backbone.View.extend({ return Backbone.View.extend({
initialize: function(o) { initialize: function(o) {
this.config = o.config; this.opts = o || {};
this.config = o.config || {};
this.listenTo( this.collection, 'add', this.addTo ); this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render ); this.listenTo( this.collection, 'reset', this.render );
}, },
@ -16,9 +17,13 @@ function(Backbone, require) {
* @return void * @return void
* @private * @private
* */ * */
addTo: function(model){ addTo: function(model) {
var i = this.collection.indexOf(model); var i = this.collection.indexOf(model);
this.addToCollection(model, null, i); this.addToCollection(model, null, i);
if(this.config.em) {
this.config.em.trigger('add:component', model);
}
}, },
/** /**
@ -34,31 +39,24 @@ function(Backbone, require) {
if(!this.compView) if(!this.compView)
this.compView = require('./ComponentView'); this.compView = require('./ComponentView');
var fragment = fragmentEl || null, var fragment = fragmentEl || null,
viewObject = this.compView; viewObject = this.compView;
//console.log('Add to collection', model, 'Index',i);
switch(model.get('type')){ var dt = this.opts.defaultTypes;
case 'text': var ct = this.opts.componentTypes;
if(!this.compViewText)
this.compViewText = require('./ComponentTextView'); var type = model.get('type');
viewObject = this.compViewText; viewObject = dt[type] ? dt[type].view : dt.default.view;
break;
case 'image':
if(!this.compViewImage)
this.compViewImage = require('./ComponentImageView');
viewObject = this.compViewImage;
break;
case 'link':
if(!this.compViewLink)
this.compViewLink = require('./ComponentLinkView');
viewObject = this.compViewLink;
break;
}
var view = new viewObject({ var view = new viewObject({
model : model, model: model,
config : this.config, config: this.config,
defaultTypes: dt,
componentTypes: ct,
}); });
var rendered = view.render().el; var rendered = view.render().el;
if(view.model.get('type') == 'textnode')
rendered = document.createTextNode(view.model.get('content'));
if(fragment){ if(fragment){
fragment.appendChild(rendered); fragment.appendChild(rendered);

37
src/dom_components/view/ToolbarButtonView.js

@ -0,0 +1,37 @@
define(['backbone'],
function (Backbone) {
return Backbone.View.extend({
events: {
'mousedown': 'handleClick',
},
attributes: function () {
return this.model.get('attributes');
},
initialize: function(opts) {
this.editor = opts.config.editor;
},
handleClick: function() {
var command = this.model.get('command');
if (typeof command === 'function') {
command(this.editor);
}
if (typeof command === 'string') {
this.editor.runCommand(command);
}
},
render: function () {
var config = this.editor.getConfig();
this.el.className += ' ' + config.stylePrefix + 'toolbar-item';
return this;
},
});
});

15
src/dom_components/view/ToolbarView.js

@ -0,0 +1,15 @@
define(['backbone', 'Abstract/view/DomainViews', './ToolbarButtonView'],
function (Backbone, DomainViews, ToolbarButtonView) {
return DomainViews.extend({
itemView: ToolbarButtonView,
initialize: function(opts) {
this.config = {editor: opts.editor || ''};
this.listenTo(this.collection, 'reset', this.render);
},
});
});

83
src/domain_abstract/ui/Input.js

@ -0,0 +1,83 @@
define(['backbone', 'text!./templates/input.html'],
function (Backbone, inputTemplate) {
return Backbone.View.extend({
events: {
'change': 'handleChange',
},
template: _.template(inputTemplate),
initialize: function(opts) {
var opt = opts || {};
var ppfx = opt.ppfx || '';
this.target = opt.target || {};
this.inputClass = ppfx + 'field';
this.inputHolderClass = ppfx + 'input-holder';
this.ppfx = ppfx;
this.listenTo(this.model, 'change:value', this.handleModelChange);
},
/**
* Handled when the view is changed
*/
handleChange: function (e) {
e.stopPropagation();
this.setValue(this.getInputEl().value);
},
/**
* Set value to the model
* @param {string} value
* @param {Object} opts
*/
setValue: function(value, opts) {
var opt = opts || {};
var model = this.model;
model.set({
value: value || model.get('defaults')
}, opt);
// Generally I get silent when I need to reflect data to view without
// reupdating the target
if(opt.silent) {
this.handleModelChange();
}
},
/**
* Updates the view when the model is changed
* */
handleModelChange: function() {
this.getInputEl().value = this.model.get('value');
},
/**
* Get the input element
* @return {HTMLElement}
*/
getInputEl: function() {
if(!this.inputEl) {
this.inputEl = $('<input>', {
type: 'text',
class: this.inputCls,
placeholder: this.model.get('defaults')
});
}
return this.inputEl.get(0);
},
render: function() {
var el = this.$el;
el.addClass(this.inputClass);
el.html(this.template({
holderClass: this.inputHolderClass,
ppfx: this.ppfx
}));
el.find('.'+ this.inputHolderClass).html(this.getInputEl());
return this;
}
});
});

74
src/domain_abstract/ui/InputColor.js

@ -0,0 +1,74 @@
define(['backbone', './Input', 'Spectrum', 'text!./templates/inputColor.html'],
function (Backbone, Input, Spectrum, inputTemplate) {
return Input.extend({
template: _.template(inputTemplate),
initialize: function(opts) {
Input.prototype.initialize.apply(this, arguments);
var ppfx = this.ppfx;
this.colorCls = ppfx + 'field-color-picker';
this.inputClass = ppfx + 'field ' + ppfx + 'field-color';
this.colorHolderClass = ppfx + 'field-colorp-c';
this.listenTo(this.model, 'change:value', this.handleModelChange);
},
/**
* Updates the view when the model is changed
* */
handleModelChange: function() {
Input.prototype.handleModelChange.apply(this, arguments);
var value = this.model.get('value');
var colorEl = this.getColorEl();
// If no color selected I will set white for the picker
value = value === 'none' ? '#fff' : value;
colorEl.spectrum('set', value);
colorEl.get(0).style.backgroundColor = value;
},
/**
* Get the color input element
* @return {HTMLElement}
*/
getColorEl: function() {
if(!this.colorEl) {
var model = this.model;
var colorEl = $('<div>', {class: this.colorCls});
var cpStyle = colorEl.get(0).style;
var elToAppend = this.target && this.target.config ? this.target.config.el : '';
colorEl.spectrum({
appendTo: elToAppend || 'body',
maxSelectionSize: 8,
showPalette: true,
showAlpha: true,
chooseText: 'Ok',
cancelText: '⨯',
palette: [],
move: function(color) {
var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
cpStyle.backgroundColor = c;
},
change: function(color) {
var c = color.getAlpha() == 1 ? color.toHexString() : color.toRgbString();
c = c.replace(/ /g,'');
cpStyle.backgroundColor = c;
model.set('value', c);
}
});
this.colorEl = colorEl;
}
return this.colorEl;
},
render: function() {
Input.prototype.render.apply(this, arguments);
this.$el.find('.' + this.colorHolderClass).html(this.getColorEl());
return this;
}
});
});

242
src/domain_abstract/ui/InputNumber.js

@ -0,0 +1,242 @@
define(['backbone', 'text!./templates/inputNumber.html'],
function (Backbone, inputTemplate) {
return Backbone.View.extend({
events: {},
template: _.template(inputTemplate),
initialize: function(opts) {
_.bindAll(this, 'moveIncrement', 'upIncrement');
var ppfx = opts.ppfx || '';
this.ppfx = ppfx;
this.docEl = $(document);
this.inputCls = ppfx + 'input-number';
this.unitCls = ppfx + 'input-unit';
this.events['click .' + ppfx + 'field-arrow-u'] = 'upArrowClick';
this.events['click .' + ppfx + 'field-arrow-d'] = 'downArrowClick';
this.events['mousedown .' + ppfx + 'field-arrows'] = 'downIncrement';
this.events['change .' + this.inputCls] = 'handleChange';
this.events['change .' + this.unitCls] = 'handleUnitChange';
this.listenTo(this.model, 'change:unit change:value', this.handleModelChange);
this.delegateEvents();
},
/**
* Set value to the model
* @param {string} value
* @param {Object} opts
*/
setValue: function(value, opts) {
var opt = opts || {};
var valid = this.validateInputValue(value, {deepCheck: 1});
var validObj = {value: valid.value};
// If found some unit value
if(valid.unit || valid.force) {
validObj.unit = valid.unit;
}
this.model.set(validObj, opt);
// Generally I get silent when I need to reflect data to view without
// reupdating the target
if(opt.silent) {
this.handleModelChange();
}
},
/**
* Handled when the view is changed
*/
handleChange: function (e) {
e.stopPropagation();
this.setValue(this.getInputEl().value);
},
/**
* Handled when the view is changed
*/
handleUnitChange: function (e) {
e.stopPropagation();
var value = this.getUnitEl().value;
this.model.set('unit', value);
},
/**
* Updates the view when the model is changed
* */
handleModelChange: function() {
var m = this.model;
this.getInputEl().value = m.get('value');
var unit = this.getUnitEl();
if (unit) {
unit.value = m.get('unit');
}
},
/**
* Get the input element
* @return {HTMLElement}
*/
getInputEl: function() {
if(!this.inputEl) {
this.inputEl = $('<input>', {
type: 'text',
class: this.inputCls,
placeholder: this.model.get('defaults')
});
}
return this.inputEl.get(0);
},
/**
* Get the unit element
* @return {HTMLElement}
*/
getUnitEl: function() {
if(!this.unitEl) {
var model = this.model;
var units = model.get('units') || [];
if(units.length){
var unitStr = '<select class="' + this.unitCls + '">';
_.each(units, function(unit){
var selected = unit == model.get('unit') ? 'selected': '';
unitStr += '<option ' + selected + ' >' + unit + '</option>';
});
unitStr += '</select>';
this.unitEl = $(unitStr);
}
}
return this.unitEl && this.unitEl.get(0);
},
/**
* Invoked when the up arrow is clicked
* */
upArrowClick: function() {
var value = this.model.get('value');
value = isNaN(value) ? 1 : parseInt(value, 10) + 1;
var valid = this.validateInputValue(value);
this.model.set('value', valid.value);
},
/**
* Invoked when the down arrow is clicked
* */
downArrowClick: function(){
var value = this.model.get('value');
value = isNaN(value) ? 0 : parseInt(value, 10) - 1;
var valid = this.validateInputValue(value);
this.model.set('value', valid.value);
},
/**
* Change easily integer input value with click&drag method
* @param Event
*
* @return void
* */
downIncrement: function(e) {
e.preventDefault();
this.moved = 0;
var value = this.model.get('value');
value = isNaN(value) ? 0 : parseInt(value, 10);
var current = {y: e.pageY, val: value };
this.docEl.mouseup(current, this.upIncrement);
this.docEl.mousemove(current, this.moveIncrement);
},
/** While the increment is clicked, moving the mouse will update input value
* @param Object
*
* @return bool
* */
moveIncrement: function (ev) {
this.moved = 1;
var pos = parseInt(ev.data.val - ev.pageY + ev.data.y, 10);
this.prValue = this.validateInputValue(pos).value;//Math.max(this.min, Math.min(this.max, pos) );
this.model.set('value', this.prValue, {avoidStore: 1});
return false;
},
/**
* Stop moveIncrement method
* @param Object
*
* @return void
* */
upIncrement: function (e) {
this.docEl.off('mouseup', this.upIncrement);
this.docEl.off('mousemove', this.moveIncrement);
if(this.prValue && this.moved)
this.model.set('value', this.prValue - 1, {silent:1}).set('value', this.prValue + 1);
},
/**
* Validate input value
* @param {String} value Raw value
* @param {Object} opts Options
* @return {Object} Validated string
*/
validateInputValue: function(value, opts) {
var force = 0;
var opt = opts || {};
var model = this.model;
var val = value || model.get('defaults');
var units = model.get('units') || [];
var unit = model.get('unit') || (units.length && units[0]) || '';
var max = model.get('max');
var min = model.get('min');
if(opt.deepCheck) {
var fixed = model.get('fixedValues') || [];
if (val) {
// If the value is one of the fixed values I leave it as it is
var regFixed = new RegExp('^' + fixed.join('|'), 'g');
if (fixed.length && regFixed.test(val)) {
val = val.match(regFixed)[0];
unit = '';
force = 1;
} else {
var valCopy = val + '';
val += ''; // Make it suitable for replace
val = parseFloat(val.replace(',', '.'));
val = !isNaN(val) ? val : model.get('defaults');
var uN = valCopy.replace(val, '');
// Check if exists as unit
if(_.indexOf(units, uN) >= 0)
unit = uN;
}
}
}
if(typeof max !== 'undefined')
val = val > max ? max : val;
if(typeof min !== 'undefined')
val = val < min ? min : val;
return {
force: force,
value: val,
unit: unit
};
},
render: function() {
var ppfx = this.ppfx;
this.$el.html(this.template({ppfx: ppfx}));
this.$el.find('.'+ ppfx +'input-holder').html(this.getInputEl());
this.$el.find('.' + ppfx + 'field-units').html(this.getUnitEl());
this.$el.addClass(ppfx + 'field');
return this;
}
});
});

1
src/domain_abstract/ui/templates/input.html

@ -0,0 +1 @@
<span class='<%= holderClass %>'></span>

6
src/domain_abstract/ui/templates/inputColor.html

@ -0,0 +1,6 @@
<div class='<%= ppfx %>input-holder'></div>
<div class="<%= ppfx %>field-colorp">
<div class="<%= ppfx %>field-colorp-c">
<div class="<%= ppfx %>checker-bg"></div>
</div>
</div>

6
src/domain_abstract/ui/templates/inputNumber.html

@ -0,0 +1,6 @@
<span class='<%= ppfx %>input-holder'></span>
<span class='<%= ppfx %>field-units'></span>
<div class="<%= ppfx %>field-arrows">
<div class="<%= ppfx %>field-arrow-u"></div>
<div class="<%= ppfx %>field-arrow-d"></div>
</div>

5
src/domain_abstract/view/DomainViews.js

@ -3,6 +3,7 @@ function(Backbone) {
return Backbone.View.extend({ return Backbone.View.extend({
// Default view
itemView: '', itemView: '',
// Defines the View per type // Defines the View per type
@ -33,8 +34,8 @@ function(Backbone) {
add: function(model, fragment){ add: function(model, fragment){
var frag = fragment || null; var frag = fragment || null;
var itemView = this.itemView; var itemView = this.itemView;
if(this.itemsView){ var typeField = model.get(this.itemType);
var typeField = model.get(this.itemType); if(this.itemsView && this.itemsView[typeField]){
itemView = this.itemsView[typeField]; itemView = this.itemsView[typeField];
} }
var view = new itemView({ var view = new itemView({

105
src/editor/config/config.js

@ -20,15 +20,28 @@ define(function () {
// Width for the editor container // Width for the editor container
width: '100%', width: '100%',
// The css that could only be seen (for instance, inside the code viewer) // CSS that could only be seen (for instance, inside the code viewer)
protectedCss: '*{box-sizing: border-box;}body{margin:0;height:100%}#wrapper{min-height:100%; overflow:auto}', protectedCss: '*{box-sizing: border-box;}body{margin:0;height:auto;background:#fff}#wrapper{min-height:100%; overflow:auto}',
// CSS for the iframe which containing the canvas, useful if you need to custom something inside
// (eg. the style of the selected component)
canvasCss: '',
// Default command // Default command
defaultCommand: 'select-comp', defaultCommand: 'select-comp',
// Show a toolbar when the component is selected
showToolbar: 1,
// Allow script tag importing
allowScripts: 0,
// If true render a select of available devices // If true render a select of available devices
showDevices: 1, showDevices: 1,
// When enabled, on device change media rules won't be created
devicePreviewMode: 0,
// Dom element // Dom element
el: '', el: '',
@ -38,9 +51,6 @@ define(function () {
//Configurations for Canvas //Configurations for Canvas
canvas: {}, canvas: {},
//Configurations for Style Manager
styleManager: {},
//Configurations for Layers //Configurations for Layers
layers: {}, layers: {},
@ -88,6 +98,42 @@ define(function () {
}], }],
}, },
//Configurations for Style Manager
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'],
}],
},
//Configurations for Block Manager //Configurations for Block Manager
blockManager: { blockManager: {
'blocks': [{ 'blocks': [{
@ -170,13 +216,56 @@ define(function () {
id: 'image', id: 'image',
label: 'Image', label: 'Image',
attributes: {class:'gjs-fonts gjs-f-image'}, attributes: {class:'gjs-fonts gjs-f-image'},
content: { type:'image', activeOnRender: 1}, content: {
style: {color: 'black'},
type:'image',
activeOnRender: 1
},
},{ },{
id: 'quo', id: 'quo',
label: 'Quote', label: 'Quote',
content: '<blockquote class="quote">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit</blockquote>', content: '<blockquote class="quote">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore ipsum dolor sit</blockquote>',
attributes: {class:'gjs-fonts gjs-f-quo'} attributes: {class:'fa fa-quote-right'}
}], },{
id: 'link',
label: 'Link',
attributes: {class:'fa fa-link'},
content: {
type:'link',
content:'Link',
style:{color: '#d983a6'}
},
},{
id: 'map',
label: 'Map',
attributes: {class:'fa fa-map-o'},
content: {
type: 'map',
style: {height: '350px'}
},
},{
id: 'video',
label: 'Video',
attributes: {class:'fa fa-youtube-play'},
content: {
type: 'video',
src: 'img/video2.webm',
style: {
height: '350px',
width: '615px',
}
},
}/*,{
id: 'table',
label: 'Table',
attributes: {class:'fa fa-table'},
content: {
type: 'table',
columns: 3,
rows: 5,
style: {height: '150px', width: '100%'}
},
}*/],
}, },
}; };

67
src/editor/main.js

@ -15,6 +15,7 @@
* * [store](#store) * * [store](#store)
* * [load](#load) * * [load](#load)
* * [getContainer](#getcontainer) * * [getContainer](#getcontainer)
* * [refresh](#refresh)
* * [on](#on) * * [on](#on)
* * [trigger](#trigger) * * [trigger](#trigger)
* * [render](#render) * * [render](#render)
@ -26,8 +27,10 @@
* var editor = grapesjs.init({...}); * var editor = grapesjs.init({...});
* ``` * ```
* Available events * Available events
* #run:{commandName} * #add:component - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
* #stop:{commandName} * #canvasScroll - Triggered when the canvas is scrolled
* #run:{commandName} - Triggered when some command is called to run (eg. editor.runCommand('preview'))
* #stop:{commandName} - Triggered when some command is called to stop (eg. editor.stopCommand('preview'))
* #load - When the editor is loaded * #load - When the editor is loaded
* *
* @module Editor * @module Editor
@ -297,32 +300,38 @@ define(function (require){
* Execute command * Execute command
* @param {string} id Command ID * @param {string} id Command ID
* @param {Object} options Custom options * @param {Object} options Custom options
* @return {*} The return is defined by the command
* @example * @example
* editor.runCommand('myCommand', {someValue: 1}); * editor.runCommand('myCommand', {someValue: 1});
*/ */
runCommand: function(id, options) { runCommand: function(id, options) {
var result;
var command = em.get('Commands').get(id); var command = em.get('Commands').get(id);
if(command){ if(command){
command.run(this, this, options); result = command.run(this, this, options);
this.trigger('run:' + id); this.trigger('run:' + id);
} }
return result;
}, },
/** /**
* Stop the command if stop method was provided * Stop the command if stop method was provided
* @param {string} id Command ID * @param {string} id Command ID
* @param {Object} options Custom options * @param {Object} options Custom options
* @return {*} The return is defined by the command
* @example * @example
* editor.stopCommand('myCommand', {someValue: 1}); * editor.stopCommand('myCommand', {someValue: 1});
*/ */
stopCommand: function(id, options) { stopCommand: function(id, options) {
var result;
var command = em.get('Commands').get(id); var command = em.get('Commands').get(id);
if(command){ if(command){
command.stop(this, this, options); result = command.stop(this, this, options);
this.trigger('stop:' + id); this.trigger('stop:' + id);
} }
return result;
}, },
/** /**
@ -342,13 +351,61 @@ define(function (require){
}, },
/** /**
* Returns container element. The one which was indicated as 'container' on init method * Returns container element. The one which was indicated as 'container'
* on init method
* @return {HTMLElement} * @return {HTMLElement}
*/ */
getContainer: function(){ getContainer: function(){
return c.el; return c.el;
}, },
/**
* Update editor dimensions and refresh data useful for positioning of tools
*
* This method could be useful when you update, for example, some position
* of the editor element (eg. canvas, panels, etc.) with CSS, where without
* refresh you'll get misleading position of tools (eg. rich text editor,
* component highlighter, etc.)
*
* @private
*/
refresh: function () {
em.refreshCanvas();
},
/**
* Replace the built-in Rich Text Editor with a custom one.
* @param {Object} obj Custom RTE Interface
* @example
* editor.setCustomRte({
* // Function for enabling custom RTE
* // el is the HTMLElement of the double clicked Text Component
* // rte is the same instance you have returned the first time you call
* // enable(). This is useful if need to check if the RTE is already enabled so
* // ion this case you'll need to return the RTE and the end of the function
* enable: function(el, rte) {
* rte = new MyCustomRte(el, {}); // this depends on the Custom RTE API
* ...
* return rte; // return the RTE instance
* },
*
* // Disable the editor, called for example when you unfocus the Text Component
* disable: function(el, rte) {
* rte.blur(); // this depends on the Custom RTE API
* }
*
* // Called when the Text Component is focused again. If you returned the RTE instance
* // from the enable function, the enable won't be called again instead will call focus,
* // in this case to avoid double binding of the editor
* focus: function (el, rte) {
* rte.focus(); // this depends on the Custom RTE API
* }
* });
*/
setCustomRte: function (obj) {
this.RichTextEditor.customRte = obj;
},
/** /**
* Attach event * Attach event
* @param {string} event Event name * @param {string} event Event name

28
src/editor/model/Editor.js

@ -15,6 +15,7 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
changesCount: 0, changesCount: 0,
storables: [], storables: [],
toLoad: [], toLoad: [],
opened: {},
device: '', device: '',
}, },
@ -311,7 +312,7 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
if(!cmp || !cm) if(!cmp || !cm)
return; return;
var wrp = cmp.getComponent(); var wrp = cmp.getComponents();
return cm.getCode(wrp, 'json'); return cm.getCode(wrp, 'json');
}, },
@ -367,8 +368,10 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
if(!cmp || !cm || !cssc) if(!cmp || !cm || !cssc)
return; return;
var wrp = cmp.getComponent(); var wrp = cmp.getComponent();
return cm.getCode(wrp, 'css', cssc); var protCss = this.config.protectedCss;
return protCss + cm.getCode(wrp, 'css', cssc);
}, },
/** /**
@ -469,5 +472,24 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
this.defaultRunning = 0; this.defaultRunning = 0;
}, },
/**
* Update canvas dimensions and refresh data useful for tools positioning
* @private
*/
refreshCanvas: function () {
this.set('canvasOffset', this.get('Canvas').getOffset());
},
/**
* Clear all selected stuf inside the window, sometimes is useful to call before
* doing some dragging opearation
* @param {Window} win If not passed the current one will be used
* @private
*/
clearSelection: function (win) {
var w = win || window;
w.getSelection().removeAllRanges();
},
}); });
}); });

12
src/editor/view/EditorView.js

@ -14,26 +14,26 @@ function(Backbone){
}, this); }, this);
}, },
render: function(){ render: function() {
var conf = this.conf; var conf = this.conf;
var contEl = $(conf.el || ('body ' + conf.container));
this.$el.empty(); this.$el.empty();
this.$cont = $(conf.el || ('body ' + conf.container));
if(conf.width) if(conf.width)
this.$cont.css('width', conf.width); contEl.css('width', conf.width);
if(conf.height) if(conf.height)
this.$cont.css('height', conf.height); contEl.css('height', conf.height);
// Canvas // Canvas
this.$el.append(this.model.get('Canvas').render()); this.$el.append(this.model.get('Canvas').render());
// Panels // Panels
this.$el.append(this.pn.render()); this.$el.append(this.pn.render());
this.$el.attr('class', this.className); this.$el.attr('class', this.className);
this.$cont.html(this.$el); contEl.addClass(conf.stylePrefix + 'editor-cont');
contEl.html(this.$el);
return this; return this;
} }

4
src/grapesjs/main.js

@ -12,6 +12,8 @@ define(function (require) {
return { return {
editors: editors,
plugins: plugins, plugins: plugins,
/** /**
@ -76,4 +78,4 @@ define(function (require) {
}(); }();
}); });

15
src/navigator/config/config.js

@ -1,10 +1,11 @@
define(function () { define(function () {
return { return {
stylePrefix : 'nv-', stylePrefix: 'nv-',
sortable : true, sortable: 1,
hidable : true, hidable: 1,
containerId : 'navigator', hideTextnode: 1,
itemClass : 'item', containerId: 'navigator',
itemsClass : 'items', itemClass: 'item',
itemsClass: 'items',
}; };
}); });

34
src/navigator/main.js

@ -1,33 +1,23 @@
define(function(require) { define(function(require) {
/**
* @class Navigator
* @param {Object} Collection
* @param {Object} Configurations
* */
function Navigator(collection, c)
{
var config = c,
defaults = require('./config/config'),
ItemsView = require('./view/ItemsView');
// Set default options function Navigator(collection, c) {
var config = c,
defaults = require('./config/config'),
ItemsView = require('./view/ItemsView');
// Set default options
for (var name in defaults) { for (var name in defaults) {
if (!(name in config)) if (!(name in config))
config[name] = defaults[name]; config[name] = defaults[name];
} }
var obj = { var obj = {
collection : collection, collection: collection,
config : config, config: config,
opened: c.opened || {}
}; };
// Check if sort is required this.ItemsView = new ItemsView(obj);
if(config.sortable){
var ItemSort = require('./view/ItemSort');
obj.sorter = new ItemSort({config : config});
}
this.ItemsView = new ItemsView(obj);
} }
Navigator.prototype = { Navigator.prototype = {
@ -37,4 +27,4 @@ define(function(require) {
}; };
return Navigator; return Navigator;
}); });

5
src/navigator/template/item.html

@ -4,8 +4,9 @@
<div class="<%= prefix %>title-c"> <div class="<%= prefix %>title-c">
<div class="<%= prefix %>title <%= addClass %>"> <div class="<%= prefix %>title <%= addClass %>">
<i id="<%= prefix %>caret" class="fa fa-chevron-right"></i> <i id="<%= prefix %>caret" class="fa fa-chevron-right <%= caretCls %>"></i>
<%= title %> <i class="fa fa-pencil <%= editBtnCls %>"></i>
<input class="<%= ppfx %>no-app <%= inputNameCls %>" value="<%= title %>" readonly>
</div> </div>
</div> </div>

330
src/navigator/view/ItemSort.js

@ -1,330 +0,0 @@
define(['backbone'],
function(Backbone) {
/**
* @class ItemSort
* */
return Backbone.View.extend({
initialize: function(o) {
_.bindAll(this,'startMove','onMove','endMove','rollback', 'itemLeft');
this.config = o.config || {};
this.pfx = o.config.stylePrefix;
this.itemClass = '.' + this.pfx + this.config.itemClass;
this.itemsClass = '.' + this.pfx + this.config.itemsClass;
this.setElement('.'+this.pfx+this.config.containerId);
},
/**
* Picking component to move
* @param {Object} Element view
* @param {Object} Event
*
* @return void
* */
startMove: function(eV, e){
this.moved = false;
this.eV = eV;
this.$sel = this.eV.$el;
this.$selParent = this.$sel.closest(this.itemsClass);
// In case the component selected is not draggable
if( !eV.model.get('draggable') )
return;
// Create placeholder if not exists
if(!this.$plh){
var pfx = this.pfx;
this.$plh = $('<div>', {id: pfx + 'placeholder'}).css({'pointer-events':'none'}).hide();
this.$plh.append( $('<div>', {id: pfx + "plh-int", class: pfx + 'insert'} ) );
if(!this.$el.length)
this.$el = $('.'+this.pfx+this.config.containerId);
this.$plh.appendTo(this.$el);
}
this.$plh.data('hide',1);
eV.freeze();
this.$el.on('mousemove',this.onMove);
$(document).on('mouseup',this.endMove);
$(document).on('keypress',this.rollback);
},
/**
* Get children dimensions
* @param {Object} Parent element
*
* @retun {Array}
* */
getChildrenDim: function(el){
var dim = [],
p = el || this.$targetEl.parent(),
oT = this.elT,
oL = this.elL,
ch = p.children('.' + this.pfx + this.config.itemClass);
ch.each(function(){
var $el = $(this),
$elO = $el.offset();
dim.push( [ $elO.top - oT, $elO.left - oL, $el.outerHeight(), $el.outerWidth(), true, this]);
});
return dim;
},
/**
* During move
* @param {Object} Event
*
* @return void
* */
onMove: function(e){
this.moved = true;
if(this.$plh.data('hide')){
this.$plh.show();
this.$plh.data('hide',0);
}
var eO = this.$el.offset();
this.elT = eO.top;
this.elL = eO.left;
this.rY = (e.pageY - this.elT) + this.$el.scrollTop();
this.rX = (e.pageX - this.elL) + this.$el.scrollLeft();
this.inspect(e);
this.updatePosition(this.rX, this.rY);
var actualPos = this.posIndex+':'+this.posMethod;
//If there is a significant changes with the pointer
if(!this.lastPos || (this.lastPos != actualPos)){
this.updatePlaceholderPos(this.posIndex, this.posMethod);
this.lastPos = this.posIndex+':'+this.posMethod;
}
//Working alternative for find taget element
//var $targetEl = this.$selParent.children('.'+this.pfx+this.config.itemClass).eq(this.aIndex);
},
/**
* Search where to put placeholder
* @param int X position of the mouse
* @param int Y position of the mouse
* @retun void
* */
updatePosition: function( posX, posY ){
this.posMethod = "before";
this.posIndex = 0;
var leftLimit = 0, xLimit = 0, dimRight = 0, yLimit = 0, xCenter = 0, yCenter = 0, dimDown = 0, dim = 0;
for(var i = 0; i < this.cDim.length; i++){ //Dim => t,l,h,w
dim = this.cDim[i];
dimDown = dim[0] + dim[2];
yCenter = dim[0] + (dim[2] / 2); //Horizontal center
xCenter = dim[1] + (dim[3] / 2); //Vertical center
dimRight = dim[1] + dim[3];
if( (xLimit && dim[1] > xLimit) || (yLimit && yCenter > yLimit) ||
(leftLimit && dimRight < leftLimit)) //No need with this one if over the limit
continue;
if(!dim[4]){ //If it's not inFlow (like float element)
if( posY < dimDown)
yLimit = dimDown;
if( posX < xCenter){ //If mouse lefter than center
xLimit = xCenter;
this.posMethod = "before";
}else{
leftLimit = xCenter;
this.posMethod = "after";
}
this.posIndex = i;
}else{
this.posIndex = this.aIndex = i;
if( posY < yCenter ){ //If mouse upper than center
this.posMethod = "before"; //Should place helper before
if(posY < dim[0])
this.aIndex = i - 1;
break; //No need to continue under inFlow element
}else
this.posMethod = "after";
}
}
},
/**
* Updates the position of the placeholder
* @param int Index of the nearest child
* @param str Before or after position
* @return void
* */
updatePlaceholderPos: function(index, method){
var marg = 0, t = 0, l = 0, w = 0, h = 0,
un = 'px',
margI = 5,
plh = this.$plh[0];
if( this.cDim[index] ){
var elDim = this.cDim[index];
//If it's like with 'float' style
if(!elDim[4]){
w = 'auto';
h = elDim[2] - (marg * 2) + un;
t = elDim[0] + marg;
l = (method == 'before') ? (elDim[1] - marg) : (elDim[1] + elDim[3] - marg);
}else{
//w = '100%';
w = elDim[3] + un;
//h = elDim[3] + un;
t = (method == 'before') ? (elDim[0] - marg) : (elDim[0] + elDim[2] - marg);
l = elDim[1];
}
}else{
if(this.$targetEl){
var trg = this.$targetEl[0],
$elO = this.$targetEl.offset();
t = $elO.top - this.elT + margI + 17;
l = $elO.left - this.elL + margI * 7;
w = (parseInt(trg.offsetWidth) - margI * 14) + un;
}
}
plh.style.top = t + un;
plh.style.left = l + un;
if(w)
plh.style.width = w;
if(h)
plh.style.height = h;
},
/**
* Leave item
* @param event
*
* @return void
* */
endMove: function(e){
this.$el.off('mousemove',this.onMove);
$(document).off('mouseup', this.endMove);
$(document).off('keypress', this.rollback);
this.eV.unfreeze();
this.$plh.hide();
if(this.moved)
this.move(this.$targetEl, this.$sel, this.posIndex, this.posMethod);
this.itemLeft();
},
/**
* Move component to new position
* @param {Object} Component to move
* @param {Object} Target component
* @param {Integer} Indicates the position inside the collection
* @param {String} Before of after component
*
* @return void
* */
move: function(target, el, posIndex, method){
var trg = target|| this.$targetEl;
trg = trg || this.$backupEl;
if(!trg)
return;
var index = posIndex || 0;
var model = el.data("model");
var collection = model.collection;
var targetModel = trg.data('model');
var targetCollection = targetModel.collection;
if(!this.cDim.length)
targetCollection = targetModel.get('components');
if(targetCollection && targetModel.get('droppable')){
index = method == 'after' ? index + 1 : index;
var modelTemp = targetCollection.add({style:{}}, { at: index});
var modelRemoved = collection.remove(model, { silent:false });
targetCollection.add(modelRemoved, { at: index, silent:false });
targetCollection.remove(modelTemp);
}else
console.warn("Invalid target position");
},
/**
* Track inside which element pointer entered
* @param event
*
* @return void
* */
inspect: function(e){
var item = $(e.target).closest(this.itemClass);
if(!this.$targetEl || (item.length && item[0] != this.$targetEl[0]) ){
this.status = 1;
if(item.length){
this.$targetEl = this.$backupEl = item;
this.$targetElP = this.$targetEl.parent();
this.$targetsEl = this.$targetEl.find(this.itemsClass + ':first');
this.$targetEl.on('mouseleave', this.itemLeft);
this.targetM = this.$targetEl.data('model');
this.dimT = this.getTargetDim(this.$targetEl[0]);
this.cDim = this.getChildrenDim();
}
}else if( this.nearToBorders(this.$targetEl[0]) || this.$targetEl[0] == this.$sel[0] ){
if(this.status == 1){
this.status = 2;
this.lastPos = null;
this.cDim = this.getChildrenDim(this.$targetElP);
}
}else if( !this.nearToBorders(this.$targetEl[0]) ){
if(this.status == 2){
this.status = 1;
this.lastPos = null;
}
this.cDim = [];
}
},
/**
* Triggered when pointer leaves item
* @param event
*
* @return void
* */
itemLeft: function(e){
if(this.$targetEl){
this.$targetEl.off('mouseleave',this.itemLeft);
this.$targetEl = null;
}
},
/**
* Returns dimension of the target
* @param Event
*
* @return Array
* */
getTargetDim: function(e){
var $el = $(e),
$elO = $el.offset();
return [ $elO.top - this.elT, $elO.left - this.elL, $el.outerHeight(), $el.outerWidth() ];
},
/**
* Check if pointer is near to the borders of the target
* @param event
* @return Bool
* */
nearToBorders: function(e){
var m = 10; //Limit in pixels for be near
if(!this.dimT)
return;
var dimT = this.dimT;
if( ((dimT[0] + m) > this.rY) || (this.rY > (dimT[0] + dimT[2] - m)) ||
((dimT[1] + m) > this.rX) || (this.rX > (dimT[1] + dimT[3] - m)) )
return 1;
else
return 0;
},
/**
* Rollback to previous situation
* @param Event
* @param Bool Indicates if rollback in anycase
* @return void
* */
rollback: function(e, force){
var key = e.which || e.keyCode;
if(key == 27 || force){
this.moved = false;
this.endMove();
}
return;
},
});
});

147
src/navigator/view/ItemView.js

@ -8,12 +8,13 @@ define(['backbone', 'text!./../template/item.html','require'],
template: _.template(ItemTemplate), template: _.template(ItemTemplate),
initialize: function(o){ initialize: function(o) {
this.opt = o; this.opt = o;
this.config = o.config; this.config = o.config;
this.em = o.config.em; this.em = o.config.em;
this.sorter = o.sorter || {}; this.ppfx = this.em.get('Config').stylePrefix;
this.pfx = this.config.stylePrefix; this.sorter = o.sorter || {};
this.pfx = this.config.stylePrefix;
if(typeof this.model.get('open') == 'undefined') if(typeof this.model.get('open') == 'undefined')
this.model.set('open',false); this.model.set('open',false);
this.listenTo(this.model.components, 'remove add change reset', this.checkChildren); this.listenTo(this.model.components, 'remove add change reset', this.checkChildren);
@ -21,12 +22,20 @@ define(['backbone', 'text!./../template/item.html','require'],
this.listenTo(this.model, 'change:status', this.updateStatus); this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model, 'change:open', this.updateOpening); this.listenTo(this.model, 'change:open', this.updateOpening);
this.className = this.pfx + 'item no-select'; this.className = this.pfx + 'item no-select';
this.events = {}; this.editBtnCls = this.pfx + 'nav-item-edit';
this.events['click > #'+this.pfx+'btn-eye'] = 'toggleVisibility'; this.inputNameCls = this.ppfx + 'nav-comp-name';
this.events['click .'+this.pfx+'title'] = 'toggleOpening'; this.caretCls = this.ppfx + 'nav-item-caret';
this.$el.data("model", this.model); this.titleCls = this.pfx + 'title';
this.customNameProp = 'custom-name';
this.events = {};
this.events['click > #'+this.pfx+'btn-eye'] = 'toggleVisibility';
this.events['click .' + this.caretCls] = 'toggleOpening';
this.events['click .' + this.titleCls] = 'handleSelect';
this.events['click .' + this.editBtnCls] = 'handleEdit';
this.events['blur .' + this.inputNameCls] = 'handleEditEnd';
//TODO listen this.$el.data('model', this.model);
this.$el.data('collection', this.model.get('components'));
if(o.config.sortable) if(o.config.sortable)
this.events['mousedown > #'+this.pfx+'move'] = 'startSort'; this.events['mousedown > #'+this.pfx+'move'] = 'startSort';
@ -34,18 +43,53 @@ define(['backbone', 'text!./../template/item.html','require'],
this.delegateEvents(); this.delegateEvents();
}, },
/**
* Handle the edit of the component name
*/
handleEdit: function(e) {
e.stopPropagation();
var inputName = this.getInputName();
inputName.readOnly = false;
inputName.focus();
},
/**
* Handle with the end of editing of the component name
*/
handleEditEnd: function (e) {
e.stopPropagation();
var inputName = this.getInputName();
inputName.readOnly = true;
this.model.set(this.customNameProp, inputName.value);
},
/**
* Get the input containing the name of the component
* @return {HTMLElement}
*/
getInputName: function () {
if(!this.inputName) {
this.inputName = this.el.querySelector('.' + this.inputNameCls);
}
return this.inputName;
},
/** /**
* Update item opening * Update item opening
* *
* @return void * @return void
* */ * */
updateOpening: function (){ updateOpening: function (){
if(this.model.get('open')){ var opened = this.opt.opened || {};
var model = this.model;
if(model.get('open')){
this.$el.addClass("open"); this.$el.addClass("open");
this.$caret.addClass('fa-chevron-down'); this.$caret.addClass('fa-chevron-down');
opened[model.cid] = model;
}else{ }else{
this.$el.removeClass("open"); this.$el.removeClass("open");
this.$caret.removeClass('fa-chevron-down'); this.$caret.removeClass('fa-chevron-down');
delete opened[model.cid];
} }
}, },
@ -58,19 +102,28 @@ define(['backbone', 'text!./../template/item.html','require'],
toggleOpening: function(e){ toggleOpening: function(e){
e.stopPropagation(); e.stopPropagation();
if(!this.model.components.length)
return;
this.model.set('open', !this.model.get('open') );
},
/**
* Handle component selection
* @return {[type]} [description]
*/
handleSelect: function (e) {
e.stopPropagation();
// Selection // Selection
if(this.em){ if(this.em){
var md = this.em.get('selectedComponent'); var md = this.em.get('selectedComponent');
if(md){ if(md){
md.set('status',''); md.set('status','');
this.model.set('status','selected'); this.model.set('status','selected');
this.em.set('selectedComponent', this.model); this.em.set('selectedComponent', this.model);
} }
} }
if(!this.model.components.length)
return;
this.model.set('open', !this.model.get('open') );
}, },
/** /**
@ -78,8 +131,10 @@ define(['backbone', 'text!./../template/item.html','require'],
* @param Event * @param Event
* */ * */
startSort: function(e){ startSort: function(e){
if(this.sorter) if (this.sorter) {
this.sorter.startMove(this, e); //this.sorter.startMove(this, e);
this.sorter.startSort(e.target);
}
}, },
/** /**
@ -161,7 +216,7 @@ define(['backbone', 'text!./../template/item.html','require'],
* @return void * @return void
* */ * */
checkChildren: function(){ checkChildren: function(){
var c = this.model.components.length, var c = this.countChildren(this.model),
pfx = this.pfx, pfx = this.pfx,
tC = '> .' + pfx + 'title-c > .' + pfx + 'title'; tC = '> .' + pfx + 'title-c > .' + pfx + 'title';
if(!this.$counter) if(!this.$counter)
@ -176,24 +231,50 @@ define(['backbone', 'text!./../template/item.html','require'],
} }
}, },
render : function(){ /**
var pfx = this.pfx, * Count children inside model
vis = this.isVisible(); * @param {Object} model
* @return {number}
* @private
*/
countChildren: function(model){
var count = 0;
model.components.each(function(m){
var isCountable = this.opt.isCountable;
var hide = this.config.hideTextnode;
if(isCountable && !isCountable(m, hide))
return;
count++;
}, this);
return count;
},
render: function(){
var pfx = this.pfx;
var vis = this.isVisible();
var count = this.countChildren(this.model);
this.$el.html( this.template({ this.$el.html( this.template({
title : this.model.getName(), title: this.model.get(this.customNameProp) || this.model.getName(),
addClass : (this.model.components.length ? '' : pfx+'no-chld'), addClass: (count ? '' : pfx+'no-chld'),
count : this.model.components.length, editBtnCls: this.editBtnCls,
visible : vis, inputNameCls: this.inputNameCls,
hidable : this.config.hidable, caretCls: this.caretCls,
prefix : pfx count: count,
visible: vis,
hidable: this.config.hidable,
prefix: pfx,
ppfx: this.ppfx
})); }));
if(typeof ItemsView == 'undefined') if(typeof ItemsView == 'undefined')
ItemsView = require('./ItemsView'); ItemsView = require('./ItemsView');
this.$components = new ItemsView({ this.$components = new ItemsView({
collection : this.model.components, collection : this.model.components,
config : this.config, config: this.config,
sorter : this.sorter, sorter: this.sorter,
parent : this.model opened: this.opt.opened,
parent: this.model
}).render().$el; }).render().$el;
this.$el.find('.'+ pfx +'children').html(this.$components); this.$el.find('.'+ pfx +'children').html(this.$components);
this.$caret = this.$el.find('> .' + pfx + 'title-c > .' + pfx + 'title > #' + pfx + 'caret'); this.$caret = this.$el.find('> .' + pfx + 'title-c > .' + pfx + 'title > #' + pfx + 'caret');

62
src/navigator/view/ItemsView.js

@ -6,18 +6,34 @@ define(['backbone','./ItemView'],
return Backbone.View.extend({ return Backbone.View.extend({
initialize: function(o) { initialize: function(o) {
this.opt = o; this.opt = o;
this.config = o.config; this.config = o.config;
this.preview = o.preview; this.preview = o.preview;
this.sorter = o.sorter || {}; this.pfx = o.config.stylePrefix;
this.pfx = o.config.stylePrefix; this.parent = o.parent;
this.parent = o.parent; this.listenTo(this.collection, 'add', this.addTo);
this.listenTo( this.collection, 'add', this.addTo ); this.listenTo(this.collection, 'reset resetNavigator', this.render);
this.listenTo( this.collection, 'reset', this.render );
this.className = this.pfx + 'items'; this.className = this.pfx + 'items';
if(this.config.sortable && !this.opt.sorter){
var pfx = this.pfx;
var utils = this.config.em.get('Utils');
this.opt.sorter = new utils.Sorter({
container: this.el,
containerSel: '.' + pfx + 'items',
itemSel: '.' + pfx + 'item',
pfx: pfx,
nested: 1
});
}
this.sorter = this.opt.sorter || '';
if(!this.parent) if(!this.parent)
this.className += ' ' + this.pfx + this.config.containerId; this.className += ' ' + this.pfx + this.config.containerId;
// For the sorter
this.$el.data('collection', this.collection);
}, },
/** /**
@ -43,10 +59,12 @@ define(['backbone','./ItemView'],
var fragment = fragmentEl || null; var fragment = fragmentEl || null;
var viewObject = ItemView; var viewObject = ItemView;
var view = new viewObject({ var view = new viewObject({
model : model, model: model,
config : this.config, config: this.config,
sorter : this.sorter, sorter: this.sorter,
isCountable: this.isCountable,
opened: this.opt.opened,
}); });
var rendered = view.render().el; var rendered = view.render().el;
@ -73,13 +91,29 @@ define(['backbone','./ItemView'],
return rendered; return rendered;
}, },
/**
* Check if the model could be count by the navigator
* @param {Object} model
* @return {Boolean}
* @private
*/
isCountable: function(model, hide) {
var type = model.get('type');
var tag = model.get('tagName');
if((type == 'textnode' || tag == 'br') && hide)
return false;
return true;
},
render: function() { render: function() {
var fragment = document.createDocumentFragment(); var fragment = document.createDocumentFragment();
this.$el.empty(); this.$el.empty();
this.collection.each(function(model){ this.collection.each(function(model) {
if(!this.isCountable(model, this.config.hideTextnode))
return;
this.addToCollection(model, fragment); this.addToCollection(model, fragment);
},this); }, this);
this.$el.append(fragment); this.$el.append(fragment);
this.$el.attr('class', _.result(this, 'className')); this.$el.attr('class', _.result(this, 'className'));

18
src/panels/main.js

@ -143,6 +143,22 @@ define(function(require) {
* attributes: { title: 'Some title'}, * attributes: { title: 'Some title'},
* active: false, * active: false,
* }); * });
* // It's also possible to pass the command as an object
* // with .run and .stop methods
* ...
* command: {
* run: function(editor) {
* ...
* },
* stop: function(editor) {
* ...
* }
* },
* // Or simply like a function which will be evaluated as a single .run command
* ...
* command: function(editor) {
* ...
* }
*/ */
addButton: function(panelId, button){ addButton: function(panelId, button){
var pn = this.getPanel(panelId); var pn = this.getPanel(panelId);
@ -191,4 +207,4 @@ define(function(require) {
}; };
}; };
}); });

14
src/panels/view/ButtonView.js

@ -209,8 +209,14 @@ function(Backbone, require) {
var command = null; var command = null;
var editor = this.em && this.em.get ? this.em.get('Editor') : null; var editor = this.em && this.em.get ? this.em.get('Editor') : null;
var commandName = this.model.get('command'); var commandName = this.model.get('command');
if(this.commands)
if (this.commands && typeof commandName === 'string') {
command = this.commands.get(commandName); command = this.commands.get(commandName);
} else if (commandName !== null && typeof commandName === 'object') {
command = commandName;
} else if (typeof commandName === 'function') {
command = {run: commandName};
}
if(this.model.get('active')){ if(this.model.get('active')){
@ -220,7 +226,7 @@ function(Backbone, require) {
if(this.parentM) if(this.parentM)
this.parentM.set('active', true, { silent: true }).trigger('checkActive'); this.parentM.set('active', true, { silent: true }).trigger('checkActive');
if(command){ if(command && command.run){
command.run(editor, this.model, this.model.get('options')); command.run(editor, this.model, this.model.get('options'));
editor.trigger('run:' + commandName); editor.trigger('run:' + commandName);
} }
@ -232,7 +238,7 @@ function(Backbone, require) {
if(this.parentM) if(this.parentM)
this.parentM.set('active', false, { silent: true }).trigger('checkActive'); this.parentM.set('active', false, { silent: true }).trigger('checkActive');
if(command){ if(command && command.stop){
command.stop(editor, this.model, this.model.get('options')); command.stop(editor, this.model, this.model.get('options'));
editor.trigger('stop:' + commandName); editor.trigger('stop:' + commandName);
} }
@ -315,4 +321,4 @@ function(Backbone, require) {
}, },
}); });
}); });

2
src/parser/config/config.js

@ -4,4 +4,4 @@ define(function () {
textTags: ['br', 'b', 'i', 'u'], textTags: ['br', 'b', 'i', 'u'],
}; };
}); });

5
src/parser/main.js

@ -9,6 +9,8 @@ define(function(require) {
return { return {
compTypes: '',
/** /**
* Name of the module * Name of the module
* @type {String} * @type {String}
@ -48,6 +50,7 @@ define(function(require) {
* @return {Object} * @return {Object}
*/ */
parseHtml: function(str){ parseHtml: function(str){
pHtml.compTypes = this.compTypes;
return pHtml.parse(str, pCss); return pHtml.parse(str, pCss);
}, },
@ -60,4 +63,4 @@ define(function(require) {
return Parser; return Parser;
}); });

2
src/parser/model/ParserCss.js

@ -119,4 +119,4 @@ define(function(require) {
}; };
}); });

108
src/parser/model/ParserHtml.js

@ -4,9 +4,12 @@ define(function(require) {
var TEXT_NODE = 'span'; var TEXT_NODE = 'span';
var c = config; var c = config;
var modelAttrStart = 'data-gjs-';
return { return {
compTypes: '',
/** /**
* Parse style string to object * Parse style string to object
* @param {string} str * @param {string} str
@ -43,7 +46,8 @@ define(function(require) {
var cls = str.split(' '); var cls = str.split(' ');
for (var i = 0, len = cls.length; i < len; i++) { for (var i = 0, len = cls.length; i < len; i++) {
var cl = cls[i].trim(); var cl = cls[i].trim();
if(!cl) var reg = new RegExp('^' + c.pStylePrefix);
if(!cl || reg.test(cl))
continue; continue;
result.push(cl); result.push(cl);
} }
@ -66,7 +70,21 @@ define(function(require) {
var attrsLen = attrs.length; var attrsLen = attrs.length;
var prevI = result.length - 1; var prevI = result.length - 1;
var prevSib = result[prevI]; var prevSib = result[prevI];
model.tagName = node.tagName ? node.tagName.toLowerCase() : ''; var ct = this.compTypes;
if(ct){
var obj = '';
for (var cType in ct) {
var component = ct[cType].model;
obj = component.isComponent(node);
if(obj)
break;
}
model = obj;
}
if(!model.tagName)
model.tagName = node.tagName ? node.tagName.toLowerCase() : '';
if(attrsLen) if(attrsLen)
model.attributes = {}; model.attributes = {};
@ -76,33 +94,41 @@ define(function(require) {
var nodeName = attrs[j].nodeName; var nodeName = attrs[j].nodeName;
var nodeValue = attrs[j].nodeValue; var nodeValue = attrs[j].nodeValue;
//Isolate style, class and src attributes //Isolate few attributes
if(nodeName == 'style') if(nodeName == 'style')
model.style = this.parseStyle(nodeValue); model.style = this.parseStyle(nodeValue);
else if(nodeName == 'class') else if(nodeName == 'class')
model.classes = this.parseClass(nodeValue); model.classes = this.parseClass(nodeValue);
else if(nodeName == 'src' && model.tagName == 'img'){ else if (nodeName == 'contenteditable')
model.type = 'image'; continue;
model.src = nodeValue; else if(nodeName.indexOf(modelAttrStart) === 0){
var modelAttr = nodeName.replace(modelAttrStart, '');
nodeValue = nodeValue === 'true' ? true : nodeValue;
nodeValue = nodeValue === 'false' ? false : nodeValue;
model[modelAttr] = nodeValue;
}else }else
model.attributes[nodeName] = nodeValue; model.attributes[nodeName] = nodeValue;
} }
// Check for nested elements
var nodeChild = node.childNodes.length; var nodeChild = node.childNodes.length;
if(nodeChild){
// Check for nested elements and avoid them if an array
// was already given
if(nodeChild && !model.components){
// Avoid infinite text nodes nesting // Avoid infinite text nodes nesting
var firstChild = node.childNodes[0]; var firstChild = node.childNodes[0];
if(nodeChild === 1 && firstChild.nodeType === 3){ if(nodeChild === 1 && firstChild.nodeType === 3){
model.type = 'text'; if(!model.type){
model.type = 'text';
}
model.content = firstChild.nodeValue; model.content = firstChild.nodeValue;
}else{ }else{
var parsed = this.parseNode(node); var parsed = this.parseNode(node);
// From: <div> <span>TEST</span> </div> <-- span is text type // From: <div> <span>TEST</span> </div> <-- span is text type
// TO: <div> TEST </div> <-- div become text type // TO: <div> TEST </div> <-- div become text type
// With 'nodeChild > 1' I know that nodes were merged
if(parsed.length == 1 && parsed[0].type == 'text' && if(parsed.length == 1 && parsed[0].type == 'text' &&
nodeChild > 1 && parsed[0].tagName == TEXT_NODE){ parsed[0].tagName == TEXT_NODE){
model.type = 'text'; model.type = 'text';
model.content = parsed[0].content; model.content = parsed[0].content;
}else }else
@ -110,38 +136,43 @@ define(function(require) {
} }
} }
var prevIsText = prevSib && prevSib.type == 'text' && prevSib.tagName == TEXT_NODE; // Check if it's a text node and if could be moved to the prevous model
// Find text nodes if(model.type == 'textnode'){
if(!model.tagName && node.nodeType === 3){ var prevIsText = prevSib && prevSib.type == 'textnode';
// Pass content to the previous model if it's a text node
if(prevIsText){ if(prevIsText){
prevSib.content += node.nodeValue; prevSib.content += model.content;
continue; continue;
} }
// Make it text node only the content is not empty // Throw away empty nodes (keep spaces)
if(node.nodeValue.trim()){ var content = node.nodeValue;
model.type = 'text'; if(content != ' ' && !content.trim()){
model.tagName = TEXT_NODE; continue;
model.content = node.nodeValue;
} }
} }
// Check if it's a text node and if it could be moved to the prevous model // If all children are texts and there is some textnode the parent should
if(c.textTags.indexOf(model.tagName) >= 0){ // be text too otherwise I'm unable to edit texnodes
if(prevIsText){ var comps = model.components;
prevSib.content += node.outerHTML; if(!model.type && comps){
continue; var allTxt = 1;
}else{ var foundTextNode = 0;
model = { for(var ci = 0; ci < comps.length; ci++){
type: 'text', var comp = comps[ci];
tagName: TEXT_NODE, if(comp.type != 'text' &&
content: node.outerHTML, comp.type != 'textnode' &&
}; c.textTags.indexOf(comp.tagName) < 0 ){
allTxt = 0;
break;
}
if(comp.type == 'textnode')
foundTextNode = 1;
} }
if(allTxt && foundTextNode)
model.type = 'text';
} }
// If tagName is still empty do not push it // If tagName is still empty and is not a textnode, do not push it
if(!model.tagName) if(!model.tagName && model.type != 'textnode')
continue; continue;
result.push(model); result.push(model);
@ -157,6 +188,7 @@ define(function(require) {
* @return {Object} * @return {Object}
*/ */
parse: function(str, parserCss){ parse: function(str, parserCss){
var config = (c.em && c.em.get('Config')) || {};
var res = { html: '', css: ''}; var res = { html: '', css: ''};
var el = document.createElement('div'); var el = document.createElement('div');
el.innerHTML = str; el.innerHTML = str;
@ -164,8 +196,10 @@ define(function(require) {
var i = scripts.length; var i = scripts.length;
// Remove all scripts // Remove all scripts
while (i--) if(!config.allowScripts){
scripts[i].parentNode.removeChild(scripts[i]); while (i--)
scripts[i].parentNode.removeChild(scripts[i]);
}
// Detach style tags and parse them // Detach style tags and parse them
if(parserCss){ if(parserCss){
@ -197,4 +231,4 @@ define(function(require) {
}; };
}); });

35
src/rich_text_editor/config/config.js

@ -4,24 +4,29 @@ define(function () {
toolbarId : 'toolbar', toolbarId : 'toolbar',
containerId : 'wrapper', containerId : 'wrapper',
commands : [{ commands : [{
command: 'bold', command: 'bold',
title: 'Bold', title: 'Bold',
class: 'fa fa-bold', class: 'fa fa-bold',
},{ },{
command: 'italic', command: 'italic',
title: 'Italic', title: 'Italic',
class: 'fa fa-italic', class: 'fa fa-italic',
},{ },{
command: 'underline', command: 'underline',
title: 'Underline', title: 'Underline',
class: 'fa fa-underline', class: 'fa fa-underline',
},{ },{
command: 'strikethrough', command: 'strikethrough',
title: 'Strikethrough', title: 'Strikethrough',
class: 'fa fa-strikethrough', class: 'fa fa-strikethrough',
group: 'format' group: 'format'
},{
command: 'insertHTML',
title: 'Link',
class: 'fa fa-link',
args: '<a class="link" href="">${content}</a>',
}/*,{ }/*,{
command: 'fontSize', command: 'fontSize',
options: [ options: [
{name: 'Huge', value: '7'}, {name: 'Huge', value: '7'},
{name: 'Normal', value: '5'}, {name: 'Normal', value: '5'},
@ -29,4 +34,4 @@ define(function () {
] ]
}*/], }*/],
}; };
}); });

85
src/rich_text_editor/main.js

@ -30,9 +30,12 @@ define(function(require) {
CommandButtons = require('./model/CommandButtons'), CommandButtons = require('./model/CommandButtons'),
CommandButtonsView = require('./view/CommandButtonsView'); CommandButtonsView = require('./view/CommandButtonsView');
var tlbPfx, toolbar, commands; var tlbPfx, toolbar, commands;
var mainSelf;
return { return {
customRte: null,
/** /**
* Name of the module * Name of the module
* @type {String} * @type {String}
@ -46,6 +49,7 @@ define(function(require) {
* @private * @private
*/ */
init: function(config) { init: function(config) {
mainSelf = this;
c = config || {}; c = config || {};
for (var name in defaults) { for (var name in defaults) {
if (!(name in c)) if (!(name in c))
@ -114,62 +118,97 @@ define(function(require) {
* Triggered when the offset of the editro is changed * Triggered when the offset of the editro is changed
* @private * @private
*/ */
udpatePosition: function(){ udpatePosition: function() {
if(!this.lastEl || !c.em) var u = 'px';
return; var canvas = c.em.get('Canvas');
var u = 'px'; var pos = canvas.getTargetToElementDim(toolbar.el, this.lastEl, {
var eOffset = c.em.get('canvasOffset'); event: 'rteToolbarPosUpdate',
var cvsView = c.em.get('Canvas').getCanvasView(); });
var dims = cvsView.getElementPos(this.lastEl); var toolbarStyle = toolbar.el.style;
var toolS = toolbar.el.style; toolbarStyle.top = pos.top + u;
var toolH = toolbar.$el.outerHeight(); toolbarStyle.left = pos.left + u;
toolS.top = (dims.top - toolH) + u;
toolS.left = (dims.left + eOffset.left) + u;
}, },
/** /**
* Bind rich text editor to the element * Bind rich text editor to the element
* @param {View} view * @param {View} view
* @param {Object} rte The instance of already defined RTE
* @private * @private
* */ * */
attach: function(view){ attach: function(view, rte) {
view.$el.wysiwyg({}).focus();
this.lastEl = view.el; this.lastEl = view.el;
var customRte = this.customRte;
if(c.em){ // If a custom RTE is defined
this.udpatePosition(); if (customRte) {
rte = customRte.enable(view.el, rte);
} else {
view.$el.wysiwyg({}).focus();
}
this.show();
if(c.em) {
setTimeout(this.udpatePosition.bind(this), 0);
c.em.off('change:canvasOffset', this.udpatePosition, this); c.em.off('change:canvasOffset', this.udpatePosition, this);
c.em.on('change:canvasOffset', this.udpatePosition, this); c.em.on('change:canvasOffset', this.udpatePosition, this);
// Update position on scrolling
c.em.off('canvasScroll', this.udpatePosition, this);
c.em.on('canvasScroll', this.udpatePosition, this);
} }
this.show();
//Avoid closing edit mode clicking on toolbar //Avoid closing edit mode clicking on toolbar
toolbar.$el.on('mousedown', this.disableProp); toolbar.$el.on('mousedown', this.disableProp);
return rte;
}, },
/** /**
* Unbind rich text editor from the element * Unbind rich text editor from the element
* @param {View} view * @param {View} view
* @param {Object} rte The instance of already defined RTE
* @private * @private
* */ * */
detach: function(view){ detach: function(view, rte) {
view.$el.wysiwyg('destroy'); var customRte = this.customRte;
if (customRte) {
view.model.set('content', view.el.innerHTML);
customRte.disable(view.el, rte);
} else {
view.$el.wysiwyg('destroy');
}
this.hide(); this.hide();
toolbar.$el.off('mousedown', this.disableProp); toolbar.$el.off('mousedown', this.disableProp);
}, },
/**
* Unbind rich text editor from the element
* @param {View} view
* @param {Object} rte The instance of already defined RTE
* @private
* */
focus: function(view, rte) {
var customRte = this.customRte;
if (customRte) {
customRte.focus(view.el, rte);
} else {
this.attach(view);
}
},
/** /**
* Show the toolbar * Show the toolbar
* @private * @private
* */ * */
show: function(){ show: function() {
toolbar.el.style.display = "block"; var toolbarStyle = toolbar.el.style;
toolbarStyle.display = "block";
}, },
/** /**
* Hide the toolbar * Hide the toolbar
* @private * @private
* */ * */
hide: function(){ hide: function() {
toolbar.el.style.display = "none"; toolbar.el.style.display = "none";
}, },
@ -177,7 +216,7 @@ define(function(require) {
* Isolate the disable propagation method * Isolate the disable propagation method
* @private * @private
* */ * */
disableProp: function(e){ disableProp: function(e) {
e.stopPropagation(); e.stopPropagation();
}, },
@ -202,4 +241,4 @@ define(function(require) {
}; };
}; };
}); });

13
src/rich_text_editor/view/CommandButtonsView.js

@ -38,13 +38,16 @@ define(['backbone','./CommandButtonView', './CommandButtonSelectView'],
viewObj = CommandButtonSelectView; viewObj = CommandButtonSelectView;
break; break;
} }
var args = model.get('args');
var attrs = {
'title': model.get('title'),
'data-edit': model.get('command'),
};
if(args)
attrs['data-args'] = args;
var view = new viewObj({ var view = new viewObj({
model: model, model: model,
attributes: { attributes: attrs,
'title': model.get('title'),
'data-edit': model.get('command'),
},
}, this.config); }, this.config);
var rendered = view.render().el; var rendered = view.render().el;

17
src/rich_text_editor/view/TextEditorView.js

@ -45,6 +45,7 @@ define(['jquery'],
editor.get(0).ownerDocument.execCommand("styleWithCSS", false, true); editor.get(0).ownerDocument.execCommand("styleWithCSS", false, true);
editor.get(0).ownerDocument.execCommand(command, 0, args); editor.get(0).ownerDocument.execCommand(command, 0, args);
updateToolbar(); updateToolbar();
editor.trigger('change');
}, },
/* /*
bindHotkeys = function (hotKeys) { bindHotkeys = function (hotKeys) {
@ -112,7 +113,16 @@ define(['jquery'],
toolbar.find(toolbarBtnSelector).unbind().click(function () { toolbar.find(toolbarBtnSelector).unbind().click(function () {
restoreSelection(); restoreSelection();
//editor.focus(); // cause defocus on selects //editor.focus(); // cause defocus on selects
editor.get(0).ownerDocument.execCommand($(this).data(options.commandRole)); var doc = editor.get(0).ownerDocument;
var el = $(this);
var comm = el.data(options.commandRole);
var args = el.data('args');
if(args){
args = args.replace('${content}', doc.getSelection());
execCommand(comm, args);
}else{
doc.execCommand(comm);
}
saveSelection(); saveSelection();
}); });
toolbar.find('[data-toggle=dropdown]').click(restoreSelection); toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
@ -124,11 +134,9 @@ define(['jquery'],
editor.focus(); editor.focus();
execCommand($(this).data(options.commandRole), newValue); execCommand($(this).data(options.commandRole), newValue);
} }
console.log('change isolated2 ', newValue);
saveSelection(); saveSelection();
}); });
toolbar.find('input[type=text]'+dName,', select'+dName).on('webkitspeechchange change', function () { toolbar.find('input[type=text]'+dName,', select'+dName).on('webkitspeechchange change', function () {
console.log('on changed ', newValue);
var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */ var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
this.value = ''; this.value = '';
restoreSelection(); restoreSelection();
@ -138,10 +146,8 @@ define(['jquery'],
} }
saveSelection(); saveSelection();
}).on('focus', function () { }).on('focus', function () {
console.log('on focus ');
var input = $(this); var input = $(this);
if (!input.data(options.selectionMarker)) { if (!input.data(options.selectionMarker)) {
console.log('i have no ', options.selectionMarker);
markSelection(input, options.selectionColor); markSelection(input, options.selectionColor);
input.focus(); input.focus();
} }
@ -228,4 +234,3 @@ define(['jquery'],
return $; return $;
}); });

10
src/selector_manager/template/classTags.html

@ -2,20 +2,20 @@
<div id="<%= pfx %>label"><%= label %></div> <div id="<%= pfx %>label"><%= label %></div>
<div id="<%= pfx %>status-c"> <div id="<%= pfx %>status-c">
<span id="<%= pfx %>input-c"> <span id="<%= pfx %>input-c">
<div class="<%= pfx %>field <%= ppfx %>select"> <div class="<%= ppfx %>field <%= ppfx %>select">
<span id="<%= pfx %>input-holder"> <span id="<%= ppfx %>input-holder">
<select id="<%= pfx %>states"> <select id="<%= pfx %>states">
<option value=""><%= statesLabel %></option> <option value=""><%= statesLabel %></option>
</select> </select>
</span> </span>
<div class="<%= pfx %>sel-arrow"> <div class="<%= ppfx %>sel-arrow">
<div class="<%= pfx %>d-s-arrow"></div> <div class="<%= ppfx %>d-s-arrow"></div>
</div> </div>
</div> </div>
</span> </span>
</div> </div>
</div> </div>
<div id="<%= pfx %>tags-field"> <div id="<%= pfx %>tags-field" class="<%= ppfx %>field">
<div id="<%= pfx %>tags-c"></div> <div id="<%= pfx %>tags-c"></div>
<input id="<%= pfx %>new" /> <input id="<%= pfx %>new" />
<span id="<%= pfx %>add-tag" class="fa fa-plus"></span> <span id="<%= pfx %>add-tag" class="fa fa-plus"></span>

2
src/selector_manager/view/ClassTagView.js

@ -109,7 +109,7 @@ define(['backbone', 'text!./../template/classTag.html'],
updateInputLabel: function(){ updateInputLabel: function(){
if(!this.$labelInput) if(!this.$labelInput)
this.$labelInput = this.$el.find('input'); this.$labelInput = this.$el.find('input');
var size = this.$labelInput.val().length - 2; var size = this.$labelInput.val().length - 1;
size = size < 1 ? 1 : size; size = size < 1 ? 1 : size;
this.$labelInput.attr('size', size); this.$labelInput.attr('size', size);
}, },

56
src/style_manager/config/config.js

@ -1,55 +1,13 @@
define(function () { define(function () {
return {
stylePrefix: 'sm-', return {
// Default sectors, which could include also properties stylePrefix: 'sm-',
//
// Example:
// sectors: [{
// name: 'Some sector name',
// properties:[{
// name : 'Width',
// property : 'width',
// type : 'integer',
// units : ['px','%'],
// defaults : 'auto',
// min : 0,
// }],
// }]
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'],
}],
// Text to show in case no element selected sectors: [],
textNoElement: 'Select an element before using Style Manager',
// Text to show in case no element selected
textNoElement: 'Select an element before using Style Manager',
}; };
}); });

7
src/style_manager/model/Property.js

@ -4,7 +4,7 @@ define(['backbone', './Layers', 'require'],
return Backbone.Model.extend({ return Backbone.Model.extend({
defaults: { defaults: {
name : '', name: '',
property: '', property: '',
type: '', type: '',
units: [], units: [],
@ -15,10 +15,11 @@ define(['backbone', './Layers', 'require'],
icon: '', icon: '',
preview: false, preview: false,
detached: false, detached: false,
functionName: '', functionName: '',
properties: [], properties: [],
layers: [], layers: [],
list: [], list: [],
fixedValues: ['initial', 'inherit'],
}, },
initialize: function(opt) { initialize: function(opt) {
@ -48,7 +49,7 @@ define(['backbone', './Layers', 'require'],
* @return {string} Value * @return {string} Value
* @private * @private
*/ */
getValue: function(){ getValue: function() {
var result = ''; var result = '';
var type = this.get('type'); var type = this.get('type');

13
src/style_manager/model/PropertyFactory.js

@ -11,6 +11,7 @@ define(['backbone'],
*/ */
build: function(props){ build: function(props){
var objs = []; var objs = [];
var dftFixedValues = ['initial', 'inherit'];
if(typeof props === 'string') if(typeof props === 'string')
props = [props]; props = [props];
@ -27,6 +28,16 @@ define(['backbone'],
break; break;
} }
// Fixed values
switch(prop){
case 'margin-top': case 'margin-right': case 'margin-bottom': case 'margin-left':
case 'padding-top': case 'padding-right': case 'padding-bottom': case 'padding-left':
case 'width': case 'max-width': case 'min-width':
case 'height': case 'max-height': case 'min-height':
obj.fixedValues = ['initial', 'inherit', 'auto'];
break;
}
// Type // Type
switch(prop){ switch(prop){
case 'float': case 'position': case 'float': case 'position':
@ -448,4 +459,4 @@ define(['backbone'],
}; };
}; };
}); });

12
src/style_manager/templates/propertyColor.html

@ -1,7 +1,9 @@
<div class="<%= pfx %>field <%= pfx %>color"> <div class="<%= ppfx %>field <%= ppfx %>field-color">
<span id='<%= pfx %>input-holder'></span> <div id='<%= pfx %>input-holder'></div>
<div class="<%= pfx %>colorp-c"> <div class="<%= ppfx %>field-colorp">
<div class="<%= ppfx %>checker-bg"></div> <div class="<%= ppfx %>field-colorp-c">
<div class="<%= ppfx %>checker-bg"></div>
</div>
</div> </div>
</div> </div>
<div style="clear:both"></div> <div style="clear:both"></div>

4
src/style_manager/templates/propertyInput.html

@ -1,4 +1,4 @@
<div class="<%= pfx %>field <%= pfx %>input"> <div class="<%= ppfx %>field">
<span id='<%= pfx %>input-holder'></span> <span id='<%= pfx %>input-holder'></span>
</div> </div>
<div style="clear:both"></div> <div style="clear:both"></div>

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

Loading…
Cancel
Save