Browse Source

Merge pull request #1775 from artf/dev

Merge dev
pull/2161/head
Artur Arseniev 8 years ago
committed by GitHub
parent
commit
86493f7f99
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .eslintrc
  2. 2
      dist/css/grapes.min.css
  3. 18928
      dist/grapes.js
  4. 8
      dist/grapes.min.js
  5. 2
      dist/grapes.min.js.map
  6. 41
      docs/api/component.md
  7. 90
      docs/api/components.md
  8. 4
      docs/api/editor.md
  9. 35
      docs/api/keymaps.md
  10. 59
      docs/api/panels.md
  11. 1
      docs/api/style_manager.md
  12. 197
      docs/modules/Components.md
  13. 2
      docs/modules/Style-manager.md
  14. 2
      docs/modules/Traits.md
  15. 4
      index.html
  16. 4338
      package-lock.json
  17. 38
      package.json
  18. 12
      src/asset_manager/config/config.js
  19. 16
      src/asset_manager/view/FileUploader.js
  20. 49
      src/block_manager/index.js
  21. 12
      src/block_manager/view/BlocksView.js
  22. 18
      src/canvas/config/config.js
  23. 34
      src/canvas/index.js
  24. 2
      src/canvas/view/CanvasView.js
  25. 2
      src/code_manager/model/JsGenerator.js
  26. 2
      src/commands/view/ComponentDelete.js
  27. 7
      src/commands/view/ComponentEnter.js
  28. 7
      src/commands/view/ComponentExit.js
  29. 7
      src/commands/view/ComponentNext.js
  30. 7
      src/commands/view/ComponentPrev.js
  31. 2
      src/commands/view/CopyComponent.js
  32. 12
      src/commands/view/Fullscreen.js
  33. 4
      src/commands/view/PasteComponent.js
  34. 36
      src/commands/view/SelectComponent.js
  35. 78
      src/css_composer/index.js
  36. 73
      src/dom_components/index.js
  37. 55
      src/dom_components/model/Component.js
  38. 2
      src/dom_components/model/ComponentLabel.js
  39. 4
      src/dom_components/model/ComponentWrapper.js
  40. 3
      src/dom_components/view/ComponentLinkView.js
  41. 30
      src/dom_components/view/ComponentView.js
  42. 5
      src/dom_components/view/ComponentsView.js
  43. 16
      src/editor/config/config.js
  44. 9
      src/editor/index.js
  45. 1
      src/editor/model/Editor.js
  46. 4
      src/editor/view/EditorView.js
  47. 22
      src/modal_dialog/index.js
  48. 2
      src/navigator/view/ItemView.js
  49. 2
      src/navigator/view/ItemsView.js
  50. 9
      src/panels/view/PanelView.js
  51. 6
      src/parser/index.js
  52. 2
      src/parser/model/BrowserParserCss.js
  53. 11
      src/parser/model/ParserHtml.js
  54. 84
      src/selector_manager/index.js
  55. 2
      src/selector_manager/model/Selectors.js
  56. 4
      src/storage_manager/config/config.js
  57. 14
      src/storage_manager/model/RemoteStorage.js
  58. 1
      src/style_manager/index.js
  59. 4
      src/style_manager/model/Layer.js
  60. 14
      src/style_manager/model/Property.js
  61. 10
      src/style_manager/model/PropertyComposite.js
  62. 154
      src/style_manager/model/PropertyFactory.js
  63. 2
      src/style_manager/model/PropertySelect.js
  64. 11
      src/style_manager/model/Sector.js
  65. 2
      src/style_manager/view/PropertyCompositeView.js
  66. 2
      src/style_manager/view/PropertySelectView.js
  67. 60
      src/style_manager/view/PropertyView.js
  68. 10
      src/style_manager/view/SectorView.js
  69. 14
      src/style_manager/view/SectorsView.js
  70. 2
      src/styles/scss/_gjs_traits.scss
  71. 13
      src/utils/Sorter.js
  72. 11
      src/utils/mixins.js
  73. 114
      test/specs/css_composer/index.js
  74. 96
      test/specs/dom_components/index.js
  75. 13
      test/specs/panels/view/PanelView.js
  76. 60
      test/specs/selector_manager/index.js
  77. 45
      test/specs/storage_manager/model/Models.js
  78. 219
      test/specs/style_manager/model/Models.js

6
.eslintrc

@ -4,10 +4,8 @@
"node": true
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"strict": 0,

2
dist/css/grapes.min.css

File diff suppressed because one or more lines are too long

18928
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

41
docs/api/component.md

