Browse Source

Merge dev in master (#2190)

Merge dev in master
pull/2197/head
Artur Arseniev 7 years ago
committed by GitHub
parent
commit
7eb9e89eb4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 20
      README.md
  2. 2
      dist/css/grapes.min.css
  3. 22464
      dist/grapes.js
  4. 8
      dist/grapes.min.js
  5. 2
      dist/grapes.min.js.map
  6. 5
      docs/.vuepress/components/DemoBasicBlocks.vue
  7. 5
      docs/.vuepress/components/DemoCanvasOnly.vue
  8. 5
      docs/.vuepress/components/DemoCustomPanels.vue
  9. 5
      docs/.vuepress/components/DemoDevices.vue
  10. 5
      docs/.vuepress/components/DemoLayers.vue
  11. 5
      docs/.vuepress/components/DemoStyle.vue
  12. 5
      docs/.vuepress/components/DemoTheme.vue
  13. 5
      docs/.vuepress/components/DemoTraits.vue
  14. 33
      docs/.vuepress/components/DemoViewer.vue
  15. 20
      docs/.vuepress/components/demos/utils.js
  16. 2
      docs/.vuepress/config.js
  17. BIN
      docs/.vuepress/public/default-link-comp.jpg
  18. BIN
      docs/.vuepress/public/docs-init-link-trait.jpg
  19. BIN
      docs/.vuepress/public/docs-link-trait-raw.jpg
  20. 2
      docs/.vuepress/theme/CarbonAds.vue
  21. 2
      docs/.vuepress/theme/Layout.vue
  22. 31
      docs/api/component.md
  23. 13
      docs/api/components.md
  24. 15
      docs/api/editor.md
  25. 2
      docs/api/rich_text_editor.md
  26. 10
      docs/api/selector_manager.md
  27. 6
      docs/api/style_manager.md
  28. 2
      docs/getting-started.md
  29. 628
      docs/modules/Components-new.md
  30. 2
      docs/modules/Components.md
  31. 552
      docs/modules/Traits.md
  32. 22997
      package-lock.json
  33. 58
      package.json
  34. 2
      src/asset_manager/config/config.js
  35. 11
      src/asset_manager/index.js
  36. 4
      src/asset_manager/model/Asset.js
  37. 6
      src/asset_manager/model/AssetImage.js
  38. 37
      src/asset_manager/model/Assets.js
  39. 3
      src/asset_manager/view/AssetImageView.js
  40. 5
      src/asset_manager/view/AssetView.js
  41. 5
      src/asset_manager/view/AssetsView.js
  42. 6
      src/asset_manager/view/FileUploader.js
  43. 2
      src/block_manager/config/config.js
  44. 12
      src/block_manager/index.js
  45. 4
      src/block_manager/model/Block.js
  46. 4
      src/block_manager/model/Blocks.js
  47. 5
      src/block_manager/model/Categories.js
  48. 2
      src/block_manager/model/Category.js
  49. 2
      src/block_manager/view/BlockView.js
  50. 8
      src/block_manager/view/BlocksView.js
  51. 6
      src/block_manager/view/CategoryView.js
  52. 2
      src/canvas/config/config.js
  53. 17
      src/canvas/index.js
  54. 4
      src/canvas/model/Canvas.js
  55. 2
      src/canvas/model/Frame.js
  56. 19
      src/canvas/view/CanvasView.js
  57. 13
      src/canvas/view/FrameView.js
  58. 2
      src/code_manager/config/config.js
  59. 21
      src/code_manager/index.js
  60. 10
      src/code_manager/model/CodeMirrorEditor.js
  61. 3
      src/code_manager/model/CssGenerator.js
  62. 2
      src/code_manager/model/HtmlGenerator.js
  63. 6
      src/code_manager/model/JsGenerator.js
  64. 2
      src/code_manager/model/JsonGenerator.js
  65. 2
      src/code_manager/view/EditorView.js
  66. 16
      src/commands/index.js
  67. 2
      src/commands/model/Command.js
  68. 4
      src/commands/model/Commands.js
  69. 2
      src/commands/view/CanvasClear.js
  70. 2
      src/commands/view/CanvasMove.js
  71. 2
      src/commands/view/ComponentDelete.js
  72. 16
      src/commands/view/ComponentDrag.js
  73. 2
      src/commands/view/ComponentEnter.js
  74. 2
      src/commands/view/ComponentExit.js
  75. 2
      src/commands/view/ComponentNext.js
  76. 2
      src/commands/view/ComponentPrev.js
  77. 2
      src/commands/view/ComponentStyleClear.js
  78. 2
      src/commands/view/CopyComponent.js
  79. 9
      src/commands/view/DeleteComponent.js
  80. 2
      src/commands/view/ExportTemplate.js
  81. 2
      src/commands/view/Fullscreen.js
  82. 10
      src/commands/view/MoveComponent.js
  83. 2
      src/commands/view/OpenAssets.js
  84. 2
      src/commands/view/OpenBlocks.js
  85. 5
      src/commands/view/OpenLayers.js
  86. 5
      src/commands/view/OpenStyleManager.js
  87. 6
      src/commands/view/OpenTraitManager.js
  88. 2
      src/commands/view/PasteComponent.js
  89. 4
      src/commands/view/Preview.js
  90. 2
      src/commands/view/Resize.js
  91. 14
      src/commands/view/SelectComponent.js
  92. 2
      src/commands/view/SelectPosition.js
  93. 2
      src/commands/view/ShowOffset.js
  94. 2
      src/commands/view/SwitchVisibility.js
  95. 2
      src/css_composer/config/config.js
  96. 19
      src/css_composer/index.js
  97. 11
      src/css_composer/model/CssRule.js
  98. 4
      src/css_composer/model/CssRules.js
  99. 4
      src/css_composer/view/CssGroupRuleView.js
  100. 4
      src/css_composer/view/CssRuleView.js

20
README.md

@ -46,21 +46,13 @@ Newsletter Demo - http://grapesjs.com/demo-newsletter-editor.html
## Features ## Features
| Blocks | Style Manager | Layer Manager |
|--|--|--|
|<img src="http://grapesjs.com/img/sc-grapesjs-blocks-prp.jpg" alt="GrapesJS - Block Manager" height="400" align="center"/>|<img src="http://grapesjs.com/img/sc-grapesjs-style-2.jpg" alt="GrapesJS - Style Manager" height="400" align="center"/>|<img src="http://grapesjs.com/img/sc-grapesjs-layers-2.jpg" alt="GrapesJS - Layer Manager" height="400" align="center"/>|
* Blocks | Code Viewer | Asset Manager |
<p align="center"><img src="http://grapesjs.com/img/sc-grapesjs-blocks-prp.jpg" alt="GrapesJS - Block Manager" height="400" align="center"/></p> |--|--|
|<img src="http://grapesjs.com/img/sc-grapesjs-code.jpg" alt="GrapesJS - Code Viewer" height="300" align="center"/>|<img src="http://grapesjs.com/img/sc-grapesjs-assets-1.jpg" alt="GrapesJS - Asset Manager" height="250" align="center"/>|
* Style Manager, for component styling<br/>
<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/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/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/sc-grapesjs-assets-1.jpg" alt="GrapesJS - Asset Manager" height="250" align="center"/></p>
* Local and remote storage * Local and remote storage

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

22464
dist/grapes.js

File diff suppressed because it is too large

8
dist/grapes.min.js

File diff suppressed because one or more lines are too long

2
dist/grapes.min.js.map

File diff suppressed because one or more lines are too long

5
docs/.vuepress/components/DemoBasicBlocks.vue

@ -8,9 +8,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
window.editor2 = grapesjs.init(utils.gjsConfigBlocks); window.editor2 = grapesjs.init(utils.gjsConfigBlocks);
} }
} }

5
docs/.vuepress/components/DemoCanvasOnly.vue

@ -2,9 +2,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
const editor = grapesjs.init(utils.gjsConfigStart); const editor = grapesjs.init(utils.gjsConfigStart);
} }
} }

5
docs/.vuepress/components/DemoCustomPanels.vue

