Browse Source

Merge remote-tracking branch 'origin/dev' into canvas-module

pull/4327/head
Alex Ritter 4 years ago
parent
commit
e1fd79fc1e
  1. 4
      dist/grapes.min.js
  2. 2
      dist/grapes.min.js.map
  3. 1
      docs/.vuepress/components/DemoBasicBlocks.vue
  4. 7
      docs/.vuepress/config.js
  5. 17
      docs/api.js
  6. 24
      docs/api/asset.md
  7. 99
      docs/api/assets.md
  8. 14
      docs/api/block.md
  9. 6
      docs/api/block_manager.md
  10. 646
      docs/api/component.md
  11. 106
      docs/api/components.md
  12. 2
      docs/api/css_composer.md
  13. 83
      docs/api/css_rule.md
  14. 86
      docs/api/editor.md
  15. 44
      docs/api/pages.md
  16. 28
      docs/api/selector_manager.md
  17. 142
      docs/api/storage_manager.md
  18. 14
      docs/api/undo_manager.md
  19. 2
      package.json
  20. 30
      src/abstract/Module.ts
  21. 1
      src/asset_manager/index.js
  22. 6
      src/asset_manager/model/Asset.js
  23. 4
      src/block_manager/index.js
  24. 4
      src/block_manager/model/Block.js
  25. 2
      src/commands/view/SwitchVisibility.js
  26. 2
      src/css_composer/index.js
  27. 10
      src/dom_components/index.ts
  28. 5
      src/dom_components/model/Component.js
  29. 40
      src/dom_components/view/ComponentView.js
  30. 36
      src/domain_abstract/ui/InputColor.js
  31. 2
      src/editor/view/EditorView.ts
  32. 1
      src/modal_dialog/config/config.js
  33. 12
      src/navigator/config/config.ts
  34. 108
      src/navigator/index.js
  35. 285
      src/navigator/index.ts
  36. 329
      src/navigator/view/ItemView.ts
  37. 84
      src/navigator/view/ItemsView.ts
  38. 2
      src/pages/index.ts
  39. 1
      src/pages/model/Page.ts
  40. 8
      src/pages/model/Pages.ts
  41. 2
      src/selector_manager/index.ts
  42. 4
      src/storage_manager/index.js
  43. 2
      src/style_manager/model/Property.js
  44. 10
      src/style_manager/model/PropertyFactory.js
  45. 2
      src/style_manager/model/PropertyStack.js
  46. 2
      src/trait_manager/view/TraitsView.js
  47. 3
      src/undo_manager/index.js
  48. 22
      src/utils/Resizer.js
  49. 97
      src/utils/mixins.ts
  50. 24
      test/specs/navigator/view/ItemView.js
  51. 14
      yarn.lock

4
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

1
docs/.vuepress/components/DemoBasicBlocks.vue