@ -57,6 +57,24 @@ component.get('tagName');
By default, when `toolbar` property is falsy the editor will add automatically commands like `move`, `delete`, etc. based on its properties.
- `components` **Collection<[Component][9]>?** Children components. Default: `null`
## init
Hook method, called once the model is created
## updated
Hook method, called when the model has been updated (eg. updated some model's property)
### Parameters
- `property` **[String][1]** Property name, if triggered after some property update
- `value` **any** Property value, if triggered after some property update
- `previous` **any** Property previous value, if triggered after some property update
## removed
Hook method, called once the model has been removed
## is
Check component's type
@ -74,6 +92,12 @@ component.is('image')
Returns **[Boolean][3]**
## index
Get the index of the component in the parent collection.
Returns **[Number][10]**
## find
Find inner components by query string.
@ -394,10 +418,17 @@ Returns **this**
## getEl
Get the DOM element of the component. This works only of the
component is already rendered
Get the DOM element of the component.
This works only if the component is already rendered
Returns **[HTMLElement][10]**
Returns **[HTMLElement][11]**
## getView
Get the View of the component.
This works only if the component is already rendered
Returns **ComponentView**
## onAll
@ -441,4 +472,6 @@ Returns **this**
[9]: #component
[10]: https://developer.mozilla.org/docs/Web/HTML/Element
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[11]: https://developer.mozilla.org/docs/Web/HTML/Element

90
docs/api/components.md

@ -24,7 +24,10 @@ const domComponents = editor.DomComponents;
- [clear][5]
- [load][6]
- [store][7]
- [render][8]
- [addType][8]
- [getType][9]
- [getTypes][10]
- [render][11]
## load
@ -34,9 +37,9 @@ The fetched data will be added to the collection
### Parameters
- `data` **[Object][9]** Object of data to load (optional, default `''`)
- `data` **[Object][12]** Object of data to load (optional, default `''`)
Returns **[Object][9]** Loaded data
Returns **[Object][12]** Loaded data
## store
@ -44,9 +47,9 @@ Store components on the selected storage
### Parameters
- `noStore` **[Boolean][10]** If true, won't store
- `noStore` **[Boolean][13]** If true, won't store
Returns **[Object][9]** Data to store
Returns **[Object][12]** Data to store
## getWrapper
@ -104,18 +107,18 @@ as 'domComponents.getComponents().add(...)'
### Parameters
- `component` **([Object][9] | Component | [Array][11]<[Object][9]>)** Component/s to add
- `component.tagName` **[string][12]** Tag name (optional, default `'div'`)
- `component.type` **[string][12]** Type of the component. Available: ''(default), 'text', 'image' (optional, default `''`)
- `component.removable` **[boolean][10]** If component is removable (optional, default `true`)
- `component.draggable` **[boolean][10]** If is possible to move the component around the structure (optional, default `true`)
- `component.droppable` **[boolean][10]** If is possible to drop inside other components (optional, default `true`)
- `component.badgable` **[boolean][10]** If the badge is visible when the component is selected (optional, default `true`)
- `component.stylable` **[boolean][10]** If is possible to style component (optional, default `true`)
- `component.copyable` **[boolean][10]** If is possible to copy&paste the component (optional, default `true`)
- `component.content` **[string][12]** String inside component (optional, default `''`)
- `component.style` **[Object][9]** Style object (optional, default `{}`)
- `component.attributes` **[Object][9]** Attribute object (optional, default `{}`)
- `component` **([Object][12] | Component | [Array][14]<[Object][12]>)** Component/s to add
- `component.tagName` **[string][15]** Tag name (optional, default `'div'`)
- `component.type` **[string][15]** Type of the component. Available: ''(default), 'text', 'image' (optional, default `''`)
- `component.removable` **[boolean][13]** If component is removable (optional, default `true`)
- `component.draggable` **[boolean][13]** If is possible to move the component around the structure (optional, default `true`)
- `component.droppable` **[boolean][13]** If is possible to drop inside other components (optional, default `true`)
- `component.badgable` **[boolean][13]** If the badge is visible when the component is selected (optional, default `true`)
- `component.stylable` **[boolean][13]** If is possible to style component (optional, default `true`)
- `component.copyable` **[boolean][13]** If is possible to copy&paste the component (optional, default `true`)
- `component.content` **[string][15]** String inside component (optional, default `''`)
- `component.style` **[Object][12]** Style object (optional, default `{}`)
- `component.attributes` **[Object][12]** Attribute object (optional, default `{}`)
### Examples
@ -132,7 +135,7 @@ var comp1 = domComponents.addComponent({
});
```
Returns **(Component | [Array][11]<Component>)** Component/s added
Returns **(Component | [Array][14]<Component>)** Component/s added
## render
@ -141,7 +144,7 @@ Once the wrapper is rendered, and it's what happens when you init the editor,
the all new components will be added automatically and property changes are all
updated immediately
Returns **[HTMLElement][13]**
Returns **[HTMLElement][16]**
## clear
@ -149,6 +152,35 @@ Remove all components
Returns **this**
## addType
Add new component type.
Read more about this in [Define New Component][17]
### Parameters
- `type` **[string][15]** Component ID
- `methods` **[Object][12]** Component methods
Returns **this**
## getType
Get component type.
Read more about this in [Define New Component][17]
### Parameters
- `type` **[string][15]** Component ID
Returns **[Object][12]** Component type defintion, eg. `{ model: ..., view: ... }`
## getTypes
Return the array of all types
Returns **[Array][14]**
[1]: https://github.com/artf/grapesjs/blob/master/src/dom_components/config/config.js
[2]: #getwrapper
@ -163,14 +195,22 @@ Returns **this**
[7]: #store
[8]: #render
[8]: #addtype
[9]: #gettype
[10]: #gettypes
[11]: #render
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[16]: https://developer.mozilla.org/docs/Web/HTML/Element
[13]: https://developer.mozilla.org/docs/Web/HTML/Element
[17]: https://grapesjs.com/docs/modules/Components.html#define-new-component

4
docs/api/editor.md

@ -23,9 +23,11 @@ editor.on('EVENT-NAME', (some, argument) => {
### Components
- `component:create` - Component is created (only the model, is not yet mounted in the canvas), called after the init() method
- `component:mount` - Component is monted to an element and rendered in canvas
- `component:add` - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
- `component:remove` - Triggered when a component is removed, the model is passed as an argument to the callback
- `component:clone` - Triggered when a new component is added by a clone command, the model is passed as an argument to the callback
- `component:clone` - Triggered when a component is cloned, the new model is passed as an argument to the callback
- `component:update` - Triggered when a component is updated (moved, styled, etc.), the model is passed as an argument to the callback
- `component:update:{propertyName}` - Listen any property change, the model is passed as an argument to the callback
- `component:styleUpdate` - Triggered when the style of the component is updated, the model is passed as an argument to the callback

35
docs/api/keymaps.md

@ -30,12 +30,13 @@ const keymaps = editor.Keymaps;
- [get][3]
- [getAll][4]
- [remove][5]
- [removeAll][6]
## getConfig
Get module configurations
Returns **[Object][6]** Configuration object
Returns **[Object][7]** Configuration object
## add
@ -43,9 +44,9 @@ Add new keymap
### Parameters
- `id` **[string][7]** Keymap id
- `keys` **[string][7]** Keymap keys, eg. `ctrl+a`, `⌘+z, ctrl+z`
- `handler` **([Function][8] \| [string][7])** Keymap handler, might be a function
- `id` **[string][8]** Keymap id
- `keys` **[string][8]** Keymap keys, eg. `ctrl+a`, `⌘+z, ctrl+z`
- `handler` **([Function][9] \| [string][8])** Keymap handler, might be a function
### Examples
@ -63,7 +64,7 @@ editor.on('keymap:emit', (id, shortcut, e) => {
})
```
Returns **[Object][6]** Added keymap
Returns **[Object][7]** Added keymap
or just a command id as a string
## get
@ -72,7 +73,7 @@ Get the keymap by id
### Parameters
- `id` **[string][7]** Keymap id
- `id` **[string][8]** Keymap id
### Examples
@ -81,7 +82,7 @@ keymaps.get('ns:my-keymap');
// -> {keys, handler};
```
Returns **[Object][6]** Keymap object
Returns **[Object][7]** Keymap object
## getAll
@ -94,7 +95,7 @@ keymaps.getAll();
// -> {id1: {}, id2: {}};
```
Returns **[Object][6]**
Returns **[Object][7]**
## remove
@ -102,7 +103,7 @@ Remove the keymap by id
### Parameters
- `id` **[string][7]** Keymap id
- `id` **[string][8]** Keymap id
### Examples
@ -111,7 +112,13 @@ keymaps.remove('ns:my-keymap');
// -> {keys, handler};
```
Returns **[Object][6]** Removed keymap
Returns **[Object][7]** Removed keymap
## removeAll
Remove all binded keymaps
Returns **this**
[1]: #getconfig
@ -123,8 +130,10 @@ Returns **[Object][6]** Removed keymap
[5]: #remove
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[6]: #removeall
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

59
docs/api/panels.md

@ -20,13 +20,12 @@ const panelManager = editor.Panels;
- [addPanel][2]
- [addButton][3]
- [removeButton][4]
- [getButton][5]
- [getPanel][6]
- [getPanels][7]
- [getPanelsEl][8]
- [removePanel][9]
- [removeButton][10]
- [getButton][4]
- [getPanel][5]
- [getPanels][6]
- [getPanelsEl][7]
- [removePanel][8]
- [removeButton][9]
## getPanels
@ -38,7 +37,7 @@ Returns **Collection** Collection of panel
Returns panels element
Returns **[HTMLElement][11]**
Returns **[HTMLElement][10]**
## addPanel
@ -46,7 +45,7 @@ Add new panel to the collection
### Parameters
- `panel` **([Object][12] | Panel)** Object with right properties or an instance of Panel
- `panel` **([Object][11] | Panel)** Object with right properties or an instance of Panel
### Examples
@ -66,7 +65,7 @@ Remove a panel from the collection
### Parameters
- `panel` **([Object][12] | Panel | [String][13])** Object with right properties or an instance of Panel or Painel id
- `panel` **([Object][11] | Panel | [String][12])** Object with right properties or an instance of Panel or Painel id
### Examples
@ -88,7 +87,7 @@ Get panel by ID
### Parameters
- `id` **[string][13]** Id string
- `id` **[string][12]** Id string
### Examples
@ -104,8 +103,8 @@ Add button to the panel
### Parameters
- `panelId` **[string][13]** Panel's ID
- `button` **([Object][12] | Button)** Button object or instance of Button
- `panelId` **[string][12]** Panel's ID
- `button` **([Object][11] | Button)** Button object or instance of Button
### Examples
@ -143,13 +142,14 @@ Remove button from the panel
### Parameters
- `panelId` **[string][13]** Panel's ID
- `button` **([Object][12] | Button | [String][13])** Button object or instance of Button or button id
- `panelId` **[String][12]** Panel's ID
- `button`
- `buttonId` **[String][12]** Button's ID
### Examples
```javascript
const removedButton = panelManager.removeButton('myNewPanel',{
const removedButton = panelManager.addButton('myNewPanel',{
id: 'myNewButton',
className: 'someClass',
command: 'someCommand',
@ -157,8 +157,7 @@ const removedButton = panelManager.removeButton('myNewPanel',{
active: false,
});
// It's also possible to use the button id
const removedButton = panelManager.removeButton('myNewPanel','myNewButton');
const removedButton = panelManager.removeButton('myNewPanel', 'myNewButton');
```
Returns **(Button | null)** Removed button.
@ -169,8 +168,8 @@ Get button from the panel
### Parameters
- `panelId` **[string][13]** Panel's ID
- `id` **[string][13]** Button's ID
- `panelId` **[string][12]** Panel's ID
- `id` **[string][12]** Button's ID
### Examples
@ -186,22 +185,20 @@ Returns **(Button | null)**
[3]: #addbutton
[4]: #removebutton
[4]: #getbutton
[5]: #getbutton
[5]: #getpanel
[6]: #getpanel
[6]: #getpanels
[7]: #getpanels
[7]: #getpanelsel
[8]: #getpanelsel
[8]: #removepanel
[9]: #removepanel
[9]: #removebutton
[10]: #removeButton
[10]: https://developer.mozilla.org/docs/Web/HTML/Element
[11]: https://developer.mozilla.org/docs/Web/HTML/Element
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

1
docs/api/style_manager.md

@ -29,7 +29,6 @@ const styleManager = editor.StyleManager;
- [removeProperty][9]
- [getProperties][10]
- [getModelToStyle][11]
- [getModelToStyle][11]
- [addType][12]
- [getType][13]
- [getTypes][14]

197
docs/modules/Components.md

@ -10,15 +10,20 @@ The Component is the base element for template composition. It is atomic, so ele
## Built-in components
* Default (Basic)
* Text
* Image
* Video
* Link
* Map
* Table
* Row (for the table)
* Cell (for the table)
* default (Basic)
* wrapper
* text
* textnode
* svg
* script
* image
* video
* label
* link
* map
* table
* row (for the table)
* cell (for the table)
@ -33,7 +38,9 @@ When we pass an HTML string to the editor like this:
</div>
```
For each DOM element 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 the elements using the browser inspector and then ask the editor to print the HTML (using `editor.getHtml()`) you'll see that the element will still be present. This is because the editor relies on Models and not on the DOM 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*).
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*).
@ -54,7 +61,9 @@ isComponent: function(el) {
}
```
This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each HTML 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. For example, with the image component this method looks like:
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
@ -220,12 +229,176 @@ comps.addType('map', {
});
```
## 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`
});
```
## 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)
[Components & JS](Components-js.html)

2
docs/modules/Style-manager.md

@ -13,7 +13,7 @@ Coming soon
Here you can find all the available built-in properties that you can use inside Style Manager via `buildProps`:
`float`, `position`, `text-align`, `display`, `font-family`, `font-weight`, `border`, `border-style`, `border-color`, `border-width`, `box-shadow`, `background-repeat`, `background-position`, `background-attachment`, `background-size`, `transition`, `transition-duration`, `transition-property`, `transition-timing-function`, `top`, `right`, `bottom`, `left`, `margin`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `padding`, `padding-top`, `padding-right`, `padding-bottom`, `padding-left`, `width`, `heigth`, `min-width`, `min-heigth`, `max-width`, `max-heigth`, `font-size`, `letter-spacing`, `line-height`, `text-shadow`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius`, `perspective`, `transform`, `transform-rotate-x`, `transform-rotate-y`, `transform-rotate-z`, `transform-scale-x`, `transform-scale-y`, `transform-scale-z`, `color`, `background-color`, `background`, `background-image`, `cursor`
`float`, `position`, `text-align`, `display`, `font-family`, `font-weight`, `border`, `border-style`, `border-color`, `border-width`, `box-shadow`, `background-repeat`, `background-position`, `background-attachment`, `background-size`, `transition`, `transition-duration`, `transition-property`, `transition-timing-function`, `top`, `right`, `bottom`, `left`, `margin`, `margin-top`, `margin-right`, `margin-bottom`, `margin-left`, `padding`, `padding-top`, `padding-right`, `padding-bottom`, `padding-left`, `width`, `height`, `min-width`, `min-height`, `max-width`, `max-height`, `font-size`, `letter-spacing`, `line-height`, `text-shadow`, `border-radius`, `border-top-left-radius`, `border-top-right-radius`, `border-bottom-left-radius`, `border-bottom-right-radius`, `perspective`, `transform`, `transform-rotate-x`, `transform-rotate-y`, `transform-rotate-z`, `transform-scale-x`, `transform-scale-y`, `transform-scale-z`, `color`, `background-color`, `background`, `background-image`, `cursor`, `flex-direction`, `flex-wrap`, `justify-content`, `align-items`, `align-content`, `order`, `flex-basis`, `flex-grow`, `flex-shrink`, `align-self`, `overflow`, `overflow-x`, `overflow-y`
Example usage:
```js

2
docs/modules/Traits.md

@ -27,7 +27,7 @@ You can add traits to the component by extending them or while creating a new on
<img :src="$withBase('/default-traits.png')">
In this example we are going to create a new Component. ([Check here](Components) for more details about the creation of new components with a new set of traits
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
```js
var editor = grapesjs.init({...});

4
index.html

@ -91,6 +91,10 @@
name: 'General',
open: false,
buildProps: ['float', 'display', 'position', 'top', 'right', 'left', 'bottom']
},{
name: 'Flex',
open: false,
buildProps: ['flex-direction', 'flex-wrap', 'justify-content', 'align-items', 'align-content', 'order', 'flex-basis', 'flex-grow', 'flex-shrink', 'align-self']
},{
name: 'Dimension',
open: false,

4338
package-lock.json

File diff suppressed because it is too large

38
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.14.43",
"version": "0.14.52",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
@ -14,11 +14,11 @@
"backbone": "^1.3.3",
"backbone-undo": "^0.2.5",
"cash-dom": "^1.3.7",
"codemirror": "^5.39.0",
"codemirror": "^5.42.0",
"codemirror-formatting": "^1.0.0",
"font-awesome": "^4.7.0",
"keymaster": "^1.6.2",
"promise-polyfill": "^8.0.0",
"promise-polyfill": "^8.1.0",
"spectrum-colorpicker": "^1.8.0",
"underscore": "^1.9.1"
},
@ -27,22 +27,22 @@
"babel-loader": "^7.1.5",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-preset-env": "^1.7.0",
"documentation": "^8.0.0",
"eslint": "^4.19.1",
"documentation": "^8.1.2",
"eslint": "^5.9.0",
"html-webpack-plugin": "^3.2.0",
"husky": "^0.14.3",
"jest": "^23.3.0",
"lint-staged": "^7.2.0",
"node-sass": "^4.9.1",
"npm-run-all": "^4.1.3",
"prettier": "^1.13.7",
"sinon": "^6.1.0",
"husky": "^1.2.0",
"jest": "^23.6.0",
"lint-staged": "^8.1.0",
"node-sass": "^4.10.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.15.3",
"sinon": "^7.1.1",
"string-replace-loader": "^2.1.1",
"vuepress": "^0.10.2",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"whatwg-fetch": "^2.0.4"
"webpack": "^4.26.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10",
"whatwg-fetch": "^3.0.0"
},
"keywords": [
"grapes",
@ -91,6 +91,11 @@
"^jquery$": "cash-dom"
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"scripts": {
"docs": "vuepress dev docs",
"docs:api": "node docs/api.js",
@ -99,7 +104,6 @@
"docs:deploy": "docs/deploy.sh",
"lint": "eslint src",
"check": "npm run lint && npm run test",
"precommit": "lint-staged",
"build": "npm run check && npm run v:patch && npm run build-dev && webpack --env=prod",
"build-n": "npm run check && npm run build:css && webpack --env=prod",
"build-dev": "webpack --env=dev && npm run build:css",

12
src/asset_manager/config/config.js

@ -55,7 +55,17 @@ module.exports = {
// Label for the add button
addBtnText: 'Add image',
// Custom uploadFile function
// To upload your assets, the module uses Fetch API, with this option you
// overwrite it with something else.
// It should return a Promise
// @example
// customFetch: (url, options) => axios(url, { data: options.body }),
customFetch: '',
// Custom uploadFile function.
// Differently from the `customFetch` option, this gives a total control
// over the uploading process, but you also have to emit all `asset:upload:*` events
// by yourself (if you need to use them somewhere)
// @example
// uploadFile: (e) => {
// var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;

16
src/asset_manager/view/FileUploader.js

@ -104,9 +104,9 @@ module.exports = Backbone.View.extend(
* */
uploadFile(e, clb) {
const files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
const { config } = this;
const body = new FormData();
const config = this.config;
const params = config.params;
const { params, customFetch } = config;
for (let param in params) {
body.append(param, params[param]);
@ -131,18 +131,20 @@ module.exports = Backbone.View.extend(
if (url) {
this.onUploadStart();
return fetch(url, {
const fetchOpts = {
method: 'post',
credentials: config.credentials || 'include',
headers,
body
})
.then(
res =>
};
const fetchResult = customFetch
? customFetch(url, fetchOpts)
: fetch(url, fetchOpts).then(res =>
((res.status / 200) | 0) == 1
? res.text()
: res.text().then(text => Promise.reject(text))
)
);
return fetchResult
.then(text => this.onUploadResponse(text, clb))
.catch(err => this.onUploadError(err));
}

49
src/block_manager/index.js

@ -63,15 +63,14 @@ module.exports = () => {
// Global blocks collection
blocks = new Blocks([]);
blocksVisible = new Blocks([]);
(categories = new BlockCategories()),
(blocksView = new BlocksView(
{
// Visible collection
collection: blocksVisible,
categories
},
c
));
categories = new BlockCategories();
blocksView = new BlocksView(
{
collection: blocksVisible,
categories
},
c
);
// Setup the sync between the global and public collections
blocks.listenTo(blocks, 'add', model => {
@ -205,8 +204,10 @@ module.exports = () => {
/**
* Render blocks
* @param {Array} blocks Blocks to render, without the argument will render
* all global blocks
* @param {Array} blocks Blocks to render, without the argument will render all global blocks
* @param {Object} [opts={}] Options
* @param {Boolean} [opts.external] Render blocks in a new container (HTMLElement will be returned)
* @param {Boolean} [opts.ignoreCategories] Render blocks without categories
* @return {HTMLElement} Rendered element
* @example
* // Render all blocks (inside the global collection)
@ -214,9 +215,9 @@ module.exports = () => {
*
* // Render new set of blocks
* const blocks = blockManager.getAll();
* blockManager.render(blocks.filter(
* block => block.get('category') == 'sections'
* ));
* const filtered = blocks.filter(block => block.get('category') == 'sections')
*
* blockManager.render(filtered);
* // Or a new set from an array
* blockManager.render([
* {label: 'Label text', content: '<div>Content</div>'}
@ -224,15 +225,33 @@ module.exports = () => {
*
* // Back to blocks from the global collection
* blockManager.render();
*
* // You can also render your blocks outside of the main block container
* const newBlocksEl = blockManager.render(filtered, { external: true });
* document.getElementById('some-id').appendChild(newBlocksEl);
*/
render(blocks) {
render(blocks, opts = {}) {
const toRender = blocks || this.getAll().models;
if (opts.external) {
return new BlocksView(
{
collection: new Blocks(toRender),
categories
},
{
...c,
...opts
}
).render().el;
}
if (!blocksView.rendered) {
blocksView.render();
blocksView.rendered = 1;
}
blocksView.updateConfig(opts);
blocksView.collection.reset(toRender);
return this.getContainer();
}

12
src/block_manager/view/BlocksView.js

@ -27,6 +27,13 @@ module.exports = require('backbone').View.extend({
}
},
updateConfig(opts = {}) {
this.config = {
...this.config,
...opts
};
},
/**
* Get sorter
* @private
@ -103,19 +110,20 @@ module.exports = require('backbone').View.extend({
* @private
* */
add(model, fragment) {
const { config } = this;
var frag = fragment || null;
var view = new BlockView(
{
model,
attributes: model.get('attributes')
},
this.config
config
);
var rendered = view.render().el;
var category = model.get('category');
// Check for categories
if (category && this.categories) {
if (category && this.categories && !config.ignoreCategories) {
if (isString(category)) {
category = {
id: category,

18
src/canvas/config/config.js

@ -6,7 +6,7 @@ module.exports = {
* Be aware that these scripts will not be printed in the export code
* @example
* scripts: [ 'https://...1.js', 'https://...2.js' ]
*/
*/
scripts: [],
/*
@ -14,7 +14,7 @@ module.exports = {
* Be aware that these styles will not be printed in the export code
* @example
* styles: [ 'https://...1.css', 'https://...2.css' ]
*/
*/
styles: [],
/**
@ -24,5 +24,17 @@ module.exports = {
* return component.getName();
* }
*/
customBadgeLabel: ''
customBadgeLabel: '',
/**
* Indicate when to start the auto scroll of the canvas on component/block dragging (value in px )
*/
autoscrollLimit: 50,
/**
* When some textable component is selected and focused (eg. input or text component) the editor
* stops some commands (eg. disables the copy/paste of components with CTRL+C/V to allow the copy/paste of the text).
* This option allows to customize, by a selector, which element should not be considered textable
*/
notTextable: ['button', 'a', 'input[type=checkbox]', 'input[type=radio]']
};

34
src/canvas/index.js

@ -28,9 +28,11 @@
* @module Canvas
*/
import { on, off, hasDnd, getElement } from 'utils/mixins';
import { on, off, hasDnd, getElement, getPointerEvent } from 'utils/mixins';
import Droppable from 'utils/Droppable';
const { requestAnimationFrame } = window;
module.exports = () => {
var c = {},
defaults = require('./config/config'),
@ -81,6 +83,7 @@ module.exports = () => {
this.startAutoscroll = this.startAutoscroll.bind(this);
this.stopAutoscroll = this.stopAutoscroll.bind(this);
this.autoscroll = this.autoscroll.bind(this);
this.updateClientY = this.updateClientY.bind(this);
return this;
},
@ -409,11 +412,11 @@ module.exports = () => {
* @private
*/
isInputFocused() {
let contentDocument = this.getFrameEl().contentDocument;
return (
contentDocument.activeElement &&
contentDocument.activeElement.tagName !== 'BODY'
);
const doc = this.getDocument();
const toIgnore = ['body', ...this.getConfig().notTextable];
const focused = doc && doc.activeElement;
return focused && !toIgnore.some(item => focused.matches(item));
},
/**
@ -452,22 +455,27 @@ module.exports = () => {
// By detaching those from the stack avoid browsers lags
// Noticeable with "fast" drag of blocks
setTimeout(() => {
on(toListen, 'mousemove', this.autoscroll);
on(toListen, 'mousemove dragover', this.updateClientY);
on(toListen, 'mouseup', this.stopAutoscroll);
requestAnimationFrame(this.autoscroll);
}, 0);
},
updateClientY(ev) {
ev.preventDefault();
this.lastClientY = getPointerEvent(ev).clientY;
},
/**
* @private
*/
autoscroll(e) {
e.preventDefault();
autoscroll() {
if (this.dragging) {
let frameWindow = this.getFrameEl().contentWindow;
let actualTop = frameWindow.document.body.scrollTop;
let nextTop = actualTop;
let clientY = e.clientY;
let limitTop = 50;
let clientY = this.lastClientY;
let limitTop = this.getConfig().autoscrollLimit;
let limitBottom = frameRect.height - limitTop;
if (clientY < limitTop) {
@ -478,8 +486,8 @@ module.exports = () => {
nextTop += clientY - limitBottom;
}
//console.log(`actualTop: ${actualTop} clientY: ${clientY} nextTop: ${nextTop} frameHeigh: ${frameRect.height}`);
frameWindow.scrollTo(0, nextTop);
requestAnimationFrame(this.autoscroll);
}
},
@ -490,7 +498,7 @@ module.exports = () => {
stopAutoscroll() {
this.dragging = 0;
let toListen = this.getScrollListeners();
off(toListen, 'mousemove', this.autoscroll);
off(toListen, 'mousemove dragover', this.updateClientY);
off(toListen, 'mouseup', this.stopAutoscroll);
},

2
src/canvas/view/CanvasView.js

@ -25,7 +25,7 @@ module.exports = Backbone.View.extend({
*/
isElInViewport(el) {
const rect = getElement(el).getBoundingClientRect();
const frameRect = this.getFrameOffset(1);
const frameRect = this.getFrameOffset();
const rTop = rect.top;
const rLeft = rect.left;
return (

2
src/code_manager/model/JsGenerator.js

@ -13,7 +13,7 @@ module.exports = Backbone.Model.extend({
// If the component has scripts we need to expose his ID
var attr = model.get('attributes');
attr = extend({}, attr, { id });
model.set('attributes', attr);
model.set('attributes', attr, { silent: 1 });
var scrStr = model.getScriptString();
// If the script was updated, I'll put its code in a separate container

2
src/commands/view/ComponentDelete.js

@ -2,7 +2,7 @@ import { isArray } from 'underscore';
module.exports = {
run(ed, sender, opts = {}) {
if (ed.getModel().isEditing()) return;
if (ed.getModel().isEditing() || ed.Canvas.isInputFocused()) return;
let components = opts.component || ed.getSelectedAll();
components = isArray(components) ? [...components] : [components];

7
src/commands/view/ComponentEnter.js

@ -1,6 +1,11 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
if (
!ed.Canvas.hasFocus() ||
ed.getModel().isEditing() ||
ed.Canvas.isInputFocused()
)
return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {

7
src/commands/view/ComponentExit.js

@ -1,6 +1,11 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
if (
!ed.Canvas.hasFocus() ||
ed.getModel().isEditing() ||
ed.Canvas.isInputFocused()
)
return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {

7
src/commands/view/ComponentNext.js

@ -1,6 +1,11 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
if (
!ed.Canvas.hasFocus() ||
ed.getModel().isEditing() ||
ed.Canvas.isInputFocused()
)
return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {

7
src/commands/view/ComponentPrev.js

@ -1,6 +1,11 @@
module.exports = {
run(ed) {
if (!ed.Canvas.hasFocus() || ed.getModel().isEditing()) return;
if (
!ed.Canvas.hasFocus() ||
ed.getModel().isEditing() ||
ed.Canvas.isInputFocused()
)
return;
const toSelect = [];
ed.getSelectedAll().forEach(component => {

2
src/commands/view/CopyComponent.js

@ -3,7 +3,7 @@ module.exports = {
const em = ed.getModel();
const models = [...ed.getSelectedAll()];
if (models.length && !em.isEditing()) {
if (models.length && !em.isEditing() && !ed.Canvas.isInputFocused()) {
em.set('clipboard', models);
}
}

12
src/commands/view/Fullscreen.js

@ -39,11 +39,13 @@ module.exports = {
* Disable fullscreen mode
*/
disable() {
var d = document;
if (d.exitFullscreen) d.exitFullscreen();
else if (d.webkitExitFullscreen) d.webkitExitFullscreen();
else if (d.mozCancelFullScreen) d.mozCancelFullScreen();
else if (d.msExitFullscreen) d.msExitFullscreen();
const d = document;
if (this.isEnabled()) {
if (d.exitFullscreen) d.exitFullscreen();
else if (d.webkitExitFullscreen) d.webkitExitFullscreen();
else if (d.mozCancelFullScreen) d.mozCancelFullScreen();
else if (d.msExitFullscreen) d.msExitFullscreen();
}
},
/**

4
src/commands/view/PasteComponent.js

@ -6,7 +6,7 @@ module.exports = {
const clp = em.get('clipboard');
const selected = ed.getSelected();
if (clp && selected && !em.isEditing()) {
if (clp && selected && !em.isEditing() && !ed.Canvas.isInputFocused()) {
ed.getSelectedAll().forEach(comp => {
if (!comp) return;
const coll = comp.collection;
@ -21,7 +21,7 @@ module.exports = {
}
added = isArray(added) ? added : [added];
added.forEach(add => ed.trigger('component:clone', add));
added.forEach(add => ed.trigger('component:paste', add));
});
selected.emitUpdate();

36
src/commands/view/SelectComponent.js

@ -350,7 +350,7 @@ module.exports = {
* @private
*/
initResize(elem) {
const em = this.em;
const { em, canvas } = this;
const editor = em ? em.get('Editor') : '';
const config = em ? em.get('Config') : '';
const pfx = config.stylePrefix || '';
@ -444,7 +444,13 @@ module.exports = {
const style = modelToStyle.getStyle();
if (!onlyHeight) {
style[keyWidth] = autoWidth ? 'auto' : `${rect.w}${unitWidth}`;
const padding = 10;
const frameOffset = canvas.getCanvasView().getFrameOffset();
const width =
rect.w < frameOffset.width - padding
? rect.w
: frameOffset.width - padding;
style[keyWidth] = autoWidth ? 'auto' : `${width}${unitWidth}`;
}
if (!onlyWidth) {
@ -527,15 +533,33 @@ module.exports = {
* @param {Object} pos
*/
updateToolbarPos(el, elPos) {
var unit = 'px';
var toolbarEl = this.canvas.getToolbarEl();
var toolbarStyle = toolbarEl.style;
const { canvas } = this;
const unit = 'px';
const toolbarEl = canvas.getToolbarEl();
const toolbarStyle = toolbarEl.style;
toolbarStyle.opacity = 0;
var pos = this.canvas.getTargetToElementDim(toolbarEl, el, {
const pos = canvas.getTargetToElementDim(toolbarEl, el, {
elPos,
event: 'toolbarPosUpdate'
});
if (pos) {
const frameOffset = canvas.getCanvasView().getFrameOffset();
// Scroll with the window if the top edge is reached and the
// element is bigger than the canvas
if (
pos.top <= pos.canvasTop &&
!(pos.elementHeight + pos.targetHeight >= frameOffset.height)
) {
pos.top = pos.elementTop + pos.elementHeight;
}
// Check if not outside of the canvas
if (pos.left < pos.canvasLeft) {
pos.left = pos.canvasLeft;
}
var leftPos = pos.left + pos.elementWidth - pos.targetWidth;
toolbarStyle.top = pos.top + unit;
toolbarStyle.left = (leftPos < 0 ? 0 : leftPos) + unit;

78
src/css_composer/index.js

@ -21,10 +21,8 @@
* * [get](#get)
* * [getAll](#getall)
* * [clear](#clear)
* * [setIdRule](#setidrule)
* * [getIdRule](#getidrule)
* * [setClassRule](#setclassrule)
* * [getClassRule](#getclassrule)
* * [setRule](#setrule)
* * [getRule](#getrule)
*
* @module CssComposer
*/
@ -304,12 +302,81 @@ module.exports = () => {
return result;
},
/**
* Add/update the CSS rule with a generic selector
* @param {string} selectors Selector, eg. '.myclass'
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Additional properties
* @param {String} [opts.atRuleType=''] At-rule type, eg. 'media'
* @param {String} [opts.atRuleParams=''] At-rule parameters, eg. '(min-width: 500px)'
* @return {CssRule} The new/updated rule
* @example
* // Simple class-based rule
* const rule = cc.setRule('.class1.class2', { color: 'red' });
* console.log(rule.toCSS()) // output: .class1.class2 { color: red }
* // With state and other mixed selector
* const rule = cc.setRule('.class1.class2:hover, div#myid', { color: 'red' });
* // output: .class1.class2:hover, div#myid { color: red }
* // With media
* const rule = cc.setRule('.class1:hover', { color: 'red' }, {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)',
* });
* // output: @media (min-width: 500px) { .class1:hover { color: red } }
*/
setRule(selectors, style, opts = {}) {
const { atRuleType, atRuleParams } = opts;
const node = em.get('Parser').parserCss.checkNode({
selectors,
style
})[0];
const { state, selectorsAdd } = node;
const sm = em.get('SelectorManager');
const selector = sm.add(node.selectors);
const rule = this.add(selector, state, atRuleParams, {
selectorsAdd,
atRule: atRuleType
});
rule.setStyle(style, opts);
return rule;
},
/**
* Get the CSS rule by a generic selector
* @param {string} selectors Selector, eg. '.myclass:hover'
* @param {String} [opts.atRuleType=''] At-rule type, eg. 'media'
* @param {String} [opts.atRuleParams=''] At-rule parameters, eg. '(min-width: 500px)'
* @return {CssRule}
* @example
* const rule = cc.getRule('.myclass1:hover');
* const rule2 = cc.getRule('.myclass1:hover, div#myid');
* const rule3 = cc.getRule('.myclass1', {
* atRuleType: 'media',
* atRuleParams: '(min-width: 500px)',
* });
*/
getRule(selectors, opts = {}) {
const sm = em.get('SelectorManager');
const node = em.get('Parser').parserCss.checkNode({ selectors })[0];
const selector = sm.get(node.selectors);
const { state, selectorsAdd } = node;
const { atRuleType, atRuleParams } = opts;
return (
selector &&
this.get(selector, state, atRuleParams, {
selectorsAdd,
atRule: atRuleType
})
);
},
/**
* Add/update the CSS rule with id selector
* @param {string} name Id selector name, eg. 'my-id'
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} The new/updated rule
* @private
* @example
* const rule = cc.setIdRule('myid', { color: 'red' });
* const ruleHover = cc.setIdRule('myid', { color: 'blue' }, { state: 'hover' });
@ -332,6 +399,7 @@ module.exports = () => {
* @param {string} name Id selector name, eg. 'my-id'
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @private
* @example
* const rule = cc.getIdRule('myid');
* const ruleHover = cc.setIdRule('myid', { state: 'hover' });
@ -349,6 +417,7 @@ module.exports = () => {
* @param {Object} style Style properties and values
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule} The new/updated rule
* @private
* @example
* const rule = cc.setClassRule('myclass', { color: 'red' });
* const ruleHover = cc.setClassRule('myclass', { color: 'blue' }, { state: 'hover' });
@ -371,6 +440,7 @@ module.exports = () => {
* @param {string} name Class selector name, eg. 'my-class'
* @param {Object} [opts={}] Custom options, like `state` and `mediaText`
* @return {CssRule}
* @private
* @example
* const rule = cc.getClassRule('myclass');
* const ruleHover = cc.getClassRule('myclass', { state: 'hover' });

73
src/dom_components/index.js

@ -20,6 +20,9 @@
* * [clear](#clear)
* * [load](#load)
* * [store](#store)
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
* * [render](#render)
*
* @module DomComponents
@ -506,13 +509,56 @@ module.exports = () => {
},
/**
* Add new component type
* @param {string} type
* @param {Object} methods
* @private
* Add new component type.
* Read more about this in [Define New Component](https://grapesjs.com/docs/modules/Components.html#define-new-component)
* @param {string} type Component ID
* @param {Object} methods Component methods
* @return {this}
*/
addType(type, methods) {
var compType = this.getType(type);
const {
model = {},
view = {},
isComponent,
extend,
extendView
} = methods;
const compType = this.getType(type);
const extendType = this.getType(extend);
const extendViewType = this.getType(extendView);
const typeToExtend = extendType
? extendType
: compType
? compType
: this.getType('default');
const modelToExt = typeToExtend.model;
const viewToExt = extendViewType
? extendViewType.view
: typeToExtend.view;
// If the model/view is a simple object I need to extend it
if (typeof model === 'object') {
methods.model = modelToExt.extend(
{
...model,
defaults: {
...modelToExt.prototype.defaults,
...(model.defaults || {})
}
},
{
isComponent:
compType && !extendType && !isComponent
? modelToExt.isComponent
: isComponent || (() => 0)
}
);
}
if (typeof view === 'object') {
methods.view = viewToExt.extend({ ...view });
}
if (compType) {
compType.model = methods.model;
compType.view = methods.view;
@ -520,12 +566,15 @@ module.exports = () => {
methods.id = type;
componentTypes.unshift(methods);
}
return this;
},
/**
* Get component type
* @param {string} type
* @private
* Get component type.
* Read more about this in [Define New Component](https://grapesjs.com/docs/modules/Components.html#define-new-component)
* @param {string} type Component ID
* @return {Object} Component type defintion, eg. `{ model: ..., view: ... }`
*/
getType(type) {
var df = componentTypes;
@ -539,6 +588,14 @@ module.exports = () => {
return;
},
/**
* Return the array of all types
* @return {Array}
*/
getTypes() {
return componentTypes;
},
selectAdd(component, opts = {}) {
if (component) {
component.set({

55
src/dom_components/model/Component.js

@ -118,6 +118,24 @@ const Component = Backbone.Model.extend(Styleable).extend(
toolbar: null
},
/**
* Hook method, called once the model is created
*/
init() {},
/**
* Hook method, called when the model has been updated (eg. updated some model's property)
* @param {String} property Property name, if triggered after some property update
* @param {*} value Property value, if triggered after some property update
* @param {*} previous Property previous value, if triggered after some property update
*/
updated(property, value, previous) {},
/**
* Hook method, called once the model has been removed
*/
removed() {},
initialize(props = {}, opt = {}) {
const em = opt.em;
@ -152,7 +170,10 @@ const Component = Backbone.Model.extend(Styleable).extend(
this.em = em;
this.config = opt.config || {};
this.ccid = Component.createId(this);
this.set('attributes', this.get('attributes') || {});
this.set('attributes', {
...(this.defaults.attributes || {}),
...(this.get('attributes') || {})
});
this.initClasses();
this.initTraits();
this.initComponents();
@ -169,10 +190,10 @@ const Component = Backbone.Model.extend(Styleable).extend(
this.emitUpdate(name, ...args)
);
});
this.init();
if (em) {
em.trigger('component:create', this);
if (!opt.temporary) {
this.init();
em && em.trigger('component:create', this);
}
},
@ -188,6 +209,15 @@ const Component = Backbone.Model.extend(Styleable).extend(
return !!(this.get('type') == type);
},
/**
* Get the index of the component in the parent collection.
* @return {Number}
*/
index() {
const { collection } = this;
return collection && collection.indexOf(this);
},
/**
* Find inner components by query string.
* **ATTENTION**: this method works only with already rendered component
@ -499,8 +529,6 @@ const Component = Backbone.Model.extend(Styleable).extend(
return this;
},
init() {},
/**
* Add new component children
* @param {Component|String} components Component to add
@ -704,7 +732,12 @@ const Component = Backbone.Model.extend(Styleable).extend(
attr.style = style;
}
return new this.constructor(attr, opts);
const cloned = new this.constructor(attr, opts);
const event = 'component:clone';
em && em.trigger(event, cloned);
this.trigger(event, cloned);
return cloned;
},
/**
@ -929,6 +962,14 @@ const Component = Backbone.Model.extend(Styleable).extend(
emitUpdate(property, ...args) {
const em = this.em;
const event = 'component:update' + (property ? `:${property}` : '');
property &&
this.updated(
property,
property && this.get(property),
property && this.previous(property),
...args
);
this.trigger(event, ...args);
em && em.trigger(event, this, ...args);
},

2
src/dom_components/model/ComponentLabel.js

@ -1,4 +1,4 @@
import Component from './ComponentText';
const Component = require('./ComponentText');
module.exports = Component.extend(
{

4
src/dom_components/model/ComponentWrapper.js

@ -1,7 +1,5 @@
// We need this one just to identify better the wrapper type
import Component from './Component';
module.exports = Component.extend(
module.exports = require('./Component').extend(
{},
{
isComponent() {

3
src/dom_components/view/ComponentLinkView.js

@ -1,5 +1,4 @@
var Backbone = require('backbone');
var ComponentView = require('./ComponentTextView');
const ComponentView = require('./ComponentTextView');
module.exports = ComponentView.extend({
render(...args) {

30
src/dom_components/view/ComponentView.js

@ -1,5 +1,5 @@
import Backbone from 'backbone';
import { isArray, isEmpty } from 'underscore';
import { isArray, isEmpty, each, keys } from 'underscore';
const Components = require('../model/Components');
const ComponentsView = require('./ComponentsView');
@ -18,7 +18,9 @@ module.exports = Backbone.View.extend({
const model = this.model;
const config = opt.config || {};
const em = config.em;
const modelOpt = model.opt || {};
this.opts = opt;
this.modelOpt = modelOpt;
this.config = config;
this.em = em || '';
this.pfx = config.stylePrefix || '';
@ -39,7 +41,7 @@ module.exports = Backbone.View.extend({
model.view = this;
this.initClasses();
this.initComponents({ avoidRender: 1 });
this.init();
!modelOpt.temporary && this.init();
},
/**
@ -232,17 +234,26 @@ module.exports = Backbone.View.extend({
* @private
* */
updateAttributes() {
const model = this.model;
const attrs = [];
const { model, $el, el } = this;
const defaultAttr = { 'data-gjs-type': model.get('type') || 'default' };
if (model.get('highlightable')) {
defaultAttr['data-highlightable'] = 1;
}
this.$el.attr({
// Remove all current attributes
each(el.attributes, attr => attrs.push(attr.nodeName));
attrs.forEach(attr => $el.removeAttr(attr));
const attr = {
...defaultAttr,
...model.getAttributes()
});
};
// Remove all `false` attributes
keys(attr).forEach(key => attr[key] === false && delete attr[key]);
$el.attr(attr);
this.updateStyle();
},
@ -352,9 +363,12 @@ module.exports = Backbone.View.extend({
},
postRender() {
const { em, model } = this;
this.onRender();
em && em.trigger('component:mount', model);
const { em, model, modelOpt } = this;
if (!modelOpt.temporary) {
this.onRender();
em && em.trigger('component:mount', model);
}
},
onRender() {}

5
src/dom_components/view/ComponentsView.js

@ -14,16 +14,19 @@ module.exports = Backbone.View.extend({
removeChildren(removed) {
const em = this.config.em;
const view = removed.view;
const temp = removed.opt.temporary;
if (!view) return;
view.remove.apply(view);
const children = view.childrenView;
children && children.stopListening();
removed.components().forEach(this.removeChildren.bind(this));
!temp && removed.removed();
if (em) {
removed.get('style-signature') &&
em
.get('Commands')
.run('core:component-style-clear', { target: removed });
em.trigger('component:remove', removed);
!temp && em.trigger('component:remove', removed);
}
},

16
src/editor/config/config.js

@ -223,6 +223,22 @@ module.exports = {
'bottom'
]
},
{
name: 'Flex',
open: false,
buildProps: [
'flex-direction',
'flex-wrap',
'justify-content',
'align-items',
'align-content',
'order',
'flex-basis',
'flex-grow',
'flex-shrink',
'align-self'
]
},
{
name: 'Dimension',
open: false,

9
src/editor/index.js

@ -18,11 +18,11 @@
* ```
*
* ### Components
* * `component:create` - Component is created (only the model, is not yet mounted in the canvas)
* * `component:create` - Component is created (only the model, is not yet mounted in the canvas), called after the init() method
* * `component:mount` - Component is monted to an element and rendered in canvas
* * `component:add` - Triggered when a new component is added to the editor, the model is passed as an argument to the callback
* * `component:remove` - Triggered when a component is removed, the model is passed as an argument to the callback
* * `component:clone` - Triggered when a new component is added by a clone command, the model is passed as an argument to the callback
* * `component:clone` - Triggered when a component is cloned, the new model is passed as an argument to the callback
* * `component:update` - Triggered when a component is updated (moved, styled, etc.), the model is passed as an argument to the callback
* * `component:update:{propertyName}` - Listen any property change, the model is passed as an argument to the callback
* * `component:styleUpdate` - Triggered when the style of the component is updated, the model is passed as an argument to the callback
@ -76,6 +76,9 @@
* ### RTE
* * `rte:enable` - RTE enabled. The view, on which RTE is enabled, is passed as an argument
* * `rte:disable` - RTE disabled. The view, on which RTE is disabled, is passed as an argument
* ### Modal
* * `modal:open` - Modal is opened
* * `modal:close` - Modal is closed
* ### Commands
* * `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview'))
* * `stop:{commandName}` - Triggered when some command is called to stop (eg. editor.stopCommand('preview'))
@ -92,7 +95,7 @@
*/
import $ from 'cash-dom';
module.exports = config => {
export default config => {
var c = config || {},
defaults = require('./config/config'),
EditorModel = require('./model/Editor'),

1
src/editor/model/Editor.js

@ -563,6 +563,7 @@ module.exports = Backbone.Model.extend({
* @private
*/
refreshCanvas() {
this.set('canvasOffset', null);
this.set('canvasOffset', this.get('Canvas').getOffset());
},

4
src/editor/view/EditorView.js

@ -2,7 +2,7 @@ const $ = Backbone.$;
module.exports = Backbone.View.extend({
initialize() {
const model = this.model;
const { model } = this;
model.view = this;
this.conf = model.config;
this.pn = model.get('Panels');
@ -10,7 +10,7 @@ module.exports = Backbone.View.extend({
this.pn.active();
this.pn.disableButtons();
model.runDefault();
setTimeout(() => model.trigger('load'), 0);
setTimeout(() => model.trigger('load', model.get('Editor')));
});
},

22
src/modal_dialog/index.js

@ -40,17 +40,22 @@ module.exports = () => {
*/
name: 'Modal',
getConfig() {
return c;
},
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
init(config) {
c = config || {};
for (var name in defaults) {
if (!(name in c)) c[name] = defaults[name];
}
init(config = {}) {
c = {
...defaults,
...config
};
this.em = c.em;
var ppfx = c.pStylePrefix;
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
@ -68,6 +73,11 @@ module.exports = () => {
this.render().appendTo(el);
},
triggerEvent(event) {
const { em } = this;
em && em.trigger(`modal:${event}`);
},
/**
* Open the modal window
* @param {Object} [opts={}] Options
@ -79,6 +89,7 @@ module.exports = () => {
opts.title && this.setTitle(opts.title);
opts.content && this.setContent(opts.content);
modal.show();
this.triggerEvent('open');
return this;
},
@ -88,6 +99,7 @@ module.exports = () => {
*/
close() {
modal.hide();
this.triggerEvent('close');
return this;
},

2
src/navigator/view/ItemView.js

@ -6,7 +6,7 @@ const inputProp = 'contentEditable';
const $ = Backbone.$;
let ItemsView;
module.exports = Backbone.View.extend({
export default Backbone.View.extend({
events: {
'mousedown [data-toggle-move]': 'startSort',
'touchstart [data-toggle-move]': 'startSort',

2
src/navigator/view/ItemsView.js

@ -1,4 +1,4 @@
const ItemView = require('./ItemView');
import ItemView from './ItemView';
module.exports = require('backbone').View.extend({
initialize(o = {}) {

9
src/panels/view/PanelView.js

@ -13,6 +13,7 @@ module.exports = Backbone.View.extend({
this.id = this.pfx + model.get('id');
this.listenTo(model, 'change:appendContent', this.appendContent);
this.listenTo(model, 'change:content', this.updateContent);
this.listenTo(model, 'change:visible', this.toggleVisible);
model.view = this;
},
@ -30,6 +31,14 @@ module.exports = Backbone.View.extend({
this.$el.html(this.model.get('content'));
},
toggleVisible() {
if (!this.model.get('visible')) {
this.$el.addClass(`${this.ppfx}hidden`);
return;
}
this.$el.removeClass(`${this.ppfx}hidden`);
},
attributes() {
return this.model.get('attributes');
},

6
src/parser/index.js

@ -47,6 +47,9 @@ module.exports = () => {
conf.Parser = this;
pHtml = new parserHtml(conf);
pCss = new parserCss(conf);
this.em = conf.em;
this.parserCss = pCss;
this.parserHtml = pHtml;
return this;
},
@ -56,7 +59,8 @@ module.exports = () => {
* @return {Object}
*/
parseHtml(str) {
pHtml.compTypes = this.compTypes;
const { em, compTypes } = this;
pHtml.compTypes = em ? em.get('DomComponents').getTypes() : compTypes;
return pHtml.parse(str, pCss);
},

2
src/parser/model/BrowserParserCss.js

@ -101,7 +101,7 @@ export const parseCondition = node => {
* @param {Object} style Key-value object of style declarations
* @return {Object}
*/
export const createNode = (selectors, style, opts = {}) => {
export const createNode = (selectors, style = {}, opts = {}) => {
const node = {};
const selLen = selectors.length;
const lastClass = selectors[selLen - 1];

11
src/parser/model/ParserHtml.js

@ -81,8 +81,15 @@ module.exports = config => {
// Iterate over all available Component Types and
// the first with a valid result will be that component
for (let it = 0; it < ct.length; it++) {
obj = ct[it].model.isComponent(node);
if (obj) break;
const compType = ct[it];
obj = compType.model.isComponent(node);
if (obj) {
if (typeof obj !== 'object') {
obj = { type: compType.id };
}
break;
}
}
model = obj;

84
src/selector_manager/index.js

@ -44,7 +44,7 @@
* @module SelectorManager
*/
import { isString, isElement, isObject } from 'underscore';
import { isString, isElement, isObject, isArray } from 'underscore';
const isId = str => isString(str) && str[0] == '#';
const isClass = str => isString(str) && str[0] == '.';
@ -118,22 +118,9 @@ module.exports = config => {
}
},
/**
* Add a new selector to collection if it's not already exists. Class type is a default one
* @param {String} name Selector name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model}
* @example
* var selector = selectorManager.add('selectorName');
* // Same as
* var selector = selectorManager.add('selectorName', {
* type: 1,
* label: 'selectorName'
* });
* */
add(name, opts = {}) {
addSelector(name, opt = {}) {
let opts = { ...opt };
if (isObject(name)) {
opts = name;
} else {
@ -143,6 +130,8 @@ module.exports = config => {
if (isId(opts.name)) {
opts.name = opts.name.substr(1);
opts.type = Selector.TYPE_ID;
} else if (isClass(opts.name)) {
opts.name = opts.name.substr(1);
}
if (opts.label && !opts.name) {
@ -161,6 +150,42 @@ module.exports = config => {
return selector;
},
getSelector(name, type = Selector.TYPE_CLASS) {
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
} else if (isClass(name)) {
name = name.substr(1);
}
return selectors.where({ name, type })[0];
},
/**
* Add a new selector to collection if it's not already exists. Class type is a default one
* @param {String|Array} name Selector/s name
* @param {Object} opts Selector options
* @param {String} [opts.label=''] Label for the selector, if it's not provided the label will be the same as the name
* @param {String} [opts.type=1] Type of the selector. At the moment, only 'class' (1) is available
* @return {Model|Array}
* @example
* const selector = selectorManager.add('selectorName');
* // Same as
* const selector = selectorManager.add('selectorName', {
* type: 1,
* label: 'selectorName'
* });
* // Multiple selectors
* const selectors = selectorManager.add(['.class1', '.class2', '#id1']);
* */
add(name, opts = {}) {
if (isArray(name)) {
return name.map(item => this.addSelector(item, opts));
} else {
return this.addSelector(name, opts);
}
},
/**
* Add class selectors
* @param {Array|string} classes Array or string of classes
@ -184,18 +209,27 @@ module.exports = config => {
/**
* Get the selector by its name
* @param {String} name Selector name
* @param {String|Array} name Selector name
* @param {String} tyoe Selector type
* @return {Model|null}
* @return {Model|Array}
* @example
* var selector = selectorManager.get('selectorName');
* const selector = selectorManager.get('selectorName');
* // or get an array
* const selectors = selectorManager.get(['class1', 'class2']);
* */
get(name, type = Selector.TYPE_CLASS) {
if (isId(name)) {
name = name.substr(1);
type = Selector.TYPE_ID;
get(name, type) {
if (isArray(name)) {
const result = [];
const selectors = name
.map(item => this.getSelector(item))
.filter(item => item);
selectors.forEach(
item => result.indexOf(item) < 0 && result.push(item)
);
return result;
} else {
return this.getSelector(name, type);
}
return selectors.where({ name, type })[0];
},
/**

2
src/selector_manager/model/Selectors.js

@ -4,6 +4,8 @@ const Selector = require('./Selector');
module.exports = require('backbone').Collection.extend({
model: Selector,
modelId: attr => `${attr.name}_${attr.type || Selector.TYPE_CLASS}`,
getStyleable() {
return filter(
this.models,

4
src/storage_manager/config/config.js

@ -53,5 +53,7 @@ module.exports = {
// set contentType paramater of $.ajax
// true: application/json; charset=utf-8'
// false: 'x-www-form-urlencoded'
contentTypeJson: false
contentTypeJson: true,
credentials: 'include'
};

14
src/storage_manager/model/RemoteStorage.js

@ -10,7 +10,8 @@ module.exports = require('backbone').Model.extend({
params: {},
beforeSend() {},
onComplete() {},
contentTypeJson: false
contentTypeJson: false,
credentials: 'include'
},
/**
@ -113,7 +114,7 @@ module.exports = require('backbone').Model.extend({
}
fetchOptions = {
method: opts.method || 'post',
credentials: 'include',
credentials: this.get('credentials'),
headers
};
@ -124,11 +125,10 @@ module.exports = require('backbone').Model.extend({
this.onStart();
this.fetch(url, fetchOptions)
.then(
res =>
((res.status / 200) | 0) == 1
? res.text()
: res.text().then(text => Promise.reject(text))
.then(res =>
((res.status / 200) | 0) == 1
? res.text()
: res.text().then(text => Promise.reject(text))
)
.then(text => this.onResponse(text, clb))
.catch(err => this.onError(err, clbErr));

1
src/style_manager/index.js

@ -25,7 +25,6 @@
* * [removeProperty](#removeproperty)
* * [getProperties](#getproperties)
* * [getModelToStyle](#getmodeltostyle)
* * [getModelToStyle](#getmodeltostyle)
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)

4
src/style_manager/model/Layer.js

@ -18,6 +18,10 @@ module.exports = Backbone.Model.extend({
'properties',
properties instanceof Properties ? properties : new Properties(properties)
);
this.get('properties').forEach(item => {
const { collection } = this;
item.parent = collection && collection.property;
});
// If there is no value I'll try to get it from values
// I need value setted to make preview working

14
src/style_manager/model/Property.js

@ -26,7 +26,19 @@ const Property = require('backbone').Model.extend(
// Use case:
// you can add all SVG CSS properties with toRequire as true
// and then require them on SVG Components
toRequire: 0
toRequire: 0,
// Specifies dependency on other properties of the selected object.
// Property is shown only when all conditions are matched.
//
// example: { display: ['flex', 'block'], position: ['absolute'] };
// in this case the property is only shown when display is
// of value 'flex' or 'block' AND position is 'absolute'
requires: null,
// Specifies dependency on properties of the parent of the selected object.
// Property is shown only when all conditions are matched.
requiresParent: null
},
initialize(props = {}, opts = {}) {

10
src/style_manager/model/PropertyComposite.js

@ -43,7 +43,7 @@ module.exports = Property.extend({
* Update property values
*/
updateValues() {
const values = this.getFullValue().split(this.get('separator'));
const values = this.getFullValue().split(this.getSplitSeparator());
this.get('properties').each((property, i) => {
const len = values.length;
// Try to get value from a shorthand:
@ -56,6 +56,14 @@ module.exports = Property.extend({
});
},
/**
* Split by sperator but avoid it inside parenthesis
* @return {RegExp}
*/
getSplitSeparator() {
return new RegExp(`${this.get('separator')}(?![^\\(]*\\))`);
},
/**
* Returns default value
* @param {Boolean} defaultProps Force to get defaults from properties

154
src/style_manager/model/PropertyFactory.js

@ -40,6 +40,7 @@ module.exports = () => ({
case 'height':
case 'max-height':
case 'min-height':
case 'flex-basis':
obj.fixedValues = ['initial', 'inherit', 'auto'];
break;
case 'font-size':
@ -72,6 +73,12 @@ module.exports = () => ({
obj.type = 'radio';
break;
case 'display':
case 'flex-direction':
case 'flex-wrap':
case 'justify-content':
case 'align-items':
case 'align-content':
case 'align-self':
case 'font-family':
case 'font-weight':
case 'border-style':
@ -84,6 +91,8 @@ module.exports = () => ({
case 'transition-timing-function':
case 'cursor':
case 'overflow':
case 'overflow-x':
case 'overflow-y':
obj.type = 'select';
break;
case 'top':
@ -128,6 +137,10 @@ module.exports = () => ({
case 'transform-scale-x':
case 'transform-scale-y':
case 'transform-scale-z':
case 'order':
case 'flex-grow':
case 'flex-shrink':
case 'flex-basis':
obj.type = 'integer';
break;
case 'margin':
@ -166,6 +179,24 @@ module.exports = () => ({
case 'display':
obj.defaults = 'block';
break;
case 'flex-direction':
obj.defaults = 'row';
break;
case 'flex-wrap':
obj.defaults = 'nowrap';
break;
case 'justify-content':
obj.defaults = 'flex-start';
break;
case 'align-items':
obj.defaults = 'stretch';
break;
case 'align-content':
obj.defaults = 'stretch';
break;
case 'align-self':
obj.defaults = 'auto';
break;
case 'position':
obj.defaults = 'static';
break;
@ -185,10 +216,6 @@ module.exports = () => ({
case 'text-shadow-v':
case 'text-shadow-blur':
case 'border-radius-c':
case 'border-top-left-radius':
case 'border-top-right-radius':
case 'border-bottom-left-radius':
case 'border-bottom-right-radius':
case 'box-shadow-h':
case 'box-shadow-v':
case 'box-shadow-spread':
@ -196,11 +223,20 @@ module.exports = () => ({
case 'transform-rotate-x':
case 'transform-rotate-y':
case 'transform-rotate-z':
case 'order':
case 'flex-grow':
obj.defaults = 0;
break;
case 'border-top-left-radius':
case 'border-top-right-radius':
case 'border-bottom-left-radius':
case 'border-bottom-right-radius':
obj.defaults = '0px';
break;
case 'transform-scale-x':
case 'transform-scale-y':
case 'transform-scale-z':
case 'flex-shrink':
obj.defaults = 1;
break;
case 'box-shadow-blur':
@ -214,6 +250,7 @@ module.exports = () => ({
case 'height':
case 'background-size':
case 'cursor':
case 'flex-basis':
obj.defaults = 'auto';
break;
case 'font-family':
@ -264,32 +301,65 @@ module.exports = () => ({
obj.defaults = 'ease';
break;
case 'overflow':
case 'overflow-x':
case 'overflow-y':
obj.defaults = 'visible';
break;
}
/*
* Add styleable dependency on other properties. Allows properties to be
* dynamically hidden or shown based on values of other properties.
*
* Property will be styleable if all of the properties (keys) in the
* requires object have any of the values specified in the array.
*/
switch (prop) {
case 'flex-direction':
case 'flex-wrap':
case 'justify-content':
case 'align-items':
case 'align-content':
obj.requires = { display: ['flex'] };
break;
case 'order':
case 'flex-basis':
case 'flex-grow':
case 'flex-shrink':
case 'align-self':
obj.requiresParent = { display: ['flex'] };
break;
}
// Units
switch (prop) {
case 'top':
case 'right':
case 'bottom':
case 'left':
case 'margin-top':
case 'margin-right':
case 'margin-bottom':
case 'margin-left':
case 'padding-top':
case 'padding-right':
case 'padding-bottom':
case 'padding-left':
case 'min-height':
case 'min-width':
case 'max-height':
case 'height':
obj.units = ['px', '%', 'vh'];
break;
case 'right':
case 'left':
case 'margin-right':
case 'margin-left':
case 'padding-right':
case 'padding-left':
case 'min-width':
case 'max-width':
case 'width':
case 'height':
case 'text-shadow-h':
obj.units = ['px', '%', 'vw'];
break;
case 'flex-basis':
obj.units = ['px', '%', 'vw', 'vh'];
break;
case 'text-shadow-v':
case 'text-shadow-h':
case 'text-shadow-blur':
case 'border-radius-c':
case 'border-top-left-radius':
@ -346,6 +416,7 @@ module.exports = () => ({
case 'box-shadow-blur':
case 'transition-duration':
case 'perspective':
case 'flex-basis':
obj.min = 0;
break;
}
@ -401,9 +472,64 @@ module.exports = () => ({
{ value: 'block' },
{ value: 'inline' },
{ value: 'inline-block' },
{ value: 'flex' },
{ value: 'none' }
];
break;
case 'flex-direction':
obj.list = [
{ value: 'row' },
{ value: 'row-reverse' },
{ value: 'column' },
{ value: 'column-reverse' }
];
break;
case 'flex-wrap':
obj.list = [
{ value: 'nowrap' },
{ value: 'wrap' },
{ value: 'wrap-reverse' }
];
break;
case 'justify-content':
obj.list = [
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'space-between' },
{ value: 'space-around' },
{ value: 'space-evenly' }
];
break;
case 'align-items':
obj.list = [
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'baseline' },
{ value: 'stretch' }
];
break;
case 'align-content':
obj.list = [
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'space-between' },
{ value: 'space-around' },
{ value: 'stretch' }
];
break;
case 'align-self':
obj.list = [
{ value: 'auto' },
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'baseline' },
{ value: 'stretch' }
];
break;
case 'position':
obj.list = [
{ value: 'static' },
@ -546,6 +672,8 @@ module.exports = () => ({
];
break;
case 'overflow':
case 'overflow-x':
case 'overflow-y':
obj.list = [
{ value: 'visible' },
{ value: 'hidden' },

2
src/style_manager/model/PropertySelect.js

@ -1,4 +1,4 @@
import Property from './PropertyRadio';
const Property = require('./PropertyRadio');
export default Property.extend({
defaults: () => ({

11
src/style_manager/model/Sector.js

@ -15,15 +15,16 @@ module.exports = Backbone.Model.extend({
},
initialize(opts) {
var o = opts || {};
var props = [];
var builded = this.buildProperties(o.buildProps);
!this.get('id') && this.set('id', this.get('name'));
const o = opts || {};
const builded = this.buildProperties(o.buildProps);
const name = this.get('name') || '';
let props = [];
!this.get('id') && this.set('id', name.replace(/ /g, '_').toLowerCase());
if (!builded) props = this.get('properties');
else props = this.extendProperties(builded);
var propsModel = new Properties(props);
const propsModel = new Properties(props);
propsModel.sector = this;
this.set('properties', propsModel);
},

2
src/style_manager/view/PropertyCompositeView.js

@ -111,7 +111,7 @@ module.exports = PropertyView.extend({
// the corresponding value from the requested index, otherwise try
// to get the value of the sub-property
if (targetValue) {
const values = targetValue.split(' ');
const values = targetValue.split(this.model.getSplitSeparator());
value = values[index];
} else {
value =

2
src/style_manager/view/PropertySelectView.js

@ -1,5 +1,5 @@
import Backbone from 'backbone';
import PropertyView from './PropertyView';
const PropertyView = require('./PropertyView');
const $ = Backbone.$;
module.exports = PropertyView.extend({

60
src/style_manager/view/PropertyView.js

@ -1,6 +1,7 @@
import Backbone from 'backbone';
import { bindAll, isArray, isUndefined, debounce } from 'underscore';
import { camelCase } from 'utils/mixins';
import { includes, each } from 'underscore';
const clearProp = 'data-clear-style';
@ -21,11 +22,13 @@ module.exports = Backbone.View.extend({
const pfx = this.pfx;
const icon = model.get('icon');
const info = model.get('info');
const parent = model.parent;
return `
<span class="${pfx}icon ${icon}" title="${info}">
${model.get('name')}
</span>
<b class="${pfx}clear" ${clearProp}>&Cross;</b>
${!parent ? `<b class="${pfx}clear" ${clearProp}>&Cross;</b>` : ''}
`;
},
@ -68,6 +71,15 @@ module.exports = Backbone.View.extend({
em && em.on(`update:component:style:${this.property}`, this.targetUpdated);
//em && em.on(`styleable:change:${this.property}`, this.targetUpdated);
// Listening to changes of properties in this.requires, so that styleable
// changes based on other properties are propagated
const requires = model.get('requires');
requires &&
Object.keys(requires).forEach(property => {
em && em.on(`component:styleUpdate:${property}`, this.targetUpdated);
});
this.listenTo(
this.propTarget,
'update styleManager:update',
@ -89,20 +101,23 @@ module.exports = Backbone.View.extend({
* @private
*/
updateStatus() {
const status = this.model.get('status');
const { model } = this;
const status = model.get('status');
const parent = model.parent;
const pfx = this.pfx;
const ppfx = this.ppfx;
const config = this.config;
const updatedCls = `${ppfx}four-color`;
const computedCls = `${ppfx}color-warn`;
const labelEl = this.$el.children(`.${pfx}label`);
const clearStyle = this.getClearEl().style;
const clearStyleEl = this.getClearEl();
const clearStyle = clearStyleEl ? clearStyleEl.style : {};
labelEl.removeClass(`${updatedCls} ${computedCls}`);
clearStyle.display = 'none';
switch (status) {
case 'updated':
labelEl.addClass(updatedCls);
!parent && labelEl.addClass(updatedCls);
if (config.clearProperties) {
clearStyle.display = 'inline';
@ -120,7 +135,8 @@ module.exports = Backbone.View.extend({
clear(e) {
e && e.stopPropagation();
this.model.clearValue();
this.targetUpdated();
// Skip one stack with setTimeout to avoid inconsistencies
setTimeout(() => this.targetUpdated());
},
/**
@ -179,7 +195,7 @@ module.exports = Backbone.View.extend({
setStatus(value) {
this.model.set('status', value);
const parent = this.model.parent;
parent && parent.set('status', value);
parent && value && parent.set('status', value);
},
emitUpdateTarget: debounce(function() {
@ -400,6 +416,10 @@ module.exports = Backbone.View.extend({
const toRequire = model.get('toRequire');
const unstylable = trg.get('unstylable');
const stylableReq = trg.get('stylable-require');
const requires = model.get('requires');
const requiresParent = model.get('requiresParent');
const sectors = this.sector ? this.sector.collection : null;
const selected = this.em ? this.em.getSelected() : null;
let stylable = trg.get('stylable');
// Stylable could also be an array indicating with which property
@ -421,6 +441,34 @@ module.exports = Backbone.View.extend({
(stylableReq.indexOf(id) >= 0 || stylableReq.indexOf(property) >= 0));
}
// Check if the property is available based on other property's values
if (sectors && requires) {
const properties = Object.keys(requires);
sectors.each(sector => {
sector.get('properties').each(model => {
if (includes(properties, model.id)) {
const values = requires[model.id];
stylable = stylable && includes(values, model.get('value'));
}
});
});
}
// Check if the property is available based on parent's property values
if (requiresParent) {
const parent = selected && selected.parent();
const parentEl = parent && parent.getEl();
if (parentEl) {
const styles = window.getComputedStyle(parentEl);
each(requiresParent, (values, property) => {
stylable =
stylable && styles[property] && includes(values, styles[property]);
});
} else {
stylable = false;
}
}
return stylable;
},

10
src/style_manager/view/SectorView.js

@ -78,15 +78,17 @@ module.exports = Backbone.View.extend({
},
render() {
const { pfx, model } = this;
const { id } = model.attributes;
this.$el.html(
this.template({
pfx: this.pfx,
label: this.model.get('name')
pfx,
label: model.get('name')
})
);
this.$caret = this.$el.find('#' + this.pfx + 'caret');
this.$caret = this.$el.find(`#${pfx}caret`);
this.renderProperties();
this.$el.attr('class', this.pfx + 'sector no-select');
this.$el.attr('class', `${pfx}sector ${pfx}sector__${id} no-select`);
this.updateOpen();
return this;
},

14
src/style_manager/view/SectorsView.js

@ -134,20 +134,16 @@ module.exports = Backbone.View.extend({
* @private
* */
addToCollection(model, fragmentEl) {
const { pfx, target, propTarget, config } = this;
var fragment = fragmentEl || null;
var view = new SectorView({
model,
id:
this.pfx +
model
.get('name')
.replace(' ', '_')
.toLowerCase(),
id: `${pfx}${model.get('id')}`,
name: model.get('name'),
properties: model.get('properties'),
target: this.target,
propTarget: this.propTarget,
config: this.config
target,
propTarget,
config
});
var rendered = view.render().el;

2
src/styles/scss/_gjs_traits.scss

@ -15,7 +15,7 @@
.#{$trt-prefix}trait {
display: flex;
justify-content: start;
justify-content: flex-start;
padding: 5px 10px;
font-weight: lighter;

13
src/utils/Sorter.js

@ -448,11 +448,18 @@ module.exports = Backbone.View.extend({
* @private
*/
styleInFlow(el, parent) {
var style = el.style;
var $el = $(el);
const style = el.style;
const $el = $(el);
const $parent = parent && $(parent);
if (style.overflow && style.overflow !== 'visible') return;
if ($el.css('float') !== 'none') return;
if (parent && $(parent).css('display') == 'flex') return;
if (
$parent &&
$parent.css('display') == 'flex' &&
$parent.css('flex-direction') !== 'column'
)
return;
switch (style.position) {
case 'static':
case 'relative':

11
src/utils/mixins.js

@ -73,7 +73,7 @@ const getUnitFromValue = value => {
const upFirst = value => value[0].toUpperCase() + value.toLowerCase().slice(1);
const camelCase = value => {
const values = value.split('-');
const values = value.split('-').filter(String);
return values[0].toLowerCase() + values.slice(1).map(upFirst);
};
@ -121,6 +121,14 @@ const getModel = (el, $) => {
return model;
};
/**
* Get cross-device pointer event
* @param {Event} ev
* @return {Event}
*/
const getPointerEvent = ev =>
ev.touches && ev.touches[0] ? ev.touches[0] : ev;
export {
on,
off,
@ -132,5 +140,6 @@ export {
getElement,
shallowDiff,
normalizeFloat,
getPointerEvent,
getUnitFromValue
};

114
test/specs/css_composer/index.js

@ -207,6 +207,120 @@ describe('Css Composer', () => {
const rule = obj.getClassRule(name, { state });
expect(rule.selectorsToString()).toEqual(`.${name}:${state}`);
});
test('Create a simple class-based rule with setRule', () => {
const selector = '.test';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Avoid creating multiple rules with the same selector', () => {
const selector = '.test';
obj.setRule(selector, { color: 'red' });
obj.setRule(selector, { color: 'blue' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:blue;`);
});
test('Create a class-based rule with setRule', () => {
const selector = '.test.test2';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a class-based rule with a state, by using setRule', () => {
const selector = '.test.test2:hover';
const result = obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with class-based and mixed selectors', () => {
const selector = '.test.test2:hover, #test .selector';
obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with only mixed selectors', () => {
const selector = '#test1 .class1, .class2 > #id2';
obj.setRule(selector, { color: 'red' });
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector);
expect(rule.get('selectors').length).toEqual(0);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.styleToString()).toEqual(`color:red;`);
});
test('Create a rule with atRule', () => {
const toTest = [
{
selector: '.class1:hover',
style: { color: 'blue' },
opts: {
atRuleType: 'media',
atRuleParams: 'screen and (min-width: 480px)'
}
},
{
selector: '.class1:hover',
style: { color: 'red' },
opts: {
atRuleType: 'media',
atRuleParams: 'screen and (min-width: 480px)'
}
}
];
toTest.forEach(test => {
const { selector, style, opts } = test;
const result = obj.setRule(selector, style, opts);
expect(obj.getAll().length).toEqual(1);
const rule = obj.getRule(selector, opts);
expect(rule.getAtRule()).toEqual(
`@${opts.atRuleType} ${opts.atRuleParams}`
);
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.getStyle()).toEqual(style);
});
});
test('Create different rules by using setRule', () => {
const toTest = [
{ selector: '.class1:hover', style: { color: '#111' } },
{ selector: '.class1.class2', style: { color: '#222' } },
{ selector: '.class1, .class2 .class3', style: { color: 'red' } },
{ selector: '.class1, .class2 .class4', style: { color: 'green' } },
{ selector: '.class4, .class1 .class2', style: { color: 'blue' } },
{
selector: '.class4, .class1 .class2',
style: { color: 'blue' },
opt: { atRuleType: 'media', atRuleParams: '(min-width: 480px)' }
}
];
toTest.forEach(test => {
const { selector, style, opt = {} } = test;
obj.setRule(selector, style, opt);
const rule = obj.getRule(selector, opt);
const atRule = `${opt.atRuleType || ''} ${opt.atRuleParams ||
''}`.trim();
expect(rule.getAtRule()).toEqual(atRule ? `@${atRule}` : '');
expect(rule.selectorsToString()).toEqual(selector);
expect(rule.getStyle()).toEqual(style);
});
expect(obj.getAll().length).toEqual(toTest.length);
});
});
Models.run();

96
test/specs/dom_components/index.js

@ -170,6 +170,102 @@ describe('DOM Components', () => {
margin: '10px'
});
});
test('Add new component type with simple model', () => {
obj = em.get('DomComponents');
const id = 'test-type';
const testProp = 'testValue';
const initialTypes = obj.componentTypes.length;
obj.addType(id, {
model: {
defaults: {
testProp
}
}
});
expect(obj.componentTypes.length).toEqual(initialTypes + 1);
obj.addComponent(`<div data-gjs-type="${id}"></div>`);
const comp = obj.getComponents().at(0);
expect(comp.get('type')).toEqual(id);
expect(comp.get('testProp')).toEqual(testProp);
});
test('Add new component type with custom isComponent', () => {
obj = em.get('DomComponents');
const id = 'test-type';
const testProp = 'testValue';
obj.addType(id, {
isComponent: el => {
return el.getAttribute('test-prop') === testProp;
}
});
expect(obj.componentTypes[0].id).toEqual(id);
obj.addComponent(`<div test-prop="${testProp}"></div>`);
const comp = obj.getComponents().at(0);
expect(comp.get('type')).toEqual(id);
expect(comp.getAttributes()['test-prop']).toEqual(testProp);
});
test('Extend component type with custom model and view', () => {
obj = em.get('DomComponents');
const id = 'image';
const testProp = 'testValue';
const initialTypes = obj.getTypes().length;
obj.addType(id, {
model: {
defaults: {
testProp
}
},
view: {
onRender() {
this.el.style.backgroundColor = 'red';
}
}
});
expect(obj.getTypes().length).toBe(initialTypes);
obj.addComponent(`<img src="##"/>`);
const comp = obj.getComponents().at(0);
expect(comp.get('type')).toBe(id);
expect(comp.get('testProp')).toBe(testProp);
expect(comp.get('editable')).toBe(1);
});
test('Add new component type by extending another one, without isComponent', () => {
obj = em.get('DomComponents');
const id = 'test-type';
const testProp = 'testValue';
obj.addType(id, {
extend: 'image',
model: {
defaults: {
testProp
}
}
});
obj.addComponent(`<img src="##"/>`);
expect(obj.getTypes()[0].id).toEqual(id);
const comp = obj.getComponents().at(0);
// I'm not specifying the isComponent
expect(comp.get('type')).toBe('image');
expect(comp.get('editable')).toBe(1);
expect(comp.get('testProp')).toBeFalsy();
});
test('Add new component type by extending another one, with custom isComponent', () => {
obj = em.get('DomComponents');
const id = 'test-type';
const testProp = 'testValue';
obj.addType(id, {
extend: 'image',
isComponent: el => el.getAttribute('test-prop') === testProp
});
obj.addComponent(`<img src="##" test-prop="${testProp}"/>`);
expect(obj.getTypes()[0].id).toEqual(id);
const comp = obj.getComponents().at(0);
expect(comp.get('type')).toBe(id);
expect(comp.get('editable')).toBe(1);
});
});
ComponentModels.run();

13
test/specs/panels/view/PanelView.js

@ -39,6 +39,19 @@ module.exports = {
expect(view.$el.html()).toEqual('test2');
});
test('Hide panel', () => {
expect(view.$el.hasClass('hidden')).toBeFalsy();
model.set('visible', false);
expect(view.$el.hasClass('hidden')).toBeTruthy();
});
test('Show panel', () => {
model.set('visible', false);
expect(view.$el.hasClass('hidden')).toBeTruthy();
model.set('visible', true);
expect(view.$el.hasClass('hidden')).toBeFalsy();
});
describe('Init with options', () => {
beforeEach(() => {
model = new Panel({

60
test/specs/selector_manager/index.js

@ -60,6 +60,13 @@ describe('SelectorManager', () => {
expect(sel.get('label')).toEqual(name);
});
test('Check name property by adding as class', () => {
var name = 'test';
var sel = obj.add(`.${name}`);
expect(sel.get('name')).toEqual(name);
expect(sel.get('label')).toEqual(name);
});
test('Add 2 selectors', () => {
obj.add('test');
obj.add('test2');
@ -72,6 +79,59 @@ describe('SelectorManager', () => {
expect(obj.getAll().length).toEqual(1);
});
test('Add multiple selectors', () => {
const cls = [
'.test1',
'test1',
'.test2',
'.test2',
'#test3',
'test3',
'test3',
'#test3'
];
const result = obj.add(cls);
expect(Array.isArray(result)).toEqual(true);
const concat = obj
.getAll()
.map(item => item.getFullName())
.join('');
expect(concat).toEqual('.test1.test2#test3.test3');
expect(obj.getAll().length).toEqual(4);
expect(
obj
.getAll()
.at(0)
.getFullName()
).toEqual('.test1');
expect(
obj
.getAll()
.at(1)
.getFullName()
).toEqual('.test2');
expect(
obj
.getAll()
.at(2)
.getFullName()
).toEqual('#test3');
expect(
obj
.getAll()
.at(3)
.getFullName()
).toEqual('.test3');
expect(obj.get(cls).length).toEqual(4);
expect(
obj
.get(cls)
.map(item => item.getFullName())
.join('')
).toEqual(concat);
});
test('Get selector', () => {
var name = 'test';
var sel = obj.add(name);

45
test/specs/storage_manager/model/Models.js

@ -96,6 +96,51 @@ module.exports = {
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[0]).toEqual(endpointLoad);
});
test("Load data with credentials option as 'include' by default", () => {
obj.load(['item1', 'item2']);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: 'include'
});
});
test("Store data with credentials option as 'include' by default", () => {
obj.store(data);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: 'include'
});
});
test('Store data with credentials option as false ', () => {
obj = new RemoteStorage({ ...storageOptions, credentials: false });
sinon
.stub(obj, 'fetch')
.returns(Promise.resolve(mockResponse({ data: 1 })));
obj.store(data);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: false
});
});
test('Load data with credentials option as false', () => {
obj = new RemoteStorage({ ...storageOptions, credentials: false });
sinon
.stub(obj, 'fetch')
.returns(Promise.resolve(mockResponse({ data: 1 })));
obj.load(['item1', 'item2']);
const callResult = obj.fetch;
expect(callResult.called).toEqual(true);
expect(callResult.firstCall.args[1]).toMatchObject({
credentials: false
});
});
});
}
};

219
test/specs/style_manager/model/Models.js

@ -60,6 +60,7 @@ module.exports = {
{ value: 'block' },
{ value: 'inline' },
{ value: 'inline-block' },
{ value: 'flex' },
{ value: 'none' }
]
});
@ -369,12 +370,121 @@ module.exports = {
{ value: 'block' },
{ value: 'inline' },
{ value: 'inline-block' },
{ value: 'flex' },
{ value: 'none' }
]
}
]);
});
test('Build flex-direction', () => {
expect(obj.build('flex-direction')).toEqual([
{
property: 'flex-direction',
type: 'select',
defaults: 'row',
list: [
{ value: 'row' },
{ value: 'row-reverse' },
{ value: 'column' },
{ value: 'column-reverse' }
],
requires: { display: ['flex'] }
}
]);
});
test('Build flex-wrap', () => {
expect(obj.build('flex-wrap')).toEqual([
{
property: 'flex-wrap',
type: 'select',
defaults: 'nowrap',
list: [
{ value: 'nowrap' },
{ value: 'wrap' },
{ value: 'wrap-reverse' }
],
requires: { display: ['flex'] }
}
]);
});
test('Build justify-content', () => {
expect(obj.build('justify-content')).toEqual([
{
property: 'justify-content',
type: 'select',
defaults: 'flex-start',
list: [
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'space-between' },
{ value: 'space-around' },
{ value: 'space-evenly' }
],
requires: { display: ['flex'] }
}
]);
});
test('Build align-items', () => {
expect(obj.build('align-items')).toEqual([
{
property: 'align-items',
type: 'select',
defaults: 'stretch',
list: [
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'baseline' },
{ value: 'stretch' }
],
requires: { display: ['flex'] }
}
]);
});
test('Build align-content', () => {
expect(obj.build('align-content')).toEqual([
{
property: 'align-content',
type: 'select',
defaults: 'stretch',
list: [
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'space-between' },
{ value: 'space-around' },
{ value: 'stretch' }
],
requires: { display: ['flex'] }
}
]);
});
test('Build align-self', () => {
expect(obj.build('align-self')).toEqual([
{
property: 'align-self',
type: 'select',
defaults: 'auto',
list: [
{ value: 'auto' },
{ value: 'flex-start' },
{ value: 'flex-end' },
{ value: 'center' },
{ value: 'baseline' },
{ value: 'stretch' }
],
requiresParent: { display: ['flex'] }
}
]);
});
test('Build position', () => {
expect(obj.build('position')).toEqual([
{
@ -391,42 +501,73 @@ module.exports = {
]);
});
test('Build top, left, right, bottom', () => {
test('Build left, right', () => {
var res = {
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vw'],
defaults: 0
};
res.property = 'top';
expect(obj.build('top')).toEqual([res]);
res.property = 'right';
expect(obj.build('right')).toEqual([res]);
res.property = 'bottom';
expect(obj.build('bottom')).toEqual([res]);
res.property = 'left';
expect(obj.build('left')).toEqual([res]);
});
test('Build width and height family', () => {
test('Build top, bottom', () => {
var res = {
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vh'],
defaults: 0
};
res.property = 'top';
expect(obj.build('top')).toEqual([res]);
res.property = 'bottom';
expect(obj.build('bottom')).toEqual([res]);
});
test('Build width family', () => {
var res = {
type: 'integer',
units: ['px', '%', 'vw'],
defaults: 'auto',
fixedValues: ['initial', 'inherit', 'auto'],
min: 0
};
res.property = 'width';
expect(obj.build('width')).toEqual([res]);
res.property = 'min-width';
expect(obj.build('min-width')).toEqual([res]);
res.property = 'max-width';
expect(obj.build('max-width')).toEqual([res]);
});
test('Build flex-basis', () => {
var res = {
type: 'integer',
units: ['px', '%', 'vw', 'vh'],
defaults: 'auto',
fixedValues: ['initial', 'inherit', 'auto'],
requiresParent: { display: ['flex'] },
min: 0
};
res.property = 'flex-basis';
expect(obj.build('flex-basis')).toEqual([res]);
});
test('Build height family', () => {
var res = {
type: 'integer',
units: ['px', '%', 'vh'],
defaults: 'auto',
fixedValues: ['initial', 'inherit', 'auto'],
min: 0
};
res.property = 'height';
expect(obj.build('height')).toEqual([res]);
res.property = 'min-height';
expect(obj.build('min-height')).toEqual([res]);
res.property = 'max-height';
expect(obj.build('max-height')).toEqual([res]);
res.property = 'min-width';
expect(obj.build('min-width')).toEqual([res]);
res.property = 'max-width';
expect(obj.build('max-width')).toEqual([res]);
});
test('Build margin', () => {
@ -438,28 +579,28 @@ module.exports = {
fixedValues: ['initial', 'inherit', 'auto'],
property: 'margin-top',
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vh'],
defaults: 0
},
{
fixedValues: ['initial', 'inherit', 'auto'],
property: 'margin-right',
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vw'],
defaults: 0
},
{
fixedValues: ['initial', 'inherit', 'auto'],
property: 'margin-bottom',
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vh'],
defaults: 0
},
{
fixedValues: ['initial', 'inherit', 'auto'],
property: 'margin-left',
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vw'],
defaults: 0
}
]
@ -476,7 +617,7 @@ module.exports = {
property: 'padding-top',
fixedValues: ['initial', 'inherit', 'auto'],
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vh'],
defaults: 0,
min: 0
},
@ -484,7 +625,7 @@ module.exports = {
property: 'padding-right',
fixedValues: ['initial', 'inherit', 'auto'],
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vw'],
defaults: 0,
min: 0
},
@ -492,7 +633,7 @@ module.exports = {
property: 'padding-bottom',
fixedValues: ['initial', 'inherit', 'auto'],
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vh'],
defaults: 0,
min: 0
},
@ -500,7 +641,7 @@ module.exports = {
property: 'padding-left',
fixedValues: ['initial', 'inherit', 'auto'],
type: 'integer',
units: ['px', '%'],
units: ['px', '%', 'vw'],
defaults: 0,
min: 0
}
@ -685,7 +826,7 @@ module.exports = {
property: 'border-top-left-radius',
type: 'integer',
units: ['px', '%'],
defaults: 0,
defaults: '0px',
min: 0
},
{
@ -693,21 +834,21 @@ module.exports = {
type: 'integer',
units: ['px', '%'],
min: 0,
defaults: 0
defaults: '0px'
},
{
property: 'border-bottom-left-radius',
type: 'integer',
units: ['px', '%'],
min: 0,
defaults: 0
defaults: '0px'
},
{
property: 'border-bottom-right-radius',
type: 'integer',
units: ['px', '%'],
min: 0,
defaults: 0
defaults: '0px'
}
]
};
@ -1014,6 +1155,36 @@ module.exports = {
};
expect(obj.build('overflow')).toEqual([res]);
});
test('Build overflow-x', () => {
var res = {
type: 'select',
property: 'overflow-x',
defaults: 'visible',
list: [
{ value: 'visible' },
{ value: 'hidden' },
{ value: 'scroll' },
{ value: 'auto' }
]
};
expect(obj.build('overflow-x')).toEqual([res]);
});
test('Build overflow-y', () => {
var res = {
type: 'select',
property: 'overflow-y',
defaults: 'visible',
list: [
{ value: 'visible' },
{ value: 'hidden' },
{ value: 'scroll' },
{ value: 'auto' }
]
};
expect(obj.build('overflow-y')).toEqual([res]);
});
});
}
};

Loading…
Cancel
Save