@ -11,9 +11,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
const editor3 = grapesjs.init(utils.gjsConfigPanels); const editor3 = grapesjs.init(utils.gjsConfigPanels);
editor3.Panels.addPanel(Object.assign({}, utils.panelTop, { editor3.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top3' el: '#panel__top3'

5
docs/.vuepress/components/DemoDevices.vue

@ -24,9 +24,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
const editor7 = grapesjs.init(utils.gjsConfigDevices); const editor7 = grapesjs.init(utils.gjsConfigDevices);
editor7.Panels.addPanel(Object.assign({}, utils.panelTop, { editor7.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top7' el: '#panel__top7'

5
docs/.vuepress/components/DemoLayers.vue

@ -20,9 +20,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
const editor4 = grapesjs.init(utils.gjsConfigLayers); const editor4 = grapesjs.init(utils.gjsConfigLayers);
editor4.Panels.addPanel(Object.assign({}, utils.panelTop, { editor4.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top4' el: '#panel__top4'

5
docs/.vuepress/components/DemoStyle.vue

@ -22,9 +22,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
const editor5 = grapesjs.init(utils.gjsConfigStyle); const editor5 = grapesjs.init(utils.gjsConfigStyle);
editor5.Panels.addPanel(Object.assign({}, utils.panelTop, { editor5.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top5' el: '#panel__top5'

5
docs/.vuepress/components/DemoTheme.vue

@ -24,9 +24,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
const editor8 = grapesjs.init(utils.gjsConfigTheme); const editor8 = grapesjs.init(utils.gjsConfigTheme);
editor8.Panels.addPanel(Object.assign({}, utils.panelTop, { editor8.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top8' el: '#panel__top8'

5
docs/.vuepress/components/DemoTraits.vue

@ -23,9 +23,10 @@
</template> </template>
<script> <script>
module.exports = { import utils from './demos/utils.js';
export default {
mounted() { mounted() {
const utils = require('./demos/utils.js');
const editor6 = grapesjs.init(utils.gjsConfigTraits); const editor6 = grapesjs.init(utils.gjsConfigTraits);
editor6.Panels.addPanel(Object.assign({}, utils.panelTop, { editor6.Panels.addPanel(Object.assign({}, utils.panelTop, {
el: '#panel__top6' el: '#panel__top6'

33
docs/.vuepress/components/DemoViewer.vue

@ -0,0 +1,33 @@
<template>
<iframe :width="width" :height="height" :src="src" allowfullscreen="allowfullscreen" frameborder="0"/>
</template>
<script>
export default {
name: 'DemoViewer',
props: {
value: {
type: String,
default: '',
},
user: {
type: String,
default: 'artur_arseniev',
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: '300',
},
},
computed: {
src() {
const { value, user } = this;
return `//jsfiddle.net/${user}/${value}/embedded/js,html,css,result`;
}
}
}
</script>

20
docs/.vuepress/components/demos/utils.js

@ -1,3 +1,21 @@
export const loadScript = url => new Promise((resolve, reject) => {
const script = document.createElement('script');
script.onload = resolve;
script.onerror = reject;
script.src = url;
document.head.appendChild(script);
});
export const loadStyle = url => new Promise((resolve) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
document.head.appendChild(link);
resolve();
});
// Don't know yet why but can't use ES6 // Don't know yet why but can't use ES6
var blockManager = { var blockManager = {
@ -353,7 +371,7 @@ var gjsConfigTheme = Object.assign({}, gjsConfigBlocks, {
deviceManager, deviceManager,
}); });
module.exports = { export default {
gjsConfigStart, gjsConfigStart,
gjsConfigBlocks, gjsConfigBlocks,
gjsConfigPanels, gjsConfigPanels,

2
docs/.vuepress/config.js

@ -1,6 +1,6 @@
const version = require('./../../package.json').version; const version = require('./../../package.json').version;
const isDev = process.argv[2] === 'dev'; const isDev = process.argv[2] === 'dev';
const devPath = 'https://localhost:8080/dist'; const devPath = 'http://localhost:8080/dist';
module.exports = { module.exports = {
title: 'GrapesJS', title: 'GrapesJS',

BIN
docs/.vuepress/public/default-link-comp.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
docs/.vuepress/public/docs-init-link-trait.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
docs/.vuepress/public/docs-link-trait-raw.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

2
docs/.vuepress/theme/CarbonAds.vue

@ -1,5 +1,5 @@
<script> <script>
module.exports = { export default {
render (h) { render (h) {
return h('div', { class: 'carbon-ads', attrs: { id: 'native-carbon' }}) return h('div', { class: 'carbon-ads', attrs: { id: 'native-carbon' }})
}, },

2
docs/.vuepress/theme/Layout.vue

@ -8,7 +8,7 @@
var Layout = require('@default-theme/Layout.vue').default; var Layout = require('@default-theme/Layout.vue').default;
var CarbonAds = require('./CarbonAds.vue').default; var CarbonAds = require('./CarbonAds.vue').default;
module.exports = { export default {
components: { components: {
Layout, Layout,
CarbonAds, CarbonAds,

31
docs/api/component.md

@ -48,6 +48,8 @@ component.get('tagName');
- `content` **[String][1]?** Content of the component (not escaped) which will be appended before children rendering. Default: `''` - `content` **[String][1]?** Content of the component (not escaped) which will be appended before children rendering. Default: `''`
- `icon` **[String][1]?** Component's icon, this string will be inserted before the name (in Layers and badge), eg. it can be an HTML string '<i class="fa fa-square-o"></i>'. Default: `''` - `icon` **[String][1]?** Component's icon, this string will be inserted before the name (in Layers and badge), eg. it can be an HTML string '<i class="fa fa-square-o"></i>'. Default: `''`
- `script` **([String][1] \| [Function][6])?** Component's javascript. More about it [here][7]. Default: `''` - `script` **([String][1] \| [Function][6])?** Component's javascript. More about it [here][7]. Default: `''`
- `script-export` **([String][1] \| [Function][6])?** You can specify javascript available only in export functions (eg. when you get the HTML).
If this property is defined it will overwrite the `script` one (in export functions). Default: `''`
- `traits` **[Array][4]&lt;([Object][2] \| [String][1])>?** Component's traits. More about it [here][8]. Default: `['id', 'title']` - `traits` **[Array][4]&lt;([Object][2] \| [String][1])>?** Component's traits. More about it [here][8]. Default: `['id', 'title']`
- `propagate` **[Array][4]&lt;[String][1]>?** Indicates an array of properties which will be inhereted by all NEW appended children. - `propagate` **[Array][4]&lt;[String][1]>?** Indicates an array of properties which will be inhereted by all NEW appended children.
For example if you create a component likes this: `{ removable: false, draggable: false, propagate: ['removable', 'draggable'] }` For example if you create a component likes this: `{ removable: false, draggable: false, propagate: ['removable', 'draggable'] }`
@ -92,12 +94,29 @@ component.is('image')
Returns **[Boolean][3]** Returns **[Boolean][3]**
## props
Return all the propeties
Returns **[Object][2]**
## index ## index
Get the index of the component in the parent collection. Get the index of the component in the parent collection.
Returns **[Number][10]** Returns **[Number][10]**
## setDragMode
Change the drag mode of the component.
To get more about this feature read: [https://github.com/artf/grapesjs/issues/1936][11]
### Parameters
- `value` **[String][1]** Drag mode, options: 'absolute' | 'translate'
Returns **this**
## find ## find
Find inner components by query string. Find inner components by query string.
@ -437,12 +456,12 @@ Add trait/s by id/s.
### Examples ### Examples
```javascript ```javascript
component.addTrat('title', { at: 1 }); // Add title trait (`at` option is the position index) component.addTrait('title', { at: 1 }); // Add title trait (`at` option is the position index)
component.addTrat({ component.addTrait({
type: 'checkbox', type: 'checkbox',
name: 'disabled', name: 'disabled',
}); });
component.addTrat(['title', {...}, ...]); component.addTrait(['title', {...}, ...]);
``` ```
Returns **[Array][4]** Array of added traits Returns **[Array][4]** Array of added traits
@ -517,7 +536,7 @@ Returns **this**
Get the DOM element of the component. Get the DOM element of the component.
This works only if the component is already rendered This works only if the component is already rendered
Returns **[HTMLElement][11]** Returns **[HTMLElement][12]**
## getView ## getView
@ -592,4 +611,6 @@ This method is used in Components.js just after the parsing
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[11]: https://developer.mozilla.org/docs/Web/HTML/Element [11]: https://github.com/artf/grapesjs/issues/1936
[12]: https://developer.mozilla.org/docs/Web/HTML/Element

13
docs/api/components.md

@ -175,6 +175,17 @@ Read more about this in [Define New Component][17]
Returns **[Object][12]** Component type defintion, eg. `{ model: ..., view: ... }` Returns **[Object][12]** Component type defintion, eg. `{ model: ..., view: ... }`
## removeType
Remove component type
### Parameters
- `id`
- `type` **[string][15]** Component ID
Returns **([Object][12] \| [undefined][18])** Removed component type, undefined otherwise
## getTypes ## getTypes
Return the array of all types Return the array of all types
@ -214,3 +225,5 @@ Returns **[Array][14]**
[16]: https://developer.mozilla.org/docs/Web/HTML/Element [16]: https://developer.mozilla.org/docs/Web/HTML/Element
[17]: https://grapesjs.com/docs/modules/Components.html#define-new-component [17]: https://grapesjs.com/docs/modules/Components.html#define-new-component
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined

15
docs/api/editor.md

@ -218,7 +218,7 @@ editor.addComponents({
}); });
``` ```
Returns **(Model | [Array][4]&lt;Model>)** Returns **[Array][4]&lt;Component>**
## getStyle ## getStyle
@ -502,6 +502,17 @@ editor.setCustomParserCss(css => {
Returns **this** Returns **this**
## setDragMode
Change the global drag mode of components.
To get more about this feature read: [https://github.com/artf/grapesjs/issues/1936][9]
### Parameters
- `value` **[String][2]** Drag mode, options: 'absolute' | 'translate'
Returns **this**
## log ## log
Trigger event log message Trigger event log message
@ -594,3 +605,5 @@ Returns **[HTMLElement][6]**
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function [7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[9]: https://github.com/artf/grapesjs/issues/1936

2
docs/api/rich_text_editor.md

@ -41,7 +41,7 @@ Add a new action to the built-in RTE toolbar
```javascript ```javascript
rte.add('bold', { rte.add('bold', {
icon: '<b>B</b>', icon: '<b>B</b>',
attributes: {title: 'Bold',} attributes: {title: 'Bold'},
result: rte => rte.exec('bold') result: rte => rte.exec('bold')
}); });
rte.add('link', { rte.add('link', {

10
docs/api/selector_manager.md

@ -123,6 +123,16 @@ Get all selectors
Returns **Collection** Returns **Collection**
## escapeName
Return escaped selector name
### Parameters
- `name` **[String][8]** Selector name to escape
Returns **[String][8]** Escaped name
[1]: https://github.com/artf/grapesjs/blob/master/src/selector_manager/config/config.js [1]: https://github.com/artf/grapesjs/blob/master/src/selector_manager/config/config.js
[2]: #getconfig [2]: #getconfig

6
docs/api/style_manager.md

@ -52,6 +52,8 @@ that one will be returned
- `sector.name` **[string][17]** Sector's label (optional, default `''`) - `sector.name` **[string][17]** Sector's label (optional, default `''`)
- `sector.open` **[Boolean][18]** Indicates if the sector should be opened (optional, default `true`) - `sector.open` **[Boolean][18]** Indicates if the sector should be opened (optional, default `true`)
- `sector.properties` **[Array][19]&lt;[Object][16]>** Array of properties (optional, default `[]`) - `sector.properties` **[Array][19]&lt;[Object][16]>** Array of properties (optional, default `[]`)
- `opts` (optional, default `{}`)
- `options` **[Object][16]** Options (optional, default `{}`)
### Examples ### Examples
@ -60,7 +62,8 @@ var sector = styleManager.addSector('mySector',{
name: 'My sector', name: 'My sector',
open: true, open: true,
properties: [{ name: 'My property'}] properties: [{ name: 'My property'}]
}); }, { at: 0 });
// With `at: 0` we place the new sector at the beginning of the collection
``` ```
Returns **Sector** Added Sector Returns **Sector** Added Sector
@ -72,6 +75,7 @@ Get sector by id
### Parameters ### Parameters
- `id` **[string][17]** Sector id - `id` **[string][17]** Sector id
- `opts` (optional, default `{}`)
### Examples ### Examples

2
docs/getting-started.md

@ -136,7 +136,7 @@ editor.BlockManager.add('my-block-id', {
// use `content` for static strings, `components` string will be parsed // use `content` for static strings, `components` string will be parsed
// and transformed in Components // and transformed in Components
components: '<span>HTML at some point</span>', components: '<span>HTML at some point</span>',
} }
] ]
} }
}) })

628
docs/modules/Components-new.md

@ -0,0 +1,628 @@
---
title: Component Manager
---
# Component Manager
The Component is the base element for template composition. It is atomic, so elements like images, text boxes, maps, etc. fit the definition of a Component. The concept of the component was made to allow the developer to bind different behaviors to different elements. Like for example, opening the Asset Manager on double click of the image.
::: warning
This guide is referring to GrapesJS v0.14.67 or higher
:::
[[toc]]
## How Components work?
Let's see in detail how components work by looking at all steps from adding an HTML string to the editor.
This is how we can add new components to the canvas:
```js
// Append components directly to the canvas
editor.addComponents(`<div>
<img src="https://path/image" />
<span title="foo">Hello world!!!</span>
</div>`);
// or into some, already defined, component.
// For instance, appending to a selected component would be:
editor.getSelected().append(`<div>...`);
// Actually, editor.addComponents is an alias of...
editor.getWrapper().append(`<div>...`);
```
::: tip
If you need to append a component in a specific position, you can use `at` option. To add a component on top of all others (in the same collection) you would use
```js
component.append('<div>...', { at: 0 })
```
or in the middle
```js
const { length } = component.components();
component.append('<div>...', { at: parseInt(length / 2, 10) })
```
:::
### Component Definition
In the first step the HTML string is parsed and trasformed to what is called **Component Definition**, so the result of the input would be:
```js
{
tagName: 'div',
components: [
{
type: 'image',
attributes: { src: 'https://path/image' },
}, {
tagName: 'span',
type: 'text',
attributes: { title: 'foo' },
components: [{
type: 'textnode',
content: 'Hello wdsforld!!!'
}]
}
]
}
```
The real **Component Definition** would be a little bit bigger so we reduced the JSON for the sake of simplicity.
You can notice the result is similar to what is generally called a **Virtual DOM**, a lightweight rappresentation of the DOM element. This actually helps the editor to keep track of the state of our elements and make performance-friendly changes/updates.
The meaning of properties like `tagName`, `attributes` and `components` are quite obvious, but what about `type`?! This particular property specifies the actual **Component** of our **Component Definition** (you check the list of default components [below](#built-in-components)) and if it's omitted, the default one will be used `type: 'default'`.
At this point, a good question would be, how the editor assignes those types by starting from a simple HTML string? This step is identified as **Component Recognition** and it's explained in detail in the next paragraph.
### Component Recognition and Component Type Stack
As we said before, when you pass an HTML string as a component to the editor, that string is parsed and compiled to the [Component Definition](#component-definition) with a new `type` property. To understand what `type` should be assigned, for each parsed HTML Element, the editor iterates over all the defined components, called **Component Type Stack**, and checks via `isComponent` method (we will see it later) if that component type is appropriate for that element. The Component Type Stack is just a simple array of components but the important part is the order of those components. Any new added Custom Component (we'll see later how to create them) goes on top of the Component Type Stack and each element returned from the parser iterates the stack from top to bottom (the last element of the stack is the `default` one), the iteration stops once one of the component returns a truthy value from the `isComponent` method.
SVG - ComponentTypeStack
::: tip
If you're importing big chunks of HTML code you might want to improve the performances by skipping the parsing and the component recognition steps by passing directly Component Definiton objects or using the JSX syntax. Read more about it here...TODO
:::
### Component Creation
Once the **Component Definition** is ready and the type is assigned, the [Component](api/component) instance can be created (known also as the **Model**). Let's step back to our previous example with the HTML string, the result of the `append` method is an array of added components.
```js
const component = editor.addComponents(`<div>
<img src="https://path/image" />
<span title="foo">Hello world!!!</span>
</div>`)[0];
```
The Component instance contains properties and methods which allows you to obtain its data and change them.
You can read properties with the `get` method, like, for example, the `type`
```js
const componentType = component.get('type'); // eg. 'image'
```
and to update properties you'd use `set`, which might change the way a component behavies in the canvas.
```js
// Make the component not draggable
component.set('draggable', false);
```
You can also use methods like `getAttributes`, `setAttributes`, `components`, etc.
```js
const innerComponents = component.components();
// Update component content
component.components(`<div>Component 1</div><div>Component 2</div>`);
```
Each component can define its own properties and methods but all of them will always extend, at least, the `default` one (then you will see how to create new custom components and how to extend the already defined) so it's good to check the [Component API](api/component) to see all available properties and methods.
The **main purpose of the Component** is to keep track of its data and to return them when necessary. One common thing you might need to ask from the component is to show its current HTML
```js
const componentHTML = component.toHTML();
```
This will return a string containing the HTML of the component and all of its children.
The component implements also `toJSON` methods so you can get its JSON structure in this way
```js
JSON.stringify(component)
```
::: tip
For storing/loading all the components you should rely on the [Storage Manager](modules/storage)
:::
The Component instance is responable for the **final data** (eg. HTML, JSON) of your templates, so if you need, for example, to update/add some attribute in the HTML you need to update its component (eg. `component.addAttributes({ title: 'Title added' })`), so the Component/Model is your **Source of Truth**.
### Component Rendering
Another important thing of components is how they are rendered in the **canvas**, this aspect is handled by the **View** of the component. It has nothing to do with the **final data**, you can return a big `<div>...</div>` string as HTML of your component but render it as a simple image in the canvas (think about placeholders for complex/dynamic data).
So, by default, the view of components is automatically synced with the data of its models (you can't have a View without a Model). If you update the attribute of the component or append a new one as a child, the view will render it in the canvas.
Unfotunatelly, sometimes, you might need some additional logic to handle better the component result. Think about allowing a user build its `<table>` element, for this specific case you might want to add custom buttons in the canvas, so it'd be easier adding/removing columns/rows. To handle those cases you can rely on the View, where you can add additional DOM component, attach events, etc. All of this will be completely unrelated with the final HTML of the `<table>` (the result the user would expect) as it handled by the Model.
Once the component is rendered (when you actually see it in the canvas) you can always access its View and the DOM element.
```js
const component = editor.getSelected();
// Get the View
const view = component.getView();
// Get the DOM element
const el = component.getEl();
```
So generally, the View is something you wouldn't need to change as the default one handles already the sync with the Model but in case you'd need more control over elements (eg. custom UI in canvas) you'll probably need to create a custom component and extend the default View with your logic. We'll see later how to create custom components.
So far we have seen the core concept behind Components and how they work. The **Model/Component** is the **source of truth** for the final code of templates (eg. the HTML export relies on it) and the *View/ComponentView* is what is used by the editor to **preview our components** to users in the canvas.
TODO
A more advanced use case of custom components is an implementation of a custom renderer inside of them
## Built-in Components
Here below you can see the list of built-in components, ordered by their position in the Component Type Stack
* [`cell`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableCell.js) - Component for handle `<td>` and `<th>` elements
* [`row`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableRow.js) - Component for handle `<tr>` elements
* [`table`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTable.js) - Component for handle `<table>` elements
* [`thead`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableHead.js) - Component for handle `<thead>` elements
* [`tbody`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableBody.js) - Component for handle `<tbody>` elements
* [`tfoot`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableFoot.js) - Component for handle `<tfoot>` elements
* [`map`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentMap.js) - Component for handle `<a>` elements
* [`link`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentLink.js) - Component for handle `<a>` elements
* [`label`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentLabel.js) - Component for handle properly `<label>` elements
* [`video`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentVideo.js) - Component for videos
* [`image`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentImage.js) - Component for images
* [`script`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentScript.js) - Component for handle `<script>` elements
* [`svg`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentSvg.js) - Component for handle SVG elements
* [`comment`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentComment.js) - Component for comments (might be useful for email editors)
* [`textnode`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTextNode.js) - Similar to the textnode in DOM definition, so a text element without a tag element.
* [`text`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentText.js) - A simple text component that can be edited inline
* [`wrapper`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentWrapper.js) - The canvas need to contain a root component, a wrapper, this component was made to identify it
* [`default`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/Component.js) - Default base component
## Define new Component
Now that we know how components work, we can start exploring the process of creating new **Custom Components**.
Let's say we want to make the editor understand and handle better `<input>` elements
First of all, place your components inside a plugin
--- OLD
## Component recognition
But now, how does the editor recognize which Component to bind to the `img` element and what to do with the `span` one?
Each Component inherits, from the base one, a particular static method
```js
/**
* @param {HTMLElement} el
* @return {Object}
*/
isComponent: function(el) {
...
}
```
This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each **HTML string/element** introduced inside the canvas will be processed by `isComponent` of all available types and if it matches, the object represented the type should be returned. The method `isComponent` **is skipped** if you add the component object (`{ type: 'my-custom-type', tagName: 'div', attribute: {...}, ...}`) or declare the type explicitly on the element (`<div data-gjs-type="my-custom-type">...</div>`)
For example, with the image component this method looks like:
```js
// Image component
isComponent: function(el) {
if(el.tagName == 'IMG')
return {type: 'image'};
}
```
Let's try with something that might look a little bit tricky. What about a Google Map?!? Google Maps are generally embedded as `iframe`s, but the template can be composed by a lot of different `iframe`s. How can I tell the editor that a particular iframe is actually a Google's Map? Well, you'll have to figure out the right pattern, you have the `HTMLElement` so you can make all the checks you want. In this particular case this pattern is used:
```js
// Map component
isComponent: function(el) {
if(el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) {
return {type: 'map', src: el.src};
}
},
```
In addition to `tagName` check, we also used the `src` property, but you can actually override it with your own logic by extending the built-in component.
## Define new Component
Let's see an example with another HTML element that is not handled by default Component types. What about `input` elements?
With the default GrapesJS configuration `input`s are treated like any other element; you can move it around, style it, etc. However, we'd like to handle this type of element more specifically. In this case, we have to create a new Component type.
Let's define few specs for our new *Input* type:
* Can be dropped only inside `form` elements
* Can't drop other elements inside it
* Can change the type of the input (text, password, email, etc.)
* Can make it required for the form
To define a new Component type you need to choose from which built-in Component inherit its properties, in our case we just gonna choose the default one. Let's see a complete example of the new type definition
```js
// Get DomComponents module
var comps = editor.DomComponents;
// Get the model and the view from the default Component type
var defaultType = comps.getType('default');
var defaultModel = defaultType.model;
var defaultView = defaultType.view;
var inputTypes = [
{value: 'text', name: 'Text'},
{value: 'email', name: 'Email'},
{value: 'password', name: 'Password'},
{value: 'number', name: 'Number'},
];
// The `input` will be the Component type ID
comps.addType('input', {
// Define the Model
model: defaultModel.extend({
// Extend default properties
defaults: Object.assign({}, defaultModel.prototype.defaults, {
// Can be dropped only inside `form` elements
draggable: 'form, form *',
// Can't drop other elements inside it
droppable: false,
// Traits (Settings)
traits: ['name', 'placeholder', {
// Change the type of the input (text, password, email, etc.)
type: 'select',
label: 'Type',
name: 'type',
options: inputTypes,
},{
// Can make it required for the form
type: 'checkbox',
label: 'Required',
name: 'required',
}],
}),
},
// The second argument of .extend are static methods and we'll put inside our
// isComponent() method. As you're putting a new Component type on top of the stack,
// not declaring isComponent() might probably break stuff, especially if you extend
// the default one.
{
isComponent: function(el) {
if(el.tagName == 'INPUT'){
return {type: 'input'};
}
},
}),
// Define the View
view: defaultType.view,
});
```
The code above is pretty much self-explanatory and as you see a lot of work is basically done on top of the Model properties.
The *View* is just extending the default one, so to cover also this part let's add some random behavior.
```js
comps.addType('input', {
model: {...},
view: defaultType.view.extend({
// Bind events
events: {
// If you want to bind the event to children elements
// 'click .someChildrenClass': 'methodName',
click: 'handleClick',
dblclick: function(){
alert('Hi!');
}
},
// It doesn't make too much sense this method inside the component
// but it's ok as an example
randomHex: function() {
return '#' + Math.floor(Math.random()*16777216).toString(16);
},
handleClick: function(e) {
this.model.set('style', {color: this.randomHex()}); // <- Affects the final HTML code
this.el.style.backgroundColor = this.randomHex(); // <- Doesn't affect the final HTML code
// Tip: updating the model will reflect the changes to the view, so, in this case,
// if you put the model change after the DOM one this will override the backgroundColor
// change made before
},
// The render() should return 'this'
render: function () {
// Extend the original render method
defaultType.view.prototype.render.apply(this, arguments);
this.el.placeholder = 'Text here'; // <- Doesn't affect the final HTML code
return this;
},
}),
});
```
From the example above you can notice few interesting things: how to bind events, how to update directly the DOM and how to update the model. The difference between updating the DOM and the model is that the HTML code (the one you get with `editor.getHtml()`) is generated from the *Model* so updating directly the DOM will not affect it, it's just the change for the canvas.
## Update Component type
Here an example of how easily you can update/override the component
```js
var originalMap = comps.getType('map');
comps.addType('map', {
model: originalMap.model.extend({
// Override how the component is rendered to HTML
toHTML: function() {
return '<div>My Custom Map</div>';
},
}, {
isComponent: function(el) {
// ... new logic for isComponent
},
}),
view: originalMap.view
});
```
## Improvement over addType <Badge text="0.14.50+"/>
Now, with the [0.14.50](https://github.com/artf/grapesjs/releases/tag/v0.14.50) release, defining new components or extending them is a bit easier (without breaking the old process)
* If you don't specify the type to extend, the `default` one will be used. In that case, you just
use objects for `model` and `view`
* The `defaults` property, in the `model`, will be merged automatically with defaults of the parent component
* If you use an object in `model` you can specify `isComponent` outside or omit it. In this case,
the `isComponent` is not mandatory but without it means the parser won't be able to identify the component
if not explicitly declared (eg. `<div data-gjs-type="new-component">...</div>`)
**Before**
```js
const defaultType = comps.getType('default');
comps.addType('new-component', {
model: defaultType.model.extend({
defaults: {
...defaultType.model.prototype.defaults,
someprop: 'somevalue',
},
...
}, {
// Even if it returns false, declaring isComponent is mandatory
isComponent(el) {
return false;
},
}),
view: defaultType.view.extend({ ... });
});
```
**After**
```js
comps.addType('new-component', {
// We can even omit isComponent here, as `false` return will be the default behavior
isComponent: el => false,
model: {
defaults: {
someprop: 'somevalue',
},
...
},
view: { ... };
});
```
* If you need to extend some component, you can use `extend` and `extendView` property.
* You can now omit `view` property if you don't need to change it
**Before**
```js
const originalMap = comps.getType('map');
comps.addType('map', {
model: originalMap.model.extend({
...
}, {
isComponent(el) {
// ... usually, you'd reuse the same logic
},
}),
// Even if I do nothing in view, I have to specify it
view: originalMap.view
});
```
**After**
The `map` type is already defined, so it will be used as a base for the model and view.
We can skip `isComponent` if the recognition logic is the same of the extended component.
```js
comps.addType('map', {
model: { ... },
});
```
Extend the `model` and `view` with some other, already defined, components.
```js
comps.addType('map', {
extend: 'other-defined-component',
model: { ... }, // Will extend 'other-defined-component'
view: { ... }, // Will extend 'other-defined-component'
// `isComponent` will be taken from `map`
});
```
```js
comps.addType('map', {
extend: 'other-defined-component',
model: { ... }, // Will extend 'other-defined-component'
extendView: 'other-defined-component-2',
view: { ... }, // Will extend 'other-defined-component-2'
// `isComponent` will be taken from `map`
});
```
### Extend parent functions <Badge text="0.14.60+"/>
When you need to reuse functions, of the parent you're extending, you can avoid writing something like this in any function:
```js
domc.getType('parent-type').model.prototype.init.apply(this, arguments);
```
by using `extendFn` and `extendFnView` arrays:
```js
domc.addType('new-type', {
extend: 'parent-type',
extendFn: ['init'], // array of model functions to extend
model: {
init() {
// do something;
},
}
});
```
The same would be for the view by using `extendFnView`
## Lifecycle Hooks
Each component triggers different lifecycle hooks, which allows you to add custom actions at their specific stages.
We can distinguish 2 different types of hooks: **global** and **local**.
You define **local** hooks when you create/extend a component type (usually via some `model`/`view` method) and the reason is to react to an event of that
particular component type. Instead, the **global** one, will be called indistinctly on any component (you listen to them via `editor.on`) and you can make
use of them for a more generic use case or also listen to them inside other components.
Let's see below the flow of all hooks:
* **Local hook**: `model.init()` method, executed once the model of the component is initiliazed
* **Global hook**: `component:create` event, called right after `model.init()`. The model is passed as an argument to the callback function.
Es. `editor.on('component:create', model => console.log('created', model))`
* **Local hook**: `view.init()` method, executed once the view of the component is initiliazed
* **Local hook**: `view.onRender()` method, executed once the component is rendered on the canvas
* **Global hook**: `component:mount` event, called right after `view.onRender()`. The model is passed as an argument to the callback function.
* **Local hook**: `model.updated()` method, executes when some property of the model is updated.
* **Global hook**: `component:update` event, called after `model.updated()`. The model is passed as an argument to the callback function.
You can also listen to specific property change via `component:update:{propertyName}`
* **Local hook**: `model.removed()` method, executed when the component is removed.
* **Global hook**: `component:remove` event, called after `model.removed()`. The model is passed as an argument to the callback function.
Below you can find an example usage of all the hooks
```js
editor.DomComponents.addType('test-component', {
model: {
defaults: {
testprop: 1,
},
init() {
console.log('Local hook: model.init');
this.listenTo(this, 'change:testprop', this.handlePropChange);
// Here we can listen global hooks with editor.on('...')
},
updated(property, value, prevValue) {
console.log('Local hook: model.updated',
'property', property, 'value', value, 'prevValue', prevValue);
},
removed() {
console.log('Local hook: model.removed');
},
handlePropChange() {
console.log('The value of testprop', this.get('testprop'));
}
},
view: {
init() {
console.log('Local hook: view.init');
},
onRender() {
console.log('Local hook: view.onRender');
},
},
});
// A block for the custom component
editor.BlockManager.add('test-component', {
label: 'Test Component',
content: '<div data-gjs-type="test-component">Test Component</div>',
});
// Global hooks
editor.on(`component:create`, model => console.log('Global hook: component:create', model.get('type')));
editor.on(`component:mount`, model => console.log('Global hook: component:mount', model.get('type')));
editor.on(`component:update:testprop`, model => console.log('Global hook: component:update:testprop', model.get('type')));
editor.on(`component:remove`, model => console.log('Global hook: component:remove', model.get('type')));
```
## Components & JS
If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page
[Components & JS](Components-js.html)
## Hints
```html
<div id="gjs">
...
<cutom-element></cutom-element>
...
</div>
<script>
var editor = grapesjs.init({
container : '#gjs',
fromElement: true,
});
editor.DomComponents.addType('cutom-element-type', {...});
</script>
```
In the example above the editor will not get the new type from the HTML because the content is already parsed and appended, so it'll get it only with new components (eg. from Blocks)
Solution 1: turn off `autorender`
```html
<script>
var editor = grapesjs.init({
autorender: 0,
container : '#gjs',
fromElement: true,
});
editor.DomComponents.addType('cutom-element-type', {...});
// after all new types
editor.render();
</script>
```
Solution 2: put all the stuff inside a plugin ([Creating plugins](Plugins.html))

2
docs/modules/Components.md

@ -40,7 +40,7 @@ When we pass an HTML string to the editor like this:
For each DOM element (`div`, `img`, `span`, etc.) the editor will create and store an object representation. Every future change to the template will be made on top of this structure, which will then reflect on the canvas. So each object, usually called *Model* (or state/store), will be the source of truth for the template, but what exactly does that mean? For each DOM element (`div`, `img`, `span`, etc.) the editor will create and store an object representation. Every future change to the template will be made on top of this structure, which will then reflect on the canvas. So each object, usually called *Model* (or state/store), will be the source of truth for the template, but what exactly does that mean?
In more practical example, once the template is rendered on the canvas, if you try to remove one of its elements (eg. by using using the browser inspector) and ask the editor to print the HTML (using `editor.getHtml()`) you'll see that the element will still be there. This is because the editor relies on Models and not on the DOM elements inside the canvas. This approach allows us to be extremely flexible on how we generate the final code (from the *Model*) and how to render it inside the canvas (from the *View*). In more practical example, once the template is rendered on the canvas, if you try to remove one of its elements (eg. by using the browser inspector) and ask the editor to print the HTML (using `editor.getHtml()`) you'll see that the element will still be there. This is because the editor relies on Models and not on the DOM elements inside the canvas. This approach allows us to be extremely flexible on how we generate the final code (from the *Model*) and how to render it inside the canvas (from the *View*).

552
docs/modules/Traits.md

@ -4,70 +4,56 @@ title: Trait Manager
# Trait Manager # Trait Manager
In GrapesJS, Traits can define different parameters and behaviors of a component. The user generally will see traits as the *Settings* of a component. A common use of traits is to customize element attributes (eg. `placeholder` for inputs) and in this case the editor comes already with some built-in, easy configurable, types. In GrapesJS, Traits define different parameters and behaviors of a component. The user generally will see traits as the *Settings* of a component. A common use of traits is to customize element attributes (eg. `placeholder` for `<input>`) or you can also bind them to the properties of your components and react on their changes.
[[toc]] ::: warning
This guide is referring to GrapesJS v0.15.3 or higher.<br><br>
To get a better understanding of the content in this guide we recommend reading [Components](Components.html) first
## Built-in trait types :::
* text
* number
* checkbox
* select
* color
[[toc]]
## Add Traits to Components ## Add Traits to Components
You can add traits to the component by extending them or while creating a new one. Let's see in this example how to make inputs more customizable by the editor. All components, by default, contain two traits: id and title (at the moment of writing). So, if you select an input and open the Settings panel you will see this: Generally you define traits on the definition of your new custom components (or by extending another one). Let's see in this example how to make inputs more customizable by the editor.
All components, by default, contain two traits: `id` and `title` (at the moment of writing). So, if you select an input and open the Settings panel you will see this:
<img :src="$withBase('/default-traits.png')"> <img :src="$withBase('/default-traits.png')">
In this example we are going to create a new Component. ([Check here](Components.html) for more details about the creation of new components with a new set of traits We can start by creating a new custom `input` component in this way:
```js ```js
var editor = grapesjs.init({...}); editor.DomComponents.addType('input', {
var domComps = editor.DomComponents; isComponent: el => el.tagName == 'INPUT',
var dType = domComps.getType('default'); model: {
var dModel = dType.model; defaults: {
var dView = dType.view;
domComps.addType('input', {
model: dModel.extend({
defaults: Object.assign({}, dModel.prototype.defaults, {
traits: [ traits: [
// strings are automatically converted to text types // Strings are automatically converted to text types
'name', 'name', // Same as: { type: 'text', name: 'name' }
'placeholder', 'placeholder',
{ {
type: 'select', type: 'select', // Type of the trait
label: 'Type', label: 'Type', // The label you will see in Settings
name: 'type', name: 'type', // The name of the attribute/property to use on component
options: [ options: [
{value: 'text', name: 'Text'}, { id: 'text', name: 'Text'},
{value: 'email', name: 'Email'}, { id: 'email', name: 'Email'},
{value: 'password', name: 'Password'}, { id: 'password', name: 'Password'},
{value: 'number', name: 'Number'}, { id: 'number', name: 'Number'},
] ]
}, { }, {
type: 'checkbox', type: 'checkbox',
label: 'Required',
name: 'required', name: 'required',
}], }],
}), // As by default, traits are binded to attributes, so to define
}, { // their initial value we can use attributes
isComponent: function(el) { attributes: { type: 'text', required: true },
if(el.tagName == 'INPUT'){
return {type: 'input'};
}
}, },
}), },
view: dView,
}); });
``` ```
@ -75,76 +61,470 @@ Now the result will be
<img :src="$withBase('/input-custom-traits.png')"> <img :src="$withBase('/input-custom-traits.png')">
Traits modify attributes of the model (which than reflected in canvas), but you can also have traits which change the property If you want you can also define traits dynamically via functions, which will be created on component initialization. It might be useful if you need to create traits based on some other component characteristic.
```js ```js
... editor.DomComponents.addType('input', {
traits: [{ isComponent: el => el.tagName == 'INPUT',
type: 'text', model: {
label: 'Test', defaults: {
name: 'model-prop-name', traits(component) {
changeProp: 1, const result = [];
}],
... // Example of some logic
if (component.get('draggable')) {
result.push('name');
} else {
result.push({
type: 'select',
// ....
});
}
return result;
}
},
},
});
``` ```
In this way you're able to listen for changes and react with your own logic If you need to react to some change of the trait you can subscribe to their attribute listeners
```js ```js
editor.DomComponents.addType('input', { editor.DomComponents.addType('input', {
model: dModel.extend({ model: {
init() { defaults: {
this.listenTo(this, 'change:model-prop-name', this.doStuff); // ...
}, },
init() {
this.on('change:attributes:type', this.handleTypeChange);
},
handleTypeChange() {
console.log('Input type changed to: ', this.getAttributes().type);
},
}
})
```
As already mentioned, by default, traits modify attributes of the model, but you can also bind them to the properties by using `changeProp` options.
```js
editor.DomComponents.addType('input', {
model: {
defaults: {
// ...
traits: [
{
name: 'placeholder',
changeProp: 1,
}
// ...
],
// As we switched from attributes to properties the
// initial value should be set from the property
placeholder: 'Initial placeholder',
},
init() {
// Also the listener changes from `change:attributes:*` to `change:*`
this.on('change:placeholder', this.handlePlhChange);
},
// ...
}
})
```
## Built-in trait types
GrapesJS comes along with few built-in types that you can use to define your traits:
### Text
Simple text input
```js
{
type: 'text', // If you don't specify the type, the `text` is the default one
name: 'my-trait', // Required and available for all traits
label: 'My trait', // The label you will see near the input
// label: false, // If you set label to `false`, the label column will be removed
placeholder: 'Insert text', // Placeholder to show inside the input
}
```
### Number
Input for numbers
```js
{
type: 'number',
// ...
placeholder: '0-100',
min: 0, // Minimum number value
max: 100, // Maximum number value
step: 5, // Number of steps
}
```
### Checkbox
Simple checkbox input
```js
{
type: 'checkbox',
// ...
valueTrue: 'YES', // Value to assign when is checked, default: `true`
valueFalse: 'NO', // Value to assign when is unchecked, default: `false`
}
```
### Select
Select input with options
```js
{
type: 'select',
// ...
options: [ // Array of options
{ id: 'opt1', name: 'Option 1'},
{ id: 'opt2', name: 'Option 2'},
]
}
```
### Color
Color picker
```js
{
type: 'color',
// ...
}
```
### Button
Button with a command to assign
```js
{
type: 'button',
// ...
text: 'Click me',
full: true, // Full width button
command: editor => alert('Hello'),
// or you can just specify the Command ID
command: 'some-command',
}
```
## Updating traits at run-time
If you need to change some trait on your component you can update it wherever you want by using [Component API](/api/component.html)
The trait is a simple property of the component so to get the complete list of current traits you can use this:
```js
const component = editor.getSelected(); // Component selected in canvas
const traits = component.get('traits');
traits.forEach(trait => console.log(trait.props()))
```
In case you need a single one:
doStuff() {} ```js
}), const component = editor.getSelected();
... console.log(component.getTrait('type').props()); // Finds by the `name` of the trait
```
If you want, for example, updating some property of the trait, do this:
```js
// Let's update `options` of our `type` trait, defined in Input component
const component = editor.getSelected();
component.getTrait('type').set('options', [
{ id: 'opt1', name: 'New option 1'},
{ id: 'opt2', name: 'New option 2'},
]);
// or with multiple values
component.getTrait('type').set({
label: 'My type',
options: [...],
}); });
``` ```
You can also easily add new traits or remove some other by using [`addTrait`](/api/component.html#addtrait)/[`removeTrait`](/api/component.html#removetrait)
```js
// Add new trait
const component = editor.getSelected();
component.addTrait({
name: 'type',
...
}, { at: 0 });
// The `at` option indicates the index where to place the new trait,
// without it, the trait will be appended at the end of the list
// Remove trait
component.removeTrait('type');
```
## Define new Trait type ## Define new Trait type
If built-in types are not enough (eg. something with more complex UI) you can define a new one. Generally, for most of the cases, default types are enough, but sometimes you might need something more.
Let's see this simple `textarea` element which updates contents of the component. In that case, you can define a totally new type of trait and bind any kind of element to it.
### Create element
Let's update the default `link` Component with a new kind of trait. This is the default situation of traits for a simple link.
<img :src="$withBase('/default-link-comp.jpg')">
Let's just replace all of its traits with a new one, `href-next`, which will allow the user to select the type of href (eg. 'url', 'email', etc.)
```js ```js
// Each new type extends the default Trait // Update component
editor.TraitManager.addType('content', { editor.DomComponents.addType('link', {
events:{ model: {
'keyup': 'onChange', // trigger parent onChange method on keyup defaults: {
traits: [
{
type: 'href-next',
name: 'href',
label: 'New href',
},
]
}
}
});
```
Now you'll see a simple text input because we have not yet defined our new trait type, so let's do it:
```js
editor.TraitManager.addType('href-next', {
// Expects as return a simple HTML string or an HTML element
createInput({ trait }) {
// Here we can decide to use properties from the trait
const traitOpts = trait.get('options') || [];
const options = traitOpts.lenght ? traitOpts : [
{ id: 'url', name: 'URL' },
{ id: 'email', name: 'Email' },
];
// Create a new element container and add some content
const el = document.createElement('div');
el.innerHTML = `
<select class="href-next__type">
${options.map(opt => `<option value="${opt.id}">${opt.name}</option>`).join('')}
</select>
<div class="href-next__url-inputs">
<input class="href-next__url" placeholder="Insert URL"/>
</div>
<div class="href-next__email-inputs">
<input class="href-next__email" placeholder="Insert email"/>
<input class="href-next__email-subject" placeholder="Insert subject"/>
</div>
`;
// Let's make our content interactive
const inputsUrl = el.querySelector('.href-next__url-inputs');
const inputsEmail = el.querySelector('.href-next__email-inputs');
const inputType = el.querySelector('.href-next__type');
inputType.addEventListener('change', ev => {
switch (ev.target.value) {
case 'url':
inputsUrl.style.display = '';
inputsEmail.style.display = 'none';
break;
case 'email':
inputsUrl.style.display = 'none';
inputsEmail.style.display = '';
break;
}
});
return el;
},
});
```
From the example above we simply created our custom inputs (by giving also the possibility to use `option` trait property) and defined some input switch behavior on the type change. Now the result would be something like this
<img :src="$withBase('/docs-init-link-trait.jpg')">
### Update layout
Before going forward and making our trait work let's talk about the layout structure of a trait. You might have noticed that the trait is composed by the label and input columns, for this reason, GrapesJS allows you to customize both of them.
For the label customization you might use `createLabel`
```js
editor.TraitManager.addType('href-next', {
// Expects as return a simple HTML string or an HTML element
createLabel({ label }) {
return `<div>
<div>Before</div>
${label}
<div>After</div>
</div>`;
},
// ...
});
```
You've probably seen already that in trait definition you can setup `label: false` to completely remove the label column, but in case you need to force this behavior in all instances of this trait type you can use `noLabel` property
```js
editor.TraitManager.addType('href-next', {
noLabel: true,
// ...
});
```
You might also notice that by default GrapesJS applies kind of a wrapper around your inputs, generally is ok for simple inputs but probably is not what you need where you're creating a complex custom trait. To remove the default wrapper you can use the `templateInput` option
```js
editor.TraitManager.addType('href-next', {
// Completely remove the wrapper
templateInput: '',
// Use a new one, by specifying with `data-input` attribute where to place the input container
templateInput: `<div class="custom-input-wrapper">
Before input
<div data-input></div>
After input
</div>`,
// It might also be a function, expects an HTML string as the result
templateInput({ trait }) {
return '<div ...';
}, },
});
```
<img :src="$withBase('/docs-link-trait-raw.jpg')">
In this case, the result will be quite raw and unstyled but the point of custom trait types is to allow you to reuse your own styled inputs, probably already designed and defined (or implemented in some UI framework).
For now, let's keep the default input wrapper and continue with the integration of our custom trait.
### Bind to component
/** At the current state, our element created in `createInput` is not binded to the component so nothing happens when you update inputs, so let's do it now
* Returns the input element
* @return {HTMLElement} ```js
*/ editor.TraitManager.addType('href-next', {
getInputEl: function() { // ...
if (!this.inputEl) {
var input = document.createElement('textarea'); // Update the component based on element changes
input.value = this.target.get('content'); // `elInput` is the result HTMLElement you get from `createInput`
this.inputEl = input; onEvent({ elInput, component, event }) {
const inputType = elInput.querySelector('.href-next__type');
let href = '';
switch (inputType.value) {
case 'url':
const valUrl = elInput.querySelector('.href-next__url').value;
href = valUrl;
break;
case 'email':
const valEmail = elInput.querySelector('.href-next__email').value;
const valSubj = elInput.querySelector('.href-next__email-subject').value;
href = `mailto:${valEmail}${valSubj ? `?subject=${valSubj}` : ''}`;
break;
} }
return this.inputEl;
component.addAttributes({ href })
}, },
});
```
/** Now, most of the stuff should already work (you can update the trait and check the HTML in code preview). You might wonder how the editor captures the input change and how is possible to control it.
* Triggered when the value of the model is changed By default, the base trait wrapper applies a listener on `change` event and calls `onEvent` on any captured event (to be captured the event should be able to [bubble](https://stackoverflow.com/questions/4616694/what-is-event-bubbling-and-capturing)). If you want, for example, to update the component on `input` event you can change the `eventCapture` property
*/
onValueChange: function () { ```js
this.target.set('content', this.model.get('value')); editor.TraitManager.addType('href-next', {
} eventCapture: ['input'], // you can use multiple events in the array
// ...
});
```
The last thing, you might have noticed the wrong initial render of our trait, where inputs are not populated in case of already defined `href` attribute. This step should be done in `onUpdate` method
```js
editor.TraitManager.addType('href-next', {
// ...
// Update elements on the component change
onUpdate({ elInput, component }) {
const href = component.getAttributes().href || '';
const inputType = elInput.querySelector('.href-next__type');
let type = 'url';
if (href.indexOf('mailto:') === 0) {
const inputEmail = elInput.querySelector('.href-next__email');
const inputSubject = elInput.querySelector('.href-next__email-subject');
const mailTo = href.replace('mailto:', '').split('?');
const email = mailTo[0];
const params = (mailTo[1] || '').split('&').reduce((acc, item) => {
const items = item.split('=');
acc[items[0]] = items[1];
return acc;
}, {});
type = 'email';
inputEmail.value = email || '';
inputSubject.value = params.subject || '';
} else {
elInput.querySelector('.href-next__url').value = href;
}
inputType.value = type;
inputType.dispatchEvent(new CustomEvent('change'));
},
}); });
```
// And then use it in your component Now the trait will update even on component change like:
... ```js
traits: [{ editor.getSelected().addAttributes({ href: 'mailto:new-email@test.com?subject=NewSubject' })
type: 'content',
}],
...
``` ```
To recap what we have done so far, to create a custom trait type all you will need are 3 methods:
* `createInput` - Where we define our custom HTML element
* `onEvent` - How to update the component on inputs changes
* `onUpdate` - How to update inputs on component changes
### Result
The final result of what we have done can be seen here
<demo-viewer value="yf6amdqb/10"/>
### Integrate external UI components
By looking at the example above might seems like a lot of code, but at the end, it's just about a little bit of logic and the native DOM API which is not super pretty. If you use a modern UI client framework (eg. Vue, React, etc.) you could see that the integration is even easier. There is how it would be integrating a custom [Vue Slider Component](https://github.com/NightCatSama/vue-slider-component) as a trait
```js
editor.TraitManager.addType('slider', {
createInput({ trait }) {
const vueInst = new Vue({ render: h => h(VueSlider) }).$mount();
const sliderInst = vueInst.$children[0];
sliderInst.$on('change', ev => this.onChange(ev)); // Use onChange to trigger onEvent
this.sliderInst = sliderInst;
return vueInst.$el;
},
onEvent({ component }) {
const value = this.sliderInst.getValue() || 0;
component.addAttributes({ value });
},
onUpdate({ component }) {
const value = component.getAttributes().value || 0;
this.sliderInst.setValue(value);
},
});
```
<demo-viewer value="x9sw2udv/79"/>
The integration with external components is possible by following these simple core points:
1. **Component rendering**: `new Vue({ render: ...`<br/>
Depends on the framework, for example, in React it should be `ReactDOM.render(element, ...`
1. **Change propogation**: `sliderInst.$on('change', ev => this.onChange(ev))`<br/>
The framework should have a mechanism to subscribe to changes and the component [should expose that change](https://nightcatsama.github.io/vue-slider-component/#/api/events)<br/>
We've also used `onChange` method which comes handy when you need to trigger manually the `onEvent` event (you should never call directly `onEvent` method, but only via `onChange` when you need)
1. **Property getters/setters**: [`sliderInst.getValue()`](https://nightcatsama.github.io/vue-slider-component/#/api/methods?hash=getvalue)/ [`sliderInst.setValue(value)`](https://nightcatsama.github.io/vue-slider-component/#/api/methods?hash=setvaluevalue)<br/>
The component should allow to read and write data from the instance

22997
package-lock.json

File diff suppressed because it is too large

58
package.json

@ -1,47 +1,49 @@
{ {
"name": "grapesjs", "name": "grapesjs",
"description": "Free and Open Source Web Builder Framework", "description": "Free and Open Source Web Builder Framework",
"version": "0.14.62", "version": "0.15.3",
"author": "Artur Arseniev", "author": "Artur Arseniev",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"homepage": "http://grapesjs.com", "homepage": "http://grapesjs.com",
"main": "dist/grapes.js", "main": "dist/grapes.min.js",
"sideEffects": [ "*.vue", "*.css", "*.scss" ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/artf/grapesjs.git" "url": "https://github.com/artf/grapesjs.git"
}, },
"dependencies": { "dependencies": {
"backbone": "^1.3.3", "@babel/runtime": "^7.5.5",
"backbone": "1.3.3",
"backbone-undo": "^0.2.5", "backbone-undo": "^0.2.5",
"cash-dom": "^1.3.7", "cash-dom": "^1.3.7",
"codemirror": "^5.42.0", "codemirror": "^5.48.2",
"codemirror-formatting": "^1.0.0", "codemirror-formatting": "^1.0.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"keymaster": "^1.6.2", "keymaster": "^1.6.2",
"promise-polyfill": "^8.1.0", "promise-polyfill": "^8.1.3",
"spectrum-colorpicker": "^1.8.0", "spectrum-colorpicker": "^1.8.0",
"underscore": "^1.9.1" "underscore": "^1.9.1"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.3", "@babel/core": "^7.5.5",
"babel-loader": "^7.1.5", "@babel/plugin-transform-runtime": "^7.5.5",
"babel-plugin-transform-object-rest-spread": "^6.26.0", "@babel/preset-env": "^7.5.5",
"babel-preset-env": "^1.7.0", "babel-loader": "^8.0.6",
"documentation": "^8.1.2", "documentation": "^8.1.2",
"eslint": "^5.9.0", "eslint": "^6.1.0",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"husky": "^1.2.0", "husky": "^2.7.0",
"jest": "^23.6.0", "jest": "^24.8.0",
"lint-staged": "^8.1.0", "lint-staged": "^8.2.1",
"node-sass": "^4.10.0", "node-sass": "^4.12.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "^1.15.3", "prettier": "^1.18.2",
"sinon": "^7.1.1", "sinon": "^7.4.1",
"string-replace-loader": "^2.1.1", "string-replace-loader": "^2.2.0",
"vuepress": "^0.10.2", "vuepress": "^0.10.2",
"webpack": "^4.26.1", "webpack": "^4.39.1",
"webpack-cli": "^3.1.2", "webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.1.10", "webpack-dev-server": "^3.8.0",
"whatwg-fetch": "^3.0.0" "whatwg-fetch": "^3.0.0"
}, },
"keywords": [ "keywords": [
@ -58,20 +60,27 @@
"babel": { "babel": {
"presets": [ "presets": [
[ [
"env", "@babel/preset-env",
{ {
"targets": [ "targets": [
"> 1%", "> 1%",
"ie 11", "ie 11",
"safari 8" "safari 8"
], ],
"useBuiltIns": true "modules": false
} }
] ]
], ],
"plugins": [ "plugins": [
"transform-object-rest-spread" "@babel/plugin-transform-runtime"
] ],
"env": {
"test": {
"presets": [
"@babel/preset-env"
]
}
}
}, },
"lint-staged": { "lint-staged": {
"{src,test}/**/*.js": [ "{src,test}/**/*.js": [
@ -112,6 +121,7 @@
"start": "run-p start:*", "start": "run-p start:*",
"start:css": "npm run build:css -- -w", "start:css": "npm run build:css -- -w",
"start:js": "webpack-dev-server --open --progress --colors", "start:js": "webpack-dev-server --open --progress --colors",
"stats": "webpack --env=prod --profile --json > stats.json && webpack-bundle-analyzer stats.json",
"format": "prettier --single-quote --write './{src,test}/**/*.js'", "format": "prettier --single-quote --write './{src,test}/**/*.js'",
"test": "jest", "test": "jest",
"test:dev": "jest --watch" "test:dev": "jest --watch"

2
src/asset_manager/config/config.js

@ -1,4 +1,4 @@
module.exports = { export default {
// Default assets // Default assets
// eg. [ // eg. [
// 'https://...image1.png', // 'https://...image1.png',

11
src/asset_manager/index.js

@ -30,12 +30,13 @@
* @module AssetManager * @module AssetManager
*/ */
module.exports = () => { import defaults from './config/config';
import Assets from './model/Assets';
import AssetsView from './view/AssetsView';
import FileUpload from './view/FileUploader';
export default () => {
let c = {}; let c = {};
const defaults = require('./config/config');
const Assets = require('./model/Assets');
const AssetsView = require('./view/AssetsView');
const FileUpload = require('./view/FileUploader');
let assets, am, fu; let assets, am, fu;
return { return {

4
src/asset_manager/model/Asset.js

@ -1,4 +1,6 @@
module.exports = require('backbone').Model.extend({ import Backbone from 'backbone';
export default Backbone.Model.extend({
idAttribute: 'src', idAttribute: 'src',
defaults: { defaults: {

6
src/asset_manager/model/AssetImage.js

@ -1,11 +1,11 @@
const Asset = require('./Asset'); import Asset from './Asset';
module.exports = Asset.extend({ export default Asset.extend({
defaults: { defaults: {
...Asset.prototype.defaults, ...Asset.prototype.defaults,
type: 'image', type: 'image',
unitDim: 'px', unitDim: 'px',
height: 0, height: 0,
width: 0 width: 0
}, }
}); });

37
src/asset_manager/model/Assets.js

@ -1,22 +1,23 @@
import Backbone from 'backbone';
import AssetImage from './AssetImage';
import AssetImageView from './../view/AssetImageView';
import TypeableCollection from 'domain_abstract/model/TypeableCollection'; import TypeableCollection from 'domain_abstract/model/TypeableCollection';
module.exports = require('backbone') export default Backbone.Collection.extend(TypeableCollection).extend({
.Collection.extend(TypeableCollection) types: [
.extend({ {
types: [ id: 'image',
{ model: AssetImage,
id: 'image', view: AssetImageView,
model: require('./AssetImage'), isType(value) {
view: require('./../view/AssetImageView'), if (typeof value == 'string') {
isType(value) { return {
if (typeof value == 'string') { type: 'image',
return { src: value
type: 'image', };
src: value
};
}
return value;
} }
return value;
} }
] }
}); ]
});

3
src/asset_manager/view/AssetImageView.js

@ -1,6 +1,7 @@
import { isFunction } from 'underscore'; import { isFunction } from 'underscore';
import AssetView from './AssetView';
module.exports = require('./AssetView').extend({ export default AssetView.extend({
events: { events: {
'click [data-toggle=asset-remove]': 'onRemove', 'click [data-toggle=asset-remove]': 'onRemove',
click: 'onClick', click: 'onClick',

5
src/asset_manager/view/AssetView.js

@ -1,6 +1,7 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { clone } from 'underscore';
module.exports = Backbone.View.extend({ export default Backbone.View.extend({
initialize(o = {}) { initialize(o = {}) {
this.options = o; this.options = o;
this.collection = o.collection; this.collection = o.collection;
@ -38,7 +39,7 @@ module.exports = Backbone.View.extend({
* */ * */
updateTarget(target) { updateTarget(target) {
if (target && target.set) { if (target && target.set) {
target.set('attributes', _.clone(target.get('attributes'))); target.set('attributes', clone(target.get('attributes')));
target.set('src', this.model.get('src')); target.set('src', this.model.get('src'));
} }
}, },

5
src/asset_manager/view/AssetsView.js

@ -1,9 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
var AssetView = require('./AssetView');
var AssetImageView = require('./AssetImageView');
var FileUploader = require('./FileUploader');
module.exports = Backbone.View.extend({ export default Backbone.View.extend({
events: { events: {
submit: 'handleSubmit' submit: 'handleSubmit'
}, },

6
src/asset_manager/view/FileUploader.js

@ -1,10 +1,10 @@
import _ from 'underscore'; import { template } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
import fetch from 'utils/fetch'; import fetch from 'utils/fetch';
module.exports = Backbone.View.extend( export default Backbone.View.extend(
{ {
template: _.template(` template: template(`
<form> <form>
<div id="<%= pfx %>title"><%= title %></div> <div id="<%= pfx %>title"><%= title %></div>
<input type="file" id="<%= uploadId %>" name="file" accept="*/*" <%= disabled ? 'disabled' : '' %> <%= multiUpload ? 'multiple' : '' %>/> <input type="file" id="<%= uploadId %>" name="file" accept="*/*" <%= disabled ? 'disabled' : '' %> <%= multiUpload ? 'multiple' : '' %>/>

2
src/block_manager/config/config.js

@ -1,4 +1,4 @@
module.exports = { export default {
// Specify the element to use as a container, string (query) or HTMLElement // Specify the element to use as a container, string (query) or HTMLElement
// With the empty value, nothing will be rendered // With the empty value, nothing will be rendered
appendTo: '', appendTo: '',

12
src/block_manager/index.js

@ -26,13 +26,13 @@
* @module BlockManager * @module BlockManager
*/ */
import { isElement } from 'underscore'; import { isElement } from 'underscore';
import defaults from './config/config';
import Blocks from './model/Blocks';
import BlockCategories from './model/Categories';
import BlocksView from './view/BlocksView';
module.exports = () => { export default () => {
var c = {}, var c = {};
defaults = require('./config/config'),
Blocks = require('./model/Blocks'),
BlockCategories = require('./model/Categories'),
BlocksView = require('./view/BlocksView');
var blocks, blocksVisible, blocksView; var blocks, blocksVisible, blocksView;
var categories = []; var categories = [];

4
src/block_manager/model/Block.js

@ -1,7 +1,7 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
var Category = require('./Category'); import Category from './Category';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
defaults: { defaults: {
// If true, triggers an 'active' event on dropped component // If true, triggers an 'active' event on dropped component
activate: 0, activate: 0,

4
src/block_manager/model/Blocks.js

@ -1,6 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
var Block = require('./Block'); import Block from './Block';
module.exports = Backbone.Collection.extend({ export default Backbone.Collection.extend({
model: Block model: Block
}); });

5
src/block_manager/model/Categories.js

@ -1,5 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import Category from './Category';
module.exports = Backbone.Collection.extend({ export default Backbone.Collection.extend({
model: require('./Category') model: Category
}); });

2
src/block_manager/model/Category.js

@ -1,6 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
defaults: { defaults: {
id: '', id: '',
label: '', label: '',

2
src/block_manager/view/BlockView.js

@ -2,7 +2,7 @@ import Backbone from 'backbone';
import { isObject } from 'underscore'; import { isObject } from 'underscore';
import { on, off, hasDnd } from 'utils/mixins'; import { on, off, hasDnd } from 'utils/mixins';
module.exports = Backbone.View.extend({ export default Backbone.View.extend({
events: { events: {
click: 'handleClick', click: 'handleClick',
mousedown: 'startDrag', mousedown: 'startDrag',

8
src/block_manager/view/BlocksView.js

@ -1,9 +1,9 @@
import Backbone from 'backbone';
import { isString, isObject, bindAll } from 'underscore'; import { isString, isObject, bindAll } from 'underscore';
import BlockView from './BlockView';
import CategoryView from './CategoryView';
const BlockView = require('./BlockView'); export default Backbone.View.extend({
const CategoryView = require('./CategoryView');
module.exports = require('backbone').View.extend({
initialize(opts, config) { initialize(opts, config) {
bindAll(this, 'getSorter', 'onDrag', 'onDrop'); bindAll(this, 'getSorter', 'onDrag', 'onDrop');
this.config = config || {}; this.config = config || {};

6
src/block_manager/view/CategoryView.js

@ -1,8 +1,8 @@
import _ from 'underscore'; import { template } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.View.extend({ export default Backbone.View.extend({
template: _.template(` template: template(`
<div class="<%= pfx %>title"> <div class="<%= pfx %>title">
<i class="<%= pfx %>caret-icon"></i> <i class="<%= pfx %>caret-icon"></i>
<%= label %> <%= label %>

2
src/canvas/config/config.js

@ -1,4 +1,4 @@
module.exports = { export default {
stylePrefix: 'cv-', stylePrefix: 'cv-',
/* /*

17
src/canvas/index.js

@ -32,16 +32,17 @@
import { on, off, hasDnd, getElement, getPointerEvent } from 'utils/mixins'; import { on, off, hasDnd, getElement, getPointerEvent } from 'utils/mixins';
import Droppable from 'utils/Droppable'; import Droppable from 'utils/Droppable';
import defaults from './config/config';
import Canvas from './model/Canvas';
import canvasView from './view/CanvasView';
const { requestAnimationFrame } = window; const { requestAnimationFrame } = window;
module.exports = () => { export default () => {
var c = {}, let c = {};
defaults = require('./config/config'), let canvas;
Canvas = require('./model/Canvas'), let frameRect;
CanvasView = require('./view/CanvasView'); let CanvasView;
var canvas;
var frameRect;
return { return {
/** /**
@ -75,7 +76,7 @@ module.exports = () => {
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix; if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
canvas = new Canvas(config); canvas = new Canvas(config);
CanvasView = new CanvasView({ CanvasView = new canvasView({
model: canvas, model: canvas,
config: c config: c
}); });

4
src/canvas/model/Canvas.js

@ -1,7 +1,7 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
var Frame = require('./Frame'); import Frame from './Frame';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
defaults: { defaults: {
frame: '', frame: '',
wrapper: '', wrapper: '',

2
src/canvas/model/Frame.js

@ -1,6 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
defaults: { defaults: {
wrapper: '', wrapper: '',
width: '', width: '',

19
src/canvas/view/CanvasView.js

@ -1,4 +1,5 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
import { bindAll } from 'underscore';
import { import {
on, on,
off, off,
@ -7,11 +8,12 @@ import {
isTextNode, isTextNode,
getElRect getElRect
} from 'utils/mixins'; } from 'utils/mixins';
const FrameView = require('./FrameView'); import FrameView from './FrameView';
const $ = Backbone.$; const $ = Backbone.$;
let timerZoom; let timerZoom;
module.exports = Backbone.View.extend({ export default Backbone.View.extend({
events: { events: {
wheel: 'onWheel' wheel: 'onWheel'
}, },
@ -25,7 +27,7 @@ module.exports = Backbone.View.extend({
}, },
initialize(o) { initialize(o) {
_.bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff', 'onKeyPress'); bindAll(this, 'renderBody', 'onFrameScroll', 'clearOff', 'onKeyPress');
on(window, 'scroll resize', this.clearOff); on(window, 'scroll resize', this.clearOff);
const { model } = this; const { model } = this;
this.config = o.config || {}; this.config = o.config || {};
@ -238,6 +240,15 @@ module.exports = Backbone.View.extend({
cursor: -webkit-grabbing; cursor: -webkit-grabbing;
} }
.${ppfx}is__grabbing {
overflow-x: hidden;
}
.${ppfx}is__grabbing,
.${ppfx}is__grabbing * {
cursor: grabbing !important;
}
${conf.canvasCss || ''} ${conf.canvasCss || ''}
${conf.protectedCss || ''} ${conf.protectedCss || ''}
`; `;
@ -251,7 +262,7 @@ module.exports = Backbone.View.extend({
body.append(this.getJsContainer()); body.append(this.getJsContainer());
em.trigger('loaded'); em.trigger('loaded');
this.frame.el.contentWindow.onscroll = this.onFrameScroll; this.frame.el.contentWindow.onscroll = this.onFrameScroll;
this.frame.udpateOffset(); this.frame.updateOffset();
// Avoid the default link behaviour in the canvas // Avoid the default link behaviour in the canvas
body.on( body.on(

13
src/canvas/view/FrameView.js

@ -1,10 +1,11 @@
import Backbone from 'backbone';
import { bindAll } from 'underscore'; import { bindAll } from 'underscore';
import { appendVNodes, empty } from 'utils/dom'; import { appendVNodes, empty } from 'utils/dom';
const motionsEv = const motionsEv =
'transitionend oTransitionEnd transitionend webkitTransitionEnd'; 'transitionend oTransitionEnd transitionend webkitTransitionEnd';
module.exports = require('backbone').View.extend({ export default Backbone.View.extend({
tagName: 'iframe', tagName: 'iframe',
attributes: { attributes: {
@ -12,7 +13,7 @@ module.exports = require('backbone').View.extend({
}, },
initialize(o) { initialize(o) {
bindAll(this, 'udpateOffset'); bindAll(this, 'updateOffset');
this.config = o.config || {}; this.config = o.config || {};
this.ppfx = this.config.pStylePrefix || ''; this.ppfx = this.config.pStylePrefix || '';
this.em = this.config.em; this.em = this.config.em;
@ -44,19 +45,19 @@ module.exports = require('backbone').View.extend({
const noChanges = currW == newW && currH == newH; const noChanges = currW == newW && currH == newH;
style.width = newW; style.width = newW;
style.height = newH; style.height = newH;
this.udpateOffset(); this.updateOffset();
// Prevent fixed highlighting box which appears when on // Prevent fixed highlighting box which appears when on
// component hover during the animation // component hover during the animation
em.stopDefault({ preserveSelected: 1 }); em.stopDefault({ preserveSelected: 1 });
noChanges ? this.udpateOffset() : $el.on(motionsEv, this.udpateOffset); noChanges ? this.updateOffset() : $el.on(motionsEv, this.updateOffset);
}, },
udpateOffset() { updateOffset() {
const em = this.em; const em = this.em;
const offset = em.get('Canvas').getOffset(); const offset = em.get('Canvas').getOffset();
em.set('canvasOffset', offset); em.set('canvasOffset', offset);
em.runDefault({ preserveSelected: 1 }); em.runDefault({ preserveSelected: 1 });
this.$el.off(motionsEv, this.udpateOffset); this.$el.off(motionsEv, this.updateOffset);
}, },
getDoc() { getDoc() {

2
src/code_manager/config/config.js

@ -1,4 +1,4 @@
module.exports = { export default {
// Style prefix // Style prefix
stylePrefix: 'cm-', stylePrefix: 'cm-',

21
src/code_manager/index.js

@ -18,17 +18,16 @@
* @module CodeManager * @module CodeManager
*/ */
import { isUndefined } from 'underscore'; import { isUndefined } from 'underscore';
import defaults from './config/config';
module.exports = () => { import gHtml from './model/HtmlGenerator';
var c = {}, import gCss from './model/CssGenerator';
defaults = require('./config/config'), import gJson from './model/JsonGenerator';
gHtml = require('./model/HtmlGenerator'), import gJs from './model/JsGenerator';
gCss = require('./model/CssGenerator'), import eCM from './model/CodeMirrorEditor';
gJson = require('./model/JsonGenerator'), import editorView from './view/EditorView';
gJs = require('./model/JsGenerator'),
eCM = require('./model/CodeMirrorEditor'), export default () => {
editorView = require('./view/EditorView'); var c = {};
var generators = {}, var generators = {},
defGenerators = {}, defGenerators = {},
viewers = {}, viewers = {},

10
src/code_manager/model/CodeMirrorEditor.js

@ -1,11 +1,11 @@
import { bindAll } from 'underscore'; import { bindAll } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
var CodeMirror = require('codemirror/lib/codemirror'); import CodeMirror from 'codemirror/lib/codemirror';
var htmlMode = require('codemirror/mode/htmlmixed/htmlmixed'); import 'codemirror/mode/htmlmixed/htmlmixed';
var cssMode = require('codemirror/mode/css/css'); import 'codemirror/mode/css/css';
var formatting = require('codemirror-formatting'); import 'codemirror-formatting';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
defaults: { defaults: {
input: '', input: '',
label: '', label: '',

3
src/code_manager/model/CssGenerator.js

@ -1,8 +1,9 @@
import Backbone from 'backbone';
import { isUndefined, each } from 'underscore'; import { isUndefined, each } from 'underscore';
const maxValue = Number.MAX_VALUE; const maxValue = Number.MAX_VALUE;
module.exports = require('backbone').Model.extend({ export default Backbone.Model.extend({
initialize() { initialize() {
this.compCls = []; this.compCls = [];
this.ids = []; this.ids = [];

2
src/code_manager/model/HtmlGenerator.js

@ -1,6 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
build(model, opts = {}) { build(model, opts = {}) {
const models = model.get('components'); const models = model.get('components');

6
src/code_manager/model/JsGenerator.js

@ -1,10 +1,10 @@
import { extend } from 'underscore'; import { extend } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
mapModel(model) { mapModel(model) {
var code = ''; var code = '';
var script = model.get('script'); var script = model.get('script-export') || model.get('script');
var type = model.get('type'); var type = model.get('type');
var comps = model.get('components'); var comps = model.get('components');
var id = model.getId(); var id = model.getId();
@ -14,7 +14,7 @@ module.exports = Backbone.Model.extend({
var attr = model.get('attributes'); var attr = model.get('attributes');
attr = extend({}, attr, { id }); attr = extend({}, attr, { id });
model.set('attributes', attr, { silent: 1 }); model.set('attributes', attr, { silent: 1 });
var scrStr = model.getScriptString(); var scrStr = model.getScriptString(script);
// If the script was updated, I'll put its code in a separate container // If the script was updated, I'll put its code in a separate container
if (model.get('scriptUpdated')) { if (model.get('scriptUpdated')) {

2
src/code_manager/model/JsonGenerator.js

@ -1,7 +1,7 @@
import { each } from 'underscore'; import { each } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
/** @inheritdoc */ /** @inheritdoc */
build(model) { build(model) {
var json = model.toJSON(); var json = model.toJSON();

2
src/code_manager/view/EditorView.js

@ -1,7 +1,7 @@
import { template } from 'underscore'; import { template } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.View.extend({ export default Backbone.View.extend({
template: template(` template: template(`
<div class="<%= pfx %>editor" id="<%= pfx %><%= codeName %>"> <div class="<%= pfx %>editor" id="<%= pfx %><%= codeName %>">
<div id="<%= pfx %>title"><%= label %></div> <div id="<%= pfx %>title"><%= label %></div>

16
src/commands/index.js

@ -31,7 +31,7 @@ import { isFunction, includes } from 'underscore';
import CommandAbstract from './view/CommandAbstract'; import CommandAbstract from './view/CommandAbstract';
import defaults from './config/config'; import defaults from './config/config';
module.exports = () => { export default () => {
let em; let em;
let c = {}; let c = {};
const commands = {}; const commands = {};
@ -118,17 +118,21 @@ module.exports = () => {
}; };
defaultCommands['tlb-move'] = { defaultCommands['tlb-move'] = {
run(ed, sender, opts) { run(ed, sender, opts = {}) {
let dragger; let dragger;
const em = ed.getModel(); const em = ed.getModel();
const event = opts && opts.event; const event = opts && opts.event;
const sel = ed.getSelected(); const { target } = opts;
const selAll = [...ed.getSelectedAll()]; const sel = target || ed.getSelected();
const selAll = target ? [target] : [...ed.getSelectedAll()];
const nativeDrag = event && event.type == 'dragstart'; const nativeDrag = event && event.type == 'dragstart';
const defComOptions = { preserveSelected: 1 }; const defComOptions = { preserveSelected: 1 };
const modes = ['absolute', 'translate']; const modes = ['absolute', 'translate'];
const hideTlb = () => em.stopDefault(defComOptions); const hideTlb = () => em.stopDefault(defComOptions);
// Dirty patch to prevent parent selection on drop (in absolute mode)
em.set('_cmpDrag', 1);
if (!sel || !sel.get('draggable')) { if (!sel || !sel.get('draggable')) {
console.warn('The element is not draggable'); console.warn('The element is not draggable');
return; return;
@ -137,7 +141,7 @@ module.exports = () => {
const mode = sel.get('dmode') || em.get('dmode'); const mode = sel.get('dmode') || em.get('dmode');
// Without setTimeout the ghost image disappears // Without setTimeout the ghost image disappears
nativeDrag ? setTimeout(() => hideTlb, 0) : hideTlb(); nativeDrag ? setTimeout(hideTlb, 0) : hideTlb();
const onEnd = (e, opts) => { const onEnd = (e, opts) => {
em.runDefault(defComOptions); em.runDefault(defComOptions);
@ -175,7 +179,7 @@ module.exports = () => {
defaultCommands['core:redo'] = e => e.UndoManager.redo(); defaultCommands['core:redo'] = e => e.UndoManager.redo();
commandsDef.forEach(item => { commandsDef.forEach(item => {
const oldCmd = item[2]; const oldCmd = item[2];
const cmd = require(`./view/${item[1]}`); const cmd = require(`./view/${item[1]}`).default;
const cmdName = `core:${item[0]}`; const cmdName = `core:${item[0]}`;
defaultCommands[cmdName] = cmd; defaultCommands[cmdName] = cmd;
if (oldCmd) { if (oldCmd) {

2
src/commands/model/Command.js

@ -1,6 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
module.exports = Backbone.Model.extend({ export default Backbone.Model.extend({
defaults: { defaults: {
id: '' id: ''
} }

4
src/commands/model/Commands.js

@ -1,6 +1,6 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
var Command = require('./Command'); import Command from './Command';
module.exports = Backbone.Collection.extend({ export default Backbone.Collection.extend({
model: Command model: Command
}); });

2
src/commands/view/CanvasClear.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(ed) { run(ed) {
ed.DomComponents.clear(); ed.DomComponents.clear();
ed.CssComposer.clear(); ed.CssComposer.clear();

2
src/commands/view/CanvasMove.js

@ -2,7 +2,7 @@ import { bindAll } from 'underscore';
import { on, off, getKeyChar } from 'utils/mixins'; import { on, off, getKeyChar } from 'utils/mixins';
import Dragger from 'utils/Dragger'; import Dragger from 'utils/Dragger';
module.exports = { export default {
run(ed) { run(ed) {
bindAll(this, 'onKeyUp', 'enableDragger', 'disableDragger'); bindAll(this, 'onKeyUp', 'enableDragger', 'disableDragger');
this.editor = ed; this.editor = ed;

2
src/commands/view/ComponentDelete.js

@ -1,6 +1,6 @@
import { isArray } from 'underscore'; import { isArray } from 'underscore';
module.exports = { export default {
run(ed, sender, opts = {}) { run(ed, sender, opts = {}) {
let components = opts.component || ed.getSelectedAll(); let components = opts.component || ed.getSelectedAll();
components = isArray(components) ? [...components] : [components]; components = isArray(components) ? [...components] : [components];

16
src/commands/view/ComponentDrag.js

@ -1,7 +1,7 @@
import { keys, bindAll, each, isUndefined } from 'underscore'; import { keys, bindAll, each, isUndefined } from 'underscore';
import Dragger from 'utils/Dragger'; import Dragger from 'utils/Dragger';
module.exports = { export default {
run(editor, sender, opts = {}) { run(editor, sender, opts = {}) {
bindAll( bindAll(
this, this,
@ -15,11 +15,8 @@ module.exports = {
'getGuidesTarget' 'getGuidesTarget'
); );
const { target, event, mode, dragger = {} } = opts; const { target, event, mode, dragger = {} } = opts;
const { Canvas } = editor;
const el = target.getEl(); const el = target.getEl();
const scale = Canvas.getZoomMultiplier();
const config = { const config = {
scale,
doc: el.ownerDocument, doc: el.ownerDocument,
onStart: this.onStart, onStart: this.onStart,
onEnd: this.onEnd, onEnd: this.onEnd,
@ -416,12 +413,13 @@ module.exports = {
}); });
}, },
toggleDrag(on) { toggleDrag(enable) {
const { ppfx, editor } = this; const { ppfx, editor } = this;
const methodCls = on ? 'add' : 'remove'; const methodCls = enable ? 'add' : 'remove';
const canvas = this.getCanvas();
const classes = [`${ppfx}is__grabbing`]; const classes = [`${ppfx}is__grabbing`];
classes.forEach(cls => canvas.classList[methodCls](cls)); const { Canvas } = editor;
editor.Canvas[on ? 'startAutoscroll' : 'stopAutoscroll'](); const body = Canvas.getBody();
classes.forEach(cls => body.classList[methodCls](cls));
Canvas[enable ? 'startAutoscroll' : 'stopAutoscroll']();
} }
}; };

2
src/commands/view/ComponentEnter.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(ed) { run(ed) {
if (!ed.Canvas.hasFocus()) return; if (!ed.Canvas.hasFocus()) return;
const toSelect = []; const toSelect = [];

2
src/commands/view/ComponentExit.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(ed, snd, opts = {}) { run(ed, snd, opts = {}) {
if (!ed.Canvas.hasFocus() && !opts.force) return; if (!ed.Canvas.hasFocus() && !opts.force) return;
const toSelect = []; const toSelect = [];

2
src/commands/view/ComponentNext.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(ed) { run(ed) {
if (!ed.Canvas.hasFocus()) return; if (!ed.Canvas.hasFocus()) return;
const toSelect = []; const toSelect = [];

2
src/commands/view/ComponentPrev.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(ed) { run(ed) {
if (!ed.Canvas.hasFocus()) return; if (!ed.Canvas.hasFocus()) return;
const toSelect = []; const toSelect = [];

2
src/commands/view/ComponentStyleClear.js

@ -1,6 +1,6 @@
import { isArray } from 'underscore'; import { isArray } from 'underscore';
module.exports = { export default {
run(ed, sender, opts = {}) { run(ed, sender, opts = {}) {
const { target } = opts; const { target } = opts;
const dc = ed.DomComponents; const dc = ed.DomComponents;

2
src/commands/view/CopyComponent.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(ed) { run(ed) {
const em = ed.getModel(); const em = ed.getModel();
const models = [...ed.getSelectedAll()]; const models = [...ed.getSelectedAll()];

9
src/commands/view/DeleteComponent.js

@ -1,11 +1,12 @@
import _ from 'underscore'; import { extend, bindAll } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
const SelectComponent = require('./SelectComponent'); import SelectComponent from './SelectComponent';
const $ = Backbone.$; const $ = Backbone.$;
module.exports = _.extend({}, SelectComponent, { export default extend({}, SelectComponent, {
init(o) { init(o) {
_.bindAll(this, 'startDelete', 'stopDelete', 'onDelete'); bindAll(this, 'startDelete', 'stopDelete', 'onDelete');
this.hoverClass = this.pfx + 'hover-delete'; this.hoverClass = this.pfx + 'hover-delete';
this.badgeClass = this.pfx + 'badge-red'; this.badgeClass = this.pfx + 'badge-red';
}, },

2
src/commands/view/ExportTemplate.js

@ -1,7 +1,7 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
const $ = Backbone.$; const $ = Backbone.$;
module.exports = { export default {
run(editor, sender, opts = {}) { run(editor, sender, opts = {}) {
sender && sender.set && sender.set('active', 0); sender && sender.set && sender.set('active', 0);
const config = editor.getConfig(); const config = editor.getConfig();

2
src/commands/view/Fullscreen.js

@ -1,6 +1,6 @@
import { isElement } from 'underscore'; import { isElement } from 'underscore';
module.exports = { export default {
/** /**
* Check if fullscreen mode is enabled * Check if fullscreen mode is enabled
* @return {Boolean} * @return {Boolean}

10
src/commands/view/MoveComponent.js

@ -1,15 +1,15 @@
import _ from 'underscore'; import { extend, bindAll } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
import { on, off } from 'utils/mixins'; import { on, off } from 'utils/mixins';
import SelectComponent from './SelectComponent';
import SelectPosition from './SelectPosition';
const SelectComponent = require('./SelectComponent');
const SelectPosition = require('./SelectPosition');
const $ = Backbone.$; const $ = Backbone.$;
module.exports = _.extend({}, SelectPosition, SelectComponent, { export default extend({}, SelectPosition, SelectComponent, {
init(o) { init(o) {
SelectComponent.init.apply(this, arguments); SelectComponent.init.apply(this, arguments);
_.bindAll(this, 'initSorter', 'rollback', 'onEndMove'); bindAll(this, 'initSorter', 'rollback', 'onEndMove');
this.opt = o; this.opt = o;
this.hoverClass = this.ppfx + 'highlighter-warning'; this.hoverClass = this.ppfx + 'highlighter-warning';
this.badgeClass = this.ppfx + 'badge-warning'; this.badgeClass = this.ppfx + 'badge-warning';

2
src/commands/view/OpenAssets.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(editor, sender, opts = {}) { run(editor, sender, opts = {}) {
const modal = editor.Modal; const modal = editor.Modal;
const am = editor.AssetManager; const am = editor.AssetManager;

2
src/commands/view/OpenBlocks.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(editor, sender) { run(editor, sender) {
const bm = editor.BlockManager; const bm = editor.BlockManager;
const pn = editor.Panels; const pn = editor.Panels;

5
src/commands/view/OpenLayers.js

@ -1,8 +1,9 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
const Layers = require('navigator'); import Layers from 'navigator';
const $ = Backbone.$; const $ = Backbone.$;
module.exports = { export default {
run(editor) { run(editor) {
const lm = editor.LayerManager; const lm = editor.LayerManager;
const pn = editor.Panels; const pn = editor.Panels;

5
src/commands/view/OpenStyleManager.js

@ -1,8 +1,9 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
const StyleManager = require('style_manager'); import StyleManager from 'style_manager';
const $ = Backbone.$; const $ = Backbone.$;
module.exports = { export default {
run(em, sender) { run(em, sender) {
this.sender = sender; this.sender = sender;
if (!this.$cn) { if (!this.$cn) {

6
src/commands/view/OpenTraitManager.js

@ -1,6 +1,8 @@
const $ = require('backbone').$; import Backbone from 'backbone';
module.exports = { const $ = Backbone.$;
export default {
run(editor, sender) { run(editor, sender) {
this.sender = sender; this.sender = sender;

2
src/commands/view/PasteComponent.js

@ -1,6 +1,6 @@
import { isArray, contains } from 'underscore'; import { isArray, contains } from 'underscore';
module.exports = { export default {
run(ed) { run(ed) {
const em = ed.getModel(); const em = ed.getModel();
const clp = em.get('clipboard'); const clp = em.get('clipboard');

4
src/commands/view/Preview.js

@ -1,6 +1,6 @@
import { each } from 'underscore'; import { each } from 'underscore';
module.exports = { export default {
getPanels(editor) { getPanels(editor) {
if (!this.panels) { if (!this.panels) {
this.panels = editor.Panels.getPanelsEl(); this.panels = editor.Panels.getPanelsEl();
@ -28,7 +28,7 @@ module.exports = {
const helper = document.createElement('span'); const helper = document.createElement('span');
helper.className = `${pfx}off-prv fa fa-eye-slash`; helper.className = `${pfx}off-prv fa fa-eye-slash`;
editorEl.appendChild(helper); editorEl.appendChild(helper);
helper.onclick = () => editor.stopCommand('preview'); helper.onclick = () => this.stopCommand();
this.helper = helper; this.helper = helper;
} }

2
src/commands/view/Resize.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(editor, sender, opts) { run(editor, sender, opts) {
var opt = opts || {}; var opt = opts || {};
var el = opt.el || ''; var el = opt.el || '';

14
src/commands/view/SelectComponent.js

@ -1,12 +1,13 @@
import Backbone from 'backbone';
import { bindAll, isElement, isUndefined } from 'underscore'; import { bindAll, isElement, isUndefined } from 'underscore';
import { on, off, getUnitFromValue, isTaggableNode } from 'utils/mixins'; import { on, off, getUnitFromValue, isTaggableNode } from 'utils/mixins';
import ToolbarView from 'dom_components/view/ToolbarView';
import Toolbar from 'dom_components/model/Toolbar';
const ToolbarView = require('dom_components/view/ToolbarView'); const $ = Backbone.$;
const Toolbar = require('dom_components/model/Toolbar');
const $ = require('backbone').$;
let showOffsets; let showOffsets;
module.exports = { export default {
init(o) { init(o) {
bindAll(this, 'onHover', 'onOut', 'onClick', 'onFrameScroll'); bindAll(this, 'onHover', 'onOut', 'onClick', 'onFrameScroll');
}, },
@ -49,7 +50,7 @@ module.exports = {
const win = this.getContentWindow(); const win = this.getContentWindow();
methods[method](body, 'mouseover', this.onHover); methods[method](body, 'mouseover', this.onHover);
methods[method](body, 'mouseout', this.onOut); methods[method](body, 'mouseout', this.onOut);
methods[method](body, 'click', this.onClick); methods[method](body, 'click touchend', this.onClick);
methods[method](win, 'scroll resize', this.onFrameScroll); methods[method](win, 'scroll resize', this.onFrameScroll);
em[method]('component:toggled', this.onSelect, this); em[method]('component:toggled', this.onSelect, this);
em[method]('change:componentHovered', this.onHovered, this); em[method]('change:componentHovered', this.onHovered, this);
@ -176,7 +177,10 @@ module.exports = {
* @private * @private
*/ */
onClick(e) { onClick(e) {
const { em } = this;
e.stopPropagation(); e.stopPropagation();
e.preventDefault();
if (em.get('_cmpDrag')) return em.set('_cmpDrag');
const $el = $(e.target); const $el = $(e.target);
let model = $el.data('model'); let model = $el.data('model');

2
src/commands/view/SelectPosition.js

@ -1,7 +1,7 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
const $ = Backbone.$; const $ = Backbone.$;
module.exports = { export default {
/** /**
* Start select position event * Start select position event
* @param {HTMLElement} trg * @param {HTMLElement} trg

2
src/commands/view/ShowOffset.js

@ -2,7 +2,7 @@ import Backbone from 'backbone';
import { isTextNode } from 'utils/mixins'; import { isTextNode } from 'utils/mixins';
const $ = Backbone.$; const $ = Backbone.$;
module.exports = { export default {
getOffsetMethod(state) { getOffsetMethod(state) {
var method = state || ''; var method = state || '';
return 'get' + method + 'OffsetViewerEl'; return 'get' + method + 'OffsetViewerEl';

2
src/commands/view/SwitchVisibility.js

@ -1,4 +1,4 @@
module.exports = { export default {
run(ed) { run(ed) {
ed.Canvas.getBody().className = this.ppfx + 'dashed'; ed.Canvas.getBody().className = this.ppfx + 'dashed';
}, },

2
src/css_composer/config/config.js

@ -1,4 +1,4 @@
module.exports = { export default {
// Style prefix // Style prefix
stylePrefix: 'css-', stylePrefix: 'css-',

19
src/css_composer/index.js

@ -28,17 +28,16 @@
*/ */
import { isArray } from 'underscore'; import { isArray } from 'underscore';
import defaults from './config/config';
module.exports = () => { import CssRule from './model/CssRule';
import CssRules from './model/CssRules';
import CssRulesView from './view/CssRulesView';
import Selectors from 'selector_manager/model/Selectors';
import Selector from 'selector_manager/model/Selector';
export default () => {
let em; let em;
var c = {}, var c = {};
defaults = require('./config/config'),
CssRule = require('./model/CssRule'),
CssRules = require('./model/CssRules'),
CssRulesView = require('./view/CssRulesView');
const Selectors = require('selector_manager/model/Selectors');
const Selector = require('selector_manager/model/Selector');
var rules, rulesView; var rules, rulesView;
return { return {

11
src/css_composer/model/CssRule.js

@ -1,11 +1,10 @@
import _ from 'underscore'; import { map } from 'underscore';
import Backbone from 'backbone'; import Backbone from 'backbone';
import Styleable from 'domain_abstract/model/Styleable'; import Styleable from 'domain_abstract/model/Styleable';
import { isEmpty, forEach } from 'underscore'; import { isEmpty, forEach } from 'underscore';
import Selectors from 'selector_manager/model/Selectors';
var Selectors = require('selector_manager/model/Selectors'); export default Backbone.Model.extend(Styleable).extend({
module.exports = Backbone.Model.extend(Styleable).extend({
defaults: { defaults: {
// Css selectors // Css selectors
selectors: {}, selectors: {},
@ -161,8 +160,8 @@ module.exports = Backbone.Model.extend(Styleable).extend({
//var a2 = _.pluck(this.get('selectors').models, cId); //var a2 = _.pluck(this.get('selectors').models, cId);
if (!(selectors instanceof Array) && !selectors.models) if (!(selectors instanceof Array) && !selectors.models)
selectors = [selectors]; selectors = [selectors];
var a1 = _.map(selectors.models || selectors, model => model.get('name')); var a1 = map(selectors.models || selectors, model => model.get('name'));
var a2 = _.map(this.get('selectors').models, model => model.get('name')); var a2 = map(this.get('selectors').models, model => model.get('name'));
var f = false; var f = false;
if (a1.length !== a2.length) return f; if (a1.length !== a2.length) return f;

4
src/css_composer/model/CssRules.js

@ -1,7 +1,7 @@
import Backbone from 'backbone'; import Backbone from 'backbone';
var CssRule = require('./CssRule'); import CssRule from './CssRule';
module.exports = Backbone.Collection.extend({ export default Backbone.Collection.extend({
initialize(models, opt) { initialize(models, opt) {
// Inject editor // Inject editor
if (opt && opt.em) this.editor = opt.em; if (opt && opt.em) this.editor = opt.em;

4
src/css_composer/view/CssGroupRuleView.js

@ -1,4 +1,6 @@
module.exports = require('./CssRuleView').extend({ import CssRuleView from './CssRuleView';
export default CssRuleView.extend({
_createElement: function(tagName) { _createElement: function(tagName) {
return document.createTextNode(''); return document.createTextNode('');
}, },

4
src/css_composer/view/CssRuleView.js

@ -1,4 +1,6 @@
module.exports = require('backbone').View.extend({ import Backbone from 'backbone';
export default Backbone.View.extend({
tagName: 'style', tagName: 'style',
initialize(o = {}) { initialize(o = {}) {

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

Loading…
Cancel
Save