@ -20,6 +20,7 @@ export default {
<style>
.gjs {
border: 3px solid #444;
box-sizing: border-box;
}
.gjs-block {
width: auto;

7
docs/.vuepress/config.js

@ -1,6 +1,7 @@
const version = require('./../../package.json').version;
const isDev = process.argv[2] === 'dev';
const devPath = 'http://localhost:8080/dist';
const devPath = 'http://localhost:8080';
const baseUrl = 'https://grapesjs.com';
const subDivider = " ‍ ‍ ‍ ";
module.exports = {
@ -10,8 +11,8 @@ module.exports = {
serviceWorker: false, // Enable Service Worker for offline usage
head: [
['link', { rel: 'icon', href: '/logo-icon.png' }],
['link', { rel: 'stylesheet', href: isDev ? `${devPath}/css/grapes.min.css` : `../stylesheets/grapes.min.css?v${version}` }],
['script', { src: isDev ? `${devPath}/grapes.min.js` : `../js/grapes.min.js?v${version}` }],
['link', { rel: 'stylesheet', href: isDev ? `${devPath}/dist/css/grapes.min.css` : `${baseUrl}/stylesheets/grapes.min.css?v${version}` }],
['script', { src: isDev ? `${devPath}/grapes.min.js` : `${baseUrl}/js/grapes.min.js?v${version}` }],
],
localesSKIP: {
'/': {

17
docs/api.js

@ -17,7 +17,7 @@ async function generateDocs () {
['block_manager/index.js', 'block_manager.md'],
['block_manager/model/Block.js', 'block.md'],
['commands/index.js', 'commands.md'],
['dom_components/index.js', 'components.md'],
['dom_components/index.ts', 'components.md'],
['dom_components/model/Component.js', 'component.md'],
['panels/index.js', 'panels.md'],
['style_manager/index.js', 'style_manager.md'],
@ -46,8 +46,14 @@ async function generateDocs () {
['pages/index.ts', 'pages.md'],
['pages/model/Page.ts', 'page.md'],
['parser/index.js', 'parser.md'],
].map(async (file) =>
documentation.build([`${srcRoot}/${file[0]}`], { shallow: true })
].map(async (file) => {
const filePath = `${srcRoot}/${file[0]}`;
if (!fs.existsSync(filePath)) {
throw `File not found '${filePath}'`;
}
return documentation.build([filePath], { shallow: true })
.then(cm => documentation.formats.md(cm, /*{ markdownToc: true }*/))
.then(output => {
const res = output
@ -57,11 +63,12 @@ async function generateDocs () {
.replace(/<\(\\\[/g, '<([')
.replace(/\| \\\[/g, '| [')
.replace(/\\n```js/g, '```js')
.replace(/docsjs\./g, '')
.replace('**Extends Model**', '');
fs.writeFileSync(`${docRoot}/api/${file[1]}`, res);
log('Created', file[1]);
})
));
});
}));
log('API Reference generation done!');
};

24
docs/api/asset.md

@ -2,18 +2,16 @@
## Asset
### Properties
* `type` **[String][1]** Asset type, eg. 'image'.
* `src` **[String][1]** Asset URL, eg. '[https://.../image.png][2]'.
* `type` **[String][1]** Asset type, eg. `'image'`.
* `src` **[String][1]** Asset URL, eg. `'https://.../image.png'`.
### getType
## getType
Get asset type.
#### Examples
### Examples
```javascript
// Asset: { src: 'https://.../image.png', type: 'image' }
@ -22,11 +20,11 @@ asset.getType(); // -> 'image'
Returns **[String][1]**
### getSrc
## getSrc
Get asset URL.
#### Examples
### Examples
```javascript
// Asset: { src: 'https://.../image.png' }
@ -35,11 +33,11 @@ asset.getSrc(); // -> 'https://.../image.png'
Returns **[String][1]**
### getFilename
## getFilename
Get filename of the asset (based on `src`).
#### Examples
### Examples
```javascript
// Asset: { src: 'https://.../image.png' }
@ -50,11 +48,11 @@ asset.getFilename(); // -> 'image'
Returns **[String][1]**
### getExtension
## getExtension
Get extension of the asset (based on `src`).
#### Examples
### Examples
```javascript
// Asset: { src: 'https://.../image.png' }
@ -66,5 +64,3 @@ asset.getExtension(); // -> ''
Returns **[String][1]**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://.../image.png

99
docs/api/assets.md

@ -42,9 +42,7 @@ const assetManager = editor.AssetManager;
* [getAll][7]
* [getAllVisible][8]
* [remove][9]
* [store][10]
* [load][11]
* [getContainer][12]
* [getContainer][10]
[Asset]: asset.html
@ -54,10 +52,10 @@ Open the asset manager.
### Parameters
* `options` **[Object][13]?** Options for the asset manager. (optional, default `{}`)
* `options` **[Object][11]?** Options for the asset manager. (optional, default `{}`)
* `options.types` **[Array][14]<[String][15]>** Types of assets to show. (optional, default `['image']`)
* `options.select` **[Function][16]?** Type of operation to perform on asset selection. If not specified, nothing will happen.
* `options.types` **[Array][12]<[String][13]>** Types of assets to show. (optional, default `['image']`)
* `options.select` **[Function][14]?** Type of operation to perform on asset selection. If not specified, nothing will happen.
### Examples
@ -97,7 +95,7 @@ Checks if the asset manager is open
assetManager.isOpen(); // true | false
```
Returns **[Boolean][17]**
Returns **[Boolean][15]**
## add
@ -105,8 +103,8 @@ Add new asset/s to the collection. URLs are supposed to be unique
### Parameters
* `asset` **([String][15] | [Object][13] | [Array][14]<[String][15]> | [Array][14]<[Object][13]>)** URL strings or an objects representing the resource.
* `opts` **[Object][13]?** Options (optional, default `{}`)
* `asset` **([String][13] | [Object][11] | [Array][12]<[String][13]> | [Array][12]<[Object][11]>)** URL strings or an objects representing the resource.
* `opts` **[Object][11]?** Options (optional, default `{}`)
### Examples
@ -133,7 +131,7 @@ Return asset by URL
### Parameters
* `src` **[String][15]** URL of the asset
* `src` **[String][13]** URL of the asset
### Examples
@ -161,7 +159,7 @@ Remove asset
### Parameters
* `asset` **([String][15] | [Asset])** Asset or asset URL
* `asset` **([String][13] | [Asset])** Asset or asset URL
* `opts`
### Examples
@ -175,70 +173,11 @@ assetManager.remove(asset);
Returns **[Asset]** Removed asset
## store
Store assets data to the selected storage
### Parameters
* `noStore` **[Boolean][17]** If true, won't store
### Examples
```javascript
var assets = assetManager.store();
```
Returns **[Object][13]** Data to store
## load
Load data from the passed object.
The fetched data will be added to the collection.
### Parameters
* `data` **[Object][13]** Object of data to load (optional, default `{}`)
### Examples
```javascript
var assets = assetManager.load({
assets: [...]
})
```
Returns **[Object][13]** Loaded assets
## getContainer
Return the Asset Manager Container
Returns **[HTMLElement][18]**
## render
Render assets
### Parameters
* `assts`
* `assets` **[array][14]** Assets to render, without the argument will render all global assets
### Examples
```javascript
// Render all assets
assetManager.render();
// Render some of the assets
const assets = assetManager.getAll();
assetManager.render(assets.filter(
asset => asset.get('category') == 'cats'
));
```
Returns **[HTMLElement][18]**
Returns **[HTMLElement][16]**
[1]: https://github.com/artf/grapesjs/blob/master/src/asset_manager/config/config.js
@ -258,20 +197,16 @@ Returns **[HTMLElement][18]**
[9]: #remove
[10]: #store
[11]: #load
[12]: #getcontainer
[10]: #getcontainer
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[18]: https://developer.mozilla.org/docs/Web/HTML/Element
[16]: https://developer.mozilla.org/docs/Web/HTML/Element

14
docs/api/block.md

@ -2,8 +2,6 @@
## Block
### Properties
* `label` **[String][1]** Block label, eg. `My block`
@ -17,31 +15,31 @@
* `onClick` **[Function][5]?** Custom behavior on click, eg. `(block, editor) => editor.getWrapper().append(block.get('content'))`
* `attributes` **[Object][2]?** Block attributes to apply in the view element
### getId
## getId
Get block id
Returns **[String][1]**
### getLabel
## getLabel
Get block label
Returns **[String][1]**
### getMedia
## getMedia
Get block media
Returns **[String][1]**
### getContent
## getContent
Get block content
Returns **([Object][2] | [String][1] | [Array][6]<([Object][2] | [String][1])>)** Component definition | HTML string
Returns **([Object][2] | [String][1] | [Array][6]<([Object][2] | [String][1])>)**
### getCategoryLabel
## getCategoryLabel
Get block category label

6
docs/api/block_manager.md

@ -1,6 +1,6 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## BlockManager
## Blocks
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
@ -19,7 +19,7 @@ Once the editor is instantiated you can use its API and listen to its events. Be
editor.on('block:add', (block) => { ... });
// Use the API
const blockManager = editor.BlockManager;
const blockManager = editor.Blocks;
blockManager.add(...);
```
@ -171,7 +171,7 @@ blockManager.render();
// Render new set of blocks
const blocks = blockManager.getAll();
const filtered = blocks.filter(block => block.get('category').id == 'Sections')
const filtered = blocks.filter(block => block.get('category') == 'sections')
blockManager.render(filtered);
// Or a new set from an array

646
docs/api/component.md

File diff suppressed because it is too large

106
docs/api/components.md

@ -47,37 +47,20 @@ cmp.addType(...);
## Methods
* [getWrapper][2]
* [getComponents][3]
* [addComponent][4]
* [clear][5]
* [load][6]
* [store][7]
* [addType][8]
* [getType][9]
* [getTypes][10]
* [render][11]
## load
Load components from the passed object, if the object is empty will try to fetch them
autonomously from the selected storage
The fetched data will be added to the collection
### Parameters
* `data` **[Object][12]** Object of data to load (optional, default `''`)
* [getComponents][3]
Returns **[Object][12]** Loaded data
* [addComponent][4]
## store
* [clear][5]
Store components on the selected storage
* [addType][6]
### Parameters
* [getType][7]
* `noStore` **[Boolean][13]** If true, won't store
* [getTypes][8]
Returns **[Object][12]** Data to store
* [Component]: component.html
## getWrapper
@ -93,7 +76,7 @@ wrapper.set('style', {'background-color': 'red'});
wrapper.set('attributes', {'title': 'Hello!'});
```
Returns **Component** Root Component
Returns **[Component]** Root Component
## getComponents
@ -126,7 +109,7 @@ comp1Children.add([
wrapperChildren.remove(comp2);
```
Returns **[Components][14]** Collection of components
Returns **[Components][9]** Collection of components
## addComponent
@ -135,20 +118,20 @@ as 'cmp.getComponents().add(...)'
### Parameters
* `component` **([Object][12] | Component | [Array][15]<[Object][12]>)** Component/s to add
* `component` **([Object][10] | [Component] | [Array][11]<[Object][10]>)** Component/s to add
* `component.tagName` **[string][16]** Tag name (optional, default `'div'`)
* `component.type` **[string][16]** Type of the component. Available: ''(default), 'text', 'image' (optional, default `''`)
* `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][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][16]** String inside component (optional, default `''`)
* `component.style` **[Object][12]** Style object (optional, default `{}`)
* `component.attributes` **[Object][12]** Attribute object (optional, default `{}`)
* `opt` **[Object][12]** the options object to be used by the \[Components.add][getComponents][3] method (optional, default `{}`)
* `component.content` **[string][12]** String inside component (optional, default `''`)
* `component.style` **[Object][10]** Style object (optional, default `{}`)
* `component.attributes` **[Object][10]** Attribute object (optional, default `{}`)
* `opt` **[Object][10]** the options object to be used by the \[Components.add][getComponents][14] method (optional, default `{}`)
### Examples
@ -165,16 +148,7 @@ var comp1 = cmp.addComponent({
});
```
Returns **(Component | [Array][15]\<Component>)** Component/s added
## render
Render and returns wrapper element with all components inside.
Once the wrapper is rendered, and it's what happens when you init the editor,
the all new components will be added automatically and property changes are all
updated immediately
Returns **[HTMLElement][17]**
Returns **([Component] | [Array][11]<[Component]>)** Component/s added
## clear
@ -189,25 +163,25 @@ Returns **this**
## addType
Add new component type.
Read more about this in [Define New Component][18]
Read more about this in [Define New Component][15]
### Parameters
* `type` **[string][16]** Component ID
* `methods` **[Object][12]** Component methods
* `type` **[string][12]** Component ID
* `methods` **[Object][10]** Component methods
Returns **this**
## getType
Get component type.
Read more about this in [Define New Component][18]
Read more about this in [Define New Component][15]
### Parameters
* `type` **[string][16]** Component ID
* `type` **[string][12]** Component ID
Returns **[Object][12]** Component type definition, eg. `{ model: ..., view: ... }`
Returns **[Object][10]** Component type definition, eg. `{ model: ..., view: ... }`
## removeType
@ -215,16 +189,16 @@ Remove component type
### Parameters
* `id`
* `type` **[string][16]** Component ID
* `id` **[string][12]**
* `type` **[string][12]** Component ID
Returns **([Object][12] | [undefined][19])** Removed component type, undefined otherwise
Returns **([Object][10] | [undefined][16])** Removed component type, undefined otherwise
## getTypes
Return the array of all types
Returns **[Array][15]**
Returns **[Array][11]**
[1]: https://github.com/artf/grapesjs/blob/master/src/dom_components/config/config.js
@ -236,30 +210,24 @@ Returns **[Array][15]**
[5]: #clear
[6]: #load
[6]: #addtype
[7]: #store
[7]: #gettype
[8]: #addtype
[8]: #gettypes
[9]: #gettype
[9]: #components
[10]: #gettypes
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[11]: #render
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[14]: #components
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[17]: https://developer.mozilla.org/docs/Web/HTML/Element
[14]: getComponents
[18]: https://grapesjs.com/docs/modules/Components.html#define-new-component
[15]: https://grapesjs.com/docs/modules/Components.html#define-new-component
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined

2
docs/api/css_composer.md

@ -1,6 +1,6 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## CssComposer
## Css
This module manages CSS rules in the canvas.
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]

83
docs/api/css_rule.md

@ -6,15 +6,15 @@
### Properties
- `selectors` **[Array][1]\<Selector>** Array of selectors
- `style` **[Object][2]** Object containing style definitions
- `selectorsAdd` **[String][3]?** Additional string css selectors
- `atRuleType` **[String][3]?** Type of at-rule, eg. `media`, 'font-face'
- `mediaText` **[String][3]?** At-rule value, eg. `(max-width: 1000px)`
- `singleAtRule` **[Boolean][4]?** This property is used only on at-rules, like 'page' or 'font-face', where the block containes only style declarations
- `state` **[String][3]?** State of the rule, eg: `hover`, `focused`
- `important` **([Boolean][4] | [Array][1]<[String][3]>)?** If true, sets `!important` on all properties. You can also pass an array to specify properties on which use important
- `stylable` **[Boolean][4]?** Indicates if the rule is stylable from the editor[Device]: device.html[State]: state.html[Component]: component.html
* `selectors` **[Array][1]\<Selector>** Array of selectors
* `style` **[Object][2]** Object containing style definitions
* `selectorsAdd` **[String][3]?** Additional string css selectors
* `atRuleType` **[String][3]?** Type of at-rule, eg. `media`, 'font-face'
* `mediaText` **[String][3]?** At-rule value, eg. `(max-width: 1000px)`
* `singleAtRule` **[Boolean][4]?** This property is used only on at-rules, like 'page' or 'font-face', where the block containes only style declarations
* `state` **[String][3]?** State of the rule, eg: `hover`, `focused`
* `important` **([Boolean][4] | [Array][1]<[String][3]>)?** If true, sets `!important` on all properties. You can also pass an array to specify properties on which use important
* `stylable` **[Boolean][4]?** Indicates if the rule is stylable from the editor[Device]: device.html[State]: state.html[Component]: component.html
### getAtRule
@ -23,18 +23,14 @@ Returns the at-rule statement when exists, eg. `@media (...)`, `@keyframes`
#### Examples
```javascript
const cssRule = editor.Css.setRule(
".class1",
{ color: "red" },
{
atRuleType: "media",
atRuleParams: "(min-width: 500px)",
}
);
const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {
atRuleType: 'media',
atRuleParams: '(min-width: 500px)'
});
cssRule.getAtRule(); // "@media (min-width: 500px)"
```
Returns **[String][3]**
Returns **[String][3]**
### selectorsToString
@ -42,19 +38,19 @@ Return selectors of the rule as a string
#### Parameters
- `opts` **[Object][2]?** Options (optional, default `{}`)
* `opts` **[Object][2]?** Options (optional, default `{}`)
- `opts.skipState` **[Boolean][4]?** Skip state from the result
* `opts.skipState` **[Boolean][4]?** Skip state from the result
#### Examples
```javascript
const cssRule = editor.Css.setRule(".class1:hover", { color: "red" });
const cssRule = editor.Css.setRule('.class1:hover', { color: 'red' });
cssRule.selectorsToString(); // ".class1:hover"
cssRule.selectorsToString({ skipState: true }); // ".class1"
```
Returns **[String][3]**
Returns **[String][3]**
### getDeclaration
@ -62,23 +58,19 @@ Get declaration block (without the at-rule statement)
#### Parameters
- `opts` **[Object][2]** Options (same as in `selectorsToString`) (optional, default `{}`)
* `opts` **[Object][2]** Options (same as in `selectorsToString`) (optional, default `{}`)
#### Examples
```javascript
const cssRule = editor.Css.setRule(
".class1",
{ color: "red" },
{
atRuleType: "media",
atRuleParams: "(min-width: 500px)",
}
);
cssRule.getDeclaration(); // ".class1{color:red;}"
const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {
atRuleType: 'media',
atRuleParams: '(min-width: 500px)'
});
cssRule.getDeclaration() // ".class1{color:red;}"
```
Returns **[String][3]**
Returns **[String][3]**
### getDevice
@ -91,7 +83,7 @@ const device = rule.getDevice();
console.log(device?.getName());
```
Returns **([Device] | null)**
Returns **([Device] | null)**
### getState
@ -104,7 +96,7 @@ const state = rule.getState();
console.log(state?.getLabel());
```
Returns **([State] | null)**
Returns **([State] | null)**
### getComponent
@ -117,7 +109,7 @@ const cmp = rule.getComponent();
console.log(cmp?.toHTML());
```
Returns **([Component] | null)**
Returns **([Component] | null)**
### toCSS
@ -125,25 +117,24 @@ Return the CSS string of the rule
#### Parameters
- `opts` **[Object][2]** Options (same as in `getDeclaration`) (optional, default `{}`)
* `opts` **[Object][2]** Options (same as in `getDeclaration`) (optional, default `{}`)
#### Examples
```javascript
const cssRule = editor.Css.setRule(
".class1",
{ color: "red" },
{
atRuleType: "media",
atRuleParams: "(min-width: 500px)",
}
);
cssRule.toCSS(); // "@media (min-width: 500px){.class1{color:red;}}"
const cssRule = editor.Css.setRule('.class1', { color: 'red' }, {
atRuleType: 'media',
atRuleParams: '(min-width: 500px)'
});
cssRule.toCSS() // "@media (min-width: 500px){.class1{color:red;}}"
```
Returns **[String][3]** CSS string
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

86
docs/api/editor.md

@ -90,10 +90,9 @@ Returns configuration object
### Parameters
* `prop` **[string][16]?** Property name
* `prop` **[string][16]?**
Returns **any** Returns the configuration object or
the value of the specified property
Returns **any** Returns the configuration object or the value of the specified property
## getHtml
@ -417,53 +416,64 @@ Returns **any** The return is defined by the command
## store
Store data to the current storage
Store data to the current storage.
This will reset the counter of changes (`editor.getDirtyCount()`).
### Parameters
* `clb` **[Function][22]** Callback function
* `options` **[Object][17]?** Storage options.
Returns **[Object][17]** Stored data
### Examples
```javascript
const storedData = await editor.store();
```
Returns **[Object][17]** Stored data.
## storeData
## load
Get the JSON data object, which could be stored and loaded back with `editor.loadData(json)`
Load data from the current storage.
### Parameters
* `options` **[Object][17]?** Storage options.
### Examples
```javascript
console.log(editor.storeData());
// { pages: [...], styles: [...], ... }
const data = await editor.load();
```
Returns **[Object][17]**
Returns **[Object][17]** Loaded data.
## load
## getProjectData
Load data from the current storage
Get the JSON project data, which could be stored and loaded back with `editor.loadProjectData(json)`
### Parameters
### Examples
* `clb` **[Function][22]** Callback function
```javascript
console.log(editor.getProjectData());
// { pages: [...], styles: [...], ... }
```
Returns **[Object][17]** Stored data
Returns **[Object][17]**
## loadData
## loadProjectData
Load data from the JSON data object
Load data from the JSON project
### Parameters
* `data` **[Object][17]** Data to load
* `data` **[Object][17]** Project to load
### Examples
```javascript
editor.loadData({ pages: [...], styles: [...], ... })
editor.loadProjectData({ pages: [...], styles: [...], ... })
```
Returns **[Object][17]** Loaded object
## getContainer
Returns container element. The one which was indicated as 'container'
@ -476,7 +486,11 @@ Returns **[HTMLElement][21]**
Return the count of changes made to the content and not yet stored.
This count resets at any `store()`
Returns **[number][23]**
Returns **[number][22]**
## clearDirtyCount
Reset the counter of changes.
## refresh
@ -488,7 +502,7 @@ refresh you'll get misleading position of tools
### Parameters
* `opts`
* `opts` **any?**
* `options` **[Object][17]?** Options
* `options.tools` **[Boolean][18]** Update the position of tools (eg. rich text editor, component highlighter, etc.) (optional, default `false`)
@ -514,7 +528,7 @@ editor.setCustomRte({
rte = new MyCustomRte(el, {}); // this depends on the Custom RTE API
...
return rte; // return the RTE instance
},
}
// Disable the editor, called for example when you unfocus the Text Component
disable: function(el, rte) {
@ -539,7 +553,7 @@ custom parser, pass `null` as the argument
### Parameters
* `parser` **([Function][22] | null)** Parser function
* `parser` **([Function][23] | null)** Parser function
### Examples
@ -600,7 +614,7 @@ Translate label
### Parameters
* `args` **...any**
* `args` **...[Array][19]\<any>**
* `key` **[String][16]** Label to translate
* `opts` **[Object][17]?** Options for the translation
@ -614,7 +628,7 @@ editor.t('msg');
// use params
editor.t('msg2', { params: { test: 'hello' } });
// custom local
editor.t('msg2', { params: { test: 'hello' }, l: 'it' });
editor.t('msg2', { params: { test: 'hello' } l: 'it' });
```
Returns **[String][16]**
@ -626,7 +640,7 @@ Attach event
### Parameters
* `event` **[string][16]** Event name
* `callback` **[Function][22]** Callback function
* `callback` **[Function][23]** Callback function
Returns **this**
@ -637,7 +651,7 @@ Attach event and detach it after the first run
### Parameters
* `event` **[string][16]** Event name
* `callback` **[Function][22]** Callback function
* `callback` **[Function][23]** Callback function
Returns **this**
@ -648,7 +662,7 @@ Detach event
### Parameters
* `event` **[string][16]** Event name
* `callback` **[Function][22]** Callback function
* `callback` **[Function][23]** Callback function
Returns **this**
@ -658,6 +672,8 @@ Trigger event
### Parameters
* `eventName` **[string][16]**
* `args` **...[Array][19]\<any>**
* `event` **[string][16]** Event to trigger
Returns **this**
@ -679,7 +695,7 @@ The callback will be executed immediately if the method is called on the already
### Parameters
* `clb` **[Function][22]** Callback to trigger
* `clb` **[Function][23]** Callback to trigger
### Examples
@ -704,7 +720,7 @@ Print safe HTML by using ES6 tagged template strings.
const unsafeStr = '<script>....</script>';
const safeStr = '<b>Hello</b>';
// Use `$${var}` to avoid escaping
const strHtml = editor.html`Escaped ${unsafeStr}, unescaped $${safeStr}`;
const strHtml = editor.html`Escaped ${unsafeStr} unescaped $${safeStr}`;
```
Returns **[String][16]**
@ -751,8 +767,8 @@ Returns **[String][16]**
[21]: https://developer.mozilla.org/docs/Web/HTML/Element
[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[22]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[23]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[23]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[24]: https://github.com/artf/grapesjs/issues/1936

44
docs/api/pages.md

@ -48,14 +48,26 @@ const pageManager = editor.Pages;
[Component]: component.html
## getAll
Get all pages
### Examples
```javascript
const arrayOfPages = pageManager.getAll();
```
Returns **[Array][9]<[Page]>**
## add
Add new page
### Parameters
* `props` **[Object][9]** Page properties
* `opts` **[Object][9]?** Options (optional, default `{}`)
* `props` **[Object][10]** Page properties
* `opts` **[Object][10]?** Options (optional, default `{}`)
### Examples
@ -75,8 +87,8 @@ Remove page
### Parameters
* `page` **([String][10] | [Page])** Page or page id
* `opts` (optional, default `{}`)
* `page` **([String][11] | [Page])** Page or page id
* `opts` **any** (optional, default `{}`)
### Examples
@ -95,7 +107,7 @@ Get page by id
### Parameters
* `id` **[String][10]** Page id
* `id` **[String][11]** Page id
### Examples
@ -117,18 +129,6 @@ const mainPage = pageManager.getMain();
Returns **[Page]**
## getAll
Get all pages
### Examples
```javascript
const arrayOfPages = pageManager.getAll();
```
Returns **[Array][11]<[Page]>**
## getAllWrappers
Get wrapper components (aka body) from all pages and frames.
@ -141,7 +141,7 @@ const wrappers = pageManager.getAllWrappers();
const allImages = wrappers.map(wrp => wrp.findType('image')).flat();
```
Returns **[Array][11]<[Component]>**
Returns **[Array][9]<[Component]>**
## select
@ -149,7 +149,7 @@ Change the selected page. This will switch the page rendered in canvas
### Parameters
* `page` **([String][10] | [Page])** Page or page id
* `page` **([String][11] | [Page])** Page or page id
* `opts` (optional, default `{}`)
### Examples
@ -191,8 +191,8 @@ Returns **[Page]**
[8]: #getselected
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

28
docs/api/selector_manager.md

@ -1,6 +1,6 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## SelectorManager
## Selectors
Selectors in GrapesJS are used in CSS Composer inside Rules and in Components as classes. To illustrate this concept let's take
a look at this code:
@ -115,7 +115,7 @@ Get the selector by its name/type
### Parameters
* `name` **[String][18]** Selector name or string identifier
* `type`
* `type` **[number][19]?**
### Examples
@ -134,7 +134,7 @@ Remove Selector.
### Parameters
* `selector` **([String][18] | [Selector])** Selector instance or Selector string identifier
* `opts`
* `opts` **any?**
### Examples
@ -172,7 +172,7 @@ Returns **[String][18]**
Get states
Returns **[Array][19]<[State]>**
Returns **[Array][20]<[State]>**
## setStates
@ -180,8 +180,8 @@ Set a new collection of states
### Parameters
* `states` **[Array][19]<[Object][17]>** Array of new states
* `opts`
* `states` **[Array][20]<[Object][17]>** Array of new states
* `opts` **any?**
### Examples
@ -192,7 +192,7 @@ const states = selectorManager.setStates([
]);
```
Returns **[Array][19]<[State]>**
Returns **[Array][20]<[State]>**
## getSelected
@ -205,7 +205,7 @@ const selected = selectorManager.getSelected();
console.log(selected.map(s => s.toString()))
```
Returns **[Array][19]<[Selector]>**
Returns **[Array][20]<[Selector]>**
## addSelected
@ -246,7 +246,7 @@ const targetsToStyle = selectorManager.getSelectedTargets();
console.log(targetsToStyle.map(target => target.getSelectorsString()))
```
Returns **[Array][19]<([Component] | [CssRule])>**
Returns **[Array][20]<([Component] | [CssRule])>**
## setComponentFirst
@ -256,13 +256,13 @@ of selectors (which would change styles on all components with those classes).
### Parameters
* `value` **[Boolean][20]**
* `value` **[Boolean][21]**
## getComponentFirst
Get the value of component-first option.
Returns **[Boolean][20]**
Returns **[Boolean][21]**
## getAll
@ -306,6 +306,8 @@ Returns **Collection<[Selector]>**
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

142
docs/api/storage_manager.md

@ -1,6 +1,6 @@
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## StorageManager
## Storage
You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object][1]
@ -19,7 +19,7 @@ Once the editor is instantiated you can use its API and listen to its events. Be
editor.on('storage:start', () => { ... });
// Use the API
const storageManager = editor.StorageManager;
const storageManager = editor.Storage;
storageManager.add(...);
```
@ -48,133 +48,128 @@ storageManager.add(...);
* [getCurrent][8]
* [getCurrentStorage][9]
* [setCurrent][10]
* [add][11]
* [get][12]
* [store][13]
* [load][14]
* [getStorageOptions][11]
* [add][12]
* [get][13]
* [store][14]
* [load][15]
## getConfig
Get configuration object
Returns **[Object][15]**
Returns **[Object][16]**
## isAutosave
Checks if autosave is enabled
Check if autosave is enabled.
Returns **[Boolean][16]**
Returns **[Boolean][17]**
## setAutosave
Set autosave value
Set autosave value.
### Parameters
* `v` **[Boolean][16]**
Returns **this**
* `value` **[Boolean][17]**
## getStepsBeforeSave
Returns number of steps required before trigger autosave
Returns number of steps required before trigger autosave.
Returns **[number][17]**
Returns **[Number][18]**
## setStepsBeforeSave
Set steps required before trigger autosave
Set steps required before trigger autosave.
### Parameters
* `v` **[number][17]**
Returns **this**
* `value` **[Number][18]**
## add
Add new storage
Add new storage.
### Parameters
* `id` **[string][18]** Storage ID
* `storage` **[Object][15]** Storage wrapper
* `type` **[String][19]** Storage type
* `storage` **[Object][16]** Storage definition
* `storage.load` **[Function][19]** Load method
* `storage.store` **[Function][19]** Store method
* `storage.load` **[Function][20]** Load method
* `storage.store` **[Function][20]** Store method
### Examples
```javascript
storageManager.add('local2', {
load: function(keys, clb, clbErr) {
var res = {};
for (var i = 0, len = keys.length; i < len; i++){
var v = localStorage.getItem(keys[i]);
if(v) res[keys[i]] = v;
}
clb(res); // might be called inside some async method
// In case of errors...
// clbErr('Went something wrong');
async load(storageOptions) {
// ...
},
async store(data, storageOptions) {
// ...
},
store: function(data, clb, clbErr) {
for(var key in data)
localStorage.setItem(key, data[key]);
clb(); // might be called inside some async method
}
});
```
Returns **this**
## get
Returns storage by id
Return storage by type.
### Parameters
* `id` **[string][18]** Storage ID
* `type` **[String][19]** Storage type
Returns **([Object][15] | null)**
Returns **([Object][16] | null)**
## getStorages
Returns all storages
Get all storages.
Returns **[Array][20]**
Returns **[Object][16]**
## getCurrent
Returns current storage type
Get current storage type.
Returns **[string][18]**
Returns **[String][19]**
## setCurrent
Set current storage type
Set current storage type.
### Parameters
* `id` **[string][18]** Storage ID
* `type` **[String][19]** Storage type
Returns **this**
## getStorageOptions
Get storage options by type.
### Parameters
* `type` **[String][19]** Storage type
Returns **[Object][16]**
## store
Store key-value resources in the current storage
Store data in the current storage.
### Parameters
* `data` **[Object][15]** Data in key-value format, eg. {item1: value1, item2: value2}
* `clb` **[Function][19]** Callback function
* `data` **[Object][16]** Project data.
* `options` **[Object][16]?** Storage options. (optional, default `{}`)
### Examples
```javascript
storageManager.store({item1: value1, item2: value2});
const data = editor.getProjectData();
await storageManager.store(data);
```
Returns **([Object][15] | null)**
Returns **[Object][16]** Stored data.
## load
@ -182,25 +177,16 @@ Load resource from the current storage by keys
### Parameters
* `keys` **([string][18] | [Array][20]<[string][18]>)** Keys to load
* `clb` **[Function][19]** Callback function
* `options` **[Object][16]?** Storage options. (optional, default `{}`)
### Examples
```javascript
storageManager.load(['item1', 'item2'], res => {
// res -> {item1: value1, item2: value2}
});
storageManager.load('item1', res => {
// res -> {item1: value1}
});
const data = await storageManager.load();
editor.loadProjectData(data);
```
## getCurrentStorage
Get current storage
Returns **Storage**
Returns **[Object][16]** Loaded data.
[1]: https://github.com/artf/grapesjs/blob/master/src/storage_manager/config/config.js
@ -222,22 +208,22 @@ Returns **Storage**
[10]: #setcurrent
[11]: #add
[11]: #getstorageoptions
[12]: #get
[12]: #add
[13]: #store
[13]: #get
[14]: #load
[14]: #store
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[15]: #load
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

14
docs/api/undo_manager.md

@ -211,18 +211,6 @@ stack.each(item => ...);
Returns **Collection**
## getStackGroup
Get grouped undo manager stack.
The difference between `getStack` is when you do multiple operations at a time,
like appending multiple components:
`editor.getWrapper().append(`<div>C1</div><div>C2</div>`);`
`getStack` will return a collection length of 2.
`getStackGroup` instead will group them as a single operation (the first
inserted component will be returned in the list) by returning an array length of 1.
Returns **[Array][17]**
## clear
Clear the stack
@ -266,5 +254,3 @@ Returns **this**
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

2
package.json

@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
"version": "0.19.1",
"version": "0.19.4",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",

30
src/abstract/Module.ts

@ -1,7 +1,7 @@
import { isElement, isUndefined } from 'underscore';
import { Collection, View } from '../common';
import EditorModel from '../editor/model/Editor';
import { createId, isDef } from '../utils/mixins';
import { createId, isDef, deepMerge } from '../utils/mixins';
export interface IModule<TConfig extends any = any>
extends IBaseModule<TConfig> {
@ -20,8 +20,9 @@ export interface IBaseModule<TConfig extends any> {
}
export interface ModuleConfig {
//name: string;
name?: string;
stylePrefix?: string;
appendTo?: string;
}
export interface IStorableModule extends IModule {
@ -39,6 +40,8 @@ export default abstract class Module<T extends ModuleConfig = ModuleConfig>
private _name: string;
cls: any[] = [];
events: any;
model?: any;
view?: any;
constructor(em: EditorModel, moduleName: string, defaults?: T) {
this._em = em;
@ -51,7 +54,9 @@ export default abstract class Module<T extends ModuleConfig = ModuleConfig>
if (!isUndefined(cfgParent) && !cfgParent) {
cfg._disable = 1;
}
this._config = {...defaults, ...cfg};
cfg.em = em;
this._config = deepMerge(defaults || {}, cfg) as T;
}
public get em() {
@ -63,8 +68,9 @@ export default abstract class Module<T extends ModuleConfig = ModuleConfig>
//abstract name: string;
isPrivate: boolean = false;
onLoad?(): void;
init(cfg: any) {}
init(cfg: T) {}
abstract destroy(): void;
abstract render(): HTMLElement;
postLoad(key: any): void {}
get name(): string {
@ -81,6 +87,20 @@ export default abstract class Module<T extends ModuleConfig = ModuleConfig>
}
postRender?(view: any): void;
/**
* Move the main DOM element of the module.
* To execute only post editor render (in postRender)
*/
__appendTo() {
const elTo = this.getConfig().appendTo;
if (elTo) {
const el = isElement(elTo) ? elTo : document.querySelector(elTo);
if (!el) return this.__logWarn('"appendTo" element not found');
el.appendChild(this.render());
}
}
}
export abstract class ItemManagerModule<
@ -103,6 +123,7 @@ export abstract class ItemManagerModule<
abstract storageKey: string;
abstract destroy(): void;
postLoad(key: any): void {}
// @ts-ignore
render() {}
getProjectData(data?: any) {
@ -213,6 +234,7 @@ export abstract class ItemManagerModule<
if (elTo) {
const el = isElement(elTo) ? elTo : document.querySelector(elTo);
if (!el) return this.__logWarn('"appendTo" element not found');
// @ts-ignore
el.appendChild(this.render());
}
}

1
src/asset_manager/index.js

@ -308,6 +308,7 @@ export default () => {
* Render assets
* @param {array} assets Assets to render, without the argument will render all global assets
* @returns {HTMLElement}
* @private
* @example
* // Render all assets
* assetManager.render();

6
src/asset_manager/model/Asset.js

@ -2,8 +2,10 @@ import { result } from 'underscore';
import { Model } from '../../common';
/**
* @property {String} type Asset type, eg. 'image'.
* @property {String} src Asset URL, eg. 'https://.../image.png'.
* @property {String} type Asset type, eg. `'image'`.
* @property {String} src Asset URL, eg. `'https://.../image.png'`.
*
* @module docsjs.Asset
*/
export default class Asset extends Model {
defaults() {

4
src/block_manager/index.js

@ -15,7 +15,7 @@
* editor.on('block:add', (block) => { ... });
*
* // Use the API
* const blockManager = editor.BlockManager;
* const blockManager = editor.Blocks;
* blockManager.add(...);
* ```
*
@ -42,7 +42,7 @@
* [Block]: block.html
* [Component]: component.html
*
* @module BlockManager
* @module Blocks
*/
import { isElement, isArray } from 'underscore';
import Module from '../abstract/moduleLegacy';

4
src/block_manager/model/Block.js

@ -12,6 +12,8 @@ import { isFunction } from 'underscore';
* @property {Boolean} [disable=false] Disable the block from being interacted
* @property {Function} [onClick] Custom behavior on click, eg. `(block, editor) => editor.getWrapper().append(block.get('content'))`
* @property {Object} [attributes={}] Block attributes to apply in the view element
*
* @module docsjs.Block
*/
export default class Block extends Model {
defaults() {
@ -55,7 +57,7 @@ export default class Block extends Model {
/**
* Get block content
* @returns {Object|String|Array<Object|String>} Component definition | HTML string
* @returns {Object|String|Array<Object|String>}
*/
getContent() {
return this.get('content');

2
src/commands/view/SwitchVisibility.js

@ -23,7 +23,7 @@ export default {
},
_onFramesChange(m, frames) {
frames.forEach(frame => this._upFrame(frame, 1));
frames.forEach(frame => frame.once('loaded', () => this._upFrame(frame, true)));
},
_upFrame(frame, active) {

2
src/css_composer/index.js

@ -24,7 +24,7 @@
*
* [CssRule]: css_rule.html
*
* @module CssComposer
* @module Css
*/
import { isArray, isString, isUndefined } from 'underscore';

10
src/dom_components/index.ts

@ -47,7 +47,8 @@
* * [addType](#addtype)
* * [getType](#gettype)
* * [getTypes](#gettypes)
* * [render](#render)
*
* * [Component]: component.html
*
* @module Components
*/
@ -310,7 +311,7 @@ export default class ComponentManager extends ItemManagerModule {
/**
* Returns root component inside the canvas. Something like `<body>` inside HTML page
* The wrapper doesn't differ from the original Component Model
* @return {Component} Root Component
* @return {[Component]} Root Component
* @example
* // Change background of the wrapper and set some attribute
* var wrapper = cmp.getWrapper();
@ -356,7 +357,7 @@ export default class ComponentManager extends ItemManagerModule {
/**
* Add new components to the wrapper's children. It's the same
* as 'cmp.getComponents().add(...)'
* @param {Object|Component|Array<Object>} component Component/s to add
* @param {Object|[Component]|Array<Object>} component Component/s to add
* @param {string} [component.tagName='div'] Tag name
* @param {string} [component.type=''] Type of the component. Available: ''(default), 'text', 'image'
* @param {boolean} [component.removable=true] If component is removable
@ -369,7 +370,7 @@ export default class ComponentManager extends ItemManagerModule {
* @param {Object} [component.style={}] Style object
* @param {Object} [component.attributes={}] Attribute object
* @param {Object} opt the options object to be used by the [Components.add]{@link getComponents} method
* @return {Component|Array<Component>} Component/s added
* @return {[Component]|Array<[Component]>} Component/s added
* @example
* // Example of a new component with some extra property
* var comp1 = cmp.addComponent({
@ -392,6 +393,7 @@ export default class ComponentManager extends ItemManagerModule {
* the all new components will be added automatically and property changes are all
* updated immediately
* @return {HTMLElement}
* @private
*/
render() {
return this.componentView?.render().el;

5
src/dom_components/model/Component.js

@ -73,6 +73,7 @@ export const keyUpdateInside = `${keyUpdate}-inside`;
* @property {Boolean} [layerable=true] Set to `false` if you need to hide the component inside Layers. Default: `true`
* @property {Boolean} [selectable=true] Allow component to be selected when clicked. Default: `true`
* @property {Boolean} [hoverable=true] Shows a highlight outline when hovering on the element if `true`. Default: `true`
* @property {Boolean} [locked=false] Disable the selection of the component and its children in the canvas. Default: `false`
* @property {Boolean} [void=false] This property is used by the HTML exporter as void elements don't have closing tags, eg. `<br/>`, `<hr/>`, etc. Default: `false`
* @property {Object} [style={}] Component default style, eg. `{ width: '100px', height: '100px', 'background-color': 'red' }`
* @property {String} [styles=''] Component related styles, eg. `.my-component-class { color: red }`
@ -89,6 +90,8 @@ export const keyUpdateInside = `${keyUpdate}-inside`;
* Eg. `toolbar: [ { attributes: {class: 'fa fa-arrows'}, command: 'tlb-move' }, ... ]`.
* By default, when `toolbar` property is falsy the editor will add automatically commands `core:component-exit` (select parent component, added if there is one), `tlb-move` (added if `draggable`) , `tlb-clone` (added if `copyable`), `tlb-delete` (added if `removable`).
* @property {Collection<Component>} [components=null] Children components. Default: `null`
*
* @module docsjs.Component
*/
export default class Component extends StyleableModel {
/**
@ -202,6 +205,7 @@ export default class Component extends StyleableModel {
__onChange(m, opts) {
const changed = this.changedAttributes();
keys(changed).forEach(prop => this.emitUpdate(prop));
['status', 'open', 'toolbar', 'traits'].forEach(name => delete changed[name]);
// Propagate component prop changes
if (!isEmptyObj(changed)) {
@ -1963,6 +1967,7 @@ Component.prototype.defaults = {
layerable: true,
selectable: true,
hoverable: true,
locked: false,
void: false,
state: '', // Indicates if the component is in some CSS state like ':hover', ':active', etc.
status: '', // State, eg. 'selected'

40
src/dom_components/view/ComponentView.js

@ -7,10 +7,6 @@ import { replaceWith } from 'utils/dom';
import { setViewEl } from 'utils/mixins';
export default class ComponentView extends Backbone.View {
static getEvents() {
return result(this.prototype, 'events');
}
className() {
return this.getClasses();
}
@ -36,7 +32,7 @@ export default class ComponentView extends Backbone.View {
this.listenTo(model, 'change:style', this.updateStyle);
this.listenTo(model, 'change:attributes', this.renderAttributes);
this.listenTo(model, 'change:highlightable', this.updateHighlight);
this.listenTo(model, 'change:status', this.updateStatus);
this.listenTo(model, 'change:status change:locked', this.updateStatus);
this.listenTo(model, 'change:script rerender', this.reset);
this.listenTo(model, 'change:content', this.updateContent);
this.listenTo(model, 'change', this.handleChange);
@ -181,41 +177,42 @@ export default class ComponentView extends Backbone.View {
* @private
* */
updateStatus(opts = {}) {
const { em } = this;
const { em, el, ppfx, model } = this;
const { extHl } = em ? em.get('Canvas').getConfig() : {};
const el = this.el;
const status = this.model.get('status');
const ppfx = this.ppfx;
const status = model.get('status');
const selectedCls = `${ppfx}selected`;
const selectedParentCls = `${selectedCls}-parent`;
const freezedCls = `${ppfx}freezed`;
const hoveredCls = `${ppfx}hovered`;
const toRemove = [selectedCls, selectedParentCls, freezedCls, hoveredCls];
const noPointerCls = `${ppfx}no-pointer`;
const toRemove = [selectedCls, selectedParentCls, freezedCls, hoveredCls, noPointerCls];
const selCls = extHl && !opts.noExtHl ? '' : selectedCls;
this.$el.removeClass(toRemove.join(' '));
var actualCls = el.getAttribute('class') || '';
var cls = '';
const actualCls = el.getAttribute('class') || '';
const cls = [actualCls];
switch (status) {
case 'selected':
cls = `${actualCls} ${selCls}`;
cls.push(selCls);
break;
case 'selected-parent':
cls = `${actualCls} ${selectedParentCls}`;
cls.push(selectedParentCls);
break;
case 'freezed':
cls = `${actualCls} ${freezedCls}`;
cls.push(freezedCls);
break;
case 'freezed-selected':
cls = `${actualCls} ${freezedCls} ${selCls}`;
cls.push(freezedCls, selCls);
break;
case 'hovered':
cls = !opts.avoidHover ? `${actualCls} ${hoveredCls}` : '';
!opts.avoidHover && cls.push(hoveredCls);
break;
}
cls = cls.trim();
cls && el.setAttribute('class', cls);
model.get('locked') && cls.push(noPointerCls);
const clsStr = cls.filter(Boolean).join(' ');
clsStr && el.setAttribute('class', clsStr);
}
/**
@ -514,3 +511,8 @@ export default class ComponentView extends Backbone.View {
onRender() {}
}
// Due to the Backbone extend mechanism, static methods are not properly extended
ComponentView.getEvents = function () {
return result(this.prototype, 'events');
};

36
src/domain_abstract/ui/InputColor.js

@ -101,9 +101,20 @@ export default class InputColor extends Input {
var elToAppend = em && em.config ? em.config.el : '';
var colorPickerConfig = (em && em.getConfig && em.getConfig().colorPicker) || {};
let changed = 0;
let changed = false;
let movedColor = '';
let previousColor;
this.$el.find('[data-colorp-c]').append(colorEl);
const handleChange = (value, complete = true) => {
if (onChange) {
onChange(value, !complete);
} else {
complete && model.setValueFromInput(0, false); // for UndoManager
model.setValueFromInput(value, complete);
}
};
colorEl.spectrum({
color: model.getValue() || false,
containerClassName: `${ppfx}one-bg ${ppfx}two-color`,
@ -121,40 +132,39 @@ export default class InputColor extends Input {
move(color) {
const cl = getColor(color);
movedColor = cl;
cpStyle.backgroundColor = cl;
onChange ? onChange(cl, true) : model.setValueFromInput(cl, 0);
handleChange(cl, false);
},
change(color) {
changed = 1;
changed = true;
const cl = getColor(color);
cpStyle.backgroundColor = cl;
if (onChange) {
onChange(cl);
} else {
model.setValueFromInput(0, 0); // for UndoManager
model.setValueFromInput(cl);
}
handleChange(cl);
self.noneColor = 0;
},
show(color) {
changed = 0;
changed = false;
movedColor = '';
previousColor = onChange ? model.getValue({ noDefault: true }) : getColor(color);
},
hide(color) {
hide() {
if (!changed && (previousColor || onChange)) {
if (self.noneColor) {
previousColor = '';
}
cpStyle.backgroundColor = previousColor;
colorEl.spectrum('set', previousColor);
onChange ? onChange(previousColor, true) : model.setValueFromInput(previousColor, 0);
handleChange(previousColor, false);
}
},
});
if (em && em.on) {
this.listenTo(em, 'component:selected', () => {
changed = 1;
movedColor && handleChange(movedColor);
changed = true;
movedColor = '';
colorEl.spectrum('hide');
});
}

2
src/editor/view/EditorView.ts

@ -28,7 +28,7 @@ export default class EditorView extends View<EditorModel> {
const { config, modules } = model;
const pfx = config.stylePrefix;
const contEl = $(config.el || `body ${config.container}`);
appendStyles(config.cssIcons, { unique: 1, prepand: 1 });
appendStyles(config.cssIcons, { unique: true, prepand: true });
$el.empty();
if (config.width) contEl.css('width', config.width);

1
src/modal_dialog/config/config.js

@ -5,6 +5,7 @@ export default {
content: '',
// Close modal on interact with backdrop
backdrop: true,
// Avoid rendering the default modal.

12
src/navigator/config/config.js → src/navigator/config/config.ts

@ -6,23 +6,23 @@ export default {
appendTo: '',
// Enable/Disable globally the possibility to sort layers
sortable: 1,
sortable: true,
// Enable/Disable globally the possibility to hide layers
hidable: 1,
hidable: true,
// Hide textnodes
hideTextnode: 1,
hideTextnode: true,
// Indicate a query string of the element to be selected as the root of layers.
// By default the root is the wrapper
root: '',
// Indicates if the wrapper is visible in layers
showWrapper: 1,
showWrapper: true,
// Show hovered components in canvas
showHover: 1,
showHover: true,
// Scroll to selected component in Canvas when it's selected in Layers
// true, false or `scrollIntoView`-like options,
@ -34,7 +34,7 @@ export default {
scrollLayers: { behavior: 'auto', block: 'nearest' },
// Highlight when a layer component is hovered
highlightHover: 1,
highlightHover: true,
/**
* WARNING: Experimental option

108
src/navigator/index.js

@ -1,108 +0,0 @@
import { isElement } from 'underscore';
import defaults from './config/config';
import View from './view/ItemView';
export default () => {
let em;
let layers;
let config = {};
return {
name: 'LayerManager',
init(opts = {}) {
config = { ...defaults, ...opts };
config.stylePrefix = opts.pStylePrefix;
em = config.em;
return this;
},
getConfig() {
return config;
},
onLoad() {
em && em.on('component:selected', this.componentChanged);
this.componentChanged();
},
postRender() {
const elTo = config.appendTo;
const root = config.root;
root && this.setRoot(root);
if (elTo) {
const el = isElement(elTo) ? elTo : document.querySelector(elTo);
el.appendChild(this.render());
}
},
/**
* Set new root for layers
* @param {HTMLElement|Component|String} el Component to be set as the root
* @return {self}
*/
setRoot(el) {
layers && layers.setRoot(el);
return this;
},
/**
* Get the root of layers
* @return {Component}
*/
getRoot() {
return layers && layers.model;
},
/**
* Return the view of layers
* @return {View}
*/
getAll() {
return layers;
},
/**
* Triggered when the selected component is changed
* @private
*/
componentChanged(selected, opts = {}) {
if (opts.fromLayers) return;
const opened = em.get('opened');
const model = em.getSelected();
const scroll = config.scrollLayers;
let parent = model && model.collection ? model.collection.parent : null;
for (let cid in opened) opened[cid].set('open', 0);
while (parent) {
parent.set('open', 1);
opened[parent.cid] = parent;
parent = parent.collection ? parent.collection.parent : null;
}
if (model && scroll) {
const el = model.viewLayer && model.viewLayer.el;
el && el.scrollIntoView(scroll);
}
},
render() {
const ItemView = View.extend(config.extend);
layers && layers.remove();
layers = new ItemView({
ItemView,
level: 0,
config,
opened: config.opened || {},
model: em.get('DomComponents').getWrapper(),
});
return layers.render().el;
},
destroy() {
layers && layers.remove();
[em, layers, config].forEach(i => (i = {}));
},
};
};

285
src/navigator/index.ts

@ -0,0 +1,285 @@
import { isString, bindAll } from 'underscore';
import { Model } from '../abstract';
import Module from '../abstract/Module';
import Component from '../dom_components/model/Component';
import EditorModel from '../editor/model/Editor';
import { hasWin, isComponent, isDef } from '../utils/mixins';
import defaults from './config/config';
import View from './view/ItemView';
interface LayerData {
name: string,
open: boolean,
selected: boolean,
hovered: boolean,
visible: boolean,
locked: boolean,
components: Component[],
}
export const evAll = 'layer';
export const evPfx = `${evAll}:`;
export const evRoot = `${evPfx}root`;
export const evComponent = `${evPfx}component`;
const events = {
all: evAll,
root: evRoot,
component: evComponent,
};
const styleOpts = { mediaText: '' };
const propsToListen = ['open', 'status', 'locked', 'custom-name', 'components', 'classes']
.map(p => `component:update:${p}`).join(' ');
const isStyleHidden = (style: any = {}) => {
return (style.display || '').trim().indexOf('none') === 0;
};
export default class LayerManager extends Module<typeof defaults> {
model!: Model;
view?: View;
events = events;
constructor(em: EditorModel) {
super(em, 'LayerManager', defaults);
bindAll(this, 'componentChanged', '__onRootChange', '__onComponent');
this.model = new Model(this, { opened: {} });
// @ts-ignore
this.config.stylePrefix = this.config.pStylePrefix;
return this;
}
onLoad() {
const { em, config, model } = this;
model.listenTo(em, 'component:selected', this.componentChanged);
model.on('change:root', this.__onRootChange);
model.listenTo(em, propsToListen, this.__onComponent);
this.componentChanged();
model.listenToOnce(em, 'load', () => {
this.setRoot(config.root);
this.__appendTo();
});
}
/**
* Set new root for layers
* @param {Component|string} component Component to be set as the root
* @return {Component}
*/
setRoot(component: Component | string): Component {
const wrapper: Component = this.em.getWrapper();
let root = isComponent(component) ? component as Component : wrapper;
if (component && isString(component) && hasWin()) {
root = wrapper.find(component)[0] || wrapper;
}
this.model.set('root', root);
return root;
}
/**
* Get the root of layers
* @return {Component}
*/
getRoot(): Component {
return this.model.get('root');// || this.em.getWrapper();
}
getLayerData(component: any): LayerData {
const status = component.get('status');
return {
name: component.getName(),
open: this.isOpen(component),
selected: status === 'selected',
hovered: status === 'hovered', // || this.em.getHovered() === component,
visible: this.isVisible(component),
locked: this.isLocked(component),
components: this.getComponents(component),
}
}
setLayerData(component: any, data: Partial<Omit<LayerData, 'components'>>, opts = {}) {
const { em, config } = this;
const { open, selected, hovered, visible, locked, name } = data;
const cmpOpts = { fromLayers: true, ...opts };
if (isDef(open)) {
this.setOpen(component, open!);
}
if (isDef(selected)) {
if (selected) {
em.setSelected(component, cmpOpts);
const scroll = config.scrollCanvas;
scroll && component.views.forEach((view: any) => view.scrollIntoView(scroll));
} else {
em.removeSelected(component, cmpOpts);
}
}
if (isDef(hovered) && config.showHover) {
hovered ? em.setHovered(component, cmpOpts) : em.setHovered(null, cmpOpts);
}
if (isDef(visible)) {
visible !== this.isVisible(component) && this.setVisible(component, visible!);
}
if (isDef(locked)) {
this.setLocked(component, locked!);
}
if (isDef(name)) {
this.setName(component, name!);
}
}
getComponents(component: Component): Component[] {
return component.components().filter((cmp: any) => this.__isLayerable(cmp));
}
setOpen(component: Component, value: boolean) {
component.set('open', value);
}
isOpen(component: Component): boolean {
return !!component.get('open');
}
/**
* Update component visibility
* */
setVisible(component: Component, value: boolean) {
const prevDspKey = '__prev-display';
const style: any = component.getStyle(styleOpts);
const { display } = style;
if (value) {
const prevDisplay = component.get(prevDspKey);
delete style.display;
if (prevDisplay) {
style.display = prevDisplay;
component.unset(prevDspKey);
}
} else {
display && component.set(prevDspKey, display);
style.display = 'none';
}
component.setStyle(style, styleOpts);
this.updateLayer(component);
this.em.trigger('component:toggled'); // Updates Style Manager #2938
}
/**
* Check if the component is visible
* */
isVisible(component: Component): boolean {
return !isStyleHidden(component.getStyle(styleOpts));
}
/**
* Update component locked value
* */
setLocked(component: Component, value: boolean) {
component.set('locked', value);
}
/**
* Check if the component is locked
* */
isLocked(component: Component): boolean {
return component.get('locked');
}
/**
* Update component name
* */
setName(component: Component, value: string) {
component.set('custom-name', value);
}
/**
* Return the view of layers
* @return {View}
* @private
*/
getAll() {
return this.view;
}
/**
* Triggered when the selected component is changed
* @private
*/
componentChanged(sel?: Component, opts = {}) {
// @ts-ignore
if (opts.fromLayers) return;
const { em, config } = this;
const { scrollLayers } = config;
const opened = this.model.get('opened');
const selected = em.getSelected();
let parent = selected?.parent();
for (let cid in opened) {
opened[cid].set('open', false);
delete opened[cid];
}
while (parent) {
parent.set('open', true);
opened[parent.cid] = parent;
parent = parent.parent();
}
if (selected && scrollLayers) {
// @ts-ignore
const el = selected.viewLayer?.el;
el?.scrollIntoView(scrollLayers);
}
}
render() {
const { config, model } = this;
const ItemView = View.extend(config.extend);
this.view = new ItemView({
el: this.view?.el,
ItemView,
level: 0,
config,
opened: model.get('opened'),
model: this.getRoot(),
module: this,
});
return this.view?.render().el as HTMLElement;
}
destroy() {
this.view?.remove();
}
__onRootChange() {
const root = this.getRoot();
this.view?.setRoot(root);
this.em.trigger(evRoot, root);
}
__onComponent(component: Component) {
this.updateLayer(component);
}
__isLayerable(cmp: Component): boolean {
const tag = cmp.get('tagName');
const hideText = this.config.hideTextnode;
const isValid = !hideText || (!cmp.is('textnode') && tag !== 'br');
return isValid && cmp.get('layerable');
}
updateLayer(component: Component, opts?: any) {
this.em.trigger(evComponent, component, opts);
}
};

329
src/navigator/view/ItemView.js → src/navigator/view/ItemView.ts

@ -1,15 +1,24 @@
import { isUndefined, isString, bindAll } from 'underscore';
import { isString, bindAll } from 'underscore';
import { View } from '../../common';
import { getModel, isEscKey, isEnterKey } from '../../utils/mixins';
import ComponentView from '../../dom_components/view/ComponentView';
import { eventDrag } from '../../dom_components/model/Component';
import Component, { eventDrag } from '../../dom_components/model/Component';
import ItemsView from './ItemsView';
import EditorModel from '../../editor/model/Editor';
import LayerManager from '../index';
export type ItemViewProps = Backbone.ViewOptions & {
ItemView: ItemView,
level: number,
config: any,
opened: {},
model: Component,
module: LayerManager,
sorter: any,
parentView: ItemView,
};
const inputProp = 'contentEditable';
const styleOpts = { mediaText: '' };
const isStyleHidden = (style = {}) => {
return (style.display || '').trim().indexOf('none') === 0;
};
let ItemsView;
export default class ItemView extends View {
events() {
@ -27,16 +36,16 @@ export default class ItemView extends View {
};
}
template(model) {
const { pfx, ppfx, config, clsNoEdit } = this;
template(model: Component) {
const { pfx, ppfx, config, clsNoEdit, module, opt } = this;
const { hidable } = config;
const count = this.countChildren(model);
const count = module.getComponents(model).length;
const addClass = !count ? this.clsNoChild : '';
const clsTitle = `${this.clsTitle} ${addClass}`;
const clsTitleC = `${this.clsTitleC} ${ppfx}one-bg`;
const clsCaret = `${this.clsCaret} fa fa-chevron-right`;
const clsInput = `${this.inputNameCls} ${clsNoEdit} ${ppfx}no-app`;
const level = this.level + 1;
const level = opt.level + 1;
const gut = `${30 + level * 10}px`;
const name = model.getName();
const icon = model.getIcon();
@ -45,7 +54,9 @@ export default class ItemView extends View {
return `
${
hidable
? `<i class="${pfx}layer-vis fa fa-eye ${this.isVisible() ? '' : 'fa-eye-slash'}" data-toggle-visible></i>`
? `<i class="${pfx}layer-vis fa fa-eye ${
module.isVisible(model) ? '' : 'fa-eye-slash'
}" data-toggle-visible></i>`
: ''
}
<div class="${clsTitleC}">
@ -64,24 +75,57 @@ export default class ItemView extends View {
<div class="${this.clsChildren}"></div>`;
}
initialize(o = {}) {
public get em(): EditorModel {
return this.module.em;
}
public get ppfx(): string {
return this.em.getConfig().stylePrefix;
}
public get pfx(): string {
return this.config.stylePrefix;
}
opt: any;
module: any;
config: any;
sorter: any;
// @ts-ignore
model!: Component;
parentView: ItemView;
items?: ItemsView;
inputNameCls: string;
clsTitleC: string;
clsTitle: string;
clsCaret: string;
clsCount: string;
clsMove: string;
clsChildren: string;
clsNoChild: string;
clsEdit: string;
clsNoEdit: string;
_rendered?: boolean;
eyeEl?: JQuery<HTMLElement>;
caret?: JQuery<HTMLElement>;
inputName?: HTMLElement;
cnt?: HTMLElement;
constructor(opt: ItemViewProps) {
super(opt);
bindAll(this, '__render');
this.opt = o;
this.level = o.level;
const config = o.config || {};
this.opt = opt;
this.module = opt.module;
const config = opt.config || {};
const { onInit } = config;
this.config = config;
this.em = o.config.em;
this.ppfx = this.em.get('Config').stylePrefix;
this.sorter = o.sorter || '';
this.pfx = this.config.stylePrefix;
this.parentView = o.parentView;
this.sorter = opt.sorter || '';
this.parentView = opt.parentView;
const pfx = this.pfx;
const ppfx = this.ppfx;
const model = this.model;
const components = model.get('components');
const type = model.get('type') || 'default';
model.set('open', false);
this.listenTo(components, 'remove add reset', this.checkChildren);
[
['change:status', this.updateStatus],
@ -90,7 +134,8 @@ export default class ItemView extends View {
['change:style:display', this.updateVisibility],
['rerender:layer', this.render],
['change:name change:custom-name', this.updateName],
].forEach(item => this.listenTo(model, item[0], item[1]));
// @ts-ignore
].forEach((item) => this.listenTo(model, item[0], item[1]));
this.className = `${pfx}layer ${pfx}layer__t-${type} no-select ${ppfx}two-color`;
this.inputNameCls = `${ppfx}layer-name`;
this.clsTitleC = `${pfx}layer-title-c`;
@ -104,6 +149,7 @@ export default class ItemView extends View {
this.clsNoEdit = `${this.inputNameCls}--no-edit`;
this.$el.data('model', model);
this.$el.data('collection', components);
// @ts-ignore
model.viewLayer = this;
onInit.bind(this)({
component: model,
@ -125,14 +171,12 @@ export default class ItemView extends View {
}
updateVisibility() {
const pfx = this.pfx;
const model = this.model;
const { pfx, model, module } = this;
const hClass = `${pfx}layer-hidden`;
const hideIcon = 'fa-eye-slash';
const hidden = isStyleHidden(model.getStyle(styleOpts));
const hidden = !module.isVisible(model);
const method = hidden ? 'addClass' : 'removeClass';
this.$el[method](hClass);
this.getVisibilityEl()[method](hideIcon);
this.getVisibilityEl()[method]('fa-eye-slash');
}
/**
@ -141,46 +185,27 @@ export default class ItemView extends View {
*
* @return void
* */
toggleVisibility(e) {
e && e.stopPropagation();
const { model, em } = this;
const prevDspKey = '__prev-display';
const prevDisplay = model.get(prevDspKey);
const style = model.getStyle(styleOpts);
const { display } = style;
const hidden = isStyleHidden(style);
if (hidden) {
delete style.display;
if (prevDisplay) {
style.display = prevDisplay;
model.unset(prevDspKey);
}
} else {
display && model.set(prevDspKey, display);
style.display = 'none';
}
model.setStyle(style, styleOpts);
em && em.trigger('component:toggled'); // Updates Style Manager #2938
toggleVisibility(ev?: MouseEvent) {
ev?.stopPropagation();
const { module, model } = this;
module.setVisible(model, !module.isVisible(model));
}
/**
* Handle the edit of the component name
*/
handleEdit(e) {
e && e.stopPropagation();
handleEdit(ev?: MouseEvent) {
ev?.stopPropagation();
const { em, $el, clsNoEdit, clsEdit } = this;
const inputEl = this.getInputName();
inputEl[inputProp] = true;
inputEl[inputProp] = 'true';
inputEl.focus();
document.execCommand('selectAll', false, null);
em && em.setEditing(1);
document.execCommand('selectAll', false);
em.setEditing(true);
$el.find(`.${this.inputNameCls}`).removeClass(clsNoEdit).addClass(clsEdit);
}
handleEditKey(ev) {
handleEditKey(ev: KeyboardEvent) {
ev.stopPropagation();
(isEscKey(ev) || isEnterKey(ev)) && this.handleEditEnd(ev);
}
@ -188,19 +213,19 @@ export default class ItemView extends View {
/**
* Handle with the end of editing of the component name
*/
handleEditEnd(e) {
e && e.stopPropagation();
handleEditEnd(ev?: KeyboardEvent) {
ev?.stopPropagation();
const { em, $el, clsNoEdit, clsEdit } = this;
const inputEl = this.getInputName();
const name = inputEl.textContent;
const name = inputEl.textContent!;
inputEl.scrollLeft = 0;
inputEl[inputProp] = false;
inputEl[inputProp] = 'false';
this.setName(name, { component: this.model, propName: 'custom-name' });
em && em.setEditing(0);
em.setEditing(false);
$el.find(`.${this.inputNameCls}`).addClass(clsNoEdit).removeClass(clsEdit);
}
setName(name, { propName }) {
setName(name: string, { propName }: { propName: string, component?: Component }) {
this.model.set(propName, name);
}
@ -210,7 +235,7 @@ export default class ItemView extends View {
*/
getInputName() {
if (!this.inputName) {
this.inputName = this.el.querySelector(`.${this.inputNameCls}`);
this.inputName = this.el.querySelector(`.${this.inputNameCls}`)!;
}
return this.inputName;
}
@ -221,18 +246,17 @@ export default class ItemView extends View {
* @return void
* */
updateOpening() {
var opened = this.opt.opened || {};
var model = this.model;
const chvDown = 'fa-chevron-down';
if (model.get('open')) {
this.$el.addClass('open');
this.getCaret().addClass(chvDown);
opened[model.cid] = model;
const { $el, model } = this;
const clsOpen = 'open';
const clsChvDown = 'fa-chevron-down';
const caret = this.getCaret();
if (this.module.isOpen(model)) {
$el.addClass(clsOpen);
caret.addClass(clsChvDown);
} else {
this.$el.removeClass('open');
this.getCaret().removeClass(chvDown);
delete opened[model.cid];
$el.removeClass(clsOpen);
caret.removeClass(clsChvDown);
}
}
@ -242,83 +266,62 @@ export default class ItemView extends View {
*
* @return void
* */
toggleOpening(e) {
const { model } = this;
e.stopImmediatePropagation();
toggleOpening(ev?: MouseEvent) {
const { model, module } = this;
ev?.stopImmediatePropagation();
if (!model.get('components').length) return;
model.set('open', !model.get('open'));
module.setOpen(model, !module.isOpen(model));
}
/**
* Handle component selection
*/
handleSelect(e) {
e.stopPropagation();
const { em, config, model } = this;
if (em) {
em.setSelected(model, { fromLayers: 1, event: e });
const scroll = config.scrollCanvas;
scroll && model.views.forEach(view => view.scrollIntoView(scroll));
}
handleSelect(event?: MouseEvent) {
event?.stopPropagation();
const { module, model } = this;
module.setLayerData(model, { selected: true }, { event });
}
/**
* Handle component selection
*/
handleHover(e) {
e.stopPropagation();
const { em, config, model } = this;
em && config.showHover && em.setHovered(model, { fromLayers: 1 });
handleHover(ev?: MouseEvent) {
ev?.stopPropagation();
const { module, model } = this;
module.setLayerData(model, { hovered: true });
}
handleHoverOut(ev) {
ev.stopPropagation();
const { em, config } = this;
em && config.showHover && em.setHovered(0, { fromLayers: 1 });
handleHoverOut(ev?: MouseEvent) {
ev?.stopPropagation();
const { module, model } = this;
module.setLayerData(model, { hovered: false });
}
/**
* Delegate to sorter
* @param Event
* */
startSort(e) {
e.stopPropagation();
startSort(ev: MouseEvent) {
ev.stopPropagation();
const { em, sorter } = this;
// Right or middel click
if (e.button && e.button !== 0) return;
if (ev.button && ev.button !== 0) return;
if (sorter) {
sorter.onStart = data => em.trigger(`${eventDrag}:start`, data);
sorter.onMoveClb = data => em.trigger(eventDrag, data);
sorter.startSort(e.target);
sorter.onStart = (data: any) => em.trigger(`${eventDrag}:start`, data);
sorter.onMoveClb = (data: any) => em.trigger(eventDrag, data);
sorter.startSort(ev.target);
}
}
/**
* Freeze item
* @return void
* */
freeze() {
this.$el.addClass(this.pfx + 'opac50');
this.model.set('open', 0);
}
/**
* Unfreeze item
* @return void
* */
unfreeze() {
this.$el.removeClass(this.pfx + 'opac50');
}
/**
* Update item on status change
* @param Event
* */
updateStatus(e) {
updateStatus() {
// @ts-ignore
ComponentView.prototype.updateStatus.apply(this, [
{
avoidHover: !this.config.highlightHover,
@ -327,65 +330,38 @@ export default class ItemView extends View {
]);
}
/**
* Check if component is visible
*
* @return boolean
* */
isVisible() {
return !isStyleHidden(this.model.getStyle());
}
/**
* Update item aspect after children changes
*
* @return void
* */
checkChildren() {
const { model, clsNoChild } = this;
const count = this.countChildren(model);
const title = this.$el.children(`.${this.clsTitleC}`).children(`.${this.clsTitle}`);
const { model, clsNoChild, $el, module } = this;
const count = module.getComponents(model).length;
const title = $el.children(`.${this.clsTitleC}`).children(`.${this.clsTitle}`);
let { cnt } = this;
if (!cnt) {
cnt = this.$el.children('[data-count]').get(0);
cnt = $el.children('[data-count]').get(0);
this.cnt = cnt;
}
title[count ? 'removeClass' : 'addClass'](clsNoChild);
if (cnt) cnt.innerHTML = count || '';
!count && model.set('open', 0);
}
/**
* Count children inside model
* @param {Object} model
* @return {number}
* @private
*/
countChildren(model) {
var count = 0;
model.get('components').each(function (m) {
var isCountable = this.opt.isCountable;
var hide = this.config.hideTextnode;
if (isCountable && !isCountable(m, hide)) return;
count++;
}, this);
return count;
!count && module.setOpen(model, false);
}
getCaret() {
if (!this.caret || !this.caret.length) {
const pfx = this.pfx;
this.caret = this.$el.children(`.${this.clsTitleC}`).find(`.${this.clsCaret}`);
}
return this.caret;
}
setRoot(el) {
setRoot(el: Component | string) {
el = isString(el) ? this.em.getWrapper().find(el)[0] : el;
const model = getModel(el);
const model = getModel(el, 0);
if (!model) return;
this.stopListening();
this.model = model;
@ -400,60 +376,55 @@ export default class ItemView extends View {
}
__clearItems() {
const { items } = this;
items && items.remove();
this.items?.remove();
}
remove() {
View.prototype.remove.apply(this, arguments);
remove(...args: []) {
View.prototype.remove.apply(this, args);
this.__clearItems();
return this;
}
render() {
const { model, config, pfx, ppfx, opt } = this;
const { model, config, pfx, ppfx, opt, sorter } = this;
this.__clearItems();
const { isCountable } = opt;
const hidden = isCountable && !isCountable(model, config.hideTextnode);
const vis = this.isVisible();
const { opened, module, ItemView } = opt;
const hidden = !module.__isLayerable(model);
const el = this.$el.empty();
const level = this.level + 1;
this.inputName = 0;
if (isUndefined(ItemsView)) {
ItemsView = require('./ItemsView').default;
}
const level = opt.level + 1;
delete this.inputName;
this.items = new ItemsView({
ItemView: opt.ItemView,
ItemView,
collection: model.get('components'),
config: this.config,
sorter: this.sorter,
opened: this.opt.opened,
config,
sorter,
opened,
parentView: this,
parent: model,
level,
module,
});
const children = this.items.render().$el;
if (!this.config.showWrapper && level === 1) {
if (!config.showWrapper && level === 1) {
el.append(children);
} else {
el.html(this.template(model));
el.find(`.${this.clsChildren}`).append(children);
}
if (!model.get('draggable') || !this.config.sortable) {
if (!model.get('draggable') || !config.sortable) {
el.children(`.${this.clsMove}`).remove();
}
!vis && (this.className += ` ${pfx}hide`);
!module.isVisible(model) && (this.className += ` ${pfx}hide`);
hidden && (this.className += ` ${ppfx}hidden`);
el.attr('class', this.className);
this.updateOpening();
el.attr('class', this.className!);
this.updateStatus();
this.updateOpening();
this.updateVisibility();
this.__render();
this._rendered = 1;
this._rendered = true;
return this;
}

84
src/navigator/view/ItemsView.js → src/navigator/view/ItemsView.ts

@ -1,21 +1,22 @@
import { View } from '../../common';
import { eventDrag } from '../../dom_components/model/Component';
import Component, { eventDrag } from '../../dom_components/model/Component';
import ItemView from './ItemView';
export default class ItemsView extends View {
initialize(o = {}) {
items: ItemView[];
opt: any;
config: any;
parentView: ItemView;
constructor(opt: any = {}) {
super(opt);
this.items = [];
this.opt = o;
const config = o.config || {};
this.level = o.level;
this.opt = opt;
const config = opt.config || {};
this.config = config;
this.preview = o.preview;
this.ppfx = config.pStylePrefix || '';
this.pfx = config.stylePrefix || '';
this.parent = o.parent;
this.parentView = o.parentView;
const pfx = this.pfx;
const ppfx = this.ppfx;
const parent = this.parent;
this.parentView = opt.parentView;
const pfx = config.stylePrefix || '';
const ppfx = config.pStylePrefix || '';
const coll = this.collection;
this.listenTo(coll, 'add', this.addTo);
this.listenTo(coll, 'reset resetNavigator', this.render);
@ -30,7 +31,7 @@ export default class ItemsView extends View {
containerSel: `.${this.className}`,
itemSel: `.${pfx}layer`,
ignoreViewChildren: 1,
onEndMove(created, sorter, data) {
onEndMove(created: any, sorter: any, data: any) {
const srcModel = sorter.getSourceModel();
em.setSelected(srcModel, { forceChange: 1 });
em.trigger(`${eventDrag}:end`, data);
@ -42,18 +43,18 @@ export default class ItemsView extends View {
});
}
this.sorter = this.opt.sorter || '';
// For the sorter
this.$el.data('collection', coll);
parent && this.$el.data('model', parent);
opt.parent && this.$el.data('model', opt.parent);
}
removeChildren(removed) {
removeChildren(removed: Component) {
// @ts-ignore
const view = removed.viewLayer;
if (!view) return;
view.remove();
removed.viewLayer = 0;
// @ts-ignore
delete removed.viewLayer;
}
/**
@ -62,7 +63,7 @@ export default class ItemsView extends View {
*
* @return Object
* */
addTo(model) {
addTo(model: Component) {
var i = this.collection.indexOf(model);
this.addToCollection(model, null, i);
}
@ -75,26 +76,26 @@ export default class ItemsView extends View {
*
* @return Object Object created
* */
addToCollection(model, fragmentEl, index) {
const { level, parentView, opt } = this;
const { ItemView } = opt;
addToCollection(model: Component, fragmentEl: DocumentFragment | null, index?: number) {
const { parentView, opt, config } = this;
const { ItemView, opened, module, level, sorter } = opt;
const fragment = fragmentEl || null;
const item = new ItemView({
ItemView,
level,
model,
parentView,
config: this.config,
sorter: this.sorter,
isCountable: this.isCountable,
opened: this.opt.opened,
config,
sorter,
opened,
module,
});
const rendered = item.render().el;
if (fragment) {
fragment.appendChild(rendered);
} else {
if (typeof index != 'undefined') {
if (typeof index !== 'undefined') {
var method = 'before';
// If the added model is the last of collection
// need to change the logic of append
@ -105,31 +106,20 @@ export default class ItemsView extends View {
// In case the added is new in the collection index will be -1
if (index < 0) {
this.$el.append(rendered);
} else this.$el.children().eq(index)[method](rendered);
} else {
// @ts-ignore
this.$el.children().eq(index)[method](rendered);
}
} else this.$el.append(rendered);
}
this.items.push(item);
return rendered;
}
remove() {
View.prototype.remove.apply(this, arguments);
remove(...args: []) {
View.prototype.remove.apply(this, args);
this.items.map(i => i.remove());
}
/**
* Check if the model could be count by the navigator
* @param {Object} model
* @return {Boolean}
* @private
*/
isCountable(model, hide) {
var type = model.get('type');
var tag = model.get('tagName');
if (((type == 'textnode' || tag == 'br') && hide) || !model.get('layerable')) {
return false;
}
return true;
return this;
}
render() {
@ -138,7 +128,7 @@ export default class ItemsView extends View {
el.innerHTML = '';
this.collection.each(model => this.addToCollection(model, frag));
el.appendChild(frag);
el.className = this.className;
el.className = this.className!;
return this;
}
}

2
src/pages/index.ts

@ -101,7 +101,7 @@ export default class PageManager extends ItemManagerModule<Config, Pages> {
* @private
*/
constructor(em: EditorModel) {
super(em, 'PageManager', new Pages([]), events);
super(em, 'PageManager', new Pages([], em), events);
bindAll(this, '_onPageChange');
const model = new Model({ _undo: true } as any);
this.model = model;

1
src/pages/model/Page.ts

@ -15,7 +15,6 @@ export default class Page extends Model {
constructor(props: any, opts: any = {}) {
super(props, opts);
const { config = {} } = opts;
const { em } = opts;
const defFrame: any = {};
this.em = em;

8
src/pages/model/Pages.ts

@ -1,11 +1,17 @@
import { Collection } from '../../common';
import EditorModel from '../../editor/model/Editor';
import Page from './Page';
export default class Pages extends Collection<Page> {
constructor(models: any) {
constructor(models: any, em: EditorModel) {
super(models);
this.on('reset', this.onReset);
this.on('remove', this.onRemove);
// @ts-ignore We need to inject `em` for pages created on reset from the Storage load
this.model = (props: {}, opts = {}) => {
return new Page(props, {...opts, em });
}
}
onReset(m: Page, opts?: { previousModels?: Pages }) {

2
src/selector_manager/index.ts

@ -69,7 +69,7 @@
* [Component]: component.html
* [CssRule]: css_rule.html
*
* @module SelectorManager
* @module Selectors
*/
import { isString, debounce, isObject, isArray } from 'underscore';

4
src/storage_manager/index.js

@ -15,7 +15,7 @@
* editor.on('storage:start', () => { ... });
*
* // Use the API
* const storageManager = editor.StorageManager;
* const storageManager = editor.Storage;
* storageManager.add(...);
* ```
*
@ -48,7 +48,7 @@
* * [store](#store)
* * [load](#load)
*
* @module StorageManager
* @module Storage
*/
import Module from '../abstract/moduleLegacy';

2
src/style_manager/model/Property.js

@ -373,7 +373,7 @@ export default class Property extends Model {
}
if (fn && hasValue) {
const fnParameter = fn === 'url' ? `'${value.replace(/'/g, '')}'` : value;
const fnParameter = fn === 'url' ? `'${value.replace(/'|"/g, '')}'` : value;
value = `${fn}(${fnParameter})`;
}

10
src/style_manager/model/PropertyFactory.js

@ -357,11 +357,11 @@ export default class PropertyFactory {
{
detached: true,
layerLabel: (l, { values }) => {
const repeat = values['background-repeat-sub'];
const pos = values['background-position-sub'];
const att = values['background-attachment-sub'];
const size = values['background-size-sub'];
return `${repeat} ${pos} ${att} ${size}`;
const repeat = values['background-repeat-sub'] || '';
const pos = values['background-position-sub'] || '';
const att = values['background-attachment-sub'] || '';
const size = values['background-size-sub'] || '';
return [repeat, pos, att, size].join(' ');
},
properties: this.__sub([
{ extend: 'background-image', id: 'background-image-sub' },

2
src/style_manager/model/PropertyStack.js

@ -320,7 +320,7 @@ export default class PropertyStack extends PropertyComposite {
// Update properties by layer value
values &&
this.getProperties().forEach(prop => {
const value = values[prop.getId()];
const value = values[prop.getId()] ?? '';
prop.__getFullValue() !== value && prop.upValue(value, { ...opts, __up: true });
});

2
src/trait_manager/view/TraitsView.js

@ -32,4 +32,4 @@ export default class TraitsView extends DomainViews {
}
}
TraitView.prototype.itemView = TraitView;
TraitsView.prototype.itemView = TraitView;

3
src/undo_manager/index.js

@ -306,11 +306,12 @@ export default () => {
* Get grouped undo manager stack.
* The difference between `getStack` is when you do multiple operations at a time,
* like appending multiple components:
* `editor.getWrapper().append(`<div>C1</div><div>C2</div>`);`
* `editor.getWrapper().append('<div>C1</div><div>C2</div>');`
* `getStack` will return a collection length of 2.
* `getStackGroup` instead will group them as a single operation (the first
* inserted component will be returned in the list) by returning an array length of 1.
* @return {Array}
* @private
*/
getStackGroup() {
const result = [];

22
src/utils/Resizer.js

@ -287,11 +287,12 @@ class Resizer {
};
// Listen events
var doc = this.getDocumentEl();
on(doc, 'mousemove', this.move);
on(doc, 'keydown', this.handleKeyDown);
on(doc, 'mouseup', this.stop);
isFunction(this.onStart) && this.onStart(e, { docs: doc, config, el, resizer });
const docs = this.getDocumentEl();
this.docs = docs;
on(docs, 'mousemove', this.move);
on(docs, 'keydown', this.handleKeyDown);
on(docs, 'mouseup', this.stop);
isFunction(this.onStart) && this.onStart(e, { docs, config, el, resizer });
this.toggleFrames(1);
this.move(e);
}
@ -339,13 +340,14 @@ class Resizer {
*/
stop(e) {
const config = this.opts;
var doc = this.getDocumentEl();
off(doc, 'mousemove', this.move);
off(doc, 'keydown', this.handleKeyDown);
off(doc, 'mouseup', this.stop);
const docs = this.docs || this.getDocumentEl();
off(docs, 'mousemove', this.move);
off(docs, 'keydown', this.handleKeyDown);
off(docs, 'mouseup', this.stop);
this.updateRect(1);
this.toggleFrames();
isFunction(this.onEnd) && this.onEnd(e, { docs: doc, config });
isFunction(this.onEnd) && this.onEnd(e, { docs, config });
delete this.docs;
}
/**

97
src/utils/mixins.js → src/utils/mixins.ts

@ -1,17 +1,19 @@
import { keys, isUndefined, isElement, isArray } from 'underscore';
export const isDef = value => typeof value !== 'undefined';
export const isDef = (value: any) => typeof value !== 'undefined';
export const hasWin = () => typeof window !== 'undefined';
export const getGlobal = () =>
typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : global;
export const toLowerCase = str => (str || '').toLowerCase();
export const toLowerCase = (str: string) => (str || '').toLowerCase();
const elProt = hasWin() ? window.Element.prototype : {};
// @ts-ignore
const matches = elProt.matches || elProt.webkitMatchesSelector || elProt.mozMatchesSelector || elProt.msMatchesSelector;
// @ts-ignore
export const getUiClass = (em, defCls) => {
const { stylePrefix, customUI } = em.getConfig();
return [customUI && `${stylePrefix}cui`, defCls].filter(i => i).join(' ');
@ -21,7 +23,7 @@ export const getUiClass = (em, defCls) => {
* Import styles asynchronously
* @param {String|Array<String>} styles
*/
const appendStyles = (styles, opts = {}) => {
const appendStyles = (styles: {}, opts: { unique?: boolean, prepand?: boolean } = {}) => {
const stls = isArray(styles) ? [...styles] : [styles];
if (stls.length) {
@ -55,8 +57,8 @@ const appendStyles = (styles, opts = {}) => {
* shallowDiff(a, b);
* // -> {baz: 2, faz: null, bar: ''};
*/
const shallowDiff = (objOrig, objNew) => {
const result = {};
const shallowDiff = (objOrig: Record<string, any>, objNew: Record<string, any>) => {
const result: Record<string, any> = {};
const keysNew = keys(objNew);
for (let prop in objOrig) {
@ -85,41 +87,35 @@ const shallowDiff = (objOrig, objNew) => {
return result;
};
/**
* @param {Object} el
* @param {string} ev
* @param {(ev: Event) => any} fn
* @param {Objec} opts
*/
const on = (el, ev, fn, opts = {}) => {
ev = ev.split(/\s+/);
const on = (el: HTMLElement|Window|Document | (Window|HTMLElement|Document)[], ev: string, fn: (ev: Event) => void, opts?: AddEventListenerOptions) => {
const evs = ev.split(/\s+/);
el = el instanceof Array ? el : [el];
for (let i = 0; i < ev.length; ++i) {
el.forEach(elem => elem && elem.addEventListener(ev[i], fn, opts));
for (let i = 0; i < evs.length; ++i) {
el.forEach(elem => elem && elem.addEventListener(evs[i], fn, opts));
}
};
const off = (el, ev, fn, opts) => {
ev = ev.split(/\s+/);
const off = (el: HTMLElement|Window|Document | (Window|HTMLElement|Document)[], ev: string, fn: (ev: Event) => void, opts?: AddEventListenerOptions) => {
const evs = ev.split(/\s+/);
el = el instanceof Array ? el : [el];
for (let i = 0; i < ev.length; ++i) {
el.forEach(elem => elem && elem.removeEventListener(ev[i], fn, opts));
for (let i = 0; i < evs.length; ++i) {
el.forEach(elem => elem && elem.removeEventListener(evs[i], fn, opts));
}
};
const getUnitFromValue = value => {
const getUnitFromValue = (value: any) => {
return value.replace(parseFloat(value), '');
};
const upFirst = value => value[0].toUpperCase() + value.toLowerCase().slice(1);
const upFirst = (value: string) => value[0].toUpperCase() + value.toLowerCase().slice(1);
const camelCase = value => {
const camelCase = (value: string) => {
return value.replace(/-./g, x => x[1].toUpperCase());
};
const normalizeFloat = (value, step = 1, valueDef = 0) => {
const normalizeFloat = (value: any, step = 1, valueDef = 0) => {
let stepDecimals = 0;
if (isNaN(value)) return valueDef;
value = parseFloat(value);
@ -132,7 +128,7 @@ const normalizeFloat = (value, step = 1, valueDef = 0) => {
return stepDecimals ? parseFloat(value.toFixed(stepDecimals)) : value;
};
const hasDnd = em => {
const hasDnd = (em: any) => {
return 'draggable' in document.createElement('i') && (em ? em.get('Config').nativeDnD : 1);
};
@ -141,10 +137,12 @@ const hasDnd = em => {
* @param {HTMLElement|Component} el Component or HTML element
* @return {HTMLElement}
*/
const getElement = el => {
const getElement = (el: HTMLElement) => {
if (isElement(el) || isTextNode(el)) {
return el;
// @ts-ignore
} else if (el && el.getEl) {
// @ts-ignore
return el.getEl();
}
};
@ -154,23 +152,23 @@ const getElement = el => {
* @param {HTMLElement} el
* @return {Boolean}
*/
const isTextNode = el => el && el.nodeType === 3;
const isTextNode = (el: HTMLElement) => el && el.nodeType === 3;
/**
* Check if element is a comment node
* @param {HTMLElement} el
* @return {Boolean}
*/
export const isCommentNode = el => el && el.nodeType === 8;
export const isCommentNode = (el: HTMLElement) => el && el.nodeType === 8;
/**
* Check if element is a comment node
* @param {HTMLElement} el
* @return {Boolean}
*/
export const isTaggableNode = el => el && !isTextNode(el) && !isCommentNode(el);
export const isTaggableNode = (el: HTMLElement) => el && !isTextNode(el) && !isCommentNode(el);
export const find = (arr, test) => {
export const find = (arr: any[], test: (item: any, i: number, arr: any[]) => boolean) => {
let result = null;
arr.some((el, i) => (test(el, i, arr) ? ((result = el), 1) : 0));
return result;
@ -186,7 +184,7 @@ export const escape = (str = '') => {
.replace(/`/g, '&#96;');
};
export const deepMerge = (...args) => {
export const deepMerge = (...args: Record<string, any>[]) => {
const target = { ...args[0] };
for (let i = 1; i < args.length; i++) {
@ -212,7 +210,7 @@ export const deepMerge = (...args) => {
* @param {HTMLElement|Component} el Component or HTML element
* @return {Component}
*/
const getModel = (el, $) => {
const getModel = (el: any, $?: any) => {
let model = el;
if (!$ && el && el.__cashData) {
model = el.__cashData.model;
@ -222,12 +220,7 @@ const getModel = (el, $) => {
return model;
};
/**
* Get DomRect for the el
* @param {any} el Component or HTML element
* @return {DOMRect}
*/
const getElRect = el => {
const getElRect = (el?: HTMLElement) => {
const def = {
top: 0,
left: 0,
@ -252,26 +245,28 @@ const getElRect = el => {
* @param {Event} ev
* @return {PointerEvent}
*/
const getPointerEvent = ev => (ev.touches && ev.touches[0] ? ev.touches[0] : ev);
const getPointerEvent = (ev: Event) =>
// @ts-ignore
(ev.touches && ev.touches[0] ? ev.touches[0] : ev);
/**
* Get cross-browser keycode
* @param {Event} ev
* @return {Number}
*/
const getKeyCode = ev => ev.which || ev.keyCode;
const getKeyChar = ev => String.fromCharCode(getKeyCode(ev));
const isEscKey = ev => getKeyCode(ev) === 27;
const isEnterKey = ev => getKeyCode(ev) === 13;
const isObject = val => val !== null && !Array.isArray(val) && typeof val === 'object';
const isEmptyObj = val => Object.keys(val).length <= 0;
const capitalize = str => str && str.charAt(0).toUpperCase() + str.substring(1);
const isComponent = obj => obj && obj.toHTML;
const isRule = obj => obj && obj.toCSS;
const getViewEl = el => el.__gjsv;
const setViewEl = (el, view) => {
const getKeyCode = (ev: KeyboardEvent) => ev.which || ev.keyCode;
const getKeyChar = (ev: KeyboardEvent) => String.fromCharCode(getKeyCode(ev));
const isEscKey = (ev: KeyboardEvent) => getKeyCode(ev) === 27;
const isEnterKey = (ev: KeyboardEvent) => getKeyCode(ev) === 13;
const isObject = (val: any) => val !== null && !Array.isArray(val) && typeof val === 'object';
const isEmptyObj = (val: Record<string, any>) => Object.keys(val).length <= 0;
const capitalize = (str: string) => str && str.charAt(0).toUpperCase() + str.substring(1);
const isComponent = (obj: any) => obj && obj.toHTML;
const isRule = (obj: any) => obj && obj.toCSS;
const getViewEl = (el: any) => el.__gjsv;
const setViewEl = (el: any, view: any) => {
el.__gjsv = view;
};

24
test/specs/navigator/view/ItemView.js

@ -1,9 +1,14 @@
import ItemView from 'navigator/view/ItemView';
import config from 'navigator/config/config';
import EditorModel from '../../../../src/editor/model/Editor';
describe('ItemView', () => {
let itemView, fakeModel, fakeModelStyle;
const isVisible = itemView => {
return itemView.module.isVisible(itemView.model);
};
beforeEach(() => {
fakeModelStyle = {};
@ -13,26 +18,25 @@ describe('ItemView', () => {
getStyle: jest.fn(() => fakeModelStyle),
};
const em = new EditorModel();
const module = em.get('LayerManager');
itemView = new ItemView({
model: fakeModel,
config: {
...config,
em: {
get: jest.fn(() => ({ stylePrefix: '' })),
},
},
module,
config: { ...config, em },
});
});
describe('.isVisible', () => {
it("should return `false` if the model's `style` object has a `display` property set to `none`, `true` otherwise", () => {
expect(itemView.isVisible()).toEqual(true);
expect(isVisible(itemView)).toEqual(true);
fakeModelStyle.display = '';
expect(itemView.isVisible()).toEqual(true);
expect(isVisible(itemView)).toEqual(true);
fakeModelStyle.display = 'none';
expect(itemView.isVisible()).toEqual(false);
expect(isVisible(itemView)).toEqual(false);
fakeModelStyle.display = 'block';
expect(itemView.isVisible()).toEqual(true);
expect(isVisible(itemView)).toEqual(true);
});
});
});

14
yarn.lock

@ -4651,8 +4651,9 @@ events@^3.0.0, events@^3.2.0:
resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz"
eventsource@^1.0.7:
version "1.1.0"
resolved "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz"
version "1.1.1"
resolved "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz#4544a35a57d7120fba4fa4c86cb4023b2c09df2f"
integrity sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==
dependencies:
original "^1.0.0"
@ -8318,7 +8319,8 @@ ordered-read-streams@^1.0.0:
original@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/original/-/original-1.0.2.tgz"
resolved "https://registry.npmjs.org/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
dependencies:
url-parse "^1.4.3"
@ -9281,7 +9283,8 @@ querystring@0.2.0:
querystringify@^2.1.1:
version "2.2.0"
resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz"
resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
queue-microtask@^1.2.2:
version "1.2.3"
@ -9725,7 +9728,8 @@ require-main-filename@^2.0.0:
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resolve-cwd@^2.0.0:
version "2.0.0"

Loading…
Cancel
Save