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

34
README.md

@ -2,10 +2,10 @@
[![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/>
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
<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.
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
* 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/>
<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/>
<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/>
<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/>
<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
@ -135,13 +141,6 @@ API References (draft) could be found here: [API-Reference]
$ 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
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
## Sponsors
The project is sponsored by
[![Sendloop](http://grapesjs.com/img/sendloop-logo-l.png)](https://sendloop.com)
## Support
If you like the project support it with a donation of your choice.

2
bower.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.3.20",
"version": "0.4.5",
"author": "Artur Arseniev",
"homepage": "http://grapesjs.com",
"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">
<defs
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
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 500 : 1"
@ -91,6 +105,51 @@
id="glyph5248"
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" />
<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>
</defs>
<sodipodi:namedview
@ -100,21 +159,21 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.1767767"
inkscape:cx="334.86414"
inkscape:cy="521.53582"
inkscape:zoom="0.22415285"
inkscape:cx="631.04328"
inkscape:cy="-1694.6037"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1440"
inkscape:window-height="800"
inkscape:window-x="268"
inkscape:window-y="79"
inkscape:window-maximized="0"
inkscape:window-x="0"
inkscape:window-y="1"
inkscape:window-maximized="1"
guidetolerance="10"
objecttolerance="10"
gridtolerance="10"
gridtolerance="2"
inkscape:snap-bbox="false"
inkscape:bbox-nodes="false"
inkscape:bbox-paths="false"
@ -143,7 +202,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
@ -154,18 +213,18 @@
transform="translate(0,-52.36227)">
<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"
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"
inkscape:connector-curvature="0" />
<path
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"
id="rect4238" />
<path
inkscape:connector-curvature="0"
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" />
<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"
@ -240,7 +299,96 @@
x="-450"
y="-1300" /></flowRegion><flowPara
style="font-size:125px"
id="flowPara4175">Borders: 20px</flowPara><flowPara
id="flowPara4175">Borders: 30px</flowPara><flowPara
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>

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">
<title>GrapesJS</title>
<link rel="stylesheet" href="styles/css/main.css">
<link rel="stylesheet" href="vendor/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="vendor/codemirror/theme/hopscotch.css">
<link rel="stylesheet" href="node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="node_modules/codemirror/theme/hopscotch.css">
<script src="private/ckeditor/ckeditor.js"></script>
</head>
<style>
body, html{ height: 100%; margin: 0;}
@ -26,6 +27,15 @@
<tr> <td>Row31</td> <td>Row32</td> <td>Row33</td> </tr>
</tbody>
</table>
<br/>
<br/>
<table>
<tr> <td>Row11</td> <td>Row12</td> <td>Row13</td> </tr>
<tr> <td>Row21</td> <td>Row22</td> <td>Row23</td> </tr>
<tr> <td>Row31</td> <td>Row32</td> <td><div>Div Content Row33</div></td> </tr>
</table>
<iframe class="iframe" src="http://player.vimeo.com/video/2?&controls=0&loop=1&color=ff0000"></iframe>
<iframe class="iframe" src="https://maps.google.com/maps?&q=London, UK&z=11&t=q&output=embed"></iframe>
-->
<div class="logo-container">
<div class="logo">GrapesJS</div>
@ -780,6 +790,11 @@
margin: 20px 30px;
}
.iframe {
height: 300px;
width: 500px;
}
@media (max-width: 768px){
.foot-form-cont{
width:400px;
@ -797,6 +812,6 @@
</style>
</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>
</html>

22
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Open source Web Template Editor",
"version": "0.3.20",
"version": "0.4.5",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
@ -11,9 +11,20 @@
"url": "https://github.com/artf/grapesjs.git"
},
"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": {
"bower": "^1.7.2",
"chai": "^3.5.0",
"documentation": "^4.0.0-beta2",
"grunt": "^0.4.5",
"grunt-bowercopy": "^1.2.4",
@ -30,8 +41,11 @@
"grunt-mocha": "^0.4.15",
"grunt-sass": "^1.1.0",
"istanbul": "^0.4.2",
"bower": "^1.7.2",
"mocha": "^3.1.2",
"node-sass": "^3.4.2",
"requirejs": "^2.3.2",
"requirejs-text": "^2.0.12",
"sinon": "^1.17.6",
"svg2ttf": "^4.0.1",
"ttf2eot": "^2.0.0",
"ttf2woff": "^2.0.1",
@ -44,6 +58,7 @@
"web template editor",
"web site builder",
"web template builder",
"html website builder",
"site builder",
"newsletter builder",
"wysiwyg",
@ -51,7 +66,6 @@
"editor"
],
"scripts": {
"postinstall": "./node_modules/.bin/bower install --config.interactive=false",
"build": "./node_modules/.bin/grunt build",
"build:fonts": "./node_modules/.bin/grunt build:fonts",
"test": "./node_modules/.bin/grunt test",

7
src/asset_manager/config/config.js

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

21
src/asset_manager/main.js

@ -5,6 +5,8 @@
* * [remove](#remove)
* * [store](#store)
* * [load](#load)
* * [onClick](#onClick)
* * [onDblClick](#onDblClick)
*
* Before using this methods you should get first the module from the editor instance, in this way:
*
@ -16,6 +18,7 @@
* @param {Object} config Configurations
* @param {Array<Object>} [config.assets=[]] Default assets
* @param {String} [config.uploadText='Drop files here or click to upload'] Upload text
* @param {String} [config.addBtnText='Add image'] Text for the add button
* @param {String} [config.upload=''] Where to send upload data. Expects as return a JSON with asset/s object
* as: {data: [{src:'...'}, {src:'...'}]}
* @return {this}
@ -217,6 +220,22 @@ define(function(require) {
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-header">
<form id="login-form" class="<%= pfx %>add-asset">
<input class="<%= ppfx %>input" placeholder="http://path/to/the/image.jpg" />
<button class="<%= ppfx %>btn-prim">Add image</button>
<form class="<%= pfx %>add-asset">
<div class="<%= ppfx %>field <%= pfx %>add-field">
<input placeholder="http://path/to/the/image.jpg"/>
</div>
<button class="<%= ppfx %>btn-prim"><%= btnText %></button>
<div style="clear:both"></div>
</form>
<div class="<%= pfx %>dips" style="display:none">
@ -12,4 +14,4 @@
</div>
<div class="<%= pfx %>assets"></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({
events:{
'click' : 'selected',
'dblclick' : 'chosen',
'click': 'handleClick',
'dblclick': 'handleDblClick',
},
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
* */
selected: function(){
this.model.collection.trigger('deselectAll');
handleClick: function() {
var onClick = this.config.onClick;
var model = this.model;
model.collection.trigger('deselectAll');
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
* */
chosen: function(){
this.updateTarget(this.model.get('src'));
var f = this.model.collection.onSelect;
if(f && typeof f == 'function'){
f(this.model);
handleDblClick: function() {
var onDblClick = this.config.onDblClick;
var model = 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
* */
updateTarget: function(v){
var target = this.model.collection.target;
if(target && target.set){
var attr = _.clone( target.get('attributes') );
var target = this.model.collection.target;
if(target && target.set) {
var attr = _.clone( target.get('attributes') );
target.set('attributes', attr );
target.set('src', v );
}
@ -63,18 +77,18 @@ define(['./AssetView','text!./../template/assetImage.html'],
},
render : function(){
var name = this.model.get('name'),
dim = this.model.get('width') && this.model.get('height') ?
var name = this.model.get('name'),
dim = this.model.get('width') && this.model.get('height') ?
this.model.get('width')+' x '+this.model.get('height') : '';
name = name ? name : this.model.get('src').split("/").pop();
name = name && name.length > 30 ? name.substring(0, 30)+'...' : name;
dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : '';
name = name ? name : this.model.get('src').split("/").pop();
name = name && name.length > 30 ? name.substring(0, 30)+'...' : name;
dim = dim ? dim + (this.model.get('unitDim') ? this.model.get('unitDim') : ' px' ) : '';
this.$el.html( this.template({
name: name,
src: this.model.get('src'),
dim: dim,
pfx: this.pfx,
ppfx: this.ppfx
name: name,
src: this.model.get('src'),
dim: dim,
pfx: this.pfx,
ppfx: this.ppfx
}));
this.$el.attr('class', this.className);
return this;

3
src/asset_manager/view/AssetsView.js

@ -119,8 +119,9 @@ define(['backbone', './AssetView', './AssetImageView', './FileUploader', 'text!.
},this);
this.$el.html(this.template({
pfx: this.pfx,
pfx: this.pfx,
ppfx: this.ppfx,
btnText: this.config.addBtnText,
}));
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(){
this.$el.html( this.template({
title : this.config.uploadText,
uploadId : this.uploadId,
disabled : this.disabled,
pfx : this.pfx
title: this.config.uploadText,
uploadId: this.uploadId,
disabled: this.disabled,
pfx: this.pfx
}) );
this.initDrop();
this.$el.attr('class', this.pfx + 'file-uploader');

14
src/block_manager/view/BlockView.js

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

45
src/block_manager/view/BlocksView.js

@ -4,16 +4,16 @@ function(Backbone, BlockView) {
return Backbone.View.extend({
initialize: function(opts, config) {
_.bindAll(this, 'getSorter', 'onDrag', 'onDrop', 'dragHelper', 'moveHelper');
_.bindAll(this, 'getSorter', 'onDrag', 'onDrop');
this.config = config || {};
this.ppfx = this.config.pStylePrefix || '';
this.listenTo(this.collection, 'add', this.addTo);
this.em = this.config.em;
this.tac = 'test-tac';
this.grabbingCls = this.ppfx + 'grabbing';
if(this.em){
this.config.getSorter = this.getSorter;
this.config.dragHelper = this.dragHelper;
this.canvas = this.em.get('Canvas');
}
},
@ -45,37 +45,17 @@ function(Backbone, BlockView) {
}
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
* @private
*/
onDrag: function(){
this.em.stopDefault();
this.em.get('Canvas').getBody().style.cursor = 'grabbing';
document.body.style.cursor = 'grabbing';
},
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';
//this.em.get('Canvas').getBody().style.cursor = 'grabbing';
//document.body.style.cursor = 'grabbing';
this.em.get('Canvas').getBody().className += ' ' + this.grabbingCls;
document.body.className += ' ' + this.grabbingCls;
},
/**
@ -84,9 +64,12 @@ function(Backbone, BlockView) {
*/
onDrop: function(model){
this.em.runDefault();
this.em.get('Canvas').getBody().style.cursor = '';
document.body.style.cursor = '';
if(model && model.get('activeOnRender')){
var bodyCanvas = this.em.get('Canvas').getBody();
var body = document.body;
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.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;
},
/**
* Returns toolbar element
* @return {HTMLElement}
*/
getToolbarEl: function(){
return CanvasView.toolbarEl;
},
/**
* Render canvas
* */
@ -158,18 +166,72 @@ define(function(require) {
},
/**
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
* @private
*/
offset: function(el){
var rect = el.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
},
* Get the offset of the element
* @param {HTMLElement} el
* @return {Object}
* @private
*/
offset: function(el){
var rect = el.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft
};
},
/**
* 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
@ -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;
this.toolsEl.style.top = '-' + body.scrollTop + u;
this.toolsEl.style.left = '-' + body.scrollLeft + u;
this.em.trigger('canvasScroll');
},
/**
@ -36,16 +37,23 @@ function(Backbone, FrameView) {
renderBody: function(){
var wrap = this.model.get('frame').get('wrapper');
if(wrap){
var ppfx = this.ppfx;
var body = this.frame.$el.contents().find('body');
var cssc = this.config.em.get('CssComposer');
var conf = this.config.em.get('Config');
body.append(wrap.render()).append(cssc.render());
var protCss = conf.protectedCss;
var frameCss = '.' + this.ppfx + 'dashed *{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' +
'.' + this.ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' +
'.' + this.ppfx + 'no-select{user-select: none; -webkit-user-select:none; -moz-user-select: none}'+
'.' + this.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}';
var frameCss = '.' + ppfx + 'dashed :not([contenteditable]) > *{outline: 1px dashed rgba(170,170,170,0.7); outline-offset: -2px}' +
'.' + ppfx + 'comp-selected{outline: 3px solid #3b97e3 !important}' +
'.' + ppfx + 'no-select{user-select: none; -webkit-user-select:none; -moz-user-select: none}'+
'.' + ppfx + 'freezed{opacity: 0.5; pointer-events: none}' +
'.' + ppfx + 'no-pointer{pointer-events: none}' +
'.' + ppfx + 'plh-image{background:#f5f5f5; border:none; height:50px; width:50px; display:block; outline:3px solid #ffca6f; cursor:pointer}' +
'.' + ppfx + 'grabbing{cursor: grabbing; cursor: -webkit-grabbing}' +
'* ::-webkit-scrollbar-track {background: rgba(0, 0, 0, 0.1)}' +
'* ::-webkit-scrollbar-thumb {background: rgba(255, 255, 255, 0.2)}' +
'* ::-webkit-scrollbar {width: 10px}' +
(conf.canvasCss || '');
if(protCss)
body.append('<style>' + frameCss + protCss + '</style>');
this.config.em.trigger('loaded');
@ -88,27 +96,47 @@ function(Backbone, FrameView) {
this.cvsOff = null;
},
/**
* Returns element's data info
* @param {HTMLElement} el
* @return {Object}
* @private
*/
getElementPos: function(el){
if(!this.frmOff)
this.frmOff = this.offset(this.frame.el);
if(!this.cvsOff)
this.cvsOff = this.offset(this.el);
var eo = this.offset(el);
var top = eo.top + this.frmOff.top - this.cvsOff.top;
var left = eo.left + this.frmOff.left - this.cvsOff.left;
return {
top: top,
left: left,
height: el.offsetHeight,
width: el.offsetWidth
};
},
/**
* Return frame offset
* @return {Object}
* @private
*/
getFrameOffset: function () {
if(!this.frmOff)
this.frmOff = this.offset(this.frame.el);
return this.frmOff;
},
/**
* Return canvas offset
* @return {Object}
* @private
*/
getCanvasOffset: function () {
if(!this.cvsOff)
this.cvsOff = this.offset(this.el);
return this.cvsOff;
},
/**
* 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
@ -116,8 +144,8 @@ function(Backbone, FrameView) {
*/
getPosition: function() {
var bEl = this.frame.el.contentDocument.body;
var fo = this.frmOff;
var co = this.cvsOff;
var fo = this.getFrameOffset();
var co = this.getCanvasOffset();
return {
top: fo.top + bEl.scrollTop - co.top,
left: fo.left + bEl.scrollLeft - co.left
@ -134,17 +162,20 @@ function(Backbone, FrameView) {
var frame = this.frame;
frame.el.onload = this.renderBody;
}
this.toolsEl = $('<div>', { id: this.ppfx + 'tools' }).get(0);
this.hlEl = $('<div>', { class: this.ppfx + 'highlighter' }).get(0);
this.badgeEl = $('<div>', {class: this.ppfx + 'badge'}).get(0);
this.placerEl = $('<div>', {class: this.ppfx + 'placeholder'}).get(0);
this.placerIntEl = $('<div>', {class: this.ppfx + 'placeholder-int'}).get(0);
this.ghostEl = $('<div>', {class: this.ppfx + 'ghost'}).get(0);
this.placerEl.appendChild(this.placerIntEl);
this.toolsEl.appendChild(this.hlEl);
this.toolsEl.appendChild(this.badgeEl);
this.toolsEl.appendChild(this.placerEl);
this.toolsEl.appendChild(this.ghostEl);
var ppfx = this.ppfx;
this.toolsEl = $('<div>', { id: ppfx + 'tools' }).get(0);
this.hlEl = $('<div>', { class: ppfx + 'highlighter' }).get(0);
this.badgeEl = $('<div>', {class: ppfx + 'badge'}).get(0);
this.placerEl = $('<div>', {class: ppfx + 'placeholder'}).get(0);
this.placerIntEl = $('<div>', {class: ppfx + 'placeholder-int'}).get(0);
this.ghostEl = $('<div>', {class: ppfx + 'ghost'}).get(0);
this.toolbarEl = $('<div>', {class: ppfx + 'toolbar'}).get(0);
this.placerEl.appendChild(this.placerIntEl);
this.toolsEl.appendChild(this.hlEl);
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);
var rte = this.em.get('rte');

5
src/canvas/view/FrameView.js

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

6
src/code_manager/config/config.js

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

13
src/code_manager/model/CodeMirrorEditor.js

@ -23,11 +23,12 @@ define(['backbone',
init: function(el)
{
this.editor = CodeMirror.fromTextArea(el, {
dragDrop : false,
lineNumbers : this.get('lineNumbers'),
readOnly : this.get('readOnly'),
mode : this.get('codeName'),
theme : this.get('theme'),
dragDrop: false,
lineWrapping: true,
lineNumbers: this.get('lineNumbers'),
readOnly: this.get('readOnly'),
mode: this.get('codeName'),
theme: this.get('theme'),
});
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({
initialize: function(){
initialize: function() {
this.compCls = [];
},
/**
* Get CSS from components
* Get CSS from component
* @param {Model} model
* @return {String}
*/
buildFromComp: function(model) {
var coll = model.get('components') || model,
code = '';
coll.each(function(m){
var css = m.get('style'),
cls = m.get('classes'),
cln = m.get('components');
cls.each(function(m){
this.compCls.push(m.get('name'));
buildFromModel: function (model) {
var code = '';
var style = model.get('style');
var classes = model.get('classes');
// Let's know what classes I've found
if(classes) {
classes.each(function(model){
this.compCls.push(model.get('name'));
}, this);
}
if(css && Object.keys(css).length !== 0){
code += '#' + m.cid + '{';
for(var prop in css){
if(css.hasOwnProperty(prop))
code += prop + ':' + css[prop] + ';';
}
code += '}';
if(style && Object.keys(style).length !== 0) {
code += '#' + model.cid + '{';
for(var prop in style){
if(style.hasOwnProperty(prop))
code += prop + ':' + style[prop] + ';';
}
code += '}';
}
if(cln.length)
code += this.buildFromComp(cln);
return code;
},
/**
* Get CSS from components
* @param {Model} model
* @return {String}
*/
buildFromComp: function(model) {
var coll = model.get('components') || model,
code = '';
coll.each(function(m) {
var cln = m.get('components');
code += this.buildFromModel(m);
if(cln.length){
code += this.buildFromComp(cln);
}
}, this);
@ -46,7 +63,8 @@ define(['backbone'],
/** @inheritdoc */
build: function(model, cssc) {
this.compCls = [];
var code = this.buildFromComp(model);
var code = this.buildFromModel(model);
code += this.buildFromComp(model);
var compCls = this.compCls;
if(cssc){

43
src/code_manager/model/HtmlGenerator.js

@ -6,47 +6,14 @@ define(['backbone'],
return Backbone.Model.extend({
/** @inheritdoc */
build: function(model){
var coll = model.get('components') || model,
code = '';
build: function(model, cssc){
var coll = model.get('components') || model,
code = '';
coll.each(function(m){
var tag = m.get('tagName'), // Tag name
attr = '', // Attributes string
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 + '"' : '';
code += m.toHTML({
cssc: cssc
});
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);
return code;

5
src/code_manager/model/JsonGenerator.js

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

61
src/commands/main.js

@ -98,9 +98,68 @@ define(function(require) {
defaultCommands['open-sm'] = require('./view/OpenStyleManager');
defaultCommands['open-tm'] = require('./view/OpenTraitManager');
defaultCommands['open-blocks'] = require('./view/OpenBlocks');
defaultCommands['open-assets'] = require('./view/OpenAssets');
defaultCommands.fullscreen = require('./view/Fullscreen');
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)
c.model = c.em.get('Canvas');

7
src/commands/view/ExportTemplate.js

@ -7,6 +7,7 @@ define(function() {
run: function(editor, sender) {
this.sender = sender;
this.wrapper = editor.DomComponents.getWrapper();
this.components = editor.DomComponents.getComponents();
this.modal = editor.Modal || null;
this.cm = editor.CodeManager || null;
@ -64,8 +65,8 @@ define(function() {
this.modal.open();
}
var addCss = this.protCss || '';
this.htmlEditor.setContent( this.cm.getCode(this.components, 'html') );
this.cssEditor.setContent( addCss + this.cm.getCode(this.components, 'css', this.cssc));
this.htmlEditor.setContent(this.cm.getCode(this.components, 'html', this.cssc));
this.cssEditor.setContent(addCss + this.cm.getCode(this.wrapper, 'css', this.cssc));
if(this.sender)
this.sender.set('active',false);
@ -73,4 +74,4 @@ define(function() {
stop: function(){}
};
});
});

32
src/commands/view/MoveComponent.js

@ -12,7 +12,7 @@ define(['backbone', './SelectComponent','./SelectPosition'],
this.noSelClass = this.ppfx + 'no-select';
},
enable: function(){
enable: function() {
SelectComponent.enable.apply(this, arguments);
this.getBadgeEl().addClass(this.badgeClass);
this.getHighlighterEl().addClass(this.hoverClass);
@ -37,17 +37,43 @@ define(['backbone', './SelectComponent','./SelectPosition'],
* */
initSorter: function(e){
var el = $(e.target).data('model');
if(!el.get('draggable'))
var drag = el.get('draggable');
if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null;
this.startSelectPosition(e.target, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMove.bind(this);
this.stopSelectComponent();
this.$wrapper.off('mousedown', this.initSorter);
this.getContentWindow().on('keydown', this.rollback);
},
/**
* Init sorter from model
* @param {Object} model
* @private
*/
initSorterFromModel: function(model) {
var drag = model.get('draggable');
if(!drag)
return;
// Avoid badge showing on move
this.cacheEl = null;
var el = model.view.el;
this.startSelectPosition(el, this.frameEl.contentDocument);
this.sorter.draggable = drag;
this.sorter.onEndMove = this.onEndMoveFromModel.bind(this);
this.stopSelectComponent();
this.getContentWindow().on('keydown', this.rollback);
},
onEndMoveFromModel: function() {
this.getContentWindow().off('keydown', this.rollback);
},
/**
* Callback after sorting
* @private
@ -110,4 +136,4 @@ define(['backbone', './SelectComponent','./SelectPosition'],
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.em = em.editor;
config.layers.opened = em.editor.get('opened');
var layers = new Layers(collection, config.layers);
this.$layers = layers.render();
@ -35,4 +36,4 @@ define(['Navigator'], function(Layers) {
this.$layers.hide();
}
};
});
});

20
src/commands/view/Preview.js

@ -8,9 +8,16 @@ define(function() {
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) {
if(sender)
sender.set('active',false);
if(sender && sender.set)
sender.set('active', false);
editor.stopCommand('sw-visibility');
var that = this;
var panels = this.getPanels(editor);
@ -26,6 +33,12 @@ define(function() {
};
}
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';
var canvasS = canvas.style;
@ -49,6 +62,7 @@ define(function() {
this.helper.style.display = 'none';
}
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
* @private
* */
var ToolbarView = require('DomComponents/view/ToolbarView');
var Toolbar = require('DomComponents/model/Toolbar');
return {
init: function(o){
@ -16,6 +19,9 @@ define(function() {
var config = this.config.em.get('Config');
this.startSelectComponent();
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 comp = this.editorModel.get('selectedComponent');
var focused = this.frameEl.contentDocument.activeElement.tagName !== 'BODY';
// On CANC (46) or Backspace (8)
if(key == 8 || key == 46) {
if(!focused)
e.preventDefault();
if(comp && !focused){
if(comp && !focused) {
if(!comp.get('removable'))
return;
return;
comp.set('status','');
comp.destroy();
this.hideBadge();
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);
this.updateBadge(trg, pos);
// Not mirrored
this.updateHighlighter(trg, pos);
},
@ -137,8 +147,14 @@ define(function() {
onOut: function(e) {
e.stopPropagation();
this.hideBadge();
if(this.hl)
this.hl.css({ left: -10000, top:-10000 });
this.hideHighlighter();
},
/**
* Hide Highlighter element
*/
hideHighlighter: function () {
this.canvas.getHighlighter().style.display = 'none';
},
/**
@ -188,15 +204,15 @@ define(function() {
* @param {Object} pos Position object
* @private
*/
updateHighlighter: function(el, pos){
if(!this.hl)
this.hl = $(this.canvas.getHighlighter());
this.hl.css({
left: pos.left,
top: pos.top,
height: pos.height,
width: pos.width
});
updateHighlighter: function(el, pos) {
var hlEl = this.canvas.getHighlighter();
var hlStyle = hlEl.style;
var unit = 'px';
hlStyle.left = pos.left + unit;
hlStyle.top = pos.top + unit;
hlStyle.height = pos.height + unit;
hlStyle.width = pos.width + unit;
hlStyle.display = 'block';
},
/**
@ -212,11 +228,97 @@ define(function() {
var $el = $(el);
var nMd = $el.data('model');
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);
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
* @private
@ -241,8 +343,16 @@ define(function() {
*/
onFrameScroll: function(e){
var el = this.cacheEl;
if(el)
this.updateBadge(el, this.getElementPos(el));
if(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;
},
run: function(em, sender) {
run: function(em) {
if(em && em.get)
this.editor = em.get('Editor');
this.enable();
},
@ -297,6 +409,9 @@ define(function() {
this.em.set('selectedComponent', null);
this.toggleClipboard();
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: {
jquery: '../vendor/jquery/jquery',
underscore: '../vendor/underscore/underscore',
backbone: '../vendor/backbone/backbone',
backboneUndo: '../vendor/backbone-undo/backbone-undo',
keymaster: '../vendor/keymaster/keymaster',
text: '../vendor/require-text/text',
Spectrum: '../vendor/spectrum/spectrum',
codemirror: '../vendor/codemirror',
formatting: '../vendor/codemirror-formatting/formatting',
jquery: '../node_modules/jquery/dist/jquery',
underscore: '../node_modules/underscore/underscore',
backbone: '../node_modules/backbone/backbone',
backboneUndo: '../node_modules/backbone-undo/Backbone.Undo',
keymaster: '../node_modules/keymaster/keymaster',
text: '../node_modules/requirejs-text/text',
Spectrum: '../node_modules/spectrum-colorpicker/spectrum',
codemirror: '../node_modules/codemirror',
formatting: '../node_modules/codemirror-formatting/formatting',
},
packages : [

2
src/css_composer/main.js

@ -236,7 +236,7 @@ define(function(require) {
var selec = sm.add(sels[j]);
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 || {});
result.push(model);
}

45
src/demo.js

@ -11,25 +11,33 @@ require(['config/require-config'], function() {
fromElement: true,
/*
components: [{
type: 'text',
style:{
width:'100px',
height:'100px'
height:'100px',
margin: '50px auto',
},
traits: ['title']
},{
style:{
width:'150px',
height:'100px'
},
traits: [{name:'title', value: "myTitleTest"}]
},{
type: 'image'
},{
type: 'link',
content: 'mylink',
}],
*/
storageManager:{ autoload: 0},
traits: ['title'],
components: [{
type: 'textnode',
content: 'text node row',
},{
type: 'textnode',
content: ', another text node',
},{
type: 'link',
content: 'someLink',
},{
type: 'textnode',
content: " More text node --- ",
}],
}],*/
storageManager:{
autoload: 0,
storeComponents: 1,
storeStyles: 1,
},
commands: {
defaults : [{
id: 'open-github',
@ -59,7 +67,7 @@ require(['config/require-config'], function() {
}
}],
},
/*
assetManager: {
storageType : '',
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},
]
},
*/
styleManager : {
sectors: [{
@ -319,6 +327,7 @@ require(['config/require-config'], function() {
);
window.editor = editor;
});

19
src/dom_components/config/config.js

@ -1,16 +1,17 @@
define(function () {
return {
stylePrefix : 'comp-',
stylePrefix: 'comp-',
wrapperId : 'wrapper',
wrapperId: 'wrapper',
// Default wrapper configuration
wrapper : {
removable : false,
copyable : false,
stylable : ['background','background-color','background-image', 'background-repeat','background-attachment','background-position'],
draggable : false,
badgable : false,
wrapper: {
//classes: ['body'],
removable: false,
copyable: false,
stylable: ['background','background-color','background-image', 'background-repeat','background-attachment','background-position'],
draggable: false,
badgable: false,
components: [],
},
@ -28,4 +29,4 @@ define(function () {
// List of void elements
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 (){
var c = {},
defaults = require('./config/config'),
Component = require('./model/Component'),
ComponentText = require('./model/ComponentText'),
ComponentImage = require('./model/ComponentImage'),
ComponentLink = require('./model/ComponentLink'),
ComponentView = require('./view/ComponentView'),
ComponentImageView = require('./view/ComponentImageView'),
ComponentTextView = require('./view/ComponentTextView');
ComponentLinkView = require('./view/ComponentLinkView');
componentTypes = {},
defaults = require('./config/config'),
Component = require('./model/Component'),
ComponentView = require('./view/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 {
componentTypes: defaultTypes,
/**
* Name of the module
* @type {String}
@ -96,14 +136,22 @@ define(function(require) {
c.rte = c.em.get('rte') || '';
c.modal = c.em.get('Modal') || '';
c.am = c.em.get('AssetManager') || '';
c.em.get('Parser').compTypes = defaultTypes;
}
component = new Component(c.wrapper, { sm: c.em, config: c });
component = new Component(c.wrapper, {
sm: c.em,
config: c,
defaultTypes: defaultTypes,
componentTypes: componentTypes,
});
component.set({ attributes: {id: 'wrapper'}});
component.get('components').add(c.components);
componentView = new ComponentView({
model: component,
config: c,
defaultTypes: defaultTypes,
componentTypes: componentTypes,
});
return this;
},
@ -283,6 +331,15 @@ define(function(require) {
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',
type: '',
editable: false,
// True if the component is removable from the canvas
removable: true,
// Indicates if it's possible to drag the component inside other
// Tip: Indicate an array of selectors where it could be dropped inside
draggable: true,
// Indicates if it's possible to drop other components inside
// Tip: Indicate an array of selectors which could be dropped inside
droppable: true,
// Set false if don't want to see the badge (with the name) over the component
badgable: true,
// True if it's possible to style it
// Tip: Indicate an array of css properties is possible to style
stylable: true,
// True if it's possible to clone the component
copyable: true,
// TODO
mirror: '',
void: false,
state: '',
status: '',
@ -20,7 +39,9 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
content: '',
style: {},
attributes: {},
classes: '',
traits: ['id', 'title'],
toolbar: null,
},
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)
this.set('void', true);
this.opt = opt;
this.sm = opt ? opt.sm || {} : {};
this.config = o || {};
this.defaultC = this.config.components || [];
this.defaultCl = this.normalizeClasses(this.config.classes || []);
this.defaultCl = this.normalizeClasses(this.get('classes') || this.config.classes || []);
this.components = new Components(this.defaultC, opt);
this.components.parent = this;
this.set('components', this.components);
this.set('classes', new Selectors(this.defaultCl));
var traits = new Traits();
traits.setTarget(this);
traits.add(this.get('traits'));
this.set('traits', traits);
this.initToolbar();
},
/**
* 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 = '';
return new this.constructor(attr, {sm: this.sm});
attr.view = '';
return new this.constructor(attr, this.opt);
},
/**
* Get name of the component
*
* @return {String}
* @return {string}
* @private
* */
getName: function() {
@ -115,10 +181,79 @@ define(['backbone','./Components', 'SelectorManager/model/Selectors', 'TraitMana
var tag = this.get('tagName');
tag = tag == 'div' ? 'box' : 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 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({
defaults: _.extend({}, Component.prototype.defaults, {
src: '',
droppable: false,
traits: ['alt'],
type: 'image',
tagName: 'img',
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) {
return Component.extend({
defaults: _.extend({}, Component.prototype.defaults, {
type: 'link',
tagName: 'a',
droppable: false,
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({
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)
options.config = opt.config;
switch(attrs.type){
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.defaultTypes)
options.defaultTypes = opt.defaultTypes;
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({
tagName : 'img',
tagName: 'img',
events : {
'dblclick' : 'openModal',
events: {
'dblclick': 'openModal',
},
initialize: function(o){
ComponentView.prototype.initialize.apply(this, arguments);
this.listenTo( this.model, 'change:src', this.updateSrc);
this.listenTo( this.model, 'dblclick active', this.openModal);
this.listenTo(this.model, 'change:src', this.updateSrc);
this.listenTo(this.model, 'dblclick active', this.openModal);
this.classEmpty = this.ppfx + 'plh-image';
if(this.config.modal)
@ -40,16 +40,17 @@ define(['backbone', './ComponentView'],
* @param {Object} e Event
* @private
* */
openModal: function(e){
var that = this;
if(this.modal && this.am){
this.modal.setTitle('Select image');
this.modal.setContent(this.am.render(1));
this.am.setTarget(this.model);
this.modal.open();
this.am.onSelect(function(){
that.modal.close();
that.am.setTarget(null);
openModal: function(e) {
var em = this.opts.config.em;
var editor = em ? em.get('Editor') : '';
if(editor) {
editor.runCommand('open-assets', {
target: this.model,
onSelect: function() {
editor.Modal.close();
editor.AssetManager.setTarget(null);
}
});
}
},
@ -58,8 +59,9 @@ define(['backbone', './ComponentView'],
this.updateAttributes();
this.updateClasses();
var actCls = this.$el.attr('class') || '';
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
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) {
return ComponentView.extend({
events: {
'click': 'onClick',
'dblclick': 'enableEditing',
},
onClick: function(e) {
e.preventDefault();
},
render: function() {
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({
events: {
'dblclick' : 'enableEditing',
'dblclick': 'enableEditing',
'change': 'parseRender',
},
initialize: function(o){
initialize: function(o) {
ComponentView.prototype.initialize.apply(this, arguments);
_.bindAll(this,'disableEditing');
this.listenTo(this.model, 'focus active', this.enableEditing);
this.rte = this.config.rte || '';
this.activeRte = null;
this.em = this.config.em;
},
/**
@ -19,9 +22,11 @@ define(['backbone', './ComponentView'],
* @param {Event} e
* @private
* */
enableEditing: function(e){
if(this.rte)
this.rte.attach(this);
enableEditing: function(e) {
if(this.rte) {
this.activeRte = this.rte.attach(this, this.activeRte);
this.rte.focus(this, this.activeRte);
}
this.toggleEvents(1);
},
@ -30,11 +35,14 @@ define(['backbone', './ComponentView'],
* @param {Event}
* @private
* */
disableEditing: function(e){
if(this.rte)
this.rte.detach(this);
disableEditing: function(e) {
if(this.rte) {
this.rte.detach(this, this.activeRte);
}
if(!this.rte.customRte) {
this.parseRender();
}
this.toggleEvents();
this.updateContents();
},
/**
@ -47,11 +55,22 @@ define(['backbone', './ComponentView'],
},
/**
* Update contents of the element
* Parse content and re-render it
* @private
**/
updateContents: function(){
this.model.set('content', this.el.innerHTML);
*/
parseRender: function(){
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) {
var method = enable ? 'on' : 'off';
// The ownerDocument is from the frame
var elDocs = [this.el.ownerDocument, document, this.rte];
$(elDocs).off('mousedown', this.disableEditing);
$(elDocs)[method]('mousedown', this.disableEditing);
// Avoid closing edit mode on component click
this.$el.off('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){
this.opts = opt || {};
this.config = opt.config || {};
this.pfx = this.config.stylePrefix || '';
this.ppfx = this.config.pStylePrefix || '';
this.components = this.model.get('components');
this.attr = this.model.get("attributes");
this.classe = this.attr.class || [];
this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:style', this.updateStyle);
this.listenTo(this.model, 'destroy remove', this.remove);
this.listenTo(this.model, 'change:style', this.updateStyle);
this.listenTo(this.model, 'change:attributes', this.updateAttributes);
this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model, 'change:state', this.updateState);
this.listenTo(this.model, 'change:status', this.updateStatus);
this.listenTo(this.model, 'change:state', this.updateState);
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);
if(this.model.get('classes').length)
@ -173,13 +175,24 @@ define(['backbone', './ComponentsView'],
event.viewResponse = this;
},
/**
* Prevent default helper
* @param {Event} e
* @private
*/
prevDef: function (e) {
e.preventDefault();
},
render: function() {
this.updateAttributes();
this.updateClasses();
this.$el.html(this.model.get('content'));
var view = new ComponentsView({
collection: this.components,
collection: this.model.get('components'),
config: this.config,
defaultTypes: this.opts.defaultTypes,
componentTypes: this.opts.componentTypes,
});
// With childNodes lets avoid wrapping 'div'

44
src/dom_components/view/ComponentsView.js

@ -4,7 +4,8 @@ function(Backbone, require) {
return Backbone.View.extend({
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, 'reset', this.render );
},
@ -16,9 +17,13 @@ function(Backbone, require) {
* @return void
* @private
* */
addTo: function(model){
addTo: function(model) {
var i = this.collection.indexOf(model);
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)
this.compView = require('./ComponentView');
var fragment = fragmentEl || null,
viewObject = this.compView;
viewObject = this.compView;
//console.log('Add to collection', model, 'Index',i);
switch(model.get('type')){
case 'text':
if(!this.compViewText)
this.compViewText = require('./ComponentTextView');
viewObject = this.compViewText;
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 dt = this.opts.defaultTypes;
var ct = this.opts.componentTypes;
var type = model.get('type');
viewObject = dt[type] ? dt[type].view : dt.default.view;
var view = new viewObject({
model : model,
config : this.config,
var view = new viewObject({
model: model,
config: this.config,
defaultTypes: dt,
componentTypes: ct,
});
var rendered = view.render().el;
if(view.model.get('type') == 'textnode')
rendered = document.createTextNode(view.model.get('content'));
if(fragment){
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({
// Default view
itemView: '',
// Defines the View per type
@ -33,8 +34,8 @@ function(Backbone) {
add: function(model, fragment){
var frag = fragment || null;
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];
}
var view = new itemView({

105
src/editor/config/config.js

@ -20,15 +20,28 @@ define(function () {
// Width for the editor container
width: '100%',
// The 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}',
// CSS that could only be seen (for instance, inside the code viewer)
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
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
showDevices: 1,
// When enabled, on device change media rules won't be created
devicePreviewMode: 0,
// Dom element
el: '',
@ -38,9 +51,6 @@ define(function () {
//Configurations for Canvas
canvas: {},
//Configurations for Style Manager
styleManager: {},
//Configurations for 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
blockManager: {
'blocks': [{
@ -170,13 +216,56 @@ define(function () {
id: 'image',
label: 'Image',
attributes: {class:'gjs-fonts gjs-f-image'},
content: { type:'image', activeOnRender: 1},
content: {
style: {color: 'black'},
type:'image',
activeOnRender: 1
},
},{
id: 'quo',
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>',
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)
* * [load](#load)
* * [getContainer](#getcontainer)
* * [refresh](#refresh)
* * [on](#on)
* * [trigger](#trigger)
* * [render](#render)
@ -26,8 +27,10 @@
* var editor = grapesjs.init({...});
* ```
* Available events
* #run:{commandName}
* #stop:{commandName}
* #add:component - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
* #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
*
* @module Editor
@ -297,32 +300,38 @@ define(function (require){
* Execute command
* @param {string} id Command ID
* @param {Object} options Custom options
* @return {*} The return is defined by the command
* @example
* editor.runCommand('myCommand', {someValue: 1});
*/
runCommand: function(id, options) {
var result;
var command = em.get('Commands').get(id);
if(command){
command.run(this, this, options);
result = command.run(this, this, options);
this.trigger('run:' + id);
}
return result;
},
/**
* Stop the command if stop method was provided
* @param {string} id Command ID
* @param {Object} options Custom options
* @return {*} The return is defined by the command
* @example
* editor.stopCommand('myCommand', {someValue: 1});
*/
stopCommand: function(id, options) {
var result;
var command = em.get('Commands').get(id);
if(command){
command.stop(this, this, options);
result = command.stop(this, this, options);
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}
*/
getContainer: function(){
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
* @param {string} event Event name

28
src/editor/model/Editor.js

@ -15,6 +15,7 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
changesCount: 0,
storables: [],
toLoad: [],
opened: {},
device: '',
},
@ -311,7 +312,7 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
if(!cmp || !cm)
return;
var wrp = cmp.getComponent();
var wrp = cmp.getComponents();
return cm.getCode(wrp, 'json');
},
@ -367,8 +368,10 @@ define(['backbone', 'backboneUndo', 'keymaster', 'Utils', 'StorageManager', 'Dev
if(!cmp || !cm || !cssc)
return;
var wrp = cmp.getComponent();
return cm.getCode(wrp, 'css', cssc);
var wrp = cmp.getComponent();
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;
},
/**
* 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);
},
render: function(){
render: function() {
var conf = this.conf;
var contEl = $(conf.el || ('body ' + conf.container));
this.$el.empty();
this.$cont = $(conf.el || ('body ' + conf.container));
if(conf.width)
this.$cont.css('width', conf.width);
contEl.css('width', conf.width);
if(conf.height)
this.$cont.css('height', conf.height);
contEl.css('height', conf.height);
// Canvas
this.$el.append(this.model.get('Canvas').render());
// Panels
this.$el.append(this.pn.render());
this.$el.attr('class', this.className);
this.$cont.html(this.$el);
contEl.addClass(conf.stylePrefix + 'editor-cont');
contEl.html(this.$el);
return this;
}

4
src/grapesjs/main.js

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

15
src/navigator/config/config.js

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

34
src/navigator/main.js

@ -1,33 +1,23 @@
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) {
if (!(name in config))
config[name] = defaults[name];
}
var obj = {
collection : collection,
config : config,
var obj = {
collection: collection,
config: config,
opened: c.opened || {}
};
// Check if sort is required
if(config.sortable){
var ItemSort = require('./view/ItemSort');
obj.sorter = new ItemSort({config : config});
}
this.ItemsView = new ItemsView(obj);
this.ItemsView = new ItemsView(obj);
}
Navigator.prototype = {
@ -37,4 +27,4 @@ define(function(require) {
};
return Navigator;
});
});

5
src/navigator/template/item.html

@ -4,8 +4,9 @@
<div class="<%= prefix %>title-c">
<div class="<%= prefix %>title <%= addClass %>">
<i id="<%= prefix %>caret" class="fa fa-chevron-right"></i>
<%= title %>
<i id="<%= prefix %>caret" class="fa fa-chevron-right <%= caretCls %>"></i>
<i class="fa fa-pencil <%= editBtnCls %>"></i>
<input class="<%= ppfx %>no-app <%= inputNameCls %>" value="<%= title %>" readonly>
</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),
initialize: function(o){
this.opt = o;
this.config = o.config;
this.em = o.config.em;
this.sorter = o.sorter || {};
this.pfx = this.config.stylePrefix;
initialize: function(o) {
this.opt = o;
this.config = o.config;
this.em = o.config.em;
this.ppfx = this.em.get('Config').stylePrefix;
this.sorter = o.sorter || {};
this.pfx = this.config.stylePrefix;
if(typeof this.model.get('open') == 'undefined')
this.model.set('open',false);
this.listenTo(this.model.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:open', this.updateOpening);
this.className = this.pfx + 'item no-select';
this.events = {};
this.events['click > #'+this.pfx+'btn-eye'] = 'toggleVisibility';
this.events['click .'+this.pfx+'title'] = 'toggleOpening';
this.$el.data("model", this.model);
this.editBtnCls = this.pfx + 'nav-item-edit';
this.inputNameCls = this.ppfx + 'nav-comp-name';
this.caretCls = this.ppfx + 'nav-item-caret';
this.titleCls = this.pfx + 'title';
this.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)
this.events['mousedown > #'+this.pfx+'move'] = 'startSort';
@ -34,18 +43,53 @@ define(['backbone', 'text!./../template/item.html','require'],
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
*
* @return void
* */
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.$caret.addClass('fa-chevron-down');
opened[model.cid] = model;
}else{
this.$el.removeClass("open");
this.$caret.removeClass('fa-chevron-down');
delete opened[model.cid];
}
},
@ -58,19 +102,28 @@ define(['backbone', 'text!./../template/item.html','require'],
toggleOpening: function(e){
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
if(this.em){
var md = this.em.get('selectedComponent');
var md = this.em.get('selectedComponent');
if(md){
md.set('status','');
this.model.set('status','selected');
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
* */
startSort: function(e){
if(this.sorter)
this.sorter.startMove(this, e);
if (this.sorter) {
//this.sorter.startMove(this, e);
this.sorter.startSort(e.target);
}
},
/**
@ -161,7 +216,7 @@ define(['backbone', 'text!./../template/item.html','require'],
* @return void
* */
checkChildren: function(){
var c = this.model.components.length,
var c = this.countChildren(this.model),
pfx = this.pfx,
tC = '> .' + pfx + 'title-c > .' + pfx + 'title';
if(!this.$counter)
@ -176,24 +231,50 @@ define(['backbone', 'text!./../template/item.html','require'],
}
},
render : function(){
var pfx = this.pfx,
vis = this.isVisible();
/**
* Count children inside model
* @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({
title : this.model.getName(),
addClass : (this.model.components.length ? '' : pfx+'no-chld'),
count : this.model.components.length,
visible : vis,
hidable : this.config.hidable,
prefix : pfx
title: this.model.get(this.customNameProp) || this.model.getName(),
addClass: (count ? '' : pfx+'no-chld'),
editBtnCls: this.editBtnCls,
inputNameCls: this.inputNameCls,
caretCls: this.caretCls,
count: count,
visible: vis,
hidable: this.config.hidable,
prefix: pfx,
ppfx: this.ppfx
}));
if(typeof ItemsView == 'undefined')
ItemsView = require('./ItemsView');
this.$components = new ItemsView({
this.$components = new ItemsView({
collection : this.model.components,
config : this.config,
sorter : this.sorter,
parent : this.model
config: this.config,
sorter: this.sorter,
opened: this.opt.opened,
parent: this.model
}).render().$el;
this.$el.find('.'+ pfx +'children').html(this.$components);
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({
initialize: function(o) {
this.opt = o;
this.config = o.config;
this.preview = o.preview;
this.sorter = o.sorter || {};
this.pfx = o.config.stylePrefix;
this.parent = o.parent;
this.listenTo( this.collection, 'add', this.addTo );
this.listenTo( this.collection, 'reset', this.render );
this.opt = o;
this.config = o.config;
this.preview = o.preview;
this.pfx = o.config.stylePrefix;
this.parent = o.parent;
this.listenTo(this.collection, 'add', this.addTo);
this.listenTo(this.collection, 'reset resetNavigator', this.render);
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)
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 viewObject = ItemView;
var view = new viewObject({
model : model,
config : this.config,
sorter : this.sorter,
var view = new viewObject({
model: model,
config: this.config,
sorter: this.sorter,
isCountable: this.isCountable,
opened: this.opt.opened,
});
var rendered = view.render().el;
@ -73,13 +91,29 @@ define(['backbone','./ItemView'],
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() {
var fragment = document.createDocumentFragment();
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);
}, this);
this.$el.append(fragment);
this.$el.attr('class', _.result(this, 'className'));

18
src/panels/main.js

@ -143,6 +143,22 @@ define(function(require) {
* attributes: { title: 'Some title'},
* 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){
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 editor = this.em && this.em.get ? this.em.get('Editor') : null;
var commandName = this.model.get('command');
if(this.commands)
if (this.commands && typeof commandName === 'string') {
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')){
@ -220,7 +226,7 @@ function(Backbone, require) {
if(this.parentM)
this.parentM.set('active', true, { silent: true }).trigger('checkActive');
if(command){
if(command && command.run){
command.run(editor, this.model, this.model.get('options'));
editor.trigger('run:' + commandName);
}
@ -232,7 +238,7 @@ function(Backbone, require) {
if(this.parentM)
this.parentM.set('active', false, { silent: true }).trigger('checkActive');
if(command){
if(command && command.stop){
command.stop(editor, this.model, this.model.get('options'));
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'],
};
});
});

5
src/parser/main.js

@ -9,6 +9,8 @@ define(function(require) {
return {
compTypes: '',
/**
* Name of the module
* @type {String}
@ -48,6 +50,7 @@ define(function(require) {
* @return {Object}
*/
parseHtml: function(str){
pHtml.compTypes = this.compTypes;
return pHtml.parse(str, pCss);
},
@ -60,4 +63,4 @@ define(function(require) {
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 c = config;
var modelAttrStart = 'data-gjs-';
return {
compTypes: '',
/**
* Parse style string to object
* @param {string} str
@ -43,7 +46,8 @@ define(function(require) {
var cls = str.split(' ');
for (var i = 0, len = cls.length; i < len; i++) {
var cl = cls[i].trim();
if(!cl)
var reg = new RegExp('^' + c.pStylePrefix);
if(!cl || reg.test(cl))
continue;
result.push(cl);
}
@ -66,7 +70,21 @@ define(function(require) {
var attrsLen = attrs.length;
var prevI = result.length - 1;
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)
model.attributes = {};
@ -76,33 +94,41 @@ define(function(require) {
var nodeName = attrs[j].nodeName;
var nodeValue = attrs[j].nodeValue;
//Isolate style, class and src attributes
//Isolate few attributes
if(nodeName == 'style')
model.style = this.parseStyle(nodeValue);
else if(nodeName == 'class')
model.classes = this.parseClass(nodeValue);
else if(nodeName == 'src' && model.tagName == 'img'){
model.type = 'image';
model.src = nodeValue;
else if (nodeName == 'contenteditable')
continue;
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
model.attributes[nodeName] = nodeValue;
}
// Check for nested elements
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
var firstChild = node.childNodes[0];
if(nodeChild === 1 && firstChild.nodeType === 3){
model.type = 'text';
if(!model.type){
model.type = 'text';
}
model.content = firstChild.nodeValue;
}else{
var parsed = this.parseNode(node);
// From: <div> <span>TEST</span> </div> <-- span is 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' &&
nodeChild > 1 && parsed[0].tagName == TEXT_NODE){
parsed[0].tagName == TEXT_NODE){
model.type = 'text';
model.content = parsed[0].content;
}else
@ -110,38 +136,43 @@ define(function(require) {
}
}
var prevIsText = prevSib && prevSib.type == 'text' && prevSib.tagName == TEXT_NODE;
// Find text nodes
if(!model.tagName && node.nodeType === 3){
// Pass content to the previous model if it's a text node
// Check if it's a text node and if could be moved to the prevous model
if(model.type == 'textnode'){
var prevIsText = prevSib && prevSib.type == 'textnode';
if(prevIsText){
prevSib.content += node.nodeValue;
prevSib.content += model.content;
continue;
}
// Make it text node only the content is not empty
if(node.nodeValue.trim()){
model.type = 'text';
model.tagName = TEXT_NODE;
model.content = node.nodeValue;
// Throw away empty nodes (keep spaces)
var content = node.nodeValue;
if(content != ' ' && !content.trim()){
continue;
}
}
// Check if it's a text node and if it could be moved to the prevous model
if(c.textTags.indexOf(model.tagName) >= 0){
if(prevIsText){
prevSib.content += node.outerHTML;
continue;
}else{
model = {
type: 'text',
tagName: TEXT_NODE,
content: node.outerHTML,
};
// If all children are texts and there is some textnode the parent should
// be text too otherwise I'm unable to edit texnodes
var comps = model.components;
if(!model.type && comps){
var allTxt = 1;
var foundTextNode = 0;
for(var ci = 0; ci < comps.length; ci++){
var comp = comps[ci];
if(comp.type != 'text' &&
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(!model.tagName)
// If tagName is still empty and is not a textnode, do not push it
if(!model.tagName && model.type != 'textnode')
continue;
result.push(model);
@ -157,6 +188,7 @@ define(function(require) {
* @return {Object}
*/
parse: function(str, parserCss){
var config = (c.em && c.em.get('Config')) || {};
var res = { html: '', css: ''};
var el = document.createElement('div');
el.innerHTML = str;
@ -164,8 +196,10 @@ define(function(require) {
var i = scripts.length;
// Remove all scripts
while (i--)
scripts[i].parentNode.removeChild(scripts[i]);
if(!config.allowScripts){
while (i--)
scripts[i].parentNode.removeChild(scripts[i]);
}
// Detach style tags and parse them
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',
containerId : 'wrapper',
commands : [{
command: 'bold',
title: 'Bold',
class: 'fa fa-bold',
command: 'bold',
title: 'Bold',
class: 'fa fa-bold',
},{
command: 'italic',
title: 'Italic',
class: 'fa fa-italic',
command: 'italic',
title: 'Italic',
class: 'fa fa-italic',
},{
command: 'underline',
title: 'Underline',
class: 'fa fa-underline',
command: 'underline',
title: 'Underline',
class: 'fa fa-underline',
},{
command: 'strikethrough',
title: 'Strikethrough',
class: 'fa fa-strikethrough',
group: 'format'
command: 'strikethrough',
title: 'Strikethrough',
class: 'fa fa-strikethrough',
group: 'format'
},{
command: 'insertHTML',
title: 'Link',
class: 'fa fa-link',
args: '<a class="link" href="">${content}</a>',
}/*,{
command: 'fontSize',
command: 'fontSize',
options: [
{name: 'Huge', value: '7'},
{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'),
CommandButtonsView = require('./view/CommandButtonsView');
var tlbPfx, toolbar, commands;
var mainSelf;
return {
customRte: null,
/**
* Name of the module
* @type {String}
@ -46,6 +49,7 @@ define(function(require) {
* @private
*/
init: function(config) {
mainSelf = this;
c = config || {};
for (var name in defaults) {
if (!(name in c))
@ -114,62 +118,97 @@ define(function(require) {
* Triggered when the offset of the editro is changed
* @private
*/
udpatePosition: function(){
if(!this.lastEl || !c.em)
return;
var u = 'px';
var eOffset = c.em.get('canvasOffset');
var cvsView = c.em.get('Canvas').getCanvasView();
var dims = cvsView.getElementPos(this.lastEl);
var toolS = toolbar.el.style;
var toolH = toolbar.$el.outerHeight();
toolS.top = (dims.top - toolH) + u;
toolS.left = (dims.left + eOffset.left) + u;
udpatePosition: function() {
var u = 'px';
var canvas = c.em.get('Canvas');
var pos = canvas.getTargetToElementDim(toolbar.el, this.lastEl, {
event: 'rteToolbarPosUpdate',
});
var toolbarStyle = toolbar.el.style;
toolbarStyle.top = pos.top + u;
toolbarStyle.left = pos.left + u;
},
/**
* Bind rich text editor to the element
* @param {View} view
* @param {Object} rte The instance of already defined RTE
* @private
* */
attach: function(view){
view.$el.wysiwyg({}).focus();
attach: function(view, rte) {
this.lastEl = view.el;
var customRte = this.customRte;
if(c.em){
this.udpatePosition();
// If a custom RTE is defined
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.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
toolbar.$el.on('mousedown', this.disableProp);
return rte;
},
/**
* Unbind rich text editor from the element
* @param {View} view
* @param {Object} rte The instance of already defined RTE
* @private
* */
detach: function(view){
view.$el.wysiwyg('destroy');
detach: function(view, rte) {
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();
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
* @private
* */
show: function(){
toolbar.el.style.display = "block";
show: function() {
var toolbarStyle = toolbar.el.style;
toolbarStyle.display = "block";
},
/**
* Hide the toolbar
* @private
* */
hide: function(){
hide: function() {
toolbar.el.style.display = "none";
},
@ -177,7 +216,7 @@ define(function(require) {
* Isolate the disable propagation method
* @private
* */
disableProp: function(e){
disableProp: function(e) {
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;
break;
}
var args = model.get('args');
var attrs = {
'title': model.get('title'),
'data-edit': model.get('command'),
};
if(args)
attrs['data-args'] = args;
var view = new viewObj({
model: model,
attributes: {
'title': model.get('title'),
'data-edit': model.get('command'),
},
attributes: attrs,
}, this.config);
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(command, 0, args);
updateToolbar();
editor.trigger('change');
},
/*
bindHotkeys = function (hotKeys) {
@ -112,7 +113,16 @@ define(['jquery'],
toolbar.find(toolbarBtnSelector).unbind().click(function () {
restoreSelection();
//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();
});
toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
@ -124,11 +134,9 @@ define(['jquery'],
editor.focus();
execCommand($(this).data(options.commandRole), newValue);
}
console.log('change isolated2 ', newValue);
saveSelection();
});
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 */
this.value = '';
restoreSelection();
@ -138,10 +146,8 @@ define(['jquery'],
}
saveSelection();
}).on('focus', function () {
console.log('on focus ');
var input = $(this);
if (!input.data(options.selectionMarker)) {
console.log('i have no ', options.selectionMarker);
markSelection(input, options.selectionColor);
input.focus();
}
@ -228,4 +234,3 @@ define(['jquery'],
return $;
});

10
src/selector_manager/template/classTags.html

@ -2,20 +2,20 @@
<div id="<%= pfx %>label"><%= label %></div>
<div id="<%= pfx %>status-c">
<span id="<%= pfx %>input-c">
<div class="<%= pfx %>field <%= ppfx %>select">
<span id="<%= pfx %>input-holder">
<div class="<%= ppfx %>field <%= ppfx %>select">
<span id="<%= ppfx %>input-holder">
<select id="<%= pfx %>states">
<option value=""><%= statesLabel %></option>
</select>
</span>
<div class="<%= pfx %>sel-arrow">
<div class="<%= pfx %>d-s-arrow"></div>
<div class="<%= ppfx %>sel-arrow">
<div class="<%= ppfx %>d-s-arrow"></div>
</div>
</div>
</span>
</div>
</div>
<div id="<%= pfx %>tags-field">
<div id="<%= pfx %>tags-field" class="<%= ppfx %>field">
<div id="<%= pfx %>tags-c"></div>
<input id="<%= pfx %>new" />
<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(){
if(!this.$labelInput)
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;
this.$labelInput.attr('size', size);
},

56
src/style_manager/config/config.js

@ -1,55 +1,13 @@
define(function () {
return {
stylePrefix: 'sm-',
return {
// Default sectors, which could include also properties
//
// 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'],
}],
stylePrefix: 'sm-',
// Text to show in case no element selected
textNoElement: 'Select an element before using Style Manager',
sectors: [],
// 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({
defaults: {
name : '',
name: '',
property: '',
type: '',
units: [],
@ -15,10 +15,11 @@ define(['backbone', './Layers', 'require'],
icon: '',
preview: false,
detached: false,
functionName: '',
functionName: '',
properties: [],
layers: [],
list: [],
fixedValues: ['initial', 'inherit'],
},
initialize: function(opt) {
@ -48,7 +49,7 @@ define(['backbone', './Layers', 'require'],
* @return {string} Value
* @private
*/
getValue: function(){
getValue: function() {
var result = '';
var type = this.get('type');

13
src/style_manager/model/PropertyFactory.js

@ -11,6 +11,7 @@ define(['backbone'],
*/
build: function(props){
var objs = [];
var dftFixedValues = ['initial', 'inherit'];
if(typeof props === 'string')
props = [props];
@ -27,6 +28,16 @@ define(['backbone'],
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
switch(prop){
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">
<span id='<%= pfx %>input-holder'></span>
<div class="<%= pfx %>colorp-c">
<div class="<%= ppfx %>checker-bg"></div>
<div class="<%= ppfx %>field <%= ppfx %>field-color">
<div id='<%= pfx %>input-holder'></div>
<div class="<%= ppfx %>field-colorp">
<div class="<%= ppfx %>field-colorp-c">
<div class="<%= ppfx %>checker-bg"></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>
</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